@giveitsmaller/sdk 0.15.0 → 0.17.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.
@@ -41,7 +41,7 @@ export { DocumentOfficeCompressPresetOptions, } from './document_office_compress
41
41
  export { DocumentOdfCompressPresetOptions, } from './document_odf_compress.js';
42
42
  export { DocumentEpubCompressPresetOptions, } from './document_epub_compress.js';
43
43
  // Re-export ergonomic enums for callers (single canonical path).
44
- export { OptimizeFor, ImageMode, ImageMetadataPolicy, IccProfilePolicy, ImageFormat, VideoCodec, VideoPreset, VideoFit, AudioBitrate, AudioCodec, AudioSampleRate, PdfProfile, PdfColorspace, } from '../../generated/sdk_spec/enums.js';
44
+ export { OptimizeFor, ImageMetadataPolicy, ImageFormat, VideoCodec, VideoPreset, VideoFit, AudioBitrate, AudioCodec, AudioSampleRate, PdfProfile, PdfColorspace, } from '../../generated/sdk_spec/enums.js';
45
45
  function cellKeyOf(media, op) {
46
46
  return `${media}_${op}`;
47
47
  }
@@ -55,8 +55,8 @@ function cellKeyOf(media, op) {
55
55
  *
56
56
  * `definedFieldsOf` filters undefined values out of each instance
57
57
  * BEFORE the merge: with TS `useDefineForClassFields` (the ES2022
58
- * default), `readonly mode?: ImageMode` declarations initialise the
59
- * field as an enumerable own property with value `undefined` BEFORE
58
+ * default), `readonly outputFormat?: ImageFormat` declarations initialise
59
+ * the field as an enumerable own property with value `undefined` BEFORE
60
60
  * the ctor body runs. A naive `Object.assign({}, parent, child)`
61
61
  * therefore lets child's `undefined` overwrite parent's defined value
62
62
  * — caught by CI on PR #125 first run. Filter-then-spread restores
