@orkestrel/workflow 0.0.6 → 0.0.8
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/README.md +23 -30
- package/dist/src/browser/index.d.ts +6 -6
- package/dist/src/browser/index.js +40 -30
- package/dist/src/browser/index.js.map +1 -1
- package/dist/src/core/index.cjs +1250 -298
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +547 -222
- package/dist/src/core/index.d.ts +547 -222
- package/dist/src/core/index.js +1235 -300
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +10 -8
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +4 -4
- package/dist/src/server/index.d.ts +4 -4
- package/dist/src/server/index.js +10 -8
- package/dist/src/server/index.js.map +1 -1
- package/package.json +24 -21
package/dist/src/core/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { arrayShape, compileGuard, createContract, integerShape, isArray, isBoolean,
|
|
1
|
+
import { arrayShape, attempt, cloneJSONRecord, cloneJSONValue, compileGuard, createContract, integerShape, isArray, isBoolean, isContractError, isFiniteNumber, isInteger, isJSONValue, isNonEmptyString, isRecord, literalShape, objectShape, optionalShape, rawShape, stringShape } from "@orkestrel/contract";
|
|
2
2
|
import { createDatabase, createMemoryDriver } from "@orkestrel/database";
|
|
3
3
|
import { createAbort } from "@orkestrel/abort";
|
|
4
4
|
import { Emitter } from "@orkestrel/emitter";
|
|
@@ -64,17 +64,18 @@ var Scheduler = class {
|
|
|
64
64
|
#sleep(ms, signal) {
|
|
65
65
|
if (signal?.aborted === true) return Promise.reject(signal.reason);
|
|
66
66
|
return new Promise((resolve, reject) => {
|
|
67
|
-
const onAbort = () => {
|
|
68
|
-
clearTimeout(handle);
|
|
69
|
-
reject(signal?.reason);
|
|
70
|
-
};
|
|
71
67
|
const handle = setTimeout(() => {
|
|
72
68
|
signal?.removeEventListener("abort", onAbort);
|
|
73
69
|
resolve();
|
|
74
70
|
}, ms);
|
|
71
|
+
const onAbort = this.#abort.bind(this, handle, reject, signal);
|
|
75
72
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
76
73
|
});
|
|
77
74
|
}
|
|
75
|
+
#abort(handle, reject, signal) {
|
|
76
|
+
clearTimeout(handle);
|
|
77
|
+
reject(signal?.reason);
|
|
78
|
+
}
|
|
78
79
|
};
|
|
79
80
|
//#endregion
|
|
80
81
|
//#region src/core/constants.ts
|
|
@@ -177,21 +178,20 @@ var TASK_TRANSITIONS = Object.freeze({
|
|
|
177
178
|
* phase may still DECLARE a larger explicit `concurrency` and pays that allocation knowingly.
|
|
178
179
|
*/
|
|
179
180
|
var DEFAULT_PHASE_CONCURRENCY = 1024;
|
|
181
|
+
/**
|
|
182
|
+
* The largest delay representable by the host timer APIs without overflow or clamping.
|
|
183
|
+
*/
|
|
184
|
+
var MAX_TIMER_MS = 2147483647;
|
|
180
185
|
//#endregion
|
|
181
186
|
//#region src/core/errors.ts
|
|
182
187
|
/**
|
|
183
|
-
* An error
|
|
188
|
+
* An error raised by the workflow runtime.
|
|
184
189
|
*
|
|
185
190
|
* @remarks
|
|
186
191
|
* Carries a {@link WorkflowErrorCode} and an optional `context` bag naming the
|
|
187
|
-
* offending node id / status.
|
|
192
|
+
* offending node id / status. Raised for an illegal lifecycle transition
|
|
188
193
|
* (`TRANSITION`), a structurally invalid {@link import('./types.js').WorkflowSnapshot}
|
|
189
|
-
*
|
|
190
|
-
* cyclic nested-workflow dispatch (`DEPTH`), and a malformed workflow-authoring-tool args
|
|
191
|
-
* blob (`TOOL`). `DEPTH` and `TOOL` are public type surface constructed by the
|
|
192
|
-
* `@orkestrel/tool` package's workflow-tool / agent-function adapters; on that seam the
|
|
193
|
-
* throw is ISOLATED by its `ToolManager` into the tool result's top-level `error`
|
|
194
|
-
* (AGENTS §14 — the universal tool-handler contract).
|
|
194
|
+
* boundary (`RESTORE`), or a refused structural/activity edit (`MUTATION`).
|
|
195
195
|
*/
|
|
196
196
|
var WorkflowError = class extends Error {
|
|
197
197
|
code;
|
|
@@ -200,7 +200,7 @@ var WorkflowError = class extends Error {
|
|
|
200
200
|
super(message);
|
|
201
201
|
this.name = "WorkflowError";
|
|
202
202
|
this.code = code;
|
|
203
|
-
this.context = context;
|
|
203
|
+
if (context !== void 0) this.context = context;
|
|
204
204
|
}
|
|
205
205
|
};
|
|
206
206
|
/**
|
|
@@ -219,7 +219,11 @@ var WorkflowError = class extends Error {
|
|
|
219
219
|
* ```
|
|
220
220
|
*/
|
|
221
221
|
function isWorkflowError(value) {
|
|
222
|
-
|
|
222
|
+
try {
|
|
223
|
+
return value instanceof WorkflowError;
|
|
224
|
+
} catch {
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
223
227
|
}
|
|
224
228
|
//#endregion
|
|
225
229
|
//#region src/core/helpers.ts
|
|
@@ -356,6 +360,17 @@ function canTransitionTask(from, to) {
|
|
|
356
360
|
return TASK_TRANSITIONS[from].includes(to);
|
|
357
361
|
}
|
|
358
362
|
/**
|
|
363
|
+
* Resolve a task's runtime silence window against its workflow default.
|
|
364
|
+
*
|
|
365
|
+
* @param value - The task-level override; any present non-positive or non-finite value disables
|
|
366
|
+
* @param fallback - The workflow-level default
|
|
367
|
+
* @returns A host-safe effective window (`1..MAX_TIMER_MS`), or `undefined`
|
|
368
|
+
*/
|
|
369
|
+
function resolveTaskSilence(value, fallback) {
|
|
370
|
+
if (value !== void 0) return Number.isFinite(value) && value > 0 && value <= 2147483647 ? value : void 0;
|
|
371
|
+
return fallback !== void 0 && Number.isFinite(fallback) && fallback > 0 && fallback <= 2147483647 ? fallback : void 0;
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
359
374
|
* Box a value as a {@link Success} — the graceful outcome half of a {@link Result}.
|
|
360
375
|
*
|
|
361
376
|
* @typeParam T - The boxed value's type
|
|
@@ -392,6 +407,20 @@ function failure(error) {
|
|
|
392
407
|
};
|
|
393
408
|
}
|
|
394
409
|
/**
|
|
410
|
+
* Normalize an unknown thrown value to a non-empty persistence-safe message.
|
|
411
|
+
*
|
|
412
|
+
* @param error - The caught value
|
|
413
|
+
* @returns A non-empty message without stack or cause data
|
|
414
|
+
*/
|
|
415
|
+
function errorToMessage(error) {
|
|
416
|
+
try {
|
|
417
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
418
|
+
return typeof message === "string" && message.length > 0 ? message : "unknown failure";
|
|
419
|
+
} catch {
|
|
420
|
+
return "unknown failure";
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
395
424
|
* Find the first {@link TaskResult} in a positional list whose boxed outcome is a
|
|
396
425
|
* `Failure` — the pure scan shared by a phase's and a workflow's derived-`failed`
|
|
397
426
|
* `fail`-event lookup.
|
|
@@ -428,11 +457,11 @@ function findFailure(results) {
|
|
|
428
457
|
* @returns The {@link WorkflowContext}
|
|
429
458
|
*/
|
|
430
459
|
function buildWorkflowContext(node) {
|
|
431
|
-
return {
|
|
460
|
+
return Object.freeze({
|
|
432
461
|
id: node.id,
|
|
433
462
|
name: node.name,
|
|
434
463
|
...node.description === void 0 ? {} : { description: node.description }
|
|
435
|
-
};
|
|
464
|
+
});
|
|
436
465
|
}
|
|
437
466
|
/**
|
|
438
467
|
* Build a {@link PhaseContext} — a phase's own identity plus a back-reference to its
|
|
@@ -443,10 +472,10 @@ function buildWorkflowContext(node) {
|
|
|
443
472
|
* @returns The {@link PhaseContext}
|
|
444
473
|
*/
|
|
445
474
|
function buildPhaseContext(workflow, node) {
|
|
446
|
-
return {
|
|
475
|
+
return Object.freeze({
|
|
447
476
|
...buildWorkflowContext(node),
|
|
448
|
-
workflow
|
|
449
|
-
};
|
|
477
|
+
workflow: buildWorkflowContext(workflow)
|
|
478
|
+
});
|
|
450
479
|
}
|
|
451
480
|
/**
|
|
452
481
|
* Build a {@link TaskContext} — a task's own identity plus a back-reference to its phase
|
|
@@ -458,31 +487,10 @@ function buildPhaseContext(workflow, node) {
|
|
|
458
487
|
* @returns The {@link TaskContext}
|
|
459
488
|
*/
|
|
460
489
|
function buildTaskContext(phase, node) {
|
|
461
|
-
return {
|
|
490
|
+
return Object.freeze({
|
|
462
491
|
...buildWorkflowContext(node),
|
|
463
|
-
phase
|
|
464
|
-
};
|
|
465
|
-
}
|
|
466
|
-
/**
|
|
467
|
-
* Narrow an `unknown` to a {@link WorkflowSnapshot} — the AGENTS §14 boundary guard for an
|
|
468
|
-
* UNTRUSTED snapshot read (a storage row a {@link import('./stores/DatabaseWorkflowStore.js').DatabaseWorkflowStore}
|
|
469
|
-
* reads back from its opaque JSON column, a snapshot loaded from disk).
|
|
470
|
-
*
|
|
471
|
-
* @remarks
|
|
472
|
-
* A total guard (it NEVER throws — adversarial input returns `false`, AGENTS §14). It checks the
|
|
473
|
-
* snapshot's SHAPE — `id` / `name` / `status` strings, a `boolean` `bail`, an array of `phases`,
|
|
474
|
-
* `created` / `updated` numbers — enough to safely impose the {@link WorkflowSnapshot} type at a
|
|
475
|
-
* storage boundary WITHOUT a cast. It is complementary to
|
|
476
|
-
* {@link import('./factories.js').assertSnapshot}, which validates the DEEPER invariant (every
|
|
477
|
-
* node's status / override drawn from the lifecycle vocabulary) and THROWS a `RESTORE`
|
|
478
|
-
* {@link import('./errors.js').WorkflowError} — the deep gate a {@link import('./factories.js').restoreWorkflow}
|
|
479
|
-
* applies. A boundary read narrows shape with this guard; a restore validates vocabulary with `assertSnapshot`.
|
|
480
|
-
*
|
|
481
|
-
* @param value - The value to test (an opaque storage read)
|
|
482
|
-
* @returns `true` when `value` has the structural shape of a {@link WorkflowSnapshot}
|
|
483
|
-
*/
|
|
484
|
-
function isWorkflowSnapshot(value) {
|
|
485
|
-
return isRecord(value) && isString(value.id) && isString(value.name) && isString(value.status) && isBoolean(value.bail) && isArray(value.phases) && isNumber(value.created) && isNumber(value.updated);
|
|
492
|
+
phase: buildPhaseContext(phase.workflow, phase)
|
|
493
|
+
});
|
|
486
494
|
}
|
|
487
495
|
/**
|
|
488
496
|
* Convert a {@link WorkflowDefinition} into an INITIAL {@link WorkflowSnapshot} — every
|
|
@@ -572,12 +580,91 @@ function taskDefinitionToSnapshot(task) {
|
|
|
572
580
|
...task.description === void 0 ? {} : { description: task.description },
|
|
573
581
|
status: "pending",
|
|
574
582
|
metadata: {},
|
|
583
|
+
attempts: 0,
|
|
575
584
|
...task.run === void 0 ? {} : { run: task.run },
|
|
576
585
|
...task.retries === void 0 ? {} : { retries: task.retries },
|
|
577
586
|
...task.timeout === void 0 ? {} : { timeout: task.timeout }
|
|
578
587
|
};
|
|
579
588
|
}
|
|
580
589
|
/**
|
|
590
|
+
* Convert interrupted running work into a recoverable pending suffix or an
|
|
591
|
+
* exhausted recovery failure without replenishing attempts.
|
|
592
|
+
*
|
|
593
|
+
* @param snapshot - A fully validated owned snapshot with no terminal overrides
|
|
594
|
+
* @returns The recovery projection
|
|
595
|
+
*/
|
|
596
|
+
function recoverWorkflowSnapshot(snapshot) {
|
|
597
|
+
const phases = [];
|
|
598
|
+
let halted = false;
|
|
599
|
+
const now = Math.max(Date.now(), snapshot.updated);
|
|
600
|
+
const workflow = buildWorkflowContext(snapshot);
|
|
601
|
+
for (const phase of snapshot.phases) {
|
|
602
|
+
const exhausted = /* @__PURE__ */ new Set();
|
|
603
|
+
for (const task of phase.tasks) {
|
|
604
|
+
const budget = (task.retries ?? 0) + 1;
|
|
605
|
+
if (task.status === "running" && task.attempts >= budget) exhausted.add(task.id);
|
|
606
|
+
}
|
|
607
|
+
const strict = phase.bail && (exhausted.size > 0 || phase.tasks.some((task) => task.status === "failed"));
|
|
608
|
+
const tasks = [];
|
|
609
|
+
for (const task of phase.tasks) {
|
|
610
|
+
const eligible = task.status === "pending" || task.status === "running";
|
|
611
|
+
if ((halted || strict) && eligible && !exhausted.has(task.id)) {
|
|
612
|
+
tasks.push({
|
|
613
|
+
...task,
|
|
614
|
+
status: "skipped"
|
|
615
|
+
});
|
|
616
|
+
continue;
|
|
617
|
+
}
|
|
618
|
+
if (!exhausted.has(task.id)) {
|
|
619
|
+
if (task.status === "running") {
|
|
620
|
+
const { activity: _activity, ...pending } = task;
|
|
621
|
+
tasks.push({
|
|
622
|
+
...pending,
|
|
623
|
+
status: "pending"
|
|
624
|
+
});
|
|
625
|
+
} else tasks.push(task);
|
|
626
|
+
continue;
|
|
627
|
+
}
|
|
628
|
+
const phaseContext = buildPhaseContext(workflow, phase);
|
|
629
|
+
const result = {
|
|
630
|
+
task: buildTaskContext(phaseContext, task),
|
|
631
|
+
phase: phaseContext,
|
|
632
|
+
workflow,
|
|
633
|
+
status: "failed",
|
|
634
|
+
result: {
|
|
635
|
+
success: false,
|
|
636
|
+
error: {
|
|
637
|
+
origin: "recovery",
|
|
638
|
+
message: `task '${task.id}' exhausted its retry budget during recovery`
|
|
639
|
+
}
|
|
640
|
+
},
|
|
641
|
+
timestamp: now
|
|
642
|
+
};
|
|
643
|
+
tasks.push({
|
|
644
|
+
...task,
|
|
645
|
+
status: "failed",
|
|
646
|
+
result
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
const status = derivePhaseStatus(tasks.map((task) => task.status));
|
|
650
|
+
phases.push({
|
|
651
|
+
...phase,
|
|
652
|
+
status,
|
|
653
|
+
tasks
|
|
654
|
+
});
|
|
655
|
+
if (strict) halted = true;
|
|
656
|
+
}
|
|
657
|
+
return {
|
|
658
|
+
...snapshot,
|
|
659
|
+
status: deriveWorkflowStatus(phases.map((phase) => ({
|
|
660
|
+
status: phase.status,
|
|
661
|
+
bail: phase.bail
|
|
662
|
+
}))),
|
|
663
|
+
phases,
|
|
664
|
+
updated: now
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
581
668
|
* Flatten a nested list of per-phase {@link TaskResult} lists into one positional list
|
|
582
669
|
* — the workflow tier of the result tree, built from each phase's `results()`.
|
|
583
670
|
*
|
|
@@ -660,16 +747,7 @@ function moveEntry(entries, key, index) {
|
|
|
660
747
|
* @returns A deferred `promise` plus its `resolve` / `reject`
|
|
661
748
|
*/
|
|
662
749
|
function createDeferred() {
|
|
663
|
-
|
|
664
|
-
let reject = () => {};
|
|
665
|
-
return {
|
|
666
|
-
promise: new Promise((res, rej) => {
|
|
667
|
-
resolve = res;
|
|
668
|
-
reject = rej;
|
|
669
|
-
}),
|
|
670
|
-
resolve,
|
|
671
|
-
reject
|
|
672
|
-
};
|
|
750
|
+
return Promise.withResolvers();
|
|
673
751
|
}
|
|
674
752
|
/**
|
|
675
753
|
* Park until `signal` aborts — a promise-parked wait (AGENTS §21), never a timer or
|
|
@@ -699,6 +777,302 @@ function parkSignal(signal) {
|
|
|
699
777
|
});
|
|
700
778
|
}
|
|
701
779
|
//#endregion
|
|
780
|
+
//#region src/core/validators.ts
|
|
781
|
+
/** Test the workflow lifecycle vocabulary. */
|
|
782
|
+
function isLifecycleStatus(value) {
|
|
783
|
+
return value === "pending" || value === "running" || value === "completed" || value === "failed" || value === "skipped" || value === "stopped";
|
|
784
|
+
}
|
|
785
|
+
/** Test a normalized persisted task failure. */
|
|
786
|
+
function isTaskFailure(value) {
|
|
787
|
+
try {
|
|
788
|
+
return isRecord(value) && Object.keys(value).every((key) => key === "origin" || key === "message") && (value.origin === "handler" || value.origin === "timeout" || value.origin === "recovery") && isNonEmptyString(value.message);
|
|
789
|
+
} catch {
|
|
790
|
+
return false;
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
/** Compare two optional description values. */
|
|
794
|
+
function matchesDescription(left, right) {
|
|
795
|
+
return left === right && (left === void 0 || typeof left === "string");
|
|
796
|
+
}
|
|
797
|
+
/** Test a result's lineage against its containing snapshot nodes. */
|
|
798
|
+
function isTaskResult(value, workflow, phase, task) {
|
|
799
|
+
try {
|
|
800
|
+
if (!isRecord(value) || !isRecord(workflow) || !isRecord(phase) || !isRecord(task) || !Object.keys(value).every((key) => key === "task" || key === "phase" || key === "workflow" || key === "status" || key === "result" || key === "timestamp") || !isLifecycleStatus(value.status) || value.status !== task.status || !isFiniteNumber(value.timestamp) || value.timestamp < 0 || !isRecord(value.task) || !isRecord(value.phase) || !isRecord(value.workflow) || !Object.keys(value.workflow).every((key) => key === "id" || key === "name" || key === "description") || !Object.keys(value.phase).every((key) => key === "id" || key === "name" || key === "description" || key === "workflow") || !Object.keys(value.task).every((key) => key === "id" || key === "name" || key === "description" || key === "phase")) return false;
|
|
801
|
+
if (value.task.id !== task.id || value.task.name !== task.name || !matchesDescription(value.task.description, task.description) || value.phase.id !== phase.id || value.phase.name !== phase.name || !matchesDescription(value.phase.description, phase.description) || value.workflow.id !== workflow.id || value.workflow.name !== workflow.name || !matchesDescription(value.workflow.description, workflow.description)) return false;
|
|
802
|
+
if (!isRecord(value.task.phase) || !isRecord(value.task.phase.workflow) || !isRecord(value.phase.workflow) || !Object.keys(value.task.phase).every((key) => key === "id" || key === "name" || key === "description" || key === "workflow") || !Object.keys(value.task.phase.workflow).every((key) => key === "id" || key === "name" || key === "description") || !Object.keys(value.phase.workflow).every((key) => key === "id" || key === "name" || key === "description")) return false;
|
|
803
|
+
if (value.task.phase.id !== phase.id || value.task.phase.name !== phase.name || !matchesDescription(value.task.phase.description, phase.description) || value.phase.workflow.id !== workflow.id || value.phase.workflow.name !== workflow.name || !matchesDescription(value.phase.workflow.description, workflow.description) || value.task.phase.workflow.id !== workflow.id || value.task.phase.workflow.name !== workflow.name || !matchesDescription(value.task.phase.workflow.description, workflow.description)) return false;
|
|
804
|
+
if (value.status === "completed") return isRecord(value.result) && value.result.success === true && Object.keys(value.result).every((key) => key === "success" || key === "value") && isJSONValue(value.result.value);
|
|
805
|
+
if (value.status === "failed") return isRecord(value.result) && value.result.success === false && Object.keys(value.result).every((key) => key === "success" || key === "error") && isTaskFailure(value.result.error);
|
|
806
|
+
return false;
|
|
807
|
+
} catch {
|
|
808
|
+
return false;
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
/**
|
|
812
|
+
* Validate a safe owned JSON graph as a coherent workflow snapshot.
|
|
813
|
+
*
|
|
814
|
+
* @remarks
|
|
815
|
+
* Callers at hostile boundaries use {@link isWorkflowSnapshot}, which owns the
|
|
816
|
+
* graph first so this semantic pass never observes accessors or prototypes.
|
|
817
|
+
*/
|
|
818
|
+
function isOwnedWorkflowSnapshot(value) {
|
|
819
|
+
try {
|
|
820
|
+
if (!isRecord(value) || !Object.keys(value).every((key) => key === "id" || key === "name" || key === "description" || key === "status" || key === "override" || key === "bail" || key === "phases" || key === "created" || key === "updated") || !isNonEmptyString(value.id) || !isNonEmptyString(value.name) || value.description !== void 0 && typeof value.description !== "string" || !isLifecycleStatus(value.status) || value.override !== void 0 && value.override !== "completed" && value.override !== "skipped" && value.override !== "stopped" || !isBoolean(value.bail) || !isArray(value.phases) || !isFiniteNumber(value.created) || value.created < 0 || !isFiniteNumber(value.updated) || value.updated < value.created) return false;
|
|
821
|
+
const phaseIds = /* @__PURE__ */ new Set();
|
|
822
|
+
const derivations = [];
|
|
823
|
+
let frontier = false;
|
|
824
|
+
let running = false;
|
|
825
|
+
let vacuous = true;
|
|
826
|
+
for (const phase of value.phases) {
|
|
827
|
+
if (!isRecord(phase) || !Object.keys(phase).every((key) => key === "id" || key === "name" || key === "description" || key === "status" || key === "override" || key === "bail" || key === "concurrency" || key === "tasks") || !isNonEmptyString(phase.id) || phaseIds.has(phase.id) || !isNonEmptyString(phase.name) || phase.description !== void 0 && typeof phase.description !== "string" || !isLifecycleStatus(phase.status) || phase.override !== void 0 && phase.override !== "skipped" && phase.override !== "stopped" || !isBoolean(phase.bail) || phase.concurrency !== void 0 && (!isInteger(phase.concurrency) || phase.concurrency < 1) || !isArray(phase.tasks)) return false;
|
|
828
|
+
const forced = phase.override === "skipped" || phase.override === "stopped";
|
|
829
|
+
const started = phase.status === "running" || phase.status === "completed" || phase.status === "failed";
|
|
830
|
+
if (!forced && frontier && started || phase.status === "running" && running) return false;
|
|
831
|
+
if (phase.status === "running") running = true;
|
|
832
|
+
if (!forced && (phase.status === "pending" || phase.status === "running" || phase.status === "failed" && phase.bail)) frontier = true;
|
|
833
|
+
phaseIds.add(phase.id);
|
|
834
|
+
const taskIds = /* @__PURE__ */ new Set();
|
|
835
|
+
const statuses = [];
|
|
836
|
+
if (phase.tasks.length > 0) vacuous = false;
|
|
837
|
+
for (const task of phase.tasks) {
|
|
838
|
+
if (!isRecord(task) || !Object.keys(task).every((key) => key === "id" || key === "name" || key === "description" || key === "status" || key === "result" || key === "metadata" || key === "attempts" || key === "run" || key === "retries" || key === "timeout" || key === "activity") || !isNonEmptyString(task.id) || taskIds.has(task.id) || !isNonEmptyString(task.name) || task.description !== void 0 && typeof task.description !== "string" || !isLifecycleStatus(task.status) || !isRecord(task.metadata) || !isJSONValue(task.metadata) || !isInteger(task.attempts) || task.attempts < 0 || task.run !== void 0 && !isNonEmptyString(task.run) || task.retries !== void 0 && (!isInteger(task.retries) || task.retries < 0) || task.timeout !== void 0 && (!isInteger(task.timeout) || task.timeout < 0 || task.timeout > 2147483647)) return false;
|
|
839
|
+
const budget = (task.retries ?? 0) + 1;
|
|
840
|
+
if (task.attempts > budget || task.status === "pending" && task.attempts >= budget) return false;
|
|
841
|
+
if (!(task.activity === void 0 || isTaskActivity(task.activity))) return false;
|
|
842
|
+
if (task.status === "running" || task.status === "completed" || task.status === "failed") {
|
|
843
|
+
if (task.attempts < 1 || task.activity === void 0) return false;
|
|
844
|
+
}
|
|
845
|
+
if (task.status === "pending" && task.activity !== void 0) return false;
|
|
846
|
+
if (task.status === "completed" || task.status === "failed") {
|
|
847
|
+
if (!isTaskResult(task.result, value, phase, task)) return false;
|
|
848
|
+
} else if (task.result !== void 0) return false;
|
|
849
|
+
taskIds.add(task.id);
|
|
850
|
+
statuses.push(task.status);
|
|
851
|
+
}
|
|
852
|
+
const derived = derivePhaseStatus(statuses);
|
|
853
|
+
if (phase.status !== (phase.override ?? derived) || phase.override !== void 0 && phase.status !== phase.override) return false;
|
|
854
|
+
derivations.push({
|
|
855
|
+
status: phase.status,
|
|
856
|
+
bail: phase.bail
|
|
857
|
+
});
|
|
858
|
+
}
|
|
859
|
+
const derived = deriveWorkflowStatus(derivations);
|
|
860
|
+
if (value.override === "completed") return value.status === "completed" && derived === "pending" && vacuous;
|
|
861
|
+
return value.status === (value.override ?? derived);
|
|
862
|
+
} catch {
|
|
863
|
+
return false;
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
/** Total hostile-boundary workflow snapshot guard. */
|
|
867
|
+
function isWorkflowSnapshot(value) {
|
|
868
|
+
const cloned = attempt(() => cloneJSONValue(value));
|
|
869
|
+
return cloned.success && isOwnedWorkflowSnapshot(cloned.value);
|
|
870
|
+
}
|
|
871
|
+
/** Test that every present behavior reference resolves before dispatch. */
|
|
872
|
+
function hasWorkflowHandlers(snapshot, functions) {
|
|
873
|
+
for (const phase of snapshot.phases) for (const task of phase.tasks) if (task.run !== void 0 && functions?.[task.run] === void 0) return false;
|
|
874
|
+
return true;
|
|
875
|
+
}
|
|
876
|
+
/** Locate the nearest identifiable node for an inconsistent owned snapshot. */
|
|
877
|
+
function workflowSnapshotContext(value) {
|
|
878
|
+
if (!isRecord(value) || !isArray(value.phases)) return void 0;
|
|
879
|
+
for (const phase of value.phases) {
|
|
880
|
+
if (!isRecord(phase)) continue;
|
|
881
|
+
const phaseContext = isNonEmptyString(phase.id) ? { phase: phase.id } : void 0;
|
|
882
|
+
if (!isBoolean(phase.bail) || phase.concurrency !== void 0 && (!isInteger(phase.concurrency) || phase.concurrency < 1) || !isArray(phase.tasks)) return phaseContext;
|
|
883
|
+
for (const task of phase.tasks) {
|
|
884
|
+
if (!isRecord(task)) continue;
|
|
885
|
+
if (task.run !== void 0 && !isNonEmptyString(task.run) || task.retries !== void 0 && (!isInteger(task.retries) || task.retries < 0) || task.timeout !== void 0 && (!isInteger(task.timeout) || task.timeout < 0 || task.timeout > 2147483647) || !isInteger(task.attempts) || task.attempts < 0) return {
|
|
886
|
+
...phaseContext ?? {},
|
|
887
|
+
...isNonEmptyString(task.id) ? { task: task.id } : {}
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
/**
|
|
893
|
+
* Test whether an unknown value is a valid whole-frame activity report.
|
|
894
|
+
*/
|
|
895
|
+
function isTaskActivityInput(value) {
|
|
896
|
+
try {
|
|
897
|
+
if (!isRecord(value)) return false;
|
|
898
|
+
const prototype = Object.getPrototypeOf(value);
|
|
899
|
+
if (prototype !== Object.prototype && prototype !== null || !Object.keys(value).every((key) => key === "note" || key === "progress" || key === "operations" || key === "constraints")) return false;
|
|
900
|
+
const note = value.note;
|
|
901
|
+
const progress = value.progress;
|
|
902
|
+
const operations = value.operations;
|
|
903
|
+
const constraints = value.constraints;
|
|
904
|
+
if (note !== void 0 && !isNonEmptyString(note)) return false;
|
|
905
|
+
if (progress !== void 0) {
|
|
906
|
+
if (!isRecord(progress)) return false;
|
|
907
|
+
const progressPrototype = Object.getPrototypeOf(progress);
|
|
908
|
+
if (progressPrototype !== Object.prototype && progressPrototype !== null || !Object.keys(progress).every((key) => key === "current" || key === "total" || key === "unit")) return false;
|
|
909
|
+
const current = progress.current;
|
|
910
|
+
const total = progress.total;
|
|
911
|
+
const unit = progress.unit;
|
|
912
|
+
if (!isFiniteNumber(current) || current < 0 || total !== void 0 && (!isFiniteNumber(total) || total < current) || unit !== void 0 && !isNonEmptyString(unit)) return false;
|
|
913
|
+
}
|
|
914
|
+
if (operations !== void 0) {
|
|
915
|
+
if (!isArray(operations)) return false;
|
|
916
|
+
const ids = /* @__PURE__ */ new Set();
|
|
917
|
+
for (const operation of operations) {
|
|
918
|
+
if (!isRecord(operation)) return false;
|
|
919
|
+
const operationPrototype = Object.getPrototypeOf(operation);
|
|
920
|
+
if (operationPrototype !== Object.prototype && operationPrototype !== null || !Object.keys(operation).every((key) => key === "id" || key === "name" || key === "started")) return false;
|
|
921
|
+
const id = operation.id;
|
|
922
|
+
const name = operation.name;
|
|
923
|
+
const started = operation.started;
|
|
924
|
+
if (!isNonEmptyString(id) || !isNonEmptyString(name) || !isFiniteNumber(started) || started < 0 || ids.has(id)) return false;
|
|
925
|
+
ids.add(id);
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
if (constraints !== void 0) {
|
|
929
|
+
if (!isArray(constraints)) return false;
|
|
930
|
+
const ids = /* @__PURE__ */ new Set();
|
|
931
|
+
for (const constraint of constraints) {
|
|
932
|
+
if (!isRecord(constraint)) return false;
|
|
933
|
+
const constraintPrototype = Object.getPrototypeOf(constraint);
|
|
934
|
+
if (constraintPrototype !== Object.prototype && constraintPrototype !== null || !Object.keys(constraint).every((key) => key === "id" || key === "name" || key === "started")) return false;
|
|
935
|
+
const id = constraint.id;
|
|
936
|
+
const name = constraint.name;
|
|
937
|
+
const started = constraint.started;
|
|
938
|
+
if (!isNonEmptyString(id) || !isNonEmptyString(name) || !isFiniteNumber(started) || started < 0 || ids.has(id)) return false;
|
|
939
|
+
ids.add(id);
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
return true;
|
|
943
|
+
} catch {
|
|
944
|
+
return false;
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
/**
|
|
948
|
+
* Test whether an unknown value is valid persisted task activity.
|
|
949
|
+
*/
|
|
950
|
+
function isTaskActivity(value) {
|
|
951
|
+
try {
|
|
952
|
+
if (!isRecord(value)) return false;
|
|
953
|
+
const prototype = Object.getPrototypeOf(value);
|
|
954
|
+
if (prototype !== Object.prototype && prototype !== null || !Object.keys(value).every((key) => key === "note" || key === "progress" || key === "operations" || key === "constraints" || key === "updated")) return false;
|
|
955
|
+
const note = value.note;
|
|
956
|
+
const progress = value.progress;
|
|
957
|
+
const operations = value.operations;
|
|
958
|
+
const constraints = value.constraints;
|
|
959
|
+
const updated = value.updated;
|
|
960
|
+
if (operations === void 0 || constraints === void 0 || !isFiniteNumber(updated) || updated < 0) return false;
|
|
961
|
+
return isTaskActivityInput({
|
|
962
|
+
...note === void 0 ? {} : { note },
|
|
963
|
+
...progress === void 0 ? {} : { progress },
|
|
964
|
+
operations,
|
|
965
|
+
constraints
|
|
966
|
+
});
|
|
967
|
+
} catch {
|
|
968
|
+
return false;
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
//#endregion
|
|
972
|
+
//#region src/core/cloners.ts
|
|
973
|
+
/**
|
|
974
|
+
* Validate and own a workflow snapshot before live construction.
|
|
975
|
+
*
|
|
976
|
+
* @param input - The hostile snapshot boundary
|
|
977
|
+
* @returns A deeply owned frozen snapshot
|
|
978
|
+
*/
|
|
979
|
+
function cloneWorkflowSnapshot(input) {
|
|
980
|
+
try {
|
|
981
|
+
const cloned = cloneJSONValue(input);
|
|
982
|
+
if (!isOwnedWorkflowSnapshot(cloned)) throw new WorkflowError("RESTORE", "workflow snapshot is inconsistent", workflowSnapshotContext(cloned));
|
|
983
|
+
return cloned;
|
|
984
|
+
} catch (error) {
|
|
985
|
+
if (isWorkflowError(error)) throw error;
|
|
986
|
+
if (isContractError(error)) throw new WorkflowError("RESTORE", `workflow snapshot could not be read safely: ${error.message}`);
|
|
987
|
+
throw new WorkflowError("RESTORE", "workflow snapshot could not be read safely");
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
/**
|
|
991
|
+
* Validate and clone one complete task activity frame.
|
|
992
|
+
*
|
|
993
|
+
* @remarks
|
|
994
|
+
* This is the hostile boundary behind task reports and snapshot hydration. Supplying
|
|
995
|
+
* `updated` stamps an input frame without reading an `updated` property from it; omitting
|
|
996
|
+
* `updated` restores a stored frame and reads its persisted timestamp exactly once. Every
|
|
997
|
+
* untrusted property is captured once inside one protected boundary. The returned frame,
|
|
998
|
+
* collections, progress, operations, and constraints are copied and frozen.
|
|
999
|
+
*
|
|
1000
|
+
* @param input - The untrusted complete activity frame
|
|
1001
|
+
* @param updated - An optional accepted timestamp used instead of a persisted `updated`
|
|
1002
|
+
* @returns An immutable cloned {@link TaskActivity}
|
|
1003
|
+
* @throws {WorkflowError} With `MUTATION` when the frame cannot be read or validated
|
|
1004
|
+
*/
|
|
1005
|
+
function cloneTaskActivity(input, updated) {
|
|
1006
|
+
try {
|
|
1007
|
+
if (!isRecord(input)) throw new WorkflowError("MUTATION", "task activity must be a record");
|
|
1008
|
+
const inputPrototype = Object.getPrototypeOf(input);
|
|
1009
|
+
if (inputPrototype !== Object.prototype && inputPrototype !== null || !Object.keys(input).every((key) => key === "note" || key === "progress" || key === "operations" || key === "constraints" || updated === void 0 && key === "updated")) throw new WorkflowError("MUTATION", "task activity must be a record");
|
|
1010
|
+
const note = input.note;
|
|
1011
|
+
const progressInput = input.progress;
|
|
1012
|
+
const operationsInput = input.operations;
|
|
1013
|
+
const constraintsInput = input.constraints;
|
|
1014
|
+
const accepted = updated === void 0 ? input.updated : updated;
|
|
1015
|
+
const operationInputs = operationsInput === void 0 ? [] : isArray(operationsInput) ? [...operationsInput] : void 0;
|
|
1016
|
+
if (operationInputs === void 0) throw new WorkflowError("MUTATION", "task activity operations must be an array");
|
|
1017
|
+
const operations = [];
|
|
1018
|
+
for (const operation of operationInputs) {
|
|
1019
|
+
if (!isRecord(operation)) throw new WorkflowError("MUTATION", "task activity contains an invalid operation");
|
|
1020
|
+
const operationPrototype = Object.getPrototypeOf(operation);
|
|
1021
|
+
if (operationPrototype !== Object.prototype && operationPrototype !== null || !Object.keys(operation).every((key) => key === "id" || key === "name" || key === "started")) throw new WorkflowError("MUTATION", "task activity contains an invalid operation");
|
|
1022
|
+
const id = operation.id;
|
|
1023
|
+
const name = operation.name;
|
|
1024
|
+
const started = operation.started;
|
|
1025
|
+
operations.push(Object.freeze({
|
|
1026
|
+
id,
|
|
1027
|
+
name,
|
|
1028
|
+
started
|
|
1029
|
+
}));
|
|
1030
|
+
}
|
|
1031
|
+
let progress;
|
|
1032
|
+
if (progressInput !== void 0) {
|
|
1033
|
+
if (!isRecord(progressInput)) throw new WorkflowError("MUTATION", "task activity contains invalid progress");
|
|
1034
|
+
const progressPrototype = Object.getPrototypeOf(progressInput);
|
|
1035
|
+
if (progressPrototype !== Object.prototype && progressPrototype !== null || !Object.keys(progressInput).every((key) => key === "current" || key === "total" || key === "unit")) throw new WorkflowError("MUTATION", "task activity contains invalid progress");
|
|
1036
|
+
const current = progressInput.current;
|
|
1037
|
+
const total = progressInput.total;
|
|
1038
|
+
const unit = progressInput.unit;
|
|
1039
|
+
progress = Object.freeze({
|
|
1040
|
+
current,
|
|
1041
|
+
...total === void 0 ? {} : { total },
|
|
1042
|
+
...unit === void 0 ? {} : { unit }
|
|
1043
|
+
});
|
|
1044
|
+
}
|
|
1045
|
+
const constraintInputs = constraintsInput === void 0 ? [] : isArray(constraintsInput) ? [...constraintsInput] : void 0;
|
|
1046
|
+
if (constraintInputs === void 0) throw new WorkflowError("MUTATION", "task activity constraints must be an array");
|
|
1047
|
+
const constraints = [];
|
|
1048
|
+
for (const constraint of constraintInputs) {
|
|
1049
|
+
if (!isRecord(constraint)) throw new WorkflowError("MUTATION", "task activity contains an invalid constraint");
|
|
1050
|
+
const constraintPrototype = Object.getPrototypeOf(constraint);
|
|
1051
|
+
if (constraintPrototype !== Object.prototype && constraintPrototype !== null || !Object.keys(constraint).every((key) => key === "id" || key === "name" || key === "started")) throw new WorkflowError("MUTATION", "task activity contains an invalid constraint");
|
|
1052
|
+
const id = constraint.id;
|
|
1053
|
+
const name = constraint.name;
|
|
1054
|
+
const started = constraint.started;
|
|
1055
|
+
constraints.push(Object.freeze({
|
|
1056
|
+
id,
|
|
1057
|
+
name,
|
|
1058
|
+
started
|
|
1059
|
+
}));
|
|
1060
|
+
}
|
|
1061
|
+
const activity = Object.freeze({
|
|
1062
|
+
...note === void 0 ? {} : { note },
|
|
1063
|
+
...progress === void 0 ? {} : { progress },
|
|
1064
|
+
operations: Object.freeze(operations),
|
|
1065
|
+
constraints: Object.freeze(constraints),
|
|
1066
|
+
updated: accepted
|
|
1067
|
+
});
|
|
1068
|
+
if (!isTaskActivity(activity)) throw new WorkflowError("MUTATION", "task activity is invalid");
|
|
1069
|
+
return activity;
|
|
1070
|
+
} catch (error) {
|
|
1071
|
+
if (isWorkflowError(error)) throw error;
|
|
1072
|
+
throw new WorkflowError("MUTATION", "task activity could not be read safely");
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
//#endregion
|
|
702
1076
|
//#region src/core/shapers.ts
|
|
703
1077
|
/**
|
|
704
1078
|
* The shape of a {@link import('./types.js').TaskDefinition} — identity plus an optional
|
|
@@ -725,6 +1099,7 @@ var taskShape = objectShape({
|
|
|
725
1099
|
})),
|
|
726
1100
|
timeout: optionalShape(integerShape({
|
|
727
1101
|
min: 0,
|
|
1102
|
+
max: MAX_TIMER_MS,
|
|
728
1103
|
description: "Per-attempt deadline in milliseconds; overrides the phase default. Omitted means no deadline."
|
|
729
1104
|
}))
|
|
730
1105
|
});
|
|
@@ -871,13 +1246,14 @@ var DatabaseWorkflowStore = class {
|
|
|
871
1246
|
async get(id) {
|
|
872
1247
|
const row = await this.#table.get(id);
|
|
873
1248
|
if (row === void 0) return void 0;
|
|
874
|
-
return
|
|
1249
|
+
return cloneWorkflowSnapshot(row.snapshot);
|
|
875
1250
|
}
|
|
876
1251
|
/** Insert or replace under the snapshot's OWN `id` (no separate id param) — the row is `{ id, snapshot }`. */
|
|
877
1252
|
async set(snapshot) {
|
|
1253
|
+
const owned = cloneWorkflowSnapshot(snapshot);
|
|
878
1254
|
await this.#table.set({
|
|
879
|
-
id:
|
|
880
|
-
snapshot
|
|
1255
|
+
id: owned.id,
|
|
1256
|
+
snapshot: owned
|
|
881
1257
|
});
|
|
882
1258
|
}
|
|
883
1259
|
/** Drop a snapshot by id; an absent id is a no-op (no throw). */
|
|
@@ -928,10 +1304,12 @@ var DatabaseWorkflowStore = class {
|
|
|
928
1304
|
var MemoryWorkflowStore = class {
|
|
929
1305
|
#snapshots = /* @__PURE__ */ new Map();
|
|
930
1306
|
get(id) {
|
|
931
|
-
|
|
1307
|
+
const snapshot = this.#snapshots.get(id);
|
|
1308
|
+
return Promise.resolve(snapshot === void 0 ? void 0 : cloneWorkflowSnapshot(snapshot));
|
|
932
1309
|
}
|
|
933
1310
|
set(snapshot) {
|
|
934
|
-
|
|
1311
|
+
const owned = cloneWorkflowSnapshot(snapshot);
|
|
1312
|
+
this.#snapshots.set(owned.id, owned);
|
|
935
1313
|
return Promise.resolve();
|
|
936
1314
|
}
|
|
937
1315
|
delete(id) {
|
|
@@ -954,9 +1332,8 @@ var MemoryWorkflowStore = class {
|
|
|
954
1332
|
* `TRANSITION` {@link WorkflowError} on an illegal move (e.g. completing a non-`running`
|
|
955
1333
|
* task) — the legal graph is the single source of truth, so the leaf can never reach an
|
|
956
1334
|
* impossible state.
|
|
957
|
-
* - **
|
|
958
|
-
*
|
|
959
|
-
* one and reinstate it AS an override — preserving the round-trip.
|
|
1335
|
+
* - **Snapshot fidelity.** A leaf needs no override: `skipped` / `stopped` are explicit terminal
|
|
1336
|
+
* statuses, and restore reinstates the leaf directly from {@link TaskSnapshot.status}.
|
|
960
1337
|
* - **The cascade.** Every status change records its boxed result (when any), fires the leaf's
|
|
961
1338
|
* OWN event, THEN calls the parent phase's `#recompute` (injected at construction) so the
|
|
962
1339
|
* transition propagates UP (Task → Phase → Workflow re-derive). The own-event-before-cascade
|
|
@@ -971,7 +1348,8 @@ var MemoryWorkflowStore = class {
|
|
|
971
1348
|
* matching {@link import('../types.js').TaskDefinition} / {@link TaskSnapshot} field. `handler`
|
|
972
1349
|
* is the RUNTIME-ONLY counterpart — `run` resolved ONCE at construction against the
|
|
973
1350
|
* workflow-level {@link import('../types.js').WorkflowOptions.functions} registry — and is
|
|
974
|
-
* NEVER persisted; `undefined` when `run` is omitted or unregistered
|
|
1351
|
+
* NEVER persisted; `undefined` when `run` is omitted or unregistered. Only omission is a
|
|
1352
|
+
* deliberate no-op; unresolved named work is rejected before dispatch.
|
|
975
1353
|
*/
|
|
976
1354
|
var Task = class {
|
|
977
1355
|
#context;
|
|
@@ -983,29 +1361,57 @@ var Task = class {
|
|
|
983
1361
|
#status;
|
|
984
1362
|
#result;
|
|
985
1363
|
#name;
|
|
986
|
-
#description;
|
|
987
1364
|
#run;
|
|
988
1365
|
#retries;
|
|
989
1366
|
#timeout;
|
|
1367
|
+
#attempts;
|
|
990
1368
|
#handler;
|
|
991
|
-
|
|
992
|
-
|
|
1369
|
+
#abort;
|
|
1370
|
+
#silence;
|
|
1371
|
+
#onSilence;
|
|
1372
|
+
#liveness;
|
|
1373
|
+
#activity;
|
|
1374
|
+
#paused;
|
|
1375
|
+
#gate;
|
|
1376
|
+
#timerSignal;
|
|
1377
|
+
constructor(context, phase, workflow, recompute, options, status = "pending", result, run, retries, timeout, metadata = {}, attempts = 0, activity, handler, silence) {
|
|
1378
|
+
this.#context = buildTaskContext(context.phase, context);
|
|
993
1379
|
this.#phase = phase;
|
|
994
1380
|
this.#workflow = workflow;
|
|
995
1381
|
this.#recompute = recompute;
|
|
996
|
-
|
|
1382
|
+
try {
|
|
1383
|
+
this.#metadata = cloneJSONRecord(options?.metadata ?? metadata);
|
|
1384
|
+
} catch (error) {
|
|
1385
|
+
if (isContractError(error)) throw new WorkflowError("RESTORE", `task '${context.id}' metadata could not be read safely: ${error.message}`, { task: context.id });
|
|
1386
|
+
throw new WorkflowError("RESTORE", `task '${context.id}' metadata could not be read safely`, { task: context.id });
|
|
1387
|
+
}
|
|
997
1388
|
this.#emitter = new Emitter({
|
|
998
|
-
on: options
|
|
999
|
-
error: options
|
|
1389
|
+
...options?.on === void 0 ? {} : { on: options.on },
|
|
1390
|
+
...options?.error === void 0 ? {} : { error: options.error }
|
|
1000
1391
|
});
|
|
1001
1392
|
this.#status = status;
|
|
1002
1393
|
this.#result = result;
|
|
1003
1394
|
this.#name = context.name;
|
|
1004
|
-
|
|
1395
|
+
if (context.description !== void 0) Object.defineProperty(this, "description", {
|
|
1396
|
+
configurable: true,
|
|
1397
|
+
value: context.description
|
|
1398
|
+
});
|
|
1005
1399
|
this.#run = run;
|
|
1006
1400
|
this.#retries = retries;
|
|
1007
1401
|
this.#timeout = timeout;
|
|
1402
|
+
this.#attempts = attempts;
|
|
1008
1403
|
this.#handler = handler;
|
|
1404
|
+
this.#abort = createAbort();
|
|
1405
|
+
this.#silence = resolveTaskSilence(options?.silence, silence);
|
|
1406
|
+
this.#onSilence = this.#expire.bind(this);
|
|
1407
|
+
this.#liveness = this.#silence === void 0 ? void 0 : createTimeout({
|
|
1408
|
+
ms: this.#silence,
|
|
1409
|
+
signal: this.#abort.signal
|
|
1410
|
+
});
|
|
1411
|
+
this.#activity = activity === void 0 ? void 0 : cloneTaskActivity(activity);
|
|
1412
|
+
this.#paused = false;
|
|
1413
|
+
this.#gate = void 0;
|
|
1414
|
+
this.#timerSignal = void 0;
|
|
1009
1415
|
}
|
|
1010
1416
|
get emitter() {
|
|
1011
1417
|
return this.#emitter;
|
|
@@ -1016,9 +1422,6 @@ var Task = class {
|
|
|
1016
1422
|
get name() {
|
|
1017
1423
|
return this.#name;
|
|
1018
1424
|
}
|
|
1019
|
-
get description() {
|
|
1020
|
-
return this.#description;
|
|
1021
|
-
}
|
|
1022
1425
|
get context() {
|
|
1023
1426
|
return this.#context;
|
|
1024
1427
|
}
|
|
@@ -1034,6 +1437,9 @@ var Task = class {
|
|
|
1034
1437
|
get result() {
|
|
1035
1438
|
return this.#result;
|
|
1036
1439
|
}
|
|
1440
|
+
get attempts() {
|
|
1441
|
+
return this.#attempts;
|
|
1442
|
+
}
|
|
1037
1443
|
get run() {
|
|
1038
1444
|
return this.#run;
|
|
1039
1445
|
}
|
|
@@ -1046,40 +1452,120 @@ var Task = class {
|
|
|
1046
1452
|
get timeout() {
|
|
1047
1453
|
return this.#timeout;
|
|
1048
1454
|
}
|
|
1455
|
+
get activity() {
|
|
1456
|
+
return this.#activity;
|
|
1457
|
+
}
|
|
1458
|
+
get silence() {
|
|
1459
|
+
return this.#silence;
|
|
1460
|
+
}
|
|
1461
|
+
get silent() {
|
|
1462
|
+
return this.#status === "running" && this.#liveness?.expired === true;
|
|
1463
|
+
}
|
|
1464
|
+
get paused() {
|
|
1465
|
+
return this.#paused;
|
|
1466
|
+
}
|
|
1467
|
+
get signal() {
|
|
1468
|
+
return this.#abort.signal;
|
|
1469
|
+
}
|
|
1049
1470
|
start() {
|
|
1050
|
-
this.#
|
|
1471
|
+
const budget = Math.max(0, this.#retries ?? 0) + 1;
|
|
1472
|
+
if (this.#status !== "pending" && this.#status !== "running" || this.#attempts >= budget) throw new WorkflowError("TRANSITION", `task '${this.id}' cannot start another attempt`, {
|
|
1473
|
+
task: this.id,
|
|
1474
|
+
status: this.#status,
|
|
1475
|
+
attempts: this.#attempts,
|
|
1476
|
+
budget
|
|
1477
|
+
});
|
|
1478
|
+
if (this.#status === "pending") this.#transition("running");
|
|
1479
|
+
this.#attempts += 1;
|
|
1480
|
+
this.#activity = cloneTaskActivity({}, this.#stamp());
|
|
1481
|
+
this.#arm();
|
|
1051
1482
|
this.#emitter.emit("start", this.id);
|
|
1052
1483
|
this.#escalate();
|
|
1053
1484
|
}
|
|
1054
1485
|
complete(value) {
|
|
1486
|
+
let owned;
|
|
1487
|
+
try {
|
|
1488
|
+
owned = cloneJSONValue(value);
|
|
1489
|
+
} catch (error) {
|
|
1490
|
+
if (isContractError(error)) throw new WorkflowError("RESTORE", `task '${this.id}' result could not be read safely: ${error.message}`, { task: this.id });
|
|
1491
|
+
throw new WorkflowError("RESTORE", `task '${this.id}' result could not be read safely`, { task: this.id });
|
|
1492
|
+
}
|
|
1055
1493
|
this.#transition("completed");
|
|
1056
|
-
|
|
1494
|
+
this.#finish();
|
|
1495
|
+
const result = this.#record("completed", Object.freeze({
|
|
1057
1496
|
success: true,
|
|
1058
|
-
value
|
|
1059
|
-
});
|
|
1497
|
+
value: owned
|
|
1498
|
+
}));
|
|
1060
1499
|
this.#emitter.emit("complete", result);
|
|
1061
1500
|
this.#escalate();
|
|
1062
1501
|
}
|
|
1063
1502
|
fail(error) {
|
|
1503
|
+
const origin = error.origin === "handler" || error.origin === "timeout" || error.origin === "recovery" ? error.origin : "handler";
|
|
1504
|
+
const message = typeof error.message === "string" && error.message.length > 0 ? error.message : "unknown failure";
|
|
1064
1505
|
this.#transition("failed");
|
|
1065
|
-
|
|
1066
|
-
const result = this.#record("failed", {
|
|
1506
|
+
this.#finish();
|
|
1507
|
+
const result = this.#record("failed", Object.freeze({
|
|
1067
1508
|
success: false,
|
|
1068
|
-
error:
|
|
1069
|
-
|
|
1509
|
+
error: Object.freeze({
|
|
1510
|
+
origin,
|
|
1511
|
+
message
|
|
1512
|
+
})
|
|
1513
|
+
}));
|
|
1070
1514
|
this.#emitter.emit("fail", result);
|
|
1071
1515
|
this.#escalate();
|
|
1072
1516
|
}
|
|
1073
1517
|
skip() {
|
|
1074
1518
|
this.#transition("skipped");
|
|
1519
|
+
this.#finish();
|
|
1520
|
+
this.#abort.abort();
|
|
1075
1521
|
this.#emitter.emit("skip");
|
|
1076
1522
|
this.#escalate();
|
|
1077
1523
|
}
|
|
1078
1524
|
stop() {
|
|
1079
1525
|
this.#transition("stopped");
|
|
1526
|
+
this.#finish();
|
|
1527
|
+
this.#abort.abort();
|
|
1080
1528
|
this.#emitter.emit("stop");
|
|
1081
1529
|
this.#escalate();
|
|
1082
1530
|
}
|
|
1531
|
+
report(input) {
|
|
1532
|
+
if (this.#status !== "running") return failure(new WorkflowError("TRANSITION", `task '${this.id}' cannot report while '${this.#status}'`, {
|
|
1533
|
+
task: this.id,
|
|
1534
|
+
status: this.#status
|
|
1535
|
+
}));
|
|
1536
|
+
try {
|
|
1537
|
+
const activity = cloneTaskActivity(input, this.#stamp());
|
|
1538
|
+
this.#activity = activity;
|
|
1539
|
+
this.#arm();
|
|
1540
|
+
this.#emitter.emit("report", activity);
|
|
1541
|
+
return success(activity);
|
|
1542
|
+
} catch (error) {
|
|
1543
|
+
return failure(error instanceof WorkflowError ? error : new WorkflowError("MUTATION", "task activity report was refused", { task: this.id }));
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
pulse() {
|
|
1547
|
+
if (this.#status !== "running" || this.#activity === void 0) return false;
|
|
1548
|
+
this.#touch();
|
|
1549
|
+
this.#arm();
|
|
1550
|
+
const activity = this.#activity;
|
|
1551
|
+
this.#emitter.emit("pulse", activity);
|
|
1552
|
+
return true;
|
|
1553
|
+
}
|
|
1554
|
+
pause() {
|
|
1555
|
+
if (this.#paused || this.#status !== "pending" && this.#status !== "running") return;
|
|
1556
|
+
this.#paused = true;
|
|
1557
|
+
this.#gate = createDeferred();
|
|
1558
|
+
this.#emitter.emit("pause");
|
|
1559
|
+
}
|
|
1560
|
+
resume() {
|
|
1561
|
+
if (!this.#paused) return;
|
|
1562
|
+
this.#paused = false;
|
|
1563
|
+
this.#release();
|
|
1564
|
+
this.#emitter.emit("resume");
|
|
1565
|
+
}
|
|
1566
|
+
wait() {
|
|
1567
|
+
return this.#paused && this.#gate !== void 0 ? this.#gate.promise : Promise.resolve();
|
|
1568
|
+
}
|
|
1083
1569
|
/**
|
|
1084
1570
|
* Apply a validated declarative patch to SELF (`name` / `description`).
|
|
1085
1571
|
*
|
|
@@ -1101,7 +1587,10 @@ var Task = class {
|
|
|
1101
1587
|
status: this.#status
|
|
1102
1588
|
});
|
|
1103
1589
|
if (value.name !== void 0) this.#name = value.name;
|
|
1104
|
-
if (value.description !== void 0) this
|
|
1590
|
+
if (value.description !== void 0) Object.defineProperty(this, "description", {
|
|
1591
|
+
configurable: true,
|
|
1592
|
+
value: value.description
|
|
1593
|
+
});
|
|
1105
1594
|
}
|
|
1106
1595
|
snapshot() {
|
|
1107
1596
|
return {
|
|
@@ -1111,9 +1600,11 @@ var Task = class {
|
|
|
1111
1600
|
status: this.#status,
|
|
1112
1601
|
...this.#result === void 0 ? {} : { result: this.#result },
|
|
1113
1602
|
metadata: this.#metadata,
|
|
1603
|
+
attempts: this.#attempts,
|
|
1114
1604
|
...this.#run === void 0 ? {} : { run: this.#run },
|
|
1115
1605
|
...this.#retries === void 0 ? {} : { retries: this.#retries },
|
|
1116
|
-
...this.#timeout === void 0 ? {} : { timeout: this.#timeout }
|
|
1606
|
+
...this.#timeout === void 0 ? {} : { timeout: this.#timeout },
|
|
1607
|
+
...this.#activity === void 0 ? {} : { activity: this.#activity }
|
|
1117
1608
|
};
|
|
1118
1609
|
}
|
|
1119
1610
|
#transition(to) {
|
|
@@ -1133,12 +1624,53 @@ var Task = class {
|
|
|
1133
1624
|
...result === void 0 ? {} : { result },
|
|
1134
1625
|
timestamp: Date.now()
|
|
1135
1626
|
};
|
|
1136
|
-
|
|
1137
|
-
|
|
1627
|
+
const frozen = Object.freeze(record);
|
|
1628
|
+
this.#result = frozen;
|
|
1629
|
+
return frozen;
|
|
1138
1630
|
}
|
|
1139
1631
|
#escalate() {
|
|
1140
1632
|
this.#recompute();
|
|
1141
1633
|
}
|
|
1634
|
+
#touch() {
|
|
1635
|
+
if (this.#activity === void 0) return;
|
|
1636
|
+
this.#activity = Object.freeze({
|
|
1637
|
+
...this.#activity,
|
|
1638
|
+
updated: this.#stamp()
|
|
1639
|
+
});
|
|
1640
|
+
}
|
|
1641
|
+
#stamp() {
|
|
1642
|
+
return Math.max(Date.now(), this.#activity?.updated ?? 0);
|
|
1643
|
+
}
|
|
1644
|
+
#finish() {
|
|
1645
|
+
this.#clear();
|
|
1646
|
+
this.#paused = false;
|
|
1647
|
+
this.#release();
|
|
1648
|
+
}
|
|
1649
|
+
#arm() {
|
|
1650
|
+
this.#clear();
|
|
1651
|
+
const liveness = this.#liveness;
|
|
1652
|
+
if (liveness === void 0 || this.#status !== "running") return;
|
|
1653
|
+
liveness.start();
|
|
1654
|
+
const signal = liveness.signal;
|
|
1655
|
+
this.#timerSignal = signal;
|
|
1656
|
+
signal.addEventListener("abort", this.#onSilence, { once: true });
|
|
1657
|
+
}
|
|
1658
|
+
#clear() {
|
|
1659
|
+
const signal = this.#timerSignal;
|
|
1660
|
+
if (signal !== void 0) signal.removeEventListener("abort", this.#onSilence);
|
|
1661
|
+
this.#timerSignal = void 0;
|
|
1662
|
+
this.#liveness?.clear();
|
|
1663
|
+
}
|
|
1664
|
+
#expire() {
|
|
1665
|
+
this.#timerSignal = void 0;
|
|
1666
|
+
if (this.#status !== "running" || this.#liveness?.expired !== true) return;
|
|
1667
|
+
this.#emitter.emit("silence");
|
|
1668
|
+
}
|
|
1669
|
+
#release() {
|
|
1670
|
+
if (this.#gate === void 0) return;
|
|
1671
|
+
this.#gate.resolve();
|
|
1672
|
+
this.#gate = void 0;
|
|
1673
|
+
}
|
|
1142
1674
|
};
|
|
1143
1675
|
//#endregion
|
|
1144
1676
|
//#region src/core/tasks/TaskManager.ts
|
|
@@ -1239,9 +1771,11 @@ var TaskManager = class {
|
|
|
1239
1771
|
* `results()` collects the settled tasks' {@link TaskResult}s (the phase tier of the result
|
|
1240
1772
|
* tree); `workflow` navigates UP to the live parent.
|
|
1241
1773
|
* - **Observable (AGENTS §13).** The owned {@link emitter} ({@link PhaseEventMap}) fires
|
|
1242
|
-
* `start` / `complete` / `fail` / `
|
|
1243
|
-
*
|
|
1244
|
-
*
|
|
1774
|
+
* `start` / `complete` / `fail` / `pause` / `resume` / `skip` / `stop` after the
|
|
1775
|
+
* corresponding status or runtime-gate change. Status events fire after the phase recomputes
|
|
1776
|
+
* and before it escalates to the workflow, preserving child/phase cause before parent effect.
|
|
1777
|
+
* The emitter isolates a listener throw and routes it to its `error` handler (the `error`
|
|
1778
|
+
* option); `fail` carries the failing task's {@link TaskResult}.
|
|
1245
1779
|
* - **Structural API (AGENTS §7).** `add` / `remove` / `move` / `update` gate BEFORE
|
|
1246
1780
|
* delegating to {@link tasks} (the manager gates the target's own existence/status/id/
|
|
1247
1781
|
* bounds), then emit the matching {@link PhaseEventMap} event on success only. NATIVE
|
|
@@ -1261,7 +1795,8 @@ var TaskManager = class {
|
|
|
1261
1795
|
* {@link import('../types.js').WorkflowFunctions} registry (threaded from
|
|
1262
1796
|
* {@link import('../types.js').WorkflowOptions.functions}) resolves each task's `run` name into
|
|
1263
1797
|
* its runtime {@link import('../types.js').TaskInterface.handler} ONCE; a `run` that is omitted
|
|
1264
|
-
* or unregistered resolves to no handler
|
|
1798
|
+
* or unregistered resolves to no handler; only an omitted `run` is a no-op, while an
|
|
1799
|
+
* unresolved present name makes the containing tree non-drivable.
|
|
1265
1800
|
* - **Runtime lifecycle (AGENTS §10).** `pause` / `resume` / `wait` mirror the workflow's own
|
|
1266
1801
|
* quartet, scoped to this phase — a driving
|
|
1267
1802
|
* {@link import('../types.js').WorkflowRunnerInterface.execute} gates a task's own
|
|
@@ -1274,11 +1809,11 @@ var TaskManager = class {
|
|
|
1274
1809
|
var Phase = class {
|
|
1275
1810
|
#id;
|
|
1276
1811
|
#name;
|
|
1277
|
-
#description;
|
|
1278
1812
|
#workflow;
|
|
1279
1813
|
#escalateUp;
|
|
1280
1814
|
#tasks = new TaskManager();
|
|
1281
1815
|
#functions;
|
|
1816
|
+
#silence;
|
|
1282
1817
|
#bail;
|
|
1283
1818
|
#concurrency;
|
|
1284
1819
|
#emitter;
|
|
@@ -1286,18 +1821,22 @@ var Phase = class {
|
|
|
1286
1821
|
#override;
|
|
1287
1822
|
#paused;
|
|
1288
1823
|
#gate;
|
|
1289
|
-
constructor(snapshot, workflow, escalate, options, bail, functions) {
|
|
1824
|
+
constructor(snapshot, workflow, escalate, options, bail, functions, silence) {
|
|
1290
1825
|
this.#id = snapshot.id;
|
|
1291
1826
|
this.#name = snapshot.name;
|
|
1292
|
-
|
|
1827
|
+
if (snapshot.description !== void 0) Object.defineProperty(this, "description", {
|
|
1828
|
+
configurable: true,
|
|
1829
|
+
value: snapshot.description
|
|
1830
|
+
});
|
|
1293
1831
|
this.#workflow = workflow;
|
|
1294
1832
|
this.#escalateUp = escalate;
|
|
1295
1833
|
this.#functions = functions;
|
|
1834
|
+
this.#silence = silence;
|
|
1296
1835
|
this.#bail = bail ?? snapshot.bail;
|
|
1297
1836
|
this.#concurrency = snapshot.concurrency;
|
|
1298
1837
|
this.#emitter = new Emitter({
|
|
1299
|
-
on: options
|
|
1300
|
-
error: options
|
|
1838
|
+
...options?.on === void 0 ? {} : { on: options.on },
|
|
1839
|
+
...options?.error === void 0 ? {} : { error: options.error }
|
|
1301
1840
|
});
|
|
1302
1841
|
for (const task of snapshot.tasks) this.#append(task, options);
|
|
1303
1842
|
this.#override = snapshot.override;
|
|
@@ -1314,14 +1853,11 @@ var Phase = class {
|
|
|
1314
1853
|
get name() {
|
|
1315
1854
|
return this.#name;
|
|
1316
1855
|
}
|
|
1317
|
-
get description() {
|
|
1318
|
-
return this.#description;
|
|
1319
|
-
}
|
|
1320
1856
|
get context() {
|
|
1321
1857
|
return buildPhaseContext(this.#workflow.context, {
|
|
1322
1858
|
id: this.#id,
|
|
1323
1859
|
name: this.#name,
|
|
1324
|
-
...this
|
|
1860
|
+
...this.description === void 0 ? {} : { description: this.description }
|
|
1325
1861
|
});
|
|
1326
1862
|
}
|
|
1327
1863
|
get workflow() {
|
|
@@ -1364,11 +1900,13 @@ var Phase = class {
|
|
|
1364
1900
|
if (this.#paused || isTerminalStatus(this.status)) return;
|
|
1365
1901
|
this.#paused = true;
|
|
1366
1902
|
this.#gate = createDeferred();
|
|
1903
|
+
this.#emitter.emit("pause");
|
|
1367
1904
|
}
|
|
1368
1905
|
resume() {
|
|
1369
1906
|
if (!this.#paused) return;
|
|
1370
1907
|
this.#paused = false;
|
|
1371
1908
|
this.#release();
|
|
1909
|
+
this.#emitter.emit("resume");
|
|
1372
1910
|
}
|
|
1373
1911
|
wait() {
|
|
1374
1912
|
return this.#paused && this.#gate !== void 0 ? this.#gate.promise : Promise.resolve();
|
|
@@ -1423,7 +1961,10 @@ var Phase = class {
|
|
|
1423
1961
|
status: this.status
|
|
1424
1962
|
});
|
|
1425
1963
|
if (value.name !== void 0) this.#name = value.name;
|
|
1426
|
-
if (value.description !== void 0) this
|
|
1964
|
+
if (value.description !== void 0) Object.defineProperty(this, "description", {
|
|
1965
|
+
configurable: true,
|
|
1966
|
+
value: value.description
|
|
1967
|
+
});
|
|
1427
1968
|
if (value.concurrency !== void 0) this.#concurrency = value.concurrency;
|
|
1428
1969
|
if (value.bail !== void 0) this.#bail = value.bail;
|
|
1429
1970
|
}
|
|
@@ -1446,6 +1987,10 @@ var Phase = class {
|
|
|
1446
1987
|
return;
|
|
1447
1988
|
}
|
|
1448
1989
|
this.#status = next;
|
|
1990
|
+
if (isTerminalStatus(next)) {
|
|
1991
|
+
this.#paused = false;
|
|
1992
|
+
this.#release();
|
|
1993
|
+
}
|
|
1449
1994
|
this.#emitFor(next);
|
|
1450
1995
|
this.#escalateUp();
|
|
1451
1996
|
}
|
|
@@ -1457,6 +2002,7 @@ var Phase = class {
|
|
|
1457
2002
|
if (status === "running") this.#emitter.emit("start", this.id);
|
|
1458
2003
|
else if (status === "completed") this.#emitter.emit("complete");
|
|
1459
2004
|
else if (status === "failed") this.#emitter.emit("fail", this.#failure());
|
|
2005
|
+
else if (status === "skipped") this.#emitter.emit("skip");
|
|
1460
2006
|
else if (status === "stopped") this.#emitter.emit("stop");
|
|
1461
2007
|
}
|
|
1462
2008
|
#failure() {
|
|
@@ -1481,7 +2027,7 @@ var Phase = class {
|
|
|
1481
2027
|
#create(snapshot, options) {
|
|
1482
2028
|
const context = buildTaskContext(this.context, snapshot);
|
|
1483
2029
|
const handler = snapshot.run === void 0 ? void 0 : this.#functions?.[snapshot.run];
|
|
1484
|
-
return new Task(context, this, this.#workflow, () => this.#recompute(), options, snapshot.status, snapshot.result, snapshot.run, snapshot.retries, snapshot.timeout, handler);
|
|
2030
|
+
return new Task(context, this, this.#workflow, () => this.#recompute(), options, snapshot.status, snapshot.result, snapshot.run, snapshot.retries, snapshot.timeout, snapshot.metadata, snapshot.attempts, snapshot.activity, handler, this.#silence);
|
|
1485
2031
|
}
|
|
1486
2032
|
#mint(definition) {
|
|
1487
2033
|
return this.#create(taskDefinitionToSnapshot(definition), void 0);
|
|
@@ -1585,18 +2131,20 @@ var PhaseManager = class {
|
|
|
1585
2131
|
* reachable ONLY under `bail: true` (a single failed task halts the workflow); under
|
|
1586
2132
|
* `bail: false` a failed phase folds into `completed`. {@link #recompute} diffs on each phase
|
|
1587
2133
|
* change; a CHANGE emits.
|
|
1588
|
-
* - **Override (AGENTS §10).** `skip` / `stop` FORCE the status;
|
|
1589
|
-
*
|
|
1590
|
-
*
|
|
2134
|
+
* - **Override (AGENTS §10).** `skip` / `stop` FORCE the status; an executed task-free pending tree
|
|
2135
|
+
* may also be force-completed vacuously. The override is PERSISTED in the snapshot's own
|
|
2136
|
+
* `override` field and restored DIRECTLY (no divergence guess). The snapshot also persists
|
|
2137
|
+
* `bail`, so a restore re-derives status identically without a silent policy default.
|
|
1591
2138
|
* - **Result tree.** `results()` flattens every phase's `results()` ({@link collectResults}) — the
|
|
1592
2139
|
* workflow tier; `phase(id)` + each `phase.task(id)` navigate DOWN, a task's `phase` / `workflow`
|
|
1593
2140
|
* navigate UP.
|
|
1594
2141
|
* - **Snapshot.** `snapshot()` serializes the whole live tree to a {@link WorkflowSnapshot} (pure
|
|
1595
2142
|
* JSON); {@link import('./factories.js').restoreWorkflow} rebuilds an equivalent live tree.
|
|
1596
2143
|
* - **Observable (AGENTS §13).** The owned {@link emitter} ({@link WorkflowEventMap}) fires
|
|
1597
|
-
* `start` / `complete` / `fail` / `
|
|
1598
|
-
*
|
|
1599
|
-
* the failing task's
|
|
2144
|
+
* `start` / `complete` / `fail` / `pause` / `resume` / `skip` / `stop` after the
|
|
2145
|
+
* corresponding status or runtime-gate change; the emitter isolates a listener throw and
|
|
2146
|
+
* routes it to its `error` handler (the `error` option); `fail` carries the failing task's
|
|
2147
|
+
* {@link TaskResult}.
|
|
1600
2148
|
* - **Structural API (AGENTS §7).** `add` / `remove` / `move` / `update` gate BEFORE
|
|
1601
2149
|
* delegating to {@link phases} (the manager gates the target's own existence/status/id/
|
|
1602
2150
|
* bounds), then emit the matching {@link WorkflowEventMap} event on success only. NATIVE,
|
|
@@ -1608,16 +2156,17 @@ var PhaseManager = class {
|
|
|
1608
2156
|
* naturally accepted.
|
|
1609
2157
|
* - **Runtime lifecycle (AGENTS §10).** `pause` / `resume` / `wait` gate execution at the runner's
|
|
1610
2158
|
* phase/task boundaries WITHOUT touching {@link status} — `paused` is runtime-only, never
|
|
1611
|
-
* persisted. `destroy` is a terminal teardown: it
|
|
1612
|
-
*
|
|
1613
|
-
*
|
|
1614
|
-
*
|
|
2159
|
+
* persisted. `destroy` is a terminal teardown: it `stop`s every non-terminal task and
|
|
2160
|
+
* phase (releasing their gates and liveness resources), aborts {@link signal}, forces the
|
|
2161
|
+
* workflow `stop` override when needed, releases its parked waiter, and marks
|
|
2162
|
+
* {@link destroyed} — all idempotent.
|
|
1615
2163
|
*/
|
|
1616
2164
|
var Workflow = class {
|
|
1617
2165
|
#context;
|
|
1618
2166
|
#bail;
|
|
1619
2167
|
#bailOverride;
|
|
1620
2168
|
#functions;
|
|
2169
|
+
#silence;
|
|
1621
2170
|
#phases = new PhaseManager();
|
|
1622
2171
|
#emitter;
|
|
1623
2172
|
#created;
|
|
@@ -1630,12 +2179,14 @@ var Workflow = class {
|
|
|
1630
2179
|
#destroyed;
|
|
1631
2180
|
constructor(snapshot, options) {
|
|
1632
2181
|
this.#context = buildWorkflowContext(snapshot);
|
|
2182
|
+
if (snapshot.description !== void 0) Object.defineProperty(this, "description", { value: snapshot.description });
|
|
1633
2183
|
this.#bail = options?.bail ?? snapshot.bail;
|
|
1634
2184
|
this.#bailOverride = options?.bail;
|
|
1635
2185
|
this.#functions = options?.functions;
|
|
2186
|
+
this.#silence = options?.silence;
|
|
1636
2187
|
this.#emitter = new Emitter({
|
|
1637
|
-
on: options
|
|
1638
|
-
error: options
|
|
2188
|
+
...options?.on === void 0 ? {} : { on: options.on },
|
|
2189
|
+
...options?.error === void 0 ? {} : { error: options.error }
|
|
1639
2190
|
});
|
|
1640
2191
|
this.#created = snapshot.created;
|
|
1641
2192
|
this.#updated = snapshot.updated;
|
|
@@ -1656,9 +2207,6 @@ var Workflow = class {
|
|
|
1656
2207
|
get name() {
|
|
1657
2208
|
return this.#context.name;
|
|
1658
2209
|
}
|
|
1659
|
-
get description() {
|
|
1660
|
-
return this.#context.description;
|
|
1661
|
-
}
|
|
1662
2210
|
get context() {
|
|
1663
2211
|
return this.#context;
|
|
1664
2212
|
}
|
|
@@ -1697,26 +2245,35 @@ var Workflow = class {
|
|
|
1697
2245
|
this.#release();
|
|
1698
2246
|
}
|
|
1699
2247
|
complete() {
|
|
1700
|
-
if (this.status === "pending") this.#force("completed");
|
|
2248
|
+
if (this.status === "pending" && this.#phases.phases().every((phase) => phase.tasks.count === 0)) this.#force("completed");
|
|
1701
2249
|
}
|
|
1702
2250
|
pause() {
|
|
1703
2251
|
if (this.#paused || isTerminalStatus(this.status) || this.#destroyed) return;
|
|
1704
2252
|
this.#paused = true;
|
|
1705
2253
|
this.#gate = createDeferred();
|
|
2254
|
+
this.#emitter.emit("pause");
|
|
1706
2255
|
}
|
|
1707
2256
|
resume() {
|
|
1708
2257
|
if (!this.#paused) return;
|
|
1709
2258
|
this.#paused = false;
|
|
1710
2259
|
this.#release();
|
|
2260
|
+
this.#emitter.emit("resume");
|
|
1711
2261
|
}
|
|
1712
2262
|
destroy() {
|
|
1713
2263
|
if (this.#destroyed) return;
|
|
1714
2264
|
this.#destroyed = true;
|
|
1715
|
-
this.#
|
|
1716
|
-
for (const phase of this.#phases.phases()) if (!isTerminalStatus(phase.status)) phase.stop();
|
|
2265
|
+
const phases = this.#phases.phases();
|
|
1717
2266
|
if (!isTerminalStatus(this.status)) this.stop();
|
|
2267
|
+
for (const phase of phases) phase.stop();
|
|
2268
|
+
for (const phase of phases) for (const task of phase.tasks.tasks()) if (!isTerminalStatus(task.status)) task.stop();
|
|
1718
2269
|
this.#paused = false;
|
|
1719
2270
|
this.#release();
|
|
2271
|
+
this.#abort.abort();
|
|
2272
|
+
for (const phase of phases) {
|
|
2273
|
+
for (const task of phase.tasks.tasks()) task.emitter.destroy();
|
|
2274
|
+
phase.emitter.destroy();
|
|
2275
|
+
}
|
|
2276
|
+
this.#emitter.destroy();
|
|
1720
2277
|
}
|
|
1721
2278
|
wait() {
|
|
1722
2279
|
return this.#paused && this.#gate !== void 0 ? this.#gate.promise : Promise.resolve();
|
|
@@ -1778,7 +2335,7 @@ var Workflow = class {
|
|
|
1778
2335
|
return result;
|
|
1779
2336
|
}
|
|
1780
2337
|
snapshot() {
|
|
1781
|
-
return {
|
|
2338
|
+
return cloneWorkflowSnapshot({
|
|
1782
2339
|
id: this.id,
|
|
1783
2340
|
name: this.name,
|
|
1784
2341
|
...this.description === void 0 ? {} : { description: this.description },
|
|
@@ -1788,13 +2345,17 @@ var Workflow = class {
|
|
|
1788
2345
|
phases: this.#phases.phases().map((phase) => phase.snapshot()),
|
|
1789
2346
|
created: this.#created,
|
|
1790
2347
|
updated: this.#updated
|
|
1791
|
-
};
|
|
2348
|
+
});
|
|
1792
2349
|
}
|
|
1793
2350
|
#recompute() {
|
|
1794
2351
|
const next = this.status;
|
|
1795
2352
|
if (next === this.#status) return;
|
|
1796
2353
|
this.#status = next;
|
|
1797
|
-
|
|
2354
|
+
if (isTerminalStatus(next)) {
|
|
2355
|
+
this.#paused = false;
|
|
2356
|
+
this.#release();
|
|
2357
|
+
}
|
|
2358
|
+
this.#updated = Math.max(Date.now(), this.#updated);
|
|
1798
2359
|
this.#emitFor(next);
|
|
1799
2360
|
}
|
|
1800
2361
|
#force(status) {
|
|
@@ -1805,6 +2366,7 @@ var Workflow = class {
|
|
|
1805
2366
|
if (status === "running") this.#emitter.emit("start", this.id);
|
|
1806
2367
|
else if (status === "completed") this.#emitter.emit("complete");
|
|
1807
2368
|
else if (status === "failed") this.#emitter.emit("fail", this.#failure());
|
|
2369
|
+
else if (status === "skipped") this.#emitter.emit("skip");
|
|
1808
2370
|
else if (status === "stopped") this.#emitter.emit("stop");
|
|
1809
2371
|
}
|
|
1810
2372
|
#addTo(phase, index, at) {
|
|
@@ -1824,11 +2386,11 @@ var Workflow = class {
|
|
|
1824
2386
|
return found;
|
|
1825
2387
|
}
|
|
1826
2388
|
#append(phase, options) {
|
|
1827
|
-
const created = new Phase(phase, this, () => this.#recompute(), options?.phases?.[phase.id], this.#bailOverride, this.#functions);
|
|
2389
|
+
const created = new Phase(phase, this, () => this.#recompute(), options?.phases?.[phase.id], this.#bailOverride, this.#functions, this.#silence);
|
|
1828
2390
|
this.#phases.append(created);
|
|
1829
2391
|
}
|
|
1830
2392
|
#mint(definition) {
|
|
1831
|
-
return new Phase(phaseDefinitionToSnapshot(definition, this.#bail), this, () => this.#recompute(), void 0, this.#bailOverride, this.#functions);
|
|
2393
|
+
return new Phase(phaseDefinitionToSnapshot(definition, this.#bail), this, () => this.#recompute(), void 0, this.#bailOverride, this.#functions, this.#silence);
|
|
1832
2394
|
}
|
|
1833
2395
|
#release() {
|
|
1834
2396
|
if (this.#gate === void 0) return;
|
|
@@ -1895,7 +2457,7 @@ var WorkflowManager = class {
|
|
|
1895
2457
|
return [...this.#workflows.values()];
|
|
1896
2458
|
}
|
|
1897
2459
|
add(definition) {
|
|
1898
|
-
const workflow = createWorkflow(definition, { functions: this.#functions });
|
|
2460
|
+
const workflow = createWorkflow(definition, { ...this.#functions === void 0 ? {} : { functions: this.#functions } });
|
|
1899
2461
|
this.#workflows.set(workflow.id, workflow);
|
|
1900
2462
|
return workflow;
|
|
1901
2463
|
}
|
|
@@ -1905,7 +2467,7 @@ var WorkflowManager = class {
|
|
|
1905
2467
|
if (this.#store === void 0) return void 0;
|
|
1906
2468
|
const snapshot = await this.#store.get(id);
|
|
1907
2469
|
if (snapshot === void 0) return void 0;
|
|
1908
|
-
const workflow = restoreWorkflow(snapshot, { functions: this.#functions });
|
|
2470
|
+
const workflow = restoreWorkflow(snapshot, { ...this.#functions === void 0 ? {} : { functions: this.#functions } });
|
|
1909
2471
|
this.#workflows.set(workflow.id, workflow);
|
|
1910
2472
|
return workflow;
|
|
1911
2473
|
}
|
|
@@ -2055,14 +2617,14 @@ var Runner = class {
|
|
|
2055
2617
|
this.#handler = options.handler;
|
|
2056
2618
|
this.#entries = options.entries;
|
|
2057
2619
|
this.#emitter = new Emitter({
|
|
2058
|
-
on: options
|
|
2059
|
-
error: options
|
|
2620
|
+
...options.on === void 0 ? {} : { on: options.on },
|
|
2621
|
+
...options.error === void 0 ? {} : { error: options.error }
|
|
2060
2622
|
});
|
|
2061
2623
|
this.#queue = createQueue({
|
|
2062
|
-
handler:
|
|
2063
|
-
concurrency: options.concurrency,
|
|
2064
|
-
retries: options.retries,
|
|
2065
|
-
timeout: options.timeout
|
|
2624
|
+
handler: this.#dispatch.bind(this),
|
|
2625
|
+
...options.concurrency === void 0 ? {} : { concurrency: options.concurrency },
|
|
2626
|
+
...options.retries === void 0 ? {} : { retries: options.retries },
|
|
2627
|
+
...options.timeout === void 0 ? {} : { timeout: options.timeout }
|
|
2066
2628
|
});
|
|
2067
2629
|
}
|
|
2068
2630
|
get emitter() {
|
|
@@ -2248,18 +2810,17 @@ var Runner = class {
|
|
|
2248
2810
|
//#endregion
|
|
2249
2811
|
//#region src/core/tasks/TaskController.ts
|
|
2250
2812
|
/**
|
|
2251
|
-
* The
|
|
2252
|
-
* running task's folded cancellation, its input, its lineage, and read-UP access to the
|
|
2253
|
-
* result tree.
|
|
2813
|
+
* The attempt-scoped handle a {@link import('./types.js').WorkflowFunction} receives.
|
|
2254
2814
|
*
|
|
2255
2815
|
* @remarks
|
|
2256
2816
|
* - **A leaf handle, NOT the runner `Controller`.** A workflow task is a leaf of the
|
|
2257
|
-
* declarative W-b tree, not a fan-out unit, so
|
|
2258
|
-
*
|
|
2259
|
-
* - **Folded signal.** `signal` is the cancellation
|
|
2260
|
-
*
|
|
2261
|
-
*
|
|
2262
|
-
*
|
|
2817
|
+
* declarative W-b tree, not a fan-out unit, so it has no `spawn`; its `wait` instead
|
|
2818
|
+
* checkpoints the workflow, phase, and task cooperative gates.
|
|
2819
|
+
* - **Folded signal.** `signal` is the cancellation folded for THIS attempt: its per-attempt
|
|
2820
|
+
* deadline, task stop/skip, workflow abort/timeout/budget/destroy, or a sibling fail-fast.
|
|
2821
|
+
* A handler races its work against it; `aborted` reads it.
|
|
2822
|
+
* - **Attempt ownership.** `report` / `pulse` are closures supplied by the runner and refuse
|
|
2823
|
+
* after this signal aborts or a retry token supersedes this handle.
|
|
2263
2824
|
* - **Input + lineage.** `input` is the task's open `metadata` bag (its
|
|
2264
2825
|
* {@link import('./types.js').TaskInput} payload, `{}` when none); `task` is the full
|
|
2265
2826
|
* {@link TaskContext}, so `task.phase` / `task.phase.workflow` navigate UP the lineage.
|
|
@@ -2275,19 +2836,273 @@ var TaskController = class {
|
|
|
2275
2836
|
signal;
|
|
2276
2837
|
input;
|
|
2277
2838
|
task;
|
|
2839
|
+
attempt;
|
|
2840
|
+
#entity;
|
|
2841
|
+
#report;
|
|
2842
|
+
#pulse;
|
|
2278
2843
|
#results;
|
|
2279
|
-
constructor(signal, input, task, results) {
|
|
2844
|
+
constructor(signal, input, task, attempt, results, report, pulse) {
|
|
2280
2845
|
this.signal = signal;
|
|
2281
2846
|
this.input = input;
|
|
2282
|
-
this.task = task;
|
|
2847
|
+
this.task = task.context;
|
|
2848
|
+
this.attempt = attempt;
|
|
2849
|
+
this.#entity = task;
|
|
2283
2850
|
this.#results = results;
|
|
2851
|
+
this.#report = report;
|
|
2852
|
+
this.#pulse = pulse;
|
|
2284
2853
|
}
|
|
2285
2854
|
get aborted() {
|
|
2286
2855
|
return this.signal.aborted;
|
|
2287
2856
|
}
|
|
2857
|
+
get paused() {
|
|
2858
|
+
if (this.#ancestorTerminal()) return false;
|
|
2859
|
+
return this.#entity.workflow.paused || this.#entity.phase.paused || !isTerminalStatus(this.#entity.status) && this.#entity.paused;
|
|
2860
|
+
}
|
|
2861
|
+
report(input) {
|
|
2862
|
+
return this.#report(input);
|
|
2863
|
+
}
|
|
2864
|
+
pulse() {
|
|
2865
|
+
return this.#pulse();
|
|
2866
|
+
}
|
|
2867
|
+
async wait() {
|
|
2868
|
+
while (this.paused && !this.signal.aborted) await this.#race(this.#gates());
|
|
2869
|
+
}
|
|
2288
2870
|
results() {
|
|
2289
2871
|
return this.#results();
|
|
2290
2872
|
}
|
|
2873
|
+
#gates() {
|
|
2874
|
+
if (this.#ancestorTerminal()) return [];
|
|
2875
|
+
const gates = [];
|
|
2876
|
+
if (this.#entity.workflow.paused) gates.push(this.#entity.workflow.wait());
|
|
2877
|
+
if (this.#entity.phase.paused) gates.push(this.#entity.phase.wait());
|
|
2878
|
+
if (!isTerminalStatus(this.#entity.status) && this.#entity.paused) gates.push(this.#entity.wait());
|
|
2879
|
+
return gates;
|
|
2880
|
+
}
|
|
2881
|
+
async #race(gates) {
|
|
2882
|
+
if (this.signal.aborted || gates.length === 0) return;
|
|
2883
|
+
const deferred = Promise.withResolvers();
|
|
2884
|
+
const onAbort = this.#resolve.bind(this, deferred);
|
|
2885
|
+
const onTerminal = this.#resolve.bind(this, deferred);
|
|
2886
|
+
this.signal.addEventListener("abort", onAbort, { once: true });
|
|
2887
|
+
this.#entity.workflow.emitter.on("skip", onTerminal);
|
|
2888
|
+
this.#entity.workflow.emitter.on("stop", onTerminal);
|
|
2889
|
+
this.#entity.phase.emitter.on("skip", onTerminal);
|
|
2890
|
+
this.#entity.phase.emitter.on("stop", onTerminal);
|
|
2891
|
+
try {
|
|
2892
|
+
if (this.#ancestorTerminal()) deferred.resolve();
|
|
2893
|
+
await Promise.race([Promise.all(gates), deferred.promise]);
|
|
2894
|
+
} finally {
|
|
2895
|
+
this.signal.removeEventListener("abort", onAbort);
|
|
2896
|
+
this.#entity.workflow.emitter.off("skip", onTerminal);
|
|
2897
|
+
this.#entity.workflow.emitter.off("stop", onTerminal);
|
|
2898
|
+
this.#entity.phase.emitter.off("skip", onTerminal);
|
|
2899
|
+
this.#entity.phase.emitter.off("stop", onTerminal);
|
|
2900
|
+
}
|
|
2901
|
+
}
|
|
2902
|
+
#ancestorTerminal() {
|
|
2903
|
+
return isTerminalStatus(this.#entity.workflow.status) || isTerminalStatus(this.#entity.phase.status);
|
|
2904
|
+
}
|
|
2905
|
+
#resolve(deferred) {
|
|
2906
|
+
deferred.resolve();
|
|
2907
|
+
}
|
|
2908
|
+
};
|
|
2909
|
+
//#endregion
|
|
2910
|
+
//#region src/core/WorkflowPersistence.ts
|
|
2911
|
+
/**
|
|
2912
|
+
* Advanced run-local snapshot persistence with one writer and one coalesced latest obligation.
|
|
2913
|
+
*
|
|
2914
|
+
* @remarks
|
|
2915
|
+
* Normally composed by `WorkflowRunner.execute({ store })`; exported for hosts that need to
|
|
2916
|
+
* coordinate the same required boundaries around their own runner integration.
|
|
2917
|
+
*/
|
|
2918
|
+
var WorkflowPersistence = class {
|
|
2919
|
+
#workflow;
|
|
2920
|
+
#store;
|
|
2921
|
+
#phases = /* @__PURE__ */ new Set();
|
|
2922
|
+
#tasks = /* @__PURE__ */ new Set();
|
|
2923
|
+
#onWorkflowChange;
|
|
2924
|
+
#onWorkflowAdd;
|
|
2925
|
+
#onWorkflowRemove;
|
|
2926
|
+
#onPhaseChange;
|
|
2927
|
+
#onPhaseAdd;
|
|
2928
|
+
#onPhaseRemove;
|
|
2929
|
+
#onTaskChange;
|
|
2930
|
+
#writing;
|
|
2931
|
+
#error;
|
|
2932
|
+
#fault;
|
|
2933
|
+
#attached = true;
|
|
2934
|
+
#revision = 0;
|
|
2935
|
+
#stored = 0;
|
|
2936
|
+
constructor(workflow, store) {
|
|
2937
|
+
this.#workflow = workflow;
|
|
2938
|
+
this.#store = store;
|
|
2939
|
+
this.#onWorkflowChange = this.#change.bind(this);
|
|
2940
|
+
this.#onWorkflowAdd = this.#addPhase.bind(this);
|
|
2941
|
+
this.#onWorkflowRemove = this.#removePhase.bind(this);
|
|
2942
|
+
this.#onPhaseChange = this.#change.bind(this);
|
|
2943
|
+
this.#onPhaseAdd = this.#addTask.bind(this);
|
|
2944
|
+
this.#onPhaseRemove = this.#removeTask.bind(this);
|
|
2945
|
+
this.#onTaskChange = this.#change.bind(this);
|
|
2946
|
+
this.#attachWorkflow();
|
|
2947
|
+
}
|
|
2948
|
+
get fault() {
|
|
2949
|
+
return this.#fault;
|
|
2950
|
+
}
|
|
2951
|
+
/**
|
|
2952
|
+
* Persist every change through this required boundary.
|
|
2953
|
+
*
|
|
2954
|
+
* @param checkpoint - The boundary being made durable
|
|
2955
|
+
* @param task - The task owning an attempt or settlement
|
|
2956
|
+
* @param attempt - The persisted attempt number
|
|
2957
|
+
* @returns Whether the latest state reached the store
|
|
2958
|
+
*/
|
|
2959
|
+
async checkpoint(checkpoint, task, attempt) {
|
|
2960
|
+
const revision = this.#mark();
|
|
2961
|
+
while (this.#stored < revision) await this.#flush();
|
|
2962
|
+
if (this.#error === void 0) return true;
|
|
2963
|
+
if (this.#fault === void 0) this.#fault = Object.freeze({
|
|
2964
|
+
origin: "persistence",
|
|
2965
|
+
checkpoint,
|
|
2966
|
+
message: this.#error,
|
|
2967
|
+
...task === void 0 ? {} : { task: task.id },
|
|
2968
|
+
...attempt === void 0 ? {} : { attempt }
|
|
2969
|
+
});
|
|
2970
|
+
return false;
|
|
2971
|
+
}
|
|
2972
|
+
/**
|
|
2973
|
+
* Stop observing the live tree and persist its final state.
|
|
2974
|
+
*
|
|
2975
|
+
* @returns Whether the final snapshot reached the store
|
|
2976
|
+
*/
|
|
2977
|
+
async finalize() {
|
|
2978
|
+
this.detach();
|
|
2979
|
+
return this.checkpoint("final");
|
|
2980
|
+
}
|
|
2981
|
+
/** Stop observing the live tree. */
|
|
2982
|
+
detach() {
|
|
2983
|
+
if (!this.#attached) return;
|
|
2984
|
+
this.#attached = false;
|
|
2985
|
+
this.#workflow.emitter.off("start", this.#onWorkflowChange);
|
|
2986
|
+
this.#workflow.emitter.off("complete", this.#onWorkflowChange);
|
|
2987
|
+
this.#workflow.emitter.off("fail", this.#onWorkflowChange);
|
|
2988
|
+
this.#workflow.emitter.off("skip", this.#onWorkflowChange);
|
|
2989
|
+
this.#workflow.emitter.off("stop", this.#onWorkflowChange);
|
|
2990
|
+
this.#workflow.emitter.off("move", this.#onWorkflowChange);
|
|
2991
|
+
this.#workflow.emitter.off("update", this.#onWorkflowChange);
|
|
2992
|
+
this.#workflow.emitter.off("add", this.#onWorkflowAdd);
|
|
2993
|
+
this.#workflow.emitter.off("remove", this.#onWorkflowRemove);
|
|
2994
|
+
for (const phase of this.#phases) this.#detachPhase(phase);
|
|
2995
|
+
}
|
|
2996
|
+
#attachWorkflow() {
|
|
2997
|
+
this.#workflow.emitter.on("start", this.#onWorkflowChange);
|
|
2998
|
+
this.#workflow.emitter.on("complete", this.#onWorkflowChange);
|
|
2999
|
+
this.#workflow.emitter.on("fail", this.#onWorkflowChange);
|
|
3000
|
+
this.#workflow.emitter.on("skip", this.#onWorkflowChange);
|
|
3001
|
+
this.#workflow.emitter.on("stop", this.#onWorkflowChange);
|
|
3002
|
+
this.#workflow.emitter.on("move", this.#onWorkflowChange);
|
|
3003
|
+
this.#workflow.emitter.on("update", this.#onWorkflowChange);
|
|
3004
|
+
this.#workflow.emitter.on("add", this.#onWorkflowAdd);
|
|
3005
|
+
this.#workflow.emitter.on("remove", this.#onWorkflowRemove);
|
|
3006
|
+
for (const phase of this.#workflow.phases.phases()) this.#attachPhase(phase);
|
|
3007
|
+
}
|
|
3008
|
+
#attachPhase(phase) {
|
|
3009
|
+
if (this.#phases.has(phase)) return;
|
|
3010
|
+
this.#phases.add(phase);
|
|
3011
|
+
phase.emitter.on("start", this.#onPhaseChange);
|
|
3012
|
+
phase.emitter.on("complete", this.#onPhaseChange);
|
|
3013
|
+
phase.emitter.on("fail", this.#onPhaseChange);
|
|
3014
|
+
phase.emitter.on("skip", this.#onPhaseChange);
|
|
3015
|
+
phase.emitter.on("stop", this.#onPhaseChange);
|
|
3016
|
+
phase.emitter.on("move", this.#onPhaseChange);
|
|
3017
|
+
phase.emitter.on("update", this.#onPhaseChange);
|
|
3018
|
+
phase.emitter.on("add", this.#onPhaseAdd);
|
|
3019
|
+
phase.emitter.on("remove", this.#onPhaseRemove);
|
|
3020
|
+
for (const task of phase.tasks.tasks()) this.#attachTask(task);
|
|
3021
|
+
}
|
|
3022
|
+
#detachPhase(phase) {
|
|
3023
|
+
if (!this.#phases.delete(phase)) return;
|
|
3024
|
+
phase.emitter.off("start", this.#onPhaseChange);
|
|
3025
|
+
phase.emitter.off("complete", this.#onPhaseChange);
|
|
3026
|
+
phase.emitter.off("fail", this.#onPhaseChange);
|
|
3027
|
+
phase.emitter.off("skip", this.#onPhaseChange);
|
|
3028
|
+
phase.emitter.off("stop", this.#onPhaseChange);
|
|
3029
|
+
phase.emitter.off("move", this.#onPhaseChange);
|
|
3030
|
+
phase.emitter.off("update", this.#onPhaseChange);
|
|
3031
|
+
phase.emitter.off("add", this.#onPhaseAdd);
|
|
3032
|
+
phase.emitter.off("remove", this.#onPhaseRemove);
|
|
3033
|
+
for (const task of phase.tasks.tasks()) this.#detachTask(task);
|
|
3034
|
+
}
|
|
3035
|
+
#attachTask(task) {
|
|
3036
|
+
if (this.#tasks.has(task)) return;
|
|
3037
|
+
this.#tasks.add(task);
|
|
3038
|
+
task.emitter.on("start", this.#onTaskChange);
|
|
3039
|
+
task.emitter.on("complete", this.#onTaskChange);
|
|
3040
|
+
task.emitter.on("fail", this.#onTaskChange);
|
|
3041
|
+
task.emitter.on("skip", this.#onTaskChange);
|
|
3042
|
+
task.emitter.on("stop", this.#onTaskChange);
|
|
3043
|
+
task.emitter.on("report", this.#onTaskChange);
|
|
3044
|
+
task.emitter.on("pulse", this.#onTaskChange);
|
|
3045
|
+
}
|
|
3046
|
+
#detachTask(task) {
|
|
3047
|
+
if (!this.#tasks.delete(task)) return;
|
|
3048
|
+
task.emitter.off("start", this.#onTaskChange);
|
|
3049
|
+
task.emitter.off("complete", this.#onTaskChange);
|
|
3050
|
+
task.emitter.off("fail", this.#onTaskChange);
|
|
3051
|
+
task.emitter.off("skip", this.#onTaskChange);
|
|
3052
|
+
task.emitter.off("stop", this.#onTaskChange);
|
|
3053
|
+
task.emitter.off("report", this.#onTaskChange);
|
|
3054
|
+
task.emitter.off("pulse", this.#onTaskChange);
|
|
3055
|
+
}
|
|
3056
|
+
#addPhase(phase) {
|
|
3057
|
+
this.#attachPhase(phase);
|
|
3058
|
+
this.#change();
|
|
3059
|
+
}
|
|
3060
|
+
#removePhase(phase) {
|
|
3061
|
+
this.#detachPhase(phase);
|
|
3062
|
+
this.#change();
|
|
3063
|
+
}
|
|
3064
|
+
#addTask(task) {
|
|
3065
|
+
this.#attachTask(task);
|
|
3066
|
+
this.#change();
|
|
3067
|
+
}
|
|
3068
|
+
#removeTask(task) {
|
|
3069
|
+
this.#detachTask(task);
|
|
3070
|
+
this.#change();
|
|
3071
|
+
}
|
|
3072
|
+
#change() {
|
|
3073
|
+
this.#mark();
|
|
3074
|
+
this.#flush();
|
|
3075
|
+
}
|
|
3076
|
+
async #flush() {
|
|
3077
|
+
if (this.#writing !== void 0) {
|
|
3078
|
+
await this.#writing;
|
|
3079
|
+
return;
|
|
3080
|
+
}
|
|
3081
|
+
const writing = this.#drain();
|
|
3082
|
+
this.#writing = writing;
|
|
3083
|
+
try {
|
|
3084
|
+
await writing;
|
|
3085
|
+
} finally {
|
|
3086
|
+
if (this.#writing === writing) this.#writing = void 0;
|
|
3087
|
+
if (this.#stored < this.#revision) this.#flush();
|
|
3088
|
+
}
|
|
3089
|
+
}
|
|
3090
|
+
async #drain() {
|
|
3091
|
+
while (this.#stored < this.#revision) {
|
|
3092
|
+
const revision = this.#revision;
|
|
3093
|
+
try {
|
|
3094
|
+
await this.#store.set(this.#workflow.snapshot());
|
|
3095
|
+
this.#error = void 0;
|
|
3096
|
+
} catch (error) {
|
|
3097
|
+
this.#error = errorToMessage(error);
|
|
3098
|
+
}
|
|
3099
|
+
this.#stored = revision;
|
|
3100
|
+
}
|
|
3101
|
+
}
|
|
3102
|
+
#mark() {
|
|
3103
|
+
this.#revision += 1;
|
|
3104
|
+
return this.#revision;
|
|
3105
|
+
}
|
|
2291
3106
|
};
|
|
2292
3107
|
//#endregion
|
|
2293
3108
|
//#region src/core/WorkflowRunner.ts
|
|
@@ -2304,17 +3119,16 @@ var TaskController = class {
|
|
|
2304
3119
|
* `AbortSignal.any` (exactly as the agent runtime folds its bounds); pacing is the shipped
|
|
2305
3120
|
* {@link SchedulerInterface}. The runner writes ZERO concurrency / retry / abort logic of
|
|
2306
3121
|
* its own — it only sequences phases, dispatches a task's own handler, and drives the live
|
|
2307
|
-
* entity.
|
|
2308
|
-
*
|
|
2309
|
-
*
|
|
3122
|
+
* entity. The workflow layer owns per-task deadlines because timeout settlement must
|
|
3123
|
+
* update the live leaf under the phase's `bail` policy before the substrate unit settles.
|
|
3124
|
+
* - **Pure engine — no integration registry.** The runner carries no behavior or provider
|
|
3125
|
+
* registry: each live {@link TaskInterface} already
|
|
2310
3126
|
* resolved its own {@link import('./types.js').WorkflowFunction} into
|
|
2311
3127
|
* {@link import('./types.js').TaskInterface.handler} ONCE at construction (build, restore,
|
|
2312
3128
|
* or a live mint all resolve it identically, from {@link WorkflowOptions.functions}), so
|
|
2313
|
-
* dispatch is simply "invoke the task's own handler".
|
|
2314
|
-
*
|
|
2315
|
-
* {@link
|
|
2316
|
-
* {@link WorkflowOptions.functions} like any other behavior. This module never imports
|
|
2317
|
-
* any tool/agent package.
|
|
3129
|
+
* dispatch is simply "invoke the task's own handler". Provider, protocol, and tool
|
|
3130
|
+
* integrations remain application-owned {@link import('./types.js').WorkflowFunction}s
|
|
3131
|
+
* composed into {@link WorkflowOptions.functions}. This module imports none of them.
|
|
2318
3132
|
* - **Two `execute` forms, one engine.** `execute(definition, options)` BUILDS the live tree
|
|
2319
3133
|
* from a {@link WorkflowDefinition} (single source of truth for the `run` / `concurrency`
|
|
2320
3134
|
* metadata); `execute(workflow, options)` DRIVES a caller-owned, ALREADY-BUILT
|
|
@@ -2334,10 +3148,9 @@ var TaskController = class {
|
|
|
2334
3148
|
* for `spawn` to accept (the runner already drained) is swept `skip`ped afterward so the
|
|
2335
3149
|
* phase always reaches a coherent terminal state.
|
|
2336
3150
|
* - **Dispatch by handler.** `#runTask` invokes the live task's own
|
|
2337
|
-
* {@link import('./types.js').TaskInterface.handler} directly
|
|
2338
|
-
*
|
|
2339
|
-
*
|
|
2340
|
-
* task's {@link import('./types.js').TaskControllerInterface} handle.
|
|
3151
|
+
* {@link import('./types.js').TaskInterface.handler} directly. An omitted `run` deliberately
|
|
3152
|
+
* auto-completes with JSON `null`; a present unresolved name is rejected by the synchronous
|
|
3153
|
+
* execution claim and never false-completes.
|
|
2341
3154
|
* - **`bail` → substrate.** Under `bail: true` (halt) a genuine task failure `fail`s the leaf
|
|
2342
3155
|
* THEN re-throws, so the substrate Runner fail-fasts — it aborts the in-flight siblings
|
|
2343
3156
|
* (their `controller.signal` fires; a mid-flight sibling `skip`s) and rejects the phase run;
|
|
@@ -2345,11 +3158,12 @@ var TaskController = class {
|
|
|
2345
3158
|
* Under `bail: false` (graceful) a failure `fail`s the leaf and RESOLVES (never throws), so
|
|
2346
3159
|
* the Runner settles every unit (allSettled) and the run finishes (the workflow derives
|
|
2347
3160
|
* `completed`, the failure recorded in the result tree).
|
|
2348
|
-
* - **Pause / stop / destroy gates.**
|
|
2349
|
-
*
|
|
2350
|
-
*
|
|
2351
|
-
*
|
|
2352
|
-
*
|
|
3161
|
+
* - **Pause / stop / destroy gates.** Workflow, phase, and task gates are checked before
|
|
3162
|
+
* dispatch, and a running handler can checkpoint their folded state through
|
|
3163
|
+
* {@link import('./types.js').TaskControllerInterface.wait}. Because the substrate acquires
|
|
3164
|
+
* concurrency before this handler gate, a paused task occupies one phase slot until resume;
|
|
3165
|
+
* already-running siblings continue and its per-attempt timeout keeps counting. A GRACEFUL
|
|
3166
|
+
* `workflow.stop()` (no signal involved) is caught at
|
|
2353
3167
|
* those same gates: not-yet-started work is `skip`ped, in-flight work finishes naturally. A
|
|
2354
3168
|
* HARD `workflow.destroy()` aborts {@link WorkflowInterface.signal}, which `#fold` has folded
|
|
2355
3169
|
* into the run's composed signal — so it cancels the active phase Runner (and every
|
|
@@ -2366,37 +3180,49 @@ var TaskController = class {
|
|
|
2366
3180
|
* {@link TaskController} signal `AbortSignal.any`-combines the substrate per-unit signal with
|
|
2367
3181
|
* `runSignal`, so a handler observes either cause directly.
|
|
2368
3182
|
* - **Re-entrant-safe.** No shared per-run mutable field: the active-Runner holder is LOCAL to
|
|
2369
|
-
* each `#execute`, so a nested `execute`
|
|
2370
|
-
*
|
|
3183
|
+
* each `#execute`, so a nested application-level `execute` cannot clobber the outer run's
|
|
3184
|
+
* state.
|
|
2371
3185
|
*/
|
|
2372
|
-
var WorkflowRunner = class {
|
|
3186
|
+
var WorkflowRunner = class WorkflowRunner {
|
|
3187
|
+
static #executions = /* @__PURE__ */ new WeakSet();
|
|
2373
3188
|
#scheduler;
|
|
2374
3189
|
constructor(scheduler) {
|
|
2375
3190
|
this.#scheduler = scheduler;
|
|
2376
3191
|
}
|
|
2377
3192
|
execute(target, options) {
|
|
2378
3193
|
if (this.#isWorkflow(target)) {
|
|
2379
|
-
|
|
2380
|
-
id: target.id,
|
|
2381
|
-
status: target.status,
|
|
2382
|
-
destroyed: target.destroyed
|
|
2383
|
-
});
|
|
3194
|
+
this.#acquire(target);
|
|
2384
3195
|
return this.#execute(target, options);
|
|
2385
3196
|
}
|
|
2386
3197
|
const workflow = new Workflow(definitionToSnapshot(target, options?.bail ?? target.bail ?? false), options);
|
|
3198
|
+
this.#acquire(workflow);
|
|
2387
3199
|
return this.#execute(workflow, options);
|
|
2388
3200
|
}
|
|
3201
|
+
#acquire(workflow) {
|
|
3202
|
+
const tasks = workflow.phases.phases().flatMap((phase) => phase.tasks.tasks());
|
|
3203
|
+
if (!((workflow.status === "pending" || workflow.status === "running") && tasks.every((task) => task.status !== "running") && (tasks.length === 0 || tasks.some((task) => task.status === "pending")) && tasks.every((task) => task.run === void 0 || task.handler !== void 0)) || workflow.destroyed || WorkflowRunner.#executions.has(workflow)) throw new WorkflowError("TRANSITION", `workflow '${workflow.id}' is not drivable`, {
|
|
3204
|
+
id: workflow.id,
|
|
3205
|
+
status: workflow.status,
|
|
3206
|
+
destroyed: workflow.destroyed
|
|
3207
|
+
});
|
|
3208
|
+
WorkflowRunner.#executions.add(workflow);
|
|
3209
|
+
}
|
|
2389
3210
|
async #execute(workflow, options) {
|
|
2390
3211
|
const ms = options?.timeout;
|
|
2391
|
-
const timeout = ms !== void 0 && ms > 0 ? createTimeout({ ms }) : void 0;
|
|
3212
|
+
const timeout = ms !== void 0 && Number.isFinite(ms) && ms > 0 && ms <= 2147483647 ? createTimeout({ ms }) : void 0;
|
|
2392
3213
|
timeout?.start();
|
|
2393
3214
|
options?.budget?.start();
|
|
2394
3215
|
const runSignal = this.#fold(workflow, options, timeout);
|
|
3216
|
+
const persistence = options?.store === void 0 ? void 0 : new WorkflowPersistence(workflow, options.store);
|
|
2395
3217
|
const holder = { runner: void 0 };
|
|
2396
|
-
const onCancel = (
|
|
3218
|
+
const onCancel = this.#abortActive.bind(this, holder, runSignal);
|
|
2397
3219
|
if (runSignal.aborted) onCancel();
|
|
2398
3220
|
else runSignal.addEventListener("abort", onCancel, { once: true });
|
|
2399
3221
|
try {
|
|
3222
|
+
if (persistence !== void 0 && !await persistence.checkpoint("initial")) {
|
|
3223
|
+
if (this.#stoppable(workflow)) workflow.stop();
|
|
3224
|
+
this.#skipFrom(workflow.phases.phases(), 0);
|
|
3225
|
+
}
|
|
2400
3226
|
let index = 0;
|
|
2401
3227
|
for (;;) {
|
|
2402
3228
|
const phases = workflow.phases.phases();
|
|
@@ -2410,12 +3236,17 @@ var WorkflowRunner = class {
|
|
|
2410
3236
|
this.#haltFrom(phases, index, workflow, runSignal);
|
|
2411
3237
|
break;
|
|
2412
3238
|
}
|
|
2413
|
-
if (workflow.paused) await this.#raceWait(
|
|
3239
|
+
if (workflow.paused) await this.#raceWait(workflow.wait(), runSignal, void 0, workflow);
|
|
2414
3240
|
if (this.#cancelled(runSignal) || this.#halted(workflow)) {
|
|
2415
3241
|
this.#haltFrom(workflow.phases.phases(), index, workflow, runSignal);
|
|
2416
3242
|
break;
|
|
2417
3243
|
}
|
|
2418
|
-
if (
|
|
3244
|
+
if (phase.status === "skipped" || phase.status === "stopped") {
|
|
3245
|
+
this.#skipFrom([phase], 0);
|
|
3246
|
+
index += 1;
|
|
3247
|
+
continue;
|
|
3248
|
+
}
|
|
3249
|
+
if (await this.#runPhase(workflow, phase, runSignal, holder, persistence)) {
|
|
2419
3250
|
this.#skipFrom(workflow.phases.phases(), index + 1);
|
|
2420
3251
|
break;
|
|
2421
3252
|
}
|
|
@@ -2429,24 +3260,28 @@ var WorkflowRunner = class {
|
|
|
2429
3260
|
}
|
|
2430
3261
|
if (this.#cancelled(runSignal)) this.#haltFrom(workflow.phases.phases(), 0, workflow, runSignal);
|
|
2431
3262
|
else if (this.#completable(workflow)) workflow.complete();
|
|
3263
|
+
const durable = await persistence?.finalize();
|
|
2432
3264
|
return {
|
|
2433
3265
|
workflow,
|
|
2434
3266
|
status: workflow.status,
|
|
2435
|
-
results: workflow.results()
|
|
3267
|
+
results: workflow.results(),
|
|
3268
|
+
...durable === void 0 ? {} : { durable },
|
|
3269
|
+
...persistence?.fault === void 0 ? {} : { fault: persistence.fault }
|
|
2436
3270
|
};
|
|
3271
|
+
} catch (error) {
|
|
3272
|
+
if (this.#stoppable(workflow)) workflow.stop();
|
|
3273
|
+
this.#skipFrom(workflow.phases.phases(), 0);
|
|
3274
|
+
await persistence?.finalize();
|
|
3275
|
+
throw error;
|
|
2437
3276
|
} finally {
|
|
3277
|
+
persistence?.detach();
|
|
2438
3278
|
timeout?.clear();
|
|
2439
3279
|
runSignal.removeEventListener("abort", onCancel);
|
|
2440
3280
|
}
|
|
2441
3281
|
}
|
|
2442
|
-
async #runPhase(workflow, phase, runSignal, holder) {
|
|
3282
|
+
async #runPhase(workflow, phase, runSignal, holder, persistence) {
|
|
2443
3283
|
const launched = /* @__PURE__ */ new Set();
|
|
2444
|
-
|
|
2445
|
-
const onAdd = (task) => {
|
|
2446
|
-
if (launched.has(task.id)) return;
|
|
2447
|
-
launched.add(task.id);
|
|
2448
|
-
runner?.spawn(task);
|
|
2449
|
-
};
|
|
3284
|
+
const onAdd = this.#spawnAdded.bind(this, launched, holder);
|
|
2450
3285
|
phase.emitter.on("add", onAdd);
|
|
2451
3286
|
try {
|
|
2452
3287
|
const tasks = phase.tasks.tasks();
|
|
@@ -2455,15 +3290,13 @@ var WorkflowRunner = class {
|
|
|
2455
3290
|
const bail = phase.bail;
|
|
2456
3291
|
const concurrency = phase.concurrency !== void 0 && phase.concurrency > 0 ? phase.concurrency : DEFAULT_PHASE_CONCURRENCY;
|
|
2457
3292
|
const attempts = /* @__PURE__ */ new Map();
|
|
3293
|
+
for (const task of tasks) attempts.set(task.id, task.attempts);
|
|
3294
|
+
const owners = /* @__PURE__ */ new Map();
|
|
2458
3295
|
const created = new Runner({
|
|
2459
3296
|
concurrency,
|
|
2460
|
-
entries: (
|
|
2461
|
-
|
|
2462
|
-
timeout: task.timeout
|
|
2463
|
-
}),
|
|
2464
|
-
handler: (controller) => this.#runTask(workflow, controller.input, controller, runSignal, bail, attempts)
|
|
3297
|
+
entries: this.#entry.bind(this),
|
|
3298
|
+
handler: this.#runUnit.bind(this, workflow, runSignal, bail, attempts, owners, persistence)
|
|
2465
3299
|
});
|
|
2466
|
-
runner = created;
|
|
2467
3300
|
holder.runner = created;
|
|
2468
3301
|
try {
|
|
2469
3302
|
await created.execute(tasks);
|
|
@@ -2480,74 +3313,215 @@ var WorkflowRunner = class {
|
|
|
2480
3313
|
for (const task of phase.tasks.tasks()) this.#skip(task);
|
|
2481
3314
|
}
|
|
2482
3315
|
}
|
|
2483
|
-
|
|
2484
|
-
|
|
3316
|
+
#abortActive(holder, runSignal) {
|
|
3317
|
+
holder.runner?.abort(runSignal.reason);
|
|
3318
|
+
}
|
|
3319
|
+
#spawnAdded(launched, holder, task) {
|
|
3320
|
+
if (launched.has(task.id)) return;
|
|
3321
|
+
launched.add(task.id);
|
|
3322
|
+
holder.runner?.spawn(task);
|
|
3323
|
+
}
|
|
3324
|
+
#entry(task) {
|
|
3325
|
+
const retries = Math.max(0, (task.retries ?? 0) - task.attempts);
|
|
3326
|
+
return retries === 0 ? {} : { retries };
|
|
3327
|
+
}
|
|
3328
|
+
#runUnit(workflow, runSignal, bail, attempts, owners, persistence, controller) {
|
|
3329
|
+
return this.#runTask(workflow, controller.input, controller, runSignal, bail, attempts, owners, persistence);
|
|
3330
|
+
}
|
|
3331
|
+
async #runTask(workflow, task, controller, runSignal, bail, attempts, owners, persistence) {
|
|
2485
3332
|
const attempt = (attempts.get(task.id) ?? 0) + 1;
|
|
2486
3333
|
attempts.set(task.id, attempt);
|
|
2487
3334
|
const last = attempt > Math.max(0, task.retries ?? 0);
|
|
2488
|
-
if (
|
|
2489
|
-
if (task
|
|
2490
|
-
|
|
2491
|
-
this.#skipCancelled(task, workflow, runSignal);
|
|
3335
|
+
if (task.status !== "pending" && task.status !== "running") return;
|
|
3336
|
+
if (this.#skipping(task, controller, runSignal) || this.#halted(workflow, task.phase)) {
|
|
3337
|
+
this.#settleCancelled(task, workflow, runSignal);
|
|
2492
3338
|
return;
|
|
2493
3339
|
}
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
return;
|
|
2498
|
-
}
|
|
2499
|
-
const handle = new TaskController(signal, task.snapshot().metadata, task.context, () => workflow.results());
|
|
3340
|
+
const ms = task.timeout;
|
|
3341
|
+
const deadline = ms !== void 0 && Number.isFinite(ms) && ms > 0 && ms <= 2147483647 ? createTimeout({ ms }) : void 0;
|
|
3342
|
+
const signal = this.#taskSignal(task, controller.signal, runSignal, deadline);
|
|
2500
3343
|
try {
|
|
2501
|
-
|
|
2502
|
-
if (task.
|
|
2503
|
-
|
|
3344
|
+
task.start();
|
|
3345
|
+
if (task.attempts !== attempt) return;
|
|
3346
|
+
owners.set(task.id, attempt);
|
|
3347
|
+
deadline?.start();
|
|
3348
|
+
const durable = persistence === void 0 ? true : await persistence.checkpoint("attempt", task, attempt);
|
|
3349
|
+
if (!this.#owns(owners, task, attempt)) return;
|
|
3350
|
+
if (!durable) {
|
|
3351
|
+
if (this.#stoppable(workflow)) workflow.stop();
|
|
2504
3352
|
return;
|
|
2505
3353
|
}
|
|
2506
|
-
if (
|
|
2507
|
-
|
|
3354
|
+
if (task.run !== void 0 && task.handler === void 0) {
|
|
3355
|
+
const error = new WorkflowError("TRANSITION", `task '${task.id}' has an unresolved run '${task.run}'`, {
|
|
3356
|
+
task: task.id,
|
|
3357
|
+
run: task.run
|
|
3358
|
+
});
|
|
3359
|
+
task.fail({
|
|
3360
|
+
origin: "handler",
|
|
3361
|
+
message: error.message
|
|
3362
|
+
});
|
|
3363
|
+
if (bail) throw error;
|
|
2508
3364
|
return;
|
|
2509
3365
|
}
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
if (task.
|
|
2513
|
-
|
|
3366
|
+
if (await this.#gate(workflow.paused ? workflow.wait() : void 0, task, workflow, controller, runSignal, signal, attempts, owners, attempt, last, bail)) return;
|
|
3367
|
+
if (await this.#gate(task.phase.paused ? task.phase.wait() : void 0, task, workflow, controller, runSignal, signal, attempts, owners, attempt, last, bail)) return;
|
|
3368
|
+
if (await this.#gate(task.paused ? task.wait() : void 0, task, workflow, controller, runSignal, signal, attempts, owners, attempt, last, bail)) return;
|
|
3369
|
+
if (this.#skipping(task, controller, runSignal) || this.#halted(workflow, task.phase)) {
|
|
3370
|
+
this.#settleCancelled(task, workflow, runSignal);
|
|
3371
|
+
return;
|
|
3372
|
+
}
|
|
3373
|
+
if (task.status !== "running") return;
|
|
3374
|
+
const handle = new TaskController(signal, task.snapshot().metadata, task, attempt, () => workflow.results(), (input) => this.#owns(owners, task, attempt) && !signal.aborted ? task.report(input) : failure(new WorkflowError("TRANSITION", `task '${task.id}' attempt '${attempt}' no longer owns activity`, {
|
|
3375
|
+
task: task.id,
|
|
3376
|
+
attempt
|
|
3377
|
+
})), () => this.#owns(owners, task, attempt) && !signal.aborted && task.pulse());
|
|
3378
|
+
let outcome;
|
|
3379
|
+
try {
|
|
3380
|
+
outcome = task.handler === void 0 ? [true, null] : await this.#raceHandler(Promise.resolve(task.handler(handle)), signal, this.#skipping.bind(this, task, controller, runSignal));
|
|
3381
|
+
} catch (error) {
|
|
3382
|
+
if (!this.#owns(owners, task, attempt)) return;
|
|
3383
|
+
if (task.status !== "running" || this.#skipping(task, controller, runSignal)) {
|
|
3384
|
+
this.#settleCancelled(task, workflow, runSignal);
|
|
3385
|
+
return;
|
|
3386
|
+
}
|
|
3387
|
+
if (signal.aborted) {
|
|
3388
|
+
this.#timedOut(owners, task, attempt, last, bail);
|
|
3389
|
+
return;
|
|
3390
|
+
}
|
|
3391
|
+
this.#failed(owners, task, attempt, error, last, bail);
|
|
3392
|
+
return;
|
|
3393
|
+
}
|
|
3394
|
+
if (!this.#owns(owners, task, attempt)) return;
|
|
3395
|
+
if (!outcome[0]) {
|
|
3396
|
+
this.#settleAttempt(task, workflow, controller, runSignal, signal, attempts, owners, attempt, last, bail, outcome[2]);
|
|
3397
|
+
return;
|
|
3398
|
+
}
|
|
3399
|
+
if (task.status !== "running") return;
|
|
3400
|
+
if (this.#skipping(task, controller, runSignal)) {
|
|
3401
|
+
this.#settleCancelled(task, workflow, runSignal);
|
|
2514
3402
|
return;
|
|
2515
3403
|
}
|
|
2516
3404
|
if (signal.aborted) {
|
|
2517
|
-
this.#timedOut(task, last);
|
|
3405
|
+
this.#timedOut(owners, task, attempt, last, bail);
|
|
2518
3406
|
return;
|
|
2519
3407
|
}
|
|
2520
|
-
if (!
|
|
2521
|
-
|
|
2522
|
-
|
|
3408
|
+
if (!this.#owns(owners, task, attempt)) return;
|
|
3409
|
+
try {
|
|
3410
|
+
task.complete(outcome[1]);
|
|
3411
|
+
} catch (error) {
|
|
3412
|
+
if (!this.#owns(owners, task, attempt)) return;
|
|
3413
|
+
if (task.status !== "running") throw error;
|
|
3414
|
+
this.#failed(owners, task, attempt, error, last, bail);
|
|
3415
|
+
}
|
|
3416
|
+
} finally {
|
|
3417
|
+
deadline?.clear();
|
|
3418
|
+
if (persistence !== void 0 && this.#owns(owners, task, attempt) && isTerminalStatus(task.status) && !await persistence.checkpoint("settlement", task, attempt) && this.#stoppable(workflow)) workflow.stop();
|
|
3419
|
+
this.#revoke(owners, task.id, attempt);
|
|
2523
3420
|
}
|
|
2524
3421
|
}
|
|
2525
|
-
#
|
|
2526
|
-
|
|
2527
|
-
task
|
|
3422
|
+
async #gate(wait, task, workflow, controller, runSignal, signal, attempts, owners, attempt, last, bail) {
|
|
3423
|
+
const genuine = wait === void 0 ? void 0 : await this.#raceWait(wait, signal, this.#skipping.bind(this, task, controller, runSignal), workflow, task.phase);
|
|
3424
|
+
return this.#settleAttempt(task, workflow, controller, runSignal, signal, attempts, owners, attempt, last, bail, genuine);
|
|
3425
|
+
}
|
|
3426
|
+
#settleAttempt(task, workflow, controller, runSignal, signal, attempts, owners, attempt, last, bail, genuine) {
|
|
3427
|
+
if (attempts.get(task.id) !== attempt || !this.#owns(owners, task, attempt)) return true;
|
|
3428
|
+
if (signal.aborted) {
|
|
3429
|
+
if (genuine ?? this.#skipping(task, controller, runSignal)) this.#settleCancelled(task, workflow, runSignal);
|
|
3430
|
+
else this.#timedOut(owners, task, attempt, last, bail);
|
|
3431
|
+
return true;
|
|
3432
|
+
}
|
|
3433
|
+
if (this.#skipping(task, controller, runSignal) || this.#halted(workflow, task.phase)) {
|
|
3434
|
+
this.#settleCancelled(task, workflow, runSignal);
|
|
3435
|
+
return true;
|
|
3436
|
+
}
|
|
3437
|
+
return task.status !== "running";
|
|
3438
|
+
}
|
|
3439
|
+
async #raceHandler(handler, signal, cancelled) {
|
|
3440
|
+
if (signal.aborted) return [
|
|
3441
|
+
false,
|
|
3442
|
+
void 0,
|
|
3443
|
+
cancelled()
|
|
3444
|
+
];
|
|
3445
|
+
const deferred = Promise.withResolvers();
|
|
3446
|
+
const onAbort = this.#resolveHandlerAbort.bind(this, deferred, cancelled);
|
|
3447
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
3448
|
+
try {
|
|
3449
|
+
return await Promise.race([handler.then((value) => [true, value]), deferred.promise]);
|
|
3450
|
+
} finally {
|
|
3451
|
+
signal.removeEventListener("abort", onAbort);
|
|
3452
|
+
}
|
|
2528
3453
|
}
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
3454
|
+
#resolveHandlerAbort(deferred, cancelled) {
|
|
3455
|
+
deferred.resolve([
|
|
3456
|
+
false,
|
|
3457
|
+
void 0,
|
|
3458
|
+
cancelled()
|
|
3459
|
+
]);
|
|
3460
|
+
}
|
|
3461
|
+
#timedOut(owners, task, attempt, last, bail) {
|
|
3462
|
+
if (!this.#owns(owners, task, attempt)) return;
|
|
3463
|
+
const error = /* @__PURE__ */ new Error(`task '${task.id}' timed out`);
|
|
3464
|
+
if (last) task.fail({
|
|
3465
|
+
origin: "timeout",
|
|
3466
|
+
message: error.message
|
|
3467
|
+
});
|
|
3468
|
+
if (!last || bail) throw error;
|
|
3469
|
+
}
|
|
3470
|
+
#failed(owners, task, attempt, error, last, bail) {
|
|
3471
|
+
if (!this.#owns(owners, task, attempt)) return;
|
|
3472
|
+
if (!last) throw error;
|
|
3473
|
+
task.fail({
|
|
3474
|
+
origin: "handler",
|
|
3475
|
+
message: errorToMessage(error)
|
|
2535
3476
|
});
|
|
3477
|
+
if (bail) throw error;
|
|
3478
|
+
}
|
|
3479
|
+
#owns(owners, task, attempt) {
|
|
3480
|
+
return owners.get(task.id) === attempt && task.attempts === attempt;
|
|
3481
|
+
}
|
|
3482
|
+
#revoke(owners, id, attempt) {
|
|
3483
|
+
if (owners.get(id) === attempt) owners.delete(id);
|
|
3484
|
+
}
|
|
3485
|
+
async #raceWait(wait, signal, cancelled, workflow, phase) {
|
|
3486
|
+
if (signal.aborted) return cancelled?.();
|
|
3487
|
+
const deferred = Promise.withResolvers();
|
|
3488
|
+
const onAbort = this.#resolveWaitAbort.bind(this, deferred, cancelled);
|
|
3489
|
+
const onTerminal = this.#resolveWaitAbort.bind(this, deferred, void 0);
|
|
3490
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
3491
|
+
workflow?.emitter.on("skip", onTerminal);
|
|
3492
|
+
workflow?.emitter.on("stop", onTerminal);
|
|
3493
|
+
phase?.emitter.on("skip", onTerminal);
|
|
3494
|
+
phase?.emitter.on("stop", onTerminal);
|
|
2536
3495
|
try {
|
|
2537
|
-
|
|
3496
|
+
if (workflow !== void 0 && this.#halted(workflow, phase)) deferred.resolve(void 0);
|
|
3497
|
+
const outcome = await Promise.race([wait, deferred.promise]);
|
|
3498
|
+
return typeof outcome === "boolean" ? outcome : void 0;
|
|
2538
3499
|
} finally {
|
|
2539
|
-
|
|
3500
|
+
signal.removeEventListener("abort", onAbort);
|
|
3501
|
+
workflow?.emitter.off("skip", onTerminal);
|
|
3502
|
+
workflow?.emitter.off("stop", onTerminal);
|
|
3503
|
+
phase?.emitter.off("skip", onTerminal);
|
|
3504
|
+
phase?.emitter.off("stop", onTerminal);
|
|
2540
3505
|
}
|
|
2541
3506
|
}
|
|
2542
|
-
#
|
|
2543
|
-
|
|
3507
|
+
#resolveWaitAbort(deferred, cancelled) {
|
|
3508
|
+
deferred.resolve(cancelled?.());
|
|
3509
|
+
}
|
|
3510
|
+
#taskSignal(task, unitSignal, runSignal, timeout) {
|
|
3511
|
+
const signals = [
|
|
3512
|
+
task.signal,
|
|
3513
|
+
unitSignal,
|
|
3514
|
+
runSignal
|
|
3515
|
+
];
|
|
3516
|
+
if (timeout !== void 0) signals.push(timeout.signal);
|
|
3517
|
+
return AbortSignal.any(signals);
|
|
2544
3518
|
}
|
|
2545
3519
|
#fold(workflow, options, timeout) {
|
|
2546
3520
|
const signals = [workflow.signal];
|
|
2547
3521
|
if (options?.signal !== void 0) signals.push(options.signal);
|
|
2548
3522
|
if (timeout !== void 0) signals.push(timeout.signal);
|
|
2549
3523
|
if (options?.budget !== void 0) signals.push(options.budget.signal);
|
|
2550
|
-
return signals.length === 1 ?
|
|
3524
|
+
return signals.length === 1 ? workflow.signal : AbortSignal.any(signals);
|
|
2551
3525
|
}
|
|
2552
3526
|
#haltFrom(phases, index, workflow, runSignal) {
|
|
2553
3527
|
if (this.#cancelled(runSignal) && this.#stoppable(workflow)) workflow.stop();
|
|
@@ -2560,22 +3534,22 @@ var WorkflowRunner = class {
|
|
|
2560
3534
|
for (const task of phase.tasks.tasks()) this.#skip(task);
|
|
2561
3535
|
}
|
|
2562
3536
|
}
|
|
2563
|
-
#
|
|
3537
|
+
#settleCancelled(task, workflow, runSignal) {
|
|
2564
3538
|
if (this.#cancelled(runSignal) && this.#stoppable(workflow)) workflow.stop();
|
|
2565
3539
|
this.#skip(task);
|
|
2566
3540
|
}
|
|
2567
3541
|
#skip(task) {
|
|
2568
3542
|
if (task.status === "pending" || task.status === "running") task.skip();
|
|
2569
3543
|
}
|
|
2570
|
-
#skipping(controller, runSignal) {
|
|
2571
|
-
return controller.aborted || runSignal.aborted;
|
|
3544
|
+
#skipping(task, controller, runSignal) {
|
|
3545
|
+
return task.signal.aborted || controller.aborted || runSignal.aborted;
|
|
2572
3546
|
}
|
|
2573
3547
|
#cancelled(runSignal) {
|
|
2574
3548
|
return runSignal.aborted;
|
|
2575
3549
|
}
|
|
2576
|
-
#halted(workflow) {
|
|
3550
|
+
#halted(workflow, phase) {
|
|
2577
3551
|
const status = workflow.status;
|
|
2578
|
-
return status === "failed" || status === "skipped" || status === "stopped";
|
|
3552
|
+
return status === "failed" || status === "skipped" || status === "stopped" || phase?.status === "skipped" || phase?.status === "stopped";
|
|
2579
3553
|
}
|
|
2580
3554
|
#stoppable(workflow) {
|
|
2581
3555
|
const status = workflow.status;
|
|
@@ -2618,14 +3592,7 @@ var WorkflowRunner = class {
|
|
|
2618
3592
|
* ```
|
|
2619
3593
|
*/
|
|
2620
3594
|
function createWorkflowContract() {
|
|
2621
|
-
|
|
2622
|
-
return {
|
|
2623
|
-
schema: contract.schema,
|
|
2624
|
-
is: contract.is,
|
|
2625
|
-
generate: (random) => contract.generate(random),
|
|
2626
|
-
parse: (value) => contract.parse(value),
|
|
2627
|
-
explain: (value) => contract.explain(value)
|
|
2628
|
-
};
|
|
3595
|
+
return createContract(workflowShape);
|
|
2629
3596
|
}
|
|
2630
3597
|
/**
|
|
2631
3598
|
* Build the live W-b entity tree from a {@link WorkflowDefinition} — the whole
|
|
@@ -2645,8 +3612,8 @@ function createWorkflowContract() {
|
|
|
2645
3612
|
*
|
|
2646
3613
|
* `options.functions` is the {@link import('./types.js').WorkflowFunctions} registry each live
|
|
2647
3614
|
* task's `run` name resolves against ONCE at construction into its runtime
|
|
2648
|
-
* {@link import('./types.js').TaskInterface.handler}
|
|
2649
|
-
*
|
|
3615
|
+
* {@link import('./types.js').TaskInterface.handler}. An omitted name is the deliberate no-op;
|
|
3616
|
+
* an unresolved present name remains inspectable but is rejected if execution is attempted.
|
|
2650
3617
|
*
|
|
2651
3618
|
* @param definition - The workflow definition to bring to life
|
|
2652
3619
|
* @param options - Runtime options (initial listeners, `bail` override, per-node options)
|
|
@@ -2680,6 +3647,9 @@ function createWorkflow(definition, options) {
|
|
|
2680
3647
|
* still wins when supplied (to deliberately re-run under a different policy). A structurally
|
|
2681
3648
|
* invalid snapshot (a status — or override — outside the lifecycle vocabulary, or a
|
|
2682
3649
|
* non-boolean `bail`) throws a `RESTORE` {@link WorkflowError}.
|
|
3650
|
+
* Runtime handlers are optional: without a matching `functions` entry, a persisted `run`
|
|
3651
|
+
* remains visible with an undefined `handler` so the exact state is inspectable. The runner
|
|
3652
|
+
* rejects that unresolved tree if execution is attempted.
|
|
2683
3653
|
*
|
|
2684
3654
|
* @param snapshot - The snapshot to restore (carries its own `bail` + `override`)
|
|
2685
3655
|
* @param options - Runtime options (initial listeners, an optional `bail` override, per-node options)
|
|
@@ -2694,8 +3664,20 @@ function createWorkflow(definition, options) {
|
|
|
2694
3664
|
* ```
|
|
2695
3665
|
*/
|
|
2696
3666
|
function restoreWorkflow(snapshot, options) {
|
|
2697
|
-
|
|
2698
|
-
|
|
3667
|
+
return new Workflow(cloneWorkflowSnapshot(snapshot), options);
|
|
3668
|
+
}
|
|
3669
|
+
/**
|
|
3670
|
+
* Rebuild an interrupted workflow at its remaining retry budget.
|
|
3671
|
+
*
|
|
3672
|
+
* @param snapshot - The hostile persisted snapshot
|
|
3673
|
+
* @param options - Runtime handlers and entity options
|
|
3674
|
+
* @returns A recoverable live workflow
|
|
3675
|
+
*/
|
|
3676
|
+
function recoverWorkflow(snapshot, options) {
|
|
3677
|
+
const owned = cloneWorkflowSnapshot(snapshot);
|
|
3678
|
+
if (owned.override !== void 0 || owned.phases.some((phase) => phase.override !== void 0)) throw new WorkflowError("RESTORE", `workflow '${owned.id}' has a terminal override`, { workflow: owned.id });
|
|
3679
|
+
if (!hasWorkflowHandlers(owned, options?.functions)) throw new WorkflowError("RESTORE", `workflow '${owned.id}' has an unresolved run`, { workflow: owned.id });
|
|
3680
|
+
return new Workflow(cloneWorkflowSnapshot(recoverWorkflowSnapshot(owned)), options);
|
|
2699
3681
|
}
|
|
2700
3682
|
/**
|
|
2701
3683
|
* Assert that a {@link WorkflowSnapshot} carries a `boolean` `bail` — at the workflow tier AND
|
|
@@ -2718,54 +3700,7 @@ function restoreWorkflow(snapshot, options) {
|
|
|
2718
3700
|
* @param snapshot - The snapshot to validate
|
|
2719
3701
|
*/
|
|
2720
3702
|
function assertSnapshot(snapshot) {
|
|
2721
|
-
|
|
2722
|
-
workflow: snapshot.id,
|
|
2723
|
-
bail: snapshot.bail
|
|
2724
|
-
});
|
|
2725
|
-
if (!WORKFLOW_STATUSES.includes(snapshot.status)) throw new WorkflowError("RESTORE", `workflow '${snapshot.id}' has an invalid status`, {
|
|
2726
|
-
workflow: snapshot.id,
|
|
2727
|
-
status: snapshot.status
|
|
2728
|
-
});
|
|
2729
|
-
if (snapshot.override !== void 0 && !WORKFLOW_STATUSES.includes(snapshot.override)) throw new WorkflowError("RESTORE", `workflow '${snapshot.id}' has an invalid override`, {
|
|
2730
|
-
workflow: snapshot.id,
|
|
2731
|
-
override: snapshot.override
|
|
2732
|
-
});
|
|
2733
|
-
for (const phase of snapshot.phases) {
|
|
2734
|
-
if (typeof phase.bail !== "boolean") throw new WorkflowError("RESTORE", `phase '${phase.id}' has a non-boolean bail`, {
|
|
2735
|
-
phase: phase.id,
|
|
2736
|
-
bail: phase.bail
|
|
2737
|
-
});
|
|
2738
|
-
if (!PHASE_STATUSES.includes(phase.status)) throw new WorkflowError("RESTORE", `phase '${phase.id}' has an invalid status`, {
|
|
2739
|
-
phase: phase.id,
|
|
2740
|
-
status: phase.status
|
|
2741
|
-
});
|
|
2742
|
-
if (phase.override !== void 0 && !PHASE_STATUSES.includes(phase.override)) throw new WorkflowError("RESTORE", `phase '${phase.id}' has an invalid override`, {
|
|
2743
|
-
phase: phase.id,
|
|
2744
|
-
override: phase.override
|
|
2745
|
-
});
|
|
2746
|
-
if (phase.concurrency !== void 0 && (!Number.isInteger(phase.concurrency) || phase.concurrency < 1)) throw new WorkflowError("RESTORE", `phase '${phase.id}' has an invalid concurrency`, {
|
|
2747
|
-
phase: phase.id,
|
|
2748
|
-
concurrency: phase.concurrency
|
|
2749
|
-
});
|
|
2750
|
-
for (const task of phase.tasks) {
|
|
2751
|
-
if (!TASK_STATUSES.includes(task.status)) throw new WorkflowError("RESTORE", `task '${task.id}' has an invalid status`, {
|
|
2752
|
-
task: task.id,
|
|
2753
|
-
status: task.status
|
|
2754
|
-
});
|
|
2755
|
-
if (task.run !== void 0 && task.run.length < 1) throw new WorkflowError("RESTORE", `task '${task.id}' has an invalid run`, {
|
|
2756
|
-
task: task.id,
|
|
2757
|
-
run: task.run
|
|
2758
|
-
});
|
|
2759
|
-
if (task.retries !== void 0 && (!Number.isInteger(task.retries) || task.retries < 0)) throw new WorkflowError("RESTORE", `task '${task.id}' has an invalid retries`, {
|
|
2760
|
-
task: task.id,
|
|
2761
|
-
retries: task.retries
|
|
2762
|
-
});
|
|
2763
|
-
if (task.timeout !== void 0 && (!Number.isInteger(task.timeout) || task.timeout < 0)) throw new WorkflowError("RESTORE", `task '${task.id}' has an invalid timeout`, {
|
|
2764
|
-
task: task.id,
|
|
2765
|
-
timeout: task.timeout
|
|
2766
|
-
});
|
|
2767
|
-
}
|
|
2768
|
-
}
|
|
3703
|
+
cloneWorkflowSnapshot(snapshot);
|
|
2769
3704
|
}
|
|
2770
3705
|
/**
|
|
2771
3706
|
* Create the in-memory durable {@link WorkflowStoreInterface} — a process-lifetime
|
|
@@ -2832,12 +3767,13 @@ function createMemoryWorkflowStore() {
|
|
|
2832
3767
|
* ```
|
|
2833
3768
|
*/
|
|
2834
3769
|
function createDatabaseWorkflowStore(driver = createMemoryDriver()) {
|
|
3770
|
+
const columns = {
|
|
3771
|
+
id: stringShape(),
|
|
3772
|
+
snapshot: rawShape({})
|
|
3773
|
+
};
|
|
2835
3774
|
return new DatabaseWorkflowStore(createDatabase({
|
|
2836
3775
|
driver,
|
|
2837
|
-
tables: { snapshots:
|
|
2838
|
-
id: stringShape(),
|
|
2839
|
-
snapshot: rawShape({})
|
|
2840
|
-
} }
|
|
3776
|
+
tables: { snapshots: columns }
|
|
2841
3777
|
}).table("snapshots"));
|
|
2842
3778
|
}
|
|
2843
3779
|
/**
|
|
@@ -2847,7 +3783,7 @@ function createDatabaseWorkflowStore(driver = createMemoryDriver()) {
|
|
|
2847
3783
|
*
|
|
2848
3784
|
* @remarks
|
|
2849
3785
|
* The runner is a PURE engine — it re-implements no concurrency / retry / abort logic, AND it
|
|
2850
|
-
* carries no
|
|
3786
|
+
* carries no behavior or provider registry of its own: each live task already
|
|
2851
3787
|
* resolved its own {@link import('./types.js').WorkflowFunction} into
|
|
2852
3788
|
* {@link import('./types.js').TaskInterface.handler} ONCE at construction, from the
|
|
2853
3789
|
* {@link WorkflowOptions.functions} registry supplied to `execute` / {@link createWorkflow}.
|
|
@@ -2861,11 +3797,10 @@ function createDatabaseWorkflowStore(driver = createMemoryDriver()) {
|
|
|
2861
3797
|
* the live entity (`start` → `complete` / `fail`), and resolves a
|
|
2862
3798
|
* {@link import('./types.js').WorkflowResult}.
|
|
2863
3799
|
*
|
|
2864
|
-
*
|
|
2865
|
-
* {@link import('./types.js').WorkflowFunction} into its
|
|
2866
|
-
* registry
|
|
2867
|
-
*
|
|
2868
|
-
* (the ROADMAP no-handler rule).
|
|
3800
|
+
* External integrations remain application-owned: a caller wires an ordinary
|
|
3801
|
+
* {@link import('./types.js').WorkflowFunction} into its own {@link WorkflowOptions.functions}
|
|
3802
|
+
* registry. Only a task that omits `run` auto-completes; unresolved named work is rejected
|
|
3803
|
+
* before dispatch.
|
|
2869
3804
|
*
|
|
2870
3805
|
* @param options - An optional pacing `scheduler` (default the shipped cross-environment one).
|
|
2871
3806
|
* See {@link WorkflowRunnerOptions}.
|
|
@@ -3015,6 +3950,6 @@ function createRunner(options) {
|
|
|
3015
3950
|
return new Runner(options);
|
|
3016
3951
|
}
|
|
3017
3952
|
//#endregion
|
|
3018
|
-
export { Controller, DEFAULT_BAIL, DEFAULT_PHASE_CONCURRENCY, DatabaseWorkflowStore, MemoryWorkflowStore, PHASE_STATUSES, Phase, PhaseManager, Runner, Scheduler, TASK_STATUSES, TASK_TRANSITIONS, TERMINAL_TASK_STATUSES, Task, TaskController, TaskManager, WORKFLOW_STATUSES, Workflow, WorkflowError, WorkflowManager, WorkflowRunner, assertSnapshot, buildPhaseContext, buildTaskContext, buildWorkflowContext, canTransitionTask, collectResults, createDatabaseWorkflowStore, createDeferred, createMemoryWorkflowStore, createRunner, createScheduler, createWorkflow, createWorkflowContract, createWorkflowManager, createWorkflowRunner, definitionToSnapshot, deriveBoundary, derivePhaseStatus, deriveWorkflowStatus, failure, findFailure, insertEntry, isTerminalStatus, isWorkflowError, isWorkflowSnapshot, moveEntry, parkSignal, phaseDefinitionToSnapshot, phaseShape, phaseUpdateShape, restoreWorkflow, success, taskDefinitionToSnapshot, taskShape, taskUpdateShape, workflowShape };
|
|
3953
|
+
export { Controller, DEFAULT_BAIL, DEFAULT_PHASE_CONCURRENCY, DatabaseWorkflowStore, MAX_TIMER_MS, MemoryWorkflowStore, PHASE_STATUSES, Phase, PhaseManager, Runner, Scheduler, TASK_STATUSES, TASK_TRANSITIONS, TERMINAL_TASK_STATUSES, Task, TaskController, TaskManager, WORKFLOW_STATUSES, Workflow, WorkflowError, WorkflowManager, WorkflowPersistence, WorkflowRunner, assertSnapshot, buildPhaseContext, buildTaskContext, buildWorkflowContext, canTransitionTask, cloneTaskActivity, cloneWorkflowSnapshot, collectResults, createDatabaseWorkflowStore, createDeferred, createMemoryWorkflowStore, createRunner, createScheduler, createWorkflow, createWorkflowContract, createWorkflowManager, createWorkflowRunner, definitionToSnapshot, deriveBoundary, derivePhaseStatus, deriveWorkflowStatus, errorToMessage, failure, findFailure, hasWorkflowHandlers, insertEntry, isLifecycleStatus, isOwnedWorkflowSnapshot, isTaskActivity, isTaskActivityInput, isTaskFailure, isTaskResult, isTerminalStatus, isWorkflowError, isWorkflowSnapshot, matchesDescription, moveEntry, parkSignal, phaseDefinitionToSnapshot, phaseShape, phaseUpdateShape, recoverWorkflow, recoverWorkflowSnapshot, resolveTaskSilence, restoreWorkflow, success, taskDefinitionToSnapshot, taskShape, taskUpdateShape, workflowShape, workflowSnapshotContext };
|
|
3019
3954
|
|
|
3020
3955
|
//# sourceMappingURL=index.js.map
|