@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
package/src/query/queryStore.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {type ResponseQueryOptions} from '@sanity/client'
|
|
2
|
+
import {type SanityQueryResult} from 'groq'
|
|
2
3
|
import {
|
|
3
4
|
catchError,
|
|
4
5
|
combineLatest,
|
|
5
|
-
defer,
|
|
6
6
|
distinctUntilChanged,
|
|
7
7
|
EMPTY,
|
|
8
8
|
filter,
|
|
@@ -23,15 +23,8 @@ import {
|
|
|
23
23
|
} from 'rxjs'
|
|
24
24
|
|
|
25
25
|
import {getClientState} from '../client/clientStore'
|
|
26
|
-
import {
|
|
27
|
-
|
|
28
|
-
* Although this is an import dependency cycle, it is not a logical cycle:
|
|
29
|
-
* 1. queryStore uses getPerspectiveState when resolving release perspectives
|
|
30
|
-
* 2. getPerspectiveState uses releasesStore as a data source
|
|
31
|
-
* 3. releasesStore uses queryStore as a data source
|
|
32
|
-
* 4. however, queryStore does not use getPerspectiveState for the perspective used in releasesStore ("raw")
|
|
33
|
-
*/
|
|
34
|
-
// eslint-disable-next-line import/no-cycle
|
|
26
|
+
import {observeLiveEvents} from '../client/liveEvents'
|
|
27
|
+
import {type DatasetHandle} from '../config/sanityConfig'
|
|
35
28
|
import {getPerspectiveState} from '../releases/getPerspectiveState'
|
|
36
29
|
import {isReleasePerspective} from '../releases/utils/isReleasePerspective'
|
|
37
30
|
import {bindActionByResource, type BoundResourceKey} from '../store/createActionBinder'
|
|
@@ -44,6 +37,7 @@ import {
|
|
|
44
37
|
import {type StoreState} from '../store/createStoreState'
|
|
45
38
|
import {defineStore, type StoreContext} from '../store/defineStore'
|
|
46
39
|
import {insecureRandomId} from '../utils/ids'
|
|
40
|
+
import {setCleanupTimeout} from '../utils/setCleanupTimeout'
|
|
47
41
|
import {
|
|
48
42
|
QUERY_STATE_CLEAR_DELAY,
|
|
49
43
|
QUERY_STORE_API_VERSION,
|
|
@@ -51,8 +45,6 @@ import {
|
|
|
51
45
|
} from './queryStoreConstants'
|
|
52
46
|
import {
|
|
53
47
|
addSubscriber,
|
|
54
|
-
cancelQuery,
|
|
55
|
-
initializeQuery,
|
|
56
48
|
type QueryStoreState,
|
|
57
49
|
removeSubscriber,
|
|
58
50
|
setLastLiveEventId,
|
|
@@ -70,7 +62,7 @@ export interface QueryOptions<
|
|
|
70
62
|
>
|
|
71
63
|
extends
|
|
72
64
|
Pick<ResponseQueryOptions, 'useCdn' | 'cache' | 'next' | 'cacheMode' | 'tag'>,
|
|
73
|
-
|
|
65
|
+
DatasetHandle<TDataset, TProjectId> {
|
|
74
66
|
query: TQuery
|
|
75
67
|
params?: Record<string, unknown>
|
|
76
68
|
}
|
|
@@ -88,9 +80,10 @@ export interface ResolveQueryOptions<
|
|
|
88
80
|
|
|
89
81
|
const EMPTY_ARRAY: never[] = []
|
|
90
82
|
|
|
91
|
-
/** @
|
|
92
|
-
export const getQueryKey = (options: QueryOptions): string =>
|
|
93
|
-
|
|
83
|
+
/** @internal */
|
|
84
|
+
export const getQueryKey = (instance: SanityInstance, options: QueryOptions): string =>
|
|
85
|
+
JSON.stringify(normalizeOptionsWithPerspective(instance, options))
|
|
86
|
+
/** @internal */
|
|
94
87
|
export const parseQueryKey = (key: string): QueryOptions => JSON.parse(key)
|
|
95
88
|
|
|
96
89
|
/**
|
|
@@ -136,11 +129,7 @@ const errorHandler = (state: StoreState<{error?: unknown}>) => {
|
|
|
136
129
|
return (error: unknown): void => state.set('setError', {error})
|
|
137
130
|
}
|
|
138
131
|
|
|
139
|
-
const listenForNewSubscribersAndFetch = ({
|
|
140
|
-
state,
|
|
141
|
-
instance,
|
|
142
|
-
key: {resource: boundResource},
|
|
143
|
-
}: StoreContext<QueryStoreState, BoundResourceKey>) => {
|
|
132
|
+
const listenForNewSubscribersAndFetch = ({state, instance}: StoreContext<QueryStoreState>) => {
|
|
144
133
|
return state.observable
|
|
145
134
|
.pipe(
|
|
146
135
|
map((s) => new Set(Object.keys(s.queries))),
|
|
@@ -172,6 +161,8 @@ const listenForNewSubscribersAndFetch = ({
|
|
|
172
161
|
const {
|
|
173
162
|
query,
|
|
174
163
|
params,
|
|
164
|
+
projectId,
|
|
165
|
+
dataset,
|
|
175
166
|
tag,
|
|
176
167
|
resource,
|
|
177
168
|
perspective: perspectiveFromOptions,
|
|
@@ -183,19 +174,15 @@ const listenForNewSubscribersAndFetch = ({
|
|
|
183
174
|
const perspective$ = isReleasePerspective(perspectiveFromOptions)
|
|
184
175
|
? getPerspectiveState(instance, {
|
|
185
176
|
perspective: perspectiveFromOptions,
|
|
186
|
-
resource
|
|
177
|
+
resource,
|
|
187
178
|
}).observable.pipe(filter(Boolean))
|
|
188
179
|
: of(perspectiveFromOptions ?? QUERY_STORE_DEFAULT_PERSPECTIVE)
|
|
189
180
|
|
|
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).
|
|
196
181
|
const client$ = getClientState(instance, {
|
|
197
182
|
apiVersion: QUERY_STORE_API_VERSION,
|
|
198
|
-
|
|
183
|
+
projectId,
|
|
184
|
+
dataset,
|
|
185
|
+
resource,
|
|
199
186
|
}).observable
|
|
200
187
|
|
|
201
188
|
return combineLatest({
|
|
@@ -213,15 +200,19 @@ const listenForNewSubscribersAndFetch = ({
|
|
|
213
200
|
tag,
|
|
214
201
|
}),
|
|
215
202
|
),
|
|
203
|
+
tap(({result, syncTags}) => {
|
|
204
|
+
state.set('setQueryData', setQueryData(group$.key, result, syncTags))
|
|
205
|
+
}),
|
|
206
|
+
// Catch inside the per-event stream: erroring the group pipe would
|
|
207
|
+
// complete the group's subscription, and since `groupBy` above never
|
|
208
|
+
// removes its group subjects, re-adding the key would emit into a
|
|
209
|
+
// subject with no subscribers — the key could never be fetched again.
|
|
210
|
+
catchError((error) => {
|
|
211
|
+
state.set('setQueryError', setQueryError(group$.key, error))
|
|
212
|
+
return EMPTY
|
|
213
|
+
}),
|
|
216
214
|
)
|
|
217
215
|
}),
|
|
218
|
-
catchError((error) => {
|
|
219
|
-
state.set('setQueryError', setQueryError(group$.key, error))
|
|
220
|
-
return EMPTY
|
|
221
|
-
}),
|
|
222
|
-
tap(({result, syncTags}) => {
|
|
223
|
-
state.set('setQueryData', setQueryData(group$.key, result, syncTags))
|
|
224
|
-
}),
|
|
225
216
|
),
|
|
226
217
|
),
|
|
227
218
|
)
|
|
@@ -233,27 +224,14 @@ const listenToLiveClientAndSetLastLiveEventIds = ({
|
|
|
233
224
|
instance,
|
|
234
225
|
key: {resource},
|
|
235
226
|
}: StoreContext<QueryStoreState, BoundResourceKey>) => {
|
|
236
|
-
const liveMessages$ =
|
|
237
|
-
apiVersion: QUERY_STORE_API_VERSION,
|
|
227
|
+
const liveMessages$ = observeLiveEvents(instance, {
|
|
238
228
|
resource,
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
if (error instanceof CorsOriginError) {
|
|
246
|
-
// Swallow only CORS errors in store without bubbling up so that they are handled by the Cors Error component
|
|
247
|
-
state.set('setError', {error})
|
|
248
|
-
return EMPTY
|
|
249
|
-
}
|
|
250
|
-
throw error
|
|
251
|
-
}),
|
|
252
|
-
),
|
|
253
|
-
),
|
|
254
|
-
share(),
|
|
255
|
-
filter((e) => e.type === 'message'),
|
|
256
|
-
)
|
|
229
|
+
// Surface CORS errors as store state (handled by the Cors Error
|
|
230
|
+
// component) instead of erroring the stream: a stream error here would
|
|
231
|
+
// reach the outer subscription's errorHandler, set the store-wide
|
|
232
|
+
// `state.error`, and permanently brick every query selector.
|
|
233
|
+
onCorsError: (error) => state.set('setError', {error}),
|
|
234
|
+
}).pipe(share())
|
|
257
235
|
|
|
258
236
|
return state.observable
|
|
259
237
|
.pipe(
|
|
@@ -293,6 +271,16 @@ const listenToLiveClientAndSetLastLiveEventIds = ({
|
|
|
293
271
|
*
|
|
294
272
|
* @beta
|
|
295
273
|
*/
|
|
274
|
+
export function getQueryState<
|
|
275
|
+
TQuery extends string = string,
|
|
276
|
+
TDataset extends string = string,
|
|
277
|
+
TProjectId extends string = string,
|
|
278
|
+
>(
|
|
279
|
+
instance: SanityInstance,
|
|
280
|
+
queryOptions: QueryOptions<TQuery, TDataset, TProjectId>,
|
|
281
|
+
): StateSource<SanityQueryResult<TQuery, `${TProjectId}.${TDataset}`> | undefined>
|
|
282
|
+
|
|
283
|
+
/** @beta */
|
|
296
284
|
export function getQueryState<TData>(
|
|
297
285
|
instance: SanityInstance,
|
|
298
286
|
queryOptions: QueryOptions,
|
|
@@ -315,20 +303,20 @@ const _getQueryState = bindActionByResource(
|
|
|
315
303
|
createStateSourceAction({
|
|
316
304
|
selector: ({state, instance}: SelectorContext<QueryStoreState>, options: QueryOptions) => {
|
|
317
305
|
if (state.error) throw state.error
|
|
318
|
-
const key = getQueryKey(
|
|
306
|
+
const key = getQueryKey(instance, options)
|
|
319
307
|
const queryState = state.queries[key]
|
|
320
308
|
if (queryState?.error) throw queryState.error
|
|
321
309
|
return queryState?.result
|
|
322
310
|
},
|
|
323
311
|
onSubscribe: ({state, instance}, options: QueryOptions) => {
|
|
324
312
|
const subscriptionId = insecureRandomId()
|
|
325
|
-
const key = getQueryKey(
|
|
313
|
+
const key = getQueryKey(instance, options)
|
|
326
314
|
|
|
327
315
|
state.set('addSubscriber', addSubscriber(key, subscriptionId))
|
|
328
316
|
|
|
329
317
|
return () => {
|
|
330
318
|
// this runs on unsubscribe
|
|
331
|
-
|
|
319
|
+
setCleanupTimeout(
|
|
332
320
|
() => state.set('removeSubscriber', removeSubscriber(key, subscriptionId)),
|
|
333
321
|
QUERY_STATE_CLEAR_DELAY,
|
|
334
322
|
)
|
|
@@ -341,16 +329,27 @@ const _getQueryState = bindActionByResource(
|
|
|
341
329
|
* Resolves the result of a query without registering a lasting subscriber.
|
|
342
330
|
*
|
|
343
331
|
* This function fetches the result of a GROQ query and returns a promise that resolves with the query result.
|
|
344
|
-
*
|
|
345
|
-
*
|
|
346
|
-
*
|
|
332
|
+
* It registers a temporary subscriber for the lifetime of the resolution (released shortly after the promise
|
|
333
|
+
* settles), so the query state participates in normal cleanup even when no lasting subscriber ever attaches —
|
|
334
|
+
* e.g. when a suspended component errors before committing. This makes it ideal for use with React Suspense,
|
|
335
|
+
* where the returned promise is thrown to delay rendering until the query result becomes available.
|
|
347
336
|
* Once the promise resolves, it is expected that a real subscriber will be added via `getQueryState` to manage ongoing updates.
|
|
348
337
|
*
|
|
349
338
|
* Additionally, an optional AbortSignal can be provided to cancel the query and immediately clear the associated state
|
|
350
|
-
* if there are no active subscribers.
|
|
339
|
+
* if there are no other active subscribers.
|
|
351
340
|
*
|
|
352
341
|
* @beta
|
|
353
342
|
*/
|
|
343
|
+
export function resolveQuery<
|
|
344
|
+
TQuery extends string = string,
|
|
345
|
+
TDataset extends string = string,
|
|
346
|
+
TProjectId extends string = string,
|
|
347
|
+
>(
|
|
348
|
+
instance: SanityInstance,
|
|
349
|
+
queryOptions: ResolveQueryOptions<TQuery, TDataset, TProjectId>,
|
|
350
|
+
): Promise<SanityQueryResult<TQuery, `${TProjectId}.${TDataset}`>>
|
|
351
|
+
|
|
352
|
+
/** @beta */
|
|
354
353
|
export function resolveQuery<TData>(
|
|
355
354
|
instance: SanityInstance,
|
|
356
355
|
queryOptions: ResolveQueryOptions,
|
|
@@ -364,7 +363,15 @@ const _resolveQuery = bindActionByResource(
|
|
|
364
363
|
({state, instance}, {signal, ...options}: ResolveQueryOptions) => {
|
|
365
364
|
const normalized = normalizeOptionsWithPerspective(instance, options)
|
|
366
365
|
const {getCurrent} = getQueryState(instance, normalized)
|
|
367
|
-
const key = getQueryKey(normalized)
|
|
366
|
+
const key = getQueryKey(instance, normalized)
|
|
367
|
+
|
|
368
|
+
// Hold a temporary subscription for the lifetime of this resolution so the
|
|
369
|
+
// key participates in normal cleanup. Without one, a fetch that errors
|
|
370
|
+
// while a component is suspended leaves a subscriber-less key behind — the
|
|
371
|
+
// component never commits, so no other subscriber exists — and its stored
|
|
372
|
+
// error would be rethrown on every future mount without ever refetching.
|
|
373
|
+
const subscriptionId = insecureRandomId()
|
|
374
|
+
state.set('addSubscriber', addSubscriber(key, subscriptionId))
|
|
368
375
|
|
|
369
376
|
const aborted$ = signal
|
|
370
377
|
? new Observable<void>((observer) => {
|
|
@@ -383,20 +390,29 @@ const _resolveQuery = bindActionByResource(
|
|
|
383
390
|
}).pipe(
|
|
384
391
|
catchError((error) => {
|
|
385
392
|
if (error instanceof Error && error.name === 'AbortError') {
|
|
386
|
-
|
|
393
|
+
// Release immediately (not after the clear delay) so that when
|
|
394
|
+
// this was the only subscriber, the key removal tears down the
|
|
395
|
+
// in-flight request right away — e.g. when useQuery switches
|
|
396
|
+
// to a different query
|
|
397
|
+
state.set('removeSubscriber', removeSubscriber(key, subscriptionId))
|
|
387
398
|
}
|
|
388
399
|
throw error
|
|
389
400
|
}),
|
|
390
401
|
)
|
|
391
402
|
: NEVER
|
|
392
403
|
|
|
393
|
-
state.set('initializeQuery', initializeQuery(key))
|
|
394
|
-
|
|
395
404
|
const resolved$ = state.observable.pipe(
|
|
396
405
|
map(getCurrent),
|
|
397
406
|
first((i) => i !== undefined),
|
|
398
407
|
)
|
|
399
408
|
|
|
400
|
-
|
|
409
|
+
const promise = firstValueFrom(race([resolved$, aborted$]))
|
|
410
|
+
const releaseSubscriber = () => {
|
|
411
|
+
setCleanupTimeout(() => {
|
|
412
|
+
state.set('removeSubscriber', removeSubscriber(key, subscriptionId))
|
|
413
|
+
}, QUERY_STATE_CLEAR_DELAY)
|
|
414
|
+
}
|
|
415
|
+
promise.then(releaseSubscriber, releaseSubscriber)
|
|
416
|
+
return promise
|
|
401
417
|
},
|
|
402
418
|
)
|
|
@@ -2,8 +2,6 @@ import {describe, expect, it} from 'vitest'
|
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
addSubscriber,
|
|
5
|
-
cancelQuery,
|
|
6
|
-
initializeQuery,
|
|
7
5
|
type QueryStoreState,
|
|
8
6
|
removeSubscriber,
|
|
9
7
|
setLastLiveEventId,
|
|
@@ -109,44 +107,4 @@ describe('Query Reducers', () => {
|
|
|
109
107
|
expect(newState.queries).not.toHaveProperty('q8')
|
|
110
108
|
})
|
|
111
109
|
})
|
|
112
|
-
|
|
113
|
-
describe('cancelQuery', () => {
|
|
114
|
-
it('should return state unchanged if key does not exist', () => {
|
|
115
|
-
const state: QueryStoreState = {queries: {}}
|
|
116
|
-
const reducer = cancelQuery('nonexistent')
|
|
117
|
-
const newState = reducer(state)
|
|
118
|
-
expect(newState).toBe(state)
|
|
119
|
-
})
|
|
120
|
-
|
|
121
|
-
it('should return state unchanged if query has subscribers', () => {
|
|
122
|
-
const state: QueryStoreState = {queries: {q9: {subscribers: ['sub1']}}}
|
|
123
|
-
const reducer = cancelQuery('q9')
|
|
124
|
-
const newState = reducer(state)
|
|
125
|
-
expect(newState).toBe(state)
|
|
126
|
-
})
|
|
127
|
-
|
|
128
|
-
it('should remove the query if no subscribers exist', () => {
|
|
129
|
-
const state: QueryStoreState = {queries: {q10: {subscribers: []}}}
|
|
130
|
-
const reducer = cancelQuery('q10')
|
|
131
|
-
const newState = reducer(state)
|
|
132
|
-
expect(newState.queries).not.toHaveProperty('q10')
|
|
133
|
-
})
|
|
134
|
-
})
|
|
135
|
-
|
|
136
|
-
describe('initializeQuery', () => {
|
|
137
|
-
it('should return state unchanged if query already exists', () => {
|
|
138
|
-
const existing = {subscribers: ['sub1']}
|
|
139
|
-
const state: QueryStoreState = {queries: {q11: existing}}
|
|
140
|
-
const reducer = initializeQuery('q11')
|
|
141
|
-
const newState = reducer(state)
|
|
142
|
-
expect(newState).toBe(state)
|
|
143
|
-
})
|
|
144
|
-
|
|
145
|
-
it('should add the query with empty subscribers if it does not exist', () => {
|
|
146
|
-
const state: QueryStoreState = {queries: {}}
|
|
147
|
-
const reducer = initializeQuery('q12')
|
|
148
|
-
const newState = reducer(state)
|
|
149
|
-
expect(newState.queries['q12']).toEqual({subscribers: []})
|
|
150
|
-
})
|
|
151
|
-
})
|
|
152
110
|
})
|
package/src/query/reducers.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {omitProperty} from '../utils/object'
|
|
2
2
|
|
|
3
3
|
interface QueryState {
|
|
4
4
|
syncTags?: string[]
|
|
@@ -54,22 +54,6 @@ export const removeSubscriber =
|
|
|
54
54
|
const prevQuery = prev.queries[key]
|
|
55
55
|
if (!prevQuery) return prev
|
|
56
56
|
const subscribers = prevQuery.subscribers.filter((id) => id !== subscriptionId)
|
|
57
|
-
if (!subscribers.length) return {...prev, queries:
|
|
57
|
+
if (!subscribers.length) return {...prev, queries: omitProperty(prev.queries, key)}
|
|
58
58
|
return {...prev, queries: {...prev.queries, [key]: {...prevQuery, subscribers}}}
|
|
59
59
|
}
|
|
60
|
-
|
|
61
|
-
export const cancelQuery =
|
|
62
|
-
(key: string) =>
|
|
63
|
-
(prev: QueryStoreState): QueryStoreState => {
|
|
64
|
-
const prevQuery = prev.queries[key]
|
|
65
|
-
if (!prevQuery) return prev
|
|
66
|
-
if (prevQuery.subscribers.length) return prev
|
|
67
|
-
return {...prev, queries: omit(prev.queries, key)}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export const initializeQuery =
|
|
71
|
-
(key: string) =>
|
|
72
|
-
(prev: QueryStoreState): QueryStoreState => {
|
|
73
|
-
if (prev.queries[key]) return prev
|
|
74
|
-
return {...prev, queries: {...prev.queries, [key]: {subscribers: []}}}
|
|
75
|
-
}
|
|
@@ -1,14 +1,13 @@
|
|
|
1
|
+
import {type ReleaseDocument} from '@sanity/client'
|
|
1
2
|
import {filter, firstValueFrom, of, Subject, take} from 'rxjs'
|
|
2
3
|
import {describe, expect, it, vi} from 'vitest'
|
|
3
4
|
|
|
4
|
-
import {type
|
|
5
|
-
import {getQueryState} from '../query/queryStore'
|
|
5
|
+
import {type PerspectiveHandle, type ReleasePerspective} from '../config/sanityConfig'
|
|
6
6
|
import {createSanityInstance, type SanityInstance} from '../store/createSanityInstance'
|
|
7
|
-
import {type StateSource} from '../store/createStateSourceAction'
|
|
8
7
|
import {getPerspectiveState} from './getPerspectiveState'
|
|
9
|
-
import {
|
|
8
|
+
import {observeReleases} from './observeReleases'
|
|
10
9
|
|
|
11
|
-
vi.mock('
|
|
10
|
+
vi.mock('./observeReleases')
|
|
12
11
|
|
|
13
12
|
vi.mock('../client/clientStore', () => ({
|
|
14
13
|
getClientState: vi.fn(() => ({
|
|
@@ -39,17 +38,12 @@ describe('getPerspectiveState', () => {
|
|
|
39
38
|
|
|
40
39
|
// the release store is reversed in getActiveReleases to match UI elsewhere
|
|
41
40
|
const activeReleases = [release2, release1]
|
|
42
|
-
const resource: ResourceHandle['resource'] = {projectId: 'p', dataset: 'd'}
|
|
43
41
|
|
|
44
42
|
beforeEach(() => {
|
|
45
|
-
instance = createSanityInstance()
|
|
43
|
+
instance = createSanityInstance({projectId: 'test', dataset: 'test'})
|
|
46
44
|
|
|
47
45
|
mockReleasesQuerySubject = new Subject<ReleaseDocument[] | undefined>()
|
|
48
|
-
vi.mocked(
|
|
49
|
-
subscribe: () => () => {},
|
|
50
|
-
getCurrent: () => undefined,
|
|
51
|
-
observable: mockReleasesQuerySubject.asObservable(),
|
|
52
|
-
} as StateSource<ReleaseDocument[] | undefined>)
|
|
46
|
+
vi.mocked(observeReleases).mockReturnValue(mockReleasesQuerySubject.asObservable())
|
|
53
47
|
})
|
|
54
48
|
|
|
55
49
|
afterEach(() => {
|
|
@@ -58,7 +52,7 @@ describe('getPerspectiveState', () => {
|
|
|
58
52
|
})
|
|
59
53
|
|
|
60
54
|
it('should return default perspective if no options or instance perspective is provided', async () => {
|
|
61
|
-
const state = getPerspectiveState(instance
|
|
55
|
+
const state = getPerspectiveState(instance)
|
|
62
56
|
mockReleasesQuerySubject.next([])
|
|
63
57
|
const perspective = await firstValueFrom(state.observable)
|
|
64
58
|
expect(perspective).toBe('drafts')
|
|
@@ -66,14 +60,14 @@ describe('getPerspectiveState', () => {
|
|
|
66
60
|
|
|
67
61
|
it('should return instance perspective if provided and no options perspective', async () => {
|
|
68
62
|
instance.config.perspective = 'published'
|
|
69
|
-
const state = getPerspectiveState(instance
|
|
63
|
+
const state = getPerspectiveState(instance)
|
|
70
64
|
mockReleasesQuerySubject.next([])
|
|
71
65
|
const perspective = await firstValueFrom(state.observable)
|
|
72
66
|
expect(perspective).toBe('published')
|
|
73
67
|
})
|
|
74
68
|
|
|
75
69
|
it('should return options perspective if provided', async () => {
|
|
76
|
-
const options = {perspective: 'raw'
|
|
70
|
+
const options: PerspectiveHandle = {perspective: 'raw'}
|
|
77
71
|
const state = getPerspectiveState(instance, options)
|
|
78
72
|
mockReleasesQuerySubject.next([])
|
|
79
73
|
const perspective = await firstValueFrom(state.observable)
|
|
@@ -81,7 +75,7 @@ describe('getPerspectiveState', () => {
|
|
|
81
75
|
})
|
|
82
76
|
|
|
83
77
|
it('should return undefined if release perspective is requested but no active releases', async () => {
|
|
84
|
-
const options = {perspective: {releaseName: 'release1'}
|
|
78
|
+
const options: PerspectiveHandle = {perspective: {releaseName: 'release1'}}
|
|
85
79
|
const state = getPerspectiveState(instance, options)
|
|
86
80
|
mockReleasesQuerySubject.next([])
|
|
87
81
|
const perspective = await firstValueFrom(state.observable)
|
|
@@ -89,7 +83,7 @@ describe('getPerspectiveState', () => {
|
|
|
89
83
|
})
|
|
90
84
|
|
|
91
85
|
it('should calculate perspective based on active releases and releaseName', async () => {
|
|
92
|
-
const options = {perspective: {releaseName: 'release1'}
|
|
86
|
+
const options: PerspectiveHandle = {perspective: {releaseName: 'release1'}}
|
|
93
87
|
const state = getPerspectiveState(instance, options)
|
|
94
88
|
mockReleasesQuerySubject.next(activeReleases)
|
|
95
89
|
|
|
@@ -103,7 +97,7 @@ describe('getPerspectiveState', () => {
|
|
|
103
97
|
})
|
|
104
98
|
|
|
105
99
|
it('should calculate perspective including multiple releases up to the specified releaseName', async () => {
|
|
106
|
-
const options = {perspective: {releaseName: 'release2'}
|
|
100
|
+
const options: PerspectiveHandle = {perspective: {releaseName: 'release2'}}
|
|
107
101
|
const state = getPerspectiveState(instance, options)
|
|
108
102
|
mockReleasesQuerySubject.next(activeReleases)
|
|
109
103
|
const perspective = await firstValueFrom(
|
|
@@ -120,7 +114,7 @@ describe('getPerspectiveState', () => {
|
|
|
120
114
|
releaseName: 'release2',
|
|
121
115
|
excludedPerspectives: ['release1', 'drafts'],
|
|
122
116
|
}
|
|
123
|
-
const options = {perspective: perspectiveConfig
|
|
117
|
+
const options: PerspectiveHandle = {perspective: perspectiveConfig}
|
|
124
118
|
const state = getPerspectiveState(instance, options)
|
|
125
119
|
mockReleasesQuerySubject.next(activeReleases)
|
|
126
120
|
const perspective = await firstValueFrom(
|
|
@@ -133,7 +127,7 @@ describe('getPerspectiveState', () => {
|
|
|
133
127
|
})
|
|
134
128
|
|
|
135
129
|
it('should throw if the specified releaseName is not found in active releases', async () => {
|
|
136
|
-
const options = {perspective: {releaseName: 'nonexistent'}
|
|
130
|
+
const options: PerspectiveHandle = {perspective: {releaseName: 'nonexistent'}}
|
|
137
131
|
const state = getPerspectiveState(instance, options)
|
|
138
132
|
mockReleasesQuerySubject.next(activeReleases)
|
|
139
133
|
|
|
@@ -148,8 +142,8 @@ describe('getPerspectiveState', () => {
|
|
|
148
142
|
})
|
|
149
143
|
|
|
150
144
|
it('should reuse the same options object for identical inputs (cache test)', async () => {
|
|
151
|
-
const options1 = {perspective: {releaseName: 'release1'}
|
|
152
|
-
const options2 = {perspective: {releaseName: 'release1'}
|
|
145
|
+
const options1: PerspectiveHandle = {perspective: {releaseName: 'release1'}}
|
|
146
|
+
const options2: PerspectiveHandle = {perspective: {releaseName: 'release1'}}
|
|
153
147
|
|
|
154
148
|
const state1 = getPerspectiveState(instance, options1)
|
|
155
149
|
mockReleasesQuerySubject.next(activeReleases)
|
|
@@ -167,7 +161,7 @@ describe('getPerspectiveState', () => {
|
|
|
167
161
|
})
|
|
168
162
|
|
|
169
163
|
it('should handle changes in activeReleases (cache test)', async () => {
|
|
170
|
-
const options = {perspective: {releaseName: 'release1'}
|
|
164
|
+
const options: PerspectiveHandle = {perspective: {releaseName: 'release1'}}
|
|
171
165
|
|
|
172
166
|
const state1 = getPerspectiveState(instance, options)
|
|
173
167
|
mockReleasesQuerySubject.next(activeReleases)
|
|
@@ -3,14 +3,6 @@ import {createSelector} from 'reselect'
|
|
|
3
3
|
import {type DocumentResource, type PerspectiveHandle} from '../config/sanityConfig'
|
|
4
4
|
import {bindActionByResource, type BoundStoreAction} from '../store/createActionBinder'
|
|
5
5
|
import {createStateSourceAction, type SelectorContext} from '../store/createStateSourceAction'
|
|
6
|
-
/*
|
|
7
|
-
* Although this is an import dependency cycle, it is not a logical cycle:
|
|
8
|
-
* 1. getPerspectiveState uses releasesStore as a data source
|
|
9
|
-
* 2. releasesStore uses queryStore as a data source
|
|
10
|
-
* 3. queryStore calls getPerspectiveState for computing release perspectives
|
|
11
|
-
* 4. however, queryStore does not use getPerspectiveState for the perspective used in releasesStore ("raw")
|
|
12
|
-
*/
|
|
13
|
-
// eslint-disable-next-line import/no-cycle
|
|
14
6
|
import {releasesStore, type ReleasesStoreState} from './releasesStore'
|
|
15
7
|
import {isReleasePerspective} from './utils/isReleasePerspective'
|
|
16
8
|
import {sortReleases} from './utils/sortReleases'
|
|
@@ -26,7 +18,7 @@ const selectActiveReleases = (context: SelectorContext<ReleasesStoreState>) =>
|
|
|
26
18
|
context.state.activeReleases
|
|
27
19
|
const selectOptions = (
|
|
28
20
|
_context: SelectorContext<ReleasesStoreState>,
|
|
29
|
-
options: PerspectiveHandle & {resource?: DocumentResource},
|
|
21
|
+
options: PerspectiveHandle & {projectId?: string; dataset?: string; resource?: DocumentResource},
|
|
30
22
|
) => options
|
|
31
23
|
|
|
32
24
|
const memoizedOptionsSelector = createSelector(
|
|
@@ -53,7 +45,6 @@ const memoizedOptionsSelector = createSelector(
|
|
|
53
45
|
},
|
|
54
46
|
)
|
|
55
47
|
|
|
56
|
-
// Lazily bind the action itself to avoid circular import initialization issues with `releasesStore`
|
|
57
48
|
const _getPerspectiveStateSelector = createStateSourceAction({
|
|
58
49
|
selector: createSelector(
|
|
59
50
|
[selectInstancePerspective, selectActiveReleases, memoizedOptionsSelector],
|
|
@@ -89,7 +80,10 @@ type BoundGetPerspectiveState = BoundStoreAction<
|
|
|
89
80
|
ReturnType<typeof _getPerspectiveStateSelector>
|
|
90
81
|
>
|
|
91
82
|
|
|
92
|
-
|
|
83
|
+
const _boundGetPerspectiveState = bindActionByResource(
|
|
84
|
+
releasesStore,
|
|
85
|
+
_getPerspectiveStateSelector,
|
|
86
|
+
) as BoundGetPerspectiveState
|
|
93
87
|
|
|
94
88
|
/**
|
|
95
89
|
* Provides a subscribable state source for a "perspective" for the Sanity client,
|
|
@@ -103,12 +97,6 @@ let _boundGetPerspectiveState: BoundGetPerspectiveState | undefined
|
|
|
103
97
|
*
|
|
104
98
|
* @public
|
|
105
99
|
*/
|
|
106
|
-
export const getPerspectiveState: BoundGetPerspectiveState = (instance, ...rest) =>
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
releasesStore,
|
|
110
|
-
_getPerspectiveStateSelector,
|
|
111
|
-
) as BoundGetPerspectiveState
|
|
112
|
-
}
|
|
113
|
-
return _boundGetPerspectiveState(instance, ...rest)
|
|
114
|
-
}
|
|
100
|
+
export const getPerspectiveState: BoundGetPerspectiveState = (instance, ...rest) =>
|
|
101
|
+
// bindActionByResource keyFn destructures { resource } from the first param, so pass {} when no options
|
|
102
|
+
_boundGetPerspectiveState(instance, ...(rest.length ? rest : [{}]))
|