@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.js
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
import { parse, evaluate } from "groq-js";
|
|
2
2
|
import { isTerminalTaskStatus, WORKFLOW_DEFINITION_TYPE, DOCUMENT_VALUE_PERMISSIONS } from "./_chunks-es/schema.js";
|
|
3
|
+
import { isStartableDefinition } from "./_chunks-es/schema.js";
|
|
3
4
|
import * as v from "valibot";
|
|
4
|
-
function
|
|
5
|
-
|
|
6
|
-
for (const part of path.split(".")) {
|
|
7
|
-
if (current == null || typeof current != "object") return;
|
|
8
|
-
current = current[part];
|
|
9
|
-
}
|
|
10
|
-
return current;
|
|
5
|
+
function findOpenStageEntry(host) {
|
|
6
|
+
return host.stages.find((s) => s.name === host.currentStage && s.exitedAt === void 0);
|
|
11
7
|
}
|
|
12
8
|
const KNOWN_SCHEMES = /* @__PURE__ */ new Set(["dataset", "canvas", "media-library", "dashboard"]);
|
|
13
9
|
function parseGdr(uri) {
|
|
@@ -111,12 +107,100 @@ function gdrRef(res, documentId, type) {
|
|
|
111
107
|
function isGdr(value) {
|
|
112
108
|
return typeof value == "object" && value !== null && typeof value.id == "string" && typeof value.type == "string";
|
|
113
109
|
}
|
|
110
|
+
function buildParams(args) {
|
|
111
|
+
const { instance, now, snapshot, extra } = args, currentTasks2 = findOpenStageEntry(instance)?.tasks ?? [];
|
|
112
|
+
return {
|
|
113
|
+
self: selfGdr(instance),
|
|
114
|
+
state: renderedState(instance.state ?? [], snapshot),
|
|
115
|
+
parent: instance.ancestors.at(-1)?.id ?? null,
|
|
116
|
+
ancestors: instance.ancestors.map((a) => a.id),
|
|
117
|
+
stage: instance.currentStage,
|
|
118
|
+
now,
|
|
119
|
+
/** `$effects` — parent context handoff by entry name, completed-effect
|
|
120
|
+
* outputs namespaced under the effect's name (`$effects['x.y'].out`). */
|
|
121
|
+
effects: effectsContextMap(instance),
|
|
122
|
+
/** `$tasks` — the current stage's task rows, statuses included. */
|
|
123
|
+
tasks: currentTasks2,
|
|
124
|
+
allTasksDone: currentTasks2.every((t) => t.status === "done" || t.status === "skipped"),
|
|
125
|
+
anyTaskFailed: currentTasks2.some((t) => t.status === "failed"),
|
|
126
|
+
...extra
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
function renderedState(entries, snapshot) {
|
|
130
|
+
const out = {};
|
|
131
|
+
for (const entry of entries) out[entry.name] = renderedValue(entry, snapshot);
|
|
132
|
+
return out;
|
|
133
|
+
}
|
|
134
|
+
function renderedValue(entry, snapshot) {
|
|
135
|
+
return entry._type !== "doc.ref" || entry.value === null || snapshot === void 0 ? entry.value : snapshot.docs.find((d) => d._id === entry.value.id) ?? entry.value;
|
|
136
|
+
}
|
|
137
|
+
function scopedStateOverlay(instance, snapshot, taskName) {
|
|
138
|
+
const stageEntry = findOpenStageEntry(instance);
|
|
139
|
+
if (stageEntry === void 0) return {};
|
|
140
|
+
const stageState = renderedState(stageEntry.state ?? [], snapshot), task = taskName ? stageEntry.tasks.find((t) => t.name === taskName) : void 0;
|
|
141
|
+
return { ...stageState, ...renderedState(task?.state ?? [], snapshot) };
|
|
142
|
+
}
|
|
143
|
+
function assignedFor(instance, taskName, actor) {
|
|
144
|
+
if (actor === void 0) return !1;
|
|
145
|
+
const entry = findOpenStageEntry(instance)?.tasks.find((t) => t.name === taskName)?.state?.find((s) => s._type === "assignees");
|
|
146
|
+
return entry === void 0 ? !1 : entry.value.some(
|
|
147
|
+
(a) => a.type === "user" ? a.id === actor.id : (actor.roles ?? []).includes(a.role)
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
function effectsContextMap(instance) {
|
|
151
|
+
const out = {};
|
|
152
|
+
for (const entry of instance.effectsContext)
|
|
153
|
+
out[entry.name] = entry._type === "effectsContext.json" ? parseJsonContextEntry(entry) : entry.value;
|
|
154
|
+
return out;
|
|
155
|
+
}
|
|
156
|
+
function parseJsonContextEntry(entry) {
|
|
157
|
+
try {
|
|
158
|
+
return JSON.parse(entry.value);
|
|
159
|
+
} catch (err) {
|
|
160
|
+
throw new Error(
|
|
161
|
+
`effectsContext entry "${entry.name}" holds unparseable JSON: ${err instanceof Error ? err.message : String(err)}`,
|
|
162
|
+
{ cause: err }
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
function paramsForLake(params) {
|
|
167
|
+
return {
|
|
168
|
+
...params,
|
|
169
|
+
self: typeof params.self == "string" ? bareId(params.self) : params.self,
|
|
170
|
+
parent: typeof params.parent == "string" ? bareId(params.parent) : params.parent,
|
|
171
|
+
ancestors: Array.isArray(params.ancestors) ? params.ancestors.map((a) => typeof a == "string" ? bareId(a) : a) : params.ancestors,
|
|
172
|
+
state: stripStateForLake(params.state)
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
function stripStateForLake(value) {
|
|
176
|
+
if (value == null) return value;
|
|
177
|
+
if (typeof value == "string") return bareId(value);
|
|
178
|
+
if (Array.isArray(value)) return value.map(stripStateForLake);
|
|
179
|
+
if (typeof value == "object") {
|
|
180
|
+
const out = {};
|
|
181
|
+
for (const [k, v2] of Object.entries(value))
|
|
182
|
+
out[k] = stripStateForLake(v2);
|
|
183
|
+
return out;
|
|
184
|
+
}
|
|
185
|
+
return value;
|
|
186
|
+
}
|
|
187
|
+
function bareId(id) {
|
|
188
|
+
return isGdrUri(id) ? extractDocumentId(id) : id;
|
|
189
|
+
}
|
|
190
|
+
function getPath(value, path) {
|
|
191
|
+
let current = value;
|
|
192
|
+
for (const part of path.split(".")) {
|
|
193
|
+
if (current == null || typeof current != "object") return;
|
|
194
|
+
current = current[part];
|
|
195
|
+
}
|
|
196
|
+
return current;
|
|
197
|
+
}
|
|
114
198
|
function resourceOf(p) {
|
|
115
199
|
return p.scheme === "dataset" ? { type: "dataset", id: `${p.projectId}.${p.dataset}` } : { type: p.scheme, id: p.resourceId ?? "" };
|
|
116
200
|
}
|
|
117
|
-
const STATE_READ = /^\$state\.(\w+)(?:\.(.+))?$/;
|
|
201
|
+
const STATE_READ = /^\$state\.(\w+)(?:\.(.+))?$/, EFFECTS_READ = /^\$effects\['([^']+)'\](?:\.(.+))?$/;
|
|
118
202
|
function isGuardReadExpr(expr) {
|
|
119
|
-
return expr === "$self" || expr === "$now" || STATE_READ.test(expr);
|
|
203
|
+
return expr === "$self" || expr === "$now" || STATE_READ.test(expr) || EFFECTS_READ.test(expr);
|
|
120
204
|
}
|
|
121
205
|
function resolveGuardRead(expr, ctx) {
|
|
122
206
|
if (expr === "$self") return selfGdr(ctx.instance);
|
|
@@ -126,8 +210,13 @@ function resolveGuardRead(expr, ctx) {
|
|
|
126
210
|
const value = (ctx.instance.state ?? []).find((s) => s.name === stateRead[1])?.value;
|
|
127
211
|
return stateRead[2] !== void 0 ? getPath(value, stateRead[2]) : value;
|
|
128
212
|
}
|
|
213
|
+
const effectsRead = EFFECTS_READ.exec(expr);
|
|
214
|
+
if (effectsRead !== null) {
|
|
215
|
+
const outputs = effectsContextMap(ctx.instance)[effectsRead[1]];
|
|
216
|
+
return effectsRead[2] !== void 0 ? getPath(outputs, effectsRead[2]) : outputs;
|
|
217
|
+
}
|
|
129
218
|
throw new Error(
|
|
130
|
-
`Guard read "${expr}" is not a supported deploy-time value \u2014 use "$self", "$now",
|
|
219
|
+
`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)`
|
|
131
220
|
);
|
|
132
221
|
}
|
|
133
222
|
function resolveIdRefTarget(expr, ctx) {
|
|
@@ -209,7 +298,7 @@ function createDefinitionValidator() {
|
|
|
209
298
|
entry.source.type === "query" && tryParse(entry.source.query, `${label}.query`);
|
|
210
299
|
}, checkGuardRead: (expr, where) => {
|
|
211
300
|
isGuardReadExpr(expr) || errors.push(
|
|
212
|
-
` \xB7 ${where}: guard read "${expr}" is not a supported deploy-time value \u2014 use "$self", "$now",
|
|
301
|
+
` \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)`
|
|
213
302
|
);
|
|
214
303
|
} };
|
|
215
304
|
}
|
|
@@ -388,89 +477,6 @@ async function assertInstanceWriteAllowed(args) {
|
|
|
388
477
|
guards: denied
|
|
389
478
|
});
|
|
390
479
|
}
|
|
391
|
-
function findOpenStageEntry(host) {
|
|
392
|
-
return host.stages.find((s) => s.name === host.currentStage && s.exitedAt === void 0);
|
|
393
|
-
}
|
|
394
|
-
function buildParams(args) {
|
|
395
|
-
const { instance, now, snapshot, extra } = args, currentTasks2 = findOpenStageEntry(instance)?.tasks ?? [];
|
|
396
|
-
return {
|
|
397
|
-
self: selfGdr(instance),
|
|
398
|
-
state: renderedState(instance.state ?? [], snapshot),
|
|
399
|
-
parent: instance.ancestors.at(-1)?.id ?? null,
|
|
400
|
-
ancestors: instance.ancestors.map((a) => a.id),
|
|
401
|
-
stage: instance.currentStage,
|
|
402
|
-
now,
|
|
403
|
-
/** `$effects` — parent context handoff by entry name, completed-effect
|
|
404
|
-
* outputs namespaced under the effect's name (`$effects['x.y'].out`). */
|
|
405
|
-
effects: effectsContextMap(instance),
|
|
406
|
-
/** `$tasks` — the current stage's task rows, statuses included. */
|
|
407
|
-
tasks: currentTasks2,
|
|
408
|
-
allTasksDone: currentTasks2.every((t) => t.status === "done" || t.status === "skipped"),
|
|
409
|
-
anyTaskFailed: currentTasks2.some((t) => t.status === "failed"),
|
|
410
|
-
...extra
|
|
411
|
-
};
|
|
412
|
-
}
|
|
413
|
-
function renderedState(entries, snapshot) {
|
|
414
|
-
const out = {};
|
|
415
|
-
for (const entry of entries) out[entry.name] = renderedValue(entry, snapshot);
|
|
416
|
-
return out;
|
|
417
|
-
}
|
|
418
|
-
function renderedValue(entry, snapshot) {
|
|
419
|
-
return entry._type !== "doc.ref" || entry.value === null || snapshot === void 0 ? entry.value : snapshot.docs.find((d) => d._id === entry.value.id) ?? entry.value;
|
|
420
|
-
}
|
|
421
|
-
function scopedStateOverlay(instance, snapshot, taskName) {
|
|
422
|
-
const stageEntry = findOpenStageEntry(instance);
|
|
423
|
-
if (stageEntry === void 0) return {};
|
|
424
|
-
const stageState = renderedState(stageEntry.state ?? [], snapshot), task = taskName ? stageEntry.tasks.find((t) => t.name === taskName) : void 0;
|
|
425
|
-
return { ...stageState, ...renderedState(task?.state ?? [], snapshot) };
|
|
426
|
-
}
|
|
427
|
-
function assignedFor(instance, taskName, actor) {
|
|
428
|
-
if (actor === void 0) return !1;
|
|
429
|
-
const entry = findOpenStageEntry(instance)?.tasks.find((t) => t.name === taskName)?.state?.find((s) => s._type === "assignees");
|
|
430
|
-
return entry === void 0 ? !1 : entry.value.some(
|
|
431
|
-
(a) => a.type === "user" ? a.id === actor.id : (actor.roles ?? []).includes(a.role)
|
|
432
|
-
);
|
|
433
|
-
}
|
|
434
|
-
function effectsContextMap(instance) {
|
|
435
|
-
const out = {};
|
|
436
|
-
for (const entry of instance.effectsContext)
|
|
437
|
-
out[entry.name] = entry._type === "effectsContext.json" ? parseJsonContextEntry(entry) : entry.value;
|
|
438
|
-
return out;
|
|
439
|
-
}
|
|
440
|
-
function parseJsonContextEntry(entry) {
|
|
441
|
-
try {
|
|
442
|
-
return JSON.parse(entry.value);
|
|
443
|
-
} catch (err) {
|
|
444
|
-
throw new Error(
|
|
445
|
-
`effectsContext entry "${entry.name}" holds unparseable JSON: ${err instanceof Error ? err.message : String(err)}`,
|
|
446
|
-
{ cause: err }
|
|
447
|
-
);
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
function paramsForLake(params) {
|
|
451
|
-
return {
|
|
452
|
-
...params,
|
|
453
|
-
self: typeof params.self == "string" ? bareId(params.self) : params.self,
|
|
454
|
-
parent: typeof params.parent == "string" ? bareId(params.parent) : params.parent,
|
|
455
|
-
ancestors: Array.isArray(params.ancestors) ? params.ancestors.map((a) => typeof a == "string" ? bareId(a) : a) : params.ancestors,
|
|
456
|
-
state: stripStateForLake(params.state)
|
|
457
|
-
};
|
|
458
|
-
}
|
|
459
|
-
function stripStateForLake(value) {
|
|
460
|
-
if (value == null) return value;
|
|
461
|
-
if (typeof value == "string") return bareId(value);
|
|
462
|
-
if (Array.isArray(value)) return value.map(stripStateForLake);
|
|
463
|
-
if (typeof value == "object") {
|
|
464
|
-
const out = {};
|
|
465
|
-
for (const [k, v2] of Object.entries(value))
|
|
466
|
-
out[k] = stripStateForLake(v2);
|
|
467
|
-
return out;
|
|
468
|
-
}
|
|
469
|
-
return value;
|
|
470
|
-
}
|
|
471
|
-
function bareId(id) {
|
|
472
|
-
return isGdrUri(id) ? extractDocumentId(id) : id;
|
|
473
|
-
}
|
|
474
480
|
function abortReason(instance) {
|
|
475
481
|
return instance.history.find(
|
|
476
482
|
(h) => h._type === "aborted"
|
|
@@ -593,6 +599,79 @@ function remediationsFor(diagnosis) {
|
|
|
593
599
|
available: RUNNABLE_VERBS.has(seed.verb)
|
|
594
600
|
}));
|
|
595
601
|
}
|
|
602
|
+
class ActionParamsInvalidError extends Error {
|
|
603
|
+
action;
|
|
604
|
+
task;
|
|
605
|
+
issues;
|
|
606
|
+
constructor(args) {
|
|
607
|
+
const lines = args.issues.map((i) => ` - ${i.param}: ${i.reason}`).join(`
|
|
608
|
+
`);
|
|
609
|
+
super(`Action "${args.action}" on task "${args.task}" rejected: invalid params
|
|
610
|
+
${lines}`), this.name = "ActionParamsInvalidError", this.action = args.action, this.task = args.task, this.issues = args.issues;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
class RequiredStateNotProvidedError extends Error {
|
|
614
|
+
definition;
|
|
615
|
+
missing;
|
|
616
|
+
constructor(args) {
|
|
617
|
+
const lines = args.missing.map((m) => ` - ${m.name} (${m.type})`).join(`
|
|
618
|
+
`), where = args.definition !== void 0 ? ` for workflow "${args.definition}"` : "";
|
|
619
|
+
super(
|
|
620
|
+
`Required init state${where} was not provided:
|
|
621
|
+
${lines}
|
|
622
|
+
Provide each via \`initialState\` when starting standalone, or via the parent's \`subworkflows.with\` when spawned.`
|
|
623
|
+
), this.name = "RequiredStateNotProvidedError", args.definition !== void 0 && (this.definition = args.definition), this.missing = args.missing;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
class WorkflowStateDivergedError extends Error {
|
|
627
|
+
instanceId;
|
|
628
|
+
/** The guard-deploy failure that triggered the (attempted) rollback. */
|
|
629
|
+
guardError;
|
|
630
|
+
/** The rollback failure, when rollback was attempted and itself failed. */
|
|
631
|
+
rollbackError;
|
|
632
|
+
constructor(args) {
|
|
633
|
+
super(
|
|
634
|
+
`Workflow "${args.instanceId}" state committed but its guards could not be reconciled: ${args.reason}.`,
|
|
635
|
+
{ cause: args.guardError }
|
|
636
|
+
), this.name = "WorkflowStateDivergedError", this.instanceId = args.instanceId, this.guardError = args.guardError, args.rollbackError !== void 0 && (this.rollbackError = args.rollbackError);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
class PartialGuardDeployError extends Error {
|
|
640
|
+
stageName;
|
|
641
|
+
deployed;
|
|
642
|
+
constructor(args) {
|
|
643
|
+
super(
|
|
644
|
+
`Partial guard deploy on stage "${args.stageName}": ${args.deployed} guard(s) deployed before a later one failed.`,
|
|
645
|
+
{ cause: args.cause }
|
|
646
|
+
), this.name = "PartialGuardDeployError", this.stageName = args.stageName, this.deployed = args.deployed;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
const CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS = 3;
|
|
650
|
+
class ConcurrentFireActionError extends Error {
|
|
651
|
+
instanceId;
|
|
652
|
+
task;
|
|
653
|
+
action;
|
|
654
|
+
attempts;
|
|
655
|
+
constructor(args) {
|
|
656
|
+
super(
|
|
657
|
+
`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.`
|
|
658
|
+
), this.name = "ConcurrentFireActionError", this.instanceId = args.instanceId, this.task = args.task, this.action = args.action, this.attempts = args.attempts;
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
function isRevisionConflict(error) {
|
|
662
|
+
if (typeof error != "object" || error === null) return !1;
|
|
663
|
+
const { statusCode, message } = error;
|
|
664
|
+
return statusCode === 409 ? !0 : typeof message == "string" && message.includes("ifRevisionId check failed");
|
|
665
|
+
}
|
|
666
|
+
class CascadeLimitError extends Error {
|
|
667
|
+
instanceId;
|
|
668
|
+
limit;
|
|
669
|
+
constructor(args) {
|
|
670
|
+
super(
|
|
671
|
+
`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.`
|
|
672
|
+
), this.name = "CascadeLimitError", this.instanceId = args.instanceId, this.limit = args.limit;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
596
675
|
function buildSnapshot(args) {
|
|
597
676
|
const docs = [], knownIds = /* @__PURE__ */ new Set();
|
|
598
677
|
for (const { doc, resource } of args.docs) {
|
|
@@ -858,9 +937,16 @@ async function committedInstance(args) {
|
|
|
858
937
|
}
|
|
859
938
|
async function deployStageGuards(args) {
|
|
860
939
|
const live = await committedInstance(args);
|
|
861
|
-
if (
|
|
862
|
-
|
|
940
|
+
if (live === void 0 || live.currentStage !== args.stageName || live.abortedAt !== void 0) return;
|
|
941
|
+
let deployed = 0;
|
|
942
|
+
for (const { client, doc } of resolvedStageGuards(args)) {
|
|
943
|
+
try {
|
|
863
944
|
await upsertGuard(client, doc);
|
|
945
|
+
} catch (cause) {
|
|
946
|
+
throw deployed > 0 ? new PartialGuardDeployError({ stageName: args.stageName, deployed, cause }) : cause;
|
|
947
|
+
}
|
|
948
|
+
deployed += 1;
|
|
949
|
+
}
|
|
864
950
|
}
|
|
865
951
|
async function retractStageGuards(args) {
|
|
866
952
|
const live = await committedInstance(args);
|
|
@@ -1049,6 +1135,7 @@ function derivePerspectiveFromState(entries) {
|
|
|
1049
1135
|
async function resolveDeclaredState(args) {
|
|
1050
1136
|
const { entryDefs, initialState, ctx, randomKey: randomKey2 } = args;
|
|
1051
1137
|
if (entryDefs === void 0 || entryDefs.length === 0) return [];
|
|
1138
|
+
assertRequiredInitProvided(entryDefs, initialState, ctx.definitionName);
|
|
1052
1139
|
const out = [];
|
|
1053
1140
|
for (const entry of entryDefs) {
|
|
1054
1141
|
const scopedCtx = { ...ctx, resolvedState: out };
|
|
@@ -1056,6 +1143,17 @@ async function resolveDeclaredState(args) {
|
|
|
1056
1143
|
}
|
|
1057
1144
|
return out;
|
|
1058
1145
|
}
|
|
1146
|
+
function assertRequiredInitProvided(entryDefs, initialState, definitionName) {
|
|
1147
|
+
const missing = entryDefs.filter((entry) => entry.required === !0 && entry.source.type === "init").filter((entry) => {
|
|
1148
|
+
const match = initialState.find((s) => s.name === entry.name && s.type === entry.type);
|
|
1149
|
+
return match === void 0 || match.value === void 0 || match.value === null;
|
|
1150
|
+
}).map((entry) => ({ name: entry.name, type: entry.type }));
|
|
1151
|
+
if (missing.length !== 0)
|
|
1152
|
+
throw new RequiredStateNotProvidedError({
|
|
1153
|
+
...definitionName !== void 0 ? { definition: definitionName } : {},
|
|
1154
|
+
missing
|
|
1155
|
+
});
|
|
1156
|
+
}
|
|
1059
1157
|
const ARRAY_SLOT_TYPES = /* @__PURE__ */ new Set([
|
|
1060
1158
|
"doc.refs",
|
|
1061
1159
|
"checklist",
|
|
@@ -1280,56 +1378,6 @@ async function resolveTaskStateEntries(args) {
|
|
|
1280
1378
|
randomKey
|
|
1281
1379
|
});
|
|
1282
1380
|
}
|
|
1283
|
-
class ActionParamsInvalidError extends Error {
|
|
1284
|
-
action;
|
|
1285
|
-
task;
|
|
1286
|
-
issues;
|
|
1287
|
-
constructor(args) {
|
|
1288
|
-
const lines = args.issues.map((i) => ` - ${i.param}: ${i.reason}`).join(`
|
|
1289
|
-
`);
|
|
1290
|
-
super(`Action "${args.action}" on task "${args.task}" rejected: invalid params
|
|
1291
|
-
${lines}`), this.name = "ActionParamsInvalidError", this.action = args.action, this.task = args.task, this.issues = args.issues;
|
|
1292
|
-
}
|
|
1293
|
-
}
|
|
1294
|
-
class WorkflowStateDivergedError extends Error {
|
|
1295
|
-
instanceId;
|
|
1296
|
-
/** The guard-deploy failure that triggered the (attempted) rollback. */
|
|
1297
|
-
guardError;
|
|
1298
|
-
/** The rollback failure, when rollback was attempted and itself failed. */
|
|
1299
|
-
rollbackError;
|
|
1300
|
-
constructor(args) {
|
|
1301
|
-
super(
|
|
1302
|
-
`Workflow "${args.instanceId}" state committed but its guards could not be reconciled: ${args.reason}.`,
|
|
1303
|
-
{ cause: args.guardError }
|
|
1304
|
-
), this.name = "WorkflowStateDivergedError", this.instanceId = args.instanceId, this.guardError = args.guardError, args.rollbackError !== void 0 && (this.rollbackError = args.rollbackError);
|
|
1305
|
-
}
|
|
1306
|
-
}
|
|
1307
|
-
const CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS = 3;
|
|
1308
|
-
class ConcurrentFireActionError extends Error {
|
|
1309
|
-
instanceId;
|
|
1310
|
-
task;
|
|
1311
|
-
action;
|
|
1312
|
-
attempts;
|
|
1313
|
-
constructor(args) {
|
|
1314
|
-
super(
|
|
1315
|
-
`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.`
|
|
1316
|
-
), this.name = "ConcurrentFireActionError", this.instanceId = args.instanceId, this.task = args.task, this.action = args.action, this.attempts = args.attempts;
|
|
1317
|
-
}
|
|
1318
|
-
}
|
|
1319
|
-
function isRevisionConflict(error) {
|
|
1320
|
-
if (typeof error != "object" || error === null) return !1;
|
|
1321
|
-
const { statusCode, message } = error;
|
|
1322
|
-
return statusCode === 409 ? !0 : typeof message == "string" && message.includes("ifRevisionId check failed");
|
|
1323
|
-
}
|
|
1324
|
-
class CascadeLimitError extends Error {
|
|
1325
|
-
instanceId;
|
|
1326
|
-
limit;
|
|
1327
|
-
constructor(args) {
|
|
1328
|
-
super(
|
|
1329
|
-
`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.`
|
|
1330
|
-
), this.name = "CascadeLimitError", this.instanceId = args.instanceId, this.limit = args.limit;
|
|
1331
|
-
}
|
|
1332
|
-
}
|
|
1333
1381
|
async function deployOrRollback(args) {
|
|
1334
1382
|
try {
|
|
1335
1383
|
await args.deploy();
|
|
@@ -1351,7 +1399,11 @@ async function deployOrRollback(args) {
|
|
|
1351
1399
|
reason: "rollback of the committed move failed"
|
|
1352
1400
|
});
|
|
1353
1401
|
}
|
|
1354
|
-
throw guardError
|
|
1402
|
+
throw guardError instanceof PartialGuardDeployError ? new WorkflowStateDivergedError({
|
|
1403
|
+
instanceId: args.instanceId,
|
|
1404
|
+
guardError,
|
|
1405
|
+
reason: "a multi-guard deploy partially applied; the rolled-back move left orphaned guard locks"
|
|
1406
|
+
}) : guardError;
|
|
1355
1407
|
}
|
|
1356
1408
|
}
|
|
1357
1409
|
function applyTaskStatusChange(args) {
|
|
@@ -1440,7 +1492,8 @@ function materializeInstance(base, mutation) {
|
|
|
1440
1492
|
...base,
|
|
1441
1493
|
currentStage: mutation.currentStage,
|
|
1442
1494
|
state: mutation.state,
|
|
1443
|
-
stages: mutation.stages
|
|
1495
|
+
stages: mutation.stages,
|
|
1496
|
+
effectsContext: mutation.effectsContext
|
|
1444
1497
|
};
|
|
1445
1498
|
}
|
|
1446
1499
|
async function persist(ctx, mutation) {
|
|
@@ -1455,7 +1508,21 @@ async function persist(ctx, mutation) {
|
|
|
1455
1508
|
tx.patch(ctx.client.patch(ctx.instance._id).set(set).ifRevisionId(ctx.instance._rev)), await tx.commit(), mutation.pendingCreates = [];
|
|
1456
1509
|
const actorForPriming = void 0;
|
|
1457
1510
|
for (const body of pendingCreates)
|
|
1458
|
-
|
|
1511
|
+
try {
|
|
1512
|
+
await primeInitialStage(ctx.client, body._id, actorForPriming, ctx.clientForGdr, ctx.clock), await cascadeAutoTransitions(
|
|
1513
|
+
ctx.client,
|
|
1514
|
+
body._id,
|
|
1515
|
+
actorForPriming,
|
|
1516
|
+
ctx.clientForGdr,
|
|
1517
|
+
ctx.clock
|
|
1518
|
+
), await propagateToAncestors(ctx.client, body._id, actorForPriming, ctx.clientForGdr, ctx.clock);
|
|
1519
|
+
} catch (cause) {
|
|
1520
|
+
throw cause instanceof WorkflowStateDivergedError ? cause : new WorkflowStateDivergedError({
|
|
1521
|
+
instanceId: ctx.instance._id,
|
|
1522
|
+
guardError: cause,
|
|
1523
|
+
reason: `spawned child "${body._id}" failed to settle after the spawn transaction committed`
|
|
1524
|
+
});
|
|
1525
|
+
}
|
|
1459
1526
|
const reloaded = await ctx.client.getDocument(ctx.instance._id);
|
|
1460
1527
|
if (!reloaded)
|
|
1461
1528
|
throw new Error(`Instance ${ctx.instance._id} disappeared after transaction commit`);
|
|
@@ -1720,6 +1787,7 @@ async function prepareChildInstance(args) {
|
|
|
1720
1787
|
selfId: childDocId,
|
|
1721
1788
|
tag: childTag,
|
|
1722
1789
|
workflowResource,
|
|
1790
|
+
definitionName: definition.name,
|
|
1723
1791
|
...inheritedPerspective !== void 0 ? { perspective: inheritedPerspective } : {}
|
|
1724
1792
|
},
|
|
1725
1793
|
randomKey
|
|
@@ -1766,6 +1834,28 @@ async function resolveBindings(args) {
|
|
|
1766
1834
|
resolved[key] = await runGroq(groq, args.params, args.snapshot);
|
|
1767
1835
|
return { ...resolved, ...args.staticInput };
|
|
1768
1836
|
}
|
|
1837
|
+
async function persistThenDeploy(ctx, mutation, deploy) {
|
|
1838
|
+
const spawned = mutation.pendingCreates.length > 0, committed = await persist(ctx, mutation);
|
|
1839
|
+
await deployOrRollback({
|
|
1840
|
+
client: ctx.client,
|
|
1841
|
+
instanceId: ctx.instance._id,
|
|
1842
|
+
committedRev: committed._rev,
|
|
1843
|
+
restore: instanceStateFields(ctx.instance),
|
|
1844
|
+
unset: ctx.instance.completedAt === void 0 ? ["completedAt"] : [],
|
|
1845
|
+
reversible: !spawned,
|
|
1846
|
+
deploy
|
|
1847
|
+
});
|
|
1848
|
+
}
|
|
1849
|
+
async function refreshStageGuards(ctx, mutation, stageName) {
|
|
1850
|
+
await deployStageGuards({
|
|
1851
|
+
client: ctx.client,
|
|
1852
|
+
clientForGdr: ctx.clientForGdr,
|
|
1853
|
+
instance: materializeInstance(ctx.instance, mutation),
|
|
1854
|
+
definition: ctx.definition,
|
|
1855
|
+
stageName,
|
|
1856
|
+
now: ctx.now
|
|
1857
|
+
});
|
|
1858
|
+
}
|
|
1769
1859
|
async function completeEffect(args) {
|
|
1770
1860
|
const { client, instanceId, effectKey, status, outputs, detail, error, durationMs, options } = args, ctx = await loadContext(client, instanceId, {
|
|
1771
1861
|
...options?.clock ? { clock: options.clock } : {},
|
|
@@ -1811,9 +1901,11 @@ async function commitCompleteEffect(ctx, effectKey, status, outputs, detail, err
|
|
|
1811
1901
|
const mutation = startMutation(ctx.instance);
|
|
1812
1902
|
mutation.pendingEffects = mutation.pendingEffects.filter((e) => e._key !== effectKey);
|
|
1813
1903
|
const ranAt = ctx.now;
|
|
1814
|
-
|
|
1904
|
+
mutation.effectHistory.push(
|
|
1815
1905
|
buildEffectHistoryEntry(pending, { status, ranAt, actor, detail, error, durationMs, outputs })
|
|
1816
|
-
)
|
|
1906
|
+
);
|
|
1907
|
+
const wroteEffectsContext = status === "done" && outputs !== void 0;
|
|
1908
|
+
return wroteEffectsContext && upsertEffectsContext(mutation, pending.name, outputs), mutation.history.push({
|
|
1817
1909
|
_key: randomKey(),
|
|
1818
1910
|
_type: "effectCompleted",
|
|
1819
1911
|
at: ranAt,
|
|
@@ -1823,7 +1915,11 @@ async function commitCompleteEffect(ctx, effectKey, status, outputs, detail, err
|
|
|
1823
1915
|
...outputs !== void 0 ? { outputs } : {},
|
|
1824
1916
|
...detail !== void 0 ? { detail } : {},
|
|
1825
1917
|
...actor !== void 0 ? { actor } : {}
|
|
1826
|
-
}), await
|
|
1918
|
+
}), await persistThenDeploy(
|
|
1919
|
+
ctx,
|
|
1920
|
+
mutation,
|
|
1921
|
+
() => wroteEffectsContext ? refreshStageGuards(ctx, mutation, ctx.instance.currentStage) : Promise.resolve()
|
|
1922
|
+
), { effectKey, status };
|
|
1827
1923
|
}
|
|
1828
1924
|
function buildQueuedEffect(effect, origin, params, actor, now) {
|
|
1829
1925
|
const key = randomKey(), pending = {
|
|
@@ -2283,28 +2379,6 @@ async function persistStageMove(ctx, mutation, exitedStage, enteredStage) {
|
|
|
2283
2379
|
now: ctx.now
|
|
2284
2380
|
});
|
|
2285
2381
|
}
|
|
2286
|
-
async function persistThenDeploy(ctx, mutation, deploy) {
|
|
2287
|
-
const spawned = mutation.pendingCreates.length > 0, committed = await persist(ctx, mutation);
|
|
2288
|
-
await deployOrRollback({
|
|
2289
|
-
client: ctx.client,
|
|
2290
|
-
instanceId: ctx.instance._id,
|
|
2291
|
-
committedRev: committed._rev,
|
|
2292
|
-
restore: instanceStateFields(ctx.instance),
|
|
2293
|
-
unset: ctx.instance.completedAt === void 0 ? ["completedAt"] : [],
|
|
2294
|
-
reversible: !spawned,
|
|
2295
|
-
deploy
|
|
2296
|
-
});
|
|
2297
|
-
}
|
|
2298
|
-
async function refreshStageGuards(ctx, mutation, stageName) {
|
|
2299
|
-
await deployStageGuards({
|
|
2300
|
-
client: ctx.client,
|
|
2301
|
-
clientForGdr: ctx.clientForGdr,
|
|
2302
|
-
instance: materializeInstance(ctx.instance, mutation),
|
|
2303
|
-
definition: ctx.definition,
|
|
2304
|
-
stageName,
|
|
2305
|
-
now: ctx.now
|
|
2306
|
-
});
|
|
2307
|
-
}
|
|
2308
2382
|
async function commitTransition(ctx, actor) {
|
|
2309
2383
|
if (isTerminal(ctx))
|
|
2310
2384
|
return { fired: !1 };
|
|
@@ -3261,6 +3335,7 @@ const workflow = {
|
|
|
3261
3335
|
selfId: id,
|
|
3262
3336
|
tag,
|
|
3263
3337
|
workflowResource,
|
|
3338
|
+
definitionName: definition.name,
|
|
3264
3339
|
...perspective !== void 0 ? { perspective } : {}
|
|
3265
3340
|
},
|
|
3266
3341
|
randomKey
|
|
@@ -4151,6 +4226,8 @@ export {
|
|
|
4151
4226
|
HISTORY_DISPLAY,
|
|
4152
4227
|
MutationGuardDeniedError,
|
|
4153
4228
|
OP_DISPLAY,
|
|
4229
|
+
PartialGuardDeployError,
|
|
4230
|
+
RequiredStateNotProvidedError,
|
|
4154
4231
|
STATE_SLOT_DISPLAY,
|
|
4155
4232
|
WORKFLOW_DEFINITION_TYPE,
|
|
4156
4233
|
WORKFLOW_INSTANCE_TYPE,
|
|
@@ -4186,6 +4263,7 @@ export {
|
|
|
4186
4263
|
instanceGuardQuery,
|
|
4187
4264
|
isDefinitionUnchanged,
|
|
4188
4265
|
isGdr,
|
|
4266
|
+
isStartableDefinition,
|
|
4189
4267
|
isTerminalStage,
|
|
4190
4268
|
lakeGuardId,
|
|
4191
4269
|
parseGdr,
|