Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add default client request options #23

Merged
merged 6 commits into from
Nov 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,9 @@ Some asynchronous operations, e.g., unlocking a door, return an [action attempt]
Seam tracks the progress of requested operation and updates the action attempt.

To make working with action attempts more convenient for applications,
this library provides the `waitForActionAttempt` option:
this library provides the `waitForActionAttempt` option.

Pass the option per-request,

```ts
await seam.locks.unlockDoor(
Expand All @@ -162,6 +164,17 @@ await seam.locks.unlockDoor(
)
```

or set the default option for the client:

```ts
const seam = new SeamHttp({
apiKey: 'your-api-key',
waitForActionAttempt: true,
})

await seam.locks.unlockDoor({ device_id })
```

Using the `waitForActionAttempt` option:

- Polls the action attempt up to the `timeout`
Expand Down
27 changes: 14 additions & 13 deletions generate-routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,11 +254,14 @@ import {
type SeamHttpOptionsWithClientSessionToken,
type SeamHttpOptionsWithConsoleSessionToken,
type SeamHttpOptionsWithPersonalAccessToken,
type SeamHttpRequestOptions,
} from 'lib/seam/connect/options.js'
import { parseOptions } from 'lib/seam/connect/parse-options.js'
import {
limitToSeamHttpRequestOptions,
parseOptions
} from 'lib/seam/connect/parse-options.js'
import {
resolveActionAttempt,
type ResolveActionAttemptOptions,
} from 'lib/seam/connect/resolve-action-attempt.js'

