@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.
- package/dist/_chunks-dts/utils.d.ts +200 -28
- package/dist/_chunks-es/_internal.js +3 -14
- package/dist/_chunks-es/_internal.js.map +1 -1
- package/dist/_chunks-es/createGroqSearchFilter.js +17 -19
- package/dist/_chunks-es/createGroqSearchFilter.js.map +1 -1
- package/dist/_chunks-es/version.js +1 -1
- package/dist/_exports/_internal.d.ts +16 -2
- package/dist/_exports/_internal.js +3 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +564 -459
- package/dist/index.js.map +1 -1
- package/package.json +16 -18
- package/src/_exports/_internal.ts +1 -0
- package/src/_exports/index.ts +25 -2
- package/src/agent/agentActions.ts +21 -25
- package/src/auth/refreshStampedToken.test.ts +2 -2
- package/src/auth/subscribeToStateAndFetchCurrentUser.test.ts +116 -0
- package/src/auth/subscribeToStateAndFetchCurrentUser.ts +27 -9
- package/src/client/clientStore.test.ts +10 -46
- package/src/client/clientStore.ts +7 -14
- package/src/comlink/node/actions/getOrCreateNode.test.ts +5 -2
- package/src/comlink/node/actions/releaseNode.test.ts +3 -3
- package/src/config/sanityConfig.ts +0 -1
- package/src/document/documentStore.ts +3 -8
- package/src/document/permissions.ts +1 -1
- package/src/document/processActions/create.ts +135 -0
- package/src/document/processActions/delete.ts +100 -0
- package/src/document/processActions/discard.ts +63 -0
- package/src/document/processActions/edit.ts +176 -0
- package/src/document/processActions/processActions.ts +168 -0
- package/src/document/processActions/publish.ts +120 -0
- package/src/document/processActions/shared.ts +47 -0
- package/src/document/processActions/unpublish.ts +85 -0
- package/src/document/processActions.test.ts +1 -1
- package/src/document/reducers.ts +1 -1
- package/src/document/sharedListener.ts +3 -5
- package/src/organization/organization.test-d.ts +102 -0
- package/src/organization/organization.test.ts +138 -0
- package/src/organization/organization.ts +166 -0
- package/src/organizations/organizations.test-d.ts +77 -0
- package/src/organizations/organizations.test.ts +150 -0
- package/src/organizations/organizations.ts +132 -0
- package/src/presence/presenceStore.test.ts +5 -5
- package/src/preview/previewProjectionUtils.ts +2 -3
- package/src/project/project.test-d.ts +93 -0
- package/src/project/project.test.ts +108 -10
- package/src/project/project.ts +152 -26
- package/src/projection/subscribeToStateAndFetchBatches.ts +4 -9
- package/src/projects/projects.test-d.ts +38 -0
- package/src/projects/projects.test.ts +104 -38
- package/src/projects/projects.ts +74 -14
- package/src/query/queryStore.ts +2 -3
- package/src/releases/releasesStore.test.ts +1 -1
- package/src/releases/releasesStore.ts +2 -2
- package/src/store/createSanityInstance.ts +3 -3
- package/src/telemetry/devMode.test.ts +8 -0
- package/src/telemetry/devMode.ts +10 -9
- package/src/telemetry/initTelemetry.test.ts +0 -17
- package/src/telemetry/initTelemetry.ts +2 -12
- package/src/document/processActions.ts +0 -735
|
@@ -1,735 +0,0 @@
|
|
|
1
|
-
import {diffValue} from '@sanity/diff-patch'
|
|
2
|
-
import {DocumentId, getDraftId, getPublishedId, getVersionId} from '@sanity/id-utils'
|
|
3
|
-
import {
|
|
4
|
-
type Mutation,
|
|
5
|
-
type PatchOperations,
|
|
6
|
-
type Reference,
|
|
7
|
-
type SanityDocument,
|
|
8
|
-
} from '@sanity/types'
|
|
9
|
-
import {evaluateSync, type ExprNode} from 'groq-js'
|
|
10
|
-
|
|
11
|
-
import {isReleasePerspective} from '../releases/utils/isReleasePerspective'
|
|
12
|
-
import {isDeepEqual} from '../utils/object'
|
|
13
|
-
import {type DocumentAction} from './actions'
|
|
14
|
-
import {type Grant} from './permissions'
|
|
15
|
-
import {type DocumentSet, getId, processMutations} from './processMutations'
|
|
16
|
-
import {type HttpAction} from './reducers'
|
|
17
|
-
|
|
18
|
-
function checkGrant(grantExpr: ExprNode, document: SanityDocument): boolean {
|
|
19
|
-
const value = evaluateSync(grantExpr, {params: {document}})
|
|
20
|
-
return value.type === 'boolean' && value.data
|
|
21
|
-
}
|
|
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
|
-
interface ActionErrorOptions {
|
|
93
|
-
message: string
|
|
94
|
-
documentId: string
|
|
95
|
-
transactionId: string
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Thrown when a precondition for an action failed.
|
|
100
|
-
*/
|
|
101
|
-
export class ActionError extends Error implements ActionErrorOptions {
|
|
102
|
-
documentId!: string
|
|
103
|
-
transactionId!: string
|
|
104
|
-
|
|
105
|
-
constructor(options: ActionErrorOptions) {
|
|
106
|
-
super(options.message)
|
|
107
|
-
Object.assign(this, options)
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
export class PermissionActionError extends ActionError {}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Applies the given set of actions to the working set of documents and converts
|
|
115
|
-
* high-level actions into lower-level outgoing mutations/actions that respect
|
|
116
|
-
* the current state of the working documents.
|
|
117
|
-
*
|
|
118
|
-
* Supports a "base" and "working" set of documents to allow actions to be
|
|
119
|
-
* applied on top of a different working set of documents in a 3-way merge
|
|
120
|
-
*
|
|
121
|
-
* Actions are applied to the base set of documents first. The difference
|
|
122
|
-
* between the base before and after is used to create a patch. This patch is
|
|
123
|
-
* then applied to the working set of documents and is set as the outgoing patch
|
|
124
|
-
* sent to the server.
|
|
125
|
-
*/
|
|
126
|
-
export function processActions({
|
|
127
|
-
actions,
|
|
128
|
-
transactionId,
|
|
129
|
-
working: initialWorking,
|
|
130
|
-
base: initialBase,
|
|
131
|
-
timestamp,
|
|
132
|
-
grants,
|
|
133
|
-
}: ProcessActionsOptions): ProcessActionsResult {
|
|
134
|
-
let working: DocumentSet = {...initialWorking}
|
|
135
|
-
let base: DocumentSet = {...initialBase}
|
|
136
|
-
|
|
137
|
-
const outgoingActions: HttpAction[] = []
|
|
138
|
-
const outgoingMutations: Mutation[] = []
|
|
139
|
-
|
|
140
|
-
for (const action of actions) {
|
|
141
|
-
switch (action.type) {
|
|
142
|
-
case 'document.create': {
|
|
143
|
-
const documentId = getId(action.documentId)
|
|
144
|
-
|
|
145
|
-
if (action.liveEdit) {
|
|
146
|
-
// For liveEdit documents, create directly without draft/published logic
|
|
147
|
-
if (working[documentId]) {
|
|
148
|
-
throw new ActionError({
|
|
149
|
-
documentId,
|
|
150
|
-
transactionId,
|
|
151
|
-
message: `This document already exists.`,
|
|
152
|
-
})
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
const newDocBase = {_type: action.documentType, _id: documentId, ...action.initialValue}
|
|
156
|
-
const newDocWorking = {
|
|
157
|
-
_type: action.documentType,
|
|
158
|
-
_id: documentId,
|
|
159
|
-
...action.initialValue,
|
|
160
|
-
}
|
|
161
|
-
const mutations: Mutation[] = [{create: newDocWorking}]
|
|
162
|
-
|
|
163
|
-
base = processMutations({
|
|
164
|
-
documents: base,
|
|
165
|
-
transactionId,
|
|
166
|
-
mutations: [{create: newDocBase}],
|
|
167
|
-
timestamp,
|
|
168
|
-
})
|
|
169
|
-
working = processMutations({
|
|
170
|
-
documents: working,
|
|
171
|
-
transactionId,
|
|
172
|
-
mutations,
|
|
173
|
-
timestamp,
|
|
174
|
-
})
|
|
175
|
-
|
|
176
|
-
if (!checkGrant(grants.create, working[documentId] as SanityDocument)) {
|
|
177
|
-
throw new PermissionActionError({
|
|
178
|
-
documentId,
|
|
179
|
-
transactionId,
|
|
180
|
-
message: `You do not have permission to create document "${documentId}".`,
|
|
181
|
-
})
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// liveEdit documents use the mutation endpoint directly -- we don't send actions
|
|
185
|
-
outgoingMutations.push(...mutations)
|
|
186
|
-
continue
|
|
187
|
-
}
|
|
188
|
-
|
|
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]
|
|
197
|
-
|
|
198
|
-
if (alreadyHasVersion) {
|
|
199
|
-
const errorDocType = versionId ? 'release version' : 'draft'
|
|
200
|
-
throw new ActionError({
|
|
201
|
-
documentId,
|
|
202
|
-
transactionId,
|
|
203
|
-
message: `A ${errorDocType} of this document already exists. Please use or discard the existing ${errorDocType} before creating a new one.`,
|
|
204
|
-
})
|
|
205
|
-
}
|
|
206
|
-
|
|
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)
|
|
209
|
-
const newDocBase = {
|
|
210
|
-
...(base[draftId] ?? base[publishedId]),
|
|
211
|
-
_type: action.documentType,
|
|
212
|
-
_id: versionId ?? draftId,
|
|
213
|
-
...action.initialValue,
|
|
214
|
-
}
|
|
215
|
-
const newDocWorking = {
|
|
216
|
-
...(working[draftId] ?? working[publishedId]),
|
|
217
|
-
_type: action.documentType,
|
|
218
|
-
_id: versionId ?? draftId,
|
|
219
|
-
...action.initialValue,
|
|
220
|
-
}
|
|
221
|
-
const mutations: Mutation[] = [{create: newDocWorking}]
|
|
222
|
-
|
|
223
|
-
base = processMutations({
|
|
224
|
-
documents: base,
|
|
225
|
-
transactionId,
|
|
226
|
-
mutations: [{create: newDocBase}],
|
|
227
|
-
timestamp,
|
|
228
|
-
})
|
|
229
|
-
working = processMutations({
|
|
230
|
-
documents: working,
|
|
231
|
-
transactionId,
|
|
232
|
-
mutations,
|
|
233
|
-
timestamp,
|
|
234
|
-
})
|
|
235
|
-
|
|
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)) {
|
|
243
|
-
throw new PermissionActionError({
|
|
244
|
-
documentId,
|
|
245
|
-
transactionId,
|
|
246
|
-
message: `You do not have permission to create a draft for document "${documentId}".`,
|
|
247
|
-
})
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
outgoingMutations.push(...mutations)
|
|
251
|
-
outgoingActions.push({
|
|
252
|
-
actionType: 'sanity.action.document.version.create',
|
|
253
|
-
publishedId,
|
|
254
|
-
attributes: newDocWorking,
|
|
255
|
-
})
|
|
256
|
-
continue
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
case 'document.delete': {
|
|
260
|
-
const documentId = action.documentId
|
|
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
|
-
|
|
270
|
-
if (action.liveEdit) {
|
|
271
|
-
if (!working[documentId]) {
|
|
272
|
-
throw new ActionError({
|
|
273
|
-
documentId,
|
|
274
|
-
transactionId,
|
|
275
|
-
message: 'The document you are trying to delete does not exist.',
|
|
276
|
-
})
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
if (!checkGrant(grants.update, working[documentId])) {
|
|
280
|
-
throw new PermissionActionError({
|
|
281
|
-
documentId,
|
|
282
|
-
transactionId,
|
|
283
|
-
message: `You do not have permission to delete this document.`,
|
|
284
|
-
})
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
const mutations: Mutation[] = [{delete: {id: documentId}}]
|
|
288
|
-
|
|
289
|
-
base = processMutations({documents: base, transactionId, mutations, timestamp})
|
|
290
|
-
working = processMutations({documents: working, transactionId, mutations, timestamp})
|
|
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)
|
|
295
|
-
outgoingMutations.push(...mutations)
|
|
296
|
-
continue
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
// Standard draft/published logic
|
|
300
|
-
const draftId = getDraftId(DocumentId(documentId))
|
|
301
|
-
const publishedId = getPublishedId(DocumentId(documentId))
|
|
302
|
-
|
|
303
|
-
if (!working[publishedId]) {
|
|
304
|
-
throw new ActionError({
|
|
305
|
-
documentId,
|
|
306
|
-
transactionId,
|
|
307
|
-
message: working[draftId]
|
|
308
|
-
? 'Cannot delete a document without a published version.'
|
|
309
|
-
: 'The document you are trying to delete does not exist.',
|
|
310
|
-
})
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
const cantDeleteDraft = working[draftId] && !checkGrant(grants.update, working[draftId])
|
|
314
|
-
const cantDeletePublished =
|
|
315
|
-
working[publishedId] && !checkGrant(grants.update, working[publishedId])
|
|
316
|
-
|
|
317
|
-
if (cantDeleteDraft || cantDeletePublished) {
|
|
318
|
-
throw new PermissionActionError({
|
|
319
|
-
documentId,
|
|
320
|
-
transactionId,
|
|
321
|
-
message: `You do not have permission to delete this document.`,
|
|
322
|
-
})
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
const mutations: Mutation[] = [{delete: {id: publishedId}}, {delete: {id: draftId}}]
|
|
326
|
-
const includeDrafts = working[draftId] ? [draftId] : undefined
|
|
327
|
-
|
|
328
|
-
base = processMutations({documents: base, transactionId, mutations, timestamp})
|
|
329
|
-
working = processMutations({documents: working, transactionId, mutations, timestamp})
|
|
330
|
-
|
|
331
|
-
outgoingMutations.push(...mutations)
|
|
332
|
-
outgoingActions.push({
|
|
333
|
-
actionType: 'sanity.action.document.delete',
|
|
334
|
-
publishedId,
|
|
335
|
-
...(includeDrafts ? {includeDrafts} : {}),
|
|
336
|
-
})
|
|
337
|
-
continue
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
case 'document.discard': {
|
|
341
|
-
const documentId = getId(action.documentId)
|
|
342
|
-
|
|
343
|
-
if (action.liveEdit) {
|
|
344
|
-
throw new ActionError({
|
|
345
|
-
documentId,
|
|
346
|
-
transactionId,
|
|
347
|
-
message: `Cannot discard changes for liveEdit document "${documentId}". LiveEdit documents do not support drafts.`,
|
|
348
|
-
})
|
|
349
|
-
}
|
|
350
|
-
|
|
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}}]
|
|
356
|
-
|
|
357
|
-
if (!working[versionId]) {
|
|
358
|
-
throw new ActionError({
|
|
359
|
-
documentId,
|
|
360
|
-
transactionId,
|
|
361
|
-
message: `There is no draft or version available to discard for document "${documentId}".`,
|
|
362
|
-
})
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
if (!checkGrant(grants.update, working[versionId])) {
|
|
366
|
-
throw new PermissionActionError({
|
|
367
|
-
documentId,
|
|
368
|
-
transactionId,
|
|
369
|
-
message: `You do not have permission to discard changes for document "${documentId}".`,
|
|
370
|
-
})
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
base = processMutations({documents: base, transactionId, mutations, timestamp})
|
|
374
|
-
working = processMutations({documents: working, transactionId, mutations, timestamp})
|
|
375
|
-
|
|
376
|
-
outgoingMutations.push(...mutations)
|
|
377
|
-
outgoingActions.push({
|
|
378
|
-
actionType: 'sanity.action.document.version.discard',
|
|
379
|
-
versionId,
|
|
380
|
-
})
|
|
381
|
-
continue
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
case 'document.edit': {
|
|
385
|
-
const documentId = getId(action.documentId)
|
|
386
|
-
|
|
387
|
-
if (action.liveEdit) {
|
|
388
|
-
// Single-document mode (liveEdit or release perspective): edit directly without draft logic
|
|
389
|
-
const userPatches = action.patches?.map((patch) => ({patch: {id: documentId, ...patch}}))
|
|
390
|
-
|
|
391
|
-
// skip this action if there are no associated patches
|
|
392
|
-
if (!userPatches?.length) continue
|
|
393
|
-
|
|
394
|
-
if (!working[documentId] || !base[documentId]) {
|
|
395
|
-
throw new ActionError({
|
|
396
|
-
documentId,
|
|
397
|
-
transactionId,
|
|
398
|
-
message: `Cannot edit document because it does not exist.`,
|
|
399
|
-
})
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
const baseBefore = base[documentId] as SanityDocument
|
|
403
|
-
if (userPatches) {
|
|
404
|
-
base = processMutations({
|
|
405
|
-
documents: base,
|
|
406
|
-
transactionId,
|
|
407
|
-
mutations: userPatches,
|
|
408
|
-
timestamp,
|
|
409
|
-
})
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
const baseAfter = base[documentId] as SanityDocument
|
|
413
|
-
const patches = diffValue(baseBefore, baseAfter)
|
|
414
|
-
|
|
415
|
-
const workingBefore = working[documentId] as SanityDocument
|
|
416
|
-
if (!checkGrant(grants.update, workingBefore)) {
|
|
417
|
-
throw new PermissionActionError({
|
|
418
|
-
documentId,
|
|
419
|
-
transactionId,
|
|
420
|
-
message: `You do not have permission to edit document "${documentId}".`,
|
|
421
|
-
})
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
const workingMutations = patches.map((patch) => ({patch: {id: documentId, ...patch}}))
|
|
425
|
-
|
|
426
|
-
working = processMutations({
|
|
427
|
-
documents: working,
|
|
428
|
-
transactionId,
|
|
429
|
-
mutations: workingMutations,
|
|
430
|
-
timestamp,
|
|
431
|
-
})
|
|
432
|
-
|
|
433
|
-
// liveEdit documents use the mutation endpoint directly -- we don't send actions
|
|
434
|
-
outgoingMutations.push(...workingMutations)
|
|
435
|
-
continue
|
|
436
|
-
}
|
|
437
|
-
|
|
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
|
-
}))
|
|
447
|
-
|
|
448
|
-
// skip this action if there are no associated patches
|
|
449
|
-
if (!userPatches?.length) continue
|
|
450
|
-
|
|
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 (
|
|
460
|
-
(!working[draftId] && !working[publishedId]) ||
|
|
461
|
-
(!base[draftId] && !base[publishedId])
|
|
462
|
-
) {
|
|
463
|
-
throw new ActionError({
|
|
464
|
-
documentId,
|
|
465
|
-
transactionId,
|
|
466
|
-
message: `Cannot edit document because it does not exist in draft or published form.`,
|
|
467
|
-
})
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
const baseMutations: Mutation[] = []
|
|
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
|
|
474
|
-
baseMutations.push({create: {...base[publishedId], _id: draftId}})
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
// the above statement and guards should make this never be null or undefined
|
|
478
|
-
const baseBefore = base[patchDocumentId] ?? base[publishedId]
|
|
479
|
-
if (userPatches) {
|
|
480
|
-
baseMutations.push(...userPatches)
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
base = processMutations({
|
|
484
|
-
documents: base,
|
|
485
|
-
transactionId,
|
|
486
|
-
mutations: baseMutations,
|
|
487
|
-
timestamp,
|
|
488
|
-
})
|
|
489
|
-
// this one will always be defined because a patch mutation will never
|
|
490
|
-
// delete an input document
|
|
491
|
-
const baseAfter = base[patchDocumentId] as SanityDocument
|
|
492
|
-
const patches = diffValue(baseBefore, baseAfter)
|
|
493
|
-
|
|
494
|
-
const workingMutations: Mutation[] = []
|
|
495
|
-
if (
|
|
496
|
-
!isReleasePerspective(action.perspective) &&
|
|
497
|
-
!working[draftId] &&
|
|
498
|
-
working[publishedId]
|
|
499
|
-
) {
|
|
500
|
-
const newDraftFromPublished = {...working[publishedId], _id: draftId}
|
|
501
|
-
|
|
502
|
-
if (!checkGrant(grants.create, newDraftFromPublished)) {
|
|
503
|
-
throw new PermissionActionError({
|
|
504
|
-
documentId,
|
|
505
|
-
transactionId,
|
|
506
|
-
message: `You do not have permission to create a draft for editing this document.`,
|
|
507
|
-
})
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
workingMutations.push({create: newDraftFromPublished})
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
// the first if statement should make this never be null or undefined
|
|
514
|
-
const workingBefore = working[patchDocumentId] ?? working[publishedId]
|
|
515
|
-
if (!checkGrant(grants.update, workingBefore!)) {
|
|
516
|
-
throw new PermissionActionError({
|
|
517
|
-
documentId,
|
|
518
|
-
transactionId,
|
|
519
|
-
message: `You do not have permission to edit document "${documentId}".`,
|
|
520
|
-
})
|
|
521
|
-
}
|
|
522
|
-
workingMutations.push(...patches.map((patch) => ({patch: {id: patchDocumentId, ...patch}})))
|
|
523
|
-
|
|
524
|
-
working = processMutations({
|
|
525
|
-
documents: working,
|
|
526
|
-
transactionId,
|
|
527
|
-
mutations: workingMutations,
|
|
528
|
-
timestamp,
|
|
529
|
-
})
|
|
530
|
-
|
|
531
|
-
outgoingMutations.push(...workingMutations)
|
|
532
|
-
outgoingActions.push(
|
|
533
|
-
...patches.map((patch) => ({
|
|
534
|
-
actionType: 'sanity.action.document.edit' as const,
|
|
535
|
-
draftId: patchDocumentId,
|
|
536
|
-
publishedId,
|
|
537
|
-
patch: patch as PatchOperations,
|
|
538
|
-
})),
|
|
539
|
-
)
|
|
540
|
-
|
|
541
|
-
continue
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
case 'document.publish': {
|
|
545
|
-
const documentId = getId(action.documentId)
|
|
546
|
-
|
|
547
|
-
if (action.liveEdit || isReleasePerspective(action.perspective)) {
|
|
548
|
-
throw new ActionError({
|
|
549
|
-
documentId,
|
|
550
|
-
transactionId,
|
|
551
|
-
message: `Cannot publish this document. Publishing is not supported for liveEdit or version (release) documents.`,
|
|
552
|
-
})
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
// Standard draft/published logic
|
|
556
|
-
const draftId = getDraftId(DocumentId(documentId))
|
|
557
|
-
const publishedId = getPublishedId(DocumentId(documentId))
|
|
558
|
-
|
|
559
|
-
const workingDraft = working[draftId]
|
|
560
|
-
const baseDraft = base[draftId]
|
|
561
|
-
if (!workingDraft || !baseDraft) {
|
|
562
|
-
throw new ActionError({
|
|
563
|
-
documentId,
|
|
564
|
-
transactionId,
|
|
565
|
-
message: `Cannot publish because no draft version was found for document "${documentId}".`,
|
|
566
|
-
})
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
// Before proceeding, verify that the working draft is identical to the base draft.
|
|
570
|
-
// TODO: is it enough just to check for the _rev or nah?
|
|
571
|
-
if (!isDeepEqual(workingDraft, baseDraft)) {
|
|
572
|
-
throw new ActionError({
|
|
573
|
-
documentId,
|
|
574
|
-
transactionId,
|
|
575
|
-
message: `Publish aborted: The document has changed elsewhere. Please try again.`,
|
|
576
|
-
})
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
const newPublishedFromDraft = {...strengthenOnPublish(workingDraft), _id: publishedId}
|
|
580
|
-
|
|
581
|
-
const mutations: Mutation[] = [
|
|
582
|
-
{delete: {id: draftId}},
|
|
583
|
-
{createOrReplace: newPublishedFromDraft},
|
|
584
|
-
]
|
|
585
|
-
|
|
586
|
-
if (working[draftId] && !checkGrant(grants.update, working[draftId])) {
|
|
587
|
-
throw new PermissionActionError({
|
|
588
|
-
documentId,
|
|
589
|
-
transactionId,
|
|
590
|
-
message: `Publish failed: You do not have permission to update the draft for "${documentId}".`,
|
|
591
|
-
})
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
if (working[publishedId] && !checkGrant(grants.update, newPublishedFromDraft)) {
|
|
595
|
-
throw new PermissionActionError({
|
|
596
|
-
documentId,
|
|
597
|
-
transactionId,
|
|
598
|
-
message: `Publish failed: You do not have permission to update the published version of "${documentId}".`,
|
|
599
|
-
})
|
|
600
|
-
} else if (!working[publishedId] && !checkGrant(grants.create, newPublishedFromDraft)) {
|
|
601
|
-
throw new PermissionActionError({
|
|
602
|
-
documentId,
|
|
603
|
-
transactionId,
|
|
604
|
-
message: `Publish failed: You do not have permission to publish a new version of "${documentId}".`,
|
|
605
|
-
})
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
base = processMutations({documents: base, transactionId, mutations, timestamp})
|
|
609
|
-
working = processMutations({documents: working, transactionId, mutations, timestamp})
|
|
610
|
-
|
|
611
|
-
outgoingMutations.push(...mutations)
|
|
612
|
-
outgoingActions.push({
|
|
613
|
-
actionType: 'sanity.action.document.publish',
|
|
614
|
-
draftId,
|
|
615
|
-
publishedId,
|
|
616
|
-
})
|
|
617
|
-
continue
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
case 'document.unpublish': {
|
|
621
|
-
const documentId = getId(action.documentId)
|
|
622
|
-
|
|
623
|
-
if (action.liveEdit || isReleasePerspective(action.perspective)) {
|
|
624
|
-
throw new ActionError({
|
|
625
|
-
documentId,
|
|
626
|
-
transactionId,
|
|
627
|
-
message: `Cannot unpublish this document. Unpublishing is not supported for liveEdit or version (release) documents.`,
|
|
628
|
-
})
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
// Standard draft/published or version logic
|
|
632
|
-
const draftId = getDraftId(DocumentId(documentId))
|
|
633
|
-
const publishedId = getPublishedId(DocumentId(documentId))
|
|
634
|
-
|
|
635
|
-
if (!working[publishedId] && !base[publishedId]) {
|
|
636
|
-
throw new ActionError({
|
|
637
|
-
documentId,
|
|
638
|
-
transactionId,
|
|
639
|
-
message: `Cannot unpublish because the document "${documentId}" is not currently published.`,
|
|
640
|
-
})
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
const sourceDoc = working[publishedId] ?? (base[publishedId] as SanityDocument)
|
|
644
|
-
const newDraftFromPublished = {...sourceDoc, _id: draftId}
|
|
645
|
-
const mutations: Mutation[] = [
|
|
646
|
-
{delete: {id: publishedId}},
|
|
647
|
-
{createIfNotExists: newDraftFromPublished},
|
|
648
|
-
]
|
|
649
|
-
|
|
650
|
-
if (!checkGrant(grants.update, sourceDoc)) {
|
|
651
|
-
throw new PermissionActionError({
|
|
652
|
-
documentId,
|
|
653
|
-
transactionId,
|
|
654
|
-
message: `You do not have permission to unpublish the document "${documentId}".`,
|
|
655
|
-
})
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
if (!working[draftId] && !checkGrant(grants.create, newDraftFromPublished)) {
|
|
659
|
-
throw new PermissionActionError({
|
|
660
|
-
documentId,
|
|
661
|
-
transactionId,
|
|
662
|
-
message: `You do not have permission to create a draft from the published version of "${documentId}".`,
|
|
663
|
-
})
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
base = processMutations({
|
|
667
|
-
documents: base,
|
|
668
|
-
transactionId,
|
|
669
|
-
mutations: [
|
|
670
|
-
{delete: {id: publishedId}},
|
|
671
|
-
{createIfNotExists: {...(base[publishedId] ?? sourceDoc), _id: draftId}},
|
|
672
|
-
],
|
|
673
|
-
timestamp,
|
|
674
|
-
})
|
|
675
|
-
working = processMutations({documents: working, transactionId, mutations, timestamp})
|
|
676
|
-
|
|
677
|
-
outgoingMutations.push(...mutations)
|
|
678
|
-
outgoingActions.push({
|
|
679
|
-
actionType: 'sanity.action.document.unpublish',
|
|
680
|
-
draftId,
|
|
681
|
-
publishedId,
|
|
682
|
-
})
|
|
683
|
-
continue
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
default: {
|
|
687
|
-
throw new Error(
|
|
688
|
-
`Unknown action type: "${
|
|
689
|
-
// @ts-expect-error invalid input
|
|
690
|
-
action.type
|
|
691
|
-
}". Please contact support if this issue persists.`,
|
|
692
|
-
)
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
const previousRevs = Object.fromEntries(
|
|
698
|
-
Object.entries(initialWorking).map(([id, doc]) => [id, doc?._rev]),
|
|
699
|
-
)
|
|
700
|
-
|
|
701
|
-
return {
|
|
702
|
-
working,
|
|
703
|
-
outgoingActions,
|
|
704
|
-
outgoingMutations,
|
|
705
|
-
previous: initialWorking,
|
|
706
|
-
previousRevs,
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
function strengthenOnPublish(draft: SanityDocument): SanityDocument {
|
|
711
|
-
const isStrengthenReference = (
|
|
712
|
-
value: object,
|
|
713
|
-
): value is Reference & Required<Pick<Reference, '_strengthenOnPublish'>> =>
|
|
714
|
-
'_strengthenOnPublish' in value
|
|
715
|
-
|
|
716
|
-
function strengthen(value: unknown): unknown {
|
|
717
|
-
if (typeof value !== 'object' || !value) return value
|
|
718
|
-
|
|
719
|
-
if (isStrengthenReference(value)) {
|
|
720
|
-
const {_strengthenOnPublish, _weak, ...rest} = value
|
|
721
|
-
return {
|
|
722
|
-
...rest,
|
|
723
|
-
...(_strengthenOnPublish.weak && {_weak: true}),
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
if (Array.isArray(value)) {
|
|
728
|
-
return value.map(strengthen)
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, strengthen(v)]))
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
return strengthen(draft) as SanityDocument
|
|
735
|
-
}
|