@sanity-labs/client-fake-for-test 0.7.0 → 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 CHANGED
@@ -73,57 +73,6 @@ function makeRequestImpl(getAccess, projectId, dataset) {
73
73
  );
74
74
  };
75
75
  }
76
- function createTestStore(initial) {
77
- const docs = /* @__PURE__ */ new Map(), promote2 = (doc) => {
78
- if (typeof doc._id != "string" || doc._id.length === 0)
79
- throw fakeError(
80
- `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)}`
81
- );
82
- if (typeof doc._type != "string" || doc._type.length === 0)
83
- throw fakeError(
84
- `Document "${doc._id}" is missing a non-empty string \`_type\` (got ${JSON.stringify(doc._type)}). Every Sanity document needs a _type.`
85
- );
86
- const now = (/* @__PURE__ */ new Date()).toISOString();
87
- return {
88
- ...doc,
89
- _id: doc._id,
90
- _type: doc._type,
91
- _rev: doc._rev ?? cryptoRandomKey(),
92
- _createdAt: doc._createdAt ?? now,
93
- _updatedAt: doc._updatedAt ?? now
94
- };
95
- }, seed = (documents) => {
96
- docs.clear();
97
- for (const d of documents) {
98
- const promoted = promote2(d);
99
- docs.set(promoted._id, promoted);
100
- }
101
- };
102
- return initial && seed(initial), {
103
- seed,
104
- add: (documents) => {
105
- for (const d of documents) {
106
- const promoted = promote2(d);
107
- docs.set(promoted._id, promoted);
108
- }
109
- },
110
- reset: () => docs.clear(),
111
- all: () => [...docs.values()],
112
- get: (id) => docs.get(id),
113
- put: (doc) => {
114
- docs.set(doc._id, doc);
115
- },
116
- remove: (id) => {
117
- const doc = docs.get(id);
118
- return doc && docs.delete(id), doc;
119
- },
120
- snapshot: () => [...docs.values()]
121
- };
122
- }
123
- function cryptoRandomKey(length = 12) {
124
- const bytes = new Uint8Array(length);
125
- return globalThis.crypto.getRandomValues(bytes), [...bytes].map((b) => b.toString(16).padStart(2, "0")).join("").slice(0, length);
126
- }
127
76
  function describeType(value) {
128
77
  return value === null ? "null" : Array.isArray(value) ? "an array" : `a ${typeof value}`;
129
78
  }
@@ -326,13 +275,140 @@ function insertIntoArray(doc, position, anchor, items) {
326
275
  const idx = resolveSelector(arr, last.selector);
327
276
  position === "replace" ? arr.splice(idx, 1, ...items) : position === "before" ? arr.splice(idx, 0, ...items) : arr.splice(idx + 1, 0, ...items);
328
277
  }
