@sanity/sdk 0.0.0-alpha.21 → 0.0.0-alpha.23

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 (127) hide show
  1. package/dist/index.d.ts +428 -325
  2. package/dist/index.js +1618 -1553
  3. package/dist/index.js.map +1 -1
  4. package/package.json +6 -7
  5. package/src/_exports/index.ts +31 -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 +9 -9
  13. package/src/auth/refreshStampedToken.ts +62 -56
  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 -237
  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 +69 -49
  70. package/src/projection/getProjectionState.ts +42 -50
  71. package/src/projection/projectionQuery.ts +1 -1
  72. package/src/projection/projectionStore.test.ts +13 -51
  73. package/src/projection/projectionStore.ts +6 -18
  74. package/src/projection/resolveProjection.test.ts +32 -127
  75. package/src/projection/resolveProjection.ts +15 -28
  76. package/src/projection/subscribeToStateAndFetchBatches.test.ts +105 -90
  77. package/src/projection/subscribeToStateAndFetchBatches.ts +94 -81
  78. package/src/projection/util.ts +2 -0
  79. package/src/projects/projects.test.ts +13 -4
  80. package/src/projects/projects.ts +6 -1
  81. package/src/query/queryStore.test.ts +10 -47
  82. package/src/query/queryStore.ts +151 -133
  83. package/src/query/queryStoreConstants.ts +2 -0
  84. package/src/store/createActionBinder.test.ts +153 -0
  85. package/src/store/createActionBinder.ts +176 -0
  86. package/src/store/createSanityInstance.test.ts +84 -0
  87. package/src/store/createSanityInstance.ts +124 -0
  88. package/src/store/createStateSourceAction.test.ts +196 -0
  89. package/src/store/createStateSourceAction.ts +260 -0
  90. package/src/store/createStoreInstance.test.ts +81 -0
  91. package/src/store/createStoreInstance.ts +80 -0
  92. package/src/store/createStoreState.test.ts +85 -0
  93. package/src/store/createStoreState.ts +92 -0
  94. package/src/store/defineStore.test.ts +18 -0
  95. package/src/store/defineStore.ts +81 -0
  96. package/src/users/reducers.test.ts +318 -0
  97. package/src/users/reducers.ts +88 -0
  98. package/src/users/types.ts +46 -4
  99. package/src/users/usersConstants.ts +4 -0
  100. package/src/users/usersStore.test.ts +350 -223
  101. package/src/users/usersStore.ts +285 -149
  102. package/src/utils/createFetcherStore.test.ts +6 -7
  103. package/src/utils/createFetcherStore.ts +150 -153
  104. package/src/{common/util.test.ts → utils/hashString.test.ts} +1 -1
  105. package/src/auth/fetchLoginUrls.test.ts +0 -163
  106. package/src/auth/fetchLoginUrls.ts +0 -74
  107. package/src/common/createLiveEventSubscriber.test.ts +0 -121
  108. package/src/common/createLiveEventSubscriber.ts +0 -55
  109. package/src/common/types.ts +0 -4
  110. package/src/instance/identity.test.ts +0 -46
  111. package/src/instance/identity.ts +0 -29
  112. package/src/instance/sanityInstance.test.ts +0 -77
  113. package/src/instance/sanityInstance.ts +0 -57
  114. package/src/instance/types.ts +0 -37
  115. package/src/preview/getPreviewProjection.ts +0 -45
  116. package/src/resources/README.md +0 -370
  117. package/src/resources/createAction.test.ts +0 -101
  118. package/src/resources/createAction.ts +0 -44
  119. package/src/resources/createResource.test.ts +0 -112
  120. package/src/resources/createResource.ts +0 -102
  121. package/src/resources/createStateSourceAction.test.ts +0 -114
  122. package/src/resources/createStateSourceAction.ts +0 -83
  123. package/src/resources/createStore.test.ts +0 -67
  124. package/src/resources/createStore.ts +0 -46
  125. package/src/store/createStore.test.ts +0 -108
  126. package/src/store/createStore.ts +0 -106
  127. /package/src/{common/util.ts → utils/hashString.ts} +0 -0
@@ -3,10 +3,9 @@ import {delay, filter, firstValueFrom, Observable, of, Subject} from 'rxjs'
3
3
  import {beforeEach, describe, expect, it, vi} from 'vitest'
