@giveitsmaller/sdk 0.8.0 → 0.10.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 (46) hide show
  1. package/README.md +8 -0
  2. package/dist/_audit.js +5 -1
  3. package/dist/builder.d.ts +1 -8
  4. package/dist/builder.js +72 -18
  5. package/dist/client.d.ts +38 -2
  6. package/dist/client.js +131 -7
  7. package/dist/credentials.js +4 -2
  8. package/dist/ergonomic/preset_resolver.js +4 -5
  9. package/dist/ergonomic/presets/image_compress.d.ts +1 -9
  10. package/dist/ergonomic/presets/image_compress.js +6 -25
  11. package/dist/ergonomic/presets/index.d.ts +1 -1
  12. package/dist/ergonomic/presets/index.js +1 -1
  13. package/dist/errors.d.ts +75 -1
  14. package/dist/errors.js +73 -0
  15. package/dist/file-first.d.ts +456 -4
  16. package/dist/file-first.js +1042 -83
  17. package/dist/generated/sdk_spec/enums.d.ts +0 -11
  18. package/dist/generated/sdk_spec/enums.js +0 -7
  19. package/dist/generated/sdk_spec/errors.d.ts +1 -1
  20. package/dist/generated/sdk_spec/errors.js +26 -0
  21. package/dist/generated/sdk_spec/presets.js +0 -3
  22. package/dist/generated/sdk_spec/version.d.ts +2 -2
  23. package/dist/generated/sdk_spec/version.js +2 -2
  24. package/dist/gisl.d.ts +22 -1
  25. package/dist/gisl.js +31 -1
  26. package/dist/handle.d.ts +153 -0
  27. package/dist/handle.js +273 -0
  28. package/dist/index.browser.d.ts +1 -0
  29. package/dist/index.browser.js +14 -0
  30. package/dist/index.core.d.ts +35 -0
  31. package/dist/index.core.js +102 -0
  32. package/dist/index.d.ts +1 -30
  33. package/dist/index.js +9 -73
  34. package/dist/lazy-downloader.d.ts +19 -0
  35. package/dist/lazy-downloader.js +19 -0
  36. package/dist/merge.d.ts +13 -1
  37. package/dist/merge.js +186 -55
  38. package/dist/node-fs.browser.d.ts +17 -0
  39. package/dist/node-fs.browser.js +7 -0
  40. package/dist/node-fs.d.ts +14 -0
  41. package/dist/node-fs.js +14 -0
  42. package/dist/sha256.d.ts +20 -0
  43. package/dist/sha256.js +108 -0
  44. package/dist/types.d.ts +54 -2
  45. package/dist/types.js +2 -0
  46. package/package.json +15 -2
@@ -11,9 +11,12 @@
11
11
  */
12
12
  import { type ProgressEvent } from './builder.js';
13
13
  import type { GislClient } from './client.js';
14
+ import type { OperationDownload, WorkflowStatusResponse } from '@giveitsmaller/contracts/openapi';
14
15
  import { OptimizeFor } from './generated/sdk_spec/enums.js';
15
16
  import type { PresetDefaults } from './ergonomic/presets/index.js';
16
17
  import type { WorkflowCreatePayload } from './types.js';
