@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.
- package/dist/index.d.ts +35 -90
- package/dist/index.js +237 -111
- package/dist/index.js.map +1 -1
- package/package.json +9 -8
- package/src/auth/authStore.test.ts +13 -13
- package/src/auth/refreshStampedToken.test.ts +16 -16
- package/src/auth/subscribeToStateAndFetchCurrentUser.test.ts +6 -6
- package/src/auth/subscribeToStorageEventsAndSetToken.test.ts +4 -4
- package/src/comlink/controller/actions/destroyController.test.ts +2 -2
- package/src/comlink/controller/actions/getOrCreateChannel.test.ts +6 -6
- package/src/comlink/controller/actions/getOrCreateController.test.ts +5 -5
- package/src/comlink/controller/actions/getOrCreateController.ts +1 -1
- package/src/comlink/controller/actions/releaseChannel.test.ts +3 -2
- package/src/comlink/controller/comlinkControllerStore.test.ts +4 -4
- package/src/comlink/node/actions/getOrCreateNode.test.ts +7 -7
- package/src/comlink/node/actions/releaseNode.test.ts +2 -2
- package/src/comlink/node/comlinkNodeStore.test.ts +4 -3
- package/src/config/sanityConfig.ts +8 -3
- package/src/document/actions.ts +11 -7
- package/src/document/applyDocumentActions.test.ts +9 -6
- package/src/document/applyDocumentActions.ts +9 -49
- package/src/document/documentStore.test.ts +128 -115
- package/src/document/documentStore.ts +40 -10
- package/src/document/permissions.test.ts +9 -9
- package/src/document/permissions.ts +17 -7
- package/src/document/processActions.test.ts +248 -0
- package/src/document/processActions.ts +173 -0
- package/src/document/reducers.ts +13 -6
- package/src/presence/presenceStore.ts +13 -7
- package/src/preview/previewStore.test.ts +10 -2
- package/src/preview/previewStore.ts +2 -1
- package/src/preview/subscribeToStateAndFetchBatches.test.ts +8 -5
- package/src/preview/subscribeToStateAndFetchBatches.ts +9 -3
- package/src/projection/projectionStore.test.ts +18 -2
- package/src/projection/projectionStore.ts +2 -1
- package/src/projection/subscribeToStateAndFetchBatches.test.ts +6 -5
- package/src/projection/subscribeToStateAndFetchBatches.ts +9 -3
- package/src/releases/getPerspectiveState.ts +2 -2
- package/src/releases/releasesStore.ts +10 -4
- package/src/store/createActionBinder.test.ts +8 -6
- package/src/store/createActionBinder.ts +44 -29
- package/src/store/createStateSourceAction.test.ts +12 -11
- package/src/store/createStateSourceAction.ts +6 -6
- package/src/store/createStoreInstance.test.ts +29 -16
- package/src/store/createStoreInstance.ts +6 -5
- package/src/store/defineStore.test.ts +1 -1
- 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:
|
|
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
|
-
|
|
67
|
-
.map((
|
|
68
|
-
|
|
69
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
|
package/src/document/reducers.ts
CHANGED
|
@@ -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
|
-
|
|
557
|
-
|
|
558
|
-
|
|
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(
|
|
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
|