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