@sanity-labs/client-fake-for-test 0.7.1 → 0.8.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.cjs +180 -139
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +43 -11
- package/dist/index.d.ts +43 -11
- package/dist/index.js +180 -139
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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 {
|
|
@@ -202,6 +202,26 @@ export declare function defaultAclPath(
|
|
|
202
202
|
dataset: string,
|
|
203
203
|
): string;
|
|
204
204
|
|
|
205
|
+
/**
|
|
206
|
+
* Draft↔published document actions — `sanity.action.document.publish`
|
|
207
|
+
* promotes the draft onto the published id (and deletes the draft);
|
|
208
|
+
* `sanity.action.document.unpublish` retracts the published document into a
|
|
209
|
+
* draft (keeping an existing draft untouched) and deletes the published one.
|
|
210
|
+
*/
|
|
211
|
+
export declare type DocumentAction =
|
|
212
|
+
| {
|
|
213
|
+
actionType: "sanity.action.document.publish";
|
|
214
|
+
draftId: string;
|
|
215
|
+
publishedId: string;
|
|
216
|
+
ifDraftRevisionId?: string;
|
|
217
|
+
ifPublishedRevisionId?: string;
|
|
218
|
+
}
|
|
219
|
+
| {
|
|
220
|
+
actionType: "sanity.action.document.unpublish";
|
|
221
|
+
draftId: string;
|
|
222
|
+
publishedId: string;
|
|
223
|
+
};
|
|
224
|
+
|
|
205
225
|
/**
|
|
206
226
|
* Document body accepted by `version.create` / `createVersion`. The id
|
|
207
227
|
* is derived from `releaseId + publishedId` so callers may omit `_id`
|
|
@@ -387,11 +407,13 @@ export declare interface MultipleMutationResult {
|
|
|
387
407
|
* A pluggable write interceptor, configured via
|
|
388
408
|
* `createTestClient({ mutationGuard })`. It runs on each write through the
|
|
389
409
|
* document mutation API — `create` / `createOrReplace` / `createIfNotExists` /
|
|
390
|
-
* `patch` / `delete` and the equivalent `transaction()` operations —
|
|
391
|
-
*
|
|
392
|
-
*
|
|
393
|
-
*
|
|
394
|
-
*
|
|
410
|
+
* `patch` / `delete` and the equivalent `transaction()` operations — and on the
|
|
411
|
+
* document publish/unpublish actions (`sanity.action.document.publish` /
|
|
412
|
+
* `.unpublish` via `client.action`) — before the store changes; **throw to
|
|
413
|
+
* reject the write** (the throw propagates out of the client call, exactly as
|
|
414
|
+
* a real lake rejection would). The release/version action API
|
|
415
|
+
* (`client.releases.*`, `createVersion`, the `sanity.action.release.*` and
|
|
416
|
+
* `*.version.*` action types) is **not** intercepted.
|
|
395
417
|
*
|
|
396
418
|
* The client itself has no opinion about *what* a guard is — it only provides
|
|
397
419
|
* the seam and the before/after/identity context. Callers (e.g. a workflow test
|
|
@@ -404,13 +426,21 @@ export declare type MutationGuard = (
|
|
|
404
426
|
) => void | Promise<void>;
|
|
405
427
|
|
|
406
428
|
/** The kind of write a {@link MutationGuard} is asked to vet. */
|
|
407
|
-
export declare type MutationGuardAction =
|
|
429
|
+
export declare type MutationGuardAction =
|
|
430
|
+
| "create"
|
|
431
|
+
| "update"
|
|
432
|
+
| "delete"
|
|
433
|
+
| "publish"
|
|
434
|
+
| "unpublish";
|
|
408
435
|
|
|
409
436
|
/**
|
|
410
437
|
* The before/after image of a single document write, handed to a
|
|
411
438
|
* {@link MutationGuard} before it is applied.
|
|
412
439
|
*
|
|
413
|
-
* - `before` is null on create
|
|
440
|
+
* - `before` is null on create and on a publish with no live published
|
|
441
|
+
* document; `after` is null on delete and unpublish.
|
|
442
|
+
* - On publish/unpublish both images are of the *published* document —
|
|
443
|
+
* `after` on publish is the draft's content promoted onto the published id.
|
|
414
444
|
* - `identity` is the acting user — the id bound to the client's token
|
|
415
445
|
* (its `accessControl.currentUser`), or undefined when the client has no
|
|
416
446
|
* identity configured.
|
|
@@ -789,9 +819,11 @@ export declare interface TestClient {
|
|
|
789
819
|
options?: TxnCommitOptions,
|
|
790
820
|
) => Promise<TxnCommitResult>;
|
|
791
821
|
/**
|
|
792
|
-
* Dispatch one or more release / version actions against the
|
|
793
|
-
* Returns a synthetic `transactionId`. Multi-action arrays are
|
|
794
|
-
* atomically — any failure rolls back the batch.
|
|
822
|
+
* Dispatch one or more release / version / document actions against the
|
|
823
|
+
* store. Returns a synthetic `transactionId`. Multi-action arrays are
|
|
824
|
+
* applied atomically — any failure rolls back the batch. The document
|
|
825
|
+
* publish/unpublish actions run through the access gate and the
|
|
826
|
+
* `mutationGuard` seam; release/version actions apply unhooked.
|
|
795
827
|
*/
|
|
796
828
|
action: (
|
|
797
829
|
action: Action | Action[],
|
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 {
|
|
@@ -202,6 +202,26 @@ export declare function defaultAclPath(
|
|
|
202
202
|
dataset: string,
|
|
203
203
|
): string;
|
|
204
204
|
|
|
205
|
+
/**
|
|
206
|
+
* Draft↔published document actions — `sanity.action.document.publish`
|
|
207
|
+
* promotes the draft onto the published id (and deletes the draft);
|
|
208
|
+
* `sanity.action.document.unpublish` retracts the published document into a
|
|
209
|
+
* draft (keeping an existing draft untouched) and deletes the published one.
|
|
210
|
+
*/
|
|
211
|
+
export declare type DocumentAction =
|
|
212
|
+
| {
|
|
213
|
+
actionType: "sanity.action.document.publish";
|
|
214
|
+
draftId: string;
|
|
215
|
+
publishedId: string;
|
|
216
|
+
ifDraftRevisionId?: string;
|
|
217
|
+
ifPublishedRevisionId?: string;
|
|
218
|
+
}
|
|
219
|
+
| {
|
|
220
|
+
actionType: "sanity.action.document.unpublish";
|
|
221
|
+
draftId: string;
|
|
222
|
+
publishedId: string;
|
|
223
|
+
};
|
|
224
|
+
|
|
205
225
|
/**
|
|
206
226
|
* Document body accepted by `version.create` / `createVersion`. The id
|
|
207
227
|
* is derived from `releaseId + publishedId` so callers may omit `_id`
|
|
@@ -387,11 +407,13 @@ export declare interface MultipleMutationResult {
|
|
|
387
407
|
* A pluggable write interceptor, configured via
|
|
388
408
|
* `createTestClient({ mutationGuard })`. It runs on each write through the
|
|
389
409
|
* document mutation API — `create` / `createOrReplace` / `createIfNotExists` /
|
|
390
|
-
* `patch` / `delete` and the equivalent `transaction()` operations —
|
|
391
|
-
*
|
|
392
|
-
*
|
|
393
|
-
*
|
|
394
|
-
*
|
|
410
|
+
* `patch` / `delete` and the equivalent `transaction()` operations — and on the
|
|
411
|
+
* document publish/unpublish actions (`sanity.action.document.publish` /
|
|
412
|
+
* `.unpublish` via `client.action`) — before the store changes; **throw to
|
|
413
|
+
* reject the write** (the throw propagates out of the client call, exactly as
|
|
414
|
+
* a real lake rejection would). The release/version action API
|
|
415
|
+
* (`client.releases.*`, `createVersion`, the `sanity.action.release.*` and
|
|
416
|
+
* `*.version.*` action types) is **not** intercepted.
|
|
395
417
|
*
|
|
396
418
|
* The client itself has no opinion about *what* a guard is — it only provides
|
|
397
419
|
* the seam and the before/after/identity context. Callers (e.g. a workflow test
|
|
@@ -404,13 +426,21 @@ export declare type MutationGuard = (
|
|
|
404
426
|
) => void | Promise<void>;
|
|
405
427
|
|
|
406
428
|
/** The kind of write a {@link MutationGuard} is asked to vet. */
|
|
407
|
-
export declare type MutationGuardAction =
|
|
429
|
+
export declare type MutationGuardAction =
|
|
430
|
+
| "create"
|
|
431
|
+
| "update"
|
|
432
|
+
| "delete"
|
|
433
|
+
| "publish"
|
|
434
|
+
| "unpublish";
|
|
408
435
|
|
|
409
436
|
/**
|
|
410
437
|
* The before/after image of a single document write, handed to a
|
|
411
438
|
* {@link MutationGuard} before it is applied.
|
|
412
439
|
*
|
|
413
|
-
* - `before` is null on create
|
|
440
|
+
* - `before` is null on create and on a publish with no live published
|
|
441
|
+
* document; `after` is null on delete and unpublish.
|
|
442
|
+
* - On publish/unpublish both images are of the *published* document —
|
|
443
|
+
* `after` on publish is the draft's content promoted onto the published id.
|
|
414
444
|
* - `identity` is the acting user — the id bound to the client's token
|
|
415
445
|
* (its `accessControl.currentUser`), or undefined when the client has no
|
|
416
446
|
* identity configured.
|
|
@@ -789,9 +819,11 @@ export declare interface TestClient {
|
|
|
789
819
|
options?: TxnCommitOptions,
|
|
790
820
|
) => Promise<TxnCommitResult>;
|
|
791
821
|
/**
|
|
792
|
-
* Dispatch one or more release / version actions against the
|
|
793
|
-
* Returns a synthetic `transactionId`. Multi-action arrays are
|
|
794
|
-
* atomically — any failure rolls back the batch.
|
|
822
|
+
* Dispatch one or more release / version / document actions against the
|
|
823
|
+
* store. Returns a synthetic `transactionId`. Multi-action arrays are
|
|
824
|
+
* applied atomically — any failure rolls back the batch. The document
|
|
825
|
+
* publish/unpublish actions run through the access gate and the
|
|
826
|
+
* `mutationGuard` seam; release/version actions apply unhooked.
|
|
795
827
|
*/
|
|
796
828
|
action: (
|
|
797
829
|
action: Action | Action[],
|
package/dist/index.js
CHANGED
|
@@ -71,57 +71,6 @@ function makeRequestImpl(getAccess, projectId, dataset) {
|
|
|
71
71
|
);
|
|
72
72
|
};
|
|
73
73
|
}
|
|
74
|
-
function createTestStore(initial) {
|
|
75
|
-
const docs = /* @__PURE__ */ new Map(), promote2 = (doc) => {
|
|
76
|
-
if (typeof doc._id != "string" || doc._id.length === 0)
|
|
77
|
-
throw fakeError(
|
|
78
|
-
`Cannot seed/store a document without a non-empty string \`_id\` (got ${JSON.stringify(doc._id)}). This fake never auto-generates ids \u2014 give every document an explicit _id. Document: ${JSON.stringify(doc)}`
|
|
79
|
-
);
|
|
80
|
-
if (typeof doc._type != "string" || doc._type.length === 0)
|
|
81
|
-
throw fakeError(
|
|
82
|
-
`Document "${doc._id}" is missing a non-empty string \`_type\` (got ${JSON.stringify(doc._type)}). Every Sanity document needs a _type.`
|
|
83
|
-
);
|
|
84
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
85
|
-
return {
|
|
86
|
-
...doc,
|
|
87
|
-
_id: doc._id,
|
|
88
|
-
_type: doc._type,
|
|
89
|
-
_rev: doc._rev ?? cryptoRandomKey(),
|
|
90
|
-
_createdAt: doc._createdAt ?? now,
|
|
91
|
-
_updatedAt: doc._updatedAt ?? now
|
|
92
|
-
};
|
|
93
|
-
}, seed = (documents) => {
|
|
94
|
-
docs.clear();
|
|
95
|
-
for (const d of documents) {
|
|
96
|
-
const promoted = promote2(d);
|
|
97
|
-
docs.set(promoted._id, promoted);
|
|
98
|
-
}
|
|
99
|
-
};
|
|
100
|
-
return initial && seed(initial), {
|
|
101
|
-
seed,
|
|
102
|
-
add: (documents) => {
|
|
103
|
-
for (const d of documents) {
|
|
104
|
-
const promoted = promote2(d);
|
|
105
|
-
docs.set(promoted._id, promoted);
|
|
106
|
-
}
|
|
107
|
-
},
|
|
108
|
-
reset: () => docs.clear(),
|
|
109
|
-
all: () => [...docs.values()],
|
|
110
|
-
get: (id) => docs.get(id),
|
|
111
|
-
put: (doc) => {
|
|
112
|
-
docs.set(doc._id, doc);
|
|
113
|
-
},
|
|
114
|
-
remove: (id) => {
|
|
115
|
-
const doc = docs.get(id);
|
|
116
|
-
return doc && docs.delete(id), doc;
|
|
117
|
-
},
|
|
118
|
-
snapshot: () => [...docs.values()]
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
function cryptoRandomKey(length = 12) {
|
|
122
|
-
const bytes = new Uint8Array(length);
|
|
123
|
-
return globalThis.crypto.getRandomValues(bytes), [...bytes].map((b) => b.toString(16).padStart(2, "0")).join("").slice(0, length);
|
|
124
|
-
}
|
|
125
74
|
function describeType(value) {
|
|
126
75
|
return value === null ? "null" : Array.isArray(value) ? "an array" : `a ${typeof value}`;
|
|
127
76
|
}
|
|
@@ -324,13 +273,140 @@ function insertIntoArray(doc, position, anchor, items) {
|
|
|
324
273
|
const idx = resolveSelector(arr, last.selector);
|
|
325
274
|
position === "replace" ? arr.splice(idx, 1, ...items) : position === "before" ? arr.splice(idx, 0, ...items) : arr.splice(idx + 1, 0, ...items);
|
|
326
275
|
}
|
|
327
|
-
const
|
|
276
|
+
const DRAFTS_PREFIX = "drafts.", VERSIONS_PREFIX$1 = "versions.", RELEASE_DOC_PREFIX$1 = "_.releases.";
|
|
277
|
+
function publishedIdOf(id) {
|
|
278
|
+
if (id.startsWith(DRAFTS_PREFIX)) return id.slice(DRAFTS_PREFIX.length);
|
|
279
|
+
if (id.startsWith(VERSIONS_PREFIX$1)) {
|
|
280
|
+
const rest = id.slice(VERSIONS_PREFIX$1.length), dot = rest.indexOf(".");
|
|
281
|
+
return dot === -1 ? rest : rest.slice(dot + 1);
|
|
282
|
+
}
|
|
283
|
+
return id;
|
|
284
|
+
}
|
|
285
|
+
function releaseNameOf(id) {
|
|
286
|
+
if (!id.startsWith(VERSIONS_PREFIX$1)) return;
|
|
287
|
+
const rest = id.slice(VERSIONS_PREFIX$1.length), dot = rest.indexOf(".");
|
|
288
|
+
if (dot !== -1)
|
|
289
|
+
return rest.slice(0, dot);
|
|
290
|
+
}
|
|
291
|
+
const isDraftId = (id) => id.startsWith(DRAFTS_PREFIX), isVersionId = (id) => id.startsWith(VERSIONS_PREFIX$1), isSystemId = (id) => id.startsWith("_."), isReleaseDocId = (id) => id.startsWith(RELEASE_DOC_PREFIX$1);
|
|
292
|
+
function applyPerspective(docs, perspective) {
|
|
293
|
+
if (perspective === "raw") return [...docs];
|
|
294
|
+
if (perspective === "published")
|
|
295
|
+
return docs.filter((d) => !isDraftId(d._id) && !isVersionId(d._id) && !isSystemId(d._id));
|
|
296
|
+
if (perspective === "drafts")
|
|
297
|
+
return mergeStack(docs, [], { includeDrafts: !0, includeReleaseDocs: !1 });
|
|
298
|
+
const stack = [];
|
|
299
|
+
let includeDrafts = !1;
|
|
300
|
+
for (const entry of perspective)
|
|
301
|
+
entry === "drafts" ? includeDrafts = !0 : stack.push(entry);
|
|
302
|
+
return mergeStack(docs, stack, { includeDrafts, includeReleaseDocs: !0 });
|
|
303
|
+
}
|
|
304
|
+
function mergeStack(docs, releaseStack, options) {
|
|
305
|
+
const groups = /* @__PURE__ */ new Map(), systemReleases = [];
|
|
306
|
+
for (const doc of docs) {
|
|
307
|
+
if (isReleaseDocId(doc._id)) {
|
|
308
|
+
options.includeReleaseDocs && systemReleases.push(doc);
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
if (isSystemId(doc._id)) continue;
|
|
312
|
+
const pubId = publishedIdOf(doc._id);
|
|
313
|
+
let group = groups.get(pubId);
|
|
314
|
+
if (group === void 0 && (group = { versions: /* @__PURE__ */ new Map() }, groups.set(pubId, group)), isDraftId(doc._id))
|
|
315
|
+
group.draft = doc;
|
|
316
|
+
else if (isVersionId(doc._id)) {
|
|
317
|
+
const releaseName = releaseNameOf(doc._id);
|
|
318
|
+
releaseName !== void 0 && group.versions.set(releaseName, doc);
|
|
319
|
+
} else
|
|
320
|
+
group.published = doc;
|
|
321
|
+
}
|
|
322
|
+
const out = [];
|
|
323
|
+
for (const [pubId, group] of groups) {
|
|
324
|
+
let chosen;
|
|
325
|
+
for (const releaseName of releaseStack) {
|
|
326
|
+
const v = group.versions.get(releaseName);
|
|
327
|
+
if (v !== void 0) {
|
|
328
|
+
if (isUnpublishStub(v)) {
|
|
329
|
+
chosen = void 0;
|
|
330
|
+
break;
|
|
331
|
+
}
|
|
332
|
+
chosen = v;
|
|
333
|
+
break;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
chosen === void 0 && options.includeDrafts && group.draft !== void 0 && (chosen = group.draft), chosen === void 0 && group.published !== void 0 && (releaseStack.some((name) => {
|
|
337
|
+
const v = group.versions.get(name);
|
|
338
|
+
return v !== void 0 && isUnpublishStub(v);
|
|
339
|
+
}) || (chosen = group.published)), chosen !== void 0 && out.push(projectToPublishedId(chosen, pubId));
|
|
340
|
+
}
|
|
341
|
+
return [...out, ...systemReleases];
|
|
342
|
+
}
|
|
343
|
+
function isUnpublishStub(doc) {
|
|
344
|
+
return doc._system?.delete === !0;
|
|
345
|
+
}
|
|
346
|
+
function projectToPublishedId(doc, pubId) {
|
|
347
|
+
return doc._id === pubId ? doc : { ...doc, _id: pubId, _originalId: doc._id };
|
|
348
|
+
}
|
|
349
|
+
function resolveDocument(docs, id, perspective) {
|
|
350
|
+
return perspective === "raw" ? docs.find((d) => d._id === id) : applyPerspective(docs, perspective).find((d) => d._id === id);
|
|
351
|
+
}
|
|
352
|
+
function createTestStore(initial) {
|
|
353
|
+
const docs = /* @__PURE__ */ new Map(), promote2 = (doc) => {
|
|
354
|
+
if (typeof doc._id != "string" || doc._id.length === 0)
|
|
355
|
+
throw fakeError(
|
|
356
|
+
`Cannot seed/store a document without a non-empty string \`_id\` (got ${JSON.stringify(doc._id)}). This fake never auto-generates ids \u2014 give every document an explicit _id. Document: ${JSON.stringify(doc)}`
|
|
357
|
+
);
|
|
358
|
+
if (typeof doc._type != "string" || doc._type.length === 0)
|
|
359
|
+
throw fakeError(
|
|
360
|
+
`Document "${doc._id}" is missing a non-empty string \`_type\` (got ${JSON.stringify(doc._type)}). Every Sanity document needs a _type.`
|
|
361
|
+
);
|
|
362
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
363
|
+
return {
|
|
364
|
+
...doc,
|
|
365
|
+
_id: doc._id,
|
|
366
|
+
_type: doc._type,
|
|
367
|
+
_rev: doc._rev ?? cryptoRandomKey(),
|
|
368
|
+
_createdAt: doc._createdAt ?? now,
|
|
369
|
+
_updatedAt: doc._updatedAt ?? now
|
|
370
|
+
};
|
|
371
|
+
}, seed = (documents) => {
|
|
372
|
+
docs.clear();
|
|
373
|
+
for (const d of documents) {
|
|
374
|
+
const promoted = promote2(d);
|
|
375
|
+
docs.set(promoted._id, promoted);
|
|
376
|
+
}
|
|
377
|
+
};
|
|
378
|
+
return initial && seed(initial), {
|
|
379
|
+
seed,
|
|
380
|
+
add: (documents) => {
|
|
381
|
+
for (const d of documents) {
|
|
382
|
+
const promoted = promote2(d);
|
|
383
|
+
docs.set(promoted._id, promoted);
|
|
384
|
+
}
|
|
385
|
+
},
|
|
386
|
+
reset: () => docs.clear(),
|
|
387
|
+
all: () => [...docs.values()],
|
|
388
|
+
get: (id) => docs.get(id),
|
|
389
|
+
put: (doc) => {
|
|
390
|
+
docs.set(doc._id, doc);
|
|
391
|
+
},
|
|
392
|
+
remove: (id) => {
|
|
393
|
+
const doc = docs.get(id);
|
|
394
|
+
return doc && docs.delete(id), doc;
|
|
395
|
+
},
|
|
396
|
+
snapshot: () => [...docs.values()]
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
function cryptoRandomKey(length = 12) {
|
|
400
|
+
const bytes = new Uint8Array(length);
|
|
401
|
+
return globalThis.crypto.getRandomValues(bytes), [...bytes].map((b) => b.toString(16).padStart(2, "0")).join("").slice(0, length);
|
|
402
|
+
}
|
|
403
|
+
const RELEASE_DOC_PREFIX = "_.releases.", VERSIONS_PREFIX = "versions.", releaseDocId = (releaseId) => `${RELEASE_DOC_PREFIX}${releaseId}`, versionDocId = (releaseId, publishedId) => `${VERSIONS_PREFIX}${releaseId}.${publishedId}`;
|
|
328
404
|
function parseVersionId(versionId) {
|
|
329
|
-
if (!versionId.startsWith(VERSIONS_PREFIX
|
|
405
|
+
if (!versionId.startsWith(VERSIONS_PREFIX))
|
|
330
406
|
throw fakeError(
|
|
331
407
|
`"${versionId}" is not a version document id. Version ids look like "versions.<releaseId>.<publishedId>" (e.g. "versions.summer-drop.product-1").`
|
|
332
408
|
);
|
|
333
|
-
const rest = versionId.slice(VERSIONS_PREFIX
|
|
409
|
+
const rest = versionId.slice(VERSIONS_PREFIX.length), dot = rest.indexOf(".");
|
|
334
410
|
if (dot === -1)
|
|
335
411
|
throw fakeError(
|
|
336
412
|
`"${versionId}" is not a version document id. Version ids look like "versions.<releaseId>.<publishedId>" (e.g. "versions.summer-drop.product-1").`
|
|
@@ -376,7 +452,7 @@ function requireActive(release) {
|
|
|
376
452
|
`Release "${release.name}" is "${release.state}", but only "active" releases accept version writes (create / replace / discard / unpublish). Unschedule or unarchive it back to "active" first, or target a different release.`
|
|
377
453
|
);
|
|
378
454
|
}
|
|
379
|
-
function applyAction(store, action) {
|
|
455
|
+
async function applyAction(store, action, hooks) {
|
|
380
456
|
switch (action.actionType) {
|
|
381
457
|
case "sanity.action.release.create": {
|
|
382
458
|
if (maybeRelease(store, action.releaseId) !== void 0)
|
|
@@ -434,7 +510,7 @@ function applyAction(store, action) {
|
|
|
434
510
|
throw fakeError(
|
|
435
511
|
`Cannot publish release "${action.releaseId}": it is "${release.state}", and only "active" or "scheduled" releases can be published.`
|
|
436
512
|
);
|
|
437
|
-
const prefix = `${VERSIONS_PREFIX
|
|
513
|
+
const prefix = `${VERSIONS_PREFIX}${action.releaseId}.`, sources = store.all().filter((d) => d._id.startsWith(prefix));
|
|
438
514
|
for (const v of sources) {
|
|
439
515
|
const pubId = v._id.slice(prefix.length);
|
|
440
516
|
if (v._system?.delete === !0)
|
|
@@ -474,7 +550,7 @@ function applyAction(store, action) {
|
|
|
474
550
|
throw fakeError(
|
|
475
551
|
`Cannot delete release "${action.releaseId}": it is "${release.state}", and only "archived" or "published" releases can be deleted. Archive it first (sanity.action.release.archive) or publish it.`
|
|
476
552
|
);
|
|
477
|
-
const prefix = `${VERSIONS_PREFIX
|
|
553
|
+
const prefix = `${VERSIONS_PREFIX}${action.releaseId}.`;
|
|
478
554
|
for (const v of store.all())
|
|
479
555
|
v._id.startsWith(prefix) && store.remove(v._id);
|
|
480
556
|
return store.remove(release._id), { transactionId: cryptoRandomKey() };
|
|
@@ -518,9 +594,46 @@ function applyAction(store, action) {
|
|
|
518
594
|
};
|
|
519
595
|
return store.put(promote(doc)), { transactionId: cryptoRandomKey() };
|
|
520
596
|
}
|
|
597
|
+
case "sanity.action.document.publish": {
|
|
598
|
+
requireDraftOf(action);
|
|
599
|
+
const draft = store.get(action.draftId);
|
|
600
|
+
if (draft === void 0)
|
|
601
|
+
throw fakeError(
|
|
602
|
+
`Cannot publish "${action.publishedId}": draft "${action.draftId}" not found. document.publish promotes an existing draft onto the published id \u2014 create the draft first (e.g. client.createOrReplace({ _id: "${action.draftId}", ... })).`
|
|
603
|
+
);
|
|
604
|
+
const published = store.get(action.publishedId);
|
|
605
|
+
requireRevisionMatch(action.draftId, draft._rev, action.ifDraftRevisionId), requireRevisionMatch(action.publishedId, published?._rev, action.ifPublishedRevisionId);
|
|
606
|
+
const promoted = promote({ ...draft, _id: action.publishedId });
|
|
607
|
+
return published !== void 0 && (promoted._createdAt = published._createdAt), await hooks?.gate?.(promoted, published === void 0 ? "create" : "update"), await hooks?.guard?.("publish", published ?? null, promoted), store.put(promoted), store.remove(action.draftId), { transactionId: cryptoRandomKey() };
|
|
608
|
+
}
|
|
609
|
+
case "sanity.action.document.unpublish": {
|
|
610
|
+
requireDraftOf(action);
|
|
611
|
+
const published = store.get(action.publishedId);
|
|
612
|
+
if (published === void 0)
|
|
613
|
+
throw fakeError(
|
|
614
|
+
`Cannot unpublish "${action.publishedId}": no published document with that _id exists. document.unpublish retracts a live published document into "${action.draftId}".`
|
|
615
|
+
);
|
|
616
|
+
if (await hooks?.gate?.(published, "update"), await hooks?.guard?.("unpublish", published, null), store.get(action.draftId) === void 0) {
|
|
617
|
+
const draft = promote({ ...published, _id: action.draftId });
|
|
618
|
+
draft._createdAt = published._createdAt, store.put(draft);
|
|
619
|
+
}
|
|
620
|
+
return store.remove(action.publishedId), { transactionId: cryptoRandomKey() };
|
|
621
|
+
}
|
|
521
622
|
}
|
|
522
623
|
}
|
|
523
|
-
function
|
|
624
|
+
function requireDraftOf(action) {
|
|
625
|
+
if (!(publishedIdOf(action.draftId) === action.publishedId && action.draftId !== action.publishedId))
|
|
626
|
+
throw fakeError(
|
|
627
|
+
`${action.actionType}: draftId "${action.draftId}" is not the draft of publishedId "${action.publishedId}" \u2014 expected draftId "drafts.${action.publishedId}".`
|
|
628
|
+
);
|
|
629
|
+
}
|
|
630
|
+
function requireRevisionMatch(documentId, actualRev, expectedRev) {
|
|
631
|
+
if (!(expectedRev === void 0 || actualRev === expectedRev))
|
|
632
|
+
throw fakeError(
|
|
633
|
+
`Revision precondition failed for "${documentId}": expected _rev "${expectedRev}" but the document is at ${actualRev === void 0 ? "no revision (missing)" : `"${actualRev}"`}.`
|
|
634
|
+
);
|
|
635
|
+
}
|
|
636
|
+
async function applyActions(store, actions, hooks) {
|
|
524
637
|
const list = Array.isArray(actions) ? actions : [actions], snapshot = /* @__PURE__ */ new Map(), remember = (id) => {
|
|
525
638
|
snapshot.has(id) || snapshot.set(id, store.get(id));
|
|
526
639
|
};
|
|
@@ -535,11 +648,11 @@ function applyActions(store, actions) {
|
|
|
535
648
|
case "sanity.action.release.archive":
|
|
536
649
|
case "sanity.action.release.unarchive":
|
|
537
650
|
case "sanity.action.release.delete":
|
|
538
|
-
remember(`${RELEASE_DOC_PREFIX
|
|
651
|
+
remember(`${RELEASE_DOC_PREFIX}${a.releaseId}`);
|
|
539
652
|
break;
|
|
540
653
|
case "sanity.action.release.publish": {
|
|
541
|
-
remember(`${RELEASE_DOC_PREFIX
|
|
542
|
-
const prefix = `${VERSIONS_PREFIX
|
|
654
|
+
remember(`${RELEASE_DOC_PREFIX}${a.releaseId}`);
|
|
655
|
+
const prefix = `${VERSIONS_PREFIX}${a.releaseId}.`;
|
|
543
656
|
for (const d of store.all())
|
|
544
657
|
d._id.startsWith(prefix) && (remember(d._id), remember(d._id.slice(prefix.length)));
|
|
545
658
|
break;
|
|
@@ -556,8 +669,12 @@ function applyActions(store, actions) {
|
|
|
556
669
|
case "sanity.action.document.version.unpublish":
|
|
557
670
|
remember(a.versionId);
|
|
558
671
|
break;
|
|
672
|
+
case "sanity.action.document.publish":
|
|
673
|
+
case "sanity.action.document.unpublish":
|
|
674
|
+
remember(a.draftId), remember(a.publishedId);
|
|
675
|
+
break;
|
|
559
676
|
}
|
|
560
|
-
last = applyAction(store, a);
|
|
677
|
+
last = await applyAction(store, a, hooks);
|
|
561
678
|
}
|
|
562
679
|
return last;
|
|
563
680
|
} catch (err) {
|
|
@@ -566,82 +683,6 @@ function applyActions(store, actions) {
|
|
|
566
683
|
throw err;
|
|
567
684
|
}
|
|
568
685
|
}
|
|
569
|
-
const DRAFTS_PREFIX = "drafts.", VERSIONS_PREFIX = "versions.", RELEASE_DOC_PREFIX = "_.releases.";
|
|
570
|
-
function publishedIdOf(id) {
|
|
571
|
-
if (id.startsWith(DRAFTS_PREFIX)) return id.slice(DRAFTS_PREFIX.length);
|
|
572
|
-
if (id.startsWith(VERSIONS_PREFIX)) {
|
|
573
|
-
const rest = id.slice(VERSIONS_PREFIX.length), dot = rest.indexOf(".");
|
|
574
|
-
return dot === -1 ? rest : rest.slice(dot + 1);
|
|
575
|
-
}
|
|
576
|
-
return id;
|
|
577
|
-
}
|
|
578
|
-
function releaseNameOf(id) {
|
|
579
|
-
if (!id.startsWith(VERSIONS_PREFIX)) return;
|
|
580
|
-
const rest = id.slice(VERSIONS_PREFIX.length), dot = rest.indexOf(".");
|
|
581
|
-
if (dot !== -1)
|
|
582
|
-
return rest.slice(0, dot);
|
|
583
|
-
}
|
|
584
|
-
const isDraftId = (id) => id.startsWith(DRAFTS_PREFIX), isVersionId = (id) => id.startsWith(VERSIONS_PREFIX), isSystemId = (id) => id.startsWith("_."), isReleaseDocId = (id) => id.startsWith(RELEASE_DOC_PREFIX);
|
|
585
|
-
function applyPerspective(docs, perspective) {
|
|
586
|
-
if (perspective === "raw") return [...docs];
|
|
587
|
-
if (perspective === "published")
|
|
588
|
-
return docs.filter((d) => !isDraftId(d._id) && !isVersionId(d._id) && !isSystemId(d._id));
|
|
589
|
-
if (perspective === "drafts")
|
|
590
|
-
return mergeStack(docs, [], { includeDrafts: !0, includeReleaseDocs: !1 });
|
|
591
|
-
const stack = [];
|
|
592
|
-
let includeDrafts = !1;
|
|
593
|
-
for (const entry of perspective)
|
|
594
|
-
entry === "drafts" ? includeDrafts = !0 : stack.push(entry);
|
|
595
|
-
return mergeStack(docs, stack, { includeDrafts, includeReleaseDocs: !0 });
|
|
596
|
-
}
|
|
597
|
-
function mergeStack(docs, releaseStack, options) {
|
|
598
|
-
const groups = /* @__PURE__ */ new Map(), systemReleases = [];
|
|
599
|
-
for (const doc of docs) {
|
|
600
|
-
if (isReleaseDocId(doc._id)) {
|
|
601
|
-
options.includeReleaseDocs && systemReleases.push(doc);
|
|
602
|
-
continue;
|
|
603
|
-
}
|
|
604
|
-
if (isSystemId(doc._id)) continue;
|
|
605
|
-
const pubId = publishedIdOf(doc._id);
|
|
606
|
-
let group = groups.get(pubId);
|
|
607
|
-
if (group === void 0 && (group = { versions: /* @__PURE__ */ new Map() }, groups.set(pubId, group)), isDraftId(doc._id))
|
|
608
|
-
group.draft = doc;
|
|
609
|
-
else if (isVersionId(doc._id)) {
|
|
610
|
-
const releaseName = releaseNameOf(doc._id);
|
|
611
|
-
releaseName !== void 0 && group.versions.set(releaseName, doc);
|
|
612
|
-
} else
|
|
613
|
-
group.published = doc;
|
|
614
|
-
}
|
|
615
|
-
const out = [];
|
|
616
|
-
for (const [pubId, group] of groups) {
|
|
617
|
-
let chosen;
|
|
618
|
-
for (const releaseName of releaseStack) {
|
|
619
|
-
const v = group.versions.get(releaseName);
|
|
620
|
-
if (v !== void 0) {
|
|
621
|
-
if (isUnpublishStub(v)) {
|
|
622
|
-
chosen = void 0;
|
|
623
|
-
break;
|
|
624
|
-
}
|
|
625
|
-
chosen = v;
|
|
626
|
-
break;
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
chosen === void 0 && options.includeDrafts && group.draft !== void 0 && (chosen = group.draft), chosen === void 0 && group.published !== void 0 && (releaseStack.some((name) => {
|
|
630
|
-
const v = group.versions.get(name);
|
|
631
|
-
return v !== void 0 && isUnpublishStub(v);
|
|
632
|
-
}) || (chosen = group.published)), chosen !== void 0 && out.push(projectToPublishedId(chosen, pubId));
|
|
633
|
-
}
|
|
634
|
-
return [...out, ...systemReleases];
|
|
635
|
-
}
|
|
636
|
-
function isUnpublishStub(doc) {
|
|
637
|
-
return doc._system?.delete === !0;
|
|
638
|
-
}
|
|
639
|
-
function projectToPublishedId(doc, pubId) {
|
|
640
|
-
return doc._id === pubId ? doc : { ...doc, _id: pubId, _originalId: doc._id };
|
|
641
|
-
}
|
|
642
|
-
function resolveDocument(docs, id, perspective) {
|
|
643
|
-
return perspective === "raw" ? docs.find((d) => d._id === id) : applyPerspective(docs, perspective).find((d) => d._id === id);
|
|
644
|
-
}
|
|
645
686
|
function createTransaction(executor) {
|
|
646
687
|
const mutations = [], handle = {
|
|
647
688
|
create: (doc) => (mutations.push({ kind: "create", doc }), handle),
|
|
@@ -1136,7 +1177,7 @@ function makeHandle(backend, projectId, dataset, accessControl, perspective, mut
|
|
|
1136
1177
|
txnOptions?.returnDocuments ?? !1,
|
|
1137
1178
|
txnOptions?.returnFirst ?? !1
|
|
1138
1179
|
);
|
|
1139
|
-
}, runAction = (actions) => serializeWrite(backend, async () => applyActions(store, actions)), accessVisibleDocs = async () => {
|
|
1180
|
+
}, runAction = (actions) => serializeWrite(backend, async () => applyActions(store, actions, { gate, guard: guardGate })), accessVisibleDocs = async () => {
|
|
1140
1181
|
const all = store.all();
|
|
1141
1182
|
if (activeAccess() === void 0) return all;
|
|
1142
1183
|
const out = [];
|