@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.
- package/dist/index.d.ts +428 -325
- package/dist/index.js +1618 -1553
- package/dist/index.js.map +1 -1
- package/package.json +6 -7
- package/src/_exports/index.ts +31 -30
- package/src/auth/authStore.test.ts +149 -104
- package/src/auth/authStore.ts +51 -100
- package/src/auth/handleAuthCallback.test.ts +67 -34
- package/src/auth/handleAuthCallback.ts +8 -7
- package/src/auth/logout.test.ts +61 -29
- package/src/auth/logout.ts +26 -28
- package/src/auth/refreshStampedToken.test.ts +9 -9
- package/src/auth/refreshStampedToken.ts +62 -56
- package/src/auth/subscribeToStateAndFetchCurrentUser.test.ts +5 -5
- package/src/auth/subscribeToStateAndFetchCurrentUser.ts +45 -47
- package/src/auth/subscribeToStorageEventsAndSetToken.test.ts +4 -5
- package/src/auth/subscribeToStorageEventsAndSetToken.ts +22 -24
- package/src/client/clientStore.test.ts +131 -67
- package/src/client/clientStore.ts +117 -116
- package/src/comlink/controller/actions/destroyController.test.ts +38 -13
- package/src/comlink/controller/actions/destroyController.ts +11 -15
- package/src/comlink/controller/actions/getOrCreateChannel.test.ts +56 -27
- package/src/comlink/controller/actions/getOrCreateChannel.ts +37 -35
- package/src/comlink/controller/actions/getOrCreateController.test.ts +27 -16
- package/src/comlink/controller/actions/getOrCreateController.ts +23 -22
- package/src/comlink/controller/actions/releaseChannel.test.ts +37 -13
- package/src/comlink/controller/actions/releaseChannel.ts +22 -21
- package/src/comlink/controller/comlinkControllerStore.test.ts +65 -36
- package/src/comlink/controller/comlinkControllerStore.ts +44 -5
- package/src/comlink/node/actions/getOrCreateNode.test.ts +31 -15
- package/src/comlink/node/actions/getOrCreateNode.ts +30 -29
- package/src/comlink/node/actions/releaseNode.test.ts +75 -55
- package/src/comlink/node/actions/releaseNode.ts +19 -21
- package/src/comlink/node/comlinkNodeStore.test.ts +6 -11
- package/src/comlink/node/comlinkNodeStore.ts +22 -5
- package/src/config/authConfig.ts +79 -0
- package/src/config/sanityConfig.ts +48 -0
- package/src/datasets/datasets.test.ts +2 -2
- package/src/datasets/datasets.ts +18 -5
- package/src/document/actions.test.ts +22 -10
- package/src/document/actions.ts +44 -56
- package/src/document/applyDocumentActions.test.ts +96 -36
- package/src/document/applyDocumentActions.ts +140 -99
- package/src/document/documentStore.test.ts +103 -155
- package/src/document/documentStore.ts +247 -237
- package/src/document/listen.ts +56 -55
- package/src/document/patchOperations.ts +0 -43
- package/src/document/permissions.test.ts +25 -12
- package/src/document/permissions.ts +11 -4
- package/src/document/processActions.test.ts +41 -8
- package/src/document/reducers.test.ts +87 -16
- package/src/document/reducers.ts +2 -2
- package/src/document/sharedListener.test.ts +34 -16
- package/src/document/sharedListener.ts +33 -11
- package/src/preview/getPreviewState.test.ts +40 -39
- package/src/preview/getPreviewState.ts +68 -56
- package/src/preview/previewConstants.ts +43 -0
- package/src/preview/previewQuery.test.ts +1 -1
- package/src/preview/previewQuery.ts +4 -5
- package/src/preview/previewStore.test.ts +13 -58
- package/src/preview/previewStore.ts +7 -21
- package/src/preview/resolvePreview.test.ts +33 -104
- package/src/preview/resolvePreview.ts +11 -21
- package/src/preview/subscribeToStateAndFetchBatches.test.ts +96 -97
- package/src/preview/subscribeToStateAndFetchBatches.ts +85 -81
- package/src/preview/util.ts +1 -0
- package/src/project/project.test.ts +3 -3
- package/src/project/project.ts +28 -5
- package/src/projection/getProjectionState.test.ts +69 -49
- package/src/projection/getProjectionState.ts +42 -50
- package/src/projection/projectionQuery.ts +1 -1
- package/src/projection/projectionStore.test.ts +13 -51
- package/src/projection/projectionStore.ts +6 -18
- package/src/projection/resolveProjection.test.ts +32 -127
- package/src/projection/resolveProjection.ts +15 -28
- package/src/projection/subscribeToStateAndFetchBatches.test.ts +105 -90
- package/src/projection/subscribeToStateAndFetchBatches.ts +94 -81
- package/src/projection/util.ts +2 -0
- package/src/projects/projects.test.ts +13 -4
- package/src/projects/projects.ts +6 -1
- package/src/query/queryStore.test.ts +10 -47
- package/src/query/queryStore.ts +151 -133
- package/src/query/queryStoreConstants.ts +2 -0
- package/src/store/createActionBinder.test.ts +153 -0
- package/src/store/createActionBinder.ts +176 -0
- package/src/store/createSanityInstance.test.ts +84 -0
- package/src/store/createSanityInstance.ts +124 -0
- package/src/store/createStateSourceAction.test.ts +196 -0
- package/src/store/createStateSourceAction.ts +260 -0
- package/src/store/createStoreInstance.test.ts +81 -0
- package/src/store/createStoreInstance.ts +80 -0
- package/src/store/createStoreState.test.ts +85 -0
- package/src/store/createStoreState.ts +92 -0
- package/src/store/defineStore.test.ts +18 -0
- package/src/store/defineStore.ts +81 -0
- package/src/users/reducers.test.ts +318 -0
- package/src/users/reducers.ts +88 -0
- package/src/users/types.ts +46 -4
- package/src/users/usersConstants.ts +4 -0
- package/src/users/usersStore.test.ts +350 -223
- package/src/users/usersStore.ts +285 -149
- package/src/utils/createFetcherStore.test.ts +6 -7
- package/src/utils/createFetcherStore.ts +150 -153
- package/src/{common/util.test.ts → utils/hashString.test.ts} +1 -1
- package/src/auth/fetchLoginUrls.test.ts +0 -163
- package/src/auth/fetchLoginUrls.ts +0 -74
- package/src/common/createLiveEventSubscriber.test.ts +0 -121
- package/src/common/createLiveEventSubscriber.ts +0 -55
- package/src/common/types.ts +0 -4
- package/src/instance/identity.test.ts +0 -46
- package/src/instance/identity.ts +0 -29
- package/src/instance/sanityInstance.test.ts +0 -77
- package/src/instance/sanityInstance.ts +0 -57
- package/src/instance/types.ts +0 -37
- package/src/preview/getPreviewProjection.ts +0 -45
- package/src/resources/README.md +0 -370
- package/src/resources/createAction.test.ts +0 -101
- package/src/resources/createAction.ts +0 -44
- package/src/resources/createResource.test.ts +0 -112
- package/src/resources/createResource.ts +0 -102
- package/src/resources/createStateSourceAction.test.ts +0 -114
- package/src/resources/createStateSourceAction.ts +0 -83
- package/src/resources/createStore.test.ts +0 -67
- package/src/resources/createStore.ts +0 -46
- package/src/store/createStore.test.ts +0 -108
- package/src/store/createStore.ts +0 -106
- /package/src/{common/util.ts → utils/hashString.ts} +0 -0
|
@@ -1,51 +1,38 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
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('../
|
|
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
|
-
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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(
|
|
69
|
-
expect(
|
|
70
|
-
|
|
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('
|
|
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
|
-
|
|
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(
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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(
|
|
171
|
+
expect(getQueryState).toHaveBeenCalledTimes(2)
|
|
172
|
+
expect(abortSpy).toHaveBeenCalled()
|
|
153
173
|
|
|
154
174
|
subscription.unsubscribe()
|
|
155
175
|
})
|
|
156
176
|
|
|
157
|
-
it('
|
|
158
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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 {
|
|
16
|
-
import {type
|
|
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
|
-
|
|
24
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
+
}
|
package/src/preview/util.ts
CHANGED
|
@@ -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 '../
|
|
7
|
-
import {type StateSource} from '../
|
|
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
|
})
|
package/src/project/project.ts
CHANGED
|
@@ -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: (
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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 */
|