4
4
 
5
5
  import {getClientState} from '../client/clientStore'
6
- import {createSanityInstance} from '../instance/sanityInstance'
7
- import {getOrCreateResource} from '../resources/createResource'
8
- import {type StateSource} from '../resources/createStateSourceAction'
9
- import {errorHandler, getQueryState, queryStore, resolveQuery} from './queryStore'
6
+ import {createSanityInstance, type SanityInstance} from '../store/createSanityInstance'
7
+ import {type StateSource} from '../store/createStateSourceAction'
8
+ import {getQueryState, resolveQuery} from './queryStore'
10
9
 
11
10
  vi.mock('./queryStoreConstants', async (importOriginal) => ({
12
11
  ...(await importOriginal<typeof import('./queryStoreConstants')>()),
@@ -18,6 +17,7 @@ vi.mock('../client/clientStore', () => ({
18
17
  }))
19
18
 
20
19
  describe('queryStore', () => {
20
+ let instance: SanityInstance
21
21
  let liveEvents: Subject<LiveEvent>
22
22
  let fetch: SanityClient['observable']['fetch']
23
23
  // Mock data for testing
@@ -29,6 +29,8 @@ describe('queryStore', () => {
29
29
  }
30
30
 
31
31
  beforeEach(() => {
32
+ instance = createSanityInstance({projectId: 'test', dataset: 'test'})
33
+
32
34
  fetch = vi
33
35
  .fn()
34
36
  .mockReturnValue(
@@ -50,8 +52,11 @@ describe('queryStore', () => {
50
52
  } as StateSource<SanityClient>)
51
53
  })
52
54
 
55
+ afterEach(() => {
56
+ instance.dispose()
57
+ })
58
+
53
59
  it('initializes query state and cleans up after unsubscribe', async () => {
54
- const instance = createSanityInstance({projectId: 'test', dataset: 'test'})
55
60
  const query = '*[_type == "movie"]'
56
61
  const state = getQueryState(instance, query)
57
62
 
@@ -78,12 +83,9 @@ describe('queryStore', () => {
78
83
 
79
84
  // Verify state is cleared
80
85
  expect(state.getCurrent()).toBeUndefined()
81
-
82
- instance.dispose()
83
86
  })
84
87
 
85
88
  it('maintains state when multiple subscribers exist', async () => {
86
- const instance = createSanityInstance({projectId: 'test', dataset: 'test'})
87
89
  const query = '*[_type == "movie"]'
88
90
  const state = getQueryState(instance, query)
89
91
 
@@ -117,12 +119,9 @@ describe('queryStore', () => {
117
119
 
118
120
  // Verify state is cleared after all subscribers are gone
119
121
  expect(state.getCurrent()).toBeUndefined()
120
-
121
- instance.dispose()
122
122
  })
123
123
 
124
124
  it('resolveQuery works without affecting subscriber cleanup', async () => {
125
- const instance = createSanityInstance({projectId: 'test', dataset: 'test'})
126
125
  const query = '*[_type == "movie"]'
127
126
 
128
127
  const state = getQueryState(instance, query)
@@ -151,12 +150,9 @@ describe('queryStore', () => {
151
150
  unsubscribe()
152
151
  await new Promise((resolve) => setTimeout(resolve, 20))
153
152
  expect(state.getCurrent()).toBeUndefined()
154
-
155
- instance.dispose()
156
153
  })
157
154
 
158
155
  it('handles abort signal in resolveQuery', async () => {
159
- const instance = createSanityInstance({projectId: 'test', dataset: 'test'})
160
156
  const query = '*[_type == "movie"]'
161
157
  const abortController = new AbortController()
162
158
 
@@ -171,8 +167,6 @@ describe('queryStore', () => {
171
167
 
172
168
  // Verify state is cleared after abort
173
169
  expect(getQueryState(instance, query).getCurrent()).toBeUndefined()
174
-
175
- instance.dispose()
176
170
  })
177
171
 
178
172
  it('refetches query when receiving live event with matching sync tag', async () => {
@@ -190,7 +184,6 @@ describe('queryStore', () => {
190
184
  ),
191
185
  )
192
186
 
193
- const instance = createSanityInstance({projectId: 'test', dataset: 'test'})
194
187
  const query = '*[_type == "movie"]'
195
188
  const state = getQueryState<{_id: string; _type: string; title: string}[]>(instance, query)
196
189
 
@@ -214,7 +207,6 @@ describe('queryStore', () => {
214
207
  expect(result).toContainEqual(updatedMovie)
215
208
 
216
209
  unsubscribe()
217
- instance.dispose()
218
210
  })
219
211
 
220
212
  it('does not refetch for non-matching sync tags', async () => {
@@ -223,7 +215,6 @@ describe('queryStore', () => {
223
215
  of({result: mockData.movies, syncTags: mockSyncTags, ms: 0}).pipe(delay(0)),
224
216
  )
225
217
 
226
- const instance = createSanityInstance({projectId: 'test', dataset: 'test'})
227
218
  const query = '*[_type == "movie"]'
228
219
  const state = getQueryState(instance, query)
229
220
 
@@ -243,7 +234,6 @@ describe('queryStore', () => {
243
234
  expect(fetch).toHaveBeenCalledTimes(1)
244
235
 
245
236
  unsubscribe()
246
- instance.dispose()
247
237
  })
248
238
 
249
239
  it('handles multiple live events with same sync tag', async () => {
@@ -258,7 +248,6 @@ describe('queryStore', () => {
258
248
  of({result: mockData.movies, syncTags: mockSyncTags, ms: 0}).pipe(delay(0)),
259
249
  )
260
250
 
261
- const instance = createSanityInstance({projectId: 'test', dataset: 'test'})
262
251
  const query = '*[_type == "movie"]'
263
252
  const state = getQueryState(instance, query)
264
253
 
@@ -284,11 +273,9 @@ describe('queryStore', () => {
284
273
  expect(vi.mocked(fetch).mock.calls[2][2]?.lastLiveEventId).toBe('event2')
285
274
 
286
275
  unsubscribe()
287
- instance.dispose()
288
276
  })
289
277
 
290
278
  it('handles errors in query fetching', async () => {
291
- const instance = createSanityInstance({projectId: 'test', dataset: 'test'})
292
279
  const errorMessage = 'Query failed'
293
280
 
294
281
  // Override fetch to simulate error
@@ -306,29 +293,9 @@ describe('queryStore', () => {
306
293
  expect(() => state.getCurrent()).toThrow(errorMessage)
307
294
 
308
295
  unsubscribe()
309
- instance.dispose()
310
- })
311
-
312
- it('throws an error if an errorHandler has been called', () => {
313
- const instance = createSanityInstance({projectId: 'test', dataset: 'test'})
314
- const query = '*[_type == "movie"]'
315
- const state = getQueryState(instance, query)
316
-
317
- const resource = getOrCreateResource(instance, queryStore)
318
-
319
- // Create an error and call the error handler
320
- const testError = new Error('Global error from error handler')
321
- const handler = errorHandler({state: resource.state, instance})
322
- handler(testError)
323
-
324
- // Verify the error is thrown when accessing state
325
- expect(() => state.getCurrent()).toThrow('Global error from error handler')
326
-
327
- instance.dispose()
328
296
  })
329
297
 
330
298
  it('delays query state removal after unsubscribe', async () => {
331
- const instance = createSanityInstance({projectId: 'test', dataset: 'test'})
332
299
  const query = '*[_type == "movie"]'
333
300
  const state = getQueryState(instance, query)
334
301
  const unsubscribe = state.subscribe()
@@ -342,12 +309,9 @@ describe('queryStore', () => {
342
309
  // Wait for the cleanup delay and then state should be removed
343
310
  await new Promise((resolve) => setTimeout(resolve, 20))
344
311
  expect(state.getCurrent()).toBeUndefined()
345
-
346
- instance.dispose()
347
312
  })
348
313
 
349
314
  it('preserves query state if a new subscriber subscribes before cleanup delay', async () => {
350
- const instance = createSanityInstance({projectId: 'test', dataset: 'test'})
351
315
  const query = '*[_type == "movie"]'
352
316
  const state = getQueryState(instance, query)
353
317
  const unsubscribe1 = state.subscribe()
@@ -374,6 +338,5 @@ describe('queryStore', () => {
374
338
  {_id: 'movie2', _type: 'movie', title: 'Movie 2'},
375
339
  ])
376
340
  unsubscribe2()
377
- instance.dispose()
378
341
  })
379
342
  })
@@ -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'