@giveitsmaller/sdk 0.18.0 → 0.20.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 (40) hide show
  1. package/README.md +1 -1
  2. package/dist/_audit.js +12 -0
  3. package/dist/builder.d.ts +1 -0
  4. package/dist/client.d.ts +8 -1
  5. package/dist/client.js +27 -32
  6. package/dist/ergonomic/image_output_routes.d.ts +6 -5
  7. package/dist/ergonomic/image_output_routes.js +29 -21
  8. package/dist/ergonomic/option_types.d.ts +85 -11
  9. package/dist/ergonomic/option_types.js +11 -6
  10. package/dist/ergonomic/option_validation.d.ts +21 -3
  11. package/dist/ergonomic/option_validation.js +36 -3
  12. package/dist/ergonomic/preset_resolver.d.ts +1 -1
  13. package/dist/ergonomic/preset_resolver.js +6 -7
  14. package/dist/ergonomic/presets/document_epub_compress.d.ts +0 -2
  15. package/dist/ergonomic/presets/document_epub_compress.js +2 -7
  16. package/dist/ergonomic/presets/document_odf_compress.d.ts +0 -2
  17. package/dist/ergonomic/presets/document_odf_compress.js +2 -7
  18. package/dist/ergonomic/presets/document_office_compress.d.ts +0 -2
  19. package/dist/ergonomic/presets/document_office_compress.js +2 -7
  20. package/dist/errors.d.ts +57 -1
  21. package/dist/errors.js +82 -1
  22. package/dist/file-first.d.ts +195 -8
  23. package/dist/file-first.js +462 -124
  24. package/dist/generated/sdk_spec/enums.d.ts +4 -2
  25. package/dist/generated/sdk_spec/enums.js +11 -5
  26. package/dist/generated/sdk_spec/presets.js +3 -12
  27. package/dist/generated/sdk_spec/version.d.ts +3 -3
  28. package/dist/generated/sdk_spec/version.js +3 -3
  29. package/dist/gisl.d.ts +72 -3
  30. package/dist/gisl.js +72 -2
  31. package/dist/index.core.d.ts +8 -6
  32. package/dist/index.core.js +9 -1
  33. package/dist/merge.d.ts +12 -0
  34. package/dist/merge.js +12 -0
  35. package/dist/retry-metadata.d.ts +37 -0
  36. package/dist/retry-metadata.js +86 -0
  37. package/dist/sse.d.ts +2 -1
  38. package/dist/sse.js +26 -6
  39. package/dist/types.d.ts +43 -1
  40. package/package.json +2 -2
@@ -13,7 +13,7 @@ import { GislItemFailedError } from './errors.js';
13
13
  import { type ProgressEvent } from './builder.js';
14
14
  import type { GislClient } from './client.js';
15
15
  import type { OperationDownload, WorkflowStatusResponse } from '@giveitsmaller/contracts/openapi';
16
- import type { ConvertOptions, ThumbnailOptions, TextWatermarkOptions, WatermarkOptions, OutputOptions, OutputFit } from './ergonomic/option_types.js';
16
+ import type { ConvertOptions, ThumbnailOptions, TransformOptions, TextWatermarkOptions, WatermarkOptions, OutputOptions, OutputFit } from './ergonomic/option_types.js';
17
17
  import { OptimizeFor } from './generated/sdk_spec/enums.js';
18
18
  import type { PresetDefaults } from './ergonomic/presets/index.js';
19
19
  import type { WorkflowCreatePayload } from './types.js';
