@sanity/workflow-engine 0.4.0 → 0.5.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
@@ -6,6 +6,11 @@ export declare interface AbortInstanceArgs extends OperationArgs {
6
6
  reason?: string;
7
7
  }
8
8
 
9
+ /** The free-text reason recorded on the aborted history entry, if any. */
10
+ export declare function abortReason(
11
+ instance: Pick<WorkflowInstance, "history">,
12
+ ): string | undefined;
13
+
9
14
  export declare type Action = v.InferOutput<typeof StoredActionSchema>;
10
15
 
11
16
  export declare class ActionDisabledError extends Error {
@@ -90,6 +95,17 @@ export declare class ActionParamsInvalidError extends Error {
90
95
  });
91
96
  }
92
97
 
98
+ /** The fireable-action verdict for one action on a task — its `allowed`
99
+ * state, structured `disabledReason`, and declared params, tagged with the
100
+ * owning task. The per-action atom both projections share:
101
+ * {@link availableActions} flattens it across a stage's tasks; a consumer
102
+ * that keeps per-task nesting (e.g. the MCP) maps it over a task's actions
103
+ * directly, so the verdict shape has a single source. */
104
+ export declare function actionVerdict(
105
+ task: TaskEvaluation,
106
+ action: ActionEvaluation,
107
+ ): AvailableAction;
108
+
93
109
  export declare interface Actor {
94
110
  kind: "user" | "ai" | "system";
95
111
  id: string;
@@ -120,6 +136,13 @@ export declare type Assignee =
120
136
  role: string;
121
137
  };
122
138
 
139
+ /** The assignees recorded for a task in a stage — the runtime half of the
140
+ * "who is this waiting on" answer. Empty when the task is unassigned. */
141
+ export declare function assigneesOf(
142
+ stage: StageEntry | undefined,
143
+ taskName: string,
144
+ ): Assignee[];
145
+
123
146
  /**
124
147
  * Persisted document and effect-queue `_type` discriminators.
125
148
  */
@@ -6497,6 +6520,30 @@ declare const AuthoringWorkflowSchema: v.StrictObjectSchema<
6497
6520
  undefined
6498
6521
  >;
6499
6522
 
6523
+ export declare interface AvailableAction {
6524
+ task: string;
6525
+ taskStatus: TaskStatus;
6526
+ action: string;
6527
+ title: string | undefined;
6528
+ allowed: boolean;
6529
+ disabledReason: DisabledReason | undefined;
6530
+ params: ActionParam[];
6531
+ }
6532
+
6533
+ /** Flatten an evaluation's current-stage tasks into the actions the actor
6534
+ * could fire, each carrying whether it's allowed (and why not). */
6535
+ export declare function availableActions(
6536
+ tasks: TaskEvaluation[],
6537
+ ): AvailableAction[];
6538
+
6539
+ /** The `workflow.availableActions` result — the projected actions plus the
6540
+ * evaluation they came from, so a consumer can read the instance/stage
6541
+ * context without a second projection. */
6542
+ export declare interface AvailableActionsResult {
6543
+ evaluation: WorkflowEvaluation;
6544
+ actions: AvailableAction[];
6545
+ }
6546
+
6500
6547
  declare type Bound<
