@sanity/sdk 3.0.0-rc.0 → 3.0.0-rc.2
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/README.md +12 -0
- package/dist/_exports/_internal.d.ts +163 -0
- package/dist/_exports/_internal.d.ts.map +1 -0
- package/dist/_exports/_internal.js +332 -0
- package/dist/_exports/_internal.js.map +1 -0
- package/dist/_exports/agent.d.ts +65 -0
- package/dist/_exports/agent.d.ts.map +1 -0
- package/dist/_exports/agent.js +71 -0
- package/dist/_exports/agent.js.map +1 -0
- package/dist/_exports/comlink.d.ts +118 -0
- package/dist/_exports/comlink.d.ts.map +1 -0
- package/dist/_exports/comlink.js +69 -0
- package/dist/_exports/comlink.js.map +1 -0
- package/dist/authStore-CtE9bLHW.js +754 -0
- package/dist/authStore-CtE9bLHW.js.map +1 -0
- package/dist/authStore-DKFX8fn7.d.ts +3179 -0
- package/dist/authStore-DKFX8fn7.d.ts.map +1 -0
- package/dist/clientStore-BtE2-q2i.js +90 -0
- package/dist/clientStore-BtE2-q2i.js.map +1 -0
- package/dist/createSanityInstance-DUI1bGIg.d.ts +278 -0
- package/dist/createSanityInstance-DUI1bGIg.d.ts.map +1 -0
- package/dist/createStateSourceAction-BOKtqFNm.js +300 -0
- package/dist/createStateSourceAction-BOKtqFNm.js.map +1 -0
- package/dist/getEnv-D5RGkQLu.js +8 -0
- package/dist/getEnv-D5RGkQLu.js.map +1 -0
- package/dist/getNodeState-CR3__B2D.js +67 -0
- package/dist/getNodeState-CR3__B2D.js.map +1 -0
- package/dist/index.d.ts +3 -2332
- package/dist/index.js +4171 -4332
- package/dist/index.js.map +1 -1
- package/dist/logger-DAtJb5RQ.js +173 -0
- package/dist/logger-DAtJb5RQ.js.map +1 -0
- package/dist/object-CxXlSVg7.js +83 -0
- package/dist/object-CxXlSVg7.js.map +1 -0
- package/dist/queryStore-DPf9sN3V.js +1346 -0
- package/dist/queryStore-DPf9sN3V.js.map +1 -0
- package/dist/rolldown-runtime-vyAXikos.js +9 -0
- package/dist/setCleanupTimeout-BiM6q1KF.js +31 -0
- package/dist/setCleanupTimeout-BiM6q1KF.js.map +1 -0
- package/dist/telemetryManager-Dq_qCkeI.js +127 -0
- package/dist/telemetryManager-Dq_qCkeI.js.map +1 -0
- package/dist/version-BChjsi4u.js +9 -0
- package/dist/version-BChjsi4u.js.map +1 -0
- package/package.json +47 -52
- package/src/_exports/_internal.ts +27 -0
- package/src/_exports/agent.ts +19 -0
- package/src/_exports/comlink.ts +16 -0
- package/src/_exports/index.ts +125 -64
- package/src/access/checkPermissions.test.ts +55 -0
- package/src/access/checkPermissions.ts +54 -0
- package/src/agent/agentActions.test.ts +16 -60
- package/src/agent/agentActions.ts +25 -38
- package/src/applications/applications.test-d.ts +88 -0
- package/src/applications/applications.test.ts +130 -0
- package/src/applications/applications.ts +367 -0
- package/src/auth/authLogger.ts +30 -0
- package/src/auth/authStore.test.ts +194 -13
- package/src/auth/authStore.ts +58 -26
- package/src/auth/getOrganizationVerificationState.test.ts +18 -17
- package/src/auth/getOrganizationVerificationState.ts +4 -4
- package/src/auth/handleAuthCallback.test.ts +35 -1
- package/src/auth/handleAuthCallback.ts +25 -6
- package/src/auth/logout.test.ts +75 -2
- package/src/auth/logout.ts +23 -4
- package/src/auth/refreshStampedToken.test.ts +219 -469
- package/src/auth/refreshStampedToken.ts +79 -195
- package/src/auth/studioAuth.ts +1 -1
- package/src/auth/studioModeAuth.ts +2 -1
- package/src/auth/subscribeToStateAndFetchCurrentUser.test.ts +146 -13
- package/src/auth/subscribeToStateAndFetchCurrentUser.ts +37 -6
- package/src/auth/subscribeToStorageEventsAndSetToken.test.ts +2 -0
- package/src/client/clientStore.test.ts +62 -37
- package/src/client/clientStore.ts +45 -75
- package/src/client/liveEvents.test.ts +151 -0
- package/src/client/liveEvents.ts +107 -0
- package/src/comlink/controller/actions/destroyController.test.ts +4 -1
- package/src/comlink/controller/actions/getOrCreateChannel.test.ts +4 -1
- package/src/comlink/controller/actions/getOrCreateChannel.ts +2 -2
- package/src/comlink/controller/actions/getOrCreateController.test.ts +4 -1
- package/src/comlink/controller/actions/releaseChannel.test.ts +1 -1
- package/src/comlink/controller/comlinkControllerStore.test.ts +4 -1
- package/src/comlink/node/actions/getOrCreateNode.test.ts +9 -3
- package/src/comlink/node/actions/getOrCreateNode.ts +2 -2
- package/src/comlink/node/actions/releaseNode.test.ts +7 -4
- package/src/comlink/node/comlinkNodeStore.test.ts +2 -2
- package/src/comlink/node/getNodeState.test.ts +1 -1
- package/src/comlink/node/getNodeState.ts +2 -1
- package/src/comments/addonDatasetStore.test.ts +215 -0
- package/src/comments/addonDatasetStore.ts +249 -0
- package/src/comments/buildCommentThreads.test.ts +83 -0
- package/src/comments/buildCommentThreads.ts +54 -0
- package/src/comments/commentActions.test.ts +540 -0
- package/src/comments/commentActions.ts +555 -0
- package/src/comments/commentFieldPath.test.ts +40 -0
- package/src/comments/commentFieldPath.ts +44 -0
- package/src/comments/commentsConstants.ts +80 -0
- package/src/comments/commentsStore.test.ts +419 -0
- package/src/comments/commentsStore.ts +483 -0
- package/src/comments/normalizeComment.test.ts +116 -0
- package/src/comments/normalizeComment.ts +45 -0
- package/src/comments/observeComments.test.ts +193 -0
- package/src/comments/observeComments.ts +113 -0
- package/src/comments/reducers.test.ts +277 -0
- package/src/comments/reducers.ts +316 -0
- package/src/comments/types.ts +249 -0
- package/src/comments/weakenReferences.test.ts +47 -0
- package/src/comments/weakenReferences.ts +29 -0
- package/src/config/__tests__/handles.test.ts +18 -12
- package/src/config/handles.ts +25 -7
- package/src/config/sanityConfig.ts +40 -82
- package/src/datasets/datasets.test.ts +2 -2
- package/src/datasets/datasets.ts +10 -4
- package/src/document/actions.test.ts +115 -33
- package/src/document/actions.ts +212 -15
- package/src/document/applyDocumentActions.test.ts +35 -13
- package/src/document/applyDocumentActions.ts +30 -9
- package/src/document/documentConstants.ts +15 -0
- package/src/document/documentStore.concurrency.test.ts +1306 -0
- package/src/document/documentStore.test.ts +753 -273
- package/src/document/documentStore.ts +197 -75
- package/src/document/events.test.ts +57 -2
- package/src/document/events.ts +82 -26
- package/src/document/listen.test.ts +56 -0
- package/src/document/listen.ts +74 -21
- package/src/document/listenerEventOperators.test.ts +311 -0
- package/src/document/listenerEventOperators.ts +217 -0
- package/src/document/patchOperations.test.ts +76 -2
- package/src/document/patchOperations.ts +60 -1
- package/src/document/permissions.test.ts +107 -60
- package/src/document/permissions.ts +44 -19
- package/src/document/processActions/create.ts +138 -0
- package/src/document/processActions/delete.ts +100 -0
- package/src/document/processActions/discard.ts +63 -0
- package/src/document/processActions/edit.ts +154 -0
- package/src/document/processActions/processActions.ts +218 -0
- package/src/document/processActions/publish.ts +120 -0
- package/src/document/processActions/releaseArchive.ts +77 -0
- package/src/document/processActions/releaseCreate.ts +59 -0
- package/src/document/processActions/releaseDelete.ts +65 -0
- package/src/document/processActions/releaseEdit.ts +37 -0
- package/src/document/processActions/releasePublish.ts +45 -0
- package/src/document/processActions/releaseSchedule.ts +87 -0
- package/src/document/processActions/releaseUtil.ts +31 -0
- package/src/document/processActions/shared.ts +308 -0
- package/src/document/processActions/unpublish.ts +85 -0
- package/src/document/processActions.test.ts +1129 -121
- package/src/document/reducers.test.ts +772 -64
- package/src/document/reducers.ts +205 -51
- package/src/document/resourceRules.test.ts +178 -0
- package/src/document/resourceRules.ts +117 -0
- package/src/document/sharedListener.test.ts +16 -14
- package/src/document/sharedListener.ts +24 -8
- package/src/document/util.test.ts +46 -0
- package/src/document/util.ts +50 -0
- package/src/favorites/favorites.test.ts +5 -6
- package/src/installations/installations.test-d.ts +73 -0
- package/src/installations/installations.test.ts +62 -0
- package/src/installations/installations.ts +206 -0
- package/src/organization/organization.test-d.ts +36 -0
- package/src/organization/organization.test.ts +138 -0
- package/src/organization/organization.ts +138 -0
- package/src/organizations/organizations.test-d.ts +56 -0
- package/src/organizations/organizations.test.ts +150 -0
- package/src/organizations/organizations.ts +107 -0
- package/src/presence/bifurTransport.test.ts +201 -9
- package/src/presence/bifurTransport.ts +98 -14
- package/src/presence/paths.test.ts +70 -0
- package/src/presence/paths.ts +41 -0
- package/src/presence/presenceStore.test.ts +915 -24
- package/src/presence/presenceStore.ts +576 -104
- package/src/presence/types.ts +175 -3
- package/src/preview/previewProjectionUtils.test.ts +9 -22
- package/src/preview/previewProjectionUtils.ts +7 -3
- package/src/preview/types.ts +0 -15
- package/src/project/project.test-d.ts +37 -0
- package/src/project/project.test.ts +157 -12
- package/src/project/project.ts +138 -25
- package/src/projection/getProjectionState.test.ts +18 -20
- package/src/projection/getProjectionState.ts +23 -3
- package/src/projection/projectionQuery.ts +5 -5
- package/src/projection/projectionStore.test.ts +1 -9
- package/src/projection/resolveProjection.test.ts +1 -2
- package/src/projection/resolveProjection.ts +16 -0
- package/src/projection/subscribeToStateAndFetchBatches.test.ts +1 -1
- package/src/projection/subscribeToStateAndFetchBatches.ts +32 -23
- package/src/projects/projects.test-d.ts +32 -0
- package/src/projects/projects.test.ts +108 -42
- package/src/projects/projects.ts +63 -27
- package/src/query/queryStore.test.ts +259 -123
- package/src/query/queryStore.ts +85 -69
- package/src/query/reducers.test.ts +0 -42
- package/src/query/reducers.ts +2 -18
- package/src/releases/getPerspectiveState.test.ts +17 -23
- package/src/releases/getPerspectiveState.ts +8 -20
- package/src/releases/observeReleases.test.ts +127 -0
- package/src/releases/observeReleases.ts +102 -0
- package/src/releases/releasesStore.test.ts +100 -53
- package/src/releases/releasesStore.ts +69 -44
- package/src/releases/utils/sortReleases.test.ts +38 -2
- package/src/releases/utils/sortReleases.ts +15 -10
- package/src/store/createActionBinder.test.ts +109 -90
- package/src/store/createActionBinder.ts +36 -28
- package/src/store/createSanityInstance.test.ts +15 -13
- package/src/store/createSanityInstance.ts +14 -2
- package/src/store/createStateSourceAction.test.ts +64 -2
- package/src/store/createStateSourceAction.ts +38 -43
- package/src/store/createStoreInstance.test.ts +4 -2
- package/src/store/fetcherStore.test-d.ts +121 -0
- package/src/store/fetcherStore.test.ts +299 -0
- package/src/store/fetcherStore.ts +801 -0
- package/src/telemetry/environment.test.ts +119 -0
- package/src/telemetry/environment.ts +92 -0
- package/src/telemetry/events.ts +42 -0
- package/src/telemetry/initTelemetry.test.ts +432 -0
- package/src/telemetry/initTelemetry.ts +218 -0
- package/src/telemetry/telemetryManager.test.ts +327 -0
- package/src/telemetry/telemetryManager.ts +199 -0
- package/src/users/reducers.test.ts +3 -1
- package/src/users/reducers.ts +5 -6
- package/src/users/types.ts +4 -4
- package/src/users/usersStore.test.ts +16 -12
- package/src/users/usersStore.ts +5 -1
- package/src/utils/buildQuery.test.ts +14 -0
- package/src/utils/buildQuery.ts +17 -0
- package/src/utils/createFetcherStore.test.ts +7 -5
- package/src/utils/createFetcherStore.ts +8 -5
- package/src/utils/getStagingApiHost.test.ts +21 -0
- package/src/utils/getStagingApiHost.ts +14 -0
- package/src/utils/ids.test.ts +1 -29
- package/src/utils/ids.ts +0 -10
- package/src/utils/includeShape.ts +36 -0
- package/src/utils/isImportError.test.ts +72 -0
- package/src/utils/isImportError.ts +34 -0
- package/src/utils/logger.test.ts +12 -0
- package/src/utils/logger.ts +8 -3
- package/src/utils/object.test.ts +95 -0
- package/src/utils/object.ts +142 -0
- package/src/utils/setCleanupTimeout.ts +37 -0
- package/src/document/processActions.ts +0 -707
- package/src/utils/defineIntent.test.ts +0 -477
- package/src/utils/defineIntent.ts +0 -244
|
@@ -1,17 +1,21 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
ConnectionFailedError,
|
|
3
|
+
CorsOriginError,
|
|
4
|
+
DisconnectError,
|
|
5
|
+
type LiveEvent,
|
|
6
|
+
type SanityClient,
|
|
7
|
+
type SyncTag,
|
|
8
|
+
} from '@sanity/client'
|
|
2
9
|
import {delay, filter, firstValueFrom, Observable, of, Subject} from 'rxjs'
|
|
3
10
|
import {beforeEach, describe, expect, it, vi} from 'vitest'
|
|
4
11
|
|
|
5
12
|
import {getClientState} from '../client/clientStore'
|
|
13
|
+
import {LIVE_EVENTS_RETRY_DELAY} from '../client/liveEvents'
|
|
6
14
|
import {isCanvasResource} from '../config/sanityConfig'
|
|
7
15
|
import {createSanityInstance, type SanityInstance} from '../store/createSanityInstance'
|
|
8
16
|
import {type StateSource} from '../store/createStateSourceAction'
|
|
9
17
|
import {getQueryState, resolveQuery} from './queryStore'
|
|
10
|
-
|
|
11
|
-
vi.mock('./queryStoreConstants', async (importOriginal) => ({
|
|
12
|
-
...(await importOriginal<typeof import('./queryStoreConstants')>()),
|
|
13
|
-
QUERY_STATE_CLEAR_DELAY: 10,
|
|
14
|
-
}))
|
|
18
|
+
import {QUERY_STATE_CLEAR_DELAY} from './queryStoreConstants'
|
|
15
19
|
|
|
16
20
|
vi.mock('../client/clientStore', () => ({
|
|
17
21
|
getClientState: vi.fn(),
|
|
@@ -34,6 +38,14 @@ vi.mock('../releases/getPerspectiveState', async () => {
|
|
|
34
38
|
}
|
|
35
39
|
})
|
|
36
40
|
|
|
41
|
+
// With fake timers, an emission gated on a pending rxjs delay or cleanup
|
|
42
|
+
// timeout never arrives on its own: create the promise first, advance the
|
|
43
|
+
// clock, then await it.
|
|
44
|
+
async function advanceAndAwait<T>(promise: Promise<T>, ms = 0): Promise<T> {
|
|
45
|
+
await vi.advanceTimersByTimeAsync(ms)
|
|
46
|
+
return promise
|
|
47
|
+
}
|
|
48
|
+
|
|
37
49
|
describe('queryStore', () => {
|
|
38
50
|
let instance: SanityInstance
|
|
39
51
|
let liveEvents: Subject<LiveEvent>
|
|
@@ -48,7 +60,8 @@ describe('queryStore', () => {
|
|
|
48
60
|
}
|
|
49
61
|
|
|
50
62
|
beforeEach(() => {
|
|
51
|
-
|
|
63
|
+
vi.useFakeTimers()
|
|
64
|
+
instance = createSanityInstance({projectId: 'test', dataset: 'test'})
|
|
52
65
|
|
|
53
66
|
fetch = vi
|
|
54
67
|
.fn()
|
|
@@ -76,11 +89,12 @@ describe('queryStore', () => {
|
|
|
76
89
|
afterEach(() => {
|
|
77
90
|
vi.mocked(getClientState).mockClear()
|
|
78
91
|
instance.dispose()
|
|
92
|
+
vi.useRealTimers()
|
|
79
93
|
})
|
|
80
94
|
|
|
81
95
|
it('initializes query state and cleans up after unsubscribe', async () => {
|
|
82
96
|
const query = '*[_type == "movie"]'
|
|
83
|
-
const state = getQueryState(instance, {query
|
|
97
|
+
const state = getQueryState(instance, {query})
|
|
84
98
|
|
|
85
99
|
// Initially undefined before subscription
|
|
86
100
|
expect(state.getCurrent()).toBeUndefined()
|
|
@@ -89,7 +103,7 @@ describe('queryStore', () => {
|
|
|
89
103
|
const unsubscribe = state.subscribe()
|
|
90
104
|
|
|
91
105
|
// Wait for data to be fetched
|
|
92
|
-
await firstValueFrom(state.observable.pipe(filter((i) => i !== undefined)))
|
|
106
|
+
await advanceAndAwait(firstValueFrom(state.observable.pipe(filter((i) => i !== undefined))))
|
|
93
107
|
|
|
94
108
|
// Verify data is present
|
|
95
109
|
expect(state.getCurrent()).toEqual([
|
|
@@ -101,7 +115,7 @@ describe('queryStore', () => {
|
|
|
101
115
|
unsubscribe()
|
|
102
116
|
|
|
103
117
|
// Wait for the cleanup delay
|
|
104
|
-
await
|
|
118
|
+
await vi.advanceTimersByTimeAsync(QUERY_STATE_CLEAR_DELAY)
|
|
105
119
|
|
|
106
120
|
// Verify state is cleared
|
|
107
121
|
expect(state.getCurrent()).toBeUndefined()
|
|
@@ -109,14 +123,14 @@ describe('queryStore', () => {
|
|
|
109
123
|
|
|
110
124
|
it('maintains state when multiple subscribers exist', async () => {
|
|
111
125
|
const query = '*[_type == "movie"]'
|
|
112
|
-
const state = getQueryState(instance, {query
|
|
126
|
+
const state = getQueryState(instance, {query})
|
|
113
127
|
|
|
114
128
|
// Add two subscribers
|
|
115
129
|
const unsubscribe1 = state.subscribe()
|
|
116
130
|
const unsubscribe2 = state.subscribe()
|
|
117
131
|
|
|
118
132
|
// Wait for data to be fetched
|
|
119
|
-
await firstValueFrom(state.observable.pipe(filter((i) => i !== undefined)))
|
|
133
|
+
await advanceAndAwait(firstValueFrom(state.observable.pipe(filter((i) => i !== undefined))))
|
|
120
134
|
|
|
121
135
|
// Verify data is present
|
|
122
136
|
expect(state.getCurrent()).toEqual([
|
|
@@ -137,7 +151,7 @@ describe('queryStore', () => {
|
|
|
137
151
|
unsubscribe2()
|
|
138
152
|
|
|
139
153
|
// Wait for cleanup delay
|
|
140
|
-
await
|
|
154
|
+
await vi.advanceTimersByTimeAsync(QUERY_STATE_CLEAR_DELAY)
|
|
141
155
|
|
|
142
156
|
// Verify state is cleared after all subscribers are gone
|
|
143
157
|
expect(state.getCurrent()).toBeUndefined()
|
|
@@ -146,22 +160,22 @@ describe('queryStore', () => {
|
|
|
146
160
|
it('resolveQuery works without affecting subscriber cleanup', async () => {
|
|
147
161
|
const query = '*[_type == "movie"]'
|
|
148
162
|
|
|
149
|
-
const state = getQueryState(instance, {query
|
|
163
|
+
const state = getQueryState(instance, {query})
|
|
150
164
|
|
|
151
165
|
// Check that getQueryState starts undefined
|
|
152
166
|
expect(state.getCurrent()).toBeUndefined()
|
|
153
167
|
|
|
154
|
-
//
|
|
155
|
-
|
|
168
|
+
// resolveQuery holds only a temporary subscriber (released after the
|
|
169
|
+
// clear delay once the promise settles)
|
|
170
|
+
const result = await advanceAndAwait(resolveQuery(instance, {query}))
|
|
156
171
|
expect(result).toEqual([
|
|
157
172
|
{_id: 'movie1', _type: 'movie', title: 'Movie 1'},
|
|
158
173
|
{_id: 'movie2', _type: 'movie', title: 'Movie 2'},
|
|
159
174
|
])
|
|
160
175
|
|
|
161
|
-
// Check that getQueryState
|
|
162
|
-
//
|
|
163
|
-
//
|
|
164
|
-
// subscriber that can trigger a clean up
|
|
176
|
+
// Check that getQueryState is resolved now. This behavior is important
|
|
177
|
+
// for supporting suspense: the resolved state must remain readable while
|
|
178
|
+
// React re-renders and attaches a lasting subscriber
|
|
165
179
|
expect(state.getCurrent()).toEqual([
|
|
166
180
|
{_id: 'movie1', _type: 'movie', title: 'Movie 1'},
|
|
167
181
|
{_id: 'movie2', _type: 'movie', title: 'Movie 2'},
|
|
@@ -170,7 +184,7 @@ describe('queryStore', () => {
|
|
|
170
184
|
// Subscribing and unsubscribing should nuke the state now
|
|
171
185
|
const unsubscribe = state.subscribe()
|
|
172
186
|
unsubscribe()
|
|
173
|
-
await
|
|
187
|
+
await vi.advanceTimersByTimeAsync(QUERY_STATE_CLEAR_DELAY)
|
|
174
188
|
expect(state.getCurrent()).toBeUndefined()
|
|
175
189
|
})
|
|
176
190
|
|
|
@@ -179,11 +193,7 @@ describe('queryStore', () => {
|
|
|
179
193
|
const abortController = new AbortController()
|
|
180
194
|
|
|
181
195
|
// Create a promise that will reject when aborted
|
|
182
|
-
const queryPromise = resolveQuery(instance, {
|
|
183
|
-
query,
|
|
184
|
-
resource: {projectId: 'p', dataset: 'd'},
|
|
185
|
-
signal: abortController.signal,
|
|
186
|
-
})
|
|
196
|
+
const queryPromise = resolveQuery(instance, {query, signal: abortController.signal})
|
|
187
197
|
|
|
188
198
|
// Abort the request
|
|
189
199
|
abortController.abort()
|
|
@@ -192,9 +202,14 @@ describe('queryStore', () => {
|
|
|
192
202
|
await expect(queryPromise).rejects.toThrow('The operation was aborted.')
|
|
193
203
|
|
|
194
204
|
// Verify state is cleared after abort
|
|
195
|
-
expect(
|
|
196
|
-
|
|
197
|
-
|
|
205
|
+
expect(getQueryState(instance, {query}).getCurrent()).toBeUndefined()
|
|
206
|
+
|
|
207
|
+
// The key must be removed immediately (not after the clear delay): a new
|
|
208
|
+
// resolveQuery re-adds it, which only triggers a fresh fetch if the abort
|
|
209
|
+
// actually released the previous temporary subscriber
|
|
210
|
+
const callsBefore = vi.mocked(fetch).mock.calls.length
|
|
211
|
+
await expect(advanceAndAwait(resolveQuery(instance, {query}))).resolves.toEqual(mockData.movies)
|
|
212
|
+
expect(vi.mocked(fetch).mock.calls.length).toBe(callsBefore + 1)
|
|
198
213
|
})
|
|
199
214
|
|
|
200
215
|
it('refetches query when receiving live event with matching sync tag', async () => {
|
|
@@ -213,13 +228,10 @@ describe('queryStore', () => {
|
|
|
213
228
|
)
|
|
214
229
|
|
|
215
230
|
const query = '*[_type == "movie"]'
|
|
216
|
-
const state = getQueryState<{_id: string; _type: string; title: string}[]>(instance, {
|
|
217
|
-
query,
|
|
218
|
-
resource: {projectId: 'p', dataset: 'd'},
|
|
219
|
-
})
|
|
231
|
+
const state = getQueryState<{_id: string; _type: string; title: string}[]>(instance, {query})
|
|
220
232
|
|
|
221
233
|
const unsubscribe = state.subscribe()
|
|
222
|
-
await firstValueFrom(state.observable.pipe(filter((i) => i !== undefined)))
|
|
234
|
+
await advanceAndAwait(firstValueFrom(state.observable.pipe(filter((i) => i !== undefined))))
|
|
223
235
|
|
|
224
236
|
// Emit live event with matching sync tag
|
|
225
237
|
liveEvents.next({
|
|
@@ -231,7 +243,9 @@ describe('queryStore', () => {
|
|
|
231
243
|
} as LiveEvent)
|
|
232
244
|
|
|
233
245
|
// Wait for updated data
|
|
234
|
-
const result = await
|
|
246
|
+
const result = await advanceAndAwait(
|
|
247
|
+
firstValueFrom(state.observable.pipe(filter((data) => data?.length === 3))),
|
|
248
|
+
)
|
|
235
249
|
|
|
236
250
|
expect(fetch).toHaveBeenCalledTimes(2)
|
|
237
251
|
expect(vi.mocked(fetch).mock.calls[1][2]?.lastLiveEventId).toBe('event1')
|
|
@@ -247,10 +261,10 @@ describe('queryStore', () => {
|
|
|
247
261
|
)
|
|
248
262
|
|
|
249
263
|
const query = '*[_type == "movie"]'
|
|
250
|
-
const state = getQueryState(instance, {query
|
|
264
|
+
const state = getQueryState(instance, {query})
|
|
251
265
|
|
|
252
266
|
const unsubscribe = state.subscribe()
|
|
253
|
-
await firstValueFrom(state.observable.pipe(filter((i) => i !== undefined)))
|
|
267
|
+
await advanceAndAwait(firstValueFrom(state.observable.pipe(filter((i) => i !== undefined))))
|
|
254
268
|
|
|
255
269
|
// Emit event with different tag
|
|
256
270
|
liveEvents.next({
|
|
@@ -261,7 +275,7 @@ describe('queryStore', () => {
|
|
|
261
275
|
event: 'created',
|
|
262
276
|
} as LiveEvent)
|
|
263
277
|
|
|
264
|
-
await
|
|
278
|
+
await vi.advanceTimersByTimeAsync(50) // Allow time for potential refetch
|
|
265
279
|
expect(fetch).toHaveBeenCalledTimes(1)
|
|
266
280
|
|
|
267
281
|
unsubscribe()
|
|
@@ -280,10 +294,10 @@ describe('queryStore', () => {
|
|
|
280
294
|
)
|
|
281
295
|
|
|
282
296
|
const query = '*[_type == "movie"]'
|
|
283
|
-
const state = getQueryState(instance, {query
|
|
297
|
+
const state = getQueryState(instance, {query})
|
|
284
298
|
|
|
285
299
|
const unsubscribe = state.subscribe()
|
|
286
|
-
await firstValueFrom(state.observable.pipe(filter((i) => i !== undefined)))
|
|
300
|
+
await advanceAndAwait(firstValueFrom(state.observable.pipe(filter((i) => i !== undefined))))
|
|
287
301
|
|
|
288
302
|
// Emit two events with same tag
|
|
289
303
|
liveEvents.next({
|
|
@@ -298,7 +312,7 @@ describe('queryStore', () => {
|
|
|
298
312
|
tags: mockSyncTags,
|
|
299
313
|
})
|
|
300
314
|
|
|
301
|
-
await
|
|
315
|
+
await vi.advanceTimersByTimeAsync(0)
|
|
302
316
|
expect(fetch).toHaveBeenCalledTimes(3)
|
|
303
317
|
expect(vi.mocked(fetch).mock.calls[1][2]?.lastLiveEventId).toBe('event1')
|
|
304
318
|
expect(vi.mocked(fetch).mock.calls[2][2]?.lastLiveEventId).toBe('event2')
|
|
@@ -317,7 +331,7 @@ describe('queryStore', () => {
|
|
|
317
331
|
)
|
|
318
332
|
|
|
319
333
|
const query = '*[_type == "movie"]'
|
|
320
|
-
const state = getQueryState(instance, {query
|
|
334
|
+
const state = getQueryState(instance, {query})
|
|
321
335
|
const unsubscribe = state.subscribe()
|
|
322
336
|
|
|
323
337
|
// Verify error is thrown when accessing state
|
|
@@ -326,28 +340,199 @@ describe('queryStore', () => {
|
|
|
326
340
|
unsubscribe()
|
|
327
341
|
})
|
|
328
342
|
|
|
343
|
+
it('refetches when a query key is re-added after an error', async () => {
|
|
344
|
+
// First fetch fails (e.g. transient network failure)
|
|
345
|
+
vi.mocked(fetch).mockReturnValueOnce(
|
|
346
|
+
new Observable((observer) => {
|
|
347
|
+
observer.error(new Error('transient network failure'))
|
|
348
|
+
}),
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
const query = '*[_type == "movie"]'
|
|
352
|
+
const state1 = getQueryState(instance, {query})
|
|
353
|
+
const unsub1 = state1.subscribe()
|
|
354
|
+
expect(() => state1.getCurrent()).toThrow('transient network failure')
|
|
355
|
+
unsub1()
|
|
356
|
+
|
|
357
|
+
// Wait for the clear delay so the key is fully removed from state
|
|
358
|
+
await vi.advanceTimersByTimeAsync(QUERY_STATE_CLEAR_DELAY)
|
|
359
|
+
|
|
360
|
+
// Retry with the same key — must trigger a fresh fetch
|
|
361
|
+
const state2 = getQueryState(instance, {query})
|
|
362
|
+
const unsub2 = state2.subscribe()
|
|
363
|
+
|
|
364
|
+
await vi.advanceTimersByTimeAsync(0)
|
|
365
|
+
expect(vi.mocked(fetch)).toHaveBeenCalledTimes(2)
|
|
366
|
+
|
|
367
|
+
const result = await advanceAndAwait(
|
|
368
|
+
firstValueFrom(state2.observable.pipe(filter((i) => i !== undefined))),
|
|
369
|
+
)
|
|
370
|
+
expect(result).toEqual(mockData.movies)
|
|
371
|
+
unsub2()
|
|
372
|
+
})
|
|
373
|
+
|
|
374
|
+
it('releases an errored key created by resolveQuery so a retry can refetch', async () => {
|
|
375
|
+
vi.mocked(fetch).mockReturnValueOnce(
|
|
376
|
+
new Observable((observer) => {
|
|
377
|
+
observer.error(new Error('network down'))
|
|
378
|
+
}),
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
const query = '*[_type == "movie"]'
|
|
382
|
+
// This is how React drives a suspended query: resolveQuery creates the
|
|
383
|
+
// key without any subscriber (the component never commits when it throws)
|
|
384
|
+
await expect(resolveQuery(instance, {query})).rejects.toThrow('network down')
|
|
385
|
+
|
|
386
|
+
// While the errored key exists, the error surfaces to error boundaries
|
|
387
|
+
const state = getQueryState(instance, {query})
|
|
388
|
+
expect(() => state.getCurrent()).toThrow('network down')
|
|
389
|
+
|
|
390
|
+
// After the clear delay, the errored key must be released — otherwise it
|
|
391
|
+
// has no subscribers, nothing ever removes it, and every future mount
|
|
392
|
+
// rethrows the stored error without ever fetching
|
|
393
|
+
await vi.advanceTimersByTimeAsync(QUERY_STATE_CLEAR_DELAY)
|
|
394
|
+
expect(state.getCurrent()).toBeUndefined()
|
|
395
|
+
|
|
396
|
+
// A retry now creates a fresh key and fetches
|
|
397
|
+
await expect(advanceAndAwait(resolveQuery(instance, {query}))).resolves.toEqual(mockData.movies)
|
|
398
|
+
})
|
|
399
|
+
|
|
400
|
+
it('stops live updates without retrying or erroring on a 4xx connection rejection', async () => {
|
|
401
|
+
const query = '*[_type == "movie"]'
|
|
402
|
+
const state = getQueryState(instance, {query})
|
|
403
|
+
const unsub = state.subscribe()
|
|
404
|
+
await advanceAndAwait(firstValueFrom(state.observable.pipe(filter((i) => i !== undefined))))
|
|
405
|
+
|
|
406
|
+
const clientState = vi.mocked(getClientState).mock.results[0]
|
|
407
|
+
?.value as StateSource<SanityClient>
|
|
408
|
+
const client = await firstValueFrom(clientState.observable)
|
|
409
|
+
const eventsMock = vi.mocked(client.live.events)
|
|
410
|
+
const callsBefore = eventsMock.mock.calls.length
|
|
411
|
+
|
|
412
|
+
// The server rejected the connection with a 401 (e.g. expired token). The
|
|
413
|
+
// client surfaces this as a fatal ConnectionFailedError with the status —
|
|
414
|
+
// retrying would reconnect once per second forever against a server that
|
|
415
|
+
// keeps rejecting
|
|
416
|
+
liveEvents.error(new ConnectionFailedError('EventSource connection failed', {status: 401}))
|
|
417
|
+
await vi.advanceTimersByTimeAsync(LIVE_EVENTS_RETRY_DELAY * 5)
|
|
418
|
+
|
|
419
|
+
expect(() => state.getCurrent()).not.toThrow()
|
|
420
|
+
expect(eventsMock.mock.calls.length).toBe(callsBefore)
|
|
421
|
+
unsub()
|
|
422
|
+
})
|
|
423
|
+
|
|
424
|
+
it('retries a connection failure without a status (transient network failure)', async () => {
|
|
425
|
+
const query = '*[_type == "movie"]'
|
|
426
|
+
const state = getQueryState(instance, {query})
|
|
427
|
+
const unsub = state.subscribe()
|
|
428
|
+
await advanceAndAwait(firstValueFrom(state.observable.pipe(filter((i) => i !== undefined))))
|
|
429
|
+
|
|
430
|
+
const clientState = vi.mocked(getClientState).mock.results[0]
|
|
431
|
+
?.value as StateSource<SanityClient>
|
|
432
|
+
const client = await firstValueFrom(clientState.observable)
|
|
433
|
+
const eventsMock = vi.mocked(client.live.events)
|
|
434
|
+
const callsBefore = eventsMock.mock.calls.length
|
|
435
|
+
|
|
436
|
+
// No status means the failure could be transient (native EventSource
|
|
437
|
+
// exposes no status) — reconnecting is correct here
|
|
438
|
+
liveEvents.error(new ConnectionFailedError('EventSource connection failed'))
|
|
439
|
+
await vi.advanceTimersByTimeAsync(LIVE_EVENTS_RETRY_DELAY * 2)
|
|
440
|
+
|
|
441
|
+
expect(() => state.getCurrent()).not.toThrow()
|
|
442
|
+
expect(eventsMock.mock.calls.length).toBeGreaterThan(callsBefore)
|
|
443
|
+
unsub()
|
|
444
|
+
})
|
|
445
|
+
|
|
446
|
+
it('surfaces CORS errors on the live connection as a store-wide error', async () => {
|
|
447
|
+
const query = '*[_type == "movie"]'
|
|
448
|
+
const state = getQueryState(instance, {query})
|
|
449
|
+
const unsub = state.subscribe()
|
|
450
|
+
await advanceAndAwait(firstValueFrom(state.observable.pipe(filter((i) => i !== undefined))))
|
|
451
|
+
|
|
452
|
+
liveEvents.error(new CorsOriginError({projectId: 'test'}))
|
|
453
|
+
await vi.advanceTimersByTimeAsync(0)
|
|
454
|
+
|
|
455
|
+
// the swallowed CORS error is recorded as store state so the query
|
|
456
|
+
// selector rethrows it (handled by the Cors Error component)
|
|
457
|
+
expect(() => state.getCurrent()).toThrow(CorsOriginError)
|
|
458
|
+
unsub()
|
|
459
|
+
})
|
|
460
|
+
|
|
461
|
+
it('stops live updates without retrying or erroring on DisconnectError', async () => {
|
|
462
|
+
const query = '*[_type == "movie"]'
|
|
463
|
+
const state = getQueryState(instance, {query})
|
|
464
|
+
const unsub = state.subscribe()
|
|
465
|
+
await advanceAndAwait(firstValueFrom(state.observable.pipe(filter((i) => i !== undefined))))
|
|
466
|
+
|
|
467
|
+
// Grab the live events factory from the client the store is subscribed to
|
|
468
|
+
const clientState = vi.mocked(getClientState).mock.results[0]
|
|
469
|
+
?.value as StateSource<SanityClient>
|
|
470
|
+
const client = await firstValueFrom(clientState.observable)
|
|
471
|
+
const eventsMock = vi.mocked(client.live.events)
|
|
472
|
+
const callsBefore = eventsMock.mock.calls.length
|
|
473
|
+
|
|
474
|
+
// The server instructed the client to stop reconnecting
|
|
475
|
+
liveEvents.error(new DisconnectError('Server disconnected client'))
|
|
476
|
+
// Advance past several retry delays
|
|
477
|
+
await vi.advanceTimersByTimeAsync(LIVE_EVENTS_RETRY_DELAY * 5)
|
|
478
|
+
|
|
479
|
+
// No store-wide error, and no reconnect attempts (a retry would call
|
|
480
|
+
// live.events again)
|
|
481
|
+
expect(() => state.getCurrent()).not.toThrow()
|
|
482
|
+
expect(eventsMock.mock.calls.length).toBe(callsBefore)
|
|
483
|
+
unsub()
|
|
484
|
+
})
|
|
485
|
+
|
|
486
|
+
it('keeps queries working after the live events connection errors', async () => {
|
|
487
|
+
const query = '*[_type == "movie"]'
|
|
488
|
+
const state = getQueryState(instance, {query})
|
|
489
|
+
const unsub = state.subscribe()
|
|
490
|
+
await advanceAndAwait(firstValueFrom(state.observable.pipe(filter((i) => i !== undefined))))
|
|
491
|
+
|
|
492
|
+
// The live connection drops (e.g. network goes offline)
|
|
493
|
+
liveEvents.error(new Error('live connection lost'))
|
|
494
|
+
await vi.advanceTimersByTimeAsync(0)
|
|
495
|
+
|
|
496
|
+
// Existing query state must remain readable — a lost live connection must
|
|
497
|
+
// not poison the whole store
|
|
498
|
+
expect(() => state.getCurrent()).not.toThrow()
|
|
499
|
+
expect(state.getCurrent()).toEqual(mockData.movies)
|
|
500
|
+
unsub()
|
|
501
|
+
|
|
502
|
+
// Wait for the clear delay so the key is fully removed, then re-add it —
|
|
503
|
+
// fetching must still work
|
|
504
|
+
await vi.advanceTimersByTimeAsync(QUERY_STATE_CLEAR_DELAY)
|
|
505
|
+
const state2 = getQueryState(instance, {query})
|
|
506
|
+
const unsub2 = state2.subscribe()
|
|
507
|
+
const result = await advanceAndAwait(
|
|
508
|
+
firstValueFrom(state2.observable.pipe(filter((i) => i !== undefined))),
|
|
509
|
+
)
|
|
510
|
+
expect(result).toEqual(mockData.movies)
|
|
511
|
+
unsub2()
|
|
512
|
+
})
|
|
513
|
+
|
|
329
514
|
it('delays query state removal after unsubscribe', async () => {
|
|
330
515
|
const query = '*[_type == "movie"]'
|
|
331
|
-
const state = getQueryState(instance, {query
|
|
516
|
+
const state = getQueryState(instance, {query})
|
|
332
517
|
const unsubscribe = state.subscribe()
|
|
333
518
|
|
|
334
|
-
await firstValueFrom(state.observable.pipe(filter((i) => i !== undefined)))
|
|
519
|
+
await advanceAndAwait(firstValueFrom(state.observable.pipe(filter((i) => i !== undefined))))
|
|
335
520
|
|
|
336
521
|
unsubscribe()
|
|
337
522
|
// Immediately after unsubscription, state should still be present due to delay
|
|
338
523
|
expect(state.getCurrent()).not.toBeUndefined()
|
|
339
524
|
|
|
340
525
|
// Wait for the cleanup delay and then state should be removed
|
|
341
|
-
await
|
|
526
|
+
await vi.advanceTimersByTimeAsync(QUERY_STATE_CLEAR_DELAY)
|
|
342
527
|
expect(state.getCurrent()).toBeUndefined()
|
|
343
528
|
})
|
|
344
529
|
|
|
345
530
|
it('preserves query state if a new subscriber subscribes before cleanup delay', async () => {
|
|
346
531
|
const query = '*[_type == "movie"]'
|
|
347
|
-
const state = getQueryState(instance, {query
|
|
532
|
+
const state = getQueryState(instance, {query})
|
|
348
533
|
const unsubscribe1 = state.subscribe()
|
|
349
534
|
|
|
350
|
-
await firstValueFrom(state.observable.pipe(filter((i) => i !== undefined)))
|
|
535
|
+
await advanceAndAwait(firstValueFrom(state.observable.pipe(filter((i) => i !== undefined))))
|
|
351
536
|
expect(state.getCurrent()).toEqual([
|
|
352
537
|
{_id: 'movie1', _type: 'movie', title: 'Movie 1'},
|
|
353
538
|
{_id: 'movie2', _type: 'movie', title: 'Movie 2'},
|
|
@@ -355,13 +540,13 @@ describe('queryStore', () => {
|
|
|
355
540
|
|
|
356
541
|
unsubscribe1()
|
|
357
542
|
// Wait less than the cleanup delay
|
|
358
|
-
await
|
|
543
|
+
await vi.advanceTimersByTimeAsync(QUERY_STATE_CLEAR_DELAY / 2)
|
|
359
544
|
|
|
360
545
|
// Subscribe again before cleanup occurs
|
|
361
546
|
const unsubscribe2 = state.subscribe()
|
|
362
547
|
|
|
363
548
|
// Wait for cleanup delay to pass
|
|
364
|
-
await
|
|
549
|
+
await vi.advanceTimersByTimeAsync(QUERY_STATE_CLEAR_DELAY)
|
|
365
550
|
|
|
366
551
|
// Since a subscriber now exists, state should still be present
|
|
367
552
|
expect(state.getCurrent()).toEqual([
|
|
@@ -382,30 +567,30 @@ describe('queryStore', () => {
|
|
|
382
567
|
}) as SanityClient['observable']['fetch'])
|
|
383
568
|
|
|
384
569
|
const draftsInstance = createSanityInstance({
|
|
570
|
+
projectId: 'test',
|
|
571
|
+
dataset: 'test',
|
|
385
572
|
perspective: 'drafts',
|
|
386
573
|
})
|
|
387
574
|
const publishedInstance = createSanityInstance({
|
|
575
|
+
projectId: 'test',
|
|
576
|
+
dataset: 'test',
|
|
388
577
|
perspective: 'published',
|
|
389
578
|
})
|
|
390
579
|
|
|
391
580
|
// Same query/options, different implicit perspectives via instance.config
|
|
392
|
-
const sDrafts = getQueryState<{_id: string}[]>(draftsInstance, {
|
|
393
|
-
query: '*[_type == "movie"]',
|
|
394
|
-
resource: {projectId: 'p', dataset: 'd'},
|
|
395
|
-
})
|
|
581
|
+
const sDrafts = getQueryState<{_id: string}[]>(draftsInstance, {query: '*[_type == "movie"]'})
|
|
396
582
|
const sPublished = getQueryState<{_id: string}[]>(publishedInstance, {
|
|
397
583
|
query: '*[_type == "movie"]',
|
|
398
|
-
resource: {projectId: 'p', dataset: 'd'},
|
|
399
584
|
})
|
|
400
585
|
|
|
401
586
|
const unsubDrafts = sDrafts.subscribe()
|
|
402
587
|
const unsubPublished = sPublished.subscribe()
|
|
403
588
|
|
|
404
|
-
const draftsResult = await
|
|
405
|
-
sDrafts.observable.pipe(filter((i) => i !== undefined)),
|
|
589
|
+
const draftsResult = await advanceAndAwait(
|
|
590
|
+
firstValueFrom(sDrafts.observable.pipe(filter((i) => i !== undefined))),
|
|
406
591
|
)
|
|
407
|
-
const publishedResult = await
|
|
408
|
-
sPublished.observable.pipe(filter((i) => i !== undefined)),
|
|
592
|
+
const publishedResult = await advanceAndAwait(
|
|
593
|
+
firstValueFrom(sPublished.observable.pipe(filter((i) => i !== undefined))),
|
|
409
594
|
)
|
|
410
595
|
|
|
411
596
|
expect(draftsResult).toEqual([{_id: 'drafts'}])
|
|
@@ -427,27 +612,25 @@ describe('queryStore', () => {
|
|
|
427
612
|
>
|
|
428
613
|
}) as SanityClient['observable']['fetch'])
|
|
429
614
|
|
|
430
|
-
const base = createSanityInstance()
|
|
615
|
+
const base = createSanityInstance({projectId: 'test', dataset: 'test'})
|
|
431
616
|
|
|
432
617
|
const sDrafts = getQueryState<{_id: string}[]>(base, {
|
|
433
618
|
query: '*[_type == "movie"]',
|
|
434
619
|
perspective: 'drafts',
|
|
435
|
-
resource: {projectId: 'p', dataset: 'd'},
|
|
436
620
|
})
|
|
437
621
|
const sPublished = getQueryState<{_id: string}[]>(base, {
|
|
438
622
|
query: '*[_type == "movie"]',
|
|
439
623
|
perspective: 'published',
|
|
440
|
-
resource: {projectId: 'p', dataset: 'd'},
|
|
441
624
|
})
|
|
442
625
|
|
|
443
626
|
const unsubDrafts = sDrafts.subscribe()
|
|
444
627
|
const unsubPublished = sPublished.subscribe()
|
|
445
628
|
|
|
446
|
-
const draftsResult = await
|
|
447
|
-
sDrafts.observable.pipe(filter((i) => i !== undefined)),
|
|
629
|
+
const draftsResult = await advanceAndAwait(
|
|
630
|
+
firstValueFrom(sDrafts.observable.pipe(filter((i) => i !== undefined))),
|
|
448
631
|
)
|
|
449
|
-
const publishedResult = await
|
|
450
|
-
sPublished.observable.pipe(filter((i) => i !== undefined)),
|
|
632
|
+
const publishedResult = await advanceAndAwait(
|
|
633
|
+
firstValueFrom(sPublished.observable.pipe(filter((i) => i !== undefined))),
|
|
451
634
|
)
|
|
452
635
|
|
|
453
636
|
expect(draftsResult).toEqual([{_id: 'drafts'}])
|
|
@@ -461,15 +644,15 @@ describe('queryStore', () => {
|
|
|
461
644
|
|
|
462
645
|
it('uses resource from params when passed in query options (listenForNewSubscribersAndFetch)', async () => {
|
|
463
646
|
const query = '*[_type == "movie"]'
|
|
464
|
-
const
|
|
647
|
+
const mediaLibrarySource = {mediaLibraryId: 'ml123'}
|
|
465
648
|
|
|
466
|
-
const state = getQueryState(instance, {query, resource:
|
|
649
|
+
const state = getQueryState(instance, {query, resource: mediaLibrarySource})
|
|
467
650
|
const unsubscribe = state.subscribe()
|
|
468
651
|
|
|
469
|
-
await firstValueFrom(state.observable.pipe(filter((i) => i !== undefined)))
|
|
652
|
+
await advanceAndAwait(firstValueFrom(state.observable.pipe(filter((i) => i !== undefined))))
|
|
470
653
|
|
|
471
|
-
// Verify getClientState was called with the
|
|
472
|
-
// This call includes projectId, dataset, and
|
|
654
|
+
// Verify getClientState was called with the resource from params in listenForNewSubscribersAndFetch
|
|
655
|
+
// This call includes projectId, dataset, and resource
|
|
473
656
|
expect(getClientState).toHaveBeenCalledWith(
|
|
474
657
|
instance,
|
|
475
658
|
expect.objectContaining({
|
|
@@ -484,16 +667,16 @@ describe('queryStore', () => {
|
|
|
484
667
|
|
|
485
668
|
it('uses resource from store context key when not a dataset resource (listenToLiveClientAndSetLastLiveEventIds)', async () => {
|
|
486
669
|
const query = '*[_type == "movie"]'
|
|
487
|
-
const
|
|
670
|
+
const canvasSource = {canvasId: 'canvas456'}
|
|
488
671
|
|
|
489
|
-
const state = getQueryState(instance, {query, resource:
|
|
672
|
+
const state = getQueryState(instance, {query, resource: canvasSource})
|
|
490
673
|
const unsubscribe = state.subscribe()
|
|
491
674
|
|
|
492
|
-
await firstValueFrom(state.observable.pipe(filter((i) => i !== undefined)))
|
|
675
|
+
await advanceAndAwait(firstValueFrom(state.observable.pipe(filter((i) => i !== undefined))))
|
|
493
676
|
|
|
494
|
-
// Verify getClientState was called with the canvas
|
|
495
|
-
// The
|
|
496
|
-
// This call only has apiVersion and
|
|
677
|
+
// Verify getClientState was called with the canvas resource for live events
|
|
678
|
+
// The resource is extracted from the store key and passed when it's not a dataset resource
|
|
679
|
+
// This call only has apiVersion and resource (no projectId/dataset)
|
|
497
680
|
const calls = vi.mocked(getClientState).mock.calls
|
|
498
681
|
const liveClientCall = calls.find(
|
|
499
682
|
([_instance, options]) =>
|
|
@@ -503,51 +686,4 @@ describe('queryStore', () => {
|
|
|
503
686
|
|
|
504
687
|
unsubscribe()
|
|
505
688
|
})
|
|
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
|
-
})
|
|
553
689
|
})
|