@sanity-labs/client-fake-for-test 0.9.1 → 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/README.md +3 -1
- package/dist/index.cjs +51 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -1
- package/dist/index.d.ts +10 -1
- package/dist/index.js +51 -18
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -80,7 +80,7 @@ More patterns — passing the fake to code typed against `SanityClient`, asserti
|
|
|
80
80
|
| Config | `config()` | Returns `{ projectId, dataset, perspective, token?, useProjectHostname }` |
|
|
81
81
|
| Config | `withConfig({ projectId?, dataset?, perspective?, accessControl?, token?, useProjectHostname? })` | Sibling handle into the same multi-dataset registry; `token` binds a registered identity (see the token→identity registry below), `accessControl` rebinds it inline — the two are mutually exclusive; `useProjectHostname: false` mints a global-hosted sibling |
|
|
82
82
|
| Perspectives | `raw` / `published` / `drafts` / release stacks | Per-client default or per-`fetch` override; draft/version layering with `_originalId` projection |
|
|
83
|
-
| HTTP | `request({ url? \| uri? })` | Present **only** when the handle has an acting identity (`accessControl`, or a bound `token`). Serves `/users/me` (host-aware: project-scoped record on the project host, `globalUser` on a global-hosted handle), the configured ACL path, and `/projects/<projectId>/users/<ids>` (project-user records with `id` + `sanityUserId`). Calling it without one throws a guidance error. |
|
|
83
|
+
| HTTP | `request({ url? \| uri? })` | Present **only** when the handle has an acting identity (`accessControl`, or a bound `token`). Serves `/users/me` (host-aware: project-scoped record on the project host, `globalUser` on a global-hosted handle), the configured ACL path, `/projects/<projectId>` (the project's members: `{ id, isRobot?, roles? }` rows, one per distinct resolved project user), and `/projects/<projectId>/users/<ids>` (project-user records with `id` + `sanityUserId`). Calling it without one throws a guidance error. |
|
|
84
84
|
|
|
85
85
|
#### Mutation return shapes
|
|
86
86
|
|
|
@@ -162,6 +162,8 @@ await client.withConfig({ useProjectHostname: false }).request({ uri: "/users/me
|
|
|
162
162
|
await client.request({ uri: "/projects/alpha/users/pAlpha" }); // { id: "pAlpha", sanityUserId: "gSnorre", ... } (ProjectUserRecord)
|
|
163
163
|
```
|
|
164
164
|
|
|
165
|
+
The project directory endpoint (`/projects/<projectId>`) returns each resolved project member's `id`, project-specific `roles`, and robot marker. Roles are served in canonical order, and duplicate-token agreement ignores role and property order. Registering two tokens with the same project user id is supported when their served member records agree; conflicting roles fail the request instead of making the result depend on registration order.
|
|
166
|
+
|
|
165
167
|
Grant-filter and mutation-guard `identity()` stay bound to the **project-scoped** record, matching the lake. Identities without `globalUser` serve one record everywhere — the production robot-token shape.
|
|
166
168
|
|
|
167
169
|
### Test-only helpers (not on real `SanityClient`)
|
package/dist/index.cjs
CHANGED
|
@@ -64,6 +64,28 @@ function defaultAclPath(projectId, dataset) {
|
|
|
64
64
|
function projectUserOf(identity, projectId) {
|
|
65
65
|
return identity.usersByProject?.[projectId] ?? identity.user;
|
|
66
66
|
}
|
|
67
|
+
function consistentProjectRecords(args) {
|
|
68
|
+
const byId = /* @__PURE__ */ new Map();
|
|
69
|
+
for (const record of args.records) {
|
|
70
|
+
const existing = byId.get(record.id);
|
|
71
|
+
if (existing !== void 0 && JSON.stringify(existing) !== JSON.stringify(record))
|
|
72
|
+
throw fakeError(
|
|
73
|
+
`request(): two registered identities resolve to project user "${record.id}" in project "${args.projectId}" with different records. The project records cannot be served unambiguously \u2014 fix the identity registrations so duplicate ids carry identical records.`
|
|
74
|
+
);
|
|
75
|
+
byId.set(record.id, record);
|
|
76
|
+
}
|
|
77
|
+
return byId;
|
|
78
|
+
}
|
|
79
|
+
function canonicalProjectRoles(roles) {
|
|
80
|
+
return roles.map((role) => ({
|
|
81
|
+
name: role.name,
|
|
82
|
+
...role.title !== void 0 ? { title: role.title } : {},
|
|
83
|
+
...role.description !== void 0 ? { description: role.description } : {}
|
|
84
|
+
})).toSorted((left, right) => {
|
|
85
|
+
const leftKey = JSON.stringify(left), rightKey = JSON.stringify(right);
|
|
86
|
+
return leftKey < rightKey ? -1 : leftKey > rightKey ? 1 : 0;
|
|
87
|
+
});
|
|
88
|
+
}
|
|
67
89
|
function serveProjectUsers(args) {
|
|
68
90
|
const { requestedIds, pathProjectId, identities } = args, toRecord = (identity) => {
|
|
69
91
|
const projectUser = projectUserOf(identity, pathProjectId);
|
|
@@ -73,33 +95,38 @@ function serveProjectUsers(args) {
|
|
|
73
95
|
displayName: projectUser.name ?? projectUser.id,
|
|
74
96
|
...projectUser.email !== void 0 ? { email: projectUser.email } : {}
|
|
75
97
|
};
|
|
76
|
-
}, byId =
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
throw fakeError(
|
|
81
|
-
`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.`
|
|
82
|
-
);
|
|
83
|
-
byId.set(resolvedId, identity);
|
|
84
|
-
}
|
|
85
|
-
const ids = requestedIds.split(","), [firstId] = ids;
|
|
98
|
+
}, byId = consistentProjectRecords({
|
|
99
|
+
projectId: pathProjectId,
|
|
100
|
+
records: identities.map(toRecord)
|
|
101
|
+
}), ids = requestedIds.split(","), [firstId] = ids;
|
|
86
102
|
if (ids.length === 1 && firstId !== void 0) {
|
|
87
|
-
const
|
|
88
|
-
if (
|
|
103
|
+
const record = byId.get(firstId);
|
|
104
|
+
if (record === void 0)
|
|
89
105
|
throw Object.assign(
|
|
90
106
|
fakeError(
|
|
91
107
|
`request(): no registered identity resolves to project user "${firstId}" in project "${pathProjectId}". Register it via createTestClient({ identities }) or addIdentity().`
|
|
92
108
|
),
|
|
93
109
|
{ statusCode: 404 }
|
|
94
110
|
);
|
|
95
|
-
return
|
|
111
|
+
return record;
|
|
96
112
|
}
|
|
97
113
|
return ids.flatMap((id) => {
|
|
98
|
-
const
|
|
99
|
-
return
|
|
114
|
+
const record = byId.get(id);
|
|
115
|
+
return record === void 0 ? [] : [record];
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
const PROJECT_USERS_PATH = /^\/projects\/([^/]+)\/users\/([^/]+)$/, PROJECT_PATH = /^\/projects\/([^/]+)$/;
|
|
119
|
+
function serveProject(args) {
|
|
120
|
+
const records = args.identities.map((identity) => {
|
|
121
|
+
const projectUser = projectUserOf(identity, args.pathProjectId);
|
|
122
|
+
return {
|
|
123
|
+
id: projectUser.id,
|
|
124
|
+
...projectUser.id.startsWith("p-") ? { isRobot: !0 } : {},
|
|
125
|
+
...projectUser.roles !== void 0 ? { roles: canonicalProjectRoles(projectUser.roles) } : {}
|
|
126
|
+
};
|
|
100
127
|
});
|
|
128
|
+
return { members: [...consistentProjectRecords({ projectId: args.pathProjectId, records }).values()] };
|
|
101
129
|
}
|
|
102
|
-
const PROJECT_USERS_PATH = /^\/projects\/([^/]+)\/users\/([^/]+)$/;
|
|
103
130
|
function makeRequestImpl(ctx) {
|
|
104
131
|
const { getAccess, projectId, dataset, useProjectHostname, listIdentities } = ctx;
|
|
105
132
|
return async (opts) => {
|
|
@@ -115,8 +142,14 @@ function makeRequestImpl(ctx) {
|
|
|
115
142
|
pathProjectId,
|
|
116
143
|
identities: listIdentities()
|
|
117
144
|
});
|
|
145
|
+
const project = path?.match(PROJECT_PATH), [, membersProjectId] = project ?? [];
|
|
146
|
+
if (membersProjectId !== void 0)
|
|
147
|
+
return serveProject({
|
|
148
|
+
pathProjectId: membersProjectId,
|
|
149
|
+
identities: listIdentities()
|
|
150
|
+
});
|
|
118
151
|
throw fakeError(
|
|
119
|
-
`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.`
|
|
152
|
+
`request(): no handler for path "${path}". With access control configured, the fake serves "/users/me", "${aclPath}", "/projects/<projectId>" (members), 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.`
|
|
120
153
|
);
|
|
121
154
|
};
|
|
122
155
|
}
|
|
@@ -827,7 +860,7 @@ const ISSUES_URL = "https://github.com/sanity-labs/client-fake-for-test/issues",
|
|
|
827
860
|
},
|
|
828
861
|
request: {
|
|
829
862
|
what: "generic HTTP request hook \u2014 only wired up when the handle has an acting identity",
|
|
830
|
-
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."
|
|
863
|
+
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, /projects/<projectId>, and /projects/<projectId>/users/<ids>). Or register a stub."
|
|
831
864
|
},
|
|
832
865
|
dryRun: {
|
|
833
866
|
what: "not a method \u2014 did you mean the `{ dryRun }` mutation option?",
|