@sanity/sdk 0.0.0-chore-react-18-compat.1 → 0.0.0-chore-react-18-compat.3

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 (134) hide show
  1. package/dist/index.d.ts +441 -322
  2. package/dist/index.js +1685 -1481
  3. package/dist/index.js.map +1 -1
  4. package/package.json +13 -15
  5. package/src/_exports/index.ts +32 -30
  6. package/src/auth/authStore.test.ts +149 -104
  7. package/src/auth/authStore.ts +51 -100
  8. package/src/auth/handleAuthCallback.test.ts +67 -34
  9. package/src/auth/handleAuthCallback.ts +8 -7
  10. package/src/auth/logout.test.ts +61 -29
  11. package/src/auth/logout.ts +26 -28
  12. package/src/auth/refreshStampedToken.test.ts +197 -91
  13. package/src/auth/refreshStampedToken.ts +170 -59
  14. package/src/auth/subscribeToStateAndFetchCurrentUser.test.ts +5 -5
  15. package/src/auth/subscribeToStateAndFetchCurrentUser.ts +45 -47
  16. package/src/auth/subscribeToStorageEventsAndSetToken.test.ts +4 -5
  17. package/src/auth/subscribeToStorageEventsAndSetToken.ts +22 -24
  18. package/src/client/clientStore.test.ts +131 -67
  19. package/src/client/clientStore.ts +117 -116
  20. package/src/comlink/controller/actions/destroyController.test.ts +38 -13
  21. package/src/comlink/controller/actions/destroyController.ts +11 -15
  22. package/src/comlink/controller/actions/getOrCreateChannel.test.ts +56 -27
  23. package/src/comlink/controller/actions/getOrCreateChannel.ts +37 -35
  24. package/src/comlink/controller/actions/getOrCreateController.test.ts +27 -16
  25. package/src/comlink/controller/actions/getOrCreateController.ts +23 -22
  26. package/src/comlink/controller/actions/releaseChannel.test.ts +37 -13
  27. package/src/comlink/controller/actions/releaseChannel.ts +22 -21
  28. package/src/comlink/controller/comlinkControllerStore.test.ts +65 -36
  29. package/src/comlink/controller/comlinkControllerStore.ts +44 -5
  30. package/src/comlink/node/actions/getOrCreateNode.test.ts +31 -15
  31. package/src/comlink/node/actions/getOrCreateNode.ts +30 -29
  32. package/src/comlink/node/actions/releaseNode.test.ts +75 -55
  33. package/src/comlink/node/actions/releaseNode.ts +19 -21
  34. package/src/comlink/node/comlinkNodeStore.test.ts +6 -11
  35. package/src/comlink/node/comlinkNodeStore.ts +22 -5
  36. package/src/config/authConfig.ts +79 -0
  37. package/src/config/sanityConfig.ts +48 -0
  38. package/src/datasets/datasets.test.ts +2 -2
  39. package/src/datasets/datasets.ts +18 -5
  40. package/src/document/actions.test.ts +22 -10
  41. package/src/document/actions.ts +44 -56
  42. package/src/document/applyDocumentActions.test.ts +96 -36
  43. package/src/document/applyDocumentActions.ts +140 -99
  44. package/src/document/documentStore.test.ts +103 -155
  45. package/src/document/documentStore.ts +247 -238
  46. package/src/document/listen.ts +56 -55
  47. package/src/document/patchOperations.ts +0 -43
  48. package/src/document/permissions.test.ts +25 -12
  49. package/src/document/permissions.ts +11 -4
  50. package/src/document/processActions.test.ts +41 -8
  51. package/src/document/reducers.test.ts +87 -16
  52. package/src/document/reducers.ts +2 -2
  53. package/src/document/sharedListener.test.ts +34 -16
  54. package/src/document/sharedListener.ts +33 -11
  55. package/src/preview/getPreviewState.test.ts +40 -39
  56. package/src/preview/getPreviewState.ts +68 -56
  57. package/src/preview/previewConstants.ts +43 -0
  58. package/src/preview/previewQuery.test.ts +1 -1
  59. package/src/preview/previewQuery.ts +4 -5
  60. package/src/preview/previewStore.test.ts +13 -58
  61. package/src/preview/previewStore.ts +7 -21
  62. package/src/preview/resolvePreview.test.ts +33 -104
  63. package/src/preview/resolvePreview.ts +11 -21
  64. package/src/preview/subscribeToStateAndFetchBatches.test.ts +96 -97
  65. package/src/preview/subscribeToStateAndFetchBatches.ts +85 -81
  66. package/src/preview/util.ts +1 -0
  67. package/src/project/project.test.ts +3 -3
  68. package/src/project/project.ts +28 -5
  69. package/src/projection/getProjectionState.test.ts +188 -72
  70. package/src/projection/getProjectionState.ts +92 -62
  71. package/src/projection/projectionQuery.test.ts +114 -12
  72. package/src/projection/projectionQuery.ts +75 -32
  73. package/src/projection/projectionStore.test.ts +13 -51
  74. package/src/projection/projectionStore.ts +6 -43
  75. package/src/projection/resolveProjection.test.ts +32 -127
  76. package/src/projection/resolveProjection.ts +16 -28
  77. package/src/projection/subscribeToStateAndFetchBatches.test.ts +203 -116
  78. package/src/projection/subscribeToStateAndFetchBatches.ts +140 -85
  79. package/src/projection/types.ts +50 -0
  80. package/src/projection/util.ts +3 -1
  81. package/src/projects/projects.test.ts +13 -4
  82. package/src/projects/projects.ts +6 -1
  83. package/src/query/queryStore.test.ts +10 -47
  84. package/src/query/queryStore.ts +151 -133
  85. package/src/query/queryStoreConstants.ts +2 -0
  86. package/src/store/createActionBinder.test.ts +153 -0
  87. package/src/store/createActionBinder.ts +176 -0
  88. package/src/store/createSanityInstance.test.ts +84 -0
  89. package/src/store/createSanityInstance.ts +124 -0
  90. package/src/store/createStateSourceAction.test.ts +196 -0
  91. package/src/store/createStateSourceAction.ts +260 -0
  92. package/src/store/createStoreInstance.test.ts +81 -0
  93. package/src/store/createStoreInstance.ts +80 -0
  94. package/src/store/createStoreState.test.ts +85 -0
  95. package/src/store/createStoreState.ts +92 -0
  96. package/src/store/defineStore.test.ts +18 -0
  97. package/src/store/defineStore.ts +81 -0
  98. package/src/users/reducers.test.ts +318 -0
  99. package/src/users/reducers.ts +88 -0
  100. package/src/users/types.ts +46 -4
  101. package/src/users/usersConstants.ts +4 -0
  102. package/src/users/usersStore.test.ts +350 -223
  103. package/src/users/usersStore.ts +285 -149
  104. package/src/utils/createFetcherStore.test.ts +6 -7
  105. package/src/utils/createFetcherStore.ts +150 -153
  106. package/src/utils/createGroqSearchFilter.test.ts +75 -0
  107. package/src/utils/createGroqSearchFilter.ts +85 -0
  108. package/src/{common/util.test.ts → utils/hashString.test.ts} +1 -1
  109. package/dist/index.cjs +0 -4888
  110. package/dist/index.cjs.map +0 -1
  111. package/dist/index.d.cts +0 -2121
  112. package/src/auth/fetchLoginUrls.test.ts +0 -163
  113. package/src/auth/fetchLoginUrls.ts +0 -74
  114. package/src/common/createLiveEventSubscriber.test.ts +0 -121
  115. package/src/common/createLiveEventSubscriber.ts +0 -55
  116. package/src/common/types.ts +0 -4
  117. package/src/instance/identity.test.ts +0 -46
  118. package/src/instance/identity.ts +0 -29
  119. package/src/instance/sanityInstance.test.ts +0 -77
  120. package/src/instance/sanityInstance.ts +0 -57
  121. package/src/instance/types.ts +0 -37
  122. package/src/preview/getPreviewProjection.ts +0 -45
  123. package/src/resources/README.md +0 -370
  124. package/src/resources/createAction.test.ts +0 -101
  125. package/src/resources/createAction.ts +0 -44
  126. package/src/resources/createResource.test.ts +0 -112
  127. package/src/resources/createResource.ts +0 -102
  128. package/src/resources/createStateSourceAction.test.ts +0 -114
  129. package/src/resources/createStateSourceAction.ts +0 -83
  130. package/src/resources/createStore.test.ts +0 -67
  131. package/src/resources/createStore.ts +0 -46
  132. package/src/store/createStore.test.ts +0 -108
  133. package/src/store/createStore.ts +0 -106
  134. /package/src/{common/util.ts → utils/hashString.ts} +0 -0
