@sanity/sdk 3.0.0-rc.0 → 3.0.0-rc.1
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/_chunks-dts/createGroqSearchFilter.d.ts +1147 -0
- package/dist/_chunks-dts/createGroqSearchFilter.d.ts.map +1 -0
- package/dist/_chunks-es/_internal.js +134 -0
- package/dist/_chunks-es/_internal.js.map +1 -0
- package/dist/_chunks-es/createGroqSearchFilter.js +1864 -0
- package/dist/_chunks-es/createGroqSearchFilter.js.map +1 -0
- package/dist/_chunks-es/telemetryManager.js +93 -0
- package/dist/_chunks-es/telemetryManager.js.map +1 -0
- package/dist/_exports/_internal.d.ts +95 -0
- package/dist/_exports/_internal.d.ts.map +1 -0
- package/dist/_exports/_internal.js +24 -0
- package/dist/_exports/_internal.js.map +1 -0
- package/dist/index.d.ts +1079 -1076
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1967 -2302
- package/dist/index.js.map +1 -1
- package/package.json +48 -47
- package/src/_exports/_internal.ts +26 -0
- package/src/_exports/index.ts +120 -11
- 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 +83 -0
- package/src/applications/applications.test.ts +130 -0
- package/src/applications/applications.ts +364 -0
- package/src/applications/userApplications.test.ts +126 -0
- package/src/applications/userApplications.ts +261 -0
- package/src/auth/authLogger.ts +30 -0
- package/src/auth/authMode.test.ts +25 -0
- package/src/auth/authMode.ts +6 -3
- package/src/auth/authStore.test.ts +194 -13
- package/src/auth/authStore.ts +59 -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/config/__tests__/handles.test.ts +18 -12
- package/src/config/handles.ts +25 -7
- package/src/config/sanityConfig.ts +109 -79
- 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 +38 -12
- package/src/document/applyDocumentActions.ts +30 -9
- package/src/document/documentConstants.ts +7 -0
- package/src/document/documentStore.concurrency.test.ts +1306 -0
- package/src/document/documentStore.test.ts +673 -278
- package/src/document/documentStore.ts +162 -71
- 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 +49 -2
- package/src/document/patchOperations.ts +50 -0
- package/src/document/permissions.test.ts +105 -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 +639 -64
- package/src/document/reducers.ts +187 -48
- 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.ts +14 -0
- package/src/favorites/favorites.test.ts +5 -6
- package/src/installations/installations.test-d.ts +68 -0
- package/src/installations/installations.test.ts +62 -0
- package/src/installations/installations.ts +202 -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 +46 -6
- package/src/presence/bifurTransport.ts +19 -2
- package/src/presence/presenceStore.test.ts +94 -23
- package/src/presence/presenceStore.ts +85 -29
- package/src/preview/getPreviewState.test.ts +137 -0
- package/src/preview/getPreviewState.ts +69 -0
- package/src/preview/previewProjectionUtils.test.ts +9 -22
- package/src/preview/previewProjectionUtils.ts +7 -3
- package/src/preview/resolvePreview.test.ts +64 -0
- package/src/preview/resolvePreview.ts +43 -0
- package/src/preview/types.ts +10 -0
- 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 -1
- 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/projection/types.ts +8 -0
- 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 +78 -63
- 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 +87 -90
- package/src/store/createActionBinder.ts +79 -25
- package/src/store/createSanityInstance.test.ts +83 -13
- package/src/store/createSanityInstance.ts +77 -2
- package/src/store/createStateSourceAction.test.ts +64 -2
- package/src/store/createStateSourceAction.ts +39 -44
- 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 +6 -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,20 +1,45 @@
|
|
|
1
1
|
import {createSelector} from 'reselect'
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
catchError,
|
|
4
|
+
combineLatest,
|
|
5
|
+
distinctUntilChanged,
|
|
6
|
+
EMPTY,
|
|
7
|
+
filter,
|
|
8
|
+
first,
|
|
9
|
+
map,
|
|
10
|
+
type Observable,
|
|
11
|
+
of,
|
|
12
|
+
Subscription,
|
|
13
|
+
switchMap,
|
|
14
|
+
} from 'rxjs'
|
|
3
15
|
|
|
4
16
|
import {getTokenState} from '../auth/authStore'
|
|
5
17
|
import {getClient} from '../client/clientStore'
|
|
6
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
type DocumentResource,
|
|
20
|
+
isCanvasResource,
|
|
21
|
+
isDatasetResource,
|
|
22
|
+
isMediaLibraryResource,
|
|
23
|
+
} from '../config/sanityConfig'
|
|
7
24
|
import {bindActionByResource, type BoundResourceKey} from '../store/createActionBinder'
|
|
8
|
-
import {
|
|
25
|
+
import {type SanityInstance} from '../store/createSanityInstance'
|
|
26
|
+
import {
|
|
27
|
+
createStateSourceAction,
|
|
28
|
+
type SelectorContext,
|
|
29
|
+
type StateSource,
|
|
30
|
+
} from '../store/createStateSourceAction'
|
|
9
31
|
import {defineStore, type StoreContext} from '../store/defineStore'
|
|
10
32
|
import {type SanityUser} from '../users/types'
|
|
11
33
|
import {getUserState} from '../users/usersStore'
|
|
12
34
|
import {createBifurTransport} from './bifurTransport'
|
|
13
35
|
import {type PresenceLocation, type TransportEvent, type UserPresence} from './types'
|
|
14
36
|
|
|
37
|
+
const PRESENCE_API_VERSION = '2026-03-30'
|
|
38
|
+
|
|
15
39
|
type PresenceStoreState = {
|
|
16
40
|
locations: Map<string, {userId: string; locations: PresenceLocation[]}>
|
|
17
41
|
users: Record<string, SanityUser | undefined>
|
|
42
|
+
organizationId?: string
|
|
18
43
|
}
|
|
19
44
|
|
|
20
45
|
const getInitialState = (): PresenceStoreState => ({
|
|
@@ -33,25 +58,25 @@ export const presenceStore = defineStore<PresenceStoreState, BoundResourceKey>({
|
|
|
33
58
|
key: {resource},
|
|
34
59
|
} = context
|
|
35
60
|
|
|
36
|
-
// Presence is only supported for dataset resources
|
|
37
61
|
if (isMediaLibraryResource(resource)) {
|
|
38
|
-
throw new Error(
|
|
39
|
-
'Presence is not supported for media library resources. Presence tracking requires a dataset resource with a projectId.',
|
|
40
|
-
)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
if (isCanvasResource(resource)) {
|
|
44
|
-
throw new Error(
|
|
45
|
-
'Presence is not supported for canvas resources. Presence tracking requires a dataset resource with a projectId.',
|
|
46
|
-
)
|
|
62
|
+
throw new Error('Presence is not supported for media library resources.')
|
|
47
63
|
}
|
|
48
64
|
|
|
49
65
|
const sessionId = crypto.randomUUID()
|
|
50
66
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
67
|
+
// Dataset resources must use the project hostname so the socket URL is project-specific.
|
|
68
|
+
// Canvas resources use the global API endpoint via the resource config.
|
|
69
|
+
const client = isDatasetResource(resource)
|
|
70
|
+
? getClient(instance, {
|
|
71
|
+
apiVersion: PRESENCE_API_VERSION,
|
|
72
|
+
projectId: resource.projectId,
|
|
73
|
+
dataset: resource.dataset,
|
|
74
|
+
useProjectHostname: true,
|
|
75
|
+
})
|
|
76
|
+
: getClient(instance, {
|
|
77
|
+
apiVersion: PRESENCE_API_VERSION,
|
|
78
|
+
resource,
|
|
79
|
+
})
|
|
55
80
|
|
|
56
81
|
const token$ = getTokenState(instance).observable.pipe(distinctUntilChanged())
|
|
57
82
|
|
|
@@ -94,6 +119,22 @@ export const presenceStore = defineStore<PresenceStoreState, BoundResourceKey>({
|
|
|
94
119
|
|
|
95
120
|
dispatch({type: 'rollCall'}).subscribe()
|
|
96
121
|
|
|
122
|
+
// Canvas resources need the organizationId to resolve users — fetch it once from the canvas endpoint
|
|
123
|
+
if (isCanvasResource(resource)) {
|
|
124
|
+
const globalClient = getClient(instance, {apiVersion: PRESENCE_API_VERSION})
|
|
125
|
+
subscription.add(
|
|
126
|
+
globalClient.observable
|
|
127
|
+
.request<{organizationId: string}>({
|
|
128
|
+
uri: `/canvases/${resource.canvasId}`,
|
|
129
|
+
tag: 'canvases.get',
|
|
130
|
+
})
|
|
131
|
+
.pipe(catchError(() => EMPTY))
|
|
132
|
+
.subscribe(({organizationId}) => {
|
|
133
|
+
state.set('presence/organizationId', (prev) => ({...prev, organizationId}))
|
|
134
|
+
}),
|
|
135
|
+
)
|
|
136
|
+
}
|
|
137
|
+
|
|
97
138
|
return () => {
|
|
98
139
|
dispatch({type: 'disconnect'}).subscribe()
|
|
99
140
|
subscription.unsubscribe()
|
|
@@ -127,13 +168,13 @@ const selectPresence = createSelector(
|
|
|
127
168
|
},
|
|
128
169
|
)
|
|
129
170
|
|
|
130
|
-
|
|
131
|
-
export const getPresence = bindActionByResource(
|
|
171
|
+
const _getPresence = bindActionByResource(
|
|
132
172
|
presenceStore,
|
|
133
173
|
createStateSourceAction({
|
|
134
174
|
selector: (context: SelectorContext<PresenceStoreState>): UserPresence[] =>
|
|
135
175
|
selectPresence(context.state),
|
|
136
176
|
onSubscribe: (context: StoreContext<PresenceStoreState, BoundResourceKey>) => {
|
|
177
|
+
const resource = context.key.resource
|
|
137
178
|
const userIds$ = context.state.observable.pipe(
|
|
138
179
|
map((state) =>
|
|
139
180
|
Array.from(state.locations.values())
|
|
@@ -143,29 +184,34 @@ export const getPresence = bindActionByResource(
|
|
|
143
184
|
distinctUntilChanged((a, b) => a.length === b.length && a.every((v, i) => v === b[i])),
|
|
144
185
|
)
|
|
145
186
|
|
|
146
|
-
|
|
187
|
+
// For canvas resources, wait for organizationId to be fetched and stored in state.
|
|
188
|
+
// For dataset resources, emit undefined immediately so the stream isn't blocked.
|
|
189
|
+
const organizationId$: Observable<string | undefined> = isCanvasResource(resource)
|
|
190
|
+
? context.state.observable.pipe(
|
|
191
|
+
map((s) => s.organizationId),
|
|
192
|
+
filter((id): id is string => id !== undefined),
|
|
193
|
+
first(),
|
|
194
|
+
)
|
|
195
|
+
: of(undefined)
|
|
196
|
+
|
|
197
|
+
const subscription = combineLatest([userIds$, organizationId$])
|
|
147
198
|
.pipe(
|
|
148
|
-
switchMap((userIds) => {
|
|
199
|
+
switchMap(([userIds, organizationId]) => {
|
|
149
200
|
if (userIds.length === 0) {
|
|
150
201
|
return of([])
|
|
151
202
|
}
|
|
152
203
|
const userObservables = userIds.map((userId) =>
|
|
153
204
|
getUserState(context.instance, {
|
|
154
205
|
userId,
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
? context.key.resource.projectId
|
|
159
|
-
: undefined,
|
|
206
|
+
...(isDatasetResource(resource)
|
|
207
|
+
? {resourceType: 'project', projectId: resource.projectId}
|
|
208
|
+
: {resourceType: 'organization', organizationId}),
|
|
160
209
|
}).pipe(filter((v): v is NonNullable<typeof v> => !!v)),
|
|
161
210
|
)
|
|
162
211
|
return combineLatest(userObservables)
|
|
163
212
|
}),
|
|
164
213
|
)
|
|
165
214
|
.subscribe((users) => {
|
|
166
|
-
if (!users) {
|
|
167
|
-
return
|
|
168
|
-
}
|
|
169
215
|
context.state.set('presence/users', (prevState) => ({
|
|
170
216
|
...prevState,
|
|
171
217
|
users: {
|
|
@@ -183,3 +229,13 @@ export const getPresence = bindActionByResource(
|
|
|
183
229
|
},
|
|
184
230
|
}),
|
|
185
231
|
)
|
|
232
|
+
|
|
233
|
+
/** @beta */
|
|
234
|
+
export function getPresence(
|
|
235
|
+
instance: SanityInstance,
|
|
236
|
+
params?: {resource?: DocumentResource},
|
|
237
|
+
): StateSource<UserPresence[]> {
|
|
238
|
+
// bit of a hack to support the old bound action by dataset
|
|
239
|
+
// in reality, this will always be passed a resource
|
|
240
|
+
return _getPresence(instance, params ?? {})
|
|
241
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import {of} from 'rxjs'
|
|
2
|
+
import {beforeEach, describe, expect, it, vi} from 'vitest'
|
|
3
|
+
|
|
4
|
+
import {getProjectionState} from '../projection/getProjectionState'
|
|
5
|
+
import {type ProjectionValuePending} from '../projection/types'
|
|
6
|
+
import {createSanityInstance} from '../store/createSanityInstance'
|
|
7
|
+
import {type StateSource} from '../store/createStateSourceAction'
|
|
8
|
+
import {getPreviewState} from './getPreviewState'
|
|
9
|
+
import {type PreviewQueryResult} from './types'
|
|
10
|
+
|
|
11
|
+
vi.mock('../projection/getProjectionState')
|
|
12
|
+
|
|
13
|
+
describe('getPreviewState', () => {
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
vi.clearAllMocks()
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it('transforms projection result to preview format', () => {
|
|
19
|
+
const mockProjectionResult: PreviewQueryResult = {
|
|
20
|
+
_id: 'doc1',
|
|
21
|
+
_type: 'article',
|
|
22
|
+
_updatedAt: '2024-01-01',
|
|
23
|
+
titleCandidates: {title: 'Test Title'},
|
|
24
|
+
subtitleCandidates: {description: 'Test Description'},
|
|
25
|
+
media: null,
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const mockProjectionState = {
|
|
29
|
+
getCurrent: vi.fn().mockReturnValue({
|
|
30
|
+
data: mockProjectionResult,
|
|
31
|
+
isPending: false,
|
|
32
|
+
} as ProjectionValuePending<PreviewQueryResult>),
|
|
33
|
+
subscribe: vi.fn(),
|
|
34
|
+
observable: of({
|
|
35
|
+
data: mockProjectionResult,
|
|
36
|
+
isPending: false,
|
|
37
|
+
} as ProjectionValuePending<PreviewQueryResult>),
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
vi.mocked(getProjectionState).mockReturnValue(
|
|
41
|
+
mockProjectionState as unknown as StateSource<
|
|
42
|
+
ProjectionValuePending<Record<string, unknown>> | undefined
|
|
43
|
+
>,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
const instance = createSanityInstance({
|
|
47
|
+
projectId: 'test-project',
|
|
48
|
+
dataset: 'test-dataset',
|
|
49
|
+
})
|
|
50
|
+
const previewState = getPreviewState(instance, {
|
|
51
|
+
documentId: 'doc1',
|
|
52
|
+
documentType: 'article',
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
const result = previewState.getCurrent()
|
|
56
|
+
|
|
57
|
+
expect(result.data).toEqual({
|
|
58
|
+
title: 'Test Title',
|
|
59
|
+
subtitle: 'Test Description',
|
|
60
|
+
media: null,
|
|
61
|
+
})
|
|
62
|
+
expect(result.isPending).toBe(false)
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('returns null data when projection result is null', () => {
|
|
66
|
+
const mockProjectionState = {
|
|
67
|
+
getCurrent: vi.fn().mockReturnValue({
|
|
68
|
+
data: null,
|
|
69
|
+
isPending: true,
|
|
70
|
+
}),
|
|
71
|
+
subscribe: vi.fn(),
|
|
72
|
+
observable: of({data: null, isPending: true}),
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
vi.mocked(getProjectionState).mockReturnValue(mockProjectionState)
|
|
76
|
+
|
|
77
|
+
const instance = createSanityInstance({
|
|
78
|
+
projectId: 'test-project',
|
|
79
|
+
dataset: 'test-dataset',
|
|
80
|
+
})
|
|
81
|
+
const previewState = getPreviewState(instance, {
|
|
82
|
+
documentId: 'doc1',
|
|
83
|
+
documentType: 'article',
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
const result = previewState.getCurrent()
|
|
87
|
+
|
|
88
|
+
expect(result.data).toBeNull()
|
|
89
|
+
expect(result.isPending).toBe(true)
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('uses fallback title when no title candidates exist', () => {
|
|
93
|
+
const mockProjectionResult: PreviewQueryResult = {
|
|
94
|
+
_id: 'doc1',
|
|
95
|
+
_type: 'article',
|
|
96
|
+
_updatedAt: '2024-01-01',
|
|
97
|
+
titleCandidates: {},
|
|
98
|
+
subtitleCandidates: {},
|
|
99
|
+
media: null,
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const mockProjectionState = {
|
|
103
|
+
getCurrent: vi.fn().mockReturnValue({
|
|
104
|
+
data: mockProjectionResult,
|
|
105
|
+
isPending: false,
|
|
106
|
+
} as ProjectionValuePending<PreviewQueryResult>),
|
|
107
|
+
subscribe: vi.fn(),
|
|
108
|
+
observable: of({
|
|
109
|
+
data: mockProjectionResult,
|
|
110
|
+
isPending: false,
|
|
111
|
+
} as ProjectionValuePending<PreviewQueryResult>),
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
vi.mocked(getProjectionState).mockReturnValue(
|
|
115
|
+
mockProjectionState as unknown as StateSource<
|
|
116
|
+
ProjectionValuePending<Record<string, unknown>> | undefined
|
|
117
|
+
>,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
const instance = createSanityInstance({
|
|
121
|
+
projectId: 'test-project',
|
|
122
|
+
dataset: 'test-dataset',
|
|
123
|
+
})
|
|
124
|
+
const previewState = getPreviewState(instance, {
|
|
125
|
+
documentId: 'doc1',
|
|
126
|
+
documentType: 'article',
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
const result = previewState.getCurrent()
|
|
130
|
+
|
|
131
|
+
expect(result.data).toEqual({
|
|
132
|
+
title: 'article: doc1',
|
|
133
|
+
subtitle: undefined,
|
|
134
|
+
media: null,
|
|
135
|
+
})
|
|
136
|
+
})
|
|
137
|
+
})
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import {map} from 'rxjs'
|
|
2
|
+
|
|
3
|
+
import {type DocumentHandle} from '../config/sanityConfig'
|
|
4
|
+
import {getProjectionState} from '../projection/getProjectionState'
|
|
5
|
+
import {type SanityInstance} from '../store/createSanityInstance'
|
|
6
|
+
import {type StateSource} from '../store/createStateSourceAction'
|
|
7
|
+
import {PREVIEW_PROJECTION} from './previewConstants'
|
|
8
|
+
import {transformProjectionToPreview} from './previewProjectionUtils'
|
|
9
|
+
import {type PreviewQueryResult, type PreviewValue, type ValuePending} from './types'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @beta
|
|
13
|
+
* @deprecated This type is deprecated and will be removed in a future release.
|
|
14
|
+
*/
|
|
15
|
+
export type GetPreviewStateOptions = DocumentHandle
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @beta
|
|
19
|
+
* @deprecated This function is deprecated and will be removed in a future release.
|
|
20
|
+
*/
|
|
21
|
+
export function getPreviewState<TResult extends object>(
|
|
22
|
+
instance: SanityInstance,
|
|
23
|
+
options: GetPreviewStateOptions,
|
|
24
|
+
): StateSource<ValuePending<TResult>>
|
|
25
|
+
/**
|
|
26
|
+
* @beta
|
|
27
|
+
* @deprecated This function is deprecated and will be removed in a future release.
|
|
28
|
+
*/
|
|
29
|
+
export function getPreviewState(
|
|
30
|
+
instance: SanityInstance,
|
|
31
|
+
options: GetPreviewStateOptions,
|
|
32
|
+
): StateSource<ValuePending<PreviewValue>>
|
|
33
|
+
/**
|
|
34
|
+
* @beta
|
|
35
|
+
* @deprecated This function is deprecated and will be removed in a future release.
|
|
36
|
+
*/
|
|
37
|
+
export function getPreviewState(
|
|
38
|
+
instance: SanityInstance,
|
|
39
|
+
options: GetPreviewStateOptions,
|
|
40
|
+
): StateSource<ValuePending<PreviewValue>> {
|
|
41
|
+
// Get the projection state
|
|
42
|
+
const projectionState = getProjectionState<PreviewQueryResult>(instance, {
|
|
43
|
+
...options,
|
|
44
|
+
projection: PREVIEW_PROJECTION,
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
// Transform helper to convert projection result to preview format
|
|
48
|
+
const transformResult = (
|
|
49
|
+
current: ReturnType<typeof projectionState.getCurrent>,
|
|
50
|
+
): ValuePending<PreviewValue> => {
|
|
51
|
+
if (!current || current.data === null) {
|
|
52
|
+
return {data: null, isPending: current?.isPending ?? false}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const previewValue = transformProjectionToPreview(instance, current.data, options.resource)
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
data: previewValue,
|
|
59
|
+
isPending: current.isPending,
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Wrap the state source to transform projection results to preview format
|
|
64
|
+
return {
|
|
65
|
+
getCurrent: () => transformResult(projectionState.getCurrent()),
|
|
66
|
+
subscribe: (callback) => projectionState.subscribe(callback),
|
|
67
|
+
observable: projectionState.observable.pipe(map(transformResult)),
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -58,7 +58,10 @@ describe('normalizeMedia', () => {
|
|
|
58
58
|
})
|
|
59
59
|
|
|
60
60
|
describe('transformProjectionToPreview', () => {
|
|
61
|
-
const instance = createSanityInstance(
|
|
61
|
+
const instance = createSanityInstance({
|
|
62
|
+
projectId: 'test-project',
|
|
63
|
+
dataset: 'test-dataset',
|
|
64
|
+
})
|
|
62
65
|
|
|
63
66
|
beforeEach(async () => {
|
|
64
67
|
vi.clearAllMocks()
|
|
@@ -78,11 +81,7 @@ describe('transformProjectionToPreview', () => {
|
|
|
78
81
|
media: null,
|
|
79
82
|
}
|
|
80
83
|
|
|
81
|
-
const result = transformProjectionToPreview(
|
|
82
|
-
instance,
|
|
83
|
-
{projectId: 'p', dataset: 'd'},
|
|
84
|
-
projectionResult,
|
|
85
|
-
)
|
|
84
|
+
const result = transformProjectionToPreview(instance, projectionResult)
|
|
86
85
|
|
|
87
86
|
expect(result).toEqual({
|
|
88
87
|
title: 'My Title',
|
|
@@ -101,11 +100,7 @@ describe('transformProjectionToPreview', () => {
|
|
|
101
100
|
media: null,
|
|
102
101
|
}
|
|
103
102
|
|
|
104
|
-
const result = transformProjectionToPreview(
|
|
105
|
-
instance,
|
|
106
|
-
{projectId: 'p', dataset: 'd'},
|
|
107
|
-
projectionResult,
|
|
108
|
-
)
|
|
103
|
+
const result = transformProjectionToPreview(instance, projectionResult)
|
|
109
104
|
|
|
110
105
|
expect(result.title).toBe('article: doc1')
|
|
111
106
|
expect(result.subtitle).toBeUndefined()
|
|
@@ -121,11 +116,7 @@ describe('transformProjectionToPreview', () => {
|
|
|
121
116
|
media: {type: 'image-asset', _ref: 'image-abc123-200x200-png', url: ''},
|
|
122
117
|
}
|
|
123
118
|
|
|
124
|
-
const result = transformProjectionToPreview(
|
|
125
|
-
instance,
|
|
126
|
-
{projectId: 'p', dataset: 'd'},
|
|
127
|
-
projectionResult,
|
|
128
|
-
)
|
|
119
|
+
const result = transformProjectionToPreview(instance, projectionResult)
|
|
129
120
|
|
|
130
121
|
expect(result).toEqual({
|
|
131
122
|
title: 'My Title',
|
|
@@ -152,11 +143,7 @@ describe('transformProjectionToPreview', () => {
|
|
|
152
143
|
},
|
|
153
144
|
}
|
|
154
145
|
|
|
155
|
-
const result = transformProjectionToPreview(
|
|
156
|
-
instance,
|
|
157
|
-
{projectId: 'p', dataset: 'd'},
|
|
158
|
-
projectionResult,
|
|
159
|
-
)
|
|
146
|
+
const result = transformProjectionToPreview(instance, projectionResult)
|
|
160
147
|
|
|
161
148
|
expect(result).toEqual({
|
|
162
149
|
title: 'My Title',
|
|
@@ -181,7 +168,7 @@ describe('transformProjectionToPreview', () => {
|
|
|
181
168
|
|
|
182
169
|
const resource = {mediaLibraryId: 'test-library'}
|
|
183
170
|
|
|
184
|
-
transformProjectionToPreview(instance,
|
|
171
|
+
transformProjectionToPreview(instance, projectionResult, resource)
|
|
185
172
|
|
|
186
173
|
const {getClient} = await import('../client/clientStore')
|
|
187
174
|
expect(getClient).toHaveBeenCalledWith(instance, {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {type SanityClient} from '@sanity/client'
|
|
2
2
|
import {createImageUrlBuilder} from '@sanity/image-url'
|
|
3
|
-
import {isObject} from 'lodash-es'
|
|
4
3
|
|
|
5
4
|
import {getClient} from '../client/clientStore'
|
|
6
5
|
import {type DocumentResource} from '../config/sanityConfig'
|
|
7
6
|
import {type SanityInstance} from '../store/createSanityInstance'
|
|
7
|
+
import {isObject} from '../utils/object'
|
|
8
8
|
import {SUBTITLE_CANDIDATES, TITLE_CANDIDATES} from './previewConstants'
|
|
9
9
|
import {type PreviewQueryResult, type PreviewValue} from './types'
|
|
10
10
|
|
|
@@ -71,13 +71,17 @@ function findFirstDefined(
|
|
|
71
71
|
*/
|
|
72
72
|
export function transformProjectionToPreview(
|
|
73
73
|
instance: SanityInstance,
|
|
74
|
-
resource: DocumentResource,
|
|
75
74
|
projectionResult: PreviewQueryResult,
|
|
75
|
+
resource?: DocumentResource,
|
|
76
76
|
): PreviewValue {
|
|
77
77
|
const title = findFirstDefined(TITLE_CANDIDATES, projectionResult.titleCandidates)
|
|
78
78
|
const subtitle = findFirstDefined(SUBTITLE_CANDIDATES, projectionResult.subtitleCandidates, title)
|
|
79
79
|
|
|
80
|
-
|
|
80
|
+
// Get a client for the resource (if provided) or use the instance config
|
|
81
|
+
const client = getClient(instance, {
|
|
82
|
+
apiVersion: API_VERSION,
|
|
83
|
+
resource,
|
|
84
|
+
})
|
|
81
85
|
|
|
82
86
|
return {
|
|
83
87
|
title: String(title || `${projectionResult._type}: ${projectionResult._id}`),
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import {beforeEach, describe, expect, it, vi} from 'vitest'
|
|
2
|
+
|
|
3
|
+
import {resolveProjection} from '../projection/resolveProjection'
|
|
4
|
+
import {type ProjectionValuePending} from '../projection/types'
|
|
5
|
+
import {createSanityInstance, type SanityInstance} from '../store/createSanityInstance'
|
|
6
|
+
import {resolvePreview} from './resolvePreview'
|
|
7
|
+
import {type PreviewQueryResult} from './types'
|
|
8
|
+
|
|
9
|
+
vi.mock('../projection/resolveProjection')
|
|
10
|
+
|
|
11
|
+
describe('resolvePreview', () => {
|
|
12
|
+
let instance: SanityInstance
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
vi.clearAllMocks()
|
|
15
|
+
instance = createSanityInstance({projectId: 'p', dataset: 'd'})
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
instance.dispose()
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('resolves and transforms projection result to preview format', async () => {
|
|
23
|
+
const mockProjectionResult: PreviewQueryResult = {
|
|
24
|
+
_id: 'doc1',
|
|
25
|
+
_type: 'article',
|
|
26
|
+
_updatedAt: '2024-01-01',
|
|
27
|
+
titleCandidates: {title: 'Resolved Title'},
|
|
28
|
+
subtitleCandidates: {description: 'Resolved Description'},
|
|
29
|
+
media: null,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
vi.mocked(resolveProjection).mockResolvedValue({
|
|
33
|
+
data: mockProjectionResult,
|
|
34
|
+
isPending: false,
|
|
35
|
+
} as ProjectionValuePending<PreviewQueryResult>)
|
|
36
|
+
|
|
37
|
+
const result = await resolvePreview(instance, {
|
|
38
|
+
documentId: 'doc1',
|
|
39
|
+
documentType: 'article',
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
expect(result.data).toEqual({
|
|
43
|
+
title: 'Resolved Title',
|
|
44
|
+
subtitle: 'Resolved Description',
|
|
45
|
+
media: null,
|
|
46
|
+
})
|
|
47
|
+
expect(result.isPending).toBe(false)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('returns null data when projection resolves with null', async () => {
|
|
51
|
+
vi.mocked(resolveProjection).mockResolvedValue({
|
|
52
|
+
data: null,
|
|
53
|
+
isPending: false,
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
const result = await resolvePreview(instance, {
|
|
57
|
+
documentId: 'doc1',
|
|
58
|
+
documentType: 'article',
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
expect(result.data).toBeNull()
|
|
62
|
+
expect(result.isPending).toBe(false)
|
|
63
|
+
})
|
|
64
|
+
})
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import {type DocumentHandle} from '../config/sanityConfig'
|
|
2
|
+
import {resolveProjection} from '../projection/resolveProjection'
|
|
3
|
+
import {type SanityInstance} from '../store/createSanityInstance'
|
|
4
|
+
import {PREVIEW_PROJECTION} from './previewConstants'
|
|
5
|
+
import {transformProjectionToPreview} from './previewProjectionUtils'
|
|
6
|
+
import {type PreviewQueryResult, type PreviewValue, type ValuePending} from './types'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @beta
|
|
10
|
+
* @deprecated This type is deprecated and will be removed in a future release.
|
|
11
|
+
*/
|
|
12
|
+
export type ResolvePreviewOptions = DocumentHandle
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @beta
|
|
16
|
+
* @deprecated This function is deprecated and will be removed in a future release.
|
|
17
|
+
*/
|
|
18
|
+
export async function resolvePreview(
|
|
19
|
+
instance: SanityInstance,
|
|
20
|
+
options: ResolvePreviewOptions,
|
|
21
|
+
): Promise<ValuePending<PreviewValue>> {
|
|
22
|
+
// Resolve the projection
|
|
23
|
+
const projectionResult = await resolveProjection<PreviewQueryResult>(instance, {
|
|
24
|
+
...options,
|
|
25
|
+
projection: PREVIEW_PROJECTION,
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
if (!projectionResult.data) {
|
|
29
|
+
return {data: null, isPending: projectionResult.isPending}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Transform to preview format
|
|
33
|
+
const previewValue = transformProjectionToPreview(
|
|
34
|
+
instance,
|
|
35
|
+
projectionResult.data,
|
|
36
|
+
options.resource,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
data: previewValue,
|
|
41
|
+
isPending: projectionResult.isPending,
|
|
42
|
+
}
|
|
43
|
+
}
|
package/src/preview/types.ts
CHANGED
|
@@ -74,3 +74,13 @@ export type ValuePending<T> = {
|
|
|
74
74
|
data: T | null
|
|
75
75
|
isPending: boolean
|
|
76
76
|
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* @public
|
|
80
|
+
* @deprecated This interface is kept for backwards compatibility but is no longer used internally.
|
|
81
|
+
* Preview state is now stored in the projection store.
|
|
82
|
+
*/
|
|
83
|
+
export interface PreviewStoreState {
|
|
84
|
+
values: {[TDocumentId in string]?: ValuePending<PreviewValue>}
|
|
85
|
+
subscriptions: {[TDocumentId in string]?: {[TSubscriptionId in string]?: true}}
|
|
86
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import {expectTypeOf, test} from 'vitest'
|
|
2
|
+
|
|
3
|
+
import {type SanityInstance} from '../store/createSanityInstance'
|
|
4
|
+
import {type StateSource} from '../store/createStateSourceAction'
|
|
5
|
+
import {type FetcherSnapshot} from '../store/fetcherStore'
|
|
6
|
+
import {type Project, project, type ProjectMember} from './project'
|
|
7
|
+
|
|
8
|
+
const instance = {} as SanityInstance
|
|
9
|
+
|
|
10
|
+
test('project.resolveState — resolves the wide-boolean detail shape', () => {
|
|
11
|
+
expectTypeOf(project.resolveState(instance, {projectId: 'p'})).resolves.toEqualTypeOf<
|
|
12
|
+
Project<boolean, boolean>
|
|
13
|
+
>()
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
test('Project — literal flags narrow members and features', () => {
|
|
17
|
+
expectTypeOf<Project<true, true>['members']>().toEqualTypeOf<ProjectMember[]>()
|
|
18
|
+
expectTypeOf<Project<true, true>['features']>().toEqualTypeOf<string[]>()
|
|
19
|
+
expectTypeOf<keyof Project<false, false> & ('members' | 'features')>().toEqualTypeOf<never>()
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
test('Project<boolean, boolean> — members and features are optional', () => {
|
|
23
|
+
type Item = Project<boolean, boolean>
|
|
24
|
+
expectTypeOf<Item['members']>().toEqualTypeOf<ProjectMember[] | undefined>()
|
|
25
|
+
expectTypeOf<Item['features']>().toEqualTypeOf<string[] | undefined>()
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
test('project.getState — exposes the snapshot envelope', () => {
|
|
29
|
+
expectTypeOf(project.getState(instance, {projectId: 'p'})).toEqualTypeOf<
|
|
30
|
+
StateSource<FetcherSnapshot<Project<boolean, boolean>>>
|
|
31
|
+
>()
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
test('project — rejects non-boolean flag values', () => {
|
|
35
|
+
// @ts-expect-error includeMembers must be a boolean
|
|
36
|
+
void project.resolveState(instance, {includeMembers: 'yes'})
|
|
37
|
+
})
|