@sanity-labs/client-fake-for-test 0.7.1 → 0.9.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
@@ -1,4 +1,4 @@
1
- export declare type Action = ReleaseAction | VersionAction;
1
+ export declare type Action = ReleaseAction | VersionAction | DocumentAction;
2
2
 
3
3
  /** Options for `action()`. Only `tag` is meaningful to the fake. */
4
4
  export declare interface ActionOptions {
@@ -107,8 +107,9 @@ export declare interface CreateTestClientOptions {
107
107
  datasets?: Record<string, SanityDocumentLike[]>;
108
108
  /**
109
109
  * Opt-in access-control simulation. When set, the client exposes
110
- * `request({ url })` that serves `/users/me` + the configured ACL
111
- * path, AND every mutation is gated through the supplied `grants`
110
+ * `request({ url })` that serves `/users/me` (host-aware), the
111
+ * configured ACL path, and `/projects/<projectId>/users/<ids>`,
112
+ * AND every mutation is gated through the supplied `grants`
112
113
  * — writes that the grants don't permit throw a `WriteAccessDeniedError`
113
114
  * shaped like Sanity's 403. Lets tests demonstrate the full
114
115
  * "engine soft-gate + lake hard-gate" contract end-to-end without
@@ -165,6 +166,14 @@ export declare interface CreateTestClientOptions {
165
166
  * tests stay green.
166
167
  */
167
168
  perspective?: Perspective;
169
+ /**
170
+ * Which API host the root handle models: the project host (default
171
+ * `true`) or the global host (`false`) — same flag as the real
172
+ * `createClient` config. A global-hosted handle's `/users/me` serves
173
+ * the identity's account-global record. `withConfig({ useProjectHostname })`
174
+ * mints siblings on the other host.
175
+ */
176
+ useProjectHostname?: boolean;
168
177
  }
169
178
 
170
179
  /**
@@ -202,6 +211,26 @@ export declare function defaultAclPath(
202
211
  dataset: string,
203
212
  ): string;
204
213
 
214
+ /**
215
+ * Draft↔published document actions — `sanity.action.document.publish`
216
+ * promotes the draft onto the published id (and deletes the draft);
217
+ * `sanity.action.document.unpublish` retracts the published document into a
218
+ * draft (keeping an existing draft untouched) and deletes the published one.
219
+ */
220
+ export declare type DocumentAction =
221
+ | {
222
+ actionType: "sanity.action.document.publish";
223
+ draftId: string;
224
+ publishedId: string;
225
+ ifDraftRevisionId?: string;
226
+ ifPublishedRevisionId?: string;
227
+ }
228
+ | {
229
+ actionType: "sanity.action.document.unpublish";
230
+ draftId: string;
231
+ publishedId: string;
232
+ };
233
+
205
234
  /**
206
235
  * Document body accepted by `version.create` / `createVersion`. The id
207
236
  * is derived from `releaseId + publishedId` so callers may omit `_id`
@@ -387,11 +416,13 @@ export declare interface MultipleMutationResult {
387
416
  * A pluggable write interceptor, configured via
388
417
  * `createTestClient({ mutationGuard })`. It runs on each write through the
389
418
  * document mutation API — `create` / `createOrReplace` / `createIfNotExists` /
390
- * `patch` / `delete` and the equivalent `transaction()` operations — before the
391
- * store changes; **throw to reject the write** (the throw propagates out of the
392
- * client call, exactly as a real lake rejection would). The release/version
393
- * action API (`client.action`, `client.releases.*`, `createVersion`) is **not**
394
- * intercepted.
419
+ * `patch` / `delete` and the equivalent `transaction()` operations — and on the
420
+ * document publish/unpublish actions (`sanity.action.document.publish` /
421
+ * `.unpublish` via `client.action`) — before the store changes; **throw to
422
+ * reject the write** (the throw propagates out of the client call, exactly as
423
+ * a real lake rejection would). The release/version action API
424
+ * (`client.releases.*`, `createVersion`, the `sanity.action.release.*` and
425
+ * `*.version.*` action types) is **not** intercepted.
395
426
  *
396
427
  * The client itself has no opinion about *what* a guard is — it only provides
397
428
  * the seam and the before/after/identity context. Callers (e.g. a workflow test
@@ -404,13 +435,21 @@ export declare type MutationGuard = (
404
435
  ) => void | Promise<void>;
405
436
 
406
437
  /** The kind of write a {@link MutationGuard} is asked to vet. */
407
- export declare type MutationGuardAction = "create" | "update" | "delete";
438
+ export declare type MutationGuardAction =
439
+ | "create"
440
+ | "update"
441
+ | "delete"
442
+ | "publish"
443
+ | "unpublish";
408
444
 
409
445
  /**
410
446
  * The before/after image of a single document write, handed to a
411
447
  * {@link MutationGuard} before it is applied.
412
448
  *
413
- * - `before` is null on create; `after` is null on delete.
449
+ * - `before` is null on create and on a publish with no live published
450
+ * document; `after` is null on delete and unpublish.
451
+ * - On publish/unpublish both images are of the *published* document —
452
+ * `after` on publish is the draft's content promoted onto the published id.
414
453
  * - `identity` is the acting user — the id bound to the client's token
415
454
  * (its `accessControl.currentUser`), or undefined when the client has no
416
455
  * identity configured.
@@ -553,6 +592,19 @@ export declare type PatchOp =
553
592
 
554
593
  export declare type Perspective = "raw" | "published" | "drafts" | string[];
555
594
 
595
+ /**
596
+ * A row served by the project-users directory endpoint
597
+ * (`/projects/<projectId>/users/<id>`), mirroring Sanity's project user
598
+ * retrieval API: the project-scoped `id` alongside its account-global
599
+ * `sanityUserId` — the one sanctioned bridge between the two namespaces.
600
+ */
601
+ export declare interface ProjectUserRecord {
602
+ id: string;
603
+ sanityUserId: string;
604
+ displayName: string;
605
+ email?: string;
606
+ }
607
+
556
608
  /** Strip `drafts.` / `versions.<name>.` prefix to recover the published id. */
557
609
  export declare function publishedIdOf(id: string): string;
558
610
 
@@ -789,9 +841,11 @@ export declare interface TestClient {
789
841
  options?: TxnCommitOptions,
790
842
  ) => Promise<TxnCommitResult>;
791
843
  /**
792
- * Dispatch one or more release / version actions against the store.
793
- * Returns a synthetic `transactionId`. Multi-action arrays are applied
794
- * atomically — any failure rolls back the batch.
844
+ * Dispatch one or more release / version / document actions against the
845
+ * store. Returns a synthetic `transactionId`. Multi-action arrays are
846
+ * applied atomically — any failure rolls back the batch. The document
847
+ * publish/unpublish actions run through the access gate and the
848
+ * `mutationGuard` seam; release/version actions apply unhooked.
795
849
  */
796
850
  action: (
797
851
  action: Action | Action[],
@@ -811,14 +865,16 @@ export declare interface TestClient {
811
865
  publishedId: string;
812
866
  }) => Promise<ActionResult>;
813
867
  /**
814
- * Current `(projectId, dataset)` and active perspective. `token` is
815
- * present iff the handle is bound to a registered identity.
868
+ * Current `(projectId, dataset)`, active perspective, and which API host
869
+ * the handle models (`useProjectHostname`). `token` is present iff the
870
+ * handle is bound to a registered identity.
816
871
  */
817
872
  config: () => {
818
873
  projectId: string;
819
874
  dataset: string;
820
875
  perspective: Perspective;
821
876
  token?: string;
877
+ useProjectHostname: boolean;
822
878
  };
823
879
  /**
824
880
  * Return a new client handle pointing at a different `(projectId, dataset)`
@@ -831,6 +887,14 @@ export declare interface TestClient {
831
887
  projectId: string;
832
888
  dataset: string;
833
889
  perspective: Perspective;
890
+ /**
891
+ * Model the global API host (`false`) instead of the project host
892
+ * (default `true`). A global-hosted sibling's `/users/me` serves the
893
+ * identity's account-global record (`globalUser`, falling back to the
894
+ * project record) — mirroring `@sanity/client`, where a non-project
895
+ * config routes requests to `api.sanity.io`.
896
+ */
897
+ useProjectHostname: boolean;
834
898
  /**
835
899
  * Rebind the acting identity (and its grants) on a sibling that shares
836
900
  * the same store — the test analogue of a token-scoped client. Lets two
@@ -841,10 +905,11 @@ export declare interface TestClient {
841
905
  /**
842
906
  * Bind the sibling to a token registered in the family's identity
843
907
  * registry (`createTestClient({ identities })` / `addIdentity`): its
844
- * `/users/me` serves that identity's user, its ACL path serves that
845
- * identity's grants, and reads/writes are gated through them — the
846
- * token determines identity, exactly like production. Unknown tokens
847
- * throw. Mutually exclusive with `accessControl`.
908
+ * `/users/me` serves that identity's record for the sibling's project
909
+ * and host, its ACL path serves that identity's grants, and
910
+ * reads/writes are gated through them — the token determines
911
+ * identity, exactly like production. Unknown tokens throw. Mutually
912
+ * exclusive with `accessControl`.
848
913
  */
849
914
  token: string;
850
915
  }>,
