@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.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),
@@ -873,6 +914,15 @@ const RECORDED_METHODS = /* @__PURE__ */ new Set([
873
914
  "unpublishVersion",
874
915
  "request"
875
916
  ]), DEFAULT_PROJECT_ID = "test", DEFAULT_DATASET = "test";
917
+ function serializeWrite(backend, run) {
918
+ const result = backend.writeChain.then(run);
919
+ return backend.writeChain = result.then(
920
+ () => {
921
+ },
922
+ () => {
923
+ }
924
+ ), result;
925
+ }
876
926
  function requireIdentity(backend, token, site) {
877
927
  if (backend.identities.has(token)) return;
878
928
  const known = [...backend.identities.keys()];
@@ -897,7 +947,8 @@ function createTestClient(options = {}) {
897
947
  stubs: /* @__PURE__ */ new Map(),
898
948
  calls: [],
899
949
  concurrentEdits: /* @__PURE__ */ new Map(),
900
- identities: new Map(Object.entries(options.identities ?? {}))
950
+ identities: new Map(Object.entries(options.identities ?? {})),
951
+ writeChain: Promise.resolve()
901
952
  }, registry = backend.registry;
902
953
  rejectAmbiguousIdentity("createTestClient()", options), options.token !== void 0 && requireIdentity(backend, options.token, "createTestClient()");
903
954
  const ensureStore = (projectId, dataset) => {
@@ -1126,7 +1177,7 @@ function makeHandle(backend, projectId, dataset, accessControl, perspective, mut
1126
1177
  txnOptions?.returnDocuments ?? !1,
1127
1178
  txnOptions?.returnFirst ?? !1
1128
1179
  );
1129
- }, accessVisibleDocs = async () => {
1180
+ }, runAction = (actions) => serializeWrite(backend, async () => applyActions(store, actions, { gate, guard: guardGate })), accessVisibleDocs = async () => {
1130
1181
  const all = store.all();
1131
1182
  if (activeAccess() === void 0) return all;
1132
1183
  const out = [];
@@ -1170,7 +1221,7 @@ function makeHandle(backend, projectId, dataset, accessControl, perspective, mut
1170
1221
  const view = await visibleDocs(effective);
1171
1222
  return ids.map((id) => resolveDocument(view, id, effective) ?? null);
1172
1223
  },
1173
- create: async (doc, opts) => {
1224
+ create: (doc, opts) => serializeWrite(backend, async () => {
1174
1225
  if (checkRevision(doc._id, opts?.ifRevisionId), store.get(doc._id) !== void 0)
1175
1226
  throw fakeError(
1176
1227
  `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.`
@@ -1184,8 +1235,8 @@ function makeHandle(backend, projectId, dataset, accessControl, perspective, mut
1184
1235
  transactionId,
1185
1236
  opts
1186
1237
  );
1187
- },
1188
- createOrReplace: async (doc, opts) => {
1238
+ }),
1239
+ createOrReplace: (doc, opts) => serializeWrite(backend, async () => {
1189
1240
  checkRevision(doc._id, opts?.ifRevisionId);
1190
1241
  const promoted = promote2(doc), existing = store.get(doc._id);
1191
1242
  await gate(existing ?? promoted, existing !== void 0 ? "update" : "create"), await guardGate(existing !== void 0 ? "update" : "create", existing ?? null, promoted);
@@ -1198,8 +1249,8 @@ function makeHandle(backend, projectId, dataset, accessControl, perspective, mut
1198
1249
  transactionId,
1199
1250
  opts
1200
1251
  );
1201
- },
1202
- createIfNotExists: async (doc, opts) => {
1252
+ }),
1253
+ createIfNotExists: (doc, opts) => serializeWrite(backend, async () => {
1203
1254
  checkRevision(doc._id, opts?.ifRevisionId);
1204
1255
  const existing = store.get(doc._id), transactionId = opts?.transactionId ?? cryptoRandomKey();
1205
1256
  if (existing !== void 0)
@@ -1216,8 +1267,8 @@ function makeHandle(backend, projectId, dataset, accessControl, perspective, mut
1216
1267
  transactionId,
1217
1268
  opts
1218
1269
  );
1219
- },
1220
- delete: async (id, opts) => {
1270
+ }),
1271
+ delete: (id, opts) => serializeWrite(backend, async () => {
1221
1272
  checkRevision(id, opts?.ifRevisionId);
1222
1273
  const existing = store.get(id);
1223
1274
  existing !== void 0 && await gate(existing, "update"), existing !== void 0 && await guardGate("delete", existing, null);
@@ -1230,23 +1281,35 @@ function makeHandle(backend, projectId, dataset, accessControl, perspective, mut
1230
1281
  opts.returnDocuments ?? !0,
1231
1282
  opts.returnFirst ?? !0
1232
1283
  );
1233
- },
1284
+ }),
1234
1285
  patch: (documentId) => createPatch(documentId, {
1235
- apply: async (docId, ops, ifRev, options) => {
1286
+ apply: (docId, ops, ifRev, options) => serializeWrite(backend, async () => {
1236
1287
  const doc = await applyPatch(docId, ops, ifRev);
1237
1288
  return record(
1238
1289
  cryptoRandomKey(),
1239
- [{ kind: "patch", documentId: docId, ops, ...ifRev !== void 0 ? { ifRev } : {} }],
1290
+ [
1291
+ {
1292
+ kind: "patch",
1293
+ documentId: docId,
1294
+ ops,
1295
+ ...ifRev !== void 0 ? { ifRev } : {}
1296
+ }
1297
+ ],
1240
1298
  options?.tag
1241
1299
  ), doc;
1242
- }
1300
+ })
1301
+ }),
1302
+ transaction: () => createTransaction({
1303
+ apply: (mutations, txnOptions) => serializeWrite(backend, () => applyTransaction(mutations, txnOptions))
1243
1304
  }),
1244
- transaction: () => createTransaction({ apply: applyTransaction }),
1245
- mutate: async (operations, options) => applyTransaction(toTxnMutations(operations), options),
1246
- action: async (action) => applyActions(store, action),
1305
+ mutate: (operations, options) => serializeWrite(backend, () => applyTransaction(toTxnMutations(operations), options)),
1306
+ // Synchronous writes still take the lock: an action landing while a
1307
+ // guarded patch is parked in its gate would otherwise be clobbered
1308
+ // when that patch resumes over its pre-action snapshot.
1309
+ action: (action) => runAction(action),
1247
1310
  releases: {
1248
1311
  create: async ({ releaseId, metadata }) => {
1249
- const result = applyActions(store, {
1312
+ const result = await runAction({
1250
1313
  actionType: "sanity.action.release.create",
1251
1314
  releaseId,
1252
1315
  ...metadata !== void 0 ? { metadata } : {}
@@ -1257,30 +1320,30 @@ function makeHandle(backend, projectId, dataset, accessControl, perspective, mut
1257
1320
  metadata: doc?.metadata ?? { releaseType: "undecided" }
1258
1321
  };
1259
1322
  },
1260
- edit: async ({ releaseId, patch }) => applyActions(store, { actionType: "sanity.action.release.edit", releaseId, patch }),
1323
+ edit: ({ releaseId, patch }) => runAction({ actionType: "sanity.action.release.edit", releaseId, patch }),
1261
1324
  get: async ({ releaseId }) => store.get(`_.releases.${releaseId}`),
1262
1325
  fetchDocuments: async ({ releaseId }) => {
1263
1326
  const prefix = `versions.${releaseId}.`;
1264
1327
  return store.all().filter((d) => d._id.startsWith(prefix));
1265
1328
  },
1266
- schedule: async ({ releaseId, publishAt }) => applyActions(store, {
1329
+ schedule: ({ releaseId, publishAt }) => runAction({
1267
1330
  actionType: "sanity.action.release.schedule",
1268
1331
  releaseId,
1269
1332
  publishAt
1270
1333
  }),
1271
- unschedule: async ({ releaseId }) => applyActions(store, { actionType: "sanity.action.release.unschedule", releaseId }),
1272
- publish: async ({ releaseId }) => applyActions(store, { actionType: "sanity.action.release.publish", releaseId }),
1273
- archive: async ({ releaseId }) => applyActions(store, { actionType: "sanity.action.release.archive", releaseId }),
1274
- unarchive: async ({ releaseId }) => applyActions(store, { actionType: "sanity.action.release.unarchive", releaseId }),
1275
- delete: async ({ releaseId }) => applyActions(store, { actionType: "sanity.action.release.delete", releaseId })
1334
+ unschedule: ({ releaseId }) => runAction({ actionType: "sanity.action.release.unschedule", releaseId }),
1335
+ publish: ({ releaseId }) => runAction({ actionType: "sanity.action.release.publish", releaseId }),
1336
+ archive: ({ releaseId }) => runAction({ actionType: "sanity.action.release.archive", releaseId }),
1337
+ unarchive: ({ releaseId }) => runAction({ actionType: "sanity.action.release.unarchive", releaseId }),
1338
+ delete: ({ releaseId }) => runAction({ actionType: "sanity.action.release.delete", releaseId })
1276
1339
  },
1277
- createVersion: async ({ releaseId, publishedId, document }) => applyActions(store, {
1340
+ createVersion: ({ releaseId, publishedId, document }) => runAction({
1278
1341
  actionType: "sanity.action.document.version.create",
1279
1342
  releaseId,
1280
1343
  publishedId,
1281
1344
  document
1282
1345
  }),
1283
- unpublishVersion: async ({ releaseId, publishedId }) => applyActions(store, {
1346
+ unpublishVersion: ({ releaseId, publishedId }) => runAction({
1284
1347
  actionType: "sanity.action.document.version.unpublish",
1285
1348
  versionId: `versions.${releaseId}.${publishedId}`,
1286
1349
  publishedId