@@ -64,7 +64,7 @@ function cellKeyOf(media, op) {
64
64
  *
65
65
  * @internal
66
66
  */
67
- function definedFieldsOf(opts) {
67
+ export function definedFieldsOf(opts) {
68
68
  const out = {};
69
69
  for (const key of Object.keys(opts)) {
70
70
  const value = opts[key];
package/dist/errors.d.ts CHANGED
@@ -461,3 +461,28 @@ export declare class GislSinkError extends GislError {
461
461
  readonly reason: GislSinkErrorReason;
462
462
  });
463
463
  }
464
+ /**
465
+ * A terminal item failure in {@link RunResult.failed} — an input whose job did
466
+ * not reach `completed`. Stored in `ItemFailure.error` so a caller can branch on
467
+ * the failure reason WITHOUT string-parsing.
468
+ *
469
+ * - `state`: the terminal lifecycle state (`failed` / `expired` / `cancelled` /
470
+ * `partially_failed` / `paused_insufficient_credits`, or a per-job
471
+ * non-`completed` status).
472
+ * - `errorMessage` / `errorCode`: the human + machine fields read from the first
473
+ * failing operation (`OperationResponse.error_message` / `.error_code`). BOTH
474
+ * are absent for non-`failed` terminal states — cancel / expire / credit-pause
475
+ * carry only the bare `state`.
476
+ *
477
+ * `message` is `state` optionally suffixed `: errorMessage`, preserving the
478
+ * pre-typed string exactly (an empty-string `errorMessage` still adds the colon).
479
+ *
480
+ * Mirrors the PHP `Gisl\Sdk\Errors\GislItemFailedError`.
481
+ */
482
+ export declare class GislItemFailedError extends GislError {
483
+ readonly key: string | null;
484
+ readonly state: string;
485
+ readonly errorMessage?: string;
486
+ readonly errorCode?: string;
487
+ constructor(key: string | null, state: string, errorMessage?: string, errorCode?: string);
488
+ }
package/dist/errors.js CHANGED
@@ -503,3 +503,37 @@ export class GislSinkError extends GislError {
503
503
  this.reason = options.reason;
504
504
  }
505
505
  }
506
+ /**
507
+ * A terminal item failure in {@link RunResult.failed} — an input whose job did
508
+ * not reach `completed`. Stored in `ItemFailure.error` so a caller can branch on
509
+ * the failure reason WITHOUT string-parsing.
510
+ *
511
+ * - `state`: the terminal lifecycle state (`failed` / `expired` / `cancelled` /
512
+ * `partially_failed` / `paused_insufficient_credits`, or a per-job
513
+ * non-`completed` status).
514
+ * - `errorMessage` / `errorCode`: the human + machine fields read from the first
515
+ * failing operation (`OperationResponse.error_message` / `.error_code`). BOTH
516
+ * are absent for non-`failed` terminal states — cancel / expire / credit-pause
517
+ * carry only the bare `state`.
518
+ *
519
+ * `message` is `state` optionally suffixed `: errorMessage`, preserving the
520
+ * pre-typed string exactly (an empty-string `errorMessage` still adds the colon).
521
+ *
522
+ * Mirrors the PHP `Gisl\Sdk\Errors\GislItemFailedError`.
523
+ */
524
+ export class GislItemFailedError extends GislError {
525
+ key;
526
+ state;
527
+ errorMessage;
528
+ errorCode;
529
+ constructor(key, state, errorMessage, errorCode) {
530
+ super(state + (errorMessage !== undefined ? `: ${errorMessage}` : ''));
531
+ this.name = 'GislItemFailedError';
532
+ this.key = key;
533
+ this.state = state;
534
+ if (errorMessage !== undefined)
535
+ this.errorMessage = errorMessage;
536
+ if (errorCode !== undefined)
537
+ this.errorCode = errorCode;
538
+ }
539
+ }
@@ -9,9 +9,11 @@
9
9
  *
10
10
  * Mirrors `packages/php/src/FileFirst/*`.
11
11
  */
12
+ import { GislItemFailedError } from './errors.js';
12
13
  import { type ProgressEvent } from './builder.js';
13
14
  import type { GislClient } from './client.js';
14
15
  import type { OperationDownload, WorkflowStatusResponse } from '@giveitsmaller/contracts/openapi';
16
+ import type { ConvertOptions, ThumbnailOptions, TextWatermarkOptions, WatermarkOptions, OutputOptions, OutputFit } from './ergonomic/option_types.js';
15
17
  import { OptimizeFor } from './generated/sdk_spec/enums.js';
16
18
  import type { PresetDefaults } from './ergonomic/presets/index.js';
17
19
  import type { WorkflowCreatePayload } from './types.js';
@@ -66,15 +68,16 @@ export interface ItemResult {
66
68
  readonly outputs: readonly OutputFile[];
67
69
  }
68
70
  /**
69
- * One failed entry in {@link RunResult.failed}: an input that did not
70
- * produce a deliverable, paired with the cause. One bad input does not sink
71
- * the rest of a multi-input run. `error` is `unknown` (mirroring the PHP
72
- * `\Throwable`) so the caller narrows with `instanceof`. Mirrors the PHP
73
- * `ItemFailure`.
71
+ * One failed entry in {@link RunResult.failed}: an input that did not produce a
72
+ * deliverable, paired with the cause. One bad input does not sink the rest of a
73
+ * multi-input run. `error` is a typed {@link GislItemFailedError} carrying the
74
+ * terminal `state` plus the failing operation's `errorMessage`/`errorCode` (when
75
+ * present), so the caller can branch on the failure WITHOUT string-parsing.
76
+ * Mirrors the PHP `ItemFailure`.
74
77
  */
75
78
  export interface ItemFailure {
76
79
  readonly key: string | null;
77
- readonly error: unknown;
80
+ readonly error: GislItemFailedError;
78
81
  }
79
82
  /**
80
83
  * Return value of {@link RunResult.downloadTo} — the local paths written,
@@ -159,27 +162,13 @@ export declare class RunResult {
159
162
  failed: readonly {
160
163
  key: string | null;
161
164
  error: string;
165
+ state: string;
166
+ errorMessage?: string;
167
+ errorCode?: string;
162
168
  }[];
163
169
  };
164
170
  private requireDownloader;
165
171
  }
166
- /**
167
- * Flatten the terminal workflow status + its downloads into a {@link RunResult}.
168
- *
169
- * Shared by {@link Recipe.run} (passes its recipe key) and the file-first
170
- * {@link Handle} reattach surface (`Handle.wait()`/`Handle.result()`, FF5a —
171
- * passes `null` because a reattached handle carries no recipe key).
172
- *
173
- * **Partition invariant (carries a prior codex-review fix — do NOT let it
174
- * drift):** success is ONLY `state === 'completed'`. Every other terminal
175
- * state — `failed`, `partially_failed`, `cancelled`, `expired`,
176
- * `paused_insufficient_credits` — partitions into `failed[]` so a caller's
177
- * `ok`/`succeeded` check can never treat a cancelled/expired/paused run as a
178
- * clean result.
179
- *
180
- * @internal Exported for reuse by the file-first `Handle`; not part of the
181
- * caller-facing fluent surface.
182
- */
183
172
  export declare function projectDownloadsToRunResult(workflowId: string, finalStatus: WorkflowStatusResponse, jobDownloads: readonly {
184
173
  files: readonly OperationDownload[];
185
174
  }[], key: string | null, downloader?: Downloader): RunResult;
@@ -249,6 +238,19 @@ export declare function isMergeStatus(finalStatus: WorkflowStatusResponse): bool
249
238
  * @internal Exported for the file-first `Handle`; not part of the public API.
250
239
  */
251
240
  export declare function isArchiveStatus(finalStatus: WorkflowStatusResponse): boolean;
241
+ /**
242
+ * True when a terminal status describes a fluent `file(...).watermark(overlay)`
243
+ * — at least one job ref `watermark` and every OTHER job ref is `src_{i}` (the
244
+ * ids the {@link WatermarkedRecipe} lowering assigns: `src_0` base, `src_1`
245
+ * overlay). Lets {@link Handle.wait}/{@link Handle.result} AND
246
+ * {@link WatermarkedRecipe.run} project ONLY the watermark output — filtering
247
+ * the `src_*` passthrough plumbing — even after a `client.workflow(id)` reattach.
248
+ * Mutually exclusive with {@link isFanoutStatus} / {@link isMergeStatus} /
249
+ * {@link isArchiveStatus}.
250
+ *
251
+ * @internal Exported for the file-first `Handle`; not part of the public API.
252
+ */
253
+ export declare function isWatermarkStatus(finalStatus: WorkflowStatusResponse): boolean;
252
254
  /**
253
255
  * The primary file a {@link Recipe} operates on — the "subject" of the
254
256
  * file-first surface. A discriminated union over the ways a caller names an
@@ -293,7 +295,7 @@ export declare const fileInput: {
293
295
  };
294
296
  /** One step in a {@link Recipe}'s chain — an op kind + captured ergonomic args. */
295
297
  interface RecipeStep {
296
- readonly opType: 'compress' | 'convert' | 'thumbnail' | 'text_watermark';
298
+ readonly opType: 'compress' | 'convert' | 'thumbnail' | 'text_watermark' | 'output';
297
299
  readonly options: Readonly<Record<string, unknown>>;
298
300
  }
299
301
  /**
@@ -339,22 +341,55 @@ export declare class Recipe {
339
341
  * option (the convert op's wire key per the contract); `options` carries any
340
342
  * additional per-op convert options.
341
343
  */
342
- convert(format: string, options?: Record<string, unknown>): Recipe;
344
+ convert(format: string, options?: ConvertOptions): Recipe;
343
345
  /**
344
- * Generate a preview. Width and/or height in pixels; any additional per-op
345
- * thumbnail options pass through. An omitted (`undefined`) value is dropped
346
- * from the wire options (not sent as `undefined`).
346
+ * Generate a preview / resize. `width` AND `height` are required (the contract
347
+ * marks both required for image/video/document); any additional per-op
348
+ * thumbnail option passes through. An omitted (`undefined`) optional value is
349
+ * dropped from the wire options (not sent as `undefined`).
347
350
  */
348
- thumbnail(options?: {
349
- width?: number;
350
- height?: number;
351
- } & Record<string, unknown>): Recipe;
351
+ thumbnail(options: ThumbnailOptions): Recipe;
352
+ /**
353
+ * Produce ONE transformed image: keep or change format, plus quality, resize
354
+ * and route-honored controls. The single user-facing image transform — the SDK
355
+ * resolves the route from `(input format, output_format)` against the contract's
356
+ * image-output-routes projection and lowers to that route's wire op:
357
+ * same-format → `compress` (optimiser, `output_format: 'original'`), format-change
358
+ * → `convert` (transcoder, `output_format: <fmt>`). Only options the resolved
359
+ * route honors are sent; a planned or not-honored option throws BEFORE upload.
360
+ * Resize (`width`/`height`/`fit`, via `options` or {@link resize}) stays on the
361
+ * SAME op — one output, never a separate thumbnail.
362
+ *
363
+ * `format` omitted → keep the input format (same-format optimiser route).
364
+ */
365
+ output(format?: string, options?: OutputOptions): Recipe;
366
+ /**
367
+ * Resize as part of the Output transform. Merges `width`/`height`/`fit` into the
368
+ * PRECEDING `output()` step (one artifact); if no Output step precedes, appends a
369
+ * same-format Output step carrying the resize. Never emits a `thumbnail` op.
370
+ * `height` is optional — width-only resize preserves aspect ratio. Resize is
371
+ * raster-only (e.g. an SVG input has no resize on its route → throws at lower).
372
+ */
373
+ resize(width: number, height?: number, fit?: OutputFit): Recipe;
352
374
  /**
353
375
  * Apply a text watermark. Single-input (the text is an option, not a
354
376
  * secondary file) — lowers to the `text_watermark` op with a `text` option;
355
377
  * `options` carries any additional per-op watermark options.
356
378
  */
357
- textWatermark(text: string, options?: Record<string, unknown>): Recipe;
379
+ textWatermark(text: string, options?: TextWatermarkOptions): Recipe;
380
+ /**
381
+ * Composite an image OVERLAY onto this file (a multi-input op). `overlay` is a
382
+ * secondary file-NODE (a {@link Recipe} — e.g. `client.file('logo.png')`),
383
+ * itself optionally processed first. Routes by THIS file's effective media:
384
+ * image base → `image_watermark` (stable), video base → `video_watermark`
385
+ * (beta). Audio/document/animated-GIF/unsupported-subtype/undetectable bases
386
+ * throw locally BEFORE any upload (the planned-op gate). `options` carries the
387
+ * wire watermark options (`anchor`, `opacity`, `margin_x`, `margin_y`,
388
+ * `overlay_width`). Returns a {@link WatermarkedRecipe} (chain post-watermark
389
+ * `compress`/`convert`/`thumbnail`, then `run`/`submit`). Distinct from
390
+ * {@link textWatermark} (single-input text overlay).
391
+ */
392
+ watermark(overlay: Recipe, options?: WatermarkOptions): WatermarkedRecipe;
358
393
  /**
359
394
  * Lower this recipe to a workflow-create payload against a resolved upload
360
395
  * id. Single-input chain → ONE job, `source: upload(fileId)`, ordered
@@ -380,6 +415,13 @@ export declare class Recipe {
380
415
  * @internal
381
416
  */
382
417
  get recipeSteps(): readonly RecipeStep[];
418
+ /**
419
+ * The primary input this recipe operates on. Read by {@link WatermarkedRecipe}
420
+ * to lift an overlay Recipe's input (for upload + media inference + src-job
421
+ * lowering) without making the ctor field public.
422
+ * @internal
423
+ */
424
+ get recipeInput(): FileInput;
383
425
  /**
384
426
  * Execute the recipe end-to-end: upload the input (when required), create
385
427
  * the workflow, await a terminal state (SSE with poll fallback), then
@@ -432,10 +474,78 @@ export declare class Recipe {
432
474
  private _uploadAndCreate;
433
475
  private withStep;
434
476
  private lowerStep;
477
+ /**
478
+ * Lower an `output` step to its route's wire op. Resolves the route from the
479
+ * (chain-folded) input format token + the requested `output_format`, then emits
480
+ * `compress` (same_format) or `convert` (format_change) carrying only the
481
+ * route-honored options. A planned option (e.g. `lossless`), an option not
482
+ * honored on the resolved route (e.g. `progressive` on a format-change), a
483
+ * planned per-value (e.g. `metadata: 'keep'`), or an unrepresentable route all
484
+ * throw a typed {@link GislConfigError} BEFORE upload. Resize (`width`/`height`/
485
+ * `fit`) is input-keyed (raster only) and rides whichever op the route selects.
486
+ */
487
+ private lowerOutputStep;
488
+ /**
489
+ * The input format token an `output` step at `uptoIndex` operates on — the
490
+ * original input's token, FOLDED through preceding `convert`/`output` steps that
491
+ * change the format (mirrors {@link compressMediaHint}). Undefined when the input
492
+ * media is not inferable (a bare upload id / unnamed, untyped Blob).
493
+ */
494
+ private outputInputToken;
495
+ /** The original input's image format token (path ext / Blob type / Blob name). */
496
+ private inputFormatToken;
435
497
  private lowerCompressOptions;
498
+ /** Media of the original input (no chain context) — used by the probe gate. */
499
+ private inputMedia;
500
+ /**
501
+ * The media class a `compress` step at `uptoIndex` actually operates on. With no
502
+ * chain context (`uptoIndex` undefined) this is the original input's media. With
503
+ * context, FOLD the preceding `convert` steps: each `convert(output_format)` changes
504
+ * the media the next step sees (56N4chXY / N8eESzQN — a chain like
505
+ * `mp3 -> convert(flac) -> compress` must resolve against flac, not mp3). Reuses the
506
+ * synthetic-filename detection precedent from {@link MergedRecipe} (`merged.<ext>`).
507
+ */
436
508
  private compressMediaHint;
509
+ /**
510
+ * Whether the media a `compress` step at `uptoIndex` operates on is lossless audio.
511
+ * Determined by the most recent preceding `convert` target (`flac`/`wav` -> lossless)
512
+ * when there is one, else by the original input. Lossless is unaffected by the
513
+ * video/ogg guard (ogg is never lossless either way).
514
+ */
437
515
  private compressAudioLossless;
438
516
  }
