@sanity/sdk 2.8.0 → 2.10.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.
Files changed (111) hide show
  1. package/dist/_chunks-dts/utils.d.ts +2450 -0
  2. package/dist/_chunks-es/_internal.js +129 -0
  3. package/dist/_chunks-es/_internal.js.map +1 -0
  4. package/dist/_chunks-es/createGroqSearchFilter.js +1537 -0
  5. package/dist/_chunks-es/createGroqSearchFilter.js.map +1 -0
  6. package/dist/_chunks-es/telemetryManager.js +87 -0
  7. package/dist/_chunks-es/telemetryManager.js.map +1 -0
  8. package/dist/_chunks-es/version.js +7 -0
  9. package/dist/_chunks-es/version.js.map +1 -0
  10. package/dist/_exports/_internal.d.ts +64 -0
  11. package/dist/_exports/_internal.js +20 -0
  12. package/dist/_exports/_internal.js.map +1 -0
  13. package/dist/index.d.ts +2 -2343
  14. package/dist/index.js +465 -1813
  15. package/dist/index.js.map +1 -1
  16. package/package.json +17 -12
  17. package/src/_exports/_internal.ts +14 -0
  18. package/src/_exports/index.ts +18 -1
  19. package/src/auth/authStore.test.ts +150 -1
  20. package/src/auth/authStore.ts +11 -11
  21. package/src/auth/dashboardAuth.ts +2 -2
  22. package/src/auth/handleAuthCallback.ts +9 -3
  23. package/src/auth/logout.test.ts +1 -1
  24. package/src/auth/logout.ts +1 -1
  25. package/src/auth/refreshStampedToken.test.ts +118 -1
  26. package/src/auth/refreshStampedToken.ts +3 -2
  27. package/src/auth/standaloneAuth.ts +9 -3
  28. package/src/auth/studioAuth.ts +34 -7
  29. package/src/auth/studioModeAuth.ts +2 -1
  30. package/src/auth/subscribeToStateAndFetchCurrentUser.test.ts +10 -2
  31. package/src/auth/subscribeToStateAndFetchCurrentUser.ts +5 -1
  32. package/src/auth/subscribeToStorageEventsAndSetToken.ts +2 -2
  33. package/src/auth/utils.ts +33 -0
  34. package/src/client/clientStore.test.ts +44 -30
  35. package/src/client/clientStore.ts +49 -48
  36. package/src/comlink/controller/actions/getOrCreateChannel.ts +2 -2
  37. package/src/comlink/node/actions/getOrCreateNode.ts +2 -2
  38. package/src/comlink/node/getNodeState.ts +2 -1
  39. package/src/config/sanityConfig.ts +78 -12
  40. package/src/document/actions.ts +18 -11
  41. package/src/document/applyDocumentActions.test.ts +7 -6
  42. package/src/document/applyDocumentActions.ts +10 -4
  43. package/src/document/documentStore.test.ts +542 -188
  44. package/src/document/documentStore.ts +142 -76
  45. package/src/document/events.ts +7 -2
  46. package/src/document/permissions.test.ts +18 -16
  47. package/src/document/permissions.ts +35 -11
  48. package/src/document/processActions.test.ts +359 -32
  49. package/src/document/processActions.ts +106 -78
  50. package/src/document/reducers.test.ts +117 -29
  51. package/src/document/reducers.ts +47 -40
  52. package/src/document/sharedListener.ts +16 -6
  53. package/src/document/util.ts +14 -0
  54. package/src/favorites/favorites.test.ts +9 -2
  55. package/src/presence/bifurTransport.test.ts +46 -6
  56. package/src/presence/bifurTransport.ts +19 -2
  57. package/src/presence/presenceStore.test.ts +96 -0
  58. package/src/presence/presenceStore.ts +96 -24
  59. package/src/preview/getPreviewState.test.ts +115 -98
  60. package/src/preview/getPreviewState.ts +38 -60
  61. package/src/preview/previewProjectionUtils.test.ts +179 -0
  62. package/src/preview/previewProjectionUtils.ts +93 -0
  63. package/src/preview/resolvePreview.test.ts +42 -25
  64. package/src/preview/resolvePreview.ts +33 -10
  65. package/src/preview/{previewStore.ts → types.ts} +8 -17
  66. package/src/projection/getProjectionState.test.ts +16 -16
  67. package/src/projection/getProjectionState.ts +6 -5
  68. package/src/projection/projectionQuery.ts +2 -3
  69. package/src/projection/projectionStore.test.ts +2 -2
  70. package/src/projection/resolveProjection.ts +2 -2
  71. package/src/projection/subscribeToStateAndFetchBatches.test.ts +1 -1
  72. package/src/projection/subscribeToStateAndFetchBatches.ts +12 -11
  73. package/src/projection/types.ts +1 -1
  74. package/src/query/queryStore.test.ts +12 -12
  75. package/src/query/queryStore.ts +12 -11
  76. package/src/query/reducers.ts +3 -3
  77. package/src/releases/getPerspectiveState.ts +7 -6
  78. package/src/releases/releasesStore.test.ts +20 -5
  79. package/src/releases/releasesStore.ts +20 -8
  80. package/src/store/createActionBinder.test.ts +31 -31
  81. package/src/store/createActionBinder.ts +43 -38
  82. package/src/store/createSanityInstance.ts +2 -3
  83. package/src/store/createStateSourceAction.test.ts +62 -0
  84. package/src/store/createStateSourceAction.ts +34 -39
  85. package/src/telemetry/__telemetry__/sdk.telemetry.ts +42 -0
  86. package/src/telemetry/devMode.test.ts +52 -0
  87. package/src/telemetry/devMode.ts +40 -0
  88. package/src/telemetry/initTelemetry.test.ts +225 -0
  89. package/src/telemetry/initTelemetry.ts +205 -0
  90. package/src/telemetry/telemetryManager.test.ts +263 -0
  91. package/src/telemetry/telemetryManager.ts +187 -0
  92. package/src/users/reducers.ts +3 -4
  93. package/src/users/usersStore.test.ts +1 -0
  94. package/src/users/usersStore.ts +5 -1
  95. package/src/utils/createFetcherStore.test.ts +6 -4
  96. package/src/utils/createFetcherStore.ts +8 -5
  97. package/src/utils/getStagingApiHost.test.ts +21 -0
  98. package/src/utils/getStagingApiHost.ts +14 -0
  99. package/src/utils/ids.test.ts +1 -29
  100. package/src/utils/ids.ts +0 -10
  101. package/src/utils/isImportError.test.ts +72 -0
  102. package/src/utils/isImportError.ts +34 -0
  103. package/src/utils/object.test.ts +95 -0
  104. package/src/utils/object.ts +142 -0
  105. package/src/utils/setCleanupTimeout.ts +24 -0
  106. package/src/preview/previewQuery.test.ts +0 -236
  107. package/src/preview/previewQuery.ts +0 -153
  108. package/src/preview/previewStore.test.ts +0 -36
  109. package/src/preview/subscribeToStateAndFetchBatches.test.ts +0 -221
  110. package/src/preview/subscribeToStateAndFetchBatches.ts +0 -112
  111. package/src/preview/util.ts +0 -13
