@sanity/workflow-engine 0.13.0 → 0.14.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/README.md +30 -0
- package/dist/_chunks-cjs/schema.cjs +715 -67
- package/dist/_chunks-cjs/schema.cjs.map +1 -1
- package/dist/_chunks-es/schema.js +716 -68
- package/dist/_chunks-es/schema.js.map +1 -1
- package/dist/define.cjs +66 -25
- package/dist/define.cjs.map +1 -1
- package/dist/define.d.cts +867 -71
- package/dist/define.d.ts +867 -71
- package/dist/define.js +67 -25
- package/dist/define.js.map +1 -1
- package/dist/index.cjs +2126 -2169
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1630 -517
- package/dist/index.d.ts +1630 -517
- package/dist/index.js +2061 -2104
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -16,11 +16,325 @@ function _interopNamespaceCompat(e) {
|
|
|
16
16
|
}), n.default = e, Object.freeze(n);
|
|
17
17
|
}
|
|
18
18
|
var v__namespace = /* @__PURE__ */ _interopNamespaceCompat(v);
|
|
19
|
+
const KNOWN_SCHEMES = /* @__PURE__ */ new Set(["dataset", "canvas", "media-library", "dashboard"]);
|
|
20
|
+
function parseGdr(uri) {
|
|
21
|
+
const colon = uri.indexOf(":");
|
|
22
|
+
if (colon < 0)
|
|
23
|
+
throw new Error(
|
|
24
|
+
`Invalid GDR "${uri}": must be a URI of form "<scheme>:<...id-parts>". Known schemes: dataset, canvas, media-library, dashboard.`
|
|
25
|
+
);
|
|
26
|
+
const scheme = uri.slice(0, colon), rest = uri.slice(colon + 1);
|
|
27
|
+
if (!KNOWN_SCHEMES.has(scheme))
|
|
28
|
+
throw new Error(
|
|
29
|
+
`Invalid GDR "${uri}": unknown scheme "${scheme}". Known: dataset, canvas, media-library, dashboard.`
|
|
30
|
+
);
|
|
31
|
+
const parts = rest.split(":");
|
|
32
|
+
if (parts.some((part) => part.length === 0))
|
|
33
|
+
throw new Error(
|
|
34
|
+
`Invalid GDR "${uri}": id parts must be non-empty (no leading, trailing, or doubled ":").`
|
|
35
|
+
);
|
|
36
|
+
if (scheme === "dataset") {
|
|
37
|
+
if (parts.length !== 3)
|
|
38
|
+
throw new Error(
|
|
39
|
+
`Invalid GDR "${uri}": dataset scheme requires <projectId>:<dataset>:<documentId> (3 parts after scheme); got ${parts.length}.`
|
|
40
|
+
);
|
|
41
|
+
return {
|
|
42
|
+
scheme: "dataset",
|
|
43
|
+
projectId: parts[0],
|
|
44
|
+
dataset: parts[1],
|
|
45
|
+
documentId: parts[2]
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
if (parts.length !== 2)
|
|
49
|
+
throw new Error(
|
|
50
|
+
`Invalid GDR "${uri}": ${scheme} scheme requires <resourceId>:<documentId> (2 parts after scheme); got ${parts.length}.`
|
|
51
|
+
);
|
|
52
|
+
return {
|
|
53
|
+
scheme,
|
|
54
|
+
resourceId: parts[0],
|
|
55
|
+
documentId: parts[1]
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function tryParseGdr(uri) {
|
|
59
|
+
try {
|
|
60
|
+
return parseGdr(uri);
|
|
61
|
+
} catch {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function gdrUri(parts) {
|
|
66
|
+
return parts.scheme === "dataset" ? `dataset:${parts.projectId}:${parts.dataset}:${parts.documentId}` : `${parts.scheme}:${parts.resourceId}:${parts.documentId}`;
|
|
67
|
+
}
|
|
68
|
+
function extractDocumentId(gdrUriString) {
|
|
69
|
+
return parseGdr(gdrUriString).documentId;
|
|
70
|
+
}
|
|
71
|
+
const RESOURCE_ALIAS_NAME_SOURCE = "[a-z0-9][a-z0-9-]*", RESOURCE_ALIAS_NAME_RE = new RegExp(`^${RESOURCE_ALIAS_NAME_SOURCE}$`);
|
|
72
|
+
function validateResourceAliasName(name) {
|
|
73
|
+
if (!RESOURCE_ALIAS_NAME_RE.test(name))
|
|
74
|
+
throw new Error(
|
|
75
|
+
`Invalid resource alias name "${name}": expected lowercase letters, digits, and dashes (no leading dash).`
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
function toPhysicalGdr(id, home) {
|
|
79
|
+
return isGdrUri(id) ? id : gdrFromResource(home, id);
|
|
80
|
+
}
|
|
81
|
+
function refDataset({
|
|
82
|
+
projectId,
|
|
83
|
+
dataset,
|
|
84
|
+
documentId,
|
|
85
|
+
type
|
|
86
|
+
}) {
|
|
87
|
+
return { id: gdrUri({ scheme: "dataset", projectId, dataset, documentId }), type };
|
|
88
|
+
}
|
|
89
|
+
function refCanvas({
|
|
90
|
+
resourceId,
|
|
91
|
+
documentId,
|
|
92
|
+
type
|
|
93
|
+
}) {
|
|
94
|
+
return { id: gdrUri({ scheme: "canvas", resourceId, documentId }), type };
|
|
95
|
+
}
|
|
96
|
+
function refMediaLibrary({
|
|
97
|
+
resourceId,
|
|
98
|
+
documentId,
|
|
99
|
+
type
|
|
100
|
+
}) {
|
|
101
|
+
return { id: gdrUri({ scheme: "media-library", resourceId, documentId }), type };
|
|
102
|
+
}
|
|
103
|
+
function refDashboard({
|
|
104
|
+
resourceId,
|
|
105
|
+
documentId,
|
|
106
|
+
type
|
|
107
|
+
}) {
|
|
108
|
+
return { id: gdrUri({ scheme: "dashboard", resourceId, documentId }), type };
|
|
109
|
+
}
|
|
110
|
+
function datasetResourceParts(id) {
|
|
111
|
+
const dot = id.indexOf(".");
|
|
112
|
+
if (dot <= 0 || dot === id.length - 1)
|
|
113
|
+
throw new Error(`Invalid dataset resource id "${id}": expected "<projectId>.<dataset>".`);
|
|
114
|
+
return { projectId: id.slice(0, dot), dataset: id.slice(dot + 1) };
|
|
115
|
+
}
|
|
116
|
+
function gdrFromResource(res, documentId) {
|
|
117
|
+
if (res.type === "dataset") {
|
|
118
|
+
const { projectId, dataset } = datasetResourceParts(res.id);
|
|
119
|
+
return gdrUri({ scheme: "dataset", projectId, dataset, documentId });
|
|
120
|
+
}
|
|
121
|
+
return gdrUri({ scheme: res.type, resourceId: res.id, documentId });
|
|
122
|
+
}
|
|
123
|
+
function gdrResourcePrefix(res) {
|
|
124
|
+
const full = gdrFromResource(res, "x");
|
|
125
|
+
return full.slice(0, full.length - 1);
|
|
126
|
+
}
|
|
127
|
+
function resourceFromParsed(parsed) {
|
|
128
|
+
return parsed.scheme === "dataset" ? { type: "dataset", id: `${parsed.projectId}.${parsed.dataset}` } : { type: parsed.scheme, id: parsed.resourceId ?? "" };
|
|
129
|
+
}
|
|
130
|
+
function resourceFromGdrUri(uri) {
|
|
131
|
+
const parsed = tryParseGdr(uri);
|
|
132
|
+
return parsed === void 0 ? void 0 : resourceFromParsed(parsed);
|
|
133
|
+
}
|
|
134
|
+
function sameResource(a, b) {
|
|
135
|
+
return a.type === b.type && a.id === b.id;
|
|
136
|
+
}
|
|
137
|
+
function selfGdr(doc) {
|
|
138
|
+
return gdrFromResource(doc.workflowResource, doc._id);
|
|
139
|
+
}
|
|
140
|
+
function definitionDocId({
|
|
141
|
+
tag,
|
|
142
|
+
definition,
|
|
143
|
+
version
|
|
144
|
+
}) {
|
|
145
|
+
return `${tag}.${definition}.v${version}`;
|
|
146
|
+
}
|
|
147
|
+
function isGdrUri(value) {
|
|
148
|
+
return typeof value == "string" && tryParseGdr(value) !== void 0;
|
|
149
|
+
}
|
|
150
|
+
function toBareId(id) {
|
|
151
|
+
return isGdrUri(id) ? extractDocumentId(id) : id;
|
|
152
|
+
}
|
|
153
|
+
function gdrRef({
|
|
154
|
+
res,
|
|
155
|
+
documentId,
|
|
156
|
+
type
|
|
157
|
+
}) {
|
|
158
|
+
return { id: gdrFromResource(res, documentId), type };
|
|
159
|
+
}
|
|
160
|
+
function isGdr(value) {
|
|
161
|
+
return typeof value == "object" && value !== null && typeof value.id == "string" && typeof value.type == "string";
|
|
162
|
+
}
|
|
163
|
+
class WorkflowError extends Error {
|
|
164
|
+
kind;
|
|
165
|
+
// oxlint-disable-next-line max-params -- the platform `Error(message, options)` signature with the discriminant prepended
|
|
166
|
+
constructor(kind, message, options) {
|
|
167
|
+
super(message, options), this.kind = kind;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
class ContractViolationError extends WorkflowError {
|
|
171
|
+
constructor(message) {
|
|
172
|
+
super("contract-violation", message), this.name = "ContractViolationError";
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
class InstanceNotFoundError extends WorkflowError {
|
|
176
|
+
instanceId;
|
|
177
|
+
constructor(args) {
|
|
178
|
+
super(
|
|
179
|
+
"instance-not-found",
|
|
180
|
+
`Workflow instance ${args.instanceId} not found${args.detail ? ` (${args.detail})` : ""}`
|
|
181
|
+
), this.name = "InstanceNotFoundError", this.instanceId = args.instanceId;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
class DefinitionNotFoundError extends WorkflowError {
|
|
185
|
+
definition;
|
|
186
|
+
version;
|
|
187
|
+
constructor(args) {
|
|
188
|
+
super(
|
|
189
|
+
"definition-not-found",
|
|
190
|
+
args.version !== void 0 ? `Workflow definition ${args.definition} v${args.version} not deployed` : `Workflow definition ${args.definition} has no deployed versions`
|
|
191
|
+
), this.name = "DefinitionNotFoundError", this.definition = args.definition, args.version !== void 0 && (this.version = args.version);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
class DefinitionInUseError extends WorkflowError {
|
|
195
|
+
definition;
|
|
196
|
+
blockedBy;
|
|
197
|
+
constructor(args) {
|
|
198
|
+
super("definition-in-use", definitionInUseMessage(args.definition, args.blockedBy)), this.name = "DefinitionInUseError", this.definition = args.definition, this.blockedBy = args.blockedBy;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
function definitionInUseMessage(definition, blockedBy) {
|
|
202
|
+
if (blockedBy.reason === "non-terminal-instances") {
|
|
203
|
+
const head = blockedBy.instanceIds.slice(0, 3).join(", "), preview = blockedBy.instanceIds.length > 3 ? `${head}, \u2026` : head;
|
|
204
|
+
return `Cannot delete ${definition}: ${blockedBy.instanceIds.length} non-terminal instance(s) exist (${preview}). Pass cascade to abort them first \u2014 instances are aborted in place, never deleted.`;
|
|
205
|
+
}
|
|
206
|
+
const names = blockedBy.referrers.map((r) => `${r.definition} v${r.version}`).join(", ");
|
|
207
|
+
return `Cannot delete ${definition}: still spawn-referenced by deployed definition(s) ${names}. Delete or redeploy the referrer(s) first.`;
|
|
208
|
+
}
|
|
209
|
+
class EffectNotFoundError extends WorkflowError {
|
|
210
|
+
instanceId;
|
|
211
|
+
effectKey;
|
|
212
|
+
constructor(args) {
|
|
213
|
+
super(
|
|
214
|
+
"effect-not-found",
|
|
215
|
+
`Pending effect "${args.effectKey}" not found on instance ${args.instanceId}`
|
|
216
|
+
), this.name = "EffectNotFoundError", this.instanceId = args.instanceId, this.effectKey = args.effectKey;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
const TAG_RE = /^[a-z0-9][a-z0-9-]*$/;
|
|
220
|
+
function validateTag(tag) {
|
|
221
|
+
if (!TAG_RE.test(tag))
|
|
222
|
+
throw new ContractViolationError(
|
|
223
|
+
`tag: invalid tag "${tag}" \u2014 must match ${TAG_RE.source} (ASCII lowercase + digits + dashes, no leading dash, no dots)`
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
function tagScopeFilter() {
|
|
227
|
+
return "tag == $tag";
|
|
228
|
+
}
|
|
229
|
+
const NonEmptyString = v__namespace.pipe(v__namespace.string(), v__namespace.nonEmpty("must not be empty"));
|
|
230
|
+
function asPredicate(validate) {
|
|
231
|
+
return (value) => {
|
|
232
|
+
try {
|
|
233
|
+
return validate(value), !0;
|
|
234
|
+
} catch {
|
|
235
|
+
return !1;
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
const isValidTag = asPredicate(validateTag), isValidAliasName = asPredicate(validateResourceAliasName), isValidDatasetId = asPredicate(datasetResourceParts), WorkflowResourceSchema = v__namespace.variant("type", [
|
|
240
|
+
v__namespace.object({
|
|
241
|
+
type: v__namespace.literal("dataset"),
|
|
242
|
+
id: v__namespace.pipe(
|
|
243
|
+
NonEmptyString,
|
|
244
|
+
v__namespace.check(isValidDatasetId, 'invalid dataset resource id \u2014 expected "<projectId>.<dataset>"')
|
|
245
|
+
)
|
|
246
|
+
}),
|
|
247
|
+
v__namespace.object({ type: v__namespace.literal("canvas"), id: NonEmptyString }),
|
|
248
|
+
v__namespace.object({ type: v__namespace.literal("media-library"), id: NonEmptyString }),
|
|
249
|
+
v__namespace.object({ type: v__namespace.literal("dashboard"), id: NonEmptyString })
|
|
250
|
+
]), ResourceBindingSchema = v__namespace.object({
|
|
251
|
+
name: v__namespace.pipe(
|
|
252
|
+
NonEmptyString,
|
|
253
|
+
v__namespace.check(
|
|
254
|
+
isValidAliasName,
|
|
255
|
+
"invalid resource handle name \u2014 lowercase letters, digits and dashes only, no leading dash"
|
|
256
|
+
)
|
|
257
|
+
),
|
|
258
|
+
resource: WorkflowResourceSchema
|
|
259
|
+
}), DefinitionSchema = v__namespace.custom(
|
|
260
|
+
(input) => typeof input == "object" && input !== null && typeof input.name == "string",
|
|
261
|
+
"expected a workflow definition (an object with a string `name`)"
|
|
262
|
+
), DeploymentSchema = v__namespace.object({
|
|
263
|
+
name: NonEmptyString,
|
|
264
|
+
tag: v__namespace.pipe(
|
|
265
|
+
v__namespace.string(),
|
|
266
|
+
v__namespace.nonEmpty(),
|
|
267
|
+
v__namespace.check(
|
|
268
|
+
isValidTag,
|
|
269
|
+
"invalid tag \u2014 lowercase letters, digits and dashes only, no leading dash, no dots"
|
|
270
|
+
)
|
|
271
|
+
),
|
|
272
|
+
workflowResource: WorkflowResourceSchema,
|
|
273
|
+
// Optional — omit for a workflow whose definitions reference no `@handle:`.
|
|
274
|
+
// No valibot default: a default makes the inferred output `resourceAliases`
|
|
275
|
+
// required (poor authoring ergonomics) and diverges input from output,
|
|
276
|
+
// which the shared `parseOrThrow` can't model. resourceAliasesToMap treats
|
|
277
|
+
// an absent map as none.
|
|
278
|
+
//
|
|
279
|
+
// Handle names must be unique: resourceAliasesToMap collapses bindings into a
|
|
280
|
+
// map, so a duplicate would silently overwrite — and the dup is gone before
|
|
281
|
+
// the engine sees it. Caught here, at the only layer that still can.
|
|
282
|
+
resourceAliases: v__namespace.optional(
|
|
283
|
+
v__namespace.pipe(
|
|
284
|
+
v__namespace.array(ResourceBindingSchema),
|
|
285
|
+
v__namespace.check(
|
|
286
|
+
(bindings) => new Set(bindings.map((binding) => binding.name)).size === bindings.length,
|
|
287
|
+
"duplicate resource handle name \u2014 each binding name must be unique within a deployment"
|
|
288
|
+
)
|
|
289
|
+
)
|
|
290
|
+
),
|
|
291
|
+
definitions: v__namespace.pipe(
|
|
292
|
+
v__namespace.array(DefinitionSchema),
|
|
293
|
+
v__namespace.minLength(1, "a deployment needs at least one definition")
|
|
294
|
+
)
|
|
295
|
+
}), WorkflowConfigSchema = v__namespace.object({
|
|
296
|
+
// Tags must be unique: a tag is the environment key the CLI selects a
|
|
297
|
+
// deployment by, so two deployments sharing one make selection ambiguous.
|
|
298
|
+
// Enforced here so the ambiguity is a config error, not a deploy-time
|
|
299
|
+
// surprise — and so consumers can treat a tag as naming one deployment.
|
|
300
|
+
deployments: v__namespace.pipe(
|
|
301
|
+
v__namespace.array(DeploymentSchema),
|
|
302
|
+
v__namespace.minLength(1, "a config needs at least one deployment"),
|
|
303
|
+
v__namespace.check(
|
|
304
|
+
(deployments) => new Set(deployments.map((deployment) => deployment.tag)).size === deployments.length,
|
|
305
|
+
"duplicate deployment tag \u2014 each deployment must use a unique tag"
|
|
306
|
+
)
|
|
307
|
+
)
|
|
308
|
+
});
|
|
309
|
+
function resourceAliasesToMap(resourceAliases) {
|
|
310
|
+
return Object.fromEntries(
|
|
311
|
+
(resourceAliases ?? []).map((binding) => [
|
|
312
|
+
binding.name,
|
|
313
|
+
binding.resource
|
|
314
|
+
])
|
|
315
|
+
);
|
|
316
|
+
}
|
|
19
317
|
function andConditions(parts) {
|
|
20
318
|
const present = parts.filter((p) => p !== void 0);
|
|
21
319
|
if (present.length !== 0)
|
|
22
320
|
return present.length === 1 ? present[0] : present.map((p) => `(${p})`).join(" && ");
|
|
23
321
|
}
|
|
322
|
+
const FIELD_READ = /^\$fields\.(\w+)(?:\.(.+))?$/, EFFECTS_READ = /^\$effects\['([^']+)'\](?:\.(.+))?$/;
|
|
323
|
+
function isGuardReadExpr(expr) {
|
|
324
|
+
return expr === "$self" || expr === "$now" || FIELD_READ.test(expr) || EFFECTS_READ.test(expr);
|
|
325
|
+
}
|
|
326
|
+
function printGuardRead(read) {
|
|
327
|
+
switch (read.type) {
|
|
328
|
+
case "self":
|
|
329
|
+
return "$self";
|
|
330
|
+
case "now":
|
|
331
|
+
return "$now";
|
|
332
|
+
case "fieldRead":
|
|
333
|
+
return read.path === void 0 ? `$fields.${read.field}` : `$fields.${read.field}.${read.path}`;
|
|
334
|
+
case "effectsRead":
|
|
335
|
+
return read.path === void 0 ? `$effects['${read.effect}']` : `$effects['${read.effect}'].${read.path}`;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
24
338
|
const UNIVERSAL_ROLE_ALIAS_KEY = "$all";
|
|
25
339
|
function normalizeRoleAliases(aliases) {
|
|
26
340
|
if (aliases === void 0 || !("*" in aliases)) return aliases;
|
|
@@ -46,6 +360,250 @@ function actorFulfillsRole({
|
|
|
46
360
|
const accepted = new Set(expandRequiredRoles([required], aliases));
|
|
47
361
|
return actorRoles.some((role) => accepted.has(role));
|
|
48
362
|
}
|
|
363
|
+
const CONDITION_VARS = [
|
|
364
|
+
{
|
|
365
|
+
name: "self",
|
|
366
|
+
binding: "always",
|
|
367
|
+
description: "GDR URI of the instance document itself \u2014 `*[_id == $self][0]` reads the instance in the snapshot."
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
name: "fields",
|
|
371
|
+
binding: "always",
|
|
372
|
+
description: "Declared field entries rendered by name (`$fields.<name>` is the value, no envelope). Stage/activity scopes overlay lexically; `doc.ref` values render the hydrated document when the snapshot holds it."
|
|
373
|
+
},
|
|
374
|
+
{
|
|
375
|
+
name: "parent",
|
|
376
|
+
binding: "always",
|
|
377
|
+
description: "The parent instance's GDR URI, `null` on a root instance."
|
|
378
|
+
},
|
|
379
|
+
{
|
|
380
|
+
name: "ancestors",
|
|
381
|
+
binding: "always",
|
|
382
|
+
description: "GDR URIs of the ancestor chain, root first."
|
|
383
|
+
},
|
|
384
|
+
{ name: "stage", binding: "always", description: "The current stage's name." },
|
|
385
|
+
{
|
|
386
|
+
name: "now",
|
|
387
|
+
binding: "always",
|
|
388
|
+
description: "The ISO clock reading shared by every condition in one evaluation pass, so they agree on the time."
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
name: "effects",
|
|
392
|
+
binding: "always",
|
|
393
|
+
description: "The `effectsContext` map: stable handler params seeded at start plus completed effects' outputs namespaced by effect name (`$effects['<effect>'].<output>`). Handler fuel \u2014 transition filters should read instance/lake state (or $effectStatus) instead."
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
name: "effectStatus",
|
|
397
|
+
binding: "always",
|
|
398
|
+
description: "Effect name \u2192 `'done'` | `'failed'` of the latest completed run queued during the current stage entry; absent until the effect drains for this entry. Re-entry-safe: runs queued under a prior entry never count, so `$effectStatus['<effect>'] == 'done'` (or `defined($effectStatus['<effect>'])` for settled-either-way) waits for the fresh run."
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
name: "activities",
|
|
402
|
+
binding: "always",
|
|
403
|
+
description: "The current stage's activity rows, statuses included."
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
name: "allActivitiesDone",
|
|
407
|
+
binding: "always",
|
|
408
|
+
description: "Every current-stage activity is `done` or `skipped` \u2014 the default transition gate."
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
name: "anyActivityFailed",
|
|
412
|
+
binding: "always",
|
|
413
|
+
description: "Some current-stage activity is `failed`."
|
|
414
|
+
},
|
|
415
|
+
{
|
|
416
|
+
name: "actor",
|
|
417
|
+
binding: "caller",
|
|
418
|
+
description: "The acting identity (id + roles); `undefined` when no caller rides the evaluation."
|
|
419
|
+
},
|
|
420
|
+
{
|
|
421
|
+
name: "assigned",
|
|
422
|
+
binding: "caller",
|
|
423
|
+
description: "Whether the caller matches the activity's assignees-kind field entry (by user id, or by a role under the definition's `roleAliases`); `false` outside an activity context."
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
name: "can",
|
|
427
|
+
binding: "caller",
|
|
428
|
+
description: "Advisory per-permission booleans computed from the caller's grants; `undefined` without grants. Bound only while an action fires, so it may appear in action filters only (deploy-enforced)."
|
|
429
|
+
},
|
|
430
|
+
{
|
|
431
|
+
name: "row",
|
|
432
|
+
binding: "spawn",
|
|
433
|
+
description: "One `subworkflows.forEach` result row, bound while its `with` map evaluates \u2014 and the row under test while a where-op `where` evaluates (per row)."
|
|
434
|
+
},
|
|
435
|
+
{
|
|
436
|
+
name: "params",
|
|
437
|
+
binding: "caller",
|
|
438
|
+
description: "The firing action's args \u2014 bound while its effect bindings and where-op `where` conditions evaluate."
|
|
439
|
+
},
|
|
440
|
+
{
|
|
441
|
+
name: "subworkflows",
|
|
442
|
+
binding: "spawn",
|
|
443
|
+
description: "The spawned child rows (status included) of the spawning activity, bound for its `completeWhen`/`failWhen` \u2014 the implicit completion gate reads it."
|
|
444
|
+
}
|
|
445
|
+
], RESERVED_CONDITION_VARS = CONDITION_VARS.map((v2) => v2.name), FILTER_SCOPE_VARS = CONDITION_VARS.filter(
|
|
446
|
+
(v2) => v2.binding === "always"
|
|
447
|
+
).map((v2) => v2.name), GUARD_PREDICATE_VARS = [
|
|
448
|
+
{
|
|
449
|
+
name: "guard",
|
|
450
|
+
description: "The guard document itself (its `metadata` carries deploy-time resolved values)."
|
|
451
|
+
},
|
|
452
|
+
{
|
|
453
|
+
name: "mutation",
|
|
454
|
+
description: "The attempted mutation \u2014 `mutation.action` is the write kind being gated."
|
|
455
|
+
}
|
|
456
|
+
], ACTOR_KINDS = ["person", "agent", "system"];
|
|
457
|
+
class FieldValueShapeError extends WorkflowError {
|
|
458
|
+
entryType;
|
|
459
|
+
entryName;
|
|
460
|
+
issues;
|
|
461
|
+
constructor(args) {
|
|
462
|
+
const issueText = args.issues.join("; ");
|
|
463
|
+
super(
|
|
464
|
+
"field-value-shape",
|
|
465
|
+
`Field entry ${args.mode} shape invalid for "${args.entryName}" (${args.entryType}): ${issueText}`
|
|
466
|
+
), this.name = "FieldValueShapeError", this.entryType = args.entryType, this.entryName = args.entryName, this.issues = args.issues;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
const GdrShape = v__namespace.looseObject({
|
|
470
|
+
id: v__namespace.pipe(
|
|
471
|
+
v__namespace.string(),
|
|
472
|
+
v__namespace.check((s) => isGdrUri(s), "must be a GDR URI")
|
|
473
|
+
),
|
|
474
|
+
type: v__namespace.pipe(v__namespace.string(), v__namespace.minLength(1))
|
|
475
|
+
}), ReleaseRefShape = v__namespace.looseObject({
|
|
476
|
+
id: v__namespace.pipe(
|
|
477
|
+
v__namespace.string(),
|
|
478
|
+
v__namespace.check((s) => isGdrUri(s), "must be a GDR URI")
|
|
479
|
+
),
|
|
480
|
+
type: v__namespace.literal("system.release"),
|
|
481
|
+
releaseName: v__namespace.pipe(v__namespace.string(), v__namespace.minLength(1))
|
|
482
|
+
}), ActorShape = v__namespace.looseObject({
|
|
483
|
+
kind: v__namespace.picklist(ACTOR_KINDS),
|
|
484
|
+
id: v__namespace.pipe(v__namespace.string(), v__namespace.minLength(1)),
|
|
485
|
+
roles: v__namespace.optional(v__namespace.array(v__namespace.string())),
|
|
486
|
+
onBehalfOf: v__namespace.optional(v__namespace.string())
|
|
487
|
+
}), AssigneeShape = v__namespace.union([
|
|
488
|
+
v__namespace.looseObject({ type: v__namespace.literal("user"), id: v__namespace.pipe(v__namespace.string(), v__namespace.minLength(1)) }),
|
|
489
|
+
v__namespace.looseObject({ type: v__namespace.literal("role"), role: v__namespace.pipe(v__namespace.string(), v__namespace.minLength(1)) })
|
|
490
|
+
]), NullableString = v__namespace.union([v__namespace.null(), v__namespace.string()]), NullableNumber = v__namespace.union([v__namespace.null(), v__namespace.number()]), NullableBoolean = v__namespace.union([v__namespace.null(), v__namespace.boolean()]), NullableDateTime = v__namespace.union([
|
|
491
|
+
v__namespace.null(),
|
|
492
|
+
v__namespace.pipe(
|
|
493
|
+
v__namespace.string(),
|
|
494
|
+
v__namespace.check((s) => !Number.isNaN(Date.parse(s)), "must be an ISO-8601 datetime string")
|
|
495
|
+
)
|
|
496
|
+
]), NullableDate = v__namespace.union([
|
|
497
|
+
v__namespace.null(),
|
|
498
|
+
v__namespace.pipe(v__namespace.string(), v__namespace.regex(/^\d{4}-\d{2}-\d{2}$/, "must be a `YYYY-MM-DD` date"))
|
|
499
|
+
]), NullableUrl = NullableString, valueSchemas = {
|
|
500
|
+
"doc.ref": v__namespace.union([v__namespace.null(), GdrShape]),
|
|
501
|
+
"doc.refs": v__namespace.array(GdrShape),
|
|
502
|
+
"release.ref": v__namespace.union([v__namespace.null(), ReleaseRefShape]),
|
|
503
|
+
query: v__namespace.any(),
|
|
504
|
+
string: NullableString,
|
|
505
|
+
text: NullableString,
|
|
506
|
+
number: NullableNumber,
|
|
507
|
+
boolean: NullableBoolean,
|
|
508
|
+
date: NullableDate,
|
|
509
|
+
datetime: NullableDateTime,
|
|
510
|
+
url: NullableUrl,
|
|
511
|
+
actor: v__namespace.union([v__namespace.null(), ActorShape]),
|
|
512
|
+
assignee: v__namespace.union([v__namespace.null(), AssigneeShape]),
|
|
513
|
+
assignees: v__namespace.array(AssigneeShape)
|
|
514
|
+
};
|
|
515
|
+
function shapeValueSchema(shape, leaf) {
|
|
516
|
+
return shape.type === "object" ? objectSchema(shape.fields ?? [], leaf) : shape.type === "array" ? v__namespace.array(objectSchema(shape.of ?? [], leaf)) : leaf[shape.type] ?? v__namespace.any();
|
|
517
|
+
}
|
|
518
|
+
function objectSchema(fields, leaf) {
|
|
519
|
+
const entries = /* @__PURE__ */ Object.create(null);
|
|
520
|
+
for (const f of fields) entries[f.name] = v__namespace.optional(shapeValueSchema(f, leaf));
|
|
521
|
+
return v__namespace.looseObject(entries);
|
|
522
|
+
}
|
|
523
|
+
function wholeValueSchema(args) {
|
|
524
|
+
const { entryType, shape, leaf } = args;
|
|
525
|
+
return entryType === "object" ? v__namespace.union([v__namespace.null(), objectSchema(shape.fields ?? [], leaf)]) : entryType === "array" ? v__namespace.array(objectSchema(shape.of ?? [], leaf)) : leaf[entryType];
|
|
526
|
+
}
|
|
527
|
+
function appendItemSchema(entryType, shape) {
|
|
528
|
+
if (entryType === "array") return objectSchema(shape.of ?? [], valueSchemas);
|
|
529
|
+
if (entryType === "doc.refs") return GdrShape;
|
|
530
|
+
if (entryType === "assignees") return AssigneeShape;
|
|
531
|
+
}
|
|
532
|
+
function checkFieldValue(args) {
|
|
533
|
+
return checkValueAgainst(args, valueSchemas);
|
|
534
|
+
}
|
|
535
|
+
function checkValueAgainst(args, leaf) {
|
|
536
|
+
const schema = wholeValueSchema({ entryType: args.entryType, shape: args, leaf });
|
|
537
|
+
if (schema === void 0) return [`unknown field entry type ${args.entryType}`];
|
|
538
|
+
const result = v__namespace.safeParse(schema, args.value);
|
|
539
|
+
return result.success ? void 0 : formatIssues(result.issues);
|
|
540
|
+
}
|
|
541
|
+
function validateFieldValue(args) {
|
|
542
|
+
const issues = checkFieldValue(args);
|
|
543
|
+
if (issues !== void 0)
|
|
544
|
+
throw new FieldValueShapeError({
|
|
545
|
+
entryType: args.entryType,
|
|
546
|
+
entryName: args.entryName,
|
|
547
|
+
issues,
|
|
548
|
+
mode: "value"
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
const BARE_ID_SOURCE = "[A-Za-z0-9_][A-Za-z0-9._-]*", BARE_ID_RE = new RegExp(`^${BARE_ID_SOURCE}$`), ALIAS_REF_RE = new RegExp(`^@${RESOURCE_ALIAS_NAME_SOURCE}:${BARE_ID_SOURCE}$`);
|
|
552
|
+
function isAuthoringRefId(id) {
|
|
553
|
+
return id.startsWith("@") ? ALIAS_REF_RE.test(id) : id.includes(":") ? isGdrUri(id) : BARE_ID_RE.test(id);
|
|
554
|
+
}
|
|
555
|
+
function isBareSeedId(id) {
|
|
556
|
+
return BARE_ID_RE.test(id);
|
|
557
|
+
}
|
|
558
|
+
const AuthoringRefId = v__namespace.pipe(
|
|
559
|
+
v__namespace.string(),
|
|
560
|
+
v__namespace.check(
|
|
561
|
+
isAuthoringRefId,
|
|
562
|
+
"must be a bare document id, a GDR URI, or a portable `@<alias>:<id>` reference"
|
|
563
|
+
)
|
|
564
|
+
), AuthoringGdrShape = v__namespace.looseObject({
|
|
565
|
+
id: AuthoringRefId,
|
|
566
|
+
type: v__namespace.pipe(v__namespace.string(), v__namespace.minLength(1))
|
|
567
|
+
}), seedValueSchemas = {
|
|
568
|
+
...valueSchemas,
|
|
569
|
+
"doc.ref": v__namespace.union([v__namespace.null(), AuthoringGdrShape]),
|
|
570
|
+
"doc.refs": v__namespace.array(AuthoringGdrShape),
|
|
571
|
+
"release.ref": v__namespace.union([
|
|
572
|
+
v__namespace.null(),
|
|
573
|
+
v__namespace.looseObject({
|
|
574
|
+
id: AuthoringRefId,
|
|
575
|
+
type: v__namespace.literal("system.release"),
|
|
576
|
+
releaseName: v__namespace.pipe(v__namespace.string(), v__namespace.minLength(1))
|
|
577
|
+
})
|
|
578
|
+
])
|
|
579
|
+
};
|
|
580
|
+
function checkLiteralSeed(args) {
|
|
581
|
+
return args.value === null ? ["a literal seed cannot be null \u2014 omit `initialValue` to start the field empty"] : checkValueAgainst(args, seedValueSchemas);
|
|
582
|
+
}
|
|
583
|
+
function validateFieldAppendItem(args) {
|
|
584
|
+
const schema = appendItemSchema(args.entryType, args);
|
|
585
|
+
if (schema === void 0)
|
|
586
|
+
throw new FieldValueShapeError({
|
|
587
|
+
entryType: args.entryType,
|
|
588
|
+
entryName: args.entryName,
|
|
589
|
+
issues: [`field entry type ${args.entryType} does not support append`],
|
|
590
|
+
mode: "item"
|
|
591
|
+
});
|
|
592
|
+
const result = v__namespace.safeParse(schema, args.item);
|
|
593
|
+
if (!result.success)
|
|
594
|
+
throw new FieldValueShapeError({
|
|
595
|
+
entryType: args.entryType,
|
|
596
|
+
entryName: args.entryName,
|
|
597
|
+
issues: formatIssues(result.issues),
|
|
598
|
+
mode: "item"
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
function formatIssues(issues) {
|
|
602
|
+
return issues.map((i) => {
|
|
603
|
+
const keys = i.path?.map((p) => p.key) ?? [];
|
|
604
|
+
return `${keys.length > 0 ? `at ${keys.join(".")}: ` : ""}${i.message}`;
|
|
605
|
+
});
|
|
606
|
+
}
|
|
49
607
|
const ACTIVITY_STATUSES = ["pending", "active", "done", "skipped", "failed"], TERMINAL_ACTIVITY_STATUSES = ["done", "skipped", "failed"];
|
|
50
608
|
function isTerminalActivityStatus(status) {
|
|
51
609
|
return TERMINAL_ACTIVITY_STATUSES.includes(status);
|
|
@@ -123,17 +681,7 @@ function manualTargetSchema(ref) {
|
|
|
123
681
|
v__namespace.strictObject({ type: v__namespace.literal("field"), field: ref })
|
|
124
682
|
]);
|
|
125
683
|
}
|
|
126
|
-
const StoredManualTargetSchema = manualTargetSchema(StoredFieldRefSchema), AuthoringManualTargetSchema = manualTargetSchema(v__namespace.union([NonEmpty, AuthoringFieldRefSchema])),
|
|
127
|
-
() => v__namespace.union([
|
|
128
|
-
v__namespace.strictObject({
|
|
129
|
-
type: v__namespace.literal("field"),
|
|
130
|
-
field: NonEmpty,
|
|
131
|
-
equals: ValueExprSchema
|
|
132
|
-
}),
|
|
133
|
-
v__namespace.strictObject({ type: v__namespace.literal("all"), of: v__namespace.array(OpPredicateSchema) }),
|
|
134
|
-
v__namespace.strictObject({ type: v__namespace.literal("any"), of: v__namespace.array(OpPredicateSchema) })
|
|
135
|
-
])
|
|
136
|
-
);
|
|
684
|
+
const StoredManualTargetSchema = manualTargetSchema(StoredFieldRefSchema), AuthoringManualTargetSchema = manualTargetSchema(v__namespace.union([NonEmpty, AuthoringFieldRefSchema])), ConditionSchema = NonEmpty;
|
|
137
685
|
function opSchemas(targetSchema) {
|
|
138
686
|
return [
|
|
139
687
|
v__namespace.strictObject({
|
|
@@ -153,13 +701,13 @@ function opSchemas(targetSchema) {
|
|
|
153
701
|
v__namespace.strictObject({
|
|
154
702
|
type: v__namespace.literal("field.updateWhere"),
|
|
155
703
|
target: targetSchema,
|
|
156
|
-
where:
|
|
704
|
+
where: ConditionSchema,
|
|
157
705
|
value: ValueExprSchema
|
|
158
706
|
}),
|
|
159
707
|
v__namespace.strictObject({
|
|
160
708
|
type: v__namespace.literal("field.removeWhere"),
|
|
161
709
|
target: targetSchema,
|
|
162
|
-
where:
|
|
710
|
+
where: ConditionSchema
|
|
163
711
|
})
|
|
164
712
|
];
|
|
165
713
|
}
|
|
@@ -259,7 +807,7 @@ const FieldShapeSchema = v__namespace.lazy(
|
|
|
259
807
|
of: v__namespace.optional(v__namespace.array(FieldShapeSchema))
|
|
260
808
|
})
|
|
261
809
|
), StoredEditableSchema = v__namespace.union([v__namespace.literal(!0), NonEmpty]), AuthoringEditableSchema = v__namespace.union([v__namespace.literal(!0), v__namespace.array(NonEmpty), NonEmpty]);
|
|
262
|
-
function
|
|
810
|
+
function fieldBase(editable) {
|
|
263
811
|
return {
|
|
264
812
|
name: FieldEntryName,
|
|
265
813
|
title: v__namespace.optional(v__namespace.string()),
|
|
@@ -275,27 +823,45 @@ function slotFields(editable) {
|
|
|
275
823
|
/**
|
|
276
824
|
* How this field SEEDS its value at materialisation — see
|
|
277
825
|
* {@link FieldSourceSchema}. Optional: an absent `initialValue` is working
|
|
278
|
-
* memory (the
|
|
826
|
+
* memory (the field starts empty and an op fills it later), the common case.
|
|
279
827
|
*/
|
|
280
828
|
initialValue: v__namespace.optional(FieldSourceSchema),
|
|
281
|
-
/** Who-may-edit this
|
|
829
|
+
/** Who-may-edit this field via the edit seam — see {@link StoredEditableSchema}. */
|
|
282
830
|
editable: v__namespace.optional(editable)
|
|
283
831
|
};
|
|
284
832
|
}
|
|
285
833
|
function fieldEntryFields(editable) {
|
|
286
834
|
return {
|
|
287
835
|
type: FieldKindSchema,
|
|
288
|
-
...
|
|
836
|
+
...fieldBase(editable),
|
|
289
837
|
/** Sub-field shapes for an `object` kind (see {@link compositeShapeOk}). */
|
|
290
838
|
fields: v__namespace.optional(v__namespace.array(FieldShapeSchema)),
|
|
291
839
|
/** Item sub-field shapes for an `array` kind (see {@link compositeShapeOk}). */
|
|
292
840
|
of: v__namespace.optional(v__namespace.array(FieldShapeSchema))
|
|
293
841
|
};
|
|
294
842
|
}
|
|
843
|
+
function literalSeedIssues(entry) {
|
|
844
|
+
if (entry.initialValue?.type === "literal")
|
|
845
|
+
return checkLiteralSeed({
|
|
846
|
+
entryType: entry.type,
|
|
847
|
+
value: entry.initialValue.value,
|
|
848
|
+
fields: entry.fields,
|
|
849
|
+
of: entry.of
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
function literalSeedCheck() {
|
|
853
|
+
return v__namespace.check(
|
|
854
|
+
(entry) => literalSeedIssues(entry) === void 0,
|
|
855
|
+
(issue) => `initialValue literal does not fit the declared kind: ${(literalSeedIssues(issue.input) ?? []).join("; ")}`
|
|
856
|
+
);
|
|
857
|
+
}
|
|
295
858
|
const FieldEntrySchema = pinned()(
|
|
296
|
-
compositeChecked(fieldEntryFields(StoredEditableSchema))
|
|
859
|
+
v__namespace.pipe(compositeChecked(fieldEntryFields(StoredEditableSchema)), literalSeedCheck())
|
|
297
860
|
), RawAuthoringFieldEntrySchema = pinned()(
|
|
298
|
-
|
|
861
|
+
v__namespace.pipe(
|
|
862
|
+
compositeChecked(fieldEntryFields(AuthoringEditableSchema)),
|
|
863
|
+
literalSeedCheck()
|
|
864
|
+
)
|
|
299
865
|
), ClaimFieldSchema = pinned()(
|
|
300
866
|
v__namespace.strictObject({
|
|
301
867
|
type: v__namespace.literal("claim"),
|
|
@@ -307,12 +873,12 @@ const FieldEntrySchema = pinned()(
|
|
|
307
873
|
function listSugarFields(type) {
|
|
308
874
|
return {
|
|
309
875
|
type: v__namespace.literal(type),
|
|
310
|
-
...
|
|
876
|
+
...fieldBase(AuthoringEditableSchema)
|
|
311
877
|
};
|
|
312
878
|
}
|
|
313
879
|
const TodoListFieldSchema = pinned()(v__namespace.strictObject(listSugarFields("todoList"))), NotesFieldSchema = pinned()(v__namespace.strictObject(listSugarFields("notes"))), AuthoringFieldEntrySchema = pinned()(
|
|
314
880
|
v__namespace.union([RawAuthoringFieldEntrySchema, ClaimFieldSchema, TodoListFieldSchema, NotesFieldSchema])
|
|
315
|
-
),
|
|
881
|
+
), EffectSchema = v__namespace.strictObject({
|
|
316
882
|
name: NonEmpty,
|
|
317
883
|
title: v__namespace.optional(v__namespace.string()),
|
|
318
884
|
description: v__namespace.optional(v__namespace.string()),
|
|
@@ -344,8 +910,8 @@ function actionFields(op) {
|
|
|
344
910
|
description: v__namespace.optional(v__namespace.string()),
|
|
345
911
|
/**
|
|
346
912
|
* The one gate mechanism: a condition over the rendered scope. Hard
|
|
347
|
-
* enforcement lives outside the engine
|
|
348
|
-
*
|
|
913
|
+
* enforcement lives outside the engine; every engine-evaluated gate is
|
|
914
|
+
* authoring/UX.
|
|
349
915
|
*/
|
|
350
916
|
filter: v__namespace.optional(ConditionSchema),
|
|
351
917
|
params: v__namespace.optional(v__namespace.array(ActionParamSchema)),
|
|
@@ -412,8 +978,8 @@ function activityFields({ field, action, op, target }) {
|
|
|
412
978
|
* (visibility). An unmet requirement keeps the activity visible but disables
|
|
413
979
|
* its actions with a `requirements-unmet` verdict naming the unmet keys;
|
|
414
980
|
* all must hold (any-of lives inside one condition). Advisory like every
|
|
415
|
-
* engine gate
|
|
416
|
-
*
|
|
981
|
+
* engine gate. Distinct from ACL (authorization) and guards
|
|
982
|
+
* (content-write locks).
|
|
417
983
|
*/
|
|
418
984
|
requirements: v__namespace.optional(v__namespace.record(NonEmpty, ConditionSchema)),
|
|
419
985
|
/**
|
|
@@ -474,43 +1040,75 @@ const StoredTransitionSchema = pinned()(
|
|
|
474
1040
|
AuditOpSchema
|
|
475
1041
|
]), AuthoringTransitionSchema = pinned()(
|
|
476
1042
|
v__namespace.strictObject(transitionFields(AuthoringTransitionOpSchema, v__namespace.optional(ConditionSchema)))
|
|
477
|
-
), GuardActionSchema = picklist(MUTATION_GUARD_ACTIONS),
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
/** Glob id patterns (bare, resource-local). */
|
|
483
|
-
idPatterns: v__namespace.optional(v__namespace.array(NonEmpty)),
|
|
484
|
-
actions: v__namespace.pipe(
|
|
485
|
-
v__namespace.array(GuardActionSchema),
|
|
486
|
-
v__namespace.minLength(1, "a guard must match at least one action")
|
|
1043
|
+
), GuardActionSchema = picklist(MUTATION_GUARD_ACTIONS), GuardReadPath = v__namespace.pipe(
|
|
1044
|
+
NonEmpty,
|
|
1045
|
+
v__namespace.check(
|
|
1046
|
+
(path) => !/[\r\n\u2028\u2029]/.test(path),
|
|
1047
|
+
"a guard read path cannot contain a line break"
|
|
487
1048
|
)
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
1049
|
+
), GuardReadSchema = v__namespace.variant("type", [
|
|
1050
|
+
v__namespace.strictObject({ type: v__namespace.literal("self") }),
|
|
1051
|
+
v__namespace.strictObject({ type: v__namespace.literal("now") }),
|
|
1052
|
+
v__namespace.strictObject({
|
|
1053
|
+
type: v__namespace.literal("fieldRead"),
|
|
1054
|
+
field: FieldEntryName,
|
|
1055
|
+
path: v__namespace.optional(GuardReadPath)
|
|
1056
|
+
}),
|
|
1057
|
+
v__namespace.strictObject({
|
|
1058
|
+
type: v__namespace.literal("effectsRead"),
|
|
1059
|
+
// The stored spelling bracket-quotes the name (`$effects['<name>']`), so a
|
|
1060
|
+
// quote inside it would corrupt the printed read.
|
|
1061
|
+
effect: v__namespace.pipe(
|
|
1062
|
+
NonEmpty,
|
|
1063
|
+
v__namespace.check((name) => !name.includes("'"), "an effect name cannot contain `'`")
|
|
1064
|
+
),
|
|
1065
|
+
path: v__namespace.optional(GuardReadPath)
|
|
1066
|
+
})
|
|
1067
|
+
]);
|
|
1068
|
+
function guardMatchFields(read) {
|
|
1069
|
+
return {
|
|
1070
|
+
/** Subject `_type`(s); empty matches any type. */
|
|
1071
|
+
types: v__namespace.optional(v__namespace.array(NonEmpty)),
|
|
1072
|
+
/** Target docs as field reads (or the instance itself), resolved at deploy to bare ids + the resource. */
|
|
1073
|
+
idRefs: v__namespace.optional(v__namespace.array(read)),
|
|
1074
|
+
/** Glob id patterns (bare, resource-local). */
|
|
1075
|
+
idPatterns: v__namespace.optional(v__namespace.array(NonEmpty)),
|
|
1076
|
+
actions: v__namespace.pipe(
|
|
1077
|
+
v__namespace.array(GuardActionSchema),
|
|
1078
|
+
v__namespace.minLength(1, "a guard must match at least one action")
|
|
1079
|
+
)
|
|
1080
|
+
};
|
|
1081
|
+
}
|
|
1082
|
+
function guardFields(read) {
|
|
1083
|
+
return {
|
|
1084
|
+
name: NonEmpty,
|
|
1085
|
+
title: v__namespace.optional(v__namespace.string()),
|
|
1086
|
+
description: v__namespace.optional(v__namespace.string()),
|
|
1087
|
+
match: v__namespace.strictObject(guardMatchFields(read)),
|
|
1088
|
+
/**
|
|
1089
|
+
* Lake GROQ predicate — a distinct eval context reading
|
|
1090
|
+
* `document.before`/`document.after`, `mutation`, `guard`, and
|
|
1091
|
+
* `identity()`. Bare ids/fields only. Omitted or empty means
|
|
1092
|
+
* UNCONDITIONAL DENY.
|
|
1093
|
+
*/
|
|
1094
|
+
predicate: v__namespace.optional(v__namespace.string()),
|
|
1095
|
+
/**
|
|
1096
|
+
* Projected workflow fields the predicate reads as `guard.metadata.*` —
|
|
1097
|
+
* the only bridge from the lake eval context (which cannot see `$fields`)
|
|
1098
|
+
* to workflow fields. Each value is a deploy-time read — a typed
|
|
1099
|
+
* {@link GuardRead} when authoring, the printed string spelling once
|
|
1100
|
+
* stored — resolved into a bare value at deploy and re-synced by the
|
|
1101
|
+
* post-field-op guard refresh.
|
|
1102
|
+
*/
|
|
1103
|
+
metadata: v__namespace.optional(v__namespace.record(NonEmpty, read))
|
|
1104
|
+
};
|
|
1105
|
+
}
|
|
1106
|
+
const GuardSchema = v__namespace.strictObject(guardFields(NonEmpty)), AuthoringGuardSchema = v__namespace.strictObject(guardFields(GuardReadSchema));
|
|
510
1107
|
function stageFields({
|
|
511
1108
|
field,
|
|
512
1109
|
activity,
|
|
513
1110
|
transition,
|
|
1111
|
+
guard,
|
|
514
1112
|
editable
|
|
515
1113
|
}) {
|
|
516
1114
|
return {
|
|
@@ -521,16 +1119,16 @@ function stageFields({
|
|
|
521
1119
|
transitions: v__namespace.optional(v__namespace.array(transition)),
|
|
522
1120
|
/**
|
|
523
1121
|
* Lake mutation guards active while this stage holds. Each compiles to a
|
|
524
|
-
*
|
|
1122
|
+
* persisted guard document deployed on stage entry and retracted on exit.
|
|
525
1123
|
*/
|
|
526
|
-
guards: v__namespace.optional(v__namespace.array(
|
|
1124
|
+
guards: v__namespace.optional(v__namespace.array(guard)),
|
|
527
1125
|
/** Stage-scoped field entries. Resolved at stage entry. */
|
|
528
1126
|
fields: v__namespace.optional(v__namespace.array(field)),
|
|
529
1127
|
/**
|
|
530
1128
|
* Tighten-only editability overrides for the time this stage holds, keyed
|
|
531
|
-
* by an in-scope
|
|
1129
|
+
* by an in-scope field name. The field's own `editable` is the ceiling; a
|
|
532
1130
|
* stage value is ANDed with it at runtime, so an override can only NARROW
|
|
533
|
-
* (never open a
|
|
1131
|
+
* (never open a field the baseline left closed). An unlisted field inherits
|
|
534
1132
|
* its baseline.
|
|
535
1133
|
*/
|
|
536
1134
|
editable: v__namespace.optional(v__namespace.record(FieldEntryName, editable))
|
|
@@ -542,6 +1140,7 @@ const StoredStageSchema = pinned()(
|
|
|
542
1140
|
field: FieldEntrySchema,
|
|
543
1141
|
activity: StoredActivitySchema,
|
|
544
1142
|
transition: StoredTransitionSchema,
|
|
1143
|
+
guard: GuardSchema,
|
|
545
1144
|
editable: StoredEditableSchema
|
|
546
1145
|
})
|
|
547
1146
|
)
|
|
@@ -551,28 +1150,29 @@ const StoredStageSchema = pinned()(
|
|
|
551
1150
|
field: AuthoringFieldEntrySchema,
|
|
552
1151
|
activity: AuthoringActivitySchema,
|
|
553
1152
|
transition: AuthoringTransitionSchema,
|
|
1153
|
+
guard: AuthoringGuardSchema,
|
|
554
1154
|
editable: AuthoringEditableSchema
|
|
555
1155
|
})
|
|
556
1156
|
)
|
|
557
1157
|
), RoleAliasesSchema = v__namespace.record(
|
|
558
1158
|
NonEmpty,
|
|
559
1159
|
v__namespace.pipe(v__namespace.array(NonEmpty), v__namespace.minLength(1, "a role alias must list at least one fulfilling role"))
|
|
560
|
-
),
|
|
1160
|
+
), WORKFLOW_LIFECYCLES = ["standalone", "child"];
|
|
561
1161
|
function workflowFields(field, stage) {
|
|
562
1162
|
return {
|
|
563
1163
|
name: NonEmpty,
|
|
564
1164
|
title: NonEmpty,
|
|
565
1165
|
description: v__namespace.optional(v__namespace.string()),
|
|
566
1166
|
/**
|
|
567
|
-
*
|
|
1167
|
+
* How instances of this workflow come to exist. `'child'` marks a
|
|
568
1168
|
* spawn-only definition — instantiated by a parent via `activity.subworkflows`,
|
|
569
|
-
* never started cold from a picker. Omitted ⇒ `'
|
|
1169
|
+
* never started cold from a picker. Omitted ⇒ `'standalone'` (startable).
|
|
570
1170
|
* Advisory: consumers filter their start pickers on it (see
|
|
571
1171
|
* {@link isStartableDefinition}); the engine does NOT refuse a
|
|
572
1172
|
* `startInstance` on a `'child'` def — load-bearing `required` field is the
|
|
573
1173
|
* runtime backstop.
|
|
574
1174
|
*/
|
|
575
|
-
|
|
1175
|
+
lifecycle: v__namespace.optional(picklist(WORKFLOW_LIFECYCLES)),
|
|
576
1176
|
/** Reference field: named for the target, holds the stage's `name`. */
|
|
577
1177
|
initialStage: NonEmpty,
|
|
578
1178
|
/** Workflow-scope field entries. Persist for the instance lifetime. */
|
|
@@ -595,7 +1195,7 @@ const AuthoringWorkflowSchema = pinned()(
|
|
|
595
1195
|
v__namespace.strictObject(workflowFields(AuthoringFieldEntrySchema, AuthoringStageSchema))
|
|
596
1196
|
), WORKFLOW_DEFINITION_TYPE = "sanity.workflow.definition";
|
|
597
1197
|
function isStartableDefinition(definition) {
|
|
598
|
-
return definition.
|
|
1198
|
+
return definition.lifecycle !== "child";
|
|
599
1199
|
}
|
|
600
1200
|
function formatValidationError(label, issues) {
|
|
601
1201
|
const lines = issues.map((issue) => ` - ${issue.path.length === 0 ? "(root)" : formatPath(issue.path)}: ${issue.message}`);
|
|
@@ -616,26 +1216,74 @@ function formatPath(path) {
|
|
|
616
1216
|
return out;
|
|
617
1217
|
}
|
|
618
1218
|
exports.ACTIVITY_KINDS = ACTIVITY_KINDS;
|
|
1219
|
+
exports.ACTOR_KINDS = ACTOR_KINDS;
|
|
619
1220
|
exports.AuthoringActionSchema = AuthoringActionSchema;
|
|
620
1221
|
exports.AuthoringActivitySchema = AuthoringActivitySchema;
|
|
621
1222
|
exports.AuthoringFieldEntrySchema = AuthoringFieldEntrySchema;
|
|
1223
|
+
exports.AuthoringGuardSchema = AuthoringGuardSchema;
|
|
622
1224
|
exports.AuthoringOpSchema = AuthoringOpSchema;
|
|
623
1225
|
exports.AuthoringStageSchema = AuthoringStageSchema;
|
|
624
1226
|
exports.AuthoringTransitionSchema = AuthoringTransitionSchema;
|
|
625
1227
|
exports.AuthoringWorkflowSchema = AuthoringWorkflowSchema;
|
|
1228
|
+
exports.CONDITION_VARS = CONDITION_VARS;
|
|
1229
|
+
exports.ContractViolationError = ContractViolationError;
|
|
626
1230
|
exports.DOCUMENT_VALUE_PERMISSIONS = DOCUMENT_VALUE_PERMISSIONS;
|
|
627
1231
|
exports.DRIVER_KINDS = DRIVER_KINDS;
|
|
1232
|
+
exports.DefinitionInUseError = DefinitionInUseError;
|
|
1233
|
+
exports.DefinitionNotFoundError = DefinitionNotFoundError;
|
|
1234
|
+
exports.EFFECTS_READ = EFFECTS_READ;
|
|
1235
|
+
exports.EffectNotFoundError = EffectNotFoundError;
|
|
628
1236
|
exports.EffectSchema = EffectSchema;
|
|
1237
|
+
exports.FIELD_READ = FIELD_READ;
|
|
1238
|
+
exports.FILTER_SCOPE_VARS = FILTER_SCOPE_VARS;
|
|
1239
|
+
exports.FieldValueShapeError = FieldValueShapeError;
|
|
629
1240
|
exports.GROQ_IDENTIFIER = GROQ_IDENTIFIER;
|
|
630
|
-
exports.
|
|
1241
|
+
exports.GUARD_PREDICATE_VARS = GUARD_PREDICATE_VARS;
|
|
1242
|
+
exports.InstanceNotFoundError = InstanceNotFoundError;
|
|
1243
|
+
exports.RESERVED_CONDITION_VARS = RESERVED_CONDITION_VARS;
|
|
1244
|
+
exports.RESOURCE_ALIAS_NAME_SOURCE = RESOURCE_ALIAS_NAME_SOURCE;
|
|
631
1245
|
exports.StoredFieldOpSchema = StoredFieldOpSchema;
|
|
632
1246
|
exports.WORKFLOW_DEFINITION_TYPE = WORKFLOW_DEFINITION_TYPE;
|
|
1247
|
+
exports.WorkflowConfigSchema = WorkflowConfigSchema;
|
|
1248
|
+
exports.WorkflowError = WorkflowError;
|
|
633
1249
|
exports.actorFulfillsRole = actorFulfillsRole;
|
|
634
1250
|
exports.andConditions = andConditions;
|
|
1251
|
+
exports.checkFieldValue = checkFieldValue;
|
|
1252
|
+
exports.datasetResourceParts = datasetResourceParts;
|
|
1253
|
+
exports.definitionDocId = definitionDocId;
|
|
635
1254
|
exports.expandRequiredRoles = expandRequiredRoles;
|
|
1255
|
+
exports.extractDocumentId = extractDocumentId;
|
|
1256
|
+
exports.formatIssues = formatIssues;
|
|
636
1257
|
exports.formatValidationError = formatValidationError;
|
|
1258
|
+
exports.gdrFromResource = gdrFromResource;
|
|
1259
|
+
exports.gdrRef = gdrRef;
|
|
1260
|
+
exports.gdrResourcePrefix = gdrResourcePrefix;
|
|
1261
|
+
exports.gdrUri = gdrUri;
|
|
1262
|
+
exports.isBareSeedId = isBareSeedId;
|
|
1263
|
+
exports.isGdr = isGdr;
|
|
1264
|
+
exports.isGdrUri = isGdrUri;
|
|
1265
|
+
exports.isGuardReadExpr = isGuardReadExpr;
|
|
637
1266
|
exports.isStartableDefinition = isStartableDefinition;
|
|
638
1267
|
exports.isTerminalActivityStatus = isTerminalActivityStatus;
|
|
639
1268
|
exports.issuesFromValibot = issuesFromValibot;
|
|
640
1269
|
exports.normalizeRoleAliases = normalizeRoleAliases;
|
|
1270
|
+
exports.parseGdr = parseGdr;
|
|
1271
|
+
exports.printGuardRead = printGuardRead;
|
|
1272
|
+
exports.refCanvas = refCanvas;
|
|
1273
|
+
exports.refDashboard = refDashboard;
|
|
1274
|
+
exports.refDataset = refDataset;
|
|
1275
|
+
exports.refMediaLibrary = refMediaLibrary;
|
|
1276
|
+
exports.resourceAliasesToMap = resourceAliasesToMap;
|
|
1277
|
+
exports.resourceFromGdrUri = resourceFromGdrUri;
|
|
1278
|
+
exports.resourceFromParsed = resourceFromParsed;
|
|
1279
|
+
exports.sameResource = sameResource;
|
|
1280
|
+
exports.selfGdr = selfGdr;
|
|
1281
|
+
exports.tagScopeFilter = tagScopeFilter;
|
|
1282
|
+
exports.toBareId = toBareId;
|
|
1283
|
+
exports.toPhysicalGdr = toPhysicalGdr;
|
|
1284
|
+
exports.tryParseGdr = tryParseGdr;
|
|
1285
|
+
exports.validateFieldAppendItem = validateFieldAppendItem;
|
|
1286
|
+
exports.validateFieldValue = validateFieldValue;
|
|
1287
|
+
exports.validateResourceAliasName = validateResourceAliasName;
|
|
1288
|
+
exports.validateTag = validateTag;
|
|
641
1289
|
//# sourceMappingURL=schema.cjs.map
|