@sanity/workflow-engine 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_chunks-cjs/schema.cjs +431 -0
- package/dist/_chunks-cjs/schema.cjs.map +1 -0
- package/dist/_chunks-es/schema.js +416 -0
- package/dist/_chunks-es/schema.js.map +1 -0
- package/dist/define.cjs +18 -418
- package/dist/define.cjs.map +1 -1
- package/dist/define.js +6 -405
- package/dist/define.js.map +1 -1
- package/dist/index.cjs +1401 -1238
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +236 -2
- package/dist/index.d.ts +236 -2
- package/dist/index.js +1397 -1233
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { parse, evaluate } from "groq-js";
|
|
2
|
+
import { WORKFLOW_DEFINITION_TYPE } from "./_chunks-es/schema.js";
|
|
2
3
|
import * as v from "valibot";
|
|
3
4
|
function validateDefinition(definition) {
|
|
4
5
|
const v2 = createDefinitionValidator();
|
|
@@ -177,9 +178,94 @@ function stripStateForLake(value) {
|
|
|
177
178
|
function bareId(id) {
|
|
178
179
|
return isGdrUri(id) ? extractDocumentId(id) : id;
|
|
179
180
|
}
|
|
180
|
-
function
|
|
181
|
-
|
|
182
|
-
|
|
181
|
+
function getPath(value, path) {
|
|
182
|
+
let current = value;
|
|
183
|
+
for (const part of path.split(".")) {
|
|
184
|
+
if (current == null || typeof current != "object") return;
|
|
185
|
+
current = current[part];
|
|
186
|
+
}
|
|
187
|
+
return current;
|
|
188
|
+
}
|
|
189
|
+
function readStateSlot(instance, scope, slotId, stageId, taskId) {
|
|
190
|
+
if (scope === "workflow")
|
|
191
|
+
return slotValue$1(instance.state?.find((s) => s.id === slotId));
|
|
192
|
+
const stageEntry = instance.stages.find(
|
|
193
|
+
(s) => s.id === (stageId ?? instance.currentStageId) && s.exitedAt === void 0
|
|
194
|
+
);
|
|
195
|
+
if (stageEntry === void 0) return;
|
|
196
|
+
if (scope === "stage")
|
|
197
|
+
return slotValue$1(stageEntry.state?.find((s) => s.id === slotId));
|
|
198
|
+
const taskEntry = stageEntry.tasks.find((t) => t.id === taskId);
|
|
199
|
+
return slotValue$1(taskEntry?.state?.find((s) => s.id === slotId));
|
|
200
|
+
}
|
|
201
|
+
function slotValue$1(slot) {
|
|
202
|
+
if (slot != null && typeof slot == "object")
|
|
203
|
+
return slot.value;
|
|
204
|
+
}
|
|
205
|
+
function resolveStaticSource(src, ctx) {
|
|
206
|
+
switch (src.source) {
|
|
207
|
+
case "literal":
|
|
208
|
+
return { handled: !0, value: src.value };
|
|
209
|
+
case "param":
|
|
210
|
+
return { handled: !0, value: ctx.params?.[src.paramId] };
|
|
211
|
+
case "actor":
|
|
212
|
+
return { handled: !0, value: ctx.actor };
|
|
213
|
+
case "now":
|
|
214
|
+
return { handled: !0, value: ctx.now };
|
|
215
|
+
default:
|
|
216
|
+
return { handled: !1 };
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
function resolveSource(src, ctx) {
|
|
220
|
+
const staticValue = resolveStaticSource(src, ctx);
|
|
221
|
+
if (staticValue.handled) return staticValue.value;
|
|
222
|
+
switch (src.source) {
|
|
223
|
+
case "self":
|
|
224
|
+
return ctx.instance._id;
|
|
225
|
+
case "stageId":
|
|
226
|
+
return ctx.stageId ?? ctx.instance.currentStageId;
|
|
227
|
+
case "stateRead":
|
|
228
|
+
return resolveStateRead(src, ctx);
|
|
229
|
+
case "effectOutput":
|
|
230
|
+
return resolveEffectOutput(src, ctx);
|
|
231
|
+
case "object":
|
|
232
|
+
return resolveObject(src, ctx);
|
|
233
|
+
case "row":
|
|
234
|
+
case "parentState":
|
|
235
|
+
return;
|
|
236
|
+
default:
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
function resolveStateRead(src, ctx) {
|
|
241
|
+
const value = readStateSlot(ctx.instance, src.scope, src.slotId, ctx.stageId, ctx.taskId);
|
|
242
|
+
return src.path !== void 0 ? getPath(value, src.path) : value;
|
|
243
|
+
}
|
|
244
|
+
function resolveEffectOutput(src, ctx) {
|
|
245
|
+
const entry = ctx.instance.effectsContext.find((e) => e.id === src.contextId);
|
|
246
|
+
if (entry === void 0) return null;
|
|
247
|
+
const value = src.path !== void 0 ? getPath(entry.value, src.path) : entry.value;
|
|
248
|
+
return value === void 0 ? null : value;
|
|
249
|
+
}
|
|
250
|
+
function resolveObject(src, ctx) {
|
|
251
|
+
const out = {};
|
|
252
|
+
for (const [field, fieldSrc] of Object.entries(src.fields))
|
|
253
|
+
out[field] = resolveSource(fieldSrc, ctx);
|
|
254
|
+
return out;
|
|
255
|
+
}
|
|
256
|
+
async function resolveBindings(args) {
|
|
257
|
+
const { bindings, staticInput, instance, now, params, actor } = args, resolved = {};
|
|
258
|
+
if (bindings) {
|
|
259
|
+
const ctx = {
|
|
260
|
+
instance,
|
|
261
|
+
now,
|
|
262
|
+
...params !== void 0 ? { params } : {},
|
|
263
|
+
...actor !== void 0 ? { actor } : {}
|
|
264
|
+
};
|
|
265
|
+
for (const [key, src] of Object.entries(bindings))
|
|
266
|
+
resolved[key] = resolveSource(src, ctx);
|
|
267
|
+
}
|
|
268
|
+
return { ...resolved, ...staticInput };
|
|
183
269
|
}
|
|
184
270
|
async function evaluateFilter(args) {
|
|
185
271
|
const { filter, definition, snapshot, params } = args;
|
|
@@ -235,130 +321,333 @@ function matchesParamType(type, value) {
|
|
|
235
321
|
function describeValue(value) {
|
|
236
322
|
return Array.isArray(value) ? "array" : value === null ? "null" : typeof value;
|
|
237
323
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
324
|
+
const GUARD_DOC_TYPE = "temp.system.guard";
|
|
325
|
+
class MutationGuardDeniedError extends Error {
|
|
326
|
+
denied;
|
|
327
|
+
documentId;
|
|
328
|
+
action;
|
|
329
|
+
constructor(args) {
|
|
330
|
+
const ids = args.denied.map((d) => d.guardId).join(", ");
|
|
331
|
+
super(`Mutation on "${args.documentId}" (${args.action}) denied by guard(s) [${ids}]`), this.name = "MutationGuardDeniedError", this.denied = args.denied, this.documentId = args.documentId, this.action = args.action;
|
|
243
332
|
}
|
|
244
|
-
return { docs, knownIds };
|
|
245
|
-
}
|
|
246
|
-
function restampForSnapshot(doc, gdrUri2, resource) {
|
|
247
|
-
return { ...rewriteRefsRecursive(doc, resource), _id: gdrUri2 };
|
|
248
333
|
}
|
|
249
|
-
function
|
|
250
|
-
|
|
251
|
-
return value.map((v2) => rewriteRefsRecursive(v2, resource));
|
|
252
|
-
if (value === null || typeof value != "object") return value;
|
|
253
|
-
const obj = value, out = {};
|
|
254
|
-
for (const [k, v2] of Object.entries(obj))
|
|
255
|
-
k === "_ref" && typeof v2 == "string" ? out[k] = v2.includes(":") ? v2 : gdrFromResource(resource, v2) : out[k] = rewriteRefsRecursive(v2, resource);
|
|
256
|
-
return out;
|
|
334
|
+
function lakeGuardId(args) {
|
|
335
|
+
return `${GUARD_DOC_TYPE}.${args.instanceDocId}.${args.stageId}.${args.index}`;
|
|
257
336
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
337
|
+
function compileGuard(args) {
|
|
338
|
+
return {
|
|
339
|
+
_id: args.id,
|
|
340
|
+
_type: GUARD_DOC_TYPE,
|
|
341
|
+
resourceType: args.resourceType,
|
|
342
|
+
resourceId: args.resourceId,
|
|
343
|
+
owner: args.owner,
|
|
344
|
+
sourceInstanceId: args.sourceInstanceId,
|
|
345
|
+
sourceDefinitionId: args.sourceDefinitionId,
|
|
346
|
+
sourceStageId: args.sourceStageId,
|
|
347
|
+
...args.name !== void 0 ? { name: args.name } : {},
|
|
348
|
+
...args.description !== void 0 ? { description: args.description } : {},
|
|
349
|
+
match: args.match,
|
|
350
|
+
predicate: args.predicate,
|
|
351
|
+
metadata: args.metadata
|
|
263
352
|
};
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
if (
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
353
|
+
}
|
|
354
|
+
function toGroqJsPredicate(predicate) {
|
|
355
|
+
return predicate.replace(new RegExp("(?<!\\$)\\bguard\\.", "g"), "$guard.").replace(new RegExp("(?<!\\$)\\bmutation\\.", "g"), "$mutation.");
|
|
356
|
+
}
|
|
357
|
+
function globMatch(pattern, value) {
|
|
358
|
+
if (!pattern.includes("*")) return pattern === value;
|
|
359
|
+
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
360
|
+
return new RegExp(`^${escaped}$`).test(value);
|
|
361
|
+
}
|
|
362
|
+
function guardMatches(guard, doc, action) {
|
|
363
|
+
const m = guard.match;
|
|
364
|
+
if (!m.actions.includes(action) || m.types && m.types.length > 0 && !(doc.type !== void 0 && m.types.includes(doc.type)))
|
|
365
|
+
return !1;
|
|
366
|
+
if (m.idRefs && m.idRefs.length > 0 || m.idPatterns && m.idPatterns.length > 0) {
|
|
367
|
+
const byRef = m.idRefs?.includes(doc.id) ?? !1, byPattern = m.idPatterns?.some((p) => globMatch(p, doc.id)) ?? !1;
|
|
368
|
+
if (!byRef && !byPattern) return !1;
|
|
275
369
|
}
|
|
276
|
-
return
|
|
370
|
+
return !0;
|
|
277
371
|
}
|
|
278
|
-
async function
|
|
279
|
-
|
|
372
|
+
async function evaluateMutationGuard(args) {
|
|
373
|
+
const { guard, context } = args;
|
|
374
|
+
if (guard.predicate === "") return !1;
|
|
375
|
+
const params = { guard, mutation: { action: context.action } };
|
|
280
376
|
try {
|
|
281
|
-
|
|
377
|
+
const tree = parse(toGroqJsPredicate(guard.predicate), { mode: "delta", params });
|
|
378
|
+
return await (await evaluate(tree, {
|
|
379
|
+
before: context.before,
|
|
380
|
+
after: context.after,
|
|
381
|
+
params,
|
|
382
|
+
...context.identity !== void 0 ? { identity: context.identity } : {}
|
|
383
|
+
})).get() === !0;
|
|
282
384
|
} catch {
|
|
283
|
-
|
|
284
|
-
return doc2 ? { doc: doc2, resource: defaultResource } : null;
|
|
385
|
+
return !1;
|
|
285
386
|
}
|
|
286
|
-
const doc = await clientForGdr(parsed).getDocument(parsed.documentId);
|
|
287
|
-
return doc ? { doc, resource: resourceFromParsed(parsed) } : null;
|
|
288
387
|
}
|
|
289
|
-
function
|
|
290
|
-
|
|
388
|
+
async function denyingGuards(args) {
|
|
389
|
+
const { guards, doc, context } = args, denied = [];
|
|
390
|
+
for (const guard of guards)
|
|
391
|
+
guardMatches(guard, doc, context.action) && (await evaluateMutationGuard({ guard, context }) || denied.push(guard));
|
|
392
|
+
return denied;
|
|
291
393
|
}
|
|
292
|
-
function
|
|
293
|
-
return
|
|
394
|
+
function resourceOf(p) {
|
|
395
|
+
return p.scheme === "dataset" ? { type: "dataset", id: `${p.projectId}.${p.dataset}` } : { type: p.scheme, id: p.resourceId ?? "" };
|
|
294
396
|
}
|
|
295
|
-
function
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
const v2 = s.value;
|
|
304
|
-
return Array.isArray(v2) ? v2.map(refId).filter((id) => id !== void 0) : [];
|
|
397
|
+
function resolveIdRefTarget(src, ctx) {
|
|
398
|
+
const value = resolveSource(src, ctx);
|
|
399
|
+
if (typeof value == "string" && isGdrUri(value))
|
|
400
|
+
return { parsed: parseGdr(value) };
|
|
401
|
+
if (value && typeof value == "object") {
|
|
402
|
+
const v2 = value;
|
|
403
|
+
if (typeof v2.id == "string" && isGdrUri(v2.id))
|
|
404
|
+
return typeof v2.type == "string" ? { parsed: parseGdr(v2.id), type: v2.type } : { parsed: parseGdr(v2.id) };
|
|
305
405
|
}
|
|
306
|
-
return
|
|
307
|
-
}
|
|
308
|
-
function refId(ref) {
|
|
309
|
-
return ref && typeof ref.id == "string" ? ref.id : void 0;
|
|
406
|
+
return null;
|
|
310
407
|
}
|
|
311
|
-
function
|
|
312
|
-
|
|
313
|
-
for (const
|
|
314
|
-
|
|
315
|
-
|
|
408
|
+
function resolveIdRefTargets(idRefs, ctx) {
|
|
409
|
+
const targets = [];
|
|
410
|
+
for (const src of idRefs ?? []) {
|
|
411
|
+
const target = resolveIdRefTarget(src, ctx);
|
|
412
|
+
if (target === null) return null;
|
|
413
|
+
targets.push(target);
|
|
316
414
|
}
|
|
317
|
-
return
|
|
415
|
+
return targets.length === 0 ? null : targets;
|
|
318
416
|
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
417
|
+
function assertSingleResource(targets) {
|
|
418
|
+
const resource = resourceOf(targets[0].parsed);
|
|
419
|
+
for (const g of targets) {
|
|
420
|
+
const r = resourceOf(g.parsed);
|
|
421
|
+
if (r.type !== resource.type || r.id !== resource.id)
|
|
422
|
+
throw new Error(
|
|
423
|
+
`Guard targets span multiple resources (${resource.type}:${resource.id} vs ${r.type}:${r.id}); a guard is single-resource.`
|
|
424
|
+
);
|
|
326
425
|
}
|
|
426
|
+
return resource;
|
|
327
427
|
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
)
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
)
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
428
|
+
function bareIdRefs(targets) {
|
|
429
|
+
return targets.flatMap((g) => [g.parsed.documentId, `drafts.${g.parsed.documentId}`]);
|
|
430
|
+
}
|
|
431
|
+
function resolveMatchTypes(targets, authorTypes) {
|
|
432
|
+
if (authorTypes !== void 0) return authorTypes;
|
|
433
|
+
const inferred = [...new Set(targets.map((g) => g.type).filter((t) => !!t))];
|
|
434
|
+
return inferred.length > 0 ? inferred : void 0;
|
|
435
|
+
}
|
|
436
|
+
function resolveMetadata(metadata, ctx) {
|
|
437
|
+
const out = {};
|
|
438
|
+
for (const [k, src] of Object.entries(metadata ?? {}))
|
|
439
|
+
out[k] = resolveSource(src, ctx);
|
|
440
|
+
return out;
|
|
441
|
+
}
|
|
442
|
+
const GUARD_OWNER = "robot:workflow-engine", GUARD_LIFTED_PREDICATE = "true";
|
|
443
|
+
function resolveGuard(guard, index, instance, stageId, now) {
|
|
444
|
+
const ctx = { instance, stageId, now }, targets = resolveIdRefTargets(guard.match.idRefs, ctx);
|
|
445
|
+
if (targets === null) return null;
|
|
446
|
+
const resource = assertSingleResource(targets), types = resolveMatchTypes(targets, guard.match.types);
|
|
447
|
+
return { doc: compileGuard({
|
|
448
|
+
id: lakeGuardId({ instanceDocId: instance._id, stageId, index }),
|
|
449
|
+
resourceType: resource.type,
|
|
450
|
+
resourceId: resource.id,
|
|
451
|
+
owner: GUARD_OWNER,
|
|
452
|
+
sourceInstanceId: instance._id,
|
|
453
|
+
sourceDefinitionId: instance.workflowId,
|
|
454
|
+
sourceStageId: stageId,
|
|
455
|
+
...guard.name !== void 0 ? { name: guard.name } : {},
|
|
456
|
+
...guard.description !== void 0 ? { description: guard.description } : {},
|
|
457
|
+
match: {
|
|
458
|
+
...types !== void 0 ? { types } : {},
|
|
459
|
+
idRefs: bareIdRefs(targets),
|
|
460
|
+
...guard.match.idPatterns !== void 0 ? { idPatterns: guard.match.idPatterns } : {},
|
|
461
|
+
actions: guard.match.actions
|
|
462
|
+
},
|
|
463
|
+
predicate: guard.predicate ?? "",
|
|
464
|
+
metadata: resolveMetadata(guard.metadata, ctx)
|
|
465
|
+
}), routeGdr: targets[0].parsed };
|
|
466
|
+
}
|
|
467
|
+
async function upsertGuard(client, doc) {
|
|
468
|
+
if (!await client.getDocument(doc._id)) {
|
|
469
|
+
await client.create(doc);
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
const { _id, _type, _rev, _createdAt, _updatedAt, ...body } = doc;
|
|
473
|
+
await client.patch(doc._id).set(body).commit();
|
|
474
|
+
}
|
|
475
|
+
function resolvedStageGuards(args) {
|
|
476
|
+
const stage = args.definition.stages.find((s) => s.id === args.stageId), out = [];
|
|
477
|
+
for (const [index, guard] of (stage?.guards ?? []).entries()) {
|
|
478
|
+
const resolved = resolveGuard(guard, index, args.instance, args.stageId, args.now);
|
|
479
|
+
resolved !== null && out.push({ client: args.clientForGdr(resolved.routeGdr), doc: resolved.doc });
|
|
480
|
+
}
|
|
481
|
+
return out;
|
|
482
|
+
}
|
|
483
|
+
async function committedStageId(args) {
|
|
484
|
+
return (await args.client.getDocument(args.instance._id))?.currentStageId;
|
|
485
|
+
}
|
|
486
|
+
async function deployStageGuards(args) {
|
|
487
|
+
if (await committedStageId(args) === args.stageId)
|
|
488
|
+
for (const { client, doc } of resolvedStageGuards(args))
|
|
489
|
+
await upsertGuard(client, doc);
|
|
490
|
+
}
|
|
491
|
+
async function retractStageGuards(args) {
|
|
492
|
+
const live = await committedStageId(args);
|
|
493
|
+
if (!(live === void 0 || live === args.stageId))
|
|
494
|
+
for (const { client, doc } of resolvedStageGuards(args))
|
|
495
|
+
await client.getDocument(doc._id) && await client.patch(doc._id).set({ predicate: GUARD_LIFTED_PREDICATE }).commit();
|
|
496
|
+
}
|
|
497
|
+
function randomKey(length = 12) {
|
|
498
|
+
const bytes = new Uint8Array(length);
|
|
499
|
+
return globalThis.crypto.getRandomValues(bytes), [...bytes].map((b) => b.toString(16).padStart(2, "0")).join("").slice(0, length);
|
|
500
|
+
}
|
|
501
|
+
function buildSnapshot(args) {
|
|
502
|
+
const docs = [], knownIds = /* @__PURE__ */ new Set();
|
|
503
|
+
for (const { doc, resource } of args.docs) {
|
|
504
|
+
const uri = gdrFromResource(resource, doc._id), restamped = restampForSnapshot(doc, uri, resource);
|
|
505
|
+
docs.push(restamped), knownIds.add(uri);
|
|
506
|
+
}
|
|
507
|
+
return { docs, knownIds };
|
|
508
|
+
}
|
|
509
|
+
function restampForSnapshot(doc, gdrUri2, resource) {
|
|
510
|
+
return { ...rewriteRefsRecursive(doc, resource), _id: gdrUri2 };
|
|
511
|
+
}
|
|
512
|
+
function rewriteRefsRecursive(value, resource) {
|
|
513
|
+
if (Array.isArray(value))
|
|
514
|
+
return value.map((v2) => rewriteRefsRecursive(v2, resource));
|
|
515
|
+
if (value === null || typeof value != "object") return value;
|
|
516
|
+
const obj = value, out = {};
|
|
517
|
+
for (const [k, v2] of Object.entries(obj))
|
|
518
|
+
k === "_ref" && typeof v2 == "string" ? out[k] = v2.includes(":") ? v2 : gdrFromResource(resource, v2) : out[k] = rewriteRefsRecursive(v2, resource);
|
|
519
|
+
return out;
|
|
520
|
+
}
|
|
521
|
+
function findOpenStageEntry(host) {
|
|
522
|
+
return host.stages.find((s) => s.id === host.currentStageId && s.exitedAt === void 0);
|
|
523
|
+
}
|
|
524
|
+
function collectWatchRefs(instance) {
|
|
525
|
+
const stage = findOpenStageEntry(instance);
|
|
526
|
+
return [
|
|
527
|
+
gdrRef(instance.workflowResource, instance._id, instance._type),
|
|
528
|
+
...instance.ancestors,
|
|
529
|
+
...slotDocRefs(instance.state),
|
|
530
|
+
...slotDocRefs(stage?.state),
|
|
531
|
+
...slotReleaseRefs(instance.state),
|
|
532
|
+
...slotReleaseRefs(stage?.state)
|
|
533
|
+
];
|
|
534
|
+
}
|
|
535
|
+
function readsRaw(ref) {
|
|
536
|
+
return ref.type === "workflow.instance" || ref.type === "system.release";
|
|
537
|
+
}
|
|
538
|
+
function contentReleaseName(args) {
|
|
539
|
+
const { ref, perspective } = args;
|
|
540
|
+
if (!readsRaw(ref) && Array.isArray(perspective))
|
|
541
|
+
return perspective.find((entry) => entry !== "drafts" && entry !== "published" && entry !== "raw");
|
|
542
|
+
}
|
|
543
|
+
function subscriptionDocumentsForInstance(instance) {
|
|
544
|
+
const seen = /* @__PURE__ */ new Set(), documents = [];
|
|
545
|
+
for (const ref of collectWatchRefs(instance))
|
|
546
|
+
!isGdrUri(ref.id) || seen.has(ref.id) || (seen.add(ref.id), documents.push({ ...parseGdr(ref.id), globalDocumentId: ref.id, type: ref.type }));
|
|
547
|
+
return {
|
|
548
|
+
documents,
|
|
549
|
+
...instance.perspective !== void 0 ? { perspective: instance.perspective } : {}
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
function slotDocRefs(slots) {
|
|
553
|
+
return slotEntries(slots).flatMap((slot) => slot._type === "workflow.state.doc.ref" ? isGdr(slot.value) ? [slot.value] : [] : slot._type === "workflow.state.doc.refs" ? Array.isArray(slot.value) ? slot.value.filter(isGdr) : [] : []);
|
|
554
|
+
}
|
|
555
|
+
function slotReleaseRefs(slots) {
|
|
556
|
+
return slotEntries(slots).flatMap(
|
|
557
|
+
(slot) => slot._type === "workflow.state.release.ref" && isGdr(slot.value) ? [slot.value] : []
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
function slotEntries(slots) {
|
|
561
|
+
return Array.isArray(slots) ? slots.filter(
|
|
562
|
+
(slot) => !!slot && typeof slot == "object"
|
|
563
|
+
) : [];
|
|
564
|
+
}
|
|
565
|
+
async function hydrateSnapshot(args) {
|
|
566
|
+
const { client, clientForGdr, instance, overlay } = args, loaded = [], visited = /* @__PURE__ */ new Set(), loadInto = async (uri, perspective) => {
|
|
567
|
+
if (visited.has(uri)) return;
|
|
568
|
+
const held = overlay?.get(uri);
|
|
569
|
+
if (held !== void 0) {
|
|
570
|
+
loaded.push(held), visited.add(uri);
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
const fetched = await loadByGdr(
|
|
574
|
+
client,
|
|
575
|
+
clientForGdr,
|
|
576
|
+
instance.workflowResource,
|
|
577
|
+
uri,
|
|
578
|
+
perspective
|
|
579
|
+
);
|
|
580
|
+
fetched && (loaded.push(fetched), visited.add(uri));
|
|
581
|
+
};
|
|
582
|
+
loaded.push({ doc: instance, resource: instance.workflowResource }), visited.add(selfGdr(instance));
|
|
583
|
+
for (const ref of collectWatchRefs(instance))
|
|
584
|
+
await loadInto(ref.id, readsRaw(ref) ? void 0 : instance.perspective);
|
|
585
|
+
return buildSnapshot({ docs: loaded });
|
|
586
|
+
}
|
|
587
|
+
async function loadByGdr(defaultClient, clientForGdr, defaultResource, uri, perspective) {
|
|
588
|
+
let parsed;
|
|
589
|
+
try {
|
|
590
|
+
parsed = parseGdr(uri);
|
|
591
|
+
} catch {
|
|
592
|
+
const doc2 = await readDoc(defaultClient, uri, perspective);
|
|
593
|
+
return doc2 ? { doc: doc2, resource: defaultResource } : null;
|
|
594
|
+
}
|
|
595
|
+
const routed = clientForGdr(parsed), doc = await readDoc(routed, parsed.documentId, perspective);
|
|
596
|
+
return doc ? { doc, resource: resourceFromParsed(parsed) } : null;
|
|
597
|
+
}
|
|
598
|
+
async function readDoc(client, id, perspective) {
|
|
599
|
+
return perspective === void 0 ? await client.getDocument(id) ?? null : await client.fetch("*[_id == $id][0]", { id }, { perspective }) ?? null;
|
|
600
|
+
}
|
|
601
|
+
function resourceFromParsed(parsed) {
|
|
602
|
+
return parsed.scheme === "dataset" ? { type: "dataset", id: `${parsed.projectId}.${parsed.dataset}` } : { type: parsed.scheme, id: parsed.resourceId ?? "" };
|
|
603
|
+
}
|
|
604
|
+
function collectSlotDocUris(resolvedStateSlots) {
|
|
605
|
+
return slotDocRefs(resolvedStateSlots).map((ref) => ref.id);
|
|
606
|
+
}
|
|
607
|
+
const WORKFLOW_INSTANCE_TYPE = "workflow.instance";
|
|
608
|
+
class SlotValueShapeError extends Error {
|
|
609
|
+
slotType;
|
|
610
|
+
slotId;
|
|
611
|
+
issues;
|
|
612
|
+
constructor(args) {
|
|
613
|
+
const issueText = args.issues.join("; ");
|
|
614
|
+
super(`Slot ${args.mode} shape invalid for "${args.slotId}" (${args.slotType}): ${issueText}`), this.name = "SlotValueShapeError", this.slotType = args.slotType, this.slotId = args.slotId, this.issues = args.issues;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
const GdrShape = v.looseObject({
|
|
618
|
+
id: v.pipe(
|
|
619
|
+
v.string(),
|
|
620
|
+
v.check((s) => isGdrUri(s), "must be a GDR URI")
|
|
621
|
+
),
|
|
622
|
+
type: v.pipe(v.string(), v.minLength(1))
|
|
623
|
+
}), ReleaseRefShape = v.looseObject({
|
|
624
|
+
id: v.pipe(
|
|
625
|
+
v.string(),
|
|
626
|
+
v.check((s) => isGdrUri(s), "must be a GDR URI")
|
|
627
|
+
),
|
|
628
|
+
type: v.literal("system.release"),
|
|
629
|
+
releaseName: v.pipe(v.string(), v.minLength(1))
|
|
630
|
+
}), ActorShape = v.looseObject({
|
|
631
|
+
kind: v.picklist(["user", "ai", "system"]),
|
|
632
|
+
id: v.pipe(v.string(), v.minLength(1)),
|
|
633
|
+
roles: v.optional(v.array(v.string())),
|
|
634
|
+
onBehalfOf: v.optional(v.string())
|
|
635
|
+
}), AssigneeShape = v.union([
|
|
636
|
+
v.looseObject({ kind: v.literal("user"), id: v.pipe(v.string(), v.minLength(1)) }),
|
|
637
|
+
v.looseObject({ kind: v.literal("role"), role: v.pipe(v.string(), v.minLength(1)) })
|
|
638
|
+
]), ChecklistItemShape = v.looseObject({
|
|
639
|
+
label: v.pipe(v.string(), v.minLength(1)),
|
|
640
|
+
done: v.boolean(),
|
|
641
|
+
doneBy: v.optional(v.string()),
|
|
642
|
+
doneAt: v.optional(v.string()),
|
|
643
|
+
_key: v.optional(v.pipe(v.string(), v.minLength(1)))
|
|
644
|
+
}), NoteItemShape = v.pipe(
|
|
645
|
+
v.looseObject({
|
|
646
|
+
_key: v.optional(v.pipe(v.string(), v.minLength(1)))
|
|
647
|
+
}),
|
|
648
|
+
v.check(
|
|
649
|
+
(val) => typeof val == "object" && val !== null && !Array.isArray(val),
|
|
650
|
+
"must be an object"
|
|
362
651
|
)
|
|
363
652
|
), NullableString = v.union([v.null(), v.string()]), NullableNumber = v.union([v.null(), v.number()]), NullableBoolean = v.union([v.null(), v.boolean()]), NullableDateTime = v.union([
|
|
364
653
|
v.null(),
|
|
@@ -582,7 +871,12 @@ async function loadContext(client, instanceId, options) {
|
|
|
582
871
|
const instance = await client.getDocument(instanceId);
|
|
583
872
|
if (!instance)
|
|
584
873
|
throw new Error(`Workflow instance ${instanceId} not found`);
|
|
585
|
-
const definition = JSON.parse(instance.definitionSnapshot), clientForGdr = options?.clientForGdr ?? (() => client), clock = options?.clock ?? wallClock, snapshot = await hydrateSnapshot({
|
|
874
|
+
const definition = JSON.parse(instance.definitionSnapshot), clientForGdr = options?.clientForGdr ?? (() => client), clock = options?.clock ?? wallClock, snapshot = await hydrateSnapshot({
|
|
875
|
+
client,
|
|
876
|
+
clientForGdr,
|
|
877
|
+
instance,
|
|
878
|
+
...options?.overlay !== void 0 ? { overlay: options.overlay } : {}
|
|
879
|
+
});
|
|
586
880
|
return { client, clientForGdr, clock, now: clock(), instance, definition, snapshot };
|
|
587
881
|
}
|
|
588
882
|
function ctxEvaluateFilter(ctx, filter) {
|
|
@@ -602,7 +896,7 @@ async function buildEngineContext(args) {
|
|
|
602
896
|
now: clock(),
|
|
603
897
|
instance,
|
|
604
898
|
definition,
|
|
605
|
-
snapshot: await hydrateSnapshot({ client, clientForGdr, instance
|
|
899
|
+
snapshot: await hydrateSnapshot({ client, clientForGdr, instance })
|
|
606
900
|
};
|
|
607
901
|
}
|
|
608
902
|
function findStage(definition, stageId) {
|
|
@@ -651,297 +945,281 @@ async function resolveTaskStateSlots(args) {
|
|
|
651
945
|
randomKey
|
|
652
946
|
});
|
|
653
947
|
}
|
|
654
|
-
function
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
const stageEntry = instance.stages.find(
|
|
658
|
-
(s) => s.id === (stageId ?? instance.currentStageId) && s.exitedAt === void 0
|
|
659
|
-
);
|
|
660
|
-
if (stageEntry === void 0) return;
|
|
661
|
-
if (scope === "stage")
|
|
662
|
-
return slotValue$1(stageEntry.state?.find((s) => s.id === slotId));
|
|
663
|
-
const taskEntry = stageEntry.tasks.find((t) => t.id === taskId);
|
|
664
|
-
return slotValue$1(taskEntry?.state?.find((s) => s.id === slotId));
|
|
665
|
-
}
|
|
666
|
-
function slotValue$1(slot) {
|
|
667
|
-
if (slot != null && typeof slot == "object")
|
|
668
|
-
return slot.value;
|
|
948
|
+
function effectsContextEntry(id, value) {
|
|
949
|
+
const _key = randomKey();
|
|
950
|
+
return typeof value == "string" ? { _key, _type: "effectsContext.string", id, value } : typeof value == "number" ? { _key, _type: "effectsContext.number", id, value } : typeof value == "boolean" ? { _key, _type: "effectsContext.boolean", id, value } : { _key, _type: "effectsContext.ref", id, value };
|
|
669
951
|
}
|
|
670
|
-
function
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
952
|
+
function buildInstanceBase(args) {
|
|
953
|
+
const { id, now, actor, perspective } = args;
|
|
954
|
+
return {
|
|
955
|
+
_id: id,
|
|
956
|
+
_type: WORKFLOW_INSTANCE_TYPE,
|
|
957
|
+
_rev: "",
|
|
958
|
+
_createdAt: now,
|
|
959
|
+
_updatedAt: now,
|
|
960
|
+
tags: args.tags,
|
|
961
|
+
workflowResource: args.workflowResource,
|
|
962
|
+
workflowId: args.workflowId,
|
|
963
|
+
pinnedVersion: args.pinnedVersion,
|
|
964
|
+
definitionSnapshot: JSON.stringify(args.definition),
|
|
965
|
+
state: args.state,
|
|
966
|
+
effectsContext: args.effectsContext,
|
|
967
|
+
ancestors: args.ancestors,
|
|
968
|
+
...perspective !== void 0 ? { perspective } : {},
|
|
969
|
+
currentStageId: args.initialStageId,
|
|
970
|
+
stages: [],
|
|
971
|
+
pendingEffects: [],
|
|
972
|
+
effectHistory: [],
|
|
973
|
+
history: [
|
|
974
|
+
{
|
|
975
|
+
_key: randomKey(),
|
|
976
|
+
_type: "workflow.history.stageEntered",
|
|
977
|
+
at: now,
|
|
978
|
+
stageId: args.initialStageId,
|
|
979
|
+
...actor !== void 0 ? { actor } : {}
|
|
980
|
+
}
|
|
981
|
+
],
|
|
982
|
+
startedAt: now,
|
|
983
|
+
lastChangedAt: now
|
|
984
|
+
};
|
|
683
985
|
}
|
|
684
|
-
function
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
const value = readStateSlot(ctx.instance, src.scope, src.slotId, ctx.stageId, ctx.taskId);
|
|
707
|
-
return src.path !== void 0 ? getPath(value, src.path) : value;
|
|
708
|
-
}
|
|
709
|
-
function resolveEffectOutput(src, ctx) {
|
|
710
|
-
const entry = ctx.instance.effectsContext.find((e) => e.id === src.contextId);
|
|
711
|
-
if (entry === void 0) return null;
|
|
712
|
-
const value = src.path !== void 0 ? getPath(entry.value, src.path) : entry.value;
|
|
713
|
-
return value === void 0 ? null : value;
|
|
714
|
-
}
|
|
715
|
-
function resolveObject(src, ctx) {
|
|
716
|
-
const out = {};
|
|
717
|
-
for (const [field, fieldSrc] of Object.entries(src.fields))
|
|
718
|
-
out[field] = resolveSource(fieldSrc, ctx);
|
|
719
|
-
return out;
|
|
720
|
-
}
|
|
721
|
-
async function resolveBindings(args) {
|
|
722
|
-
const { bindings, staticInput, instance, now, params, actor } = args, resolved = {};
|
|
723
|
-
if (bindings) {
|
|
724
|
-
const ctx = {
|
|
725
|
-
instance,
|
|
726
|
-
now,
|
|
727
|
-
...params !== void 0 ? { params } : {},
|
|
728
|
-
...actor !== void 0 ? { actor } : {}
|
|
729
|
-
};
|
|
730
|
-
for (const [key, src] of Object.entries(bindings))
|
|
731
|
-
resolved[key] = resolveSource(src, ctx);
|
|
732
|
-
}
|
|
733
|
-
return { ...resolved, ...staticInput };
|
|
734
|
-
}
|
|
735
|
-
function effectsContextEntry(id, value) {
|
|
736
|
-
const _key = randomKey();
|
|
737
|
-
return typeof value == "string" ? { _key, _type: "effectsContext.string", id, value } : typeof value == "number" ? { _key, _type: "effectsContext.number", id, value } : typeof value == "boolean" ? { _key, _type: "effectsContext.boolean", id, value } : { _key, _type: "effectsContext.ref", id, value };
|
|
986
|
+
function startMutation(instance) {
|
|
987
|
+
return {
|
|
988
|
+
currentStageId: instance.currentStageId,
|
|
989
|
+
// Deep-ish copy. Per-scope state slot arrays need their own copies
|
|
990
|
+
// so ops can mutate without leaking into the source.
|
|
991
|
+
state: (instance.state ?? []).map((s) => ({ ...s })),
|
|
992
|
+
stages: instance.stages.map((s) => ({
|
|
993
|
+
...s,
|
|
994
|
+
state: (s.state ?? []).map((slot) => ({ ...slot })),
|
|
995
|
+
tasks: s.tasks.map((t) => ({
|
|
996
|
+
...t,
|
|
997
|
+
...t.state !== void 0 ? { state: t.state.map((slot) => ({ ...slot })) } : {}
|
|
998
|
+
}))
|
|
999
|
+
})),
|
|
1000
|
+
pendingEffects: [...instance.pendingEffects],
|
|
1001
|
+
effectHistory: [...instance.effectHistory],
|
|
1002
|
+
effectsContext: [...instance.effectsContext],
|
|
1003
|
+
history: [...instance.history],
|
|
1004
|
+
lastChangedAt: instance.lastChangedAt,
|
|
1005
|
+
...instance.completedAt !== void 0 ? { completedAt: instance.completedAt } : {},
|
|
1006
|
+
pendingCreates: []
|
|
1007
|
+
};
|
|
738
1008
|
}
|
|
739
|
-
function
|
|
740
|
-
const { id, now, actor, perspective } = args;
|
|
1009
|
+
function taskStatusChangedEntry(args) {
|
|
741
1010
|
return {
|
|
742
|
-
|
|
743
|
-
_type: "workflow.
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
pinnedVersion: args.pinnedVersion,
|
|
751
|
-
definitionSnapshot: JSON.stringify(args.definition),
|
|
752
|
-
state: args.state,
|
|
753
|
-
effectsContext: args.effectsContext,
|
|
754
|
-
ancestors: args.ancestors,
|
|
755
|
-
...perspective !== void 0 ? { perspective } : {},
|
|
756
|
-
currentStageId: args.initialStageId,
|
|
757
|
-
stages: [],
|
|
758
|
-
pendingEffects: [],
|
|
759
|
-
effectHistory: [],
|
|
760
|
-
history: [
|
|
761
|
-
{
|
|
762
|
-
_key: randomKey(),
|
|
763
|
-
_type: "workflow.history.stageEntered",
|
|
764
|
-
at: now,
|
|
765
|
-
stageId: args.initialStageId,
|
|
766
|
-
...actor !== void 0 ? { actor } : {}
|
|
767
|
-
}
|
|
768
|
-
],
|
|
769
|
-
startedAt: now,
|
|
770
|
-
lastChangedAt: now
|
|
1011
|
+
_key: randomKey(),
|
|
1012
|
+
_type: "workflow.history.taskStatusChanged",
|
|
1013
|
+
at: args.at,
|
|
1014
|
+
stageId: args.stageId,
|
|
1015
|
+
taskId: args.taskId,
|
|
1016
|
+
from: args.from,
|
|
1017
|
+
to: args.to,
|
|
1018
|
+
...args.actor !== void 0 ? { actor: args.actor } : {}
|
|
771
1019
|
};
|
|
772
1020
|
}
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
1021
|
+
function stageTransitionHistory(args) {
|
|
1022
|
+
const { fromStageId, toStageId, at, transitionId, actor, via, reason } = args, shared = {
|
|
1023
|
+
at,
|
|
1024
|
+
...transitionId !== void 0 ? { transitionId } : {},
|
|
1025
|
+
...actor !== void 0 ? { actor } : {},
|
|
1026
|
+
...via !== void 0 ? { via } : {},
|
|
1027
|
+
...reason !== void 0 ? { reason } : {}
|
|
1028
|
+
};
|
|
1029
|
+
return [
|
|
1030
|
+
{
|
|
1031
|
+
_key: randomKey(),
|
|
1032
|
+
_type: "workflow.history.stageExited",
|
|
1033
|
+
stageId: fromStageId,
|
|
1034
|
+
toStageId,
|
|
1035
|
+
...shared
|
|
1036
|
+
},
|
|
1037
|
+
{
|
|
1038
|
+
_key: randomKey(),
|
|
1039
|
+
_type: "workflow.history.transitionFired",
|
|
1040
|
+
fromStageId,
|
|
1041
|
+
toStageId,
|
|
1042
|
+
...shared
|
|
1043
|
+
},
|
|
1044
|
+
{
|
|
1045
|
+
_key: randomKey(),
|
|
1046
|
+
_type: "workflow.history.stageEntered",
|
|
1047
|
+
stageId: toStageId,
|
|
1048
|
+
fromStageId,
|
|
1049
|
+
...shared
|
|
1050
|
+
}
|
|
1051
|
+
];
|
|
782
1052
|
}
|
|
783
|
-
function
|
|
784
|
-
|
|
1053
|
+
function instanceStateFields(src) {
|
|
1054
|
+
const fields = {
|
|
1055
|
+
currentStageId: src.currentStageId,
|
|
1056
|
+
state: src.state,
|
|
1057
|
+
stages: src.stages,
|
|
1058
|
+
pendingEffects: src.pendingEffects,
|
|
1059
|
+
effectHistory: src.effectHistory,
|
|
1060
|
+
effectsContext: src.effectsContext,
|
|
1061
|
+
history: src.history
|
|
1062
|
+
};
|
|
1063
|
+
return src.completedAt !== void 0 && (fields.completedAt = src.completedAt), fields;
|
|
785
1064
|
}
|
|
786
|
-
function
|
|
1065
|
+
function materializeInstance(base, mutation) {
|
|
787
1066
|
return {
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
owner: args.owner,
|
|
793
|
-
sourceInstanceId: args.sourceInstanceId,
|
|
794
|
-
sourceDefinitionId: args.sourceDefinitionId,
|
|
795
|
-
sourceStageId: args.sourceStageId,
|
|
796
|
-
...args.name !== void 0 ? { name: args.name } : {},
|
|
797
|
-
...args.description !== void 0 ? { description: args.description } : {},
|
|
798
|
-
match: args.match,
|
|
799
|
-
predicate: args.predicate,
|
|
800
|
-
metadata: args.metadata
|
|
1067
|
+
...base,
|
|
1068
|
+
currentStageId: mutation.currentStageId,
|
|
1069
|
+
state: mutation.state,
|
|
1070
|
+
stages: mutation.stages
|
|
801
1071
|
};
|
|
802
1072
|
}
|
|
803
|
-
function
|
|
804
|
-
|
|
1073
|
+
async function persist(ctx, mutation) {
|
|
1074
|
+
const set = {
|
|
1075
|
+
...instanceStateFields(mutation),
|
|
1076
|
+
lastChangedAt: ctx.now
|
|
1077
|
+
}, pendingCreates = mutation.pendingCreates;
|
|
1078
|
+
if (pendingCreates.length === 0)
|
|
1079
|
+
return ctx.client.patch(ctx.instance._id).set(set).ifRevisionId(ctx.instance._rev).commit();
|
|
1080
|
+
const tx = ctx.client.transaction();
|
|
1081
|
+
for (const body of pendingCreates) tx.create(body);
|
|
1082
|
+
tx.patch(ctx.client.patch(ctx.instance._id).set(set).ifRevisionId(ctx.instance._rev)), await tx.commit(), mutation.pendingCreates = [];
|
|
1083
|
+
const actorForPriming = void 0;
|
|
1084
|
+
for (const body of pendingCreates)
|
|
1085
|
+
await primeInitialStage(ctx.client, body._id, actorForPriming, ctx.clientForGdr, ctx.clock), await cascadeAutoTransitions(ctx.client, body._id, actorForPriming, ctx.clientForGdr, ctx.clock), await propagateToAncestors(ctx.client, body._id, actorForPriming, ctx.clientForGdr, ctx.clock);
|
|
1086
|
+
const reloaded = await ctx.client.getDocument(ctx.instance._id);
|
|
1087
|
+
if (!reloaded)
|
|
1088
|
+
throw new Error(`Instance ${ctx.instance._id} disappeared after transaction commit`);
|
|
1089
|
+
return reloaded;
|
|
805
1090
|
}
|
|
806
|
-
function
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
1091
|
+
function currentStageEntry(mutation) {
|
|
1092
|
+
const entry = findOpenStageEntry(mutation);
|
|
1093
|
+
if (entry === void 0)
|
|
1094
|
+
throw new Error(
|
|
1095
|
+
`Mutation invariant broken: no current (un-exited) StageEntry for currentStageId "${mutation.currentStageId}"`
|
|
1096
|
+
);
|
|
1097
|
+
return entry;
|
|
810
1098
|
}
|
|
811
|
-
function
|
|
812
|
-
|
|
813
|
-
if (!m.actions.includes(action) || m.types && m.types.length > 0 && !(doc.type !== void 0 && m.types.includes(doc.type)))
|
|
814
|
-
return !1;
|
|
815
|
-
if (m.idRefs && m.idRefs.length > 0 || m.idPatterns && m.idPatterns.length > 0) {
|
|
816
|
-
const byRef = m.idRefs?.includes(doc.id) ?? !1, byPattern = m.idPatterns?.some((p) => globMatch(p, doc.id)) ?? !1;
|
|
817
|
-
if (!byRef && !byPattern) return !1;
|
|
818
|
-
}
|
|
819
|
-
return !0;
|
|
1099
|
+
function currentTasks(mutation) {
|
|
1100
|
+
return currentStageEntry(mutation).tasks;
|
|
820
1101
|
}
|
|
821
|
-
|
|
822
|
-
const
|
|
823
|
-
if (
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
before: context.before,
|
|
829
|
-
after: context.after,
|
|
830
|
-
params,
|
|
831
|
-
...context.identity !== void 0 ? { identity: context.identity } : {}
|
|
832
|
-
})).get() === !0;
|
|
833
|
-
} catch {
|
|
834
|
-
return !1;
|
|
835
|
-
}
|
|
1102
|
+
function findTaskInCurrentStage(ctx, taskId) {
|
|
1103
|
+
const stage = findStage(ctx.definition, ctx.instance.currentStageId), task = (stage.tasks ?? []).find((t) => t.id === taskId);
|
|
1104
|
+
if (task === void 0)
|
|
1105
|
+
throw new Error(
|
|
1106
|
+
`Task "${taskId}" not found in current stage "${stage.id}" of ${ctx.definition.workflowId}`
|
|
1107
|
+
);
|
|
1108
|
+
return { stage, task };
|
|
836
1109
|
}
|
|
837
|
-
|
|
838
|
-
const
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
return
|
|
1110
|
+
function requireMutationTaskEntry(mutation, taskId) {
|
|
1111
|
+
const mutEntry = currentTasks(mutation).find((t) => t.id === taskId);
|
|
1112
|
+
if (mutEntry === void 0)
|
|
1113
|
+
throw new Error(`Task "${taskId}" disappeared from mutation copy \u2014 invariant broken`);
|
|
1114
|
+
return mutEntry;
|
|
842
1115
|
}
|
|
843
|
-
function
|
|
844
|
-
return
|
|
1116
|
+
function findCurrentStageEntry(instance) {
|
|
1117
|
+
return findOpenStageEntry(instance);
|
|
845
1118
|
}
|
|
846
|
-
function
|
|
847
|
-
|
|
848
|
-
if (typeof value == "string" && isGdrUri(value))
|
|
849
|
-
return { parsed: parseGdr(value) };
|
|
850
|
-
if (value && typeof value == "object") {
|
|
851
|
-
const v2 = value;
|
|
852
|
-
if (typeof v2.id == "string" && isGdrUri(v2.id))
|
|
853
|
-
return typeof v2.type == "string" ? { parsed: parseGdr(v2.id), type: v2.type } : { parsed: parseGdr(v2.id) };
|
|
854
|
-
}
|
|
855
|
-
return null;
|
|
856
|
-
}
|
|
857
|
-
function resolveIdRefTargets(idRefs, ctx) {
|
|
858
|
-
const targets = [];
|
|
859
|
-
for (const src of idRefs ?? []) {
|
|
860
|
-
const target = resolveIdRefTarget(src, ctx);
|
|
861
|
-
if (target === null) return null;
|
|
862
|
-
targets.push(target);
|
|
863
|
-
}
|
|
864
|
-
return targets.length === 0 ? null : targets;
|
|
865
|
-
}
|
|
866
|
-
function assertSingleResource(targets) {
|
|
867
|
-
const resource = resourceOf(targets[0].parsed);
|
|
868
|
-
for (const g of targets) {
|
|
869
|
-
const r = resourceOf(g.parsed);
|
|
870
|
-
if (r.type !== resource.type || r.id !== resource.id)
|
|
871
|
-
throw new Error(
|
|
872
|
-
`Guard targets span multiple resources (${resource.type}:${resource.id} vs ${r.type}:${r.id}); a guard is single-resource.`
|
|
873
|
-
);
|
|
874
|
-
}
|
|
875
|
-
return resource;
|
|
876
|
-
}
|
|
877
|
-
function bareIdRefs(targets) {
|
|
878
|
-
return targets.flatMap((g) => [g.parsed.documentId, `drafts.${g.parsed.documentId}`]);
|
|
879
|
-
}
|
|
880
|
-
function resolveMatchTypes(targets, authorTypes) {
|
|
881
|
-
if (authorTypes !== void 0) return authorTypes;
|
|
882
|
-
const inferred = [...new Set(targets.map((g) => g.type).filter((t) => !!t))];
|
|
883
|
-
return inferred.length > 0 ? inferred : void 0;
|
|
884
|
-
}
|
|
885
|
-
function resolveMetadata(metadata, ctx) {
|
|
886
|
-
const out = {};
|
|
887
|
-
for (const [k, src] of Object.entries(metadata ?? {}))
|
|
888
|
-
out[k] = resolveSource(src, ctx);
|
|
889
|
-
return out;
|
|
1119
|
+
function findCurrentTasks(instance) {
|
|
1120
|
+
return findCurrentStageEntry(instance)?.tasks ?? [];
|
|
890
1121
|
}
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
...guard.description !== void 0 ? { description: guard.description } : {},
|
|
906
|
-
match: {
|
|
907
|
-
...types !== void 0 ? { types } : {},
|
|
908
|
-
idRefs: bareIdRefs(targets),
|
|
909
|
-
...guard.match.idPatterns !== void 0 ? { idPatterns: guard.match.idPatterns } : {},
|
|
910
|
-
actions: guard.match.actions
|
|
911
|
-
},
|
|
912
|
-
predicate: guard.predicate ?? "",
|
|
913
|
-
metadata: resolveMetadata(guard.metadata, ctx)
|
|
914
|
-
}), routeGdr: targets[0].parsed };
|
|
1122
|
+
async function completeEffect(args) {
|
|
1123
|
+
const { client, instanceId, effectKey, status, outputs, detail, error, durationMs, options } = args, ctx = await loadContext(client, instanceId, {
|
|
1124
|
+
...options?.clock ? { clock: options.clock } : {}
|
|
1125
|
+
});
|
|
1126
|
+
return commitCompleteEffect(
|
|
1127
|
+
ctx,
|
|
1128
|
+
effectKey,
|
|
1129
|
+
status,
|
|
1130
|
+
outputs,
|
|
1131
|
+
detail,
|
|
1132
|
+
error,
|
|
1133
|
+
durationMs,
|
|
1134
|
+
options?.actor
|
|
1135
|
+
);
|
|
915
1136
|
}
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
1137
|
+
function buildEffectHistoryEntry(pending, outcome) {
|
|
1138
|
+
const { status, ranAt, actor, detail, error, durationMs, outputs } = outcome, resolvedActor = actor ?? pending.actor;
|
|
1139
|
+
return {
|
|
1140
|
+
_key: pending._key,
|
|
1141
|
+
effectId: pending.effectId,
|
|
1142
|
+
...pending.title !== void 0 ? { title: pending.title } : {},
|
|
1143
|
+
...pending.description !== void 0 ? { description: pending.description } : {},
|
|
1144
|
+
params: pending.params,
|
|
1145
|
+
origin: pending.origin,
|
|
1146
|
+
...resolvedActor !== void 0 ? { actor: resolvedActor } : {},
|
|
1147
|
+
ranAt,
|
|
1148
|
+
...durationMs !== void 0 ? { durationMs } : {},
|
|
1149
|
+
status,
|
|
1150
|
+
...detail !== void 0 ? { detail } : {},
|
|
1151
|
+
...error !== void 0 ? { error } : {},
|
|
1152
|
+
...outputs !== void 0 ? { outputs } : {}
|
|
1153
|
+
};
|
|
923
1154
|
}
|
|
924
|
-
function
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
resolved !== null && out.push({ client: args.clientForGdr(resolved.routeGdr), doc: resolved.doc });
|
|
1155
|
+
function upsertEffectsContext(mutation, outputs) {
|
|
1156
|
+
for (const [id, value] of Object.entries(outputs)) {
|
|
1157
|
+
const existingIndex = mutation.effectsContext.findIndex((e) => e.id === id), entry = effectsContextEntry(id, value);
|
|
1158
|
+
existingIndex >= 0 ? mutation.effectsContext[existingIndex] = entry : mutation.effectsContext.push(entry);
|
|
929
1159
|
}
|
|
930
|
-
return out;
|
|
931
1160
|
}
|
|
932
|
-
async function
|
|
933
|
-
|
|
1161
|
+
async function commitCompleteEffect(ctx, effectKey, status, outputs, detail, error, durationMs, actor) {
|
|
1162
|
+
const pending = ctx.instance.pendingEffects.find((e) => e._key === effectKey);
|
|
1163
|
+
if (pending === void 0)
|
|
1164
|
+
throw new Error(`Pending effect "${effectKey}" not found on instance ${ctx.instance._id}`);
|
|
1165
|
+
const mutation = startMutation(ctx.instance);
|
|
1166
|
+
mutation.pendingEffects = mutation.pendingEffects.filter((e) => e._key !== effectKey);
|
|
1167
|
+
const ranAt = ctx.now;
|
|
1168
|
+
return mutation.effectHistory.push(
|
|
1169
|
+
buildEffectHistoryEntry(pending, { status, ranAt, actor, detail, error, durationMs, outputs })
|
|
1170
|
+
), status === "done" && outputs !== void 0 && upsertEffectsContext(mutation, outputs), mutation.history.push({
|
|
1171
|
+
_key: randomKey(),
|
|
1172
|
+
_type: "workflow.history.effectCompleted",
|
|
1173
|
+
at: ranAt,
|
|
1174
|
+
effectKey,
|
|
1175
|
+
effectId: pending.effectId,
|
|
1176
|
+
status,
|
|
1177
|
+
...outputs !== void 0 ? { outputs } : {},
|
|
1178
|
+
...detail !== void 0 ? { detail } : {},
|
|
1179
|
+
...actor !== void 0 ? { actor } : {}
|
|
1180
|
+
}), await persist(ctx, mutation), { effectKey, status };
|
|
934
1181
|
}
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
1182
|
+
function buildQueuedEffect(effect, origin, params, actor, now) {
|
|
1183
|
+
const key = randomKey(), pending = {
|
|
1184
|
+
_key: key,
|
|
1185
|
+
_type: "workflow.pendingEffect",
|
|
1186
|
+
effectId: effect.id,
|
|
1187
|
+
...effect.title !== void 0 ? { title: effect.title } : {},
|
|
1188
|
+
...effect.description !== void 0 ? { description: effect.description } : {},
|
|
1189
|
+
...effect.bindings !== void 0 ? { bindings: effect.bindings } : {},
|
|
1190
|
+
params,
|
|
1191
|
+
origin,
|
|
1192
|
+
...actor !== void 0 ? { actor } : {},
|
|
1193
|
+
queuedAt: now
|
|
1194
|
+
}, history = {
|
|
1195
|
+
_key: randomKey(),
|
|
1196
|
+
_type: "workflow.history.effectQueued",
|
|
1197
|
+
at: now,
|
|
1198
|
+
effectKey: key,
|
|
1199
|
+
effectId: effect.id,
|
|
1200
|
+
origin
|
|
1201
|
+
};
|
|
1202
|
+
return { pending, history };
|
|
939
1203
|
}
|
|
940
|
-
async function
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
1204
|
+
async function queueEffects(ctx, mutation, effects, origin, actor, callerParams) {
|
|
1205
|
+
if (!effects) return;
|
|
1206
|
+
const now = ctx.now, liveInstance = {
|
|
1207
|
+
...ctx.instance,
|
|
1208
|
+
state: mutation.state,
|
|
1209
|
+
stages: mutation.stages,
|
|
1210
|
+
effectsContext: mutation.effectsContext
|
|
1211
|
+
};
|
|
1212
|
+
for (const effect of effects) {
|
|
1213
|
+
const params = await resolveBindings({
|
|
1214
|
+
bindings: effect.bindings,
|
|
1215
|
+
staticInput: effect.input,
|
|
1216
|
+
instance: liveInstance,
|
|
1217
|
+
now,
|
|
1218
|
+
...callerParams !== void 0 ? { params: callerParams } : {},
|
|
1219
|
+
...actor !== void 0 ? { actor } : {}
|
|
1220
|
+
}), { pending, history } = buildQueuedEffect(effect, origin, params, actor, now);
|
|
1221
|
+
mutation.pendingEffects.push(pending), mutation.history.push(history);
|
|
1222
|
+
}
|
|
945
1223
|
}
|
|
946
1224
|
class ActionParamsInvalidError extends Error {
|
|
947
1225
|
action;
|
|
@@ -1017,6 +1295,26 @@ async function deployOrRollback(args) {
|
|
|
1017
1295
|
throw guardError;
|
|
1018
1296
|
}
|
|
1019
1297
|
}
|
|
1298
|
+
const TAG_RE = /^[a-z0-9][a-z0-9-]*$/;
|
|
1299
|
+
function validateTags(tags) {
|
|
1300
|
+
if (tags.length === 0)
|
|
1301
|
+
throw new Error("tags: must be a non-empty array");
|
|
1302
|
+
for (const tag of tags)
|
|
1303
|
+
if (!TAG_RE.test(tag))
|
|
1304
|
+
throw new Error(
|
|
1305
|
+
`tags: invalid tag "${tag}" \u2014 must match ${TAG_RE.source} (ASCII lowercase + digits + dashes, no leading dash, no dots)`
|
|
1306
|
+
);
|
|
1307
|
+
}
|
|
1308
|
+
function canonicalTag(tags) {
|
|
1309
|
+
return tags[0];
|
|
1310
|
+
}
|
|
1311
|
+
function tagScopeFilter(param = "engineTags") {
|
|
1312
|
+
return `count(tags[@ in $${param}]) > 0`;
|
|
1313
|
+
}
|
|
1314
|
+
function definitionLookupGroq(explicit) {
|
|
1315
|
+
const scoped = `_type == "${WORKFLOW_DEFINITION_TYPE}" && workflowId == $workflowId && ${tagScopeFilter()}`;
|
|
1316
|
+
return explicit ? `*[${scoped} && version == $version][0]` : `*[${scoped}] | order(version desc)[0]`;
|
|
1317
|
+
}
|
|
1020
1318
|
async function discoverItems(args) {
|
|
1021
1319
|
const { client, groq, params, perspective } = args, fetchOptions = perspective !== void 0 ? { perspective } : void 0;
|
|
1022
1320
|
return client.fetch(groq, paramsForLake(params), fetchOptions);
|
|
@@ -1140,10 +1438,11 @@ function coerceSpawnGdr(raw, workflowResource) {
|
|
|
1140
1438
|
return typeof raw == "string" && raw.length > 0 ? { id: toUri(raw), type: "document" } : null;
|
|
1141
1439
|
}
|
|
1142
1440
|
async function resolveLogicalDefinition(client, ref, tags) {
|
|
1143
|
-
const wantsExplicit = typeof ref.version == "number", params = { workflowId: ref.workflowId, tags };
|
|
1144
|
-
wantsExplicit && (params.version = ref.version)
|
|
1145
|
-
|
|
1146
|
-
|
|
1441
|
+
const wantsExplicit = typeof ref.version == "number", params = { workflowId: ref.workflowId, engineTags: tags };
|
|
1442
|
+
return wantsExplicit && (params.version = ref.version), await client.fetch(
|
|
1443
|
+
definitionLookupGroq(wantsExplicit),
|
|
1444
|
+
params
|
|
1445
|
+
) ?? null;
|
|
1147
1446
|
}
|
|
1148
1447
|
async function prepareChildInstance(args) {
|
|
1149
1448
|
const { client, parent, task, spawn, initialState, effectsContext, actor, now } = args, definition = await resolveLogicalDefinition(client, spawn.definitionRef, parent.tags ?? []);
|
|
@@ -1153,11 +1452,11 @@ async function prepareChildInstance(args) {
|
|
|
1153
1452
|
`Spawn target workflowId="${spawn.definitionRef.workflowId}" ${versionLabel} not deployed (parent ${parent._id}, task ${task.id})`
|
|
1154
1453
|
);
|
|
1155
1454
|
}
|
|
1156
|
-
const childTags = parent.tags ?? [], childPrefix = childTags[0] ?? "wf", workflowResource = parent.workflowResource, childDocId = `${childPrefix}.wf-instance.${randomKey()}`, childRef = { id: gdrFromResource(workflowResource, childDocId), type:
|
|
1455
|
+
const childTags = parent.tags ?? [], childPrefix = childTags[0] ?? "wf", workflowResource = parent.workflowResource, childDocId = `${childPrefix}.wf-instance.${randomKey()}`, childRef = { id: gdrFromResource(workflowResource, childDocId), type: WORKFLOW_INSTANCE_TYPE }, ancestors = [
|
|
1157
1456
|
...parent.ancestors,
|
|
1158
1457
|
{
|
|
1159
1458
|
id: selfGdr(parent),
|
|
1160
|
-
type:
|
|
1459
|
+
type: WORKFLOW_INSTANCE_TYPE
|
|
1161
1460
|
}
|
|
1162
1461
|
], inheritedPerspective = parent.perspective, childState = await resolveDeclaredState({
|
|
1163
1462
|
slotDefs: definition.state,
|
|
@@ -1478,9 +1777,10 @@ async function pickTransition(ctx, stage, action) {
|
|
|
1478
1777
|
if (await ctxEvaluateFilter(ctx, candidate.filter)) return candidate;
|
|
1479
1778
|
}
|
|
1480
1779
|
async function evaluateAutoTransitions(args) {
|
|
1481
|
-
const { client, instanceId, options, clientForGdr, clock } = args, ctx = await loadContext(client, instanceId, {
|
|
1780
|
+
const { client, instanceId, options, clientForGdr, clock, overlay } = args, ctx = await loadContext(client, instanceId, {
|
|
1482
1781
|
...clientForGdr ? { clientForGdr } : {},
|
|
1483
|
-
...clock ? { clock } : {}
|
|
1782
|
+
...clock ? { clock } : {},
|
|
1783
|
+
...overlay ? { overlay } : {}
|
|
1484
1784
|
});
|
|
1485
1785
|
return commitTransition(ctx, void 0, options?.actor);
|
|
1486
1786
|
}
|
|
@@ -1492,8 +1792,7 @@ async function primeInitialStage(client, instanceId, actor, clientForGdr, clock)
|
|
|
1492
1792
|
const now = (clock ?? wallClock)(), snapshot = await hydrateSnapshot({
|
|
1493
1793
|
client,
|
|
1494
1794
|
clientForGdr: clientForGdr ?? (() => client),
|
|
1495
|
-
instance
|
|
1496
|
-
definition
|
|
1795
|
+
instance
|
|
1497
1796
|
}), tasks = [];
|
|
1498
1797
|
for (const task of stage.tasks ?? []) {
|
|
1499
1798
|
const inScope = await evaluateFilter({
|
|
@@ -1586,10 +1885,11 @@ async function gatherAutoResolveCandidates(ctx, tasks) {
|
|
|
1586
1885
|
}
|
|
1587
1886
|
return candidates;
|
|
1588
1887
|
}
|
|
1589
|
-
async function resolveCompleteWhen(client, instanceId, clientForGdr, clock) {
|
|
1888
|
+
async function resolveCompleteWhen(client, instanceId, clientForGdr, clock, overlay) {
|
|
1590
1889
|
const ctx = await loadContext(client, instanceId, {
|
|
1591
1890
|
...clientForGdr ? { clientForGdr } : {},
|
|
1592
|
-
...clock ? { clock } : {}
|
|
1891
|
+
...clock ? { clock } : {},
|
|
1892
|
+
...overlay ? { overlay } : {}
|
|
1593
1893
|
}), stage = findStage(ctx.definition, ctx.instance.currentStageId), tasksWithAutoPredicate = (stage.tasks ?? []).filter(
|
|
1594
1894
|
(t) => t.completeWhen !== void 0 || t.failWhen !== void 0
|
|
1595
1895
|
);
|
|
@@ -1618,15 +1918,16 @@ async function resolveCompleteWhen(client, instanceId, clientForGdr, clock) {
|
|
|
1618
1918
|
}
|
|
1619
1919
|
return count > 0 && await persist(ctx, mutation), count;
|
|
1620
1920
|
}
|
|
1621
|
-
async function cascadeAutoTransitions(client, instanceId, actor, clientForGdr, clock) {
|
|
1921
|
+
async function cascadeAutoTransitions(client, instanceId, actor, clientForGdr, clock, overlay) {
|
|
1622
1922
|
let count = 0;
|
|
1623
1923
|
for (; ; ) {
|
|
1624
|
-
if (await resolveCompleteWhen(client, instanceId, clientForGdr, clock), !(await evaluateAutoTransitions({
|
|
1924
|
+
if (await resolveCompleteWhen(client, instanceId, clientForGdr, clock, overlay), !(await evaluateAutoTransitions({
|
|
1625
1925
|
client,
|
|
1626
1926
|
instanceId,
|
|
1627
1927
|
...actor !== void 0 ? { options: { actor } } : {},
|
|
1628
1928
|
...clientForGdr ? { clientForGdr } : {},
|
|
1629
|
-
...clock ? { clock } : {}
|
|
1929
|
+
...clock ? { clock } : {},
|
|
1930
|
+
...overlay ? { overlay } : {}
|
|
1630
1931
|
})).fired) return count;
|
|
1631
1932
|
if (count++, count >= CASCADE_LIMIT)
|
|
1632
1933
|
throw new CascadeLimitError({ instanceId, limit: CASCADE_LIMIT });
|
|
@@ -1636,7 +1937,7 @@ async function findSatisfiedParentSpawn(client, child) {
|
|
|
1636
1937
|
const parentRef = child.ancestors.at(-1);
|
|
1637
1938
|
if (parentRef === void 0) return;
|
|
1638
1939
|
const parent = await client.getDocument(gdrToBareId(parentRef.id));
|
|
1639
|
-
if (!parent || parent._type !==
|
|
1940
|
+
if (!parent || parent._type !== WORKFLOW_INSTANCE_TYPE || typeof parent.definitionSnapshot != "string") return;
|
|
1640
1941
|
const definition = JSON.parse(parent.definitionSnapshot), stage = definition.stages.find((s) => s.id === parent.currentStageId);
|
|
1641
1942
|
if (stage === void 0) return;
|
|
1642
1943
|
const parentCurrentTasks = findCurrentTasks(parent), task = (stage.tasks ?? []).find((t) => t.spawns === void 0 ? !1 : parentCurrentTasks.find((e) => e.id === t.id)?.spawnedInstances?.some((r) => gdrToBareId(r.id) === child._id) ?? !1);
|
|
@@ -1670,361 +1971,519 @@ async function propagateToAncestors(client, instanceId, actor, clientForGdr, clo
|
|
|
1670
1971
|
})
|
|
1671
1972
|
), await persist(ctx, mutation), await cascadeAutoTransitions(client, parent._id, actor, clientForGdr, clock), await propagateToAncestors(client, parent._id, actor, clientForGdr, clock);
|
|
1672
1973
|
}
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
tasks: s.tasks.map((t) => ({
|
|
1683
|
-
...t,
|
|
1684
|
-
...t.state !== void 0 ? { state: t.state.map((slot) => ({ ...slot })) } : {}
|
|
1685
|
-
}))
|
|
1686
|
-
})),
|
|
1687
|
-
pendingEffects: [...instance.pendingEffects],
|
|
1688
|
-
effectHistory: [...instance.effectHistory],
|
|
1689
|
-
effectsContext: [...instance.effectsContext],
|
|
1690
|
-
history: [...instance.history],
|
|
1691
|
-
lastChangedAt: instance.lastChangedAt,
|
|
1692
|
-
...instance.completedAt !== void 0 ? { completedAt: instance.completedAt } : {},
|
|
1693
|
-
pendingCreates: []
|
|
1694
|
-
};
|
|
1695
|
-
}
|
|
1696
|
-
function taskStatusChangedEntry(args) {
|
|
1697
|
-
return {
|
|
1698
|
-
_key: randomKey(),
|
|
1699
|
-
_type: "workflow.history.taskStatusChanged",
|
|
1700
|
-
at: args.at,
|
|
1701
|
-
stageId: args.stageId,
|
|
1702
|
-
taskId: args.taskId,
|
|
1703
|
-
from: args.from,
|
|
1704
|
-
to: args.to,
|
|
1705
|
-
...args.actor !== void 0 ? { actor: args.actor } : {}
|
|
1706
|
-
};
|
|
1974
|
+
const parsedFilters = /* @__PURE__ */ new Map();
|
|
1975
|
+
async function matchesFilter(args) {
|
|
1976
|
+
const { document, filter, userId } = args;
|
|
1977
|
+
parsedFilters.has(filter) || parsedFilters.set(filter, parse(`*[${filter}]`));
|
|
1978
|
+
const ast = parsedFilters.get(filter), data = await (await evaluate(ast, {
|
|
1979
|
+
dataset: [document],
|
|
1980
|
+
...userId !== void 0 ? { identity: userId } : {}
|
|
1981
|
+
})).get();
|
|
1982
|
+
return Array.isArray(data) && data.length === 1;
|
|
1707
1983
|
}
|
|
1708
|
-
function
|
|
1709
|
-
const {
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
_type: "workflow.history.stageExited",
|
|
1720
|
-
stageId: fromStageId,
|
|
1721
|
-
toStageId,
|
|
1722
|
-
...shared
|
|
1723
|
-
},
|
|
1724
|
-
{
|
|
1725
|
-
_key: randomKey(),
|
|
1726
|
-
_type: "workflow.history.transitionFired",
|
|
1727
|
-
fromStageId,
|
|
1728
|
-
toStageId,
|
|
1729
|
-
...shared
|
|
1730
|
-
},
|
|
1731
|
-
{
|
|
1732
|
-
_key: randomKey(),
|
|
1733
|
-
_type: "workflow.history.stageEntered",
|
|
1734
|
-
stageId: toStageId,
|
|
1735
|
-
fromStageId,
|
|
1736
|
-
...shared
|
|
1737
|
-
}
|
|
1738
|
-
];
|
|
1739
|
-
}
|
|
1740
|
-
function instanceStateFields(src) {
|
|
1741
|
-
const fields = {
|
|
1742
|
-
currentStageId: src.currentStageId,
|
|
1743
|
-
state: src.state,
|
|
1744
|
-
stages: src.stages,
|
|
1745
|
-
pendingEffects: src.pendingEffects,
|
|
1746
|
-
effectHistory: src.effectHistory,
|
|
1747
|
-
effectsContext: src.effectsContext,
|
|
1748
|
-
history: src.history
|
|
1749
|
-
};
|
|
1750
|
-
return src.completedAt !== void 0 && (fields.completedAt = src.completedAt), fields;
|
|
1751
|
-
}
|
|
1752
|
-
function materializeInstance(base, mutation) {
|
|
1753
|
-
return {
|
|
1754
|
-
...base,
|
|
1755
|
-
currentStageId: mutation.currentStageId,
|
|
1756
|
-
state: mutation.state,
|
|
1757
|
-
stages: mutation.stages
|
|
1758
|
-
};
|
|
1759
|
-
}
|
|
1760
|
-
async function persist(ctx, mutation) {
|
|
1761
|
-
const set = {
|
|
1762
|
-
...instanceStateFields(mutation),
|
|
1763
|
-
lastChangedAt: ctx.now
|
|
1764
|
-
}, pendingCreates = mutation.pendingCreates;
|
|
1765
|
-
if (pendingCreates.length === 0)
|
|
1766
|
-
return ctx.client.patch(ctx.instance._id).set(set).ifRevisionId(ctx.instance._rev).commit();
|
|
1767
|
-
const tx = ctx.client.transaction();
|
|
1768
|
-
for (const body of pendingCreates) tx.create(body);
|
|
1769
|
-
tx.patch(ctx.client.patch(ctx.instance._id).set(set).ifRevisionId(ctx.instance._rev)), await tx.commit(), mutation.pendingCreates = [];
|
|
1770
|
-
const actorForPriming = void 0;
|
|
1771
|
-
for (const body of pendingCreates)
|
|
1772
|
-
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);
|
|
1773
|
-
const reloaded = await ctx.client.getDocument(ctx.instance._id);
|
|
1774
|
-
if (!reloaded)
|
|
1775
|
-
throw new Error(`Instance ${ctx.instance._id} disappeared after transaction commit`);
|
|
1776
|
-
return reloaded;
|
|
1984
|
+
async function grantsPermissionOn(args) {
|
|
1985
|
+
const { document, grants, permission, userId } = args;
|
|
1986
|
+
if (!document || grants.length === 0) return !1;
|
|
1987
|
+
for (const grant of grants)
|
|
1988
|
+
if (grant.permissions.includes(permission) && await matchesFilter({
|
|
1989
|
+
document,
|
|
1990
|
+
filter: grant.filter,
|
|
1991
|
+
...userId !== void 0 ? { userId } : {}
|
|
1992
|
+
}))
|
|
1993
|
+
return !0;
|
|
1994
|
+
return !1;
|
|
1777
1995
|
}
|
|
1778
|
-
function
|
|
1779
|
-
|
|
1996
|
+
async function fetchGrants(args) {
|
|
1997
|
+
const { client, resourcePath, signal } = args;
|
|
1998
|
+
return client.request({ url: resourcePath, ...signal ? { signal } : {} });
|
|
1780
1999
|
}
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
2000
|
+
const actorCache = /* @__PURE__ */ new WeakMap(), grantsCache = /* @__PURE__ */ new WeakMap();
|
|
2001
|
+
async function resolveAccess(client, args = {}) {
|
|
2002
|
+
const overrideActor = args.override?.actor, overrideGrants = args.override?.grants;
|
|
2003
|
+
if (overrideActor !== void 0 && overrideGrants !== void 0)
|
|
2004
|
+
return { actor: overrideActor, grants: overrideGrants };
|
|
2005
|
+
const requestFn = client.request === void 0 ? void 0 : (opts) => client.request(opts);
|
|
2006
|
+
if (requestFn === void 0) {
|
|
2007
|
+
if (overrideActor === void 0)
|
|
2008
|
+
throw new Error(
|
|
2009
|
+
"workflow: no actor available. The engine resolves the actor from the client's token via `client.request({ url: '/users/me' })`. Supply a real `@sanity/client` configured with a token, or pass an explicit `access.actor` override (the test bench provides one by default)."
|
|
2010
|
+
);
|
|
2011
|
+
return { actor: overrideActor };
|
|
2012
|
+
}
|
|
2013
|
+
const actorPromise = overrideActor !== void 0 ? Promise.resolve(overrideActor) : cachedActor(client, requestFn), grantsPromise = resolveGrants(overrideGrants, args.grantsFromPath, client, requestFn), [actor, grants] = await Promise.all([actorPromise, grantsPromise]);
|
|
2014
|
+
if (actor === void 0)
|
|
1784
2015
|
throw new Error(
|
|
1785
|
-
|
|
2016
|
+
"workflow: failed to resolve actor from `/users/me`. The client is configured but the endpoint returned no usable identity \u2014 check the token, or pass `access.actor` explicitly."
|
|
1786
2017
|
);
|
|
1787
|
-
return
|
|
2018
|
+
return { actor, ...grants !== void 0 ? { grants } : {} };
|
|
1788
2019
|
}
|
|
1789
|
-
function
|
|
1790
|
-
|
|
2020
|
+
function cachedActor(client, requestFn) {
|
|
2021
|
+
const cached = actorCache.get(client);
|
|
2022
|
+
if (cached !== void 0) return cached;
|
|
2023
|
+
const pending = fetchActor(requestFn).catch((err) => {
|
|
2024
|
+
throw actorCache.get(client) === pending && actorCache.delete(client), err;
|
|
2025
|
+
});
|
|
2026
|
+
return actorCache.set(client, pending), pending;
|
|
1791
2027
|
}
|
|
1792
|
-
function
|
|
1793
|
-
|
|
1794
|
-
if (task === void 0)
|
|
1795
|
-
throw new Error(
|
|
1796
|
-
`Task "${taskId}" not found in current stage "${stage.id}" of ${ctx.definition.workflowId}`
|
|
1797
|
-
);
|
|
1798
|
-
return { stage, task };
|
|
2028
|
+
function resolveGrants(overrideGrants, grantsFromPath, client, requestFn) {
|
|
2029
|
+
return overrideGrants !== void 0 ? Promise.resolve(overrideGrants) : grantsFromPath !== void 0 ? cachedGrants(client, requestFn, grantsFromPath) : Promise.resolve(void 0);
|
|
1799
2030
|
}
|
|
1800
|
-
function
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
return
|
|
2031
|
+
function cachedGrants(client, requestFn, resourcePath) {
|
|
2032
|
+
let byPath = grantsCache.get(client);
|
|
2033
|
+
byPath === void 0 && (byPath = /* @__PURE__ */ new Map(), grantsCache.set(client, byPath));
|
|
2034
|
+
let cached = byPath.get(resourcePath);
|
|
2035
|
+
return cached === void 0 && (cached = fetchGrantsCached(requestFn, resourcePath), byPath.set(resourcePath, cached)), cached;
|
|
1805
2036
|
}
|
|
1806
|
-
function
|
|
1807
|
-
|
|
2037
|
+
async function fetchActor(requestFn) {
|
|
2038
|
+
let user;
|
|
2039
|
+
try {
|
|
2040
|
+
user = await requestFn({
|
|
2041
|
+
uri: "/users/me",
|
|
2042
|
+
tag: "workflow.access.resolve-actor"
|
|
2043
|
+
});
|
|
2044
|
+
} catch (err) {
|
|
2045
|
+
throw new Error(
|
|
2046
|
+
'workflow: /users/me request failed. The engine resolves the actor from the client\'s token via `client.request({ uri: "/users/me" })`. Check the token/connectivity, or pass an explicit `access.actor` override.',
|
|
2047
|
+
{ cause: err }
|
|
2048
|
+
);
|
|
2049
|
+
}
|
|
2050
|
+
if (!user || typeof user.id != "string" || user.id.length === 0) return;
|
|
2051
|
+
const roleNames = user.roles?.map((r) => r.name).filter((n) => !!n) ?? [];
|
|
2052
|
+
return {
|
|
2053
|
+
kind: "user",
|
|
2054
|
+
id: user.id,
|
|
2055
|
+
...roleNames.length > 0 ? { roles: roleNames } : {}
|
|
2056
|
+
};
|
|
1808
2057
|
}
|
|
1809
|
-
function
|
|
1810
|
-
|
|
2058
|
+
async function fetchGrantsCached(requestFn, resourcePath) {
|
|
2059
|
+
try {
|
|
2060
|
+
return await fetchGrants({ client: { request: requestFn }, resourcePath });
|
|
2061
|
+
} catch (err) {
|
|
2062
|
+
console.warn(
|
|
2063
|
+
`workflow: failed to fetch grants from "${resourcePath}"; skipping permission gate. Original error: ${err instanceof Error ? err.message : String(err)}`
|
|
2064
|
+
);
|
|
2065
|
+
return;
|
|
2066
|
+
}
|
|
1811
2067
|
}
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
2068
|
+
const WILDCARD_ROLE = "*";
|
|
2069
|
+
async function evaluateInstance(args) {
|
|
2070
|
+
const { client, tags, instanceId, resourceClients } = args, now = (args.clock ?? wallClock)();
|
|
2071
|
+
validateTags(tags);
|
|
2072
|
+
const { actor, grants } = await resolveAccess(client, {
|
|
2073
|
+
...args.access !== void 0 ? { override: args.access } : {},
|
|
2074
|
+
...args.grantsFromPath !== void 0 ? { grantsFromPath: args.grantsFromPath } : {}
|
|
2075
|
+
}), instance = await client.getDocument(instanceId);
|
|
2076
|
+
if (!instance)
|
|
2077
|
+
throw new Error(`Workflow instance "${instanceId}" not found`);
|
|
2078
|
+
if (!tags.some((t) => instance.tags?.includes(t)))
|
|
2079
|
+
throw new Error(`Workflow instance "${instanceId}" not visible to this engine (tag mismatch)`);
|
|
2080
|
+
const definition = parseDefinitionSnapshot(instance), snapshot = await hydrateSnapshot({ client, clientForGdr: resourceClients !== void 0 ? (parsed) => resourceClients(parsed) ?? client : () => client, instance });
|
|
2081
|
+
return evaluateFromSnapshot({
|
|
2082
|
+
instance,
|
|
2083
|
+
definition,
|
|
2084
|
+
actor,
|
|
2085
|
+
snapshot,
|
|
2086
|
+
now,
|
|
2087
|
+
...grants !== void 0 ? { grants } : {}
|
|
1815
2088
|
});
|
|
1816
|
-
return commitCompleteEffect(
|
|
1817
|
-
ctx,
|
|
1818
|
-
effectKey,
|
|
1819
|
-
status,
|
|
1820
|
-
outputs,
|
|
1821
|
-
detail,
|
|
1822
|
-
error,
|
|
1823
|
-
durationMs,
|
|
1824
|
-
options?.actor
|
|
1825
|
-
);
|
|
1826
2089
|
}
|
|
1827
|
-
function
|
|
1828
|
-
const {
|
|
2090
|
+
async function evaluateFromSnapshot(args) {
|
|
2091
|
+
const { instance, definition, actor, grants, snapshot } = args, now = args.now ?? wallClock(), stage = definition.stages.find((s) => s.id === instance.currentStageId);
|
|
2092
|
+
if (stage === void 0)
|
|
2093
|
+
throw new Error(
|
|
2094
|
+
`Instance "${instance._id}" currentStageId "${instance.currentStageId}" not in definition`
|
|
2095
|
+
);
|
|
2096
|
+
const currentTaskEntries = findOpenStageEntry(instance)?.tasks ?? [], taskEvaluations = [];
|
|
2097
|
+
for (const task of stage.tasks ?? [])
|
|
2098
|
+
taskEvaluations.push(
|
|
2099
|
+
await evaluateTask({
|
|
2100
|
+
task,
|
|
2101
|
+
statusEntry: currentTaskEntries.find((t) => t.id === task.id),
|
|
2102
|
+
instance,
|
|
2103
|
+
definition,
|
|
2104
|
+
actor,
|
|
2105
|
+
grants,
|
|
2106
|
+
snapshot,
|
|
2107
|
+
now
|
|
2108
|
+
})
|
|
2109
|
+
);
|
|
2110
|
+
const transitionEvaluations = [];
|
|
2111
|
+
for (const transition of stage.transitions ?? [])
|
|
2112
|
+
transitionEvaluations.push({
|
|
2113
|
+
transition,
|
|
2114
|
+
filterSatisfied: await evaluateFilter({
|
|
2115
|
+
filter: transition.filter,
|
|
2116
|
+
definition,
|
|
2117
|
+
snapshot,
|
|
2118
|
+
params: buildParams(instance, now)
|
|
2119
|
+
})
|
|
2120
|
+
});
|
|
2121
|
+
const currentStage = {
|
|
2122
|
+
stage,
|
|
2123
|
+
tasks: taskEvaluations,
|
|
2124
|
+
transitions: transitionEvaluations
|
|
2125
|
+
}, pendingOnYou = taskEvaluations.filter((t) => t.pendingOnActor), canInteract = taskEvaluations.some((t) => t.actions.some((a) => a.allowed));
|
|
1829
2126
|
return {
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
...actor !== void 0 ? { actor } : pending.actor !== void 0 ? { actor: pending.actor } : {},
|
|
1837
|
-
ranAt,
|
|
1838
|
-
...durationMs !== void 0 ? { durationMs } : {},
|
|
1839
|
-
status,
|
|
1840
|
-
...detail !== void 0 ? { detail } : {},
|
|
1841
|
-
...error !== void 0 ? { error } : {},
|
|
1842
|
-
...outputs !== void 0 ? { outputs } : {}
|
|
2127
|
+
instance,
|
|
2128
|
+
definition,
|
|
2129
|
+
actor,
|
|
2130
|
+
currentStage,
|
|
2131
|
+
pendingOnYou,
|
|
2132
|
+
canInteract
|
|
1843
2133
|
};
|
|
1844
2134
|
}
|
|
1845
|
-
function
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
effectKey,
|
|
1865
|
-
effectId: pending.effectId,
|
|
2135
|
+
async function evaluateTask(args) {
|
|
2136
|
+
const { task, statusEntry, instance, definition, actor, grants, snapshot, now } = args, status = statusEntry?.status ?? "pending", actions = [];
|
|
2137
|
+
for (const action of task.actions ?? [])
|
|
2138
|
+
actions.push(
|
|
2139
|
+
await evaluateAction({
|
|
2140
|
+
action,
|
|
2141
|
+
task,
|
|
2142
|
+
...statusEntry !== void 0 ? { statusEntry } : {},
|
|
2143
|
+
status,
|
|
2144
|
+
instance,
|
|
2145
|
+
definition,
|
|
2146
|
+
actor,
|
|
2147
|
+
...grants !== void 0 ? { grants } : {},
|
|
2148
|
+
snapshot,
|
|
2149
|
+
now
|
|
2150
|
+
})
|
|
2151
|
+
);
|
|
2152
|
+
return {
|
|
2153
|
+
task,
|
|
1866
2154
|
status,
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
function buildQueuedEffect(effect, origin, params, actor, now) {
|
|
1873
|
-
const key = randomKey(), pending = {
|
|
1874
|
-
_key: key,
|
|
1875
|
-
_type: "workflow.pendingEffect",
|
|
1876
|
-
effectId: effect.id,
|
|
1877
|
-
...effect.title !== void 0 ? { title: effect.title } : {},
|
|
1878
|
-
...effect.description !== void 0 ? { description: effect.description } : {},
|
|
1879
|
-
...effect.bindings !== void 0 ? { bindings: effect.bindings } : {},
|
|
1880
|
-
params,
|
|
1881
|
-
origin,
|
|
1882
|
-
...actor !== void 0 ? { actor } : {},
|
|
1883
|
-
queuedAt: now
|
|
1884
|
-
}, history = {
|
|
1885
|
-
_key: randomKey(),
|
|
1886
|
-
_type: "workflow.history.effectQueued",
|
|
1887
|
-
at: now,
|
|
1888
|
-
effectKey: key,
|
|
1889
|
-
effectId: effect.id,
|
|
1890
|
-
origin
|
|
2155
|
+
// "pending on actor" treats both `pending` and `active` as waiting on
|
|
2156
|
+
// the assignee — pending tasks just haven't been auto-invoked yet, but
|
|
2157
|
+
// they're still inbox items.
|
|
2158
|
+
pendingOnActor: (status === "active" || status === "pending") && actorIsAssignee(actor, task.assignees ?? []),
|
|
2159
|
+
actions
|
|
1891
2160
|
};
|
|
1892
|
-
return { pending, history };
|
|
1893
2161
|
}
|
|
1894
|
-
async function
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
stages: mutation.stages,
|
|
1900
|
-
effectsContext: mutation.effectsContext
|
|
1901
|
-
};
|
|
1902
|
-
for (const effect of effects) {
|
|
1903
|
-
const params = await resolveBindings({
|
|
1904
|
-
bindings: effect.bindings,
|
|
1905
|
-
staticInput: effect.input,
|
|
1906
|
-
instance: liveInstance,
|
|
1907
|
-
now,
|
|
1908
|
-
...callerParams !== void 0 ? { params: callerParams } : {},
|
|
1909
|
-
...actor !== void 0 ? { actor } : {}
|
|
1910
|
-
}), { pending, history } = buildQueuedEffect(effect, origin, params, actor, now);
|
|
1911
|
-
mutation.pendingEffects.push(pending), mutation.history.push(history);
|
|
1912
|
-
}
|
|
2162
|
+
async function evaluateAction(args) {
|
|
2163
|
+
const { action, task, status, instance, definition, actor, grants, snapshot, now } = args, syncReason = lifecycleReason(instance, definition, status) ?? actorReason(action, task, actor);
|
|
2164
|
+
if (syncReason !== void 0) return disabled(action, syncReason);
|
|
2165
|
+
const asyncReason = await permissionReason(action, instance, actor, grants) ?? await filterReason(action, instance, definition, snapshot, now);
|
|
2166
|
+
return asyncReason !== void 0 ? disabled(action, asyncReason) : { action, allowed: !0 };
|
|
1913
2167
|
}
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
return STATE_OP_TYPES.includes(summary.opType);
|
|
2168
|
+
function lifecycleReason(instance, definition, status) {
|
|
2169
|
+
if (instance.completedAt !== void 0)
|
|
2170
|
+
return { kind: "instance-completed", completedAt: instance.completedAt };
|
|
2171
|
+
if (definition.stages.find((s) => s.id === instance.currentStageId)?.kind === "terminal")
|
|
2172
|
+
return { kind: "stage-terminal", stageId: instance.currentStageId };
|
|
2173
|
+
if (status === "done" || status === "skipped" || status === "failed")
|
|
2174
|
+
return { kind: "task-not-active", status };
|
|
1922
2175
|
}
|
|
1923
|
-
function
|
|
1924
|
-
const
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
continue;
|
|
1930
|
-
}
|
|
1931
|
-
present && (checkParamType(value, decl) || issues.push({
|
|
1932
|
-
paramId: decl.id,
|
|
1933
|
-
reason: `expected paramType=${decl.paramType}, got ${typeof value}`
|
|
1934
|
-
}));
|
|
1935
|
-
}
|
|
1936
|
-
if (issues.length > 0)
|
|
1937
|
-
throw new ActionParamsInvalidError({ action: action.id, taskId, issues });
|
|
1938
|
-
return params;
|
|
2176
|
+
function actorReason(action, task, actor) {
|
|
2177
|
+
const actorRoles = actor.roles ?? [];
|
|
2178
|
+
if (action.roles !== void 0 && action.roles.length > 0 && !(actorRoles.includes(WILDCARD_ROLE) || action.roles.some((r) => actorRoles.includes(r))))
|
|
2179
|
+
return { kind: "role-required", required: [...action.roles], actorRoles: [...actorRoles] };
|
|
2180
|
+
if (action.requireAssignment === !0 && !actorIsAssignee(actor, task.assignees ?? []))
|
|
2181
|
+
return { kind: "not-assigned", assignees: [...task.assignees ?? []], actorId: actor.id };
|
|
1939
2182
|
}
|
|
1940
|
-
function
|
|
1941
|
-
|
|
2183
|
+
async function permissionReason(action, instance, actor, grants) {
|
|
2184
|
+
if (grants === void 0) return;
|
|
2185
|
+
const permission = action.requiredPermission ?? "update";
|
|
2186
|
+
if (!await grantsPermissionOn({
|
|
2187
|
+
document: instance,
|
|
2188
|
+
grants,
|
|
2189
|
+
permission,
|
|
2190
|
+
userId: actor.id
|
|
2191
|
+
}))
|
|
2192
|
+
return { kind: "permission-denied", permission, matchingGrants: grants.length };
|
|
1942
2193
|
}
|
|
1943
|
-
function
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
2194
|
+
async function filterReason(action, instance, definition, snapshot, now) {
|
|
2195
|
+
if (!(action.filter === void 0 || await evaluateFilter({
|
|
2196
|
+
filter: action.filter,
|
|
2197
|
+
definition,
|
|
2198
|
+
snapshot,
|
|
2199
|
+
params: buildParams(instance, now)
|
|
2200
|
+
})))
|
|
2201
|
+
return { kind: "filter-failed", filter: action.filter };
|
|
2202
|
+
}
|
|
2203
|
+
function disabled(action, reason) {
|
|
2204
|
+
return { action, allowed: !1, disabledReason: reason };
|
|
2205
|
+
}
|
|
2206
|
+
function actorIsAssignee(actor, assignees) {
|
|
2207
|
+
if (assignees.length === 0) return !1;
|
|
2208
|
+
const actorRoles = actor.roles ?? [], wildcard = actorRoles.includes(WILDCARD_ROLE);
|
|
2209
|
+
for (const assignee of assignees)
|
|
2210
|
+
if (assignee.kind === "user" && assignee.id === actor.id || assignee.kind === "role" && (wildcard || actorRoles.includes(assignee.role)))
|
|
1960
2211
|
return !0;
|
|
1961
|
-
|
|
1962
|
-
return !1;
|
|
1963
|
-
}
|
|
2212
|
+
return !1;
|
|
1964
2213
|
}
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
_type: "workflow.history.opApplied",
|
|
1974
|
-
at: now,
|
|
1975
|
-
stageId,
|
|
1976
|
-
taskId,
|
|
1977
|
-
actionId: actionName,
|
|
1978
|
-
opType: summary.opType,
|
|
1979
|
-
...summary.target !== void 0 ? { target: summary.target } : {},
|
|
1980
|
-
...summary.resolved !== void 0 ? { resolved: summary.resolved } : {},
|
|
1981
|
-
...actor !== void 0 ? { actor } : {}
|
|
1982
|
-
});
|
|
2214
|
+
function parseDefinitionSnapshot(instance) {
|
|
2215
|
+
try {
|
|
2216
|
+
return JSON.parse(instance.definitionSnapshot);
|
|
2217
|
+
} catch (err) {
|
|
2218
|
+
throw new Error(
|
|
2219
|
+
`Failed to parse definitionSnapshot on instance "${instance._id}": ${err instanceof Error ? err.message : String(err)}`,
|
|
2220
|
+
{ cause: err }
|
|
2221
|
+
);
|
|
1983
2222
|
}
|
|
1984
|
-
return summaries;
|
|
1985
2223
|
}
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
return applyStateSet(op, ctx);
|
|
1990
|
-
case "workflow.op.state.append":
|
|
1991
|
-
return applyStateAppend(op, ctx);
|
|
1992
|
-
case "workflow.op.state.updateWhere":
|
|
1993
|
-
return applyStateUpdateWhere(op, ctx);
|
|
1994
|
-
case "workflow.op.state.removeWhere":
|
|
1995
|
-
return applyStateRemoveWhere(op, ctx);
|
|
1996
|
-
case "workflow.op.status.set":
|
|
1997
|
-
return applyStatusSet(op, ctx);
|
|
1998
|
-
}
|
|
2224
|
+
const resourceKey = (r) => `${r.type}.${r.id}`;
|
|
2225
|
+
function guardsForResource(client) {
|
|
2226
|
+
return client.fetch("*[_type == $t] | order(_id asc)", { t: GUARD_DOC_TYPE });
|
|
1999
2227
|
}
|
|
2000
|
-
function
|
|
2001
|
-
const
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2228
|
+
async function guardsForInstance(args) {
|
|
2229
|
+
const { client, clientForGdr, instance } = args, clientsByResource = /* @__PURE__ */ new Map([
|
|
2230
|
+
[resourceKey(instance.workflowResource), client]
|
|
2231
|
+
]), stateUris = [
|
|
2232
|
+
...collectSlotDocUris(instance.state),
|
|
2233
|
+
...instance.stages.flatMap((stage) => collectSlotDocUris(stage.state))
|
|
2234
|
+
];
|
|
2235
|
+
for (const uri of stateUris) {
|
|
2236
|
+
const parsed = tryParseGdr(uri);
|
|
2237
|
+
if (parsed === void 0) continue;
|
|
2238
|
+
const key = resourceKey(resourceFromParsed(parsed));
|
|
2239
|
+
clientsByResource.has(key) || clientsByResource.set(key, clientForGdr(parsed));
|
|
2240
|
+
}
|
|
2241
|
+
const perResource = await Promise.all(
|
|
2242
|
+
[...clientsByResource.values()].map(
|
|
2243
|
+
(c) => c.fetch("*[_type == $t && sourceInstanceId == $id]", {
|
|
2244
|
+
t: GUARD_DOC_TYPE,
|
|
2245
|
+
id: instance._id
|
|
2246
|
+
})
|
|
2247
|
+
)
|
|
2006
2248
|
);
|
|
2007
|
-
return
|
|
2249
|
+
return dedupById(perResource.flat());
|
|
2008
2250
|
}
|
|
2009
|
-
function
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
!0
|
|
2015
|
-
);
|
|
2016
|
-
if (slot !== void 0) {
|
|
2017
|
-
validateSlotAppendItem({ slotType: slot._type, slotId: slot.id, item });
|
|
2018
|
-
const current = Array.isArray(slot.value) ? slot.value : [];
|
|
2019
|
-
slot.value = [...current, withRowKey(item)];
|
|
2251
|
+
function tryParseGdr(uri) {
|
|
2252
|
+
try {
|
|
2253
|
+
return parseGdr(uri);
|
|
2254
|
+
} catch {
|
|
2255
|
+
return;
|
|
2020
2256
|
}
|
|
2021
|
-
return { opType: op.type, target: op.target, resolved: { item } };
|
|
2022
2257
|
}
|
|
2023
|
-
function
|
|
2024
|
-
return
|
|
2258
|
+
function dedupById(guards) {
|
|
2259
|
+
return [...new Map(guards.map((g) => [g._id, g])).values()].sort((a, b) => a._id.localeCompare(b._id));
|
|
2025
2260
|
}
|
|
2026
|
-
function
|
|
2027
|
-
|
|
2261
|
+
function definitionDocId(_workflowResource, prefix, workflowId, version) {
|
|
2262
|
+
return `${prefix}.${workflowId}.v${version}`;
|
|
2263
|
+
}
|
|
2264
|
+
async function sortByDependencies(client, definitions, tags, workflowResource) {
|
|
2265
|
+
const prefix = canonicalTag(tags), byWorkflowId = /* @__PURE__ */ new Map();
|
|
2266
|
+
for (const def of definitions) {
|
|
2267
|
+
const list = byWorkflowId.get(def.workflowId) ?? [];
|
|
2268
|
+
list.push(def), byWorkflowId.set(def.workflowId, list);
|
|
2269
|
+
}
|
|
2270
|
+
const findInBatch = (ref) => findRefInBatch(byWorkflowId, ref);
|
|
2271
|
+
return await assertCrossBatchRefsDeployed(client, definitions, {
|
|
2272
|
+
tags,
|
|
2273
|
+
workflowResource,
|
|
2274
|
+
prefix,
|
|
2275
|
+
findInBatch
|
|
2276
|
+
}), topoSortDefinitions(definitions, { workflowResource, prefix, findInBatch });
|
|
2277
|
+
}
|
|
2278
|
+
function findRefInBatch(byWorkflowId, ref) {
|
|
2279
|
+
const candidates = byWorkflowId.get(ref.workflowId);
|
|
2280
|
+
if (!(candidates === void 0 || candidates.length === 0))
|
|
2281
|
+
return typeof ref.version == "number" ? candidates.find((c) => c.version === ref.version) : candidates.reduce(
|
|
2282
|
+
(best, c) => best === void 0 || c.version > best.version ? c : best,
|
|
2283
|
+
void 0
|
|
2284
|
+
);
|
|
2285
|
+
}
|
|
2286
|
+
async function assertCrossBatchRefsDeployed(client, definitions, ctx) {
|
|
2287
|
+
const missing = [];
|
|
2288
|
+
for (const def of definitions) {
|
|
2289
|
+
const fromId = definitionDocId(ctx.workflowResource, ctx.prefix, def.workflowId, def.version);
|
|
2290
|
+
for (const ref of refsOf(def)) {
|
|
2291
|
+
if (ctx.findInBatch(ref) !== void 0) continue;
|
|
2292
|
+
const label = await resolveDeployedRefLabel(client, ref, ctx.tags);
|
|
2293
|
+
label !== void 0 && missing.push({ from: fromId, ref: label });
|
|
2294
|
+
}
|
|
2295
|
+
}
|
|
2296
|
+
if (missing.length === 0) return;
|
|
2297
|
+
const lines = missing.map((m) => ` - ${m.from} \u2192 ${m.ref} (neither in batch nor deployed)`);
|
|
2298
|
+
throw new Error(
|
|
2299
|
+
`workflow.deployDefinitions: ${missing.length} unresolved reference${missing.length === 1 ? "" : "s"}:
|
|
2300
|
+
` + lines.join(`
|
|
2301
|
+
`)
|
|
2302
|
+
);
|
|
2303
|
+
}
|
|
2304
|
+
async function resolveDeployedRefLabel(client, ref, tags) {
|
|
2305
|
+
const wantsExplicit = typeof ref.version == "number", params = { workflowId: ref.workflowId, engineTags: tags };
|
|
2306
|
+
if (wantsExplicit && (params.version = ref.version), !await client.fetch(
|
|
2307
|
+
definitionLookupGroq(wantsExplicit),
|
|
2308
|
+
params
|
|
2309
|
+
))
|
|
2310
|
+
return wantsExplicit ? `${ref.workflowId} v${ref.version}` : ref.workflowId;
|
|
2311
|
+
}
|
|
2312
|
+
function topoSortDefinitions(definitions, ctx) {
|
|
2313
|
+
const visited = /* @__PURE__ */ new Set(), visiting = /* @__PURE__ */ new Set(), ordered = [], visit = (def) => {
|
|
2314
|
+
const id = definitionDocId(ctx.workflowResource, ctx.prefix, def.workflowId, def.version);
|
|
2315
|
+
if (!visited.has(id)) {
|
|
2316
|
+
if (visiting.has(id))
|
|
2317
|
+
throw new Error(`workflow.deployDefinitions: dependency cycle involving "${id}"`);
|
|
2318
|
+
visiting.add(id);
|
|
2319
|
+
for (const ref of refsOf(def)) {
|
|
2320
|
+
const child = ctx.findInBatch(ref);
|
|
2321
|
+
child !== void 0 && visit(child);
|
|
2322
|
+
}
|
|
2323
|
+
visiting.delete(id), visited.add(id), ordered.push(def);
|
|
2324
|
+
}
|
|
2325
|
+
};
|
|
2326
|
+
for (const def of definitions) visit(def);
|
|
2327
|
+
return ordered;
|
|
2328
|
+
}
|
|
2329
|
+
function refsOf(def) {
|
|
2330
|
+
const out = [];
|
|
2331
|
+
for (const stage of def.stages)
|
|
2332
|
+
for (const task of stage.tasks ?? []) {
|
|
2333
|
+
const ref = task.spawns?.definitionRef;
|
|
2334
|
+
ref !== void 0 && out.push({
|
|
2335
|
+
workflowId: ref.workflowId,
|
|
2336
|
+
...ref.version !== void 0 ? { version: ref.version } : {}
|
|
2337
|
+
});
|
|
2338
|
+
}
|
|
2339
|
+
return out;
|
|
2340
|
+
}
|
|
2341
|
+
function isDefinitionUnchanged(existing, expected) {
|
|
2342
|
+
return stableStringify(stripSystemFields(existing)) === stableStringify(stripSystemFields(expected));
|
|
2343
|
+
}
|
|
2344
|
+
function stripSystemFields(doc) {
|
|
2345
|
+
const out = {};
|
|
2346
|
+
for (const [k, v2] of Object.entries(doc))
|
|
2347
|
+
k === "_rev" || k === "_createdAt" || k === "_updatedAt" || (out[k] = v2);
|
|
2348
|
+
return out;
|
|
2349
|
+
}
|
|
2350
|
+
function stableStringify(value) {
|
|
2351
|
+
return value === null || typeof value != "object" ? JSON.stringify(value) : Array.isArray(value) ? `[${value.map(stableStringify).join(",")}]` : `{${Object.entries(value).toSorted(
|
|
2352
|
+
([a], [b]) => a.localeCompare(b)
|
|
2353
|
+
).map(([k, v2]) => `${JSON.stringify(k)}:${stableStringify(v2)}`).join(",")}}`;
|
|
2354
|
+
}
|
|
2355
|
+
async function loadDefinition(client, workflowId, version, tags) {
|
|
2356
|
+
if (version !== void 0) {
|
|
2357
|
+
const doc = await client.fetch(
|
|
2358
|
+
definitionLookupGroq(!0),
|
|
2359
|
+
{ workflowId, version, engineTags: tags }
|
|
2360
|
+
);
|
|
2361
|
+
if (!doc)
|
|
2362
|
+
throw new Error(`Workflow definition ${workflowId} v${version} not deployed`);
|
|
2363
|
+
return doc;
|
|
2364
|
+
}
|
|
2365
|
+
const latest = await client.fetch(definitionLookupGroq(!1), {
|
|
2366
|
+
workflowId,
|
|
2367
|
+
engineTags: tags
|
|
2368
|
+
});
|
|
2369
|
+
if (!latest)
|
|
2370
|
+
throw new Error(`No deployed definition for workflow ${workflowId}`);
|
|
2371
|
+
return latest;
|
|
2372
|
+
}
|
|
2373
|
+
const STATE_OP_TYPES = [
|
|
2374
|
+
"workflow.op.state.set",
|
|
2375
|
+
"workflow.op.state.append",
|
|
2376
|
+
"workflow.op.state.updateWhere",
|
|
2377
|
+
"workflow.op.state.removeWhere"
|
|
2378
|
+
];
|
|
2379
|
+
function isStateOp(summary) {
|
|
2380
|
+
return STATE_OP_TYPES.includes(summary.opType);
|
|
2381
|
+
}
|
|
2382
|
+
function validateActionParams(action, taskId, callerParams) {
|
|
2383
|
+
const declared = action.params ?? [], params = callerParams ?? {}, issues = [];
|
|
2384
|
+
for (const decl of declared) {
|
|
2385
|
+
const value = params[decl.id], present = decl.id in params && value !== void 0 && value !== null;
|
|
2386
|
+
if (decl.required === !0 && !present) {
|
|
2387
|
+
issues.push({ paramId: decl.id, reason: "required but missing" });
|
|
2388
|
+
continue;
|
|
2389
|
+
}
|
|
2390
|
+
present && (checkParamType(value, decl) || issues.push({
|
|
2391
|
+
paramId: decl.id,
|
|
2392
|
+
reason: `expected paramType=${decl.paramType}, got ${typeof value}`
|
|
2393
|
+
}));
|
|
2394
|
+
}
|
|
2395
|
+
if (issues.length > 0)
|
|
2396
|
+
throw new ActionParamsInvalidError({ action: action.id, taskId, issues });
|
|
2397
|
+
return params;
|
|
2398
|
+
}
|
|
2399
|
+
function hasStringField(value, field) {
|
|
2400
|
+
return typeof value == "object" && value !== null && field in value && typeof value[field] == "string";
|
|
2401
|
+
}
|
|
2402
|
+
function checkParamType(value, decl) {
|
|
2403
|
+
switch (decl.paramType) {
|
|
2404
|
+
case "string":
|
|
2405
|
+
case "url":
|
|
2406
|
+
case "dateTime":
|
|
2407
|
+
return typeof value == "string";
|
|
2408
|
+
case "number":
|
|
2409
|
+
return typeof value == "number" && Number.isFinite(value);
|
|
2410
|
+
case "boolean":
|
|
2411
|
+
return typeof value == "boolean";
|
|
2412
|
+
case "actor":
|
|
2413
|
+
return hasStringField(value, "kind");
|
|
2414
|
+
case "doc.ref":
|
|
2415
|
+
return hasStringField(value, "id");
|
|
2416
|
+
case "doc.refs":
|
|
2417
|
+
return Array.isArray(value) && value.every((v2) => checkParamType(v2, { paramType: "doc.ref" }));
|
|
2418
|
+
case "json":
|
|
2419
|
+
return !0;
|
|
2420
|
+
default:
|
|
2421
|
+
return !1;
|
|
2422
|
+
}
|
|
2423
|
+
}
|
|
2424
|
+
async function runActionOps(args) {
|
|
2425
|
+
const { ops, mutation, stageId, taskId, actionName, params, actor, self, now } = args;
|
|
2426
|
+
if (ops === void 0 || ops.length === 0) return [];
|
|
2427
|
+
const summaries = [];
|
|
2428
|
+
for (const op of ops) {
|
|
2429
|
+
const summary = applyOp(op, { mutation, stageId, taskId, params, actor, self, now });
|
|
2430
|
+
summaries.push(summary), mutation.history.push({
|
|
2431
|
+
_key: randomKey(),
|
|
2432
|
+
_type: "workflow.history.opApplied",
|
|
2433
|
+
at: now,
|
|
2434
|
+
stageId,
|
|
2435
|
+
taskId,
|
|
2436
|
+
actionId: actionName,
|
|
2437
|
+
opType: summary.opType,
|
|
2438
|
+
...summary.target !== void 0 ? { target: summary.target } : {},
|
|
2439
|
+
...summary.resolved !== void 0 ? { resolved: summary.resolved } : {},
|
|
2440
|
+
...actor !== void 0 ? { actor } : {}
|
|
2441
|
+
});
|
|
2442
|
+
}
|
|
2443
|
+
return summaries;
|
|
2444
|
+
}
|
|
2445
|
+
function applyOp(op, ctx) {
|
|
2446
|
+
switch (op.type) {
|
|
2447
|
+
case "workflow.op.state.set":
|
|
2448
|
+
return applyStateSet(op, ctx);
|
|
2449
|
+
case "workflow.op.state.append":
|
|
2450
|
+
return applyStateAppend(op, ctx);
|
|
2451
|
+
case "workflow.op.state.updateWhere":
|
|
2452
|
+
return applyStateUpdateWhere(op, ctx);
|
|
2453
|
+
case "workflow.op.state.removeWhere":
|
|
2454
|
+
return applyStateRemoveWhere(op, ctx);
|
|
2455
|
+
case "workflow.op.status.set":
|
|
2456
|
+
return applyStatusSet(op, ctx);
|
|
2457
|
+
}
|
|
2458
|
+
}
|
|
2459
|
+
function applyStateSet(op, ctx) {
|
|
2460
|
+
const value = resolveOpSource(op.value, ctx), slot = locateSlot(
|
|
2461
|
+
ctx,
|
|
2462
|
+
op.target,
|
|
2463
|
+
/*createIfMissing*/
|
|
2464
|
+
!0
|
|
2465
|
+
);
|
|
2466
|
+
return slot !== void 0 && (validateSlotValue({ slotType: slot._type, slotId: slot.id, value }), slot.value = value), { opType: op.type, target: op.target, resolved: { value } };
|
|
2467
|
+
}
|
|
2468
|
+
function applyStateAppend(op, ctx) {
|
|
2469
|
+
const item = resolveOpSource(op.item, ctx), slot = locateSlot(
|
|
2470
|
+
ctx,
|
|
2471
|
+
op.target,
|
|
2472
|
+
/*createIfMissing*/
|
|
2473
|
+
!0
|
|
2474
|
+
);
|
|
2475
|
+
if (slot !== void 0) {
|
|
2476
|
+
validateSlotAppendItem({ slotType: slot._type, slotId: slot.id, item });
|
|
2477
|
+
const current = Array.isArray(slot.value) ? slot.value : [];
|
|
2478
|
+
slot.value = [...current, withRowKey(item)];
|
|
2479
|
+
}
|
|
2480
|
+
return { opType: op.type, target: op.target, resolved: { item } };
|
|
2481
|
+
}
|
|
2482
|
+
function withRowKey(item) {
|
|
2483
|
+
return typeof item == "object" && item !== null && !Array.isArray(item) && !("_key" in item) ? { _key: randomKey(), ...item } : item;
|
|
2484
|
+
}
|
|
2485
|
+
function applyStateUpdateWhere(op, ctx) {
|
|
2486
|
+
const slot = locateSlot(
|
|
2028
2487
|
ctx,
|
|
2029
2488
|
op.target,
|
|
2030
2489
|
/*createIfMissing*/
|
|
@@ -2132,382 +2591,89 @@ async function fireAction(args) {
|
|
|
2132
2591
|
if (!isRevisionConflict(error)) throw error;
|
|
2133
2592
|
}
|
|
2134
2593
|
}
|
|
2135
|
-
throw new ConcurrentFireActionError({
|
|
2136
|
-
instanceId,
|
|
2137
|
-
taskId,
|
|
2138
|
-
action,
|
|
2139
|
-
attempts: CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS
|
|
2140
|
-
});
|
|
2141
|
-
}
|
|
2142
|
-
async function resolveActionCommit(ctx, taskId, actionName, callerParams) {
|
|
2143
|
-
const { stage, task } = findTaskInCurrentStage(ctx, taskId), action = (task.actions ?? []).find((a) => a.id === actionName);
|
|
2144
|
-
if (action === void 0)
|
|
2145
|
-
throw new Error(`Action "${actionName}" not declared on task "${taskId}"`);
|
|
2146
|
-
if (!await ctxEvaluateFilter(ctx, action.filter))
|
|
2147
|
-
throw new Error(`Action "${actionName}" filter rejected on task "${taskId}"`);
|
|
2148
|
-
const entry = findCurrentTasks(ctx.instance).find((t) => t.id === taskId);
|
|
2149
|
-
if (entry === void 0 || entry.status !== "active")
|
|
2150
|
-
throw new Error(
|
|
2151
|
-
`Task "${taskId}" must be active to fire action "${actionName}"; status is ${entry?.status ?? "missing"}`
|
|
2152
|
-
);
|
|
2153
|
-
const params = validateActionParams(action, taskId, callerParams);
|
|
2154
|
-
return { stage, task, action, params };
|
|
2155
|
-
}
|
|
2156
|
-
function applyActionSetStatus(action, mutation, mutEntry, stageId, taskId, actor, now) {
|
|
2157
|
-
if (action.setStatus === void 0) return;
|
|
2158
|
-
const initialStatus = mutEntry.status, newStatus = action.setStatus;
|
|
2159
|
-
return mutEntry.status = newStatus, mutEntry.completedAt = now, actor && (mutEntry.completedBy = actor.id), mutation.history.push(
|
|
2160
|
-
taskStatusChangedEntry({
|
|
2161
|
-
stageId,
|
|
2162
|
-
taskId,
|
|
2163
|
-
from: initialStatus,
|
|
2164
|
-
to: newStatus,
|
|
2165
|
-
at: now,
|
|
2166
|
-
...actor !== void 0 ? { actor } : {}
|
|
2167
|
-
})
|
|
2168
|
-
), newStatus;
|
|
2169
|
-
}
|
|
2170
|
-
async function commitAction(ctx, taskId, actionName, callerParams, actor) {
|
|
2171
|
-
const { stage, task, action, params } = await resolveActionCommit(
|
|
2172
|
-
ctx,
|
|
2173
|
-
taskId,
|
|
2174
|
-
actionName,
|
|
2175
|
-
callerParams
|
|
2176
|
-
), mutation = startMutation(ctx.instance), mutEntry = requireMutationTaskEntry(mutation, taskId), now = ctx.now;
|
|
2177
|
-
mutation.history.push({
|
|
2178
|
-
_key: randomKey(),
|
|
2179
|
-
_type: "workflow.history.actionFired",
|
|
2180
|
-
at: now,
|
|
2181
|
-
stageId: stage.id,
|
|
2182
|
-
taskId,
|
|
2183
|
-
actionId: actionName,
|
|
2184
|
-
...actor !== void 0 ? { actor } : {}
|
|
2185
|
-
});
|
|
2186
|
-
const ranOps = await runActionOps({
|
|
2187
|
-
ops: action.ops,
|
|
2188
|
-
mutation,
|
|
2189
|
-
stageId: stage.id,
|
|
2190
|
-
taskId,
|
|
2191
|
-
actionName,
|
|
2192
|
-
params,
|
|
2193
|
-
actor,
|
|
2194
|
-
self: selfGdr(ctx.instance),
|
|
2195
|
-
now
|
|
2196
|
-
}), newStatus = applyActionSetStatus(action, mutation, mutEntry, stage.id, taskId, actor, now);
|
|
2197
|
-
return await queueEffects(
|
|
2198
|
-
ctx,
|
|
2199
|
-
mutation,
|
|
2200
|
-
action.effects,
|
|
2201
|
-
{
|
|
2202
|
-
kind: "action",
|
|
2203
|
-
id: `${task.id}:${actionName}`
|
|
2204
|
-
},
|
|
2205
|
-
actor,
|
|
2206
|
-
params
|
|
2207
|
-
), await persistThenDeploy(
|
|
2208
|
-
ctx,
|
|
2209
|
-
mutation,
|
|
2210
|
-
() => ranOps.some(isStateOp) ? refreshStageGuards(ctx, mutation, stage.id) : Promise.resolve()
|
|
2211
|
-
), {
|
|
2212
|
-
fired: !0,
|
|
2213
|
-
taskId,
|
|
2214
|
-
action: actionName,
|
|
2215
|
-
...newStatus !== void 0 ? { newStatus } : {},
|
|
2216
|
-
...ranOps.length > 0 ? { ranOps } : {}
|
|
2217
|
-
};
|
|
2218
|
-
}
|
|
2219
|
-
const parsedFilters = /* @__PURE__ */ new Map();
|
|
2220
|
-
async function matchesFilter(args) {
|
|
2221
|
-
const { document, filter, userId } = args;
|
|
2222
|
-
parsedFilters.has(filter) || parsedFilters.set(filter, parse(`*[${filter}]`));
|
|
2223
|
-
const ast = parsedFilters.get(filter), data = await (await evaluate(ast, {
|
|
2224
|
-
dataset: [document],
|
|
2225
|
-
...userId !== void 0 ? { identity: userId } : {}
|
|
2226
|
-
})).get();
|
|
2227
|
-
return Array.isArray(data) && data.length === 1;
|
|
2228
|
-
}
|
|
2229
|
-
async function grantsPermissionOn(args) {
|
|
2230
|
-
const { document, grants, permission, userId } = args;
|
|
2231
|
-
if (!document || grants.length === 0) return !1;
|
|
2232
|
-
for (const grant of grants)
|
|
2233
|
-
if (grant.permissions.includes(permission) && await matchesFilter({
|
|
2234
|
-
document,
|
|
2235
|
-
filter: grant.filter,
|
|
2236
|
-
...userId !== void 0 ? { userId } : {}
|
|
2237
|
-
}))
|
|
2238
|
-
return !0;
|
|
2239
|
-
return !1;
|
|
2240
|
-
}
|
|
2241
|
-
async function fetchGrants(args) {
|
|
2242
|
-
const { client, resourcePath, signal } = args;
|
|
2243
|
-
return client.request({ url: resourcePath, ...signal ? { signal } : {} });
|
|
2244
|
-
}
|
|
2245
|
-
const actorCache = /* @__PURE__ */ new WeakMap(), grantsCache = /* @__PURE__ */ new WeakMap();
|
|
2246
|
-
async function resolveAccess(client, args = {}) {
|
|
2247
|
-
const overrideActor = args.override?.actor, overrideGrants = args.override?.grants;
|
|
2248
|
-
if (overrideActor !== void 0 && overrideGrants !== void 0)
|
|
2249
|
-
return { actor: overrideActor, grants: overrideGrants };
|
|
2250
|
-
const requestFn = client.request === void 0 ? void 0 : (opts) => client.request(opts);
|
|
2251
|
-
if (requestFn === void 0) {
|
|
2252
|
-
if (overrideActor === void 0)
|
|
2253
|
-
throw new Error(
|
|
2254
|
-
"workflow: no actor available. The engine resolves the actor from the client's token via `client.request({ url: '/users/me' })`. Supply a real `@sanity/client` configured with a token, or pass an explicit `access.actor` override (the test bench provides one by default)."
|
|
2255
|
-
);
|
|
2256
|
-
return { actor: overrideActor };
|
|
2257
|
-
}
|
|
2258
|
-
const actorPromise = overrideActor !== void 0 ? Promise.resolve(overrideActor) : cachedActor(client, requestFn), grantsPromise = overrideGrants !== void 0 ? Promise.resolve(overrideGrants) : args.grantsFromPath !== void 0 ? cachedGrants(client, requestFn, args.grantsFromPath) : Promise.resolve(void 0), [actor, grants] = await Promise.all([actorPromise, grantsPromise]);
|
|
2259
|
-
if (actor === void 0)
|
|
2260
|
-
throw new Error(
|
|
2261
|
-
"workflow: failed to resolve actor from `/users/me`. The client is configured but the endpoint returned no usable identity \u2014 check the token, or pass `access.actor` explicitly."
|
|
2262
|
-
);
|
|
2263
|
-
return { actor, ...grants !== void 0 ? { grants } : {} };
|
|
2264
|
-
}
|
|
2265
|
-
function cachedActor(client, requestFn) {
|
|
2266
|
-
const cached = actorCache.get(client);
|
|
2267
|
-
if (cached !== void 0) return cached;
|
|
2268
|
-
const pending = fetchActor(requestFn).catch((err) => {
|
|
2269
|
-
throw actorCache.get(client) === pending && actorCache.delete(client), err;
|
|
2270
|
-
});
|
|
2271
|
-
return actorCache.set(client, pending), pending;
|
|
2272
|
-
}
|
|
2273
|
-
function cachedGrants(client, requestFn, resourcePath) {
|
|
2274
|
-
let byPath = grantsCache.get(client);
|
|
2275
|
-
byPath === void 0 && (byPath = /* @__PURE__ */ new Map(), grantsCache.set(client, byPath));
|
|
2276
|
-
let cached = byPath.get(resourcePath);
|
|
2277
|
-
return cached === void 0 && (cached = fetchGrantsCached(requestFn, resourcePath), byPath.set(resourcePath, cached)), cached;
|
|
2278
|
-
}
|
|
2279
|
-
async function fetchActor(requestFn) {
|
|
2280
|
-
let user;
|
|
2281
|
-
try {
|
|
2282
|
-
user = await requestFn({
|
|
2283
|
-
uri: "/users/me",
|
|
2284
|
-
tag: "workflow.access.resolve-actor"
|
|
2285
|
-
});
|
|
2286
|
-
} catch (err) {
|
|
2287
|
-
throw new Error(
|
|
2288
|
-
'workflow: /users/me request failed. The engine resolves the actor from the client\'s token via `client.request({ uri: "/users/me" })`. Check the token/connectivity, or pass an explicit `access.actor` override.',
|
|
2289
|
-
{ cause: err }
|
|
2290
|
-
);
|
|
2291
|
-
}
|
|
2292
|
-
if (!user || typeof user.id != "string" || user.id.length === 0) return;
|
|
2293
|
-
const roleNames = user.roles?.map((r) => r.name).filter((n) => !!n) ?? [];
|
|
2294
|
-
return {
|
|
2295
|
-
kind: "user",
|
|
2296
|
-
id: user.id,
|
|
2297
|
-
...roleNames.length > 0 ? { roles: roleNames } : {}
|
|
2298
|
-
};
|
|
2299
|
-
}
|
|
2300
|
-
async function fetchGrantsCached(requestFn, resourcePath) {
|
|
2301
|
-
try {
|
|
2302
|
-
return await fetchGrants({ client: { request: requestFn }, resourcePath });
|
|
2303
|
-
} catch (err) {
|
|
2304
|
-
console.warn(
|
|
2305
|
-
`workflow: failed to fetch grants from "${resourcePath}"; skipping permission gate. Original error: ${err instanceof Error ? err.message : String(err)}`
|
|
2306
|
-
);
|
|
2307
|
-
return;
|
|
2308
|
-
}
|
|
2309
|
-
}
|
|
2310
|
-
const TAG_RE = /^[a-z0-9][a-z0-9-]*$/;
|
|
2311
|
-
function validateTags(tags) {
|
|
2312
|
-
if (tags.length === 0)
|
|
2313
|
-
throw new Error("tags: must be a non-empty array");
|
|
2314
|
-
for (const tag of tags)
|
|
2315
|
-
if (!TAG_RE.test(tag))
|
|
2316
|
-
throw new Error(
|
|
2317
|
-
`tags: invalid tag "${tag}" \u2014 must match ${TAG_RE.source} (ASCII lowercase + digits + dashes, no leading dash, no dots)`
|
|
2318
|
-
);
|
|
2319
|
-
}
|
|
2320
|
-
function canonicalTag(tags) {
|
|
2321
|
-
return tags[0];
|
|
2322
|
-
}
|
|
2323
|
-
const WILDCARD_ROLE = "*";
|
|
2324
|
-
async function evaluateInstance(args) {
|
|
2325
|
-
const { client, tags, instanceId, resourceClients } = args, now = (args.clock ?? wallClock)();
|
|
2326
|
-
validateTags(tags);
|
|
2327
|
-
const { actor, grants } = await resolveAccess(client, {
|
|
2328
|
-
...args.access !== void 0 ? { override: args.access } : {},
|
|
2329
|
-
...args.grantsFromPath !== void 0 ? { grantsFromPath: args.grantsFromPath } : {}
|
|
2330
|
-
}), instance = await client.getDocument(instanceId);
|
|
2331
|
-
if (!instance)
|
|
2332
|
-
throw new Error(`Workflow instance "${instanceId}" not found`);
|
|
2333
|
-
if (!tags.some((t) => instance.tags?.includes(t)))
|
|
2334
|
-
throw new Error(`Workflow instance "${instanceId}" not visible to this engine (tag mismatch)`);
|
|
2335
|
-
const definition = parseDefinitionSnapshot(instance), stage = definition.stages.find((s) => s.id === instance.currentStageId);
|
|
2336
|
-
if (stage === void 0)
|
|
2337
|
-
throw new Error(
|
|
2338
|
-
`Instance "${instanceId}" currentStageId "${instance.currentStageId}" not in definition`
|
|
2339
|
-
);
|
|
2340
|
-
const snapshot = await hydrateSnapshot({
|
|
2341
|
-
client,
|
|
2342
|
-
clientForGdr: resourceClients !== void 0 ? (parsed) => resourceClients(parsed) ?? client : () => client,
|
|
2343
|
-
instance,
|
|
2344
|
-
definition
|
|
2345
|
-
}), currentTaskEntries = instance.stages.find(
|
|
2346
|
-
(s) => s.id === instance.currentStageId && s.exitedAt === void 0
|
|
2347
|
-
)?.tasks ?? [], taskEvaluations = [];
|
|
2348
|
-
for (const task of stage.tasks ?? [])
|
|
2349
|
-
taskEvaluations.push(
|
|
2350
|
-
await evaluateTask({
|
|
2351
|
-
task,
|
|
2352
|
-
statusEntry: currentTaskEntries.find((t) => t.id === task.id),
|
|
2353
|
-
instance,
|
|
2354
|
-
definition,
|
|
2355
|
-
actor,
|
|
2356
|
-
grants,
|
|
2357
|
-
snapshot,
|
|
2358
|
-
now
|
|
2359
|
-
})
|
|
2360
|
-
);
|
|
2361
|
-
const transitionEvaluations = [];
|
|
2362
|
-
for (const transition of stage.transitions ?? [])
|
|
2363
|
-
transitionEvaluations.push({
|
|
2364
|
-
transition,
|
|
2365
|
-
filterSatisfied: await evaluateFilter({
|
|
2366
|
-
filter: transition.filter,
|
|
2367
|
-
definition,
|
|
2368
|
-
snapshot,
|
|
2369
|
-
params: buildParams(instance, now)
|
|
2370
|
-
})
|
|
2371
|
-
});
|
|
2372
|
-
const currentStage = {
|
|
2373
|
-
stage,
|
|
2374
|
-
tasks: taskEvaluations,
|
|
2375
|
-
transitions: transitionEvaluations
|
|
2376
|
-
}, pendingOnYou = taskEvaluations.filter((t) => t.pendingOnActor), canInteract = taskEvaluations.some((t) => t.actions.some((a) => a.allowed));
|
|
2377
|
-
return {
|
|
2378
|
-
instance,
|
|
2379
|
-
definition,
|
|
2380
|
-
actor,
|
|
2381
|
-
currentStage,
|
|
2382
|
-
pendingOnYou,
|
|
2383
|
-
canInteract
|
|
2384
|
-
};
|
|
2385
|
-
}
|
|
2386
|
-
async function evaluateTask(args) {
|
|
2387
|
-
const { task, statusEntry, instance, definition, actor, grants, snapshot, now } = args, status = statusEntry?.status ?? "pending", actions = [];
|
|
2388
|
-
for (const action of task.actions ?? [])
|
|
2389
|
-
actions.push(
|
|
2390
|
-
await evaluateAction({
|
|
2391
|
-
action,
|
|
2392
|
-
task,
|
|
2393
|
-
...statusEntry !== void 0 ? { statusEntry } : {},
|
|
2394
|
-
status,
|
|
2395
|
-
instance,
|
|
2396
|
-
definition,
|
|
2397
|
-
actor,
|
|
2398
|
-
...grants !== void 0 ? { grants } : {},
|
|
2399
|
-
snapshot,
|
|
2400
|
-
now
|
|
2401
|
-
})
|
|
2402
|
-
);
|
|
2403
|
-
return {
|
|
2404
|
-
task,
|
|
2405
|
-
status,
|
|
2406
|
-
// "pending on actor" treats both `pending` and `active` as waiting on
|
|
2407
|
-
// the assignee — pending tasks just haven't been auto-invoked yet, but
|
|
2408
|
-
// they're still inbox items.
|
|
2409
|
-
pendingOnActor: (status === "active" || status === "pending") && actorIsAssignee(actor, task.assignees ?? []),
|
|
2410
|
-
actions
|
|
2411
|
-
};
|
|
2412
|
-
}
|
|
2413
|
-
async function evaluateAction(args) {
|
|
2414
|
-
const { action, task, status, instance, definition, actor, grants, snapshot, now } = args, syncReason = lifecycleReason(instance, definition, status) ?? actorReason(action, task, actor);
|
|
2415
|
-
if (syncReason !== void 0) return disabled(action, syncReason);
|
|
2416
|
-
const asyncReason = await permissionReason(action, instance, actor, grants) ?? await filterReason(action, instance, definition, snapshot, now);
|
|
2417
|
-
return asyncReason !== void 0 ? disabled(action, asyncReason) : { action, allowed: !0 };
|
|
2418
|
-
}
|
|
2419
|
-
function lifecycleReason(instance, definition, status) {
|
|
2420
|
-
if (instance.completedAt !== void 0)
|
|
2421
|
-
return { kind: "instance-completed", completedAt: instance.completedAt };
|
|
2422
|
-
if (definition.stages.find((s) => s.id === instance.currentStageId)?.kind === "terminal")
|
|
2423
|
-
return { kind: "stage-terminal", stageId: instance.currentStageId };
|
|
2424
|
-
if (status === "done" || status === "skipped" || status === "failed")
|
|
2425
|
-
return { kind: "task-not-active", status };
|
|
2426
|
-
}
|
|
2427
|
-
function actorReason(action, task, actor) {
|
|
2428
|
-
const actorRoles = actor.roles ?? [];
|
|
2429
|
-
if (action.roles !== void 0 && action.roles.length > 0 && !(actorRoles.includes(WILDCARD_ROLE) || action.roles.some((r) => actorRoles.includes(r))))
|
|
2430
|
-
return { kind: "role-required", required: [...action.roles], actorRoles: [...actorRoles] };
|
|
2431
|
-
if (action.requireAssignment === !0 && !actorIsAssignee(actor, task.assignees ?? []))
|
|
2432
|
-
return { kind: "not-assigned", assignees: [...task.assignees ?? []], actorId: actor.id };
|
|
2433
|
-
}
|
|
2434
|
-
async function permissionReason(action, instance, actor, grants) {
|
|
2435
|
-
if (grants === void 0) return;
|
|
2436
|
-
const permission = action.requiredPermission ?? "update";
|
|
2437
|
-
if (!await grantsPermissionOn({
|
|
2438
|
-
document: instance,
|
|
2439
|
-
grants,
|
|
2440
|
-
permission,
|
|
2441
|
-
userId: actor.id
|
|
2442
|
-
}))
|
|
2443
|
-
return { kind: "permission-denied", permission, matchingGrants: grants.length };
|
|
2444
|
-
}
|
|
2445
|
-
async function filterReason(action, instance, definition, snapshot, now) {
|
|
2446
|
-
if (!(action.filter === void 0 || await evaluateFilter({
|
|
2447
|
-
filter: action.filter,
|
|
2448
|
-
definition,
|
|
2449
|
-
snapshot,
|
|
2450
|
-
params: buildParams(instance, now)
|
|
2451
|
-
})))
|
|
2452
|
-
return { kind: "filter-failed", filter: action.filter };
|
|
2453
|
-
}
|
|
2454
|
-
function disabled(action, reason) {
|
|
2455
|
-
return { action, allowed: !1, disabledReason: reason };
|
|
2456
|
-
}
|
|
2457
|
-
function actorIsAssignee(actor, assignees) {
|
|
2458
|
-
if (assignees.length === 0) return !1;
|
|
2459
|
-
const actorRoles = actor.roles ?? [], wildcard = actorRoles.includes(WILDCARD_ROLE);
|
|
2460
|
-
for (const assignee of assignees)
|
|
2461
|
-
if (assignee.kind === "user" && assignee.id === actor.id || assignee.kind === "role" && (wildcard || actorRoles.includes(assignee.role)))
|
|
2462
|
-
return !0;
|
|
2463
|
-
return !1;
|
|
2464
|
-
}
|
|
2465
|
-
function parseDefinitionSnapshot(instance) {
|
|
2466
|
-
try {
|
|
2467
|
-
return JSON.parse(instance.definitionSnapshot);
|
|
2468
|
-
} catch (err) {
|
|
2469
|
-
throw new Error(
|
|
2470
|
-
`Failed to parse definitionSnapshot on instance "${instance._id}": ${err instanceof Error ? err.message : String(err)}`,
|
|
2471
|
-
{ cause: err }
|
|
2472
|
-
);
|
|
2473
|
-
}
|
|
2474
|
-
}
|
|
2475
|
-
const resourceKey = (r) => `${r.type}.${r.id}`;
|
|
2476
|
-
function guardsForResource(client) {
|
|
2477
|
-
return client.fetch("*[_type == $t] | order(_id asc)", { t: GUARD_DOC_TYPE });
|
|
2478
|
-
}
|
|
2479
|
-
async function guardsForInstance(args) {
|
|
2480
|
-
const { client, clientForGdr, instance } = args, clientsByResource = /* @__PURE__ */ new Map([
|
|
2481
|
-
[resourceKey(instance.workflowResource), client]
|
|
2482
|
-
]), stateUris = [
|
|
2483
|
-
...collectSlotDocUris(instance.state),
|
|
2484
|
-
...instance.stages.flatMap((stage) => collectSlotDocUris(stage.state))
|
|
2485
|
-
];
|
|
2486
|
-
for (const uri of stateUris) {
|
|
2487
|
-
const parsed = tryParseGdr(uri);
|
|
2488
|
-
if (parsed === void 0) continue;
|
|
2489
|
-
const key = resourceKey(resourceFromParsed(parsed));
|
|
2490
|
-
clientsByResource.has(key) || clientsByResource.set(key, clientForGdr(parsed));
|
|
2491
|
-
}
|
|
2492
|
-
const perResource = await Promise.all(
|
|
2493
|
-
[...clientsByResource.values()].map(
|
|
2494
|
-
(c) => c.fetch("*[_type == $t && sourceInstanceId == $id]", {
|
|
2495
|
-
t: GUARD_DOC_TYPE,
|
|
2496
|
-
id: instance._id
|
|
2497
|
-
})
|
|
2498
|
-
)
|
|
2499
|
-
);
|
|
2500
|
-
return dedupById(perResource.flat());
|
|
2594
|
+
throw new ConcurrentFireActionError({
|
|
2595
|
+
instanceId,
|
|
2596
|
+
taskId,
|
|
2597
|
+
action,
|
|
2598
|
+
attempts: CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS
|
|
2599
|
+
});
|
|
2501
2600
|
}
|
|
2502
|
-
function
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2601
|
+
async function resolveActionCommit(ctx, taskId, actionName, callerParams) {
|
|
2602
|
+
const { stage, task } = findTaskInCurrentStage(ctx, taskId), action = (task.actions ?? []).find((a) => a.id === actionName);
|
|
2603
|
+
if (action === void 0)
|
|
2604
|
+
throw new Error(`Action "${actionName}" not declared on task "${taskId}"`);
|
|
2605
|
+
if (!await ctxEvaluateFilter(ctx, action.filter))
|
|
2606
|
+
throw new Error(`Action "${actionName}" filter rejected on task "${taskId}"`);
|
|
2607
|
+
const entry = findCurrentTasks(ctx.instance).find((t) => t.id === taskId);
|
|
2608
|
+
if (entry === void 0 || entry.status !== "active")
|
|
2609
|
+
throw new Error(
|
|
2610
|
+
`Task "${taskId}" must be active to fire action "${actionName}"; status is ${entry?.status ?? "missing"}`
|
|
2611
|
+
);
|
|
2612
|
+
const params = validateActionParams(action, taskId, callerParams);
|
|
2613
|
+
return { stage, task, action, params };
|
|
2508
2614
|
}
|
|
2509
|
-
function
|
|
2510
|
-
|
|
2615
|
+
function applyActionSetStatus(action, mutation, mutEntry, stageId, taskId, actor, now) {
|
|
2616
|
+
if (action.setStatus === void 0) return;
|
|
2617
|
+
const initialStatus = mutEntry.status, newStatus = action.setStatus;
|
|
2618
|
+
return mutEntry.status = newStatus, mutEntry.completedAt = now, actor && (mutEntry.completedBy = actor.id), mutation.history.push(
|
|
2619
|
+
taskStatusChangedEntry({
|
|
2620
|
+
stageId,
|
|
2621
|
+
taskId,
|
|
2622
|
+
from: initialStatus,
|
|
2623
|
+
to: newStatus,
|
|
2624
|
+
at: now,
|
|
2625
|
+
...actor !== void 0 ? { actor } : {}
|
|
2626
|
+
})
|
|
2627
|
+
), newStatus;
|
|
2628
|
+
}
|
|
2629
|
+
async function commitAction(ctx, taskId, actionName, callerParams, actor) {
|
|
2630
|
+
const { stage, task, action, params } = await resolveActionCommit(
|
|
2631
|
+
ctx,
|
|
2632
|
+
taskId,
|
|
2633
|
+
actionName,
|
|
2634
|
+
callerParams
|
|
2635
|
+
), mutation = startMutation(ctx.instance), mutEntry = requireMutationTaskEntry(mutation, taskId), now = ctx.now;
|
|
2636
|
+
mutation.history.push({
|
|
2637
|
+
_key: randomKey(),
|
|
2638
|
+
_type: "workflow.history.actionFired",
|
|
2639
|
+
at: now,
|
|
2640
|
+
stageId: stage.id,
|
|
2641
|
+
taskId,
|
|
2642
|
+
actionId: actionName,
|
|
2643
|
+
...actor !== void 0 ? { actor } : {}
|
|
2644
|
+
});
|
|
2645
|
+
const ranOps = await runActionOps({
|
|
2646
|
+
ops: action.ops,
|
|
2647
|
+
mutation,
|
|
2648
|
+
stageId: stage.id,
|
|
2649
|
+
taskId,
|
|
2650
|
+
actionName,
|
|
2651
|
+
params,
|
|
2652
|
+
actor,
|
|
2653
|
+
self: selfGdr(ctx.instance),
|
|
2654
|
+
now
|
|
2655
|
+
}), newStatus = applyActionSetStatus(action, mutation, mutEntry, stage.id, taskId, actor, now);
|
|
2656
|
+
return await queueEffects(
|
|
2657
|
+
ctx,
|
|
2658
|
+
mutation,
|
|
2659
|
+
action.effects,
|
|
2660
|
+
{
|
|
2661
|
+
kind: "action",
|
|
2662
|
+
id: `${task.id}:${actionName}`
|
|
2663
|
+
},
|
|
2664
|
+
actor,
|
|
2665
|
+
params
|
|
2666
|
+
), await persistThenDeploy(
|
|
2667
|
+
ctx,
|
|
2668
|
+
mutation,
|
|
2669
|
+
() => ranOps.some(isStateOp) ? refreshStageGuards(ctx, mutation, stage.id) : Promise.resolve()
|
|
2670
|
+
), {
|
|
2671
|
+
fired: !0,
|
|
2672
|
+
taskId,
|
|
2673
|
+
action: actionName,
|
|
2674
|
+
...newStatus !== void 0 ? { newStatus } : {},
|
|
2675
|
+
...ranOps.length > 0 ? { ranOps } : {}
|
|
2676
|
+
};
|
|
2511
2677
|
}
|
|
2512
2678
|
class ActionDisabledError extends Error {
|
|
2513
2679
|
reason;
|
|
@@ -2531,121 +2697,6 @@ function formatDisabledReason(taskId, action, reason) {
|
|
|
2531
2697
|
const detail = disabledReasonDetail[reason.kind](reason);
|
|
2532
2698
|
return `Action "${taskId}:${action}" is not allowed: ${detail}`;
|
|
2533
2699
|
}
|
|
2534
|
-
function definitionDocId(_workflowResource, prefix, workflowId, version) {
|
|
2535
|
-
return `${prefix}.${workflowId}.v${version}`;
|
|
2536
|
-
}
|
|
2537
|
-
async function sortByDependencies(client, definitions, tags, workflowResource) {
|
|
2538
|
-
const prefix = canonicalTag(tags), byWorkflowId = /* @__PURE__ */ new Map();
|
|
2539
|
-
for (const def of definitions) {
|
|
2540
|
-
const list = byWorkflowId.get(def.workflowId) ?? [];
|
|
2541
|
-
list.push(def), byWorkflowId.set(def.workflowId, list);
|
|
2542
|
-
}
|
|
2543
|
-
const findInBatch = (ref) => findRefInBatch(byWorkflowId, ref);
|
|
2544
|
-
return await assertCrossBatchRefsDeployed(client, definitions, {
|
|
2545
|
-
tags,
|
|
2546
|
-
workflowResource,
|
|
2547
|
-
prefix,
|
|
2548
|
-
findInBatch
|
|
2549
|
-
}), topoSortDefinitions(definitions, { workflowResource, prefix, findInBatch });
|
|
2550
|
-
}
|
|
2551
|
-
function findRefInBatch(byWorkflowId, ref) {
|
|
2552
|
-
const candidates = byWorkflowId.get(ref.workflowId);
|
|
2553
|
-
if (!(candidates === void 0 || candidates.length === 0))
|
|
2554
|
-
return typeof ref.version == "number" ? candidates.find((c) => c.version === ref.version) : candidates.reduce(
|
|
2555
|
-
(best, c) => best === void 0 || c.version > best.version ? c : best,
|
|
2556
|
-
void 0
|
|
2557
|
-
);
|
|
2558
|
-
}
|
|
2559
|
-
async function assertCrossBatchRefsDeployed(client, definitions, ctx) {
|
|
2560
|
-
const missing = [];
|
|
2561
|
-
for (const def of definitions) {
|
|
2562
|
-
const fromId = definitionDocId(ctx.workflowResource, ctx.prefix, def.workflowId, def.version);
|
|
2563
|
-
for (const ref of refsOf(def)) {
|
|
2564
|
-
if (ctx.findInBatch(ref) !== void 0) continue;
|
|
2565
|
-
const label = await resolveDeployedRefLabel(client, ref, ctx.tags);
|
|
2566
|
-
label !== void 0 && missing.push({ from: fromId, ref: label });
|
|
2567
|
-
}
|
|
2568
|
-
}
|
|
2569
|
-
if (missing.length === 0) return;
|
|
2570
|
-
const lines = missing.map((m) => ` - ${m.from} \u2192 ${m.ref} (neither in batch nor deployed)`);
|
|
2571
|
-
throw new Error(
|
|
2572
|
-
`workflow.deployDefinitions: ${missing.length} unresolved reference${missing.length === 1 ? "" : "s"}:
|
|
2573
|
-
` + lines.join(`
|
|
2574
|
-
`)
|
|
2575
|
-
);
|
|
2576
|
-
}
|
|
2577
|
-
async function resolveDeployedRefLabel(client, ref, tags) {
|
|
2578
|
-
const wantsExplicit = typeof ref.version == "number", params = { workflowId: ref.workflowId, tags };
|
|
2579
|
-
if (wantsExplicit && (params.version = ref.version), !await client.fetch(
|
|
2580
|
-
definitionLookupGroq(wantsExplicit),
|
|
2581
|
-
params
|
|
2582
|
-
))
|
|
2583
|
-
return wantsExplicit ? `${ref.workflowId} v${ref.version}` : ref.workflowId;
|
|
2584
|
-
}
|
|
2585
|
-
function topoSortDefinitions(definitions, ctx) {
|
|
2586
|
-
const visited = /* @__PURE__ */ new Set(), visiting = /* @__PURE__ */ new Set(), ordered = [], visit = (def) => {
|
|
2587
|
-
const id = definitionDocId(ctx.workflowResource, ctx.prefix, def.workflowId, def.version);
|
|
2588
|
-
if (!visited.has(id)) {
|
|
2589
|
-
if (visiting.has(id))
|
|
2590
|
-
throw new Error(`workflow.deployDefinitions: dependency cycle involving "${id}"`);
|
|
2591
|
-
visiting.add(id);
|
|
2592
|
-
for (const ref of refsOf(def)) {
|
|
2593
|
-
const child = ctx.findInBatch(ref);
|
|
2594
|
-
child !== void 0 && visit(child);
|
|
2595
|
-
}
|
|
2596
|
-
visiting.delete(id), visited.add(id), ordered.push(def);
|
|
2597
|
-
}
|
|
2598
|
-
};
|
|
2599
|
-
for (const def of definitions) visit(def);
|
|
2600
|
-
return ordered;
|
|
2601
|
-
}
|
|
2602
|
-
function definitionLookupGroq(wantsExplicit) {
|
|
2603
|
-
return wantsExplicit ? '*[_type == "workflow.definition" && workflowId == $workflowId && version == $version && count(tags[@ in $tags]) > 0][0]' : '*[_type == "workflow.definition" && workflowId == $workflowId && count(tags[@ in $tags]) > 0] | order(version desc)[0]';
|
|
2604
|
-
}
|
|
2605
|
-
function refsOf(def) {
|
|
2606
|
-
const out = [];
|
|
2607
|
-
for (const stage of def.stages)
|
|
2608
|
-
for (const task of stage.tasks ?? []) {
|
|
2609
|
-
const ref = task.spawns?.definitionRef;
|
|
2610
|
-
ref !== void 0 && out.push({
|
|
2611
|
-
workflowId: ref.workflowId,
|
|
2612
|
-
...ref.version !== void 0 ? { version: ref.version } : {}
|
|
2613
|
-
});
|
|
2614
|
-
}
|
|
2615
|
-
return out;
|
|
2616
|
-
}
|
|
2617
|
-
function isDefinitionUnchanged(existing, expected) {
|
|
2618
|
-
return stableStringify(stripSystemFields(existing)) === stableStringify(stripSystemFields(expected));
|
|
2619
|
-
}
|
|
2620
|
-
function stripSystemFields(doc) {
|
|
2621
|
-
const out = {};
|
|
2622
|
-
for (const [k, v2] of Object.entries(doc))
|
|
2623
|
-
k === "_rev" || k === "_createdAt" || k === "_updatedAt" || (out[k] = v2);
|
|
2624
|
-
return out;
|
|
2625
|
-
}
|
|
2626
|
-
function stableStringify(value) {
|
|
2627
|
-
return value === null || typeof value != "object" ? JSON.stringify(value) : Array.isArray(value) ? `[${value.map(stableStringify).join(",")}]` : `{${Object.entries(value).toSorted(
|
|
2628
|
-
([a], [b]) => a.localeCompare(b)
|
|
2629
|
-
).map(([k, v2]) => `${JSON.stringify(k)}:${stableStringify(v2)}`).join(",")}}`;
|
|
2630
|
-
}
|
|
2631
|
-
async function loadDefinition(client, workflowId, version, tags) {
|
|
2632
|
-
if (version !== void 0) {
|
|
2633
|
-
const doc = await client.fetch(
|
|
2634
|
-
"*[_type == 'workflow.definition' && workflowId == $id && version == $v && count(tags[@ in $engineTags]) > 0][0]",
|
|
2635
|
-
{ id: workflowId, v: version, engineTags: tags }
|
|
2636
|
-
);
|
|
2637
|
-
if (!doc)
|
|
2638
|
-
throw new Error(`Workflow definition ${workflowId} v${version} not deployed`);
|
|
2639
|
-
return doc;
|
|
2640
|
-
}
|
|
2641
|
-
const latest = await client.fetch(
|
|
2642
|
-
"*[_type == 'workflow.definition' && workflowId == $id && count(tags[@ in $engineTags]) > 0] | order(version desc)[0]",
|
|
2643
|
-
{ id: workflowId, engineTags: tags }
|
|
2644
|
-
);
|
|
2645
|
-
if (!latest)
|
|
2646
|
-
throw new Error(`No deployed definition for workflow ${workflowId}`);
|
|
2647
|
-
return latest;
|
|
2648
|
-
}
|
|
2649
2700
|
function bareIdFromSpawnRef(uri) {
|
|
2650
2701
|
if (!uri.includes(":")) return [uri];
|
|
2651
2702
|
try {
|
|
@@ -2666,8 +2717,15 @@ async function resolveOperationContext(args) {
|
|
|
2666
2717
|
clientForGdr: buildClientForGdr(args.client, args.resourceClients)
|
|
2667
2718
|
};
|
|
2668
2719
|
}
|
|
2669
|
-
async function cascade(client, instanceId, actor, clientForGdr, clock) {
|
|
2670
|
-
const count = await cascadeAutoTransitions(
|
|
2720
|
+
async function cascade(client, instanceId, actor, clientForGdr, clock, overlay) {
|
|
2721
|
+
const count = await cascadeAutoTransitions(
|
|
2722
|
+
client,
|
|
2723
|
+
instanceId,
|
|
2724
|
+
actor,
|
|
2725
|
+
clientForGdr,
|
|
2726
|
+
clock,
|
|
2727
|
+
overlay
|
|
2728
|
+
);
|
|
2671
2729
|
return await propagateToAncestors(client, instanceId, actor, clientForGdr, clock), count;
|
|
2672
2730
|
}
|
|
2673
2731
|
function buildClientForGdr(defaultClient, resolver) {
|
|
@@ -2686,6 +2744,22 @@ function intersectsTags(docTags, engineTags) {
|
|
|
2686
2744
|
function toEffectsContextEntries(ctx) {
|
|
2687
2745
|
return Object.entries(ctx).map(([id, value]) => effectsContextEntry(id, value));
|
|
2688
2746
|
}
|
|
2747
|
+
function assertActionAllowed(evaluation, taskId, action) {
|
|
2748
|
+
const actionEval = evaluation.currentStage.tasks.find((t) => t.task.id === taskId)?.actions.find((a) => a.action.id === action);
|
|
2749
|
+
if (actionEval?.allowed === !1 && actionEval.disabledReason)
|
|
2750
|
+
throw new ActionDisabledError({ taskId, action, reason: actionEval.disabledReason });
|
|
2751
|
+
}
|
|
2752
|
+
async function applyAction(args) {
|
|
2753
|
+
const { client, instance, taskId, action, params, actor, clock } = args, options = { actor, ...clock !== void 0 ? { clock } : {} };
|
|
2754
|
+
return findOpenStageEntry(instance)?.tasks.find((t) => t.id === taskId)?.status === "pending" && await invokeTask({ client, instanceId: instance._id, taskId, options }), (await fireAction({
|
|
2755
|
+
client,
|
|
2756
|
+
instanceId: instance._id,
|
|
2757
|
+
taskId,
|
|
2758
|
+
action,
|
|
2759
|
+
...params !== void 0 ? { params } : {},
|
|
2760
|
+
options
|
|
2761
|
+
})).ranOps;
|
|
2762
|
+
}
|
|
2689
2763
|
const workflow = {
|
|
2690
2764
|
/**
|
|
2691
2765
|
* Persist a workflow definition to the lake. Stored as a
|
|
@@ -2718,7 +2792,7 @@ const workflow = {
|
|
|
2718
2792
|
const id = definitionDocId(workflowResource, prefix, def.workflowId, def.version), expected = {
|
|
2719
2793
|
...def,
|
|
2720
2794
|
_id: id,
|
|
2721
|
-
_type:
|
|
2795
|
+
_type: WORKFLOW_DEFINITION_TYPE,
|
|
2722
2796
|
tags
|
|
2723
2797
|
}, existing = await client.getDocument(id);
|
|
2724
2798
|
if (existing && intersectsTags(existing.tags, tags) && isDefinitionUnchanged(existing, expected)) {
|
|
@@ -2814,44 +2888,32 @@ const workflow = {
|
|
|
2814
2888
|
params,
|
|
2815
2889
|
idempotent,
|
|
2816
2890
|
resourceClients
|
|
2817
|
-
} = args, { access, actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, before = await reload(client, instanceId, tags)
|
|
2818
|
-
|
|
2819
|
-
)?.tasks.find((t) => t.id === taskId);
|
|
2820
|
-
if (taskEntry === void 0 && idempotent === !0)
|
|
2891
|
+
} = args, { access, actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, before = await reload(client, instanceId, tags);
|
|
2892
|
+
if (findOpenStageEntry(before)?.tasks.find((t) => t.id === taskId) === void 0 && idempotent === !0)
|
|
2821
2893
|
return { instance: before, cascaded: 0, fired: !1 };
|
|
2822
|
-
const
|
|
2894
|
+
const evaluation = await evaluateInstance({
|
|
2823
2895
|
client,
|
|
2824
2896
|
tags,
|
|
2825
2897
|
instanceId,
|
|
2826
2898
|
access,
|
|
2827
2899
|
clock,
|
|
2828
2900
|
...resourceClients !== void 0 ? { resourceClients } : {}
|
|
2829
|
-
})).currentStage.tasks.find((t) => t.task.id === taskId)?.actions.find((a) => a.action.id === action);
|
|
2830
|
-
if (actionEval !== void 0 && !actionEval.allowed && actionEval.disabledReason)
|
|
2831
|
-
throw new ActionDisabledError({
|
|
2832
|
-
taskId,
|
|
2833
|
-
action,
|
|
2834
|
-
reason: actionEval.disabledReason
|
|
2835
|
-
});
|
|
2836
|
-
taskEntry?.status === "pending" && await invokeTask({
|
|
2837
|
-
client,
|
|
2838
|
-
instanceId,
|
|
2839
|
-
taskId,
|
|
2840
|
-
options: { actor, clock }
|
|
2841
2901
|
});
|
|
2842
|
-
|
|
2902
|
+
assertActionAllowed(evaluation, taskId, action);
|
|
2903
|
+
const ranOps = await applyAction({
|
|
2843
2904
|
client,
|
|
2844
|
-
|
|
2905
|
+
instance: before,
|
|
2845
2906
|
taskId,
|
|
2846
2907
|
action,
|
|
2847
|
-
|
|
2848
|
-
|
|
2908
|
+
actor,
|
|
2909
|
+
clock,
|
|
2910
|
+
...params !== void 0 ? { params } : {}
|
|
2849
2911
|
}), cascaded = await cascade(client, instanceId, actor, clientForGdr, clock);
|
|
2850
2912
|
return {
|
|
2851
2913
|
instance: await reload(client, instanceId, tags),
|
|
2852
2914
|
cascaded,
|
|
2853
2915
|
fired: !0,
|
|
2854
|
-
...
|
|
2916
|
+
...ranOps !== void 0 ? { ranOps } : {}
|
|
2855
2917
|
};
|
|
2856
2918
|
},
|
|
2857
2919
|
/**
|
|
@@ -2976,7 +3038,7 @@ const workflow = {
|
|
|
2976
3038
|
queryInScope: async (args) => {
|
|
2977
3039
|
const { client, tags, instanceId, groq, params, resourceClients } = args, clock = args.clock ?? wallClock;
|
|
2978
3040
|
validateTags(tags);
|
|
2979
|
-
const instance = await reload(client, instanceId, tags),
|
|
3041
|
+
const instance = await reload(client, instanceId, tags), clientForGdr = buildClientForGdr(client, resourceClients), snapshot = await hydrateSnapshot({ client, clientForGdr, instance }), reserved = buildParams(instance, clock()), tree = parse(groq, { params: { ...reserved, ...params } });
|
|
2980
3042
|
return await (await evaluate(tree, {
|
|
2981
3043
|
dataset: snapshot.docs,
|
|
2982
3044
|
params: { ...reserved, ...params }
|
|
@@ -3019,7 +3081,7 @@ const workflow = {
|
|
|
3019
3081
|
validateTags(tags);
|
|
3020
3082
|
const parent = await reload(client, instanceId, tags), isSpawned = (h) => h._type === "workflow.history.spawned", ids = parent.history.filter(isSpawned).filter((h) => taskId === void 0 || h.taskId === taskId).flatMap((h) => bareIdFromSpawnRef(h.instanceRef.id));
|
|
3021
3083
|
return ids.length === 0 ? [] : client.fetch(
|
|
3022
|
-
|
|
3084
|
+
`*[_id in $ids && ${tagScopeFilter()}] | order(startedAt asc)`,
|
|
3023
3085
|
{ ids, engineTags: tags }
|
|
3024
3086
|
);
|
|
3025
3087
|
},
|
|
@@ -3038,7 +3100,7 @@ async function verifyDeployedDefinitionsInternal(args) {
|
|
|
3038
3100
|
const { client, tags, effectHandlers, missingHandler, logger } = args;
|
|
3039
3101
|
validateTags(tags);
|
|
3040
3102
|
const log = logger("verifyDeployedDefinitions"), definitions = await client.fetch(
|
|
3041
|
-
|
|
3103
|
+
`*[_type == "${WORKFLOW_DEFINITION_TYPE}" && ${tagScopeFilter()}] | order(workflowId asc, version asc)`,
|
|
3042
3104
|
{ engineTags: tags }
|
|
3043
3105
|
), seen = [], missingByName = /* @__PURE__ */ new Map();
|
|
3044
3106
|
for (const def of definitions)
|
|
@@ -3189,6 +3251,89 @@ async function applyMissingHandler(policy, info, log) {
|
|
|
3189
3251
|
return "fail";
|
|
3190
3252
|
}
|
|
3191
3253
|
}
|
|
3254
|
+
function createInstanceSession(args) {
|
|
3255
|
+
const { client, tags, clock } = args, clientForGdr = buildClientForGdr(client, args.resourceClients);
|
|
3256
|
+
let instance = asHeldInstance(args.instance), overlay = /* @__PURE__ */ new Map(), committing = !1, buffered;
|
|
3257
|
+
const selfUri = () => gdrFromResource(instance.workflowResource, instance._id), applyUpdate = (docs) => {
|
|
3258
|
+
const next = /* @__PURE__ */ new Map();
|
|
3259
|
+
for (const ld of docs) {
|
|
3260
|
+
const owned = { doc: structuredClone(ld.doc), resource: ld.resource }, uri = gdrFromResource(owned.resource, owned.doc._id);
|
|
3261
|
+
if (uri === selfUri()) {
|
|
3262
|
+
instance = asHeldInstance(owned.doc);
|
|
3263
|
+
continue;
|
|
3264
|
+
}
|
|
3265
|
+
next.set(uri, owned);
|
|
3266
|
+
}
|
|
3267
|
+
overlay = next;
|
|
3268
|
+
}, access = () => resolveAccess(client, {
|
|
3269
|
+
...args.access !== void 0 ? { override: args.access } : {},
|
|
3270
|
+
...args.grantsFromPath !== void 0 ? { grantsFromPath: args.grantsFromPath } : {}
|
|
3271
|
+
}), snapshotFrom = (held) => buildSnapshot({ docs: [{ doc: instance, resource: instance.workflowResource }, ...held.values()] }), evaluateWith = async (held) => {
|
|
3272
|
+
const { actor, grants } = await access();
|
|
3273
|
+
return evaluateFromSnapshot({
|
|
3274
|
+
instance,
|
|
3275
|
+
definition: parseDefinitionSnapshot(instance),
|
|
3276
|
+
actor,
|
|
3277
|
+
snapshot: snapshotFrom(held),
|
|
3278
|
+
...clock !== void 0 ? { now: clock() } : {},
|
|
3279
|
+
...grants !== void 0 ? { grants } : {}
|
|
3280
|
+
});
|
|
3281
|
+
}, commit = async (run) => {
|
|
3282
|
+
committing = !0;
|
|
3283
|
+
const held = new Map(overlay);
|
|
3284
|
+
try {
|
|
3285
|
+
const { actor } = await access();
|
|
3286
|
+
return await run(actor, held);
|
|
3287
|
+
} finally {
|
|
3288
|
+
if (committing = !1, buffered !== void 0) {
|
|
3289
|
+
const next = buffered;
|
|
3290
|
+
buffered = void 0, applyUpdate(next);
|
|
3291
|
+
}
|
|
3292
|
+
}
|
|
3293
|
+
};
|
|
3294
|
+
return {
|
|
3295
|
+
get subscriptionDocuments() {
|
|
3296
|
+
return subscriptionDocumentsForInstance(instance);
|
|
3297
|
+
},
|
|
3298
|
+
update(docs) {
|
|
3299
|
+
if (committing) {
|
|
3300
|
+
buffered = docs;
|
|
3301
|
+
return;
|
|
3302
|
+
}
|
|
3303
|
+
applyUpdate(docs);
|
|
3304
|
+
},
|
|
3305
|
+
evaluate() {
|
|
3306
|
+
return evaluateWith(overlay);
|
|
3307
|
+
},
|
|
3308
|
+
tick() {
|
|
3309
|
+
return commit(async (actor, held) => {
|
|
3310
|
+
const cascaded = await cascade(client, instance._id, actor, clientForGdr, clock, held);
|
|
3311
|
+
return instance = await reload(client, instance._id, tags), { instance, cascaded, fired: cascaded > 0 };
|
|
3312
|
+
});
|
|
3313
|
+
},
|
|
3314
|
+
fireAction({ taskId, action, params }) {
|
|
3315
|
+
return commit(async (actor, held) => {
|
|
3316
|
+
assertActionAllowed(await evaluateWith(held), taskId, action);
|
|
3317
|
+
const ranOps = await applyAction({
|
|
3318
|
+
client,
|
|
3319
|
+
instance,
|
|
3320
|
+
taskId,
|
|
3321
|
+
action,
|
|
3322
|
+
actor,
|
|
3323
|
+
...clock !== void 0 ? { clock } : {},
|
|
3324
|
+
...params !== void 0 ? { params } : {}
|
|
3325
|
+
}), cascaded = await cascade(client, instance._id, actor, clientForGdr, clock, held);
|
|
3326
|
+
return instance = await reload(client, instance._id, tags), { instance, cascaded, fired: !0, ...ranOps !== void 0 ? { ranOps } : {} };
|
|
3327
|
+
});
|
|
3328
|
+
}
|
|
3329
|
+
};
|
|
3330
|
+
}
|
|
3331
|
+
function asHeldInstance(doc) {
|
|
3332
|
+
const candidate = doc;
|
|
3333
|
+
if (candidate._type !== "workflow.instance" || typeof candidate.currentStageId != "string" || !Array.isArray(candidate.stages))
|
|
3334
|
+
throw new Error(`update: self-doc ${doc._id} is not a valid workflow.instance`);
|
|
3335
|
+
return doc;
|
|
3336
|
+
}
|
|
3192
3337
|
const defaultLoggerFactory = (name) => ({
|
|
3193
3338
|
// info/warn/error all go to stderr — these are diagnostic, not
|
|
3194
3339
|
// program output. Keeps stdout reserved for the caller's own data
|
|
@@ -3231,6 +3376,16 @@ function createEngine(args) {
|
|
|
3231
3376
|
evaluateInstance: (rest) => evaluateInstance(bind(rest)),
|
|
3232
3377
|
setStage: (rest) => workflow.setStage(bind(rest)),
|
|
3233
3378
|
getInstance: ({ instanceId }) => workflow.getInstance({ client, tags, workflowResource, instanceId }),
|
|
3379
|
+
subscriptionDocumentsForInstance: ({ instanceId }) => workflow.getInstance({ client, tags, workflowResource, instanceId }).then(subscriptionDocumentsForInstance),
|
|
3380
|
+
instance: (instanceDoc, opts) => createInstanceSession({
|
|
3381
|
+
client,
|
|
3382
|
+
tags,
|
|
3383
|
+
instance: instanceDoc,
|
|
3384
|
+
...resourceClients !== void 0 ? { resourceClients } : {},
|
|
3385
|
+
...clock !== void 0 ? { clock } : {},
|
|
3386
|
+
...opts?.access !== void 0 ? { access: opts.access } : {},
|
|
3387
|
+
...opts?.grantsFromPath !== void 0 ? { grantsFromPath: opts.grantsFromPath } : {}
|
|
3388
|
+
}),
|
|
3234
3389
|
guardsForInstance: ({ instanceId }) => workflow.guardsForInstance({
|
|
3235
3390
|
client,
|
|
3236
3391
|
tags,
|
|
@@ -3496,15 +3651,20 @@ export {
|
|
|
3496
3651
|
MutationGuardDeniedError,
|
|
3497
3652
|
OP_DISPLAY,
|
|
3498
3653
|
STATE_SLOT_DISPLAY,
|
|
3654
|
+
WORKFLOW_DEFINITION_TYPE,
|
|
3655
|
+
WORKFLOW_INSTANCE_TYPE,
|
|
3499
3656
|
WorkflowStateDivergedError,
|
|
3657
|
+
buildSnapshot,
|
|
3500
3658
|
canonicalTag,
|
|
3501
3659
|
compileGuard,
|
|
3660
|
+
contentReleaseName,
|
|
3502
3661
|
createEngine,
|
|
3503
3662
|
defaultLoggerFactory,
|
|
3504
3663
|
denyingGuards,
|
|
3505
3664
|
deployStageGuards,
|
|
3506
3665
|
displayDescription,
|
|
3507
3666
|
displayTitle,
|
|
3667
|
+
evaluateFromSnapshot,
|
|
3508
3668
|
evaluateMutationGuard,
|
|
3509
3669
|
extractDocumentId,
|
|
3510
3670
|
gdrFromResource,
|
|
@@ -3517,14 +3677,18 @@ export {
|
|
|
3517
3677
|
isGdr,
|
|
3518
3678
|
lakeGuardId,
|
|
3519
3679
|
parseGdr,
|
|
3680
|
+
readsRaw,
|
|
3520
3681
|
refCanvas,
|
|
3521
3682
|
refDashboard,
|
|
3522
3683
|
refDataset,
|
|
3523
3684
|
refMediaLibrary,
|
|
3524
3685
|
resolveAccess,
|
|
3686
|
+
resourceFromParsed,
|
|
3525
3687
|
retractStageGuards,
|
|
3526
3688
|
silentLogger,
|
|
3527
3689
|
stripSystemFields,
|
|
3690
|
+
subscriptionDocumentsForInstance,
|
|
3691
|
+
tagScopeFilter,
|
|
3528
3692
|
validateDefinition,
|
|
3529
3693
|
validateTags,
|
|
3530
3694
|
wallClock,
|