@sanity/workflow-engine-test 0.10.0 → 0.12.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.ts CHANGED
@@ -1,46 +1,45 @@
1
1
  import type { AbortInstanceArgs } from "@sanity/workflow-engine";
2
2
  import { ActivityStatus } from "@sanity/workflow-engine";
3
3
  import { Actor } from "@sanity/workflow-engine";
4
- import { CandidateDocument } from "@sanity/workflow-engine";
4
+ import { ChildrenArgs } from "@sanity/workflow-engine";
5
5
  import { Clock } from "@sanity/workflow-engine";
6
6
  import type { CompleteEffectArgs } from "@sanity/workflow-engine";
7
7
  import { CreateEngineArgs } from "@sanity/workflow-engine";
8
8
  import type { DeclaredExecutionContext } from "@sanity/workflow-engine";
9
+ import { DefinitionsForDocumentArgs } from "@sanity/workflow-engine";
10
+ import type { DeleteDefinitionArgs } from "@sanity/workflow-engine";
11
+ import { DeleteDefinitionResult } from "@sanity/workflow-engine";
9
12
  import { DeployDefinitionsArgs } from "@sanity/workflow-engine";
10
13
  import { DeployDefinitionsResult } from "@sanity/workflow-engine";
11
14
  import { DeployedDefinition } from "@sanity/workflow-engine";
12
15
  import type { EditFieldArgs } from "@sanity/workflow-engine";
13
16
  import { Engine } from "@sanity/workflow-engine";
14
17
  import type { EvaluateArgs } from "@sanity/workflow-engine";
18
+ import { EvaluateStartArgs } from "@sanity/workflow-engine";
19
+ import { FindPendingEffectsArgs } from "@sanity/workflow-engine";
15
20
  import type { FireActionArgs } from "@sanity/workflow-engine";
16
- import { GdrUri } from "@sanity/workflow-engine";
17
21
  import type { Grant } from "@sanity/workflow-engine";
18
22
  import { InitialFieldValue } from "@sanity/workflow-engine";
23
+ import { InstanceRefArgs } from "@sanity/workflow-engine";
24
+ import { InstancesForDocumentArgs } from "@sanity/workflow-engine";
25
+ import { InstancesQueryFilter } from "@sanity/workflow-engine";
19
26
  import { MutationGuardAction } from "@sanity/workflow-engine";
20
27
  import { MutationGuardDoc } from "@sanity/workflow-engine";
21
28
  import type { OperationArgs } from "@sanity/workflow-engine";
22
29
  import { OperationResult } from "@sanity/workflow-engine";
23
30
  import { PendingEffect } from "@sanity/workflow-engine";
31
+ import type { ResourceClientResolver } from "@sanity/workflow-engine";
24
32
  import type { SetStageArgs } from "@sanity/workflow-engine";
33
+ import { StartEvaluation } from "@sanity/workflow-engine";
25
34
  import type { StartInstanceArgs } from "@sanity/workflow-engine";
35
+ import type { TestClient } from "@sanity-labs/client-fake-for-test";
26
36
  import type { WorkflowAccess } from "@sanity/workflow-engine";
37
+ import { WorkflowDefinition } from "@sanity/workflow-engine";
27
38
  import { WorkflowDefinitionInput } from "@sanity/workflow-engine";
28
39
  import { WorkflowEvaluation } from "@sanity/workflow-engine";
29
40
  import { WorkflowInstance } from "@sanity/workflow-engine";
30
41
  import type { WorkflowResource } from "@sanity/workflow-engine";
31
42
 
32
- declare type Action = ReleaseAction | VersionAction;
33
-
34
- /** Options for `action()`. Only `tag` is meaningful to the fake. */
35
- declare interface ActionOptions {
36
- /** Request tag, recorded on the {@link CallRecord} (`null` = explicit suppression). */
37
- tag?: string | null;
38
- }
39
-
40
- declare interface ActionResult {
41
- transactionId: string;
42
- }
43
-
44
43
  /**
45
44
  * The bench's default identity — wildcard user + wildcard grants,
46
45
  * registered as the default token on the bench's fake client so the
@@ -94,6 +93,9 @@ declare type BenchArgsWithGrants<T> = BenchArgs<T> & {
94
93
 
95
94
  declare type BenchCompleteEffectArgs = BenchArgs<CompleteEffectArgs>;
96
95
 
96
+ declare type BenchDeleteDefinitionArgs =
97
+ BenchArgsWithGrants<DeleteDefinitionArgs>;
98
+
97
99
  declare type BenchEditFieldArgs = BenchArgsWithGrants<EditFieldArgs>;
98
100
 
99
101
  declare type BenchEvaluateArgs = BenchArgsWithGrants<EvaluateArgs>;
@@ -112,6 +114,9 @@ declare interface BenchState {
112
114
  tag: string;
113
115
  /** The workflow resource every wrapped call targets. */
114
116
  workflowResource: WorkflowResource;
117
+ /** The cross-resource read routing every wrapped call carries, when the
118
+ * bench declared any — see {@link CreateBenchOptions.resourceClients}. */
119
+ resourceClients?: ResourceClientResolver;
115
120
  /** The declared execution context stamped on every wrapped call's history
116
121
  * entries (default `{kind: 'test'}`) — inherited by {@link createBenchEngine}. */
117
122
  executionContext: DeclaredExecutionContext;
