import { Scenario as BaseScenario, type ClientOptions as BaseClientOptions } from '../client'; import { EnhancedAssets } from './asset'; import { EnhancedGenerate } from './generate'; import { EnhancedJobs } from './job'; import { EnhancedModels } from './models'; import { EnhancedUploads } from './upload'; import { EnhancedWorkflows } from './workflow'; export interface ClientOptions extends BaseClientOptions { /** * Default project to scope every request to (sent as `?projectId=...`). * * Useful with `bearerAuth` when the user belongs to multiple projects — * the server derives the team from the project, so `teamId` doesn't need * to be configured at construction time. With `apiKey` auth, the project * is already implied by the key and this option is unnecessary. * * Per-call `options.query.projectId` overrides this default for a single * request. SDK helpers (`.wait()`, `model.run()`, ...) reuse the scope * effective on the originating call, so an action created in project A * keeps polling in project A even if the client default changes later. */ projectId?: string | null | undefined; } /** * Enhanced Scenario client. * * Extends the generated client — all original methods are inherited. * Job-producing methods return responses where `job` has `.wait()`. * Workflow retrieve returns a response where `workflow` has `.findNode()`. * * @example * ```ts * import Scenario from '@scenario-labs/sdk'; * * // With API key — project is implied by the key. * const client = new Scenario({ apiKey: '...', apiSecret: '...' }); * * // With bearer JWT — pin a default project for all requests. * const client = new Scenario({ bearerAuth: jwt, projectId: 'proj_abc' }); * * // Per-call override (one request only): * await client.uploads.uploadFile(params, { query: { projectId: 'proj_other' } }); * * // Enhanced: response.job.wait() — auto-reuses the scope of the call that produced the job * const run = await client.workflows.run(workflowId, { body }); * const completed = await run.job.wait(); * * // Enhanced: upload.wait() * const res = await client.uploads.retrieve(uploadId); * const imported = await res.upload.wait(); * * // Enhanced: asset.download() * const a = await client.assets.retrieve(assetId); * const bytes = await a.asset.download(); * ``` */ export class Scenario extends BaseScenario { override workflows: EnhancedWorkflows = new EnhancedWorkflows(this); override generate: EnhancedGenerate = new EnhancedGenerate(this); override models: EnhancedModels = new EnhancedModels(this); override uploads: EnhancedUploads = new EnhancedUploads(this); override assets: EnhancedAssets = new EnhancedAssets(this); override jobs: EnhancedJobs = new EnhancedJobs(this); /** The default project this client scopes every request to, or `null` if unset. */ readonly projectId: string | null; constructor(options: ClientOptions = {}) { const { projectId, defaultQuery, ...rest } = options; // Strip any stale projectId from defaultQuery so the typed field is the // single source of truth (matters when `withOptions` reconstructs the client). const cleanedDefaultQuery = stripKey(defaultQuery, 'projectId'); super({ ...rest, // Pass projectId through so it lands in `_options` and survives a // `withOptions()` round-trip. The base ClientOptions doesn't declare // this field, hence the cast. projectId, defaultQuery: projectId ? { ...cleanedDefaultQuery, projectId } : cleanedDefaultQuery, } as BaseClientOptions); this.projectId = projectId ?? null; } } function stripKey(obj: T | undefined, key: string): Partial | undefined { if (!obj || !(key in obj)) return obj; const { [key]: _omit, ...rest } = obj as Record; return rest as Partial; }