@sanity/workflow-engine 0.1.0 → 0.2.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/define.cjs +271 -249
- 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 +255 -249
- package/dist/define.js.map +1 -1
- package/dist/index.cjs +626 -341
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5862 -3151
- package/dist/index.d.ts +5862 -3151
- package/dist/index.js +610 -341
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -1,18 +1,34 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: !0 });
|
|
3
|
-
var groqJs = require("groq-js"),
|
|
3
|
+
var groqJs = require("groq-js"), v = require("valibot");
|
|
4
|
+
function _interopNamespaceCompat(e) {
|
|
5
|
+
if (e && typeof e == "object" && "default" in e) return e;
|
|
6
|
+
var n = /* @__PURE__ */ Object.create(null);
|
|
7
|
+
return e && Object.keys(e).forEach(function(k) {
|
|
8
|
+
if (k !== "default") {
|
|
9
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
10
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
11
|
+
enumerable: !0,
|
|
12
|
+
get: function() {
|
|
13
|
+
return e[k];
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
}), n.default = e, Object.freeze(n);
|
|
18
|
+
}
|
|
19
|
+
var v__namespace = /* @__PURE__ */ _interopNamespaceCompat(v);
|
|
4
20
|
function validateDefinition(definition) {
|
|
5
|
-
const
|
|
21
|
+
const v2 = createDefinitionValidator();
|
|
6
22
|
for (const slot of definition.state ?? [])
|
|
7
|
-
|
|
23
|
+
v2.checkSlot(slot, `workflow.state "${slot.id}"`);
|
|
8
24
|
for (const p of definition.predicates ?? [])
|
|
9
|
-
p.groq &&
|
|
25
|
+
p.groq && v2.checkFilter(p.groq, `predicate "${p.id}"`);
|
|
10
26
|
for (const stage of definition.stages)
|
|
11
|
-
validateStage(
|
|
12
|
-
if (
|
|
27
|
+
validateStage(v2, stage);
|
|
28
|
+
if (v2.errors.length > 0)
|
|
13
29
|
throw new Error(
|
|
14
|
-
`defineWorkflow("${definition.workflowId}", v${definition.version}): ${
|
|
15
|
-
` +
|
|
30
|
+
`defineWorkflow("${definition.workflowId}", v${definition.version}): ${v2.errors.length} validation error${v2.errors.length === 1 ? "" : "s"}:
|
|
31
|
+
` + v2.errors.join(`
|
|
16
32
|
`)
|
|
17
33
|
);
|
|
18
34
|
}
|
|
@@ -31,31 +47,29 @@ function createDefinitionValidator() {
|
|
|
31
47
|
};
|
|
32
48
|
return { errors, tryParse, checkFilter: (groq, where) => {
|
|
33
49
|
tryParse(groq, where), rejectTypeScan(groq, where);
|
|
34
|
-
}, checkSlot: (slot,
|
|
35
|
-
slot.source.kind === "
|
|
36
|
-
` \xB7 ${scopeLabel} slot "${slot.id}": source.kind="computed" is reserved (not implemented in v0.1)`
|
|
37
|
-
), slot.source.kind === "query" && tryParse(slot.source.query, `${slotLabel}.query`);
|
|
50
|
+
}, checkSlot: (slot, slotLabel) => {
|
|
51
|
+
slot.source.kind === "query" && tryParse(slot.source.query, `${slotLabel}.query`);
|
|
38
52
|
} };
|
|
39
53
|
}
|
|
40
|
-
function validateStage(
|
|
54
|
+
function validateStage(v2, stage) {
|
|
41
55
|
for (const slot of stage.state ?? [])
|
|
42
|
-
|
|
43
|
-
typeof stage.completion == "string" &&
|
|
56
|
+
v2.checkSlot(slot, `stage "${stage.id}".state "${slot.id}"`);
|
|
57
|
+
typeof stage.completion == "string" && v2.tryParse(stage.completion, `stage "${stage.id}".completion`);
|
|
44
58
|
for (const t of stage.transitions ?? [])
|
|
45
|
-
typeof t.filter == "string" &&
|
|
59
|
+
typeof t.filter == "string" && v2.checkFilter(t.filter, `transition ${stage.id} \u2192 ${t.to} filter`);
|
|
46
60
|
for (const task of stage.tasks ?? [])
|
|
47
|
-
validateTask(
|
|
61
|
+
validateTask(v2, stage.id, task);
|
|
48
62
|
}
|
|
49
|
-
function validateTask(
|
|
63
|
+
function validateTask(v2, stageId, task) {
|
|
50
64
|
const where = `stage "${stageId}" task "${task.id}"`;
|
|
51
65
|
for (const slot of task.state ?? [])
|
|
52
|
-
|
|
53
|
-
typeof task.filter == "string" &&
|
|
66
|
+
v2.checkSlot(slot, `${where}.state "${slot.id}"`);
|
|
67
|
+
typeof task.filter == "string" && v2.checkFilter(task.filter, `${where}.filter`), typeof task.completeWhen == "string" && v2.checkFilter(task.completeWhen, `${where}.completeWhen`);
|
|
54
68
|
for (const a of task.actions ?? [])
|
|
55
|
-
typeof a.filter == "string" &&
|
|
56
|
-
task.spawns?.forEach?.groq &&
|
|
69
|
+
typeof a.filter == "string" && v2.checkFilter(a.filter, `task "${task.id}" action "${a.id}".filter`);
|
|
70
|
+
task.spawns?.forEach?.groq && v2.tryParse(task.spawns.forEach.groq, `task "${task.id}".spawns.forEach.groq`);
|
|
57
71
|
}
|
|
58
|
-
const KNOWN_SCHEMES = /* @__PURE__ */ new Set(["dataset", "canvas", "media-library", "dashboard"]);
|
|
72
|
+
const wallClock = () => (/* @__PURE__ */ new Date()).toISOString(), KNOWN_SCHEMES = /* @__PURE__ */ new Set(["dataset", "canvas", "media-library", "dashboard"]);
|
|
59
73
|
function parseGdr(uri) {
|
|
60
74
|
const colon = uri.indexOf(":");
|
|
61
75
|
if (colon < 0)
|
|
@@ -122,6 +136,9 @@ function gdrFromResource(res, documentId) {
|
|
|
122
136
|
}
|
|
123
137
|
return gdrUri({ scheme: res.type, resourceId: res.id, documentId });
|
|
124
138
|
}
|
|
139
|
+
function selfGdr(doc) {
|
|
140
|
+
return gdrFromResource(doc.workflowResource, doc._id);
|
|
141
|
+
}
|
|
125
142
|
function isGdrUri(value) {
|
|
126
143
|
if (typeof value != "string") return !1;
|
|
127
144
|
try {
|
|
@@ -136,15 +153,15 @@ function gdrRef(res, documentId, type) {
|
|
|
136
153
|
function isGdr(value) {
|
|
137
154
|
return typeof value == "object" && value !== null && typeof value.id == "string" && typeof value.type == "string";
|
|
138
155
|
}
|
|
139
|
-
function buildParams(instance, extra) {
|
|
156
|
+
function buildParams(instance, now, extra) {
|
|
140
157
|
return {
|
|
141
|
-
self:
|
|
158
|
+
self: selfGdr(instance),
|
|
142
159
|
state: stateMap(instance.state ?? []),
|
|
143
160
|
parent: instance.ancestors.at(-1)?.id ?? null,
|
|
144
161
|
ancestors: instance.ancestors.map((a) => a.id),
|
|
145
162
|
/** Current stage id — bind for filters that scope to the current stage. */
|
|
146
163
|
stage: instance.currentStageId,
|
|
147
|
-
now
|
|
164
|
+
now,
|
|
148
165
|
...extra
|
|
149
166
|
};
|
|
150
167
|
}
|
|
@@ -168,8 +185,8 @@ function stripStateForLake(value) {
|
|
|
168
185
|
if (Array.isArray(value)) return value.map(stripStateForLake);
|
|
169
186
|
if (typeof value == "object") {
|
|
170
187
|
const out = {};
|
|
171
|
-
for (const [k,
|
|
172
|
-
out[k] = stripStateForLake(
|
|
188
|
+
for (const [k, v2] of Object.entries(value))
|
|
189
|
+
out[k] = stripStateForLake(v2);
|
|
173
190
|
return out;
|
|
174
191
|
}
|
|
175
192
|
return value;
|
|
@@ -227,7 +244,7 @@ function matchesParamType(type, value) {
|
|
|
227
244
|
case "boolean":
|
|
228
245
|
return typeof value == "boolean";
|
|
229
246
|
case "string[]":
|
|
230
|
-
return Array.isArray(value) && value.every((
|
|
247
|
+
return Array.isArray(value) && value.every((v2) => typeof v2 == "string");
|
|
231
248
|
case "reference":
|
|
232
249
|
return isGdr(value);
|
|
233
250
|
}
|
|
@@ -248,11 +265,11 @@ function restampForSnapshot(doc, gdrUri2, resource) {
|
|
|
248
265
|
}
|
|
249
266
|
function rewriteRefsRecursive(value, resource) {
|
|
250
267
|
if (Array.isArray(value))
|
|
251
|
-
return value.map((
|
|
268
|
+
return value.map((v2) => rewriteRefsRecursive(v2, resource));
|
|
252
269
|
if (value === null || typeof value != "object") return value;
|
|
253
270
|
const obj = value, out = {};
|
|
254
|
-
for (const [k,
|
|
255
|
-
k === "_ref" && typeof
|
|
271
|
+
for (const [k, v2] of Object.entries(obj))
|
|
272
|
+
k === "_ref" && typeof v2 == "string" ? out[k] = v2.includes(":") ? v2 : gdrFromResource(resource, v2) : out[k] = rewriteRefsRecursive(v2, resource);
|
|
256
273
|
return out;
|
|
257
274
|
}
|
|
258
275
|
async function hydrateSnapshot(args) {
|
|
@@ -261,7 +278,7 @@ async function hydrateSnapshot(args) {
|
|
|
261
278
|
const fetched = await loadByGdr(client, clientForGdr, instance.workflowResource, uri);
|
|
262
279
|
fetched && (loaded.push(fetched), visited.add(uri));
|
|
263
280
|
};
|
|
264
|
-
loaded.push({ doc: instance, resource: instance.workflowResource }), visited.add(
|
|
281
|
+
loaded.push({ doc: instance, resource: instance.workflowResource }), visited.add(selfGdr(instance));
|
|
265
282
|
for (const ancestor of instance.ancestors)
|
|
266
283
|
await loadInto(ancestor.id);
|
|
267
284
|
for (const uri of collectSlotDocUris(instance.state))
|
|
@@ -296,12 +313,12 @@ function slotDocUris(slot) {
|
|
|
296
313
|
if (!slot || typeof slot != "object") return [];
|
|
297
314
|
const s = slot;
|
|
298
315
|
if (s._type === "workflow.state.doc.ref") {
|
|
299
|
-
const
|
|
316
|
+
const v2 = s.value, id = refId(v2);
|
|
300
317
|
return id !== void 0 ? [id] : [];
|
|
301
318
|
}
|
|
302
319
|
if (s._type === "workflow.state.doc.refs") {
|
|
303
|
-
const
|
|
304
|
-
return Array.isArray(
|
|
320
|
+
const v2 = s.value;
|
|
321
|
+
return Array.isArray(v2) ? v2.map(refId).filter((id) => id !== void 0) : [];
|
|
305
322
|
}
|
|
306
323
|
return [];
|
|
307
324
|
}
|
|
@@ -325,50 +342,61 @@ class SlotValueShapeError extends Error {
|
|
|
325
342
|
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;
|
|
326
343
|
}
|
|
327
344
|
}
|
|
328
|
-
const GdrShape =
|
|
329
|
-
id:
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
type:
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
345
|
+
const GdrShape = v__namespace.looseObject({
|
|
346
|
+
id: v__namespace.pipe(
|
|
347
|
+
v__namespace.string(),
|
|
348
|
+
v__namespace.check((s) => isGdrUri(s), "must be a GDR URI")
|
|
349
|
+
),
|
|
350
|
+
type: v__namespace.pipe(v__namespace.string(), v__namespace.minLength(1))
|
|
351
|
+
}), ReleaseRefShape = v__namespace.looseObject({
|
|
352
|
+
id: v__namespace.pipe(
|
|
353
|
+
v__namespace.string(),
|
|
354
|
+
v__namespace.check((s) => isGdrUri(s), "must be a GDR URI")
|
|
355
|
+
),
|
|
356
|
+
type: v__namespace.literal("system.release"),
|
|
357
|
+
releaseName: v__namespace.pipe(v__namespace.string(), v__namespace.minLength(1))
|
|
358
|
+
}), ActorShape = v__namespace.looseObject({
|
|
359
|
+
kind: v__namespace.picklist(["user", "ai", "system"]),
|
|
360
|
+
id: v__namespace.pipe(v__namespace.string(), v__namespace.minLength(1)),
|
|
361
|
+
roles: v__namespace.optional(v__namespace.array(v__namespace.string())),
|
|
362
|
+
onBehalfOf: v__namespace.optional(v__namespace.string())
|
|
363
|
+
}), AssigneeShape = v__namespace.union([
|
|
364
|
+
v__namespace.looseObject({ kind: v__namespace.literal("user"), id: v__namespace.pipe(v__namespace.string(), v__namespace.minLength(1)) }),
|
|
365
|
+
v__namespace.looseObject({ kind: v__namespace.literal("role"), role: v__namespace.pipe(v__namespace.string(), v__namespace.minLength(1)) })
|
|
366
|
+
]), ChecklistItemShape = v__namespace.looseObject({
|
|
367
|
+
label: v__namespace.pipe(v__namespace.string(), v__namespace.minLength(1)),
|
|
368
|
+
done: v__namespace.boolean(),
|
|
369
|
+
doneBy: v__namespace.optional(v__namespace.string()),
|
|
370
|
+
doneAt: v__namespace.optional(v__namespace.string()),
|
|
371
|
+
_key: v__namespace.optional(v__namespace.pipe(v__namespace.string(), v__namespace.minLength(1)))
|
|
372
|
+
}), NoteItemShape = v__namespace.pipe(
|
|
373
|
+
v__namespace.looseObject({
|
|
374
|
+
_key: v__namespace.optional(v__namespace.pipe(v__namespace.string(), v__namespace.minLength(1)))
|
|
375
|
+
}),
|
|
376
|
+
v__namespace.check(
|
|
377
|
+
(val) => typeof val == "object" && val !== null && !Array.isArray(val),
|
|
378
|
+
"must be an object"
|
|
379
|
+
)
|
|
380
|
+
), NullableString = v__namespace.union([v__namespace.null(), v__namespace.string()]), NullableNumber = v__namespace.union([v__namespace.null(), v__namespace.number()]), NullableBoolean = v__namespace.union([v__namespace.null(), v__namespace.boolean()]), NullableDateTime = v__namespace.union([
|
|
381
|
+
v__namespace.null(),
|
|
382
|
+
v__namespace.pipe(
|
|
383
|
+
v__namespace.string(),
|
|
384
|
+
v__namespace.check((s) => !Number.isNaN(Date.parse(s)), "must be an ISO-8601 datetime string")
|
|
385
|
+
)
|
|
358
386
|
]), NullableUrl = NullableString, valueSchemas = {
|
|
359
|
-
"workflow.state.doc.ref":
|
|
360
|
-
"workflow.state.doc.refs":
|
|
361
|
-
"workflow.state.release.ref":
|
|
362
|
-
"workflow.state.query":
|
|
387
|
+
"workflow.state.doc.ref": v__namespace.union([v__namespace.null(), GdrShape]),
|
|
388
|
+
"workflow.state.doc.refs": v__namespace.array(GdrShape),
|
|
389
|
+
"workflow.state.release.ref": v__namespace.union([v__namespace.null(), ReleaseRefShape]),
|
|
390
|
+
"workflow.state.query": v__namespace.any(),
|
|
363
391
|
"workflow.state.value.string": NullableString,
|
|
364
392
|
"workflow.state.value.url": NullableUrl,
|
|
365
393
|
"workflow.state.value.number": NullableNumber,
|
|
366
394
|
"workflow.state.value.boolean": NullableBoolean,
|
|
367
395
|
"workflow.state.value.dateTime": NullableDateTime,
|
|
368
|
-
"workflow.state.value.actor":
|
|
369
|
-
"workflow.state.checklist":
|
|
370
|
-
"workflow.state.notes":
|
|
371
|
-
"workflow.state.assignees":
|
|
396
|
+
"workflow.state.value.actor": v__namespace.union([v__namespace.null(), ActorShape]),
|
|
397
|
+
"workflow.state.checklist": v__namespace.array(ChecklistItemShape),
|
|
398
|
+
"workflow.state.notes": v__namespace.array(NoteItemShape),
|
|
399
|
+
"workflow.state.assignees": v__namespace.array(AssigneeShape)
|
|
372
400
|
}, itemSchemas = {
|
|
373
401
|
"workflow.state.doc.refs": GdrShape,
|
|
374
402
|
"workflow.state.checklist": ChecklistItemShape,
|
|
@@ -387,12 +415,12 @@ function validateSlotValue(args) {
|
|
|
387
415
|
issues: [`unknown slot type ${args.slotType}`],
|
|
388
416
|
mode: "value"
|
|
389
417
|
});
|
|
390
|
-
const result =
|
|
418
|
+
const result = v__namespace.safeParse(schema, args.value);
|
|
391
419
|
if (!result.success)
|
|
392
420
|
throw new SlotValueShapeError({
|
|
393
421
|
slotType: args.slotType,
|
|
394
422
|
slotId: args.slotId,
|
|
395
|
-
issues: formatIssues(result.
|
|
423
|
+
issues: formatIssues(result.issues),
|
|
396
424
|
mode: "value"
|
|
397
425
|
});
|
|
398
426
|
}
|
|
@@ -404,17 +432,20 @@ function validateSlotAppendItem(args) {
|
|
|
404
432
|
issues: [`slot type ${args.slotType} does not support append`],
|
|
405
433
|
mode: "item"
|
|
406
434
|
});
|
|
407
|
-
const
|
|
435
|
+
const schema = itemSchemas[args.slotType], result = v__namespace.safeParse(schema, args.item);
|
|
408
436
|
if (!result.success)
|
|
409
437
|
throw new SlotValueShapeError({
|
|
410
438
|
slotType: args.slotType,
|
|
411
439
|
slotId: args.slotId,
|
|
412
|
-
issues: formatIssues(result.
|
|
440
|
+
issues: formatIssues(result.issues),
|
|
413
441
|
mode: "item"
|
|
414
442
|
});
|
|
415
443
|
}
|
|
416
444
|
function formatIssues(issues) {
|
|
417
|
-
return issues.map((i) =>
|
|
445
|
+
return issues.map((i) => {
|
|
446
|
+
const keys = i.path?.map((p) => p.key) ?? [];
|
|
447
|
+
return `${keys.length > 0 ? `at ${keys.join(".")}: ` : ""}${i.message}`;
|
|
448
|
+
});
|
|
418
449
|
}
|
|
419
450
|
function derivePerspectiveFromState(slots) {
|
|
420
451
|
if (slots !== void 0) {
|
|
@@ -459,7 +490,7 @@ async function resolveQueryValue(slot, source, ctx, client, defaultValue) {
|
|
|
459
490
|
state: stateMapFromResolved(ctx.resolvedState ?? []),
|
|
460
491
|
stage: ctx.stageId ?? null,
|
|
461
492
|
task: ctx.taskId ?? null,
|
|
462
|
-
now:
|
|
493
|
+
now: ctx.now,
|
|
463
494
|
engineTags: ctx.tags ?? []
|
|
464
495
|
});
|
|
465
496
|
try {
|
|
@@ -476,7 +507,7 @@ async function resolveSlotValue(slot, initialState, ctx, defaultValue) {
|
|
|
476
507
|
const source = slot.source;
|
|
477
508
|
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;
|
|
478
509
|
}
|
|
479
|
-
function buildResolvedSlot(slot, value, _key) {
|
|
510
|
+
function buildResolvedSlot(slot, value, _key, now) {
|
|
480
511
|
const titleProp = slot.title !== void 0 ? { title: slot.title } : {}, descriptionProp = slot.description !== void 0 ? { description: slot.description } : {};
|
|
481
512
|
return slot.type === "workflow.state.query" ? {
|
|
482
513
|
_key,
|
|
@@ -485,7 +516,7 @@ function buildResolvedSlot(slot, value, _key) {
|
|
|
485
516
|
...titleProp,
|
|
486
517
|
...descriptionProp,
|
|
487
518
|
value,
|
|
488
|
-
resolvedAt:
|
|
519
|
+
resolvedAt: now
|
|
489
520
|
} : {
|
|
490
521
|
_key,
|
|
491
522
|
_type: slot.type,
|
|
@@ -497,7 +528,7 @@ function buildResolvedSlot(slot, value, _key) {
|
|
|
497
528
|
}
|
|
498
529
|
async function resolveOneSlot(slot, initialState, ctx, randomKey2) {
|
|
499
530
|
const defaultValue = defaultSlotValue(slot.type), value = await resolveSlotValue(slot, initialState, ctx, defaultValue);
|
|
500
|
-
return validateSlotValue({ slotType: slot.type, slotId: slot.id, value }), buildResolvedSlot(slot, value, randomKey2());
|
|
531
|
+
return validateSlotValue({ slotType: slot.type, slotId: slot.id, value }), buildResolvedSlot(slot, value, randomKey2(), ctx.now);
|
|
501
532
|
}
|
|
502
533
|
function stateMapFromResolved(slots) {
|
|
503
534
|
const out = {};
|
|
@@ -511,10 +542,10 @@ function assertInitValueShape(slot, value) {
|
|
|
511
542
|
}
|
|
512
543
|
if (slot.type === "workflow.state.release.ref") {
|
|
513
544
|
assertGdrShape(value, `state slot "${slot.id}" (workflow.state.release.ref)`);
|
|
514
|
-
const
|
|
515
|
-
if (typeof
|
|
545
|
+
const v2 = value;
|
|
546
|
+
if (typeof v2.releaseName != "string" || v2.releaseName.length === 0)
|
|
516
547
|
throw new Error(
|
|
517
|
-
`Invalid init value for state slot "${slot.id}" (workflow.state.release.ref): \`releaseName\` must be a non-empty string. Got ${JSON.stringify(
|
|
548
|
+
`Invalid init value for state slot "${slot.id}" (workflow.state.release.ref): \`releaseName\` must be a non-empty string. Got ${JSON.stringify(v2.releaseName)}.`
|
|
518
549
|
);
|
|
519
550
|
return;
|
|
520
551
|
}
|
|
@@ -532,18 +563,18 @@ function assertGdrShape(value, context) {
|
|
|
532
563
|
throw new Error(
|
|
533
564
|
`Invalid GDR for ${context}: expected { id: "<scheme>:...", type: "<schema>" }, got ${typeof value}.`
|
|
534
565
|
);
|
|
535
|
-
const
|
|
536
|
-
if (typeof
|
|
566
|
+
const v2 = value;
|
|
567
|
+
if (typeof v2.id != "string" || !isGdrUri(v2.id))
|
|
537
568
|
throw new Error(
|
|
538
|
-
`Invalid GDR for ${context}: \`id\` must be a GDR URI ("<scheme>:<...id-parts>" with scheme dataset|canvas|media-library|dashboard). Got ${JSON.stringify(
|
|
569
|
+
`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.`
|
|
539
570
|
);
|
|
540
|
-
if (typeof
|
|
571
|
+
if (typeof v2.type != "string" || v2.type.length === 0)
|
|
541
572
|
throw new Error(
|
|
542
|
-
`Invalid GDR for ${context}: \`type\` (schema name) must be a non-empty string. Got ${JSON.stringify(
|
|
573
|
+
`Invalid GDR for ${context}: \`type\` (schema name) must be a non-empty string. Got ${JSON.stringify(v2.type)}.`
|
|
543
574
|
);
|
|
544
575
|
}
|
|
545
576
|
function normalizeQueryResult(slotType, raw, workflowResource) {
|
|
546
|
-
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((
|
|
577
|
+
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;
|
|
547
578
|
}
|
|
548
579
|
function toGdrUri(docId, workflowResource) {
|
|
549
580
|
return isGdrUri(docId) ? docId : gdrFromResource(workflowResource, docId);
|
|
@@ -568,22 +599,24 @@ async function loadContext(client, instanceId, options) {
|
|
|
568
599
|
const instance = await client.getDocument(instanceId);
|
|
569
600
|
if (!instance)
|
|
570
601
|
throw new Error(`Workflow instance ${instanceId} not found`);
|
|
571
|
-
const definition = JSON.parse(instance.definitionSnapshot), clientForGdr = options?.clientForGdr ?? (() => client), snapshot = await hydrateSnapshot({ client, clientForGdr, instance, definition });
|
|
572
|
-
return { client, clientForGdr, instance, definition, snapshot };
|
|
602
|
+
const definition = JSON.parse(instance.definitionSnapshot), clientForGdr = options?.clientForGdr ?? (() => client), clock = options?.clock ?? wallClock, snapshot = await hydrateSnapshot({ client, clientForGdr, instance, definition });
|
|
603
|
+
return { client, clientForGdr, clock, now: clock(), instance, definition, snapshot };
|
|
573
604
|
}
|
|
574
605
|
function ctxEvaluateFilter(ctx, filter) {
|
|
575
606
|
return evaluateFilter({
|
|
576
607
|
filter,
|
|
577
608
|
definition: ctx.definition,
|
|
578
609
|
snapshot: ctx.snapshot,
|
|
579
|
-
params: buildParams(ctx.instance)
|
|
610
|
+
params: buildParams(ctx.instance, ctx.now)
|
|
580
611
|
});
|
|
581
612
|
}
|
|
582
613
|
async function buildEngineContext(args) {
|
|
583
|
-
const { client, clientForGdr, instance, definition } = args;
|
|
614
|
+
const { client, clientForGdr, instance, definition } = args, clock = args.clock ?? wallClock;
|
|
584
615
|
return {
|
|
585
616
|
client,
|
|
586
617
|
clientForGdr,
|
|
618
|
+
clock,
|
|
619
|
+
now: clock(),
|
|
587
620
|
instance,
|
|
588
621
|
definition,
|
|
589
622
|
snapshot: await hydrateSnapshot({ client, clientForGdr, instance, definition })
|
|
@@ -596,12 +629,13 @@ function findStage(definition, stageId) {
|
|
|
596
629
|
return stage;
|
|
597
630
|
}
|
|
598
631
|
async function resolveStageStateSlots(args) {
|
|
599
|
-
const { client, instance, stage, initialState } = args;
|
|
632
|
+
const { client, instance, stage, now, initialState } = args;
|
|
600
633
|
return resolveDeclaredState({
|
|
601
634
|
slotDefs: stage.state,
|
|
602
635
|
initialState: initialState ?? [],
|
|
603
636
|
ctx: {
|
|
604
637
|
client,
|
|
638
|
+
now,
|
|
605
639
|
selfId: instance._id,
|
|
606
640
|
tags: instance.tags ?? [],
|
|
607
641
|
stageId: stage.id,
|
|
@@ -616,12 +650,13 @@ async function resolveStageStateSlots(args) {
|
|
|
616
650
|
});
|
|
617
651
|
}
|
|
618
652
|
async function resolveTaskStateSlots(args) {
|
|
619
|
-
const { client, instance, stage, task } = args;
|
|
653
|
+
const { client, instance, stage, task, now } = args;
|
|
620
654
|
return resolveDeclaredState({
|
|
621
655
|
slotDefs: task.state,
|
|
622
656
|
initialState: [],
|
|
623
657
|
ctx: {
|
|
624
658
|
client,
|
|
659
|
+
now,
|
|
625
660
|
selfId: instance._id,
|
|
626
661
|
tags: instance.tags ?? [],
|
|
627
662
|
stageId: stage.id,
|
|
@@ -649,16 +684,24 @@ function slotValue$1(slot) {
|
|
|
649
684
|
if (slot != null && typeof slot == "object")
|
|
650
685
|
return slot.value;
|
|
651
686
|
}
|
|
652
|
-
function
|
|
687
|
+
function resolveStaticSource(src, ctx) {
|
|
653
688
|
switch (src.source) {
|
|
654
689
|
case "literal":
|
|
655
|
-
return src.value;
|
|
690
|
+
return { handled: !0, value: src.value };
|
|
656
691
|
case "param":
|
|
657
|
-
return ctx.params?.[src.paramId];
|
|
692
|
+
return { handled: !0, value: ctx.params?.[src.paramId] };
|
|
658
693
|
case "actor":
|
|
659
|
-
return ctx.actor;
|
|
694
|
+
return { handled: !0, value: ctx.actor };
|
|
660
695
|
case "now":
|
|
661
|
-
return
|
|
696
|
+
return { handled: !0, value: ctx.now };
|
|
697
|
+
default:
|
|
698
|
+
return { handled: !1 };
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
function resolveSource(src, ctx) {
|
|
702
|
+
const staticValue = resolveStaticSource(src, ctx);
|
|
703
|
+
if (staticValue.handled) return staticValue.value;
|
|
704
|
+
switch (src.source) {
|
|
662
705
|
case "self":
|
|
663
706
|
return ctx.instance._id;
|
|
664
707
|
case "stageId":
|
|
@@ -669,6 +712,11 @@ function resolveSource(src, ctx) {
|
|
|
669
712
|
return resolveEffectOutput(src, ctx);
|
|
670
713
|
case "object":
|
|
671
714
|
return resolveObject(src, ctx);
|
|
715
|
+
case "row":
|
|
716
|
+
case "parentState":
|
|
717
|
+
return;
|
|
718
|
+
default:
|
|
719
|
+
return;
|
|
672
720
|
}
|
|
673
721
|
}
|
|
674
722
|
function resolveStateRead(src, ctx) {
|
|
@@ -688,10 +736,11 @@ function resolveObject(src, ctx) {
|
|
|
688
736
|
return out;
|
|
689
737
|
}
|
|
690
738
|
async function resolveBindings(args) {
|
|
691
|
-
const { bindings, staticInput, instance, params, actor } = args, resolved = {};
|
|
739
|
+
const { bindings, staticInput, instance, now, params, actor } = args, resolved = {};
|
|
692
740
|
if (bindings) {
|
|
693
741
|
const ctx = {
|
|
694
742
|
instance,
|
|
743
|
+
now,
|
|
695
744
|
...params !== void 0 ? { params } : {},
|
|
696
745
|
...actor !== void 0 ? { actor } : {}
|
|
697
746
|
};
|
|
@@ -738,16 +787,29 @@ function buildInstanceBase(args) {
|
|
|
738
787
|
lastChangedAt: now
|
|
739
788
|
};
|
|
740
789
|
}
|
|
790
|
+
const GUARD_DOC_TYPE = "temp.system.guard";
|
|
791
|
+
class MutationGuardDeniedError extends Error {
|
|
792
|
+
denied;
|
|
793
|
+
documentId;
|
|
794
|
+
action;
|
|
795
|
+
constructor(args) {
|
|
796
|
+
const ids = args.denied.map((d) => d.guardId).join(", ");
|
|
797
|
+
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;
|
|
798
|
+
}
|
|
799
|
+
}
|
|
741
800
|
function lakeGuardId(args) {
|
|
742
|
-
return
|
|
801
|
+
return `${GUARD_DOC_TYPE}.${args.instanceDocId}.${args.stageId}.${args.index}`;
|
|
743
802
|
}
|
|
744
803
|
function compileGuard(args) {
|
|
745
804
|
return {
|
|
746
805
|
_id: args.id,
|
|
747
|
-
_type:
|
|
806
|
+
_type: GUARD_DOC_TYPE,
|
|
748
807
|
resourceType: args.resourceType,
|
|
749
808
|
resourceId: args.resourceId,
|
|
750
809
|
owner: args.owner,
|
|
810
|
+
sourceInstanceId: args.sourceInstanceId,
|
|
811
|
+
sourceDefinitionId: args.sourceDefinitionId,
|
|
812
|
+
sourceStageId: args.sourceStageId,
|
|
751
813
|
...args.name !== void 0 ? { name: args.name } : {},
|
|
752
814
|
...args.description !== void 0 ? { description: args.description } : {},
|
|
753
815
|
match: args.match,
|
|
@@ -803,9 +865,9 @@ function resolveIdRefTarget(src, ctx) {
|
|
|
803
865
|
if (typeof value == "string" && isGdrUri(value))
|
|
804
866
|
return { parsed: parseGdr(value) };
|
|
805
867
|
if (value && typeof value == "object") {
|
|
806
|
-
const
|
|
807
|
-
if (typeof
|
|
808
|
-
return typeof
|
|
868
|
+
const v2 = value;
|
|
869
|
+
if (typeof v2.id == "string" && isGdrUri(v2.id))
|
|
870
|
+
return typeof v2.type == "string" ? { parsed: parseGdr(v2.id), type: v2.type } : { parsed: parseGdr(v2.id) };
|
|
809
871
|
}
|
|
810
872
|
return null;
|
|
811
873
|
}
|
|
@@ -844,8 +906,8 @@ function resolveMetadata(metadata, ctx) {
|
|
|
844
906
|
return out;
|
|
845
907
|
}
|
|
846
908
|
const GUARD_OWNER = "robot:workflow-engine", GUARD_LIFTED_PREDICATE = "true";
|
|
847
|
-
function resolveGuard(guard, index, instance, stageId) {
|
|
848
|
-
const ctx = { instance, stageId }, targets = resolveIdRefTargets(guard.match.idRefs, ctx);
|
|
909
|
+
function resolveGuard(guard, index, instance, stageId, now) {
|
|
910
|
+
const ctx = { instance, stageId, now }, targets = resolveIdRefTargets(guard.match.idRefs, ctx);
|
|
849
911
|
if (targets === null) return null;
|
|
850
912
|
const resource = assertSingleResource(targets), types = resolveMatchTypes(targets, guard.match.types);
|
|
851
913
|
return { doc: compileGuard({
|
|
@@ -853,6 +915,9 @@ function resolveGuard(guard, index, instance, stageId) {
|
|
|
853
915
|
resourceType: resource.type,
|
|
854
916
|
resourceId: resource.id,
|
|
855
917
|
owner: GUARD_OWNER,
|
|
918
|
+
sourceInstanceId: instance._id,
|
|
919
|
+
sourceDefinitionId: instance.workflowId,
|
|
920
|
+
sourceStageId: stageId,
|
|
856
921
|
...guard.name !== void 0 ? { name: guard.name } : {},
|
|
857
922
|
...guard.description !== void 0 ? { description: guard.description } : {},
|
|
858
923
|
match: {
|
|
@@ -866,47 +931,108 @@ function resolveGuard(guard, index, instance, stageId) {
|
|
|
866
931
|
}), routeGdr: targets[0].parsed };
|
|
867
932
|
}
|
|
868
933
|
async function upsertGuard(client, doc) {
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
metadata: doc.metadata
|
|
876
|
-
};
|
|
877
|
-
doc.name !== void 0 && (set.name = doc.name), doc.description !== void 0 && (set.description = doc.description), existing ? await client.patch(doc._id).set(set).commit() : await client.create(doc);
|
|
934
|
+
if (!await client.getDocument(doc._id)) {
|
|
935
|
+
await client.create(doc);
|
|
936
|
+
return;
|
|
937
|
+
}
|
|
938
|
+
const { _id, _type, _rev, _createdAt, _updatedAt, ...body } = doc;
|
|
939
|
+
await client.patch(doc._id).set(body).commit();
|
|
878
940
|
}
|
|
879
941
|
function resolvedStageGuards(args) {
|
|
880
942
|
const stage = args.definition.stages.find((s) => s.id === args.stageId), out = [];
|
|
881
943
|
for (const [index, guard] of (stage?.guards ?? []).entries()) {
|
|
882
|
-
const resolved = resolveGuard(guard, index, args.instance, args.stageId);
|
|
944
|
+
const resolved = resolveGuard(guard, index, args.instance, args.stageId, args.now);
|
|
883
945
|
resolved !== null && out.push({ client: args.clientForGdr(resolved.routeGdr), doc: resolved.doc });
|
|
884
946
|
}
|
|
885
947
|
return out;
|
|
886
948
|
}
|
|
949
|
+
async function committedStageId(args) {
|
|
950
|
+
return (await args.client.getDocument(args.instance._id))?.currentStageId;
|
|
951
|
+
}
|
|
887
952
|
async function deployStageGuards(args) {
|
|
888
|
-
|
|
889
|
-
|
|
953
|
+
if (await committedStageId(args) === args.stageId)
|
|
954
|
+
for (const { client, doc } of resolvedStageGuards(args))
|
|
955
|
+
await upsertGuard(client, doc);
|
|
890
956
|
}
|
|
891
957
|
async function retractStageGuards(args) {
|
|
892
|
-
|
|
893
|
-
|
|
958
|
+
const live = await committedStageId(args);
|
|
959
|
+
if (!(live === void 0 || live === args.stageId))
|
|
960
|
+
for (const { client, doc } of resolvedStageGuards(args))
|
|
961
|
+
await client.getDocument(doc._id) && await client.patch(doc._id).set({ predicate: GUARD_LIFTED_PREDICATE }).commit();
|
|
894
962
|
}
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
963
|
+
class ActionParamsInvalidError extends Error {
|
|
964
|
+
action;
|
|
965
|
+
taskId;
|
|
966
|
+
issues;
|
|
967
|
+
constructor(args) {
|
|
968
|
+
const lines = args.issues.map((i) => ` - ${i.paramId}: ${i.reason}`).join(`
|
|
969
|
+
`);
|
|
970
|
+
super(`Action "${args.action}" on task "${args.taskId}" rejected: invalid params
|
|
971
|
+
${lines}`), this.name = "ActionParamsInvalidError", this.action = args.action, this.taskId = args.taskId, this.issues = args.issues;
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
class WorkflowStateDivergedError extends Error {
|
|
975
|
+
instanceId;
|
|
976
|
+
/** The guard-deploy failure that triggered the (attempted) rollback. */
|
|
977
|
+
guardError;
|
|
978
|
+
/** The rollback failure, when rollback was attempted and itself failed. */
|
|
979
|
+
rollbackError;
|
|
980
|
+
constructor(args) {
|
|
981
|
+
super(
|
|
982
|
+
`Workflow "${args.instanceId}" state committed but its guards could not be reconciled: ${args.reason}.`,
|
|
983
|
+
{ cause: args.guardError }
|
|
984
|
+
), this.name = "WorkflowStateDivergedError", this.instanceId = args.instanceId, this.guardError = args.guardError, args.rollbackError !== void 0 && (this.rollbackError = args.rollbackError);
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
const CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS = 3;
|
|
988
|
+
class ConcurrentFireActionError extends Error {
|
|
989
|
+
instanceId;
|
|
990
|
+
taskId;
|
|
991
|
+
action;
|
|
992
|
+
attempts;
|
|
993
|
+
constructor(args) {
|
|
994
|
+
super(
|
|
995
|
+
`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.`
|
|
996
|
+
), this.name = "ConcurrentFireActionError", this.instanceId = args.instanceId, this.taskId = args.taskId, this.action = args.action, this.attempts = args.attempts;
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
function isRevisionConflict(error) {
|
|
1000
|
+
if (typeof error != "object" || error === null) return !1;
|
|
1001
|
+
const { statusCode, message } = error;
|
|
1002
|
+
return statusCode === 409 ? !0 : typeof message == "string" && message.includes("ifRevisionId check failed");
|
|
1003
|
+
}
|
|
1004
|
+
class CascadeLimitError extends Error {
|
|
1005
|
+
instanceId;
|
|
1006
|
+
limit;
|
|
1007
|
+
constructor(args) {
|
|
1008
|
+
super(
|
|
1009
|
+
`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.`
|
|
1010
|
+
), this.name = "CascadeLimitError", this.instanceId = args.instanceId, this.limit = args.limit;
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
async function deployOrRollback(args) {
|
|
1014
|
+
try {
|
|
1015
|
+
await args.deploy();
|
|
1016
|
+
} catch (guardError) {
|
|
1017
|
+
if (!args.reversible)
|
|
1018
|
+
throw new WorkflowStateDivergedError({
|
|
1019
|
+
instanceId: args.instanceId,
|
|
1020
|
+
guardError,
|
|
1021
|
+
reason: "the move created child instances a parent-only rollback cannot undo"
|
|
1022
|
+
});
|
|
1023
|
+
try {
|
|
1024
|
+
let patch = args.client.patch(args.instanceId).set(args.restore);
|
|
1025
|
+
args.unset && args.unset.length > 0 && (patch = patch.unset(args.unset)), args.committedRev !== void 0 && (patch = patch.ifRevisionId(args.committedRev)), await patch.commit();
|
|
1026
|
+
} catch (rollbackError) {
|
|
1027
|
+
throw new WorkflowStateDivergedError({
|
|
1028
|
+
instanceId: args.instanceId,
|
|
1029
|
+
guardError,
|
|
1030
|
+
rollbackError,
|
|
1031
|
+
reason: "rollback of the committed move failed"
|
|
1032
|
+
});
|
|
1033
|
+
}
|
|
1034
|
+
throw guardError;
|
|
1035
|
+
}
|
|
910
1036
|
}
|
|
911
1037
|
async function discoverItems(args) {
|
|
912
1038
|
const { client, groq, params, perspective } = args, fetchOptions = perspective !== void 0 ? { perspective } : void 0;
|
|
@@ -923,31 +1049,29 @@ function clientForDiscovery(defaultClient, clientForGdr, subjectGdrUri) {
|
|
|
923
1049
|
const MAX_SPAWN_DEPTH = 6;
|
|
924
1050
|
async function spawnChildren(ctx, mutation, task, spawn, actor) {
|
|
925
1051
|
if (ctx.instance.ancestors.length + 1 > MAX_SPAWN_DEPTH) {
|
|
926
|
-
const chain = [
|
|
927
|
-
...ctx.instance.ancestors.map((a) => a.id),
|
|
928
|
-
gdrFromResource(ctx.instance.workflowResource, ctx.instance._id)
|
|
929
|
-
].join(" \u2192 ");
|
|
1052
|
+
const chain = [...ctx.instance.ancestors.map((a) => a.id), selfGdr(ctx.instance)].join(" \u2192 ");
|
|
930
1053
|
throw new Error(
|
|
931
1054
|
`Spawn depth limit (${MAX_SPAWN_DEPTH}) exceeded on task "${task.id}" of ${ctx.instance._id}. Ancestor chain: ${chain}`
|
|
932
1055
|
);
|
|
933
1056
|
}
|
|
934
|
-
const rows = await resolveSpawnRows(ctx, spawn), refs = [];
|
|
1057
|
+
const now = ctx.now, rows = await resolveSpawnRows(ctx, spawn), refs = [];
|
|
935
1058
|
for (const row of rows) {
|
|
936
|
-
const projected = projectSpawnRow(ctx.instance, spawn, row), { ref, body } = await prepareChildInstance({
|
|
1059
|
+
const projected = projectSpawnRow(ctx.instance, spawn, row, now), { ref, body } = await prepareChildInstance({
|
|
937
1060
|
client: ctx.client,
|
|
938
1061
|
parent: ctx.instance,
|
|
939
1062
|
task,
|
|
940
1063
|
spawn,
|
|
941
1064
|
initialState: projected.initialState,
|
|
942
1065
|
effectsContext: projected.effectsContext,
|
|
943
|
-
actor
|
|
1066
|
+
actor,
|
|
1067
|
+
now
|
|
944
1068
|
});
|
|
945
1069
|
refs.push(ref), mutation.pendingCreates.push(body);
|
|
946
1070
|
}
|
|
947
1071
|
return refs;
|
|
948
1072
|
}
|
|
949
1073
|
async function resolveSpawnRows(ctx, spawn) {
|
|
950
|
-
const params = buildParams(ctx.instance), groq = spawn.forEach.groq;
|
|
1074
|
+
const params = buildParams(ctx.instance, ctx.now), groq = spawn.forEach.groq;
|
|
951
1075
|
let value;
|
|
952
1076
|
if (groq.includes("*")) {
|
|
953
1077
|
const routingUri = firstDocRefGdrUri(ctx.instance.state), client = clientForDiscovery(ctx.client, ctx.clientForGdr, routingUri);
|
|
@@ -969,7 +1093,7 @@ function firstDocRefGdrUri(slots) {
|
|
|
969
1093
|
if (s._type === "workflow.state.doc.ref" && s.value !== null) return s.value.id;
|
|
970
1094
|
}
|
|
971
1095
|
}
|
|
972
|
-
function resolveSpawnSource(source, parent, row) {
|
|
1096
|
+
function resolveSpawnSource(source, parent, row, now) {
|
|
973
1097
|
switch (source.source) {
|
|
974
1098
|
case "literal":
|
|
975
1099
|
return source.value;
|
|
@@ -980,15 +1104,15 @@ function resolveSpawnSource(source, parent, row) {
|
|
|
980
1104
|
return slot === void 0 ? null : source.path !== void 0 ? getPath(slot, source.path) : slot;
|
|
981
1105
|
}
|
|
982
1106
|
case "self":
|
|
983
|
-
return
|
|
1107
|
+
return selfGdr(parent);
|
|
984
1108
|
case "stageId":
|
|
985
1109
|
return parent.currentStageId;
|
|
986
1110
|
case "now":
|
|
987
|
-
return
|
|
1111
|
+
return now;
|
|
988
1112
|
case "object": {
|
|
989
1113
|
const out = {};
|
|
990
|
-
for (const [k,
|
|
991
|
-
out[k] = resolveSpawnSource(
|
|
1114
|
+
for (const [k, v2] of Object.entries(source.fields))
|
|
1115
|
+
out[k] = resolveSpawnSource(v2, parent, row, now);
|
|
992
1116
|
return out;
|
|
993
1117
|
}
|
|
994
1118
|
case "actor":
|
|
@@ -998,10 +1122,10 @@ function resolveSpawnSource(source, parent, row) {
|
|
|
998
1122
|
return null;
|
|
999
1123
|
}
|
|
1000
1124
|
}
|
|
1001
|
-
function projectSpawnRow(parent, spawn, row) {
|
|
1125
|
+
function projectSpawnRow(parent, spawn, row, now) {
|
|
1002
1126
|
const initialState = [];
|
|
1003
1127
|
for (const entry of spawn.forEach.as.initialState ?? []) {
|
|
1004
|
-
const raw = resolveSpawnSource(entry.value, parent, row), value = canonicaliseSpawnValue(entry.type, raw, parent.workflowResource);
|
|
1128
|
+
const raw = resolveSpawnSource(entry.value, parent, row, now), value = canonicaliseSpawnValue(entry.type, raw, parent.workflowResource);
|
|
1005
1129
|
initialState.push({
|
|
1006
1130
|
type: entry.type,
|
|
1007
1131
|
id: entry.id,
|
|
@@ -1010,13 +1134,13 @@ function projectSpawnRow(parent, spawn, row) {
|
|
|
1010
1134
|
}
|
|
1011
1135
|
const effectsContext = {};
|
|
1012
1136
|
for (const [k, src] of Object.entries(spawn.forEach.as.effectsContext ?? {})) {
|
|
1013
|
-
const
|
|
1014
|
-
|
|
1137
|
+
const v2 = resolveSpawnSource(src, parent, row, now);
|
|
1138
|
+
v2 != null && (typeof v2 == "string" || typeof v2 == "number" || typeof v2 == "boolean" || typeof v2 == "object" && "id" in v2 && "type" in v2) && (effectsContext[k] = v2);
|
|
1015
1139
|
}
|
|
1016
1140
|
return { initialState, effectsContext };
|
|
1017
1141
|
}
|
|
1018
1142
|
function canonicaliseSpawnValue(slotType, value, workflowResource) {
|
|
1019
|
-
return slotType === "workflow.state.doc.ref" ? coerceSpawnGdr(value, workflowResource) : slotType === "workflow.state.doc.refs" && Array.isArray(value) ? value.map((
|
|
1143
|
+
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;
|
|
1020
1144
|
}
|
|
1021
1145
|
function coerceSpawnGdr(raw, workflowResource) {
|
|
1022
1146
|
const toUri = (id) => isGdrUri(id) ? id : gdrFromResource(workflowResource, id);
|
|
@@ -1039,7 +1163,7 @@ async function resolveLogicalDefinition(client, ref, tags) {
|
|
|
1039
1163
|
return await client.fetch(groq, params) ?? null;
|
|
1040
1164
|
}
|
|
1041
1165
|
async function prepareChildInstance(args) {
|
|
1042
|
-
const { client, parent, task, spawn, initialState, effectsContext, actor } = args, definition = await resolveLogicalDefinition(client, spawn.definitionRef, parent.tags ?? []);
|
|
1166
|
+
const { client, parent, task, spawn, initialState, effectsContext, actor, now } = args, definition = await resolveLogicalDefinition(client, spawn.definitionRef, parent.tags ?? []);
|
|
1043
1167
|
if (definition === null) {
|
|
1044
1168
|
const versionLabel = spawn.definitionRef.version === void 0 || spawn.definitionRef.version === "latest" ? "latest" : `v${spawn.definitionRef.version}`;
|
|
1045
1169
|
throw new Error(
|
|
@@ -1049,7 +1173,7 @@ async function prepareChildInstance(args) {
|
|
|
1049
1173
|
const childTags = parent.tags ?? [], childPrefix = childTags[0] ?? "wf", workflowResource = parent.workflowResource, childDocId = `${childPrefix}.wf-instance.${randomKey()}`, childRef = { id: gdrFromResource(workflowResource, childDocId), type: "workflow.instance" }, ancestors = [
|
|
1050
1174
|
...parent.ancestors,
|
|
1051
1175
|
{
|
|
1052
|
-
id:
|
|
1176
|
+
id: selfGdr(parent),
|
|
1053
1177
|
type: "workflow.instance"
|
|
1054
1178
|
}
|
|
1055
1179
|
], inheritedPerspective = parent.perspective, childState = await resolveDeclaredState({
|
|
@@ -1057,6 +1181,7 @@ async function prepareChildInstance(args) {
|
|
|
1057
1181
|
initialState,
|
|
1058
1182
|
ctx: {
|
|
1059
1183
|
client,
|
|
1184
|
+
now,
|
|
1060
1185
|
selfId: childDocId,
|
|
1061
1186
|
tags: childTags,
|
|
1062
1187
|
workflowResource,
|
|
@@ -1076,7 +1201,7 @@ async function prepareChildInstance(args) {
|
|
|
1076
1201
|
}
|
|
1077
1202
|
), base = buildInstanceBase({
|
|
1078
1203
|
id: childDocId,
|
|
1079
|
-
now
|
|
1204
|
+
now,
|
|
1080
1205
|
tags: childTags,
|
|
1081
1206
|
workflowResource,
|
|
1082
1207
|
workflowId: definition.workflowId,
|
|
@@ -1112,7 +1237,9 @@ async function checkSpawnCompletion(client, entry, spawn) {
|
|
|
1112
1237
|
}
|
|
1113
1238
|
}
|
|
1114
1239
|
async function invokeTask(args) {
|
|
1115
|
-
const { client, instanceId, taskId, options } = args, ctx = await loadContext(client, instanceId
|
|
1240
|
+
const { client, instanceId, taskId, options } = args, ctx = await loadContext(client, instanceId, {
|
|
1241
|
+
...options?.clock ? { clock: options.clock } : {}
|
|
1242
|
+
});
|
|
1116
1243
|
return commitInvoke(ctx, taskId, options?.actor);
|
|
1117
1244
|
}
|
|
1118
1245
|
async function commitInvoke(ctx, taskId, actor) {
|
|
@@ -1124,41 +1251,41 @@ async function commitInvoke(ctx, taskId, actor) {
|
|
|
1124
1251
|
const mutation = startMutation(ctx.instance), mutEntry = requireMutationTaskEntry(mutation, taskId);
|
|
1125
1252
|
return await activateTask(ctx, mutation, task, mutEntry, actor), await persist(ctx, mutation), { invoked: !0, taskId };
|
|
1126
1253
|
}
|
|
1127
|
-
async function autoResolveOnActivate(ctx, mutation, task, entry) {
|
|
1254
|
+
async function autoResolveOnActivate(ctx, mutation, task, entry, now) {
|
|
1128
1255
|
const checks = [
|
|
1129
1256
|
{ filter: task.failWhen, outcome: "failed", systemId: "engine.failWhen" },
|
|
1130
1257
|
{ filter: task.completeWhen, outcome: "done", systemId: "engine.completeWhen" }
|
|
1131
1258
|
];
|
|
1132
|
-
for (const { filter, outcome, systemId } of checks)
|
|
1133
|
-
if (filter
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
), !0;
|
|
1145
|
-
}
|
|
1259
|
+
for (const { filter, outcome, systemId } of checks)
|
|
1260
|
+
if (filter !== void 0 && await ctxEvaluateFilter(ctx, filter))
|
|
1261
|
+
return entry.status = outcome, entry.completedAt = now, entry.completedBy = systemId, mutation.history.push(
|
|
1262
|
+
taskStatusChangedEntry({
|
|
1263
|
+
stageId: mutation.currentStageId,
|
|
1264
|
+
taskId: task.id,
|
|
1265
|
+
from: "active",
|
|
1266
|
+
to: outcome,
|
|
1267
|
+
at: now,
|
|
1268
|
+
actor: { kind: "system", id: systemId }
|
|
1269
|
+
})
|
|
1270
|
+
), !0;
|
|
1146
1271
|
return !1;
|
|
1147
1272
|
}
|
|
1148
1273
|
async function activateTask(ctx, mutation, task, entry, actor) {
|
|
1149
|
-
|
|
1274
|
+
const now = ctx.now;
|
|
1275
|
+
if (entry.status = "active", entry.startedAt = now, task.state !== void 0 && task.state.length > 0) {
|
|
1150
1276
|
const stage = findStage(ctx.definition, mutation.currentStageId);
|
|
1151
1277
|
entry.state = await resolveTaskStateSlots({
|
|
1152
1278
|
client: ctx.client,
|
|
1153
1279
|
instance: ctx.instance,
|
|
1154
1280
|
stage,
|
|
1155
|
-
task
|
|
1281
|
+
task,
|
|
1282
|
+
now
|
|
1156
1283
|
});
|
|
1157
1284
|
}
|
|
1158
|
-
if (!await autoResolveOnActivate(ctx, mutation, task, entry) && (mutation.history.push({
|
|
1285
|
+
if (!await autoResolveOnActivate(ctx, mutation, task, entry, now) && (mutation.history.push({
|
|
1159
1286
|
_key: randomKey(),
|
|
1160
1287
|
_type: "workflow.history.taskActivated",
|
|
1161
|
-
at:
|
|
1288
|
+
at: now,
|
|
1162
1289
|
stageId: mutation.currentStageId,
|
|
1163
1290
|
taskId: task.id,
|
|
1164
1291
|
...actor !== void 0 ? { actor } : {}
|
|
@@ -1178,18 +1305,19 @@ async function activateTask(ctx, mutation, task, entry, actor) {
|
|
|
1178
1305
|
mutation.history.push({
|
|
1179
1306
|
_key: randomKey(),
|
|
1180
1307
|
_type: "workflow.history.spawned",
|
|
1181
|
-
at:
|
|
1308
|
+
at: now,
|
|
1182
1309
|
taskId: task.id,
|
|
1183
1310
|
instanceRef: ref
|
|
1184
1311
|
});
|
|
1185
1312
|
if (await checkSpawnCompletion(ctx.client, entry, task.spawns)) {
|
|
1186
1313
|
const previousStatus = entry.status;
|
|
1187
|
-
entry.status = "done", entry.completedAt =
|
|
1314
|
+
entry.status = "done", entry.completedAt = now, mutation.history.push(
|
|
1188
1315
|
taskStatusChangedEntry({
|
|
1189
1316
|
stageId: mutation.currentStageId,
|
|
1190
1317
|
taskId: task.id,
|
|
1191
1318
|
from: previousStatus,
|
|
1192
1319
|
to: "done",
|
|
1320
|
+
at: now,
|
|
1193
1321
|
...actor !== void 0 ? { actor } : {}
|
|
1194
1322
|
})
|
|
1195
1323
|
);
|
|
@@ -1206,7 +1334,7 @@ async function buildStageTasks(ctx, stage) {
|
|
|
1206
1334
|
status: inScope ? "pending" : "skipped",
|
|
1207
1335
|
...task.filter !== void 0 ? {
|
|
1208
1336
|
filterEvaluation: {
|
|
1209
|
-
at:
|
|
1337
|
+
at: ctx.now,
|
|
1210
1338
|
truthy: inScope
|
|
1211
1339
|
}
|
|
1212
1340
|
} : {}
|
|
@@ -1218,7 +1346,9 @@ function activatesOnStageEnter(task) {
|
|
|
1218
1346
|
return task.invokeOn === "stageEnter" || task.spawns !== void 0 || task.completeWhen !== void 0 || task.failWhen !== void 0;
|
|
1219
1347
|
}
|
|
1220
1348
|
async function setStage(args) {
|
|
1221
|
-
const { client, instanceId, targetStageId, reason, options } = args, ctx = await loadContext(client, instanceId
|
|
1349
|
+
const { client, instanceId, targetStageId, reason, options } = args, ctx = await loadContext(client, instanceId, {
|
|
1350
|
+
...options?.clock ? { clock: options.clock } : {}
|
|
1351
|
+
});
|
|
1222
1352
|
return commitSetStage(ctx, targetStageId, reason, options?.actor);
|
|
1223
1353
|
}
|
|
1224
1354
|
async function enterStage(ctx, mutation, nextStage, actor, at) {
|
|
@@ -1230,7 +1360,8 @@ async function enterStage(ctx, mutation, nextStage, actor, at) {
|
|
|
1230
1360
|
state: await resolveStageStateSlots({
|
|
1231
1361
|
client: ctx.client,
|
|
1232
1362
|
instance: ctx.instance,
|
|
1233
|
-
stage: nextStage
|
|
1363
|
+
stage: nextStage,
|
|
1364
|
+
now: at
|
|
1234
1365
|
}),
|
|
1235
1366
|
tasks: await buildStageTasks(ctx, nextStage)
|
|
1236
1367
|
};
|
|
@@ -1249,7 +1380,7 @@ async function enterStage(ctx, mutation, nextStage, actor, at) {
|
|
|
1249
1380
|
nextStage.kind === "terminal" && (mutation.completedAt = at);
|
|
1250
1381
|
}
|
|
1251
1382
|
async function beginStageMove(ctx, currentStage, actor) {
|
|
1252
|
-
const mutation = startMutation(ctx.instance), at =
|
|
1383
|
+
const mutation = startMutation(ctx.instance), at = ctx.now;
|
|
1253
1384
|
return await queueEffects(
|
|
1254
1385
|
ctx,
|
|
1255
1386
|
mutation,
|
|
@@ -1283,13 +1414,46 @@ async function commitSetStage(ctx, targetStageId, reason, actor) {
|
|
|
1283
1414
|
};
|
|
1284
1415
|
}
|
|
1285
1416
|
async function persistStageMove(ctx, mutation, exitedStageId, enteredStageId) {
|
|
1286
|
-
await
|
|
1417
|
+
await persistThenDeploy(
|
|
1418
|
+
ctx,
|
|
1419
|
+
mutation,
|
|
1420
|
+
() => deployStageGuards({
|
|
1421
|
+
client: ctx.client,
|
|
1422
|
+
clientForGdr: ctx.clientForGdr,
|
|
1423
|
+
instance: materializeInstance(ctx.instance, mutation),
|
|
1424
|
+
definition: ctx.definition,
|
|
1425
|
+
stageId: enteredStageId,
|
|
1426
|
+
now: ctx.now
|
|
1427
|
+
})
|
|
1428
|
+
), await retractStageGuards({
|
|
1287
1429
|
client: ctx.client,
|
|
1288
1430
|
clientForGdr: ctx.clientForGdr,
|
|
1289
1431
|
instance: ctx.instance,
|
|
1290
1432
|
definition: ctx.definition,
|
|
1291
|
-
exitedStageId,
|
|
1292
|
-
|
|
1433
|
+
stageId: exitedStageId,
|
|
1434
|
+
now: ctx.now
|
|
1435
|
+
});
|
|
1436
|
+
}
|
|
1437
|
+
async function persistThenDeploy(ctx, mutation, deploy) {
|
|
1438
|
+
const spawned = mutation.pendingCreates.length > 0, committed = await persist(ctx, mutation);
|
|
1439
|
+
await deployOrRollback({
|
|
1440
|
+
client: ctx.client,
|
|
1441
|
+
instanceId: ctx.instance._id,
|
|
1442
|
+
committedRev: committed._rev,
|
|
1443
|
+
restore: instanceStateFields(ctx.instance),
|
|
1444
|
+
unset: ctx.instance.completedAt === void 0 ? ["completedAt"] : [],
|
|
1445
|
+
reversible: !spawned,
|
|
1446
|
+
deploy
|
|
1447
|
+
});
|
|
1448
|
+
}
|
|
1449
|
+
async function refreshStageGuards(ctx, mutation, stageId) {
|
|
1450
|
+
await deployStageGuards({
|
|
1451
|
+
client: ctx.client,
|
|
1452
|
+
clientForGdr: ctx.clientForGdr,
|
|
1453
|
+
instance: materializeInstance(ctx.instance, mutation),
|
|
1454
|
+
definition: ctx.definition,
|
|
1455
|
+
stageId,
|
|
1456
|
+
now: ctx.now
|
|
1293
1457
|
});
|
|
1294
1458
|
}
|
|
1295
1459
|
async function commitTransition(ctx, action, actor) {
|
|
@@ -1331,15 +1495,18 @@ async function pickTransition(ctx, stage, action) {
|
|
|
1331
1495
|
if (await ctxEvaluateFilter(ctx, candidate.filter)) return candidate;
|
|
1332
1496
|
}
|
|
1333
1497
|
async function evaluateAutoTransitions(args) {
|
|
1334
|
-
const { client, instanceId, options, clientForGdr } = args, ctx = await loadContext(client, instanceId,
|
|
1498
|
+
const { client, instanceId, options, clientForGdr, clock } = args, ctx = await loadContext(client, instanceId, {
|
|
1499
|
+
...clientForGdr ? { clientForGdr } : {},
|
|
1500
|
+
...clock ? { clock } : {}
|
|
1501
|
+
});
|
|
1335
1502
|
return commitTransition(ctx, void 0, options?.actor);
|
|
1336
1503
|
}
|
|
1337
|
-
async function primeInitialStage(client, instanceId, actor, clientForGdr) {
|
|
1504
|
+
async function primeInitialStage(client, instanceId, actor, clientForGdr, clock) {
|
|
1338
1505
|
const instance = await client.getDocument(instanceId);
|
|
1339
1506
|
if (!instance || instance.stages.length > 0 || instance.pendingEffects.length > 0) return;
|
|
1340
1507
|
const definition = JSON.parse(instance.definitionSnapshot), stage = definition.stages.find((s) => s.id === instance.currentStageId);
|
|
1341
1508
|
if (stage === void 0) return;
|
|
1342
|
-
const snapshot = await hydrateSnapshot({
|
|
1509
|
+
const now = (clock ?? wallClock)(), snapshot = await hydrateSnapshot({
|
|
1343
1510
|
client,
|
|
1344
1511
|
clientForGdr: clientForGdr ?? (() => client),
|
|
1345
1512
|
instance,
|
|
@@ -1350,7 +1517,7 @@ async function primeInitialStage(client, instanceId, actor, clientForGdr) {
|
|
|
1350
1517
|
filter: task.filter,
|
|
1351
1518
|
definition,
|
|
1352
1519
|
snapshot,
|
|
1353
|
-
params: buildParams(instance)
|
|
1520
|
+
params: buildParams(instance, now)
|
|
1354
1521
|
});
|
|
1355
1522
|
tasks.push({
|
|
1356
1523
|
_key: randomKey(),
|
|
@@ -1361,8 +1528,8 @@ async function primeInitialStage(client, instanceId, actor, clientForGdr) {
|
|
|
1361
1528
|
const initialStageEntry = {
|
|
1362
1529
|
_key: randomKey(),
|
|
1363
1530
|
id: stage.id,
|
|
1364
|
-
enteredAt:
|
|
1365
|
-
state: await resolveStageStateSlots({ client, instance, stage }),
|
|
1531
|
+
enteredAt: now,
|
|
1532
|
+
state: await resolveStageStateSlots({ client, instance, stage, now }),
|
|
1366
1533
|
tasks
|
|
1367
1534
|
}, pendingEffects = [], newHistory = [...instance.history];
|
|
1368
1535
|
for (const effect of stage.onEnter ?? []) {
|
|
@@ -1370,29 +1537,44 @@ async function primeInitialStage(client, instanceId, actor, clientForGdr) {
|
|
|
1370
1537
|
bindings: effect.bindings,
|
|
1371
1538
|
staticInput: effect.input,
|
|
1372
1539
|
instance,
|
|
1540
|
+
now,
|
|
1373
1541
|
...actor !== void 0 ? { actor } : {}
|
|
1374
1542
|
}), { pending, history } = buildQueuedEffect(
|
|
1375
1543
|
effect,
|
|
1376
1544
|
{ kind: "stageEnter", id: stage.id },
|
|
1377
1545
|
params,
|
|
1378
|
-
actor
|
|
1546
|
+
actor,
|
|
1547
|
+
now
|
|
1379
1548
|
);
|
|
1380
1549
|
pendingEffects.push(pending), newHistory.push(history);
|
|
1381
1550
|
}
|
|
1382
|
-
await client.patch(instance._id).set({
|
|
1551
|
+
const committed = await client.patch(instance._id).set({
|
|
1383
1552
|
stages: [initialStageEntry],
|
|
1384
1553
|
pendingEffects,
|
|
1385
1554
|
history: newHistory,
|
|
1386
|
-
lastChangedAt:
|
|
1387
|
-
}).ifRevisionId(instance._rev).commit()
|
|
1555
|
+
lastChangedAt: now
|
|
1556
|
+
}).ifRevisionId(instance._rev).commit();
|
|
1557
|
+
await deployOrRollback({
|
|
1388
1558
|
client,
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1559
|
+
instanceId: instance._id,
|
|
1560
|
+
committedRev: committed._rev,
|
|
1561
|
+
restore: {
|
|
1562
|
+
stages: instance.stages,
|
|
1563
|
+
pendingEffects: instance.pendingEffects,
|
|
1564
|
+
history: instance.history
|
|
1565
|
+
},
|
|
1566
|
+
reversible: !0,
|
|
1567
|
+
deploy: () => deployStageGuards({
|
|
1568
|
+
client,
|
|
1569
|
+
clientForGdr: clientForGdr ?? (() => client),
|
|
1570
|
+
instance,
|
|
1571
|
+
definition,
|
|
1572
|
+
stageId: stage.id,
|
|
1573
|
+
now
|
|
1574
|
+
})
|
|
1575
|
+
}), await autoActivatePrimedTasks(client, instanceId, definition, stage, actor, clientForGdr, clock);
|
|
1394
1576
|
}
|
|
1395
|
-
async function autoActivatePrimedTasks(client, instanceId, definition, stage, actor, clientForGdr) {
|
|
1577
|
+
async function autoActivatePrimedTasks(client, instanceId, definition, stage, actor, clientForGdr, clock) {
|
|
1396
1578
|
const resolvedClientForGdr = clientForGdr ?? (() => client);
|
|
1397
1579
|
for (const task of stage.tasks ?? []) {
|
|
1398
1580
|
if (!activatesOnStageEnter(task)) continue;
|
|
@@ -1402,7 +1584,8 @@ async function autoActivatePrimedTasks(client, instanceId, definition, stage, ac
|
|
|
1402
1584
|
client,
|
|
1403
1585
|
clientForGdr: resolvedClientForGdr,
|
|
1404
1586
|
instance: updated,
|
|
1405
|
-
definition
|
|
1587
|
+
definition,
|
|
1588
|
+
...clock ? { clock } : {}
|
|
1406
1589
|
}), mutation = startMutation(updated), mutEntry = currentTasks(mutation).find((t) => t.id === task.id);
|
|
1407
1590
|
mutEntry && mutEntry.status === "pending" && (await activateTask(updatedCtx, mutation, task, mutEntry, actor), await persist(updatedCtx, mutation));
|
|
1408
1591
|
}
|
|
@@ -1420,8 +1603,11 @@ async function gatherAutoResolveCandidates(ctx, tasks) {
|
|
|
1420
1603
|
}
|
|
1421
1604
|
return candidates;
|
|
1422
1605
|
}
|
|
1423
|
-
async function resolveCompleteWhen(client, instanceId, clientForGdr) {
|
|
1424
|
-
const ctx = await loadContext(client, instanceId,
|
|
1606
|
+
async function resolveCompleteWhen(client, instanceId, clientForGdr, clock) {
|
|
1607
|
+
const ctx = await loadContext(client, instanceId, {
|
|
1608
|
+
...clientForGdr ? { clientForGdr } : {},
|
|
1609
|
+
...clock ? { clock } : {}
|
|
1610
|
+
}), stage = findStage(ctx.definition, ctx.instance.currentStageId), tasksWithAutoPredicate = (stage.tasks ?? []).filter(
|
|
1425
1611
|
(t) => t.completeWhen !== void 0 || t.failWhen !== void 0
|
|
1426
1612
|
);
|
|
1427
1613
|
if (tasksWithAutoPredicate.length === 0) return 0;
|
|
@@ -1435,65 +1621,71 @@ async function resolveCompleteWhen(client, instanceId, clientForGdr) {
|
|
|
1435
1621
|
const previousStatus = mutEntry.status, actor = {
|
|
1436
1622
|
kind: "system",
|
|
1437
1623
|
id: outcome === "failed" ? "engine.failWhen" : "engine.completeWhen"
|
|
1438
|
-
};
|
|
1439
|
-
mutEntry.status = outcome, mutEntry.completedAt =
|
|
1624
|
+
}, now = ctx.now;
|
|
1625
|
+
mutEntry.status = outcome, mutEntry.completedAt = now, mutEntry.completedBy = actor.id, mutation.history.push(
|
|
1440
1626
|
taskStatusChangedEntry({
|
|
1441
1627
|
stageId: stage.id,
|
|
1442
1628
|
taskId: task.id,
|
|
1443
1629
|
from: previousStatus,
|
|
1444
1630
|
to: outcome,
|
|
1631
|
+
at: now,
|
|
1445
1632
|
actor
|
|
1446
1633
|
})
|
|
1447
1634
|
), count++;
|
|
1448
1635
|
}
|
|
1449
1636
|
return count > 0 && await persist(ctx, mutation), count;
|
|
1450
1637
|
}
|
|
1451
|
-
async function cascadeAutoTransitions(client, instanceId, actor, clientForGdr) {
|
|
1638
|
+
async function cascadeAutoTransitions(client, instanceId, actor, clientForGdr, clock) {
|
|
1452
1639
|
let count = 0;
|
|
1453
1640
|
for (; ; ) {
|
|
1454
|
-
if (await resolveCompleteWhen(client, instanceId, clientForGdr), !(await evaluateAutoTransitions({
|
|
1641
|
+
if (await resolveCompleteWhen(client, instanceId, clientForGdr, clock), !(await evaluateAutoTransitions({
|
|
1455
1642
|
client,
|
|
1456
1643
|
instanceId,
|
|
1457
1644
|
...actor !== void 0 ? { options: { actor } } : {},
|
|
1458
|
-
...clientForGdr ? { clientForGdr } : {}
|
|
1645
|
+
...clientForGdr ? { clientForGdr } : {},
|
|
1646
|
+
...clock ? { clock } : {}
|
|
1459
1647
|
})).fired) return count;
|
|
1460
|
-
if (count++, count
|
|
1461
|
-
throw new
|
|
1462
|
-
`Cascade did not stabilise after ${CASCADE_LIMIT} auto-transitions on ${instanceId} \u2014 possible infinite loop`
|
|
1463
|
-
);
|
|
1648
|
+
if (count++, count >= CASCADE_LIMIT)
|
|
1649
|
+
throw new CascadeLimitError({ instanceId, limit: CASCADE_LIMIT });
|
|
1464
1650
|
}
|
|
1465
1651
|
}
|
|
1466
|
-
async function
|
|
1467
|
-
const
|
|
1468
|
-
if (!instance || instance.ancestors.length === 0) return;
|
|
1469
|
-
const parentRef = instance.ancestors[instance.ancestors.length - 1];
|
|
1652
|
+
async function findSatisfiedParentSpawn(client, child) {
|
|
1653
|
+
const parentRef = child.ancestors.at(-1);
|
|
1470
1654
|
if (parentRef === void 0) return;
|
|
1471
|
-
const
|
|
1472
|
-
if (!parent || parent._type !== "workflow.instance" || typeof parent.definitionSnapshot != "string")
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1655
|
+
const parent = await client.getDocument(gdrToBareId(parentRef.id));
|
|
1656
|
+
if (!parent || parent._type !== "workflow.instance" || typeof parent.definitionSnapshot != "string") return;
|
|
1657
|
+
const definition = JSON.parse(parent.definitionSnapshot), stage = definition.stages.find((s) => s.id === parent.currentStageId);
|
|
1658
|
+
if (stage === void 0) return;
|
|
1659
|
+
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);
|
|
1660
|
+
if (task?.spawns === void 0) return;
|
|
1661
|
+
const entry = parentCurrentTasks.find((e) => e.id === task.id);
|
|
1662
|
+
if (!(entry === void 0 || entry.status === "done") && await checkSpawnCompletion(client, entry, task.spawns))
|
|
1663
|
+
return { parent, definition, stage, task };
|
|
1664
|
+
}
|
|
1665
|
+
async function propagateToAncestors(client, instanceId, actor, clientForGdr, clock) {
|
|
1666
|
+
const instance = await client.getDocument(instanceId);
|
|
1667
|
+
if (!instance) return;
|
|
1668
|
+
const found = await findSatisfiedParentSpawn(client, instance);
|
|
1669
|
+
if (found === void 0) return;
|
|
1670
|
+
const { parent, definition, stage, task } = found, ctx = await buildEngineContext({
|
|
1481
1671
|
client,
|
|
1482
1672
|
clientForGdr: clientForGdr ?? (() => client),
|
|
1483
1673
|
instance: parent,
|
|
1484
|
-
definition
|
|
1485
|
-
|
|
1674
|
+
definition,
|
|
1675
|
+
...clock ? { clock } : {}
|
|
1676
|
+
}), mutation = startMutation(parent), mutEntry = currentTasks(mutation).find((e) => e.id === task.id);
|
|
1486
1677
|
if (mutEntry === void 0) return;
|
|
1487
|
-
const previousStatus = mutEntry.status;
|
|
1488
|
-
mutEntry.status = "done", mutEntry.completedAt =
|
|
1678
|
+
const previousStatus = mutEntry.status, now = ctx.now;
|
|
1679
|
+
mutEntry.status = "done", mutEntry.completedAt = now, mutation.history.push(
|
|
1489
1680
|
taskStatusChangedEntry({
|
|
1490
|
-
stageId:
|
|
1491
|
-
taskId:
|
|
1681
|
+
stageId: stage.id,
|
|
1682
|
+
taskId: task.id,
|
|
1492
1683
|
from: previousStatus,
|
|
1493
1684
|
to: "done",
|
|
1685
|
+
at: now,
|
|
1494
1686
|
...actor !== void 0 ? { actor } : {}
|
|
1495
1687
|
})
|
|
1496
|
-
), await persist(ctx, mutation), await cascadeAutoTransitions(client,
|
|
1688
|
+
), await persist(ctx, mutation), await cascadeAutoTransitions(client, parent._id, actor, clientForGdr, clock), await propagateToAncestors(client, parent._id, actor, clientForGdr, clock);
|
|
1497
1689
|
}
|
|
1498
1690
|
function startMutation(instance) {
|
|
1499
1691
|
return {
|
|
@@ -1522,7 +1714,7 @@ function taskStatusChangedEntry(args) {
|
|
|
1522
1714
|
return {
|
|
1523
1715
|
_key: randomKey(),
|
|
1524
1716
|
_type: "workflow.history.taskStatusChanged",
|
|
1525
|
-
at: args.at
|
|
1717
|
+
at: args.at,
|
|
1526
1718
|
stageId: args.stageId,
|
|
1527
1719
|
taskId: args.taskId,
|
|
1528
1720
|
from: args.from,
|
|
@@ -1562,19 +1754,31 @@ function stageTransitionHistory(args) {
|
|
|
1562
1754
|
}
|
|
1563
1755
|
];
|
|
1564
1756
|
}
|
|
1565
|
-
|
|
1566
|
-
const
|
|
1757
|
+
function instanceStateFields(src) {
|
|
1758
|
+
const fields = {
|
|
1759
|
+
currentStageId: src.currentStageId,
|
|
1760
|
+
state: src.state,
|
|
1761
|
+
stages: src.stages,
|
|
1762
|
+
pendingEffects: src.pendingEffects,
|
|
1763
|
+
effectHistory: src.effectHistory,
|
|
1764
|
+
effectsContext: src.effectsContext,
|
|
1765
|
+
history: src.history
|
|
1766
|
+
};
|
|
1767
|
+
return src.completedAt !== void 0 && (fields.completedAt = src.completedAt), fields;
|
|
1768
|
+
}
|
|
1769
|
+
function materializeInstance(base, mutation) {
|
|
1770
|
+
return {
|
|
1771
|
+
...base,
|
|
1567
1772
|
currentStageId: mutation.currentStageId,
|
|
1568
1773
|
state: mutation.state,
|
|
1569
|
-
stages: mutation.stages
|
|
1570
|
-
pendingEffects: mutation.pendingEffects,
|
|
1571
|
-
effectHistory: mutation.effectHistory,
|
|
1572
|
-
effectsContext: mutation.effectsContext,
|
|
1573
|
-
history: mutation.history,
|
|
1574
|
-
lastChangedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1774
|
+
stages: mutation.stages
|
|
1575
1775
|
};
|
|
1576
|
-
|
|
1577
|
-
|
|
1776
|
+
}
|
|
1777
|
+
async function persist(ctx, mutation) {
|
|
1778
|
+
const set = {
|
|
1779
|
+
...instanceStateFields(mutation),
|
|
1780
|
+
lastChangedAt: ctx.now
|
|
1781
|
+
}, pendingCreates = mutation.pendingCreates;
|
|
1578
1782
|
if (pendingCreates.length === 0)
|
|
1579
1783
|
return ctx.client.patch(ctx.instance._id).set(set).ifRevisionId(ctx.instance._rev).commit();
|
|
1580
1784
|
const tx = ctx.client.transaction();
|
|
@@ -1582,7 +1786,7 @@ async function persist(ctx, mutation) {
|
|
|
1582
1786
|
tx.patch(ctx.client.patch(ctx.instance._id).set(set).ifRevisionId(ctx.instance._rev)), await tx.commit(), mutation.pendingCreates = [];
|
|
1583
1787
|
const actorForPriming = void 0;
|
|
1584
1788
|
for (const body of pendingCreates)
|
|
1585
|
-
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);
|
|
1789
|
+
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);
|
|
1586
1790
|
const reloaded = await ctx.client.getDocument(ctx.instance._id);
|
|
1587
1791
|
if (!reloaded)
|
|
1588
1792
|
throw new Error(`Instance ${ctx.instance._id} disappeared after transaction commit`);
|
|
@@ -1623,7 +1827,9 @@ function findCurrentTasks(instance) {
|
|
|
1623
1827
|
return findCurrentStageEntry(instance)?.tasks ?? [];
|
|
1624
1828
|
}
|
|
1625
1829
|
async function completeEffect(args) {
|
|
1626
|
-
const { client, instanceId, effectKey, status, outputs, detail, error, durationMs, options } = args, ctx = await loadContext(client, instanceId
|
|
1830
|
+
const { client, instanceId, effectKey, status, outputs, detail, error, durationMs, options } = args, ctx = await loadContext(client, instanceId, {
|
|
1831
|
+
...options?.clock ? { clock: options.clock } : {}
|
|
1832
|
+
});
|
|
1627
1833
|
return commitCompleteEffect(
|
|
1628
1834
|
ctx,
|
|
1629
1835
|
effectKey,
|
|
@@ -1665,7 +1871,7 @@ async function commitCompleteEffect(ctx, effectKey, status, outputs, detail, err
|
|
|
1665
1871
|
throw new Error(`Pending effect "${effectKey}" not found on instance ${ctx.instance._id}`);
|
|
1666
1872
|
const mutation = startMutation(ctx.instance);
|
|
1667
1873
|
mutation.pendingEffects = mutation.pendingEffects.filter((e) => e._key !== effectKey);
|
|
1668
|
-
const ranAt =
|
|
1874
|
+
const ranAt = ctx.now;
|
|
1669
1875
|
return mutation.effectHistory.push(
|
|
1670
1876
|
buildEffectHistoryEntry(pending, { status, ranAt, actor, detail, error, durationMs, outputs })
|
|
1671
1877
|
), status === "done" && outputs !== void 0 && upsertEffectsContext(mutation, outputs), mutation.history.push({
|
|
@@ -1680,7 +1886,7 @@ async function commitCompleteEffect(ctx, effectKey, status, outputs, detail, err
|
|
|
1680
1886
|
...actor !== void 0 ? { actor } : {}
|
|
1681
1887
|
}), await persist(ctx, mutation), { effectKey, status };
|
|
1682
1888
|
}
|
|
1683
|
-
function buildQueuedEffect(effect, origin, params, actor) {
|
|
1889
|
+
function buildQueuedEffect(effect, origin, params, actor, now) {
|
|
1684
1890
|
const key = randomKey(), pending = {
|
|
1685
1891
|
_key: key,
|
|
1686
1892
|
_type: "workflow.pendingEffect",
|
|
@@ -1691,11 +1897,11 @@ function buildQueuedEffect(effect, origin, params, actor) {
|
|
|
1691
1897
|
params,
|
|
1692
1898
|
origin,
|
|
1693
1899
|
...actor !== void 0 ? { actor } : {},
|
|
1694
|
-
queuedAt:
|
|
1900
|
+
queuedAt: now
|
|
1695
1901
|
}, history = {
|
|
1696
1902
|
_key: randomKey(),
|
|
1697
1903
|
_type: "workflow.history.effectQueued",
|
|
1698
|
-
at:
|
|
1904
|
+
at: now,
|
|
1699
1905
|
effectKey: key,
|
|
1700
1906
|
effectId: effect.id,
|
|
1701
1907
|
origin
|
|
@@ -1704,7 +1910,7 @@ function buildQueuedEffect(effect, origin, params, actor) {
|
|
|
1704
1910
|
}
|
|
1705
1911
|
async function queueEffects(ctx, mutation, effects, origin, actor, callerParams) {
|
|
1706
1912
|
if (!effects) return;
|
|
1707
|
-
const liveInstance = {
|
|
1913
|
+
const now = ctx.now, liveInstance = {
|
|
1708
1914
|
...ctx.instance,
|
|
1709
1915
|
state: mutation.state,
|
|
1710
1916
|
stages: mutation.stages,
|
|
@@ -1715,22 +1921,21 @@ async function queueEffects(ctx, mutation, effects, origin, actor, callerParams)
|
|
|
1715
1921
|
bindings: effect.bindings,
|
|
1716
1922
|
staticInput: effect.input,
|
|
1717
1923
|
instance: liveInstance,
|
|
1924
|
+
now,
|
|
1718
1925
|
...callerParams !== void 0 ? { params: callerParams } : {},
|
|
1719
1926
|
...actor !== void 0 ? { actor } : {}
|
|
1720
|
-
}), { pending, history } = buildQueuedEffect(effect, origin, params, actor);
|
|
1927
|
+
}), { pending, history } = buildQueuedEffect(effect, origin, params, actor, now);
|
|
1721
1928
|
mutation.pendingEffects.push(pending), mutation.history.push(history);
|
|
1722
1929
|
}
|
|
1723
1930
|
}
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
${lines}`), this.name = "ActionParamsInvalidError", this.action = args.action, this.taskId = args.taskId, this.issues = args.issues;
|
|
1733
|
-
}
|
|
1931
|
+
const STATE_OP_TYPES = [
|
|
1932
|
+
"workflow.op.state.set",
|
|
1933
|
+
"workflow.op.state.append",
|
|
1934
|
+
"workflow.op.state.updateWhere",
|
|
1935
|
+
"workflow.op.state.removeWhere"
|
|
1936
|
+
];
|
|
1937
|
+
function isStateOp(summary) {
|
|
1938
|
+
return STATE_OP_TYPES.includes(summary.opType);
|
|
1734
1939
|
}
|
|
1735
1940
|
function validateActionParams(action, taskId, callerParams) {
|
|
1736
1941
|
const declared = action.params ?? [], params = callerParams ?? {}, issues = [];
|
|
@@ -1767,7 +1972,7 @@ function checkParamType(value, decl) {
|
|
|
1767
1972
|
case "doc.ref":
|
|
1768
1973
|
return hasStringField(value, "id");
|
|
1769
1974
|
case "doc.refs":
|
|
1770
|
-
return Array.isArray(value) && value.every((
|
|
1975
|
+
return Array.isArray(value) && value.every((v2) => checkParamType(v2, { paramType: "doc.ref" }));
|
|
1771
1976
|
case "json":
|
|
1772
1977
|
return !0;
|
|
1773
1978
|
default:
|
|
@@ -1775,15 +1980,15 @@ function checkParamType(value, decl) {
|
|
|
1775
1980
|
}
|
|
1776
1981
|
}
|
|
1777
1982
|
async function runActionOps(args) {
|
|
1778
|
-
const { ops, mutation, stageId, taskId, actionName, params, actor } = args;
|
|
1983
|
+
const { ops, mutation, stageId, taskId, actionName, params, actor, self, now } = args;
|
|
1779
1984
|
if (ops === void 0 || ops.length === 0) return [];
|
|
1780
1985
|
const summaries = [];
|
|
1781
1986
|
for (const op of ops) {
|
|
1782
|
-
const summary = applyOp(op, { mutation, stageId, taskId, params, actor });
|
|
1987
|
+
const summary = applyOp(op, { mutation, stageId, taskId, params, actor, self, now });
|
|
1783
1988
|
summaries.push(summary), mutation.history.push({
|
|
1784
1989
|
_key: randomKey(),
|
|
1785
1990
|
_type: "workflow.history.opApplied",
|
|
1786
|
-
at:
|
|
1991
|
+
at: now,
|
|
1787
1992
|
stageId,
|
|
1788
1993
|
taskId,
|
|
1789
1994
|
actionId: actionName,
|
|
@@ -1842,8 +2047,8 @@ function applyStateUpdateWhere(op, ctx) {
|
|
|
1842
2047
|
/*createIfMissing*/
|
|
1843
2048
|
!1
|
|
1844
2049
|
), resolvedMerge = {};
|
|
1845
|
-
for (const [k,
|
|
1846
|
-
resolvedMerge[k] = resolveOpSource(
|
|
2050
|
+
for (const [k, v2] of Object.entries(op.merge))
|
|
2051
|
+
resolvedMerge[k] = resolveOpSource(v2, ctx);
|
|
1847
2052
|
return slot !== void 0 && Array.isArray(slot.value) && (slot.value = slot.value.map(
|
|
1848
2053
|
(row) => evalOpPredicate(op.where, row, ctx) ? { ...row, ...resolvedMerge } : row
|
|
1849
2054
|
)), { opType: op.type, target: op.target, resolved: { merge: resolvedMerge } };
|
|
@@ -1886,18 +2091,11 @@ function locateSlot(ctx, target, createIfMissing) {
|
|
|
1886
2091
|
}, host.push(slot)), slot;
|
|
1887
2092
|
}
|
|
1888
2093
|
function resolveOpSource(src, ctx) {
|
|
2094
|
+
const staticValue = resolveStaticSource(src, ctx);
|
|
2095
|
+
if (staticValue.handled) return staticValue.value;
|
|
1889
2096
|
switch (src.source) {
|
|
1890
|
-
case "literal":
|
|
1891
|
-
return src.value;
|
|
1892
|
-
case "param":
|
|
1893
|
-
return ctx.params[src.paramId];
|
|
1894
|
-
case "actor":
|
|
1895
|
-
return ctx.actor;
|
|
1896
|
-
case "now":
|
|
1897
|
-
return (/* @__PURE__ */ new Date()).toISOString();
|
|
1898
2097
|
case "self":
|
|
1899
|
-
return;
|
|
1900
|
-
// Not applicable inside ops; reserved for higher-level binding.
|
|
2098
|
+
return ctx.self;
|
|
1901
2099
|
case "stageId":
|
|
1902
2100
|
return ctx.stageId;
|
|
1903
2101
|
case "stateRead": {
|
|
@@ -1907,8 +2105,8 @@ function resolveOpSource(src, ctx) {
|
|
|
1907
2105
|
case "effectOutput": {
|
|
1908
2106
|
const entry = ctx.mutation.effectsContext.find((e) => e.id === src.contextId);
|
|
1909
2107
|
if (entry === void 0) return null;
|
|
1910
|
-
const
|
|
1911
|
-
return
|
|
2108
|
+
const v2 = src.path !== void 0 ? getPath(entry.value, src.path) : entry.value;
|
|
2109
|
+
return v2 === void 0 ? null : v2;
|
|
1912
2110
|
}
|
|
1913
2111
|
case "object": {
|
|
1914
2112
|
const out = {};
|
|
@@ -1916,6 +2114,11 @@ function resolveOpSource(src, ctx) {
|
|
|
1916
2114
|
out[field] = resolveOpSource(fieldSrc, ctx);
|
|
1917
2115
|
return out;
|
|
1918
2116
|
}
|
|
2117
|
+
case "row":
|
|
2118
|
+
case "parentState":
|
|
2119
|
+
return;
|
|
2120
|
+
default:
|
|
2121
|
+
return;
|
|
1919
2122
|
}
|
|
1920
2123
|
}
|
|
1921
2124
|
function slotValue(slots, slotId) {
|
|
@@ -1935,8 +2138,23 @@ function evalOpPredicate(p, row, ctx) {
|
|
|
1935
2138
|
return row[p.field] === target;
|
|
1936
2139
|
}
|
|
1937
2140
|
async function fireAction(args) {
|
|
1938
|
-
const { client, instanceId, taskId, action, params, options } = args
|
|
1939
|
-
|
|
2141
|
+
const { client, instanceId, taskId, action, params, options } = args;
|
|
2142
|
+
for (let attempt = 1; attempt <= CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS; attempt++) {
|
|
2143
|
+
const ctx = await loadContext(client, instanceId, {
|
|
2144
|
+
...options?.clock ? { clock: options.clock } : {}
|
|
2145
|
+
});
|
|
2146
|
+
try {
|
|
2147
|
+
return await commitAction(ctx, taskId, action, params, options?.actor);
|
|
2148
|
+
} catch (error) {
|
|
2149
|
+
if (!isRevisionConflict(error)) throw error;
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
throw new ConcurrentFireActionError({
|
|
2153
|
+
instanceId,
|
|
2154
|
+
taskId,
|
|
2155
|
+
action,
|
|
2156
|
+
attempts: CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS
|
|
2157
|
+
});
|
|
1940
2158
|
}
|
|
1941
2159
|
async function resolveActionCommit(ctx, taskId, actionName, callerParams) {
|
|
1942
2160
|
const { stage, task } = findTaskInCurrentStage(ctx, taskId), action = (task.actions ?? []).find((a) => a.id === actionName);
|
|
@@ -1952,15 +2170,16 @@ async function resolveActionCommit(ctx, taskId, actionName, callerParams) {
|
|
|
1952
2170
|
const params = validateActionParams(action, taskId, callerParams);
|
|
1953
2171
|
return { stage, task, action, params };
|
|
1954
2172
|
}
|
|
1955
|
-
function applyActionSetStatus(action, mutation, mutEntry, stageId, taskId, actor) {
|
|
2173
|
+
function applyActionSetStatus(action, mutation, mutEntry, stageId, taskId, actor, now) {
|
|
1956
2174
|
if (action.setStatus === void 0) return;
|
|
1957
2175
|
const initialStatus = mutEntry.status, newStatus = action.setStatus;
|
|
1958
|
-
return mutEntry.status = newStatus, mutEntry.completedAt =
|
|
2176
|
+
return mutEntry.status = newStatus, mutEntry.completedAt = now, actor && (mutEntry.completedBy = actor.id), mutation.history.push(
|
|
1959
2177
|
taskStatusChangedEntry({
|
|
1960
2178
|
stageId,
|
|
1961
2179
|
taskId,
|
|
1962
2180
|
from: initialStatus,
|
|
1963
2181
|
to: newStatus,
|
|
2182
|
+
at: now,
|
|
1964
2183
|
...actor !== void 0 ? { actor } : {}
|
|
1965
2184
|
})
|
|
1966
2185
|
), newStatus;
|
|
@@ -1971,11 +2190,11 @@ async function commitAction(ctx, taskId, actionName, callerParams, actor) {
|
|
|
1971
2190
|
taskId,
|
|
1972
2191
|
actionName,
|
|
1973
2192
|
callerParams
|
|
1974
|
-
), mutation = startMutation(ctx.instance), mutEntry = requireMutationTaskEntry(mutation, taskId);
|
|
2193
|
+
), mutation = startMutation(ctx.instance), mutEntry = requireMutationTaskEntry(mutation, taskId), now = ctx.now;
|
|
1975
2194
|
mutation.history.push({
|
|
1976
2195
|
_key: randomKey(),
|
|
1977
2196
|
_type: "workflow.history.actionFired",
|
|
1978
|
-
at:
|
|
2197
|
+
at: now,
|
|
1979
2198
|
stageId: stage.id,
|
|
1980
2199
|
taskId,
|
|
1981
2200
|
actionId: actionName,
|
|
@@ -1988,8 +2207,10 @@ async function commitAction(ctx, taskId, actionName, callerParams, actor) {
|
|
|
1988
2207
|
taskId,
|
|
1989
2208
|
actionName,
|
|
1990
2209
|
params,
|
|
1991
|
-
actor
|
|
1992
|
-
|
|
2210
|
+
actor,
|
|
2211
|
+
self: selfGdr(ctx.instance),
|
|
2212
|
+
now
|
|
2213
|
+
}), newStatus = applyActionSetStatus(action, mutation, mutEntry, stage.id, taskId, actor, now);
|
|
1993
2214
|
return await queueEffects(
|
|
1994
2215
|
ctx,
|
|
1995
2216
|
mutation,
|
|
@@ -2000,7 +2221,11 @@ async function commitAction(ctx, taskId, actionName, callerParams, actor) {
|
|
|
2000
2221
|
},
|
|
2001
2222
|
actor,
|
|
2002
2223
|
params
|
|
2003
|
-
), await
|
|
2224
|
+
), await persistThenDeploy(
|
|
2225
|
+
ctx,
|
|
2226
|
+
mutation,
|
|
2227
|
+
() => ranOps.some(isStateOp) ? refreshStageGuards(ctx, mutation, stage.id) : Promise.resolve()
|
|
2228
|
+
), {
|
|
2004
2229
|
fired: !0,
|
|
2005
2230
|
taskId,
|
|
2006
2231
|
action: actionName,
|
|
@@ -2039,7 +2264,7 @@ async function resolveAccess(client, args = {}) {
|
|
|
2039
2264
|
const overrideActor = args.override?.actor, overrideGrants = args.override?.grants;
|
|
2040
2265
|
if (overrideActor !== void 0 && overrideGrants !== void 0)
|
|
2041
2266
|
return { actor: overrideActor, grants: overrideGrants };
|
|
2042
|
-
const requestFn = client.request;
|
|
2267
|
+
const requestFn = client.request === void 0 ? void 0 : (opts) => client.request(opts);
|
|
2043
2268
|
if (requestFn === void 0) {
|
|
2044
2269
|
if (overrideActor === void 0)
|
|
2045
2270
|
throw new Error(
|
|
@@ -2114,7 +2339,7 @@ function canonicalTag(tags) {
|
|
|
2114
2339
|
}
|
|
2115
2340
|
const WILDCARD_ROLE = "*";
|
|
2116
2341
|
async function evaluateInstance(args) {
|
|
2117
|
-
const { client, tags, instanceId, resourceClients } = args;
|
|
2342
|
+
const { client, tags, instanceId, resourceClients } = args, now = (args.clock ?? wallClock)();
|
|
2118
2343
|
validateTags(tags);
|
|
2119
2344
|
const { actor, grants } = await resolveAccess(client, {
|
|
2120
2345
|
...args.access !== void 0 ? { override: args.access } : {},
|
|
@@ -2146,7 +2371,8 @@ async function evaluateInstance(args) {
|
|
|
2146
2371
|
definition,
|
|
2147
2372
|
actor,
|
|
2148
2373
|
grants,
|
|
2149
|
-
snapshot
|
|
2374
|
+
snapshot,
|
|
2375
|
+
now
|
|
2150
2376
|
})
|
|
2151
2377
|
);
|
|
2152
2378
|
const transitionEvaluations = [];
|
|
@@ -2157,7 +2383,7 @@ async function evaluateInstance(args) {
|
|
|
2157
2383
|
filter: transition.filter,
|
|
2158
2384
|
definition,
|
|
2159
2385
|
snapshot,
|
|
2160
|
-
params: buildParams(instance)
|
|
2386
|
+
params: buildParams(instance, now)
|
|
2161
2387
|
})
|
|
2162
2388
|
});
|
|
2163
2389
|
const currentStage = {
|
|
@@ -2175,7 +2401,7 @@ async function evaluateInstance(args) {
|
|
|
2175
2401
|
};
|
|
2176
2402
|
}
|
|
2177
2403
|
async function evaluateTask(args) {
|
|
2178
|
-
const { task, statusEntry, instance, definition, actor, grants, snapshot } = args, status = statusEntry?.status ?? "pending", actions = [];
|
|
2404
|
+
const { task, statusEntry, instance, definition, actor, grants, snapshot, now } = args, status = statusEntry?.status ?? "pending", actions = [];
|
|
2179
2405
|
for (const action of task.actions ?? [])
|
|
2180
2406
|
actions.push(
|
|
2181
2407
|
await evaluateAction({
|
|
@@ -2187,7 +2413,8 @@ async function evaluateTask(args) {
|
|
|
2187
2413
|
definition,
|
|
2188
2414
|
actor,
|
|
2189
2415
|
...grants !== void 0 ? { grants } : {},
|
|
2190
|
-
snapshot
|
|
2416
|
+
snapshot,
|
|
2417
|
+
now
|
|
2191
2418
|
})
|
|
2192
2419
|
);
|
|
2193
2420
|
return {
|
|
@@ -2201,9 +2428,9 @@ async function evaluateTask(args) {
|
|
|
2201
2428
|
};
|
|
2202
2429
|
}
|
|
2203
2430
|
async function evaluateAction(args) {
|
|
2204
|
-
const { action, task, status, instance, definition, actor, grants, snapshot } = args, syncReason = lifecycleReason(instance, definition, status) ?? actorReason(action, task, actor);
|
|
2431
|
+
const { action, task, status, instance, definition, actor, grants, snapshot, now } = args, syncReason = lifecycleReason(instance, definition, status) ?? actorReason(action, task, actor);
|
|
2205
2432
|
if (syncReason !== void 0) return disabled(action, syncReason);
|
|
2206
|
-
const asyncReason = await permissionReason(action, instance, actor, grants) ?? await filterReason(action, instance, definition, snapshot);
|
|
2433
|
+
const asyncReason = await permissionReason(action, instance, actor, grants) ?? await filterReason(action, instance, definition, snapshot, now);
|
|
2207
2434
|
return asyncReason !== void 0 ? disabled(action, asyncReason) : { action, allowed: !0 };
|
|
2208
2435
|
}
|
|
2209
2436
|
function lifecycleReason(instance, definition, status) {
|
|
@@ -2232,12 +2459,12 @@ async function permissionReason(action, instance, actor, grants) {
|
|
|
2232
2459
|
}))
|
|
2233
2460
|
return { kind: "permission-denied", permission, matchingGrants: grants.length };
|
|
2234
2461
|
}
|
|
2235
|
-
async function filterReason(action, instance, definition, snapshot) {
|
|
2462
|
+
async function filterReason(action, instance, definition, snapshot, now) {
|
|
2236
2463
|
if (!(action.filter === void 0 || await evaluateFilter({
|
|
2237
2464
|
filter: action.filter,
|
|
2238
2465
|
definition,
|
|
2239
2466
|
snapshot,
|
|
2240
|
-
params: buildParams(instance)
|
|
2467
|
+
params: buildParams(instance, now)
|
|
2241
2468
|
})))
|
|
2242
2469
|
return { kind: "filter-failed", filter: action.filter };
|
|
2243
2470
|
}
|
|
@@ -2262,6 +2489,43 @@ function parseDefinitionSnapshot(instance) {
|
|
|
2262
2489
|
);
|
|
2263
2490
|
}
|
|
2264
2491
|
}
|
|
2492
|
+
const resourceKey = (r) => `${r.type}.${r.id}`;
|
|
2493
|
+
function guardsForResource(client) {
|
|
2494
|
+
return client.fetch("*[_type == $t] | order(_id asc)", { t: GUARD_DOC_TYPE });
|
|
2495
|
+
}
|
|
2496
|
+
async function guardsForInstance(args) {
|
|
2497
|
+
const { client, clientForGdr, instance } = args, clientsByResource = /* @__PURE__ */ new Map([
|
|
2498
|
+
[resourceKey(instance.workflowResource), client]
|
|
2499
|
+
]), stateUris = [
|
|
2500
|
+
...collectSlotDocUris(instance.state),
|
|
2501
|
+
...instance.stages.flatMap((stage) => collectSlotDocUris(stage.state))
|
|
2502
|
+
];
|
|
2503
|
+
for (const uri of stateUris) {
|
|
2504
|
+
const parsed = tryParseGdr(uri);
|
|
2505
|
+
if (parsed === void 0) continue;
|
|
2506
|
+
const key = resourceKey(resourceFromParsed(parsed));
|
|
2507
|
+
clientsByResource.has(key) || clientsByResource.set(key, clientForGdr(parsed));
|
|
2508
|
+
}
|
|
2509
|
+
const perResource = await Promise.all(
|
|
2510
|
+
[...clientsByResource.values()].map(
|
|
2511
|
+
(c) => c.fetch("*[_type == $t && sourceInstanceId == $id]", {
|
|
2512
|
+
t: GUARD_DOC_TYPE,
|
|
2513
|
+
id: instance._id
|
|
2514
|
+
})
|
|
2515
|
+
)
|
|
2516
|
+
);
|
|
2517
|
+
return dedupById(perResource.flat());
|
|
2518
|
+
}
|
|
2519
|
+
function tryParseGdr(uri) {
|
|
2520
|
+
try {
|
|
2521
|
+
return parseGdr(uri);
|
|
2522
|
+
} catch {
|
|
2523
|
+
return;
|
|
2524
|
+
}
|
|
2525
|
+
}
|
|
2526
|
+
function dedupById(guards) {
|
|
2527
|
+
return [...new Map(guards.map((g) => [g._id, g])).values()].sort((a, b) => a._id.localeCompare(b._id));
|
|
2528
|
+
}
|
|
2265
2529
|
class ActionDisabledError extends Error {
|
|
2266
2530
|
reason;
|
|
2267
2531
|
taskId;
|
|
@@ -2372,14 +2636,14 @@ function isDefinitionUnchanged(existing, expected) {
|
|
|
2372
2636
|
}
|
|
2373
2637
|
function stripSystemFields(doc) {
|
|
2374
2638
|
const out = {};
|
|
2375
|
-
for (const [k,
|
|
2376
|
-
k === "_rev" || k === "_createdAt" || k === "_updatedAt" || (out[k] =
|
|
2639
|
+
for (const [k, v2] of Object.entries(doc))
|
|
2640
|
+
k === "_rev" || k === "_createdAt" || k === "_updatedAt" || (out[k] = v2);
|
|
2377
2641
|
return out;
|
|
2378
2642
|
}
|
|
2379
2643
|
function stableStringify(value) {
|
|
2380
2644
|
return value === null || typeof value != "object" ? JSON.stringify(value) : Array.isArray(value) ? `[${value.map(stableStringify).join(",")}]` : `{${Object.entries(value).toSorted(
|
|
2381
2645
|
([a], [b]) => a.localeCompare(b)
|
|
2382
|
-
).map(([k,
|
|
2646
|
+
).map(([k, v2]) => `${JSON.stringify(k)}:${stableStringify(v2)}`).join(",")}}`;
|
|
2383
2647
|
}
|
|
2384
2648
|
async function loadDefinition(client, workflowId, version, tags) {
|
|
2385
2649
|
if (version !== void 0) {
|
|
@@ -2419,9 +2683,9 @@ async function resolveOperationContext(args) {
|
|
|
2419
2683
|
clientForGdr: buildClientForGdr(args.client, args.resourceClients)
|
|
2420
2684
|
};
|
|
2421
2685
|
}
|
|
2422
|
-
async function cascade(client, instanceId, actor, clientForGdr) {
|
|
2423
|
-
const count = await cascadeAutoTransitions(client, instanceId, actor, clientForGdr);
|
|
2424
|
-
return await propagateToAncestors(client, instanceId, actor, clientForGdr), count;
|
|
2686
|
+
async function cascade(client, instanceId, actor, clientForGdr, clock) {
|
|
2687
|
+
const count = await cascadeAutoTransitions(client, instanceId, actor, clientForGdr, clock);
|
|
2688
|
+
return await propagateToAncestors(client, instanceId, actor, clientForGdr, clock), count;
|
|
2425
2689
|
}
|
|
2426
2690
|
function buildClientForGdr(defaultClient, resolver) {
|
|
2427
2691
|
return resolver === void 0 ? () => defaultClient : (parsed) => resolver(parsed) ?? defaultClient;
|
|
@@ -2513,16 +2777,17 @@ const workflow = {
|
|
|
2513
2777
|
effectsContext,
|
|
2514
2778
|
instanceId,
|
|
2515
2779
|
perspective
|
|
2516
|
-
} = args, { actor, clientForGdr } = await resolveOperationContext(args), definition = await loadDefinition(client, workflowId, version, tags), id = instanceId ?? `${canonicalTag(tags)}.wf-instance.${randomKey()}`;
|
|
2780
|
+
} = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, definition = await loadDefinition(client, workflowId, version, tags), id = instanceId ?? `${canonicalTag(tags)}.wf-instance.${randomKey()}`;
|
|
2517
2781
|
if (definition.stages.find((s) => s.id === definition.initialStageId) === void 0)
|
|
2518
2782
|
throw new Error(
|
|
2519
2783
|
`Initial stage "${definition.initialStageId}" missing in ${workflowId} v${definition.version}`
|
|
2520
2784
|
);
|
|
2521
|
-
const now = (
|
|
2785
|
+
const now = clock(), effectsContextEntries = effectsContext ? toEffectsContextEntries(effectsContext) : [], resolvedState = await resolveDeclaredState({
|
|
2522
2786
|
slotDefs: definition.state ?? [],
|
|
2523
2787
|
initialState: initialState ?? [],
|
|
2524
2788
|
ctx: {
|
|
2525
2789
|
client,
|
|
2790
|
+
now,
|
|
2526
2791
|
selfId: id,
|
|
2527
2792
|
tags,
|
|
2528
2793
|
workflowResource,
|
|
@@ -2544,7 +2809,7 @@ const workflow = {
|
|
|
2544
2809
|
initialStageId: definition.initialStageId,
|
|
2545
2810
|
actor
|
|
2546
2811
|
});
|
|
2547
|
-
return await client.create(base), await primeInitialStage(client, id, actor, clientForGdr), await cascade(client, id, actor, clientForGdr), reload(client, id, tags);
|
|
2812
|
+
return await client.create(base), await primeInitialStage(client, id, actor, clientForGdr, clock), await cascade(client, id, actor, clientForGdr, clock), reload(client, id, tags);
|
|
2548
2813
|
},
|
|
2549
2814
|
/**
|
|
2550
2815
|
* Fire an action against a task. If the task is pending, it is
|
|
@@ -2566,7 +2831,7 @@ const workflow = {
|
|
|
2566
2831
|
params,
|
|
2567
2832
|
idempotent,
|
|
2568
2833
|
resourceClients
|
|
2569
|
-
} = args, { access, actor, clientForGdr } = await resolveOperationContext(args), before = await reload(client, instanceId, tags), taskEntry = before.stages.find(
|
|
2834
|
+
} = args, { access, actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, before = await reload(client, instanceId, tags), taskEntry = before.stages.find(
|
|
2570
2835
|
(s) => s.id === before.currentStageId && s.exitedAt === void 0
|
|
2571
2836
|
)?.tasks.find((t) => t.id === taskId);
|
|
2572
2837
|
if (taskEntry === void 0 && idempotent === !0)
|
|
@@ -2576,6 +2841,7 @@ const workflow = {
|
|
|
2576
2841
|
tags,
|
|
2577
2842
|
instanceId,
|
|
2578
2843
|
access,
|
|
2844
|
+
clock,
|
|
2579
2845
|
...resourceClients !== void 0 ? { resourceClients } : {}
|
|
2580
2846
|
})).currentStage.tasks.find((t) => t.task.id === taskId)?.actions.find((a) => a.action.id === action);
|
|
2581
2847
|
if (actionEval !== void 0 && !actionEval.allowed && actionEval.disabledReason)
|
|
@@ -2588,7 +2854,7 @@ const workflow = {
|
|
|
2588
2854
|
client,
|
|
2589
2855
|
instanceId,
|
|
2590
2856
|
taskId,
|
|
2591
|
-
options: { actor }
|
|
2857
|
+
options: { actor, clock }
|
|
2592
2858
|
});
|
|
2593
2859
|
const actionResult = await fireAction({
|
|
2594
2860
|
client,
|
|
@@ -2596,8 +2862,8 @@ const workflow = {
|
|
|
2596
2862
|
taskId,
|
|
2597
2863
|
action,
|
|
2598
2864
|
...params !== void 0 ? { params } : {},
|
|
2599
|
-
options: { actor }
|
|
2600
|
-
}), cascaded = await cascade(client, instanceId, actor, clientForGdr);
|
|
2865
|
+
options: { actor, clock }
|
|
2866
|
+
}), cascaded = await cascade(client, instanceId, actor, clientForGdr, clock);
|
|
2601
2867
|
return {
|
|
2602
2868
|
instance: await reload(client, instanceId, tags),
|
|
2603
2869
|
cascaded,
|
|
@@ -2613,7 +2879,7 @@ const workflow = {
|
|
|
2613
2879
|
* reference them. Cascades after.
|
|
2614
2880
|
*/
|
|
2615
2881
|
completeEffect: async (args) => {
|
|
2616
|
-
const { client, tags, instanceId, effectKey, status, outputs, detail, error, durationMs } = args, { actor, clientForGdr } = await resolveOperationContext(args);
|
|
2882
|
+
const { client, tags, instanceId, effectKey, status, outputs, detail, error, durationMs } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock;
|
|
2617
2883
|
await completeEffect({
|
|
2618
2884
|
client,
|
|
2619
2885
|
instanceId,
|
|
@@ -2623,9 +2889,9 @@ const workflow = {
|
|
|
2623
2889
|
...detail !== void 0 ? { detail } : {},
|
|
2624
2890
|
...error !== void 0 ? { error } : {},
|
|
2625
2891
|
...durationMs !== void 0 ? { durationMs } : {},
|
|
2626
|
-
...actor !== void 0 ? {
|
|
2892
|
+
options: { ...actor !== void 0 ? { actor } : {}, clock }
|
|
2627
2893
|
});
|
|
2628
|
-
const cascaded = await cascade(client, instanceId, actor, clientForGdr);
|
|
2894
|
+
const cascaded = await cascade(client, instanceId, actor, clientForGdr, clock);
|
|
2629
2895
|
return {
|
|
2630
2896
|
instance: await reload(client, instanceId, tags),
|
|
2631
2897
|
cascaded,
|
|
@@ -2642,9 +2908,9 @@ const workflow = {
|
|
|
2642
2908
|
* affected instances and the engine re-evaluates.
|
|
2643
2909
|
*/
|
|
2644
2910
|
tick: async (args) => {
|
|
2645
|
-
const { client, tags, instanceId } = args, { actor, clientForGdr } = await resolveOperationContext(args);
|
|
2911
|
+
const { client, tags, instanceId } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock;
|
|
2646
2912
|
await reload(client, instanceId, tags);
|
|
2647
|
-
const cascaded = await cascade(client, instanceId, actor, clientForGdr);
|
|
2913
|
+
const cascaded = await cascade(client, instanceId, actor, clientForGdr, clock);
|
|
2648
2914
|
return {
|
|
2649
2915
|
instance: await reload(client, instanceId, tags),
|
|
2650
2916
|
cascaded,
|
|
@@ -2657,15 +2923,15 @@ const workflow = {
|
|
|
2657
2923
|
* upstream; this verb performs the mechanical move.
|
|
2658
2924
|
*/
|
|
2659
2925
|
setStage: async (args) => {
|
|
2660
|
-
const { client, tags, instanceId, targetStageId, reason } = args, { actor, clientForGdr } = await resolveOperationContext(args);
|
|
2926
|
+
const { client, tags, instanceId, targetStageId, reason } = args, { actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock;
|
|
2661
2927
|
await reload(client, instanceId, tags);
|
|
2662
2928
|
const result = await setStage({
|
|
2663
2929
|
client,
|
|
2664
2930
|
instanceId,
|
|
2665
2931
|
targetStageId,
|
|
2666
2932
|
...reason !== void 0 ? { reason } : {},
|
|
2667
|
-
...actor !== void 0 ? {
|
|
2668
|
-
}), cascaded = result.fired ? await cascade(client, instanceId, actor, clientForGdr) : 0;
|
|
2933
|
+
options: { ...actor !== void 0 ? { actor } : {}, clock }
|
|
2934
|
+
}), cascaded = result.fired ? await cascade(client, instanceId, actor, clientForGdr, clock) : 0;
|
|
2669
2935
|
return {
|
|
2670
2936
|
instance: await reload(client, instanceId, tags),
|
|
2671
2937
|
cascaded,
|
|
@@ -2681,6 +2947,16 @@ const workflow = {
|
|
|
2681
2947
|
const { client, tags, instanceId } = args;
|
|
2682
2948
|
return validateTags(tags), reload(client, instanceId, tags);
|
|
2683
2949
|
},
|
|
2950
|
+
guardsForInstance: async (args) => {
|
|
2951
|
+
const { client, tags, instanceId, resourceClients } = args;
|
|
2952
|
+
validateTags(tags);
|
|
2953
|
+
const instance = await reload(client, instanceId, tags);
|
|
2954
|
+
return guardsForInstance({
|
|
2955
|
+
client,
|
|
2956
|
+
clientForGdr: buildClientForGdr(client, resourceClients),
|
|
2957
|
+
instance
|
|
2958
|
+
});
|
|
2959
|
+
},
|
|
2684
2960
|
/**
|
|
2685
2961
|
* Run a caller-supplied GROQ query with the engine's tags bound as
|
|
2686
2962
|
* `$engineTags`. This does NOT rewrite the query — arbitrary GROQ
|
|
@@ -2715,9 +2991,9 @@ const workflow = {
|
|
|
2715
2991
|
* without re-implementing hydration. Pure read — never writes.
|
|
2716
2992
|
*/
|
|
2717
2993
|
queryInScope: async (args) => {
|
|
2718
|
-
const { client, tags, instanceId, groq, params, resourceClients } = args;
|
|
2994
|
+
const { client, tags, instanceId, groq, params, resourceClients } = args, clock = args.clock ?? wallClock;
|
|
2719
2995
|
validateTags(tags);
|
|
2720
|
-
const instance = await reload(client, instanceId, tags), definition = JSON.parse(instance.definitionSnapshot), clientForGdr = buildClientForGdr(client, resourceClients), snapshot = await hydrateSnapshot({ client, clientForGdr, instance, definition }), reserved = buildParams(instance), tree = groqJs.parse(groq, { params: { ...reserved, ...params } });
|
|
2996
|
+
const instance = await reload(client, instanceId, tags), definition = JSON.parse(instance.definitionSnapshot), clientForGdr = buildClientForGdr(client, resourceClients), snapshot = await hydrateSnapshot({ client, clientForGdr, instance, definition }), reserved = buildParams(instance, clock()), tree = groqJs.parse(groq, { params: { ...reserved, ...params } });
|
|
2721
2997
|
return await (await groqJs.evaluate(tree, {
|
|
2722
2998
|
dataset: snapshot.docs,
|
|
2723
2999
|
params: { ...reserved, ...params }
|
|
@@ -2901,7 +3177,8 @@ async function claimPendingEffect(client, instance, effectKey, drainerActor) {
|
|
|
2901
3177
|
};
|
|
2902
3178
|
try {
|
|
2903
3179
|
return await client.patch(instance._id).ifRevisionId(instance._rev).set({ [`pendingEffects[_key=="${effectKey}"].claim`]: claim }).commit(), !0;
|
|
2904
|
-
} catch {
|
|
3180
|
+
} catch (error) {
|
|
3181
|
+
if (!isRevisionConflict(error)) throw error;
|
|
2905
3182
|
return !1;
|
|
2906
3183
|
}
|
|
2907
3184
|
}
|
|
@@ -2946,13 +3223,14 @@ const defaultLoggerFactory = (name) => ({
|
|
|
2946
3223
|
}
|
|
2947
3224
|
};
|
|
2948
3225
|
function createEngine(args) {
|
|
2949
|
-
const { client, workflowResource, tags, resourceClients } = args;
|
|
3226
|
+
const { client, workflowResource, tags, resourceClients, clock } = args;
|
|
2950
3227
|
validateTags(tags);
|
|
2951
3228
|
const effectHandlers = args.effectHandlers ?? {}, missingHandler = args.missingHandler ?? "fail", logger = args.loggerFactory ?? defaultLoggerFactory, bind = (rest) => ({
|
|
2952
3229
|
client,
|
|
2953
3230
|
tags,
|
|
2954
3231
|
workflowResource,
|
|
2955
3232
|
...resourceClients !== void 0 ? { resourceClients } : {},
|
|
3233
|
+
...clock !== void 0 ? { clock } : {},
|
|
2956
3234
|
...rest
|
|
2957
3235
|
});
|
|
2958
3236
|
return {
|
|
@@ -2970,6 +3248,13 @@ function createEngine(args) {
|
|
|
2970
3248
|
evaluateInstance: (rest) => evaluateInstance(bind(rest)),
|
|
2971
3249
|
setStage: (rest) => workflow.setStage(bind(rest)),
|
|
2972
3250
|
getInstance: ({ instanceId }) => workflow.getInstance({ client, tags, workflowResource, instanceId }),
|
|
3251
|
+
guardsForInstance: ({ instanceId }) => workflow.guardsForInstance({
|
|
3252
|
+
client,
|
|
3253
|
+
tags,
|
|
3254
|
+
workflowResource,
|
|
3255
|
+
instanceId,
|
|
3256
|
+
...resourceClients !== void 0 ? { resourceClients } : {}
|
|
3257
|
+
}),
|
|
2973
3258
|
children: ({ instanceId, taskId }) => workflow.children({
|
|
2974
3259
|
client,
|
|
2975
3260
|
tags,
|
|
@@ -2990,7 +3275,8 @@ function createEngine(args) {
|
|
|
2990
3275
|
workflowResource,
|
|
2991
3276
|
instanceId,
|
|
2992
3277
|
groq,
|
|
2993
|
-
...params !== void 0 ? { params } : {}
|
|
3278
|
+
...params !== void 0 ? { params } : {},
|
|
3279
|
+
...clock !== void 0 ? { clock } : {}
|
|
2994
3280
|
}),
|
|
2995
3281
|
listPendingEffects: ({ instanceId }) => workflow.listPendingEffects({ client, tags, workflowResource, instanceId }),
|
|
2996
3282
|
findPendingEffects: ({ instanceId, claimed, names }) => workflow.findPendingEffects({
|
|
@@ -3021,15 +3307,6 @@ function createEngine(args) {
|
|
|
3021
3307
|
})
|
|
3022
3308
|
};
|
|
3023
3309
|
}
|
|
3024
|
-
class MutationGuardDeniedError extends Error {
|
|
3025
|
-
denied;
|
|
3026
|
-
documentId;
|
|
3027
|
-
action;
|
|
3028
|
-
constructor(args) {
|
|
3029
|
-
const ids = args.denied.map((d) => d.guardId).join(", ");
|
|
3030
|
-
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;
|
|
3031
|
-
}
|
|
3032
|
-
}
|
|
3033
3310
|
const HISTORY_DISPLAY = {
|
|
3034
3311
|
"workflow.history.stageEntered": {
|
|
3035
3312
|
title: "Stage entered",
|
|
@@ -3224,14 +3501,18 @@ function displayDescription(typeKey) {
|
|
|
3224
3501
|
exports.AUTHORING_DISPLAY = AUTHORING_DISPLAY;
|
|
3225
3502
|
exports.ActionDisabledError = ActionDisabledError;
|
|
3226
3503
|
exports.ActionParamsInvalidError = ActionParamsInvalidError;
|
|
3504
|
+
exports.CascadeLimitError = CascadeLimitError;
|
|
3505
|
+
exports.ConcurrentFireActionError = ConcurrentFireActionError;
|
|
3227
3506
|
exports.DISPLAY = DISPLAY;
|
|
3228
3507
|
exports.EFFECTS_CONTEXT_DISPLAY = EFFECTS_CONTEXT_DISPLAY;
|
|
3508
|
+
exports.GUARD_DOC_TYPE = GUARD_DOC_TYPE;
|
|
3229
3509
|
exports.GUARD_LIFTED_PREDICATE = GUARD_LIFTED_PREDICATE;
|
|
3230
3510
|
exports.GUARD_OWNER = GUARD_OWNER;
|
|
3231
3511
|
exports.HISTORY_DISPLAY = HISTORY_DISPLAY;
|
|
3232
3512
|
exports.MutationGuardDeniedError = MutationGuardDeniedError;
|
|
3233
3513
|
exports.OP_DISPLAY = OP_DISPLAY;
|
|
3234
3514
|
exports.STATE_SLOT_DISPLAY = STATE_SLOT_DISPLAY;
|
|
3515
|
+
exports.WorkflowStateDivergedError = WorkflowStateDivergedError;
|
|
3235
3516
|
exports.canonicalTag = canonicalTag;
|
|
3236
3517
|
exports.compileGuard = compileGuard;
|
|
3237
3518
|
exports.createEngine = createEngine;
|
|
@@ -3246,10 +3527,12 @@ exports.gdrFromResource = gdrFromResource;
|
|
|
3246
3527
|
exports.gdrRef = gdrRef;
|
|
3247
3528
|
exports.gdrUri = gdrUri;
|
|
3248
3529
|
exports.guardMatches = guardMatches;
|
|
3530
|
+
exports.guardsForInstance = guardsForInstance;
|
|
3531
|
+
exports.guardsForResource = guardsForResource;
|
|
3532
|
+
exports.isDefinitionUnchanged = isDefinitionUnchanged;
|
|
3249
3533
|
exports.isGdr = isGdr;
|
|
3250
3534
|
exports.lakeGuardId = lakeGuardId;
|
|
3251
3535
|
exports.parseGdr = parseGdr;
|
|
3252
|
-
exports.reconcileStageGuards = reconcileStageGuards;
|
|
3253
3536
|
exports.refCanvas = refCanvas;
|
|
3254
3537
|
exports.refDashboard = refDashboard;
|
|
3255
3538
|
exports.refDataset = refDataset;
|
|
@@ -3257,7 +3540,9 @@ exports.refMediaLibrary = refMediaLibrary;
|
|
|
3257
3540
|
exports.resolveAccess = resolveAccess;
|
|
3258
3541
|
exports.retractStageGuards = retractStageGuards;
|
|
3259
3542
|
exports.silentLogger = silentLogger;
|
|
3543
|
+
exports.stripSystemFields = stripSystemFields;
|
|
3260
3544
|
exports.validateDefinition = validateDefinition;
|
|
3261
3545
|
exports.validateTags = validateTags;
|
|
3546
|
+
exports.wallClock = wallClock;
|
|
3262
3547
|
exports.workflow = workflow;
|
|
3263
3548
|
//# sourceMappingURL=index.cjs.map
|