@sanity/workflow-engine 0.1.0 → 0.3.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 +431 -0
- package/dist/_chunks-cjs/schema.cjs.map +1 -0
- package/dist/_chunks-es/schema.js +416 -0
- package/dist/_chunks-es/schema.js.map +1 -0
- package/dist/define.cjs +57 -435
- package/dist/define.cjs.map +1 -1
- package/dist/define.d.cts +5035 -2647
- package/dist/define.d.ts +5035 -2647
- package/dist/define.js +34 -427
- package/dist/define.js.map +1 -1
- package/dist/index.cjs +1737 -1289
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6141 -3196
- package/dist/index.d.ts +6141 -3196
- package/dist/index.js +1624 -1191
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import { parse, evaluate } from "groq-js";
|
|
2
|
-
import {
|
|
2
|
+
import { WORKFLOW_DEFINITION_TYPE } from "./_chunks-es/schema.js";
|
|
3
|
+
import * as v from "valibot";
|
|
3
4
|
function validateDefinition(definition) {
|
|
4
|
-
const
|
|
5
|
+
const v2 = createDefinitionValidator();
|
|
5
6
|
for (const slot of definition.state ?? [])
|
|
6
|
-
|
|
7
|
+
v2.checkSlot(slot, `workflow.state "${slot.id}"`);
|
|
7
8
|
for (const p of definition.predicates ?? [])
|
|
8
|
-
p.groq &&
|
|
9
|
+
p.groq && v2.checkFilter(p.groq, `predicate "${p.id}"`);
|
|
9
10
|
for (const stage of definition.stages)
|
|
10
|
-
validateStage(
|
|
11
|
-
if (
|
|
11
|
+
validateStage(v2, stage);
|
|
12
|
+
if (v2.errors.length > 0)
|
|
12
13
|
throw new Error(
|
|
13
|
-
`defineWorkflow("${definition.workflowId}", v${definition.version}): ${
|
|
14
|
-
` +
|
|
14
|
+
`defineWorkflow("${definition.workflowId}", v${definition.version}): ${v2.errors.length} validation error${v2.errors.length === 1 ? "" : "s"}:
|
|
15
|
+
` + v2.errors.join(`
|
|
15
16
|
`)
|
|
16
17
|
);
|
|
17
18
|
}
|
|
@@ -30,31 +31,29 @@ function createDefinitionValidator() {
|
|
|
30
31
|
};
|
|
31
32
|
return { errors, tryParse, checkFilter: (groq, where) => {
|
|
32
33
|
tryParse(groq, where), rejectTypeScan(groq, where);
|
|
33
|
-
}, checkSlot: (slot,
|
|
34
|
-
slot.source.kind === "
|
|
35
|
-
` \xB7 ${scopeLabel} slot "${slot.id}": source.kind="computed" is reserved (not implemented in v0.1)`
|
|
36
|
-
), slot.source.kind === "query" && tryParse(slot.source.query, `${slotLabel}.query`);
|
|
34
|
+
}, checkSlot: (slot, slotLabel) => {
|
|
35
|
+
slot.source.kind === "query" && tryParse(slot.source.query, `${slotLabel}.query`);
|
|
37
36
|
} };
|
|
38
37
|
}
|
|
39
|
-
function validateStage(
|
|
38
|
+
function validateStage(v2, stage) {
|
|
40
39
|
for (const slot of stage.state ?? [])
|
|
41
|
-
|
|
42
|
-
typeof stage.completion == "string" &&
|
|
40
|
+
v2.checkSlot(slot, `stage "${stage.id}".state "${slot.id}"`);
|
|
41
|
+
typeof stage.completion == "string" && v2.tryParse(stage.completion, `stage "${stage.id}".completion`);
|
|
43
42
|
for (const t of stage.transitions ?? [])
|
|
44
|
-
typeof t.filter == "string" &&
|
|
43
|
+
typeof t.filter == "string" && v2.checkFilter(t.filter, `transition ${stage.id} \u2192 ${t.to} filter`);
|
|
45
44
|
for (const task of stage.tasks ?? [])
|
|
46
|
-
validateTask(
|
|
45
|
+
validateTask(v2, stage.id, task);
|
|
47
46
|
}
|
|
48
|
-
function validateTask(
|
|
47
|
+
function validateTask(v2, stageId, task) {
|
|
49
48
|
const where = `stage "${stageId}" task "${task.id}"`;
|
|
50
49
|
for (const slot of task.state ?? [])
|
|
51
|
-
|
|
52
|
-
typeof task.filter == "string" &&
|
|
50
|
+
v2.checkSlot(slot, `${where}.state "${slot.id}"`);
|
|
51
|
+
typeof task.filter == "string" && v2.checkFilter(task.filter, `${where}.filter`), typeof task.completeWhen == "string" && v2.checkFilter(task.completeWhen, `${where}.completeWhen`);
|
|
53
52
|
for (const a of task.actions ?? [])
|
|
54
|
-
typeof a.filter == "string" &&
|
|
55
|
-
task.spawns?.forEach?.groq &&
|
|
53
|
+
typeof a.filter == "string" && v2.checkFilter(a.filter, `task "${task.id}" action "${a.id}".filter`);
|
|
54
|
+
task.spawns?.forEach?.groq && v2.tryParse(task.spawns.forEach.groq, `task "${task.id}".spawns.forEach.groq`);
|
|
56
55
|
}
|
|
57
|
-
const KNOWN_SCHEMES = /* @__PURE__ */ new Set(["dataset", "canvas", "media-library", "dashboard"]);
|
|
56
|
+
const wallClock = () => (/* @__PURE__ */ new Date()).toISOString(), KNOWN_SCHEMES = /* @__PURE__ */ new Set(["dataset", "canvas", "media-library", "dashboard"]);
|
|
58
57
|
function parseGdr(uri) {
|
|
59
58
|
const colon = uri.indexOf(":");
|
|
60
59
|
if (colon < 0)
|
|
@@ -121,6 +120,9 @@ function gdrFromResource(res, documentId) {
|
|
|
121
120
|
}
|
|
122
121
|
return gdrUri({ scheme: res.type, resourceId: res.id, documentId });
|
|
123
122
|
}
|
|
123
|
+
function selfGdr(doc) {
|
|
124
|
+
return gdrFromResource(doc.workflowResource, doc._id);
|
|
125
|
+
}
|
|
124
126
|
function isGdrUri(value) {
|
|
125
127
|
if (typeof value != "string") return !1;
|
|
126
128
|
try {
|
|
@@ -135,15 +137,15 @@ function gdrRef(res, documentId, type) {
|
|
|
135
137
|
function isGdr(value) {
|
|
136
138
|
return typeof value == "object" && value !== null && typeof value.id == "string" && typeof value.type == "string";
|
|
137
139
|
}
|
|
138
|
-
function buildParams(instance, extra) {
|
|
140
|
+
function buildParams(instance, now, extra) {
|
|
139
141
|
return {
|
|
140
|
-
self:
|
|
142
|
+
self: selfGdr(instance),
|
|
141
143
|
state: stateMap(instance.state ?? []),
|
|
142
144
|
parent: instance.ancestors.at(-1)?.id ?? null,
|
|
143
145
|
ancestors: instance.ancestors.map((a) => a.id),
|
|
144
146
|
/** Current stage id — bind for filters that scope to the current stage. */
|
|
145
147
|
stage: instance.currentStageId,
|
|
146
|
-
now
|
|
148
|
+
now,
|
|
147
149
|
...extra
|
|
148
150
|
};
|
|
149
151
|
}
|
|
@@ -167,8 +169,8 @@ function stripStateForLake(value) {
|
|
|
167
169
|
if (Array.isArray(value)) return value.map(stripStateForLake);
|
|
168
170
|
if (typeof value == "object") {
|
|
169
171
|
const out = {};
|
|
170
|
-
for (const [k,
|
|
171
|
-
out[k] = stripStateForLake(
|
|
172
|
+
for (const [k, v2] of Object.entries(value))
|
|
173
|
+
out[k] = stripStateForLake(v2);
|
|
172
174
|
return out;
|
|
173
175
|
}
|
|
174
176
|
return value;
|
|
@@ -176,9 +178,94 @@ function stripStateForLake(value) {
|
|
|
176
178
|
function bareId(id) {
|
|
177
179
|
return isGdrUri(id) ? extractDocumentId(id) : id;
|
|
178
180
|
}
|
|
179
|
-
function
|
|
180
|
-
|
|
181
|
-
|
|
181
|
+
function getPath(value, path) {
|
|
182
|
+
let current = value;
|
|
183
|
+
for (const part of path.split(".")) {
|
|
184
|
+
if (current == null || typeof current != "object") return;
|
|
185
|
+
current = current[part];
|
|
186
|
+
}
|
|
187
|
+
return current;
|
|
188
|
+
}
|
|
189
|
+
function readStateSlot(instance, scope, slotId, stageId, taskId) {
|
|
190
|
+
if (scope === "workflow")
|
|
191
|
+
return slotValue$1(instance.state?.find((s) => s.id === slotId));
|
|
192
|
+
const stageEntry = instance.stages.find(
|
|
193
|
+
(s) => s.id === (stageId ?? instance.currentStageId) && s.exitedAt === void 0
|
|
194
|
+
);
|
|
195
|
+
if (stageEntry === void 0) return;
|
|
196
|
+
if (scope === "stage")
|
|
197
|
+
return slotValue$1(stageEntry.state?.find((s) => s.id === slotId));
|
|
198
|
+
const taskEntry = stageEntry.tasks.find((t) => t.id === taskId);
|
|
199
|
+
return slotValue$1(taskEntry?.state?.find((s) => s.id === slotId));
|
|
200
|
+
}
|
|
201
|
+
function slotValue$1(slot) {
|
|
202
|
+
if (slot != null && typeof slot == "object")
|
|
203
|
+
return slot.value;
|
|
204
|
+
}
|
|
205
|
+
function resolveStaticSource(src, ctx) {
|
|
206
|
+
switch (src.source) {
|
|
207
|
+
case "literal":
|
|
208
|
+
return { handled: !0, value: src.value };
|
|
209
|
+
case "param":
|
|
210
|
+
return { handled: !0, value: ctx.params?.[src.paramId] };
|
|
211
|
+
case "actor":
|
|
212
|
+
return { handled: !0, value: ctx.actor };
|
|
213
|
+
case "now":
|
|
214
|
+
return { handled: !0, value: ctx.now };
|
|
215
|
+
default:
|
|
216
|
+
return { handled: !1 };
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
function resolveSource(src, ctx) {
|
|
220
|
+
const staticValue = resolveStaticSource(src, ctx);
|
|
221
|
+
if (staticValue.handled) return staticValue.value;
|
|
222
|
+
switch (src.source) {
|
|
223
|
+
case "self":
|
|
224
|
+
return ctx.instance._id;
|
|
225
|
+
case "stageId":
|
|
226
|
+
return ctx.stageId ?? ctx.instance.currentStageId;
|
|
227
|
+
case "stateRead":
|
|
228
|
+
return resolveStateRead(src, ctx);
|
|
229
|
+
case "effectOutput":
|
|
230
|
+
return resolveEffectOutput(src, ctx);
|
|
231
|
+
case "object":
|
|
232
|
+
return resolveObject(src, ctx);
|
|
233
|
+
case "row":
|
|
234
|
+
case "parentState":
|
|
235
|
+
return;
|
|
236
|
+
default:
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
function resolveStateRead(src, ctx) {
|
|
241
|
+
const value = readStateSlot(ctx.instance, src.scope, src.slotId, ctx.stageId, ctx.taskId);
|
|
242
|
+
return src.path !== void 0 ? getPath(value, src.path) : value;
|
|
243
|
+
}
|
|
244
|
+
function resolveEffectOutput(src, ctx) {
|
|
245
|
+
const entry = ctx.instance.effectsContext.find((e) => e.id === src.contextId);
|
|
246
|
+
if (entry === void 0) return null;
|
|
247
|
+
const value = src.path !== void 0 ? getPath(entry.value, src.path) : entry.value;
|
|
248
|
+
return value === void 0 ? null : value;
|
|
249
|
+
}
|
|
250
|
+
function resolveObject(src, ctx) {
|
|
251
|
+
const out = {};
|
|
252
|
+
for (const [field, fieldSrc] of Object.entries(src.fields))
|
|
253
|
+
out[field] = resolveSource(fieldSrc, ctx);
|
|
254
|
+
return out;
|
|
255
|
+
}
|
|
256
|
+
async function resolveBindings(args) {
|
|
257
|
+
const { bindings, staticInput, instance, now, params, actor } = args, resolved = {};
|
|
258
|
+
if (bindings) {
|
|
259
|
+
const ctx = {
|
|
260
|
+
instance,
|
|
261
|
+
now,
|
|
262
|
+
...params !== void 0 ? { params } : {},
|
|
263
|
+
...actor !== void 0 ? { actor } : {}
|
|
264
|
+
};
|
|
265
|
+
for (const [key, src] of Object.entries(bindings))
|
|
266
|
+
resolved[key] = resolveSource(src, ctx);
|
|
267
|
+
}
|
|
268
|
+
return { ...resolved, ...staticInput };
|
|
182
269
|
}
|
|
183
270
|
async function evaluateFilter(args) {
|
|
184
271
|
const { filter, definition, snapshot, params } = args;
|
|
@@ -226,7 +313,7 @@ function matchesParamType(type, value) {
|
|
|
226
313
|
case "boolean":
|
|
227
314
|
return typeof value == "boolean";
|
|
228
315
|
case "string[]":
|
|
229
|
-
return Array.isArray(value) && value.every((
|
|
316
|
+
return Array.isArray(value) && value.every((v2) => typeof v2 == "string");
|
|
230
317
|
case "reference":
|
|
231
318
|
return isGdr(value);
|
|
232
319
|
}
|
|
@@ -234,6 +321,183 @@ function matchesParamType(type, value) {
|
|
|
234
321
|
function describeValue(value) {
|
|
235
322
|
return Array.isArray(value) ? "array" : value === null ? "null" : typeof value;
|
|
236
323
|
}
|
|
324
|
+
const GUARD_DOC_TYPE = "temp.system.guard";
|
|
325
|
+
class MutationGuardDeniedError extends Error {
|
|
326
|
+
denied;
|
|
327
|
+
documentId;
|
|
328
|
+
action;
|
|
329
|
+
constructor(args) {
|
|
330
|
+
const ids = args.denied.map((d) => d.guardId).join(", ");
|
|
331
|
+
super(`Mutation on "${args.documentId}" (${args.action}) denied by guard(s) [${ids}]`), this.name = "MutationGuardDeniedError", this.denied = args.denied, this.documentId = args.documentId, this.action = args.action;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
function lakeGuardId(args) {
|
|
335
|
+
return `${GUARD_DOC_TYPE}.${args.instanceDocId}.${args.stageId}.${args.index}`;
|
|
336
|
+
}
|
|
337
|
+
function compileGuard(args) {
|
|
338
|
+
return {
|
|
339
|
+
_id: args.id,
|
|
340
|
+
_type: GUARD_DOC_TYPE,
|
|
341
|
+
resourceType: args.resourceType,
|
|
342
|
+
resourceId: args.resourceId,
|
|
343
|
+
owner: args.owner,
|
|
344
|
+
sourceInstanceId: args.sourceInstanceId,
|
|
345
|
+
sourceDefinitionId: args.sourceDefinitionId,
|
|
346
|
+
sourceStageId: args.sourceStageId,
|
|
347
|
+
...args.name !== void 0 ? { name: args.name } : {},
|
|
348
|
+
...args.description !== void 0 ? { description: args.description } : {},
|
|
349
|
+
match: args.match,
|
|
350
|
+
predicate: args.predicate,
|
|
351
|
+
metadata: args.metadata
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
function toGroqJsPredicate(predicate) {
|
|
355
|
+
return predicate.replace(new RegExp("(?<!\\$)\\bguard\\.", "g"), "$guard.").replace(new RegExp("(?<!\\$)\\bmutation\\.", "g"), "$mutation.");
|
|
356
|
+
}
|
|
357
|
+
function globMatch(pattern, value) {
|
|
358
|
+
if (!pattern.includes("*")) return pattern === value;
|
|
359
|
+
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
360
|
+
return new RegExp(`^${escaped}$`).test(value);
|
|
361
|
+
}
|
|
362
|
+
function guardMatches(guard, doc, action) {
|
|
363
|
+
const m = guard.match;
|
|
364
|
+
if (!m.actions.includes(action) || m.types && m.types.length > 0 && !(doc.type !== void 0 && m.types.includes(doc.type)))
|
|
365
|
+
return !1;
|
|
366
|
+
if (m.idRefs && m.idRefs.length > 0 || m.idPatterns && m.idPatterns.length > 0) {
|
|
367
|
+
const byRef = m.idRefs?.includes(doc.id) ?? !1, byPattern = m.idPatterns?.some((p) => globMatch(p, doc.id)) ?? !1;
|
|
368
|
+
if (!byRef && !byPattern) return !1;
|
|
369
|
+
}
|
|
370
|
+
return !0;
|
|
371
|
+
}
|
|
372
|
+
async function evaluateMutationGuard(args) {
|
|
373
|
+
const { guard, context } = args;
|
|
374
|
+
if (guard.predicate === "") return !1;
|
|
375
|
+
const params = { guard, mutation: { action: context.action } };
|
|
376
|
+
try {
|
|
377
|
+
const tree = parse(toGroqJsPredicate(guard.predicate), { mode: "delta", params });
|
|
378
|
+
return await (await evaluate(tree, {
|
|
379
|
+
before: context.before,
|
|
380
|
+
after: context.after,
|
|
381
|
+
params,
|
|
382
|
+
...context.identity !== void 0 ? { identity: context.identity } : {}
|
|
383
|
+
})).get() === !0;
|
|
384
|
+
} catch {
|
|
385
|
+
return !1;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
async function denyingGuards(args) {
|
|
389
|
+
const { guards, doc, context } = args, denied = [];
|
|
390
|
+
for (const guard of guards)
|
|
391
|
+
guardMatches(guard, doc, context.action) && (await evaluateMutationGuard({ guard, context }) || denied.push(guard));
|
|
392
|
+
return denied;
|
|
393
|
+
}
|
|
394
|
+
function resourceOf(p) {
|
|
395
|
+
return p.scheme === "dataset" ? { type: "dataset", id: `${p.projectId}.${p.dataset}` } : { type: p.scheme, id: p.resourceId ?? "" };
|
|
396
|
+
}
|
|
397
|
+
function resolveIdRefTarget(src, ctx) {
|
|
398
|
+
const value = resolveSource(src, ctx);
|
|
399
|
+
if (typeof value == "string" && isGdrUri(value))
|
|
400
|
+
return { parsed: parseGdr(value) };
|
|
401
|
+
if (value && typeof value == "object") {
|
|
402
|
+
const v2 = value;
|
|
403
|
+
if (typeof v2.id == "string" && isGdrUri(v2.id))
|
|
404
|
+
return typeof v2.type == "string" ? { parsed: parseGdr(v2.id), type: v2.type } : { parsed: parseGdr(v2.id) };
|
|
405
|
+
}
|
|
406
|
+
return null;
|
|
407
|
+
}
|
|
408
|
+
function resolveIdRefTargets(idRefs, ctx) {
|
|
409
|
+
const targets = [];
|
|
410
|
+
for (const src of idRefs ?? []) {
|
|
411
|
+
const target = resolveIdRefTarget(src, ctx);
|
|
412
|
+
if (target === null) return null;
|
|
413
|
+
targets.push(target);
|
|
414
|
+
}
|
|
415
|
+
return targets.length === 0 ? null : targets;
|
|
416
|
+
}
|
|
417
|
+
function assertSingleResource(targets) {
|
|
418
|
+
const resource = resourceOf(targets[0].parsed);
|
|
419
|
+
for (const g of targets) {
|
|
420
|
+
const r = resourceOf(g.parsed);
|
|
421
|
+
if (r.type !== resource.type || r.id !== resource.id)
|
|
422
|
+
throw new Error(
|
|
423
|
+
`Guard targets span multiple resources (${resource.type}:${resource.id} vs ${r.type}:${r.id}); a guard is single-resource.`
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
return resource;
|
|
427
|
+
}
|
|
428
|
+
function bareIdRefs(targets) {
|
|
429
|
+
return targets.flatMap((g) => [g.parsed.documentId, `drafts.${g.parsed.documentId}`]);
|
|
430
|
+
}
|
|
431
|
+
function resolveMatchTypes(targets, authorTypes) {
|
|
432
|
+
if (authorTypes !== void 0) return authorTypes;
|
|
433
|
+
const inferred = [...new Set(targets.map((g) => g.type).filter((t) => !!t))];
|
|
434
|
+
return inferred.length > 0 ? inferred : void 0;
|
|
435
|
+
}
|
|
436
|
+
function resolveMetadata(metadata, ctx) {
|
|
437
|
+
const out = {};
|
|
438
|
+
for (const [k, src] of Object.entries(metadata ?? {}))
|
|
439
|
+
out[k] = resolveSource(src, ctx);
|
|
440
|
+
return out;
|
|
441
|
+
}
|
|
442
|
+
const GUARD_OWNER = "robot:workflow-engine", GUARD_LIFTED_PREDICATE = "true";
|
|
443
|
+
function resolveGuard(guard, index, instance, stageId, now) {
|
|
444
|
+
const ctx = { instance, stageId, now }, targets = resolveIdRefTargets(guard.match.idRefs, ctx);
|
|
445
|
+
if (targets === null) return null;
|
|
446
|
+
const resource = assertSingleResource(targets), types = resolveMatchTypes(targets, guard.match.types);
|
|
447
|
+
return { doc: compileGuard({
|
|
448
|
+
id: lakeGuardId({ instanceDocId: instance._id, stageId, index }),
|
|
449
|
+
resourceType: resource.type,
|
|
450
|
+
resourceId: resource.id,
|
|
451
|
+
owner: GUARD_OWNER,
|
|
452
|
+
sourceInstanceId: instance._id,
|
|
453
|
+
sourceDefinitionId: instance.workflowId,
|
|
454
|
+
sourceStageId: stageId,
|
|
455
|
+
...guard.name !== void 0 ? { name: guard.name } : {},
|
|
456
|
+
...guard.description !== void 0 ? { description: guard.description } : {},
|
|
457
|
+
match: {
|
|
458
|
+
...types !== void 0 ? { types } : {},
|
|
459
|
+
idRefs: bareIdRefs(targets),
|
|
460
|
+
...guard.match.idPatterns !== void 0 ? { idPatterns: guard.match.idPatterns } : {},
|
|
461
|
+
actions: guard.match.actions
|
|
462
|
+
},
|
|
463
|
+
predicate: guard.predicate ?? "",
|
|
464
|
+
metadata: resolveMetadata(guard.metadata, ctx)
|
|
465
|
+
}), routeGdr: targets[0].parsed };
|
|
466
|
+
}
|
|
467
|
+
async function upsertGuard(client, doc) {
|
|
468
|
+
if (!await client.getDocument(doc._id)) {
|
|
469
|
+
await client.create(doc);
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
const { _id, _type, _rev, _createdAt, _updatedAt, ...body } = doc;
|
|
473
|
+
await client.patch(doc._id).set(body).commit();
|
|
474
|
+
}
|
|
475
|
+
function resolvedStageGuards(args) {
|
|
476
|
+
const stage = args.definition.stages.find((s) => s.id === args.stageId), out = [];
|
|
477
|
+
for (const [index, guard] of (stage?.guards ?? []).entries()) {
|
|
478
|
+
const resolved = resolveGuard(guard, index, args.instance, args.stageId, args.now);
|
|
479
|
+
resolved !== null && out.push({ client: args.clientForGdr(resolved.routeGdr), doc: resolved.doc });
|
|
480
|
+
}
|
|
481
|
+
return out;
|
|
482
|
+
}
|
|
483
|
+
async function committedStageId(args) {
|
|
484
|
+
return (await args.client.getDocument(args.instance._id))?.currentStageId;
|
|
485
|
+
}
|
|
486
|
+
async function deployStageGuards(args) {
|
|
487
|
+
if (await committedStageId(args) === args.stageId)
|
|
488
|
+
for (const { client, doc } of resolvedStageGuards(args))
|
|
489
|
+
await upsertGuard(client, doc);
|
|
490
|
+
}
|
|
491
|
+
async function retractStageGuards(args) {
|
|
492
|
+
const live = await committedStageId(args);
|
|
493
|
+
if (!(live === void 0 || live === args.stageId))
|
|
494
|
+
for (const { client, doc } of resolvedStageGuards(args))
|
|
495
|
+
await client.getDocument(doc._id) && await client.patch(doc._id).set({ predicate: GUARD_LIFTED_PREDICATE }).commit();
|
|
496
|
+
}
|
|
497
|
+
function randomKey(length = 12) {
|
|
498
|
+
const bytes = new Uint8Array(length);
|
|
499
|
+
return globalThis.crypto.getRandomValues(bytes), [...bytes].map((b) => b.toString(16).padStart(2, "0")).join("").slice(0, length);
|
|
500
|
+
}
|
|
237
501
|
function buildSnapshot(args) {
|
|
238
502
|
const docs = [], knownIds = /* @__PURE__ */ new Set();
|
|
239
503
|
for (const { doc, resource } of args.docs) {
|
|
@@ -247,74 +511,100 @@ function restampForSnapshot(doc, gdrUri2, resource) {
|
|
|
247
511
|
}
|
|
248
512
|
function rewriteRefsRecursive(value, resource) {
|
|
249
513
|
if (Array.isArray(value))
|
|
250
|
-
return value.map((
|
|
514
|
+
return value.map((v2) => rewriteRefsRecursive(v2, resource));
|
|
251
515
|
if (value === null || typeof value != "object") return value;
|
|
252
516
|
const obj = value, out = {};
|
|
253
|
-
for (const [k,
|
|
254
|
-
k === "_ref" && typeof
|
|
517
|
+
for (const [k, v2] of Object.entries(obj))
|
|
518
|
+
k === "_ref" && typeof v2 == "string" ? out[k] = v2.includes(":") ? v2 : gdrFromResource(resource, v2) : out[k] = rewriteRefsRecursive(v2, resource);
|
|
255
519
|
return out;
|
|
256
520
|
}
|
|
521
|
+
function findOpenStageEntry(host) {
|
|
522
|
+
return host.stages.find((s) => s.id === host.currentStageId && s.exitedAt === void 0);
|
|
523
|
+
}
|
|
524
|
+
function collectWatchRefs(instance) {
|
|
525
|
+
const stage = findOpenStageEntry(instance);
|
|
526
|
+
return [
|
|
527
|
+
gdrRef(instance.workflowResource, instance._id, instance._type),
|
|
528
|
+
...instance.ancestors,
|
|
529
|
+
...slotDocRefs(instance.state),
|
|
530
|
+
...slotDocRefs(stage?.state),
|
|
531
|
+
...slotReleaseRefs(instance.state),
|
|
532
|
+
...slotReleaseRefs(stage?.state)
|
|
533
|
+
];
|
|
534
|
+
}
|
|
535
|
+
function readsRaw(ref) {
|
|
536
|
+
return ref.type === "workflow.instance" || ref.type === "system.release";
|
|
537
|
+
}
|
|
538
|
+
function contentReleaseName(args) {
|
|
539
|
+
const { ref, perspective } = args;
|
|
540
|
+
if (!readsRaw(ref) && Array.isArray(perspective))
|
|
541
|
+
return perspective.find((entry) => entry !== "drafts" && entry !== "published" && entry !== "raw");
|
|
542
|
+
}
|
|
543
|
+
function subscriptionDocumentsForInstance(instance) {
|
|
544
|
+
const seen = /* @__PURE__ */ new Set(), documents = [];
|
|
545
|
+
for (const ref of collectWatchRefs(instance))
|
|
546
|
+
!isGdrUri(ref.id) || seen.has(ref.id) || (seen.add(ref.id), documents.push({ ...parseGdr(ref.id), globalDocumentId: ref.id, type: ref.type }));
|
|
547
|
+
return {
|
|
548
|
+
documents,
|
|
549
|
+
...instance.perspective !== void 0 ? { perspective: instance.perspective } : {}
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
function slotDocRefs(slots) {
|
|
553
|
+
return slotEntries(slots).flatMap((slot) => slot._type === "workflow.state.doc.ref" ? isGdr(slot.value) ? [slot.value] : [] : slot._type === "workflow.state.doc.refs" ? Array.isArray(slot.value) ? slot.value.filter(isGdr) : [] : []);
|
|
554
|
+
}
|
|
555
|
+
function slotReleaseRefs(slots) {
|
|
556
|
+
return slotEntries(slots).flatMap(
|
|
557
|
+
(slot) => slot._type === "workflow.state.release.ref" && isGdr(slot.value) ? [slot.value] : []
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
function slotEntries(slots) {
|
|
561
|
+
return Array.isArray(slots) ? slots.filter(
|
|
562
|
+
(slot) => !!slot && typeof slot == "object"
|
|
563
|
+
) : [];
|
|
564
|
+
}
|
|
257
565
|
async function hydrateSnapshot(args) {
|
|
258
|
-
const { client, clientForGdr, instance,
|
|
566
|
+
const { client, clientForGdr, instance, overlay } = args, loaded = [], visited = /* @__PURE__ */ new Set(), loadInto = async (uri, perspective) => {
|
|
259
567
|
if (visited.has(uri)) return;
|
|
260
|
-
const
|
|
568
|
+
const held = overlay?.get(uri);
|
|
569
|
+
if (held !== void 0) {
|
|
570
|
+
loaded.push(held), visited.add(uri);
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
const fetched = await loadByGdr(
|
|
574
|
+
client,
|
|
575
|
+
clientForGdr,
|
|
576
|
+
instance.workflowResource,
|
|
577
|
+
uri,
|
|
578
|
+
perspective
|
|
579
|
+
);
|
|
261
580
|
fetched && (loaded.push(fetched), visited.add(uri));
|
|
262
581
|
};
|
|
263
|
-
loaded.push({ doc: instance, resource: instance.workflowResource }), visited.add(
|
|
264
|
-
for (const
|
|
265
|
-
await loadInto(
|
|
266
|
-
for (const uri of collectSlotDocUris(instance.state))
|
|
267
|
-
await loadInto(uri);
|
|
268
|
-
if (definition) {
|
|
269
|
-
const stageEntry = instance.stages.find(
|
|
270
|
-
(s) => s.id === instance.currentStageId && s.exitedAt === void 0
|
|
271
|
-
);
|
|
272
|
-
for (const uri of collectSlotDocUris(stageEntry?.state))
|
|
273
|
-
await loadInto(uri);
|
|
274
|
-
}
|
|
582
|
+
loaded.push({ doc: instance, resource: instance.workflowResource }), visited.add(selfGdr(instance));
|
|
583
|
+
for (const ref of collectWatchRefs(instance))
|
|
584
|
+
await loadInto(ref.id, readsRaw(ref) ? void 0 : instance.perspective);
|
|
275
585
|
return buildSnapshot({ docs: loaded });
|
|
276
586
|
}
|
|
277
|
-
async function loadByGdr(defaultClient, clientForGdr, defaultResource, uri) {
|
|
587
|
+
async function loadByGdr(defaultClient, clientForGdr, defaultResource, uri, perspective) {
|
|
278
588
|
let parsed;
|
|
279
589
|
try {
|
|
280
590
|
parsed = parseGdr(uri);
|
|
281
591
|
} catch {
|
|
282
|
-
const doc2 = await defaultClient
|
|
592
|
+
const doc2 = await readDoc(defaultClient, uri, perspective);
|
|
283
593
|
return doc2 ? { doc: doc2, resource: defaultResource } : null;
|
|
284
594
|
}
|
|
285
|
-
const
|
|
595
|
+
const routed = clientForGdr(parsed), doc = await readDoc(routed, parsed.documentId, perspective);
|
|
286
596
|
return doc ? { doc, resource: resourceFromParsed(parsed) } : null;
|
|
287
597
|
}
|
|
598
|
+
async function readDoc(client, id, perspective) {
|
|
599
|
+
return perspective === void 0 ? await client.getDocument(id) ?? null : await client.fetch("*[_id == $id][0]", { id }, { perspective }) ?? null;
|
|
600
|
+
}
|
|
288
601
|
function resourceFromParsed(parsed) {
|
|
289
602
|
return parsed.scheme === "dataset" ? { type: "dataset", id: `${parsed.projectId}.${parsed.dataset}` } : { type: parsed.scheme, id: parsed.resourceId ?? "" };
|
|
290
603
|
}
|
|
291
604
|
function collectSlotDocUris(resolvedStateSlots) {
|
|
292
|
-
return
|
|
293
|
-
}
|
|
294
|
-
function slotDocUris(slot) {
|
|
295
|
-
if (!slot || typeof slot != "object") return [];
|
|
296
|
-
const s = slot;
|
|
297
|
-
if (s._type === "workflow.state.doc.ref") {
|
|
298
|
-
const v = s.value, id = refId(v);
|
|
299
|
-
return id !== void 0 ? [id] : [];
|
|
300
|
-
}
|
|
301
|
-
if (s._type === "workflow.state.doc.refs") {
|
|
302
|
-
const v = s.value;
|
|
303
|
-
return Array.isArray(v) ? v.map(refId).filter((id) => id !== void 0) : [];
|
|
304
|
-
}
|
|
305
|
-
return [];
|
|
306
|
-
}
|
|
307
|
-
function refId(ref) {
|
|
308
|
-
return ref && typeof ref.id == "string" ? ref.id : void 0;
|
|
309
|
-
}
|
|
310
|
-
function getPath(value, path) {
|
|
311
|
-
let current = value;
|
|
312
|
-
for (const part of path.split(".")) {
|
|
313
|
-
if (current == null || typeof current != "object") return;
|
|
314
|
-
current = current[part];
|
|
315
|
-
}
|
|
316
|
-
return current;
|
|
605
|
+
return slotDocRefs(resolvedStateSlots).map((ref) => ref.id);
|
|
317
606
|
}
|
|
607
|
+
const WORKFLOW_INSTANCE_TYPE = "workflow.instance";
|
|
318
608
|
class SlotValueShapeError extends Error {
|
|
319
609
|
slotType;
|
|
320
610
|
slotId;
|
|
@@ -324,50 +614,61 @@ class SlotValueShapeError extends Error {
|
|
|
324
614
|
super(`Slot ${args.mode} shape invalid for "${args.slotId}" (${args.slotType}): ${issueText}`), this.name = "SlotValueShapeError", this.slotType = args.slotType, this.slotId = args.slotId, this.issues = args.issues;
|
|
325
615
|
}
|
|
326
616
|
}
|
|
327
|
-
const GdrShape =
|
|
328
|
-
id:
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
type:
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
617
|
+
const GdrShape = v.looseObject({
|
|
618
|
+
id: v.pipe(
|
|
619
|
+
v.string(),
|
|
620
|
+
v.check((s) => isGdrUri(s), "must be a GDR URI")
|
|
621
|
+
),
|
|
622
|
+
type: v.pipe(v.string(), v.minLength(1))
|
|
623
|
+
}), ReleaseRefShape = v.looseObject({
|
|
624
|
+
id: v.pipe(
|
|
625
|
+
v.string(),
|
|
626
|
+
v.check((s) => isGdrUri(s), "must be a GDR URI")
|
|
627
|
+
),
|
|
628
|
+
type: v.literal("system.release"),
|
|
629
|
+
releaseName: v.pipe(v.string(), v.minLength(1))
|
|
630
|
+
}), ActorShape = v.looseObject({
|
|
631
|
+
kind: v.picklist(["user", "ai", "system"]),
|
|
632
|
+
id: v.pipe(v.string(), v.minLength(1)),
|
|
633
|
+
roles: v.optional(v.array(v.string())),
|
|
634
|
+
onBehalfOf: v.optional(v.string())
|
|
635
|
+
}), AssigneeShape = v.union([
|
|
636
|
+
v.looseObject({ kind: v.literal("user"), id: v.pipe(v.string(), v.minLength(1)) }),
|
|
637
|
+
v.looseObject({ kind: v.literal("role"), role: v.pipe(v.string(), v.minLength(1)) })
|
|
638
|
+
]), ChecklistItemShape = v.looseObject({
|
|
639
|
+
label: v.pipe(v.string(), v.minLength(1)),
|
|
640
|
+
done: v.boolean(),
|
|
641
|
+
doneBy: v.optional(v.string()),
|
|
642
|
+
doneAt: v.optional(v.string()),
|
|
643
|
+
_key: v.optional(v.pipe(v.string(), v.minLength(1)))
|
|
644
|
+
}), NoteItemShape = v.pipe(
|
|
645
|
+
v.looseObject({
|
|
646
|
+
_key: v.optional(v.pipe(v.string(), v.minLength(1)))
|
|
647
|
+
}),
|
|
648
|
+
v.check(
|
|
649
|
+
(val) => typeof val == "object" && val !== null && !Array.isArray(val),
|
|
650
|
+
"must be an object"
|
|
651
|
+
)
|
|
652
|
+
), NullableString = v.union([v.null(), v.string()]), NullableNumber = v.union([v.null(), v.number()]), NullableBoolean = v.union([v.null(), v.boolean()]), NullableDateTime = v.union([
|
|
653
|
+
v.null(),
|
|
654
|
+
v.pipe(
|
|
655
|
+
v.string(),
|
|
656
|
+
v.check((s) => !Number.isNaN(Date.parse(s)), "must be an ISO-8601 datetime string")
|
|
657
|
+
)
|
|
357
658
|
]), NullableUrl = NullableString, valueSchemas = {
|
|
358
|
-
"workflow.state.doc.ref":
|
|
359
|
-
"workflow.state.doc.refs":
|
|
360
|
-
"workflow.state.release.ref":
|
|
361
|
-
"workflow.state.query":
|
|
659
|
+
"workflow.state.doc.ref": v.union([v.null(), GdrShape]),
|
|
660
|
+
"workflow.state.doc.refs": v.array(GdrShape),
|
|
661
|
+
"workflow.state.release.ref": v.union([v.null(), ReleaseRefShape]),
|
|
662
|
+
"workflow.state.query": v.any(),
|
|
362
663
|
"workflow.state.value.string": NullableString,
|
|
363
664
|
"workflow.state.value.url": NullableUrl,
|
|
364
665
|
"workflow.state.value.number": NullableNumber,
|
|
365
666
|
"workflow.state.value.boolean": NullableBoolean,
|
|
366
667
|
"workflow.state.value.dateTime": NullableDateTime,
|
|
367
|
-
"workflow.state.value.actor":
|
|
368
|
-
"workflow.state.checklist":
|
|
369
|
-
"workflow.state.notes":
|
|
370
|
-
"workflow.state.assignees":
|
|
668
|
+
"workflow.state.value.actor": v.union([v.null(), ActorShape]),
|
|
669
|
+
"workflow.state.checklist": v.array(ChecklistItemShape),
|
|
670
|
+
"workflow.state.notes": v.array(NoteItemShape),
|
|
671
|
+
"workflow.state.assignees": v.array(AssigneeShape)
|
|
371
672
|
}, itemSchemas = {
|
|
372
673
|
"workflow.state.doc.refs": GdrShape,
|
|
373
674
|
"workflow.state.checklist": ChecklistItemShape,
|
|
@@ -386,12 +687,12 @@ function validateSlotValue(args) {
|
|
|
386
687
|
issues: [`unknown slot type ${args.slotType}`],
|
|
387
688
|
mode: "value"
|
|
388
689
|
});
|
|
389
|
-
const result =
|
|
690
|
+
const result = v.safeParse(schema, args.value);
|
|
390
691
|
if (!result.success)
|
|
391
692
|
throw new SlotValueShapeError({
|
|
392
693
|
slotType: args.slotType,
|
|
393
694
|
slotId: args.slotId,
|
|
394
|
-
issues: formatIssues(result.
|
|
695
|
+
issues: formatIssues(result.issues),
|
|
395
696
|
mode: "value"
|
|
396
697
|
});
|
|
397
698
|
}
|
|
@@ -403,17 +704,20 @@ function validateSlotAppendItem(args) {
|
|
|
403
704
|
issues: [`slot type ${args.slotType} does not support append`],
|
|
404
705
|
mode: "item"
|
|
405
706
|
});
|
|
406
|
-
const
|
|
707
|
+
const schema = itemSchemas[args.slotType], result = v.safeParse(schema, args.item);
|
|
407
708
|
if (!result.success)
|
|
408
709
|
throw new SlotValueShapeError({
|
|
409
710
|
slotType: args.slotType,
|
|
410
711
|
slotId: args.slotId,
|
|
411
|
-
issues: formatIssues(result.
|
|
712
|
+
issues: formatIssues(result.issues),
|
|
412
713
|
mode: "item"
|
|
413
714
|
});
|
|
414
715
|
}
|
|
415
716
|
function formatIssues(issues) {
|
|
416
|
-
return issues.map((i) =>
|
|
717
|
+
return issues.map((i) => {
|
|
718
|
+
const keys = i.path?.map((p) => p.key) ?? [];
|
|
719
|
+
return `${keys.length > 0 ? `at ${keys.join(".")}: ` : ""}${i.message}`;
|
|
720
|
+
});
|
|
417
721
|
}
|
|
418
722
|
function derivePerspectiveFromState(slots) {
|
|
419
723
|
if (slots !== void 0) {
|
|
@@ -458,7 +762,7 @@ async function resolveQueryValue(slot, source, ctx, client, defaultValue) {
|
|
|
458
762
|
state: stateMapFromResolved(ctx.resolvedState ?? []),
|
|
459
763
|
stage: ctx.stageId ?? null,
|
|
460
764
|
task: ctx.taskId ?? null,
|
|
461
|
-
now:
|
|
765
|
+
now: ctx.now,
|
|
462
766
|
engineTags: ctx.tags ?? []
|
|
463
767
|
});
|
|
464
768
|
try {
|
|
@@ -475,7 +779,7 @@ async function resolveSlotValue(slot, initialState, ctx, defaultValue) {
|
|
|
475
779
|
const source = slot.source;
|
|
476
780
|
return source.kind === "init" ? resolveInitValue(slot, initialState, defaultValue) : source.kind === "literal" ? source.value ?? defaultValue : source.kind === "stateRead" ? resolveStateReadValue(source, ctx, defaultValue) : source.kind === "query" && ctx.client !== void 0 ? resolveQueryValue(slot, source, ctx, ctx.client, defaultValue) : defaultValue;
|
|
477
781
|
}
|
|
478
|
-
function buildResolvedSlot(slot, value, _key) {
|
|
782
|
+
function buildResolvedSlot(slot, value, _key, now) {
|
|
479
783
|
const titleProp = slot.title !== void 0 ? { title: slot.title } : {}, descriptionProp = slot.description !== void 0 ? { description: slot.description } : {};
|
|
480
784
|
return slot.type === "workflow.state.query" ? {
|
|
481
785
|
_key,
|
|
@@ -484,7 +788,7 @@ function buildResolvedSlot(slot, value, _key) {
|
|
|
484
788
|
...titleProp,
|
|
485
789
|
...descriptionProp,
|
|
486
790
|
value,
|
|
487
|
-
resolvedAt:
|
|
791
|
+
resolvedAt: now
|
|
488
792
|
} : {
|
|
489
793
|
_key,
|
|
490
794
|
_type: slot.type,
|
|
@@ -496,7 +800,7 @@ function buildResolvedSlot(slot, value, _key) {
|
|
|
496
800
|
}
|
|
497
801
|
async function resolveOneSlot(slot, initialState, ctx, randomKey2) {
|
|
498
802
|
const defaultValue = defaultSlotValue(slot.type), value = await resolveSlotValue(slot, initialState, ctx, defaultValue);
|
|
499
|
-
return validateSlotValue({ slotType: slot.type, slotId: slot.id, value }), buildResolvedSlot(slot, value, randomKey2());
|
|
803
|
+
return validateSlotValue({ slotType: slot.type, slotId: slot.id, value }), buildResolvedSlot(slot, value, randomKey2(), ctx.now);
|
|
500
804
|
}
|
|
501
805
|
function stateMapFromResolved(slots) {
|
|
502
806
|
const out = {};
|
|
@@ -510,10 +814,10 @@ function assertInitValueShape(slot, value) {
|
|
|
510
814
|
}
|
|
511
815
|
if (slot.type === "workflow.state.release.ref") {
|
|
512
816
|
assertGdrShape(value, `state slot "${slot.id}" (workflow.state.release.ref)`);
|
|
513
|
-
const
|
|
514
|
-
if (typeof
|
|
817
|
+
const v2 = value;
|
|
818
|
+
if (typeof v2.releaseName != "string" || v2.releaseName.length === 0)
|
|
515
819
|
throw new Error(
|
|
516
|
-
`Invalid init value for state slot "${slot.id}" (workflow.state.release.ref): \`releaseName\` must be a non-empty string. Got ${JSON.stringify(
|
|
820
|
+
`Invalid init value for state slot "${slot.id}" (workflow.state.release.ref): \`releaseName\` must be a non-empty string. Got ${JSON.stringify(v2.releaseName)}.`
|
|
517
821
|
);
|
|
518
822
|
return;
|
|
519
823
|
}
|
|
@@ -531,18 +835,18 @@ function assertGdrShape(value, context) {
|
|
|
531
835
|
throw new Error(
|
|
532
836
|
`Invalid GDR for ${context}: expected { id: "<scheme>:...", type: "<schema>" }, got ${typeof value}.`
|
|
533
837
|
);
|
|
534
|
-
const
|
|
535
|
-
if (typeof
|
|
838
|
+
const v2 = value;
|
|
839
|
+
if (typeof v2.id != "string" || !isGdrUri(v2.id))
|
|
536
840
|
throw new Error(
|
|
537
|
-
`Invalid GDR for ${context}: \`id\` must be a GDR URI ("<scheme>:<...id-parts>" with scheme dataset|canvas|media-library|dashboard). Got ${JSON.stringify(
|
|
841
|
+
`Invalid GDR for ${context}: \`id\` must be a GDR URI ("<scheme>:<...id-parts>" with scheme dataset|canvas|media-library|dashboard). Got ${JSON.stringify(v2.id)}. Construct via \`gdrFromResource\` / \`refDataset\` / \`refCanvas\` etc. \u2014 bare document ids are not accepted.`
|
|
538
842
|
);
|
|
539
|
-
if (typeof
|
|
843
|
+
if (typeof v2.type != "string" || v2.type.length === 0)
|
|
540
844
|
throw new Error(
|
|
541
|
-
`Invalid GDR for ${context}: \`type\` (schema name) must be a non-empty string. Got ${JSON.stringify(
|
|
845
|
+
`Invalid GDR for ${context}: \`type\` (schema name) must be a non-empty string. Got ${JSON.stringify(v2.type)}.`
|
|
542
846
|
);
|
|
543
847
|
}
|
|
544
848
|
function normalizeQueryResult(slotType, raw, workflowResource) {
|
|
545
|
-
return raw == null ? raw : slotType === "workflow.state.doc.ref" ? coerceToGdr(raw, workflowResource) : slotType === "workflow.state.doc.refs" ? Array.isArray(raw) ? raw.map((item) => coerceToGdr(item, workflowResource)).filter((
|
|
849
|
+
return raw == null ? raw : slotType === "workflow.state.doc.ref" ? coerceToGdr(raw, workflowResource) : slotType === "workflow.state.doc.refs" ? Array.isArray(raw) ? raw.map((item) => coerceToGdr(item, workflowResource)).filter((v2) => v2 !== null) : [] : raw;
|
|
546
850
|
}
|
|
547
851
|
function toGdrUri(docId, workflowResource) {
|
|
548
852
|
return isGdrUri(docId) ? docId : gdrFromResource(workflowResource, docId);
|
|
@@ -567,25 +871,32 @@ async function loadContext(client, instanceId, options) {
|
|
|
567
871
|
const instance = await client.getDocument(instanceId);
|
|
568
872
|
if (!instance)
|
|
569
873
|
throw new Error(`Workflow instance ${instanceId} not found`);
|
|
570
|
-
const definition = JSON.parse(instance.definitionSnapshot), clientForGdr = options?.clientForGdr ?? (() => client),
|
|
571
|
-
|
|
874
|
+
const definition = JSON.parse(instance.definitionSnapshot), clientForGdr = options?.clientForGdr ?? (() => client), clock = options?.clock ?? wallClock, snapshot = await hydrateSnapshot({
|
|
875
|
+
client,
|
|
876
|
+
clientForGdr,
|
|
877
|
+
instance,
|
|
878
|
+
...options?.overlay !== void 0 ? { overlay: options.overlay } : {}
|
|
879
|
+
});
|
|
880
|
+
return { client, clientForGdr, clock, now: clock(), instance, definition, snapshot };
|
|
572
881
|
}
|
|
573
882
|
function ctxEvaluateFilter(ctx, filter) {
|
|
574
883
|
return evaluateFilter({
|
|
575
884
|
filter,
|
|
576
885
|
definition: ctx.definition,
|
|
577
886
|
snapshot: ctx.snapshot,
|
|
578
|
-
params: buildParams(ctx.instance)
|
|
887
|
+
params: buildParams(ctx.instance, ctx.now)
|
|
579
888
|
});
|
|
580
889
|
}
|
|
581
890
|
async function buildEngineContext(args) {
|
|
582
|
-
const { client, clientForGdr, instance, definition } = args;
|
|
891
|
+
const { client, clientForGdr, instance, definition } = args, clock = args.clock ?? wallClock;
|
|
583
892
|
return {
|
|
584
893
|
client,
|
|
585
894
|
clientForGdr,
|
|
895
|
+
clock,
|
|
896
|
+
now: clock(),
|
|
586
897
|
instance,
|
|
587
898
|
definition,
|
|
588
|
-
snapshot: await hydrateSnapshot({ client, clientForGdr, instance
|
|
899
|
+
snapshot: await hydrateSnapshot({ client, clientForGdr, instance })
|
|
589
900
|
};
|
|
590
901
|
}
|
|
591
902
|
function findStage(definition, stageId) {
|
|
@@ -595,12 +906,13 @@ function findStage(definition, stageId) {
|
|
|
595
906
|
return stage;
|
|
596
907
|
}
|
|
597
908
|
async function resolveStageStateSlots(args) {
|
|
598
|
-
const { client, instance, stage, initialState } = args;
|
|
909
|
+
const { client, instance, stage, now, initialState } = args;
|
|
599
910
|
return resolveDeclaredState({
|
|
600
911
|
slotDefs: stage.state,
|
|
601
912
|
initialState: initialState ?? [],
|
|
602
913
|
ctx: {
|
|
603
914
|
client,
|
|
915
|
+
now,
|
|
604
916
|
selfId: instance._id,
|
|
605
917
|
tags: instance.tags ?? [],
|
|
606
918
|
stageId: stage.id,
|
|
@@ -615,12 +927,13 @@ async function resolveStageStateSlots(args) {
|
|
|
615
927
|
});
|
|
616
928
|
}
|
|
617
929
|
async function resolveTaskStateSlots(args) {
|
|
618
|
-
const { client, instance, stage, task } = args;
|
|
930
|
+
const { client, instance, stage, task, now } = args;
|
|
619
931
|
return resolveDeclaredState({
|
|
620
932
|
slotDefs: task.state,
|
|
621
933
|
initialState: [],
|
|
622
934
|
ctx: {
|
|
623
935
|
client,
|
|
936
|
+
now,
|
|
624
937
|
selfId: instance._id,
|
|
625
938
|
tags: instance.tags ?? [],
|
|
626
939
|
stageId: stage.id,
|
|
@@ -632,73 +945,6 @@ async function resolveTaskStateSlots(args) {
|
|
|
632
945
|
randomKey
|
|
633
946
|
});
|
|
634
947
|
}
|
|
635
|
-
function readStateSlot(instance, scope, slotId, stageId, taskId) {
|
|
636
|
-
if (scope === "workflow")
|
|
637
|
-
return slotValue$1(instance.state?.find((s) => s.id === slotId));
|
|
638
|
-
const stageEntry = instance.stages.find(
|
|
639
|
-
(s) => s.id === (stageId ?? instance.currentStageId) && s.exitedAt === void 0
|
|
640
|
-
);
|
|
641
|
-
if (stageEntry === void 0) return;
|
|
642
|
-
if (scope === "stage")
|
|
643
|
-
return slotValue$1(stageEntry.state?.find((s) => s.id === slotId));
|
|
644
|
-
const taskEntry = stageEntry.tasks.find((t) => t.id === taskId);
|
|
645
|
-
return slotValue$1(taskEntry?.state?.find((s) => s.id === slotId));
|
|
646
|
-
}
|
|
647
|
-
function slotValue$1(slot) {
|
|
648
|
-
if (slot != null && typeof slot == "object")
|
|
649
|
-
return slot.value;
|
|
650
|
-
}
|
|
651
|
-
function resolveSource(src, ctx) {
|
|
652
|
-
switch (src.source) {
|
|
653
|
-
case "literal":
|
|
654
|
-
return src.value;
|
|
655
|
-
case "param":
|
|
656
|
-
return ctx.params?.[src.paramId];
|
|
657
|
-
case "actor":
|
|
658
|
-
return ctx.actor;
|
|
659
|
-
case "now":
|
|
660
|
-
return (/* @__PURE__ */ new Date()).toISOString();
|
|
661
|
-
case "self":
|
|
662
|
-
return ctx.instance._id;
|
|
663
|
-
case "stageId":
|
|
664
|
-
return ctx.stageId ?? ctx.instance.currentStageId;
|
|
665
|
-
case "stateRead":
|
|
666
|
-
return resolveStateRead(src, ctx);
|
|
667
|
-
case "effectOutput":
|
|
668
|
-
return resolveEffectOutput(src, ctx);
|
|
669
|
-
case "object":
|
|
670
|
-
return resolveObject(src, ctx);
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
function resolveStateRead(src, ctx) {
|
|
674
|
-
const value = readStateSlot(ctx.instance, src.scope, src.slotId, ctx.stageId, ctx.taskId);
|
|
675
|
-
return src.path !== void 0 ? getPath(value, src.path) : value;
|
|
676
|
-
}
|
|
677
|
-
function resolveEffectOutput(src, ctx) {
|
|
678
|
-
const entry = ctx.instance.effectsContext.find((e) => e.id === src.contextId);
|
|
679
|
-
if (entry === void 0) return null;
|
|
680
|
-
const value = src.path !== void 0 ? getPath(entry.value, src.path) : entry.value;
|
|
681
|
-
return value === void 0 ? null : value;
|
|
682
|
-
}
|
|
683
|
-
function resolveObject(src, ctx) {
|
|
684
|
-
const out = {};
|
|
685
|
-
for (const [field, fieldSrc] of Object.entries(src.fields))
|
|
686
|
-
out[field] = resolveSource(fieldSrc, ctx);
|
|
687
|
-
return out;
|
|
688
|
-
}
|
|
689
|
-
async function resolveBindings(args) {
|
|
690
|
-
const { bindings, staticInput, instance, params, actor } = args, resolved = {};
|
|
691
|
-
if (bindings) {
|
|
692
|
-
const ctx = {
|
|
693
|
-
instance,
|
|
694
|
-
...params !== void 0 ? { params } : {},
|
|
695
|
-
...actor !== void 0 ? { actor } : {}
|
|
696
|
-
};
|
|
697
|
-
for (const [key, src] of Object.entries(bindings))
|
|
698
|
-
resolved[key] = resolveSource(src, ctx);
|
|
699
|
-
}
|
|
700
|
-
return { ...resolved, ...staticInput };
|
|
701
|
-
}
|
|
702
948
|
function effectsContextEntry(id, value) {
|
|
703
949
|
const _key = randomKey();
|
|
704
950
|
return typeof value == "string" ? { _key, _type: "effectsContext.string", id, value } : typeof value == "number" ? { _key, _type: "effectsContext.number", id, value } : typeof value == "boolean" ? { _key, _type: "effectsContext.boolean", id, value } : { _key, _type: "effectsContext.ref", id, value };
|
|
@@ -707,7 +953,7 @@ function buildInstanceBase(args) {
|
|
|
707
953
|
const { id, now, actor, perspective } = args;
|
|
708
954
|
return {
|
|
709
955
|
_id: id,
|
|
710
|
-
_type:
|
|
956
|
+
_type: WORKFLOW_INSTANCE_TYPE,
|
|
711
957
|
_rev: "",
|
|
712
958
|
_createdAt: now,
|
|
713
959
|
_updatedAt: now,
|
|
@@ -737,175 +983,337 @@ function buildInstanceBase(args) {
|
|
|
737
983
|
lastChangedAt: now
|
|
738
984
|
};
|
|
739
985
|
}
|
|
740
|
-
function
|
|
741
|
-
return
|
|
986
|
+
function startMutation(instance) {
|
|
987
|
+
return {
|
|
988
|
+
currentStageId: instance.currentStageId,
|
|
989
|
+
// Deep-ish copy. Per-scope state slot arrays need their own copies
|
|
990
|
+
// so ops can mutate without leaking into the source.
|
|
991
|
+
state: (instance.state ?? []).map((s) => ({ ...s })),
|
|
992
|
+
stages: instance.stages.map((s) => ({
|
|
993
|
+
...s,
|
|
994
|
+
state: (s.state ?? []).map((slot) => ({ ...slot })),
|
|
995
|
+
tasks: s.tasks.map((t) => ({
|
|
996
|
+
...t,
|
|
997
|
+
...t.state !== void 0 ? { state: t.state.map((slot) => ({ ...slot })) } : {}
|
|
998
|
+
}))
|
|
999
|
+
})),
|
|
1000
|
+
pendingEffects: [...instance.pendingEffects],
|
|
1001
|
+
effectHistory: [...instance.effectHistory],
|
|
1002
|
+
effectsContext: [...instance.effectsContext],
|
|
1003
|
+
history: [...instance.history],
|
|
1004
|
+
lastChangedAt: instance.lastChangedAt,
|
|
1005
|
+
...instance.completedAt !== void 0 ? { completedAt: instance.completedAt } : {},
|
|
1006
|
+
pendingCreates: []
|
|
1007
|
+
};
|
|
742
1008
|
}
|
|
743
|
-
function
|
|
1009
|
+
function taskStatusChangedEntry(args) {
|
|
744
1010
|
return {
|
|
745
|
-
|
|
746
|
-
_type: "
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
predicate: args.predicate,
|
|
754
|
-
metadata: args.metadata
|
|
1011
|
+
_key: randomKey(),
|
|
1012
|
+
_type: "workflow.history.taskStatusChanged",
|
|
1013
|
+
at: args.at,
|
|
1014
|
+
stageId: args.stageId,
|
|
1015
|
+
taskId: args.taskId,
|
|
1016
|
+
from: args.from,
|
|
1017
|
+
to: args.to,
|
|
1018
|
+
...args.actor !== void 0 ? { actor: args.actor } : {}
|
|
755
1019
|
};
|
|
756
1020
|
}
|
|
757
|
-
function
|
|
758
|
-
|
|
1021
|
+
function stageTransitionHistory(args) {
|
|
1022
|
+
const { fromStageId, toStageId, at, transitionId, actor, via, reason } = args, shared = {
|
|
1023
|
+
at,
|
|
1024
|
+
...transitionId !== void 0 ? { transitionId } : {},
|
|
1025
|
+
...actor !== void 0 ? { actor } : {},
|
|
1026
|
+
...via !== void 0 ? { via } : {},
|
|
1027
|
+
...reason !== void 0 ? { reason } : {}
|
|
1028
|
+
};
|
|
1029
|
+
return [
|
|
1030
|
+
{
|
|
1031
|
+
_key: randomKey(),
|
|
1032
|
+
_type: "workflow.history.stageExited",
|
|
1033
|
+
stageId: fromStageId,
|
|
1034
|
+
toStageId,
|
|
1035
|
+
...shared
|
|
1036
|
+
},
|
|
1037
|
+
{
|
|
1038
|
+
_key: randomKey(),
|
|
1039
|
+
_type: "workflow.history.transitionFired",
|
|
1040
|
+
fromStageId,
|
|
1041
|
+
toStageId,
|
|
1042
|
+
...shared
|
|
1043
|
+
},
|
|
1044
|
+
{
|
|
1045
|
+
_key: randomKey(),
|
|
1046
|
+
_type: "workflow.history.stageEntered",
|
|
1047
|
+
stageId: toStageId,
|
|
1048
|
+
fromStageId,
|
|
1049
|
+
...shared
|
|
1050
|
+
}
|
|
1051
|
+
];
|
|
759
1052
|
}
|
|
760
|
-
function
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
1053
|
+
function instanceStateFields(src) {
|
|
1054
|
+
const fields = {
|
|
1055
|
+
currentStageId: src.currentStageId,
|
|
1056
|
+
state: src.state,
|
|
1057
|
+
stages: src.stages,
|
|
1058
|
+
pendingEffects: src.pendingEffects,
|
|
1059
|
+
effectHistory: src.effectHistory,
|
|
1060
|
+
effectsContext: src.effectsContext,
|
|
1061
|
+
history: src.history
|
|
1062
|
+
};
|
|
1063
|
+
return src.completedAt !== void 0 && (fields.completedAt = src.completedAt), fields;
|
|
764
1064
|
}
|
|
765
|
-
function
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
}
|
|
773
|
-
return !0;
|
|
1065
|
+
function materializeInstance(base, mutation) {
|
|
1066
|
+
return {
|
|
1067
|
+
...base,
|
|
1068
|
+
currentStageId: mutation.currentStageId,
|
|
1069
|
+
state: mutation.state,
|
|
1070
|
+
stages: mutation.stages
|
|
1071
|
+
};
|
|
774
1072
|
}
|
|
775
|
-
async function
|
|
776
|
-
const
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
return
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
1073
|
+
async function persist(ctx, mutation) {
|
|
1074
|
+
const set = {
|
|
1075
|
+
...instanceStateFields(mutation),
|
|
1076
|
+
lastChangedAt: ctx.now
|
|
1077
|
+
}, pendingCreates = mutation.pendingCreates;
|
|
1078
|
+
if (pendingCreates.length === 0)
|
|
1079
|
+
return ctx.client.patch(ctx.instance._id).set(set).ifRevisionId(ctx.instance._rev).commit();
|
|
1080
|
+
const tx = ctx.client.transaction();
|
|
1081
|
+
for (const body of pendingCreates) tx.create(body);
|
|
1082
|
+
tx.patch(ctx.client.patch(ctx.instance._id).set(set).ifRevisionId(ctx.instance._rev)), await tx.commit(), mutation.pendingCreates = [];
|
|
1083
|
+
const actorForPriming = void 0;
|
|
1084
|
+
for (const body of pendingCreates)
|
|
1085
|
+
await primeInitialStage(ctx.client, body._id, actorForPriming, ctx.clientForGdr, ctx.clock), await cascadeAutoTransitions(ctx.client, body._id, actorForPriming, ctx.clientForGdr, ctx.clock), await propagateToAncestors(ctx.client, body._id, actorForPriming, ctx.clientForGdr, ctx.clock);
|
|
1086
|
+
const reloaded = await ctx.client.getDocument(ctx.instance._id);
|
|
1087
|
+
if (!reloaded)
|
|
1088
|
+
throw new Error(`Instance ${ctx.instance._id} disappeared after transaction commit`);
|
|
1089
|
+
return reloaded;
|
|
790
1090
|
}
|
|
791
|
-
|
|
792
|
-
const
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
1091
|
+
function currentStageEntry(mutation) {
|
|
1092
|
+
const entry = findOpenStageEntry(mutation);
|
|
1093
|
+
if (entry === void 0)
|
|
1094
|
+
throw new Error(
|
|
1095
|
+
`Mutation invariant broken: no current (un-exited) StageEntry for currentStageId "${mutation.currentStageId}"`
|
|
1096
|
+
);
|
|
1097
|
+
return entry;
|
|
796
1098
|
}
|
|
797
|
-
function
|
|
798
|
-
return
|
|
1099
|
+
function currentTasks(mutation) {
|
|
1100
|
+
return currentStageEntry(mutation).tasks;
|
|
799
1101
|
}
|
|
800
|
-
function
|
|
801
|
-
const
|
|
802
|
-
if (
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
return typeof v.type == "string" ? { parsed: parseGdr(v.id), type: v.type } : { parsed: parseGdr(v.id) };
|
|
808
|
-
}
|
|
809
|
-
return null;
|
|
1102
|
+
function findTaskInCurrentStage(ctx, taskId) {
|
|
1103
|
+
const stage = findStage(ctx.definition, ctx.instance.currentStageId), task = (stage.tasks ?? []).find((t) => t.id === taskId);
|
|
1104
|
+
if (task === void 0)
|
|
1105
|
+
throw new Error(
|
|
1106
|
+
`Task "${taskId}" not found in current stage "${stage.id}" of ${ctx.definition.workflowId}`
|
|
1107
|
+
);
|
|
1108
|
+
return { stage, task };
|
|
810
1109
|
}
|
|
811
|
-
function
|
|
812
|
-
const
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
targets.push(target);
|
|
817
|
-
}
|
|
818
|
-
return targets.length === 0 ? null : targets;
|
|
1110
|
+
function requireMutationTaskEntry(mutation, taskId) {
|
|
1111
|
+
const mutEntry = currentTasks(mutation).find((t) => t.id === taskId);
|
|
1112
|
+
if (mutEntry === void 0)
|
|
1113
|
+
throw new Error(`Task "${taskId}" disappeared from mutation copy \u2014 invariant broken`);
|
|
1114
|
+
return mutEntry;
|
|
819
1115
|
}
|
|
820
|
-
function
|
|
821
|
-
|
|
822
|
-
for (const g of targets) {
|
|
823
|
-
const r = resourceOf(g.parsed);
|
|
824
|
-
if (r.type !== resource.type || r.id !== resource.id)
|
|
825
|
-
throw new Error(
|
|
826
|
-
`Guard targets span multiple resources (${resource.type}:${resource.id} vs ${r.type}:${r.id}); a guard is single-resource.`
|
|
827
|
-
);
|
|
828
|
-
}
|
|
829
|
-
return resource;
|
|
1116
|
+
function findCurrentStageEntry(instance) {
|
|
1117
|
+
return findOpenStageEntry(instance);
|
|
830
1118
|
}
|
|
831
|
-
function
|
|
832
|
-
return
|
|
1119
|
+
function findCurrentTasks(instance) {
|
|
1120
|
+
return findCurrentStageEntry(instance)?.tasks ?? [];
|
|
833
1121
|
}
|
|
834
|
-
function
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
1122
|
+
async function completeEffect(args) {
|
|
1123
|
+
const { client, instanceId, effectKey, status, outputs, detail, error, durationMs, options } = args, ctx = await loadContext(client, instanceId, {
|
|
1124
|
+
...options?.clock ? { clock: options.clock } : {}
|
|
1125
|
+
});
|
|
1126
|
+
return commitCompleteEffect(
|
|
1127
|
+
ctx,
|
|
1128
|
+
effectKey,
|
|
1129
|
+
status,
|
|
1130
|
+
outputs,
|
|
1131
|
+
detail,
|
|
1132
|
+
error,
|
|
1133
|
+
durationMs,
|
|
1134
|
+
options?.actor
|
|
1135
|
+
);
|
|
838
1136
|
}
|
|
839
|
-
function
|
|
840
|
-
const
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
1137
|
+
function buildEffectHistoryEntry(pending, outcome) {
|
|
1138
|
+
const { status, ranAt, actor, detail, error, durationMs, outputs } = outcome, resolvedActor = actor ?? pending.actor;
|
|
1139
|
+
return {
|
|
1140
|
+
_key: pending._key,
|
|
1141
|
+
effectId: pending.effectId,
|
|
1142
|
+
...pending.title !== void 0 ? { title: pending.title } : {},
|
|
1143
|
+
...pending.description !== void 0 ? { description: pending.description } : {},
|
|
1144
|
+
params: pending.params,
|
|
1145
|
+
origin: pending.origin,
|
|
1146
|
+
...resolvedActor !== void 0 ? { actor: resolvedActor } : {},
|
|
1147
|
+
ranAt,
|
|
1148
|
+
...durationMs !== void 0 ? { durationMs } : {},
|
|
1149
|
+
status,
|
|
1150
|
+
...detail !== void 0 ? { detail } : {},
|
|
1151
|
+
...error !== void 0 ? { error } : {},
|
|
1152
|
+
...outputs !== void 0 ? { outputs } : {}
|
|
1153
|
+
};
|
|
844
1154
|
}
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
return { doc: compileGuard({
|
|
851
|
-
id: lakeGuardId({ instanceDocId: instance._id, stageId, index }),
|
|
852
|
-
resourceType: resource.type,
|
|
853
|
-
resourceId: resource.id,
|
|
854
|
-
owner: GUARD_OWNER,
|
|
855
|
-
...guard.name !== void 0 ? { name: guard.name } : {},
|
|
856
|
-
...guard.description !== void 0 ? { description: guard.description } : {},
|
|
857
|
-
match: {
|
|
858
|
-
...types !== void 0 ? { types } : {},
|
|
859
|
-
idRefs: bareIdRefs(targets),
|
|
860
|
-
...guard.match.idPatterns !== void 0 ? { idPatterns: guard.match.idPatterns } : {},
|
|
861
|
-
actions: guard.match.actions
|
|
862
|
-
},
|
|
863
|
-
predicate: guard.predicate ?? "",
|
|
864
|
-
metadata: resolveMetadata(guard.metadata, ctx)
|
|
865
|
-
}), routeGdr: targets[0].parsed };
|
|
1155
|
+
function upsertEffectsContext(mutation, outputs) {
|
|
1156
|
+
for (const [id, value] of Object.entries(outputs)) {
|
|
1157
|
+
const existingIndex = mutation.effectsContext.findIndex((e) => e.id === id), entry = effectsContextEntry(id, value);
|
|
1158
|
+
existingIndex >= 0 ? mutation.effectsContext[existingIndex] = entry : mutation.effectsContext.push(entry);
|
|
1159
|
+
}
|
|
866
1160
|
}
|
|
867
|
-
async function
|
|
868
|
-
const
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
1161
|
+
async function commitCompleteEffect(ctx, effectKey, status, outputs, detail, error, durationMs, actor) {
|
|
1162
|
+
const pending = ctx.instance.pendingEffects.find((e) => e._key === effectKey);
|
|
1163
|
+
if (pending === void 0)
|
|
1164
|
+
throw new Error(`Pending effect "${effectKey}" not found on instance ${ctx.instance._id}`);
|
|
1165
|
+
const mutation = startMutation(ctx.instance);
|
|
1166
|
+
mutation.pendingEffects = mutation.pendingEffects.filter((e) => e._key !== effectKey);
|
|
1167
|
+
const ranAt = ctx.now;
|
|
1168
|
+
return mutation.effectHistory.push(
|
|
1169
|
+
buildEffectHistoryEntry(pending, { status, ranAt, actor, detail, error, durationMs, outputs })
|
|
1170
|
+
), status === "done" && outputs !== void 0 && upsertEffectsContext(mutation, outputs), mutation.history.push({
|
|
1171
|
+
_key: randomKey(),
|
|
1172
|
+
_type: "workflow.history.effectCompleted",
|
|
1173
|
+
at: ranAt,
|
|
1174
|
+
effectKey,
|
|
1175
|
+
effectId: pending.effectId,
|
|
1176
|
+
status,
|
|
1177
|
+
...outputs !== void 0 ? { outputs } : {},
|
|
1178
|
+
...detail !== void 0 ? { detail } : {},
|
|
1179
|
+
...actor !== void 0 ? { actor } : {}
|
|
1180
|
+
}), await persist(ctx, mutation), { effectKey, status };
|
|
1181
|
+
}
|
|
1182
|
+
function buildQueuedEffect(effect, origin, params, actor, now) {
|
|
1183
|
+
const key = randomKey(), pending = {
|
|
1184
|
+
_key: key,
|
|
1185
|
+
_type: "workflow.pendingEffect",
|
|
1186
|
+
effectId: effect.id,
|
|
1187
|
+
...effect.title !== void 0 ? { title: effect.title } : {},
|
|
1188
|
+
...effect.description !== void 0 ? { description: effect.description } : {},
|
|
1189
|
+
...effect.bindings !== void 0 ? { bindings: effect.bindings } : {},
|
|
1190
|
+
params,
|
|
1191
|
+
origin,
|
|
1192
|
+
...actor !== void 0 ? { actor } : {},
|
|
1193
|
+
queuedAt: now
|
|
1194
|
+
}, history = {
|
|
1195
|
+
_key: randomKey(),
|
|
1196
|
+
_type: "workflow.history.effectQueued",
|
|
1197
|
+
at: now,
|
|
1198
|
+
effectKey: key,
|
|
1199
|
+
effectId: effect.id,
|
|
1200
|
+
origin
|
|
875
1201
|
};
|
|
876
|
-
|
|
1202
|
+
return { pending, history };
|
|
877
1203
|
}
|
|
878
|
-
function
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
1204
|
+
async function queueEffects(ctx, mutation, effects, origin, actor, callerParams) {
|
|
1205
|
+
if (!effects) return;
|
|
1206
|
+
const now = ctx.now, liveInstance = {
|
|
1207
|
+
...ctx.instance,
|
|
1208
|
+
state: mutation.state,
|
|
1209
|
+
stages: mutation.stages,
|
|
1210
|
+
effectsContext: mutation.effectsContext
|
|
1211
|
+
};
|
|
1212
|
+
for (const effect of effects) {
|
|
1213
|
+
const params = await resolveBindings({
|
|
1214
|
+
bindings: effect.bindings,
|
|
1215
|
+
staticInput: effect.input,
|
|
1216
|
+
instance: liveInstance,
|
|
1217
|
+
now,
|
|
1218
|
+
...callerParams !== void 0 ? { params: callerParams } : {},
|
|
1219
|
+
...actor !== void 0 ? { actor } : {}
|
|
1220
|
+
}), { pending, history } = buildQueuedEffect(effect, origin, params, actor, now);
|
|
1221
|
+
mutation.pendingEffects.push(pending), mutation.history.push(history);
|
|
883
1222
|
}
|
|
884
|
-
return out;
|
|
885
1223
|
}
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
1224
|
+
class ActionParamsInvalidError extends Error {
|
|
1225
|
+
action;
|
|
1226
|
+
taskId;
|
|
1227
|
+
issues;
|
|
1228
|
+
constructor(args) {
|
|
1229
|
+
const lines = args.issues.map((i) => ` - ${i.paramId}: ${i.reason}`).join(`
|
|
1230
|
+
`);
|
|
1231
|
+
super(`Action "${args.action}" on task "${args.taskId}" rejected: invalid params
|
|
1232
|
+
${lines}`), this.name = "ActionParamsInvalidError", this.action = args.action, this.taskId = args.taskId, this.issues = args.issues;
|
|
1233
|
+
}
|
|
889
1234
|
}
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
1235
|
+
class WorkflowStateDivergedError extends Error {
|
|
1236
|
+
instanceId;
|
|
1237
|
+
/** The guard-deploy failure that triggered the (attempted) rollback. */
|
|
1238
|
+
guardError;
|
|
1239
|
+
/** The rollback failure, when rollback was attempted and itself failed. */
|
|
1240
|
+
rollbackError;
|
|
1241
|
+
constructor(args) {
|
|
1242
|
+
super(
|
|
1243
|
+
`Workflow "${args.instanceId}" state committed but its guards could not be reconciled: ${args.reason}.`,
|
|
1244
|
+
{ cause: args.guardError }
|
|
1245
|
+
), this.name = "WorkflowStateDivergedError", this.instanceId = args.instanceId, this.guardError = args.guardError, args.rollbackError !== void 0 && (this.rollbackError = args.rollbackError);
|
|
1246
|
+
}
|
|
893
1247
|
}
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
}
|
|
1248
|
+
const CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS = 3;
|
|
1249
|
+
class ConcurrentFireActionError extends Error {
|
|
1250
|
+
instanceId;
|
|
1251
|
+
taskId;
|
|
1252
|
+
action;
|
|
1253
|
+
attempts;
|
|
1254
|
+
constructor(args) {
|
|
1255
|
+
super(
|
|
1256
|
+
`Action "${args.action}" on task "${args.taskId}" (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.`
|
|
1257
|
+
), this.name = "ConcurrentFireActionError", this.instanceId = args.instanceId, this.taskId = args.taskId, this.action = args.action, this.attempts = args.attempts;
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
function isRevisionConflict(error) {
|
|
1261
|
+
if (typeof error != "object" || error === null) return !1;
|
|
1262
|
+
const { statusCode, message } = error;
|
|
1263
|
+
return statusCode === 409 ? !0 : typeof message == "string" && message.includes("ifRevisionId check failed");
|
|
1264
|
+
}
|
|
1265
|
+
class CascadeLimitError extends Error {
|
|
1266
|
+
instanceId;
|
|
1267
|
+
limit;
|
|
1268
|
+
constructor(args) {
|
|
1269
|
+
super(
|
|
1270
|
+
`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.`
|
|
1271
|
+
), this.name = "CascadeLimitError", this.instanceId = args.instanceId, this.limit = args.limit;
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
async function deployOrRollback(args) {
|
|
1275
|
+
try {
|
|
1276
|
+
await args.deploy();
|
|
1277
|
+
} catch (guardError) {
|
|
1278
|
+
if (!args.reversible)
|
|
1279
|
+
throw new WorkflowStateDivergedError({
|
|
1280
|
+
instanceId: args.instanceId,
|
|
1281
|
+
guardError,
|
|
1282
|
+
reason: "the move created child instances a parent-only rollback cannot undo"
|
|
1283
|
+
});
|
|
1284
|
+
try {
|
|
1285
|
+
let patch = args.client.patch(args.instanceId).set(args.restore);
|
|
1286
|
+
args.unset && args.unset.length > 0 && (patch = patch.unset(args.unset)), args.committedRev !== void 0 && (patch = patch.ifRevisionId(args.committedRev)), await patch.commit();
|
|
1287
|
+
} catch (rollbackError) {
|
|
1288
|
+
throw new WorkflowStateDivergedError({
|
|
1289
|
+
instanceId: args.instanceId,
|
|
1290
|
+
guardError,
|
|
1291
|
+
rollbackError,
|
|
1292
|
+
reason: "rollback of the committed move failed"
|
|
1293
|
+
});
|
|
1294
|
+
}
|
|
1295
|
+
throw guardError;
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
const TAG_RE = /^[a-z0-9][a-z0-9-]*$/;
|
|
1299
|
+
function validateTags(tags) {
|
|
1300
|
+
if (tags.length === 0)
|
|
1301
|
+
throw new Error("tags: must be a non-empty array");
|
|
1302
|
+
for (const tag of tags)
|
|
1303
|
+
if (!TAG_RE.test(tag))
|
|
1304
|
+
throw new Error(
|
|
1305
|
+
`tags: invalid tag "${tag}" \u2014 must match ${TAG_RE.source} (ASCII lowercase + digits + dashes, no leading dash, no dots)`
|
|
1306
|
+
);
|
|
1307
|
+
}
|
|
1308
|
+
function canonicalTag(tags) {
|
|
1309
|
+
return tags[0];
|
|
1310
|
+
}
|
|
1311
|
+
function tagScopeFilter(param = "engineTags") {
|
|
1312
|
+
return `count(tags[@ in $${param}]) > 0`;
|
|
1313
|
+
}
|
|
1314
|
+
function definitionLookupGroq(explicit) {
|
|
1315
|
+
const scoped = `_type == "${WORKFLOW_DEFINITION_TYPE}" && workflowId == $workflowId && ${tagScopeFilter()}`;
|
|
1316
|
+
return explicit ? `*[${scoped} && version == $version][0]` : `*[${scoped}] | order(version desc)[0]`;
|
|
909
1317
|
}
|
|
910
1318
|
async function discoverItems(args) {
|
|
911
1319
|
const { client, groq, params, perspective } = args, fetchOptions = perspective !== void 0 ? { perspective } : void 0;
|
|
@@ -922,31 +1330,29 @@ function clientForDiscovery(defaultClient, clientForGdr, subjectGdrUri) {
|
|
|
922
1330
|
const MAX_SPAWN_DEPTH = 6;
|
|
923
1331
|
async function spawnChildren(ctx, mutation, task, spawn, actor) {
|
|
924
1332
|
if (ctx.instance.ancestors.length + 1 > MAX_SPAWN_DEPTH) {
|
|
925
|
-
const chain = [
|
|
926
|
-
...ctx.instance.ancestors.map((a) => a.id),
|
|
927
|
-
gdrFromResource(ctx.instance.workflowResource, ctx.instance._id)
|
|
928
|
-
].join(" \u2192 ");
|
|
1333
|
+
const chain = [...ctx.instance.ancestors.map((a) => a.id), selfGdr(ctx.instance)].join(" \u2192 ");
|
|
929
1334
|
throw new Error(
|
|
930
1335
|
`Spawn depth limit (${MAX_SPAWN_DEPTH}) exceeded on task "${task.id}" of ${ctx.instance._id}. Ancestor chain: ${chain}`
|
|
931
1336
|
);
|
|
932
1337
|
}
|
|
933
|
-
const rows = await resolveSpawnRows(ctx, spawn), refs = [];
|
|
1338
|
+
const now = ctx.now, rows = await resolveSpawnRows(ctx, spawn), refs = [];
|
|
934
1339
|
for (const row of rows) {
|
|
935
|
-
const projected = projectSpawnRow(ctx.instance, spawn, row), { ref, body } = await prepareChildInstance({
|
|
1340
|
+
const projected = projectSpawnRow(ctx.instance, spawn, row, now), { ref, body } = await prepareChildInstance({
|
|
936
1341
|
client: ctx.client,
|
|
937
1342
|
parent: ctx.instance,
|
|
938
1343
|
task,
|
|
939
1344
|
spawn,
|
|
940
1345
|
initialState: projected.initialState,
|
|
941
1346
|
effectsContext: projected.effectsContext,
|
|
942
|
-
actor
|
|
1347
|
+
actor,
|
|
1348
|
+
now
|
|
943
1349
|
});
|
|
944
1350
|
refs.push(ref), mutation.pendingCreates.push(body);
|
|
945
1351
|
}
|
|
946
1352
|
return refs;
|
|
947
1353
|
}
|
|
948
1354
|
async function resolveSpawnRows(ctx, spawn) {
|
|
949
|
-
const params = buildParams(ctx.instance), groq = spawn.forEach.groq;
|
|
1355
|
+
const params = buildParams(ctx.instance, ctx.now), groq = spawn.forEach.groq;
|
|
950
1356
|
let value;
|
|
951
1357
|
if (groq.includes("*")) {
|
|
952
1358
|
const routingUri = firstDocRefGdrUri(ctx.instance.state), client = clientForDiscovery(ctx.client, ctx.clientForGdr, routingUri);
|
|
@@ -968,7 +1374,7 @@ function firstDocRefGdrUri(slots) {
|
|
|
968
1374
|
if (s._type === "workflow.state.doc.ref" && s.value !== null) return s.value.id;
|
|
969
1375
|
}
|
|
970
1376
|
}
|
|
971
|
-
function resolveSpawnSource(source, parent, row) {
|
|
1377
|
+
function resolveSpawnSource(source, parent, row, now) {
|
|
972
1378
|
switch (source.source) {
|
|
973
1379
|
case "literal":
|
|
974
1380
|
return source.value;
|
|
@@ -979,15 +1385,15 @@ function resolveSpawnSource(source, parent, row) {
|
|
|
979
1385
|
return slot === void 0 ? null : source.path !== void 0 ? getPath(slot, source.path) : slot;
|
|
980
1386
|
}
|
|
981
1387
|
case "self":
|
|
982
|
-
return
|
|
1388
|
+
return selfGdr(parent);
|
|
983
1389
|
case "stageId":
|
|
984
1390
|
return parent.currentStageId;
|
|
985
1391
|
case "now":
|
|
986
|
-
return
|
|
1392
|
+
return now;
|
|
987
1393
|
case "object": {
|
|
988
1394
|
const out = {};
|
|
989
|
-
for (const [k,
|
|
990
|
-
out[k] = resolveSpawnSource(
|
|
1395
|
+
for (const [k, v2] of Object.entries(source.fields))
|
|
1396
|
+
out[k] = resolveSpawnSource(v2, parent, row, now);
|
|
991
1397
|
return out;
|
|
992
1398
|
}
|
|
993
1399
|
case "actor":
|
|
@@ -997,10 +1403,10 @@ function resolveSpawnSource(source, parent, row) {
|
|
|
997
1403
|
return null;
|
|
998
1404
|
}
|
|
999
1405
|
}
|
|
1000
|
-
function projectSpawnRow(parent, spawn, row) {
|
|
1406
|
+
function projectSpawnRow(parent, spawn, row, now) {
|
|
1001
1407
|
const initialState = [];
|
|
1002
1408
|
for (const entry of spawn.forEach.as.initialState ?? []) {
|
|
1003
|
-
const raw = resolveSpawnSource(entry.value, parent, row), value = canonicaliseSpawnValue(entry.type, raw, parent.workflowResource);
|
|
1409
|
+
const raw = resolveSpawnSource(entry.value, parent, row, now), value = canonicaliseSpawnValue(entry.type, raw, parent.workflowResource);
|
|
1004
1410
|
initialState.push({
|
|
1005
1411
|
type: entry.type,
|
|
1006
1412
|
id: entry.id,
|
|
@@ -1009,13 +1415,13 @@ function projectSpawnRow(parent, spawn, row) {
|
|
|
1009
1415
|
}
|
|
1010
1416
|
const effectsContext = {};
|
|
1011
1417
|
for (const [k, src] of Object.entries(spawn.forEach.as.effectsContext ?? {})) {
|
|
1012
|
-
const
|
|
1013
|
-
|
|
1418
|
+
const v2 = resolveSpawnSource(src, parent, row, now);
|
|
1419
|
+
v2 != null && (typeof v2 == "string" || typeof v2 == "number" || typeof v2 == "boolean" || typeof v2 == "object" && "id" in v2 && "type" in v2) && (effectsContext[k] = v2);
|
|
1014
1420
|
}
|
|
1015
1421
|
return { initialState, effectsContext };
|
|
1016
1422
|
}
|
|
1017
1423
|
function canonicaliseSpawnValue(slotType, value, workflowResource) {
|
|
1018
|
-
return slotType === "workflow.state.doc.ref" ? coerceSpawnGdr(value, workflowResource) : slotType === "workflow.state.doc.refs" && Array.isArray(value) ? value.map((
|
|
1424
|
+
return slotType === "workflow.state.doc.ref" ? coerceSpawnGdr(value, workflowResource) : slotType === "workflow.state.doc.refs" && Array.isArray(value) ? value.map((v2) => coerceSpawnGdr(v2, workflowResource)).filter((v2) => v2 !== null) : value;
|
|
1019
1425
|
}
|
|
1020
1426
|
function coerceSpawnGdr(raw, workflowResource) {
|
|
1021
1427
|
const toUri = (id) => isGdrUri(id) ? id : gdrFromResource(workflowResource, id);
|
|
@@ -1032,30 +1438,32 @@ function coerceSpawnGdr(raw, workflowResource) {
|
|
|
1032
1438
|
return typeof raw == "string" && raw.length > 0 ? { id: toUri(raw), type: "document" } : null;
|
|
1033
1439
|
}
|
|
1034
1440
|
async function resolveLogicalDefinition(client, ref, tags) {
|
|
1035
|
-
const wantsExplicit = typeof ref.version == "number", params = { workflowId: ref.workflowId, tags };
|
|
1036
|
-
wantsExplicit && (params.version = ref.version)
|
|
1037
|
-
|
|
1038
|
-
|
|
1441
|
+
const wantsExplicit = typeof ref.version == "number", params = { workflowId: ref.workflowId, engineTags: tags };
|
|
1442
|
+
return wantsExplicit && (params.version = ref.version), await client.fetch(
|
|
1443
|
+
definitionLookupGroq(wantsExplicit),
|
|
1444
|
+
params
|
|
1445
|
+
) ?? null;
|
|
1039
1446
|
}
|
|
1040
1447
|
async function prepareChildInstance(args) {
|
|
1041
|
-
const { client, parent, task, spawn, initialState, effectsContext, actor } = args, definition = await resolveLogicalDefinition(client, spawn.definitionRef, parent.tags ?? []);
|
|
1448
|
+
const { client, parent, task, spawn, initialState, effectsContext, actor, now } = args, definition = await resolveLogicalDefinition(client, spawn.definitionRef, parent.tags ?? []);
|
|
1042
1449
|
if (definition === null) {
|
|
1043
1450
|
const versionLabel = spawn.definitionRef.version === void 0 || spawn.definitionRef.version === "latest" ? "latest" : `v${spawn.definitionRef.version}`;
|
|
1044
1451
|
throw new Error(
|
|
1045
1452
|
`Spawn target workflowId="${spawn.definitionRef.workflowId}" ${versionLabel} not deployed (parent ${parent._id}, task ${task.id})`
|
|
1046
1453
|
);
|
|
1047
1454
|
}
|
|
1048
|
-
const childTags = parent.tags ?? [], childPrefix = childTags[0] ?? "wf", workflowResource = parent.workflowResource, childDocId = `${childPrefix}.wf-instance.${randomKey()}`, childRef = { id: gdrFromResource(workflowResource, childDocId), type:
|
|
1455
|
+
const childTags = parent.tags ?? [], childPrefix = childTags[0] ?? "wf", workflowResource = parent.workflowResource, childDocId = `${childPrefix}.wf-instance.${randomKey()}`, childRef = { id: gdrFromResource(workflowResource, childDocId), type: WORKFLOW_INSTANCE_TYPE }, ancestors = [
|
|
1049
1456
|
...parent.ancestors,
|
|
1050
1457
|
{
|
|
1051
|
-
id:
|
|
1052
|
-
type:
|
|
1458
|
+
id: selfGdr(parent),
|
|
1459
|
+
type: WORKFLOW_INSTANCE_TYPE
|
|
1053
1460
|
}
|
|
1054
1461
|
], inheritedPerspective = parent.perspective, childState = await resolveDeclaredState({
|
|
1055
1462
|
slotDefs: definition.state,
|
|
1056
1463
|
initialState,
|
|
1057
1464
|
ctx: {
|
|
1058
1465
|
client,
|
|
1466
|
+
now,
|
|
1059
1467
|
selfId: childDocId,
|
|
1060
1468
|
tags: childTags,
|
|
1061
1469
|
workflowResource,
|
|
@@ -1075,7 +1483,7 @@ async function prepareChildInstance(args) {
|
|
|
1075
1483
|
}
|
|
1076
1484
|
), base = buildInstanceBase({
|
|
1077
1485
|
id: childDocId,
|
|
1078
|
-
now
|
|
1486
|
+
now,
|
|
1079
1487
|
tags: childTags,
|
|
1080
1488
|
workflowResource,
|
|
1081
1489
|
workflowId: definition.workflowId,
|
|
@@ -1111,7 +1519,9 @@ async function checkSpawnCompletion(client, entry, spawn) {
|
|
|
1111
1519
|
}
|
|
1112
1520
|
}
|
|
1113
1521
|
async function invokeTask(args) {
|
|
1114
|
-
const { client, instanceId, taskId, options } = args, ctx = await loadContext(client, instanceId
|
|
1522
|
+
const { client, instanceId, taskId, options } = args, ctx = await loadContext(client, instanceId, {
|
|
1523
|
+
...options?.clock ? { clock: options.clock } : {}
|
|
1524
|
+
});
|
|
1115
1525
|
return commitInvoke(ctx, taskId, options?.actor);
|
|
1116
1526
|
}
|
|
1117
1527
|
async function commitInvoke(ctx, taskId, actor) {
|
|
@@ -1123,41 +1533,41 @@ async function commitInvoke(ctx, taskId, actor) {
|
|
|
1123
1533
|
const mutation = startMutation(ctx.instance), mutEntry = requireMutationTaskEntry(mutation, taskId);
|
|
1124
1534
|
return await activateTask(ctx, mutation, task, mutEntry, actor), await persist(ctx, mutation), { invoked: !0, taskId };
|
|
1125
1535
|
}
|
|
1126
|
-
async function autoResolveOnActivate(ctx, mutation, task, entry) {
|
|
1536
|
+
async function autoResolveOnActivate(ctx, mutation, task, entry, now) {
|
|
1127
1537
|
const checks = [
|
|
1128
1538
|
{ filter: task.failWhen, outcome: "failed", systemId: "engine.failWhen" },
|
|
1129
1539
|
{ filter: task.completeWhen, outcome: "done", systemId: "engine.completeWhen" }
|
|
1130
1540
|
];
|
|
1131
|
-
for (const { filter, outcome, systemId } of checks)
|
|
1132
|
-
if (filter
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
), !0;
|
|
1144
|
-
}
|
|
1541
|
+
for (const { filter, outcome, systemId } of checks)
|
|
1542
|
+
if (filter !== void 0 && await ctxEvaluateFilter(ctx, filter))
|
|
1543
|
+
return entry.status = outcome, entry.completedAt = now, entry.completedBy = systemId, mutation.history.push(
|
|
1544
|
+
taskStatusChangedEntry({
|
|
1545
|
+
stageId: mutation.currentStageId,
|
|
1546
|
+
taskId: task.id,
|
|
1547
|
+
from: "active",
|
|
1548
|
+
to: outcome,
|
|
1549
|
+
at: now,
|
|
1550
|
+
actor: { kind: "system", id: systemId }
|
|
1551
|
+
})
|
|
1552
|
+
), !0;
|
|
1145
1553
|
return !1;
|
|
1146
1554
|
}
|
|
1147
1555
|
async function activateTask(ctx, mutation, task, entry, actor) {
|
|
1148
|
-
|
|
1556
|
+
const now = ctx.now;
|
|
1557
|
+
if (entry.status = "active", entry.startedAt = now, task.state !== void 0 && task.state.length > 0) {
|
|
1149
1558
|
const stage = findStage(ctx.definition, mutation.currentStageId);
|
|
1150
1559
|
entry.state = await resolveTaskStateSlots({
|
|
1151
1560
|
client: ctx.client,
|
|
1152
1561
|
instance: ctx.instance,
|
|
1153
1562
|
stage,
|
|
1154
|
-
task
|
|
1563
|
+
task,
|
|
1564
|
+
now
|
|
1155
1565
|
});
|
|
1156
1566
|
}
|
|
1157
|
-
if (!await autoResolveOnActivate(ctx, mutation, task, entry) && (mutation.history.push({
|
|
1567
|
+
if (!await autoResolveOnActivate(ctx, mutation, task, entry, now) && (mutation.history.push({
|
|
1158
1568
|
_key: randomKey(),
|
|
1159
1569
|
_type: "workflow.history.taskActivated",
|
|
1160
|
-
at:
|
|
1570
|
+
at: now,
|
|
1161
1571
|
stageId: mutation.currentStageId,
|
|
1162
1572
|
taskId: task.id,
|
|
1163
1573
|
...actor !== void 0 ? { actor } : {}
|
|
@@ -1177,18 +1587,19 @@ async function activateTask(ctx, mutation, task, entry, actor) {
|
|
|
1177
1587
|
mutation.history.push({
|
|
1178
1588
|
_key: randomKey(),
|
|
1179
1589
|
_type: "workflow.history.spawned",
|
|
1180
|
-
at:
|
|
1590
|
+
at: now,
|
|
1181
1591
|
taskId: task.id,
|
|
1182
1592
|
instanceRef: ref
|
|
1183
1593
|
});
|
|
1184
1594
|
if (await checkSpawnCompletion(ctx.client, entry, task.spawns)) {
|
|
1185
1595
|
const previousStatus = entry.status;
|
|
1186
|
-
entry.status = "done", entry.completedAt =
|
|
1596
|
+
entry.status = "done", entry.completedAt = now, mutation.history.push(
|
|
1187
1597
|
taskStatusChangedEntry({
|
|
1188
1598
|
stageId: mutation.currentStageId,
|
|
1189
1599
|
taskId: task.id,
|
|
1190
1600
|
from: previousStatus,
|
|
1191
1601
|
to: "done",
|
|
1602
|
+
at: now,
|
|
1192
1603
|
...actor !== void 0 ? { actor } : {}
|
|
1193
1604
|
})
|
|
1194
1605
|
);
|
|
@@ -1205,7 +1616,7 @@ async function buildStageTasks(ctx, stage) {
|
|
|
1205
1616
|
status: inScope ? "pending" : "skipped",
|
|
1206
1617
|
...task.filter !== void 0 ? {
|
|
1207
1618
|
filterEvaluation: {
|
|
1208
|
-
at:
|
|
1619
|
+
at: ctx.now,
|
|
1209
1620
|
truthy: inScope
|
|
1210
1621
|
}
|
|
1211
1622
|
} : {}
|
|
@@ -1217,7 +1628,9 @@ function activatesOnStageEnter(task) {
|
|
|
1217
1628
|
return task.invokeOn === "stageEnter" || task.spawns !== void 0 || task.completeWhen !== void 0 || task.failWhen !== void 0;
|
|
1218
1629
|
}
|
|
1219
1630
|
async function setStage(args) {
|
|
1220
|
-
const { client, instanceId, targetStageId, reason, options } = args, ctx = await loadContext(client, instanceId
|
|
1631
|
+
const { client, instanceId, targetStageId, reason, options } = args, ctx = await loadContext(client, instanceId, {
|
|
1632
|
+
...options?.clock ? { clock: options.clock } : {}
|
|
1633
|
+
});
|
|
1221
1634
|
return commitSetStage(ctx, targetStageId, reason, options?.actor);
|
|
1222
1635
|
}
|
|
1223
1636
|
async function enterStage(ctx, mutation, nextStage, actor, at) {
|
|
@@ -1229,7 +1642,8 @@ async function enterStage(ctx, mutation, nextStage, actor, at) {
|
|
|
1229
1642
|
state: await resolveStageStateSlots({
|
|
1230
1643
|
client: ctx.client,
|
|
1231
1644
|
instance: ctx.instance,
|
|
1232
|
-
stage: nextStage
|
|
1645
|
+
stage: nextStage,
|
|
1646
|
+
now: at
|
|
1233
1647
|
}),
|
|
1234
1648
|
tasks: await buildStageTasks(ctx, nextStage)
|
|
1235
1649
|
};
|
|
@@ -1248,7 +1662,7 @@ async function enterStage(ctx, mutation, nextStage, actor, at) {
|
|
|
1248
1662
|
nextStage.kind === "terminal" && (mutation.completedAt = at);
|
|
1249
1663
|
}
|
|
1250
1664
|
async function beginStageMove(ctx, currentStage, actor) {
|
|
1251
|
-
const mutation = startMutation(ctx.instance), at =
|
|
1665
|
+
const mutation = startMutation(ctx.instance), at = ctx.now;
|
|
1252
1666
|
return await queueEffects(
|
|
1253
1667
|
ctx,
|
|
1254
1668
|
mutation,
|
|
@@ -1282,13 +1696,46 @@ async function commitSetStage(ctx, targetStageId, reason, actor) {
|
|
|
1282
1696
|
};
|
|
1283
1697
|
}
|
|
1284
1698
|
async function persistStageMove(ctx, mutation, exitedStageId, enteredStageId) {
|
|
1285
|
-
await
|
|
1699
|
+
await persistThenDeploy(
|
|
1700
|
+
ctx,
|
|
1701
|
+
mutation,
|
|
1702
|
+
() => deployStageGuards({
|
|
1703
|
+
client: ctx.client,
|
|
1704
|
+
clientForGdr: ctx.clientForGdr,
|
|
1705
|
+
instance: materializeInstance(ctx.instance, mutation),
|
|
1706
|
+
definition: ctx.definition,
|
|
1707
|
+
stageId: enteredStageId,
|
|
1708
|
+
now: ctx.now
|
|
1709
|
+
})
|
|
1710
|
+
), await retractStageGuards({
|
|
1286
1711
|
client: ctx.client,
|
|
1287
1712
|
clientForGdr: ctx.clientForGdr,
|
|
1288
1713
|
instance: ctx.instance,
|
|
1289
1714
|
definition: ctx.definition,
|
|
1290
|
-
exitedStageId,
|
|
1291
|
-
|
|
1715
|
+
stageId: exitedStageId,
|
|
1716
|
+
now: ctx.now
|
|
1717
|
+
});
|
|
1718
|
+
}
|
|
1719
|
+
async function persistThenDeploy(ctx, mutation, deploy) {
|
|
1720
|
+
const spawned = mutation.pendingCreates.length > 0, committed = await persist(ctx, mutation);
|
|
1721
|
+
await deployOrRollback({
|
|
1722
|
+
client: ctx.client,
|
|
1723
|
+
instanceId: ctx.instance._id,
|
|
1724
|
+
committedRev: committed._rev,
|
|
1725
|
+
restore: instanceStateFields(ctx.instance),
|
|
1726
|
+
unset: ctx.instance.completedAt === void 0 ? ["completedAt"] : [],
|
|
1727
|
+
reversible: !spawned,
|
|
1728
|
+
deploy
|
|
1729
|
+
});
|
|
1730
|
+
}
|
|
1731
|
+
async function refreshStageGuards(ctx, mutation, stageId) {
|
|
1732
|
+
await deployStageGuards({
|
|
1733
|
+
client: ctx.client,
|
|
1734
|
+
clientForGdr: ctx.clientForGdr,
|
|
1735
|
+
instance: materializeInstance(ctx.instance, mutation),
|
|
1736
|
+
definition: ctx.definition,
|
|
1737
|
+
stageId,
|
|
1738
|
+
now: ctx.now
|
|
1292
1739
|
});
|
|
1293
1740
|
}
|
|
1294
1741
|
async function commitTransition(ctx, action, actor) {
|
|
@@ -1330,26 +1777,29 @@ async function pickTransition(ctx, stage, action) {
|
|
|
1330
1777
|
if (await ctxEvaluateFilter(ctx, candidate.filter)) return candidate;
|
|
1331
1778
|
}
|
|
1332
1779
|
async function evaluateAutoTransitions(args) {
|
|
1333
|
-
const { client, instanceId, options, clientForGdr } = args, ctx = await loadContext(client, instanceId,
|
|
1780
|
+
const { client, instanceId, options, clientForGdr, clock, overlay } = args, ctx = await loadContext(client, instanceId, {
|
|
1781
|
+
...clientForGdr ? { clientForGdr } : {},
|
|
1782
|
+
...clock ? { clock } : {},
|
|
1783
|
+
...overlay ? { overlay } : {}
|
|
1784
|
+
});
|
|
1334
1785
|
return commitTransition(ctx, void 0, options?.actor);
|
|
1335
1786
|
}
|
|
1336
|
-
async function primeInitialStage(client, instanceId, actor, clientForGdr) {
|
|
1787
|
+
async function primeInitialStage(client, instanceId, actor, clientForGdr, clock) {
|
|
1337
1788
|
const instance = await client.getDocument(instanceId);
|
|
1338
1789
|
if (!instance || instance.stages.length > 0 || instance.pendingEffects.length > 0) return;
|
|
1339
1790
|
const definition = JSON.parse(instance.definitionSnapshot), stage = definition.stages.find((s) => s.id === instance.currentStageId);
|
|
1340
1791
|
if (stage === void 0) return;
|
|
1341
|
-
const snapshot = await hydrateSnapshot({
|
|
1792
|
+
const now = (clock ?? wallClock)(), snapshot = await hydrateSnapshot({
|
|
1342
1793
|
client,
|
|
1343
1794
|
clientForGdr: clientForGdr ?? (() => client),
|
|
1344
|
-
instance
|
|
1345
|
-
definition
|
|
1795
|
+
instance
|
|
1346
1796
|
}), tasks = [];
|
|
1347
1797
|
for (const task of stage.tasks ?? []) {
|
|
1348
1798
|
const inScope = await evaluateFilter({
|
|
1349
1799
|
filter: task.filter,
|
|
1350
1800
|
definition,
|
|
1351
1801
|
snapshot,
|
|
1352
|
-
params: buildParams(instance)
|
|
1802
|
+
params: buildParams(instance, now)
|
|
1353
1803
|
});
|
|
1354
1804
|
tasks.push({
|
|
1355
1805
|
_key: randomKey(),
|
|
@@ -1360,8 +1810,8 @@ async function primeInitialStage(client, instanceId, actor, clientForGdr) {
|
|
|
1360
1810
|
const initialStageEntry = {
|
|
1361
1811
|
_key: randomKey(),
|
|
1362
1812
|
id: stage.id,
|
|
1363
|
-
enteredAt:
|
|
1364
|
-
state: await resolveStageStateSlots({ client, instance, stage }),
|
|
1813
|
+
enteredAt: now,
|
|
1814
|
+
state: await resolveStageStateSlots({ client, instance, stage, now }),
|
|
1365
1815
|
tasks
|
|
1366
1816
|
}, pendingEffects = [], newHistory = [...instance.history];
|
|
1367
1817
|
for (const effect of stage.onEnter ?? []) {
|
|
@@ -1369,29 +1819,44 @@ async function primeInitialStage(client, instanceId, actor, clientForGdr) {
|
|
|
1369
1819
|
bindings: effect.bindings,
|
|
1370
1820
|
staticInput: effect.input,
|
|
1371
1821
|
instance,
|
|
1822
|
+
now,
|
|
1372
1823
|
...actor !== void 0 ? { actor } : {}
|
|
1373
1824
|
}), { pending, history } = buildQueuedEffect(
|
|
1374
1825
|
effect,
|
|
1375
1826
|
{ kind: "stageEnter", id: stage.id },
|
|
1376
1827
|
params,
|
|
1377
|
-
actor
|
|
1828
|
+
actor,
|
|
1829
|
+
now
|
|
1378
1830
|
);
|
|
1379
1831
|
pendingEffects.push(pending), newHistory.push(history);
|
|
1380
1832
|
}
|
|
1381
|
-
await client.patch(instance._id).set({
|
|
1833
|
+
const committed = await client.patch(instance._id).set({
|
|
1382
1834
|
stages: [initialStageEntry],
|
|
1383
1835
|
pendingEffects,
|
|
1384
1836
|
history: newHistory,
|
|
1385
|
-
lastChangedAt:
|
|
1386
|
-
}).ifRevisionId(instance._rev).commit()
|
|
1837
|
+
lastChangedAt: now
|
|
1838
|
+
}).ifRevisionId(instance._rev).commit();
|
|
1839
|
+
await deployOrRollback({
|
|
1387
1840
|
client,
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1841
|
+
instanceId: instance._id,
|
|
1842
|
+
committedRev: committed._rev,
|
|
1843
|
+
restore: {
|
|
1844
|
+
stages: instance.stages,
|
|
1845
|
+
pendingEffects: instance.pendingEffects,
|
|
1846
|
+
history: instance.history
|
|
1847
|
+
},
|
|
1848
|
+
reversible: !0,
|
|
1849
|
+
deploy: () => deployStageGuards({
|
|
1850
|
+
client,
|
|
1851
|
+
clientForGdr: clientForGdr ?? (() => client),
|
|
1852
|
+
instance,
|
|
1853
|
+
definition,
|
|
1854
|
+
stageId: stage.id,
|
|
1855
|
+
now
|
|
1856
|
+
})
|
|
1857
|
+
}), await autoActivatePrimedTasks(client, instanceId, definition, stage, actor, clientForGdr, clock);
|
|
1393
1858
|
}
|
|
1394
|
-
async function autoActivatePrimedTasks(client, instanceId, definition, stage, actor, clientForGdr) {
|
|
1859
|
+
async function autoActivatePrimedTasks(client, instanceId, definition, stage, actor, clientForGdr, clock) {
|
|
1395
1860
|
const resolvedClientForGdr = clientForGdr ?? (() => client);
|
|
1396
1861
|
for (const task of stage.tasks ?? []) {
|
|
1397
1862
|
if (!activatesOnStageEnter(task)) continue;
|
|
@@ -1401,7 +1866,8 @@ async function autoActivatePrimedTasks(client, instanceId, definition, stage, ac
|
|
|
1401
1866
|
client,
|
|
1402
1867
|
clientForGdr: resolvedClientForGdr,
|
|
1403
1868
|
instance: updated,
|
|
1404
|
-
definition
|
|
1869
|
+
definition,
|
|
1870
|
+
...clock ? { clock } : {}
|
|
1405
1871
|
}), mutation = startMutation(updated), mutEntry = currentTasks(mutation).find((t) => t.id === task.id);
|
|
1406
1872
|
mutEntry && mutEntry.status === "pending" && (await activateTask(updatedCtx, mutation, task, mutEntry, actor), await persist(updatedCtx, mutation));
|
|
1407
1873
|
}
|
|
@@ -1419,8 +1885,12 @@ async function gatherAutoResolveCandidates(ctx, tasks) {
|
|
|
1419
1885
|
}
|
|
1420
1886
|
return candidates;
|
|
1421
1887
|
}
|
|
1422
|
-
async function resolveCompleteWhen(client, instanceId, clientForGdr) {
|
|
1423
|
-
const ctx = await loadContext(client, instanceId,
|
|
1888
|
+
async function resolveCompleteWhen(client, instanceId, clientForGdr, clock, overlay) {
|
|
1889
|
+
const ctx = await loadContext(client, instanceId, {
|
|
1890
|
+
...clientForGdr ? { clientForGdr } : {},
|
|
1891
|
+
...clock ? { clock } : {},
|
|
1892
|
+
...overlay ? { overlay } : {}
|
|
1893
|
+
}), stage = findStage(ctx.definition, ctx.instance.currentStageId), tasksWithAutoPredicate = (stage.tasks ?? []).filter(
|
|
1424
1894
|
(t) => t.completeWhen !== void 0 || t.failWhen !== void 0
|
|
1425
1895
|
);
|
|
1426
1896
|
if (tasksWithAutoPredicate.length === 0) return 0;
|
|
@@ -1434,302 +1904,480 @@ async function resolveCompleteWhen(client, instanceId, clientForGdr) {
|
|
|
1434
1904
|
const previousStatus = mutEntry.status, actor = {
|
|
1435
1905
|
kind: "system",
|
|
1436
1906
|
id: outcome === "failed" ? "engine.failWhen" : "engine.completeWhen"
|
|
1437
|
-
};
|
|
1438
|
-
mutEntry.status = outcome, mutEntry.completedAt =
|
|
1907
|
+
}, now = ctx.now;
|
|
1908
|
+
mutEntry.status = outcome, mutEntry.completedAt = now, mutEntry.completedBy = actor.id, mutation.history.push(
|
|
1439
1909
|
taskStatusChangedEntry({
|
|
1440
1910
|
stageId: stage.id,
|
|
1441
1911
|
taskId: task.id,
|
|
1442
1912
|
from: previousStatus,
|
|
1443
1913
|
to: outcome,
|
|
1914
|
+
at: now,
|
|
1444
1915
|
actor
|
|
1445
1916
|
})
|
|
1446
1917
|
), count++;
|
|
1447
1918
|
}
|
|
1448
1919
|
return count > 0 && await persist(ctx, mutation), count;
|
|
1449
1920
|
}
|
|
1450
|
-
async function cascadeAutoTransitions(client, instanceId, actor, clientForGdr) {
|
|
1921
|
+
async function cascadeAutoTransitions(client, instanceId, actor, clientForGdr, clock, overlay) {
|
|
1451
1922
|
let count = 0;
|
|
1452
1923
|
for (; ; ) {
|
|
1453
|
-
if (await resolveCompleteWhen(client, instanceId, clientForGdr), !(await evaluateAutoTransitions({
|
|
1924
|
+
if (await resolveCompleteWhen(client, instanceId, clientForGdr, clock, overlay), !(await evaluateAutoTransitions({
|
|
1454
1925
|
client,
|
|
1455
1926
|
instanceId,
|
|
1456
1927
|
...actor !== void 0 ? { options: { actor } } : {},
|
|
1457
|
-
...clientForGdr ? { clientForGdr } : {}
|
|
1928
|
+
...clientForGdr ? { clientForGdr } : {},
|
|
1929
|
+
...clock ? { clock } : {},
|
|
1930
|
+
...overlay ? { overlay } : {}
|
|
1458
1931
|
})).fired) return count;
|
|
1459
|
-
if (count++, count
|
|
1460
|
-
throw new
|
|
1461
|
-
`Cascade did not stabilise after ${CASCADE_LIMIT} auto-transitions on ${instanceId} \u2014 possible infinite loop`
|
|
1462
|
-
);
|
|
1932
|
+
if (count++, count >= CASCADE_LIMIT)
|
|
1933
|
+
throw new CascadeLimitError({ instanceId, limit: CASCADE_LIMIT });
|
|
1463
1934
|
}
|
|
1464
1935
|
}
|
|
1465
|
-
async function
|
|
1466
|
-
const
|
|
1467
|
-
if (!instance || instance.ancestors.length === 0) return;
|
|
1468
|
-
const parentRef = instance.ancestors[instance.ancestors.length - 1];
|
|
1936
|
+
async function findSatisfiedParentSpawn(client, child) {
|
|
1937
|
+
const parentRef = child.ancestors.at(-1);
|
|
1469
1938
|
if (parentRef === void 0) return;
|
|
1470
|
-
const
|
|
1471
|
-
if (!parent || parent._type !==
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1939
|
+
const parent = await client.getDocument(gdrToBareId(parentRef.id));
|
|
1940
|
+
if (!parent || parent._type !== WORKFLOW_INSTANCE_TYPE || typeof parent.definitionSnapshot != "string") return;
|
|
1941
|
+
const definition = JSON.parse(parent.definitionSnapshot), stage = definition.stages.find((s) => s.id === parent.currentStageId);
|
|
1942
|
+
if (stage === void 0) return;
|
|
1943
|
+
const parentCurrentTasks = findCurrentTasks(parent), task = (stage.tasks ?? []).find((t) => t.spawns === void 0 ? !1 : parentCurrentTasks.find((e) => e.id === t.id)?.spawnedInstances?.some((r) => gdrToBareId(r.id) === child._id) ?? !1);
|
|
1944
|
+
if (task?.spawns === void 0) return;
|
|
1945
|
+
const entry = parentCurrentTasks.find((e) => e.id === task.id);
|
|
1946
|
+
if (!(entry === void 0 || entry.status === "done") && await checkSpawnCompletion(client, entry, task.spawns))
|
|
1947
|
+
return { parent, definition, stage, task };
|
|
1948
|
+
}
|
|
1949
|
+
async function propagateToAncestors(client, instanceId, actor, clientForGdr, clock) {
|
|
1950
|
+
const instance = await client.getDocument(instanceId);
|
|
1951
|
+
if (!instance) return;
|
|
1952
|
+
const found = await findSatisfiedParentSpawn(client, instance);
|
|
1953
|
+
if (found === void 0) return;
|
|
1954
|
+
const { parent, definition, stage, task } = found, ctx = await buildEngineContext({
|
|
1480
1955
|
client,
|
|
1481
1956
|
clientForGdr: clientForGdr ?? (() => client),
|
|
1482
1957
|
instance: parent,
|
|
1483
|
-
definition
|
|
1484
|
-
|
|
1958
|
+
definition,
|
|
1959
|
+
...clock ? { clock } : {}
|
|
1960
|
+
}), mutation = startMutation(parent), mutEntry = currentTasks(mutation).find((e) => e.id === task.id);
|
|
1485
1961
|
if (mutEntry === void 0) return;
|
|
1486
|
-
const previousStatus = mutEntry.status;
|
|
1487
|
-
mutEntry.status = "done", mutEntry.completedAt =
|
|
1962
|
+
const previousStatus = mutEntry.status, now = ctx.now;
|
|
1963
|
+
mutEntry.status = "done", mutEntry.completedAt = now, mutation.history.push(
|
|
1488
1964
|
taskStatusChangedEntry({
|
|
1489
|
-
stageId:
|
|
1490
|
-
taskId:
|
|
1965
|
+
stageId: stage.id,
|
|
1966
|
+
taskId: task.id,
|
|
1491
1967
|
from: previousStatus,
|
|
1492
1968
|
to: "done",
|
|
1969
|
+
at: now,
|
|
1493
1970
|
...actor !== void 0 ? { actor } : {}
|
|
1494
1971
|
})
|
|
1495
|
-
), await persist(ctx, mutation), await cascadeAutoTransitions(client,
|
|
1496
|
-
}
|
|
1497
|
-
function startMutation(instance) {
|
|
1498
|
-
return {
|
|
1499
|
-
currentStageId: instance.currentStageId,
|
|
1500
|
-
// Deep-ish copy. Per-scope state slot arrays need their own copies
|
|
1501
|
-
// so ops can mutate without leaking into the source.
|
|
1502
|
-
state: (instance.state ?? []).map((s) => ({ ...s })),
|
|
1503
|
-
stages: instance.stages.map((s) => ({
|
|
1504
|
-
...s,
|
|
1505
|
-
state: (s.state ?? []).map((slot) => ({ ...slot })),
|
|
1506
|
-
tasks: s.tasks.map((t) => ({
|
|
1507
|
-
...t,
|
|
1508
|
-
...t.state !== void 0 ? { state: t.state.map((slot) => ({ ...slot })) } : {}
|
|
1509
|
-
}))
|
|
1510
|
-
})),
|
|
1511
|
-
pendingEffects: [...instance.pendingEffects],
|
|
1512
|
-
effectHistory: [...instance.effectHistory],
|
|
1513
|
-
effectsContext: [...instance.effectsContext],
|
|
1514
|
-
history: [...instance.history],
|
|
1515
|
-
lastChangedAt: instance.lastChangedAt,
|
|
1516
|
-
...instance.completedAt !== void 0 ? { completedAt: instance.completedAt } : {},
|
|
1517
|
-
pendingCreates: []
|
|
1518
|
-
};
|
|
1972
|
+
), await persist(ctx, mutation), await cascadeAutoTransitions(client, parent._id, actor, clientForGdr, clock), await propagateToAncestors(client, parent._id, actor, clientForGdr, clock);
|
|
1519
1973
|
}
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
...args.actor !== void 0 ? { actor: args.actor } : {}
|
|
1530
|
-
};
|
|
1531
|
-
}
|
|
1532
|
-
function stageTransitionHistory(args) {
|
|
1533
|
-
const { fromStageId, toStageId, at, transitionId, actor, via, reason } = args, shared = {
|
|
1534
|
-
at,
|
|
1535
|
-
...transitionId !== void 0 ? { transitionId } : {},
|
|
1536
|
-
...actor !== void 0 ? { actor } : {},
|
|
1537
|
-
...via !== void 0 ? { via } : {},
|
|
1538
|
-
...reason !== void 0 ? { reason } : {}
|
|
1539
|
-
};
|
|
1540
|
-
return [
|
|
1541
|
-
{
|
|
1542
|
-
_key: randomKey(),
|
|
1543
|
-
_type: "workflow.history.stageExited",
|
|
1544
|
-
stageId: fromStageId,
|
|
1545
|
-
toStageId,
|
|
1546
|
-
...shared
|
|
1547
|
-
},
|
|
1548
|
-
{
|
|
1549
|
-
_key: randomKey(),
|
|
1550
|
-
_type: "workflow.history.transitionFired",
|
|
1551
|
-
fromStageId,
|
|
1552
|
-
toStageId,
|
|
1553
|
-
...shared
|
|
1554
|
-
},
|
|
1555
|
-
{
|
|
1556
|
-
_key: randomKey(),
|
|
1557
|
-
_type: "workflow.history.stageEntered",
|
|
1558
|
-
stageId: toStageId,
|
|
1559
|
-
fromStageId,
|
|
1560
|
-
...shared
|
|
1561
|
-
}
|
|
1562
|
-
];
|
|
1563
|
-
}
|
|
1564
|
-
async function persist(ctx, mutation) {
|
|
1565
|
-
const set = {
|
|
1566
|
-
currentStageId: mutation.currentStageId,
|
|
1567
|
-
state: mutation.state,
|
|
1568
|
-
stages: mutation.stages,
|
|
1569
|
-
pendingEffects: mutation.pendingEffects,
|
|
1570
|
-
effectHistory: mutation.effectHistory,
|
|
1571
|
-
effectsContext: mutation.effectsContext,
|
|
1572
|
-
history: mutation.history,
|
|
1573
|
-
lastChangedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1574
|
-
};
|
|
1575
|
-
mutation.completedAt !== void 0 && (set.completedAt = mutation.completedAt);
|
|
1576
|
-
const pendingCreates = mutation.pendingCreates;
|
|
1577
|
-
if (pendingCreates.length === 0)
|
|
1578
|
-
return ctx.client.patch(ctx.instance._id).set(set).ifRevisionId(ctx.instance._rev).commit();
|
|
1579
|
-
const tx = ctx.client.transaction();
|
|
1580
|
-
for (const body of pendingCreates) tx.create(body);
|
|
1581
|
-
tx.patch(ctx.client.patch(ctx.instance._id).set(set).ifRevisionId(ctx.instance._rev)), await tx.commit(), mutation.pendingCreates = [];
|
|
1582
|
-
const actorForPriming = void 0;
|
|
1583
|
-
for (const body of pendingCreates)
|
|
1584
|
-
await primeInitialStage(ctx.client, body._id, actorForPriming, ctx.clientForGdr), await cascadeAutoTransitions(ctx.client, body._id, actorForPriming, ctx.clientForGdr), await propagateToAncestors(ctx.client, body._id, actorForPriming, ctx.clientForGdr);
|
|
1585
|
-
const reloaded = await ctx.client.getDocument(ctx.instance._id);
|
|
1586
|
-
if (!reloaded)
|
|
1587
|
-
throw new Error(`Instance ${ctx.instance._id} disappeared after transaction commit`);
|
|
1588
|
-
return reloaded;
|
|
1589
|
-
}
|
|
1590
|
-
function findOpenStageEntry(host) {
|
|
1591
|
-
return host.stages.find((s) => s.id === host.currentStageId && s.exitedAt === void 0);
|
|
1974
|
+
const parsedFilters = /* @__PURE__ */ new Map();
|
|
1975
|
+
async function matchesFilter(args) {
|
|
1976
|
+
const { document, filter, userId } = args;
|
|
1977
|
+
parsedFilters.has(filter) || parsedFilters.set(filter, parse(`*[${filter}]`));
|
|
1978
|
+
const ast = parsedFilters.get(filter), data = await (await evaluate(ast, {
|
|
1979
|
+
dataset: [document],
|
|
1980
|
+
...userId !== void 0 ? { identity: userId } : {}
|
|
1981
|
+
})).get();
|
|
1982
|
+
return Array.isArray(data) && data.length === 1;
|
|
1592
1983
|
}
|
|
1593
|
-
function
|
|
1594
|
-
const
|
|
1595
|
-
if (
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1984
|
+
async function grantsPermissionOn(args) {
|
|
1985
|
+
const { document, grants, permission, userId } = args;
|
|
1986
|
+
if (!document || grants.length === 0) return !1;
|
|
1987
|
+
for (const grant of grants)
|
|
1988
|
+
if (grant.permissions.includes(permission) && await matchesFilter({
|
|
1989
|
+
document,
|
|
1990
|
+
filter: grant.filter,
|
|
1991
|
+
...userId !== void 0 ? { userId } : {}
|
|
1992
|
+
}))
|
|
1993
|
+
return !0;
|
|
1994
|
+
return !1;
|
|
1600
1995
|
}
|
|
1601
|
-
function
|
|
1602
|
-
|
|
1996
|
+
async function fetchGrants(args) {
|
|
1997
|
+
const { client, resourcePath, signal } = args;
|
|
1998
|
+
return client.request({ url: resourcePath, ...signal ? { signal } : {} });
|
|
1603
1999
|
}
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
2000
|
+
const actorCache = /* @__PURE__ */ new WeakMap(), grantsCache = /* @__PURE__ */ new WeakMap();
|
|
2001
|
+
async function resolveAccess(client, args = {}) {
|
|
2002
|
+
const overrideActor = args.override?.actor, overrideGrants = args.override?.grants;
|
|
2003
|
+
if (overrideActor !== void 0 && overrideGrants !== void 0)
|
|
2004
|
+
return { actor: overrideActor, grants: overrideGrants };
|
|
2005
|
+
const requestFn = client.request === void 0 ? void 0 : (opts) => client.request(opts);
|
|
2006
|
+
if (requestFn === void 0) {
|
|
2007
|
+
if (overrideActor === void 0)
|
|
2008
|
+
throw new Error(
|
|
2009
|
+
"workflow: no actor available. The engine resolves the actor from the client's token via `client.request({ url: '/users/me' })`. Supply a real `@sanity/client` configured with a token, or pass an explicit `access.actor` override (the test bench provides one by default)."
|
|
2010
|
+
);
|
|
2011
|
+
return { actor: overrideActor };
|
|
2012
|
+
}
|
|
2013
|
+
const actorPromise = overrideActor !== void 0 ? Promise.resolve(overrideActor) : cachedActor(client, requestFn), grantsPromise = resolveGrants(overrideGrants, args.grantsFromPath, client, requestFn), [actor, grants] = await Promise.all([actorPromise, grantsPromise]);
|
|
2014
|
+
if (actor === void 0)
|
|
1607
2015
|
throw new Error(
|
|
1608
|
-
|
|
2016
|
+
"workflow: failed to resolve actor from `/users/me`. The client is configured but the endpoint returned no usable identity \u2014 check the token, or pass `access.actor` explicitly."
|
|
1609
2017
|
);
|
|
1610
|
-
return {
|
|
1611
|
-
}
|
|
1612
|
-
function requireMutationTaskEntry(mutation, taskId) {
|
|
1613
|
-
const mutEntry = currentTasks(mutation).find((t) => t.id === taskId);
|
|
1614
|
-
if (mutEntry === void 0)
|
|
1615
|
-
throw new Error(`Task "${taskId}" disappeared from mutation copy \u2014 invariant broken`);
|
|
1616
|
-
return mutEntry;
|
|
2018
|
+
return { actor, ...grants !== void 0 ? { grants } : {} };
|
|
1617
2019
|
}
|
|
1618
|
-
function
|
|
1619
|
-
|
|
2020
|
+
function cachedActor(client, requestFn) {
|
|
2021
|
+
const cached = actorCache.get(client);
|
|
2022
|
+
if (cached !== void 0) return cached;
|
|
2023
|
+
const pending = fetchActor(requestFn).catch((err) => {
|
|
2024
|
+
throw actorCache.get(client) === pending && actorCache.delete(client), err;
|
|
2025
|
+
});
|
|
2026
|
+
return actorCache.set(client, pending), pending;
|
|
1620
2027
|
}
|
|
1621
|
-
function
|
|
1622
|
-
return
|
|
2028
|
+
function resolveGrants(overrideGrants, grantsFromPath, client, requestFn) {
|
|
2029
|
+
return overrideGrants !== void 0 ? Promise.resolve(overrideGrants) : grantsFromPath !== void 0 ? cachedGrants(client, requestFn, grantsFromPath) : Promise.resolve(void 0);
|
|
1623
2030
|
}
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
status,
|
|
1630
|
-
outputs,
|
|
1631
|
-
detail,
|
|
1632
|
-
error,
|
|
1633
|
-
durationMs,
|
|
1634
|
-
options?.actor
|
|
1635
|
-
);
|
|
2031
|
+
function cachedGrants(client, requestFn, resourcePath) {
|
|
2032
|
+
let byPath = grantsCache.get(client);
|
|
2033
|
+
byPath === void 0 && (byPath = /* @__PURE__ */ new Map(), grantsCache.set(client, byPath));
|
|
2034
|
+
let cached = byPath.get(resourcePath);
|
|
2035
|
+
return cached === void 0 && (cached = fetchGrantsCached(requestFn, resourcePath), byPath.set(resourcePath, cached)), cached;
|
|
1636
2036
|
}
|
|
1637
|
-
function
|
|
1638
|
-
|
|
2037
|
+
async function fetchActor(requestFn) {
|
|
2038
|
+
let user;
|
|
2039
|
+
try {
|
|
2040
|
+
user = await requestFn({
|
|
2041
|
+
uri: "/users/me",
|
|
2042
|
+
tag: "workflow.access.resolve-actor"
|
|
2043
|
+
});
|
|
2044
|
+
} catch (err) {
|
|
2045
|
+
throw new Error(
|
|
2046
|
+
'workflow: /users/me request failed. The engine resolves the actor from the client\'s token via `client.request({ uri: "/users/me" })`. Check the token/connectivity, or pass an explicit `access.actor` override.',
|
|
2047
|
+
{ cause: err }
|
|
2048
|
+
);
|
|
2049
|
+
}
|
|
2050
|
+
if (!user || typeof user.id != "string" || user.id.length === 0) return;
|
|
2051
|
+
const roleNames = user.roles?.map((r) => r.name).filter((n) => !!n) ?? [];
|
|
1639
2052
|
return {
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
...
|
|
1643
|
-
...pending.description !== void 0 ? { description: pending.description } : {},
|
|
1644
|
-
params: pending.params,
|
|
1645
|
-
origin: pending.origin,
|
|
1646
|
-
...actor !== void 0 ? { actor } : pending.actor !== void 0 ? { actor: pending.actor } : {},
|
|
1647
|
-
ranAt,
|
|
1648
|
-
...durationMs !== void 0 ? { durationMs } : {},
|
|
1649
|
-
status,
|
|
1650
|
-
...detail !== void 0 ? { detail } : {},
|
|
1651
|
-
...error !== void 0 ? { error } : {},
|
|
1652
|
-
...outputs !== void 0 ? { outputs } : {}
|
|
2053
|
+
kind: "user",
|
|
2054
|
+
id: user.id,
|
|
2055
|
+
...roleNames.length > 0 ? { roles: roleNames } : {}
|
|
1653
2056
|
};
|
|
1654
2057
|
}
|
|
1655
|
-
function
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
2058
|
+
async function fetchGrantsCached(requestFn, resourcePath) {
|
|
2059
|
+
try {
|
|
2060
|
+
return await fetchGrants({ client: { request: requestFn }, resourcePath });
|
|
2061
|
+
} catch (err) {
|
|
2062
|
+
console.warn(
|
|
2063
|
+
`workflow: failed to fetch grants from "${resourcePath}"; skipping permission gate. Original error: ${err instanceof Error ? err.message : String(err)}`
|
|
2064
|
+
);
|
|
2065
|
+
return;
|
|
1659
2066
|
}
|
|
1660
2067
|
}
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
const
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
}
|
|
1682
|
-
function buildQueuedEffect(effect, origin, params, actor) {
|
|
1683
|
-
const key = randomKey(), pending = {
|
|
1684
|
-
_key: key,
|
|
1685
|
-
_type: "workflow.pendingEffect",
|
|
1686
|
-
effectId: effect.id,
|
|
1687
|
-
...effect.title !== void 0 ? { title: effect.title } : {},
|
|
1688
|
-
...effect.description !== void 0 ? { description: effect.description } : {},
|
|
1689
|
-
...effect.bindings !== void 0 ? { bindings: effect.bindings } : {},
|
|
1690
|
-
params,
|
|
1691
|
-
origin,
|
|
1692
|
-
...actor !== void 0 ? { actor } : {},
|
|
1693
|
-
queuedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1694
|
-
}, history = {
|
|
1695
|
-
_key: randomKey(),
|
|
1696
|
-
_type: "workflow.history.effectQueued",
|
|
1697
|
-
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1698
|
-
effectKey: key,
|
|
1699
|
-
effectId: effect.id,
|
|
1700
|
-
origin
|
|
1701
|
-
};
|
|
1702
|
-
return { pending, history };
|
|
2068
|
+
const WILDCARD_ROLE = "*";
|
|
2069
|
+
async function evaluateInstance(args) {
|
|
2070
|
+
const { client, tags, instanceId, resourceClients } = args, now = (args.clock ?? wallClock)();
|
|
2071
|
+
validateTags(tags);
|
|
2072
|
+
const { actor, grants } = await resolveAccess(client, {
|
|
2073
|
+
...args.access !== void 0 ? { override: args.access } : {},
|
|
2074
|
+
...args.grantsFromPath !== void 0 ? { grantsFromPath: args.grantsFromPath } : {}
|
|
2075
|
+
}), instance = await client.getDocument(instanceId);
|
|
2076
|
+
if (!instance)
|
|
2077
|
+
throw new Error(`Workflow instance "${instanceId}" not found`);
|
|
2078
|
+
if (!tags.some((t) => instance.tags?.includes(t)))
|
|
2079
|
+
throw new Error(`Workflow instance "${instanceId}" not visible to this engine (tag mismatch)`);
|
|
2080
|
+
const definition = parseDefinitionSnapshot(instance), snapshot = await hydrateSnapshot({ client, clientForGdr: resourceClients !== void 0 ? (parsed) => resourceClients(parsed) ?? client : () => client, instance });
|
|
2081
|
+
return evaluateFromSnapshot({
|
|
2082
|
+
instance,
|
|
2083
|
+
definition,
|
|
2084
|
+
actor,
|
|
2085
|
+
snapshot,
|
|
2086
|
+
now,
|
|
2087
|
+
...grants !== void 0 ? { grants } : {}
|
|
2088
|
+
});
|
|
1703
2089
|
}
|
|
1704
|
-
async function
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
2090
|
+
async function evaluateFromSnapshot(args) {
|
|
2091
|
+
const { instance, definition, actor, grants, snapshot } = args, now = args.now ?? wallClock(), stage = definition.stages.find((s) => s.id === instance.currentStageId);
|
|
2092
|
+
if (stage === void 0)
|
|
2093
|
+
throw new Error(
|
|
2094
|
+
`Instance "${instance._id}" currentStageId "${instance.currentStageId}" not in definition`
|
|
2095
|
+
);
|
|
2096
|
+
const currentTaskEntries = findOpenStageEntry(instance)?.tasks ?? [], taskEvaluations = [];
|
|
2097
|
+
for (const task of stage.tasks ?? [])
|
|
2098
|
+
taskEvaluations.push(
|
|
2099
|
+
await evaluateTask({
|
|
2100
|
+
task,
|
|
2101
|
+
statusEntry: currentTaskEntries.find((t) => t.id === task.id),
|
|
2102
|
+
instance,
|
|
2103
|
+
definition,
|
|
2104
|
+
actor,
|
|
2105
|
+
grants,
|
|
2106
|
+
snapshot,
|
|
2107
|
+
now
|
|
2108
|
+
})
|
|
2109
|
+
);
|
|
2110
|
+
const transitionEvaluations = [];
|
|
2111
|
+
for (const transition of stage.transitions ?? [])
|
|
2112
|
+
transitionEvaluations.push({
|
|
2113
|
+
transition,
|
|
2114
|
+
filterSatisfied: await evaluateFilter({
|
|
2115
|
+
filter: transition.filter,
|
|
2116
|
+
definition,
|
|
2117
|
+
snapshot,
|
|
2118
|
+
params: buildParams(instance, now)
|
|
2119
|
+
})
|
|
2120
|
+
});
|
|
2121
|
+
const currentStage = {
|
|
2122
|
+
stage,
|
|
2123
|
+
tasks: taskEvaluations,
|
|
2124
|
+
transitions: transitionEvaluations
|
|
2125
|
+
}, pendingOnYou = taskEvaluations.filter((t) => t.pendingOnActor), canInteract = taskEvaluations.some((t) => t.actions.some((a) => a.allowed));
|
|
2126
|
+
return {
|
|
2127
|
+
instance,
|
|
2128
|
+
definition,
|
|
2129
|
+
actor,
|
|
2130
|
+
currentStage,
|
|
2131
|
+
pendingOnYou,
|
|
2132
|
+
canInteract
|
|
2133
|
+
};
|
|
2134
|
+
}
|
|
2135
|
+
async function evaluateTask(args) {
|
|
2136
|
+
const { task, statusEntry, instance, definition, actor, grants, snapshot, now } = args, status = statusEntry?.status ?? "pending", actions = [];
|
|
2137
|
+
for (const action of task.actions ?? [])
|
|
2138
|
+
actions.push(
|
|
2139
|
+
await evaluateAction({
|
|
2140
|
+
action,
|
|
2141
|
+
task,
|
|
2142
|
+
...statusEntry !== void 0 ? { statusEntry } : {},
|
|
2143
|
+
status,
|
|
2144
|
+
instance,
|
|
2145
|
+
definition,
|
|
2146
|
+
actor,
|
|
2147
|
+
...grants !== void 0 ? { grants } : {},
|
|
2148
|
+
snapshot,
|
|
2149
|
+
now
|
|
2150
|
+
})
|
|
2151
|
+
);
|
|
2152
|
+
return {
|
|
2153
|
+
task,
|
|
2154
|
+
status,
|
|
2155
|
+
// "pending on actor" treats both `pending` and `active` as waiting on
|
|
2156
|
+
// the assignee — pending tasks just haven't been auto-invoked yet, but
|
|
2157
|
+
// they're still inbox items.
|
|
2158
|
+
pendingOnActor: (status === "active" || status === "pending") && actorIsAssignee(actor, task.assignees ?? []),
|
|
2159
|
+
actions
|
|
2160
|
+
};
|
|
2161
|
+
}
|
|
2162
|
+
async function evaluateAction(args) {
|
|
2163
|
+
const { action, task, status, instance, definition, actor, grants, snapshot, now } = args, syncReason = lifecycleReason(instance, definition, status) ?? actorReason(action, task, actor);
|
|
2164
|
+
if (syncReason !== void 0) return disabled(action, syncReason);
|
|
2165
|
+
const asyncReason = await permissionReason(action, instance, actor, grants) ?? await filterReason(action, instance, definition, snapshot, now);
|
|
2166
|
+
return asyncReason !== void 0 ? disabled(action, asyncReason) : { action, allowed: !0 };
|
|
2167
|
+
}
|
|
2168
|
+
function lifecycleReason(instance, definition, status) {
|
|
2169
|
+
if (instance.completedAt !== void 0)
|
|
2170
|
+
return { kind: "instance-completed", completedAt: instance.completedAt };
|
|
2171
|
+
if (definition.stages.find((s) => s.id === instance.currentStageId)?.kind === "terminal")
|
|
2172
|
+
return { kind: "stage-terminal", stageId: instance.currentStageId };
|
|
2173
|
+
if (status === "done" || status === "skipped" || status === "failed")
|
|
2174
|
+
return { kind: "task-not-active", status };
|
|
2175
|
+
}
|
|
2176
|
+
function actorReason(action, task, actor) {
|
|
2177
|
+
const actorRoles = actor.roles ?? [];
|
|
2178
|
+
if (action.roles !== void 0 && action.roles.length > 0 && !(actorRoles.includes(WILDCARD_ROLE) || action.roles.some((r) => actorRoles.includes(r))))
|
|
2179
|
+
return { kind: "role-required", required: [...action.roles], actorRoles: [...actorRoles] };
|
|
2180
|
+
if (action.requireAssignment === !0 && !actorIsAssignee(actor, task.assignees ?? []))
|
|
2181
|
+
return { kind: "not-assigned", assignees: [...task.assignees ?? []], actorId: actor.id };
|
|
2182
|
+
}
|
|
2183
|
+
async function permissionReason(action, instance, actor, grants) {
|
|
2184
|
+
if (grants === void 0) return;
|
|
2185
|
+
const permission = action.requiredPermission ?? "update";
|
|
2186
|
+
if (!await grantsPermissionOn({
|
|
2187
|
+
document: instance,
|
|
2188
|
+
grants,
|
|
2189
|
+
permission,
|
|
2190
|
+
userId: actor.id
|
|
2191
|
+
}))
|
|
2192
|
+
return { kind: "permission-denied", permission, matchingGrants: grants.length };
|
|
2193
|
+
}
|
|
2194
|
+
async function filterReason(action, instance, definition, snapshot, now) {
|
|
2195
|
+
if (!(action.filter === void 0 || await evaluateFilter({
|
|
2196
|
+
filter: action.filter,
|
|
2197
|
+
definition,
|
|
2198
|
+
snapshot,
|
|
2199
|
+
params: buildParams(instance, now)
|
|
2200
|
+
})))
|
|
2201
|
+
return { kind: "filter-failed", filter: action.filter };
|
|
2202
|
+
}
|
|
2203
|
+
function disabled(action, reason) {
|
|
2204
|
+
return { action, allowed: !1, disabledReason: reason };
|
|
2205
|
+
}
|
|
2206
|
+
function actorIsAssignee(actor, assignees) {
|
|
2207
|
+
if (assignees.length === 0) return !1;
|
|
2208
|
+
const actorRoles = actor.roles ?? [], wildcard = actorRoles.includes(WILDCARD_ROLE);
|
|
2209
|
+
for (const assignee of assignees)
|
|
2210
|
+
if (assignee.kind === "user" && assignee.id === actor.id || assignee.kind === "role" && (wildcard || actorRoles.includes(assignee.role)))
|
|
2211
|
+
return !0;
|
|
2212
|
+
return !1;
|
|
2213
|
+
}
|
|
2214
|
+
function parseDefinitionSnapshot(instance) {
|
|
2215
|
+
try {
|
|
2216
|
+
return JSON.parse(instance.definitionSnapshot);
|
|
2217
|
+
} catch (err) {
|
|
2218
|
+
throw new Error(
|
|
2219
|
+
`Failed to parse definitionSnapshot on instance "${instance._id}": ${err instanceof Error ? err.message : String(err)}`,
|
|
2220
|
+
{ cause: err }
|
|
2221
|
+
);
|
|
1721
2222
|
}
|
|
1722
2223
|
}
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
2224
|
+
const resourceKey = (r) => `${r.type}.${r.id}`;
|
|
2225
|
+
function guardsForResource(client) {
|
|
2226
|
+
return client.fetch("*[_type == $t] | order(_id asc)", { t: GUARD_DOC_TYPE });
|
|
2227
|
+
}
|
|
2228
|
+
async function guardsForInstance(args) {
|
|
2229
|
+
const { client, clientForGdr, instance } = args, clientsByResource = /* @__PURE__ */ new Map([
|
|
2230
|
+
[resourceKey(instance.workflowResource), client]
|
|
2231
|
+
]), stateUris = [
|
|
2232
|
+
...collectSlotDocUris(instance.state),
|
|
2233
|
+
...instance.stages.flatMap((stage) => collectSlotDocUris(stage.state))
|
|
2234
|
+
];
|
|
2235
|
+
for (const uri of stateUris) {
|
|
2236
|
+
const parsed = tryParseGdr(uri);
|
|
2237
|
+
if (parsed === void 0) continue;
|
|
2238
|
+
const key = resourceKey(resourceFromParsed(parsed));
|
|
2239
|
+
clientsByResource.has(key) || clientsByResource.set(key, clientForGdr(parsed));
|
|
2240
|
+
}
|
|
2241
|
+
const perResource = await Promise.all(
|
|
2242
|
+
[...clientsByResource.values()].map(
|
|
2243
|
+
(c) => c.fetch("*[_type == $t && sourceInstanceId == $id]", {
|
|
2244
|
+
t: GUARD_DOC_TYPE,
|
|
2245
|
+
id: instance._id
|
|
2246
|
+
})
|
|
2247
|
+
)
|
|
2248
|
+
);
|
|
2249
|
+
return dedupById(perResource.flat());
|
|
2250
|
+
}
|
|
2251
|
+
function tryParseGdr(uri) {
|
|
2252
|
+
try {
|
|
2253
|
+
return parseGdr(uri);
|
|
2254
|
+
} catch {
|
|
2255
|
+
return;
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
function dedupById(guards) {
|
|
2259
|
+
return [...new Map(guards.map((g) => [g._id, g])).values()].sort((a, b) => a._id.localeCompare(b._id));
|
|
2260
|
+
}
|
|
2261
|
+
function definitionDocId(_workflowResource, prefix, workflowId, version) {
|
|
2262
|
+
return `${prefix}.${workflowId}.v${version}`;
|
|
2263
|
+
}
|
|
2264
|
+
async function sortByDependencies(client, definitions, tags, workflowResource) {
|
|
2265
|
+
const prefix = canonicalTag(tags), byWorkflowId = /* @__PURE__ */ new Map();
|
|
2266
|
+
for (const def of definitions) {
|
|
2267
|
+
const list = byWorkflowId.get(def.workflowId) ?? [];
|
|
2268
|
+
list.push(def), byWorkflowId.set(def.workflowId, list);
|
|
2269
|
+
}
|
|
2270
|
+
const findInBatch = (ref) => findRefInBatch(byWorkflowId, ref);
|
|
2271
|
+
return await assertCrossBatchRefsDeployed(client, definitions, {
|
|
2272
|
+
tags,
|
|
2273
|
+
workflowResource,
|
|
2274
|
+
prefix,
|
|
2275
|
+
findInBatch
|
|
2276
|
+
}), topoSortDefinitions(definitions, { workflowResource, prefix, findInBatch });
|
|
2277
|
+
}
|
|
2278
|
+
function findRefInBatch(byWorkflowId, ref) {
|
|
2279
|
+
const candidates = byWorkflowId.get(ref.workflowId);
|
|
2280
|
+
if (!(candidates === void 0 || candidates.length === 0))
|
|
2281
|
+
return typeof ref.version == "number" ? candidates.find((c) => c.version === ref.version) : candidates.reduce(
|
|
2282
|
+
(best, c) => best === void 0 || c.version > best.version ? c : best,
|
|
2283
|
+
void 0
|
|
2284
|
+
);
|
|
2285
|
+
}
|
|
2286
|
+
async function assertCrossBatchRefsDeployed(client, definitions, ctx) {
|
|
2287
|
+
const missing = [];
|
|
2288
|
+
for (const def of definitions) {
|
|
2289
|
+
const fromId = definitionDocId(ctx.workflowResource, ctx.prefix, def.workflowId, def.version);
|
|
2290
|
+
for (const ref of refsOf(def)) {
|
|
2291
|
+
if (ctx.findInBatch(ref) !== void 0) continue;
|
|
2292
|
+
const label = await resolveDeployedRefLabel(client, ref, ctx.tags);
|
|
2293
|
+
label !== void 0 && missing.push({ from: fromId, ref: label });
|
|
2294
|
+
}
|
|
2295
|
+
}
|
|
2296
|
+
if (missing.length === 0) return;
|
|
2297
|
+
const lines = missing.map((m) => ` - ${m.from} \u2192 ${m.ref} (neither in batch nor deployed)`);
|
|
2298
|
+
throw new Error(
|
|
2299
|
+
`workflow.deployDefinitions: ${missing.length} unresolved reference${missing.length === 1 ? "" : "s"}:
|
|
2300
|
+
` + lines.join(`
|
|
2301
|
+
`)
|
|
2302
|
+
);
|
|
2303
|
+
}
|
|
2304
|
+
async function resolveDeployedRefLabel(client, ref, tags) {
|
|
2305
|
+
const wantsExplicit = typeof ref.version == "number", params = { workflowId: ref.workflowId, engineTags: tags };
|
|
2306
|
+
if (wantsExplicit && (params.version = ref.version), !await client.fetch(
|
|
2307
|
+
definitionLookupGroq(wantsExplicit),
|
|
2308
|
+
params
|
|
2309
|
+
))
|
|
2310
|
+
return wantsExplicit ? `${ref.workflowId} v${ref.version}` : ref.workflowId;
|
|
2311
|
+
}
|
|
2312
|
+
function topoSortDefinitions(definitions, ctx) {
|
|
2313
|
+
const visited = /* @__PURE__ */ new Set(), visiting = /* @__PURE__ */ new Set(), ordered = [], visit = (def) => {
|
|
2314
|
+
const id = definitionDocId(ctx.workflowResource, ctx.prefix, def.workflowId, def.version);
|
|
2315
|
+
if (!visited.has(id)) {
|
|
2316
|
+
if (visiting.has(id))
|
|
2317
|
+
throw new Error(`workflow.deployDefinitions: dependency cycle involving "${id}"`);
|
|
2318
|
+
visiting.add(id);
|
|
2319
|
+
for (const ref of refsOf(def)) {
|
|
2320
|
+
const child = ctx.findInBatch(ref);
|
|
2321
|
+
child !== void 0 && visit(child);
|
|
2322
|
+
}
|
|
2323
|
+
visiting.delete(id), visited.add(id), ordered.push(def);
|
|
2324
|
+
}
|
|
2325
|
+
};
|
|
2326
|
+
for (const def of definitions) visit(def);
|
|
2327
|
+
return ordered;
|
|
2328
|
+
}
|
|
2329
|
+
function refsOf(def) {
|
|
2330
|
+
const out = [];
|
|
2331
|
+
for (const stage of def.stages)
|
|
2332
|
+
for (const task of stage.tasks ?? []) {
|
|
2333
|
+
const ref = task.spawns?.definitionRef;
|
|
2334
|
+
ref !== void 0 && out.push({
|
|
2335
|
+
workflowId: ref.workflowId,
|
|
2336
|
+
...ref.version !== void 0 ? { version: ref.version } : {}
|
|
2337
|
+
});
|
|
2338
|
+
}
|
|
2339
|
+
return out;
|
|
2340
|
+
}
|
|
2341
|
+
function isDefinitionUnchanged(existing, expected) {
|
|
2342
|
+
return stableStringify(stripSystemFields(existing)) === stableStringify(stripSystemFields(expected));
|
|
2343
|
+
}
|
|
2344
|
+
function stripSystemFields(doc) {
|
|
2345
|
+
const out = {};
|
|
2346
|
+
for (const [k, v2] of Object.entries(doc))
|
|
2347
|
+
k === "_rev" || k === "_createdAt" || k === "_updatedAt" || (out[k] = v2);
|
|
2348
|
+
return out;
|
|
2349
|
+
}
|
|
2350
|
+
function stableStringify(value) {
|
|
2351
|
+
return value === null || typeof value != "object" ? JSON.stringify(value) : Array.isArray(value) ? `[${value.map(stableStringify).join(",")}]` : `{${Object.entries(value).toSorted(
|
|
2352
|
+
([a], [b]) => a.localeCompare(b)
|
|
2353
|
+
).map(([k, v2]) => `${JSON.stringify(k)}:${stableStringify(v2)}`).join(",")}}`;
|
|
2354
|
+
}
|
|
2355
|
+
async function loadDefinition(client, workflowId, version, tags) {
|
|
2356
|
+
if (version !== void 0) {
|
|
2357
|
+
const doc = await client.fetch(
|
|
2358
|
+
definitionLookupGroq(!0),
|
|
2359
|
+
{ workflowId, version, engineTags: tags }
|
|
2360
|
+
);
|
|
2361
|
+
if (!doc)
|
|
2362
|
+
throw new Error(`Workflow definition ${workflowId} v${version} not deployed`);
|
|
2363
|
+
return doc;
|
|
1732
2364
|
}
|
|
2365
|
+
const latest = await client.fetch(definitionLookupGroq(!1), {
|
|
2366
|
+
workflowId,
|
|
2367
|
+
engineTags: tags
|
|
2368
|
+
});
|
|
2369
|
+
if (!latest)
|
|
2370
|
+
throw new Error(`No deployed definition for workflow ${workflowId}`);
|
|
2371
|
+
return latest;
|
|
2372
|
+
}
|
|
2373
|
+
const STATE_OP_TYPES = [
|
|
2374
|
+
"workflow.op.state.set",
|
|
2375
|
+
"workflow.op.state.append",
|
|
2376
|
+
"workflow.op.state.updateWhere",
|
|
2377
|
+
"workflow.op.state.removeWhere"
|
|
2378
|
+
];
|
|
2379
|
+
function isStateOp(summary) {
|
|
2380
|
+
return STATE_OP_TYPES.includes(summary.opType);
|
|
1733
2381
|
}
|
|
1734
2382
|
function validateActionParams(action, taskId, callerParams) {
|
|
1735
2383
|
const declared = action.params ?? [], params = callerParams ?? {}, issues = [];
|
|
@@ -1766,7 +2414,7 @@ function checkParamType(value, decl) {
|
|
|
1766
2414
|
case "doc.ref":
|
|
1767
2415
|
return hasStringField(value, "id");
|
|
1768
2416
|
case "doc.refs":
|
|
1769
|
-
return Array.isArray(value) && value.every((
|
|
2417
|
+
return Array.isArray(value) && value.every((v2) => checkParamType(v2, { paramType: "doc.ref" }));
|
|
1770
2418
|
case "json":
|
|
1771
2419
|
return !0;
|
|
1772
2420
|
default:
|
|
@@ -1774,15 +2422,15 @@ function checkParamType(value, decl) {
|
|
|
1774
2422
|
}
|
|
1775
2423
|
}
|
|
1776
2424
|
async function runActionOps(args) {
|
|
1777
|
-
const { ops, mutation, stageId, taskId, actionName, params, actor } = args;
|
|
2425
|
+
const { ops, mutation, stageId, taskId, actionName, params, actor, self, now } = args;
|
|
1778
2426
|
if (ops === void 0 || ops.length === 0) return [];
|
|
1779
2427
|
const summaries = [];
|
|
1780
2428
|
for (const op of ops) {
|
|
1781
|
-
const summary = applyOp(op, { mutation, stageId, taskId, params, actor });
|
|
2429
|
+
const summary = applyOp(op, { mutation, stageId, taskId, params, actor, self, now });
|
|
1782
2430
|
summaries.push(summary), mutation.history.push({
|
|
1783
2431
|
_key: randomKey(),
|
|
1784
2432
|
_type: "workflow.history.opApplied",
|
|
1785
|
-
at:
|
|
2433
|
+
at: now,
|
|
1786
2434
|
stageId,
|
|
1787
2435
|
taskId,
|
|
1788
2436
|
actionId: actionName,
|
|
@@ -1841,8 +2489,8 @@ function applyStateUpdateWhere(op, ctx) {
|
|
|
1841
2489
|
/*createIfMissing*/
|
|
1842
2490
|
!1
|
|
1843
2491
|
), resolvedMerge = {};
|
|
1844
|
-
for (const [k,
|
|
1845
|
-
resolvedMerge[k] = resolveOpSource(
|
|
2492
|
+
for (const [k, v2] of Object.entries(op.merge))
|
|
2493
|
+
resolvedMerge[k] = resolveOpSource(v2, ctx);
|
|
1846
2494
|
return slot !== void 0 && Array.isArray(slot.value) && (slot.value = slot.value.map(
|
|
1847
2495
|
(row) => evalOpPredicate(op.where, row, ctx) ? { ...row, ...resolvedMerge } : row
|
|
1848
2496
|
)), { opType: op.type, target: op.target, resolved: { merge: resolvedMerge } };
|
|
@@ -1885,18 +2533,11 @@ function locateSlot(ctx, target, createIfMissing) {
|
|
|
1885
2533
|
}, host.push(slot)), slot;
|
|
1886
2534
|
}
|
|
1887
2535
|
function resolveOpSource(src, ctx) {
|
|
2536
|
+
const staticValue = resolveStaticSource(src, ctx);
|
|
2537
|
+
if (staticValue.handled) return staticValue.value;
|
|
1888
2538
|
switch (src.source) {
|
|
1889
|
-
case "literal":
|
|
1890
|
-
return src.value;
|
|
1891
|
-
case "param":
|
|
1892
|
-
return ctx.params[src.paramId];
|
|
1893
|
-
case "actor":
|
|
1894
|
-
return ctx.actor;
|
|
1895
|
-
case "now":
|
|
1896
|
-
return (/* @__PURE__ */ new Date()).toISOString();
|
|
1897
2539
|
case "self":
|
|
1898
|
-
return;
|
|
1899
|
-
// Not applicable inside ops; reserved for higher-level binding.
|
|
2540
|
+
return ctx.self;
|
|
1900
2541
|
case "stageId":
|
|
1901
2542
|
return ctx.stageId;
|
|
1902
2543
|
case "stateRead": {
|
|
@@ -1906,8 +2547,8 @@ function resolveOpSource(src, ctx) {
|
|
|
1906
2547
|
case "effectOutput": {
|
|
1907
2548
|
const entry = ctx.mutation.effectsContext.find((e) => e.id === src.contextId);
|
|
1908
2549
|
if (entry === void 0) return null;
|
|
1909
|
-
const
|
|
1910
|
-
return
|
|
2550
|
+
const v2 = src.path !== void 0 ? getPath(entry.value, src.path) : entry.value;
|
|
2551
|
+
return v2 === void 0 ? null : v2;
|
|
1911
2552
|
}
|
|
1912
2553
|
case "object": {
|
|
1913
2554
|
const out = {};
|
|
@@ -1915,6 +2556,11 @@ function resolveOpSource(src, ctx) {
|
|
|
1915
2556
|
out[field] = resolveOpSource(fieldSrc, ctx);
|
|
1916
2557
|
return out;
|
|
1917
2558
|
}
|
|
2559
|
+
case "row":
|
|
2560
|
+
case "parentState":
|
|
2561
|
+
return;
|
|
2562
|
+
default:
|
|
2563
|
+
return;
|
|
1918
2564
|
}
|
|
1919
2565
|
}
|
|
1920
2566
|
function slotValue(slots, slotId) {
|
|
@@ -1933,333 +2579,101 @@ function evalOpPredicate(p, row, ctx) {
|
|
|
1933
2579
|
const target = resolveOpSource(p.equals, ctx);
|
|
1934
2580
|
return row[p.field] === target;
|
|
1935
2581
|
}
|
|
1936
|
-
async function fireAction(args) {
|
|
1937
|
-
const { client, instanceId, taskId, action, params, options } = args
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
}
|
|
1954
|
-
function applyActionSetStatus(action, mutation, mutEntry, stageId, taskId, actor) {
|
|
1955
|
-
if (action.setStatus === void 0) return;
|
|
1956
|
-
const initialStatus = mutEntry.status, newStatus = action.setStatus;
|
|
1957
|
-
return mutEntry.status = newStatus, mutEntry.completedAt = (/* @__PURE__ */ new Date()).toISOString(), actor && (mutEntry.completedBy = actor.id), mutation.history.push(
|
|
1958
|
-
taskStatusChangedEntry({
|
|
1959
|
-
stageId,
|
|
1960
|
-
taskId,
|
|
1961
|
-
from: initialStatus,
|
|
1962
|
-
to: newStatus,
|
|
1963
|
-
...actor !== void 0 ? { actor } : {}
|
|
1964
|
-
})
|
|
1965
|
-
), newStatus;
|
|
1966
|
-
}
|
|
1967
|
-
async function commitAction(ctx, taskId, actionName, callerParams, actor) {
|
|
1968
|
-
const { stage, task, action, params } = await resolveActionCommit(
|
|
1969
|
-
ctx,
|
|
1970
|
-
taskId,
|
|
1971
|
-
actionName,
|
|
1972
|
-
callerParams
|
|
1973
|
-
), mutation = startMutation(ctx.instance), mutEntry = requireMutationTaskEntry(mutation, taskId);
|
|
1974
|
-
mutation.history.push({
|
|
1975
|
-
_key: randomKey(),
|
|
1976
|
-
_type: "workflow.history.actionFired",
|
|
1977
|
-
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1978
|
-
stageId: stage.id,
|
|
1979
|
-
taskId,
|
|
1980
|
-
actionId: actionName,
|
|
1981
|
-
...actor !== void 0 ? { actor } : {}
|
|
1982
|
-
});
|
|
1983
|
-
const ranOps = await runActionOps({
|
|
1984
|
-
ops: action.ops,
|
|
1985
|
-
mutation,
|
|
1986
|
-
stageId: stage.id,
|
|
1987
|
-
taskId,
|
|
1988
|
-
actionName,
|
|
1989
|
-
params,
|
|
1990
|
-
actor
|
|
1991
|
-
}), newStatus = applyActionSetStatus(action, mutation, mutEntry, stage.id, taskId, actor);
|
|
1992
|
-
return await queueEffects(
|
|
1993
|
-
ctx,
|
|
1994
|
-
mutation,
|
|
1995
|
-
action.effects,
|
|
1996
|
-
{
|
|
1997
|
-
kind: "action",
|
|
1998
|
-
id: `${task.id}:${actionName}`
|
|
1999
|
-
},
|
|
2000
|
-
actor,
|
|
2001
|
-
params
|
|
2002
|
-
), await persist(ctx, mutation), {
|
|
2003
|
-
fired: !0,
|
|
2004
|
-
taskId,
|
|
2005
|
-
action: actionName,
|
|
2006
|
-
...newStatus !== void 0 ? { newStatus } : {},
|
|
2007
|
-
...ranOps.length > 0 ? { ranOps } : {}
|
|
2008
|
-
};
|
|
2009
|
-
}
|
|
2010
|
-
const parsedFilters = /* @__PURE__ */ new Map();
|
|
2011
|
-
async function matchesFilter(args) {
|
|
2012
|
-
const { document, filter, userId } = args;
|
|
2013
|
-
parsedFilters.has(filter) || parsedFilters.set(filter, parse(`*[${filter}]`));
|
|
2014
|
-
const ast = parsedFilters.get(filter), data = await (await evaluate(ast, {
|
|
2015
|
-
dataset: [document],
|
|
2016
|
-
...userId !== void 0 ? { identity: userId } : {}
|
|
2017
|
-
})).get();
|
|
2018
|
-
return Array.isArray(data) && data.length === 1;
|
|
2019
|
-
}
|
|
2020
|
-
async function grantsPermissionOn(args) {
|
|
2021
|
-
const { document, grants, permission, userId } = args;
|
|
2022
|
-
if (!document || grants.length === 0) return !1;
|
|
2023
|
-
for (const grant of grants)
|
|
2024
|
-
if (grant.permissions.includes(permission) && await matchesFilter({
|
|
2025
|
-
document,
|
|
2026
|
-
filter: grant.filter,
|
|
2027
|
-
...userId !== void 0 ? { userId } : {}
|
|
2028
|
-
}))
|
|
2029
|
-
return !0;
|
|
2030
|
-
return !1;
|
|
2031
|
-
}
|
|
2032
|
-
async function fetchGrants(args) {
|
|
2033
|
-
const { client, resourcePath, signal } = args;
|
|
2034
|
-
return client.request({ url: resourcePath, ...signal ? { signal } : {} });
|
|
2035
|
-
}
|
|
2036
|
-
const actorCache = /* @__PURE__ */ new WeakMap(), grantsCache = /* @__PURE__ */ new WeakMap();
|
|
2037
|
-
async function resolveAccess(client, args = {}) {
|
|
2038
|
-
const overrideActor = args.override?.actor, overrideGrants = args.override?.grants;
|
|
2039
|
-
if (overrideActor !== void 0 && overrideGrants !== void 0)
|
|
2040
|
-
return { actor: overrideActor, grants: overrideGrants };
|
|
2041
|
-
const requestFn = client.request;
|
|
2042
|
-
if (requestFn === void 0) {
|
|
2043
|
-
if (overrideActor === void 0)
|
|
2044
|
-
throw new Error(
|
|
2045
|
-
"workflow: no actor available. The engine resolves the actor from the client's token via `client.request({ url: '/users/me' })`. Supply a real `@sanity/client` configured with a token, or pass an explicit `access.actor` override (the test bench provides one by default)."
|
|
2046
|
-
);
|
|
2047
|
-
return { actor: overrideActor };
|
|
2048
|
-
}
|
|
2049
|
-
const actorPromise = overrideActor !== void 0 ? Promise.resolve(overrideActor) : cachedActor(client, requestFn), grantsPromise = overrideGrants !== void 0 ? Promise.resolve(overrideGrants) : args.grantsFromPath !== void 0 ? cachedGrants(client, requestFn, args.grantsFromPath) : Promise.resolve(void 0), [actor, grants] = await Promise.all([actorPromise, grantsPromise]);
|
|
2050
|
-
if (actor === void 0)
|
|
2051
|
-
throw new Error(
|
|
2052
|
-
"workflow: failed to resolve actor from `/users/me`. The client is configured but the endpoint returned no usable identity \u2014 check the token, or pass `access.actor` explicitly."
|
|
2053
|
-
);
|
|
2054
|
-
return { actor, ...grants !== void 0 ? { grants } : {} };
|
|
2055
|
-
}
|
|
2056
|
-
function cachedActor(client, requestFn) {
|
|
2057
|
-
const cached = actorCache.get(client);
|
|
2058
|
-
if (cached !== void 0) return cached;
|
|
2059
|
-
const pending = fetchActor(requestFn).catch((err) => {
|
|
2060
|
-
throw actorCache.get(client) === pending && actorCache.delete(client), err;
|
|
2061
|
-
});
|
|
2062
|
-
return actorCache.set(client, pending), pending;
|
|
2063
|
-
}
|
|
2064
|
-
function cachedGrants(client, requestFn, resourcePath) {
|
|
2065
|
-
let byPath = grantsCache.get(client);
|
|
2066
|
-
byPath === void 0 && (byPath = /* @__PURE__ */ new Map(), grantsCache.set(client, byPath));
|
|
2067
|
-
let cached = byPath.get(resourcePath);
|
|
2068
|
-
return cached === void 0 && (cached = fetchGrantsCached(requestFn, resourcePath), byPath.set(resourcePath, cached)), cached;
|
|
2069
|
-
}
|
|
2070
|
-
async function fetchActor(requestFn) {
|
|
2071
|
-
let user;
|
|
2072
|
-
try {
|
|
2073
|
-
user = await requestFn({
|
|
2074
|
-
uri: "/users/me",
|
|
2075
|
-
tag: "workflow.access.resolve-actor"
|
|
2076
|
-
});
|
|
2077
|
-
} catch (err) {
|
|
2078
|
-
throw new Error(
|
|
2079
|
-
'workflow: /users/me request failed. The engine resolves the actor from the client\'s token via `client.request({ uri: "/users/me" })`. Check the token/connectivity, or pass an explicit `access.actor` override.',
|
|
2080
|
-
{ cause: err }
|
|
2081
|
-
);
|
|
2082
|
-
}
|
|
2083
|
-
if (!user || typeof user.id != "string" || user.id.length === 0) return;
|
|
2084
|
-
const roleNames = user.roles?.map((r) => r.name).filter((n) => !!n) ?? [];
|
|
2085
|
-
return {
|
|
2086
|
-
kind: "user",
|
|
2087
|
-
id: user.id,
|
|
2088
|
-
...roleNames.length > 0 ? { roles: roleNames } : {}
|
|
2089
|
-
};
|
|
2090
|
-
}
|
|
2091
|
-
async function fetchGrantsCached(requestFn, resourcePath) {
|
|
2092
|
-
try {
|
|
2093
|
-
return await fetchGrants({ client: { request: requestFn }, resourcePath });
|
|
2094
|
-
} catch (err) {
|
|
2095
|
-
console.warn(
|
|
2096
|
-
`workflow: failed to fetch grants from "${resourcePath}"; skipping permission gate. Original error: ${err instanceof Error ? err.message : String(err)}`
|
|
2097
|
-
);
|
|
2098
|
-
return;
|
|
2099
|
-
}
|
|
2100
|
-
}
|
|
2101
|
-
const TAG_RE = /^[a-z0-9][a-z0-9-]*$/;
|
|
2102
|
-
function validateTags(tags) {
|
|
2103
|
-
if (tags.length === 0)
|
|
2104
|
-
throw new Error("tags: must be a non-empty array");
|
|
2105
|
-
for (const tag of tags)
|
|
2106
|
-
if (!TAG_RE.test(tag))
|
|
2107
|
-
throw new Error(
|
|
2108
|
-
`tags: invalid tag "${tag}" \u2014 must match ${TAG_RE.source} (ASCII lowercase + digits + dashes, no leading dash, no dots)`
|
|
2109
|
-
);
|
|
2110
|
-
}
|
|
2111
|
-
function canonicalTag(tags) {
|
|
2112
|
-
return tags[0];
|
|
2113
|
-
}
|
|
2114
|
-
const WILDCARD_ROLE = "*";
|
|
2115
|
-
async function evaluateInstance(args) {
|
|
2116
|
-
const { client, tags, instanceId, resourceClients } = args;
|
|
2117
|
-
validateTags(tags);
|
|
2118
|
-
const { actor, grants } = await resolveAccess(client, {
|
|
2119
|
-
...args.access !== void 0 ? { override: args.access } : {},
|
|
2120
|
-
...args.grantsFromPath !== void 0 ? { grantsFromPath: args.grantsFromPath } : {}
|
|
2121
|
-
}), instance = await client.getDocument(instanceId);
|
|
2122
|
-
if (!instance)
|
|
2123
|
-
throw new Error(`Workflow instance "${instanceId}" not found`);
|
|
2124
|
-
if (!tags.some((t) => instance.tags?.includes(t)))
|
|
2125
|
-
throw new Error(`Workflow instance "${instanceId}" not visible to this engine (tag mismatch)`);
|
|
2126
|
-
const definition = parseDefinitionSnapshot(instance), stage = definition.stages.find((s) => s.id === instance.currentStageId);
|
|
2127
|
-
if (stage === void 0)
|
|
2128
|
-
throw new Error(
|
|
2129
|
-
`Instance "${instanceId}" currentStageId "${instance.currentStageId}" not in definition`
|
|
2130
|
-
);
|
|
2131
|
-
const snapshot = await hydrateSnapshot({
|
|
2132
|
-
client,
|
|
2133
|
-
clientForGdr: resourceClients !== void 0 ? (parsed) => resourceClients(parsed) ?? client : () => client,
|
|
2134
|
-
instance,
|
|
2135
|
-
definition
|
|
2136
|
-
}), currentTaskEntries = instance.stages.find(
|
|
2137
|
-
(s) => s.id === instance.currentStageId && s.exitedAt === void 0
|
|
2138
|
-
)?.tasks ?? [], taskEvaluations = [];
|
|
2139
|
-
for (const task of stage.tasks ?? [])
|
|
2140
|
-
taskEvaluations.push(
|
|
2141
|
-
await evaluateTask({
|
|
2142
|
-
task,
|
|
2143
|
-
statusEntry: currentTaskEntries.find((t) => t.id === task.id),
|
|
2144
|
-
instance,
|
|
2145
|
-
definition,
|
|
2146
|
-
actor,
|
|
2147
|
-
grants,
|
|
2148
|
-
snapshot
|
|
2149
|
-
})
|
|
2150
|
-
);
|
|
2151
|
-
const transitionEvaluations = [];
|
|
2152
|
-
for (const transition of stage.transitions ?? [])
|
|
2153
|
-
transitionEvaluations.push({
|
|
2154
|
-
transition,
|
|
2155
|
-
filterSatisfied: await evaluateFilter({
|
|
2156
|
-
filter: transition.filter,
|
|
2157
|
-
definition,
|
|
2158
|
-
snapshot,
|
|
2159
|
-
params: buildParams(instance)
|
|
2160
|
-
})
|
|
2161
|
-
});
|
|
2162
|
-
const currentStage = {
|
|
2163
|
-
stage,
|
|
2164
|
-
tasks: taskEvaluations,
|
|
2165
|
-
transitions: transitionEvaluations
|
|
2166
|
-
}, pendingOnYou = taskEvaluations.filter((t) => t.pendingOnActor), canInteract = taskEvaluations.some((t) => t.actions.some((a) => a.allowed));
|
|
2167
|
-
return {
|
|
2168
|
-
instance,
|
|
2169
|
-
definition,
|
|
2170
|
-
actor,
|
|
2171
|
-
currentStage,
|
|
2172
|
-
pendingOnYou,
|
|
2173
|
-
canInteract
|
|
2174
|
-
};
|
|
2175
|
-
}
|
|
2176
|
-
async function evaluateTask(args) {
|
|
2177
|
-
const { task, statusEntry, instance, definition, actor, grants, snapshot } = args, status = statusEntry?.status ?? "pending", actions = [];
|
|
2178
|
-
for (const action of task.actions ?? [])
|
|
2179
|
-
actions.push(
|
|
2180
|
-
await evaluateAction({
|
|
2181
|
-
action,
|
|
2182
|
-
task,
|
|
2183
|
-
...statusEntry !== void 0 ? { statusEntry } : {},
|
|
2184
|
-
status,
|
|
2185
|
-
instance,
|
|
2186
|
-
definition,
|
|
2187
|
-
actor,
|
|
2188
|
-
...grants !== void 0 ? { grants } : {},
|
|
2189
|
-
snapshot
|
|
2190
|
-
})
|
|
2191
|
-
);
|
|
2192
|
-
return {
|
|
2193
|
-
task,
|
|
2194
|
-
status,
|
|
2195
|
-
// "pending on actor" treats both `pending` and `active` as waiting on
|
|
2196
|
-
// the assignee — pending tasks just haven't been auto-invoked yet, but
|
|
2197
|
-
// they're still inbox items.
|
|
2198
|
-
pendingOnActor: (status === "active" || status === "pending") && actorIsAssignee(actor, task.assignees ?? []),
|
|
2199
|
-
actions
|
|
2200
|
-
};
|
|
2201
|
-
}
|
|
2202
|
-
async function evaluateAction(args) {
|
|
2203
|
-
const { action, task, status, instance, definition, actor, grants, snapshot } = args, syncReason = lifecycleReason(instance, definition, status) ?? actorReason(action, task, actor);
|
|
2204
|
-
if (syncReason !== void 0) return disabled(action, syncReason);
|
|
2205
|
-
const asyncReason = await permissionReason(action, instance, actor, grants) ?? await filterReason(action, instance, definition, snapshot);
|
|
2206
|
-
return asyncReason !== void 0 ? disabled(action, asyncReason) : { action, allowed: !0 };
|
|
2207
|
-
}
|
|
2208
|
-
function lifecycleReason(instance, definition, status) {
|
|
2209
|
-
if (instance.completedAt !== void 0)
|
|
2210
|
-
return { kind: "instance-completed", completedAt: instance.completedAt };
|
|
2211
|
-
if (definition.stages.find((s) => s.id === instance.currentStageId)?.kind === "terminal")
|
|
2212
|
-
return { kind: "stage-terminal", stageId: instance.currentStageId };
|
|
2213
|
-
if (status === "done" || status === "skipped" || status === "failed")
|
|
2214
|
-
return { kind: "task-not-active", status };
|
|
2215
|
-
}
|
|
2216
|
-
function actorReason(action, task, actor) {
|
|
2217
|
-
const actorRoles = actor.roles ?? [];
|
|
2218
|
-
if (action.roles !== void 0 && action.roles.length > 0 && !(actorRoles.includes(WILDCARD_ROLE) || action.roles.some((r) => actorRoles.includes(r))))
|
|
2219
|
-
return { kind: "role-required", required: [...action.roles], actorRoles: [...actorRoles] };
|
|
2220
|
-
if (action.requireAssignment === !0 && !actorIsAssignee(actor, task.assignees ?? []))
|
|
2221
|
-
return { kind: "not-assigned", assignees: [...task.assignees ?? []], actorId: actor.id };
|
|
2222
|
-
}
|
|
2223
|
-
async function permissionReason(action, instance, actor, grants) {
|
|
2224
|
-
if (grants === void 0) return;
|
|
2225
|
-
const permission = action.requiredPermission ?? "update";
|
|
2226
|
-
if (!await grantsPermissionOn({
|
|
2227
|
-
document: instance,
|
|
2228
|
-
grants,
|
|
2229
|
-
permission,
|
|
2230
|
-
userId: actor.id
|
|
2231
|
-
}))
|
|
2232
|
-
return { kind: "permission-denied", permission, matchingGrants: grants.length };
|
|
2233
|
-
}
|
|
2234
|
-
async function filterReason(action, instance, definition, snapshot) {
|
|
2235
|
-
if (!(action.filter === void 0 || await evaluateFilter({
|
|
2236
|
-
filter: action.filter,
|
|
2237
|
-
definition,
|
|
2238
|
-
snapshot,
|
|
2239
|
-
params: buildParams(instance)
|
|
2240
|
-
})))
|
|
2241
|
-
return { kind: "filter-failed", filter: action.filter };
|
|
2242
|
-
}
|
|
2243
|
-
function disabled(action, reason) {
|
|
2244
|
-
return { action, allowed: !1, disabledReason: reason };
|
|
2245
|
-
}
|
|
2246
|
-
function actorIsAssignee(actor, assignees) {
|
|
2247
|
-
if (assignees.length === 0) return !1;
|
|
2248
|
-
const actorRoles = actor.roles ?? [], wildcard = actorRoles.includes(WILDCARD_ROLE);
|
|
2249
|
-
for (const assignee of assignees)
|
|
2250
|
-
if (assignee.kind === "user" && assignee.id === actor.id || assignee.kind === "role" && (wildcard || actorRoles.includes(assignee.role)))
|
|
2251
|
-
return !0;
|
|
2252
|
-
return !1;
|
|
2582
|
+
async function fireAction(args) {
|
|
2583
|
+
const { client, instanceId, taskId, action, params, options } = args;
|
|
2584
|
+
for (let attempt = 1; attempt <= CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS; attempt++) {
|
|
2585
|
+
const ctx = await loadContext(client, instanceId, {
|
|
2586
|
+
...options?.clock ? { clock: options.clock } : {}
|
|
2587
|
+
});
|
|
2588
|
+
try {
|
|
2589
|
+
return await commitAction(ctx, taskId, action, params, options?.actor);
|
|
2590
|
+
} catch (error) {
|
|
2591
|
+
if (!isRevisionConflict(error)) throw error;
|
|
2592
|
+
}
|
|
2593
|
+
}
|
|
2594
|
+
throw new ConcurrentFireActionError({
|
|
2595
|
+
instanceId,
|
|
2596
|
+
taskId,
|
|
2597
|
+
action,
|
|
2598
|
+
attempts: CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS
|
|
2599
|
+
});
|
|
2253
2600
|
}
|
|
2254
|
-
function
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2601
|
+
async function resolveActionCommit(ctx, taskId, actionName, callerParams) {
|
|
2602
|
+
const { stage, task } = findTaskInCurrentStage(ctx, taskId), action = (task.actions ?? []).find((a) => a.id === actionName);
|
|
2603
|
+
if (action === void 0)
|
|
2604
|
+
throw new Error(`Action "${actionName}" not declared on task "${taskId}"`);
|
|
2605
|
+
if (!await ctxEvaluateFilter(ctx, action.filter))
|
|
2606
|
+
throw new Error(`Action "${actionName}" filter rejected on task "${taskId}"`);
|
|
2607
|
+
const entry = findCurrentTasks(ctx.instance).find((t) => t.id === taskId);
|
|
2608
|
+
if (entry === void 0 || entry.status !== "active")
|
|
2258
2609
|
throw new Error(
|
|
2259
|
-
`
|
|
2260
|
-
{ cause: err }
|
|
2610
|
+
`Task "${taskId}" must be active to fire action "${actionName}"; status is ${entry?.status ?? "missing"}`
|
|
2261
2611
|
);
|
|
2262
|
-
|
|
2612
|
+
const params = validateActionParams(action, taskId, callerParams);
|
|
2613
|
+
return { stage, task, action, params };
|
|
2614
|
+
}
|
|
2615
|
+
function applyActionSetStatus(action, mutation, mutEntry, stageId, taskId, actor, now) {
|
|
2616
|
+
if (action.setStatus === void 0) return;
|
|
2617
|
+
const initialStatus = mutEntry.status, newStatus = action.setStatus;
|
|
2618
|
+
return mutEntry.status = newStatus, mutEntry.completedAt = now, actor && (mutEntry.completedBy = actor.id), mutation.history.push(
|
|
2619
|
+
taskStatusChangedEntry({
|
|
2620
|
+
stageId,
|
|
2621
|
+
taskId,
|
|
2622
|
+
from: initialStatus,
|
|
2623
|
+
to: newStatus,
|
|
2624
|
+
at: now,
|
|
2625
|
+
...actor !== void 0 ? { actor } : {}
|
|
2626
|
+
})
|
|
2627
|
+
), newStatus;
|
|
2628
|
+
}
|
|
2629
|
+
async function commitAction(ctx, taskId, actionName, callerParams, actor) {
|
|
2630
|
+
const { stage, task, action, params } = await resolveActionCommit(
|
|
2631
|
+
ctx,
|
|
2632
|
+
taskId,
|
|
2633
|
+
actionName,
|
|
2634
|
+
callerParams
|
|
2635
|
+
), mutation = startMutation(ctx.instance), mutEntry = requireMutationTaskEntry(mutation, taskId), now = ctx.now;
|
|
2636
|
+
mutation.history.push({
|
|
2637
|
+
_key: randomKey(),
|
|
2638
|
+
_type: "workflow.history.actionFired",
|
|
2639
|
+
at: now,
|
|
2640
|
+
stageId: stage.id,
|
|
2641
|
+
taskId,
|
|
2642
|
+
actionId: actionName,
|
|
2643
|
+
...actor !== void 0 ? { actor } : {}
|
|
2644
|
+
});
|
|
2645
|
+
const ranOps = await runActionOps({
|
|
2646
|
+
ops: action.ops,
|
|
2647
|
+
mutation,
|
|
2648
|
+
stageId: stage.id,
|
|
2649
|
+
taskId,
|
|
2650
|
+
actionName,
|
|
2651
|
+
params,
|
|
2652
|
+
actor,
|
|
2653
|
+
self: selfGdr(ctx.instance),
|
|
2654
|
+
now
|
|
2655
|
+
}), newStatus = applyActionSetStatus(action, mutation, mutEntry, stage.id, taskId, actor, now);
|
|
2656
|
+
return await queueEffects(
|
|
2657
|
+
ctx,
|
|
2658
|
+
mutation,
|
|
2659
|
+
action.effects,
|
|
2660
|
+
{
|
|
2661
|
+
kind: "action",
|
|
2662
|
+
id: `${task.id}:${actionName}`
|
|
2663
|
+
},
|
|
2664
|
+
actor,
|
|
2665
|
+
params
|
|
2666
|
+
), await persistThenDeploy(
|
|
2667
|
+
ctx,
|
|
2668
|
+
mutation,
|
|
2669
|
+
() => ranOps.some(isStateOp) ? refreshStageGuards(ctx, mutation, stage.id) : Promise.resolve()
|
|
2670
|
+
), {
|
|
2671
|
+
fired: !0,
|
|
2672
|
+
taskId,
|
|
2673
|
+
action: actionName,
|
|
2674
|
+
...newStatus !== void 0 ? { newStatus } : {},
|
|
2675
|
+
...ranOps.length > 0 ? { ranOps } : {}
|
|
2676
|
+
};
|
|
2263
2677
|
}
|
|
2264
2678
|
class ActionDisabledError extends Error {
|
|
2265
2679
|
reason;
|
|
@@ -2283,121 +2697,6 @@ function formatDisabledReason(taskId, action, reason) {
|
|
|
2283
2697
|
const detail = disabledReasonDetail[reason.kind](reason);
|
|
2284
2698
|
return `Action "${taskId}:${action}" is not allowed: ${detail}`;
|
|
2285
2699
|
}
|
|
2286
|
-
function definitionDocId(_workflowResource, prefix, workflowId, version) {
|
|
2287
|
-
return `${prefix}.${workflowId}.v${version}`;
|
|
2288
|
-
}
|
|
2289
|
-
async function sortByDependencies(client, definitions, tags, workflowResource) {
|
|
2290
|
-
const prefix = canonicalTag(tags), byWorkflowId = /* @__PURE__ */ new Map();
|
|
2291
|
-
for (const def of definitions) {
|
|
2292
|
-
const list = byWorkflowId.get(def.workflowId) ?? [];
|
|
2293
|
-
list.push(def), byWorkflowId.set(def.workflowId, list);
|
|
2294
|
-
}
|
|
2295
|
-
const findInBatch = (ref) => findRefInBatch(byWorkflowId, ref);
|
|
2296
|
-
return await assertCrossBatchRefsDeployed(client, definitions, {
|
|
2297
|
-
tags,
|
|
2298
|
-
workflowResource,
|
|
2299
|
-
prefix,
|
|
2300
|
-
findInBatch
|
|
2301
|
-
}), topoSortDefinitions(definitions, { workflowResource, prefix, findInBatch });
|
|
2302
|
-
}
|
|
2303
|
-
function findRefInBatch(byWorkflowId, ref) {
|
|
2304
|
-
const candidates = byWorkflowId.get(ref.workflowId);
|
|
2305
|
-
if (!(candidates === void 0 || candidates.length === 0))
|
|
2306
|
-
return typeof ref.version == "number" ? candidates.find((c) => c.version === ref.version) : candidates.reduce(
|
|
2307
|
-
(best, c) => best === void 0 || c.version > best.version ? c : best,
|
|
2308
|
-
void 0
|
|
2309
|
-
);
|
|
2310
|
-
}
|
|
2311
|
-
async function assertCrossBatchRefsDeployed(client, definitions, ctx) {
|
|
2312
|
-
const missing = [];
|
|
2313
|
-
for (const def of definitions) {
|
|
2314
|
-
const fromId = definitionDocId(ctx.workflowResource, ctx.prefix, def.workflowId, def.version);
|
|
2315
|
-
for (const ref of refsOf(def)) {
|
|
2316
|
-
if (ctx.findInBatch(ref) !== void 0) continue;
|
|
2317
|
-
const label = await resolveDeployedRefLabel(client, ref, ctx.tags);
|
|
2318
|
-
label !== void 0 && missing.push({ from: fromId, ref: label });
|
|
2319
|
-
}
|
|
2320
|
-
}
|
|
2321
|
-
if (missing.length === 0) return;
|
|
2322
|
-
const lines = missing.map((m) => ` - ${m.from} \u2192 ${m.ref} (neither in batch nor deployed)`);
|
|
2323
|
-
throw new Error(
|
|
2324
|
-
`workflow.deployDefinitions: ${missing.length} unresolved reference${missing.length === 1 ? "" : "s"}:
|
|
2325
|
-
` + lines.join(`
|
|
2326
|
-
`)
|
|
2327
|
-
);
|
|
2328
|
-
}
|
|
2329
|
-
async function resolveDeployedRefLabel(client, ref, tags) {
|
|
2330
|
-
const wantsExplicit = typeof ref.version == "number", params = { workflowId: ref.workflowId, tags };
|
|
2331
|
-
if (wantsExplicit && (params.version = ref.version), !await client.fetch(
|
|
2332
|
-
definitionLookupGroq(wantsExplicit),
|
|
2333
|
-
params
|
|
2334
|
-
))
|
|
2335
|
-
return wantsExplicit ? `${ref.workflowId} v${ref.version}` : ref.workflowId;
|
|
2336
|
-
}
|
|
2337
|
-
function topoSortDefinitions(definitions, ctx) {
|
|
2338
|
-
const visited = /* @__PURE__ */ new Set(), visiting = /* @__PURE__ */ new Set(), ordered = [], visit = (def) => {
|
|
2339
|
-
const id = definitionDocId(ctx.workflowResource, ctx.prefix, def.workflowId, def.version);
|
|
2340
|
-
if (!visited.has(id)) {
|
|
2341
|
-
if (visiting.has(id))
|
|
2342
|
-
throw new Error(`workflow.deployDefinitions: dependency cycle involving "${id}"`);
|
|
2343
|
-
visiting.add(id);
|
|
2344
|
-
for (const ref of refsOf(def)) {
|
|
2345
|
-
const child = ctx.findInBatch(ref);
|
|
2346
|
-
child !== void 0 && visit(child);
|
|
2347
|
-
}
|
|
2348
|
-
visiting.delete(id), visited.add(id), ordered.push(def);
|
|
2349
|
-
}
|
|
2350
|
-
};
|
|
2351
|
-
for (const def of definitions) visit(def);
|
|
2352
|
-
return ordered;
|
|
2353
|
-
}
|
|
2354
|
-
function definitionLookupGroq(wantsExplicit) {
|
|
2355
|
-
return wantsExplicit ? '*[_type == "workflow.definition" && workflowId == $workflowId && version == $version && count(tags[@ in $tags]) > 0][0]' : '*[_type == "workflow.definition" && workflowId == $workflowId && count(tags[@ in $tags]) > 0] | order(version desc)[0]';
|
|
2356
|
-
}
|
|
2357
|
-
function refsOf(def) {
|
|
2358
|
-
const out = [];
|
|
2359
|
-
for (const stage of def.stages)
|
|
2360
|
-
for (const task of stage.tasks ?? []) {
|
|
2361
|
-
const ref = task.spawns?.definitionRef;
|
|
2362
|
-
ref !== void 0 && out.push({
|
|
2363
|
-
workflowId: ref.workflowId,
|
|
2364
|
-
...ref.version !== void 0 ? { version: ref.version } : {}
|
|
2365
|
-
});
|
|
2366
|
-
}
|
|
2367
|
-
return out;
|
|
2368
|
-
}
|
|
2369
|
-
function isDefinitionUnchanged(existing, expected) {
|
|
2370
|
-
return stableStringify(stripSystemFields(existing)) === stableStringify(stripSystemFields(expected));
|
|
2371
|
-
}
|
|
2372
|
-
function stripSystemFields(doc) {
|
|
2373
|
-
const out = {};
|
|
2374
|
-
for (const [k, v] of Object.entries(doc))
|
|
2375
|
-
k === "_rev" || k === "_createdAt" || k === "_updatedAt" || (out[k] = v);
|
|
2376
|
-
return out;
|
|
2377
|
-
}
|
|
2378
|
-
function stableStringify(value) {
|
|
2379
|
-
return value === null || typeof value != "object" ? JSON.stringify(value) : Array.isArray(value) ? `[${value.map(stableStringify).join(",")}]` : `{${Object.entries(value).toSorted(
|
|
2380
|
-
([a], [b]) => a.localeCompare(b)
|
|
2381
|
-
).map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`).join(",")}}`;
|
|
2382
|
-
}
|
|
2383
|
-
async function loadDefinition(client, workflowId, version, tags) {
|
|
2384
|
-
if (version !== void 0) {
|
|
2385
|
-
const doc = await client.fetch(
|
|
2386
|
-
"*[_type == 'workflow.definition' && workflowId == $id && version == $v && count(tags[@ in $engineTags]) > 0][0]",
|
|
2387
|
-
{ id: workflowId, v: version, engineTags: tags }
|
|
2388
|
-
);
|
|
2389
|
-
if (!doc)
|
|
2390
|
-
throw new Error(`Workflow definition ${workflowId} v${version} not deployed`);
|
|
2391
|
-
return doc;
|
|
2392
|
-
}
|
|
2393
|
-
const latest = await client.fetch(
|
|
2394
|
-
"*[_type == 'workflow.definition' && workflowId == $id && count(tags[@ in $engineTags]) > 0] | order(version desc)[0]",
|
|
2395
|
-
{ id: workflowId, engineTags: tags }
|
|
2396
|
-
);
|
|
2397
|
-
if (!latest)
|
|
2398
|
-
throw new Error(`No deployed definition for workflow ${workflowId}`);
|
|
2399
|
-
return latest;
|
|
2400
|
-
}
|
|
2401
2700
|
function bareIdFromSpawnRef(uri) {
|
|
2402
2701
|
if (!uri.includes(":")) return [uri];
|
|
2403
2702
|
try {
|
|
@@ -2418,9 +2717,16 @@ async function resolveOperationContext(args) {
|
|
|
2418
2717
|
clientForGdr: buildClientForGdr(args.client, args.resourceClients)
|
|
2419
2718
|
};
|
|
2420
2719
|
}
|
|
2421
|
-
async function cascade(client, instanceId, actor, clientForGdr) {
|
|
2422
|
-
const count = await cascadeAutoTransitions(
|
|
2423
|
-
|
|
2720
|
+
async function cascade(client, instanceId, actor, clientForGdr, clock, overlay) {
|
|
2721
|
+
const count = await cascadeAutoTransitions(
|
|
2722
|
+
client,
|
|
2723
|
+
instanceId,
|
|
2724
|
+
actor,
|
|
2725
|
+
clientForGdr,
|
|
2726
|
+
clock,
|
|
2727
|
+
overlay
|
|
2728
|
+
);
|
|
2729
|
+
return await propagateToAncestors(client, instanceId, actor, clientForGdr, clock), count;
|
|
2424
2730
|
}
|
|
2425
2731
|
function buildClientForGdr(defaultClient, resolver) {
|
|
2426
2732
|
return resolver === void 0 ? () => defaultClient : (parsed) => resolver(parsed) ?? defaultClient;
|
|
@@ -2438,6 +2744,22 @@ function intersectsTags(docTags, engineTags) {
|
|
|
2438
2744
|
function toEffectsContextEntries(ctx) {
|
|
2439
2745
|
return Object.entries(ctx).map(([id, value]) => effectsContextEntry(id, value));
|
|
2440
2746
|
}
|
|
2747
|
+
function assertActionAllowed(evaluation, taskId, action) {
|
|
2748
|
+
const actionEval = evaluation.currentStage.tasks.find((t) => t.task.id === taskId)?.actions.find((a) => a.action.id === action);
|
|
2749
|
+
if (actionEval?.allowed === !1 && actionEval.disabledReason)
|
|
2750
|
+
throw new ActionDisabledError({ taskId, action, reason: actionEval.disabledReason });
|
|
2751
|
+
}
|
|
2752
|
+
async function applyAction(args) {
|
|
2753
|
+
const { client, instance, taskId, action, params, actor, clock } = args, options = { actor, ...clock !== void 0 ? { clock } : {} };
|
|
2754
|
+
return findOpenStageEntry(instance)?.tasks.find((t) => t.id === taskId)?.status === "pending" && await invokeTask({ client, instanceId: instance._id, taskId, options }), (await fireAction({
|
|
2755
|
+
client,
|
|
2756
|
+
instanceId: instance._id,
|
|
2757
|
+
taskId,
|
|
2758
|
+
action,
|
|
2759
|
+
...params !== void 0 ? { params } : {},
|
|
2760
|
+
options
|
|
2761
|
+
})).ranOps;
|
|
2762
|
+
}
|
|
2441
2763
|
const workflow = {
|
|
2442
2764
|
/**
|
|
2443
2765
|
* Persist a workflow definition to the lake. Stored as a
|
|
@@ -2470,7 +2792,7 @@ const workflow = {
|
|
|
2470
2792
|
const id = definitionDocId(workflowResource, prefix, def.workflowId, def.version), expected = {
|
|
2471
2793
|
...def,
|
|
2472
2794
|
_id: id,
|
|
2473
|
-
_type:
|
|
2795
|
+
_type: WORKFLOW_DEFINITION_TYPE,
|
|
2474
2796
|
tags
|
|
2475
2797
|
}, existing = await client.getDocument(id);
|
|
2476
2798
|
if (existing && intersectsTags(existing.tags, tags) && isDefinitionUnchanged(existing, expected)) {
|
|
@@ -2512,16 +2834,17 @@ const workflow = {
|
|
|
2512
2834
|
effectsContext,
|
|
2513
2835
|
instanceId,
|
|
2514
2836
|
perspective
|
|
2515
|
-
} = args, { actor, clientForGdr } = await resolveOperationContext(args), definition = await loadDefinition(client, workflowId, version, tags), id = instanceId ?? `${canonicalTag(tags)}.wf-instance.${randomKey()}`;
|
|
2837
|
+
} = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, definition = await loadDefinition(client, workflowId, version, tags), id = instanceId ?? `${canonicalTag(tags)}.wf-instance.${randomKey()}`;
|
|
2516
2838
|
if (definition.stages.find((s) => s.id === definition.initialStageId) === void 0)
|
|
2517
2839
|
throw new Error(
|
|
2518
2840
|
`Initial stage "${definition.initialStageId}" missing in ${workflowId} v${definition.version}`
|
|
2519
2841
|
);
|
|
2520
|
-
const now = (
|
|
2842
|
+
const now = clock(), effectsContextEntries = effectsContext ? toEffectsContextEntries(effectsContext) : [], resolvedState = await resolveDeclaredState({
|
|
2521
2843
|
slotDefs: definition.state ?? [],
|
|
2522
2844
|
initialState: initialState ?? [],
|
|
2523
2845
|
ctx: {
|
|
2524
2846
|
client,
|
|
2847
|
+
now,
|
|
2525
2848
|
selfId: id,
|
|
2526
2849
|
tags,
|
|
2527
2850
|
workflowResource,
|
|
@@ -2543,7 +2866,7 @@ const workflow = {
|
|
|
2543
2866
|
initialStageId: definition.initialStageId,
|
|
2544
2867
|
actor
|
|
2545
2868
|
});
|
|
2546
|
-
return await client.create(base), await primeInitialStage(client, id, actor, clientForGdr), await cascade(client, id, actor, clientForGdr), reload(client, id, tags);
|
|
2869
|
+
return await client.create(base), await primeInitialStage(client, id, actor, clientForGdr, clock), await cascade(client, id, actor, clientForGdr, clock), reload(client, id, tags);
|
|
2547
2870
|
},
|
|
2548
2871
|
/**
|
|
2549
2872
|
* Fire an action against a task. If the task is pending, it is
|
|
@@ -2565,43 +2888,32 @@ const workflow = {
|
|
|
2565
2888
|
params,
|
|
2566
2889
|
idempotent,
|
|
2567
2890
|
resourceClients
|
|
2568
|
-
} = args, { access, actor, clientForGdr } = await resolveOperationContext(args), before = await reload(client, instanceId, tags)
|
|
2569
|
-
|
|
2570
|
-
)?.tasks.find((t) => t.id === taskId);
|
|
2571
|
-
if (taskEntry === void 0 && idempotent === !0)
|
|
2891
|
+
} = args, { access, actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, before = await reload(client, instanceId, tags);
|
|
2892
|
+
if (findOpenStageEntry(before)?.tasks.find((t) => t.id === taskId) === void 0 && idempotent === !0)
|
|
2572
2893
|
return { instance: before, cascaded: 0, fired: !1 };
|
|
2573
|
-
const
|
|
2894
|
+
const evaluation = await evaluateInstance({
|
|
2574
2895
|
client,
|
|
2575
2896
|
tags,
|
|
2576
2897
|
instanceId,
|
|
2577
2898
|
access,
|
|
2899
|
+
clock,
|
|
2578
2900
|
...resourceClients !== void 0 ? { resourceClients } : {}
|
|
2579
|
-
})).currentStage.tasks.find((t) => t.task.id === taskId)?.actions.find((a) => a.action.id === action);
|
|
2580
|
-
if (actionEval !== void 0 && !actionEval.allowed && actionEval.disabledReason)
|
|
2581
|
-
throw new ActionDisabledError({
|
|
2582
|
-
taskId,
|
|
2583
|
-
action,
|
|
2584
|
-
reason: actionEval.disabledReason
|
|
2585
|
-
});
|
|
2586
|
-
taskEntry?.status === "pending" && await invokeTask({
|
|
2587
|
-
client,
|
|
2588
|
-
instanceId,
|
|
2589
|
-
taskId,
|
|
2590
|
-
options: { actor }
|
|
2591
2901
|
});
|
|
2592
|
-
|
|
2902
|
+
assertActionAllowed(evaluation, taskId, action);
|
|
2903
|
+
const ranOps = await applyAction({
|
|
2593
2904
|
client,
|
|
2594
|
-
|
|
2905
|
+
instance: before,
|
|
2595
2906
|
taskId,
|
|
2596
2907
|
action,
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2908
|
+
actor,
|
|
2909
|
+
clock,
|
|
2910
|
+
...params !== void 0 ? { params } : {}
|
|
2911
|
+
}), cascaded = await cascade(client, instanceId, actor, clientForGdr, clock);
|
|
2600
2912
|
return {
|
|
2601
2913
|
instance: await reload(client, instanceId, tags),
|
|
2602
2914
|
cascaded,
|
|
2603
2915
|
fired: !0,
|
|
2604
|
-
...
|
|
2916
|
+
...ranOps !== void 0 ? { ranOps } : {}
|
|
2605
2917
|
};
|
|
2606
2918
|
},
|
|
2607
2919
|
/**
|
|
@@ -2612,7 +2924,7 @@ const workflow = {
|
|
|
2612
2924
|
* reference them. Cascades after.
|
|
2613
2925
|
*/
|
|
2614
2926
|
completeEffect: async (args) => {
|
|
2615
|
-
const { client, tags, instanceId, effectKey, status, outputs, detail, error, durationMs } = args, { actor, clientForGdr } = await resolveOperationContext(args);
|
|
2927
|
+
const { client, tags, instanceId, effectKey, status, outputs, detail, error, durationMs } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock;
|
|
2616
2928
|
await completeEffect({
|
|
2617
2929
|
client,
|
|
2618
2930
|
instanceId,
|
|
@@ -2622,9 +2934,9 @@ const workflow = {
|
|
|
2622
2934
|
...detail !== void 0 ? { detail } : {},
|
|
2623
2935
|
...error !== void 0 ? { error } : {},
|
|
2624
2936
|
...durationMs !== void 0 ? { durationMs } : {},
|
|
2625
|
-
...actor !== void 0 ? {
|
|
2937
|
+
options: { ...actor !== void 0 ? { actor } : {}, clock }
|
|
2626
2938
|
});
|
|
2627
|
-
const cascaded = await cascade(client, instanceId, actor, clientForGdr);
|
|
2939
|
+
const cascaded = await cascade(client, instanceId, actor, clientForGdr, clock);
|
|
2628
2940
|
return {
|
|
2629
2941
|
instance: await reload(client, instanceId, tags),
|
|
2630
2942
|
cascaded,
|
|
@@ -2641,9 +2953,9 @@ const workflow = {
|
|
|
2641
2953
|
* affected instances and the engine re-evaluates.
|
|
2642
2954
|
*/
|
|
2643
2955
|
tick: async (args) => {
|
|
2644
|
-
const { client, tags, instanceId } = args, { actor, clientForGdr } = await resolveOperationContext(args);
|
|
2956
|
+
const { client, tags, instanceId } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock;
|
|
2645
2957
|
await reload(client, instanceId, tags);
|
|
2646
|
-
const cascaded = await cascade(client, instanceId, actor, clientForGdr);
|
|
2958
|
+
const cascaded = await cascade(client, instanceId, actor, clientForGdr, clock);
|
|
2647
2959
|
return {
|
|
2648
2960
|
instance: await reload(client, instanceId, tags),
|
|
2649
2961
|
cascaded,
|
|
@@ -2656,15 +2968,15 @@ const workflow = {
|
|
|
2656
2968
|
* upstream; this verb performs the mechanical move.
|
|
2657
2969
|
*/
|
|
2658
2970
|
setStage: async (args) => {
|
|
2659
|
-
const { client, tags, instanceId, targetStageId, reason } = args, { actor, clientForGdr } = await resolveOperationContext(args);
|
|
2971
|
+
const { client, tags, instanceId, targetStageId, reason } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock;
|
|
2660
2972
|
await reload(client, instanceId, tags);
|
|
2661
2973
|
const result = await setStage({
|
|
2662
2974
|
client,
|
|
2663
2975
|
instanceId,
|
|
2664
2976
|
targetStageId,
|
|
2665
2977
|
...reason !== void 0 ? { reason } : {},
|
|
2666
|
-
...actor !== void 0 ? {
|
|
2667
|
-
}), cascaded = result.fired ? await cascade(client, instanceId, actor, clientForGdr) : 0;
|
|
2978
|
+
options: { ...actor !== void 0 ? { actor } : {}, clock }
|
|
2979
|
+
}), cascaded = result.fired ? await cascade(client, instanceId, actor, clientForGdr, clock) : 0;
|
|
2668
2980
|
return {
|
|
2669
2981
|
instance: await reload(client, instanceId, tags),
|
|
2670
2982
|
cascaded,
|
|
@@ -2680,6 +2992,16 @@ const workflow = {
|
|
|
2680
2992
|
const { client, tags, instanceId } = args;
|
|
2681
2993
|
return validateTags(tags), reload(client, instanceId, tags);
|
|
2682
2994
|
},
|
|
2995
|
+
guardsForInstance: async (args) => {
|
|
2996
|
+
const { client, tags, instanceId, resourceClients } = args;
|
|
2997
|
+
validateTags(tags);
|
|
2998
|
+
const instance = await reload(client, instanceId, tags);
|
|
2999
|
+
return guardsForInstance({
|
|
3000
|
+
client,
|
|
3001
|
+
clientForGdr: buildClientForGdr(client, resourceClients),
|
|
3002
|
+
instance
|
|
3003
|
+
});
|
|
3004
|
+
},
|
|
2683
3005
|
/**
|
|
2684
3006
|
* Run a caller-supplied GROQ query with the engine's tags bound as
|
|
2685
3007
|
* `$engineTags`. This does NOT rewrite the query — arbitrary GROQ
|
|
@@ -2714,9 +3036,9 @@ const workflow = {
|
|
|
2714
3036
|
* without re-implementing hydration. Pure read — never writes.
|
|
2715
3037
|
*/
|
|
2716
3038
|
queryInScope: async (args) => {
|
|
2717
|
-
const { client, tags, instanceId, groq, params, resourceClients } = args;
|
|
3039
|
+
const { client, tags, instanceId, groq, params, resourceClients } = args, clock = args.clock ?? wallClock;
|
|
2718
3040
|
validateTags(tags);
|
|
2719
|
-
const instance = await reload(client, instanceId, tags),
|
|
3041
|
+
const instance = await reload(client, instanceId, tags), clientForGdr = buildClientForGdr(client, resourceClients), snapshot = await hydrateSnapshot({ client, clientForGdr, instance }), reserved = buildParams(instance, clock()), tree = parse(groq, { params: { ...reserved, ...params } });
|
|
2720
3042
|
return await (await evaluate(tree, {
|
|
2721
3043
|
dataset: snapshot.docs,
|
|
2722
3044
|
params: { ...reserved, ...params }
|
|
@@ -2759,7 +3081,7 @@ const workflow = {
|
|
|
2759
3081
|
validateTags(tags);
|
|
2760
3082
|
const parent = await reload(client, instanceId, tags), isSpawned = (h) => h._type === "workflow.history.spawned", ids = parent.history.filter(isSpawned).filter((h) => taskId === void 0 || h.taskId === taskId).flatMap((h) => bareIdFromSpawnRef(h.instanceRef.id));
|
|
2761
3083
|
return ids.length === 0 ? [] : client.fetch(
|
|
2762
|
-
|
|
3084
|
+
`*[_id in $ids && ${tagScopeFilter()}] | order(startedAt asc)`,
|
|
2763
3085
|
{ ids, engineTags: tags }
|
|
2764
3086
|
);
|
|
2765
3087
|
},
|
|
@@ -2778,7 +3100,7 @@ async function verifyDeployedDefinitionsInternal(args) {
|
|
|
2778
3100
|
const { client, tags, effectHandlers, missingHandler, logger } = args;
|
|
2779
3101
|
validateTags(tags);
|
|
2780
3102
|
const log = logger("verifyDeployedDefinitions"), definitions = await client.fetch(
|
|
2781
|
-
|
|
3103
|
+
`*[_type == "${WORKFLOW_DEFINITION_TYPE}" && ${tagScopeFilter()}] | order(workflowId asc, version asc)`,
|
|
2782
3104
|
{ engineTags: tags }
|
|
2783
3105
|
), seen = [], missingByName = /* @__PURE__ */ new Map();
|
|
2784
3106
|
for (const def of definitions)
|
|
@@ -2900,7 +3222,8 @@ async function claimPendingEffect(client, instance, effectKey, drainerActor) {
|
|
|
2900
3222
|
};
|
|
2901
3223
|
try {
|
|
2902
3224
|
return await client.patch(instance._id).ifRevisionId(instance._rev).set({ [`pendingEffects[_key=="${effectKey}"].claim`]: claim }).commit(), !0;
|
|
2903
|
-
} catch {
|
|
3225
|
+
} catch (error) {
|
|
3226
|
+
if (!isRevisionConflict(error)) throw error;
|
|
2904
3227
|
return !1;
|
|
2905
3228
|
}
|
|
2906
3229
|
}
|
|
@@ -2928,6 +3251,89 @@ async function applyMissingHandler(policy, info, log) {
|
|
|
2928
3251
|
return "fail";
|
|
2929
3252
|
}
|
|
2930
3253
|
}
|
|
3254
|
+
function createInstanceSession(args) {
|
|
3255
|
+
const { client, tags, clock } = args, clientForGdr = buildClientForGdr(client, args.resourceClients);
|
|
3256
|
+
let instance = asHeldInstance(args.instance), overlay = /* @__PURE__ */ new Map(), committing = !1, buffered;
|
|
3257
|
+
const selfUri = () => gdrFromResource(instance.workflowResource, instance._id), applyUpdate = (docs) => {
|
|
3258
|
+
const next = /* @__PURE__ */ new Map();
|
|
3259
|
+
for (const ld of docs) {
|
|
3260
|
+
const owned = { doc: structuredClone(ld.doc), resource: ld.resource }, uri = gdrFromResource(owned.resource, owned.doc._id);
|
|
3261
|
+
if (uri === selfUri()) {
|
|
3262
|
+
instance = asHeldInstance(owned.doc);
|
|
3263
|
+
continue;
|
|
3264
|
+
}
|
|
3265
|
+
next.set(uri, owned);
|
|
3266
|
+
}
|
|
3267
|
+
overlay = next;
|
|
3268
|
+
}, access = () => resolveAccess(client, {
|
|
3269
|
+
...args.access !== void 0 ? { override: args.access } : {},
|
|
3270
|
+
...args.grantsFromPath !== void 0 ? { grantsFromPath: args.grantsFromPath } : {}
|
|
3271
|
+
}), snapshotFrom = (held) => buildSnapshot({ docs: [{ doc: instance, resource: instance.workflowResource }, ...held.values()] }), evaluateWith = async (held) => {
|
|
3272
|
+
const { actor, grants } = await access();
|
|
3273
|
+
return evaluateFromSnapshot({
|
|
3274
|
+
instance,
|
|
3275
|
+
definition: parseDefinitionSnapshot(instance),
|
|
3276
|
+
actor,
|
|
3277
|
+
snapshot: snapshotFrom(held),
|
|
3278
|
+
...clock !== void 0 ? { now: clock() } : {},
|
|
3279
|
+
...grants !== void 0 ? { grants } : {}
|
|
3280
|
+
});
|
|
3281
|
+
}, commit = async (run) => {
|
|
3282
|
+
committing = !0;
|
|
3283
|
+
const held = new Map(overlay);
|
|
3284
|
+
try {
|
|
3285
|
+
const { actor } = await access();
|
|
3286
|
+
return await run(actor, held);
|
|
3287
|
+
} finally {
|
|
3288
|
+
if (committing = !1, buffered !== void 0) {
|
|
3289
|
+
const next = buffered;
|
|
3290
|
+
buffered = void 0, applyUpdate(next);
|
|
3291
|
+
}
|
|
3292
|
+
}
|
|
3293
|
+
};
|
|
3294
|
+
return {
|
|
3295
|
+
get subscriptionDocuments() {
|
|
3296
|
+
return subscriptionDocumentsForInstance(instance);
|
|
3297
|
+
},
|
|
3298
|
+
update(docs) {
|
|
3299
|
+
if (committing) {
|
|
3300
|
+
buffered = docs;
|
|
3301
|
+
return;
|
|
3302
|
+
}
|
|
3303
|
+
applyUpdate(docs);
|
|
3304
|
+
},
|
|
3305
|
+
evaluate() {
|
|
3306
|
+
return evaluateWith(overlay);
|
|
3307
|
+
},
|
|
3308
|
+
tick() {
|
|
3309
|
+
return commit(async (actor, held) => {
|
|
3310
|
+
const cascaded = await cascade(client, instance._id, actor, clientForGdr, clock, held);
|
|
3311
|
+
return instance = await reload(client, instance._id, tags), { instance, cascaded, fired: cascaded > 0 };
|
|
3312
|
+
});
|
|
3313
|
+
},
|
|
3314
|
+
fireAction({ taskId, action, params }) {
|
|
3315
|
+
return commit(async (actor, held) => {
|
|
3316
|
+
assertActionAllowed(await evaluateWith(held), taskId, action);
|
|
3317
|
+
const ranOps = await applyAction({
|
|
3318
|
+
client,
|
|
3319
|
+
instance,
|
|
3320
|
+
taskId,
|
|
3321
|
+
action,
|
|
3322
|
+
actor,
|
|
3323
|
+
...clock !== void 0 ? { clock } : {},
|
|
3324
|
+
...params !== void 0 ? { params } : {}
|
|
3325
|
+
}), cascaded = await cascade(client, instance._id, actor, clientForGdr, clock, held);
|
|
3326
|
+
return instance = await reload(client, instance._id, tags), { instance, cascaded, fired: !0, ...ranOps !== void 0 ? { ranOps } : {} };
|
|
3327
|
+
});
|
|
3328
|
+
}
|
|
3329
|
+
};
|
|
3330
|
+
}
|
|
3331
|
+
function asHeldInstance(doc) {
|
|
3332
|
+
const candidate = doc;
|
|
3333
|
+
if (candidate._type !== "workflow.instance" || typeof candidate.currentStageId != "string" || !Array.isArray(candidate.stages))
|
|
3334
|
+
throw new Error(`update: self-doc ${doc._id} is not a valid workflow.instance`);
|
|
3335
|
+
return doc;
|
|
3336
|
+
}
|
|
2931
3337
|
const defaultLoggerFactory = (name) => ({
|
|
2932
3338
|
// info/warn/error all go to stderr — these are diagnostic, not
|
|
2933
3339
|
// program output. Keeps stdout reserved for the caller's own data
|
|
@@ -2945,13 +3351,14 @@ const defaultLoggerFactory = (name) => ({
|
|
|
2945
3351
|
}
|
|
2946
3352
|
};
|
|
2947
3353
|
function createEngine(args) {
|
|
2948
|
-
const { client, workflowResource, tags, resourceClients } = args;
|
|
3354
|
+
const { client, workflowResource, tags, resourceClients, clock } = args;
|
|
2949
3355
|
validateTags(tags);
|
|
2950
3356
|
const effectHandlers = args.effectHandlers ?? {}, missingHandler = args.missingHandler ?? "fail", logger = args.loggerFactory ?? defaultLoggerFactory, bind = (rest) => ({
|
|
2951
3357
|
client,
|
|
2952
3358
|
tags,
|
|
2953
3359
|
workflowResource,
|
|
2954
3360
|
...resourceClients !== void 0 ? { resourceClients } : {},
|
|
3361
|
+
...clock !== void 0 ? { clock } : {},
|
|
2955
3362
|
...rest
|
|
2956
3363
|
});
|
|
2957
3364
|
return {
|
|
@@ -2969,6 +3376,23 @@ function createEngine(args) {
|
|
|
2969
3376
|
evaluateInstance: (rest) => evaluateInstance(bind(rest)),
|
|
2970
3377
|
setStage: (rest) => workflow.setStage(bind(rest)),
|
|
2971
3378
|
getInstance: ({ instanceId }) => workflow.getInstance({ client, tags, workflowResource, instanceId }),
|
|
3379
|
+
subscriptionDocumentsForInstance: ({ instanceId }) => workflow.getInstance({ client, tags, workflowResource, instanceId }).then(subscriptionDocumentsForInstance),
|
|
3380
|
+
instance: (instanceDoc, opts) => createInstanceSession({
|
|
3381
|
+
client,
|
|
3382
|
+
tags,
|
|
3383
|
+
instance: instanceDoc,
|
|
3384
|
+
...resourceClients !== void 0 ? { resourceClients } : {},
|
|
3385
|
+
...clock !== void 0 ? { clock } : {},
|
|
3386
|
+
...opts?.access !== void 0 ? { access: opts.access } : {},
|
|
3387
|
+
...opts?.grantsFromPath !== void 0 ? { grantsFromPath: opts.grantsFromPath } : {}
|
|
3388
|
+
}),
|
|
3389
|
+
guardsForInstance: ({ instanceId }) => workflow.guardsForInstance({
|
|
3390
|
+
client,
|
|
3391
|
+
tags,
|
|
3392
|
+
workflowResource,
|
|
3393
|
+
instanceId,
|
|
3394
|
+
...resourceClients !== void 0 ? { resourceClients } : {}
|
|
3395
|
+
}),
|
|
2972
3396
|
children: ({ instanceId, taskId }) => workflow.children({
|
|
2973
3397
|
client,
|
|
2974
3398
|
tags,
|
|
@@ -2989,7 +3413,8 @@ function createEngine(args) {
|
|
|
2989
3413
|
workflowResource,
|
|
2990
3414
|
instanceId,
|
|
2991
3415
|
groq,
|
|
2992
|
-
...params !== void 0 ? { params } : {}
|
|
3416
|
+
...params !== void 0 ? { params } : {},
|
|
3417
|
+
...clock !== void 0 ? { clock } : {}
|
|
2993
3418
|
}),
|
|
2994
3419
|
listPendingEffects: ({ instanceId }) => workflow.listPendingEffects({ client, tags, workflowResource, instanceId }),
|
|
2995
3420
|
findPendingEffects: ({ instanceId, claimed, names }) => workflow.findPendingEffects({
|
|
@@ -3020,15 +3445,6 @@ function createEngine(args) {
|
|
|
3020
3445
|
})
|
|
3021
3446
|
};
|
|
3022
3447
|
}
|
|
3023
|
-
class MutationGuardDeniedError extends Error {
|
|
3024
|
-
denied;
|
|
3025
|
-
documentId;
|
|
3026
|
-
action;
|
|
3027
|
-
constructor(args) {
|
|
3028
|
-
const ids = args.denied.map((d) => d.guardId).join(", ");
|
|
3029
|
-
super(`Mutation on "${args.documentId}" (${args.action}) denied by guard(s) [${ids}]`), this.name = "MutationGuardDeniedError", this.denied = args.denied, this.documentId = args.documentId, this.action = args.action;
|
|
3030
|
-
}
|
|
3031
|
-
}
|
|
3032
3448
|
const HISTORY_DISPLAY = {
|
|
3033
3449
|
"workflow.history.stageEntered": {
|
|
3034
3450
|
title: "Stage entered",
|
|
@@ -3224,41 +3640,58 @@ export {
|
|
|
3224
3640
|
AUTHORING_DISPLAY,
|
|
3225
3641
|
ActionDisabledError,
|
|
3226
3642
|
ActionParamsInvalidError,
|
|
3643
|
+
CascadeLimitError,
|
|
3644
|
+
ConcurrentFireActionError,
|
|
3227
3645
|
DISPLAY,
|
|
3228
3646
|
EFFECTS_CONTEXT_DISPLAY,
|
|
3647
|
+
GUARD_DOC_TYPE,
|
|
3229
3648
|
GUARD_LIFTED_PREDICATE,
|
|
3230
3649
|
GUARD_OWNER,
|
|
3231
3650
|
HISTORY_DISPLAY,
|
|
3232
3651
|
MutationGuardDeniedError,
|
|
3233
3652
|
OP_DISPLAY,
|
|
3234
3653
|
STATE_SLOT_DISPLAY,
|
|
3654
|
+
WORKFLOW_DEFINITION_TYPE,
|
|
3655
|
+
WORKFLOW_INSTANCE_TYPE,
|
|
3656
|
+
WorkflowStateDivergedError,
|
|
3657
|
+
buildSnapshot,
|
|
3235
3658
|
canonicalTag,
|
|
3236
3659
|
compileGuard,
|
|
3660
|
+
contentReleaseName,
|
|
3237
3661
|
createEngine,
|
|
3238
3662
|
defaultLoggerFactory,
|
|
3239
3663
|
denyingGuards,
|
|
3240
3664
|
deployStageGuards,
|
|
3241
3665
|
displayDescription,
|
|
3242
3666
|
displayTitle,
|
|
3667
|
+
evaluateFromSnapshot,
|
|
3243
3668
|
evaluateMutationGuard,
|
|
3244
3669
|
extractDocumentId,
|
|
3245
3670
|
gdrFromResource,
|
|
3246
3671
|
gdrRef,
|
|
3247
3672
|
gdrUri,
|
|
3248
3673
|
guardMatches,
|
|
3674
|
+
guardsForInstance,
|
|
3675
|
+
guardsForResource,
|
|
3676
|
+
isDefinitionUnchanged,
|
|
3249
3677
|
isGdr,
|
|
3250
3678
|
lakeGuardId,
|
|
3251
3679
|
parseGdr,
|
|
3252
|
-
|
|
3680
|
+
readsRaw,
|
|
3253
3681
|
refCanvas,
|
|
3254
3682
|
refDashboard,
|
|
3255
3683
|
refDataset,
|
|
3256
3684
|
refMediaLibrary,
|
|
3257
3685
|
resolveAccess,
|
|
3686
|
+
resourceFromParsed,
|
|
3258
3687
|
retractStageGuards,
|
|
3259
3688
|
silentLogger,
|
|
3689
|
+
stripSystemFields,
|
|
3690
|
+
subscriptionDocumentsForInstance,
|
|
3691
|
+
tagScopeFilter,
|
|
3260
3692
|
validateDefinition,
|
|
3261
3693
|
validateTags,
|
|
3694
|
+
wallClock,
|
|
3262
3695
|
workflow
|
|
3263
3696
|
};
|
|
3264
3697
|
//# sourceMappingURL=index.js.map
|