@sanity/workflow-engine 0.9.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 +56 -56
- package/dist/_chunks-cjs/schema.cjs.map +1 -1
- package/dist/_chunks-es/schema.js +56 -56
- package/dist/_chunks-es/schema.js.map +1 -1
- package/dist/define.cjs +57 -57
- package/dist/define.cjs.map +1 -1
- package/dist/define.d.cts +382 -382
- package/dist/define.d.ts +382 -382
- package/dist/define.js +58 -58
- package/dist/define.js.map +1 -1
- package/dist/index.cjs +229 -229
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +743 -743
- package/dist/index.d.ts +743 -743
- package/dist/index.js +229 -229
- 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,15 +149,15 @@ 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
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
162
|
(a) => a.type === "user" ? a.id === actor.id : schema.actorFulfillsRole(actor.roles, a.role, roleAliases)
|
|
163
163
|
);
|
|
@@ -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,15 +678,15 @@ 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
|
|
681
|
+
class ConcurrentEditFieldError extends Error {
|
|
682
682
|
instanceId;
|
|
683
683
|
target;
|
|
684
684
|
attempts;
|
|
685
685
|
constructor(args) {
|
|
686
|
-
const where = args.target.task !== void 0 ? `${args.target.task}.${args.target.
|
|
686
|
+
const where = args.target.task !== void 0 ? `${args.target.task}.${args.target.field}` : args.target.field;
|
|
687
687
|
super(
|
|
688
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 = "
|
|
689
|
+
), this.name = "ConcurrentEditFieldError", this.instanceId = args.instanceId, this.target = args.target, this.attempts = args.attempts;
|
|
690
690
|
}
|
|
691
691
|
}
|
|
692
692
|
class CascadeLimitError extends Error {
|
|
@@ -734,10 +734,10 @@ function collectWatchRefs(instance) {
|
|
|
734
734
|
return [
|
|
735
735
|
gdrRef(instance.workflowResource, instance._id, instance._type),
|
|
736
736
|
...instance.ancestors,
|
|
737
|
-
...entryDocRefs(instance.
|
|
738
|
-
...entryDocRefs(stage?.
|
|
739
|
-
...entryReleaseRefs(instance.
|
|
740
|
-
...entryReleaseRefs(stage?.
|
|
737
|
+
...entryDocRefs(instance.fields),
|
|
738
|
+
...entryDocRefs(stage?.fields),
|
|
739
|
+
...entryReleaseRefs(instance.fields),
|
|
740
|
+
...entryReleaseRefs(stage?.fields)
|
|
741
741
|
];
|
|
742
742
|
}
|
|
743
743
|
function readsRaw(ref) {
|
|
@@ -761,21 +761,21 @@ function subscriptionDocumentsForInstance(instance) {
|
|
|
761
761
|
...instance.perspective !== void 0 ? { perspective: instance.perspective } : {}
|
|
762
762
|
};
|
|
763
763
|
}
|
|
764
|
-
function entryDocRefs(
|
|
765
|
-
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) : [] : []);
|
|
766
766
|
}
|
|
767
|
-
function entryReleaseRefs(
|
|
768
|
-
return
|
|
767
|
+
function entryReleaseRefs(entries) {
|
|
768
|
+
return fieldEntries(entries).flatMap(
|
|
769
769
|
(entry) => entry._type === "release.ref" && isGdr(entry.value) ? [entry.value] : []
|
|
770
770
|
);
|
|
771
771
|
}
|
|
772
|
-
function entrySubjectRefs(
|
|
773
|
-
return
|
|
772
|
+
function entrySubjectRefs(entries) {
|
|
773
|
+
return fieldEntries(entries).flatMap(
|
|
774
774
|
(entry) => (entry._type === "doc.ref" || entry._type === "release.ref") && isGdr(entry.value) ? [entry.value] : []
|
|
775
775
|
);
|
|
776
776
|
}
|
|
777
|
-
function
|
|
778
|
-
return Array.isArray(
|
|
777
|
+
function fieldEntries(entries) {
|
|
778
|
+
return Array.isArray(entries) ? entries.filter(
|
|
779
779
|
(entry) => !!entry && typeof entry == "object"
|
|
780
780
|
) : [];
|
|
781
781
|
}
|
|
@@ -821,8 +821,8 @@ async function readDoc(client, id, perspective) {
|
|
|
821
821
|
function resourceFromParsed(parsed) {
|
|
822
822
|
return parsed.scheme === "dataset" ? { type: "dataset", id: `${parsed.projectId}.${parsed.dataset}` } : { type: parsed.scheme, id: parsed.resourceId ?? "" };
|
|
823
823
|
}
|
|
824
|
-
function collectEntryDocUris(
|
|
825
|
-
return entryDocRefs(
|
|
824
|
+
function collectEntryDocUris(resolvedFieldEntries) {
|
|
825
|
+
return entryDocRefs(resolvedFieldEntries).map((ref) => ref.id);
|
|
826
826
|
}
|
|
827
827
|
const resourceKey = (r) => `${r.type}.${r.id}`;
|
|
828
828
|
function guardsForResource(client) {
|
|
@@ -840,8 +840,8 @@ function verdictGuardsForInstance(client, instanceId) {
|
|
|
840
840
|
}
|
|
841
841
|
async function guardsForInstance(args) {
|
|
842
842
|
const { client, clientForGdr, instance } = args, stateUris = [
|
|
843
|
-
...collectEntryDocUris(instance.
|
|
844
|
-
...instance.stages.flatMap((stage) => collectEntryDocUris(stage.
|
|
843
|
+
...collectEntryDocUris(instance.fields),
|
|
844
|
+
...instance.stages.flatMap((stage) => collectEntryDocUris(stage.fields))
|
|
845
845
|
], clients = resourceClientMap(
|
|
846
846
|
instance.workflowResource,
|
|
847
847
|
client,
|
|
@@ -873,16 +873,16 @@ function guardIdRefs(definition) {
|
|
|
873
873
|
);
|
|
874
874
|
}
|
|
875
875
|
function staticIdRefGdr(idRef, stage, definition) {
|
|
876
|
-
const read = /^\$
|
|
876
|
+
const read = /^\$fields\.([\w-]+)/.exec(idRef);
|
|
877
877
|
if (read === null) return;
|
|
878
878
|
const source = entriesInScope(stage, definition).find((e) => e.name === read[1])?.source;
|
|
879
879
|
return source?.type === "literal" ? gdrFromValue(source.value) : void 0;
|
|
880
880
|
}
|
|
881
881
|
function entriesInScope(stage, definition) {
|
|
882
882
|
return [
|
|
883
|
-
...definition.
|
|
884
|
-
...stage.
|
|
885
|
-
...(stage.tasks ?? []).flatMap((task) => task.
|
|
883
|
+
...definition.fields ?? [],
|
|
884
|
+
...stage.fields ?? [],
|
|
885
|
+
...(stage.tasks ?? []).flatMap((task) => task.fields ?? [])
|
|
886
886
|
];
|
|
887
887
|
}
|
|
888
888
|
function gdrFromValue(value) {
|
|
@@ -1032,15 +1032,15 @@ async function runGroq(groq, params, snapshot) {
|
|
|
1032
1032
|
const tree = groqJs.parse(groq, { params });
|
|
1033
1033
|
return (await groqJs.evaluate(tree, { dataset: snapshot.docs, params })).get();
|
|
1034
1034
|
}
|
|
1035
|
-
class
|
|
1035
|
+
class FieldValueShapeError extends Error {
|
|
1036
1036
|
entryType;
|
|
1037
1037
|
entryName;
|
|
1038
1038
|
issues;
|
|
1039
1039
|
constructor(args) {
|
|
1040
1040
|
const issueText = args.issues.join("; ");
|
|
1041
1041
|
super(
|
|
1042
|
-
`
|
|
1043
|
-
), 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;
|
|
1044
1044
|
}
|
|
1045
1045
|
}
|
|
1046
1046
|
const GdrShape = v__namespace.looseObject({
|
|
@@ -1107,35 +1107,35 @@ const GdrShape = v__namespace.looseObject({
|
|
|
1107
1107
|
function isAppendable(entryType) {
|
|
1108
1108
|
return entryType in itemSchemas;
|
|
1109
1109
|
}
|
|
1110
|
-
function
|
|
1110
|
+
function validateFieldValue(args) {
|
|
1111
1111
|
const schema2 = valueSchemas[args.entryType];
|
|
1112
1112
|
if (schema2 === void 0)
|
|
1113
|
-
throw new
|
|
1113
|
+
throw new FieldValueShapeError({
|
|
1114
1114
|
entryType: args.entryType,
|
|
1115
1115
|
entryName: args.entryName,
|
|
1116
|
-
issues: [`unknown
|
|
1116
|
+
issues: [`unknown field entry type ${args.entryType}`],
|
|
1117
1117
|
mode: "value"
|
|
1118
1118
|
});
|
|
1119
1119
|
const result = v__namespace.safeParse(schema2, args.value);
|
|
1120
1120
|
if (!result.success)
|
|
1121
|
-
throw new
|
|
1121
|
+
throw new FieldValueShapeError({
|
|
1122
1122
|
entryType: args.entryType,
|
|
1123
1123
|
entryName: args.entryName,
|
|
1124
1124
|
issues: formatIssues(result.issues),
|
|
1125
1125
|
mode: "value"
|
|
1126
1126
|
});
|
|
1127
1127
|
}
|
|
1128
|
-
function
|
|
1128
|
+
function validateFieldAppendItem(args) {
|
|
1129
1129
|
if (!isAppendable(args.entryType))
|
|
1130
|
-
throw new
|
|
1130
|
+
throw new FieldValueShapeError({
|
|
1131
1131
|
entryType: args.entryType,
|
|
1132
1132
|
entryName: args.entryName,
|
|
1133
|
-
issues: [`
|
|
1133
|
+
issues: [`field entry type ${args.entryType} does not support append`],
|
|
1134
1134
|
mode: "item"
|
|
1135
1135
|
});
|
|
1136
1136
|
const schema2 = itemSchemas[args.entryType], result = v__namespace.safeParse(schema2, args.item);
|
|
1137
1137
|
if (!result.success)
|
|
1138
|
-
throw new
|
|
1138
|
+
throw new FieldValueShapeError({
|
|
1139
1139
|
entryType: args.entryType,
|
|
1140
1140
|
entryName: args.entryName,
|
|
1141
1141
|
issues: formatIssues(result.issues),
|
|
@@ -1148,7 +1148,7 @@ function formatIssues(issues) {
|
|
|
1148
1148
|
return `${keys.length > 0 ? `at ${keys.join(".")}: ` : ""}${i.message}`;
|
|
1149
1149
|
});
|
|
1150
1150
|
}
|
|
1151
|
-
function
|
|
1151
|
+
function derivePerspectiveFromFields(entries) {
|
|
1152
1152
|
if (entries !== void 0) {
|
|
1153
1153
|
for (const entry of entries)
|
|
1154
1154
|
if (entry._type === "release.ref" && entry.value !== null) {
|
|
@@ -1158,24 +1158,24 @@ function derivePerspectiveFromState(entries) {
|
|
|
1158
1158
|
}
|
|
1159
1159
|
}
|
|
1160
1160
|
}
|
|
1161
|
-
async function
|
|
1162
|
-
const { entryDefs,
|
|
1161
|
+
async function resolveDeclaredFields(args) {
|
|
1162
|
+
const { entryDefs, initialFields, ctx, randomKey: randomKey2 } = args;
|
|
1163
1163
|
if (entryDefs === void 0 || entryDefs.length === 0) return [];
|
|
1164
|
-
assertRequiredInitProvided(entryDefs,
|
|
1164
|
+
assertRequiredInitProvided(entryDefs, initialFields, ctx.definitionName);
|
|
1165
1165
|
const out = [];
|
|
1166
1166
|
for (const entry of entryDefs) {
|
|
1167
|
-
const scopedCtx = { ...ctx,
|
|
1168
|
-
out.push(await resolveOneEntry(entry,
|
|
1167
|
+
const scopedCtx = { ...ctx, resolvedFields: out };
|
|
1168
|
+
out.push(await resolveOneEntry(entry, initialFields, scopedCtx, randomKey2));
|
|
1169
1169
|
}
|
|
1170
1170
|
return out;
|
|
1171
1171
|
}
|
|
1172
|
-
function assertRequiredInitProvided(entryDefs,
|
|
1172
|
+
function assertRequiredInitProvided(entryDefs, initialFields, definitionName) {
|
|
1173
1173
|
const missing = entryDefs.filter((entry) => entry.required === !0 && entry.source.type === "init").filter((entry) => {
|
|
1174
|
-
const match =
|
|
1174
|
+
const match = initialFields.find((s) => s.name === entry.name && s.type === entry.type);
|
|
1175
1175
|
return match === void 0 || match.value === void 0 || match.value === null;
|
|
1176
1176
|
}).map((entry) => ({ name: entry.name, type: entry.type }));
|
|
1177
1177
|
if (missing.length !== 0)
|
|
1178
|
-
throw new
|
|
1178
|
+
throw new RequiredFieldNotProvidedError({
|
|
1179
1179
|
...definitionName !== void 0 ? { definition: definitionName } : {},
|
|
1180
1180
|
missing
|
|
1181
1181
|
});
|
|
@@ -1189,18 +1189,18 @@ const ARRAY_SLOT_TYPES = /* @__PURE__ */ new Set([
|
|
|
1189
1189
|
function defaultEntryValue(entryType) {
|
|
1190
1190
|
return ARRAY_SLOT_TYPES.has(entryType) ? [] : null;
|
|
1191
1191
|
}
|
|
1192
|
-
function resolveInitValue(entry,
|
|
1193
|
-
const initMatch =
|
|
1192
|
+
function resolveInitValue(entry, initialFields, defaultValue) {
|
|
1193
|
+
const initMatch = initialFields.find((s) => s.name === entry.name && s.type === entry.type);
|
|
1194
1194
|
return initMatch === void 0 ? defaultValue : (assertInitValueShape(entry, initMatch.value), initMatch.value);
|
|
1195
1195
|
}
|
|
1196
|
-
function
|
|
1197
|
-
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;
|
|
1198
1198
|
return (source.path !== void 0 ? getPath(targetValue, source.path) : targetValue) ?? defaultValue;
|
|
1199
1199
|
}
|
|
1200
1200
|
async function resolveQueryValue(entry, source, ctx, client, defaultValue) {
|
|
1201
1201
|
const params = paramsForLake({
|
|
1202
1202
|
self: ctx.selfId ?? null,
|
|
1203
|
-
|
|
1203
|
+
fields: fieldMapFromResolved(ctx.resolvedFields ?? []),
|
|
1204
1204
|
stage: ctx.stageName ?? null,
|
|
1205
1205
|
task: ctx.taskName ?? null,
|
|
1206
1206
|
now: ctx.now,
|
|
@@ -1216,9 +1216,9 @@ async function resolveQueryValue(entry, source, ctx, client, defaultValue) {
|
|
|
1216
1216
|
);
|
|
1217
1217
|
}
|
|
1218
1218
|
}
|
|
1219
|
-
async function resolveEntryValue(entry,
|
|
1219
|
+
async function resolveEntryValue(entry, initialFields, ctx, defaultValue) {
|
|
1220
1220
|
const source = entry.source;
|
|
1221
|
-
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;
|
|
1222
1222
|
}
|
|
1223
1223
|
function buildResolvedEntry(entry, value, _key, now) {
|
|
1224
1224
|
const titleProp = entry.title !== void 0 ? { title: entry.title } : {}, descriptionProp = entry.description !== void 0 ? { description: entry.description } : {};
|
|
@@ -1239,36 +1239,36 @@ function buildResolvedEntry(entry, value, _key, now) {
|
|
|
1239
1239
|
value
|
|
1240
1240
|
};
|
|
1241
1241
|
}
|
|
1242
|
-
async function resolveOneEntry(entry,
|
|
1243
|
-
const defaultValue = defaultEntryValue(entry.type), value = await resolveEntryValue(entry,
|
|
1244
|
-
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);
|
|
1245
1245
|
}
|
|
1246
|
-
function
|
|
1246
|
+
function fieldMapFromResolved(entries) {
|
|
1247
1247
|
const out = {};
|
|
1248
1248
|
for (const s of entries) out[s.name] = s.value;
|
|
1249
1249
|
return out;
|
|
1250
1250
|
}
|
|
1251
1251
|
function assertInitValueShape(entry, value) {
|
|
1252
1252
|
if (entry.type === "doc.ref") {
|
|
1253
|
-
assertGdrShape(value, `
|
|
1253
|
+
assertGdrShape(value, `field entry "${entry.name}" (doc.ref)`);
|
|
1254
1254
|
return;
|
|
1255
1255
|
}
|
|
1256
1256
|
if (entry.type === "release.ref") {
|
|
1257
|
-
assertGdrShape(value, `
|
|
1257
|
+
assertGdrShape(value, `field entry "${entry.name}" (release.ref)`);
|
|
1258
1258
|
const v2 = value;
|
|
1259
1259
|
if (typeof v2.releaseName != "string" || v2.releaseName.length === 0)
|
|
1260
1260
|
throw new Error(
|
|
1261
|
-
`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)}.`
|
|
1262
1262
|
);
|
|
1263
1263
|
return;
|
|
1264
1264
|
}
|
|
1265
1265
|
if (entry.type === "doc.refs") {
|
|
1266
1266
|
if (!Array.isArray(value))
|
|
1267
1267
|
throw new Error(
|
|
1268
|
-
`Invalid init value for
|
|
1268
|
+
`Invalid init value for field entry "${entry.name}" (doc.refs): expected an array of GDRs, got ${typeof value}.`
|
|
1269
1269
|
);
|
|
1270
1270
|
for (const [i, item] of value.entries())
|
|
1271
|
-
assertGdrShape(item, `
|
|
1271
|
+
assertGdrShape(item, `field entry "${entry.name}" (doc.refs) item [${i}]`);
|
|
1272
1272
|
}
|
|
1273
1273
|
}
|
|
1274
1274
|
function assertGdrShape(value, context) {
|
|
@@ -1327,12 +1327,12 @@ async function loadContext(client, instanceId, options) {
|
|
|
1327
1327
|
return { client, clientForGdr, clock, now: clock(), instance, definition, snapshot };
|
|
1328
1328
|
}
|
|
1329
1329
|
async function ctxConditionParams(ctx, opts) {
|
|
1330
|
-
const base = buildParams({ instance: ctx.instance, now: ctx.now, snapshot: ctx.snapshot }),
|
|
1331
|
-
...base.
|
|
1332
|
-
...
|
|
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)
|
|
1333
1333
|
}, params = {
|
|
1334
1334
|
...base,
|
|
1335
|
-
|
|
1335
|
+
fields,
|
|
1336
1336
|
actor: opts?.actor,
|
|
1337
1337
|
assigned: opts?.taskName !== void 0 ? assignedFor(ctx.instance, opts.taskName, opts?.actor, ctx.definition.roleAliases) : !1,
|
|
1338
1338
|
...opts?.vars
|
|
@@ -1371,11 +1371,11 @@ function findStage(definition, stageName) {
|
|
|
1371
1371
|
throw new Error(`Stage "${stageName}" not found in definition ${definition.name}`);
|
|
1372
1372
|
return stage;
|
|
1373
1373
|
}
|
|
1374
|
-
async function
|
|
1375
|
-
const { client, instance, stage, now,
|
|
1376
|
-
return
|
|
1377
|
-
entryDefs: stage.
|
|
1378
|
-
|
|
1374
|
+
async function resolveStageFieldEntries(args) {
|
|
1375
|
+
const { client, instance, stage, now, initialFields } = args;
|
|
1376
|
+
return resolveDeclaredFields({
|
|
1377
|
+
entryDefs: stage.fields,
|
|
1378
|
+
initialFields: initialFields ?? [],
|
|
1379
1379
|
ctx: {
|
|
1380
1380
|
client,
|
|
1381
1381
|
now,
|
|
@@ -1383,19 +1383,19 @@ async function resolveStageStateEntries(args) {
|
|
|
1383
1383
|
tag: instance.tag,
|
|
1384
1384
|
stageName: stage.name,
|
|
1385
1385
|
workflowResource: instance.workflowResource,
|
|
1386
|
-
// Allow stage-scope entries' `source: { type: "
|
|
1387
|
-
// "workflow", ... }` to see the already-resolved workflow-scope
|
|
1388
|
-
|
|
1386
|
+
// Allow stage-scope entries' `source: { type: "fieldRead", scope:
|
|
1387
|
+
// "workflow", ... }` to see the already-resolved workflow-scope fields.
|
|
1388
|
+
workflowFields: instance.fields ?? [],
|
|
1389
1389
|
...instance.perspective !== void 0 ? { perspective: instance.perspective } : {}
|
|
1390
1390
|
},
|
|
1391
1391
|
randomKey
|
|
1392
1392
|
});
|
|
1393
1393
|
}
|
|
1394
|
-
async function
|
|
1394
|
+
async function resolveTaskFieldEntries(args) {
|
|
1395
1395
|
const { client, instance, stage, task, now } = args;
|
|
1396
|
-
return
|
|
1397
|
-
entryDefs: task.
|
|
1398
|
-
|
|
1396
|
+
return resolveDeclaredFields({
|
|
1397
|
+
entryDefs: task.fields,
|
|
1398
|
+
initialFields: [],
|
|
1399
1399
|
ctx: {
|
|
1400
1400
|
client,
|
|
1401
1401
|
now,
|
|
@@ -1404,7 +1404,7 @@ async function resolveTaskStateEntries(args) {
|
|
|
1404
1404
|
stageName: stage.name,
|
|
1405
1405
|
workflowResource: instance.workflowResource,
|
|
1406
1406
|
taskName: task.name,
|
|
1407
|
-
|
|
1407
|
+
workflowFields: instance.fields ?? [],
|
|
1408
1408
|
...instance.perspective !== void 0 ? { perspective: instance.perspective } : {}
|
|
1409
1409
|
},
|
|
1410
1410
|
randomKey
|
|
@@ -1486,15 +1486,15 @@ function stageTransitionHistory(args) {
|
|
|
1486
1486
|
function startMutation(instance) {
|
|
1487
1487
|
return {
|
|
1488
1488
|
currentStage: instance.currentStage,
|
|
1489
|
-
// Deep-ish copy. Per-scope
|
|
1489
|
+
// Deep-ish copy. Per-scope field entry arrays need their own copies
|
|
1490
1490
|
// so ops can mutate without leaking into the source.
|
|
1491
|
-
|
|
1491
|
+
fields: (instance.fields ?? []).map((s) => ({ ...s })),
|
|
1492
1492
|
stages: instance.stages.map((s) => ({
|
|
1493
1493
|
...s,
|
|
1494
|
-
|
|
1494
|
+
fields: (s.fields ?? []).map((entry) => ({ ...entry })),
|
|
1495
1495
|
tasks: s.tasks.map((t) => ({
|
|
1496
1496
|
...t,
|
|
1497
|
-
...t.
|
|
1497
|
+
...t.fields !== void 0 ? { fields: t.fields.map((entry) => ({ ...entry })) } : {}
|
|
1498
1498
|
}))
|
|
1499
1499
|
})),
|
|
1500
1500
|
pendingEffects: [...instance.pendingEffects],
|
|
@@ -1510,7 +1510,7 @@ function startMutation(instance) {
|
|
|
1510
1510
|
function instanceStateFields(src) {
|
|
1511
1511
|
const fields = {
|
|
1512
1512
|
currentStage: src.currentStage,
|
|
1513
|
-
|
|
1513
|
+
fields: src.fields,
|
|
1514
1514
|
stages: src.stages,
|
|
1515
1515
|
pendingEffects: src.pendingEffects,
|
|
1516
1516
|
effectHistory: src.effectHistory,
|
|
@@ -1523,7 +1523,7 @@ function materializeInstance(base, mutation) {
|
|
|
1523
1523
|
return {
|
|
1524
1524
|
...base,
|
|
1525
1525
|
currentStage: mutation.currentStage,
|
|
1526
|
-
|
|
1526
|
+
fields: mutation.fields,
|
|
1527
1527
|
stages: mutation.stages,
|
|
1528
1528
|
effectsContext: mutation.effectsContext
|
|
1529
1529
|
};
|
|
@@ -1645,7 +1645,7 @@ function buildInstanceBase(args) {
|
|
|
1645
1645
|
definition: args.definitionName,
|
|
1646
1646
|
pinnedVersion: args.pinnedVersion,
|
|
1647
1647
|
definitionSnapshot: JSON.stringify(args.definition),
|
|
1648
|
-
|
|
1648
|
+
fields: args.fields,
|
|
1649
1649
|
effectsContext: args.effectsContext,
|
|
1650
1650
|
ancestors: args.ancestors,
|
|
1651
1651
|
...perspective !== void 0 ? { perspective } : {},
|
|
@@ -1703,7 +1703,7 @@ async function spawnSubworkflows(ctx, mutation, task, sub, actor) {
|
|
|
1703
1703
|
...actor ? { actor } : {}
|
|
1704
1704
|
}), { rows, discoveryResource } = await resolveSpawnRows(ctx, sub), effectsContext = await resolveContextHandoff(ctx, sub, parentScope), refs = [];
|
|
1705
1705
|
for (const row of rows) {
|
|
1706
|
-
const
|
|
1706
|
+
const initialFields = await projectRowFields(
|
|
1707
1707
|
ctx,
|
|
1708
1708
|
sub,
|
|
1709
1709
|
definition,
|
|
@@ -1714,7 +1714,7 @@ async function spawnSubworkflows(ctx, mutation, task, sub, actor) {
|
|
|
1714
1714
|
client: ctx.client,
|
|
1715
1715
|
parent: ctx.instance,
|
|
1716
1716
|
definition,
|
|
1717
|
-
|
|
1717
|
+
initialFields,
|
|
1718
1718
|
effectsContext,
|
|
1719
1719
|
actor,
|
|
1720
1720
|
now
|
|
@@ -1727,7 +1727,7 @@ async function resolveSpawnRows(ctx, sub) {
|
|
|
1727
1727
|
const params = buildParams({ instance: ctx.instance, now: ctx.now, snapshot: ctx.snapshot }), groq = sub.forEach;
|
|
1728
1728
|
let value, discoveryResource;
|
|
1729
1729
|
if (groq.includes("*")) {
|
|
1730
|
-
const routingUri = entrySubjectRefs(ctx.instance.
|
|
1730
|
+
const routingUri = entrySubjectRefs(ctx.instance.fields)[0]?.id, client = clientForDiscovery(ctx.client, ctx.clientForGdr, routingUri);
|
|
1731
1731
|
client !== ctx.client && routingUri !== void 0 && (discoveryResource = resourceFromGdrUri(routingUri)), value = await discoverItems({
|
|
1732
1732
|
client,
|
|
1733
1733
|
groq,
|
|
@@ -1743,13 +1743,13 @@ async function resolveSpawnRows(ctx, sub) {
|
|
|
1743
1743
|
}
|
|
1744
1744
|
return Array.isArray(value) ? { rows: value, discoveryResource } : value == null ? { rows: [], discoveryResource } : { rows: [value], discoveryResource };
|
|
1745
1745
|
}
|
|
1746
|
-
async function
|
|
1746
|
+
async function projectRowFields(ctx, sub, childDefinition, parentScope, row, discoveryResource) {
|
|
1747
1747
|
const out = [];
|
|
1748
1748
|
for (const [name, groq] of Object.entries(sub.with ?? {})) {
|
|
1749
|
-
const declared = (childDefinition.
|
|
1749
|
+
const declared = (childDefinition.fields ?? []).find((e) => e.name === name);
|
|
1750
1750
|
if (declared === void 0)
|
|
1751
1751
|
throw new Error(
|
|
1752
|
-
`subworkflows.with["${name}"]: subworkflow "${childDefinition.name}" declares no
|
|
1752
|
+
`subworkflows.with["${name}"]: subworkflow "${childDefinition.name}" declares no field entry "${name}"`
|
|
1753
1753
|
);
|
|
1754
1754
|
const raw = await runGroq(groq, { ...parentScope, row }, ctx.snapshot), value = canonicaliseSpawnValue(declared.type, raw, {
|
|
1755
1755
|
parentResource: ctx.instance.workflowResource,
|
|
@@ -1804,15 +1804,15 @@ async function resolveDefinitionRef(client, ref, tag) {
|
|
|
1804
1804
|
) ?? null;
|
|
1805
1805
|
}
|
|
1806
1806
|
async function prepareChildInstance(args) {
|
|
1807
|
-
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 = [
|
|
1808
1808
|
...parent.ancestors,
|
|
1809
1809
|
{
|
|
1810
1810
|
id: selfGdr(parent),
|
|
1811
1811
|
type: WORKFLOW_INSTANCE_TYPE
|
|
1812
1812
|
}
|
|
1813
|
-
], inheritedPerspective = parent.perspective,
|
|
1814
|
-
entryDefs: definition.
|
|
1815
|
-
|
|
1813
|
+
], inheritedPerspective = parent.perspective, childFields = await resolveDeclaredFields({
|
|
1814
|
+
entryDefs: definition.fields,
|
|
1815
|
+
initialFields,
|
|
1816
1816
|
ctx: {
|
|
1817
1817
|
client,
|
|
1818
1818
|
now,
|
|
@@ -1823,7 +1823,7 @@ async function prepareChildInstance(args) {
|
|
|
1823
1823
|
...inheritedPerspective !== void 0 ? { perspective: inheritedPerspective } : {}
|
|
1824
1824
|
},
|
|
1825
1825
|
randomKey
|
|
1826
|
-
}), childPerspective =
|
|
1826
|
+
}), childPerspective = derivePerspectiveFromFields(childFields) ?? inheritedPerspective, effectsContextEntries = Object.entries(effectsContext).map(
|
|
1827
1827
|
([key, value]) => {
|
|
1828
1828
|
if (typeof value == "object" && value !== null && "id" in value && "type" in value) {
|
|
1829
1829
|
if (!isGdrUri(value.id))
|
|
@@ -1842,7 +1842,7 @@ async function prepareChildInstance(args) {
|
|
|
1842
1842
|
definitionName: definition.name,
|
|
1843
1843
|
pinnedVersion: definition.version,
|
|
1844
1844
|
definition,
|
|
1845
|
-
|
|
1845
|
+
fields: childFields,
|
|
1846
1846
|
effectsContext: effectsContextEntries,
|
|
1847
1847
|
ancestors,
|
|
1848
1848
|
perspective: childPerspective,
|
|
@@ -2007,15 +2007,15 @@ function resolveStaticSource(src, ctx) {
|
|
|
2007
2007
|
return { handled: !1 };
|
|
2008
2008
|
}
|
|
2009
2009
|
}
|
|
2010
|
-
const
|
|
2011
|
-
"
|
|
2012
|
-
"
|
|
2013
|
-
"
|
|
2014
|
-
"
|
|
2015
|
-
"
|
|
2010
|
+
const FIELD_OP_TYPES = [
|
|
2011
|
+
"field.set",
|
|
2012
|
+
"field.unset",
|
|
2013
|
+
"field.append",
|
|
2014
|
+
"field.updateWhere",
|
|
2015
|
+
"field.removeWhere"
|
|
2016
2016
|
];
|
|
2017
|
-
function
|
|
2018
|
-
return
|
|
2017
|
+
function isFieldOp(summary) {
|
|
2018
|
+
return FIELD_OP_TYPES.includes(summary.opType);
|
|
2019
2019
|
}
|
|
2020
2020
|
function validateActionParams(action, taskName, callerParams) {
|
|
2021
2021
|
const declared = action.params ?? [], params = callerParams ?? {}, issues = [];
|
|
@@ -2088,23 +2088,23 @@ function opAppliedEntry(args) {
|
|
|
2088
2088
|
}
|
|
2089
2089
|
function applyOp(op, ctx) {
|
|
2090
2090
|
switch (op.type) {
|
|
2091
|
-
case "
|
|
2092
|
-
return
|
|
2093
|
-
case "
|
|
2094
|
-
return
|
|
2095
|
-
case "
|
|
2096
|
-
return
|
|
2097
|
-
case "
|
|
2098
|
-
return
|
|
2099
|
-
case "
|
|
2100
|
-
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);
|
|
2101
2101
|
case "status.set":
|
|
2102
2102
|
return applyStatusSet(op, ctx);
|
|
2103
2103
|
}
|
|
2104
2104
|
}
|
|
2105
|
-
function
|
|
2105
|
+
function applyFieldSet(op, ctx) {
|
|
2106
2106
|
const value = resolveOpSource(op.value, ctx), entry = locateEntry(ctx, op.target);
|
|
2107
|
-
return
|
|
2107
|
+
return validateFieldValue({ entryType: entry._type, entryName: entry.name, value }), setEntryValue(entry, value), { opType: op.type, target: op.target, resolved: { value } };
|
|
2108
2108
|
}
|
|
2109
2109
|
const EMPTY_BY_KIND = {
|
|
2110
2110
|
"doc.refs": [],
|
|
@@ -2112,24 +2112,24 @@ const EMPTY_BY_KIND = {
|
|
|
2112
2112
|
notes: [],
|
|
2113
2113
|
assignees: []
|
|
2114
2114
|
};
|
|
2115
|
-
function
|
|
2115
|
+
function applyFieldUnset(op, ctx) {
|
|
2116
2116
|
const entry = locateEntry(ctx, op.target);
|
|
2117
2117
|
return setEntryValue(entry, EMPTY_BY_KIND[entry._type] ?? null), { opType: op.type, target: op.target };
|
|
2118
2118
|
}
|
|
2119
|
-
function
|
|
2119
|
+
function applyFieldAppend(op, ctx) {
|
|
2120
2120
|
const item = resolveOpSource(op.value, ctx), entry = locateEntry(ctx, op.target);
|
|
2121
|
-
|
|
2121
|
+
validateFieldAppendItem({ entryType: entry._type, entryName: entry.name, item });
|
|
2122
2122
|
const current = Array.isArray(entry.value) ? entry.value : [];
|
|
2123
2123
|
return setEntryValue(entry, [...current, withRowKey(item)]), { opType: op.type, target: op.target, resolved: { item } };
|
|
2124
2124
|
}
|
|
2125
2125
|
function withRowKey(item) {
|
|
2126
2126
|
return typeof item == "object" && item !== null && !Array.isArray(item) && !("_key" in item) ? { _key: randomKey(), ...item } : item;
|
|
2127
2127
|
}
|
|
2128
|
-
function
|
|
2128
|
+
function applyFieldUpdateWhere(op, ctx) {
|
|
2129
2129
|
const entry = locateEntry(ctx, op.target), rows = requireArrayValue(entry, op), merge = resolveOpSource(op.value, ctx);
|
|
2130
2130
|
if (merge === null || typeof merge != "object" || Array.isArray(merge))
|
|
2131
2131
|
throw new Error(
|
|
2132
|
-
`
|
|
2132
|
+
`field.updateWhere value must resolve to an object of fields to merge (target "${op.target.field}")`
|
|
2133
2133
|
);
|
|
2134
2134
|
return setEntryValue(
|
|
2135
2135
|
entry,
|
|
@@ -2138,7 +2138,7 @@ function applyStateUpdateWhere(op, ctx) {
|
|
|
2138
2138
|
)
|
|
2139
2139
|
), { opType: op.type, target: op.target, resolved: { merge } };
|
|
2140
2140
|
}
|
|
2141
|
-
function
|
|
2141
|
+
function applyFieldRemoveWhere(op, ctx) {
|
|
2142
2142
|
const entry = locateEntry(ctx, op.target), rows = requireArrayValue(entry, op);
|
|
2143
2143
|
return setEntryValue(
|
|
2144
2144
|
entry,
|
|
@@ -2148,7 +2148,7 @@ function applyStateRemoveWhere(op, ctx) {
|
|
|
2148
2148
|
function requireArrayValue(entry, op) {
|
|
2149
2149
|
if (!Array.isArray(entry.value))
|
|
2150
2150
|
throw new Error(
|
|
2151
|
-
`${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`
|
|
2152
2152
|
);
|
|
2153
2153
|
return entry.value;
|
|
2154
2154
|
}
|
|
@@ -2169,19 +2169,19 @@ function setEntryValue(entry, value) {
|
|
|
2169
2169
|
entry.value = value;
|
|
2170
2170
|
}
|
|
2171
2171
|
function locateEntry(ctx, target) {
|
|
2172
|
-
const entry =
|
|
2172
|
+
const entry = fieldHost(ctx, target.scope)?.find((s) => s.name === target.field);
|
|
2173
2173
|
if (entry === void 0)
|
|
2174
2174
|
throw new Error(
|
|
2175
|
-
`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`
|
|
2176
2176
|
);
|
|
2177
2177
|
return entry;
|
|
2178
2178
|
}
|
|
2179
|
-
function
|
|
2180
|
-
if (scope === "workflow") return ctx.mutation.
|
|
2179
|
+
function fieldHost(ctx, scope) {
|
|
2180
|
+
if (scope === "workflow") return ctx.mutation.fields;
|
|
2181
2181
|
const stageEntry = findOpenStageEntry(ctx.mutation);
|
|
2182
|
-
if (scope === "stage") return stageEntry?.
|
|
2182
|
+
if (scope === "stage") return stageEntry?.fields;
|
|
2183
2183
|
const task = stageEntry?.tasks.find((t) => t.name === ctx.taskName);
|
|
2184
|
-
return task !== void 0 && task.
|
|
2184
|
+
return task !== void 0 && task.fields === void 0 && (task.fields = []), task?.fields;
|
|
2185
2185
|
}
|
|
2186
2186
|
function resolveOpSource(src, ctx) {
|
|
2187
2187
|
const staticValue = resolveStaticSource(src, ctx);
|
|
@@ -2191,7 +2191,7 @@ function resolveOpSource(src, ctx) {
|
|
|
2191
2191
|
return ctx.self;
|
|
2192
2192
|
case "stage":
|
|
2193
2193
|
return ctx.stage;
|
|
2194
|
-
case "
|
|
2194
|
+
case "fieldRead": {
|
|
2195
2195
|
const value = readEntryFromMutation(ctx, src);
|
|
2196
2196
|
return src.path !== void 0 ? getPath(value, src.path) : value;
|
|
2197
2197
|
}
|
|
@@ -2202,7 +2202,7 @@ function resolveOpSource(src, ctx) {
|
|
|
2202
2202
|
return out;
|
|
2203
2203
|
}
|
|
2204
2204
|
default:
|
|
2205
|
-
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`);
|
|
2206
2206
|
}
|
|
2207
2207
|
}
|
|
2208
2208
|
function entryValue(entries, name) {
|
|
@@ -2211,7 +2211,7 @@ function entryValue(entries, name) {
|
|
|
2211
2211
|
function readEntryFromMutation(ctx, src) {
|
|
2212
2212
|
const scopes = src.scope !== void 0 ? [src.scope] : ["stage", "workflow"], stageEntry = findOpenStageEntry(ctx.mutation);
|
|
2213
2213
|
for (const scope of scopes) {
|
|
2214
|
-
const host = scope === "workflow" ? ctx.mutation.
|
|
2214
|
+
const host = scope === "workflow" ? ctx.mutation.fields : stageEntry?.fields, value = entryValue(host, src.field);
|
|
2215
2215
|
if (value !== void 0) return value;
|
|
2216
2216
|
}
|
|
2217
2217
|
}
|
|
@@ -2243,9 +2243,9 @@ async function commitInvoke(ctx, taskName, actor) {
|
|
|
2243
2243
|
}
|
|
2244
2244
|
async function activateTask(ctx, mutation, task, entry, actor) {
|
|
2245
2245
|
const now = ctx.now;
|
|
2246
|
-
if (entry.status = "active", entry.startedAt = now, task.
|
|
2246
|
+
if (entry.status = "active", entry.startedAt = now, task.fields !== void 0 && task.fields.length > 0) {
|
|
2247
2247
|
const stage = findStage(ctx.definition, mutation.currentStage);
|
|
2248
|
-
entry.
|
|
2248
|
+
entry.fields = await resolveTaskFieldEntries({
|
|
2249
2249
|
client: ctx.client,
|
|
2250
2250
|
instance: ctx.instance,
|
|
2251
2251
|
stage,
|
|
@@ -2350,7 +2350,7 @@ async function enterStage(ctx, mutation, nextStage, actor, at) {
|
|
|
2350
2350
|
_key: randomKey(),
|
|
2351
2351
|
name: nextStage.name,
|
|
2352
2352
|
enteredAt: at,
|
|
2353
|
-
|
|
2353
|
+
fields: await resolveStageFieldEntries({
|
|
2354
2354
|
client: ctx.client,
|
|
2355
2355
|
instance: ctx.instance,
|
|
2356
2356
|
stage: nextStage,
|
|
@@ -2490,7 +2490,7 @@ async function primeInitialStage(client, instanceId, actor, clientForGdr, clock)
|
|
|
2490
2490
|
_key: randomKey(),
|
|
2491
2491
|
name: stage.name,
|
|
2492
2492
|
enteredAt: now,
|
|
2493
|
-
|
|
2493
|
+
fields: await resolveStageFieldEntries({ client, instance, stage, now }),
|
|
2494
2494
|
tasks: await buildStageTasks(ctx, stage)
|
|
2495
2495
|
}, committed = await client.patch(instance._id).set({
|
|
2496
2496
|
stages: [initialStageEntry],
|
|
@@ -2728,10 +2728,10 @@ function effectiveEditable(baseline, override) {
|
|
|
2728
2728
|
}
|
|
2729
2729
|
function slotSites(definition, stage) {
|
|
2730
2730
|
const sites = [];
|
|
2731
|
-
for (const entry of definition.
|
|
2732
|
-
for (const entry of stage.
|
|
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
2733
|
for (const task of stage.tasks ?? [])
|
|
2734
|
-
for (const entry of task.
|
|
2734
|
+
for (const entry of task.fields ?? []) sites.push({ scope: "task", task: task.name, entry });
|
|
2735
2735
|
return sites;
|
|
2736
2736
|
}
|
|
2737
2737
|
function toResolvedSlot(site, stage) {
|
|
@@ -2741,7 +2741,7 @@ function toResolvedSlot(site, stage) {
|
|
|
2741
2741
|
name: site.entry.name,
|
|
2742
2742
|
type: site.entry.type,
|
|
2743
2743
|
...site.entry.title !== void 0 ? { title: site.entry.title } : {},
|
|
2744
|
-
ref: { scope: site.scope,
|
|
2744
|
+
ref: { scope: site.scope, field: site.entry.name },
|
|
2745
2745
|
effective: effectiveEditable(site.entry.editable, stage.editable?.[site.entry.name])
|
|
2746
2746
|
};
|
|
2747
2747
|
}
|
|
@@ -2749,7 +2749,7 @@ function editableSlotsInStage(definition, stage) {
|
|
|
2749
2749
|
return slotSites(definition, stage).filter((site) => site.entry.editable !== void 0).map((site) => toResolvedSlot(site, stage));
|
|
2750
2750
|
}
|
|
2751
2751
|
function editTargetMatches(slot, target) {
|
|
2752
|
-
return slot.name !== 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
2753
|
}
|
|
2754
2754
|
const SCOPE_PRECEDENCE = { task: 0, stage: 1, workflow: 2 };
|
|
2755
2755
|
function resolveEditTarget(args) {
|
|
@@ -2773,17 +2773,17 @@ function slotWindowOpen(instance, slot) {
|
|
|
2773
2773
|
return status === "active" ? { open: !0 } : { open: !1, detail: `task "${slot.task}" is ${status ?? "not active"}` };
|
|
2774
2774
|
}
|
|
2775
2775
|
function readSlotValue(instance, slot) {
|
|
2776
|
-
return
|
|
2776
|
+
return slotFieldHost(instance, slot)?.find((e) => e.name === slot.name)?.value;
|
|
2777
2777
|
}
|
|
2778
|
-
function
|
|
2779
|
-
if (slot.scope === "workflow") return instance.
|
|
2778
|
+
function slotFieldHost(instance, slot) {
|
|
2779
|
+
if (slot.scope === "workflow") return instance.fields;
|
|
2780
2780
|
const stageEntry = findOpenStageEntry(instance);
|
|
2781
|
-
return slot.scope === "stage" ? stageEntry?.
|
|
2781
|
+
return slot.scope === "stage" ? stageEntry?.fields : stageEntry?.tasks.find((t) => t.name === slot.task)?.fields;
|
|
2782
2782
|
}
|
|
2783
2783
|
function slotProvenance(instance, ref) {
|
|
2784
2784
|
let latest;
|
|
2785
2785
|
for (const entry of instance.history)
|
|
2786
|
-
entry._type === "opApplied" && (entry.target?.scope !== ref.scope || entry.target.
|
|
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
2787
|
return latest === void 0 ? {} : {
|
|
2788
2788
|
...latest.actor !== void 0 ? { setBy: latest.actor } : {},
|
|
2789
2789
|
setAt: latest.at
|
|
@@ -2941,9 +2941,9 @@ function taskScopeFor(args) {
|
|
|
2941
2941
|
const { scope, instance, snapshot, actor, taskName, roleAliases } = args;
|
|
2942
2942
|
return {
|
|
2943
2943
|
...scope,
|
|
2944
|
-
|
|
2945
|
-
...scope.
|
|
2946
|
-
...
|
|
2944
|
+
fields: {
|
|
2945
|
+
...scope.fields,
|
|
2946
|
+
...scopedFieldOverlay(instance, snapshot, taskName)
|
|
2947
2947
|
},
|
|
2948
2948
|
assigned: assignedFor(instance, taskName, actor, roleAliases)
|
|
2949
2949
|
};
|
|
@@ -2989,8 +2989,8 @@ async function evaluateTask(args) {
|
|
|
2989
2989
|
status,
|
|
2990
2990
|
// "pending on actor" treats both `pending` and `active` as waiting on
|
|
2991
2991
|
// the assignee — pending tasks just haven't been activated yet, but
|
|
2992
|
-
// they're still inbox items. The inbox reads the assignees-kind
|
|
2993
|
-
// 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).
|
|
2994
2994
|
pendingOnActor: (status === "active" || status === "pending") && assigned,
|
|
2995
2995
|
...unmetRequirements.length > 0 ? { unmetRequirements } : {},
|
|
2996
2996
|
actions
|
|
@@ -3259,7 +3259,7 @@ async function commitAction(ctx, taskName, actionName, callerParams, options) {
|
|
|
3259
3259
|
}), await persistThenDeploy(
|
|
3260
3260
|
ctx,
|
|
3261
3261
|
mutation,
|
|
3262
|
-
() => ranOps.some(
|
|
3262
|
+
() => ranOps.some(isFieldOp) ? refreshStageGuards(ctx, mutation, stage.name) : Promise.resolve()
|
|
3263
3263
|
), {
|
|
3264
3264
|
fired: !0,
|
|
3265
3265
|
task: taskName,
|
|
@@ -3288,11 +3288,11 @@ function formatDisabledReason(task, action, reason) {
|
|
|
3288
3288
|
const detail = disabledReasonDetail[reason.kind](reason);
|
|
3289
3289
|
return `Action "${task}:${action}" is not allowed: ${detail}`;
|
|
3290
3290
|
}
|
|
3291
|
-
class
|
|
3291
|
+
class EditFieldDeniedError extends Error {
|
|
3292
3292
|
reason;
|
|
3293
3293
|
target;
|
|
3294
3294
|
constructor(args) {
|
|
3295
|
-
super(formatEditDisabledReason(args.target, args.reason)), this.name = "
|
|
3295
|
+
super(formatEditDisabledReason(args.target, args.reason)), this.name = "EditFieldDeniedError", this.reason = args.reason, this.target = args.target;
|
|
3296
3296
|
}
|
|
3297
3297
|
}
|
|
3298
3298
|
const editDisabledReasonDetail = {
|
|
@@ -3305,10 +3305,10 @@ const editDisabledReasonDetail = {
|
|
|
3305
3305
|
function formatEditDisabledReason(target, reason) {
|
|
3306
3306
|
const detail = editDisabledReasonDetail[reason.kind](
|
|
3307
3307
|
reason
|
|
3308
|
-
), where = target.task !== void 0 ? `${target.task}.${target.
|
|
3309
|
-
return `
|
|
3308
|
+
), where = target.task !== void 0 ? `${target.task}.${target.field}` : target.field;
|
|
3309
|
+
return `Field "${target.scope}:${where}" is not editable: ${detail}`;
|
|
3310
3310
|
}
|
|
3311
|
-
async function
|
|
3311
|
+
async function editField(args) {
|
|
3312
3312
|
const { client, instanceId, target, mode = "set", value, options } = args;
|
|
3313
3313
|
for (let attempt = 1; attempt <= CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS; attempt++) {
|
|
3314
3314
|
const ctx = await loadCallContext(client, instanceId, options);
|
|
@@ -3318,7 +3318,7 @@ async function editState(args) {
|
|
|
3318
3318
|
if (!isRevisionConflict(error)) throw error;
|
|
3319
3319
|
}
|
|
3320
3320
|
}
|
|
3321
|
-
throw new
|
|
3321
|
+
throw new ConcurrentEditFieldError({
|
|
3322
3322
|
instanceId,
|
|
3323
3323
|
target: labelTarget(target),
|
|
3324
3324
|
attempts: CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS
|
|
@@ -3328,7 +3328,7 @@ async function commitEdit(ctx, target, mode, value, options) {
|
|
|
3328
3328
|
const actor = options?.actor, stage = findStage(ctx.definition, ctx.instance.currentStage), slot = resolveEditTarget({ definition: ctx.definition, stage, target });
|
|
3329
3329
|
if (slot === void 0)
|
|
3330
3330
|
throw new Error(
|
|
3331
|
-
`No
|
|
3331
|
+
`No field slot "${describeTarget(target)}" resolves in current stage "${stage.name}" of ${ctx.definition.name}`
|
|
3332
3332
|
);
|
|
3333
3333
|
await assertSlotEditable(ctx, slot, options);
|
|
3334
3334
|
const op = buildEditOp(slot, mode, value), mutation = startMutation(ctx.instance), ranOps = runOps({
|
|
@@ -3347,7 +3347,7 @@ async function commitEdit(ctx, target, mode, value, options) {
|
|
|
3347
3347
|
return await persistThenDeploy(
|
|
3348
3348
|
ctx,
|
|
3349
3349
|
mutation,
|
|
3350
|
-
() => ranOps.some(
|
|
3350
|
+
() => ranOps.some(isFieldOp) ? refreshStageGuards(ctx, mutation, stage.name) : Promise.resolve()
|
|
3351
3351
|
), {
|
|
3352
3352
|
edited: !0,
|
|
3353
3353
|
target: slotTarget(slot),
|
|
@@ -3366,14 +3366,14 @@ async function assertSlotEditable(ctx, slot, options) {
|
|
|
3366
3366
|
guardDenial: void 0,
|
|
3367
3367
|
predicateSatisfied
|
|
3368
3368
|
});
|
|
3369
|
-
if (reason !== void 0) throw new
|
|
3369
|
+
if (reason !== void 0) throw new EditFieldDeniedError({ target: slotTarget(slot), reason });
|
|
3370
3370
|
}
|
|
3371
3371
|
function buildEditOp(slot, mode, value) {
|
|
3372
|
-
if (mode === "unset") return { type: "
|
|
3372
|
+
if (mode === "unset") return { type: "field.unset", target: slot.ref };
|
|
3373
3373
|
if (value === void 0)
|
|
3374
|
-
throw new Error(`
|
|
3374
|
+
throw new Error(`editField mode "${mode}" requires a value for slot "${slot.name}"`);
|
|
3375
3375
|
return {
|
|
3376
|
-
type: mode === "append" ? "
|
|
3376
|
+
type: mode === "append" ? "field.append" : "field.set",
|
|
3377
3377
|
target: slot.ref,
|
|
3378
3378
|
value: { type: "literal", value }
|
|
3379
3379
|
};
|
|
@@ -3381,19 +3381,19 @@ function buildEditOp(slot, mode, value) {
|
|
|
3381
3381
|
function slotTarget(slot) {
|
|
3382
3382
|
return {
|
|
3383
3383
|
scope: slot.scope,
|
|
3384
|
-
|
|
3384
|
+
field: slot.name,
|
|
3385
3385
|
...slot.task !== void 0 ? { task: slot.task } : {}
|
|
3386
3386
|
};
|
|
3387
3387
|
}
|
|
3388
3388
|
function labelTarget(target) {
|
|
3389
3389
|
return {
|
|
3390
3390
|
scope: target.scope ?? (target.task !== void 0 ? "task" : "workflow"),
|
|
3391
|
-
|
|
3391
|
+
field: target.field,
|
|
3392
3392
|
...target.task !== void 0 ? { task: target.task } : {}
|
|
3393
3393
|
};
|
|
3394
3394
|
}
|
|
3395
3395
|
function describeTarget(target) {
|
|
3396
|
-
const where = target.task !== void 0 ? `${target.task}.${target.
|
|
3396
|
+
const where = target.task !== void 0 ? `${target.task}.${target.field}` : target.field;
|
|
3397
3397
|
return target.scope !== void 0 ? `${target.scope}:${where}` : where;
|
|
3398
3398
|
}
|
|
3399
3399
|
function bareIdFromSpawnRef(uri) {
|
|
@@ -3466,10 +3466,10 @@ function assertActionAllowed(evaluation, task, action) {
|
|
|
3466
3466
|
function assertEditAllowed(evaluation, target) {
|
|
3467
3467
|
const slot = evaluation.editableSlots.filter((s) => editTargetMatches(s, target)).sort((a, b) => SCOPE_PRECEDENCE[a.scope] - SCOPE_PRECEDENCE[b.scope])[0];
|
|
3468
3468
|
if (slot !== void 0 && !slot.editable && slot.disabledReason !== void 0)
|
|
3469
|
-
throw new
|
|
3469
|
+
throw new EditFieldDeniedError({
|
|
3470
3470
|
target: {
|
|
3471
3471
|
scope: slot.scope,
|
|
3472
|
-
|
|
3472
|
+
field: slot.name,
|
|
3473
3473
|
...slot.task !== void 0 ? { task: slot.task } : {}
|
|
3474
3474
|
},
|
|
3475
3475
|
reason: slot.disabledReason
|
|
@@ -3477,7 +3477,7 @@ function assertEditAllowed(evaluation, target) {
|
|
|
3477
3477
|
}
|
|
3478
3478
|
async function applyEdit(args) {
|
|
3479
3479
|
const { client, instance, target, mode, value, actor, grants, clock, clientForGdr } = args;
|
|
3480
|
-
return (await
|
|
3480
|
+
return (await editField({
|
|
3481
3481
|
client,
|
|
3482
3482
|
instanceId: instance._id,
|
|
3483
3483
|
target,
|
|
@@ -3650,7 +3650,7 @@ const workflow = {
|
|
|
3650
3650
|
workflowResource,
|
|
3651
3651
|
definition: definitionName,
|
|
3652
3652
|
version,
|
|
3653
|
-
|
|
3653
|
+
initialFields,
|
|
3654
3654
|
ancestors,
|
|
3655
3655
|
effectsContext,
|
|
3656
3656
|
instanceId,
|
|
@@ -3660,9 +3660,9 @@ const workflow = {
|
|
|
3660
3660
|
throw new Error(
|
|
3661
3661
|
`Initial stage "${definition.initialStage}" missing in ${definitionName} v${definition.version}`
|
|
3662
3662
|
);
|
|
3663
|
-
const now = clock(), effectsContextEntries = effectsContext ? toEffectsContextEntries(effectsContext) : [],
|
|
3664
|
-
entryDefs: definition.
|
|
3665
|
-
|
|
3663
|
+
const now = clock(), effectsContextEntries = effectsContext ? toEffectsContextEntries(effectsContext) : [], resolvedFields = await resolveDeclaredFields({
|
|
3664
|
+
entryDefs: definition.fields ?? [],
|
|
3665
|
+
initialFields: initialFields ?? [],
|
|
3666
3666
|
ctx: {
|
|
3667
3667
|
client,
|
|
3668
3668
|
now,
|
|
@@ -3673,7 +3673,7 @@ const workflow = {
|
|
|
3673
3673
|
...perspective !== void 0 ? { perspective } : {}
|
|
3674
3674
|
},
|
|
3675
3675
|
randomKey
|
|
3676
|
-
}), effectivePerspective = perspective ??
|
|
3676
|
+
}), effectivePerspective = perspective ?? derivePerspectiveFromFields(resolvedFields), base = buildInstanceBase({
|
|
3677
3677
|
id,
|
|
3678
3678
|
now,
|
|
3679
3679
|
tag,
|
|
@@ -3681,7 +3681,7 @@ const workflow = {
|
|
|
3681
3681
|
definitionName: definition.name,
|
|
3682
3682
|
pinnedVersion: definition.version,
|
|
3683
3683
|
definition,
|
|
3684
|
-
|
|
3684
|
+
fields: resolvedFields,
|
|
3685
3685
|
effectsContext: effectsContextEntries,
|
|
3686
3686
|
ancestors: ancestors ?? [],
|
|
3687
3687
|
perspective: effectivePerspective,
|
|
@@ -3735,11 +3735,11 @@ const workflow = {
|
|
|
3735
3735
|
});
|
|
3736
3736
|
},
|
|
3737
3737
|
/**
|
|
3738
|
-
* Edit a declared-editable
|
|
3738
|
+
* Edit a declared-editable field slot directly — reassign, reschedule,
|
|
3739
3739
|
* claim-by-hand, append to a running log — through the generic edit seam,
|
|
3740
3740
|
* instead of a bespoke action per field. Soft-gates on the slot's declared
|
|
3741
3741
|
* editability (the same projection a UI renders), applies the edit as a
|
|
3742
|
-
* `
|
|
3742
|
+
* `field.*` op (so provenance + history are stamped by the op path),
|
|
3743
3743
|
* refreshes the stage's guards, then cascades — an edit to a value a
|
|
3744
3744
|
* transition reads can and should move the instance. Advisory like every
|
|
3745
3745
|
* engine gate; the lake ACL + guards are the only hard locks.
|
|
@@ -3749,7 +3749,7 @@ const workflow = {
|
|
|
3749
3749
|
* UI must bind it to a deliberate boundary (blur / Enter / Save / debounce),
|
|
3750
3750
|
* never an `onChange` per keystroke.
|
|
3751
3751
|
*/
|
|
3752
|
-
|
|
3752
|
+
editField: async (args) => {
|
|
3753
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
3754
|
return dispatchGatedWrite({
|
|
3755
3755
|
client,
|
|
@@ -3914,7 +3914,7 @@ const workflow = {
|
|
|
3914
3914
|
* Hydrates the instance's snapshot (instance + ancestors + every doc
|
|
3915
3915
|
* declared by a `doc.ref` / `doc.refs` entry in scope), then
|
|
3916
3916
|
* evaluates the supplied GROQ in groq-js against that dataset. The
|
|
3917
|
-
* rendered scope is auto-bound: `$self`, `$
|
|
3917
|
+
* rendered scope is auto-bound: `$self`, `$fields`, `$parent`,
|
|
3918
3918
|
* `$ancestors`, `$stage`, `$now`, `$effects`, `$tasks`,
|
|
3919
3919
|
* `$allTasksDone`, `$anyTaskFailed` — ids in GDR URI form to match
|
|
3920
3920
|
* the snapshot's keying.
|
|
@@ -4247,7 +4247,7 @@ function createInstanceSession(args) {
|
|
|
4247
4247
|
return settleAfterApply(actor, held, ranOps);
|
|
4248
4248
|
});
|
|
4249
4249
|
},
|
|
4250
|
-
|
|
4250
|
+
editField({ target, mode, value }) {
|
|
4251
4251
|
return commit(async (actor, held) => {
|
|
4252
4252
|
assertEditAllowed(await evaluateWith(held, heldGuards), target);
|
|
4253
4253
|
const { grants } = await access(), ranOps = await applyEdit({
|
|
@@ -4309,7 +4309,7 @@ function createEngine(args) {
|
|
|
4309
4309
|
deployDefinitions: (rest) => workflow.deployDefinitions(bind(rest)),
|
|
4310
4310
|
startInstance: (rest) => workflow.startInstance(bind(rest)),
|
|
4311
4311
|
fireAction: (rest) => workflow.fireAction(bind(rest)),
|
|
4312
|
-
|
|
4312
|
+
editField: (rest) => workflow.editField(bind(rest)),
|
|
4313
4313
|
completeEffect: (rest) => workflow.completeEffect(bind(rest)),
|
|
4314
4314
|
tick: (rest) => workflow.tick(bind(rest)),
|
|
4315
4315
|
evaluateInstance: (rest) => evaluateInstance(bind(rest)),
|
|
@@ -4468,9 +4468,9 @@ const HISTORY_DISPLAY = {
|
|
|
4468
4468
|
},
|
|
4469
4469
|
opApplied: {
|
|
4470
4470
|
title: "Op applied",
|
|
4471
|
-
description: "An inline
|
|
4471
|
+
description: "An inline field-mutation op (field.set / field.append / status.set / etc.) ran during an action commit."
|
|
4472
4472
|
}
|
|
4473
|
-
},
|
|
4473
|
+
}, FIELD_SLOT_DISPLAY = {
|
|
4474
4474
|
"doc.ref": {
|
|
4475
4475
|
title: "Document reference",
|
|
4476
4476
|
description: "Single GDR pointer at a document in this or another resource."
|
|
@@ -4524,23 +4524,23 @@ const HISTORY_DISPLAY = {
|
|
|
4524
4524
|
description: "Ordered list of typed (user|role) assignees."
|
|
4525
4525
|
}
|
|
4526
4526
|
}, OP_DISPLAY = {
|
|
4527
|
-
"
|
|
4528
|
-
title: "Set
|
|
4529
|
-
description: "Overwrite a
|
|
4527
|
+
"field.set": {
|
|
4528
|
+
title: "Set field entry",
|
|
4529
|
+
description: "Overwrite a field entry's value with a resolved Source."
|
|
4530
4530
|
},
|
|
4531
|
-
"
|
|
4532
|
-
title: "Unset
|
|
4533
|
-
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)."
|
|
4534
4534
|
},
|
|
4535
|
-
"
|
|
4536
|
-
title: "Append to
|
|
4535
|
+
"field.append": {
|
|
4536
|
+
title: "Append to field entry",
|
|
4537
4537
|
description: "Push a resolved item onto an array-kind entry (notes, checklist, assignees, refs)."
|
|
4538
4538
|
},
|
|
4539
|
-
"
|
|
4539
|
+
"field.updateWhere": {
|
|
4540
4540
|
title: "Update matching rows",
|
|
4541
4541
|
description: "Merge fields into rows of an array-kind entry that match the predicate."
|
|
4542
4542
|
},
|
|
4543
|
-
"
|
|
4543
|
+
"field.removeWhere": {
|
|
4544
4544
|
title: "Remove matching rows",
|
|
4545
4545
|
description: "Drop rows of an array-kind entry that match the predicate."
|
|
4546
4546
|
},
|
|
@@ -4584,11 +4584,11 @@ const HISTORY_DISPLAY = {
|
|
|
4584
4584
|
},
|
|
4585
4585
|
[WORKFLOW_INSTANCE_TYPE]: {
|
|
4586
4586
|
title: "Workflow instance",
|
|
4587
|
-
description: "A running (or finished) workflow against its declared
|
|
4587
|
+
description: "A running (or finished) workflow against its declared fields."
|
|
4588
4588
|
}
|
|
4589
4589
|
}, DISPLAY = {
|
|
4590
4590
|
...HISTORY_DISPLAY,
|
|
4591
|
-
...
|
|
4591
|
+
...FIELD_SLOT_DISPLAY,
|
|
4592
4592
|
...OP_DISPLAY,
|
|
4593
4593
|
...EFFECTS_CONTEXT_DISPLAY,
|
|
4594
4594
|
...AUTHORING_DISPLAY
|
|
@@ -4606,12 +4606,13 @@ exports.AUTHORING_DISPLAY = AUTHORING_DISPLAY;
|
|
|
4606
4606
|
exports.ActionDisabledError = ActionDisabledError;
|
|
4607
4607
|
exports.ActionParamsInvalidError = ActionParamsInvalidError;
|
|
4608
4608
|
exports.CascadeLimitError = CascadeLimitError;
|
|
4609
|
-
exports.
|
|
4609
|
+
exports.ConcurrentEditFieldError = ConcurrentEditFieldError;
|
|
4610
4610
|
exports.ConcurrentFireActionError = ConcurrentFireActionError;
|
|
4611
4611
|
exports.DEFAULT_CONTENT_PERSPECTIVE = DEFAULT_CONTENT_PERSPECTIVE;
|
|
4612
4612
|
exports.DISPLAY = DISPLAY;
|
|
4613
4613
|
exports.EFFECTS_CONTEXT_DISPLAY = EFFECTS_CONTEXT_DISPLAY;
|
|
4614
|
-
exports.
|
|
4614
|
+
exports.EditFieldDeniedError = EditFieldDeniedError;
|
|
4615
|
+
exports.FIELD_SLOT_DISPLAY = FIELD_SLOT_DISPLAY;
|
|
4615
4616
|
exports.GUARD_DOC_TYPE = GUARD_DOC_TYPE;
|
|
4616
4617
|
exports.GUARD_LIFTED_PREDICATE = GUARD_LIFTED_PREDICATE;
|
|
4617
4618
|
exports.GUARD_OWNER = GUARD_OWNER;
|
|
@@ -4619,8 +4620,7 @@ exports.HISTORY_DISPLAY = HISTORY_DISPLAY;
|
|
|
4619
4620
|
exports.MutationGuardDeniedError = MutationGuardDeniedError;
|
|
4620
4621
|
exports.OP_DISPLAY = OP_DISPLAY;
|
|
4621
4622
|
exports.PartialGuardDeployError = PartialGuardDeployError;
|
|
4622
|
-
exports.
|
|
4623
|
-
exports.STATE_SLOT_DISPLAY = STATE_SLOT_DISPLAY;
|
|
4623
|
+
exports.RequiredFieldNotProvidedError = RequiredFieldNotProvidedError;
|
|
4624
4624
|
exports.WORKFLOW_INSTANCE_TYPE = WORKFLOW_INSTANCE_TYPE;
|
|
4625
4625
|
exports.WorkflowStateDivergedError = WorkflowStateDivergedError;
|
|
4626
4626
|
exports.abortReason = abortReason;
|