@sanity-labs/client-fake-for-test 0.0.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 +125 -0
- package/dist/index.cjs +522 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +465 -0
- package/dist/index.d.ts +465 -0
- package/dist/index.js +522 -0
- package/dist/index.js.map +1 -0
- package/package.json +70 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Apply an ordered list of patch ops to a document, returning the new doc.
|
|
3
|
+
*
|
|
4
|
+
* The path semantics are deliberately a small subset:
|
|
5
|
+
*
|
|
6
|
+
* - **Dotted paths** (`a.b.c`) for nested object fields.
|
|
7
|
+
* - **Array index** (`a.b[0]`) for positional access into arrays.
|
|
8
|
+
* - **Keyed lookup** (`a.b[_key=="x"]`) for finding an array entry by `_key`.
|
|
9
|
+
*
|
|
10
|
+
* Anything more exotic (recursive `..`, GROQ filter expressions, multi-match)
|
|
11
|
+
* is intentionally out of scope and will throw — the workflow engine only
|
|
12
|
+
* needs the three forms above.
|
|
13
|
+
*/
|
|
14
|
+
export declare function applyPatchOps(
|
|
15
|
+
doc: SanityDocument,
|
|
16
|
+
ops: PatchOp[],
|
|
17
|
+
): SanityDocument;
|
|
18
|
+
|
|
19
|
+
export declare function createPatch(
|
|
20
|
+
documentId: string,
|
|
21
|
+
executor: PatchExecutor,
|
|
22
|
+
): PatchHandle;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Create a fresh, isolated test client backed by a private multi-dataset
|
|
26
|
+
* registry. `withConfig` returns a sibling handle into the same registry.
|
|
27
|
+
*
|
|
28
|
+
* ```
|
|
29
|
+
* const client = createTestClient({
|
|
30
|
+
* datasets: {
|
|
31
|
+
* "test:definitions": [...],
|
|
32
|
+
* "test:instances": [...],
|
|
33
|
+
* },
|
|
34
|
+
* });
|
|
35
|
+
* const defs = client.withConfig({ dataset: "definitions" });
|
|
36
|
+
* const inst = client.withConfig({ dataset: "instances" });
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export declare function createTestClient(
|
|
40
|
+
options?: CreateTestClientOptions,
|
|
41
|
+
): TestClient;
|
|
42
|
+
|
|
43
|
+
export declare interface CreateTestClientOptions {
|
|
44
|
+
/** Default project id. Defaults to `"test"`. */
|
|
45
|
+
projectId?: string;
|
|
46
|
+
/** Default dataset name. Defaults to `"test"`. */
|
|
47
|
+
dataset?: string;
|
|
48
|
+
/** Documents to seed into the default `(projectId, dataset)` store. */
|
|
49
|
+
documents?: SanityDocumentLike[];
|
|
50
|
+
/**
|
|
51
|
+
* Optional: seed multiple datasets at once. Keys are `"projectId:dataset"`
|
|
52
|
+
* pairs. Useful when workflows span multiple datasets.
|
|
53
|
+
*/
|
|
54
|
+
datasets?: Record<string, SanityDocumentLike[]>;
|
|
55
|
+
/**
|
|
56
|
+
* Opt-in access-control simulation. When set, the client exposes
|
|
57
|
+
* `request({ url })` that serves `/users/me` + the configured ACL
|
|
58
|
+
* path, AND every mutation is gated through the supplied `grants`
|
|
59
|
+
* — writes that the grants don't permit throw a `WriteAccessDeniedError`
|
|
60
|
+
* shaped like Sanity's 403. Lets tests demonstrate the full
|
|
61
|
+
* "engine soft-gate + lake hard-gate" contract end-to-end without
|
|
62
|
+
* a real HTTP round-trip.
|
|
63
|
+
*
|
|
64
|
+
* When omitted, `request` is undefined and all writes proceed
|
|
65
|
+
* unconditionally — the legacy behavior.
|
|
66
|
+
*/
|
|
67
|
+
accessControl?: TestClientAccessControl;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Create a fresh, isolated store. The returned object closes over a private
|
|
72
|
+
* Map; two stores created from two calls do not see each other.
|
|
73
|
+
*/
|
|
74
|
+
export declare function createTestStore(
|
|
75
|
+
initial?: SanityDocumentLike[],
|
|
76
|
+
): TestStore;
|
|
77
|
+
|
|
78
|
+
export declare function createTransaction(
|
|
79
|
+
executor: TxnExecutor,
|
|
80
|
+
): TransactionHandle;
|
|
81
|
+
|
|
82
|
+
export declare interface CurrentUser {
|
|
83
|
+
id: string;
|
|
84
|
+
name?: string;
|
|
85
|
+
email?: string;
|
|
86
|
+
/** @deprecated singular `role` on legacy responses */
|
|
87
|
+
role?: string;
|
|
88
|
+
roles?: {
|
|
89
|
+
name: string;
|
|
90
|
+
title?: string;
|
|
91
|
+
description?: string;
|
|
92
|
+
}[];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Default ACL path the engine pulls grants from. Mirrors Sanity's
|
|
97
|
+
* project/dataset ACL endpoint. Callers can override via
|
|
98
|
+
* `accessControl.aclPath`.
|
|
99
|
+
*/
|
|
100
|
+
export declare function defaultAclPath(
|
|
101
|
+
projectId: string,
|
|
102
|
+
dataset: string,
|
|
103
|
+
): string;
|
|
104
|
+
|
|
105
|
+
export declare type DocumentValuePermission = "create" | "read" | "update";
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Resolve whether `grants` allow `permission` on `document` for the
|
|
109
|
+
* given user. Same logic the engine uses in `permissions.grantsPermissionOn`
|
|
110
|
+
* (most-permissive wins; multiple grants compose).
|
|
111
|
+
*
|
|
112
|
+
* Returns `{ allowed: boolean, matchingGrants: number }` so callers
|
|
113
|
+
* can build the structured error without re-counting.
|
|
114
|
+
*/
|
|
115
|
+
export declare function evaluateWriteAccess(args: {
|
|
116
|
+
document: SanityDocumentLike & {
|
|
117
|
+
_id?: string;
|
|
118
|
+
_type?: string;
|
|
119
|
+
};
|
|
120
|
+
grants: Grant[];
|
|
121
|
+
permission: DocumentValuePermission;
|
|
122
|
+
userId: string;
|
|
123
|
+
}): Promise<{
|
|
124
|
+
allowed: boolean;
|
|
125
|
+
matchingGrants: number;
|
|
126
|
+
}>;
|
|
127
|
+
|
|
128
|
+
export declare interface Grant {
|
|
129
|
+
filter: string;
|
|
130
|
+
permissions: DocumentValuePermission[];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export declare interface MutationOptions {
|
|
134
|
+
ifRevisionId?: string;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export declare interface PatchExecutor {
|
|
138
|
+
apply: (
|
|
139
|
+
documentId: string,
|
|
140
|
+
ops: PatchOp[],
|
|
141
|
+
ifRev: string | undefined,
|
|
142
|
+
) => Promise<SanityDocument>;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export declare interface PatchHandle {
|
|
146
|
+
set: (props: Record<string, unknown>) => PatchHandle;
|
|
147
|
+
setIfMissing: (props: Record<string, unknown>) => PatchHandle;
|
|
148
|
+
unset: (paths: string[]) => PatchHandle;
|
|
149
|
+
inc: (props: Record<string, number>) => PatchHandle;
|
|
150
|
+
dec: (props: Record<string, number>) => PatchHandle;
|
|
151
|
+
insert: (
|
|
152
|
+
position: "before" | "after" | "replace",
|
|
153
|
+
anchor: string,
|
|
154
|
+
items: unknown[],
|
|
155
|
+
) => PatchHandle;
|
|
156
|
+
ifRevisionId: (rev: string) => PatchHandle;
|
|
157
|
+
commit: () => Promise<SanityDocument>;
|
|
158
|
+
/** Test-only introspection — not on real Patch */
|
|
159
|
+
toJSON: () => {
|
|
160
|
+
id: string;
|
|
161
|
+
ops: PatchOp[];
|
|
162
|
+
ifRev?: string;
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Patch operations the test client supports.
|
|
168
|
+
*
|
|
169
|
+
* Subset of `@sanity/client`'s PatchOperations. Each entry is one
|
|
170
|
+
* mutation step applied in order at commit time.
|
|
171
|
+
*/
|
|
172
|
+
export declare type PatchOp =
|
|
173
|
+
| {
|
|
174
|
+
kind: "set";
|
|
175
|
+
props: Record<string, unknown>;
|
|
176
|
+
}
|
|
177
|
+
| {
|
|
178
|
+
kind: "setIfMissing";
|
|
179
|
+
props: Record<string, unknown>;
|
|
180
|
+
}
|
|
181
|
+
| {
|
|
182
|
+
kind: "unset";
|
|
183
|
+
paths: string[];
|
|
184
|
+
}
|
|
185
|
+
| {
|
|
186
|
+
kind: "inc";
|
|
187
|
+
props: Record<string, number>;
|
|
188
|
+
}
|
|
189
|
+
| {
|
|
190
|
+
kind: "dec";
|
|
191
|
+
props: Record<string, number>;
|
|
192
|
+
}
|
|
193
|
+
| {
|
|
194
|
+
kind: "insert";
|
|
195
|
+
position: "before" | "after" | "replace";
|
|
196
|
+
anchor: string;
|
|
197
|
+
items: unknown[];
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
/** @public */
|
|
201
|
+
declare interface SanityDocument {
|
|
202
|
+
_id: string;
|
|
203
|
+
_type: string;
|
|
204
|
+
_createdAt: string;
|
|
205
|
+
_updatedAt: string;
|
|
206
|
+
_rev: string;
|
|
207
|
+
[key: string]: unknown;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Similar to `SanityDocument` but only requires the `_id` and `_type`
|
|
212
|
+
*
|
|
213
|
+
* @see SanityDocument
|
|
214
|
+
*
|
|
215
|
+
* @public
|
|
216
|
+
*/
|
|
217
|
+
declare interface SanityDocumentLike {
|
|
218
|
+
_id: string;
|
|
219
|
+
_type: string;
|
|
220
|
+
_createdAt?: string;
|
|
221
|
+
_updatedAt?: string;
|
|
222
|
+
_rev?: string;
|
|
223
|
+
_system?: {
|
|
224
|
+
delete?: boolean;
|
|
225
|
+
};
|
|
226
|
+
[key: string]: unknown;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export declare interface SystemFields {
|
|
230
|
+
_rev: string;
|
|
231
|
+
_createdAt: string;
|
|
232
|
+
_updatedAt: string;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Data-plane subset of `@sanity/client`'s surface, with multi-dataset support.
|
|
237
|
+
*
|
|
238
|
+
* One `createTestClient()` call creates a registry of stores keyed by
|
|
239
|
+
* `(projectId, dataset)`. The returned client points at one of them.
|
|
240
|
+
* `withConfig({ projectId, dataset })` returns a new handle into the same
|
|
241
|
+
* registry but pointing at a different store — useful for workflows where
|
|
242
|
+
* definitions live in one dataset and instances live in another.
|
|
243
|
+
*
|
|
244
|
+
* Two separate `createTestClient()` calls have separate registries, so
|
|
245
|
+
* tests stay isolated from each other.
|
|
246
|
+
*
|
|
247
|
+
* Methods not on the real client:
|
|
248
|
+
*
|
|
249
|
+
* - `seed`, `add`, `reset`, `snapshot`, `store` — bulk-load helpers,
|
|
250
|
+
* scoped to the current dataset.
|
|
251
|
+
* - `datasets()` — lists known dataset keys in this registry.
|
|
252
|
+
*
|
|
253
|
+
* Methods deliberately omitted:
|
|
254
|
+
*
|
|
255
|
+
* - Project / agent / asset / live / listen APIs.
|
|
256
|
+
* - Perspective handling.
|
|
257
|
+
*/
|
|
258
|
+
export declare interface TestClient {
|
|
259
|
+
fetch: <T = unknown>(
|
|
260
|
+
query: string,
|
|
261
|
+
params?: Record<string, unknown>,
|
|
262
|
+
) => Promise<T>;
|
|
263
|
+
getDocument: <T = SanityDocument>(id: string) => Promise<T | null>;
|
|
264
|
+
getDocuments: <T = SanityDocument>(ids: string[]) => Promise<(T | null)[]>;
|
|
265
|
+
/**
|
|
266
|
+
* Sanity-style HTTP request hook. Present iff the client was
|
|
267
|
+
* created with `accessControl`. The engine's access resolver pokes
|
|
268
|
+
* `/users/me` and the configured ACL path through this — without it
|
|
269
|
+
* the engine throws on any verb that needs an actor (which is the
|
|
270
|
+
* cue for the bench to supply an `access` override).
|
|
271
|
+
*
|
|
272
|
+
* Test cases that want to exercise the engine's token-resolution
|
|
273
|
+
* path should configure `accessControl`; cases that want bench-style
|
|
274
|
+
* always-allow should leave it off and pass `access` through the
|
|
275
|
+
* bench.
|
|
276
|
+
*/
|
|
277
|
+
request?: <T>(opts: {
|
|
278
|
+
url?: string;
|
|
279
|
+
uri?: string;
|
|
280
|
+
signal?: AbortSignal;
|
|
281
|
+
tag?: string;
|
|
282
|
+
}) => Promise<T>;
|
|
283
|
+
create: <T extends SanityDocumentLike>(
|
|
284
|
+
doc: T,
|
|
285
|
+
options?: MutationOptions,
|
|
286
|
+
) => Promise<T & SystemFields>;
|
|
287
|
+
createOrReplace: <T extends SanityDocumentLike>(
|
|
288
|
+
doc: T,
|
|
289
|
+
options?: MutationOptions,
|
|
290
|
+
) => Promise<T & SystemFields>;
|
|
291
|
+
createIfNotExists: <T extends SanityDocumentLike>(
|
|
292
|
+
doc: T,
|
|
293
|
+
options?: MutationOptions,
|
|
294
|
+
) => Promise<T & SystemFields>;
|
|
295
|
+
delete: (
|
|
296
|
+
id: string,
|
|
297
|
+
options?: MutationOptions,
|
|
298
|
+
) => Promise<{
|
|
299
|
+
_id: string;
|
|
300
|
+
} | null>;
|
|
301
|
+
patch: (documentId: string) => PatchHandle;
|
|
302
|
+
transaction: () => TransactionHandle;
|
|
303
|
+
/** Current `(projectId, dataset)`. */
|
|
304
|
+
config: () => {
|
|
305
|
+
projectId: string;
|
|
306
|
+
dataset: string;
|
|
307
|
+
};
|
|
308
|
+
/**
|
|
309
|
+
* Return a new client handle pointing at a different `(projectId, dataset)`
|
|
310
|
+
* within the SAME backing registry. Either field can be omitted to keep
|
|
311
|
+
* the current value. Two clients produced by `withConfig` see each
|
|
312
|
+
* other's writes if they point at the same `(projectId, dataset)`.
|
|
313
|
+
*/
|
|
314
|
+
withConfig: (
|
|
315
|
+
config: Partial<{
|
|
316
|
+
projectId: string;
|
|
317
|
+
dataset: string;
|
|
318
|
+
}>,
|
|
319
|
+
) => TestClient;
|
|
320
|
+
/** Replace the current dataset with the given documents. */
|
|
321
|
+
seed: (documents: SanityDocumentLike[]) => void;
|
|
322
|
+
/** Add documents to the current dataset without dropping existing ones. */
|
|
323
|
+
add: (documents: SanityDocumentLike[]) => void;
|
|
324
|
+
/** Drop all documents in the current dataset. */
|
|
325
|
+
reset: () => void;
|
|
326
|
+
/** Read-only snapshot of all documents in the current dataset. */
|
|
327
|
+
snapshot: () => readonly SanityDocument[];
|
|
328
|
+
/** Direct store access for the current dataset. */
|
|
329
|
+
store: () => TestStore;
|
|
330
|
+
/** List `(projectId, dataset)` pairs known in this registry. */
|
|
331
|
+
datasets: () => {
|
|
332
|
+
projectId: string;
|
|
333
|
+
dataset: string;
|
|
334
|
+
}[];
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
export declare interface TestClientAccessControl {
|
|
338
|
+
/** What `client.request({ url: "/users/me" })` returns. */
|
|
339
|
+
currentUser: CurrentUser;
|
|
340
|
+
/** What `client.request({ url: aclPath })` returns. */
|
|
341
|
+
grants: Grant[];
|
|
342
|
+
/**
|
|
343
|
+
* URL path the engine's access resolver hits to fetch grants.
|
|
344
|
+
* Defaults to `/projects/<projectId>/datasets/<dataset>/acl`.
|
|
345
|
+
*/
|
|
346
|
+
aclPath?: string;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* In-memory document store backing a TestClient. Closure-scoped — created
|
|
351
|
+
* fresh per test, no global state.
|
|
352
|
+
*
|
|
353
|
+
* The shape mirrors what `groq-js` evaluates against: an array of
|
|
354
|
+
* documents. Internally we keep a Map by `_id` for O(1) lookup, and
|
|
355
|
+
* project to an array on each query.
|
|
356
|
+
*/
|
|
357
|
+
export declare interface TestStore {
|
|
358
|
+
/** Replace the entire dataset with the given documents. Existing docs are dropped. */
|
|
359
|
+
seed: (documents: SanityDocumentLike[]) => void;
|
|
360
|
+
/** Add documents without dropping existing ones. Conflicts (same _id) overwrite. */
|
|
361
|
+
add: (documents: SanityDocumentLike[]) => void;
|
|
362
|
+
/** Remove all documents. */
|
|
363
|
+
reset: () => void;
|
|
364
|
+
/** All documents as an array, in insertion order. */
|
|
365
|
+
all: () => SanityDocument[];
|
|
366
|
+
/** Get one document by _id, or undefined. */
|
|
367
|
+
get: (id: string) => SanityDocument | undefined;
|
|
368
|
+
/** Insert a fully-formed document by _id. Replaces if it already exists. */
|
|
369
|
+
put: (doc: SanityDocument) => void;
|
|
370
|
+
/** Remove a document by _id. Returns the removed doc or undefined. */
|
|
371
|
+
remove: (id: string) => SanityDocument | undefined;
|
|
372
|
+
/** Snapshot of all docs, useful for assertions. */
|
|
373
|
+
snapshot: () => readonly SanityDocument[];
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
export declare interface TransactionHandle {
|
|
377
|
+
create: (doc: SanityDocumentLike) => TransactionHandle;
|
|
378
|
+
createOrReplace: (doc: SanityDocumentLike) => TransactionHandle;
|
|
379
|
+
createIfNotExists: (doc: SanityDocumentLike) => TransactionHandle;
|
|
380
|
+
delete: (id: string) => TransactionHandle;
|
|
381
|
+
/** Accept either a Patch builder (drained at commit) or an inline doc id + ops. */
|
|
382
|
+
patch: (
|
|
383
|
+
documentIdOrPatch: string | PatchHandle,
|
|
384
|
+
ops?: PatchOp[],
|
|
385
|
+
ifRev?: string,
|
|
386
|
+
) => TransactionHandle;
|
|
387
|
+
commit: (options?: TxnCommitOptions) => Promise<TxnCommitResult>;
|
|
388
|
+
/** Test-only introspection — not on real Transaction. */
|
|
389
|
+
toJSON: () => TxnMutation[];
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
export declare interface TxnCommitOptions {
|
|
393
|
+
/** If true, returns the resulting documents instead of just ids. */
|
|
394
|
+
returnDocuments?: boolean;
|
|
395
|
+
/** With `returnDocuments`, return only the first updated doc. */
|
|
396
|
+
returnFirst?: boolean;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
export declare type TxnCommitResult =
|
|
400
|
+
| {
|
|
401
|
+
transactionId: string;
|
|
402
|
+
results: {
|
|
403
|
+
id: string;
|
|
404
|
+
operation: "create" | "update" | "delete";
|
|
405
|
+
}[];
|
|
406
|
+
}
|
|
407
|
+
| SanityDocument
|
|
408
|
+
| SanityDocument[];
|
|
409
|
+
|
|
410
|
+
export declare interface TxnExecutor {
|
|
411
|
+
apply: (
|
|
412
|
+
mutations: TxnMutation[],
|
|
413
|
+
options?: TxnCommitOptions,
|
|
414
|
+
) => Promise<TxnCommitResult>;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* A transaction collects mutations and applies them atomically at commit
|
|
419
|
+
* time. Mutations are applied in declaration order; if any one fails, the
|
|
420
|
+
* whole transaction throws and no writes land in the store.
|
|
421
|
+
*
|
|
422
|
+
* Subset of `@sanity/client`'s Transaction. Same shape, narrower surface.
|
|
423
|
+
*/
|
|
424
|
+
export declare type TxnMutation =
|
|
425
|
+
| {
|
|
426
|
+
kind: "create";
|
|
427
|
+
doc: SanityDocumentLike;
|
|
428
|
+
}
|
|
429
|
+
| {
|
|
430
|
+
kind: "createOrReplace";
|
|
431
|
+
doc: SanityDocumentLike;
|
|
432
|
+
}
|
|
433
|
+
| {
|
|
434
|
+
kind: "createIfNotExists";
|
|
435
|
+
doc: SanityDocumentLike;
|
|
436
|
+
}
|
|
437
|
+
| {
|
|
438
|
+
kind: "delete";
|
|
439
|
+
id: string;
|
|
440
|
+
}
|
|
441
|
+
| {
|
|
442
|
+
kind: "patch";
|
|
443
|
+
documentId: string;
|
|
444
|
+
ops: PatchOp[];
|
|
445
|
+
ifRev?: string;
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Sanity-style 403 raised when a configured-grants check rejects a
|
|
450
|
+
* write. The thrown object carries the doc id, the attempted
|
|
451
|
+
* permission, and the count of matching grants (for parity with the
|
|
452
|
+
* engine's `permission-denied` disabled-reason).
|
|
453
|
+
*/
|
|
454
|
+
export declare class WriteAccessDeniedError extends Error {
|
|
455
|
+
readonly documentId: string;
|
|
456
|
+
readonly permission: DocumentValuePermission;
|
|
457
|
+
readonly matchingGrants: number;
|
|
458
|
+
constructor(args: {
|
|
459
|
+
documentId: string;
|
|
460
|
+
permission: DocumentValuePermission;
|
|
461
|
+
matchingGrants: number;
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
export {};
|