@giveitsmaller/sdk 0.19.0 → 0.21.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.
Files changed (45) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +8 -15
  3. package/dist/_audit.js +14 -4
  4. package/dist/builder.d.ts +5 -4
  5. package/dist/builder.js +16 -16
  6. package/dist/client.d.ts +17 -7
  7. package/dist/client.js +116 -62
  8. package/dist/ergonomic/image_output_routes.d.ts +30 -0
  9. package/dist/ergonomic/image_output_routes.js +82 -11
  10. package/dist/ergonomic/option_types.d.ts +34 -1
  11. package/dist/ergonomic/option_types.js +5 -1
  12. package/dist/ergonomic/option_validation.d.ts +19 -1
  13. package/dist/ergonomic/option_validation.js +34 -1
  14. package/dist/ergonomic/preset_resolver.d.ts +2 -2
  15. package/dist/ergonomic/preset_resolver.js +6 -10
  16. package/dist/ergonomic/presets/index.d.ts +9 -8
  17. package/dist/ergonomic/presets/index.js +1 -9
  18. package/dist/errors.d.ts +105 -3
  19. package/dist/errors.js +136 -2
  20. package/dist/file-first.d.ts +234 -16
  21. package/dist/file-first.js +692 -175
  22. package/dist/generated/sdk_spec/enums.d.ts +0 -26
  23. package/dist/generated/sdk_spec/enums.js +0 -16
  24. package/dist/generated/sdk_spec/errors.d.ts +1 -1
  25. package/dist/generated/sdk_spec/errors.js +12 -0
  26. package/dist/generated/sdk_spec/presets.js +0 -14
  27. package/dist/generated/sdk_spec/version.d.ts +2 -2
  28. package/dist/generated/sdk_spec/version.js +2 -2
  29. package/dist/gisl.d.ts +93 -5
  30. package/dist/gisl.js +72 -2
  31. package/dist/handle.d.ts +6 -1
  32. package/dist/handle.js +42 -13
  33. package/dist/index.core.d.ts +10 -8
  34. package/dist/index.core.js +11 -3
  35. package/dist/merge.d.ts +12 -0
  36. package/dist/merge.js +14 -2
  37. package/dist/retry-metadata.d.ts +37 -0
  38. package/dist/retry-metadata.js +86 -0
  39. package/dist/sse.d.ts +2 -1
  40. package/dist/sse.js +26 -6
  41. package/dist/types.d.ts +54 -4
  42. package/dist/types.js +1 -0
  43. package/package.json +3 -3
  44. package/dist/ergonomic/presets/document_pdf_compress.d.ts +0 -12
  45. package/dist/ergonomic/presets/document_pdf_compress.js +0 -33
@@ -1,6 +1,6 @@
1
1
  import type { ResolvedOptions } from '../builder.js';
2
2
  import type { OptimizeFor } from '../generated/sdk_spec/enums.js';
3
- import { type PresetDefaults, type PresetMedia, type PresetOp } from './presets/index.js';
3
+ import { type PresetDefaults, type PresetMedia, type DetectedMedia, type PresetOp } from './presets/index.js';
4
4
  /**
5
5
  * The preset matrix version emitted on every resolve. Re-exported from the
6
6
  * GENERATED `sdk_spec/version.ts` (source of truth: contracts
@@ -17,7 +17,7 @@ export declare const PRESET_VERSION: "1.6";
17
17
  * extend the union.
18
18
  */
