Skip to content

Commit 03e702a

Browse files
committed
feat: add support for source to perspective store
A non-breaking change to `usePerspective` which adds support for the `source` parameter. This is paired up with a breaking change to the release/perspective handling in core which now no longer looks at SanityInstance. In the future this will then open us for us having an independent "perspective" context provided by the React package.
1 parent 661c187 commit 03e702a

File tree

4 files changed

+43
-68
lines changed

4 files changed

+43
-68
lines changed

packages/core/src/query/queryStore.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import {
2323
} from 'rxjs'
2424

2525
import {getClientState} from '../client/clientStore'
26-
import {type DocumentSource, type ReleasePerspective} from '../config/sanityConfig'
26+
import {type DocumentSource, type ReleasePerspective, sourceFor} from '../config/sanityConfig'
2727
import {getPerspectiveState} from '../releases/getPerspectiveState'
2828
import {bindActionByDataset, type BoundDatasetKey} from '../store/createActionBinder'
2929
import {type SanityInstance} from '../store/createSanityInstance'
@@ -163,8 +163,7 @@ const listenForNewSubscribersAndFetch = ({
163163

164164
const perspective$ = getPerspectiveState(instance, {
165165
perspective: perspectiveFromOptions,
166-
projectId,
167-
dataset,
166+
source: sourceFor({projectId, dataset}),
168167
}).observable.pipe(filter(Boolean))
169168

170169
const client$ = getClientState(instance, {

packages/core/src/releases/getPerspectiveState.test.ts

Lines changed: 13 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {filter, firstValueFrom, of, Subject, take} from 'rxjs'
22
import {describe, expect, it, vi} from 'vitest'
33

4-
import {type PerspectiveHandle, type ReleasePerspective} from '../config/sanityConfig'
4+
import {type ReleasePerspective, sourceFor} from '../config/sanityConfig'
55
import {createSanityInstance, type SanityInstance} from '../store/createSanityInstance'
66
import {listenQuery as mockListenQuery} from '../utils/listenQuery'
77
import {getPerspectiveState} from './getPerspectiveState'
@@ -20,6 +20,7 @@ vi.mock('../client/clientStore', () => ({
2020
}))
2121

2222
describe('getPerspectiveState', () => {
23+
const source = sourceFor({projectId: 'test', dataset: 'test'})
2324
let instance: SanityInstance
2425
let mockReleasesQuerySubject: Subject<ReleaseDocument[]>
2526

@@ -53,40 +54,22 @@ describe('getPerspectiveState', () => {
5354
vi.clearAllMocks()
5455
})
5556

56-
it('should return default perspective if no options or instance perspective is provided', async () => {
57-
const state = getPerspectiveState(instance, {})
58-
mockReleasesQuerySubject.next([])
59-
const perspective = await firstValueFrom(state.observable)
60-
expect(perspective).toBe('drafts')
61-
})
62-
63-
it('should return instance perspective if provided and no options perspective', async () => {
64-
instance.config.perspective = 'published'
65-
const state = getPerspectiveState(instance, {})
66-
mockReleasesQuerySubject.next([])
67-
const perspective = await firstValueFrom(state.observable)
68-
expect(perspective).toBe('published')
69-
})
70-
7157
it('should return options perspective if provided', async () => {
72-
const options: PerspectiveHandle = {perspective: 'raw'}
73-
const state = getPerspectiveState(instance, options)
58+
const state = getPerspectiveState(instance, {perspective: 'raw', source})
7459
mockReleasesQuerySubject.next([])
7560
const perspective = await firstValueFrom(state.observable)
7661
expect(perspective).toBe('raw')
7762
})
7863

7964
it('should return undefined if release perspective is requested but no active releases', async () => {
80-
const options: PerspectiveHandle = {perspective: {releaseName: 'release1'}}
81-
const state = getPerspectiveState(instance, options)
65+
const state = getPerspectiveState(instance, {perspective: {releaseName: 'release1'}, source})
8266
mockReleasesQuerySubject.next([])
8367
const perspective = await firstValueFrom(state.observable)
8468
expect(perspective).toBeUndefined()
8569
})
8670

8771
it('should calculate perspective based on active releases and releaseName', async () => {
88-
const options: PerspectiveHandle = {perspective: {releaseName: 'release1'}}
89-
const state = getPerspectiveState(instance, options)
72+
const state = getPerspectiveState(instance, {perspective: {releaseName: 'release1'}, source})
9073
mockReleasesQuerySubject.next(activeReleases)
9174

9275
const perspective = await firstValueFrom(
@@ -99,8 +82,7 @@ describe('getPerspectiveState', () => {
9982
})
10083

10184
it('should calculate perspective including multiple releases up to the specified releaseName', async () => {
102-
const options: PerspectiveHandle = {perspective: {releaseName: 'release2'}}
103-
const state = getPerspectiveState(instance, options)
85+
const state = getPerspectiveState(instance, {perspective: {releaseName: 'release2'}, source})
10486
mockReleasesQuerySubject.next(activeReleases)
10587
const perspective = await firstValueFrom(
10688
state.observable.pipe(
@@ -116,8 +98,7 @@ describe('getPerspectiveState', () => {
11698
releaseName: 'release2',
11799
excludedPerspectives: ['drafts', 'release1'],
118100
}
119-
const options: PerspectiveHandle = {perspective: perspectiveConfig}
120-
const state = getPerspectiveState(instance, options)
101+
const state = getPerspectiveState(instance, {perspective: perspectiveConfig, source})
121102
mockReleasesQuerySubject.next(activeReleases)
122103
const perspective = await firstValueFrom(
123104
state.observable.pipe(
@@ -129,8 +110,7 @@ describe('getPerspectiveState', () => {
129110
})
130111

131112
it('should throw if the specified releaseName is not found in active releases', async () => {
132-
const options: PerspectiveHandle = {perspective: {releaseName: 'nonexistent'}}
133-
const state = getPerspectiveState(instance, options)
113+
const state = getPerspectiveState(instance, {perspective: {releaseName: 'nonexistent'}, source})
134114
mockReleasesQuerySubject.next(activeReleases)
135115

136116
await expect(
@@ -144,10 +124,10 @@ describe('getPerspectiveState', () => {
144124
})
145125

146126
it('should reuse the same options object for identical inputs (cache test)', async () => {
147-
const options1: PerspectiveHandle = {perspective: {releaseName: 'release1'}}
148-
const options2: PerspectiveHandle = {perspective: {releaseName: 'release1'}}
127+
const options1 = {perspective: {releaseName: 'release1'}}
128+
const options2 = {perspective: {releaseName: 'release1'}}
149129

150-
const state1 = getPerspectiveState(instance, options1)
130+
const state1 = getPerspectiveState(instance, {...options1, source})
151131
mockReleasesQuerySubject.next(activeReleases)
152132
await firstValueFrom(
153133
state1.observable.pipe(
@@ -156,15 +136,14 @@ describe('getPerspectiveState', () => {
156136
),
157137
)
158138

159-
const state2 = getPerspectiveState(instance, options2)
139+
const state2 = getPerspectiveState(instance, {...options2, source})
160140
const perspective2 = state2.getCurrent()
161141

162142
expect(perspective2).toEqual(['drafts', 'release1'])
163143
})
164144

165145
it('should handle changes in activeReleases (cache test)', async () => {
166-
const options: PerspectiveHandle = {perspective: {releaseName: 'release1'}}
167-
146+
const options = {perspective: {releaseName: 'release1'}, source}
168147
const state1 = getPerspectiveState(instance, options)
169148
mockReleasesQuerySubject.next(activeReleases)
170149
const perspective1 = await firstValueFrom(

packages/core/src/releases/getPerspectiveState.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1+
import {type ClientPerspective} from '@sanity/client'
12
import {createSelector} from 'reselect'
23

3-
import {type PerspectiveHandle, type ReleasePerspective} from '../config/sanityConfig'
4+
import {
5+
type DocumentSource,
6+
type PerspectiveHandle,
7+
type ReleasePerspective,
8+
} from '../config/sanityConfig'
49
import {bindActionByDataset} from '../store/createActionBinder'
510
import {createStateSourceAction, type SelectorContext} from '../store/createStateSourceAction'
611
import {releasesStore, type ReleasesStoreState} from './releasesStore'
@@ -12,18 +17,14 @@ function isReleasePerspective(
1217
return typeof perspective === 'object' && perspective !== null && 'releaseName' in perspective
1318
}
1419

15-
const DEFAULT_PERSPECTIVE = 'drafts'
16-
1720
// Cache for options
1821
const optionsCache = new Map<string, Map<string, PerspectiveHandle>>()
1922

20-
const selectInstancePerspective = (context: SelectorContext<ReleasesStoreState>) =>
21-
context.instance.config.perspective
2223
const selectActiveReleases = (context: SelectorContext<ReleasesStoreState>) =>
2324
context.state.activeReleases
2425
const selectOptions = (
2526
_context: SelectorContext<ReleasesStoreState>,
26-
options: PerspectiveHandle & {projectId?: string; dataset?: string},
27+
options: {perspective: ClientPerspective | ReleasePerspective; source: DocumentSource},
2728
) => options
2829

2930
const memoizedOptionsSelector = createSelector(
@@ -66,10 +67,9 @@ export const getPerspectiveState = bindActionByDataset(
6667
releasesStore,
6768
createStateSourceAction({
6869
selector: createSelector(
69-
[selectInstancePerspective, selectActiveReleases, memoizedOptionsSelector],
70-
(instancePerspective, activeReleases, memoizedOptions) => {
71-
const perspective =
72-
memoizedOptions?.perspective ?? instancePerspective ?? DEFAULT_PERSPECTIVE
70+
[selectActiveReleases, memoizedOptionsSelector],
71+
(activeReleases, memoizedOptions) => {
72+
const perspective = memoizedOptions.perspective
7373

7474
if (!isReleasePerspective(perspective)) return perspective
7575

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,14 @@
1-
import {
2-
getActiveReleasesState,
3-
getPerspectiveState,
4-
type PerspectiveHandle,
5-
type SanityInstance,
6-
type StateSource,
7-
} from '@sanity/sdk'
8-
import {filter, firstValueFrom} from 'rxjs'
1+
import {type DocumentSource, getPerspectiveState, type PerspectiveHandle} from '@sanity/sdk'
2+
import {useMemo} from 'react'
93

10-
import {createStateSourceHook} from '../helpers/createStateSourceHook'
4+
import {useSanityInstanceAndSource} from '../context/useSanityInstance'
5+
import {useStoreState} from '../helpers/useStoreState'
116

127
/**
138
* @public
149
*/
1510
type UsePerspective = {
16-
(perspectiveHandle: PerspectiveHandle): string | string[]
11+
(perspectiveHandle: PerspectiveHandle & {source?: DocumentSource}): string | string[]
1712
}
1813

1914
/**
@@ -30,21 +25,23 @@ type UsePerspective = {
3025
* ```tsx
3126
* import {usePerspective, useQuery} from '@sanity/sdk-react'
3227
33-
* const perspective = usePerspective({perspective: 'rxg1346', projectId: 'abc123', dataset: 'production'})
28+
* const perspective = usePerspective({perspective: 'rxg1346'})
3429
* const {data} = useQuery<Movie[]>('*[_type == "movie"]', {
3530
* perspective: perspective,
3631
* })
3732
* ```
3833
*
3934
* @returns The perspective for the given perspective handle.
4035
*/
41-
export const usePerspective: UsePerspective = createStateSourceHook({
42-
getState: getPerspectiveState as (
43-
instance: SanityInstance,
44-
perspectiveHandle?: PerspectiveHandle,
45-
) => StateSource<string | string[]>,
46-
shouldSuspend: (instance: SanityInstance, options: PerspectiveHandle): boolean =>
47-
getPerspectiveState(instance, options).getCurrent() === undefined,
48-
suspender: (instance: SanityInstance, _options?: PerspectiveHandle) =>
49-
firstValueFrom(getActiveReleasesState(instance, {}).observable.pipe(filter(Boolean))),
50-
})
36+
export const usePerspective: UsePerspective = ({perspective, source}) => {
37+
const [instance, actualSource] = useSanityInstanceAndSource({source})
38+
39+
const actualPerspective = perspective ?? instance.config.perspective ?? 'drafts'
40+
41+
const state = useMemo(
42+
() => getPerspectiveState(instance, {perspective: actualPerspective, source: actualSource}),
43+
[instance, actualPerspective, actualSource],
44+
)
45+
46+
return useStoreState(state)
47+
}

0 commit comments

Comments
 (0)