517
+ /**
518
+ * The single SDK-side source of truth for which `(wire op, base mime)`
519
+ * combinations the file-first `watermark()` verb may emit, and their
520
+ * availability. The generated typed metadata sidecar does NOT carry the
521
+ * supported-mime allowlist (`MimeGroupMetadata` has no `mimes` field and
522
+ * `per_mime_availability` is empty for these ops), so this hand table is the
523
+ * gate's source — PINNED to the generated `availability.json` by a conformance
524
+ * test (mirrors the wire-key-conformance pattern): a contract regen that
525
+ * changes the supported mimes or availability of `image_watermark` /
526
+ * `video_watermark` fails that test. The gate reads ONLY this table.
527
+ * @internal
528
+ */
529
+ export declare const WATERMARK_CAPABILITY: {
530
+ readonly image_watermark: {
531
+ readonly image: {
532
+ readonly mimes: readonly ["image/jpeg", "image/png", "image/webp"];
533
+ readonly availability: "stable";
534
+ };
535
+ readonly image_gif: {
536
+ readonly mimes: readonly ["image/gif"];
537
+ readonly availability: "planned";
538
+ };
539
+ };
540
+ readonly video_watermark: {
541
+ readonly video: {
542
+ readonly mimes: readonly ["video/mp4", "video/webm"];
543
+ readonly availability: "beta";
544
+ };
545
+ };
546
+ };
547
+ /** Wire op types the file-first `watermark()` verb can route to. */
548
+ export type WatermarkWireOp = 'image_watermark' | 'video_watermark';
439
549
  /**
440
550
  * The homogeneous fan-out builder value (FF3a). `client.files([a, b, c])`
441
551
  * returns a `FilesRecipe`; the op-chain methods (`compress`, `convert`,
@@ -474,15 +584,12 @@ export declare class FilesRecipe {
474
584
  * lowering builds an internal Recipe that throws the same `GislConfigError`.
475
585
  */
