@sanity/sdk 2.3.1 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +173 -105
- package/dist/index.js +354 -122
- package/dist/index.js.map +1 -1
- package/package.json +12 -11
- package/src/_exports/index.ts +30 -0
- package/src/agent/agentActions.test.ts +81 -0
- package/src/agent/agentActions.ts +139 -0
- package/src/auth/authStore.test.ts +13 -13
- package/src/auth/refreshStampedToken.test.ts +16 -16
- package/src/auth/subscribeToStateAndFetchCurrentUser.test.ts +6 -6
- package/src/auth/subscribeToStorageEventsAndSetToken.test.ts +4 -4
- package/src/auth/utils.ts +36 -0
- package/src/client/clientStore.test.ts +151 -0
- package/src/client/clientStore.ts +39 -1
- package/src/comlink/controller/actions/destroyController.test.ts +2 -2
- package/src/comlink/controller/actions/getOrCreateChannel.test.ts +6 -6
- package/src/comlink/controller/actions/getOrCreateController.test.ts +5 -5
- package/src/comlink/controller/actions/getOrCreateController.ts +1 -1
- package/src/comlink/controller/actions/releaseChannel.test.ts +3 -2
- package/src/comlink/controller/comlinkControllerStore.test.ts +4 -4
- package/src/comlink/node/actions/getOrCreateNode.test.ts +7 -7
- package/src/comlink/node/actions/releaseNode.test.ts +2 -2
- package/src/comlink/node/comlinkNodeStore.test.ts +4 -3
- package/src/config/sanityConfig.ts +49 -3
- package/src/document/actions.test.ts +34 -0
- package/src/document/actions.ts +31 -7
- package/src/document/applyDocumentActions.test.ts +9 -6
- package/src/document/applyDocumentActions.ts +9 -49
- package/src/document/documentStore.test.ts +148 -107
- package/src/document/documentStore.ts +40 -10
- package/src/document/permissions.test.ts +9 -9
- package/src/document/permissions.ts +17 -7
- package/src/document/processActions.test.ts +345 -0
- package/src/document/processActions.ts +185 -2
- package/src/document/reducers.ts +13 -6
- package/src/presence/presenceStore.ts +13 -7
- package/src/preview/previewStore.test.ts +10 -2
- package/src/preview/previewStore.ts +2 -1
- package/src/preview/subscribeToStateAndFetchBatches.test.ts +8 -5
- package/src/preview/subscribeToStateAndFetchBatches.ts +9 -3
- package/src/projection/projectionStore.test.ts +18 -2
- package/src/projection/projectionStore.ts +2 -1
- package/src/projection/subscribeToStateAndFetchBatches.test.ts +6 -5
- package/src/projection/subscribeToStateAndFetchBatches.ts +9 -3
- package/src/query/queryStore.ts +7 -4
- package/src/releases/getPerspectiveState.ts +2 -2
- package/src/releases/releasesStore.ts +10 -4
- package/src/store/createActionBinder.test.ts +8 -6
- package/src/store/createActionBinder.ts +50 -14
- package/src/store/createStateSourceAction.test.ts +12 -11
- package/src/store/createStateSourceAction.ts +6 -6
- package/src/store/createStoreInstance.test.ts +29 -16
- package/src/store/createStoreInstance.ts +6 -5
- package/src/store/defineStore.test.ts +1 -1
- package/src/store/defineStore.ts +12 -7
|
@@ -140,6 +140,52 @@ export function processActions({
|
|
|
140
140
|
switch (action.type) {
|
|
141
141
|
case 'document.create': {
|
|
142
142
|
const documentId = getId(action.documentId)
|
|
143
|
+
|
|
144
|
+
if (action.liveEdit) {
|
|
145
|
+
// For liveEdit documents, create directly without draft/published logic
|
|
146
|
+
if (working[documentId]) {
|
|
147
|
+
throw new ActionError({
|
|
148
|
+
documentId,
|
|
149
|
+
transactionId,
|
|
150
|
+
message: `This document already exists.`,
|
|
151
|
+
})
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const newDocBase = {_type: action.documentType, _id: documentId}
|
|
155
|
+
const newDocWorking = {_type: action.documentType, _id: documentId}
|
|
156
|
+
const mutations: Mutation[] = [{create: newDocWorking}]
|
|
157
|
+
|
|
158
|
+
base = processMutations({
|
|
159
|
+
documents: base,
|
|
160
|
+
transactionId,
|
|
161
|
+
mutations: [{create: newDocBase}],
|
|
162
|
+
timestamp,
|
|
163
|
+
})
|
|
164
|
+
working = processMutations({
|
|
165
|
+
documents: working,
|
|
166
|
+
transactionId,
|
|
167
|
+
mutations,
|
|
168
|
+
timestamp,
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
if (!checkGrant(grants.create, working[documentId] as SanityDocument)) {
|
|
172
|
+
throw new PermissionActionError({
|
|
173
|
+
documentId,
|
|
174
|
+
transactionId,
|
|
175
|
+
message: `You do not have permission to create document "${documentId}".`,
|
|
176
|
+
})
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
outgoingMutations.push(...mutations)
|
|
180
|
+
outgoingActions.push({
|
|
181
|
+
actionType: 'sanity.action.document.create',
|
|
182
|
+
publishedId: documentId,
|
|
183
|
+
attributes: newDocWorking,
|
|
184
|
+
})
|
|
185
|
+
continue
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Standard draft/published logic
|
|
143
189
|
const draftId = getDraftId(documentId)
|
|
144
190
|
const publishedId = getPublishedId(documentId)
|
|
145
191
|
|
|
@@ -152,8 +198,18 @@ export function processActions({
|
|
|
152
198
|
}
|
|
153
199
|
|
|
154
200
|
// Spread the (possibly undefined) published version directly.
|
|
155
|
-
const newDocBase = {
|
|
156
|
-
|
|
201
|
+
const newDocBase = {
|
|
202
|
+
...base[publishedId],
|
|
203
|
+
_type: action.documentType,
|
|
204
|
+
_id: draftId,
|
|
205
|
+
...action.initialValue,
|
|
206
|
+
}
|
|
207
|
+
const newDocWorking = {
|
|
208
|
+
...working[publishedId],
|
|
209
|
+
_type: action.documentType,
|
|
210
|
+
_id: draftId,
|
|
211
|
+
...action.initialValue,
|
|
212
|
+
}
|
|
157
213
|
const mutations: Mutation[] = [{create: newDocWorking}]
|
|
158
214
|
|
|
159
215
|
base = processMutations({
|
|
@@ -188,6 +244,39 @@ export function processActions({
|
|
|
188
244
|
|
|
189
245
|
case 'document.delete': {
|
|
190
246
|
const documentId = action.documentId
|
|
247
|
+
|
|
248
|
+
if (action.liveEdit) {
|
|
249
|
+
// For liveEdit documents, delete directly
|
|
250
|
+
if (!working[documentId]) {
|
|
251
|
+
throw new ActionError({
|
|
252
|
+
documentId,
|
|
253
|
+
transactionId,
|
|
254
|
+
message: 'The document you are trying to delete does not exist.',
|
|
255
|
+
})
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (!checkGrant(grants.update, working[documentId])) {
|
|
259
|
+
throw new PermissionActionError({
|
|
260
|
+
documentId,
|
|
261
|
+
transactionId,
|
|
262
|
+
message: `You do not have permission to delete this document.`,
|
|
263
|
+
})
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const mutations: Mutation[] = [{delete: {id: documentId}}]
|
|
267
|
+
|
|
268
|
+
base = processMutations({documents: base, transactionId, mutations, timestamp})
|
|
269
|
+
working = processMutations({documents: working, transactionId, mutations, timestamp})
|
|
270
|
+
|
|
271
|
+
outgoingMutations.push(...mutations)
|
|
272
|
+
outgoingActions.push({
|
|
273
|
+
actionType: 'sanity.action.document.delete',
|
|
274
|
+
publishedId: documentId,
|
|
275
|
+
})
|
|
276
|
+
continue
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Standard draft/published logic
|
|
191
280
|
const draftId = getDraftId(documentId)
|
|
192
281
|
const publishedId = getPublishedId(documentId)
|
|
193
282
|
|
|
@@ -230,6 +319,16 @@ export function processActions({
|
|
|
230
319
|
|
|
231
320
|
case 'document.discard': {
|
|
232
321
|
const documentId = getId(action.documentId)
|
|
322
|
+
|
|
323
|
+
if (action.liveEdit) {
|
|
324
|
+
throw new ActionError({
|
|
325
|
+
documentId,
|
|
326
|
+
transactionId,
|
|
327
|
+
message: `Cannot discard changes for liveEdit document "${documentId}". LiveEdit documents do not support drafts.`,
|
|
328
|
+
})
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Standard draft/published logic
|
|
233
332
|
const draftId = getDraftId(documentId)
|
|
234
333
|
const mutations: Mutation[] = [{delete: {id: draftId}}]
|
|
235
334
|
|
|
@@ -262,6 +361,70 @@ export function processActions({
|
|
|
262
361
|
|
|
263
362
|
case 'document.edit': {
|
|
264
363
|
const documentId = getId(action.documentId)
|
|
364
|
+
|
|
365
|
+
if (action.liveEdit) {
|
|
366
|
+
// For liveEdit documents, edit directly without draft logic
|
|
367
|
+
const userPatches = action.patches?.map((patch) => ({patch: {id: documentId, ...patch}}))
|
|
368
|
+
|
|
369
|
+
// skip this action if there are no associated patches
|
|
370
|
+
if (!userPatches?.length) continue
|
|
371
|
+
|
|
372
|
+
if (!working[documentId] || !base[documentId]) {
|
|
373
|
+
throw new ActionError({
|
|
374
|
+
documentId,
|
|
375
|
+
transactionId,
|
|
376
|
+
message: `Cannot edit document because it does not exist.`,
|
|
377
|
+
})
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const baseBefore = base[documentId] as SanityDocument
|
|
381
|
+
if (userPatches) {
|
|
382
|
+
base = processMutations({
|
|
383
|
+
documents: base,
|
|
384
|
+
transactionId,
|
|
385
|
+
mutations: userPatches,
|
|
386
|
+
timestamp,
|
|
387
|
+
})
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const baseAfter = base[documentId] as SanityDocument
|
|
391
|
+
const patches = diffValue(baseBefore, baseAfter)
|
|
392
|
+
|
|
393
|
+
const workingBefore = working[documentId] as SanityDocument
|
|
394
|
+
if (!checkGrant(grants.update, workingBefore)) {
|
|
395
|
+
throw new PermissionActionError({
|
|
396
|
+
documentId,
|
|
397
|
+
transactionId,
|
|
398
|
+
message: `You do not have permission to edit document "${documentId}".`,
|
|
399
|
+
})
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const workingMutations = patches.map((patch) => ({patch: {id: documentId, ...patch}}))
|
|
403
|
+
|
|
404
|
+
working = processMutations({
|
|
405
|
+
documents: working,
|
|
406
|
+
transactionId,
|
|
407
|
+
mutations: workingMutations,
|
|
408
|
+
timestamp,
|
|
409
|
+
})
|
|
410
|
+
|
|
411
|
+
outgoingMutations.push(...workingMutations)
|
|
412
|
+
outgoingActions.push(
|
|
413
|
+
...patches.map(
|
|
414
|
+
(patch): HttpAction => ({
|
|
415
|
+
actionType: 'sanity.action.document.edit',
|
|
416
|
+
// Server requires draftId to have drafts. prefix for validation, even for liveEdit
|
|
417
|
+
draftId: getDraftId(documentId),
|
|
418
|
+
publishedId: documentId,
|
|
419
|
+
patch: patch as PatchOperations,
|
|
420
|
+
}),
|
|
421
|
+
),
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
continue
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Standard draft/published logic
|
|
265
428
|
const draftId = getDraftId(documentId)
|
|
266
429
|
const publishedId = getPublishedId(documentId)
|
|
267
430
|
const userPatches = action.patches?.map((patch) => ({patch: {id: draftId, ...patch}}))
|
|
@@ -352,6 +515,16 @@ export function processActions({
|
|
|
352
515
|
|
|
353
516
|
case 'document.publish': {
|
|
354
517
|
const documentId = getId(action.documentId)
|
|
518
|
+
|
|
519
|
+
if (action.liveEdit) {
|
|
520
|
+
throw new ActionError({
|
|
521
|
+
documentId,
|
|
522
|
+
transactionId,
|
|
523
|
+
message: `Cannot publish liveEdit document "${documentId}". LiveEdit documents do not support drafts or publishing.`,
|
|
524
|
+
})
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// Standard draft/published logic
|
|
355
528
|
const draftId = getDraftId(documentId)
|
|
356
529
|
const publishedId = getPublishedId(documentId)
|
|
357
530
|
|
|
@@ -418,6 +591,16 @@ export function processActions({
|
|
|
418
591
|
|
|
419
592
|
case 'document.unpublish': {
|
|
420
593
|
const documentId = getId(action.documentId)
|
|
594
|
+
|
|
595
|
+
if (action.liveEdit) {
|
|
596
|
+
throw new ActionError({
|
|
597
|
+
documentId,
|
|
598
|
+
transactionId,
|
|
599
|
+
message: `Cannot unpublish liveEdit document "${documentId}". LiveEdit documents do not support drafts or publishing.`,
|
|
600
|
+
})
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// Standard draft/published logic
|
|
421
604
|
const draftId = getDraftId(documentId)
|
|
422
605
|
const publishedId = getPublishedId(documentId)
|
|
423
606
|
|
package/src/document/reducers.ts
CHANGED
|
@@ -20,6 +20,7 @@ export type SyncTransactionState = Pick<
|
|
|
20
20
|
|
|
21
21
|
type ActionMap = {
|
|
22
22
|
create: 'sanity.action.document.version.create'
|
|
23
|
+
createLiveEdit: 'sanity.action.document.create'
|
|
23
24
|
discard: 'sanity.action.document.version.discard'
|
|
24
25
|
unpublish: 'sanity.action.document.unpublish'
|
|
25
26
|
delete: 'sanity.action.document.delete'
|
|
@@ -34,6 +35,7 @@ type OptimisticLock = {
|
|
|
34
35
|
|
|
35
36
|
export type HttpAction =
|
|
36
37
|
| {actionType: ActionMap['create']; publishedId: string; attributes: SanityDocumentLike}
|
|
38
|
+
| {actionType: ActionMap['createLiveEdit']; publishedId: string; attributes: SanityDocumentLike}
|
|
37
39
|
| {actionType: ActionMap['discard']; versionId: string; purge?: boolean}
|
|
38
40
|
| {actionType: ActionMap['unpublish']; draftId: string; publishedId: string}
|
|
39
41
|
| {actionType: ActionMap['delete']; publishedId: string; includeDrafts?: string[]}
|
|
@@ -550,13 +552,19 @@ export function removeSubscriptionIdFromDocument(
|
|
|
550
552
|
export function manageSubscriberIds(
|
|
551
553
|
{state}: StoreContext<SyncTransactionState>,
|
|
552
554
|
documentId: string | string[],
|
|
555
|
+
options?: {expandDraftPublished?: boolean},
|
|
553
556
|
): () => void {
|
|
557
|
+
const expandDraftPublished = options?.expandDraftPublished ?? true
|
|
554
558
|
const documentIds = Array.from(
|
|
555
559
|
new Set(
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
+
expandDraftPublished
|
|
561
|
+
? (Array.isArray(documentId) ? documentId : [documentId]).flatMap((id) => [
|
|
562
|
+
getPublishedId(id),
|
|
563
|
+
getDraftId(id),
|
|
564
|
+
])
|
|
565
|
+
: Array.isArray(documentId)
|
|
566
|
+
? documentId
|
|
567
|
+
: [documentId],
|
|
560
568
|
),
|
|
561
569
|
)
|
|
562
570
|
const subscriptionId = insecureRandomId()
|
|
@@ -579,8 +587,7 @@ export function manageSubscriberIds(
|
|
|
579
587
|
}
|
|
580
588
|
}
|
|
581
589
|
|
|
582
|
-
export function getDocumentIdsFromActions(
|
|
583
|
-
const actions = Array.isArray(action) ? action : [action]
|
|
590
|
+
export function getDocumentIdsFromActions(actions: DocumentAction[]): string[] {
|
|
584
591
|
return Array.from(
|
|
585
592
|
new Set(
|
|
586
593
|
actions
|
|
@@ -4,7 +4,7 @@ import {combineLatest, distinctUntilChanged, filter, map, of, Subscription, swit
|
|
|
4
4
|
|
|
5
5
|
import {getTokenState} from '../auth/authStore'
|
|
6
6
|
import {getClient} from '../client/clientStore'
|
|
7
|
-
import {bindActionByDataset} from '../store/createActionBinder'
|
|
7
|
+
import {bindActionByDataset, type BoundDatasetKey} from '../store/createActionBinder'
|
|
8
8
|
import {createStateSourceAction, type SelectorContext} from '../store/createStateSourceAction'
|
|
9
9
|
import {defineStore, type StoreContext} from '../store/defineStore'
|
|
10
10
|
import {type SanityUser} from '../users/types'
|
|
@@ -23,15 +23,21 @@ const getInitialState = (): PresenceStoreState => ({
|
|
|
23
23
|
})
|
|
24
24
|
|
|
25
25
|
/** @public */
|
|
26
|
-
export const presenceStore = defineStore<PresenceStoreState>({
|
|
26
|
+
export const presenceStore = defineStore<PresenceStoreState, BoundDatasetKey>({
|
|
27
27
|
name: 'presence',
|
|
28
28
|
getInitialState,
|
|
29
|
-
initialize: (context: StoreContext<PresenceStoreState>) => {
|
|
30
|
-
const {
|
|
29
|
+
initialize: (context: StoreContext<PresenceStoreState, BoundDatasetKey>) => {
|
|
30
|
+
const {
|
|
31
|
+
instance,
|
|
32
|
+
state,
|
|
33
|
+
key: {projectId, dataset},
|
|
34
|
+
} = context
|
|
31
35
|
const sessionId = crypto.randomUUID()
|
|
32
36
|
|
|
33
37
|
const client = getClient(instance, {
|
|
34
38
|
apiVersion: '2022-06-30',
|
|
39
|
+
projectId,
|
|
40
|
+
dataset,
|
|
35
41
|
})
|
|
36
42
|
|
|
37
43
|
const token$ = getTokenState(instance).observable.pipe(distinctUntilChanged())
|
|
@@ -112,9 +118,9 @@ const selectPresence = createSelector(
|
|
|
112
118
|
export const getPresence = bindActionByDataset(
|
|
113
119
|
presenceStore,
|
|
114
120
|
createStateSourceAction({
|
|
115
|
-
selector: (context: SelectorContext<PresenceStoreState
|
|
121
|
+
selector: (context: SelectorContext<PresenceStoreState>, _?): UserPresence[] =>
|
|
116
122
|
selectPresence(context.state),
|
|
117
|
-
onSubscribe: (context) => {
|
|
123
|
+
onSubscribe: (context: StoreContext<PresenceStoreState, BoundDatasetKey>, _?) => {
|
|
118
124
|
const userIds$ = context.state.observable.pipe(
|
|
119
125
|
map((state) =>
|
|
120
126
|
Array.from(state.locations.values())
|
|
@@ -134,7 +140,7 @@ export const getPresence = bindActionByDataset(
|
|
|
134
140
|
getUserState(context.instance, {
|
|
135
141
|
userId,
|
|
136
142
|
resourceType: 'project',
|
|
137
|
-
projectId: context.
|
|
143
|
+
projectId: context.key.projectId,
|
|
138
144
|
}).pipe(filter((v): v is NonNullable<typeof v> => !!v)),
|
|
139
145
|
)
|
|
140
146
|
return combineLatest(userObservables)
|
|
@@ -18,9 +18,17 @@ describe('previewStore', () => {
|
|
|
18
18
|
|
|
19
19
|
const instance = createSanityInstance({projectId: 'p', dataset: 'd'})
|
|
20
20
|
|
|
21
|
-
const {state, dispose} = createStoreInstance(
|
|
21
|
+
const {state, dispose} = createStoreInstance(
|
|
22
|
+
instance,
|
|
23
|
+
{name: 'p.d', projectId: 'p', dataset: 'd'},
|
|
24
|
+
previewStore,
|
|
25
|
+
)
|
|
22
26
|
|
|
23
|
-
expect(subscribeToStateAndFetchBatches).toHaveBeenCalledWith({
|
|
27
|
+
expect(subscribeToStateAndFetchBatches).toHaveBeenCalledWith({
|
|
28
|
+
instance,
|
|
29
|
+
state,
|
|
30
|
+
key: {name: 'p.d', projectId: 'p', dataset: 'd'},
|
|
31
|
+
})
|
|
24
32
|
|
|
25
33
|
dispose()
|
|
26
34
|
instance.dispose()
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import {type BoundDatasetKey} from '../store/createActionBinder'
|
|
1
2
|
import {defineStore} from '../store/defineStore'
|
|
2
3
|
import {subscribeToStateAndFetchBatches} from './subscribeToStateAndFetchBatches'
|
|
3
4
|
|
|
@@ -79,7 +80,7 @@ export interface PreviewStoreState {
|
|
|
79
80
|
subscriptions: {[TDocumentId in string]?: {[TSubscriptionId in string]?: true}}
|
|
80
81
|
}
|
|
81
82
|
|
|
82
|
-
export const previewStore = defineStore<PreviewStoreState>({
|
|
83
|
+
export const previewStore = defineStore<PreviewStoreState, BoundDatasetKey>({
|
|
83
84
|
name: 'Preview',
|
|
84
85
|
getInitialState() {
|
|
85
86
|
return {
|
|
@@ -2,6 +2,7 @@ import {NEVER, Observable, type Observer} from 'rxjs'
|
|
|
2
2
|
import {describe, expect, it, vi} from 'vitest'
|
|
3
3
|
|
|
4
4
|
import {getQueryState, resolveQuery} from '../query/queryStore'
|
|
5
|
+
import {type BoundDatasetKey} from '../store/createActionBinder'
|
|
5
6
|
import {createSanityInstance, type SanityInstance} from '../store/createSanityInstance'
|
|
6
7
|
import {type StateSource} from '../store/createStateSourceAction'
|
|
7
8
|
import {createStoreState, type StoreState} from '../store/createStoreState'
|
|
@@ -14,6 +15,7 @@ vi.mock('../query/queryStore')
|
|
|
14
15
|
describe('subscribeToStateAndFetchBatches', () => {
|
|
15
16
|
let instance: SanityInstance
|
|
16
17
|
let state: StoreState<PreviewStoreState>
|
|
18
|
+
let key: BoundDatasetKey
|
|
17
19
|
|
|
18
20
|
beforeEach(() => {
|
|
19
21
|
vi.clearAllMocks()
|
|
@@ -22,6 +24,7 @@ describe('subscribeToStateAndFetchBatches', () => {
|
|
|
22
24
|
subscriptions: {},
|
|
23
25
|
values: {},
|
|
24
26
|
})
|
|
27
|
+
key = {name: 'test.test', projectId: 'test', dataset: 'test'}
|
|
25
28
|
|
|
26
29
|
vi.mocked(getQueryState).mockReturnValue({
|
|
27
30
|
getCurrent: () => undefined,
|
|
@@ -36,7 +39,7 @@ describe('subscribeToStateAndFetchBatches', () => {
|
|
|
36
39
|
})
|
|
37
40
|
|
|
38
41
|
it('batches rapid subscription changes into single requests', async () => {
|
|
39
|
-
const subscription = subscribeToStateAndFetchBatches({instance, state})
|
|
42
|
+
const subscription = subscribeToStateAndFetchBatches({instance, state, key})
|
|
40
43
|
|
|
41
44
|
// Add multiple subscriptions rapidly
|
|
42
45
|
state.set('addSubscription1', {
|
|
@@ -77,7 +80,7 @@ describe('subscribeToStateAndFetchBatches', () => {
|
|
|
77
80
|
observable: new Observable(subscriber),
|
|
78
81
|
} as StateSource<PreviewQueryResult[] | undefined>)
|
|
79
82
|
|
|
80
|
-
const subscription = subscribeToStateAndFetchBatches({instance, state})
|
|
83
|
+
const subscription = subscribeToStateAndFetchBatches({instance, state, key})
|
|
81
84
|
|
|
82
85
|
expect(subscriber).not.toHaveBeenCalled()
|
|
83
86
|
|
|
@@ -126,7 +129,7 @@ describe('subscribeToStateAndFetchBatches', () => {
|
|
|
126
129
|
subscriptions: {doc1: {sub1: true}},
|
|
127
130
|
})
|
|
128
131
|
|
|
129
|
-
const subscription = subscribeToStateAndFetchBatches({instance, state})
|
|
132
|
+
const subscription = subscribeToStateAndFetchBatches({instance, state, key})
|
|
130
133
|
|
|
131
134
|
// Add a subscription for a document already in the batch
|
|
132
135
|
state.set('addSubscriptionAlreadyInBatch', (prev) => ({
|
|
@@ -152,7 +155,7 @@ describe('subscribeToStateAndFetchBatches', () => {
|
|
|
152
155
|
|
|
153
156
|
it('cancels and restarts fetches when subscription set changes', async () => {
|
|
154
157
|
const abortSpy = vi.spyOn(AbortController.prototype, 'abort')
|
|
155
|
-
const subscription = subscribeToStateAndFetchBatches({instance, state})
|
|
158
|
+
const subscription = subscribeToStateAndFetchBatches({instance, state, key})
|
|
156
159
|
|
|
157
160
|
// Add initial subscription
|
|
158
161
|
state.set('addSubscription1', {
|
|
@@ -182,7 +185,7 @@ describe('subscribeToStateAndFetchBatches', () => {
|
|
|
182
185
|
observable: new Observable(subscriber),
|
|
183
186
|
} as StateSource<PreviewQueryResult[] | undefined>)
|
|
184
187
|
|
|
185
|
-
const subscription = subscribeToStateAndFetchBatches({instance, state})
|
|
188
|
+
const subscription = subscribeToStateAndFetchBatches({instance, state, key})
|
|
186
189
|
|
|
187
190
|
// Add a subscription
|
|
188
191
|
state.set('addSubscription', {
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
} from 'rxjs'
|
|
16
16
|
|
|
17
17
|
import {getQueryState, resolveQuery} from '../query/queryStore'
|
|
18
|
+
import {type BoundDatasetKey} from '../store/createActionBinder'
|
|
18
19
|
import {type StoreContext} from '../store/defineStore'
|
|
19
20
|
import {createPreviewQuery, processPreviewQuery} from './previewQuery'
|
|
20
21
|
import {type PreviewQueryResult, type PreviewStoreState} from './previewStore'
|
|
@@ -28,7 +29,8 @@ const isSetEqual = <T>(a: Set<T>, b: Set<T>) =>
|
|
|
28
29
|
export const subscribeToStateAndFetchBatches = ({
|
|
29
30
|
state,
|
|
30
31
|
instance,
|
|
31
|
-
|
|
32
|
+
key: {projectId, dataset},
|
|
33
|
+
}: StoreContext<PreviewStoreState, BoundDatasetKey>): Subscription => {
|
|
32
34
|
const newSubscriberIds$ = state.observable.pipe(
|
|
33
35
|
map(({subscriptions}) => new Set(Object.keys(subscriptions))),
|
|
34
36
|
distinctUntilChanged(isSetEqual),
|
|
@@ -64,6 +66,8 @@ export const subscribeToStateAndFetchBatches = ({
|
|
|
64
66
|
params,
|
|
65
67
|
tag: PREVIEW_TAG,
|
|
66
68
|
perspective: PREVIEW_PERSPECTIVE,
|
|
69
|
+
projectId,
|
|
70
|
+
dataset,
|
|
67
71
|
})
|
|
68
72
|
const source$ = defer(() => {
|
|
69
73
|
if (getCurrent() === undefined) {
|
|
@@ -74,6 +78,8 @@ export const subscribeToStateAndFetchBatches = ({
|
|
|
74
78
|
tag: PREVIEW_TAG,
|
|
75
79
|
perspective: PREVIEW_PERSPECTIVE,
|
|
76
80
|
signal: controller.signal,
|
|
81
|
+
projectId,
|
|
82
|
+
dataset,
|
|
77
83
|
}),
|
|
78
84
|
).pipe(switchMap(() => observable))
|
|
79
85
|
}
|
|
@@ -91,8 +97,8 @@ export const subscribeToStateAndFetchBatches = ({
|
|
|
91
97
|
}),
|
|
92
98
|
map(({ids, data}) => ({
|
|
93
99
|
values: processPreviewQuery({
|
|
94
|
-
projectId
|
|
95
|
-
dataset
|
|
100
|
+
projectId,
|
|
101
|
+
dataset,
|
|
96
102
|
ids,
|
|
97
103
|
results: data,
|
|
98
104
|
}),
|
|
@@ -26,10 +26,26 @@ describe('projectionStore', () => {
|
|
|
26
26
|
|
|
27
27
|
const instance = createSanityInstance({projectId: 'p', dataset: 'd'})
|
|
28
28
|
|
|
29
|
-
const {state, dispose} = createStoreInstance(
|
|
29
|
+
const {state, dispose} = createStoreInstance(
|
|
30
|
+
instance,
|
|
31
|
+
{
|
|
32
|
+
name: 'p.d',
|
|
33
|
+
projectId: 'p',
|
|
34
|
+
dataset: 'd',
|
|
35
|
+
},
|
|
36
|
+
projectionStore,
|
|
37
|
+
)
|
|
30
38
|
|
|
31
39
|
expect(subscribeToStateAndFetchBatches).toHaveBeenCalledOnce()
|
|
32
|
-
expect(subscribeToStateAndFetchBatches).toHaveBeenCalledWith({
|
|
40
|
+
expect(subscribeToStateAndFetchBatches).toHaveBeenCalledWith({
|
|
41
|
+
instance,
|
|
42
|
+
state,
|
|
43
|
+
key: {
|
|
44
|
+
name: 'p.d',
|
|
45
|
+
projectId: 'p',
|
|
46
|
+
dataset: 'd',
|
|
47
|
+
},
|
|
48
|
+
})
|
|
33
49
|
|
|
34
50
|
dispose()
|
|
35
51
|
instance.dispose()
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import {type BoundDatasetKey} from '../store/createActionBinder'
|
|
1
2
|
import {defineStore} from '../store/defineStore'
|
|
2
3
|
import {subscribeToStateAndFetchBatches} from './subscribeToStateAndFetchBatches'
|
|
3
4
|
import {type ProjectionStoreState} from './types'
|
|
4
5
|
|
|
5
|
-
export const projectionStore = defineStore<ProjectionStoreState>({
|
|
6
|
+
export const projectionStore = defineStore<ProjectionStoreState, BoundDatasetKey>({
|
|
6
7
|
name: 'Projection',
|
|
7
8
|
getInitialState() {
|
|
8
9
|
return {
|
|
@@ -15,6 +15,7 @@ vi.mock('../query/queryStore')
|
|
|
15
15
|
describe('subscribeToStateAndFetchBatches', () => {
|
|
16
16
|
let instance: SanityInstance
|
|
17
17
|
let state: StoreState<ProjectionStoreState>
|
|
18
|
+
const key = {name: 'test.test', projectId: 'test', dataset: 'test'}
|
|
18
19
|
|
|
19
20
|
beforeEach(() => {
|
|
20
21
|
vi.clearAllMocks()
|
|
@@ -38,7 +39,7 @@ describe('subscribeToStateAndFetchBatches', () => {
|
|
|
38
39
|
})
|
|
39
40
|
|
|
40
41
|
it('batches rapid subscription changes into single requests', async () => {
|
|
41
|
-
const subscription = subscribeToStateAndFetchBatches({instance, state})
|
|
42
|
+
const subscription = subscribeToStateAndFetchBatches({instance, state, key})
|
|
42
43
|
const projection = '{title, description}'
|
|
43
44
|
const projectionHash = hashString(projection)
|
|
44
45
|
|
|
@@ -95,7 +96,7 @@ describe('subscribeToStateAndFetchBatches', () => {
|
|
|
95
96
|
observable: new Observable(subscriber),
|
|
96
97
|
} as StateSource<ProjectionQueryResult[] | undefined>)
|
|
97
98
|
|
|
98
|
-
const subscription = subscribeToStateAndFetchBatches({instance, state})
|
|
99
|
+
const subscription = subscribeToStateAndFetchBatches({instance, state, key})
|
|
99
100
|
const projection = '{title}'
|
|
100
101
|
const projectionHash = hashString(projection)
|
|
101
102
|
|
|
@@ -166,7 +167,7 @@ describe('subscribeToStateAndFetchBatches', () => {
|
|
|
166
167
|
subscriptions: {doc1: {[projectionHash]: {sub1: true}}},
|
|
167
168
|
})
|
|
168
169
|
|
|
169
|
-
const subscription = subscribeToStateAndFetchBatches({instance, state})
|
|
170
|
+
const subscription = subscribeToStateAndFetchBatches({instance, state, key})
|
|
170
171
|
|
|
171
172
|
// Add another subscription for doc1 (same hash)
|
|
172
173
|
state.set('addSubscriptionAlreadyInBatch', (prev) => ({
|
|
@@ -218,7 +219,7 @@ describe('subscribeToStateAndFetchBatches', () => {
|
|
|
218
219
|
|
|
219
220
|
it('cancels and restarts fetches when subscription set changes', async () => {
|
|
220
221
|
const abortSpy = vi.spyOn(AbortController.prototype, 'abort')
|
|
221
|
-
const subscription = subscribeToStateAndFetchBatches({instance, state})
|
|
222
|
+
const subscription = subscribeToStateAndFetchBatches({instance, state, key})
|
|
222
223
|
const projection = '{title, description}'
|
|
223
224
|
const projectionHash = hashString(projection)
|
|
224
225
|
const projection2 = '{_id}' // Different projection
|
|
@@ -266,7 +267,7 @@ describe('subscribeToStateAndFetchBatches', () => {
|
|
|
266
267
|
observable: new Observable(subscriber),
|
|
267
268
|
} as StateSource<ProjectionQueryResult[] | undefined>)
|
|
268
269
|
|
|
269
|
-
const subscription = subscribeToStateAndFetchBatches({instance, state})
|
|
270
|
+
const subscription = subscribeToStateAndFetchBatches({instance, state, key})
|
|
270
271
|
const projection = '{title, description}'
|
|
271
272
|
const projectionHash = hashString(projection)
|
|
272
273
|
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
} from 'rxjs'
|
|
18
18
|
|
|
19
19
|
import {getQueryState, resolveQuery} from '../query/queryStore'
|
|
20
|
+
import {type BoundDatasetKey} from '../store/createActionBinder'
|
|
20
21
|
import {type StoreContext} from '../store/defineStore'
|
|
21
22
|
import {
|
|
22
23
|
createProjectionQuery,
|
|
@@ -34,7 +35,8 @@ const isSetEqual = <T>(a: Set<T>, b: Set<T>) =>
|
|
|
34
35
|
export const subscribeToStateAndFetchBatches = ({
|
|
35
36
|
state,
|
|
36
37
|
instance,
|
|
37
|
-
|
|
38
|
+
key: {projectId, dataset},
|
|
39
|
+
}: StoreContext<ProjectionStoreState, BoundDatasetKey>): Subscription => {
|
|
38
40
|
const documentProjections$ = state.observable.pipe(
|
|
39
41
|
map((s) => s.documentProjections),
|
|
40
42
|
distinctUntilChanged(isEqual),
|
|
@@ -94,6 +96,8 @@ export const subscribeToStateAndFetchBatches = ({
|
|
|
94
96
|
const {getCurrent, observable} = getQueryState<ProjectionQueryResult[]>(instance, {
|
|
95
97
|
query,
|
|
96
98
|
params,
|
|
99
|
+
projectId,
|
|
100
|
+
dataset,
|
|
97
101
|
tag: PROJECTION_TAG,
|
|
98
102
|
perspective: PROJECTION_PERSPECTIVE,
|
|
99
103
|
})
|
|
@@ -104,6 +108,8 @@ export const subscribeToStateAndFetchBatches = ({
|
|
|
104
108
|
resolveQuery<ProjectionQueryResult[]>(instance, {
|
|
105
109
|
query,
|
|
106
110
|
params,
|
|
111
|
+
projectId,
|
|
112
|
+
dataset,
|
|
107
113
|
tag: PROJECTION_TAG,
|
|
108
114
|
perspective: PROJECTION_PERSPECTIVE,
|
|
109
115
|
signal: controller.signal,
|
|
@@ -125,8 +131,8 @@ export const subscribeToStateAndFetchBatches = ({
|
|
|
125
131
|
}),
|
|
126
132
|
map(({ids, data}) =>
|
|
127
133
|
processProjectionQuery({
|
|
128
|
-
projectId
|
|
129
|
-
dataset
|
|
134
|
+
projectId,
|
|
135
|
+
dataset,
|
|
130
136
|
ids,
|
|
131
137
|
results: data,
|
|
132
138
|
}),
|
package/src/query/queryStore.ts
CHANGED
|
@@ -23,9 +23,9 @@ import {
|
|
|
23
23
|
} from 'rxjs'
|
|
24
24
|
|
|
25
25
|
import {getClientState} from '../client/clientStore'
|
|
26
|
-
import {type DatasetHandle} from '../config/sanityConfig'
|
|
26
|
+
import {type DatasetHandle, type DocumentSource} from '../config/sanityConfig'
|
|
27
27
|
import {getPerspectiveState} from '../releases/getPerspectiveState'
|
|
28
|
-
import {
|
|
28
|
+
import {bindActionBySource} from '../store/createActionBinder'
|
|
29
29
|
import {type SanityInstance} from '../store/createSanityInstance'
|
|
30
30
|
import {
|
|
31
31
|
createStateSourceAction,
|
|
@@ -62,6 +62,7 @@ export interface QueryOptions<
|
|
|
62
62
|
DatasetHandle<TDataset, TProjectId> {
|
|
63
63
|
query: TQuery
|
|
64
64
|
params?: Record<string, unknown>
|
|
65
|
+
source?: DocumentSource
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
/**
|
|
@@ -160,6 +161,7 @@ const listenForNewSubscribersAndFetch = ({state, instance}: StoreContext<QuerySt
|
|
|
160
161
|
projectId,
|
|
161
162
|
dataset,
|
|
162
163
|
tag,
|
|
164
|
+
source,
|
|
163
165
|
perspective: perspectiveFromOptions,
|
|
164
166
|
...restOptions
|
|
165
167
|
} = parseQueryKey(group$.key)
|
|
@@ -172,6 +174,7 @@ const listenForNewSubscribersAndFetch = ({state, instance}: StoreContext<QuerySt
|
|
|
172
174
|
apiVersion: QUERY_STORE_API_VERSION,
|
|
173
175
|
projectId,
|
|
174
176
|
dataset,
|
|
177
|
+
source,
|
|
175
178
|
}).observable
|
|
176
179
|
|
|
177
180
|
return combineLatest([lastLiveEventId$, client$, perspective$]).pipe(
|
|
@@ -290,7 +293,7 @@ export function getQueryState(
|
|
|
290
293
|
): ReturnType<typeof _getQueryState> {
|
|
291
294
|
return _getQueryState(...args)
|
|
292
295
|
}
|
|
293
|
-
const _getQueryState =
|
|
296
|
+
const _getQueryState = bindActionBySource(
|
|
294
297
|
queryStore,
|
|
295
298
|
createStateSourceAction({
|
|
296
299
|
selector: ({state, instance}: SelectorContext<QueryStoreState>, options: QueryOptions) => {
|
|
@@ -349,7 +352,7 @@ export function resolveQuery<TData>(
|
|
|
349
352
|
export function resolveQuery(...args: Parameters<typeof _resolveQuery>): Promise<unknown> {
|
|
350
353
|
return _resolveQuery(...args)
|
|
351
354
|
}
|
|
352
|
-
const _resolveQuery =
|
|
355
|
+
const _resolveQuery = bindActionBySource(
|
|
353
356
|
queryStore,
|
|
354
357
|
({state, instance}, {signal, ...options}: ResolveQueryOptions) => {
|
|
355
358
|
const normalized = normalizeOptionsWithPerspective(instance, options)
|