@orkestrel/workflow 0.0.11 → 0.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/core/index.cjs +234 -250
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +86 -100
- package/dist/src/core/index.d.ts +86 -100
- package/dist/src/core/index.js +233 -248
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.d.cts +2 -2
- package/dist/src/server/index.d.ts +2 -2
- package/package.json +4 -3
package/dist/src/core/index.js
CHANGED
|
@@ -154,6 +154,159 @@ function isWorkflowError(value) {
|
|
|
154
154
|
}
|
|
155
155
|
}
|
|
156
156
|
//#endregion
|
|
157
|
+
//#region src/core/validators.ts
|
|
158
|
+
/** Test the workflow lifecycle vocabulary. */
|
|
159
|
+
function isLifecycleStatus(value) {
|
|
160
|
+
return value === "pending" || value === "running" || value === "completed" || value === "failed" || value === "skipped" || value === "stopped";
|
|
161
|
+
}
|
|
162
|
+
/** Test a normalized persisted task failure. */
|
|
163
|
+
function isTaskFailure(value) {
|
|
164
|
+
try {
|
|
165
|
+
return isRecord(value) && Object.keys(value).every((key) => key === "origin" || key === "message") && (value.origin === "handler" || value.origin === "timeout" || value.origin === "recovery") && isNonEmptyString(value.message);
|
|
166
|
+
} catch {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Validate a safe owned JSON graph as a coherent workflow snapshot.
|
|
172
|
+
*
|
|
173
|
+
* @remarks
|
|
174
|
+
* Callers at hostile boundaries use {@link isWorkflowSnapshot}, which owns the
|
|
175
|
+
* graph first so this semantic pass never observes accessors or prototypes.
|
|
176
|
+
*/
|
|
177
|
+
function isOwnedWorkflowSnapshot(value) {
|
|
178
|
+
try {
|
|
179
|
+
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;
|
|
180
|
+
const phaseIds = /* @__PURE__ */ new Set();
|
|
181
|
+
const derivations = [];
|
|
182
|
+
let frontier = false;
|
|
183
|
+
let running = false;
|
|
184
|
+
let vacuous = true;
|
|
185
|
+
for (const phase of value.phases) {
|
|
186
|
+
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;
|
|
187
|
+
const forced = phase.override === "skipped" || phase.override === "stopped";
|
|
188
|
+
const started = phase.status === "running" || phase.status === "completed" || phase.status === "failed";
|
|
189
|
+
if (!forced && frontier && started || phase.status === "running" && running) return false;
|
|
190
|
+
if (phase.status === "running") running = true;
|
|
191
|
+
if (!forced && (phase.status === "pending" || phase.status === "running" || phase.status === "failed" && phase.bail)) frontier = true;
|
|
192
|
+
phaseIds.add(phase.id);
|
|
193
|
+
const taskIds = /* @__PURE__ */ new Set();
|
|
194
|
+
const statuses = [];
|
|
195
|
+
if (phase.tasks.length > 0) vacuous = false;
|
|
196
|
+
for (const task of phase.tasks) {
|
|
197
|
+
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;
|
|
198
|
+
const budget = (task.retries ?? 0) + 1;
|
|
199
|
+
if (task.attempts > budget || task.status === "pending" && task.attempts >= budget) return false;
|
|
200
|
+
if (!(task.activity === void 0 || isTaskActivity(task.activity))) return false;
|
|
201
|
+
if (task.status === "running" || task.status === "completed" || task.status === "failed") {
|
|
202
|
+
if (task.attempts < 1 || task.activity === void 0) return false;
|
|
203
|
+
}
|
|
204
|
+
if (task.status === "pending" && task.activity !== void 0) return false;
|
|
205
|
+
if (task.status === "completed" || task.status === "failed") {
|
|
206
|
+
if (!isTaskResult(task.result, value, phase, task)) return false;
|
|
207
|
+
} else if (task.result !== void 0) return false;
|
|
208
|
+
taskIds.add(task.id);
|
|
209
|
+
statuses.push(task.status);
|
|
210
|
+
}
|
|
211
|
+
const derived = derivePhaseStatus(statuses);
|
|
212
|
+
if (phase.status !== (phase.override ?? derived) || phase.override !== void 0 && phase.status !== phase.override) return false;
|
|
213
|
+
derivations.push({
|
|
214
|
+
status: phase.status,
|
|
215
|
+
bail: phase.bail
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
const derived = deriveWorkflowStatus(derivations);
|
|
219
|
+
if (value.override === "completed") return value.status === "completed" && derived === "pending" && vacuous;
|
|
220
|
+
return value.status === (value.override ?? derived);
|
|
221
|
+
} catch {
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
/** Total hostile-boundary workflow snapshot guard. */
|
|
226
|
+
function isWorkflowSnapshot(value) {
|
|
227
|
+
const cloned = attempt(() => cloneJSONValue(value));
|
|
228
|
+
return cloned.success && isOwnedWorkflowSnapshot(cloned.value);
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Test whether an unknown value is a valid whole-frame activity report.
|
|
232
|
+
*/
|
|
233
|
+
function isTaskActivityInput(value) {
|
|
234
|
+
try {
|
|
235
|
+
if (!isRecord(value)) return false;
|
|
236
|
+
const prototype = Object.getPrototypeOf(value);
|
|
237
|
+
if (prototype !== Object.prototype && prototype !== null || !Object.keys(value).every((key) => key === "note" || key === "progress" || key === "operations" || key === "constraints")) return false;
|
|
238
|
+
const note = value.note;
|
|
239
|
+
const progress = value.progress;
|
|
240
|
+
const operations = value.operations;
|
|
241
|
+
const constraints = value.constraints;
|
|
242
|
+
if (note !== void 0 && !isNonEmptyString(note)) return false;
|
|
243
|
+
if (progress !== void 0) {
|
|
244
|
+
if (!isRecord(progress)) return false;
|
|
245
|
+
const progressPrototype = Object.getPrototypeOf(progress);
|
|
246
|
+
if (progressPrototype !== Object.prototype && progressPrototype !== null || !Object.keys(progress).every((key) => key === "current" || key === "total" || key === "unit")) return false;
|
|
247
|
+
const current = progress.current;
|
|
248
|
+
const total = progress.total;
|
|
249
|
+
const unit = progress.unit;
|
|
250
|
+
if (!isFiniteNumber(current) || current < 0 || total !== void 0 && (!isFiniteNumber(total) || total < current) || unit !== void 0 && !isNonEmptyString(unit)) return false;
|
|
251
|
+
}
|
|
252
|
+
if (operations !== void 0) {
|
|
253
|
+
if (!isArray(operations)) return false;
|
|
254
|
+
const ids = /* @__PURE__ */ new Set();
|
|
255
|
+
for (const operation of operations) {
|
|
256
|
+
if (!isRecord(operation)) return false;
|
|
257
|
+
const operationPrototype = Object.getPrototypeOf(operation);
|
|
258
|
+
if (operationPrototype !== Object.prototype && operationPrototype !== null || !Object.keys(operation).every((key) => key === "id" || key === "name" || key === "started")) return false;
|
|
259
|
+
const id = operation.id;
|
|
260
|
+
const name = operation.name;
|
|
261
|
+
const started = operation.started;
|
|
262
|
+
if (!isNonEmptyString(id) || !isNonEmptyString(name) || !isFiniteNumber(started) || started < 0 || ids.has(id)) return false;
|
|
263
|
+
ids.add(id);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
if (constraints !== void 0) {
|
|
267
|
+
if (!isArray(constraints)) return false;
|
|
268
|
+
const ids = /* @__PURE__ */ new Set();
|
|
269
|
+
for (const constraint of constraints) {
|
|
270
|
+
if (!isRecord(constraint)) return false;
|
|
271
|
+
const constraintPrototype = Object.getPrototypeOf(constraint);
|
|
272
|
+
if (constraintPrototype !== Object.prototype && constraintPrototype !== null || !Object.keys(constraint).every((key) => key === "id" || key === "name" || key === "started")) return false;
|
|
273
|
+
const id = constraint.id;
|
|
274
|
+
const name = constraint.name;
|
|
275
|
+
const started = constraint.started;
|
|
276
|
+
if (!isNonEmptyString(id) || !isNonEmptyString(name) || !isFiniteNumber(started) || started < 0 || ids.has(id)) return false;
|
|
277
|
+
ids.add(id);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return true;
|
|
281
|
+
} catch {
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Test whether an unknown value is valid persisted task activity.
|
|
287
|
+
*/
|
|
288
|
+
function isTaskActivity(value) {
|
|
289
|
+
try {
|
|
290
|
+
if (!isRecord(value)) return false;
|
|
291
|
+
const prototype = Object.getPrototypeOf(value);
|
|
292
|
+
if (prototype !== Object.prototype && prototype !== null || !Object.keys(value).every((key) => key === "note" || key === "progress" || key === "operations" || key === "constraints" || key === "updated")) return false;
|
|
293
|
+
const note = value.note;
|
|
294
|
+
const progress = value.progress;
|
|
295
|
+
const operations = value.operations;
|
|
296
|
+
const constraints = value.constraints;
|
|
297
|
+
const updated = value.updated;
|
|
298
|
+
if (operations === void 0 || constraints === void 0 || !isFiniteNumber(updated) || updated < 0) return false;
|
|
299
|
+
return isTaskActivityInput({
|
|
300
|
+
...note === void 0 ? {} : { note },
|
|
301
|
+
...progress === void 0 ? {} : { progress },
|
|
302
|
+
operations,
|
|
303
|
+
constraints
|
|
304
|
+
});
|
|
305
|
+
} catch {
|
|
306
|
+
return false;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
//#endregion
|
|
157
310
|
//#region src/core/helpers.ts
|
|
158
311
|
/**
|
|
159
312
|
* Capture every top-level {@link WorkflowOptions} value exactly once into an owned plain bag.
|
|
@@ -627,6 +780,53 @@ function recoverWorkflowSnapshot(snapshot) {
|
|
|
627
780
|
updated: now
|
|
628
781
|
};
|
|
629
782
|
}
|
|
783
|
+
/** Compare two optional description values. */
|
|
784
|
+
function matchesDescription(left, right) {
|
|
785
|
+
return left === right && (left === void 0 || typeof left === "string");
|
|
786
|
+
}
|
|
787
|
+
/** Test a result's lineage against its containing snapshot nodes. */
|
|
788
|
+
function isTaskResult(value, workflow, phase, task) {
|
|
789
|
+
try {
|
|
790
|
+
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;
|
|
791
|
+
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;
|
|
792
|
+
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;
|
|
793
|
+
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;
|
|
794
|
+
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);
|
|
795
|
+
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);
|
|
796
|
+
return false;
|
|
797
|
+
} catch {
|
|
798
|
+
return false;
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
function hasWorkflowHandlers(workflow, functions) {
|
|
802
|
+
if ("destroyed" in workflow) {
|
|
803
|
+
for (const phase of workflow.phases.phases()) for (const task of phase.tasks.tasks()) if (task.run !== void 0 && !isFunction(task.handler)) return false;
|
|
804
|
+
return true;
|
|
805
|
+
}
|
|
806
|
+
const runs = /* @__PURE__ */ new Set();
|
|
807
|
+
for (const phase of workflow.phases) for (const task of phase.tasks) {
|
|
808
|
+
if (task.run === void 0 || runs.has(task.run)) continue;
|
|
809
|
+
runs.add(task.run);
|
|
810
|
+
if (!isFunction(functions?.[task.run])) return false;
|
|
811
|
+
}
|
|
812
|
+
return true;
|
|
813
|
+
}
|
|
814
|
+
/** Locate the nearest identifiable node for an inconsistent owned snapshot. */
|
|
815
|
+
function workflowSnapshotContext(value) {
|
|
816
|
+
if (!isRecord(value) || !isArray(value.phases)) return void 0;
|
|
817
|
+
for (const phase of value.phases) {
|
|
818
|
+
if (!isRecord(phase)) continue;
|
|
819
|
+
const phaseContext = isNonEmptyString(phase.id) ? { phase: phase.id } : void 0;
|
|
820
|
+
if (!isBoolean(phase.bail) || phase.concurrency !== void 0 && (!isInteger(phase.concurrency) || phase.concurrency < 1) || !isArray(phase.tasks)) return phaseContext;
|
|
821
|
+
for (const task of phase.tasks) {
|
|
822
|
+
if (!isRecord(task)) continue;
|
|
823
|
+
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 {
|
|
824
|
+
...phaseContext ?? {},
|
|
825
|
+
...isNonEmptyString(task.id) ? { task: task.id } : {}
|
|
826
|
+
};
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
}
|
|
630
830
|
/**
|
|
631
831
|
* Flatten a nested list of per-phase {@link TaskResult} lists into one positional list
|
|
632
832
|
* — the workflow tier of the result tree, built from each phase's `results()`.
|
|
@@ -871,206 +1071,6 @@ var Scheduler = class {
|
|
|
871
1071
|
}
|
|
872
1072
|
};
|
|
873
1073
|
//#endregion
|
|
874
|
-
//#region src/core/validators.ts
|
|
875
|
-
/** Test the workflow lifecycle vocabulary. */
|
|
876
|
-
function isLifecycleStatus(value) {
|
|
877
|
-
return value === "pending" || value === "running" || value === "completed" || value === "failed" || value === "skipped" || value === "stopped";
|
|
878
|
-
}
|
|
879
|
-
/** Test a normalized persisted task failure. */
|
|
880
|
-
function isTaskFailure(value) {
|
|
881
|
-
try {
|
|
882
|
-
return isRecord(value) && Object.keys(value).every((key) => key === "origin" || key === "message") && (value.origin === "handler" || value.origin === "timeout" || value.origin === "recovery") && isNonEmptyString(value.message);
|
|
883
|
-
} catch {
|
|
884
|
-
return false;
|
|
885
|
-
}
|
|
886
|
-
}
|
|
887
|
-
/** Compare two optional description values. */
|
|
888
|
-
function matchesDescription(left, right) {
|
|
889
|
-
return left === right && (left === void 0 || typeof left === "string");
|
|
890
|
-
}
|
|
891
|
-
/** Test a result's lineage against its containing snapshot nodes. */
|
|
892
|
-
function isTaskResult(value, workflow, phase, task) {
|
|
893
|
-
try {
|
|
894
|
-
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;
|
|
895
|
-
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;
|
|
896
|
-
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;
|
|
897
|
-
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;
|
|
898
|
-
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);
|
|
899
|
-
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);
|
|
900
|
-
return false;
|
|
901
|
-
} catch {
|
|
902
|
-
return false;
|
|
903
|
-
}
|
|
904
|
-
}
|
|
905
|
-
/**
|
|
906
|
-
* Validate a safe owned JSON graph as a coherent workflow snapshot.
|
|
907
|
-
*
|
|
908
|
-
* @remarks
|
|
909
|
-
* Callers at hostile boundaries use {@link isWorkflowSnapshot}, which owns the
|
|
910
|
-
* graph first so this semantic pass never observes accessors or prototypes.
|
|
911
|
-
*/
|
|
912
|
-
function isOwnedWorkflowSnapshot(value) {
|
|
913
|
-
try {
|
|
914
|
-
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;
|
|
915
|
-
const phaseIds = /* @__PURE__ */ new Set();
|
|
916
|
-
const derivations = [];
|
|
917
|
-
let frontier = false;
|
|
918
|
-
let running = false;
|
|
919
|
-
let vacuous = true;
|
|
920
|
-
for (const phase of value.phases) {
|
|
921
|
-
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;
|
|
922
|
-
const forced = phase.override === "skipped" || phase.override === "stopped";
|
|
923
|
-
const started = phase.status === "running" || phase.status === "completed" || phase.status === "failed";
|
|
924
|
-
if (!forced && frontier && started || phase.status === "running" && running) return false;
|
|
925
|
-
if (phase.status === "running") running = true;
|
|
926
|
-
if (!forced && (phase.status === "pending" || phase.status === "running" || phase.status === "failed" && phase.bail)) frontier = true;
|
|
927
|
-
phaseIds.add(phase.id);
|
|
928
|
-
const taskIds = /* @__PURE__ */ new Set();
|
|
929
|
-
const statuses = [];
|
|
930
|
-
if (phase.tasks.length > 0) vacuous = false;
|
|
931
|
-
for (const task of phase.tasks) {
|
|
932
|
-
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;
|
|
933
|
-
const budget = (task.retries ?? 0) + 1;
|
|
934
|
-
if (task.attempts > budget || task.status === "pending" && task.attempts >= budget) return false;
|
|
935
|
-
if (!(task.activity === void 0 || isTaskActivity(task.activity))) return false;
|
|
936
|
-
if (task.status === "running" || task.status === "completed" || task.status === "failed") {
|
|
937
|
-
if (task.attempts < 1 || task.activity === void 0) return false;
|
|
938
|
-
}
|
|
939
|
-
if (task.status === "pending" && task.activity !== void 0) return false;
|
|
940
|
-
if (task.status === "completed" || task.status === "failed") {
|
|
941
|
-
if (!isTaskResult(task.result, value, phase, task)) return false;
|
|
942
|
-
} else if (task.result !== void 0) return false;
|
|
943
|
-
taskIds.add(task.id);
|
|
944
|
-
statuses.push(task.status);
|
|
945
|
-
}
|
|
946
|
-
const derived = derivePhaseStatus(statuses);
|
|
947
|
-
if (phase.status !== (phase.override ?? derived) || phase.override !== void 0 && phase.status !== phase.override) return false;
|
|
948
|
-
derivations.push({
|
|
949
|
-
status: phase.status,
|
|
950
|
-
bail: phase.bail
|
|
951
|
-
});
|
|
952
|
-
}
|
|
953
|
-
const derived = deriveWorkflowStatus(derivations);
|
|
954
|
-
if (value.override === "completed") return value.status === "completed" && derived === "pending" && vacuous;
|
|
955
|
-
return value.status === (value.override ?? derived);
|
|
956
|
-
} catch {
|
|
957
|
-
return false;
|
|
958
|
-
}
|
|
959
|
-
}
|
|
960
|
-
/** Total hostile-boundary workflow snapshot guard. */
|
|
961
|
-
function isWorkflowSnapshot(value) {
|
|
962
|
-
const cloned = attempt(() => cloneJSONValue(value));
|
|
963
|
-
return cloned.success && isOwnedWorkflowSnapshot(cloned.value);
|
|
964
|
-
}
|
|
965
|
-
function hasWorkflowHandlers(workflow, functions) {
|
|
966
|
-
if ("destroyed" in workflow) {
|
|
967
|
-
for (const phase of workflow.phases.phases()) for (const task of phase.tasks.tasks()) if (task.run !== void 0 && !isFunction(task.handler)) return false;
|
|
968
|
-
return true;
|
|
969
|
-
}
|
|
970
|
-
const runs = /* @__PURE__ */ new Set();
|
|
971
|
-
for (const phase of workflow.phases) for (const task of phase.tasks) {
|
|
972
|
-
if (task.run === void 0 || runs.has(task.run)) continue;
|
|
973
|
-
runs.add(task.run);
|
|
974
|
-
if (!isFunction(functions?.[task.run])) return false;
|
|
975
|
-
}
|
|
976
|
-
return true;
|
|
977
|
-
}
|
|
978
|
-
/** Locate the nearest identifiable node for an inconsistent owned snapshot. */
|
|
979
|
-
function workflowSnapshotContext(value) {
|
|
980
|
-
if (!isRecord(value) || !isArray(value.phases)) return void 0;
|
|
981
|
-
for (const phase of value.phases) {
|
|
982
|
-
if (!isRecord(phase)) continue;
|
|
983
|
-
const phaseContext = isNonEmptyString(phase.id) ? { phase: phase.id } : void 0;
|
|
984
|
-
if (!isBoolean(phase.bail) || phase.concurrency !== void 0 && (!isInteger(phase.concurrency) || phase.concurrency < 1) || !isArray(phase.tasks)) return phaseContext;
|
|
985
|
-
for (const task of phase.tasks) {
|
|
986
|
-
if (!isRecord(task)) continue;
|
|
987
|
-
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 {
|
|
988
|
-
...phaseContext ?? {},
|
|
989
|
-
...isNonEmptyString(task.id) ? { task: task.id } : {}
|
|
990
|
-
};
|
|
991
|
-
}
|
|
992
|
-
}
|
|
993
|
-
}
|
|
994
|
-
/**
|
|
995
|
-
* Test whether an unknown value is a valid whole-frame activity report.
|
|
996
|
-
*/
|
|
997
|
-
function isTaskActivityInput(value) {
|
|
998
|
-
try {
|
|
999
|
-
if (!isRecord(value)) return false;
|
|
1000
|
-
const prototype = Object.getPrototypeOf(value);
|
|
1001
|
-
if (prototype !== Object.prototype && prototype !== null || !Object.keys(value).every((key) => key === "note" || key === "progress" || key === "operations" || key === "constraints")) return false;
|
|
1002
|
-
const note = value.note;
|
|
1003
|
-
const progress = value.progress;
|
|
1004
|
-
const operations = value.operations;
|
|
1005
|
-
const constraints = value.constraints;
|
|
1006
|
-
if (note !== void 0 && !isNonEmptyString(note)) return false;
|
|
1007
|
-
if (progress !== void 0) {
|
|
1008
|
-
if (!isRecord(progress)) return false;
|
|
1009
|
-
const progressPrototype = Object.getPrototypeOf(progress);
|
|
1010
|
-
if (progressPrototype !== Object.prototype && progressPrototype !== null || !Object.keys(progress).every((key) => key === "current" || key === "total" || key === "unit")) return false;
|
|
1011
|
-
const current = progress.current;
|
|
1012
|
-
const total = progress.total;
|
|
1013
|
-
const unit = progress.unit;
|
|
1014
|
-
if (!isFiniteNumber(current) || current < 0 || total !== void 0 && (!isFiniteNumber(total) || total < current) || unit !== void 0 && !isNonEmptyString(unit)) return false;
|
|
1015
|
-
}
|
|
1016
|
-
if (operations !== void 0) {
|
|
1017
|
-
if (!isArray(operations)) return false;
|
|
1018
|
-
const ids = /* @__PURE__ */ new Set();
|
|
1019
|
-
for (const operation of operations) {
|
|
1020
|
-
if (!isRecord(operation)) return false;
|
|
1021
|
-
const operationPrototype = Object.getPrototypeOf(operation);
|
|
1022
|
-
if (operationPrototype !== Object.prototype && operationPrototype !== null || !Object.keys(operation).every((key) => key === "id" || key === "name" || key === "started")) return false;
|
|
1023
|
-
const id = operation.id;
|
|
1024
|
-
const name = operation.name;
|
|
1025
|
-
const started = operation.started;
|
|
1026
|
-
if (!isNonEmptyString(id) || !isNonEmptyString(name) || !isFiniteNumber(started) || started < 0 || ids.has(id)) return false;
|
|
1027
|
-
ids.add(id);
|
|
1028
|
-
}
|
|
1029
|
-
}
|
|
1030
|
-
if (constraints !== void 0) {
|
|
1031
|
-
if (!isArray(constraints)) return false;
|
|
1032
|
-
const ids = /* @__PURE__ */ new Set();
|
|
1033
|
-
for (const constraint of constraints) {
|
|
1034
|
-
if (!isRecord(constraint)) return false;
|
|
1035
|
-
const constraintPrototype = Object.getPrototypeOf(constraint);
|
|
1036
|
-
if (constraintPrototype !== Object.prototype && constraintPrototype !== null || !Object.keys(constraint).every((key) => key === "id" || key === "name" || key === "started")) return false;
|
|
1037
|
-
const id = constraint.id;
|
|
1038
|
-
const name = constraint.name;
|
|
1039
|
-
const started = constraint.started;
|
|
1040
|
-
if (!isNonEmptyString(id) || !isNonEmptyString(name) || !isFiniteNumber(started) || started < 0 || ids.has(id)) return false;
|
|
1041
|
-
ids.add(id);
|
|
1042
|
-
}
|
|
1043
|
-
}
|
|
1044
|
-
return true;
|
|
1045
|
-
} catch {
|
|
1046
|
-
return false;
|
|
1047
|
-
}
|
|
1048
|
-
}
|
|
1049
|
-
/**
|
|
1050
|
-
* Test whether an unknown value is valid persisted task activity.
|
|
1051
|
-
*/
|
|
1052
|
-
function isTaskActivity(value) {
|
|
1053
|
-
try {
|
|
1054
|
-
if (!isRecord(value)) return false;
|
|
1055
|
-
const prototype = Object.getPrototypeOf(value);
|
|
1056
|
-
if (prototype !== Object.prototype && prototype !== null || !Object.keys(value).every((key) => key === "note" || key === "progress" || key === "operations" || key === "constraints" || key === "updated")) return false;
|
|
1057
|
-
const note = value.note;
|
|
1058
|
-
const progress = value.progress;
|
|
1059
|
-
const operations = value.operations;
|
|
1060
|
-
const constraints = value.constraints;
|
|
1061
|
-
const updated = value.updated;
|
|
1062
|
-
if (operations === void 0 || constraints === void 0 || !isFiniteNumber(updated) || updated < 0) return false;
|
|
1063
|
-
return isTaskActivityInput({
|
|
1064
|
-
...note === void 0 ? {} : { note },
|
|
1065
|
-
...progress === void 0 ? {} : { progress },
|
|
1066
|
-
operations,
|
|
1067
|
-
constraints
|
|
1068
|
-
});
|
|
1069
|
-
} catch {
|
|
1070
|
-
return false;
|
|
1071
|
-
}
|
|
1072
|
-
}
|
|
1073
|
-
//#endregion
|
|
1074
1074
|
//#region src/core/cloners.ts
|
|
1075
1075
|
/**
|
|
1076
1076
|
* Validate and own a workflow snapshot before live construction.
|
|
@@ -1327,18 +1327,18 @@ var phaseUpdateShape = objectShape({
|
|
|
1327
1327
|
* idle-TTL / eviction — a persisted run-state is durable orchestration state that lives until an
|
|
1328
1328
|
* explicit `delete`. The public surface is EXACTLY `get` / `set` / `delete` — no extra members (the
|
|
1329
1329
|
* §22 method bijection with {@link WorkflowStoreInterface}). Restore stays a caller concern: read a
|
|
1330
|
-
* snapshot back and rebuild the live tree with {@link import('../factories.js').
|
|
1330
|
+
* snapshot back and rebuild the live tree with {@link import('../factories.js').createRestoredWorkflow}.
|
|
1331
1331
|
*
|
|
1332
1332
|
* @example
|
|
1333
1333
|
* ```ts
|
|
1334
1334
|
* import { createMemoryDriver } from '@orkestrel/database'
|
|
1335
|
-
* import { createDatabaseWorkflowStore, createWorkflow,
|
|
1335
|
+
* import { createDatabaseWorkflowStore, createWorkflow, createRestoredWorkflow } from '@orkestrel/workflow'
|
|
1336
1336
|
*
|
|
1337
1337
|
* const store = createDatabaseWorkflowStore(createMemoryDriver()) // a durable driver swaps in here
|
|
1338
1338
|
* const workflow = createWorkflow(definition)
|
|
1339
1339
|
* await store.set(workflow.snapshot()) // persist the run state (one JSON column)
|
|
1340
1340
|
* const snapshot = await store.get(definition.id)
|
|
1341
|
-
* const restored = snapshot &&
|
|
1341
|
+
* const restored = snapshot && createRestoredWorkflow(snapshot) // an identical live tree
|
|
1342
1342
|
* await store.delete(definition.id) // drop it
|
|
1343
1343
|
* ```
|
|
1344
1344
|
*/
|
|
@@ -1398,17 +1398,17 @@ var DatabaseWorkflowStore = class {
|
|
|
1398
1398
|
*
|
|
1399
1399
|
* The public surface is EXACTLY `get` / `set` / `delete` — no extra members (the §22 method
|
|
1400
1400
|
* bijection with {@link WorkflowStoreInterface}). Restore is a caller concern: read a snapshot
|
|
1401
|
-
* back and rebuild the live tree with {@link import('../factories.js').
|
|
1401
|
+
* back and rebuild the live tree with {@link import('../factories.js').createRestoredWorkflow}.
|
|
1402
1402
|
*
|
|
1403
1403
|
* @example
|
|
1404
1404
|
* ```ts
|
|
1405
|
-
* import { createMemoryWorkflowStore, createWorkflow,
|
|
1405
|
+
* import { createMemoryWorkflowStore, createWorkflow, createRestoredWorkflow } from '@orkestrel/workflow'
|
|
1406
1406
|
*
|
|
1407
1407
|
* const store = createMemoryWorkflowStore()
|
|
1408
1408
|
* const workflow = createWorkflow(definition)
|
|
1409
1409
|
* await store.set(workflow.snapshot()) // persist the run state
|
|
1410
1410
|
* const snapshot = await store.get(definition.id)
|
|
1411
|
-
* const restored = snapshot &&
|
|
1411
|
+
* const restored = snapshot && createRestoredWorkflow(snapshot) // an identical live tree
|
|
1412
1412
|
* await store.delete(definition.id) // drop it
|
|
1413
1413
|
* ```
|
|
1414
1414
|
*/
|
|
@@ -2250,7 +2250,7 @@ var PhaseManager = class {
|
|
|
2250
2250
|
* @remarks
|
|
2251
2251
|
* - **Construction.** Built from a {@link WorkflowSnapshot} (the unified input —
|
|
2252
2252
|
* {@link import('./factories.js').createWorkflow} seeds an initial snapshot from a
|
|
2253
|
-
* {@link import('./types.js').WorkflowDefinition}, {@link import('./factories.js').
|
|
2253
|
+
* {@link import('./types.js').WorkflowDefinition}, {@link import('./factories.js').createRestoredWorkflow}
|
|
2254
2254
|
* passes a persisted one). Each child {@link Phase} is wired to escalate to `#recompute`.
|
|
2255
2255
|
* - **Derived status.** `status` is `#override` when forced, else
|
|
2256
2256
|
* {@link deriveWorkflowStatus} over the live phases' statuses feeding `bail`. `failed` is
|
|
@@ -2265,7 +2265,7 @@ var PhaseManager = class {
|
|
|
2265
2265
|
* workflow tier; `phase(id)` + each `phase.task(id)` navigate DOWN, a task's `phase` / `workflow`
|
|
2266
2266
|
* navigate UP.
|
|
2267
2267
|
* - **Snapshot.** `snapshot()` serializes the whole live tree to a {@link WorkflowSnapshot} (pure
|
|
2268
|
-
* JSON); {@link import('./factories.js').
|
|
2268
|
+
* JSON); {@link import('./factories.js').createRestoredWorkflow} rebuilds an equivalent live tree.
|
|
2269
2269
|
* - **Observable (AGENTS §13).** The owned {@link emitter} ({@link WorkflowEventMap}) fires
|
|
2270
2270
|
* `start` / `complete` / `fail` / `pause` / `resume` / `skip` / `stop` after the
|
|
2271
2271
|
* corresponding status or runtime-gate change; the emitter isolates a listener throw and
|
|
@@ -2669,7 +2669,7 @@ var WorkflowManager = class {
|
|
|
2669
2669
|
if (!this.#owns(id, mutation, generation)) return this.#resolve(id, generation);
|
|
2670
2670
|
let workflow;
|
|
2671
2671
|
try {
|
|
2672
|
-
workflow =
|
|
2672
|
+
workflow = createRestoredWorkflow(owned, { ...this.#functions === void 0 ? {} : { functions: this.#functions } });
|
|
2673
2673
|
} catch (error) {
|
|
2674
2674
|
if (!this.#owns(id, mutation, generation)) return this.#resolve(id, generation);
|
|
2675
2675
|
throw error;
|
|
@@ -3978,12 +3978,12 @@ function createWorkflow(definition, options) {
|
|
|
3978
3978
|
return new Workflow(definitionToSnapshot(definition, captured.bail ?? definition.bail ?? false), captured);
|
|
3979
3979
|
}
|
|
3980
3980
|
/**
|
|
3981
|
-
*
|
|
3981
|
+
* Build an equivalent live W-b entity tree from a {@link WorkflowSnapshot} — the
|
|
3982
3982
|
* inverse of {@link WorkflowInterface.snapshot}, restoring structure + each node's status
|
|
3983
3983
|
* + recorded results + positional order + the persisted `#override`.
|
|
3984
3984
|
*
|
|
3985
3985
|
* @remarks
|
|
3986
|
-
* Round-trip fidelity is paramount: a `snapshot()` → `
|
|
3986
|
+
* Round-trip fidelity is paramount: a `snapshot()` → `createRestoredWorkflow()` reproduces the
|
|
3987
3987
|
* same status at every node (each `#override` restored DIRECTLY from the snapshot's own
|
|
3988
3988
|
* `override` field, not guessed from a status divergence), the same recorded
|
|
3989
3989
|
* {@link import('./types.js').TaskResult}s, and the same positional order (an interior
|
|
@@ -4003,18 +4003,18 @@ function createWorkflow(definition, options) {
|
|
|
4003
4003
|
*
|
|
4004
4004
|
* @example
|
|
4005
4005
|
* ```ts
|
|
4006
|
-
* import {
|
|
4006
|
+
* import { createRestoredWorkflow } from '@orkestrel/workflow'
|
|
4007
4007
|
*
|
|
4008
|
-
* const restored =
|
|
4008
|
+
* const restored = createRestoredWorkflow(workflow.snapshot()) // bail comes from the snapshot
|
|
4009
4009
|
* restored.status === workflow.status // true
|
|
4010
4010
|
* ```
|
|
4011
4011
|
*/
|
|
4012
|
-
function
|
|
4012
|
+
function createRestoredWorkflow(snapshot, options) {
|
|
4013
4013
|
const captured = captureWorkflowOptions(options);
|
|
4014
4014
|
return new Workflow(cloneWorkflowSnapshot(snapshot), captured);
|
|
4015
4015
|
}
|
|
4016
4016
|
/**
|
|
4017
|
-
*
|
|
4017
|
+
* Build an interrupted workflow back to life at its remaining retry budget.
|
|
4018
4018
|
*
|
|
4019
4019
|
* @remarks
|
|
4020
4020
|
* Each phase captures every unique initial `run` binding once before constructing tasks. Recovery
|
|
@@ -4023,9 +4023,17 @@ function restoreWorkflow(snapshot, options) {
|
|
|
4023
4023
|
*
|
|
4024
4024
|
* @param snapshot - The hostile persisted snapshot
|
|
4025
4025
|
* @param options - Runtime handlers and entity options
|
|
4026
|
-
* @returns A recoverable live
|
|
4026
|
+
* @returns A recoverable live {@link WorkflowInterface} root
|
|
4027
|
+
*
|
|
4028
|
+
* @example
|
|
4029
|
+
* ```ts
|
|
4030
|
+
* import { createRecoveredWorkflow } from '@orkestrel/workflow'
|
|
4031
|
+
*
|
|
4032
|
+
* const recovered = createRecoveredWorkflow(snapshot, { functions })
|
|
4033
|
+
* recovered.status // 'pending' — interrupted running work returned to its remaining budget
|
|
4034
|
+
* ```
|
|
4027
4035
|
*/
|
|
4028
|
-
function
|
|
4036
|
+
function createRecoveredWorkflow(snapshot, options) {
|
|
4029
4037
|
const captured = captureWorkflowOptions(options);
|
|
4030
4038
|
const owned = cloneWorkflowSnapshot(snapshot);
|
|
4031
4039
|
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 });
|
|
@@ -4034,29 +4042,6 @@ function recoverWorkflow(snapshot, options) {
|
|
|
4034
4042
|
return workflow;
|
|
4035
4043
|
}
|
|
4036
4044
|
/**
|
|
4037
|
-
* Assert that a {@link WorkflowSnapshot} carries a `boolean` `bail` — at the workflow tier AND
|
|
4038
|
-
* on every phase — and that its every node's status (and its `override`, when present) is drawn
|
|
4039
|
-
* from the lifecycle vocabulary, throwing a `RESTORE` {@link WorkflowError} otherwise.
|
|
4040
|
-
*
|
|
4041
|
-
* @remarks
|
|
4042
|
-
* The boundary-narrowing guard (AGENTS §14) for {@link restoreWorkflow}: a snapshot is
|
|
4043
|
-
* untrusted JSON, so a status (or an override) outside
|
|
4044
|
-
* {@link import('./constants.js').WORKFLOW_STATUSES} /
|
|
4045
|
-
* {@link import('./constants.js').PHASE_STATUSES} / {@link import('./constants.js').TASK_STATUSES},
|
|
4046
|
-
* a non-boolean `bail` (the workflow's OR any phase's — both are REQUIRED persisted policy), a
|
|
4047
|
-
* present-but-invalid phase `concurrency` (not a positive integer), or a present-but-invalid task
|
|
4048
|
-
* `run` (an empty string) / `retries` / `timeout` (not a non-negative integer),
|
|
4049
|
-
* is rejected loudly (naming the offending node) rather than silently producing a broken tree.
|
|
4050
|
-
* The `override` / `concurrency` / `run` / `retries` / `timeout` are optional, so each is only
|
|
4051
|
-
* checked WHEN present. Structural shape beyond these fields is the contract's concern; this
|
|
4052
|
-
* guards exactly the fields the live state machine reads back.
|
|
4053
|
-
*
|
|
4054
|
-
* @param snapshot - The snapshot to validate
|
|
4055
|
-
*/
|
|
4056
|
-
function assertSnapshot(snapshot) {
|
|
4057
|
-
cloneWorkflowSnapshot(snapshot);
|
|
4058
|
-
}
|
|
4059
|
-
/**
|
|
4060
4045
|
* Create the in-memory durable {@link WorkflowStoreInterface} — a process-lifetime
|
|
4061
4046
|
* {@link MemoryWorkflowStore} persisting {@link WorkflowSnapshot}s by workflow id, the DEFAULT
|
|
4062
4047
|
* backend behind the W-d persistence seam.
|
|
@@ -4069,19 +4054,19 @@ function assertSnapshot(snapshot) {
|
|
|
4069
4054
|
* {@link createDatabaseWorkflowStore} (the snapshot as one opaque JSON column over a `databases`
|
|
4070
4055
|
* table) — for a DURABLE store (run-state surviving a restart) pass it a JSON / SQLite / IndexedDB
|
|
4071
4056
|
* driver, and it swaps in WITHOUT touching the runner or the entity tree. Restore stays a caller
|
|
4072
|
-
* concern: read a snapshot back and rebuild the live tree with {@link
|
|
4057
|
+
* concern: read a snapshot back and rebuild the live tree with {@link createRestoredWorkflow}.
|
|
4073
4058
|
*
|
|
4074
4059
|
* @returns A memory-backed {@link WorkflowStoreInterface}
|
|
4075
4060
|
*
|
|
4076
4061
|
* @example
|
|
4077
4062
|
* ```ts
|
|
4078
|
-
* import { createMemoryWorkflowStore, createWorkflow,
|
|
4063
|
+
* import { createMemoryWorkflowStore, createWorkflow, createRestoredWorkflow } from '@orkestrel/workflow'
|
|
4079
4064
|
*
|
|
4080
4065
|
* const store = createMemoryWorkflowStore()
|
|
4081
4066
|
* const workflow = createWorkflow(definition)
|
|
4082
4067
|
* await store.set(workflow.snapshot()) // persist the run state
|
|
4083
4068
|
* const snapshot = await store.get(definition.id)
|
|
4084
|
-
* const restored = snapshot &&
|
|
4069
|
+
* const restored = snapshot && createRestoredWorkflow(snapshot) // an identical live tree
|
|
4085
4070
|
* ```
|
|
4086
4071
|
*/
|
|
4087
4072
|
function createMemoryWorkflowStore() {
|
|
@@ -4112,13 +4097,13 @@ function createMemoryWorkflowStore() {
|
|
|
4112
4097
|
* @example
|
|
4113
4098
|
* ```ts
|
|
4114
4099
|
* import { createMemoryDriver } from '@orkestrel/database'
|
|
4115
|
-
* import { createDatabaseWorkflowStore, createWorkflow,
|
|
4100
|
+
* import { createDatabaseWorkflowStore, createWorkflow, createRestoredWorkflow } from '@orkestrel/workflow'
|
|
4116
4101
|
*
|
|
4117
4102
|
* const store = createDatabaseWorkflowStore(createMemoryDriver()) // a durable driver swaps in here
|
|
4118
4103
|
* const workflow = createWorkflow(definition)
|
|
4119
4104
|
* await store.set(workflow.snapshot()) // persist the run state (one JSON column)
|
|
4120
4105
|
* const snapshot = await store.get(definition.id)
|
|
4121
|
-
* const restored = snapshot &&
|
|
4106
|
+
* const restored = snapshot && createRestoredWorkflow(snapshot) // an identical live tree
|
|
4122
4107
|
* ```
|
|
4123
4108
|
*/
|
|
4124
4109
|
function createDatabaseWorkflowStore(driver = createMemoryDriver()) {
|
|
@@ -4187,11 +4172,11 @@ function createWorkflowRunner(options) {
|
|
|
4187
4172
|
* @remarks
|
|
4188
4173
|
* `options.functions` flows into every workflow the manager mints (`add`, via
|
|
4189
4174
|
* {@link createWorkflow}) or hydrates (`open`'s registry-miss path, via
|
|
4190
|
-
* {@link
|
|
4175
|
+
* {@link createRestoredWorkflow}), so a hydrated workflow is RUNNABLE rather than a dead snapshot
|
|
4191
4176
|
* mirror. `options.store` is the EXACT analogue of the twins' `store` seam — omitted ⇒ the
|
|
4192
4177
|
* manager is registry-only (`open` resolves only what is registered, `save` is a no-op). This
|
|
4193
4178
|
* is PURELY ADDITIVE: direct {@link WorkflowStoreInterface} use and
|
|
4194
|
-
* {@link
|
|
4179
|
+
* {@link createRestoredWorkflow} remain valid — the manager is one more caller-driven persistence
|
|
4195
4180
|
* seam, not a replacement.
|
|
4196
4181
|
*
|
|
4197
4182
|
* @param options - The optional `store` seam and the `functions` registry threaded into every mint/hydrate
|
|
@@ -4307,6 +4292,6 @@ function createRunner(options) {
|
|
|
4307
4292
|
return new Runner(options);
|
|
4308
4293
|
}
|
|
4309
4294
|
//#endregion
|
|
4310
|
-
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,
|
|
4295
|
+
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, buildPhaseContext, buildTaskContext, buildWorkflowContext, canTransitionTask, captureWorkflowOptions, cloneTaskActivity, cloneWorkflowSnapshot, collectResults, createDatabaseWorkflowStore, createDeferred, createMemoryWorkflowStore, createRecoveredWorkflow, createRestoredWorkflow, 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, recoverWorkflowSnapshot, resolveTaskSilence, scheduleHost, success, taskDefinitionToSnapshot, taskShape, taskUpdateShape, workflowShape, workflowSnapshotContext };
|
|
4311
4296
|
|
|
4312
4297
|
//# sourceMappingURL=index.js.map
|