6501
6548
  Args extends {
6502
6549
  client: WorkflowClient;
@@ -6612,6 +6659,13 @@ export declare interface CompleteEffectArgs extends OperationArgs {
6612
6659
  durationMs?: number;
6613
6660
  }
6614
6661
 
6662
+ /** Compare each definition against the deployed doc at its own docId. */
6663
+ export declare function computeDiffEntries(
6664
+ client: Pick<WorkflowClient, "getDocument">,
6665
+ defs: WorkflowDefinition[],
6666
+ target: DeployTarget,
6667
+ ): Promise<DiffEntry[]>;
6668
+
6615
6669
  /**
6616
6670
  * Thrown when a `fireAction` commit loses the optimistic-locking race on
6617
6671
  * all {@link CONCURRENT_FIRE_ACTION_MAX_ATTEMPTS} attempts — every reload +
@@ -6718,6 +6772,49 @@ export declare function datasetResourceParts(id: string): {
6718
6772
  */
6719
6773
  export declare const defaultLoggerFactory: LoggerFactory;
6720
6774
 
6775
+ export declare interface DeleteDefinitionArgs {
6776
+ client: WorkflowClient;
6777
+ /** Engine-scope tags — required. See `validateTags` + `tags.ts`. */
6778
+ tags: string[];
6779
+ /** The Sanity resource the engine's own data lives in. */
6780
+ workflowResource: WorkflowResource;
6781
+ /** Cross-resource routing — see `StartInstanceArgs.resourceClients`. */
6782
+ resourceClients?: ResourceClientResolver;
6783
+ /** The definition's `name` — delete addresses the workflow, not a doc id. */
6784
+ definition: string;
6785
+ /** Delete only this deployed version. Default: every deployed version. */
6786
+ version?: number;
6787
+ /**
6788
+ * Abort every non-terminal instance pinned to the targeted versions
6789
+ * before deleting. Instances are ONLY aborted — instance documents are
6790
+ * never deleted; they stay in the lake as the audit record. Without
6791
+ * this flag the delete refuses when such instances exist.
6792
+ */
6793
+ cascade?: boolean;
6794
+ /** Free-text reason stamped on each cascade-abort history entry. */
6795
+ reason?: string;
6796
+ /** Optional access-state override — see `StartInstanceArgs.access`. */
6797
+ access?: WorkflowAccessOverride;
6798
+ /** URL path for grant resolution — see `StartInstanceArgs.grantsFromPath`. */
6799
+ grantsFromPath?: string;
6800
+ }
6801
+
6802
+ export declare interface DeleteDefinitionResult {
6803
+ /** The definition's `name`. */
6804
+ name: string;
6805
+ /** Versions actually removed, newest first. */
6806
+ deletedVersions: number[];
6807
+ /** Instances hard-stopped by `cascade`, in abort order. Empty without it. */
6808
+ abortedInstanceIds: string[];
6809
+ /**
6810
+ * Orphaned guard docs removed across the definition's statically-named
6811
+ * datasources. Only populated when the LAST version goes — guard docs
6812
+ * are stamped with the version-less `name`, so they stay while any
6813
+ * version remains deployed.
6814
+ */
6815
+ deletedGuardCount: number;
6816
+ }
6817
+
6721
6818
  /**
6722
6819
  * Run every guard whose `match` applies and collect those that DENY. Empty
6723
6820
  * result ⇒ allowed (optimistically).
@@ -6771,6 +6868,103 @@ export declare interface DeployDefinitionsResult {
6771
6868
  */
6772
6869
  export declare function deployStageGuards(args: StageGuardArgs): Promise<void>;
6773
6870
 
6871
+ /** Where a definition deploys: the engine tags it partitions under, and the
6872
+ * workflow resource it belongs to. Structurally what `deployDefinitions`
6873
+ * receives, minus the definitions themselves. */
6874
+ export declare interface DeployTarget {
6875
+ tags: string[];
6876
+ workflowResource: WorkflowResource;
6877
+ }
6878
+
6879
+ /**
6880
+ * The slice of an instance + its {@link WorkflowEvaluation} the classifier
6881
+ * reads. Narrowed so callers (and tests) can craft an exact stuck permutation
6882
+ * without standing up a full evaluation; {@link diagnoseInputFromEvaluation}
6883
+ * builds it from a real {@link WorkflowEvaluation}.
6884
+ */
6885
+ export declare interface DiagnoseInput {
6886
+ instance: Pick<
6887
+ WorkflowInstance,
6888
+ | "currentStage"
6889
+ | "stages"
6890
+ | "completedAt"
6891
+ | "abortedAt"
6892
+ | "effectHistory"
6893
+ | "pendingEffects"
6894
+ | "history"
6895
+ >;
6896
+ tasks: TaskEvaluation[];
6897
+ transitions: TransitionEvaluation[];
6898
+ }
6899
+
6900
+ /** Narrow a full {@link WorkflowEvaluation} to the {@link DiagnoseInput} the
6901
+ * classifier reads. */
6902
+ export declare function diagnoseInputFromEvaluation(
6903
+ evaluation: WorkflowEvaluation,
6904
+ ): DiagnoseInput;
6905
+
6906
+ /**
6907
+ * Classify an instance. Terminal states win first; then the genuine stuck
6908
+ * causes (nothing advances on its own or via a normal action); then `waiting`
6909
+ * (an action is available — healthy); else `progressing` (a transition is
6910
+ * already satisfied and will cascade).
6911
+ */
6912
+ export declare function diagnoseInstance(input: DiagnoseInput): Diagnosis;
6913
+
6914
+ /** The `workflow.diagnose` result — the verdict plus the evaluation it was
6915
+ * derived from, so a consumer can render the supporting evidence (current
6916
+ * stage tasks + transitions) without a second projection. */
6917
+ export declare interface DiagnoseResult {
6918
+ evaluation: WorkflowEvaluation;
6919
+ diagnosis: Diagnosis;
6920
+ }
6921
+
6922
+ export declare type Diagnosis =
6923
+ | {
6924
+ state: "progressing";
6925
+ }
6926
+ | {
6927
+ state: "waiting";
6928
+ task: string;
6929
+ assignees: Assignee[];
6930
+ actions: string[];
6931
+ }
6932
+ | {
6933
+ state: "completed";
6934
+ at: string;
6935
+ }
6936
+ | {
6937
+ state: "aborted";
6938
+ at: string;
6939
+ reason?: string;
6940
+ }
6941
+ | {
6942
+ state: "stuck";
6943
+ cause: StuckCause;
6944
+ };
6945
+
6946
+ export declare interface DiffEntry {
6947
+ name: string;
6948
+ version: number;
6949
+ status: "create" | "update" | "unchanged";
6950
+ docId: string;
6951
+ expected: Record<string, unknown>;
6952
+ existing?: Record<string, unknown>;
6953
+ }
6954
+
6955
+ /**
6956
+ * Compare one local definition against a deployed document (or its
6957
+ * absence), classifying it as create / update / unchanged using the
6958
+ * engine's own no-op verdict. Pure — callers own the fetch, so the same
6959
+ * comparison serves both deploy's exact-docId lookup and `definition
6960
+ * diff`'s latest-version query.
6961
+ */
6962
+ export declare function diffEntry(
6963
+ def: WorkflowDefinition,
6964
+ existingRaw: Record<string, unknown> | undefined,
6965
+ target: DeployTarget,
6966
+ ): DiffEntry;
6967
+
6774
6968
  export declare type DisabledReason =
6775
6969
  | {
6776
6970
  /**
@@ -7060,10 +7254,22 @@ export declare interface Engine {
7060
7254
  evaluateInstance: (
7061
7255
  args: Bound<EvaluateArgs>,
7062
7256
  ) => ReturnType<typeof evaluateInstance>;
7257
+ /** Diagnose why an instance is or isn't progressing — a classified
7258
+ * {@link DiagnoseResult} plus the evaluation it was derived from. */
7259
+ diagnose: (args: Bound<EvaluateArgs>) => Promise<DiagnoseResult>;
7260
+ /** The actions firable on the instance's current stage, each flagged
7261
+ * allowed/disabled, plus the evaluation they came from. */
7262
+ availableActions: (
7263
+ args: Bound<EvaluateArgs>,
7264
+ ) => Promise<AvailableActionsResult>;
7063
7265
  /** Admin override — bypass filters/transitions and force the stage. */
7064
7266
  setStage: (args: Bound<SetStageArgs>) => Promise<DispatchResult>;
7065
7267
  /** Admin override — hard-stop an in-flight instance where it stands. */
7066
7268
  abortInstance: (args: Bound<AbortInstanceArgs>) => Promise<DispatchResult>;
7269
+ /** Admin override — remove a deployed definition (instances are only ever aborted, never deleted). */
7270
+ deleteDefinition: (
7271
+ args: Bound<DeleteDefinitionArgs>,
7272
+ ) => Promise<DeleteDefinitionResult>;
7067
7273
  /** Fetch a workflow instance by id. */
7068
7274
  getInstance: (args: { instanceId: string }) => Promise<WorkflowInstance>;
7069
7275
  /** The reactive {@link WatchSet} for an instance — every document whose
@@ -8160,6 +8366,12 @@ export declare interface OpAppliedSummary {
8160
8366
  resolved?: Record<string, unknown>;
8161
8367
  }
8162
8368
 
8369
+ /** The instance's current, not-yet-exited {@link StageEntry}, if it has one —
8370
+ * the engine's canonical {@link findOpenStageEntry}, over the diagnosis input. */
8371
+ export declare function openStage(
8372
+ instance: Pick<WorkflowInstance, "currentStage" | "stages">,
8373
+ ): StageEntry | undefined;
8374
+
8163
8375
  export declare interface OperationArgs {
8164
8376
  client: WorkflowClient;
8165
8377
  /** Engine-scope tags — required. */
@@ -11417,6 +11629,31 @@ export declare function stripSystemFields(
11417
11629
  doc: Record<string, unknown>,
11418
11630
  ): Record<string, unknown>;
11419
11631
 
11632
+ /**
11633
+ * Why an in-flight instance is genuinely blocked — nothing advances it on its
11634
+ * own OR via a normal action. Ordered most- to least-actionable in
11635
+ * {@link diagnoseInstance}: a failed effect is the root cause even when it left
11636
+ * its task looking merely "active", so it wins over the task- and
11637
+ * transition-level symptoms it produces. Note an active task awaiting a human
11638
+ * action is NOT here — that's the healthy {@link Diagnosis} `waiting` state.
11639
+ */
11640
+ export declare type StuckCause =
11641
+ | {
11642
+ kind: "failed-effect";
11643
+ effect: EffectHistoryEntry;
11644
+ }
11645
+ | {
11646
+ kind: "hung-effect";
11647
+ effect: PendingEffect;
11648
+ }
11649
+ | {
11650
+ kind: "failed-task";
11651
+ task: string;
11652
+ }
11653
+ | {
11654
+ kind: "no-transition-fires";
11655
+ };
11656
+
11420
11657
  /**
11421
11658
  * A document the reactive layer should subscribe to, with its GDR
11422
11659
  * exploded so consumers never re-parse: the resource-addressing parts
@@ -11729,6 +11966,16 @@ export declare const workflow: {
11729
11966
  deployDefinitions: (
11730
11967
  args: DeployDefinitionsArgs,
11731
11968
  ) => Promise<DeployDefinitionsResult>;
11969
+ /**
11970
+ * Remove a deployed workflow definition (all versions, or one via
11971
+ * `version`). Refuses while non-terminal instances exist unless
11972
+ * `cascade` aborts them first — instances are never deleted, only
11973
+ * aborted in place; see {@link deleteDefinitionInternal} for the
11974
+ * full contract (spawn-referrer check, guard-doc housekeeping).
11975
+ */
11976
+ deleteDefinition: (
11977
+ args: Clocked<DeleteDefinitionArgs>,
11978
+ ) => Promise<DeleteDefinitionResult>;
11732
11979
  /**
11733
11980
  * Spawn a new workflow instance from a deployed definition.
11734
11981
  *
@@ -11779,10 +12026,9 @@ export declare const workflow: {
11779
12026
  setStage: (args: Clocked<SetStageArgs>) => Promise<DispatchResult>;
11780
12027
  /**
11781
12028
  * Admin override — hard-stop an in-flight instance where it stands.
11782
- * No stage move, no transition effects, pending effects cancelled; see
11783
- * the engine-level {@link engineAbortInstance} for the full contract.
11784
- * Ancestors are propagated (not cascaded — the instance is terminal)
11785
- * so a parent gate waiting on this child re-evaluates.
12029
+ * No stage move, no transition effects, pending effects cancelled;
12030
+ * see {@link abortAndPropagate} for the abort + ancestor-propagation
12031
+ * contract (propagated, not cascaded — the instance is terminal).
11786
12032
  */
11787
12033
  abortInstance: (args: Clocked<AbortInstanceArgs>) => Promise<DispatchResult>;
11788
12034
  /**
@@ -11887,6 +12133,26 @@ export declare const workflow: {
11887
12133
  * `fireAction` to gate writes via the same logic.
11888
12134
  */
11889
12135
  evaluate: (args: Clocked<EvaluateArgs>) => Promise<WorkflowEvaluation>;
12136
+ /**
12137
+ * Diagnose why an instance is or isn't progressing. Projects the
12138
+ * instance (the same read as `evaluate`) and classifies it — terminal,
12139
+ * `progressing`, `waiting` (an action is available — healthy), or `stuck`
12140
+ * with a structured cause — returning that verdict as a
12141
+ * {@link DiagnoseResult} alongside the evaluation it came from, so a
12142
+ * consumer can render the supporting evidence without a second projection.
12143
+ * Pure read.
12144
+ */
12145
+ diagnose: (args: Clocked<EvaluateArgs>) => Promise<DiagnoseResult>;
12146
+ /**
12147
+ * List the actions an actor could fire on an instance's current stage,
12148
+ * each flagged `allowed` (with a structured `disabledReason` when not).
12149
+ * Projects the instance from the actor's perspective and flattens its
12150
+ * tasks' actions. Returns the evaluation alongside the actions so a
12151
+ * consumer can read the instance/stage context. Pure read.
12152
+ */
12153
+ availableActions: (
12154
+ args: Clocked<EvaluateArgs>,
12155
+ ) => Promise<AvailableActionsResult>;
11890
12156
  /**
11891
12157
  * Materialised spawned children of a parent instance.
11892
12158
  *
@@ -14041,6 +14307,15 @@ export declare interface WorkflowTransaction {
14041
14307
  * clients drain it structurally.
14042
14308
  */
14043
14309
  patch: (patch: any) => WorkflowTransaction;
14310
+ /**
14311
+ * Queue a document delete. Deliberately a transaction-only capability —
14312
+ * the top-level client surface stays delete-free so no engine code path
14313
+ * can casually remove documents; the sole consumer is `deleteDefinition`
14314
+ * housekeeping (definition docs + orphaned guard docs). Both the real
14315
+ * `@sanity/client` Transaction and the test fake's TransactionHandle
14316
+ * carry this shape.
14317
+ */
14318
+ delete: (id: string) => WorkflowTransaction;
14044
14319
  commit: () => Promise<unknown>;
14045
14320
  }
14046
14321