From 2a368c10f0cf36f870cb2c2aaed1ea4367dc79e2 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 24 Aug 2026 19:40:35 +0000 Subject: [PATCH 1/3] fix: Validate every paginated request like the original request The paginator rebuilt each page request with only the pathname, method, response key, and data, dropping the request parameters and validation configuration, so required parameter checks were skipped for every paginated fetch including the first page. A cursor was also dropped entirely when the original request had no params or body. Build each page through a new SeamHttpRequest.withPageCursor, which copies the entire request configuration and merges the page cursor into the params or body chosen by the request method. New configuration fields now travel to page requests automatically instead of being hand-copied in the paginator. Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01B8xeJm2Hd923k8uo6eoFd2 --- src/lib/seam-http-request.ts | 26 +++++++++++++++++ src/lib/seam-paginator.ts | 20 ++----------- test/seam/connect/seam-paginator.test.ts | 36 ++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 17 deletions(-) diff --git a/src/lib/seam-http-request.ts b/src/lib/seam-http-request.ts index 719de08d..72962781 100644 --- a/src/lib/seam-http-request.ts +++ b/src/lib/seam-http-request.ts @@ -113,6 +113,32 @@ export class SeamHttpRequest< return this.#config.body } + /** + * Returns a copy of this request with the page_cursor parameter set, + * keeping the entire request configuration, + * so every page is built and validated exactly like the original request. + * Used by SeamPaginator to fetch pages. + */ + withPageCursor( + pageCursor?: string, + ): SeamHttpRequest { + const usesParams = ['GET', 'DELETE'].includes(this.method.toUpperCase()) + + const requestData = { + ...(usesParams + ? (this.#config.params ?? {}) + : ((this.#config.body as Record | null) ?? {})), + page_cursor: pageCursor, + } + + return new SeamHttpRequest(this.#parent, { + ...this.#config, + parameters: requestData, + params: usesParams ? requestData : undefined, + body: usesParams ? undefined : requestData, + }) + } + /** * Sends the request and returns the response data. * If the response contains an action attempt, diff --git a/src/lib/seam-paginator.ts b/src/lib/seam-paginator.ts index 0741f46f..90d1604b 100644 --- a/src/lib/seam-paginator.ts +++ b/src/lib/seam-paginator.ts @@ -1,6 +1,6 @@ import type { Client } from './client.js' import type { SeamHttpRequestOptions } from './options.js' -import { SeamHttpRequest } from './seam-http-request.js' +import type { SeamHttpRequest } from './seam-http-request.js' interface SeamPaginatorParent { readonly client: Client @@ -34,10 +34,9 @@ export class SeamPaginator< const TResponseKey extends keyof TResponse, > implements AsyncIterable> { readonly #request: SeamHttpRequest - readonly #parent: SeamPaginatorParent constructor( - parent: SeamPaginatorParent, + _parent: SeamPaginatorParent, request: SeamHttpRequest, ) { if (!request.hasPagination) { @@ -45,7 +44,6 @@ export class SeamPaginator< `The ${request.pathname} endpoint does not support pagination`, ) } - this.#parent = parent this.#request = request } @@ -81,19 +79,7 @@ export class SeamPaginator< throw new Error('Cannot paginate a response without a responseKey') } - const request = new SeamHttpRequest(this.#parent, { - pathname: this.#request.pathname, - method: this.#request.method, - responseKey, - params: - this.#request.params != null - ? { ...this.#request.params, page_cursor: nextPageCursor } - : undefined, - body: - this.#request.body != null - ? { ...this.#request.body, page_cursor: nextPageCursor } - : undefined, - }) + const request = this.#request.withPageCursor(nextPageCursor ?? undefined) const response = await request.fetchResponse() const data = response[responseKey] diff --git a/test/seam/connect/seam-paginator.test.ts b/test/seam/connect/seam-paginator.test.ts index 2fca098c..3e7d44ef 100644 --- a/test/seam/connect/seam-paginator.test.ts +++ b/test/seam/connect/seam-paginator.test.ts @@ -88,6 +88,42 @@ test('SeamPaginator: flatten allows iteration over all devices', async (t) => { t.is(devices.length, allDevices.length) }) +test('SeamPaginator: validates request parameters before fetching a page', async (t) => { + const { seed, endpoint } = await getTestServer(t) + const seam = SeamHttp.fromApiKey(seed.seam_apikey1_token, { endpoint }) + + let requestCount = 0 + seam.client.interceptors.request.use((config) => { + if (config.url === '/access_codes/list') requestCount++ + return config + }) + + const pages = seam.createPaginator( + // @ts-expect-error Verify an invalid request is rejected when paginated. + seam.accessCodes.list({}), + ) + + await t.throwsAsync(async () => await pages.firstPage(), { + instanceOf: TypeError, + message: 'At least one parameter is required for /access_codes/list', + }) + + t.is(requestCount, 0) +}) + +test('SeamPaginator: fetches pages for a request with valid parameters', async (t) => { + const { seed, endpoint } = await getTestServer(t) + const seam = SeamHttp.fromApiKey(seed.seam_apikey1_token, { endpoint }) + + const pages = seam.createPaginator( + seam.accessCodes.list({ device_id: seed.august_device_1 }), + ) + const [accessCodes, pagination] = await pages.firstPage() + + t.true(Array.isArray(accessCodes)) + t.false(pagination.hasNextPage) +}) + test('SeamPaginator: instance allows iteration over all pages', async (t) => { const { seed, endpoint } = await getTestServer(t) const seam = SeamHttp.fromApiKey(seed.seam_apikey1_token, { endpoint }) From 72073a947a9377a37ecbf924b069a9ef18f1fbb6 Mon Sep 17 00:00:00 2001 From: Evan Sosenko Date: Mon, 24 Aug 2026 15:01:10 -0700 Subject: [PATCH 2/3] Apply suggestion from @razor-x --- src/lib/seam-paginator.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/seam-paginator.ts b/src/lib/seam-paginator.ts index 90d1604b..425e396a 100644 --- a/src/lib/seam-paginator.ts +++ b/src/lib/seam-paginator.ts @@ -36,7 +36,6 @@ export class SeamPaginator< readonly #request: SeamHttpRequest constructor( - _parent: SeamPaginatorParent, request: SeamHttpRequest, ) { if (!request.hasPagination) { From 12aba989aa0626d6ea2d2c91149a1a202a3a82dc Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 24 Aug 2026 23:29:30 +0000 Subject: [PATCH 3/3] docs: Trim the withPageCursor doc Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01B8xeJm2Hd923k8uo6eoFd2 --- src/lib/seam-http-request.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/lib/seam-http-request.ts b/src/lib/seam-http-request.ts index 5e616022..5a4c0c89 100644 --- a/src/lib/seam-http-request.ts +++ b/src/lib/seam-http-request.ts @@ -115,10 +115,7 @@ export class SeamHttpRequest< } /** - * Returns a copy of this request with the page_cursor parameter set, - * keeping the entire request configuration, - * so every page is built and validated exactly like the original request. - * Used by SeamPaginator to fetch pages. + * Returns a copy of this request with the page cursor set. */ withPageCursor( pageCursor?: string,