@sanity/workflow-engine 0.0.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/LICENSE +21 -0
- package/README.md +18 -0
- package/dist/define.cjs +528 -0
- package/dist/define.cjs.map +1 -0
- package/dist/define.d.cts +3294 -0
- package/dist/define.d.ts +3294 -0
- package/dist/define.js +528 -0
- package/dist/define.js.map +1 -0
- package/dist/index.cjs +3263 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +5759 -0
- package/dist/index.d.ts +5759 -0
- package/dist/index.js +3264 -0
- package/dist/index.js.map +1 -0
- package/package.json +68 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sanity, Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# @sanity/workflow-engine
|
|
2
|
+
|
|
3
|
+
Workflow / BPM engine for Sanity content. Define workflows as data, run them as
|
|
4
|
+
instances against a Sanity client, gate transitions on GROQ filters, and queue
|
|
5
|
+
effects for runtimes to drain.
|
|
6
|
+
|
|
7
|
+
> **Status:** 0.x, internal. Restricted-access package; the API may change
|
|
8
|
+
> between minor versions.
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install @sanity/workflow-engine
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## License
|
|
17
|
+
|
|
18
|
+
[MIT](./LICENSE)
|
package/dist/define.cjs
ADDED
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: !0 });
|
|
3
|
+
var zod = require("zod");
|
|
4
|
+
const TASK_STATUSES = ["pending", "active", "done", "skipped", "failed"], STATE_SCOPES = ["workflow", "stage", "task"], DOCUMENT_VALUE_PERMISSIONS = ["create", "read", "update"], MUTATION_GUARD_ACTIONS = [
|
|
5
|
+
"create",
|
|
6
|
+
"update",
|
|
7
|
+
"delete",
|
|
8
|
+
"publish",
|
|
9
|
+
"unpublish"
|
|
10
|
+
];
|
|
11
|
+
function knownList(ids) {
|
|
12
|
+
return [...ids].map((s) => `"${s}"`).join(", ");
|
|
13
|
+
}
|
|
14
|
+
function checkStagesAndTasks(def, ctx) {
|
|
15
|
+
const stageIds = /* @__PURE__ */ new Set();
|
|
16
|
+
for (const [i, stage] of def.stages.entries())
|
|
17
|
+
stageIds.has(stage.id) && ctx.addIssue({
|
|
18
|
+
code: "custom",
|
|
19
|
+
path: ["stages", i, "id"],
|
|
20
|
+
message: `duplicate stage id "${stage.id}" (already declared earlier)`
|
|
21
|
+
}), stageIds.add(stage.id), stage.kind === "terminal" && stage.transitions && stage.transitions.length > 0 && ctx.addIssue({
|
|
22
|
+
code: "custom",
|
|
23
|
+
path: ["stages", i, "transitions"],
|
|
24
|
+
message: `terminal stage "${stage.id}" must not declare transitions`
|
|
25
|
+
}), checkDuplicateTaskIds(stage, i, ctx);
|
|
26
|
+
return stageIds;
|
|
27
|
+
}
|
|
28
|
+
function checkDuplicateTaskIds(stage, i, ctx) {
|
|
29
|
+
const taskIds = /* @__PURE__ */ new Set();
|
|
30
|
+
for (const [j, task] of (stage.tasks ?? []).entries())
|
|
31
|
+
taskIds.has(task.id) && ctx.addIssue({
|
|
32
|
+
code: "custom",
|
|
33
|
+
path: ["stages", i, "tasks", j, "id"],
|
|
34
|
+
message: `duplicate task id "${task.id}" in stage "${stage.id}"`
|
|
35
|
+
}), taskIds.add(task.id);
|
|
36
|
+
}
|
|
37
|
+
function checkInitialStage(def, stageIds, ctx) {
|
|
38
|
+
stageIds.has(def.initialStageId) || ctx.addIssue({
|
|
39
|
+
code: "custom",
|
|
40
|
+
path: ["initialStageId"],
|
|
41
|
+
message: `initialStageId "${def.initialStageId}" is not a declared stage. Known stages: ${knownList(stageIds)}`
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
function checkTransitionTargets(def, stageIds, ctx) {
|
|
45
|
+
for (const [i, stage] of def.stages.entries())
|
|
46
|
+
for (const [k, t] of (stage.transitions ?? []).entries())
|
|
47
|
+
stageIds.has(t.to) || ctx.addIssue({
|
|
48
|
+
code: "custom",
|
|
49
|
+
path: ["stages", i, "transitions", k, "to"],
|
|
50
|
+
message: `transition target "${t.to}" is not a declared stage. Known stages: ${knownList(stageIds)}`
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
function collectPredicateIds(def, ctx) {
|
|
54
|
+
const predicateIds = /* @__PURE__ */ new Set();
|
|
55
|
+
for (const [i, p] of (def.predicates ?? []).entries())
|
|
56
|
+
predicateIds.has(p.id) && ctx.addIssue({
|
|
57
|
+
code: "custom",
|
|
58
|
+
path: ["predicates", i, "id"],
|
|
59
|
+
message: `duplicate predicate id "${p.id}"`
|
|
60
|
+
}), predicateIds.add(p.id);
|
|
61
|
+
return predicateIds;
|
|
62
|
+
}
|
|
63
|
+
function checkFilterRefs(def, predicateIds, ctx) {
|
|
64
|
+
const visit = (g, path) => {
|
|
65
|
+
g === void 0 || typeof g == "string" || predicateIds.has(g.ref) || ctx.addIssue({
|
|
66
|
+
code: "custom",
|
|
67
|
+
path: [...path, "ref"],
|
|
68
|
+
message: `Filter references predicate "${g.ref}" which is not declared. Known predicates: ${knownList(predicateIds) || "(none)"}`
|
|
69
|
+
});
|
|
70
|
+
};
|
|
71
|
+
for (const [i, stage] of def.stages.entries()) {
|
|
72
|
+
visit(stage.completion, ["stages", i, "completion"]);
|
|
73
|
+
for (const [j, task] of (stage.tasks ?? []).entries()) {
|
|
74
|
+
visit(task.filter, ["stages", i, "tasks", j, "filter"]), visit(task.completeWhen, ["stages", i, "tasks", j, "completeWhen"]), visit(task.failWhen, ["stages", i, "tasks", j, "failWhen"]);
|
|
75
|
+
for (const [a, action] of (task.actions ?? []).entries())
|
|
76
|
+
visit(action.filter, ["stages", i, "tasks", j, "actions", a, "filter"]);
|
|
77
|
+
}
|
|
78
|
+
for (const [k, t] of (stage.transitions ?? []).entries())
|
|
79
|
+
visit(t.filter, ["stages", i, "transitions", k, "filter"]);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function checkWorkflowInvariants(def, ctx) {
|
|
83
|
+
const stageIds = checkStagesAndTasks(def, ctx);
|
|
84
|
+
checkInitialStage(def, stageIds, ctx), checkTransitionTargets(def, stageIds, ctx);
|
|
85
|
+
const predicateIds = collectPredicateIds(def, ctx);
|
|
86
|
+
checkFilterRefs(def, predicateIds, ctx);
|
|
87
|
+
}
|
|
88
|
+
const NonEmpty = zod.z.string().min(1, "must be a non-empty string"), GdrSchema = zod.z.object({
|
|
89
|
+
id: zod.z.string().regex(
|
|
90
|
+
/^(dataset|canvas|media-library|dashboard):/,
|
|
91
|
+
"must be a GDR URI of form '<scheme>:<...id-parts>' where scheme is one of: dataset, canvas, media-library, dashboard"
|
|
92
|
+
),
|
|
93
|
+
type: NonEmpty
|
|
94
|
+
}).strict(), SourceSchema = zod.z.lazy(
|
|
95
|
+
() => zod.z.union([
|
|
96
|
+
zod.z.object({
|
|
97
|
+
source: zod.z.literal("literal"),
|
|
98
|
+
value: zod.z.union([zod.z.string(), zod.z.number(), zod.z.boolean(), zod.z.null()])
|
|
99
|
+
}).strict(),
|
|
100
|
+
zod.z.object({ source: zod.z.literal("param"), paramId: NonEmpty }).strict(),
|
|
101
|
+
zod.z.object({ source: zod.z.literal("actor") }).strict(),
|
|
102
|
+
zod.z.object({ source: zod.z.literal("now") }).strict(),
|
|
103
|
+
zod.z.object({ source: zod.z.literal("self") }).strict(),
|
|
104
|
+
zod.z.object({ source: zod.z.literal("stageId") }).strict(),
|
|
105
|
+
zod.z.object({
|
|
106
|
+
source: zod.z.literal("stateRead"),
|
|
107
|
+
scope: zod.z.enum(STATE_SCOPES),
|
|
108
|
+
slotId: NonEmpty,
|
|
109
|
+
path: zod.z.string().optional()
|
|
110
|
+
}).strict(),
|
|
111
|
+
// effectsContext lookup — reads from `instance.effectsContext[id === ID].value`.
|
|
112
|
+
// Used by chained effects where one handler's output feeds another's binding.
|
|
113
|
+
zod.z.object({
|
|
114
|
+
source: zod.z.literal("effectOutput"),
|
|
115
|
+
contextId: NonEmpty,
|
|
116
|
+
path: zod.z.string().optional()
|
|
117
|
+
}).strict(),
|
|
118
|
+
zod.z.object({ source: zod.z.literal("row"), path: zod.z.string().optional() }).strict(),
|
|
119
|
+
zod.z.object({
|
|
120
|
+
source: zod.z.literal("parentState"),
|
|
121
|
+
slotId: NonEmpty,
|
|
122
|
+
path: zod.z.string().optional()
|
|
123
|
+
}).strict(),
|
|
124
|
+
// Compound object source — resolves each field's Source recursively
|
|
125
|
+
// and packages the result into a JSON object. Lets ops append rich
|
|
126
|
+
// rows to `checklist` / `notes` / `assignees` slots, e.g.
|
|
127
|
+
// `{ source: "object", fields: { signer: { source: "actor" },
|
|
128
|
+
// at: { source: "now" } } }`.
|
|
129
|
+
zod.z.object({
|
|
130
|
+
source: zod.z.literal("object"),
|
|
131
|
+
fields: zod.z.record(NonEmpty, SourceSchema)
|
|
132
|
+
}).strict()
|
|
133
|
+
])
|
|
134
|
+
), ScopeKeySchema = zod.z.object({
|
|
135
|
+
scope: zod.z.enum(STATE_SCOPES),
|
|
136
|
+
slotId: NonEmpty
|
|
137
|
+
}).strict(), OpPredicateSchema = zod.z.lazy(
|
|
138
|
+
() => zod.z.union([
|
|
139
|
+
zod.z.object({
|
|
140
|
+
field: NonEmpty,
|
|
141
|
+
equals: SourceSchema
|
|
142
|
+
}).strict(),
|
|
143
|
+
zod.z.object({ all: zod.z.array(OpPredicateSchema) }).strict(),
|
|
144
|
+
zod.z.object({ any: zod.z.array(OpPredicateSchema) }).strict()
|
|
145
|
+
])
|
|
146
|
+
), OpSchema = zod.z.discriminatedUnion("type", [
|
|
147
|
+
zod.z.object({
|
|
148
|
+
type: zod.z.literal("workflow.op.state.set"),
|
|
149
|
+
target: ScopeKeySchema,
|
|
150
|
+
value: SourceSchema
|
|
151
|
+
}).strict(),
|
|
152
|
+
zod.z.object({
|
|
153
|
+
type: zod.z.literal("workflow.op.state.append"),
|
|
154
|
+
target: ScopeKeySchema,
|
|
155
|
+
item: SourceSchema
|
|
156
|
+
}).strict(),
|
|
157
|
+
zod.z.object({
|
|
158
|
+
type: zod.z.literal("workflow.op.state.updateWhere"),
|
|
159
|
+
target: ScopeKeySchema,
|
|
160
|
+
where: OpPredicateSchema,
|
|
161
|
+
merge: zod.z.record(NonEmpty, SourceSchema)
|
|
162
|
+
}).strict(),
|
|
163
|
+
zod.z.object({
|
|
164
|
+
type: zod.z.literal("workflow.op.state.removeWhere"),
|
|
165
|
+
target: ScopeKeySchema,
|
|
166
|
+
where: OpPredicateSchema
|
|
167
|
+
}).strict(),
|
|
168
|
+
zod.z.object({
|
|
169
|
+
type: zod.z.literal("workflow.op.status.set"),
|
|
170
|
+
taskId: NonEmpty,
|
|
171
|
+
status: zod.z.enum(TASK_STATUSES)
|
|
172
|
+
}).strict()
|
|
173
|
+
]), StateSlotSourceSchema = zod.z.union([
|
|
174
|
+
zod.z.object({ kind: zod.z.literal("init") }).strict(),
|
|
175
|
+
zod.z.object({
|
|
176
|
+
kind: zod.z.literal("query"),
|
|
177
|
+
query: NonEmpty,
|
|
178
|
+
resource: zod.z.union([zod.z.literal("workflow"), zod.z.literal("subject"), GdrSchema]).optional()
|
|
179
|
+
}).strict(),
|
|
180
|
+
zod.z.object({ kind: zod.z.literal("write") }).strict(),
|
|
181
|
+
zod.z.object({ kind: zod.z.literal("computed"), compute: NonEmpty }).strict(),
|
|
182
|
+
/**
|
|
183
|
+
* Pre-populate the slot at resolution time with a constant value.
|
|
184
|
+
* Useful for stage- or task-scope slots that should start non-empty
|
|
185
|
+
* (e.g. a default headline, a starting tally).
|
|
186
|
+
*/
|
|
187
|
+
zod.z.object({ kind: zod.z.literal("literal"), value: zod.z.unknown() }).strict(),
|
|
188
|
+
/**
|
|
189
|
+
* Pre-populate by reading another already-resolved slot. `scope:
|
|
190
|
+
* "workflow"` reads from the workflow-scope `state[]` (resolved at
|
|
191
|
+
* startInstance). `scope: "stage"` reads from an earlier slot in
|
|
192
|
+
* this same stage entry's `state[]` (sequential resolution).
|
|
193
|
+
* Optional `path` selects a nested field (e.g. `path: "id"` on a
|
|
194
|
+
* doc.ref slot).
|
|
195
|
+
*/
|
|
196
|
+
zod.z.object({
|
|
197
|
+
kind: zod.z.literal("stateRead"),
|
|
198
|
+
scope: zod.z.union([zod.z.literal("workflow"), zod.z.literal("stage")]),
|
|
199
|
+
slotId: NonEmpty,
|
|
200
|
+
path: zod.z.string().optional()
|
|
201
|
+
}).strict()
|
|
202
|
+
]), StateSlotBase = {
|
|
203
|
+
id: NonEmpty,
|
|
204
|
+
title: zod.z.string().optional(),
|
|
205
|
+
description: zod.z.string().optional(),
|
|
206
|
+
source: StateSlotSourceSchema
|
|
207
|
+
}, StateSlotKindSchema = zod.z.enum([
|
|
208
|
+
"workflow.state.doc.ref",
|
|
209
|
+
"workflow.state.doc.refs",
|
|
210
|
+
// Release reference. A workflow declares this slot to say "I target a
|
|
211
|
+
// Content Release"; the runtime fills it with a specific release at
|
|
212
|
+
// start time, and the engine auto-derives the instance's read
|
|
213
|
+
// perspective from it.
|
|
214
|
+
"workflow.state.release.ref",
|
|
215
|
+
"workflow.state.query",
|
|
216
|
+
"workflow.state.value.string",
|
|
217
|
+
"workflow.state.value.url",
|
|
218
|
+
"workflow.state.value.number",
|
|
219
|
+
"workflow.state.value.boolean",
|
|
220
|
+
"workflow.state.value.dateTime",
|
|
221
|
+
"workflow.state.value.actor",
|
|
222
|
+
"workflow.state.checklist",
|
|
223
|
+
"workflow.state.notes",
|
|
224
|
+
"workflow.state.assignees"
|
|
225
|
+
]), StateSlotSchema = zod.z.discriminatedUnion(
|
|
226
|
+
"type",
|
|
227
|
+
StateSlotKindSchema.options.map(
|
|
228
|
+
(kind) => zod.z.object({ type: zod.z.literal(kind), ...StateSlotBase }).strict()
|
|
229
|
+
)
|
|
230
|
+
), FilterSchema = zod.z.union([
|
|
231
|
+
// Inline GROQ — only reserved params allowed at runtime.
|
|
232
|
+
NonEmpty,
|
|
233
|
+
// Reference to a named predicate, optionally with named args.
|
|
234
|
+
zod.z.object({
|
|
235
|
+
ref: NonEmpty,
|
|
236
|
+
args: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
|
|
237
|
+
}).strict()
|
|
238
|
+
]), PredicateParamSchema = zod.z.object({
|
|
239
|
+
id: NonEmpty,
|
|
240
|
+
title: zod.z.string().optional(),
|
|
241
|
+
description: zod.z.string().optional(),
|
|
242
|
+
type: zod.z.enum(["string", "number", "boolean", "string[]", "reference"]),
|
|
243
|
+
enum: zod.z.array(zod.z.string()).optional()
|
|
244
|
+
}).strict(), PredicateSchema = zod.z.object({
|
|
245
|
+
type: zod.z.literal("workflow.predicate").optional(),
|
|
246
|
+
id: NonEmpty,
|
|
247
|
+
title: zod.z.string().optional(),
|
|
248
|
+
description: zod.z.string().optional(),
|
|
249
|
+
groq: NonEmpty,
|
|
250
|
+
params: zod.z.array(PredicateParamSchema).optional()
|
|
251
|
+
}).strict(), EffectSchema = zod.z.object({
|
|
252
|
+
type: zod.z.literal("workflow.effect").optional(),
|
|
253
|
+
id: NonEmpty,
|
|
254
|
+
title: zod.z.string().optional(),
|
|
255
|
+
description: zod.z.string().optional(),
|
|
256
|
+
// Bindings resolve typed Source values at commit time, producing
|
|
257
|
+
// concrete JSON in the queued effect's `params`. NO GROQ.
|
|
258
|
+
bindings: zod.z.record(zod.z.string(), SourceSchema).optional(),
|
|
259
|
+
input: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
|
|
260
|
+
}).strict(), TerminalTaskStatus = zod.z.enum(["done", "skipped", "failed"]), PermissionSchema = zod.z.enum(DOCUMENT_VALUE_PERMISSIONS), ActionParamSchema = zod.z.object({
|
|
261
|
+
type: zod.z.literal("workflow.action.param").optional(),
|
|
262
|
+
id: NonEmpty,
|
|
263
|
+
title: zod.z.string().optional(),
|
|
264
|
+
description: zod.z.string().optional(),
|
|
265
|
+
paramType: zod.z.enum([
|
|
266
|
+
"string",
|
|
267
|
+
"number",
|
|
268
|
+
"boolean",
|
|
269
|
+
"url",
|
|
270
|
+
"dateTime",
|
|
271
|
+
"actor",
|
|
272
|
+
"doc.ref",
|
|
273
|
+
"doc.refs",
|
|
274
|
+
"json"
|
|
275
|
+
]),
|
|
276
|
+
required: zod.z.boolean().optional()
|
|
277
|
+
}).strict(), ActionSchema = zod.z.object({
|
|
278
|
+
type: zod.z.literal("workflow.action").optional(),
|
|
279
|
+
id: NonEmpty,
|
|
280
|
+
title: zod.z.string().optional(),
|
|
281
|
+
description: zod.z.string().optional(),
|
|
282
|
+
filter: FilterSchema.optional(),
|
|
283
|
+
/** Caller-supplied params, validated before ops/effects run. */
|
|
284
|
+
params: zod.z.array(ActionParamSchema).optional(),
|
|
285
|
+
/**
|
|
286
|
+
* Status the task flips to when this action fires. Omit to leave
|
|
287
|
+
* the task status unchanged — used by intermediate `claim` /
|
|
288
|
+
* `assign` / partial-update actions where the task should stay
|
|
289
|
+
* active so a later action can fire on it. Declared values must
|
|
290
|
+
* be terminal (`done` / `skipped` / `failed`); the engine never
|
|
291
|
+
* mid-flip back to `active` outside of the `workflow.op.status.set`
|
|
292
|
+
* op explicit path.
|
|
293
|
+
*/
|
|
294
|
+
setStatus: TerminalTaskStatus.optional(),
|
|
295
|
+
/**
|
|
296
|
+
* Inline state-mutation primitives executed during the action commit.
|
|
297
|
+
* Each op runs against the in-memory mutation (deterministic, no
|
|
298
|
+
* external I/O). Resolved Sources turn into concrete JSON before the
|
|
299
|
+
* op applies. Logged on `history[]` as `workflow.history.opApplied`
|
|
300
|
+
* and surfaced on the `ActionResult.ranOps`.
|
|
301
|
+
*/
|
|
302
|
+
ops: zod.z.array(OpSchema).optional(),
|
|
303
|
+
effects: zod.z.array(EffectSchema).optional(),
|
|
304
|
+
/**
|
|
305
|
+
* Role gating — OR-match against `Actor.roles[]`. If omitted, no role
|
|
306
|
+
* gate. `"*"` in Actor.roles always matches (bench / all-access).
|
|
307
|
+
*/
|
|
308
|
+
roles: zod.z.array(NonEmpty).optional(),
|
|
309
|
+
/**
|
|
310
|
+
* When true, the actor must appear in the task's `assignees[]` (as a
|
|
311
|
+
* user-kind assignee matching by id) for the action to be allowed.
|
|
312
|
+
*/
|
|
313
|
+
requireAssignment: zod.z.boolean().optional(),
|
|
314
|
+
/**
|
|
315
|
+
* Which permission on the workflow.instance document the actor must
|
|
316
|
+
* hold (per supplied `grants`). Defaults to "update". Only checked
|
|
317
|
+
* when grants are supplied at evaluate/fireAction time.
|
|
318
|
+
*/
|
|
319
|
+
requiredPermission: PermissionSchema.optional()
|
|
320
|
+
}).strict(), CompletionPolicySchema = zod.z.union([
|
|
321
|
+
zod.z.object({ kind: zod.z.literal("all") }).strict(),
|
|
322
|
+
zod.z.object({ kind: zod.z.literal("any") }).strict(),
|
|
323
|
+
zod.z.object({
|
|
324
|
+
kind: zod.z.literal("count"),
|
|
325
|
+
n: zod.z.number().int().positive()
|
|
326
|
+
}).strict()
|
|
327
|
+
]), LogicalDefinitionRefSchema = zod.z.object({
|
|
328
|
+
workflowId: NonEmpty,
|
|
329
|
+
version: zod.z.union([zod.z.number().int().positive(), zod.z.literal("latest")]).optional()
|
|
330
|
+
}).strict(), SpawnForEachSchema = zod.z.object({
|
|
331
|
+
groq: NonEmpty,
|
|
332
|
+
as: zod.z.object({
|
|
333
|
+
initialState: zod.z.array(
|
|
334
|
+
zod.z.object({
|
|
335
|
+
type: StateSlotKindSchema,
|
|
336
|
+
id: NonEmpty,
|
|
337
|
+
value: SourceSchema
|
|
338
|
+
}).strict()
|
|
339
|
+
).optional(),
|
|
340
|
+
effectsContext: zod.z.record(zod.z.string(), SourceSchema).optional()
|
|
341
|
+
}).strict()
|
|
342
|
+
}).strict(), SpawnSchema = zod.z.object({
|
|
343
|
+
type: zod.z.literal("workflow.spawn").optional(),
|
|
344
|
+
id: zod.z.string().optional(),
|
|
345
|
+
title: zod.z.string().optional(),
|
|
346
|
+
description: zod.z.string().optional(),
|
|
347
|
+
definitionRef: LogicalDefinitionRefSchema,
|
|
348
|
+
forEach: SpawnForEachSchema,
|
|
349
|
+
completionPolicy: CompletionPolicySchema.optional()
|
|
350
|
+
}).strict(), AssigneeSchema = zod.z.discriminatedUnion("kind", [
|
|
351
|
+
zod.z.object({ kind: zod.z.literal("user"), id: NonEmpty }).strict(),
|
|
352
|
+
zod.z.object({ kind: zod.z.literal("role"), role: NonEmpty }).strict()
|
|
353
|
+
]), TaskSchema = zod.z.object({
|
|
354
|
+
type: zod.z.literal("workflow.task").optional(),
|
|
355
|
+
id: NonEmpty,
|
|
356
|
+
title: zod.z.string().optional(),
|
|
357
|
+
description: zod.z.string().optional(),
|
|
358
|
+
assignees: zod.z.array(AssigneeSchema).optional(),
|
|
359
|
+
filter: FilterSchema.optional(),
|
|
360
|
+
invokeOn: zod.z.enum(["stageEnter", "manual"]).optional(),
|
|
361
|
+
/**
|
|
362
|
+
* Auto-completion predicate — same shape as `transition.filter`. When
|
|
363
|
+
* truthy at activation or on any subsequent cascade, the engine
|
|
364
|
+
* flips the task to `done` with a `{ kind: "system", id: "engine.completeWhen" }`
|
|
365
|
+
* actor. A full filter, not just a wall-clock deadline.
|
|
366
|
+
*/
|
|
367
|
+
completeWhen: FilterSchema.optional(),
|
|
368
|
+
/**
|
|
369
|
+
* Auto-failure predicate — symmetric to `completeWhen`. Same shape,
|
|
370
|
+
* same evaluation cadence (activation + every cascade), but flips
|
|
371
|
+
* the task to `failed` (not `done`) with a `{ kind: "system",
|
|
372
|
+
* id: "engine.failWhen" }` actor. When both `completeWhen` and
|
|
373
|
+
* `failWhen` resolve truthy on the same evaluation, `failWhen`
|
|
374
|
+
* wins — failure is the more notable signal and should not be
|
|
375
|
+
* silently swallowed.
|
|
376
|
+
*/
|
|
377
|
+
failWhen: FilterSchema.optional(),
|
|
378
|
+
effects: zod.z.array(EffectSchema).optional(),
|
|
379
|
+
actions: zod.z.array(ActionSchema).optional(),
|
|
380
|
+
spawns: SpawnSchema.optional(),
|
|
381
|
+
contentRefs: zod.z.array(zod.z.unknown()).optional(),
|
|
382
|
+
/** Task-scoped state slots. Resolved at task activation time. */
|
|
383
|
+
state: zod.z.array(StateSlotSchema).optional()
|
|
384
|
+
}).strict(), TransitionSchema = zod.z.object({
|
|
385
|
+
type: zod.z.literal("workflow.transition").optional(),
|
|
386
|
+
id: NonEmpty,
|
|
387
|
+
title: zod.z.string().optional(),
|
|
388
|
+
description: zod.z.string().optional(),
|
|
389
|
+
to: NonEmpty,
|
|
390
|
+
on: zod.z.string().optional(),
|
|
391
|
+
filter: FilterSchema.optional(),
|
|
392
|
+
effects: zod.z.array(EffectSchema).optional()
|
|
393
|
+
}).strict(), GuardActionSchema = zod.z.enum(MUTATION_GUARD_ACTIONS), GuardMatchSchema = zod.z.object({
|
|
394
|
+
/** Subject `_type`(s); empty matches any type. */
|
|
395
|
+
types: zod.z.array(NonEmpty).optional(),
|
|
396
|
+
/** Target docs as `Source`s resolved at deploy to bare ids + the resource. */
|
|
397
|
+
idRefs: zod.z.array(SourceSchema).optional(),
|
|
398
|
+
/** Glob id patterns (bare, resource-local). */
|
|
399
|
+
idPatterns: zod.z.array(NonEmpty).optional(),
|
|
400
|
+
actions: zod.z.array(GuardActionSchema).min(1, "a guard must match at least one action")
|
|
401
|
+
}).strict(), GuardSchema = zod.z.object({
|
|
402
|
+
name: zod.z.string().optional(),
|
|
403
|
+
description: zod.z.string().optional(),
|
|
404
|
+
match: GuardMatchSchema,
|
|
405
|
+
/**
|
|
406
|
+
* Lake GROQ predicate. Reads `document.before`/`document.after`, `mutation`,
|
|
407
|
+
* `guard`, and `identity()`. Bare ids/fields only — no GDRs. Omitted or
|
|
408
|
+
* empty means UNCONDITIONAL DENY.
|
|
409
|
+
*/
|
|
410
|
+
predicate: zod.z.string().optional(),
|
|
411
|
+
/**
|
|
412
|
+
* Projected workflow state the predicate reads as `guard.metadata.*`. Values
|
|
413
|
+
* are `Source`s resolved at deploy and re-synced by the engine. Copied data,
|
|
414
|
+
* never a cross-resource pointer.
|
|
415
|
+
*/
|
|
416
|
+
metadata: zod.z.record(NonEmpty, SourceSchema).optional()
|
|
417
|
+
}).strict(), StageKindSchema = zod.z.enum(["initial", "intermediate", "terminal", "waiting"]), StageSchema = zod.z.object({
|
|
418
|
+
type: zod.z.literal("workflow.stage").optional(),
|
|
419
|
+
id: NonEmpty,
|
|
420
|
+
title: zod.z.string().optional(),
|
|
421
|
+
description: zod.z.string().optional(),
|
|
422
|
+
kind: StageKindSchema.optional(),
|
|
423
|
+
tasks: zod.z.array(TaskSchema).optional(),
|
|
424
|
+
transitions: zod.z.array(TransitionSchema).optional(),
|
|
425
|
+
completion: FilterSchema.optional(),
|
|
426
|
+
onEnter: zod.z.array(EffectSchema).optional(),
|
|
427
|
+
onExit: zod.z.array(EffectSchema).optional(),
|
|
428
|
+
/**
|
|
429
|
+
* Lake mutation guards active while this stage holds. Each compiles to a
|
|
430
|
+
* `temp.system.guard` doc deployed on stage entry and retracted on exit.
|
|
431
|
+
* Plural because one stage's intent may span resources (one guard each).
|
|
432
|
+
*/
|
|
433
|
+
guards: zod.z.array(GuardSchema).optional(),
|
|
434
|
+
contentRefs: zod.z.array(zod.z.unknown()).optional(),
|
|
435
|
+
/** Stage-scoped state slots. Resolved at stage entry and persisted on
|
|
436
|
+
* the StageEntry. */
|
|
437
|
+
state: zod.z.array(StateSlotSchema).optional()
|
|
438
|
+
}).strict(), WorkflowDefinitionShape = zod.z.object({
|
|
439
|
+
workflowId: NonEmpty,
|
|
440
|
+
version: zod.z.number().int().positive(),
|
|
441
|
+
title: NonEmpty,
|
|
442
|
+
description: zod.z.string().optional(),
|
|
443
|
+
initialStageId: NonEmpty,
|
|
444
|
+
/**
|
|
445
|
+
* Workflow-level state slots. Persist for the instance lifetime.
|
|
446
|
+
* Every stage's filters/ops can read them via
|
|
447
|
+
* `*[_id == $self][0].state[key == "<key>"][0].value`. Init-sourced
|
|
448
|
+
* slots are supplied at `startInstance` via `initialState`.
|
|
449
|
+
*/
|
|
450
|
+
state: zod.z.array(StateSlotSchema).optional(),
|
|
451
|
+
stages: zod.z.array(StageSchema).min(1, "must declare at least one stage"),
|
|
452
|
+
predicates: zod.z.array(PredicateSchema).optional()
|
|
453
|
+
}).strict(), WorkflowDefinitionSchema = WorkflowDefinitionShape.superRefine(checkWorkflowInvariants);
|
|
454
|
+
function formatZodError(error, label) {
|
|
455
|
+
const lines = error.issues.map((issue) => ` - ${issue.path.length === 0 ? "(root)" : formatPath(issue.path)}: ${issue.message}`);
|
|
456
|
+
return `${label} failed validation (${error.issues.length} issue${error.issues.length === 1 ? "" : "s"}):
|
|
457
|
+
${lines.join(`
|
|
458
|
+
`)}`;
|
|
459
|
+
}
|
|
460
|
+
function formatPath(path) {
|
|
461
|
+
let out = "";
|
|
462
|
+
for (const seg of path)
|
|
463
|
+
typeof seg == "number" ? out += `[${seg}]` : out += out.length === 0 ? String(seg) : `.${String(seg)}`;
|
|
464
|
+
return out;
|
|
465
|
+
}
|
|
466
|
+
function defineWorkflow(definition) {
|
|
467
|
+
return parseOrThrow(
|
|
468
|
+
WorkflowDefinitionSchema,
|
|
469
|
+
definition,
|
|
470
|
+
typeof definition.workflowId == "string" ? `defineWorkflow("${definition.workflowId}")` : "defineWorkflow"
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
function defineStage(stage) {
|
|
474
|
+
return parseOrThrow(StageSchema, stage, labelFor("defineStage", stage, "id"));
|
|
475
|
+
}
|
|
476
|
+
function defineTask(task) {
|
|
477
|
+
return parseOrThrow(TaskSchema, task, labelFor("defineTask", task, "id"));
|
|
478
|
+
}
|
|
479
|
+
function defineAction(action) {
|
|
480
|
+
return parseOrThrow(ActionSchema, action, labelFor("defineAction", action, "id"));
|
|
481
|
+
}
|
|
482
|
+
function defineTransition(transition) {
|
|
483
|
+
return parseOrThrow(
|
|
484
|
+
TransitionSchema,
|
|
485
|
+
transition,
|
|
486
|
+
typeof transition.id == "string" ? `defineTransition("${transition.id}")` : typeof transition.to == "string" ? `defineTransition(\u2192 "${transition.to}")` : "defineTransition"
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
function defineFilter(filter) {
|
|
490
|
+
return parseOrThrow(FilterSchema, filter, "defineFilter");
|
|
491
|
+
}
|
|
492
|
+
function defineAssignee(assignee) {
|
|
493
|
+
return parseOrThrow(AssigneeSchema, assignee, "defineAssignee");
|
|
494
|
+
}
|
|
495
|
+
function definePredicate(predicate) {
|
|
496
|
+
return parseOrThrow(PredicateSchema, predicate, labelFor("definePredicate", predicate, "id"));
|
|
497
|
+
}
|
|
498
|
+
function defineEffect(effect) {
|
|
499
|
+
return parseOrThrow(EffectSchema, effect, labelFor("defineEffect", effect, "id"));
|
|
500
|
+
}
|
|
501
|
+
function defineEffectDescriptor(descriptor) {
|
|
502
|
+
if (!descriptor.name) throw new Error("EffectDescriptor missing name");
|
|
503
|
+
return descriptor;
|
|
504
|
+
}
|
|
505
|
+
function parseOrThrow(schema, input, label) {
|
|
506
|
+
const result = schema.safeParse(input);
|
|
507
|
+
if (!result.success)
|
|
508
|
+
throw new Error(formatZodError(result.error, label));
|
|
509
|
+
return result.data;
|
|
510
|
+
}
|
|
511
|
+
function labelFor(fn, value, key) {
|
|
512
|
+
if (value && typeof value == "object" && key in value) {
|
|
513
|
+
const v = value[key];
|
|
514
|
+
if (typeof v == "string") return `${fn}("${v}")`;
|
|
515
|
+
}
|
|
516
|
+
return fn;
|
|
517
|
+
}
|
|
518
|
+
exports.defineAction = defineAction;
|
|
519
|
+
exports.defineAssignee = defineAssignee;
|
|
520
|
+
exports.defineEffect = defineEffect;
|
|
521
|
+
exports.defineEffectDescriptor = defineEffectDescriptor;
|
|
522
|
+
exports.defineFilter = defineFilter;
|
|
523
|
+
exports.definePredicate = definePredicate;
|
|
524
|
+
exports.defineStage = defineStage;
|
|
525
|
+
exports.defineTask = defineTask;
|
|
526
|
+
exports.defineTransition = defineTransition;
|
|
527
|
+
exports.defineWorkflow = defineWorkflow;
|
|
528
|
+
//# sourceMappingURL=define.cjs.map
|