@sanity/sdk 2.4.0 → 2.6.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.
Files changed (57) hide show
  1. package/dist/index.d.ts +346 -110
  2. package/dist/index.js +428 -136
  3. package/dist/index.js.map +1 -1
  4. package/package.json +10 -9
  5. package/src/_exports/index.ts +15 -3
  6. package/src/auth/authStore.test.ts +13 -13
  7. package/src/auth/refreshStampedToken.test.ts +16 -16
  8. package/src/auth/subscribeToStateAndFetchCurrentUser.test.ts +6 -6
  9. package/src/auth/subscribeToStorageEventsAndSetToken.test.ts +4 -4
  10. package/src/client/clientStore.test.ts +45 -43
  11. package/src/client/clientStore.ts +23 -9
  12. package/src/comlink/controller/actions/destroyController.test.ts +2 -2
  13. package/src/comlink/controller/actions/getOrCreateChannel.test.ts +6 -6
  14. package/src/comlink/controller/actions/getOrCreateController.test.ts +5 -5
  15. package/src/comlink/controller/actions/getOrCreateController.ts +1 -1
  16. package/src/comlink/controller/actions/releaseChannel.test.ts +3 -2
  17. package/src/comlink/controller/comlinkControllerStore.test.ts +4 -4
  18. package/src/comlink/node/actions/getOrCreateNode.test.ts +7 -7
  19. package/src/comlink/node/actions/releaseNode.test.ts +2 -2
  20. package/src/comlink/node/comlinkNodeStore.test.ts +4 -3
  21. package/src/config/loggingConfig.ts +149 -0
  22. package/src/config/sanityConfig.ts +47 -23
  23. package/src/document/actions.ts +11 -7
  24. package/src/document/applyDocumentActions.test.ts +9 -6
  25. package/src/document/applyDocumentActions.ts +9 -49
  26. package/src/document/documentStore.test.ts +128 -115
  27. package/src/document/documentStore.ts +40 -10
  28. package/src/document/permissions.test.ts +9 -9
  29. package/src/document/permissions.ts +17 -7
  30. package/src/document/processActions.test.ts +248 -0
  31. package/src/document/processActions.ts +173 -0
  32. package/src/document/reducers.ts +13 -6
  33. package/src/presence/presenceStore.ts +13 -7
  34. package/src/preview/previewStore.test.ts +10 -2
  35. package/src/preview/previewStore.ts +2 -1
  36. package/src/preview/subscribeToStateAndFetchBatches.test.ts +8 -5
  37. package/src/preview/subscribeToStateAndFetchBatches.ts +9 -3
  38. package/src/projection/projectionStore.test.ts +18 -2
  39. package/src/projection/projectionStore.ts +2 -1
  40. package/src/projection/subscribeToStateAndFetchBatches.test.ts +6 -5
  41. package/src/projection/subscribeToStateAndFetchBatches.ts +9 -3
  42. package/src/query/queryStore.ts +3 -1
  43. package/src/releases/getPerspectiveState.ts +2 -2
  44. package/src/releases/releasesStore.ts +10 -4
  45. package/src/store/createActionBinder.test.ts +8 -6
  46. package/src/store/createActionBinder.ts +54 -28
  47. package/src/store/createSanityInstance.test.ts +85 -1
  48. package/src/store/createSanityInstance.ts +53 -4
  49. package/src/store/createStateSourceAction.test.ts +12 -11
  50. package/src/store/createStateSourceAction.ts +6 -6
  51. package/src/store/createStoreInstance.test.ts +29 -16
  52. package/src/store/createStoreInstance.ts +6 -5
  53. package/src/store/defineStore.test.ts +1 -1
  54. package/src/store/defineStore.ts +12 -7
  55. package/src/utils/logger-usage-example.md +141 -0
  56. package/src/utils/logger.test.ts +757 -0
  57. package/src/utils/logger.ts +537 -0
@@ -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
 
