@sanity/workflow-engine 0.10.0 → 0.12.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 +189 -74
- package/dist/_chunks-cjs/schema.cjs.map +1 -1
- package/dist/_chunks-es/schema.js +190 -75
- package/dist/_chunks-es/schema.js.map +1 -1
- package/dist/define.cjs +220 -121
- package/dist/define.cjs.map +1 -1
- package/dist/define.d.cts +6473 -3502
- package/dist/define.d.ts +6473 -3502
- package/dist/define.js +221 -122
- package/dist/define.js.map +1 -1
- package/dist/index.cjs +1131 -921
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14866 -10827
- package/dist/index.d.ts +14866 -10827
- package/dist/index.js +1133 -923
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/define.cjs
CHANGED
|
@@ -29,24 +29,26 @@ function serializeGroqValue(value) {
|
|
|
29
29
|
return serialized;
|
|
30
30
|
}
|
|
31
31
|
function desugarWorkflow(authoring) {
|
|
32
|
-
const
|
|
33
|
-
|
|
32
|
+
const issues = [];
|
|
33
|
+
checkReservedRoleAliasKeys(authoring.roleAliases, issues);
|
|
34
|
+
const roleAliases = schema.normalizeRoleAliases(authoring.roleAliases), ctx = {
|
|
35
|
+
issues,
|
|
34
36
|
claimFields: /* @__PURE__ */ new Map(),
|
|
35
37
|
claimedFields: /* @__PURE__ */ new Set(),
|
|
36
|
-
roleAliases
|
|
38
|
+
roleAliases
|
|
37
39
|
}, workflowFields = desugarFieldEntries(authoring.fields, ["fields"], ctx), workflowLayer = layerOf(workflowFields), stages = authoring.stages.map((stage, i) => {
|
|
38
40
|
const path = ["stages", i], stageFields = desugarFieldEntries(stage.fields, [...path, "fields"], ctx), stageEnv = {
|
|
39
41
|
layers: [
|
|
40
42
|
{ scope: "stage", entries: layerOf(stageFields) },
|
|
41
43
|
{ scope: "workflow", entries: workflowLayer }
|
|
42
44
|
]
|
|
43
|
-
},
|
|
44
|
-
(
|
|
45
|
+
}, activities = (stage.activities ?? []).map(
|
|
46
|
+
(activity, j) => desugarActivity(activity, [...path, "activities", j], stageEnv, ctx)
|
|
45
47
|
), transitions = (stage.transitions ?? []).map(
|
|
46
48
|
(transition, k) => desugarTransition(transition, [...path, "transitions", k], stageEnv, ctx)
|
|
47
49
|
), editable = desugarStageEditable(
|
|
48
50
|
stage.editable,
|
|
49
|
-
editableSlotNames(workflowFields, stageFields,
|
|
51
|
+
editableSlotNames(workflowFields, stageFields, activities),
|
|
50
52
|
[...path, "editable"],
|
|
51
53
|
ctx
|
|
52
54
|
);
|
|
@@ -58,7 +60,7 @@ function desugarWorkflow(authoring) {
|
|
|
58
60
|
guards: stage.guards
|
|
59
61
|
}),
|
|
60
62
|
...stageFields ? { fields: stageFields } : {},
|
|
61
|
-
...
|
|
63
|
+
...activities.length > 0 ? { activities } : {},
|
|
62
64
|
...transitions.length > 0 ? { transitions } : {},
|
|
63
65
|
...editable ? { editable } : {}
|
|
64
66
|
};
|
|
@@ -66,41 +68,76 @@ function desugarWorkflow(authoring) {
|
|
|
66
68
|
return checkUnclaimedClaimFields(ctx), { definition: {
|
|
67
69
|
...stripUndefined({
|
|
68
70
|
name: authoring.name,
|
|
69
|
-
version: authoring.version,
|
|
70
71
|
title: authoring.title,
|
|
71
72
|
description: authoring.description,
|
|
72
73
|
role: authoring.role,
|
|
73
74
|
initialStage: authoring.initialStage,
|
|
74
75
|
predicates: authoring.predicates,
|
|
75
|
-
roleAliases
|
|
76
|
+
roleAliases
|
|
76
77
|
}),
|
|
77
78
|
...workflowFields ? { fields: workflowFields } : {},
|
|
78
79
|
stages
|
|
79
80
|
}, issues: ctx.issues };
|
|
80
81
|
}
|
|
82
|
+
function checkReservedRoleAliasKeys(aliases, issues) {
|
|
83
|
+
for (const key of Object.keys(aliases ?? {}))
|
|
84
|
+
key.startsWith("$") && issues.push({
|
|
85
|
+
path: ["roleAliases", key],
|
|
86
|
+
message: `role alias key "${key}" uses the reserved "$" prefix \u2014 that namespace is the engine's stored spelling for the universal fulfiller. Use "*" to mean "fulfills any gate", or rename the role.`
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
const TODOLIST_OF = [
|
|
90
|
+
{ type: "string", name: "label", title: "Label" },
|
|
91
|
+
{ type: "string", name: "status", title: "Status" },
|
|
92
|
+
{ type: "assignee", name: "assignee", title: "Assignee" },
|
|
93
|
+
{ type: "date", name: "dueDate", title: "Due date" }
|
|
94
|
+
], NOTES_OF = [
|
|
95
|
+
{ type: "text", name: "body", title: "Body" },
|
|
96
|
+
{ type: "actor", name: "actor", title: "Actor" },
|
|
97
|
+
{ type: "datetime", name: "at", title: "At" }
|
|
98
|
+
];
|
|
81
99
|
function desugarFieldEntries(entries, path, ctx) {
|
|
82
|
-
return !entries || entries.length === 0 ? entries === void 0 ? void 0 : [] : entries.map((entry, i) =>
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
100
|
+
return !entries || entries.length === 0 ? entries === void 0 ? void 0 : [] : entries.map((entry, i) => desugarFieldEntry(entry, [...path, i], ctx));
|
|
101
|
+
}
|
|
102
|
+
function desugarFieldEntry(entry, path, ctx) {
|
|
103
|
+
if (entry.type === "claim") return desugarClaimField(entry, path, ctx);
|
|
104
|
+
if (entry.type === "todoList" || entry.type === "notes") return desugarListField(entry, path, ctx);
|
|
105
|
+
const editable = normalizeEditable(entry.editable, [...path, "editable"], ctx);
|
|
106
|
+
return {
|
|
107
|
+
...stripUndefined({
|
|
108
|
+
type: entry.type,
|
|
109
|
+
name: entry.name,
|
|
110
|
+
title: entry.title,
|
|
111
|
+
description: entry.description,
|
|
112
|
+
required: entry.required,
|
|
113
|
+
initialValue: entry.initialValue,
|
|
114
|
+
editable,
|
|
115
|
+
fields: entry.fields,
|
|
116
|
+
of: entry.of
|
|
117
|
+
})
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function desugarClaimField(entry, path, ctx) {
|
|
121
|
+
const desugared = {
|
|
122
|
+
...stripUndefined({ name: entry.name, title: entry.title, description: entry.description }),
|
|
123
|
+
type: "actor"
|
|
124
|
+
};
|
|
125
|
+
return ctx.claimFields.set(desugared, { name: entry.name, path }), desugared;
|
|
126
|
+
}
|
|
127
|
+
function desugarListField(entry, path, ctx) {
|
|
128
|
+
const editable = normalizeEditable(entry.editable, [...path, "editable"], ctx);
|
|
129
|
+
return {
|
|
130
|
+
...stripUndefined({
|
|
131
|
+
name: entry.name,
|
|
132
|
+
title: entry.title,
|
|
133
|
+
description: entry.description,
|
|
134
|
+
required: entry.required,
|
|
135
|
+
initialValue: entry.initialValue,
|
|
136
|
+
editable
|
|
137
|
+
}),
|
|
138
|
+
type: "array",
|
|
139
|
+
of: entry.type === "todoList" ? TODOLIST_OF : NOTES_OF
|
|
140
|
+
};
|
|
104
141
|
}
|
|
105
142
|
function normalizeEditable(editable, path, ctx) {
|
|
106
143
|
if (editable === void 0 || editable === !0) return editable;
|
|
@@ -117,12 +154,12 @@ function normalizeEditable(editable, path, ctx) {
|
|
|
117
154
|
}
|
|
118
155
|
return editable;
|
|
119
156
|
}
|
|
120
|
-
function editableSlotNames(workflowFields, stageFields,
|
|
157
|
+
function editableSlotNames(workflowFields, stageFields, activities) {
|
|
121
158
|
const names = /* @__PURE__ */ new Set(), collect = (entries) => {
|
|
122
159
|
for (const entry of entries ?? []) entry.editable !== void 0 && names.add(entry.name);
|
|
123
160
|
};
|
|
124
161
|
collect(workflowFields), collect(stageFields);
|
|
125
|
-
for (const
|
|
162
|
+
for (const activity of activities) collect(activity.fields);
|
|
126
163
|
return names;
|
|
127
164
|
}
|
|
128
165
|
function desugarStageEditable(overrides, inScope, path, ctx) {
|
|
@@ -132,7 +169,7 @@ function desugarStageEditable(overrides, inScope, path, ctx) {
|
|
|
132
169
|
if (!inScope.has(name)) {
|
|
133
170
|
ctx.issues.push({
|
|
134
171
|
path: [...path, name],
|
|
135
|
-
message: `stage editable override "${name}" does not narrow an editable slot in scope \u2014 name a workflow/stage/
|
|
172
|
+
message: `stage editable override "${name}" does not narrow an editable slot in scope \u2014 name a workflow/stage/activity slot of this stage that declares \`editable\``
|
|
136
173
|
});
|
|
137
174
|
continue;
|
|
138
175
|
}
|
|
@@ -144,35 +181,46 @@ function desugarStageEditable(overrides, inScope, path, ctx) {
|
|
|
144
181
|
function layerOf(entries) {
|
|
145
182
|
return new Map((entries ?? []).map((entry) => [entry.name, entry]));
|
|
146
183
|
}
|
|
147
|
-
function
|
|
148
|
-
const
|
|
149
|
-
layers: [{ scope: "
|
|
150
|
-
}, ops = desugarOps(
|
|
151
|
-
(action, a) => desugarAction(action, [...path, "actions", a], env,
|
|
152
|
-
);
|
|
184
|
+
function desugarActivity(activity, path, stageEnv, ctx) {
|
|
185
|
+
const activityFields = desugarFieldEntries(activity.fields, [...path, "fields"], ctx), env = {
|
|
186
|
+
layers: [{ scope: "activity", entries: layerOf(activityFields) }, ...stageEnv.layers]
|
|
187
|
+
}, ops = desugarOps(activity.ops, [...path, "ops"], env, activity.name, ctx), actions = (activity.actions ?? []).map(
|
|
188
|
+
(action, a) => desugarAction(action, [...path, "actions", a], env, activity.name, ctx)
|
|
189
|
+
), target = desugarTarget(activity.target, env, [...path, "target"], ctx);
|
|
153
190
|
return {
|
|
154
191
|
...stripUndefined({
|
|
155
|
-
name:
|
|
156
|
-
title:
|
|
157
|
-
description:
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
192
|
+
name: activity.name,
|
|
193
|
+
title: activity.title,
|
|
194
|
+
description: activity.description,
|
|
195
|
+
kind: activity.kind,
|
|
196
|
+
filter: activity.filter,
|
|
197
|
+
requirements: activity.requirements,
|
|
198
|
+
completeWhen: activity.completeWhen,
|
|
199
|
+
failWhen: activity.failWhen,
|
|
200
|
+
effects: activity.effects,
|
|
201
|
+
subworkflows: activity.subworkflows
|
|
164
202
|
}),
|
|
165
|
-
activation:
|
|
166
|
-
...
|
|
203
|
+
activation: activity.activation ?? "manual",
|
|
204
|
+
...target ? { target } : {},
|
|
205
|
+
...activityFields ? { fields: activityFields } : {},
|
|
167
206
|
...ops ? { ops } : {},
|
|
168
207
|
...actions.length > 0 ? { actions } : {}
|
|
169
208
|
};
|
|
170
209
|
}
|
|
171
|
-
|
|
210
|
+
const TARGET_DOC_KINDS = ["doc.ref", "doc.refs", "release.ref"];
|
|
211
|
+
function desugarTarget(target, env, path, ctx) {
|
|
212
|
+
if (target === void 0 || target.type === "url") return target;
|
|
213
|
+
const ref = typeof target.field == "string" ? { field: target.field } : target.field, field = resolveRef(ref, env, [...path, "field"], ctx) ?? fallbackRef(ref), entry = entryAt(env, field);
|
|
214
|
+
return entry && !TARGET_DOC_KINDS.includes(entry.type) && ctx.issues.push({
|
|
215
|
+
path: [...path, "field"],
|
|
216
|
+
message: `manual activity target references "${field.field}" of kind "${entry.type}" \u2014 a deep-link target needs a document-valued entry (${TARGET_DOC_KINDS.join(", ")})`
|
|
217
|
+
}), { type: "field", field };
|
|
218
|
+
}
|
|
219
|
+
function desugarAction(action, path, env, activityName, ctx) {
|
|
172
220
|
if ("type" in action)
|
|
173
221
|
return desugarClaimAction(action, path, env, ctx);
|
|
174
|
-
const filter = schema.andConditions([rolesCondition(action.roles, ctx.roleAliases), action.filter]), ops = desugarOps(action.ops, [...path, "ops"], env,
|
|
175
|
-
return action.status !== void 0 && ops.push({ type: "status.set",
|
|
222
|
+
const filter = schema.andConditions([rolesCondition(action.roles, ctx.roleAliases), action.filter]), ops = desugarOps(action.ops, [...path, "ops"], env, activityName, ctx) ?? [];
|
|
223
|
+
return action.status !== void 0 && ops.push({ type: "status.set", activity: activityName, status: action.status }), {
|
|
176
224
|
...stripUndefined({
|
|
177
225
|
name: action.name,
|
|
178
226
|
title: action.title,
|
|
@@ -188,9 +236,9 @@ function desugarClaimAction(action, path, env, ctx) {
|
|
|
188
236
|
const ref = typeof action.field == "string" ? { field: action.field } : action.field, resolved = resolveRef(ref, env, [...path, "field"], ctx);
|
|
189
237
|
if (resolved) {
|
|
190
238
|
const entry = entryAt(env, resolved);
|
|
191
|
-
entry && entry.type !== "
|
|
239
|
+
entry && entry.type !== "actor" && ctx.issues.push({
|
|
192
240
|
path: [...path, "field"],
|
|
193
|
-
message: `claim action "${action.name}" references "${resolved.field}" of kind "${entry.type}" \u2014 a claim pair needs an actor-valued entry (a "claim" or "
|
|
241
|
+
message: `claim action "${action.name}" references "${resolved.field}" of kind "${entry.type}" \u2014 a claim pair needs an actor-valued entry (a "claim" or "actor" field declaration)`
|
|
194
242
|
}), checkShadowedClaimTarget(action.name, ref.field, resolved, env, [...path, "field"], ctx), entry && ctx.claimedFields.add(entry);
|
|
195
243
|
}
|
|
196
244
|
const noSteal = `!defined($fields.${ref.field})`, filter = schema.andConditions([
|
|
@@ -221,7 +269,7 @@ function checkUnclaimedClaimFields(ctx) {
|
|
|
221
269
|
for (const [entry, { name, path }] of ctx.claimFields)
|
|
222
270
|
ctx.claimedFields.has(entry) || ctx.issues.push({
|
|
223
271
|
path,
|
|
224
|
-
message: `claim field "${name}" is never referenced by a claim action \u2014 you announced the pattern and wrote half of it. Declare a defineAction({ type: "claim", field: "${name}" }) or use a raw
|
|
272
|
+
message: `claim field "${name}" is never referenced by a claim action \u2014 you announced the pattern and wrote half of it. Declare a defineAction({ type: "claim", field: "${name}" }) or use a raw actor entry`
|
|
225
273
|
});
|
|
226
274
|
}
|
|
227
275
|
function desugarTransition(transition, path, stageEnv, ctx) {
|
|
@@ -234,21 +282,21 @@ function desugarTransition(transition, path, stageEnv, ctx) {
|
|
|
234
282
|
to: transition.to,
|
|
235
283
|
effects: transition.effects
|
|
236
284
|
}),
|
|
237
|
-
filter: transition.filter ?? "$
|
|
285
|
+
filter: transition.filter ?? "$allActivitiesDone",
|
|
238
286
|
...ops ? { ops } : {}
|
|
239
287
|
};
|
|
240
288
|
}
|
|
241
|
-
function desugarOps(ops, path, env,
|
|
289
|
+
function desugarOps(ops, path, env, firingActivity, ctx) {
|
|
242
290
|
if (ops)
|
|
243
|
-
return ops.map((op, i) => desugarOp(op, [...path, i], env,
|
|
291
|
+
return ops.map((op, i) => desugarOp(op, [...path, i], env, firingActivity, ctx));
|
|
244
292
|
}
|
|
245
|
-
function desugarOp(op, path, env,
|
|
293
|
+
function desugarOp(op, path, env, firingActivity, ctx) {
|
|
246
294
|
if (op.type === "status.set") {
|
|
247
|
-
const
|
|
248
|
-
return
|
|
249
|
-
path: [...path, "
|
|
250
|
-
message: "status.set outside an action must name its target
|
|
251
|
-
}), { type: "status.set",
|
|
295
|
+
const activity = op.activity ?? firingActivity;
|
|
296
|
+
return activity === void 0 && ctx.issues.push({
|
|
297
|
+
path: [...path, "activity"],
|
|
298
|
+
message: "status.set outside an action must name its target activity"
|
|
299
|
+
}), { type: "status.set", activity: activity ?? "", status: op.status };
|
|
252
300
|
}
|
|
253
301
|
if (op.type === "audit") return desugarAuditOp(op, path, env, ctx);
|
|
254
302
|
const target = resolveRef(op.target, env, [...path, "target"], ctx) ?? fallbackRef(op.target);
|
|
@@ -312,14 +360,14 @@ const RESERVED_CONDITION_VARS = [
|
|
|
312
360
|
"row",
|
|
313
361
|
"now",
|
|
314
362
|
"effects",
|
|
315
|
-
"
|
|
363
|
+
"activities",
|
|
316
364
|
"subworkflows",
|
|
317
365
|
"self",
|
|
318
366
|
"stage",
|
|
319
367
|
"parent",
|
|
320
368
|
"ancestors",
|
|
321
|
-
"
|
|
322
|
-
"
|
|
369
|
+
"allActivitiesDone",
|
|
370
|
+
"anyActivityFailed"
|
|
323
371
|
], PREDICATE_CALLER_VARS = ["actor", "assigned", "can"];
|
|
324
372
|
function knownList(ids) {
|
|
325
373
|
return [...ids].map((s) => `"${s}"`).join(", ");
|
|
@@ -337,12 +385,12 @@ function checkStages(def, issues) {
|
|
|
337
385
|
issues
|
|
338
386
|
);
|
|
339
387
|
for (const [i, stage] of def.stages.entries()) {
|
|
340
|
-
const
|
|
341
|
-
(stage.
|
|
342
|
-
name:
|
|
343
|
-
path: ["stages", i, "
|
|
388
|
+
const activityNames = checkDuplicates(
|
|
389
|
+
(stage.activities ?? []).map((activity, j) => ({
|
|
390
|
+
name: activity.name,
|
|
391
|
+
path: ["stages", i, "activities", j, "name"]
|
|
344
392
|
})),
|
|
345
|
-
`
|
|
393
|
+
`activity name in stage "${stage.name}"`,
|
|
346
394
|
issues
|
|
347
395
|
);
|
|
348
396
|
checkDuplicates(
|
|
@@ -352,34 +400,34 @@ function checkStages(def, issues) {
|
|
|
352
400
|
})),
|
|
353
401
|
`transition name in stage "${stage.name}"`,
|
|
354
402
|
issues
|
|
355
|
-
),
|
|
403
|
+
), checkActivities(def, i, activityNames, issues);
|
|
356
404
|
}
|
|
357
405
|
return stageNames;
|
|
358
406
|
}
|
|
359
|
-
function
|
|
407
|
+
function checkActivities(def, i, activityNames, issues) {
|
|
360
408
|
const stage = def.stages[i];
|
|
361
|
-
for (const [j,
|
|
362
|
-
const path = ["stages", i, "
|
|
409
|
+
for (const [j, activity] of (stage.activities ?? []).entries()) {
|
|
410
|
+
const path = ["stages", i, "activities", j];
|
|
363
411
|
checkDuplicates(
|
|
364
|
-
(
|
|
412
|
+
(activity.actions ?? []).map((action, a) => ({
|
|
365
413
|
name: action.name,
|
|
366
414
|
path: [...path, "actions", a, "name"]
|
|
367
415
|
})),
|
|
368
|
-
`action name in
|
|
416
|
+
`action name in activity "${activity.name}"`,
|
|
369
417
|
issues
|
|
370
|
-
), checkStatusSetTargets(
|
|
418
|
+
), checkStatusSetTargets(activity, activityNames, path, issues);
|
|
371
419
|
}
|
|
372
420
|
}
|
|
373
|
-
function checkStatusSetTargets(
|
|
374
|
-
checkStatusSetOps(
|
|
375
|
-
for (const [a, action] of (
|
|
376
|
-
checkStatusSetOps(action.ops,
|
|
421
|
+
function checkStatusSetTargets(activity, activityNames, path, issues) {
|
|
422
|
+
checkStatusSetOps(activity.ops, activityNames, [...path, "ops"], issues);
|
|
423
|
+
for (const [a, action] of (activity.actions ?? []).entries())
|
|
424
|
+
checkStatusSetOps(action.ops, activityNames, [...path, "actions", a, "ops"], issues);
|
|
377
425
|
}
|
|
378
|
-
function checkStatusSetOps(ops,
|
|
426
|
+
function checkStatusSetOps(ops, activityNames, path, issues) {
|
|
379
427
|
for (const [o, op] of (ops ?? []).entries())
|
|
380
|
-
op.type !== "status.set" ||
|
|
381
|
-
path: [...path, o, "
|
|
382
|
-
message: `status.set targets
|
|
428
|
+
op.type !== "status.set" || activityNames.has(op.activity) || issues.push({
|
|
429
|
+
path: [...path, o, "activity"],
|
|
430
|
+
message: `status.set targets activity "${op.activity}" which is not declared in this stage. Known activities: ${knownList(activityNames)}`
|
|
383
431
|
});
|
|
384
432
|
}
|
|
385
433
|
function checkInitialStage(def, stageNames, issues) {
|
|
@@ -399,10 +447,14 @@ function checkTransitionTargets(def, stageNames, issues) {
|
|
|
399
447
|
function checkEffectNames(def, issues) {
|
|
400
448
|
const sites = [];
|
|
401
449
|
for (const [i, stage] of def.stages.entries()) {
|
|
402
|
-
for (const [j,
|
|
403
|
-
collectEffects(
|
|
404
|
-
for (const [a, action] of (
|
|
405
|
-
collectEffects(
|
|
450
|
+
for (const [j, activity] of (stage.activities ?? []).entries()) {
|
|
451
|
+
collectEffects(activity.effects, ["stages", i, "activities", j, "effects"], sites);
|
|
452
|
+
for (const [a, action] of (activity.actions ?? []).entries())
|
|
453
|
+
collectEffects(
|
|
454
|
+
action.effects,
|
|
455
|
+
["stages", i, "activities", j, "actions", a, "effects"],
|
|
456
|
+
sites
|
|
457
|
+
);
|
|
406
458
|
}
|
|
407
459
|
for (const [k, t] of (stage.transitions ?? []).entries())
|
|
408
460
|
collectEffects(t.effects, ["stages", i, "transitions", k, "effects"], sites);
|
|
@@ -437,12 +489,12 @@ function fieldScopes(def) {
|
|
|
437
489
|
path: ["stages", i, "fields"],
|
|
438
490
|
label: `stage "${stage.name}" fields`
|
|
439
491
|
});
|
|
440
|
-
for (const [j,
|
|
492
|
+
for (const [j, activity] of (stage.activities ?? []).entries())
|
|
441
493
|
scopes.push({
|
|
442
|
-
entries:
|
|
443
|
-
scope: "
|
|
444
|
-
path: ["stages", i, "
|
|
445
|
-
label: `
|
|
494
|
+
entries: activity.fields,
|
|
495
|
+
scope: "activity",
|
|
496
|
+
path: ["stages", i, "activities", j, "fields"],
|
|
497
|
+
label: `activity "${activity.name}" fields`
|
|
446
498
|
});
|
|
447
499
|
}
|
|
448
500
|
return scopes;
|
|
@@ -450,13 +502,20 @@ function fieldScopes(def) {
|
|
|
450
502
|
function checkRequiredField(def, issues) {
|
|
451
503
|
for (const { entries, scope, path, label } of fieldScopes(def))
|
|
452
504
|
for (const [n, entry] of (entries ?? []).entries())
|
|
453
|
-
entry.required === !0
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
505
|
+
if (entry.required === !0) {
|
|
506
|
+
if (scope !== "workflow")
|
|
507
|
+
issues.push({
|
|
508
|
+
path: [...path, n, "required"],
|
|
509
|
+
message: `${label} entry "${entry.name}" is \`required\`, but \`required\` applies only to workflow-scope \`input\` entries \u2014 only those are caller-supplied at start/spawn`
|
|
510
|
+
});
|
|
511
|
+
else if (entry.initialValue?.type !== "input") {
|
|
512
|
+
const seed = entry.initialValue?.type ?? "working memory (no initialValue)";
|
|
513
|
+
issues.push({
|
|
514
|
+
path: [...path, n, "required"],
|
|
515
|
+
message: `field entry "${entry.name}" has initialValue "${seed}" \u2014 \`required\` applies only to \`input\`-sourced entries (the caller fills them at start/spawn)`
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
}
|
|
460
519
|
}
|
|
461
520
|
function checkFieldEntryNames(def, issues) {
|
|
462
521
|
for (const { entries, path, label } of fieldScopes(def))
|
|
@@ -512,34 +571,34 @@ function nonActionFilterConditionSites(def) {
|
|
|
512
571
|
`transition "${t.name}"`,
|
|
513
572
|
sites
|
|
514
573
|
);
|
|
515
|
-
for (const [j,
|
|
516
|
-
|
|
574
|
+
for (const [j, activity] of (stage.activities ?? []).entries())
|
|
575
|
+
collectActivityConditionSites(activity, ["stages", i, "activities", j], sites);
|
|
517
576
|
}
|
|
518
577
|
return sites;
|
|
519
578
|
}
|
|
520
|
-
function
|
|
579
|
+
function collectActivityConditionSites(activity, path, sites) {
|
|
521
580
|
for (const field of ["filter", "completeWhen", "failWhen"]) {
|
|
522
|
-
const groq2 =
|
|
523
|
-
groq2 !== void 0 && sites.push({ groq: groq2, path: [...path, field], label: `
|
|
581
|
+
const groq2 = activity[field];
|
|
582
|
+
groq2 !== void 0 && sites.push({ groq: groq2, path: [...path, field], label: `activity "${activity.name}".${field}` });
|
|
524
583
|
}
|
|
525
|
-
collectBindingSites(
|
|
526
|
-
for (const [a, action] of (
|
|
584
|
+
collectBindingSites(activity.effects, [...path, "effects"], `activity "${activity.name}"`, sites);
|
|
585
|
+
for (const [a, action] of (activity.actions ?? []).entries())
|
|
527
586
|
collectBindingSites(
|
|
528
587
|
action.effects,
|
|
529
588
|
[...path, "actions", a, "effects"],
|
|
530
589
|
`action "${action.name}"`,
|
|
531
590
|
sites
|
|
532
591
|
);
|
|
533
|
-
collectSubworkflowSites(
|
|
592
|
+
collectSubworkflowSites(activity, path, sites);
|
|
534
593
|
}
|
|
535
|
-
function collectSubworkflowSites(
|
|
536
|
-
const sub =
|
|
594
|
+
function collectSubworkflowSites(activity, path, sites) {
|
|
595
|
+
const sub = activity.subworkflows;
|
|
537
596
|
if (sub === void 0) return;
|
|
538
597
|
const subPath = [...path, "subworkflows"];
|
|
539
598
|
sites.push({
|
|
540
599
|
groq: sub.forEach,
|
|
541
600
|
path: [...subPath, "forEach"],
|
|
542
|
-
label: `
|
|
601
|
+
label: `activity "${activity.name}".subworkflows.forEach`
|
|
543
602
|
});
|
|
544
603
|
for (const [group, record] of [
|
|
545
604
|
["with", sub.with],
|
|
@@ -549,7 +608,7 @@ function collectSubworkflowSites(task, path, sites) {
|
|
|
549
608
|
sites.push({
|
|
550
609
|
groq: groq2,
|
|
551
610
|
path: [...subPath, group, key],
|
|
552
|
-
label: `
|
|
611
|
+
label: `activity "${activity.name}".subworkflows.${group} "${key}"`
|
|
553
612
|
});
|
|
554
613
|
}
|
|
555
614
|
function collectBindingSites(effects, path, label, sites) {
|
|
@@ -568,9 +627,49 @@ function checkAssigneesEntries(def, issues) {
|
|
|
568
627
|
message: "at most one assignees-kind field entry per scope \u2014 the inbox reads it by kind"
|
|
569
628
|
});
|
|
570
629
|
}
|
|
630
|
+
function activityShape(activity) {
|
|
631
|
+
return {
|
|
632
|
+
hasActions: (activity.actions ?? []).length > 0,
|
|
633
|
+
hasEffects: (activity.effects ?? []).length > 0,
|
|
634
|
+
hasCompleteWhen: activity.completeWhen !== void 0,
|
|
635
|
+
hasSubworkflows: activity.subworkflows !== void 0
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
const KIND_SHAPE_RULES = {
|
|
639
|
+
user: (s) => s.hasActions ? [] : ["needs at least one action for a person to fire"],
|
|
640
|
+
service: (s) => s.hasEffects ? [] : ["needs at least one effect \u2014 the automated work it performs"],
|
|
641
|
+
manual: (s) => [
|
|
642
|
+
s.hasActions || s.hasCompleteWhen ? void 0 : "needs a way to complete \u2014 an action to acknowledge it, or a `completeWhen` predicate to verify it (otherwise it auto-resolves on activation)",
|
|
643
|
+
s.hasSubworkflows ? "is off-system work, not a fan-out \u2014 drop `subworkflows` or move it to its own activity" : void 0
|
|
644
|
+
].filter((m) => m !== void 0),
|
|
645
|
+
receive: (s) => [
|
|
646
|
+
s.hasCompleteWhen || s.hasSubworkflows ? void 0 : "needs a `completeWhen` (or `subworkflows`) condition to wait on",
|
|
647
|
+
s.hasActions ? "has no actor, so it cannot carry actions" : void 0
|
|
648
|
+
].filter((m) => m !== void 0),
|
|
649
|
+
script: (s) => [
|
|
650
|
+
s.hasActions ? "runs inline with no actor, so it cannot carry actions" : void 0,
|
|
651
|
+
s.hasCompleteWhen ? "resolves on activation, so it cannot carry a `completeWhen` (that is a `receive`/`service` wait)" : void 0,
|
|
652
|
+
s.hasSubworkflows ? "resolves on activation, so it cannot fan out with `subworkflows`" : void 0
|
|
653
|
+
].filter((m) => m !== void 0)
|
|
654
|
+
};
|
|
655
|
+
function checkActivityKinds(def, issues) {
|
|
656
|
+
for (const [i, stage] of def.stages.entries())
|
|
657
|
+
for (const [j, activity] of (stage.activities ?? []).entries()) {
|
|
658
|
+
const path = ["stages", i, "activities", j];
|
|
659
|
+
if (activity.target !== void 0 && activity.kind !== "manual" && issues.push({
|
|
660
|
+
path: [...path, "target"],
|
|
661
|
+
message: `activity "${activity.name}" has a \`target\` but is not \`kind: "manual"\` \u2014 a target is the deep-link for off-system manual work`
|
|
662
|
+
}), activity.kind !== void 0)
|
|
663
|
+
for (const fault of KIND_SHAPE_RULES[activity.kind](activityShape(activity)))
|
|
664
|
+
issues.push({
|
|
665
|
+
path: [...path, "kind"],
|
|
666
|
+
message: `activity "${activity.name}" declares kind "${activity.kind}" but ${fault}`
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
}
|
|
571
670
|
function checkWorkflowInvariants(def) {
|
|
572
671
|
const issues = [], stageNames = checkStages(def, issues);
|
|
573
|
-
return checkInitialStage(def, stageNames, issues), checkTransitionTargets(def, stageNames, issues), checkEffectNames(def, issues), checkGuardNames(def, issues), checkFieldEntryNames(def, issues), checkRequiredField(def, issues), checkPredicates(def, issues), checkCanOutsideActionFilters(def, issues), checkAssigneesEntries(def, issues), issues;
|
|
672
|
+
return checkInitialStage(def, stageNames, issues), checkTransitionTargets(def, stageNames, issues), checkEffectNames(def, issues), checkGuardNames(def, issues), checkFieldEntryNames(def, issues), checkRequiredField(def, issues), checkPredicates(def, issues), checkCanOutsideActionFilters(def, issues), checkAssigneesEntries(def, issues), checkActivityKinds(def, issues), issues;
|
|
574
673
|
}
|
|
575
674
|
function defineWorkflow(definition) {
|
|
576
675
|
const label = labelFor("defineWorkflow", definition), parsed = parseOrThrow(schema.AuthoringWorkflowSchema, definition, label), { definition: stored, issues: desugarIssues } = desugarWorkflow(parsed), issues = [...desugarIssues, ...checkWorkflowInvariants(stored)];
|
|
@@ -580,8 +679,8 @@ function defineWorkflow(definition) {
|
|
|
580
679
|
function defineStage(stage) {
|
|
581
680
|
return parseOrThrow(schema.AuthoringStageSchema, stage, labelFor("defineStage", stage));
|
|
582
681
|
}
|
|
583
|
-
function
|
|
584
|
-
return parseOrThrow(schema.
|
|
682
|
+
function defineActivity(activity) {
|
|
683
|
+
return parseOrThrow(schema.AuthoringActivitySchema, activity, labelFor("defineActivity", activity));
|
|
585
684
|
}
|
|
586
685
|
function defineAction(action) {
|
|
587
686
|
return parseOrThrow(schema.AuthoringActionSchema, action, labelFor("defineAction", action));
|
|
@@ -623,13 +722,13 @@ function labelFor(fn, value) {
|
|
|
623
722
|
return fn;
|
|
624
723
|
}
|
|
625
724
|
exports.defineAction = defineAction;
|
|
725
|
+
exports.defineActivity = defineActivity;
|
|
626
726
|
exports.defineEffect = defineEffect;
|
|
627
727
|
exports.defineEffectDescriptor = defineEffectDescriptor;
|
|
628
728
|
exports.defineField = defineField;
|
|
629
729
|
exports.defineGuard = defineGuard;
|
|
630
730
|
exports.defineOp = defineOp;
|
|
631
731
|
exports.defineStage = defineStage;
|
|
632
|
-
exports.defineTask = defineTask;
|
|
633
732
|
exports.defineTransition = defineTransition;
|
|
634
733
|
exports.defineWorkflow = defineWorkflow;
|
|
635
734
|
exports.groq = groq;
|