@sanity/workflow-engine-test 0.10.0 → 0.11.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,21 +1,27 @@
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 { FindPendingEffectsArgs } from "@sanity/workflow-engine";
15
19
  import type { FireActionArgs } from "@sanity/workflow-engine";
16
- import { GdrUri } from "@sanity/workflow-engine";
17
20
  import type { Grant } from "@sanity/workflow-engine";
18
21
  import { InitialFieldValue } from "@sanity/workflow-engine";
22
+ import { InstanceRefArgs } from "@sanity/workflow-engine";
23
+ import { InstancesForDocumentArgs } from "@sanity/workflow-engine";
24
+ import { InstancesQueryFilter } from "@sanity/workflow-engine";
19
25
  import { MutationGuardAction } from "@sanity/workflow-engine";
20
26
  import { MutationGuardDoc } from "@sanity/workflow-engine";
21
27
  import type { OperationArgs } from "@sanity/workflow-engine";
@@ -23,24 +29,13 @@ import { OperationResult } from "@sanity/workflow-engine";
23
29
  import { PendingEffect } from "@sanity/workflow-engine";
24
30
  import type { SetStageArgs } from "@sanity/workflow-engine";
25
31
  import type { StartInstanceArgs } from "@sanity/workflow-engine";
32
+ import type { TestClient } from "@sanity-labs/client-fake-for-test";
26
33
  import type { WorkflowAccess } from "@sanity/workflow-engine";
27
34
  import { WorkflowDefinitionInput } from "@sanity/workflow-engine";
28
35
  import { WorkflowEvaluation } from "@sanity/workflow-engine";
29
36
  import { WorkflowInstance } from "@sanity/workflow-engine";
30
37
  import type { WorkflowResource } from "@sanity/workflow-engine";
31
38
 
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
39
  /**
45
40
  * The bench's default identity — wildcard user + wildcard grants,
46
41
  * registered as the default token on the bench's fake client so the
@@ -94,6 +89,9 @@ declare type BenchArgsWithGrants<T> = BenchArgs<T> & {
94
89
 
95
90
  declare type BenchCompleteEffectArgs = BenchArgs<CompleteEffectArgs>;
96
91
 
92
+ declare type BenchDeleteDefinitionArgs =
93
+ BenchArgsWithGrants<DeleteDefinitionArgs>;
94
+
97
95
  declare type BenchEditFieldArgs = BenchArgsWithGrants<EditFieldArgs>;
98
96
 
99
97
  declare type BenchEvaluateArgs = BenchArgsWithGrants<EvaluateArgs>;
@@ -146,8 +144,7 @@ declare interface BenchState {
146
144
  * Freeze the bench clock at an explicit ISO instant. Every subsequent
147
145
  * engine call (`startInstance` / `fireAction` / `tick` / `completeEffect`
148
146
  * / `evaluate`) sees this as `$now` and stamps it on history, so
149
- * time-based `completeWhen` / `failWhen` / SLA predicates are
150
- * deterministic.
147
+ * time-based `when` triggers and SLA predicates are deterministic.
151
148
  */
152
149
  setNow: (iso: string) => void;
153
150
  /**
@@ -160,28 +157,6 @@ declare interface BenchState {
160
157
 
161
158
  declare type BenchTickArgs = BenchArgs<OperationArgs>;
162
159
 
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
160
  /** Helpers composed across the wrapper + read groups at bench assembly. */
