@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.js
CHANGED
|
@@ -111,7 +111,7 @@ function buildParams(args) {
|
|
|
111
111
|
const { instance, now, snapshot, extra } = args, currentTasks2 = findOpenStageEntry(instance)?.tasks ?? [];
|
|
112
112
|
return {
|
|
113
113
|
self: selfGdr(instance),
|
|
114
|
-
|
|
114
|
+
fields: renderedFields(instance.fields ?? [], snapshot),
|
|
115
115
|
parent: instance.ancestors.at(-1)?.id ?? null,
|
|
116
116
|
ancestors: instance.ancestors.map((a) => a.id),
|
|
117
117
|
stage: instance.currentStage,
|
|
@@ -126,7 +126,7 @@ function buildParams(args) {
|
|
|
126
126
|
...extra
|
|
127
127
|
};
|
|
128
128
|
}
|
|
129
|
-
function
|
|
129
|
+
function renderedFields(entries, snapshot) {
|
|
130
130
|
const out = {};
|
|
131
131
|
for (const entry of entries) out[entry.name] = renderedValue(entry, snapshot);
|
|
132
132
|
return out;
|
|
@@ -134,15 +134,15 @@ function renderedState(entries, snapshot) {
|
|
|
134
134
|
function renderedValue(entry, snapshot) {
|
|
135
135
|
return entry._type !== "doc.ref" || entry.value === null || snapshot === void 0 ? entry.value : snapshot.docs.find((d) => d._id === entry.value.id) ?? entry.value;
|
|
136
136
|
}
|
|
137
|
-
function
|
|
137
|
+
function scopedFieldOverlay(instance, snapshot, taskName) {
|
|
138
138
|
const stageEntry = findOpenStageEntry(instance);
|
|
139
139
|
if (stageEntry === void 0) return {};
|
|
140
|
-
const
|
|
141
|
-
return { ...
|
|
140
|
+
const stageFields = renderedFields(stageEntry.fields ?? [], snapshot), task = taskName ? stageEntry.tasks.find((t) => t.name === taskName) : void 0;
|
|
141
|
+
return { ...stageFields, ...renderedFields(task?.fields ?? [], snapshot) };
|
|
142
142
|
}
|
|
143
143
|
function assignedFor(instance, taskName, actor, roleAliases) {
|
|
144
144
|
if (actor === void 0) return !1;
|
|
145
|
-
const entry = findOpenStageEntry(instance)?.tasks.find((t) => t.name === taskName)?.
|
|
145
|
+
const entry = findOpenStageEntry(instance)?.tasks.find((t) => t.name === taskName)?.fields?.find((s) => s._type === "assignees");
|
|
146
146
|
return entry === void 0 ? !1 : entry.value.some(
|
|
147
147
|
(a) => a.type === "user" ? a.id === actor.id : actorFulfillsRole(actor.roles, a.role, roleAliases)
|
|
148
148
|
);
|
|
@@ -169,17 +169,17 @@ function paramsForLake(params) {
|
|
|
169
169
|
self: typeof params.self == "string" ? bareId(params.self) : params.self,
|
|
170
170
|
parent: typeof params.parent == "string" ? bareId(params.parent) : params.parent,
|
|
171
171
|
ancestors: Array.isArray(params.ancestors) ? params.ancestors.map((a) => typeof a == "string" ? bareId(a) : a) : params.ancestors,
|
|
172
|
-
|
|
172
|
+
fields: stripFieldsForLake(params.fields)
|
|
173
173
|
};
|
|
174
174
|
}
|
|
175
|
-
function
|
|
175
|
+
function stripFieldsForLake(value) {
|
|
176
176
|
if (value == null) return value;
|
|
177
177
|
if (typeof value == "string") return bareId(value);
|
|
178
|
-
if (Array.isArray(value)) return value.map(
|
|
178
|
+
if (Array.isArray(value)) return value.map(stripFieldsForLake);
|
|
179
179
|
if (typeof value == "object") {
|
|
180
180
|
const out = {};
|
|
181
181
|
for (const [k, v2] of Object.entries(value))
|
|
182
|
-
out[k] =
|
|
182
|
+
out[k] = stripFieldsForLake(v2);
|
|
183
183
|
return out;
|
|
184
184
|
}
|
|
185
185
|
return value;
|
|
@@ -198,17 +198,17 @@ function getPath(value, path) {
|
|
|
198
198
|
function resourceOf(p) {
|
|
199
199
|
return p.scheme === "dataset" ? { type: "dataset", id: `${p.projectId}.${p.dataset}` } : { type: p.scheme, id: p.resourceId ?? "" };
|
|
200
200
|
}
|
|
201
|
-
const
|
|
201
|
+
const FIELD_READ = /^\$fields\.(\w+)(?:\.(.+))?$/, EFFECTS_READ = /^\$effects\['([^']+)'\](?:\.(.+))?$/;
|
|
202
202
|
function isGuardReadExpr(expr) {
|
|
203
|
-
return expr === "$self" || expr === "$now" ||
|
|
203
|
+
return expr === "$self" || expr === "$now" || FIELD_READ.test(expr) || EFFECTS_READ.test(expr);
|
|
204
204
|
}
|
|
205
205
|
function resolveGuardRead(expr, ctx) {
|
|
206
206
|
if (expr === "$self") return selfGdr(ctx.instance);
|
|
207
207
|
if (expr === "$now") return ctx.now;
|
|
208
|
-
const
|
|
209
|
-
if (
|
|
210
|
-
const value = (ctx.instance.
|
|
211
|
-
return
|
|
208
|
+
const fieldRead = FIELD_READ.exec(expr);
|
|
209
|
+
if (fieldRead !== null) {
|
|
210
|
+
const value = (ctx.instance.fields ?? []).find((s) => s.name === fieldRead[1])?.value;
|
|
211
|
+
return fieldRead[2] !== void 0 ? getPath(value, fieldRead[2]) : value;
|
|
212
212
|
}
|
|
213
213
|
const effectsRead = EFFECTS_READ.exec(expr);
|
|
214
214
|
if (effectsRead !== null) {
|
|
@@ -216,7 +216,7 @@ function resolveGuardRead(expr, ctx) {
|
|
|
216
216
|
return effectsRead[2] !== void 0 ? getPath(outputs, effectsRead[2]) : outputs;
|
|
217
217
|
}
|
|
218
218
|
throw new Error(
|
|
219
|
-
`Guard read "${expr}" is not a supported deploy-time value \u2014 use "$self", "$now", "$
|
|
219
|
+
`Guard read "${expr}" is not a supported deploy-time value \u2014 use "$self", "$now", "$fields.<name>[.path]", or "$effects['<name>'][.path]" (guards store resolved values; the lake cannot see $fields or $effects)`
|
|
220
220
|
);
|
|
221
221
|
}
|
|
222
222
|
function resolveIdRefTarget(expr, ctx) {
|
|
@@ -266,8 +266,8 @@ function resolveMetadata(metadata, ctx) {
|
|
|
266
266
|
}
|
|
267
267
|
function validateDefinition(definition) {
|
|
268
268
|
const v2 = createDefinitionValidator();
|
|
269
|
-
for (const entry of definition.
|
|
270
|
-
v2.checkEntry(entry, `workflow.
|
|
269
|
+
for (const entry of definition.fields ?? [])
|
|
270
|
+
v2.checkEntry(entry, `workflow.fields "${entry.name}"`);
|
|
271
271
|
for (const [name, groq] of Object.entries(definition.predicates ?? {}))
|
|
272
272
|
v2.checkCondition(groq, `predicate "${name}"`);
|
|
273
273
|
for (const stage of definition.stages)
|
|
@@ -289,7 +289,7 @@ function createDefinitionValidator() {
|
|
|
289
289
|
}
|
|
290
290
|
}, rejectTypeScan = (groq, where) => {
|
|
291
291
|
/\*\s*\[\s*_type\b/.test(groq) && errors.push(
|
|
292
|
-
` \xB7 ${where}: condition scans by \`_type\` \u2014 that's a discovery query, not a predicate. Conditions evaluate against the in-memory snapshot (instance + ancestors +
|
|
292
|
+
` \xB7 ${where}: condition scans by \`_type\` \u2014 that's a discovery query, not a predicate. Conditions evaluate against the in-memory snapshot (instance + ancestors + field-declared docs). To bring extra docs in scope, declare a \`doc.ref\` (or \`doc.refs\`) field entry on the workflow or this stage. For lake scans like "all articles in this release", use \`subworkflows.forEach\` instead.`
|
|
293
293
|
);
|
|
294
294
|
};
|
|
295
295
|
return { errors, tryParse, checkCondition: (groq, where) => {
|
|
@@ -298,13 +298,13 @@ function createDefinitionValidator() {
|
|
|
298
298
|
entry.source.type === "query" && tryParse(entry.source.query, `${label}.query`);
|
|
299
299
|
}, checkGuardRead: (expr, where) => {
|
|
300
300
|
isGuardReadExpr(expr) || errors.push(
|
|
301
|
-
` \xB7 ${where}: guard read "${expr}" is not a supported deploy-time value \u2014 use "$self", "$now", "$
|
|
301
|
+
` \xB7 ${where}: guard read "${expr}" is not a supported deploy-time value \u2014 use "$self", "$now", "$fields.<name>[.path]", or "$effects['<name>'][.path]" (guards store resolved values; the lake cannot see $fields or $effects)`
|
|
302
302
|
);
|
|
303
303
|
} };
|
|
304
304
|
}
|
|
305
305
|
function validateStage(v2, stage) {
|
|
306
|
-
for (const entry of stage.
|
|
307
|
-
v2.checkEntry(entry, `stage "${stage.name}".
|
|
306
|
+
for (const entry of stage.fields ?? [])
|
|
307
|
+
v2.checkEntry(entry, `stage "${stage.name}".fields "${entry.name}"`);
|
|
308
308
|
for (const guard of stage.guards ?? [])
|
|
309
309
|
validateGuardReads(v2, stage.name, guard);
|
|
310
310
|
for (const t of stage.transitions ?? []) {
|
|
@@ -324,8 +324,8 @@ function validateGuardReads(v2, stageName, guard) {
|
|
|
324
324
|
}
|
|
325
325
|
function validateTask(v2, stageName, task) {
|
|
326
326
|
const where = `stage "${stageName}" task "${task.name}"`;
|
|
327
|
-
for (const entry of task.
|
|
328
|
-
v2.checkEntry(entry, `${where}.
|
|
327
|
+
for (const entry of task.fields ?? [])
|
|
328
|
+
v2.checkEntry(entry, `${where}.fields "${entry.name}"`);
|
|
329
329
|
task.filter !== void 0 && v2.checkCondition(task.filter, `${where}.filter`), task.completeWhen !== void 0 && v2.checkCondition(task.completeWhen, `${where}.completeWhen`), task.failWhen !== void 0 && v2.checkCondition(task.failWhen, `${where}.failWhen`);
|
|
330
330
|
for (const [name, groq] of Object.entries(task.requirements ?? {}))
|
|
331
331
|
v2.checkCondition(groq, `${where}.requirements "${name}"`);
|
|
@@ -497,7 +497,7 @@ function openStage(instance) {
|
|
|
497
497
|
return findOpenStageEntry(instance);
|
|
498
498
|
}
|
|
499
499
|
function assigneesOf(stage, taskName) {
|
|
500
|
-
return (stage?.tasks.find((t) => t.name === taskName)?.
|
|
500
|
+
return (stage?.tasks.find((t) => t.name === taskName)?.fields ?? []).filter((s) => s._type === "assignees").flatMap((s) => s.value);
|
|
501
501
|
}
|
|
502
502
|
function isResolved(status) {
|
|
503
503
|
return status === "done" || status === "skipped";
|
|
@@ -610,17 +610,17 @@ class ActionParamsInvalidError extends Error {
|
|
|
610
610
|
${lines}`), this.name = "ActionParamsInvalidError", this.action = args.action, this.task = args.task, this.issues = args.issues;
|
|
611
611
|
}
|
|
612
612
|
}
|
|
613
|
-
class
|
|
613
|
+
class RequiredFieldNotProvidedError extends Error {
|
|
614
614
|
definition;
|
|
615
615
|
missing;
|
|
616
616
|
constructor(args) {
|
|
617
617
|
const lines = args.missing.map((m) => ` - ${m.name} (${m.type})`).join(`
|
|
618
618
|
`), where = args.definition !== void 0 ? ` for workflow "${args.definition}"` : "";
|
|
619
619
|
super(
|
|
620
|
-
`Required init
|
|
620
|
+
`Required init fields${where} was not provided:
|
|
621
621
|
${lines}
|
|
622
|
-
Provide each via \`
|
|
623
|
-
), this.name = "
|
|
622
|
+
Provide each via \`initialFields\` when starting standalone, or via the parent's \`subworkflows.with\` when spawned.`
|
|
623
|
+
), this.name = "RequiredFieldNotProvidedError", args.definition !== void 0 && (this.definition = args.definition), this.missing = args.missing;
|
|
624
624
|
}
|
|
625
625
|
}
|
|
626
626
|
class WorkflowStateDivergedError extends Error {
|
|
@@ -663,15 +663,15 @@ function isRevisionConflict(error) {
|
|
|
663
663
|
const { statusCode, message } = error;
|
|
664
664
|
return statusCode === 409 ? !0 : typeof message == "string" && message.includes("ifRevisionId check failed");
|
|
665
665
|
}
|
|
666
|
-
class
|
|
666
|
+
class ConcurrentEditFieldError extends Error {
|
|
667
667
|
instanceId;
|
|
668
668
|
target;
|
|
669
669
|
attempts;
|
|
670
670
|
constructor(args) {
|
|
671
|
-
const where = args.target.task !== void 0 ? `${args.target.task}.${args.target.
|
|
671
|
+
const where = args.target.task !== void 0 ? `${args.target.task}.${args.target.field}` : args.target.field;
|
|
672
672
|
super(
|
|
673
673
|
`Edit of "${args.target.scope}:${where}" (instance ${args.instanceId}) lost the optimistic-locking race ${args.attempts} attempts running \u2014 a concurrent writer kept committing first. Re-read and retry, or investigate a write storm on this instance.`
|
|
674
|
-
), this.name = "
|
|
674
|
+
), this.name = "ConcurrentEditFieldError", this.instanceId = args.instanceId, this.target = args.target, this.attempts = args.attempts;
|
|
675
675
|
}
|
|
676
676
|
}
|
|
677
677
|
class CascadeLimitError extends Error {
|
|
@@ -719,10 +719,10 @@ function collectWatchRefs(instance) {
|
|
|
719
719
|
return [
|
|
720
720
|
gdrRef(instance.workflowResource, instance._id, instance._type),
|
|
721
721
|
...instance.ancestors,
|
|
722
|
-
...entryDocRefs(instance.
|
|
723
|
-
...entryDocRefs(stage?.
|
|
724
|
-
...entryReleaseRefs(instance.
|
|
725
|
-
...entryReleaseRefs(stage?.
|
|
722
|
+
...entryDocRefs(instance.fields),
|
|
723
|
+
...entryDocRefs(stage?.fields),
|
|
724
|
+
...entryReleaseRefs(instance.fields),
|
|
725
|
+
...entryReleaseRefs(stage?.fields)
|
|
726
726
|
];
|
|
727
727
|
}
|
|
728
728
|
function readsRaw(ref) {
|
|
@@ -746,21 +746,21 @@ function subscriptionDocumentsForInstance(instance) {
|
|
|
746
746
|
...instance.perspective !== void 0 ? { perspective: instance.perspective } : {}
|
|
747
747
|
};
|
|
748
748
|
}
|
|
749
|
-
function entryDocRefs(
|
|
750
|
-
return
|
|
749
|
+
function entryDocRefs(entries) {
|
|
750
|
+
return fieldEntries(entries).flatMap((entry) => entry._type === "doc.ref" ? isGdr(entry.value) ? [entry.value] : [] : entry._type === "doc.refs" ? Array.isArray(entry.value) ? entry.value.filter(isGdr) : [] : []);
|
|
751
751
|
}
|
|
752
|
-
function entryReleaseRefs(
|
|
753
|
-
return
|
|
752
|
+
function entryReleaseRefs(entries) {
|
|
753
|
+
return fieldEntries(entries).flatMap(
|
|
754
754
|
(entry) => entry._type === "release.ref" && isGdr(entry.value) ? [entry.value] : []
|
|
755
755
|
);
|
|
756
756
|
}
|
|
757
|
-
function entrySubjectRefs(
|
|
758
|
-
return
|
|
757
|
+
function entrySubjectRefs(entries) {
|
|
758
|
+
return fieldEntries(entries).flatMap(
|
|
759
759
|
(entry) => (entry._type === "doc.ref" || entry._type === "release.ref") && isGdr(entry.value) ? [entry.value] : []
|
|
760
760
|
);
|
|
761
761
|
}
|
|
762
|
-
function
|
|
763
|
-
return Array.isArray(
|
|
762
|
+
function fieldEntries(entries) {
|
|
763
|
+
return Array.isArray(entries) ? entries.filter(
|
|
764
764
|
(entry) => !!entry && typeof entry == "object"
|
|
765
765
|
) : [];
|
|
766
766
|
}
|
|
@@ -806,8 +806,8 @@ async function readDoc(client, id, perspective) {
|
|
|
806
806
|
function resourceFromParsed(parsed) {
|
|
807
807
|
return parsed.scheme === "dataset" ? { type: "dataset", id: `${parsed.projectId}.${parsed.dataset}` } : { type: parsed.scheme, id: parsed.resourceId ?? "" };
|
|
808
808
|
}
|
|
809
|
-
function collectEntryDocUris(
|
|
810
|
-
return entryDocRefs(
|
|
809
|
+
function collectEntryDocUris(resolvedFieldEntries) {
|
|
810
|
+
return entryDocRefs(resolvedFieldEntries).map((ref) => ref.id);
|
|
811
811
|
}
|
|
812
812
|
const resourceKey = (r) => `${r.type}.${r.id}`;
|
|
813
813
|
function guardsForResource(client) {
|
|
@@ -825,8 +825,8 @@ function verdictGuardsForInstance(client, instanceId) {
|
|
|
825
825
|
}
|
|
826
826
|
async function guardsForInstance(args) {
|
|
827
827
|
const { client, clientForGdr, instance } = args, stateUris = [
|
|
828
|
-
...collectEntryDocUris(instance.
|
|
829
|
-
...instance.stages.flatMap((stage) => collectEntryDocUris(stage.
|
|
828
|
+
...collectEntryDocUris(instance.fields),
|
|
829
|
+
...instance.stages.flatMap((stage) => collectEntryDocUris(stage.fields))
|
|
830
830
|
], clients = resourceClientMap(
|
|
831
831
|
instance.workflowResource,
|
|
832
832
|
client,
|
|
@@ -858,16 +858,16 @@ function guardIdRefs(definition) {
|
|
|
858
858
|
);
|
|
859
859
|
}
|
|
860
860
|
function staticIdRefGdr(idRef, stage, definition) {
|
|
861
|
-
const read = /^\$
|
|
861
|
+
const read = /^\$fields\.([\w-]+)/.exec(idRef);
|
|
862
862
|
if (read === null) return;
|
|
863
863
|
const source = entriesInScope(stage, definition).find((e) => e.name === read[1])?.source;
|
|
864
864
|
return source?.type === "literal" ? gdrFromValue(source.value) : void 0;
|
|
865
865
|
}
|
|
866
866
|
function entriesInScope(stage, definition) {
|
|
867
867
|
return [
|
|
868
|
-
...definition.
|
|
869
|
-
...stage.
|
|
870
|
-
...(stage.tasks ?? []).flatMap((task) => task.
|
|
868
|
+
...definition.fields ?? [],
|
|
869
|
+
...stage.fields ?? [],
|
|
870
|
+
...(stage.tasks ?? []).flatMap((task) => task.fields ?? [])
|
|
871
871
|
];
|
|
872
872
|
}
|
|
873
873
|
function gdrFromValue(value) {
|
|
@@ -1017,15 +1017,15 @@ async function runGroq(groq, params, snapshot) {
|
|
|
1017
1017
|
const tree = parse(groq, { params });
|
|
1018
1018
|
return (await evaluate(tree, { dataset: snapshot.docs, params })).get();
|
|
1019
1019
|
}
|
|
1020
|
-
class
|
|
1020
|
+
class FieldValueShapeError extends Error {
|
|
1021
1021
|
entryType;
|
|
1022
1022
|
entryName;
|
|
1023
1023
|
issues;
|
|
1024
1024
|
constructor(args) {
|
|
1025
1025
|
const issueText = args.issues.join("; ");
|
|
1026
1026
|
super(
|
|
1027
|
-
`
|
|
1028
|
-
), this.name = "
|
|
1027
|
+
`Field entry ${args.mode} shape invalid for "${args.entryName}" (${args.entryType}): ${issueText}`
|
|
1028
|
+
), this.name = "FieldValueShapeError", this.entryType = args.entryType, this.entryName = args.entryName, this.issues = args.issues;
|
|
1029
1029
|
}
|
|
1030
1030
|
}
|
|
1031
1031
|
const GdrShape = v.looseObject({
|
|
@@ -1092,35 +1092,35 @@ const GdrShape = v.looseObject({
|
|
|
1092
1092
|
function isAppendable(entryType) {
|
|
1093
1093
|
return entryType in itemSchemas;
|
|
1094
1094
|
}
|
|
1095
|
-
function
|
|
1095
|
+
function validateFieldValue(args) {
|
|
1096
1096
|
const schema = valueSchemas[args.entryType];
|
|
1097
1097
|
if (schema === void 0)
|
|
1098
|
-
throw new
|
|
1098
|
+
throw new FieldValueShapeError({
|
|
1099
1099
|
entryType: args.entryType,
|
|
1100
1100
|
entryName: args.entryName,
|
|
1101
|
-
issues: [`unknown
|
|
1101
|
+
issues: [`unknown field entry type ${args.entryType}`],
|
|
1102
1102
|
mode: "value"
|
|
1103
1103
|
});
|
|
1104
1104
|
const result = v.safeParse(schema, args.value);
|
|
1105
1105
|
if (!result.success)
|
|
1106
|
-
throw new
|
|
1106
|
+
throw new FieldValueShapeError({
|
|
1107
1107
|
entryType: args.entryType,
|
|
1108
1108
|
entryName: args.entryName,
|
|
1109
1109
|
issues: formatIssues(result.issues),
|
|
1110
1110
|
mode: "value"
|
|
1111
1111
|
});
|
|
1112
1112
|
}
|
|
1113
|
-
function
|
|
1113
|
+
function validateFieldAppendItem(args) {
|
|
1114
1114
|
if (!isAppendable(args.entryType))
|
|
1115
|
-
throw new
|
|
1115
|
+
throw new FieldValueShapeError({
|
|
1116
1116
|
entryType: args.entryType,
|
|
1117
1117
|
entryName: args.entryName,
|
|
1118
|
-
issues: [`
|
|
1118
|
+
issues: [`field entry type ${args.entryType} does not support append`],
|
|
1119
1119
|
mode: "item"
|
|
1120
1120
|
});
|
|
1121
1121
|
const schema = itemSchemas[args.entryType], result = v.safeParse(schema, args.item);
|
|
1122
1122
|
if (!result.success)
|
|
1123
|
-
throw new
|
|
1123
|
+
throw new FieldValueShapeError({
|
|
1124
1124
|
entryType: args.entryType,
|
|
1125
1125
|
entryName: args.entryName,
|
|
1126
1126
|
issues: formatIssues(result.issues),
|
|
@@ -1133,7 +1133,7 @@ function formatIssues(issues) {
|
|
|
1133
1133
|
return `${keys.length > 0 ? `at ${keys.join(".")}: ` : ""}${i.message}`;
|
|
1134
1134
|
});
|
|
1135
1135
|
}
|
|
1136
|
-
function
|
|
1136
|
+
function derivePerspectiveFromFields(entries) {
|
|
1137
1137
|
if (entries !== void 0) {
|
|
1138
1138
|
for (const entry of entries)
|
|
1139
1139
|
if (entry._type === "release.ref" && entry.value !== null) {
|
|
@@ -1143,24 +1143,24 @@ function derivePerspectiveFromState(entries) {
|
|
|
1143
1143
|
}
|
|
1144
1144
|
}
|
|
1145
1145
|
}
|
|
1146
|
-
async function
|
|
1147
|
-
const { entryDefs,
|
|
1146
|
+
async function resolveDeclaredFields(args) {
|
|
1147
|
+
const { entryDefs, initialFields, ctx, randomKey: randomKey2 } = args;
|
|
1148
1148
|
if (entryDefs === void 0 || entryDefs.length === 0) return [];
|
|
1149
|
-
assertRequiredInitProvided(entryDefs,
|
|
1149
|
+
assertRequiredInitProvided(entryDefs, initialFields, ctx.definitionName);
|
|
1150
1150
|
const out = [];
|
|
1151
1151
|
for (const entry of entryDefs) {
|
|
1152
|
-
const scopedCtx = { ...ctx,
|
|
1153
|
-
out.push(await resolveOneEntry(entry,
|
|
1152
|
+
const scopedCtx = { ...ctx, resolvedFields: out };
|
|
1153
|
+
out.push(await resolveOneEntry(entry, initialFields, scopedCtx, randomKey2));
|
|
1154
1154
|
}
|
|
1155
1155
|
return out;
|
|
1156
1156
|
}
|
|
1157
|
-
function assertRequiredInitProvided(entryDefs,
|
|
1157
|
+
function assertRequiredInitProvided(entryDefs, initialFields, definitionName) {
|
|
1158
1158
|
const missing = entryDefs.filter((entry) => entry.required === !0 && entry.source.type === "init").filter((entry) => {
|
|
1159
|
-
const match =
|
|
1159
|
+
const match = initialFields.find((s) => s.name === entry.name && s.type === entry.type);
|
|
1160
1160
|
return match === void 0 || match.value === void 0 || match.value === null;
|
|
1161
1161
|
}).map((entry) => ({ name: entry.name, type: entry.type }));
|
|
1162
1162
|
if (missing.length !== 0)
|
|
1163
|
-
throw new
|
|
1163
|
+
throw new RequiredFieldNotProvidedError({
|
|
1164
1164
|
...definitionName !== void 0 ? { definition: definitionName } : {},
|
|
1165
1165
|
missing
|
|
1166
1166
|
});
|
|
@@ -1174,18 +1174,18 @@ const ARRAY_SLOT_TYPES = /* @__PURE__ */ new Set([
|
|
|
1174
1174
|
function defaultEntryValue(entryType) {
|
|
1175
1175
|
return ARRAY_SLOT_TYPES.has(entryType) ? [] : null;
|
|
1176
1176
|
}
|
|
1177
|
-
function resolveInitValue(entry,
|
|
1178
|
-
const initMatch =
|
|
1177
|
+
function resolveInitValue(entry, initialFields, defaultValue) {
|
|
1178
|
+
const initMatch = initialFields.find((s) => s.name === entry.name && s.type === entry.type);
|
|
1179
1179
|
return initMatch === void 0 ? defaultValue : (assertInitValueShape(entry, initMatch.value), initMatch.value);
|
|
1180
1180
|
}
|
|
1181
|
-
function
|
|
1182
|
-
const target = (source.scope === "workflow" ? ctx.
|
|
1181
|
+
function resolveFieldReadValue(source, ctx, defaultValue) {
|
|
1182
|
+
const target = (source.scope === "workflow" ? ctx.workflowFields ?? [] : ctx.resolvedFields ?? []).find((s) => s.name === source.field), targetValue = target === void 0 ? void 0 : target.value;
|
|
1183
1183
|
return (source.path !== void 0 ? getPath(targetValue, source.path) : targetValue) ?? defaultValue;
|
|
1184
1184
|
}
|
|
1185
1185
|
async function resolveQueryValue(entry, source, ctx, client, defaultValue) {
|
|
1186
1186
|
const params = paramsForLake({
|
|
1187
1187
|
self: ctx.selfId ?? null,
|
|
1188
|
-
|
|
1188
|
+
fields: fieldMapFromResolved(ctx.resolvedFields ?? []),
|
|
1189
1189
|
stage: ctx.stageName ?? null,
|
|
1190
1190
|
task: ctx.taskName ?? null,
|
|
1191
1191
|
now: ctx.now,
|
|
@@ -1201,9 +1201,9 @@ async function resolveQueryValue(entry, source, ctx, client, defaultValue) {
|
|
|
1201
1201
|
);
|
|
1202
1202
|
}
|
|
1203
1203
|
}
|
|
1204
|
-
async function resolveEntryValue(entry,
|
|
1204
|
+
async function resolveEntryValue(entry, initialFields, ctx, defaultValue) {
|
|
1205
1205
|
const source = entry.source;
|
|
1206
|
-
return source.type === "init" ? resolveInitValue(entry,
|
|
1206
|
+
return source.type === "init" ? resolveInitValue(entry, initialFields, defaultValue) : source.type === "literal" ? source.value ?? defaultValue : source.type === "fieldRead" ? resolveFieldReadValue(source, ctx, defaultValue) : source.type === "query" && ctx.client !== void 0 ? resolveQueryValue(entry, source, ctx, ctx.client, defaultValue) : defaultValue;
|
|
1207
1207
|
}
|
|
1208
1208
|
function buildResolvedEntry(entry, value, _key, now) {
|
|
1209
1209
|
const titleProp = entry.title !== void 0 ? { title: entry.title } : {}, descriptionProp = entry.description !== void 0 ? { description: entry.description } : {};
|
|
@@ -1224,36 +1224,36 @@ function buildResolvedEntry(entry, value, _key, now) {
|
|
|
1224
1224
|
value
|
|
1225
1225
|
};
|
|
1226
1226
|
}
|
|
1227
|
-
async function resolveOneEntry(entry,
|
|
1228
|
-
const defaultValue = defaultEntryValue(entry.type), value = await resolveEntryValue(entry,
|
|
1229
|
-
return
|
|
1227
|
+
async function resolveOneEntry(entry, initialFields, ctx, randomKey2) {
|
|
1228
|
+
const defaultValue = defaultEntryValue(entry.type), value = await resolveEntryValue(entry, initialFields, ctx, defaultValue);
|
|
1229
|
+
return validateFieldValue({ entryType: entry.type, entryName: entry.name, value }), buildResolvedEntry(entry, value, randomKey2(), ctx.now);
|
|
1230
1230
|
}
|
|
1231
|
-
function
|
|
1231
|
+
function fieldMapFromResolved(entries) {
|
|
1232
1232
|
const out = {};
|
|
1233
1233
|
for (const s of entries) out[s.name] = s.value;
|
|
1234
1234
|
return out;
|
|
1235
1235
|
}
|
|
1236
1236
|
function assertInitValueShape(entry, value) {
|
|
1237
1237
|
if (entry.type === "doc.ref") {
|
|
1238
|
-
assertGdrShape(value, `
|
|
1238
|
+
assertGdrShape(value, `field entry "${entry.name}" (doc.ref)`);
|
|
1239
1239
|
return;
|
|
1240
1240
|
}
|
|
1241
1241
|
if (entry.type === "release.ref") {
|
|
1242
|
-
assertGdrShape(value, `
|
|
1242
|
+
assertGdrShape(value, `field entry "${entry.name}" (release.ref)`);
|
|
1243
1243
|
const v2 = value;
|
|
1244
1244
|
if (typeof v2.releaseName != "string" || v2.releaseName.length === 0)
|
|
1245
1245
|
throw new Error(
|
|
1246
|
-
`Invalid init value for
|
|
1246
|
+
`Invalid init value for field entry "${entry.name}" (release.ref): \`releaseName\` must be a non-empty string. Got ${JSON.stringify(v2.releaseName)}.`
|
|
1247
1247
|
);
|
|
1248
1248
|
return;
|
|
1249
1249
|
}
|
|
1250
1250
|
if (entry.type === "doc.refs") {
|
|
1251
1251
|
if (!Array.isArray(value))
|
|
1252
1252
|
throw new Error(
|
|
1253
|
-
`Invalid init value for
|
|
1253
|
+
`Invalid init value for field entry "${entry.name}" (doc.refs): expected an array of GDRs, got ${typeof value}.`
|
|
1254
1254
|
);
|
|
1255
1255
|
for (const [i, item] of value.entries())
|
|
1256
|
-
assertGdrShape(item, `
|
|
1256
|
+
assertGdrShape(item, `field entry "${entry.name}" (doc.refs) item [${i}]`);
|
|
1257
1257
|
}
|
|
1258
1258
|
}
|
|
1259
1259
|
function assertGdrShape(value, context) {
|
|
@@ -1312,12 +1312,12 @@ async function loadContext(client, instanceId, options) {
|
|
|
1312
1312
|
return { client, clientForGdr, clock, now: clock(), instance, definition, snapshot };
|
|
1313
1313
|
}
|
|
1314
1314
|
async function ctxConditionParams(ctx, opts) {
|
|
1315
|
-
const base = buildParams({ instance: ctx.instance, now: ctx.now, snapshot: ctx.snapshot }),
|
|
1316
|
-
...base.
|
|
1317
|
-
...
|
|
1315
|
+
const base = buildParams({ instance: ctx.instance, now: ctx.now, snapshot: ctx.snapshot }), fields = {
|
|
1316
|
+
...base.fields,
|
|
1317
|
+
...scopedFieldOverlay(ctx.instance, ctx.snapshot, opts?.taskName)
|
|
1318
1318
|
}, params = {
|
|
1319
1319
|
...base,
|
|
1320
|
-
|
|
1320
|
+
fields,
|
|
1321
1321
|
actor: opts?.actor,
|
|
1322
1322
|
assigned: opts?.taskName !== void 0 ? assignedFor(ctx.instance, opts.taskName, opts?.actor, ctx.definition.roleAliases) : !1,
|
|
1323
1323
|
...opts?.vars
|
|
@@ -1356,11 +1356,11 @@ function findStage(definition, stageName) {
|
|
|
1356
1356
|
throw new Error(`Stage "${stageName}" not found in definition ${definition.name}`);
|
|
1357
1357
|
return stage;
|
|
1358
1358
|
}
|
|
1359
|
-
async function
|
|
1360
|
-
const { client, instance, stage, now,
|
|
1361
|
-
return
|
|
1362
|
-
entryDefs: stage.
|
|
1363
|
-
|
|
1359
|
+
async function resolveStageFieldEntries(args) {
|
|
1360
|
+
const { client, instance, stage, now, initialFields } = args;
|
|
1361
|
+
return resolveDeclaredFields({
|
|
1362
|
+
entryDefs: stage.fields,
|
|
1363
|
+
initialFields: initialFields ?? [],
|
|
1364
1364
|
ctx: {
|
|
1365
1365
|
client,
|
|
1366
1366
|
now,
|
|
@@ -1368,19 +1368,19 @@ async function resolveStageStateEntries(args) {
|
|
|
1368
1368
|
tag: instance.tag,
|
|
1369
1369
|
stageName: stage.name,
|
|
1370
1370
|
workflowResource: instance.workflowResource,
|
|
1371
|
-
// Allow stage-scope entries' `source: { type: "
|
|
1372
|
-
// "workflow", ... }` to see the already-resolved workflow-scope
|
|
1373
|
-
|
|
1371
|
+
// Allow stage-scope entries' `source: { type: "fieldRead", scope:
|
|
1372
|
+
// "workflow", ... }` to see the already-resolved workflow-scope fields.
|
|
1373
|
+
workflowFields: instance.fields ?? [],
|
|
1374
1374
|
...instance.perspective !== void 0 ? { perspective: instance.perspective } : {}
|
|
1375
1375
|
},
|
|
1376
1376
|
randomKey
|
|
1377
1377
|
});
|
|
1378
1378
|
}
|
|
1379
|
-
async function
|
|
1379
|
+
async function resolveTaskFieldEntries(args) {
|
|
1380
1380
|
const { client, instance, stage, task, now } = args;
|
|
1381
|
-
return
|
|
1382
|
-
entryDefs: task.
|
|
1383
|
-
|
|
1381
|
+
return resolveDeclaredFields({
|
|
1382
|
+
entryDefs: task.fields,
|
|
1383
|
+
initialFields: [],
|
|
1384
1384
|
ctx: {
|
|
1385
1385
|
client,
|
|
1386
1386
|
now,
|
|
@@ -1389,7 +1389,7 @@ async function resolveTaskStateEntries(args) {
|
|
|
1389
1389
|
stageName: stage.name,
|
|
1390
1390
|
workflowResource: instance.workflowResource,
|
|
1391
1391
|
taskName: task.name,
|
|
1392
|
-
|
|
1392
|
+
workflowFields: instance.fields ?? [],
|
|
1393
1393
|
...instance.perspective !== void 0 ? { perspective: instance.perspective } : {}
|
|
1394
1394
|
},
|
|
1395
1395
|
randomKey
|
|
@@ -1471,15 +1471,15 @@ function stageTransitionHistory(args) {
|
|
|
1471
1471
|
function startMutation(instance) {
|
|
1472
1472
|
return {
|
|
1473
1473
|
currentStage: instance.currentStage,
|
|
1474
|
-
// Deep-ish copy. Per-scope
|
|
1474
|
+
// Deep-ish copy. Per-scope field entry arrays need their own copies
|
|
1475
1475
|
// so ops can mutate without leaking into the source.
|
|
1476
|
-
|
|
1476
|
+
fields: (instance.fields ?? []).map((s) => ({ ...s })),
|
|
1477
1477
|
stages: instance.stages.map((s) => ({
|
|
1478
1478
|
...s,
|
|
1479
|
-
|
|
1479
|
+
fields: (s.fields ?? []).map((entry) => ({ ...entry })),
|
|
1480
1480
|
tasks: s.tasks.map((t) => ({
|
|
1481
1481
|
...t,
|
|
1482
|
-
...t.
|
|
1482
|
+
...t.fields !== void 0 ? { fields: t.fields.map((entry) => ({ ...entry })) } : {}
|
|
1483
1483
|
}))
|
|
1484
1484
|
})),
|
|
1485
1485
|
pendingEffects: [...instance.pendingEffects],
|
|
@@ -1495,7 +1495,7 @@ function startMutation(instance) {
|
|
|
1495
1495
|
function instanceStateFields(src) {
|
|
1496
1496
|
const fields = {
|
|
1497
1497
|
currentStage: src.currentStage,
|
|
1498
|
-
|
|
1498
|
+
fields: src.fields,
|
|
1499
1499
|
stages: src.stages,
|
|
1500
1500
|
pendingEffects: src.pendingEffects,
|
|
1501
1501
|
effectHistory: src.effectHistory,
|
|
@@ -1508,7 +1508,7 @@ function materializeInstance(base, mutation) {
|
|
|
1508
1508
|
return {
|
|
1509
1509
|
...base,
|
|
1510
1510
|
currentStage: mutation.currentStage,
|
|
1511
|
-
|
|
1511
|
+
fields: mutation.fields,
|
|
1512
1512
|
stages: mutation.stages,
|
|
1513
1513
|
effectsContext: mutation.effectsContext
|
|
1514
1514
|
};
|
|
@@ -1630,7 +1630,7 @@ function buildInstanceBase(args) {
|
|
|
1630
1630
|
definition: args.definitionName,
|
|
1631
1631
|
pinnedVersion: args.pinnedVersion,
|
|
1632
1632
|
definitionSnapshot: JSON.stringify(args.definition),
|
|
1633
|
-
|
|
1633
|
+
fields: args.fields,
|
|
1634
1634
|
effectsContext: args.effectsContext,
|
|
1635
1635
|
ancestors: args.ancestors,
|
|
1636
1636
|
...perspective !== void 0 ? { perspective } : {},
|
|
@@ -1688,7 +1688,7 @@ async function spawnSubworkflows(ctx, mutation, task, sub, actor) {
|
|
|
1688
1688
|
...actor ? { actor } : {}
|
|
1689
1689
|
}), { rows, discoveryResource } = await resolveSpawnRows(ctx, sub), effectsContext = await resolveContextHandoff(ctx, sub, parentScope), refs = [];
|
|
1690
1690
|
for (const row of rows) {
|
|
1691
|
-
const
|
|
1691
|
+
const initialFields = await projectRowFields(
|
|
1692
1692
|
ctx,
|
|
1693
1693
|
sub,
|
|
1694
1694
|
definition,
|
|
@@ -1699,7 +1699,7 @@ async function spawnSubworkflows(ctx, mutation, task, sub, actor) {
|
|
|
1699
1699
|
client: ctx.client,
|
|
1700
1700
|
parent: ctx.instance,
|
|
1701
1701
|
definition,
|
|
1702
|
-
|
|
1702
|
+
initialFields,
|
|
1703
1703
|
effectsContext,
|
|
1704
1704
|
actor,
|
|
1705
1705
|
now
|
|
@@ -1712,7 +1712,7 @@ async function resolveSpawnRows(ctx, sub) {
|
|
|
1712
1712
|
const params = buildParams({ instance: ctx.instance, now: ctx.now, snapshot: ctx.snapshot }), groq = sub.forEach;
|
|
1713
1713
|
let value, discoveryResource;
|
|
1714
1714
|
if (groq.includes("*")) {
|
|
1715
|
-
const routingUri = entrySubjectRefs(ctx.instance.
|
|
1715
|
+
const routingUri = entrySubjectRefs(ctx.instance.fields)[0]?.id, client = clientForDiscovery(ctx.client, ctx.clientForGdr, routingUri);
|
|
1716
1716
|
client !== ctx.client && routingUri !== void 0 && (discoveryResource = resourceFromGdrUri(routingUri)), value = await discoverItems({
|
|
1717
1717
|
client,
|
|
1718
1718
|
groq,
|
|
@@ -1728,13 +1728,13 @@ async function resolveSpawnRows(ctx, sub) {
|
|
|
1728
1728
|
}
|
|
1729
1729
|
return Array.isArray(value) ? { rows: value, discoveryResource } : value == null ? { rows: [], discoveryResource } : { rows: [value], discoveryResource };
|
|
1730
1730
|
}
|
|
1731
|
-
async function
|
|
1731
|
+
async function projectRowFields(ctx, sub, childDefinition, parentScope, row, discoveryResource) {
|
|
1732
1732
|
const out = [];
|
|
1733
1733
|
for (const [name, groq] of Object.entries(sub.with ?? {})) {
|
|
1734
|
-
const declared = (childDefinition.
|
|
1734
|
+
const declared = (childDefinition.fields ?? []).find((e) => e.name === name);
|
|
1735
1735
|
if (declared === void 0)
|
|
1736
1736
|
throw new Error(
|
|
1737
|
-
`subworkflows.with["${name}"]: subworkflow "${childDefinition.name}" declares no
|
|
1737
|
+
`subworkflows.with["${name}"]: subworkflow "${childDefinition.name}" declares no field entry "${name}"`
|
|
1738
1738
|
);
|
|
1739
1739
|
const raw = await runGroq(groq, { ...parentScope, row }, ctx.snapshot), value = canonicaliseSpawnValue(declared.type, raw, {
|
|
1740
1740
|
parentResource: ctx.instance.workflowResource,
|
|
@@ -1789,15 +1789,15 @@ async function resolveDefinitionRef(client, ref, tag) {
|
|
|
1789
1789
|
) ?? null;
|
|
1790
1790
|
}
|
|
1791
1791
|
async function prepareChildInstance(args) {
|
|
1792
|
-
const { client, parent, definition,
|
|
1792
|
+
const { client, parent, definition, initialFields, effectsContext, actor, now } = args, childTag = parent.tag, workflowResource = parent.workflowResource, childDocId = `${childTag}.wf-instance.${randomKey()}`, childRef = { id: gdrFromResource(workflowResource, childDocId), type: WORKFLOW_INSTANCE_TYPE }, ancestors = [
|
|
1793
1793
|
...parent.ancestors,
|
|
1794
1794
|
{
|
|
1795
1795
|
id: selfGdr(parent),
|
|
1796
1796
|
type: WORKFLOW_INSTANCE_TYPE
|
|
1797
1797
|
}
|
|
1798
|
-
], inheritedPerspective = parent.perspective,
|
|
1799
|
-
entryDefs: definition.
|
|
1800
|
-
|
|
1798
|
+
], inheritedPerspective = parent.perspective, childFields = await resolveDeclaredFields({
|
|
1799
|
+
entryDefs: definition.fields,
|
|
1800
|
+
initialFields,
|
|
1801
1801
|
ctx: {
|
|
1802
1802
|
client,
|
|
1803
1803
|
now,
|
|
@@ -1808,7 +1808,7 @@ async function prepareChildInstance(args) {
|
|
|
1808
1808
|
...inheritedPerspective !== void 0 ? { perspective: inheritedPerspective } : {}
|
|
1809
1809
|
},
|
|
1810
1810
|
randomKey
|
|
1811
|
-
}), childPerspective =
|
|
1811
|
+
}), childPerspective = derivePerspectiveFromFields(childFields) ?? inheritedPerspective, effectsContextEntries = Object.entries(effectsContext).map(
|
|
1812
1812
|
([key, value]) => {
|
|
1813
1813
|
if (typeof value == "object" && value !== null && "id" in value && "type" in value) {
|
|
1814
1814
|
if (!isGdrUri(value.id))
|
|
@@ -1827,7 +1827,7 @@ async function prepareChildInstance(args) {
|
|
|
1827
1827
|
definitionName: definition.name,
|
|
1828
1828
|
pinnedVersion: definition.version,
|
|
1829
1829
|
definition,
|
|
1830
|
-
|
|
1830
|
+
fields: childFields,
|
|
1831
1831
|
effectsContext: effectsContextEntries,
|
|
1832
1832
|
ancestors,
|
|
1833
1833
|
perspective: childPerspective,
|
|
@@ -1992,15 +1992,15 @@ function resolveStaticSource(src, ctx) {
|
|
|
1992
1992
|
return { handled: !1 };
|
|
1993
1993
|
}
|
|
1994
1994
|
}
|
|
1995
|
-
const
|
|
1996
|
-
"
|
|
1997
|
-
"
|
|
1998
|
-
"
|
|
1999
|
-
"
|
|
2000
|
-
"
|
|
1995
|
+
const FIELD_OP_TYPES = [
|
|
1996
|
+
"field.set",
|
|
1997
|
+
"field.unset",
|
|
1998
|
+
"field.append",
|
|
1999
|
+
"field.updateWhere",
|
|
2000
|
+
"field.removeWhere"
|
|
2001
2001
|
];
|
|
2002
|
-
function
|
|
2003
|
-
return
|
|
2002
|
+
function isFieldOp(summary) {
|
|
2003
|
+
return FIELD_OP_TYPES.includes(summary.opType);
|
|
2004
2004
|
}
|
|
2005
2005
|
function validateActionParams(action, taskName, callerParams) {
|
|
2006
2006
|
const declared = action.params ?? [], params = callerParams ?? {}, issues = [];
|
|
@@ -2073,23 +2073,23 @@ function opAppliedEntry(args) {
|
|
|
2073
2073
|
}
|
|
2074
2074
|
function applyOp(op, ctx) {
|
|
2075
2075
|
switch (op.type) {
|
|
2076
|
-
case "
|
|
2077
|
-
return
|
|
2078
|
-
case "
|
|
2079
|
-
return
|
|
2080
|
-
case "
|
|
2081
|
-
return
|
|
2082
|
-
case "
|
|
2083
|
-
return
|
|
2084
|
-
case "
|
|
2085
|
-
return
|
|
2076
|
+
case "field.set":
|
|
2077
|
+
return applyFieldSet(op, ctx);
|
|
2078
|
+
case "field.unset":
|
|
2079
|
+
return applyFieldUnset(op, ctx);
|
|
2080
|
+
case "field.append":
|
|
2081
|
+
return applyFieldAppend(op, ctx);
|
|
2082
|
+
case "field.updateWhere":
|
|
2083
|
+
return applyFieldUpdateWhere(op, ctx);
|
|
2084
|
+
case "field.removeWhere":
|
|
2085
|
+
return applyFieldRemoveWhere(op, ctx);
|
|
2086
2086
|
case "status.set":
|
|
2087
2087
|
return applyStatusSet(op, ctx);
|
|
2088
2088
|
}
|
|
2089
2089
|
}
|
|
2090
|
-
function
|
|
2090
|
+
function applyFieldSet(op, ctx) {
|
|
2091
2091
|
const value = resolveOpSource(op.value, ctx), entry = locateEntry(ctx, op.target);
|
|
2092
|
-
return
|
|
2092
|
+
return validateFieldValue({ entryType: entry._type, entryName: entry.name, value }), setEntryValue(entry, value), { opType: op.type, target: op.target, resolved: { value } };
|
|
2093
2093
|
}
|
|
2094
2094
|
const EMPTY_BY_KIND = {
|
|
2095
2095
|
"doc.refs": [],
|
|
@@ -2097,24 +2097,24 @@ const EMPTY_BY_KIND = {
|
|
|
2097
2097
|
notes: [],
|
|
2098
2098
|
assignees: []
|
|
2099
2099
|
};
|
|
2100
|
-
function
|
|
2100
|
+
function applyFieldUnset(op, ctx) {
|
|
2101
2101
|
const entry = locateEntry(ctx, op.target);
|
|
2102
2102
|
return setEntryValue(entry, EMPTY_BY_KIND[entry._type] ?? null), { opType: op.type, target: op.target };
|
|
2103
2103
|
}
|
|
2104
|
-
function
|
|
2104
|
+
function applyFieldAppend(op, ctx) {
|
|
2105
2105
|
const item = resolveOpSource(op.value, ctx), entry = locateEntry(ctx, op.target);
|
|
2106
|
-
|
|
2106
|
+
validateFieldAppendItem({ entryType: entry._type, entryName: entry.name, item });
|
|
2107
2107
|
const current = Array.isArray(entry.value) ? entry.value : [];
|
|
2108
2108
|
return setEntryValue(entry, [...current, withRowKey(item)]), { opType: op.type, target: op.target, resolved: { item } };
|
|
2109
2109
|
}
|
|
2110
2110
|
function withRowKey(item) {
|
|
2111
2111
|
return typeof item == "object" && item !== null && !Array.isArray(item) && !("_key" in item) ? { _key: randomKey(), ...item } : item;
|
|
2112
2112
|
}
|
|
2113
|
-
function
|
|
2113
|
+
function applyFieldUpdateWhere(op, ctx) {
|
|
2114
2114
|
const entry = locateEntry(ctx, op.target), rows = requireArrayValue(entry, op), merge = resolveOpSource(op.value, ctx);
|
|
2115
2115
|
if (merge === null || typeof merge != "object" || Array.isArray(merge))
|
|
2116
2116
|
throw new Error(
|
|
2117
|
-
`
|
|
2117
|
+
`field.updateWhere value must resolve to an object of fields to merge (target "${op.target.field}")`
|
|
2118
2118
|
);
|
|
2119
2119
|
return setEntryValue(
|
|
2120
2120
|
entry,
|
|
@@ -2123,7 +2123,7 @@ function applyStateUpdateWhere(op, ctx) {
|
|
|
2123
2123
|
)
|
|
2124
2124
|
), { opType: op.type, target: op.target, resolved: { merge } };
|
|
2125
2125
|
}
|
|
2126
|
-
function
|
|
2126
|
+
function applyFieldRemoveWhere(op, ctx) {
|
|
2127
2127
|
const entry = locateEntry(ctx, op.target), rows = requireArrayValue(entry, op);
|
|
2128
2128
|
return setEntryValue(
|
|
2129
2129
|
entry,
|
|
@@ -2133,7 +2133,7 @@ function applyStateRemoveWhere(op, ctx) {
|
|
|
2133
2133
|
function requireArrayValue(entry, op) {
|
|
2134
2134
|
if (!Array.isArray(entry.value))
|
|
2135
2135
|
throw new Error(
|
|
2136
|
-
`${op.type} target ${op.target.scope}:"${op.target.
|
|
2136
|
+
`${op.type} target ${op.target.scope}:"${op.target.field}" holds a non-array ${entry._type} value \u2014 where-ops operate on array entries`
|
|
2137
2137
|
);
|
|
2138
2138
|
return entry.value;
|
|
2139
2139
|
}
|
|
@@ -2154,19 +2154,19 @@ function setEntryValue(entry, value) {
|
|
|
2154
2154
|
entry.value = value;
|
|
2155
2155
|
}
|
|
2156
2156
|
function locateEntry(ctx, target) {
|
|
2157
|
-
const entry =
|
|
2157
|
+
const entry = fieldHost(ctx, target.scope)?.find((s) => s.name === target.field);
|
|
2158
2158
|
if (entry === void 0)
|
|
2159
2159
|
throw new Error(
|
|
2160
|
-
`Op target ${target.scope}:"${target.
|
|
2160
|
+
`Op target ${target.scope}:"${target.field}" has no resolved field entry on this instance \u2014 the deployed definition and the instance state have diverged`
|
|
2161
2161
|
);
|
|
2162
2162
|
return entry;
|
|
2163
2163
|
}
|
|
2164
|
-
function
|
|
2165
|
-
if (scope === "workflow") return ctx.mutation.
|
|
2164
|
+
function fieldHost(ctx, scope) {
|
|
2165
|
+
if (scope === "workflow") return ctx.mutation.fields;
|
|
2166
2166
|
const stageEntry = findOpenStageEntry(ctx.mutation);
|
|
2167
|
-
if (scope === "stage") return stageEntry?.
|
|
2167
|
+
if (scope === "stage") return stageEntry?.fields;
|
|
2168
2168
|
const task = stageEntry?.tasks.find((t) => t.name === ctx.taskName);
|
|
2169
|
-
return task !== void 0 && task.
|
|
2169
|
+
return task !== void 0 && task.fields === void 0 && (task.fields = []), task?.fields;
|
|
2170
2170
|
}
|
|
2171
2171
|
function resolveOpSource(src, ctx) {
|
|
2172
2172
|
const staticValue = resolveStaticSource(src, ctx);
|
|
@@ -2176,7 +2176,7 @@ function resolveOpSource(src, ctx) {
|
|
|
2176
2176
|
return ctx.self;
|
|
2177
2177
|
case "stage":
|
|
2178
2178
|
return ctx.stage;
|
|
2179
|
-
case "
|
|
2179
|
+
case "fieldRead": {
|
|
2180
2180
|
const value = readEntryFromMutation(ctx, src);
|
|
2181
2181
|
return src.path !== void 0 ? getPath(value, src.path) : value;
|
|
2182
2182
|
}
|
|
@@ -2187,7 +2187,7 @@ function resolveOpSource(src, ctx) {
|
|
|
2187
2187
|
return out;
|
|
2188
2188
|
}
|
|
2189
2189
|
default:
|
|
2190
|
-
throw new Error(`Source type "${src.type}" is a
|
|
2190
|
+
throw new Error(`Source type "${src.type}" is a field-entry origin, not a value`);
|
|
2191
2191
|
}
|
|
2192
2192
|
}
|
|
2193
2193
|
function entryValue(entries, name) {
|
|
@@ -2196,7 +2196,7 @@ function entryValue(entries, name) {
|
|
|
2196
2196
|
function readEntryFromMutation(ctx, src) {
|
|
2197
2197
|
const scopes = src.scope !== void 0 ? [src.scope] : ["stage", "workflow"], stageEntry = findOpenStageEntry(ctx.mutation);
|
|
2198
2198
|
for (const scope of scopes) {
|
|
2199
|
-
const host = scope === "workflow" ? ctx.mutation.
|
|
2199
|
+
const host = scope === "workflow" ? ctx.mutation.fields : stageEntry?.fields, value = entryValue(host, src.field);
|
|
2200
2200
|
if (value !== void 0) return value;
|
|
2201
2201
|
}
|
|
2202
2202
|
}
|
|
@@ -2228,9 +2228,9 @@ async function commitInvoke(ctx, taskName, actor) {
|
|
|
2228
2228
|
}
|
|
2229
2229
|
async function activateTask(ctx, mutation, task, entry, actor) {
|
|
2230
2230
|
const now = ctx.now;
|
|
2231
|
-
if (entry.status = "active", entry.startedAt = now, task.
|
|
2231
|
+
if (entry.status = "active", entry.startedAt = now, task.fields !== void 0 && task.fields.length > 0) {
|
|
2232
2232
|
const stage = findStage(ctx.definition, mutation.currentStage);
|
|
2233
|
-
entry.
|
|
2233
|
+
entry.fields = await resolveTaskFieldEntries({
|
|
2234
2234
|
client: ctx.client,
|
|
2235
2235
|
instance: ctx.instance,
|
|
2236
2236
|
stage,
|
|
@@ -2335,7 +2335,7 @@ async function enterStage(ctx, mutation, nextStage, actor, at) {
|
|
|
2335
2335
|
_key: randomKey(),
|
|
2336
2336
|
name: nextStage.name,
|
|
2337
2337
|
enteredAt: at,
|
|
2338
|
-
|
|
2338
|
+
fields: await resolveStageFieldEntries({
|
|
2339
2339
|
client: ctx.client,
|
|
2340
2340
|
instance: ctx.instance,
|
|
2341
2341
|
stage: nextStage,
|
|
@@ -2475,7 +2475,7 @@ async function primeInitialStage(client, instanceId, actor, clientForGdr, clock)
|
|
|
2475
2475
|
_key: randomKey(),
|
|
2476
2476
|
name: stage.name,
|
|
2477
2477
|
enteredAt: now,
|
|
2478
|
-
|
|
2478
|
+
fields: await resolveStageFieldEntries({ client, instance, stage, now }),
|
|
2479
2479
|
tasks: await buildStageTasks(ctx, stage)
|
|
2480
2480
|
}, committed = await client.patch(instance._id).set({
|
|
2481
2481
|
stages: [initialStageEntry],
|
|
@@ -2713,10 +2713,10 @@ function effectiveEditable(baseline, override) {
|
|
|
2713
2713
|
}
|
|
2714
2714
|
function slotSites(definition, stage) {
|
|
2715
2715
|
const sites = [];
|
|
2716
|
-
for (const entry of definition.
|
|
2717
|
-
for (const entry of stage.
|
|
2716
|
+
for (const entry of definition.fields ?? []) sites.push({ scope: "workflow", entry });
|
|
2717
|
+
for (const entry of stage.fields ?? []) sites.push({ scope: "stage", entry });
|
|
2718
2718
|
for (const task of stage.tasks ?? [])
|
|
2719
|
-
for (const entry of task.
|
|
2719
|
+
for (const entry of task.fields ?? []) sites.push({ scope: "task", task: task.name, entry });
|
|
2720
2720
|
return sites;
|
|
2721
2721
|
}
|
|
2722
2722
|
function toResolvedSlot(site, stage) {
|
|
@@ -2726,7 +2726,7 @@ function toResolvedSlot(site, stage) {
|
|
|
2726
2726
|
name: site.entry.name,
|
|
2727
2727
|
type: site.entry.type,
|
|
2728
2728
|
...site.entry.title !== void 0 ? { title: site.entry.title } : {},
|
|
2729
|
-
ref: { scope: site.scope,
|
|
2729
|
+
ref: { scope: site.scope, field: site.entry.name },
|
|
2730
2730
|
effective: effectiveEditable(site.entry.editable, stage.editable?.[site.entry.name])
|
|
2731
2731
|
};
|
|
2732
2732
|
}
|
|
@@ -2734,7 +2734,7 @@ function editableSlotsInStage(definition, stage) {
|
|
|
2734
2734
|
return slotSites(definition, stage).filter((site) => site.entry.editable !== void 0).map((site) => toResolvedSlot(site, stage));
|
|
2735
2735
|
}
|
|
2736
2736
|
function editTargetMatches(slot, target) {
|
|
2737
|
-
return slot.name !== target.
|
|
2737
|
+
return slot.name !== target.field ? !1 : target.task !== void 0 ? slot.scope === "task" && slot.task === target.task : target.scope !== void 0 ? slot.scope === target.scope : !0;
|
|
2738
2738
|
}
|
|
2739
2739
|
const SCOPE_PRECEDENCE = { task: 0, stage: 1, workflow: 2 };
|
|
2740
2740
|
function resolveEditTarget(args) {
|
|
@@ -2758,17 +2758,17 @@ function slotWindowOpen(instance, slot) {
|
|
|
2758
2758
|
return status === "active" ? { open: !0 } : { open: !1, detail: `task "${slot.task}" is ${status ?? "not active"}` };
|
|
2759
2759
|
}
|
|
2760
2760
|
function readSlotValue(instance, slot) {
|
|
2761
|
-
return
|
|
2761
|
+
return slotFieldHost(instance, slot)?.find((e) => e.name === slot.name)?.value;
|
|
2762
2762
|
}
|
|
2763
|
-
function
|
|
2764
|
-
if (slot.scope === "workflow") return instance.
|
|
2763
|
+
function slotFieldHost(instance, slot) {
|
|
2764
|
+
if (slot.scope === "workflow") return instance.fields;
|
|
2765
2765
|
const stageEntry = findOpenStageEntry(instance);
|
|
2766
|
-
return slot.scope === "stage" ? stageEntry?.
|
|
2766
|
+
return slot.scope === "stage" ? stageEntry?.fields : stageEntry?.tasks.find((t) => t.name === slot.task)?.fields;
|
|
2767
2767
|
}
|
|
2768
2768
|
function slotProvenance(instance, ref) {
|
|
2769
2769
|
let latest;
|
|
2770
2770
|
for (const entry of instance.history)
|
|
2771
|
-
entry._type === "opApplied" && (entry.target?.scope !== ref.scope || entry.target.
|
|
2771
|
+
entry._type === "opApplied" && (entry.target?.scope !== ref.scope || entry.target.field !== ref.field || (latest = { at: entry.at, ...entry.actor !== void 0 ? { actor: entry.actor } : {} }));
|
|
2772
2772
|
return latest === void 0 ? {} : {
|
|
2773
2773
|
...latest.actor !== void 0 ? { setBy: latest.actor } : {},
|
|
2774
2774
|
setAt: latest.at
|
|
@@ -2926,9 +2926,9 @@ function taskScopeFor(args) {
|
|
|
2926
2926
|
const { scope, instance, snapshot, actor, taskName, roleAliases } = args;
|
|
2927
2927
|
return {
|
|
2928
2928
|
...scope,
|
|
2929
|
-
|
|
2930
|
-
...scope.
|
|
2931
|
-
...
|
|
2929
|
+
fields: {
|
|
2930
|
+
...scope.fields,
|
|
2931
|
+
...scopedFieldOverlay(instance, snapshot, taskName)
|
|
2932
2932
|
},
|
|
2933
2933
|
assigned: assignedFor(instance, taskName, actor, roleAliases)
|
|
2934
2934
|
};
|
|
@@ -2974,8 +2974,8 @@ async function evaluateTask(args) {
|
|
|
2974
2974
|
status,
|
|
2975
2975
|
// "pending on actor" treats both `pending` and `active` as waiting on
|
|
2976
2976
|
// the assignee — pending tasks just haven't been activated yet, but
|
|
2977
|
-
// they're still inbox items. The inbox reads the assignees-kind
|
|
2978
|
-
// entry BY KIND (who-data is
|
|
2977
|
+
// they're still inbox items. The inbox reads the assignees-kind field
|
|
2978
|
+
// entry BY KIND (who-data is fields).
|
|
2979
2979
|
pendingOnActor: (status === "active" || status === "pending") && assigned,
|
|
2980
2980
|
...unmetRequirements.length > 0 ? { unmetRequirements } : {},
|
|
2981
2981
|
actions
|
|
@@ -3244,7 +3244,7 @@ async function commitAction(ctx, taskName, actionName, callerParams, options) {
|
|
|
3244
3244
|
}), await persistThenDeploy(
|
|
3245
3245
|
ctx,
|
|
3246
3246
|
mutation,
|
|
3247
|
-
() => ranOps.some(
|
|
3247
|
+
() => ranOps.some(isFieldOp) ? refreshStageGuards(ctx, mutation, stage.name) : Promise.resolve()
|
|
3248
3248
|
), {
|
|
3249
3249
|
fired: !0,
|
|
3250
3250
|
task: taskName,
|
|
@@ -3273,11 +3273,11 @@ function formatDisabledReason(task, action, reason) {
|
|
|
3273
3273
|
const detail = disabledReasonDetail[reason.kind](reason);
|
|
3274
3274
|
return `Action "${task}:${action}" is not allowed: ${detail}`;
|
|
3275
3275
|
}
|
|
3276
|
-
class
|
|
3276
|
+
class EditFieldDeniedError extends Error {
|
|
3277
3277
|
reason;
|
|
3278
3278
|
target;
|
|
3279
3279
|
constructor(args) {
|
|
3280
|
-
super(formatEditDisabledReason(args.target, args.reason)), this.name = "
|
|
3280
|
+
super(formatEditDisabledReason(args.target, args.reason)), this.name = "EditFieldDeniedError", this.reason = args.reason, this.target = args.target;
|
|
3281
3281
|
}
|
|
3282
3282
|
}
|
|
3283
3283
|
const editDisabledReasonDetail = {
|
|
@@ -3290,10 +3290,10 @@ const editDisabledReasonDetail = {
|
|
|
3290
3290
|
function formatEditDisabledReason(target, reason) {
|
|
3291
3291
|
const detail = editDisabledReasonDetail[reason.kind](
|
|
3292
3292
|
reason
|
|
3293
|
-
), where = target.task !== void 0 ? `${target.task}.${target.
|
|
3294
|
-
return `
|
|
3293
|
+
), where = target.task !== void 0 ? `${target.task}.${target.field}` : target.field;
|
|
3294
|
+
return `Field "${target.scope}:${where}" is not editable: ${detail}`;
|
|
3295
3295
|
}
|
|
3296
|
-
async function
|
|
3296
|
+
async function editField(args) {
|
|
3297
3297
|
const { client, instanceId, target, mode = "set", value, options } = args;
|
|
3298
3298
|
for (let attempt = 1; attempt <= CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS; attempt++) {
|
|
3299
3299
|
const ctx = await loadCallContext(client, instanceId, options);
|
|
@@ -3303,7 +3303,7 @@ async function editState(args) {
|
|
|
3303
3303
|
if (!isRevisionConflict(error)) throw error;
|
|
3304
3304
|
}
|
|
3305
3305
|
}
|
|
3306
|
-
throw new
|
|
3306
|
+
throw new ConcurrentEditFieldError({
|
|
3307
3307
|
instanceId,
|
|
3308
3308
|
target: labelTarget(target),
|
|
3309
3309
|
attempts: CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS
|
|
@@ -3313,7 +3313,7 @@ async function commitEdit(ctx, target, mode, value, options) {
|
|
|
3313
3313
|
const actor = options?.actor, stage = findStage(ctx.definition, ctx.instance.currentStage), slot = resolveEditTarget({ definition: ctx.definition, stage, target });
|
|
3314
3314
|
if (slot === void 0)
|
|
3315
3315
|
throw new Error(
|
|
3316
|
-
`No
|
|
3316
|
+
`No field slot "${describeTarget(target)}" resolves in current stage "${stage.name}" of ${ctx.definition.name}`
|
|
3317
3317
|
);
|
|
3318
3318
|
await assertSlotEditable(ctx, slot, options);
|
|
3319
3319
|
const op = buildEditOp(slot, mode, value), mutation = startMutation(ctx.instance), ranOps = runOps({
|
|
@@ -3332,7 +3332,7 @@ async function commitEdit(ctx, target, mode, value, options) {
|
|
|
3332
3332
|
return await persistThenDeploy(
|
|
3333
3333
|
ctx,
|
|
3334
3334
|
mutation,
|
|
3335
|
-
() => ranOps.some(
|
|
3335
|
+
() => ranOps.some(isFieldOp) ? refreshStageGuards(ctx, mutation, stage.name) : Promise.resolve()
|
|
3336
3336
|
), {
|
|
3337
3337
|
edited: !0,
|
|
3338
3338
|
target: slotTarget(slot),
|
|
@@ -3351,14 +3351,14 @@ async function assertSlotEditable(ctx, slot, options) {
|
|
|
3351
3351
|
guardDenial: void 0,
|
|
3352
3352
|
predicateSatisfied
|
|
3353
3353
|
});
|
|
3354
|
-
if (reason !== void 0) throw new
|
|
3354
|
+
if (reason !== void 0) throw new EditFieldDeniedError({ target: slotTarget(slot), reason });
|
|
3355
3355
|
}
|
|
3356
3356
|
function buildEditOp(slot, mode, value) {
|
|
3357
|
-
if (mode === "unset") return { type: "
|
|
3357
|
+
if (mode === "unset") return { type: "field.unset", target: slot.ref };
|
|
3358
3358
|
if (value === void 0)
|
|
3359
|
-
throw new Error(`
|
|
3359
|
+
throw new Error(`editField mode "${mode}" requires a value for slot "${slot.name}"`);
|
|
3360
3360
|
return {
|
|
3361
|
-
type: mode === "append" ? "
|
|
3361
|
+
type: mode === "append" ? "field.append" : "field.set",
|
|
3362
3362
|
target: slot.ref,
|
|
3363
3363
|
value: { type: "literal", value }
|
|
3364
3364
|
};
|
|
@@ -3366,19 +3366,19 @@ function buildEditOp(slot, mode, value) {
|
|
|
3366
3366
|
function slotTarget(slot) {
|
|
3367
3367
|
return {
|
|
3368
3368
|
scope: slot.scope,
|
|
3369
|
-
|
|
3369
|
+
field: slot.name,
|
|
3370
3370
|
...slot.task !== void 0 ? { task: slot.task } : {}
|
|
3371
3371
|
};
|
|
3372
3372
|
}
|
|
3373
3373
|
function labelTarget(target) {
|
|
3374
3374
|
return {
|
|
3375
3375
|
scope: target.scope ?? (target.task !== void 0 ? "task" : "workflow"),
|
|
3376
|
-
|
|
3376
|
+
field: target.field,
|
|
3377
3377
|
...target.task !== void 0 ? { task: target.task } : {}
|
|
3378
3378
|
};
|
|
3379
3379
|
}
|
|
3380
3380
|
function describeTarget(target) {
|
|
3381
|
-
const where = target.task !== void 0 ? `${target.task}.${target.
|
|
3381
|
+
const where = target.task !== void 0 ? `${target.task}.${target.field}` : target.field;
|
|
3382
3382
|
return target.scope !== void 0 ? `${target.scope}:${where}` : where;
|
|
3383
3383
|
}
|
|
3384
3384
|
function bareIdFromSpawnRef(uri) {
|
|
@@ -3451,10 +3451,10 @@ function assertActionAllowed(evaluation, task, action) {
|
|
|
3451
3451
|
function assertEditAllowed(evaluation, target) {
|
|
3452
3452
|
const slot = evaluation.editableSlots.filter((s) => editTargetMatches(s, target)).sort((a, b) => SCOPE_PRECEDENCE[a.scope] - SCOPE_PRECEDENCE[b.scope])[0];
|
|
3453
3453
|
if (slot !== void 0 && !slot.editable && slot.disabledReason !== void 0)
|
|
3454
|
-
throw new
|
|
3454
|
+
throw new EditFieldDeniedError({
|
|
3455
3455
|
target: {
|
|
3456
3456
|
scope: slot.scope,
|
|
3457
|
-
|
|
3457
|
+
field: slot.name,
|
|
3458
3458
|
...slot.task !== void 0 ? { task: slot.task } : {}
|
|
3459
3459
|
},
|
|
3460
3460
|
reason: slot.disabledReason
|
|
@@ -3462,7 +3462,7 @@ function assertEditAllowed(evaluation, target) {
|
|
|
3462
3462
|
}
|
|
3463
3463
|
async function applyEdit(args) {
|
|
3464
3464
|
const { client, instance, target, mode, value, actor, grants, clock, clientForGdr } = args;
|
|
3465
|
-
return (await
|
|
3465
|
+
return (await editField({
|
|
3466
3466
|
client,
|
|
3467
3467
|
instanceId: instance._id,
|
|
3468
3468
|
target,
|
|
@@ -3635,7 +3635,7 @@ const workflow = {
|
|
|
3635
3635
|
workflowResource,
|
|
3636
3636
|
definition: definitionName,
|
|
3637
3637
|
version,
|
|
3638
|
-
|
|
3638
|
+
initialFields,
|
|
3639
3639
|
ancestors,
|
|
3640
3640
|
effectsContext,
|
|
3641
3641
|
instanceId,
|
|
@@ -3645,9 +3645,9 @@ const workflow = {
|
|
|
3645
3645
|
throw new Error(
|
|
3646
3646
|
`Initial stage "${definition.initialStage}" missing in ${definitionName} v${definition.version}`
|
|
3647
3647
|
);
|
|
3648
|
-
const now = clock(), effectsContextEntries = effectsContext ? toEffectsContextEntries(effectsContext) : [],
|
|
3649
|
-
entryDefs: definition.
|
|
3650
|
-
|
|
3648
|
+
const now = clock(), effectsContextEntries = effectsContext ? toEffectsContextEntries(effectsContext) : [], resolvedFields = await resolveDeclaredFields({
|
|
3649
|
+
entryDefs: definition.fields ?? [],
|
|
3650
|
+
initialFields: initialFields ?? [],
|
|
3651
3651
|
ctx: {
|
|
3652
3652
|
client,
|
|
3653
3653
|
now,
|
|
@@ -3658,7 +3658,7 @@ const workflow = {
|
|
|
3658
3658
|
...perspective !== void 0 ? { perspective } : {}
|
|
3659
3659
|
},
|
|
3660
3660
|
randomKey
|
|
3661
|
-
}), effectivePerspective = perspective ??
|
|
3661
|
+
}), effectivePerspective = perspective ?? derivePerspectiveFromFields(resolvedFields), base = buildInstanceBase({
|
|
3662
3662
|
id,
|
|
3663
3663
|
now,
|
|
3664
3664
|
tag,
|
|
@@ -3666,7 +3666,7 @@ const workflow = {
|
|
|
3666
3666
|
definitionName: definition.name,
|
|
3667
3667
|
pinnedVersion: definition.version,
|
|
3668
3668
|
definition,
|
|
3669
|
-
|
|
3669
|
+
fields: resolvedFields,
|
|
3670
3670
|
effectsContext: effectsContextEntries,
|
|
3671
3671
|
ancestors: ancestors ?? [],
|
|
3672
3672
|
perspective: effectivePerspective,
|
|
@@ -3720,11 +3720,11 @@ const workflow = {
|
|
|
3720
3720
|
});
|
|
3721
3721
|
},
|
|
3722
3722
|
/**
|
|
3723
|
-
* Edit a declared-editable
|
|
3723
|
+
* Edit a declared-editable field slot directly — reassign, reschedule,
|
|
3724
3724
|
* claim-by-hand, append to a running log — through the generic edit seam,
|
|
3725
3725
|
* instead of a bespoke action per field. Soft-gates on the slot's declared
|
|
3726
3726
|
* editability (the same projection a UI renders), applies the edit as a
|
|
3727
|
-
* `
|
|
3727
|
+
* `field.*` op (so provenance + history are stamped by the op path),
|
|
3728
3728
|
* refreshes the stage's guards, then cascades — an edit to a value a
|
|
3729
3729
|
* transition reads can and should move the instance. Advisory like every
|
|
3730
3730
|
* engine gate; the lake ACL + guards are the only hard locks.
|
|
@@ -3734,7 +3734,7 @@ const workflow = {
|
|
|
3734
3734
|
* UI must bind it to a deliberate boundary (blur / Enter / Save / debounce),
|
|
3735
3735
|
* never an `onChange` per keystroke.
|
|
3736
3736
|
*/
|
|
3737
|
-
|
|
3737
|
+
editField: async (args) => {
|
|
3738
3738
|
const { client, tag, instanceId, target, mode, value, resourceClients } = args, { access, actor, clientForGdr } = await resolveOperationContext(args), clock = args.clock ?? wallClock, before = await reload(client, instanceId, tag);
|
|
3739
3739
|
return dispatchGatedWrite({
|
|
3740
3740
|
client,
|
|
@@ -3899,7 +3899,7 @@ const workflow = {
|
|
|
3899
3899
|
* Hydrates the instance's snapshot (instance + ancestors + every doc
|
|
3900
3900
|
* declared by a `doc.ref` / `doc.refs` entry in scope), then
|
|
3901
3901
|
* evaluates the supplied GROQ in groq-js against that dataset. The
|
|
3902
|
-
* rendered scope is auto-bound: `$self`, `$
|
|
3902
|
+
* rendered scope is auto-bound: `$self`, `$fields`, `$parent`,
|
|
3903
3903
|
* `$ancestors`, `$stage`, `$now`, `$effects`, `$tasks`,
|
|
3904
3904
|
* `$allTasksDone`, `$anyTaskFailed` — ids in GDR URI form to match
|
|
3905
3905
|
* the snapshot's keying.
|
|
@@ -4232,7 +4232,7 @@ function createInstanceSession(args) {
|
|
|
4232
4232
|
return settleAfterApply(actor, held, ranOps);
|
|
4233
4233
|
});
|
|
4234
4234
|
},
|
|
4235
|
-
|
|
4235
|
+
editField({ target, mode, value }) {
|
|
4236
4236
|
return commit(async (actor, held) => {
|
|
4237
4237
|
assertEditAllowed(await evaluateWith(held, heldGuards), target);
|
|
4238
4238
|
const { grants } = await access(), ranOps = await applyEdit({
|
|
@@ -4294,7 +4294,7 @@ function createEngine(args) {
|
|
|
4294
4294
|
deployDefinitions: (rest) => workflow.deployDefinitions(bind(rest)),
|
|
4295
4295
|
startInstance: (rest) => workflow.startInstance(bind(rest)),
|
|
4296
4296
|
fireAction: (rest) => workflow.fireAction(bind(rest)),
|
|
4297
|
-
|
|
4297
|
+
editField: (rest) => workflow.editField(bind(rest)),
|
|
4298
4298
|
completeEffect: (rest) => workflow.completeEffect(bind(rest)),
|
|
4299
4299
|
tick: (rest) => workflow.tick(bind(rest)),
|
|
4300
4300
|
evaluateInstance: (rest) => evaluateInstance(bind(rest)),
|
|
@@ -4453,9 +4453,9 @@ const HISTORY_DISPLAY = {
|
|
|
4453
4453
|
},
|
|
4454
4454
|
opApplied: {
|
|
4455
4455
|
title: "Op applied",
|
|
4456
|
-
description: "An inline
|
|
4456
|
+
description: "An inline field-mutation op (field.set / field.append / status.set / etc.) ran during an action commit."
|
|
4457
4457
|
}
|
|
4458
|
-
},
|
|
4458
|
+
}, FIELD_SLOT_DISPLAY = {
|
|
4459
4459
|
"doc.ref": {
|
|
4460
4460
|
title: "Document reference",
|
|
4461
4461
|
description: "Single GDR pointer at a document in this or another resource."
|
|
@@ -4509,23 +4509,23 @@ const HISTORY_DISPLAY = {
|
|
|
4509
4509
|
description: "Ordered list of typed (user|role) assignees."
|
|
4510
4510
|
}
|
|
4511
4511
|
}, OP_DISPLAY = {
|
|
4512
|
-
"
|
|
4513
|
-
title: "Set
|
|
4514
|
-
description: "Overwrite a
|
|
4512
|
+
"field.set": {
|
|
4513
|
+
title: "Set field entry",
|
|
4514
|
+
description: "Overwrite a field entry's value with a resolved Source."
|
|
4515
4515
|
},
|
|
4516
|
-
"
|
|
4517
|
-
title: "Unset
|
|
4518
|
-
description: "Reset a
|
|
4516
|
+
"field.unset": {
|
|
4517
|
+
title: "Unset field entry",
|
|
4518
|
+
description: "Reset a field entry to its default (null for scalars, [] for array kinds)."
|
|
4519
4519
|
},
|
|
4520
|
-
"
|
|
4521
|
-
title: "Append to
|
|
4520
|
+
"field.append": {
|
|
4521
|
+
title: "Append to field entry",
|
|
4522
4522
|
description: "Push a resolved item onto an array-kind entry (notes, checklist, assignees, refs)."
|
|
4523
4523
|
},
|
|
4524
|
-
"
|
|
4524
|
+
"field.updateWhere": {
|
|
4525
4525
|
title: "Update matching rows",
|
|
4526
4526
|
description: "Merge fields into rows of an array-kind entry that match the predicate."
|
|
4527
4527
|
},
|
|
4528
|
-
"
|
|
4528
|
+
"field.removeWhere": {
|
|
4529
4529
|
title: "Remove matching rows",
|
|
4530
4530
|
description: "Drop rows of an array-kind entry that match the predicate."
|
|
4531
4531
|
},
|
|
@@ -4569,11 +4569,11 @@ const HISTORY_DISPLAY = {
|
|
|
4569
4569
|
},
|
|
4570
4570
|
[WORKFLOW_INSTANCE_TYPE]: {
|
|
4571
4571
|
title: "Workflow instance",
|
|
4572
|
-
description: "A running (or finished) workflow against its declared
|
|
4572
|
+
description: "A running (or finished) workflow against its declared fields."
|
|
4573
4573
|
}
|
|
4574
4574
|
}, DISPLAY = {
|
|
4575
4575
|
...HISTORY_DISPLAY,
|
|
4576
|
-
...
|
|
4576
|
+
...FIELD_SLOT_DISPLAY,
|
|
4577
4577
|
...OP_DISPLAY,
|
|
4578
4578
|
...EFFECTS_CONTEXT_DISPLAY,
|
|
4579
4579
|
...AUTHORING_DISPLAY
|
|
@@ -4590,12 +4590,13 @@ export {
|
|
|
4590
4590
|
ActionDisabledError,
|
|
4591
4591
|
ActionParamsInvalidError,
|
|
4592
4592
|
CascadeLimitError,
|
|
4593
|
-
|
|
4593
|
+
ConcurrentEditFieldError,
|
|
4594
4594
|
ConcurrentFireActionError,
|
|
4595
4595
|
DEFAULT_CONTENT_PERSPECTIVE,
|
|
4596
4596
|
DISPLAY,
|
|
4597
4597
|
EFFECTS_CONTEXT_DISPLAY,
|
|
4598
|
-
|
|
4598
|
+
EditFieldDeniedError,
|
|
4599
|
+
FIELD_SLOT_DISPLAY,
|
|
4599
4600
|
GUARD_DOC_TYPE,
|
|
4600
4601
|
GUARD_LIFTED_PREDICATE,
|
|
4601
4602
|
GUARD_OWNER,
|
|
@@ -4603,8 +4604,7 @@ export {
|
|
|
4603
4604
|
MutationGuardDeniedError,
|
|
4604
4605
|
OP_DISPLAY,
|
|
4605
4606
|
PartialGuardDeployError,
|
|
4606
|
-
|
|
4607
|
-
STATE_SLOT_DISPLAY,
|
|
4607
|
+
RequiredFieldNotProvidedError,
|
|
4608
4608
|
WORKFLOW_DEFINITION_TYPE,
|
|
4609
4609
|
WORKFLOW_INSTANCE_TYPE,
|
|
4610
4610
|
WorkflowStateDivergedError,
|