From 53aec36a31eae80b977b89cbaed1f68f8e2c96cb Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 14 Aug 2026 17:02:07 +0000 Subject: [PATCH 1/2] fix: Make SeamEvent a discriminated union Generate the SeamEvent union from the blueprint's top-level events collection only, excluding the generic event resource whose all-optional properties (including event_type) prevented TypeScript from narrowing the union on event_type and made event_id optional everywhere. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01L1miDUhtRigVPj3ZmPPU8y --- codegen/lib/layouts/resources.ts | 13 ++- src/lib/resources/event.ts | 137 +------------------------------ 2 files changed, 15 insertions(+), 135 deletions(-) diff --git a/codegen/lib/layouts/resources.ts b/codegen/lib/layouts/resources.ts index 02326a62..f7e4847e 100644 --- a/codegen/lib/layouts/resources.ts +++ b/codegen/lib/layouts/resources.ts @@ -25,8 +25,19 @@ export const getResourceTypeName = (resourceType: string): string => export const getResourceLayoutContexts = ( blueprint: Blueprint, ): ResourceLayoutContext[] => { + // Events and action attempts are discriminated unions built from the + // top-level blueprint collections. The generic resources sharing those + // resource types have all-optional properties, and including one as a union + // member would defeat narrowing on the discriminant. + const discriminatedResourceTypes = new Set( + [...blueprint.events, ...blueprint.actionAttempts].map( + ({ resourceType }) => resourceType, + ), + ) const resources = [ - ...blueprint.resources, + ...blueprint.resources.filter( + ({ resourceType }) => !discriminatedResourceTypes.has(resourceType), + ), ...blueprint.events, ...blueprint.actionAttempts, ] diff --git a/src/lib/resources/event.ts b/src/lib/resources/event.ts index c554cd97..b6368ba9 100644 --- a/src/lib/resources/event.ts +++ b/src/lib/resources/event.ts @@ -3,141 +3,10 @@ * Do not edit this file. */ +/** + * An [access code](https://docs.seam.co/low-level-apis/smart-locks/access-codes) was created. + */ export type SeamEvent = - | { - /** - * Date and time at which the event was created. - */ - created_at?: string | undefined - /** - * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. - */ - event_description?: string | undefined - /** - * ID of the event. - */ - event_id?: string | undefined - - event_type?: - | 'access_code.created' - | 'access_code.changed' - | 'access_code.name_changed' - | 'access_code.code_changed' - | 'access_code.time_frame_changed' - | 'access_code.mutations_requested' - | 'access_code.scheduled_on_device' - | 'access_code.set_on_device' - | 'access_code.removed_from_device' - | 'access_code.delay_in_setting_on_device' - | 'access_code.failed_to_set_on_device' - | 'access_code.deleted' - | 'access_code.delay_in_removing_from_device' - | 'access_code.failed_to_remove_from_device' - | 'access_code.modified_external_to_seam' - | 'access_code.deleted_external_to_seam' - | 'access_code.backup_access_code_pulled' - | 'access_code.unmanaged.converted_to_managed' - | 'access_code.unmanaged.failed_to_convert_to_managed' - | 'access_code.unmanaged.created' - | 'access_code.unmanaged.removed' - | 'access_grant.created' - | 'access_grant.deleted' - | 'access_grant.access_granted_to_all_doors' - | 'access_grant.access_granted_to_door' - | 'access_grant.access_to_door_lost' - | 'access_grant.access_times_changed' - | 'access_grant.could_not_create_requested_access_methods' - | 'access_method.issued' - | 'access_method.revoked' - | 'access_method.card_encoding_required' - | 'access_method.deleted' - | 'access_method.reissued' - | 'access_method.created' - | 'access_method.delay_in_issuing' - | 'access_method.failed_to_issue' - | 'acs_system.connected' - | 'acs_system.added' - | 'acs_system.disconnected' - | 'acs_credential.deleted' - | 'acs_credential.issued' - | 'acs_credential.reissued' - | 'acs_credential.invalidated' - | 'acs_user.created' - | 'acs_user.deleted' - | 'acs_encoder.added' - | 'acs_encoder.removed' - | 'acs_access_group.deleted' - | 'acs_entrance.added' - | 'acs_entrance.removed' - | 'client_session.deleted' - | 'connected_account.connected' - | 'connected_account.created' - | 'connected_account.successful_login' - | 'connected_account.disconnected' - | 'connected_account.completed_first_sync' - | 'connected_account.deleted' - | 'connected_account.completed_first_sync_after_reconnection' - | 'connected_account.reauthorization_requested' - | 'action_attempt.lock_door.succeeded' - | 'action_attempt.lock_door.failed' - | 'action_attempt.unlock_door.succeeded' - | 'action_attempt.unlock_door.failed' - | 'action_attempt.simulate_keypad_code_entry.succeeded' - | 'action_attempt.simulate_keypad_code_entry.failed' - | 'action_attempt.simulate_manual_lock_via_keypad.succeeded' - | 'action_attempt.simulate_manual_lock_via_keypad.failed' - | 'connect_webview.login_succeeded' - | 'connect_webview.login_failed' - | 'device.connected' - | 'device.added' - | 'device.converted_to_unmanaged' - | 'device.unmanaged.converted_to_managed' - | 'device.unmanaged.connected' - | 'device.disconnected' - | 'device.unmanaged.disconnected' - | 'device.tampered' - | 'device.low_battery' - | 'device.battery_status_changed' - | 'device.removed' - | 'device.deleted' - | 'device.third_party_integration_detected' - | 'device.third_party_integration_no_longer_detected' - | 'device.salto.privacy_mode_activated' - | 'device.salto.privacy_mode_deactivated' - | 'device.connection_became_flaky' - | 'device.connection_stabilized' - | 'device.error.subscription_required' - | 'device.error.subscription_required.resolved' - | 'device.accessory_keypad_connected' - | 'device.accessory_keypad_disconnected' - | 'noise_sensor.noise_threshold_triggered' - | 'lock.locked' - | 'lock.unlocked' - | 'lock.access_denied' - | 'thermostat.climate_preset_activated' - | 'thermostat.manually_adjusted' - | 'thermostat.temperature_threshold_exceeded' - | 'thermostat.temperature_threshold_no_longer_exceeded' - | 'thermostat.temperature_reached_set_point' - | 'thermostat.temperature_changed' - | 'device.name_changed' - | 'camera.activated' - | 'device.doorbell_rang' - | 'enrollment_automation.deleted' - | 'phone.deactivated' - | 'space.device_membership_changed' - | 'space.created' - | 'space.deleted' - | undefined - /** - * Date and time at which the event occurred. - */ - occurred_at?: string | undefined - /** - * ID of the workspace associated with the event. - */ - workspace_id?: string | undefined - } | { /** * ID of the affected access code. From e8bdf10df73de83ae8b5de247708ce1c1a1835fa Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 14 Aug 2026 21:49:15 +0000 Subject: [PATCH 2/2] fix: Widen discriminated resource type set for lookup Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01L1miDUhtRigVPj3ZmPPU8y --- codegen/lib/layouts/resources.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codegen/lib/layouts/resources.ts b/codegen/lib/layouts/resources.ts index f7e4847e..718edb96 100644 --- a/codegen/lib/layouts/resources.ts +++ b/codegen/lib/layouts/resources.ts @@ -29,7 +29,7 @@ export const getResourceLayoutContexts = ( // top-level blueprint collections. The generic resources sharing those // resource types have all-optional properties, and including one as a union // member would defeat narrowing on the discriminant. - const discriminatedResourceTypes = new Set( + const discriminatedResourceTypes = new Set( [...blueprint.events, ...blueprint.actionAttempts].map( ({ resourceType }) => resourceType, ),