Skip to content
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
3 changes: 2 additions & 1 deletion codegen/layouts/partials/route-class-endpoint.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
hasRequiredParameters: {{hasRequiredParameters}},
requiredParameterNames: [{{#each requiredParameterNames}}{{json .}}{{#unless @last}}, {{/unless}}{{/each}}],
responseKey: {{#if returnsVoid}}undefined{{else}}'{{responseKey}}'{{/if}},
options,
{{#if hasPagination}}hasPagination: true,
{{/if}}options,
{{#if returnsActionAttempt}}
actionAttempts: SeamHttpActionAttempts.fromClient(this.client, {
...this.defaults,
Expand Down
2 changes: 2 additions & 0 deletions codegen/lib/layouts/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export interface EndpointLayoutContext {
className: string
method: Method
responseKey: string
hasPagination: boolean
requestFormat: 'params' | 'body'
parametersTypeName: string
responseTypeName: string
Expand Down Expand Up @@ -141,6 +142,7 @@ export const getEndpointLayoutContext = (
methodName,
functionName: camelCase(prefix),
method: endpoint.request.preferredMethod,
hasPagination: endpoint.hasPagination,
className: getClassName(route.path),
requestFormat,
returnsActionAttempt,
Expand Down
1 change: 1 addition & 0 deletions src/lib/routes/access-codes/access-codes.ts

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

1 change: 1 addition & 0 deletions src/lib/routes/access-codes/unmanaged/unmanaged.ts

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

1 change: 1 addition & 0 deletions src/lib/routes/access-grants/access-grants.ts

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

1 change: 1 addition & 0 deletions src/lib/routes/access-grants/unmanaged/unmanaged.ts

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

1 change: 1 addition & 0 deletions src/lib/routes/access-methods/access-methods.ts

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

1 change: 1 addition & 0 deletions src/lib/routes/acs/credentials/credentials.ts

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

1 change: 1 addition & 0 deletions src/lib/routes/acs/encoders/encoders.ts

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

1 change: 1 addition & 0 deletions src/lib/routes/acs/entrances/entrances.ts

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

1 change: 1 addition & 0 deletions src/lib/routes/acs/users/users.ts

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

1 change: 1 addition & 0 deletions src/lib/routes/action-attempts/action-attempts.ts

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

1 change: 1 addition & 0 deletions src/lib/routes/connect-webviews/connect-webviews.ts

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

1 change: 1 addition & 0 deletions src/lib/routes/connected-accounts/connected-accounts.ts

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

1 change: 1 addition & 0 deletions src/lib/routes/devices/devices.ts

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

1 change: 1 addition & 0 deletions src/lib/routes/devices/unmanaged/unmanaged.ts

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

1 change: 1 addition & 0 deletions src/lib/routes/spaces/spaces.ts

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

1 change: 1 addition & 0 deletions src/lib/routes/user-identities/unmanaged/unmanaged.ts

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

1 change: 1 addition & 0 deletions src/lib/routes/user-identities/user-identities.ts

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

9 changes: 8 additions & 1 deletion src/lib/seam-http-request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ interface SeamHttpRequestConfig<TResponseKey> {
readonly body?: unknown
readonly params?: undefined | Record<string, unknown>
readonly responseKey: TResponseKey
readonly hasPagination?: boolean
readonly options?: Pick<SeamHttpRequestOptions, 'waitForActionAttempt'>
readonly actionAttempts?: ActionAttemptsClient
readonly parameters?: unknown
Expand Down Expand Up @@ -51,6 +52,10 @@ export class SeamHttpRequest<
return this.#config.responseKey
}

public get hasPagination(): boolean {
return this.#config.hasPagination ?? false
}

public get url(): URL {
const { client } = this.#parent

Expand Down Expand Up @@ -194,7 +199,9 @@ const getUrlPrefix = (input: string): string => {
}
if (globalThis.location != null) {
const pathname = input.startsWith('/') ? input : `/${input}`
return new URL(`${globalThis.location.origin}${pathname}`).toString()
return new URL(`${globalThis.location.origin}${pathname}`)
.toString()
.replace(/\/$/, '')
}
throw new Error(
`Cannot resolve origin from ${input} in a non-browser environment`,
Expand Down
2 changes: 1 addition & 1 deletion src/lib/seam-paginator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export class SeamPaginator<
parent: SeamPaginatorParent,
request: SeamHttpRequest<TResponse, TResponseKey>,
) {
if (request.responseKey == null) {
if (!request.hasPagination) {
throw new Error(
`The ${request.pathname} endpoint does not support pagination`,
)
Expand Down
49 changes: 33 additions & 16 deletions test/seam/connect/seam-http-request.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,56 +104,73 @@ test('SeamHttpRequest: url is a URL when endpoint is a url with a path', async (
)
})

test.failing(
test.serial(
'SeamHttpRequest: url is a URL when endpoint is path',
async (t) => {
const { seed } = await getTestServer(t)
Object.defineProperty(globalThis, 'location', {
configurable: true,
value: { origin: 'https://example.com' },
})
t.teardown(() => Reflect.deleteProperty(globalThis, 'location'))

const seam = SeamHttp.fromApiKey(seed.seam_apikey1_token, {
endpoint: '/some/sub/path',
})

const { url } = seam.devices.get({ device_id: 'abc123' })

t.true(url instanceof URL)
t.deepEqual(
toPlainUrlObject(url),
toPlainUrlObject(
new URL('https://example.com/some/sub/path/devices/get'),
new URL(
'https://example.com/some/sub/path/devices/get?device_id=abc123&_strict=true',
),
),
)
},
)

test.failing(
test.serial(
'SeamHttpRequest: url is a URL when endpoint is empty',
async (t) => {
const { seed } = await getTestServer(t)
Object.defineProperty(globalThis, 'location', {
configurable: true,
value: { origin: 'https://example.com' },
})
t.teardown(() => Reflect.deleteProperty(globalThis, 'location'))

const seam = SeamHttp.fromApiKey(seed.seam_apikey1_token, {
endpoint: '',
})

// TODO: Set globalThis.location.origin = 'https://example.com'

const { url } = seam.devices.get({ device_id: 'abc123' })

t.true(url instanceof URL)
t.deepEqual(
toPlainUrlObject(url),
toPlainUrlObject(new URL('https://example.com/devices/get')),
toPlainUrlObject(
new URL(
'https://example.com/devices/get?device_id=abc123&_strict=true',
),
),
)
},
)

test('SeamHttpRequest: url throws if unable to resolve origin', async (t) => {
const { seed } = await getTestServer(t)
const seam = SeamHttp.fromApiKey(seed.seam_apikey1_token, {
endpoint: '',
})
test.serial(
'SeamHttpRequest: url throws if unable to resolve origin',
async (t) => {
const { seed } = await getTestServer(t)
const seam = SeamHttp.fromApiKey(seed.seam_apikey1_token, {
endpoint: '',
})

const request = seam.devices.get({ device_id: 'abc123' })
const request = seam.devices.get({ device_id: 'abc123' })

t.throws(() => request.url, { message: /Cannot resolve origin/ })
})
t.throws(() => request.url, { message: /Cannot resolve origin/ })
},
)

const toPlainUrlObject = (url: URL): Omit<URL, 'searchParams' | 'toJSON'> => {
return {
Expand Down
18 changes: 7 additions & 11 deletions test/seam/connect/seam-paginator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,14 @@ test('SeamPaginator: cannot paginate a request with an empty response', async (t
)
})

// TODO: Validate the request supports pagination by extending SeamHttpRequest with this knowledge via codegen.
test.failing(
'SeamPaginator: cannot paginate an request that does not return pagination data',
async (t) => {
const { seed, endpoint } = await getTestServer(t)
const seam = SeamHttp.fromApiKey(seed.seam_apikey1_token, { endpoint })
test('SeamPaginator: cannot paginate a request that does not return pagination data', async (t) => {
const { seed, endpoint } = await getTestServer(t)
const seam = SeamHttp.fromApiKey(seed.seam_apikey1_token, { endpoint })

t.throws(() => seam.createPaginator(seam.workspaces.list()), {
message: /does not support pagination/,
})
},
)
t.throws(() => seam.createPaginator(seam.workspaces.list()), {
message: /does not support pagination/,
})
})

test('SeamPaginator: firstPage returns the first page', async (t) => {
const { seed, endpoint } = await getTestServer(t)
Expand Down
Loading