@@ -198,6 +244,39 @@ export function processActions({
198
244
 
199
245
  case 'document.delete': {
200
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
201
280
  const draftId = getDraftId(documentId)
202
281
  const publishedId = getPublishedId(documentId)
203
282
 
@@ -240,6 +319,16 @@ export function processActions({
240
319
 
241
320
  case 'document.discard': {
242
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
243
332
  const draftId = getDraftId(documentId)
244
333
  const mutations: Mutation[] = [{delete: {id: draftId}}]
245
334
 
@@ -272,6 +361,70 @@ export function processActions({
272
361
 
273
362
  case 'document.edit': {
274
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
275
428
  const draftId = getDraftId(documentId)
276
429
  const publishedId = getPublishedId(documentId)
277
430
  const userPatches = action.patches?.map((patch) => ({patch: {id: draftId, ...patch}}))
@@ -362,6 +515,16 @@ export function processActions({
362
515
 
363
516
  case 'document.publish': {
364
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
365
528
  const draftId = getDraftId(documentId)
366
529
  const publishedId = getPublishedId(documentId)
367
530
 
@@ -428,6 +591,16 @@ export function processActions({
428
591
 
429
592
  case 'document.unpublish': {
430
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
431
604
  const draftId = getDraftId(documentId)
432
605
  const publishedId = getPublishedId(documentId)
433
606
 
@@ -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
- (Array.isArray(documentId) ? documentId : [documentId]).flatMap((id) => [
557
- getPublishedId(id),
558
- getDraftId(id),
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(action: DocumentAction | DocumentAction[]): string[] {
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 {instance, state} = context
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>): UserPresence[] =>
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.instance.config.projectId,
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(instance, previewStore)
21
+ const {state, dispose} = createStoreInstance(
22
+ instance,
23
+ {name: 'p.d', projectId: 'p', dataset: 'd'},
24
+ previewStore,
25
+ )
22
26
 
23
- expect(subscribeToStateAndFetchBatches).toHaveBeenCalledWith({instance, state})
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
- }: StoreContext<PreviewStoreState>): Subscription => {
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: instance.config.projectId!,
95
- dataset: instance.config.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(instance, projectionStore)
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({instance, state})
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
- }: StoreContext<ProjectionStoreState>): Subscription => {
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: instance.config.projectId!,
129
- dataset: instance.config.dataset!,
134
+ projectId,
135
+ dataset,
130
136
  ids,
131
137
  results: data,
132
138
  }),
@@ -58,7 +58,9 @@ export interface QueryOptions<
58
58
  TQuery extends string = string,
59
59
  TDataset extends string = string,
60
60
  TProjectId extends string = string,
61
- > extends Pick<ResponseQueryOptions, 'useCdn' | 'cache' | 'next' | 'cacheMode' | 'tag'>,
61
+ >
62
+ extends
63
+ Pick<ResponseQueryOptions, 'useCdn' | 'cache' | 'next' | 'cacheMode' | 'tag'>,
62
64
  DatasetHandle<TDataset, TProjectId> {
63
65
  query: TQuery
64
66
  params?: Record<string, unknown>
@@ -17,13 +17,13 @@ const DEFAULT_PERSPECTIVE = 'drafts'
17
17
  // Cache for options
18
18
  const optionsCache = new Map<string, Map<string, PerspectiveHandle>>()
19
19
 
20
- const selectInstancePerspective = (context: SelectorContext<ReleasesStoreState>) =>
20
+ const selectInstancePerspective = (context: SelectorContext<ReleasesStoreState>, _?: unknown) =>
21
21
  context.instance.config.perspective
22
22
  const selectActiveReleases = (context: SelectorContext<ReleasesStoreState>) =>
23
23
  context.state.activeReleases
24
24
  const selectOptions = (
25
25
  _context: SelectorContext<ReleasesStoreState>,
26
- options?: PerspectiveHandle,
26
+ options: PerspectiveHandle & {projectId?: string; dataset?: string},
27
27
  ) => options
28
28
 
29
29
  const memoizedOptionsSelector = createSelector(
@@ -3,7 +3,7 @@ import {type SanityDocument} from '@sanity/types'
3
3
  import {catchError, EMPTY, retry, switchMap, timer} from 'rxjs'
4
4
 
5
5
  import {getClientState} from '../client/clientStore'
6
- import {bindActionByDataset} from '../store/createActionBinder'
6
+ import {bindActionByDataset, type BoundDatasetKey} from '../store/createActionBinder'
7
7
  import {createStateSourceAction} from '../store/createStateSourceAction'
8
8
  import {defineStore, type StoreContext} from '../store/defineStore'
9
9
  import {listenQuery} from '../utils/listenQuery'
@@ -32,7 +32,7 @@ export interface ReleasesStoreState {
32
32
  error?: unknown
33
33
  }
34
34
 
35
- export const releasesStore = defineStore<ReleasesStoreState>({
35
+ export const releasesStore = defineStore<ReleasesStoreState, BoundDatasetKey>({
36
36
  name: 'Releases',
37
37
  getInitialState: (): ReleasesStoreState => ({
38
38
  activeReleases: undefined,
@@ -50,17 +50,23 @@ export const releasesStore = defineStore<ReleasesStoreState>({
50
50
  export const getActiveReleasesState = bindActionByDataset(
51
51
  releasesStore,
52
52
  createStateSourceAction({
53
- selector: ({state}) => state.activeReleases,
53
+ selector: ({state}, _?) => state.activeReleases,
54
54
  }),
55
55
  )
56
56
 
57
57
  const RELEASES_QUERY = 'releases::all()'
58
58
  const QUERY_PARAMS = {}
59
59
 
60
- const subscribeToReleases = ({instance, state}: StoreContext<ReleasesStoreState>) => {
60
+ const subscribeToReleases = ({
61
+ instance,
62
+ state,
63
+ key: {projectId, dataset},
64
+ }: StoreContext<ReleasesStoreState, BoundDatasetKey>) => {
61
65
  return getClientState(instance, {
62
66
  apiVersion: '2025-04-10',
63
67
  perspective: 'raw',
68
+ projectId,
69
+ dataset,
64
70
  })
65
71
  .observable.pipe(
66
72
  switchMap((client: SanityClient) =>