@@ -1,4 +1,5 @@
1
1
  import {diffValue} from '@sanity/diff-patch'
2
+ import {DocumentId, getDraftId, getPublishedId, getVersionId} from '@sanity/id-utils'
2
3
  import {
3
4
  type Mutation,
4
5
  type PatchOperations,
@@ -6,9 +7,9 @@ import {
6
7
  type SanityDocument,
7
8
  } from '@sanity/types'
8
9
  import {evaluateSync, type ExprNode} from 'groq-js'
9
- import {isEqual} from 'lodash-es'
10
10
 
11
- import {getDraftId, getPublishedId} from '../utils/ids'
11
+ import {isReleasePerspective} from '../releases/utils/isReleasePerspective'
12
+ import {isDeepEqual} from '../utils/object'
12
13
  import {type DocumentAction} from './actions'
13
14
  import {type Grant} from './permissions'
14
15
  import {type DocumentSet, getId, processMutations} from './processMutations'
@@ -151,8 +152,12 @@ export function processActions({
151
152
  })
152
153
  }
153
154
 
154
- const newDocBase = {_type: action.documentType, _id: documentId}
155
- const newDocWorking = {_type: action.documentType, _id: documentId}
155
+ const newDocBase = {_type: action.documentType, _id: documentId, ...action.initialValue}
156
+ const newDocWorking = {
157
+ _type: action.documentType,
158
+ _id: documentId,
159
+ ...action.initialValue,
160
+ }
156
161
  const mutations: Mutation[] = [{create: newDocWorking}]
157
162
 
158
163
  base = processMutations({
@@ -176,38 +181,41 @@ export function processActions({
176
181
  })
177
182
  }
178
183
 
184
+ // liveEdit documents use the mutation endpoint directly -- we don't send actions
179
185
  outgoingMutations.push(...mutations)
180
- outgoingActions.push({
181
- actionType: 'sanity.action.document.create',
182
- publishedId: documentId,
183
- attributes: newDocWorking,
184
- })
185
186
  continue
186
187
  }
187
188
 
188
- // Standard draft/published logic
189
- const draftId = getDraftId(documentId)
190
- const publishedId = getPublishedId(documentId)
189
+ // Standard draft/published/version logic
190
+ const versionId = isReleasePerspective(action.perspective)
191
+ ? getVersionId(DocumentId(documentId), action.perspective.releaseName)
192
+ : undefined
193
+ const draftId = getDraftId(DocumentId(documentId))
194
+ const publishedId = getPublishedId(DocumentId(documentId))
195
+
196
+ const alreadyHasVersion = versionId ? working[versionId] : working[draftId]
191
197
 
