@sanity/sdk 2.4.0 → 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.
Files changed (47) hide show
  1. package/dist/index.d.ts +35 -90
  2. package/dist/index.js +237 -111
  3. package/dist/index.js.map +1 -1
  4. package/package.json +9 -8
  5. package/src/auth/authStore.test.ts +13 -13
  6. package/src/auth/refreshStampedToken.test.ts +16 -16
  7. package/src/auth/subscribeToStateAndFetchCurrentUser.test.ts +6 -6
  8. package/src/auth/subscribeToStorageEventsAndSetToken.test.ts +4 -4
  9. package/src/comlink/controller/actions/destroyController.test.ts +2 -2
  10. package/src/comlink/controller/actions/getOrCreateChannel.test.ts +6 -6
  11. package/src/comlink/controller/actions/getOrCreateController.test.ts +5 -5
  12. package/src/comlink/controller/actions/getOrCreateController.ts +1 -1
  13. package/src/comlink/controller/actions/releaseChannel.test.ts +3 -2
  14. package/src/comlink/controller/comlinkControllerStore.test.ts +4 -4
  15. package/src/comlink/node/actions/getOrCreateNode.test.ts +7 -7
  16. package/src/comlink/node/actions/releaseNode.test.ts +2 -2
  17. package/src/comlink/node/comlinkNodeStore.test.ts +4 -3
  18. package/src/config/sanityConfig.ts +8 -3
  19. package/src/document/actions.ts +11 -7
  20. package/src/document/applyDocumentActions.test.ts +9 -6
  21. package/src/document/applyDocumentActions.ts +9 -49
  22. package/src/document/documentStore.test.ts +128 -115
  23. package/src/document/documentStore.ts +40 -10
  24. package/src/document/permissions.test.ts +9 -9
  25. package/src/document/permissions.ts +17 -7
  26. package/src/document/processActions.test.ts +248 -0
  27. package/src/document/processActions.ts +173 -0
  28. package/src/document/reducers.ts +13 -6
  29. package/src/presence/presenceStore.ts +13 -7
  30. package/src/preview/previewStore.test.ts +10 -2
  31. package/src/preview/previewStore.ts +2 -1
  32. package/src/preview/subscribeToStateAndFetchBatches.test.ts +8 -5
  33. package/src/preview/subscribeToStateAndFetchBatches.ts +9 -3
  34. package/src/projection/projectionStore.test.ts +18 -2
  35. package/src/projection/projectionStore.ts +2 -1
  36. package/src/projection/subscribeToStateAndFetchBatches.test.ts +6 -5
  37. package/src/projection/subscribeToStateAndFetchBatches.ts +9 -3
  38. package/src/releases/getPerspectiveState.ts +2 -2
  39. package/src/releases/releasesStore.ts +10 -4
  40. package/src/store/createActionBinder.test.ts +8 -6
  41. package/src/store/createActionBinder.ts +44 -29
  42. package/src/store/createStateSourceAction.test.ts +12 -11
  43. package/src/store/createStateSourceAction.ts +6 -6
  44. package/src/store/createStoreInstance.test.ts +29 -16
  45. package/src/store/createStoreInstance.ts +6 -5
  46. package/src/store/defineStore.test.ts +1 -1
  47. package/src/store/defineStore.ts +12 -7
@@ -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
  }),
@@ -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) =>
@@ -12,7 +12,7 @@ beforeEach(() => vi.mocked(createStoreInstance).mockClear())
12
12
 