18
+ import type { MergeOptions } from './merge.js';
19
+ import { Handle } from './handle.js';
17
20
  /**
18
21
  * Streams a single output URL to a local path. The seam between the
19
22
  * file-first {@link RunResult} sinks and the SDK's HTTP/auth layer.
@@ -160,6 +163,92 @@ export declare class RunResult {
160
163
  };
161
164
  private requireDownloader;
162
165
  }
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
+ export declare function projectDownloadsToRunResult(workflowId: string, finalStatus: WorkflowStatusResponse, jobDownloads: readonly {
184
+ files: readonly OperationDownload[];
185
+ }[], key: string | null, downloader?: Downloader): RunResult;
186
+ /**
187
+ * Flatten a terminal multi-job workflow (the `client.files([...])` fan-out)
188
+ * into a partitioned {@link RunResult}. One job per input file, keyed by the
189
+ * `file-{i}` job ref the {@link FilesRecipe} lowering assigns; the result's
190
+ * `succeeded` / `failed` partition is PER JOB, so one bad input does not sink
191
+ * the rest.
192
+ *
193
+ * Join model: `finalStatus.jobs[]` carries the per-job {@link JobStatus} +
194
+ * `operations[]` (for the error message); `jobDownloads[]` carries the per-job
195
+ * output files. Both are joined on the job `ref` ("file-{i}"); the partition
196
+ * key is the index `"{i}"` parsed out of that ref. The flat `artifacts[]` is
197
+ * every job's outputs in job order (the order `finalStatus.jobs[]` lists them).
198
+ *
199
+ * **Partition invariant (mirrors {@link projectDownloadsToRunResult} PER JOB —
200
+ * do NOT let it drift):** a job is a SUCCESS only when its
201
+ * {@link JobResponse.status} `=== 'completed'`. Any other per-job status —
202
+ * `failed`, `pending`, `waiting`, `blocked_insufficient_credits`,
203
+ * `in_progress` — partitions that job into `failed[]` (with that job's first
204
+ * operation error message, scoped to THAT job only).
205
+ *
206
+ * @internal Exported for the file-first `client.files([...]).run()` producer;
207
+ * not part of the caller-facing fluent surface.
208
+ */
209
+ export declare function projectMultiJobToRunResult(workflowId: string, finalStatus: WorkflowStatusResponse, jobDownloads: readonly {
210
+ ref: string;
211
+ files: readonly OperationDownload[];
212
+ }[], keyByRef: ReadonlyMap<string, string | null>, downloader?: Downloader): RunResult;
213
+ /**
214
+ * True when a terminal status describes a homogeneous `files([...])` fan-out —
215
+ * i.e. it has at least one job and EVERY job ref is `file-{i}` (the ids the
216
+ * {@link FilesRecipe} lowering assigns). A single-file {@link Recipe} omits the
217
+ * job id, so its job carries a non-`file-N` ref (e.g. `op`) and this is false.
218
+ *
219
+ * This is the data-driven seam that lets {@link Handle.wait}/{@link Handle.result}
220
+ * pick the per-job producer ({@link projectMultiJobToRunResult}) over the
221
+ * single-output one for a fan-out — WITHOUT a construction-time marker, so a
222
+ * fan-out **reattached** via `client.workflow(id)` (which carries no marker)
223
+ * still partitions per job. Keys are recovered from the `file-{i}` refs.
224
+ *
225
+ * @internal Exported for the file-first `Handle`; not part of the public API.
226
+ */
227
+ export declare function isFanoutStatus(finalStatus: WorkflowStatusResponse): boolean;
228
+ /**
229
+ * True when a terminal status describes a fluent `files([...]).merge(...)`
230
+ * combine — at least one job ref `merge` and every OTHER job ref is `src_{i}`
231
+ * (the ids the {@link MergedRecipe} lowering assigns). The data-driven seam that
232
+ * lets {@link Handle.wait}/{@link Handle.result} project ONLY the merged output
233
+ * — filtering the `src_*` passthrough plumbing — even after a
234
+ * `client.workflow(id)` reattach (no construction-time marker), matching
235
+ * {@link MergedRecipe.run}'s `ref === 'merge'` filter. Mutually exclusive with
236
+ * {@link isFanoutStatus} (a fan-out's refs are all `file-{i}`).
237
+ *
238
+ * @internal Exported for the file-first `Handle`; not part of the public API.
239
+ */
240
+ export declare function isMergeStatus(finalStatus: WorkflowStatusResponse): boolean;
241
+ /**
242
+ * True when a terminal status describes a fluent `files([...]).archive(...)`
243
+ * bundle — at least one job ref `archive` and every OTHER job ref is `src_{i}`
244
+ * (the ids the {@link ArchivedRecipe} lowering assigns). Lets
245
+ * {@link Handle.wait}/{@link Handle.result} project ONLY the archive output —
246
+ * filtering the `src_*` passthrough plumbing — even after a `client.workflow(id)`
247
+ * reattach. Mutually exclusive with {@link isFanoutStatus} / {@link isMergeStatus}.
248
+ *
249
+ * @internal Exported for the file-first `Handle`; not part of the public API.
250
+ */
251
+ export declare function isArchiveStatus(finalStatus: WorkflowStatusResponse): boolean;
163
252
  /**
164
253
  * The primary file a {@link Recipe} operates on — the "subject" of the
165
254
  * file-first surface. A discriminated union over the ways a caller names an
@@ -187,6 +276,19 @@ export type FileInput = {
187
276
  export declare const fileInput: {
188
277
  readonly path: (path: string) => FileInput;
189
278
  readonly blob: (blob: Blob) => FileInput;
279
+ /**
280
+ * Reference an already-uploaded file by its upload id, instead of
281
+ * re-uploading bytes.
282
+ *
283
+ * Auth-ownership: an upload created by an **authenticated** caller is owned
284
+ * by that caller. If you reuse the id from a client configured with a
285
+ * *different* auth context (a different `apiKey` / session), workflow-create
286
+ * returns `404 upload_not_found` — the server enforces ownership (api
287
+ * PqpD9ySv). Reference an upload id only under the SAME auth that created it.
288
+ * The normal upload-then-create-in-one-client flow is consistent by
289
+ * construction (the same `Authorization` rides every request). Ownerless
290
+ * (anonymous-intake) uploads are unaffected.
291
+ */
190
292
  readonly uploadId: (fileId: string) => FileInput;