19
19
  export interface ResolveCompressOptionsInput {
20
- readonly media: PresetMedia;
20
+ readonly media: DetectedMedia;
21
21
  readonly op: PresetOp;
22
22
  /** Defaults registered via `gisl.create({ presetDefaults: ... })`. */
23
23
  readonly presetDefaults?: PresetDefaults;
@@ -37,7 +37,7 @@
37
37
  import { sha256Hex } from '../sha256.js';
38
38
  import { GislConfigError } from '../errors.js';
39
39
  import { PRESET_VERSION as GENERATED_PRESET_VERSION } from '../generated/sdk_spec/version.js';
40
- import { ImageCompressPresetOptions, AudioCompressPresetOptions, VideoCompressPresetOptions, DocumentPdfCompressPresetOptions, DocumentOfficeCompressPresetOptions, DocumentOdfCompressPresetOptions, DocumentEpubCompressPresetOptions, definedFieldsOf, } from './presets/index.js';
40
+ import { ImageCompressPresetOptions, AudioCompressPresetOptions, VideoCompressPresetOptions, DocumentOfficeCompressPresetOptions, DocumentOdfCompressPresetOptions, DocumentEpubCompressPresetOptions, definedFieldsOf, } from './presets/index.js';
41
41
  /**
42
42
  * The preset matrix version emitted on every resolve. Re-exported from the
43
43
  * GENERATED `sdk_spec/version.ts` (source of truth: contracts
@@ -175,8 +175,6 @@ function sdkDefaultRecord(media, op, optimize) {
175
175
  return { ...AudioCompressPresetOptions.shippedDefaultsFor(optimize) };
176
176
  case 'video':
177
177
  return { ...VideoCompressPresetOptions.shippedDefaultsFor(optimize) };
178
- case 'document_pdf':
179
- return { ...DocumentPdfCompressPresetOptions.shippedDefaultsFor(optimize) };
180
178
  case 'document_office':
181
179
  return { ...DocumentOfficeCompressPresetOptions.shippedDefaultsFor(optimize) };
182
180
  case 'document_odf':
@@ -213,9 +211,6 @@ function presetDefaultsCellRecord(defaults, media, op, optimize) {
213
211
  case 'video':
214
212
  cell = defaults.cellFor('video', 'compress', optimize);
215
213
  break;
216
- case 'document_pdf':
217
- cell = defaults.cellFor('document_pdf', 'compress', optimize);
218
- break;
219
214
  case 'document_office':
220
215
  cell = defaults.cellFor('document_office', 'compress', optimize);
221
216
  break;
@@ -252,7 +247,6 @@ const MEDIA_FIELDS = Object.freeze({
252
247
  image: new Set(['quality', 'metadata', 'outputFormat']),
253
248
  audio: new Set(['bitrate', 'channels', 'sampleRate', 'normalize']),
254
249
  video: new Set(['codec', 'targetSize', 'crf', 'preset', 'width', 'height', 'fit', 'fps', 'faststart', 'audioCodec', 'audioBitrate']),
255
- document_pdf: new Set(['profile', 'grayscale']),
256
250
  document_office: new Set(['stripMacros', 'stripHiddenData', 'stripUnusedFonts']),
257
251
  document_odf: new Set(['stripMetadata', 'stripUnusedStyles']),
258
252
  document_epub: new Set(['fontSubsetting', 'stripUnusedCss']),
@@ -278,9 +272,9 @@ function detectMismatchedOverrides(media, overrides) {
278
272
  const otherSet = MEDIA_FIELDS[otherMedia];
279
273
  if (unknownFields.every((k) => otherSet.has(k))) {
280
274
  // PascalCase every underscore-separated segment so multi-segment
281
- // media (`document_pdf` → `DocumentPdf…`) emit the actual exported
275
+ // media (`document_office` → `DocumentOffice…`) emit the actual exported
282
276
  // class name (code-review MEDIUM: previously emitted
283
- // `Documentpdf…` which doesn't resolve in user code).
277
+ // `Documentoffice…` which doesn't resolve in user code).
284
278
  const className = otherMedia
285
279
  .split('_')
286
280
  .map((s) => s.charAt(0).toUpperCase() + s.slice(1))
@@ -321,7 +315,6 @@ export const KNOWN_WIRE_FIELDS = Object.freeze({
321
315
  image: new Set(['quality', 'metadata', 'output_format']),
322
316
  audio: new Set(['bitrate', 'channels', 'sample_rate', 'normalize', 'trim_start', 'trim_end']),
323
317
  video: new Set(['codec', 'encoding_mode', 'crf', 'target_size_bytes', 'preset', 'width', 'height', 'fit', 'fps', 'faststart', 'audio_codec', 'audio_bitrate', 'trim_start', 'trim_end']),
324
- document_pdf: new Set(['profile', 'grayscale']),
325
318
  document_office: new Set(['strip_macros', 'strip_hidden_data', 'strip_unused_fonts']),
326
319
  document_odf: new Set(['strip_metadata', 'strip_unused_styles']),
327
320
  document_epub: new Set(['font_subsetting', 'strip_unused_css']),
@@ -444,6 +437,9 @@ function computePresetConfigHash(clientDefault, scopedDefault, callPresetOverrid
444
437
  * `optimize` unset ⇒ layer 1 contributes nothing; `resolvedOptions.preset = null`.
445
438
  */
446
439
  export function resolveCompressOptions(input) {
440
+ if (input.media === 'document_pdf') {
441
+ throw new GislConfigError('PDF compression was removed at contracts v2.166.0; convert() / transform() still accept PDF.', { reason: 'unsupported_media' });
442
+ }
447
443
  const { media, op, presetDefaults, scopedPresetDefaults, presetOverrides, optimize, explicitOptions, audioLossless } = input;
448
444
  if (op !== 'compress') {
449
445
  throw new GislConfigError(`Preset resolution is only wired for compress operations today; got op='${op}'.`, { reason: 'unsupported_op' });
@@ -2,27 +2,31 @@ import { OptimizeFor } from '../../generated/sdk_spec/enums.js';
2
2
  import { ImageCompressPresetOptions, type ImageCompressPresetOptionsInput } from './image_compress.js';
3
3
  import { AudioCompressPresetOptions, type AudioCompressPresetOptionsInput } from './audio_compress.js';
4
4
  import { VideoCompressPresetOptions, type VideoCompressPresetOptionsInput } from './video_compress.js';
5
- import { DocumentPdfCompressPresetOptions, type DocumentPdfCompressPresetOptionsInput } from './document_pdf_compress.js';
6
5
  import { DocumentOfficeCompressPresetOptions, type DocumentOfficeCompressPresetOptionsInput } from './document_office_compress.js';
7
6
  import { DocumentOdfCompressPresetOptions, type DocumentOdfCompressPresetOptionsInput } from './document_odf_compress.js';
8
7
  import { DocumentEpubCompressPresetOptions, type DocumentEpubCompressPresetOptionsInput } from './document_epub_compress.js';
9
8
  export { ImageCompressPresetOptions, type ImageCompressPresetOptionsInput, } from './image_compress.js';
10
9
  export { AudioCompressPresetOptions, type AudioCompressPresetOptionsInput, } from './audio_compress.js';
11
10
  export { VideoCompressPresetOptions, type VideoCompressPresetOptionsInput, } from './video_compress.js';
12
- export { DocumentPdfCompressPresetOptions, type DocumentPdfCompressPresetOptionsInput, } from './document_pdf_compress.js';
13
11
  export { DocumentOfficeCompressPresetOptions, type DocumentOfficeCompressPresetOptionsInput, } from './document_office_compress.js';
14
12
  export { DocumentOdfCompressPresetOptions, type DocumentOdfCompressPresetOptionsInput, } from './document_odf_compress.js';
15
13
  export { DocumentEpubCompressPresetOptions, type DocumentEpubCompressPresetOptionsInput, } from './document_epub_compress.js';
16
- export { OptimizeFor, ImageMetadataPolicy, ImageFormat, VideoCodec, VideoPreset, VideoFit, AudioBitrate, AudioCodec, AudioSampleRate, PdfProfile, PdfColorspace, } from '../../generated/sdk_spec/enums.js';
14
+ export { OptimizeFor, ImageMetadataPolicy, ImageFormat, VideoCodec, VideoPreset, VideoFit, AudioBitrate, AudioCodec, AudioSampleRate, } from '../../generated/sdk_spec/enums.js';
17
15
  /** Supported media×op pairs for preset cells in T4a. Compress-only. */
18
- export type PresetMedia = 'image' | 'audio' | 'video' | 'document_pdf' | 'document_office' | 'document_odf' | 'document_epub';
16
+ export type PresetMedia = 'image' | 'audio' | 'video' | 'document_office' | 'document_odf' | 'document_epub';
17
+ /**
18
+ * Media the file-first detector can identify — the compressible
19
+ * `PresetMedia` set PLUS `document_pdf`, which is detectable (and a valid
20
+ * watermark-reject / convert / transform base) but NOT compressible.
21
+ */
22
+ export type DetectedMedia = PresetMedia | 'document_pdf';
19
23
  export type PresetOp = 'compress';
20
24
  /**
21
25
  * Union of leaf-DTO types the resolver will see from `cellFor()`.
22
26
  * Discriminated by which `media` the caller passes — the type system
23
27
  * narrows the return automatically via the overload set below.
24
28
  */
25
- export type AnyPresetOptions = ImageCompressPresetOptions | AudioCompressPresetOptions | VideoCompressPresetOptions | DocumentPdfCompressPresetOptions | DocumentOfficeCompressPresetOptions | DocumentOdfCompressPresetOptions | DocumentEpubCompressPresetOptions;
29
+ export type AnyPresetOptions = ImageCompressPresetOptions | AudioCompressPresetOptions | VideoCompressPresetOptions | DocumentOfficeCompressPresetOptions | DocumentOdfCompressPresetOptions | DocumentEpubCompressPresetOptions;
26
30
  /**
27
31
  * Per-cell field-merge: parent fields ⊕ child fields where defined.
28
32
  * Re-construct the leaf DTO via the matching `<LeafClass>.from(merged)`
@@ -73,8 +77,6 @@ export declare class PresetDefaults {
73
77
  audioCompress(level: OptimizeFor, input?: AudioCompressPresetOptionsInput): PresetDefaults;
74
78
  /** Register a (level, delta) on the video-compress cell. Immutable. */
75
79
  videoCompress(level: OptimizeFor, input?: VideoCompressPresetOptionsInput): PresetDefaults;
76
- /** Register a (level, delta) on the document-pdf-compress cell. Immutable. */
77
- pdfCompress(level: OptimizeFor, input?: DocumentPdfCompressPresetOptionsInput): PresetDefaults;
78
80
  /** Register a (level, delta) on the document-office-compress cell. Immutable. */
79
81
  officeCompress(level: OptimizeFor, input?: DocumentOfficeCompressPresetOptionsInput): PresetDefaults;
80
82
  /** Register a (level, delta) on the document-odf-compress cell. Immutable. */
@@ -84,7 +86,6 @@ export declare class PresetDefaults {
84
86
  /** @internal */ cellFor(media: 'image', op: 'compress', level: OptimizeFor): ImageCompressPresetOptions | undefined;
85
87
  /** @internal */ cellFor(media: 'audio', op: 'compress', level: OptimizeFor): AudioCompressPresetOptions | undefined;
86
88
  /** @internal */ cellFor(media: 'video', op: 'compress', level: OptimizeFor): VideoCompressPresetOptions | undefined;
87
- /** @internal */ cellFor(media: 'document_pdf', op: 'compress', level: OptimizeFor): DocumentPdfCompressPresetOptions | undefined;
88
89
  /** @internal */ cellFor(media: 'document_office', op: 'compress', level: OptimizeFor): DocumentOfficeCompressPresetOptions | undefined;
89
90
  /** @internal */ cellFor(media: 'document_odf', op: 'compress', level: OptimizeFor): DocumentOdfCompressPresetOptions | undefined;
90
91
  /** @internal */ cellFor(media: 'document_epub', op: 'compress', level: OptimizeFor): DocumentEpubCompressPresetOptions | undefined;
@@ -28,7 +28,6 @@
28
28
  import { ImageCompressPresetOptions, } from './image_compress.js';
29
29
  import { AudioCompressPresetOptions, } from './audio_compress.js';
30
30
  import { VideoCompressPresetOptions, } from './video_compress.js';
31
- import { DocumentPdfCompressPresetOptions, } from './document_pdf_compress.js';
32
31
  import { DocumentOfficeCompressPresetOptions, } from './document_office_compress.js';
33
32
  import { DocumentOdfCompressPresetOptions, } from './document_odf_compress.js';
34
33
  import { DocumentEpubCompressPresetOptions, } from './document_epub_compress.js';
@@ -36,12 +35,11 @@ import { DocumentEpubCompressPresetOptions, } from './document_epub_compress.js'
36
35
  export { ImageCompressPresetOptions, } from './image_compress.js';
37
36
  export { AudioCompressPresetOptions, } from './audio_compress.js';
38
37
  export { VideoCompressPresetOptions, } from './video_compress.js';
39
- export { DocumentPdfCompressPresetOptions, } from './document_pdf_compress.js';
40
38
  export { DocumentOfficeCompressPresetOptions, } from './document_office_compress.js';
41
39
  export { DocumentOdfCompressPresetOptions, } from './document_odf_compress.js';
42
40
  export { DocumentEpubCompressPresetOptions, } from './document_epub_compress.js';
43
41
  // Re-export ergonomic enums for callers (single canonical path).
44
- export { OptimizeFor, ImageMetadataPolicy, ImageFormat, VideoCodec, VideoPreset, VideoFit, AudioBitrate, AudioCodec, AudioSampleRate, PdfProfile, PdfColorspace, } from '../../generated/sdk_spec/enums.js';
42
+ export { OptimizeFor, ImageMetadataPolicy, ImageFormat, VideoCodec, VideoPreset, VideoFit, AudioBitrate, AudioCodec, AudioSampleRate, } from '../../generated/sdk_spec/enums.js';
45
43
  function cellKeyOf(media, op) {
46
44
  return `${media}_${op}`;
47
45
  }
@@ -89,8 +87,6 @@ function mergePresetOptions(cellKey, parentOpts, childOpts) {
89
87
  return AudioCompressPresetOptions.from(mergedFields);
90
88
  case 'video_compress':
91
89
  return VideoCompressPresetOptions.from(mergedFields);
92
- case 'document_pdf_compress':
93
- return DocumentPdfCompressPresetOptions.from(mergedFields);
94
90
  case 'document_office_compress':
95
91
  return DocumentOfficeCompressPresetOptions.from(mergedFields);
96
92
  case 'document_odf_compress':
@@ -178,10 +174,6 @@ export class PresetDefaults {
178
174
  videoCompress(level, input = {}) {
179
175
  return new PresetDefaults(withCellEntry(this.cells, 'video_compress', level, VideoCompressPresetOptions.from(input)));
180
176
  }
181
- /** Register a (level, delta) on the document-pdf-compress cell. Immutable. */
182
- pdfCompress(level, input = {}) {
183
- return new PresetDefaults(withCellEntry(this.cells, 'document_pdf_compress', level, DocumentPdfCompressPresetOptions.from(input)));
184
- }
185
177
  /** Register a (level, delta) on the document-office-compress cell. Immutable. */
186
178
  officeCompress(level, input = {}) {
187
179
  return new PresetDefaults(withCellEntry(this.cells, 'document_office_compress', level, DocumentOfficeCompressPresetOptions.from(input)));
package/dist/errors.d.ts CHANGED
@@ -1,4 +1,6 @@
1
- import type { AuthErrorResponse, AuthRejectionEnvelope, AuthRejectionEnvelopeErrorTypeEnum, BalanceExhaustedResponse, FeatureNotAvailableResponse, FeatureTierRestrictedResponse, ProbePendingResponse, TierRestrictionResponse, UploadDurationExceedsTierResponse, UploadSizeExceedsTierResponse, WorkflowExpiredResponse } from '@giveitsmaller/contracts/openapi';
1
+ import type { AuthErrorResponse, AuthRejectionEnvelope, AuthRejectionEnvelopeErrorTypeEnum, BalanceExhaustedResponse, FeatureNotAvailableResponse, FeatureTierRestrictedResponse, LongFormConcurrencyLimitResponse, ProbePendingResponse, TierRestrictionResponse, UploadDurationExceedsTierResponse, UploadSizeExceedsTierResponse, WorkflowExpiredResponse } from '@giveitsmaller/contracts/openapi';
2
+ import type { ErrorCategory } from './generated/sdk_spec/errors.js';
3
+ import type { RateLimitSnapshot } from './retry-metadata.js';
2
4
  export declare class GislError extends Error {
3
5
  constructor(message: string);
4
6
  }
@@ -14,6 +16,11 @@ export interface GislApiErrorOptions {
14
16
  readonly locale?: string;
15
17
  readonly messageParams?: Record<string, unknown>;
16
18
  readonly payload?: unknown;
19
+ /**
20
+ * The wire-stable machine error code (the response envelope's `error` field,
21
+ * SCREAMING_SNAKE, never localised). See {@link GislApiError.errorCode}.
22
+ */
23
+ readonly errorCode?: string;
17
24
  /**
18
25
  * The response headers from the HTTP response that produced this error.
19
26
  * Keys are LOWERCASED (HTTP header names are case-insensitive per RFC 9110,
@@ -33,6 +40,19 @@ export interface GislApiErrorOptions {
33
40
  export declare class GislApiError extends GislError {
34
41
  readonly statusCode: number;
35
42
  readonly errorMessage: string;
43
+ /**
44
+ * The wire-stable machine error code — the response envelope's `error` field
45
+ * (SCREAMING_SNAKE, never localised). DISTINCT from {@link errorMessage},
46
+ * which is the human `message`. Mirrors the PHP `GislApiError.errorCode`.
47
+ *
48
+ * Optional here (PHP's is a required field defaulting to `'unknown_error'`):
49
+ * a DELIBERATE optional-vs-sentinel divergence — `undefined` when the wire
50
+ * envelope carries no `error` (e.g. a non-JSON / invalid-JSON response). When
51
+ * the wire DOES carry `error`, both SDKs surface the same value. Machine
52
+ * dispatch still keys off the typed subclasses (`payload.errorType`); this is
53
+ * the flat machine code for a base `GislApiError` (e.g. a plain 404).
54
+ */
55
+ readonly errorCode?: string;
36
56
  readonly path?: string;
37
57
  readonly details?: unknown;
38
58
  readonly messageKey?: string;
@@ -53,6 +73,42 @@ export declare class GislApiError extends GislError {
53
73
  */
54
74
  readonly contentLanguage?: string;
55
75
  constructor(statusCode: number, errorMessage: string, path?: string, details?: unknown, options?: GislApiErrorOptions);
76
+ /**
77
+ * Resolve the generated `ERROR_CODES` entry for this error, SOURCE-AWARE
78
+ * (plan D1). ~9 registry codes are keyed by the `error_type` discriminator
79
+ * rather than the envelope `error` field, so try the typed discriminator
80
+ * FIRST (camel `errorType`, raw-snake `error_type` fallback), then fall back
81
+ * to the flat machine {@link errorCode}. Returns `undefined` when neither
82
+ * resolves — e.g. a bare base error whose payload carries no discriminator.
83
+ * NEVER throws on a missing payload / discriminator.
84
+ */
85
+ private resolveErrorEntry;
86
+ /**
87
+ * Whether retrying this request could plausibly succeed. `true` when the HTTP
88
+ * status is inherently retryable (408 / 429 / 5xx) OR the resolved taxonomy
89
+ * entry marks the code retryable (e.g. `probe_pending`). Note: logical OR
90
+ * (not `??`) — a 429 is retryable regardless of the taxonomy, and a
91
+ * registry-retryable code is retryable regardless of status.
92
+ */
93
+ get retryable(): boolean;
94
+ /**
95
+ * The taxonomy category for this error's machine code, from the generated
96
+ * `ERROR_CODES` registry, or `undefined` when the code isn't in the registry
97
+ * (e.g. a bare base error whose payload carries no discriminator).
98
+ */
99
+ get category(): ErrorCategory | undefined;
100
+ /**
101
+ * The rate-limit snapshot parsed from the `x-ratelimit-*` response headers,
102
+ * or `undefined` when they aren't all present as non-negative integers. Read
103
+ * this after a 429 to schedule a back-off.
104
+ */
105
+ get rateLimit(): RateLimitSnapshot | undefined;
106
+ /**
107
+ * The server-suggested back-off delay in whole seconds, parsed from the
108
+ * `Retry-After` response header, or `undefined` when absent / zero / past /
109
+ * malformed. Mirrors the retry-loop parser's semantics.
110
+ */
111
+ get retryAfterSeconds(): number | undefined;
56
112
  }
57
113
  /**
58
114
  * Shape of a single validation detail entry. Mirrors the v2
@@ -78,6 +134,34 @@ export declare class GislBalanceExhaustedError extends GislApiError {
78
134
  readonly payload: BalanceExhaustedResponse;
79
135
  constructor(statusCode: number, errorMessage: string, payload: BalanceExhaustedResponse, path?: string, extra?: Omit<GislApiErrorOptions, 'payload'>);
80
136
  }
137
+ /**
138
+ * `429` on `POST /api/workflows` when the caller already holds the maximum
139
+ * number of concurrent in-flight long-form (Fargate) workflows their tier
140
+ * permits (Pro 2 / Max 5; Enterprise uncapped). DISTINCT from an infrastructure
141
+ * rate-limit `429`: it carries the machine code `LONG_FORM_CONCURRENCY_LIMIT_EXCEEDED`
142
+ * and a `links.upgrade` deep link, and has **no `Retry-After`** — the limit clears
143
+ * when an in-flight long-form workflow finishes, not on a timer. A generic infra
144
+ * rate-limit `429` (no matching code) surfaces as the base {@link GislApiError}
145
+ * instead, where {@link GislApiError.retryAfterSeconds} applies.
146
+ *
147
+ * Dispatched on the `error` CODE, not `error_type` (the envelope carries none).
148
+ *
149
+ * @example
150
+ * try {
151
+ * await client.createWorkflow({ jobs });
152
+ * } catch (e) {
153
+ * if (e instanceof GislLongFormConcurrencyError) {
154
+ * showUpgradeCta(e.upgradeUrl); // wait on completion or upgrade — do NOT back off
155
+ * }
156
+ * throw e;
157
+ * }
158
+ */
159
+ export declare class GislLongFormConcurrencyError extends GislApiError {
160
+ readonly payload: LongFormConcurrencyLimitResponse;
161
+ constructor(statusCode: number, errorMessage: string, payload: LongFormConcurrencyLimitResponse, path?: string, extra?: Omit<GislApiErrorOptions, 'payload'>);
162
+ /** The pricing / upgrade deep link (`links.upgrade`), or `undefined` when absent. */
163
+ get upgradeUrl(): string | undefined;
164
+ }
81
165
  export declare class GislTierRestrictedError extends GislApiError {
82
166
  readonly payload: TierRestrictionResponse;
83
167
  constructor(statusCode: number, errorMessage: string, payload: TierRestrictionResponse, path?: string, extra?: Omit<GislApiErrorOptions, 'payload'>);
@@ -355,7 +439,25 @@ export declare class GislBundleAlreadyArchivedError extends GislConfigError {
355
439
  constructor();
356
440
  }
357
441
  export declare class GislTimeoutError extends GislError {
358
- constructor(message: string);
442
+ /**
443
+ * The workflow this timeout is scoped to, when the SDK knows it. Set on a
444
+ * timed-out `run()` / `wait()` / poll / download once the workflow has been
445
+ * created: a timeout does NOT mean the work failed — the server keeps
446
+ * processing, so poll `client.getWorkflowStatus(workflowId)` /
447
+ * `getWorkflowDownloads(workflowId)` to recover a result that completed after
448
+ * the deadline, instead of re-running (a re-run re-uploads and, for
449
+ * authenticated callers, settles a SECOND charge for the same deliverable).
450
+ *
451
+ * `undefined` when the SDK has no id to offer. That is NOT a guarantee that
452
+ * nothing was created or charged: it covers both the safe case (an upload /
453
+ * probe timeout before any workflow existed) AND the AMBIGUOUS case (the
454
+ * `POST /api/workflows` request itself timed out — the server may have
455
+ * created and charged the workflow before its response was lost). Treat an
456
+ * absent id as "cannot auto-recover", not "clean slate": reconcile (e.g. list
457
+ * recent workflows) before re-running rather than assuming nothing happened.
458
+ */
459
+ readonly workflowId?: string;
460
+ constructor(message: string, workflowId?: string);
359
461
  }
360
462
  /**
361
463
  * Transport-level failure: the underlying `fetch` (or other transport) could
@@ -439,7 +541,7 @@ export type GislSinkErrorReason = 'not_single_output' | 'downloader_unavailable'
439
541
  /**
440
542
  * Thrown by the file-first `RunResult` sinks (`toFile()` / `downloadTo()`,
441
543
  * FF1) when they cannot deliver. The machine-readable `reason` discriminates
442
- * the three cases, mirroring the `reason`-bag convention on
544
+ * the six cases below, mirroring the `reason`-bag convention on
443
545
  * {@link GislConfigError}:
444
546
  *
445
547
  * - `not_single_output` — `toFile()` requires exactly one output but the
package/dist/errors.js CHANGED
@@ -1,3 +1,12 @@
1
+ // W8v4jWzx — the generated error-taxonomy registry stays INTERNAL to this
2
+ // module (only the `ErrorCategory` TYPE is re-exported from the public barrel).
3
+ import { ERROR_CODES } from './generated/sdk_spec/errors.js';
4
+ import { isApiRetryableStatus, rateLimitFromHeaders, retryAfterSecondsFromHeaders, } from './retry-metadata.js';
5
+ // Registry keys are lowercase_snake; normalise the wire code / discriminator
6
+ // (trim + lowercase) before looking it up in ERROR_CODES.
7
+ function normalizeErrorCode(rawCode) {
8
+ return rawCode.trim().toLowerCase();
9
+ }
1
10
  export class GislError extends Error {
2
11
  constructor(message) {
3
12
  super(message);
@@ -7,6 +16,19 @@ export class GislError extends Error {
7
16
  export class GislApiError extends GislError {
8
17
  statusCode;
9
18
  errorMessage;
19
+ /**
20
+ * The wire-stable machine error code — the response envelope's `error` field
21
+ * (SCREAMING_SNAKE, never localised). DISTINCT from {@link errorMessage},
22
+ * which is the human `message`. Mirrors the PHP `GislApiError.errorCode`.
23
+ *
24
+ * Optional here (PHP's is a required field defaulting to `'unknown_error'`):
25
+ * a DELIBERATE optional-vs-sentinel divergence — `undefined` when the wire
26
+ * envelope carries no `error` (e.g. a non-JSON / invalid-JSON response). When
27
+ * the wire DOES carry `error`, both SDKs surface the same value. Machine
28
+ * dispatch still keys off the typed subclasses (`payload.errorType`); this is
29
+ * the flat machine code for a base `GislApiError` (e.g. a plain 404).
30
+ */
31
+ errorCode;
10
32
  path;
11
33
  details;
12
34
  messageKey;
@@ -41,10 +63,69 @@ export class GislApiError extends GislError {
41
63
  this.locale = options.locale;
42
64
  this.messageParams = options.messageParams;
43
65
  this.payload = options.payload;
66
+ this.errorCode = options.errorCode;
44
67
  this.responseHeaders = options.responseHeaders;
45
68
  this.contentLanguage = options.contentLanguage;
46
69
  }
47
70
  }
71
+ /**
72
+ * Resolve the generated `ERROR_CODES` entry for this error, SOURCE-AWARE
73
+ * (plan D1). ~9 registry codes are keyed by the `error_type` discriminator
74
+ * rather than the envelope `error` field, so try the typed discriminator
75
+ * FIRST (camel `errorType`, raw-snake `error_type` fallback), then fall back
76
+ * to the flat machine {@link errorCode}. Returns `undefined` when neither
77
+ * resolves — e.g. a bare base error whose payload carries no discriminator.
78
+ * NEVER throws on a missing payload / discriminator.
79
+ */
80
+ resolveErrorEntry() {
81
+ const payload = this.payload;
82
+ const rawErrorType = payload?.errorType ?? payload?.error_type;
83
+ if (typeof rawErrorType === 'string') {
84
+ const byType = ERROR_CODES[normalizeErrorCode(rawErrorType)];
85
+ if (byType !== undefined)
86
+ return byType;
87
+ }
88
+ if (this.errorCode !== undefined) {
89
+ const byCode = ERROR_CODES[normalizeErrorCode(this.errorCode)];
90
+ if (byCode !== undefined)
91
+ return byCode;
92
+ }
93
+ return undefined;
94
+ }
95
+ /**
96
+ * Whether retrying this request could plausibly succeed. `true` when the HTTP
97
+ * status is inherently retryable (408 / 429 / 5xx) OR the resolved taxonomy
98
+ * entry marks the code retryable (e.g. `probe_pending`). Note: logical OR
99
+ * (not `??`) — a 429 is retryable regardless of the taxonomy, and a
100
+ * registry-retryable code is retryable regardless of status.
101
+ */
102
+ get retryable() {
103
+ return (isApiRetryableStatus(this.statusCode) || (this.resolveErrorEntry()?.retryable ?? false));
104
+ }
105
+ /**
106
+ * The taxonomy category for this error's machine code, from the generated
107
+ * `ERROR_CODES` registry, or `undefined` when the code isn't in the registry
108
+ * (e.g. a bare base error whose payload carries no discriminator).
109
+ */
110
+ get category() {
111
+ return this.resolveErrorEntry()?.category;
112
+ }
113
+ /**
114
+ * The rate-limit snapshot parsed from the `x-ratelimit-*` response headers,
115
+ * or `undefined` when they aren't all present as non-negative integers. Read
116
+ * this after a 429 to schedule a back-off.
117
+ */
118
+ get rateLimit() {
119
+ return rateLimitFromHeaders(this.responseHeaders);
120
+ }
121
+ /**
122
+ * The server-suggested back-off delay in whole seconds, parsed from the
123
+ * `Retry-After` response header, or `undefined` when absent / zero / past /
124
+ * malformed. Mirrors the retry-loop parser's semantics.
125
+ */
126
+ get retryAfterSeconds() {
127
+ return retryAfterSecondsFromHeaders(this.responseHeaders);
128
+ }
48
129
  }
49
130
  export class GislValidationError extends GislApiError {
50
131
  constructor(statusCode, errorMessage, details, path, options) {
@@ -64,6 +145,38 @@ export class GislBalanceExhaustedError extends GislApiError {
64
145
  this.name = 'GislBalanceExhaustedError';
65
146
  }
66
147
  }
148
+ /**
149
+ * `429` on `POST /api/workflows` when the caller already holds the maximum
150
+ * number of concurrent in-flight long-form (Fargate) workflows their tier
151
+ * permits (Pro 2 / Max 5; Enterprise uncapped). DISTINCT from an infrastructure
152
+ * rate-limit `429`: it carries the machine code `LONG_FORM_CONCURRENCY_LIMIT_EXCEEDED`
153
+ * and a `links.upgrade` deep link, and has **no `Retry-After`** — the limit clears
154
+ * when an in-flight long-form workflow finishes, not on a timer. A generic infra
155
+ * rate-limit `429` (no matching code) surfaces as the base {@link GislApiError}
156
+ * instead, where {@link GislApiError.retryAfterSeconds} applies.
157
+ *
158
+ * Dispatched on the `error` CODE, not `error_type` (the envelope carries none).
159
+ *
160
+ * @example
161
+ * try {
162
+ * await client.createWorkflow({ jobs });
163
+ * } catch (e) {
164
+ * if (e instanceof GislLongFormConcurrencyError) {
165
+ * showUpgradeCta(e.upgradeUrl); // wait on completion or upgrade — do NOT back off
166
+ * }
167
+ * throw e;
168
+ * }
169
+ */
170
+ export class GislLongFormConcurrencyError extends GislApiError {
171
+ constructor(statusCode, errorMessage, payload, path, extra) {
172
+ super(statusCode, errorMessage, path, undefined, buildOptionsWithPayload(payload, extra));
173
+ this.name = 'GislLongFormConcurrencyError';
174
+ }
175
+ /** The pricing / upgrade deep link (`links.upgrade`), or `undefined` when absent. */
176
+ get upgradeUrl() {
177
+ return this.payload.links?.upgrade;
178
+ }
179
+ }
67
180
  export class GislTierRestrictedError extends GislApiError {
68
181
  constructor(statusCode, errorMessage, payload, path, extra) {
69
182
  super(statusCode, errorMessage, path, undefined, buildOptionsWithPayload(payload, extra));
@@ -366,9 +479,30 @@ export class GislBundleAlreadyArchivedError extends GislConfigError {
366
479
  }
367
480
  }
368
481
  export class GislTimeoutError extends GislError {
369
- constructor(message) {
482
+ /**
483
+ * The workflow this timeout is scoped to, when the SDK knows it. Set on a
484
+ * timed-out `run()` / `wait()` / poll / download once the workflow has been
485
+ * created: a timeout does NOT mean the work failed — the server keeps
486
+ * processing, so poll `client.getWorkflowStatus(workflowId)` /
487
+ * `getWorkflowDownloads(workflowId)` to recover a result that completed after
488
+ * the deadline, instead of re-running (a re-run re-uploads and, for
489
+ * authenticated callers, settles a SECOND charge for the same deliverable).
490
+ *
491
+ * `undefined` when the SDK has no id to offer. That is NOT a guarantee that
492
+ * nothing was created or charged: it covers both the safe case (an upload /
493
+ * probe timeout before any workflow existed) AND the AMBIGUOUS case (the
494
+ * `POST /api/workflows` request itself timed out — the server may have
495
+ * created and charged the workflow before its response was lost). Treat an
496
+ * absent id as "cannot auto-recover", not "clean slate": reconcile (e.g. list
497
+ * recent workflows) before re-running rather than assuming nothing happened.
498
+ */
499
+ workflowId;
500
+ constructor(message, workflowId) {
370
501
  super(message);
371
502
  this.name = 'GislTimeoutError';
503
+ // Normalise an empty id to "absent" — an empty string is not a usable
504
+ // recovery handle (some throw sites derive the id as `… ?? ''`).
505
+ this.workflowId = workflowId === '' ? undefined : workflowId;
372
506
  }
373
507
  }
374
508
  /**
@@ -479,7 +613,7 @@ export class GislResultNotReadyError extends GislError {
479
613
  /**
480
614
  * Thrown by the file-first `RunResult` sinks (`toFile()` / `downloadTo()`,
481
615
  * FF1) when they cannot deliver. The machine-readable `reason` discriminates
482
- * the three cases, mirroring the `reason`-bag convention on
616
+ * the six cases below, mirroring the `reason`-bag convention on
483
617
  * {@link GislConfigError}:
484
618
  *
485
619
  * - `not_single_output` — `toFile()` requires exactly one output but the