@@ -938,8 +1003,18 @@ export declare interface TestClient {
938
1003
  }
939
1004
 
940
1005
  export declare interface TestClientAccessControl {
941
- /** What `client.request({ url: "/users/me" })` returns. */
1006
+ /** What `client.request({ url: "/users/me" })` returns on a project-hosted handle. */
942
1007
  currentUser: CurrentUser;
1008
+ /**
1009
+ * What `/users/me` returns on a global-hosted handle
1010
+ * (`useProjectHostname: false`, at creation or via `withConfig`).
1011
+ * Sanity serves the
1012
+ * account-global user there (`g*` id, no roles) while the project host
1013
+ * serves the project-scoped record (`p*` id, project roles). Omit to
1014
+ * serve `currentUser` from both — the single-id collapse that is also
1015
+ * exactly how robot tokens behave in production.
1016
+ */
1017
+ globalUser?: CurrentUser;
943
1018
  /** What `client.request({ url: aclPath })` returns. */
944
1019
  grants: Grant[];
945
1020
  /**
@@ -952,13 +1027,36 @@ export declare interface TestClientAccessControl {
952
1027
  /**
953
1028
  * One entry in the token→identity registry (`createTestClient({ identities })`).
954
1029
  * A handle bound to the entry's token behaves exactly like one configured
955
- * with `accessControl: { currentUser: user, grants }` — `/users/me` serves
956
- * `user`, the default ACL path serves `grants`, and reads/writes are gated
957
- * through them.
1030
+ * with `accessControl: { currentUser: projectUserOf(identity, projectId),
1031
+ * globalUser, grants }` — `/users/me` serves the record for the handle's
1032
+ * project and host, the default ACL path serves `grants`, and reads/writes
1033
+ * are gated through them.
958
1034
  */
959
1035
  export declare interface TestClientIdentity {
960
- /** What `/users/me` returns for clients bound to this token. */
1036
+ /**
1037
+ * The project-scoped user record: what `/users/me` returns on a
1038
+ * project-hosted handle, and the id `identity()` resolves to in grant
1039
+ * filters and mutation guards. Per-project variants go in
1040
+ * `usersByProject`; this is the record for any project without one.
1041
+ */
961
1042
  user: CurrentUser;
1043
+ /**
1044
+ * The account-global user record, served by `/users/me` on a
1045
+ * global-hosted handle (`useProjectHostname: false`, at creation or
1046
+ * via `withConfig`).
1047
+ * The same person carries a different id per project (`p*`) and one
1048
+ * global id (`g*`); registering both lets tests model that split.
1049
+ * Omit for single-id principals (robot tokens resolve to one `p-*` id
1050
+ * on every host).
1051
+ */
1052
+ globalUser?: CurrentUser;
1053
+ /**
1054
+ * Per-project user records, keyed by projectId — the same token
1055
+ * resolves to a different project-scoped id in each project, which is
1056
+ * what these model. A handle's project falls back to `user` when its
1057
+ * projectId has no entry.
1058
+ */
1059
+ usersByProject?: Record<string, CurrentUser>;
962
1060
  /** Grants served at the ACL path and enforced on reads/writes. */
963
1061
  grants: Grant[];
964
1062
  }
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export declare type Action = ReleaseAction | VersionAction;
1
+ export declare type Action = ReleaseAction | VersionAction | DocumentAction;
2
2
 
3
3
  /** Options for `action()`. Only `tag` is meaningful to the fake. */
4
4
  export declare interface ActionOptions {
@@ -107,8 +107,9 @@ export declare interface CreateTestClientOptions {
107
107
  datasets?: Record<string, SanityDocumentLike[]>;
108
108
  /**
109
109
  * Opt-in access-control simulation. When set, the client exposes
110
- * `request({ url })` that serves `/users/me` + the configured ACL
111
- * path, AND every mutation is gated through the supplied `grants`
110
+ * `request({ url })` that serves `/users/me` (host-aware), the
111
+ * configured ACL path, and `/projects/<projectId>/users/<ids>`,
112
+ * AND every mutation is gated through the supplied `grants`
112
113
  * — writes that the grants don't permit throw a `WriteAccessDeniedError`
113
114
  * shaped like Sanity's 403. Lets tests demonstrate the full
114
115
  * "engine soft-gate + lake hard-gate" contract end-to-end without
@@ -165,6 +166,14 @@ export declare interface CreateTestClientOptions {
165
166
  * tests stay green.
166
167
  */
167
168
  perspective?: Perspective;
169
+ /**
170
+ * Which API host the root handle models: the project host (default
171
+ * `true`) or the global host (`false`) — same flag as the real
172
+ * `createClient` config. A global-hosted handle's `/users/me` serves
173
+ * the identity's account-global record. `withConfig({ useProjectHostname })`
174
+ * mints siblings on the other host.
175
+ */
176
+ useProjectHostname?: boolean;
168
177
  }
169
178
 
170
179
  /**
@@ -202,6 +211,26 @@ export declare function defaultAclPath(
202
211
  dataset: string,
203
212
  ): string;
204
213
 
214
+ /**
215
+ * Draft↔published document actions — `sanity.action.document.publish`
216
+ * promotes the draft onto the published id (and deletes the draft);
217
+ * `sanity.action.document.unpublish` retracts the published document into a
218
+ * draft (keeping an existing draft untouched) and deletes the published one.
219
+ */
220
+ export declare type DocumentAction =
221
+ | {
222
+ actionType: "sanity.action.document.publish";
223
+ draftId: string;
224
+ publishedId: string;
225
+ ifDraftRevisionId?: string;
226
+ ifPublishedRevisionId?: string;
227
+ }
228
+ | {
229
+ actionType: "sanity.action.document.unpublish";
230
+ draftId: string;
231
+ publishedId: string;
232
+ };
233
+
205
234
  /**
206
235
  * Document body accepted by `version.create` / `createVersion`. The id
207
236
  * is derived from `releaseId + publishedId` so callers may omit `_id`
@@ -387,11 +416,13 @@ export declare interface MultipleMutationResult {
387
416
  * A pluggable write interceptor, configured via
388
417
  * `createTestClient({ mutationGuard })`. It runs on each write through the
389
418
  * document mutation API — `create` / `createOrReplace` / `createIfNotExists` /
390
- * `patch` / `delete` and the equivalent `transaction()` operations — before the
391
- * store changes; **throw to reject the write** (the throw propagates out of the
392
- * client call, exactly as a real lake rejection would). The release/version
393
- * action API (`client.action`, `client.releases.*`, `createVersion`) is **not**
394
- * intercepted.
419
+ * `patch` / `delete` and the equivalent `transaction()` operations — and on the
420
+ * document publish/unpublish actions (`sanity.action.document.publish` /
421
+ * `.unpublish` via `client.action`) — before the store changes; **throw to
422
+ * reject the write** (the throw propagates out of the client call, exactly as
423
+ * a real lake rejection would). The release/version action API
424
+ * (`client.releases.*`, `createVersion`, the `sanity.action.release.*` and
425
+ * `*.version.*` action types) is **not** intercepted.
395
426
  *
396
427
  * The client itself has no opinion about *what* a guard is — it only provides
397
428
  * the seam and the before/after/identity context. Callers (e.g. a workflow test
@@ -404,13 +435,21 @@ export declare type MutationGuard = (
404
435
  ) => void | Promise<void>;
405
436
 
406
437
  /** The kind of write a {@link MutationGuard} is asked to vet. */
407
- export declare type MutationGuardAction = "create" | "update" | "delete";
438
+ export declare type MutationGuardAction =
439
+ | "create"
440
+ | "update"
441
+ | "delete"
442
+ | "publish"
443
+ | "unpublish";
408
444
 
409
445
  /**
410
446
  * The before/after image of a single document write, handed to a
411
447
  * {@link MutationGuard} before it is applied.
412
448
  *
413
- * - `before` is null on create; `after` is null on delete.
449
+ * - `before` is null on create and on a publish with no live published
450
+ * document; `after` is null on delete and unpublish.
451
+ * - On publish/unpublish both images are of the *published* document —
452
+ * `after` on publish is the draft's content promoted onto the published id.
414
453
  * - `identity` is the acting user — the id bound to the client's token
415
454
  * (its `accessControl.currentUser`), or undefined when the client has no
416
455
  * identity configured.
@@ -553,6 +592,19 @@ export declare type PatchOp =
553
592
 
554
593
  export declare type Perspective = "raw" | "published" | "drafts" | string[];
555
594
 
595
+ /**
596
+ * A row served by the project-users directory endpoint
597
+ * (`/projects/<projectId>/users/<id>`), mirroring Sanity's project user
598
+ * retrieval API: the project-scoped `id` alongside its account-global
599
+ * `sanityUserId` — the one sanctioned bridge between the two namespaces.
600
+ */
601
+ export declare interface ProjectUserRecord {
602
+ id: string;
603
+ sanityUserId: string;
604
+ displayName: string;
605
+ email?: string;
606
+ }
607
+
556
608
  /** Strip `drafts.` / `versions.<name>.` prefix to recover the published id. */
557
609
  export declare function publishedIdOf(id: string): string;
558
610
 
@@ -789,9 +841,11 @@ export declare interface TestClient {
789
841
  options?: TxnCommitOptions,
790
842
  ) => Promise<TxnCommitResult>;
791
843
  /**
792
- * Dispatch one or more release / version actions against the store.
793
- * Returns a synthetic `transactionId`. Multi-action arrays are applied
794
- * atomically — any failure rolls back the batch.
844
+ * Dispatch one or more release / version / document actions against the
845
+ * store. Returns a synthetic `transactionId`. Multi-action arrays are
846
+ * applied atomically — any failure rolls back the batch. The document
847
+ * publish/unpublish actions run through the access gate and the
848
+ * `mutationGuard` seam; release/version actions apply unhooked.
795
849
  */
796
850
  action: (
797
851
  action: Action | Action[],
@@ -811,14 +865,16 @@ export declare interface TestClient {
811
865
  publishedId: string;
812
866
  }) => Promise<ActionResult>;
813
867
  /**
814
- * Current `(projectId, dataset)` and active perspective. `token` is
815
- * present iff the handle is bound to a registered identity.
868
+ * Current `(projectId, dataset)`, active perspective, and which API host
869
+ * the handle models (`useProjectHostname`). `token` is present iff the
870
+ * handle is bound to a registered identity.
816
871
  */
817
872
  config: () => {
818
873
  projectId: string;
819
874
  dataset: string;
820
875
  perspective: Perspective;
821
876
  token?: string;
877
+ useProjectHostname: boolean;
822
878
  };
823
879
  /**
824
880
  * Return a new client handle pointing at a different `(projectId, dataset)`
@@ -831,6 +887,14 @@ export declare interface TestClient {
831
887
  projectId: string;
832
888
  dataset: string;
833
889
  perspective: Perspective;
890
+ /**
891
+ * Model the global API host (`false`) instead of the project host
892
+ * (default `true`). A global-hosted sibling's `/users/me` serves the
893
+ * identity's account-global record (`globalUser`, falling back to the
894
+ * project record) — mirroring `@sanity/client`, where a non-project
895
+ * config routes requests to `api.sanity.io`.
896
+ */
897
+ useProjectHostname: boolean;
834
898
  /**
835
899
  * Rebind the acting identity (and its grants) on a sibling that shares
836
900
  * the same store — the test analogue of a token-scoped client. Lets two
@@ -841,10 +905,11 @@ export declare interface TestClient {
841
905
  /**
842
906
  * Bind the sibling to a token registered in the family's identity
843
907
  * registry (`createTestClient({ identities })` / `addIdentity`): its
844
- * `/users/me` serves that identity's user, its ACL path serves that
845
- * identity's grants, and reads/writes are gated through them — the
846
- * token determines identity, exactly like production. Unknown tokens
847
- * throw. Mutually exclusive with `accessControl`.
908
+ * `/users/me` serves that identity's record for the sibling's project
909
+ * and host, its ACL path serves that identity's grants, and
910
+ * reads/writes are gated through them — the token determines
911
+ * identity, exactly like production. Unknown tokens throw. Mutually
912
+ * exclusive with `accessControl`.
848
913
  */
849
914
  token: string;
850
915
  }>,
@@ -938,8 +1003,18 @@ export declare interface TestClient {
938
1003
  }
939
1004
 
940
1005
  export declare interface TestClientAccessControl {
941
- /** What `client.request({ url: "/users/me" })` returns. */
1006
+ /** What `client.request({ url: "/users/me" })` returns on a project-hosted handle. */
942
1007
  currentUser: CurrentUser;
1008
+ /**
1009
+ * What `/users/me` returns on a global-hosted handle
1010
+ * (`useProjectHostname: false`, at creation or via `withConfig`).
1011
+ * Sanity serves the
1012
+ * account-global user there (`g*` id, no roles) while the project host
1013
+ * serves the project-scoped record (`p*` id, project roles). Omit to
1014
+ * serve `currentUser` from both — the single-id collapse that is also
1015
+ * exactly how robot tokens behave in production.
1016
+ */
1017
+ globalUser?: CurrentUser;
943
1018
  /** What `client.request({ url: aclPath })` returns. */
944
1019
  grants: Grant[];
945
1020
  /**
@@ -952,13 +1027,36 @@ export declare interface TestClientAccessControl {
952
1027
  /**
953
1028
  * One entry in the token→identity registry (`createTestClient({ identities })`).
954
1029
  * A handle bound to the entry's token behaves exactly like one configured
955
- * with `accessControl: { currentUser: user, grants }` — `/users/me` serves
956
- * `user`, the default ACL path serves `grants`, and reads/writes are gated
957
- * through them.
1030
+ * with `accessControl: { currentUser: projectUserOf(identity, projectId),
1031
+ * globalUser, grants }` — `/users/me` serves the record for the handle's
1032
+ * project and host, the default ACL path serves `grants`, and reads/writes
1033
+ * are gated through them.
958
1034
  */
959
1035
  export declare interface TestClientIdentity {
960
- /** What `/users/me` returns for clients bound to this token. */
1036
+ /**
1037
+ * The project-scoped user record: what `/users/me` returns on a
1038
+ * project-hosted handle, and the id `identity()` resolves to in grant
1039
+ * filters and mutation guards. Per-project variants go in
1040
+ * `usersByProject`; this is the record for any project without one.
1041
+ */
961
1042
  user: CurrentUser;
1043
+ /**
1044
+ * The account-global user record, served by `/users/me` on a
1045
+ * global-hosted handle (`useProjectHostname: false`, at creation or
1046
+ * via `withConfig`).
1047
+ * The same person carries a different id per project (`p*`) and one
1048
+ * global id (`g*`); registering both lets tests model that split.
1049
+ * Omit for single-id principals (robot tokens resolve to one `p-*` id
1050
+ * on every host).
1051
+ */
1052
+ globalUser?: CurrentUser;
1053
+ /**
1054
+ * Per-project user records, keyed by projectId — the same token
1055
+ * resolves to a different project-scoped id in each project, which is
1056
+ * what these model. A handle's project falls back to `user` when its
1057
+ * projectId has no entry.
1058
+ */
1059
+ usersByProject?: Record<string, CurrentUser>;
962
1060
  /** Grants served at the ACL path and enforced on reads/writes. */
963
1061
  grants: Grant[];
964
1062
  }