@sanity/workflow-engine 0.8.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_chunks-cjs/schema.cjs +112 -65
- package/dist/_chunks-cjs/schema.cjs.map +1 -1
- package/dist/_chunks-es/schema.js +112 -65
- package/dist/_chunks-es/schema.js.map +1 -1
- package/dist/define.cjs +128 -65
- package/dist/define.cjs.map +1 -1
- package/dist/define.d.cts +886 -431
- package/dist/define.d.ts +886 -431
- package/dist/define.js +129 -66
- package/dist/define.js.map +1 -1
- package/dist/index.cjs +620 -244
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1649 -808
- package/dist/index.d.ts +1649 -808
- package/dist/index.js +621 -245
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/define.cjs
CHANGED
|
@@ -29,16 +29,26 @@ function serializeGroqValue(value) {
|
|
|
29
29
|
return serialized;
|
|
30
30
|
}
|
|
31
31
|
function desugarWorkflow(authoring) {
|
|
32
|
-
const ctx = {
|
|
33
|
-
|
|
32
|
+
const ctx = {
|
|
33
|
+
issues: [],
|
|
34
|
+
claimFields: /* @__PURE__ */ new Map(),
|
|
35
|
+
claimedFields: /* @__PURE__ */ new Set(),
|
|
36
|
+
roleAliases: authoring.roleAliases
|
|
37
|
+
}, workflowFields = desugarFieldEntries(authoring.fields, ["fields"], ctx), workflowLayer = layerOf(workflowFields), stages = authoring.stages.map((stage, i) => {
|
|
38
|
+
const path = ["stages", i], stageFields = desugarFieldEntries(stage.fields, [...path, "fields"], ctx), stageEnv = {
|
|
34
39
|
layers: [
|
|
35
|
-
{ scope: "stage", entries: layerOf(
|
|
40
|
+
{ scope: "stage", entries: layerOf(stageFields) },
|
|
36
41
|
{ scope: "workflow", entries: workflowLayer }
|
|
37
42
|
]
|
|
38
43
|
}, tasks = (stage.tasks ?? []).map(
|
|
39
44
|
(task, j) => desugarTask(task, [...path, "tasks", j], stageEnv, ctx)
|
|
40
45
|
), transitions = (stage.transitions ?? []).map(
|
|
41
46
|
(transition, k) => desugarTransition(transition, [...path, "transitions", k], stageEnv, ctx)
|
|
47
|
+
), editable = desugarStageEditable(
|
|
48
|
+
stage.editable,
|
|
49
|
+
editableSlotNames(workflowFields, stageFields, tasks),
|
|
50
|
+
[...path, "editable"],
|
|
51
|
+
ctx
|
|
42
52
|
);
|
|
43
53
|
return {
|
|
44
54
|
...stripUndefined({
|
|
@@ -47,12 +57,13 @@ function desugarWorkflow(authoring) {
|
|
|
47
57
|
description: stage.description,
|
|
48
58
|
guards: stage.guards
|
|
49
59
|
}),
|
|
50
|
-
...
|
|
60
|
+
...stageFields ? { fields: stageFields } : {},
|
|
51
61
|
...tasks.length > 0 ? { tasks } : {},
|
|
52
|
-
...transitions.length > 0 ? { transitions } : {}
|
|
62
|
+
...transitions.length > 0 ? { transitions } : {},
|
|
63
|
+
...editable ? { editable } : {}
|
|
53
64
|
};
|
|
54
65
|
});
|
|
55
|
-
return
|
|
66
|
+
return checkUnclaimedClaimFields(ctx), { definition: {
|
|
56
67
|
...stripUndefined({
|
|
57
68
|
name: authoring.name,
|
|
58
69
|
version: authoring.version,
|
|
@@ -60,29 +71,82 @@ function desugarWorkflow(authoring) {
|
|
|
60
71
|
description: authoring.description,
|
|
61
72
|
role: authoring.role,
|
|
62
73
|
initialStage: authoring.initialStage,
|
|
63
|
-
predicates: authoring.predicates
|
|
74
|
+
predicates: authoring.predicates,
|
|
75
|
+
roleAliases: authoring.roleAliases
|
|
64
76
|
}),
|
|
65
|
-
...
|
|
77
|
+
...workflowFields ? { fields: workflowFields } : {},
|
|
66
78
|
stages
|
|
67
79
|
}, issues: ctx.issues };
|
|
68
80
|
}
|
|
69
|
-
function
|
|
81
|
+
function desugarFieldEntries(entries, path, ctx) {
|
|
70
82
|
return !entries || entries.length === 0 ? entries === void 0 ? void 0 : [] : entries.map((entry, i) => {
|
|
71
|
-
if (entry.type
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
83
|
+
if (entry.type === "claim") {
|
|
84
|
+
const desugared = {
|
|
85
|
+
...stripUndefined({ name: entry.name, title: entry.title, description: entry.description }),
|
|
86
|
+
type: "value.actor",
|
|
87
|
+
source: { type: "write" }
|
|
88
|
+
};
|
|
89
|
+
return ctx.claimFields.set(desugared, { name: entry.name, path: [...path, i] }), desugared;
|
|
90
|
+
}
|
|
91
|
+
const editable = normalizeEditable(entry.editable, [...path, i, "editable"], ctx);
|
|
92
|
+
return {
|
|
93
|
+
...stripUndefined({
|
|
94
|
+
type: entry.type,
|
|
95
|
+
name: entry.name,
|
|
96
|
+
title: entry.title,
|
|
97
|
+
description: entry.description,
|
|
98
|
+
required: entry.required,
|
|
99
|
+
editable
|
|
100
|
+
}),
|
|
101
|
+
source: entry.source
|
|
76
102
|
};
|
|
77
|
-
return ctx.claimStates.set(desugared, { name: entry.name, path: [...path, i] }), desugared;
|
|
78
103
|
});
|
|
79
104
|
}
|
|
105
|
+
function normalizeEditable(editable, path, ctx) {
|
|
106
|
+
if (editable === void 0 || editable === !0) return editable;
|
|
107
|
+
if (Array.isArray(editable)) {
|
|
108
|
+
const condition = rolesCondition(editable, ctx.roleAliases);
|
|
109
|
+
if (condition === void 0) {
|
|
110
|
+
ctx.issues.push({
|
|
111
|
+
path,
|
|
112
|
+
message: "editable: [] names no roles \u2014 use `true` to open the slot to anyone in its scope, or list at least one role"
|
|
113
|
+
});
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
return condition;
|
|
117
|
+
}
|
|
118
|
+
return editable;
|
|
119
|
+
}
|
|
120
|
+
function editableSlotNames(workflowFields, stageFields, tasks) {
|
|
121
|
+
const names = /* @__PURE__ */ new Set(), collect = (entries) => {
|
|
122
|
+
for (const entry of entries ?? []) entry.editable !== void 0 && names.add(entry.name);
|
|
123
|
+
};
|
|
124
|
+
collect(workflowFields), collect(stageFields);
|
|
125
|
+
for (const task of tasks) collect(task.fields);
|
|
126
|
+
return names;
|
|
127
|
+
}
|
|
128
|
+
function desugarStageEditable(overrides, inScope, path, ctx) {
|
|
129
|
+
if (overrides === void 0) return;
|
|
130
|
+
const out = {};
|
|
131
|
+
for (const [name, value] of Object.entries(overrides)) {
|
|
132
|
+
if (!inScope.has(name)) {
|
|
133
|
+
ctx.issues.push({
|
|
134
|
+
path: [...path, name],
|
|
135
|
+
message: `stage editable override "${name}" does not narrow an editable slot in scope \u2014 name a workflow/stage/task slot of this stage that declares \`editable\``
|
|
136
|
+
});
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
const normalized = normalizeEditable(value, [...path, name], ctx);
|
|
140
|
+
normalized !== void 0 && (out[name] = normalized);
|
|
141
|
+
}
|
|
142
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
143
|
+
}
|
|
80
144
|
function layerOf(entries) {
|
|
81
145
|
return new Map((entries ?? []).map((entry) => [entry.name, entry]));
|
|
82
146
|
}
|
|
83
147
|
function desugarTask(task, path, stageEnv, ctx) {
|
|
84
|
-
const
|
|
85
|
-
layers: [{ scope: "task", entries: layerOf(
|
|
148
|
+
const taskFields = desugarFieldEntries(task.fields, [...path, "fields"], ctx), env = {
|
|
149
|
+
layers: [{ scope: "task", entries: layerOf(taskFields) }, ...stageEnv.layers]
|
|
86
150
|
}, ops = desugarOps(task.ops, [...path, "ops"], env, task.name, ctx), actions = (task.actions ?? []).map(
|
|
87
151
|
(action, a) => desugarAction(action, [...path, "actions", a], env, task.name, ctx)
|
|
88
152
|
);
|
|
@@ -99,7 +163,7 @@ function desugarTask(task, path, stageEnv, ctx) {
|
|
|
99
163
|
subworkflows: task.subworkflows
|
|
100
164
|
}),
|
|
101
165
|
activation: task.activation ?? "manual",
|
|
102
|
-
...
|
|
166
|
+
...taskFields ? { fields: taskFields } : {},
|
|
103
167
|
...ops ? { ops } : {},
|
|
104
168
|
...actions.length > 0 ? { actions } : {}
|
|
105
169
|
};
|
|
@@ -107,7 +171,7 @@ function desugarTask(task, path, stageEnv, ctx) {
|
|
|
107
171
|
function desugarAction(action, path, env, taskName, ctx) {
|
|
108
172
|
if ("type" in action)
|
|
109
173
|
return desugarClaimAction(action, path, env, ctx);
|
|
110
|
-
const filter =
|
|
174
|
+
const filter = schema.andConditions([rolesCondition(action.roles, ctx.roleAliases), action.filter]), ops = desugarOps(action.ops, [...path, "ops"], env, taskName, ctx) ?? [];
|
|
111
175
|
return action.status !== void 0 && ops.push({ type: "status.set", task: taskName, status: action.status }), {
|
|
112
176
|
...stripUndefined({
|
|
113
177
|
name: action.name,
|
|
@@ -121,15 +185,19 @@ function desugarAction(action, path, env, taskName, ctx) {
|
|
|
121
185
|
};
|
|
122
186
|
}
|
|
123
187
|
function desugarClaimAction(action, path, env, ctx) {
|
|
124
|
-
const ref = typeof action.
|
|
188
|
+
const ref = typeof action.field == "string" ? { field: action.field } : action.field, resolved = resolveRef(ref, env, [...path, "field"], ctx);
|
|
125
189
|
if (resolved) {
|
|
126
190
|
const entry = entryAt(env, resolved);
|
|
127
191
|
entry && entry.type !== "value.actor" && ctx.issues.push({
|
|
128
|
-
path: [...path, "
|
|
129
|
-
message: `claim action "${action.name}" references "${resolved.
|
|
130
|
-
}), checkShadowedClaimTarget(action.name, ref.
|
|
192
|
+
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 "value.actor" field declaration)`
|
|
194
|
+
}), checkShadowedClaimTarget(action.name, ref.field, resolved, env, [...path, "field"], ctx), entry && ctx.claimedFields.add(entry);
|
|
131
195
|
}
|
|
132
|
-
const noSteal = `!defined($
|
|
196
|
+
const noSteal = `!defined($fields.${ref.field})`, filter = schema.andConditions([
|
|
197
|
+
noSteal,
|
|
198
|
+
rolesCondition(action.roles, ctx.roleAliases),
|
|
199
|
+
action.filter
|
|
200
|
+
]), ops = resolved ? [{ type: "field.set", target: resolved, value: { type: "actor" } }] : [];
|
|
133
201
|
return {
|
|
134
202
|
...stripUndefined({
|
|
135
203
|
name: action.name,
|
|
@@ -142,18 +210,18 @@ function desugarClaimAction(action, path, env, ctx) {
|
|
|
142
210
|
...ops.length > 0 ? { ops } : {}
|
|
143
211
|
};
|
|
144
212
|
}
|
|
145
|
-
function checkShadowedClaimTarget(actionName,
|
|
146
|
-
const nearest = env.layers.find((l) => l.entries.has(
|
|
213
|
+
function checkShadowedClaimTarget(actionName, field, resolved, env, path, ctx) {
|
|
214
|
+
const nearest = env.layers.find((l) => l.entries.has(field));
|
|
147
215
|
nearest === void 0 || nearest.scope === resolved.scope || ctx.issues.push({
|
|
148
216
|
path,
|
|
149
|
-
message: `claim action "${actionName}" targets "${
|
|
217
|
+
message: `claim action "${actionName}" targets "${field}" at scope "${resolved.scope}", but a nearer ${nearest.scope}-scope entry of the same name shadows it in $fields \u2014 the no-steal filter would read the shadowing entry while the claim writes the "${resolved.scope}" one. Rename one of the entries or claim the nearer one`
|
|
150
218
|
});
|
|
151
219
|
}
|
|
152
|
-
function
|
|
153
|
-
for (const [entry, { name, path }] of ctx.
|
|
154
|
-
ctx.
|
|
220
|
+
function checkUnclaimedClaimFields(ctx) {
|
|
221
|
+
for (const [entry, { name, path }] of ctx.claimFields)
|
|
222
|
+
ctx.claimedFields.has(entry) || ctx.issues.push({
|
|
155
223
|
path,
|
|
156
|
-
message: `claim
|
|
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 value.actor entry`
|
|
157
225
|
});
|
|
158
226
|
}
|
|
159
227
|
function desugarTransition(transition, path, stageEnv, ctx) {
|
|
@@ -193,7 +261,7 @@ function desugarAuditOp(op, path, env, ctx) {
|
|
|
193
261
|
return ctx.issues.push({
|
|
194
262
|
path: [...path, "value"],
|
|
195
263
|
message: `audit value must be an object source carrying the domain fields (got "${op.value.type}")`
|
|
196
|
-
}), { type: "
|
|
264
|
+
}), { type: "field.append", target, value: op.value };
|
|
197
265
|
const actorField = op.stampFields?.actor ?? "actor", atField = op.stampFields?.at ?? "at", fields = { ...op.value.fields };
|
|
198
266
|
for (const [stamp, source] of [
|
|
199
267
|
[actorField, AUDIT_STAMPS.actor],
|
|
@@ -208,32 +276,27 @@ function desugarAuditOp(op, path, env, ctx) {
|
|
|
208
276
|
}
|
|
209
277
|
fields[stamp] = source;
|
|
210
278
|
}
|
|
211
|
-
return { type: "
|
|
279
|
+
return { type: "field.append", target, value: { type: "object", fields } };
|
|
212
280
|
}
|
|
213
281
|
function resolveRef(ref, env, path, ctx) {
|
|
214
282
|
const layers = ref.scope === void 0 ? env.layers : env.layers.filter((l) => l.scope === ref.scope);
|
|
215
283
|
for (const layer of layers)
|
|
216
|
-
if (layer.entries.has(ref.
|
|
284
|
+
if (layer.entries.has(ref.field)) return { scope: layer.scope, field: ref.field };
|
|
217
285
|
const reachable = env.layers.flatMap((l) => [...l.entries.keys()].map((n) => `${l.scope}:${n}`));
|
|
218
286
|
ctx.issues.push({
|
|
219
287
|
path,
|
|
220
|
-
message: `
|
|
288
|
+
message: `field reference "${ref.field}"${ref.scope ? ` (scope "${ref.scope}")` : ""} does not resolve to a declared entry. Reachable: ${reachable.join(", ") || "(none)"}`
|
|
221
289
|
});
|
|
222
290
|
}
|
|
223
291
|
function entryAt(env, ref) {
|
|
224
|
-
return env.layers.find((l) => l.scope === ref.scope)?.entries.get(ref.
|
|
292
|
+
return env.layers.find((l) => l.scope === ref.scope)?.entries.get(ref.field);
|
|
225
293
|
}
|
|
226
294
|
function fallbackRef(ref) {
|
|
227
|
-
return { scope: ref.scope ?? "workflow",
|
|
295
|
+
return { scope: ref.scope ?? "workflow", field: ref.field };
|
|
228
296
|
}
|
|
229
|
-
function rolesCondition(roles) {
|
|
297
|
+
function rolesCondition(roles, aliases) {
|
|
230
298
|
if (!(!roles || roles.length === 0))
|
|
231
|
-
return groq`count($actor.roles[@ in ${roles}]) > 0`;
|
|
232
|
-
}
|
|
233
|
-
function composeFilter(parts) {
|
|
234
|
-
const present = parts.filter((p) => p !== void 0);
|
|
235
|
-
if (present.length !== 0)
|
|
236
|
-
return present.length === 1 ? present[0] : present.map((p) => `(${p})`).join(" && ");
|
|
299
|
+
return groq`count($actor.roles[@ in ${schema.expandRequiredRoles(roles, aliases)}]) > 0`;
|
|
237
300
|
}
|
|
238
301
|
function stripUndefined(obj) {
|
|
239
302
|
const out = {};
|
|
@@ -242,7 +305,7 @@ function stripUndefined(obj) {
|
|
|
242
305
|
return out;
|
|
243
306
|
}
|
|
244
307
|
const RESERVED_CONDITION_VARS = [
|
|
245
|
-
"
|
|
308
|
+
"fields",
|
|
246
309
|
"actor",
|
|
247
310
|
"assigned",
|
|
248
311
|
"can",
|
|
@@ -363,43 +426,43 @@ function checkGuardNames(def, issues) {
|
|
|
363
426
|
issues
|
|
364
427
|
);
|
|
365
428
|
}
|
|
366
|
-
function
|
|
429
|
+
function fieldScopes(def) {
|
|
367
430
|
const scopes = [
|
|
368
|
-
{ entries: def.
|
|
431
|
+
{ entries: def.fields, scope: "workflow", path: ["fields"], label: "workflow fields" }
|
|
369
432
|
];
|
|
370
433
|
for (const [i, stage] of def.stages.entries()) {
|
|
371
434
|
scopes.push({
|
|
372
|
-
entries: stage.
|
|
435
|
+
entries: stage.fields,
|
|
373
436
|
scope: "stage",
|
|
374
|
-
path: ["stages", i, "
|
|
375
|
-
label: `stage "${stage.name}"
|
|
437
|
+
path: ["stages", i, "fields"],
|
|
438
|
+
label: `stage "${stage.name}" fields`
|
|
376
439
|
});
|
|
377
440
|
for (const [j, task] of (stage.tasks ?? []).entries())
|
|
378
441
|
scopes.push({
|
|
379
|
-
entries: task.
|
|
442
|
+
entries: task.fields,
|
|
380
443
|
scope: "task",
|
|
381
|
-
path: ["stages", i, "tasks", j, "
|
|
382
|
-
label: `task "${task.name}"
|
|
444
|
+
path: ["stages", i, "tasks", j, "fields"],
|
|
445
|
+
label: `task "${task.name}" fields`
|
|
383
446
|
});
|
|
384
447
|
}
|
|
385
448
|
return scopes;
|
|
386
449
|
}
|
|
387
|
-
function
|
|
388
|
-
for (const { entries, scope, path, label } of
|
|
450
|
+
function checkRequiredField(def, issues) {
|
|
451
|
+
for (const { entries, scope, path, label } of fieldScopes(def))
|
|
389
452
|
for (const [n, entry] of (entries ?? []).entries())
|
|
390
453
|
entry.required === !0 && (scope !== "workflow" ? issues.push({
|
|
391
454
|
path: [...path, n, "required"],
|
|
392
455
|
message: `${label} entry "${entry.name}" is \`required\`, but \`required\` applies only to workflow-scope \`init\` entries \u2014 only those are caller-supplied at start/spawn`
|
|
393
456
|
}) : entry.source.type !== "init" && issues.push({
|
|
394
457
|
path: [...path, n, "required"],
|
|
395
|
-
message: `
|
|
458
|
+
message: `field entry "${entry.name}" is \`required\` but its source is "${entry.source.type}" \u2014 \`required\` applies only to \`init\`-sourced entries (the caller fills them at start/spawn)`
|
|
396
459
|
}));
|
|
397
460
|
}
|
|
398
|
-
function
|
|
399
|
-
for (const { entries, path, label } of
|
|
461
|
+
function checkFieldEntryNames(def, issues) {
|
|
462
|
+
for (const { entries, path, label } of fieldScopes(def))
|
|
400
463
|
checkDuplicates(
|
|
401
464
|
(entries ?? []).map((entry, n) => ({ name: entry.name, path: [...path, n, "name"] })),
|
|
402
|
-
`
|
|
465
|
+
`field entry name in ${label}`,
|
|
403
466
|
issues
|
|
404
467
|
);
|
|
405
468
|
}
|
|
@@ -499,15 +562,15 @@ function collectBindingSites(effects, path, label, sites) {
|
|
|
499
562
|
});
|
|
500
563
|
}
|
|
501
564
|
function checkAssigneesEntries(def, issues) {
|
|
502
|
-
for (const { entries, path } of
|
|
565
|
+
for (const { entries, path } of fieldScopes(def))
|
|
503
566
|
(entries ?? []).filter((e) => e.type === "assignees").length <= 1 || issues.push({
|
|
504
567
|
path,
|
|
505
|
-
message: "at most one assignees-kind
|
|
568
|
+
message: "at most one assignees-kind field entry per scope \u2014 the inbox reads it by kind"
|
|
506
569
|
});
|
|
507
570
|
}
|
|
508
571
|
function checkWorkflowInvariants(def) {
|
|
509
572
|
const issues = [], stageNames = checkStages(def, issues);
|
|
510
|
-
return checkInitialStage(def, stageNames, issues), checkTransitionTargets(def, stageNames, issues), checkEffectNames(def, issues), checkGuardNames(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;
|
|
511
574
|
}
|
|
512
575
|
function defineWorkflow(definition) {
|
|
513
576
|
const label = labelFor("defineWorkflow", definition), parsed = parseOrThrow(schema.AuthoringWorkflowSchema, definition, label), { definition: stored, issues: desugarIssues } = desugarWorkflow(parsed), issues = [...desugarIssues, ...checkWorkflowInvariants(stored)];
|
|
@@ -526,8 +589,8 @@ function defineAction(action) {
|
|
|
526
589
|
function defineTransition(transition) {
|
|
527
590
|
return parseOrThrow(schema.AuthoringTransitionSchema, transition, transitionLabel(transition));
|
|
528
591
|
}
|
|
529
|
-
function
|
|
530
|
-
return parseOrThrow(schema.
|
|
592
|
+
function defineField(entry) {
|
|
593
|
+
return parseOrThrow(schema.AuthoringFieldEntrySchema, entry, labelFor("defineField", entry));
|
|
531
594
|
}
|
|
532
595
|
function defineOp(op) {
|
|
533
596
|
return parseOrThrow(schema.AuthoringOpSchema, op, "defineOp");
|
|
@@ -562,10 +625,10 @@ function labelFor(fn, value) {
|
|
|
562
625
|
exports.defineAction = defineAction;
|
|
563
626
|
exports.defineEffect = defineEffect;
|
|
564
627
|
exports.defineEffectDescriptor = defineEffectDescriptor;
|
|
628
|
+
exports.defineField = defineField;
|
|
565
629
|
exports.defineGuard = defineGuard;
|
|
566
630
|
exports.defineOp = defineOp;
|
|
567
631
|
exports.defineStage = defineStage;
|
|
568
|
-
exports.defineState = defineState;
|
|
569
632
|
exports.defineTask = defineTask;
|
|
570
633
|
exports.defineTransition = defineTransition;
|
|
571
634
|
exports.defineWorkflow = defineWorkflow;
|