@sanity/sdk 2.8.0 → 2.9.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/_chunks-dts/utils.d.ts +2396 -0
- package/dist/_chunks-es/_internal.js +129 -0
- package/dist/_chunks-es/_internal.js.map +1 -0
- package/dist/_chunks-es/createGroqSearchFilter.js +1460 -0
- package/dist/_chunks-es/createGroqSearchFilter.js.map +1 -0
- package/dist/_chunks-es/telemetryManager.js +87 -0
- package/dist/_chunks-es/telemetryManager.js.map +1 -0
- package/dist/_chunks-es/version.js +7 -0
- package/dist/_chunks-es/version.js.map +1 -0
- package/dist/_exports/_internal.d.ts +64 -0
- package/dist/_exports/_internal.js +20 -0
- package/dist/_exports/_internal.js.map +1 -0
- package/dist/index.d.ts +2 -2343
- package/dist/index.js +383 -1777
- package/dist/index.js.map +1 -1
- package/package.json +11 -4
- package/src/_exports/_internal.ts +14 -0
- package/src/_exports/index.ts +10 -1
- package/src/auth/authStore.test.ts +150 -1
- package/src/auth/authStore.ts +11 -11
- package/src/auth/dashboardAuth.ts +2 -2
- package/src/auth/handleAuthCallback.ts +9 -3
- package/src/auth/logout.test.ts +1 -1
- package/src/auth/logout.ts +1 -1
- package/src/auth/refreshStampedToken.test.ts +118 -1
- package/src/auth/refreshStampedToken.ts +3 -2
- package/src/auth/standaloneAuth.ts +9 -3
- package/src/auth/studioAuth.ts +34 -7
- package/src/auth/studioModeAuth.ts +2 -1
- package/src/auth/subscribeToStateAndFetchCurrentUser.test.ts +10 -2
- package/src/auth/subscribeToStateAndFetchCurrentUser.ts +5 -1
- package/src/auth/subscribeToStorageEventsAndSetToken.ts +2 -2
- package/src/auth/utils.ts +33 -0
- package/src/client/clientStore.test.ts +14 -0
- package/src/client/clientStore.ts +2 -1
- package/src/comlink/node/getNodeState.ts +2 -1
- package/src/config/sanityConfig.ts +6 -0
- package/src/document/actions.ts +18 -11
- package/src/document/applyDocumentActions.test.ts +7 -6
- package/src/document/applyDocumentActions.ts +10 -4
- package/src/document/documentStore.test.ts +536 -188
- package/src/document/documentStore.ts +142 -76
- package/src/document/events.ts +7 -2
- package/src/document/permissions.test.ts +18 -16
- package/src/document/permissions.ts +35 -11
- package/src/document/processActions.test.ts +359 -32
- package/src/document/processActions.ts +104 -76
- package/src/document/reducers.test.ts +117 -29
- package/src/document/reducers.ts +43 -36
- package/src/document/sharedListener.ts +16 -6
- package/src/document/util.ts +14 -0
- package/src/favorites/favorites.test.ts +9 -2
- package/src/presence/bifurTransport.ts +6 -1
- package/src/preview/getPreviewState.test.ts +115 -98
- package/src/preview/getPreviewState.ts +38 -60
- package/src/preview/previewProjectionUtils.test.ts +179 -0
- package/src/preview/previewProjectionUtils.ts +93 -0
- package/src/preview/resolvePreview.test.ts +42 -25
- package/src/preview/resolvePreview.ts +29 -10
- package/src/preview/{previewStore.ts → types.ts} +8 -17
- package/src/projection/getProjectionState.test.ts +16 -16
- package/src/projection/getProjectionState.ts +2 -1
- package/src/projection/projectionQuery.ts +2 -3
- package/src/projection/types.ts +1 -1
- package/src/query/queryStore.ts +2 -1
- package/src/releases/getPerspectiveState.ts +7 -6
- package/src/releases/releasesStore.test.ts +20 -5
- package/src/releases/releasesStore.ts +20 -8
- package/src/store/createStateSourceAction.test.ts +62 -0
- package/src/store/createStateSourceAction.ts +34 -39
- package/src/telemetry/__telemetry__/sdk.telemetry.ts +42 -0
- package/src/telemetry/devMode.test.ts +52 -0
- package/src/telemetry/devMode.ts +40 -0
- package/src/telemetry/initTelemetry.test.ts +225 -0
- package/src/telemetry/initTelemetry.ts +205 -0
- package/src/telemetry/telemetryManager.test.ts +263 -0
- package/src/telemetry/telemetryManager.ts +187 -0
- package/src/users/usersStore.test.ts +1 -0
- package/src/users/usersStore.ts +5 -1
- package/src/utils/createFetcherStore.test.ts +6 -4
- package/src/utils/createFetcherStore.ts +2 -1
- 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/setCleanupTimeout.ts +24 -0
- package/src/preview/previewQuery.test.ts +0 -236
- package/src/preview/previewQuery.ts +0 -153
- package/src/preview/previewStore.test.ts +0 -36
- package/src/preview/subscribeToStateAndFetchBatches.test.ts +0 -221
- package/src/preview/subscribeToStateAndFetchBatches.ts +0 -112
- package/src/preview/util.ts +0 -13
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {type Action} from '@sanity/client'
|
|
2
|
-
import {getPublishedId} from '@sanity/
|
|
1
|
+
import {type Action, type Mutation} from '@sanity/client'
|
|
2
|
+
import {DocumentId, getDraftId, getPublishedId, getVersionId} from '@sanity/id-utils'
|
|
3
3
|
import {jsonMatch} from '@sanity/json-match'
|
|
4
4
|
import {type SanityDocument} from 'groq'
|
|
5
5
|
import {type ExprNode} from 'groq-js'
|
|
@@ -26,30 +26,45 @@ import {
|
|
|
26
26
|
withLatestFrom,
|
|
27
27
|
} from 'rxjs'
|
|
28
28
|
|
|
29
|
-
import {getClientState} from '../client/clientStore'
|
|
30
|
-
import {type DocumentHandle} from '../config/sanityConfig'
|
|
29
|
+
import {type ClientOptions, getClientState} from '../client/clientStore'
|
|
31
30
|
import {
|
|
32
|
-
|
|
33
|
-
type
|
|
31
|
+
type DocumentHandle,
|
|
32
|
+
type DocumentSource,
|
|
33
|
+
isCanvasSource,
|
|
34
|
+
isDatasetSource,
|
|
35
|
+
isMediaLibrarySource,
|
|
36
|
+
} from '../config/sanityConfig'
|
|
37
|
+
import {isReleasePerspective} from '../releases/utils/isReleasePerspective'
|
|
38
|
+
import {
|
|
39
|
+
bindActionBySource,
|
|
40
|
+
type BoundSourceKey,
|
|
34
41
|
type StoreAction,
|
|
35
42
|
} from '../store/createActionBinder'
|
|
36
43
|
import {type SanityInstance} from '../store/createSanityInstance'
|
|
37
44
|
import {createStateSourceAction, type StateSource} from '../store/createStateSourceAction'
|
|
38
45
|
import {defineStore, type StoreContext} from '../store/defineStore'
|
|
39
|
-
import {getDraftId} from '../utils/ids'
|
|
40
46
|
import {type DocumentAction} from './actions'
|
|
41
47
|
import {API_VERSION, INITIAL_OUTGOING_THROTTLE_TIME} from './documentConstants'
|
|
42
|
-
import {
|
|
48
|
+
import {
|
|
49
|
+
type DocumentEvent,
|
|
50
|
+
type DocumentTransactionSubmissionResult,
|
|
51
|
+
getDocumentEvents,
|
|
52
|
+
} from './events'
|
|
43
53
|
import {listen, OutOfSyncError} from './listen'
|
|
44
54
|
import {type JsonMatch} from './patchOperations'
|
|
45
|
-
import {
|
|
55
|
+
import {
|
|
56
|
+
calculatePermissions,
|
|
57
|
+
createGrantsLookup,
|
|
58
|
+
type DatasetAcl,
|
|
59
|
+
type DocumentPermissionsResult,
|
|
60
|
+
type Grant,
|
|
61
|
+
} from './permissions'
|
|
46
62
|
import {ActionError} from './processActions'
|
|
47
63
|
import {
|
|
48
64
|
type AppliedTransaction,
|
|
49
65
|
applyFirstQueuedTransaction,
|
|
50
66
|
applyRemoteDocument,
|
|
51
67
|
cleanupOutgoingTransaction,
|
|
52
|
-
getDocumentIdsFromActions,
|
|
53
68
|
manageSubscriberIds,
|
|
54
69
|
type OutgoingTransaction,
|
|
55
70
|
type QueuedTransaction,
|
|
@@ -107,15 +122,15 @@ export interface DocumentState {
|
|
|
107
122
|
unverifiedRevisions?: {[TTransactionId in string]?: UnverifiedDocumentRevision}
|
|
108
123
|
}
|
|
109
124
|
|
|
110
|
-
export const documentStore = defineStore<DocumentStoreState,
|
|
125
|
+
export const documentStore = defineStore<DocumentStoreState, BoundSourceKey>({
|
|
111
126
|
name: 'Document',
|
|
112
|
-
getInitialState: (instance) => ({
|
|
127
|
+
getInitialState: (instance, {source}) => ({
|
|
113
128
|
documentStates: {},
|
|
114
129
|
// these can be emptied on refetch
|
|
115
130
|
queued: [],
|
|
116
131
|
applied: [],
|
|
117
|
-
sharedListener: createSharedListener(instance),
|
|
118
|
-
fetchDocument: createFetchDocument(instance),
|
|
132
|
+
sharedListener: createSharedListener(instance, source),
|
|
133
|
+
fetchDocument: createFetchDocument(instance, source),
|
|
119
134
|
events: new Subject(),
|
|
120
135
|
}),
|
|
121
136
|
initialize(context) {
|
|
@@ -183,33 +198,32 @@ export function getDocumentState(
|
|
|
183
198
|
return _getDocumentState(...args)
|
|
184
199
|
}
|
|
185
200
|
|
|
186
|
-
const _getDocumentState =
|
|
201
|
+
const _getDocumentState = bindActionBySource(
|
|
187
202
|
documentStore,
|
|
188
203
|
createStateSourceAction({
|
|
189
204
|
selector: ({state: {error, documentStates}}, options: DocumentOptions<string | undefined>) => {
|
|
190
|
-
const {documentId, path, liveEdit} = options
|
|
205
|
+
const {documentId: docId, path, liveEdit, perspective} = options
|
|
206
|
+
const documentId = DocumentId(docId)
|
|
191
207
|
if (error) throw error
|
|
208
|
+
let document: SanityDocument | null | undefined
|
|
192
209
|
|
|
193
210
|
if (liveEdit) {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
if (
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
211
|
+
document = documentStates[documentId]?.local
|
|
212
|
+
} else {
|
|
213
|
+
let version: SanityDocument | null | undefined
|
|
214
|
+
if (isReleasePerspective(perspective)) {
|
|
215
|
+
const versionId = getVersionId(documentId, perspective.releaseName)
|
|
216
|
+
version = documentStates[versionId]?.local
|
|
217
|
+
// early exit if we don't have the version document and we're in a release perspective
|
|
218
|
+
if (version === undefined) return undefined
|
|
219
|
+
}
|
|
220
|
+
const draft = documentStates[getDraftId(documentId)]?.local
|
|
221
|
+
const published = documentStates[getPublishedId(documentId)]?.local
|
|
222
|
+
// early exit if we don't have all the documents for draft/published logic
|
|
223
|
+
if (draft === undefined || published === undefined) return undefined
|
|
224
|
+
document = version ?? draft ?? published
|
|
202
225
|
}
|
|
203
226
|
|
|
204
|
-
// Standard draft/published logic
|
|
205
|
-
const draftId = getDraftId(documentId)
|
|
206
|
-
const publishedId = getPublishedId(documentId)
|
|
207
|
-
const draft = documentStates[draftId]?.local
|
|
208
|
-
const published = documentStates[publishedId]?.local
|
|
209
|
-
|
|
210
|
-
// wait for draft and published to be loaded before returning a value
|
|
211
|
-
if (draft === undefined || published === undefined) return undefined
|
|
212
|
-
const document = draft ?? published
|
|
213
227
|
if (!path) return document
|
|
214
228
|
const result = jsonMatch(document, path).next()
|
|
215
229
|
if (result.done) return undefined
|
|
@@ -217,7 +231,7 @@ const _getDocumentState = bindActionByDataset(
|
|
|
217
231
|
return value
|
|
218
232
|
},
|
|
219
233
|
onSubscribe: (context, options: DocumentOptions<string | undefined>) =>
|
|
220
|
-
manageSubscriberIds(context, options
|
|
234
|
+
manageSubscriberIds(context, [options]),
|
|
221
235
|
}),
|
|
222
236
|
)
|
|
223
237
|
|
|
@@ -241,7 +255,7 @@ export function resolveDocument(
|
|
|
241
255
|
): Promise<SanityDocument | null> {
|
|
242
256
|
return _resolveDocument(...args)
|
|
243
257
|
}
|
|
244
|
-
const _resolveDocument =
|
|
258
|
+
const _resolveDocument = bindActionBySource(
|
|
245
259
|
documentStore,
|
|
246
260
|
({instance}, docHandle: DocumentHandle<string, string, string>) => {
|
|
247
261
|
return firstValueFrom(
|
|
@@ -254,57 +268,59 @@ const _resolveDocument = bindActionByDataset(
|
|
|
254
268
|
)
|
|
255
269
|
|
|
256
270
|
/** @beta */
|
|
257
|
-
export const getDocumentSyncStatus =
|
|
271
|
+
export const getDocumentSyncStatus = bindActionBySource(
|
|
258
272
|
documentStore,
|
|
259
273
|
createStateSourceAction({
|
|
260
274
|
selector: (
|
|
261
275
|
{state: {error, documentStates: documents, outgoing, applied, queued}},
|
|
262
276
|
doc: DocumentHandle,
|
|
263
277
|
) => {
|
|
264
|
-
const documentId = typeof doc === 'string' ? doc : doc.documentId
|
|
278
|
+
const documentId = DocumentId(typeof doc === 'string' ? doc : doc.documentId)
|
|
265
279
|
if (error) throw error
|
|
266
280
|
|
|
267
281
|
if (doc.liveEdit) {
|
|
268
282
|
// For liveEdit documents, only check the single document
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
283
|
+
if (documents[documentId] === undefined) return undefined
|
|
284
|
+
} else {
|
|
285
|
+
const version = isReleasePerspective(doc.perspective)
|
|
286
|
+
? documents[getVersionId(documentId, doc.perspective.releaseName)]
|
|
287
|
+
: undefined
|
|
288
|
+
if (isReleasePerspective(doc.perspective) && version === undefined) return undefined
|
|
289
|
+
// Standard draft/published logic
|
|
290
|
+
const draft = documents[getDraftId(documentId)]
|
|
291
|
+
const published = documents[getPublishedId(documentId)]
|
|
292
|
+
if (draft === undefined || published === undefined) return undefined
|
|
272
293
|
}
|
|
273
|
-
|
|
274
|
-
// Standard draft/published logic
|
|
275
|
-
const draftId = getDraftId(documentId)
|
|
276
|
-
const publishedId = getPublishedId(documentId)
|
|
277
|
-
|
|
278
|
-
const draft = documents[draftId]
|
|
279
|
-
const published = documents[publishedId]
|
|
280
|
-
|
|
281
|
-
if (draft === undefined || published === undefined) return undefined
|
|
282
294
|
return !queued.length && !applied.length && !outgoing
|
|
283
295
|
},
|
|
284
|
-
onSubscribe: (context, doc: DocumentHandle) =>
|
|
296
|
+
onSubscribe: (context, doc: DocumentHandle) => {
|
|
297
|
+
return manageSubscriberIds(context, [doc])
|
|
298
|
+
},
|
|
285
299
|
}),
|
|
286
300
|
)
|
|
287
301
|
|
|
288
302
|
type PermissionsStateOptions = {
|
|
303
|
+
source?: DocumentSource
|
|
289
304
|
actions: DocumentAction[]
|
|
290
305
|
}
|
|
291
306
|
|
|
292
307
|
/** @beta */
|
|
293
|
-
export const getPermissionsState =
|
|
308
|
+
export const getPermissionsState = bindActionBySource(
|
|
294
309
|
documentStore,
|
|
295
310
|
createStateSourceAction({
|
|
296
311
|
selector: calculatePermissions,
|
|
297
|
-
onSubscribe: (context, {actions}: PermissionsStateOptions) =>
|
|
298
|
-
manageSubscriberIds(context,
|
|
312
|
+
onSubscribe: (context, {actions}: PermissionsStateOptions) => {
|
|
313
|
+
manageSubscriberIds(context, actions)
|
|
314
|
+
},
|
|
299
315
|
}) as StoreAction<
|
|
300
316
|
DocumentStoreState,
|
|
301
317
|
[PermissionsStateOptions],
|
|
302
|
-
StateSource<
|
|
318
|
+
StateSource<DocumentPermissionsResult>
|
|
303
319
|
>,
|
|
304
320
|
)
|
|
305
321
|
|
|
306
322
|
/** @beta */
|
|
307
|
-
export const resolvePermissions =
|
|
323
|
+
export const resolvePermissions = bindActionBySource(
|
|
308
324
|
documentStore,
|
|
309
325
|
({instance}, options: PermissionsStateOptions) => {
|
|
310
326
|
return firstValueFrom(
|
|
@@ -314,16 +330,18 @@ export const resolvePermissions = bindActionByDataset(
|
|
|
314
330
|
)
|
|
315
331
|
|
|
316
332
|
/** @beta */
|
|
317
|
-
export const subscribeDocumentEvents =
|
|
333
|
+
export const subscribeDocumentEvents = bindActionBySource(
|
|
318
334
|
documentStore,
|
|
319
|
-
({state}, eventHandler: (e: DocumentEvent) => void) => {
|
|
335
|
+
({state}, options: {source?: DocumentSource; eventHandler: (e: DocumentEvent) => void}) => {
|
|
320
336
|
const {events} = state.get()
|
|
321
|
-
const subscription = events.subscribe(eventHandler)
|
|
337
|
+
const subscription = events.subscribe(options.eventHandler)
|
|
322
338
|
return () => subscription.unsubscribe()
|
|
323
339
|
},
|
|
324
340
|
)
|
|
325
341
|
|
|
326
|
-
const subscribeToQueuedAndApplyNextTransaction = ({
|
|
342
|
+
const subscribeToQueuedAndApplyNextTransaction = ({
|
|
343
|
+
state,
|
|
344
|
+
}: StoreContext<DocumentStoreState, BoundSourceKey>) => {
|
|
327
345
|
const {events} = state.get()
|
|
328
346
|
return state.observable
|
|
329
347
|
.pipe(
|
|
@@ -354,7 +372,8 @@ const subscribeToQueuedAndApplyNextTransaction = ({state}: StoreContext<Document
|
|
|
354
372
|
const subscribeToAppliedAndSubmitNextTransaction = ({
|
|
355
373
|
state,
|
|
356
374
|
instance,
|
|
357
|
-
|
|
375
|
+
key: {source},
|
|
376
|
+
}: StoreContext<DocumentStoreState, BoundSourceKey>) => {
|
|
358
377
|
const {events} = state.get()
|
|
359
378
|
|
|
360
379
|
return state.observable
|
|
@@ -374,22 +393,52 @@ const subscribeToAppliedAndSubmitNextTransaction = ({
|
|
|
374
393
|
tap((next) => state.set('transitionAppliedTransactionsToOutgoing', next)),
|
|
375
394
|
map((s) => s.outgoing),
|
|
376
395
|
distinctUntilChanged(),
|
|
377
|
-
withLatestFrom(
|
|
396
|
+
withLatestFrom(
|
|
397
|
+
getClientState(instance, {
|
|
398
|
+
apiVersion: API_VERSION,
|
|
399
|
+
// TODO: remove in v3 when we're ready for everything to be queried via source
|
|
400
|
+
source: source && !isDatasetSource(source) ? source : undefined,
|
|
401
|
+
}).observable,
|
|
402
|
+
),
|
|
378
403
|
concatMap(([outgoing, client]) => {
|
|
379
404
|
if (!outgoing) return EMPTY
|
|
405
|
+
|
|
406
|
+
const revertOnError = catchError((error: unknown) => {
|
|
407
|
+
state.set('revertOutgoingTransaction', revertOutgoingTransaction)
|
|
408
|
+
const message = error instanceof Error ? error.message : 'Request failed'
|
|
409
|
+
events.next({type: 'reverted', message, outgoing, error})
|
|
410
|
+
return EMPTY
|
|
411
|
+
})
|
|
412
|
+
|
|
413
|
+
const toResult = map((result: unknown) => ({
|
|
414
|
+
result: result as DocumentTransactionSubmissionResult,
|
|
415
|
+
outgoing,
|
|
416
|
+
}))
|
|
417
|
+
|
|
418
|
+
// Any liveEdit action in the batch routes to the mutations API. For mixed batches
|
|
419
|
+
// non-liveEdit operations (e.g. publish) lose atomicity, but that is acceptable
|
|
420
|
+
// given how rare mixed batches are.
|
|
421
|
+
if (outgoing.actions.some((action) => action.liveEdit)) {
|
|
422
|
+
return client.observable
|
|
423
|
+
.mutate(outgoing.outgoingMutations as Mutation[], {
|
|
424
|
+
transactionId: outgoing.transactionId,
|
|
425
|
+
visibility: 'async',
|
|
426
|
+
returnDocuments: false,
|
|
427
|
+
returnFirst: false,
|
|
428
|
+
tag: 'document.mutate',
|
|
429
|
+
skipCrossDatasetReferenceValidation: true,
|
|
430
|
+
})
|
|
431
|
+
.pipe(revertOnError, toResult)
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Pure non-liveEdit transactions use the actions API.
|
|
380
435
|
return client.observable
|
|
381
436
|
.action(outgoing.outgoingActions as Action[], {
|
|
382
437
|
transactionId: outgoing.transactionId,
|
|
383
438
|
skipCrossDatasetReferenceValidation: true,
|
|
439
|
+
tag: 'document.action',
|
|
384
440
|
})
|
|
385
|
-
.pipe(
|
|
386
|
-
catchError((error) => {
|
|
387
|
-
state.set('revertOutgoingTransaction', revertOutgoingTransaction)
|
|
388
|
-
events.next({type: 'reverted', message: error.message, outgoing, error})
|
|
389
|
-
return EMPTY
|
|
390
|
-
}),
|
|
391
|
-
map((result) => ({result, outgoing})),
|
|
392
|
-
)
|
|
441
|
+
.pipe(revertOnError, toResult)
|
|
393
442
|
}),
|
|
394
443
|
tap(({outgoing, result}) => {
|
|
395
444
|
state.set('cleanupOutgoingTransaction', cleanupOutgoingTransaction)
|
|
@@ -401,7 +450,7 @@ const subscribeToAppliedAndSubmitNextTransaction = ({
|
|
|
401
450
|
}
|
|
402
451
|
|
|
403
452
|
const subscribeToSubscriptionsAndListenToDocuments = (
|
|
404
|
-
context: StoreContext<DocumentStoreState>,
|
|
453
|
+
context: StoreContext<DocumentStoreState, BoundSourceKey>,
|
|
405
454
|
) => {
|
|
406
455
|
const {state} = context
|
|
407
456
|
const {events} = state.get()
|
|
@@ -431,8 +480,8 @@ const subscribeToSubscriptionsAndListenToDocuments = (
|
|
|
431
480
|
...added.map((id) => ({id, add: true})),
|
|
432
481
|
...removed.map((id) => ({id, add: false})),
|
|
433
482
|
].sort((a, b) => {
|
|
434
|
-
const aIsDraft = a.id === getDraftId(a.id)
|
|
435
|
-
const bIsDraft = b.id === getDraftId(b.id)
|
|
483
|
+
const aIsDraft = a.id === getDraftId(DocumentId(a.id))
|
|
484
|
+
const bIsDraft = b.id === getDraftId(DocumentId(b.id))
|
|
436
485
|
|
|
437
486
|
if (aIsDraft && bIsDraft) return a.id.localeCompare(b.id, 'en-US')
|
|
438
487
|
if (aIsDraft) return -1
|
|
@@ -469,13 +518,30 @@ const subscribeToSubscriptionsAndListenToDocuments = (
|
|
|
469
518
|
const subscribeToClientAndFetchDatasetAcl = ({
|
|
470
519
|
instance,
|
|
471
520
|
state,
|
|
472
|
-
key: {
|
|
473
|
-
}: StoreContext<DocumentStoreState,
|
|
474
|
-
|
|
521
|
+
key: {source},
|
|
522
|
+
}: StoreContext<DocumentStoreState, BoundSourceKey>) => {
|
|
523
|
+
const clientOptions: ClientOptions = {apiVersion: API_VERSION}
|
|
524
|
+
// TODO: remove in v3 when we're ready for everything to be queried via source
|
|
525
|
+
if (source && !isDatasetSource(source)) {
|
|
526
|
+
clientOptions.source = source
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
let uri: string
|
|
530
|
+
if (source && isDatasetSource(source)) {
|
|
531
|
+
uri = `/projects/${source.projectId}/datasets/${source.dataset}/acl`
|
|
532
|
+
} else if (source && isMediaLibrarySource(source)) {
|
|
533
|
+
uri = `/media-libraries/${source.mediaLibraryId}/acl`
|
|
534
|
+
} else if (source && isCanvasSource(source)) {
|
|
535
|
+
uri = `/canvases/${source.canvasId}/acl`
|
|
536
|
+
} else {
|
|
537
|
+
throw new Error(`Received invalid source: ${JSON.stringify(source)}`)
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
return getClientState(instance, clientOptions)
|
|
475
541
|
.observable.pipe(
|
|
476
542
|
switchMap((client) =>
|
|
477
543
|
client.observable.request<DatasetAcl>({
|
|
478
|
-
uri
|
|
544
|
+
uri,
|
|
479
545
|
tag: 'acl.get',
|
|
480
546
|
withCredentials: true,
|
|
481
547
|
}),
|
package/src/document/events.ts
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
import {type SanityClient} from '@sanity/client'
|
|
1
|
+
import {type MultipleMutationResult, type SanityClient} from '@sanity/client'
|
|
2
2
|
|
|
3
3
|
import {type DocumentAction} from './actions'
|
|
4
4
|
import {type OutgoingTransaction} from './reducers'
|
|
5
5
|
|
|
6
|
+
/** @beta Response body from submitting an outgoing transaction (actions or mutations API). */
|
|
7
|
+
export type DocumentTransactionSubmissionResult =
|
|
8
|
+
| Awaited<ReturnType<SanityClient['action']>>
|
|
9
|
+
| MultipleMutationResult
|
|
10
|
+
|
|
6
11
|
/** @beta */
|
|
7
12
|
export type DocumentEvent =
|
|
8
13
|
| ActionErrorEvent
|
|
@@ -35,7 +40,7 @@ export interface ActionErrorEvent {
|
|
|
35
40
|
export interface TransactionAcceptedEvent {
|
|
36
41
|
type: 'accepted'
|
|
37
42
|
outgoing: OutgoingTransaction
|
|
38
|
-
result:
|
|
43
|
+
result: DocumentTransactionSubmissionResult
|
|
39
44
|
}
|
|
40
45
|
/**
|
|
41
46
|
* @beta
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import {DocumentId, getDraftId, getPublishedId} from '@sanity/id-utils'
|
|
1
2
|
import {type SanityDocument} from '@sanity/types'
|
|
2
3
|
import {evaluateSync, type ExprNode, parse} from 'groq-js'
|
|
3
4
|
import {describe, expect, it} from 'vitest'
|
|
4
5
|
|
|
5
6
|
import {createSanityInstance} from '../store/createSanityInstance'
|
|
6
|
-
import {getDraftId, getPublishedId} from '../utils/ids'
|
|
7
7
|
import {type DocumentAction} from './actions'
|
|
8
8
|
import {calculatePermissions, createGrantsLookup, type DatasetAcl, type Grant} from './permissions'
|
|
9
9
|
import {type SyncTransactionState} from './reducers'
|
|
@@ -66,8 +66,10 @@ describe('calculatePermissions', () => {
|
|
|
66
66
|
// For a document.create action, the selector expects both published and draft keys.
|
|
67
67
|
const state = createState(
|
|
68
68
|
{
|
|
69
|
-
[getPublishedId('doc1')]: {
|
|
70
|
-
|
|
69
|
+
[getPublishedId(DocumentId('doc1'))]: {
|
|
70
|
+
local: createDoc(DocumentId('doc1'), 'Original Title'),
|
|
71
|
+
},
|
|
72
|
+
[getDraftId(DocumentId('doc1'))]: {local: null},
|
|
71
73
|
},
|
|
72
74
|
defaultGrants,
|
|
73
75
|
)
|
|
@@ -83,7 +85,7 @@ describe('calculatePermissions', () => {
|
|
|
83
85
|
// Missing the draft key will cause documentsSelector to return undefined.
|
|
84
86
|
const state = createState(
|
|
85
87
|
{
|
|
86
|
-
[getPublishedId('doc1')]: {local: createDoc('doc1', 'Title')},
|
|
88
|
+
[getPublishedId(DocumentId('doc1'))]: {local: createDoc(DocumentId('doc1'), 'Title')},
|
|
87
89
|
// Missing getDraftId('doc1')
|
|
88
90
|
},
|
|
89
91
|
defaultGrants,
|
|
@@ -99,8 +101,8 @@ describe('calculatePermissions', () => {
|
|
|
99
101
|
const deniedGrants = {...defaultGrants, create: alwaysDeny}
|
|
100
102
|
const state = createState(
|
|
101
103
|
{
|
|
102
|
-
[getPublishedId('doc1')]: {local: createDoc('doc1', 'Title')},
|
|
103
|
-
[getDraftId('doc1')]: {local: null},
|
|
104
|
+
[getPublishedId(DocumentId('doc1'))]: {local: createDoc(DocumentId('doc1'), 'Title')},
|
|
105
|
+
[getDraftId(DocumentId('doc1'))]: {local: null},
|
|
104
106
|
},
|
|
105
107
|
deniedGrants,
|
|
106
108
|
)
|
|
@@ -127,8 +129,8 @@ describe('calculatePermissions', () => {
|
|
|
127
129
|
// Both published and draft documents are present as null.
|
|
128
130
|
const state = createState(
|
|
129
131
|
{
|
|
130
|
-
[getPublishedId('doc1')]: {local: null},
|
|
131
|
-
[getDraftId('doc1')]: {local: null},
|
|
132
|
+
[getPublishedId(DocumentId('doc1'))]: {local: null},
|
|
133
|
+
[getDraftId(DocumentId('doc1'))]: {local: null},
|
|
132
134
|
},
|
|
133
135
|
defaultGrants,
|
|
134
136
|
)
|
|
@@ -153,8 +155,8 @@ describe('calculatePermissions', () => {
|
|
|
153
155
|
const deniedGrants = {...defaultGrants, update: alwaysDeny}
|
|
154
156
|
const state = createState(
|
|
155
157
|
{
|
|
156
|
-
[getPublishedId('doc1')]: {local: createDoc('doc1', 'Title')},
|
|
157
|
-
[getDraftId('doc1')]: {local: createDoc(
|
|
158
|
+
[getPublishedId(DocumentId('doc1'))]: {local: createDoc(DocumentId('doc1'), 'Title')},
|
|
159
|
+
[getDraftId(DocumentId('doc1'))]: {local: createDoc(DocumentId('doc1'), 'Draft Title')},
|
|
158
160
|
},
|
|
159
161
|
deniedGrants,
|
|
160
162
|
)
|
|
@@ -179,8 +181,8 @@ describe('calculatePermissions', () => {
|
|
|
179
181
|
|
|
180
182
|
it('should return undefined if grants are not provided', () => {
|
|
181
183
|
const state = createState({
|
|
182
|
-
[getPublishedId('doc1')]: {local: createDoc('doc1', 'Title')},
|
|
183
|
-
[getDraftId('doc1')]: {local: null},
|
|
184
|
+
[getPublishedId(DocumentId('doc1'))]: {local: createDoc(DocumentId('doc1'), 'Title')},
|
|
185
|
+
[getDraftId(DocumentId('doc1'))]: {local: null},
|
|
184
186
|
})
|
|
185
187
|
const actions: DocumentAction[] = [
|
|
186
188
|
{documentId: 'doc1', type: 'document.create', documentType: 'article'},
|
|
@@ -192,8 +194,8 @@ describe('calculatePermissions', () => {
|
|
|
192
194
|
// For document.delete, if the published document is missing, processActions throws an ActionError.
|
|
193
195
|
const state = createState(
|
|
194
196
|
{
|
|
195
|
-
[getPublishedId('doc1')]: {local: null},
|
|
196
|
-
[getDraftId('doc1')]: {local: null},
|
|
197
|
+
[getPublishedId(DocumentId('doc1'))]: {local: null},
|
|
198
|
+
[getDraftId(DocumentId('doc1'))]: {local: null},
|
|
197
199
|
},
|
|
198
200
|
defaultGrants,
|
|
199
201
|
)
|
|
@@ -217,8 +219,8 @@ describe('calculatePermissions', () => {
|
|
|
217
219
|
it('should memoize the result for identical state and actions inputs', () => {
|
|
218
220
|
const state = createState(
|
|
219
221
|
{
|
|
220
|
-
[getPublishedId('doc1')]: {local: createDoc('doc1', 'Title')},
|
|
221
|
-
[getDraftId('doc1')]: {local: null},
|
|
222
|
+
[getPublishedId(DocumentId('doc1'))]: {local: createDoc(DocumentId('doc1'), 'Title')},
|
|
223
|
+
[getDraftId(DocumentId('doc1'))]: {local: null},
|
|
222
224
|
},
|
|
223
225
|
defaultGrants,
|
|
224
226
|
)
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import {DocumentId, getDraftId, getPublishedId, getVersionId} from '@sanity/id-utils'
|
|
1
2
|
import {type SanityDocument} from '@sanity/types'
|
|
2
3
|
import {evaluateSync, type ExprNode, parse} from 'groq-js'
|
|
3
4
|
import {createSelector} from 'reselect'
|
|
4
5
|
|
|
6
|
+
import {isReleasePerspective} from '../releases/utils/isReleasePerspective'
|
|
5
7
|
import {type SelectorContext} from '../store/createStateSourceAction'
|
|
6
|
-
import {getDraftId, getPublishedId} from '../utils/ids'
|
|
7
8
|
import {MultiKeyWeakMap} from '../utils/MultiKeyWeakMap'
|
|
8
9
|
import {type DocumentAction} from './actions'
|
|
9
10
|
import {ActionError, PermissionActionError, processActions} from './processActions'
|
|
@@ -71,7 +72,14 @@ const documentsSelector = createSelector(
|
|
|
71
72
|
// For liveEdit documents, only fetch the single document
|
|
72
73
|
if (action.liveEdit) return [action.documentId]
|
|
73
74
|
// For standard documents, fetch both draft and published
|
|
74
|
-
|
|
75
|
+
const ids: string[] = [
|
|
76
|
+
getPublishedId(DocumentId(action.documentId)),
|
|
77
|
+
getDraftId(DocumentId(action.documentId)),
|
|
78
|
+
]
|
|
79
|
+
if (isReleasePerspective(action.perspective)) {
|
|
80
|
+
ids.push(getVersionId(DocumentId(action.documentId), action.perspective.releaseName))
|
|
81
|
+
}
|
|
82
|
+
return ids
|
|
75
83
|
})
|
|
76
84
|
.flat(),
|
|
77
85
|
)
|
|
@@ -210,16 +218,32 @@ const _calculatePermissions = createSelector(
|
|
|
210
218
|
// Check edit actions with no patches
|
|
211
219
|
if (action.type === 'document.edit' && !action.patches?.length) {
|
|
212
220
|
const docId = action.documentId
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
221
|
+
let doc: SanityDocument | null | undefined
|
|
222
|
+
if (action.liveEdit) {
|
|
223
|
+
doc = documents[docId]
|
|
224
|
+
}
|
|
225
|
+
// don't allow users to edit version documents that don't exist
|
|
226
|
+
// they should be explicitly created first, as in studio
|
|
227
|
+
else if (isReleasePerspective(action.perspective)) {
|
|
228
|
+
doc = documents[getVersionId(DocumentId(docId), action.perspective.releaseName)]
|
|
229
|
+
} else {
|
|
230
|
+
doc =
|
|
231
|
+
documents[getDraftId(DocumentId(docId))] ?? documents[getPublishedId(DocumentId(docId))]
|
|
232
|
+
}
|
|
217
233
|
if (!doc) {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
234
|
+
if (isReleasePerspective(action.perspective)) {
|
|
235
|
+
reasons.push({
|
|
236
|
+
type: 'precondition',
|
|
237
|
+
message: `The version document with ID "${docId}" could not be found. Please create it or add it to the release first.`,
|
|
238
|
+
documentId: docId,
|
|
239
|
+
})
|
|
240
|
+
} else {
|
|
241
|
+
reasons.push({
|
|
242
|
+
type: 'precondition',
|
|
243
|
+
message: `The document with ID "${docId}" could not be found. Please check that it exists before editing.`,
|
|
244
|
+
documentId: docId,
|
|
245
|
+
})
|
|
246
|
+
}
|
|
223
247
|
} else if (!checkGrant(grants.update, doc)) {
|
|
224
248
|
reasons.push({
|
|
225
249
|
type: 'access',
|