@sanity/workflow-engine 0.8.0 → 0.10.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 +112 -65
- package/dist/_chunks-cjs/schema.cjs.map +1 -1
- package/dist/_chunks-es/schema.js +112 -65
- package/dist/_chunks-es/schema.js.map +1 -1
- package/dist/define.cjs +128 -65
- package/dist/define.cjs.map +1 -1
- package/dist/define.d.cts +886 -431
- package/dist/define.d.ts +886 -431
- package/dist/define.js +129 -66
- package/dist/define.js.map +1 -1
- package/dist/index.cjs +620 -244
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1649 -808
- package/dist/index.d.ts +1649 -808
- package/dist/index.js +621 -245
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { parse, evaluate } from "groq-js";
|
|
2
|
-
import { isTerminalTaskStatus, WORKFLOW_DEFINITION_TYPE, DOCUMENT_VALUE_PERMISSIONS } from "./_chunks-es/schema.js";
|
|
2
|
+
import { actorFulfillsRole, isTerminalTaskStatus, WORKFLOW_DEFINITION_TYPE, andConditions, DOCUMENT_VALUE_PERMISSIONS } from "./_chunks-es/schema.js";
|
|
3
3
|
import { isStartableDefinition } from "./_chunks-es/schema.js";
|
|
4
4
|
import * as v from "valibot";
|
|
5
5
|
function findOpenStageEntry(host) {
|
|
@@ -111,7 +111,7 @@ function buildParams(args) {
|
|
|
111
111
|
const { instance, now, snapshot, extra } = args, currentTasks2 = findOpenStageEntry(instance)?.tasks ?? [];
|
|
112
112
|
return {
|
|
113
113
|
self: selfGdr(instance),
|
|
114
|
-
|
|
114
|
+
fields: renderedFields(instance.fields ?? [], snapshot),
|
|
115
115
|
parent: instance.ancestors.at(-1)?.id ?? null,
|
|
116
116
|
ancestors: instance.ancestors.map((a) => a.id),
|
|
117
117
|
stage: instance.currentStage,
|
|
@@ -126,7 +126,7 @@ function buildParams(args) {
|
|
|
126
126
|
...extra
|
|
127
127
|
};
|
|
128
128
|
}
|
|
129
|
-
function
|
|
129
|
+
function renderedFields(entries, snapshot) {
|
|
130
130
|
const out = {};
|
|
131
131
|
for (const entry of entries) out[entry.name] = renderedValue(entry, snapshot);
|
|
132
132
|
return out;
|
|
@@ -134,17 +134,17 @@ function renderedState(entries, snapshot) {
|
|
|
134
134
|
function renderedValue(entry, snapshot) {
|
|
135
135
|
return entry._type !== "doc.ref" || entry.value === null || snapshot === void 0 ? entry.value : snapshot.docs.find((d) => d._id === entry.value.id) ?? entry.value;
|
|
136
136
|
}
|
|
137
|
-
function
|
|
137
|
+
function scopedFieldOverlay(instance, snapshot, taskName) {
|
|
138
138
|
const stageEntry = findOpenStageEntry(instance);
|
|
139
139
|
if (stageEntry === void 0) return {};
|
|
140
|
-
const
|
|
141
|
-
return { ...
|
|
140
|
+
const stageFields = renderedFields(stageEntry.fields ?? [], snapshot), task = taskName ? stageEntry.tasks.find((t) => t.name === taskName) : void 0;
|
|
141
|
+
return { ...stageFields, ...renderedFields(task?.fields ?? [], snapshot) };
|
|
142
142
|
}
|
|
143
|
-
function assignedFor(instance, taskName, actor) {
|
|
143
|
+
function assignedFor(instance, taskName, actor, roleAliases) {
|
|
144
144
|
if (actor === void 0) return !1;
|
|
145
|
-
const entry = findOpenStageEntry(instance)?.tasks.find((t) => t.name === taskName)?.
|
|
145
|
+
const entry = findOpenStageEntry(instance)?.tasks.find((t) => t.name === taskName)?.fields?.find((s) => s._type === "assignees");
|
|
146
146
|
return entry === void 0 ? !1 : entry.value.some(
|
|
147
|
-
(a) => a.type === "user" ? a.id === actor.id : (actor.roles
|
|
147
|
+
(a) => a.type === "user" ? a.id === actor.id : actorFulfillsRole(actor.roles, a.role, roleAliases)
|
|
148
148
|
);
|
|
149
149
|
}
|
|
150
150
|
function effectsContextMap(instance) {
|
|
@@ -169,17 +169,17 @@ function paramsForLake(params) {
|
|
|
169
169
|
self: typeof params.self == "string" ? bareId(params.self) : params.self,
|
|
170
170
|
parent: typeof params.parent == "string" ? bareId(params.parent) : params.parent,
|
|
171
171
|
ancestors: Array.isArray(params.ancestors) ? params.ancestors.map((a) => typeof a == "string" ? bareId(a) : a) : params.ancestors,
|
|
172
|
-
|
|
172
|
+
fields: stripFieldsForLake(params.fields)
|
|
173
173
|
};
|
|
174
174
|
}
|
|
175
|
-
function
|
|
175
|
+
function stripFieldsForLake(value) {
|
|
176
176
|
if (value == null) return value;
|
|
177
177
|
if (typeof value == "string") return bareId(value);
|
|
178
|
-
if (Array.isArray(value)) return value.map(
|
|
178
|
+
if (Array.isArray(value)) return value.map(stripFieldsForLake);
|
|
179
179
|
if (typeof value == "object") {
|
|
180
180
|
const out = {};
|
|
181
181
|
for (const [k, v2] of Object.entries(value))
|
|
182
|
-
out[k] =
|
|
182
|
+
out[k] = stripFieldsForLake(v2);
|
|
183
183
|
return out;
|
|
184
184
|
}
|
|
185
185
|
return value;
|
|
@@ -198,17 +198,17 @@ function getPath(value, path) {
|
|
|
198
198
|
function resourceOf(p) {
|
|
199
199
|
return p.scheme === "dataset" ? { type: "dataset", id: `${p.projectId}.${p.dataset}` } : { type: p.scheme, id: p.resourceId ?? "" };
|
|
200
200
|
}
|
|
201
|
-
const
|
|
201
|
+
const FIELD_READ = /^\$fields\.(\w+)(?:\.(.+))?$/, EFFECTS_READ = /^\$effects\['([^']+)'\](?:\.(.+))?$/;
|
|
202
202
|
function isGuardReadExpr(expr) {
|
|
203
|
-
return expr === "$self" || expr === "$now" ||
|
|
203
|
+
return expr === "$self" || expr === "$now" || FIELD_READ.test(expr) || EFFECTS_READ.test(expr);
|
|
204
204
|
}
|
|
205
205
|
function resolveGuardRead(expr, ctx) {
|
|
206
206
|
if (expr === "$self") return selfGdr(ctx.instance);
|
|
207
207
|
if (expr === "$now") return ctx.now;
|
|
208
|
-
const
|
|
209
|
-
if (
|
|
210
|
-
const value = (ctx.instance.
|
|
211
|
-
return
|
|
208
|
+
const fieldRead = FIELD_READ.exec(expr);
|
|
209
|
+
if (fieldRead !== null) {
|
|
210
|
+
const value = (ctx.instance.fields ?? []).find((s) => s.name === fieldRead[1])?.value;
|
|
211
|
+
return fieldRead[2] !== void 0 ? getPath(value, fieldRead[2]) : value;
|
|
212
212
|
}
|
|
213
213
|
const effectsRead = EFFECTS_READ.exec(expr);
|
|
214
214
|
if (effectsRead !== null) {
|
|
@@ -216,7 +216,7 @@ function resolveGuardRead(expr, ctx) {
|
|
|
216
216
|
return effectsRead[2] !== void 0 ? getPath(outputs, effectsRead[2]) : outputs;
|
|
217
217
|
}
|
|
218
218
|
throw new Error(
|
|
219
|
-
`Guard read "${expr}" is not a supported deploy-time value \u2014 use "$self", "$now", "$
|
|
219
|
+
`Guard read "${expr}" is not a supported deploy-time value \u2014 use "$self", "$now", "$fields.<name>[.path]", or "$effects['<name>'][.path]" (guards store resolved values; the lake cannot see $fields or $effects)`
|
|
220
220
|
);
|
|
221
221
|
}
|
|
222
222
|
function resolveIdRefTarget(expr, ctx) {
|
|
@@ -266,8 +266,8 @@ function resolveMetadata(metadata, ctx) {
|
|
|
266
266
|
}
|
|
267
267
|
function validateDefinition(definition) {
|
|
268
268
|
const v2 = createDefinitionValidator();
|
|
269
|
-
for (const entry of definition.
|
|
270
|
-
v2.checkEntry(entry, `workflow.
|
|
269
|
+
for (const entry of definition.fields ?? [])
|
|
270
|
+
v2.checkEntry(entry, `workflow.fields "${entry.name}"`);
|
|
271
271
|
for (const [name, groq] of Object.entries(definition.predicates ?? {}))
|
|
272
272
|
v2.checkCondition(groq, `predicate "${name}"`);
|
|
273
273
|
for (const stage of definition.stages)
|
|
@@ -289,7 +289,7 @@ function createDefinitionValidator() {
|
|
|
289
289
|
}
|
|
290
290
|
}, rejectTypeScan = (groq, where) => {
|
|
291
291
|
/\*\s*\[\s*_type\b/.test(groq) && errors.push(
|
|
292
|
-
` \xB7 ${where}: condition scans by \`_type\` \u2014 that's a discovery query, not a predicate. Conditions evaluate against the in-memory snapshot (instance + ancestors +
|
|
292
|
+
` \xB7 ${where}: condition scans by \`_type\` \u2014 that's a discovery query, not a predicate. Conditions evaluate against the in-memory snapshot (instance + ancestors + field-declared docs). To bring extra docs in scope, declare a \`doc.ref\` (or \`doc.refs\`) field entry on the workflow or this stage. For lake scans like "all articles in this release", use \`subworkflows.forEach\` instead.`
|
|
293
293
|
);
|
|
294
294
|
};
|
|
295
295
|
return { errors, tryParse, checkCondition: (groq, where) => {
|
|
@@ -298,13 +298,13 @@ function createDefinitionValidator() {
|
|
|
298
298
|
entry.source.type === "query" && tryParse(entry.source.query, `${label}.query`);
|
|
299
299
|
}, checkGuardRead: (expr, where) => {
|
|
300
300
|
isGuardReadExpr(expr) || errors.push(
|
|
301
|
-
` \xB7 ${where}: guard read "${expr}" is not a supported deploy-time value \u2014 use "$self", "$now", "$
|
|
301
|
+
` \xB7 ${where}: guard read "${expr}" is not a supported deploy-time value \u2014 use "$self", "$now", "$fields.<name>[.path]", or "$effects['<name>'][.path]" (guards store resolved values; the lake cannot see $fields or $effects)`
|
|
302
302
|
);
|
|
303
303
|
} };
|
|
304
304
|
}
|
|
305
305
|
function validateStage(v2, stage) {
|
|
306
|
-
for (const entry of stage.
|
|
307
|
-
v2.checkEntry(entry, `stage "${stage.name}".
|
|
306
|
+
for (const entry of stage.fields ?? [])
|
|
307
|
+
v2.checkEntry(entry, `stage "${stage.name}".fields "${entry.name}"`);
|
|
308
308
|
for (const guard of stage.guards ?? [])
|
|
309
309
|
validateGuardReads(v2, stage.name, guard);
|
|
310
310
|
for (const t of stage.transitions ?? []) {
|
|
@@ -324,8 +324,8 @@ function validateGuardReads(v2, stageName, guard) {
|
|
|
324
324
|
}
|
|
325
325
|
function validateTask(v2, stageName, task) {
|
|
326
326
|
const where = `stage "${stageName}" task "${task.name}"`;
|
|
327
|
-
for (const entry of task.
|
|
328
|
-
v2.checkEntry(entry, `${where}.
|
|
327
|
+
for (const entry of task.fields ?? [])
|
|
328
|
+
v2.checkEntry(entry, `${where}.fields "${entry.name}"`);
|
|
329
329
|
task.filter !== void 0 && v2.checkCondition(task.filter, `${where}.filter`), task.completeWhen !== void 0 && v2.checkCondition(task.completeWhen, `${where}.completeWhen`), task.failWhen !== void 0 && v2.checkCondition(task.failWhen, `${where}.failWhen`);
|
|
330
330
|
for (const [name, groq] of Object.entries(task.requirements ?? {}))
|
|
331
331
|
v2.checkCondition(groq, `${where}.requirements "${name}"`);
|
|
@@ -497,7 +497,7 @@ function openStage(instance) {
|
|
|
497
497
|
return findOpenStageEntry(instance);
|
|
498
498
|
}
|
|
499
499
|
function assigneesOf(stage, taskName) {
|
|
500
|
-
return (stage?.tasks.find((t) => t.name === taskName)?.
|
|
500
|
+
return (stage?.tasks.find((t) => t.name === taskName)?.fields ?? []).filter((s) => s._type === "assignees").flatMap((s) => s.value);
|
|
501
501
|
}
|
|
502
502
|
function isResolved(status) {
|
|
503
503
|
return status === "done" || status === "skipped";
|
|
@@ -610,17 +610,17 @@ class ActionParamsInvalidError extends Error {
|
|
|
610
610
|
${lines}`), this.name = "ActionParamsInvalidError", this.action = args.action, this.task = args.task, this.issues = args.issues;
|
|
611
611
|
}
|
|
612
612
|
}
|
|
613
|
-
class
|
|
613
|
+
class RequiredFieldNotProvidedError extends Error {
|
|
614
614
|
definition;
|
|
615
615
|
missing;
|
|
616
616
|
constructor(args) {
|
|
617
617
|
const lines = args.missing.map((m) => ` - ${m.name} (${m.type})`).join(`
|
|
618
618
|
`), where = args.definition !== void 0 ? ` for workflow "${args.definition}"` : "";
|
|
619
619
|
super(
|
|
620
|
-
`Required init
|
|
620
|
+
`Required init fields${where} was not provided:
|
|
621
621
|
${lines}
|
|
622
|
-
Provide each via \`
|
|
623
|
-
), this.name = "
|
|
622
|
+
Provide each via \`initialFields\` when starting standalone, or via the parent's \`subworkflows.with\` when spawned.`
|
|
623
|
+
), this.name = "RequiredFieldNotProvidedError", args.definition !== void 0 && (this.definition = args.definition), this.missing = args.missing;
|
|
624
624
|
}
|
|
625
625
|
}
|
|
626
626
|
class WorkflowStateDivergedError extends Error {
|
|
@@ -663,6 +663,17 @@ function isRevisionConflict(error) {
|
|
|
663
663
|
const { statusCode, message } = error;
|
|
664
664
|
return statusCode === 409 ? !0 : typeof message == "string" && message.includes("ifRevisionId check failed");
|
|
665
665
|
}
|
|
666
|
+
class ConcurrentEditFieldError extends Error {
|
|
667
|
+
instanceId;
|
|
668
|
+
target;
|
|
669
|
+
attempts;
|
|
670
|
+
constructor(args) {
|
|
671
|
+
const where = args.target.task !== void 0 ? `${args.target.task}.${args.target.field}` : args.target.field;
|
|
672
|
+
super(
|
|
673
|
+
`Edit of "${args.target.scope}:${where}" (instance ${args.instanceId}) lost the optimistic-locking race ${args.attempts} attempts running \u2014 a concurrent writer kept committing first. Re-read and retry, or investigate a write storm on this instance.`
|
|
674
|
+
), this.name = "ConcurrentEditFieldError", this.instanceId = args.instanceId, this.target = args.target, this.attempts = args.attempts;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
666
677
|
class CascadeLimitError extends Error {
|
|
667
678
|
instanceId;
|
|
668
679
|
limit;
|
|
@@ -708,10 +719,10 @@ function collectWatchRefs(instance) {
|
|
|
708
719
|
return [
|
|
709
720
|
gdrRef(instance.workflowResource, instance._id, instance._type),
|
|
710
721
|
...instance.ancestors,
|
|
711
|
-
...entryDocRefs(instance.
|
|
712
|
-
...entryDocRefs(stage?.
|
|
713
|
-
...entryReleaseRefs(instance.
|
|
714
|
-
...entryReleaseRefs(stage?.
|
|
722
|
+
...entryDocRefs(instance.fields),
|
|
723
|
+
...entryDocRefs(stage?.fields),
|
|
724
|
+
...entryReleaseRefs(instance.fields),
|
|
725
|
+
...entryReleaseRefs(stage?.fields)
|
|
715
726
|
];
|
|
716
727
|
}
|
|
717
728
|
function readsRaw(ref) {
|
|
@@ -735,21 +746,21 @@ function subscriptionDocumentsForInstance(instance) {
|
|
|
735
746
|
...instance.perspective !== void 0 ? { perspective: instance.perspective } : {}
|
|
736
747
|
};
|
|
737
748
|
}
|
|
738
|
-
function entryDocRefs(
|
|
739
|
-
return
|
|
749
|
+
function entryDocRefs(entries) {
|
|
750
|
+
return fieldEntries(entries).flatMap((entry) => entry._type === "doc.ref" ? isGdr(entry.value) ? [entry.value] : [] : entry._type === "doc.refs" ? Array.isArray(entry.value) ? entry.value.filter(isGdr) : [] : []);
|
|
740
751
|
}
|
|
741
|
-
function entryReleaseRefs(
|
|
742
|
-
return
|
|
752
|
+
function entryReleaseRefs(entries) {
|
|
753
|
+
return fieldEntries(entries).flatMap(
|
|
743
754
|
(entry) => entry._type === "release.ref" && isGdr(entry.value) ? [entry.value] : []
|
|
744
755
|
);
|
|
745
756
|
}
|
|
746
|
-
function entrySubjectRefs(
|
|
747
|
-
return
|
|
757
|
+
function entrySubjectRefs(entries) {
|
|
758
|
+
return fieldEntries(entries).flatMap(
|
|
748
759
|
(entry) => (entry._type === "doc.ref" || entry._type === "release.ref") && isGdr(entry.value) ? [entry.value] : []
|
|
749
760
|
);
|
|
750
761
|
}
|
|
751
|
-
function
|
|
752
|
-
return Array.isArray(
|
|
762
|
+
function fieldEntries(entries) {
|
|
763
|
+
return Array.isArray(entries) ? entries.filter(
|
|
753
764
|
(entry) => !!entry && typeof entry == "object"
|
|
754
765
|
) : [];
|
|
755
766
|
}
|
|
@@ -795,8 +806,8 @@ async function readDoc(client, id, perspective) {
|
|
|
795
806
|
function resourceFromParsed(parsed) {
|
|
796
807
|
return parsed.scheme === "dataset" ? { type: "dataset", id: `${parsed.projectId}.${parsed.dataset}` } : { type: parsed.scheme, id: parsed.resourceId ?? "" };
|
|
797
808
|
}
|
|
798
|
-
function collectEntryDocUris(
|
|
799
|
-
return entryDocRefs(
|
|
809
|
+
function collectEntryDocUris(resolvedFieldEntries) {
|
|
810
|
+
return entryDocRefs(resolvedFieldEntries).map((ref) => ref.id);
|
|
800
811
|
}
|
|
801
812
|
const resourceKey = (r) => `${r.type}.${r.id}`;
|
|
802
813
|
function guardsForResource(client) {
|
|
@@ -814,8 +825,8 @@ function verdictGuardsForInstance(client, instanceId) {
|
|
|
814
825
|
}
|
|
815
826
|
async function guardsForInstance(args) {
|
|
816
827
|
const { client, clientForGdr, instance } = args, stateUris = [
|
|
817
|
-
...collectEntryDocUris(instance.
|
|
818
|
-
...instance.stages.flatMap((stage) => collectEntryDocUris(stage.
|
|
828
|
+
...collectEntryDocUris(instance.fields),
|
|
829
|
+
...instance.stages.flatMap((stage) => collectEntryDocUris(stage.fields))
|
|
819
830
|
], clients = resourceClientMap(
|
|
820
831
|
instance.workflowResource,
|
|
821
832
|
client,
|
|
@@ -847,16 +858,16 @@ function guardIdRefs(definition) {
|
|
|
847
858
|
);
|
|
848
859
|
}
|
|
849
860
|
function staticIdRefGdr(idRef, stage, definition) {
|
|
850
|
-
const read = /^\$
|
|
861
|
+
const read = /^\$fields\.([\w-]+)/.exec(idRef);
|
|
851
862
|
if (read === null) return;
|
|
852
863
|
const source = entriesInScope(stage, definition).find((e) => e.name === read[1])?.source;
|
|
853
864
|
return source?.type === "literal" ? gdrFromValue(source.value) : void 0;
|
|
854
865
|
}
|
|
855
866
|
function entriesInScope(stage, definition) {
|
|
856
867
|
return [
|
|
857
|
-
...definition.
|
|
858
|
-
...stage.
|
|
859
|
-
...(stage.tasks ?? []).flatMap((task) => task.
|
|
868
|
+
...definition.fields ?? [],
|
|
869
|
+
...stage.fields ?? [],
|
|
870
|
+
...(stage.tasks ?? []).flatMap((task) => task.fields ?? [])
|
|
860
871
|
];
|
|
861
872
|
}
|
|
862
873
|
function gdrFromValue(value) {
|
|
@@ -1006,15 +1017,15 @@ async function runGroq(groq, params, snapshot) {
|
|
|
1006
1017
|
const tree = parse(groq, { params });
|
|
1007
1018
|
return (await evaluate(tree, { dataset: snapshot.docs, params })).get();
|
|
1008
1019
|
}
|
|
1009
|
-
class
|
|
1020
|
+
class FieldValueShapeError extends Error {
|
|
1010
1021
|
entryType;
|
|
1011
1022
|
entryName;
|
|
1012
1023
|
issues;
|
|
1013
1024
|
constructor(args) {
|
|
1014
1025
|
const issueText = args.issues.join("; ");
|
|
1015
1026
|
super(
|
|
1016
|
-
`
|
|
1017
|
-
), this.name = "
|
|
1027
|
+
`Field entry ${args.mode} shape invalid for "${args.entryName}" (${args.entryType}): ${issueText}`
|
|
1028
|
+
), this.name = "FieldValueShapeError", this.entryType = args.entryType, this.entryName = args.entryName, this.issues = args.issues;
|
|
1018
1029
|
}
|
|
1019
1030
|
}
|
|
1020
1031
|
const GdrShape = v.looseObject({
|
|
@@ -1081,35 +1092,35 @@ const GdrShape = v.looseObject({
|
|
|
1081
1092
|
function isAppendable(entryType) {
|
|
1082
1093
|
return entryType in itemSchemas;
|
|
1083
1094
|
}
|
|
1084
|
-
function
|
|
1095
|
+
function validateFieldValue(args) {
|
|
1085
1096
|
const schema = valueSchemas[args.entryType];
|
|
1086
1097
|
if (schema === void 0)
|
|
1087
|
-
throw new
|
|
1098
|
+
throw new FieldValueShapeError({
|
|
1088
1099
|
entryType: args.entryType,
|
|
1089
1100
|
entryName: args.entryName,
|
|
1090
|
-
issues: [`unknown
|
|
1101
|
+
issues: [`unknown field entry type ${args.entryType}`],
|
|
1091
1102
|
mode: "value"
|
|
1092
1103
|
});
|
|
1093
1104
|
const result = v.safeParse(schema, args.value);
|
|
1094
1105
|
if (!result.success)
|
|
1095
|
-
throw new
|
|
1106
|
+
throw new FieldValueShapeError({
|
|
1096
1107
|
entryType: args.entryType,
|
|
1097
1108
|
entryName: args.entryName,
|
|
1098
1109
|
issues: formatIssues(result.issues),
|
|
1099
1110
|
mode: "value"
|
|
1100
1111
|
});
|
|
1101
1112
|
}
|
|
1102
|
-
function
|
|
1113
|
+
function validateFieldAppendItem(args) {
|
|
1103
1114
|
if (!isAppendable(args.entryType))
|
|
1104
|
-
throw new
|
|
1115
|
+
throw new FieldValueShapeError({
|
|
1105
1116
|
entryType: args.entryType,
|
|
1106
1117
|
entryName: args.entryName,
|
|
1107
|
-
issues: [`
|
|
1118
|
+
issues: [`field entry type ${args.entryType} does not support append`],
|
|
1108
1119
|
mode: "item"
|
|
1109
1120
|
});
|
|
1110
1121
|
const schema = itemSchemas[args.entryType], result = v.safeParse(schema, args.item);
|
|
1111
1122
|
if (!result.success)
|
|
1112
|
-
throw new
|
|
1123
|
+
throw new FieldValueShapeError({
|
|
1113
1124
|
entryType: args.entryType,
|
|
1114
1125
|
entryName: args.entryName,
|
|
1115
1126
|
issues: formatIssues(result.issues),
|
|
@@ -1122,7 +1133,7 @@ function formatIssues(issues) {
|
|
|
1122
1133
|
return `${keys.length > 0 ? `at ${keys.join(".")}: ` : ""}${i.message}`;
|
|
1123
1134
|
});
|
|
1124
1135
|
}
|
|
1125
|
-
function
|
|
1136
|
+
function derivePerspectiveFromFields(entries) {
|
|
1126
1137
|
if (entries !== void 0) {
|
|
1127
1138
|
for (const entry of entries)
|
|
1128
1139
|
if (entry._type === "release.ref" && entry.value !== null) {
|
|
@@ -1132,24 +1143,24 @@ function derivePerspectiveFromState(entries) {
|
|
|
1132
1143
|
}
|
|
1133
1144
|
}
|
|
1134
1145
|
}
|
|
1135
|
-
async function
|
|
1136
|
-
const { entryDefs,
|
|
1146
|
+
async function resolveDeclaredFields(args) {
|
|
1147
|
+
const { entryDefs, initialFields, ctx, randomKey: randomKey2 } = args;
|
|
1137
1148
|
if (entryDefs === void 0 || entryDefs.length === 0) return [];
|
|
1138
|
-
assertRequiredInitProvided(entryDefs,
|
|
1149
|
+
assertRequiredInitProvided(entryDefs, initialFields, ctx.definitionName);
|
|
1139
1150
|
const out = [];
|
|
1140
1151
|
for (const entry of entryDefs) {
|
|
1141
|
-
const scopedCtx = { ...ctx,
|
|
1142
|
-
out.push(await resolveOneEntry(entry,
|
|
1152
|
+
const scopedCtx = { ...ctx, resolvedFields: out };
|
|
1153
|
+
out.push(await resolveOneEntry(entry, initialFields, scopedCtx, randomKey2));
|
|
1143
1154
|
}
|
|
1144
1155
|
return out;
|
|
1145
1156
|
}
|
|
1146
|
-
function assertRequiredInitProvided(entryDefs,
|
|
1157
|
+
function assertRequiredInitProvided(entryDefs, initialFields, definitionName) {
|
|
1147
1158
|
const missing = entryDefs.filter((entry) => entry.required === !0 && entry.source.type === "init").filter((entry) => {
|
|
1148
|
-
const match =
|
|
1159
|
+
const match = initialFields.find((s) => s.name === entry.name && s.type === entry.type);
|
|
1149
1160
|
return match === void 0 || match.value === void 0 || match.value === null;
|
|
1150
1161
|
}).map((entry) => ({ name: entry.name, type: entry.type }));
|
|
1151
1162
|
if (missing.length !== 0)
|
|
1152
|
-
throw new
|
|
1163
|
+
throw new RequiredFieldNotProvidedError({
|
|
1153
1164
|
...definitionName !== void 0 ? { definition: definitionName } : {},
|
|
1154
1165
|
missing
|
|
1155
1166
|
});
|
|
@@ -1163,18 +1174,18 @@ const ARRAY_SLOT_TYPES = /* @__PURE__ */ new Set([
|
|
|
1163
1174
|
function defaultEntryValue(entryType) {
|
|
1164
1175
|
return ARRAY_SLOT_TYPES.has(entryType) ? [] : null;
|
|
1165
1176
|
}
|
|
1166
|
-
function resolveInitValue(entry,
|
|
1167
|
-
const initMatch =
|
|
1177
|
+
function resolveInitValue(entry, initialFields, defaultValue) {
|
|
1178
|
+
const initMatch = initialFields.find((s) => s.name === entry.name && s.type === entry.type);
|
|
1168
1179
|
return initMatch === void 0 ? defaultValue : (assertInitValueShape(entry, initMatch.value), initMatch.value);
|
|
1169
1180
|
}
|
|
1170
|
-
function
|
|
1171
|
-
const target = (source.scope === "workflow" ? ctx.
|
|
1181
|
+
function resolveFieldReadValue(source, ctx, defaultValue) {
|
|
1182
|
+
const target = (source.scope === "workflow" ? ctx.workflowFields ?? [] : ctx.resolvedFields ?? []).find((s) => s.name === source.field), targetValue = target === void 0 ? void 0 : target.value;
|
|
1172
1183
|
return (source.path !== void 0 ? getPath(targetValue, source.path) : targetValue) ?? defaultValue;
|
|
1173
1184
|
}
|
|
1174
1185
|
async function resolveQueryValue(entry, source, ctx, client, defaultValue) {
|
|
1175
1186
|
const params = paramsForLake({
|
|
1176
1187
|
self: ctx.selfId ?? null,
|
|
1177
|
-
|
|
1188
|
+
fields: fieldMapFromResolved(ctx.resolvedFields ?? []),
|
|
1178
1189
|
stage: ctx.stageName ?? null,
|
|
1179
1190
|
task: ctx.taskName ?? null,
|
|
1180
1191
|
now: ctx.now,
|
|
@@ -1190,9 +1201,9 @@ async function resolveQueryValue(entry, source, ctx, client, defaultValue) {
|
|
|
1190
1201
|
);
|
|
1191
1202
|
}
|
|
1192
1203
|
}
|
|
1193
|
-
async function resolveEntryValue(entry,
|
|
1204
|
+
async function resolveEntryValue(entry, initialFields, ctx, defaultValue) {
|
|
1194
1205
|
const source = entry.source;
|
|
1195
|
-
return source.type === "init" ? resolveInitValue(entry,
|
|
1206
|
+
return source.type === "init" ? resolveInitValue(entry, initialFields, defaultValue) : source.type === "literal" ? source.value ?? defaultValue : source.type === "fieldRead" ? resolveFieldReadValue(source, ctx, defaultValue) : source.type === "query" && ctx.client !== void 0 ? resolveQueryValue(entry, source, ctx, ctx.client, defaultValue) : defaultValue;
|
|
1196
1207
|
}
|
|
1197
1208
|
function buildResolvedEntry(entry, value, _key, now) {
|
|
1198
1209
|
const titleProp = entry.title !== void 0 ? { title: entry.title } : {}, descriptionProp = entry.description !== void 0 ? { description: entry.description } : {};
|
|
@@ -1213,36 +1224,36 @@ function buildResolvedEntry(entry, value, _key, now) {
|
|
|
1213
1224
|
value
|
|
1214
1225
|
};
|
|
1215
1226
|
}
|
|
1216
|
-
async function resolveOneEntry(entry,
|
|
1217
|
-
const defaultValue = defaultEntryValue(entry.type), value = await resolveEntryValue(entry,
|
|
1218
|
-
return
|
|
1227
|
+
async function resolveOneEntry(entry, initialFields, ctx, randomKey2) {
|
|
1228
|
+
const defaultValue = defaultEntryValue(entry.type), value = await resolveEntryValue(entry, initialFields, ctx, defaultValue);
|
|
1229
|
+
return validateFieldValue({ entryType: entry.type, entryName: entry.name, value }), buildResolvedEntry(entry, value, randomKey2(), ctx.now);
|
|
1219
1230
|
}
|
|
1220
|
-
function
|
|
1231
|
+
function fieldMapFromResolved(entries) {
|
|
1221
1232
|
const out = {};
|
|
1222
1233
|
for (const s of entries) out[s.name] = s.value;
|
|
1223
1234
|
return out;
|
|
1224
1235
|
}
|
|
1225
1236
|
function assertInitValueShape(entry, value) {
|
|
1226
1237
|
if (entry.type === "doc.ref") {
|
|
1227
|
-
assertGdrShape(value, `
|
|
1238
|
+
assertGdrShape(value, `field entry "${entry.name}" (doc.ref)`);
|
|
1228
1239
|
return;
|
|
1229
1240
|
}
|
|
1230
1241
|
if (entry.type === "release.ref") {
|
|
1231
|
-
assertGdrShape(value, `
|
|
1242
|
+
assertGdrShape(value, `field entry "${entry.name}" (release.ref)`);
|
|
1232
1243
|
const v2 = value;
|
|
1233
1244
|
if (typeof v2.releaseName != "string" || v2.releaseName.length === 0)
|
|
1234
1245
|
throw new Error(
|
|
1235
|
-
`Invalid init value for
|
|
1246
|
+
`Invalid init value for field entry "${entry.name}" (release.ref): \`releaseName\` must be a non-empty string. Got ${JSON.stringify(v2.releaseName)}.`
|
|
1236
1247
|
);
|
|
1237
1248
|
return;
|
|
1238
1249
|
}
|
|
1239
1250
|
if (entry.type === "doc.refs") {
|
|
1240
1251
|
if (!Array.isArray(value))
|
|
1241
1252
|
throw new Error(
|
|
1242
|
-
`Invalid init value for
|
|
1253
|
+
`Invalid init value for field entry "${entry.name}" (doc.refs): expected an array of GDRs, got ${typeof value}.`
|
|
1243
1254
|
);
|
|
1244
1255
|
for (const [i, item] of value.entries())
|
|
1245
|
-
assertGdrShape(item, `
|
|
1256
|
+
assertGdrShape(item, `field entry "${entry.name}" (doc.refs) item [${i}]`);
|
|
1246
1257
|
}
|
|
1247
1258
|
}
|
|
1248
1259
|
function assertGdrShape(value, context) {
|
|
@@ -1282,6 +1293,12 @@ function coerceToGdr(raw, workflowResource) {
|
|
|
1282
1293
|
function gdrToBareId(uri) {
|
|
1283
1294
|
return uri.includes(":") ? extractDocumentId(uri) : uri;
|
|
1284
1295
|
}
|
|
1296
|
+
function loadCallContext(client, instanceId, options) {
|
|
1297
|
+
return loadContext(client, instanceId, {
|
|
1298
|
+
...options?.clock ? { clock: options.clock } : {},
|
|
1299
|
+
...options?.clientForGdr ? { clientForGdr: options.clientForGdr } : {}
|
|
1300
|
+
});
|
|
1301
|
+
}
|
|
1285
1302
|
async function loadContext(client, instanceId, options) {
|
|
1286
1303
|
const instance = await client.getDocument(instanceId);
|
|
1287
1304
|
if (!instance)
|
|
@@ -1295,14 +1312,14 @@ async function loadContext(client, instanceId, options) {
|
|
|
1295
1312
|
return { client, clientForGdr, clock, now: clock(), instance, definition, snapshot };
|
|
1296
1313
|
}
|
|
1297
1314
|
async function ctxConditionParams(ctx, opts) {
|
|
1298
|
-
const base = buildParams({ instance: ctx.instance, now: ctx.now, snapshot: ctx.snapshot }),
|
|
1299
|
-
...base.
|
|
1300
|
-
...
|
|
1315
|
+
const base = buildParams({ instance: ctx.instance, now: ctx.now, snapshot: ctx.snapshot }), fields = {
|
|
1316
|
+
...base.fields,
|
|
1317
|
+
...scopedFieldOverlay(ctx.instance, ctx.snapshot, opts?.taskName)
|
|
1301
1318
|
}, params = {
|
|
1302
1319
|
...base,
|
|
1303
|
-
|
|
1320
|
+
fields,
|
|
1304
1321
|
actor: opts?.actor,
|
|
1305
|
-
assigned: opts?.taskName !== void 0 ? assignedFor(ctx.instance, opts.taskName, opts?.actor) : !1,
|
|
1322
|
+
assigned: opts?.taskName !== void 0 ? assignedFor(ctx.instance, opts.taskName, opts?.actor, ctx.definition.roleAliases) : !1,
|
|
1306
1323
|
...opts?.vars
|
|
1307
1324
|
};
|
|
1308
1325
|
return { ...await evaluatePredicates({
|
|
@@ -1339,11 +1356,11 @@ function findStage(definition, stageName) {
|
|
|
1339
1356
|
throw new Error(`Stage "${stageName}" not found in definition ${definition.name}`);
|
|
1340
1357
|
return stage;
|
|
1341
1358
|
}
|
|
1342
|
-
async function
|
|
1343
|
-
const { client, instance, stage, now,
|
|
1344
|
-
return
|
|
1345
|
-
entryDefs: stage.
|
|
1346
|
-
|
|
1359
|
+
async function resolveStageFieldEntries(args) {
|
|
1360
|
+
const { client, instance, stage, now, initialFields } = args;
|
|
1361
|
+
return resolveDeclaredFields({
|
|
1362
|
+
entryDefs: stage.fields,
|
|
1363
|
+
initialFields: initialFields ?? [],
|
|
1347
1364
|
ctx: {
|
|
1348
1365
|
client,
|
|
1349
1366
|
now,
|
|
@@ -1351,19 +1368,19 @@ async function resolveStageStateEntries(args) {
|
|
|
1351
1368
|
tag: instance.tag,
|
|
1352
1369
|
stageName: stage.name,
|
|
1353
1370
|
workflowResource: instance.workflowResource,
|
|
1354
|
-
// Allow stage-scope entries' `source: { type: "
|
|
1355
|
-
// "workflow", ... }` to see the already-resolved workflow-scope
|
|
1356
|
-
|
|
1371
|
+
// Allow stage-scope entries' `source: { type: "fieldRead", scope:
|
|
1372
|
+
// "workflow", ... }` to see the already-resolved workflow-scope fields.
|
|
1373
|
+
workflowFields: instance.fields ?? [],
|
|
1357
1374
|
...instance.perspective !== void 0 ? { perspective: instance.perspective } : {}
|
|
1358
1375
|
},
|
|
1359
1376
|
randomKey
|
|
1360
1377
|
});
|
|
1361
1378
|
}
|
|
1362
|
-
async function
|
|
1379
|
+
async function resolveTaskFieldEntries(args) {
|
|
1363
1380
|
const { client, instance, stage, task, now } = args;
|
|
1364
|
-
return
|
|
1365
|
-
entryDefs: task.
|
|
1366
|
-
|
|
1381
|
+
return resolveDeclaredFields({
|
|
1382
|
+
entryDefs: task.fields,
|
|
1383
|
+
initialFields: [],
|
|
1367
1384
|
ctx: {
|
|
1368
1385
|
client,
|
|
1369
1386
|
now,
|
|
@@ -1372,7 +1389,7 @@ async function resolveTaskStateEntries(args) {
|
|
|
1372
1389
|
stageName: stage.name,
|
|
1373
1390
|
workflowResource: instance.workflowResource,
|
|
1374
1391
|
taskName: task.name,
|
|
1375
|
-
|
|
1392
|
+
workflowFields: instance.fields ?? [],
|
|
1376
1393
|
...instance.perspective !== void 0 ? { perspective: instance.perspective } : {}
|
|
1377
1394
|
},
|
|
1378
1395
|
randomKey
|
|
@@ -1454,15 +1471,15 @@ function stageTransitionHistory(args) {
|
|
|
1454
1471
|
function startMutation(instance) {
|
|
1455
1472
|
return {
|
|
1456
1473
|
currentStage: instance.currentStage,
|
|
1457
|
-
// Deep-ish copy. Per-scope
|
|
1474
|
+
// Deep-ish copy. Per-scope field entry arrays need their own copies
|
|
1458
1475
|
// so ops can mutate without leaking into the source.
|
|
1459
|
-
|
|
1476
|
+
fields: (instance.fields ?? []).map((s) => ({ ...s })),
|
|
1460
1477
|
stages: instance.stages.map((s) => ({
|
|
1461
1478
|
...s,
|
|
1462
|
-
|
|
1479
|
+
fields: (s.fields ?? []).map((entry) => ({ ...entry })),
|
|
1463
1480
|
tasks: s.tasks.map((t) => ({
|
|
1464
1481
|
...t,
|
|
1465
|
-
...t.
|
|
1482
|
+
...t.fields !== void 0 ? { fields: t.fields.map((entry) => ({ ...entry })) } : {}
|
|
1466
1483
|
}))
|
|
1467
1484
|
})),
|
|
1468
1485
|
pendingEffects: [...instance.pendingEffects],
|
|
@@ -1478,7 +1495,7 @@ function startMutation(instance) {
|
|
|
1478
1495
|
function instanceStateFields(src) {
|
|
1479
1496
|
const fields = {
|
|
1480
1497
|
currentStage: src.currentStage,
|
|
1481
|
-
|
|
1498
|
+
fields: src.fields,
|
|
1482
1499
|
stages: src.stages,
|
|
1483
1500
|
pendingEffects: src.pendingEffects,
|
|
1484
1501
|
effectHistory: src.effectHistory,
|
|
@@ -1491,7 +1508,7 @@ function materializeInstance(base, mutation) {
|
|
|
1491
1508
|
return {
|
|
1492
1509
|
...base,
|
|
1493
1510
|
currentStage: mutation.currentStage,
|
|
1494
|
-
|
|
1511
|
+
fields: mutation.fields,
|
|
1495
1512
|
stages: mutation.stages,
|
|
1496
1513
|
effectsContext: mutation.effectsContext
|
|
1497
1514
|
};
|
|
@@ -1613,7 +1630,7 @@ function buildInstanceBase(args) {
|
|
|
1613
1630
|
definition: args.definitionName,
|
|
1614
1631
|
pinnedVersion: args.pinnedVersion,
|
|
1615
1632
|
definitionSnapshot: JSON.stringify(args.definition),
|
|
1616
|
-
|
|
1633
|
+
fields: args.fields,
|
|
1617
1634
|
effectsContext: args.effectsContext,
|
|
1618
1635
|
ancestors: args.ancestors,
|
|
1619
1636
|
...perspective !== void 0 ? { perspective } : {},
|
|
@@ -1671,7 +1688,7 @@ async function spawnSubworkflows(ctx, mutation, task, sub, actor) {
|
|
|
1671
1688
|
...actor ? { actor } : {}
|
|
1672
1689
|
}), { rows, discoveryResource } = await resolveSpawnRows(ctx, sub), effectsContext = await resolveContextHandoff(ctx, sub, parentScope), refs = [];
|
|
1673
1690
|
for (const row of rows) {
|
|
1674
|
-
const
|
|
1691
|
+
const initialFields = await projectRowFields(
|
|
1675
1692
|
ctx,
|
|
1676
1693
|
sub,
|
|
1677
1694
|
definition,
|
|
@@ -1682,7 +1699,7 @@ async function spawnSubworkflows(ctx, mutation, task, sub, actor) {
|
|
|
1682
1699
|
client: ctx.client,
|
|
1683
1700
|
parent: ctx.instance,
|
|
1684
1701
|
definition,
|
|
1685
|
-
|
|
1702
|
+
initialFields,
|
|
1686
1703
|
effectsContext,
|
|
1687
1704
|
actor,
|
|
1688
1705
|
now
|
|
@@ -1695,7 +1712,7 @@ async function resolveSpawnRows(ctx, sub) {
|
|
|
1695
1712
|
const params = buildParams({ instance: ctx.instance, now: ctx.now, snapshot: ctx.snapshot }), groq = sub.forEach;
|
|
1696
1713
|
let value, discoveryResource;
|
|
1697
1714
|
if (groq.includes("*")) {
|
|
1698
|
-
const routingUri = entrySubjectRefs(ctx.instance.
|
|
1715
|
+
const routingUri = entrySubjectRefs(ctx.instance.fields)[0]?.id, client = clientForDiscovery(ctx.client, ctx.clientForGdr, routingUri);
|
|
1699
1716
|
client !== ctx.client && routingUri !== void 0 && (discoveryResource = resourceFromGdrUri(routingUri)), value = await discoverItems({
|
|
1700
1717
|
client,
|
|
1701
1718
|
groq,
|
|
@@ -1711,13 +1728,13 @@ async function resolveSpawnRows(ctx, sub) {
|
|
|
1711
1728
|
}
|
|
1712
1729
|
return Array.isArray(value) ? { rows: value, discoveryResource } : value == null ? { rows: [], discoveryResource } : { rows: [value], discoveryResource };
|
|
1713
1730
|
}
|
|
1714
|
-
async function
|
|
1731
|
+
async function projectRowFields(ctx, sub, childDefinition, parentScope, row, discoveryResource) {
|
|
1715
1732
|
const out = [];
|
|
1716
1733
|
for (const [name, groq] of Object.entries(sub.with ?? {})) {
|
|
1717
|
-
const declared = (childDefinition.
|
|
1734
|
+
const declared = (childDefinition.fields ?? []).find((e) => e.name === name);
|
|
1718
1735
|
if (declared === void 0)
|
|
1719
1736
|
throw new Error(
|
|
1720
|
-
`subworkflows.with["${name}"]: subworkflow "${childDefinition.name}" declares no
|
|
1737
|
+
`subworkflows.with["${name}"]: subworkflow "${childDefinition.name}" declares no field entry "${name}"`
|
|
1721
1738
|
);
|
|
1722
1739
|
const raw = await runGroq(groq, { ...parentScope, row }, ctx.snapshot), value = canonicaliseSpawnValue(declared.type, raw, {
|
|
1723
1740
|
parentResource: ctx.instance.workflowResource,
|
|
@@ -1772,15 +1789,15 @@ async function resolveDefinitionRef(client, ref, tag) {
|
|
|
1772
1789
|
) ?? null;
|
|
1773
1790
|
}
|
|
1774
1791
|
async function prepareChildInstance(args) {
|
|
1775
|
-
const { client, parent, definition,
|
|
1792
|
+
const { client, parent, definition, initialFields, effectsContext, actor, now } = args, childTag = parent.tag, workflowResource = parent.workflowResource, childDocId = `${childTag}.wf-instance.${randomKey()}`, childRef = { id: gdrFromResource(workflowResource, childDocId), type: WORKFLOW_INSTANCE_TYPE }, ancestors = [
|
|
1776
1793
|
...parent.ancestors,
|
|
1777
1794
|
{
|
|
1778
1795
|
id: selfGdr(parent),
|
|
1779
1796
|
type: WORKFLOW_INSTANCE_TYPE
|
|
1780
1797
|
}
|
|
1781
|
-
], inheritedPerspective = parent.perspective,
|
|
1782
|
-
entryDefs: definition.
|
|
1783
|
-
|
|
1798
|
+
], inheritedPerspective = parent.perspective, childFields = await resolveDeclaredFields({
|
|
1799
|
+
entryDefs: definition.fields,
|
|
1800
|
+
initialFields,
|
|
1784
1801
|
ctx: {
|
|
1785
1802
|
client,
|
|
1786
1803
|
now,
|
|
@@ -1791,7 +1808,7 @@ async function prepareChildInstance(args) {
|
|
|
1791
1808
|
...inheritedPerspective !== void 0 ? { perspective: inheritedPerspective } : {}
|
|
1792
1809
|
},
|
|
1793
1810
|
randomKey
|
|
1794
|
-
}), childPerspective =
|
|
1811
|
+
}), childPerspective = derivePerspectiveFromFields(childFields) ?? inheritedPerspective, effectsContextEntries = Object.entries(effectsContext).map(
|
|
1795
1812
|
([key, value]) => {
|
|
1796
1813
|
if (typeof value == "object" && value !== null && "id" in value && "type" in value) {
|
|
1797
1814
|
if (!isGdrUri(value.id))
|
|
@@ -1810,7 +1827,7 @@ async function prepareChildInstance(args) {
|
|
|
1810
1827
|
definitionName: definition.name,
|
|
1811
1828
|
pinnedVersion: definition.version,
|
|
1812
1829
|
definition,
|
|
1813
|
-
|
|
1830
|
+
fields: childFields,
|
|
1814
1831
|
effectsContext: effectsContextEntries,
|
|
1815
1832
|
ancestors,
|
|
1816
1833
|
perspective: childPerspective,
|
|
@@ -1975,15 +1992,15 @@ function resolveStaticSource(src, ctx) {
|
|
|
1975
1992
|
return { handled: !1 };
|
|
1976
1993
|
}
|
|
1977
1994
|
}
|
|
1978
|
-
const
|
|
1979
|
-
"
|
|
1980
|
-
"
|
|
1981
|
-
"
|
|
1982
|
-
"
|
|
1983
|
-
"
|
|
1995
|
+
const FIELD_OP_TYPES = [
|
|
1996
|
+
"field.set",
|
|
1997
|
+
"field.unset",
|
|
1998
|
+
"field.append",
|
|
1999
|
+
"field.updateWhere",
|
|
2000
|
+
"field.removeWhere"
|
|
1984
2001
|
];
|
|
1985
|
-
function
|
|
1986
|
-
return
|
|
2002
|
+
function isFieldOp(summary) {
|
|
2003
|
+
return FIELD_OP_TYPES.includes(summary.opType);
|
|
1987
2004
|
}
|
|
1988
2005
|
function validateActionParams(action, taskName, callerParams) {
|
|
1989
2006
|
const declared = action.params ?? [], params = callerParams ?? {}, issues = [];
|
|
@@ -2033,41 +2050,46 @@ function runOps(args) {
|
|
|
2033
2050
|
const summaries = [];
|
|
2034
2051
|
for (const op of ops) {
|
|
2035
2052
|
const summary = applyOp(op, { mutation, stage, taskName: origin.task, params, actor, self, now });
|
|
2036
|
-
summaries.push(summary), mutation.history.push({
|
|
2037
|
-
_key: randomKey(),
|
|
2038
|
-
_type: "opApplied",
|
|
2039
|
-
at: now,
|
|
2040
|
-
stage,
|
|
2041
|
-
...origin.task !== void 0 ? { task: origin.task } : {},
|
|
2042
|
-
...origin.action !== void 0 ? { action: origin.action } : {},
|
|
2043
|
-
...origin.transition !== void 0 ? { transition: origin.transition } : {},
|
|
2044
|
-
opType: summary.opType,
|
|
2045
|
-
...summary.target !== void 0 ? { target: summary.target } : {},
|
|
2046
|
-
...summary.resolved !== void 0 ? { resolved: summary.resolved } : {},
|
|
2047
|
-
...actor !== void 0 ? { actor } : {}
|
|
2048
|
-
});
|
|
2053
|
+
summaries.push(summary), mutation.history.push(opAppliedEntry({ origin, summary, stage, actor, now }));
|
|
2049
2054
|
}
|
|
2050
2055
|
return summaries;
|
|
2051
2056
|
}
|
|
2057
|
+
function opAppliedEntry(args) {
|
|
2058
|
+
const { origin, summary, stage, actor, now } = args;
|
|
2059
|
+
return {
|
|
2060
|
+
_key: randomKey(),
|
|
2061
|
+
_type: "opApplied",
|
|
2062
|
+
at: now,
|
|
2063
|
+
stage,
|
|
2064
|
+
...origin.task !== void 0 ? { task: origin.task } : {},
|
|
2065
|
+
...origin.action !== void 0 ? { action: origin.action } : {},
|
|
2066
|
+
...origin.transition !== void 0 ? { transition: origin.transition } : {},
|
|
2067
|
+
...origin.edit === !0 ? { edit: !0 } : {},
|
|
2068
|
+
opType: summary.opType,
|
|
2069
|
+
...summary.target !== void 0 ? { target: summary.target } : {},
|
|
2070
|
+
...summary.resolved !== void 0 ? { resolved: summary.resolved } : {},
|
|
2071
|
+
...actor !== void 0 ? { actor } : {}
|
|
2072
|
+
};
|
|
2073
|
+
}
|
|
2052
2074
|
function applyOp(op, ctx) {
|
|
2053
2075
|
switch (op.type) {
|
|
2054
|
-
case "
|
|
2055
|
-
return
|
|
2056
|
-
case "
|
|
2057
|
-
return
|
|
2058
|
-
case "
|
|
2059
|
-
return
|
|
2060
|
-
case "
|
|
2061
|
-
return
|
|
2062
|
-
case "
|
|
2063
|
-
return
|
|
2076
|
+
case "field.set":
|
|
2077
|
+
return applyFieldSet(op, ctx);
|
|
2078
|
+
case "field.unset":
|
|
2079
|
+
return applyFieldUnset(op, ctx);
|
|
2080
|
+
case "field.append":
|
|
2081
|
+
return applyFieldAppend(op, ctx);
|
|
2082
|
+
case "field.updateWhere":
|
|
2083
|
+
return applyFieldUpdateWhere(op, ctx);
|
|
2084
|
+
case "field.removeWhere":
|
|
2085
|
+
return applyFieldRemoveWhere(op, ctx);
|
|
2064
2086
|
case "status.set":
|
|
2065
2087
|
return applyStatusSet(op, ctx);
|
|
2066
2088
|
}
|
|
2067
2089
|
}
|
|
2068
|
-
function
|
|
2090
|
+
function applyFieldSet(op, ctx) {
|
|
2069
2091
|
const value = resolveOpSource(op.value, ctx), entry = locateEntry(ctx, op.target);
|
|
2070
|
-
return
|
|
2092
|
+
return validateFieldValue({ entryType: entry._type, entryName: entry.name, value }), setEntryValue(entry, value), { opType: op.type, target: op.target, resolved: { value } };
|
|
2071
2093
|
}
|
|
2072
2094
|
const EMPTY_BY_KIND = {
|
|
2073
2095
|
"doc.refs": [],
|
|
@@ -2075,24 +2097,24 @@ const EMPTY_BY_KIND = {
|
|
|
2075
2097
|
notes: [],
|
|
2076
2098
|
assignees: []
|
|
2077
2099
|
};
|
|
2078
|
-
function
|
|
2100
|
+
function applyFieldUnset(op, ctx) {
|
|
2079
2101
|
const entry = locateEntry(ctx, op.target);
|
|
2080
2102
|
return setEntryValue(entry, EMPTY_BY_KIND[entry._type] ?? null), { opType: op.type, target: op.target };
|
|
2081
2103
|
}
|
|
2082
|
-
function
|
|
2104
|
+
function applyFieldAppend(op, ctx) {
|
|
2083
2105
|
const item = resolveOpSource(op.value, ctx), entry = locateEntry(ctx, op.target);
|
|
2084
|
-
|
|
2106
|
+
validateFieldAppendItem({ entryType: entry._type, entryName: entry.name, item });
|
|
2085
2107
|
const current = Array.isArray(entry.value) ? entry.value : [];
|
|
2086
2108
|
return setEntryValue(entry, [...current, withRowKey(item)]), { opType: op.type, target: op.target, resolved: { item } };
|
|
2087
2109
|
}
|
|
2088
2110
|
function withRowKey(item) {
|
|
2089
2111
|
return typeof item == "object" && item !== null && !Array.isArray(item) && !("_key" in item) ? { _key: randomKey(), ...item } : item;
|
|
2090
2112
|
}
|
|
2091
|
-
function
|
|
2113
|
+
function applyFieldUpdateWhere(op, ctx) {
|
|
2092
2114
|
const entry = locateEntry(ctx, op.target), rows = requireArrayValue(entry, op), merge = resolveOpSource(op.value, ctx);
|
|
2093
2115
|
if (merge === null || typeof merge != "object" || Array.isArray(merge))
|
|
2094
2116
|
throw new Error(
|
|
2095
|
-
`
|
|
2117
|
+
`field.updateWhere value must resolve to an object of fields to merge (target "${op.target.field}")`
|
|
2096
2118
|
);
|
|
2097
2119
|
return setEntryValue(
|
|
2098
2120
|
entry,
|
|
@@ -2101,7 +2123,7 @@ function applyStateUpdateWhere(op, ctx) {
|
|
|
2101
2123
|
)
|
|
2102
2124
|
), { opType: op.type, target: op.target, resolved: { merge } };
|
|
2103
2125
|
}
|
|
2104
|
-
function
|
|
2126
|
+
function applyFieldRemoveWhere(op, ctx) {
|
|
2105
2127
|
const entry = locateEntry(ctx, op.target), rows = requireArrayValue(entry, op);
|
|
2106
2128
|
return setEntryValue(
|
|
2107
2129
|
entry,
|
|
@@ -2111,7 +2133,7 @@ function applyStateRemoveWhere(op, ctx) {
|
|
|
2111
2133
|
function requireArrayValue(entry, op) {
|
|
2112
2134
|
if (!Array.isArray(entry.value))
|
|
2113
2135
|
throw new Error(
|
|
2114
|
-
`${op.type} target ${op.target.scope}:"${op.target.
|
|
2136
|
+
`${op.type} target ${op.target.scope}:"${op.target.field}" holds a non-array ${entry._type} value \u2014 where-ops operate on array entries`
|
|
2115
2137
|
);
|
|
2116
2138
|
return entry.value;
|
|
2117
2139
|
}
|
|
@@ -2132,19 +2154,19 @@ function setEntryValue(entry, value) {
|
|
|
2132
2154
|
entry.value = value;
|
|
2133
2155
|
}
|
|
2134
2156
|
function locateEntry(ctx, target) {
|
|
2135
|
-
const entry =
|
|
2157
|
+
const entry = fieldHost(ctx, target.scope)?.find((s) => s.name === target.field);
|
|
2136
2158
|
if (entry === void 0)
|
|
2137
2159
|
throw new Error(
|
|
2138
|
-
`Op target ${target.scope}:"${target.
|
|
2160
|
+
`Op target ${target.scope}:"${target.field}" has no resolved field entry on this instance \u2014 the deployed definition and the instance state have diverged`
|
|
2139
2161
|
);
|
|
2140
2162
|
return entry;
|
|
2141
2163
|
}
|
|
2142
|
-
function
|
|
2143
|
-
if (scope === "workflow") return ctx.mutation.
|
|
2164
|
+
function fieldHost(ctx, scope) {
|
|
2165
|
+
if (scope === "workflow") return ctx.mutation.fields;
|
|
2144
2166
|
const stageEntry = findOpenStageEntry(ctx.mutation);
|
|
2145
|
-
if (scope === "stage") return stageEntry?.
|
|
2167
|
+
if (scope === "stage") return stageEntry?.fields;
|
|
2146
2168
|
const task = stageEntry?.tasks.find((t) => t.name === ctx.taskName);
|
|
2147
|
-
return task !== void 0 && task.
|
|
2169
|
+
return task !== void 0 && task.fields === void 0 && (task.fields = []), task?.fields;
|
|
2148
2170
|
}
|
|
2149
2171
|
function resolveOpSource(src, ctx) {
|
|
2150
2172
|
const staticValue = resolveStaticSource(src, ctx);
|
|
@@ -2154,7 +2176,7 @@ function resolveOpSource(src, ctx) {
|
|
|
2154
2176
|
return ctx.self;
|
|
2155
2177
|
case "stage":
|
|
2156
2178
|
return ctx.stage;
|
|
2157
|
-
case "
|
|
2179
|
+
case "fieldRead": {
|
|
2158
2180
|
const value = readEntryFromMutation(ctx, src);
|
|
2159
2181
|
return src.path !== void 0 ? getPath(value, src.path) : value;
|
|
2160
2182
|
}
|
|
@@ -2165,7 +2187,7 @@ function resolveOpSource(src, ctx) {
|
|
|
2165
2187
|
return out;
|
|
2166
2188
|
}
|
|
2167
2189
|
default:
|
|
2168
|
-
throw new Error(`Source type "${src.type}" is a
|
|
2190
|
+
throw new Error(`Source type "${src.type}" is a field-entry origin, not a value`);
|
|
2169
2191
|
}
|
|
2170
2192
|
}
|
|
2171
2193
|
function entryValue(entries, name) {
|
|
@@ -2174,7 +2196,7 @@ function entryValue(entries, name) {
|
|
|
2174
2196
|
function readEntryFromMutation(ctx, src) {
|
|
2175
2197
|
const scopes = src.scope !== void 0 ? [src.scope] : ["stage", "workflow"], stageEntry = findOpenStageEntry(ctx.mutation);
|
|
2176
2198
|
for (const scope of scopes) {
|
|
2177
|
-
const host = scope === "workflow" ? ctx.mutation.
|
|
2199
|
+
const host = scope === "workflow" ? ctx.mutation.fields : stageEntry?.fields, value = entryValue(host, src.field);
|
|
2178
2200
|
if (value !== void 0) return value;
|
|
2179
2201
|
}
|
|
2180
2202
|
}
|
|
@@ -2206,9 +2228,9 @@ async function commitInvoke(ctx, taskName, actor) {
|
|
|
2206
2228
|
}
|
|
2207
2229
|
async function activateTask(ctx, mutation, task, entry, actor) {
|
|
2208
2230
|
const now = ctx.now;
|
|
2209
|
-
if (entry.status = "active", entry.startedAt = now, task.
|
|
2231
|
+
if (entry.status = "active", entry.startedAt = now, task.fields !== void 0 && task.fields.length > 0) {
|
|
2210
2232
|
const stage = findStage(ctx.definition, mutation.currentStage);
|
|
2211
|
-
entry.
|
|
2233
|
+
entry.fields = await resolveTaskFieldEntries({
|
|
2212
2234
|
client: ctx.client,
|
|
2213
2235
|
instance: ctx.instance,
|
|
2214
2236
|
stage,
|
|
@@ -2313,7 +2335,7 @@ async function enterStage(ctx, mutation, nextStage, actor, at) {
|
|
|
2313
2335
|
_key: randomKey(),
|
|
2314
2336
|
name: nextStage.name,
|
|
2315
2337
|
enteredAt: at,
|
|
2316
|
-
|
|
2338
|
+
fields: await resolveStageFieldEntries({
|
|
2317
2339
|
client: ctx.client,
|
|
2318
2340
|
instance: ctx.instance,
|
|
2319
2341
|
stage: nextStage,
|
|
@@ -2453,7 +2475,7 @@ async function primeInitialStage(client, instanceId, actor, clientForGdr, clock)
|
|
|
2453
2475
|
_key: randomKey(),
|
|
2454
2476
|
name: stage.name,
|
|
2455
2477
|
enteredAt: now,
|
|
2456
|
-
|
|
2478
|
+
fields: await resolveStageFieldEntries({ client, instance, stage, now }),
|
|
2457
2479
|
tasks: await buildStageTasks(ctx, stage)
|
|
2458
2480
|
}, committed = await client.patch(instance._id).set({
|
|
2459
2481
|
stages: [initialStageEntry],
|
|
@@ -2682,6 +2704,85 @@ async function fetchGrantsCached(requestFn, resourcePath) {
|
|
|
2682
2704
|
return;
|
|
2683
2705
|
}
|
|
2684
2706
|
}
|
|
2707
|
+
function effectiveEditable(baseline, override) {
|
|
2708
|
+
if (baseline === void 0) return;
|
|
2709
|
+
if (override === void 0) return baseline;
|
|
2710
|
+
if (baseline === !0 && override === !0) return !0;
|
|
2711
|
+
const parts = [baseline, override].map((e) => e === !0 ? void 0 : e);
|
|
2712
|
+
return andConditions(parts) ?? !0;
|
|
2713
|
+
}
|
|
2714
|
+
function slotSites(definition, stage) {
|
|
2715
|
+
const sites = [];
|
|
2716
|
+
for (const entry of definition.fields ?? []) sites.push({ scope: "workflow", entry });
|
|
2717
|
+
for (const entry of stage.fields ?? []) sites.push({ scope: "stage", entry });
|
|
2718
|
+
for (const task of stage.tasks ?? [])
|
|
2719
|
+
for (const entry of task.fields ?? []) sites.push({ scope: "task", task: task.name, entry });
|
|
2720
|
+
return sites;
|
|
2721
|
+
}
|
|
2722
|
+
function toResolvedSlot(site, stage) {
|
|
2723
|
+
return {
|
|
2724
|
+
scope: site.scope,
|
|
2725
|
+
...site.task !== void 0 ? { task: site.task } : {},
|
|
2726
|
+
name: site.entry.name,
|
|
2727
|
+
type: site.entry.type,
|
|
2728
|
+
...site.entry.title !== void 0 ? { title: site.entry.title } : {},
|
|
2729
|
+
ref: { scope: site.scope, field: site.entry.name },
|
|
2730
|
+
effective: effectiveEditable(site.entry.editable, stage.editable?.[site.entry.name])
|
|
2731
|
+
};
|
|
2732
|
+
}
|
|
2733
|
+
function editableSlotsInStage(definition, stage) {
|
|
2734
|
+
return slotSites(definition, stage).filter((site) => site.entry.editable !== void 0).map((site) => toResolvedSlot(site, stage));
|
|
2735
|
+
}
|
|
2736
|
+
function editTargetMatches(slot, target) {
|
|
2737
|
+
return slot.name !== target.field ? !1 : target.task !== void 0 ? slot.scope === "task" && slot.task === target.task : target.scope !== void 0 ? slot.scope === target.scope : !0;
|
|
2738
|
+
}
|
|
2739
|
+
const SCOPE_PRECEDENCE = { task: 0, stage: 1, workflow: 2 };
|
|
2740
|
+
function resolveEditTarget(args) {
|
|
2741
|
+
const { definition, stage, target } = args, found = slotSites(definition, stage).filter(
|
|
2742
|
+
(site) => editTargetMatches(
|
|
2743
|
+
{
|
|
2744
|
+
scope: site.scope,
|
|
2745
|
+
name: site.entry.name,
|
|
2746
|
+
...site.task !== void 0 ? { task: site.task } : {}
|
|
2747
|
+
},
|
|
2748
|
+
target
|
|
2749
|
+
)
|
|
2750
|
+
).sort((a, b) => SCOPE_PRECEDENCE[a.scope] - SCOPE_PRECEDENCE[b.scope])[0];
|
|
2751
|
+
return found ? toResolvedSlot(found, stage) : void 0;
|
|
2752
|
+
}
|
|
2753
|
+
function slotWindowOpen(instance, slot) {
|
|
2754
|
+
if (instance.completedAt !== void 0) return { open: !1, detail: "instance completed" };
|
|
2755
|
+
if (instance.abortedAt !== void 0) return { open: !1, detail: "instance aborted" };
|
|
2756
|
+
if (slot.scope !== "task") return { open: !0 };
|
|
2757
|
+
const status = findOpenStageEntry(instance)?.tasks.find((t) => t.name === slot.task)?.status;
|
|
2758
|
+
return status === "active" ? { open: !0 } : { open: !1, detail: `task "${slot.task}" is ${status ?? "not active"}` };
|
|
2759
|
+
}
|
|
2760
|
+
function readSlotValue(instance, slot) {
|
|
2761
|
+
return slotFieldHost(instance, slot)?.find((e) => e.name === slot.name)?.value;
|
|
2762
|
+
}
|
|
2763
|
+
function slotFieldHost(instance, slot) {
|
|
2764
|
+
if (slot.scope === "workflow") return instance.fields;
|
|
2765
|
+
const stageEntry = findOpenStageEntry(instance);
|
|
2766
|
+
return slot.scope === "stage" ? stageEntry?.fields : stageEntry?.tasks.find((t) => t.name === slot.task)?.fields;
|
|
2767
|
+
}
|
|
2768
|
+
function slotProvenance(instance, ref) {
|
|
2769
|
+
let latest;
|
|
2770
|
+
for (const entry of instance.history)
|
|
2771
|
+
entry._type === "opApplied" && (entry.target?.scope !== ref.scope || entry.target.field !== ref.field || (latest = { at: entry.at, ...entry.actor !== void 0 ? { actor: entry.actor } : {} }));
|
|
2772
|
+
return latest === void 0 ? {} : {
|
|
2773
|
+
...latest.actor !== void 0 ? { setBy: latest.actor } : {},
|
|
2774
|
+
setAt: latest.at
|
|
2775
|
+
};
|
|
2776
|
+
}
|
|
2777
|
+
function editDisabledReason(args) {
|
|
2778
|
+
const { effective, instance, window, guardDenial, predicateSatisfied } = args;
|
|
2779
|
+
if (effective === void 0) return { kind: "not-editable" };
|
|
2780
|
+
if (!window.open)
|
|
2781
|
+
return instance.completedAt !== void 0 ? { kind: "instance-completed", completedAt: instance.completedAt } : { kind: "edit-window-closed", detail: window.detail ?? "closed" };
|
|
2782
|
+
if (guardDenial !== void 0) return guardDenial;
|
|
2783
|
+
if (!predicateSatisfied)
|
|
2784
|
+
return { kind: "editor-not-permitted", predicate: effective === !0 ? "true" : effective };
|
|
2785
|
+
}
|
|
2685
2786
|
async function evaluateInstance(args) {
|
|
2686
2787
|
const { client, tag, instanceId, resourceClients } = args, now = (args.clock ?? wallClock)();
|
|
2687
2788
|
validateTag(tag);
|
|
@@ -2715,6 +2816,7 @@ async function evaluateFromSnapshot(args) {
|
|
|
2715
2816
|
actor,
|
|
2716
2817
|
snapshot,
|
|
2717
2818
|
scope,
|
|
2819
|
+
roleAliases: definition.roleAliases,
|
|
2718
2820
|
stageHasExits: !isTerminalStage(stage),
|
|
2719
2821
|
guardDenial
|
|
2720
2822
|
})
|
|
@@ -2736,16 +2838,65 @@ async function evaluateFromSnapshot(args) {
|
|
|
2736
2838
|
stage,
|
|
2737
2839
|
tasks: taskEvaluations,
|
|
2738
2840
|
transitions: transitionEvaluations
|
|
2739
|
-
}, pendingOnYou = taskEvaluations.filter((t) => t.pendingOnActor), canInteract = taskEvaluations.some((t) => t.actions.some((a) => a.allowed))
|
|
2841
|
+
}, pendingOnYou = taskEvaluations.filter((t) => t.pendingOnActor), canInteract = taskEvaluations.some((t) => t.actions.some((a) => a.allowed)), editGuardDenial = guardDenial?.kind === "mutation-guard-denied" ? guardDenial : void 0, editableSlots = await evaluateEditableSlots({
|
|
2842
|
+
instance,
|
|
2843
|
+
definition,
|
|
2844
|
+
stage,
|
|
2845
|
+
actor,
|
|
2846
|
+
snapshot,
|
|
2847
|
+
scope,
|
|
2848
|
+
guardDenial: editGuardDenial
|
|
2849
|
+
});
|
|
2740
2850
|
return {
|
|
2741
2851
|
instance,
|
|
2742
2852
|
definition,
|
|
2743
2853
|
actor,
|
|
2744
2854
|
currentStage,
|
|
2745
2855
|
pendingOnYou,
|
|
2746
|
-
canInteract
|
|
2856
|
+
canInteract,
|
|
2857
|
+
editableSlots
|
|
2747
2858
|
};
|
|
2748
2859
|
}
|
|
2860
|
+
async function evaluateEditableSlots(args) {
|
|
2861
|
+
const { instance, definition, stage, actor, snapshot, scope, guardDenial } = args, slots = [];
|
|
2862
|
+
for (const slot of editableSlotsInStage(definition, stage)) {
|
|
2863
|
+
const window = slotWindowOpen(instance, slot), predicateSatisfied = await editPredicateSatisfied({
|
|
2864
|
+
slot,
|
|
2865
|
+
window,
|
|
2866
|
+
guardDenial,
|
|
2867
|
+
instance,
|
|
2868
|
+
snapshot,
|
|
2869
|
+
scope,
|
|
2870
|
+
actor,
|
|
2871
|
+
roleAliases: definition.roleAliases
|
|
2872
|
+
}), reason = editDisabledReason({
|
|
2873
|
+
effective: slot.effective,
|
|
2874
|
+
instance,
|
|
2875
|
+
window,
|
|
2876
|
+
guardDenial,
|
|
2877
|
+
predicateSatisfied
|
|
2878
|
+
}), value = readSlotValue(instance, slot);
|
|
2879
|
+
slots.push({
|
|
2880
|
+
scope: slot.scope,
|
|
2881
|
+
...slot.task !== void 0 ? { task: slot.task } : {},
|
|
2882
|
+
name: slot.name,
|
|
2883
|
+
type: slot.type,
|
|
2884
|
+
...slot.title !== void 0 ? { title: slot.title } : {},
|
|
2885
|
+
value,
|
|
2886
|
+
editable: reason === void 0,
|
|
2887
|
+
...reason !== void 0 ? { disabledReason: reason } : {},
|
|
2888
|
+
...slotProvenance(instance, slot.ref)
|
|
2889
|
+
});
|
|
2890
|
+
}
|
|
2891
|
+
return slots;
|
|
2892
|
+
}
|
|
2893
|
+
async function editPredicateSatisfied(args) {
|
|
2894
|
+
const { slot, window, guardDenial, instance, snapshot, scope, actor, roleAliases } = args;
|
|
2895
|
+
if (!window.open || guardDenial !== void 0) return !1;
|
|
2896
|
+
if (slot.effective === !0) return !0;
|
|
2897
|
+
const params = slot.scope === "task" && slot.task !== void 0 ? taskScopeFor({ scope, instance, snapshot, actor, taskName: slot.task, roleAliases }) : scope;
|
|
2898
|
+
return evaluateCondition({ condition: slot.effective, snapshot, params });
|
|
2899
|
+
}
|
|
2749
2900
|
async function renderScope(args) {
|
|
2750
2901
|
const { instance, definition, actor, grants, snapshot, now } = args, params = {
|
|
2751
2902
|
...buildParams({ instance, now, snapshot }),
|
|
@@ -2771,15 +2922,36 @@ async function advisoryCan(instance, actor, grants) {
|
|
|
2771
2922
|
});
|
|
2772
2923
|
return can;
|
|
2773
2924
|
}
|
|
2774
|
-
|
|
2775
|
-
const {
|
|
2925
|
+
function taskScopeFor(args) {
|
|
2926
|
+
const { scope, instance, snapshot, actor, taskName, roleAliases } = args;
|
|
2927
|
+
return {
|
|
2776
2928
|
...scope,
|
|
2777
|
-
|
|
2778
|
-
...scope.
|
|
2779
|
-
...
|
|
2929
|
+
fields: {
|
|
2930
|
+
...scope.fields,
|
|
2931
|
+
...scopedFieldOverlay(instance, snapshot, taskName)
|
|
2780
2932
|
},
|
|
2781
|
-
assigned
|
|
2782
|
-
}
|
|
2933
|
+
assigned: assignedFor(instance, taskName, actor, roleAliases)
|
|
2934
|
+
};
|
|
2935
|
+
}
|
|
2936
|
+
async function evaluateTask(args) {
|
|
2937
|
+
const {
|
|
2938
|
+
task,
|
|
2939
|
+
statusEntry,
|
|
2940
|
+
instance,
|
|
2941
|
+
actor,
|
|
2942
|
+
snapshot,
|
|
2943
|
+
scope,
|
|
2944
|
+
roleAliases,
|
|
2945
|
+
stageHasExits,
|
|
2946
|
+
guardDenial
|
|
2947
|
+
} = args, status = statusEntry?.status ?? "pending", taskScope = taskScopeFor({
|
|
2948
|
+
scope,
|
|
2949
|
+
instance,
|
|
2950
|
+
snapshot,
|
|
2951
|
+
actor,
|
|
2952
|
+
taskName: task.name,
|
|
2953
|
+
roleAliases
|
|
2954
|
+
}), assigned = taskScope.assigned === !0, unmetRequirements = await evaluateRequirements({
|
|
2783
2955
|
requirements: task.requirements,
|
|
2784
2956
|
snapshot,
|
|
2785
2957
|
params: taskScope
|
|
@@ -2802,8 +2974,8 @@ async function evaluateTask(args) {
|
|
|
2802
2974
|
status,
|
|
2803
2975
|
// "pending on actor" treats both `pending` and `active` as waiting on
|
|
2804
2976
|
// the assignee — pending tasks just haven't been activated yet, but
|
|
2805
|
-
// they're still inbox items. The inbox reads the assignees-kind
|
|
2806
|
-
// entry BY KIND (who-data is
|
|
2977
|
+
// they're still inbox items. The inbox reads the assignees-kind field
|
|
2978
|
+
// entry BY KIND (who-data is fields).
|
|
2807
2979
|
pendingOnActor: (status === "active" || status === "pending") && assigned,
|
|
2808
2980
|
...unmetRequirements.length > 0 ? { unmetRequirements } : {},
|
|
2809
2981
|
actions
|
|
@@ -3006,10 +3178,7 @@ async function commitAbort(ctx, reason, actor) {
|
|
|
3006
3178
|
async function fireAction(args) {
|
|
3007
3179
|
const { client, instanceId, task, action, params, options } = args;
|
|
3008
3180
|
for (let attempt = 1; attempt <= CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS; attempt++) {
|
|
3009
|
-
const ctx = await
|
|
3010
|
-
...options?.clock ? { clock: options.clock } : {},
|
|
3011
|
-
...options?.clientForGdr ? { clientForGdr: options.clientForGdr } : {}
|
|
3012
|
-
});
|
|
3181
|
+
const ctx = await loadCallContext(client, instanceId, options);
|
|
3013
3182
|
try {
|
|
3014
3183
|
return await commitAction(ctx, task, action, params, options);
|
|
3015
3184
|
} catch (error) {
|
|
@@ -3075,7 +3244,7 @@ async function commitAction(ctx, taskName, actionName, callerParams, options) {
|
|
|
3075
3244
|
}), await persistThenDeploy(
|
|
3076
3245
|
ctx,
|
|
3077
3246
|
mutation,
|
|
3078
|
-
() => ranOps.some(
|
|
3247
|
+
() => ranOps.some(isFieldOp) ? refreshStageGuards(ctx, mutation, stage.name) : Promise.resolve()
|
|
3079
3248
|
), {
|
|
3080
3249
|
fired: !0,
|
|
3081
3250
|
task: taskName,
|
|
@@ -3104,6 +3273,114 @@ function formatDisabledReason(task, action, reason) {
|
|
|
3104
3273
|
const detail = disabledReasonDetail[reason.kind](reason);
|
|
3105
3274
|
return `Action "${task}:${action}" is not allowed: ${detail}`;
|
|
3106
3275
|
}
|
|
3276
|
+
class EditFieldDeniedError extends Error {
|
|
3277
|
+
reason;
|
|
3278
|
+
target;
|
|
3279
|
+
constructor(args) {
|
|
3280
|
+
super(formatEditDisabledReason(args.target, args.reason)), this.name = "EditFieldDeniedError", this.reason = args.reason, this.target = args.target;
|
|
3281
|
+
}
|
|
3282
|
+
}
|
|
3283
|
+
const editDisabledReasonDetail = {
|
|
3284
|
+
"not-editable": () => "slot is not declared editable",
|
|
3285
|
+
"instance-completed": (r) => `instance completed at ${r.completedAt}`,
|
|
3286
|
+
"edit-window-closed": (r) => `edit window closed (${r.detail})`,
|
|
3287
|
+
"editor-not-permitted": (r) => `editor not permitted (${r.predicate})`,
|
|
3288
|
+
"mutation-guard-denied": (r) => `blocked by mutation guard(s) [${r.guardIds.join(", ")}]${r.detail ? ` (${r.detail})` : ""}`
|
|
3289
|
+
};
|
|
3290
|
+
function formatEditDisabledReason(target, reason) {
|
|
3291
|
+
const detail = editDisabledReasonDetail[reason.kind](
|
|
3292
|
+
reason
|
|
3293
|
+
), where = target.task !== void 0 ? `${target.task}.${target.field}` : target.field;
|
|
3294
|
+
return `Field "${target.scope}:${where}" is not editable: ${detail}`;
|
|
3295
|
+
}
|
|
3296
|
+
async function editField(args) {
|
|
3297
|
+
const { client, instanceId, target, mode = "set", value, options } = args;
|
|
3298
|
+
for (let attempt = 1; attempt <= CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS; attempt++) {
|
|
3299
|
+
const ctx = await loadCallContext(client, instanceId, options);
|
|
3300
|
+
try {
|
|
3301
|
+
return await commitEdit(ctx, target, mode, value, options);
|
|
3302
|
+
} catch (error) {
|
|
3303
|
+
if (!isRevisionConflict(error)) throw error;
|
|
3304
|
+
}
|
|
3305
|
+
}
|
|
3306
|
+
throw new ConcurrentEditFieldError({
|
|
3307
|
+
instanceId,
|
|
3308
|
+
target: labelTarget(target),
|
|
3309
|
+
attempts: CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS
|
|
3310
|
+
});
|
|
3311
|
+
}
|
|
3312
|
+
async function commitEdit(ctx, target, mode, value, options) {
|
|
3313
|
+
const actor = options?.actor, stage = findStage(ctx.definition, ctx.instance.currentStage), slot = resolveEditTarget({ definition: ctx.definition, stage, target });
|
|
3314
|
+
if (slot === void 0)
|
|
3315
|
+
throw new Error(
|
|
3316
|
+
`No field slot "${describeTarget(target)}" resolves in current stage "${stage.name}" of ${ctx.definition.name}`
|
|
3317
|
+
);
|
|
3318
|
+
await assertSlotEditable(ctx, slot, options);
|
|
3319
|
+
const op = buildEditOp(slot, mode, value), mutation = startMutation(ctx.instance), ranOps = runOps({
|
|
3320
|
+
ops: [op],
|
|
3321
|
+
mutation,
|
|
3322
|
+
stage: stage.name,
|
|
3323
|
+
origin: {
|
|
3324
|
+
edit: !0,
|
|
3325
|
+
...slot.scope === "task" && slot.task !== void 0 ? { task: slot.task } : {}
|
|
3326
|
+
},
|
|
3327
|
+
params: {},
|
|
3328
|
+
actor,
|
|
3329
|
+
self: selfGdr(ctx.instance),
|
|
3330
|
+
now: ctx.now
|
|
3331
|
+
});
|
|
3332
|
+
return await persistThenDeploy(
|
|
3333
|
+
ctx,
|
|
3334
|
+
mutation,
|
|
3335
|
+
() => ranOps.some(isFieldOp) ? refreshStageGuards(ctx, mutation, stage.name) : Promise.resolve()
|
|
3336
|
+
), {
|
|
3337
|
+
edited: !0,
|
|
3338
|
+
target: slotTarget(slot),
|
|
3339
|
+
...ranOps.length > 0 ? { ranOps } : {}
|
|
3340
|
+
};
|
|
3341
|
+
}
|
|
3342
|
+
async function assertSlotEditable(ctx, slot, options) {
|
|
3343
|
+
const actor = options?.actor, window = slotWindowOpen(ctx.instance, slot), can = options?.grants !== void 0 && actor !== void 0 ? await advisoryCan(ctx.instance, actor, options.grants) : void 0, predicateSatisfied = window.open && slot.effective !== !0 && slot.effective !== void 0 ? await ctxEvaluateCondition(ctx, slot.effective, {
|
|
3344
|
+
...slot.task !== void 0 ? { taskName: slot.task } : {},
|
|
3345
|
+
...actor !== void 0 ? { actor } : {},
|
|
3346
|
+
...can !== void 0 ? { vars: { can } } : {}
|
|
3347
|
+
}) : slot.effective === !0, reason = editDisabledReason({
|
|
3348
|
+
effective: slot.effective,
|
|
3349
|
+
instance: ctx.instance,
|
|
3350
|
+
window,
|
|
3351
|
+
guardDenial: void 0,
|
|
3352
|
+
predicateSatisfied
|
|
3353
|
+
});
|
|
3354
|
+
if (reason !== void 0) throw new EditFieldDeniedError({ target: slotTarget(slot), reason });
|
|
3355
|
+
}
|
|
3356
|
+
function buildEditOp(slot, mode, value) {
|
|
3357
|
+
if (mode === "unset") return { type: "field.unset", target: slot.ref };
|
|
3358
|
+
if (value === void 0)
|
|
3359
|
+
throw new Error(`editField mode "${mode}" requires a value for slot "${slot.name}"`);
|
|
3360
|
+
return {
|
|
3361
|
+
type: mode === "append" ? "field.append" : "field.set",
|
|
3362
|
+
target: slot.ref,
|
|
3363
|
+
value: { type: "literal", value }
|
|
3364
|
+
};
|
|
3365
|
+
}
|
|
3366
|
+
function slotTarget(slot) {
|
|
3367
|
+
return {
|
|
3368
|
+
scope: slot.scope,
|
|
3369
|
+
field: slot.name,
|
|
3370
|
+
...slot.task !== void 0 ? { task: slot.task } : {}
|
|
3371
|
+
};
|
|
3372
|
+
}
|
|
3373
|
+
function labelTarget(target) {
|
|
3374
|
+
return {
|
|
3375
|
+
scope: target.scope ?? (target.task !== void 0 ? "task" : "workflow"),
|
|
3376
|
+
field: target.field,
|
|
3377
|
+
...target.task !== void 0 ? { task: target.task } : {}
|
|
3378
|
+
};
|
|
3379
|
+
}
|
|
3380
|
+
function describeTarget(target) {
|
|
3381
|
+
const where = target.task !== void 0 ? `${target.task}.${target.field}` : target.field;
|
|
3382
|
+
return target.scope !== void 0 ? `${target.scope}:${where}` : where;
|
|
3383
|
+
}
|
|
3107
3384
|
function bareIdFromSpawnRef(uri) {
|
|
3108
3385
|
if (!uri.includes(":")) return [uri];
|
|
3109
3386
|
try {
|
|
@@ -3150,18 +3427,60 @@ function buildClientForGdr(defaultClient, resolver) {
|
|
|
3150
3427
|
function toEffectsContextEntries(ctx) {
|
|
3151
3428
|
return Object.entries(ctx).map(([id, value]) => effectsContextEntry(id, value));
|
|
3152
3429
|
}
|
|
3430
|
+
async function dispatchGatedWrite(args) {
|
|
3431
|
+
const { client, tag, instanceId, resourceClients, access, apply } = args, evaluation = await evaluateInstance({
|
|
3432
|
+
client,
|
|
3433
|
+
tag,
|
|
3434
|
+
instanceId,
|
|
3435
|
+
access,
|
|
3436
|
+
clock: args.clock,
|
|
3437
|
+
...resourceClients !== void 0 ? { resourceClients } : {}
|
|
3438
|
+
}), ranOps = await apply(args.before, evaluation), cascaded = await cascade(client, instanceId, args.actor, args.clientForGdr, args.clock);
|
|
3439
|
+
return {
|
|
3440
|
+
instance: await reload(client, instanceId, tag),
|
|
3441
|
+
cascaded,
|
|
3442
|
+
fired: !0,
|
|
3443
|
+
...ranOps !== void 0 ? { ranOps } : {}
|
|
3444
|
+
};
|
|
3445
|
+
}
|
|
3153
3446
|
function assertActionAllowed(evaluation, task, action) {
|
|
3154
3447
|
const actionEval = evaluation.currentStage.tasks.find((t) => t.task.name === task)?.actions.find((a) => a.action.name === action);
|
|
3155
3448
|
if (actionEval?.allowed === !1 && actionEval.disabledReason)
|
|
3156
3449
|
throw new ActionDisabledError({ task, action, reason: actionEval.disabledReason });
|
|
3157
3450
|
}
|
|
3158
|
-
|
|
3159
|
-
const
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3451
|
+
function assertEditAllowed(evaluation, target) {
|
|
3452
|
+
const slot = evaluation.editableSlots.filter((s) => editTargetMatches(s, target)).sort((a, b) => SCOPE_PRECEDENCE[a.scope] - SCOPE_PRECEDENCE[b.scope])[0];
|
|
3453
|
+
if (slot !== void 0 && !slot.editable && slot.disabledReason !== void 0)
|
|
3454
|
+
throw new EditFieldDeniedError({
|
|
3455
|
+
target: {
|
|
3456
|
+
scope: slot.scope,
|
|
3457
|
+
field: slot.name,
|
|
3458
|
+
...slot.task !== void 0 ? { task: slot.task } : {}
|
|
3459
|
+
},
|
|
3460
|
+
reason: slot.disabledReason
|
|
3461
|
+
});
|
|
3462
|
+
}
|
|
3463
|
+
async function applyEdit(args) {
|
|
3464
|
+
const { client, instance, target, mode, value, actor, grants, clock, clientForGdr } = args;
|
|
3465
|
+
return (await editField({
|
|
3466
|
+
client,
|
|
3467
|
+
instanceId: instance._id,
|
|
3468
|
+
target,
|
|
3469
|
+
...mode !== void 0 ? { mode } : {},
|
|
3470
|
+
...value !== void 0 ? { value } : {},
|
|
3471
|
+
options: buildEngineCallOptions({ actor, grants, clock, clientForGdr })
|
|
3472
|
+
})).ranOps;
|
|
3473
|
+
}
|
|
3474
|
+
function buildEngineCallOptions(args) {
|
|
3475
|
+
return {
|
|
3476
|
+
actor: args.actor,
|
|
3477
|
+
...args.grants !== void 0 ? { grants: args.grants } : {},
|
|
3478
|
+
...args.clock !== void 0 ? { clock: args.clock } : {},
|
|
3479
|
+
...args.clientForGdr !== void 0 ? { clientForGdr: args.clientForGdr } : {}
|
|
3164
3480
|
};
|
|
3481
|
+
}
|
|
3482
|
+
async function applyAction(args) {
|
|
3483
|
+
const { client, instance, task, action, params, actor, grants, clock, clientForGdr } = args, options = buildEngineCallOptions({ actor, grants, clock, clientForGdr });
|
|
3165
3484
|
return findOpenStageEntry(instance)?.tasks.find((t) => t.name === task)?.status === "pending" && await invokeTask({ client, instanceId: instance._id, task, options }), (await fireAction({
|
|
3166
3485
|
client,
|
|
3167
3486
|
instanceId: instance._id,
|
|
@@ -3316,7 +3635,7 @@ const workflow = {
|
|
|
3316
3635
|
workflowResource,
|
|
3317
3636
|
definition: definitionName,
|
|
3318
3637
|
version,
|
|
3319
|
-
|
|
3638
|
+
initialFields,
|
|
3320
3639
|
ancestors,
|
|
3321
3640
|
effectsContext,
|
|
3322
3641
|
instanceId,
|
|
@@ -3326,9 +3645,9 @@ const workflow = {
|
|
|
3326
3645
|
throw new Error(
|
|
3327
3646
|
`Initial stage "${definition.initialStage}" missing in ${definitionName} v${definition.version}`
|
|
3328
3647
|
);
|
|
3329
|
-
const now = clock(), effectsContextEntries = effectsContext ? toEffectsContextEntries(effectsContext) : [],
|
|
3330
|
-
entryDefs: definition.
|
|
3331
|
-
|
|
3648
|
+
const now = clock(), effectsContextEntries = effectsContext ? toEffectsContextEntries(effectsContext) : [], resolvedFields = await resolveDeclaredFields({
|
|
3649
|
+
entryDefs: definition.fields ?? [],
|
|
3650
|
+
initialFields: initialFields ?? [],
|
|
3332
3651
|
ctx: {
|
|
3333
3652
|
client,
|
|
3334
3653
|
now,
|
|
@@ -3339,7 +3658,7 @@ const workflow = {
|
|
|
3339
3658
|
...perspective !== void 0 ? { perspective } : {}
|
|
3340
3659
|
},
|
|
3341
3660
|
randomKey
|
|
3342
|
-
}), effectivePerspective = perspective ??
|
|
3661
|
+
}), effectivePerspective = perspective ?? derivePerspectiveFromFields(resolvedFields), base = buildInstanceBase({
|
|
3343
3662
|
id,
|
|
3344
3663
|
now,
|
|
3345
3664
|
tag,
|
|
@@ -3347,7 +3666,7 @@ const workflow = {
|
|
|
3347
3666
|
definitionName: definition.name,
|
|
3348
3667
|
pinnedVersion: definition.version,
|
|
3349
3668
|
definition,
|
|
3350
|
-
|
|
3669
|
+
fields: resolvedFields,
|
|
3351
3670
|
effectsContext: effectsContextEntries,
|
|
3352
3671
|
ancestors: ancestors ?? [],
|
|
3353
3672
|
perspective: effectivePerspective,
|
|
@@ -3377,34 +3696,68 @@ const workflow = {
|
|
|
3377
3696
|
idempotent,
|
|
3378
3697
|
resourceClients
|
|
3379
3698
|
} = args, { access, actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, before = await reload(client, instanceId, tag);
|
|
3380
|
-
|
|
3381
|
-
return { instance: before, cascaded: 0, fired: !1 };
|
|
3382
|
-
const evaluation = await evaluateInstance({
|
|
3699
|
+
return findOpenStageEntry(before)?.tasks.find((t) => t.name === task) === void 0 && idempotent === !0 ? { instance: before, cascaded: 0, fired: !1 } : dispatchGatedWrite({
|
|
3383
3700
|
client,
|
|
3384
3701
|
tag,
|
|
3385
3702
|
instanceId,
|
|
3386
3703
|
access,
|
|
3704
|
+
actor,
|
|
3705
|
+
clientForGdr,
|
|
3387
3706
|
clock,
|
|
3388
|
-
|
|
3707
|
+
before,
|
|
3708
|
+
...resourceClients !== void 0 ? { resourceClients } : {},
|
|
3709
|
+
apply: (instance, evaluation) => (assertActionAllowed(evaluation, task, action), applyAction({
|
|
3710
|
+
client,
|
|
3711
|
+
instance,
|
|
3712
|
+
task,
|
|
3713
|
+
action,
|
|
3714
|
+
actor,
|
|
3715
|
+
...access.grants !== void 0 ? { grants: access.grants } : {},
|
|
3716
|
+
clock,
|
|
3717
|
+
clientForGdr,
|
|
3718
|
+
...params !== void 0 ? { params } : {}
|
|
3719
|
+
}))
|
|
3389
3720
|
});
|
|
3390
|
-
|
|
3391
|
-
|
|
3721
|
+
},
|
|
3722
|
+
/**
|
|
3723
|
+
* Edit a declared-editable field slot directly — reassign, reschedule,
|
|
3724
|
+
* claim-by-hand, append to a running log — through the generic edit seam,
|
|
3725
|
+
* instead of a bespoke action per field. Soft-gates on the slot's declared
|
|
3726
|
+
* editability (the same projection a UI renders), applies the edit as a
|
|
3727
|
+
* `field.*` op (so provenance + history are stamped by the op path),
|
|
3728
|
+
* refreshes the stage's guards, then cascades — an edit to a value a
|
|
3729
|
+
* transition reads can and should move the instance. Advisory like every
|
|
3730
|
+
* engine gate; the lake ACL + guards are the only hard locks.
|
|
3731
|
+
*
|
|
3732
|
+
* Each call is a discrete COMMIT (a history entry, a guard refresh, a
|
|
3733
|
+
* cascade, an `ifRevisionId` write), not a draft patch — so an inline-field
|
|
3734
|
+
* UI must bind it to a deliberate boundary (blur / Enter / Save / debounce),
|
|
3735
|
+
* never an `onChange` per keystroke.
|
|
3736
|
+
*/
|
|
3737
|
+
editField: async (args) => {
|
|
3738
|
+
const { client, tag, instanceId, target, mode, value, resourceClients } = args, { access, actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, before = await reload(client, instanceId, tag);
|
|
3739
|
+
return dispatchGatedWrite({
|
|
3392
3740
|
client,
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3741
|
+
tag,
|
|
3742
|
+
instanceId,
|
|
3743
|
+
access,
|
|
3396
3744
|
actor,
|
|
3397
|
-
...access.grants !== void 0 ? { grants: access.grants } : {},
|
|
3398
|
-
clock,
|
|
3399
3745
|
clientForGdr,
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3746
|
+
clock,
|
|
3747
|
+
before,
|
|
3748
|
+
...resourceClients !== void 0 ? { resourceClients } : {},
|
|
3749
|
+
apply: (instance, evaluation) => (assertEditAllowed(evaluation, target), applyEdit({
|
|
3750
|
+
client,
|
|
3751
|
+
instance,
|
|
3752
|
+
target,
|
|
3753
|
+
...mode !== void 0 ? { mode } : {},
|
|
3754
|
+
...value !== void 0 ? { value } : {},
|
|
3755
|
+
actor,
|
|
3756
|
+
...access.grants !== void 0 ? { grants: access.grants } : {},
|
|
3757
|
+
clock,
|
|
3758
|
+
clientForGdr
|
|
3759
|
+
}))
|
|
3760
|
+
});
|
|
3408
3761
|
},
|
|
3409
3762
|
/**
|
|
3410
3763
|
* Report a queued effect's outcome. Drains it from `pendingEffects`,
|
|
@@ -3546,7 +3899,7 @@ const workflow = {
|
|
|
3546
3899
|
* Hydrates the instance's snapshot (instance + ancestors + every doc
|
|
3547
3900
|
* declared by a `doc.ref` / `doc.refs` entry in scope), then
|
|
3548
3901
|
* evaluates the supplied GROQ in groq-js against that dataset. The
|
|
3549
|
-
* rendered scope is auto-bound: `$self`, `$
|
|
3902
|
+
* rendered scope is auto-bound: `$self`, `$fields`, `$parent`,
|
|
3550
3903
|
* `$ancestors`, `$stage`, `$now`, `$effects`, `$tasks`,
|
|
3551
3904
|
* `$allTasksDone`, `$anyTaskFailed` — ids in GDR URI form to match
|
|
3552
3905
|
* the snapshot's keying.
|
|
@@ -3822,6 +4175,9 @@ function createInstanceSession(args) {
|
|
|
3822
4175
|
...clock !== void 0 ? { now: clock() } : {},
|
|
3823
4176
|
...grants !== void 0 ? { grants } : {}
|
|
3824
4177
|
});
|
|
4178
|
+
}, settleAfterApply = async (actor, held, ranOps) => {
|
|
4179
|
+
const cascaded = await cascade(client, instance._id, actor, clientForGdr, clock, held);
|
|
4180
|
+
return instance = await reload(client, instance._id, tag), { instance, cascaded, fired: !0, ...ranOps !== void 0 ? { ranOps } : {} };
|
|
3825
4181
|
}, commit = async (run) => {
|
|
3826
4182
|
committing = !0;
|
|
3827
4183
|
const held = new Map(overlay);
|
|
@@ -3872,8 +4228,25 @@ function createInstanceSession(args) {
|
|
|
3872
4228
|
...grants !== void 0 ? { grants } : {},
|
|
3873
4229
|
...clock !== void 0 ? { clock } : {},
|
|
3874
4230
|
...params !== void 0 ? { params } : {}
|
|
3875
|
-
})
|
|
3876
|
-
return
|
|
4231
|
+
});
|
|
4232
|
+
return settleAfterApply(actor, held, ranOps);
|
|
4233
|
+
});
|
|
4234
|
+
},
|
|
4235
|
+
editField({ target, mode, value }) {
|
|
4236
|
+
return commit(async (actor, held) => {
|
|
4237
|
+
assertEditAllowed(await evaluateWith(held, heldGuards), target);
|
|
4238
|
+
const { grants } = await access(), ranOps = await applyEdit({
|
|
4239
|
+
client,
|
|
4240
|
+
instance,
|
|
4241
|
+
target,
|
|
4242
|
+
...mode !== void 0 ? { mode } : {},
|
|
4243
|
+
...value !== void 0 ? { value } : {},
|
|
4244
|
+
actor,
|
|
4245
|
+
clientForGdr,
|
|
4246
|
+
...grants !== void 0 ? { grants } : {},
|
|
4247
|
+
...clock !== void 0 ? { clock } : {}
|
|
4248
|
+
});
|
|
4249
|
+
return settleAfterApply(actor, held, ranOps);
|
|
3877
4250
|
});
|
|
3878
4251
|
}
|
|
3879
4252
|
};
|
|
@@ -3921,6 +4294,7 @@ function createEngine(args) {
|
|
|
3921
4294
|
deployDefinitions: (rest) => workflow.deployDefinitions(bind(rest)),
|
|
3922
4295
|
startInstance: (rest) => workflow.startInstance(bind(rest)),
|
|
3923
4296
|
fireAction: (rest) => workflow.fireAction(bind(rest)),
|
|
4297
|
+
editField: (rest) => workflow.editField(bind(rest)),
|
|
3924
4298
|
completeEffect: (rest) => workflow.completeEffect(bind(rest)),
|
|
3925
4299
|
tick: (rest) => workflow.tick(bind(rest)),
|
|
3926
4300
|
evaluateInstance: (rest) => evaluateInstance(bind(rest)),
|
|
@@ -4079,9 +4453,9 @@ const HISTORY_DISPLAY = {
|
|
|
4079
4453
|
},
|
|
4080
4454
|
opApplied: {
|
|
4081
4455
|
title: "Op applied",
|
|
4082
|
-
description: "An inline
|
|
4456
|
+
description: "An inline field-mutation op (field.set / field.append / status.set / etc.) ran during an action commit."
|
|
4083
4457
|
}
|
|
4084
|
-
},
|
|
4458
|
+
}, FIELD_SLOT_DISPLAY = {
|
|
4085
4459
|
"doc.ref": {
|
|
4086
4460
|
title: "Document reference",
|
|
4087
4461
|
description: "Single GDR pointer at a document in this or another resource."
|
|
@@ -4135,23 +4509,23 @@ const HISTORY_DISPLAY = {
|
|
|
4135
4509
|
description: "Ordered list of typed (user|role) assignees."
|
|
4136
4510
|
}
|
|
4137
4511
|
}, OP_DISPLAY = {
|
|
4138
|
-
"
|
|
4139
|
-
title: "Set
|
|
4140
|
-
description: "Overwrite a
|
|
4512
|
+
"field.set": {
|
|
4513
|
+
title: "Set field entry",
|
|
4514
|
+
description: "Overwrite a field entry's value with a resolved Source."
|
|
4141
4515
|
},
|
|
4142
|
-
"
|
|
4143
|
-
title: "Unset
|
|
4144
|
-
description: "Reset a
|
|
4516
|
+
"field.unset": {
|
|
4517
|
+
title: "Unset field entry",
|
|
4518
|
+
description: "Reset a field entry to its default (null for scalars, [] for array kinds)."
|
|
4145
4519
|
},
|
|
4146
|
-
"
|
|
4147
|
-
title: "Append to
|
|
4520
|
+
"field.append": {
|
|
4521
|
+
title: "Append to field entry",
|
|
4148
4522
|
description: "Push a resolved item onto an array-kind entry (notes, checklist, assignees, refs)."
|
|
4149
4523
|
},
|
|
4150
|
-
"
|
|
4524
|
+
"field.updateWhere": {
|
|
4151
4525
|
title: "Update matching rows",
|
|
4152
4526
|
description: "Merge fields into rows of an array-kind entry that match the predicate."
|
|
4153
4527
|
},
|
|
4154
|
-
"
|
|
4528
|
+
"field.removeWhere": {
|
|
4155
4529
|
title: "Remove matching rows",
|
|
4156
4530
|
description: "Drop rows of an array-kind entry that match the predicate."
|
|
4157
4531
|
},
|
|
@@ -4195,11 +4569,11 @@ const HISTORY_DISPLAY = {
|
|
|
4195
4569
|
},
|
|
4196
4570
|
[WORKFLOW_INSTANCE_TYPE]: {
|
|
4197
4571
|
title: "Workflow instance",
|
|
4198
|
-
description: "A running (or finished) workflow against its declared
|
|
4572
|
+
description: "A running (or finished) workflow against its declared fields."
|
|
4199
4573
|
}
|
|
4200
4574
|
}, DISPLAY = {
|
|
4201
4575
|
...HISTORY_DISPLAY,
|
|
4202
|
-
...
|
|
4576
|
+
...FIELD_SLOT_DISPLAY,
|
|
4203
4577
|
...OP_DISPLAY,
|
|
4204
4578
|
...EFFECTS_CONTEXT_DISPLAY,
|
|
4205
4579
|
...AUTHORING_DISPLAY
|
|
@@ -4216,10 +4590,13 @@ export {
|
|
|
4216
4590
|
ActionDisabledError,
|
|
4217
4591
|
ActionParamsInvalidError,
|
|
4218
4592
|
CascadeLimitError,
|
|
4593
|
+
ConcurrentEditFieldError,
|
|
4219
4594
|
ConcurrentFireActionError,
|
|
4220
4595
|
DEFAULT_CONTENT_PERSPECTIVE,
|
|
4221
4596
|
DISPLAY,
|
|
4222
4597
|
EFFECTS_CONTEXT_DISPLAY,
|
|
4598
|
+
EditFieldDeniedError,
|
|
4599
|
+
FIELD_SLOT_DISPLAY,
|
|
4223
4600
|
GUARD_DOC_TYPE,
|
|
4224
4601
|
GUARD_LIFTED_PREDICATE,
|
|
4225
4602
|
GUARD_OWNER,
|
|
@@ -4227,8 +4604,7 @@ export {
|
|
|
4227
4604
|
MutationGuardDeniedError,
|
|
4228
4605
|
OP_DISPLAY,
|
|
4229
4606
|
PartialGuardDeployError,
|
|
4230
|
-
|
|
4231
|
-
STATE_SLOT_DISPLAY,
|
|
4607
|
+
RequiredFieldNotProvidedError,
|
|
4232
4608
|
WORKFLOW_DEFINITION_TYPE,
|
|
4233
4609
|
WORKFLOW_INSTANCE_TYPE,
|
|
4234
4610
|
WorkflowStateDivergedError,
|