@sanity/sdk 3.0.0-rc.0 → 3.0.0-rc.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/createGroqSearchFilter.d.ts +1147 -0
- package/dist/_chunks-dts/createGroqSearchFilter.d.ts.map +1 -0
- package/dist/_chunks-es/_internal.js +134 -0
- package/dist/_chunks-es/_internal.js.map +1 -0
- package/dist/_chunks-es/createGroqSearchFilter.js +1864 -0
- package/dist/_chunks-es/createGroqSearchFilter.js.map +1 -0
- package/dist/_chunks-es/telemetryManager.js +93 -0
- package/dist/_chunks-es/telemetryManager.js.map +1 -0
- package/dist/_exports/_internal.d.ts +95 -0
- package/dist/_exports/_internal.d.ts.map +1 -0
- package/dist/_exports/_internal.js +24 -0
- package/dist/_exports/_internal.js.map +1 -0
- package/dist/index.d.ts +1079 -1076
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1967 -2302
- package/dist/index.js.map +1 -1
- package/package.json +48 -47
- package/src/_exports/_internal.ts +26 -0
- package/src/_exports/index.ts +120 -11
- package/src/access/checkPermissions.test.ts +55 -0
- package/src/access/checkPermissions.ts +54 -0
- package/src/agent/agentActions.test.ts +16 -60
- package/src/agent/agentActions.ts +25 -38
- package/src/applications/applications.test-d.ts +83 -0
- package/src/applications/applications.test.ts +130 -0
- package/src/applications/applications.ts +364 -0
- package/src/applications/userApplications.test.ts +126 -0
- package/src/applications/userApplications.ts +261 -0
- package/src/auth/authLogger.ts +30 -0
- package/src/auth/authMode.test.ts +25 -0
- package/src/auth/authMode.ts +6 -3
- package/src/auth/authStore.test.ts +194 -13
- package/src/auth/authStore.ts +59 -26
- package/src/auth/getOrganizationVerificationState.test.ts +18 -17
- package/src/auth/getOrganizationVerificationState.ts +4 -4
- package/src/auth/handleAuthCallback.test.ts +35 -1
- package/src/auth/handleAuthCallback.ts +25 -6
- package/src/auth/logout.test.ts +75 -2
- package/src/auth/logout.ts +23 -4
- package/src/auth/refreshStampedToken.test.ts +219 -469
- package/src/auth/refreshStampedToken.ts +79 -195
- package/src/auth/studioAuth.ts +1 -1
- package/src/auth/studioModeAuth.ts +2 -1
- package/src/auth/subscribeToStateAndFetchCurrentUser.test.ts +146 -13
- package/src/auth/subscribeToStateAndFetchCurrentUser.ts +37 -6
- package/src/auth/subscribeToStorageEventsAndSetToken.test.ts +2 -0
- package/src/client/clientStore.test.ts +62 -37
- package/src/client/clientStore.ts +45 -75
- package/src/client/liveEvents.test.ts +151 -0
- package/src/client/liveEvents.ts +107 -0
- package/src/comlink/controller/actions/destroyController.test.ts +4 -1
- package/src/comlink/controller/actions/getOrCreateChannel.test.ts +4 -1
- package/src/comlink/controller/actions/getOrCreateChannel.ts +2 -2
- package/src/comlink/controller/actions/getOrCreateController.test.ts +4 -1
- package/src/comlink/controller/actions/releaseChannel.test.ts +1 -1
- package/src/comlink/controller/comlinkControllerStore.test.ts +4 -1
- package/src/comlink/node/actions/getOrCreateNode.test.ts +9 -3
- package/src/comlink/node/actions/getOrCreateNode.ts +2 -2
- package/src/comlink/node/actions/releaseNode.test.ts +7 -4
- package/src/comlink/node/comlinkNodeStore.test.ts +2 -2
- package/src/comlink/node/getNodeState.test.ts +1 -1
- package/src/comlink/node/getNodeState.ts +2 -1
- package/src/config/__tests__/handles.test.ts +18 -12
- package/src/config/handles.ts +25 -7
- package/src/config/sanityConfig.ts +109 -79
- package/src/datasets/datasets.test.ts +2 -2
- package/src/datasets/datasets.ts +10 -4
- package/src/document/actions.test.ts +115 -33
- package/src/document/actions.ts +212 -15
- package/src/document/applyDocumentActions.test.ts +38 -12
- package/src/document/applyDocumentActions.ts +30 -9
- package/src/document/documentConstants.ts +7 -0
- package/src/document/documentStore.concurrency.test.ts +1306 -0
- package/src/document/documentStore.test.ts +673 -278
- package/src/document/documentStore.ts +162 -71
- package/src/document/events.test.ts +57 -2
- package/src/document/events.ts +82 -26
- package/src/document/listen.test.ts +56 -0
- package/src/document/listen.ts +74 -21
- package/src/document/listenerEventOperators.test.ts +311 -0
- package/src/document/listenerEventOperators.ts +217 -0
- package/src/document/patchOperations.test.ts +49 -2
- package/src/document/patchOperations.ts +50 -0
- package/src/document/permissions.test.ts +105 -60
- package/src/document/permissions.ts +44 -19
- package/src/document/processActions/create.ts +138 -0
- package/src/document/processActions/delete.ts +100 -0
- package/src/document/processActions/discard.ts +63 -0
- package/src/document/processActions/edit.ts +154 -0
- package/src/document/processActions/processActions.ts +218 -0
- package/src/document/processActions/publish.ts +120 -0
- package/src/document/processActions/releaseArchive.ts +77 -0
- package/src/document/processActions/releaseCreate.ts +59 -0
- package/src/document/processActions/releaseDelete.ts +65 -0
- package/src/document/processActions/releaseEdit.ts +37 -0
- package/src/document/processActions/releasePublish.ts +45 -0
- package/src/document/processActions/releaseSchedule.ts +87 -0
- package/src/document/processActions/releaseUtil.ts +31 -0
- package/src/document/processActions/shared.ts +308 -0
- package/src/document/processActions/unpublish.ts +85 -0
- package/src/document/processActions.test.ts +1129 -121
- package/src/document/reducers.test.ts +639 -64
- package/src/document/reducers.ts +187 -48
- package/src/document/resourceRules.test.ts +178 -0
- package/src/document/resourceRules.ts +117 -0
- package/src/document/sharedListener.test.ts +16 -14
- package/src/document/sharedListener.ts +24 -8
- package/src/document/util.ts +14 -0
- package/src/favorites/favorites.test.ts +5 -6
- package/src/installations/installations.test-d.ts +68 -0
- package/src/installations/installations.test.ts +62 -0
- package/src/installations/installations.ts +202 -0
- package/src/organization/organization.test-d.ts +36 -0
- package/src/organization/organization.test.ts +138 -0
- package/src/organization/organization.ts +138 -0
- package/src/organizations/organizations.test-d.ts +56 -0
- package/src/organizations/organizations.test.ts +150 -0
- package/src/organizations/organizations.ts +107 -0
- package/src/presence/bifurTransport.test.ts +46 -6
- package/src/presence/bifurTransport.ts +19 -2
- package/src/presence/presenceStore.test.ts +94 -23
- package/src/presence/presenceStore.ts +85 -29
- package/src/preview/getPreviewState.test.ts +137 -0
- package/src/preview/getPreviewState.ts +69 -0
- package/src/preview/previewProjectionUtils.test.ts +9 -22
- package/src/preview/previewProjectionUtils.ts +7 -3
- package/src/preview/resolvePreview.test.ts +64 -0
- package/src/preview/resolvePreview.ts +43 -0
- package/src/preview/types.ts +10 -0
- package/src/project/project.test-d.ts +37 -0
- package/src/project/project.test.ts +157 -12
- package/src/project/project.ts +138 -25
- package/src/projection/getProjectionState.test.ts +18 -20
- package/src/projection/getProjectionState.ts +23 -3
- package/src/projection/projectionQuery.ts +5 -5
- package/src/projection/projectionStore.test.ts +1 -1
- package/src/projection/resolveProjection.test.ts +1 -2
- package/src/projection/resolveProjection.ts +16 -0
- package/src/projection/subscribeToStateAndFetchBatches.test.ts +1 -1
- package/src/projection/subscribeToStateAndFetchBatches.ts +32 -23
- package/src/projection/types.ts +8 -0
- package/src/projects/projects.test-d.ts +32 -0
- package/src/projects/projects.test.ts +108 -42
- package/src/projects/projects.ts +63 -27
- package/src/query/queryStore.test.ts +259 -123
- package/src/query/queryStore.ts +78 -63
- package/src/query/reducers.test.ts +0 -42
- package/src/query/reducers.ts +2 -18
- package/src/releases/getPerspectiveState.test.ts +17 -23
- package/src/releases/getPerspectiveState.ts +8 -20
- package/src/releases/observeReleases.test.ts +127 -0
- package/src/releases/observeReleases.ts +102 -0
- package/src/releases/releasesStore.test.ts +100 -53
- package/src/releases/releasesStore.ts +69 -44
- package/src/releases/utils/sortReleases.test.ts +38 -2
- package/src/releases/utils/sortReleases.ts +15 -10
- package/src/store/createActionBinder.test.ts +87 -90
- package/src/store/createActionBinder.ts +79 -25
- package/src/store/createSanityInstance.test.ts +83 -13
- package/src/store/createSanityInstance.ts +77 -2
- package/src/store/createStateSourceAction.test.ts +64 -2
- package/src/store/createStateSourceAction.ts +39 -44
- package/src/store/createStoreInstance.test.ts +4 -2
- package/src/store/fetcherStore.test-d.ts +121 -0
- package/src/store/fetcherStore.test.ts +299 -0
- package/src/store/fetcherStore.ts +801 -0
- package/src/telemetry/environment.test.ts +119 -0
- package/src/telemetry/environment.ts +92 -0
- package/src/telemetry/events.ts +42 -0
- package/src/telemetry/initTelemetry.test.ts +432 -0
- package/src/telemetry/initTelemetry.ts +218 -0
- package/src/telemetry/telemetryManager.test.ts +327 -0
- package/src/telemetry/telemetryManager.ts +199 -0
- package/src/users/reducers.test.ts +6 -1
- package/src/users/reducers.ts +5 -6
- package/src/users/types.ts +4 -4
- package/src/users/usersStore.test.ts +16 -12
- package/src/users/usersStore.ts +5 -1
- package/src/utils/buildQuery.test.ts +14 -0
- package/src/utils/buildQuery.ts +17 -0
- package/src/utils/createFetcherStore.test.ts +7 -5
- package/src/utils/createFetcherStore.ts +8 -5
- package/src/utils/getStagingApiHost.test.ts +21 -0
- package/src/utils/getStagingApiHost.ts +14 -0
- package/src/utils/ids.test.ts +1 -29
- package/src/utils/ids.ts +0 -10
- package/src/utils/includeShape.ts +36 -0
- package/src/utils/isImportError.test.ts +72 -0
- package/src/utils/isImportError.ts +34 -0
- package/src/utils/logger.test.ts +12 -0
- package/src/utils/logger.ts +8 -3
- package/src/utils/object.test.ts +95 -0
- package/src/utils/object.ts +142 -0
- package/src/utils/setCleanupTimeout.ts +37 -0
- package/src/document/processActions.ts +0 -707
- package/src/utils/defineIntent.test.ts +0 -477
- package/src/utils/defineIntent.ts +0 -244
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import {diffValue} from '@sanity/diff-patch'
|
|
2
|
+
import {jsonMatch, stringifyPath} from '@sanity/json-match'
|
|
3
|
+
import {type Mutation, type PatchOperations, type SanityDocument} from '@sanity/types'
|
|
4
|
+
import {evaluateSync, type ExprNode} from 'groq-js'
|
|
5
|
+
|
|
6
|
+
import {type Grant} from '../permissions'
|
|
7
|
+
import {type DocumentSet, processMutations} from '../processMutations'
|
|
8
|
+
import {type HttpAction} from '../reducers'
|
|
9
|
+
|
|
10
|
+
export interface ActionHandlerContext {
|
|
11
|
+
base: DocumentSet
|
|
12
|
+
working: DocumentSet
|
|
13
|
+
transactionId: string
|
|
14
|
+
timestamp: string
|
|
15
|
+
grants: Record<Grant, ExprNode>
|
|
16
|
+
/**
|
|
17
|
+
* The current user's ID, used by GROQ's `identity()` when evaluating ACL
|
|
18
|
+
* filters. May be `undefined` before the user has loaded; in that case
|
|
19
|
+
* `identity()` evaluates to null.
|
|
20
|
+
*/
|
|
21
|
+
identity: string | undefined
|
|
22
|
+
outgoingActions: HttpAction[]
|
|
23
|
+
outgoingMutations: Mutation[]
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface ActionHandlerResult {
|
|
27
|
+
base: DocumentSet
|
|
28
|
+
working: DocumentSet
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function checkGrant(
|
|
32
|
+
grantExpr: ExprNode,
|
|
33
|
+
document: SanityDocument,
|
|
34
|
+
identity: string | undefined,
|
|
35
|
+
): boolean {
|
|
36
|
+
const value = evaluateSync(grantExpr, {params: {document}, identity})
|
|
37
|
+
return value.type === 'boolean' && value.data
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface ActionErrorOptions {
|
|
41
|
+
message: string
|
|
42
|
+
documentId: string
|
|
43
|
+
transactionId: string
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Thrown when a precondition for an action failed.
|
|
48
|
+
*/
|
|
49
|
+
export class ActionError extends Error implements ActionErrorOptions {
|
|
50
|
+
documentId!: string
|
|
51
|
+
transactionId!: string
|
|
52
|
+
|
|
53
|
+
constructor(options: ActionErrorOptions) {
|
|
54
|
+
super(options.message)
|
|
55
|
+
Object.assign(this, options)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export class PermissionActionError extends ActionError {}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Creates a `processMutations` wrapper that surfaces application failures as
|
|
63
|
+
* `ActionError`s when the caller asked to preserve their patch operations.
|
|
64
|
+
* With preserved operations, patches can legitimately fail to apply (e.g.
|
|
65
|
+
* re-applied onto a diverged document during a rebase), so wrapping lets a
|
|
66
|
+
* rebase skip the transaction instead of failing the store. Without
|
|
67
|
+
* `preserveOperations`, errors are rethrown untouched.
|
|
68
|
+
*/
|
|
69
|
+
export function createMutationApplier(options: {
|
|
70
|
+
documentId: string
|
|
71
|
+
transactionId: string
|
|
72
|
+
timestamp: string
|
|
73
|
+
preserveOperations: boolean | undefined
|
|
74
|
+
}): (
|
|
75
|
+
documents: DocumentSet,
|
|
76
|
+
mutations: Mutation[],
|
|
77
|
+
documentSetName: 'base' | 'working',
|
|
78
|
+
) => DocumentSet {
|
|
79
|
+
const {documentId, transactionId, timestamp, preserveOperations} = options
|
|
80
|
+
return (documents, mutations, documentSetName) => {
|
|
81
|
+
try {
|
|
82
|
+
return processMutations({documents, transactionId, mutations, timestamp})
|
|
83
|
+
} catch (error) {
|
|
84
|
+
if (!preserveOperations) throw error
|
|
85
|
+
throw new ActionError({
|
|
86
|
+
documentId,
|
|
87
|
+
transactionId,
|
|
88
|
+
message: `Failed to apply patches to the ${documentSetName} document: ${
|
|
89
|
+
error instanceof Error ? error.message : 'Unknown error'
|
|
90
|
+
}`,
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Re-applies the operational intent of user-supplied `inc`/`dec` patches to a
|
|
98
|
+
* set of snapshot-diffed patches.
|
|
99
|
+
*
|
|
100
|
+
* The outgoing patches sent to Content Lake are re-derived by diffing the
|
|
101
|
+
* base document before and after the user's patches were applied. That diff
|
|
102
|
+
* collapses an `inc` into a `set` of the resulting number, which turns
|
|
103
|
+
* concurrent increments from different clients into last-write-wins. This
|
|
104
|
+
* helper finds diffed `set` operations that are exactly explained by the
|
|
105
|
+
* user's `inc`/`dec` operations and swaps them back, so the server applies
|
|
106
|
+
* the increment against whatever value is current at commit time.
|
|
107
|
+
*
|
|
108
|
+
* A `set` is only swapped when its value equals the base value plus the
|
|
109
|
+
* accumulated user deltas for that exact path; anything else (e.g. an `inc`
|
|
110
|
+
* combined with a `set` of the same field) keeps the diffed `set`.
|
|
111
|
+
*
|
|
112
|
+
* Content Lake fails the whole transaction when `inc`/`dec` targets a missing
|
|
113
|
+
* path (unlike `set`, which always succeeds). To avoid turning a concurrent
|
|
114
|
+
* field removal into a transaction revert, the emitted patch seeds the target
|
|
115
|
+
* with a `setIfMissing` of the base value; the server executes `setIfMissing`
|
|
116
|
+
* before `inc`/`dec` within a single patch, so a concurrently removed field
|
|
117
|
+
* degrades to the same result the diffed `set` would have produced. A path
|
|
118
|
+
* into a concurrently removed keyed array item can still fail the
|
|
119
|
+
* transaction (`setIfMissing` cannot create array items); the transaction is
|
|
120
|
+
* then reverted and reported, which is preferable to silently resurrecting
|
|
121
|
+
* the item.
|
|
122
|
+
*/
|
|
123
|
+
export function preserveNumericOperations(
|
|
124
|
+
baseBefore: SanityDocument | null | undefined,
|
|
125
|
+
userPatches: PatchOperations[] | undefined,
|
|
126
|
+
diffedPatches: PatchOperations[],
|
|
127
|
+
): PatchOperations[] {
|
|
128
|
+
if (!baseBefore || !userPatches?.length) return diffedPatches
|
|
129
|
+
|
|
130
|
+
// accumulate the user's inc/dec deltas per concrete (stringified) path,
|
|
131
|
+
// tracking the expected final value by replaying the additions in order
|
|
132
|
+
const targets = new Map<string, {baseValue: number; expectedValue: number; delta: number}>()
|
|
133
|
+
for (const patch of userPatches) {
|
|
134
|
+
for (const [op, sign] of [
|
|
135
|
+
['inc', 1],
|
|
136
|
+
['dec', -1],
|
|
137
|
+
] as const) {
|
|
138
|
+
const record = patch[op]
|
|
139
|
+
if (!record) continue
|
|
140
|
+
for (const [pathExpression, amount] of Object.entries(record)) {
|
|
141
|
+
if (typeof amount !== 'number') continue
|
|
142
|
+
for (const match of jsonMatch(baseBefore, pathExpression)) {
|
|
143
|
+
const path = stringifyPath(match.path)
|
|
144
|
+
const existing = targets.get(path)
|
|
145
|
+
if (existing) {
|
|
146
|
+
existing.expectedValue += sign * amount
|
|
147
|
+
existing.delta += sign * amount
|
|
148
|
+
} else if (typeof match.value === 'number') {
|
|
149
|
+
targets.set(path, {
|
|
150
|
+
baseValue: match.value,
|
|
151
|
+
expectedValue: match.value + sign * amount,
|
|
152
|
+
delta: sign * amount,
|
|
153
|
+
})
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (!targets.size) return diffedPatches
|
|
160
|
+
|
|
161
|
+
const preserved = new Map<string, {delta: number; baseValue: number}>()
|
|
162
|
+
const next = diffedPatches.flatMap((patch) => {
|
|
163
|
+
if (!patch.set) return [patch]
|
|
164
|
+
|
|
165
|
+
const keptSet: Record<string, unknown> = {}
|
|
166
|
+
for (const [path, value] of Object.entries(patch.set)) {
|
|
167
|
+
const target = targets.get(path)
|
|
168
|
+
if (target && typeof value === 'number' && value === target.expectedValue) {
|
|
169
|
+
preserved.set(path, {delta: target.delta, baseValue: target.baseValue})
|
|
170
|
+
} else {
|
|
171
|
+
keptSet[path] = value
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (Object.keys(keptSet).length === Object.keys(patch.set).length) return [patch]
|
|
176
|
+
const {set: _set, ...rest} = patch
|
|
177
|
+
const withKept = Object.keys(keptSet).length ? {...rest, set: keptSet} : rest
|
|
178
|
+
return Object.keys(withKept).length ? [withKept] : []
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
if (!preserved.size) return next
|
|
182
|
+
|
|
183
|
+
const setIfMissing: Record<string, unknown> = {}
|
|
184
|
+
const inc: Record<string, number> = {}
|
|
185
|
+
const dec: Record<string, number> = {}
|
|
186
|
+
for (const [path, {delta, baseValue}] of preserved) {
|
|
187
|
+
setIfMissing[path] = baseValue
|
|
188
|
+
if (delta >= 0) inc[path] = delta
|
|
189
|
+
else dec[path] = -delta
|
|
190
|
+
}
|
|
191
|
+
return [
|
|
192
|
+
...next,
|
|
193
|
+
{
|
|
194
|
+
setIfMissing,
|
|
195
|
+
...(Object.keys(inc).length && {inc}),
|
|
196
|
+
...(Object.keys(dec).length && {dec}),
|
|
197
|
+
},
|
|
198
|
+
]
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
interface ApplySingleDocPatchOptions {
|
|
202
|
+
base: DocumentSet
|
|
203
|
+
working: DocumentSet
|
|
204
|
+
documentId: string
|
|
205
|
+
patches: PatchOperations[] | undefined
|
|
206
|
+
transactionId: string
|
|
207
|
+
timestamp: string
|
|
208
|
+
grants: Record<Grant, ExprNode>
|
|
209
|
+
identity: string | undefined
|
|
210
|
+
/**
|
|
211
|
+
* Error message thrown when the target document does not exist in either
|
|
212
|
+
* the base or working set.
|
|
213
|
+
*/
|
|
214
|
+
notFoundMessage?: string
|
|
215
|
+
/**
|
|
216
|
+
* Error message thrown when the working document fails the `update` grant.
|
|
217
|
+
*/
|
|
218
|
+
permissionMessage?: string
|
|
219
|
+
/**
|
|
220
|
+
* When `true`, the given patches are used verbatim instead of being
|
|
221
|
+
* re-derived by diffing the base document before and after application.
|
|
222
|
+
* Patch application failures are surfaced as `ActionError`s so a rebase
|
|
223
|
+
* can skip the transaction instead of failing the store.
|
|
224
|
+
*/
|
|
225
|
+
preserveOperations?: boolean
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
interface ApplySingleDocPatchResult {
|
|
229
|
+
base: DocumentSet
|
|
230
|
+
working: DocumentSet
|
|
231
|
+
/**
|
|
232
|
+
* Patch operations representing the minimal diff between base before and
|
|
233
|
+
* after the user's patches were applied. These are the patches that should
|
|
234
|
+
* be sent to the server (or applied to the working set as mutations).
|
|
235
|
+
*/
|
|
236
|
+
diffedPatches: PatchOperations[]
|
|
237
|
+
/**
|
|
238
|
+
* Mutation envelopes for `diffedPatches` already keyed to `documentId`.
|
|
239
|
+
* Useful for callers that want to push them to `outgoingMutations`.
|
|
240
|
+
*/
|
|
241
|
+
workingMutations: Mutation[]
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Shared logic for applying user-provided patches to a single document that
|
|
246
|
+
* is identified by an exact ID (no draft/published wrapping). Used by the
|
|
247
|
+
* liveEdit branch of `document.edit` and by `release.edit`.
|
|
248
|
+
*
|
|
249
|
+
* Returns the updated base + working sets, plus the diffed patches in both
|
|
250
|
+
* raw and mutation form so the caller can decide what to send to the server.
|
|
251
|
+
*/
|
|
252
|
+
export function applySingleDocPatch({
|
|
253
|
+
base: initialBase,
|
|
254
|
+
working: initialWorking,
|
|
255
|
+
documentId,
|
|
256
|
+
patches,
|
|
257
|
+
transactionId,
|
|
258
|
+
timestamp,
|
|
259
|
+
grants,
|
|
260
|
+
identity,
|
|
261
|
+
notFoundMessage = 'Cannot edit document because it does not exist.',
|
|
262
|
+
permissionMessage = `You do not have permission to edit document "${documentId}".`,
|
|
263
|
+
preserveOperations,
|
|
264
|
+
}: ApplySingleDocPatchOptions): ApplySingleDocPatchResult {
|
|
265
|
+
let base = initialBase
|
|
266
|
+
let working = initialWorking
|
|
267
|
+
|
|
268
|
+
const userPatches = patches?.map((patch) => ({patch: {id: documentId, ...patch}}))
|
|
269
|
+
|
|
270
|
+
if (!userPatches?.length) {
|
|
271
|
+
return {base, working, diffedPatches: [], workingMutations: []}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (!working[documentId] || !base[documentId]) {
|
|
275
|
+
throw new ActionError({documentId, transactionId, message: notFoundMessage})
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const applyMutations = createMutationApplier({
|
|
279
|
+
documentId,
|
|
280
|
+
transactionId,
|
|
281
|
+
timestamp,
|
|
282
|
+
preserveOperations,
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
const baseBefore = base[documentId]
|
|
286
|
+
base = applyMutations(base, userPatches, 'base')
|
|
287
|
+
const baseAfter = base[documentId]
|
|
288
|
+
const diffedPatches = preserveOperations
|
|
289
|
+
? (patches as PatchOperations[])
|
|
290
|
+
: preserveNumericOperations(
|
|
291
|
+
baseBefore,
|
|
292
|
+
patches,
|
|
293
|
+
diffValue(baseBefore, baseAfter) as PatchOperations[],
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
const workingBefore = working[documentId] as SanityDocument
|
|
297
|
+
if (!checkGrant(grants.update, workingBefore, identity)) {
|
|
298
|
+
throw new PermissionActionError({documentId, transactionId, message: permissionMessage})
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const workingMutations: Mutation[] = diffedPatches.map((patch) => ({
|
|
302
|
+
patch: {id: documentId, ...patch},
|
|
303
|
+
}))
|
|
304
|
+
|
|
305
|
+
working = applyMutations(working, workingMutations, 'working')
|
|
306
|
+
|
|
307
|
+
return {base, working, diffedPatches, workingMutations}
|
|
308
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import {DocumentId, getDraftId, getPublishedId} from '@sanity/id-utils'
|
|
2
|
+
import {type Mutation, type SanityDocument} from '@sanity/types'
|
|
3
|
+
|
|
4
|
+
import {isReleasePerspective} from '../../releases/utils/isReleasePerspective'
|
|
5
|
+
import {type UnpublishDocumentAction} 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 handleUnpublish(
|
|
16
|
+
action: UnpublishDocumentAction,
|
|
17
|
+
ctx: ActionHandlerContext,
|
|
18
|
+
): ActionHandlerResult {
|
|
19
|
+
const {transactionId, timestamp, grants, identity, outgoingActions, outgoingMutations} = ctx
|
|
20
|
+
let {base, working} = ctx
|
|
21
|
+
|
|
22
|
+
const documentId = getId(action.documentId)
|
|
23
|
+
|
|
24
|
+
if (action.liveEdit || isReleasePerspective(action.perspective)) {
|
|
25
|
+
throw new ActionError({
|
|
26
|
+
documentId,
|
|
27
|
+
transactionId,
|
|
28
|
+
message: `Cannot unpublish this document. Unpublishing is not supported for liveEdit or version (release) documents.`,
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Standard draft/published or version logic
|
|
33
|
+
const draftId = getDraftId(DocumentId(documentId))
|
|
34
|
+
const publishedId = getPublishedId(DocumentId(documentId))
|
|
35
|
+
|
|
36
|
+
if (!working[publishedId] && !base[publishedId]) {
|
|
37
|
+
throw new ActionError({
|
|
38
|
+
documentId,
|
|
39
|
+
transactionId,
|
|
40
|
+
message: `Cannot unpublish because the document "${documentId}" is not currently published.`,
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const sourceDoc = working[publishedId] ?? (base[publishedId] as SanityDocument)
|
|
45
|
+
const newDraftFromPublished = {...sourceDoc, _id: draftId}
|
|
46
|
+
const mutations: Mutation[] = [
|
|
47
|
+
{delete: {id: publishedId}},
|
|
48
|
+
{createIfNotExists: newDraftFromPublished},
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
if (!checkGrant(grants.update, sourceDoc, identity)) {
|
|
52
|
+
throw new PermissionActionError({
|
|
53
|
+
documentId,
|
|
54
|
+
transactionId,
|
|
55
|
+
message: `You do not have permission to unpublish the document "${documentId}".`,
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!working[draftId] && !checkGrant(grants.create, newDraftFromPublished, identity)) {
|
|
60
|
+
throw new PermissionActionError({
|
|
61
|
+
documentId,
|
|
62
|
+
transactionId,
|
|
63
|
+
message: `You do not have permission to create a draft from the published version of "${documentId}".`,
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
base = processMutations({
|
|
68
|
+
documents: base,
|
|
69
|
+
transactionId,
|
|
70
|
+
mutations: [
|
|
71
|
+
{delete: {id: publishedId}},
|
|
72
|
+
{createIfNotExists: {...(base[publishedId] ?? sourceDoc), _id: draftId}},
|
|
73
|
+
],
|
|
74
|
+
timestamp,
|
|
75
|
+
})
|
|
76
|
+
working = processMutations({documents: working, transactionId, mutations, timestamp})
|
|
77
|
+
|
|
78
|
+
outgoingMutations.push(...mutations)
|
|
79
|
+
outgoingActions.push({
|
|
80
|
+
actionType: 'sanity.action.document.unpublish',
|
|
81
|
+
draftId,
|
|
82
|
+
publishedId,
|
|
83
|
+
})
|
|
84
|
+
return {base, working}
|
|
85
|
+
}
|