186
161
  declare interface ComposedHelpers {
187
162
  /**
@@ -274,35 +249,17 @@ export declare interface CreateBenchOptions {
274
249
  executionContext?: DeclaredExecutionContext;
275
250
  }
276
251
 
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
252
  /** Default workflow resource for benches that don't pass one. */
291
253
  export declare const DEFAULT_WORKFLOW_RESOURCE: WorkflowResource;
292
254
 
293
255
  /**
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.
256
+ * The write {@link GuardHelpers.editDocument} performs. Every arm is a real
257
+ * distinct client write the guard seam vets under that action `create` is
258
+ * deliberately absent because editing addresses an existing document; create
259
+ * through the client (`bench.client.create` / `clientFor(...).create`), which
260
+ * the same seam gates as a create.
298
261
  */
299
- declare interface DocumentBody {
300
- _id?: string;
301
- _type: string;
302
- [field: string]: unknown;
303
- }
304
-
305
- declare type DocumentValuePermission = "create" | "read" | "update";
262
+ export declare type EditDocumentAction = Exclude<MutationGuardAction, "create">;
306
263
 
307
264
  /**
308
265
  * How {@link GuardHelpers.editDocument} mutates the document. Each present
@@ -332,6 +289,9 @@ declare type EngineWrappers = {
332
289
  deployDefinitions: <T extends WorkflowDefinitionInput<T>>(
333
290
  args: DeployDefinitionsArgs<T>,
334
291
  ) => Promise<DeployDefinitionsResult>;
292
+ deleteDefinition: (
293
+ args: BenchDeleteDefinitionArgs,
294
+ ) => Promise<DeleteDefinitionResult>;
335
295
  startInstance: (args: BenchStartInstanceArgs) => Promise<OperationResult>;
336
296
  fireAction: (args: BenchFireActionArgs) => Promise<OperationResult>;
337
297
  editField: (args: BenchEditFieldArgs) => Promise<OperationResult>;
@@ -346,97 +306,6 @@ declare type EngineWrappers = {
346
306
  guardsForDefinition: (definition: string) => Promise<MutationGuardDoc[]>;
347
307
  };
348
308
 
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[];
438
- }
439
-
440
309
  declare type GuardHelpers = {
441
310
  /** All deployed guard docs ({@link MutationGuardDoc}) in the store. */
442
311
  listGuards: () => Promise<MutationGuardDoc[]>;
@@ -454,137 +323,28 @@ declare type GuardHelpers = {
454
323
  * client gates the write against deployed guards (incl. `identity()`) and
455
324
  * throws `MutationGuardDeniedError` on denial — the lake's intended
456
325
  * 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.
326
+ * token-scoped sibling so `identity()` resolves to that actor.
327
+ *
328
+ * Each {@link EditDocumentAction} performs that real write, so a guard
329
+ * scoped to it is genuinely consulted: `update` (default) applies `patch`;
330
+ * `delete` deletes; `publish` promotes the existing draft
331
+ * (`drafts.<documentId>`) onto `documentId`; `unpublish` retracts
332
+ * `documentId` into its draft. `documentId` is always the published id.
333
+ * A `patch` ({@link EditDocumentPatch}) is required for `update` and
334
+ * rejected for every other action — the combination is contradictory input.
335
+ *
336
+ * Resolves to the post-write document as the DEFAULT identity reads it —
337
+ * `undefined` after `delete`/`unpublish` (the doc is gone), or when the
338
+ * default identity cannot read the written doc.
460
339
  */
461
340
  editDocument: (opts: {
462
341
  documentId: string;
463
342
  patch?: EditDocumentPatch;
464
- action?: MutationGuardAction;
343
+ action?: EditDocumentAction;
465
344
  as?: Actor;
466
- }) => Promise<StoreDocument>;
345
+ }) => Promise<StoreDocument | undefined>;
467
346
  };