@@ -57,6 +57,34 @@ export interface OutputFile {
57
57
  readonly filename: string;
58
58
  readonly sizeBytes: number;
59
59
  readonly operation: string;
60
+ /**
61
+ * For a `target_size` encode: the quality the encode-measure loop settled
62
+ * on. Projected from the generated {@link OperationDownload.chosenQuality};
63
+ * undefined (omitted) for non-target-size outputs. Pairs with
64
+ * {@link targetSizeMet}.
65
+ */
66
+ readonly chosenQuality?: number;
67
+ /**
68
+ * For a `target_size` encode: whether the output landed at or under the
69
+ * requested byte target. `false` is an honest best-effort outcome (target
70
+ * unreachable at min quality), NOT a failure. Projected from the generated
71
+ * {@link OperationDownload.targetSizeMet}; undefined for non-target-size
72
+ * outputs.
73
+ */
74
+ readonly targetSizeMet?: boolean;
75
+ /**
76
+ * The measured perceptual quality of an `auto_quality` encode (0-1). Projected
77
+ * from the generated {@link OperationDownload.measuredQuality}; undefined
78
+ * (omitted) when the worker reported no measurement. Pairs with
79
+ * {@link qualityMetric}, which names the metric it was measured on.
80
+ */
81
+ readonly measuredQuality?: number;
82
+ /**
83
+ * The metric {@link measuredQuality} was measured on (e.g. `ssimulacra2`).
84
+ * Projected from the generated {@link OperationDownload.qualityMetric};
85
+ * undefined when no measurement was reported.
86
+ */
87
+ readonly qualityMetric?: string;
60
88
  }
61
89
  /**
62
90
  * One succeeded entry in {@link RunResult.succeeded}: a single input's
@@ -101,6 +129,10 @@ export interface Manifest {
101
129
  * - `ok`: true iff `failed` is empty. (A boolean — the partition lists are
102
130
  * `succeeded`/`failed`; resolves the design doc's `ok` bool-vs-list
103
131
  * contradiction.)
132
+ * - `targetSizeMissed`: derived target-size signal — undefined when no output
133
+ * reports a target-size outcome (not a target_size run); otherwise true iff
134
+ * some artifact has `targetSizeMet === false`. Omitted from the JSON when
135
+ * undefined so non-target-size runs keep the common-case shape.
104
136
  * - `state`: lifecycle state (`completed` | `failed` | ...). Named `state`,
105
137
  * NOT `status`, matching the file-first `StatusSnapshot.state`.
106
138
  * - sinks fetch via the injected {@link Downloader}; a result with no
@@ -117,6 +149,14 @@ export declare class RunResult {
117
149
  readonly url?: string;
118
150
  /** True iff {@link failed} is empty. */
119
151
  readonly ok: boolean;
152
+ /**
153
+ * Whether any output missed its requested byte target. Derived from the
154
+ * per-output {@link OutputFile.targetSizeMet}: undefined when NO artifact
155
+ * reports a target-size outcome (every `targetSizeMet` undefined — not a
156
+ * target_size run); otherwise true iff some artifact has
157
+ * `targetSizeMet === false`.
158
+ */
159
+ readonly targetSizeMissed?: boolean;
120
160
  constructor(workflowId: string, state: string, artifacts: readonly OutputFile[], succeeded: readonly ItemResult[], failed: readonly ItemFailure[], downloader?: Downloader | undefined);
121
161
  /**
122
162
  * Address a succeeded input by the `key:` given to `file()`. Duplicate keys
@@ -143,16 +183,18 @@ export declare class RunResult {
143
183
  failOnPartial?: boolean;
144
184
  }): Promise<Manifest>;
145
185
  /**
146
- * Plain-object projection. Field ORDER (workflowId, state, ok, url?,
147
- * artifacts, succeeded, failed) is fixed to match the PHP `toArray()`
148
- * reference so JSON-string parity holds (FF1 shape assertion + FF2b harness
149
- * fixture). `url` is omitted entirely when undefined — `JSON.stringify`
150
- * then produces the identical shape to PHP's omit-when-null `toArray()`.
186
+ * Plain-object projection. Field ORDER (workflowId, state, ok,
187
+ * targetSizeMissed?, url?, artifacts, succeeded, failed) is fixed to match
188
+ * the PHP `toArray()` reference so JSON-string parity holds (FF1 shape
189
+ * assertion + FF2b harness fixture). `targetSizeMissed` + `url` are omitted
190
+ * entirely when undefined `JSON.stringify` then produces the identical
191
+ * shape to PHP's omit-when-null `toArray()`.
151
192
  */
