@sanity/workflow-engine 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_chunks-cjs/schema.cjs +23 -1
- package/dist/_chunks-cjs/schema.cjs.map +1 -1
- package/dist/_chunks-es/schema.js +23 -1
- package/dist/_chunks-es/schema.js.map +1 -1
- package/dist/define.cjs +18 -2
- package/dist/define.cjs.map +1 -1
- package/dist/define.d.cts +146 -0
- package/dist/define.d.ts +146 -0
- package/dist/define.js +18 -2
- package/dist/define.js.map +1 -1
- package/dist/index.cjs +251 -174
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +247 -5
- package/dist/index.d.ts +247 -5
- package/dist/index.js +252 -174
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -17,13 +17,8 @@ function _interopNamespaceCompat(e) {
|
|
|
17
17
|
}), n.default = e, Object.freeze(n);
|
|
18
18
|
}
|
|
19
19
|
var v__namespace = /* @__PURE__ */ _interopNamespaceCompat(v);
|
|
20
|
-
function
|
|
21
|
-
|
|
22
|
-
for (const part of path.split(".")) {
|
|
23
|
-
if (current == null || typeof current != "object") return;
|
|
24
|
-
current = current[part];
|
|
25
|
-
}
|
|
26
|
-
return current;
|
|
20
|
+
function findOpenStageEntry(host) {
|
|
21
|
+
return host.stages.find((s) => s.name === host.currentStage && s.exitedAt === void 0);
|
|
27
22
|
}
|
|
28
23
|
const KNOWN_SCHEMES = /* @__PURE__ */ new Set(["dataset", "canvas", "media-library", "dashboard"]);
|
|
29
24
|
function parseGdr(uri) {
|
|
@@ -127,12 +122,100 @@ function gdrRef(res, documentId, type) {
|
|
|
127
122
|
function isGdr(value) {
|
|
128
123
|
return typeof value == "object" && value !== null && typeof value.id == "string" && typeof value.type == "string";
|
|
129
124
|
}
|
|
125
|
+
function buildParams(args) {
|
|
126
|
+
const { instance, now, snapshot, extra } = args, currentTasks2 = findOpenStageEntry(instance)?.tasks ?? [];
|
|
127
|
+
return {
|
|
128
|
+
self: selfGdr(instance),
|
|
129
|
+
state: renderedState(instance.state ?? [], snapshot),
|
|
130
|
+
parent: instance.ancestors.at(-1)?.id ?? null,
|
|
131
|
+
ancestors: instance.ancestors.map((a) => a.id),
|
|
132
|
+
stage: instance.currentStage,
|
|
133
|
+
now,
|
|
134
|
+
/** `$effects` — parent context handoff by entry name, completed-effect
|
|
135
|
+
* outputs namespaced under the effect's name (`$effects['x.y'].out`). */
|
|
136
|
+
effects: effectsContextMap(instance),
|
|
137
|
+
/** `$tasks` — the current stage's task rows, statuses included. */
|
|
138
|
+
tasks: currentTasks2,
|
|
139
|
+
allTasksDone: currentTasks2.every((t) => t.status === "done" || t.status === "skipped"),
|
|
140
|
+
anyTaskFailed: currentTasks2.some((t) => t.status === "failed"),
|
|
141
|
+
...extra
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
function renderedState(entries, snapshot) {
|
|
145
|
+
const out = {};
|
|
146
|
+
for (const entry of entries) out[entry.name] = renderedValue(entry, snapshot);
|
|
147
|
+
return out;
|
|
148
|
+
}
|
|
149
|
+
function renderedValue(entry, snapshot) {
|
|
150
|
+
return entry._type !== "doc.ref" || entry.value === null || snapshot === void 0 ? entry.value : snapshot.docs.find((d) => d._id === entry.value.id) ?? entry.value;
|
|
151
|
+
}
|
|
152
|
+
function scopedStateOverlay(instance, snapshot, taskName) {
|
|
153
|
+
const stageEntry = findOpenStageEntry(instance);
|
|
154
|
+
if (stageEntry === void 0) return {};
|
|
155
|
+
const stageState = renderedState(stageEntry.state ?? [], snapshot), task = taskName ? stageEntry.tasks.find((t) => t.name === taskName) : void 0;
|
|
156
|
+
return { ...stageState, ...renderedState(task?.state ?? [], snapshot) };
|
|
157
|
+
}
|
|
158
|
+
function assignedFor(instance, taskName, actor) {
|
|
159
|
+
if (actor === void 0) return !1;
|
|
160
|
+
const entry = findOpenStageEntry(instance)?.tasks.find((t) => t.name === taskName)?.state?.find((s) => s._type === "assignees");
|
|
161
|
+
return entry === void 0 ? !1 : entry.value.some(
|
|
162
|
+
(a) => a.type === "user" ? a.id === actor.id : (actor.roles ?? []).includes(a.role)
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
function effectsContextMap(instance) {
|
|
166
|
+
const out = {};
|
|
167
|
+
for (const entry of instance.effectsContext)
|
|
168
|
+
out[entry.name] = entry._type === "effectsContext.json" ? parseJsonContextEntry(entry) : entry.value;
|
|
169
|
+
return out;
|
|
170
|
+
}
|
|
171
|
+
function parseJsonContextEntry(entry) {
|
|
172
|
+
try {
|
|
173
|
+
return JSON.parse(entry.value);
|
|
174
|
+
} catch (err) {
|
|
175
|
+
throw new Error(
|
|
176
|
+
`effectsContext entry "${entry.name}" holds unparseable JSON: ${err instanceof Error ? err.message : String(err)}`,
|
|
177
|
+
{ cause: err }
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
function paramsForLake(params) {
|
|
182
|
+
return {
|
|
183
|
+
...params,
|
|
184
|
+
self: typeof params.self == "string" ? bareId(params.self) : params.self,
|
|
185
|
+
parent: typeof params.parent == "string" ? bareId(params.parent) : params.parent,
|
|
186
|
+
ancestors: Array.isArray(params.ancestors) ? params.ancestors.map((a) => typeof a == "string" ? bareId(a) : a) : params.ancestors,
|
|
187
|
+
state: stripStateForLake(params.state)
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
function stripStateForLake(value) {
|
|
191
|
+
if (value == null) return value;
|
|
192
|
+
if (typeof value == "string") return bareId(value);
|
|
193
|
+
if (Array.isArray(value)) return value.map(stripStateForLake);
|
|
194
|
+
if (typeof value == "object") {
|
|
195
|
+
const out = {};
|
|
196
|
+
for (const [k, v2] of Object.entries(value))
|
|
197
|
+
out[k] = stripStateForLake(v2);
|
|
198
|
+
return out;
|
|
199
|
+
}
|
|
200
|
+
return value;
|
|
201
|
+
}
|
|
202
|
+
function bareId(id) {
|
|
203
|
+
return isGdrUri(id) ? extractDocumentId(id) : id;
|
|
204
|
+
}
|
|
205
|
+
function getPath(value, path) {
|
|
206
|
+
let current = value;
|
|
207
|
+
for (const part of path.split(".")) {
|
|
208
|
+
if (current == null || typeof current != "object") return;
|
|
209
|
+
current = current[part];
|
|
210
|
+
}
|
|
211
|
+
return current;
|
|
212
|
+
}
|
|
130
213
|
function resourceOf(p) {
|
|
131
214
|
return p.scheme === "dataset" ? { type: "dataset", id: `${p.projectId}.${p.dataset}` } : { type: p.scheme, id: p.resourceId ?? "" };
|
|
132
215
|
}
|
|
133
|
-
const STATE_READ = /^\$state\.(\w+)(?:\.(.+))?$/;
|
|
216
|
+
const STATE_READ = /^\$state\.(\w+)(?:\.(.+))?$/, EFFECTS_READ = /^\$effects\['([^']+)'\](?:\.(.+))?$/;
|
|
134
217
|
function isGuardReadExpr(expr) {
|
|
135
|
-
return expr === "$self" || expr === "$now" || STATE_READ.test(expr);
|
|
218
|
+
return expr === "$self" || expr === "$now" || STATE_READ.test(expr) || EFFECTS_READ.test(expr);
|
|
136
219
|
}
|
|
137
220
|
function resolveGuardRead(expr, ctx) {
|
|
138
221
|
if (expr === "$self") return selfGdr(ctx.instance);
|
|
@@ -142,8 +225,13 @@ function resolveGuardRead(expr, ctx) {
|
|
|
142
225
|
const value = (ctx.instance.state ?? []).find((s) => s.name === stateRead[1])?.value;
|
|
143
226
|
return stateRead[2] !== void 0 ? getPath(value, stateRead[2]) : value;
|
|
144
227
|
}
|
|
228
|
+
const effectsRead = EFFECTS_READ.exec(expr);
|
|
229
|
+
if (effectsRead !== null) {
|
|
230
|
+
const outputs = effectsContextMap(ctx.instance)[effectsRead[1]];
|
|
231
|
+
return effectsRead[2] !== void 0 ? getPath(outputs, effectsRead[2]) : outputs;
|
|
232
|
+
}
|
|
145
233
|
throw new Error(
|
|
146
|
-
`Guard read "${expr}" is not a supported deploy-time value \u2014 use "$self", "$now",
|
|
234
|
+
`Guard read "${expr}" is not a supported deploy-time value \u2014 use "$self", "$now", "$state.<name>[.path]", or "$effects['<name>'][.path]" (guards store resolved values; the lake cannot see $state or $effects)`
|
|
147
235
|
);
|
|
148
236
|
}
|
|
149
237
|
function resolveIdRefTarget(expr, ctx) {
|
|
@@ -225,7 +313,7 @@ function createDefinitionValidator() {
|
|
|
225
313
|
entry.source.type === "query" && tryParse(entry.source.query, `${label}.query`);
|
|
226
314
|
}, checkGuardRead: (expr, where) => {
|
|
227
315
|
isGuardReadExpr(expr) || errors.push(
|
|
228
|
-
` \xB7 ${where}: guard read "${expr}" is not a supported deploy-time value \u2014 use "$self", "$now",
|
|
316
|
+
` \xB7 ${where}: guard read "${expr}" is not a supported deploy-time value \u2014 use "$self", "$now", "$state.<name>[.path]", or "$effects['<name>'][.path]" (guards store resolved values; the lake cannot see $state or $effects)`
|
|
229
317
|
);
|
|
230
318
|
} };
|
|
231
319
|
}
|
|
@@ -404,89 +492,6 @@ async function assertInstanceWriteAllowed(args) {
|
|
|
404
492
|
guards: denied
|
|
405
493
|
});
|
|
406
494
|
}
|
|
407
|
-
function findOpenStageEntry(host) {
|
|
408
|
-
return host.stages.find((s) => s.name === host.currentStage && s.exitedAt === void 0);
|
|
409
|
-
}
|
|
410
|
-
function buildParams(args) {
|
|
411
|
-
const { instance, now, snapshot, extra } = args, currentTasks2 = findOpenStageEntry(instance)?.tasks ?? [];
|
|
412
|
-
return {
|
|
413
|
-
self: selfGdr(instance),
|
|
414
|
-
state: renderedState(instance.state ?? [], snapshot),
|
|
415
|
-
parent: instance.ancestors.at(-1)?.id ?? null,
|
|
416
|
-
ancestors: instance.ancestors.map((a) => a.id),
|
|
417
|
-
stage: instance.currentStage,
|
|
418
|
-
now,
|
|
419
|
-
/** `$effects` — parent context handoff by entry name, completed-effect
|
|
420
|
-
* outputs namespaced under the effect's name (`$effects['x.y'].out`). */
|
|
421
|
-
effects: effectsContextMap(instance),
|
|
422
|
-
/** `$tasks` — the current stage's task rows, statuses included. */
|
|
423
|
-
tasks: currentTasks2,
|
|
424
|
-
allTasksDone: currentTasks2.every((t) => t.status === "done" || t.status === "skipped"),
|
|
425
|
-
anyTaskFailed: currentTasks2.some((t) => t.status === "failed"),
|
|
426
|
-
...extra
|
|
427
|
-
};
|
|
428
|
-
}
|
|
429
|
-
function renderedState(entries, snapshot) {
|
|
430
|
-
const out = {};
|
|
431
|
-
for (const entry of entries) out[entry.name] = renderedValue(entry, snapshot);
|
|
432
|
-
return out;
|
|
433
|
-
}
|
|
434
|
-
function renderedValue(entry, snapshot) {
|
|
435
|
-
return entry._type !== "doc.ref" || entry.value === null || snapshot === void 0 ? entry.value : snapshot.docs.find((d) => d._id === entry.value.id) ?? entry.value;
|
|
436
|
-
}
|
|
437
|
-
function scopedStateOverlay(instance, snapshot, taskName) {
|
|
438
|
-
const stageEntry = findOpenStageEntry(instance);
|
|
439
|
-
if (stageEntry === void 0) return {};
|
|
440
|
-
const stageState = renderedState(stageEntry.state ?? [], snapshot), task = taskName ? stageEntry.tasks.find((t) => t.name === taskName) : void 0;
|
|
441
|
-
return { ...stageState, ...renderedState(task?.state ?? [], snapshot) };
|
|
442
|
-
}
|
|
443
|
-
function assignedFor(instance, taskName, actor) {
|
|
444
|
-
if (actor === void 0) return !1;
|
|
445
|
-
const entry = findOpenStageEntry(instance)?.tasks.find((t) => t.name === taskName)?.state?.find((s) => s._type === "assignees");
|
|
446
|
-
return entry === void 0 ? !1 : entry.value.some(
|
|
447
|
-
(a) => a.type === "user" ? a.id === actor.id : (actor.roles ?? []).includes(a.role)
|
|
448
|
-
);
|
|
449
|
-
}
|
|
450
|
-
function effectsContextMap(instance) {
|
|
451
|
-
const out = {};
|
|
452
|
-
for (const entry of instance.effectsContext)
|
|
453
|
-
out[entry.name] = entry._type === "effectsContext.json" ? parseJsonContextEntry(entry) : entry.value;
|
|
454
|
-
return out;
|
|
455
|
-
}
|
|
456
|
-
function parseJsonContextEntry(entry) {
|
|
457
|
-
try {
|
|
458
|
-
return JSON.parse(entry.value);
|
|
459
|
-
} catch (err) {
|
|
460
|
-
throw new Error(
|
|
461
|
-
`effectsContext entry "${entry.name}" holds unparseable JSON: ${err instanceof Error ? err.message : String(err)}`,
|
|
462
|
-
{ cause: err }
|
|
463
|
-
);
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
function paramsForLake(params) {
|
|
467
|
-
return {
|
|
468
|
-
...params,
|
|
469
|
-
self: typeof params.self == "string" ? bareId(params.self) : params.self,
|
|
470
|
-
parent: typeof params.parent == "string" ? bareId(params.parent) : params.parent,
|
|
471
|
-
ancestors: Array.isArray(params.ancestors) ? params.ancestors.map((a) => typeof a == "string" ? bareId(a) : a) : params.ancestors,
|
|
472
|
-
state: stripStateForLake(params.state)
|
|
473
|
-
};
|
|
474
|
-
}
|
|
475
|
-
function stripStateForLake(value) {
|
|
476
|
-
if (value == null) return value;
|
|
477
|
-
if (typeof value == "string") return bareId(value);
|
|
478
|
-
if (Array.isArray(value)) return value.map(stripStateForLake);
|
|
479
|
-
if (typeof value == "object") {
|
|
480
|
-
const out = {};
|
|
481
|
-
for (const [k, v2] of Object.entries(value))
|
|
482
|
-
out[k] = stripStateForLake(v2);
|
|
483
|
-
return out;
|
|
484
|
-
}
|
|
485
|
-
return value;
|
|
486
|
-
}
|
|
487
|
-
function bareId(id) {
|
|
488
|
-
return isGdrUri(id) ? extractDocumentId(id) : id;
|
|
489
|
-
}
|
|
490
495
|
function abortReason(instance) {
|
|
491
496
|
return instance.history.find(
|
|
492
497
|
(h) => h._type === "aborted"
|
|
@@ -609,6 +614,79 @@ function remediationsFor(diagnosis) {
|
|
|
609
614
|
available: RUNNABLE_VERBS.has(seed.verb)
|
|
610
615
|
}));
|
|
611
616
|
}
|
|
617
|
+
class ActionParamsInvalidError extends Error {
|
|
618
|
+
action;
|
|
619
|
+
task;
|
|
620
|
+
issues;
|
|
621
|
+
constructor(args) {
|
|
622
|
+
const lines = args.issues.map((i) => ` - ${i.param}: ${i.reason}`).join(`
|
|
623
|
+
`);
|
|
624
|
+
super(`Action "${args.action}" on task "${args.task}" rejected: invalid params
|
|
625
|
+
${lines}`), this.name = "ActionParamsInvalidError", this.action = args.action, this.task = args.task, this.issues = args.issues;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
class RequiredStateNotProvidedError extends Error {
|
|
629
|
+
definition;
|
|
630
|
+
missing;
|
|
631
|
+
constructor(args) {
|
|
632
|
+
const lines = args.missing.map((m) => ` - ${m.name} (${m.type})`).join(`
|
|
633
|
+
`), where = args.definition !== void 0 ? ` for workflow "${args.definition}"` : "";
|
|
634
|
+
super(
|
|
635
|
+
`Required init state${where} was not provided:
|
|
636
|
+
${lines}
|
|
637
|
+
Provide each via \`initialState\` when starting standalone, or via the parent's \`subworkflows.with\` when spawned.`
|
|
638
|
+
), this.name = "RequiredStateNotProvidedError", args.definition !== void 0 && (this.definition = args.definition), this.missing = args.missing;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
class WorkflowStateDivergedError extends Error {
|
|
642
|
+
instanceId;
|
|
643
|
+
/** The guard-deploy failure that triggered the (attempted) rollback. */
|
|
644
|
+
guardError;
|
|
645
|
+
/** The rollback failure, when rollback was attempted and itself failed. */
|
|
646
|
+
rollbackError;
|
|
647
|
+
constructor(args) {
|
|
648
|
+
super(
|
|
649
|
+
`Workflow "${args.instanceId}" state committed but its guards could not be reconciled: ${args.reason}.`,
|
|
650
|
+
{ cause: args.guardError }
|
|
651
|
+
), this.name = "WorkflowStateDivergedError", this.instanceId = args.instanceId, this.guardError = args.guardError, args.rollbackError !== void 0 && (this.rollbackError = args.rollbackError);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
class PartialGuardDeployError extends Error {
|
|
655
|
+
stageName;
|
|
656
|
+
deployed;
|
|
657
|
+
constructor(args) {
|
|
658
|
+
super(
|
|
659
|
+
`Partial guard deploy on stage "${args.stageName}": ${args.deployed} guard(s) deployed before a later one failed.`,
|
|
660
|
+
{ cause: args.cause }
|
|
661
|
+
), this.name = "PartialGuardDeployError", this.stageName = args.stageName, this.deployed = args.deployed;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
const CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS = 3;
|
|
665
|
+
class ConcurrentFireActionError extends Error {
|
|
666
|
+
instanceId;
|
|
667
|
+
task;
|
|
668
|
+
action;
|
|
669
|
+
attempts;
|
|
670
|
+
constructor(args) {
|
|
671
|
+
super(
|
|
672
|
+
`Action "${args.action}" on task "${args.task}" (instance ${args.instanceId}) lost the optimistic-locking race ${args.attempts} attempts running \u2014 a concurrent writer kept committing first. Re-read and retry, or investigate a write storm on this instance.`
|
|
673
|
+
), this.name = "ConcurrentFireActionError", this.instanceId = args.instanceId, this.task = args.task, this.action = args.action, this.attempts = args.attempts;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
function isRevisionConflict(error) {
|
|
677
|
+
if (typeof error != "object" || error === null) return !1;
|
|
678
|
+
const { statusCode, message } = error;
|
|
679
|
+
return statusCode === 409 ? !0 : typeof message == "string" && message.includes("ifRevisionId check failed");
|
|
680
|
+
}
|
|
681
|
+
class CascadeLimitError extends Error {
|
|
682
|
+
instanceId;
|
|
683
|
+
limit;
|
|
684
|
+
constructor(args) {
|
|
685
|
+
super(
|
|
686
|
+
`Cascade did not stabilise after ${args.limit} auto-transitions on ${args.instanceId} \u2014 likely a runaway loop (transition guards that stay mutually satisfied). Check that each transition's filter eventually goes false.`
|
|
687
|
+
), this.name = "CascadeLimitError", this.instanceId = args.instanceId, this.limit = args.limit;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
612
690
|
function buildSnapshot(args) {
|
|
613
691
|
const docs = [], knownIds = /* @__PURE__ */ new Set();
|
|
614
692
|
for (const { doc, resource } of args.docs) {
|
|
@@ -874,9 +952,16 @@ async function committedInstance(args) {
|
|
|
874
952
|
}
|
|
875
953
|
async function deployStageGuards(args) {
|
|
876
954
|
const live = await committedInstance(args);
|
|
877
|
-
if (
|
|
878
|
-
|
|
955
|
+
if (live === void 0 || live.currentStage !== args.stageName || live.abortedAt !== void 0) return;
|
|
956
|
+
let deployed = 0;
|
|
957
|
+
for (const { client, doc } of resolvedStageGuards(args)) {
|
|
958
|
+
try {
|
|
879
959
|
await upsertGuard(client, doc);
|
|
960
|
+
} catch (cause) {
|
|
961
|
+
throw deployed > 0 ? new PartialGuardDeployError({ stageName: args.stageName, deployed, cause }) : cause;
|
|
962
|
+
}
|
|
963
|
+
deployed += 1;
|
|
964
|
+
}
|
|
880
965
|
}
|
|
881
966
|
async function retractStageGuards(args) {
|
|
882
967
|
const live = await committedInstance(args);
|
|
@@ -1065,6 +1150,7 @@ function derivePerspectiveFromState(entries) {
|
|
|
1065
1150
|
async function resolveDeclaredState(args) {
|
|
1066
1151
|
const { entryDefs, initialState, ctx, randomKey: randomKey2 } = args;
|
|
1067
1152
|
if (entryDefs === void 0 || entryDefs.length === 0) return [];
|
|
1153
|
+
assertRequiredInitProvided(entryDefs, initialState, ctx.definitionName);
|
|
1068
1154
|
const out = [];
|
|
1069
1155
|
for (const entry of entryDefs) {
|
|
1070
1156
|
const scopedCtx = { ...ctx, resolvedState: out };
|
|
@@ -1072,6 +1158,17 @@ async function resolveDeclaredState(args) {
|
|
|
1072
1158
|
}
|
|
1073
1159
|
return out;
|
|
1074
1160
|
}
|
|
1161
|
+
function assertRequiredInitProvided(entryDefs, initialState, definitionName) {
|
|
1162
|
+
const missing = entryDefs.filter((entry) => entry.required === !0 && entry.source.type === "init").filter((entry) => {
|
|
1163
|
+
const match = initialState.find((s) => s.name === entry.name && s.type === entry.type);
|
|
1164
|
+
return match === void 0 || match.value === void 0 || match.value === null;
|
|
1165
|
+
}).map((entry) => ({ name: entry.name, type: entry.type }));
|
|
1166
|
+
if (missing.length !== 0)
|
|
1167
|
+
throw new RequiredStateNotProvidedError({
|
|
1168
|
+
...definitionName !== void 0 ? { definition: definitionName } : {},
|
|
1169
|
+
missing
|
|
1170
|
+
});
|
|
1171
|
+
}
|
|
1075
1172
|
const ARRAY_SLOT_TYPES = /* @__PURE__ */ new Set([
|
|
1076
1173
|
"doc.refs",
|
|
1077
1174
|
"checklist",
|
|
@@ -1296,56 +1393,6 @@ async function resolveTaskStateEntries(args) {
|
|
|
1296
1393
|
randomKey
|
|
1297
1394
|
});
|
|
1298
1395
|
}
|
|
1299
|
-
class ActionParamsInvalidError extends Error {
|
|
1300
|
-
action;
|
|
1301
|
-
task;
|
|
1302
|
-
issues;
|
|
1303
|
-
constructor(args) {
|
|
1304
|
-
const lines = args.issues.map((i) => ` - ${i.param}: ${i.reason}`).join(`
|
|
1305
|
-
`);
|
|
1306
|
-
super(`Action "${args.action}" on task "${args.task}" rejected: invalid params
|
|
1307
|
-
${lines}`), this.name = "ActionParamsInvalidError", this.action = args.action, this.task = args.task, this.issues = args.issues;
|
|
1308
|
-
}
|
|
1309
|
-
}
|
|
1310
|
-
class WorkflowStateDivergedError extends Error {
|
|
1311
|
-
instanceId;
|
|
1312
|
-
/** The guard-deploy failure that triggered the (attempted) rollback. */
|
|
1313
|
-
guardError;
|
|
1314
|
-
/** The rollback failure, when rollback was attempted and itself failed. */
|
|
1315
|
-
rollbackError;
|
|
1316
|
-
constructor(args) {
|
|
1317
|
-
super(
|
|
1318
|
-
`Workflow "${args.instanceId}" state committed but its guards could not be reconciled: ${args.reason}.`,
|
|
1319
|
-
{ cause: args.guardError }
|
|
1320
|
-
), this.name = "WorkflowStateDivergedError", this.instanceId = args.instanceId, this.guardError = args.guardError, args.rollbackError !== void 0 && (this.rollbackError = args.rollbackError);
|
|
1321
|
-
}
|
|
1322
|
-
}
|
|
1323
|
-
const CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS = 3;
|
|
1324
|
-
class ConcurrentFireActionError extends Error {
|
|
1325
|
-
instanceId;
|
|
1326
|
-
task;
|
|
1327
|
-
action;
|
|
1328
|
-
attempts;
|
|
1329
|
-
constructor(args) {
|
|
1330
|
-
super(
|
|
1331
|
-
`Action "${args.action}" on task "${args.task}" (instance ${args.instanceId}) lost the optimistic-locking race ${args.attempts} attempts running \u2014 a concurrent writer kept committing first. Re-read and retry, or investigate a write storm on this instance.`
|
|
1332
|
-
), this.name = "ConcurrentFireActionError", this.instanceId = args.instanceId, this.task = args.task, this.action = args.action, this.attempts = args.attempts;
|
|
1333
|
-
}
|
|
1334
|
-
}
|
|
1335
|
-
function isRevisionConflict(error) {
|
|
1336
|
-
if (typeof error != "object" || error === null) return !1;
|
|
1337
|
-
const { statusCode, message } = error;
|
|
1338
|
-
return statusCode === 409 ? !0 : typeof message == "string" && message.includes("ifRevisionId check failed");
|
|
1339
|
-
}
|
|
1340
|
-
class CascadeLimitError extends Error {
|
|
1341
|
-
instanceId;
|
|
1342
|
-
limit;
|
|
1343
|
-
constructor(args) {
|
|
1344
|
-
super(
|
|
1345
|
-
`Cascade did not stabilise after ${args.limit} auto-transitions on ${args.instanceId} \u2014 likely a runaway loop (transition guards that stay mutually satisfied). Check that each transition's filter eventually goes false.`
|
|
1346
|
-
), this.name = "CascadeLimitError", this.instanceId = args.instanceId, this.limit = args.limit;
|
|
1347
|
-
}
|
|
1348
|
-
}
|
|
1349
1396
|
async function deployOrRollback(args) {
|
|
1350
1397
|
try {
|
|
1351
1398
|
await args.deploy();
|
|
@@ -1367,7 +1414,11 @@ async function deployOrRollback(args) {
|
|
|
1367
1414
|
reason: "rollback of the committed move failed"
|
|
1368
1415
|
});
|
|
1369
1416
|
}
|
|
1370
|
-
throw guardError
|
|
1417
|
+
throw guardError instanceof PartialGuardDeployError ? new WorkflowStateDivergedError({
|
|
1418
|
+
instanceId: args.instanceId,
|
|
1419
|
+
guardError,
|
|
1420
|
+
reason: "a multi-guard deploy partially applied; the rolled-back move left orphaned guard locks"
|
|
1421
|
+
}) : guardError;
|
|
1371
1422
|
}
|
|
1372
1423
|
}
|
|
1373
1424
|
function applyTaskStatusChange(args) {
|
|
@@ -1456,7 +1507,8 @@ function materializeInstance(base, mutation) {
|
|
|
1456
1507
|
...base,
|
|
1457
1508
|
currentStage: mutation.currentStage,
|
|
1458
1509
|
state: mutation.state,
|
|
1459
|
-
stages: mutation.stages
|
|
1510
|
+
stages: mutation.stages,
|
|
1511
|
+
effectsContext: mutation.effectsContext
|
|
1460
1512
|
};
|
|
1461
1513
|
}
|
|
1462
1514
|
async function persist(ctx, mutation) {
|
|
@@ -1471,7 +1523,21 @@ async function persist(ctx, mutation) {
|
|
|
1471
1523
|
tx.patch(ctx.client.patch(ctx.instance._id).set(set).ifRevisionId(ctx.instance._rev)), await tx.commit(), mutation.pendingCreates = [];
|
|
1472
1524
|
const actorForPriming = void 0;
|
|
1473
1525
|
for (const body of pendingCreates)
|
|
1474
|
-
|
|
1526
|
+
try {
|
|
1527
|
+
await primeInitialStage(ctx.client, body._id, actorForPriming, ctx.clientForGdr, ctx.clock), await cascadeAutoTransitions(
|
|
1528
|
+
ctx.client,
|
|
1529
|
+
body._id,
|
|
1530
|
+
actorForPriming,
|
|
1531
|
+
ctx.clientForGdr,
|
|
1532
|
+
ctx.clock
|
|
1533
|
+
), await propagateToAncestors(ctx.client, body._id, actorForPriming, ctx.clientForGdr, ctx.clock);
|
|
1534
|
+
} catch (cause) {
|
|
1535
|
+
throw cause instanceof WorkflowStateDivergedError ? cause : new WorkflowStateDivergedError({
|
|
1536
|
+
instanceId: ctx.instance._id,
|
|
1537
|
+
guardError: cause,
|
|
1538
|
+
reason: `spawned child "${body._id}" failed to settle after the spawn transaction committed`
|
|
1539
|
+
});
|
|
1540
|
+
}
|
|
1475
1541
|
const reloaded = await ctx.client.getDocument(ctx.instance._id);
|
|
1476
1542
|
if (!reloaded)
|
|
1477
1543
|
throw new Error(`Instance ${ctx.instance._id} disappeared after transaction commit`);
|
|
@@ -1736,6 +1802,7 @@ async function prepareChildInstance(args) {
|
|
|
1736
1802
|
selfId: childDocId,
|
|
1737
1803
|
tag: childTag,
|
|
1738
1804
|
workflowResource,
|
|
1805
|
+
definitionName: definition.name,
|
|
1739
1806
|
...inheritedPerspective !== void 0 ? { perspective: inheritedPerspective } : {}
|
|
1740
1807
|
},
|
|
1741
1808
|
randomKey
|
|
@@ -1782,6 +1849,28 @@ async function resolveBindings(args) {
|
|
|
1782
1849
|
resolved[key] = await runGroq(groq, args.params, args.snapshot);
|
|
1783
1850
|
return { ...resolved, ...args.staticInput };
|
|
1784
1851
|
}
|
|
1852
|
+
async function persistThenDeploy(ctx, mutation, deploy) {
|
|
1853
|
+
const spawned = mutation.pendingCreates.length > 0, committed = await persist(ctx, mutation);
|
|
1854
|
+
await deployOrRollback({
|
|
1855
|
+
client: ctx.client,
|
|
1856
|
+
instanceId: ctx.instance._id,
|
|
1857
|
+
committedRev: committed._rev,
|
|
1858
|
+
restore: instanceStateFields(ctx.instance),
|
|
1859
|
+
unset: ctx.instance.completedAt === void 0 ? ["completedAt"] : [],
|
|
1860
|
+
reversible: !spawned,
|
|
1861
|
+
deploy
|
|
1862
|
+
});
|
|
1863
|
+
}
|
|
1864
|
+
async function refreshStageGuards(ctx, mutation, stageName) {
|
|
1865
|
+
await deployStageGuards({
|
|
1866
|
+
client: ctx.client,
|
|
1867
|
+
clientForGdr: ctx.clientForGdr,
|
|
1868
|
+
instance: materializeInstance(ctx.instance, mutation),
|
|
1869
|
+
definition: ctx.definition,
|
|
1870
|
+
stageName,
|
|
1871
|
+
now: ctx.now
|
|
1872
|
+
});
|
|
1873
|
+
}
|
|
1785
1874
|
async function completeEffect(args) {
|
|
1786
1875
|
const { client, instanceId, effectKey, status, outputs, detail, error, durationMs, options } = args, ctx = await loadContext(client, instanceId, {
|
|
1787
1876
|
...options?.clock ? { clock: options.clock } : {},
|
|
@@ -1827,9 +1916,11 @@ async function commitCompleteEffect(ctx, effectKey, status, outputs, detail, err
|
|
|
1827
1916
|
const mutation = startMutation(ctx.instance);
|
|
1828
1917
|
mutation.pendingEffects = mutation.pendingEffects.filter((e) => e._key !== effectKey);
|
|
1829
1918
|
const ranAt = ctx.now;
|
|
1830
|
-
|
|
1919
|
+
mutation.effectHistory.push(
|
|
1831
1920
|
buildEffectHistoryEntry(pending, { status, ranAt, actor, detail, error, durationMs, outputs })
|
|
1832
|
-
)
|
|
1921
|
+
);
|
|
1922
|
+
const wroteEffectsContext = status === "done" && outputs !== void 0;
|
|
1923
|
+
return wroteEffectsContext && upsertEffectsContext(mutation, pending.name, outputs), mutation.history.push({
|
|
1833
1924
|
_key: randomKey(),
|
|
1834
1925
|
_type: "effectCompleted",
|
|
1835
1926
|
at: ranAt,
|
|
@@ -1839,7 +1930,11 @@ async function commitCompleteEffect(ctx, effectKey, status, outputs, detail, err
|
|
|
1839
1930
|
...outputs !== void 0 ? { outputs } : {},
|
|
1840
1931
|
...detail !== void 0 ? { detail } : {},
|
|
1841
1932
|
...actor !== void 0 ? { actor } : {}
|
|
1842
|
-
}), await
|
|
1933
|
+
}), await persistThenDeploy(
|
|
1934
|
+
ctx,
|
|
1935
|
+
mutation,
|
|
1936
|
+
() => wroteEffectsContext ? refreshStageGuards(ctx, mutation, ctx.instance.currentStage) : Promise.resolve()
|
|
1937
|
+
), { effectKey, status };
|
|
1843
1938
|
}
|
|
1844
1939
|
function buildQueuedEffect(effect, origin, params, actor, now) {
|
|
1845
1940
|
const key = randomKey(), pending = {
|
|
@@ -2299,28 +2394,6 @@ async function persistStageMove(ctx, mutation, exitedStage, enteredStage) {
|
|
|
2299
2394
|
now: ctx.now
|
|
2300
2395
|
});
|
|
2301
2396
|
}
|
|
2302
|
-
async function persistThenDeploy(ctx, mutation, deploy) {
|
|
2303
|
-
const spawned = mutation.pendingCreates.length > 0, committed = await persist(ctx, mutation);
|
|
2304
|
-
await deployOrRollback({
|
|
2305
|
-
client: ctx.client,
|
|
2306
|
-
instanceId: ctx.instance._id,
|
|
2307
|
-
committedRev: committed._rev,
|
|
2308
|
-
restore: instanceStateFields(ctx.instance),
|
|
2309
|
-
unset: ctx.instance.completedAt === void 0 ? ["completedAt"] : [],
|
|
2310
|
-
reversible: !spawned,
|
|
2311
|
-
deploy
|
|
2312
|
-
});
|
|
2313
|
-
}
|
|
2314
|
-
async function refreshStageGuards(ctx, mutation, stageName) {
|
|
2315
|
-
await deployStageGuards({
|
|
2316
|
-
client: ctx.client,
|
|
2317
|
-
clientForGdr: ctx.clientForGdr,
|
|
2318
|
-
instance: materializeInstance(ctx.instance, mutation),
|
|
2319
|
-
definition: ctx.definition,
|
|
2320
|
-
stageName,
|
|
2321
|
-
now: ctx.now
|
|
2322
|
-
});
|
|
2323
|
-
}
|
|
2324
2397
|
async function commitTransition(ctx, actor) {
|
|
2325
2398
|
if (isTerminal(ctx))
|
|
2326
2399
|
return { fired: !1 };
|
|
@@ -3277,6 +3350,7 @@ const workflow = {
|
|
|
3277
3350
|
selfId: id,
|
|
3278
3351
|
tag,
|
|
3279
3352
|
workflowResource,
|
|
3353
|
+
definitionName: definition.name,
|
|
3280
3354
|
...perspective !== void 0 ? { perspective } : {}
|
|
3281
3355
|
},
|
|
3282
3356
|
randomKey
|
|
@@ -4153,6 +4227,7 @@ function displayDescription(typeKey) {
|
|
|
4153
4227
|
return DISPLAY[typeKey]?.description;
|
|
4154
4228
|
}
|
|
4155
4229
|
exports.WORKFLOW_DEFINITION_TYPE = schema.WORKFLOW_DEFINITION_TYPE;
|
|
4230
|
+
exports.isStartableDefinition = schema.isStartableDefinition;
|
|
4156
4231
|
exports.AUTHORING_DISPLAY = AUTHORING_DISPLAY;
|
|
4157
4232
|
exports.ActionDisabledError = ActionDisabledError;
|
|
4158
4233
|
exports.ActionParamsInvalidError = ActionParamsInvalidError;
|
|
@@ -4167,6 +4242,8 @@ exports.GUARD_OWNER = GUARD_OWNER;
|
|
|
4167
4242
|
exports.HISTORY_DISPLAY = HISTORY_DISPLAY;
|
|
4168
4243
|
exports.MutationGuardDeniedError = MutationGuardDeniedError;
|
|
4169
4244
|
exports.OP_DISPLAY = OP_DISPLAY;
|
|
4245
|
+
exports.PartialGuardDeployError = PartialGuardDeployError;
|
|
4246
|
+
exports.RequiredStateNotProvidedError = RequiredStateNotProvidedError;
|
|
4170
4247
|
exports.STATE_SLOT_DISPLAY = STATE_SLOT_DISPLAY;
|
|
4171
4248
|
exports.WORKFLOW_INSTANCE_TYPE = WORKFLOW_INSTANCE_TYPE;
|
|
4172
4249
|
exports.WorkflowStateDivergedError = WorkflowStateDivergedError;
|