@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
@@ -1,51 +1,38 @@
1
- import {SanityClient, type SyncTag} from '@sanity/client'
2
- import {Observable, of, Subject} from 'rxjs'
3
- import {describe, expect, it, type Mock, vi} from 'vitest'
4
-
5
- import {getClientState} from '../client/clientStore'
6
- import {createSanityInstance} from '../instance/sanityInstance'
7
- import {createResourceState, type ResourceState} from '../resources/createResource'
8
- import {type StateSource} from '../resources/createStateSourceAction'
1
+ import {NEVER, Observable, type Observer} from 'rxjs'
2
+ import {describe, expect, it, vi} from 'vitest'
3
+
4
+ import {getQueryState, resolveQuery} from '../query/queryStore'
5
+ import {createSanityInstance, type SanityInstance} from '../store/createSanityInstance'
6
+ import {type StateSource} from '../store/createStateSourceAction'
7
+ import {createStoreState, type StoreState} from '../store/createStoreState'
9
8
  import {type PreviewQueryResult, type PreviewStoreState} from './previewStore'
10
9
  import {subscribeToStateAndFetchBatches} from './subscribeToStateAndFetchBatches'
10
+ import {PREVIEW_PERSPECTIVE, PREVIEW_TAG} from './util'
11
11
 
12
- vi.mock('../client/clientStore')
13
-
14
- vi.mock('../resources/createResource', async (importOriginal) => {
15
- const original = await importOriginal<typeof import('../resources/createResource')>()
16
- return {...original, getOrCreateResource: vi.fn()}
17
- })
12
+ vi.mock('../query/queryStore')
18
13
 