476
586
  compress(optimize?: OptimizeFor, options?: Record<string, unknown>): FilesRecipe;
477
- /** Change every input's format. `format` lowers to the contract `output_format` wire key (via {@link Recipe.convert}), NOT `format`. */
478
- convert(format: string, options?: Record<string, unknown>): FilesRecipe;
479
- /** Generate a preview of every input. Omitted dimensions are dropped from the wire options. */
480
- thumbnail(options?: {
481
- width?: number;
482
- height?: number;
483
- } & Record<string, unknown>): FilesRecipe;
484
- /** Apply the same text watermark to every input. */
485
- textWatermark(text: string, options?: Record<string, unknown>): FilesRecipe;
587
+ /** Change every input's format. `format` lowers to the contract `output_format` wire key (via {@link Recipe.convert}), NOT `format`. Option keys are validated (via the base {@link Recipe}) before any upload. */
588
+ convert(format: string, options?: ConvertOptions): FilesRecipe;
589
+ /** Generate a preview of every input. `width` AND `height` are required; validated via the base {@link Recipe} before any upload. */
590
+ thumbnail(options: ThumbnailOptions): FilesRecipe;
591
+ /** Apply the same text watermark to every input. Option keys validated via the base {@link Recipe}. */
592
+ textWatermark(text: string, options?: TextWatermarkOptions): FilesRecipe;
486
593
  /**
487
594
  * Combine the inputs into ONE output (N→1), in array order (FF3b). Returns a
488
595
  * single-output {@link MergedRecipe} you chain further ops on
@@ -613,13 +720,10 @@ export declare class MergedRecipe {
613
720
  constructor(inputs: readonly FileInput[], mergeOptions: MergeOptions, postSteps?: readonly RecipeStep[], presetDefaults?: PresetDefaults | undefined, scopedPresetDefaults?: PresetDefaults | undefined, client?: GislClient | undefined);
614
721
  /** Reduce the merged output's size. See {@link Recipe.compress}. */
