@simmit/sdk 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -3,7 +3,11 @@
3
3
  [![CI](https://github.com/voidly-labs/simmit-sdk-typescript/actions/workflows/ci.yml/badge.svg)](https://github.com/voidly-labs/simmit-sdk-typescript/actions/workflows/ci.yml)
4
4
  [![npm](https://img.shields.io/npm/v/@simmit/sdk.svg)](https://www.npmjs.com/package/@simmit/sdk)
5
5
 
6
- TypeScript SDK for the [Simmit API](https://api.simmit.com) cloud execution for SimulationCraft.
6
+ TypeScript SDK for [Simmit](https://simmit.com), an API for running SimulationCraft in the cloud.
7
+
8
+ Send the same `.simc` profile you'd run locally and Simmit executes SimulationCraft (SimC) for you on managed hardware, so you can run sims from anywhere that can make an HTTP request, with no local SimC build to manage.
9
+
10
+ Documentation: [dashboard.simmit.com/docs](https://dashboard.simmit.com/docs)
7
11
 
8
12
  ## Installation
9
13
 
@@ -11,29 +15,18 @@ TypeScript SDK for the [Simmit API](https://api.simmit.com) — cloud execution
11
15
  npm install @simmit/sdk
12
16
  ```
13
17
 
14
- Node 20+. Zero runtime dependencies global `fetch` and WebCrypto only.
18
+ Node 20+. Zero runtime dependencies (global `fetch` and WebCrypto only).
15
19
 
16
20
  ## Usage
17
21
 
18
22
  ```ts
19
23
  import Simmit from '@simmit/sdk'
20
24
 
21
- const client = new Simmit() // reads SIMMIT_SECRET_KEY from the environment
22
- ```
23
-
24
- Or pass the secret key explicitly:
25
-
26
- ```ts
27
- const client = new Simmit({ secretKey: 'smt_sk_...' })
28
- ```
29
-
30
- ### Run a sim and wait for the result
31
-
32
- `createAndWait` submits a job and polls until it reaches a terminal state,
33
- resolving with the completed job — ideal for scripts and queue workers that can
34
- hold a promise open:
25
+ const client = new Simmit({
26
+ secretKey: process.env['SIMMIT_SECRET_KEY'] // This is the default and can be omitted
27
+ })
35
28
 
36
- ```ts
29
+ // Submit a SimC profile, wait for the sim to finish, and read the result.
37
30
  const job = await client.jobs.createAndWait({
38
31
  build: { channel: 'latest' },
39
32
  profile: { text: profileText } // a SimC profile, up to 2 MB
@@ -42,7 +35,12 @@ const job = await client.jobs.createAndWait({
42
35
  const result = await client.jobs.getResult(job.id)
43
36
  ```
44
37
 
45
- Observe progress, or capture the job id before polling starts:
38
+ The rest of the surface is shown below until an SDK reference lands in the [docs](https://dashboard.simmit.com/docs).
39
+
40
+ ### Progress hooks
41
+
42
+ `createAndWait` is ideal for scripts and queue workers that can hold a promise
43
+ open. Hook into progress, or capture the job id before polling starts:
46
44
 
47
45
  ```ts
48
46
  await client.jobs.createAndWait(
@@ -79,8 +77,8 @@ await client.jobs.cancel(id) // request cancellation
79
77
  ### Artifact download URLs
80
78
 
81
79
  `getResult` returns each artifact with a stable download `url`, valid for the
82
- artifact's full retention window. To fetch that URL on demand instead e.g. a
83
- browser flow that controls the final fetch use:
80
+ artifact's full retention window. To fetch that URL on demand instead (e.g. a
81
+ browser flow that controls the final fetch), use:
84
82
 
85
83
  ```ts
86
84
  const { url } = await client.artifacts.getUrl(artifactId)
@@ -115,8 +113,8 @@ try {
115
113
 
116
114
  `createAndWait` also throws `JobFailedError` / `JobCancelledError` /
117
115
  `JobTimedOutError` (each carrying the full `.job`), and `JobWaitTimeoutError` if
118
- the wait deadline passes before the job finishes the job keeps running, so
119
- call `client.jobs.cancel(jobId)` to stop the spend.
116
+ the wait deadline passes before the job finishes. The job keeps running, so call
117
+ `client.jobs.cancel(jobId)` to stop the spend.
120
118
 
121
119
  ### Response headers
122
120
 
@@ -132,8 +130,8 @@ response.headers.get('x-idempotent-replay')
132
130
  ### Verifying webhooks
133
131
 
134
132
  `unwrapWebhook` verifies a webhook signature and returns the parsed event. It is
135
- standalone no client and no API key required, just your webhook signing
136
- secret so it is safe to run in a webhook receiver:
133
+ standalone (no client and no API key required, just your webhook signing
134
+ secret), so it is safe to run in a webhook receiver:
137
135
 
138
136
  ```ts
139
137
  import { unwrapWebhook } from '@simmit/sdk'
@@ -149,13 +147,85 @@ if (event.payload.status === 'completed') {
149
147
  }
150
148
  ```
151
149
 
150
+ ## Using the SDK in a web app
151
+
152
+ The SDK is server-side only: the secret key spends credits and must never reach
153
+ the browser. A typical web flow submits a job on one request, persists the
154
+ returned id, and reads the result on a later request. A `job.terminal` webhook
155
+ (above) is the reliable completion signal.
156
+
157
+ ### Construct the client lazily
158
+
159
+ `new Simmit()` throws when no secret key is set. Frameworks that evaluate route
160
+ modules at build time (Next.js, Remix, SvelteKit) run that code with no
161
+ environment, so a top-level `new Simmit()` breaks the build. Construct it lazily
162
+ so the key is read at first use, on a real request:
163
+
164
+ ```ts
165
+ // lib/simmit.ts
166
+ import 'server-only'
167
+ import Simmit from '@simmit/sdk'
168
+
169
+ let client: Simmit | undefined
170
+
171
+ export function simmit(): Simmit {
172
+ return (client ??= new Simmit())
173
+ }
174
+ ```
175
+
176
+ The `server-only` import fails the build if this module is ever pulled into a
177
+ client component.
178
+
179
+ ### Make submit idempotent
180
+
181
+ `jobs.create` attaches a fresh idempotency key per call, which keeps the SDK's
182
+ own retries safe. It does not cover a user double-clicking submit: that is two
183
+ calls, and two billed jobs. Pass a stable key (for example a per-render form
184
+ nonce) so the second submit replays the first job instead of creating another:
185
+
186
+ ```ts
187
+ await simmit().jobs.create(params, { idempotencyKey: formNonce })
188
+ ```
189
+
190
+ ### Branch on the result
191
+
192
+ Use `isTerminal` to tell whether the job is still running. A plain
193
+ `job.status === 'completed'` check narrows a finished job to `CompletedJob`
194
+ before you read its result:
195
+
196
+ ```ts
197
+ import { isTerminal } from '@simmit/sdk'
198
+ import { simmit } from '@/lib/simmit'
199
+
200
+ const job = await simmit().jobs.get(id)
201
+
202
+ if (!isTerminal(job.status)) {
203
+ // still running: send the caller back to a progress view
204
+ return
205
+ }
206
+
207
+ if (job.status !== 'completed') {
208
+ // terminal but not successful: 'failed' | 'cancelled' | 'timed_out'
209
+ console.error(job.statusReason)
210
+ return
211
+ }
212
+
213
+ // job is CompletedJob here (=== 'completed' narrowed it)
214
+ const { result } = await simmit().jobs.getResult(job.id)
215
+ const actor = result.summary?.mainActor
216
+ // mainActor is null for a completed run with no single headline actor (a
217
+ // profileset-only or multi-player sim keeps per-actor numbers in the JSON
218
+ // artifact), so guard it even on success.
219
+ const dps = actor ? Math.round(actor.mean) : null
220
+ ```
221
+
152
222
  ## Development
153
223
 
154
224
  - Node 20+ (`.nvmrc` pins the dev version), pnpm.
155
- - `pnpm generate` regenerate `src/generated/openapi.d.ts` from the committed `openapi.json` snapshot. Never hand-edit generated output; only `src/api-types.ts` may import from `src/generated/`.
156
- - `pnpm build` dual ESM+CJS via tsup.
157
- - `pnpm test` vitest (hermetic; mocks `fetch`, no network).
158
- - `pnpm smoke` manual check against a real API (needs `SIMMIT_SECRET_KEY`; set `SIMMIT_PROFILE_FILE` to also run a full createresult, or `TEST_API_BASE_URL` to target a non-prod endpoint). See `scripts/smoke.mjs`.
225
+ - `pnpm generate` regenerates `src/generated/openapi.d.ts` from the committed `openapi.json` snapshot. Never hand-edit generated output; only `src/api-types.ts` may import from `src/generated/`.
226
+ - `pnpm build` builds dual ESM+CJS via tsup.
227
+ - `pnpm test` runs vitest (hermetic; mocks `fetch`, no network).
228
+ - `pnpm smoke` runs a manual check against a real API (needs `SIMMIT_SECRET_KEY`; set `SIMMIT_PROFILE_FILE` to also run a full create-then-result, or `TEST_API_BASE_URL` to target a non-prod endpoint). See `scripts/smoke.mjs`.
159
229
 
160
230
  ## License
161
231
 
package/dist/index.cjs CHANGED
@@ -49,9 +49,11 @@ __export(index_exports, {
49
49
  ServiceUnavailableError: () => ServiceUnavailableError,
50
50
  Simmit: () => Simmit,
51
51
  SimmitError: () => SimmitError,
52
+ TERMINAL_JOB_STATUSES: () => TERMINAL_JOB_STATUSES,
52
53
  UnprocessableEntityError: () => UnprocessableEntityError,
53
54
  WebhookVerificationError: () => WebhookVerificationError,
54
55
  default: () => Simmit,
56
+ isTerminal: () => isTerminal,
55
57
  unwrapWebhook: () => unwrapWebhook
56
58
  });
57
59
  module.exports = __toCommonJS(index_exports);
@@ -68,7 +70,7 @@ var APIError = class _APIError extends SimmitError {
68
70
  code;
69
71
  /** Typed `meta` from the error envelope. */
70
72
  meta;
71
- /** Raw parsed JSON error body escape hatch for unmapped fields. */
73
+ /** Raw parsed JSON error body: escape hatch for unmapped fields. */
72
74
  error;
73
75
  constructor(status, body, message, headers) {
74
76
  super(_APIError.makeMessage(status, body, message));
@@ -395,7 +397,7 @@ function buildHeaders(config, spec, options) {
395
397
  authorization: `Bearer ${config.secretKey}`,
396
398
  ...spec.body !== void 0 ? { "content-type": "application/json" } : {},
397
399
  ...idempotent && !options.idempotencyKey ? {
398
- // Generated once per call and reused across retry attempts — that
400
+ // Generated once per call and reused across retry attempts. That
399
401
  // is what makes POST retries safe by default. The auto
400
402
  // key is an SDK built-in default (lowest tier), so defaultHeaders
401
403
  // may override it.
@@ -448,7 +450,7 @@ var Artifacts = class {
448
450
  }
449
451
  /**
450
452
  * Fetch a stable public download URL for an artifact, valid for the
451
- * artifact's full retention window the same URL `jobs.getResult` returns,
453
+ * artifact's full retention window: the same URL `jobs.getResult` returns,
452
454
  * fetched on demand (e.g. browser flows that control the final fetch). The
453
455
  * artifact is gone (410) once its retention window passes.
454
456
  */
@@ -485,15 +487,6 @@ var MAX_POLL_INTERVAL_MS = 1e4;
485
487
  var POLL_BACKOFF_FACTOR = 1.5;
486
488
  var DEADLINE_GRACE_MS = 6e4;
487
489
  var FALLBACK_WAIT_TIMEOUT_MS = 45 * 60 * 1e3;
488
- var TERMINAL_STATUSES = /* @__PURE__ */ new Set([
489
- "completed",
490
- "failed",
491
- "cancelled",
492
- "timed_out"
493
- ]);
494
- function isTerminal(status) {
495
- return TERMINAL_STATUSES.has(status);
496
- }
497
490
  function deriveWaitTimeoutMs(created) {
498
491
  const { runtimeSeconds, queueSeconds } = created.runtime.ceiling;
499
492
  if (runtimeSeconds != null && queueSeconds != null) {
@@ -505,6 +498,17 @@ function nextPollInterval(interval) {
505
498
  return Math.min(interval * POLL_BACKOFF_FACTOR, MAX_POLL_INTERVAL_MS);
506
499
  }
507
500
 
501
+ // src/status.ts
502
+ var TERMINAL_JOB_STATUSES = [
503
+ "completed",
504
+ "failed",
505
+ "cancelled",
506
+ "timed_out"
507
+ ];
508
+ function isTerminal(status) {
509
+ return TERMINAL_JOB_STATUSES.includes(status);
510
+ }
511
+
508
512
  // src/resources/jobs.ts
509
513
  var Jobs = class {
510
514
  #client;
@@ -531,7 +535,7 @@ var Jobs = class {
531
535
  );
532
536
  }
533
537
  /**
534
- * Fetch the live status of a job in any state `status`, `errorCode`,
538
+ * Fetch the live status of a job in any state: `status`, `errorCode`,
535
539
  * `progress`, and `queue` estimate. Unlike `getResult`, it never throws for a
536
540
  * non-terminal job, so it is the supported way to drive a custom poll loop.
537
541
  */
@@ -546,7 +550,7 @@ var Jobs = class {
546
550
  }
547
551
  /**
548
552
  * Fetch the result summary of a terminal job. Throws `ResultNotReadyError`
549
- * (409) while the job is still running poll `/status` or use
553
+ * (409) while the job is still running. Poll `/status` or use
550
554
  * `createAndWait` rather than `/result` for a job in flight.
551
555
  */
552
556
  getResult(jobId, options) {
@@ -564,7 +568,7 @@ var Jobs = class {
564
568
  * a 10s cap), then fetches the full record. Resolves with the `CompletedJob`
565
569
  * on success; throws `JobFailedError` / `JobCancelledError` /
566
570
  * `JobTimedOutError` for the other terminal states, or `JobWaitTimeoutError`
567
- * if the deadline passes first the job keeps running and is **not**
571
+ * if the deadline passes first. The job keeps running and is **not**
568
572
  * cancelled (call `cancel(jobId)` to stop the spend). `signal` aborts the wait
569
573
  * with `APIUserAbortError`, also without cancelling.
570
574
  */
@@ -769,8 +773,10 @@ function timingSafeEqual(a, b) {
769
773
  ServiceUnavailableError,
770
774
  Simmit,
771
775
  SimmitError,
776
+ TERMINAL_JOB_STATUSES,
772
777
  UnprocessableEntityError,
773
778
  WebhookVerificationError,
779
+ isTerminal,
774
780
  unwrapWebhook
775
781
  });
776
782
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/error.ts","../src/api-promise.ts","../src/internal/abort.ts","../src/internal/request.ts","../src/resources/artifacts.ts","../src/resources/credits.ts","../src/internal/poll.ts","../src/resources/jobs.ts","../src/client.ts","../src/webhook.ts"],"sourcesContent":["// Public entry point. Surface specified in DESIGN.md.\nexport { default, default as Simmit } from './client'\nexport type { ClientOptions, RequestOptions } from './client'\nexport { APIPromise } from './api-promise'\nexport * from './error'\nexport type * from './api-types'\n// Resource classes are instantiated by the client (`client.jobs`, `client.credits`);\n// exported as types only so callers can annotate without constructing them.\nexport type { Jobs, JobWaitOptions } from './resources/jobs'\nexport type { Credits } from './resources/credits'\nexport type { Artifacts } from './resources/artifacts'\n// Standalone webhook verification — no client (and no secret key) required.\nexport { unwrapWebhook } from './webhook'\nexport type { WebhookEvent } from './webhook'\n","import type { Job, JobStatus } from './api-types'\n\nexport class SimmitError extends Error {}\n\n/**\n * Value shapes the API's generic error `meta` bag can carry\n * (400/401/404/410/413 responses): JSON scalars, scalar arrays, or arrays of\n * flat objects.\n */\nexport type MetaValue =\n | string\n | number\n | boolean\n | null\n | Array<string | number | boolean>\n | Array<Record<string, string | number | boolean | null>>\n\nexport type GenericMeta = Record<string, MetaValue>\n\n/** The API's uniform error envelope: `{ error, code, meta }`. */\ninterface ErrorEnvelope {\n error?: unknown\n code?: unknown\n meta?: unknown\n}\n\nexport class APIError<\n TStatus extends number | undefined = number | undefined,\n TCode extends string | undefined = string | undefined,\n TMeta = GenericMeta | null\n> extends SimmitError {\n /** HTTP status of the response that caused the error. */\n readonly status: TStatus\n /** HTTP headers of the response that caused the error. */\n readonly headers: Headers | undefined\n /** Machine-readable `code` from the error envelope. */\n readonly code: TCode\n /** Typed `meta` from the error envelope. */\n readonly meta: TMeta\n /** Raw parsed JSON error body — escape hatch for unmapped fields. */\n readonly error: object | undefined\n\n constructor(\n status: TStatus,\n body: object | undefined,\n message: string | undefined,\n headers: Headers | undefined\n ) {\n super(APIError.makeMessage(status, body, message))\n this.status = status\n this.headers = headers\n this.error = body\n const envelope = body as ErrorEnvelope | undefined\n this.code = (\n typeof envelope?.code === 'string' ? envelope.code : undefined\n ) as TCode\n this.meta = (body ? (envelope?.meta ?? null) : undefined) as TMeta\n }\n\n private static makeMessage(\n status: number | undefined,\n body: object | undefined,\n message: string | undefined\n ): string {\n // The API's human-readable message field is named `error`, not `message`.\n const bodyMessage = (body as ErrorEnvelope | undefined)?.error\n const msg =\n typeof bodyMessage === 'string'\n ? bodyMessage\n : body\n ? JSON.stringify(body)\n : message\n\n if (status && msg) return `${status} ${msg}`\n if (status) return `${status} status code (no body)`\n if (msg) return msg\n return '(no status code or body)'\n }\n\n /**\n * Maps a response to the most specific error class: status selects the base\n * class; an enumerated `code` with structured `meta` selects the subclass;\n * anything unrecognized falls back to the status class so new server codes\n * degrade gracefully without breaking `instanceof` handling.\n */\n static generate(\n status: number | undefined,\n body: object | undefined,\n message: string | undefined,\n headers: Headers | undefined\n ): APIError<\n number | undefined,\n string | undefined,\n GenericMeta | null | undefined\n > {\n if (!status || !headers) {\n return new APIConnectionError({\n message,\n cause: body instanceof Error ? body : undefined\n })\n }\n\n const code = (body as ErrorEnvelope | undefined)?.code\n\n if (status === 400) return new BadRequestError(400, body, message, headers)\n if (status === 401) {\n return new AuthenticationError(401, body, message, headers)\n }\n if (status === 402) {\n if (code === 'insufficient_credits') {\n return new InsufficientCreditsError(402, body, message, headers)\n }\n if (code === 'insufficient_credits_liability') {\n return new InsufficientCreditsLiabilityError(\n 402,\n body,\n message,\n headers\n )\n }\n return new BillingError(402, body, message, headers)\n }\n if (status === 404) return new NotFoundError(404, body, message, headers)\n if (status === 409) {\n if (code === 'idempotency_key_reuse') {\n return new IdempotencyKeyReuseError(409, body, message, headers)\n }\n if (code === 'result_not_ready') {\n return new ResultNotReadyError(409, body, message, headers)\n }\n if (code === 'job_not_cancellable') {\n return new JobNotCancellableError(409, body, message, headers)\n }\n return new ConflictError(409, body, message, headers)\n }\n if (status === 413) {\n return new RequestTooLargeError(413, body, message, headers)\n }\n if (status === 422) {\n if (code === 'input_sanitized_rejected') {\n return new InvalidProfileError(422, body, message, headers)\n }\n if (code === 'result_unavailable') {\n return new ResultUnavailableError(422, body, message, headers)\n }\n return new UnprocessableEntityError(422, body, message, headers)\n }\n if (status === 429) {\n if (code === 'max_active_jobs_exceeded') {\n return new MaxActiveJobsError(429, body, message, headers)\n }\n return new RateLimitError(429, body, message, headers)\n }\n if (status === 503 && isServiceUnavailableBody(body)) {\n return new ServiceUnavailableError(503, body, message, headers)\n }\n if (status >= 500) {\n return new InternalServerError(status, body, message, headers)\n }\n return new APIError(status, body, message, headers)\n }\n}\n\n// ── 4xx status classes (code subclasses where the spec enumerates) ──────────\n\nexport class BadRequestError extends APIError<400, string> {}\n\nexport type AuthenticationErrorCode =\n | 'missing_token'\n | 'invalid_token'\n | 'revoked_token'\n | 'expired_token'\n\nexport class AuthenticationError extends APIError<\n 401,\n AuthenticationErrorCode\n> {}\n\n// 402 codes are docs-enumerated; the spec leaves `code` un-enumerated, so the\n// base class keeps `string` for forward compatibility.\nexport class BillingError extends APIError<402, string> {}\n\nexport type InsufficientCreditsMeta = {\n reason: string\n ceilingRuntimeSeconds?: number\n /** Largest maxRuntimeSeconds the current balance can cover. */\n maxAffordableRuntimeSeconds?: number\n docsUrl?: string\n}\n\nexport class InsufficientCreditsError extends BillingError {\n declare readonly code: 'insufficient_credits'\n declare readonly meta: InsufficientCreditsMeta | null\n}\n\nexport type InsufficientCreditsLiabilityMeta = {\n reason: string\n /** The high-priority fee in effect — top up, or resubmit at priority 'standard'. */\n priorityFeeCredits: number\n docsUrl?: string\n}\n\nexport class InsufficientCreditsLiabilityError extends BillingError {\n declare readonly code: 'insufficient_credits_liability'\n declare readonly meta: InsufficientCreditsLiabilityMeta | null\n}\n\nexport class NotFoundError extends APIError<404, string> {}\n\nexport class ConflictError extends APIError<409, string> {}\n\nexport class IdempotencyKeyReuseError extends ConflictError {\n declare readonly code: 'idempotency_key_reuse'\n declare readonly meta: {\n reason: 'idempotency_key_reuse'\n /** ID of the job that originally consumed this idempotency key. */\n originalJobId: string\n docsUrl?: string\n }\n}\n\nexport class ResultNotReadyError extends ConflictError {\n declare readonly code: 'result_not_ready'\n declare readonly meta: {\n status: 'pending' | 'queued' | 'starting' | 'running'\n }\n}\n\nexport class JobNotCancellableError extends ConflictError {\n declare readonly code: 'job_not_cancellable'\n declare readonly meta: { id: string; status: JobStatus }\n}\n\nexport class RequestTooLargeError extends APIError<413, string> {}\n\nexport class UnprocessableEntityError extends APIError<422, string> {}\n\nexport class InvalidProfileError extends UnprocessableEntityError {\n declare readonly code: 'input_sanitized_rejected'\n declare readonly meta: {\n reason: 'input_sanitized_rejected'\n message: string\n docsUrl: string\n /** Sample of rejected lines; see blockedCount/blockedTruncated for the full set. */\n blocked: Array<{ line: number; text: string }>\n blockedCount: number\n blockedTruncated: boolean\n }\n}\n\nexport class ResultUnavailableError extends UnprocessableEntityError {\n declare readonly code: 'result_unavailable'\n declare readonly meta: {\n status: 'completed' | 'failed' | 'cancelled' | 'timed_out'\n }\n}\n\nexport type RateLimitErrorCode =\n | 'rate_limit_exceeded'\n | 'max_active_jobs_exceeded'\n\nexport class RateLimitError extends APIError<429, RateLimitErrorCode> {\n declare readonly meta:\n | { scope: 'developer' }\n | {\n reason: 'max_active_jobs_exceeded'\n maxActiveJobs: number\n activeJobs: number\n }\n | null\n}\n\nexport class MaxActiveJobsError extends RateLimitError {\n declare readonly code: 'max_active_jobs_exceeded'\n declare readonly meta: {\n reason: 'max_active_jobs_exceeded'\n /** Maximum number of jobs the account can have in flight. */\n maxActiveJobs: number\n /** Jobs in flight when this request was rejected. */\n activeJobs: number\n }\n}\n\n// ── 5xx ─────────────────────────────────────────────────────────────────────\n\nexport class InternalServerError extends APIError<number, string> {}\n\n/**\n * 503 carries four enumerated codes with distinct meta — a discriminated\n * union, narrowed via `.body`. `api_maintenance` gets no special retry\n * behavior: standard policy applies, and the typed\n * `meta.retryAfterSeconds` is surfaced so callers can schedule their own\n * resubmission.\n */\nexport type ServiceUnavailableBody =\n | {\n code: 'queue_unavailable'\n meta: { reason: 'queue_unavailable'; queueHealth: string }\n }\n | {\n code: 'queue_health_unknown'\n meta: { reason: 'queue_health_unknown'; laneId: string }\n }\n | {\n code: 'secret_store_unavailable'\n meta: { reason: 'secret_store_unavailable' }\n }\n | { code: 'api_maintenance'; meta: { retryAfterSeconds: number } }\n\nconst SERVICE_UNAVAILABLE_CODES = new Set([\n 'queue_unavailable',\n 'queue_health_unknown',\n 'secret_store_unavailable',\n 'api_maintenance'\n])\n\n// A 503 whose body isn't the enumerated envelope (e.g. load-balancer HTML)\n// falls back to InternalServerError so `.body` below never lies.\nfunction isServiceUnavailableBody(\n body: object | undefined\n): body is ServiceUnavailableBody {\n const code = (body as ErrorEnvelope | undefined)?.code\n return typeof code === 'string' && SERVICE_UNAVAILABLE_CODES.has(code)\n}\n\nexport class ServiceUnavailableError extends InternalServerError {\n declare readonly status: 503\n\n /** The discriminated 503 envelope: `if (e.body.code === 'api_maintenance') e.body.meta.retryAfterSeconds`. */\n get body(): ServiceUnavailableBody {\n return this.error as ServiceUnavailableBody\n }\n}\n\n// ── No HTTP response ────────────────────────────────────────────────────────\n\nexport class APIConnectionError extends APIError<\n undefined,\n undefined,\n undefined\n> {\n constructor({\n message,\n cause\n }: { message?: string | undefined; cause?: Error | undefined } = {}) {\n super(undefined, undefined, message ?? 'Connection error.', undefined)\n if (cause) this.cause = cause\n }\n}\n\nexport class APIConnectionTimeoutError extends APIConnectionError {\n constructor({ message }: { message?: string } = {}) {\n super({ message: message ?? 'Request timed out.' })\n }\n}\n\nexport class APIUserAbortError extends APIError<\n undefined,\n undefined,\n undefined\n> {\n constructor({ message }: { message?: string } = {}) {\n super(undefined, undefined, message ?? 'Request was aborted.', undefined)\n }\n}\n\n// ── Job-level errors (thrown only by createAndWait) ──────────────────────────\n\n/** Catch-all for a job that reached a terminal state other than `completed`. */\nexport abstract class JobUnsuccessfulError extends SimmitError {\n readonly job: Job\n\n constructor(job: Job, message?: string) {\n super(\n message ??\n `Job ${job.id} ${job.status}` +\n (job.statusReason ? `: ${job.statusReason}` : '') +\n (job.errorCode ? ` (${job.errorCode})` : '')\n )\n this.job = job\n }\n}\n\nexport class JobFailedError extends JobUnsuccessfulError {}\n\n/** Includes queue_timeout auto-cancellation, not just user cancels. */\nexport class JobCancelledError extends JobUnsuccessfulError {}\n\n/** The job hit its runtime ceiling server-side and is billed for what ran. */\nexport class JobTimedOutError extends JobUnsuccessfulError {}\n\n/**\n * The SDK gave up polling — the job itself is still running and billing.\n * Keep tracking via `jobs.get(jobId)` or stop the spend with `jobs.cancel(jobId)`.\n */\nexport class JobWaitTimeoutError extends SimmitError {\n readonly jobId: string\n readonly lastStatus: JobStatus\n\n constructor(args: {\n jobId: string\n lastStatus: JobStatus\n message?: string\n }) {\n super(\n args.message ??\n `Timed out waiting for job ${args.jobId} (last status: ${args.lastStatus}). ` +\n 'The job is still running server-side and continues to bill.'\n )\n this.jobId = args.jobId\n this.lastStatus = args.lastStatus\n }\n}\n\n// ── Webhook verification (thrown by unwrapWebhook) ───────────────────────────\n\nexport class WebhookVerificationError extends SimmitError {}\n","/**\n * A `Promise<T>` with raw-response access — the generic answer to response\n * headers the return types can't see (`X-Idempotent-Replay`, `X-Active-Jobs`,\n * `X-RateLimit-*`).\n *\n * const { data, response } = await client.jobs.create(params).withResponse()\n * response.headers.get('x-idempotent-replay')\n */\nexport class APIPromise<T> extends Promise<T> {\n // Chained promises (.then/.catch) must be plain Promises: this class's\n // constructor signature is incompatible with the executor the runtime\n // would otherwise pass via the species constructor.\n static override get [Symbol.species]() {\n return Promise\n }\n\n readonly #parsed: Promise<{ data: T; response: Response }>\n\n constructor(parsed: Promise<{ data: T; response: Response }>) {\n // The base promise is a pre-settled placeholder that is never observed:\n // then() below delegates to #parsed lazily (catch/finally route through\n // then() per spec). Subscribing eagerly here would reject this instance\n // even when the caller only consumes withResponse(), leaking an\n // unhandled rejection on failures.\n super((resolve) => resolve(undefined as never))\n this.#parsed = parsed\n }\n\n override then<TResult1 = T, TResult2 = never>(\n onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null,\n onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null\n ): Promise<TResult1 | TResult2> {\n return this.#parsed\n .then((result) => result.data)\n .then(onfulfilled, onrejected)\n }\n\n withResponse(): Promise<{ data: T; response: Response }> {\n return this.#parsed\n }\n\n asResponse(): Promise<Response> {\n return this.#parsed.then((result) => result.response)\n }\n}\n","// Abort-aware timing utilities shared by the request layer (backoff sleeps) and\n// the createAndWait poll loop. A pending sleep rejects with APIUserAbortError\n// the moment the caller's signal fires.\nimport { APIUserAbortError } from '../error'\n\nexport function throwIfUserAborted(signal: AbortSignal | undefined): void {\n if (signal?.aborted) throw new APIUserAbortError()\n}\n\nexport function sleep(\n ms: number,\n signal: AbortSignal | undefined\n): Promise<void> {\n return new Promise((resolve, reject) => {\n throwIfUserAborted(signal)\n const timeoutId = setTimeout(() => {\n signal?.removeEventListener('abort', onAbort)\n resolve()\n }, ms)\n const onAbort = () => {\n clearTimeout(timeoutId)\n reject(new APIUserAbortError())\n }\n signal?.addEventListener('abort', onAbort, { once: true })\n })\n}\n","// Internal request layer: header assembly, per-attempt timeout/abort\n// composition, retry with backoff + Retry-After, idempotency-key injection,\n// and error mapping. Not exported from the package.\nimport { APIPromise } from '../api-promise'\nimport {\n APIConnectionError,\n APIConnectionTimeoutError,\n APIError,\n APIUserAbortError\n} from '../error'\nimport type { RequestOptions } from '../client'\nimport { sleep, throwIfUserAborted } from './abort'\n\nexport interface ClientConfig {\n secretKey: string\n baseURL: string\n timeout: number\n maxRetries: number\n defaultHeaders: Record<string, string | null | undefined> | undefined\n fetch: typeof globalThis.fetch\n fetchOptions: RequestInit | undefined\n}\n\nexport interface RequestSpec {\n method: 'GET' | 'POST'\n path: string\n body?: unknown\n /** POST job creation: auto-generate an idempotency-key when none supplied. */\n idempotent?: boolean\n}\n\n// Retry policy constants — typed code config, not env.\nconst INITIAL_BACKOFF_MS = 500\nconst MAX_BACKOFF_MS = 8_000\nconst MAX_RETRY_AFTER_MS = 60_000\n\nexport function makeRequest<T>(\n config: ClientConfig,\n spec: RequestSpec,\n options: RequestOptions = {}\n): APIPromise<T> {\n return new APIPromise(run<T>(config, spec, options))\n}\n\nasync function run<T>(\n config: ClientConfig,\n spec: RequestSpec,\n options: RequestOptions\n): Promise<{ data: T; response: Response }> {\n const maxRetries = options.maxRetries ?? config.maxRetries\n const timeout = options.timeout ?? config.timeout\n const headers = buildHeaders(config, spec, options)\n const url = `${config.baseURL.replace(/\\/+$/, '')}${spec.path}`\n const body = spec.body === undefined ? undefined : JSON.stringify(spec.body)\n\n for (let attempt = 0; ; attempt++) {\n throwIfUserAborted(options.signal)\n\n let result: AttemptResult\n try {\n result = await fetchAttempt(config, spec, options, {\n url,\n headers,\n body,\n timeout\n })\n } catch (err) {\n if (err instanceof APIUserAbortError) throw err\n // Connection error, malformed success body, or per-attempt timeout —\n // all retryable.\n if (attempt < maxRetries) {\n await backoff(attempt, undefined, options.signal)\n continue\n }\n throw err\n }\n\n const { response, json } = result\n\n if (response.ok) {\n return { data: json as T, response }\n }\n\n if (shouldRetryStatus(response.status) && attempt < maxRetries) {\n await backoff(\n attempt,\n response.headers.get('retry-after'),\n options.signal\n )\n continue\n }\n\n throw APIError.generate(\n response.status,\n typeof json === 'object' && json !== null ? json : undefined,\n response.statusText,\n response.headers\n )\n }\n}\n\ninterface AttemptResult {\n response: Response\n /** Parsed JSON body; undefined when an error response carried a non-JSON body. */\n json: unknown\n}\n\nasync function fetchAttempt(\n config: ClientConfig,\n spec: RequestSpec,\n options: RequestOptions,\n attempt: {\n url: string\n headers: Record<string, string>\n body: string | undefined\n timeout: number\n }\n): Promise<AttemptResult> {\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), attempt.timeout)\n const onUserAbort = () => controller.abort()\n options.signal?.addEventListener('abort', onUserAbort, { once: true })\n\n try {\n const response = await config.fetch(attempt.url, {\n ...config.fetchOptions,\n method: spec.method,\n headers: attempt.headers,\n ...(attempt.body !== undefined ? { body: attempt.body } : {}),\n signal: controller.signal\n })\n\n // The body is read inside the timed scope too — a stalled body must not\n // hang past the per-attempt timeout (fetch ties the body stream to the\n // controller's signal, so the abort cancels the read).\n let json: unknown\n if (response.ok) {\n // Success bodies must parse; a truncated/malformed one is treated as a\n // transport failure (classified below) and retried like one.\n json = await response.json()\n } else {\n try {\n json = await response.json()\n } catch (err) {\n // Aborted mid-read is a timeout/abort, not a non-JSON body.\n if (controller.signal.aborted) throw err\n json = undefined // e.g. a load-balancer HTML error page\n }\n }\n return { response, json }\n } catch (err) {\n if (options.signal?.aborted) throw new APIUserAbortError()\n if (controller.signal.aborted) {\n throw new APIConnectionTimeoutError()\n }\n throw new APIConnectionError({\n cause: err instanceof Error ? err : undefined\n })\n } finally {\n clearTimeout(timeoutId)\n options.signal?.removeEventListener('abort', onUserAbort)\n }\n}\n\nfunction buildHeaders(\n config: ClientConfig,\n spec: RequestSpec,\n options: RequestOptions\n): Record<string, string> {\n const idempotent = spec.idempotent && spec.method === 'POST'\n const merged: Record<string, string | null | undefined> = {\n authorization: `Bearer ${config.secretKey}`,\n ...(spec.body !== undefined ? { 'content-type': 'application/json' } : {}),\n ...(idempotent && !options.idempotencyKey\n ? {\n // Generated once per call and reused across retry attempts — that\n // is what makes POST retries safe by default. The auto\n // key is an SDK built-in default (lowest tier), so defaultHeaders\n // may override it.\n 'idempotency-key': `simmit-node-retry-${crypto.randomUUID()}`\n }\n : {}),\n ...lowercaseKeys(config.defaultHeaders),\n ...(idempotent && options.idempotencyKey\n ? {\n // An explicit key is a per-request option: it must beat constructor\n // defaultHeaders. Raw options.headers still wins last.\n 'idempotency-key': options.idempotencyKey\n }\n : {}),\n ...lowercaseKeys(options.headers)\n }\n\n const headers: Record<string, string> = {}\n for (const [key, value] of Object.entries(merged)) {\n // A null value deletes the header; undefined entries are skipped.\n if (typeof value === 'string') headers[key] = value\n }\n return headers\n}\n\nfunction lowercaseKeys(\n record: Record<string, string | null | undefined> | undefined\n): Record<string, string | null | undefined> {\n if (!record) return {}\n return Object.fromEntries(\n Object.entries(record).map(([key, value]) => [key.toLowerCase(), value])\n )\n}\n\nfunction shouldRetryStatus(status: number): boolean {\n // 408 kept defensively even though the API never emits it. 409 is never\n // retried: result_not_ready is thrown immediately by design and the other\n // 409s are deterministic.\n return status === 408 || status === 429 || status >= 500\n}\n\nasync function backoff(\n attempt: number,\n retryAfterHeader: string | null | undefined,\n signal: AbortSignal | undefined\n): Promise<void> {\n const retryAfterMs = parseRetryAfter(retryAfterHeader)\n const delay =\n retryAfterMs !== undefined\n ? retryAfterMs\n : Math.min(INITIAL_BACKOFF_MS * 2 ** attempt, MAX_BACKOFF_MS) *\n (1 - 0.25 * Math.random())\n await sleep(delay, signal)\n}\n\n/** Accepts `Retry-After` only when it parses to a delay in (0, 60s] — the SDK never sleeps arbitrarily long on a server hint. */\nfunction parseRetryAfter(\n header: string | null | undefined\n): number | undefined {\n if (!header) return undefined\n let ms: number\n if (/^\\d+$/.test(header.trim())) {\n ms = Number(header.trim()) * 1000\n } else {\n ms = new Date(header).getTime() - Date.now()\n }\n return Number.isFinite(ms) && ms > 0 && ms <= MAX_RETRY_AFTER_MS\n ? ms\n : undefined\n}\n","import type { APIPromise } from '../api-promise'\nimport type { ArtifactUrl } from '../api-types'\nimport type Simmit from '../client'\nimport type { RequestOptions } from '../client'\n\n/** The `artifacts` resource. */\nexport class Artifacts {\n readonly #client: Simmit\n\n constructor(client: Simmit) {\n this.#client = client\n }\n\n /**\n * Fetch a stable public download URL for an artifact, valid for the\n * artifact's full retention window — the same URL `jobs.getResult` returns,\n * fetched on demand (e.g. browser flows that control the final fetch). The\n * artifact is gone (410) once its retention window passes.\n */\n getUrl(\n artifactId: string,\n options?: RequestOptions\n ): APIPromise<ArtifactUrl> {\n return this.#client._request<ArtifactUrl>(\n {\n method: 'GET',\n path: `/v1/simc/artifacts/${encodeURIComponent(artifactId)}/url`\n },\n options\n )\n }\n}\n","import type { APIPromise } from '../api-promise'\nimport type { CreditBalance } from '../api-types'\nimport type Simmit from '../client'\nimport type { RequestOptions } from '../client'\n\n/** The `credits` resource. */\nexport class Credits {\n readonly #client: Simmit\n\n constructor(client: Simmit) {\n this.#client = client\n }\n\n /** Fetch the account's current credit balance and per-grant breakdown. */\n get(options?: RequestOptions): APIPromise<CreditBalance> {\n return this.#client._request<CreditBalance>(\n { method: 'GET', path: '/v1/simc/credits' },\n options\n )\n }\n}\n","// Pure helpers for the createAndWait poll loop. Kept separate from the\n// orchestration so the cadence and deadline math are unit-testable.\nimport type { JobCreateResponse, JobStatus } from '../api-types'\n\nexport const MIN_POLL_INTERVAL_MS = 100\nexport const DEFAULT_POLL_INTERVAL_MS = 1_000\nexport const MAX_POLL_INTERVAL_MS = 10_000\nexport const POLL_BACKOFF_FACTOR = 1.5\nconst DEADLINE_GRACE_MS = 60_000\nconst FALLBACK_WAIT_TIMEOUT_MS = 45 * 60 * 1_000\n\nconst TERMINAL_STATUSES = new Set<JobStatus>([\n 'completed',\n 'failed',\n 'cancelled',\n 'timed_out'\n])\n\nexport function isTerminal(status: JobStatus): boolean {\n return TERMINAL_STATUSES.has(status)\n}\n\n/**\n * Default wait deadline: the applied ceilings the create response reports —\n * `(queueSeconds + runtimeSeconds) × 1000` plus a 60s grace — or a 45-minute\n * fallback when either ceiling is null.\n */\nexport function deriveWaitTimeoutMs(created: JobCreateResponse): number {\n const { runtimeSeconds, queueSeconds } = created.runtime.ceiling\n if (runtimeSeconds != null && queueSeconds != null) {\n return (runtimeSeconds + queueSeconds) * 1_000 + DEADLINE_GRACE_MS\n }\n return FALLBACK_WAIT_TIMEOUT_MS\n}\n\n/** Next poll interval: grow ×1.5, capped at 10s. */\nexport function nextPollInterval(interval: number): number {\n return Math.min(interval * POLL_BACKOFF_FACTOR, MAX_POLL_INTERVAL_MS)\n}\n","import type { APIPromise } from '../api-promise'\nimport type {\n CompletedJob,\n Job,\n JobCancelResponse,\n JobCreateParams,\n JobCreateResponse,\n JobResult,\n JobStatus,\n JobStatusResponse\n} from '../api-types'\nimport type Simmit from '../client'\nimport type { RequestOptions } from '../client'\nimport {\n JobCancelledError,\n JobFailedError,\n JobTimedOutError,\n JobWaitTimeoutError\n} from '../error'\nimport { sleep } from '../internal/abort'\nimport {\n DEFAULT_POLL_INTERVAL_MS,\n deriveWaitTimeoutMs,\n isTerminal,\n MIN_POLL_INTERVAL_MS,\n nextPollInterval\n} from '../internal/poll'\n\nexport interface JobWaitOptions extends RequestOptions {\n /** Initial delay between status polls, ms. Grows ×1.5 per poll to a 10s cap; values under 100 are raised to it. Default 1_000. */\n pollIntervalMs?: number\n /** Overall wait deadline, ms. Default derived from the job's applied ceilings. */\n waitTimeoutMs?: number\n /** Fired once with the raw create response (job id, ceilings, input warnings) before polling. */\n onCreated?: (response: JobCreateResponse) => void\n /** Fired after every successful status poll (progress, stage, queue estimate). */\n onPoll?: (status: JobStatusResponse) => void\n}\n\n/**\n * The `jobs` resource. Each single-request method is a thin wrapper over\n * `client._request` with the path/method/types pinned to the spec;\n * `createAndWait` orchestrates several of them.\n */\nexport class Jobs {\n readonly #client: Simmit\n\n constructor(client: Simmit) {\n this.#client = client\n }\n\n /**\n * Submit a new SimC sim. Returns immediately with the job handle; the sim\n * runs asynchronously. `idempotent: true` makes the request layer attach an\n * auto-generated idempotency key so the POST is safe to retry; pass\n * `options.idempotencyKey` to supply your own.\n */\n create(\n params: JobCreateParams,\n options?: RequestOptions\n ): APIPromise<JobCreateResponse> {\n return this.#client._request<JobCreateResponse>(\n { method: 'POST', path: '/v1/simc/jobs', body: params, idempotent: true },\n options\n )\n }\n\n /** Fetch the full record for a job. */\n get(jobId: string, options?: RequestOptions): APIPromise<Job> {\n return this.#client._request<Job>(\n { method: 'GET', path: `/v1/simc/jobs/${encodeURIComponent(jobId)}` },\n options\n )\n }\n\n /**\n * Fetch the live status of a job in any state — `status`, `errorCode`,\n * `progress`, and `queue` estimate. Unlike `getResult`, it never throws for a\n * non-terminal job, so it is the supported way to drive a custom poll loop.\n */\n getStatus(\n jobId: string,\n options?: RequestOptions\n ): APIPromise<JobStatusResponse> {\n return this.#client._request<JobStatusResponse>(\n {\n method: 'GET',\n path: `/v1/simc/jobs/${encodeURIComponent(jobId)}/status`\n },\n options\n )\n }\n\n /**\n * Fetch the result summary of a terminal job. Throws `ResultNotReadyError`\n * (409) while the job is still running — poll `/status` or use\n * `createAndWait` rather than `/result` for a job in flight.\n */\n getResult(jobId: string, options?: RequestOptions): APIPromise<JobResult> {\n return this.#client._request<JobResult>(\n {\n method: 'GET',\n path: `/v1/simc/jobs/${encodeURIComponent(jobId)}/result`\n },\n options\n )\n }\n\n /**\n * Submit a job and resolve once it reaches a terminal state. Polls\n * `GET /v1/simc/jobs/{id}/status` (first after `pollIntervalMs`, then ×1.5 to\n * a 10s cap), then fetches the full record. Resolves with the `CompletedJob`\n * on success; throws `JobFailedError` / `JobCancelledError` /\n * `JobTimedOutError` for the other terminal states, or `JobWaitTimeoutError`\n * if the deadline passes first — the job keeps running and is **not**\n * cancelled (call `cancel(jobId)` to stop the spend). `signal` aborts the wait\n * with `APIUserAbortError`, also without cancelling.\n */\n async createAndWait(\n params: JobCreateParams,\n options: JobWaitOptions = {}\n ): Promise<CompletedJob> {\n const {\n pollIntervalMs,\n waitTimeoutMs,\n onCreated,\n onPoll,\n ...requestOptions\n } = options\n\n const created = await this.create(params, requestOptions)\n onCreated?.(created)\n\n const deadline =\n Date.now() + (waitTimeoutMs ?? deriveWaitTimeoutMs(created))\n // nextPollInterval only ever grows the interval, so a non-positive seed\n // would hot-poll the status endpoint; floor it.\n let interval = Math.max(\n pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS,\n MIN_POLL_INTERVAL_MS\n )\n let lastStatus: JobStatus = 'pending'\n\n for (;;) {\n const remaining = deadline - Date.now()\n if (remaining <= 0) {\n throw new JobWaitTimeoutError({ jobId: created.id, lastStatus })\n }\n // Never sleep past the deadline, so the wait gives up promptly.\n await sleep(Math.min(interval, remaining), requestOptions.signal)\n\n const status = await this.getStatus(created.id, requestOptions)\n onPoll?.(status)\n lastStatus = status.status\n\n if (isTerminal(status.status)) {\n // The status payload is lightweight; the full record carries the fields\n // CompletedJob and the job-error classes expose.\n const job = await this.get(created.id, requestOptions)\n switch (job.status) {\n case 'completed':\n return job as CompletedJob\n case 'failed':\n throw new JobFailedError(job)\n case 'cancelled':\n throw new JobCancelledError(job)\n case 'timed_out':\n throw new JobTimedOutError(job)\n }\n // Raced back to non-terminal between /status and the full record; keep polling.\n lastStatus = job.status\n }\n interval = nextPollInterval(interval)\n }\n }\n\n /**\n * Request cancellation. Returns `status: 'cancelled'` when the job ended\n * before it ran, or `status: 'cancel_requested'` when an in-flight job was\n * signaled to stop. Repeat calls are naturally idempotent, so no key is sent.\n */\n cancel(\n jobId: string,\n options?: RequestOptions\n ): APIPromise<JobCancelResponse> {\n return this.#client._request<JobCancelResponse>(\n {\n method: 'POST',\n path: `/v1/simc/jobs/${encodeURIComponent(jobId)}/cancel`\n },\n options\n )\n }\n}\n","import { APIPromise } from './api-promise'\nimport { SimmitError } from './error'\nimport {\n makeRequest,\n type ClientConfig,\n type RequestSpec\n} from './internal/request'\nimport { Artifacts } from './resources/artifacts'\nimport { Credits } from './resources/credits'\nimport { Jobs } from './resources/jobs'\n\nexport interface ClientOptions {\n /** Defaults to process.env['SIMMIT_SECRET_KEY'] — exactly one env fallback. Construction\n * throws SimmitError('Missing secret key. Pass secretKey or set SIMMIT_SECRET_KEY.').\n * \"Secret key\" is the credential noun end to end (dashboard → docs → env var → option →\n * error): it spends credits and must never ship client-side. */\n secretKey?: string | null\n /** Defaults to process.env['SIMMIT_BASE_URL'] ?? 'https://api.simmit.com'. */\n baseURL?: string | null\n /** Per-attempt timeout in ms. Default 60_000. (Retries can extend total wall time.) */\n timeout?: number\n /** Max retries after the first attempt for retryable failures. Default 2. */\n maxRetries?: number\n /** Headers sent with every request. Merged under per-request headers. */\n defaultHeaders?: Record<string, string | null | undefined>\n /** Custom fetch (testing, proxies). Defaults to globalThis.fetch. */\n fetch?: typeof globalThis.fetch\n /** Extra RequestInit fields passed to every fetch call (e.g. undici dispatcher). */\n fetchOptions?: RequestInit\n}\n\nexport interface RequestOptions {\n /** Per-attempt timeout in ms. Overrides ClientOptions.timeout. */\n timeout?: number\n /** Abort the call (including retries and waiting). Throws APIUserAbortError. Never retried. */\n signal?: AbortSignal\n /** Overrides ClientOptions.maxRetries for this call. */\n maxRetries?: number\n /** Merged over defaultHeaders; a null value deletes the header. */\n headers?: Record<string, string | null | undefined>\n /** jobs.create / jobs.createAndWait only: replaces the auto-generated idempotency-key. */\n idempotencyKey?: string\n}\n\nexport default class Simmit {\n readonly jobs: Jobs\n readonly credits: Credits\n readonly artifacts: Artifacts\n\n readonly baseURL: string\n\n readonly #config: ClientConfig\n\n constructor(options: ClientOptions = {}) {\n const secretKey = options.secretKey ?? readEnv('SIMMIT_SECRET_KEY')\n if (!secretKey) {\n throw new SimmitError(\n 'Missing secret key. Pass secretKey or set SIMMIT_SECRET_KEY.'\n )\n }\n\n this.baseURL =\n options.baseURL ?? readEnv('SIMMIT_BASE_URL') ?? 'https://api.simmit.com'\n\n this.#config = {\n secretKey,\n baseURL: this.baseURL,\n timeout: options.timeout ?? 60_000,\n maxRetries: options.maxRetries ?? 2,\n defaultHeaders: options.defaultHeaders,\n // Resolved lazily so a fetch patched onto globalThis after the client\n // is constructed (msw, APM instrumentation) is still honored.\n fetch: options.fetch ?? ((...args) => globalThis.fetch(...args)),\n fetchOptions: options.fetchOptions\n }\n\n this.jobs = new Jobs(this)\n this.credits = new Credits(this)\n this.artifacts = new Artifacts(this)\n }\n\n /** @internal Resource classes route through here; not public surface. */\n _request<T>(spec: RequestSpec, options?: RequestOptions): APIPromise<T> {\n return makeRequest(this.#config, spec, options)\n }\n}\n\nfunction readEnv(name: string): string | undefined {\n if (typeof process === 'undefined') return undefined\n const value = process.env?.[name]?.trim()\n return value || undefined\n}\n","// Standalone webhook verification. Not a client method: receivers must not\n// need a secret-key-bearing client (whose constructor throws without a key).\n// WebCrypto only — zero deps, and runs in Workers as well as Node.\nimport type { JobStatus } from './api-types'\nimport { WebhookVerificationError } from './error'\n\nconst DEFAULT_TOLERANCE_SECONDS = 300\n\n/** The one hand-written wire type: the webhook payload has no OpenAPI schema. */\nexport interface WebhookEvent {\n kind: 'job.terminal'\n version: 'v1'\n timestamp: string\n payload: {\n id: string\n statusReason: string | null\n status: Extract<\n JobStatus,\n 'completed' | 'failed' | 'cancelled' | 'timed_out'\n >\n }\n}\n\n/**\n * Verifies an `X-Simmit-Signature` header — `t=<unix>,v1=<hex>`, an HMAC-SHA256\n * (timing-safe) over `${t}.${rawBody}` within a 300s default tolerance — and\n * returns the parsed event. Throws `WebhookVerificationError` on a bad\n * signature, malformed header, or stale timestamp.\n *\n * Pass `rawBody` exactly as received: re-serializing changes the bytes and\n * breaks verification. `secret` is the webhook signing secret (dashboard →\n * Clients & Keys → Webhook), not your API key.\n */\nexport async function unwrapWebhook(\n rawBody: string,\n signatureHeader: string,\n secret: string,\n options?: { toleranceSeconds?: number }\n): Promise<WebhookEvent> {\n // An empty secret would otherwise surface as an opaque WebCrypto DataError;\n // a NaN tolerance would make the age check pass for everything.\n if (!secret) {\n throw new WebhookVerificationError('Webhook signing secret is empty.')\n }\n const tolerance = options?.toleranceSeconds ?? DEFAULT_TOLERANCE_SECONDS\n if (!Number.isFinite(tolerance) || tolerance < 0) {\n throw new WebhookVerificationError(\n 'toleranceSeconds must be a non-negative number.'\n )\n }\n\n const { timestampRaw, timestamp, signature } =\n parseSignatureHeader(signatureHeader)\n\n const expected = await hmacSha256Hex(secret, `${timestampRaw}.${rawBody}`)\n if (!timingSafeEqual(expected, signature)) {\n throw new WebhookVerificationError('Webhook signature does not match.')\n }\n\n // Compare on whole seconds, matching the header's unix-seconds `t`.\n if (Math.abs(Math.floor(Date.now() / 1000) - timestamp) > tolerance) {\n throw new WebhookVerificationError(\n 'Webhook timestamp is outside the tolerance window.'\n )\n }\n\n try {\n return JSON.parse(rawBody) as WebhookEvent\n } catch {\n throw new WebhookVerificationError('Webhook body is not valid JSON.')\n }\n}\n\nfunction parseSignatureHeader(header: string): {\n timestampRaw: string\n timestamp: number\n signature: string\n} {\n let timestampRaw: string | undefined\n let signature: string | undefined\n for (const part of header.split(',')) {\n const eq = part.indexOf('=')\n if (eq === -1) continue\n const key = part.slice(0, eq).trim()\n const value = part.slice(eq + 1).trim()\n if (key === 't') timestampRaw = value\n else if (key === 'v1') signature = value\n }\n\n // `t` is unix whole seconds; reject anything but digits so the accepted\n // header matches the documented contract.\n if (!timestampRaw || !signature || !/^\\d+$/.test(timestampRaw)) {\n throw new WebhookVerificationError(\n 'Malformed signature header; expected \"t=<unix>,v1=<hex>\".'\n )\n }\n // The signed payload uses the timestamp exactly as sent, so keep the raw\n // string for signing and the parsed number only for the tolerance check.\n return { timestampRaw, timestamp: Number(timestampRaw), signature }\n}\n\nasync function hmacSha256Hex(secret: string, payload: string): Promise<string> {\n const encoder = new TextEncoder()\n const key = await crypto.subtle.importKey(\n 'raw',\n encoder.encode(secret),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign']\n )\n const mac = await crypto.subtle.sign('HMAC', key, encoder.encode(payload))\n return toHex(new Uint8Array(mac))\n}\n\nfunction toHex(bytes: Uint8Array): string {\n let hex = ''\n for (const byte of bytes) hex += byte.toString(16).padStart(2, '0')\n return hex\n}\n\n// Constant-time comparison. The digest width is public, so a length mismatch\n// may short-circuit without leaking secret-dependent timing.\nfunction timingSafeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) return false\n let mismatch = 0\n for (let i = 0; i < a.length; i++) {\n mismatch |= a.charCodeAt(i) ^ b.charCodeAt(i)\n }\n return mismatch === 0\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/error.ts","../src/api-promise.ts","../src/internal/abort.ts","../src/internal/request.ts","../src/resources/artifacts.ts","../src/resources/credits.ts","../src/internal/poll.ts","../src/status.ts","../src/resources/jobs.ts","../src/client.ts","../src/webhook.ts"],"sourcesContent":["// Public entry point. Surface specified in DESIGN.md.\nexport { default, default as Simmit } from './client'\nexport type { ClientOptions, RequestOptions } from './client'\nexport { APIPromise } from './api-promise'\nexport * from './error'\nexport type * from './api-types'\n// Resource classes are instantiated by the client (`client.jobs`, `client.credits`);\n// exported as types only so callers can annotate without constructing them.\nexport type { Jobs, JobWaitOptions } from './resources/jobs'\nexport type { Credits } from './resources/credits'\nexport type { Artifacts } from './resources/artifacts'\n// Standalone webhook verification: no client (and no secret key) required.\nexport { unwrapWebhook } from './webhook'\nexport type { WebhookEvent } from './webhook'\n// Status helpers: pure, no client required.\nexport { isTerminal, TERMINAL_JOB_STATUSES } from './status'\nexport type { TerminalJobStatus } from './status'\n","import type { Job, JobStatus } from './api-types'\n\nexport class SimmitError extends Error {}\n\n/**\n * Value shapes the API's generic error `meta` bag can carry\n * (400/401/404/410/413 responses): JSON scalars, scalar arrays, or arrays of\n * flat objects.\n */\nexport type MetaValue =\n | string\n | number\n | boolean\n | null\n | Array<string | number | boolean>\n | Array<Record<string, string | number | boolean | null>>\n\nexport type GenericMeta = Record<string, MetaValue>\n\n/** The API's uniform error envelope: `{ error, code, meta }`. */\ninterface ErrorEnvelope {\n error?: unknown\n code?: unknown\n meta?: unknown\n}\n\nexport class APIError<\n TStatus extends number | undefined = number | undefined,\n TCode extends string | undefined = string | undefined,\n TMeta = GenericMeta | null\n> extends SimmitError {\n /** HTTP status of the response that caused the error. */\n readonly status: TStatus\n /** HTTP headers of the response that caused the error. */\n readonly headers: Headers | undefined\n /** Machine-readable `code` from the error envelope. */\n readonly code: TCode\n /** Typed `meta` from the error envelope. */\n readonly meta: TMeta\n /** Raw parsed JSON error body: escape hatch for unmapped fields. */\n readonly error: object | undefined\n\n constructor(\n status: TStatus,\n body: object | undefined,\n message: string | undefined,\n headers: Headers | undefined\n ) {\n super(APIError.makeMessage(status, body, message))\n this.status = status\n this.headers = headers\n this.error = body\n const envelope = body as ErrorEnvelope | undefined\n this.code = (\n typeof envelope?.code === 'string' ? envelope.code : undefined\n ) as TCode\n this.meta = (body ? (envelope?.meta ?? null) : undefined) as TMeta\n }\n\n private static makeMessage(\n status: number | undefined,\n body: object | undefined,\n message: string | undefined\n ): string {\n // The API's human-readable message field is named `error`, not `message`.\n const bodyMessage = (body as ErrorEnvelope | undefined)?.error\n const msg =\n typeof bodyMessage === 'string'\n ? bodyMessage\n : body\n ? JSON.stringify(body)\n : message\n\n if (status && msg) return `${status} ${msg}`\n if (status) return `${status} status code (no body)`\n if (msg) return msg\n return '(no status code or body)'\n }\n\n /**\n * Maps a response to the most specific error class: status selects the base\n * class; an enumerated `code` with structured `meta` selects the subclass;\n * anything unrecognized falls back to the status class so new server codes\n * degrade gracefully without breaking `instanceof` handling.\n */\n static generate(\n status: number | undefined,\n body: object | undefined,\n message: string | undefined,\n headers: Headers | undefined\n ): APIError<\n number | undefined,\n string | undefined,\n GenericMeta | null | undefined\n > {\n if (!status || !headers) {\n return new APIConnectionError({\n message,\n cause: body instanceof Error ? body : undefined\n })\n }\n\n const code = (body as ErrorEnvelope | undefined)?.code\n\n if (status === 400) return new BadRequestError(400, body, message, headers)\n if (status === 401) {\n return new AuthenticationError(401, body, message, headers)\n }\n if (status === 402) {\n if (code === 'insufficient_credits') {\n return new InsufficientCreditsError(402, body, message, headers)\n }\n if (code === 'insufficient_credits_liability') {\n return new InsufficientCreditsLiabilityError(\n 402,\n body,\n message,\n headers\n )\n }\n return new BillingError(402, body, message, headers)\n }\n if (status === 404) return new NotFoundError(404, body, message, headers)\n if (status === 409) {\n if (code === 'idempotency_key_reuse') {\n return new IdempotencyKeyReuseError(409, body, message, headers)\n }\n if (code === 'result_not_ready') {\n return new ResultNotReadyError(409, body, message, headers)\n }\n if (code === 'job_not_cancellable') {\n return new JobNotCancellableError(409, body, message, headers)\n }\n return new ConflictError(409, body, message, headers)\n }\n if (status === 413) {\n return new RequestTooLargeError(413, body, message, headers)\n }\n if (status === 422) {\n if (code === 'input_sanitized_rejected') {\n return new InvalidProfileError(422, body, message, headers)\n }\n if (code === 'result_unavailable') {\n return new ResultUnavailableError(422, body, message, headers)\n }\n return new UnprocessableEntityError(422, body, message, headers)\n }\n if (status === 429) {\n if (code === 'max_active_jobs_exceeded') {\n return new MaxActiveJobsError(429, body, message, headers)\n }\n return new RateLimitError(429, body, message, headers)\n }\n if (status === 503 && isServiceUnavailableBody(body)) {\n return new ServiceUnavailableError(503, body, message, headers)\n }\n if (status >= 500) {\n return new InternalServerError(status, body, message, headers)\n }\n return new APIError(status, body, message, headers)\n }\n}\n\n// ── 4xx status classes (code subclasses where the spec enumerates) ──────────\n\nexport class BadRequestError extends APIError<400, string> {}\n\nexport type AuthenticationErrorCode =\n | 'missing_token'\n | 'invalid_token'\n | 'revoked_token'\n | 'expired_token'\n\nexport class AuthenticationError extends APIError<\n 401,\n AuthenticationErrorCode\n> {}\n\n// 402 codes are docs-enumerated; the spec leaves `code` un-enumerated, so the\n// base class keeps `string` for forward compatibility.\nexport class BillingError extends APIError<402, string> {}\n\nexport type InsufficientCreditsMeta = {\n reason: string\n ceilingRuntimeSeconds?: number\n /** Largest maxRuntimeSeconds the current balance can cover. */\n maxAffordableRuntimeSeconds?: number\n docsUrl?: string\n}\n\nexport class InsufficientCreditsError extends BillingError {\n declare readonly code: 'insufficient_credits'\n declare readonly meta: InsufficientCreditsMeta | null\n}\n\nexport type InsufficientCreditsLiabilityMeta = {\n reason: string\n /** The high-priority fee in effect. Top up, or resubmit at priority 'standard'. */\n priorityFeeCredits: number\n docsUrl?: string\n}\n\nexport class InsufficientCreditsLiabilityError extends BillingError {\n declare readonly code: 'insufficient_credits_liability'\n declare readonly meta: InsufficientCreditsLiabilityMeta | null\n}\n\nexport class NotFoundError extends APIError<404, string> {}\n\nexport class ConflictError extends APIError<409, string> {}\n\nexport class IdempotencyKeyReuseError extends ConflictError {\n declare readonly code: 'idempotency_key_reuse'\n declare readonly meta: {\n reason: 'idempotency_key_reuse'\n /** ID of the job that originally consumed this idempotency key. */\n originalJobId: string\n docsUrl?: string\n }\n}\n\nexport class ResultNotReadyError extends ConflictError {\n declare readonly code: 'result_not_ready'\n declare readonly meta: {\n status: 'pending' | 'queued' | 'starting' | 'running'\n }\n}\n\nexport class JobNotCancellableError extends ConflictError {\n declare readonly code: 'job_not_cancellable'\n declare readonly meta: { id: string; status: JobStatus }\n}\n\nexport class RequestTooLargeError extends APIError<413, string> {}\n\nexport class UnprocessableEntityError extends APIError<422, string> {}\n\nexport class InvalidProfileError extends UnprocessableEntityError {\n declare readonly code: 'input_sanitized_rejected'\n declare readonly meta: {\n reason: 'input_sanitized_rejected'\n message: string\n docsUrl: string\n /** Sample of rejected lines; see blockedCount/blockedTruncated for the full set. */\n blocked: Array<{ line: number; text: string }>\n blockedCount: number\n blockedTruncated: boolean\n }\n}\n\nexport class ResultUnavailableError extends UnprocessableEntityError {\n declare readonly code: 'result_unavailable'\n declare readonly meta: {\n status: 'completed' | 'failed' | 'cancelled' | 'timed_out'\n }\n}\n\nexport type RateLimitErrorCode =\n | 'rate_limit_exceeded'\n | 'max_active_jobs_exceeded'\n\nexport class RateLimitError extends APIError<429, RateLimitErrorCode> {\n declare readonly meta:\n | { scope: 'developer' }\n | {\n reason: 'max_active_jobs_exceeded'\n maxActiveJobs: number\n activeJobs: number\n }\n | null\n}\n\nexport class MaxActiveJobsError extends RateLimitError {\n declare readonly code: 'max_active_jobs_exceeded'\n declare readonly meta: {\n reason: 'max_active_jobs_exceeded'\n /** Maximum number of jobs the account can have in flight. */\n maxActiveJobs: number\n /** Jobs in flight when this request was rejected. */\n activeJobs: number\n }\n}\n\n// ── 5xx ─────────────────────────────────────────────────────────────────────\n\nexport class InternalServerError extends APIError<number, string> {}\n\n/**\n * 503 carries four enumerated codes with distinct meta: a discriminated\n * union, narrowed via `.body`. `api_maintenance` gets no special retry\n * behavior: standard policy applies, and the typed\n * `meta.retryAfterSeconds` is surfaced so callers can schedule their own\n * resubmission.\n */\nexport type ServiceUnavailableBody =\n | {\n code: 'queue_unavailable'\n meta: { reason: 'queue_unavailable'; queueHealth: string }\n }\n | {\n code: 'queue_health_unknown'\n meta: { reason: 'queue_health_unknown'; laneId: string }\n }\n | {\n code: 'secret_store_unavailable'\n meta: { reason: 'secret_store_unavailable' }\n }\n | { code: 'api_maintenance'; meta: { retryAfterSeconds: number } }\n\nconst SERVICE_UNAVAILABLE_CODES = new Set([\n 'queue_unavailable',\n 'queue_health_unknown',\n 'secret_store_unavailable',\n 'api_maintenance'\n])\n\n// A 503 whose body isn't the enumerated envelope (e.g. load-balancer HTML)\n// falls back to InternalServerError so `.body` below never lies.\nfunction isServiceUnavailableBody(\n body: object | undefined\n): body is ServiceUnavailableBody {\n const code = (body as ErrorEnvelope | undefined)?.code\n return typeof code === 'string' && SERVICE_UNAVAILABLE_CODES.has(code)\n}\n\nexport class ServiceUnavailableError extends InternalServerError {\n declare readonly status: 503\n\n /** The discriminated 503 envelope: `if (e.body.code === 'api_maintenance') e.body.meta.retryAfterSeconds`. */\n get body(): ServiceUnavailableBody {\n return this.error as ServiceUnavailableBody\n }\n}\n\n// ── No HTTP response ────────────────────────────────────────────────────────\n\nexport class APIConnectionError extends APIError<\n undefined,\n undefined,\n undefined\n> {\n constructor({\n message,\n cause\n }: { message?: string | undefined; cause?: Error | undefined } = {}) {\n super(undefined, undefined, message ?? 'Connection error.', undefined)\n if (cause) this.cause = cause\n }\n}\n\nexport class APIConnectionTimeoutError extends APIConnectionError {\n constructor({ message }: { message?: string } = {}) {\n super({ message: message ?? 'Request timed out.' })\n }\n}\n\nexport class APIUserAbortError extends APIError<\n undefined,\n undefined,\n undefined\n> {\n constructor({ message }: { message?: string } = {}) {\n super(undefined, undefined, message ?? 'Request was aborted.', undefined)\n }\n}\n\n// ── Job-level errors (thrown only by createAndWait) ──────────────────────────\n\n/** Catch-all for a job that reached a terminal state other than `completed`. */\nexport abstract class JobUnsuccessfulError extends SimmitError {\n readonly job: Job\n\n constructor(job: Job, message?: string) {\n super(\n message ??\n `Job ${job.id} ${job.status}` +\n (job.statusReason ? `: ${job.statusReason}` : '') +\n (job.errorCode ? ` (${job.errorCode})` : '')\n )\n this.job = job\n }\n}\n\nexport class JobFailedError extends JobUnsuccessfulError {}\n\n/** Includes queue_timeout auto-cancellation, not just user cancels. */\nexport class JobCancelledError extends JobUnsuccessfulError {}\n\n/** The job hit its runtime ceiling server-side and is billed for what ran. */\nexport class JobTimedOutError extends JobUnsuccessfulError {}\n\n/**\n * The SDK gave up polling. The job itself is still running and billing.\n * Keep tracking via `jobs.get(jobId)` or stop the spend with `jobs.cancel(jobId)`.\n */\nexport class JobWaitTimeoutError extends SimmitError {\n readonly jobId: string\n readonly lastStatus: JobStatus\n\n constructor(args: {\n jobId: string\n lastStatus: JobStatus\n message?: string\n }) {\n super(\n args.message ??\n `Timed out waiting for job ${args.jobId} (last status: ${args.lastStatus}). ` +\n 'The job is still running server-side and continues to bill.'\n )\n this.jobId = args.jobId\n this.lastStatus = args.lastStatus\n }\n}\n\n// ── Webhook verification (thrown by unwrapWebhook) ───────────────────────────\n\nexport class WebhookVerificationError extends SimmitError {}\n","/**\n * A `Promise<T>` with raw-response access: the generic answer to response\n * headers the return types can't see (`X-Idempotent-Replay`, `X-Active-Jobs`,\n * `X-RateLimit-*`).\n *\n * const { data, response } = await client.jobs.create(params).withResponse()\n * response.headers.get('x-idempotent-replay')\n */\nexport class APIPromise<T> extends Promise<T> {\n // Chained promises (.then/.catch) must be plain Promises: this class's\n // constructor signature is incompatible with the executor the runtime\n // would otherwise pass via the species constructor.\n static override get [Symbol.species]() {\n return Promise\n }\n\n readonly #parsed: Promise<{ data: T; response: Response }>\n\n constructor(parsed: Promise<{ data: T; response: Response }>) {\n // The base promise is a pre-settled placeholder that is never observed:\n // then() below delegates to #parsed lazily (catch/finally route through\n // then() per spec). Subscribing eagerly here would reject this instance\n // even when the caller only consumes withResponse(), leaking an\n // unhandled rejection on failures.\n super((resolve) => resolve(undefined as never))\n this.#parsed = parsed\n }\n\n override then<TResult1 = T, TResult2 = never>(\n onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null,\n onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null\n ): Promise<TResult1 | TResult2> {\n return this.#parsed\n .then((result) => result.data)\n .then(onfulfilled, onrejected)\n }\n\n withResponse(): Promise<{ data: T; response: Response }> {\n return this.#parsed\n }\n\n asResponse(): Promise<Response> {\n return this.#parsed.then((result) => result.response)\n }\n}\n","// Abort-aware timing utilities shared by the request layer (backoff sleeps) and\n// the createAndWait poll loop. A pending sleep rejects with APIUserAbortError\n// the moment the caller's signal fires.\nimport { APIUserAbortError } from '../error'\n\nexport function throwIfUserAborted(signal: AbortSignal | undefined): void {\n if (signal?.aborted) throw new APIUserAbortError()\n}\n\nexport function sleep(\n ms: number,\n signal: AbortSignal | undefined\n): Promise<void> {\n return new Promise((resolve, reject) => {\n throwIfUserAborted(signal)\n const timeoutId = setTimeout(() => {\n signal?.removeEventListener('abort', onAbort)\n resolve()\n }, ms)\n const onAbort = () => {\n clearTimeout(timeoutId)\n reject(new APIUserAbortError())\n }\n signal?.addEventListener('abort', onAbort, { once: true })\n })\n}\n","// Internal request layer: header assembly, per-attempt timeout/abort\n// composition, retry with backoff + Retry-After, idempotency-key injection,\n// and error mapping. Not exported from the package.\nimport { APIPromise } from '../api-promise'\nimport {\n APIConnectionError,\n APIConnectionTimeoutError,\n APIError,\n APIUserAbortError\n} from '../error'\nimport type { RequestOptions } from '../client'\nimport { sleep, throwIfUserAborted } from './abort'\n\nexport interface ClientConfig {\n secretKey: string\n baseURL: string\n timeout: number\n maxRetries: number\n defaultHeaders: Record<string, string | null | undefined> | undefined\n fetch: typeof globalThis.fetch\n fetchOptions: RequestInit | undefined\n}\n\nexport interface RequestSpec {\n method: 'GET' | 'POST'\n path: string\n body?: unknown\n /** POST job creation: auto-generate an idempotency-key when none supplied. */\n idempotent?: boolean\n}\n\n// Retry policy constants: typed code config, not env.\nconst INITIAL_BACKOFF_MS = 500\nconst MAX_BACKOFF_MS = 8_000\nconst MAX_RETRY_AFTER_MS = 60_000\n\nexport function makeRequest<T>(\n config: ClientConfig,\n spec: RequestSpec,\n options: RequestOptions = {}\n): APIPromise<T> {\n return new APIPromise(run<T>(config, spec, options))\n}\n\nasync function run<T>(\n config: ClientConfig,\n spec: RequestSpec,\n options: RequestOptions\n): Promise<{ data: T; response: Response }> {\n const maxRetries = options.maxRetries ?? config.maxRetries\n const timeout = options.timeout ?? config.timeout\n const headers = buildHeaders(config, spec, options)\n const url = `${config.baseURL.replace(/\\/+$/, '')}${spec.path}`\n const body = spec.body === undefined ? undefined : JSON.stringify(spec.body)\n\n for (let attempt = 0; ; attempt++) {\n throwIfUserAborted(options.signal)\n\n let result: AttemptResult\n try {\n result = await fetchAttempt(config, spec, options, {\n url,\n headers,\n body,\n timeout\n })\n } catch (err) {\n if (err instanceof APIUserAbortError) throw err\n // Connection error, malformed success body, or per-attempt timeout.\n // All retryable.\n if (attempt < maxRetries) {\n await backoff(attempt, undefined, options.signal)\n continue\n }\n throw err\n }\n\n const { response, json } = result\n\n if (response.ok) {\n return { data: json as T, response }\n }\n\n if (shouldRetryStatus(response.status) && attempt < maxRetries) {\n await backoff(\n attempt,\n response.headers.get('retry-after'),\n options.signal\n )\n continue\n }\n\n throw APIError.generate(\n response.status,\n typeof json === 'object' && json !== null ? json : undefined,\n response.statusText,\n response.headers\n )\n }\n}\n\ninterface AttemptResult {\n response: Response\n /** Parsed JSON body; undefined when an error response carried a non-JSON body. */\n json: unknown\n}\n\nasync function fetchAttempt(\n config: ClientConfig,\n spec: RequestSpec,\n options: RequestOptions,\n attempt: {\n url: string\n headers: Record<string, string>\n body: string | undefined\n timeout: number\n }\n): Promise<AttemptResult> {\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), attempt.timeout)\n const onUserAbort = () => controller.abort()\n options.signal?.addEventListener('abort', onUserAbort, { once: true })\n\n try {\n const response = await config.fetch(attempt.url, {\n ...config.fetchOptions,\n method: spec.method,\n headers: attempt.headers,\n ...(attempt.body !== undefined ? { body: attempt.body } : {}),\n signal: controller.signal\n })\n\n // The body is read inside the timed scope too: a stalled body must not\n // hang past the per-attempt timeout (fetch ties the body stream to the\n // controller's signal, so the abort cancels the read).\n let json: unknown\n if (response.ok) {\n // Success bodies must parse; a truncated/malformed one is treated as a\n // transport failure (classified below) and retried like one.\n json = await response.json()\n } else {\n try {\n json = await response.json()\n } catch (err) {\n // Aborted mid-read is a timeout/abort, not a non-JSON body.\n if (controller.signal.aborted) throw err\n json = undefined // e.g. a load-balancer HTML error page\n }\n }\n return { response, json }\n } catch (err) {\n if (options.signal?.aborted) throw new APIUserAbortError()\n if (controller.signal.aborted) {\n throw new APIConnectionTimeoutError()\n }\n throw new APIConnectionError({\n cause: err instanceof Error ? err : undefined\n })\n } finally {\n clearTimeout(timeoutId)\n options.signal?.removeEventListener('abort', onUserAbort)\n }\n}\n\nfunction buildHeaders(\n config: ClientConfig,\n spec: RequestSpec,\n options: RequestOptions\n): Record<string, string> {\n const idempotent = spec.idempotent && spec.method === 'POST'\n const merged: Record<string, string | null | undefined> = {\n authorization: `Bearer ${config.secretKey}`,\n ...(spec.body !== undefined ? { 'content-type': 'application/json' } : {}),\n ...(idempotent && !options.idempotencyKey\n ? {\n // Generated once per call and reused across retry attempts. That\n // is what makes POST retries safe by default. The auto\n // key is an SDK built-in default (lowest tier), so defaultHeaders\n // may override it.\n 'idempotency-key': `simmit-node-retry-${crypto.randomUUID()}`\n }\n : {}),\n ...lowercaseKeys(config.defaultHeaders),\n ...(idempotent && options.idempotencyKey\n ? {\n // An explicit key is a per-request option: it must beat constructor\n // defaultHeaders. Raw options.headers still wins last.\n 'idempotency-key': options.idempotencyKey\n }\n : {}),\n ...lowercaseKeys(options.headers)\n }\n\n const headers: Record<string, string> = {}\n for (const [key, value] of Object.entries(merged)) {\n // A null value deletes the header; undefined entries are skipped.\n if (typeof value === 'string') headers[key] = value\n }\n return headers\n}\n\nfunction lowercaseKeys(\n record: Record<string, string | null | undefined> | undefined\n): Record<string, string | null | undefined> {\n if (!record) return {}\n return Object.fromEntries(\n Object.entries(record).map(([key, value]) => [key.toLowerCase(), value])\n )\n}\n\nfunction shouldRetryStatus(status: number): boolean {\n // 408 kept defensively even though the API never emits it. 409 is never\n // retried: result_not_ready is thrown immediately by design and the other\n // 409s are deterministic.\n return status === 408 || status === 429 || status >= 500\n}\n\nasync function backoff(\n attempt: number,\n retryAfterHeader: string | null | undefined,\n signal: AbortSignal | undefined\n): Promise<void> {\n const retryAfterMs = parseRetryAfter(retryAfterHeader)\n const delay =\n retryAfterMs !== undefined\n ? retryAfterMs\n : Math.min(INITIAL_BACKOFF_MS * 2 ** attempt, MAX_BACKOFF_MS) *\n (1 - 0.25 * Math.random())\n await sleep(delay, signal)\n}\n\n/** Accepts `Retry-After` only when it parses to a delay in (0, 60s]: the SDK never sleeps arbitrarily long on a server hint. */\nfunction parseRetryAfter(\n header: string | null | undefined\n): number | undefined {\n if (!header) return undefined\n let ms: number\n if (/^\\d+$/.test(header.trim())) {\n ms = Number(header.trim()) * 1000\n } else {\n ms = new Date(header).getTime() - Date.now()\n }\n return Number.isFinite(ms) && ms > 0 && ms <= MAX_RETRY_AFTER_MS\n ? ms\n : undefined\n}\n","import type { APIPromise } from '../api-promise'\nimport type { ArtifactUrl } from '../api-types'\nimport type Simmit from '../client'\nimport type { RequestOptions } from '../client'\n\n/** The `artifacts` resource. */\nexport class Artifacts {\n readonly #client: Simmit\n\n constructor(client: Simmit) {\n this.#client = client\n }\n\n /**\n * Fetch a stable public download URL for an artifact, valid for the\n * artifact's full retention window: the same URL `jobs.getResult` returns,\n * fetched on demand (e.g. browser flows that control the final fetch). The\n * artifact is gone (410) once its retention window passes.\n */\n getUrl(\n artifactId: string,\n options?: RequestOptions\n ): APIPromise<ArtifactUrl> {\n return this.#client._request<ArtifactUrl>(\n {\n method: 'GET',\n path: `/v1/simc/artifacts/${encodeURIComponent(artifactId)}/url`\n },\n options\n )\n }\n}\n","import type { APIPromise } from '../api-promise'\nimport type { CreditBalance } from '../api-types'\nimport type Simmit from '../client'\nimport type { RequestOptions } from '../client'\n\n/** The `credits` resource. */\nexport class Credits {\n readonly #client: Simmit\n\n constructor(client: Simmit) {\n this.#client = client\n }\n\n /** Fetch the account's current credit balance and per-grant breakdown. */\n get(options?: RequestOptions): APIPromise<CreditBalance> {\n return this.#client._request<CreditBalance>(\n { method: 'GET', path: '/v1/simc/credits' },\n options\n )\n }\n}\n","// Pure helpers for the createAndWait poll loop. Kept separate from the\n// orchestration so the cadence and deadline math are unit-testable.\nimport type { JobCreateResponse } from '../api-types'\n\nexport const MIN_POLL_INTERVAL_MS = 100\nexport const DEFAULT_POLL_INTERVAL_MS = 1_000\nexport const MAX_POLL_INTERVAL_MS = 10_000\nexport const POLL_BACKOFF_FACTOR = 1.5\nconst DEADLINE_GRACE_MS = 60_000\nconst FALLBACK_WAIT_TIMEOUT_MS = 45 * 60 * 1_000\n\n/**\n * Default wait deadline derived from the applied ceilings the create response\n * reports: `(queueSeconds + runtimeSeconds) × 1000` plus a 60s grace, falling\n * back to 45 minutes when either ceiling is null.\n */\nexport function deriveWaitTimeoutMs(created: JobCreateResponse): number {\n const { runtimeSeconds, queueSeconds } = created.runtime.ceiling\n if (runtimeSeconds != null && queueSeconds != null) {\n return (runtimeSeconds + queueSeconds) * 1_000 + DEADLINE_GRACE_MS\n }\n return FALLBACK_WAIT_TIMEOUT_MS\n}\n\n/** Next poll interval: grow ×1.5, capped at 10s. */\nexport function nextPollInterval(interval: number): number {\n return Math.min(interval * POLL_BACKOFF_FACTOR, MAX_POLL_INTERVAL_MS)\n}\n","import type { JobStatus } from './api-types'\n\n/**\n * Job statuses that are terminal: a job in one of these has stopped and will\n * not change again. Terminal does not mean successful; only `completed` carries\n * a result (`failed`, `cancelled`, and `timed_out` do not).\n */\nexport const TERMINAL_JOB_STATUSES = [\n 'completed',\n 'failed',\n 'cancelled',\n 'timed_out'\n] as const satisfies readonly JobStatus[]\n\n/** A `JobStatus` that is terminal: the job has stopped and will not change. */\nexport type TerminalJobStatus = (typeof TERMINAL_JOB_STATUSES)[number]\n\n/** True when `status` is terminal, i.e. the job has reached an end state. */\nexport function isTerminal(status: JobStatus): status is TerminalJobStatus {\n return (TERMINAL_JOB_STATUSES as readonly JobStatus[]).includes(status)\n}\n","import type { APIPromise } from '../api-promise'\nimport type {\n CompletedJob,\n Job,\n JobCancelResponse,\n JobCreateParams,\n JobCreateResponse,\n JobResult,\n JobStatus,\n JobStatusResponse\n} from '../api-types'\nimport type Simmit from '../client'\nimport type { RequestOptions } from '../client'\nimport {\n JobCancelledError,\n JobFailedError,\n JobTimedOutError,\n JobWaitTimeoutError\n} from '../error'\nimport { sleep } from '../internal/abort'\nimport {\n DEFAULT_POLL_INTERVAL_MS,\n deriveWaitTimeoutMs,\n MIN_POLL_INTERVAL_MS,\n nextPollInterval\n} from '../internal/poll'\nimport { isTerminal } from '../status'\n\nexport interface JobWaitOptions extends RequestOptions {\n /** Initial delay between status polls, ms. Grows ×1.5 per poll to a 10s cap; values under 100 are raised to it. Default 1_000. */\n pollIntervalMs?: number\n /** Overall wait deadline, ms. Default derived from the job's applied ceilings. */\n waitTimeoutMs?: number\n /** Fired once with the raw create response (job id, ceilings, input warnings) before polling. */\n onCreated?: (response: JobCreateResponse) => void\n /** Fired after every successful status poll (progress, stage, queue estimate). */\n onPoll?: (status: JobStatusResponse) => void\n}\n\n/**\n * The `jobs` resource. Each single-request method is a thin wrapper over\n * `client._request` with the path/method/types pinned to the spec;\n * `createAndWait` orchestrates several of them.\n */\nexport class Jobs {\n readonly #client: Simmit\n\n constructor(client: Simmit) {\n this.#client = client\n }\n\n /**\n * Submit a new SimC sim. Returns immediately with the job handle; the sim\n * runs asynchronously. `idempotent: true` makes the request layer attach an\n * auto-generated idempotency key so the POST is safe to retry; pass\n * `options.idempotencyKey` to supply your own.\n */\n create(\n params: JobCreateParams,\n options?: RequestOptions\n ): APIPromise<JobCreateResponse> {\n return this.#client._request<JobCreateResponse>(\n { method: 'POST', path: '/v1/simc/jobs', body: params, idempotent: true },\n options\n )\n }\n\n /** Fetch the full record for a job. */\n get(jobId: string, options?: RequestOptions): APIPromise<Job> {\n return this.#client._request<Job>(\n { method: 'GET', path: `/v1/simc/jobs/${encodeURIComponent(jobId)}` },\n options\n )\n }\n\n /**\n * Fetch the live status of a job in any state: `status`, `errorCode`,\n * `progress`, and `queue` estimate. Unlike `getResult`, it never throws for a\n * non-terminal job, so it is the supported way to drive a custom poll loop.\n */\n getStatus(\n jobId: string,\n options?: RequestOptions\n ): APIPromise<JobStatusResponse> {\n return this.#client._request<JobStatusResponse>(\n {\n method: 'GET',\n path: `/v1/simc/jobs/${encodeURIComponent(jobId)}/status`\n },\n options\n )\n }\n\n /**\n * Fetch the result summary of a terminal job. Throws `ResultNotReadyError`\n * (409) while the job is still running. Poll `/status` or use\n * `createAndWait` rather than `/result` for a job in flight.\n */\n getResult(jobId: string, options?: RequestOptions): APIPromise<JobResult> {\n return this.#client._request<JobResult>(\n {\n method: 'GET',\n path: `/v1/simc/jobs/${encodeURIComponent(jobId)}/result`\n },\n options\n )\n }\n\n /**\n * Submit a job and resolve once it reaches a terminal state. Polls\n * `GET /v1/simc/jobs/{id}/status` (first after `pollIntervalMs`, then ×1.5 to\n * a 10s cap), then fetches the full record. Resolves with the `CompletedJob`\n * on success; throws `JobFailedError` / `JobCancelledError` /\n * `JobTimedOutError` for the other terminal states, or `JobWaitTimeoutError`\n * if the deadline passes first. The job keeps running and is **not**\n * cancelled (call `cancel(jobId)` to stop the spend). `signal` aborts the wait\n * with `APIUserAbortError`, also without cancelling.\n */\n async createAndWait(\n params: JobCreateParams,\n options: JobWaitOptions = {}\n ): Promise<CompletedJob> {\n const {\n pollIntervalMs,\n waitTimeoutMs,\n onCreated,\n onPoll,\n ...requestOptions\n } = options\n\n const created = await this.create(params, requestOptions)\n onCreated?.(created)\n\n const deadline =\n Date.now() + (waitTimeoutMs ?? deriveWaitTimeoutMs(created))\n // nextPollInterval only ever grows the interval, so a non-positive seed\n // would hot-poll the status endpoint; floor it.\n let interval = Math.max(\n pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS,\n MIN_POLL_INTERVAL_MS\n )\n let lastStatus: JobStatus = 'pending'\n\n for (;;) {\n const remaining = deadline - Date.now()\n if (remaining <= 0) {\n throw new JobWaitTimeoutError({ jobId: created.id, lastStatus })\n }\n // Never sleep past the deadline, so the wait gives up promptly.\n await sleep(Math.min(interval, remaining), requestOptions.signal)\n\n const status = await this.getStatus(created.id, requestOptions)\n onPoll?.(status)\n lastStatus = status.status\n\n if (isTerminal(status.status)) {\n // The status payload is lightweight; the full record carries the fields\n // CompletedJob and the job-error classes expose.\n const job = await this.get(created.id, requestOptions)\n switch (job.status) {\n case 'completed':\n return job as CompletedJob\n case 'failed':\n throw new JobFailedError(job)\n case 'cancelled':\n throw new JobCancelledError(job)\n case 'timed_out':\n throw new JobTimedOutError(job)\n }\n // Raced back to non-terminal between /status and the full record; keep polling.\n lastStatus = job.status\n }\n interval = nextPollInterval(interval)\n }\n }\n\n /**\n * Request cancellation. Returns `status: 'cancelled'` when the job ended\n * before it ran, or `status: 'cancel_requested'` when an in-flight job was\n * signaled to stop. Repeat calls are naturally idempotent, so no key is sent.\n */\n cancel(\n jobId: string,\n options?: RequestOptions\n ): APIPromise<JobCancelResponse> {\n return this.#client._request<JobCancelResponse>(\n {\n method: 'POST',\n path: `/v1/simc/jobs/${encodeURIComponent(jobId)}/cancel`\n },\n options\n )\n }\n}\n","import { APIPromise } from './api-promise'\nimport { SimmitError } from './error'\nimport {\n makeRequest,\n type ClientConfig,\n type RequestSpec\n} from './internal/request'\nimport { Artifacts } from './resources/artifacts'\nimport { Credits } from './resources/credits'\nimport { Jobs } from './resources/jobs'\n\nexport interface ClientOptions {\n /** Defaults to process.env['SIMMIT_SECRET_KEY'], exactly one env fallback. Construction\n * throws SimmitError('Missing secret key. Pass secretKey or set SIMMIT_SECRET_KEY.').\n * \"Secret key\" is the credential noun end to end (dashboard → docs → env var → option →\n * error): it spends credits and must never ship client-side. */\n secretKey?: string | null\n /** Defaults to process.env['SIMMIT_BASE_URL'] ?? 'https://api.simmit.com'. */\n baseURL?: string | null\n /** Per-attempt timeout in ms. Default 60_000. (Retries can extend total wall time.) */\n timeout?: number\n /** Max retries after the first attempt for retryable failures. Default 2. */\n maxRetries?: number\n /** Headers sent with every request. Merged under per-request headers. */\n defaultHeaders?: Record<string, string | null | undefined>\n /** Custom fetch (testing, proxies). Defaults to globalThis.fetch. */\n fetch?: typeof globalThis.fetch\n /** Extra RequestInit fields passed to every fetch call (e.g. undici dispatcher). */\n fetchOptions?: RequestInit\n}\n\nexport interface RequestOptions {\n /** Per-attempt timeout in ms. Overrides ClientOptions.timeout. */\n timeout?: number\n /** Abort the call (including retries and waiting). Throws APIUserAbortError. Never retried. */\n signal?: AbortSignal\n /** Overrides ClientOptions.maxRetries for this call. */\n maxRetries?: number\n /** Merged over defaultHeaders; a null value deletes the header. */\n headers?: Record<string, string | null | undefined>\n /** jobs.create / jobs.createAndWait only: replaces the auto-generated idempotency-key. */\n idempotencyKey?: string\n}\n\nexport default class Simmit {\n readonly jobs: Jobs\n readonly credits: Credits\n readonly artifacts: Artifacts\n\n readonly baseURL: string\n\n readonly #config: ClientConfig\n\n constructor(options: ClientOptions = {}) {\n const secretKey = options.secretKey ?? readEnv('SIMMIT_SECRET_KEY')\n if (!secretKey) {\n throw new SimmitError(\n 'Missing secret key. Pass secretKey or set SIMMIT_SECRET_KEY.'\n )\n }\n\n this.baseURL =\n options.baseURL ?? readEnv('SIMMIT_BASE_URL') ?? 'https://api.simmit.com'\n\n this.#config = {\n secretKey,\n baseURL: this.baseURL,\n timeout: options.timeout ?? 60_000,\n maxRetries: options.maxRetries ?? 2,\n defaultHeaders: options.defaultHeaders,\n // Resolved lazily so a fetch patched onto globalThis after the client\n // is constructed (msw, APM instrumentation) is still honored.\n fetch: options.fetch ?? ((...args) => globalThis.fetch(...args)),\n fetchOptions: options.fetchOptions\n }\n\n this.jobs = new Jobs(this)\n this.credits = new Credits(this)\n this.artifacts = new Artifacts(this)\n }\n\n /** @internal Resource classes route through here; not public surface. */\n _request<T>(spec: RequestSpec, options?: RequestOptions): APIPromise<T> {\n return makeRequest(this.#config, spec, options)\n }\n}\n\nfunction readEnv(name: string): string | undefined {\n if (typeof process === 'undefined') return undefined\n const value = process.env?.[name]?.trim()\n return value || undefined\n}\n","// Standalone webhook verification. Not a client method: receivers must not\n// need a secret-key-bearing client (whose constructor throws without a key).\n// WebCrypto only, zero deps, and runs in Workers as well as Node.\nimport type { JobStatus } from './api-types'\nimport { WebhookVerificationError } from './error'\n\nconst DEFAULT_TOLERANCE_SECONDS = 300\n\n/** The one hand-written wire type: the webhook payload has no OpenAPI schema. */\nexport interface WebhookEvent {\n kind: 'job.terminal'\n version: 'v1'\n timestamp: string\n payload: {\n id: string\n statusReason: string | null\n status: Extract<\n JobStatus,\n 'completed' | 'failed' | 'cancelled' | 'timed_out'\n >\n }\n}\n\n/**\n * Verifies an `X-Simmit-Signature` header (`t=<unix>,v1=<hex>`, an HMAC-SHA256\n * (timing-safe) over `${t}.${rawBody}` within a 300s default tolerance) and\n * returns the parsed event. Throws `WebhookVerificationError` on a bad\n * signature, malformed header, or stale timestamp.\n *\n * Pass `rawBody` exactly as received: re-serializing changes the bytes and\n * breaks verification. `secret` is the webhook signing secret (dashboard →\n * Clients & Keys → Webhook), not your API key.\n */\nexport async function unwrapWebhook(\n rawBody: string,\n signatureHeader: string,\n secret: string,\n options?: { toleranceSeconds?: number }\n): Promise<WebhookEvent> {\n // An empty secret would otherwise surface as an opaque WebCrypto DataError;\n // a NaN tolerance would make the age check pass for everything.\n if (!secret) {\n throw new WebhookVerificationError('Webhook signing secret is empty.')\n }\n const tolerance = options?.toleranceSeconds ?? DEFAULT_TOLERANCE_SECONDS\n if (!Number.isFinite(tolerance) || tolerance < 0) {\n throw new WebhookVerificationError(\n 'toleranceSeconds must be a non-negative number.'\n )\n }\n\n const { timestampRaw, timestamp, signature } =\n parseSignatureHeader(signatureHeader)\n\n const expected = await hmacSha256Hex(secret, `${timestampRaw}.${rawBody}`)\n if (!timingSafeEqual(expected, signature)) {\n throw new WebhookVerificationError('Webhook signature does not match.')\n }\n\n // Compare on whole seconds, matching the header's unix-seconds `t`.\n if (Math.abs(Math.floor(Date.now() / 1000) - timestamp) > tolerance) {\n throw new WebhookVerificationError(\n 'Webhook timestamp is outside the tolerance window.'\n )\n }\n\n try {\n return JSON.parse(rawBody) as WebhookEvent\n } catch {\n throw new WebhookVerificationError('Webhook body is not valid JSON.')\n }\n}\n\nfunction parseSignatureHeader(header: string): {\n timestampRaw: string\n timestamp: number\n signature: string\n} {\n let timestampRaw: string | undefined\n let signature: string | undefined\n for (const part of header.split(',')) {\n const eq = part.indexOf('=')\n if (eq === -1) continue\n const key = part.slice(0, eq).trim()\n const value = part.slice(eq + 1).trim()\n if (key === 't') timestampRaw = value\n else if (key === 'v1') signature = value\n }\n\n // `t` is unix whole seconds; reject anything but digits so the accepted\n // header matches the documented contract.\n if (!timestampRaw || !signature || !/^\\d+$/.test(timestampRaw)) {\n throw new WebhookVerificationError(\n 'Malformed signature header; expected \"t=<unix>,v1=<hex>\".'\n )\n }\n // The signed payload uses the timestamp exactly as sent, so keep the raw\n // string for signing and the parsed number only for the tolerance check.\n return { timestampRaw, timestamp: Number(timestampRaw), signature }\n}\n\nasync function hmacSha256Hex(secret: string, payload: string): Promise<string> {\n const encoder = new TextEncoder()\n const key = await crypto.subtle.importKey(\n 'raw',\n encoder.encode(secret),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign']\n )\n const mac = await crypto.subtle.sign('HMAC', key, encoder.encode(payload))\n return toHex(new Uint8Array(mac))\n}\n\nfunction toHex(bytes: Uint8Array): string {\n let hex = ''\n for (const byte of bytes) hex += byte.toString(16).padStart(2, '0')\n return hex\n}\n\n// Constant-time comparison. The digest width is public, so a length mismatch\n// may short-circuit without leaking secret-dependent timing.\nfunction timingSafeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) return false\n let mismatch = 0\n for (let i = 0; i < a.length; i++) {\n mismatch |= a.charCodeAt(i) ^ b.charCodeAt(i)\n }\n return mismatch === 0\n}\n"],"mappings":"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","names":[]}
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * A `Promise<T>` with raw-response access the generic answer to response
2
+ * A `Promise<T>` with raw-response access: the generic answer to response
3
3
  * headers the return types can't see (`X-Idempotent-Replay`, `X-Active-Jobs`,
4
4
  * `X-RateLimit-*`).
5
5
  *
@@ -1789,7 +1789,7 @@ declare class Artifacts {
1789
1789
  constructor(client: Simmit);
1790
1790
  /**
1791
1791
  * Fetch a stable public download URL for an artifact, valid for the
1792
- * artifact's full retention window the same URL `jobs.getResult` returns,
1792
+ * artifact's full retention window: the same URL `jobs.getResult` returns,
1793
1793
  * fetched on demand (e.g. browser flows that control the final fetch). The
1794
1794
  * artifact is gone (410) once its retention window passes.
1795
1795
  */
@@ -1832,14 +1832,14 @@ declare class Jobs {
1832
1832
  /** Fetch the full record for a job. */
1833
1833
  get(jobId: string, options?: RequestOptions): APIPromise<Job>;
1834
1834
  /**
1835
- * Fetch the live status of a job in any state `status`, `errorCode`,
1835
+ * Fetch the live status of a job in any state: `status`, `errorCode`,
1836
1836
  * `progress`, and `queue` estimate. Unlike `getResult`, it never throws for a
1837
1837
  * non-terminal job, so it is the supported way to drive a custom poll loop.
1838
1838
  */
1839
1839
  getStatus(jobId: string, options?: RequestOptions): APIPromise<JobStatusResponse>;
1840
1840
  /**
1841
1841
  * Fetch the result summary of a terminal job. Throws `ResultNotReadyError`
1842
- * (409) while the job is still running poll `/status` or use
1842
+ * (409) while the job is still running. Poll `/status` or use
1843
1843
  * `createAndWait` rather than `/result` for a job in flight.
1844
1844
  */
1845
1845
  getResult(jobId: string, options?: RequestOptions): APIPromise<JobResult>;
@@ -1849,7 +1849,7 @@ declare class Jobs {
1849
1849
  * a 10s cap), then fetches the full record. Resolves with the `CompletedJob`
1850
1850
  * on success; throws `JobFailedError` / `JobCancelledError` /
1851
1851
  * `JobTimedOutError` for the other terminal states, or `JobWaitTimeoutError`
1852
- * if the deadline passes first the job keeps running and is **not**
1852
+ * if the deadline passes first. The job keeps running and is **not**
1853
1853
  * cancelled (call `cancel(jobId)` to stop the spend). `signal` aborts the wait
1854
1854
  * with `APIUserAbortError`, also without cancelling.
1855
1855
  */
@@ -1863,7 +1863,7 @@ declare class Jobs {
1863
1863
  }
1864
1864
 
1865
1865
  interface ClientOptions {
1866
- /** Defaults to process.env['SIMMIT_SECRET_KEY'] exactly one env fallback. Construction
1866
+ /** Defaults to process.env['SIMMIT_SECRET_KEY'], exactly one env fallback. Construction
1867
1867
  * throws SimmitError('Missing secret key. Pass secretKey or set SIMMIT_SECRET_KEY.').
1868
1868
  * "Secret key" is the credential noun end to end (dashboard → docs → env var → option →
1869
1869
  * error): it spends credits and must never ship client-side. */
@@ -1922,7 +1922,7 @@ declare class APIError<TStatus extends number | undefined = number | undefined,
1922
1922
  readonly code: TCode;
1923
1923
  /** Typed `meta` from the error envelope. */
1924
1924
  readonly meta: TMeta;
1925
- /** Raw parsed JSON error body escape hatch for unmapped fields. */
1925
+ /** Raw parsed JSON error body: escape hatch for unmapped fields. */
1926
1926
  readonly error: object | undefined;
1927
1927
  constructor(status: TStatus, body: object | undefined, message: string | undefined, headers: Headers | undefined);
1928
1928
  private static makeMessage;
@@ -1954,7 +1954,7 @@ declare class InsufficientCreditsError extends BillingError {
1954
1954
  }
1955
1955
  type InsufficientCreditsLiabilityMeta = {
1956
1956
  reason: string;
1957
- /** The high-priority fee in effect top up, or resubmit at priority 'standard'. */
1957
+ /** The high-priority fee in effect. Top up, or resubmit at priority 'standard'. */
1958
1958
  priorityFeeCredits: number;
1959
1959
  docsUrl?: string;
1960
1960
  };
@@ -2036,7 +2036,7 @@ declare class MaxActiveJobsError extends RateLimitError {
2036
2036
  declare class InternalServerError extends APIError<number, string> {
2037
2037
  }
2038
2038
  /**
2039
- * 503 carries four enumerated codes with distinct meta a discriminated
2039
+ * 503 carries four enumerated codes with distinct meta: a discriminated
2040
2040
  * union, narrowed via `.body`. `api_maintenance` gets no special retry
2041
2041
  * behavior: standard policy applies, and the typed
2042
2042
  * `meta.retryAfterSeconds` is surfaced so callers can schedule their own
@@ -2100,7 +2100,7 @@ declare class JobCancelledError extends JobUnsuccessfulError {
2100
2100
  declare class JobTimedOutError extends JobUnsuccessfulError {
2101
2101
  }
2102
2102
  /**
2103
- * The SDK gave up polling the job itself is still running and billing.
2103
+ * The SDK gave up polling. The job itself is still running and billing.
2104
2104
  * Keep tracking via `jobs.get(jobId)` or stop the spend with `jobs.cancel(jobId)`.
2105
2105
  */
2106
2106
  declare class JobWaitTimeoutError extends SimmitError {
@@ -2127,8 +2127,8 @@ interface WebhookEvent {
2127
2127
  };
2128
2128
  }
2129
2129
  /**
2130
- * Verifies an `X-Simmit-Signature` header `t=<unix>,v1=<hex>`, an HMAC-SHA256
2131
- * (timing-safe) over `${t}.${rawBody}` within a 300s default tolerance and
2130
+ * Verifies an `X-Simmit-Signature` header (`t=<unix>,v1=<hex>`, an HMAC-SHA256
2131
+ * (timing-safe) over `${t}.${rawBody}` within a 300s default tolerance) and
2132
2132
  * returns the parsed event. Throws `WebhookVerificationError` on a bad
2133
2133
  * signature, malformed header, or stale timestamp.
2134
2134
  *
@@ -2140,4 +2140,15 @@ declare function unwrapWebhook(rawBody: string, signatureHeader: string, secret:
2140
2140
  toleranceSeconds?: number;
2141
2141
  }): Promise<WebhookEvent>;
2142
2142
 
2143
- export { APIConnectionError, APIConnectionTimeoutError, APIError, APIPromise, APIUserAbortError, type ArtifactUrl, Artifacts, AuthenticationError, type AuthenticationErrorCode, BadRequestError, BillingError, type ClientOptions, type CompletedJob, ConflictError, type CreditBalance, type CreditGrant, Credits, type GenericMeta, IdempotencyKeyReuseError, InsufficientCreditsError, InsufficientCreditsLiabilityError, type InsufficientCreditsLiabilityMeta, type InsufficientCreditsMeta, InternalServerError, InvalidProfileError, type Job, type JobCancelResponse, JobCancelledError, type JobCreateParams, type JobCreateResponse, type JobErrorCode, JobFailedError, JobNotCancellableError, type JobResult, type JobStatus, type JobStatusResponse, JobTimedOutError, JobUnsuccessfulError, type JobWaitOptions, JobWaitTimeoutError, Jobs, MaxActiveJobsError, type MetaValue, NotFoundError, RateLimitError, type RateLimitErrorCode, type RequestOptions, RequestTooLargeError, ResultNotReadyError, ResultUnavailableError, type ServiceUnavailableBody, ServiceUnavailableError, Simmit, SimmitError, UnprocessableEntityError, type WebhookEvent, WebhookVerificationError, Simmit as default, unwrapWebhook };
2143
+ /**
2144
+ * Job statuses that are terminal: a job in one of these has stopped and will
2145
+ * not change again. Terminal does not mean successful; only `completed` carries
2146
+ * a result (`failed`, `cancelled`, and `timed_out` do not).
2147
+ */
2148
+ declare const TERMINAL_JOB_STATUSES: readonly ["completed", "failed", "cancelled", "timed_out"];
2149
+ /** A `JobStatus` that is terminal: the job has stopped and will not change. */
2150
+ type TerminalJobStatus = (typeof TERMINAL_JOB_STATUSES)[number];
2151
+ /** True when `status` is terminal, i.e. the job has reached an end state. */
2152
+ declare function isTerminal(status: JobStatus): status is TerminalJobStatus;
2153
+
2154
+ export { APIConnectionError, APIConnectionTimeoutError, APIError, APIPromise, APIUserAbortError, type ArtifactUrl, Artifacts, AuthenticationError, type AuthenticationErrorCode, BadRequestError, BillingError, type ClientOptions, type CompletedJob, ConflictError, type CreditBalance, type CreditGrant, Credits, type GenericMeta, IdempotencyKeyReuseError, InsufficientCreditsError, InsufficientCreditsLiabilityError, type InsufficientCreditsLiabilityMeta, type InsufficientCreditsMeta, InternalServerError, InvalidProfileError, type Job, type JobCancelResponse, JobCancelledError, type JobCreateParams, type JobCreateResponse, type JobErrorCode, JobFailedError, JobNotCancellableError, type JobResult, type JobStatus, type JobStatusResponse, JobTimedOutError, JobUnsuccessfulError, type JobWaitOptions, JobWaitTimeoutError, Jobs, MaxActiveJobsError, type MetaValue, NotFoundError, RateLimitError, type RateLimitErrorCode, type RequestOptions, RequestTooLargeError, ResultNotReadyError, ResultUnavailableError, type ServiceUnavailableBody, ServiceUnavailableError, Simmit, SimmitError, TERMINAL_JOB_STATUSES, type TerminalJobStatus, UnprocessableEntityError, type WebhookEvent, WebhookVerificationError, Simmit as default, isTerminal, unwrapWebhook };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * A `Promise<T>` with raw-response access the generic answer to response
2
+ * A `Promise<T>` with raw-response access: the generic answer to response
3
3
  * headers the return types can't see (`X-Idempotent-Replay`, `X-Active-Jobs`,
4
4
  * `X-RateLimit-*`).
5
5
  *
@@ -1789,7 +1789,7 @@ declare class Artifacts {
1789
1789
  constructor(client: Simmit);
1790
1790
  /**
1791
1791
  * Fetch a stable public download URL for an artifact, valid for the
1792
- * artifact's full retention window the same URL `jobs.getResult` returns,
1792
+ * artifact's full retention window: the same URL `jobs.getResult` returns,
1793
1793
  * fetched on demand (e.g. browser flows that control the final fetch). The
1794
1794
  * artifact is gone (410) once its retention window passes.
1795
1795
  */
@@ -1832,14 +1832,14 @@ declare class Jobs {
1832
1832
  /** Fetch the full record for a job. */
1833
1833
  get(jobId: string, options?: RequestOptions): APIPromise<Job>;
1834
1834
  /**
1835
- * Fetch the live status of a job in any state `status`, `errorCode`,
1835
+ * Fetch the live status of a job in any state: `status`, `errorCode`,
1836
1836
  * `progress`, and `queue` estimate. Unlike `getResult`, it never throws for a
1837
1837
  * non-terminal job, so it is the supported way to drive a custom poll loop.
1838
1838
  */
1839
1839
  getStatus(jobId: string, options?: RequestOptions): APIPromise<JobStatusResponse>;
1840
1840
  /**
1841
1841
  * Fetch the result summary of a terminal job. Throws `ResultNotReadyError`
1842
- * (409) while the job is still running poll `/status` or use
1842
+ * (409) while the job is still running. Poll `/status` or use
1843
1843
  * `createAndWait` rather than `/result` for a job in flight.
1844
1844
  */
1845
1845
  getResult(jobId: string, options?: RequestOptions): APIPromise<JobResult>;
@@ -1849,7 +1849,7 @@ declare class Jobs {
1849
1849
  * a 10s cap), then fetches the full record. Resolves with the `CompletedJob`
1850
1850
  * on success; throws `JobFailedError` / `JobCancelledError` /
1851
1851
  * `JobTimedOutError` for the other terminal states, or `JobWaitTimeoutError`
1852
- * if the deadline passes first the job keeps running and is **not**
1852
+ * if the deadline passes first. The job keeps running and is **not**
1853
1853
  * cancelled (call `cancel(jobId)` to stop the spend). `signal` aborts the wait
1854
1854
  * with `APIUserAbortError`, also without cancelling.
1855
1855
  */
@@ -1863,7 +1863,7 @@ declare class Jobs {
1863
1863
  }
1864
1864
 
1865
1865
  interface ClientOptions {
1866
- /** Defaults to process.env['SIMMIT_SECRET_KEY'] exactly one env fallback. Construction
1866
+ /** Defaults to process.env['SIMMIT_SECRET_KEY'], exactly one env fallback. Construction
1867
1867
  * throws SimmitError('Missing secret key. Pass secretKey or set SIMMIT_SECRET_KEY.').
1868
1868
  * "Secret key" is the credential noun end to end (dashboard → docs → env var → option →
1869
1869
  * error): it spends credits and must never ship client-side. */
@@ -1922,7 +1922,7 @@ declare class APIError<TStatus extends number | undefined = number | undefined,
1922
1922
  readonly code: TCode;
1923
1923
  /** Typed `meta` from the error envelope. */
1924
1924
  readonly meta: TMeta;
1925
- /** Raw parsed JSON error body escape hatch for unmapped fields. */
1925
+ /** Raw parsed JSON error body: escape hatch for unmapped fields. */
1926
1926
  readonly error: object | undefined;
1927
1927
  constructor(status: TStatus, body: object | undefined, message: string | undefined, headers: Headers | undefined);
1928
1928
  private static makeMessage;
@@ -1954,7 +1954,7 @@ declare class InsufficientCreditsError extends BillingError {
1954
1954
  }
1955
1955
  type InsufficientCreditsLiabilityMeta = {
1956
1956
  reason: string;
1957
- /** The high-priority fee in effect top up, or resubmit at priority 'standard'. */
1957
+ /** The high-priority fee in effect. Top up, or resubmit at priority 'standard'. */
1958
1958
  priorityFeeCredits: number;
1959
1959
  docsUrl?: string;
1960
1960
  };
@@ -2036,7 +2036,7 @@ declare class MaxActiveJobsError extends RateLimitError {
2036
2036
  declare class InternalServerError extends APIError<number, string> {
2037
2037
  }
2038
2038
  /**
2039
- * 503 carries four enumerated codes with distinct meta a discriminated
2039
+ * 503 carries four enumerated codes with distinct meta: a discriminated
2040
2040
  * union, narrowed via `.body`. `api_maintenance` gets no special retry
2041
2041
  * behavior: standard policy applies, and the typed
2042
2042
  * `meta.retryAfterSeconds` is surfaced so callers can schedule their own
@@ -2100,7 +2100,7 @@ declare class JobCancelledError extends JobUnsuccessfulError {
2100
2100
  declare class JobTimedOutError extends JobUnsuccessfulError {
2101
2101
  }
2102
2102
  /**
2103
- * The SDK gave up polling the job itself is still running and billing.
2103
+ * The SDK gave up polling. The job itself is still running and billing.
2104
2104
  * Keep tracking via `jobs.get(jobId)` or stop the spend with `jobs.cancel(jobId)`.
2105
2105
  */
2106
2106
  declare class JobWaitTimeoutError extends SimmitError {
@@ -2127,8 +2127,8 @@ interface WebhookEvent {
2127
2127
  };
2128
2128
  }
2129
2129
  /**
2130
- * Verifies an `X-Simmit-Signature` header `t=<unix>,v1=<hex>`, an HMAC-SHA256
2131
- * (timing-safe) over `${t}.${rawBody}` within a 300s default tolerance and
2130
+ * Verifies an `X-Simmit-Signature` header (`t=<unix>,v1=<hex>`, an HMAC-SHA256
2131
+ * (timing-safe) over `${t}.${rawBody}` within a 300s default tolerance) and
2132
2132
  * returns the parsed event. Throws `WebhookVerificationError` on a bad
2133
2133
  * signature, malformed header, or stale timestamp.
2134
2134
  *
@@ -2140,4 +2140,15 @@ declare function unwrapWebhook(rawBody: string, signatureHeader: string, secret:
2140
2140
  toleranceSeconds?: number;
2141
2141
  }): Promise<WebhookEvent>;
2142
2142
 
2143
- export { APIConnectionError, APIConnectionTimeoutError, APIError, APIPromise, APIUserAbortError, type ArtifactUrl, Artifacts, AuthenticationError, type AuthenticationErrorCode, BadRequestError, BillingError, type ClientOptions, type CompletedJob, ConflictError, type CreditBalance, type CreditGrant, Credits, type GenericMeta, IdempotencyKeyReuseError, InsufficientCreditsError, InsufficientCreditsLiabilityError, type InsufficientCreditsLiabilityMeta, type InsufficientCreditsMeta, InternalServerError, InvalidProfileError, type Job, type JobCancelResponse, JobCancelledError, type JobCreateParams, type JobCreateResponse, type JobErrorCode, JobFailedError, JobNotCancellableError, type JobResult, type JobStatus, type JobStatusResponse, JobTimedOutError, JobUnsuccessfulError, type JobWaitOptions, JobWaitTimeoutError, Jobs, MaxActiveJobsError, type MetaValue, NotFoundError, RateLimitError, type RateLimitErrorCode, type RequestOptions, RequestTooLargeError, ResultNotReadyError, ResultUnavailableError, type ServiceUnavailableBody, ServiceUnavailableError, Simmit, SimmitError, UnprocessableEntityError, type WebhookEvent, WebhookVerificationError, Simmit as default, unwrapWebhook };
2143
+ /**
2144
+ * Job statuses that are terminal: a job in one of these has stopped and will
2145
+ * not change again. Terminal does not mean successful; only `completed` carries
2146
+ * a result (`failed`, `cancelled`, and `timed_out` do not).
2147
+ */
2148
+ declare const TERMINAL_JOB_STATUSES: readonly ["completed", "failed", "cancelled", "timed_out"];
2149
+ /** A `JobStatus` that is terminal: the job has stopped and will not change. */
2150
+ type TerminalJobStatus = (typeof TERMINAL_JOB_STATUSES)[number];
2151
+ /** True when `status` is terminal, i.e. the job has reached an end state. */
2152
+ declare function isTerminal(status: JobStatus): status is TerminalJobStatus;
2153
+
2154
+ export { APIConnectionError, APIConnectionTimeoutError, APIError, APIPromise, APIUserAbortError, type ArtifactUrl, Artifacts, AuthenticationError, type AuthenticationErrorCode, BadRequestError, BillingError, type ClientOptions, type CompletedJob, ConflictError, type CreditBalance, type CreditGrant, Credits, type GenericMeta, IdempotencyKeyReuseError, InsufficientCreditsError, InsufficientCreditsLiabilityError, type InsufficientCreditsLiabilityMeta, type InsufficientCreditsMeta, InternalServerError, InvalidProfileError, type Job, type JobCancelResponse, JobCancelledError, type JobCreateParams, type JobCreateResponse, type JobErrorCode, JobFailedError, JobNotCancellableError, type JobResult, type JobStatus, type JobStatusResponse, JobTimedOutError, JobUnsuccessfulError, type JobWaitOptions, JobWaitTimeoutError, Jobs, MaxActiveJobsError, type MetaValue, NotFoundError, RateLimitError, type RateLimitErrorCode, type RequestOptions, RequestTooLargeError, ResultNotReadyError, ResultUnavailableError, type ServiceUnavailableBody, ServiceUnavailableError, Simmit, SimmitError, TERMINAL_JOB_STATUSES, type TerminalJobStatus, UnprocessableEntityError, type WebhookEvent, WebhookVerificationError, Simmit as default, isTerminal, unwrapWebhook };
package/dist/index.js CHANGED
@@ -10,7 +10,7 @@ var APIError = class _APIError extends SimmitError {
10
10
  code;
11
11
  /** Typed `meta` from the error envelope. */
12
12
  meta;
13
- /** Raw parsed JSON error body escape hatch for unmapped fields. */
13
+ /** Raw parsed JSON error body: escape hatch for unmapped fields. */
14
14
  error;
15
15
  constructor(status, body, message, headers) {
16
16
  super(_APIError.makeMessage(status, body, message));
@@ -337,7 +337,7 @@ function buildHeaders(config, spec, options) {
337
337
  authorization: `Bearer ${config.secretKey}`,
338
338
  ...spec.body !== void 0 ? { "content-type": "application/json" } : {},
339
339
  ...idempotent && !options.idempotencyKey ? {
340
- // Generated once per call and reused across retry attempts — that
340
+ // Generated once per call and reused across retry attempts. That
341
341
  // is what makes POST retries safe by default. The auto
342
342
  // key is an SDK built-in default (lowest tier), so defaultHeaders
343
343
  // may override it.
@@ -390,7 +390,7 @@ var Artifacts = class {
390
390
  }
391
391
  /**
392
392
  * Fetch a stable public download URL for an artifact, valid for the
393
- * artifact's full retention window the same URL `jobs.getResult` returns,
393
+ * artifact's full retention window: the same URL `jobs.getResult` returns,
394
394
  * fetched on demand (e.g. browser flows that control the final fetch). The
395
395
  * artifact is gone (410) once its retention window passes.
396
396
  */
@@ -427,15 +427,6 @@ var MAX_POLL_INTERVAL_MS = 1e4;
427
427
  var POLL_BACKOFF_FACTOR = 1.5;
428
428
  var DEADLINE_GRACE_MS = 6e4;
429
429
  var FALLBACK_WAIT_TIMEOUT_MS = 45 * 60 * 1e3;
430
- var TERMINAL_STATUSES = /* @__PURE__ */ new Set([
431
- "completed",
432
- "failed",
433
- "cancelled",
434
- "timed_out"
435
- ]);
436
- function isTerminal(status) {
437
- return TERMINAL_STATUSES.has(status);
438
- }
439
430
  function deriveWaitTimeoutMs(created) {
440
431
  const { runtimeSeconds, queueSeconds } = created.runtime.ceiling;
441
432
  if (runtimeSeconds != null && queueSeconds != null) {
@@ -447,6 +438,17 @@ function nextPollInterval(interval) {
447
438
  return Math.min(interval * POLL_BACKOFF_FACTOR, MAX_POLL_INTERVAL_MS);
448
439
  }
449
440
 
441
+ // src/status.ts
442
+ var TERMINAL_JOB_STATUSES = [
443
+ "completed",
444
+ "failed",
445
+ "cancelled",
446
+ "timed_out"
447
+ ];
448
+ function isTerminal(status) {
449
+ return TERMINAL_JOB_STATUSES.includes(status);
450
+ }
451
+
450
452
  // src/resources/jobs.ts
451
453
  var Jobs = class {
452
454
  #client;
@@ -473,7 +475,7 @@ var Jobs = class {
473
475
  );
474
476
  }
475
477
  /**
476
- * Fetch the live status of a job in any state `status`, `errorCode`,
478
+ * Fetch the live status of a job in any state: `status`, `errorCode`,
477
479
  * `progress`, and `queue` estimate. Unlike `getResult`, it never throws for a
478
480
  * non-terminal job, so it is the supported way to drive a custom poll loop.
479
481
  */
@@ -488,7 +490,7 @@ var Jobs = class {
488
490
  }
489
491
  /**
490
492
  * Fetch the result summary of a terminal job. Throws `ResultNotReadyError`
491
- * (409) while the job is still running poll `/status` or use
493
+ * (409) while the job is still running. Poll `/status` or use
492
494
  * `createAndWait` rather than `/result` for a job in flight.
493
495
  */
494
496
  getResult(jobId, options) {
@@ -506,7 +508,7 @@ var Jobs = class {
506
508
  * a 10s cap), then fetches the full record. Resolves with the `CompletedJob`
507
509
  * on success; throws `JobFailedError` / `JobCancelledError` /
508
510
  * `JobTimedOutError` for the other terminal states, or `JobWaitTimeoutError`
509
- * if the deadline passes first the job keeps running and is **not**
511
+ * if the deadline passes first. The job keeps running and is **not**
510
512
  * cancelled (call `cancel(jobId)` to stop the spend). `signal` aborts the wait
511
513
  * with `APIUserAbortError`, also without cancelling.
512
514
  */
@@ -710,9 +712,11 @@ export {
710
712
  ServiceUnavailableError,
711
713
  Simmit,
712
714
  SimmitError,
715
+ TERMINAL_JOB_STATUSES,
713
716
  UnprocessableEntityError,
714
717
  WebhookVerificationError,
715
718
  Simmit as default,
719
+ isTerminal,
716
720
  unwrapWebhook
717
721
  };
718
722
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/error.ts","../src/api-promise.ts","../src/internal/abort.ts","../src/internal/request.ts","../src/resources/artifacts.ts","../src/resources/credits.ts","../src/internal/poll.ts","../src/resources/jobs.ts","../src/client.ts","../src/webhook.ts"],"sourcesContent":["import type { Job, JobStatus } from './api-types'\n\nexport class SimmitError extends Error {}\n\n/**\n * Value shapes the API's generic error `meta` bag can carry\n * (400/401/404/410/413 responses): JSON scalars, scalar arrays, or arrays of\n * flat objects.\n */\nexport type MetaValue =\n | string\n | number\n | boolean\n | null\n | Array<string | number | boolean>\n | Array<Record<string, string | number | boolean | null>>\n\nexport type GenericMeta = Record<string, MetaValue>\n\n/** The API's uniform error envelope: `{ error, code, meta }`. */\ninterface ErrorEnvelope {\n error?: unknown\n code?: unknown\n meta?: unknown\n}\n\nexport class APIError<\n TStatus extends number | undefined = number | undefined,\n TCode extends string | undefined = string | undefined,\n TMeta = GenericMeta | null\n> extends SimmitError {\n /** HTTP status of the response that caused the error. */\n readonly status: TStatus\n /** HTTP headers of the response that caused the error. */\n readonly headers: Headers | undefined\n /** Machine-readable `code` from the error envelope. */\n readonly code: TCode\n /** Typed `meta` from the error envelope. */\n readonly meta: TMeta\n /** Raw parsed JSON error body — escape hatch for unmapped fields. */\n readonly error: object | undefined\n\n constructor(\n status: TStatus,\n body: object | undefined,\n message: string | undefined,\n headers: Headers | undefined\n ) {\n super(APIError.makeMessage(status, body, message))\n this.status = status\n this.headers = headers\n this.error = body\n const envelope = body as ErrorEnvelope | undefined\n this.code = (\n typeof envelope?.code === 'string' ? envelope.code : undefined\n ) as TCode\n this.meta = (body ? (envelope?.meta ?? null) : undefined) as TMeta\n }\n\n private static makeMessage(\n status: number | undefined,\n body: object | undefined,\n message: string | undefined\n ): string {\n // The API's human-readable message field is named `error`, not `message`.\n const bodyMessage = (body as ErrorEnvelope | undefined)?.error\n const msg =\n typeof bodyMessage === 'string'\n ? bodyMessage\n : body\n ? JSON.stringify(body)\n : message\n\n if (status && msg) return `${status} ${msg}`\n if (status) return `${status} status code (no body)`\n if (msg) return msg\n return '(no status code or body)'\n }\n\n /**\n * Maps a response to the most specific error class: status selects the base\n * class; an enumerated `code` with structured `meta` selects the subclass;\n * anything unrecognized falls back to the status class so new server codes\n * degrade gracefully without breaking `instanceof` handling.\n */\n static generate(\n status: number | undefined,\n body: object | undefined,\n message: string | undefined,\n headers: Headers | undefined\n ): APIError<\n number | undefined,\n string | undefined,\n GenericMeta | null | undefined\n > {\n if (!status || !headers) {\n return new APIConnectionError({\n message,\n cause: body instanceof Error ? body : undefined\n })\n }\n\n const code = (body as ErrorEnvelope | undefined)?.code\n\n if (status === 400) return new BadRequestError(400, body, message, headers)\n if (status === 401) {\n return new AuthenticationError(401, body, message, headers)\n }\n if (status === 402) {\n if (code === 'insufficient_credits') {\n return new InsufficientCreditsError(402, body, message, headers)\n }\n if (code === 'insufficient_credits_liability') {\n return new InsufficientCreditsLiabilityError(\n 402,\n body,\n message,\n headers\n )\n }\n return new BillingError(402, body, message, headers)\n }\n if (status === 404) return new NotFoundError(404, body, message, headers)\n if (status === 409) {\n if (code === 'idempotency_key_reuse') {\n return new IdempotencyKeyReuseError(409, body, message, headers)\n }\n if (code === 'result_not_ready') {\n return new ResultNotReadyError(409, body, message, headers)\n }\n if (code === 'job_not_cancellable') {\n return new JobNotCancellableError(409, body, message, headers)\n }\n return new ConflictError(409, body, message, headers)\n }\n if (status === 413) {\n return new RequestTooLargeError(413, body, message, headers)\n }\n if (status === 422) {\n if (code === 'input_sanitized_rejected') {\n return new InvalidProfileError(422, body, message, headers)\n }\n if (code === 'result_unavailable') {\n return new ResultUnavailableError(422, body, message, headers)\n }\n return new UnprocessableEntityError(422, body, message, headers)\n }\n if (status === 429) {\n if (code === 'max_active_jobs_exceeded') {\n return new MaxActiveJobsError(429, body, message, headers)\n }\n return new RateLimitError(429, body, message, headers)\n }\n if (status === 503 && isServiceUnavailableBody(body)) {\n return new ServiceUnavailableError(503, body, message, headers)\n }\n if (status >= 500) {\n return new InternalServerError(status, body, message, headers)\n }\n return new APIError(status, body, message, headers)\n }\n}\n\n// ── 4xx status classes (code subclasses where the spec enumerates) ──────────\n\nexport class BadRequestError extends APIError<400, string> {}\n\nexport type AuthenticationErrorCode =\n | 'missing_token'\n | 'invalid_token'\n | 'revoked_token'\n | 'expired_token'\n\nexport class AuthenticationError extends APIError<\n 401,\n AuthenticationErrorCode\n> {}\n\n// 402 codes are docs-enumerated; the spec leaves `code` un-enumerated, so the\n// base class keeps `string` for forward compatibility.\nexport class BillingError extends APIError<402, string> {}\n\nexport type InsufficientCreditsMeta = {\n reason: string\n ceilingRuntimeSeconds?: number\n /** Largest maxRuntimeSeconds the current balance can cover. */\n maxAffordableRuntimeSeconds?: number\n docsUrl?: string\n}\n\nexport class InsufficientCreditsError extends BillingError {\n declare readonly code: 'insufficient_credits'\n declare readonly meta: InsufficientCreditsMeta | null\n}\n\nexport type InsufficientCreditsLiabilityMeta = {\n reason: string\n /** The high-priority fee in effect — top up, or resubmit at priority 'standard'. */\n priorityFeeCredits: number\n docsUrl?: string\n}\n\nexport class InsufficientCreditsLiabilityError extends BillingError {\n declare readonly code: 'insufficient_credits_liability'\n declare readonly meta: InsufficientCreditsLiabilityMeta | null\n}\n\nexport class NotFoundError extends APIError<404, string> {}\n\nexport class ConflictError extends APIError<409, string> {}\n\nexport class IdempotencyKeyReuseError extends ConflictError {\n declare readonly code: 'idempotency_key_reuse'\n declare readonly meta: {\n reason: 'idempotency_key_reuse'\n /** ID of the job that originally consumed this idempotency key. */\n originalJobId: string\n docsUrl?: string\n }\n}\n\nexport class ResultNotReadyError extends ConflictError {\n declare readonly code: 'result_not_ready'\n declare readonly meta: {\n status: 'pending' | 'queued' | 'starting' | 'running'\n }\n}\n\nexport class JobNotCancellableError extends ConflictError {\n declare readonly code: 'job_not_cancellable'\n declare readonly meta: { id: string; status: JobStatus }\n}\n\nexport class RequestTooLargeError extends APIError<413, string> {}\n\nexport class UnprocessableEntityError extends APIError<422, string> {}\n\nexport class InvalidProfileError extends UnprocessableEntityError {\n declare readonly code: 'input_sanitized_rejected'\n declare readonly meta: {\n reason: 'input_sanitized_rejected'\n message: string\n docsUrl: string\n /** Sample of rejected lines; see blockedCount/blockedTruncated for the full set. */\n blocked: Array<{ line: number; text: string }>\n blockedCount: number\n blockedTruncated: boolean\n }\n}\n\nexport class ResultUnavailableError extends UnprocessableEntityError {\n declare readonly code: 'result_unavailable'\n declare readonly meta: {\n status: 'completed' | 'failed' | 'cancelled' | 'timed_out'\n }\n}\n\nexport type RateLimitErrorCode =\n | 'rate_limit_exceeded'\n | 'max_active_jobs_exceeded'\n\nexport class RateLimitError extends APIError<429, RateLimitErrorCode> {\n declare readonly meta:\n | { scope: 'developer' }\n | {\n reason: 'max_active_jobs_exceeded'\n maxActiveJobs: number\n activeJobs: number\n }\n | null\n}\n\nexport class MaxActiveJobsError extends RateLimitError {\n declare readonly code: 'max_active_jobs_exceeded'\n declare readonly meta: {\n reason: 'max_active_jobs_exceeded'\n /** Maximum number of jobs the account can have in flight. */\n maxActiveJobs: number\n /** Jobs in flight when this request was rejected. */\n activeJobs: number\n }\n}\n\n// ── 5xx ─────────────────────────────────────────────────────────────────────\n\nexport class InternalServerError extends APIError<number, string> {}\n\n/**\n * 503 carries four enumerated codes with distinct meta — a discriminated\n * union, narrowed via `.body`. `api_maintenance` gets no special retry\n * behavior: standard policy applies, and the typed\n * `meta.retryAfterSeconds` is surfaced so callers can schedule their own\n * resubmission.\n */\nexport type ServiceUnavailableBody =\n | {\n code: 'queue_unavailable'\n meta: { reason: 'queue_unavailable'; queueHealth: string }\n }\n | {\n code: 'queue_health_unknown'\n meta: { reason: 'queue_health_unknown'; laneId: string }\n }\n | {\n code: 'secret_store_unavailable'\n meta: { reason: 'secret_store_unavailable' }\n }\n | { code: 'api_maintenance'; meta: { retryAfterSeconds: number } }\n\nconst SERVICE_UNAVAILABLE_CODES = new Set([\n 'queue_unavailable',\n 'queue_health_unknown',\n 'secret_store_unavailable',\n 'api_maintenance'\n])\n\n// A 503 whose body isn't the enumerated envelope (e.g. load-balancer HTML)\n// falls back to InternalServerError so `.body` below never lies.\nfunction isServiceUnavailableBody(\n body: object | undefined\n): body is ServiceUnavailableBody {\n const code = (body as ErrorEnvelope | undefined)?.code\n return typeof code === 'string' && SERVICE_UNAVAILABLE_CODES.has(code)\n}\n\nexport class ServiceUnavailableError extends InternalServerError {\n declare readonly status: 503\n\n /** The discriminated 503 envelope: `if (e.body.code === 'api_maintenance') e.body.meta.retryAfterSeconds`. */\n get body(): ServiceUnavailableBody {\n return this.error as ServiceUnavailableBody\n }\n}\n\n// ── No HTTP response ────────────────────────────────────────────────────────\n\nexport class APIConnectionError extends APIError<\n undefined,\n undefined,\n undefined\n> {\n constructor({\n message,\n cause\n }: { message?: string | undefined; cause?: Error | undefined } = {}) {\n super(undefined, undefined, message ?? 'Connection error.', undefined)\n if (cause) this.cause = cause\n }\n}\n\nexport class APIConnectionTimeoutError extends APIConnectionError {\n constructor({ message }: { message?: string } = {}) {\n super({ message: message ?? 'Request timed out.' })\n }\n}\n\nexport class APIUserAbortError extends APIError<\n undefined,\n undefined,\n undefined\n> {\n constructor({ message }: { message?: string } = {}) {\n super(undefined, undefined, message ?? 'Request was aborted.', undefined)\n }\n}\n\n// ── Job-level errors (thrown only by createAndWait) ──────────────────────────\n\n/** Catch-all for a job that reached a terminal state other than `completed`. */\nexport abstract class JobUnsuccessfulError extends SimmitError {\n readonly job: Job\n\n constructor(job: Job, message?: string) {\n super(\n message ??\n `Job ${job.id} ${job.status}` +\n (job.statusReason ? `: ${job.statusReason}` : '') +\n (job.errorCode ? ` (${job.errorCode})` : '')\n )\n this.job = job\n }\n}\n\nexport class JobFailedError extends JobUnsuccessfulError {}\n\n/** Includes queue_timeout auto-cancellation, not just user cancels. */\nexport class JobCancelledError extends JobUnsuccessfulError {}\n\n/** The job hit its runtime ceiling server-side and is billed for what ran. */\nexport class JobTimedOutError extends JobUnsuccessfulError {}\n\n/**\n * The SDK gave up polling — the job itself is still running and billing.\n * Keep tracking via `jobs.get(jobId)` or stop the spend with `jobs.cancel(jobId)`.\n */\nexport class JobWaitTimeoutError extends SimmitError {\n readonly jobId: string\n readonly lastStatus: JobStatus\n\n constructor(args: {\n jobId: string\n lastStatus: JobStatus\n message?: string\n }) {\n super(\n args.message ??\n `Timed out waiting for job ${args.jobId} (last status: ${args.lastStatus}). ` +\n 'The job is still running server-side and continues to bill.'\n )\n this.jobId = args.jobId\n this.lastStatus = args.lastStatus\n }\n}\n\n// ── Webhook verification (thrown by unwrapWebhook) ───────────────────────────\n\nexport class WebhookVerificationError extends SimmitError {}\n","/**\n * A `Promise<T>` with raw-response access — the generic answer to response\n * headers the return types can't see (`X-Idempotent-Replay`, `X-Active-Jobs`,\n * `X-RateLimit-*`).\n *\n * const { data, response } = await client.jobs.create(params).withResponse()\n * response.headers.get('x-idempotent-replay')\n */\nexport class APIPromise<T> extends Promise<T> {\n // Chained promises (.then/.catch) must be plain Promises: this class's\n // constructor signature is incompatible with the executor the runtime\n // would otherwise pass via the species constructor.\n static override get [Symbol.species]() {\n return Promise\n }\n\n readonly #parsed: Promise<{ data: T; response: Response }>\n\n constructor(parsed: Promise<{ data: T; response: Response }>) {\n // The base promise is a pre-settled placeholder that is never observed:\n // then() below delegates to #parsed lazily (catch/finally route through\n // then() per spec). Subscribing eagerly here would reject this instance\n // even when the caller only consumes withResponse(), leaking an\n // unhandled rejection on failures.\n super((resolve) => resolve(undefined as never))\n this.#parsed = parsed\n }\n\n override then<TResult1 = T, TResult2 = never>(\n onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null,\n onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null\n ): Promise<TResult1 | TResult2> {\n return this.#parsed\n .then((result) => result.data)\n .then(onfulfilled, onrejected)\n }\n\n withResponse(): Promise<{ data: T; response: Response }> {\n return this.#parsed\n }\n\n asResponse(): Promise<Response> {\n return this.#parsed.then((result) => result.response)\n }\n}\n","// Abort-aware timing utilities shared by the request layer (backoff sleeps) and\n// the createAndWait poll loop. A pending sleep rejects with APIUserAbortError\n// the moment the caller's signal fires.\nimport { APIUserAbortError } from '../error'\n\nexport function throwIfUserAborted(signal: AbortSignal | undefined): void {\n if (signal?.aborted) throw new APIUserAbortError()\n}\n\nexport function sleep(\n ms: number,\n signal: AbortSignal | undefined\n): Promise<void> {\n return new Promise((resolve, reject) => {\n throwIfUserAborted(signal)\n const timeoutId = setTimeout(() => {\n signal?.removeEventListener('abort', onAbort)\n resolve()\n }, ms)\n const onAbort = () => {\n clearTimeout(timeoutId)\n reject(new APIUserAbortError())\n }\n signal?.addEventListener('abort', onAbort, { once: true })\n })\n}\n","// Internal request layer: header assembly, per-attempt timeout/abort\n// composition, retry with backoff + Retry-After, idempotency-key injection,\n// and error mapping. Not exported from the package.\nimport { APIPromise } from '../api-promise'\nimport {\n APIConnectionError,\n APIConnectionTimeoutError,\n APIError,\n APIUserAbortError\n} from '../error'\nimport type { RequestOptions } from '../client'\nimport { sleep, throwIfUserAborted } from './abort'\n\nexport interface ClientConfig {\n secretKey: string\n baseURL: string\n timeout: number\n maxRetries: number\n defaultHeaders: Record<string, string | null | undefined> | undefined\n fetch: typeof globalThis.fetch\n fetchOptions: RequestInit | undefined\n}\n\nexport interface RequestSpec {\n method: 'GET' | 'POST'\n path: string\n body?: unknown\n /** POST job creation: auto-generate an idempotency-key when none supplied. */\n idempotent?: boolean\n}\n\n// Retry policy constants — typed code config, not env.\nconst INITIAL_BACKOFF_MS = 500\nconst MAX_BACKOFF_MS = 8_000\nconst MAX_RETRY_AFTER_MS = 60_000\n\nexport function makeRequest<T>(\n config: ClientConfig,\n spec: RequestSpec,\n options: RequestOptions = {}\n): APIPromise<T> {\n return new APIPromise(run<T>(config, spec, options))\n}\n\nasync function run<T>(\n config: ClientConfig,\n spec: RequestSpec,\n options: RequestOptions\n): Promise<{ data: T; response: Response }> {\n const maxRetries = options.maxRetries ?? config.maxRetries\n const timeout = options.timeout ?? config.timeout\n const headers = buildHeaders(config, spec, options)\n const url = `${config.baseURL.replace(/\\/+$/, '')}${spec.path}`\n const body = spec.body === undefined ? undefined : JSON.stringify(spec.body)\n\n for (let attempt = 0; ; attempt++) {\n throwIfUserAborted(options.signal)\n\n let result: AttemptResult\n try {\n result = await fetchAttempt(config, spec, options, {\n url,\n headers,\n body,\n timeout\n })\n } catch (err) {\n if (err instanceof APIUserAbortError) throw err\n // Connection error, malformed success body, or per-attempt timeout —\n // all retryable.\n if (attempt < maxRetries) {\n await backoff(attempt, undefined, options.signal)\n continue\n }\n throw err\n }\n\n const { response, json } = result\n\n if (response.ok) {\n return { data: json as T, response }\n }\n\n if (shouldRetryStatus(response.status) && attempt < maxRetries) {\n await backoff(\n attempt,\n response.headers.get('retry-after'),\n options.signal\n )\n continue\n }\n\n throw APIError.generate(\n response.status,\n typeof json === 'object' && json !== null ? json : undefined,\n response.statusText,\n response.headers\n )\n }\n}\n\ninterface AttemptResult {\n response: Response\n /** Parsed JSON body; undefined when an error response carried a non-JSON body. */\n json: unknown\n}\n\nasync function fetchAttempt(\n config: ClientConfig,\n spec: RequestSpec,\n options: RequestOptions,\n attempt: {\n url: string\n headers: Record<string, string>\n body: string | undefined\n timeout: number\n }\n): Promise<AttemptResult> {\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), attempt.timeout)\n const onUserAbort = () => controller.abort()\n options.signal?.addEventListener('abort', onUserAbort, { once: true })\n\n try {\n const response = await config.fetch(attempt.url, {\n ...config.fetchOptions,\n method: spec.method,\n headers: attempt.headers,\n ...(attempt.body !== undefined ? { body: attempt.body } : {}),\n signal: controller.signal\n })\n\n // The body is read inside the timed scope too — a stalled body must not\n // hang past the per-attempt timeout (fetch ties the body stream to the\n // controller's signal, so the abort cancels the read).\n let json: unknown\n if (response.ok) {\n // Success bodies must parse; a truncated/malformed one is treated as a\n // transport failure (classified below) and retried like one.\n json = await response.json()\n } else {\n try {\n json = await response.json()\n } catch (err) {\n // Aborted mid-read is a timeout/abort, not a non-JSON body.\n if (controller.signal.aborted) throw err\n json = undefined // e.g. a load-balancer HTML error page\n }\n }\n return { response, json }\n } catch (err) {\n if (options.signal?.aborted) throw new APIUserAbortError()\n if (controller.signal.aborted) {\n throw new APIConnectionTimeoutError()\n }\n throw new APIConnectionError({\n cause: err instanceof Error ? err : undefined\n })\n } finally {\n clearTimeout(timeoutId)\n options.signal?.removeEventListener('abort', onUserAbort)\n }\n}\n\nfunction buildHeaders(\n config: ClientConfig,\n spec: RequestSpec,\n options: RequestOptions\n): Record<string, string> {\n const idempotent = spec.idempotent && spec.method === 'POST'\n const merged: Record<string, string | null | undefined> = {\n authorization: `Bearer ${config.secretKey}`,\n ...(spec.body !== undefined ? { 'content-type': 'application/json' } : {}),\n ...(idempotent && !options.idempotencyKey\n ? {\n // Generated once per call and reused across retry attempts — that\n // is what makes POST retries safe by default. The auto\n // key is an SDK built-in default (lowest tier), so defaultHeaders\n // may override it.\n 'idempotency-key': `simmit-node-retry-${crypto.randomUUID()}`\n }\n : {}),\n ...lowercaseKeys(config.defaultHeaders),\n ...(idempotent && options.idempotencyKey\n ? {\n // An explicit key is a per-request option: it must beat constructor\n // defaultHeaders. Raw options.headers still wins last.\n 'idempotency-key': options.idempotencyKey\n }\n : {}),\n ...lowercaseKeys(options.headers)\n }\n\n const headers: Record<string, string> = {}\n for (const [key, value] of Object.entries(merged)) {\n // A null value deletes the header; undefined entries are skipped.\n if (typeof value === 'string') headers[key] = value\n }\n return headers\n}\n\nfunction lowercaseKeys(\n record: Record<string, string | null | undefined> | undefined\n): Record<string, string | null | undefined> {\n if (!record) return {}\n return Object.fromEntries(\n Object.entries(record).map(([key, value]) => [key.toLowerCase(), value])\n )\n}\n\nfunction shouldRetryStatus(status: number): boolean {\n // 408 kept defensively even though the API never emits it. 409 is never\n // retried: result_not_ready is thrown immediately by design and the other\n // 409s are deterministic.\n return status === 408 || status === 429 || status >= 500\n}\n\nasync function backoff(\n attempt: number,\n retryAfterHeader: string | null | undefined,\n signal: AbortSignal | undefined\n): Promise<void> {\n const retryAfterMs = parseRetryAfter(retryAfterHeader)\n const delay =\n retryAfterMs !== undefined\n ? retryAfterMs\n : Math.min(INITIAL_BACKOFF_MS * 2 ** attempt, MAX_BACKOFF_MS) *\n (1 - 0.25 * Math.random())\n await sleep(delay, signal)\n}\n\n/** Accepts `Retry-After` only when it parses to a delay in (0, 60s] — the SDK never sleeps arbitrarily long on a server hint. */\nfunction parseRetryAfter(\n header: string | null | undefined\n): number | undefined {\n if (!header) return undefined\n let ms: number\n if (/^\\d+$/.test(header.trim())) {\n ms = Number(header.trim()) * 1000\n } else {\n ms = new Date(header).getTime() - Date.now()\n }\n return Number.isFinite(ms) && ms > 0 && ms <= MAX_RETRY_AFTER_MS\n ? ms\n : undefined\n}\n","import type { APIPromise } from '../api-promise'\nimport type { ArtifactUrl } from '../api-types'\nimport type Simmit from '../client'\nimport type { RequestOptions } from '../client'\n\n/** The `artifacts` resource. */\nexport class Artifacts {\n readonly #client: Simmit\n\n constructor(client: Simmit) {\n this.#client = client\n }\n\n /**\n * Fetch a stable public download URL for an artifact, valid for the\n * artifact's full retention window — the same URL `jobs.getResult` returns,\n * fetched on demand (e.g. browser flows that control the final fetch). The\n * artifact is gone (410) once its retention window passes.\n */\n getUrl(\n artifactId: string,\n options?: RequestOptions\n ): APIPromise<ArtifactUrl> {\n return this.#client._request<ArtifactUrl>(\n {\n method: 'GET',\n path: `/v1/simc/artifacts/${encodeURIComponent(artifactId)}/url`\n },\n options\n )\n }\n}\n","import type { APIPromise } from '../api-promise'\nimport type { CreditBalance } from '../api-types'\nimport type Simmit from '../client'\nimport type { RequestOptions } from '../client'\n\n/** The `credits` resource. */\nexport class Credits {\n readonly #client: Simmit\n\n constructor(client: Simmit) {\n this.#client = client\n }\n\n /** Fetch the account's current credit balance and per-grant breakdown. */\n get(options?: RequestOptions): APIPromise<CreditBalance> {\n return this.#client._request<CreditBalance>(\n { method: 'GET', path: '/v1/simc/credits' },\n options\n )\n }\n}\n","// Pure helpers for the createAndWait poll loop. Kept separate from the\n// orchestration so the cadence and deadline math are unit-testable.\nimport type { JobCreateResponse, JobStatus } from '../api-types'\n\nexport const MIN_POLL_INTERVAL_MS = 100\nexport const DEFAULT_POLL_INTERVAL_MS = 1_000\nexport const MAX_POLL_INTERVAL_MS = 10_000\nexport const POLL_BACKOFF_FACTOR = 1.5\nconst DEADLINE_GRACE_MS = 60_000\nconst FALLBACK_WAIT_TIMEOUT_MS = 45 * 60 * 1_000\n\nconst TERMINAL_STATUSES = new Set<JobStatus>([\n 'completed',\n 'failed',\n 'cancelled',\n 'timed_out'\n])\n\nexport function isTerminal(status: JobStatus): boolean {\n return TERMINAL_STATUSES.has(status)\n}\n\n/**\n * Default wait deadline: the applied ceilings the create response reports —\n * `(queueSeconds + runtimeSeconds) × 1000` plus a 60s grace — or a 45-minute\n * fallback when either ceiling is null.\n */\nexport function deriveWaitTimeoutMs(created: JobCreateResponse): number {\n const { runtimeSeconds, queueSeconds } = created.runtime.ceiling\n if (runtimeSeconds != null && queueSeconds != null) {\n return (runtimeSeconds + queueSeconds) * 1_000 + DEADLINE_GRACE_MS\n }\n return FALLBACK_WAIT_TIMEOUT_MS\n}\n\n/** Next poll interval: grow ×1.5, capped at 10s. */\nexport function nextPollInterval(interval: number): number {\n return Math.min(interval * POLL_BACKOFF_FACTOR, MAX_POLL_INTERVAL_MS)\n}\n","import type { APIPromise } from '../api-promise'\nimport type {\n CompletedJob,\n Job,\n JobCancelResponse,\n JobCreateParams,\n JobCreateResponse,\n JobResult,\n JobStatus,\n JobStatusResponse\n} from '../api-types'\nimport type Simmit from '../client'\nimport type { RequestOptions } from '../client'\nimport {\n JobCancelledError,\n JobFailedError,\n JobTimedOutError,\n JobWaitTimeoutError\n} from '../error'\nimport { sleep } from '../internal/abort'\nimport {\n DEFAULT_POLL_INTERVAL_MS,\n deriveWaitTimeoutMs,\n isTerminal,\n MIN_POLL_INTERVAL_MS,\n nextPollInterval\n} from '../internal/poll'\n\nexport interface JobWaitOptions extends RequestOptions {\n /** Initial delay between status polls, ms. Grows ×1.5 per poll to a 10s cap; values under 100 are raised to it. Default 1_000. */\n pollIntervalMs?: number\n /** Overall wait deadline, ms. Default derived from the job's applied ceilings. */\n waitTimeoutMs?: number\n /** Fired once with the raw create response (job id, ceilings, input warnings) before polling. */\n onCreated?: (response: JobCreateResponse) => void\n /** Fired after every successful status poll (progress, stage, queue estimate). */\n onPoll?: (status: JobStatusResponse) => void\n}\n\n/**\n * The `jobs` resource. Each single-request method is a thin wrapper over\n * `client._request` with the path/method/types pinned to the spec;\n * `createAndWait` orchestrates several of them.\n */\nexport class Jobs {\n readonly #client: Simmit\n\n constructor(client: Simmit) {\n this.#client = client\n }\n\n /**\n * Submit a new SimC sim. Returns immediately with the job handle; the sim\n * runs asynchronously. `idempotent: true` makes the request layer attach an\n * auto-generated idempotency key so the POST is safe to retry; pass\n * `options.idempotencyKey` to supply your own.\n */\n create(\n params: JobCreateParams,\n options?: RequestOptions\n ): APIPromise<JobCreateResponse> {\n return this.#client._request<JobCreateResponse>(\n { method: 'POST', path: '/v1/simc/jobs', body: params, idempotent: true },\n options\n )\n }\n\n /** Fetch the full record for a job. */\n get(jobId: string, options?: RequestOptions): APIPromise<Job> {\n return this.#client._request<Job>(\n { method: 'GET', path: `/v1/simc/jobs/${encodeURIComponent(jobId)}` },\n options\n )\n }\n\n /**\n * Fetch the live status of a job in any state — `status`, `errorCode`,\n * `progress`, and `queue` estimate. Unlike `getResult`, it never throws for a\n * non-terminal job, so it is the supported way to drive a custom poll loop.\n */\n getStatus(\n jobId: string,\n options?: RequestOptions\n ): APIPromise<JobStatusResponse> {\n return this.#client._request<JobStatusResponse>(\n {\n method: 'GET',\n path: `/v1/simc/jobs/${encodeURIComponent(jobId)}/status`\n },\n options\n )\n }\n\n /**\n * Fetch the result summary of a terminal job. Throws `ResultNotReadyError`\n * (409) while the job is still running — poll `/status` or use\n * `createAndWait` rather than `/result` for a job in flight.\n */\n getResult(jobId: string, options?: RequestOptions): APIPromise<JobResult> {\n return this.#client._request<JobResult>(\n {\n method: 'GET',\n path: `/v1/simc/jobs/${encodeURIComponent(jobId)}/result`\n },\n options\n )\n }\n\n /**\n * Submit a job and resolve once it reaches a terminal state. Polls\n * `GET /v1/simc/jobs/{id}/status` (first after `pollIntervalMs`, then ×1.5 to\n * a 10s cap), then fetches the full record. Resolves with the `CompletedJob`\n * on success; throws `JobFailedError` / `JobCancelledError` /\n * `JobTimedOutError` for the other terminal states, or `JobWaitTimeoutError`\n * if the deadline passes first — the job keeps running and is **not**\n * cancelled (call `cancel(jobId)` to stop the spend). `signal` aborts the wait\n * with `APIUserAbortError`, also without cancelling.\n */\n async createAndWait(\n params: JobCreateParams,\n options: JobWaitOptions = {}\n ): Promise<CompletedJob> {\n const {\n pollIntervalMs,\n waitTimeoutMs,\n onCreated,\n onPoll,\n ...requestOptions\n } = options\n\n const created = await this.create(params, requestOptions)\n onCreated?.(created)\n\n const deadline =\n Date.now() + (waitTimeoutMs ?? deriveWaitTimeoutMs(created))\n // nextPollInterval only ever grows the interval, so a non-positive seed\n // would hot-poll the status endpoint; floor it.\n let interval = Math.max(\n pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS,\n MIN_POLL_INTERVAL_MS\n )\n let lastStatus: JobStatus = 'pending'\n\n for (;;) {\n const remaining = deadline - Date.now()\n if (remaining <= 0) {\n throw new JobWaitTimeoutError({ jobId: created.id, lastStatus })\n }\n // Never sleep past the deadline, so the wait gives up promptly.\n await sleep(Math.min(interval, remaining), requestOptions.signal)\n\n const status = await this.getStatus(created.id, requestOptions)\n onPoll?.(status)\n lastStatus = status.status\n\n if (isTerminal(status.status)) {\n // The status payload is lightweight; the full record carries the fields\n // CompletedJob and the job-error classes expose.\n const job = await this.get(created.id, requestOptions)\n switch (job.status) {\n case 'completed':\n return job as CompletedJob\n case 'failed':\n throw new JobFailedError(job)\n case 'cancelled':\n throw new JobCancelledError(job)\n case 'timed_out':\n throw new JobTimedOutError(job)\n }\n // Raced back to non-terminal between /status and the full record; keep polling.\n lastStatus = job.status\n }\n interval = nextPollInterval(interval)\n }\n }\n\n /**\n * Request cancellation. Returns `status: 'cancelled'` when the job ended\n * before it ran, or `status: 'cancel_requested'` when an in-flight job was\n * signaled to stop. Repeat calls are naturally idempotent, so no key is sent.\n */\n cancel(\n jobId: string,\n options?: RequestOptions\n ): APIPromise<JobCancelResponse> {\n return this.#client._request<JobCancelResponse>(\n {\n method: 'POST',\n path: `/v1/simc/jobs/${encodeURIComponent(jobId)}/cancel`\n },\n options\n )\n }\n}\n","import { APIPromise } from './api-promise'\nimport { SimmitError } from './error'\nimport {\n makeRequest,\n type ClientConfig,\n type RequestSpec\n} from './internal/request'\nimport { Artifacts } from './resources/artifacts'\nimport { Credits } from './resources/credits'\nimport { Jobs } from './resources/jobs'\n\nexport interface ClientOptions {\n /** Defaults to process.env['SIMMIT_SECRET_KEY'] — exactly one env fallback. Construction\n * throws SimmitError('Missing secret key. Pass secretKey or set SIMMIT_SECRET_KEY.').\n * \"Secret key\" is the credential noun end to end (dashboard → docs → env var → option →\n * error): it spends credits and must never ship client-side. */\n secretKey?: string | null\n /** Defaults to process.env['SIMMIT_BASE_URL'] ?? 'https://api.simmit.com'. */\n baseURL?: string | null\n /** Per-attempt timeout in ms. Default 60_000. (Retries can extend total wall time.) */\n timeout?: number\n /** Max retries after the first attempt for retryable failures. Default 2. */\n maxRetries?: number\n /** Headers sent with every request. Merged under per-request headers. */\n defaultHeaders?: Record<string, string | null | undefined>\n /** Custom fetch (testing, proxies). Defaults to globalThis.fetch. */\n fetch?: typeof globalThis.fetch\n /** Extra RequestInit fields passed to every fetch call (e.g. undici dispatcher). */\n fetchOptions?: RequestInit\n}\n\nexport interface RequestOptions {\n /** Per-attempt timeout in ms. Overrides ClientOptions.timeout. */\n timeout?: number\n /** Abort the call (including retries and waiting). Throws APIUserAbortError. Never retried. */\n signal?: AbortSignal\n /** Overrides ClientOptions.maxRetries for this call. */\n maxRetries?: number\n /** Merged over defaultHeaders; a null value deletes the header. */\n headers?: Record<string, string | null | undefined>\n /** jobs.create / jobs.createAndWait only: replaces the auto-generated idempotency-key. */\n idempotencyKey?: string\n}\n\nexport default class Simmit {\n readonly jobs: Jobs\n readonly credits: Credits\n readonly artifacts: Artifacts\n\n readonly baseURL: string\n\n readonly #config: ClientConfig\n\n constructor(options: ClientOptions = {}) {\n const secretKey = options.secretKey ?? readEnv('SIMMIT_SECRET_KEY')\n if (!secretKey) {\n throw new SimmitError(\n 'Missing secret key. Pass secretKey or set SIMMIT_SECRET_KEY.'\n )\n }\n\n this.baseURL =\n options.baseURL ?? readEnv('SIMMIT_BASE_URL') ?? 'https://api.simmit.com'\n\n this.#config = {\n secretKey,\n baseURL: this.baseURL,\n timeout: options.timeout ?? 60_000,\n maxRetries: options.maxRetries ?? 2,\n defaultHeaders: options.defaultHeaders,\n // Resolved lazily so a fetch patched onto globalThis after the client\n // is constructed (msw, APM instrumentation) is still honored.\n fetch: options.fetch ?? ((...args) => globalThis.fetch(...args)),\n fetchOptions: options.fetchOptions\n }\n\n this.jobs = new Jobs(this)\n this.credits = new Credits(this)\n this.artifacts = new Artifacts(this)\n }\n\n /** @internal Resource classes route through here; not public surface. */\n _request<T>(spec: RequestSpec, options?: RequestOptions): APIPromise<T> {\n return makeRequest(this.#config, spec, options)\n }\n}\n\nfunction readEnv(name: string): string | undefined {\n if (typeof process === 'undefined') return undefined\n const value = process.env?.[name]?.trim()\n return value || undefined\n}\n","// Standalone webhook verification. Not a client method: receivers must not\n// need a secret-key-bearing client (whose constructor throws without a key).\n// WebCrypto only — zero deps, and runs in Workers as well as Node.\nimport type { JobStatus } from './api-types'\nimport { WebhookVerificationError } from './error'\n\nconst DEFAULT_TOLERANCE_SECONDS = 300\n\n/** The one hand-written wire type: the webhook payload has no OpenAPI schema. */\nexport interface WebhookEvent {\n kind: 'job.terminal'\n version: 'v1'\n timestamp: string\n payload: {\n id: string\n statusReason: string | null\n status: Extract<\n JobStatus,\n 'completed' | 'failed' | 'cancelled' | 'timed_out'\n >\n }\n}\n\n/**\n * Verifies an `X-Simmit-Signature` header — `t=<unix>,v1=<hex>`, an HMAC-SHA256\n * (timing-safe) over `${t}.${rawBody}` within a 300s default tolerance — and\n * returns the parsed event. Throws `WebhookVerificationError` on a bad\n * signature, malformed header, or stale timestamp.\n *\n * Pass `rawBody` exactly as received: re-serializing changes the bytes and\n * breaks verification. `secret` is the webhook signing secret (dashboard →\n * Clients & Keys → Webhook), not your API key.\n */\nexport async function unwrapWebhook(\n rawBody: string,\n signatureHeader: string,\n secret: string,\n options?: { toleranceSeconds?: number }\n): Promise<WebhookEvent> {\n // An empty secret would otherwise surface as an opaque WebCrypto DataError;\n // a NaN tolerance would make the age check pass for everything.\n if (!secret) {\n throw new WebhookVerificationError('Webhook signing secret is empty.')\n }\n const tolerance = options?.toleranceSeconds ?? DEFAULT_TOLERANCE_SECONDS\n if (!Number.isFinite(tolerance) || tolerance < 0) {\n throw new WebhookVerificationError(\n 'toleranceSeconds must be a non-negative number.'\n )\n }\n\n const { timestampRaw, timestamp, signature } =\n parseSignatureHeader(signatureHeader)\n\n const expected = await hmacSha256Hex(secret, `${timestampRaw}.${rawBody}`)\n if (!timingSafeEqual(expected, signature)) {\n throw new WebhookVerificationError('Webhook signature does not match.')\n }\n\n // Compare on whole seconds, matching the header's unix-seconds `t`.\n if (Math.abs(Math.floor(Date.now() / 1000) - timestamp) > tolerance) {\n throw new WebhookVerificationError(\n 'Webhook timestamp is outside the tolerance window.'\n )\n }\n\n try {\n return JSON.parse(rawBody) as WebhookEvent\n } catch {\n throw new WebhookVerificationError('Webhook body is not valid JSON.')\n }\n}\n\nfunction parseSignatureHeader(header: string): {\n timestampRaw: string\n timestamp: number\n signature: string\n} {\n let timestampRaw: string | undefined\n let signature: string | undefined\n for (const part of header.split(',')) {\n const eq = part.indexOf('=')\n if (eq === -1) continue\n const key = part.slice(0, eq).trim()\n const value = part.slice(eq + 1).trim()\n if (key === 't') timestampRaw = value\n else if (key === 'v1') signature = value\n }\n\n // `t` is unix whole seconds; reject anything but digits so the accepted\n // header matches the documented contract.\n if (!timestampRaw || !signature || !/^\\d+$/.test(timestampRaw)) {\n throw new WebhookVerificationError(\n 'Malformed signature header; expected \"t=<unix>,v1=<hex>\".'\n )\n }\n // The signed payload uses the timestamp exactly as sent, so keep the raw\n // string for signing and the parsed number only for the tolerance check.\n return { timestampRaw, timestamp: Number(timestampRaw), signature }\n}\n\nasync function hmacSha256Hex(secret: string, payload: string): Promise<string> {\n const encoder = new TextEncoder()\n const key = await crypto.subtle.importKey(\n 'raw',\n encoder.encode(secret),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign']\n )\n const mac = await crypto.subtle.sign('HMAC', key, encoder.encode(payload))\n return toHex(new Uint8Array(mac))\n}\n\nfunction toHex(bytes: Uint8Array): string {\n let hex = ''\n for (const byte of bytes) hex += byte.toString(16).padStart(2, '0')\n return hex\n}\n\n// Constant-time comparison. The digest width is public, so a length mismatch\n// may short-circuit without leaking secret-dependent timing.\nfunction timingSafeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) return false\n let mismatch = 0\n for (let i = 0; i < a.length; i++) {\n mismatch |= a.charCodeAt(i) ^ b.charCodeAt(i)\n }\n return mismatch === 0\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/error.ts","../src/api-promise.ts","../src/internal/abort.ts","../src/internal/request.ts","../src/resources/artifacts.ts","../src/resources/credits.ts","../src/internal/poll.ts","../src/status.ts","../src/resources/jobs.ts","../src/client.ts","../src/webhook.ts"],"sourcesContent":["import type { Job, JobStatus } from './api-types'\n\nexport class SimmitError extends Error {}\n\n/**\n * Value shapes the API's generic error `meta` bag can carry\n * (400/401/404/410/413 responses): JSON scalars, scalar arrays, or arrays of\n * flat objects.\n */\nexport type MetaValue =\n | string\n | number\n | boolean\n | null\n | Array<string | number | boolean>\n | Array<Record<string, string | number | boolean | null>>\n\nexport type GenericMeta = Record<string, MetaValue>\n\n/** The API's uniform error envelope: `{ error, code, meta }`. */\ninterface ErrorEnvelope {\n error?: unknown\n code?: unknown\n meta?: unknown\n}\n\nexport class APIError<\n TStatus extends number | undefined = number | undefined,\n TCode extends string | undefined = string | undefined,\n TMeta = GenericMeta | null\n> extends SimmitError {\n /** HTTP status of the response that caused the error. */\n readonly status: TStatus\n /** HTTP headers of the response that caused the error. */\n readonly headers: Headers | undefined\n /** Machine-readable `code` from the error envelope. */\n readonly code: TCode\n /** Typed `meta` from the error envelope. */\n readonly meta: TMeta\n /** Raw parsed JSON error body: escape hatch for unmapped fields. */\n readonly error: object | undefined\n\n constructor(\n status: TStatus,\n body: object | undefined,\n message: string | undefined,\n headers: Headers | undefined\n ) {\n super(APIError.makeMessage(status, body, message))\n this.status = status\n this.headers = headers\n this.error = body\n const envelope = body as ErrorEnvelope | undefined\n this.code = (\n typeof envelope?.code === 'string' ? envelope.code : undefined\n ) as TCode\n this.meta = (body ? (envelope?.meta ?? null) : undefined) as TMeta\n }\n\n private static makeMessage(\n status: number | undefined,\n body: object | undefined,\n message: string | undefined\n ): string {\n // The API's human-readable message field is named `error`, not `message`.\n const bodyMessage = (body as ErrorEnvelope | undefined)?.error\n const msg =\n typeof bodyMessage === 'string'\n ? bodyMessage\n : body\n ? JSON.stringify(body)\n : message\n\n if (status && msg) return `${status} ${msg}`\n if (status) return `${status} status code (no body)`\n if (msg) return msg\n return '(no status code or body)'\n }\n\n /**\n * Maps a response to the most specific error class: status selects the base\n * class; an enumerated `code` with structured `meta` selects the subclass;\n * anything unrecognized falls back to the status class so new server codes\n * degrade gracefully without breaking `instanceof` handling.\n */\n static generate(\n status: number | undefined,\n body: object | undefined,\n message: string | undefined,\n headers: Headers | undefined\n ): APIError<\n number | undefined,\n string | undefined,\n GenericMeta | null | undefined\n > {\n if (!status || !headers) {\n return new APIConnectionError({\n message,\n cause: body instanceof Error ? body : undefined\n })\n }\n\n const code = (body as ErrorEnvelope | undefined)?.code\n\n if (status === 400) return new BadRequestError(400, body, message, headers)\n if (status === 401) {\n return new AuthenticationError(401, body, message, headers)\n }\n if (status === 402) {\n if (code === 'insufficient_credits') {\n return new InsufficientCreditsError(402, body, message, headers)\n }\n if (code === 'insufficient_credits_liability') {\n return new InsufficientCreditsLiabilityError(\n 402,\n body,\n message,\n headers\n )\n }\n return new BillingError(402, body, message, headers)\n }\n if (status === 404) return new NotFoundError(404, body, message, headers)\n if (status === 409) {\n if (code === 'idempotency_key_reuse') {\n return new IdempotencyKeyReuseError(409, body, message, headers)\n }\n if (code === 'result_not_ready') {\n return new ResultNotReadyError(409, body, message, headers)\n }\n if (code === 'job_not_cancellable') {\n return new JobNotCancellableError(409, body, message, headers)\n }\n return new ConflictError(409, body, message, headers)\n }\n if (status === 413) {\n return new RequestTooLargeError(413, body, message, headers)\n }\n if (status === 422) {\n if (code === 'input_sanitized_rejected') {\n return new InvalidProfileError(422, body, message, headers)\n }\n if (code === 'result_unavailable') {\n return new ResultUnavailableError(422, body, message, headers)\n }\n return new UnprocessableEntityError(422, body, message, headers)\n }\n if (status === 429) {\n if (code === 'max_active_jobs_exceeded') {\n return new MaxActiveJobsError(429, body, message, headers)\n }\n return new RateLimitError(429, body, message, headers)\n }\n if (status === 503 && isServiceUnavailableBody(body)) {\n return new ServiceUnavailableError(503, body, message, headers)\n }\n if (status >= 500) {\n return new InternalServerError(status, body, message, headers)\n }\n return new APIError(status, body, message, headers)\n }\n}\n\n// ── 4xx status classes (code subclasses where the spec enumerates) ──────────\n\nexport class BadRequestError extends APIError<400, string> {}\n\nexport type AuthenticationErrorCode =\n | 'missing_token'\n | 'invalid_token'\n | 'revoked_token'\n | 'expired_token'\n\nexport class AuthenticationError extends APIError<\n 401,\n AuthenticationErrorCode\n> {}\n\n// 402 codes are docs-enumerated; the spec leaves `code` un-enumerated, so the\n// base class keeps `string` for forward compatibility.\nexport class BillingError extends APIError<402, string> {}\n\nexport type InsufficientCreditsMeta = {\n reason: string\n ceilingRuntimeSeconds?: number\n /** Largest maxRuntimeSeconds the current balance can cover. */\n maxAffordableRuntimeSeconds?: number\n docsUrl?: string\n}\n\nexport class InsufficientCreditsError extends BillingError {\n declare readonly code: 'insufficient_credits'\n declare readonly meta: InsufficientCreditsMeta | null\n}\n\nexport type InsufficientCreditsLiabilityMeta = {\n reason: string\n /** The high-priority fee in effect. Top up, or resubmit at priority 'standard'. */\n priorityFeeCredits: number\n docsUrl?: string\n}\n\nexport class InsufficientCreditsLiabilityError extends BillingError {\n declare readonly code: 'insufficient_credits_liability'\n declare readonly meta: InsufficientCreditsLiabilityMeta | null\n}\n\nexport class NotFoundError extends APIError<404, string> {}\n\nexport class ConflictError extends APIError<409, string> {}\n\nexport class IdempotencyKeyReuseError extends ConflictError {\n declare readonly code: 'idempotency_key_reuse'\n declare readonly meta: {\n reason: 'idempotency_key_reuse'\n /** ID of the job that originally consumed this idempotency key. */\n originalJobId: string\n docsUrl?: string\n }\n}\n\nexport class ResultNotReadyError extends ConflictError {\n declare readonly code: 'result_not_ready'\n declare readonly meta: {\n status: 'pending' | 'queued' | 'starting' | 'running'\n }\n}\n\nexport class JobNotCancellableError extends ConflictError {\n declare readonly code: 'job_not_cancellable'\n declare readonly meta: { id: string; status: JobStatus }\n}\n\nexport class RequestTooLargeError extends APIError<413, string> {}\n\nexport class UnprocessableEntityError extends APIError<422, string> {}\n\nexport class InvalidProfileError extends UnprocessableEntityError {\n declare readonly code: 'input_sanitized_rejected'\n declare readonly meta: {\n reason: 'input_sanitized_rejected'\n message: string\n docsUrl: string\n /** Sample of rejected lines; see blockedCount/blockedTruncated for the full set. */\n blocked: Array<{ line: number; text: string }>\n blockedCount: number\n blockedTruncated: boolean\n }\n}\n\nexport class ResultUnavailableError extends UnprocessableEntityError {\n declare readonly code: 'result_unavailable'\n declare readonly meta: {\n status: 'completed' | 'failed' | 'cancelled' | 'timed_out'\n }\n}\n\nexport type RateLimitErrorCode =\n | 'rate_limit_exceeded'\n | 'max_active_jobs_exceeded'\n\nexport class RateLimitError extends APIError<429, RateLimitErrorCode> {\n declare readonly meta:\n | { scope: 'developer' }\n | {\n reason: 'max_active_jobs_exceeded'\n maxActiveJobs: number\n activeJobs: number\n }\n | null\n}\n\nexport class MaxActiveJobsError extends RateLimitError {\n declare readonly code: 'max_active_jobs_exceeded'\n declare readonly meta: {\n reason: 'max_active_jobs_exceeded'\n /** Maximum number of jobs the account can have in flight. */\n maxActiveJobs: number\n /** Jobs in flight when this request was rejected. */\n activeJobs: number\n }\n}\n\n// ── 5xx ─────────────────────────────────────────────────────────────────────\n\nexport class InternalServerError extends APIError<number, string> {}\n\n/**\n * 503 carries four enumerated codes with distinct meta: a discriminated\n * union, narrowed via `.body`. `api_maintenance` gets no special retry\n * behavior: standard policy applies, and the typed\n * `meta.retryAfterSeconds` is surfaced so callers can schedule their own\n * resubmission.\n */\nexport type ServiceUnavailableBody =\n | {\n code: 'queue_unavailable'\n meta: { reason: 'queue_unavailable'; queueHealth: string }\n }\n | {\n code: 'queue_health_unknown'\n meta: { reason: 'queue_health_unknown'; laneId: string }\n }\n | {\n code: 'secret_store_unavailable'\n meta: { reason: 'secret_store_unavailable' }\n }\n | { code: 'api_maintenance'; meta: { retryAfterSeconds: number } }\n\nconst SERVICE_UNAVAILABLE_CODES = new Set([\n 'queue_unavailable',\n 'queue_health_unknown',\n 'secret_store_unavailable',\n 'api_maintenance'\n])\n\n// A 503 whose body isn't the enumerated envelope (e.g. load-balancer HTML)\n// falls back to InternalServerError so `.body` below never lies.\nfunction isServiceUnavailableBody(\n body: object | undefined\n): body is ServiceUnavailableBody {\n const code = (body as ErrorEnvelope | undefined)?.code\n return typeof code === 'string' && SERVICE_UNAVAILABLE_CODES.has(code)\n}\n\nexport class ServiceUnavailableError extends InternalServerError {\n declare readonly status: 503\n\n /** The discriminated 503 envelope: `if (e.body.code === 'api_maintenance') e.body.meta.retryAfterSeconds`. */\n get body(): ServiceUnavailableBody {\n return this.error as ServiceUnavailableBody\n }\n}\n\n// ── No HTTP response ────────────────────────────────────────────────────────\n\nexport class APIConnectionError extends APIError<\n undefined,\n undefined,\n undefined\n> {\n constructor({\n message,\n cause\n }: { message?: string | undefined; cause?: Error | undefined } = {}) {\n super(undefined, undefined, message ?? 'Connection error.', undefined)\n if (cause) this.cause = cause\n }\n}\n\nexport class APIConnectionTimeoutError extends APIConnectionError {\n constructor({ message }: { message?: string } = {}) {\n super({ message: message ?? 'Request timed out.' })\n }\n}\n\nexport class APIUserAbortError extends APIError<\n undefined,\n undefined,\n undefined\n> {\n constructor({ message }: { message?: string } = {}) {\n super(undefined, undefined, message ?? 'Request was aborted.', undefined)\n }\n}\n\n// ── Job-level errors (thrown only by createAndWait) ──────────────────────────\n\n/** Catch-all for a job that reached a terminal state other than `completed`. */\nexport abstract class JobUnsuccessfulError extends SimmitError {\n readonly job: Job\n\n constructor(job: Job, message?: string) {\n super(\n message ??\n `Job ${job.id} ${job.status}` +\n (job.statusReason ? `: ${job.statusReason}` : '') +\n (job.errorCode ? ` (${job.errorCode})` : '')\n )\n this.job = job\n }\n}\n\nexport class JobFailedError extends JobUnsuccessfulError {}\n\n/** Includes queue_timeout auto-cancellation, not just user cancels. */\nexport class JobCancelledError extends JobUnsuccessfulError {}\n\n/** The job hit its runtime ceiling server-side and is billed for what ran. */\nexport class JobTimedOutError extends JobUnsuccessfulError {}\n\n/**\n * The SDK gave up polling. The job itself is still running and billing.\n * Keep tracking via `jobs.get(jobId)` or stop the spend with `jobs.cancel(jobId)`.\n */\nexport class JobWaitTimeoutError extends SimmitError {\n readonly jobId: string\n readonly lastStatus: JobStatus\n\n constructor(args: {\n jobId: string\n lastStatus: JobStatus\n message?: string\n }) {\n super(\n args.message ??\n `Timed out waiting for job ${args.jobId} (last status: ${args.lastStatus}). ` +\n 'The job is still running server-side and continues to bill.'\n )\n this.jobId = args.jobId\n this.lastStatus = args.lastStatus\n }\n}\n\n// ── Webhook verification (thrown by unwrapWebhook) ───────────────────────────\n\nexport class WebhookVerificationError extends SimmitError {}\n","/**\n * A `Promise<T>` with raw-response access: the generic answer to response\n * headers the return types can't see (`X-Idempotent-Replay`, `X-Active-Jobs`,\n * `X-RateLimit-*`).\n *\n * const { data, response } = await client.jobs.create(params).withResponse()\n * response.headers.get('x-idempotent-replay')\n */\nexport class APIPromise<T> extends Promise<T> {\n // Chained promises (.then/.catch) must be plain Promises: this class's\n // constructor signature is incompatible with the executor the runtime\n // would otherwise pass via the species constructor.\n static override get [Symbol.species]() {\n return Promise\n }\n\n readonly #parsed: Promise<{ data: T; response: Response }>\n\n constructor(parsed: Promise<{ data: T; response: Response }>) {\n // The base promise is a pre-settled placeholder that is never observed:\n // then() below delegates to #parsed lazily (catch/finally route through\n // then() per spec). Subscribing eagerly here would reject this instance\n // even when the caller only consumes withResponse(), leaking an\n // unhandled rejection on failures.\n super((resolve) => resolve(undefined as never))\n this.#parsed = parsed\n }\n\n override then<TResult1 = T, TResult2 = never>(\n onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null,\n onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null\n ): Promise<TResult1 | TResult2> {\n return this.#parsed\n .then((result) => result.data)\n .then(onfulfilled, onrejected)\n }\n\n withResponse(): Promise<{ data: T; response: Response }> {\n return this.#parsed\n }\n\n asResponse(): Promise<Response> {\n return this.#parsed.then((result) => result.response)\n }\n}\n","// Abort-aware timing utilities shared by the request layer (backoff sleeps) and\n// the createAndWait poll loop. A pending sleep rejects with APIUserAbortError\n// the moment the caller's signal fires.\nimport { APIUserAbortError } from '../error'\n\nexport function throwIfUserAborted(signal: AbortSignal | undefined): void {\n if (signal?.aborted) throw new APIUserAbortError()\n}\n\nexport function sleep(\n ms: number,\n signal: AbortSignal | undefined\n): Promise<void> {\n return new Promise((resolve, reject) => {\n throwIfUserAborted(signal)\n const timeoutId = setTimeout(() => {\n signal?.removeEventListener('abort', onAbort)\n resolve()\n }, ms)\n const onAbort = () => {\n clearTimeout(timeoutId)\n reject(new APIUserAbortError())\n }\n signal?.addEventListener('abort', onAbort, { once: true })\n })\n}\n","// Internal request layer: header assembly, per-attempt timeout/abort\n// composition, retry with backoff + Retry-After, idempotency-key injection,\n// and error mapping. Not exported from the package.\nimport { APIPromise } from '../api-promise'\nimport {\n APIConnectionError,\n APIConnectionTimeoutError,\n APIError,\n APIUserAbortError\n} from '../error'\nimport type { RequestOptions } from '../client'\nimport { sleep, throwIfUserAborted } from './abort'\n\nexport interface ClientConfig {\n secretKey: string\n baseURL: string\n timeout: number\n maxRetries: number\n defaultHeaders: Record<string, string | null | undefined> | undefined\n fetch: typeof globalThis.fetch\n fetchOptions: RequestInit | undefined\n}\n\nexport interface RequestSpec {\n method: 'GET' | 'POST'\n path: string\n body?: unknown\n /** POST job creation: auto-generate an idempotency-key when none supplied. */\n idempotent?: boolean\n}\n\n// Retry policy constants: typed code config, not env.\nconst INITIAL_BACKOFF_MS = 500\nconst MAX_BACKOFF_MS = 8_000\nconst MAX_RETRY_AFTER_MS = 60_000\n\nexport function makeRequest<T>(\n config: ClientConfig,\n spec: RequestSpec,\n options: RequestOptions = {}\n): APIPromise<T> {\n return new APIPromise(run<T>(config, spec, options))\n}\n\nasync function run<T>(\n config: ClientConfig,\n spec: RequestSpec,\n options: RequestOptions\n): Promise<{ data: T; response: Response }> {\n const maxRetries = options.maxRetries ?? config.maxRetries\n const timeout = options.timeout ?? config.timeout\n const headers = buildHeaders(config, spec, options)\n const url = `${config.baseURL.replace(/\\/+$/, '')}${spec.path}`\n const body = spec.body === undefined ? undefined : JSON.stringify(spec.body)\n\n for (let attempt = 0; ; attempt++) {\n throwIfUserAborted(options.signal)\n\n let result: AttemptResult\n try {\n result = await fetchAttempt(config, spec, options, {\n url,\n headers,\n body,\n timeout\n })\n } catch (err) {\n if (err instanceof APIUserAbortError) throw err\n // Connection error, malformed success body, or per-attempt timeout.\n // All retryable.\n if (attempt < maxRetries) {\n await backoff(attempt, undefined, options.signal)\n continue\n }\n throw err\n }\n\n const { response, json } = result\n\n if (response.ok) {\n return { data: json as T, response }\n }\n\n if (shouldRetryStatus(response.status) && attempt < maxRetries) {\n await backoff(\n attempt,\n response.headers.get('retry-after'),\n options.signal\n )\n continue\n }\n\n throw APIError.generate(\n response.status,\n typeof json === 'object' && json !== null ? json : undefined,\n response.statusText,\n response.headers\n )\n }\n}\n\ninterface AttemptResult {\n response: Response\n /** Parsed JSON body; undefined when an error response carried a non-JSON body. */\n json: unknown\n}\n\nasync function fetchAttempt(\n config: ClientConfig,\n spec: RequestSpec,\n options: RequestOptions,\n attempt: {\n url: string\n headers: Record<string, string>\n body: string | undefined\n timeout: number\n }\n): Promise<AttemptResult> {\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), attempt.timeout)\n const onUserAbort = () => controller.abort()\n options.signal?.addEventListener('abort', onUserAbort, { once: true })\n\n try {\n const response = await config.fetch(attempt.url, {\n ...config.fetchOptions,\n method: spec.method,\n headers: attempt.headers,\n ...(attempt.body !== undefined ? { body: attempt.body } : {}),\n signal: controller.signal\n })\n\n // The body is read inside the timed scope too: a stalled body must not\n // hang past the per-attempt timeout (fetch ties the body stream to the\n // controller's signal, so the abort cancels the read).\n let json: unknown\n if (response.ok) {\n // Success bodies must parse; a truncated/malformed one is treated as a\n // transport failure (classified below) and retried like one.\n json = await response.json()\n } else {\n try {\n json = await response.json()\n } catch (err) {\n // Aborted mid-read is a timeout/abort, not a non-JSON body.\n if (controller.signal.aborted) throw err\n json = undefined // e.g. a load-balancer HTML error page\n }\n }\n return { response, json }\n } catch (err) {\n if (options.signal?.aborted) throw new APIUserAbortError()\n if (controller.signal.aborted) {\n throw new APIConnectionTimeoutError()\n }\n throw new APIConnectionError({\n cause: err instanceof Error ? err : undefined\n })\n } finally {\n clearTimeout(timeoutId)\n options.signal?.removeEventListener('abort', onUserAbort)\n }\n}\n\nfunction buildHeaders(\n config: ClientConfig,\n spec: RequestSpec,\n options: RequestOptions\n): Record<string, string> {\n const idempotent = spec.idempotent && spec.method === 'POST'\n const merged: Record<string, string | null | undefined> = {\n authorization: `Bearer ${config.secretKey}`,\n ...(spec.body !== undefined ? { 'content-type': 'application/json' } : {}),\n ...(idempotent && !options.idempotencyKey\n ? {\n // Generated once per call and reused across retry attempts. That\n // is what makes POST retries safe by default. The auto\n // key is an SDK built-in default (lowest tier), so defaultHeaders\n // may override it.\n 'idempotency-key': `simmit-node-retry-${crypto.randomUUID()}`\n }\n : {}),\n ...lowercaseKeys(config.defaultHeaders),\n ...(idempotent && options.idempotencyKey\n ? {\n // An explicit key is a per-request option: it must beat constructor\n // defaultHeaders. Raw options.headers still wins last.\n 'idempotency-key': options.idempotencyKey\n }\n : {}),\n ...lowercaseKeys(options.headers)\n }\n\n const headers: Record<string, string> = {}\n for (const [key, value] of Object.entries(merged)) {\n // A null value deletes the header; undefined entries are skipped.\n if (typeof value === 'string') headers[key] = value\n }\n return headers\n}\n\nfunction lowercaseKeys(\n record: Record<string, string | null | undefined> | undefined\n): Record<string, string | null | undefined> {\n if (!record) return {}\n return Object.fromEntries(\n Object.entries(record).map(([key, value]) => [key.toLowerCase(), value])\n )\n}\n\nfunction shouldRetryStatus(status: number): boolean {\n // 408 kept defensively even though the API never emits it. 409 is never\n // retried: result_not_ready is thrown immediately by design and the other\n // 409s are deterministic.\n return status === 408 || status === 429 || status >= 500\n}\n\nasync function backoff(\n attempt: number,\n retryAfterHeader: string | null | undefined,\n signal: AbortSignal | undefined\n): Promise<void> {\n const retryAfterMs = parseRetryAfter(retryAfterHeader)\n const delay =\n retryAfterMs !== undefined\n ? retryAfterMs\n : Math.min(INITIAL_BACKOFF_MS * 2 ** attempt, MAX_BACKOFF_MS) *\n (1 - 0.25 * Math.random())\n await sleep(delay, signal)\n}\n\n/** Accepts `Retry-After` only when it parses to a delay in (0, 60s]: the SDK never sleeps arbitrarily long on a server hint. */\nfunction parseRetryAfter(\n header: string | null | undefined\n): number | undefined {\n if (!header) return undefined\n let ms: number\n if (/^\\d+$/.test(header.trim())) {\n ms = Number(header.trim()) * 1000\n } else {\n ms = new Date(header).getTime() - Date.now()\n }\n return Number.isFinite(ms) && ms > 0 && ms <= MAX_RETRY_AFTER_MS\n ? ms\n : undefined\n}\n","import type { APIPromise } from '../api-promise'\nimport type { ArtifactUrl } from '../api-types'\nimport type Simmit from '../client'\nimport type { RequestOptions } from '../client'\n\n/** The `artifacts` resource. */\nexport class Artifacts {\n readonly #client: Simmit\n\n constructor(client: Simmit) {\n this.#client = client\n }\n\n /**\n * Fetch a stable public download URL for an artifact, valid for the\n * artifact's full retention window: the same URL `jobs.getResult` returns,\n * fetched on demand (e.g. browser flows that control the final fetch). The\n * artifact is gone (410) once its retention window passes.\n */\n getUrl(\n artifactId: string,\n options?: RequestOptions\n ): APIPromise<ArtifactUrl> {\n return this.#client._request<ArtifactUrl>(\n {\n method: 'GET',\n path: `/v1/simc/artifacts/${encodeURIComponent(artifactId)}/url`\n },\n options\n )\n }\n}\n","import type { APIPromise } from '../api-promise'\nimport type { CreditBalance } from '../api-types'\nimport type Simmit from '../client'\nimport type { RequestOptions } from '../client'\n\n/** The `credits` resource. */\nexport class Credits {\n readonly #client: Simmit\n\n constructor(client: Simmit) {\n this.#client = client\n }\n\n /** Fetch the account's current credit balance and per-grant breakdown. */\n get(options?: RequestOptions): APIPromise<CreditBalance> {\n return this.#client._request<CreditBalance>(\n { method: 'GET', path: '/v1/simc/credits' },\n options\n )\n }\n}\n","// Pure helpers for the createAndWait poll loop. Kept separate from the\n// orchestration so the cadence and deadline math are unit-testable.\nimport type { JobCreateResponse } from '../api-types'\n\nexport const MIN_POLL_INTERVAL_MS = 100\nexport const DEFAULT_POLL_INTERVAL_MS = 1_000\nexport const MAX_POLL_INTERVAL_MS = 10_000\nexport const POLL_BACKOFF_FACTOR = 1.5\nconst DEADLINE_GRACE_MS = 60_000\nconst FALLBACK_WAIT_TIMEOUT_MS = 45 * 60 * 1_000\n\n/**\n * Default wait deadline derived from the applied ceilings the create response\n * reports: `(queueSeconds + runtimeSeconds) × 1000` plus a 60s grace, falling\n * back to 45 minutes when either ceiling is null.\n */\nexport function deriveWaitTimeoutMs(created: JobCreateResponse): number {\n const { runtimeSeconds, queueSeconds } = created.runtime.ceiling\n if (runtimeSeconds != null && queueSeconds != null) {\n return (runtimeSeconds + queueSeconds) * 1_000 + DEADLINE_GRACE_MS\n }\n return FALLBACK_WAIT_TIMEOUT_MS\n}\n\n/** Next poll interval: grow ×1.5, capped at 10s. */\nexport function nextPollInterval(interval: number): number {\n return Math.min(interval * POLL_BACKOFF_FACTOR, MAX_POLL_INTERVAL_MS)\n}\n","import type { JobStatus } from './api-types'\n\n/**\n * Job statuses that are terminal: a job in one of these has stopped and will\n * not change again. Terminal does not mean successful; only `completed` carries\n * a result (`failed`, `cancelled`, and `timed_out` do not).\n */\nexport const TERMINAL_JOB_STATUSES = [\n 'completed',\n 'failed',\n 'cancelled',\n 'timed_out'\n] as const satisfies readonly JobStatus[]\n\n/** A `JobStatus` that is terminal: the job has stopped and will not change. */\nexport type TerminalJobStatus = (typeof TERMINAL_JOB_STATUSES)[number]\n\n/** True when `status` is terminal, i.e. the job has reached an end state. */\nexport function isTerminal(status: JobStatus): status is TerminalJobStatus {\n return (TERMINAL_JOB_STATUSES as readonly JobStatus[]).includes(status)\n}\n","import type { APIPromise } from '../api-promise'\nimport type {\n CompletedJob,\n Job,\n JobCancelResponse,\n JobCreateParams,\n JobCreateResponse,\n JobResult,\n JobStatus,\n JobStatusResponse\n} from '../api-types'\nimport type Simmit from '../client'\nimport type { RequestOptions } from '../client'\nimport {\n JobCancelledError,\n JobFailedError,\n JobTimedOutError,\n JobWaitTimeoutError\n} from '../error'\nimport { sleep } from '../internal/abort'\nimport {\n DEFAULT_POLL_INTERVAL_MS,\n deriveWaitTimeoutMs,\n MIN_POLL_INTERVAL_MS,\n nextPollInterval\n} from '../internal/poll'\nimport { isTerminal } from '../status'\n\nexport interface JobWaitOptions extends RequestOptions {\n /** Initial delay between status polls, ms. Grows ×1.5 per poll to a 10s cap; values under 100 are raised to it. Default 1_000. */\n pollIntervalMs?: number\n /** Overall wait deadline, ms. Default derived from the job's applied ceilings. */\n waitTimeoutMs?: number\n /** Fired once with the raw create response (job id, ceilings, input warnings) before polling. */\n onCreated?: (response: JobCreateResponse) => void\n /** Fired after every successful status poll (progress, stage, queue estimate). */\n onPoll?: (status: JobStatusResponse) => void\n}\n\n/**\n * The `jobs` resource. Each single-request method is a thin wrapper over\n * `client._request` with the path/method/types pinned to the spec;\n * `createAndWait` orchestrates several of them.\n */\nexport class Jobs {\n readonly #client: Simmit\n\n constructor(client: Simmit) {\n this.#client = client\n }\n\n /**\n * Submit a new SimC sim. Returns immediately with the job handle; the sim\n * runs asynchronously. `idempotent: true` makes the request layer attach an\n * auto-generated idempotency key so the POST is safe to retry; pass\n * `options.idempotencyKey` to supply your own.\n */\n create(\n params: JobCreateParams,\n options?: RequestOptions\n ): APIPromise<JobCreateResponse> {\n return this.#client._request<JobCreateResponse>(\n { method: 'POST', path: '/v1/simc/jobs', body: params, idempotent: true },\n options\n )\n }\n\n /** Fetch the full record for a job. */\n get(jobId: string, options?: RequestOptions): APIPromise<Job> {\n return this.#client._request<Job>(\n { method: 'GET', path: `/v1/simc/jobs/${encodeURIComponent(jobId)}` },\n options\n )\n }\n\n /**\n * Fetch the live status of a job in any state: `status`, `errorCode`,\n * `progress`, and `queue` estimate. Unlike `getResult`, it never throws for a\n * non-terminal job, so it is the supported way to drive a custom poll loop.\n */\n getStatus(\n jobId: string,\n options?: RequestOptions\n ): APIPromise<JobStatusResponse> {\n return this.#client._request<JobStatusResponse>(\n {\n method: 'GET',\n path: `/v1/simc/jobs/${encodeURIComponent(jobId)}/status`\n },\n options\n )\n }\n\n /**\n * Fetch the result summary of a terminal job. Throws `ResultNotReadyError`\n * (409) while the job is still running. Poll `/status` or use\n * `createAndWait` rather than `/result` for a job in flight.\n */\n getResult(jobId: string, options?: RequestOptions): APIPromise<JobResult> {\n return this.#client._request<JobResult>(\n {\n method: 'GET',\n path: `/v1/simc/jobs/${encodeURIComponent(jobId)}/result`\n },\n options\n )\n }\n\n /**\n * Submit a job and resolve once it reaches a terminal state. Polls\n * `GET /v1/simc/jobs/{id}/status` (first after `pollIntervalMs`, then ×1.5 to\n * a 10s cap), then fetches the full record. Resolves with the `CompletedJob`\n * on success; throws `JobFailedError` / `JobCancelledError` /\n * `JobTimedOutError` for the other terminal states, or `JobWaitTimeoutError`\n * if the deadline passes first. The job keeps running and is **not**\n * cancelled (call `cancel(jobId)` to stop the spend). `signal` aborts the wait\n * with `APIUserAbortError`, also without cancelling.\n */\n async createAndWait(\n params: JobCreateParams,\n options: JobWaitOptions = {}\n ): Promise<CompletedJob> {\n const {\n pollIntervalMs,\n waitTimeoutMs,\n onCreated,\n onPoll,\n ...requestOptions\n } = options\n\n const created = await this.create(params, requestOptions)\n onCreated?.(created)\n\n const deadline =\n Date.now() + (waitTimeoutMs ?? deriveWaitTimeoutMs(created))\n // nextPollInterval only ever grows the interval, so a non-positive seed\n // would hot-poll the status endpoint; floor it.\n let interval = Math.max(\n pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS,\n MIN_POLL_INTERVAL_MS\n )\n let lastStatus: JobStatus = 'pending'\n\n for (;;) {\n const remaining = deadline - Date.now()\n if (remaining <= 0) {\n throw new JobWaitTimeoutError({ jobId: created.id, lastStatus })\n }\n // Never sleep past the deadline, so the wait gives up promptly.\n await sleep(Math.min(interval, remaining), requestOptions.signal)\n\n const status = await this.getStatus(created.id, requestOptions)\n onPoll?.(status)\n lastStatus = status.status\n\n if (isTerminal(status.status)) {\n // The status payload is lightweight; the full record carries the fields\n // CompletedJob and the job-error classes expose.\n const job = await this.get(created.id, requestOptions)\n switch (job.status) {\n case 'completed':\n return job as CompletedJob\n case 'failed':\n throw new JobFailedError(job)\n case 'cancelled':\n throw new JobCancelledError(job)\n case 'timed_out':\n throw new JobTimedOutError(job)\n }\n // Raced back to non-terminal between /status and the full record; keep polling.\n lastStatus = job.status\n }\n interval = nextPollInterval(interval)\n }\n }\n\n /**\n * Request cancellation. Returns `status: 'cancelled'` when the job ended\n * before it ran, or `status: 'cancel_requested'` when an in-flight job was\n * signaled to stop. Repeat calls are naturally idempotent, so no key is sent.\n */\n cancel(\n jobId: string,\n options?: RequestOptions\n ): APIPromise<JobCancelResponse> {\n return this.#client._request<JobCancelResponse>(\n {\n method: 'POST',\n path: `/v1/simc/jobs/${encodeURIComponent(jobId)}/cancel`\n },\n options\n )\n }\n}\n","import { APIPromise } from './api-promise'\nimport { SimmitError } from './error'\nimport {\n makeRequest,\n type ClientConfig,\n type RequestSpec\n} from './internal/request'\nimport { Artifacts } from './resources/artifacts'\nimport { Credits } from './resources/credits'\nimport { Jobs } from './resources/jobs'\n\nexport interface ClientOptions {\n /** Defaults to process.env['SIMMIT_SECRET_KEY'], exactly one env fallback. Construction\n * throws SimmitError('Missing secret key. Pass secretKey or set SIMMIT_SECRET_KEY.').\n * \"Secret key\" is the credential noun end to end (dashboard → docs → env var → option →\n * error): it spends credits and must never ship client-side. */\n secretKey?: string | null\n /** Defaults to process.env['SIMMIT_BASE_URL'] ?? 'https://api.simmit.com'. */\n baseURL?: string | null\n /** Per-attempt timeout in ms. Default 60_000. (Retries can extend total wall time.) */\n timeout?: number\n /** Max retries after the first attempt for retryable failures. Default 2. */\n maxRetries?: number\n /** Headers sent with every request. Merged under per-request headers. */\n defaultHeaders?: Record<string, string | null | undefined>\n /** Custom fetch (testing, proxies). Defaults to globalThis.fetch. */\n fetch?: typeof globalThis.fetch\n /** Extra RequestInit fields passed to every fetch call (e.g. undici dispatcher). */\n fetchOptions?: RequestInit\n}\n\nexport interface RequestOptions {\n /** Per-attempt timeout in ms. Overrides ClientOptions.timeout. */\n timeout?: number\n /** Abort the call (including retries and waiting). Throws APIUserAbortError. Never retried. */\n signal?: AbortSignal\n /** Overrides ClientOptions.maxRetries for this call. */\n maxRetries?: number\n /** Merged over defaultHeaders; a null value deletes the header. */\n headers?: Record<string, string | null | undefined>\n /** jobs.create / jobs.createAndWait only: replaces the auto-generated idempotency-key. */\n idempotencyKey?: string\n}\n\nexport default class Simmit {\n readonly jobs: Jobs\n readonly credits: Credits\n readonly artifacts: Artifacts\n\n readonly baseURL: string\n\n readonly #config: ClientConfig\n\n constructor(options: ClientOptions = {}) {\n const secretKey = options.secretKey ?? readEnv('SIMMIT_SECRET_KEY')\n if (!secretKey) {\n throw new SimmitError(\n 'Missing secret key. Pass secretKey or set SIMMIT_SECRET_KEY.'\n )\n }\n\n this.baseURL =\n options.baseURL ?? readEnv('SIMMIT_BASE_URL') ?? 'https://api.simmit.com'\n\n this.#config = {\n secretKey,\n baseURL: this.baseURL,\n timeout: options.timeout ?? 60_000,\n maxRetries: options.maxRetries ?? 2,\n defaultHeaders: options.defaultHeaders,\n // Resolved lazily so a fetch patched onto globalThis after the client\n // is constructed (msw, APM instrumentation) is still honored.\n fetch: options.fetch ?? ((...args) => globalThis.fetch(...args)),\n fetchOptions: options.fetchOptions\n }\n\n this.jobs = new Jobs(this)\n this.credits = new Credits(this)\n this.artifacts = new Artifacts(this)\n }\n\n /** @internal Resource classes route through here; not public surface. */\n _request<T>(spec: RequestSpec, options?: RequestOptions): APIPromise<T> {\n return makeRequest(this.#config, spec, options)\n }\n}\n\nfunction readEnv(name: string): string | undefined {\n if (typeof process === 'undefined') return undefined\n const value = process.env?.[name]?.trim()\n return value || undefined\n}\n","// Standalone webhook verification. Not a client method: receivers must not\n// need a secret-key-bearing client (whose constructor throws without a key).\n// WebCrypto only, zero deps, and runs in Workers as well as Node.\nimport type { JobStatus } from './api-types'\nimport { WebhookVerificationError } from './error'\n\nconst DEFAULT_TOLERANCE_SECONDS = 300\n\n/** The one hand-written wire type: the webhook payload has no OpenAPI schema. */\nexport interface WebhookEvent {\n kind: 'job.terminal'\n version: 'v1'\n timestamp: string\n payload: {\n id: string\n statusReason: string | null\n status: Extract<\n JobStatus,\n 'completed' | 'failed' | 'cancelled' | 'timed_out'\n >\n }\n}\n\n/**\n * Verifies an `X-Simmit-Signature` header (`t=<unix>,v1=<hex>`, an HMAC-SHA256\n * (timing-safe) over `${t}.${rawBody}` within a 300s default tolerance) and\n * returns the parsed event. Throws `WebhookVerificationError` on a bad\n * signature, malformed header, or stale timestamp.\n *\n * Pass `rawBody` exactly as received: re-serializing changes the bytes and\n * breaks verification. `secret` is the webhook signing secret (dashboard →\n * Clients & Keys → Webhook), not your API key.\n */\nexport async function unwrapWebhook(\n rawBody: string,\n signatureHeader: string,\n secret: string,\n options?: { toleranceSeconds?: number }\n): Promise<WebhookEvent> {\n // An empty secret would otherwise surface as an opaque WebCrypto DataError;\n // a NaN tolerance would make the age check pass for everything.\n if (!secret) {\n throw new WebhookVerificationError('Webhook signing secret is empty.')\n }\n const tolerance = options?.toleranceSeconds ?? DEFAULT_TOLERANCE_SECONDS\n if (!Number.isFinite(tolerance) || tolerance < 0) {\n throw new WebhookVerificationError(\n 'toleranceSeconds must be a non-negative number.'\n )\n }\n\n const { timestampRaw, timestamp, signature } =\n parseSignatureHeader(signatureHeader)\n\n const expected = await hmacSha256Hex(secret, `${timestampRaw}.${rawBody}`)\n if (!timingSafeEqual(expected, signature)) {\n throw new WebhookVerificationError('Webhook signature does not match.')\n }\n\n // Compare on whole seconds, matching the header's unix-seconds `t`.\n if (Math.abs(Math.floor(Date.now() / 1000) - timestamp) > tolerance) {\n throw new WebhookVerificationError(\n 'Webhook timestamp is outside the tolerance window.'\n )\n }\n\n try {\n return JSON.parse(rawBody) as WebhookEvent\n } catch {\n throw new WebhookVerificationError('Webhook body is not valid JSON.')\n }\n}\n\nfunction parseSignatureHeader(header: string): {\n timestampRaw: string\n timestamp: number\n signature: string\n} {\n let timestampRaw: string | undefined\n let signature: string | undefined\n for (const part of header.split(',')) {\n const eq = part.indexOf('=')\n if (eq === -1) continue\n const key = part.slice(0, eq).trim()\n const value = part.slice(eq + 1).trim()\n if (key === 't') timestampRaw = value\n else if (key === 'v1') signature = value\n }\n\n // `t` is unix whole seconds; reject anything but digits so the accepted\n // header matches the documented contract.\n if (!timestampRaw || !signature || !/^\\d+$/.test(timestampRaw)) {\n throw new WebhookVerificationError(\n 'Malformed signature header; expected \"t=<unix>,v1=<hex>\".'\n )\n }\n // The signed payload uses the timestamp exactly as sent, so keep the raw\n // string for signing and the parsed number only for the tolerance check.\n return { timestampRaw, timestamp: Number(timestampRaw), signature }\n}\n\nasync function hmacSha256Hex(secret: string, payload: string): Promise<string> {\n const encoder = new TextEncoder()\n const key = await crypto.subtle.importKey(\n 'raw',\n encoder.encode(secret),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign']\n )\n const mac = await crypto.subtle.sign('HMAC', key, encoder.encode(payload))\n return toHex(new Uint8Array(mac))\n}\n\nfunction toHex(bytes: Uint8Array): string {\n let hex = ''\n for (const byte of bytes) hex += byte.toString(16).padStart(2, '0')\n return hex\n}\n\n// Constant-time comparison. The digest width is public, so a length mismatch\n// may short-circuit without leaking secret-dependent timing.\nfunction timingSafeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) return false\n let mismatch = 0\n for (let i = 0; i < a.length; i++) {\n mismatch |= a.charCodeAt(i) ^ b.charCodeAt(i)\n }\n return mismatch === 0\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@simmit/sdk",
3
- "version": "0.1.0",
4
- "description": "TypeScript SDK for the Simmit API cloud execution for SimulationCraft",
3
+ "version": "0.2.0",
4
+ "description": "TypeScript SDK for Simmit, an API for running SimulationCraft (SimC) in the cloud",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "main": "./dist/index.cjs",
@@ -52,8 +52,12 @@
52
52
  "simulationcraft",
53
53
  "simc",
54
54
  "wow",
55
+ "world-of-warcraft",
56
+ "dps",
57
+ "simulation",
55
58
  "sdk",
56
- "api-client"
59
+ "api-client",
60
+ "cloud"
57
61
  ],
58
62
  "author": "Voidly Labs",
59
63
  "repository": {