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