615
722
  compress(optimize?: OptimizeFor, options?: Record<string, unknown>): MergedRecipe;
616
- /** Change the merged output's format. See {@link Recipe.convert}. */
617
- convert(format: string, options?: Record<string, unknown>): MergedRecipe;
618
- /** Thumbnail the merged output. Omitted dimensions are dropped from the wire options. */
619
- thumbnail(options?: {
620
- width?: number;
621
- height?: number;
622
- } & Record<string, unknown>): MergedRecipe;
723
+ /** Change the merged output's format. See {@link Recipe.convert}. Option keys validated pre-upload. */
724
+ convert(format: string, options?: ConvertOptions): MergedRecipe;
725
+ /** Thumbnail the merged output. `width` AND `height` are required; validated pre-upload. */
726
+ thumbnail(options: ThumbnailOptions): MergedRecipe;
623
727
  /**
624
728
  * Lower to the merge DAG: one `passthrough` source job per input + one
625
729
  * `merge` job whose `operations[]` is `[merge, ...post-combine ops]`. The
@@ -776,4 +880,92 @@ export declare class ArchivedRecipe {
776
880
  */
777
881
  private wireArchiveOptions;
778
882
  }
883
+ /**
884
+ * The single-output recipe you're in AFTER `file(base).watermark(overlay, …)`
885
+ * (FF4a). Composites an image OVERLAY onto the base (image_watermark for image
886
+ * bases, video_watermark for video bases — routed at lowering by the base's
887
+ * effective media). A multi-input op: base + overlay each enter via their own
888
+ * `passthrough` source job (`src_0` base, `src_1` overlay; their own preceding
889
+ * steps lower into those jobs), and the `watermark` job consumes them via
890
+ * `job_output` inputs tagged `role: base` / `role: overlay`. Post-watermark
891
+ * `compress`/`convert`/`thumbnail` chain onto the watermark output. Mirrors
892
+ * {@link MergedRecipe}. `textWatermark` is intentionally NOT a post-verb here.
893
+ */
894
+ export declare class WatermarkedRecipe {
895
+ private readonly baseInput;
896
+ private readonly baseSteps;
897
+ private readonly overlay;
898
+ private readonly watermarkOptions;
899
+ private readonly postSteps;
900
+ private readonly presetDefaults?;
901
+ private readonly scopedPresetDefaults?;
902
+ private readonly client?;
903
+ constructor(baseInput: FileInput, baseSteps: readonly RecipeStep[], overlay: Recipe, watermarkOptions: WatermarkOptions, postSteps?: readonly RecipeStep[], presetDefaults?: PresetDefaults | undefined, scopedPresetDefaults?: PresetDefaults | undefined, client?: GislClient | undefined);
904
+ /** Reduce the watermarked output's size. See {@link Recipe.compress}. */
905
+ compress(optimize?: OptimizeFor, options?: Record<string, unknown>): WatermarkedRecipe;
906
+ /** Change the watermarked output's format. See {@link Recipe.convert}. Option keys validated pre-upload. */
907
+ convert(format: string, options?: ConvertOptions): WatermarkedRecipe;
908
+ /** Thumbnail the watermarked output. `width` AND `height` are required; validated pre-upload. */
909
+ thumbnail(options: ThumbnailOptions): WatermarkedRecipe;
910
+ /**
911
+ * Lower to the watermark DAG: a `src_0` passthrough/base-steps job + a `src_1`
912
+ * passthrough/overlay-steps job + one `watermark` job whose `inputs[]` consume
913
+ * them via `job_output` (role base/overlay) and whose `operations[]` is
914
+ * `[image_watermark|video_watermark, ...post-watermark ops]`. `fileIds` is
915
+ * `[baseId, overlayId]` (upload order). Throws pre-lowering if the base media
916
+ * is undetectable/unsupported (the planned-op gate).
917
+ *
918
+ * @internal Consumed by {@link run}/{@link submit} (after upload) + the parity harness.
919
+ */
920
+ toWorkflowPayload(fileIds: readonly string[], callbackUrl?: string): WorkflowCreatePayload;
921
+ /** The number of post-watermark ops chained so far (introspection / tests). */
922
+ get stepCount(): number;
923
+ /**
924
+ * Execute end-to-end: upload base + overlay, create the watermark workflow,
925
+ * await terminal (SSE with poll fallback), then resolve ONLY the watermark
926
+ * output into a {@link RunResult}. Requires a client bound at construction.
927
+ * Mirrors {@link MergedRecipe.run}.
928
+ */
929
+ run(options?: {
930
+ maxWait?: string | number;
931
+ onProgress?: (event: ProgressEvent) => void;
932
+ signal?: AbortSignal;
933
+ pollIntervalMs?: number;
934
+ probeBeforeCreate?: boolean;
935
+ probeTimeoutMs?: number;
936
+ }): Promise<RunResult>;
937
+ /**
938
+ * Fire-and-forget: upload base + overlay + create the watermark workflow
939
+ * (wiring `webhook` into `callback_url` when given), return a client-bound
940
+ * {@link Handle}. Does NOT wait for terminal status. Mirrors {@link MergedRecipe.submit}.
941
+ */
942
+ submit(webhook?: string, options?: {
943
+ probeBeforeCreate?: boolean;
944
+ probeTimeoutMs?: number;
945
+ }): Promise<Handle>;
946
+ /** Base + overlay inputs, in upload/lowering order (`[base, overlay]`). */
947
+ private inputsInOrder;
948
+ /**
949
+ * Validate the watermark BEFORE any upload: the base must route to a shippable
950
+ * wire op (throws for undetectable/unsupported/planned bases), and the overlay
951
+ * must be an image. Shared by {@link run}/{@link submit}. Mirrors
952
+ * {@link MergedRecipe.validatePreUpload}.
953
+ */
954
+ private validatePreUpload;
955
+ /**
956
+ * Upload base + overlay (verbatim for a pre-uploaded id; uploading a path /
957
+ * blob otherwise) then create ONE watermark workflow. Validates pre-upload.
958
+ * Shared first half of {@link run} + {@link submit}; mirrors
959
+ * {@link MergedRecipe._uploadAllAndCreate}.
960
+ */
961
+ private _uploadAllAndCreate;
962
+ /**
963
+ * Lower the post-watermark chain over a synthetic input whose extension
964
+ * matches the watermark OUTPUT media (image→png, video→mp4) so
965
+ * `compress(optimize)` resolves the correct preset — mirrors
966
+ * {@link MergedRecipe.lowerPostSteps}.
967
+ */
968
+ private lowerPostSteps;
969
+ private withStep;
970
+ }
779
971
  export {};