13
13
  describe('createActionBinder', () => {
14
14
  it('should bind an action and call it with correct context and parameters, using caching', () => {
15
- const binder = createActionBinder(() => '')
15
+ const binder = createActionBinder((..._rest) => ({name: ''}))
16
16
  const storeDefinition = {
17
17
  name: 'TestStore',
18
18
  getInitialState: () => ({counter: 0}),
@@ -37,7 +37,9 @@ describe('createActionBinder', () => {
37
37
  })
38
38
 
39
39
  it('should create separate store instances for different composite keys', () => {
40
- const binder = createActionBinder(({projectId, dataset}) => `${projectId}.${dataset}`)
40
+ const binder = createActionBinder(({config: {projectId, dataset}}, ..._rest) => ({
41
+ name: `${projectId}.${dataset}`,
42
+ }))
41
43
  const storeDefinition = {
42
44
  name: 'TestStore',
43
45
  getInitialState: () => ({counter: 0}),
@@ -59,7 +61,7 @@ describe('createActionBinder', () => {
59
61
  })
60
62
 
61
63
  it('should dispose the store instance when the last instance is disposed', () => {
62
- const binder = createActionBinder(() => '')
64
+ const binder = createActionBinder((..._rest) => ({name: ''}))
63
65
  const storeDefinition = {
64
66
  name: 'TestStore',
65
67
  getInitialState: () => ({counter: 0}),
@@ -93,10 +95,10 @@ describe('bindActionByDataset', () => {
93
95
  name: 'DSStore',
94
96
  getInitialState: () => ({counter: 0}),
95
97
  }
96
- const action = vi.fn((_context, value: string) => value)
98
+ const action = vi.fn((_context, {value}: {value: string}) => value)
97
99
  const boundAction = bindActionByDataset(storeDefinition, action)
98
100
  const instance = createSanityInstance({projectId: 'proj1', dataset: 'ds1'})
99
- const result = boundAction(instance, 'hello')
101
+ const result = boundAction(instance, {value: 'hello'})
100
102
  expect(result).toBe('hello')
101
103
  })
102
104
 
@@ -105,7 +107,7 @@ describe('bindActionByDataset', () => {
105
107
  name: 'DSStore',
106
108
  getInitialState: () => ({counter: 0}),
107
109
  }
108
- const action = vi.fn((_context) => 'fail')
110
+ const action = vi.fn((_context, _?) => 'fail')
109
111
  const boundAction = bindActionByDataset(storeDefinition, action)
110
112
  // Instance with missing dataset
111
113
  const instance = createSanityInstance({projectId: 'proj1', dataset: ''})
@@ -1,14 +1,20 @@
1
- import {type DocumentSource, type SanityConfig, SOURCE_ID} from '../config/sanityConfig'
1
+ import {type DocumentSource, SOURCE_ID} from '../config/sanityConfig'
2
2
  import {type SanityInstance} from './createSanityInstance'
3
3
  import {createStoreInstance, type StoreInstance} from './createStoreInstance'
4
4
  import {type StoreState} from './createStoreState'
5
5
  import {type StoreContext, type StoreDefinition} from './defineStore'
6
6
 
7
+ export type BoundDatasetKey = {
8
+ name: string
9
+ projectId: string
10
+ dataset: string
11
+ }
12
+
7
13
  /**
8
14
  * Defines a store action that operates on a specific state type
9
15
  */
10
- export type StoreAction<TState, TParams extends unknown[], TReturn> = (
11
- context: StoreContext<TState>,
16
+ export type StoreAction<TState, TParams extends unknown[], TReturn, TKey = unknown> = (
17
+ context: StoreContext<TState, TKey>,
12
18
  ...params: TParams
13
19
  ) => TReturn
14
20
 
@@ -43,9 +49,10 @@ export type BoundStoreAction<_TState, TParams extends unknown[], TReturn> = (
43
49
  * )
44
50
  * ```
45
51
  */
46
- export function createActionBinder<TKeyParams extends unknown[]>(
47
- keyFn: (config: SanityConfig, ...params: TKeyParams) => string,
48
- ) {
52
+ export function createActionBinder<
53
+ TKey extends {name: string},
54
+ TKeyParams extends unknown[] = unknown[],
55
+ >(keyFn: (instance: SanityInstance, ...params: TKeyParams) => TKey) {
49
56
  const instanceRegistry = new Map<string, Set<string>>()
50
57
  const storeRegistry = new Map<string, StoreInstance<unknown>>()
51
58
 
@@ -57,12 +64,12 @@ export function createActionBinder<TKeyParams extends unknown[]>(
57
64
  * @returns A function that executes the action with a Sanity instance
58
65
  */
59
66
  return function bindAction<TState, TParams extends TKeyParams, TReturn>(
60
- storeDefinition: StoreDefinition<TState>,
61
- action: StoreAction<TState, TParams, TReturn>,
67
+ storeDefinition: StoreDefinition<TState, TKey>,
68
+ action: StoreAction<TState, TParams, TReturn, TKey>,
62
69
  ): BoundStoreAction<TState, TParams, TReturn> {
63
70
  return function boundAction(instance: SanityInstance, ...params: TParams) {
64
- const keySuffix = keyFn(instance.config, ...params)
65
- const compositeKey = storeDefinition.name + (keySuffix ? `:${keySuffix}` : '')
71
+ const key = keyFn(instance, ...params)
72
+ const compositeKey = storeDefinition.name + (key.name ? `:${key.name}` : '')
66
73
 
67
74
  // Get or create instance set for this composite key
68
75
  let instances = instanceRegistry.get(compositeKey)
@@ -89,12 +96,12 @@ export function createActionBinder<TKeyParams extends unknown[]>(
89
96
  // Get or create store instance
90
97
  let storeInstance = storeRegistry.get(compositeKey)
91
98
  if (!storeInstance) {
92
- storeInstance = createStoreInstance(instance, storeDefinition)
99
+ storeInstance = createStoreInstance(instance, key, storeDefinition)
93
100
  storeRegistry.set(compositeKey, storeInstance)
94
101
  }
95
102
 
96
103
  // Execute action with store context
97
- return action({instance, state: storeInstance.state as StoreState<TState>}, ...params)
104
+ return action({instance, state: storeInstance.state as StoreState<TState>, key}, ...params)
98
105
  }
99
106
  }
100
107
  }
@@ -130,31 +137,39 @@ export function createActionBinder<TKeyParams extends unknown[]>(
130
137
  * fetchDocument(sanityInstance, 'doc123')
131
138
  * ```
132
139
  */
133
- export const bindActionByDataset = createActionBinder<unknown[]>(({projectId, dataset}) => {
140
+ export const bindActionByDataset = createActionBinder<
141
+ BoundDatasetKey,
142
+ [(object & {projectId?: string; dataset?: string})?, ...unknown[]]
143
+ >((instance, options) => {
144
+ const projectId = options?.projectId ?? instance.config.projectId
145
+ const dataset = options?.dataset ?? instance.config.dataset
134
146
  if (!projectId || !dataset) {
135
147
  throw new Error('This API requires a project ID and dataset configured.')
136
148
  }
137
- return `${projectId}.${dataset}`
149
+ return {name: `${projectId}.${dataset}`, projectId, dataset}
138
150
  })
139
151
 
140
152
  /**
141
153
  * Binds an action to a store that's scoped to a specific document source.
142
154
  **/
143
- export const bindActionBySource = createActionBinder<[{source?: DocumentSource}, ...unknown[]]>(
144
- ({projectId, dataset}, {source}) => {
145
- if (source) {
146
- const id = source[SOURCE_ID]
147
- if (!id) throw new Error('Invalid source (missing ID information)')
148
- if (Array.isArray(id)) return id.join(':')
149
- return `${id.projectId}.${id.dataset}`
150
- }
155
+ export const bindActionBySource = createActionBinder<
156
+ {name: string},
157
+ [{source?: DocumentSource}, ...unknown[]]
158
+ >((instance, {source}) => {
159
+ if (source) {
160
+ const id = source[SOURCE_ID]
161
+ if (!id) throw new Error('Invalid source (missing ID information)')
162
+ if (Array.isArray(id)) return {name: id.join(':')}
163
+ return {name: `${id.projectId}.${id.dataset}`}
164
+ }
151
165
 
152
- if (!projectId || !dataset) {
153
- throw new Error('This API requires a project ID and dataset configured.')
154
- }
155
- return `${projectId}.${dataset}`
156
- },
157
- )
166
+ const {projectId, dataset} = instance.config
167
+
168
+ if (!projectId || !dataset) {
169
+ throw new Error('This API requires a project ID and dataset configured.')
170
+ }
171
+ return {name: `${projectId}.${dataset}`}
172
+ })
158
173
 
159
174
  /**
160
175
  * Binds an action to a global store that's shared across all Sanity instances
@@ -194,4 +209,4 @@ export const bindActionBySource = createActionBinder<[{source?: DocumentSource},
194
209
  * getCurrentUser(sanityInstance)
195
210
  * ```
196
211
  */
197
- export const bindActionGlobally = createActionBinder<unknown[]>(() => 'global')
212
+ export const bindActionGlobally = createActionBinder((..._rest) => ({name: 'global'}))