152
193
  toJSON(): {
153
194
  workflowId: string;
154
195
  state: string;
155
196
  ok: boolean;
197
+ targetSizeMissed?: boolean;
156
198
  url?: string;
157
199
  artifacts: readonly OutputFile[];
158
200
  succeeded: readonly {
@@ -295,7 +337,7 @@ export declare const fileInput: {
295
337
  };
296
338
  /** One step in a {@link Recipe}'s chain — an op kind + captured ergonomic args. */
297
339
  interface RecipeStep {
298
- readonly opType: 'compress' | 'convert' | 'thumbnail' | 'text_watermark' | 'output';
340
+ readonly opType: 'compress' | 'convert' | 'thumbnail' | 'text_watermark' | 'output' | 'transform';
299
341
  readonly options: Readonly<Record<string, unknown>>;
300
342
  }
301
343
  /**
@@ -349,6 +391,19 @@ export declare class Recipe {
349
391
  * dropped from the wire options (not sent as `undefined`).
350
392
  */
351
393
  thumbnail(options: ThumbnailOptions): Recipe;
394
+ /**
395
+ * Geometric transform: rotate (0/90/180/270°) and/or flip. Chainable — the
396
+ * canonical single-job order is `transform → convert → compress → thumbnail`,
397
+ * so downstream size options refer to the final (post-transform) frame.
398
+ *
399
+ * Passthrough: `rotate`/`flip` are forwarded as-is; the SDK does NOT narrow
400
+ * per media (a `flip` on a PDF input passes SDK validation but the server
401
+ * rejects it — documents rotate only). The transform op is `availability:
402
+ * planned` today, so workflow-create returns `feature_not_available` (422)
403
+ * until the per-media Lambdas ship. A no-op (`rotate:0` + `flip:none`) is
404
+ * rejected server-side as `invalid_options`.
405
+ */
406
+ transform(options?: TransformOptions): Recipe;
352
407
  /**
353
408
  * Produce ONE transformed image: keep or change format, plus quality, resize
354
409
  * and route-honored controls. The single user-facing image transform — the SDK
@@ -385,7 +440,8 @@ export declare class Recipe {
385
440
  * (beta). Audio/document/animated-GIF/unsupported-subtype/undetectable bases
386
441
  * throw locally BEFORE any upload (the planned-op gate). `options` carries the
387
442
  * wire watermark options (`anchor`, `opacity`, `margin_x`, `margin_y`,
388
- * `overlay_width`). Returns a {@link WatermarkedRecipe} (chain post-watermark
443
+ * `overlay_width`, or `overlays[]` for the multi-overlay stack). Returns a
444
+ * {@link WatermarkedRecipe} (chain post-watermark
389
445
  * `compress`/`convert`/`thumbnail`, then `run`/`submit`). Distinct from
390
446
  * {@link textWatermark} (single-input text overlay).
391
447
  */
@@ -437,6 +493,8 @@ export declare class Recipe {
437
493
  maxWait?: string | number;
438
494
  onProgress?: (event: ProgressEvent) => void;
439
495
  signal?: AbortSignal;
496
+ /** Force the poll fallback instead of attempting SSE. Default true (SSE-first, poll fallback). */
497
+ useSSE?: boolean;
440
498
  pollIntervalMs?: number;
441
499
  probeBeforeCreate?: boolean;
442
500
  probeTimeoutMs?: number;
@@ -536,6 +594,14 @@ export declare const WATERMARK_CAPABILITY: {
536
594
  readonly mimes: readonly ["image/gif"];
537
595
  readonly availability: "planned";
538
596
  };
597
+ readonly image_tiff: {
598
+ readonly mimes: readonly ["image/tiff"];
599
+ readonly availability: "stable";
600
+ };
601
+ readonly image_bmp: {
602
+ readonly mimes: readonly ["image/bmp"];
603
+ readonly availability: "stable";
604
+ };
539
605
  };
540
606
  readonly video_watermark: {
541
607
  readonly video: {
@@ -588,6 +654,8 @@ export declare class FilesRecipe {
588
654
  convert(format: string, options?: ConvertOptions): FilesRecipe;
589
655
  /** Generate a preview of every input. `width` AND `height` are required; validated via the base {@link Recipe} before any upload. */
590
656
  thumbnail(options: ThumbnailOptions): FilesRecipe;
657
+ /** Apply the same geometric transform (rotate/flip) to every input. Validated via the base {@link Recipe}. */
658
+ transform(options?: TransformOptions): FilesRecipe;
591
659
  /** Apply the same text watermark to every input. Option keys validated via the base {@link Recipe}. */
592
660
  textWatermark(text: string, options?: TextWatermarkOptions): FilesRecipe;
593
661
  /**
@@ -644,6 +712,8 @@ export declare class FilesRecipe {
644
712
  maxWait?: string | number;
645
713
  onProgress?: (event: ProgressEvent) => void;
646
714
  signal?: AbortSignal;
715
+ /** Force the poll fallback instead of attempting SSE. Default true (SSE-first, poll fallback). */
716
+ useSSE?: boolean;
647
717
  pollIntervalMs?: number;
648
718
  probeBeforeCreate?: boolean;
649
719
  probeTimeoutMs?: number;
@@ -724,6 +794,8 @@ export declare class MergedRecipe {
724
794
  convert(format: string, options?: ConvertOptions): MergedRecipe;
725
795
  /** Thumbnail the merged output. `width` AND `height` are required; validated pre-upload. */
726
796
  thumbnail(options: ThumbnailOptions): MergedRecipe;
797
+ /** Geometric transform (rotate/flip) of the merged output. Passthrough; see {@link Recipe.transform}. */
798
+ transform(options?: TransformOptions): MergedRecipe;
727
799
  /**
728
800
  * Lower to the merge DAG: one `passthrough` source job per input + one
729
801
  * `merge` job whose `operations[]` is `[merge, ...post-combine ops]`. The
@@ -751,6 +823,8 @@ export declare class MergedRecipe {
751
823
  maxWait?: string | number;
752
824
  onProgress?: (event: ProgressEvent) => void;
753
825
  signal?: AbortSignal;
826
+ /** Force the poll fallback instead of attempting SSE. Default true (SSE-first, poll fallback). */
827
+ useSSE?: boolean;
754
828
  pollIntervalMs?: number;
755
829
  probeBeforeCreate?: boolean;
756
830
  probeTimeoutMs?: number;
@@ -850,6 +924,8 @@ export declare class ArchivedRecipe {
850
924
  maxWait?: string | number;
851
925
  onProgress?: (event: ProgressEvent) => void;
852
926
  signal?: AbortSignal;
927
+ /** Force the poll fallback instead of attempting SSE. Default true (SSE-first, poll fallback). */
928
+ useSSE?: boolean;
853
929
  pollIntervalMs?: number;
854
930
  probeBeforeCreate?: boolean;
855
931
  probeTimeoutMs?: number;
@@ -907,6 +983,8 @@ export declare class WatermarkedRecipe {
907
983
  convert(format: string, options?: ConvertOptions): WatermarkedRecipe;
908
984
  /** Thumbnail the watermarked output. `width` AND `height` are required; validated pre-upload. */
909
985
  thumbnail(options: ThumbnailOptions): WatermarkedRecipe;
986
+ /** Geometric transform (rotate/flip) of the watermarked output. Passthrough; see {@link Recipe.transform}. */
987
+ transform(options?: TransformOptions): WatermarkedRecipe;
910
988
  /**
911
989
  * Lower to the watermark DAG: a `src_0` passthrough/base-steps job + a `src_1`
912
990
  * passthrough/overlay-steps job + one `watermark` job whose `inputs[]` consume
@@ -930,6 +1008,8 @@ export declare class WatermarkedRecipe {
930
1008
  maxWait?: string | number;
931
1009
  onProgress?: (event: ProgressEvent) => void;
932
1010
  signal?: AbortSignal;
1011
+ /** Force the poll fallback instead of attempting SSE. Default true (SSE-first, poll fallback). */
1012
+ useSSE?: boolean;
933
1013
  pollIntervalMs?: number;
934
1014
  probeBeforeCreate?: boolean;
935
1015
  probeTimeoutMs?: number;
@@ -968,4 +1048,111 @@ export declare class WatermarkedRecipe {
968
1048
  private lowerPostSteps;
969
1049
  private withStep;
970
1050
  }
1051
+ /**
1052
+ * The keyed multi-recipe batch builder (FF7 / MFaCjL8d). `client.batch([r1, r2, …])`
1053
+ * runs N DISTINCT single-input keyed {@link Recipe}s as ONE workflow; the
1054
+ * partitioned {@link RunResult} addresses each entry's outputs by the caller key
1055
+ * given at `client.file(input, key)` time (`res.byKey('hero')`), and one failed
1056
+ * entry lands in `failed` without sinking the rest.
1057
+ *
1058
+ * **v1 scope (locked):** `.run()` only (no `submit()` / reattach — a follow-up);
1059
+ * single-input {@link Recipe} entries only — the multi-input builders
1060
+ * ({@link FilesRecipe}, {@link MergedRecipe}, {@link WatermarkedRecipe},
1061
+ * {@link ArchivedRecipe}) are REJECTED pre-upload. Cross-entry upload dedupe
1062
+ * IS applied (1LwSJcz1): two entries sourcing the SAME input (by
1063
+ * {@link inputIdentity}) upload ONCE and share the resulting fileId —
1064
+ * correctness-neutral (same bytes → same per-job output), it only elides
1065
+ * redundant uploads. Observable caveat: `onProgress` upload-phase events drop
1066
+ * to one-per-UNIQUE input rather than one-per-entry.
1067
+ *
1068
+ * **Lowering (one workflow):** for each entry `i`, lower its single job via
1069
+ * {@link Recipe.toWorkflowPayload} and re-id it `b{i}` — a POSITIONAL namespace
1070
+ * DISTINCT from the fan-out `file-{i}` / merge-archive-watermark `src_{i}` refs so
1071
+ * a future reattach can't misdetect the wire as a fan-out / merge. `keyByRef`
1072
+ * maps each `b{i}` ref to that entry's caller key, so
1073
+ * {@link projectMultiJobToRunResult} partitions per entry (1 job ↔ 1 key:
1074
+ * `completed` → `succeeded`, else → `failed` with a {@link GislItemFailedError}).
1075
+ *
1076
+ * **Immutability:** the ctor is CLIENT-ONLY (the ordered entries + the client) —
1077
+ * entries are already-built Recipes that captured their own preset defaults at
1078
+ * `client.file(...)` time, so batch never re-plumbs
1079
+ * presetDefaults/scopedPresetDefaults. Mirrors the PHP `BatchRecipe`.
1080
+ */
1081
+ export declare class BatchRecipe {
1082
+ private readonly client?;
1083
+ private readonly recipes;
1084
+ constructor(recipes: ReadonlyArray<Recipe>, client?: GislClient | undefined);
1085
+ /**
1086
+ * Execute the batch end-to-end: validate + lowering-preflight EVERY entry
1087
+ * BEFORE any upload, upload each entry's input, create ONE multi-job workflow
1088
+ * (one `b{i}` job per entry), await a terminal state (SSE with poll fallback,
1089
+ * honouring `useSSE`), then partition the per-job downloads into a keyed
1090
+ * {@link RunResult}. `partially_failed` is a NORMAL terminal state here — the
1091
+ * completed entries land in `succeeded`, the rest in `failed`.
1092
+ *
1093
+ * Requires a client bound at construction time — `gisl().batch([...])` wires
1094
+ * it; a directly-constructed {@link BatchRecipe} (e.g. a lowering-only test)
1095
+ * throws {@link GislConfigError}. Mirrors the fan-out {@link FilesRecipe.run}.
1096
+ */
1097
+ run(options?: {
1098
+ maxWait?: string | number;
1099
+ onProgress?: (event: ProgressEvent) => void;
1100
+ signal?: AbortSignal;
1101
+ /** Force the poll fallback instead of attempting SSE. Default true (SSE-first, poll fallback). */
1102
+ useSSE?: boolean;
1103
+ pollIntervalMs?: number;
1104
+ probeBeforeCreate?: boolean;
1105
+ probeTimeoutMs?: number;
1106
+ }): Promise<RunResult>;
1107
+ /**
1108
+ * Lower the batch to ONE multi-job workflow-create payload against a list of
1109
+ * resolved upload ids (one per entry, in entry order). Each entry `i` becomes
1110
+ * ONE job re-id'd `b{i}` carrying that entry's lowered `source` + `operations`.
1111
+ * Composes the single-file {@link Recipe.toWorkflowPayload} per entry so each
1112
+ * keeps its own media-hint + preset resolution and lowering logic is not
1113
+ * duplicated. `callback_url` is built in ONLY when a webhook is supplied
1114
+ * (batch v1 run() supplies none, so it is omitted).
1115
+ *
1116
+ * @internal Consumed by {@link run} (after uploading) and the cross-language
1117
+ * golden-payload lowering test (with fixed ids). Not caller-facing.
1118
+ */
1119
+ toWorkflowPayload(fileIds: readonly string[], callbackUrl?: string): WorkflowCreatePayload;
1120
+ /** The number of recipe entries in this batch (introspection / tests). */
1121
+ get recipeCount(): number;
1122
+ /**
1123
+ * Validate the batch AND lowering-preflight every entry BEFORE any upload
1124
+ * fires — an invalid entry costs no bandwidth. TWO PASSES (mirrors PHP
1125
+ * `BatchRecipe`'s structural-loop-then-preflight-loop), throwing
1126
+ * {@link GislConfigError}:
1127
+ * 0. empty batch → `no_recipes` (checked first).
1128
+ * PASS 1 (structural, ALL entries in order):
1129
+ * - a KNOWN multi-input builder (checked FIRST — they do NOT extend
1130
+ * {@link Recipe}, so the not-a-Recipe catch-all would otherwise misreport
1131
+ * them as plain type errors) → `multi_input_recipe_unsupported`;
1132
+ * - a non-{@link Recipe} entry → `invalid_recipe`;
1133
+ * - a missing/empty key → `missing_key`;
1134
+ * - a duplicate key → `duplicate_key`.
1135
+ * PASS 2 (lowering preflight, ALL entries): lower each entry (via
1136
+ * {@link Recipe.toWorkflowPayload}) so an invalid lowering throws BEFORE any
1137
+ * upload, mirroring what {@link FilesRecipe} lowers pre-create.
1138
+ *
1139
+ * Two passes so a batch with MULTIPLE distinct violations throws the SAME
1140
+ * reason regardless of entry order (a structural error anywhere wins over a
1141
+ * lowering error elsewhere) — converging TS + PHP error reporting. The
1142
+ * offending key/index rides the MESSAGE (not `conflictingFields`, which is
1143
+ * reserved for wire FIELD names).
1144
+ */
1145
+ private validatePreUpload;
1146
+ /**
1147
+ * Collapse the entry inputs to a first-appearance-unique list for cross-entry
1148
+ * upload dedupe: two entries sourcing the SAME input (by {@link inputIdentity})
1149
+ * upload ONCE and share the fileId. Returns the ordered `uniqueInputs` plus an
1150
+ * `entryToUnique` index map (length N, entry order) so {@link run} can expand
1151
+ * the unique fileIds back to one-per-entry before {@link toWorkflowPayload} —
1152
+ * keeping the b{i} refs + {@link keyByRef} N-length and correctness-neutral.
1153
+ */
1154
+ private planUploads;
1155
+ /** Map each `b{i}` job ref to that entry's caller key (validated non-empty). */
1156
+ private keyByRef;
1157
+ }
971
1158
  export {};