329
- 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}`;
278
+ const DRAFTS_PREFIX = "drafts.", VERSIONS_PREFIX$1 = "versions.", RELEASE_DOC_PREFIX$1 = "_.releases.";
279
+ function publishedIdOf(id) {
280
+ if (id.startsWith(DRAFTS_PREFIX)) return id.slice(DRAFTS_PREFIX.length);
281
+ if (id.startsWith(VERSIONS_PREFIX$1)) {
282
+ const rest = id.slice(VERSIONS_PREFIX$1.length), dot = rest.indexOf(".");
283
+ return dot === -1 ? rest : rest.slice(dot + 1);
284
+ }
285
+ return id;
286
+ }
287
+ function releaseNameOf(id) {
288
+ if (!id.startsWith(VERSIONS_PREFIX$1)) return;
289
+ const rest = id.slice(VERSIONS_PREFIX$1.length), dot = rest.indexOf(".");
290
+ if (dot !== -1)
291
+ return rest.slice(0, dot);
292
+ }
293
+ 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);
294
+ function applyPerspective(docs, perspective) {
295
+ if (perspective === "raw") return [...docs];
296
+ if (perspective === "published")
297
+ return docs.filter((d) => !isDraftId(d._id) && !isVersionId(d._id) && !isSystemId(d._id));
298
+ if (perspective === "drafts")
299
+ return mergeStack(docs, [], { includeDrafts: !0, includeReleaseDocs: !1 });
300
+ const stack = [];
301
+ let includeDrafts = !1;
302
+ for (const entry of perspective)
303
+ entry === "drafts" ? includeDrafts = !0 : stack.push(entry);
304
+ return mergeStack(docs, stack, { includeDrafts, includeReleaseDocs: !0 });
305
+ }
306
+ function mergeStack(docs, releaseStack, options) {
307
+ const groups = /* @__PURE__ */ new Map(), systemReleases = [];
308
+ for (const doc of docs) {
309
+ if (isReleaseDocId(doc._id)) {
310
+ options.includeReleaseDocs && systemReleases.push(doc);
311
+ continue;
312
+ }
313
+ if (isSystemId(doc._id)) continue;
314
+ const pubId = publishedIdOf(doc._id);
315
+ let group = groups.get(pubId);
316
+ if (group === void 0 && (group = { versions: /* @__PURE__ */ new Map() }, groups.set(pubId, group)), isDraftId(doc._id))
317
+ group.draft = doc;
318
+ else if (isVersionId(doc._id)) {
319
+ const releaseName = releaseNameOf(doc._id);
320
+ releaseName !== void 0 && group.versions.set(releaseName, doc);
321
+ } else
322
+ group.published = doc;
323
+ }
324
+ const out = [];
325
+ for (const [pubId, group] of groups) {
326
+ let chosen;
327
+ for (const releaseName of releaseStack) {
328
+ const v = group.versions.get(releaseName);
329
+ if (v !== void 0) {
330
+ if (isUnpublishStub(v)) {
331
+ chosen = void 0;
332
+ break;
333
+ }
334
+ chosen = v;
335
+ break;
336
+ }
337
+ }
338
+ chosen === void 0 && options.includeDrafts && group.draft !== void 0 && (chosen = group.draft), chosen === void 0 && group.published !== void 0 && (releaseStack.some((name) => {
339
+ const v = group.versions.get(name);
340
+ return v !== void 0 && isUnpublishStub(v);
341
+ }) || (chosen = group.published)), chosen !== void 0 && out.push(projectToPublishedId(chosen, pubId));
342
+ }
343
+ return [...out, ...systemReleases];
344
+ }
345
+ function isUnpublishStub(doc) {
346
+ return doc._system?.delete === !0;
347
+ }
348
+ function projectToPublishedId(doc, pubId) {
349
+ return doc._id === pubId ? doc : { ...doc, _id: pubId, _originalId: doc._id };
350
+ }
351
+ function resolveDocument(docs, id, perspective) {
352
+ return perspective === "raw" ? docs.find((d) => d._id === id) : applyPerspective(docs, perspective).find((d) => d._id === id);
353
+ }
354
+ function createTestStore(initial) {
355
+ const docs = /* @__PURE__ */ new Map(), promote2 = (doc) => {
356
+ if (typeof doc._id != "string" || doc._id.length === 0)
357
+ throw fakeError(
358
+ `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)}`
359
+ );
360
+ if (typeof doc._type != "string" || doc._type.length === 0)
361
+ throw fakeError(
362
+ `Document "${doc._id}" is missing a non-empty string \`_type\` (got ${JSON.stringify(doc._type)}). Every Sanity document needs a _type.`
363
+ );
364
+ const now = (/* @__PURE__ */ new Date()).toISOString();
365
+ return {
366
+ ...doc,
367
+ _id: doc._id,
368
+ _type: doc._type,
369
+ _rev: doc._rev ?? cryptoRandomKey(),
370
+ _createdAt: doc._createdAt ?? now,
371
+ _updatedAt: doc._updatedAt ?? now
372
+ };
373
+ }, seed = (documents) => {
374
+ docs.clear();
375
+ for (const d of documents) {
376
+ const promoted = promote2(d);
377
+ docs.set(promoted._id, promoted);
378
+ }
379
+ };
380
+ return initial && seed(initial), {
381
+ seed,
382
+ add: (documents) => {
383
+ for (const d of documents) {
384
+ const promoted = promote2(d);
385
+ docs.set(promoted._id, promoted);
386
+ }
387
+ },
388
+ reset: () => docs.clear(),
389
+ all: () => [...docs.values()],
390
+ get: (id) => docs.get(id),
391
+ put: (doc) => {
392
+ docs.set(doc._id, doc);
393
+ },
394
+ remove: (id) => {
395
+ const doc = docs.get(id);
396
+ return doc && docs.delete(id), doc;
397
+ },
398
+ snapshot: () => [...docs.values()]
399
+ };
400
+ }
401
+ function cryptoRandomKey(length = 12) {
402
+ const bytes = new Uint8Array(length);
403
+ return globalThis.crypto.getRandomValues(bytes), [...bytes].map((b) => b.toString(16).padStart(2, "0")).join("").slice(0, length);
404
+ }
405
+ const RELEASE_DOC_PREFIX = "_.releases.", VERSIONS_PREFIX = "versions.", releaseDocId = (releaseId) => `${RELEASE_DOC_PREFIX}${releaseId}`, versionDocId = (releaseId, publishedId) => `${VERSIONS_PREFIX}${releaseId}.${publishedId}`;
330
406
  function parseVersionId(versionId) {
331
- if (!versionId.startsWith(VERSIONS_PREFIX$1))
407
+ if (!versionId.startsWith(VERSIONS_PREFIX))
332
408
  throw fakeError(
333
409
  `"${versionId}" is not a version document id. Version ids look like "versions.<releaseId>.<publishedId>" (e.g. "versions.summer-drop.product-1").`
334
410
  );
335
- const rest = versionId.slice(VERSIONS_PREFIX$1.length), dot = rest.indexOf(".");
411
+ const rest = versionId.slice(VERSIONS_PREFIX.length), dot = rest.indexOf(".");
336
412
  if (dot === -1)
337
413
  throw fakeError(
338
414
  `"${versionId}" is not a version document id. Version ids look like "versions.<releaseId>.<publishedId>" (e.g. "versions.summer-drop.product-1").`
@@ -378,7 +454,7 @@ function requireActive(release) {
378
454
  `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.`
379
455
  );
380
456
  }
381
- function applyAction(store, action) {
457
+ async function applyAction(store, action, hooks) {
382
458
  switch (action.actionType) {
383
459
  case "sanity.action.release.create": {
384
460
  if (maybeRelease(store, action.releaseId) !== void 0)
@@ -436,7 +512,7 @@ function applyAction(store, action) {
436
512
  throw fakeError(
437
513
  `Cannot publish release "${action.releaseId}": it is "${release.state}", and only "active" or "scheduled" releases can be published.`
438
514
  );
439
- const prefix = `${VERSIONS_PREFIX$1}${action.releaseId}.`, sources = store.all().filter((d) => d._id.startsWith(prefix));
515
+ const prefix = `${VERSIONS_PREFIX}${action.releaseId}.`, sources = store.all().filter((d) => d._id.startsWith(prefix));
440
516
  for (const v of sources) {
441
517
  const pubId = v._id.slice(prefix.length);
442
518
  if (v._system?.delete === !0)
@@ -476,7 +552,7 @@ function applyAction(store, action) {
476
552
  throw fakeError(
477
553
  `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.`
478
554
  );
479
- const prefix = `${VERSIONS_PREFIX$1}${action.releaseId}.`;
555
+ const prefix = `${VERSIONS_PREFIX}${action.releaseId}.`;
480
556
  for (const v of store.all())
481
557
  v._id.startsWith(prefix) && store.remove(v._id);
482
558
  return store.remove(release._id), { transactionId: cryptoRandomKey() };
@@ -520,9 +596,46 @@ function applyAction(store, action) {
520
596
  };
521
597
  return store.put(promote(doc)), { transactionId: cryptoRandomKey() };
522
598
  }
599
+ case "sanity.action.document.publish": {
600
+ requireDraftOf(action);
601
+ const draft = store.get(action.draftId);
602
+ if (draft === void 0)
603
+ throw fakeError(
604
+ `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}", ... })).`
605
+ );
606
+ const published = store.get(action.publishedId);
607
+ requireRevisionMatch(action.draftId, draft._rev, action.ifDraftRevisionId), requireRevisionMatch(action.publishedId, published?._rev, action.ifPublishedRevisionId);
608
+ const promoted = promote({ ...draft, _id: action.publishedId });
609
+ 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() };
610
+ }
611
+ case "sanity.action.document.unpublish": {
612
+ requireDraftOf(action);
613
+ const published = store.get(action.publishedId);
614
+ if (published === void 0)
615
+ throw fakeError(
616
+ `Cannot unpublish "${action.publishedId}": no published document with that _id exists. document.unpublish retracts a live published document into "${action.draftId}".`
617
+ );
618
+ if (await hooks?.gate?.(published, "update"), await hooks?.guard?.("unpublish", published, null), store.get(action.draftId) === void 0) {
619
+ const draft = promote({ ...published, _id: action.draftId });
620
+ draft._createdAt = published._createdAt, store.put(draft);
621
+ }
622
+ return store.remove(action.publishedId), { transactionId: cryptoRandomKey() };
623
+ }
523
624
  }
524
625
  }
525
- function applyActions(store, actions) {
626
+ function requireDraftOf(action) {
627
+ if (!(publishedIdOf(action.draftId) === action.publishedId && action.draftId !== action.publishedId))
628
+ throw fakeError(
629
+ `${action.actionType}: draftId "${action.draftId}" is not the draft of publishedId "${action.publishedId}" \u2014 expected draftId "drafts.${action.publishedId}".`
630
+ );
631
+ }
632
+ function requireRevisionMatch(documentId, actualRev, expectedRev) {
633
+ if (!(expectedRev === void 0 || actualRev === expectedRev))
634
+ throw fakeError(
635
+ `Revision precondition failed for "${documentId}": expected _rev "${expectedRev}" but the document is at ${actualRev === void 0 ? "no revision (missing)" : `"${actualRev}"`}.`
636
+ );
637
+ }
638
+ async function applyActions(store, actions, hooks) {
526
639
  const list = Array.isArray(actions) ? actions : [actions], snapshot = /* @__PURE__ */ new Map(), remember = (id) => {
527
640
  snapshot.has(id) || snapshot.set(id, store.get(id));
528
641
  };
@@ -537,11 +650,11 @@ function applyActions(store, actions) {
537
650
  case "sanity.action.release.archive":
538
651
  case "sanity.action.release.unarchive":
539
652
  case "sanity.action.release.delete":
540
- remember(`${RELEASE_DOC_PREFIX$1}${a.releaseId}`);
653
+ remember(`${RELEASE_DOC_PREFIX}${a.releaseId}`);
541
654
  break;
542
655
  case "sanity.action.release.publish": {
543
- remember(`${RELEASE_DOC_PREFIX$1}${a.releaseId}`);
544
- const prefix = `${VERSIONS_PREFIX$1}${a.releaseId}.`;
656
+ remember(`${RELEASE_DOC_PREFIX}${a.releaseId}`);
657
+ const prefix = `${VERSIONS_PREFIX}${a.releaseId}.`;
545
658
  for (const d of store.all())
546
659
  d._id.startsWith(prefix) && (remember(d._id), remember(d._id.slice(prefix.length)));
547
660
  break;
@@ -558,8 +671,12 @@ function applyActions(store, actions) {
558
671
  case "sanity.action.document.version.unpublish":
559
672
  remember(a.versionId);
560
673
  break;
674
+ case "sanity.action.document.publish":
675
+ case "sanity.action.document.unpublish":
676
+ remember(a.draftId), remember(a.publishedId);
677
+ break;
561
678
  }
562
- last = applyAction(store, a);
679
+ last = await applyAction(store, a, hooks);
563
680
  }
564
681
  return last;
565
682
  } catch (err) {
@@ -568,82 +685,6 @@ function applyActions(store, actions) {
568
685
  throw err;
569
686
  }
570
687
  }
571
- const DRAFTS_PREFIX = "drafts.", VERSIONS_PREFIX = "versions.", RELEASE_DOC_PREFIX = "_.releases.";
572
- function publishedIdOf(id) {
573
- if (id.startsWith(DRAFTS_PREFIX)) return id.slice(DRAFTS_PREFIX.length);
574
- if (id.startsWith(VERSIONS_PREFIX)) {
575
- const rest = id.slice(VERSIONS_PREFIX.length), dot = rest.indexOf(".");
576
- return dot === -1 ? rest : rest.slice(dot + 1);
577
- }
578
- return id;
579
- }
580
- function releaseNameOf(id) {
581
- if (!id.startsWith(VERSIONS_PREFIX)) return;
582
- const rest = id.slice(VERSIONS_PREFIX.length), dot = rest.indexOf(".");
583
- if (dot !== -1)
584
- return rest.slice(0, dot);
585
- }
586
- const isDraftId = (id) => id.startsWith(DRAFTS_PREFIX), isVersionId = (id) => id.startsWith(VERSIONS_PREFIX), isSystemId = (id) => id.startsWith("_."), isReleaseDocId = (id) => id.startsWith(RELEASE_DOC_PREFIX);
587
- function applyPerspective(docs, perspective) {
588
- if (perspective === "raw") return [...docs];
589
- if (perspective === "published")
590
- return docs.filter((d) => !isDraftId(d._id) && !isVersionId(d._id) && !isSystemId(d._id));
591
- if (perspective === "drafts")
592
- return mergeStack(docs, [], { includeDrafts: !0, includeReleaseDocs: !1 });
593
- const stack = [];
594
- let includeDrafts = !1;
595
- for (const entry of perspective)
596
- entry === "drafts" ? includeDrafts = !0 : stack.push(entry);
597
- return mergeStack(docs, stack, { includeDrafts, includeReleaseDocs: !0 });
598
- }
599
- function mergeStack(docs, releaseStack, options) {
600
- const groups = /* @__PURE__ */ new Map(), systemReleases = [];
601
- for (const doc of docs) {
602
- if (isReleaseDocId(doc._id)) {
603
- options.includeReleaseDocs && systemReleases.push(doc);
604
- continue;
605
- }
606
- if (isSystemId(doc._id)) continue;
607
- const pubId = publishedIdOf(doc._id);
608
- let group = groups.get(pubId);
609
- if (group === void 0 && (group = { versions: /* @__PURE__ */ new Map() }, groups.set(pubId, group)), isDraftId(doc._id))
610
- group.draft = doc;
611
- else if (isVersionId(doc._id)) {
612
- const releaseName = releaseNameOf(doc._id);
613
- releaseName !== void 0 && group.versions.set(releaseName, doc);
614
- } else
615
- group.published = doc;
616
- }
617
- const out = [];
618
- for (const [pubId, group] of groups) {
619
- let chosen;
620
- for (const releaseName of releaseStack) {
621
- const v = group.versions.get(releaseName);
622
- if (v !== void 0) {
623
- if (isUnpublishStub(v)) {
624
- chosen = void 0;
625
- break;
626
- }
627
- chosen = v;
628
- break;
629
- }
630
- }
631
- chosen === void 0 && options.includeDrafts && group.draft !== void 0 && (chosen = group.draft), chosen === void 0 && group.published !== void 0 && (releaseStack.some((name) => {
632
- const v = group.versions.get(name);
633
- return v !== void 0 && isUnpublishStub(v);
634
- }) || (chosen = group.published)), chosen !== void 0 && out.push(projectToPublishedId(chosen, pubId));
635
- }
636
- return [...out, ...systemReleases];
637
- }
638
- function isUnpublishStub(doc) {
639
- return doc._system?.delete === !0;
640
- }
641
- function projectToPublishedId(doc, pubId) {
642
- return doc._id === pubId ? doc : { ...doc, _id: pubId, _originalId: doc._id };
643
- }
644
- function resolveDocument(docs, id, perspective) {
645
- return perspective === "raw" ? docs.find((d) => d._id === id) : applyPerspective(docs, perspective).find((d) => d._id === id);
646
- }
647
688
  function createTransaction(executor) {
648
689
  const mutations = [], handle = {
649
690
  create: (doc) => (mutations.push({ kind: "create", doc }), handle),
@@ -875,6 +916,15 @@ const RECORDED_METHODS = /* @__PURE__ */ new Set([
875
916
  "unpublishVersion",
876
917
  "request"
877
918
  ]), DEFAULT_PROJECT_ID = "test", DEFAULT_DATASET = "test";
919
+ function serializeWrite(backend, run) {
920
+ const result = backend.writeChain.then(run);
921
+ return backend.writeChain = result.then(
922
+ () => {
923
+ },
924
+ () => {
925
+ }
926
+ ), result;
927
+ }
878
928
  function requireIdentity(backend, token, site) {
879
929
  if (backend.identities.has(token)) return;
880
930
  const known = [...backend.identities.keys()];
@@ -899,7 +949,8 @@ function createTestClient(options = {}) {
899
949
  stubs: /* @__PURE__ */ new Map(),
900
950
  calls: [],
901
951
  concurrentEdits: /* @__PURE__ */ new Map(),
902
- identities: new Map(Object.entries(options.identities ?? {}))
952
+ identities: new Map(Object.entries(options.identities ?? {})),
953
+ writeChain: Promise.resolve()
903
954
  }, registry = backend.registry;
904
955
  rejectAmbiguousIdentity("createTestClient()", options), options.token !== void 0 && requireIdentity(backend, options.token, "createTestClient()");
905
956
  const ensureStore = (projectId, dataset) => {
@@ -1128,7 +1179,7 @@ function makeHandle(backend, projectId, dataset, accessControl, perspective, mut
1128
1179
  txnOptions?.returnDocuments ?? !1,
1129
1180
  txnOptions?.returnFirst ?? !1
1130
1181
  );
1131
- }, accessVisibleDocs = async () => {
1182
+ }, runAction = (actions) => serializeWrite(backend, async () => applyActions(store, actions, { gate, guard: guardGate })), accessVisibleDocs = async () => {
1132
1183
  const all = store.all();
1133
1184
  if (activeAccess() === void 0) return all;
1134
1185
  const out = [];
@@ -1172,7 +1223,7 @@ function makeHandle(backend, projectId, dataset, accessControl, perspective, mut
1172
1223
  const view = await visibleDocs(effective);
1173
1224
  return ids.map((id) => resolveDocument(view, id, effective) ?? null);
1174
1225
  },
1175
- create: async (doc, opts) => {
1226
+ create: (doc, opts) => serializeWrite(backend, async () => {
1176
1227
  if (checkRevision(doc._id, opts?.ifRevisionId), store.get(doc._id) !== void 0)
1177
1228
  throw fakeError(
1178
1229
  `create() failed: a document with _id "${doc._id}" already exists in dataset "${projectId}:${dataset}". Use createOrReplace() to overwrite it, or createIfNotExists() to leave the existing one untouched.`
@@ -1186,8 +1237,8 @@ function makeHandle(backend, projectId, dataset, accessControl, perspective, mut
1186
1237
  transactionId,
1187
1238
  opts
1188
1239
  );
1189
- },
1190
- createOrReplace: async (doc, opts) => {
1240
+ }),
1241
+ createOrReplace: (doc, opts) => serializeWrite(backend, async () => {
1191
1242
  checkRevision(doc._id, opts?.ifRevisionId);
1192
1243
  const promoted = promote2(doc), existing = store.get(doc._id);
1193
1244
  await gate(existing ?? promoted, existing !== void 0 ? "update" : "create"), await guardGate(existing !== void 0 ? "update" : "create", existing ?? null, promoted);
@@ -1200,8 +1251,8 @@ function makeHandle(backend, projectId, dataset, accessControl, perspective, mut
1200
1251
  transactionId,
1201
1252
  opts
1202
1253
  );
1203
- },
1204
- createIfNotExists: async (doc, opts) => {
1254
+ }),
1255
+ createIfNotExists: (doc, opts) => serializeWrite(backend, async () => {
1205
1256
  checkRevision(doc._id, opts?.ifRevisionId);
1206
1257
  const existing = store.get(doc._id), transactionId = opts?.transactionId ?? cryptoRandomKey();
1207
1258
  if (existing !== void 0)
@@ -1218,8 +1269,8 @@ function makeHandle(backend, projectId, dataset, accessControl, perspective, mut
1218
1269
  transactionId,
1219
1270
  opts
1220
1271
  );
1221
- },
1222
- delete: async (id, opts) => {
1272
+ }),
1273
+ delete: (id, opts) => serializeWrite(backend, async () => {
1223
1274
  checkRevision(id, opts?.ifRevisionId);
1224
1275
  const existing = store.get(id);
1225
1276
  existing !== void 0 && await gate(existing, "update"), existing !== void 0 && await guardGate("delete", existing, null);
@@ -1232,23 +1283,35 @@ function makeHandle(backend, projectId, dataset, accessControl, perspective, mut
1232
1283
  opts.returnDocuments ?? !0,
1233
1284
  opts.returnFirst ?? !0
1234
1285
  );
1235
- },
1286
+ }),
1236
1287
  patch: (documentId) => createPatch(documentId, {
1237
- apply: async (docId, ops, ifRev, options) => {
1288
+ apply: (docId, ops, ifRev, options) => serializeWrite(backend, async () => {
1238
1289
  const doc = await applyPatch(docId, ops, ifRev);
1239
1290
  return record(
1240
1291
  cryptoRandomKey(),
1241
- [{ kind: "patch", documentId: docId, ops, ...ifRev !== void 0 ? { ifRev } : {} }],
1292
+ [
1293
+ {
1294
+ kind: "patch",
1295
+ documentId: docId,
1296
+ ops,
1297
+ ...ifRev !== void 0 ? { ifRev } : {}
1298
+ }
1299
+ ],
1242
1300
  options?.tag
1243
1301
  ), doc;
1244
- }
1302
+ })
1303
+ }),
1304
+ transaction: () => createTransaction({
1305
+ apply: (mutations, txnOptions) => serializeWrite(backend, () => applyTransaction(mutations, txnOptions))
1245
1306
  }),
1246
- transaction: () => createTransaction({ apply: applyTransaction }),
1247
- mutate: async (operations, options) => applyTransaction(toTxnMutations(operations), options),
1248
- action: async (action) => applyActions(store, action),
1307
+ mutate: (operations, options) => serializeWrite(backend, () => applyTransaction(toTxnMutations(operations), options)),
1308
+ // Synchronous writes still take the lock: an action landing while a
1309
+ // guarded patch is parked in its gate would otherwise be clobbered
1310
+ // when that patch resumes over its pre-action snapshot.
1311
+ action: (action) => runAction(action),
1249
1312
  releases: {
1250
1313
  create: async ({ releaseId, metadata }) => {
1251
- const result = applyActions(store, {
1314
+ const result = await runAction({
1252
1315
  actionType: "sanity.action.release.create",
1253
1316
  releaseId,
1254
1317
  ...metadata !== void 0 ? { metadata } : {}
@@ -1259,30 +1322,30 @@ function makeHandle(backend, projectId, dataset, accessControl, perspective, mut
1259
1322
  metadata: doc?.metadata ?? { releaseType: "undecided" }
1260
1323
  };
1261
1324
  },
1262
- edit: async ({ releaseId, patch }) => applyActions(store, { actionType: "sanity.action.release.edit", releaseId, patch }),
1325
+ edit: ({ releaseId, patch }) => runAction({ actionType: "sanity.action.release.edit", releaseId, patch }),
1263
1326
  get: async ({ releaseId }) => store.get(`_.releases.${releaseId}`),
1264
1327
  fetchDocuments: async ({ releaseId }) => {
1265
1328
  const prefix = `versions.${releaseId}.`;
1266
1329
  return store.all().filter((d) => d._id.startsWith(prefix));
1267
1330
  },
1268
- schedule: async ({ releaseId, publishAt }) => applyActions(store, {
1331
+ schedule: ({ releaseId, publishAt }) => runAction({
1269
1332
  actionType: "sanity.action.release.schedule",
1270
1333
  releaseId,
1271
1334
  publishAt
1272
1335
  }),
1273
- unschedule: async ({ releaseId }) => applyActions(store, { actionType: "sanity.action.release.unschedule", releaseId }),
1274
- publish: async ({ releaseId }) => applyActions(store, { actionType: "sanity.action.release.publish", releaseId }),
1275
- archive: async ({ releaseId }) => applyActions(store, { actionType: "sanity.action.release.archive", releaseId }),
1276
- unarchive: async ({ releaseId }) => applyActions(store, { actionType: "sanity.action.release.unarchive", releaseId }),
1277
- delete: async ({ releaseId }) => applyActions(store, { actionType: "sanity.action.release.delete", releaseId })
1336
+ unschedule: ({ releaseId }) => runAction({ actionType: "sanity.action.release.unschedule", releaseId }),
1337
+ publish: ({ releaseId }) => runAction({ actionType: "sanity.action.release.publish", releaseId }),
1338
+ archive: ({ releaseId }) => runAction({ actionType: "sanity.action.release.archive", releaseId }),
1339
+ unarchive: ({ releaseId }) => runAction({ actionType: "sanity.action.release.unarchive", releaseId }),
1340
+ delete: ({ releaseId }) => runAction({ actionType: "sanity.action.release.delete", releaseId })
1278
1341
  },
1279
- createVersion: async ({ releaseId, publishedId, document }) => applyActions(store, {
1342
+ createVersion: ({ releaseId, publishedId, document }) => runAction({
1280
1343
  actionType: "sanity.action.document.version.create",
1281
1344
  releaseId,
1282
1345
  publishedId,
1283
1346
  document
1284
1347
  }),
1285
- unpublishVersion: async ({ releaseId, publishedId }) => applyActions(store, {
1348
+ unpublishVersion: ({ releaseId, publishedId }) => runAction({
1286
1349
  actionType: "sanity.action.document.version.unpublish",
1287
1350
  versionId: `versions.${releaseId}.${publishedId}`,
1288
1351
  publishedId