@giveitsmaller/sdk 0.7.0 → 0.9.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.
@@ -0,0 +1,503 @@
1
+ /**
2
+ * File-first result surface — the value the file-first layer's `run()` /
3
+ * `Handle.wait()` / `Handle.result()` return (producers land in FF2b/FF5).
4
+ *
5
+ * Coexists with the operation-first `Result`/`Artifact` (in `builder.ts`)
6
+ * until the operation-first layer is removed (FF6). The file-first shape is
7
+ * flatter and adds an always-present per-input partition (`succeeded` /
8
+ * `failed`) so one bad input in a multi-input run doesn't sink the rest.
9
+ *
10
+ * Mirrors `packages/php/src/FileFirst/*`.
11
+ */
12
+ import { type ProgressEvent } from './builder.js';
13
+ import type { GislClient } from './client.js';
14
+ import type { OperationDownload, WorkflowStatusResponse } from '@giveitsmaller/contracts/openapi';
15
+ import { OptimizeFor } from './generated/sdk_spec/enums.js';
16
+ import type { PresetDefaults } from './ergonomic/presets/index.js';
17
+ import type { WorkflowCreatePayload } from './types.js';
18
+ import { Handle } from './handle.js';
19
+ /**
20
+ * Streams a single output URL to a local path. The seam between the
21
+ * file-first {@link RunResult} sinks and the SDK's HTTP/auth layer.
22
+ *
23
+ * FF1 defines ONLY this type — the concrete implementation (fetch +
24
+ * filesystem streamer) is wired by the producer tickets (`run()`/`submit()`,
25
+ * FF2b/FF5), which construct a `RunResult` with a real downloader bound to
26
+ * the client's auth context. Unit tests inject a small stub. A `RunResult`
27
+ * built WITHOUT a downloader (e.g. in a browser, or any no-I/O context)
28
+ * throws {@link GislSinkError} from its sinks rather than reaching for a
29
+ * global client.
30
+ *
31
+ * Mirrors the PHP `Downloader` interface.
32
+ *
33
+ * STREAMING CONTRACT: implementations MUST stream the URL body to
34
+ * `destPath` — they MUST NOT buffer the whole output in memory. The
35
+ * `Promise<void>` return exists precisely so no buffered-bytes value can
36
+ * leak into the calling convention.
37
+ */
38
+ export interface Downloader {
39
+ /**
40
+ * Stream the body at `url` to the local filesystem path `destPath`.
41
+ * Implementations create/overwrite `destPath`. Failures reject.
42
+ */
43
+ downloadTo(url: string, destPath: string): Promise<void>;
44
+ }
45
+ /**
46
+ * A single deliverable output of a file-first run — the file-first layer's
47
+ * flat output type. Leaner than the operation-first `Artifact`: just the
48
+ * four fields a caller needs to identify + fetch an output.
49
+ *
50
+ * Mirrors the PHP `OutputFile`.
51
+ */
52
+ export interface OutputFile {
53
+ readonly url: string;
54
+ readonly filename: string;
55
+ readonly sizeBytes: number;
56
+ readonly operation: string;
57
+ }
58
+ /**
59
+ * One succeeded entry in {@link RunResult.succeeded}: a single input's
60
+ * outputs, addressable by the `key:` the caller gave that file (null when
61
+ * no key was supplied). Mirrors the PHP `ItemResult`.
62
+ */
63
+ export interface ItemResult {
64
+ readonly key: string | null;
65
+ readonly outputs: readonly OutputFile[];
66
+ }
67
+ /**
68
+ * One failed entry in {@link RunResult.failed}: an input that did not
69
+ * produce a deliverable, paired with the cause. One bad input does not sink
70
+ * the rest of a multi-input run. `error` is `unknown` (mirroring the PHP
71
+ * `\Throwable`) so the caller narrows with `instanceof`. Mirrors the PHP
72
+ * `ItemFailure`.
73
+ */
74
+ export interface ItemFailure {
75
+ readonly key: string | null;
76
+ readonly error: unknown;
77
+ }
78
+ /**
79
+ * Return value of {@link RunResult.downloadTo} — the local paths written,
80
+ * in the SAME order as {@link RunResult.artifacts}. Mirrors the PHP
81
+ * `Manifest`.
82
+ */
83
+ export interface Manifest {
84
+ readonly paths: readonly string[];
85
+ }
86
+ /**
87
+ * Result of a file-first run. Coexists with the operation-first `Result`
88
+ * (in `builder.ts`) until FF6.
89
+ *
90
+ * Mirrors the PHP `RunResult` class. A class (not a bare interface) because
91
+ * it carries the `byKey()`/`toFile()`/`downloadTo()` behaviour; the data
92
+ * fields stay public + readonly so `toArray()` round-trips.
93
+ *
94
+ * Field notes:
95
+ * - `url`: single-output sugar — the lone artifact's URL when exactly one
96
+ * output exists, else undefined.
97
+ * - `ok`: true iff `failed` is empty. (A boolean — the partition lists are
98
+ * `succeeded`/`failed`; resolves the design doc's `ok` bool-vs-list
99
+ * contradiction.)
100
+ * - `state`: lifecycle state (`completed` | `failed` | ...). Named `state`,
101
+ * NOT `status`, matching the file-first `StatusSnapshot.state`.
102
+ * - sinks fetch via the injected {@link Downloader}; a result with no
103
+ * downloader throws {@link GislSinkError} (reason `downloader_unavailable`).
104
+ */
105
+ export declare class RunResult {
106
+ readonly workflowId: string;
107
+ readonly state: string;
108
+ readonly artifacts: readonly OutputFile[];
109
+ readonly succeeded: readonly ItemResult[];
110
+ readonly failed: readonly ItemFailure[];
111
+ private readonly downloader?;
112
+ /** Single-output sugar: the lone artifact's URL, or undefined for 0 / >1. */
113
+ readonly url?: string;
114
+ /** True iff {@link failed} is empty. */
115
+ readonly ok: boolean;
116
+ constructor(workflowId: string, state: string, artifacts: readonly OutputFile[], succeeded: readonly ItemResult[], failed: readonly ItemFailure[], downloader?: Downloader | undefined);
117
+ /**
118
+ * Address a succeeded input by the `key:` given to `file()`. Duplicate keys
119
+ * are not valid input — the producer enforces key uniqueness (a later
120
+ * ticket); the first match is returned.
121
+ * @throws {GislNoSuchKeyError} when no succeeded entry has that key (a
122
+ * keyless run always throws — it is positionally addressable only).
123
+ */
124
+ byKey(key: string): ItemResult;
125
+ /**
126
+ * Write the single output to `path`. Requires EXACTLY ONE artifact.
127
+ * @throws {GislSinkError} reason `not_single_output` for 0/>1 outputs;
128
+ * reason `downloader_unavailable` when no downloader is bound.
129
+ */
130
+ toFile(path: string): Promise<void>;
131
+ /**
132
+ * Download every output into `dir` (filename per output), in output order.
133
+ * Returns the {@link Manifest} of local paths written.
134
+ * @throws {GislSinkError} reason `partial_failure` when `failOnPartial` and
135
+ * the run had failed inputs; reason `downloader_unavailable` when no
136
+ * downloader is bound.
137
+ */
138
+ downloadTo(dir: string, options?: {
139
+ failOnPartial?: boolean;
140
+ }): Promise<Manifest>;
141
+ /**
142
+ * Plain-object projection. Field ORDER (workflowId, state, ok, url?,
143
+ * artifacts, succeeded, failed) is fixed to match the PHP `toArray()`
144
+ * reference so JSON-string parity holds (FF1 shape assertion + FF2b harness
145
+ * fixture). `url` is omitted entirely when undefined — `JSON.stringify`
146
+ * then produces the identical shape to PHP's omit-when-null `toArray()`.
147
+ */
148
+ toJSON(): {
149
+ workflowId: string;
150
+ state: string;
151
+ ok: boolean;
152
+ url?: string;
153
+ artifacts: readonly OutputFile[];
154
+ succeeded: readonly {
155
+ key: string | null;
156
+ outputs: readonly OutputFile[];
157
+ }[];
158
+ failed: readonly {
159
+ key: string | null;
160
+ error: string;
161
+ }[];
162
+ };
163
+ private requireDownloader;
164
+ }
165
+ /**
166
+ * Flatten the terminal workflow status + its downloads into a {@link RunResult}.
167
+ *
168
+ * Shared by {@link Recipe.run} (passes its recipe key) and the file-first
169
+ * {@link Handle} reattach surface (`Handle.wait()`/`Handle.result()`, FF5a —
170
+ * passes `null` because a reattached handle carries no recipe key).
171
+ *
172
+ * **Partition invariant (carries a prior codex-review fix — do NOT let it
173
+ * drift):** success is ONLY `state === 'completed'`. Every other terminal
174
+ * state — `failed`, `partially_failed`, `cancelled`, `expired`,
175
+ * `paused_insufficient_credits` — partitions into `failed[]` so a caller's
176
+ * `ok`/`succeeded` check can never treat a cancelled/expired/paused run as a
177
+ * clean result.
178
+ *
179
+ * @internal Exported for reuse by the file-first `Handle`; not part of the
180
+ * caller-facing fluent surface.
181
+ */
182
+ export declare function projectDownloadsToRunResult(workflowId: string, finalStatus: WorkflowStatusResponse, jobDownloads: readonly {
183
+ files: readonly OperationDownload[];
184
+ }[], key: string | null, downloader?: Downloader): RunResult;
185
+ /**
186
+ * Flatten a terminal multi-job workflow (the `client.files([...])` fan-out)
187
+ * into a partitioned {@link RunResult}. One job per input file, keyed by the
188
+ * `file-{i}` job ref the {@link FilesRecipe} lowering assigns; the result's
189
+ * `succeeded` / `failed` partition is PER JOB, so one bad input does not sink
190
+ * the rest.
191
+ *
192
+ * Join model: `finalStatus.jobs[]` carries the per-job {@link JobStatus} +
193
+ * `operations[]` (for the error message); `jobDownloads[]` carries the per-job
194
+ * output files. Both are joined on the job `ref` ("file-{i}"); the partition
195
+ * key is the index `"{i}"` parsed out of that ref. The flat `artifacts[]` is
196
+ * every job's outputs in job order (the order `finalStatus.jobs[]` lists them).
197
+ *
198
+ * **Partition invariant (mirrors {@link projectDownloadsToRunResult} PER JOB —
199
+ * do NOT let it drift):** a job is a SUCCESS only when its
200
+ * {@link JobResponse.status} `=== 'completed'`. Any other per-job status —
201
+ * `failed`, `pending`, `waiting`, `blocked_insufficient_credits`,
202
+ * `in_progress` — partitions that job into `failed[]` (with that job's first
203
+ * operation error message, scoped to THAT job only).
204
+ *
205
+ * @internal Exported for the file-first `client.files([...]).run()` producer;
206
+ * not part of the caller-facing fluent surface.
207
+ */
208
+ export declare function projectMultiJobToRunResult(workflowId: string, finalStatus: WorkflowStatusResponse, jobDownloads: readonly {
209
+ ref: string;
210
+ files: readonly OperationDownload[];
211
+ }[], keyByRef: ReadonlyMap<string, string | null>, downloader?: Downloader): RunResult;
212
+ /**
213
+ * True when a terminal status describes a homogeneous `files([...])` fan-out —
214
+ * i.e. it has at least one job and EVERY job ref is `file-{i}` (the ids the
215
+ * {@link FilesRecipe} lowering assigns). A single-file {@link Recipe} omits the
216
+ * job id, so its job carries a non-`file-N` ref (e.g. `op`) and this is false.
217
+ *
218
+ * This is the data-driven seam that lets {@link Handle.wait}/{@link Handle.result}
219
+ * pick the per-job producer ({@link projectMultiJobToRunResult}) over the
220
+ * single-output one for a fan-out — WITHOUT a construction-time marker, so a
221
+ * fan-out **reattached** via `client.workflow(id)` (which carries no marker)
222
+ * still partitions per job. Keys are recovered from the `file-{i}` refs.
223
+ *
224
+ * @internal Exported for the file-first `Handle`; not part of the public API.
225
+ */
226
+ export declare function isFanoutStatus(finalStatus: WorkflowStatusResponse): boolean;
227
+ /**
228
+ * The primary file a {@link Recipe} operates on — the "subject" of the
229
+ * file-first surface. A discriminated union over the ways a caller names an
230
+ * input:
231
+ *
232
+ * - `path` — a local filesystem path (Node; the common case).
233
+ * - `blob` — an in-memory `Blob`/`File` (browser, or Node 18+).
234
+ * - `uploadId` — a previously-uploaded `file_id` (reuse across recipes).
235
+ *
236
+ * FF2a does NO upload, so only the `path` + `uploadId` arms are exercised
237
+ * end-to-end here; the `blob` arm is DEFINED and type-checked but its upload
238
+ * is wired by FF2b (`run()`). Mirrors the PHP `FileInput` value object.
239
+ */
240
+ export type FileInput = {
241
+ readonly kind: 'path';
242
+ readonly path: string;
243
+ } | {
244
+ readonly kind: 'blob';
245
+ readonly blob: Blob;
246
+ } | {
247
+ readonly kind: 'uploadId';
248
+ readonly fileId: string;
249
+ };
250
+ /** Named constructors for {@link FileInput} — mirror the PHP static factories. */
251
+ export declare const fileInput: {
252
+ readonly path: (path: string) => FileInput;
253
+ readonly blob: (blob: Blob) => FileInput;
254
+ readonly uploadId: (fileId: string) => FileInput;
255
+ };
256
+ /** One step in a {@link Recipe}'s chain — an op kind + captured ergonomic args. */
257
+ interface RecipeStep {
258
+ readonly opType: 'compress' | 'convert' | 'thumbnail' | 'text_watermark';
259
+ readonly options: Readonly<Record<string, unknown>>;
260
+ }
261
+ /**
262
+ * The file-first builder value. `client.file(path)` returns a `Recipe`;
263
+ * single-input operations called on it (`compress`, `convert`, `thumbnail`,
264
+ * `textWatermark`) chain SEQUENTIALLY — each op feeds the next, and the chain
265
+ * lowers to ONE workflow job with an ordered `operations[]` (per ADR-0004:
266
+ * operations execute sequentially, each consuming the previous output). A
267
+ * chain yields the TERMINAL output only; intermediates are consumed (surfaced
268
+ * by FF2b's `run()`/{@link RunResult}).
269
+ *
270
+ * **Immutable / clone-on-write.** Every op returns a NEW `Recipe` carrying the
271
+ * appended step — `this` is never mutated. A Recipe is therefore a reusable
272
+ * value: branching the same base recipe two different ways cannot let one
273
+ * branch observe the other's steps (the aliasing trap mutable builders fall
274
+ * into).
275
+ *
276
+ * FF2a is network-free: there is NO `run()` here (that is FF2b). The lowering
277
+ * seam {@link toWorkflowPayload} takes the resolved upload id as a parameter
278
+ * so it stays pure — FF2b's `run()` calls the SAME method after uploading, and
279
+ * the parity harness calls it with a fixed id to assert the lowered shape.
280
+ *
281
+ * Mirrors the PHP `Recipe`.
282
+ */
283
+ export declare class Recipe {
284
+ private readonly input;
285
+ private readonly recipeKey;
286
+ private readonly steps;
287
+ private readonly presetDefaults?;
288
+ private readonly scopedPresetDefaults?;
289
+ private readonly client?;
290
+ constructor(input: FileInput, recipeKey?: string | undefined, steps?: readonly RecipeStep[], presetDefaults?: PresetDefaults | undefined, scopedPresetDefaults?: PresetDefaults | undefined, client?: GislClient | undefined);
291
+ /**
292
+ * Reduce file size. `optimize` selects a per-media preset (resolved to
293
+ * concrete wire fields at lower-time, exactly as `client.compress()` does).
294
+ */
295
+ compress(optimize?: OptimizeFor): Recipe;
296
+ /** Change format. `format` is lowered verbatim to the `format` wire option. */
297
+ convert(format: string): Recipe;
298
+ /**
299
+ * Generate a preview. Width and/or height in pixels; an omitted dimension is
300
+ * dropped from the wire options (not sent as `undefined`).
301
+ */
302
+ thumbnail(options?: {
303
+ width?: number;
304
+ height?: number;
305
+ }): Recipe;
306
+ /**
307
+ * Apply a text watermark. Single-input (the text is an option, not a
308
+ * secondary file) — lowers to the `text_watermark` op with a `text` option.
309
+ */
310
+ textWatermark(text: string): Recipe;
311
+ /**
312
+ * Lower this recipe to a workflow-create payload against a resolved upload
313
+ * id. Single-input chain → ONE job, `source: upload(fileId)`, ordered
314
+ * `operations[]`; the job `id` is omitted (a single job referenced by
315
+ * nothing — the server auto-assigns `job_N`).
316
+ *
317
+ * When `callbackUrl` is given (the file-first `submit()` path), it is built
318
+ * INTO the payload at construction (`callback_url`) rather than spread onto an
319
+ * already-built readonly payload. `run()` passes no `callbackUrl`.
320
+ *
321
+ * @internal Consumed by FF2b's `run()` (after a real upload), FF5b's
322
+ * `submit()` (with a webhook), and the cross-language parity harness (with a
323
+ * fixed id). Not part of the caller-facing fluent surface.
324
+ */
325
+ toWorkflowPayload(fileId: string, callbackUrl?: string): WorkflowCreatePayload;
326
+ /** The result-addressing key passed to `file()`, or undefined. */
327
+ key(): string | undefined;
328
+ /** The number of operations chained so far (introspection / tests). */
329
+ get stepCount(): number;
330
+ /**
331
+ * The captured op chain. Read by {@link FilesRecipe} to compose a shared
332
+ * chain across many inputs without duplicating the chain-method validation.
333
+ * @internal
334
+ */
335
+ get recipeSteps(): readonly RecipeStep[];
336
+ /**
337
+ * Execute the recipe end-to-end: upload the input (when required), create
338
+ * the workflow, await a terminal state (SSE with poll fallback), then
339
+ * resolve the produced downloads into a flat {@link RunResult}. Throws
340
+ * {@link GislTimeoutError} if `maxWait` elapses before terminal status.
341
+ *
342
+ * Mirrors the operation-first `OperationBuilder.run` (in `builder.ts`).
343
+ * Requires a client bound at construction time — `gisl().file(...)` wires
344
+ * it; a directly-constructed `Recipe` (e.g. in a lowering-only test) has no
345
+ * client and throws {@link GislConfigError}.
346
+ */
347
+ run(options?: {
348
+ maxWait?: string | number;
349
+ onProgress?: (event: ProgressEvent) => void;
350
+ signal?: AbortSignal;
351
+ pollIntervalMs?: number;
352
+ }): Promise<RunResult>;
353
+ /**
354
+ * Fire-and-forget the recipe: upload the input (when required), create the
355
+ * workflow (wiring `webhook` into `callback_url` when given), and return a
356
+ * client-bound {@link Handle} carrying the workflow id + webhook secret + the
357
+ * recipe key. Does NOT wait for terminal status — call `handle.wait()` /
358
+ * `handle.result()` later to collect the {@link RunResult}.
359
+ *
360
+ * Requires a client bound at construction time (same `no_client` guard as
361
+ * {@link run}). `webhook` is OPTIONAL: when omitted, no `callback_url` is
362
+ * sent. Mirrors the PHP `Recipe.submit()`.
363
+ *
364
+ * @param webhook Absolute callback URL the server POSTs lifecycle events to.
365
+ */
366
+ submit(webhook?: string): Promise<Handle>;
367
+ /**
368
+ * Resolve the upload id (verbatim for a pre-uploaded id; uploading a path /
369
+ * blob otherwise, emitting `{phase:'upload'}` progress), check the post-upload
370
+ * deadline, lower to the workflow-create payload (wiring `webhook` into
371
+ * `callback_url`), and create the workflow. Shared first half of
372
+ * {@link run} + {@link submit}.
373
+ *
374
+ * The post-upload deadline check carries a prior codex fix (9a117f04eb59): a
375
+ * slow upload must not proceed to createWorkflow past the deadline.
376
+ */
377
+ private _uploadAndCreate;
378
+ private withStep;
379
+ private lowerStep;
380
+ private lowerCompressOptions;
381
+ private compressMediaHint;
382
+ }
383
+ /**
384
+ * The homogeneous fan-out builder value (FF3a). `client.files([a, b, c])`
385
+ * returns a `FilesRecipe`; the op-chain methods (`compress`, `convert`,
386
+ * `thumbnail`, `textWatermark`) build ONE shared recipe (chain) that is applied
387
+ * to EVERY input file in ONE workflow. `run()` returns a partitioned
388
+ * {@link RunResult} — one `succeeded`/`failed` entry per input, keyed by its
389
+ * 0-based index ("0", "1", …) so one bad input does not sink the rest.
390
+ *
391
+ * **Immutable / clone-on-write**, exactly like {@link Recipe}: every op returns
392
+ * a NEW `FilesRecipe` carrying the appended step. The inputs are held as an
393
+ * ORDERED list (NOT a map) so the per-file index is the partition key.
394
+ *
395
+ * **Lowering composes {@link Recipe} per file** rather than duplicating
396
+ * `lowerStep`/`lowerCompressOptions`: for each input `i` it builds an internal
397
+ * single-file `Recipe(input_i, …, steps)`, calls its `toWorkflowPayload` to get
398
+ * that file's one-job payload, then merges all jobs into ONE
399
+ * {@link WorkflowCreatePayload} with `jobs[i].id = "file-{i}"`. This preserves
400
+ * each file's media-hint (different extensions per input resolve compress
401
+ * presets independently).
402
+ *
403
+ * Exposes both `run()` (blocking, returns a partitioned {@link RunResult}) and
404
+ * `submit(webhook?)` (fire-and-forget, returns a {@link Handle}). Mirrors the
405
+ * PHP `FilesRecipe`.
406
+ */
407
+ export declare class FilesRecipe {
408
+ private readonly inputs;
409
+ private readonly steps;
410
+ private readonly presetDefaults?;
411
+ private readonly scopedPresetDefaults?;
412
+ private readonly client?;
413
+ constructor(inputs: readonly FileInput[], steps?: readonly RecipeStep[], presetDefaults?: PresetDefaults | undefined, scopedPresetDefaults?: PresetDefaults | undefined, client?: GislClient | undefined);
414
+ /**
415
+ * Reduce file size on every input. `optimize` selects a per-media preset
416
+ * (resolved per file at lower-time, so each input's extension picks its own
417
+ * preset). Reuses {@link Recipe}'s validation — a directly-constructed
418
+ * lowering builds an internal Recipe that throws the same `GislConfigError`.
419
+ */
420
+ compress(optimize?: OptimizeFor): FilesRecipe;
421
+ /** Change every input's format. `format` lowers verbatim to the `format` option. */
422
+ convert(format: string): FilesRecipe;
423
+ /** Generate a preview of every input. Omitted dimensions are dropped from the wire options. */
424
+ thumbnail(options?: {
425
+ width?: number;
426
+ height?: number;
427
+ }): FilesRecipe;
428
+ /** Apply the same text watermark to every input. */
429
+ textWatermark(text: string): FilesRecipe;
430
+ /** The number of inputs in this fan-out (introspection / tests). */
431
+ get inputCount(): number;
432
+ /** The number of operations chained so far (introspection / tests). */
433
+ get stepCount(): number;
434
+ /**
435
+ * Lower this fan-out to a single multi-job workflow-create payload against a
436
+ * list of resolved upload ids (one per input, in input order). Each input `i`
437
+ * becomes ONE job with `id = "file-{i}"`, its `source: upload(fileIds[i])`,
438
+ * and the SHARED lowered `operations[]`. Composes the single-file
439
+ * {@link Recipe.toWorkflowPayload} per file so per-file media-hints resolve
440
+ * independently and lowering logic is not duplicated.
441
+ *
442
+ * @internal Consumed by {@link run} (after uploading all inputs) and the
443
+ * cross-language parity harness (with fixed ids). Not caller-facing.
444
+ */
445
+ toWorkflowPayload(fileIds: readonly string[], callbackUrl?: string): WorkflowCreatePayload;
446
+ /**
447
+ * Execute the fan-out end-to-end: upload EVERY input, create ONE workflow
448
+ * with one job per input, await a terminal state (SSE with poll fallback),
449
+ * then resolve the per-job downloads into a partitioned {@link RunResult}.
450
+ * `partially_failed` is a NORMAL terminal state here — its successful jobs
451
+ * land in `succeeded`, its failed jobs in `failed`.
452
+ *
453
+ * Requires a client bound at construction time — `gisl().files(...)` wires
454
+ * it; a directly-constructed `FilesRecipe` throws {@link GislConfigError}.
455
+ * Mirrors the single-file {@link Recipe.run}; see {@link submit} for the
456
+ * fire-and-forget arm.
457
+ */
458
+ run(options?: {
459
+ maxWait?: string | number;
460
+ onProgress?: (event: ProgressEvent) => void;
461
+ signal?: AbortSignal;
462
+ pollIntervalMs?: number;
463
+ }): Promise<RunResult>;
464
+ /**
465
+ * Fire-and-forget the fan-out: upload every input, create ONE multi-job
466
+ * workflow (wiring `webhook` into `callback_url` when given), and return a
467
+ * client-bound {@link Handle}. Does NOT wait for terminal status — call
468
+ * `handle.wait()` / `handle.result()` later to collect the partitioned
469
+ * {@link RunResult}. The Handle detects the fan-out from the wire `file-{i}`
470
+ * job refs, so per-file `byKey()` works even after a `client.workflow(id)`
471
+ * reattach (the keys are the input indices `"0"`, `"1"`, …).
472
+ *
473
+ * Requires a client bound at construction time (same `no_client` guard as
474
+ * {@link run}). `webhook` is OPTIONAL. Fire-and-forget, so NO whole-run
475
+ * deadline (a multi-GB upload is bounded by the HTTP client's own timeout).
476
+ * Mirrors the single-file {@link Recipe.submit}.
477
+ *
478
+ * @param webhook Absolute callback URL the server POSTs lifecycle events to.
479
+ */
480
+ submit(webhook?: string): Promise<Handle>;
481
+ /**
482
+ * Upload every input (verbatim for a pre-uploaded id; uploading a path /
483
+ * blob otherwise, emitting `{phase:'upload'}` progress) then create ONE
484
+ * multi-job workflow (one job per input, `callback_url` built in when
485
+ * `webhook` is given). Shared first half of {@link run} + {@link submit}.
486
+ *
487
+ * Uploads are sequential so progress events stay ordered and the abort
488
+ * signal is honoured promptly; a resource arm is impossible in TS (Blob).
489
+ * `run()` passes a whole-run deadline (a slow upload must not proceed to
490
+ * createWorkflow past maxWait); `submit()` passes `undefined`, so the
491
+ * deadline checks are skipped.
492
+ */
493
+ private _uploadAllAndCreate;
494
+ /**
495
+ * The shared single-file {@link Recipe} that captures the op chain (input is
496
+ * a placeholder — only the steps are read). Reuses Recipe's op-chain
497
+ * validation + coercion so a `FilesRecipe.compress(bad)` throws the identical
498
+ * `GislConfigError` as `Recipe.compress(bad)`.
499
+ */
500
+ private baseRecipe;
501
+ private withStep;
502
+ }
503
+ export {};