468
347
 
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
348
  declare type QueryHelpers = {
589
349
  /**
590
350
  * Run an arbitrary GROQ query through `workflow.query`. The query MUST
@@ -616,34 +376,35 @@ declare type QueryHelpers = {
616
376
  };
617
377
 
618
378
  declare type ReadHelpers = {
619
- /** Read the workflow instance by id. Throws if missing. */
620
- getInstance: (instanceId: string) => Promise<WorkflowInstance>;
379
+ /**
380
+ * The engine's instance point-read: throws typed `InstanceNotFoundError`
381
+ * when the doc is missing or belongs to another engine's tag partition.
382
+ */
383
+ getInstance: (args: InstanceRefArgs) => Promise<WorkflowInstance>;
621
384
  /** Current stage name of an instance. */
622
385
  currentStage: (instanceId: string) => Promise<string>;
623
- /** Pending effects on an instance. */
624
- pendingEffects: (instanceId: string) => Promise<PendingEffect[]>;
386
+ /** Every pending effect on an instance — claimed and unclaimed alike. */
387
+ listPendingEffects: (args: InstanceRefArgs) => Promise<PendingEffect[]>;
388
+ /** Pending effects filtered by claim presence and/or effect names. */
389
+ findPendingEffects: (
390
+ args: FindPendingEffectsArgs,
391
+ ) => Promise<PendingEffect[]>;
625
392
  /** Status of a single activity on an instance, or undefined if missing. */
626
393
  activityStatus: (
627
394
  instanceId: string,
628
395
  activity: string,
629
396
  ) => 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>>;
397
+ /** The rendered `$context` map — the start-time seed + a parent's spawn
398
+ * handoff, keyed by entry `name`. */
399
+ contextMap: (instanceId: string) => Promise<Record<string, unknown>>;
400
+ /** The rendered `$effects` map — completed effects' outputs, namespaced
401
+ * by effect name. */
402
+ effectOutputsMap: (instanceId: string) => Promise<Record<string, unknown>>;
633
403
  /**
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.
404
+ * Spawned children of the instance, optionally restricted to those
405
+ * spawned by `activity`. Ordered by `startedAt` ascending.
642
406
  */
643
- children: (
644
- parentInstanceId: string,
645
- activity?: string,
646
- ) => Promise<WorkflowInstance[]>;
407
+ children: (args: ChildrenArgs) => Promise<WorkflowInstance[]>;
647
408
  /**
648
409
  * All workflow instances whose `doc.ref` field entry named
649
410
  * `entryName` (default `"subject"`) matches the given doc id. Useful
@@ -655,83 +416,37 @@ declare type ReadHelpers = {
655
416
  entryName?: string,
656
417
  ) => Promise<WorkflowInstance[]>;
657
418
  /**
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.
419
+ * All instances of `definition` currently at `stage`. In-flight only by
420
+ * default; `includeCompleted: true` widens to completed/aborted instances.
421
+ * The options derive from the engine's {@link InstancesQueryFilter}, so a
422
+ * vocabulary drift is a compile error here, not a silently-dropped filter.
661
423
  */
662
- instancesByStage: (options: {
663
- workflowName: string;
664
- stage: string;
665
- openOnly?: boolean;
666
- }) => Promise<WorkflowInstance[]>;
424
+ instancesByStage: (
425
+ options: Required<Pick<InstancesQueryFilter, "definition" | "stage">> &
426
+ Pick<InstancesQueryFilter, "includeCompleted">,
427
+ ) => Promise<WorkflowInstance[]>;
667
428
  /**
668
429
  * Every in-flight instance whose reactive watch-set includes `document`
669
430
  * (a resource-qualified GDR URI) — the reverse of the forward watch-set.
670
431
  * Answers "which instances should this changed doc `tick`?" the way a
671
432
  * non-reactive content forwarder needs. Sorted by `startedAt` ascending.
672
433
  */
673
- instancesForDocument: (document: GdrUri) => Promise<WorkflowInstance[]>;
434
+ instancesForDocument: (
435
+ args: InstancesForDocumentArgs,
436
+ ) => Promise<WorkflowInstance[]>;
674
437
  /**
675
438
  * The startable half of {@link ReadHelpers.instancesForDocument}: the
676
439
  * latest deployed version of every definition applicable to the LOADED
677
440
  * candidate document — startable, a required subject entry accepts its
678
- * `_type`, `applicableWhen` passes. All matches, name ascending.
441
+ * `_type`, `start.filter` passes. All matches, name ascending.
679
442
  */
680
443
  definitionsForDocument: (
681
- document: CandidateDocument,
444
+ args: DefinitionsForDocumentArgs,
682
445
  ) => Promise<DeployedDefinition[]>;
683
446
  /** Snapshot of all documents in the store — for inspecting the world. */
684
447
  snapshot: () => readonly StoreDocument[];
685
448
  };
686
449
 
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
450
  /**
736
451
  * A `release.ref` entry pointing at `_.releases.<releaseName>` in the bench's
737
452
  * default resource — what a release-targeting workflow's `release` field
@@ -744,86 +459,6 @@ export declare function releaseField(
744
459
  },
745
460
  ): InitialFieldValue;
746
461
 
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
- /**
799
- * Similar to `SanityDocument` but only requires the `_id` and `_type`
800
- *
801
- * @see SanityDocument
802
- *
803
- * @public
804
- */
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
- }
826
-
827
462
  /** A document as stored in the bench's fake lake. */
828
463
  declare type StoreDocument = {
829
464
  _id: string;
@@ -844,367 +479,6 @@ export declare function subjectField(
844
479
  },
845
480
  ): InitialFieldValue;
846
481
 
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
482
  /**
1209
483
  * The default grants used when tests don't specify any. A single
1210
484
  * permissive grant matches every document and confers all permissions.