@sanity/sdk 2.4.0 → 2.5.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 (47) hide show
  1. package/dist/index.d.ts +35 -90
  2. package/dist/index.js +237 -111
  3. package/dist/index.js.map +1 -1
  4. package/package.json +9 -8
  5. package/src/auth/authStore.test.ts +13 -13
  6. package/src/auth/refreshStampedToken.test.ts +16 -16
  7. package/src/auth/subscribeToStateAndFetchCurrentUser.test.ts +6 -6
  8. package/src/auth/subscribeToStorageEventsAndSetToken.test.ts +4 -4
  9. package/src/comlink/controller/actions/destroyController.test.ts +2 -2
  10. package/src/comlink/controller/actions/getOrCreateChannel.test.ts +6 -6
  11. package/src/comlink/controller/actions/getOrCreateController.test.ts +5 -5
  12. package/src/comlink/controller/actions/getOrCreateController.ts +1 -1
  13. package/src/comlink/controller/actions/releaseChannel.test.ts +3 -2
  14. package/src/comlink/controller/comlinkControllerStore.test.ts +4 -4
  15. package/src/comlink/node/actions/getOrCreateNode.test.ts +7 -7
  16. package/src/comlink/node/actions/releaseNode.test.ts +2 -2
  17. package/src/comlink/node/comlinkNodeStore.test.ts +4 -3
  18. package/src/config/sanityConfig.ts +8 -3
  19. package/src/document/actions.ts +11 -7
  20. package/src/document/applyDocumentActions.test.ts +9 -6
  21. package/src/document/applyDocumentActions.ts +9 -49
  22. package/src/document/documentStore.test.ts +128 -115
  23. package/src/document/documentStore.ts +40 -10
  24. package/src/document/permissions.test.ts +9 -9
  25. package/src/document/permissions.ts +17 -7
  26. package/src/document/processActions.test.ts +248 -0
  27. package/src/document/processActions.ts +173 -0
  28. package/src/document/reducers.ts +13 -6
  29. package/src/presence/presenceStore.ts +13 -7
  30. package/src/preview/previewStore.test.ts +10 -2
  31. package/src/preview/previewStore.ts +2 -1
  32. package/src/preview/subscribeToStateAndFetchBatches.test.ts +8 -5
  33. package/src/preview/subscribeToStateAndFetchBatches.ts +9 -3
  34. package/src/projection/projectionStore.test.ts +18 -2
  35. package/src/projection/projectionStore.ts +2 -1
  36. package/src/projection/subscribeToStateAndFetchBatches.test.ts +6 -5
  37. package/src/projection/subscribeToStateAndFetchBatches.ts +9 -3
  38. package/src/releases/getPerspectiveState.ts +2 -2
  39. package/src/releases/releasesStore.ts +10 -4
  40. package/src/store/createActionBinder.test.ts +8 -6
  41. package/src/store/createActionBinder.ts +44 -29
  42. package/src/store/createStateSourceAction.test.ts +12 -11
  43. package/src/store/createStateSourceAction.ts +6 -6
  44. package/src/store/createStoreInstance.test.ts +29 -16
  45. package/src/store/createStoreInstance.ts +6 -5
  46. package/src/store/defineStore.test.ts +1 -1
  47. package/src/store/defineStore.ts +12 -7
@@ -75,7 +75,7 @@ describe('calculatePermissions', () => {
75
75
  const actions: DocumentAction[] = [
76
76
  {documentId: 'doc1', type: 'document.create', documentType: 'article'},
77
77
  ]
78
- const result = calculatePermissions({instance, state}, actions)
78
+ const result = calculatePermissions({instance, state}, {actions})
79
79
  expect(result).toEqual({allowed: true})
80
80
  })
81
81
 
@@ -91,7 +91,7 @@ describe('calculatePermissions', () => {
91
91
  const actions: DocumentAction[] = [
92
92
  {documentId: 'doc1', type: 'document.create', documentType: 'article'},
93
93
  ]
94
- expect(calculatePermissions({instance, state}, actions)).toBeUndefined()
94
+ expect(calculatePermissions({instance, state}, {actions})).toBeUndefined()
95
95
  })
96
96
 
