@sanity-labs/client-fake-for-test 0.12.0 → 0.14.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 +43 -6
- package/dist/index.cjs +663 -56
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +172 -11
- package/dist/index.d.ts +172 -11
- package/dist/index.js +663 -56
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -77,10 +77,11 @@ More patterns — passing the fake to code typed against `SanityClient`, asserti
|
|
|
77
77
|
| Transaction | `transaction()` chain → `.commit()` | Mutations: `.create`, `.createOrReplace`, `.createIfNotExists`, `.delete`, `.patch(id, recipe)`. Atomic — any throw rolls back the whole batch. Default result is a `MultipleMutationResult` (`{ transactionId, documentIds, results }`) |
|
|
78
78
|
| Actions | `action(action \| action[])` | Content Lake release / version actions, applied atomically |
|
|
79
79
|
| Releases | `releases.*`, `createVersion`, `unpublishVersion` | Content Releases lifecycle — create / edit / schedule / publish / archive / delete, plus version create / replace / discard / unpublish |
|
|
80
|
-
| Config | `config()` | Returns `{ projectId, dataset, perspective, token?, useProjectHostname }` |
|
|
81
|
-
| Config | `
|
|
82
|
-
|
|
|
83
|
-
|
|
|
80
|
+
| Config | `config()` | Returns `{ projectId, dataset, resource?, perspective, token?, useProjectHostname }` |
|
|
81
|
+
| Config | `getDataUrl(operation, path?)` | The same URL `@sanity/client` builds: `<resource base>/<operation>/<path>` on a `resource` handle, `/data/<operation>/<dataset>/<path>` otherwise |
|
|
82
|
+
| Config | `withConfig({ projectId?, dataset?, resource?, perspective?, accessControl?, token?, useProjectHostname? })` | Sibling handle into the same multi-resource registry; `resource` points it at a Media Library, Canvas, dashboard or dataset (see [Resource types](#resource-types-media-library-canvas-dashboard)); `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 |
|
|
83
|
+
| Perspectives | `raw` / `published` / `drafts` / release stacks / a single release name or comma-joined stack (`"rA"`, `"drafts,rA"`), as `@sanity/client` sends them | Per-client default or per-`fetch` override; draft/version layering with `_originalId` projection |
|
|
84
|
+
| 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 or `resource` handle), the handle's ACL (`<data base>/acl`, or an explicit `aclPath`), `/projects/<projectId>` (the project's members: `{ id, isRobot?, roles? }` rows, one per distinct resolved project user, plus `organizationId` when configured), `/projects/<projectId>/users/<ids>` (project-user records with `id` + `sanityUserId`), the History API, resource lookup and the Access API (see [Resource types](#resource-types-media-library-canvas-dashboard)). Calling it without one throws a guidance error. |
|
|
84
85
|
|
|
85
86
|
#### Mutation return shapes
|
|
86
87
|
|
|
@@ -171,7 +172,41 @@ await client.request({ uri: "/projects/alpha/users/pAlpha" }); // { id: "pAlpha"
|
|
|
171
172
|
|
|
172
173
|
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.
|
|
173
174
|
|
|
174
|
-
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.
|
|
175
|
+
Grant-filter and mutation-guard `identity()` stay bound to the **project-scoped** record on a dataset, matching the lake. On a Media Library, Canvas or dashboard there is no project, so they see the account-global record (`globalUser`, falling back to `user`). Identities without `globalUser` serve one record everywhere — the production robot-token shape.
|
|
176
|
+
|
|
177
|
+
### Resource types: Media Library, Canvas, dashboard
|
|
178
|
+
|
|
179
|
+
A handle configured with `resource: { type, id }` — the same config `@sanity/client` takes — reads and writes that resource's own store, and answers each capability the way the platform does on that resource type (staging probe, 2026-11-06). Where the platform says no, the fake throws the platform's status and body, shaped like `@sanity/client`'s `ClientError` (`statusCode`, `message`, `response.body`, `details`).
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
const client = createTestClient({
|
|
183
|
+
resource: { type: "media-library", id: "ml1" },
|
|
184
|
+
organizationId: "org1",
|
|
185
|
+
identities: { "t-robot": robot },
|
|
186
|
+
token: "t-robot",
|
|
187
|
+
});
|
|
188
|
+
const canvas = client.withConfig({ resource: { type: "canvas", id: "c1" } });
|
|
189
|
+
await client.request({ url: client.getDataUrl("history", `documents/a?revision=${rev}`) });
|
|
190
|
+
await canvas.releases.create({ releaseId: "r1" }); // 404 Cannot POST /canvases/c1/actions
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
| Capability | dataset | media-library | canvas | dashboard |
|
|
194
|
+
| --- | --- | --- | --- | --- |
|
|
195
|
+
| `drafts.` / `versions.` ids, perspectives | ✅ | ✅ | ✅ | ✅ |
|
|
196
|
+
| `/actions` (releases, version and document actions) | ✅ | ✅, except `release.create` → 403 `releaseLimitExceededError` | 404 `Cannot POST …/actions` | 404 `Cannot POST …/actions` |
|
|
197
|
+
| History: `documents/<ids>?revision=`, `transactions/<ids>` (NDJSON), `events/documents/<ids>` | ✅ | ✅ | ✅ | 404 `Cannot GET …` |
|
|
198
|
+
| ACL `<data base>/acl` | ✅ | ✅ | ✅ | 404 `Cannot GET …` |
|
|
199
|
+
| Access API `…/user-permissions/me`, `…/me/check` | ✅ | ✅ | ✅ | ✅ |
|
|
200
|
+
| Access API `…/users` | ✅ | ✅ | 403 | 403 |
|
|
201
|
+
| Access API `…/users/<sanityUserId>` (served on `organization`, below) | 403 | 403 | 403 | 403 |
|
|
202
|
+
| Access API `…/roles` | ✅ | 403 | 403 | 403 |
|
|
203
|
+
| Org lookup | `/projects/<projectId>` | `GET /media-libraries/<id>` | `GET /canvases/<id>` | `GET /dashboards/<id>` |
|
|
204
|
+
|
|
205
|
+
- **Access API.** Paths are `/access/<type>/<id>/…` with type `organization`, `project`, `media-library`, `canvas` or `dashboard`. An identity's `access` entries, keyed `"<type>:<id>"`, hold its `roles` there (membership) and the `permissions` the `user-permissions` endpoints report. Every identity is a project member with its project roles. A user row is `{ sanityUserId, profile: { id, displayName, email, provider, createdAt }, memberships: [{ resourceType, resourceId, roleNames }] }`; the fake does not model `provider` or `createdAt`, so they are always `"sanity"` and the epoch, and `email` is `""` where the identity seeds none. Listings come back on one page (`nextCursor: null`); users listings add `totalCount`. `includeImpliedRoles` is accepted and ignored: rows carry the seeded roles only, so seed an implied role explicitly when a test needs it. An unknown type answers 400 `Invalid resource type <type>`. An id the registry does not hold (never seeded, written or addressed) answers 404 `Media library not found` / `Canvas not found` / `Dashboard not found`, except `…/me/check`, which answers `false` as staging does. `…/users/<sanityUserId>` is served only on the organization, where the fake checks members read; elsewhere it answers 403 (the reference requires `sanity.<type>.members.read` for it; no type's single-user read was probed on staging, only the media-library listing). The other 403 cells were not proven on staging (no token could list them), so they answer the way the platform refuses a missing permission: `{ statusCode: 403, error: "Forbidden", message: "You do not have permission to access this resource" }`.
|
|
206
|
+
- **Organization.** `/access/organization/<organizationId>/users` and `…/users/<sanityUserId>` (one row, or 404 `User not found` for a non-member) serve the members of the client's `organizationId`: identities with roles in their `"organization:<id>"` entry. Each row lists the organization membership, then the member's roles on every resource the client holds (its libraries, canvases, dashboards and projects), as the platform's organization row does. Like the platform, it needs `sanity.organization.members.read` in the actor's `"organization:<id>"` `permissions` (the built-in `member` role grants it), else 403. Another organization id answers 404 `Organization not found`; `…/roles` answers 403.
|
|
207
|
+
- **History.** Every committed write since the client was created is recorded per resource, authored by the acting identity; seeded documents can be read at their seeded `_rev`. A handle answers for its own resource, and hides documents its grants cannot read, as reads do (a transaction that also touched such a document is served without it). Events model `createLiveDocument` / `updateLiveDocument` only.
|
|
208
|
+
- **Org lookup.** `createTestClient({ organizationId })` names the organization every resource of the client belongs to.
|
|
209
|
+
- **Identity.** `@sanity/client` sends a `resource` client's requests to the global host, so `/users/me` answers `globalUser`.
|
|
175
210
|
|
|
176
211
|
### Test-only helpers (not on real `SanityClient`)
|
|
177
212
|
|
|
@@ -185,6 +220,7 @@ Useful in test bodies, but a giveaway that the code under test isn't using the r
|
|
|
185
220
|
| `client.reset()` | Drop everything in the current dataset |
|
|
186
221
|
| `client.store()` | Direct access to the underlying `TestStore` |
|
|
187
222
|
| `client.datasets()` | `{ projectId, dataset }[]` known to the registry |
|
|
223
|
+
| `client.resources()` | Every resource known to the registry, `{ type, id }[]` (datasets as `id: "<projectId>.<dataset>"`) |
|
|
188
224
|
| `client.mutations()` | Every committed mutation, flattened and in order — assert exactly what writes your code submitted |
|
|
189
225
|
| `client.transactions()` | Every committed transaction (a direct mutation is a one-mutation transaction), each tagged with `transactionId` + `(projectId, dataset)` |
|
|
190
226
|
| `client.clearLog()` | Reset the mutation / transaction log |
|
|
@@ -196,6 +232,7 @@ Useful in test bodies, but a giveaway that the code under test isn't using the r
|
|
|
196
232
|
| `client.addIdentity(token, identity)` | Register (upsert) a token→identity entry in the family-shared registry |
|
|
197
233
|
| `client.identities()` | Snapshot of the registered token→identity entries |
|
|
198
234
|
| `createTestClient({ datasets: { "p:d": [...] } })` | Seed multiple `(projectId, dataset)` stores up front |
|
|
235
|
+
| `createTestClient({ resources: { "media-library:ml1": [...] } })` | Seed any resource type up front; keys are `"<type>:<id>"` |
|
|
199
236
|
|
|
200
237
|
#### Two layers of observability
|
|
201
238
|
|
|
@@ -252,7 +289,7 @@ Nested access reports the full path (`client.assets.upload(...)` → `client.ass
|
|
|
252
289
|
| --- | --- | --- |
|
|
253
290
|
| `listen(...)` — real-time | ❌ Guarded | Test subscription logic separately, or `client.stub("listen", fn)` |
|
|
254
291
|
| `clone()` | ❌ Guarded | `withConfig({})` returns a sibling handle |
|
|
255
|
-
| `getUrl(uri)`
|
|
292
|
+
| `getUrl(uri)` — URL builder | ❌ Guarded | Hard-code in tests, or stub |
|
|
256
293
|
| `request(...)` — generic HTTP | ⚠️ Only with an acting identity | Configure `accessControl` or bind a registered `token`, stub it, or use [MSW](https://mswjs.io) |
|
|
257
294
|
| `agent.*` / `assets.*` / `projects.*` / `users.*` / `live.*` / `mediaLibrary.*` | ❌ Guarded | Seed docs directly, or `client.stub(name, fn)` |
|
|
258
295
|
| `observable.*` — RxJS API | ❌ Guarded | Wrap the Promise API with `from()` if needed |
|