19
14
  describe('subscribeToStateAndFetchBatches', () => {
20
- const instance = createSanityInstance({projectId: 'test', dataset: 'test'})
21
-
22
- let state: ResourceState<PreviewStoreState>
23
- let fetchResults: Subject<{result: PreviewQueryResult[]; syncTags: SyncTag[]}>
24
- let mockFetch: Mock
15
+ let instance: SanityInstance
16
+ let state: StoreState<PreviewStoreState>
25
17
 
26
18
  beforeEach(() => {
27
- state = createResourceState<PreviewStoreState>({
28
- documentTypes: {},
29
- lastLiveEventId: null,
19
+ vi.clearAllMocks()
20
+ instance = createSanityInstance({projectId: 'test', dataset: 'test'})
21
+ state = createStoreState<PreviewStoreState>({
30
22
  subscriptions: {},
31
- syncTags: {},
32
23
  values: {},
33
24
  })
34
- fetchResults = new Subject()
35
-
36
- mockFetch = vi.fn().mockImplementation(
37
- () =>
38
- new Observable((subscriber) => {
39
- const subscription = fetchResults.subscribe({
40
- next: (val) => subscriber.next(val),
41
- complete: () => subscriber.complete(),
42
- })
43
- return () => subscription.unsubscribe()
44
- }),
45
- )
46
- vi.mocked(getClientState).mockReturnValue({
47
- observable: of({observable: {fetch: mockFetch}} as unknown as SanityClient),
48
- } as StateSource<SanityClient>)
25
+
26
+ vi.mocked(getQueryState).mockReturnValue({
27
+ getCurrent: () => undefined,
28
+ observable: NEVER as Observable<PreviewQueryResult[] | undefined>,
29
+ } as StateSource<PreviewQueryResult[] | undefined>)
30
+
31
+ vi.mocked(resolveQuery).mockResolvedValue(undefined)
32
+ })
33
+
34
+ afterEach(() => {
35
+ instance.dispose()
49
36
  })
50
37
 
51
38
  it('batches rapid subscription changes into single requests', async () => {
@@ -53,55 +40,88 @@ describe('subscribeToStateAndFetchBatches', () => {
53
40
 
54
41
  // Add multiple subscriptions rapidly
55
42
  state.set('addSubscription1', {
56
- documentTypes: {doc1: 'test'},
57
43
  subscriptions: {doc1: {sub1: true}},
58
44
  })
59
45
 
60
46
  state.set('addSubscription2', (prev) => ({
61
- documentTypes: {...prev.documentTypes, doc2: 'test'},
62
47
  subscriptions: {...prev.subscriptions, doc2: {sub2: true}},
63
48
  }))
64
49
 
65
50
  // Wait for debounce
66
51
  await new Promise((resolve) => setTimeout(resolve, 100))
67
52
 
68
- expect(mockFetch).toHaveBeenCalledTimes(1)
69
- expect(mockFetch.mock.calls[0][1]).toMatchObject({
70
- __ids_0d8f6ec5: ['doc1', 'drafts.doc1', 'doc2', 'drafts.doc2'],
71
- })
53
+ expect(getQueryState).toHaveBeenCalledTimes(1)
54
+ expect(getQueryState).toHaveBeenCalledWith(
55
+ instance,
56
+ expect.any(String),
57
+ expect.objectContaining({
58
+ params: expect.objectContaining({
59
+ __ids_71322c7a: ['doc1', 'drafts.doc1', 'doc2', 'drafts.doc2'],
60
+ }),
61
+ perspective: PREVIEW_PERSPECTIVE,
62
+ tag: PREVIEW_TAG,
63
+ }),
64
+ )
72
65
 
73
66
  subscription.unsubscribe()
74
67
  })
75
68
 
76
- it('always uses latest client/schema/eventId state when fetching', async () => {
69
+ it('processes query results and updates state with resolved values', async () => {
70
+ const teardown = vi.fn()
71
+ const subscriber = vi
72
+ .fn<(observer: Observer<PreviewQueryResult[] | undefined>) => () => void>()
73
+ .mockReturnValue(teardown)
74
+
75
+ vi.mocked(getQueryState).mockReturnValue({
76
+ getCurrent: () => undefined,
77
+ observable: new Observable(subscriber),
78
+ } as StateSource<PreviewQueryResult[] | undefined>)
79
+
77
80
  const subscription = subscribeToStateAndFetchBatches({instance, state})
78
81
 
82
+ expect(subscriber).not.toHaveBeenCalled()
83
+
79
84
  // Add a subscription
80
85
  state.set('addSubscription', {
81
- documentTypes: {doc1: 'test'},
82
86
  subscriptions: {doc1: {sub1: true}},
83
87
  })
84
88
 
85
- // Update lastLiveEventId
86
- state.set('updateEventId', {lastLiveEventId: 'event1'})
89
+ expect(subscriber).not.toHaveBeenCalled()
87
90
 
88
91
  // Wait for debounce
89
92
  await new Promise((resolve) => setTimeout(resolve, 100))
90
93
 
91
- expect(mockFetch).toHaveBeenCalledWith(
92
- expect.any(String),
93
- expect.any(Object),
94
- expect.objectContaining({
95
- lastLiveEventId: 'event1',
94
+ expect(subscriber).toHaveBeenCalled()
95
+ expect(teardown).not.toHaveBeenCalled()
96
+
97
+ const [observer] = subscriber.mock.lastCall!
98
+
99
+ const timestamp = new Date().toISOString()
100
+
101
+ observer.next([
102
+ {
103
+ _id: 'doc1',
104
+ _type: 'test',
105
+ _updatedAt: timestamp,
106
+ titleCandidates: {title: 'Test Document'},
107
+ subtitleCandidates: {description: 'Test Description'},
108
+ },
109
+ ])
110
+
111
+ const {values} = state.get()
112
+ expect(values['doc1']).toEqual({
113
+ isPending: false,
114
+ data: expect.objectContaining({
115
+ title: 'Test Document',
96
116
  }),
97
- )
117
+ })
98
118
 
99
119
  subscription.unsubscribe()
120
+ expect(teardown).toHaveBeenCalled()
100
121
  })
101
122
 
102
123
  it('handles new subscriptions optimistically with pending states', async () => {
103
124
  state.set('initializeValues', {
104
- documentTypes: {doc1: 'test', doc2: 'test'},
105
125
  values: {doc1: {data: {title: 'Doc 1'}, isPending: false}},
106
126
  subscriptions: {doc1: {sub1: true}},
107
127
  })
@@ -131,11 +151,11 @@ describe('subscribeToStateAndFetchBatches', () => {
131
151
  })
132
152
 
133
153
  it('cancels and restarts fetches when subscription set changes', async () => {
154
+ const abortSpy = vi.spyOn(AbortController.prototype, 'abort')
134
155
  const subscription = subscribeToStateAndFetchBatches({instance, state})
135
156
 
136
157
  // Add initial subscription
137
158
  state.set('addSubscription1', {
138
- documentTypes: {doc1: 'test'},
139
159
  subscriptions: {doc1: {sub1: true}},
140
160
  })
141
161
 
@@ -143,64 +163,47 @@ describe('subscribeToStateAndFetchBatches', () => {
143
163
 
144
164
  // Add another subscription before first fetch completes
145
165
  state.set('addSubscription2', (prev) => ({
146
- documentTypes: {...prev.documentTypes, doc2: 'test'},
147
166
  subscriptions: {...prev.subscriptions, doc2: {sub2: true}},
148
167
  }))
149
168
 
150
169
  await new Promise((resolve) => setTimeout(resolve, 100))
151
170
 
152
- expect(mockFetch).toHaveBeenCalledTimes(2)
171
+ expect(getQueryState).toHaveBeenCalledTimes(2)
172
+ expect(abortSpy).toHaveBeenCalled()
153
173
 
154
174
  subscription.unsubscribe()
155
175
  })
156
176
 
157
- it('only refetches when actually needed due to distinctUntilChanged() usage', async () => {
158
- const subscription = subscribeToStateAndFetchBatches({instance, state})
159
-
160
- // Add a subscription
161
- state.set('addSubscription', {
162
- documentTypes: {doc1: 'test'},
163
- subscriptions: {doc1: {sub1: true}},
164
- })
165
-
166
- await new Promise((resolve) => setTimeout(resolve, 100))
167
-
168
- // Update state but don't change subscriptions
169
- state.set('unrelatedChange', {
170
- syncTags: {'s1:tag1': true},
171
- })
172
-
173
- await new Promise((resolve) => setTimeout(resolve, 100))
174
-
175
- expect(mockFetch).toHaveBeenCalledTimes(1)
177
+ it('processes and applies fetch results correctly', async () => {
178
+ const subscriber = vi.fn<(observer: Observer<PreviewQueryResult[] | undefined>) => () => void>()
176
179
 
177
- subscription.unsubscribe()
178
- })
180
+ vi.mocked(getQueryState).mockReturnValue({
181
+ getCurrent: () => undefined,
182
+ observable: new Observable(subscriber),
183
+ } as StateSource<PreviewQueryResult[] | undefined>)
179
184
 
180
- it('processes and applies fetch results correctly', async () => {
181
185
  const subscription = subscribeToStateAndFetchBatches({instance, state})
182
186
 
183
187
  // Add a subscription
184
188
  state.set('addSubscription', {
185
- documentTypes: {doc1: 'test'},
186
189
  subscriptions: {doc1: {sub1: true}},
187
190
  })
188
191
 
189
192
  await new Promise((resolve) => setTimeout(resolve, 100))
190
193
 
194
+ expect(subscriber).toHaveBeenCalled()
195
+ const [observer] = subscriber.mock.lastCall!
196
+
191
197
  // Emit fetch results
192
- fetchResults.next({
193
- result: [
194
- {
195
- _id: 'doc1',
196
- _type: 'test',
197
- _updatedAt: '2024-01-01T00:00:00Z',
198
- titleCandidates: {title: 'Test Document'},
199
- subtitleCandidates: {description: 'Test Description'},
200
- },
201
- ],
202
- syncTags: ['s1:tag1', 's1:tag2'],
203
- })
198
+ observer.next([
199
+ {
200
+ _id: 'doc1',
201
+ _type: 'test',
202
+ _updatedAt: '2024-01-01T00:00:00Z',
203
+ titleCandidates: {title: 'Test Document'},
204
+ subtitleCandidates: {description: 'Test Description'},
205
+ },
206
+ ])
204
207
 
205
208
  // Check that the state was updated
206
209
  expect(state.get().values['doc1']).toEqual({
@@ -209,10 +212,6 @@ describe('subscribeToStateAndFetchBatches', () => {
209
212
  }),
210
213
  isPending: false,
211
214
  })
212
- expect(state.get().syncTags).toEqual({
213
- 's1:tag1': true,
214
- 's1:tag2': true,
215
- })
216
215
 
217
216
  subscription.unsubscribe()
218
217
  })
@@ -1,100 +1,104 @@
1
- import {type SyncTag} from '@sanity/client'
2
1
  import {
3
- combineLatest,
4
2
  debounceTime,
3
+ defer,
5
4
  distinctUntilChanged,
6
5
  EMPTY,
6
+ filter,
7
+ from,
7
8
  map,
9
+ Observable,
8
10
  pairwise,
9
11
  startWith,
12
+ Subscription,
10
13
  switchMap,
11
14
  tap,
12
- withLatestFrom,
13
15
  } from 'rxjs'
14
16
 
15
- import {getClientState} from '../client/clientStore'
16
- import {type ActionContext, createInternalAction} from '../resources/createAction'
17
+ import {getQueryState, resolveQuery} from '../query/queryStore'
18
+ import {type StoreContext} from '../store/defineStore'
17
19
  import {createPreviewQuery, processPreviewQuery} from './previewQuery'
18
20
  import {type PreviewQueryResult, type PreviewStoreState} from './previewStore'
19
- import {PREVIEW_TAG} from './util'
21
+ import {PREVIEW_PERSPECTIVE, PREVIEW_TAG} from './util'
20
22
 
21
23
  const BATCH_DEBOUNCE_TIME = 50
22
24
 
23
- export const subscribeToStateAndFetchBatches = createInternalAction(
24
- ({state, instance}: ActionContext<PreviewStoreState>) => {
25
- return function () {
26
- const client$ = getClientState(instance, {apiVersion: 'vX'}).observable
27
- const documentTypes$ = state.observable.pipe(
28
- map((i) => i.documentTypes),
29
- distinctUntilChanged(),
30
- )
31
- const lastLiveEventId$ = state.observable.pipe(
32
- map((i) => i.lastLiveEventId),
33
- distinctUntilChanged(),
34
- )
25
+ const isSetEqual = <T>(a: Set<T>, b: Set<T>) =>
26
+ a.size === b.size && Array.from(a).every((i) => b.has(i))
35
27
 
36
- const newSubscriberIds$ = state.observable.pipe(
37
- map(({subscriptions}) => new Set(Object.keys(subscriptions))),
38
- distinctUntilChanged((a, b) =>
39
- a.size !== b.size ? false : Array.from(a).every((i) => b.has(i)),
40
- ),
41
- debounceTime(BATCH_DEBOUNCE_TIME),
42
- startWith(new Set<string>()),
43
- pairwise(),
44
- tap(([prevIds, currIds]) => {
45
- // for all new subscriptions, set their values to pending
46
- const newIds = [...currIds].filter((element) => !prevIds.has(element))
47
- state.set('updatingPending', (prev) => {
48
- const pendingValues = newIds.reduce<PreviewStoreState['values']>((acc, id) => {
49
- const prevValue = prev.values[id]
50
- const value = prevValue?.data ? prevValue.data : null
51
- acc[id] = {data: value, isPending: true}
52
- return acc
53
- }, {})
54
- return {values: {...prev.values, ...pendingValues}}
55
- })
56
- }),
57
- withLatestFrom(documentTypes$),
58
- map(([[, ids], documentTypes]) => ({ids, documentTypes})),
59
- )
28
+ export const subscribeToStateAndFetchBatches = ({
29
+ state,
30
+ instance,
31
+ }: StoreContext<PreviewStoreState>): Subscription => {
32
+ const newSubscriberIds$ = state.observable.pipe(
33
+ map(({subscriptions}) => new Set(Object.keys(subscriptions))),
34
+ distinctUntilChanged(isSetEqual),
35
+ debounceTime(BATCH_DEBOUNCE_TIME),
36
+ startWith(new Set<string>()),
37
+ pairwise(),
38
+ tap(([prevIds, currIds]) => {
39
+ // for all new subscriptions, set their values to pending
40
+ const newIds = [...currIds].filter((element) => !prevIds.has(element))
41
+ state.set('updatingPending', (prev) => {
42
+ const pendingValues = newIds.reduce<PreviewStoreState['values']>((acc, id) => {
43
+ const prevValue = prev.values[id]
44
+ const value = prevValue?.data ? prevValue.data : null
45
+ acc[id] = {data: value, isPending: true}
46
+ return acc
47
+ }, {})
48
+ return {values: {...prev.values, ...pendingValues}}
49
+ })
50
+ }),
51
+ map(([, ids]) => ids),
52
+ distinctUntilChanged(isSetEqual),
53
+ )
60
54
 
61
- return combineLatest([newSubscriberIds$, lastLiveEventId$, client$])
62
- .pipe(
63
- switchMap(([{ids}, lastLiveEventId, client]) => {
64
- if (!ids.size) return EMPTY
65
- const {query, params} = createPreviewQuery(ids)
55
+ return newSubscriberIds$
56
+ .pipe(
57
+ switchMap((ids) => {
58
+ if (!ids.size) return EMPTY
59
+ const {query, params} = createPreviewQuery(ids)
60
+ const controller = new AbortController()
61
+ return new Observable<PreviewQueryResult[]>((observer) => {
62
+ const {getCurrent, observable} = getQueryState<PreviewQueryResult[]>(instance, query, {
63
+ params,
64
+ tag: PREVIEW_TAG,
65
+ perspective: PREVIEW_PERSPECTIVE,
66
+ })
67
+ const source$ = defer(() => {
68
+ if (getCurrent() === undefined) {
69
+ return from(
70
+ resolveQuery<PreviewQueryResult[]>(instance, query, {
71
+ params,
72
+ tag: PREVIEW_TAG,
73
+ perspective: PREVIEW_PERSPECTIVE,
74
+ signal: controller.signal,
75
+ }),
76
+ ).pipe(switchMap(() => observable))
77
+ }
78
+ return observable
79
+ }).pipe(filter((result) => result !== undefined))
80
+ const subscription = source$.subscribe(observer)
81
+ return () => {
82
+ if (!controller.signal.aborted) {
83
+ controller.abort()
84
+ }
66
85
 
67
- return client.observable
68
- .fetch<PreviewQueryResult[]>(query, params, {
69
- filterResponse: false,
70
- returnQuery: false,
71
- perspective: 'drafts',
72
- tag: PREVIEW_TAG,
73
- lastLiveEventId,
74
- })
75
- .pipe(map((response) => ({...response, ids})))
76
- }),
77
- map(({ids, result, syncTags}) => ({
78
- syncTags,
79
- values: processPreviewQuery({
80
- projectId: instance.identity.projectId,
81
- dataset: instance.identity.dataset,
82
- ids,
83
- results: result,
84
- }),
85
- })),
86
- )
87
- .subscribe({
88
- next: ({syncTags = [], values}) => {
89
- state.set('updateResult', (prev) => ({
90
- values: {...prev.values, ...values},
91
- syncTags: syncTags.reduce<Record<SyncTag, true>>((acc, next) => {
92
- acc[next] = true
93
- return acc
94
- }, {}),
95
- }))
96
- },
97
- })
98
- }
99
- },
100
- )
86
+ subscription.unsubscribe()
87
+ }
88
+ }).pipe(map((data) => ({data, ids})))
89
+ }),
90
+ map(({ids, data}) => ({
91
+ values: processPreviewQuery({
92
+ projectId: instance.config.projectId!,
93
+ dataset: instance.config.dataset!,
94
+ ids,
95
+ results: data,
96
+ }),
97
+ })),
98
+ )
99
+ .subscribe({
100
+ next: ({values}) => {
101
+ state.set('updateResult', (prev) => ({values: {...prev.values, ...values}}))
102
+ },
103
+ })
104
+ }
@@ -2,6 +2,7 @@ import {getEnv} from '../utils/getEnv'
2
2
  import {type PreviewValue, type ValuePending} from './previewStore'
3
3
 
4
4
  export const PREVIEW_TAG = 'preview'
5
+ export const PREVIEW_PERSPECTIVE = 'drafts'
5
6
  export const STABLE_EMPTY_PREVIEW: ValuePending<PreviewValue> = {data: null, isPending: false}
6
7
  export const STABLE_ERROR_PREVIEW: ValuePending<PreviewValue> = {
7
8
  data: {
@@ -3,8 +3,8 @@ import {of} from 'rxjs'
3
3
  import {describe, it} from 'vitest'
4
4
 
5
5
  import {getClientState} from '../client/clientStore'
6
- import {createSanityInstance} from '../instance/sanityInstance'
7
- import {type StateSource} from '../resources/createStateSourceAction'
6
+ import {createSanityInstance} from '../store/createSanityInstance'
7
+ import {type StateSource} from '../store/createStateSourceAction'
8
8
  import {resolveProject} from './project'
9
9
 
10
10
  vi.mock('../client/clientStore')
@@ -26,7 +26,7 @@ describe('project', () => {
26
26
  observable: of(mockClient),
27
27
  } as StateSource<SanityClient>)
28
28
 
29
- const result = await resolveProject(instance, 'a')
29
+ const result = await resolveProject(instance, {projectId: 'a'})
30
30
  expect(result).toEqual(project)
31
31
  expect(getById).toHaveBeenCalledWith('a')
32
32
  })
@@ -1,15 +1,38 @@
1
1
  import {switchMap} from 'rxjs'
2
2
 
3
3
  import {getClientState} from '../client/clientStore'
4
+ import {type ProjectHandle} from '../config/sanityConfig'
4
5
  import {createFetcherStore} from '../utils/createFetcherStore'
5
6
 
7
+ const API_VERSION = 'v2025-02-19'
8
+
6
9
  const project = createFetcherStore({
7
10
  name: 'Project',
8
- getKey: (projectId: string) => projectId,
9
- fetcher: (instance) => (projectId: string) =>
10
- getClientState(instance, {apiVersion: 'vX', scope: 'global'}).observable.pipe(
11
- switchMap((client) => client.observable.projects.getById(projectId)),
12
- ),
11
+ getKey: (instance, options?: ProjectHandle) => {
12
+ const projectId = options?.projectId ?? instance.config.projectId
13
+ if (!projectId) {
14
+ throw new Error('A projectId is required to use the project API.')
15
+ }
16
+ return projectId
17
+ },
18
+ fetcher:
19
+ (instance) =>
20
+ (options: ProjectHandle = {}) => {
21
+ const projectId = options.projectId ?? instance.config.projectId
22
+
23
+ return getClientState(instance, {
24
+ apiVersion: API_VERSION,
25
+ scope: 'global',
26
+ projectId,
27
+ }).observable.pipe(
28
+ switchMap((client) =>
29
+ client.observable.projects.getById(
30
+ // non-null assertion is fine with the above throwing
31
+ (projectId ?? instance.config.projectId)!,
32
+ ),
33
+ ),
34
+ )
35
+ },
13
36
  })
14
37
 
15
38
  /** @public */