Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/core/src/datasets/datasets.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ describe('datasets', () => {
observable: of(mockClient),
} as StateSource<SanityClient>)

const result = await resolveDatasets(instance)
const result = await resolveDatasets(instance, {projectId: 'p'})
expect(result).toEqual(datasets)
expect(list).toHaveBeenCalled()
})
Expand Down
31 changes: 15 additions & 16 deletions packages/core/src/datasets/datasets.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,28 @@
import {switchMap} from 'rxjs'

import {getClientState} from '../client/clientStore'
import {type ProjectHandle} from '../config/sanityConfig'
import {createFetcherStore} from '../utils/createFetcherStore'

const API_VERSION = 'v2025-02-19'

type DatasetOptions = {
projectId: string
}

/** @public */
export const datasets = createFetcherStore({
name: 'Datasets',
getKey: (instance, options?: ProjectHandle) => {
const projectId = options?.projectId ?? instance.config.projectId
if (!projectId) {
throw new Error('A projectId is required to use the project API.')
}
return projectId
},
fetcher: (instance) => (options?: ProjectHandle) => {
return getClientState(instance, {
apiVersion: API_VERSION,
// non-null assertion is fine because we check above
projectId: (options?.projectId ?? instance.config.projectId)!,
useProjectHostname: true,
}).observable.pipe(switchMap((client) => client.observable.datasets.list()))
},
getKey: (_, {projectId}: DatasetOptions) => projectId,
fetcher:
(instance) =>
({projectId}: DatasetOptions) => {
return getClientState(instance, {
apiVersion: API_VERSION,
// non-null assertion is fine because we check above
projectId,
useProjectHostname: true,
}).observable.pipe(switchMap((client) => client.observable.datasets.list()))
},
})

/** @public */
Expand Down
26 changes: 7 additions & 19 deletions packages/core/src/project/project.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,25 @@
import {switchMap} from 'rxjs'

import {getClientState} from '../client/clientStore'
import {type ProjectHandle} from '../config/sanityConfig'
import {createFetcherStore} from '../utils/createFetcherStore'

const API_VERSION = 'v2025-02-19'

type ProjectOptions = {
projectId: string
}

const project = createFetcherStore({
name: 'Project',
getKey: (instance, options?: ProjectHandle) => {
const projectId = options?.projectId ?? instance.config.projectId
if (!projectId) {
throw new Error('A projectId is required to use the project API.')
}
return projectId
},
getKey: (_, {projectId}: ProjectOptions) => projectId,
fetcher:
(instance) =>
(options: ProjectHandle = {}) => {
const projectId = options.projectId ?? instance.config.projectId

({projectId}: ProjectOptions) => {
return getClientState(instance, {
apiVersion: API_VERSION,
scope: 'global',
projectId,
}).observable.pipe(
switchMap((client) =>
client.observable.projects.getById(
// non-null assertion is fine with the above throwing
(projectId ?? instance.config.projectId)!,
),
),
)
}).observable.pipe(switchMap((client) => client.observable.projects.getById(projectId)))
},
})

Expand Down
80 changes: 0 additions & 80 deletions packages/react/src/hooks/datasets/useDatasets.test.ts

This file was deleted.

31 changes: 11 additions & 20 deletions packages/react/src/hooks/datasets/useDatasets.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
import {type DatasetsResponse} from '@sanity/client'
import {
getDatasetsState,
type ProjectHandle,
resolveDatasets,
type SanityInstance,
type StateSource,
} from '@sanity/sdk'
import {identity} from 'rxjs'
import {getDatasetsState} from '@sanity/sdk'
import {useMemo} from 'react'

import {createStateSourceHook} from '../helpers/createStateSourceHook'
import {useSanityInstance} from '../context/useSanityInstance'
import {useStoreState} from '../helpers/useStoreState'

type UseDatasets = {
/**
Expand Down Expand Up @@ -39,14 +34,10 @@ type UseDatasets = {
* @public
* @function
*/
export const useDatasets: UseDatasets = createStateSourceHook({
getState: getDatasetsState as (
instance: SanityInstance,
projectHandle?: ProjectHandle,
) => StateSource<DatasetsResponse>,
shouldSuspend: (instance, projectHandle?: ProjectHandle) =>
// remove `undefined` since we're suspending when that is the case
getDatasetsState(instance, projectHandle).getCurrent() === undefined,
suspender: resolveDatasets,
getConfig: identity as (projectHandle?: ProjectHandle) => ProjectHandle,
})
export const useDatasets: UseDatasets = () => {
const instance = useSanityInstance()
const {projectId} = instance.config
if (!projectId) throw new Error('useDatasets must be configured with projectId')
const state = useMemo(() => getDatasetsState(instance, {projectId}), [instance, projectId])
return useStoreState(state)
}
17 changes: 17 additions & 0 deletions packages/react/src/hooks/helpers/useStoreState.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import {type StateSource} from '@sanity/sdk'
import {useSyncExternalStore} from 'react'
import {first, firstValueFrom} from 'rxjs'

/**
* useStoreState is a hook around a StateSource which is initially undefined and then
* eventually always defined.
*/
export function useStoreState<T>(state: StateSource<T | undefined>): T {
const current = state.getCurrent()

if (current === undefined) {
throw firstValueFrom(state.observable.pipe(first((i) => i !== undefined)))
}

return useSyncExternalStore(state.subscribe, state.getCurrent as () => T)
}
80 changes: 0 additions & 80 deletions packages/react/src/hooks/projects/useProject.test.ts

This file was deleted.

32 changes: 11 additions & 21 deletions packages/react/src/hooks/projects/useProject.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
import {
getProjectState,
type ProjectHandle,
resolveProject,
type SanityInstance,
type SanityProject,
type StateSource,
} from '@sanity/sdk'
import {identity} from 'rxjs'
import {getProjectState, type ProjectHandle, type SanityProject} from '@sanity/sdk'
import {useMemo} from 'react'

import {createStateSourceHook} from '../helpers/createStateSourceHook'
import {useSanityInstance} from '../context/useSanityInstance'
import {useStoreState} from '../helpers/useStoreState'

type UseProject = {
/**
Expand Down Expand Up @@ -38,14 +32,10 @@ type UseProject = {
* @public
* @function
*/
export const useProject: UseProject = createStateSourceHook({
// remove `undefined` since we're suspending when that is the case
getState: getProjectState as (
instance: SanityInstance,
projectHandle?: ProjectHandle,
) => StateSource<SanityProject>,
shouldSuspend: (instance, projectHandle) =>
getProjectState(instance, projectHandle).getCurrent() === undefined,
suspender: resolveProject,
getConfig: identity,
})
export const useProject: UseProject = (options) => {
const instance = useSanityInstance()
const projectId = options?.projectId ?? instance.config.projectId
if (!projectId) throw new Error('useProject must be configured with projectId')
const state = useMemo(() => getProjectState(instance, {projectId}), [instance, projectId])
return useStoreState(state)
}
Loading