import type { RequestOptions } from '../internal/request-options'; /** * Per-request project scope. * * With API-key auth, the server resolves the project from the key. With * bearer (JWT) auth a user can belong to multiple projects, so callers must * specify which one to act on by passing `projectId` as a query param * (the server derives `teamId` from it — no need to send it explicitly). * * SDK helpers like `Job.wait()` and `Upload.wait()` make follow-up calls * internally — they capture the scope effective on the originating call and * forward it on each retry, so an action created in project A always polls * in project A even if the client default changes between calls. */ export interface Scope { projectId?: string; } /** Extract `projectId` from a `RequestOptions.query`. Returns `undefined` if absent. */ export function extractScope(options?: RequestOptions): Scope | undefined { const query = options?.query as Record | undefined; if (!query) return undefined; const projectId = typeof query['projectId'] === 'string' ? query['projectId'] : undefined; if (!projectId) return undefined; return { projectId }; } /** * Resolve the effective scope for an outgoing call: * 1. If the per-call options carry a `projectId` query — use that (the explicit override wins). * 2. Otherwise, fall back to the client's default `projectId`. * 3. If neither is set (e.g. API-key auth with implicit scope), return `undefined`. * * Returns the snapshot we want to *capture on an enhanced entity* so that * follow-up helper calls (`.wait()`, `entity.run()`, ...) reuse the same * scope and stay in sync with the call that produced them. */ export function effectiveScope( options: RequestOptions | undefined, fallbackProjectId: string | null | undefined, ): Scope | undefined { const override = extractScope(options); if (override) return override; if (fallbackProjectId) return { projectId: fallbackProjectId }; return undefined; } /** * Read the client's default `projectId` without repeating the structural * cast at every call site. The base generated `Scenario` type doesn't * declare `projectId` — only the enhanced one in `./scenario` does — so * `lib/` modules that import `Scenario` from `'../client'` use this helper * instead of duplicating the same cast inline. */ export function clientProjectId(client: unknown): string | null | undefined { return (client as { projectId?: string | null }).projectId; } /** Build a `RequestOptions` that adds the scope as a query param, merging into any existing query. */ export function withScope(scope: Scope | undefined, options?: RequestOptions): RequestOptions | undefined { if (!scope?.projectId) return options; const existingQuery = (options?.query as Record | undefined) ?? {}; return { ...options, query: { ...existingQuery, projectId: scope.projectId }, }; }