192
- if (working[draftId]) {
198
+ if (alreadyHasVersion) {
199
+ const errorDocType = versionId ? 'release version' : 'draft'
193
200
  throw new ActionError({
194
201
  documentId,
195
202
  transactionId,
196
- message: `A draft version of this document already exists. Please use or discard the existing draft before creating a new one.`,
203
+ message: `A ${errorDocType} of this document already exists. Please use or discard the existing ${errorDocType} before creating a new one.`,
197
204
  })
198
205
  }
199
206
 
200
- // Spread the (possibly undefined) published version directly.
207
+ // Spread the (possibly undefined) draft or published version directly.
208
+ // (studio uses the draft version as a base if you are in a release perspective)
201
209
  const newDocBase = {
202
- ...base[publishedId],
210
+ ...(base[draftId] ?? base[publishedId]),
203
211
  _type: action.documentType,
204
- _id: draftId,
212
+ _id: versionId ?? draftId,
205
213
  ...action.initialValue,
206
214
  }
207
215
  const newDocWorking = {
208
- ...working[publishedId],
216
+ ...(working[draftId] ?? working[publishedId]),
209
217
  _type: action.documentType,
210
- _id: draftId,
218
+ _id: versionId ?? draftId,
211
219
  ...action.initialValue,
212
220
  }
213
221
  const mutations: Mutation[] = [{create: newDocWorking}]
@@ -225,7 +233,13 @@ export function processActions({
225
233
  timestamp,
226
234
  })
227
235
 
228
- if (!checkGrant(grants.create, working[draftId] as SanityDocument)) {
236
+ if (versionId && !checkGrant(grants.create, working[versionId] as SanityDocument)) {
237
+ throw new PermissionActionError({
238
+ documentId,
239
+ transactionId,
240
+ message: `You do not have permission to create a release version for document "${documentId}".`,
241
+ })
242
+ } else if (!versionId && !checkGrant(grants.create, working[draftId] as SanityDocument)) {
229
243
  throw new PermissionActionError({
230
244
  documentId,
231
245
  transactionId,
@@ -245,8 +259,15 @@ export function processActions({
245
259
  case 'document.delete': {
246
260
  const documentId = action.documentId
247
261
 
262
+ if (isReleasePerspective(action.perspective)) {
263
+ throw new ActionError({
264
+ documentId,
265
+ transactionId,
266
+ message: `Cannot delete a version document. You may want to use the "unpublish" or "discard" actions instead.`,
267
+ })
268
+ }
269
+
248
270
  if (action.liveEdit) {
249
- // For liveEdit documents, delete directly
250
271
  if (!working[documentId]) {
251
272
  throw new ActionError({
252
273
  documentId,
@@ -268,17 +289,16 @@ export function processActions({
268
289
  base = processMutations({documents: base, transactionId, mutations, timestamp})
269
290
  working = processMutations({documents: working, transactionId, mutations, timestamp})
270
291
 
292
+ // although liveEdit documents can use the actions API for deletion,
293
+ // having this be an action while other operations are mutations creates an inconsistency
294
+ // (and a possible race condition in document store where mutations might get skipped)
271
295
  outgoingMutations.push(...mutations)
272
- outgoingActions.push({
273
- actionType: 'sanity.action.document.delete',
274
- publishedId: documentId,
275
- })
276
296
  continue
277
297
  }
278
298
 
279
299
  // Standard draft/published logic
280
- const draftId = getDraftId(documentId)
281
- const publishedId = getPublishedId(documentId)
300
+ const draftId = getDraftId(DocumentId(documentId))
301
+ const publishedId = getPublishedId(DocumentId(documentId))
282
302
 
283
303
  if (!working[publishedId]) {
284
304
  throw new ActionError({
@@ -328,19 +348,21 @@ export function processActions({
328
348
  })
329
349
  }
330
350
 
331
- // Standard draft/published logic
332
- const draftId = getDraftId(documentId)
333
- const mutations: Mutation[] = [{delete: {id: draftId}}]
351
+ // draft/published or version logic
352
+ const versionId = isReleasePerspective(action.perspective)
353
+ ? getVersionId(DocumentId(documentId), action.perspective.releaseName)
354
+ : getDraftId(DocumentId(documentId))
355
+ const mutations: Mutation[] = [{delete: {id: versionId}}]
334
356
 
335
- if (!working[draftId]) {
357
+ if (!working[versionId]) {
336
358
  throw new ActionError({
337
359
  documentId,
338
360
  transactionId,
339
- message: `There is no draft available to discard for document "${documentId}".`,
361
+ message: `There is no draft or version available to discard for document "${documentId}".`,
340
362
  })
341
363
  }
342
364
 
343
- if (!checkGrant(grants.update, working[draftId])) {
365
+ if (!checkGrant(grants.update, working[versionId])) {
344
366
  throw new PermissionActionError({
345
367
  documentId,
346
368
  transactionId,
@@ -354,7 +376,7 @@ export function processActions({
354
376
  outgoingMutations.push(...mutations)
355
377
  outgoingActions.push({
356
378
  actionType: 'sanity.action.document.version.discard',
357
- versionId: draftId,
379
+ versionId,
358
380
  })
359
381
  continue
360
382
  }
@@ -363,7 +385,7 @@ export function processActions({
363
385
  const documentId = getId(action.documentId)
364
386
 
365
387
  if (action.liveEdit) {
366
- // For liveEdit documents, edit directly without draft logic
388
+ // Single-document mode (liveEdit or release perspective): edit directly without draft logic
367
389
  const userPatches = action.patches?.map((patch) => ({patch: {id: documentId, ...patch}}))
368
390
 
369
391
  // skip this action if there are no associated patches
@@ -408,31 +430,33 @@ export function processActions({
408
430
  timestamp,
409
431
  })
410
432
 
433
+ // liveEdit documents use the mutation endpoint directly -- we don't send actions
411
434
  outgoingMutations.push(...workingMutations)
412
- outgoingActions.push(
413
- ...patches.map(
414
- (patch): HttpAction => ({
415
- actionType: 'sanity.action.document.edit',
416
- // Server requires draftId to have drafts. prefix for validation, even for liveEdit
417
- draftId: getDraftId(documentId),
418
- publishedId: documentId,
419
- patch: patch as PatchOperations,
420
- }),
421
- ),
422
- )
423
-
424
435
  continue
425
436
  }
426
437
 
427
- // Standard draft/published logic
428
- const draftId = getDraftId(documentId)
429
- const publishedId = getPublishedId(documentId)
430
- const userPatches = action.patches?.map((patch) => ({patch: {id: draftId, ...patch}}))
438
+ const versionId = isReleasePerspective(action.perspective)
439
+ ? getVersionId(DocumentId(documentId), action.perspective.releaseName)
440
+ : undefined
441
+ const draftId = getDraftId(DocumentId(documentId))
442
+ const publishedId = getPublishedId(DocumentId(documentId))
443
+ const patchDocumentId = isReleasePerspective(action.perspective) ? versionId! : draftId
444
+ const userPatches = action.patches?.map((patch) => ({
445
+ patch: {id: patchDocumentId, ...patch},
446
+ }))
431
447
 
432
448
  // skip this action if there are no associated patches
433
449
  if (!userPatches?.length) continue
434
450
 
435
- if (
451
+ if (isReleasePerspective(action.perspective)) {
452
+ if (!working[versionId!] && !base[versionId!]) {
453
+ throw new ActionError({
454
+ documentId,
455
+ transactionId,
456
+ message: `This document does not exist in the release. Please create it or add it to the release first.`,
457
+ })
458
+ }
459
+ } else if (
436
460
  (!working[draftId] && !working[publishedId]) ||
437
461
  (!base[draftId] && !base[publishedId])
438
462
  ) {
@@ -444,12 +468,14 @@ export function processActions({
444
468
  }
445
469
 
446
470
  const baseMutations: Mutation[] = []
447
- if (!base[draftId] && base[publishedId]) {
471
+ // don't create a draft from the published version in a release perspective
472
+ if (!isReleasePerspective(action.perspective) && !base[draftId] && base[publishedId]) {
473
+ // otherwise make a draft from the published version
448
474
  baseMutations.push({create: {...base[publishedId], _id: draftId}})
449
475
  }
450
476
 
451
- // the first if statement should make this never be null or undefined
452
- const baseBefore = (base[draftId] ?? base[publishedId]) as SanityDocument
477
+ // the above statement and guards should make this never be null or undefined
478
+ const baseBefore = base[patchDocumentId] ?? base[publishedId]
453
479
  if (userPatches) {
454
480
  baseMutations.push(...userPatches)
455
481
  }
@@ -462,11 +488,15 @@ export function processActions({
462
488
  })
463
489
  // this one will always be defined because a patch mutation will never
464
490
  // delete an input document
465
- const baseAfter = base[draftId] as SanityDocument
491
+ const baseAfter = base[patchDocumentId] as SanityDocument
466
492
  const patches = diffValue(baseBefore, baseAfter)
467
493
 
468
494
  const workingMutations: Mutation[] = []
469
- if (!working[draftId] && working[publishedId]) {
495
+ if (
496
+ !isReleasePerspective(action.perspective) &&
497
+ !working[draftId] &&
498
+ working[publishedId]
499
+ ) {
470
500
  const newDraftFromPublished = {...working[publishedId], _id: draftId}
471
501
 
472
502
  if (!checkGrant(grants.create, newDraftFromPublished)) {
@@ -481,15 +511,15 @@ export function processActions({
481
511
  }
482
512
 
483
513
  // the first if statement should make this never be null or undefined
484
- const workingBefore = (working[draftId] ?? working[publishedId]) as SanityDocument
485
- if (!checkGrant(grants.update, workingBefore)) {
514
+ const workingBefore = working[patchDocumentId] ?? working[publishedId]
515
+ if (!checkGrant(grants.update, workingBefore!)) {
486
516
  throw new PermissionActionError({
487
517
  documentId,
488
518
  transactionId,
489
519
  message: `You do not have permission to edit document "${documentId}".`,
490
520
  })
491
521
  }
492
- workingMutations.push(...patches.map((patch) => ({patch: {id: draftId, ...patch}})))
522
+ workingMutations.push(...patches.map((patch) => ({patch: {id: patchDocumentId, ...patch}})))
493
523
 
494
524
  working = processMutations({
495
525
  documents: working,
@@ -500,14 +530,12 @@ export function processActions({
500
530
 
501
531
  outgoingMutations.push(...workingMutations)
502
532
  outgoingActions.push(
503
- ...patches.map(
504
- (patch): HttpAction => ({
505
- actionType: 'sanity.action.document.edit',
506
- draftId,
507
- publishedId,
508
- patch: patch as PatchOperations,
509
- }),
510
- ),
533
+ ...patches.map((patch) => ({
534
+ actionType: 'sanity.action.document.edit' as const,
535
+ draftId: patchDocumentId,
536
+ publishedId,
537
+ patch: patch as PatchOperations,
538
+ })),
511
539
  )
512
540
 
513
541
  continue
@@ -516,17 +544,17 @@ export function processActions({
516
544
  case 'document.publish': {
517
545
  const documentId = getId(action.documentId)
518
546
 
519
- if (action.liveEdit) {
547
+ if (action.liveEdit || isReleasePerspective(action.perspective)) {
520
548
  throw new ActionError({
521
549
  documentId,
522
550
  transactionId,
523
- message: `Cannot publish liveEdit document "${documentId}". LiveEdit documents do not support drafts or publishing.`,
551
+ message: `Cannot publish this document. Publishing is not supported for liveEdit or version (release) documents.`,
524
552
  })
525
553
  }
526
554
 
527
555
  // Standard draft/published logic
528
- const draftId = getDraftId(documentId)
529
- const publishedId = getPublishedId(documentId)
556
+ const draftId = getDraftId(DocumentId(documentId))
557
+ const publishedId = getPublishedId(DocumentId(documentId))
530
558
 
531
559
  const workingDraft = working[draftId]
532
560
  const baseDraft = base[draftId]
@@ -540,7 +568,7 @@ export function processActions({
540
568
 
541
569
  // Before proceeding, verify that the working draft is identical to the base draft.
542
570
  // TODO: is it enough just to check for the _rev or nah?
543
- if (!isEqual(workingDraft, baseDraft)) {
571
+ if (!isDeepEqual(workingDraft, baseDraft)) {
544
572
  throw new ActionError({
545
573
  documentId,
546
574
  transactionId,
@@ -592,17 +620,17 @@ export function processActions({
592
620
  case 'document.unpublish': {
593
621
  const documentId = getId(action.documentId)
594
622
 
595
- if (action.liveEdit) {
623
+ if (action.liveEdit || isReleasePerspective(action.perspective)) {
596
624
  throw new ActionError({
597
625
  documentId,
598
626
  transactionId,
599
- message: `Cannot unpublish liveEdit document "${documentId}". LiveEdit documents do not support drafts or publishing.`,
627
+ message: `Cannot unpublish this document. Unpublishing is not supported for liveEdit or version (release) documents.`,
600
628
  })
601
629
  }
602
630
 
603
- // Standard draft/published logic
604
- const draftId = getDraftId(documentId)
605
- const publishedId = getPublishedId(documentId)
631
+ // Standard draft/published or version logic
632
+ const draftId = getDraftId(DocumentId(documentId))
633
+ const publishedId = getPublishedId(DocumentId(documentId))
606
634
 
607
635
  if (!working[publishedId] && !base[publishedId]) {
608
636
  throw new ActionError({
@@ -1,9 +1,9 @@
1
+ import {DocumentId, getDraftId, getPublishedId} from '@sanity/id-utils'
1
2
  import {type SanityDocument} from '@sanity/types'
2
3
  import {parse} from 'groq-js'
3
4
  import {Subject} from 'rxjs'
4
5
  import {describe, expect, it} from 'vitest'
5
6
 
6
- import {getDraftId, getPublishedId} from '../utils/ids'
7
7
  import {type DocumentEvent} from './events'
8
8
  import {type RemoteDocument} from './listen'
9
9
  import {type DocumentSet} from './processMutations'
@@ -66,8 +66,8 @@ describe('queueTransaction', () => {
66
66
  expect(newState.queued[0]).toEqual(transaction)
67
67
 
68
68
  // Check that both the published and draft documentStates got a subscription id added.
69
- const draftId = getDraftId('doc1')
70
- const pubId = getPublishedId('doc1')
69
+ const draftId = getDraftId(DocumentId('doc1'))
70
+ const pubId = getPublishedId(DocumentId('doc1'))
71
71
  expect(newState.documentStates[draftId]).toBeDefined()
72
72
  expect(newState.documentStates[pubId]).toBeDefined()
73
73
  expect(newState.documentStates[draftId]?.subscriptions).toContain('txn1')
@@ -77,8 +77,8 @@ describe('queueTransaction', () => {
77
77
 
78
78
  describe('removeQueuedTransaction', () => {
79
79
  it('removes the transaction from queued and removes subscription ids from documents', () => {
80
- const draftId = getDraftId('doc1')
81
- const pubId = getPublishedId('doc1')
80
+ const draftId = getDraftId(DocumentId('doc1'))
81
+ const pubId = getPublishedId(DocumentId('doc1'))
82
82
 
83
83
  const initialState: SyncTransactionState = {
84
84
  queued: [
@@ -98,8 +98,8 @@ describe('removeQueuedTransaction', () => {
98
98
  outgoing: undefined,
99
99
  grants,
100
100
  documentStates: {
101
- [draftId]: {id: draftId, subscriptions: ['txn1'], local: {}},
102
- [pubId]: {id: pubId, subscriptions: ['txn1'], local: {}},
101
+ [draftId]: {id: draftId, subscriptions: ['txn1']},
102
+ [pubId]: {id: pubId, subscriptions: ['txn1']},
103
103
  } as SyncTransactionState['documentStates'],
104
104
  }
105
105
 
@@ -138,7 +138,7 @@ describe('applyFirstQueuedTransaction', () => {
138
138
 
139
139
  it('returns unchanged state if any required document is not yet loaded', () => {
140
140
  // If a document's local value is undefined, the reducer should do nothing.
141
- const draftId = getDraftId('doc1')
141
+ const draftId = getDraftId(DocumentId('doc1'))
142
142
  const state: SyncTransactionState = {
143
143
  queued: [
144
144
  {
@@ -176,8 +176,8 @@ describe('applyFirstQueuedTransaction', () => {
176
176
 
177
177
  it('applies the first queued transaction (using a discard action)', () => {
178
178
  // For a discard action, processActions deletes the draft document.
179
- const draftId = getDraftId('doc1')
180
- const pubId = getPublishedId('doc1')
179
+ const draftId = getDraftId(DocumentId('doc1'))
180
+ const pubId = getPublishedId(DocumentId('doc1'))
181
181
  const initialDraft = {...exampleDoc, _id: draftId, foo: 'bar', _rev: 'rev1'}
182
182
  const initialPub = {...exampleDoc, _id: pubId, foo: 'bar', _rev: 'rev1'}
183
183
 
@@ -235,18 +235,26 @@ describe('batchAppliedTransactions', () => {
235
235
  disableBatching: false,
236
236
  outgoingActions: [],
237
237
  outgoingMutations: [],
238
- base: {[getDraftId('doc1')]: {_id: getDraftId('doc1'), _rev: 'rev1'}} as DocumentSet,
238
+ base: {
239
+ [getDraftId(DocumentId('doc1'))]: {_id: getDraftId(DocumentId('doc1')), _rev: 'rev1'},
240
+ } as unknown as DocumentSet,
239
241
  working: {
240
- [getDraftId('doc1')]: {
242
+ [getDraftId(DocumentId('doc1'))]: {
241
243
  ...exampleDoc,
242
- _id: getDraftId('doc1'),
244
+ _id: getDraftId(DocumentId('doc1')),
243
245
  _rev: 'rev2',
244
246
  foo: 'a',
245
247
  bar: 'b',
246
248
  },
247
249
  },
248
- previous: {[getDraftId('doc1')]: {...exampleDoc, _id: getDraftId('doc1'), _rev: 'rev1'}},
249
- previousRevs: {[getDraftId('doc1')]: 'rev1'},
250
+ previous: {
251
+ [getDraftId(DocumentId('doc1'))]: {
252
+ ...exampleDoc,
253
+ _id: getDraftId(DocumentId('doc1')),
254
+ _rev: 'rev1',
255
+ },
256
+ },
257
+ previousRevs: {[getDraftId(DocumentId('doc1'))]: 'rev1'},
250
258
  timestamp: '2025-02-06T00:00:00.000Z',
251
259
  }
252
260
  const result = batchAppliedTransactions([appliedTx])
@@ -256,13 +264,14 @@ describe('batchAppliedTransactions', () => {
256
264
  })
257
265
 
258
266
  it('batches two edit transactions when possible', () => {
259
- const draftId = getDraftId('doc1')
267
+ const documentId = DocumentId('doc1')
268
+ const draftId = getDraftId(documentId)
260
269
  const appliedTx1: AppliedTransaction = {
261
270
  transactionId: 'txn5',
262
271
  actions: [
263
272
  {
264
273
  type: 'document.edit',
265
- documentId: 'doc1',
274
+ documentId,
266
275
  documentType: 'book',
267
276
  patches: [{set: {foo: 'a'}}],
268
277
  },
@@ -272,7 +281,7 @@ describe('batchAppliedTransactions', () => {
272
281
  {
273
282
  actionType: 'sanity.action.document.edit',
274
283
  draftId,
275
- publishedId: getPublishedId('doc1'),
284
+ publishedId: getPublishedId(documentId),
276
285
  patch: {set: {foo: 'a'}},
277
286
  },
278
287
  ],
@@ -288,7 +297,7 @@ describe('batchAppliedTransactions', () => {
288
297
  actions: [
289
298
  {
290
299
  type: 'document.edit',
291
- documentId: 'doc1',
300
+ documentId,
292
301
  documentType: 'book',
293
302
  patches: [{set: {bar: 'b'}}],
294
303
  },
@@ -298,7 +307,7 @@ describe('batchAppliedTransactions', () => {
298
307
  {
299
308
  actionType: 'sanity.action.document.edit',
300
309
  draftId,
301
- publishedId: getPublishedId('doc1'),
310
+ publishedId: getPublishedId(documentId),
302
311
  patch: {set: {bar: 'b'}},
303
312
  },
304
313
  ],
@@ -322,7 +331,7 @@ describe('batchAppliedTransactions', () => {
322
331
  })
323
332
 
324
333
  it('returns a transaction with disableBatching true if a single edit action already has disableBatching set', () => {
325
- const draftId = getDraftId('docA')
334
+ const draftId = getDraftId(DocumentId('docA'))
326
335
  const appliedTx: AppliedTransaction = {
327
336
  transactionId: 'txn-disable',
328
337
  actions: [
@@ -350,6 +359,85 @@ describe('batchAppliedTransactions', () => {
350
359
  // The actions array should be the same as the input's.
351
360
  expect(result?.actions).toEqual(appliedTx.actions)
352
361
  })
362
+
363
+ it('does not batch a liveEdit edit with a non-liveEdit edit', () => {
364
+ const nonLiveEditTx: AppliedTransaction = {
365
+ transactionId: 'txn-nonlive',
366
+ actions: [
367
+ {
368
+ type: 'document.edit',
369
+ documentId: 'doc1',
370
+ documentType: 'article',
371
+ patches: [{set: {foo: 'a'}}],
372
+ },
373
+ ],
374
+ disableBatching: false,
375
+ outgoingActions: [
376
+ {
377
+ actionType: 'sanity.action.document.edit',
378
+ draftId: getDraftId(DocumentId('doc1')),
379
+ publishedId: getPublishedId(DocumentId('doc1')),
380
+ patch: {set: {foo: 'a'}},
381
+ },
382
+ ],
383
+ outgoingMutations: [],
384
+ base: {
385
+ [getDraftId(DocumentId('doc1'))]: {
386
+ ...exampleDoc,
387
+ _id: getDraftId(DocumentId('doc1')),
388
+ _rev: 'rev1',
389
+ },
390
+ },
391
+ working: {
392
+ [getDraftId(DocumentId('doc1'))]: {
393
+ ...exampleDoc,
394
+ _id: getDraftId(DocumentId('doc1')),
395
+ _rev: 'rev2',
396
+ },
397
+ },
398
+ previous: {
399
+ [getDraftId(DocumentId('doc1'))]: {
400
+ ...exampleDoc,
401
+ _id: getDraftId(DocumentId('doc1')),
402
+ _rev: 'rev1',
403
+ },
404
+ },
405
+ previousRevs: {[getDraftId(DocumentId('doc1'))]: 'rev1'},
406
+ timestamp: '2025-02-06T00:00:00.000Z',
407
+ }
408
+ const liveEditTx: AppliedTransaction = {
409
+ transactionId: 'txn-live',
410
+ actions: [
411
+ {
412
+ type: 'document.edit',
413
+ documentId: 'doc2',
414
+ documentType: 'liveArticle',
415
+ liveEdit: true,
416
+ patches: [{set: {bar: 'b'}}],
417
+ },
418
+ ],
419
+ disableBatching: false,
420
+ outgoingActions: [],
421
+ outgoingMutations: [{patch: {id: 'doc2', set: {bar: 'b'}}}],
422
+ base: {doc2: {...exampleDoc, _id: 'doc2', _rev: 'rev1'}},
423
+ working: {doc2: {...exampleDoc, _id: 'doc2', _rev: 'rev2'}},
424
+ previous: {doc2: {...exampleDoc, _id: 'doc2', _rev: 'rev1'}},
425
+ previousRevs: {doc2: 'rev1'},
426
+ timestamp: '2025-02-06T00:01:00.000Z',
427
+ }
428
+
429
+ // liveEdit first: should return only the liveEdit transaction
430
+ const resultLiveFirst = batchAppliedTransactions([liveEditTx, nonLiveEditTx])
431
+ expect(resultLiveFirst?.batchedTransactionIds).toEqual(['txn-live'])
432
+ expect(resultLiveFirst?.outgoingMutations).toEqual(liveEditTx.outgoingMutations)
433
+ expect(resultLiveFirst?.outgoingActions).toHaveLength(0)
434
+
435
+ // non-liveEdit first: should return only the non-liveEdit transaction
436
+ const resultNonLiveFirst = batchAppliedTransactions([nonLiveEditTx, liveEditTx])
437
+ expect(resultNonLiveFirst?.batchedTransactionIds).toEqual(['txn-nonlive'])
438
+ expect(resultNonLiveFirst?.outgoingActions).toEqual(nonLiveEditTx.outgoingActions)
439
+ expect(resultNonLiveFirst?.outgoingMutations).toHaveLength(0)
440
+ })
353
441
  })
354
442
 
355
443
  describe('transitionAppliedTransactionsToOutgoing', () => {
@@ -366,7 +454,7 @@ describe('transitionAppliedTransactionsToOutgoing', () => {
366
454
  })
367
455
 
368
456
  it('transitions applied transactions to an outgoing transaction', () => {
369
- const draftId = getDraftId('doc1')
457
+ const draftId = getDraftId(DocumentId('doc1'))
370
458
  const initialDoc = {...exampleDoc, _id: draftId, foo: 'old', _rev: 'rev1'}
371
459
  const state: SyncTransactionState = {
372
460
  queued: [],
@@ -426,8 +514,8 @@ describe('cleanupOutgoingTransaction', () => {
426
514
  })
427
515
 
428
516
  it('removes subscription ids for all documents associated with the outgoing transaction and then clears outgoing', () => {
429
- const draftId = getDraftId('doc1')
430
- const pubId = getPublishedId('doc1')
517
+ const draftId = getDraftId(DocumentId('doc1'))
518
+ const pubId = getPublishedId(DocumentId('doc1'))
431
519
  const state: SyncTransactionState = {
432
520
  queued: [],
433
521
  applied: [],
@@ -467,8 +555,8 @@ describe('cleanupOutgoingTransaction', () => {
467
555
 
468
556
  describe('revertOutgoingTransaction', () => {
469
557
  it('reverts the outgoing transaction and updates documentStates by removing unverified revisions', () => {
470
- const draftId = getDraftId('doc1')
471
- const pubId = getPublishedId('doc1')
558
+ const draftId = getDraftId(DocumentId('doc1'))
559
+ const pubId = getPublishedId(DocumentId('doc1'))
472
560
  // In this test we simulate a state with one applied transaction and an outgoing transaction.
473
561
  const state: SyncTransactionState = {
474
562
  queued: [],
@@ -565,7 +653,7 @@ describe('applyRemoteDocument', () => {
565
653
  })
566
654
 
567
655
  it('verifies an unverified revision when the revision matches and previousRev is as expected', () => {
568
- const docId = getDraftId('doc1')
656
+ const docId = getDraftId(DocumentId('doc1'))
569
657
  const initialState: SyncTransactionState = {
570
658
  queued: [],
571
659
  applied: [],
@@ -607,7 +695,7 @@ describe('applyRemoteDocument', () => {
607
695
 
608
696
  it('rebases local changes when no matching unverified revision is found', () => {
609
697
  // In this branch we simply let processActions rebase so that the local becomes the remote.
610
- const docId = getDraftId('doc1')
698
+ const docId = getDraftId(DocumentId('doc1'))
611
699
  const initialState: SyncTransactionState = {
612
700
  queued: [],
613
701
  applied: [],
@@ -642,7 +730,7 @@ describe('applyRemoteDocument', () => {
642
730
 
643
731
  // Test that a sync event removes outdated unverified revisions.
644
732
  it('removes outdated unverified revisions when a sync event is received', () => {
645
- const docId = getDraftId('doc1')
733
+ const docId = getDraftId(DocumentId('doc1'))
646
734
  // An unverified revision created at an earlier time.
647
735
  const outdatedTimestamp = new Date('2025-02-06T00:09:00.000Z').toISOString()
648
736
  // The incoming sync event timestamp is later.