@sanity/workflow-engine 0.13.0 → 0.15.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.
@@ -1,626 +0,0 @@
1
- import * as v from "valibot";
2
- function andConditions(parts) {
3
- const present = parts.filter((p) => p !== void 0);
4
- if (present.length !== 0)
5
- return present.length === 1 ? present[0] : present.map((p) => `(${p})`).join(" && ");
6
- }
7
- const UNIVERSAL_ROLE_ALIAS_KEY = "$all";
8
- function normalizeRoleAliases(aliases) {
9
- if (aliases === void 0 || !("*" in aliases)) return aliases;
10
- const { ["*"]: universal, ...perRole } = aliases;
11
- return { ...perRole, [UNIVERSAL_ROLE_ALIAS_KEY]: universal };
12
- }
13
- function expandRequiredRoles(required, aliases) {
14
- if (aliases === void 0) return [...required];
15
- const out = /* @__PURE__ */ new Set();
16
- for (const role of required) {
17
- out.add(role);
18
- for (const fulfiller of aliases[role] ?? []) out.add(fulfiller);
19
- }
20
- for (const fulfiller of aliases[UNIVERSAL_ROLE_ALIAS_KEY] ?? []) out.add(fulfiller);
21
- return [...out];
22
- }
23
- function actorFulfillsRole({
24
- actorRoles,
25
- required,
26
- aliases
27
- }) {
28
- if (actorRoles === void 0 || actorRoles.length === 0) return !1;
29
- const accepted = new Set(expandRequiredRoles([required], aliases));
30
- return actorRoles.some((role) => accepted.has(role));
31
- }
32
- const ACTIVITY_STATUSES = ["pending", "active", "done", "skipped", "failed"], TERMINAL_ACTIVITY_STATUSES = ["done", "skipped", "failed"];
33
- function isTerminalActivityStatus(status) {
34
- return TERMINAL_ACTIVITY_STATUSES.includes(status);
35
- }
36
- const FIELD_SCOPES = ["workflow", "stage", "activity"], DOCUMENT_VALUE_PERMISSIONS = ["create", "read", "update"], MUTATION_GUARD_ACTIONS = [
37
- "create",
38
- "update",
39
- "delete",
40
- "publish",
41
- "unpublish"
42
- ], ACTIVITY_KINDS = ["user", "service", "script", "manual", "receive"], DRIVER_KINDS = ["person", "agent", "service", "engine"], NonEmpty = v.pipe(v.string(), v.minLength(1, "must be a non-empty string")), PositiveInt = v.pipe(v.number(), v.integer(), v.minValue(1)), GROQ_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
43
- function groqIdentifier(referencedAs) {
44
- return v.pipe(
45
- v.string(),
46
- v.regex(
47
- GROQ_IDENTIFIER,
48
- `must be a GROQ-safe identifier (letters, digits, underscore; not starting with a digit) because it is referenced as ${referencedAs} in GROQ conditions`
49
- )
50
- );
51
- }
52
- function picklist(options) {
53
- return v.picklist(
54
- options,
55
- `Invalid option: expected one of ${options.map((o) => `"${o}"`).join("|")}`
56
- );
57
- }
58
- function pinned() {
59
- return (schema, ..._exact) => schema;
60
- }
61
- const LiteralSchema = v.strictObject({ type: v.literal("literal"), value: v.unknown() }), FieldReadSchema = v.strictObject({
62
- type: v.literal("fieldRead"),
63
- scope: v.optional(v.union([v.literal("workflow"), v.literal("stage")])),
64
- field: NonEmpty,
65
- path: v.optional(v.string())
66
- }), FieldSourceSchema = v.union([
67
- // The caller supplies it at start/spawn (`initialFields` / `subworkflows.with`).
68
- v.strictObject({ type: v.literal("input") }),
69
- // Computed once by running GROQ against the lake at materialisation.
70
- v.strictObject({ type: v.literal("query"), query: NonEmpty }),
71
- LiteralSchema,
72
- FieldReadSchema
73
- ]), ValueExprSchema = v.lazy(
74
- () => v.union([
75
- LiteralSchema,
76
- FieldReadSchema,
77
- v.strictObject({ type: v.literal("param"), param: NonEmpty }),
78
- v.strictObject({ type: v.literal("actor") }),
79
- v.strictObject({ type: v.literal("now") }),
80
- v.strictObject({ type: v.literal("self") }),
81
- v.strictObject({ type: v.literal("stage") }),
82
- v.strictObject({ type: v.literal("object"), fields: v.record(NonEmpty, ValueExprSchema) })
83
- ])
84
- ), StoredFieldRefSchema = v.strictObject({
85
- scope: picklist(FIELD_SCOPES),
86
- field: NonEmpty
87
- }), AuthoringFieldRefSchema = v.strictObject({
88
- scope: v.optional(picklist(FIELD_SCOPES)),
89
- field: NonEmpty
90
- }), HREF_SCHEMES = ["http:", "https:"];
91
- function isHttpUrl(value) {
92
- try {
93
- return HREF_SCHEMES.includes(new URL(value).protocol);
94
- } catch {
95
- return !1;
96
- }
97
- }
98
- const UrlString = v.pipe(
99
- v.string(),
100
- v.url("must be a valid URL"),
101
- v.check(isHttpUrl, "must be an http(s) URL")
102
- );
103
- function manualTargetSchema(ref) {
104
- return v.variant("type", [
105
- v.strictObject({ type: v.literal("url"), url: UrlString }),
106
- v.strictObject({ type: v.literal("field"), field: ref })
107
- ]);
108
- }
109
- const StoredManualTargetSchema = manualTargetSchema(StoredFieldRefSchema), AuthoringManualTargetSchema = manualTargetSchema(v.union([NonEmpty, AuthoringFieldRefSchema])), OpPredicateSchema = v.lazy(
110
- () => v.union([
111
- v.strictObject({
112
- type: v.literal("field"),
113
- field: NonEmpty,
114
- equals: ValueExprSchema
115
- }),
116
- v.strictObject({ type: v.literal("all"), of: v.array(OpPredicateSchema) }),
117
- v.strictObject({ type: v.literal("any"), of: v.array(OpPredicateSchema) })
118
- ])
119
- );
120
- function opSchemas(targetSchema) {
121
- return [
122
- v.strictObject({
123
- type: v.literal("field.set"),
124
- target: targetSchema,
125
- value: ValueExprSchema
126
- }),
127
- v.strictObject({
128
- type: v.literal("field.unset"),
129
- target: targetSchema
130
- }),
131
- v.strictObject({
132
- type: v.literal("field.append"),
133
- target: targetSchema,
134
- value: ValueExprSchema
135
- }),
136
- v.strictObject({
137
- type: v.literal("field.updateWhere"),
138
- target: targetSchema,
139
- where: OpPredicateSchema,
140
- value: ValueExprSchema
141
- }),
142
- v.strictObject({
143
- type: v.literal("field.removeWhere"),
144
- target: targetSchema,
145
- where: OpPredicateSchema
146
- })
147
- ];
148
- }
149
- const StoredFieldOpSchema = v.variant("type", [...opSchemas(StoredFieldRefSchema)]), StoredOpSchema = v.variant("type", [
150
- ...opSchemas(StoredFieldRefSchema),
151
- v.strictObject({
152
- type: v.literal("status.set"),
153
- activity: NonEmpty,
154
- status: picklist(ACTIVITY_STATUSES)
155
- })
156
- ]), StoredTransitionOpSchema = StoredFieldOpSchema, AuditOpSchema = v.strictObject({
157
- type: v.literal("audit"),
158
- target: AuthoringFieldRefSchema,
159
- value: ValueExprSchema,
160
- stampFields: v.optional(
161
- v.strictObject({
162
- actor: v.optional(NonEmpty),
163
- at: v.optional(NonEmpty)
164
- })
165
- )
166
- }), AuthoringOpSchema = v.variant("type", [
167
- ...opSchemas(AuthoringFieldRefSchema),
168
- v.strictObject({
169
- type: v.literal("status.set"),
170
- activity: v.optional(NonEmpty),
171
- status: picklist(ACTIVITY_STATUSES)
172
- }),
173
- AuditOpSchema
174
- ]), FIELD_VALUE_KINDS = [
175
- "doc.ref",
176
- "doc.refs",
177
- // Release reference. A workflow declares this entry to say "I target a
178
- // Content Release"; the runtime fills it at start time and auto-derives
179
- // the instance's read perspective from it.
180
- "release.ref",
181
- "string",
182
- "text",
183
- "number",
184
- "boolean",
185
- "date",
186
- "datetime",
187
- "url",
188
- "actor",
189
- // A single {@link Assignee} — the singular of `assignees`.
190
- "assignee",
191
- // The WHO-FOR entry: the inbox reverse-query reads it by kind, and the
192
- // rendered `$assigned` gate matches the caller against it.
193
- "assignees",
194
- // Compositional kinds — mirror Sanity's `object.fields` / `array.of`.
195
- "object",
196
- "array"
197
- ], FieldValueKindSchema = picklist(FIELD_VALUE_KINDS), FieldKindSchema = picklist(FIELD_VALUE_KINDS), FieldEntryName = groqIdentifier("`$fields.<name>`");
198
- function asShape(input) {
199
- return typeof input == "object" && input !== null ? input : {};
200
- }
201
- function compositeShapeOk(input) {
202
- const shape = asShape(input);
203
- return shape.type === "object" ? Array.isArray(shape.fields) && shape.fields.length > 0 && shape.of === void 0 : shape.type === "array" ? Array.isArray(shape.of) && shape.of.length > 0 && shape.fields === void 0 : shape.fields === void 0 && shape.of === void 0;
204
- }
205
- function compositeShapeMessage(input) {
206
- const shape = asShape(input);
207
- return shape.type === "object" ? shape.of !== void 0 ? "an `object` kind declares its sub-fields with `fields`, not `of`" : "an `object` kind needs a non-empty `fields` list of sub-field shapes" : shape.type === "array" ? shape.fields !== void 0 ? "an `array` kind declares its item shape with `of`, not `fields`" : "an `array` kind needs a non-empty `of` list of sub-field shapes" : `\`fields\` / \`of\` are only valid on the \`object\` / \`array\` kinds, not "${String(shape.type)}"`;
208
- }
209
- function duplicateSubfieldName(input) {
210
- const shape = asShape(input);
211
- let list = [];
212
- Array.isArray(shape.fields) ? list = shape.fields : Array.isArray(shape.of) && (list = shape.of);
213
- const seen = /* @__PURE__ */ new Set();
214
- for (const item of list) {
215
- const name = asShape(item).name;
216
- if (typeof name == "string") {
217
- if (seen.has(name)) return name;
218
- seen.add(name);
219
- }
220
- }
221
- }
222
- function compositeChecked(entries) {
223
- return v.pipe(
224
- v.strictObject(entries),
225
- v.check(
226
- (input) => compositeShapeOk(input),
227
- (issue) => compositeShapeMessage(issue.input)
228
- ),
229
- v.check(
230
- (input) => duplicateSubfieldName(input) === void 0,
231
- (issue) => `duplicate sub-field name "${duplicateSubfieldName(issue.input)}" \u2014 sub-field names must be unique within \`fields\` / \`of\``
232
- )
233
- );
234
- }
235
- const FieldShapeSchema = v.lazy(
236
- () => compositeChecked({
237
- type: FieldValueKindSchema,
238
- name: FieldEntryName,
239
- title: v.optional(v.string()),
240
- description: v.optional(v.string()),
241
- fields: v.optional(v.array(FieldShapeSchema)),
242
- of: v.optional(v.array(FieldShapeSchema))
243
- })
244
- ), StoredEditableSchema = v.union([v.literal(!0), NonEmpty]), AuthoringEditableSchema = v.union([v.literal(!0), v.array(NonEmpty), NonEmpty]);
245
- function slotFields(editable) {
246
- return {
247
- name: FieldEntryName,
248
- title: v.optional(v.string()),
249
- description: v.optional(v.string()),
250
- /**
251
- * When true, the caller MUST supply this entry at start (via
252
- * `initialFields`) or spawn (via the parent's `subworkflows.with`). A
253
- * missing required entry throws rather than silently defaulting to
254
- * `null`/`[]` — the same fail-fast an action's `required` param gets.
255
- * Valid only on a workflow-scope `input`-sourced entry (deploy invariant).
256
- */
257
- required: v.optional(v.boolean()),
258
- /**
259
- * How this field SEEDS its value at materialisation — see
260
- * {@link FieldSourceSchema}. Optional: an absent `initialValue` is working
261
- * memory (the slot starts empty and an op fills it later), the common case.
262
- */
263
- initialValue: v.optional(FieldSourceSchema),
264
- /** Who-may-edit this slot via the edit seam — see {@link StoredEditableSchema}. */
265
- editable: v.optional(editable)
266
- };
267
- }
268
- function fieldEntryFields(editable) {
269
- return {
270
- type: FieldKindSchema,
271
- ...slotFields(editable),
272
- /** Sub-field shapes for an `object` kind (see {@link compositeShapeOk}). */
273
- fields: v.optional(v.array(FieldShapeSchema)),
274
- /** Item sub-field shapes for an `array` kind (see {@link compositeShapeOk}). */
275
- of: v.optional(v.array(FieldShapeSchema))
276
- };
277
- }
278
- const FieldEntrySchema = pinned()(
279
- compositeChecked(fieldEntryFields(StoredEditableSchema))
280
- ), RawAuthoringFieldEntrySchema = pinned()(
281
- compositeChecked(fieldEntryFields(AuthoringEditableSchema))
282
- ), ClaimFieldSchema = pinned()(
283
- v.strictObject({
284
- type: v.literal("claim"),
285
- name: FieldEntryName,
286
- title: v.optional(v.string()),
287
- description: v.optional(v.string())
288
- })
289
- );
290
- function listSugarFields(type) {
291
- return {
292
- type: v.literal(type),
293
- ...slotFields(AuthoringEditableSchema)
294
- };
295
- }
296
- const TodoListFieldSchema = pinned()(v.strictObject(listSugarFields("todoList"))), NotesFieldSchema = pinned()(v.strictObject(listSugarFields("notes"))), AuthoringFieldEntrySchema = pinned()(
297
- v.union([RawAuthoringFieldEntrySchema, ClaimFieldSchema, TodoListFieldSchema, NotesFieldSchema])
298
- ), ConditionSchema = NonEmpty, EffectSchema = v.strictObject({
299
- name: NonEmpty,
300
- title: v.optional(v.string()),
301
- description: v.optional(v.string()),
302
- /** GROQ reads over the rendered scope, resolved to concrete JSON at queue time. */
303
- bindings: v.optional(v.record(v.string(), ConditionSchema)),
304
- /** Static config, passed through to the handler verbatim. */
305
- input: v.optional(v.record(v.string(), v.unknown()))
306
- }), ActionParamSchema = v.strictObject({
307
- type: picklist([
308
- "string",
309
- "number",
310
- "boolean",
311
- "url",
312
- "dateTime",
313
- "actor",
314
- "doc.ref",
315
- "doc.refs",
316
- "json"
317
- ]),
318
- name: NonEmpty,
319
- title: v.optional(v.string()),
320
- description: v.optional(v.string()),
321
- required: v.optional(v.boolean())
322
- });
323
- function actionFields(op) {
324
- return {
325
- name: NonEmpty,
326
- title: v.optional(v.string()),
327
- description: v.optional(v.string()),
328
- /**
329
- * The one gate mechanism: a condition over the rendered scope. Hard
330
- * enforcement lives outside the engine entirely (the lake ACL on the
331
- * documents, and guards) — every engine-evaluated gate is authoring/UX.
332
- */
333
- filter: v.optional(ConditionSchema),
334
- params: v.optional(v.array(ActionParamSchema)),
335
- ops: v.optional(v.array(op)),
336
- effects: v.optional(v.array(EffectSchema))
337
- };
338
- }
339
- const StoredActionSchema = pinned()(v.strictObject(actionFields(StoredOpSchema))), TerminalActivityStatus = picklist(TERMINAL_ACTIVITY_STATUSES), RawAuthoringActionSchema = pinned()(
340
- v.strictObject({
341
- ...actionFields(AuthoringOpSchema),
342
- roles: v.optional(v.array(NonEmpty)),
343
- status: v.optional(TerminalActivityStatus)
344
- })
345
- ), ClaimActionSchema = pinned()(
346
- v.strictObject({
347
- type: v.literal("claim"),
348
- name: NonEmpty,
349
- title: v.optional(v.string()),
350
- description: v.optional(v.string()),
351
- field: v.union([NonEmpty, AuthoringFieldRefSchema]),
352
- roles: v.optional(v.array(NonEmpty)),
353
- filter: v.optional(ConditionSchema),
354
- params: v.optional(v.array(ActionParamSchema)),
355
- effects: v.optional(v.array(EffectSchema))
356
- })
357
- ), AuthoringActionSchema = pinned()(
358
- v.union([RawAuthoringActionSchema, ClaimActionSchema])
359
- ), DefinitionRefSchema = v.strictObject({
360
- name: NonEmpty,
361
- version: v.optional(v.union([PositiveInt, v.literal("latest")]))
362
- }), SubworkflowsSchema = v.strictObject({
363
- /** GROQ producing one row per subworkflow; each row binds as `$row`. */
364
- forEach: NonEmpty,
365
- definition: DefinitionRefSchema,
366
- /** Initial fields for each subworkflow — entry name → GROQ over `$row` + the parent scope. */
367
- with: v.optional(v.record(NonEmpty, ConditionSchema)),
368
- /**
369
- * Extra values evaluated in the parent's rendered scope at spawn time and
370
- * delivered into each subworkflow's `$effects` bag — the parent→child
371
- * handoff, read exactly like an effect output.
372
- */
373
- context: v.optional(v.record(NonEmpty, ConditionSchema))
374
- });
375
- function activityFields({ field, action, op, target }) {
376
- return {
377
- name: NonEmpty,
378
- title: v.optional(v.string()),
379
- description: v.optional(v.string()),
380
- /**
381
- * Advisory BPMN-aligned classification of this activity by its executor (see
382
- * {@link ACTIVITY_KINDS}). Optional and derived from the activity's shape when
383
- * omitted; declaring it lets deploy check the shape matches the lane.
384
- * Changes no gating or resolution — a label for tooling, like `assignees`.
385
- */
386
- kind: v.optional(picklist(ACTIVITY_KINDS)),
387
- /** Deep-link target for a `kind: "manual"` activity (off-system work). Render-only
388
- * metadata; only valid on a manual activity (deploy-checked). */
389
- target: v.optional(target),
390
- activation: v.optional(picklist(["auto", "manual"])),
391
- filter: v.optional(ConditionSchema),
392
- /**
393
- * Readiness gates, by name — conditions over the rendered scope that must
394
- * hold for the activity to be *executable*, orthogonal to `filter`
395
- * (visibility). An unmet requirement keeps the activity visible but disables
396
- * its actions with a `requirements-unmet` verdict naming the unmet keys;
397
- * all must hold (any-of lives inside one condition). Advisory like every
398
- * engine gate — the lake still enforces. Distinct from ACL (authorization)
399
- * and guards (content-write locks).
400
- */
401
- requirements: v.optional(v.record(NonEmpty, ConditionSchema)),
402
- /**
403
- * Auto-completion condition — evaluated at activation and on every
404
- * cascade; truthy flips the activity to `done` with a system actor. On a
405
- * spawning activity it typically reads `$subworkflows`.
406
- */
407
- completeWhen: v.optional(ConditionSchema),
408
- /**
409
- * Auto-failure condition — symmetric to `completeWhen`, flips to
410
- * `failed`. When both are truthy on the same evaluation, `failWhen`
411
- * wins — failure is the more notable signal.
412
- */
413
- failWhen: v.optional(ConditionSchema),
414
- ops: v.optional(v.array(op)),
415
- effects: v.optional(v.array(EffectSchema)),
416
- actions: v.optional(v.array(action)),
417
- subworkflows: v.optional(SubworkflowsSchema),
418
- /** Activity-scoped field entries. Resolved at activity activation time. */
419
- fields: v.optional(v.array(field))
420
- };
421
- }
422
- const StoredActivitySchema = pinned()(
423
- v.strictObject(
424
- activityFields({
425
- field: FieldEntrySchema,
426
- action: StoredActionSchema,
427
- op: StoredOpSchema,
428
- target: StoredManualTargetSchema
429
- })
430
- )
431
- ), AuthoringActivitySchema = pinned()(
432
- v.strictObject(
433
- activityFields({
434
- field: AuthoringFieldEntrySchema,
435
- action: AuthoringActionSchema,
436
- op: AuthoringOpSchema,
437
- target: AuthoringManualTargetSchema
438
- })
439
- )
440
- );
441
- function transitionFields(op, filter) {
442
- return {
443
- name: NonEmpty,
444
- title: v.optional(v.string()),
445
- description: v.optional(v.string()),
446
- to: NonEmpty,
447
- filter,
448
- /** The `field.*` subset — field-write-on-move, the stage's EXIT/ARRIVAL payload. */
449
- ops: v.optional(v.array(op)),
450
- effects: v.optional(v.array(EffectSchema))
451
- };
452
- }
453
- const StoredTransitionSchema = pinned()(
454
- v.strictObject(transitionFields(StoredTransitionOpSchema, ConditionSchema))
455
- ), AuthoringTransitionOpSchema = v.variant("type", [
456
- ...opSchemas(AuthoringFieldRefSchema),
457
- AuditOpSchema
458
- ]), AuthoringTransitionSchema = pinned()(
459
- v.strictObject(transitionFields(AuthoringTransitionOpSchema, v.optional(ConditionSchema)))
460
- ), GuardActionSchema = picklist(MUTATION_GUARD_ACTIONS), GuardMatchSchema = v.strictObject({
461
- /** Subject `_type`(s); empty matches any type. */
462
- types: v.optional(v.array(NonEmpty)),
463
- /** Target docs as `$fields` reads (or `"$self"`), resolved at deploy to bare ids + the resource. */
464
- idRefs: v.optional(v.array(NonEmpty)),
465
- /** Glob id patterns (bare, resource-local). */
466
- idPatterns: v.optional(v.array(NonEmpty)),
467
- actions: v.pipe(
468
- v.array(GuardActionSchema),
469
- v.minLength(1, "a guard must match at least one action")
470
- )
471
- }), GuardSchema = v.strictObject({
472
- name: NonEmpty,
473
- title: v.optional(v.string()),
474
- description: v.optional(v.string()),
475
- match: GuardMatchSchema,
476
- /**
477
- * Lake GROQ predicate — a distinct eval context reading
478
- * `document.before`/`document.after`, `mutation`, `guard`, and
479
- * `identity()`. Bare ids/fields only. Omitted or empty means
480
- * UNCONDITIONAL DENY.
481
- */
482
- predicate: v.optional(v.string()),
483
- /**
484
- * Projected workflow fields the predicate reads as `guard.metadata.*` —
485
- * the only bridge from the lake eval context (which cannot see `$fields`)
486
- * to workflow fields. Values are NOT GROQ: each is a deploy-time read in
487
- * the guard mini-language — `"$self"`, `"$now"`, or
488
- * `"$fields.<name>[.path]"` — resolved into a bare value at deploy and
489
- * re-synced by the post-field-op guard refresh.
490
- */
491
- metadata: v.optional(v.record(NonEmpty, NonEmpty))
492
- });
493
- function stageFields({
494
- field,
495
- activity,
496
- transition,
497
- editable
498
- }) {
499
- return {
500
- name: NonEmpty,
501
- title: v.optional(v.string()),
502
- description: v.optional(v.string()),
503
- activities: v.optional(v.array(activity)),
504
- transitions: v.optional(v.array(transition)),
505
- /**
506
- * Lake mutation guards active while this stage holds. Each compiles to a
507
- * `temp.system.guard` doc deployed on stage entry and retracted on exit.
508
- */
509
- guards: v.optional(v.array(GuardSchema)),
510
- /** Stage-scoped field entries. Resolved at stage entry. */
511
- fields: v.optional(v.array(field)),
512
- /**
513
- * Tighten-only editability overrides for the time this stage holds, keyed
514
- * by an in-scope slot name. The slot's own `editable` is the ceiling; a
515
- * stage value is ANDed with it at runtime, so an override can only NARROW
516
- * (never open a slot the baseline left closed). An unlisted slot inherits
517
- * its baseline.
518
- */
519
- editable: v.optional(v.record(FieldEntryName, editable))
520
- };
521
- }
522
- const StoredStageSchema = pinned()(
523
- v.strictObject(
524
- stageFields({
525
- field: FieldEntrySchema,
526
- activity: StoredActivitySchema,
527
- transition: StoredTransitionSchema,
528
- editable: StoredEditableSchema
529
- })
530
- )
531
- ), AuthoringStageSchema = pinned()(
532
- v.strictObject(
533
- stageFields({
534
- field: AuthoringFieldEntrySchema,
535
- activity: AuthoringActivitySchema,
536
- transition: AuthoringTransitionSchema,
537
- editable: AuthoringEditableSchema
538
- })
539
- )
540
- ), RoleAliasesSchema = v.record(
541
- NonEmpty,
542
- v.pipe(v.array(NonEmpty), v.minLength(1, "a role alias must list at least one fulfilling role"))
543
- ), WORKFLOW_ROLES = ["workflow", "child"];
544
- function workflowFields(field, stage) {
545
- return {
546
- name: NonEmpty,
547
- title: NonEmpty,
548
- description: v.optional(v.string()),
549
- /**
550
- * Whether a human may start this workflow standalone. `'child'` marks a
551
- * spawn-only definition — instantiated by a parent via `activity.subworkflows`,
552
- * never started cold from a picker. Omitted ⇒ `'workflow'` (startable).
553
- * Advisory: consumers filter their start pickers on it (see
554
- * {@link isStartableDefinition}); the engine does NOT refuse a
555
- * `startInstance` on a `'child'` def — load-bearing `required` field is the
556
- * runtime backstop.
557
- */
558
- role: v.optional(picklist(WORKFLOW_ROLES)),
559
- /** Reference field: named for the target, holds the stage's `name`. */
560
- initialStage: NonEmpty,
561
- /** Workflow-scope field entries. Persist for the instance lifetime. */
562
- fields: v.optional(v.array(field)),
563
- stages: v.pipe(v.array(stage), v.minLength(1, "must declare at least one stage")),
564
- /**
565
- * Nullary named conditions — each `name: groq` entry is pre-evaluated
566
- * and bound as the boolean `$name` var, composable with native GROQ.
567
- * Redefining a built-in var is a deploy error (never silently shadow).
568
- */
569
- predicates: v.optional(v.record(groqIdentifier("`$<name>`"), ConditionSchema)),
570
- /** Role aliasing for this definition — see {@link RoleAliasesSchema}. */
571
- roleAliases: v.optional(RoleAliasesSchema)
572
- };
573
- }
574
- pinned()(
575
- v.strictObject(workflowFields(FieldEntrySchema, StoredStageSchema))
576
- );
577
- const AuthoringWorkflowSchema = pinned()(
578
- v.strictObject(workflowFields(AuthoringFieldEntrySchema, AuthoringStageSchema))
579
- ), WORKFLOW_DEFINITION_TYPE = "sanity.workflow.definition";
580
- function isStartableDefinition(definition) {
581
- return definition.role !== "child";
582
- }
583
- function formatValidationError(label, issues) {
584
- const lines = issues.map((issue) => ` - ${issue.path.length === 0 ? "(root)" : formatPath(issue.path)}: ${issue.message}`);
585
- return `${label} failed validation (${issues.length} issue${issues.length === 1 ? "" : "s"}):
586
- ${lines.join(`
587
- `)}`;
588
- }
589
- function issuesFromValibot(issues) {
590
- return issues.map((issue) => ({
591
- path: issue.path ? issue.path.map((item) => item.key) : [],
592
- message: issue.message
593
- }));
594
- }
595
- function formatPath(path) {
596
- let out = "";
597
- for (const seg of path)
598
- typeof seg == "number" ? out += `[${seg}]` : out += out.length === 0 ? String(seg) : `.${String(seg)}`;
599
- return out;
600
- }
601
- export {
602
- ACTIVITY_KINDS,
603
- AuthoringActionSchema,
604
- AuthoringActivitySchema,
605
- AuthoringFieldEntrySchema,
606
- AuthoringOpSchema,
607
- AuthoringStageSchema,
608
- AuthoringTransitionSchema,
609
- AuthoringWorkflowSchema,
610
- DOCUMENT_VALUE_PERMISSIONS,
611
- DRIVER_KINDS,
612
- EffectSchema,
613
- GROQ_IDENTIFIER,
614
- GuardSchema,
615
- StoredFieldOpSchema,
616
- WORKFLOW_DEFINITION_TYPE,
617
- actorFulfillsRole,
618
- andConditions,
619
- expandRequiredRoles,
620
- formatValidationError,
621
- isStartableDefinition,
622
- isTerminalActivityStatus,
623
- issuesFromValibot,
624
- normalizeRoleAliases
625
- };
626
- //# sourceMappingURL=schema.js.map