@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.
- package/CHANGELOG.md +743 -0
- package/DATAMODEL.md +183 -0
- package/README.md +30 -0
- package/dist/_chunks-cjs/invariants.cjs +2461 -0
- package/dist/_chunks-es/invariants.js +2251 -0
- package/dist/define.cjs +676 -868
- package/dist/define.d.cts +1047 -85
- package/dist/define.d.ts +1047 -85
- package/dist/define.js +652 -865
- package/dist/index.cjs +9777 -5302
- package/dist/index.d.cts +3985 -733
- package/dist/index.d.ts +3985 -733
- package/dist/index.js +9401 -5359
- package/package.json +9 -5
- package/dist/_chunks-cjs/schema.cjs +0 -641
- package/dist/_chunks-cjs/schema.cjs.map +0 -1
- package/dist/_chunks-es/schema.js +0 -626
- package/dist/_chunks-es/schema.js.map +0 -1
- package/dist/define.cjs.map +0 -1
- package/dist/define.js.map +0 -1
- package/dist/index.cjs.map +0 -1
- package/dist/index.js.map +0 -1
|
@@ -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
|