@sanity/workflow-engine 0.27.0 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -5975,6 +5975,17 @@ export declare function isNotesEntry(
5975
5975
  /** Whether a project-user API failure explicitly means the user is absent. */
5976
5976
  export declare function isProjectUserNotFoundError(error: unknown): boolean;
5977
5977
 
5978
+ /**
5979
+ * True when a write looks like a lost optimistic lock — every `statusCode: 409`
5980
+ * from the real client, or the bench's `ifRevisionId check failed` message.
5981
+ * A bare 409 is not exclusively a lost fence: a `create` against an existing id
5982
+ * (`documentAlreadyExistsError`) carries the same status. Narrow the error to
5983
+ * one rev-guarded write before asking — anything wider (a whole cascade, or a
5984
+ * commit together with its guard deploy) reads a create collision as a lost
5985
+ * race.
5986
+ */
5987
+ export declare function isRevisionConflict(error: unknown): boolean;
5988
+
5978
5989
  /** Entry-level {@link isSingleDocRefKind}: narrows a resolved entry to the
5979
5990
  * single-GDR arms (`doc.ref` / `subject`) — the value is one GDR (or null)
5980
5991
  * and the entry may carry the accepted-target `types`. */
@@ -6586,11 +6597,6 @@ export declare interface ParsedGdr {
6586
6597
  documentId: string;
6587
6598
  }
6588
6599
 
6589
- declare type ParsedWorkflowConfig = v.InferOutput<typeof WorkflowConfigSchema>;
6590
-
6591
- declare type ParsedWorkflowDeployment =
6592
- ParsedWorkflowConfig["deployments"][number];
6593
-
6594
6600
  /**
6595
6601
  * Parse a GDR URI into its scheme + addressing parts. Throws on
6596
6602
  * unknown scheme or malformed shape.
@@ -9582,11 +9588,18 @@ export declare interface WorkflowCommitOptions {
9582
9588
  tag?: string;
9583
9589
  }
9584
9590
 
9585
- export declare type WorkflowConfig = Omit<
9586
- ParsedWorkflowConfig,
9591
+ export declare type WorkflowConfig = v.InferOutput<typeof WorkflowConfigSchema>;
9592
+
9593
+ /**
9594
+ * What an author writes for a whole config: every deployment is a
9595
+ * {@link WorkflowDeploymentInput}. `defineWorkflowConfig` accepts this and
9596
+ * returns the looser {@link WorkflowConfig}.
9597
+ */
9598
+ export declare type WorkflowConfigInput = Omit<
9599
+ WorkflowConfig,
9587
9600
  "deployments"
9588
9601
  > & {
9589
- deployments: WorkflowDeployment[];
9602
+ deployments: WorkflowDeploymentInput[];
9590
9603
  };
9591
9604
 
9592
9605
  declare const WorkflowConfigSchema: v.ObjectSchema<
@@ -10297,15 +10310,25 @@ declare const WorkflowDefinitionSchema: v.GenericSchema<
10297
10310
  WorkflowFields<FieldEntry, Stage, StartBlock>
10298
10311
  >;
10299
10312
 
10300
- export declare type WorkflowDeployment = Omit<
10301
- ParsedWorkflowDeployment,
10313
+ /** One deployment as loaded from a config: the floor is optional/unverified so
10314
+ * a command that never selects a deployment can hold a stale or missing
10315
+ * acknowledgement. Deployment-scoped paths assert before they act; authors
10316
+ * write {@link WorkflowDeploymentInput}. */
10317
+ export declare type WorkflowDeployment = WorkflowConfig["deployments"][number];
10318
+
10319
+ /**
10320
+ * What an author writes for one deployment: the current reader floor as the
10321
+ * reviewed literal. Compile-time only — omitting it or setting a wrong value
10322
+ * is a type error in the editor. Runtime parse still tolerates a stale or
10323
+ * missing floor on {@link WorkflowDeployment} so commands that never select a
10324
+ * deployment still run; deployment-scoped paths assert the selected
10325
+ * deployment (instance-id commands do not).
10326
+ */
10327
+ export declare type WorkflowDeploymentInput = Omit<
10328
+ WorkflowDeployment,
10302
10329
  "expectedMinReaderModel"
10303
10330
  > & {
10304
- /**
10305
- * Reviewed numeric literal. Runtime validation owns the exact installed-floor check so a stale
10306
- * acknowledgement reaches the readers-first rollout guidance instead of becoming a type error.
10307
- */
10308
- expectedMinReaderModel: number;
10331
+ expectedMinReaderModel: typeof DATA_MODEL_MIN_READER;
10309
10332
  };
10310
10333
 
10311
10334
  export declare const WorkflowEffectCompleted: WorkflowTelemetryEvent<WorkflowEffectCompletedData>;
package/dist/index.d.ts CHANGED
@@ -5975,6 +5975,17 @@ export declare function isNotesEntry(
5975
5975
  /** Whether a project-user API failure explicitly means the user is absent. */
5976
5976
  export declare function isProjectUserNotFoundError(error: unknown): boolean;
5977
5977
 
5978
+ /**
5979
+ * True when a write looks like a lost optimistic lock — every `statusCode: 409`
5980
+ * from the real client, or the bench's `ifRevisionId check failed` message.
5981
+ * A bare 409 is not exclusively a lost fence: a `create` against an existing id
5982
+ * (`documentAlreadyExistsError`) carries the same status. Narrow the error to
5983
+ * one rev-guarded write before asking — anything wider (a whole cascade, or a
5984
+ * commit together with its guard deploy) reads a create collision as a lost
5985
+ * race.
5986
+ */
5987
+ export declare function isRevisionConflict(error: unknown): boolean;
5988
+
5978
5989
  /** Entry-level {@link isSingleDocRefKind}: narrows a resolved entry to the
5979
5990
  * single-GDR arms (`doc.ref` / `subject`) — the value is one GDR (or null)
5980
5991
  * and the entry may carry the accepted-target `types`. */
@@ -6586,11 +6597,6 @@ export declare interface ParsedGdr {
6586
6597
  documentId: string;
6587
6598
  }
6588
6599
 
6589
- declare type ParsedWorkflowConfig = v.InferOutput<typeof WorkflowConfigSchema>;
6590
-
6591
- declare type ParsedWorkflowDeployment =
6592
- ParsedWorkflowConfig["deployments"][number];
6593
-
6594
6600
  /**
6595
6601
  * Parse a GDR URI into its scheme + addressing parts. Throws on
6596
6602
  * unknown scheme or malformed shape.
@@ -9582,11 +9588,18 @@ export declare interface WorkflowCommitOptions {
9582
9588
  tag?: string;
9583
9589
  }
9584
9590
 
9585
- export declare type WorkflowConfig = Omit<
9586
- ParsedWorkflowConfig,
9591
+ export declare type WorkflowConfig = v.InferOutput<typeof WorkflowConfigSchema>;
9592
+
9593
+ /**
9594
+ * What an author writes for a whole config: every deployment is a
9595
+ * {@link WorkflowDeploymentInput}. `defineWorkflowConfig` accepts this and
9596
+ * returns the looser {@link WorkflowConfig}.
9597
+ */
9598
+ export declare type WorkflowConfigInput = Omit<
9599
+ WorkflowConfig,
9587
9600
  "deployments"
9588
9601
  > & {
9589
- deployments: WorkflowDeployment[];
9602
+ deployments: WorkflowDeploymentInput[];
9590
9603
  };
9591
9604
 
9592
9605
  declare const WorkflowConfigSchema: v.ObjectSchema<
@@ -10297,15 +10310,25 @@ declare const WorkflowDefinitionSchema: v.GenericSchema<
10297
10310
  WorkflowFields<FieldEntry, Stage, StartBlock>
10298
10311
  >;
10299
10312
 
10300
- export declare type WorkflowDeployment = Omit<
10301
- ParsedWorkflowDeployment,
10313
+ /** One deployment as loaded from a config: the floor is optional/unverified so
10314
+ * a command that never selects a deployment can hold a stale or missing
10315
+ * acknowledgement. Deployment-scoped paths assert before they act; authors
10316
+ * write {@link WorkflowDeploymentInput}. */
10317
+ export declare type WorkflowDeployment = WorkflowConfig["deployments"][number];
10318
+
10319
+ /**
10320
+ * What an author writes for one deployment: the current reader floor as the
10321
+ * reviewed literal. Compile-time only — omitting it or setting a wrong value
10322
+ * is a type error in the editor. Runtime parse still tolerates a stale or
10323
+ * missing floor on {@link WorkflowDeployment} so commands that never select a
10324
+ * deployment still run; deployment-scoped paths assert the selected
10325
+ * deployment (instance-id commands do not).
10326
+ */
10327
+ export declare type WorkflowDeploymentInput = Omit<
10328
+ WorkflowDeployment,
10302
10329
  "expectedMinReaderModel"
10303
10330
  > & {
10304
- /**
10305
- * Reviewed numeric literal. Runtime validation owns the exact installed-floor check so a stale
10306
- * acknowledgement reaches the readers-first rollout guidance instead of becoming a type error.
10307
- */
10308
- expectedMinReaderModel: number;
10331
+ expectedMinReaderModel: typeof DATA_MODEL_MIN_READER;
10309
10332
  };
10310
10333
 
10311
10334
  export declare const WorkflowEffectCompleted: WorkflowTelemetryEvent<WorkflowEffectCompletedData>;
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
- import { terminalState, andConditions, deriveActivityKind, parseDefinitionSnapshot, CALLER_BOUND_VARS, START_REQUIREMENT_VARS, CONDITION_VARS, isSubjectEntry, isUnevaluable, ContractViolationError, isStartableDefinition, conditionFieldReadNames, rethrowWithContext, isGdr, errorMessage, conditionParameterNames, START_FILTER_VARS, gdrFromResource, selfGdr, isSingleDocRefKind, parentRef, actorFulfillsRole, toBareId, WorkflowError, sameResource, resourceFromParsed, tryParseGdr, isTerminalActivityStatus, FieldValueShapeError, validateFieldValue, validateFieldAppendItem, evaluateCondition, isSingleDocRefEntry, choiceValueIssues, scalarValidationIssues, StoredFieldOpSchema, formatIssues, conditionSyntaxIssues, checkWorkflowInvariants, formatIssuePath, isGuardReadExpr, conditionEffectReads, EFFECTS_READ, datasetResourceParts, evaluatePredicates, WORKFLOW_DEFINITION_TYPE, tagScopeFilter, isCascadeFired, WORKFLOW_INSTANCE_TYPE, parseGdr, isGdrUri, gdrRef, isInputSourced, parseFieldValue, VersionSpecificDatasetGdrError, toPhysicalGdr, isBareSeedId, tolerantEntries, NonEmptyString, FIELD_VALUE_KINDS, tolerantObject, ActorShape, IsoTimestamp, ACTIVITY_STATUSES, GdrShape, DRIVER_KINDS, FIELD_SCOPES, fieldValueSchemas, parsePersistedDoc, ACTOR_KINDS, classifyPrincipalId, directoryBridgeId, assertReadableModel, InstanceNotFoundError, modelStampFor, evaluateConditionOutcome, runGroq, FIELD_READ, MUTATION_GUARD_ACTIONS, resourceGdr, minReaderModelOf, resourceFromGdrUri, refTypeIssues, rejectedRefTypes, DOCUMENT_VALUE_PERMISSIONS, lakePrincipalId, driverKind, EffectNotFoundError, firstCarriedGlobalId, deriveExecutorClassification, validateTag, extractDocumentId, parseStoredDefinition, validateResourceAliasName, labelFor, DefinitionNotFoundError, definitionDocId, SpawnContractsInvalidError, gdrResourcePrefix, RESOURCE_ALIAS_NAME_SOURCE, isUnprimed, DefinitionInUseError, assertReaderModelAcknowledgement, isParseableInstant, groupMembershipNames } from "./_chunks-es/invariants.js";
1
+ import { rethrowWithContext, andConditions, deriveActivityKind, WorkflowError, CALLER_BOUND_VARS, START_REQUIREMENT_VARS, CONDITION_VARS, isSubjectEntry, isUnevaluable, ContractViolationError, isStartableDefinition, conditionFieldReadNames, isGdr, errorMessage, conditionParameterNames, START_FILTER_VARS, gdrFromResource, selfGdr, isSingleDocRefKind, actorFulfillsRole, toBareId, sameResource, resourceFromParsed, tryParseGdr, isTerminalActivityStatus, FieldValueShapeError, validateFieldValue, validateFieldAppendItem, evaluateCondition, isSingleDocRefEntry, choiceValueIssues, scalarValidationIssues, StoredFieldOpSchema, formatIssues, conditionSyntaxIssues, checkWorkflowInvariants, formatIssuePath, isGuardReadExpr, conditionEffectReads, EFFECTS_READ, datasetResourceParts, evaluatePredicates, WORKFLOW_DEFINITION_TYPE, tagScopeFilter, isCascadeFired, parseGdr, isGdrUri, gdrRef, isInputSourced, parseFieldValue, VersionSpecificDatasetGdrError, toPhysicalGdr, isBareSeedId, tolerantEntries, NonEmptyString, FIELD_VALUE_KINDS, tolerantObject, ActorShape, IsoTimestamp, ACTIVITY_STATUSES, GdrShape, DRIVER_KINDS, FIELD_SCOPES, fieldValueSchemas, parsePersistedDoc, ACTOR_KINDS, classifyPrincipalId, directoryBridgeId, InstanceNotFoundError, evaluateConditionOutcome, runGroq, FIELD_READ, MUTATION_GUARD_ACTIONS, resourceGdr, resourceFromGdrUri, refTypeIssues, rejectedRefTypes, DOCUMENT_VALUE_PERMISSIONS, lakePrincipalId, driverKind, EffectNotFoundError, firstCarriedGlobalId, deriveExecutorClassification, validateTag, extractDocumentId, parseStoredDefinition, validateResourceAliasName, labelFor, DefinitionNotFoundError, definitionDocId, SpawnContractsInvalidError, gdrResourcePrefix, RESOURCE_ALIAS_NAME_SOURCE, DefinitionInUseError, isParseableInstant, groupMembershipNames } from "./_chunks-es/invariants.js";
2
2
 
3
- import { ACTION_SEMANTICS, ACTIVITY_KINDS, ANONYMOUS_IDENTITY, DATA_MODEL_CHANGES, DATA_MODEL_MIN_READER, DATA_MODEL_VERSION, DEFAULT_TRANSITION_WHEN, EXECUTOR_CLASSIFICATIONS, FILTER_SCOPE_VARS, GROUP_KINDS, GUARD_PREDICATE_VARS, ModelVersionAheadError, PersistedDocShapeError, READER_MODEL_ROLLOUT_URL, RESERVED_CONDITION_VARS, ReaderModelAcknowledgementError, SYSTEM_IDENTITY, clientConfigFromResource, fieldTreeShape, gdrUri, isNotesEntry, isTodoListEntry, isTodoListItem, modelVersionOf, parseDefinitionSnapshotValue, parseResourceGdr, readsRootDocument, refCanvas, refDashboard, refDataset, refKindAcceptsTypes, refMediaLibrary, releaseDocId, releaseRef, requiredModelFeatures, requiredReaderModel, resourceAliasesToMap, schemaTreeShape, startKindOf } from "./_chunks-es/invariants.js";
3
+ import { ACTION_SEMANTICS, ACTIVITY_KINDS, ANONYMOUS_IDENTITY, DEFAULT_TRANSITION_WHEN, EXECUTOR_CLASSIFICATIONS, FILTER_SCOPE_VARS, GROUP_KINDS, GUARD_PREDICATE_VARS, PersistedDocShapeError, RESERVED_CONDITION_VARS, SYSTEM_IDENTITY, clientConfigFromResource, gdrUri, isNotesEntry, isTodoListEntry, isTodoListItem, parseResourceGdr, readsRootDocument, refCanvas, refDashboard, refDataset, refKindAcceptsTypes, refMediaLibrary, releaseDocId, releaseRef, resourceAliasesToMap, schemaTreeShape, startKindOf } from "./_chunks-es/invariants.js";
4
4
 
5
5
  import { atomNode, dedupeBy, analyzeCondition, atomReadsDataset, checklistLines as checklistLines$1, phrase, quoted, listAnd, describeAtom as describeAtom$1, describeCondition as describeCondition$1, guillemets, formatValue, humanize, listOr, scopeReadOf, describeRead, explainCondition, dedupeReads, whatIfCondition, MAX_COUNTERFACTUAL_INDEX } from "@sanity/groq-condition-describe";
6
6
 
@@ -41,6 +41,53 @@ function findCurrentActivityEntry(host, activityName) {
41
41
  return findOpenStageEntry(host)?.activities.find(a => a.name === activityName);
42
42
  }
43
43
 
44
+ const WORKFLOW_INSTANCE_TYPE = "sanity.workflow.instance";
45
+
46
+ function terminalState(instance) {
47
+ return instance.abortedAt !== void 0 ? "aborted" : instance.completedAt !== void 0 ? "completed" : "in-flight";
48
+ }
49
+
50
+ function isUnprimed(instance) {
51
+ return instance.stages.length === 0 && terminalState(instance) === "in-flight";
52
+ }
53
+
54
+ function parseDefinitionSnapshotValue(instance) {
55
+ try {
56
+ return normalizeLegacyActivityRequirements(JSON.parse(instance.definitionSnapshot));
57
+ } catch (err) {
58
+ rethrowWithContext(err, `Failed to parse definitionSnapshot on instance "${instance._id}"`);
59
+ }
60
+ }
61
+
62
+ function normalizeLegacyActivityRequirements(value) {
63
+ for (const stage of arrayMember(value, "stages")) for (const activity of arrayMember(stage, "activities")) normalizeLegacyRequirementMap(activity);
64
+ return value;
65
+ }
66
+
67
+ function arrayMember(value, key) {
68
+ if (typeof value != "object" || value === null) return [];
69
+ const member = value[key];
70
+ return Array.isArray(member) ? member : [];
71
+ }
72
+
73
+ function normalizeLegacyRequirementMap(value) {
74
+ if (typeof value != "object" || value === null) return;
75
+ const activity = value, requirements = activity.requirements;
76
+ typeof requirements != "object" || requirements === null || Array.isArray(requirements) || (activity.requirements = Object.entries(requirements).map(([name, query]) => ({
77
+ type: "groq",
78
+ name: name,
79
+ query: query
80
+ })));
81
+ }
82
+
83
+ function parseDefinitionSnapshot(instance) {
84
+ return parseDefinitionSnapshotValue(instance);
85
+ }
86
+
87
+ function parentRef(instance) {
88
+ return instance.ancestors.at(-1);
89
+ }
90
+
44
91
  function effectiveEditable(baseline, override) {
45
92
  if (baseline === void 0) return;
46
93
  if (override === void 0) return baseline;
@@ -388,6 +435,240 @@ function fieldEditedData(args) {
388
435
  };
389
436
  }
390
437
 
438
+ const DATA_MODEL_VERSION = 5, DATA_MODEL_MIN_READER = 4, READER_MODEL_ROLLOUT_URL = "https://www.sanity.io/docs/editorial-workflows/prerelease";
439
+
440
+ class ReaderModelAcknowledgementError extends WorkflowError {
441
+ code="WORKFLOW_READER_MODEL_ACKNOWLEDGEMENT_MISMATCH";
442
+ expectedMinReaderModel;
443
+ engineMinReaderModel=DATA_MODEL_MIN_READER;
444
+ engineModelVersion=DATA_MODEL_VERSION;
445
+ documentationUrl=READER_MODEL_ROLLOUT_URL;
446
+ constructor(expectedMinReaderModel, context = "Deployment") {
447
+ const expected = expectedMinReaderModel === void 0 ? "missing" : String(expectedMinReaderModel);
448
+ super("reader-model-acknowledgement", `${context} expected reader floor ${expected}; the installed engine requires acknowledgement ${DATA_MODEL_MIN_READER}.\nDo not change the acknowledgement yet: accepting ${DATA_MODEL_MIN_READER} authorizes this engine to write documents that older readers will refuse.\nUpgrade every Studio, CLI, MCP server, Function, and other runtime that reads engine-owned documents; verify that rollout in every environment sharing the workflow resource; then change the literal in deployment configuration and deploy the writer.\nRollout guide: ${READER_MODEL_ROLLOUT_URL}`),
449
+ this.name = "ReaderModelAcknowledgementError", this.expectedMinReaderModel = expectedMinReaderModel;
450
+ }
451
+ }
452
+
453
+ function assertReaderModelAcknowledgement(expectedMinReaderModel, context) {
454
+ if (typeof expectedMinReaderModel != "number" || !Number.isFinite(expectedMinReaderModel) || !Number.isInteger(expectedMinReaderModel) || expectedMinReaderModel < 0 || expectedMinReaderModel !== DATA_MODEL_MIN_READER) throw new ReaderModelAcknowledgementError(expectedMinReaderModel, context);
455
+ }
456
+
457
+ const DATA_MODEL_CHANGES = Object.freeze([ Object.freeze({
458
+ id: "governed-model-stamps",
459
+ introducedInModel: 1,
460
+ minReaderModel: 0,
461
+ documentTypes: Object.freeze([ "definition", "instance" ]),
462
+ compatibility: "additive",
463
+ applicability: "unconditional",
464
+ summary: "Definition and instance documents carry model provenance and reader-floor stamps."
465
+ }), Object.freeze({
466
+ id: "subject-field-kind",
467
+ introducedInModel: 2,
468
+ minReaderModel: 0,
469
+ documentTypes: Object.freeze([ "definition", "instance" ]),
470
+ compatibility: "additive",
471
+ applicability: "detectable",
472
+ summary: "A workflow-level subject field identifies the document a workflow is about."
473
+ }), Object.freeze({
474
+ id: "typed-scalar-choice-lists",
475
+ introducedInModel: 2,
476
+ minReaderModel: 2,
477
+ documentTypes: Object.freeze([ "definition", "instance" ]),
478
+ compatibility: "reader-floor",
479
+ applicability: "detectable",
480
+ summary: "Scalar fields may constrain writes to a persisted typed choice list."
481
+ }), Object.freeze({
482
+ id: "action-semantics",
483
+ introducedInModel: 2,
484
+ minReaderModel: 0,
485
+ documentTypes: Object.freeze([ "definition" ]),
486
+ compatibility: "additive",
487
+ applicability: "detectable",
488
+ summary: "Ordinary actions may carry a closed bag of advisory workflow semantics."
489
+ }), Object.freeze({
490
+ id: "inclusive-scalar-bounds",
491
+ introducedInModel: 2,
492
+ minReaderModel: 2,
493
+ documentTypes: Object.freeze([ "definition", "instance" ]),
494
+ compatibility: "reader-floor",
495
+ applicability: "detectable",
496
+ summary: "String, text, and number values may carry persisted inclusive bounds."
497
+ }), Object.freeze({
498
+ id: "progress-field-kind",
499
+ introducedInModel: 3,
500
+ minReaderModel: 0,
501
+ documentTypes: Object.freeze([ "definition", "instance" ]),
502
+ compatibility: "additive",
503
+ applicability: "detectable",
504
+ summary: "A progress field kind carries application-defined 0–100 completion."
505
+ }), Object.freeze({
506
+ id: "effect-claim-tokens",
507
+ introducedInModel: 3,
508
+ minReaderModel: 0,
509
+ documentTypes: Object.freeze([ "instance" ]),
510
+ compatibility: "additive",
511
+ applicability: "detectable",
512
+ summary: "Pending-effect claims carry an exact-claim token gating mid-dispatch state reports."
513
+ }), Object.freeze({
514
+ id: "classified-principal-ids",
515
+ introducedInModel: 4,
516
+ minReaderModel: 4,
517
+ documentTypes: Object.freeze([ "instance" ]),
518
+ compatibility: "reader-floor",
519
+ applicability: "unconditional",
520
+ summary: "Principal ids are namespace-classified: actor and assignee writes carry the account-global user id only, and readers resolve legacy project-scoped ids through the prefix classifier at the instance read funnel."
521
+ }), Object.freeze({
522
+ id: "readiness-requirements",
523
+ introducedInModel: 4,
524
+ minReaderModel: 4,
525
+ documentTypes: Object.freeze([ "definition" ]),
526
+ compatibility: "reader-floor",
527
+ applicability: "detectable",
528
+ summary: "Start and activity readiness use named polymorphic requirement arrays."
529
+ }), Object.freeze({
530
+ id: "due-date-field-kinds",
531
+ introducedInModel: 5,
532
+ minReaderModel: 0,
533
+ documentTypes: Object.freeze([ "definition", "instance" ]),
534
+ compatibility: "additive",
535
+ applicability: "detectable",
536
+ summary: "Due-date field kinds (dueDate, dueDatetime) mark a level deadline, elevated aliases of date/datetime carrying the same stored value."
537
+ }) ]);
538
+
539
+ function recordOf(value) {
540
+ return value !== null && typeof value == "object" && !Array.isArray(value) ? value : void 0;
541
+ }
542
+
543
+ function recordsAt(record, key) {
544
+ const value = record[key];
545
+ return Array.isArray(value) ? value.map(recordOf).filter(item => item !== void 0) : [];
546
+ }
547
+
548
+ function nestedFieldEntries(entries) {
549
+ return entries.flatMap(entry => [ entry, ...nestedFieldEntries(recordsAt(entry, "fields")), ...nestedFieldEntries(recordsAt(entry, "of")) ]);
550
+ }
551
+
552
+ function parsedDefinitionSnapshot(root) {
553
+ if (typeof root.definitionSnapshot == "string") return recordOf(parseDefinitionSnapshotValue({
554
+ _id: typeof root._id == "string" ? root._id : "<unknown instance>",
555
+ definitionSnapshot: root.definitionSnapshot
556
+ }));
557
+ }
558
+
559
+ function persistedFieldEntries(document) {
560
+ const root = recordOf(document);
561
+ if (root === void 0) return [];
562
+ const snapshot = parsedDefinitionSnapshot(root), roots = snapshot === void 0 ? [ root ] : [ root, snapshot ], stages = roots.flatMap(candidate => recordsAt(candidate, "stages")), activities = stages.flatMap(stage => recordsAt(stage, "activities")), actions = activities.flatMap(activity => recordsAt(activity, "actions")), effects = actions.flatMap(action => recordsAt(action, "effects"));
563
+ return nestedFieldEntries([ ...roots.flatMap(candidate => recordsAt(candidate, "fields")), ...stages.flatMap(stage => recordsAt(stage, "fields")), ...activities.flatMap(activity => recordsAt(activity, "fields")), ...actions.flatMap(action => recordsAt(action, "params")), ...effects.flatMap(effect => recordsAt(effect, "outputs")) ]);
564
+ }
565
+
566
+ function hasChoiceList(document) {
567
+ return persistedFieldEntries(document).some(entry => {
568
+ const options = recordOf(entry.options);
569
+ return options !== void 0 && Array.isArray(options.list);
570
+ });
571
+ }
572
+
573
+ function hasFieldKind(document, kind) {
574
+ return persistedFieldEntries(document).some(entry => entry.type === kind || entry._type === kind);
575
+ }
576
+
577
+ function hasActionSemantics(document) {
578
+ const root = recordOf(document);
579
+ return root === void 0 ? !1 : recordsAt(root, "stages").flatMap(stage => recordsAt(stage, "activities")).flatMap(activity => recordsAt(activity, "actions")).some(action => Array.isArray(action.semantics));
580
+ }
581
+
582
+ function hasScalarValidation(document) {
583
+ return persistedFieldEntries(document).some(entry => {
584
+ const validation = recordOf(entry.validation);
585
+ return typeof validation?.min == "number" || typeof validation?.max == "number";
586
+ });
587
+ }
588
+
589
+ function hasClaimTokens(document) {
590
+ const root = recordOf(document);
591
+ return root === void 0 ? !1 : recordsAt(root, "pendingEffects").some(entry => {
592
+ const claim = recordOf(entry.claim);
593
+ return claim !== void 0 && typeof claim.claimToken == "string";
594
+ });
595
+ }
596
+
597
+ function hasReadinessRequirements(document) {
598
+ const root = recordOf(document);
599
+ return root === void 0 ? !1 : Array.isArray(recordOf(root.start)?.requirements) ? !0 : recordsAt(root, "stages").some(stage => recordsAt(stage, "activities").some(activity => Array.isArray(activity.requirements)));
600
+ }
601
+
602
+ const featureDetectors = {
603
+ "governed-model-stamps": () => !0,
604
+ "subject-field-kind": document => hasFieldKind(document, "subject"),
605
+ "typed-scalar-choice-lists": hasChoiceList,
606
+ "action-semantics": hasActionSemantics,
607
+ "inclusive-scalar-bounds": hasScalarValidation,
608
+ "progress-field-kind": document => hasFieldKind(document, "progress"),
609
+ "effect-claim-tokens": hasClaimTokens,
610
+ "classified-principal-ids": () => !0,
611
+ "readiness-requirements": hasReadinessRequirements,
612
+ "due-date-field-kinds": document => hasFieldKind(document, "dueDate") || hasFieldKind(document, "dueDatetime")
613
+ };
614
+
615
+ function requiredModelFeatures(documentType, document) {
616
+ return DATA_MODEL_CHANGES.filter(change => change.documentTypes.some(candidate => candidate === documentType) && featureDetectors[change.id](document));
617
+ }
618
+
619
+ function requiredReaderModel(documentType, document) {
620
+ return Math.max(0, ...requiredModelFeatures(documentType, document).map(change => change.minReaderModel));
621
+ }
622
+
623
+ function modelStampFor(args) {
624
+ return {
625
+ modelVersion: DATA_MODEL_VERSION,
626
+ minReaderModel: Math.max(DATA_MODEL_MIN_READER, args.storedMinReaderModel ?? 0, requiredReaderModel(args.documentType, args.document))
627
+ };
628
+ }
629
+
630
+ function fieldTreeShape(value) {
631
+ if (Array.isArray(value)) return value.map(fieldTreeShape);
632
+ if (value === null) return "null";
633
+ if (typeof value == "object") {
634
+ const record = value;
635
+ return Object.fromEntries(Object.keys(record).toSorted().map(key => [ key, fieldTreeShape(record[key]) ]));
636
+ }
637
+ return typeof value;
638
+ }
639
+
640
+ function modelVersionOf(doc) {
641
+ const stamp = doc.modelVersion;
642
+ return typeof stamp == "number" ? stamp : 0;
643
+ }
644
+
645
+ function minReaderModelOf(doc) {
646
+ const floor = doc.minReaderModel;
647
+ return typeof floor == "number" ? floor : modelVersionOf(doc);
648
+ }
649
+
650
+ class ModelVersionAheadError extends WorkflowError {
651
+ documentId;
652
+ documentModelVersion;
653
+ requiredReaderModel;
654
+ engineModelVersion;
655
+ constructor(args) {
656
+ super("model-version-ahead", `Document "${args.documentId}" was written by engine data model ${args.documentModelVersion} and requires a reader at model ${args.requiredReaderModel} or newer; this engine reads up to model ${DATA_MODEL_VERSION}. Upgrade @sanity/workflow-engine to a version that understands it.`),
657
+ this.name = "ModelVersionAheadError", this.documentId = args.documentId, this.documentModelVersion = args.documentModelVersion,
658
+ this.requiredReaderModel = args.requiredReaderModel, this.engineModelVersion = DATA_MODEL_VERSION;
659
+ }
660
+ }
661
+
662
+ function assertReadableModel(doc) {
663
+ const documentReaderModel = minReaderModelOf(doc);
664
+ if (documentReaderModel > DATA_MODEL_VERSION) throw new ModelVersionAheadError({
665
+ documentId: doc._id,
666
+ documentModelVersion: modelVersionOf(doc),
667
+ requiredReaderModel: documentReaderModel
668
+ });
669
+ return doc;
670
+ }
671
+
391
672
  function effectSites(def) {
392
673
  const sites = [];
393
674
  for (const stage of def.stages ?? []) for (const activity of stage.activities ?? []) for (const action of activity.actions ?? []) for (const effect of action.effects ?? []) sites.push({
@@ -2258,6 +2539,12 @@ function isRevisionConflict(error) {
2258
2539
  return statusCode === 409 ? !0 : typeof message == "string" && message.includes("ifRevisionId check failed");
2259
2540
  }
2260
2541
 
2542
+ function isCreateIdCollision(error) {
2543
+ if (typeof error != "object" || error === null) return !1;
2544
+ const {message: message, responseBody: responseBody} = error;
2545
+ return typeof message == "string" && message.includes("already exists") && (message.includes("Document by ID") || message.includes("create() failed")) ? !0 : typeof responseBody == "string" && responseBody.includes("documentAlreadyExistsError");
2546
+ }
2547
+
2261
2548
  class ConcurrentEditFieldError extends WorkflowError {
2262
2549
  instanceId;
2263
2550
  target;
@@ -5883,12 +6170,14 @@ function resolveGuard({guard: guard, instance: instance, stageName: stageName, n
5883
6170
 
5884
6171
  async function upsertGuard(args) {
5885
6172
  const {client: client, doc: doc, exists: exists} = args;
5886
- if (!exists) {
6173
+ if (!exists) try {
5887
6174
  await client.create(doc, {
5888
6175
  ...SYNC_COMMIT,
5889
6176
  tag: REQUEST_TAG.guardDeploy
5890
6177
  });
5891
6178
  return;
6179
+ } catch (error) {
6180
+ if (!isCreateIdCollision(error)) throw error;
5892
6181
  }
5893
6182
  const {_id: _id, _type: _type, _rev: _rev, _createdAt: _createdAt, _updatedAt: _updatedAt, ...body} = doc;
5894
6183
  await client.patch(doc._id).set(body).commit({
@@ -13002,4 +13291,4 @@ function displayDescription(typeKey) {
13002
13291
  if (typeKey) return DISPLAY[typeKey]?.description;
13003
13292
  }
13004
13293
 
13005
- export { ACTION_SEMANTICS, ACTIVITY_KINDS, ACTIVITY_KIND_DISPLAY, ACTOR_KINDS, ANONYMOUS_IDENTITY, AUTHORING_DISPLAY, ActionDisabledError, ActionParamsInvalidError, CONDITION_VARS, CONTEXT_ENTRY_DISPLAY, CascadeLimitError, ConcurrentCommitEffectOpsError, ConcurrentCompleteEffectError, ConcurrentEditFieldError, ConcurrentFireActionError, ContractViolationError, DATA_MODEL_CHANGES, DATA_MODEL_MIN_READER, DATA_MODEL_VERSION, DEFAULT_CONTENT_PERSPECTIVE, DEFAULT_EFFECT_LEASE_MS, DEFAULT_IDEMPOTENCY_TTL_MS, DEFAULT_TRANSITION_WHEN, DISPLAY, DRIVER_KINDS, DRIVER_KIND_DISPLAY, DefinitionInUseError, DefinitionNotFoundError, EFFECT_COMMIT_DISPATCH_CAP, EFFECT_COMMIT_QUEUE_DEPTH, ENGINE_API_VERSION, EXECUTION_KINDS, EXECUTOR_CLASSIFICATIONS, EXECUTOR_CLASSIFICATION_DISPLAY, EditFieldDeniedError, EffectCommitQueueOverflowError, EffectNotFoundError, EffectOpsInvalidError, EffectOutputsInvalidError, FIELD_KIND_DISPLAY, FILTER_SCOPE_VARS, FieldValueShapeError, GROUP_KINDS, GROUP_KIND_DISPLAY, GUARD_DOC_TYPE, GUARD_OWNER, GUARD_PREDICATE_VARS, HISTORY_DISPLAY, InitialFieldsInvalidError, InstanceNotFoundError, MAX_COUNTERFACTUAL_INDEX2 as MAX_COUNTERFACTUAL_INDEX, MissingHandlerError, ModelVersionAheadError, MutationGuardDeniedError, MutationGuardDocSchema, NEUTRAL_MARK, OP_DISPLAY, OUTCOME_MARKS, PartialGuardDeployError, PersistedDocShapeError, READER_MODEL_ROLLOUT_URL, RESERVED_CONDITION_VARS, ReaderModelAcknowledgementError, RefResourceUndeclaredError, RequiredFieldNotProvidedError, START_FILTER_VARS, START_REQUIREMENT_VARS, SYSTEM_IDENTITY, SpawnContractsInvalidError, StaleEffectClaimError, StartNotAllowedError, StartNotPrimedError, StartNotSettledError, WORKFLOW_DEFINITION_TYPE, WORKFLOW_INSTANCE_TYPE, WorkflowActionFired, WorkflowActivityReset, WorkflowDefinitionDeleted, WorkflowDefinitionDeployed, WorkflowEffectCompleted, WorkflowEffectStateReported, WorkflowEffectsDrained, WorkflowError, WorkflowFieldEdited, WorkflowInstanceAborted, WorkflowInstanceSchema, WorkflowInstanceStarted, WorkflowInstanceTicked, WorkflowStageSet, WorkflowStageTransitioned, WorkflowStateDivergedError, abortReason, acceptsDocumentType, aclPathForResource, actionDisabledDetail, actionRendering, actionVerdict, activityAutonomyOf, analyzeCondition2 as analyzeCondition, applicableDefinitions, assertReadableModel, assertReaderModelAcknowledgement, atomReadsDataset2 as atomReadsDataset, autonomySummary, availableActions, buildInitialFields, buildSnapshot, checklistLines, classifyPrincipalId, clientConfigFromResource, clientProjectUserDirectory, compileGuard, computeDiffEntries, conditionFieldReadNames, conditionSitesOf, contentDocQuery, contentDraftFallback, contentReleaseName, contextMap, createEngine, createTelemetryIntake, datasetResourceParts, defaultLoggerFactory, definitionDeployedData, definitionLookupGroq, definitionTagsGroq, definitionsListGroq, deniedGuardLabels, deniedGuardRefs, denyingGuards, deployStageGuards, deployedTagsGroq, deriveActivityKind, deriveExecutorClassification, deriveWorkflowAutonomy, describeAtom, describeAutonomyWait, describeCondition, describeDefinition, describeFieldInsight, describeNode, describeSite, describeSiteHeading, diagnoseInputFromEvaluation, diagnoseInstance, diffEntry, displayDescription, displayTitle, documentActionDenials, documentPrefilter, driverKind, effectOutputsMap, entryDocRefs, errorMessage, evaluateFromSnapshot, evaluateMutationGuard, evaluateStartFilter, expandResourceAliases, explainCondition2 as explainCondition, explainStartRequirement, extractDocumentId, fieldTreeShape, findCurrentActivityEntry, findOpenStageEntry, formatRead, gdrFromResource, gdrRef, gdrUri, groupMembershipNames, groupSitesOf, guardMatches, guardsForDefinition, guardsForInstance, guardsForResource, guillemets2 as guillemets, hasSingleSubjectRequirement, hashDefinitionContent, humanize2 as humanize, inFlightFilter, initialFieldIssues, instanceDocId, instanceGuardQuery, instanceWatchesDocument, instancesGuardQuery, instancesQuery, isCascadeFired, isClaimExpired, isClientProjectUser, isComparisonOp, isDefinitionApplicable, isFilterScopedOut, isGdr, isInputSourced, isNotesEntry, isProjectUserNotFoundError, isSingleDocRefEntry, isSingleDocRefKind, isStartableDefinition, isSubjectEntry, isTelemetryEnvDenied, isTerminalActivityStatus, isTerminalStage, isTodoListEntry, isTodoListItem, isUnprimed, lakeGuardId, lakePrincipalId, latestDefinitionsGroq, latestDeployedDefinitions, lintEffectOutputs, minReaderModelOf, missingRequiredInputs, modelVersionOf, narrateAutonomyWaits, noopTelemetry, parentRef, parseDefinitionInput, parseDefinitionSnapshot, parseDefinitionSnapshotValue, parseGdr, parseGuardDocument, parseInstanceDocument, parseResourceGdr, processShellUserProperties, projectStartSliceRow, projectToWatchRef, quoted2 as quoted, readInstanceDoc, readsRaw, readsRootDocument, refCanvas, refDashboard, refDataset, refKindAcceptsTypes, refMediaLibrary, refsOf, rejectedRefTypes, releaseDocId, releaseRef, remediationsFor, requiredModelFeatures, requiredReaderModel, resolveAccess, resolveActor, resolveClientActor, resolveFieldEntry$1 as resolveFieldEntry, resourceAliasesToMap, resourceFromParsed, resourceGdr, retractStageGuards, sameResource, scalarValidationIssues, schemaTreeShape, sentenceCase, silentLogger, singleSubjectRequirementRefused, stageAutonomyOf, startFieldsParam, startKindOf, startRefusal, stripSystemFields, subjectDenialLabels, subscriptionDocument, subscriptionDocumentsForInstance, sweepStaleClaims, tagScopeFilter, terminalState, toBareId, tryParseGdr, unboundRequirementReads, unsatisfiedTransitionSummaries, userLoginProvider, validateDefinition, validateTag, verdictGuardsForInstance, wallClock, whatIfCondition2 as whatIfCondition, withAssignment, workflow };
13294
+ export { ACTION_SEMANTICS, ACTIVITY_KINDS, ACTIVITY_KIND_DISPLAY, ACTOR_KINDS, ANONYMOUS_IDENTITY, AUTHORING_DISPLAY, ActionDisabledError, ActionParamsInvalidError, CONDITION_VARS, CONTEXT_ENTRY_DISPLAY, CascadeLimitError, ConcurrentCommitEffectOpsError, ConcurrentCompleteEffectError, ConcurrentEditFieldError, ConcurrentFireActionError, ContractViolationError, DATA_MODEL_CHANGES, DATA_MODEL_MIN_READER, DATA_MODEL_VERSION, DEFAULT_CONTENT_PERSPECTIVE, DEFAULT_EFFECT_LEASE_MS, DEFAULT_IDEMPOTENCY_TTL_MS, DEFAULT_TRANSITION_WHEN, DISPLAY, DRIVER_KINDS, DRIVER_KIND_DISPLAY, DefinitionInUseError, DefinitionNotFoundError, EFFECT_COMMIT_DISPATCH_CAP, EFFECT_COMMIT_QUEUE_DEPTH, ENGINE_API_VERSION, EXECUTION_KINDS, EXECUTOR_CLASSIFICATIONS, EXECUTOR_CLASSIFICATION_DISPLAY, EditFieldDeniedError, EffectCommitQueueOverflowError, EffectNotFoundError, EffectOpsInvalidError, EffectOutputsInvalidError, FIELD_KIND_DISPLAY, FILTER_SCOPE_VARS, FieldValueShapeError, GROUP_KINDS, GROUP_KIND_DISPLAY, GUARD_DOC_TYPE, GUARD_OWNER, GUARD_PREDICATE_VARS, HISTORY_DISPLAY, InitialFieldsInvalidError, InstanceNotFoundError, MAX_COUNTERFACTUAL_INDEX2 as MAX_COUNTERFACTUAL_INDEX, MissingHandlerError, ModelVersionAheadError, MutationGuardDeniedError, MutationGuardDocSchema, NEUTRAL_MARK, OP_DISPLAY, OUTCOME_MARKS, PartialGuardDeployError, PersistedDocShapeError, READER_MODEL_ROLLOUT_URL, RESERVED_CONDITION_VARS, ReaderModelAcknowledgementError, RefResourceUndeclaredError, RequiredFieldNotProvidedError, START_FILTER_VARS, START_REQUIREMENT_VARS, SYSTEM_IDENTITY, SpawnContractsInvalidError, StaleEffectClaimError, StartNotAllowedError, StartNotPrimedError, StartNotSettledError, WORKFLOW_DEFINITION_TYPE, WORKFLOW_INSTANCE_TYPE, WorkflowActionFired, WorkflowActivityReset, WorkflowDefinitionDeleted, WorkflowDefinitionDeployed, WorkflowEffectCompleted, WorkflowEffectStateReported, WorkflowEffectsDrained, WorkflowError, WorkflowFieldEdited, WorkflowInstanceAborted, WorkflowInstanceSchema, WorkflowInstanceStarted, WorkflowInstanceTicked, WorkflowStageSet, WorkflowStageTransitioned, WorkflowStateDivergedError, abortReason, acceptsDocumentType, aclPathForResource, actionDisabledDetail, actionRendering, actionVerdict, activityAutonomyOf, analyzeCondition2 as analyzeCondition, applicableDefinitions, assertReadableModel, assertReaderModelAcknowledgement, atomReadsDataset2 as atomReadsDataset, autonomySummary, availableActions, buildInitialFields, buildSnapshot, checklistLines, classifyPrincipalId, clientConfigFromResource, clientProjectUserDirectory, compileGuard, computeDiffEntries, conditionFieldReadNames, conditionSitesOf, contentDocQuery, contentDraftFallback, contentReleaseName, contextMap, createEngine, createTelemetryIntake, datasetResourceParts, defaultLoggerFactory, definitionDeployedData, definitionLookupGroq, definitionTagsGroq, definitionsListGroq, deniedGuardLabels, deniedGuardRefs, denyingGuards, deployStageGuards, deployedTagsGroq, deriveActivityKind, deriveExecutorClassification, deriveWorkflowAutonomy, describeAtom, describeAutonomyWait, describeCondition, describeDefinition, describeFieldInsight, describeNode, describeSite, describeSiteHeading, diagnoseInputFromEvaluation, diagnoseInstance, diffEntry, displayDescription, displayTitle, documentActionDenials, documentPrefilter, driverKind, effectOutputsMap, entryDocRefs, errorMessage, evaluateFromSnapshot, evaluateMutationGuard, evaluateStartFilter, expandResourceAliases, explainCondition2 as explainCondition, explainStartRequirement, extractDocumentId, fieldTreeShape, findCurrentActivityEntry, findOpenStageEntry, formatRead, gdrFromResource, gdrRef, gdrUri, groupMembershipNames, groupSitesOf, guardMatches, guardsForDefinition, guardsForInstance, guardsForResource, guillemets2 as guillemets, hasSingleSubjectRequirement, hashDefinitionContent, humanize2 as humanize, inFlightFilter, initialFieldIssues, instanceDocId, instanceGuardQuery, instanceWatchesDocument, instancesGuardQuery, instancesQuery, isCascadeFired, isClaimExpired, isClientProjectUser, isComparisonOp, isDefinitionApplicable, isFilterScopedOut, isGdr, isInputSourced, isNotesEntry, isProjectUserNotFoundError, isRevisionConflict, isSingleDocRefEntry, isSingleDocRefKind, isStartableDefinition, isSubjectEntry, isTelemetryEnvDenied, isTerminalActivityStatus, isTerminalStage, isTodoListEntry, isTodoListItem, isUnprimed, lakeGuardId, lakePrincipalId, latestDefinitionsGroq, latestDeployedDefinitions, lintEffectOutputs, minReaderModelOf, missingRequiredInputs, modelVersionOf, narrateAutonomyWaits, noopTelemetry, parentRef, parseDefinitionInput, parseDefinitionSnapshot, parseDefinitionSnapshotValue, parseGdr, parseGuardDocument, parseInstanceDocument, parseResourceGdr, processShellUserProperties, projectStartSliceRow, projectToWatchRef, quoted2 as quoted, readInstanceDoc, readsRaw, readsRootDocument, refCanvas, refDashboard, refDataset, refKindAcceptsTypes, refMediaLibrary, refsOf, rejectedRefTypes, releaseDocId, releaseRef, remediationsFor, requiredModelFeatures, requiredReaderModel, resolveAccess, resolveActor, resolveClientActor, resolveFieldEntry$1 as resolveFieldEntry, resourceAliasesToMap, resourceFromParsed, resourceGdr, retractStageGuards, sameResource, scalarValidationIssues, schemaTreeShape, sentenceCase, silentLogger, singleSubjectRequirementRefused, stageAutonomyOf, startFieldsParam, startKindOf, startRefusal, stripSystemFields, subjectDenialLabels, subscriptionDocument, subscriptionDocumentsForInstance, sweepStaleClaims, tagScopeFilter, terminalState, toBareId, tryParseGdr, unboundRequirementReads, unsatisfiedTransitionSummaries, userLoginProvider, validateDefinition, validateTag, verdictGuardsForInstance, wallClock, whatIfCondition2 as whatIfCondition, withAssignment, workflow };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanity/workflow-engine",
3
- "version": "0.27.0",
3
+ "version": "0.28.0",
4
4
  "description": "Workflow / BPM engine for Sanity content — define workflows as data, run them as instances against a Sanity client.",
5
5
  "keywords": [
6
6
  "bpm",