@sanity/workflow-engine 0.11.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 +158 -79
- package/dist/_chunks-cjs/schema.cjs.map +1 -1
- package/dist/_chunks-es/schema.js +159 -80
- package/dist/_chunks-es/schema.js.map +1 -1
- package/dist/define.cjs +185 -137
- package/dist/define.cjs.map +1 -1
- package/dist/define.d.cts +6180 -3587
- package/dist/define.d.ts +6180 -3587
- package/dist/define.js +186 -138
- package/dist/define.js.map +1 -1
- package/dist/index.cjs +1103 -946
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10238 -6915
- package/dist/index.d.ts +10238 -6915
- package/dist/index.js +1105 -948
- 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,28 +181,28 @@ 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
|
-
), target = desugarTarget(
|
|
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
|
-
kind:
|
|
159
|
-
filter:
|
|
160
|
-
requirements:
|
|
161
|
-
completeWhen:
|
|
162
|
-
failWhen:
|
|
163
|
-
effects:
|
|
164
|
-
subworkflows:
|
|
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
|
|
165
202
|
}),
|
|
166
|
-
activation:
|
|
203
|
+
activation: activity.activation ?? "manual",
|
|
167
204
|
...target ? { target } : {},
|
|
168
|
-
...
|
|
205
|
+
...activityFields ? { fields: activityFields } : {},
|
|
169
206
|
...ops ? { ops } : {},
|
|
170
207
|
...actions.length > 0 ? { actions } : {}
|
|
171
208
|
};
|
|
@@ -176,14 +213,14 @@ function desugarTarget(target, env, path, ctx) {
|
|
|
176
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);
|
|
177
214
|
return entry && !TARGET_DOC_KINDS.includes(entry.type) && ctx.issues.push({
|
|
178
215
|
path: [...path, "field"],
|
|
179
|
-
message: `manual
|
|
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(", ")})`
|
|
180
217
|
}), { type: "field", field };
|
|
181
218
|
}
|
|
182
|
-
function desugarAction(action, path, env,
|
|
219
|
+
function desugarAction(action, path, env, activityName, ctx) {
|
|
183
220
|
if ("type" in action)
|
|
184
221
|
return desugarClaimAction(action, path, env, ctx);
|
|
185
|
-
const filter = schema.andConditions([rolesCondition(action.roles, ctx.roleAliases), action.filter]), ops = desugarOps(action.ops, [...path, "ops"], env,
|
|
186
|
-
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 }), {
|
|
187
224
|
...stripUndefined({
|
|
188
225
|
name: action.name,
|
|
189
226
|
title: action.title,
|
|
@@ -199,9 +236,9 @@ function desugarClaimAction(action, path, env, ctx) {
|
|
|
199
236
|
const ref = typeof action.field == "string" ? { field: action.field } : action.field, resolved = resolveRef(ref, env, [...path, "field"], ctx);
|
|
200
237
|
if (resolved) {
|
|
201
238
|
const entry = entryAt(env, resolved);
|
|
202
|
-
entry && entry.type !== "
|
|
239
|
+
entry && entry.type !== "actor" && ctx.issues.push({
|
|
203
240
|
path: [...path, "field"],
|
|
204
|
-
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)`
|
|
205
242
|
}), checkShadowedClaimTarget(action.name, ref.field, resolved, env, [...path, "field"], ctx), entry && ctx.claimedFields.add(entry);
|
|
206
243
|
}
|
|
207
244
|
const noSteal = `!defined($fields.${ref.field})`, filter = schema.andConditions([
|
|
@@ -232,7 +269,7 @@ function checkUnclaimedClaimFields(ctx) {
|
|
|
232
269
|
for (const [entry, { name, path }] of ctx.claimFields)
|
|
233
270
|
ctx.claimedFields.has(entry) || ctx.issues.push({
|
|
234
271
|
path,
|
|
235
|
-
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`
|
|
236
273
|
});
|
|
237
274
|
}
|
|
238
275
|
function desugarTransition(transition, path, stageEnv, ctx) {
|
|
@@ -245,21 +282,21 @@ function desugarTransition(transition, path, stageEnv, ctx) {
|
|
|
245
282
|
to: transition.to,
|
|
246
283
|
effects: transition.effects
|
|
247
284
|
}),
|
|
248
|
-
filter: transition.filter ?? "$
|
|
285
|
+
filter: transition.filter ?? "$allActivitiesDone",
|
|
249
286
|
...ops ? { ops } : {}
|
|
250
287
|
};
|
|
251
288
|
}
|
|
252
|
-
function desugarOps(ops, path, env,
|
|
289
|
+
function desugarOps(ops, path, env, firingActivity, ctx) {
|
|
253
290
|
if (ops)
|
|
254
|
-
return ops.map((op, i) => desugarOp(op, [...path, i], env,
|
|
291
|
+
return ops.map((op, i) => desugarOp(op, [...path, i], env, firingActivity, ctx));
|
|
255
292
|
}
|
|
256
|
-
function desugarOp(op, path, env,
|
|
293
|
+
function desugarOp(op, path, env, firingActivity, ctx) {
|
|
257
294
|
if (op.type === "status.set") {
|
|
258
|
-
const
|
|
259
|
-
return
|
|
260
|
-
path: [...path, "
|
|
261
|
-
message: "status.set outside an action must name its target
|
|
262
|
-
}), { 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 };
|
|
263
300
|
}
|
|
264
301
|
if (op.type === "audit") return desugarAuditOp(op, path, env, ctx);
|
|
265
302
|
const target = resolveRef(op.target, env, [...path, "target"], ctx) ?? fallbackRef(op.target);
|
|
@@ -323,14 +360,14 @@ const RESERVED_CONDITION_VARS = [
|
|
|
323
360
|
"row",
|
|
324
361
|
"now",
|
|
325
362
|
"effects",
|
|
326
|
-
"
|
|
363
|
+
"activities",
|
|
327
364
|
"subworkflows",
|
|
328
365
|
"self",
|
|
329
366
|
"stage",
|
|
330
367
|
"parent",
|
|
331
368
|
"ancestors",
|
|
332
|
-
"
|
|
333
|
-
"
|
|
369
|
+
"allActivitiesDone",
|
|
370
|
+
"anyActivityFailed"
|
|
334
371
|
], PREDICATE_CALLER_VARS = ["actor", "assigned", "can"];
|
|
335
372
|
function knownList(ids) {
|
|
336
373
|
return [...ids].map((s) => `"${s}"`).join(", ");
|
|
@@ -348,12 +385,12 @@ function checkStages(def, issues) {
|
|
|
348
385
|
issues
|
|
349
386
|
);
|
|
350
387
|
for (const [i, stage] of def.stages.entries()) {
|
|
351
|
-
const
|
|
352
|
-
(stage.
|
|
353
|
-
name:
|
|
354
|
-
path: ["stages", i, "
|
|
388
|
+
const activityNames = checkDuplicates(
|
|
389
|
+
(stage.activities ?? []).map((activity, j) => ({
|
|
390
|
+
name: activity.name,
|
|
391
|
+
path: ["stages", i, "activities", j, "name"]
|
|
355
392
|
})),
|
|
356
|
-
`
|
|
393
|
+
`activity name in stage "${stage.name}"`,
|
|
357
394
|
issues
|
|
358
395
|
);
|
|
359
396
|
checkDuplicates(
|
|
@@ -363,34 +400,34 @@ function checkStages(def, issues) {
|
|
|
363
400
|
})),
|
|
364
401
|
`transition name in stage "${stage.name}"`,
|
|
365
402
|
issues
|
|
366
|
-
),
|
|
403
|
+
), checkActivities(def, i, activityNames, issues);
|
|
367
404
|
}
|
|
368
405
|
return stageNames;
|
|
369
406
|
}
|
|
370
|
-
function
|
|
407
|
+
function checkActivities(def, i, activityNames, issues) {
|
|
371
408
|
const stage = def.stages[i];
|
|
372
|
-
for (const [j,
|
|
373
|
-
const path = ["stages", i, "
|
|
409
|
+
for (const [j, activity] of (stage.activities ?? []).entries()) {
|
|
410
|
+
const path = ["stages", i, "activities", j];
|
|
374
411
|
checkDuplicates(
|
|
375
|
-
(
|
|
412
|
+
(activity.actions ?? []).map((action, a) => ({
|
|
376
413
|
name: action.name,
|
|
377
414
|
path: [...path, "actions", a, "name"]
|
|
378
415
|
})),
|
|
379
|
-
`action name in
|
|
416
|
+
`action name in activity "${activity.name}"`,
|
|
380
417
|
issues
|
|
381
|
-
), checkStatusSetTargets(
|
|
418
|
+
), checkStatusSetTargets(activity, activityNames, path, issues);
|
|
382
419
|
}
|
|
383
420
|
}
|
|
384
|
-
function checkStatusSetTargets(
|
|
385
|
-
checkStatusSetOps(
|
|
386
|
-
for (const [a, action] of (
|
|
387
|
-
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);
|
|
388
425
|
}
|
|
389
|
-
function checkStatusSetOps(ops,
|
|
426
|
+
function checkStatusSetOps(ops, activityNames, path, issues) {
|
|
390
427
|
for (const [o, op] of (ops ?? []).entries())
|
|
391
|
-
op.type !== "status.set" ||
|
|
392
|
-
path: [...path, o, "
|
|
393
|
-
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)}`
|
|
394
431
|
});
|
|
395
432
|
}
|
|
396
433
|
function checkInitialStage(def, stageNames, issues) {
|
|
@@ -410,10 +447,14 @@ function checkTransitionTargets(def, stageNames, issues) {
|
|
|
410
447
|
function checkEffectNames(def, issues) {
|
|
411
448
|
const sites = [];
|
|
412
449
|
for (const [i, stage] of def.stages.entries()) {
|
|
413
|
-
for (const [j,
|
|
414
|
-
collectEffects(
|
|
415
|
-
for (const [a, action] of (
|
|
416
|
-
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
|
+
);
|
|
417
458
|
}
|
|
418
459
|
for (const [k, t] of (stage.transitions ?? []).entries())
|
|
419
460
|
collectEffects(t.effects, ["stages", i, "transitions", k, "effects"], sites);
|
|
@@ -448,12 +489,12 @@ function fieldScopes(def) {
|
|
|
448
489
|
path: ["stages", i, "fields"],
|
|
449
490
|
label: `stage "${stage.name}" fields`
|
|
450
491
|
});
|
|
451
|
-
for (const [j,
|
|
492
|
+
for (const [j, activity] of (stage.activities ?? []).entries())
|
|
452
493
|
scopes.push({
|
|
453
|
-
entries:
|
|
454
|
-
scope: "
|
|
455
|
-
path: ["stages", i, "
|
|
456
|
-
label: `
|
|
494
|
+
entries: activity.fields,
|
|
495
|
+
scope: "activity",
|
|
496
|
+
path: ["stages", i, "activities", j, "fields"],
|
|
497
|
+
label: `activity "${activity.name}" fields`
|
|
457
498
|
});
|
|
458
499
|
}
|
|
459
500
|
return scopes;
|
|
@@ -461,13 +502,20 @@ function fieldScopes(def) {
|
|
|
461
502
|
function checkRequiredField(def, issues) {
|
|
462
503
|
for (const { entries, scope, path, label } of fieldScopes(def))
|
|
463
504
|
for (const [n, entry] of (entries ?? []).entries())
|
|
464
|
-
entry.required === !0
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
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
|
+
}
|
|
471
519
|
}
|
|
472
520
|
function checkFieldEntryNames(def, issues) {
|
|
473
521
|
for (const { entries, path, label } of fieldScopes(def))
|
|
@@ -523,34 +571,34 @@ function nonActionFilterConditionSites(def) {
|
|
|
523
571
|
`transition "${t.name}"`,
|
|
524
572
|
sites
|
|
525
573
|
);
|
|
526
|
-
for (const [j,
|
|
527
|
-
|
|
574
|
+
for (const [j, activity] of (stage.activities ?? []).entries())
|
|
575
|
+
collectActivityConditionSites(activity, ["stages", i, "activities", j], sites);
|
|
528
576
|
}
|
|
529
577
|
return sites;
|
|
530
578
|
}
|
|
531
|
-
function
|
|
579
|
+
function collectActivityConditionSites(activity, path, sites) {
|
|
532
580
|
for (const field of ["filter", "completeWhen", "failWhen"]) {
|
|
533
|
-
const groq2 =
|
|
534
|
-
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}` });
|
|
535
583
|
}
|
|
536
|
-
collectBindingSites(
|
|
537
|
-
for (const [a, action] of (
|
|
584
|
+
collectBindingSites(activity.effects, [...path, "effects"], `activity "${activity.name}"`, sites);
|
|
585
|
+
for (const [a, action] of (activity.actions ?? []).entries())
|
|
538
586
|
collectBindingSites(
|
|
539
587
|
action.effects,
|
|
540
588
|
[...path, "actions", a, "effects"],
|
|
541
589
|
`action "${action.name}"`,
|
|
542
590
|
sites
|
|
543
591
|
);
|
|
544
|
-
collectSubworkflowSites(
|
|
592
|
+
collectSubworkflowSites(activity, path, sites);
|
|
545
593
|
}
|
|
546
|
-
function collectSubworkflowSites(
|
|
547
|
-
const sub =
|
|
594
|
+
function collectSubworkflowSites(activity, path, sites) {
|
|
595
|
+
const sub = activity.subworkflows;
|
|
548
596
|
if (sub === void 0) return;
|
|
549
597
|
const subPath = [...path, "subworkflows"];
|
|
550
598
|
sites.push({
|
|
551
599
|
groq: sub.forEach,
|
|
552
600
|
path: [...subPath, "forEach"],
|
|
553
|
-
label: `
|
|
601
|
+
label: `activity "${activity.name}".subworkflows.forEach`
|
|
554
602
|
});
|
|
555
603
|
for (const [group, record] of [
|
|
556
604
|
["with", sub.with],
|
|
@@ -560,7 +608,7 @@ function collectSubworkflowSites(task, path, sites) {
|
|
|
560
608
|
sites.push({
|
|
561
609
|
groq: groq2,
|
|
562
610
|
path: [...subPath, group, key],
|
|
563
|
-
label: `
|
|
611
|
+
label: `activity "${activity.name}".subworkflows.${group} "${key}"`
|
|
564
612
|
});
|
|
565
613
|
}
|
|
566
614
|
function collectBindingSites(effects, path, label, sites) {
|
|
@@ -579,12 +627,12 @@ function checkAssigneesEntries(def, issues) {
|
|
|
579
627
|
message: "at most one assignees-kind field entry per scope \u2014 the inbox reads it by kind"
|
|
580
628
|
});
|
|
581
629
|
}
|
|
582
|
-
function
|
|
630
|
+
function activityShape(activity) {
|
|
583
631
|
return {
|
|
584
|
-
hasActions: (
|
|
585
|
-
hasEffects: (
|
|
586
|
-
hasCompleteWhen:
|
|
587
|
-
hasSubworkflows:
|
|
632
|
+
hasActions: (activity.actions ?? []).length > 0,
|
|
633
|
+
hasEffects: (activity.effects ?? []).length > 0,
|
|
634
|
+
hasCompleteWhen: activity.completeWhen !== void 0,
|
|
635
|
+
hasSubworkflows: activity.subworkflows !== void 0
|
|
588
636
|
};
|
|
589
637
|
}
|
|
590
638
|
const KIND_SHAPE_RULES = {
|
|
@@ -592,7 +640,7 @@ const KIND_SHAPE_RULES = {
|
|
|
592
640
|
service: (s) => s.hasEffects ? [] : ["needs at least one effect \u2014 the automated work it performs"],
|
|
593
641
|
manual: (s) => [
|
|
594
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)",
|
|
595
|
-
s.hasSubworkflows ? "is off-system work, not a fan-out \u2014 drop `subworkflows` or move it to its own
|
|
643
|
+
s.hasSubworkflows ? "is off-system work, not a fan-out \u2014 drop `subworkflows` or move it to its own activity" : void 0
|
|
596
644
|
].filter((m) => m !== void 0),
|
|
597
645
|
receive: (s) => [
|
|
598
646
|
s.hasCompleteWhen || s.hasSubworkflows ? void 0 : "needs a `completeWhen` (or `subworkflows`) condition to wait on",
|
|
@@ -604,24 +652,24 @@ const KIND_SHAPE_RULES = {
|
|
|
604
652
|
s.hasSubworkflows ? "resolves on activation, so it cannot fan out with `subworkflows`" : void 0
|
|
605
653
|
].filter((m) => m !== void 0)
|
|
606
654
|
};
|
|
607
|
-
function
|
|
655
|
+
function checkActivityKinds(def, issues) {
|
|
608
656
|
for (const [i, stage] of def.stages.entries())
|
|
609
|
-
for (const [j,
|
|
610
|
-
const path = ["stages", i, "
|
|
611
|
-
if (
|
|
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({
|
|
612
660
|
path: [...path, "target"],
|
|
613
|
-
message: `
|
|
614
|
-
}),
|
|
615
|
-
for (const fault of KIND_SHAPE_RULES[
|
|
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)))
|
|
616
664
|
issues.push({
|
|
617
665
|
path: [...path, "kind"],
|
|
618
|
-
message: `
|
|
666
|
+
message: `activity "${activity.name}" declares kind "${activity.kind}" but ${fault}`
|
|
619
667
|
});
|
|
620
668
|
}
|
|
621
669
|
}
|
|
622
670
|
function checkWorkflowInvariants(def) {
|
|
623
671
|
const issues = [], stageNames = checkStages(def, issues);
|
|
624
|
-
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),
|
|
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;
|
|
625
673
|
}
|
|
626
674
|
function defineWorkflow(definition) {
|
|
627
675
|
const label = labelFor("defineWorkflow", definition), parsed = parseOrThrow(schema.AuthoringWorkflowSchema, definition, label), { definition: stored, issues: desugarIssues } = desugarWorkflow(parsed), issues = [...desugarIssues, ...checkWorkflowInvariants(stored)];
|
|
@@ -631,8 +679,8 @@ function defineWorkflow(definition) {
|
|
|
631
679
|
function defineStage(stage) {
|
|
632
680
|
return parseOrThrow(schema.AuthoringStageSchema, stage, labelFor("defineStage", stage));
|
|
633
681
|
}
|
|
634
|
-
function
|
|
635
|
-
return parseOrThrow(schema.
|
|
682
|
+
function defineActivity(activity) {
|
|
683
|
+
return parseOrThrow(schema.AuthoringActivitySchema, activity, labelFor("defineActivity", activity));
|
|
636
684
|
}
|
|
637
685
|
function defineAction(action) {
|
|
638
686
|
return parseOrThrow(schema.AuthoringActionSchema, action, labelFor("defineAction", action));
|
|
@@ -674,13 +722,13 @@ function labelFor(fn, value) {
|
|
|
674
722
|
return fn;
|
|
675
723
|
}
|
|
676
724
|
exports.defineAction = defineAction;
|
|
725
|
+
exports.defineActivity = defineActivity;
|
|
677
726
|
exports.defineEffect = defineEffect;
|
|
678
727
|
exports.defineEffectDescriptor = defineEffectDescriptor;
|
|
679
728
|
exports.defineField = defineField;
|
|
680
729
|
exports.defineGuard = defineGuard;
|
|
681
730
|
exports.defineOp = defineOp;
|
|
682
731
|
exports.defineStage = defineStage;
|
|
683
|
-
exports.defineTask = defineTask;
|
|
684
732
|
exports.defineTransition = defineTransition;
|
|
685
733
|
exports.defineWorkflow = defineWorkflow;
|
|
686
734
|
exports.groq = groq;
|