${
Expand Down Expand Up @@ -291,6 +294,7 @@ const renderClass = (
`
export class SeamHttp${pascalCase(namespace)} {
client: Client
readonly defaults: Required<SeamHttpRequestOptions>

${constructors
.replaceAll(': SeamHttp ', `: SeamHttp${pascalCase(namespace)} `)
Expand Down Expand Up @@ -342,10 +346,11 @@ const renderClassMethod = ({
})
${
resource === 'action_attempt'
? `if (waitForActionAttempt != null && waitForActionAttempt !== false) {
? `const waitForActionAttempt = options.waitForActionAttempt ?? this.defaults.waitForActionAttempt
if (waitForActionAttempt !== false) {
return resolveActionAttempt(
data.${resource},
SeamHttpActionAttempts.fromClient(this.client),
SeamHttpActionAttempts.fromClient(this.client, { ...this.defaults, waitForActionAttempt: false }),
typeof waitForActionAttempt === 'boolean' ? {} : waitForActionAttempt,
)
}`
Expand All @@ -359,9 +364,9 @@ const renderClassMethodOptions = ({
resource,
}: Pick<Endpoint, 'resource'>): string => {
if (resource === 'action_attempt') {
return `{ waitForActionAttempt = false }: ${renderClassMethodOptionsTypeDef(
{ resource },
)} = {},`
return `options: ${renderClassMethodOptionsTypeDef({
resource,
})} = {},`
}
return ''
}
Expand All @@ -376,11 +381,7 @@ const renderClassMethodOptionsTypeDef = ({
resource,
}: Pick<Endpoint, 'resource'>): string => {
if (resource === 'action_attempt') {
return `
{
waitForActionAttempt?: boolean | Partial<ResolveActionAttemptOptions>
}
`
return "Pick<SeamHttpRequestOptions, 'waitForActionAttempt'>"
}
return 'never'
}
Expand All @@ -394,7 +395,7 @@ const renderSubresourceMethod = (
)}${pascalCase(subresource)} {
return SeamHttp${pascalCase(namespace)}${pascalCase(
subresource,
)}.fromClient(this.client)
)}.fromClient(this.client, this.defaults)
}
`

Expand Down
3 changes: 0 additions & 3 deletions src/lib/seam/connect/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,11 @@ export type Client = AxiosInstance
export interface ClientOptions {
axiosOptions?: AxiosRequestConfig
axiosRetryOptions?: AxiosRetryConfig
client?: Client
}

type AxiosRetryConfig = Parameters<AxiosRetry>[1]

export const createClient = (options: ClientOptions): AxiosInstance => {
if (options.client != null) return options.client

const client = axios.create({
paramsSerializer,
...options.axiosOptions,
Expand Down
15 changes: 11 additions & 4 deletions src/lib/seam/connect/options.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import type { Client, ClientOptions } from './client.js'
import { isSeamHttpRequestOption } from './parse-options.js'
import type { ResolveActionAttemptOptions } from './resolve-action-attempt.js'

export type SeamHttpMultiWorkspaceOptions =
| SeamHttpMultiWorkspaceOptionsWithClient
Expand All @@ -13,16 +15,21 @@ export type SeamHttpOptions =
| SeamHttpOptionsWithConsoleSessionToken
| SeamHttpOptionsWithPersonalAccessToken

interface SeamHttpCommonOptions extends ClientOptions {
interface SeamHttpCommonOptions extends ClientOptions, SeamHttpRequestOptions {
endpoint?: string
}

export interface SeamHttpRequestOptions {
waitForActionAttempt?: boolean | ResolveActionAttemptOptions
}

export interface SeamHttpFromPublishableKeyOptions
extends SeamHttpCommonOptions {}

export interface SeamHttpOptionsFromEnv extends SeamHttpCommonOptions {}

export interface SeamHttpMultiWorkspaceOptionsWithClient {
export interface SeamHttpMultiWorkspaceOptionsWithClient
extends SeamHttpRequestOptions {
client: Client
}

Expand All @@ -31,7 +38,7 @@ export const isSeamHttpMultiWorkspaceOptionsWithClient = (
): options is SeamHttpMultiWorkspaceOptionsWithClient =>
isSeamHttpOptionsWithClient(options)

export interface SeamHttpOptionsWithClient {
export interface SeamHttpOptionsWithClient extends SeamHttpRequestOptions {
client: Client
}

Expand All @@ -42,7 +49,7 @@ export const isSeamHttpOptionsWithClient = (
if (options.client == null) return false

const keys = Object.keys(options).filter((k) => k !== 'client')
if (keys.length > 0) {
if (keys.filter((k) => !isSeamHttpRequestOption(k)).length > 0) {
throw new SeamHttpInvalidOptionsError(
`The client option cannot be used with any other option, but received: ${keys.join(
', ',
Expand Down
47 changes: 43 additions & 4 deletions src/lib/seam/connect/parse-options.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import version from 'lib/version.js'

import { getAuthHeaders } from './auth.js'
import type { ClientOptions } from './client.js'
import type { Client, ClientOptions } from './client.js'
import {
isSeamHttpMultiWorkspaceOptionsWithClient,
isSeamHttpOptionsWithClient,
isSeamHttpOptionsWithClientSessionToken,
type SeamHttpMultiWorkspaceOptions,
type SeamHttpOptions,
type SeamHttpRequestOptions,
} from './options.js'

const defaultEndpoint = 'https://connect.getseam.com'
Expand All @@ -21,15 +22,20 @@ export type Options =
| SeamHttpMultiWorkspaceOptions
| (SeamHttpOptions & { publishableKey?: string })

type ParsedOptions = Required<
(ClientOptions | { client: Client }) & SeamHttpRequestOptions
>

export const parseOptions = (
apiKeyOrOptions: string | Options,
): ClientOptions => {
): ParsedOptions => {
const options = getNormalizedOptions(apiKeyOrOptions)

if (isSeamHttpOptionsWithClient(options)) return options
if (isSeamHttpMultiWorkspaceOptionsWithClient(options)) return options

return {
...options,
axiosOptions: {
baseURL: options.endpoint ?? getEndpointFromEnv() ?? defaultEndpoint,
withCredentials: isSeamHttpOptionsWithClientSessionToken(options),
Expand All @@ -48,20 +54,30 @@ export const parseOptions = (

const getNormalizedOptions = (
apiKeyOrOptions: string | Options,
): SeamHttpOptions => {
): SeamHttpOptions & Required<SeamHttpRequestOptions> => {
const options =
typeof apiKeyOrOptions === 'string'
? { apiKey: apiKeyOrOptions }
: apiKeyOrOptions

if (isSeamHttpOptionsWithClient(options)) return options
const requestOptions = {
waitForActionAttempt: options.waitForActionAttempt ?? false,
}

if (isSeamHttpOptionsWithClient(options)) {
return {
...options,
...requestOptions,
}
}

const apiKey =
'apiKey' in options ? options.apiKey : getApiKeyFromEnv(options)

return {
...options,
...(apiKey != null ? { apiKey } : {}),
...requestOptions,
}
}

Expand All @@ -80,3 +96,26 @@ const getEndpointFromEnv = (): string | null | undefined => {
globalThis.process?.env?.SEAM_API_URL
)
}

export const limitToSeamHttpRequestOptions = (
options: Required<SeamHttpRequestOptions>,
): Required<SeamHttpRequestOptions> => {
return Object.keys(options)
.filter(isSeamHttpRequestOption)
.reduce(
(obj, key) => ({
...obj,
[key]: options[key],
}),
{},
) as Required<SeamHttpRequestOptions>
}

export const isSeamHttpRequestOption = (
key: string,
): key is keyof SeamHttpRequestOptions => {
const keys: Record<keyof SeamHttpRequestOptions, true> = {
waitForActionAttempt: true,
}
return Object.keys(keys).includes(key)
}
17 changes: 14 additions & 3 deletions src/lib/seam/connect/routes/access-codes-unmanaged.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 15 additions & 4 deletions src/lib/seam/connect/routes/access-codes.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 14 additions & 3 deletions src/lib/seam/connect/routes/acs-access-groups.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading