@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.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,35 +164,46 @@ 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
|
-
);
|
|
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
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
|
147
185
|
}),
|
|
148
|
-
activation:
|
|
149
|
-
...
|
|
186
|
+
activation: activity.activation ?? "manual",
|
|
187
|
+
...target ? { target } : {},
|
|
188
|
+
...activityFields ? { fields: activityFields } : {},
|
|
150
189
|
...ops ? { ops } : {},
|
|
151
190
|
...actions.length > 0 ? { actions } : {}
|
|
152
191
|
};
|
|
153
192
|
}
|
|
154
|
-
|
|
193
|
+
const TARGET_DOC_KINDS = ["doc.ref", "doc.refs", "release.ref"];
|
|
194
|
+
function desugarTarget(target, env, path, ctx) {
|
|
195
|
+
if (target === void 0 || target.type === "url") return target;
|
|
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);
|
|
197
|
+
return entry && !TARGET_DOC_KINDS.includes(entry.type) && ctx.issues.push({
|
|
198
|
+
path: [...path, "field"],
|
|
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(", ")})`
|
|
200
|
+
}), { type: "field", field };
|
|
201
|
+
}
|
|
202
|
+
function desugarAction(action, path, env, activityName, ctx) {
|
|
155
203
|
if ("type" in action)
|
|
156
204
|
return desugarClaimAction(action, path, env, ctx);
|
|
157
|
-
const filter = andConditions([rolesCondition(action.roles, ctx.roleAliases), action.filter]), ops = desugarOps(action.ops, [...path, "ops"], env,
|
|
158
|
-
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 }), {
|
|
159
207
|
...stripUndefined({
|
|
160
208
|
name: action.name,
|
|
161
209
|
title: action.title,
|
|
@@ -171,9 +219,9 @@ function desugarClaimAction(action, path, env, ctx) {
|
|
|
171
219
|
const ref = typeof action.field == "string" ? { field: action.field } : action.field, resolved = resolveRef(ref, env, [...path, "field"], ctx);
|
|
172
220
|
if (resolved) {
|
|
173
221
|
const entry = entryAt(env, resolved);
|
|
174
|
-
entry && entry.type !== "
|
|
222
|
+
entry && entry.type !== "actor" && ctx.issues.push({
|
|
175
223
|
path: [...path, "field"],
|
|
176
|
-
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)`
|
|
177
225
|
}), checkShadowedClaimTarget(action.name, ref.field, resolved, env, [...path, "field"], ctx), entry && ctx.claimedFields.add(entry);
|
|
178
226
|
}
|
|
179
227
|
const noSteal = `!defined($fields.${ref.field})`, filter = andConditions([
|
|
@@ -204,7 +252,7 @@ function checkUnclaimedClaimFields(ctx) {
|
|
|
204
252
|
for (const [entry, { name, path }] of ctx.claimFields)
|
|
205
253
|
ctx.claimedFields.has(entry) || ctx.issues.push({
|
|
206
254
|
path,
|
|
207
|
-
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`
|
|
208
256
|
});
|
|
209
257
|
}
|
|
210
258
|
function desugarTransition(transition, path, stageEnv, ctx) {
|
|
@@ -217,21 +265,21 @@ function desugarTransition(transition, path, stageEnv, ctx) {
|
|
|
217
265
|
to: transition.to,
|
|
218
266
|
effects: transition.effects
|
|
219
267
|
}),
|
|
220
|
-
filter: transition.filter ?? "$
|
|
268
|
+
filter: transition.filter ?? "$allActivitiesDone",
|
|
221
269
|
...ops ? { ops } : {}
|
|
222
270
|
};
|
|
223
271
|
}
|
|
224
|
-
function desugarOps(ops, path, env,
|
|
272
|
+
function desugarOps(ops, path, env, firingActivity, ctx) {
|
|
225
273
|
if (ops)
|
|
226
|
-
return ops.map((op, i) => desugarOp(op, [...path, i], env,
|
|
274
|
+
return ops.map((op, i) => desugarOp(op, [...path, i], env, firingActivity, ctx));
|
|
227
275
|
}
|
|
228
|
-
function desugarOp(op, path, env,
|
|
276
|
+
function desugarOp(op, path, env, firingActivity, ctx) {
|
|
229
277
|
if (op.type === "status.set") {
|
|
230
|
-
const
|
|
231
|
-
return
|
|
232
|
-
path: [...path, "
|
|
233
|
-
message: "status.set outside an action must name its target
|
|
234
|
-
}), { 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 };
|
|
235
283
|
}
|
|
236
284
|
if (op.type === "audit") return desugarAuditOp(op, path, env, ctx);
|
|
237
285
|
const target = resolveRef(op.target, env, [...path, "target"], ctx) ?? fallbackRef(op.target);
|
|
@@ -295,14 +343,14 @@ const RESERVED_CONDITION_VARS = [
|
|
|
295
343
|
"row",
|
|
296
344
|
"now",
|
|
297
345
|
"effects",
|
|
298
|
-
"
|
|
346
|
+
"activities",
|
|
299
347
|
"subworkflows",
|
|
300
348
|
"self",
|
|
301
349
|
"stage",
|
|
302
350
|
"parent",
|
|
303
351
|
"ancestors",
|
|
304
|
-
"
|
|
305
|
-
"
|
|
352
|
+
"allActivitiesDone",
|
|
353
|
+
"anyActivityFailed"
|
|
306
354
|
], PREDICATE_CALLER_VARS = ["actor", "assigned", "can"];
|
|
307
355
|
function knownList(ids) {
|
|
308
356
|
return [...ids].map((s) => `"${s}"`).join(", ");
|
|
@@ -320,12 +368,12 @@ function checkStages(def, issues) {
|
|
|
320
368
|
issues
|
|
321
369
|
);
|
|
322
370
|
for (const [i, stage] of def.stages.entries()) {
|
|
323
|
-
const
|
|
324
|
-
(stage.
|
|
325
|
-
name:
|
|
326
|
-
path: ["stages", i, "
|
|
371
|
+
const activityNames = checkDuplicates(
|
|
372
|
+
(stage.activities ?? []).map((activity, j) => ({
|
|
373
|
+
name: activity.name,
|
|
374
|
+
path: ["stages", i, "activities", j, "name"]
|
|
327
375
|
})),
|
|
328
|
-
`
|
|
376
|
+
`activity name in stage "${stage.name}"`,
|
|
329
377
|
issues
|
|
330
378
|
);
|
|
331
379
|
checkDuplicates(
|
|
@@ -335,34 +383,34 @@ function checkStages(def, issues) {
|
|
|
335
383
|
})),
|
|
336
384
|
`transition name in stage "${stage.name}"`,
|
|
337
385
|
issues
|
|
338
|
-
),
|
|
386
|
+
), checkActivities(def, i, activityNames, issues);
|
|
339
387
|
}
|
|
340
388
|
return stageNames;
|
|
341
389
|
}
|
|
342
|
-
function
|
|
390
|
+
function checkActivities(def, i, activityNames, issues) {
|
|
343
391
|
const stage = def.stages[i];
|
|
344
|
-
for (const [j,
|
|
345
|
-
const path = ["stages", i, "
|
|
392
|
+
for (const [j, activity] of (stage.activities ?? []).entries()) {
|
|
393
|
+
const path = ["stages", i, "activities", j];
|
|
346
394
|
checkDuplicates(
|
|
347
|
-
(
|
|
395
|
+
(activity.actions ?? []).map((action, a) => ({
|
|
348
396
|
name: action.name,
|
|
349
397
|
path: [...path, "actions", a, "name"]
|
|
350
398
|
})),
|
|
351
|
-
`action name in
|
|
399
|
+
`action name in activity "${activity.name}"`,
|
|
352
400
|
issues
|
|
353
|
-
), checkStatusSetTargets(
|
|
401
|
+
), checkStatusSetTargets(activity, activityNames, path, issues);
|
|
354
402
|
}
|
|
355
403
|
}
|
|
356
|
-
function checkStatusSetTargets(
|
|
357
|
-
checkStatusSetOps(
|
|
358
|
-
for (const [a, action] of (
|
|
359
|
-
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);
|
|
360
408
|
}
|
|
361
|
-
function checkStatusSetOps(ops,
|
|
409
|
+
function checkStatusSetOps(ops, activityNames, path, issues) {
|
|
362
410
|
for (const [o, op] of (ops ?? []).entries())
|
|
363
|
-
op.type !== "status.set" ||
|
|
364
|
-
path: [...path, o, "
|
|
365
|
-
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)}`
|
|
366
414
|
});
|
|
367
415
|
}
|
|
368
416
|
function checkInitialStage(def, stageNames, issues) {
|
|
@@ -382,10 +430,14 @@ function checkTransitionTargets(def, stageNames, issues) {
|
|
|
382
430
|
function checkEffectNames(def, issues) {
|
|
383
431
|
const sites = [];
|
|
384
432
|
for (const [i, stage] of def.stages.entries()) {
|
|
385
|
-
for (const [j,
|
|
386
|
-
collectEffects(
|
|
387
|
-
for (const [a, action] of (
|
|
388
|
-
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
|
+
);
|
|
389
441
|
}
|
|
390
442
|
for (const [k, t] of (stage.transitions ?? []).entries())
|
|
391
443
|
collectEffects(t.effects, ["stages", i, "transitions", k, "effects"], sites);
|
|
@@ -420,12 +472,12 @@ function fieldScopes(def) {
|
|
|
420
472
|
path: ["stages", i, "fields"],
|
|
421
473
|
label: `stage "${stage.name}" fields`
|
|
422
474
|
});
|
|
423
|
-
for (const [j,
|
|
475
|
+
for (const [j, activity] of (stage.activities ?? []).entries())
|
|
424
476
|
scopes.push({
|
|
425
|
-
entries:
|
|
426
|
-
scope: "
|
|
427
|
-
path: ["stages", i, "
|
|
428
|
-
label: `
|
|
477
|
+
entries: activity.fields,
|
|
478
|
+
scope: "activity",
|
|
479
|
+
path: ["stages", i, "activities", j, "fields"],
|
|
480
|
+
label: `activity "${activity.name}" fields`
|
|
429
481
|
});
|
|
430
482
|
}
|
|
431
483
|
return scopes;
|
|
@@ -433,13 +485,20 @@ function fieldScopes(def) {
|
|
|
433
485
|
function checkRequiredField(def, issues) {
|
|
434
486
|
for (const { entries, scope, path, label } of fieldScopes(def))
|
|
435
487
|
for (const [n, entry] of (entries ?? []).entries())
|
|
436
|
-
entry.required === !0
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
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
|
+
}
|
|
443
502
|
}
|
|
444
503
|
function checkFieldEntryNames(def, issues) {
|
|
445
504
|
for (const { entries, path, label } of fieldScopes(def))
|
|
@@ -495,34 +554,34 @@ function nonActionFilterConditionSites(def) {
|
|
|
495
554
|
`transition "${t.name}"`,
|
|
496
555
|
sites
|
|
497
556
|
);
|
|
498
|
-
for (const [j,
|
|
499
|
-
|
|
557
|
+
for (const [j, activity] of (stage.activities ?? []).entries())
|
|
558
|
+
collectActivityConditionSites(activity, ["stages", i, "activities", j], sites);
|
|
500
559
|
}
|
|
501
560
|
return sites;
|
|
502
561
|
}
|
|
503
|
-
function
|
|
562
|
+
function collectActivityConditionSites(activity, path, sites) {
|
|
504
563
|
for (const field of ["filter", "completeWhen", "failWhen"]) {
|
|
505
|
-
const groq2 =
|
|
506
|
-
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}` });
|
|
507
566
|
}
|
|
508
|
-
collectBindingSites(
|
|
509
|
-
for (const [a, action] of (
|
|
567
|
+
collectBindingSites(activity.effects, [...path, "effects"], `activity "${activity.name}"`, sites);
|
|
568
|
+
for (const [a, action] of (activity.actions ?? []).entries())
|
|
510
569
|
collectBindingSites(
|
|
511
570
|
action.effects,
|
|
512
571
|
[...path, "actions", a, "effects"],
|
|
513
572
|
`action "${action.name}"`,
|
|
514
573
|
sites
|
|
515
574
|
);
|
|
516
|
-
collectSubworkflowSites(
|
|
575
|
+
collectSubworkflowSites(activity, path, sites);
|
|
517
576
|
}
|
|
518
|
-
function collectSubworkflowSites(
|
|
519
|
-
const sub =
|
|
577
|
+
function collectSubworkflowSites(activity, path, sites) {
|
|
578
|
+
const sub = activity.subworkflows;
|
|
520
579
|
if (sub === void 0) return;
|
|
521
580
|
const subPath = [...path, "subworkflows"];
|
|
522
581
|
sites.push({
|
|
523
582
|
groq: sub.forEach,
|
|
524
583
|
path: [...subPath, "forEach"],
|
|
525
|
-
label: `
|
|
584
|
+
label: `activity "${activity.name}".subworkflows.forEach`
|
|
526
585
|
});
|
|
527
586
|
for (const [group, record] of [
|
|
528
587
|
["with", sub.with],
|
|
@@ -532,7 +591,7 @@ function collectSubworkflowSites(task, path, sites) {
|
|
|
532
591
|
sites.push({
|
|
533
592
|
groq: groq2,
|
|
534
593
|
path: [...subPath, group, key],
|
|
535
|
-
label: `
|
|
594
|
+
label: `activity "${activity.name}".subworkflows.${group} "${key}"`
|
|
536
595
|
});
|
|
537
596
|
}
|
|
538
597
|
function collectBindingSites(effects, path, label, sites) {
|
|
@@ -551,9 +610,49 @@ function checkAssigneesEntries(def, issues) {
|
|
|
551
610
|
message: "at most one assignees-kind field entry per scope \u2014 the inbox reads it by kind"
|
|
552
611
|
});
|
|
553
612
|
}
|
|
613
|
+
function activityShape(activity) {
|
|
614
|
+
return {
|
|
615
|
+
hasActions: (activity.actions ?? []).length > 0,
|
|
616
|
+
hasEffects: (activity.effects ?? []).length > 0,
|
|
617
|
+
hasCompleteWhen: activity.completeWhen !== void 0,
|
|
618
|
+
hasSubworkflows: activity.subworkflows !== void 0
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
const KIND_SHAPE_RULES = {
|
|
622
|
+
user: (s) => s.hasActions ? [] : ["needs at least one action for a person to fire"],
|
|
623
|
+
service: (s) => s.hasEffects ? [] : ["needs at least one effect \u2014 the automated work it performs"],
|
|
624
|
+
manual: (s) => [
|
|
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)",
|
|
626
|
+
s.hasSubworkflows ? "is off-system work, not a fan-out \u2014 drop `subworkflows` or move it to its own activity" : void 0
|
|
627
|
+
].filter((m) => m !== void 0),
|
|
628
|
+
receive: (s) => [
|
|
629
|
+
s.hasCompleteWhen || s.hasSubworkflows ? void 0 : "needs a `completeWhen` (or `subworkflows`) condition to wait on",
|
|
630
|
+
s.hasActions ? "has no actor, so it cannot carry actions" : void 0
|
|
631
|
+
].filter((m) => m !== void 0),
|
|
632
|
+
script: (s) => [
|
|
633
|
+
s.hasActions ? "runs inline with no actor, so it cannot carry actions" : void 0,
|
|
634
|
+
s.hasCompleteWhen ? "resolves on activation, so it cannot carry a `completeWhen` (that is a `receive`/`service` wait)" : void 0,
|
|
635
|
+
s.hasSubworkflows ? "resolves on activation, so it cannot fan out with `subworkflows`" : void 0
|
|
636
|
+
].filter((m) => m !== void 0)
|
|
637
|
+
};
|
|
638
|
+
function checkActivityKinds(def, issues) {
|
|
639
|
+
for (const [i, stage] of def.stages.entries())
|
|
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({
|
|
643
|
+
path: [...path, "target"],
|
|
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)))
|
|
647
|
+
issues.push({
|
|
648
|
+
path: [...path, "kind"],
|
|
649
|
+
message: `activity "${activity.name}" declares kind "${activity.kind}" but ${fault}`
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
}
|
|
554
653
|
function checkWorkflowInvariants(def) {
|
|
555
654
|
const issues = [], stageNames = checkStages(def, issues);
|
|
556
|
-
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;
|
|
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;
|
|
557
656
|
}
|
|
558
657
|
function defineWorkflow(definition) {
|
|
559
658
|
const label = labelFor("defineWorkflow", definition), parsed = parseOrThrow(AuthoringWorkflowSchema, definition, label), { definition: stored, issues: desugarIssues } = desugarWorkflow(parsed), issues = [...desugarIssues, ...checkWorkflowInvariants(stored)];
|
|
@@ -563,8 +662,8 @@ function defineWorkflow(definition) {
|
|
|
563
662
|
function defineStage(stage) {
|
|
564
663
|
return parseOrThrow(AuthoringStageSchema, stage, labelFor("defineStage", stage));
|
|
565
664
|
}
|
|
566
|
-
function
|
|
567
|
-
return parseOrThrow(
|
|
665
|
+
function defineActivity(activity) {
|
|
666
|
+
return parseOrThrow(AuthoringActivitySchema, activity, labelFor("defineActivity", activity));
|
|
568
667
|
}
|
|
569
668
|
function defineAction(action) {
|
|
570
669
|
return parseOrThrow(AuthoringActionSchema, action, labelFor("defineAction", action));
|
|
@@ -607,13 +706,13 @@ function labelFor(fn, value) {
|
|
|
607
706
|
}
|
|
608
707
|
export {
|
|
609
708
|
defineAction,
|
|
709
|
+
defineActivity,
|
|
610
710
|
defineEffect,
|
|
611
711
|
defineEffectDescriptor,
|
|
612
712
|
defineField,
|
|
613
713
|
defineGuard,
|
|
614
714
|
defineOp,
|
|
615
715
|
defineStage,
|
|
616
|
-
defineTask,
|
|
617
716
|
defineTransition,
|
|
618
717
|
defineWorkflow,
|
|
619
718
|
groq
|