191
293
  };
192
294
  /** One step in a {@link Recipe}'s chain — an op kind + captured ergonomic args. */
@@ -250,15 +352,25 @@ export declare class Recipe {
250
352
  * `operations[]`; the job `id` is omitted (a single job referenced by
251
353
  * nothing — the server auto-assigns `job_N`).
252
354
  *
253
- * @internal Consumed by FF2b's `run()` (after a real upload) and by the
254
- * cross-language parity harness (with a fixed id). Not part of the
255
- * caller-facing fluent surface.
355
+ * When `callbackUrl` is given (the file-first `submit()` path), it is built
356
+ * INTO the payload at construction (`callback_url`) rather than spread onto an
357
+ * already-built readonly payload. `run()` passes no `callbackUrl`.
358
+ *
359
+ * @internal Consumed by FF2b's `run()` (after a real upload), FF5b's
360
+ * `submit()` (with a webhook), and the cross-language parity harness (with a
361
+ * fixed id). Not part of the caller-facing fluent surface.
256
362
  */
257
- toWorkflowPayload(fileId: string): WorkflowCreatePayload;
363
+ toWorkflowPayload(fileId: string, callbackUrl?: string): WorkflowCreatePayload;
258
364
  /** The result-addressing key passed to `file()`, or undefined. */
259
365
  key(): string | undefined;
260
366
  /** The number of operations chained so far (introspection / tests). */
261
367
  get stepCount(): number;
368
+ /**
369
+ * The captured op chain. Read by {@link FilesRecipe} to compose a shared
370
+ * chain across many inputs without duplicating the chain-method validation.
371
+ * @internal
372
+ */
373
+ get recipeSteps(): readonly RecipeStep[];
262
374
  /**
263
375
  * Execute the recipe end-to-end: upload the input (when required), create
264
376
  * the workflow, await a terminal state (SSE with poll fallback), then
@@ -276,9 +388,349 @@ export declare class Recipe {
276
388
  signal?: AbortSignal;
277
389
  pollIntervalMs?: number;
278
390
  }): Promise<RunResult>;
391
+ /**
392
+ * Fire-and-forget the recipe: upload the input (when required), create the
393
+ * workflow (wiring `webhook` into `callback_url` when given), and return a
394
+ * client-bound {@link Handle} carrying the workflow id + webhook secret + the
395
+ * recipe key. Does NOT wait for terminal status — call `handle.wait()` /
396
+ * `handle.result()` later to collect the {@link RunResult}.
397
+ *
398
+ * Requires a client bound at construction time (same `no_client` guard as
399
+ * {@link run}). `webhook` is OPTIONAL: when omitted, no `callback_url` is
400
+ * sent. Mirrors the PHP `Recipe.submit()`.
401
+ *
402
+ * @param webhook Absolute callback URL the server POSTs lifecycle events to.
403
+ */
404
+ submit(webhook?: string): Promise<Handle>;
405
+ /**
406
+ * Resolve the upload id (verbatim for a pre-uploaded id; uploading a path /
407
+ * blob otherwise, emitting `{phase:'upload'}` progress), check the post-upload
408
+ * deadline, lower to the workflow-create payload (wiring `webhook` into
409
+ * `callback_url`), and create the workflow. Shared first half of
410
+ * {@link run} + {@link submit}.
411
+ *
412
+ * The post-upload deadline check carries a prior codex fix (9a117f04eb59): a
413
+ * slow upload must not proceed to createWorkflow past the deadline.
414
+ */
415
+ private _uploadAndCreate;
279
416
  private withStep;
280
417
  private lowerStep;
281
418
  private lowerCompressOptions;
282
419
  private compressMediaHint;
283
420
  }
