@sanity/sdk 2.10.0 → 2.11.1

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 (60) hide show
  1. package/dist/_chunks-dts/utils.d.ts +200 -28
  2. package/dist/_chunks-es/_internal.js +3 -14
  3. package/dist/_chunks-es/_internal.js.map +1 -1
  4. package/dist/_chunks-es/createGroqSearchFilter.js +17 -19
  5. package/dist/_chunks-es/createGroqSearchFilter.js.map +1 -1
  6. package/dist/_chunks-es/version.js +1 -1
  7. package/dist/_exports/_internal.d.ts +16 -2
  8. package/dist/_exports/_internal.js +3 -1
  9. package/dist/index.d.ts +2 -2
  10. package/dist/index.js +564 -459
  11. package/dist/index.js.map +1 -1
  12. package/package.json +16 -18
  13. package/src/_exports/_internal.ts +1 -0
  14. package/src/_exports/index.ts +25 -2
  15. package/src/agent/agentActions.ts +21 -25
  16. package/src/auth/refreshStampedToken.test.ts +2 -2
  17. package/src/auth/subscribeToStateAndFetchCurrentUser.test.ts +116 -0
  18. package/src/auth/subscribeToStateAndFetchCurrentUser.ts +27 -9
  19. package/src/client/clientStore.test.ts +10 -46
  20. package/src/client/clientStore.ts +7 -14
  21. package/src/comlink/node/actions/getOrCreateNode.test.ts +5 -2
  22. package/src/comlink/node/actions/releaseNode.test.ts +3 -3
  23. package/src/config/sanityConfig.ts +0 -1
  24. package/src/document/documentStore.ts +3 -8
  25. package/src/document/permissions.ts +1 -1
  26. package/src/document/processActions/create.ts +135 -0
  27. package/src/document/processActions/delete.ts +100 -0
  28. package/src/document/processActions/discard.ts +63 -0
  29. package/src/document/processActions/edit.ts +176 -0
  30. package/src/document/processActions/processActions.ts +168 -0
  31. package/src/document/processActions/publish.ts +120 -0
  32. package/src/document/processActions/shared.ts +47 -0
  33. package/src/document/processActions/unpublish.ts +85 -0
  34. package/src/document/processActions.test.ts +1 -1
  35. package/src/document/reducers.ts +1 -1
  36. package/src/document/sharedListener.ts +3 -5
  37. package/src/organization/organization.test-d.ts +102 -0
  38. package/src/organization/organization.test.ts +138 -0
  39. package/src/organization/organization.ts +166 -0
  40. package/src/organizations/organizations.test-d.ts +77 -0
  41. package/src/organizations/organizations.test.ts +150 -0
  42. package/src/organizations/organizations.ts +132 -0
  43. package/src/presence/presenceStore.test.ts +5 -5
  44. package/src/preview/previewProjectionUtils.ts +2 -3
  45. package/src/project/project.test-d.ts +93 -0
  46. package/src/project/project.test.ts +108 -10
  47. package/src/project/project.ts +152 -26
  48. package/src/projection/subscribeToStateAndFetchBatches.ts +4 -9
  49. package/src/projects/projects.test-d.ts +38 -0
  50. package/src/projects/projects.test.ts +104 -38
  51. package/src/projects/projects.ts +74 -14
  52. package/src/query/queryStore.ts +2 -3
  53. package/src/releases/releasesStore.test.ts +1 -1
  54. package/src/releases/releasesStore.ts +2 -2
  55. package/src/store/createSanityInstance.ts +3 -3
  56. package/src/telemetry/devMode.test.ts +8 -0
  57. package/src/telemetry/devMode.ts +10 -9
  58. package/src/telemetry/initTelemetry.test.ts +0 -17
  59. package/src/telemetry/initTelemetry.ts +2 -12
  60. package/src/document/processActions.ts +0 -735
