Skip to content

Commit

Permalink
Add waitForActionAttempt option
Browse files Browse the repository at this point in the history
  • Loading branch information
razor-x committed Nov 15, 2023
1 parent ac74dea commit 8cb5b8a
Show file tree
Hide file tree
Showing 25 changed files with 469 additions and 65 deletions.
28 changes: 17 additions & 11 deletions examples/unlock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { Builder, Command, Describe } from 'landlubber'
import {
isSeamActionAttemptFailedError,
isSeamActionAttemptTimeoutError,
resolveActionAttempt,
type LocksUnlockDoorResponse,
} from '@seamapi/http/connect'

import type { Handler } from './index.js'
Expand All @@ -24,23 +24,29 @@ export const builder: Builder = {
}

export const handler: Handler<Options> = async ({ deviceId, seam, logger }) => {
const actionAttempt = await seam.locks.unlockDoor({
device_id: deviceId,
})

try {
const sucessfulActionAttempt = await resolveActionAttempt(
actionAttempt,
seam,
const actionAttempt = await seam.locks.unlockDoor(
{
device_id: deviceId,
},
{ waitForActionAttempt: true },
)
logger.info({ actionAttempt: sucessfulActionAttempt }, 'unlocked')
logger.info({ actionAttempt }, 'unlocked')
} catch (err: unknown) {
if (isSeamActionAttemptFailedError<typeof actionAttempt>(err)) {
if (
isSeamActionAttemptFailedError<LocksUnlockDoorResponse['action_attempt']>(
err,
)
) {
logger.info({ err }, 'Could not unlock the door')
return
}

if (isSeamActionAttemptTimeoutError<typeof actionAttempt>(err)) {
if (
isSeamActionAttemptTimeoutError<
LocksUnlockDoorResponse['action_attempt']
>(err)
) {
logger.info({ err }, 'Door took too long to unlock')
return
}
Expand Down
51 changes: 51 additions & 0 deletions generate-routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,12 +256,21 @@ import {
type SeamHttpOptionsWithPersonalAccessToken,
} from 'lib/seam/connect/options.js'
import { parseOptions } from 'lib/seam/connect/parse-options.js'
import {
resolveActionAttempt,
type ResolveActionAttemptOptions,
} from 'lib/seam/connect/resolve-action-attempt.js'
${
namespace === 'client_sessions'
? ''
: "import { SeamHttpClientSessions } from './client-sessions.js'"
}
${
namespace === 'action_attempts'
? ''
: "import { SeamHttpActionAttempts } from './action-attempts.js'"
}
${subresources
.map((subresource) => renderSubresourceImport(subresource, namespace))
.join('\n')}
Expand Down Expand Up @@ -314,6 +323,7 @@ const renderClassMethod = ({
name,
namespace,
})},
${renderClassMethodOptions({ resource })}
): Promise<${
resource === null
? 'void'
Expand All @@ -330,10 +340,45 @@ const renderClassMethod = ({
requestFormat === 'params' ? 'params,' : ''
} ${requestFormat === 'body' ? 'data: body,' : ''}
})
${
resource === 'action_attempt'
? `if (waitForActionAttempt) { return resolveActionAttempt(data.${resource}, SeamHttpActionAttempts.fromClient(this.client), { timeout, pollingInterval }) }`
: ''
}
${resource === null ? '' : `return data.${resource}`}
}
`

const renderClassMethodOptions = ({
resource,
}: Pick<Endpoint, 'resource'>): string => {
if (resource === 'action_attempt') {
return `{ waitForActionAttempt = false, timeout = 5000, pollingInterval = 500 }: ${renderClassMethodOptionsTypeDef(
{ resource },
)} = {},`
}
return ''
}

const renderClassMethodOptionsType = ({
name,
namespace,
}: Pick<Endpoint, 'name' | 'namespace'>): string =>
[pascalCase(namespace), pascalCase(name), 'Options'].join('')

const renderClassMethodOptionsTypeDef = ({
resource,
}: Pick<Endpoint, 'resource'>): string => {
if (resource === 'action_attempt') {
return `
Partial<ResolveActionAttemptOptions> & {
waitForActionAttempt?: boolean
}
`
}
return 'never'
}

const renderSubresourceMethod = (
subresource: string,
namespace: string,
Expand All @@ -354,6 +399,7 @@ const renderEndpointExports = ({
name,
path,
namespace,
resource,
requestFormat,
}: Endpoint): string => `
export type ${renderRequestType({
Expand All @@ -364,6 +410,11 @@ export type ${renderRequestType({
export type ${renderResponseType({ name, namespace })}= SetNonNullable<
Required<RouteResponse<'${path}'>>
>
export type ${renderClassMethodOptionsType({
name,
namespace,
})} = ${renderClassMethodOptionsTypeDef({ resource })}
`

const renderRequestType = ({
Expand Down
8 changes: 8 additions & 0 deletions src/lib/seam/connect/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
export { SeamHttpInvalidTokenError } from './auth.js'
export * from './error-interceptor.js'
export * from './options.js'
export {
isSeamActionAttemptError,
isSeamActionAttemptFailedError,
isSeamActionAttemptTimeoutError,
SeamActionAttemptError,
SeamActionAttemptFailedError,
SeamActionAttemptTimeoutError,
} from './resolve-action-attempt.js'
export * from './routes/index.js'
export * from './seam-http.js'
export * from './seam-http-error.js'
Expand Down
25 changes: 12 additions & 13 deletions src/lib/seam/connect/resolve-action-attempt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,18 @@ import type {
FailedActionAttempt,
SuccessfulActionAttempt,
} from './action-attempt-types.js'
import type { SeamHttp } from './seam-http.js'
import type { SeamHttpActionAttempts } from './routes/index.js'

interface Options {
timeout?: number
pollingInterval?: number
export interface ResolveActionAttemptOptions {
timeout: number
pollingInterval: number
}

export const resolveActionAttempt = async <T extends ActionAttempt>(
actionAttemptOrPromise: T | Promise<T>,
seam: SeamHttp,
{ timeout = 5000, pollingInterval = 500 }: Options = {},
actionAttempt: T,
actionAttempts: SeamHttpActionAttempts,
{ timeout, pollingInterval }: ResolveActionAttemptOptions,
): Promise<SuccessfulActionAttempt<T>> => {
const actionAttempt = await actionAttemptOrPromise
let timeoutRef
const timeoutPromise = new Promise<SuccessfulActionAttempt<T>>(
(_resolve, reject) => {
Expand All @@ -27,7 +26,7 @@ export const resolveActionAttempt = async <T extends ActionAttempt>(

try {
return await Promise.race([
pollActionAttempt<T>(actionAttempt, seam, { pollingInterval }),
pollActionAttempt<T>(actionAttempt, actionAttempts, { pollingInterval }),
timeoutPromise,
])
} finally {
Expand All @@ -37,8 +36,8 @@ export const resolveActionAttempt = async <T extends ActionAttempt>(

const pollActionAttempt = async <T extends ActionAttempt>(
actionAttempt: T,
seam: SeamHttp,
options: Pick<Options, 'pollingInterval'>,
actionAttempts: SeamHttpActionAttempts,
options: Pick<ResolveActionAttemptOptions, 'pollingInterval'>,
): Promise<SuccessfulActionAttempt<T>> => {
if (isSuccessfulActionAttempt(actionAttempt)) {
return actionAttempt
Expand All @@ -50,13 +49,13 @@ const pollActionAttempt = async <T extends ActionAttempt>(

await new Promise((resolve) => setTimeout(resolve, options.pollingInterval))

const nextActionAttempt = await seam.actionAttempts.get({
const nextActionAttempt = await actionAttempts.get({
action_attempt_id: actionAttempt.action_attempt_id,
})

return await pollActionAttempt(
nextActionAttempt as unknown as T,
seam,
actionAttempts,
options,
)
}
Expand Down
12 changes: 12 additions & 0 deletions src/lib/seam/connect/routes/access-codes-unmanaged.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ export class SeamHttpAccessCodesUnmanaged {
data: body,
},
)

return data.access_code
}

Expand All @@ -160,6 +161,7 @@ export class SeamHttpAccessCodesUnmanaged {
method: 'post',
data: body,
})

return data.access_codes
}

Expand All @@ -179,30 +181,40 @@ export type AccessCodesUnmanagedConvertToManagedResponse = SetNonNullable<
Required<RouteResponse<'/access_codes/unmanaged/convert_to_managed'>>
>

export type AccessCodesUnmanagedConvertToManagedOptions = never

export type AccessCodesUnmanagedDeleteBody =
RouteRequestBody<'/access_codes/unmanaged/delete'>

export type AccessCodesUnmanagedDeleteResponse = SetNonNullable<
Required<RouteResponse<'/access_codes/unmanaged/delete'>>
>

export type AccessCodesUnmanagedDeleteOptions = never

export type AccessCodesUnmanagedGetParams =
RouteRequestBody<'/access_codes/unmanaged/get'>

export type AccessCodesUnmanagedGetResponse = SetNonNullable<
Required<RouteResponse<'/access_codes/unmanaged/get'>>
>

export type AccessCodesUnmanagedGetOptions = never

export type AccessCodesUnmanagedListParams =
RouteRequestBody<'/access_codes/unmanaged/list'>

export type AccessCodesUnmanagedListResponse = SetNonNullable<
Required<RouteResponse<'/access_codes/unmanaged/list'>>
>

export type AccessCodesUnmanagedListOptions = never

export type AccessCodesUnmanagedUpdateBody =
RouteRequestBody<'/access_codes/unmanaged/update'>

export type AccessCodesUnmanagedUpdateResponse = SetNonNullable<
Required<RouteResponse<'/access_codes/unmanaged/update'>>
>

export type AccessCodesUnmanagedUpdateOptions = never
22 changes: 22 additions & 0 deletions src/lib/seam/connect/routes/access-codes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ export class SeamHttpAccessCodes {
method: 'post',
data: body,
})

return data.access_code
}

Expand All @@ -145,6 +146,7 @@ export class SeamHttpAccessCodes {
method: 'post',
data: body,
})

return data.access_codes
}

Expand All @@ -166,6 +168,7 @@ export class SeamHttpAccessCodes {
data: body,
},
)

return data.generated_code
}

Expand All @@ -177,6 +180,7 @@ export class SeamHttpAccessCodes {
method: 'post',
data: body,
})

return data.access_code
}

Expand All @@ -188,6 +192,7 @@ export class SeamHttpAccessCodes {
method: 'post',
data: body,
})

return data.access_codes
}

Expand All @@ -200,6 +205,7 @@ export class SeamHttpAccessCodes {
method: 'post',
data: body,
})

return data.backup_access_code
}

Expand All @@ -218,47 +224,63 @@ export type AccessCodesCreateResponse = SetNonNullable<
Required<RouteResponse<'/access_codes/create'>>
>

export type AccessCodesCreateOptions = never

export type AccessCodesCreateMultipleBody =
RouteRequestBody<'/access_codes/create_multiple'>

export type AccessCodesCreateMultipleResponse = SetNonNullable<
Required<RouteResponse<'/access_codes/create_multiple'>>
>

export type AccessCodesCreateMultipleOptions = never

export type AccessCodesDeleteBody = RouteRequestBody<'/access_codes/delete'>

export type AccessCodesDeleteResponse = SetNonNullable<
Required<RouteResponse<'/access_codes/delete'>>
>

export type AccessCodesDeleteOptions = never

export type AccessCodesGenerateCodeBody =
RouteRequestBody<'/access_codes/generate_code'>

export type AccessCodesGenerateCodeResponse = SetNonNullable<
Required<RouteResponse<'/access_codes/generate_code'>>
>

export type AccessCodesGenerateCodeOptions = never

export type AccessCodesGetParams = RouteRequestBody<'/access_codes/get'>

export type AccessCodesGetResponse = SetNonNullable<
Required<RouteResponse<'/access_codes/get'>>
>

export type AccessCodesGetOptions = never

export type AccessCodesListParams = RouteRequestBody<'/access_codes/list'>

export type AccessCodesListResponse = SetNonNullable<
Required<RouteResponse<'/access_codes/list'>>
>

export type AccessCodesListOptions = never

export type AccessCodesPullBackupAccessCodeBody =
RouteRequestBody<'/access_codes/pull_backup_access_code'>

export type AccessCodesPullBackupAccessCodeResponse = SetNonNullable<
Required<RouteResponse<'/access_codes/pull_backup_access_code'>>
>

export type AccessCodesPullBackupAccessCodeOptions = never

export type AccessCodesUpdateBody = RouteRequestBody<'/access_codes/update'>

export type AccessCodesUpdateResponse = SetNonNullable<
Required<RouteResponse<'/access_codes/update'>>
>

export type AccessCodesUpdateOptions = never
Loading

0 comments on commit 8cb5b8a

Please sign in to comment.