diff --git a/tests/api-resources/downloads.test.ts b/tests/api-resources/downloads.test.ts index 612b506..3bd4563 100644 --- a/tests/api-resources/downloads.test.ts +++ b/tests/api-resources/downloads.test.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import Browserbase from '@browserbasehq/sdk'; -import { Response } from 'node-fetch'; +import { Response } from '@browserbasehq/sdk/_shims/index'; const client = new Browserbase({ apiKey: 'My API Key', diff --git a/tests/lib/downloads.test.ts b/tests/lib/downloads.test.ts new file mode 100644 index 0000000..b797764 --- /dev/null +++ b/tests/lib/downloads.test.ts @@ -0,0 +1,205 @@ +import Browserbase, { NotFoundError, type ClientOptions } from '@browserbasehq/sdk'; +import nodeFetch from 'node-fetch'; +import { once } from 'node:events'; +import { + createServer, + type IncomingHttpHeaders, + type IncomingMessage, + type Server, + type ServerResponse, +} from 'node:http'; +import { type AddressInfo } from 'node:net'; +import { Writable } from 'node:stream'; +import { pipeline } from 'node:stream/promises'; + +const sessionId = '00000000-0000-4000-8000-000000000001'; +const download = (id: string) => ({ + id, + sessionId, + filename: 'report.json', + mimeType: 'application/json', + size: 12, + checksum: 'a'.repeat(64), + createdAt: '2026-09-01T00:00:00Z', +}); + +type RecordedRequest = { method: string | undefined; url: URL; headers: IncomingHttpHeaders }; +type Handler = (req: IncomingMessage, res: ServerResponse) => void; + +const servers: Server[] = []; + +afterEach(async () => { + for (const server of servers.splice(0)) { + server.closeAllConnections(); + await new Promise((resolve) => server.close(resolve)); + } +}); + +function json(res: ServerResponse, data: unknown) { + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify(data)); +} + +// The default runtime uses Node's native fetch; node-fetch is what callers on older setups pass in. +describe.each<[string, Pick]>([ + ['native fetch', {}], + ['node-fetch', { fetch: nodeFetch as unknown as NonNullable }], +])('downloads with %s', (_, fetchOptions) => { + async function clientFor(handler: Handler) { + const requests: RecordedRequest[] = []; + const server = createServer((req, res) => { + requests.push({ + method: req.method, + url: new URL(req.url ?? '/', 'http://localhost'), + headers: req.headers, + }); + handler(req, res); + }); + servers.push(server); + server.listen(0, '127.0.0.1'); + await once(server, 'listening'); + const client = new Browserbase({ + apiKey: 'test-key', + baseURL: `http://127.0.0.1:${(server.address() as AddressInfo).port}`, + maxRetries: 0, + ...fetchOptions, + }); + return { client, requests }; + } + + test('auto-pagination preserves filters, advances from response offset, and stops at total', async () => { + const { client, requests } = await clientFor((req, res) => { + const offset = Number(new URL(req.url ?? '/', 'http://localhost').searchParams.get('offset')); + json(res, { downloads: [download(String(offset))], offset, limit: 2, total: 4 }); + }); + const filters = { + sessionId, + filename: 'a report & summary.json', + mimeType: 'application/json', + minSize: 0, + maxSize: 1024, + createdAfter: '2026-09-01T00:00:00Z', + createdBefore: '2026-09-02T00:00:00Z', + limit: 2, + offset: 2, + }; + const page = await client.downloads.list(filters); + expect(requests).toHaveLength(1); + expect([page.limit, page.offset, page.total]).toEqual([2, 2, 4]); + const ids: string[] = []; + for await (const item of page) ids.push(item.id); + expect(ids).toEqual(['2', '3']); + expect(requests).toHaveLength(2); + for (const [index, request] of requests.entries()) { + expect(request.url.pathname).toBe('/v1/downloads'); + expect(request.headers['x-bb-api-key']).toBe('test-key'); + expect(Object.fromEntries(request.url.searchParams)).toEqual({ + ...Object.fromEntries(Object.entries(filters).map(([key, value]) => [key, String(value)])), + offset: String(index + 2), + }); + } + }); + + test('empty pages stop iteration even when total has become stale', async () => { + const { client, requests } = await clientFor((_, res) => { + json(res, { downloads: [], offset: 0, limit: 20, total: 10 }); + }); + const items = []; + for await (const item of client.downloads.list({ sessionId })) items.push(item); + expect(items).toEqual([]); + expect(requests).toHaveLength(1); + expect(requests[0]!.url.searchParams.has('limit')).toBe(false); + }); + + test('metadata, deletion, errors, and the legacy ZIP endpoint retain their contracts', async () => { + const { client, requests } = await clientFor((req, res) => { + if (req.method === 'DELETE') return void res.writeHead(204).end(); + if (req.url?.endsWith('/missing')) { + return void res.writeHead(404, { 'Content-Type': 'application/json' }).end('{"message":"Not found"}'); + } + if (req.url?.includes('/sessions/')) { + return void res.writeHead(200, { 'Content-Type': 'application/zip' }).end('PK'); + } + json(res, download('file-id')); + }); + expect((await client.downloads.retrieve('file-id')).id).toBe('file-id'); + expect(requests[0]!.headers.accept).toBe('application/json'); + expect(await client.downloads.delete('file-id')).toBeNull(); + await expect(client.downloads.download('missing')).rejects.toThrow(NotFoundError); + const zip = await client.sessions.downloads.list(sessionId); + expect(await zip.text()).toBe('PK'); + expect(requests[3]!.headers.accept).toBe('application/zip'); + expect(requests[3]!.url.pathname).toBe(`/v1/sessions/${sessionId}/downloads`); + }); + + test.each(['application/octet-stream', 'application/json'])( + 'streams %s before completion with backpressure', + async (contentType) => { + // The server cannot finish until the caller consumes the first chunk. + // An implementation that eagerly buffers the body will deadlock this test. + let finish = () => {}; + const { client, requests } = await clientFor((_, res) => { + res.writeHead(200, { + 'Content-Type': contentType, + 'Content-Disposition': 'attachment; filename="file"', + }); + res.write('first'); + finish = () => { + finish = () => {}; + res.end('last'); + }; + }); + const response = await client.downloads.download('file-id'); + expect(response.bodyUsed).toBe(false); + expect(requests[0]!.url.pathname).toBe('/v1/downloads/file-id'); + expect(requests[0]!.headers.accept).not.toContain('application/json'); + expect(response.headers.get('content-type')).toBe(contentType); + expect(response.headers.get('content-disposition')).toContain('attachment'); + const chunks: Uint8Array[] = []; + await pipeline( + response.body!, + new Writable({ + highWaterMark: 1, + write(chunk: Uint8Array, __, callback) { + chunks.push(chunk); + finish(); + setImmediate(callback); + }, + }), + ); + expect(Buffer.concat(chunks).toString()).toBe('firstlast'); + }, + 5000, + ); + + test('empty files stream successfully', async () => { + const { client } = await clientFor((_, res) => res.writeHead(200).end()); + const response = await client.downloads.download('empty'); + expect((await response.arrayBuffer()).byteLength).toBe(0); + }); + + test('an aborted download closes its stream', async () => { + const controller = new AbortController(); + let closed = () => {}; + const connectionClosed = new Promise((resolve) => { + closed = resolve; + }); + const { client } = await clientFor((_, res) => { + res.once('close', closed); + res.writeHead(200, { 'Content-Type': 'application/octet-stream' }); + res.write('first'); + }); + const response = await client.downloads.download('file-id', { signal: controller.signal }); + const consumed = pipeline( + response.body!, + new Writable({ + write(_, __, callback) { + controller.abort(); + callback(); + }, + }), + ); + await expect(consumed).rejects.toMatchObject({ name: 'AbortError' }); + await connectionClosed; + }, 5000); +}); diff --git a/tests/lib/exports.test.ts b/tests/lib/exports.test.ts new file mode 100644 index 0000000..fd22d6d --- /dev/null +++ b/tests/lib/exports.test.ts @@ -0,0 +1,48 @@ +import * as sdk from '@browserbasehq/sdk'; +import Browserbase from '@browserbasehq/sdk'; + +const resources = ['Contexts', 'Downloads', 'Extensions', 'FetchAPI', 'Projects', 'Sessions'] as const; +const errors = [ + 'APIConnectionError', + 'APIConnectionTimeoutError', + 'APIError', + 'APIUserAbortError', + 'AuthenticationError', + 'BadRequestError', + 'BrowserbaseError', + 'ConflictError', + 'InternalServerError', + 'NotFoundError', + 'PermissionDeniedError', + 'RateLimitError', + 'UnprocessableEntityError', +] as const; +const utilities = ['fileFromPath', 'toFile'] as const; + +test('default export is the Browserbase class', () => { + expect(typeof Browserbase).toBe('function'); + expect(Browserbase.name).toBe('Browserbase'); +}); + +test('named Browserbase export matches default', () => { + expect(sdk.Browserbase).toBe(Browserbase); +}); + +test('resource classes are static props on Browserbase', () => { + for (const name of resources) expect(typeof Browserbase[name]).toBe('function'); +}); + +// CommonJS callers read these off the default export, ESM callers import them by name. +test('error classes are named exports and static props', () => { + for (const name of errors) { + expect(typeof sdk[name]).toBe('function'); + expect(Browserbase[name]).toBe(sdk[name]); + } +}); + +test('utility functions are named exports and static props', () => { + for (const name of utilities) { + expect(typeof sdk[name]).toBe('function'); + expect(Browserbase[name]).toBe(sdk[name]); + } +}); diff --git a/tests/lib/sdk-types.ts b/tests/lib/sdk-types.ts new file mode 100644 index 0000000..28b9c30 --- /dev/null +++ b/tests/lib/sdk-types.ts @@ -0,0 +1,790 @@ +// This file never executes. `./scripts/lint` type-checks it, so renaming, removing, or +// widening an SDK type to `any` fails CI. `exports.test.ts` covers the runtime exports. + +import type Browserbase from '@browserbasehq/sdk'; +import type { ClientOptions } from '@browserbasehq/sdk'; +import type { + APIConnectionError, + APIConnectionTimeoutError, + APIError, + APIUserAbortError, + AuthenticationError, + BadRequestError, + BrowserbaseError, + ConflictError, + fileFromPath, + InternalServerError, + NotFoundError, + PermissionDeniedError, + RateLimitError, + toFile, + UnprocessableEntityError, +} from '@browserbasehq/sdk'; + +// These utility types form a small assertion library that operates +// entirely at compile time. The central idea is that `Check` +// constrains its type parameter to `true`. If a type expression +// resolves to anything other than `true` (because a field was +// removed, a type collapsed to `never`, or Stainless widened +// something to `any`) the constraint fails and `tsc` reports an +// error on the offending line. +// +// `IsNotAny` exploits a quirk of TypeScript's `any`: the +// intersection `0 & any` is `any`, so `0 extends 1 & any` is +// true. For every other type, `0 extends 1 & T` is false. This +// lets us reject `any` specifically, which matters because `any` +// would silently satisfy every shape check and hide real breakage. +// +// `IsNotNever` wraps T in a tuple to prevent distribution over +// conditional types. Without the tuple, `never extends never` +// distributes into zero branches and collapses to `never` rather +// than `true`. +// +// `HasKeys` checks that every key in the union K is present on T. +// We use it to verify optional fields still exist even when they +// don't appear in a structural-extends check. + +type IsNotAny = 0 extends 1 & T ? false : true; +type IsNotNever = [T] extends [never] ? false : true; +type Exists = + IsNotAny extends true ? + IsNotNever extends true ? + true + : false + : false; +type Check<_T extends true> = void; +type HasKeys = [K] extends [keyof T] ? true : false; + +// These must fail: `never` satisfies `extends true`, so predicates must +// return `false` for a failed assertion. +// @ts-expect-error any must not satisfy an existence check +type _RejectAny = Check>; +// @ts-expect-error never must not satisfy an existence check +type _RejectNever = Check>; +// @ts-expect-error a missing optional key must fail +type _RejectMissingKey = Check>; + +// The checks below verify that the SDK's public surface area has +// not shrunk. Each `Check>` confirms that a type +// still exists and is neither `any` nor `never`. These are grouped +// by concern: top-level exports, error classes, namespaced +// resource types, and session sub-resources. + +// Top-level exports +type _TopLevel = [ + Check>, + Check>, + Check>, + Check>, +]; + +// Error classes +type _Errors = [ + Check>, + Check>, + Check>, + Check>, + Check>, + Check>, + Check>, + Check>, + Check>, + Check>, + Check>, + Check>, + Check>, +]; + +// Namespace exports (Browserbase.*) +type _Namespace = [Check>]; + +type _Resources = [ + Check>, + Check>, + Check>, + Check>, + Check>, +]; + +type _ContextTypes = [ + Check>, + Check>, + Check>, + Check>, +]; + +type _ExtensionTypes = [ + Check>, + Check>, +]; + +type _ProjectTypes = [ + Check>, + Check>, + Check>, +]; + +type _SessionTypes = [ + Check>, + Check>, + Check>, + Check>, + Check>, + Check>, + Check>, + Check>, +]; + +type _SessionSubresourceClasses = [ + Check>, + Check>, + Check>, + Check>, +]; + +type _SessionSubresourceTypes = [ + Check>, + Check>, + Check>, + Check>, + Check>, + Check>, +]; + +// Existence checks tell us that a type exists, but they can't +// tell us what it looks like. The shapes below assert that each +// SDK type still carries the fields our users depend on, with the +// correct value types and optionality. +// +// Each shape uses a conditional-extends pattern: if the SDK type +// extends the expected structural type, the expression resolves +// to `true`; otherwise it resolves to `false`, which violates the +// `Check` constraint and surfaces a compile error. A companion +// `HasKeys` check ensures that optional properties still exist on +// the type even though they may be absent from the value. +// +// We intentionally mirror the full field set rather than spot- +// checking a few fields. Stainless regenerates the entire type +// from the OpenAPI spec, so any field could vanish between builds. + +// ClientOptions +type _ClientOptionsShape = Check< + HasKeys< + ClientOptions, + | 'apiKey' + | 'baseURL' + | 'defaultHeaders' + | 'defaultQuery' + | 'fetch' + | 'httpAgent' + | 'maxRetries' + | 'timeout' + > +>; + +// Context +type _ContextCreateParamsKeys = Check>; +type _ContextShape = Check< + Browserbase.Context extends ( + { + id: string; + createdAt: string; + projectId: string; + updatedAt: string; + } + ) ? + true + : false +>; + +type _ContextCreateParamsShape = Check< + Browserbase.ContextCreateParams extends ( + { + projectId?: string; + } + ) ? + true + : false +>; + +type _ContextCreateResponseShape = Check< + Browserbase.ContextCreateResponse extends ( + { + id: string; + cipherAlgorithm: string; + initializationVectorSize: number; + publicKey: string; + uploadUrl: string; + } + ) ? + true + : false +>; + +type _ContextUpdateResponseShape = Check< + Browserbase.ContextUpdateResponse extends ( + { + id: string; + cipherAlgorithm: string; + initializationVectorSize: number; + publicKey: string; + uploadUrl: string; + } + ) ? + true + : false +>; + +// Extension +type _ExtensionShape = Check< + Browserbase.Extension extends ( + { + id: string; + createdAt: string; + fileName: string; + projectId: string; + updatedAt: string; + } + ) ? + true + : false +>; + +type _ExtensionCreateParamsKeys = Check>; + +// FetchAPI +type _FetchAPICreateParamsShape = Check< + Browserbase.FetchAPICreateParams extends ( + { + url: string; + } + ) ? + true + : false +>; + +type _FetchAPICreateParamsOptionalKeys = Check< + HasKeys +>; + +type _FetchAPICreateResponseShape = Check< + Browserbase.FetchAPICreateResponse extends ( + { + id: string; + content: string | Record; + contentType: string; + encoding: string; + headers: Record; + statusCode: number; + } + ) ? + true + : false +>; + +// Project +type _ProjectShape = Check< + Browserbase.Project extends ( + { + id: string; + concurrency: number; + createdAt: string; + defaultTimeout: number; + name: string; + ownerId: string; + updatedAt: string; + } + ) ? + true + : false +>; + +type _ProjectListResponseShape = Check< + Browserbase.ProjectListResponse extends Array ? true : false +>; + +type _ProjectUsageShape = Check< + Browserbase.ProjectUsage extends ( + { + browserMinutes: number; + proxyBytes: number; + } + ) ? + true + : false +>; + +// Session +type _SessionShape = Check< + Browserbase.Session extends ( + { + id: string; + createdAt: string; + expiresAt: string; + keepAlive: boolean; + projectId: string; + proxyBytes: number; + region: 'us-west-2' | 'us-east-1' | 'eu-central-1' | 'ap-southeast-1'; + startedAt: string; + status: 'PENDING' | 'RUNNING' | 'ERROR' | 'TIMED_OUT' | 'COMPLETED'; + updatedAt: string; + contextId?: string; + endedAt?: string; + userMetadata?: Record; + } + ) ? + true + : false +>; + +type _SessionOptionalKeys = Check>; + +type _SessionCreateParamsShape = Check< + Browserbase.SessionCreateParams extends ( + { + projectId?: string; + browserSettings?: Browserbase.SessionCreateParams.BrowserSettings; + extensionId?: string; + keepAlive?: boolean; + proxies?: + | boolean + | Array< + | Browserbase.SessionCreateParams.BrowserbaseProxyConfig + | Browserbase.SessionCreateParams.ExternalProxyConfig + | Browserbase.SessionCreateParams.NoneProxyConfig + >; + region?: 'us-west-2' | 'us-east-1' | 'eu-central-1' | 'ap-southeast-1'; + api_timeout?: number; + userMetadata?: Record; + } + ) ? + true + : false +>; + +type _SessionCreateParamsOptionalKeys = Check< + HasKeys< + Browserbase.SessionCreateParams, + | 'projectId' + | 'browserSettings' + | 'extensionId' + | 'keepAlive' + | 'proxies' + | 'region' + | 'api_timeout' + | 'userMetadata' + > +>; + +// SessionCreateParams nested types +type _SessionCreateParamsNestedTypes = [ + Check>, + Check>, + Check>, +]; + +type _BrowserSettingsShape = Check< + Browserbase.SessionCreateParams.BrowserSettings extends ( + { + advancedStealth?: boolean; + blockAds?: boolean; + captchaImageSelector?: string; + captchaInputSelector?: string; + context?: Browserbase.SessionCreateParams.BrowserSettings.Context; + extensionId?: string; + logSession?: boolean; + recordSession?: boolean; + solveCaptchas?: boolean; + verified?: boolean; + viewport?: Browserbase.SessionCreateParams.BrowserSettings.Viewport; + } + ) ? + true + : false +>; + +type _BrowserSettingsOptionalKeys = Check< + HasKeys< + Browserbase.SessionCreateParams.BrowserSettings, + | 'advancedStealth' + | 'blockAds' + | 'captchaImageSelector' + | 'captchaInputSelector' + | 'context' + | 'extensionId' + | 'logSession' + | 'recordSession' + | 'solveCaptchas' + | 'verified' + | 'viewport' + > +>; + +type _BrowserSettingsNestedTypes = [ + Check>, + Check>, +]; + +type _BrowserSettingsContextShape = Check< + Browserbase.SessionCreateParams.BrowserSettings.Context extends ( + { + id: string; + persist?: boolean; + } + ) ? + true + : false +>; + +type _BrowserSettingsViewportShape = Check< + Browserbase.SessionCreateParams.BrowserSettings.Viewport extends ( + { + height?: number; + width?: number; + } + ) ? + true + : false +>; + +type _BrowserSettingsViewportOptionalKeys = Check< + HasKeys +>; + +// Proxy configs are named types in the OpenAPI spec under +// `components/schemas`. Stainless generates distinct TypeScript +// types from named `$ref`s; inline schemas would collapse into +// opaque names like `UnionObjectVariant0`. These checks ensure +// the named types survive SDK generation. + +type _BrowserbaseProxyConfigShape = Check< + Browserbase.SessionCreateParams.BrowserbaseProxyConfig extends ( + { + type: 'browserbase'; + domainPattern?: string; + geolocation?: Browserbase.SessionCreateParams.BrowserbaseProxyConfig.Geolocation; + } + ) ? + true + : false +>; + +type _BrowserbaseProxyConfigOptionalKeys = Check< + HasKeys +>; + +type _BrowserbaseProxyGeolocationShape = Check< + Browserbase.SessionCreateParams.BrowserbaseProxyConfig.Geolocation extends ( + { + country: string; + city?: string; + state?: string; + } + ) ? + true + : false +>; + +type _BrowserbaseProxyGeolocationOptionalKeys = Check< + HasKeys +>; + +type _ExternalProxyConfigShape = Check< + Browserbase.SessionCreateParams.ExternalProxyConfig extends ( + { + server: string; + type: 'external'; + domainPattern?: string; + password?: string; + username?: string; + } + ) ? + true + : false +>; + +type _ExternalProxyConfigOptionalKeys = Check< + HasKeys +>; + +// -- SessionCreateResponse -- +type _SessionCreateResponseShape = Check< + Browserbase.SessionCreateResponse extends ( + { + id: string; + connectUrl: string; + createdAt: string; + expiresAt: string; + keepAlive: boolean; + projectId: string; + proxyBytes: number; + region: 'us-west-2' | 'us-east-1' | 'eu-central-1' | 'ap-southeast-1'; + seleniumRemoteUrl: string; + signingKey: string; + startedAt: string; + status: 'PENDING' | 'RUNNING' | 'ERROR' | 'TIMED_OUT' | 'COMPLETED'; + updatedAt: string; + contextId?: string; + endedAt?: string; + userMetadata?: Record; + } + ) ? + true + : false +>; + +type _SessionCreateResponseOptionalKeys = Check< + HasKeys +>; + +// -- SessionListParams -- +type _SessionListParamsShape = Check< + Browserbase.SessionListParams extends ( + { + q?: string; + status?: 'PENDING' | 'RUNNING' | 'ERROR' | 'TIMED_OUT' | 'COMPLETED'; + } + ) ? + true + : false +>; + +type _SessionListParamsKeys = Check>; + +type _SessionListResponseShape = Check< + Browserbase.SessionListResponse extends Array ? true : false +>; + +type _SessionLiveURLsShape = Check< + Browserbase.SessionLiveURLs extends ( + { + debuggerFullscreenUrl: string; + debuggerUrl: string; + pages: Array<{ + id: string; + debuggerFullscreenUrl: string; + debuggerUrl: string; + faviconUrl: string; + title: string; + url: string; + }>; + wsUrl: string; + } + ) ? + true + : false +>; + +// SessionRetrieveResponse +type _SessionRetrieveResponseShape = Check< + Browserbase.SessionRetrieveResponse extends ( + { + id: string; + createdAt: string; + expiresAt: string; + keepAlive: boolean; + projectId: string; + proxyBytes: number; + region: 'us-west-2' | 'us-east-1' | 'eu-central-1' | 'ap-southeast-1'; + startedAt: string; + status: 'PENDING' | 'RUNNING' | 'ERROR' | 'TIMED_OUT' | 'COMPLETED'; + updatedAt: string; + connectUrl?: string; + contextId?: string; + endedAt?: string; + seleniumRemoteUrl?: string; + signingKey?: string; + userMetadata?: Record; + } + ) ? + true + : false +>; + +type _SessionRetrieveResponseOptionalKeys = Check< + HasKeys< + Browserbase.SessionRetrieveResponse, + 'connectUrl' | 'contextId' | 'endedAt' | 'seleniumRemoteUrl' | 'signingKey' | 'userMetadata' + > +>; + +// SessionUpdateParams +type _SessionUpdateParamsKeys = Check>; +type _SessionUpdateParamsShape = Check< + Browserbase.SessionUpdateParams extends ( + { + projectId?: string; + status: 'REQUEST_RELEASE'; + } + ) ? + true + : false +>; + +// SessionLog (via Sessions namespace) +type _SessionLogShape = Check< + Browserbase.Sessions.SessionLog extends ( + { + method: string; + pageId: number; + sessionId: string; + frameId?: string; + loaderId?: string; + request?: Browserbase.Sessions.SessionLog.Request; + response?: Browserbase.Sessions.SessionLog.Response; + timestamp?: number; + } + ) ? + true + : false +>; + +type _SessionLogOptionalKeys = Check< + HasKeys +>; + +// SessionLog nested types +type _SessionLogNestedTypes = [ + Check>, + Check>, +]; + +type _SessionLogRequestShape = Check< + Browserbase.Sessions.SessionLog.Request extends ( + { + params: Record; + rawBody: string; + timestamp?: number; + } + ) ? + true + : false +>; + +type _SessionLogRequestOptionalKeys = Check>; + +type _SessionLogResponseShape = Check< + Browserbase.Sessions.SessionLog.Response extends ( + { + rawBody: string; + result: Record; + timestamp?: number; + } + ) ? + true + : false +>; + +type _SessionLogResponseOptionalKeys = Check>; + +type _LogListResponseShape = Check< + Browserbase.Sessions.LogListResponse extends Array ? true : false +>; + +// SessionRecording (via Sessions namespace) +type _SessionRecordingShape = Check< + Browserbase.Sessions.SessionRecording extends ( + { + data: Record; + sessionId: string; + timestamp: number; + type: number; + } + ) ? + true + : false +>; + +type _RecordingRetrieveResponseShape = Check< + Browserbase.Sessions.RecordingRetrieveResponse extends Array ? true + : false +>; + +// Uploads (via Sessions namespace) +type _UploadCreateParamsKeys = Check>; + +type _UploadCreateResponseShape = Check< + Browserbase.Sessions.UploadCreateResponse extends ( + { + message: string; + } + ) ? + true + : false +>; + +// A type can have the right shape and still break callers if +// the method that returns it changes its signature. These checks +// verify that each resource method still returns the expected +// type. We use `Awaited>` to unwrap the Promise +// and test the resolved value directly. +// +// The `bb` variable is declared but never assigned. It only +// exists to give TypeScript something to hang `typeof` on. + +declare const bb: Browserbase; + +type _MethodReturnTypes = [ + // Contexts + Check< + Awaited> extends Browserbase.ContextCreateResponse ? true : false + >, + Check> extends Browserbase.Context ? true : false>, + Check< + Awaited> extends Browserbase.ContextUpdateResponse ? true : false + >, + + // FetchAPI + Check< + Awaited> extends Browserbase.FetchAPICreateResponse ? true : false + >, + + // Extensions + Check> extends Browserbase.Extension ? true : false>, + Check> extends void ? true : false>, + Check> extends Browserbase.Extension ? true : false>, + + // Projects + Check> extends Browserbase.ProjectListResponse ? true : false>, + Check> extends Browserbase.Project ? true : false>, + Check> extends Browserbase.ProjectUsage ? true : false>, + + // Sessions + Check< + Awaited> extends Browserbase.SessionCreateResponse ? true : false + >, + Check> extends Browserbase.SessionLiveURLs ? true : false>, + Check> extends Browserbase.SessionListResponse ? true : false>, + Check< + Awaited> extends Browserbase.SessionRetrieveResponse ? true + : false + >, + Check> extends Browserbase.Session ? true : false>, + + // Session subresources + Check>>>, + Check< + Awaited> extends Browserbase.Sessions.LogListResponse ? true + : false + >, + Check< + Awaited> extends ( + Browserbase.Sessions.RecordingRetrieveResponse + ) ? + true + : false + >, + Check< + Awaited> extends Browserbase.Sessions.UploadCreateResponse ? + true + : false + >, +]; + +// Subresource instances +type _SubresourceInstances = [ + Check>, + Check>, + Check>, + Check>, +];