@@ -146,8 +151,7 @@ declare interface BenchState {
146
151
  * Freeze the bench clock at an explicit ISO instant. Every subsequent
147
152
  * engine call (`startInstance` / `fireAction` / `tick` / `completeEffect`
148
153
  * / `evaluate`) sees this as `$now` and stamps it on history, so
149
- * time-based `completeWhen` / `failWhen` / SLA predicates are
150
- * deterministic.
154
+ * time-based `when` triggers and SLA predicates are deterministic.
151
155
  */
152
156
  setNow: (iso: string) => void;
153
157
  /**
@@ -160,28 +164,6 @@ declare interface BenchState {
160
164
 
161
165
  declare type BenchTickArgs = BenchArgs<OperationArgs>;
162
166
 
163
- /**
164
- * One recorded invocation of a client method — the spy layer behind
165
- * `client.calls()` / `client.callCount()`. `result` holds the resolved
166
- * value (awaited, for async methods); `error` holds the thrown/rejected
167
- * value. Builder-returning methods (`patch`, `transaction`) record the
168
- * builder as `result`; their eventual writes show up in the mutation log.
169
- */
170
- declare interface CallRecord {
171
- /** Method name; namespace methods are dotted, e.g. `releases.create`. */
172
- method: string;
173
- /** Arguments as passed (stored by reference, like `vi.fn`). */
174
- args: unknown[];
175
- /** Resolved/returned value, set once the call settles successfully. */
176
- result?: unknown;
177
- /** Thrown or rejected value, set if the call failed. */
178
- error?: unknown;
179
- /** Request tag from the call's options, when one was passed (`null` = explicit suppression). */
180
- tag?: string | null;
181
- projectId: string;
182
- dataset: string;
183
- }
184
-
185
167
  /** Helpers composed across the wrapper + read groups at bench assembly. */
186
168
  declare interface ComposedHelpers {
187
169
  /**
@@ -259,6 +241,23 @@ export declare interface CreateBenchOptions {
259
241
  * URIs read as `dataset:test:test:<docId>`.
260
242
  */
261
243
  workflowResource?: WorkflowResource;
244
+ /**
245
+ * Sibling datasets the bench serves (and thereby declares) from its own
246
+ * store — the common cross-resource case. Each listed resource routes to
247
+ * a `withConfig`-derived handle over the bench's store, so reads reach it
248
+ * and runtime-supplied refs into it are admitted. Only `dataset`
249
+ * resources can be siblings of the bench's store; anything else needs
250
+ * {@link CreateBenchOptions.resourceClients}.
251
+ */
252
+ serveResources?: WorkflowResource[];
253
+ /**
254
+ * Cross-resource read routing for the bench engine scope — a served
255
+ * resource is also part of the declared surface for written refs. The
256
+ * escape hatch for resolver logic beyond same-store siblings; prefer
257
+ * {@link CreateBenchOptions.serveResources}, which is consulted first
258
+ * when both are given. Omit for single-resource benches.
259
+ */
260
+ resourceClients?: ResourceClientResolver;
262
261
  /**
263
262
  * Freeze the bench clock at this ISO instant from construction, so
264
263
  * `$now` and every engine timestamp are deterministic. Equivalent to
@@ -274,35 +273,17 @@ export declare interface CreateBenchOptions {
274
273
  executionContext?: DeclaredExecutionContext;
275
274
  }
276
275
 
277
- declare interface CurrentUser {
278
- id: string;
279
- name?: string;
280
- email?: string;
281
- /** @deprecated singular `role` on legacy responses */
282
- role?: string;
283
- roles?: {
284
- name: string;
285
- title?: string;
286
- description?: string;
287
- }[];
288
- }
289
-
290
276
  /** Default workflow resource for benches that don't pass one. */
291
277
  export declare const DEFAULT_WORKFLOW_RESOURCE: WorkflowResource;
292
278
 
293
279
  /**
294
- * Document body accepted by `version.create` / `createVersion`. The id
295
- * is derived from `releaseId + publishedId` so callers may omit `_id`
296
- * here. `version.replace` requires `_id` since it identifies the doc
297
- * to replace.
280
+ * The write {@link GuardHelpers.editDocument} performs. Every arm is a real
281
+ * distinct client write the guard seam vets under that action `create` is
282
+ * deliberately absent because editing addresses an existing document; create
283
+ * through the client (`bench.client.create` / `clientFor(...).create`), which
284
+ * the same seam gates as a create.
298
285
  */
299
- declare interface DocumentBody {
300
- _id?: string;
301
- _type: string;
302
- [field: string]: unknown;
303
- }
304
-
305
- declare type DocumentValuePermission = "create" | "read" | "update";
286
+ export declare type EditDocumentAction = Exclude<MutationGuardAction, "create">;
306
287
 
307
288
  /**
308
289
  * How {@link GuardHelpers.editDocument} mutates the document. Each present
@@ -324,6 +305,7 @@ declare type EngineScope = {
324
305
  client: TestClient;
325
306
  tag: string;
326
307
  workflowResource: WorkflowResource;
308
+ resourceClients?: ResourceClientResolver;
327
309
  executionContext: DeclaredExecutionContext;
328
310
  };
329
311
 
@@ -332,6 +314,9 @@ declare type EngineWrappers = {
332
314
  deployDefinitions: <T extends WorkflowDefinitionInput<T>>(
333
315
  args: DeployDefinitionsArgs<T>,
334
316
  ) => Promise<DeployDefinitionsResult>;
317
+ deleteDefinition: (
318
+ args: BenchDeleteDefinitionArgs,
319
+ ) => Promise<DeleteDefinitionResult>;
335
320
  startInstance: (args: BenchStartInstanceArgs) => Promise<OperationResult>;
336
321
  fireAction: (args: BenchFireActionArgs) => Promise<OperationResult>;
337
322
  editField: (args: BenchEditFieldArgs) => Promise<OperationResult>;
@@ -346,95 +331,18 @@ declare type EngineWrappers = {
346
331
  guardsForDefinition: (definition: string) => Promise<MutationGuardDoc[]>;
347
332
  };
348
333
 
349
- /**
350
- * A single mutation in the raw array form accepted by `mutate()` — the
351
- * same shape `@sanity/client` accepts. The inline `patch` form mirrors
352
- * the real client's keyed patch object.
353
- */
354
- declare type FakeMutation =
355
- | {
356
- create: SanityDocumentLike;
357
- }
358
- | {
359
- createOrReplace: SanityDocumentLike;
360
- }
361
- | {
362
- createIfNotExists: SanityDocumentLike;
363
- }
364
- | {
365
- delete: {
366
- id: string;
367
- };
368
- }
369
- | {
370
- patch: {
371
- id: string;
372
- ifRevisionID?: string;
373
- set?: Record<string, unknown>;
374
- setIfMissing?: Record<string, unknown>;
375
- unset?: string[];
376
- inc?: Record<string, number>;
377
- dec?: Record<string, number>;
378
- insert?: {
379
- before?: string;
380
- after?: string;
381
- replace?: string;
382
- items: unknown[];
383
- };
384
- };
385
- };
386
-
387
- /**
388
- * Data-plane subset of `@sanity/client`'s surface, with multi-dataset support.
389
- *
390
- * One `createTestClient()` call creates a registry of stores keyed by
391
- * `(projectId, dataset)`. The returned client points at one of them.
392
- * `withConfig({ projectId, dataset })` returns a new handle into the same
393
- * registry but pointing at a different store — useful for workflows where
394
- * definitions live in one dataset and instances live in another.
395
- *
396
- * Two separate `createTestClient()` calls have separate registries, so
397
- * tests stay isolated from each other.
398
- *
399
- * Methods not on the real client:
400
- *
401
- * - `seed`, `add`, `reset`, `snapshot`, `store` — bulk-load helpers,
402
- * scoped to the current dataset.
403
- * - `simulateConcurrentEdit()` — force the next N revision-guarded writes
404
- * to a document to 409, for optimistic-locking retry tests.
405
- * - `datasets()` — lists known dataset keys in this registry.
406
- *
407
- * Methods deliberately omitted:
408
- *
409
- * - Project / agent / asset / live / listen APIs.
410
- */
411
- declare interface FetchOptions {
412
- /** Override the client's configured perspective for this read. */
413
- perspective?: Perspective;
414
- /**
415
- * When `false`, resolve to a {@link RawQueryResponse} (`{ query, ms,
416
- * result }`) instead of the bare result — mirrors `@sanity/client`.
417
- * Defaults to `true`.
418
- */
419
- filterResponse?: boolean;
420
- /**
421
- * With `filterResponse: false`, set to `false` to omit `query` from the
422
- * raw response. Ignored when `filterResponse` is not `false`.
423
- */
424
- returnQuery?: boolean;
425
- /**
426
- * Request tag, recorded on the {@link CallRecord} for assertions.
427
- * `null` mirrors `@sanity/client`'s explicit-suppression form and is
428
- * recorded as-is. No other behavior is simulated.
429
- */
430
- tag?: string | null;
431
- /** Accepted for signature parity; not simulated. */
432
- signal?: AbortSignal;
433
- }
434
-
435
- declare interface Grant_2 {
436
- filter: string;
437
- permissions: DocumentValuePermission[];
334
+ /** The slice of a bench {@link startedInstanceFixture} drives — structural,
335
+ * so the fixture module needs no import of the bench itself. */
336
+ declare interface FixtureBench {
337
+ deployDefinitions: (args: {
338
+ definitions: WorkflowDefinition[];
339
+ }) => Promise<unknown>;
340
+ startInstance: (args: {
341
+ definition: string;
342
+ instanceId?: string;
343
+ }) => Promise<{
344
+ instance: WorkflowInstance;
345
+ }>;
438
346
  }
439
347
 
440
348
  declare type GuardHelpers = {
@@ -454,137 +362,28 @@ declare type GuardHelpers = {
454
362
  * client gates the write against deployed guards (incl. `identity()`) and
455
363
  * throws `MutationGuardDeniedError` on denial — the lake's intended
456
364
  * rejection, simulated by the bench. `as` routes the write through a
457
- * token-scoped sibling so `identity()` resolves to that actor. A `patch`
458
- * ({@link EditDocumentPatch}) is required for edits and rejected for
459
- * `action: 'delete'` the combination is contradictory input.
365
+ * token-scoped sibling so `identity()` resolves to that actor.
366
+ *
367
+ * Each {@link EditDocumentAction} performs that real write, so a guard
368
+ * scoped to it is genuinely consulted: `update` (default) applies `patch`;
369
+ * `delete` deletes; `publish` promotes the existing draft
370
+ * (`drafts.<documentId>`) onto `documentId`; `unpublish` retracts
371
+ * `documentId` into its draft. `documentId` is always the published id.
372
+ * A `patch` ({@link EditDocumentPatch}) is required for `update` and
373
+ * rejected for every other action — the combination is contradictory input.
374
+ *
375
+ * Resolves to the post-write document as the DEFAULT identity reads it —
376
+ * `undefined` after `delete`/`unpublish` (the doc is gone), or when the
377
+ * default identity cannot read the written doc.
460
378
  */
461
379
  editDocument: (opts: {
462
380
  documentId: string;
463
381
  patch?: EditDocumentPatch;
464
- action?: MutationGuardAction;
382
+ action?: EditDocumentAction;
465
383
  as?: Actor;
466
- }) => Promise<StoreDocument>;
384
+ }) => Promise<StoreDocument | undefined>;
467
385
  };
468
386
 
469
- /** Result of a mutation when ids are requested for all affected documents. */
470
- declare interface MultipleMutationResult {
471
- transactionId: string;
472
- documentIds: string[];
473
- results: {
474
- id: string;
475
- operation: MutationOperation;
476
- }[];
477
- }
478
-
479
- /** Append-only record of one committed transaction (a single mutation is a 1-entry transaction). */
480
- declare interface MutationLogEntry {
481
- transactionId: string;
482
- projectId: string;
483
- dataset: string;
484
- mutations: TxnMutation[];
485
- /** Request tag the commit carried, when one was passed (`null` = explicit suppression). */
486
- tag?: string | null;
487
- }
488
-
489
- /** The operation recorded per affected document, matching `@sanity/client`. */
490
- declare type MutationOperation = "create" | "update" | "delete" | "none";
491
-
492
- declare interface MutationOptions {
493
- ifRevisionId?: string;
494
- /**
495
- * Shape the resolved value, mirroring `@sanity/client`:
496
- * - default → the mutated document
497
- * - `returnDocuments: false` → an id-shaped mutation result
498
- * ({@link SingleMutationResult} / {@link MultipleMutationResult})
499
- * - `returnFirst: false` with documents → an array
500
- *
501
- * The static return type reflects the default (the document); pass a
502
- * type argument or cast when requesting an id-shaped result.
503
- */
504
- returnDocuments?: boolean;
505
- returnFirst?: boolean;
506
- /** Override the synthetic transaction id used in the result and log. */
507
- transactionId?: string;
508
- /** When `true`, validate + shape the result but don't persist. */
509
- dryRun?: boolean;
510
- /** Accepted for signature parity; not simulated. */
511
- visibility?: "sync" | "async" | "deferred";
512
- /** Accepted for signature parity; not simulated. */
513
- autoGenerateArrayKeys?: boolean;
514
- /**
515
- * Request tag, recorded on the {@link CallRecord} and the
516
- * {@link MutationLogEntry} for assertions. `null` mirrors
517
- * `@sanity/client`'s explicit-suppression form and is recorded as-is.
518
- * No other behavior is simulated.
519
- */
520
- tag?: string | null;
521
- }
522
-
523
- /** Options for `PatchHandle.commit()`. Only `tag` is meaningful to the fake. */
524
- declare interface PatchCommitOptions {
525
- /** Request tag, recorded on the mutation log entry (`null` = explicit suppression). */
526
- tag?: string | null;
527
- /** Accepted for signature parity; not simulated. */
528
- visibility?: "sync" | "async" | "deferred";
529
- }
530
-
531
- declare interface PatchHandle {
532
- set: (props: Record<string, unknown>) => PatchHandle;
533
- setIfMissing: (props: Record<string, unknown>) => PatchHandle;
534
- unset: (paths: string[]) => PatchHandle;
535
- inc: (props: Record<string, number>) => PatchHandle;
536
- dec: (props: Record<string, number>) => PatchHandle;
537
- insert: (
538
- position: "before" | "after" | "replace",
539
- anchor: string,
540
- items: unknown[],
541
- ) => PatchHandle;
542
- ifRevisionId: (rev: string) => PatchHandle;
543
- commit: (options?: PatchCommitOptions) => Promise<SanityDocument>;
544
- /** Test-only introspection — not on real Patch */
545
- toJSON: () => {
546
- id: string;
547
- ops: PatchOp[];
548
- ifRev?: string;
549
- };
550
- }
551
-
552
- /**
553
- * Patch operations the test client supports.
554
- *
555
- * Subset of `@sanity/client`'s PatchOperations. Each entry is one
556
- * mutation step applied in order at commit time.
557
- */
558
- declare type PatchOp =
559
- | {
560
- kind: "set";
561
- props: Record<string, unknown>;
562
- }
563
- | {
564
- kind: "setIfMissing";
565
- props: Record<string, unknown>;
566
- }
567
- | {
568
- kind: "unset";
569
- paths: string[];
570
- }
571
- | {
572
- kind: "inc";
573
- props: Record<string, number>;
574
- }
575
- | {
576
- kind: "dec";
577
- props: Record<string, number>;
578
- }
579
- | {
580
- kind: "insert";
581
- position: "before" | "after" | "replace";
582
- anchor: string;
583
- items: unknown[];
584
- };
585
-
586
- declare type Perspective = "raw" | "published" | "drafts" | string[];
587
-
588
387
  declare type QueryHelpers = {
589
388
  /**
590
389
  * Run an arbitrary GROQ query through `workflow.query`. The query MUST
@@ -598,11 +397,13 @@ declare type QueryHelpers = {
598
397
  ) => Promise<T>;
599
398
  /**
600
399
  * Snapshot-aware GROQ for `instanceId` — runs against the same
601
- * in-memory view the engine's filters see. Reserved params (`$self`,
602
- * `$subject`, `$parent`, `$ancestors`, `$now`, `$stage`) auto-bound
603
- * in GDR URI form. Use to ask "what does the engine see for this
604
- * workflow?" useful for filter debugging, UIs that mirror engine
605
- * state, etc.
400
+ * in-memory view the engine's filters see, with the caller-free
401
+ * rendered scope auto-bound: every always-bound condition var,
402
+ * `$fields` with the open stage's overlay merged, `$assigned` at its
403
+ * caller-free `false`, and the author's pre-evaluated `$<predicate>`
404
+ * booleans, ids in GDR URI form. Use to ask "what does the engine see
405
+ * for this workflow?" — useful for filter debugging, UIs that mirror
406
+ * engine state, etc.
606
407
  */
607
408
  queryInScope: <T = unknown>({
608
409
  instanceId,
@@ -616,34 +417,35 @@ declare type QueryHelpers = {
616
417
  };
617
418
 
618
419
  declare type ReadHelpers = {
619
- /** Read the workflow instance by id. Throws if missing. */
620
- getInstance: (instanceId: string) => Promise<WorkflowInstance>;
420
+ /**
421
+ * The engine's instance point-read: throws typed `InstanceNotFoundError`
422
+ * when the doc is missing or belongs to another engine's tag partition.
423
+ */
424
+ getInstance: (args: InstanceRefArgs) => Promise<WorkflowInstance>;
621
425
  /** Current stage name of an instance. */
622
426
  currentStage: (instanceId: string) => Promise<string>;
623
- /** Pending effects on an instance. */
624
- pendingEffects: (instanceId: string) => Promise<PendingEffect[]>;
427
+ /** Every pending effect on an instance — claimed and unclaimed alike. */
428
+ listPendingEffects: (args: InstanceRefArgs) => Promise<PendingEffect[]>;
429
+ /** Pending effects filtered by claim presence and/or effect names. */
430
+ findPendingEffects: (
431
+ args: FindPendingEffectsArgs,
432
+ ) => Promise<PendingEffect[]>;
625
433
  /** Status of a single activity on an instance, or undefined if missing. */
626
434
  activityStatus: (
627
435
  instanceId: string,
628
436
  activity: string,
629
437
  ) => Promise<ActivityStatus | undefined>;
630
- /** The rendered `$effects` map — context entries keyed by `name`,
631
- * completed-effect outputs namespaced under the effect's name. */
632
- effectsContextMap: (instanceId: string) => Promise<Record<string, unknown>>;
438
+ /** The rendered `$context` map — the start-time seed + a parent's spawn
439
+ * handoff, keyed by entry `name`. */
440
+ contextMap: (instanceId: string) => Promise<Record<string, unknown>>;
441
+ /** The rendered `$effects` map — completed effects' outputs, namespaced
442
+ * by effect name. */
443
+ effectOutputsMap: (instanceId: string) => Promise<Record<string, unknown>>;
633
444
  /**
634
- * Spawned children of `parentInstanceId`. If `activity` is provided,
635
- * only children spawned by that specific activity on the parent.
636
- *
637
- * Walks `history.spawned` entries on the parent — the durable record
638
- * (the workflow-scope registry tracks the same children, keyed for
639
- * adoption rather than enumeration).
640
- *
641
- * Returns children ordered by `startedAt` ascending.
445
+ * Spawned children of the instance, optionally restricted to those
446
+ * spawned by `activity`. Ordered by `startedAt` ascending.
642
447
  */
643
- children: (
644
- parentInstanceId: string,
645
- activity?: string,
646
- ) => Promise<WorkflowInstance[]>;
448
+ children: (args: ChildrenArgs) => Promise<WorkflowInstance[]>;
647
449
  /**
648
450
  * All workflow instances whose `doc.ref` field entry named
649
451
  * `entryName` (default `"subject"`) matches the given doc id. Useful
@@ -655,83 +457,45 @@ declare type ReadHelpers = {
655
457
  entryName?: string,
656
458
  ) => Promise<WorkflowInstance[]>;
657
459
  /**
658
- * All instances of `workflowName` currently at `stage`. Includes
659
- * completed instances (`completedAt != null`) by default; set
660
- * `{ openOnly: true }` to filter to in-flight only.
460
+ * All instances of `definition` currently at `stage`. In-flight only by
461
+ * default; `includeCompleted: true` widens to completed/aborted instances.
462
+ * The options derive from the engine's {@link InstancesQueryFilter}, so a
463
+ * vocabulary drift is a compile error here, not a silently-dropped filter.
661
464
  */
662
- instancesByStage: (options: {
663
- workflowName: string;
664
- stage: string;
665
- openOnly?: boolean;
666
- }) => Promise<WorkflowInstance[]>;
465
+ instancesByStage: (
466
+ options: Required<Pick<InstancesQueryFilter, "definition" | "stage">> &
467
+ Pick<InstancesQueryFilter, "includeCompleted">,
468
+ ) => Promise<WorkflowInstance[]>;
667
469
  /**
668
470
  * Every in-flight instance whose reactive watch-set includes `document`
669
471
  * (a resource-qualified GDR URI) — the reverse of the forward watch-set.
670
472
  * Answers "which instances should this changed doc `tick`?" the way a
671
473
  * non-reactive content forwarder needs. Sorted by `startedAt` ascending.
672
474
  */
673
- instancesForDocument: (document: GdrUri) => Promise<WorkflowInstance[]>;
475
+ instancesForDocument: (
476
+ args: InstancesForDocumentArgs,
477
+ ) => Promise<WorkflowInstance[]>;
674
478
  /**
675
479
  * The startable half of {@link ReadHelpers.instancesForDocument}: the
676
480
  * latest deployed version of every definition applicable to the LOADED
677
481
  * candidate document — startable, a required subject entry accepts its
678
- * `_type`, `applicableWhen` passes. All matches, name ascending.
482
+ * `_type`, `start.filter` (browse-time-pure) passes. All matches, name
483
+ * ascending.
679
484
  */
680
485
  definitionsForDocument: (
681
- document: CandidateDocument,
486
+ args: DefinitionsForDocumentArgs,
682
487
  ) => Promise<DeployedDefinition[]>;
488
+ /**
489
+ * Pre-flight `startInstance`'s gates for a definition + candidate
490
+ * `initialFields`: the `start.allowed` verdict (with its insight) plus the
491
+ * still-missing required inputs — the read a start surface renders before
492
+ * committing.
493
+ */
494
+ evaluateStart: (args: EvaluateStartArgs) => Promise<StartEvaluation>;
683
495
  /** Snapshot of all documents in the store — for inspecting the world. */
684
496
  snapshot: () => readonly StoreDocument[];
685
497
  };
686
498
 
687
- declare type ReleaseAction =
688
- | {
689
- actionType: "sanity.action.release.create";
690
- releaseId: string;
691
- metadata?: Partial<ReleaseMetadata>;
692
- }
693
- | {
694
- actionType: "sanity.action.release.edit";
695
- releaseId: string;
696
- patch: {
697
- set?: Record<string, unknown>;
698
- unset?: string[];
699
- };
700
- }
701
- | {
702
- actionType: "sanity.action.release.schedule";
703
- releaseId: string;
704
- publishAt: string;
705
- }
706
- | {
707
- actionType: "sanity.action.release.unschedule";
708
- releaseId: string;
709
- }
710
- | {
711
- actionType: "sanity.action.release.publish";
712
- releaseId: string;
713
- }
714
- | {
715
- actionType: "sanity.action.release.archive";
716
- releaseId: string;
717
- }
718
- | {
719
- actionType: "sanity.action.release.unarchive";
720
- releaseId: string;
721
- }
722
- | {
723
- actionType: "sanity.action.release.delete";
724
- releaseId: string;
725
- };
726
-
727
- declare interface ReleaseDocument extends SanityDocument {
728
- _type: "system.release";
729
- name: string;
730
- state: ReleaseState;
731
- metadata: ReleaseMetadata;
732
- publishAt?: string;
733
- }
734
-
735
499
  /**
736
500
  * A `release.ref` entry pointing at `_.releases.<releaseName>` in the bench's
737
501
  * default resource — what a release-targeting workflow's `release` field
@@ -744,85 +508,19 @@ export declare function releaseField(
744
508
  },
745
509
  ): InitialFieldValue;
746
510
 
747
- declare interface ReleaseMetadata {
748
- title?: string;
749
- description?: string;
750
- releaseType?: ReleaseType;
751
- intendedPublishAt?: string;
752
- }
753
-
754
- declare interface ReleasesHandle {
755
- create: (params: {
756
- releaseId: string;
757
- metadata?: Partial<ReleaseMetadata>;
758
- }) => Promise<
759
- ActionResult & {
760
- releaseId: string;
761
- metadata: ReleaseMetadata;
762
- }
763
- >;
764
- edit: (params: {
765
- releaseId: string;
766
- patch: {
767
- set?: Record<string, unknown>;
768
- unset?: string[];
769
- };
770
- }) => Promise<ActionResult>;
771
- get: (params: { releaseId: string }) => Promise<ReleaseDocument | undefined>;
772
- fetchDocuments: (params: { releaseId: string }) => Promise<SanityDocument[]>;
773
- schedule: (params: {
774
- releaseId: string;
775
- publishAt: string;
776
- }) => Promise<ActionResult>;
777
- unschedule: (params: { releaseId: string }) => Promise<ActionResult>;
778
- publish: (params: { releaseId: string }) => Promise<ActionResult>;
779
- archive: (params: { releaseId: string }) => Promise<ActionResult>;
780
- unarchive: (params: { releaseId: string }) => Promise<ActionResult>;
781
- delete: (params: { releaseId: string }) => Promise<ActionResult>;
782
- }
783
-
784
- declare type ReleaseState = "active" | "scheduled" | "published" | "archived";
785
-
786
- declare type ReleaseType = "asap" | "scheduled" | "undecided";
787
-
788
- /** @public */
789
- declare interface SanityDocument {
790
- _id: string;
791
- _type: string;
792
- _createdAt: string;
793
- _updatedAt: string;
794
- _rev: string;
795
- [key: string]: unknown;
796
- }
797
-
798
511
  /**
799
- * Similar to `SanityDocument` but only requires the `_id` and `_type`
800
- *
801
- * @see SanityDocument
802
- *
803
- * @public
512
+ * A REAL, minimal started instance deployed and started through the bench's
513
+ * actual verbs, parked forever on its first stage. The fixture for consumers
514
+ * (adapter tests, validation tests) that need a persisted-shape-valid
515
+ * instance document without caring about its workflow: hand-crafted instance
516
+ * stubs do not pass the engine's read parse.
804
517
  */
805
- declare interface SanityDocumentLike {
806
- _id: string;
807
- _type: string;
808
- _createdAt?: string;
809
- _updatedAt?: string;
810
- _rev?: string;
811
- _system?: {
812
- delete?: boolean;
813
- };
814
- [key: string]: unknown;
815
- }
816
-
817
- /** Result of a mutation when `returnFirst` resolves true and ids are requested. */
818
- declare interface SingleMutationResult {
819
- transactionId: string;
820
- documentId: string;
821
- results: {
822
- id: string;
823
- operation: MutationOperation;
824
- }[];
825
- }
518
+ export declare function startedInstanceFixture(
519
+ bench: FixtureBench,
520
+ opts?: {
521
+ instanceId?: string;
522
+ },
523
+ ): Promise<WorkflowInstance>;
826
524
 
827
525
  /** A document as stored in the bench's fake lake. */
828
526
  declare type StoreDocument = {
@@ -844,367 +542,6 @@ export declare function subjectField(
844
542
  },
845
543
  ): InitialFieldValue;
846
544
 
847
- declare interface SystemFields {
848
- _rev: string;
849
- _createdAt: string;
850
- _updatedAt: string;
851
- }
852
-
853
- declare interface TestClient {
854
- fetch: <T = unknown>(
855
- query: string,
856
- params?: Record<string, unknown>,
857
- options?: FetchOptions,
858
- ) => Promise<T>;
859
- getDocument: <T = SanityDocument>(
860
- id: string,
861
- options?: FetchOptions,
862
- ) => Promise<T | undefined>;
863
- getDocuments: <T = SanityDocument>(
864
- ids: string[],
865
- options?: FetchOptions,
866
- ) => Promise<(T | null)[]>;
867
- /**
868
- * Sanity-style HTTP request hook. Present iff the handle has an
869
- * acting identity — created with `accessControl`, or bound to a
870
- * registered token (`identities` + `token`). The engine's access
871
- * resolver pokes `/users/me` and the configured ACL path through
872
- * this — without it the engine throws on any verb that needs an
873
- * actor.
874
- */
875
- request?: <T>(opts: {
876
- url?: string;
877
- uri?: string;
878
- signal?: AbortSignal;
879
- tag?: string | null;
880
- }) => Promise<T>;
881
- create: <T extends SanityDocumentLike>(
882
- doc: T,
883
- options?: MutationOptions,
884
- ) => Promise<T & SystemFields>;
885
- createOrReplace: <T extends SanityDocumentLike>(
886
- doc: T,
887
- options?: MutationOptions,
888
- ) => Promise<T & SystemFields>;
889
- createIfNotExists: <T extends SanityDocumentLike>(
890
- doc: T,
891
- options?: MutationOptions,
892
- ) => Promise<T & SystemFields>;
893
- delete: (
894
- id: string,
895
- options?: MutationOptions,
896
- ) => Promise<{
897
- _id: string;
898
- } | null>;
899
- patch: (documentId: string) => PatchHandle;
900
- transaction: () => TransactionHandle;
901
- /**
902
- * Apply a batch of mutations in the raw array form (or a `patch()` /
903
- * `transaction()` builder) — mirrors `@sanity/client`'s `mutate()`.
904
- * Atomic: any failure rolls the whole batch back. The resolved shape
905
- * follows `options.returnDocuments` / `returnFirst` exactly like the
906
- * real client (default → {@link MultipleMutationResult}).
907
- */
908
- mutate: (
909
- operations: FakeMutation[] | PatchHandle | TransactionHandle,
910
- options?: TxnCommitOptions,
911
- ) => Promise<TxnCommitResult>;
912
- /**
913
- * Dispatch one or more release / version actions against the store.
914
- * Returns a synthetic `transactionId`. Multi-action arrays are applied
915
- * atomically — any failure rolls back the batch.
916
- */
917
- action: (
918
- action: Action | Action[],
919
- options?: ActionOptions,
920
- ) => Promise<ActionResult>;
921
- /** Helpers mirroring `@sanity/client`'s `client.releases.*` surface. */
922
- releases: ReleasesHandle;
923
- /** Helper for `sanity.action.document.version.create`. */
924
- createVersion: (params: {
925
- releaseId: string;
926
- publishedId: string;
927
- document: DocumentBody;
928
- }) => Promise<ActionResult>;
929
- /** Helper for `sanity.action.document.version.unpublish`. */
930
- unpublishVersion: (params: {
931
- releaseId: string;
932
- publishedId: string;
933
- }) => Promise<ActionResult>;
934
- /**
935
- * Current `(projectId, dataset)` and active perspective. `token` is
936
- * present iff the handle is bound to a registered identity.
937
- */
938
- config: () => {
939
- projectId: string;
940
- dataset: string;
941
- perspective: Perspective;
942
- token?: string;
943
- };
944
- /**
945
- * Return a new client handle pointing at a different `(projectId, dataset)`
946
- * within the SAME backing registry. Either field can be omitted to keep
947
- * the current value. Two clients produced by `withConfig` see each
948
- * other's writes if they point at the same `(projectId, dataset)`.
949
- */
950
- withConfig: (
951
- config: Partial<{
952
- projectId: string;
953
- dataset: string;
954
- perspective: Perspective;
955
- /**
956
- * Rebind the acting identity (and its grants) on a sibling that shares
957
- * the same store — the test analogue of a token-scoped client. Lets two
958
- * actors write the same dataset, e.g. for `identity()`-based guards.
959
- * Mutually exclusive with `token`.
960
- */
961
- accessControl: TestClientAccessControl;
962
- /**
963
- * Bind the sibling to a token registered in the family's identity
964
- * registry (`createTestClient({ identities })` / `addIdentity`): its
965
- * `/users/me` serves that identity's user, its ACL path serves that
966
- * identity's grants, and reads/writes are gated through them — the
967
- * token determines identity, exactly like production. Unknown tokens
968
- * throw. Mutually exclusive with `accessControl`.
969
- */
970
- token: string;
971
- }>,
972
- ) => TestClient;
973
- /** Replace the current dataset with the given documents. */
974
- seed: (documents: SanityDocumentLike[]) => void;
975
- /** Add documents to the current dataset without dropping existing ones. */
976
- add: (documents: SanityDocumentLike[]) => void;
977
- /** Drop all documents in the current dataset. */
978
- reset: () => void;
979
- /** Read-only snapshot of all documents in the current dataset. */
980
- snapshot: () => readonly SanityDocument[];
981
- /** Direct store access for the current dataset. */
982
- store: () => TestStore;
983
- /**
984
- * Simulate `count` concurrent edits to `id` in the current dataset: the
985
- * next `count` revision-guarded mutations (those passing `ifRevisionId`)
986
- * to that document throw a 409-shaped conflict, as if another client
987
- * committed between the caller's read and write. Mutations that don't
988
- * pass `ifRevisionId` are unaffected.
989
- *
990
- * Reach for this when the code under test reads a document's `_rev` and
991
- * commits with `ifRevisionId` internally — so you can't stage the race
992
- * by mutating the document first (the code would just re-read the new
993
- * revision and its commit would match). The counter is consumed one per
994
- * guarded commit, so `count` lets a test drive a recover-after-N-losses
995
- * path or exhaust a retry budget, deterministically and without relying
996
- * on promise interleaving.
997
- *
998
- * Note: this forces the conflict *outcome* — it short-circuits the
999
- * revision comparison rather than changing the stored `_rev`. The code
1000
- * under test sees the identical 409 error a real lost race produces; the
1001
- * stored revision is left untouched.
1002
- */
1003
- simulateConcurrentEdit: (id: string, count?: number) => void;
1004
- /** List `(projectId, dataset)` pairs known in this registry. */
1005
- datasets: () => {
1006
- projectId: string;
1007
- dataset: string;
1008
- }[];
1009
- /**
1010
- * Register a handler for a `@sanity/client` member the fake doesn't
1011
- * implement (e.g. `listen`, `assets`). Accessing that member on the
1012
- * client then returns the handler instead of throwing the
1013
- * "not implemented" guard error. Shared across sibling `withConfig`
1014
- * handles. Stubs fill gaps only — they don't shadow implemented methods.
1015
- */
1016
- stub: (name: string, handler: unknown) => void;
1017
- /** The currently-registered stubs, keyed by member name. */
1018
- stubs: () => Record<string, unknown>;
1019
- /**
1020
- * Register (or upsert) a token→identity entry in the family's shared
1021
- * registry. Siblings already bound to `token` resolve lazily, so a
1022
- * re-registration takes effect on them immediately. Registering an
1023
- * identity does not bind it — pass `token` at `createTestClient` or
1024
- * `withConfig({ token })` to act as it.
1025
- */
1026
- addIdentity: (token: string, identity: TestClientIdentity) => void;
1027
- /** Snapshot of the registered token→identity entries. */
1028
- identities: () => Record<string, TestClientIdentity>;
1029
- /**
1030
- * Every mutation committed through this client's registry, flattened
1031
- * and in order — `create` / `createOrReplace` / `createIfNotExists` /
1032
- * `delete` / `patch`, whether issued directly, via `transaction()`, or
1033
- * via `mutate()`. The spy hook for "assert exactly what writes my code
1034
- * submitted".
1035
- */
1036
- mutations: () => TxnMutation[];
1037
- /**
1038
- * Every committed transaction, in order. A direct mutation is recorded
1039
- * as a one-mutation transaction. Each entry carries its synthetic
1040
- * `transactionId` and the `(projectId, dataset)` it targeted.
1041
- */
1042
- transactions: () => MutationLogEntry[];
1043
- /** Clear the recorded mutation / transaction log. */
1044
- clearLog: () => void;
1045
- /**
1046
- * Every recorded method call, in order — the spy layer for "was `fetch`
1047
- * called, how many times, and with what params". Records the
1048
- * `@sanity/client`-surface methods (`fetch`, `getDocument`, `create`,
1049
- * `patch`, `mutate`, `releases.*`, …), not the test-only helpers.
1050
- * Pass a method name to filter (e.g. `calls("fetch")`); namespace
1051
- * methods are dotted (`calls("releases.create")`). Shared across
1052
- * `withConfig` siblings.
1053
- */
1054
- calls: (method?: string) => CallRecord[];
1055
- /** Number of recorded calls, optionally filtered by method name. */
1056
- callCount: (method?: string) => number;
1057
- /** Clear the recorded call log. */
1058
- clearCalls: () => void;
1059
- }
1060
-
1061
- declare interface TestClientAccessControl {
1062
- /** What `client.request({ url: "/users/me" })` returns. */
1063
- currentUser: CurrentUser;
1064
- /** What `client.request({ url: aclPath })` returns. */
1065
- grants: Grant_2[];
1066
- /**
1067
- * URL path the engine's access resolver hits to fetch grants.
1068
- * Defaults to `/projects/<projectId>/datasets/<dataset>/acl`.
1069
- */
1070
- aclPath?: string;
1071
- }
1072
-
1073
- /**
1074
- * One entry in the token→identity registry (`createTestClient({ identities })`).
1075
- * A handle bound to the entry's token behaves exactly like one configured
1076
- * with `accessControl: { currentUser: user, grants }` — `/users/me` serves
1077
- * `user`, the default ACL path serves `grants`, and reads/writes are gated
1078
- * through them.
1079
- */
1080
- declare interface TestClientIdentity {
1081
- /** What `/users/me` returns for clients bound to this token. */
1082
- user: CurrentUser;
1083
- /** Grants served at the ACL path and enforced on reads/writes. */
1084
- grants: Grant_2[];
1085
- }
1086
-
1087
- /**
1088
- * In-memory document store backing a TestClient. Closure-scoped — created
1089
- * fresh per test, no global state.
1090
- *
1091
- * The shape mirrors what `groq-js` evaluates against: an array of
1092
- * documents. Internally we keep a Map by `_id` for O(1) lookup, and
1093
- * project to an array on each query.
1094
- */
1095
- declare interface TestStore {
1096
- /** Replace the entire dataset with the given documents. Existing docs are dropped. */
1097
- seed: (documents: SanityDocumentLike[]) => void;
1098
- /** Add documents without dropping existing ones. Conflicts (same _id) overwrite. */
1099
- add: (documents: SanityDocumentLike[]) => void;
1100
- /** Remove all documents. */
1101
- reset: () => void;
1102
- /** All documents as an array, in insertion order. */
1103
- all: () => SanityDocument[];
1104
- /** Get one document by _id, or undefined. */
1105
- get: (id: string) => SanityDocument | undefined;
1106
- /** Insert a fully-formed document by _id. Replaces if it already exists. */
1107
- put: (doc: SanityDocument) => void;
1108
- /** Remove a document by _id. Returns the removed doc or undefined. */
1109
- remove: (id: string) => SanityDocument | undefined;
1110
- /** Snapshot of all docs, useful for assertions. */
1111
- snapshot: () => readonly SanityDocument[];
1112
- }
1113
-
1114
- declare interface TransactionHandle {
1115
- create: (doc: SanityDocumentLike) => TransactionHandle;
1116
- createOrReplace: (doc: SanityDocumentLike) => TransactionHandle;
1117
- createIfNotExists: (doc: SanityDocumentLike) => TransactionHandle;
1118
- delete: (id: string) => TransactionHandle;
1119
- /** Accept either a Patch builder (drained at commit) or an inline doc id + ops. */
1120
- patch: (
1121
- documentIdOrPatch: string | PatchHandle,
1122
- ops?: PatchOp[],
1123
- ifRev?: string,
1124
- ) => TransactionHandle;
1125
- commit: (options?: TxnCommitOptions) => Promise<TxnCommitResult>;
1126
- /** Test-only introspection — not on real Transaction. */
1127
- toJSON: () => TxnMutation[];
1128
- }
1129
-
1130
- declare interface TxnCommitOptions {
1131
- /**
1132
- * Shape the resolved value (mirrors `@sanity/client`):
1133
- * - default (both unset) → {@link MultipleMutationResult}
1134
- * - `returnDocuments: true` → the resulting document(s)
1135
- * - `returnFirst: true` with `returnDocuments` → a single document
1136
- * - `returnDocuments: false` → an id-shaped mutation result
1137
- */
1138
- returnDocuments?: boolean;
1139
- returnFirst?: boolean;
1140
- /** Override the synthetic transaction id (mirrors `client.transaction(undefined, { transactionId })`). */
1141
- transactionId?: string;
1142
- /**
1143
- * Request tag, recorded on the mutation log entry for assertions.
1144
- * `null` mirrors `@sanity/client`'s explicit-suppression form and is
1145
- * recorded as-is. No other behavior is simulated.
1146
- */
1147
- tag?: string | null;
1148
- }
1149
-
1150
- declare type TxnCommitResult =
1151
- | MultipleMutationResult
1152
- | SingleMutationResult
1153
- | SanityDocument
1154
- | SanityDocument[];
1155
-
1156
- /**
1157
- * A transaction collects mutations and applies them atomically at commit
1158
- * time. Mutations are applied in declaration order; if any one fails, the
1159
- * whole transaction throws and no writes land in the store.
1160
- *
1161
- * Subset of `@sanity/client`'s Transaction. Same shape, narrower surface.
1162
- */
1163
- declare type TxnMutation =
1164
- | {
1165
- kind: "create";
1166
- doc: SanityDocumentLike;
1167
- }
1168
- | {
1169
- kind: "createOrReplace";
1170
- doc: SanityDocumentLike;
1171
- }
1172
- | {
1173
- kind: "createIfNotExists";
1174
- doc: SanityDocumentLike;
1175
- }
1176
- | {
1177
- kind: "delete";
1178
- id: string;
1179
- }
1180
- | {
1181
- kind: "patch";
1182
- documentId: string;
1183
- ops: PatchOp[];
1184
- ifRev?: string;
1185
- };
1186
-
1187
- declare type VersionAction =
1188
- | {
1189
- actionType: "sanity.action.document.version.create";
1190
- publishedId: string;
1191
- document: DocumentBody;
1192
- releaseId?: string;
1193
- }
1194
- | {
1195
- actionType: "sanity.action.document.version.replace";
1196
- document: SanityDocumentLike;
1197
- }
1198
- | {
1199
- actionType: "sanity.action.document.version.discard";
1200
- versionId: string;
1201
- }
1202
- | {
1203
- actionType: "sanity.action.document.version.unpublish";
1204
- versionId: string;
1205
- publishedId: string;
1206
- };
1207
-
1208
545
  /**
1209
546
  * The default grants used when tests don't specify any. A single
1210
547
  * permissive grant matches every document and confers all permissions.