From ef710614cc79faaf687f584cbd4669123babcba6 Mon Sep 17 00:00:00 2001 From: Andrii Balitskyi <10balian10@gmail.com> Date: Thu, 11 Apr 2024 13:23:52 +0200 Subject: [PATCH 01/11] Use x-fern-sdk-return-value to dynamically determine endpoint resources --- generate-routes.ts | 67 +++++++++++----------------------------------- 1 file changed, 16 insertions(+), 51 deletions(-) diff --git a/generate-routes.ts b/generate-routes.ts index 5c62872d..1a2ec2fc 100644 --- a/generate-routes.ts +++ b/generate-routes.ts @@ -11,6 +11,7 @@ const routeOutputPath = resolve('src', 'lib', 'seam', 'connect', 'routes') const routePaths = [ '/access_codes', + '/access_codes/simulate', '/access_codes/unmanaged', '/acs', '/acs/access_groups', @@ -32,6 +33,7 @@ const routePaths = [ '/networks', '/noise_sensors', '/noise_sensors/noise_thresholds', + '/noise_sensors/simulate', '/phones', '/phones/simulate', '/thermostats', @@ -62,36 +64,6 @@ const routePathSubresources: Partial< '/user_identities': ['enrollment_automations'], } -const ignoredEndpointPaths = [ - '/access_codes/simulate/create_unmanaged_access_code', - '/connect_webviews/view', - '/health', - '/health/get_health', - '/health/get_service_health', - '/health/service/[service_name]', - '/noise_sensors/simulate/trigger_noise_threshold', - '/workspaces/reset_sandbox', -] as const - -const endpointResources: Partial< - Record< - keyof typeof openapi.paths, - null | 'action_attempt' | 'noise_threshold' - > -> = { - // Set all ignored endpoints null to simplify code generation. - ...ignoredEndpointPaths.reduce((acc, cur) => ({ ...acc, [cur]: null }), {}), - - // These endpoints return a deprecated action attempt or resource. - '/access_codes/delete': null, - '/access_codes/unmanaged/delete': null, - '/access_codes/update': null, - '/noise_sensors/noise_thresholds/create': 'noise_threshold', - '/noise_sensors/noise_thresholds/delete': null, - '/noise_sensors/noise_thresholds/update': null, - '/thermostats/climate_setting_schedules/update': null, -} as const - interface Route { namespace: string endpoints: Endpoint[] @@ -117,14 +89,10 @@ interface ClassMeta { const createRoutes = (): Route[] => { const paths = Object.keys(openapi.paths) - const unmatchedEndpointPaths = paths - .filter( - (path) => - !routePaths.some((routePath) => isEndpointUnderRoute(path, routePath)), - ) - .filter( - (path) => !(ignoredEndpointPaths as unknown as string[]).includes(path), - ) + const unmatchedEndpointPaths = paths.filter( + (path) => + !routePaths.some((routePath) => isEndpointUnderRoute(path, routePath)), + ) if (unmatchedEndpointPaths.length > 0) { throw new Error( @@ -184,27 +152,28 @@ const deriveResource = ( endpointPath: string, method: Method, ): string | null => { - if (isEndpointResource(endpointPath)) { - return endpointResources[endpointPath] ?? null - } - if (isOpenapiPath(endpointPath)) { const spec = openapi.paths[endpointPath] const methodKey = method.toLowerCase() if (methodKey === 'post' && 'post' in spec) { - const response = spec.post.responses[200] - if (!('content' in response)) return null - return deriveResourceFromSchema( - response.content['application/json']?.schema?.properties ?? {}, - ) + const postSpec = spec.post + const openapiResponseKeyProp = 'x-fern-sdk-return-value' + const openapiEndpointResource = + openapiResponseKeyProp in postSpec + ? (postSpec[openapiResponseKeyProp] as string) + : null + + return openapiEndpointResource } if (methodKey === 'get' && 'get' in spec) { const response = spec.get.responses[200] + if (!('content' in response)) { throw new Error(`Missing resource for ${method} ${endpointPath}`) } + return deriveResourceFromSchema( response.content['application/json']?.schema?.properties ?? {}, ) @@ -226,10 +195,6 @@ const deriveSemanticMethod = (methods: string[]): Method => { throw new Error(`Could not find valid method in ${methods.join(', ')}`) } -const isEndpointResource = ( - key: string, -): key is keyof typeof endpointResources => key in endpointResources - const isOpenapiPath = (key: string): key is keyof typeof openapi.paths => key in openapi.paths From 43bb264269d66c6e0523e845e4f8d2efd7452bb2 Mon Sep 17 00:00:00 2001 From: Seam Bot Date: Thu, 11 Apr 2024 11:24:29 +0000 Subject: [PATCH 02/11] ci: Format code --- generate-routes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generate-routes.ts b/generate-routes.ts index 1a2ec2fc..95b73246 100644 --- a/generate-routes.ts +++ b/generate-routes.ts @@ -161,7 +161,7 @@ const deriveResource = ( const openapiResponseKeyProp = 'x-fern-sdk-return-value' const openapiEndpointResource = openapiResponseKeyProp in postSpec - ? (postSpec[openapiResponseKeyProp] as string) + ? (postSpec[openapiResponseKeyProp]) : null return openapiEndpointResource From 363438282ed97b02d9345a96eb1caba7a0fb6ce0 Mon Sep 17 00:00:00 2001 From: Seam Bot Date: Thu, 11 Apr 2024 11:25:03 +0000 Subject: [PATCH 03/11] ci: Format code --- generate-routes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generate-routes.ts b/generate-routes.ts index 95b73246..a01cc4a1 100644 --- a/generate-routes.ts +++ b/generate-routes.ts @@ -161,7 +161,7 @@ const deriveResource = ( const openapiResponseKeyProp = 'x-fern-sdk-return-value' const openapiEndpointResource = openapiResponseKeyProp in postSpec - ? (postSpec[openapiResponseKeyProp]) + ? postSpec[openapiResponseKeyProp] : null return openapiEndpointResource From 08a36f25e2e80a88ec3fe5ed50546df5afe879c8 Mon Sep 17 00:00:00 2001 From: Andrii Balitskyi <10balian10@gmail.com> Date: Fri, 12 Apr 2024 10:43:30 +0200 Subject: [PATCH 04/11] Move the declaration of openapiResponseKeyProp --- generate-routes.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/generate-routes.ts b/generate-routes.ts index a01cc4a1..4560a9b6 100644 --- a/generate-routes.ts +++ b/generate-routes.ts @@ -9,6 +9,8 @@ import { format, resolveConfig } from 'prettier' const rootClassPath = resolve('src', 'lib', 'seam', 'connect', 'seam-http.ts') const routeOutputPath = resolve('src', 'lib', 'seam', 'connect', 'routes') +const openapiResponseKeyProp = 'x-fern-sdk-return-value' + const routePaths = [ '/access_codes', '/access_codes/simulate', @@ -158,7 +160,6 @@ const deriveResource = ( if (methodKey === 'post' && 'post' in spec) { const postSpec = spec.post - const openapiResponseKeyProp = 'x-fern-sdk-return-value' const openapiEndpointResource = openapiResponseKeyProp in postSpec ? postSpec[openapiResponseKeyProp] From 662cfbb5d3d7047ea0f1ba3b967107f67fb665e8 Mon Sep 17 00:00:00 2001 From: Andrii Balitskyi <10balian10@gmail.com> Date: Fri, 12 Apr 2024 10:46:51 +0200 Subject: [PATCH 05/11] Update seamapi/types --- package-lock.json | 14 +++++--------- package.json | 2 +- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index f7424792..e4cbdfba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,7 @@ }, "devDependencies": { "@seamapi/fake-seam-connect": "^1.44.2", - "@seamapi/types": "1.138.0", + "@seamapi/types": "^1.157.1", "@types/eslint": "^8.44.2", "@types/node": "^20.8.10", "ava": "^5.0.1", @@ -1020,10 +1020,11 @@ } }, "node_modules/@seamapi/types": { - "version": "1.138.0", - "resolved": "https://registry.npmjs.org/@seamapi/types/-/types-1.138.0.tgz", - "integrity": "sha512-VQCGqwXYnFkZgDLQFidaIR5N5/TgEFQBG7WpBSMSWxemS5z8ztZLQbng4YlIdXovaMQjUzNG0LBooz52otdm4g==", + "version": "1.157.1", + "resolved": "https://registry.npmjs.org/@seamapi/types/-/types-1.157.1.tgz", + "integrity": "sha512-vIzhW6vtdg1rHUxv3VGlmRnc6jJvxPsMLTOuDFW+8F6y4xOyD2uMyTTjniQWD+MX8Uv4ngkbl6w/dcWsiosPyg==", "dev": true, + "license": "MIT", "engines": { "node": ">=18.12.0", "npm": ">= 9.0.0" @@ -1031,11 +1032,6 @@ "peerDependencies": { "type-fest": "^4.3.1", "zod": "^3.21.4" - }, - "peerDependenciesMeta": { - "type-fest": { - "optional": true - } } }, "node_modules/@seamapi/url-search-params-serializer": { diff --git a/package.json b/package.json index 593c91e1..812ea9f5 100644 --- a/package.json +++ b/package.json @@ -103,7 +103,7 @@ }, "devDependencies": { "@seamapi/fake-seam-connect": "^1.44.2", - "@seamapi/types": "1.138.0", + "@seamapi/types": "1.157.1", "@types/eslint": "^8.44.2", "@types/node": "^20.8.10", "ava": "^5.0.1", From d9b35f27e14cdf3c1d7e05a49f514c09554d6ee8 Mon Sep 17 00:00:00 2001 From: Seam Bot Date: Fri, 12 Apr 2024 08:48:03 +0000 Subject: [PATCH 06/11] ci: Generate code --- package-lock.json | 4 +- package.json | 2 +- .../connect/routes/access-codes-simulate.ts | 182 ++++++++++++++++++ .../seam/connect/routes/acs-credentials.ts | 16 +- .../seam/connect/routes/client-sessions.ts | 4 +- .../seam/connect/routes/connect-webviews.ts | 24 +-- src/lib/seam/connect/routes/index.ts | 2 + .../connect/routes/noise-sensors-simulate.ts | 176 +++++++++++++++++ .../seam/connect/routes/user-identities.ts | 7 +- src/lib/seam/connect/routes/workspaces.ts | 11 +- 10 files changed, 382 insertions(+), 46 deletions(-) create mode 100644 src/lib/seam/connect/routes/access-codes-simulate.ts create mode 100644 src/lib/seam/connect/routes/noise-sensors-simulate.ts diff --git a/package-lock.json b/package-lock.json index e4cbdfba..df3e965f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,7 @@ }, "devDependencies": { "@seamapi/fake-seam-connect": "^1.44.2", - "@seamapi/types": "^1.157.1", + "@seamapi/types": "1.157.1", "@types/eslint": "^8.44.2", "@types/node": "^20.8.10", "ava": "^5.0.1", @@ -47,7 +47,7 @@ "npm": ">= 9.0.0" }, "peerDependencies": { - "@seamapi/types": "^1.138.0", + "@seamapi/types": "^1.157.1", "type-fest": "^4.0.0" }, "peerDependenciesMeta": { diff --git a/package.json b/package.json index 812ea9f5..a570b4c1 100644 --- a/package.json +++ b/package.json @@ -84,7 +84,7 @@ "npm": ">= 9.0.0" }, "peerDependencies": { - "@seamapi/types": "^1.138.0", + "@seamapi/types": "^1.157.1", "type-fest": "^4.0.0" }, "peerDependenciesMeta": { diff --git a/src/lib/seam/connect/routes/access-codes-simulate.ts b/src/lib/seam/connect/routes/access-codes-simulate.ts new file mode 100644 index 00000000..4dafcde5 --- /dev/null +++ b/src/lib/seam/connect/routes/access-codes-simulate.ts @@ -0,0 +1,182 @@ +/* + * Automatically generated by generate-routes.ts. + * Do not edit this file or add other files to this directory. + */ + +import type { RouteRequestBody, RouteResponse } from '@seamapi/types/connect' +import type { SetNonNullable } from 'type-fest' + +import { + getAuthHeadersForClientSessionToken, + warnOnInsecureuserIdentifierKey, +} from 'lib/seam/connect/auth.js' +import { type Client, createClient } from 'lib/seam/connect/client.js' +import { + isSeamHttpOptionsWithApiKey, + isSeamHttpOptionsWithClient, + isSeamHttpOptionsWithClientSessionToken, + isSeamHttpOptionsWithConsoleSessionToken, + isSeamHttpOptionsWithPersonalAccessToken, + type SeamHttpFromPublishableKeyOptions, + SeamHttpInvalidOptionsError, + type SeamHttpOptions, + type SeamHttpOptionsWithApiKey, + type SeamHttpOptionsWithClient, + type SeamHttpOptionsWithClientSessionToken, + type SeamHttpOptionsWithConsoleSessionToken, + type SeamHttpOptionsWithPersonalAccessToken, + type SeamHttpRequestOptions, +} from 'lib/seam/connect/options.js' +import { + limitToSeamHttpRequestOptions, + parseOptions, +} from 'lib/seam/connect/parse-options.js' +import { SeamHttpRequest } from 'lib/seam/connect/seam-http-request.js' + +import { SeamHttpClientSessions } from './client-sessions.js' + +export class SeamHttpAccessCodesSimulate { + client: Client + readonly defaults: Required + + constructor(apiKeyOrOptions: string | SeamHttpOptions = {}) { + const options = parseOptions(apiKeyOrOptions) + this.client = 'client' in options ? options.client : createClient(options) + this.defaults = limitToSeamHttpRequestOptions(options) + } + + static fromClient( + client: SeamHttpOptionsWithClient['client'], + options: Omit = {}, + ): SeamHttpAccessCodesSimulate { + const constructorOptions = { ...options, client } + if (!isSeamHttpOptionsWithClient(constructorOptions)) { + throw new SeamHttpInvalidOptionsError('Missing client') + } + return new SeamHttpAccessCodesSimulate(constructorOptions) + } + + static fromApiKey( + apiKey: SeamHttpOptionsWithApiKey['apiKey'], + options: Omit = {}, + ): SeamHttpAccessCodesSimulate { + const constructorOptions = { ...options, apiKey } + if (!isSeamHttpOptionsWithApiKey(constructorOptions)) { + throw new SeamHttpInvalidOptionsError('Missing apiKey') + } + return new SeamHttpAccessCodesSimulate(constructorOptions) + } + + static fromClientSessionToken( + clientSessionToken: SeamHttpOptionsWithClientSessionToken['clientSessionToken'], + options: Omit< + SeamHttpOptionsWithClientSessionToken, + 'clientSessionToken' + > = {}, + ): SeamHttpAccessCodesSimulate { + const constructorOptions = { ...options, clientSessionToken } + if (!isSeamHttpOptionsWithClientSessionToken(constructorOptions)) { + throw new SeamHttpInvalidOptionsError('Missing clientSessionToken') + } + return new SeamHttpAccessCodesSimulate(constructorOptions) + } + + static async fromPublishableKey( + publishableKey: string, + userIdentifierKey: string, + options: SeamHttpFromPublishableKeyOptions = {}, + ): Promise { + warnOnInsecureuserIdentifierKey(userIdentifierKey) + const clientOptions = parseOptions({ ...options, publishableKey }) + if (isSeamHttpOptionsWithClient(clientOptions)) { + throw new SeamHttpInvalidOptionsError( + 'The client option cannot be used with SeamHttp.fromPublishableKey', + ) + } + const client = createClient(clientOptions) + const clientSessions = SeamHttpClientSessions.fromClient(client) + const { token } = await clientSessions.getOrCreate({ + user_identifier_key: userIdentifierKey, + }) + return SeamHttpAccessCodesSimulate.fromClientSessionToken(token, options) + } + + static fromConsoleSessionToken( + consoleSessionToken: SeamHttpOptionsWithConsoleSessionToken['consoleSessionToken'], + workspaceId: SeamHttpOptionsWithConsoleSessionToken['workspaceId'], + options: Omit< + SeamHttpOptionsWithConsoleSessionToken, + 'consoleSessionToken' | 'workspaceId' + > = {}, + ): SeamHttpAccessCodesSimulate { + const constructorOptions = { ...options, consoleSessionToken, workspaceId } + if (!isSeamHttpOptionsWithConsoleSessionToken(constructorOptions)) { + throw new SeamHttpInvalidOptionsError( + 'Missing consoleSessionToken or workspaceId', + ) + } + return new SeamHttpAccessCodesSimulate(constructorOptions) + } + + static fromPersonalAccessToken( + personalAccessToken: SeamHttpOptionsWithPersonalAccessToken['personalAccessToken'], + workspaceId: SeamHttpOptionsWithPersonalAccessToken['workspaceId'], + options: Omit< + SeamHttpOptionsWithPersonalAccessToken, + 'personalAccessToken' | 'workspaceId' + > = {}, + ): SeamHttpAccessCodesSimulate { + const constructorOptions = { ...options, personalAccessToken, workspaceId } + if (!isSeamHttpOptionsWithPersonalAccessToken(constructorOptions)) { + throw new SeamHttpInvalidOptionsError( + 'Missing personalAccessToken or workspaceId', + ) + } + return new SeamHttpAccessCodesSimulate(constructorOptions) + } + + async updateClientSessionToken( + clientSessionToken: SeamHttpOptionsWithClientSessionToken['clientSessionToken'], + ): Promise { + const { headers } = this.client.defaults + const authHeaders = getAuthHeadersForClientSessionToken({ + clientSessionToken, + }) + for (const key of Object.keys(authHeaders)) { + if (headers[key] == null) { + throw new Error( + 'Cannot update a clientSessionToken on a client created without a clientSessionToken', + ) + } + } + this.client.defaults.headers = { ...headers, ...authHeaders } + const clientSessions = SeamHttpClientSessions.fromClient(this.client) + await clientSessions.get() + } + + createUnmanagedAccessCode( + body?: AccessCodesSimulateCreateUnmanagedAccessCodeBody, + ): SeamHttpRequest< + AccessCodesSimulateCreateUnmanagedAccessCodeResponse, + 'access_code' + > { + return new SeamHttpRequest(this, { + path: '/access_codes/simulate/create_unmanaged_access_code', + method: 'post', + body, + responseKey: 'access_code', + }) + } +} + +export type AccessCodesSimulateCreateUnmanagedAccessCodeBody = + RouteRequestBody<'/access_codes/simulate/create_unmanaged_access_code'> + +export type AccessCodesSimulateCreateUnmanagedAccessCodeResponse = + SetNonNullable< + Required< + RouteResponse<'/access_codes/simulate/create_unmanaged_access_code'> + > + > + +export type AccessCodesSimulateCreateUnmanagedAccessCodeOptions = never diff --git a/src/lib/seam/connect/routes/acs-credentials.ts b/src/lib/seam/connect/routes/acs-credentials.ts index f8c03175..cc1a285e 100644 --- a/src/lib/seam/connect/routes/acs-credentials.ts +++ b/src/lib/seam/connect/routes/acs-credentials.ts @@ -154,14 +154,12 @@ export class SeamHttpAcsCredentials { await clientSessions.get() } - assign( - body?: AcsCredentialsAssignBody, - ): SeamHttpRequest { + assign(body?: AcsCredentialsAssignBody): SeamHttpRequest { return new SeamHttpRequest(this, { path: '/acs/credentials/assign', method: 'post', body, - responseKey: 'acs_credential', + responseKey: undefined, }) } @@ -209,23 +207,21 @@ export class SeamHttpAcsCredentials { unassign( body?: AcsCredentialsUnassignBody, - ): SeamHttpRequest { + ): SeamHttpRequest { return new SeamHttpRequest(this, { path: '/acs/credentials/unassign', method: 'post', body, - responseKey: 'acs_credential', + responseKey: undefined, }) } - update( - body?: AcsCredentialsUpdateBody, - ): SeamHttpRequest { + update(body?: AcsCredentialsUpdateBody): SeamHttpRequest { return new SeamHttpRequest(this, { path: '/acs/credentials/update', method: 'post', body, - responseKey: 'acs_credential', + responseKey: undefined, }) } } diff --git a/src/lib/seam/connect/routes/client-sessions.ts b/src/lib/seam/connect/routes/client-sessions.ts index 13912df8..75f366c1 100644 --- a/src/lib/seam/connect/routes/client-sessions.ts +++ b/src/lib/seam/connect/routes/client-sessions.ts @@ -196,12 +196,12 @@ export class SeamHttpClientSessions { grantAccess( body?: ClientSessionsGrantAccessBody, - ): SeamHttpRequest { + ): SeamHttpRequest { return new SeamHttpRequest(this, { path: '/client_sessions/grant_access', method: 'post', body, - responseKey: 'client_session', + responseKey: undefined, }) } diff --git a/src/lib/seam/connect/routes/connect-webviews.ts b/src/lib/seam/connect/routes/connect-webviews.ts index 99be2c74..b8e5c925 100644 --- a/src/lib/seam/connect/routes/connect-webviews.ts +++ b/src/lib/seam/connect/routes/connect-webviews.ts @@ -3,11 +3,7 @@ * Do not edit this file or add other files to this directory. */ -import type { - RouteRequestBody, - RouteRequestParams, - RouteResponse, -} from '@seamapi/types/connect' +import type { RouteRequestBody, RouteResponse } from '@seamapi/types/connect' import type { SetNonNullable } from 'type-fest' import { @@ -199,15 +195,6 @@ export class SeamHttpConnectWebviews { responseKey: 'connect_webviews', }) } - - view(params?: ConnectWebviewsViewParams): SeamHttpRequest { - return new SeamHttpRequest(this, { - path: '/connect_webviews/view', - method: 'get', - params, - responseKey: undefined, - }) - } } export type ConnectWebviewsCreateBody = @@ -244,12 +231,3 @@ export type ConnectWebviewsListResponse = SetNonNullable< > export type ConnectWebviewsListOptions = never - -export type ConnectWebviewsViewParams = - RouteRequestParams<'/connect_webviews/view'> - -export type ConnectWebviewsViewResponse = SetNonNullable< - Required> -> - -export type ConnectWebviewsViewOptions = never diff --git a/src/lib/seam/connect/routes/index.ts b/src/lib/seam/connect/routes/index.ts index 84ef19ae..5e545c39 100644 --- a/src/lib/seam/connect/routes/index.ts +++ b/src/lib/seam/connect/routes/index.ts @@ -1,4 +1,5 @@ export * from './access-codes.js' +export * from './access-codes-simulate.js' export * from './access-codes-unmanaged.js' export * from './acs.js' export * from './acs-access-groups.js' @@ -20,6 +21,7 @@ export * from './locks.js' export * from './networks.js' export * from './noise-sensors.js' export * from './noise-sensors-noise-thresholds.js' +export * from './noise-sensors-simulate.js' export * from './phones.js' export * from './phones-simulate.js' export * from './thermostats.js' diff --git a/src/lib/seam/connect/routes/noise-sensors-simulate.ts b/src/lib/seam/connect/routes/noise-sensors-simulate.ts new file mode 100644 index 00000000..3b600e14 --- /dev/null +++ b/src/lib/seam/connect/routes/noise-sensors-simulate.ts @@ -0,0 +1,176 @@ +/* + * Automatically generated by generate-routes.ts. + * Do not edit this file or add other files to this directory. + */ + +import type { RouteRequestBody, RouteResponse } from '@seamapi/types/connect' +import type { SetNonNullable } from 'type-fest' + +import { + getAuthHeadersForClientSessionToken, + warnOnInsecureuserIdentifierKey, +} from 'lib/seam/connect/auth.js' +import { type Client, createClient } from 'lib/seam/connect/client.js' +import { + isSeamHttpOptionsWithApiKey, + isSeamHttpOptionsWithClient, + isSeamHttpOptionsWithClientSessionToken, + isSeamHttpOptionsWithConsoleSessionToken, + isSeamHttpOptionsWithPersonalAccessToken, + type SeamHttpFromPublishableKeyOptions, + SeamHttpInvalidOptionsError, + type SeamHttpOptions, + type SeamHttpOptionsWithApiKey, + type SeamHttpOptionsWithClient, + type SeamHttpOptionsWithClientSessionToken, + type SeamHttpOptionsWithConsoleSessionToken, + type SeamHttpOptionsWithPersonalAccessToken, + type SeamHttpRequestOptions, +} from 'lib/seam/connect/options.js' +import { + limitToSeamHttpRequestOptions, + parseOptions, +} from 'lib/seam/connect/parse-options.js' +import { SeamHttpRequest } from 'lib/seam/connect/seam-http-request.js' + +import { SeamHttpClientSessions } from './client-sessions.js' + +export class SeamHttpNoiseSensorsSimulate { + client: Client + readonly defaults: Required + + constructor(apiKeyOrOptions: string | SeamHttpOptions = {}) { + const options = parseOptions(apiKeyOrOptions) + this.client = 'client' in options ? options.client : createClient(options) + this.defaults = limitToSeamHttpRequestOptions(options) + } + + static fromClient( + client: SeamHttpOptionsWithClient['client'], + options: Omit = {}, + ): SeamHttpNoiseSensorsSimulate { + const constructorOptions = { ...options, client } + if (!isSeamHttpOptionsWithClient(constructorOptions)) { + throw new SeamHttpInvalidOptionsError('Missing client') + } + return new SeamHttpNoiseSensorsSimulate(constructorOptions) + } + + static fromApiKey( + apiKey: SeamHttpOptionsWithApiKey['apiKey'], + options: Omit = {}, + ): SeamHttpNoiseSensorsSimulate { + const constructorOptions = { ...options, apiKey } + if (!isSeamHttpOptionsWithApiKey(constructorOptions)) { + throw new SeamHttpInvalidOptionsError('Missing apiKey') + } + return new SeamHttpNoiseSensorsSimulate(constructorOptions) + } + + static fromClientSessionToken( + clientSessionToken: SeamHttpOptionsWithClientSessionToken['clientSessionToken'], + options: Omit< + SeamHttpOptionsWithClientSessionToken, + 'clientSessionToken' + > = {}, + ): SeamHttpNoiseSensorsSimulate { + const constructorOptions = { ...options, clientSessionToken } + if (!isSeamHttpOptionsWithClientSessionToken(constructorOptions)) { + throw new SeamHttpInvalidOptionsError('Missing clientSessionToken') + } + return new SeamHttpNoiseSensorsSimulate(constructorOptions) + } + + static async fromPublishableKey( + publishableKey: string, + userIdentifierKey: string, + options: SeamHttpFromPublishableKeyOptions = {}, + ): Promise { + warnOnInsecureuserIdentifierKey(userIdentifierKey) + const clientOptions = parseOptions({ ...options, publishableKey }) + if (isSeamHttpOptionsWithClient(clientOptions)) { + throw new SeamHttpInvalidOptionsError( + 'The client option cannot be used with SeamHttp.fromPublishableKey', + ) + } + const client = createClient(clientOptions) + const clientSessions = SeamHttpClientSessions.fromClient(client) + const { token } = await clientSessions.getOrCreate({ + user_identifier_key: userIdentifierKey, + }) + return SeamHttpNoiseSensorsSimulate.fromClientSessionToken(token, options) + } + + static fromConsoleSessionToken( + consoleSessionToken: SeamHttpOptionsWithConsoleSessionToken['consoleSessionToken'], + workspaceId: SeamHttpOptionsWithConsoleSessionToken['workspaceId'], + options: Omit< + SeamHttpOptionsWithConsoleSessionToken, + 'consoleSessionToken' | 'workspaceId' + > = {}, + ): SeamHttpNoiseSensorsSimulate { + const constructorOptions = { ...options, consoleSessionToken, workspaceId } + if (!isSeamHttpOptionsWithConsoleSessionToken(constructorOptions)) { + throw new SeamHttpInvalidOptionsError( + 'Missing consoleSessionToken or workspaceId', + ) + } + return new SeamHttpNoiseSensorsSimulate(constructorOptions) + } + + static fromPersonalAccessToken( + personalAccessToken: SeamHttpOptionsWithPersonalAccessToken['personalAccessToken'], + workspaceId: SeamHttpOptionsWithPersonalAccessToken['workspaceId'], + options: Omit< + SeamHttpOptionsWithPersonalAccessToken, + 'personalAccessToken' | 'workspaceId' + > = {}, + ): SeamHttpNoiseSensorsSimulate { + const constructorOptions = { ...options, personalAccessToken, workspaceId } + if (!isSeamHttpOptionsWithPersonalAccessToken(constructorOptions)) { + throw new SeamHttpInvalidOptionsError( + 'Missing personalAccessToken or workspaceId', + ) + } + return new SeamHttpNoiseSensorsSimulate(constructorOptions) + } + + async updateClientSessionToken( + clientSessionToken: SeamHttpOptionsWithClientSessionToken['clientSessionToken'], + ): Promise { + const { headers } = this.client.defaults + const authHeaders = getAuthHeadersForClientSessionToken({ + clientSessionToken, + }) + for (const key of Object.keys(authHeaders)) { + if (headers[key] == null) { + throw new Error( + 'Cannot update a clientSessionToken on a client created without a clientSessionToken', + ) + } + } + this.client.defaults.headers = { ...headers, ...authHeaders } + const clientSessions = SeamHttpClientSessions.fromClient(this.client) + await clientSessions.get() + } + + triggerNoiseThreshold( + body?: NoiseSensorsSimulateTriggerNoiseThresholdBody, + ): SeamHttpRequest { + return new SeamHttpRequest(this, { + path: '/noise_sensors/simulate/trigger_noise_threshold', + method: 'post', + body, + responseKey: undefined, + }) + } +} + +export type NoiseSensorsSimulateTriggerNoiseThresholdBody = + RouteRequestBody<'/noise_sensors/simulate/trigger_noise_threshold'> + +export type NoiseSensorsSimulateTriggerNoiseThresholdResponse = SetNonNullable< + Required> +> + +export type NoiseSensorsSimulateTriggerNoiseThresholdOptions = never diff --git a/src/lib/seam/connect/routes/user-identities.ts b/src/lib/seam/connect/routes/user-identities.ts index bc0e8f04..c5cab4ec 100644 --- a/src/lib/seam/connect/routes/user-identities.ts +++ b/src/lib/seam/connect/routes/user-identities.ts @@ -228,15 +228,12 @@ export class SeamHttpUserIdentities { listAccessibleDevices( body?: UserIdentitiesListAccessibleDevicesParams, - ): SeamHttpRequest< - UserIdentitiesListAccessibleDevicesResponse, - 'accessible_devices' - > { + ): SeamHttpRequest { return new SeamHttpRequest(this, { path: '/user_identities/list_accessible_devices', method: 'post', body, - responseKey: 'accessible_devices', + responseKey: 'devices', }) } diff --git a/src/lib/seam/connect/routes/workspaces.ts b/src/lib/seam/connect/routes/workspaces.ts index c90cc1a3..92c5b5b8 100644 --- a/src/lib/seam/connect/routes/workspaces.ts +++ b/src/lib/seam/connect/routes/workspaces.ts @@ -189,12 +189,14 @@ export class SeamHttpWorkspaces { resetSandbox( body?: WorkspacesResetSandboxBody, - ): SeamHttpRequest { + options: Pick = {}, + ): SeamHttpRequest { return new SeamHttpRequest(this, { path: '/workspaces/reset_sandbox', method: 'post', body, - responseKey: undefined, + responseKey: 'action_attempt', + options, }) } } @@ -230,4 +232,7 @@ export type WorkspacesResetSandboxResponse = SetNonNullable< Required> > -export type WorkspacesResetSandboxOptions = never +export type WorkspacesResetSandboxOptions = Pick< + SeamHttpRequestOptions, + 'waitForActionAttempt' +> From 98b8d5de0ff2d05ebda402bd9c7c8a920bd50933 Mon Sep 17 00:00:00 2001 From: Andrii Balitskyi <10balian10@gmail.com> Date: Mon, 15 Apr 2024 14:42:43 +0200 Subject: [PATCH 07/11] Add /access_codes/simulate and /noise_sensors/simulate to routePathSubresources --- generate-routes.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/generate-routes.ts b/generate-routes.ts index c8b798d6..e6c9cf7b 100644 --- a/generate-routes.ts +++ b/generate-routes.ts @@ -71,7 +71,7 @@ const routePaths = [ const routePathSubresources: Partial< Record<(typeof routePaths)[number], string[]> > = { - '/access_codes': ['unmanaged'], + '/access_codes': ['unmanaged', 'simulate'], '/acs': [ 'access_groups', 'credential_pools', @@ -83,7 +83,7 @@ const routePathSubresources: Partial< ], '/phones': ['simulate'], '/devices': ['unmanaged', 'simulate'], - '/noise_sensors': ['noise_thresholds'], + '/noise_sensors': ['noise_thresholds', 'simulate'], '/thermostats': ['climate_setting_schedules'], '/user_identities': ['enrollment_automations'], } From 24f186f60a9981a8abe3819703bab42c18baf215 Mon Sep 17 00:00:00 2001 From: Seam Bot Date: Mon, 15 Apr 2024 12:43:39 +0000 Subject: [PATCH 08/11] ci: Generate code --- src/lib/seam/connect/routes/access-codes.ts | 5 +++++ src/lib/seam/connect/routes/noise-sensors.ts | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/src/lib/seam/connect/routes/access-codes.ts b/src/lib/seam/connect/routes/access-codes.ts index 968bcab4..217a6df7 100644 --- a/src/lib/seam/connect/routes/access-codes.ts +++ b/src/lib/seam/connect/routes/access-codes.ts @@ -33,6 +33,7 @@ import { } from 'lib/seam/connect/parse-options.js' import { SeamHttpRequest } from 'lib/seam/connect/seam-http-request.js' +import { SeamHttpAccessCodesSimulate } from './access-codes-simulate.js' import { SeamHttpAccessCodesUnmanaged } from './access-codes-unmanaged.js' import { SeamHttpClientSessions } from './client-sessions.js' @@ -159,6 +160,10 @@ export class SeamHttpAccessCodes { return SeamHttpAccessCodesUnmanaged.fromClient(this.client, this.defaults) } + get simulate(): SeamHttpAccessCodesSimulate { + return SeamHttpAccessCodesSimulate.fromClient(this.client, this.defaults) + } + create( body?: AccessCodesCreateBody, ): SeamHttpRequest { diff --git a/src/lib/seam/connect/routes/noise-sensors.ts b/src/lib/seam/connect/routes/noise-sensors.ts index 797a9876..598c6d90 100644 --- a/src/lib/seam/connect/routes/noise-sensors.ts +++ b/src/lib/seam/connect/routes/noise-sensors.ts @@ -31,6 +31,7 @@ import { import { SeamHttpClientSessions } from './client-sessions.js' import { SeamHttpNoiseSensorsNoiseThresholds } from './noise-sensors-noise-thresholds.js' +import { SeamHttpNoiseSensorsSimulate } from './noise-sensors-simulate.js' export class SeamHttpNoiseSensors { client: Client @@ -157,4 +158,8 @@ export class SeamHttpNoiseSensors { this.defaults, ) } + + get simulate(): SeamHttpNoiseSensorsSimulate { + return SeamHttpNoiseSensorsSimulate.fromClient(this.client, this.defaults) + } } From bbb5d9ae73e06b8e019ffcc54777f4ee3f1f495c Mon Sep 17 00:00:00 2001 From: Andrii Balitskyi <10balian10@gmail.com> Date: Mon, 15 Apr 2024 14:54:15 +0200 Subject: [PATCH 09/11] Remove deriveResourceFromSchema --- generate-routes.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/generate-routes.ts b/generate-routes.ts index e6c9cf7b..1ea27707 100644 --- a/generate-routes.ts +++ b/generate-routes.ts @@ -197,18 +197,19 @@ const deriveResource = ( throw new Error(`Missing resource for ${method} ${endpointPath}`) } - return deriveResourceFromSchema( - response.content['application/json']?.schema?.properties ?? {}, + const responseSchemaProperties = + response.content['application/json']?.schema?.properties ?? {} + const endpointResource = Object.keys(responseSchemaProperties).find( + (key) => key !== 'ok', ) + + return endpointResource ?? null } } throw new Error(`Could not derive resource for ${method} ${endpointPath}`) } -const deriveResourceFromSchema = (properties: object): string | null => - Object.keys(properties).filter((key) => key !== 'ok')[0] ?? null - const deriveSemanticMethod = (methods: string[]): Method => { // UPSTREAM: This should return GET before POST. // Blocked on https://github.com/seamapi/nextlove/issues/117 From 388cb3025022a84534d54c894c635148bef29e8b Mon Sep 17 00:00:00 2001 From: Seam Bot Date: Mon, 15 Apr 2024 12:58:36 +0000 Subject: [PATCH 10/11] ci: Generate code --- src/lib/seam/connect/routes/access-codes-simulate.ts | 2 +- src/lib/seam/connect/routes/noise-sensors-simulate.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/seam/connect/routes/access-codes-simulate.ts b/src/lib/seam/connect/routes/access-codes-simulate.ts index 4dafcde5..f87010f0 100644 --- a/src/lib/seam/connect/routes/access-codes-simulate.ts +++ b/src/lib/seam/connect/routes/access-codes-simulate.ts @@ -4,7 +4,6 @@ */ import type { RouteRequestBody, RouteResponse } from '@seamapi/types/connect' -import type { SetNonNullable } from 'type-fest' import { getAuthHeadersForClientSessionToken, @@ -32,6 +31,7 @@ import { parseOptions, } from 'lib/seam/connect/parse-options.js' import { SeamHttpRequest } from 'lib/seam/connect/seam-http-request.js' +import type { SetNonNullable } from 'lib/types.js' import { SeamHttpClientSessions } from './client-sessions.js' diff --git a/src/lib/seam/connect/routes/noise-sensors-simulate.ts b/src/lib/seam/connect/routes/noise-sensors-simulate.ts index 3b600e14..73b3ca94 100644 --- a/src/lib/seam/connect/routes/noise-sensors-simulate.ts +++ b/src/lib/seam/connect/routes/noise-sensors-simulate.ts @@ -4,7 +4,6 @@ */ import type { RouteRequestBody, RouteResponse } from '@seamapi/types/connect' -import type { SetNonNullable } from 'type-fest' import { getAuthHeadersForClientSessionToken, @@ -32,6 +31,7 @@ import { parseOptions, } from 'lib/seam/connect/parse-options.js' import { SeamHttpRequest } from 'lib/seam/connect/seam-http-request.js' +import type { SetNonNullable } from 'lib/types.js' import { SeamHttpClientSessions } from './client-sessions.js' From e61b1cd74912486723c506a8b4190705529989a5 Mon Sep 17 00:00:00 2001 From: Andrii Balitskyi <10balian10@gmail.com> Date: Wed, 17 Apr 2024 10:13:49 +0200 Subject: [PATCH 11/11] Use resource deriving func only for GET response schemas --- generate-routes.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/generate-routes.ts b/generate-routes.ts index 6d9e7d8c..c8a9cbb8 100644 --- a/generate-routes.ts +++ b/generate-routes.ts @@ -197,19 +197,20 @@ const deriveResource = ( throw new Error(`Missing resource for ${method} ${endpointPath}`) } - const responseSchemaProperties = - response.content['application/json']?.schema?.properties ?? {} - const endpointResource = Object.keys(responseSchemaProperties).find( - (key) => key !== 'ok', + return deriveResourceFromSchemaForGetRequest( + response.content['application/json']?.schema?.properties ?? {}, ) - - return endpointResource ?? null } } throw new Error(`Could not derive resource for ${method} ${endpointPath}`) } +const deriveResourceFromSchemaForGetRequest = ( + properties: object, +): string | null => + Object.keys(properties).filter((key) => key !== 'ok')[0] ?? null + const deriveSemanticMethod = (methods: string[]): Method => { // UPSTREAM: This should return GET before POST. // Blocked on https://github.com/seamapi/nextlove/issues/117