@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.cjs
CHANGED
|
@@ -126,7 +126,7 @@ function buildParams(args) {
|
|
|
126
126
|
const { instance, now, snapshot, extra } = args, currentTasks2 = findOpenStageEntry(instance)?.tasks ?? [];
|
|
127
127
|
return {
|
|
128
128
|
self: selfGdr(instance),
|
|
129
|
-
|
|
129
|
+
fields: renderedFields(instance.fields ?? [], snapshot),
|
|
130
130
|
parent: instance.ancestors.at(-1)?.id ?? null,
|
|
131
131
|
ancestors: instance.ancestors.map((a) => a.id),
|
|
132
132
|
stage: instance.currentStage,
|
|
@@ -141,7 +141,7 @@ function buildParams(args) {
|
|
|
141
141
|
...extra
|
|
142
142
|
};
|
|
143
143
|
}
|
|
144
|
-
function
|
|
144
|
+
function renderedFields(entries, snapshot) {
|
|
145
145
|
const out = {};
|
|
146
146
|
for (const entry of entries) out[entry.name] = renderedValue(entry, snapshot);
|
|
147
147
|
return out;
|
|
@@ -149,17 +149,17 @@ function renderedState(entries, snapshot) {
|
|
|
149
149
|
function renderedValue(entry, snapshot) {
|
|
150
150
|
return entry._type !== "doc.ref" || entry.value === null || snapshot === void 0 ? entry.value : snapshot.docs.find((d) => d._id === entry.value.id) ?? entry.value;
|
|
151
151
|
}
|
|
152
|
-
function
|
|
152
|
+
function scopedFieldOverlay(instance, snapshot, taskName) {
|
|
153
153
|
const stageEntry = findOpenStageEntry(instance);
|
|
154
154
|
if (stageEntry === void 0) return {};
|
|
155
|
-
const
|
|
156
|
-
return { ...
|
|
155
|
+
const stageFields = renderedFields(stageEntry.fields ?? [], snapshot), task = taskName ? stageEntry.tasks.find((t) => t.name === taskName) : void 0;
|
|
156
|
+
return { ...stageFields, ...renderedFields(task?.fields ?? [], snapshot) };
|
|
157
157
|
}
|
|
158
|
-
function assignedFor(instance, taskName, actor) {
|
|
158
|
+
function assignedFor(instance, taskName, actor, roleAliases) {
|
|
159
159
|
if (actor === void 0) return !1;
|
|
160
|
-
const entry = findOpenStageEntry(instance)?.tasks.find((t) => t.name === taskName)?.
|
|
160
|
+
const entry = findOpenStageEntry(instance)?.tasks.find((t) => t.name === taskName)?.fields?.find((s) => s._type === "assignees");
|
|
161
161
|
return entry === void 0 ? !1 : entry.value.some(
|
|
162
|
-
(a) => a.type === "user" ? a.id === actor.id : (actor.roles
|
|
162
|
+
(a) => a.type === "user" ? a.id === actor.id : schema.actorFulfillsRole(actor.roles, a.role, roleAliases)
|
|
163
163
|
);
|
|
164
164
|
}
|
|
165
165
|
function effectsContextMap(instance) {
|
|
@@ -184,17 +184,17 @@ function paramsForLake(params) {
|
|
|
184
184
|
self: typeof params.self == "string" ? bareId(params.self) : params.self,
|
|
185
185
|
parent: typeof params.parent == "string" ? bareId(params.parent) : params.parent,
|
|
186
186
|
ancestors: Array.isArray(params.ancestors) ? params.ancestors.map((a) => typeof a == "string" ? bareId(a) : a) : params.ancestors,
|
|
187
|
-
|
|
187
|
+
fields: stripFieldsForLake(params.fields)
|
|
188
188
|
};
|
|
189
189
|
}
|
|
190
|
-
function
|
|
190
|
+
function stripFieldsForLake(value) {
|
|
191
191
|
if (value == null) return value;
|
|
192
192
|
if (typeof value == "string") return bareId(value);
|
|
193
|
-
if (Array.isArray(value)) return value.map(
|
|
193
|
+
if (Array.isArray(value)) return value.map(stripFieldsForLake);
|
|
194
194
|
if (typeof value == "object") {
|
|
195
195
|
const out = {};
|
|
196
196
|
for (const [k, v2] of Object.entries(value))
|
|
197
|
-
out[k] =
|
|
197
|
+
out[k] = stripFieldsForLake(v2);
|
|
198
198
|
return out;
|
|
199
199
|
}
|
|
200
200
|
return value;
|
|
@@ -213,17 +213,17 @@ function getPath(value, path) {
|
|
|
213
213
|
function resourceOf(p) {
|
|
214
214
|
return p.scheme === "dataset" ? { type: "dataset", id: `${p.projectId}.${p.dataset}` } : { type: p.scheme, id: p.resourceId ?? "" };
|
|
215
215
|
}
|
|
216
|
-
const
|
|
216
|
+
const FIELD_READ = /^\$fields\.(\w+)(?:\.(.+))?$/, EFFECTS_READ = /^\$effects\['([^']+)'\](?:\.(.+))?$/;
|
|
217
217
|
function isGuardReadExpr(expr) {
|
|
218
|
-
return expr === "$self" || expr === "$now" ||
|
|
218
|
+
return expr === "$self" || expr === "$now" || FIELD_READ.test(expr) || EFFECTS_READ.test(expr);
|
|
219
219
|
}
|
|
220
220
|
function resolveGuardRead(expr, ctx) {
|
|
221
221
|
if (expr === "$self") return selfGdr(ctx.instance);
|
|
222
222
|
if (expr === "$now") return ctx.now;
|
|
223
|
-
const
|
|
224
|
-
if (
|
|
225
|
-
const value = (ctx.instance.
|
|
226
|
-
return
|
|
223
|
+
const fieldRead = FIELD_READ.exec(expr);
|
|
224
|
+
if (fieldRead !== null) {
|
|
225
|
+
const value = (ctx.instance.fields ?? []).find((s) => s.name === fieldRead[1])?.value;
|
|
226
|
+
return fieldRead[2] !== void 0 ? getPath(value, fieldRead[2]) : value;
|
|
227
227
|
}
|
|
228
228
|
const effectsRead = EFFECTS_READ.exec(expr);
|
|
229
229
|
if (effectsRead !== null) {
|
|
@@ -231,7 +231,7 @@ function resolveGuardRead(expr, ctx) {
|
|
|
231
231
|
return effectsRead[2] !== void 0 ? getPath(outputs, effectsRead[2]) : outputs;
|
|
232
232
|
}
|
|
233
233
|
throw new Error(
|
|
234
|
-
`Guard read "${expr}" is not a supported deploy-time value \u2014 use "$self", "$now", "$
|
|
234
|
+
`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)`
|
|
235
235
|
);
|
|
236
236
|
}
|
|
237
237
|
function resolveIdRefTarget(expr, ctx) {
|
|
@@ -281,8 +281,8 @@ function resolveMetadata(metadata, ctx) {
|
|
|
281
281
|
}
|
|
282
282
|
function validateDefinition(definition) {
|
|
283
283
|
const v2 = createDefinitionValidator();
|
|
284
|
-
for (const entry of definition.
|
|
285
|
-
v2.checkEntry(entry, `workflow.
|
|
284
|
+
for (const entry of definition.fields ?? [])
|
|
285
|
+
v2.checkEntry(entry, `workflow.fields "${entry.name}"`);
|
|
286
286
|
for (const [name, groq] of Object.entries(definition.predicates ?? {}))
|
|
287
287
|
v2.checkCondition(groq, `predicate "${name}"`);
|
|
288
288
|
for (const stage of definition.stages)
|
|
@@ -304,7 +304,7 @@ function createDefinitionValidator() {
|
|
|
304
304
|
}
|
|
305
305
|
}, rejectTypeScan = (groq, where) => {
|
|
306
306
|
/\*\s*\[\s*_type\b/.test(groq) && errors.push(
|
|
307
|
-
` \xB7 ${where}: condition scans by \`_type\` \u2014 that's a discovery query, not a predicate. Conditions evaluate against the in-memory snapshot (instance + ancestors +
|
|
307
|
+
` \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.`
|
|
308
308
|
);
|
|
309
309
|
};
|
|
310
310
|
return { errors, tryParse, checkCondition: (groq, where) => {
|
|
@@ -313,13 +313,13 @@ function createDefinitionValidator() {
|
|
|
313
313
|
entry.source.type === "query" && tryParse(entry.source.query, `${label}.query`);
|
|
314
314
|
}, checkGuardRead: (expr, where) => {
|
|
315
315
|
isGuardReadExpr(expr) || errors.push(
|
|
316
|
-
` \xB7 ${where}: guard read "${expr}" is not a supported deploy-time value \u2014 use "$self", "$now", "$
|
|
316
|
+
` \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)`
|
|
317
317
|
);
|
|
318
318
|
} };
|
|
319
319
|
}
|
|
320
320
|
function validateStage(v2, stage) {
|
|
321
|
-
for (const entry of stage.
|
|
322
|
-
v2.checkEntry(entry, `stage "${stage.name}".
|
|
321
|
+
for (const entry of stage.fields ?? [])
|
|
322
|
+
v2.checkEntry(entry, `stage "${stage.name}".fields "${entry.name}"`);
|
|
323
323
|
for (const guard of stage.guards ?? [])
|
|
324
324
|
validateGuardReads(v2, stage.name, guard);
|
|
325
325
|
for (const t of stage.transitions ?? []) {
|
|
@@ -339,8 +339,8 @@ function validateGuardReads(v2, stageName, guard) {
|
|
|
339
339
|
}
|
|
340
340
|
function validateTask(v2, stageName, task) {
|
|
341
341
|
const where = `stage "${stageName}" task "${task.name}"`;
|
|
342
|
-
for (const entry of task.
|
|
343
|
-
v2.checkEntry(entry, `${where}.
|
|
342
|
+
for (const entry of task.fields ?? [])
|
|
343
|
+
v2.checkEntry(entry, `${where}.fields "${entry.name}"`);
|
|
344
344
|
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`);
|
|
345
345
|
for (const [name, groq] of Object.entries(task.requirements ?? {}))
|
|
346
346
|
v2.checkCondition(groq, `${where}.requirements "${name}"`);
|
|
@@ -512,7 +512,7 @@ function openStage(instance) {
|
|
|
512
512
|
return findOpenStageEntry(instance);
|
|
513
513
|
}
|
|
514
514
|
function assigneesOf(stage, taskName) {
|
|
515
|
-
return (stage?.tasks.find((t) => t.name === taskName)?.
|
|
515
|
+
return (stage?.tasks.find((t) => t.name === taskName)?.fields ?? []).filter((s) => s._type === "assignees").flatMap((s) => s.value);
|
|
516
516
|
}
|
|
517
517
|
function isResolved(status) {
|
|
518
518
|
return status === "done" || status === "skipped";
|
|
@@ -625,17 +625,17 @@ class ActionParamsInvalidError extends Error {
|
|
|
625
625
|
${lines}`), this.name = "ActionParamsInvalidError", this.action = args.action, this.task = args.task, this.issues = args.issues;
|
|
626
626
|
}
|
|
627
627
|
}
|
|
628
|
-
class
|
|
628
|
+
class RequiredFieldNotProvidedError extends Error {
|
|
629
629
|
definition;
|
|
630
630
|
missing;
|
|
631
631
|
constructor(args) {
|
|
632
632
|
const lines = args.missing.map((m) => ` - ${m.name} (${m.type})`).join(`
|
|
633
633
|
`), where = args.definition !== void 0 ? ` for workflow "${args.definition}"` : "";
|
|
634
634
|
super(
|
|
635
|
-
`Required init
|
|
635
|
+
`Required init fields${where} was not provided:
|
|
636
636
|
${lines}
|
|
637
|
-
Provide each via \`
|
|
638
|
-
), this.name = "
|
|
637
|
+
Provide each via \`initialFields\` when starting standalone, or via the parent's \`subworkflows.with\` when spawned.`
|
|
638
|
+
), this.name = "RequiredFieldNotProvidedError", args.definition !== void 0 && (this.definition = args.definition), this.missing = args.missing;
|
|
639
639
|
}
|
|
640
640
|
}
|
|
641
641
|
class WorkflowStateDivergedError extends Error {
|
|
@@ -678,6 +678,17 @@ function isRevisionConflict(error) {
|
|
|
678
678
|
const { statusCode, message } = error;
|
|
679
679
|
return statusCode === 409 ? !0 : typeof message == "string" && message.includes("ifRevisionId check failed");
|
|
680
680
|
}
|
|
681
|
+
class ConcurrentEditFieldError extends Error {
|
|
682
|
+
instanceId;
|
|
683
|
+
target;
|
|
684
|
+
attempts;
|
|
685
|
+
constructor(args) {
|
|
686
|
+
const where = args.target.task !== void 0 ? `${args.target.task}.${args.target.field}` : args.target.field;
|
|
687
|
+
super(
|
|
688
|
+
`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.`
|
|
689
|
+
), this.name = "ConcurrentEditFieldError", this.instanceId = args.instanceId, this.target = args.target, this.attempts = args.attempts;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
681
692
|
class CascadeLimitError extends Error {
|
|
682
693
|
instanceId;
|
|
683
694
|
limit;
|
|
@@ -723,10 +734,10 @@ function collectWatchRefs(instance) {
|
|
|
723
734
|
return [
|
|
724
735
|
gdrRef(instance.workflowResource, instance._id, instance._type),
|
|
725
736
|
...instance.ancestors,
|
|
726
|
-
...entryDocRefs(instance.
|
|
727
|
-
...entryDocRefs(stage?.
|
|
728
|
-
...entryReleaseRefs(instance.
|
|
729
|
-
...entryReleaseRefs(stage?.
|
|
737
|
+
...entryDocRefs(instance.fields),
|
|
738
|
+
...entryDocRefs(stage?.fields),
|
|
739
|
+
...entryReleaseRefs(instance.fields),
|
|
740
|
+
...entryReleaseRefs(stage?.fields)
|
|
730
741
|
];
|
|
731
742
|
}
|
|
732
743
|
function readsRaw(ref) {
|
|
@@ -750,21 +761,21 @@ function subscriptionDocumentsForInstance(instance) {
|
|
|
750
761
|
...instance.perspective !== void 0 ? { perspective: instance.perspective } : {}
|
|
751
762
|
};
|
|
752
763
|
}
|
|
753
|
-
function entryDocRefs(
|
|
754
|
-
return
|
|
764
|
+
function entryDocRefs(entries) {
|
|
765
|
+
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) : [] : []);
|
|
755
766
|
}
|
|
756
|
-
function entryReleaseRefs(
|
|
757
|
-
return
|
|
767
|
+
function entryReleaseRefs(entries) {
|
|
768
|
+
return fieldEntries(entries).flatMap(
|
|
758
769
|
(entry) => entry._type === "release.ref" && isGdr(entry.value) ? [entry.value] : []
|
|
759
770
|
);
|
|
760
771
|
}
|
|
761
|
-
function entrySubjectRefs(
|
|
762
|
-
return
|
|
772
|
+
function entrySubjectRefs(entries) {
|
|
773
|
+
return fieldEntries(entries).flatMap(
|
|
763
774
|
(entry) => (entry._type === "doc.ref" || entry._type === "release.ref") && isGdr(entry.value) ? [entry.value] : []
|
|
764
775
|
);
|
|
765
776
|
}
|
|
766
|
-
function
|
|
767
|
-
return Array.isArray(
|
|
777
|
+
function fieldEntries(entries) {
|
|
778
|
+
return Array.isArray(entries) ? entries.filter(
|
|
768
779
|
(entry) => !!entry && typeof entry == "object"
|
|
769
780
|
) : [];
|
|
770
781
|
}
|
|
@@ -810,8 +821,8 @@ async function readDoc(client, id, perspective) {
|
|
|
810
821
|
function resourceFromParsed(parsed) {
|
|
811
822
|
return parsed.scheme === "dataset" ? { type: "dataset", id: `${parsed.projectId}.${parsed.dataset}` } : { type: parsed.scheme, id: parsed.resourceId ?? "" };
|
|
812
823
|
}
|
|
813
|
-
function collectEntryDocUris(
|
|
814
|
-
return entryDocRefs(
|
|
824
|
+
function collectEntryDocUris(resolvedFieldEntries) {
|
|
825
|
+
return entryDocRefs(resolvedFieldEntries).map((ref) => ref.id);
|
|
815
826
|
}
|
|
816
827
|
const resourceKey = (r) => `${r.type}.${r.id}`;
|
|
817
828
|
function guardsForResource(client) {
|
|
@@ -829,8 +840,8 @@ function verdictGuardsForInstance(client, instanceId) {
|
|
|
829
840
|
}
|
|
830
841
|
async function guardsForInstance(args) {
|
|
831
842
|
const { client, clientForGdr, instance } = args, stateUris = [
|
|
832
|
-
...collectEntryDocUris(instance.
|
|
833
|
-
...instance.stages.flatMap((stage) => collectEntryDocUris(stage.
|
|
843
|
+
...collectEntryDocUris(instance.fields),
|
|
844
|
+
...instance.stages.flatMap((stage) => collectEntryDocUris(stage.fields))
|
|
834
845
|
], clients = resourceClientMap(
|
|
835
846
|
instance.workflowResource,
|
|
836
847
|
client,
|
|
@@ -862,16 +873,16 @@ function guardIdRefs(definition) {
|
|
|
862
873
|
);
|
|
863
874
|
}
|
|
864
875
|
function staticIdRefGdr(idRef, stage, definition) {
|
|
865
|
-
const read = /^\$
|
|
876
|
+
const read = /^\$fields\.([\w-]+)/.exec(idRef);
|
|
866
877
|
if (read === null) return;
|
|
867
878
|
const source = entriesInScope(stage, definition).find((e) => e.name === read[1])?.source;
|
|
868
879
|
return source?.type === "literal" ? gdrFromValue(source.value) : void 0;
|
|
869
880
|
}
|
|
870
881
|
function entriesInScope(stage, definition) {
|
|
871
882
|
return [
|
|
872
|
-
...definition.
|
|
873
|
-
...stage.
|
|
874
|
-
...(stage.tasks ?? []).flatMap((task) => task.
|
|
883
|
+
...definition.fields ?? [],
|
|
884
|
+
...stage.fields ?? [],
|
|
885
|
+
...(stage.tasks ?? []).flatMap((task) => task.fields ?? [])
|
|
875
886
|
];
|
|
876
887
|
}
|
|
877
888
|
function gdrFromValue(value) {
|
|
@@ -1021,15 +1032,15 @@ async function runGroq(groq, params, snapshot) {
|
|
|
1021
1032
|
const tree = groqJs.parse(groq, { params });
|
|
1022
1033
|
return (await groqJs.evaluate(tree, { dataset: snapshot.docs, params })).get();
|
|
1023
1034
|
}
|
|
1024
|
-
class
|
|
1035
|
+
class FieldValueShapeError extends Error {
|
|
1025
1036
|
entryType;
|
|
1026
1037
|
entryName;
|
|
1027
1038
|
issues;
|
|
1028
1039
|
constructor(args) {
|
|
1029
1040
|
const issueText = args.issues.join("; ");
|
|
1030
1041
|
super(
|
|
1031
|
-
`
|
|
1032
|
-
), this.name = "
|
|
1042
|
+
`Field entry ${args.mode} shape invalid for "${args.entryName}" (${args.entryType}): ${issueText}`
|
|
1043
|
+
), this.name = "FieldValueShapeError", this.entryType = args.entryType, this.entryName = args.entryName, this.issues = args.issues;
|
|
1033
1044
|
}
|
|
1034
1045
|
}
|
|
1035
1046
|
const GdrShape = v__namespace.looseObject({
|
|
@@ -1096,35 +1107,35 @@ const GdrShape = v__namespace.looseObject({
|
|
|
1096
1107
|
function isAppendable(entryType) {
|
|
1097
1108
|
return entryType in itemSchemas;
|
|
1098
1109
|
}
|
|
1099
|
-
function
|
|
1110
|
+
function validateFieldValue(args) {
|
|
1100
1111
|
const schema2 = valueSchemas[args.entryType];
|
|
1101
1112
|
if (schema2 === void 0)
|
|
1102
|
-
throw new
|
|
1113
|
+
throw new FieldValueShapeError({
|
|
1103
1114
|
entryType: args.entryType,
|
|
1104
1115
|
entryName: args.entryName,
|
|
1105
|
-
issues: [`unknown
|
|
1116
|
+
issues: [`unknown field entry type ${args.entryType}`],
|
|
1106
1117
|
mode: "value"
|
|
1107
1118
|
});
|
|
1108
1119
|
const result = v__namespace.safeParse(schema2, args.value);
|
|
1109
1120
|
if (!result.success)
|
|
1110
|
-
throw new
|
|
1121
|
+
throw new FieldValueShapeError({
|
|
1111
1122
|
entryType: args.entryType,
|
|
1112
1123
|
entryName: args.entryName,
|
|
1113
1124
|
issues: formatIssues(result.issues),
|
|
1114
1125
|
mode: "value"
|
|
1115
1126
|
});
|
|
1116
1127
|
}
|
|
1117
|
-
function
|
|
1128
|
+
function validateFieldAppendItem(args) {
|
|
1118
1129
|
if (!isAppendable(args.entryType))
|
|
1119
|
-
throw new
|
|
1130
|
+
throw new FieldValueShapeError({
|
|
1120
1131
|
entryType: args.entryType,
|
|
1121
1132
|
entryName: args.entryName,
|
|
1122
|
-
issues: [`
|
|
1133
|
+
issues: [`field entry type ${args.entryType} does not support append`],
|
|
1123
1134
|
mode: "item"
|
|
1124
1135
|
});
|
|
1125
1136
|
const schema2 = itemSchemas[args.entryType], result = v__namespace.safeParse(schema2, args.item);
|
|
1126
1137
|
if (!result.success)
|
|
1127
|
-
throw new
|
|
1138
|
+
throw new FieldValueShapeError({
|
|
1128
1139
|
entryType: args.entryType,
|
|
1129
1140
|
entryName: args.entryName,
|
|
1130
1141
|
issues: formatIssues(result.issues),
|
|
@@ -1137,7 +1148,7 @@ function formatIssues(issues) {
|
|
|
1137
1148
|
return `${keys.length > 0 ? `at ${keys.join(".")}: ` : ""}${i.message}`;
|
|
1138
1149
|
});
|
|
1139
1150
|
}
|
|
1140
|
-
function
|
|
1151
|
+
function derivePerspectiveFromFields(entries) {
|
|
1141
1152
|
if (entries !== void 0) {
|
|
1142
1153
|
for (const entry of entries)
|
|
1143
1154
|
if (entry._type === "release.ref" && entry.value !== null) {
|
|
@@ -1147,24 +1158,24 @@ function derivePerspectiveFromState(entries) {
|
|
|
1147
1158
|
}
|
|
1148
1159
|
}
|
|
1149
1160
|
}
|
|
1150
|
-
async function
|
|
1151
|
-
const { entryDefs,
|
|
1161
|
+
async function resolveDeclaredFields(args) {
|
|
1162
|
+
const { entryDefs, initialFields, ctx, randomKey: randomKey2 } = args;
|
|
1152
1163
|
if (entryDefs === void 0 || entryDefs.length === 0) return [];
|
|
1153
|
-
assertRequiredInitProvided(entryDefs,
|
|
1164
|
+
assertRequiredInitProvided(entryDefs, initialFields, ctx.definitionName);
|
|
1154
1165
|
const out = [];
|
|
1155
1166
|
for (const entry of entryDefs) {
|
|
1156
|
-
const scopedCtx = { ...ctx,
|
|
1157
|
-
out.push(await resolveOneEntry(entry,
|
|
1167
|
+
const scopedCtx = { ...ctx, resolvedFields: out };
|
|
1168
|
+
out.push(await resolveOneEntry(entry, initialFields, scopedCtx, randomKey2));
|
|
1158
1169
|
}
|
|
1159
1170
|
return out;
|
|
1160
1171
|
}
|
|
1161
|
-
function assertRequiredInitProvided(entryDefs,
|
|
1172
|
+
function assertRequiredInitProvided(entryDefs, initialFields, definitionName) {
|
|
1162
1173
|
const missing = entryDefs.filter((entry) => entry.required === !0 && entry.source.type === "init").filter((entry) => {
|
|
1163
|
-
const match =
|
|
1174
|
+
const match = initialFields.find((s) => s.name === entry.name && s.type === entry.type);
|
|
1164
1175
|
return match === void 0 || match.value === void 0 || match.value === null;
|
|
1165
1176
|
}).map((entry) => ({ name: entry.name, type: entry.type }));
|
|
1166
1177
|
if (missing.length !== 0)
|
|
1167
|
-
throw new
|
|
1178
|
+
throw new RequiredFieldNotProvidedError({
|
|
1168
1179
|
...definitionName !== void 0 ? { definition: definitionName } : {},
|
|
1169
1180
|
missing
|
|
1170
1181
|
});
|
|
@@ -1178,18 +1189,18 @@ const ARRAY_SLOT_TYPES = /* @__PURE__ */ new Set([
|
|
|
1178
1189
|
function defaultEntryValue(entryType) {
|
|
1179
1190
|
return ARRAY_SLOT_TYPES.has(entryType) ? [] : null;
|
|
1180
1191
|
}
|
|
1181
|
-
function resolveInitValue(entry,
|
|
1182
|
-
const initMatch =
|
|
1192
|
+
function resolveInitValue(entry, initialFields, defaultValue) {
|
|
1193
|
+
const initMatch = initialFields.find((s) => s.name === entry.name && s.type === entry.type);
|
|
1183
1194
|
return initMatch === void 0 ? defaultValue : (assertInitValueShape(entry, initMatch.value), initMatch.value);
|
|
1184
1195
|
}
|
|
1185
|
-
function
|
|
1186
|
-
const target = (source.scope === "workflow" ? ctx.
|
|
1196
|
+
function resolveFieldReadValue(source, ctx, defaultValue) {
|
|
1197
|
+
const target = (source.scope === "workflow" ? ctx.workflowFields ?? [] : ctx.resolvedFields ?? []).find((s) => s.name === source.field), targetValue = target === void 0 ? void 0 : target.value;
|
|
1187
1198
|
return (source.path !== void 0 ? getPath(targetValue, source.path) : targetValue) ?? defaultValue;
|
|
1188
1199
|
}
|
|
1189
1200
|
async function resolveQueryValue(entry, source, ctx, client, defaultValue) {
|
|
1190
1201
|
const params = paramsForLake({
|
|
1191
1202
|
self: ctx.selfId ?? null,
|
|
1192
|
-
|
|
1203
|
+
fields: fieldMapFromResolved(ctx.resolvedFields ?? []),
|
|
1193
1204
|
stage: ctx.stageName ?? null,
|
|
1194
1205
|
task: ctx.taskName ?? null,
|
|
1195
1206
|
now: ctx.now,
|
|
@@ -1205,9 +1216,9 @@ async function resolveQueryValue(entry, source, ctx, client, defaultValue) {
|
|
|
1205
1216
|
);
|
|
1206
1217
|
}
|
|
1207
1218
|
}
|
|
1208
|
-
async function resolveEntryValue(entry,
|
|
1219
|
+
async function resolveEntryValue(entry, initialFields, ctx, defaultValue) {
|
|
1209
1220
|
const source = entry.source;
|
|
1210
|
-
return source.type === "init" ? resolveInitValue(entry,
|
|
1221
|
+
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;
|
|
1211
1222
|
}
|
|
1212
1223
|
function buildResolvedEntry(entry, value, _key, now) {
|
|
1213
1224
|
const titleProp = entry.title !== void 0 ? { title: entry.title } : {}, descriptionProp = entry.description !== void 0 ? { description: entry.description } : {};
|
|
@@ -1228,36 +1239,36 @@ function buildResolvedEntry(entry, value, _key, now) {
|
|
|
1228
1239
|
value
|
|
1229
1240
|
};
|
|
1230
1241
|
}
|
|
1231
|
-
async function resolveOneEntry(entry,
|
|
1232
|
-
const defaultValue = defaultEntryValue(entry.type), value = await resolveEntryValue(entry,
|
|
1233
|
-
return
|
|
1242
|
+
async function resolveOneEntry(entry, initialFields, ctx, randomKey2) {
|
|
1243
|
+
const defaultValue = defaultEntryValue(entry.type), value = await resolveEntryValue(entry, initialFields, ctx, defaultValue);
|
|
1244
|
+
return validateFieldValue({ entryType: entry.type, entryName: entry.name, value }), buildResolvedEntry(entry, value, randomKey2(), ctx.now);
|
|
1234
1245
|
}
|
|
1235
|
-
function
|
|
1246
|
+
function fieldMapFromResolved(entries) {
|
|
1236
1247
|
const out = {};
|
|
1237
1248
|
for (const s of entries) out[s.name] = s.value;
|
|
1238
1249
|
return out;
|
|
1239
1250
|
}
|
|
1240
1251
|
function assertInitValueShape(entry, value) {
|
|
1241
1252
|
if (entry.type === "doc.ref") {
|
|
1242
|
-
assertGdrShape(value, `
|
|
1253
|
+
assertGdrShape(value, `field entry "${entry.name}" (doc.ref)`);
|
|
1243
1254
|
return;
|
|
1244
1255
|
}
|
|
1245
1256
|
if (entry.type === "release.ref") {
|
|
1246
|
-
assertGdrShape(value, `
|
|
1257
|
+
assertGdrShape(value, `field entry "${entry.name}" (release.ref)`);
|
|
1247
1258
|
const v2 = value;
|
|
1248
1259
|
if (typeof v2.releaseName != "string" || v2.releaseName.length === 0)
|
|
1249
1260
|
throw new Error(
|
|
1250
|
-
`Invalid init value for
|
|
1261
|
+
`Invalid init value for field entry "${entry.name}" (release.ref): \`releaseName\` must be a non-empty string. Got ${JSON.stringify(v2.releaseName)}.`
|
|
1251
1262
|
);
|
|
1252
1263
|
return;
|
|
1253
1264
|
}
|
|
1254
1265
|
if (entry.type === "doc.refs") {
|
|
1255
1266
|
if (!Array.isArray(value))
|
|
1256
1267
|
throw new Error(
|
|
1257
|
-
`Invalid init value for
|
|
1268
|
+
`Invalid init value for field entry "${entry.name}" (doc.refs): expected an array of GDRs, got ${typeof value}.`
|
|
1258
1269
|
);
|
|
1259
1270
|
for (const [i, item] of value.entries())
|
|
1260
|
-
assertGdrShape(item, `
|
|
1271
|
+
assertGdrShape(item, `field entry "${entry.name}" (doc.refs) item [${i}]`);
|
|
1261
1272
|
}
|
|
1262
1273
|
}
|
|
1263
1274
|
function assertGdrShape(value, context) {
|
|
@@ -1297,6 +1308,12 @@ function coerceToGdr(raw, workflowResource) {
|
|
|
1297
1308
|
function gdrToBareId(uri) {
|
|
1298
1309
|
return uri.includes(":") ? extractDocumentId(uri) : uri;
|
|
1299
1310
|
}
|
|
1311
|
+
function loadCallContext(client, instanceId, options) {
|
|
1312
|
+
return loadContext(client, instanceId, {
|
|
1313
|
+
...options?.clock ? { clock: options.clock } : {},
|
|
1314
|
+
...options?.clientForGdr ? { clientForGdr: options.clientForGdr } : {}
|
|
1315
|
+
});
|
|
1316
|
+
}
|
|
1300
1317
|
async function loadContext(client, instanceId, options) {
|
|
1301
1318
|
const instance = await client.getDocument(instanceId);
|
|
1302
1319
|
if (!instance)
|
|
@@ -1310,14 +1327,14 @@ async function loadContext(client, instanceId, options) {
|
|
|
1310
1327
|
return { client, clientForGdr, clock, now: clock(), instance, definition, snapshot };
|
|
1311
1328
|
}
|
|
1312
1329
|
async function ctxConditionParams(ctx, opts) {
|
|
1313
|
-
const base = buildParams({ instance: ctx.instance, now: ctx.now, snapshot: ctx.snapshot }),
|
|
1314
|
-
...base.
|
|
1315
|
-
...
|
|
1330
|
+
const base = buildParams({ instance: ctx.instance, now: ctx.now, snapshot: ctx.snapshot }), fields = {
|
|
1331
|
+
...base.fields,
|
|
1332
|
+
...scopedFieldOverlay(ctx.instance, ctx.snapshot, opts?.taskName)
|
|
1316
1333
|
}, params = {
|
|
1317
1334
|
...base,
|
|
1318
|
-
|
|
1335
|
+
fields,
|
|
1319
1336
|
actor: opts?.actor,
|
|
1320
|
-
assigned: opts?.taskName !== void 0 ? assignedFor(ctx.instance, opts.taskName, opts?.actor) : !1,
|
|
1337
|
+
assigned: opts?.taskName !== void 0 ? assignedFor(ctx.instance, opts.taskName, opts?.actor, ctx.definition.roleAliases) : !1,
|
|
1321
1338
|
...opts?.vars
|
|
1322
1339
|
};
|
|
1323
1340
|
return { ...await evaluatePredicates({
|
|
@@ -1354,11 +1371,11 @@ function findStage(definition, stageName) {
|
|
|
1354
1371
|
throw new Error(`Stage "${stageName}" not found in definition ${definition.name}`);
|
|
1355
1372
|
return stage;
|
|
1356
1373
|
}
|
|
1357
|
-
async function
|
|
1358
|
-
const { client, instance, stage, now,
|
|
1359
|
-
return
|
|
1360
|
-
entryDefs: stage.
|
|
1361
|
-
|
|
1374
|
+
async function resolveStageFieldEntries(args) {
|
|
1375
|
+
const { client, instance, stage, now, initialFields } = args;
|
|
1376
|
+
return resolveDeclaredFields({
|
|
1377
|
+
entryDefs: stage.fields,
|
|
1378
|
+
initialFields: initialFields ?? [],
|
|
1362
1379
|
ctx: {
|
|
1363
1380
|
client,
|
|
1364
1381
|
now,
|
|
@@ -1366,19 +1383,19 @@ async function resolveStageStateEntries(args) {
|
|
|
1366
1383
|
tag: instance.tag,
|
|
1367
1384
|
stageName: stage.name,
|
|
1368
1385
|
workflowResource: instance.workflowResource,
|
|
1369
|
-
// Allow stage-scope entries' `source: { type: "
|
|
1370
|
-
// "workflow", ... }` to see the already-resolved workflow-scope
|
|
1371
|
-
|
|
1386
|
+
// Allow stage-scope entries' `source: { type: "fieldRead", scope:
|
|
1387
|
+
// "workflow", ... }` to see the already-resolved workflow-scope fields.
|
|
1388
|
+
workflowFields: instance.fields ?? [],
|
|
1372
1389
|
...instance.perspective !== void 0 ? { perspective: instance.perspective } : {}
|
|
1373
1390
|
},
|
|
1374
1391
|
randomKey
|
|
1375
1392
|
});
|
|
1376
1393
|
}
|
|
1377
|
-
async function
|
|
1394
|
+
async function resolveTaskFieldEntries(args) {
|
|
1378
1395
|
const { client, instance, stage, task, now } = args;
|
|
1379
|
-
return
|
|
1380
|
-
entryDefs: task.
|
|
1381
|
-
|
|
1396
|
+
return resolveDeclaredFields({
|
|
1397
|
+
entryDefs: task.fields,
|
|
1398
|
+
initialFields: [],
|
|
1382
1399
|
ctx: {
|
|
1383
1400
|
client,
|
|
1384
1401
|
now,
|
|
@@ -1387,7 +1404,7 @@ async function resolveTaskStateEntries(args) {
|
|
|
1387
1404
|
stageName: stage.name,
|
|
1388
1405
|
workflowResource: instance.workflowResource,
|
|
1389
1406
|
taskName: task.name,
|
|
1390
|
-
|
|
1407
|
+
workflowFields: instance.fields ?? [],
|
|
1391
1408
|
...instance.perspective !== void 0 ? { perspective: instance.perspective } : {}
|
|
1392
1409
|
},
|
|
1393
1410
|
randomKey
|
|
@@ -1469,15 +1486,15 @@ function stageTransitionHistory(args) {
|
|
|
1469
1486
|
function startMutation(instance) {
|
|
1470
1487
|
return {
|
|
1471
1488
|
currentStage: instance.currentStage,
|
|
1472
|
-
// Deep-ish copy. Per-scope
|
|
1489
|
+
// Deep-ish copy. Per-scope field entry arrays need their own copies
|
|
1473
1490
|
// so ops can mutate without leaking into the source.
|
|
1474
|
-
|
|
1491
|
+
fields: (instance.fields ?? []).map((s) => ({ ...s })),
|
|
1475
1492
|
stages: instance.stages.map((s) => ({
|
|
1476
1493
|
...s,
|
|
1477
|
-
|
|
1494
|
+
fields: (s.fields ?? []).map((entry) => ({ ...entry })),
|
|
1478
1495
|
tasks: s.tasks.map((t) => ({
|
|
1479
1496
|
...t,
|
|
1480
|
-
...t.
|
|
1497
|
+
...t.fields !== void 0 ? { fields: t.fields.map((entry) => ({ ...entry })) } : {}
|
|
1481
1498
|
}))
|
|
1482
1499
|
})),
|
|
1483
1500
|
pendingEffects: [...instance.pendingEffects],
|
|
@@ -1493,7 +1510,7 @@ function startMutation(instance) {
|
|
|
1493
1510
|
function instanceStateFields(src) {
|
|
1494
1511
|
const fields = {
|
|
1495
1512
|
currentStage: src.currentStage,
|
|
1496
|
-
|
|
1513
|
+
fields: src.fields,
|
|
1497
1514
|
stages: src.stages,
|
|
1498
1515
|
pendingEffects: src.pendingEffects,
|
|
1499
1516
|
effectHistory: src.effectHistory,
|
|
@@ -1506,7 +1523,7 @@ function materializeInstance(base, mutation) {
|
|
|
1506
1523
|
return {
|
|
1507
1524
|
...base,
|
|
1508
1525
|
currentStage: mutation.currentStage,
|
|
1509
|
-
|
|
1526
|
+
fields: mutation.fields,
|
|
1510
1527
|
stages: mutation.stages,
|
|
1511
1528
|
effectsContext: mutation.effectsContext
|
|
1512
1529
|
};
|
|
@@ -1628,7 +1645,7 @@ function buildInstanceBase(args) {
|
|
|
1628
1645
|
definition: args.definitionName,
|
|
1629
1646
|
pinnedVersion: args.pinnedVersion,
|
|
1630
1647
|
definitionSnapshot: JSON.stringify(args.definition),
|
|
1631
|
-
|
|
1648
|
+
fields: args.fields,
|
|
1632
1649
|
effectsContext: args.effectsContext,
|
|
1633
1650
|
ancestors: args.ancestors,
|
|
1634
1651
|
...perspective !== void 0 ? { perspective } : {},
|
|
@@ -1686,7 +1703,7 @@ async function spawnSubworkflows(ctx, mutation, task, sub, actor) {
|
|
|
1686
1703
|
...actor ? { actor } : {}
|
|
1687
1704
|
}), { rows, discoveryResource } = await resolveSpawnRows(ctx, sub), effectsContext = await resolveContextHandoff(ctx, sub, parentScope), refs = [];
|
|
1688
1705
|
for (const row of rows) {
|
|
1689
|
-
const
|
|
1706
|
+
const initialFields = await projectRowFields(
|
|
1690
1707
|
ctx,
|
|
1691
1708
|
sub,
|
|
1692
1709
|
definition,
|
|
@@ -1697,7 +1714,7 @@ async function spawnSubworkflows(ctx, mutation, task, sub, actor) {
|
|
|
1697
1714
|
client: ctx.client,
|
|
1698
1715
|
parent: ctx.instance,
|
|
1699
1716
|
definition,
|
|
1700
|
-
|
|
1717
|
+
initialFields,
|
|
1701
1718
|
effectsContext,
|
|
1702
1719
|
actor,
|
|
1703
1720
|
now
|
|
@@ -1710,7 +1727,7 @@ async function resolveSpawnRows(ctx, sub) {
|
|
|
1710
1727
|
const params = buildParams({ instance: ctx.instance, now: ctx.now, snapshot: ctx.snapshot }), groq = sub.forEach;
|
|
1711
1728
|
let value, discoveryResource;
|
|
1712
1729
|
if (groq.includes("*")) {
|
|
1713
|
-
const routingUri = entrySubjectRefs(ctx.instance.
|
|
1730
|
+
const routingUri = entrySubjectRefs(ctx.instance.fields)[0]?.id, client = clientForDiscovery(ctx.client, ctx.clientForGdr, routingUri);
|
|
1714
1731
|
client !== ctx.client && routingUri !== void 0 && (discoveryResource = resourceFromGdrUri(routingUri)), value = await discoverItems({
|
|
1715
1732
|
client,
|
|
1716
1733
|
groq,
|
|
@@ -1726,13 +1743,13 @@ async function resolveSpawnRows(ctx, sub) {
|
|
|
1726
1743
|
}
|
|
1727
1744
|
return Array.isArray(value) ? { rows: value, discoveryResource } : value == null ? { rows: [], discoveryResource } : { rows: [value], discoveryResource };
|
|
1728
1745
|
}
|
|
1729
|
-
async function
|
|
1746
|
+
async function projectRowFields(ctx, sub, childDefinition, parentScope, row, discoveryResource) {
|
|
1730
1747
|
const out = [];
|
|
1731
1748
|
for (const [name, groq] of Object.entries(sub.with ?? {})) {
|
|
1732
|
-
const declared = (childDefinition.
|
|
1749
|
+
const declared = (childDefinition.fields ?? []).find((e) => e.name === name);
|
|
1733
1750
|
if (declared === void 0)
|
|
1734
1751
|
throw new Error(
|
|
1735
|
-
`subworkflows.with["${name}"]: subworkflow "${childDefinition.name}" declares no
|
|
1752
|
+
`subworkflows.with["${name}"]: subworkflow "${childDefinition.name}" declares no field entry "${name}"`
|
|
1736
1753
|
);
|
|
1737
1754
|
const raw = await runGroq(groq, { ...parentScope, row }, ctx.snapshot), value = canonicaliseSpawnValue(declared.type, raw, {
|
|
1738
1755
|
parentResource: ctx.instance.workflowResource,
|
|
@@ -1787,15 +1804,15 @@ async function resolveDefinitionRef(client, ref, tag) {
|
|
|
1787
1804
|
) ?? null;
|
|
1788
1805
|
}
|
|
1789
1806
|
async function prepareChildInstance(args) {
|
|
1790
|
-
const { client, parent, definition,
|
|
1807
|
+
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 = [
|
|
1791
1808
|
...parent.ancestors,
|
|
1792
1809
|
{
|
|
1793
1810
|
id: selfGdr(parent),
|
|
1794
1811
|
type: WORKFLOW_INSTANCE_TYPE
|
|
1795
1812
|
}
|
|
1796
|
-
], inheritedPerspective = parent.perspective,
|
|
1797
|
-
entryDefs: definition.
|
|
1798
|
-
|
|
1813
|
+
], inheritedPerspective = parent.perspective, childFields = await resolveDeclaredFields({
|
|
1814
|
+
entryDefs: definition.fields,
|
|
1815
|
+
initialFields,
|
|
1799
1816
|
ctx: {
|
|
1800
1817
|
client,
|
|
1801
1818
|
now,
|
|
@@ -1806,7 +1823,7 @@ async function prepareChildInstance(args) {
|
|
|
1806
1823
|
...inheritedPerspective !== void 0 ? { perspective: inheritedPerspective } : {}
|
|
1807
1824
|
},
|
|
1808
1825
|
randomKey
|
|
1809
|
-
}), childPerspective =
|
|
1826
|
+
}), childPerspective = derivePerspectiveFromFields(childFields) ?? inheritedPerspective, effectsContextEntries = Object.entries(effectsContext).map(
|
|
1810
1827
|
([key, value]) => {
|
|
1811
1828
|
if (typeof value == "object" && value !== null && "id" in value && "type" in value) {
|
|
1812
1829
|
if (!isGdrUri(value.id))
|
|
@@ -1825,7 +1842,7 @@ async function prepareChildInstance(args) {
|
|
|
1825
1842
|
definitionName: definition.name,
|
|
1826
1843
|
pinnedVersion: definition.version,
|
|
1827
1844
|
definition,
|
|
1828
|
-
|
|
1845
|
+
fields: childFields,
|
|
1829
1846
|
effectsContext: effectsContextEntries,
|
|
1830
1847
|
ancestors,
|
|
1831
1848
|
perspective: childPerspective,
|
|
@@ -1990,15 +2007,15 @@ function resolveStaticSource(src, ctx) {
|
|
|
1990
2007
|
return { handled: !1 };
|
|
1991
2008
|
}
|
|
1992
2009
|
}
|
|
1993
|
-
const
|
|
1994
|
-
"
|
|
1995
|
-
"
|
|
1996
|
-
"
|
|
1997
|
-
"
|
|
1998
|
-
"
|
|
2010
|
+
const FIELD_OP_TYPES = [
|
|
2011
|
+
"field.set",
|
|
2012
|
+
"field.unset",
|
|
2013
|
+
"field.append",
|
|
2014
|
+
"field.updateWhere",
|
|
2015
|
+
"field.removeWhere"
|
|
1999
2016
|
];
|
|
2000
|
-
function
|
|
2001
|
-
return
|
|
2017
|
+
function isFieldOp(summary) {
|
|
2018
|
+
return FIELD_OP_TYPES.includes(summary.opType);
|
|
2002
2019
|
}
|
|
2003
2020
|
function validateActionParams(action, taskName, callerParams) {
|
|
2004
2021
|
const declared = action.params ?? [], params = callerParams ?? {}, issues = [];
|
|
@@ -2048,41 +2065,46 @@ function runOps(args) {
|
|
|
2048
2065
|
const summaries = [];
|
|
2049
2066
|
for (const op of ops) {
|
|
2050
2067
|
const summary = applyOp(op, { mutation, stage, taskName: origin.task, params, actor, self, now });
|
|
2051
|
-
summaries.push(summary), mutation.history.push({
|
|
2052
|
-
_key: randomKey(),
|
|
2053
|
-
_type: "opApplied",
|
|
2054
|
-
at: now,
|
|
2055
|
-
stage,
|
|
2056
|
-
...origin.task !== void 0 ? { task: origin.task } : {},
|
|
2057
|
-
...origin.action !== void 0 ? { action: origin.action } : {},
|
|
2058
|
-
...origin.transition !== void 0 ? { transition: origin.transition } : {},
|
|
2059
|
-
opType: summary.opType,
|
|
2060
|
-
...summary.target !== void 0 ? { target: summary.target } : {},
|
|
2061
|
-
...summary.resolved !== void 0 ? { resolved: summary.resolved } : {},
|
|
2062
|
-
...actor !== void 0 ? { actor } : {}
|
|
2063
|
-
});
|
|
2068
|
+
summaries.push(summary), mutation.history.push(opAppliedEntry({ origin, summary, stage, actor, now }));
|
|
2064
2069
|
}
|
|
2065
2070
|
return summaries;
|
|
2066
2071
|
}
|
|
2072
|
+
function opAppliedEntry(args) {
|
|
2073
|
+
const { origin, summary, stage, actor, now } = args;
|
|
2074
|
+
return {
|
|
2075
|
+
_key: randomKey(),
|
|
2076
|
+
_type: "opApplied",
|
|
2077
|
+
at: now,
|
|
2078
|
+
stage,
|
|
2079
|
+
...origin.task !== void 0 ? { task: origin.task } : {},
|
|
2080
|
+
...origin.action !== void 0 ? { action: origin.action } : {},
|
|
2081
|
+
...origin.transition !== void 0 ? { transition: origin.transition } : {},
|
|
2082
|
+
...origin.edit === !0 ? { edit: !0 } : {},
|
|
2083
|
+
opType: summary.opType,
|
|
2084
|
+
...summary.target !== void 0 ? { target: summary.target } : {},
|
|
2085
|
+
...summary.resolved !== void 0 ? { resolved: summary.resolved } : {},
|
|
2086
|
+
...actor !== void 0 ? { actor } : {}
|
|
2087
|
+
};
|
|
2088
|
+
}
|
|
2067
2089
|
function applyOp(op, ctx) {
|
|
2068
2090
|
switch (op.type) {
|
|
2069
|
-
case "
|
|
2070
|
-
return
|
|
2071
|
-
case "
|
|
2072
|
-
return
|
|
2073
|
-
case "
|
|
2074
|
-
return
|
|
2075
|
-
case "
|
|
2076
|
-
return
|
|
2077
|
-
case "
|
|
2078
|
-
return
|
|
2091
|
+
case "field.set":
|
|
2092
|
+
return applyFieldSet(op, ctx);
|
|
2093
|
+
case "field.unset":
|
|
2094
|
+
return applyFieldUnset(op, ctx);
|
|
2095
|
+
case "field.append":
|
|
2096
|
+
return applyFieldAppend(op, ctx);
|
|
2097
|
+
case "field.updateWhere":
|
|
2098
|
+
return applyFieldUpdateWhere(op, ctx);
|
|
2099
|
+
case "field.removeWhere":
|
|
2100
|
+
return applyFieldRemoveWhere(op, ctx);
|
|
2079
2101
|
case "status.set":
|
|
2080
2102
|
return applyStatusSet(op, ctx);
|
|
2081
2103
|
}
|
|
2082
2104
|
}
|
|
2083
|
-
function
|
|
2105
|
+
function applyFieldSet(op, ctx) {
|
|
2084
2106
|
const value = resolveOpSource(op.value, ctx), entry = locateEntry(ctx, op.target);
|
|
2085
|
-
return
|
|
2107
|
+
return validateFieldValue({ entryType: entry._type, entryName: entry.name, value }), setEntryValue(entry, value), { opType: op.type, target: op.target, resolved: { value } };
|
|
2086
2108
|
}
|
|
2087
2109
|
const EMPTY_BY_KIND = {
|
|
2088
2110
|
"doc.refs": [],
|
|
@@ -2090,24 +2112,24 @@ const EMPTY_BY_KIND = {
|
|
|
2090
2112
|
notes: [],
|
|
2091
2113
|
assignees: []
|
|
2092
2114
|
};
|
|
2093
|
-
function
|
|
2115
|
+
function applyFieldUnset(op, ctx) {
|
|
2094
2116
|
const entry = locateEntry(ctx, op.target);
|
|
2095
2117
|
return setEntryValue(entry, EMPTY_BY_KIND[entry._type] ?? null), { opType: op.type, target: op.target };
|
|
2096
2118
|
}
|
|
2097
|
-
function
|
|
2119
|
+
function applyFieldAppend(op, ctx) {
|
|
2098
2120
|
const item = resolveOpSource(op.value, ctx), entry = locateEntry(ctx, op.target);
|
|
2099
|
-
|
|
2121
|
+
validateFieldAppendItem({ entryType: entry._type, entryName: entry.name, item });
|
|
2100
2122
|
const current = Array.isArray(entry.value) ? entry.value : [];
|
|
2101
2123
|
return setEntryValue(entry, [...current, withRowKey(item)]), { opType: op.type, target: op.target, resolved: { item } };
|
|
2102
2124
|
}
|
|
2103
2125
|
function withRowKey(item) {
|
|
2104
2126
|
return typeof item == "object" && item !== null && !Array.isArray(item) && !("_key" in item) ? { _key: randomKey(), ...item } : item;
|
|
2105
2127
|
}
|
|
2106
|
-
function
|
|
2128
|
+
function applyFieldUpdateWhere(op, ctx) {
|
|
2107
2129
|
const entry = locateEntry(ctx, op.target), rows = requireArrayValue(entry, op), merge = resolveOpSource(op.value, ctx);
|
|
2108
2130
|
if (merge === null || typeof merge != "object" || Array.isArray(merge))
|
|
2109
2131
|
throw new Error(
|
|
2110
|
-
`
|
|
2132
|
+
`field.updateWhere value must resolve to an object of fields to merge (target "${op.target.field}")`
|
|
2111
2133
|
);
|
|
2112
2134
|
return setEntryValue(
|
|
2113
2135
|
entry,
|
|
@@ -2116,7 +2138,7 @@ function applyStateUpdateWhere(op, ctx) {
|
|
|
2116
2138
|
)
|
|
2117
2139
|
), { opType: op.type, target: op.target, resolved: { merge } };
|
|
2118
2140
|
}
|
|
2119
|
-
function
|
|
2141
|
+
function applyFieldRemoveWhere(op, ctx) {
|
|
2120
2142
|
const entry = locateEntry(ctx, op.target), rows = requireArrayValue(entry, op);
|
|
2121
2143
|
return setEntryValue(
|
|
2122
2144
|
entry,
|
|
@@ -2126,7 +2148,7 @@ function applyStateRemoveWhere(op, ctx) {
|
|
|
2126
2148
|
function requireArrayValue(entry, op) {
|
|
2127
2149
|
if (!Array.isArray(entry.value))
|
|
2128
2150
|
throw new Error(
|
|
2129
|
-
`${op.type} target ${op.target.scope}:"${op.target.
|
|
2151
|
+
`${op.type} target ${op.target.scope}:"${op.target.field}" holds a non-array ${entry._type} value \u2014 where-ops operate on array entries`
|
|
2130
2152
|
);
|
|
2131
2153
|
return entry.value;
|
|
2132
2154
|
}
|
|
@@ -2147,19 +2169,19 @@ function setEntryValue(entry, value) {
|
|
|
2147
2169
|
entry.value = value;
|
|
2148
2170
|
}
|
|
2149
2171
|
function locateEntry(ctx, target) {
|
|
2150
|
-
const entry =
|
|
2172
|
+
const entry = fieldHost(ctx, target.scope)?.find((s) => s.name === target.field);
|
|
2151
2173
|
if (entry === void 0)
|
|
2152
2174
|
throw new Error(
|
|
2153
|
-
`Op target ${target.scope}:"${target.
|
|
2175
|
+
`Op target ${target.scope}:"${target.field}" has no resolved field entry on this instance \u2014 the deployed definition and the instance state have diverged`
|
|
2154
2176
|
);
|
|
2155
2177
|
return entry;
|
|
2156
2178
|
}
|
|
2157
|
-
function
|
|
2158
|
-
if (scope === "workflow") return ctx.mutation.
|
|
2179
|
+
function fieldHost(ctx, scope) {
|
|
2180
|
+
if (scope === "workflow") return ctx.mutation.fields;
|
|
2159
2181
|
const stageEntry = findOpenStageEntry(ctx.mutation);
|
|
2160
|
-
if (scope === "stage") return stageEntry?.
|
|
2182
|
+
if (scope === "stage") return stageEntry?.fields;
|
|
2161
2183
|
const task = stageEntry?.tasks.find((t) => t.name === ctx.taskName);
|
|
2162
|
-
return task !== void 0 && task.
|
|
2184
|
+
return task !== void 0 && task.fields === void 0 && (task.fields = []), task?.fields;
|
|
2163
2185
|
}
|
|
2164
2186
|
function resolveOpSource(src, ctx) {
|
|
2165
2187
|
const staticValue = resolveStaticSource(src, ctx);
|
|
@@ -2169,7 +2191,7 @@ function resolveOpSource(src, ctx) {
|
|
|
2169
2191
|
return ctx.self;
|
|
2170
2192
|
case "stage":
|
|
2171
2193
|
return ctx.stage;
|
|
2172
|
-
case "
|
|
2194
|
+
case "fieldRead": {
|
|
2173
2195
|
const value = readEntryFromMutation(ctx, src);
|
|
2174
2196
|
return src.path !== void 0 ? getPath(value, src.path) : value;
|
|
2175
2197
|
}
|
|
@@ -2180,7 +2202,7 @@ function resolveOpSource(src, ctx) {
|
|
|
2180
2202
|
return out;
|
|
2181
2203
|
}
|
|
2182
2204
|
default:
|
|
2183
|
-
throw new Error(`Source type "${src.type}" is a
|
|
2205
|
+
throw new Error(`Source type "${src.type}" is a field-entry origin, not a value`);
|
|
2184
2206
|
}
|
|
2185
2207
|
}
|
|
2186
2208
|
function entryValue(entries, name) {
|
|
@@ -2189,7 +2211,7 @@ function entryValue(entries, name) {
|
|
|
2189
2211
|
function readEntryFromMutation(ctx, src) {
|
|
2190
2212
|
const scopes = src.scope !== void 0 ? [src.scope] : ["stage", "workflow"], stageEntry = findOpenStageEntry(ctx.mutation);
|
|
2191
2213
|
for (const scope of scopes) {
|
|
2192
|
-
const host = scope === "workflow" ? ctx.mutation.
|
|
2214
|
+
const host = scope === "workflow" ? ctx.mutation.fields : stageEntry?.fields, value = entryValue(host, src.field);
|
|
2193
2215
|
if (value !== void 0) return value;
|
|
2194
2216
|
}
|
|
2195
2217
|
}
|
|
@@ -2221,9 +2243,9 @@ async function commitInvoke(ctx, taskName, actor) {
|
|
|
2221
2243
|
}
|
|
2222
2244
|
async function activateTask(ctx, mutation, task, entry, actor) {
|
|
2223
2245
|
const now = ctx.now;
|
|
2224
|
-
if (entry.status = "active", entry.startedAt = now, task.
|
|
2246
|
+
if (entry.status = "active", entry.startedAt = now, task.fields !== void 0 && task.fields.length > 0) {
|
|
2225
2247
|
const stage = findStage(ctx.definition, mutation.currentStage);
|
|
2226
|
-
entry.
|
|
2248
|
+
entry.fields = await resolveTaskFieldEntries({
|
|
2227
2249
|
client: ctx.client,
|
|
2228
2250
|
instance: ctx.instance,
|
|
2229
2251
|
stage,
|
|
@@ -2328,7 +2350,7 @@ async function enterStage(ctx, mutation, nextStage, actor, at) {
|
|
|
2328
2350
|
_key: randomKey(),
|
|
2329
2351
|
name: nextStage.name,
|
|
2330
2352
|
enteredAt: at,
|
|
2331
|
-
|
|
2353
|
+
fields: await resolveStageFieldEntries({
|
|
2332
2354
|
client: ctx.client,
|
|
2333
2355
|
instance: ctx.instance,
|
|
2334
2356
|
stage: nextStage,
|
|
@@ -2468,7 +2490,7 @@ async function primeInitialStage(client, instanceId, actor, clientForGdr, clock)
|
|
|
2468
2490
|
_key: randomKey(),
|
|
2469
2491
|
name: stage.name,
|
|
2470
2492
|
enteredAt: now,
|
|
2471
|
-
|
|
2493
|
+
fields: await resolveStageFieldEntries({ client, instance, stage, now }),
|
|
2472
2494
|
tasks: await buildStageTasks(ctx, stage)
|
|
2473
2495
|
}, committed = await client.patch(instance._id).set({
|
|
2474
2496
|
stages: [initialStageEntry],
|
|
@@ -2697,6 +2719,85 @@ async function fetchGrantsCached(requestFn, resourcePath) {
|
|
|
2697
2719
|
return;
|
|
2698
2720
|
}
|
|
2699
2721
|
}
|
|
2722
|
+
function effectiveEditable(baseline, override) {
|
|
2723
|
+
if (baseline === void 0) return;
|
|
2724
|
+
if (override === void 0) return baseline;
|
|
2725
|
+
if (baseline === !0 && override === !0) return !0;
|
|
2726
|
+
const parts = [baseline, override].map((e) => e === !0 ? void 0 : e);
|
|
2727
|
+
return schema.andConditions(parts) ?? !0;
|
|
2728
|
+
}
|
|
2729
|
+
function slotSites(definition, stage) {
|
|
2730
|
+
const sites = [];
|
|
2731
|
+
for (const entry of definition.fields ?? []) sites.push({ scope: "workflow", entry });
|
|
2732
|
+
for (const entry of stage.fields ?? []) sites.push({ scope: "stage", entry });
|
|
2733
|
+
for (const task of stage.tasks ?? [])
|
|
2734
|
+
for (const entry of task.fields ?? []) sites.push({ scope: "task", task: task.name, entry });
|
|
2735
|
+
return sites;
|
|
2736
|
+
}
|
|
2737
|
+
function toResolvedSlot(site, stage) {
|
|
2738
|
+
return {
|
|
2739
|
+
scope: site.scope,
|
|
2740
|
+
...site.task !== void 0 ? { task: site.task } : {},
|
|
2741
|
+
name: site.entry.name,
|
|
2742
|
+
type: site.entry.type,
|
|
2743
|
+
...site.entry.title !== void 0 ? { title: site.entry.title } : {},
|
|
2744
|
+
ref: { scope: site.scope, field: site.entry.name },
|
|
2745
|
+
effective: effectiveEditable(site.entry.editable, stage.editable?.[site.entry.name])
|
|
2746
|
+
};
|
|
2747
|
+
}
|
|
2748
|
+
function editableSlotsInStage(definition, stage) {
|
|
2749
|
+
return slotSites(definition, stage).filter((site) => site.entry.editable !== void 0).map((site) => toResolvedSlot(site, stage));
|
|
2750
|
+
}
|
|
2751
|
+
function editTargetMatches(slot, target) {
|
|
2752
|
+
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;
|
|
2753
|
+
}
|
|
2754
|
+
const SCOPE_PRECEDENCE = { task: 0, stage: 1, workflow: 2 };
|
|
2755
|
+
function resolveEditTarget(args) {
|
|
2756
|
+
const { definition, stage, target } = args, found = slotSites(definition, stage).filter(
|
|
2757
|
+
(site) => editTargetMatches(
|
|
2758
|
+
{
|
|
2759
|
+
scope: site.scope,
|
|
2760
|
+
name: site.entry.name,
|
|
2761
|
+
...site.task !== void 0 ? { task: site.task } : {}
|
|
2762
|
+
},
|
|
2763
|
+
target
|
|
2764
|
+
)
|
|
2765
|
+
).sort((a, b) => SCOPE_PRECEDENCE[a.scope] - SCOPE_PRECEDENCE[b.scope])[0];
|
|
2766
|
+
return found ? toResolvedSlot(found, stage) : void 0;
|
|
2767
|
+
}
|
|
2768
|
+
function slotWindowOpen(instance, slot) {
|
|
2769
|
+
if (instance.completedAt !== void 0) return { open: !1, detail: "instance completed" };
|
|
2770
|
+
if (instance.abortedAt !== void 0) return { open: !1, detail: "instance aborted" };
|
|
2771
|
+
if (slot.scope !== "task") return { open: !0 };
|
|
2772
|
+
const status = findOpenStageEntry(instance)?.tasks.find((t) => t.name === slot.task)?.status;
|
|
2773
|
+
return status === "active" ? { open: !0 } : { open: !1, detail: `task "${slot.task}" is ${status ?? "not active"}` };
|
|
2774
|
+
}
|
|
2775
|
+
function readSlotValue(instance, slot) {
|
|
2776
|
+
return slotFieldHost(instance, slot)?.find((e) => e.name === slot.name)?.value;
|
|
2777
|
+
}
|
|
2778
|
+
function slotFieldHost(instance, slot) {
|
|
2779
|
+
if (slot.scope === "workflow") return instance.fields;
|
|
2780
|
+
const stageEntry = findOpenStageEntry(instance);
|
|
2781
|
+
return slot.scope === "stage" ? stageEntry?.fields : stageEntry?.tasks.find((t) => t.name === slot.task)?.fields;
|
|
2782
|
+
}
|
|
2783
|
+
function slotProvenance(instance, ref) {
|
|
2784
|
+
let latest;
|
|
2785
|
+
for (const entry of instance.history)
|
|
2786
|
+
entry._type === "opApplied" && (entry.target?.scope !== ref.scope || entry.target.field !== ref.field || (latest = { at: entry.at, ...entry.actor !== void 0 ? { actor: entry.actor } : {} }));
|
|
2787
|
+
return latest === void 0 ? {} : {
|
|
2788
|
+
...latest.actor !== void 0 ? { setBy: latest.actor } : {},
|
|
2789
|
+
setAt: latest.at
|
|
2790
|
+
};
|
|
2791
|
+
}
|
|
2792
|
+
function editDisabledReason(args) {
|
|
2793
|
+
const { effective, instance, window, guardDenial, predicateSatisfied } = args;
|
|
2794
|
+
if (effective === void 0) return { kind: "not-editable" };
|
|
2795
|
+
if (!window.open)
|
|
2796
|
+
return instance.completedAt !== void 0 ? { kind: "instance-completed", completedAt: instance.completedAt } : { kind: "edit-window-closed", detail: window.detail ?? "closed" };
|
|
2797
|
+
if (guardDenial !== void 0) return guardDenial;
|
|
2798
|
+
if (!predicateSatisfied)
|
|
2799
|
+
return { kind: "editor-not-permitted", predicate: effective === !0 ? "true" : effective };
|
|
2800
|
+
}
|
|
2700
2801
|
async function evaluateInstance(args) {
|
|
2701
2802
|
const { client, tag, instanceId, resourceClients } = args, now = (args.clock ?? wallClock)();
|
|
2702
2803
|
validateTag(tag);
|
|
@@ -2730,6 +2831,7 @@ async function evaluateFromSnapshot(args) {
|
|
|
2730
2831
|
actor,
|
|
2731
2832
|
snapshot,
|
|
2732
2833
|
scope,
|
|
2834
|
+
roleAliases: definition.roleAliases,
|
|
2733
2835
|
stageHasExits: !isTerminalStage(stage),
|
|
2734
2836
|
guardDenial
|
|
2735
2837
|
})
|
|
@@ -2751,16 +2853,65 @@ async function evaluateFromSnapshot(args) {
|
|
|
2751
2853
|
stage,
|
|
2752
2854
|
tasks: taskEvaluations,
|
|
2753
2855
|
transitions: transitionEvaluations
|
|
2754
|
-
}, pendingOnYou = taskEvaluations.filter((t) => t.pendingOnActor), canInteract = taskEvaluations.some((t) => t.actions.some((a) => a.allowed))
|
|
2856
|
+
}, 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({
|
|
2857
|
+
instance,
|
|
2858
|
+
definition,
|
|
2859
|
+
stage,
|
|
2860
|
+
actor,
|
|
2861
|
+
snapshot,
|
|
2862
|
+
scope,
|
|
2863
|
+
guardDenial: editGuardDenial
|
|
2864
|
+
});
|
|
2755
2865
|
return {
|
|
2756
2866
|
instance,
|
|
2757
2867
|
definition,
|
|
2758
2868
|
actor,
|
|
2759
2869
|
currentStage,
|
|
2760
2870
|
pendingOnYou,
|
|
2761
|
-
canInteract
|
|
2871
|
+
canInteract,
|
|
2872
|
+
editableSlots
|
|
2762
2873
|
};
|
|
2763
2874
|
}
|
|
2875
|
+
async function evaluateEditableSlots(args) {
|
|
2876
|
+
const { instance, definition, stage, actor, snapshot, scope, guardDenial } = args, slots = [];
|
|
2877
|
+
for (const slot of editableSlotsInStage(definition, stage)) {
|
|
2878
|
+
const window = slotWindowOpen(instance, slot), predicateSatisfied = await editPredicateSatisfied({
|
|
2879
|
+
slot,
|
|
2880
|
+
window,
|
|
2881
|
+
guardDenial,
|
|
2882
|
+
instance,
|
|
2883
|
+
snapshot,
|
|
2884
|
+
scope,
|
|
2885
|
+
actor,
|
|
2886
|
+
roleAliases: definition.roleAliases
|
|
2887
|
+
}), reason = editDisabledReason({
|
|
2888
|
+
effective: slot.effective,
|
|
2889
|
+
instance,
|
|
2890
|
+
window,
|
|
2891
|
+
guardDenial,
|
|
2892
|
+
predicateSatisfied
|
|
2893
|
+
}), value = readSlotValue(instance, slot);
|
|
2894
|
+
slots.push({
|
|
2895
|
+
scope: slot.scope,
|
|
2896
|
+
...slot.task !== void 0 ? { task: slot.task } : {},
|
|
2897
|
+
name: slot.name,
|
|
2898
|
+
type: slot.type,
|
|
2899
|
+
...slot.title !== void 0 ? { title: slot.title } : {},
|
|
2900
|
+
value,
|
|
2901
|
+
editable: reason === void 0,
|
|
2902
|
+
...reason !== void 0 ? { disabledReason: reason } : {},
|
|
2903
|
+
...slotProvenance(instance, slot.ref)
|
|
2904
|
+
});
|
|
2905
|
+
}
|
|
2906
|
+
return slots;
|
|
2907
|
+
}
|
|
2908
|
+
async function editPredicateSatisfied(args) {
|
|
2909
|
+
const { slot, window, guardDenial, instance, snapshot, scope, actor, roleAliases } = args;
|
|
2910
|
+
if (!window.open || guardDenial !== void 0) return !1;
|
|
2911
|
+
if (slot.effective === !0) return !0;
|
|
2912
|
+
const params = slot.scope === "task" && slot.task !== void 0 ? taskScopeFor({ scope, instance, snapshot, actor, taskName: slot.task, roleAliases }) : scope;
|
|
2913
|
+
return evaluateCondition({ condition: slot.effective, snapshot, params });
|
|
2914
|
+
}
|
|
2764
2915
|
async function renderScope(args) {
|
|
2765
2916
|
const { instance, definition, actor, grants, snapshot, now } = args, params = {
|
|
2766
2917
|
...buildParams({ instance, now, snapshot }),
|
|
@@ -2786,15 +2937,36 @@ async function advisoryCan(instance, actor, grants) {
|
|
|
2786
2937
|
});
|
|
2787
2938
|
return can;
|
|
2788
2939
|
}
|
|
2789
|
-
|
|
2790
|
-
const {
|
|
2940
|
+
function taskScopeFor(args) {
|
|
2941
|
+
const { scope, instance, snapshot, actor, taskName, roleAliases } = args;
|
|
2942
|
+
return {
|
|
2791
2943
|
...scope,
|
|
2792
|
-
|
|
2793
|
-
...scope.
|
|
2794
|
-
...
|
|
2944
|
+
fields: {
|
|
2945
|
+
...scope.fields,
|
|
2946
|
+
...scopedFieldOverlay(instance, snapshot, taskName)
|
|
2795
2947
|
},
|
|
2796
|
-
assigned
|
|
2797
|
-
}
|
|
2948
|
+
assigned: assignedFor(instance, taskName, actor, roleAliases)
|
|
2949
|
+
};
|
|
2950
|
+
}
|
|
2951
|
+
async function evaluateTask(args) {
|
|
2952
|
+
const {
|
|
2953
|
+
task,
|
|
2954
|
+
statusEntry,
|
|
2955
|
+
instance,
|
|
2956
|
+
actor,
|
|
2957
|
+
snapshot,
|
|
2958
|
+
scope,
|
|
2959
|
+
roleAliases,
|
|
2960
|
+
stageHasExits,
|
|
2961
|
+
guardDenial
|
|
2962
|
+
} = args, status = statusEntry?.status ?? "pending", taskScope = taskScopeFor({
|
|
2963
|
+
scope,
|
|
2964
|
+
instance,
|
|
2965
|
+
snapshot,
|
|
2966
|
+
actor,
|
|
2967
|
+
taskName: task.name,
|
|
2968
|
+
roleAliases
|
|
2969
|
+
}), assigned = taskScope.assigned === !0, unmetRequirements = await evaluateRequirements({
|
|
2798
2970
|
requirements: task.requirements,
|
|
2799
2971
|
snapshot,
|
|
2800
2972
|
params: taskScope
|
|
@@ -2817,8 +2989,8 @@ async function evaluateTask(args) {
|
|
|
2817
2989
|
status,
|
|
2818
2990
|
// "pending on actor" treats both `pending` and `active` as waiting on
|
|
2819
2991
|
// the assignee — pending tasks just haven't been activated yet, but
|
|
2820
|
-
// they're still inbox items. The inbox reads the assignees-kind
|
|
2821
|
-
// entry BY KIND (who-data is
|
|
2992
|
+
// they're still inbox items. The inbox reads the assignees-kind field
|
|
2993
|
+
// entry BY KIND (who-data is fields).
|
|
2822
2994
|
pendingOnActor: (status === "active" || status === "pending") && assigned,
|
|
2823
2995
|
...unmetRequirements.length > 0 ? { unmetRequirements } : {},
|
|
2824
2996
|
actions
|
|
@@ -3021,10 +3193,7 @@ async function commitAbort(ctx, reason, actor) {
|
|
|
3021
3193
|
async function fireAction(args) {
|
|
3022
3194
|
const { client, instanceId, task, action, params, options } = args;
|
|
3023
3195
|
for (let attempt = 1; attempt <= CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS; attempt++) {
|
|
3024
|
-
const ctx = await
|
|
3025
|
-
...options?.clock ? { clock: options.clock } : {},
|
|
3026
|
-
...options?.clientForGdr ? { clientForGdr: options.clientForGdr } : {}
|
|
3027
|
-
});
|
|
3196
|
+
const ctx = await loadCallContext(client, instanceId, options);
|
|
3028
3197
|
try {
|
|
3029
3198
|
return await commitAction(ctx, task, action, params, options);
|
|
3030
3199
|
} catch (error) {
|
|
@@ -3090,7 +3259,7 @@ async function commitAction(ctx, taskName, actionName, callerParams, options) {
|
|
|
3090
3259
|
}), await persistThenDeploy(
|
|
3091
3260
|
ctx,
|
|
3092
3261
|
mutation,
|
|
3093
|
-
() => ranOps.some(
|
|
3262
|
+
() => ranOps.some(isFieldOp) ? refreshStageGuards(ctx, mutation, stage.name) : Promise.resolve()
|
|
3094
3263
|
), {
|
|
3095
3264
|
fired: !0,
|
|
3096
3265
|
task: taskName,
|
|
@@ -3119,6 +3288,114 @@ function formatDisabledReason(task, action, reason) {
|
|
|
3119
3288
|
const detail = disabledReasonDetail[reason.kind](reason);
|
|
3120
3289
|
return `Action "${task}:${action}" is not allowed: ${detail}`;
|
|
3121
3290
|
}
|
|
3291
|
+
class EditFieldDeniedError extends Error {
|
|
3292
|
+
reason;
|
|
3293
|
+
target;
|
|
3294
|
+
constructor(args) {
|
|
3295
|
+
super(formatEditDisabledReason(args.target, args.reason)), this.name = "EditFieldDeniedError", this.reason = args.reason, this.target = args.target;
|
|
3296
|
+
}
|
|
3297
|
+
}
|
|
3298
|
+
const editDisabledReasonDetail = {
|
|
3299
|
+
"not-editable": () => "slot is not declared editable",
|
|
3300
|
+
"instance-completed": (r) => `instance completed at ${r.completedAt}`,
|
|
3301
|
+
"edit-window-closed": (r) => `edit window closed (${r.detail})`,
|
|
3302
|
+
"editor-not-permitted": (r) => `editor not permitted (${r.predicate})`,
|
|
3303
|
+
"mutation-guard-denied": (r) => `blocked by mutation guard(s) [${r.guardIds.join(", ")}]${r.detail ? ` (${r.detail})` : ""}`
|
|
3304
|
+
};
|
|
3305
|
+
function formatEditDisabledReason(target, reason) {
|
|
3306
|
+
const detail = editDisabledReasonDetail[reason.kind](
|
|
3307
|
+
reason
|
|
3308
|
+
), where = target.task !== void 0 ? `${target.task}.${target.field}` : target.field;
|
|
3309
|
+
return `Field "${target.scope}:${where}" is not editable: ${detail}`;
|
|
3310
|
+
}
|
|
3311
|
+
async function editField(args) {
|
|
3312
|
+
const { client, instanceId, target, mode = "set", value, options } = args;
|
|
3313
|
+
for (let attempt = 1; attempt <= CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS; attempt++) {
|
|
3314
|
+
const ctx = await loadCallContext(client, instanceId, options);
|
|
3315
|
+
try {
|
|
3316
|
+
return await commitEdit(ctx, target, mode, value, options);
|
|
3317
|
+
} catch (error) {
|
|
3318
|
+
if (!isRevisionConflict(error)) throw error;
|
|
3319
|
+
}
|
|
3320
|
+
}
|
|
3321
|
+
throw new ConcurrentEditFieldError({
|
|
3322
|
+
instanceId,
|
|
3323
|
+
target: labelTarget(target),
|
|
3324
|
+
attempts: CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS
|
|
3325
|
+
});
|
|
3326
|
+
}
|
|
3327
|
+
async function commitEdit(ctx, target, mode, value, options) {
|
|
3328
|
+
const actor = options?.actor, stage = findStage(ctx.definition, ctx.instance.currentStage), slot = resolveEditTarget({ definition: ctx.definition, stage, target });
|
|
3329
|
+
if (slot === void 0)
|
|
3330
|
+
throw new Error(
|
|
3331
|
+
`No field slot "${describeTarget(target)}" resolves in current stage "${stage.name}" of ${ctx.definition.name}`
|
|
3332
|
+
);
|
|
3333
|
+
await assertSlotEditable(ctx, slot, options);
|
|
3334
|
+
const op = buildEditOp(slot, mode, value), mutation = startMutation(ctx.instance), ranOps = runOps({
|
|
3335
|
+
ops: [op],
|
|
3336
|
+
mutation,
|
|
3337
|
+
stage: stage.name,
|
|
3338
|
+
origin: {
|
|
3339
|
+
edit: !0,
|
|
3340
|
+
...slot.scope === "task" && slot.task !== void 0 ? { task: slot.task } : {}
|
|
3341
|
+
},
|
|
3342
|
+
params: {},
|
|
3343
|
+
actor,
|
|
3344
|
+
self: selfGdr(ctx.instance),
|
|
3345
|
+
now: ctx.now
|
|
3346
|
+
});
|
|
3347
|
+
return await persistThenDeploy(
|
|
3348
|
+
ctx,
|
|
3349
|
+
mutation,
|
|
3350
|
+
() => ranOps.some(isFieldOp) ? refreshStageGuards(ctx, mutation, stage.name) : Promise.resolve()
|
|
3351
|
+
), {
|
|
3352
|
+
edited: !0,
|
|
3353
|
+
target: slotTarget(slot),
|
|
3354
|
+
...ranOps.length > 0 ? { ranOps } : {}
|
|
3355
|
+
};
|
|
3356
|
+
}
|
|
3357
|
+
async function assertSlotEditable(ctx, slot, options) {
|
|
3358
|
+
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, {
|
|
3359
|
+
...slot.task !== void 0 ? { taskName: slot.task } : {},
|
|
3360
|
+
...actor !== void 0 ? { actor } : {},
|
|
3361
|
+
...can !== void 0 ? { vars: { can } } : {}
|
|
3362
|
+
}) : slot.effective === !0, reason = editDisabledReason({
|
|
3363
|
+
effective: slot.effective,
|
|
3364
|
+
instance: ctx.instance,
|
|
3365
|
+
window,
|
|
3366
|
+
guardDenial: void 0,
|
|
3367
|
+
predicateSatisfied
|
|
3368
|
+
});
|
|
3369
|
+
if (reason !== void 0) throw new EditFieldDeniedError({ target: slotTarget(slot), reason });
|
|
3370
|
+
}
|
|
3371
|
+
function buildEditOp(slot, mode, value) {
|
|
3372
|
+
if (mode === "unset") return { type: "field.unset", target: slot.ref };
|
|
3373
|
+
if (value === void 0)
|
|
3374
|
+
throw new Error(`editField mode "${mode}" requires a value for slot "${slot.name}"`);
|
|
3375
|
+
return {
|
|
3376
|
+
type: mode === "append" ? "field.append" : "field.set",
|
|
3377
|
+
target: slot.ref,
|
|
3378
|
+
value: { type: "literal", value }
|
|
3379
|
+
};
|
|
3380
|
+
}
|
|
3381
|
+
function slotTarget(slot) {
|
|
3382
|
+
return {
|
|
3383
|
+
scope: slot.scope,
|
|
3384
|
+
field: slot.name,
|
|
3385
|
+
...slot.task !== void 0 ? { task: slot.task } : {}
|
|
3386
|
+
};
|
|
3387
|
+
}
|
|
3388
|
+
function labelTarget(target) {
|
|
3389
|
+
return {
|
|
3390
|
+
scope: target.scope ?? (target.task !== void 0 ? "task" : "workflow"),
|
|
3391
|
+
field: target.field,
|
|
3392
|
+
...target.task !== void 0 ? { task: target.task } : {}
|
|
3393
|
+
};
|
|
3394
|
+
}
|
|
3395
|
+
function describeTarget(target) {
|
|
3396
|
+
const where = target.task !== void 0 ? `${target.task}.${target.field}` : target.field;
|
|
3397
|
+
return target.scope !== void 0 ? `${target.scope}:${where}` : where;
|
|
3398
|
+
}
|
|
3122
3399
|
function bareIdFromSpawnRef(uri) {
|
|
3123
3400
|
if (!uri.includes(":")) return [uri];
|
|
3124
3401
|
try {
|
|
@@ -3165,18 +3442,60 @@ function buildClientForGdr(defaultClient, resolver) {
|
|
|
3165
3442
|
function toEffectsContextEntries(ctx) {
|
|
3166
3443
|
return Object.entries(ctx).map(([id, value]) => effectsContextEntry(id, value));
|
|
3167
3444
|
}
|
|
3445
|
+
async function dispatchGatedWrite(args) {
|
|
3446
|
+
const { client, tag, instanceId, resourceClients, access, apply } = args, evaluation = await evaluateInstance({
|
|
3447
|
+
client,
|
|
3448
|
+
tag,
|
|
3449
|
+
instanceId,
|
|
3450
|
+
access,
|
|
3451
|
+
clock: args.clock,
|
|
3452
|
+
...resourceClients !== void 0 ? { resourceClients } : {}
|
|
3453
|
+
}), ranOps = await apply(args.before, evaluation), cascaded = await cascade(client, instanceId, args.actor, args.clientForGdr, args.clock);
|
|
3454
|
+
return {
|
|
3455
|
+
instance: await reload(client, instanceId, tag),
|
|
3456
|
+
cascaded,
|
|
3457
|
+
fired: !0,
|
|
3458
|
+
...ranOps !== void 0 ? { ranOps } : {}
|
|
3459
|
+
};
|
|
3460
|
+
}
|
|
3168
3461
|
function assertActionAllowed(evaluation, task, action) {
|
|
3169
3462
|
const actionEval = evaluation.currentStage.tasks.find((t) => t.task.name === task)?.actions.find((a) => a.action.name === action);
|
|
3170
3463
|
if (actionEval?.allowed === !1 && actionEval.disabledReason)
|
|
3171
3464
|
throw new ActionDisabledError({ task, action, reason: actionEval.disabledReason });
|
|
3172
3465
|
}
|
|
3173
|
-
|
|
3174
|
-
const
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3466
|
+
function assertEditAllowed(evaluation, target) {
|
|
3467
|
+
const slot = evaluation.editableSlots.filter((s) => editTargetMatches(s, target)).sort((a, b) => SCOPE_PRECEDENCE[a.scope] - SCOPE_PRECEDENCE[b.scope])[0];
|
|
3468
|
+
if (slot !== void 0 && !slot.editable && slot.disabledReason !== void 0)
|
|
3469
|
+
throw new EditFieldDeniedError({
|
|
3470
|
+
target: {
|
|
3471
|
+
scope: slot.scope,
|
|
3472
|
+
field: slot.name,
|
|
3473
|
+
...slot.task !== void 0 ? { task: slot.task } : {}
|
|
3474
|
+
},
|
|
3475
|
+
reason: slot.disabledReason
|
|
3476
|
+
});
|
|
3477
|
+
}
|
|
3478
|
+
async function applyEdit(args) {
|
|
3479
|
+
const { client, instance, target, mode, value, actor, grants, clock, clientForGdr } = args;
|
|
3480
|
+
return (await editField({
|
|
3481
|
+
client,
|
|
3482
|
+
instanceId: instance._id,
|
|
3483
|
+
target,
|
|
3484
|
+
...mode !== void 0 ? { mode } : {},
|
|
3485
|
+
...value !== void 0 ? { value } : {},
|
|
3486
|
+
options: buildEngineCallOptions({ actor, grants, clock, clientForGdr })
|
|
3487
|
+
})).ranOps;
|
|
3488
|
+
}
|
|
3489
|
+
function buildEngineCallOptions(args) {
|
|
3490
|
+
return {
|
|
3491
|
+
actor: args.actor,
|
|
3492
|
+
...args.grants !== void 0 ? { grants: args.grants } : {},
|
|
3493
|
+
...args.clock !== void 0 ? { clock: args.clock } : {},
|
|
3494
|
+
...args.clientForGdr !== void 0 ? { clientForGdr: args.clientForGdr } : {}
|
|
3179
3495
|
};
|
|
3496
|
+
}
|
|
3497
|
+
async function applyAction(args) {
|
|
3498
|
+
const { client, instance, task, action, params, actor, grants, clock, clientForGdr } = args, options = buildEngineCallOptions({ actor, grants, clock, clientForGdr });
|
|
3180
3499
|
return findOpenStageEntry(instance)?.tasks.find((t) => t.name === task)?.status === "pending" && await invokeTask({ client, instanceId: instance._id, task, options }), (await fireAction({
|
|
3181
3500
|
client,
|
|
3182
3501
|
instanceId: instance._id,
|
|
@@ -3331,7 +3650,7 @@ const workflow = {
|
|
|
3331
3650
|
workflowResource,
|
|
3332
3651
|
definition: definitionName,
|
|
3333
3652
|
version,
|
|
3334
|
-
|
|
3653
|
+
initialFields,
|
|
3335
3654
|
ancestors,
|
|
3336
3655
|
effectsContext,
|
|
3337
3656
|
instanceId,
|
|
@@ -3341,9 +3660,9 @@ const workflow = {
|
|
|
3341
3660
|
throw new Error(
|
|
3342
3661
|
`Initial stage "${definition.initialStage}" missing in ${definitionName} v${definition.version}`
|
|
3343
3662
|
);
|
|
3344
|
-
const now = clock(), effectsContextEntries = effectsContext ? toEffectsContextEntries(effectsContext) : [],
|
|
3345
|
-
entryDefs: definition.
|
|
3346
|
-
|
|
3663
|
+
const now = clock(), effectsContextEntries = effectsContext ? toEffectsContextEntries(effectsContext) : [], resolvedFields = await resolveDeclaredFields({
|
|
3664
|
+
entryDefs: definition.fields ?? [],
|
|
3665
|
+
initialFields: initialFields ?? [],
|
|
3347
3666
|
ctx: {
|
|
3348
3667
|
client,
|
|
3349
3668
|
now,
|
|
@@ -3354,7 +3673,7 @@ const workflow = {
|
|
|
3354
3673
|
...perspective !== void 0 ? { perspective } : {}
|
|
3355
3674
|
},
|
|
3356
3675
|
randomKey
|
|
3357
|
-
}), effectivePerspective = perspective ??
|
|
3676
|
+
}), effectivePerspective = perspective ?? derivePerspectiveFromFields(resolvedFields), base = buildInstanceBase({
|
|
3358
3677
|
id,
|
|
3359
3678
|
now,
|
|
3360
3679
|
tag,
|
|
@@ -3362,7 +3681,7 @@ const workflow = {
|
|
|
3362
3681
|
definitionName: definition.name,
|
|
3363
3682
|
pinnedVersion: definition.version,
|
|
3364
3683
|
definition,
|
|
3365
|
-
|
|
3684
|
+
fields: resolvedFields,
|
|
3366
3685
|
effectsContext: effectsContextEntries,
|
|
3367
3686
|
ancestors: ancestors ?? [],
|
|
3368
3687
|
perspective: effectivePerspective,
|
|
@@ -3392,34 +3711,68 @@ const workflow = {
|
|
|
3392
3711
|
idempotent,
|
|
3393
3712
|
resourceClients
|
|
3394
3713
|
} = args, { access, actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, before = await reload(client, instanceId, tag);
|
|
3395
|
-
|
|
3396
|
-
return { instance: before, cascaded: 0, fired: !1 };
|
|
3397
|
-
const evaluation = await evaluateInstance({
|
|
3714
|
+
return findOpenStageEntry(before)?.tasks.find((t) => t.name === task) === void 0 && idempotent === !0 ? { instance: before, cascaded: 0, fired: !1 } : dispatchGatedWrite({
|
|
3398
3715
|
client,
|
|
3399
3716
|
tag,
|
|
3400
3717
|
instanceId,
|
|
3401
3718
|
access,
|
|
3719
|
+
actor,
|
|
3720
|
+
clientForGdr,
|
|
3402
3721
|
clock,
|
|
3403
|
-
|
|
3722
|
+
before,
|
|
3723
|
+
...resourceClients !== void 0 ? { resourceClients } : {},
|
|
3724
|
+
apply: (instance, evaluation) => (assertActionAllowed(evaluation, task, action), applyAction({
|
|
3725
|
+
client,
|
|
3726
|
+
instance,
|
|
3727
|
+
task,
|
|
3728
|
+
action,
|
|
3729
|
+
actor,
|
|
3730
|
+
...access.grants !== void 0 ? { grants: access.grants } : {},
|
|
3731
|
+
clock,
|
|
3732
|
+
clientForGdr,
|
|
3733
|
+
...params !== void 0 ? { params } : {}
|
|
3734
|
+
}))
|
|
3404
3735
|
});
|
|
3405
|
-
|
|
3406
|
-
|
|
3736
|
+
},
|
|
3737
|
+
/**
|
|
3738
|
+
* Edit a declared-editable field slot directly — reassign, reschedule,
|
|
3739
|
+
* claim-by-hand, append to a running log — through the generic edit seam,
|
|
3740
|
+
* instead of a bespoke action per field. Soft-gates on the slot's declared
|
|
3741
|
+
* editability (the same projection a UI renders), applies the edit as a
|
|
3742
|
+
* `field.*` op (so provenance + history are stamped by the op path),
|
|
3743
|
+
* refreshes the stage's guards, then cascades — an edit to a value a
|
|
3744
|
+
* transition reads can and should move the instance. Advisory like every
|
|
3745
|
+
* engine gate; the lake ACL + guards are the only hard locks.
|
|
3746
|
+
*
|
|
3747
|
+
* Each call is a discrete COMMIT (a history entry, a guard refresh, a
|
|
3748
|
+
* cascade, an `ifRevisionId` write), not a draft patch — so an inline-field
|
|
3749
|
+
* UI must bind it to a deliberate boundary (blur / Enter / Save / debounce),
|
|
3750
|
+
* never an `onChange` per keystroke.
|
|
3751
|
+
*/
|
|
3752
|
+
editField: async (args) => {
|
|
3753
|
+
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);
|
|
3754
|
+
return dispatchGatedWrite({
|
|
3407
3755
|
client,
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3756
|
+
tag,
|
|
3757
|
+
instanceId,
|
|
3758
|
+
access,
|
|
3411
3759
|
actor,
|
|
3412
|
-
...access.grants !== void 0 ? { grants: access.grants } : {},
|
|
3413
|
-
clock,
|
|
3414
3760
|
clientForGdr,
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3761
|
+
clock,
|
|
3762
|
+
before,
|
|
3763
|
+
...resourceClients !== void 0 ? { resourceClients } : {},
|
|
3764
|
+
apply: (instance, evaluation) => (assertEditAllowed(evaluation, target), applyEdit({
|
|
3765
|
+
client,
|
|
3766
|
+
instance,
|
|
3767
|
+
target,
|
|
3768
|
+
...mode !== void 0 ? { mode } : {},
|
|
3769
|
+
...value !== void 0 ? { value } : {},
|
|
3770
|
+
actor,
|
|
3771
|
+
...access.grants !== void 0 ? { grants: access.grants } : {},
|
|
3772
|
+
clock,
|
|
3773
|
+
clientForGdr
|
|
3774
|
+
}))
|
|
3775
|
+
});
|
|
3423
3776
|
},
|
|
3424
3777
|
/**
|
|
3425
3778
|
* Report a queued effect's outcome. Drains it from `pendingEffects`,
|
|
@@ -3561,7 +3914,7 @@ const workflow = {
|
|
|
3561
3914
|
* Hydrates the instance's snapshot (instance + ancestors + every doc
|
|
3562
3915
|
* declared by a `doc.ref` / `doc.refs` entry in scope), then
|
|
3563
3916
|
* evaluates the supplied GROQ in groq-js against that dataset. The
|
|
3564
|
-
* rendered scope is auto-bound: `$self`, `$
|
|
3917
|
+
* rendered scope is auto-bound: `$self`, `$fields`, `$parent`,
|
|
3565
3918
|
* `$ancestors`, `$stage`, `$now`, `$effects`, `$tasks`,
|
|
3566
3919
|
* `$allTasksDone`, `$anyTaskFailed` — ids in GDR URI form to match
|
|
3567
3920
|
* the snapshot's keying.
|
|
@@ -3837,6 +4190,9 @@ function createInstanceSession(args) {
|
|
|
3837
4190
|
...clock !== void 0 ? { now: clock() } : {},
|
|
3838
4191
|
...grants !== void 0 ? { grants } : {}
|
|
3839
4192
|
});
|
|
4193
|
+
}, settleAfterApply = async (actor, held, ranOps) => {
|
|
4194
|
+
const cascaded = await cascade(client, instance._id, actor, clientForGdr, clock, held);
|
|
4195
|
+
return instance = await reload(client, instance._id, tag), { instance, cascaded, fired: !0, ...ranOps !== void 0 ? { ranOps } : {} };
|
|
3840
4196
|
}, commit = async (run) => {
|
|
3841
4197
|
committing = !0;
|
|
3842
4198
|
const held = new Map(overlay);
|
|
@@ -3887,8 +4243,25 @@ function createInstanceSession(args) {
|
|
|
3887
4243
|
...grants !== void 0 ? { grants } : {},
|
|
3888
4244
|
...clock !== void 0 ? { clock } : {},
|
|
3889
4245
|
...params !== void 0 ? { params } : {}
|
|
3890
|
-
})
|
|
3891
|
-
return
|
|
4246
|
+
});
|
|
4247
|
+
return settleAfterApply(actor, held, ranOps);
|
|
4248
|
+
});
|
|
4249
|
+
},
|
|
4250
|
+
editField({ target, mode, value }) {
|
|
4251
|
+
return commit(async (actor, held) => {
|
|
4252
|
+
assertEditAllowed(await evaluateWith(held, heldGuards), target);
|
|
4253
|
+
const { grants } = await access(), ranOps = await applyEdit({
|
|
4254
|
+
client,
|
|
4255
|
+
instance,
|
|
4256
|
+
target,
|
|
4257
|
+
...mode !== void 0 ? { mode } : {},
|
|
4258
|
+
...value !== void 0 ? { value } : {},
|
|
4259
|
+
actor,
|
|
4260
|
+
clientForGdr,
|
|
4261
|
+
...grants !== void 0 ? { grants } : {},
|
|
4262
|
+
...clock !== void 0 ? { clock } : {}
|
|
4263
|
+
});
|
|
4264
|
+
return settleAfterApply(actor, held, ranOps);
|
|
3892
4265
|
});
|
|
3893
4266
|
}
|
|
3894
4267
|
};
|
|
@@ -3936,6 +4309,7 @@ function createEngine(args) {
|
|
|
3936
4309
|
deployDefinitions: (rest) => workflow.deployDefinitions(bind(rest)),
|
|
3937
4310
|
startInstance: (rest) => workflow.startInstance(bind(rest)),
|
|
3938
4311
|
fireAction: (rest) => workflow.fireAction(bind(rest)),
|
|
4312
|
+
editField: (rest) => workflow.editField(bind(rest)),
|
|
3939
4313
|
completeEffect: (rest) => workflow.completeEffect(bind(rest)),
|
|
3940
4314
|
tick: (rest) => workflow.tick(bind(rest)),
|
|
3941
4315
|
evaluateInstance: (rest) => evaluateInstance(bind(rest)),
|
|
@@ -4094,9 +4468,9 @@ const HISTORY_DISPLAY = {
|
|
|
4094
4468
|
},
|
|
4095
4469
|
opApplied: {
|
|
4096
4470
|
title: "Op applied",
|
|
4097
|
-
description: "An inline
|
|
4471
|
+
description: "An inline field-mutation op (field.set / field.append / status.set / etc.) ran during an action commit."
|
|
4098
4472
|
}
|
|
4099
|
-
},
|
|
4473
|
+
}, FIELD_SLOT_DISPLAY = {
|
|
4100
4474
|
"doc.ref": {
|
|
4101
4475
|
title: "Document reference",
|
|
4102
4476
|
description: "Single GDR pointer at a document in this or another resource."
|
|
@@ -4150,23 +4524,23 @@ const HISTORY_DISPLAY = {
|
|
|
4150
4524
|
description: "Ordered list of typed (user|role) assignees."
|
|
4151
4525
|
}
|
|
4152
4526
|
}, OP_DISPLAY = {
|
|
4153
|
-
"
|
|
4154
|
-
title: "Set
|
|
4155
|
-
description: "Overwrite a
|
|
4527
|
+
"field.set": {
|
|
4528
|
+
title: "Set field entry",
|
|
4529
|
+
description: "Overwrite a field entry's value with a resolved Source."
|
|
4156
4530
|
},
|
|
4157
|
-
"
|
|
4158
|
-
title: "Unset
|
|
4159
|
-
description: "Reset a
|
|
4531
|
+
"field.unset": {
|
|
4532
|
+
title: "Unset field entry",
|
|
4533
|
+
description: "Reset a field entry to its default (null for scalars, [] for array kinds)."
|
|
4160
4534
|
},
|
|
4161
|
-
"
|
|
4162
|
-
title: "Append to
|
|
4535
|
+
"field.append": {
|
|
4536
|
+
title: "Append to field entry",
|
|
4163
4537
|
description: "Push a resolved item onto an array-kind entry (notes, checklist, assignees, refs)."
|
|
4164
4538
|
},
|
|
4165
|
-
"
|
|
4539
|
+
"field.updateWhere": {
|
|
4166
4540
|
title: "Update matching rows",
|
|
4167
4541
|
description: "Merge fields into rows of an array-kind entry that match the predicate."
|
|
4168
4542
|
},
|
|
4169
|
-
"
|
|
4543
|
+
"field.removeWhere": {
|
|
4170
4544
|
title: "Remove matching rows",
|
|
4171
4545
|
description: "Drop rows of an array-kind entry that match the predicate."
|
|
4172
4546
|
},
|
|
@@ -4210,11 +4584,11 @@ const HISTORY_DISPLAY = {
|
|
|
4210
4584
|
},
|
|
4211
4585
|
[WORKFLOW_INSTANCE_TYPE]: {
|
|
4212
4586
|
title: "Workflow instance",
|
|
4213
|
-
description: "A running (or finished) workflow against its declared
|
|
4587
|
+
description: "A running (or finished) workflow against its declared fields."
|
|
4214
4588
|
}
|
|
4215
4589
|
}, DISPLAY = {
|
|
4216
4590
|
...HISTORY_DISPLAY,
|
|
4217
|
-
...
|
|
4591
|
+
...FIELD_SLOT_DISPLAY,
|
|
4218
4592
|
...OP_DISPLAY,
|
|
4219
4593
|
...EFFECTS_CONTEXT_DISPLAY,
|
|
4220
4594
|
...AUTHORING_DISPLAY
|
|
@@ -4232,10 +4606,13 @@ exports.AUTHORING_DISPLAY = AUTHORING_DISPLAY;
|
|
|
4232
4606
|
exports.ActionDisabledError = ActionDisabledError;
|
|
4233
4607
|
exports.ActionParamsInvalidError = ActionParamsInvalidError;
|
|
4234
4608
|
exports.CascadeLimitError = CascadeLimitError;
|
|
4609
|
+
exports.ConcurrentEditFieldError = ConcurrentEditFieldError;
|
|
4235
4610
|
exports.ConcurrentFireActionError = ConcurrentFireActionError;
|
|
4236
4611
|
exports.DEFAULT_CONTENT_PERSPECTIVE = DEFAULT_CONTENT_PERSPECTIVE;
|
|
4237
4612
|
exports.DISPLAY = DISPLAY;
|
|
4238
4613
|
exports.EFFECTS_CONTEXT_DISPLAY = EFFECTS_CONTEXT_DISPLAY;
|
|
4614
|
+
exports.EditFieldDeniedError = EditFieldDeniedError;
|
|
4615
|
+
exports.FIELD_SLOT_DISPLAY = FIELD_SLOT_DISPLAY;
|
|
4239
4616
|
exports.GUARD_DOC_TYPE = GUARD_DOC_TYPE;
|
|
4240
4617
|
exports.GUARD_LIFTED_PREDICATE = GUARD_LIFTED_PREDICATE;
|
|
4241
4618
|
exports.GUARD_OWNER = GUARD_OWNER;
|
|
@@ -4243,8 +4620,7 @@ exports.HISTORY_DISPLAY = HISTORY_DISPLAY;
|
|
|
4243
4620
|
exports.MutationGuardDeniedError = MutationGuardDeniedError;
|
|
4244
4621
|
exports.OP_DISPLAY = OP_DISPLAY;
|
|
4245
4622
|
exports.PartialGuardDeployError = PartialGuardDeployError;
|
|
4246
|
-
exports.
|
|
4247
|
-
exports.STATE_SLOT_DISPLAY = STATE_SLOT_DISPLAY;
|
|
4623
|
+
exports.RequiredFieldNotProvidedError = RequiredFieldNotProvidedError;
|
|
4248
4624
|
exports.WORKFLOW_INSTANCE_TYPE = WORKFLOW_INSTANCE_TYPE;
|
|
4249
4625
|
exports.WorkflowStateDivergedError = WorkflowStateDivergedError;
|
|
4250
4626
|
exports.abortReason = abortReason;
|