@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.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 — before the
391
- * store changes; **throw to reject the write** (the throw propagates out of the
392
- * client call, exactly as a real lake rejection would). The release/version
393
- * action API (`client.action`, `client.releases.*`, `createVersion`) is **not**
394
- * intercepted.
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 = "create" | "update" | "delete";
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; `after` is null on delete.
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 store.
793
- * Returns a synthetic `transactionId`. Multi-action arrays are applied
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 — before the
391
- * store changes; **throw to reject the write** (the throw propagates out of the
392
- * client call, exactly as a real lake rejection would). The release/version
393
- * action API (`client.action`, `client.releases.*`, `createVersion`) is **not**
394
- * intercepted.
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 = "create" | "update" | "delete";
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; `after` is null on delete.
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 store.
793
- * Returns a synthetic `transactionId`. Multi-action arrays are applied
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 RELEASE_DOC_PREFIX$1 = "_.releases.", VERSIONS_PREFIX$1 = "versions.", releaseDocId = (releaseId) => `${RELEASE_DOC_PREFIX$1}${releaseId}`, versionDocId = (releaseId, publishedId) => `${VERSIONS_PREFIX$1}${releaseId}.${publishedId}`;
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$1))
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$1.length), dot = rest.indexOf(".");
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$1}${action.releaseId}.`, sources = store.all().filter((d) => d._id.startsWith(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$1}${action.releaseId}.`;
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 applyActions(store, actions) {
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$1}${a.releaseId}`);
651
+ remember(`${RELEASE_DOC_PREFIX}${a.releaseId}`);
539
652
  break;
540
653
  case "sanity.action.release.publish": {
541
- remember(`${RELEASE_DOC_PREFIX$1}${a.releaseId}`);
542
- const prefix = `${VERSIONS_PREFIX$1}${a.releaseId}.`;
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 = [];