@@ -20,13 +20,19 @@ import {
20
20
  tap,
21
21
  } from 'rxjs'
22
22
 
23
- import {type ClientOptions, getClientState} from '../client/clientStore'
24
- import {type SanityInstance} from '../instance/types'
25
- import {type ActionContext, createAction, createInternalAction} from '../resources/createAction'
26
- import {createResource} from '../resources/createResource'
27
- import {createStateSourceAction, type StateSource} from '../resources/createStateSourceAction'
23
+ import {getClientState} from '../client/clientStore'
24
+ import {type DatasetHandle} from '../config/sanityConfig'
25
+ import {bindActionByDataset} from '../store/createActionBinder'
26
+ import {type SanityInstance} from '../store/createSanityInstance'
27
+ import {
28
+ createStateSourceAction,
29
+ type SelectorContext,
30
+ type StateSource,
31
+ } from '../store/createStateSourceAction'
32
+ import {type StoreState} from '../store/createStoreState'
33
+ import {defineStore, type StoreContext} from '../store/defineStore'
28
34
  import {insecureRandomId} from '../utils/ids'
29
- import {QUERY_STATE_CLEAR_DELAY} from './queryStoreConstants'
35
+ import {QUERY_STATE_CLEAR_DELAY, QUERY_STORE_API_VERSION} from './queryStoreConstants'
30
36
  import {
31
37
  addSubscriber,
32
38
  cancelQuery,
@@ -42,8 +48,11 @@ import {
42
48
  * @beta
43
49
  */
44
50
  export interface QueryOptions
45
- extends Pick<ResponseQueryOptions, 'perspective' | 'useCdn' | 'cache' | 'next' | 'cacheMode'>,
46
- Pick<ClientOptions, 'scope' | 'resourceId'> {
51
+ extends Pick<
52
+ ResponseQueryOptions,
53
+ 'perspective' | 'useCdn' | 'cache' | 'next' | 'cacheMode' | 'tag'
54
+ >,
55
+ DatasetHandle {
47
56
  params?: Record<string, unknown>
48
57
  }
49
58
 
@@ -63,13 +72,13 @@ export const getQueryKey = (query: string, options: QueryOptions = {}): string =
63
72
  export const parseQueryKey = (key: string): {query: string; options: QueryOptions} =>
64
73
  JSON.parse(key)
65
74
 
66
- export const queryStore = createResource<QueryStoreState>({
75
+ const queryStore = defineStore<QueryStoreState>({
67
76
  name: 'QueryStore',
68
77
  getInitialState: () => ({queries: {}}),
69
- initialize() {
78
+ initialize(context) {
70
79
  const subscriptions = [
71
- listenForNewSubscribersAndFetch(this),
72
- listenToLiveClientAndSetLastLiveEventIds(this),
80
+ listenForNewSubscribersAndFetch(context),
81
+ listenToLiveClientAndSetLastLiveEventIds(context),
73
82
  ]
74
83
 
75
84
  return () => {
@@ -80,106 +89,107 @@ export const queryStore = createResource<QueryStoreState>({
80
89
  },
81
90
  })
82
91
 
83
- export const errorHandler = createInternalAction(({state}: ActionContext<{error?: unknown}>) => {
84
- return function () {
85
- return (error: unknown) => state.set('setError', {error})
86
- }
87
- })
88
-
89
- const listenForNewSubscribersAndFetch = createInternalAction(
90
- ({state, instance}: ActionContext<QueryStoreState>) => {
91
- return function () {
92
- return state.observable
93
- .pipe(
94
- map((s) => new Set(Object.keys(s.queries))),
95
- distinctUntilChanged((curr, next) => {
96
- if (curr.size !== next.size) return false
97
- return Array.from(next).every((i) => curr.has(i))
98
- }),
99
- startWith(new Set<string>()),
100
- pairwise(),
101
- mergeMap(([curr, next]) => {
102
- const added = Array.from(next).filter((i) => !curr.has(i))
103
- const removed = Array.from(curr).filter((i) => !next.has(i))
104
-
105
- return [
106
- ...added.map((key) => ({key, added: true})),
107
- ...removed.map((key) => ({key, added: false})),
108
- ]
109
- }),
110
- groupBy((i) => i.key),
111
- mergeMap((group$) =>
112
- group$.pipe(
113
- switchMap((e) => {
114
- if (!e.added) return EMPTY
92
+ const errorHandler = (state: StoreState<{error?: unknown}>) => {
93
+ return (error: unknown): void => state.set('setError', {error})
94
+ }
115
95
 
116
- const lastLiveEventId$ = state.observable.pipe(
117
- map((s) => s.queries[group$.key]?.lastLiveEventId),
118
- distinctUntilChanged(),
119
- )
120
- const {query, options: {params, scope, ...options} = {}} = parseQueryKey(group$.key)
121
- const client$ = getClientState(instance, {apiVersion: 'vX', scope}).observable
96
+ const listenForNewSubscribersAndFetch = ({state, instance}: StoreContext<QueryStoreState>) => {
97
+ return state.observable
98
+ .pipe(
99
+ map((s) => new Set(Object.keys(s.queries))),
100
+ distinctUntilChanged((curr, next) => {
101
+ if (curr.size !== next.size) return false
102
+ return Array.from(next).every((i) => curr.has(i))
103
+ }),
104
+ startWith(new Set<string>()),
105
+ pairwise(),
106
+ mergeMap(([curr, next]) => {
107
+ const added = Array.from(next).filter((i) => !curr.has(i))
108
+ const removed = Array.from(curr).filter((i) => !next.has(i))
122
109
 
123
- return combineLatest([lastLiveEventId$, client$]).pipe(
124
- switchMap(([lastLiveEventId, client]) =>
125
- client.observable.fetch(query, params, {
126
- ...options,
127
- filterResponse: false,
128
- returnQuery: false,
129
- lastLiveEventId,
130
- }),
131
- ),
132
- )
133
- }),
134
- catchError((error) => {
135
- state.set('setQueryError', setQueryError(group$.key, error))
136
- return EMPTY
137
- }),
138
- tap(({result, syncTags}) => {
139
- state.set('setQueryData', setQueryData(group$.key, result, syncTags))
140
- }),
141
- ),
142
- ),
143
- )
144
- .subscribe({error: errorHandler(this)})
145
- }
146
- },
147
- )
110
+ return [
111
+ ...added.map((key) => ({key, added: true})),
112
+ ...removed.map((key) => ({key, added: false})),
113
+ ]
114
+ }),
115
+ groupBy((i) => i.key),
116
+ mergeMap((group$) =>
117
+ group$.pipe(
118
+ switchMap((e) => {
119
+ if (!e.added) return EMPTY
148
120
 
149
- const listenToLiveClientAndSetLastLiveEventIds = createInternalAction(
150
- ({state, instance}: ActionContext<QueryStoreState>) => {
151
- return function () {
152
- const liveMessages$ = getClientState(instance, {apiVersion: 'vX'}).observable.pipe(
153
- switchMap((client) =>
154
- client.live.events({includeDrafts: !!client.config().token, tag: 'query-store'}),
155
- ),
156
- share(),
157
- filter((e) => e.type === 'message'),
158
- )
159
-
160
- return state.observable
161
- .pipe(
162
- mergeMap((s) => Object.entries(s.queries)),
163
- groupBy(([key]) => key),
164
- mergeMap((group$) => {
165
- const syncTags$ = group$.pipe(
166
- map(([, queryState]) => queryState),
167
- map((i) => i?.syncTags ?? EMPTY_ARRAY),
121
+ const lastLiveEventId$ = state.observable.pipe(
122
+ map((s) => s.queries[group$.key]?.lastLiveEventId),
168
123
  distinctUntilChanged(),
169
124
  )
125
+ const {query, options: {params, projectId, dataset, tag, ...options} = {}} =
126
+ parseQueryKey(group$.key)
127
+ const client$ = getClientState(instance, {
128
+ apiVersion: QUERY_STORE_API_VERSION,
129
+ projectId,
130
+ dataset,
131
+ }).observable
170
132
 
171
- return combineLatest([liveMessages$, syncTags$]).pipe(
172
- filter(([message, syncTags]) => message.tags.some((tag) => syncTags.includes(tag))),
173
- tap(([message]) => {
174
- state.set('setLastLiveEventId', setLastLiveEventId(group$.key, message.id))
175
- }),
133
+ return combineLatest([lastLiveEventId$, client$]).pipe(
134
+ switchMap(([lastLiveEventId, client]) =>
135
+ client.observable.fetch(query, params, {
136
+ ...options,
137
+ filterResponse: false,
138
+ returnQuery: false,
139
+ lastLiveEventId,
140
+ tag,
141
+ }),
142
+ ),
176
143
  )
177
144
  }),
145
+ catchError((error) => {
146
+ state.set('setQueryError', setQueryError(group$.key, error))
147
+ return EMPTY
148
+ }),
149
+ tap(({result, syncTags}) => {
150
+ state.set('setQueryData', setQueryData(group$.key, result, syncTags))
151
+ }),
152
+ ),
153
+ ),
154
+ )
155
+ .subscribe({error: errorHandler(state)})
156
+ }
157
+
158
+ const listenToLiveClientAndSetLastLiveEventIds = ({
159
+ state,
160
+ instance,
161
+ }: StoreContext<QueryStoreState>) => {
162
+ const liveMessages$ = getClientState(instance, {
163
+ apiVersion: QUERY_STORE_API_VERSION,
164
+ }).observable.pipe(
165
+ switchMap((client) =>
166
+ client.live.events({includeDrafts: !!client.config().token, tag: 'query-store'}),
167
+ ),
168
+ share(),
169
+ filter((e) => e.type === 'message'),
170
+ )
171
+
172
+ return state.observable
173
+ .pipe(
174
+ mergeMap((s) => Object.entries(s.queries)),
175
+ groupBy(([key]) => key),
176
+ mergeMap((group$) => {
177
+ const syncTags$ = group$.pipe(
178
+ map(([, queryState]) => queryState),
179
+ map((i) => i?.syncTags ?? EMPTY_ARRAY),
180
+ distinctUntilChanged(),
181
+ )
182
+
183
+ return combineLatest([liveMessages$, syncTags$]).pipe(
184
+ filter(([message, syncTags]) => message.tags.some((tag) => syncTags.includes(tag))),
185
+ tap(([message]) => {
186
+ state.set('setLastLiveEventId', setLastLiveEventId(group$.key, message.id))
187
+ }),
178
188
  )
179
- .subscribe({error: errorHandler(this)})
180
- }
181
- },
182
- )
189
+ }),
190
+ )
191
+ .subscribe({error: errorHandler(state)})
192
+ }
183
193
 
184
194
  /**
185
195
  * Returns the state source for a query.
@@ -198,13 +208,13 @@ const listenToLiveClientAndSetLastLiveEventIds = createInternalAction(
198
208
  * @beta
199
209
  */
200
210
  export function getQueryState<T>(
201
- instance: SanityInstance | ActionContext<QueryStoreState>,
211
+ instance: SanityInstance,
202
212
  query: string,
203
213
  options?: QueryOptions,
204
214
  ): StateSource<T | undefined>
205
215
  /** @beta */
206
216
  export function getQueryState(
207
- instance: SanityInstance | ActionContext<QueryStoreState>,
217
+ instance: SanityInstance,
208
218
  query: string,
209
219
  options?: QueryOptions,
210
220
  ): StateSource<unknown>
@@ -212,29 +222,36 @@ export function getQueryState(
212
222
  export function getQueryState(...args: Parameters<typeof _getQueryState>): StateSource<unknown> {
213
223
  return _getQueryState(...args)
214
224
  }
215
- const _getQueryState = createStateSourceAction(queryStore, {
216
- selector: (state: QueryStoreState, query: string, options?: QueryOptions) => {
217
- if (state.error) throw state.error
218
- const key = getQueryKey(query, options)
219
- const queryState = state.queries[key]
220
- if (queryState?.error) throw queryState.error
221
- return queryState?.result
222
- },
223
- onSubscribe: ({state}, query, options?: QueryOptions) => {
224
- const subscriptionId = insecureRandomId()
225
- const key = getQueryKey(query, options)
225
+ const _getQueryState = bindActionByDataset(
226
+ queryStore,
227
+ createStateSourceAction({
228
+ selector: (
229
+ {state}: SelectorContext<QueryStoreState>,
230
+ query: string,
231
+ options?: QueryOptions,
232
+ ) => {
233
+ if (state.error) throw state.error
234
+ const key = getQueryKey(query, options)
235
+ const queryState = state.queries[key]
236
+ if (queryState?.error) throw queryState.error
237
+ return queryState?.result
238
+ },
239
+ onSubscribe: ({state}, query, options?: QueryOptions) => {
240
+ const subscriptionId = insecureRandomId()
241
+ const key = getQueryKey(query, options)
226
242
 
227
- state.set('addSubscriber', addSubscriber(key, subscriptionId))
243
+ state.set('addSubscriber', addSubscriber(key, subscriptionId))
228
244
 
229
- return () => {
230
- // this runs on unsubscribe
231
- setTimeout(
232
- () => state.set('removeSubscriber', removeSubscriber(key, subscriptionId)),
233
- QUERY_STATE_CLEAR_DELAY,
234
- )
235
- }
236
- },
237
- })
245
+ return () => {
246
+ // this runs on unsubscribe
247
+ setTimeout(
248
+ () => state.set('removeSubscriber', removeSubscriber(key, subscriptionId)),
249
+ QUERY_STATE_CLEAR_DELAY,
250
+ )
251
+ }
252
+ },
253
+ }),
254
+ )
238
255
 
239
256
  /**
240
257
  * Resolves the result of a query without registering a lasting subscriber.
@@ -251,13 +268,13 @@ const _getQueryState = createStateSourceAction(queryStore, {
251
268
  * @beta
252
269
  */
253
270
  export function resolveQuery<T>(
254
- instance: SanityInstance | ActionContext<QueryStoreState>,
271
+ instance: SanityInstance,
255
272
  query: string,
256
273
  options?: ResolveQueryOptions,
257
274
  ): Promise<T>
258
275
  /** @beta */
259
276
  export function resolveQuery(
260
- instance: SanityInstance | ActionContext<QueryStoreState>,
277
+ instance: SanityInstance,
261
278
  query: string,
262
279
  options?: ResolveQueryOptions,
263
280
  ): Promise<unknown>
@@ -265,9 +282,10 @@ export function resolveQuery(
265
282
  export function resolveQuery(...args: Parameters<typeof _resolveQuery>): Promise<unknown> {
266
283
  return _resolveQuery(...args)
267
284
  }
268
- const _resolveQuery = createAction(queryStore, ({state}) => {
269
- return function (query: string, {signal, ...options}: ResolveQueryOptions = {}) {
270
- const {getCurrent} = getQueryState(this, query, options)
285
+ const _resolveQuery = bindActionByDataset(
286
+ queryStore,
287
+ ({state, instance}, query: string, {signal, ...options}: ResolveQueryOptions = {}) => {
288
+ const {getCurrent} = getQueryState(instance, query, options)
271
289
  const key = getQueryKey(query, options)
272
290
 
273
291
  const aborted$ = signal
@@ -302,5 +320,5 @@ const _resolveQuery = createAction(queryStore, ({state}) => {
302
320
  )
303
321
 
304
322
  return firstValueFrom(race([resolved$, aborted$]))
305
- }
306
- })
323
+ },
324
+ )
@@ -6,3 +6,5 @@
6
6
  * different views quickly.
7
7
  */
8
8
  export const QUERY_STATE_CLEAR_DELAY = 1000
9
+ // NOTE: Have to use vX for the text::query groq function
10
+ export const QUERY_STORE_API_VERSION = 'vX'
@@ -0,0 +1,153 @@
1
+ import {beforeEach, describe, expect, it, vi} from 'vitest'
2
+
3
+ import {bindActionByDataset, bindActionGlobally, createActionBinder} from './createActionBinder'
4
+ import {createSanityInstance} from './createSanityInstance'
5
+ import {createStoreInstance} from './createStoreInstance'
6
+
7
+ // Mock store instance creation for testing
8
+ vi.mock('./createStoreInstance', () => ({
9
+ createStoreInstance: vi.fn(() => ({state: {counter: 0}, dispose: vi.fn()})),
10
+ }))
11
+ beforeEach(() => vi.mocked(createStoreInstance).mockClear())
12
+
13
+ describe('createActionBinder', () => {
14
+ it('should bind an action and call it with correct context and parameters, using caching', () => {
15
+ const binder = createActionBinder(() => '')
16
+ const storeDefinition = {
17
+ name: 'TestStore',
18
+ getInitialState: () => ({counter: 0}),
19
+ }
20
+ // Action that increments counter by given value
21
+ const action = vi.fn((context, increment: number) => {
22
+ context.state.counter += increment
23
+ return context.state.counter
24
+ })
25
+ const boundAction = binder(storeDefinition, action)
26
+ const instance = createSanityInstance({projectId: 'proj1', dataset: 'ds1'})
27
+
28
+ // First call creates store instance
29
+ const result1 = boundAction(instance, 5)
30
+ expect(result1).toBe(5)
31
+ // Second call reuses cached store
32
+ const result2 = boundAction(instance, 5)
33
+ expect(result2).toBe(10)
34
+
35
+ expect(action).toHaveBeenCalledTimes(2)
36
+ expect(vi.mocked(createStoreInstance)).toHaveBeenCalledTimes(1)
37
+ })
38
+
39
+ it('should create separate store instances for different composite keys', () => {
40
+ const binder = createActionBinder(({projectId, dataset}) => `${projectId}.${dataset}`)
41
+ const storeDefinition = {
42
+ name: 'TestStore',
43
+ getInitialState: () => ({counter: 0}),
44
+ }
45
+ const action = vi.fn((context, val: number) => {
46
+ context.state.counter += val
47
+ return context.state.counter
48
+ })
49
+ const boundAction = binder(storeDefinition, action)
50
+ const instanceA = createSanityInstance({projectId: 'p1', dataset: 'd1'})
51
+ const instanceB = createSanityInstance({projectId: 'p2', dataset: 'd2'})
52
+
53
+ const resultA = boundAction(instanceA, 3)
54
+ const resultB = boundAction(instanceB, 4)
55
+
56
+ expect(resultA).toBe(3)
57
+ expect(resultB).toBe(4)
58
+ expect(vi.mocked(createStoreInstance)).toHaveBeenCalledTimes(2)
59
+ })
60
+
61
+ it('should dispose the store instance when the last instance is disposed', () => {
62
+ const binder = createActionBinder(() => '')
63
+ const storeDefinition = {
64
+ name: 'TestStore',
65
+ getInitialState: () => ({counter: 0}),
66
+ }
67
+ const action = vi.fn((context) => context.state.counter)
68
+ const boundAction = binder(storeDefinition, action)
69
+ const instance1 = createSanityInstance({projectId: 'p1', dataset: 'd1'})
70
+ const instance2 = createSanityInstance({projectId: 'p1', dataset: 'd1'})
71
+
72
+ // Call action on both instances
73
+ boundAction(instance1)
74
+ boundAction(instance2)
75
+ expect(vi.mocked(createStoreInstance)).toHaveBeenCalledTimes(1)
76
+
77
+ const [{value: storeInstance}] = vi.mocked(createStoreInstance).mock.results
78
+ expect(storeInstance).toBeDefined()
79
+
80
+ // First disposal shouldn't trigger store disposal
81
+ instance1.dispose()
82
+ expect(storeInstance.dispose).not.toHaveBeenCalled()
83
+
84
+ // Last disposal should trigger store disposal
85
+ instance2.dispose()
86
+ expect(storeInstance.dispose).toHaveBeenCalledTimes(1)
87
+ })
88
+ })
89
+
90
+ describe('bindActionByDataset', () => {
91
+ it('should work correctly when projectId and dataset are provided', () => {
92
+ const storeDefinition = {
93
+ name: 'DSStore',
94
+ getInitialState: () => ({counter: 0}),
95
+ }
96
+ const action = vi.fn((_context, value: string) => value)
97
+ const boundAction = bindActionByDataset(storeDefinition, action)
98
+ const instance = createSanityInstance({projectId: 'proj1', dataset: 'ds1'})
99
+ const result = boundAction(instance, 'hello')
100
+ expect(result).toBe('hello')
101
+ })
102
+
103
+ it('should throw an error if projectId or dataset is missing', () => {
104
+ const storeDefinition = {
105
+ name: 'DSStore',
106
+ getInitialState: () => ({counter: 0}),
107
+ }
108
+ const action = vi.fn((_context) => 'fail')
109
+ const boundAction = bindActionByDataset(storeDefinition, action)
110
+ // Instance with missing dataset
111
+ const instance = createSanityInstance({projectId: 'proj1', dataset: ''})
112
+ expect(() => boundAction(instance)).toThrow(
113
+ 'This API requires a project ID and dataset configured.',
114
+ )
115
+ })
116
+ })
117
+
118
+ describe('bindActionGlobally', () => {
119
+ it('should work correctly ignoring config in key generation', () => {
120
+ const storeDefinition = {
121
+ name: 'GlobalStore',
122
+ getInitialState: () => ({counter: 0}),
123
+ }
124
+ const action = vi.fn((_context, x: number) => x)
125
+ const boundAction = bindActionGlobally(storeDefinition, action)
126
+
127
+ // Create instances with different configs
128
+ const instance1 = createSanityInstance({projectId: 'any', dataset: 'any'})
129
+ const instance2 = createSanityInstance({projectId: 'different', dataset: 'config'})
130
+
131
+ // Both instances should use the same store
132
+ const result1 = boundAction(instance1, 42)
133
+ const result2 = boundAction(instance2, 99)
134
+
135
+ expect(result1).toBe(42)
136
+ expect(result2).toBe(99)
137
+
138
+ // Verify single store instance used
139
+ expect(vi.mocked(createStoreInstance)).toHaveBeenCalledTimes(1)
140
+
141
+ // Verify action called with correct arguments
142
+ expect(action).toHaveBeenNthCalledWith(1, expect.anything(), 42)
143
+ expect(action).toHaveBeenNthCalledWith(2, expect.anything(), 99)
144
+
145
+ // Test disposal tracking
146
+ const [{value: storeInstance}] = vi.mocked(createStoreInstance).mock.results
147
+ instance1.dispose()
148
+ expect(storeInstance.dispose).not.toHaveBeenCalled()
149
+
150
+ instance2.dispose()
151
+ expect(storeInstance.dispose).toHaveBeenCalledTimes(1)
152
+ })
153
+ })