@sanity-labs/client-fake-for-test 0.8.0 → 0.9.1
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/README.md +29 -5
- package/dist/index.cjs +108 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +84 -13
- package/dist/index.d.ts +84 -13
- package/dist/index.js +108 -27
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -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`
|
|
111
|
-
* path,
|
|
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,16 @@ export declare interface CreateTestClientOptions {
|
|
|
165
166
|
* tests stay green.
|
|
166
167
|
*/
|
|
167
168
|
perspective?: Perspective;
|
|
169
|
+
/** Prefix composed with request tags and used alone for untagged requests. */
|
|
170
|
+
requestTagPrefix?: string;
|
|
171
|
+
/**
|
|
172
|
+
* Which API host the root handle models: the project host (default
|
|
173
|
+
* `true`) or the global host (`false`) — same flag as the real
|
|
174
|
+
* `createClient` config. A global-hosted handle's `/users/me` serves
|
|
175
|
+
* the identity's account-global record. `withConfig({ useProjectHostname })`
|
|
176
|
+
* mints siblings on the other host.
|
|
177
|
+
*/
|
|
178
|
+
useProjectHostname?: boolean;
|
|
168
179
|
}
|
|
169
180
|
|
|
170
181
|
/**
|
|
@@ -583,6 +594,19 @@ export declare type PatchOp =
|
|
|
583
594
|
|
|
584
595
|
export declare type Perspective = "raw" | "published" | "drafts" | string[];
|
|
585
596
|
|
|
597
|
+
/**
|
|
598
|
+
* A row served by the project-users directory endpoint
|
|
599
|
+
* (`/projects/<projectId>/users/<id>`), mirroring Sanity's project user
|
|
600
|
+
* retrieval API: the project-scoped `id` alongside its account-global
|
|
601
|
+
* `sanityUserId` — the one sanctioned bridge between the two namespaces.
|
|
602
|
+
*/
|
|
603
|
+
export declare interface ProjectUserRecord {
|
|
604
|
+
id: string;
|
|
605
|
+
sanityUserId: string;
|
|
606
|
+
displayName: string;
|
|
607
|
+
email?: string;
|
|
608
|
+
}
|
|
609
|
+
|
|
586
610
|
/** Strip `drafts.` / `versions.<name>.` prefix to recover the published id. */
|
|
587
611
|
export declare function publishedIdOf(id: string): string;
|
|
588
612
|
|
|
@@ -843,14 +867,17 @@ export declare interface TestClient {
|
|
|
843
867
|
publishedId: string;
|
|
844
868
|
}) => Promise<ActionResult>;
|
|
845
869
|
/**
|
|
846
|
-
* Current `(projectId, dataset)
|
|
847
|
-
*
|
|
870
|
+
* Current `(projectId, dataset)`, active perspective, and which API host
|
|
871
|
+
* the handle models (`useProjectHostname`). `token` is present iff the
|
|
872
|
+
* handle is bound to a registered identity.
|
|
848
873
|
*/
|
|
849
874
|
config: () => {
|
|
850
875
|
projectId: string;
|
|
851
876
|
dataset: string;
|
|
852
877
|
perspective: Perspective;
|
|
878
|
+
requestTagPrefix?: string;
|
|
853
879
|
token?: string;
|
|
880
|
+
useProjectHostname: boolean;
|
|
854
881
|
};
|
|
855
882
|
/**
|
|
856
883
|
* Return a new client handle pointing at a different `(projectId, dataset)`
|
|
@@ -863,6 +890,16 @@ export declare interface TestClient {
|
|
|
863
890
|
projectId: string;
|
|
864
891
|
dataset: string;
|
|
865
892
|
perspective: Perspective;
|
|
893
|
+
/** Prefix composed with request tags and used alone for untagged requests. */
|
|
894
|
+
requestTagPrefix: string;
|
|
895
|
+
/**
|
|
896
|
+
* Model the global API host (`false`) instead of the project host
|
|
897
|
+
* (default `true`). A global-hosted sibling's `/users/me` serves the
|
|
898
|
+
* identity's account-global record (`globalUser`, falling back to the
|
|
899
|
+
* project record) — mirroring `@sanity/client`, where a non-project
|
|
900
|
+
* config routes requests to `api.sanity.io`.
|
|
901
|
+
*/
|
|
902
|
+
useProjectHostname: boolean;
|
|
866
903
|
/**
|
|
867
904
|
* Rebind the acting identity (and its grants) on a sibling that shares
|
|
868
905
|
* the same store — the test analogue of a token-scoped client. Lets two
|
|
@@ -873,10 +910,11 @@ export declare interface TestClient {
|
|
|
873
910
|
/**
|
|
874
911
|
* Bind the sibling to a token registered in the family's identity
|
|
875
912
|
* registry (`createTestClient({ identities })` / `addIdentity`): its
|
|
876
|
-
* `/users/me` serves that identity's
|
|
877
|
-
*
|
|
878
|
-
*
|
|
879
|
-
*
|
|
913
|
+
* `/users/me` serves that identity's record for the sibling's project
|
|
914
|
+
* and host, its ACL path serves that identity's grants, and
|
|
915
|
+
* reads/writes are gated through them — the token determines
|
|
916
|
+
* identity, exactly like production. Unknown tokens throw. Mutually
|
|
917
|
+
* exclusive with `accessControl`.
|
|
880
918
|
*/
|
|
881
919
|
token: string;
|
|
882
920
|
}>,
|
|
@@ -970,8 +1008,18 @@ export declare interface TestClient {
|
|
|
970
1008
|
}
|
|
971
1009
|
|
|
972
1010
|
export declare interface TestClientAccessControl {
|
|
973
|
-
/** What `client.request({ url: "/users/me" })` returns. */
|
|
1011
|
+
/** What `client.request({ url: "/users/me" })` returns on a project-hosted handle. */
|
|
974
1012
|
currentUser: CurrentUser;
|
|
1013
|
+
/**
|
|
1014
|
+
* What `/users/me` returns on a global-hosted handle
|
|
1015
|
+
* (`useProjectHostname: false`, at creation or via `withConfig`).
|
|
1016
|
+
* Sanity serves the
|
|
1017
|
+
* account-global user there (`g*` id, no roles) while the project host
|
|
1018
|
+
* serves the project-scoped record (`p*` id, project roles). Omit to
|
|
1019
|
+
* serve `currentUser` from both — the single-id collapse that is also
|
|
1020
|
+
* exactly how robot tokens behave in production.
|
|
1021
|
+
*/
|
|
1022
|
+
globalUser?: CurrentUser;
|
|
975
1023
|
/** What `client.request({ url: aclPath })` returns. */
|
|
976
1024
|
grants: Grant[];
|
|
977
1025
|
/**
|
|
@@ -984,13 +1032,36 @@ export declare interface TestClientAccessControl {
|
|
|
984
1032
|
/**
|
|
985
1033
|
* One entry in the token→identity registry (`createTestClient({ identities })`).
|
|
986
1034
|
* A handle bound to the entry's token behaves exactly like one configured
|
|
987
|
-
* with `accessControl: { currentUser:
|
|
988
|
-
*
|
|
989
|
-
*
|
|
1035
|
+
* with `accessControl: { currentUser: projectUserOf(identity, projectId),
|
|
1036
|
+
* globalUser, grants }` — `/users/me` serves the record for the handle's
|
|
1037
|
+
* project and host, the default ACL path serves `grants`, and reads/writes
|
|
1038
|
+
* are gated through them.
|
|
990
1039
|
*/
|
|
991
1040
|
export declare interface TestClientIdentity {
|
|
992
|
-
/**
|
|
1041
|
+
/**
|
|
1042
|
+
* The project-scoped user record: what `/users/me` returns on a
|
|
1043
|
+
* project-hosted handle, and the id `identity()` resolves to in grant
|
|
1044
|
+
* filters and mutation guards. Per-project variants go in
|
|
1045
|
+
* `usersByProject`; this is the record for any project without one.
|
|
1046
|
+
*/
|
|
993
1047
|
user: CurrentUser;
|
|
1048
|
+
/**
|
|
1049
|
+
* The account-global user record, served by `/users/me` on a
|
|
1050
|
+
* global-hosted handle (`useProjectHostname: false`, at creation or
|
|
1051
|
+
* via `withConfig`).
|
|
1052
|
+
* The same person carries a different id per project (`p*`) and one
|
|
1053
|
+
* global id (`g*`); registering both lets tests model that split.
|
|
1054
|
+
* Omit for single-id principals (robot tokens resolve to one `p-*` id
|
|
1055
|
+
* on every host).
|
|
1056
|
+
*/
|
|
1057
|
+
globalUser?: CurrentUser;
|
|
1058
|
+
/**
|
|
1059
|
+
* Per-project user records, keyed by projectId — the same token
|
|
1060
|
+
* resolves to a different project-scoped id in each project, which is
|
|
1061
|
+
* what these model. A handle's project falls back to `user` when its
|
|
1062
|
+
* projectId has no entry.
|
|
1063
|
+
*/
|
|
1064
|
+
usersByProject?: Record<string, CurrentUser>;
|
|
994
1065
|
/** Grants served at the ACL path and enforced on reads/writes. */
|
|
995
1066
|
grants: Grant[];
|
|
996
1067
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -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`
|
|
111
|
-
* path,
|
|
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,16 @@ export declare interface CreateTestClientOptions {
|
|
|
165
166
|
* tests stay green.
|
|
166
167
|
*/
|
|
167
168
|
perspective?: Perspective;
|
|
169
|
+
/** Prefix composed with request tags and used alone for untagged requests. */
|
|
170
|
+
requestTagPrefix?: string;
|
|
171
|
+
/**
|
|
172
|
+
* Which API host the root handle models: the project host (default
|
|
173
|
+
* `true`) or the global host (`false`) — same flag as the real
|
|
174
|
+
* `createClient` config. A global-hosted handle's `/users/me` serves
|
|
175
|
+
* the identity's account-global record. `withConfig({ useProjectHostname })`
|
|
176
|
+
* mints siblings on the other host.
|
|
177
|
+
*/
|
|
178
|
+
useProjectHostname?: boolean;
|
|
168
179
|
}
|
|
169
180
|
|
|
170
181
|
/**
|
|
@@ -583,6 +594,19 @@ export declare type PatchOp =
|
|
|
583
594
|
|
|
584
595
|
export declare type Perspective = "raw" | "published" | "drafts" | string[];
|
|
585
596
|
|
|
597
|
+
/**
|
|
598
|
+
* A row served by the project-users directory endpoint
|
|
599
|
+
* (`/projects/<projectId>/users/<id>`), mirroring Sanity's project user
|
|
600
|
+
* retrieval API: the project-scoped `id` alongside its account-global
|
|
601
|
+
* `sanityUserId` — the one sanctioned bridge between the two namespaces.
|
|
602
|
+
*/
|
|
603
|
+
export declare interface ProjectUserRecord {
|
|
604
|
+
id: string;
|
|
605
|
+
sanityUserId: string;
|
|
606
|
+
displayName: string;
|
|
607
|
+
email?: string;
|
|
608
|
+
}
|
|
609
|
+
|
|
586
610
|
/** Strip `drafts.` / `versions.<name>.` prefix to recover the published id. */
|
|
587
611
|
export declare function publishedIdOf(id: string): string;
|
|
588
612
|
|
|
@@ -843,14 +867,17 @@ export declare interface TestClient {
|
|
|
843
867
|
publishedId: string;
|
|
844
868
|
}) => Promise<ActionResult>;
|
|
845
869
|
/**
|
|
846
|
-
* Current `(projectId, dataset)
|
|
847
|
-
*
|
|
870
|
+
* Current `(projectId, dataset)`, active perspective, and which API host
|
|
871
|
+
* the handle models (`useProjectHostname`). `token` is present iff the
|
|
872
|
+
* handle is bound to a registered identity.
|
|
848
873
|
*/
|
|
849
874
|
config: () => {
|
|
850
875
|
projectId: string;
|
|
851
876
|
dataset: string;
|
|
852
877
|
perspective: Perspective;
|
|
878
|
+
requestTagPrefix?: string;
|
|
853
879
|
token?: string;
|
|
880
|
+
useProjectHostname: boolean;
|
|
854
881
|
};
|
|
855
882
|
/**
|
|
856
883
|
* Return a new client handle pointing at a different `(projectId, dataset)`
|
|
@@ -863,6 +890,16 @@ export declare interface TestClient {
|
|
|
863
890
|
projectId: string;
|
|
864
891
|
dataset: string;
|
|
865
892
|
perspective: Perspective;
|
|
893
|
+
/** Prefix composed with request tags and used alone for untagged requests. */
|
|
894
|
+
requestTagPrefix: string;
|
|
895
|
+
/**
|
|
896
|
+
* Model the global API host (`false`) instead of the project host
|
|
897
|
+
* (default `true`). A global-hosted sibling's `/users/me` serves the
|
|
898
|
+
* identity's account-global record (`globalUser`, falling back to the
|
|
899
|
+
* project record) — mirroring `@sanity/client`, where a non-project
|
|
900
|
+
* config routes requests to `api.sanity.io`.
|
|
901
|
+
*/
|
|
902
|
+
useProjectHostname: boolean;
|
|
866
903
|
/**
|
|
867
904
|
* Rebind the acting identity (and its grants) on a sibling that shares
|
|
868
905
|
* the same store — the test analogue of a token-scoped client. Lets two
|
|
@@ -873,10 +910,11 @@ export declare interface TestClient {
|
|
|
873
910
|
/**
|
|
874
911
|
* Bind the sibling to a token registered in the family's identity
|
|
875
912
|
* registry (`createTestClient({ identities })` / `addIdentity`): its
|
|
876
|
-
* `/users/me` serves that identity's
|
|
877
|
-
*
|
|
878
|
-
*
|
|
879
|
-
*
|
|
913
|
+
* `/users/me` serves that identity's record for the sibling's project
|
|
914
|
+
* and host, its ACL path serves that identity's grants, and
|
|
915
|
+
* reads/writes are gated through them — the token determines
|
|
916
|
+
* identity, exactly like production. Unknown tokens throw. Mutually
|
|
917
|
+
* exclusive with `accessControl`.
|
|
880
918
|
*/
|
|
881
919
|
token: string;
|
|
882
920
|
}>,
|
|
@@ -970,8 +1008,18 @@ export declare interface TestClient {
|
|
|
970
1008
|
}
|
|
971
1009
|
|
|
972
1010
|
export declare interface TestClientAccessControl {
|
|
973
|
-
/** What `client.request({ url: "/users/me" })` returns. */
|
|
1011
|
+
/** What `client.request({ url: "/users/me" })` returns on a project-hosted handle. */
|
|
974
1012
|
currentUser: CurrentUser;
|
|
1013
|
+
/**
|
|
1014
|
+
* What `/users/me` returns on a global-hosted handle
|
|
1015
|
+
* (`useProjectHostname: false`, at creation or via `withConfig`).
|
|
1016
|
+
* Sanity serves the
|
|
1017
|
+
* account-global user there (`g*` id, no roles) while the project host
|
|
1018
|
+
* serves the project-scoped record (`p*` id, project roles). Omit to
|
|
1019
|
+
* serve `currentUser` from both — the single-id collapse that is also
|
|
1020
|
+
* exactly how robot tokens behave in production.
|
|
1021
|
+
*/
|
|
1022
|
+
globalUser?: CurrentUser;
|
|
975
1023
|
/** What `client.request({ url: aclPath })` returns. */
|
|
976
1024
|
grants: Grant[];
|
|
977
1025
|
/**
|
|
@@ -984,13 +1032,36 @@ export declare interface TestClientAccessControl {
|
|
|
984
1032
|
/**
|
|
985
1033
|
* One entry in the token→identity registry (`createTestClient({ identities })`).
|
|
986
1034
|
* A handle bound to the entry's token behaves exactly like one configured
|
|
987
|
-
* with `accessControl: { currentUser:
|
|
988
|
-
*
|
|
989
|
-
*
|
|
1035
|
+
* with `accessControl: { currentUser: projectUserOf(identity, projectId),
|
|
1036
|
+
* globalUser, grants }` — `/users/me` serves the record for the handle's
|
|
1037
|
+
* project and host, the default ACL path serves `grants`, and reads/writes
|
|
1038
|
+
* are gated through them.
|
|
990
1039
|
*/
|
|
991
1040
|
export declare interface TestClientIdentity {
|
|
992
|
-
/**
|
|
1041
|
+
/**
|
|
1042
|
+
* The project-scoped user record: what `/users/me` returns on a
|
|
1043
|
+
* project-hosted handle, and the id `identity()` resolves to in grant
|
|
1044
|
+
* filters and mutation guards. Per-project variants go in
|
|
1045
|
+
* `usersByProject`; this is the record for any project without one.
|
|
1046
|
+
*/
|
|
993
1047
|
user: CurrentUser;
|
|
1048
|
+
/**
|
|
1049
|
+
* The account-global user record, served by `/users/me` on a
|
|
1050
|
+
* global-hosted handle (`useProjectHostname: false`, at creation or
|
|
1051
|
+
* via `withConfig`).
|
|
1052
|
+
* The same person carries a different id per project (`p*`) and one
|
|
1053
|
+
* global id (`g*`); registering both lets tests model that split.
|
|
1054
|
+
* Omit for single-id principals (robot tokens resolve to one `p-*` id
|
|
1055
|
+
* on every host).
|
|
1056
|
+
*/
|
|
1057
|
+
globalUser?: CurrentUser;
|
|
1058
|
+
/**
|
|
1059
|
+
* Per-project user records, keyed by projectId — the same token
|
|
1060
|
+
* resolves to a different project-scoped id in each project, which is
|
|
1061
|
+
* what these model. A handle's project falls back to `user` when its
|
|
1062
|
+
* projectId has no entry.
|
|
1063
|
+
*/
|
|
1064
|
+
usersByProject?: Record<string, CurrentUser>;
|
|
994
1065
|
/** Grants served at the ACL path and enforced on reads/writes. */
|
|
995
1066
|
grants: Grant[];
|
|
996
1067
|
}
|
package/dist/index.js
CHANGED
|
@@ -59,15 +59,62 @@ async function enforceWriteAccess(args) {
|
|
|
59
59
|
function defaultAclPath(projectId, dataset) {
|
|
60
60
|
return `/projects/${projectId}/datasets/${dataset}/acl`;
|
|
61
61
|
}
|
|
62
|
-
function
|
|
62
|
+
function projectUserOf(identity, projectId) {
|
|
63
|
+
return identity.usersByProject?.[projectId] ?? identity.user;
|
|
64
|
+
}
|
|
65
|
+
function serveProjectUsers(args) {
|
|
66
|
+
const { requestedIds, pathProjectId, identities } = args, toRecord = (identity) => {
|
|
67
|
+
const projectUser = projectUserOf(identity, pathProjectId);
|
|
68
|
+
return {
|
|
69
|
+
id: projectUser.id,
|
|
70
|
+
sanityUserId: identity.globalUser?.id ?? projectUser.id,
|
|
71
|
+
displayName: projectUser.name ?? projectUser.id,
|
|
72
|
+
...projectUser.email !== void 0 ? { email: projectUser.email } : {}
|
|
73
|
+
};
|
|
74
|
+
}, byId = /* @__PURE__ */ new Map();
|
|
75
|
+
for (const identity of identities) {
|
|
76
|
+
const resolvedId = projectUserOf(identity, pathProjectId).id, existing = byId.get(resolvedId);
|
|
77
|
+
if (existing !== void 0 && JSON.stringify(toRecord(existing)) !== JSON.stringify(toRecord(identity)))
|
|
78
|
+
throw fakeError(
|
|
79
|
+
`request(): two registered identities resolve to project user "${resolvedId}" in project "${pathProjectId}" with different records. The directory cannot answer unambiguously \u2014 fix the identity registrations so duplicate ids carry identical records.`
|
|
80
|
+
);
|
|
81
|
+
byId.set(resolvedId, identity);
|
|
82
|
+
}
|
|
83
|
+
const ids = requestedIds.split(","), [firstId] = ids;
|
|
84
|
+
if (ids.length === 1 && firstId !== void 0) {
|
|
85
|
+
const identity = byId.get(firstId);
|
|
86
|
+
if (identity === void 0)
|
|
87
|
+
throw Object.assign(
|
|
88
|
+
fakeError(
|
|
89
|
+
`request(): no registered identity resolves to project user "${firstId}" in project "${pathProjectId}". Register it via createTestClient({ identities }) or addIdentity().`
|
|
90
|
+
),
|
|
91
|
+
{ statusCode: 404 }
|
|
92
|
+
);
|
|
93
|
+
return toRecord(identity);
|
|
94
|
+
}
|
|
95
|
+
return ids.flatMap((id) => {
|
|
96
|
+
const identity = byId.get(id);
|
|
97
|
+
return identity === void 0 ? [] : [toRecord(identity)];
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
const PROJECT_USERS_PATH = /^\/projects\/([^/]+)\/users\/([^/]+)$/;
|
|
101
|
+
function makeRequestImpl(ctx) {
|
|
102
|
+
const { getAccess, projectId, dataset, useProjectHostname, listIdentities } = ctx;
|
|
63
103
|
return async (opts) => {
|
|
64
104
|
const accessControl = getAccess(), aclPath = accessControl.aclPath ?? defaultAclPath(projectId, dataset), path = opts.uri ?? opts.url;
|
|
65
105
|
if (path === "/users/me")
|
|
66
|
-
return accessControl.currentUser;
|
|
106
|
+
return useProjectHostname ? accessControl.currentUser : accessControl.globalUser ?? accessControl.currentUser;
|
|
67
107
|
if (path === aclPath)
|
|
68
108
|
return accessControl.grants;
|
|
109
|
+
const projectUsers = path?.match(PROJECT_USERS_PATH), [, pathProjectId, requestedIds] = projectUsers ?? [];
|
|
110
|
+
if (pathProjectId !== void 0 && requestedIds !== void 0)
|
|
111
|
+
return serveProjectUsers({
|
|
112
|
+
requestedIds,
|
|
113
|
+
pathProjectId,
|
|
114
|
+
identities: listIdentities()
|
|
115
|
+
});
|
|
69
116
|
throw fakeError(
|
|
70
|
-
`request(): no handler for path "${path}". With access control configured, the fake
|
|
117
|
+
`request(): no handler for path "${path}". With access control configured, the fake serves "/users/me", "${aclPath}", and "/projects/<projectId>/users/<ids>". For any other endpoint, register a stub (client.stub("request", fn)) or intercept the HTTP call with a tool like MSW.`
|
|
71
118
|
);
|
|
72
119
|
};
|
|
73
120
|
}
|
|
@@ -778,7 +825,7 @@ const ISSUES_URL = "https://github.com/sanity-labs/client-fake-for-test/issues",
|
|
|
778
825
|
},
|
|
779
826
|
request: {
|
|
780
827
|
what: "generic HTTP request hook \u2014 only wired up when the handle has an acting identity",
|
|
781
|
-
workaround: "Create the client with `accessControl`, or bind a registered token (`identities` + `token` / `withConfig({ token })`), to enable request() (it serves /users/me
|
|
828
|
+
workaround: "Create the client with `accessControl`, or bind a registered token (`identities` + `token` / `withConfig({ token })`), to enable request() (it serves /users/me, the ACL path, and /projects/<projectId>/users/<ids>). Or register a stub."
|
|
782
829
|
},
|
|
783
830
|
dryRun: {
|
|
784
831
|
what: "not a method \u2014 did you mean the `{ dryRun }` mutation option?",
|
|
@@ -961,15 +1008,17 @@ function createTestClient(options = {}) {
|
|
|
961
1008
|
const { projectId, dataset } = parseRegistryKey(key);
|
|
962
1009
|
ensureStore(projectId, dataset).seed(docs);
|
|
963
1010
|
}
|
|
964
|
-
return makeHandle(
|
|
1011
|
+
return makeHandle({
|
|
965
1012
|
backend,
|
|
966
|
-
defaultProjectId,
|
|
967
|
-
defaultDataset,
|
|
968
|
-
options.accessControl,
|
|
969
|
-
options.perspective ?? "raw",
|
|
970
|
-
options.
|
|
971
|
-
options.
|
|
972
|
-
|
|
1013
|
+
projectId: defaultProjectId,
|
|
1014
|
+
dataset: defaultDataset,
|
|
1015
|
+
accessControl: options.accessControl,
|
|
1016
|
+
perspective: options.perspective ?? "raw",
|
|
1017
|
+
requestTagPrefix: options.requestTagPrefix,
|
|
1018
|
+
mutationGuard: options.mutationGuard,
|
|
1019
|
+
token: options.token,
|
|
1020
|
+
useProjectHostname: options.useProjectHostname ?? !0
|
|
1021
|
+
});
|
|
973
1022
|
}
|
|
974
1023
|
function dedupeIds(results) {
|
|
975
1024
|
const seen = /* @__PURE__ */ new Set(), out = [];
|
|
@@ -1021,13 +1070,27 @@ function toTxnMutations(operations) {
|
|
|
1021
1070
|
}
|
|
1022
1071
|
];
|
|
1023
1072
|
}
|
|
1024
|
-
function makeHandle(
|
|
1025
|
-
const {
|
|
1073
|
+
function makeHandle(handleArgs) {
|
|
1074
|
+
const {
|
|
1075
|
+
backend,
|
|
1076
|
+
projectId,
|
|
1077
|
+
dataset,
|
|
1078
|
+
accessControl,
|
|
1079
|
+
perspective,
|
|
1080
|
+
requestTagPrefix,
|
|
1081
|
+
mutationGuard,
|
|
1082
|
+
token,
|
|
1083
|
+
useProjectHostname
|
|
1084
|
+
} = handleArgs, { registry } = backend, activeAccess = () => {
|
|
1026
1085
|
if (token === void 0) return accessControl;
|
|
1027
1086
|
const identity = backend.identities.get(token);
|
|
1028
1087
|
if (identity === void 0)
|
|
1029
1088
|
throw fakeError(`no identity is registered for token "${token}" (was it removed?).`);
|
|
1030
|
-
return {
|
|
1089
|
+
return {
|
|
1090
|
+
currentUser: projectUserOf(identity, projectId),
|
|
1091
|
+
...identity.globalUser !== void 0 ? { globalUser: identity.globalUser } : {},
|
|
1092
|
+
grants: identity.grants
|
|
1093
|
+
};
|
|
1031
1094
|
}, requireActiveAccess = () => {
|
|
1032
1095
|
const access = activeAccess();
|
|
1033
1096
|
if (access === void 0)
|
|
@@ -1038,14 +1101,15 @@ function makeHandle(backend, projectId, dataset, accessControl, perspective, mut
|
|
|
1038
1101
|
let s = registry.get(key);
|
|
1039
1102
|
return s === void 0 && (s = createTestStore(), registry.set(key, s)), s;
|
|
1040
1103
|
})(), concurrentEditKey = (id) => registryKey(projectId, dataset) + " " + id, record = (transactionId, mutations, tag) => {
|
|
1104
|
+
const effectiveTag = effectiveRequestTag(tag);
|
|
1041
1105
|
backend.log.push({
|
|
1042
1106
|
transactionId,
|
|
1043
1107
|
projectId,
|
|
1044
1108
|
dataset,
|
|
1045
1109
|
mutations,
|
|
1046
|
-
...
|
|
1110
|
+
...effectiveTag !== void 0 ? { tag: effectiveTag } : {}
|
|
1047
1111
|
});
|
|
1048
|
-
}, writeResult = (documents, results, transactionId, opts) => formatMutationResult(
|
|
1112
|
+
}, effectiveRequestTag = (tag) => requestTagPrefix === void 0 ? tag : tag ? `${requestTagPrefix}.${tag}` : requestTagPrefix, writeResult = (documents, results, transactionId, opts) => formatMutationResult(
|
|
1049
1113
|
documents,
|
|
1050
1114
|
results,
|
|
1051
1115
|
transactionId,
|
|
@@ -1348,20 +1412,37 @@ function makeHandle(backend, projectId, dataset, accessControl, perspective, mut
|
|
|
1348
1412
|
versionId: `versions.${releaseId}.${publishedId}`,
|
|
1349
1413
|
publishedId
|
|
1350
1414
|
}),
|
|
1351
|
-
...token !== void 0 || accessControl !== void 0 ? {
|
|
1352
|
-
|
|
1415
|
+
...token !== void 0 || accessControl !== void 0 ? {
|
|
1416
|
+
request: makeRequestImpl({
|
|
1417
|
+
getAccess: requireActiveAccess,
|
|
1418
|
+
projectId,
|
|
1419
|
+
dataset,
|
|
1420
|
+
useProjectHostname,
|
|
1421
|
+
listIdentities: () => [...backend.identities.values()]
|
|
1422
|
+
})
|
|
1423
|
+
} : {},
|
|
1424
|
+
config: () => ({
|
|
1425
|
+
projectId,
|
|
1426
|
+
dataset,
|
|
1427
|
+
perspective,
|
|
1428
|
+
...requestTagPrefix !== void 0 ? { requestTagPrefix } : {},
|
|
1429
|
+
...token !== void 0 ? { token } : {},
|
|
1430
|
+
useProjectHostname
|
|
1431
|
+
}),
|
|
1353
1432
|
withConfig: (config) => {
|
|
1354
1433
|
rejectAmbiguousIdentity("withConfig()", config), config.token !== void 0 && requireIdentity(backend, config.token, "withConfig()");
|
|
1355
1434
|
const rebinds = config.token !== void 0 || config.accessControl !== void 0;
|
|
1356
|
-
return makeHandle(
|
|
1435
|
+
return makeHandle({
|
|
1357
1436
|
backend,
|
|
1358
|
-
config.projectId ?? projectId,
|
|
1359
|
-
config.dataset ?? dataset,
|
|
1360
|
-
rebinds ? config.accessControl : accessControl,
|
|
1361
|
-
config.perspective ?? perspective,
|
|
1437
|
+
projectId: config.projectId ?? projectId,
|
|
1438
|
+
dataset: config.dataset ?? dataset,
|
|
1439
|
+
accessControl: rebinds ? config.accessControl : accessControl,
|
|
1440
|
+
perspective: config.perspective ?? perspective,
|
|
1441
|
+
requestTagPrefix: config.requestTagPrefix ?? requestTagPrefix,
|
|
1362
1442
|
mutationGuard,
|
|
1363
|
-
rebinds ? config.token : token
|
|
1364
|
-
|
|
1443
|
+
token: rebinds ? config.token : token,
|
|
1444
|
+
useProjectHostname: config.useProjectHostname ?? useProjectHostname
|
|
1445
|
+
});
|
|
1365
1446
|
},
|
|
1366
1447
|
seed: (documents) => store.seed(documents),
|
|
1367
1448
|
add: (documents) => store.add(documents),
|
|
@@ -1395,7 +1476,7 @@ function makeHandle(backend, projectId, dataset, accessControl, perspective, mut
|
|
|
1395
1476
|
return wrapWithGuards(client, {
|
|
1396
1477
|
getStub: (name) => backend.stubs.get(name),
|
|
1397
1478
|
onCall: (method, args) => {
|
|
1398
|
-
const rec = { method, args, projectId, dataset }, tag = tagFromArgs(method, args);
|
|
1479
|
+
const rec = { method, args, projectId, dataset }, tag = OPTIONS_ARG_POSITION[method] === void 0 ? void 0 : effectiveRequestTag(tagFromArgs(method, args));
|
|
1399
1480
|
return tag !== void 0 && (rec.tag = tag), backend.calls.push(rec), (outcome) => {
|
|
1400
1481
|
"error" in outcome ? rec.error = outcome.error : rec.result = outcome.result;
|
|
1401
1482
|
};
|