421
+ /**
422
+ * The homogeneous fan-out builder value (FF3a). `client.files([a, b, c])`
423
+ * returns a `FilesRecipe`; the op-chain methods (`compress`, `convert`,
424
+ * `thumbnail`, `textWatermark`) build ONE shared recipe (chain) that is applied
425
+ * to EVERY input file in ONE workflow. `run()` returns a partitioned
426
+ * {@link RunResult} — one `succeeded`/`failed` entry per input, keyed by its
427
+ * 0-based index ("0", "1", …) so one bad input does not sink the rest.
428
+ *
429
+ * **Immutable / clone-on-write**, exactly like {@link Recipe}: every op returns
430
+ * a NEW `FilesRecipe` carrying the appended step. The inputs are held as an
431
+ * ORDERED list (NOT a map) so the per-file index is the partition key.
432
+ *
433
+ * **Lowering composes {@link Recipe} per file** rather than duplicating
434
+ * `lowerStep`/`lowerCompressOptions`: for each input `i` it builds an internal
435
+ * single-file `Recipe(input_i, …, steps)`, calls its `toWorkflowPayload` to get
436
+ * that file's one-job payload, then merges all jobs into ONE
437
+ * {@link WorkflowCreatePayload} with `jobs[i].id = "file-{i}"`. This preserves
438
+ * each file's media-hint (different extensions per input resolve compress
439
+ * presets independently).
440
+ *
441
+ * Exposes both `run()` (blocking, returns a partitioned {@link RunResult}) and
442
+ * `submit(webhook?)` (fire-and-forget, returns a {@link Handle}). Mirrors the
443
+ * PHP `FilesRecipe`.
444
+ */
445
+ export declare class FilesRecipe {
446
+ private readonly inputs;
447
+ private readonly steps;
448
+ private readonly presetDefaults?;
449
+ private readonly scopedPresetDefaults?;
450
+ private readonly client?;
451
+ constructor(inputs: readonly FileInput[], steps?: readonly RecipeStep[], presetDefaults?: PresetDefaults | undefined, scopedPresetDefaults?: PresetDefaults | undefined, client?: GislClient | undefined);
452
+ /**
453
+ * Reduce file size on every input. `optimize` selects a per-media preset
454
+ * (resolved per file at lower-time, so each input's extension picks its own
455
+ * preset). Reuses {@link Recipe}'s validation — a directly-constructed
456
+ * lowering builds an internal Recipe that throws the same `GislConfigError`.
457
+ */
458
+ compress(optimize?: OptimizeFor): FilesRecipe;
459
+ /** Change every input's format. `format` lowers verbatim to the `format` option. */
460
+ convert(format: string): FilesRecipe;
461
+ /** Generate a preview of every input. Omitted dimensions are dropped from the wire options. */
462
+ thumbnail(options?: {
463
+ width?: number;
464
+ height?: number;
465
+ }): FilesRecipe;
466
+ /** Apply the same text watermark to every input. */
467
+ textWatermark(text: string): FilesRecipe;
468
+ /**
469
+ * Combine the inputs into ONE output (N→1), in array order (FF3b). Returns a
470
+ * single-output {@link MergedRecipe} you chain further ops on
471
+ * (`files([...]).merge().compress()`). Reuses the operation-first
472
+ * {@link MergeOptions} for the merge-level options, so the wire shape matches
473
+ * `client.merge([...], options)`.
474
+ *
475
+ * `merge()` must be the FIRST op on `files([...])` — per-file ops before a
476
+ * combine (compress-each-then-merge) are a separate follow-up, rejected here
477
+ * with `GislConfigError` reason `pre_merge_ops_unsupported`.
478
+ */
479
+ merge(options?: MergeOptions): MergedRecipe;
480
+ /**
481
+ * Bundle the inputs into ONE archive (N→1, zip / tar.gz) — media-agnostic,
482
+ * inputs may mix types. Returns a terminal {@link ArchivedRecipe} (a zip is
483
+ * the final artefact — no post-bundle chain). `format` / `folderStructure` are
484
+ * optional; the server defaults to zip + flat.
485
+ *
486
+ * `archive()` must be the FIRST op on `files([...])` → `GislConfigError` reason
487
+ * `pre_archive_ops_unsupported` otherwise.
488
+ */
489
+ archive(options?: ArchiveRecipeOptions): ArchivedRecipe;
490
+ /** The number of inputs in this fan-out (introspection / tests). */
491
+ get inputCount(): number;
492
+ /** The number of operations chained so far (introspection / tests). */
493
+ get stepCount(): number;
494
+ /**
495
+ * Lower this fan-out to a single multi-job workflow-create payload against a
496
+ * list of resolved upload ids (one per input, in input order). Each input `i`
497
+ * becomes ONE job with `id = "file-{i}"`, its `source: upload(fileIds[i])`,
498
+ * and the SHARED lowered `operations[]`. Composes the single-file
499
+ * {@link Recipe.toWorkflowPayload} per file so per-file media-hints resolve
500
+ * independently and lowering logic is not duplicated.
501
+ *
502
+ * @internal Consumed by {@link run} (after uploading all inputs) and the
503
+ * cross-language parity harness (with fixed ids). Not caller-facing.
504
+ */
505
+ toWorkflowPayload(fileIds: readonly string[], callbackUrl?: string): WorkflowCreatePayload;
506
+ /**
507
+ * Execute the fan-out end-to-end: upload EVERY input, create ONE workflow
508
+ * with one job per input, await a terminal state (SSE with poll fallback),
509
+ * then resolve the per-job downloads into a partitioned {@link RunResult}.
510
+ * `partially_failed` is a NORMAL terminal state here — its successful jobs
511
+ * land in `succeeded`, its failed jobs in `failed`.
512
+ *
513
+ * Requires a client bound at construction time — `gisl().files(...)` wires
514
+ * it; a directly-constructed `FilesRecipe` throws {@link GislConfigError}.
515
+ * Mirrors the single-file {@link Recipe.run}; see {@link submit} for the
516
+ * fire-and-forget arm.
517
+ */
518
+ run(options?: {
519
+ maxWait?: string | number;
520
+ onProgress?: (event: ProgressEvent) => void;
521
+ signal?: AbortSignal;
522
+ pollIntervalMs?: number;
523
+ }): Promise<RunResult>;
524
+ /**
525
+ * Fire-and-forget the fan-out: upload every input, create ONE multi-job
526
+ * workflow (wiring `webhook` into `callback_url` when given), and return a
527
+ * client-bound {@link Handle}. Does NOT wait for terminal status — call
528
+ * `handle.wait()` / `handle.result()` later to collect the partitioned
529
+ * {@link RunResult}. The Handle detects the fan-out from the wire `file-{i}`
530
+ * job refs, so per-file `byKey()` works even after a `client.workflow(id)`
531
+ * reattach (the keys are the input indices `"0"`, `"1"`, …).
532
+ *
533
+ * Requires a client bound at construction time (same `no_client` guard as
534
+ * {@link run}). `webhook` is OPTIONAL. Fire-and-forget, so NO whole-run
535
+ * deadline (a multi-GB upload is bounded by the HTTP client's own timeout).
536
+ * Mirrors the single-file {@link Recipe.submit}.
537
+ *
538
+ * @param webhook Absolute callback URL the server POSTs lifecycle events to.
539
+ */
540
+ submit(webhook?: string): Promise<Handle>;
541
+ /**
542
+ * Upload every input (verbatim for a pre-uploaded id; uploading a path /
543
+ * blob otherwise, emitting `{phase:'upload'}` progress) then create ONE
544
+ * multi-job workflow (one job per input, `callback_url` built in when
545
+ * `webhook` is given). Shared first half of {@link run} + {@link submit}.
546
+ *
547
+ * Uploads are sequential so progress events stay ordered and the abort
548
+ * signal is honoured promptly; a resource arm is impossible in TS (Blob).
549
+ * `run()` passes a whole-run deadline (a slow upload must not proceed to
550
+ * createWorkflow past maxWait); `submit()` passes `undefined`, so the
551
+ * deadline checks are skipped.
552
+ */
553
+ private _uploadAllAndCreate;
554
+ /**
555
+ * The shared single-file {@link Recipe} that captures the op chain (input is
556
+ * a placeholder — only the steps are read). Reuses Recipe's op-chain
557
+ * validation + coercion so a `FilesRecipe.compress(bad)` throws the identical
558
+ * `GislConfigError` as `Recipe.compress(bad)`.
559
+ */
560
+ private baseRecipe;
561
+ private withStep;
562
+ }
563
+ /**
564
+ * The single-output recipe you're in AFTER a fluent `files([...]).merge(...)`
565
+ * (FF3b). Merge collapses the N inputs into ONE output, so the per-file ops
566
+ * ({@link FilesRecipe.compress} etc.) no longer apply — instead this exposes the
567
+ * SAME chain ops as the single-file {@link Recipe}, applied to the merged
568
+ * result. `files([...]).merge().compress()` is the flagship case (example 14).
569
+ *
570
+ * **Lowering (one workflow):** each input is uploaded once and wrapped in its
571
+ * own single-input `passthrough` source job (`src_N`); the `merge` job consumes
572
+ * those via `job_output` inputs (array order = play order) and carries the merge
573
+ * op FIRST in its `operations[]`, followed by any post-combine ops (compress /
574
+ * convert / thumbnail) so they run on the merged output in the same job. The
575
+ * merge-level wire options reuse {@link wireMergeOptions} so a fluent merge
576
+ * lowers identically to the operation-first `client.merge()`.
577
+ *
578
+ * Immutable / clone-on-write like {@link Recipe} / {@link FilesRecipe}. Mirrors
579
+ * the PHP `MergedRecipe` in `packages/php/src/FileFirst/MergedRecipe.php`.
580
+ */
581
+ export declare class MergedRecipe {
582
+ private readonly inputs;
583
+ private readonly mergeOptions;
584
+ private readonly postSteps;
585
+ private readonly presetDefaults?;
586
+ private readonly scopedPresetDefaults?;
587
+ private readonly client?;
588
+ constructor(inputs: readonly FileInput[], mergeOptions: MergeOptions, postSteps?: readonly RecipeStep[], presetDefaults?: PresetDefaults | undefined, scopedPresetDefaults?: PresetDefaults | undefined, client?: GislClient | undefined);
589
+ /** Reduce the merged output's size. See {@link Recipe.compress}. */
590
+ compress(optimize?: OptimizeFor): MergedRecipe;
591
+ /** Change the merged output's format. See {@link Recipe.convert}. */
592
+ convert(format: string): MergedRecipe;
593
+ /** Thumbnail the merged output. Omitted dimensions are dropped from the wire options. */
594
+ thumbnail(options?: {
595
+ width?: number;
596
+ height?: number;
597
+ }): MergedRecipe;
598
+ /**
599
+ * Lower to the merge DAG: one `passthrough` source job per input + one
600
+ * `merge` job whose `operations[]` is `[merge, ...post-combine ops]`. The
601
+ * merge job's `inputs[]` consume the source jobs via `job_output` in input
602
+ * (play) order.
603
+ *
604
+ * @internal Consumed by {@link run} (after uploading all inputs), {@link submit}
605
+ * (with a webhook), and the cross-language parity harness (with fixed ids).
606
+ */
607
+ toWorkflowPayload(fileIds: readonly string[], callbackUrl?: string): WorkflowCreatePayload;
608
+ /** The number of inputs being combined (introspection / tests). */
609
+ get inputCount(): number;
610
+ /** The number of post-combine ops chained so far (introspection / tests). */
611
+ get stepCount(): number;
612
+ /**
613
+ * Execute end-to-end: upload every input, create the merge workflow, await a
614
+ * terminal state (SSE with poll fallback), then resolve ONLY the merged output
615
+ * into a {@link RunResult}. Throws {@link GislTimeoutError} on `maxWait`.
616
+ *
617
+ * Requires a client bound at construction time — `gisl().files(...).merge(...)`
618
+ * wires it; a directly-constructed `MergedRecipe` throws {@link GislConfigError}.
619
+ * Mirrors the single-file {@link Recipe.run}.
620
+ */
621
+ run(options?: {
622
+ maxWait?: string | number;
623
+ onProgress?: (event: ProgressEvent) => void;
624
+ signal?: AbortSignal;
625
+ pollIntervalMs?: number;
626
+ }): Promise<RunResult>;
627
+ /**
628
+ * Fire-and-forget: upload + create the merge workflow (wiring `webhook` into
629
+ * `callback_url` when given), return a client-bound {@link Handle}. Does NOT
630
+ * wait for terminal status. Mirrors {@link Recipe.submit}.
631
+ */
632
+ submit(webhook?: string): Promise<Handle>;
633
+ /**
634
+ * Upload every input (verbatim for a pre-uploaded id; uploading a path / blob
635
+ * otherwise, emitting `{phase:'upload'}` progress) then create ONE merge
636
+ * workflow. Rejects fewer than 2 inputs BEFORE any upload fires. Shared first
637
+ * half of {@link run} + {@link submit}.
638
+ */
639
+ private _uploadAllAndCreate;
640
+ /**
641
+ * Reject an invalid combine BEFORE any upload fires — mirrors the operation-
642
+ * first `MergeBuilder.planSequence()` bounds so a typo'd merge costs no
643
+ * bandwidth: 2–10 inputs (merge schema `min/max_inputs`), and an image merge
644
+ * must carry an explicit `output_type` (the server rejects image merges
645
+ * without one). Shared by {@link run} + {@link submit} via
646
+ * {@link _uploadAllAndCreate}.
647
+ */
648
+ private validatePreUpload;
649
+ /**
650
+ * Lower the post-combine chain by composing a single-file {@link Recipe} over a
651
+ * synthetic input whose extension matches the merged OUTPUT media — so
652
+ * `compress(optimize)` resolves the correct preset for the merged result (it
653
+ * needs a media hint, which a merge output carries no filename for). Reuses
654
+ * Recipe's `lowerStep` rather than duplicating it.
655
+ */
656
+ private lowerPostSteps;
657
+ /**
658
+ * The merged-output media. Honours an explicit {@link MergeOptions.mediaKind};
659
+ * otherwise infers from the first PATH input's extension (mirrors
660
+ * {@link MergeBuilder}); defaults to video.
661
+ */
662
+ private inferMediaKind;
663
+ private outputExtensionFor;
664
+ private withStep;
665
+ }
666
+ /**
667
+ * Options for a fluent `files([...]).archive(...)` bundle. Both fields are
668
+ * optional — the server defaults `format` to `zip` and `folderStructure` to
669
+ * `flat` (archive op schema). Mirrors the PHP `ArchivedRecipe` ctor params.
670
+ */
671
+ export interface ArchiveRecipeOptions {
672
+ /** Archive container format. */
673
+ readonly format?: 'zip' | 'tar.gz';
674
+ /** `flat` = all files at the top level; `by_job` = a subfolder per source. */
675
+ readonly folderStructure?: 'flat' | 'by_job';
676
+ }
677
+ /**
678
+ * The single-output recipe you're in AFTER a fluent `files([...]).archive(...)`
679
+ * (FF3b). Archive bundles the N inputs into ONE downloadable archive (zip /
680
+ * tar.gz) — media-agnostic, inputs may mix types. Unlike {@link MergedRecipe},
681
+ * archive is TERMINAL: a zip is the final artefact, so there is no post-bundle
682
+ * chain — this exposes only `run()` / `submit()`.
683
+ *
684
+ * **Lowering (one workflow):** each input is uploaded once and wrapped in its
685
+ * own single-input `passthrough` source job (`src_N`); the `archive` job
686
+ * consumes those via `job_output` inputs (array order = entry order) and carries
687
+ * the single `archive` op. The archive job's id is `archive`, so {@link RunResult}
688
+ * projects ONLY its output. Mirrors the PHP `ArchivedRecipe`.
689
+ */
690
+ export declare class ArchivedRecipe {
691
+ private readonly inputs;
692
+ private readonly options;
693
+ private readonly client?;
694
+ constructor(inputs: readonly FileInput[], options?: ArchiveRecipeOptions, client?: GislClient | undefined);
695
+ /** The number of inputs being bundled (introspection / tests). */
696
+ get inputCount(): number;
697
+ /**
698
+ * Lower to the archive DAG: one `passthrough` source job per input + one
699
+ * `archive` job consuming them via `job_output`.
700
+ *
701
+ * @internal Consumed by {@link run} / {@link submit} (after uploading) and the
702
+ * cross-language parity harness (with fixed ids).
703
+ */
704
+ toWorkflowPayload(fileIds: readonly string[], callbackUrl?: string): WorkflowCreatePayload;
705
+ /**
706
+ * Execute end-to-end: upload every input, create the archive workflow, await a
707
+ * terminal state (SSE with poll fallback), then resolve ONLY the archive output
708
+ * into a {@link RunResult}. Throws {@link GislTimeoutError} on `maxWait`.
709
+ * Requires a client bound via `gisl().files(...).archive(...)`.
710
+ */
711
+ run(options?: {
712
+ maxWait?: string | number;
713
+ onProgress?: (event: ProgressEvent) => void;
714
+ signal?: AbortSignal;
715
+ pollIntervalMs?: number;
716
+ }): Promise<RunResult>;
717
+ /**
718
+ * Fire-and-forget: upload + create the archive workflow (wiring `webhook` into
719
+ * `callback_url` when given), return a client-bound {@link Handle}. Mirrors
720
+ * {@link MergedRecipe.submit}.
721
+ */
722
+ submit(webhook?: string): Promise<Handle>;
723
+ private _uploadAllAndCreate;
724
+ /**
725
+ * Reject an invalid bundle BEFORE any upload fires — the archive schema allows
726
+ * 2–50 inputs (`min/max_inputs`), so a typo'd bundle costs no bandwidth.
727
+ */
728
+ private validatePreUpload;
729
+ /**
730
+ * Project the archive options into the wire shape. Both fields are optional
731
+ * (the server defaults `format` to zip and `folder_structure` to flat), so an
732
+ * omitted option is dropped rather than sent.
733
+ */
734
+ private wireArchiveOptions;
735
+ }
284
736
  export {};