@giveitsmaller/sdk 0.13.0 → 0.14.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/dist/builder.d.ts +27 -0
- package/dist/builder.js +42 -0
- package/dist/client.d.ts +15 -0
- package/dist/client.js +15 -0
- package/dist/file-first.d.ts +37 -4
- package/dist/file-first.js +149 -17
- package/dist/merge.d.ts +9 -0
- package/dist/merge.js +42 -2
- package/package.json +1 -1
package/dist/builder.d.ts
CHANGED
|
@@ -253,10 +253,26 @@ export interface RunOptions {
|
|
|
253
253
|
readonly useSSE?: boolean;
|
|
254
254
|
/** Override the poll interval used by the fallback (ms). */
|
|
255
255
|
readonly pollIntervalMs?: number;
|
|
256
|
+
/**
|
|
257
|
+
* Best-effort probe-before-create for a VIDEO upload that went multipart:
|
|
258
|
+
* after upload, before createWorkflow, wait for the server's probe to land
|
|
259
|
+
* so it admits the parallel video split. Default `true`; set `false` to
|
|
260
|
+
* skip the wait entirely. Never-bounce — a give-up just proceeds to create.
|
|
261
|
+
*/
|
|
262
|
+
readonly probeBeforeCreate?: boolean;
|
|
263
|
+
/** Overall timeout (ms) for the probe-before-create wait. */
|
|
264
|
+
readonly probeTimeoutMs?: number;
|
|
256
265
|
}
|
|
257
266
|
export interface SubmitOptions {
|
|
258
267
|
/** Webhook URL — wired to `WorkflowCreateRequest.callback_url`. */
|
|
259
268
|
readonly webhook: string;
|
|
269
|
+
/**
|
|
270
|
+
* Best-effort probe-before-create for a VIDEO upload that went multipart.
|
|
271
|
+
* Default `true`; set `false` to skip the wait. See {@link RunOptions}.
|
|
272
|
+
*/
|
|
273
|
+
readonly probeBeforeCreate?: boolean;
|
|
274
|
+
/** Overall timeout (ms) for the probe-before-create wait. */
|
|
275
|
+
readonly probeTimeoutMs?: number;
|
|
260
276
|
}
|
|
261
277
|
/**
|
|
262
278
|
* Captures the (op-type, input, options) tuple for one ergonomic-layer
|
|
@@ -405,6 +421,17 @@ export declare function _projectResult(status: WorkflowStatusResponse, jobDownlo
|
|
|
405
421
|
}[], appliedOptions: Record<string, unknown>, resolvedOptionsOverride?: ResolvedOptions): Result;
|
|
406
422
|
/** @internal — exported for reuse by `merge.ts` (T3) and future builders. */
|
|
407
423
|
export declare function _checkAborted(signal: AbortSignal | undefined): void;
|
|
424
|
+
/**
|
|
425
|
+
* Cap a best-effort probe-before-create timeout to the remaining `maxWait`
|
|
426
|
+
* budget so the probe wait can never push createWorkflow past the caller's
|
|
427
|
+
* deadline. Under a deadline an UNSET `probeTimeoutMs` becomes the remaining
|
|
428
|
+
* budget (never the 30s waitForProbe default); a set value is clamped to the
|
|
429
|
+
* remaining budget. With no deadline (the `submit()` fire-and-forget path),
|
|
430
|
+
* `probeTimeoutMs` passes through unchanged.
|
|
431
|
+
*
|
|
432
|
+
* @internal — exported for reuse by `file-first.ts` + `merge.ts`.
|
|
433
|
+
*/
|
|
434
|
+
export declare function _cappedProbeTimeoutMs(probeTimeoutMs: number | undefined, deadline: number | undefined): number | undefined;
|
|
408
435
|
/**
|
|
409
436
|
* Parse a `maxWait` argument: number = milliseconds; string with suffix
|
|
410
437
|
* `ms` / `s` / `m` / `h`. Throws if the string is malformed.
|
package/dist/builder.js
CHANGED
|
@@ -275,6 +275,24 @@ export class OperationBuilder {
|
|
|
275
275
|
if (Date.now() >= deadline) {
|
|
276
276
|
throw new GislTimeoutError(`Upload completed but maxWait elapsed before workflow could be created`);
|
|
277
277
|
}
|
|
278
|
+
// Best-effort probe-before-create for a multipart video upload (never-bounce).
|
|
279
|
+
// Capped to the remaining maxWait budget so a slow probe cannot push
|
|
280
|
+
// createWorkflow past the caller's deadline.
|
|
281
|
+
await this.client.maybeWaitForVideoProbe(uploadResp.fileId, {
|
|
282
|
+
enabled: options.probeBeforeCreate ?? true,
|
|
283
|
+
isVideo: _detectCompressMedia(this.input) === 'video',
|
|
284
|
+
sizeBytes: uploadResp.sizeBytes,
|
|
285
|
+
timeoutMs: _cappedProbeTimeoutMs(options.probeTimeoutMs, deadline),
|
|
286
|
+
signal,
|
|
287
|
+
});
|
|
288
|
+
// A cancel arriving during the FINAL successful probe request must not still
|
|
289
|
+
// create the workflow (maybeWaitForVideoProbe returns landed without a final
|
|
290
|
+
// abort re-check), so check here BEFORE createWorkflow.
|
|
291
|
+
_checkAborted(signal);
|
|
292
|
+
// RE-CHECK the deadline AFTER the probe wait (it consumes time).
|
|
293
|
+
if (Date.now() >= deadline) {
|
|
294
|
+
throw new GislTimeoutError('Probe wait completed but maxWait elapsed before workflow could be created');
|
|
295
|
+
}
|
|
278
296
|
// 2. Build + create the workflow.
|
|
279
297
|
const job = {
|
|
280
298
|
id: 'op',
|
|
@@ -320,6 +338,13 @@ export class OperationBuilder {
|
|
|
320
338
|
// call before the upload — same fail-early contract as run().
|
|
321
339
|
const resolved = this._resolve();
|
|
322
340
|
const uploadResp = await this.client.uploadFile(this.input);
|
|
341
|
+
// Best-effort probe-before-create for a multipart video upload (never-bounce).
|
|
342
|
+
await this.client.maybeWaitForVideoProbe(uploadResp.fileId, {
|
|
343
|
+
enabled: options.probeBeforeCreate ?? true,
|
|
344
|
+
isVideo: _detectCompressMedia(this.input) === 'video',
|
|
345
|
+
sizeBytes: uploadResp.sizeBytes,
|
|
346
|
+
timeoutMs: options.probeTimeoutMs,
|
|
347
|
+
});
|
|
323
348
|
const job = {
|
|
324
349
|
id: 'op',
|
|
325
350
|
source: uploadSource(uploadResp.fileId),
|
|
@@ -759,6 +784,23 @@ export function _checkAborted(signal) {
|
|
|
759
784
|
throw new DOMException('Aborted', 'AbortError');
|
|
760
785
|
}
|
|
761
786
|
}
|
|
787
|
+
/**
|
|
788
|
+
* Cap a best-effort probe-before-create timeout to the remaining `maxWait`
|
|
789
|
+
* budget so the probe wait can never push createWorkflow past the caller's
|
|
790
|
+
* deadline. Under a deadline an UNSET `probeTimeoutMs` becomes the remaining
|
|
791
|
+
* budget (never the 30s waitForProbe default); a set value is clamped to the
|
|
792
|
+
* remaining budget. With no deadline (the `submit()` fire-and-forget path),
|
|
793
|
+
* `probeTimeoutMs` passes through unchanged.
|
|
794
|
+
*
|
|
795
|
+
* @internal — exported for reuse by `file-first.ts` + `merge.ts`.
|
|
796
|
+
*/
|
|
797
|
+
export function _cappedProbeTimeoutMs(probeTimeoutMs, deadline) {
|
|
798
|
+
if (deadline === undefined) {
|
|
799
|
+
return probeTimeoutMs;
|
|
800
|
+
}
|
|
801
|
+
const remaining = Math.max(0, deadline - Date.now());
|
|
802
|
+
return probeTimeoutMs !== undefined ? Math.min(probeTimeoutMs, remaining) : remaining;
|
|
803
|
+
}
|
|
762
804
|
async function sleep(ms, signal) {
|
|
763
805
|
return await new Promise((resolve, reject) => {
|
|
764
806
|
const t = setTimeout(() => {
|
package/dist/client.d.ts
CHANGED
|
@@ -352,6 +352,21 @@ export declare class GislClient {
|
|
|
352
352
|
* the wait returns.
|
|
353
353
|
*/
|
|
354
354
|
waitForProbe(fileId: string, options?: ProbeWaitOptions): Promise<ProbeWaitResult>;
|
|
355
|
+
/**
|
|
356
|
+
* Best-effort probe-before-create for a VIDEO upload that went multipart.
|
|
357
|
+
* No-op unless enabled AND isVideo AND the upload exceeded the multipart
|
|
358
|
+
* threshold (i.e. it was a multipart upload — small single-shot videos skip
|
|
359
|
+
* the wait). Delegates to {@link waitForProbe} (never-bounce): a give-up just
|
|
360
|
+
* returns; genuine failures / caller abort propagate. The caller passes
|
|
361
|
+
* `isVideo` so the low-level client never imports ergonomic media detection.
|
|
362
|
+
*/
|
|
363
|
+
maybeWaitForVideoProbe(fileId: string, opts: {
|
|
364
|
+
enabled: boolean;
|
|
365
|
+
isVideo: boolean;
|
|
366
|
+
sizeBytes?: number;
|
|
367
|
+
timeoutMs?: number;
|
|
368
|
+
signal?: AbortSignal;
|
|
369
|
+
}): Promise<void>;
|
|
355
370
|
/**
|
|
356
371
|
* Probe N uploaded files in parallel and partition the results by
|
|
357
372
|
* outcome. Returns `{ ok, rejected, errors }` so the caller can
|
package/dist/client.js
CHANGED
|
@@ -2136,6 +2136,21 @@ export class GislClient {
|
|
|
2136
2136
|
return { landed: false, reason: 'timeout' };
|
|
2137
2137
|
}
|
|
2138
2138
|
}
|
|
2139
|
+
/**
|
|
2140
|
+
* Best-effort probe-before-create for a VIDEO upload that went multipart.
|
|
2141
|
+
* No-op unless enabled AND isVideo AND the upload exceeded the multipart
|
|
2142
|
+
* threshold (i.e. it was a multipart upload — small single-shot videos skip
|
|
2143
|
+
* the wait). Delegates to {@link waitForProbe} (never-bounce): a give-up just
|
|
2144
|
+
* returns; genuine failures / caller abort propagate. The caller passes
|
|
2145
|
+
* `isVideo` so the low-level client never imports ergonomic media detection.
|
|
2146
|
+
*/
|
|
2147
|
+
async maybeWaitForVideoProbe(fileId, opts) {
|
|
2148
|
+
if (!opts.enabled || !opts.isVideo)
|
|
2149
|
+
return;
|
|
2150
|
+
if (opts.sizeBytes === undefined || opts.sizeBytes <= this.multipartThreshold)
|
|
2151
|
+
return;
|
|
2152
|
+
await this.waitForProbe(fileId, { timeoutMs: opts.timeoutMs, signal: opts.signal });
|
|
2153
|
+
}
|
|
2139
2154
|
/**
|
|
2140
2155
|
* Probe N uploaded files in parallel and partition the results by
|
|
2141
2156
|
* outcome. Returns `{ ok, rejected, errors }` so the caller can
|
package/dist/file-first.d.ts
CHANGED
|
@@ -396,6 +396,8 @@ export declare class Recipe {
|
|
|
396
396
|
onProgress?: (event: ProgressEvent) => void;
|
|
397
397
|
signal?: AbortSignal;
|
|
398
398
|
pollIntervalMs?: number;
|
|
399
|
+
probeBeforeCreate?: boolean;
|
|
400
|
+
probeTimeoutMs?: number;
|
|
399
401
|
}): Promise<RunResult>;
|
|
400
402
|
/**
|
|
401
403
|
* Fire-and-forget the recipe: upload the input (when required), create the
|
|
@@ -409,8 +411,14 @@ export declare class Recipe {
|
|
|
409
411
|
* sent. Mirrors the PHP `Recipe.submit()`.
|
|
410
412
|
*
|
|
411
413
|
* @param webhook Absolute callback URL the server POSTs lifecycle events to.
|
|
414
|
+
* @param options Opt-out (`probeBeforeCreate: false`) / tune (`probeTimeoutMs`)
|
|
415
|
+
* the best-effort video probe-before-create. Kept as a 2nd optional param so
|
|
416
|
+
* the existing positional `webhook` arg stays backward compatible.
|
|
412
417
|
*/
|
|
413
|
-
submit(webhook?: string
|
|
418
|
+
submit(webhook?: string, options?: {
|
|
419
|
+
probeBeforeCreate?: boolean;
|
|
420
|
+
probeTimeoutMs?: number;
|
|
421
|
+
}): Promise<Handle>;
|
|
414
422
|
/**
|
|
415
423
|
* Resolve the upload id (verbatim for a pre-uploaded id; uploading a path /
|
|
416
424
|
* blob otherwise, emitting `{phase:'upload'}` progress), check the post-upload
|
|
@@ -530,6 +538,8 @@ export declare class FilesRecipe {
|
|
|
530
538
|
onProgress?: (event: ProgressEvent) => void;
|
|
531
539
|
signal?: AbortSignal;
|
|
532
540
|
pollIntervalMs?: number;
|
|
541
|
+
probeBeforeCreate?: boolean;
|
|
542
|
+
probeTimeoutMs?: number;
|
|
533
543
|
}): Promise<RunResult>;
|
|
534
544
|
/**
|
|
535
545
|
* Fire-and-forget the fan-out: upload every input, create ONE multi-job
|
|
@@ -546,8 +556,13 @@ export declare class FilesRecipe {
|
|
|
546
556
|
* Mirrors the single-file {@link Recipe.submit}.
|
|
547
557
|
*
|
|
548
558
|
* @param webhook Absolute callback URL the server POSTs lifecycle events to.
|
|
559
|
+
* @param options Opt-out / tune the best-effort video probe-before-create
|
|
560
|
+
* (2nd optional param so the positional `webhook` arg stays compatible).
|
|
549
561
|
*/
|
|
550
|
-
submit(webhook?: string
|
|
562
|
+
submit(webhook?: string, options?: {
|
|
563
|
+
probeBeforeCreate?: boolean;
|
|
564
|
+
probeTimeoutMs?: number;
|
|
565
|
+
}): Promise<Handle>;
|
|
551
566
|
/**
|
|
552
567
|
* Upload every input (verbatim for a pre-uploaded id; uploading a path /
|
|
553
568
|
* blob otherwise, emitting `{phase:'upload'}` progress) then create ONE
|
|
@@ -633,13 +648,22 @@ export declare class MergedRecipe {
|
|
|
633
648
|
onProgress?: (event: ProgressEvent) => void;
|
|
634
649
|
signal?: AbortSignal;
|
|
635
650
|
pollIntervalMs?: number;
|
|
651
|
+
probeBeforeCreate?: boolean;
|
|
652
|
+
probeTimeoutMs?: number;
|
|
636
653
|
}): Promise<RunResult>;
|
|
637
654
|
/**
|
|
638
655
|
* Fire-and-forget: upload + create the merge workflow (wiring `webhook` into
|
|
639
656
|
* `callback_url` when given), return a client-bound {@link Handle}. Does NOT
|
|
640
657
|
* wait for terminal status. Mirrors {@link Recipe.submit}.
|
|
658
|
+
*
|
|
659
|
+
* @param webhook Absolute callback URL the server POSTs lifecycle events to.
|
|
660
|
+
* @param options Opt-out / tune the best-effort video probe-before-create
|
|
661
|
+
* (2nd optional param so the positional `webhook` arg stays compatible).
|
|
641
662
|
*/
|
|
642
|
-
submit(webhook?: string
|
|
663
|
+
submit(webhook?: string, options?: {
|
|
664
|
+
probeBeforeCreate?: boolean;
|
|
665
|
+
probeTimeoutMs?: number;
|
|
666
|
+
}): Promise<Handle>;
|
|
643
667
|
/**
|
|
644
668
|
* Upload every input (verbatim for a pre-uploaded id; uploading a path / blob
|
|
645
669
|
* otherwise, emitting `{phase:'upload'}` progress) then create ONE merge
|
|
@@ -723,13 +747,22 @@ export declare class ArchivedRecipe {
|
|
|
723
747
|
onProgress?: (event: ProgressEvent) => void;
|
|
724
748
|
signal?: AbortSignal;
|
|
725
749
|
pollIntervalMs?: number;
|
|
750
|
+
probeBeforeCreate?: boolean;
|
|
751
|
+
probeTimeoutMs?: number;
|
|
726
752
|
}): Promise<RunResult>;
|
|
727
753
|
/**
|
|
728
754
|
* Fire-and-forget: upload + create the archive workflow (wiring `webhook` into
|
|
729
755
|
* `callback_url` when given), return a client-bound {@link Handle}. Mirrors
|
|
730
756
|
* {@link MergedRecipe.submit}.
|
|
757
|
+
*
|
|
758
|
+
* @param webhook Absolute callback URL the server POSTs lifecycle events to.
|
|
759
|
+
* @param options Opt-out / tune the best-effort video probe-before-create
|
|
760
|
+
* (2nd optional param so the positional `webhook` arg stays compatible).
|
|
731
761
|
*/
|
|
732
|
-
submit(webhook?: string
|
|
762
|
+
submit(webhook?: string, options?: {
|
|
763
|
+
probeBeforeCreate?: boolean;
|
|
764
|
+
probeTimeoutMs?: number;
|
|
765
|
+
}): Promise<Handle>;
|
|
733
766
|
private _uploadAllAndCreate;
|
|
734
767
|
/**
|
|
735
768
|
* Reject an invalid bundle BEFORE any upload fires — the archive schema allows
|
package/dist/file-first.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* Mirrors `packages/php/src/FileFirst/*`.
|
|
11
11
|
*/
|
|
12
12
|
import { GislConfigError, GislNetworkError, GislNoSuchKeyError, GislSinkError, GislTimeoutError, SseEndedWithoutTerminal } from './errors.js';
|
|
13
|
-
import { _detectCompressMedia, _detectAudioLossless, _consumeSseToTerminal, _pollToTerminal, _parseMaxWait, _checkAborted, } from './builder.js';
|
|
13
|
+
import { _detectCompressMedia, _detectAudioLossless, _consumeSseToTerminal, _pollToTerminal, _parseMaxWait, _checkAborted, _cappedProbeTimeoutMs, } from './builder.js';
|
|
14
14
|
import { LazyHttpDownloader } from './lazy-downloader.js';
|
|
15
15
|
import { resolveCompressOptions, } from './ergonomic/preset_resolver.js';
|
|
16
16
|
import { OptimizeFor } from './generated/sdk_spec/enums.js';
|
|
@@ -529,7 +529,7 @@ export class Recipe {
|
|
|
529
529
|
const deadline = Date.now() + _parseMaxWait(options.maxWait ?? 300_000);
|
|
530
530
|
// 1+2. Upload (when required) + create the workflow. Shared with submit()
|
|
531
531
|
// (which passes a webhook → callback_url). run() passes no webhook.
|
|
532
|
-
const created = await this._uploadAndCreate(undefined, deadline, onProgress, signal);
|
|
532
|
+
const created = await this._uploadAndCreate(undefined, deadline, onProgress, signal, options.probeBeforeCreate, options.probeTimeoutMs);
|
|
533
533
|
// 3. Wait to terminal status — SSE first, poll on a genuine SSE error.
|
|
534
534
|
// Caller-aborted + deadline-elapsed errors MUST propagate (not transient).
|
|
535
535
|
let finalStatus;
|
|
@@ -587,8 +587,11 @@ export class Recipe {
|
|
|
587
587
|
* sent. Mirrors the PHP `Recipe.submit()`.
|
|
588
588
|
*
|
|
589
589
|
* @param webhook Absolute callback URL the server POSTs lifecycle events to.
|
|
590
|
+
* @param options Opt-out (`probeBeforeCreate: false`) / tune (`probeTimeoutMs`)
|
|
591
|
+
* the best-effort video probe-before-create. Kept as a 2nd optional param so
|
|
592
|
+
* the existing positional `webhook` arg stays backward compatible.
|
|
590
593
|
*/
|
|
591
|
-
async submit(webhook) {
|
|
594
|
+
async submit(webhook, options) {
|
|
592
595
|
if (this.client === undefined) {
|
|
593
596
|
throw new GislConfigError('Recipe.submit() requires a client; build the recipe via gisl().file(...) rather than constructing Recipe directly.', { reason: 'no_client' });
|
|
594
597
|
}
|
|
@@ -597,7 +600,7 @@ export class Recipe {
|
|
|
597
600
|
// own request timeout, not an arbitrary submit-side cap. Pass `undefined`
|
|
598
601
|
// so the post-upload deadline check is skipped: a 300s cap here would throw
|
|
599
602
|
// on a slow-but-successful big upload before createWorkflow (codex).
|
|
600
|
-
const created = await this._uploadAndCreate(webhook, undefined);
|
|
603
|
+
const created = await this._uploadAndCreate(webhook, undefined, undefined, undefined, options?.probeBeforeCreate, options?.probeTimeoutMs);
|
|
601
604
|
return new Handle(created.workflowId, created.webhookSecret != null ? created.webhookSecret : undefined, this.client, this.recipeKey ?? null);
|
|
602
605
|
}
|
|
603
606
|
/**
|
|
@@ -610,10 +613,13 @@ export class Recipe {
|
|
|
610
613
|
* The post-upload deadline check carries a prior codex fix (9a117f04eb59): a
|
|
611
614
|
* slow upload must not proceed to createWorkflow past the deadline.
|
|
612
615
|
*/
|
|
613
|
-
async _uploadAndCreate(webhook, deadline, onProgress, signal) {
|
|
616
|
+
async _uploadAndCreate(webhook, deadline, onProgress, signal, probeBeforeCreate, probeTimeoutMs) {
|
|
614
617
|
// 1. Resolve the upload id. A pre-uploaded id skips the upload entirely;
|
|
615
618
|
// a path / blob is uploaded now, emitting {phase:'upload'} progress.
|
|
616
619
|
let fileId;
|
|
620
|
+
// A pre-uploaded id carries no local mime/size, so the video probe-gate is
|
|
621
|
+
// skipped for it (no `up` to read sizeBytes from).
|
|
622
|
+
let uploadSizeBytes;
|
|
617
623
|
if (this.input.kind === 'uploadId') {
|
|
618
624
|
fileId = this.input.fileId;
|
|
619
625
|
}
|
|
@@ -630,6 +636,7 @@ export class Recipe {
|
|
|
630
636
|
: {}),
|
|
631
637
|
});
|
|
632
638
|
fileId = up.fileId;
|
|
639
|
+
uploadSizeBytes = up.sizeBytes;
|
|
633
640
|
}
|
|
634
641
|
_checkAborted(signal);
|
|
635
642
|
// run() passes a whole-run deadline (the codex 9a117f04eb59 fix: a slow
|
|
@@ -638,6 +645,35 @@ export class Recipe {
|
|
|
638
645
|
if (deadline !== undefined && Date.now() >= deadline) {
|
|
639
646
|
throw new GislTimeoutError('Upload completed but maxWait elapsed before workflow could be created');
|
|
640
647
|
}
|
|
648
|
+
// Best-effort probe-before-create: for a VIDEO upload that went multipart,
|
|
649
|
+
// let the server see the codec + duration before createWorkflow so it
|
|
650
|
+
// admits the ~3× parallel split. Never-bounce — a give-up just proceeds.
|
|
651
|
+
// The probe wait is CAPPED to the remaining maxWait budget so a slow probe
|
|
652
|
+
// cannot push createWorkflow past the caller's deadline (an unset
|
|
653
|
+
// probeTimeoutMs under a deadline becomes the remaining budget, never the
|
|
654
|
+
// 30s default).
|
|
655
|
+
// Skip a pre-uploaded (`uploadId`) input entirely — no local mime/size to
|
|
656
|
+
// gate on (mirrors the multi-input seams, which omit uploadId inputs from
|
|
657
|
+
// their probe targets).
|
|
658
|
+
if (this.input.kind !== 'uploadId') {
|
|
659
|
+
await this.client.maybeWaitForVideoProbe(fileId, {
|
|
660
|
+
enabled: probeBeforeCreate ?? true,
|
|
661
|
+
isVideo: this.compressMediaHint() === 'video',
|
|
662
|
+
sizeBytes: uploadSizeBytes,
|
|
663
|
+
timeoutMs: _cappedProbeTimeoutMs(probeTimeoutMs, deadline),
|
|
664
|
+
signal,
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
// A cancel arriving during the FINAL successful probe request must not still
|
|
668
|
+
// create the workflow (maybeWaitForVideoProbe returns landed without a final
|
|
669
|
+
// abort re-check), so check here BEFORE createWorkflow.
|
|
670
|
+
_checkAborted(signal);
|
|
671
|
+
// RE-CHECK the deadline AFTER the probe wait: the wait itself consumes time,
|
|
672
|
+
// so a workflow must not be created past maxWait even when the wait was
|
|
673
|
+
// capped (mirrors the post-upload check above).
|
|
674
|
+
if (deadline !== undefined && Date.now() >= deadline) {
|
|
675
|
+
throw new GislTimeoutError('Probe wait completed but maxWait elapsed before workflow could be created');
|
|
676
|
+
}
|
|
641
677
|
// 2. Create the workflow from the lowered payload (callback_url built into
|
|
642
678
|
// the payload at construction when a webhook is given).
|
|
643
679
|
const payload = this.toWorkflowPayload(fileId, webhook);
|
|
@@ -884,7 +920,7 @@ export class FilesRecipe {
|
|
|
884
920
|
const deadline = Date.now() + _parseMaxWait(options.maxWait ?? 300_000);
|
|
885
921
|
// 1+2. Upload EVERY input + create ONE multi-job workflow. Shared with
|
|
886
922
|
// submit() (which passes a webhook → callback_url and no deadline).
|
|
887
|
-
const created = await this._uploadAllAndCreate(undefined, deadline, onProgress, signal);
|
|
923
|
+
const created = await this._uploadAllAndCreate(undefined, deadline, onProgress, signal, options.probeBeforeCreate, options.probeTimeoutMs);
|
|
888
924
|
// 3. Wait to terminal status — SSE first, poll on a genuine SSE error.
|
|
889
925
|
// `partially_failed` is a normal terminal state here (the helper treats it
|
|
890
926
|
// as terminal); only caller-aborted / deadline / API errors propagate.
|
|
@@ -941,12 +977,14 @@ export class FilesRecipe {
|
|
|
941
977
|
* Mirrors the single-file {@link Recipe.submit}.
|
|
942
978
|
*
|
|
943
979
|
* @param webhook Absolute callback URL the server POSTs lifecycle events to.
|
|
980
|
+
* @param options Opt-out / tune the best-effort video probe-before-create
|
|
981
|
+
* (2nd optional param so the positional `webhook` arg stays compatible).
|
|
944
982
|
*/
|
|
945
|
-
async submit(webhook) {
|
|
983
|
+
async submit(webhook, options) {
|
|
946
984
|
if (this.client === undefined) {
|
|
947
985
|
throw new GislConfigError('FilesRecipe.submit() requires a client; build the fan-out via gisl().files(...) rather than constructing FilesRecipe directly.', { reason: 'no_client' });
|
|
948
986
|
}
|
|
949
|
-
const created = await this._uploadAllAndCreate(webhook, undefined);
|
|
987
|
+
const created = await this._uploadAllAndCreate(webhook, undefined, undefined, undefined, options?.probeBeforeCreate, options?.probeTimeoutMs);
|
|
950
988
|
return new Handle(created.workflowId, created.webhookSecret != null ? created.webhookSecret : undefined, this.client, null);
|
|
951
989
|
}
|
|
952
990
|
/**
|
|
@@ -961,8 +999,11 @@ export class FilesRecipe {
|
|
|
961
999
|
* createWorkflow past maxWait); `submit()` passes `undefined`, so the
|
|
962
1000
|
* deadline checks are skipped.
|
|
963
1001
|
*/
|
|
964
|
-
async _uploadAllAndCreate(webhook, deadline, onProgress, signal) {
|
|
1002
|
+
async _uploadAllAndCreate(webhook, deadline, onProgress, signal, probeBeforeCreate, probeTimeoutMs) {
|
|
965
1003
|
const fileIds = [];
|
|
1004
|
+
// Track each freshly-uploaded input's probe-gate inputs (a pre-uploaded id
|
|
1005
|
+
// carries no local mime/size, so it is excluded — never probed).
|
|
1006
|
+
const probeTargets = [];
|
|
966
1007
|
for (const input of this.inputs) {
|
|
967
1008
|
// Fail fast between uploads — a deadline that elapses mid-batch should
|
|
968
1009
|
// not force every remaining input to upload before throwing.
|
|
@@ -986,12 +1027,39 @@ export class FilesRecipe {
|
|
|
986
1027
|
: {}),
|
|
987
1028
|
});
|
|
988
1029
|
fileIds.push(up.fileId);
|
|
1030
|
+
probeTargets.push({
|
|
1031
|
+
fileId: up.fileId,
|
|
1032
|
+
isVideo: _detectCompressMedia(source) === 'video',
|
|
1033
|
+
sizeBytes: up.sizeBytes,
|
|
1034
|
+
});
|
|
989
1035
|
}
|
|
990
1036
|
}
|
|
991
1037
|
_checkAborted(signal);
|
|
992
1038
|
if (deadline !== undefined && Date.now() >= deadline) {
|
|
993
1039
|
throw new GislTimeoutError('Uploads completed but maxWait elapsed before workflow could be created');
|
|
994
1040
|
}
|
|
1041
|
+
// Best-effort probe-before-create for the multipart-video inputs. Run the
|
|
1042
|
+
// waits CONCURRENTLY (Promise.all): each is bounded by the SAME capped
|
|
1043
|
+
// timeout, so the aggregate wall-clock stays ~timeout rather than N×timeout.
|
|
1044
|
+
// The cap is the remaining maxWait budget so the waits cannot push
|
|
1045
|
+
// createWorkflow past the caller's deadline. Never-bounce, so a give-up
|
|
1046
|
+
// just proceeds.
|
|
1047
|
+
const cappedProbeTimeoutMs = _cappedProbeTimeoutMs(probeTimeoutMs, deadline);
|
|
1048
|
+
await Promise.all(probeTargets.map((t) => this.client.maybeWaitForVideoProbe(t.fileId, {
|
|
1049
|
+
enabled: probeBeforeCreate ?? true,
|
|
1050
|
+
isVideo: t.isVideo,
|
|
1051
|
+
sizeBytes: t.sizeBytes,
|
|
1052
|
+
timeoutMs: cappedProbeTimeoutMs,
|
|
1053
|
+
signal,
|
|
1054
|
+
})));
|
|
1055
|
+
// A cancel arriving during a FINAL successful probe request must not still
|
|
1056
|
+
// create the workflow (the probe waits return landed without a final abort
|
|
1057
|
+
// re-check), so check here BEFORE createWorkflow.
|
|
1058
|
+
_checkAborted(signal);
|
|
1059
|
+
// RE-CHECK the deadline AFTER the probe waits (they consume time).
|
|
1060
|
+
if (deadline !== undefined && Date.now() >= deadline) {
|
|
1061
|
+
throw new GislTimeoutError('Probe wait completed but maxWait elapsed before workflow could be created');
|
|
1062
|
+
}
|
|
995
1063
|
const created = await this.client.createWorkflow(this.toWorkflowPayload(fileIds, webhook));
|
|
996
1064
|
_checkAborted(signal);
|
|
997
1065
|
return created;
|
|
@@ -1129,7 +1197,7 @@ export class MergedRecipe {
|
|
|
1129
1197
|
throw new GislConfigError('MergedRecipe.run() requires a client; build the merge via gisl().files(...).merge(...) rather than constructing MergedRecipe directly.', { reason: 'no_client' });
|
|
1130
1198
|
}
|
|
1131
1199
|
const deadline = Date.now() + _parseMaxWait(options.maxWait ?? 300_000);
|
|
1132
|
-
const created = await this._uploadAllAndCreate(undefined, deadline, onProgress, signal);
|
|
1200
|
+
const created = await this._uploadAllAndCreate(undefined, deadline, onProgress, signal, options.probeBeforeCreate, options.probeTimeoutMs);
|
|
1133
1201
|
let finalStatus;
|
|
1134
1202
|
try {
|
|
1135
1203
|
finalStatus = await _consumeSseToTerminal(this.client, {
|
|
@@ -1170,12 +1238,16 @@ export class MergedRecipe {
|
|
|
1170
1238
|
* Fire-and-forget: upload + create the merge workflow (wiring `webhook` into
|
|
1171
1239
|
* `callback_url` when given), return a client-bound {@link Handle}. Does NOT
|
|
1172
1240
|
* wait for terminal status. Mirrors {@link Recipe.submit}.
|
|
1241
|
+
*
|
|
1242
|
+
* @param webhook Absolute callback URL the server POSTs lifecycle events to.
|
|
1243
|
+
* @param options Opt-out / tune the best-effort video probe-before-create
|
|
1244
|
+
* (2nd optional param so the positional `webhook` arg stays compatible).
|
|
1173
1245
|
*/
|
|
1174
|
-
async submit(webhook) {
|
|
1246
|
+
async submit(webhook, options) {
|
|
1175
1247
|
if (this.client === undefined) {
|
|
1176
1248
|
throw new GislConfigError('MergedRecipe.submit() requires a client; build the merge via gisl().files(...).merge(...) rather than constructing MergedRecipe directly.', { reason: 'no_client' });
|
|
1177
1249
|
}
|
|
1178
|
-
const created = await this._uploadAllAndCreate(webhook, undefined);
|
|
1250
|
+
const created = await this._uploadAllAndCreate(webhook, undefined, undefined, undefined, options?.probeBeforeCreate, options?.probeTimeoutMs);
|
|
1179
1251
|
return new Handle(created.workflowId, created.webhookSecret != null ? created.webhookSecret : undefined, this.client, null);
|
|
1180
1252
|
}
|
|
1181
1253
|
// ---------------------------------------------------------------------------
|
|
@@ -1185,9 +1257,13 @@ export class MergedRecipe {
|
|
|
1185
1257
|
* workflow. Rejects fewer than 2 inputs BEFORE any upload fires. Shared first
|
|
1186
1258
|
* half of {@link run} + {@link submit}.
|
|
1187
1259
|
*/
|
|
1188
|
-
async _uploadAllAndCreate(webhook, deadline, onProgress, signal) {
|
|
1260
|
+
async _uploadAllAndCreate(webhook, deadline, onProgress, signal, probeBeforeCreate, probeTimeoutMs) {
|
|
1189
1261
|
this.validatePreUpload();
|
|
1190
1262
|
const fileIds = [];
|
|
1263
|
+
// Per-input video detection: merge's inferMediaKind decides the OUTPUT
|
|
1264
|
+
// media, not each input's, so detect per input via _detectCompressMedia.
|
|
1265
|
+
// A pre-uploaded id carries no local mime/size, so it is never probed.
|
|
1266
|
+
const probeTargets = [];
|
|
1191
1267
|
for (const input of this.inputs) {
|
|
1192
1268
|
_checkAborted(signal);
|
|
1193
1269
|
if (deadline !== undefined && Date.now() >= deadline) {
|
|
@@ -1209,12 +1285,36 @@ export class MergedRecipe {
|
|
|
1209
1285
|
: {}),
|
|
1210
1286
|
});
|
|
1211
1287
|
fileIds.push(up.fileId);
|
|
1288
|
+
probeTargets.push({
|
|
1289
|
+
fileId: up.fileId,
|
|
1290
|
+
isVideo: _detectCompressMedia(source) === 'video',
|
|
1291
|
+
sizeBytes: up.sizeBytes,
|
|
1292
|
+
});
|
|
1212
1293
|
}
|
|
1213
1294
|
}
|
|
1214
1295
|
_checkAborted(signal);
|
|
1215
1296
|
if (deadline !== undefined && Date.now() >= deadline) {
|
|
1216
1297
|
throw new GislTimeoutError('Uploads completed but maxWait elapsed before the merge workflow could be created');
|
|
1217
1298
|
}
|
|
1299
|
+
// Best-effort, concurrent probe-before-create for the multipart-video
|
|
1300
|
+
// inputs (never-bounce; each capped to the remaining maxWait budget so the
|
|
1301
|
+
// waits cannot push createWorkflow past the caller's deadline).
|
|
1302
|
+
const cappedProbeTimeoutMs = _cappedProbeTimeoutMs(probeTimeoutMs, deadline);
|
|
1303
|
+
await Promise.all(probeTargets.map((t) => this.client.maybeWaitForVideoProbe(t.fileId, {
|
|
1304
|
+
enabled: probeBeforeCreate ?? true,
|
|
1305
|
+
isVideo: t.isVideo,
|
|
1306
|
+
sizeBytes: t.sizeBytes,
|
|
1307
|
+
timeoutMs: cappedProbeTimeoutMs,
|
|
1308
|
+
signal,
|
|
1309
|
+
})));
|
|
1310
|
+
// A cancel arriving during a FINAL successful probe request must not still
|
|
1311
|
+
// create the workflow (the probe waits return landed without a final abort
|
|
1312
|
+
// re-check), so check here BEFORE createWorkflow.
|
|
1313
|
+
_checkAborted(signal);
|
|
1314
|
+
// RE-CHECK the deadline AFTER the probe waits (they consume time).
|
|
1315
|
+
if (deadline !== undefined && Date.now() >= deadline) {
|
|
1316
|
+
throw new GislTimeoutError('Probe wait completed but maxWait elapsed before the merge workflow could be created');
|
|
1317
|
+
}
|
|
1218
1318
|
const created = await this.client.createWorkflow(this.toWorkflowPayload(fileIds, webhook));
|
|
1219
1319
|
_checkAborted(signal);
|
|
1220
1320
|
return created;
|
|
@@ -1369,7 +1469,7 @@ export class ArchivedRecipe {
|
|
|
1369
1469
|
throw new GislConfigError('ArchivedRecipe.run() requires a client; build the bundle via gisl().files(...).archive(...) rather than constructing ArchivedRecipe directly.', { reason: 'no_client' });
|
|
1370
1470
|
}
|
|
1371
1471
|
const deadline = Date.now() + _parseMaxWait(options.maxWait ?? 300_000);
|
|
1372
|
-
const created = await this._uploadAllAndCreate(undefined, deadline, onProgress, signal);
|
|
1472
|
+
const created = await this._uploadAllAndCreate(undefined, deadline, onProgress, signal, options.probeBeforeCreate, options.probeTimeoutMs);
|
|
1373
1473
|
let finalStatus;
|
|
1374
1474
|
try {
|
|
1375
1475
|
finalStatus = await _consumeSseToTerminal(this.client, {
|
|
@@ -1409,18 +1509,26 @@ export class ArchivedRecipe {
|
|
|
1409
1509
|
* Fire-and-forget: upload + create the archive workflow (wiring `webhook` into
|
|
1410
1510
|
* `callback_url` when given), return a client-bound {@link Handle}. Mirrors
|
|
1411
1511
|
* {@link MergedRecipe.submit}.
|
|
1512
|
+
*
|
|
1513
|
+
* @param webhook Absolute callback URL the server POSTs lifecycle events to.
|
|
1514
|
+
* @param options Opt-out / tune the best-effort video probe-before-create
|
|
1515
|
+
* (2nd optional param so the positional `webhook` arg stays compatible).
|
|
1412
1516
|
*/
|
|
1413
|
-
async submit(webhook) {
|
|
1517
|
+
async submit(webhook, options) {
|
|
1414
1518
|
if (this.client === undefined) {
|
|
1415
1519
|
throw new GislConfigError('ArchivedRecipe.submit() requires a client; build the bundle via gisl().files(...).archive(...) rather than constructing ArchivedRecipe directly.', { reason: 'no_client' });
|
|
1416
1520
|
}
|
|
1417
|
-
const created = await this._uploadAllAndCreate(webhook, undefined);
|
|
1521
|
+
const created = await this._uploadAllAndCreate(webhook, undefined, undefined, undefined, options?.probeBeforeCreate, options?.probeTimeoutMs);
|
|
1418
1522
|
return new Handle(created.workflowId, created.webhookSecret != null ? created.webhookSecret : undefined, this.client, null);
|
|
1419
1523
|
}
|
|
1420
1524
|
// ---------------------------------------------------------------------------
|
|
1421
|
-
async _uploadAllAndCreate(webhook, deadline, onProgress, signal) {
|
|
1525
|
+
async _uploadAllAndCreate(webhook, deadline, onProgress, signal, probeBeforeCreate, probeTimeoutMs) {
|
|
1422
1526
|
this.validatePreUpload();
|
|
1423
1527
|
const fileIds = [];
|
|
1528
|
+
// Archive is media-agnostic (no inference) — detect per input via
|
|
1529
|
+
// _detectCompressMedia so only video uploads are probed. A pre-uploaded id
|
|
1530
|
+
// carries no local mime/size, so it is never probed.
|
|
1531
|
+
const probeTargets = [];
|
|
1424
1532
|
for (const input of this.inputs) {
|
|
1425
1533
|
_checkAborted(signal);
|
|
1426
1534
|
if (deadline !== undefined && Date.now() >= deadline) {
|
|
@@ -1442,12 +1550,36 @@ export class ArchivedRecipe {
|
|
|
1442
1550
|
: {}),
|
|
1443
1551
|
});
|
|
1444
1552
|
fileIds.push(up.fileId);
|
|
1553
|
+
probeTargets.push({
|
|
1554
|
+
fileId: up.fileId,
|
|
1555
|
+
isVideo: _detectCompressMedia(source) === 'video',
|
|
1556
|
+
sizeBytes: up.sizeBytes,
|
|
1557
|
+
});
|
|
1445
1558
|
}
|
|
1446
1559
|
}
|
|
1447
1560
|
_checkAborted(signal);
|
|
1448
1561
|
if (deadline !== undefined && Date.now() >= deadline) {
|
|
1449
1562
|
throw new GislTimeoutError('Uploads completed but maxWait elapsed before the archive workflow could be created');
|
|
1450
1563
|
}
|
|
1564
|
+
// Best-effort, concurrent probe-before-create for the multipart-video
|
|
1565
|
+
// inputs (never-bounce; each capped to the remaining maxWait budget so the
|
|
1566
|
+
// waits cannot push createWorkflow past the caller's deadline).
|
|
1567
|
+
const cappedProbeTimeoutMs = _cappedProbeTimeoutMs(probeTimeoutMs, deadline);
|
|
1568
|
+
await Promise.all(probeTargets.map((t) => this.client.maybeWaitForVideoProbe(t.fileId, {
|
|
1569
|
+
enabled: probeBeforeCreate ?? true,
|
|
1570
|
+
isVideo: t.isVideo,
|
|
1571
|
+
sizeBytes: t.sizeBytes,
|
|
1572
|
+
timeoutMs: cappedProbeTimeoutMs,
|
|
1573
|
+
signal,
|
|
1574
|
+
})));
|
|
1575
|
+
// A cancel arriving during a FINAL successful probe request must not still
|
|
1576
|
+
// create the workflow (the probe waits return landed without a final abort
|
|
1577
|
+
// re-check), so check here BEFORE createWorkflow.
|
|
1578
|
+
_checkAborted(signal);
|
|
1579
|
+
// RE-CHECK the deadline AFTER the probe waits (they consume time).
|
|
1580
|
+
if (deadline !== undefined && Date.now() >= deadline) {
|
|
1581
|
+
throw new GislTimeoutError('Probe wait completed but maxWait elapsed before the archive workflow could be created');
|
|
1582
|
+
}
|
|
1451
1583
|
const created = await this.client.createWorkflow(this.toWorkflowPayload(fileIds, webhook));
|
|
1452
1584
|
_checkAborted(signal);
|
|
1453
1585
|
return created;
|
package/dist/merge.d.ts
CHANGED
|
@@ -137,6 +137,15 @@ export declare class MergeBuilder {
|
|
|
137
137
|
private planSequence;
|
|
138
138
|
private inferMediaKind;
|
|
139
139
|
private uploadUniqueAssets;
|
|
140
|
+
/**
|
|
141
|
+
* Best-effort, concurrent probe-before-create for the multipart-video
|
|
142
|
+
* inputs (never-bounce; each bounded by the SAME capped timeout, so the
|
|
143
|
+
* aggregate wall-clock stays ~timeout rather than N×timeout). When `deadline`
|
|
144
|
+
* is set (the `run()` path) the timeout is capped to the remaining maxWait
|
|
145
|
+
* budget so the waits cannot push createWorkflow past the caller's deadline;
|
|
146
|
+
* `submit()` passes `undefined` (fire-and-forget, no cap).
|
|
147
|
+
*/
|
|
148
|
+
private waitForVideoProbes;
|
|
140
149
|
private buildPayload;
|
|
141
150
|
private opOptionsForResolved;
|
|
142
151
|
private awaitTerminal;
|
package/dist/merge.js
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
*/
|
|
28
28
|
import { uploadSource, jobOutputSource } from './types.js';
|
|
29
29
|
import { GislConfigError, GislNetworkError, GislPerInputOptionsNotSupportedError, GislTimeoutError, GislUndeclaredAssetError, GislUnusedAssetError, SseEndedWithoutTerminal, } from './errors.js';
|
|
30
|
-
import { _checkAborted, _consumeSseToTerminal, _parseMaxWait, _pollToTerminal, _projectResult, } from './builder.js';
|
|
30
|
+
import { _cappedProbeTimeoutMs, _checkAborted, _consumeSseToTerminal, _detectCompressMedia, _parseMaxWait, _pollToTerminal, _projectResult, } from './builder.js';
|
|
31
31
|
import { Handle } from './handle.js';
|
|
32
32
|
/**
|
|
33
33
|
* Construct a path-asset. Bare-string arguments to `merge(...)` are
|
|
@@ -91,15 +91,28 @@ export class MergeBuilder {
|
|
|
91
91
|
const plan = this.planSequence();
|
|
92
92
|
// 2. Upload each unique asset exactly ONCE. Pass the deadline so the
|
|
93
93
|
// upload loop can abort mid-batch on a slow connection.
|
|
94
|
+
const probeTargets = [];
|
|
94
95
|
const uploadedByAssetId = await this.uploadUniqueAssets(plan.uniqueAssets, {
|
|
95
96
|
signal,
|
|
96
97
|
onProgress,
|
|
97
98
|
deadline,
|
|
99
|
+
probeTargets,
|
|
98
100
|
});
|
|
99
101
|
_checkAborted(signal);
|
|
100
102
|
if (Date.now() >= deadline) {
|
|
101
103
|
throw new GislTimeoutError(`Upload(s) completed but maxWait elapsed before merge workflow could be created`);
|
|
102
104
|
}
|
|
105
|
+
// Best-effort probe-before-create for the multipart-video inputs (capped to
|
|
106
|
+
// the remaining maxWait budget).
|
|
107
|
+
await this.waitForVideoProbes(probeTargets, options.probeBeforeCreate, options.probeTimeoutMs, signal, deadline);
|
|
108
|
+
// A cancel arriving during a FINAL successful probe request must not still
|
|
109
|
+
// create the workflow (the probe waits return landed without a final abort
|
|
110
|
+
// re-check), so check here BEFORE createWorkflow.
|
|
111
|
+
_checkAborted(signal);
|
|
112
|
+
// RE-CHECK the deadline AFTER the probe waits (they consume time).
|
|
113
|
+
if (Date.now() >= deadline) {
|
|
114
|
+
throw new GislTimeoutError(`Probe wait completed but maxWait elapsed before merge workflow could be created`);
|
|
115
|
+
}
|
|
103
116
|
// 3. Build the merge JobDefinitionPayload (multi-input).
|
|
104
117
|
const payload = this.buildPayload(plan, uploadedByAssetId);
|
|
105
118
|
const created = await this.client.createWorkflow(payload);
|
|
@@ -135,7 +148,11 @@ export class MergeBuilder {
|
|
|
135
148
|
}
|
|
136
149
|
async submit(options) {
|
|
137
150
|
const plan = this.planSequence();
|
|
138
|
-
const
|
|
151
|
+
const probeTargets = [];
|
|
152
|
+
const uploadedByAssetId = await this.uploadUniqueAssets(plan.uniqueAssets, { probeTargets });
|
|
153
|
+
// Best-effort probe-before-create for the multipart-video inputs.
|
|
154
|
+
// Fire-and-forget — no deadline, so no cap (mirrors Recipe::submit()).
|
|
155
|
+
await this.waitForVideoProbes(probeTargets, options.probeBeforeCreate, options.probeTimeoutMs, undefined, undefined);
|
|
139
156
|
const payload = this.buildPayload(plan, uploadedByAssetId);
|
|
140
157
|
payload.callback_url = options.webhook;
|
|
141
158
|
const created = await this.client.createWorkflow(payload);
|
|
@@ -281,9 +298,32 @@ export class MergeBuilder {
|
|
|
281
298
|
}
|
|
282
299
|
const resp = await this.client.uploadFile(a.path, uploadOpts);
|
|
283
300
|
uploaded.set(id, resp.fileId);
|
|
301
|
+
opts.probeTargets?.push({
|
|
302
|
+
fileId: resp.fileId,
|
|
303
|
+
isVideo: _detectCompressMedia(a.path) === 'video',
|
|
304
|
+
sizeBytes: resp.sizeBytes,
|
|
305
|
+
});
|
|
284
306
|
}
|
|
285
307
|
return uploaded;
|
|
286
308
|
}
|
|
309
|
+
/**
|
|
310
|
+
* Best-effort, concurrent probe-before-create for the multipart-video
|
|
311
|
+
* inputs (never-bounce; each bounded by the SAME capped timeout, so the
|
|
312
|
+
* aggregate wall-clock stays ~timeout rather than N×timeout). When `deadline`
|
|
313
|
+
* is set (the `run()` path) the timeout is capped to the remaining maxWait
|
|
314
|
+
* budget so the waits cannot push createWorkflow past the caller's deadline;
|
|
315
|
+
* `submit()` passes `undefined` (fire-and-forget, no cap).
|
|
316
|
+
*/
|
|
317
|
+
async waitForVideoProbes(probeTargets, probeBeforeCreate, probeTimeoutMs, signal, deadline) {
|
|
318
|
+
const cappedProbeTimeoutMs = _cappedProbeTimeoutMs(probeTimeoutMs, deadline);
|
|
319
|
+
await Promise.all(probeTargets.map((t) => this.client.maybeWaitForVideoProbe(t.fileId, {
|
|
320
|
+
enabled: probeBeforeCreate ?? true,
|
|
321
|
+
isVideo: t.isVideo,
|
|
322
|
+
sizeBytes: t.sizeBytes,
|
|
323
|
+
timeoutMs: cappedProbeTimeoutMs,
|
|
324
|
+
signal,
|
|
325
|
+
})));
|
|
326
|
+
}
|
|
287
327
|
buildPayload(plan, uploadedByAssetId) {
|
|
288
328
|
// p0SuJEeK — the API rejects upload-direct multi-input
|
|
289
329
|
// (`MultiInputSource` excludes the `upload` leaf: "use type=job_output").
|