@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.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as v from "valibot";
|
|
2
|
-
import { andConditions, expandRequiredRoles, GROQ_IDENTIFIER, formatValidationError, issuesFromValibot, AuthoringWorkflowSchema, AuthoringStageSchema,
|
|
2
|
+
import { andConditions, expandRequiredRoles, normalizeRoleAliases, GROQ_IDENTIFIER, formatValidationError, issuesFromValibot, AuthoringWorkflowSchema, AuthoringStageSchema, AuthoringActivitySchema, AuthoringActionSchema, AuthoringTransitionSchema, AuthoringFieldEntrySchema, AuthoringOpSchema, GuardSchema, EffectSchema } from "./_chunks-es/schema.js";
|
|
3
3
|
function groq(strings, ...values) {
|
|
4
4
|
return strings.reduce((out, part, i) => i === 0 ? part : `${out}${serializeGroqValue(values[i - 1])}${part}`, "");
|
|
5
5
|
}
|
|
@@ -12,24 +12,26 @@ function serializeGroqValue(value) {
|
|
|
12
12
|
return serialized;
|
|
13
13
|
}
|
|
14
14
|
function desugarWorkflow(authoring) {
|
|
15
|
-
const
|
|
16
|
-
|
|
15
|
+
const issues = [];
|
|
16
|
+
checkReservedRoleAliasKeys(authoring.roleAliases, issues);
|
|
17
|
+
const roleAliases = normalizeRoleAliases(authoring.roleAliases), ctx = {
|
|
18
|
+
issues,
|
|
17
19
|
claimFields: /* @__PURE__ */ new Map(),
|
|
18
20
|
claimedFields: /* @__PURE__ */ new Set(),
|
|
19
|
-
roleAliases
|
|
21
|
+
roleAliases
|
|
20
22
|
}, workflowFields = desugarFieldEntries(authoring.fields, ["fields"], ctx), workflowLayer = layerOf(workflowFields), stages = authoring.stages.map((stage, i) => {
|
|
21
23
|
const path = ["stages", i], stageFields = desugarFieldEntries(stage.fields, [...path, "fields"], ctx), stageEnv = {
|
|
22
24
|
layers: [
|
|
23
25
|
{ scope: "stage", entries: layerOf(stageFields) },
|
|
24
26
|
{ scope: "workflow", entries: workflowLayer }
|
|
25
27
|
]
|
|
26
|
-
},
|
|
27
|
-
(
|
|
28
|
+
}, activities = (stage.activities ?? []).map(
|
|
29
|
+
(activity, j) => desugarActivity(activity, [...path, "activities", j], stageEnv, ctx)
|
|
28
30
|
), transitions = (stage.transitions ?? []).map(
|
|
29
31
|
(transition, k) => desugarTransition(transition, [...path, "transitions", k], stageEnv, ctx)
|
|
30
32
|
), editable = desugarStageEditable(
|
|
31
33
|
stage.editable,
|
|
32
|
-
editableSlotNames(workflowFields, stageFields,
|
|
34
|
+
editableSlotNames(workflowFields, stageFields, activities),
|
|
33
35
|
[...path, "editable"],
|
|
34
36
|
ctx
|
|
35
37
|
);
|
|
@@ -41,7 +43,7 @@ function desugarWorkflow(authoring) {
|
|
|
41
43
|
guards: stage.guards
|
|
42
44
|
}),
|
|
43
45
|
...stageFields ? { fields: stageFields } : {},
|
|
44
|
-
...
|
|
46
|
+
...activities.length > 0 ? { activities } : {},
|
|
45
47
|
...transitions.length > 0 ? { transitions } : {},
|
|
46
48
|
...editable ? { editable } : {}
|
|
47
49
|
};
|
|
@@ -49,41 +51,76 @@ function desugarWorkflow(authoring) {
|
|
|
49
51
|
return checkUnclaimedClaimFields(ctx), { definition: {
|
|
50
52
|
...stripUndefined({
|
|
51
53
|
name: authoring.name,
|
|
52
|
-
version: authoring.version,
|
|
53
54
|
title: authoring.title,
|
|
54
55
|
description: authoring.description,
|
|
55
56
|
role: authoring.role,
|
|
56
57
|
initialStage: authoring.initialStage,
|
|
57
58
|
predicates: authoring.predicates,
|
|
58
|
-
roleAliases
|
|
59
|
+
roleAliases
|
|
59
60
|
}),
|
|
60
61
|
...workflowFields ? { fields: workflowFields } : {},
|
|
61
62
|
stages
|
|
62
63
|
}, issues: ctx.issues };
|
|
63
64
|
}
|
|
65
|
+
function checkReservedRoleAliasKeys(aliases, issues) {
|
|
66
|
+
for (const key of Object.keys(aliases ?? {}))
|
|
67
|
+
key.startsWith("$") && issues.push({
|
|
68
|
+
path: ["roleAliases", key],
|
|
69
|
+
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.`
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
const TODOLIST_OF = [
|
|
73
|
+
{ type: "string", name: "label", title: "Label" },
|
|
74
|
+
{ type: "string", name: "status", title: "Status" },
|
|
75
|
+
{ type: "assignee", name: "assignee", title: "Assignee" },
|
|
76
|
+
{ type: "date", name: "dueDate", title: "Due date" }
|
|
77
|
+
], NOTES_OF = [
|
|
78
|
+
{ type: "text", name: "body", title: "Body" },
|
|
79
|
+
{ type: "actor", name: "actor", title: "Actor" },
|
|
80
|
+
{ type: "datetime", name: "at", title: "At" }
|
|
81
|
+
];
|
|
64
82
|
function desugarFieldEntries(entries, path, ctx) {
|
|
65
|
-
return !entries || entries.length === 0 ? entries === void 0 ? void 0 : [] : entries.map((entry, i) =>
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
83
|
+
return !entries || entries.length === 0 ? entries === void 0 ? void 0 : [] : entries.map((entry, i) => desugarFieldEntry(entry, [...path, i], ctx));
|
|
84
|
+
}
|
|
85
|
+
function desugarFieldEntry(entry, path, ctx) {
|
|
86
|
+
if (entry.type === "claim") return desugarClaimField(entry, path, ctx);
|
|
87
|
+
if (entry.type === "todoList" || entry.type === "notes") return desugarListField(entry, path, ctx);
|
|
88
|
+
const editable = normalizeEditable(entry.editable, [...path, "editable"], ctx);
|
|
89
|
+
return {
|
|
90
|
+
...stripUndefined({
|
|
91
|
+
type: entry.type,
|
|
92
|
+
name: entry.name,
|
|
93
|
+
title: entry.title,
|
|
94
|
+
description: entry.description,
|
|
95
|
+
required: entry.required,
|
|
96
|
+
initialValue: entry.initialValue,
|
|
97
|
+
editable,
|
|
98
|
+
fields: entry.fields,
|
|
99
|
+
of: entry.of
|
|
100
|
+
})
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function desugarClaimField(entry, path, ctx) {
|
|
104
|
+
const desugared = {
|
|
105
|
+
...stripUndefined({ name: entry.name, title: entry.title, description: entry.description }),
|
|
106
|
+
type: "actor"
|
|
107
|
+
};
|
|
108
|
+
return ctx.claimFields.set(desugared, { name: entry.name, path }), desugared;
|
|
109
|
+
}
|
|
110
|
+
function desugarListField(entry, path, ctx) {
|
|
111
|
+
const editable = normalizeEditable(entry.editable, [...path, "editable"], ctx);
|
|
112
|
+
return {
|
|
113
|
+
...stripUndefined({
|
|
114
|
+
name: entry.name,
|
|
115
|
+
title: entry.title,
|
|
116
|
+
description: entry.description,
|
|
117
|
+
required: entry.required,
|
|
118
|
+
initialValue: entry.initialValue,
|
|
119
|
+
editable
|
|
120
|
+
}),
|
|
121
|
+
type: "array",
|
|
122
|
+
of: entry.type === "todoList" ? TODOLIST_OF : NOTES_OF
|
|
123
|
+
};
|
|
87
124
|
}
|
|
88
125
|
function normalizeEditable(editable, path, ctx) {
|
|
89
126
|
if (editable === void 0 || editable === !0) return editable;
|
|
@@ -100,12 +137,12 @@ function normalizeEditable(editable, path, ctx) {
|
|
|
100
137
|
}
|
|
101
138
|
return editable;
|
|
102
139
|
}
|
|
103
|
-
function editableSlotNames(workflowFields, stageFields,
|
|
140
|
+
function editableSlotNames(workflowFields, stageFields, activities) {
|
|
104
141
|
const names = /* @__PURE__ */ new Set(), collect = (entries) => {
|
|
105
142
|
for (const entry of entries ?? []) entry.editable !== void 0 && names.add(entry.name);
|
|
106
143
|
};
|
|
107
144
|
collect(workflowFields), collect(stageFields);
|
|
108
|
-
for (const
|
|
145
|
+
for (const activity of activities) collect(activity.fields);
|
|
109
146
|
return names;
|
|
110
147
|
}
|
|
111
148
|
function desugarStageEditable(overrides, inScope, path, ctx) {
|
|
@@ -115,7 +152,7 @@ function desugarStageEditable(overrides, inScope, path, ctx) {
|
|
|
115
152
|
if (!inScope.has(name)) {
|
|
116
153
|
ctx.issues.push({
|
|
117
154
|
path: [...path, name],
|
|
118
|
-
message: `stage editable override "${name}" does not narrow an editable slot in scope \u2014 name a workflow/stage/
|
|
155
|
+
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\``
|
|
119
156
|
});
|
|
120
157
|
continue;
|
|
121
158
|
}
|
|
@@ -127,28 +164,28 @@ function desugarStageEditable(overrides, inScope, path, ctx) {
|
|
|
127
164
|
function layerOf(entries) {
|
|
128
165
|
return new Map((entries ?? []).map((entry) => [entry.name, entry]));
|
|
129
166
|
}
|
|
130
|
-
function
|
|
131
|
-
const
|
|
132
|
-
layers: [{ scope: "
|
|
133
|
-
}, ops = desugarOps(
|
|
134
|
-
(action, a) => desugarAction(action, [...path, "actions", a], env,
|
|
135
|
-
), target = desugarTarget(
|
|
167
|
+
function desugarActivity(activity, path, stageEnv, ctx) {
|
|
168
|
+
const activityFields = desugarFieldEntries(activity.fields, [...path, "fields"], ctx), env = {
|
|
169
|
+
layers: [{ scope: "activity", entries: layerOf(activityFields) }, ...stageEnv.layers]
|
|
170
|
+
}, ops = desugarOps(activity.ops, [...path, "ops"], env, activity.name, ctx), actions = (activity.actions ?? []).map(
|
|
171
|
+
(action, a) => desugarAction(action, [...path, "actions", a], env, activity.name, ctx)
|
|
172
|
+
), target = desugarTarget(activity.target, env, [...path, "target"], ctx);
|
|
136
173
|
return {
|
|
137
174
|
...stripUndefined({
|
|
138
|
-
name:
|
|
139
|
-
title:
|
|
140
|
-
description:
|
|
141
|
-
kind:
|
|
142
|
-
filter:
|
|
143
|
-
requirements:
|
|
144
|
-
completeWhen:
|
|
145
|
-
failWhen:
|
|
146
|
-
effects:
|
|
147
|
-
subworkflows:
|
|
175
|
+
name: activity.name,
|
|
176
|
+
title: activity.title,
|
|
177
|
+
description: activity.description,
|
|
178
|
+
kind: activity.kind,
|
|
179
|
+
filter: activity.filter,
|
|
180
|
+
requirements: activity.requirements,
|
|
181
|
+
completeWhen: activity.completeWhen,
|
|
182
|
+
failWhen: activity.failWhen,
|
|
183
|
+
effects: activity.effects,
|
|
184
|
+
subworkflows: activity.subworkflows
|
|
148
185
|
}),
|
|
149
|
-
activation:
|
|
186
|
+
activation: activity.activation ?? "manual",
|
|
150
187
|
...target ? { target } : {},
|
|
151
|
-
...
|
|
188
|
+
...activityFields ? { fields: activityFields } : {},
|
|
152
189
|
...ops ? { ops } : {},
|
|
153
190
|
...actions.length > 0 ? { actions } : {}
|
|
154
191
|
};
|
|
@@ -159,14 +196,14 @@ function desugarTarget(target, env, path, ctx) {
|
|
|
159
196
|
const ref = typeof target.field == "string" ? { field: target.field } : target.field, field = resolveRef(ref, env, [...path, "field"], ctx) ?? fallbackRef(ref), entry = entryAt(env, field);
|
|
160
197
|
return entry && !TARGET_DOC_KINDS.includes(entry.type) && ctx.issues.push({
|
|
161
198
|
path: [...path, "field"],
|
|
162
|
-
message: `manual
|
|
199
|
+
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(", ")})`
|
|
163
200
|
}), { type: "field", field };
|
|
164
201
|
}
|
|
165
|
-
function desugarAction(action, path, env,
|
|
202
|
+
function desugarAction(action, path, env, activityName, ctx) {
|
|
166
203
|
if ("type" in action)
|
|
167
204
|
return desugarClaimAction(action, path, env, ctx);
|
|
168
|
-
const filter = andConditions([rolesCondition(action.roles, ctx.roleAliases), action.filter]), ops = desugarOps(action.ops, [...path, "ops"], env,
|
|
169
|
-
return action.status !== void 0 && ops.push({ type: "status.set",
|
|
205
|
+
const filter = andConditions([rolesCondition(action.roles, ctx.roleAliases), action.filter]), ops = desugarOps(action.ops, [...path, "ops"], env, activityName, ctx) ?? [];
|
|
206
|
+
return action.status !== void 0 && ops.push({ type: "status.set", activity: activityName, status: action.status }), {
|
|
170
207
|
...stripUndefined({
|
|
171
208
|
name: action.name,
|
|
172
209
|
title: action.title,
|
|
@@ -182,9 +219,9 @@ function desugarClaimAction(action, path, env, ctx) {
|
|
|
182
219
|
const ref = typeof action.field == "string" ? { field: action.field } : action.field, resolved = resolveRef(ref, env, [...path, "field"], ctx);
|
|
183
220
|
if (resolved) {
|
|
184
221
|
const entry = entryAt(env, resolved);
|
|
185
|
-
entry && entry.type !== "
|
|
222
|
+
entry && entry.type !== "actor" && ctx.issues.push({
|
|
186
223
|
path: [...path, "field"],
|
|
187
|
-
message: `claim action "${action.name}" references "${resolved.field}" of kind "${entry.type}" \u2014 a claim pair needs an actor-valued entry (a "claim" or "
|
|
224
|
+
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)`
|
|
188
225
|
}), checkShadowedClaimTarget(action.name, ref.field, resolved, env, [...path, "field"], ctx), entry && ctx.claimedFields.add(entry);
|
|
189
226
|
}
|
|
190
227
|
const noSteal = `!defined($fields.${ref.field})`, filter = andConditions([
|
|
@@ -215,7 +252,7 @@ function checkUnclaimedClaimFields(ctx) {
|
|
|
215
252
|
for (const [entry, { name, path }] of ctx.claimFields)
|
|
216
253
|
ctx.claimedFields.has(entry) || ctx.issues.push({
|
|
217
254
|
path,
|
|
218
|
-
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
|
|
255
|
+
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`
|
|
219
256
|
});
|
|
220
257
|
}
|
|
221
258
|
function desugarTransition(transition, path, stageEnv, ctx) {
|
|
@@ -228,21 +265,21 @@ function desugarTransition(transition, path, stageEnv, ctx) {
|
|
|
228
265
|
to: transition.to,
|
|
229
266
|
effects: transition.effects
|
|
230
267
|
}),
|
|
231
|
-
filter: transition.filter ?? "$
|
|
268
|
+
filter: transition.filter ?? "$allActivitiesDone",
|
|
232
269
|
...ops ? { ops } : {}
|
|
233
270
|
};
|
|
234
271
|
}
|
|
235
|
-
function desugarOps(ops, path, env,
|
|
272
|
+
function desugarOps(ops, path, env, firingActivity, ctx) {
|
|
236
273
|
if (ops)
|
|
237
|
-
return ops.map((op, i) => desugarOp(op, [...path, i], env,
|
|
274
|
+
return ops.map((op, i) => desugarOp(op, [...path, i], env, firingActivity, ctx));
|
|
238
275
|
}
|
|
239
|
-
function desugarOp(op, path, env,
|
|
276
|
+
function desugarOp(op, path, env, firingActivity, ctx) {
|
|
240
277
|
if (op.type === "status.set") {
|
|
241
|
-
const
|
|
242
|
-
return
|
|
243
|
-
path: [...path, "
|
|
244
|
-
message: "status.set outside an action must name its target
|
|
245
|
-
}), { type: "status.set",
|
|
278
|
+
const activity = op.activity ?? firingActivity;
|
|
279
|
+
return activity === void 0 && ctx.issues.push({
|
|
280
|
+
path: [...path, "activity"],
|
|
281
|
+
message: "status.set outside an action must name its target activity"
|
|
282
|
+
}), { type: "status.set", activity: activity ?? "", status: op.status };
|
|
246
283
|
}
|
|
247
284
|
if (op.type === "audit") return desugarAuditOp(op, path, env, ctx);
|
|
248
285
|
const target = resolveRef(op.target, env, [...path, "target"], ctx) ?? fallbackRef(op.target);
|
|
@@ -306,14 +343,14 @@ const RESERVED_CONDITION_VARS = [
|
|
|
306
343
|
"row",
|
|
307
344
|
"now",
|
|
308
345
|
"effects",
|
|
309
|
-
"
|
|
346
|
+
"activities",
|
|
310
347
|
"subworkflows",
|
|
311
348
|
"self",
|
|
312
349
|
"stage",
|
|
313
350
|
"parent",
|
|
314
351
|
"ancestors",
|
|
315
|
-
"
|
|
316
|
-
"
|
|
352
|
+
"allActivitiesDone",
|
|
353
|
+
"anyActivityFailed"
|
|
317
354
|
], PREDICATE_CALLER_VARS = ["actor", "assigned", "can"];
|
|
318
355
|
function knownList(ids) {
|
|
319
356
|
return [...ids].map((s) => `"${s}"`).join(", ");
|
|
@@ -331,12 +368,12 @@ function checkStages(def, issues) {
|
|
|
331
368
|
issues
|
|
332
369
|
);
|
|
333
370
|
for (const [i, stage] of def.stages.entries()) {
|
|
334
|
-
const
|
|
335
|
-
(stage.
|
|
336
|
-
name:
|
|
337
|
-
path: ["stages", i, "
|
|
371
|
+
const activityNames = checkDuplicates(
|
|
372
|
+
(stage.activities ?? []).map((activity, j) => ({
|
|
373
|
+
name: activity.name,
|
|
374
|
+
path: ["stages", i, "activities", j, "name"]
|
|
338
375
|
})),
|
|
339
|
-
`
|
|
376
|
+
`activity name in stage "${stage.name}"`,
|
|
340
377
|
issues
|
|
341
378
|
);
|
|
342
379
|
checkDuplicates(
|
|
@@ -346,34 +383,34 @@ function checkStages(def, issues) {
|
|
|
346
383
|
})),
|
|
347
384
|
`transition name in stage "${stage.name}"`,
|
|
348
385
|
issues
|
|
349
|
-
),
|
|
386
|
+
), checkActivities(def, i, activityNames, issues);
|
|
350
387
|
}
|
|
351
388
|
return stageNames;
|
|
352
389
|
}
|
|
353
|
-
function
|
|
390
|
+
function checkActivities(def, i, activityNames, issues) {
|
|
354
391
|
const stage = def.stages[i];
|
|
355
|
-
for (const [j,
|
|
356
|
-
const path = ["stages", i, "
|
|
392
|
+
for (const [j, activity] of (stage.activities ?? []).entries()) {
|
|
393
|
+
const path = ["stages", i, "activities", j];
|
|
357
394
|
checkDuplicates(
|
|
358
|
-
(
|
|
395
|
+
(activity.actions ?? []).map((action, a) => ({
|
|
359
396
|
name: action.name,
|
|
360
397
|
path: [...path, "actions", a, "name"]
|
|
361
398
|
})),
|
|
362
|
-
`action name in
|
|
399
|
+
`action name in activity "${activity.name}"`,
|
|
363
400
|
issues
|
|
364
|
-
), checkStatusSetTargets(
|
|
401
|
+
), checkStatusSetTargets(activity, activityNames, path, issues);
|
|
365
402
|
}
|
|
366
403
|
}
|
|
367
|
-
function checkStatusSetTargets(
|
|
368
|
-
checkStatusSetOps(
|
|
369
|
-
for (const [a, action] of (
|
|
370
|
-
checkStatusSetOps(action.ops,
|
|
404
|
+
function checkStatusSetTargets(activity, activityNames, path, issues) {
|
|
405
|
+
checkStatusSetOps(activity.ops, activityNames, [...path, "ops"], issues);
|
|
406
|
+
for (const [a, action] of (activity.actions ?? []).entries())
|
|
407
|
+
checkStatusSetOps(action.ops, activityNames, [...path, "actions", a, "ops"], issues);
|
|
371
408
|
}
|
|
372
|
-
function checkStatusSetOps(ops,
|
|
409
|
+
function checkStatusSetOps(ops, activityNames, path, issues) {
|
|
373
410
|
for (const [o, op] of (ops ?? []).entries())
|
|
374
|
-
op.type !== "status.set" ||
|
|
375
|
-
path: [...path, o, "
|
|
376
|
-
message: `status.set targets
|
|
411
|
+
op.type !== "status.set" || activityNames.has(op.activity) || issues.push({
|
|
412
|
+
path: [...path, o, "activity"],
|
|
413
|
+
message: `status.set targets activity "${op.activity}" which is not declared in this stage. Known activities: ${knownList(activityNames)}`
|
|
377
414
|
});
|
|
378
415
|
}
|
|
379
416
|
function checkInitialStage(def, stageNames, issues) {
|
|
@@ -393,10 +430,14 @@ function checkTransitionTargets(def, stageNames, issues) {
|
|
|
393
430
|
function checkEffectNames(def, issues) {
|
|
394
431
|
const sites = [];
|
|
395
432
|
for (const [i, stage] of def.stages.entries()) {
|
|
396
|
-
for (const [j,
|
|
397
|
-
collectEffects(
|
|
398
|
-
for (const [a, action] of (
|
|
399
|
-
collectEffects(
|
|
433
|
+
for (const [j, activity] of (stage.activities ?? []).entries()) {
|
|
434
|
+
collectEffects(activity.effects, ["stages", i, "activities", j, "effects"], sites);
|
|
435
|
+
for (const [a, action] of (activity.actions ?? []).entries())
|
|
436
|
+
collectEffects(
|
|
437
|
+
action.effects,
|
|
438
|
+
["stages", i, "activities", j, "actions", a, "effects"],
|
|
439
|
+
sites
|
|
440
|
+
);
|
|
400
441
|
}
|
|
401
442
|
for (const [k, t] of (stage.transitions ?? []).entries())
|
|
402
443
|
collectEffects(t.effects, ["stages", i, "transitions", k, "effects"], sites);
|
|
@@ -431,12 +472,12 @@ function fieldScopes(def) {
|
|
|
431
472
|
path: ["stages", i, "fields"],
|
|
432
473
|
label: `stage "${stage.name}" fields`
|
|
433
474
|
});
|
|
434
|
-
for (const [j,
|
|
475
|
+
for (const [j, activity] of (stage.activities ?? []).entries())
|
|
435
476
|
scopes.push({
|
|
436
|
-
entries:
|
|
437
|
-
scope: "
|
|
438
|
-
path: ["stages", i, "
|
|
439
|
-
label: `
|
|
477
|
+
entries: activity.fields,
|
|
478
|
+
scope: "activity",
|
|
479
|
+
path: ["stages", i, "activities", j, "fields"],
|
|
480
|
+
label: `activity "${activity.name}" fields`
|
|
440
481
|
});
|
|
441
482
|
}
|
|
442
483
|
return scopes;
|
|
@@ -444,13 +485,20 @@ function fieldScopes(def) {
|
|
|
444
485
|
function checkRequiredField(def, issues) {
|
|
445
486
|
for (const { entries, scope, path, label } of fieldScopes(def))
|
|
446
487
|
for (const [n, entry] of (entries ?? []).entries())
|
|
447
|
-
entry.required === !0
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
488
|
+
if (entry.required === !0) {
|
|
489
|
+
if (scope !== "workflow")
|
|
490
|
+
issues.push({
|
|
491
|
+
path: [...path, n, "required"],
|
|
492
|
+
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`
|
|
493
|
+
});
|
|
494
|
+
else if (entry.initialValue?.type !== "input") {
|
|
495
|
+
const seed = entry.initialValue?.type ?? "working memory (no initialValue)";
|
|
496
|
+
issues.push({
|
|
497
|
+
path: [...path, n, "required"],
|
|
498
|
+
message: `field entry "${entry.name}" has initialValue "${seed}" \u2014 \`required\` applies only to \`input\`-sourced entries (the caller fills them at start/spawn)`
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
}
|
|
454
502
|
}
|
|
455
503
|
function checkFieldEntryNames(def, issues) {
|
|
456
504
|
for (const { entries, path, label } of fieldScopes(def))
|
|
@@ -506,34 +554,34 @@ function nonActionFilterConditionSites(def) {
|
|
|
506
554
|
`transition "${t.name}"`,
|
|
507
555
|
sites
|
|
508
556
|
);
|
|
509
|
-
for (const [j,
|
|
510
|
-
|
|
557
|
+
for (const [j, activity] of (stage.activities ?? []).entries())
|
|
558
|
+
collectActivityConditionSites(activity, ["stages", i, "activities", j], sites);
|
|
511
559
|
}
|
|
512
560
|
return sites;
|
|
513
561
|
}
|
|
514
|
-
function
|
|
562
|
+
function collectActivityConditionSites(activity, path, sites) {
|
|
515
563
|
for (const field of ["filter", "completeWhen", "failWhen"]) {
|
|
516
|
-
const groq2 =
|
|
517
|
-
groq2 !== void 0 && sites.push({ groq: groq2, path: [...path, field], label: `
|
|
564
|
+
const groq2 = activity[field];
|
|
565
|
+
groq2 !== void 0 && sites.push({ groq: groq2, path: [...path, field], label: `activity "${activity.name}".${field}` });
|
|
518
566
|
}
|
|
519
|
-
collectBindingSites(
|
|
520
|
-
for (const [a, action] of (
|
|
567
|
+
collectBindingSites(activity.effects, [...path, "effects"], `activity "${activity.name}"`, sites);
|
|
568
|
+
for (const [a, action] of (activity.actions ?? []).entries())
|
|
521
569
|
collectBindingSites(
|
|
522
570
|
action.effects,
|
|
523
571
|
[...path, "actions", a, "effects"],
|
|
524
572
|
`action "${action.name}"`,
|
|
525
573
|
sites
|
|
526
574
|
);
|
|
527
|
-
collectSubworkflowSites(
|
|
575
|
+
collectSubworkflowSites(activity, path, sites);
|
|
528
576
|
}
|
|
529
|
-
function collectSubworkflowSites(
|
|
530
|
-
const sub =
|
|
577
|
+
function collectSubworkflowSites(activity, path, sites) {
|
|
578
|
+
const sub = activity.subworkflows;
|
|
531
579
|
if (sub === void 0) return;
|
|
532
580
|
const subPath = [...path, "subworkflows"];
|
|
533
581
|
sites.push({
|
|
534
582
|
groq: sub.forEach,
|
|
535
583
|
path: [...subPath, "forEach"],
|
|
536
|
-
label: `
|
|
584
|
+
label: `activity "${activity.name}".subworkflows.forEach`
|
|
537
585
|
});
|
|
538
586
|
for (const [group, record] of [
|
|
539
587
|
["with", sub.with],
|
|
@@ -543,7 +591,7 @@ function collectSubworkflowSites(task, path, sites) {
|
|
|
543
591
|
sites.push({
|
|
544
592
|
groq: groq2,
|
|
545
593
|
path: [...subPath, group, key],
|
|
546
|
-
label: `
|
|
594
|
+
label: `activity "${activity.name}".subworkflows.${group} "${key}"`
|
|
547
595
|
});
|
|
548
596
|
}
|
|
549
597
|
function collectBindingSites(effects, path, label, sites) {
|
|
@@ -562,12 +610,12 @@ function checkAssigneesEntries(def, issues) {
|
|
|
562
610
|
message: "at most one assignees-kind field entry per scope \u2014 the inbox reads it by kind"
|
|
563
611
|
});
|
|
564
612
|
}
|
|
565
|
-
function
|
|
613
|
+
function activityShape(activity) {
|
|
566
614
|
return {
|
|
567
|
-
hasActions: (
|
|
568
|
-
hasEffects: (
|
|
569
|
-
hasCompleteWhen:
|
|
570
|
-
hasSubworkflows:
|
|
615
|
+
hasActions: (activity.actions ?? []).length > 0,
|
|
616
|
+
hasEffects: (activity.effects ?? []).length > 0,
|
|
617
|
+
hasCompleteWhen: activity.completeWhen !== void 0,
|
|
618
|
+
hasSubworkflows: activity.subworkflows !== void 0
|
|
571
619
|
};
|
|
572
620
|
}
|
|
573
621
|
const KIND_SHAPE_RULES = {
|
|
@@ -575,7 +623,7 @@ const KIND_SHAPE_RULES = {
|
|
|
575
623
|
service: (s) => s.hasEffects ? [] : ["needs at least one effect \u2014 the automated work it performs"],
|
|
576
624
|
manual: (s) => [
|
|
577
625
|
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)",
|
|
578
|
-
s.hasSubworkflows ? "is off-system work, not a fan-out \u2014 drop `subworkflows` or move it to its own
|
|
626
|
+
s.hasSubworkflows ? "is off-system work, not a fan-out \u2014 drop `subworkflows` or move it to its own activity" : void 0
|
|
579
627
|
].filter((m) => m !== void 0),
|
|
580
628
|
receive: (s) => [
|
|
581
629
|
s.hasCompleteWhen || s.hasSubworkflows ? void 0 : "needs a `completeWhen` (or `subworkflows`) condition to wait on",
|
|
@@ -587,24 +635,24 @@ const KIND_SHAPE_RULES = {
|
|
|
587
635
|
s.hasSubworkflows ? "resolves on activation, so it cannot fan out with `subworkflows`" : void 0
|
|
588
636
|
].filter((m) => m !== void 0)
|
|
589
637
|
};
|
|
590
|
-
function
|
|
638
|
+
function checkActivityKinds(def, issues) {
|
|
591
639
|
for (const [i, stage] of def.stages.entries())
|
|
592
|
-
for (const [j,
|
|
593
|
-
const path = ["stages", i, "
|
|
594
|
-
if (
|
|
640
|
+
for (const [j, activity] of (stage.activities ?? []).entries()) {
|
|
641
|
+
const path = ["stages", i, "activities", j];
|
|
642
|
+
if (activity.target !== void 0 && activity.kind !== "manual" && issues.push({
|
|
595
643
|
path: [...path, "target"],
|
|
596
|
-
message: `
|
|
597
|
-
}),
|
|
598
|
-
for (const fault of KIND_SHAPE_RULES[
|
|
644
|
+
message: `activity "${activity.name}" has a \`target\` but is not \`kind: "manual"\` \u2014 a target is the deep-link for off-system manual work`
|
|
645
|
+
}), activity.kind !== void 0)
|
|
646
|
+
for (const fault of KIND_SHAPE_RULES[activity.kind](activityShape(activity)))
|
|
599
647
|
issues.push({
|
|
600
648
|
path: [...path, "kind"],
|
|
601
|
-
message: `
|
|
649
|
+
message: `activity "${activity.name}" declares kind "${activity.kind}" but ${fault}`
|
|
602
650
|
});
|
|
603
651
|
}
|
|
604
652
|
}
|
|
605
653
|
function checkWorkflowInvariants(def) {
|
|
606
654
|
const issues = [], stageNames = checkStages(def, issues);
|
|
607
|
-
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),
|
|
655
|
+
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;
|
|
608
656
|
}
|
|
609
657
|
function defineWorkflow(definition) {
|
|
610
658
|
const label = labelFor("defineWorkflow", definition), parsed = parseOrThrow(AuthoringWorkflowSchema, definition, label), { definition: stored, issues: desugarIssues } = desugarWorkflow(parsed), issues = [...desugarIssues, ...checkWorkflowInvariants(stored)];
|
|
@@ -614,8 +662,8 @@ function defineWorkflow(definition) {
|
|
|
614
662
|
function defineStage(stage) {
|
|
615
663
|
return parseOrThrow(AuthoringStageSchema, stage, labelFor("defineStage", stage));
|
|
616
664
|
}
|
|
617
|
-
function
|
|
618
|
-
return parseOrThrow(
|
|
665
|
+
function defineActivity(activity) {
|
|
666
|
+
return parseOrThrow(AuthoringActivitySchema, activity, labelFor("defineActivity", activity));
|
|
619
667
|
}
|
|
620
668
|
function defineAction(action) {
|
|
621
669
|
return parseOrThrow(AuthoringActionSchema, action, labelFor("defineAction", action));
|
|
@@ -658,13 +706,13 @@ function labelFor(fn, value) {
|
|
|
658
706
|
}
|
|
659
707
|
export {
|
|
660
708
|
defineAction,
|
|
709
|
+
defineActivity,
|
|
661
710
|
defineEffect,
|
|
662
711
|
defineEffectDescriptor,
|
|
663
712
|
defineField,
|
|
664
713
|
defineGuard,
|
|
665
714
|
defineOp,
|
|
666
715
|
defineStage,
|
|
667
|
-
defineTask,
|
|
668
716
|
defineTransition,
|
|
669
717
|
defineWorkflow,
|
|
670
718
|
groq
|