@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,36 +1,23 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {filter, firstValueFrom} from 'rxjs'
|
|
2
|
+
|
|
3
|
+
import {type DocumentHandle} from '../config/sanityConfig'
|
|
4
|
+
import {bindActionByDataset} from '../store/createActionBinder'
|
|
3
5
|
import {getProjectionState} from './getProjectionState'
|
|
4
|
-
import {
|
|
5
|
-
projectionStore,
|
|
6
|
-
type ProjectionStoreState,
|
|
7
|
-
type ProjectionValuePending,
|
|
8
|
-
type ValidProjection,
|
|
9
|
-
} from './projectionStore'
|
|
6
|
+
import {projectionStore, type ValidProjection} from './projectionStore'
|
|
10
7
|
|
|
11
|
-
interface ResolveProjectionOptions {
|
|
12
|
-
document: DocumentHandle
|
|
8
|
+
interface ResolveProjectionOptions extends DocumentHandle {
|
|
13
9
|
projection: ValidProjection
|
|
14
10
|
}
|
|
15
11
|
|
|
16
12
|
/**
|
|
17
13
|
* @beta
|
|
18
14
|
*/
|
|
19
|
-
export const resolveProjection =
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const current = getCurrent()
|
|
29
|
-
if (current?.data) {
|
|
30
|
-
resolve(current)
|
|
31
|
-
unsubscribe()
|
|
32
|
-
}
|
|
33
|
-
})
|
|
34
|
-
})
|
|
35
|
-
}
|
|
36
|
-
})
|
|
15
|
+
export const resolveProjection = bindActionByDataset(
|
|
16
|
+
projectionStore,
|
|
17
|
+
({instance}, {projection, ...docHandle}: ResolveProjectionOptions) =>
|
|
18
|
+
firstValueFrom(
|
|
19
|
+
getProjectionState(instance, {...docHandle, projection}).observable.pipe(
|
|
20
|
+
filter((i) => !!i.data),
|
|
21
|
+
),
|
|
22
|
+
),
|
|
23
|
+
)
|
|
@@ -1,51 +1,39 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
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'
|
|
8
|
+
import {hashString} from '../utils/hashString'
|
|
10
9
|
import {type ProjectionQueryResult, type ProjectionStoreState} from './projectionStore'
|
|
11
10
|
import {subscribeToStateAndFetchBatches} from './subscribeToStateAndFetchBatches'
|
|
12
11
|
|
|
13
|
-
vi.mock('../
|
|
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<ProjectionStoreState>
|
|
23
|
-
let fetchResults: Subject<{result: ProjectionQueryResult[]; syncTags: SyncTag[]}>
|
|
24
|
-
let mockFetch: Mock
|
|
15
|
+
let instance: SanityInstance
|
|
16
|
+
let state: StoreState<ProjectionStoreState>
|
|
25
17
|
|
|
26
18
|
beforeEach(() => {
|
|
27
|
-
|
|
19
|
+
vi.clearAllMocks()
|
|
20
|
+
instance = createSanityInstance({projectId: 'test', dataset: 'test'})
|
|
21
|
+
state = createStoreState<ProjectionStoreState>({
|
|
28
22
|
documentProjections: {},
|
|
29
|
-
lastLiveEventId: null,
|
|
30
23
|
subscriptions: {},
|
|
31
|
-
syncTags: {},
|
|
32
24
|
values: {},
|
|
33
25
|
})
|
|
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>)
|
|
26
|
+
|
|
27
|
+
vi.mocked(getQueryState).mockReturnValue({
|
|
28
|
+
getCurrent: () => undefined,
|
|
29
|
+
observable: NEVER as Observable<ProjectionQueryResult[] | undefined>,
|
|
30
|
+
} as StateSource<ProjectionQueryResult[] | undefined>)
|
|
31
|
+
|
|
32
|
+
vi.mocked(resolveQuery).mockResolvedValue(undefined)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
afterEach(() => {
|
|
36
|
+
instance.dispose()
|
|
49
37
|
})
|
|
50
38
|
|
|
51
39
|
it('batches rapid subscription changes into single requests', async () => {
|
|
@@ -67,38 +55,82 @@ describe('subscribeToStateAndFetchBatches', () => {
|
|
|
67
55
|
// Wait for debounce
|
|
68
56
|
await new Promise((resolve) => setTimeout(resolve, 100))
|
|
69
57
|
|
|
70
|
-
expect(
|
|
71
|
-
expect(
|
|
72
|
-
|
|
73
|
-
|
|
58
|
+
expect(getQueryState).toHaveBeenCalledTimes(1)
|
|
59
|
+
expect(getQueryState).toHaveBeenCalledWith(
|
|
60
|
+
instance,
|
|
61
|
+
expect.any(String),
|
|
62
|
+
expect.objectContaining({
|
|
63
|
+
params: {
|
|
64
|
+
[`__ids_${projectionHash}`]: ['doc1', 'drafts.doc1', 'doc2', 'drafts.doc2'],
|
|
65
|
+
},
|
|
66
|
+
}),
|
|
67
|
+
)
|
|
74
68
|
|
|
75
69
|
subscription.unsubscribe()
|
|
76
70
|
})
|
|
77
71
|
|
|
78
|
-
it('
|
|
72
|
+
it('processes query results and updates state with resolved values', async () => {
|
|
73
|
+
const teardown = vi.fn()
|
|
74
|
+
const subscriber = vi
|
|
75
|
+
.fn<(observer: Observer<ProjectionQueryResult[] | undefined>) => () => void>()
|
|
76
|
+
.mockReturnValue(teardown)
|
|
77
|
+
|
|
78
|
+
vi.mocked(getQueryState).mockReturnValue({
|
|
79
|
+
getCurrent: () => undefined,
|
|
80
|
+
observable: new Observable(subscriber),
|
|
81
|
+
} as StateSource<ProjectionQueryResult[] | undefined>)
|
|
82
|
+
|
|
79
83
|
const subscription = subscribeToStateAndFetchBatches({instance, state})
|
|
80
84
|
|
|
85
|
+
expect(subscriber).not.toHaveBeenCalled()
|
|
86
|
+
|
|
81
87
|
// Add a subscription
|
|
82
88
|
state.set('addSubscription', {
|
|
83
|
-
documentProjections: {doc1: '{title
|
|
89
|
+
documentProjections: {doc1: '{title}'},
|
|
84
90
|
subscriptions: {doc1: {sub1: true}},
|
|
85
91
|
})
|
|
86
92
|
|
|
87
|
-
|
|
88
|
-
state.set('updateEventId', {lastLiveEventId: 'event1'})
|
|
93
|
+
expect(subscriber).not.toHaveBeenCalled()
|
|
89
94
|
|
|
90
95
|
// Wait for debounce
|
|
91
96
|
await new Promise((resolve) => setTimeout(resolve, 100))
|
|
92
97
|
|
|
93
|
-
expect(
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
98
|
+
expect(subscriber).toHaveBeenCalled()
|
|
99
|
+
expect(teardown).not.toHaveBeenCalled()
|
|
100
|
+
|
|
101
|
+
const [observer] = subscriber.mock.lastCall!
|
|
102
|
+
|
|
103
|
+
const timestamp = new Date().toISOString()
|
|
104
|
+
|
|
105
|
+
observer.next([
|
|
106
|
+
{
|
|
107
|
+
_id: 'doc1',
|
|
108
|
+
_type: 'doc',
|
|
109
|
+
_updatedAt: timestamp,
|
|
110
|
+
result: {title: 'resolved'},
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
_id: 'drafts.doc1',
|
|
114
|
+
_type: 'doc',
|
|
115
|
+
_updatedAt: timestamp,
|
|
116
|
+
result: {title: 'resolved'},
|
|
117
|
+
},
|
|
118
|
+
])
|
|
119
|
+
|
|
120
|
+
const {values} = state.get()
|
|
121
|
+
expect(values['doc1']).toEqual({
|
|
122
|
+
isPending: false,
|
|
123
|
+
data: {
|
|
124
|
+
title: 'resolved',
|
|
125
|
+
status: {
|
|
126
|
+
lastEditedDraftAt: timestamp,
|
|
127
|
+
lastEditedPublishedAt: timestamp,
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
})
|
|
100
131
|
|
|
101
132
|
subscription.unsubscribe()
|
|
133
|
+
expect(teardown).toHaveBeenCalled()
|
|
102
134
|
})
|
|
103
135
|
|
|
104
136
|
it('handles new subscriptions optimistically with pending states', async () => {
|
|
@@ -133,6 +165,7 @@ describe('subscribeToStateAndFetchBatches', () => {
|
|
|
133
165
|
})
|
|
134
166
|
|
|
135
167
|
it('cancels and restarts fetches when subscription set changes', async () => {
|
|
168
|
+
const abortSpy = vi.spyOn(AbortController.prototype, 'abort')
|
|
136
169
|
const subscription = subscribeToStateAndFetchBatches({instance, state})
|
|
137
170
|
|
|
138
171
|
// Add initial subscription
|
|
@@ -151,35 +184,21 @@ describe('subscribeToStateAndFetchBatches', () => {
|
|
|
151
184
|
|
|
152
185
|
await new Promise((resolve) => setTimeout(resolve, 100))
|
|
153
186
|
|
|
154
|
-
expect(
|
|
187
|
+
expect(getQueryState).toHaveBeenCalledTimes(3)
|
|
188
|
+
expect(abortSpy).toHaveBeenCalled()
|
|
155
189
|
|
|
156
190
|
subscription.unsubscribe()
|
|
157
191
|
})
|
|
158
192
|
|
|
159
|
-
it('
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
// Add a subscription
|
|
163
|
-
state.set('addSubscription', {
|
|
164
|
-
documentProjections: {doc1: '{title, description}'},
|
|
165
|
-
subscriptions: {doc1: {sub1: true}},
|
|
166
|
-
})
|
|
167
|
-
|
|
168
|
-
await new Promise((resolve) => setTimeout(resolve, 100))
|
|
169
|
-
|
|
170
|
-
// Update state but don't change subscriptions
|
|
171
|
-
state.set('unrelatedChange', {
|
|
172
|
-
syncTags: {'s1:tag1': true},
|
|
173
|
-
})
|
|
174
|
-
|
|
175
|
-
await new Promise((resolve) => setTimeout(resolve, 100))
|
|
176
|
-
|
|
177
|
-
expect(mockFetch).toHaveBeenCalledTimes(1)
|
|
193
|
+
it('processes and applies fetch results correctly', async () => {
|
|
194
|
+
const subscriber =
|
|
195
|
+
vi.fn<(observer: Observer<ProjectionQueryResult[] | undefined>) => () => void>()
|
|
178
196
|
|
|
179
|
-
|
|
180
|
-
|
|
197
|
+
vi.mocked(getQueryState).mockReturnValue({
|
|
198
|
+
getCurrent: () => undefined,
|
|
199
|
+
observable: new Observable(subscriber),
|
|
200
|
+
} as StateSource<ProjectionQueryResult[] | undefined>)
|
|
181
201
|
|
|
182
|
-
it('processes and applies fetch results correctly', async () => {
|
|
183
202
|
const subscription = subscribeToStateAndFetchBatches({instance, state})
|
|
184
203
|
|
|
185
204
|
// Add a subscription
|
|
@@ -190,18 +209,18 @@ describe('subscribeToStateAndFetchBatches', () => {
|
|
|
190
209
|
|
|
191
210
|
await new Promise((resolve) => setTimeout(resolve, 100))
|
|
192
211
|
|
|
212
|
+
expect(subscriber).toHaveBeenCalled()
|
|
213
|
+
const [observer] = subscriber.mock.lastCall!
|
|
214
|
+
|
|
193
215
|
// Emit fetch results
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
],
|
|
203
|
-
syncTags: ['s1:tag1', 's1:tag2'],
|
|
204
|
-
})
|
|
216
|
+
observer.next([
|
|
217
|
+
{
|
|
218
|
+
_id: 'doc1',
|
|
219
|
+
_type: 'test',
|
|
220
|
+
_updatedAt: '2024-01-01T00:00:00Z',
|
|
221
|
+
result: {title: 'Test Document', description: 'Test Description'},
|
|
222
|
+
},
|
|
223
|
+
])
|
|
205
224
|
|
|
206
225
|
// Check that the state was updated
|
|
207
226
|
expect(state.get().values['doc1']).toEqual({
|
|
@@ -214,10 +233,6 @@ describe('subscribeToStateAndFetchBatches', () => {
|
|
|
214
233
|
}),
|
|
215
234
|
isPending: false,
|
|
216
235
|
})
|
|
217
|
-
expect(state.get().syncTags).toEqual({
|
|
218
|
-
's1:tag1': true,
|
|
219
|
-
's1:tag2': true,
|
|
220
|
-
})
|
|
221
236
|
|
|
222
237
|
subscription.unsubscribe()
|
|
223
238
|
})
|
|
@@ -1,104 +1,117 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {isEqual} from 'lodash-es'
|
|
2
2
|
import {
|
|
3
3
|
combineLatest,
|
|
4
4
|
debounceTime,
|
|
5
|
+
defer,
|
|
5
6
|
distinctUntilChanged,
|
|
6
7
|
EMPTY,
|
|
8
|
+
filter,
|
|
9
|
+
from,
|
|
7
10
|
map,
|
|
8
11
|
Observable,
|
|
9
12
|
pairwise,
|
|
10
13
|
startWith,
|
|
14
|
+
Subscription,
|
|
11
15
|
switchMap,
|
|
12
16
|
tap,
|
|
13
|
-
withLatestFrom,
|
|
14
17
|
} from 'rxjs'
|
|
15
18
|
|
|
16
|
-
import {
|
|
17
|
-
import {type
|
|
19
|
+
import {getQueryState, resolveQuery} from '../query/queryStore'
|
|
20
|
+
import {type StoreContext} from '../store/defineStore'
|
|
18
21
|
import {createProjectionQuery, processProjectionQuery} from './projectionQuery'
|
|
19
22
|
import {type ProjectionQueryResult, type ProjectionStoreState} from './projectionStore'
|
|
20
|
-
import {PROJECTION_TAG} from './util'
|
|
23
|
+
import {PROJECTION_PERSPECTIVE, PROJECTION_TAG} from './util'
|
|
21
24
|
|
|
22
25
|
const BATCH_DEBOUNCE_TIME = 50
|
|
23
26
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
return function () {
|
|
27
|
-
const client$ = new Observable<SanityClient>((observer) =>
|
|
28
|
-
getClientState(instance, {apiVersion: 'vX'}).observable.subscribe(observer),
|
|
29
|
-
)
|
|
27
|
+
const isSetEqual = <T>(a: Set<T>, b: Set<T>) =>
|
|
28
|
+
a.size === b.size && Array.from(a).every((i) => b.has(i))
|
|
30
29
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
30
|
+
export const subscribeToStateAndFetchBatches = ({
|
|
31
|
+
state,
|
|
32
|
+
instance,
|
|
33
|
+
}: StoreContext<ProjectionStoreState>): Subscription => {
|
|
34
|
+
const documentProjections$ = state.observable.pipe(
|
|
35
|
+
map((i) => i.documentProjections),
|
|
36
|
+
distinctUntilChanged(),
|
|
37
|
+
)
|
|
39
38
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
39
|
+
const newSubscriberIds$ = state.observable.pipe(
|
|
40
|
+
map(({subscriptions}) => new Set(Object.keys(subscriptions))),
|
|
41
|
+
distinctUntilChanged(isSetEqual),
|
|
42
|
+
debounceTime(BATCH_DEBOUNCE_TIME),
|
|
43
|
+
startWith(new Set<string>()),
|
|
44
|
+
pairwise(),
|
|
45
|
+
tap(([prevIds, currIds]) => {
|
|
46
|
+
// for all new subscriptions, set their values to pending
|
|
47
|
+
const newIds = [...currIds].filter((element) => !prevIds.has(element))
|
|
48
|
+
state.set('updatingPending', (prev) => {
|
|
49
|
+
const pendingValues = newIds.reduce<ProjectionStoreState['values']>((acc, id) => {
|
|
50
|
+
const prevValue = prev.values[id]
|
|
51
|
+
const value = prevValue?.data ? prevValue.data : null
|
|
52
|
+
acc[id] = {data: value, isPending: true}
|
|
53
|
+
return acc
|
|
54
|
+
}, {})
|
|
55
|
+
return {values: {...prev.values, ...pendingValues}}
|
|
56
|
+
})
|
|
57
|
+
}),
|
|
58
|
+
map(([, ids]) => ids),
|
|
59
|
+
distinctUntilChanged(isSetEqual),
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
return combineLatest([newSubscriberIds$, documentProjections$])
|
|
63
|
+
.pipe(
|
|
64
|
+
distinctUntilChanged(isEqual),
|
|
65
|
+
switchMap(([ids, documentProjections]) => {
|
|
66
|
+
if (!ids.size) return EMPTY
|
|
67
|
+
const {query, params} = createProjectionQuery(ids, documentProjections)
|
|
68
|
+
const controller = new AbortController()
|
|
69
|
+
|
|
70
|
+
return new Observable<ProjectionQueryResult[]>((observer) => {
|
|
71
|
+
const {getCurrent, observable} = getQueryState<ProjectionQueryResult[]>(instance, query, {
|
|
72
|
+
params,
|
|
73
|
+
tag: PROJECTION_TAG,
|
|
74
|
+
perspective: PROJECTION_PERSPECTIVE,
|
|
59
75
|
})
|
|
60
|
-
}),
|
|
61
|
-
withLatestFrom(documentProjections$),
|
|
62
|
-
map(([[, ids], documentProjections]) => ({ids, documentProjections})),
|
|
63
|
-
)
|
|
64
76
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
77
|
+
const source$ = defer(() => {
|
|
78
|
+
if (getCurrent() === undefined) {
|
|
79
|
+
return from(
|
|
80
|
+
resolveQuery<ProjectionQueryResult[]>(instance, query, {
|
|
81
|
+
params,
|
|
82
|
+
tag: PROJECTION_TAG,
|
|
83
|
+
perspective: PROJECTION_PERSPECTIVE,
|
|
84
|
+
signal: controller.signal,
|
|
85
|
+
}),
|
|
86
|
+
).pipe(switchMap(() => observable))
|
|
87
|
+
}
|
|
88
|
+
return observable
|
|
89
|
+
}).pipe(filter((result) => result !== undefined))
|
|
70
90
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}, {}),
|
|
99
|
-
}))
|
|
100
|
-
},
|
|
101
|
-
})
|
|
102
|
-
}
|
|
103
|
-
},
|
|
104
|
-
)
|
|
91
|
+
const subscription = source$.subscribe(observer)
|
|
92
|
+
|
|
93
|
+
return () => {
|
|
94
|
+
if (!controller.signal.aborted) {
|
|
95
|
+
controller.abort()
|
|
96
|
+
}
|
|
97
|
+
subscription.unsubscribe()
|
|
98
|
+
}
|
|
99
|
+
}).pipe(map((data) => ({data, ids})))
|
|
100
|
+
}),
|
|
101
|
+
map(({ids, data}) => ({
|
|
102
|
+
values: processProjectionQuery({
|
|
103
|
+
projectId: instance.config.projectId!,
|
|
104
|
+
dataset: instance.config.dataset!,
|
|
105
|
+
ids,
|
|
106
|
+
results: data,
|
|
107
|
+
}),
|
|
108
|
+
})),
|
|
109
|
+
)
|
|
110
|
+
.subscribe({
|
|
111
|
+
next: ({values}) => {
|
|
112
|
+
state.set('updateResult', (prev) => ({
|
|
113
|
+
values: {...prev.values, ...values},
|
|
114
|
+
}))
|
|
115
|
+
},
|
|
116
|
+
})
|
|
117
|
+
}
|
package/src/projection/util.ts
CHANGED
|
@@ -1,17 +1,26 @@
|
|
|
1
1
|
import {type SanityClient} from '@sanity/client'
|
|
2
2
|
import {of} from 'rxjs'
|
|
3
|
-
import {describe, it} from 'vitest'
|
|
3
|
+
import {afterEach, beforeEach, 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, type SanityInstance} from '../store/createSanityInstance'
|
|
7
|
+
import {type StateSource} from '../store/createStateSourceAction'
|
|
8
8
|
import {resolveProjects} from './projects'
|
|
9
9
|
|
|
10
10
|
vi.mock('../client/clientStore')
|
|
11
11
|
|
|
12
12
|
describe('projects', () => {
|
|
13
|
+
let instance: SanityInstance
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
instance = createSanityInstance({projectId: 'p', dataset: 'd'})
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
afterEach(() => {
|
|
20
|
+
instance.dispose()
|
|
21
|
+
})
|
|
22
|
+
|
|
13
23
|
it('calls the `client.observable.projects.list` method on the client and returns the result', async () => {
|
|
14
|
-
const instance = createSanityInstance({projectId: 'p', dataset: 'd'})
|
|
15
24
|
const projects = [{id: 'a'}, {id: 'b'}]
|
|
16
25
|
const list = vi.fn().mockReturnValue(of(projects))
|
|
17
26
|
|
package/src/projects/projects.ts
CHANGED
|
@@ -3,11 +3,16 @@ import {switchMap} from 'rxjs'
|
|
|
3
3
|
import {getClientState} from '../client/clientStore'
|
|
4
4
|
import {createFetcherStore} from '../utils/createFetcherStore'
|
|
5
5
|
|
|
6
|
+
const API_VERSION = 'v2025-02-19'
|
|
7
|
+
|
|
6
8
|
const projects = createFetcherStore({
|
|
7
9
|
name: 'Projects',
|
|
8
10
|
getKey: () => 'projects',
|
|
9
11
|
fetcher: (instance) => () =>
|
|
10
|
-
getClientState(instance, {
|
|
12
|
+
getClientState(instance, {
|
|
13
|
+
apiVersion: API_VERSION,
|
|
14
|
+
scope: 'global',
|
|
15
|
+
}).observable.pipe(
|
|
11
16
|
switchMap((client) => client.observable.projects.list({includeMembers: false})),
|
|
12
17
|
),
|
|
13
18
|
})
|