@sanity/sdk 2.8.0 → 2.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_chunks-dts/utils.d.ts +2396 -0
- package/dist/_chunks-es/_internal.js +129 -0
- package/dist/_chunks-es/_internal.js.map +1 -0
- package/dist/_chunks-es/createGroqSearchFilter.js +1460 -0
- package/dist/_chunks-es/createGroqSearchFilter.js.map +1 -0
- package/dist/_chunks-es/telemetryManager.js +87 -0
- package/dist/_chunks-es/telemetryManager.js.map +1 -0
- package/dist/_chunks-es/version.js +7 -0
- package/dist/_chunks-es/version.js.map +1 -0
- package/dist/_exports/_internal.d.ts +64 -0
- package/dist/_exports/_internal.js +20 -0
- package/dist/_exports/_internal.js.map +1 -0
- package/dist/index.d.ts +2 -2343
- package/dist/index.js +383 -1777
- package/dist/index.js.map +1 -1
- package/package.json +11 -4
- package/src/_exports/_internal.ts +14 -0
- package/src/_exports/index.ts +10 -1
- package/src/auth/authStore.test.ts +150 -1
- package/src/auth/authStore.ts +11 -11
- package/src/auth/dashboardAuth.ts +2 -2
- package/src/auth/handleAuthCallback.ts +9 -3
- package/src/auth/logout.test.ts +1 -1
- package/src/auth/logout.ts +1 -1
- package/src/auth/refreshStampedToken.test.ts +118 -1
- package/src/auth/refreshStampedToken.ts +3 -2
- package/src/auth/standaloneAuth.ts +9 -3
- package/src/auth/studioAuth.ts +34 -7
- package/src/auth/studioModeAuth.ts +2 -1
- package/src/auth/subscribeToStateAndFetchCurrentUser.test.ts +10 -2
- package/src/auth/subscribeToStateAndFetchCurrentUser.ts +5 -1
- package/src/auth/subscribeToStorageEventsAndSetToken.ts +2 -2
- package/src/auth/utils.ts +33 -0
- package/src/client/clientStore.test.ts +14 -0
- package/src/client/clientStore.ts +2 -1
- package/src/comlink/node/getNodeState.ts +2 -1
- package/src/config/sanityConfig.ts +6 -0
- package/src/document/actions.ts +18 -11
- package/src/document/applyDocumentActions.test.ts +7 -6
- package/src/document/applyDocumentActions.ts +10 -4
- package/src/document/documentStore.test.ts +536 -188
- package/src/document/documentStore.ts +142 -76
- package/src/document/events.ts +7 -2
- package/src/document/permissions.test.ts +18 -16
- package/src/document/permissions.ts +35 -11
- package/src/document/processActions.test.ts +359 -32
- package/src/document/processActions.ts +104 -76
- package/src/document/reducers.test.ts +117 -29
- package/src/document/reducers.ts +43 -36
- package/src/document/sharedListener.ts +16 -6
- package/src/document/util.ts +14 -0
- package/src/favorites/favorites.test.ts +9 -2
- package/src/presence/bifurTransport.ts +6 -1
- package/src/preview/getPreviewState.test.ts +115 -98
- package/src/preview/getPreviewState.ts +38 -60
- package/src/preview/previewProjectionUtils.test.ts +179 -0
- package/src/preview/previewProjectionUtils.ts +93 -0
- package/src/preview/resolvePreview.test.ts +42 -25
- package/src/preview/resolvePreview.ts +29 -10
- package/src/preview/{previewStore.ts → types.ts} +8 -17
- package/src/projection/getProjectionState.test.ts +16 -16
- package/src/projection/getProjectionState.ts +2 -1
- package/src/projection/projectionQuery.ts +2 -3
- package/src/projection/types.ts +1 -1
- package/src/query/queryStore.ts +2 -1
- package/src/releases/getPerspectiveState.ts +7 -6
- package/src/releases/releasesStore.test.ts +20 -5
- package/src/releases/releasesStore.ts +20 -8
- package/src/store/createStateSourceAction.test.ts +62 -0
- package/src/store/createStateSourceAction.ts +34 -39
- package/src/telemetry/__telemetry__/sdk.telemetry.ts +42 -0
- package/src/telemetry/devMode.test.ts +52 -0
- package/src/telemetry/devMode.ts +40 -0
- package/src/telemetry/initTelemetry.test.ts +225 -0
- package/src/telemetry/initTelemetry.ts +205 -0
- package/src/telemetry/telemetryManager.test.ts +263 -0
- package/src/telemetry/telemetryManager.ts +187 -0
- package/src/users/usersStore.test.ts +1 -0
- package/src/users/usersStore.ts +5 -1
- package/src/utils/createFetcherStore.test.ts +6 -4
- package/src/utils/createFetcherStore.ts +2 -1
- 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/setCleanupTimeout.ts +24 -0
- package/src/preview/previewQuery.test.ts +0 -236
- package/src/preview/previewQuery.ts +0 -153
- package/src/preview/previewStore.test.ts +0 -36
- package/src/preview/subscribeToStateAndFetchBatches.test.ts +0 -221
- package/src/preview/subscribeToStateAndFetchBatches.ts +0 -112
- package/src/preview/util.ts +0 -13
package/src/document/reducers.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import {getPublishedId} from '@sanity/
|
|
1
|
+
import {DocumentId, getDraftId, getPublishedId, getVersionId} from '@sanity/id-utils'
|
|
2
2
|
import {type Mutation, type PatchOperations, type SanityDocumentLike} from '@sanity/types'
|
|
3
3
|
import {omit} from 'lodash-es'
|
|
4
4
|
|
|
5
|
+
import {type DocumentHandle} from '../config/sanityConfig'
|
|
6
|
+
import {isReleasePerspective} from '../releases/utils/isReleasePerspective'
|
|
5
7
|
import {type StoreContext} from '../store/defineStore'
|
|
6
|
-
import {
|
|
8
|
+
import {insecureRandomId} from '../utils/ids'
|
|
9
|
+
import {setCleanupTimeout} from '../utils/setCleanupTimeout'
|
|
7
10
|
import {type DocumentAction} from './actions'
|
|
8
11
|
import {DOCUMENT_STATE_CLEAR_DELAY} from './documentConstants'
|
|
9
12
|
import {type DocumentState, type DocumentStoreState} from './documentStore'
|
|
@@ -18,9 +21,13 @@ export type SyncTransactionState = Pick<
|
|
|
18
21
|
'queued' | 'applied' | 'documentStates' | 'outgoing' | 'grants'
|
|
19
22
|
>
|
|
20
23
|
|
|
24
|
+
type DocumentHandleLike = Pick<DocumentHandle, 'perspective'> & {
|
|
25
|
+
documentId?: string
|
|
26
|
+
liveEdit?: boolean
|
|
27
|
+
}
|
|
28
|
+
|
|
21
29
|
type ActionMap = {
|
|
22
30
|
create: 'sanity.action.document.version.create'
|
|
23
|
-
createLiveEdit: 'sanity.action.document.create'
|
|
24
31
|
discard: 'sanity.action.document.version.discard'
|
|
25
32
|
unpublish: 'sanity.action.document.unpublish'
|
|
26
33
|
delete: 'sanity.action.document.delete'
|
|
@@ -35,7 +42,6 @@ type OptimisticLock = {
|
|
|
35
42
|
|
|
36
43
|
export type HttpAction =
|
|
37
44
|
| {actionType: ActionMap['create']; publishedId: string; attributes: SanityDocumentLike}
|
|
38
|
-
| {actionType: ActionMap['createLiveEdit']; publishedId: string; attributes: SanityDocumentLike}
|
|
39
45
|
| {actionType: ActionMap['discard']; versionId: string; purge?: boolean}
|
|
40
46
|
| {actionType: ActionMap['unpublish']; draftId: string; publishedId: string}
|
|
41
47
|
| {actionType: ActionMap['delete']; publishedId: string; includeDrafts?: string[]}
|
|
@@ -115,9 +121,9 @@ export interface AppliedTransaction extends QueuedTransaction {
|
|
|
115
121
|
outgoingActions: HttpAction[]
|
|
116
122
|
|
|
117
123
|
/**
|
|
118
|
-
* similar to `outgoingActions` but comprised of mutations instead of
|
|
119
|
-
*
|
|
120
|
-
*
|
|
124
|
+
* similar to `outgoingActions` but comprised of mutations instead of actions.
|
|
125
|
+
* Useful for debugging, and is also used by liveEdit documents to send mutations,
|
|
126
|
+
* since they can't use the Actions API which is pretty dependent on the draft model.
|
|
121
127
|
*/
|
|
122
128
|
outgoingMutations: Mutation[]
|
|
123
129
|
}
|
|
@@ -145,7 +151,7 @@ export function queueTransaction(
|
|
|
145
151
|
transaction: QueuedTransaction,
|
|
146
152
|
): SyncTransactionState {
|
|
147
153
|
const {transactionId, actions} = transaction
|
|
148
|
-
const prevWithSubscriptionIds =
|
|
154
|
+
const prevWithSubscriptionIds = getDocumentIdsFromHandleLikes(actions).reduce(
|
|
149
155
|
(acc, id) => addSubscriptionIdToDocument(acc, id, transactionId),
|
|
150
156
|
prev,
|
|
151
157
|
)
|
|
@@ -163,7 +169,7 @@ export function removeQueuedTransaction(
|
|
|
163
169
|
const transaction = prev.queued.find((t) => t.transactionId === transactionId)
|
|
164
170
|
if (!transaction) return prev
|
|
165
171
|
|
|
166
|
-
const prevWithSubscriptionIds =
|
|
172
|
+
const prevWithSubscriptionIds = getDocumentIdsFromHandleLikes(transaction.actions).reduce(
|
|
167
173
|
(acc, id) => removeSubscriptionIdFromDocument(acc, id, transactionId),
|
|
168
174
|
prev,
|
|
169
175
|
)
|
|
@@ -179,7 +185,7 @@ export function applyFirstQueuedTransaction(prev: SyncTransactionState): SyncTra
|
|
|
179
185
|
if (!queued) return prev
|
|
180
186
|
if (!prev.grants) return prev
|
|
181
187
|
|
|
182
|
-
const ids =
|
|
188
|
+
const ids = getDocumentIdsFromHandleLikes(queued.actions)
|
|
183
189
|
// the local value is only ever `undefined` if it has not been loaded yet
|
|
184
190
|
// we can't get the next applied state unless all relevant documents are ready
|
|
185
191
|
if (ids.some((id) => prev.documentStates[id]?.local === undefined)) return prev
|
|
@@ -265,6 +271,9 @@ export function batchAppliedTransactions([curr, ...rest]: AppliedTransaction[]):
|
|
|
265
271
|
if (!next) return undefined
|
|
266
272
|
if (next.disableBatching) return editAction
|
|
267
273
|
|
|
274
|
+
// Don't batch a liveEdit edit with a non-liveEdit edit — they route to different APIs
|
|
275
|
+
if (!!action.liveEdit !== !!next.actions[0]?.liveEdit) return editAction
|
|
276
|
+
|
|
268
277
|
return {
|
|
269
278
|
disableBatching: false,
|
|
270
279
|
// Use the transactionId from the later (next) transaction.
|
|
@@ -337,7 +346,7 @@ export function cleanupOutgoingTransaction(prev: SyncTransactionState): SyncTran
|
|
|
337
346
|
if (!outgoing) return prev
|
|
338
347
|
|
|
339
348
|
let next = prev
|
|
340
|
-
const ids =
|
|
349
|
+
const ids = getDocumentIdsFromHandleLikes(outgoing.actions)
|
|
341
350
|
for (const transactionId of outgoing.batchedTransactionIds) {
|
|
342
351
|
for (const documentId of ids) {
|
|
343
352
|
next = removeSubscriptionIdFromDocument(next, documentId, transactionId)
|
|
@@ -551,22 +560,10 @@ export function removeSubscriptionIdFromDocument(
|
|
|
551
560
|
|
|
552
561
|
export function manageSubscriberIds(
|
|
553
562
|
{state}: StoreContext<SyncTransactionState>,
|
|
554
|
-
|
|
555
|
-
options?: {expandDraftPublished?: boolean},
|
|
563
|
+
handles: DocumentHandleLike[],
|
|
556
564
|
): () => void {
|
|
557
|
-
const
|
|
558
|
-
|
|
559
|
-
new Set(
|
|
560
|
-
expandDraftPublished
|
|
561
|
-
? (Array.isArray(documentId) ? documentId : [documentId]).flatMap((id) => [
|
|
562
|
-
getPublishedId(id),
|
|
563
|
-
getDraftId(id),
|
|
564
|
-
])
|
|
565
|
-
: Array.isArray(documentId)
|
|
566
|
-
? documentId
|
|
567
|
-
: [documentId],
|
|
568
|
-
),
|
|
569
|
-
)
|
|
565
|
+
const documentIds = getDocumentIdsFromHandleLikes(handles)
|
|
566
|
+
|
|
570
567
|
const subscriptionId = insecureRandomId()
|
|
571
568
|
state.set('addSubscribers', (prev) =>
|
|
572
569
|
documentIds.reduce(
|
|
@@ -576,7 +573,7 @@ export function manageSubscriberIds(
|
|
|
576
573
|
)
|
|
577
574
|
|
|
578
575
|
return () => {
|
|
579
|
-
|
|
576
|
+
setCleanupTimeout(() => {
|
|
580
577
|
state.set('removeSubscribers', (prev) =>
|
|
581
578
|
documentIds.reduce(
|
|
582
579
|
(acc, id) => removeSubscriptionIdFromDocument(acc, id, subscriptionId),
|
|
@@ -587,13 +584,23 @@ export function manageSubscriberIds(
|
|
|
587
584
|
}
|
|
588
585
|
}
|
|
589
586
|
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
)
|
|
598
|
-
|
|
587
|
+
// document handles are passed in via the public facing API, but we also need to
|
|
588
|
+
// pull the correct document ids from action bodies, which have similar but not
|
|
589
|
+
// identical shapes to the document handles.
|
|
590
|
+
function getDocumentIdsFromHandleLikes(handles: DocumentHandleLike[]): string[] {
|
|
591
|
+
return handles.flatMap((handle) => {
|
|
592
|
+
const idsForDocument = []
|
|
593
|
+
if (!handle.documentId) return []
|
|
594
|
+
if (handle.liveEdit) {
|
|
595
|
+
return [handle.documentId]
|
|
596
|
+
}
|
|
597
|
+
if (isReleasePerspective(handle.perspective)) {
|
|
598
|
+
idsForDocument.push(
|
|
599
|
+
getVersionId(DocumentId(handle.documentId), handle.perspective.releaseName),
|
|
600
|
+
)
|
|
601
|
+
}
|
|
602
|
+
idsForDocument.push(getPublishedId(DocumentId(handle.documentId)))
|
|
603
|
+
idsForDocument.push(getDraftId(DocumentId(handle.documentId)))
|
|
604
|
+
return idsForDocument
|
|
605
|
+
})
|
|
599
606
|
}
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
} from 'rxjs'
|
|
15
15
|
|
|
16
16
|
import {getClientState} from '../client/clientStore'
|
|
17
|
+
import {type DocumentSource, isDatasetSource} from '../config/sanityConfig'
|
|
17
18
|
import {type SanityInstance} from '../store/createSanityInstance'
|
|
18
19
|
|
|
19
20
|
const API_VERSION = 'v2025-05-06'
|
|
@@ -23,10 +24,15 @@ export interface SharedListener {
|
|
|
23
24
|
dispose: () => void
|
|
24
25
|
}
|
|
25
26
|
|
|
26
|
-
export function createSharedListener(
|
|
27
|
+
export function createSharedListener(
|
|
28
|
+
instance: SanityInstance,
|
|
29
|
+
source?: DocumentSource,
|
|
30
|
+
): SharedListener {
|
|
27
31
|
const dispose$ = new Subject<void>()
|
|
28
32
|
const events$ = getClientState(instance, {
|
|
29
33
|
apiVersion: API_VERSION,
|
|
34
|
+
// TODO: remove in v3 when we're ready for everything to be queried via source
|
|
35
|
+
source: source && !isDatasetSource(source) ? source : undefined,
|
|
30
36
|
}).observable.pipe(
|
|
31
37
|
switchMap((client) =>
|
|
32
38
|
// TODO: it seems like the client.listen method is not emitting disconnected
|
|
@@ -62,13 +68,17 @@ export function createSharedListener(instance: SanityInstance): SharedListener {
|
|
|
62
68
|
}
|
|
63
69
|
}
|
|
64
70
|
|
|
65
|
-
export function createFetchDocument(instance: SanityInstance) {
|
|
71
|
+
export function createFetchDocument(instance: SanityInstance, source?: DocumentSource) {
|
|
66
72
|
return function (documentId: string): Observable<SanityDocument | null> {
|
|
67
|
-
return getClientState(instance, {
|
|
73
|
+
return getClientState(instance, {
|
|
74
|
+
apiVersion: API_VERSION,
|
|
75
|
+
// TODO: remove in v3 when we're ready for everything to be queried via source
|
|
76
|
+
source: source && !isDatasetSource(source) ? source : undefined,
|
|
77
|
+
}).observable.pipe(
|
|
68
78
|
switchMap((client) => {
|
|
69
|
-
//
|
|
70
|
-
//
|
|
71
|
-
const loadDocument = createDocumentLoaderFromClient(client
|
|
79
|
+
// creates a observable request to the /doc/{documentId} endpoint for a given document id
|
|
80
|
+
// should work across all kinds of document IDs (drafts.**, version.**., etc.)
|
|
81
|
+
const loadDocument = createDocumentLoaderFromClient(client)
|
|
72
82
|
return loadDocument(documentId)
|
|
73
83
|
}),
|
|
74
84
|
map((result) => {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import {DocumentId, getPublishedId, getVersionId} from '@sanity/id-utils'
|
|
2
|
+
|
|
3
|
+
import {type DocumentHandle} from '../config/sanityConfig'
|
|
4
|
+
import {isReleasePerspective} from '../releases/utils/isReleasePerspective'
|
|
5
|
+
|
|
6
|
+
export function getEffectiveDocumentId(doc: DocumentHandle): string {
|
|
7
|
+
if (doc.liveEdit) {
|
|
8
|
+
return doc.documentId
|
|
9
|
+
} else if (isReleasePerspective(doc.perspective)) {
|
|
10
|
+
return getVersionId(DocumentId(doc.documentId), doc.perspective.releaseName)
|
|
11
|
+
} else {
|
|
12
|
+
return getPublishedId(DocumentId(doc.documentId))
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -126,9 +126,15 @@ describe('favoritesStore', () => {
|
|
|
126
126
|
})
|
|
127
127
|
|
|
128
128
|
it('handles error and returns default response', async () => {
|
|
129
|
+
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
129
130
|
setupMockStateSource({fetchImpl: vi.fn().mockRejectedValue(new Error('Failed to fetch'))})
|
|
130
131
|
const result = await resolveFavoritesState(instance!, mockContext)
|
|
131
132
|
expect(result).toEqual({isFavorited: false})
|
|
133
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
134
|
+
'Favorites service connection error',
|
|
135
|
+
expect.any(Error),
|
|
136
|
+
)
|
|
137
|
+
consoleErrorSpy.mockRestore()
|
|
132
138
|
})
|
|
133
139
|
|
|
134
140
|
it('shares observable between multiple subscribers and cleans up', async () => {
|
|
@@ -140,8 +146,9 @@ describe('favoritesStore', () => {
|
|
|
140
146
|
await firstValueFrom(state.observable)
|
|
141
147
|
expect(mockFetch).toHaveBeenCalledTimes(1)
|
|
142
148
|
// Second subscriber should use cached response
|
|
143
|
-
const
|
|
144
|
-
|
|
149
|
+
const state2 = getFavoritesState(instance!, mockContext)
|
|
150
|
+
const sub2 = state2.subscribe()
|
|
151
|
+
await firstValueFrom(state2.observable)
|
|
145
152
|
expect(mockFetch).toHaveBeenCalledTimes(1)
|
|
146
153
|
// Cleanup
|
|
147
154
|
sub1()
|
|
@@ -36,8 +36,13 @@ type IncomingBifurEvent = RollCallEvent | BifurStateMessage | BifurDisconnectMes
|
|
|
36
36
|
|
|
37
37
|
function getBifurClient(client: SanityClient, token$: Observable<string | null>): BifurClient {
|
|
38
38
|
const bifurVersionedClient = client.withConfig({apiVersion: '2022-06-30'})
|
|
39
|
-
const {
|
|
39
|
+
const {
|
|
40
|
+
dataset,
|
|
41
|
+
url: baseUrl,
|
|
42
|
+
requestTagPrefix = 'sanity.sdk.presence',
|
|
43
|
+
} = bifurVersionedClient.config()
|
|
40
44
|
const url = `${baseUrl.replace(/\/+$/, '')}/socket/${dataset}`.replace(/^http/, 'ws')
|
|
45
|
+
|
|
41
46
|
const urlWithTag = `${url}?tag=${requestTagPrefix}`
|
|
42
47
|
|
|
43
48
|
return fromUrl(urlWithTag, {token$})
|
|
@@ -1,120 +1,137 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {describe, it} from 'vitest'
|
|
1
|
+
import {of} from 'rxjs'
|
|
2
|
+
import {beforeEach, describe, expect, it, vi} from 'vitest'
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
import {type
|
|
6
|
-
import {
|
|
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'
|
|
7
8
|
import {getPreviewState} from './getPreviewState'
|
|
8
|
-
import {type
|
|
9
|
-
import {subscribeToStateAndFetchBatches} from './subscribeToStateAndFetchBatches'
|
|
10
|
-
import {STABLE_EMPTY_PREVIEW} from './util'
|
|
9
|
+
import {type PreviewQueryResult} from './types'
|
|
11
10
|
|
|
12
|
-
vi.mock('../
|
|
13
|
-
const util = await importOriginal<typeof import('../utils/ids')>()
|
|
14
|
-
return {...util, insecureRandomId: vi.fn(util.insecureRandomId)}
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
vi.mock('./subscribeToStateAndFetchBatches.ts')
|
|
11
|
+
vi.mock('../projection/getProjectionState')
|
|
18
12
|
|
|
19
13
|
describe('getPreviewState', () => {
|
|
20
|
-
let instance: SanityInstance
|
|
21
|
-
const docHandle = {documentId: 'exampleId', documentType: 'exampleType'}
|
|
22
|
-
let state: StoreState<PreviewStoreState & {extra?: unknown}>
|
|
23
|
-
|
|
24
14
|
beforeEach(() => {
|
|
25
|
-
|
|
26
|
-
vi.mocked(subscribeToStateAndFetchBatches).mockImplementation((context) => {
|
|
27
|
-
state = context.state
|
|
28
|
-
return NEVER.subscribe()
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
instance = createSanityInstance({projectId: 'exampleProject', dataset: 'exampleDataset'})
|
|
15
|
+
vi.clearAllMocks()
|
|
32
16
|
})
|
|
33
17
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
expect(subscriber).toHaveBeenCalledTimes(2)
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
it('adds a subscription ID and document type to the state on subscription', () => {
|
|
69
|
-
const previewState = getPreviewState(instance, docHandle)
|
|
70
|
-
|
|
71
|
-
expect(state.get().subscriptions).toEqual({})
|
|
72
|
-
vi.mocked(insecureRandomId)
|
|
73
|
-
.mockImplementationOnce(() => 'pseudoRandomId1')
|
|
74
|
-
.mockImplementationOnce(() => 'pseudoRandomId2')
|
|
75
|
-
|
|
76
|
-
const unsubscribe1 = previewState.subscribe(vi.fn())
|
|
77
|
-
const unsubscribe2 = previewState.subscribe(vi.fn())
|
|
78
|
-
|
|
79
|
-
expect(state.get().subscriptions).toEqual({
|
|
80
|
-
exampleId: {pseudoRandomId1: true, pseudoRandomId2: true},
|
|
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',
|
|
81
49
|
})
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
exampleId: {pseudoRandomId1: true},
|
|
50
|
+
const previewState = getPreviewState(instance, {
|
|
51
|
+
documentId: 'doc1',
|
|
52
|
+
documentType: 'article',
|
|
86
53
|
})
|
|
87
54
|
|
|
88
|
-
|
|
89
|
-
|
|
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)
|
|
90
63
|
})
|
|
91
64
|
|
|
92
|
-
it('
|
|
93
|
-
const
|
|
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
|
+
})
|
|
94
85
|
|
|
95
|
-
|
|
96
|
-
values: {...prev.values, [docHandle.documentId]: {data: {title: 'Foo'}, isPending: true}},
|
|
97
|
-
}))
|
|
86
|
+
const result = previewState.getCurrent()
|
|
98
87
|
|
|
99
|
-
|
|
100
|
-
|
|
88
|
+
expect(result.data).toBeNull()
|
|
89
|
+
expect(result.isPending).toBe(true)
|
|
90
|
+
})
|
|
101
91
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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',
|
|
105
123
|
})
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
data: {title: 'Foo'},
|
|
110
|
-
isPending: true,
|
|
124
|
+
const previewState = getPreviewState(instance, {
|
|
125
|
+
documentId: 'doc1',
|
|
126
|
+
documentType: 'article',
|
|
111
127
|
})
|
|
112
128
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
expect(
|
|
116
|
-
|
|
117
|
-
|
|
129
|
+
const result = previewState.getCurrent()
|
|
130
|
+
|
|
131
|
+
expect(result.data).toEqual({
|
|
132
|
+
title: 'article: doc1',
|
|
133
|
+
subtitle: undefined,
|
|
134
|
+
media: null,
|
|
118
135
|
})
|
|
119
136
|
})
|
|
120
137
|
})
|
|
@@ -1,29 +1,22 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {map} from 'rxjs'
|
|
2
2
|
|
|
3
3
|
import {type DocumentHandle} from '../config/sanityConfig'
|
|
4
|
-
import {
|
|
4
|
+
import {getProjectionState} from '../projection/getProjectionState'
|
|
5
5
|
import {type SanityInstance} from '../store/createSanityInstance'
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
} from '../store/createStateSourceAction'
|
|
11
|
-
import {getPublishedId, insecureRandomId} from '../utils/ids'
|
|
12
|
-
import {
|
|
13
|
-
previewStore,
|
|
14
|
-
type PreviewStoreState,
|
|
15
|
-
type PreviewValue,
|
|
16
|
-
type ValuePending,
|
|
17
|
-
} from './previewStore'
|
|
18
|
-
import {STABLE_EMPTY_PREVIEW} from './util'
|
|
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'
|
|
19
10
|
|
|
20
11
|
/**
|
|
21
12
|
* @beta
|
|
13
|
+
* @deprecated This type is deprecated and will be removed in a future release.
|
|
22
14
|
*/
|
|
23
15
|
export type GetPreviewStateOptions = DocumentHandle
|
|
24
16
|
|
|
25
17
|
/**
|
|
26
18
|
* @beta
|
|
19
|
+
* @deprecated This function is deprecated and will be removed in a future release.
|
|
27
20
|
*/
|
|
28
21
|
export function getPreviewState<TResult extends object>(
|
|
29
22
|
instance: SanityInstance,
|
|
@@ -31,6 +24,7 @@ export function getPreviewState<TResult extends object>(
|
|
|
31
24
|
): StateSource<ValuePending<TResult>>
|
|
32
25
|
/**
|
|
33
26
|
* @beta
|
|
27
|
+
* @deprecated This function is deprecated and will be removed in a future release.
|
|
34
28
|
*/
|
|
35
29
|
export function getPreviewState(
|
|
36
30
|
instance: SanityInstance,
|
|
@@ -38,54 +32,38 @@ export function getPreviewState(
|
|
|
38
32
|
): StateSource<ValuePending<PreviewValue>>
|
|
39
33
|
/**
|
|
40
34
|
* @beta
|
|
35
|
+
* @deprecated This function is deprecated and will be removed in a future release.
|
|
41
36
|
*/
|
|
42
37
|
export function getPreviewState(
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
+
})
|
|
47
46
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
{state}: SelectorContext<PreviewStoreState>,
|
|
56
|
-
docHandle: GetPreviewStateOptions,
|
|
57
|
-
): ValuePending<object> => state.values[docHandle.documentId] ?? STABLE_EMPTY_PREVIEW,
|
|
58
|
-
onSubscribe: ({state}, docHandle: GetPreviewStateOptions) => {
|
|
59
|
-
const subscriptionId = insecureRandomId()
|
|
60
|
-
const documentId = getPublishedId(docHandle.documentId)
|
|
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
|
+
}
|
|
61
54
|
|
|
62
|
-
|
|
63
|
-
subscriptions: {
|
|
64
|
-
...prev.subscriptions,
|
|
65
|
-
[documentId]: {
|
|
66
|
-
...prev.subscriptions[documentId],
|
|
67
|
-
[subscriptionId]: true,
|
|
68
|
-
},
|
|
69
|
-
},
|
|
70
|
-
}))
|
|
55
|
+
const previewValue = transformProjectionToPreview(instance, current.data, options.source)
|
|
71
56
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const previewValue = prevValue?.data ? prevValue.data : null
|
|
57
|
+
return {
|
|
58
|
+
data: previewValue,
|
|
59
|
+
isPending: current.isPending,
|
|
60
|
+
}
|
|
61
|
+
}
|
|
78
62
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
})
|
|
88
|
-
}
|
|
89
|
-
},
|
|
90
|
-
}),
|
|
91
|
-
)
|
|
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
|
+
}
|