@sanity/workflow-engine 0.1.0 → 0.3.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 CHANGED
@@ -1,69 +1,71 @@
1
1
  "use strict";
2
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
- ];
3
+ var v = require("valibot"), schema = require("./_chunks-cjs/schema.cjs");
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, ctx) {
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) && ctx.addIssue({
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 && ctx.addIssue({
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, ctx);
32
+ }), checkDuplicateTaskIds(stage, i, issues);
26
33
  return stageIds;
27
34
  }
28
- function checkDuplicateTaskIds(stage, i, ctx) {
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) && ctx.addIssue({
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, ctx) {
38
- stageIds.has(def.initialStageId) || ctx.addIssue({
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, ctx) {
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) || ctx.addIssue({
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, ctx) {
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) && ctx.addIssue({
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, ctx) {
66
+ function checkFilterRefs(def, predicateIds, issues) {
64
67
  const visit = (g, path) => {
65
- g === void 0 || typeof g == "string" || predicateIds.has(g.ref) || ctx.addIssue({
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,439 +81,59 @@ function checkFilterRefs(def, predicateIds, ctx) {
79
81
  visit(t.filter, ["stages", i, "transitions", k, "filter"]);
80
82
  }
81
83
  }
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;
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;
465
89
  }
466
90
  function defineWorkflow(definition) {
467
- return parseOrThrow(
468
- WorkflowDefinitionSchema,
469
- definition,
470
- typeof definition.workflowId == "string" ? `defineWorkflow("${definition.workflowId}")` : "defineWorkflow"
471
- );
91
+ const workflowId = definition.workflowId, label = typeof workflowId == "string" ? `defineWorkflow("${workflowId}")` : "defineWorkflow", parsed = parseOrThrow(schema.WorkflowDefinitionSchema, definition, label), issues = checkWorkflowInvariants(parsed);
92
+ if (issues.length > 0) throw new Error(schema.formatValidationError(label, issues));
93
+ return parsed;
472
94
  }
473
95
  function defineStage(stage) {
474
- return parseOrThrow(StageSchema, stage, labelFor("defineStage", stage, "id"));
96
+ return parseOrThrow(schema.StageSchema, stage, labelFor("defineStage", stage, "id"));
475
97
  }
476
98
  function defineTask(task) {
477
- return parseOrThrow(TaskSchema, task, labelFor("defineTask", task, "id"));
99
+ return parseOrThrow(schema.TaskSchema, task, labelFor("defineTask", task, "id"));
478
100
  }
479
101
  function defineAction(action) {
480
- return parseOrThrow(ActionSchema, action, labelFor("defineAction", action, "id"));
102
+ return parseOrThrow(schema.ActionSchema, action, labelFor("defineAction", action, "id"));
481
103
  }
482
104
  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
- );
105
+ return parseOrThrow(schema.TransitionSchema, transition, transitionLabel(transition));
106
+ }
107
+ function transitionLabel(transition) {
108
+ const { id, to } = transition;
109
+ return typeof id == "string" ? `defineTransition("${id}")` : typeof to == "string" ? `defineTransition(\u2192 "${to}")` : "defineTransition";
488
110
  }
489
111
  function defineFilter(filter) {
490
- return parseOrThrow(FilterSchema, filter, "defineFilter");
112
+ return parseOrThrow(schema.FilterSchema, filter, "defineFilter");
491
113
  }
492
114
  function defineAssignee(assignee) {
493
- return parseOrThrow(AssigneeSchema, assignee, "defineAssignee");
115
+ return parseOrThrow(schema.AssigneeSchema, assignee, "defineAssignee");
494
116
  }
495
117
  function definePredicate(predicate) {
496
- return parseOrThrow(PredicateSchema, predicate, labelFor("definePredicate", predicate, "id"));
118
+ return parseOrThrow(schema.PredicateSchema, predicate, labelFor("definePredicate", predicate, "id"));
497
119
  }
498
120
  function defineEffect(effect) {
499
- return parseOrThrow(EffectSchema, effect, labelFor("defineEffect", effect, "id"));
121
+ return parseOrThrow(schema.EffectSchema, effect, labelFor("defineEffect", effect, "id"));
500
122
  }
501
123
  function defineEffectDescriptor(descriptor) {
502
124
  if (!descriptor.name) throw new Error("EffectDescriptor missing name");
503
125
  return descriptor;
504
126
  }
505
- function parseOrThrow(schema, input, label) {
506
- const result = schema.safeParse(input);
127
+ function parseOrThrow(schema$1, input, label) {
128
+ const result = v__namespace.safeParse(schema$1, input);
507
129
  if (!result.success)
508
- throw new Error(formatZodError(result.error, label));
509
- return result.data;
130
+ throw new Error(schema.formatValidationError(label, schema.issuesFromValibot(result.issues)));
131
+ return result.output;
510
132
  }
511
133
  function labelFor(fn, value, key) {
512
134
  if (value && typeof value == "object" && key in value) {
513
- const v = value[key];
514
- if (typeof v == "string") return `${fn}("${v}")`;
135
+ const raw = value[key];
136
+ if (typeof raw == "string") return `${fn}("${raw}")`;
515
137
  }
516
138
  return fn;
517
139
  }