97
97
  it('should catch PermissionActionError from processActions and return allowed false with a reason', () => {
@@ -107,7 +107,7 @@ describe('calculatePermissions', () => {
107
107
  const actions: DocumentAction[] = [
108
108
  {documentId: 'doc1', type: 'document.create', documentType: 'article'},
109
109
  ]
110
- const result = calculatePermissions({instance, state}, actions)
110
+ const result = calculatePermissions({instance, state}, {actions})
111
111
  expect(result).toBeDefined()
112
112
  expect(result?.allowed).toBe(false)
113
113
  expect(result?.reasons).toEqual(
@@ -135,7 +135,7 @@ describe('calculatePermissions', () => {
135
135
  const actions: DocumentAction[] = [
136
136
  {documentId: 'doc1', documentType: 'book', type: 'document.edit'},
137
137
  ]
138
- const result = calculatePermissions({instance, state}, actions)
138
+ const result = calculatePermissions({instance, state}, {actions})
139
139
  expect(result).toBeDefined()
140
140
  expect(result?.allowed).toBe(false)
141
141
  expect(result?.reasons).toEqual(
@@ -161,7 +161,7 @@ describe('calculatePermissions', () => {
161
161
  const actions: DocumentAction[] = [
162
162
  {documentId: 'doc1', documentType: 'book', type: 'document.edit'},
163
163
  ]
164
- const result = calculatePermissions({instance, state}, actions)
164
+ const result = calculatePermissions({instance, state}, {actions})
165
165
  expect(result).toBeDefined()
166
166
  expect(result?.allowed).toBe(false)
167
167
  expect(result?.reasons).toEqual(
@@ -185,7 +185,7 @@ describe('calculatePermissions', () => {
185
185
  const actions: DocumentAction[] = [
186
186
  {documentId: 'doc1', type: 'document.create', documentType: 'article'},
187
187
  ]
188
- expect(calculatePermissions({instance, state}, actions)).toBeUndefined()
188
+ expect(calculatePermissions({instance, state}, {actions})).toBeUndefined()
189
189
  })
190
190
 
191
191
  it('should catch ActionError from processActions and return a precondition error reason', () => {
@@ -200,7 +200,7 @@ describe('calculatePermissions', () => {
200
200
  const actions: DocumentAction[] = [
201
201
  {documentId: 'doc1', documentType: 'book', type: 'document.delete'},
202
202
  ]
203
- const result = calculatePermissions({instance, state}, actions)
203
+ const result = calculatePermissions({instance, state}, {actions})
204
204
  expect(result).toBeDefined()
205
205
  expect(result?.allowed).toBe(false)
206
206
  expect(result?.reasons).toEqual(
@@ -228,8 +228,8 @@ describe('calculatePermissions', () => {
228
228
  documentType: 'article',
229
229
  }
230
230
  // notice how the action is a copy
231
- const result1 = calculatePermissions({instance, state}, [{...action}])
232
- const result2 = calculatePermissions({instance, state}, [{...action}])
231
+ const result1 = calculatePermissions({instance, state}, {actions: [{...action}]})
232
+ const result2 = calculatePermissions({instance, state}, {actions: [{...action}]})
233
233
  expect(result1).toBe(result2)
234
234
  })
235
235
  })
@@ -58,15 +58,22 @@ const nullReplacer: object = {}
58
58
  const documentsSelector = createSelector(
59
59
  [
60
60
  ({state: {documentStates}}: SelectorContext<SyncTransactionState>) => documentStates,
61
- (_context: SelectorContext<SyncTransactionState>, actions: DocumentAction | DocumentAction[]) =>
61
+ (_context: SelectorContext<SyncTransactionState>, {actions}: {actions: DocumentAction[]}) =>
62
62
  actions,
63
63
  ],
64
64
  (documentStates, actions) => {
65
+ // Collect all document IDs needed for permission checks.
66
+ // Important: liveEdit documents don't have drafts, so we only fetch the single document to avoid waiting for non-existent draft documents.
65
67
  const documentIds = new Set(
66
- (Array.isArray(actions) ? actions : [actions])
67
- .map((i) => i.documentId)
68
- .filter((i) => typeof i === 'string')
69
- .flatMap((documentId) => [getPublishedId(documentId), getDraftId(documentId)]),
68
+ actions
69
+ .map((action) => {
70
+ if (typeof action.documentId !== 'string') return []
71
+ // For liveEdit documents, only fetch the single document
72
+ if (action.liveEdit) return [action.documentId]
73
+ // For standard documents, fetch both draft and published
74
+ return [getPublishedId(action.documentId), getDraftId(action.documentId)]
75
+ })
76
+ .flat(),
70
77
  )
71
78
 
72
79
  const documents: DocumentSet = {}
@@ -99,7 +106,7 @@ const documentsSelector = createSelector(
99
106
  const memoizedActionsSelector = createSelector(
100
107
  [
101
108
  documentsSelector,
102
- (_state: SelectorContext<SyncTransactionState>, actions: DocumentAction | DocumentAction[]) =>
109
+ (_state: SelectorContext<SyncTransactionState>, {actions}: {actions: DocumentAction[]}) =>
103
110
  actions,
104
111
  ],
105
112
  (documents, actions) => {
@@ -203,7 +210,10 @@ const _calculatePermissions = createSelector(
203
210
  // Check edit actions with no patches
204
211
  if (action.type === 'document.edit' && !action.patches?.length) {
205
212
  const docId = action.documentId
206
- const doc = documents[getDraftId(docId)] ?? documents[getPublishedId(docId)]
213
+ // For liveEdit documents, only check the single document
214
+ const doc = action.liveEdit
215
+ ? documents[docId]
216
+ : (documents[getDraftId(docId)] ?? documents[getPublishedId(docId)])
207
217
  if (!doc) {
208
218
  reasons.push({
209
219
  type: 'precondition',
@@ -29,6 +29,16 @@ const defaultGrants = {
29
29
  const transactionId = 'txn-123'
30
30
  const timestamp = '2025-02-02T00:00:00.000Z'
31
31
 
32
+ // Helper: Create a sample liveEdit document
33
+ const createLiveEditDoc = (id: string, title: string, rev: string = 'initial'): SanityDocument => ({
34
+ _id: id,
35
+ _type: 'liveArticle',
36
+ _createdAt: '2025-01-01T00:00:00.000Z',
37
+ _updatedAt: '2025-01-01T00:00:00.000Z',
38
+ _rev: rev,
39
+ title,
40
+ })
41
+
32
42
  describe('processActions', () => {
33
43
  describe('document.create', () => {
34
44
  it('should create a new draft document from a published document', () => {
@@ -845,4 +855,242 @@ describe('processActions', () => {
845
855
  ).toThrow(/Unknown action type: "document.unrecognizedAction"/)
846
856
  })
847
857
  })
858
+
859
+ describe('liveEdit documents', () => {
860
+ describe('document.create', () => {
861
+ it('should create a liveEdit document directly without draft logic', () => {
862
+ const base: DocumentSet = {}
863
+ const working: DocumentSet = {}
864
+ const actions: DocumentAction[] = [
865
+ {
866
+ documentId: 'live1',
867
+ type: 'document.create',
868
+ documentType: 'liveArticle',
869
+ liveEdit: true,
870
+ },
871
+ ]
872
+
873
+ const result = processActions({
874
+ actions,
875
+ transactionId,
876
+ base,
877
+ working,
878
+ timestamp,
879
+ grants: defaultGrants,
880
+ })
881
+
882
+ const doc = result.working['live1']
883
+ expect(doc).toBeDefined()
884
+ expect(doc?._id).toBe('live1')
885
+ expect(doc?._type).toBe('liveArticle')
886
+ expect(doc?._rev).toBe(transactionId)
887
+
888
+ // Should use document.create action, not version.create
889
+ expect(result.outgoingActions).toHaveLength(1)
890
+ const action = result.outgoingActions[0]
891
+ expect(action.actionType).toBe('sanity.action.document.create')
892
+ if ('attributes' in action && 'publishedId' in action) {
893
+ expect(action.publishedId).toBe('live1')
894
+ expect(action.attributes._id).toBe('live1')
895
+ expect(action.attributes._type).toBe('liveArticle')
896
+ } else {
897
+ throw new Error('Expected action to have attributes and publishedId')
898
+ }
899
+ })
900
+
901
+ it('should throw an error if liveEdit document already exists', () => {
902
+ const existingDoc = createLiveEditDoc('live1', 'Existing')
903
+ const base: DocumentSet = {live1: existingDoc}
904
+ const working: DocumentSet = {live1: existingDoc}
905
+ const actions: DocumentAction[] = [
906
+ {
907
+ documentId: 'live1',
908
+ type: 'document.create',
909
+ documentType: 'liveArticle',
910
+ liveEdit: true,
911
+ },
912
+ ]
913
+
914
+ expect(() =>
915
+ processActions({actions, transactionId, base, working, timestamp, grants: defaultGrants}),
916
+ ).toThrow('This document already exists')
917
+ })
918
+ })
919
+
920
+ describe('document.edit', () => {
921
+ it('should edit a liveEdit document directly', () => {
922
+ const doc = createLiveEditDoc('live1', 'Original Title')
923
+ const base: DocumentSet = {live1: doc}
924
+ const working: DocumentSet = {live1: doc}
925
+ const actions: DocumentAction[] = [
926
+ {
927
+ documentId: 'live1',
928
+ type: 'document.edit',
929
+ documentType: 'liveArticle',
930
+ liveEdit: true,
931
+ patches: [{set: {title: 'Updated Title'}}],
932
+ },
933
+ ]
934
+
935
+ const result = processActions({
936
+ actions,
937
+ transactionId,
938
+ base,
939
+ working,
940
+ timestamp,
941
+ grants: defaultGrants,
942
+ })
943
+
944
+ const editedDoc = result.working['live1']
945
+ expect(editedDoc).toBeDefined()
946
+ expect(editedDoc?.['title']).toBe('Updated Title')
947
+ expect(editedDoc?._id).toBe('live1')
948
+
949
+ // Should use document.edit action with draftId prefixed (for server validation) but publishedId as the actual doc
950
+ expect(result.outgoingActions[0]).toMatchObject({
951
+ actionType: 'sanity.action.document.edit',
952
+ draftId: 'drafts.live1',
953
+ publishedId: 'live1',
954
+ })
955
+ })
956
+
957
+ it('should throw an error if liveEdit document does not exist', () => {
958
+ const base: DocumentSet = {}
959
+ const working: DocumentSet = {}
960
+ const actions: DocumentAction[] = [
961
+ {
962
+ documentId: 'live1',
963
+ type: 'document.edit',
964
+ documentType: 'liveArticle',
965
+ liveEdit: true,
966
+ patches: [{set: {title: 'New Title'}}],
967
+ },
968
+ ]
969
+
970
+ expect(() =>
971
+ processActions({actions, transactionId, base, working, timestamp, grants: defaultGrants}),
972
+ ).toThrow('Cannot edit document because it does not exist')
973
+ })
974
+ })
975
+
976
+ describe('document.delete', () => {
977
+ it('should delete a liveEdit document directly', () => {
978
+ const doc = createLiveEditDoc('live1', 'To Delete')
979
+ const base: DocumentSet = {live1: doc}
980
+ const working: DocumentSet = {live1: doc}
981
+ const actions: DocumentAction[] = [
982
+ {
983
+ documentId: 'live1',
984
+ type: 'document.delete',
985
+ documentType: 'liveArticle',
986
+ liveEdit: true,
987
+ },
988
+ ]
989
+
990
+ const result = processActions({
991
+ actions,
992
+ transactionId,
993
+ base,
994
+ working,
995
+ timestamp,
996
+ grants: defaultGrants,
997
+ })
998
+
999
+ expect(result.working['live1']).toBeNull()
1000
+
1001
+ expect(result.outgoingActions).toEqual([
1002
+ {
1003
+ actionType: 'sanity.action.document.delete',
1004
+ publishedId: 'live1',
1005
+ },
1006
+ ])
1007
+ })
1008
+
1009
+ it('should throw an error if liveEdit document does not exist', () => {
1010
+ const base: DocumentSet = {}
1011
+ const working: DocumentSet = {}
1012
+ const actions: DocumentAction[] = [
1013
+ {
1014
+ documentId: 'live1',
1015
+ type: 'document.delete',
1016
+ documentType: 'liveArticle',
1017
+ liveEdit: true,
1018
+ },
1019
+ ]
1020
+
1021
+ expect(() =>
1022
+ processActions({actions, transactionId, base, working, timestamp, grants: defaultGrants}),
1023
+ ).toThrow('The document you are trying to delete does not exist')
1024
+ })
1025
+ })
1026
+
1027
+ describe('document.publish', () => {
1028
+ it('should throw an error for liveEdit documents', () => {
1029
+ const doc = createLiveEditDoc('live1', 'Title')
1030
+ const base: DocumentSet = {live1: doc}
1031
+ const working: DocumentSet = {live1: doc}
1032
+ const actions: DocumentAction[] = [
1033
+ {
1034
+ documentId: 'live1',
1035
+ type: 'document.publish',
1036
+ documentType: 'liveArticle',
1037
+ liveEdit: true,
1038
+ },
1039
+ ]
1040
+
1041
+ expect(() =>
1042
+ processActions({actions, transactionId, base, working, timestamp, grants: defaultGrants}),
1043
+ ).toThrow('Cannot publish liveEdit document')
1044
+ expect(() =>
1045
+ processActions({actions, transactionId, base, working, timestamp, grants: defaultGrants}),
1046
+ ).toThrow('LiveEdit documents do not support drafts or publishing')
1047
+ })
1048
+ })
1049
+
1050
+ describe('document.unpublish', () => {
1051
+ it('should throw an error for liveEdit documents', () => {
1052
+ const doc = createLiveEditDoc('live1', 'Title')
1053
+ const base: DocumentSet = {live1: doc}
1054
+ const working: DocumentSet = {live1: doc}
1055
+ const actions: DocumentAction[] = [
1056
+ {
1057
+ documentId: 'live1',
1058
+ type: 'document.unpublish',
1059
+ documentType: 'liveArticle',
1060
+ liveEdit: true,
1061
+ },
1062
+ ]
1063
+
1064
+ expect(() =>
1065
+ processActions({actions, transactionId, base, working, timestamp, grants: defaultGrants}),
1066
+ ).toThrow('Cannot unpublish liveEdit document')
1067
+ expect(() =>
1068
+ processActions({actions, transactionId, base, working, timestamp, grants: defaultGrants}),
1069
+ ).toThrow('LiveEdit documents do not support drafts or publishing')
1070
+ })
1071
+ })
1072
+
1073
+ describe('document.discard', () => {
1074
+ it('should throw an error for liveEdit documents', () => {
1075
+ const doc = createLiveEditDoc('live1', 'Title')
1076
+ const base: DocumentSet = {live1: doc}
1077
+ const working: DocumentSet = {live1: doc}
1078
+ const actions: DocumentAction[] = [
1079
+ {
1080
+ documentId: 'live1',
1081
+ type: 'document.discard',
1082
+ documentType: 'liveArticle',
1083
+ liveEdit: true,
1084
+ },
1085
+ ]
1086
+
1087
+ expect(() =>
1088
+ processActions({actions, transactionId, base, working, timestamp, grants: defaultGrants}),
1089
+ ).toThrow('Cannot discard changes for liveEdit document')
1090
+ expect(() =>
1091
+ processActions({actions, transactionId, base, working, timestamp, grants: defaultGrants}),
1092
+ ).toThrow('LiveEdit documents do not support drafts')
1093
+ })
1094
+ })
1095
+ })
848
1096
  })
@@ -140,6 +140,52 @@ export function processActions({
140
140
  switch (action.type) {
141
141
  case 'document.create': {
142
142
  const documentId = getId(action.documentId)
143
+
144
+ if (action.liveEdit) {
145
+ // For liveEdit documents, create directly without draft/published logic
146
+ if (working[documentId]) {
147
+ throw new ActionError({
148
+ documentId,
149
+ transactionId,
150
+ message: `This document already exists.`,
151
+ })
152
+ }
153
+
154
+ const newDocBase = {_type: action.documentType, _id: documentId}
155
+ const newDocWorking = {_type: action.documentType, _id: documentId}
156
+ const mutations: Mutation[] = [{create: newDocWorking}]
157
+
158
+ base = processMutations({
159
+ documents: base,
160
+ transactionId,
161
+ mutations: [{create: newDocBase}],
162
+ timestamp,
163
+ })
164
+ working = processMutations({
165
+ documents: working,
166
+ transactionId,
167
+ mutations,
168
+ timestamp,
169
+ })
170
+
171
+ if (!checkGrant(grants.create, working[documentId] as SanityDocument)) {
172
+ throw new PermissionActionError({
173
+ documentId,
174
+ transactionId,
175
+ message: `You do not have permission to create document "${documentId}".`,
176
+ })
177
+ }
178
+
179
+ outgoingMutations.push(...mutations)
180
+ outgoingActions.push({
181
+ actionType: 'sanity.action.document.create',
182
+ publishedId: documentId,
183
+ attributes: newDocWorking,
184
+ })
185
+ continue
186
+ }
187
+
188
+ // Standard draft/published logic
143
189
  const draftId = getDraftId(documentId)
144
190
  const publishedId = getPublishedId(documentId)
145
191
 
@@ -198,6 +244,39 @@ export function processActions({
198
244
 
199
245
  case 'document.delete': {
200
246
  const documentId = action.documentId
247
+
248
+ if (action.liveEdit) {
249
+ // For liveEdit documents, delete directly
250
+ if (!working[documentId]) {
251
+ throw new ActionError({
252
+ documentId,
253
+ transactionId,
254
+ message: 'The document you are trying to delete does not exist.',
255
+ })
256
+ }
257
+
258
+ if (!checkGrant(grants.update, working[documentId])) {
259
+ throw new PermissionActionError({
260
+ documentId,
261
+ transactionId,
262
+ message: `You do not have permission to delete this document.`,
263
+ })
264
+ }
265
+
266
+ const mutations: Mutation[] = [{delete: {id: documentId}}]
267
+
268
+ base = processMutations({documents: base, transactionId, mutations, timestamp})
269
+ working = processMutations({documents: working, transactionId, mutations, timestamp})
270
+
271
+ outgoingMutations.push(...mutations)
272
+ outgoingActions.push({
273
+ actionType: 'sanity.action.document.delete',
274
+ publishedId: documentId,
275
+ })
276
+ continue
277
+ }
278
+
279
+ // Standard draft/published logic
201
280
  const draftId = getDraftId(documentId)
202
281
  const publishedId = getPublishedId(documentId)
203
282
 
@@ -240,6 +319,16 @@ export function processActions({
240
319
 
241
320
  case 'document.discard': {
242
321
  const documentId = getId(action.documentId)
322
+
323
+ if (action.liveEdit) {
324
+ throw new ActionError({
325
+ documentId,
326
+ transactionId,
327
+ message: `Cannot discard changes for liveEdit document "${documentId}". LiveEdit documents do not support drafts.`,
328
+ })
329
+ }
330
+
331
+ // Standard draft/published logic
243
332
  const draftId = getDraftId(documentId)
244
333
  const mutations: Mutation[] = [{delete: {id: draftId}}]
245
334
 
@@ -272,6 +361,70 @@ export function processActions({
272
361
 
273
362
  case 'document.edit': {
274
363
  const documentId = getId(action.documentId)
364
+
365
+ if (action.liveEdit) {
366
+ // For liveEdit documents, edit directly without draft logic
367
+ const userPatches = action.patches?.map((patch) => ({patch: {id: documentId, ...patch}}))
368
+
369
+ // skip this action if there are no associated patches
370
+ if (!userPatches?.length) continue
371
+
372
+ if (!working[documentId] || !base[documentId]) {
373
+ throw new ActionError({
374
+ documentId,
375
+ transactionId,
376
+ message: `Cannot edit document because it does not exist.`,
377
+ })
378
+ }
379
+
380
+ const baseBefore = base[documentId] as SanityDocument
381
+ if (userPatches) {
382
+ base = processMutations({
383
+ documents: base,
384
+ transactionId,
385
+ mutations: userPatches,
386
+ timestamp,
387
+ })
388
+ }
389
+
390
+ const baseAfter = base[documentId] as SanityDocument
391
+ const patches = diffValue(baseBefore, baseAfter)
392
+
393
+ const workingBefore = working[documentId] as SanityDocument
394
+ if (!checkGrant(grants.update, workingBefore)) {
395
+ throw new PermissionActionError({
396
+ documentId,
397
+ transactionId,
398
+ message: `You do not have permission to edit document "${documentId}".`,
399
+ })
400
+ }
401
+
402
+ const workingMutations = patches.map((patch) => ({patch: {id: documentId, ...patch}}))
403
+
404
+ working = processMutations({
405
+ documents: working,
406
+ transactionId,
407
+ mutations: workingMutations,
408
+ timestamp,
409
+ })
410
+
411
+ 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
+ continue
425
+ }
426
+
427
+ // Standard draft/published logic
275
428
  const draftId = getDraftId(documentId)
276
429
  const publishedId = getPublishedId(documentId)
277
430
  const userPatches = action.patches?.map((patch) => ({patch: {id: draftId, ...patch}}))
@@ -362,6 +515,16 @@ export function processActions({
362
515
 
363
516
  case 'document.publish': {
364
517
  const documentId = getId(action.documentId)
518
+
519
+ if (action.liveEdit) {
520
+ throw new ActionError({
521
+ documentId,
522
+ transactionId,
523
+ message: `Cannot publish liveEdit document "${documentId}". LiveEdit documents do not support drafts or publishing.`,
524
+ })
525
+ }
526
+
527
+ // Standard draft/published logic
365
528
  const draftId = getDraftId(documentId)
366
529
  const publishedId = getPublishedId(documentId)
367
530
 
@@ -428,6 +591,16 @@ export function processActions({
428
591
 
429
592
  case 'document.unpublish': {
430
593
  const documentId = getId(action.documentId)
594
+
595
+ if (action.liveEdit) {
596
+ throw new ActionError({
597
+ documentId,
598
+ transactionId,
599
+ message: `Cannot unpublish liveEdit document "${documentId}". LiveEdit documents do not support drafts or publishing.`,
600
+ })
601
+ }
602
+
603
+ // Standard draft/published logic
431
604
  const draftId = getDraftId(documentId)
432
605
  const publishedId = getPublishedId(documentId)
433
606
 
@@ -20,6 +20,7 @@ export type SyncTransactionState = Pick<
20
20
 
21
21
  type ActionMap = {
22
22
  create: 'sanity.action.document.version.create'
23
+ createLiveEdit: 'sanity.action.document.create'
23
24
  discard: 'sanity.action.document.version.discard'
24
25
  unpublish: 'sanity.action.document.unpublish'
25
26
  delete: 'sanity.action.document.delete'
@@ -34,6 +35,7 @@ type OptimisticLock = {
34
35
 
35
36
  export type HttpAction =
36
37
  | {actionType: ActionMap['create']; publishedId: string; attributes: SanityDocumentLike}
38
+ | {actionType: ActionMap['createLiveEdit']; publishedId: string; attributes: SanityDocumentLike}
37
39
  | {actionType: ActionMap['discard']; versionId: string; purge?: boolean}
38
40
  | {actionType: ActionMap['unpublish']; draftId: string; publishedId: string}
39
41
  | {actionType: ActionMap['delete']; publishedId: string; includeDrafts?: string[]}
@@ -550,13 +552,19 @@ export function removeSubscriptionIdFromDocument(
550
552
  export function manageSubscriberIds(
551
553
  {state}: StoreContext<SyncTransactionState>,
552
554
  documentId: string | string[],
555
+ options?: {expandDraftPublished?: boolean},
553
556
  ): () => void {
557
+ const expandDraftPublished = options?.expandDraftPublished ?? true
554
558
  const documentIds = Array.from(
555
559
  new Set(
556
- (Array.isArray(documentId) ? documentId : [documentId]).flatMap((id) => [
557
- getPublishedId(id),
558
- getDraftId(id),
559
- ]),
560
+ expandDraftPublished
561
+ ? (Array.isArray(documentId) ? documentId : [documentId]).flatMap((id) => [
562
+ getPublishedId(id),
563
+ getDraftId(id),
564
+ ])
565
+ : Array.isArray(documentId)
566
+ ? documentId
567
+ : [documentId],
560
568
  ),
561
569
  )
562
570
  const subscriptionId = insecureRandomId()
@@ -579,8 +587,7 @@ export function manageSubscriberIds(
579
587
  }
580
588
  }
581
589
 
582
- export function getDocumentIdsFromActions(action: DocumentAction | DocumentAction[]): string[] {
583
- const actions = Array.isArray(action) ? action : [action]
590
+ export function getDocumentIdsFromActions(actions: DocumentAction[]): string[] {
584
591
  return Array.from(
585
592
  new Set(
586
593
  actions