@@ -0,0 +1,135 @@
1
+ import {DocumentId, getDraftId, getPublishedId, getVersionId} from '@sanity/id-utils'
2
+ import {type Mutation, type SanityDocument} from '@sanity/types'
3
+
4
+ import {isReleasePerspective} from '../../releases/utils/isReleasePerspective'
5
+ import {type CreateDocumentAction} from '../actions'
6
+ import {getId, processMutations} from '../processMutations'
7
+ import {
8
+ ActionError,
9
+ type ActionHandlerContext,
10
+ type ActionHandlerResult,
11
+ checkGrant,
12
+ PermissionActionError,
13
+ } from './shared'
14
+
15
+ export function handleCreate(
16
+ action: CreateDocumentAction,
17
+ ctx: ActionHandlerContext,
18
+ ): ActionHandlerResult {
19
+ const {transactionId, timestamp, grants, outgoingActions, outgoingMutations} = ctx
20
+ let {base, working} = ctx
21
+
22
+ const documentId = getId(action.documentId)
23
+
24
+ if (action.liveEdit) {
25
+ if (working[documentId]) {
26
+ throw new ActionError({
27
+ documentId,
28
+ transactionId,
29
+ message: `This document already exists.`,
30
+ })
31
+ }
32
+
33
+ const newDocBase = {_type: action.documentType, _id: documentId, ...action.initialValue}
34
+ const newDocWorking = {
35
+ _type: action.documentType,
36
+ _id: documentId,
37
+ ...action.initialValue,
38
+ }
39
+ const mutations: Mutation[] = [{create: newDocWorking}]
40
+
41
+ base = processMutations({
42
+ documents: base,
43
+ transactionId,
44
+ mutations: [{create: newDocBase}],
45
+ timestamp,
46
+ })
47
+ working = processMutations({
48
+ documents: working,
49
+ transactionId,
50
+ mutations,
51
+ timestamp,
52
+ })
53
+
54
+ if (!checkGrant(grants.create, working[documentId] as SanityDocument)) {
55
+ throw new PermissionActionError({
56
+ documentId,
57
+ transactionId,
58
+ message: `You do not have permission to create document "${documentId}".`,
59
+ })
60
+ }
61
+
62
+ // liveEdit documents use the mutation endpoint directly -- we don't send actions
63
+ outgoingMutations.push(...mutations)
64
+ return {base, working}
65
+ }
66
+
67
+ // Standard draft/published/version logic
68
+ const versionId = isReleasePerspective(action.perspective)
69
+ ? getVersionId(DocumentId(documentId), action.perspective.releaseName)
70
+ : undefined
71
+ const draftId = getDraftId(DocumentId(documentId))
72
+ const publishedId = getPublishedId(DocumentId(documentId))
73
+
74
+ const alreadyHasVersion = versionId ? working[versionId] : working[draftId]
75
+
76
+ if (alreadyHasVersion) {
77
+ const errorDocType = versionId ? 'release version' : 'draft'
78
+ throw new ActionError({
79
+ documentId,
80
+ transactionId,
81
+ message: `A ${errorDocType} of this document already exists. Please use or discard the existing ${errorDocType} before creating a new one.`,
82
+ })
83
+ }
84
+
85
+ // Spread the (possibly undefined) draft or published version directly.
86
+ // (studio uses the draft version as a base if you are in a release perspective)
87
+ const newDocBase = {
88
+ ...(base[draftId] ?? base[publishedId]),
89
+ _type: action.documentType,
90
+ _id: versionId ?? draftId,
91
+ ...action.initialValue,
92
+ }
93
+ const newDocWorking = {
94
+ ...(working[draftId] ?? working[publishedId]),
95
+ _type: action.documentType,
96
+ _id: versionId ?? draftId,
97
+ ...action.initialValue,
98
+ }
99
+ const mutations: Mutation[] = [{create: newDocWorking}]
100
+
101
+ base = processMutations({
102
+ documents: base,
103
+ transactionId,
104
+ mutations: [{create: newDocBase}],
105
+ timestamp,
106
+ })
107
+ working = processMutations({
108
+ documents: working,
109
+ transactionId,
110
+ mutations,
111
+ timestamp,
112
+ })
113
+
114
+ if (versionId && !checkGrant(grants.create, working[versionId] as SanityDocument)) {
115
+ throw new PermissionActionError({
116
+ documentId,
117
+ transactionId,
118
+ message: `You do not have permission to create a release version for document "${documentId}".`,
119
+ })
120
+ } else if (!versionId && !checkGrant(grants.create, working[draftId] as SanityDocument)) {
121
+ throw new PermissionActionError({
122
+ documentId,
123
+ transactionId,
124
+ message: `You do not have permission to create a draft for document "${documentId}".`,
125
+ })
126
+ }
127
+
128
+ outgoingMutations.push(...mutations)
129
+ outgoingActions.push({
130
+ actionType: 'sanity.action.document.version.create',
131
+ publishedId,
132
+ attributes: newDocWorking,
133
+ })
134
+ return {base, working}
135
+ }
@@ -0,0 +1,100 @@
1
+ import {DocumentId, getDraftId, getPublishedId} from '@sanity/id-utils'
2
+ import {type Mutation} from '@sanity/types'
3
+
4
+ import {isReleasePerspective} from '../../releases/utils/isReleasePerspective'
5
+ import {type DeleteDocumentAction} from '../actions'
6
+ import {processMutations} from '../processMutations'
7
+ import {
8
+ ActionError,
9
+ type ActionHandlerContext,
10
+ type ActionHandlerResult,
11
+ checkGrant,
12
+ PermissionActionError,
13
+ } from './shared'
14
+
15
+ export function handleDelete(
16
+ action: DeleteDocumentAction,
17
+ ctx: ActionHandlerContext,
18
+ ): ActionHandlerResult {
19
+ const {transactionId, timestamp, grants, outgoingActions, outgoingMutations} = ctx
20
+ let {base, working} = ctx
21
+
22
+ const documentId = action.documentId
23
+
24
+ if (isReleasePerspective(action.perspective)) {
25
+ throw new ActionError({
26
+ documentId,
27
+ transactionId,
28
+ message: `Cannot delete a version document. You may want to use the "unpublish" or "discard" actions instead.`,
29
+ })
30
+ }
31
+
32
+ if (action.liveEdit) {
33
+ if (!working[documentId]) {
34
+ throw new ActionError({
35
+ documentId,
36
+ transactionId,
37
+ message: 'The document you are trying to delete does not exist.',
38
+ })
39
+ }
40
+
41
+ if (!checkGrant(grants.update, working[documentId])) {
42
+ throw new PermissionActionError({
43
+ documentId,
44
+ transactionId,
45
+ message: `You do not have permission to delete this document.`,
46
+ })
47
+ }
48
+
49
+ const mutations: Mutation[] = [{delete: {id: documentId}}]
50
+
51
+ base = processMutations({documents: base, transactionId, mutations, timestamp})
52
+ working = processMutations({documents: working, transactionId, mutations, timestamp})
53
+
54
+ // although liveEdit documents can use the actions API for deletion,
55
+ // having this be an action while other operations are mutations creates an inconsistency
56
+ // (and a possible race condition in document store where mutations might get skipped)
57
+ outgoingMutations.push(...mutations)
58
+ return {base, working}
59
+ }
60
+
61
+ // Standard draft/published logic
62
+ const draftId = getDraftId(DocumentId(documentId))
63
+ const publishedId = getPublishedId(DocumentId(documentId))
64
+
65
+ if (!working[publishedId]) {
66
+ throw new ActionError({
67
+ documentId,
68
+ transactionId,
69
+ message: working[draftId]
70
+ ? 'Cannot delete a document without a published version.'
71
+ : 'The document you are trying to delete does not exist.',
72
+ })
73
+ }
74
+
75
+ const cantDeleteDraft = working[draftId] && !checkGrant(grants.update, working[draftId])
76
+ const cantDeletePublished =
77
+ working[publishedId] && !checkGrant(grants.update, working[publishedId])
78
+
79
+ if (cantDeleteDraft || cantDeletePublished) {
80
+ throw new PermissionActionError({
81
+ documentId,
82
+ transactionId,
83
+ message: `You do not have permission to delete this document.`,
84
+ })
85
+ }
86
+
87
+ const mutations: Mutation[] = [{delete: {id: publishedId}}, {delete: {id: draftId}}]
88
+ const includeDrafts = working[draftId] ? [draftId] : undefined
89
+
90
+ base = processMutations({documents: base, transactionId, mutations, timestamp})
91
+ working = processMutations({documents: working, transactionId, mutations, timestamp})
92
+
93
+ outgoingMutations.push(...mutations)
94
+ outgoingActions.push({
95
+ actionType: 'sanity.action.document.delete',
96
+ publishedId,
97
+ ...(includeDrafts ? {includeDrafts} : {}),
98
+ })
99
+ return {base, working}
100
+ }
@@ -0,0 +1,63 @@
1
+ import {DocumentId, getDraftId, getVersionId} from '@sanity/id-utils'
2
+ import {type Mutation} from '@sanity/types'
3
+
4
+ import {isReleasePerspective} from '../../releases/utils/isReleasePerspective'
5
+ import {type DiscardDocumentAction} from '../actions'
6
+ import {getId, processMutations} from '../processMutations'
7
+ import {
8
+ ActionError,
9
+ type ActionHandlerContext,
10
+ type ActionHandlerResult,
11
+ checkGrant,
12
+ PermissionActionError,
13
+ } from './shared'
14
+
15
+ export function handleDiscard(
16
+ action: DiscardDocumentAction,
17
+ ctx: ActionHandlerContext,
18
+ ): ActionHandlerResult {
19
+ const {transactionId, timestamp, grants, outgoingActions, outgoingMutations} = ctx
20
+ let {base, working} = ctx
21
+
22
+ const documentId = getId(action.documentId)
23
+
24
+ if (action.liveEdit) {
25
+ throw new ActionError({
26
+ documentId,
27
+ transactionId,
28
+ message: `Cannot discard changes for liveEdit document "${documentId}". LiveEdit documents do not support drafts.`,
29
+ })
30
+ }
31
+
32
+ // draft/published or version logic
33
+ const versionId = isReleasePerspective(action.perspective)
34
+ ? getVersionId(DocumentId(documentId), action.perspective.releaseName)
35
+ : getDraftId(DocumentId(documentId))
36
+ const mutations: Mutation[] = [{delete: {id: versionId}}]
37
+
38
+ if (!working[versionId]) {
39
+ throw new ActionError({
40
+ documentId,
41
+ transactionId,
42
+ message: `There is no draft or version available to discard for document "${documentId}".`,
43
+ })
44
+ }
45
+
46
+ if (!checkGrant(grants.update, working[versionId])) {
47
+ throw new PermissionActionError({
48
+ documentId,
49
+ transactionId,
50
+ message: `You do not have permission to discard changes for document "${documentId}".`,
51
+ })
52
+ }
53
+
54
+ base = processMutations({documents: base, transactionId, mutations, timestamp})
55
+ working = processMutations({documents: working, transactionId, mutations, timestamp})
56
+
57
+ outgoingMutations.push(...mutations)
58
+ outgoingActions.push({
59
+ actionType: 'sanity.action.document.version.discard',
60
+ versionId,
61
+ })
62
+ return {base, working}
63
+ }
@@ -0,0 +1,176 @@
1
+ import {diffValue} from '@sanity/diff-patch'
2
+ import {DocumentId, getDraftId, getPublishedId, getVersionId} from '@sanity/id-utils'
3
+ import {type Mutation, type PatchOperations, type SanityDocument} from '@sanity/types'
4
+
5
+ import {isReleasePerspective} from '../../releases/utils/isReleasePerspective'
6
+ import {type EditDocumentAction} from '../actions'
7
+ import {getId, processMutations} from '../processMutations'
8
+ import {
9
+ ActionError,
10
+ type ActionHandlerContext,
11
+ type ActionHandlerResult,
12
+ checkGrant,
13
+ PermissionActionError,
14
+ } from './shared'
15
+
16
+ export function handleEdit(
17
+ action: EditDocumentAction,
18
+ ctx: ActionHandlerContext,
19
+ ): ActionHandlerResult {
20
+ const {transactionId, timestamp, grants, outgoingActions, outgoingMutations} = ctx
21
+ let {base, working} = ctx
22
+
23
+ const documentId = getId(action.documentId)
24
+
25
+ if (action.liveEdit) {
26
+ // Single-document mode (liveEdit or release perspective): edit directly without draft logic
27
+ const userPatches = action.patches?.map((patch) => ({patch: {id: documentId, ...patch}}))
28
+
29
+ // skip this action if there are no associated patches
30
+ if (!userPatches?.length) return {base, working}
31
+
32
+ if (!working[documentId] || !base[documentId]) {
33
+ throw new ActionError({
34
+ documentId,
35
+ transactionId,
36
+ message: `Cannot edit document because it does not exist.`,
37
+ })
38
+ }
39
+
40
+ const baseBefore = base[documentId] as SanityDocument
41
+ if (userPatches) {
42
+ base = processMutations({
43
+ documents: base,
44
+ transactionId,
45
+ mutations: userPatches,
46
+ timestamp,
47
+ })
48
+ }
49
+
50
+ const baseAfter = base[documentId] as SanityDocument
51
+ const patches = diffValue(baseBefore, baseAfter)
52
+
53
+ const workingBefore = working[documentId] as SanityDocument
54
+ if (!checkGrant(grants.update, workingBefore)) {
55
+ throw new PermissionActionError({
56
+ documentId,
57
+ transactionId,
58
+ message: `You do not have permission to edit document "${documentId}".`,
59
+ })
60
+ }
61
+
62
+ const workingMutations = patches.map((patch) => ({patch: {id: documentId, ...patch}}))
63
+
64
+ working = processMutations({
65
+ documents: working,
66
+ transactionId,
67
+ mutations: workingMutations,
68
+ timestamp,
69
+ })
70
+
71
+ // liveEdit documents use the mutation endpoint directly -- we don't send actions
72
+ outgoingMutations.push(...workingMutations)
73
+ return {base, working}
74
+ }
75
+
76
+ const versionId = isReleasePerspective(action.perspective)
77
+ ? getVersionId(DocumentId(documentId), action.perspective.releaseName)
78
+ : undefined
79
+ const draftId = getDraftId(DocumentId(documentId))
80
+ const publishedId = getPublishedId(DocumentId(documentId))
81
+ const patchDocumentId = isReleasePerspective(action.perspective) ? versionId! : draftId
82
+ const userPatches = action.patches?.map((patch) => ({
83
+ patch: {id: patchDocumentId, ...patch},
84
+ }))
85
+
86
+ // skip this action if there are no associated patches
87
+ if (!userPatches?.length) return {base, working}
88
+
89
+ if (isReleasePerspective(action.perspective)) {
90
+ if (!working[versionId!] && !base[versionId!]) {
91
+ throw new ActionError({
92
+ documentId,
93
+ transactionId,
94
+ message: `This document does not exist in the release. Please create it or add it to the release first.`,
95
+ })
96
+ }
97
+ } else if (
98
+ (!working[draftId] && !working[publishedId]) ||
99
+ (!base[draftId] && !base[publishedId])
100
+ ) {
101
+ throw new ActionError({
102
+ documentId,
103
+ transactionId,
104
+ message: `Cannot edit document because it does not exist in draft or published form.`,
105
+ })
106
+ }
107
+
108
+ const baseMutations: Mutation[] = []
109
+ // don't create a draft from the published version in a release perspective
110
+ if (!isReleasePerspective(action.perspective) && !base[draftId] && base[publishedId]) {
111
+ // otherwise make a draft from the published version
112
+ baseMutations.push({create: {...base[publishedId], _id: draftId}})
113
+ }
114
+
115
+ // the above statement and guards should make this never be null or undefined
116
+ const baseBefore = base[patchDocumentId] ?? base[publishedId]
117
+ if (userPatches) {
118
+ baseMutations.push(...userPatches)
119
+ }
120
+
121
+ base = processMutations({
122
+ documents: base,
123
+ transactionId,
124
+ mutations: baseMutations,
125
+ timestamp,
126
+ })
127
+ // this one will always be defined because a patch mutation will never
128
+ // delete an input document
129
+ const baseAfter = base[patchDocumentId] as SanityDocument
130
+ const patches = diffValue(baseBefore, baseAfter)
131
+
132
+ const workingMutations: Mutation[] = []
133
+ if (!isReleasePerspective(action.perspective) && !working[draftId] && working[publishedId]) {
134
+ const newDraftFromPublished = {...working[publishedId], _id: draftId}
135
+
136
+ if (!checkGrant(grants.create, newDraftFromPublished)) {
137
+ throw new PermissionActionError({
138
+ documentId,
139
+ transactionId,
140
+ message: `You do not have permission to create a draft for editing this document.`,
141
+ })
142
+ }
143
+
144
+ workingMutations.push({create: newDraftFromPublished})
145
+ }
146
+
147
+ // the first if statement should make this never be null or undefined
148
+ const workingBefore = working[patchDocumentId] ?? working[publishedId]
149
+ if (!checkGrant(grants.update, workingBefore!)) {
150
+ throw new PermissionActionError({
151
+ documentId,
152
+ transactionId,
153
+ message: `You do not have permission to edit document "${documentId}".`,
154
+ })
155
+ }
156
+ workingMutations.push(...patches.map((patch) => ({patch: {id: patchDocumentId, ...patch}})))
157
+
158
+ working = processMutations({
159
+ documents: working,
160
+ transactionId,
161
+ mutations: workingMutations,
162
+ timestamp,
163
+ })
164
+
165
+ outgoingMutations.push(...workingMutations)
166
+ outgoingActions.push(
167
+ ...patches.map((patch) => ({
168
+ actionType: 'sanity.action.document.edit' as const,
169
+ draftId: patchDocumentId,
170
+ publishedId,
171
+ patch: patch as PatchOperations,
172
+ })),
173
+ )
174
+
175
+ return {base, working}
176
+ }
@@ -0,0 +1,168 @@
1
+ import {type Mutation} from '@sanity/types'
2
+ import {type ExprNode} from 'groq-js'
3
+
4
+ import {type DocumentAction} from '../actions'
5
+ import {type Grant} from '../permissions'
6
+ import {type DocumentSet} from '../processMutations'
7
+ import {type HttpAction} from '../reducers'
8
+ import {handleCreate} from './create'
9
+ import {handleDelete} from './delete'
10
+ import {handleDiscard} from './discard'
11
+ import {handleEdit} from './edit'
12
+ import {handlePublish} from './publish'
13
+ import {
14
+ ActionError,
15
+ type ActionHandlerContext,
16
+ type ActionHandlerResult,
17
+ PermissionActionError,
18
+ } from './shared'
19
+ import {handleUnpublish} from './unpublish'
20
+
21
+ export {ActionError, PermissionActionError}
22
+
23
+ interface ProcessActionsOptions {
24
+ /**
25
+ * The ID of this transaction. This will become the resulting `_rev` for all
26
+ * documents affected by changes derived from the current set of actions.
27
+ */
28
+ transactionId: string
29
+
30
+ /**
31
+ * The actions to apply to the given documents
32
+ */
33
+ actions: DocumentAction[]
34
+
35
+ /**
36
+ * The set of documents these actions were intended to be applied to. These
37
+ * set of documents should be captured right before a queued action is
38
+ * applied.
39
+ */
40
+ base: DocumentSet
41
+
42
+ /**
43
+ * The current "working" set of documents. A patch will be created by applying
44
+ * the actions to the base. This patch will then be applied to the working
45
+ * set for conflict resolution. Initially, this value should match the base
46
+ * set.
47
+ */
48
+ working: DocumentSet
49
+
50
+ /**
51
+ * The timestamp to use for `_updateAt` and other similar timestamps for this
52
+ * transaction
53
+ */
54
+ timestamp: string
55
+
56
+ /**
57
+ * the lookup with pre-parsed GROQ expressions
58
+ */
59
+ grants: Record<Grant, ExprNode>
60
+
61
+ // // TODO: implement initial values from the schema?
62
+ // initialValues?: {[TDocumentType in string]?: {_type: string}}
63
+ }
64
+
65
+ interface ProcessActionsResult {
66
+ /**
67
+ * The resulting document set after the actions have been applied. This is
68
+ * derived from the working documents.
69
+ */
70
+ working: DocumentSet
71
+ /**
72
+ * The document set before the actions have been applied. This is simply the
73
+ * input of the `working` document set.
74
+ */
75
+ previous: DocumentSet
76
+ /**
77
+ * The outgoing action that were collected when applying the actions. These
78
+ * are sent to the Actions HTTP API
79
+ */
80
+ outgoingActions: HttpAction[]
81
+ /**
82
+ * The outgoing mutations that were collected when applying the actions. These
83
+ * are here for debugging purposes.
84
+ */
85
+ outgoingMutations: Mutation[]
86
+ /**
87
+ * The previous revisions of the given documents before the actions were applied.
88
+ */
89
+ previousRevs: {[TDocumentId in string]?: string}
90
+ }
91
+
92
+ /**
93
+ * Applies the given set of actions to the working set of documents and converts
94
+ * high-level actions into lower-level outgoing mutations/actions that respect
95
+ * the current state of the working documents.
96
+ *
97
+ * Supports a "base" and "working" set of documents to allow actions to be
98
+ * applied on top of a different working set of documents in a 3-way merge
99
+ *
100
+ * Actions are applied to the base set of documents first. The difference
101
+ * between the base before and after is used to create a patch. This patch is
102
+ * then applied to the working set of documents and is set as the outgoing patch
103
+ * sent to the server.
104
+ */
105
+ export function processActions({
106
+ actions,
107
+ transactionId,
108
+ working: initialWorking,
109
+ base: initialBase,
110
+ timestamp,
111
+ grants,
112
+ }: ProcessActionsOptions): ProcessActionsResult {
113
+ let base: DocumentSet = {...initialBase}
114
+ let working: DocumentSet = {...initialWorking}
115
+
116
+ const outgoingActions: HttpAction[] = []
117
+ const outgoingMutations: Mutation[] = []
118
+
119
+ for (const action of actions) {
120
+ const result = dispatch(action, {
121
+ base,
122
+ working,
123
+ transactionId,
124
+ timestamp,
125
+ grants,
126
+ outgoingActions,
127
+ outgoingMutations,
128
+ })
129
+ base = result.base
130
+ working = result.working
131
+ }
132
+
133
+ const previousRevs = Object.fromEntries(
134
+ Object.entries(initialWorking).map(([id, doc]) => [id, doc?._rev]),
135
+ )
136
+
137
+ return {
138
+ working,
139
+ outgoingActions,
140
+ outgoingMutations,
141
+ previous: initialWorking,
142
+ previousRevs,
143
+ }
144
+ }
145
+
146
+ function dispatch(action: DocumentAction, ctx: ActionHandlerContext): ActionHandlerResult {
147
+ switch (action.type) {
148
+ case 'document.create':
149
+ return handleCreate(action, ctx)
150
+ case 'document.delete':
151
+ return handleDelete(action, ctx)
152
+ case 'document.discard':
153
+ return handleDiscard(action, ctx)
154
+ case 'document.edit':
155
+ return handleEdit(action, ctx)
156
+ case 'document.publish':
157
+ return handlePublish(action, ctx)
158
+ case 'document.unpublish':
159
+ return handleUnpublish(action, ctx)
160
+ default:
161
+ throw new Error(
162
+ `Unknown action type: "${
163
+ // @ts-expect-error invalid input
164
+ action.type
165
+ }". Please contact support if this issue persists.`,
166
+ )
167
+ }
168
+ }