@sanity/sdk 2.7.0 → 3.0.0-rc.0
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 +228 -239
- package/dist/index.js +287 -454
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/src/_exports/index.ts +16 -17
- package/src/agent/agentActions.test.ts +60 -16
- package/src/agent/agentActions.ts +29 -20
- package/src/auth/authMode.test.ts +0 -25
- package/src/auth/authMode.ts +3 -6
- package/src/auth/authStore.test.ts +129 -66
- package/src/auth/authStore.ts +9 -11
- package/src/auth/dashboardAuth.ts +2 -2
- package/src/auth/getOrganizationVerificationState.test.ts +10 -11
- package/src/auth/handleAuthCallback.test.ts +0 -12
- package/src/auth/handleAuthCallback.ts +9 -3
- package/src/auth/logout.test.ts +0 -6
- package/src/auth/refreshStampedToken.test.ts +121 -17
- package/src/auth/standaloneAuth.ts +9 -3
- package/src/auth/studioAuth.ts +35 -8
- package/src/auth/subscribeToStateAndFetchCurrentUser.test.ts +9 -3
- package/src/auth/subscribeToStateAndFetchCurrentUser.ts +1 -1
- package/src/auth/subscribeToStorageEventsAndSetToken.test.ts +0 -2
- package/src/auth/subscribeToStorageEventsAndSetToken.ts +2 -2
- package/src/auth/utils.ts +33 -0
- package/src/client/clientStore.test.ts +14 -61
- package/src/client/clientStore.ts +52 -28
- package/src/comlink/controller/actions/destroyController.test.ts +1 -4
- package/src/comlink/controller/actions/getOrCreateChannel.test.ts +1 -4
- package/src/comlink/controller/actions/getOrCreateController.test.ts +1 -4
- package/src/comlink/controller/actions/releaseChannel.test.ts +1 -1
- package/src/comlink/controller/comlinkControllerStore.test.ts +1 -4
- package/src/comlink/node/actions/getOrCreateNode.test.ts +1 -4
- package/src/comlink/node/actions/releaseNode.test.ts +1 -4
- package/src/comlink/node/comlinkNodeStore.test.ts +2 -2
- package/src/comlink/node/getNodeState.test.ts +1 -1
- package/src/config/__tests__/handles.test.ts +12 -18
- package/src/config/handles.ts +7 -25
- package/src/config/sanityConfig.ts +99 -52
- package/src/datasets/datasets.test.ts +2 -2
- package/src/datasets/datasets.ts +4 -10
- package/src/document/actions.test.ts +33 -4
- package/src/document/actions.ts +3 -10
- package/src/document/applyDocumentActions.test.ts +17 -18
- package/src/document/applyDocumentActions.ts +9 -12
- package/src/document/documentStore.test.ts +303 -133
- package/src/document/documentStore.ts +70 -61
- package/src/document/permissions.test.ts +44 -8
- package/src/document/processActions.test.ts +77 -7
- package/src/document/reducers.test.ts +35 -3
- package/src/document/sharedListener.test.ts +13 -13
- package/src/document/sharedListener.ts +8 -3
- package/src/favorites/favorites.test.ts +10 -2
- package/src/presence/presenceStore.test.ts +34 -9
- package/src/presence/presenceStore.ts +29 -13
- package/src/preview/previewProjectionUtils.test.ts +192 -0
- package/src/preview/previewProjectionUtils.ts +88 -0
- package/src/preview/{previewStore.ts → types.ts} +6 -25
- package/src/project/project.test.ts +1 -1
- package/src/project/project.ts +14 -20
- package/src/projection/getProjectionState.test.ts +4 -2
- package/src/projection/getProjectionState.ts +2 -21
- package/src/projection/projectionQuery.ts +2 -3
- package/src/projection/projectionStore.test.ts +3 -3
- package/src/projection/resolveProjection.test.ts +2 -1
- package/src/projection/resolveProjection.ts +2 -18
- package/src/projection/subscribeToStateAndFetchBatches.test.ts +2 -2
- package/src/projection/subscribeToStateAndFetchBatches.ts +23 -36
- package/src/projection/types.ts +1 -9
- package/src/projects/projects.test.ts +1 -1
- package/src/query/queryStore.test.ts +86 -28
- package/src/query/queryStore.ts +23 -38
- package/src/releases/getPerspectiveState.test.ts +14 -13
- package/src/releases/getPerspectiveState.ts +6 -6
- package/src/releases/releasesStore.test.ts +21 -6
- package/src/releases/releasesStore.ts +18 -8
- package/src/store/createActionBinder.test.ts +114 -111
- package/src/store/createActionBinder.ts +52 -101
- package/src/store/createSanityInstance.test.ts +13 -83
- package/src/store/createSanityInstance.ts +2 -78
- package/src/store/createStateSourceAction.test.ts +2 -2
- package/src/store/createStateSourceAction.ts +5 -5
- package/src/store/createStoreInstance.test.ts +2 -4
- package/src/users/reducers.test.ts +1 -6
- package/src/users/reducers.ts +2 -2
- package/src/users/types.ts +4 -4
- package/src/users/usersStore.test.ts +12 -15
- package/src/utils/createFetcherStore.test.ts +1 -1
- package/src/utils/logger.test.ts +0 -12
- package/src/utils/logger.ts +3 -8
- package/src/preview/getPreviewState.test.ts +0 -120
- package/src/preview/getPreviewState.ts +0 -91
- package/src/preview/previewQuery.test.ts +0 -236
- package/src/preview/previewQuery.ts +0 -153
- package/src/preview/previewStore.test.ts +0 -36
- package/src/preview/resolvePreview.test.ts +0 -47
- package/src/preview/resolvePreview.ts +0 -20
- package/src/preview/subscribeToStateAndFetchBatches.test.ts +0 -221
- package/src/preview/subscribeToStateAndFetchBatches.ts +0 -112
- package/src/preview/util.ts +0 -13
|
@@ -24,13 +24,13 @@ describe('projectionStore', () => {
|
|
|
24
24
|
new Observable(subscriber).subscribe(),
|
|
25
25
|
)
|
|
26
26
|
|
|
27
|
-
const instance = createSanityInstance(
|
|
27
|
+
const instance = createSanityInstance()
|
|
28
28
|
|
|
29
29
|
const {state, dispose} = createStoreInstance(
|
|
30
30
|
instance,
|
|
31
31
|
{
|
|
32
32
|
name: 'p.d',
|
|
33
|
-
|
|
33
|
+
resource: {projectId: 'p', dataset: 'd'},
|
|
34
34
|
perspective: 'drafts',
|
|
35
35
|
},
|
|
36
36
|
projectionStore,
|
|
@@ -42,7 +42,7 @@ describe('projectionStore', () => {
|
|
|
42
42
|
state,
|
|
43
43
|
key: {
|
|
44
44
|
name: 'p.d',
|
|
45
|
-
|
|
45
|
+
resource: {projectId: 'p', dataset: 'd'},
|
|
46
46
|
perspective: 'drafts',
|
|
47
47
|
},
|
|
48
48
|
})
|
|
@@ -23,7 +23,7 @@ describe('resolveProjection', () => {
|
|
|
23
23
|
} as ProjectionValuePending<Record<string, unknown>>),
|
|
24
24
|
} as StateSource<ProjectionValuePending<Record<string, unknown>>>)
|
|
25
25
|
|
|
26
|
-
instance = createSanityInstance(
|
|
26
|
+
instance = createSanityInstance()
|
|
27
27
|
})
|
|
28
28
|
|
|
29
29
|
afterEach(() => {
|
|
@@ -34,6 +34,7 @@ describe('resolveProjection', () => {
|
|
|
34
34
|
const docHandle = createDocumentHandle({
|
|
35
35
|
documentId: 'doc123',
|
|
36
36
|
documentType: 'movie',
|
|
37
|
+
resource: {projectId: 'p', dataset: 'd'},
|
|
37
38
|
})
|
|
38
39
|
const projection = '{title}'
|
|
39
40
|
|
|
@@ -1,27 +1,11 @@
|
|
|
1
|
-
import {type SanityProjectionResult} from 'groq'
|
|
2
1
|
import {filter, firstValueFrom} from 'rxjs'
|
|
3
2
|
|
|
4
|
-
import {
|
|
3
|
+
import {bindActionByResourceAndPerspective} from '../store/createActionBinder'
|
|
5
4
|
import {type SanityInstance} from '../store/createSanityInstance'
|
|
6
5
|
import {getProjectionState, type ProjectionOptions} from './getProjectionState'
|
|
7
6
|
import {projectionStore} from './projectionStore'
|
|
8
7
|
import {type ProjectionValuePending} from './types'
|
|
9
8
|
|
|
10
|
-
/** @beta */
|
|
11
|
-
export function resolveProjection<
|
|
12
|
-
TProjection extends string = string,
|
|
13
|
-
TDocumentType extends string = string,
|
|
14
|
-
TDataset extends string = string,
|
|
15
|
-
TProjectId extends string = string,
|
|
16
|
-
>(
|
|
17
|
-
instance: SanityInstance,
|
|
18
|
-
options: ProjectionOptions<TProjection, TDocumentType, TDataset, TProjectId>,
|
|
19
|
-
): Promise<
|
|
20
|
-
ProjectionValuePending<
|
|
21
|
-
SanityProjectionResult<TProjection, TDocumentType, `${TProjectId}.${TDataset}`>
|
|
22
|
-
>
|
|
23
|
-
>
|
|
24
|
-
|
|
25
9
|
/** @beta */
|
|
26
10
|
export function resolveProjection<TData extends object>(
|
|
27
11
|
instance: SanityInstance,
|
|
@@ -38,7 +22,7 @@ export function resolveProjection(
|
|
|
38
22
|
/**
|
|
39
23
|
* @beta
|
|
40
24
|
*/
|
|
41
|
-
const _resolveProjection =
|
|
25
|
+
const _resolveProjection = bindActionByResourceAndPerspective(
|
|
42
26
|
projectionStore,
|
|
43
27
|
(
|
|
44
28
|
{instance}: {instance: SanityInstance},
|
|
@@ -17,13 +17,13 @@ describe('subscribeToStateAndFetchBatches', () => {
|
|
|
17
17
|
let state: StoreState<ProjectionStoreState>
|
|
18
18
|
const key = {
|
|
19
19
|
name: 'test.test:drafts',
|
|
20
|
-
|
|
20
|
+
resource: {projectId: 'test', dataset: 'test'},
|
|
21
21
|
perspective: 'drafts' as const,
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
beforeEach(() => {
|
|
25
25
|
vi.clearAllMocks()
|
|
26
|
-
instance = createSanityInstance(
|
|
26
|
+
instance = createSanityInstance()
|
|
27
27
|
state = createStoreState<ProjectionStoreState>({
|
|
28
28
|
documentProjections: {},
|
|
29
29
|
documentStatuses: {},
|
|
@@ -16,7 +16,6 @@ import {
|
|
|
16
16
|
tap,
|
|
17
17
|
} from 'rxjs'
|
|
18
18
|
|
|
19
|
-
import {isDatasetSource} from '../config/sanityConfig'
|
|
20
19
|
import {getQueryState, resolveQuery} from '../query/queryStore'
|
|
21
20
|
import {type BoundPerspectiveKey} from '../store/createActionBinder'
|
|
22
21
|
import {type StoreContext} from '../store/defineStore'
|
|
@@ -42,7 +41,7 @@ interface StatusQueryResult {
|
|
|
42
41
|
export const subscribeToStateAndFetchBatches = ({
|
|
43
42
|
state,
|
|
44
43
|
instance,
|
|
45
|
-
key: {
|
|
44
|
+
key: {resource, perspective},
|
|
46
45
|
}: StoreContext<ProjectionStoreState, BoundPerspectiveKey>): Subscription => {
|
|
47
46
|
const documentProjections$ = state.observable.pipe(
|
|
48
47
|
map((s) => s.documentProjections),
|
|
@@ -106,29 +105,23 @@ export const subscribeToStateAndFetchBatches = ({
|
|
|
106
105
|
|
|
107
106
|
const projectionQuery$ = new Observable<ProjectionQueryResult[]>((observer) => {
|
|
108
107
|
const {getCurrent, observable} = getQueryState<ProjectionQueryResult[]>(instance, {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
},
|
|
115
|
-
// temporary guard here until we're ready for everything to be queried via global API
|
|
116
|
-
...(source && !isDatasetSource(source) ? {source} : {}),
|
|
108
|
+
query,
|
|
109
|
+
params,
|
|
110
|
+
tag: PROJECTION_TAG,
|
|
111
|
+
perspective,
|
|
112
|
+
resource,
|
|
117
113
|
})
|
|
118
114
|
|
|
119
115
|
const querySource$ = defer(() => {
|
|
120
116
|
if (getCurrent() === undefined) {
|
|
121
117
|
return from(
|
|
122
118
|
resolveQuery<ProjectionQueryResult[]>(instance, {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
},
|
|
130
|
-
// temporary guard here until we're ready for everything to be queried via global API in v3
|
|
131
|
-
...(source && !isDatasetSource(source) ? {source} : {}),
|
|
119
|
+
query,
|
|
120
|
+
params,
|
|
121
|
+
tag: PROJECTION_TAG,
|
|
122
|
+
signal: controller.signal,
|
|
123
|
+
perspective,
|
|
124
|
+
resource,
|
|
132
125
|
}),
|
|
133
126
|
).pipe(switchMap(() => observable))
|
|
134
127
|
}
|
|
@@ -147,29 +140,23 @@ export const subscribeToStateAndFetchBatches = ({
|
|
|
147
140
|
|
|
148
141
|
const statusQuery$ = new Observable<StatusQueryResult[]>((observer) => {
|
|
149
142
|
const {getCurrent, observable} = getQueryState<StatusQueryResult[]>(instance, {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
},
|
|
156
|
-
// temporary guard here until we're ready for everything to be queried via global API
|
|
157
|
-
...(source && !isDatasetSource(source) ? {source} : {}),
|
|
143
|
+
query: statusQuery,
|
|
144
|
+
params: statusParams,
|
|
145
|
+
tag: PROJECTION_TAG,
|
|
146
|
+
perspective: 'raw',
|
|
147
|
+
resource,
|
|
158
148
|
})
|
|
159
149
|
|
|
160
150
|
const statusQuerySource$ = defer(() => {
|
|
161
151
|
if (getCurrent() === undefined) {
|
|
162
152
|
return from(
|
|
163
153
|
resolveQuery<StatusQueryResult[]>(instance, {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
},
|
|
171
|
-
// temporary guard here until we're ready for everything to be queried via global API
|
|
172
|
-
...(source && !isDatasetSource(source) ? {source} : {}),
|
|
154
|
+
query: statusQuery,
|
|
155
|
+
params: statusParams,
|
|
156
|
+
tag: PROJECTION_TAG,
|
|
157
|
+
signal: controller.signal,
|
|
158
|
+
perspective: 'raw',
|
|
159
|
+
resource,
|
|
173
160
|
}),
|
|
174
161
|
).pipe(switchMap(() => observable))
|
|
175
162
|
}
|
package/src/projection/types.ts
CHANGED
|
@@ -11,14 +11,6 @@ export interface DocumentProjectionValues<TValue extends object = object> {
|
|
|
11
11
|
[projectionHash: string]: ProjectionValuePending<TValue>
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
/**
|
|
15
|
-
* @public
|
|
16
|
-
* @deprecated
|
|
17
|
-
* Template literals are a bit too limited, so this type is deprecated.
|
|
18
|
-
* Use `string` instead. Projection strings are validated at runtime.
|
|
19
|
-
*/
|
|
20
|
-
export type ValidProjection = string
|
|
21
|
-
|
|
22
14
|
export interface DocumentProjections {
|
|
23
15
|
[projectionHash: string]: string
|
|
24
16
|
}
|
|
@@ -29,7 +21,7 @@ interface DocumentProjectionSubscriptions {
|
|
|
29
21
|
}
|
|
30
22
|
}
|
|
31
23
|
|
|
32
|
-
interface DocumentStatus {
|
|
24
|
+
export interface DocumentStatus {
|
|
33
25
|
lastEditedDraftAt?: string
|
|
34
26
|
lastEditedPublishedAt?: string
|
|
35
27
|
lastEditedVersionAt?: string
|
|
@@ -3,7 +3,7 @@ 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 {
|
|
6
|
+
import {isCanvasResource} from '../config/sanityConfig'
|
|
7
7
|
import {createSanityInstance, type SanityInstance} from '../store/createSanityInstance'
|
|
8
8
|
import {type StateSource} from '../store/createStateSourceAction'
|
|
9
9
|
import {getQueryState, resolveQuery} from './queryStore'
|
|
@@ -48,7 +48,7 @@ describe('queryStore', () => {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
beforeEach(() => {
|
|
51
|
-
instance = createSanityInstance(
|
|
51
|
+
instance = createSanityInstance()
|
|
52
52
|
|
|
53
53
|
fetch = vi
|
|
54
54
|
.fn()
|
|
@@ -80,7 +80,7 @@ describe('queryStore', () => {
|
|
|
80
80
|
|
|
81
81
|
it('initializes query state and cleans up after unsubscribe', async () => {
|
|
82
82
|
const query = '*[_type == "movie"]'
|
|
83
|
-
const state = getQueryState(instance, {query})
|
|
83
|
+
const state = getQueryState(instance, {query, resource: {projectId: 'p', dataset: 'd'}})
|
|
84
84
|
|
|
85
85
|
// Initially undefined before subscription
|
|
86
86
|
expect(state.getCurrent()).toBeUndefined()
|
|
@@ -109,7 +109,7 @@ describe('queryStore', () => {
|
|
|
109
109
|
|
|
110
110
|
it('maintains state when multiple subscribers exist', async () => {
|
|
111
111
|
const query = '*[_type == "movie"]'
|
|
112
|
-
const state = getQueryState(instance, {query})
|
|
112
|
+
const state = getQueryState(instance, {query, resource: {projectId: 'p', dataset: 'd'}})
|
|
113
113
|
|
|
114
114
|
// Add two subscribers
|
|
115
115
|
const unsubscribe1 = state.subscribe()
|
|
@@ -146,13 +146,13 @@ describe('queryStore', () => {
|
|
|
146
146
|
it('resolveQuery works without affecting subscriber cleanup', async () => {
|
|
147
147
|
const query = '*[_type == "movie"]'
|
|
148
148
|
|
|
149
|
-
const state = getQueryState(instance, {query})
|
|
149
|
+
const state = getQueryState(instance, {query, resource: {projectId: 'p', dataset: 'd'}})
|
|
150
150
|
|
|
151
151
|
// Check that getQueryState starts undefined
|
|
152
152
|
expect(state.getCurrent()).toBeUndefined()
|
|
153
153
|
|
|
154
154
|
// Use resolveQuery which should not add a subscriber
|
|
155
|
-
const result = await resolveQuery(instance, {query})
|
|
155
|
+
const result = await resolveQuery(instance, {query, resource: {projectId: 'p', dataset: 'd'}})
|
|
156
156
|
expect(result).toEqual([
|
|
157
157
|
{_id: 'movie1', _type: 'movie', title: 'Movie 1'},
|
|
158
158
|
{_id: 'movie2', _type: 'movie', title: 'Movie 2'},
|
|
@@ -179,7 +179,11 @@ describe('queryStore', () => {
|
|
|
179
179
|
const abortController = new AbortController()
|
|
180
180
|
|
|
181
181
|
// Create a promise that will reject when aborted
|
|
182
|
-
const queryPromise = resolveQuery(instance, {
|
|
182
|
+
const queryPromise = resolveQuery(instance, {
|
|
183
|
+
query,
|
|
184
|
+
resource: {projectId: 'p', dataset: 'd'},
|
|
185
|
+
signal: abortController.signal,
|
|
186
|
+
})
|
|
183
187
|
|
|
184
188
|
// Abort the request
|
|
185
189
|
abortController.abort()
|
|
@@ -188,7 +192,9 @@ describe('queryStore', () => {
|
|
|
188
192
|
await expect(queryPromise).rejects.toThrow('The operation was aborted.')
|
|
189
193
|
|
|
190
194
|
// Verify state is cleared after abort
|
|
191
|
-
expect(
|
|
195
|
+
expect(
|
|
196
|
+
getQueryState(instance, {query, resource: {projectId: 'p', dataset: 'd'}}).getCurrent(),
|
|
197
|
+
).toBeUndefined()
|
|
192
198
|
})
|
|
193
199
|
|
|
194
200
|
it('refetches query when receiving live event with matching sync tag', async () => {
|
|
@@ -207,7 +213,10 @@ describe('queryStore', () => {
|
|
|
207
213
|
)
|
|
208
214
|
|
|
209
215
|
const query = '*[_type == "movie"]'
|
|
210
|
-
const state = getQueryState<{_id: string; _type: string; title: string}[]>(instance, {
|
|
216
|
+
const state = getQueryState<{_id: string; _type: string; title: string}[]>(instance, {
|
|
217
|
+
query,
|
|
218
|
+
resource: {projectId: 'p', dataset: 'd'},
|
|
219
|
+
})
|
|
211
220
|
|
|
212
221
|
const unsubscribe = state.subscribe()
|
|
213
222
|
await firstValueFrom(state.observable.pipe(filter((i) => i !== undefined)))
|
|
@@ -238,7 +247,7 @@ describe('queryStore', () => {
|
|
|
238
247
|
)
|
|
239
248
|
|
|
240
249
|
const query = '*[_type == "movie"]'
|
|
241
|
-
const state = getQueryState(instance, {query})
|
|
250
|
+
const state = getQueryState(instance, {query, resource: {projectId: 'p', dataset: 'd'}})
|
|
242
251
|
|
|
243
252
|
const unsubscribe = state.subscribe()
|
|
244
253
|
await firstValueFrom(state.observable.pipe(filter((i) => i !== undefined)))
|
|
@@ -271,7 +280,7 @@ describe('queryStore', () => {
|
|
|
271
280
|
)
|
|
272
281
|
|
|
273
282
|
const query = '*[_type == "movie"]'
|
|
274
|
-
const state = getQueryState(instance, {query})
|
|
283
|
+
const state = getQueryState(instance, {query, resource: {projectId: 'p', dataset: 'd'}})
|
|
275
284
|
|
|
276
285
|
const unsubscribe = state.subscribe()
|
|
277
286
|
await firstValueFrom(state.observable.pipe(filter((i) => i !== undefined)))
|
|
@@ -308,7 +317,7 @@ describe('queryStore', () => {
|
|
|
308
317
|
)
|
|
309
318
|
|
|
310
319
|
const query = '*[_type == "movie"]'
|
|
311
|
-
const state = getQueryState(instance, {query})
|
|
320
|
+
const state = getQueryState(instance, {query, resource: {projectId: 'p', dataset: 'd'}})
|
|
312
321
|
const unsubscribe = state.subscribe()
|
|
313
322
|
|
|
314
323
|
// Verify error is thrown when accessing state
|
|
@@ -319,7 +328,7 @@ describe('queryStore', () => {
|
|
|
319
328
|
|
|
320
329
|
it('delays query state removal after unsubscribe', async () => {
|
|
321
330
|
const query = '*[_type == "movie"]'
|
|
322
|
-
const state = getQueryState(instance, {query})
|
|
331
|
+
const state = getQueryState(instance, {query, resource: {projectId: 'p', dataset: 'd'}})
|
|
323
332
|
const unsubscribe = state.subscribe()
|
|
324
333
|
|
|
325
334
|
await firstValueFrom(state.observable.pipe(filter((i) => i !== undefined)))
|
|
@@ -335,7 +344,7 @@ describe('queryStore', () => {
|
|
|
335
344
|
|
|
336
345
|
it('preserves query state if a new subscriber subscribes before cleanup delay', async () => {
|
|
337
346
|
const query = '*[_type == "movie"]'
|
|
338
|
-
const state = getQueryState(instance, {query})
|
|
347
|
+
const state = getQueryState(instance, {query, resource: {projectId: 'p', dataset: 'd'}})
|
|
339
348
|
const unsubscribe1 = state.subscribe()
|
|
340
349
|
|
|
341
350
|
await firstValueFrom(state.observable.pipe(filter((i) => i !== undefined)))
|
|
@@ -373,20 +382,20 @@ describe('queryStore', () => {
|
|
|
373
382
|
}) as SanityClient['observable']['fetch'])
|
|
374
383
|
|
|
375
384
|
const draftsInstance = createSanityInstance({
|
|
376
|
-
projectId: 'test',
|
|
377
|
-
dataset: 'test',
|
|
378
385
|
perspective: 'drafts',
|
|
379
386
|
})
|
|
380
387
|
const publishedInstance = createSanityInstance({
|
|
381
|
-
projectId: 'test',
|
|
382
|
-
dataset: 'test',
|
|
383
388
|
perspective: 'published',
|
|
384
389
|
})
|
|
385
390
|
|
|
386
391
|
// Same query/options, different implicit perspectives via instance.config
|
|
387
|
-
const sDrafts = getQueryState<{_id: string}[]>(draftsInstance, {
|
|
392
|
+
const sDrafts = getQueryState<{_id: string}[]>(draftsInstance, {
|
|
393
|
+
query: '*[_type == "movie"]',
|
|
394
|
+
resource: {projectId: 'p', dataset: 'd'},
|
|
395
|
+
})
|
|
388
396
|
const sPublished = getQueryState<{_id: string}[]>(publishedInstance, {
|
|
389
397
|
query: '*[_type == "movie"]',
|
|
398
|
+
resource: {projectId: 'p', dataset: 'd'},
|
|
390
399
|
})
|
|
391
400
|
|
|
392
401
|
const unsubDrafts = sDrafts.subscribe()
|
|
@@ -418,15 +427,17 @@ describe('queryStore', () => {
|
|
|
418
427
|
>
|
|
419
428
|
}) as SanityClient['observable']['fetch'])
|
|
420
429
|
|
|
421
|
-
const base = createSanityInstance(
|
|
430
|
+
const base = createSanityInstance()
|
|
422
431
|
|
|
423
432
|
const sDrafts = getQueryState<{_id: string}[]>(base, {
|
|
424
433
|
query: '*[_type == "movie"]',
|
|
425
434
|
perspective: 'drafts',
|
|
435
|
+
resource: {projectId: 'p', dataset: 'd'},
|
|
426
436
|
})
|
|
427
437
|
const sPublished = getQueryState<{_id: string}[]>(base, {
|
|
428
438
|
query: '*[_type == "movie"]',
|
|
429
439
|
perspective: 'published',
|
|
440
|
+
resource: {projectId: 'p', dataset: 'd'},
|
|
430
441
|
})
|
|
431
442
|
|
|
432
443
|
const unsubDrafts = sDrafts.subscribe()
|
|
@@ -448,11 +459,11 @@ describe('queryStore', () => {
|
|
|
448
459
|
base.dispose()
|
|
449
460
|
})
|
|
450
461
|
|
|
451
|
-
it('uses
|
|
462
|
+
it('uses resource from params when passed in query options (listenForNewSubscribersAndFetch)', async () => {
|
|
452
463
|
const query = '*[_type == "movie"]'
|
|
453
|
-
const
|
|
464
|
+
const mediaLibraryResource = {mediaLibraryId: 'ml123'}
|
|
454
465
|
|
|
455
|
-
const state = getQueryState(instance, {query,
|
|
466
|
+
const state = getQueryState(instance, {query, resource: mediaLibraryResource})
|
|
456
467
|
const unsubscribe = state.subscribe()
|
|
457
468
|
|
|
458
469
|
await firstValueFrom(state.observable.pipe(filter((i) => i !== undefined)))
|
|
@@ -462,7 +473,7 @@ describe('queryStore', () => {
|
|
|
462
473
|
expect(getClientState).toHaveBeenCalledWith(
|
|
463
474
|
instance,
|
|
464
475
|
expect.objectContaining({
|
|
465
|
-
|
|
476
|
+
resource: expect.objectContaining({
|
|
466
477
|
mediaLibraryId: 'ml123',
|
|
467
478
|
}),
|
|
468
479
|
}),
|
|
@@ -471,11 +482,11 @@ describe('queryStore', () => {
|
|
|
471
482
|
unsubscribe()
|
|
472
483
|
})
|
|
473
484
|
|
|
474
|
-
it('uses
|
|
485
|
+
it('uses resource from store context key when not a dataset resource (listenToLiveClientAndSetLastLiveEventIds)', async () => {
|
|
475
486
|
const query = '*[_type == "movie"]'
|
|
476
|
-
const
|
|
487
|
+
const canvasResource = {canvasId: 'canvas456'}
|
|
477
488
|
|
|
478
|
-
const state = getQueryState(instance, {query,
|
|
489
|
+
const state = getQueryState(instance, {query, resource: canvasResource})
|
|
479
490
|
const unsubscribe = state.subscribe()
|
|
480
491
|
|
|
481
492
|
await firstValueFrom(state.observable.pipe(filter((i) => i !== undefined)))
|
|
@@ -486,10 +497,57 @@ describe('queryStore', () => {
|
|
|
486
497
|
const calls = vi.mocked(getClientState).mock.calls
|
|
487
498
|
const liveClientCall = calls.find(
|
|
488
499
|
([_instance, options]) =>
|
|
489
|
-
|
|
500
|
+
isCanvasResource(options.resource!) && options.resource.canvasId === 'canvas456',
|
|
490
501
|
)
|
|
491
502
|
expect(liveClientCall).toBeDefined()
|
|
492
503
|
|
|
493
504
|
unsubscribe()
|
|
494
505
|
})
|
|
506
|
+
|
|
507
|
+
it('uses bound resource when a shared store receives a query with the same explicit resource', async () => {
|
|
508
|
+
// Regression test: when a store for source B is first created by an
|
|
509
|
+
// instance (passing source B explicitly), subsequent queries added to
|
|
510
|
+
// that store by a different instance (also passing source B explicitly)
|
|
511
|
+
// must still use resource B for client creation.
|
|
512
|
+
const projectBSource = {projectId: 'project-b', dataset: 'production'}
|
|
513
|
+
|
|
514
|
+
const rootInstance = createSanityInstance()
|
|
515
|
+
|
|
516
|
+
// 1. Root instance queries with an explicit source for project B.
|
|
517
|
+
// This creates the QueryStore:project-b.production store, whose
|
|
518
|
+
// initialize() captures rootInstance in its closure.
|
|
519
|
+
const stateWithResource = getQueryState(rootInstance, {
|
|
520
|
+
query: '*[_type == "author"]',
|
|
521
|
+
resource: projectBSource,
|
|
522
|
+
})
|
|
523
|
+
const unsub1 = stateWithResource.subscribe()
|
|
524
|
+
await firstValueFrom(stateWithResource.observable.pipe(filter((i) => i !== undefined)))
|
|
525
|
+
|
|
526
|
+
vi.mocked(getClientState).mockClear()
|
|
527
|
+
|
|
528
|
+
// 2. A second instance queries the SAME store (same composite key)
|
|
529
|
+
// with the same explicit resource.
|
|
530
|
+
const secondInstance = createSanityInstance()
|
|
531
|
+
const stateWithSameResource = getQueryState(secondInstance, {
|
|
532
|
+
query: '*[_type == "movie"]',
|
|
533
|
+
resource: projectBSource,
|
|
534
|
+
})
|
|
535
|
+
const unsub2 = stateWithSameResource.subscribe()
|
|
536
|
+
await firstValueFrom(stateWithSameResource.observable.pipe(filter((i) => i !== undefined)))
|
|
537
|
+
|
|
538
|
+
// The listener should create a client using the bound source (project B).
|
|
539
|
+
const fetchCalls = vi.mocked(getClientState).mock.calls
|
|
540
|
+
const correctCall = fetchCalls.find(
|
|
541
|
+
([, options]) =>
|
|
542
|
+
options.resource &&
|
|
543
|
+
'projectId' in options.resource &&
|
|
544
|
+
options.resource.projectId === 'project-b',
|
|
545
|
+
)
|
|
546
|
+
expect(correctCall).toBeDefined()
|
|
547
|
+
|
|
548
|
+
unsub1()
|
|
549
|
+
unsub2()
|
|
550
|
+
secondInstance.dispose()
|
|
551
|
+
rootInstance.dispose()
|
|
552
|
+
})
|
|
495
553
|
})
|
package/src/query/queryStore.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import {CorsOriginError, type ResponseQueryOptions} from '@sanity/client'
|
|
2
|
-
import {type SanityQueryResult} from 'groq'
|
|
3
2
|
import {
|
|
4
3
|
catchError,
|
|
5
4
|
combineLatest,
|
|
@@ -24,7 +23,7 @@ import {
|
|
|
24
23
|
} from 'rxjs'
|
|
25
24
|
|
|
26
25
|
import {getClientState} from '../client/clientStore'
|
|
27
|
-
import {type
|
|
26
|
+
import {type ResourceHandle} from '../config/sanityConfig'
|
|
28
27
|
/*
|
|
29
28
|
* Although this is an import dependency cycle, it is not a logical cycle:
|
|
30
29
|
* 1. queryStore uses getPerspectiveState when resolving release perspectives
|
|
@@ -35,7 +34,7 @@ import {type DatasetHandle, isDatasetSource} from '../config/sanityConfig'
|
|
|
35
34
|
// eslint-disable-next-line import/no-cycle
|
|
36
35
|
import {getPerspectiveState} from '../releases/getPerspectiveState'
|
|
37
36
|
import {isReleasePerspective} from '../releases/utils/isReleasePerspective'
|
|
38
|
-
import {
|
|
37
|
+
import {bindActionByResource, type BoundResourceKey} from '../store/createActionBinder'
|
|
39
38
|
import {type SanityInstance} from '../store/createSanityInstance'
|
|
40
39
|
import {
|
|
41
40
|
createStateSourceAction,
|
|
@@ -71,7 +70,7 @@ export interface QueryOptions<
|
|
|
71
70
|
>
|
|
72
71
|
extends
|
|
73
72
|
Pick<ResponseQueryOptions, 'useCdn' | 'cache' | 'next' | 'cacheMode' | 'tag'>,
|
|
74
|
-
|
|
73
|
+
ResourceHandle<TDataset, TProjectId> {
|
|
75
74
|
query: TQuery
|
|
76
75
|
params?: Record<string, unknown>
|
|
77
76
|
}
|
|
@@ -116,7 +115,7 @@ function normalizeOptionsWithPerspective(
|
|
|
116
115
|
}
|
|
117
116
|
}
|
|
118
117
|
|
|
119
|
-
const queryStore = defineStore<QueryStoreState,
|
|
118
|
+
const queryStore = defineStore<QueryStoreState, BoundResourceKey>({
|
|
120
119
|
name: 'QueryStore',
|
|
121
120
|
getInitialState: () => ({queries: {}}),
|
|
122
121
|
initialize(context) {
|
|
@@ -137,7 +136,11 @@ const errorHandler = (state: StoreState<{error?: unknown}>) => {
|
|
|
137
136
|
return (error: unknown): void => state.set('setError', {error})
|
|
138
137
|
}
|
|
139
138
|
|
|
140
|
-
const listenForNewSubscribersAndFetch = ({
|
|
139
|
+
const listenForNewSubscribersAndFetch = ({
|
|
140
|
+
state,
|
|
141
|
+
instance,
|
|
142
|
+
key: {resource: boundResource},
|
|
143
|
+
}: StoreContext<QueryStoreState, BoundResourceKey>) => {
|
|
141
144
|
return state.observable
|
|
142
145
|
.pipe(
|
|
143
146
|
map((s) => new Set(Object.keys(s.queries))),
|
|
@@ -169,10 +172,8 @@ const listenForNewSubscribersAndFetch = ({state, instance}: StoreContext<QuerySt
|
|
|
169
172
|
const {
|
|
170
173
|
query,
|
|
171
174
|
params,
|
|
172
|
-
projectId,
|
|
173
|
-
dataset,
|
|
174
175
|
tag,
|
|
175
|
-
|
|
176
|
+
resource,
|
|
176
177
|
perspective: perspectiveFromOptions,
|
|
177
178
|
...restOptions
|
|
178
179
|
} = parseQueryKey(group$.key)
|
|
@@ -182,14 +183,19 @@ const listenForNewSubscribersAndFetch = ({state, instance}: StoreContext<QuerySt
|
|
|
182
183
|
const perspective$ = isReleasePerspective(perspectiveFromOptions)
|
|
183
184
|
? getPerspectiveState(instance, {
|
|
184
185
|
perspective: perspectiveFromOptions,
|
|
186
|
+
resource: resource ?? boundResource,
|
|
185
187
|
}).observable.pipe(filter(Boolean))
|
|
186
188
|
: of(perspectiveFromOptions ?? QUERY_STORE_DEFAULT_PERSPECTIVE)
|
|
187
189
|
|
|
190
|
+
// Use the store's bound resource as fallback when the query key
|
|
191
|
+
// doesn't include an explicit resource. The store is scoped to a
|
|
192
|
+
// specific resource via bindActionByResource, but the captured
|
|
193
|
+
// `instance` may have a different default resource (e.g. when the
|
|
194
|
+
// store was first created by a caller that passed an explicit
|
|
195
|
+
// resource while using the root app instance).
|
|
188
196
|
const client$ = getClientState(instance, {
|
|
189
197
|
apiVersion: QUERY_STORE_API_VERSION,
|
|
190
|
-
|
|
191
|
-
dataset,
|
|
192
|
-
source,
|
|
198
|
+
resource: resource ?? boundResource,
|
|
193
199
|
}).observable
|
|
194
200
|
|
|
195
201
|
return combineLatest({
|
|
@@ -225,12 +231,11 @@ const listenForNewSubscribersAndFetch = ({state, instance}: StoreContext<QuerySt
|
|
|
225
231
|
const listenToLiveClientAndSetLastLiveEventIds = ({
|
|
226
232
|
state,
|
|
227
233
|
instance,
|
|
228
|
-
key: {
|
|
229
|
-
}: StoreContext<QueryStoreState,
|
|
234
|
+
key: {resource},
|
|
235
|
+
}: StoreContext<QueryStoreState, BoundResourceKey>) => {
|
|
230
236
|
const liveMessages$ = getClientState(instance, {
|
|
231
237
|
apiVersion: QUERY_STORE_API_VERSION,
|
|
232
|
-
|
|
233
|
-
...(source && !isDatasetSource(source) ? {source} : {}),
|
|
238
|
+
resource,
|
|
234
239
|
}).observable.pipe(
|
|
235
240
|
switchMap((client) =>
|
|
236
241
|
defer(() =>
|
|
@@ -288,16 +293,6 @@ const listenToLiveClientAndSetLastLiveEventIds = ({
|
|
|
288
293
|
*
|
|
289
294
|
* @beta
|
|
290
295
|
*/
|
|
291
|
-
export function getQueryState<
|
|
292
|
-
TQuery extends string = string,
|
|
293
|
-
TDataset extends string = string,
|
|
294
|
-
TProjectId extends string = string,
|
|
295
|
-
>(
|
|
296
|
-
instance: SanityInstance,
|
|
297
|
-
queryOptions: QueryOptions<TQuery, TDataset, TProjectId>,
|
|
298
|
-
): StateSource<SanityQueryResult<TQuery, `${TProjectId}.${TDataset}`> | undefined>
|
|
299
|
-
|
|
300
|
-
/** @beta */
|
|
301
296
|
export function getQueryState<TData>(
|
|
302
297
|
instance: SanityInstance,
|
|
303
298
|
queryOptions: QueryOptions,
|
|
@@ -315,7 +310,7 @@ export function getQueryState(
|
|
|
315
310
|
): ReturnType<typeof _getQueryState> {
|
|
316
311
|
return _getQueryState(...args)
|
|
317
312
|
}
|
|
318
|
-
const _getQueryState =
|
|
313
|
+
const _getQueryState = bindActionByResource(
|
|
319
314
|
queryStore,
|
|
320
315
|
createStateSourceAction({
|
|
321
316
|
selector: ({state, instance}: SelectorContext<QueryStoreState>, options: QueryOptions) => {
|
|
@@ -356,16 +351,6 @@ const _getQueryState = bindActionBySource(
|
|
|
356
351
|
*
|
|
357
352
|
* @beta
|
|
358
353
|
*/
|
|
359
|
-
export function resolveQuery<
|
|
360
|
-
TQuery extends string = string,
|
|
361
|
-
TDataset extends string = string,
|
|
362
|
-
TProjectId extends string = string,
|
|
363
|
-
>(
|
|
364
|
-
instance: SanityInstance,
|
|
365
|
-
queryOptions: ResolveQueryOptions<TQuery, TDataset, TProjectId>,
|
|
366
|
-
): Promise<SanityQueryResult<TQuery, `${TProjectId}.${TDataset}`>>
|
|
367
|
-
|
|
368
|
-
/** @beta */
|
|
369
354
|
export function resolveQuery<TData>(
|
|
370
355
|
instance: SanityInstance,
|
|
371
356
|
queryOptions: ResolveQueryOptions,
|
|
@@ -374,7 +359,7 @@ export function resolveQuery<TData>(
|
|
|
374
359
|
export function resolveQuery(...args: Parameters<typeof _resolveQuery>): Promise<unknown> {
|
|
375
360
|
return _resolveQuery(...args)
|
|
376
361
|
}
|
|
377
|
-
const _resolveQuery =
|
|
362
|
+
const _resolveQuery = bindActionByResource(
|
|
378
363
|
queryStore,
|
|
379
364
|
({state, instance}, {signal, ...options}: ResolveQueryOptions) => {
|
|
380
365
|
const normalized = normalizeOptionsWithPerspective(instance, options)
|