@giveitsmaller/sdk 0.20.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +8 -15
- package/dist/_audit.js +2 -4
- package/dist/builder.d.ts +4 -4
- package/dist/builder.js +16 -16
- package/dist/client.d.ts +9 -6
- package/dist/client.js +90 -31
- package/dist/ergonomic/image_output_routes.d.ts +30 -0
- package/dist/ergonomic/image_output_routes.js +82 -11
- package/dist/ergonomic/preset_resolver.d.ts +2 -2
- package/dist/ergonomic/preset_resolver.js +6 -10
- package/dist/ergonomic/presets/index.d.ts +9 -8
- package/dist/ergonomic/presets/index.js +1 -9
- package/dist/errors.d.ts +48 -2
- package/dist/errors.js +54 -1
- package/dist/file-first.d.ts +81 -13
- package/dist/file-first.js +279 -64
- package/dist/generated/sdk_spec/enums.d.ts +0 -26
- package/dist/generated/sdk_spec/enums.js +0 -16
- package/dist/generated/sdk_spec/errors.d.ts +1 -1
- package/dist/generated/sdk_spec/errors.js +12 -0
- package/dist/generated/sdk_spec/presets.js +0 -14
- package/dist/generated/sdk_spec/version.d.ts +2 -2
- package/dist/generated/sdk_spec/version.js +2 -2
- package/dist/gisl.d.ts +21 -2
- package/dist/handle.d.ts +6 -1
- package/dist/handle.js +42 -13
- package/dist/index.core.d.ts +4 -4
- package/dist/index.core.js +2 -2
- package/dist/merge.js +2 -2
- package/dist/types.d.ts +11 -3
- package/dist/types.js +1 -0
- package/package.json +3 -3
- package/dist/ergonomic/presets/document_pdf_compress.d.ts +0 -12
- package/dist/ergonomic/presets/document_pdf_compress.js +0 -33
package/dist/file-first.js
CHANGED
|
@@ -14,7 +14,7 @@ import { _detectCompressMedia, _detectAudioLossless, _consumeSseToTerminal, _pol
|
|
|
14
14
|
import { LazyHttpDownloader } from './lazy-downloader.js';
|
|
15
15
|
import { resolveCompressOptions, } from './ergonomic/preset_resolver.js';
|
|
16
16
|
import { validateVerbOptions, assertThumbnailDimensions } from './ergonomic/option_validation.js';
|
|
17
|
-
import { resolveOutputRoute, tokenForMime, tokenForPath, isPlannedValue, FACADE_MANAGED_OUTPUTS, } from './ergonomic/image_output_routes.js';
|
|
17
|
+
import { resolveOutputRoute, tokenForMime, tokenForPath, isPlannedValue, isUnknownEnumValue, FACADE_MANAGED_OUTPUTS, } from './ergonomic/image_output_routes.js';
|
|
18
18
|
import { OptimizeFor } from './generated/sdk_spec/enums.js';
|
|
19
19
|
import { uploadSource, jobOutputSource } from './types.js';
|
|
20
20
|
// Value import used only at call-time (inside MergedRecipe.toWorkflowPayload),
|
|
@@ -341,10 +341,54 @@ export function isFanoutStatus(finalStatus) {
|
|
|
341
341
|
return jobs.length > 0 && jobs.every((job) => _FANOUT_REF.test(job.ref));
|
|
342
342
|
}
|
|
343
343
|
const _MERGE_SRC_REF = /^src_\d+$/;
|
|
344
|
+
/**
|
|
345
|
+
* Job id/ref for the DOWNSTREAM job that carries post-`sole_op` steps. A
|
|
346
|
+
* `sole_op` op (image_watermark / video_watermark / merge — ADR-0025) MUST be
|
|
347
|
+
* the only op in its job, so when a caller chains `compress()` / `convert()` /
|
|
348
|
+
* `thumbnail()` / `transform()` after `watermark()` / `merge()`, those steps
|
|
349
|
+
* lower into this separate job that consumes the sole_op output via
|
|
350
|
+
* `job_output` (the server derives the DAG from the `from` reference — no
|
|
351
|
+
* explicit `workflow_edges` needed). When present it is the TERMINAL deliverable,
|
|
352
|
+
* so `run()` / the {@link Handle} project THIS job's output, and the status-shape
|
|
353
|
+
* detectors accept it alongside the sole_op + `src_{i}` refs. PIiUit28.
|
|
354
|
+
*/
|
|
355
|
+
export const _POST_STEP_JOB_REF = 'post';
|
|
356
|
+
/**
|
|
357
|
+
* Job id/ref for the UPSTREAM job carrying any steps that PRECEDE a single-input
|
|
358
|
+
* `sole_op` op (e.g. `.compress().textWatermark()`): the pre-steps run in this
|
|
359
|
+
* job, the `sole_op` job then consumes its output via `job_output`. Distinct
|
|
360
|
+
* from the multi-input `src_{i}` fan-in refs. IQc01rj0.
|
|
361
|
+
*/
|
|
362
|
+
export const _PRE_STEP_JOB_REF = 'pre';
|
|
363
|
+
/**
|
|
364
|
+
* Wire op types the API marks `sole_op` (ADR-0025) — the op MUST be the ONLY op
|
|
365
|
+
* in its job. Mirrors `operation-capabilities.json` `operations.<op>.sole_op`,
|
|
366
|
+
* inlined as a browser-safe const (the raw-JSON sidecar subpath is Node-only)
|
|
367
|
+
* and PINNED to that projection by `sole-op-conformance.test.ts` — a contract
|
|
368
|
+
* regen that flips an op's `sole_op` fails there. The single-input
|
|
369
|
+
* {@link Recipe.toWorkflowPayload} reads THIS set to split a chain at every
|
|
370
|
+
* sole_op boundary into a `job_output`-linked job chain (so
|
|
371
|
+
* `.textWatermark('x').compress()` lowers to a valid DAG, not a contract-invalid
|
|
372
|
+
* co-bundled job). Mirrored by PHP `Recipe::SOLE_OP_TYPES`. IQc01rj0.
|
|
373
|
+
* @internal
|
|
374
|
+
*/
|
|
375
|
+
export const SOLE_OP_TYPES = new Set([
|
|
376
|
+
'archive',
|
|
377
|
+
'audio_overlay',
|
|
378
|
+
'audio_to_video',
|
|
379
|
+
'custom_luma',
|
|
380
|
+
'image_watermark',
|
|
381
|
+
'merge',
|
|
382
|
+
'split',
|
|
383
|
+
'text_watermark',
|
|
384
|
+
'video_text_watermark',
|
|
385
|
+
'video_watermark',
|
|
386
|
+
]);
|
|
344
387
|
/**
|
|
345
388
|
* True when a terminal status describes a fluent `files([...]).merge(...)`
|
|
346
389
|
* combine — at least one job ref `merge` and every OTHER job ref is `src_{i}`
|
|
347
|
-
* (the ids the {@link MergedRecipe} lowering
|
|
390
|
+
* or the downstream `post` job (the ids the {@link MergedRecipe} lowering
|
|
391
|
+
* assigns; `post` carries any post-combine steps). The data-driven seam that
|
|
348
392
|
* lets {@link Handle.wait}/{@link Handle.result} project ONLY the merged output
|
|
349
393
|
* — filtering the `src_*` passthrough plumbing — even after a
|
|
350
394
|
* `client.workflow(id)` reattach (no construction-time marker), matching
|
|
@@ -363,6 +407,9 @@ export function isMergeStatus(finalStatus) {
|
|
|
363
407
|
hasMerge = true;
|
|
364
408
|
continue;
|
|
365
409
|
}
|
|
410
|
+
// The downstream post-`sole_op` steps job (PIiUit28) is part of a merge DAG.
|
|
411
|
+
if (job.ref === _POST_STEP_JOB_REF)
|
|
412
|
+
continue;
|
|
366
413
|
if (!_MERGE_SRC_REF.test(job.ref))
|
|
367
414
|
return false;
|
|
368
415
|
}
|
|
@@ -395,9 +442,10 @@ export function isArchiveStatus(finalStatus) {
|
|
|
395
442
|
}
|
|
396
443
|
/**
|
|
397
444
|
* True when a terminal status describes a fluent `file(...).watermark(overlay)`
|
|
398
|
-
* — at least one job ref `watermark` and every OTHER job ref is `src_{i}`
|
|
399
|
-
* ids the {@link WatermarkedRecipe} lowering
|
|
400
|
-
* overlay
|
|
445
|
+
* — at least one job ref `watermark` and every OTHER job ref is `src_{i}` or
|
|
446
|
+
* the downstream `post` job (the ids the {@link WatermarkedRecipe} lowering
|
|
447
|
+
* assigns: `src_0` base, `src_1` overlay, `post` any post-watermark steps).
|
|
448
|
+
* Lets {@link Handle.wait}/{@link Handle.result} AND
|
|
401
449
|
* {@link WatermarkedRecipe.run} project ONLY the watermark output — filtering
|
|
402
450
|
* the `src_*` passthrough plumbing — even after a `client.workflow(id)` reattach.
|
|
403
451
|
* Mutually exclusive with {@link isFanoutStatus} / {@link isMergeStatus} /
|
|
@@ -415,11 +463,117 @@ export function isWatermarkStatus(finalStatus) {
|
|
|
415
463
|
hasWatermark = true;
|
|
416
464
|
continue;
|
|
417
465
|
}
|
|
466
|
+
// The downstream post-`sole_op` steps job (PIiUit28) is part of a watermark DAG.
|
|
467
|
+
if (job.ref === _POST_STEP_JOB_REF)
|
|
468
|
+
continue;
|
|
418
469
|
if (!_MERGE_SRC_REF.test(job.ref))
|
|
419
470
|
return false;
|
|
420
471
|
}
|
|
421
472
|
return hasWatermark;
|
|
422
473
|
}
|
|
474
|
+
/**
|
|
475
|
+
* True when a terminal status describes a SINGLE-INPUT `sole_op` chain — e.g.
|
|
476
|
+
* `.textWatermark('x').compress()` lowered to a `text_watermark` job + a
|
|
477
|
+
* downstream `post` job (and an optional upstream `pre` job for steps before the
|
|
478
|
+
* sole_op). Every job ref is a `sole_op` wire type ({@link SOLE_OP_TYPES}) or the
|
|
479
|
+
* `pre`/`post` chain refs, with NO `src_{i}` fan-in ref (which distinguishes it
|
|
480
|
+
* from the multi-input merge/watermark/archive DAGs). Lets a submitted/reattached
|
|
481
|
+
* {@link Handle} project ONLY the terminal deliverable — filtering the
|
|
482
|
+
* intermediate sole_op artifact — without builder state. IQc01rj0.
|
|
483
|
+
*
|
|
484
|
+
* @internal Exported for the file-first `Handle`; not part of the public API.
|
|
485
|
+
*/
|
|
486
|
+
export function isSoleOpChainStatus(finalStatus) {
|
|
487
|
+
const jobs = finalStatus.jobs ?? [];
|
|
488
|
+
if (jobs.length === 0)
|
|
489
|
+
return false;
|
|
490
|
+
let hasSoleOp = false;
|
|
491
|
+
for (const job of jobs) {
|
|
492
|
+
if (SOLE_OP_TYPES.has(job.ref)) {
|
|
493
|
+
hasSoleOp = true;
|
|
494
|
+
continue;
|
|
495
|
+
}
|
|
496
|
+
if (job.ref === _PRE_STEP_JOB_REF || job.ref === _POST_STEP_JOB_REF)
|
|
497
|
+
continue;
|
|
498
|
+
return false;
|
|
499
|
+
}
|
|
500
|
+
return hasSoleOp;
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* The terminal deliverable ref for a single-input `sole_op` chain status: the
|
|
504
|
+
* downstream `post` job when present, else the `sole_op` job itself (the ref in
|
|
505
|
+
* {@link SOLE_OP_TYPES}). Mirrors how the merge/watermark paths pick their
|
|
506
|
+
* terminal via {@link terminalOutputRef} in `handle.ts`. IQc01rj0.
|
|
507
|
+
*
|
|
508
|
+
* @internal
|
|
509
|
+
*/
|
|
510
|
+
export function soleOpChainDeliverableRef(finalStatus) {
|
|
511
|
+
// Determine the terminal from the DAG (the job refs), NOT from which downloads
|
|
512
|
+
// exist: if the `post` job is in the graph it IS the deliverable even when it
|
|
513
|
+
// FAILED and produced no download — filtering to it then yields no artifact +
|
|
514
|
+
// the failure surfaces via the status, rather than silently exposing the
|
|
515
|
+
// successful intermediate sole_op artifact (codex).
|
|
516
|
+
const refs = (finalStatus.jobs ?? []).map((j) => j.ref);
|
|
517
|
+
const soleOpRef = refs.find((r) => SOLE_OP_TYPES.has(r)) ?? '';
|
|
518
|
+
return refs.includes(_POST_STEP_JOB_REF) ? _POST_STEP_JOB_REF : soleOpRef;
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* Split a single-input operation chain into a `job_output`-linked job chain at
|
|
522
|
+
* its `sole_op` boundary (IQc01rj0). A `sole_op` op (ADR-0025) MUST be the ONLY
|
|
523
|
+
* op in its job, so `.textWatermark('x').compress()` cannot lower to one
|
|
524
|
+
* co-bundled job — the `text_watermark` runs alone (job id = its op type) and
|
|
525
|
+
* the trailing steps lower into a downstream `post` job that consumes it via
|
|
526
|
+
* `job_output`; steps that PRECEDE the sole_op run in an upstream `pre` job.
|
|
527
|
+
*
|
|
528
|
+
* Returns a single job verbatim (NO `id`) when no split is needed — no sole_op,
|
|
529
|
+
* or a lone sole_op already alone — so the vast majority of recipes keep their
|
|
530
|
+
* byte-identical one-job shape. Throws when the chain carries more than one
|
|
531
|
+
* sole_op op (a single-input recipe reaches at most one sole_op verb today;
|
|
532
|
+
* supporting N is a follow-up).
|
|
533
|
+
*/
|
|
534
|
+
function _splitSingleInputJobs(ops, fileId) {
|
|
535
|
+
const soleCount = ops.reduce((n, op) => (SOLE_OP_TYPES.has(op.type) ? n + 1 : n), 0);
|
|
536
|
+
if (soleCount > 1) {
|
|
537
|
+
throw new GislConfigError('This recipe chains more than one sole_op operation (e.g. two textWatermark() steps), ' +
|
|
538
|
+
'which is not supported yet — apply them as separate workflows.', { reason: 'multi_sole_op_unsupported' });
|
|
539
|
+
}
|
|
540
|
+
const i = ops.findIndex((op) => SOLE_OP_TYPES.has(op.type));
|
|
541
|
+
if (i === -1 || (i === 0 && ops.length === 1)) {
|
|
542
|
+
// No sole_op, or a lone sole_op already alone → one job, no id (unchanged).
|
|
543
|
+
return [{ source: uploadSource(fileId), operations: [...ops] }];
|
|
544
|
+
}
|
|
545
|
+
const preOps = ops.slice(0, i);
|
|
546
|
+
const soleOp = ops[i];
|
|
547
|
+
const postOps = ops.slice(i + 1);
|
|
548
|
+
const jobs = [];
|
|
549
|
+
if (preOps.length > 0) {
|
|
550
|
+
jobs.push({ id: _PRE_STEP_JOB_REF, source: uploadSource(fileId), operations: [...preOps] });
|
|
551
|
+
}
|
|
552
|
+
jobs.push({
|
|
553
|
+
id: soleOp.type,
|
|
554
|
+
source: preOps.length > 0 ? jobOutputSource(_PRE_STEP_JOB_REF) : uploadSource(fileId),
|
|
555
|
+
operations: [soleOp],
|
|
556
|
+
});
|
|
557
|
+
if (postOps.length > 0) {
|
|
558
|
+
jobs.push({ id: _POST_STEP_JOB_REF, source: jobOutputSource(soleOp.type), operations: [...postOps] });
|
|
559
|
+
}
|
|
560
|
+
return jobs;
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* The single job of a NESTED single-input lowering, asserting it did not split
|
|
564
|
+
* (IQc01rj0). A nested Recipe used as a watermark base/overlay or a fan-out
|
|
565
|
+
* entry that itself carries a `sole_op` op alongside OTHER steps splits into a
|
|
566
|
+
* job chain; folding that chain into the OUTER DAG (as a `src_{i}`/`file-{i}`
|
|
567
|
+
* job) is not supported yet, so fail fast rather than silently drop the
|
|
568
|
+
* split-off downstream job (the whole point of the split).
|
|
569
|
+
*/
|
|
570
|
+
function _nestedSingleJob(payload, context) {
|
|
571
|
+
if (payload.jobs.length !== 1) {
|
|
572
|
+
throw new GislConfigError(`A ${context} recipe chains a sole_op op (e.g. textWatermark()) alongside other steps, which ` +
|
|
573
|
+
'is not supported here yet — apply the sole_op step in a standalone recipe.', { reason: 'nested_sole_op_unsupported' });
|
|
574
|
+
}
|
|
575
|
+
return payload.jobs[0];
|
|
576
|
+
}
|
|
423
577
|
/** Named constructors for {@link FileInput} — mirror the PHP static factories. */
|
|
424
578
|
export const fileInput = {
|
|
425
579
|
path(path) {
|
|
@@ -694,10 +848,25 @@ export class Recipe {
|
|
|
694
848
|
*/
|
|
695
849
|
toWorkflowPayload(fileId, callbackUrl) {
|
|
696
850
|
const operations = this.steps.map((step, i) => this.lowerStep(step, i));
|
|
697
|
-
//
|
|
698
|
-
//
|
|
699
|
-
|
|
700
|
-
|
|
851
|
+
// Split at any `sole_op` boundary into a `job_output`-linked chain (IQc01rj0);
|
|
852
|
+
// a chain with no sole_op stays a single byte-identical job. Job key order
|
|
853
|
+
// (id?, source, operations) matches the PHP `toWire()` — byte-identical JSON.
|
|
854
|
+
const jobs = _splitSingleInputJobs(operations, fileId);
|
|
855
|
+
return callbackUrl === undefined ? { jobs } : { jobs, callback_url: callbackUrl };
|
|
856
|
+
}
|
|
857
|
+
/**
|
|
858
|
+
* Trigger the per-step lowering purely for its validation side effects
|
|
859
|
+
* (route honoring, planned / out-of-enum values, `media_unknown`), discarding
|
|
860
|
+
* the result. Called BEFORE uploading bytes so a route-invalid recipe fails
|
|
861
|
+
* fast instead of after the upload is spent — parity with PHP
|
|
862
|
+
* `assertOperationsLowerable`. Lowering reads only `steps` + the input token,
|
|
863
|
+
* not the upload id, so this is a faithful preflight (0azjb6Rg).
|
|
864
|
+
*/
|
|
865
|
+
assertOperationsLowerable() {
|
|
866
|
+
const ops = this.steps.map((step, i) => this.lowerStep(step, i));
|
|
867
|
+
// Also run the sole_op split so a multi-sole_op recipe fails pre-upload
|
|
868
|
+
// (IQc01rj0). The placeholder id is discarded — only the throw matters.
|
|
869
|
+
_splitSingleInputJobs(ops, 'preflight');
|
|
701
870
|
}
|
|
702
871
|
/** The result-addressing key passed to `file()`, or undefined. */
|
|
703
872
|
key() {
|
|
@@ -741,7 +910,7 @@ export class Recipe {
|
|
|
741
910
|
if (this.client === undefined) {
|
|
742
911
|
throw new GislConfigError('Recipe.run() requires a client; build the recipe via gisl().file(...) rather than constructing Recipe directly.', { reason: 'no_client' });
|
|
743
912
|
}
|
|
744
|
-
const deadline = Date.now() + _parseMaxWait(options.maxWait ??
|
|
913
|
+
const deadline = Date.now() + _parseMaxWait(options.maxWait ?? 600_000);
|
|
745
914
|
// 1+2. Upload (when required) + create the workflow. Shared with submit()
|
|
746
915
|
// (which passes a webhook → callback_url). run() passes no webhook.
|
|
747
916
|
const created = await this._uploadAndCreate(undefined, deadline, onProgress, signal, options.probeBeforeCreate, options.probeTimeoutMs);
|
|
@@ -759,18 +928,25 @@ export class Recipe {
|
|
|
759
928
|
// 4. Fetch downloads. The maxWait deadline covers upload + create + wait +
|
|
760
929
|
// downloads, so check before issuing the request (mirrors builder.ts).
|
|
761
930
|
if (Date.now() >= deadline) {
|
|
762
|
-
throw new GislTimeoutError(`Workflow ${created.workflowId} reached terminal status but maxWait elapsed before downloads could be fetched
|
|
931
|
+
throw new GislTimeoutError(`Workflow ${created.workflowId} reached terminal status but maxWait elapsed before downloads could be fetched`, created.workflowId);
|
|
763
932
|
}
|
|
764
933
|
const downloads = await this.client.getWorkflowDownloads(created.workflowId);
|
|
765
934
|
// TDqmkWpX: re-check AFTER the downloads fetch so a slow getWorkflowDownloads
|
|
766
935
|
// cannot return a success past the advertised maxWait deadline.
|
|
767
936
|
if (Date.now() >= deadline) {
|
|
768
|
-
throw new GislTimeoutError(`Workflow ${created.workflowId} downloads fetch completed after maxWait elapsed
|
|
937
|
+
throw new GislTimeoutError(`Workflow ${created.workflowId} downloads fetch completed after maxWait elapsed`, created.workflowId);
|
|
769
938
|
}
|
|
770
939
|
// Download URLs from getWorkflowDownloads are pre-signed and require no SDK
|
|
771
940
|
// auth, so the downloader issues a plain unauthenticated fetch.
|
|
772
941
|
const downloader = new LazyHttpDownloader();
|
|
773
|
-
|
|
942
|
+
// A single-input `sole_op` split (IQc01rj0, e.g. textWatermark().compress())
|
|
943
|
+
// produces a job chain — project ONLY the terminal deliverable, filtering the
|
|
944
|
+
// intermediate sole_op artifact (mirrors the merge/watermark terminal filter
|
|
945
|
+
// and Handle.project so submit()/reattach agree with run()).
|
|
946
|
+
const runDownloads = isSoleOpChainStatus(finalStatus)
|
|
947
|
+
? downloads.downloads.filter((d) => d.ref === soleOpChainDeliverableRef(finalStatus))
|
|
948
|
+
: downloads.downloads;
|
|
949
|
+
return projectDownloadsToRunResult(created.workflowId, finalStatus, runDownloads, this.recipeKey ?? null, downloader);
|
|
774
950
|
}
|
|
775
951
|
/**
|
|
776
952
|
* Fire-and-forget the recipe: upload the input (when required), create the
|
|
@@ -795,7 +971,7 @@ export class Recipe {
|
|
|
795
971
|
// submit() is fire-and-forget — NO whole-run deadline. The upload may be
|
|
796
972
|
// large (a multi-GB master, example 12) and is bounded by the HTTP client's
|
|
797
973
|
// own request timeout, not an arbitrary submit-side cap. Pass `undefined`
|
|
798
|
-
// so the post-upload deadline check is skipped: a
|
|
974
|
+
// so the post-upload deadline check is skipped: a 600s cap here would throw
|
|
799
975
|
// on a slow-but-successful big upload before createWorkflow (codex).
|
|
800
976
|
const created = await this._uploadAndCreate(webhook, undefined, undefined, undefined, options?.probeBeforeCreate, options?.probeTimeoutMs);
|
|
801
977
|
return new Handle(created.workflowId, created.webhookSecret != null ? created.webhookSecret : undefined, this.client, this.recipeKey ?? null);
|
|
@@ -811,6 +987,13 @@ export class Recipe {
|
|
|
811
987
|
* slow upload must not proceed to createWorkflow past the deadline.
|
|
812
988
|
*/
|
|
813
989
|
async _uploadAndCreate(webhook, deadline, onProgress, signal, probeBeforeCreate, probeTimeoutMs) {
|
|
990
|
+
// 0. Preflight the operation lowering BEFORE any upload so a route-invalid
|
|
991
|
+
// recipe (an unhonored / planned / out-of-enum option, or media_unknown on a
|
|
992
|
+
// bare upload-id input) fails fast instead of after the upload bytes are
|
|
993
|
+
// spent — parity with PHP's uploadAndCreate (0azjb6Rg). Lowering does not
|
|
994
|
+
// depend on the upload id, so a clean preflight guarantees the real
|
|
995
|
+
// toWorkflowPayload() lowering below also succeeds.
|
|
996
|
+
this.assertOperationsLowerable();
|
|
814
997
|
// 1. Resolve the upload id. A pre-uploaded id skips the upload entirely;
|
|
815
998
|
// a path / blob is uploaded now, emitting {phase:'upload'} progress.
|
|
816
999
|
let fileId;
|
|
@@ -950,6 +1133,15 @@ export class Recipe {
|
|
|
950
1133
|
`(${resolved.inputToken} → ${requested ?? resolved.inputToken}). ` +
|
|
951
1134
|
'Check it applies to this format/route combination.', { reason: 'option_not_on_route', conflictingFields: [key] });
|
|
952
1135
|
}
|
|
1136
|
+
// Enum-membership gate (rtkzl9gr): reject a value outside the option's
|
|
1137
|
+
// enum before upload (e.g. `metadata: 'keep'` on avif/svg, whose enum is
|
|
1138
|
+
// ['strip','all']). same_format only — the compress option enums apply
|
|
1139
|
+
// definitionally there; a format_change routes via convert, whose enums
|
|
1140
|
+
// may differ, so we leave it to the (conservative) planned gate.
|
|
1141
|
+
if (resolved.route === 'same_format' && isUnknownEnumValue(resolved.inputToken, key, value)) {
|
|
1142
|
+
throw new GislConfigError(`output(): '${key}: ${String(value)}' is not an accepted value for '${key}' on ` +
|
|
1143
|
+
`'${resolved.inputToken}' images. Check the values this format's route accepts.`, { reason: 'invalid_option_value', conflictingFields: [key] });
|
|
1144
|
+
}
|
|
953
1145
|
if (isPlannedValue(resolved.inputToken, key, value)) {
|
|
954
1146
|
throw new GislConfigError(`output(): '${key}: ${String(value)}' is advertised but not available yet (planned).`, { reason: 'feature_not_available', conflictingFields: [key] });
|
|
955
1147
|
}
|
|
@@ -1257,8 +1449,8 @@ function _resolveWatermarkWireOp(base) {
|
|
|
1257
1449
|
'image/jpeg, image/png, image/webp; video_watermark accepts video/mp4, video/webm. ' +
|
|
1258
1450
|
'Convert the base to a supported format first.', { reason: 'unsupported_media' });
|
|
1259
1451
|
}
|
|
1260
|
-
throw new GislConfigError(`watermark does not support ${media} base files — overlay watermarking targets image or video bases ` +
|
|
1261
|
-
'(
|
|
1452
|
+
throw new GislConfigError(`watermark does not support ${media} base files — overlay watermarking targets image or video bases. ` +
|
|
1453
|
+
'textWatermark() is image-only, so it is not an alternative for document or audio bases.', { reason: 'unsupported_media' });
|
|
1262
1454
|
}
|
|
1263
1455
|
/**
|
|
1264
1456
|
* Validate a watermark overlay locally: the overlay role is always an IMAGE.
|
|
@@ -1479,7 +1671,7 @@ export class FilesRecipe {
|
|
|
1479
1671
|
toWorkflowPayload(fileIds, callbackUrl) {
|
|
1480
1672
|
const jobs = this.inputs.map((input, i) => {
|
|
1481
1673
|
const single = new Recipe(input, undefined, this.steps, this.presetDefaults, this.scopedPresetDefaults);
|
|
1482
|
-
const oneJob = single.toWorkflowPayload(fileIds[i])
|
|
1674
|
+
const oneJob = _nestedSingleJob(single.toWorkflowPayload(fileIds[i]), 'fan-out');
|
|
1483
1675
|
// Key order (id, source, operations) matches the PHP `toWire()` so the
|
|
1484
1676
|
// JSON-string serialisation is byte-identical across languages.
|
|
1485
1677
|
return { id: `file-${i}`, source: oneJob.source, operations: oneJob.operations };
|
|
@@ -1507,7 +1699,7 @@ export class FilesRecipe {
|
|
|
1507
1699
|
if (this.client === undefined) {
|
|
1508
1700
|
throw new GislConfigError('FilesRecipe.run() requires a client; build the fan-out via gisl().files(...) rather than constructing FilesRecipe directly.', { reason: 'no_client' });
|
|
1509
1701
|
}
|
|
1510
|
-
const deadline = Date.now() + _parseMaxWait(options.maxWait ??
|
|
1702
|
+
const deadline = Date.now() + _parseMaxWait(options.maxWait ?? 600_000);
|
|
1511
1703
|
// 1+2. Upload EVERY input + create ONE multi-job workflow. Shared with
|
|
1512
1704
|
// submit() (which passes a webhook → callback_url and no deadline).
|
|
1513
1705
|
const created = await this._uploadAllAndCreate(undefined, deadline, onProgress, signal, options.probeBeforeCreate, options.probeTimeoutMs);
|
|
@@ -1524,13 +1716,13 @@ export class FilesRecipe {
|
|
|
1524
1716
|
});
|
|
1525
1717
|
// 4. Fetch downloads + project per-job into the partitioned RunResult.
|
|
1526
1718
|
if (Date.now() >= deadline) {
|
|
1527
|
-
throw new GislTimeoutError(`Workflow ${created.workflowId} reached terminal status but maxWait elapsed before downloads could be fetched
|
|
1719
|
+
throw new GislTimeoutError(`Workflow ${created.workflowId} reached terminal status but maxWait elapsed before downloads could be fetched`, created.workflowId);
|
|
1528
1720
|
}
|
|
1529
1721
|
const downloads = await this.client.getWorkflowDownloads(created.workflowId);
|
|
1530
1722
|
// TDqmkWpX: re-check AFTER the downloads fetch so a slow getWorkflowDownloads
|
|
1531
1723
|
// cannot return a success past the advertised maxWait deadline.
|
|
1532
1724
|
if (Date.now() >= deadline) {
|
|
1533
|
-
throw new GislTimeoutError(`Workflow ${created.workflowId} downloads fetch completed after maxWait elapsed
|
|
1725
|
+
throw new GislTimeoutError(`Workflow ${created.workflowId} downloads fetch completed after maxWait elapsed`, created.workflowId);
|
|
1534
1726
|
}
|
|
1535
1727
|
// keyByRef maps each job ref ("file-{i}") to the partition key. Today the
|
|
1536
1728
|
// key is just the index string; the Map seam leaves room for the FF3b
|
|
@@ -1615,9 +1807,10 @@ export class FilesRecipe {
|
|
|
1615
1807
|
*
|
|
1616
1808
|
* **Lowering (one workflow):** each input is uploaded once and wrapped in its
|
|
1617
1809
|
* own single-input `passthrough` source job (`src_N`); the `merge` job consumes
|
|
1618
|
-
* those via `job_output` inputs (array order = play order)
|
|
1619
|
-
* op
|
|
1620
|
-
* convert / thumbnail
|
|
1810
|
+
* those via `job_output` inputs (array order = play order). `merge` is
|
|
1811
|
+
* `sole_op` (ADR-0025), so it is the ONLY op in its job; any post-combine ops
|
|
1812
|
+
* (compress / convert / thumbnail / transform) lower into a downstream `post`
|
|
1813
|
+
* job that consumes the merged output via `job_output`. The
|
|
1621
1814
|
* merge-level wire options reuse {@link wireMergeOptions} so a fluent merge
|
|
1622
1815
|
* lowers identically to the operation-first `client.merge()`.
|
|
1623
1816
|
*
|
|
@@ -1679,9 +1872,9 @@ export class MergedRecipe {
|
|
|
1679
1872
|
}
|
|
1680
1873
|
/**
|
|
1681
1874
|
* Lower to the merge DAG: one `passthrough` source job per input + one
|
|
1682
|
-
* `merge` job whose `operations[]` is `[merge
|
|
1683
|
-
*
|
|
1684
|
-
*
|
|
1875
|
+
* `merge` job whose `operations[]` is exactly `[merge]` (sole_op). The merge
|
|
1876
|
+
* job's `inputs[]` consume the source jobs via `job_output` in input (play)
|
|
1877
|
+
* order; any post-combine ops lower into a downstream `post` job.
|
|
1685
1878
|
*
|
|
1686
1879
|
* @internal Consumed by {@link run} (after uploading all inputs), {@link submit}
|
|
1687
1880
|
* (with a webhook), and the cross-language parity harness (with fixed ids).
|
|
@@ -1697,12 +1890,19 @@ export class MergedRecipe {
|
|
|
1697
1890
|
sourceJobs.push({ id: srcId, source: uploadSource(fileId), operations: [{ type: 'passthrough' }] });
|
|
1698
1891
|
inputs.push({ source: jobOutputSource(srcId) });
|
|
1699
1892
|
});
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1893
|
+
// `merge` is `sole_op` (ADR-0025): the op MUST be alone in its job.
|
|
1894
|
+
// Post-merge steps lower into a DOWNSTREAM job (see {@link _POST_STEP_JOB_REF})
|
|
1895
|
+
// that consumes the merge output via `job_output`. PIiUit28.
|
|
1896
|
+
const mergeJob = {
|
|
1897
|
+
id: 'merge',
|
|
1898
|
+
inputs,
|
|
1899
|
+
operations: [{ type: 'merge', options: wireMergeOptions(this.mergeOptions, mediaKind) }],
|
|
1900
|
+
};
|
|
1705
1901
|
const jobs = [...sourceJobs, mergeJob];
|
|
1902
|
+
const postOps = this.lowerPostSteps(mediaKind);
|
|
1903
|
+
if (postOps.length > 0) {
|
|
1904
|
+
jobs.push({ id: _POST_STEP_JOB_REF, source: jobOutputSource('merge'), operations: postOps });
|
|
1905
|
+
}
|
|
1706
1906
|
return callbackUrl === undefined ? { jobs } : { jobs, callback_url: callbackUrl };
|
|
1707
1907
|
}
|
|
1708
1908
|
/** The number of inputs being combined (introspection / tests). */
|
|
@@ -1728,7 +1928,7 @@ export class MergedRecipe {
|
|
|
1728
1928
|
if (this.client === undefined) {
|
|
1729
1929
|
throw new GislConfigError('MergedRecipe.run() requires a client; build the merge via gisl().files(...).merge(...) rather than constructing MergedRecipe directly.', { reason: 'no_client' });
|
|
1730
1930
|
}
|
|
1731
|
-
const deadline = Date.now() + _parseMaxWait(options.maxWait ??
|
|
1931
|
+
const deadline = Date.now() + _parseMaxWait(options.maxWait ?? 600_000);
|
|
1732
1932
|
const created = await this._uploadAllAndCreate(undefined, deadline, onProgress, signal, options.probeBeforeCreate, options.probeTimeoutMs);
|
|
1733
1933
|
const finalStatus = await _awaitTerminal(this.client, {
|
|
1734
1934
|
workflowId: created.workflowId,
|
|
@@ -1739,18 +1939,20 @@ export class MergedRecipe {
|
|
|
1739
1939
|
useSSE: options.useSSE ?? true,
|
|
1740
1940
|
});
|
|
1741
1941
|
if (Date.now() >= deadline) {
|
|
1742
|
-
throw new GislTimeoutError(`Workflow ${created.workflowId} reached terminal status but maxWait elapsed before downloads could be fetched
|
|
1942
|
+
throw new GislTimeoutError(`Workflow ${created.workflowId} reached terminal status but maxWait elapsed before downloads could be fetched`, created.workflowId);
|
|
1743
1943
|
}
|
|
1744
1944
|
const downloads = await this.client.getWorkflowDownloads(created.workflowId);
|
|
1745
1945
|
// TDqmkWpX: re-check AFTER the downloads fetch so a slow getWorkflowDownloads
|
|
1746
1946
|
// cannot return a success past the advertised maxWait deadline.
|
|
1747
1947
|
if (Date.now() >= deadline) {
|
|
1748
|
-
throw new GislTimeoutError(`Workflow ${created.workflowId} downloads fetch completed after maxWait elapsed
|
|
1749
|
-
}
|
|
1750
|
-
// Project ONLY the
|
|
1751
|
-
// re-expose the raw uploads
|
|
1752
|
-
//
|
|
1753
|
-
|
|
1948
|
+
throw new GislTimeoutError(`Workflow ${created.workflowId} downloads fetch completed after maxWait elapsed`, created.workflowId);
|
|
1949
|
+
}
|
|
1950
|
+
// Project ONLY the terminal deliverable — the `src_*` passthrough jobs
|
|
1951
|
+
// re-expose the raw uploads (plumbing). Post-merge steps lower into the
|
|
1952
|
+
// downstream `_POST_STEP_JOB_REF` job, which is then the deliverable;
|
|
1953
|
+
// otherwise the `merge` job is (mirrors merge.ts + PHP).
|
|
1954
|
+
const outputRef = this.postSteps.length > 0 ? _POST_STEP_JOB_REF : 'merge';
|
|
1955
|
+
const mergeDownloads = downloads.downloads.filter((d) => d.ref === outputRef);
|
|
1754
1956
|
const downloader = new LazyHttpDownloader();
|
|
1755
1957
|
return projectDownloadsToRunResult(created.workflowId, finalStatus, mergeDownloads, null, downloader);
|
|
1756
1958
|
}
|
|
@@ -1939,7 +2141,7 @@ export class ArchivedRecipe {
|
|
|
1939
2141
|
if (this.client === undefined) {
|
|
1940
2142
|
throw new GislConfigError('ArchivedRecipe.run() requires a client; build the bundle via gisl().files(...).archive(...) rather than constructing ArchivedRecipe directly.', { reason: 'no_client' });
|
|
1941
2143
|
}
|
|
1942
|
-
const deadline = Date.now() + _parseMaxWait(options.maxWait ??
|
|
2144
|
+
const deadline = Date.now() + _parseMaxWait(options.maxWait ?? 600_000);
|
|
1943
2145
|
const created = await this._uploadAllAndCreate(undefined, deadline, onProgress, signal, options.probeBeforeCreate, options.probeTimeoutMs);
|
|
1944
2146
|
const finalStatus = await _awaitTerminal(this.client, {
|
|
1945
2147
|
workflowId: created.workflowId,
|
|
@@ -1950,13 +2152,13 @@ export class ArchivedRecipe {
|
|
|
1950
2152
|
useSSE: options.useSSE ?? true,
|
|
1951
2153
|
});
|
|
1952
2154
|
if (Date.now() >= deadline) {
|
|
1953
|
-
throw new GislTimeoutError(`Workflow ${created.workflowId} reached terminal status but maxWait elapsed before downloads could be fetched
|
|
2155
|
+
throw new GislTimeoutError(`Workflow ${created.workflowId} reached terminal status but maxWait elapsed before downloads could be fetched`, created.workflowId);
|
|
1954
2156
|
}
|
|
1955
2157
|
const downloads = await this.client.getWorkflowDownloads(created.workflowId);
|
|
1956
2158
|
// TDqmkWpX: re-check AFTER the downloads fetch so a slow getWorkflowDownloads
|
|
1957
2159
|
// cannot return a success past the advertised maxWait deadline.
|
|
1958
2160
|
if (Date.now() >= deadline) {
|
|
1959
|
-
throw new GislTimeoutError(`Workflow ${created.workflowId} downloads fetch completed after maxWait elapsed
|
|
2161
|
+
throw new GislTimeoutError(`Workflow ${created.workflowId} downloads fetch completed after maxWait elapsed`, created.workflowId);
|
|
1960
2162
|
}
|
|
1961
2163
|
// Project ONLY the archive job's output — the `src_*` passthrough jobs
|
|
1962
2164
|
// re-expose the raw uploads, which are plumbing, not the deliverable.
|
|
@@ -2028,7 +2230,8 @@ export class ArchivedRecipe {
|
|
|
2028
2230
|
* `passthrough` source job (`src_0` base, `src_1` overlay; their own preceding
|
|
2029
2231
|
* steps lower into those jobs), and the `watermark` job consumes them via
|
|
2030
2232
|
* `job_output` inputs tagged `role: base` / `role: overlay`. Post-watermark
|
|
2031
|
-
* `compress`/`convert`/`thumbnail`
|
|
2233
|
+
* `compress`/`convert`/`thumbnail`/`transform` steps lower into a downstream
|
|
2234
|
+
* `post` job on the watermark output (`image_watermark` is `sole_op`). Mirrors
|
|
2032
2235
|
* {@link MergedRecipe}. `textWatermark` is intentionally NOT a post-verb here.
|
|
2033
2236
|
*/
|
|
2034
2237
|
export class WatermarkedRecipe {
|
|
@@ -2091,8 +2294,9 @@ export class WatermarkedRecipe {
|
|
|
2091
2294
|
/**
|
|
2092
2295
|
* Lower to the watermark DAG: a `src_0` passthrough/base-steps job + a `src_1`
|
|
2093
2296
|
* passthrough/overlay-steps job + one `watermark` job whose `inputs[]` consume
|
|
2094
|
-
* them via `job_output` (role base/overlay)
|
|
2095
|
-
*
|
|
2297
|
+
* them via `job_output` (role base/overlay). The watermark op is `sole_op`
|
|
2298
|
+
* (ADR-0025), so `operations[]` is exactly `[image_watermark|video_watermark]`;
|
|
2299
|
+
* any post-watermark ops lower into a downstream `post` job. `fileIds` is
|
|
2096
2300
|
* `[baseId, overlayId]` (upload order). Throws pre-lowering if the base media
|
|
2097
2301
|
* is undetectable/unsupported (the planned-op gate).
|
|
2098
2302
|
*
|
|
@@ -2104,12 +2308,12 @@ export class WatermarkedRecipe {
|
|
|
2104
2308
|
const overlayId = fileIds[1];
|
|
2105
2309
|
// src_0: the base (its preceding steps, else a lossless passthrough).
|
|
2106
2310
|
const baseOps = this.baseSteps.length > 0
|
|
2107
|
-
? new Recipe(this.baseInput, undefined, this.baseSteps, this.presetDefaults, this.scopedPresetDefaults)
|
|
2108
|
-
.toWorkflowPayload(baseId).
|
|
2311
|
+
? _nestedSingleJob(new Recipe(this.baseInput, undefined, this.baseSteps, this.presetDefaults, this.scopedPresetDefaults)
|
|
2312
|
+
.toWorkflowPayload(baseId), 'watermark base').operations
|
|
2109
2313
|
: [{ type: 'passthrough' }];
|
|
2110
2314
|
// src_1: the overlay recipe (its own steps, else a lossless passthrough).
|
|
2111
2315
|
const overlayOps = this.overlay.recipeSteps.length > 0
|
|
2112
|
-
? this.overlay.toWorkflowPayload(overlayId).
|
|
2316
|
+
? _nestedSingleJob(this.overlay.toWorkflowPayload(overlayId), 'watermark overlay').operations
|
|
2113
2317
|
: [{ type: 'passthrough' }];
|
|
2114
2318
|
// Key order (id, source, operations) matches PHP toWire() — byte-identical JSON.
|
|
2115
2319
|
const srcBase = { id: 'src_0', source: uploadSource(baseId), operations: baseOps };
|
|
@@ -2118,12 +2322,20 @@ export class WatermarkedRecipe {
|
|
|
2118
2322
|
{ source: jobOutputSource('src_0'), role: 'base' },
|
|
2119
2323
|
{ source: jobOutputSource('src_1'), role: 'overlay' },
|
|
2120
2324
|
];
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
const watermarkJob = {
|
|
2325
|
+
// `image_watermark` / `video_watermark` are `sole_op` (ADR-0025): the op
|
|
2326
|
+
// MUST be alone in its job. Post-watermark steps lower into a DOWNSTREAM
|
|
2327
|
+
// job (see {@link _POST_STEP_JOB_REF}) that consumes the watermark output
|
|
2328
|
+
// via `job_output`. PIiUit28.
|
|
2329
|
+
const watermarkJob = {
|
|
2330
|
+
id: 'watermark',
|
|
2331
|
+
inputs,
|
|
2332
|
+
operations: [_lowerWatermarkOp(wireOp, this.watermarkOptions)],
|
|
2333
|
+
};
|
|
2126
2334
|
const jobs = [srcBase, srcOverlay, watermarkJob];
|
|
2335
|
+
const postOps = this.lowerPostSteps(wireOp);
|
|
2336
|
+
if (postOps.length > 0) {
|
|
2337
|
+
jobs.push({ id: _POST_STEP_JOB_REF, source: jobOutputSource('watermark'), operations: postOps });
|
|
2338
|
+
}
|
|
2127
2339
|
return callbackUrl === undefined ? { jobs } : { jobs, callback_url: callbackUrl };
|
|
2128
2340
|
}
|
|
2129
2341
|
/** The number of post-watermark ops chained so far (introspection / tests). */
|
|
@@ -2142,7 +2354,7 @@ export class WatermarkedRecipe {
|
|
|
2142
2354
|
if (this.client === undefined) {
|
|
2143
2355
|
throw new GislConfigError('WatermarkedRecipe.run() requires a client; build the watermark via gisl().file(...).watermark(...) rather than constructing WatermarkedRecipe directly.', { reason: 'no_client' });
|
|
2144
2356
|
}
|
|
2145
|
-
const deadline = Date.now() + _parseMaxWait(options.maxWait ??
|
|
2357
|
+
const deadline = Date.now() + _parseMaxWait(options.maxWait ?? 600_000);
|
|
2146
2358
|
const created = await this._uploadAllAndCreate(undefined, deadline, onProgress, signal, options.probeBeforeCreate, options.probeTimeoutMs);
|
|
2147
2359
|
const finalStatus = await _awaitTerminal(this.client, {
|
|
2148
2360
|
workflowId: created.workflowId,
|
|
@@ -2153,15 +2365,18 @@ export class WatermarkedRecipe {
|
|
|
2153
2365
|
useSSE: options.useSSE ?? true,
|
|
2154
2366
|
});
|
|
2155
2367
|
if (Date.now() >= deadline) {
|
|
2156
|
-
throw new GislTimeoutError(`Workflow ${created.workflowId} reached terminal status but maxWait elapsed before downloads could be fetched
|
|
2368
|
+
throw new GislTimeoutError(`Workflow ${created.workflowId} reached terminal status but maxWait elapsed before downloads could be fetched`, created.workflowId);
|
|
2157
2369
|
}
|
|
2158
2370
|
const downloads = await this.client.getWorkflowDownloads(created.workflowId);
|
|
2159
2371
|
if (Date.now() >= deadline) {
|
|
2160
|
-
throw new GislTimeoutError(`Workflow ${created.workflowId} downloads fetch completed after maxWait elapsed
|
|
2161
|
-
}
|
|
2162
|
-
// Project ONLY the
|
|
2163
|
-
// re-expose the raw base/overlay uploads
|
|
2164
|
-
|
|
2372
|
+
throw new GislTimeoutError(`Workflow ${created.workflowId} downloads fetch completed after maxWait elapsed`, created.workflowId);
|
|
2373
|
+
}
|
|
2374
|
+
// Project ONLY the terminal deliverable — the `src_*` passthrough jobs
|
|
2375
|
+
// re-expose the raw base/overlay uploads (plumbing). When post-watermark
|
|
2376
|
+
// steps were chained they lowered into the downstream `_POST_STEP_JOB_REF`
|
|
2377
|
+
// job, which is now the deliverable; otherwise the `watermark` job is.
|
|
2378
|
+
const outputRef = this.postSteps.length > 0 ? _POST_STEP_JOB_REF : 'watermark';
|
|
2379
|
+
const watermarkDownloads = downloads.downloads.filter((d) => d.ref === outputRef);
|
|
2165
2380
|
const downloader = new LazyHttpDownloader();
|
|
2166
2381
|
return projectDownloadsToRunResult(created.workflowId, finalStatus, watermarkDownloads, null, downloader);
|
|
2167
2382
|
}
|
|
@@ -2327,7 +2542,7 @@ export class BatchRecipe {
|
|
|
2327
2542
|
// FilesRecipe behavior and is intentionally NOT closed here — a TS
|
|
2328
2543
|
// path-precheck would diverge batch from FilesRecipe.)
|
|
2329
2544
|
this.validatePreUpload();
|
|
2330
|
-
const deadline = Date.now() + _parseMaxWait(options.maxWait ??
|
|
2545
|
+
const deadline = Date.now() + _parseMaxWait(options.maxWait ?? 600_000);
|
|
2331
2546
|
// 1+2. Upload each entry's input + create ONE multi-job workflow. batch v1
|
|
2332
2547
|
// sends NO webhook (run()-only), so `callback_url` is omitted from the
|
|
2333
2548
|
// payload (the closure receives `callbackUrl` undefined).
|
|
@@ -2359,13 +2574,13 @@ export class BatchRecipe {
|
|
|
2359
2574
|
});
|
|
2360
2575
|
// 4. Fetch downloads + project per-job into the keyed RunResult.
|
|
2361
2576
|
if (Date.now() >= deadline) {
|
|
2362
|
-
throw new GislTimeoutError(`Workflow ${created.workflowId} reached terminal status but maxWait elapsed before downloads could be fetched
|
|
2577
|
+
throw new GislTimeoutError(`Workflow ${created.workflowId} reached terminal status but maxWait elapsed before downloads could be fetched`, created.workflowId);
|
|
2363
2578
|
}
|
|
2364
2579
|
const downloads = await this.client.getWorkflowDownloads(created.workflowId);
|
|
2365
2580
|
// TDqmkWpX: re-check AFTER the downloads fetch so a slow getWorkflowDownloads
|
|
2366
2581
|
// cannot return a success past the advertised maxWait deadline.
|
|
2367
2582
|
if (Date.now() >= deadline) {
|
|
2368
|
-
throw new GislTimeoutError(`Workflow ${created.workflowId} downloads fetch completed after maxWait elapsed
|
|
2583
|
+
throw new GislTimeoutError(`Workflow ${created.workflowId} downloads fetch completed after maxWait elapsed`, created.workflowId);
|
|
2369
2584
|
}
|
|
2370
2585
|
const downloader = new LazyHttpDownloader();
|
|
2371
2586
|
return projectMultiJobToRunResult(created.workflowId, finalStatus, downloads.downloads, this.keyByRef(), downloader);
|
|
@@ -2384,7 +2599,7 @@ export class BatchRecipe {
|
|
|
2384
2599
|
*/
|
|
2385
2600
|
toWorkflowPayload(fileIds, callbackUrl) {
|
|
2386
2601
|
const jobs = this.recipes.map((entry, i) => {
|
|
2387
|
-
const oneJob = entry.toWorkflowPayload(fileIds[i])
|
|
2602
|
+
const oneJob = _nestedSingleJob(entry.toWorkflowPayload(fileIds[i]), 'batch');
|
|
2388
2603
|
// Positional id `b{i}` — a namespace DISTINCT from the fan-out `file-{i}` /
|
|
2389
2604
|
// merge `src_{i}` refs. Key order (id, source, operations) matches the PHP
|
|
2390
2605
|
// `toWire()` so the JSON serialisation is byte-identical across languages.
|
|
@@ -63,20 +63,6 @@ export declare const AudioSampleRate: {
|
|
|
63
63
|
readonly _48000: 48000;
|
|
64
64
|
};
|
|
65
65
|
export type AudioSampleRate = typeof AudioSampleRate[keyof typeof AudioSampleRate];
|
|
66
|
-
export declare const PdfProfile: {
|
|
67
|
-
readonly Screen: "screen";
|
|
68
|
-
readonly Ebook: "ebook";
|
|
69
|
-
readonly Printer: "printer";
|
|
70
|
-
readonly Prepress: "prepress";
|
|
71
|
-
};
|
|
72
|
-
export type PdfProfile = typeof PdfProfile[keyof typeof PdfProfile];
|
|
73
|
-
export declare const PdfColorspace: {
|
|
74
|
-
readonly Unchanged: "unchanged";
|
|
75
|
-
readonly Rgb: "rgb";
|
|
76
|
-
readonly Cmyk: "cmyk";
|
|
77
|
-
readonly Grayscale: "grayscale";
|
|
78
|
-
};
|
|
79
|
-
export type PdfColorspace = typeof PdfColorspace[keyof typeof PdfColorspace];
|
|
80
66
|
/** Catalog of every ergonomic enum (canonicalName → wire). */
|
|
81
67
|
export declare const ERGONOMIC_ENUMS: {
|
|
82
68
|
readonly OptimizeFor: {
|
|
@@ -135,16 +121,4 @@ export declare const ERGONOMIC_ENUMS: {
|
|
|
135
121
|
readonly _44100: 44100;
|
|
136
122
|
readonly _48000: 48000;
|
|
137
123
|
};
|
|
138
|
-
readonly PdfProfile: {
|
|
139
|
-
readonly Screen: "screen";
|
|
140
|
-
readonly Ebook: "ebook";
|
|
141
|
-
readonly Printer: "printer";
|
|
142
|
-
readonly Prepress: "prepress";
|
|
143
|
-
};
|
|
144
|
-
readonly PdfColorspace: {
|
|
145
|
-
readonly Unchanged: "unchanged";
|
|
146
|
-
readonly Rgb: "rgb";
|
|
147
|
-
readonly Cmyk: "cmyk";
|
|
148
|
-
readonly Grayscale: "grayscale";
|
|
149
|
-
};
|
|
150
124
|
};
|