@sanity/sdk-react 2.10.0 → 2.11.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.
Files changed (54) hide show
  1. package/dist/index.d.ts +257 -200
  2. package/dist/index.js +364 -253
  3. package/dist/index.js.map +1 -1
  4. package/package.json +6 -9
  5. package/src/_exports/index.ts +2 -0
  6. package/src/_exports/sdk-react.ts +4 -0
  7. package/src/components/SDKProvider.test.tsx +5 -12
  8. package/src/components/SDKProvider.tsx +26 -24
  9. package/src/config/handles.ts +55 -0
  10. package/src/constants.ts +5 -0
  11. package/src/context/DefaultResourceContext.ts +10 -0
  12. package/src/context/PerspectiveContext.ts +12 -0
  13. package/src/context/ResourceProvider.test.tsx +2 -2
  14. package/src/context/ResourceProvider.tsx +53 -49
  15. package/src/hooks/agent/agentActions.ts +55 -38
  16. package/src/hooks/context/useResource.test.tsx +32 -0
  17. package/src/hooks/context/useResource.ts +24 -0
  18. package/src/hooks/context/useSanityInstance.test.tsx +42 -111
  19. package/src/hooks/context/useSanityInstance.ts +28 -50
  20. package/src/hooks/dashboard/useDispatchIntent.test.ts +5 -1
  21. package/src/hooks/dashboard/useDispatchIntent.ts +3 -3
  22. package/src/hooks/dashboard/useManageFavorite.test.tsx +16 -12
  23. package/src/hooks/dashboard/utils/useResourceIdFromDocumentHandle.ts +1 -5
  24. package/src/hooks/document/{useApplyDocumentActions.test.ts → useApplyDocumentActions.test.tsx} +42 -77
  25. package/src/hooks/document/useApplyDocumentActions.ts +28 -62
  26. package/src/hooks/document/useDocument.ts +3 -5
  27. package/src/hooks/document/useDocumentEvent.ts +4 -3
  28. package/src/hooks/document/useDocumentPermissions.test.tsx +58 -150
  29. package/src/hooks/document/useDocumentPermissions.ts +78 -55
  30. package/src/hooks/document/useEditDocument.test.tsx +25 -60
  31. package/src/hooks/document/useEditDocument.ts +1 -1
  32. package/src/hooks/documents/useDocuments.ts +13 -8
  33. package/src/hooks/helpers/createStateSourceHook.tsx +1 -2
  34. package/src/hooks/helpers/useNormalizedResourceOptions.test.tsx +253 -0
  35. package/src/hooks/helpers/useNormalizedResourceOptions.ts +85 -47
  36. package/src/hooks/organizations/useOrganization.test-d.ts +53 -0
  37. package/src/hooks/organizations/useOrganization.test.ts +65 -0
  38. package/src/hooks/organizations/useOrganization.ts +40 -0
  39. package/src/hooks/organizations/useOrganizations.test-d.ts +55 -0
  40. package/src/hooks/organizations/useOrganizations.test.ts +85 -0
  41. package/src/hooks/organizations/useOrganizations.ts +45 -0
  42. package/src/hooks/paginatedDocuments/usePaginatedDocuments.ts +23 -9
  43. package/src/hooks/presence/usePresence.ts +4 -11
  44. package/src/hooks/preview/useDocumentPreview.tsx +4 -7
  45. package/src/hooks/projection/useDocumentProjection.ts +5 -7
  46. package/src/hooks/projects/useProject.test-d.ts +49 -0
  47. package/src/hooks/projects/useProject.ts +33 -41
  48. package/src/hooks/projects/useProjects.test-d.ts +49 -0
  49. package/src/hooks/projects/useProjects.ts +17 -23
  50. package/src/hooks/query/useQuery.ts +1 -1
  51. package/src/hooks/releases/useActiveReleases.ts +6 -6
  52. package/src/hooks/releases/usePerspective.ts +7 -12
  53. package/src/hooks/users/useUser.ts +1 -1
  54. package/src/hooks/users/useUsers.ts +1 -1
@@ -1,5 +1,4 @@
1
1
  import {
2
- type DocumentHandle,
3
2
  PREVIEW_PROJECTION,
4
3
  type PreviewQueryResult,
5
4
  type PreviewValue,
@@ -7,11 +6,9 @@ import {
7
6
  } from '@sanity/sdk'
8
7
  import {useMemo} from 'react'
9
8
 
9
+ import {type DocumentHandle} from '../../config/handles'
10
10
  import {useSanityInstance} from '../context/useSanityInstance'
11
- import {
12
- useNormalizedResourceOptions,
13
- type WithResourceNameSupport,
14
- } from '../helpers/useNormalizedResourceOptions'
11
+ import {useNormalizedResourceOptions} from '../helpers/useNormalizedResourceOptions'
15
12
  import {trackHookUsage} from '../helpers/useTrackHookUsage'
16
13
  import {useDocumentProjection} from '../projection/useDocumentProjection'
17
14
 
@@ -19,7 +16,7 @@ import {useDocumentProjection} from '../projection/useDocumentProjection'
19
16
  * @public
20
17
  * @category Types
21
18
  */
22
- export interface useDocumentPreviewOptions extends WithResourceNameSupport<DocumentHandle> {
19
+ export interface useDocumentPreviewOptions extends DocumentHandle {
23
20
  /**
24
21
  * Optional ref object to track visibility. When provided, preview resolution
25
22
  * only occurs when the referenced element is visible in the viewport.
@@ -98,7 +95,7 @@ export function useDocumentPreview({
98
95
  ref,
99
96
  ...docHandle
100
97
  }: useDocumentPreviewOptions): useDocumentPreviewResults {
101
- const instance = useSanityInstance(docHandle)
98
+ const instance = useSanityInstance()
102
99
  trackHookUsage(instance, 'useDocumentPreview')
103
100
  const normalizedDocHandle = useNormalizedResourceOptions(docHandle)
104
101
 
@@ -1,13 +1,11 @@
1
- import {type DocumentHandle, getProjectionState, resolveProjection} from '@sanity/sdk'
1
+ import {getProjectionState, resolveProjection} from '@sanity/sdk'
2
2
  import {type SanityProjectionResult} from 'groq'
3
3
  import {useCallback, useMemo, useSyncExternalStore} from 'react'
4
4
  import {distinctUntilChanged, EMPTY, Observable, startWith, switchMap} from 'rxjs'
5
5
 
6
+ import {type DocumentHandle} from '../../config/handles'
6
7
  import {useSanityInstance} from '../context/useSanityInstance'
7
- import {
8
- useNormalizedResourceOptions,
9
- type WithResourceNameSupport,
10
- } from '../helpers/useNormalizedResourceOptions'
8
+ import {useNormalizedResourceOptions} from '../helpers/useNormalizedResourceOptions'
11
9
  import {trackHookUsage} from '../helpers/useTrackHookUsage'
12
10
 
13
11
  /**
@@ -19,7 +17,7 @@ export interface useDocumentProjectionOptions<
19
17
  TDocumentType extends string = string,
20
18
  TDataset extends string = string,
21
19
  TProjectId extends string = string,
22
- > extends WithResourceNameSupport<DocumentHandle<TDocumentType, TDataset, TProjectId>> {
20
+ > extends DocumentHandle<TDocumentType, TDataset, TProjectId> {
23
21
  /** The GROQ projection string */
24
22
  projection: TProjection
25
23
  /** Optional parameters for the projection query */
@@ -181,7 +179,7 @@ export function useDocumentProjection<TData extends object>({
181
179
  projection,
182
180
  ...docHandle
183
181
  }: useDocumentProjectionOptions): useDocumentProjectionResults<TData> {
184
- const instance = useSanityInstance(docHandle)
182
+ const instance = useSanityInstance()
185
183
  trackHookUsage(instance, 'useDocumentProjection')
186
184
 
187
185
  // Normalize projection string to handle template literals with whitespace
@@ -0,0 +1,49 @@
1
+ import {type Project, type ProjectMember} from '@sanity/sdk'
2
+ import {expectTypeOf, test} from 'vitest'
3
+
4
+ import {useProject} from './useProject'
5
+
6
+ test('useProject — no args: members and features both included by default', () => {
7
+ expectTypeOf(useProject()).toEqualTypeOf<Project<true, true>>()
8
+ type Result = ReturnType<typeof useProject<true, true>>
9
+ expectTypeOf<Result['members']>().toEqualTypeOf<ProjectMember[]>()
10
+ })
11
+
12
+ test('useProject — includeMembers: false drops members from the type', () => {
13
+ expectTypeOf(useProject({includeMembers: false})).toEqualTypeOf<Project<false, true>>()
14
+ })
15
+
16
+ test('useProject — includeFeatures: false drops features from the type', () => {
17
+ expectTypeOf(useProject({includeFeatures: false})).toEqualTypeOf<Project<true, false>>()
18
+ })
19
+
20
+ test('useProject — both flags true → both arrays present', () => {
21
+ expectTypeOf(useProject({includeMembers: true, includeFeatures: true})).toEqualTypeOf<
22
+ Project<true, true>
23
+ >()
24
+ })
25
+
26
+ test('useProject — both flags false → bare base shape', () => {
27
+ expectTypeOf(useProject({includeMembers: false, includeFeatures: false})).toEqualTypeOf<
28
+ Project<false, false>
29
+ >()
30
+ type Result = ReturnType<typeof useProject<false, false>>
31
+ expectTypeOf<Result['id']>().toEqualTypeOf<string>()
32
+ })
33
+
34
+ test('useProject — rejects non-boolean flag values', () => {
35
+ // @ts-expect-error — includeMembers must be a boolean
36
+ void useProject({includeMembers: 'yes'})
37
+ })
38
+
39
+ test('useProject — projectId alone does not change the data shape', () => {
40
+ expectTypeOf(useProject({projectId: 'p'})).toEqualTypeOf<Project<true, true>>()
41
+ })
42
+
43
+ test('useProject — non-literal boolean flag makes members optional', () => {
44
+ const includeMembers = false as boolean
45
+ expectTypeOf(useProject({includeMembers})).toEqualTypeOf<Project<boolean, true>>()
46
+ type Result = ReturnType<typeof useProject<boolean, true>>
47
+ expectTypeOf<Result['members']>().toEqualTypeOf<ProjectMember[] | undefined>()
48
+ expectTypeOf<Pick<Result, 'members'>>().toEqualTypeOf<{members?: ProjectMember[]}>()
49
+ })
@@ -1,51 +1,43 @@
1
- import {
2
- getProjectState,
3
- type ProjectHandle,
4
- resolveProject,
5
- type SanityInstance,
6
- type SanityProject,
7
- type StateSource,
8
- } from '@sanity/sdk'
1
+ import {getProjectState, type Project, type ProjectOptions, resolveProject} from '@sanity/sdk'
9
2
  import {identity} from 'rxjs'
10
3
 
11
4
  import {createStateSourceHook} from '../helpers/createStateSourceHook'
12
5
 
13
- type UseProject = {
14
- /**
15
- *
16
- * Returns metadata for a given project
17
- *
18
- * @category Projects
19
- * @param projectId - The ID of the project to retrieve metadata for
20
- * @returns The metadata for the project
21
- * @example
22
- * ```tsx
23
- * function ProjectMetadata({ projectId }: { projectId: string }) {
24
- * const project = useProject(projectId)
25
- *
26
- * return (
27
- * <figure style={{ backgroundColor: project.metadata.color || 'lavender'}}>
28
- * <h1>{project.displayName}</h1>
29
- * </figure>
30
- * )
31
- * }
32
- * ```
33
- */
34
- (projectHandle?: ProjectHandle): SanityProject
35
- }
36
-
37
6
  /**
7
+ * Returns metadata for a given project.
8
+ *
9
+ * @category Projects
10
+ * @param options - Configuration options
11
+ * @returns The metadata for the project. `members` is included only when
12
+ * `includeMembers: true`; `features` is included unless `includeFeatures: false`.
13
+ * @example
14
+ * ```tsx
15
+ * function ProjectMetadata({projectId}: {projectId: string}) {
16
+ * const project = useProject({projectId})
17
+ *
18
+ * return (
19
+ * <figure style={{backgroundColor: project.metadata.color || 'lavender'}}>
20
+ * <h1>{project.displayName}</h1>
21
+ * </figure>
22
+ * )
23
+ * }
24
+ * ```
25
+ * @example
26
+ * ```tsx
27
+ * const projectWithMembersAndFeatures = useProject({projectId})
28
+ * const projectWithMembers = useProject({projectId, includeMembers: true})
29
+ * const projectWithoutMembers = useProject({projectId, includeMembers: false})
30
+ * const projectWithoutFeatures = useProject({projectId, includeFeatures: false})
31
+ * ```
38
32
  * @public
39
33
  * @function
40
34
  */
41
- export const useProject: UseProject = createStateSourceHook({
42
- // remove `undefined` since we're suspending when that is the case
43
- getState: getProjectState as (
44
- instance: SanityInstance,
45
- projectHandle?: ProjectHandle,
46
- ) => StateSource<SanityProject>,
47
- shouldSuspend: (instance, projectHandle) =>
48
- getProjectState(instance, projectHandle).getCurrent() === undefined,
35
+ export const useProject = createStateSourceHook({
36
+ getState: getProjectState,
37
+ shouldSuspend: (instance, ...params) =>
38
+ getProjectState(instance, ...params).getCurrent() === undefined,
49
39
  suspender: resolveProject,
50
40
  getConfig: identity,
51
- })
41
+ }) as <IncludeMembers extends boolean = true, IncludeFeatures extends boolean = true>(
42
+ options?: ProjectOptions<IncludeMembers, IncludeFeatures>,
43
+ ) => Project<IncludeMembers, IncludeFeatures>
@@ -0,0 +1,49 @@
1
+ import {type Project, type ProjectMember} from '@sanity/sdk'
2
+ import {expectTypeOf, test} from 'vitest'
3
+
4
+ import {useProjects} from './useProjects'
5
+
6
+ test('useProjects — no args: features included, members omitted', () => {
7
+ expectTypeOf(useProjects()).toEqualTypeOf<Project<false, true>[]>()
8
+ })
9
+
10
+ test('useProjects — includeMembers: true adds members to the type', () => {
11
+ expectTypeOf(useProjects({includeMembers: true})).toEqualTypeOf<Project<true, true>[]>()
12
+ type Result = ReturnType<typeof useProjects<true, true>>
13
+ expectTypeOf<Result[number]['members']>().toEqualTypeOf<ProjectMember[]>()
14
+ })
15
+
16
+ test('useProjects — includeFeatures: false drops features from the type', () => {
17
+ expectTypeOf(useProjects({includeFeatures: false})).toEqualTypeOf<Project<false, false>[]>()
18
+ })
19
+
20
+ test('useProjects — both flags true → both arrays present', () => {
21
+ expectTypeOf(useProjects({includeMembers: true, includeFeatures: true})).toEqualTypeOf<
22
+ Project<true, true>[]
23
+ >()
24
+ })
25
+
26
+ test('useProjects — both flags false → bare base shape', () => {
27
+ expectTypeOf(useProjects({includeMembers: false, includeFeatures: false})).toEqualTypeOf<
28
+ Project<false, false>[]
29
+ >()
30
+ type Result = ReturnType<typeof useProjects<false, false>>
31
+ expectTypeOf<Result[number]['id']>().toEqualTypeOf<string>()
32
+ })
33
+
34
+ test('useProjects — rejects non-boolean flag values', () => {
35
+ // @ts-expect-error — includeMembers must be a boolean
36
+ void useProjects({includeMembers: 'yes'})
37
+ })
38
+
39
+ test('useProjects — organizationId alone does not change the data shape', () => {
40
+ expectTypeOf(useProjects({organizationId: 'org_123'})).toEqualTypeOf<Project<false, true>[]>()
41
+ })
42
+
43
+ test('useProjects — non-literal boolean flag makes members optional', () => {
44
+ const includeMembers = false as boolean
45
+ expectTypeOf(useProjects({includeMembers})).toEqualTypeOf<Project<boolean, true>[]>()
46
+ type Result = ReturnType<typeof useProjects<boolean, true>>
47
+ expectTypeOf<Result[number]['members']>().toEqualTypeOf<ProjectMember[] | undefined>()
48
+ expectTypeOf<Pick<Result[number], 'members'>>().toEqualTypeOf<{members?: ProjectMember[]}>()
49
+ })
@@ -1,5 +1,4 @@
1
- import {type SanityProject} from '@sanity/client'
2
- import {getProjectsState, resolveProjects, type SanityInstance, type StateSource} from '@sanity/sdk'
1
+ import {getProjectsState, type Project, type ProjectsOptions, resolveProjects} from '@sanity/sdk'
3
2
 
4
3
  import {createStateSourceHook} from '../helpers/createStateSourceHook'
5
4
 
@@ -7,24 +6,17 @@ import {createStateSourceHook} from '../helpers/createStateSourceHook'
7
6
  * @public
8
7
  * @category Types
9
8
  * @interface
9
+ * @deprecated use the Project type directly.
10
10
  */
11
- export type ProjectWithoutMembers = Omit<SanityProject, 'members'>
12
-
13
- /**
14
- * @public
15
- * @category Types
16
- */
17
- type UseProjects = <TIncludeMembers extends boolean = false>(options?: {
18
- organizationId?: string
19
- includeMembers?: TIncludeMembers
20
- }) => TIncludeMembers extends true ? SanityProject[] : ProjectWithoutMembers[]
11
+ export type ProjectWithoutMembers = Project
21
12
 
22
13
  /**
23
14
  * Returns metadata for each project you have access to.
24
15
  *
25
16
  * @category Projects
26
17
  * @param options - Configuration options
27
- * @returns An array of project metadata. If includeMembers is true, returns full SanityProject objects. Otherwise, returns ProjectWithoutMembers objects.
18
+ * @returns An array of project metadata. `members` is included only when
19
+ * `includeMembers: true`; `features` is included unless `includeFeatures: false`.
28
20
  * @example
29
21
  * ```tsx
30
22
  * const projects = useProjects()
@@ -39,18 +31,20 @@ type UseProjects = <TIncludeMembers extends boolean = false>(options?: {
39
31
  * ```
40
32
  * @example
41
33
  * ```tsx
42
- * const projectsWithMembers = useProjects({ includeMembers: true })
43
- * const projectsWithoutMembers = useProjects({ includeMembers: false })
34
+ * const projects = useProjects()
35
+ * const projectsWithFeatures = useProjects()
36
+ * const projectsWithMembers = useProjects({includeMembers: true})
37
+ * const projectsWithoutMembers = useProjects({includeMembers: false})
38
+ * const projectsWithoutFeatures = useProjects({includeFeatures: false})
44
39
  * ```
45
40
  * @public
46
41
  * @function
47
42
  */
48
- export const useProjects: UseProjects = createStateSourceHook({
49
- getState: getProjectsState as (
50
- instance: SanityInstance,
51
- options?: {organizationId?: string; includeMembers?: boolean},
52
- ) => StateSource<SanityProject[] | ProjectWithoutMembers[]>,
53
- shouldSuspend: (instance, options) =>
54
- getProjectsState(instance, options).getCurrent() === undefined,
43
+ export const useProjects = createStateSourceHook({
44
+ getState: getProjectsState,
45
+ shouldSuspend: (instance, ...params) =>
46
+ getProjectsState(instance, ...params).getCurrent() === undefined,
55
47
  suspender: resolveProjects,
56
- }) as UseProjects
48
+ }) as <IncludeMembers extends boolean = false, IncludeFeatures extends boolean = true>(
49
+ options?: ProjectsOptions<IncludeMembers, IncludeFeatures>,
50
+ ) => Project<IncludeMembers, IncludeFeatures>[]
@@ -152,7 +152,7 @@ export function useQuery(options: WithResourceNameSupport<QueryOptions>): {
152
152
  isPending: boolean
153
153
  } {
154
154
  // Implementation returns unknown, overloads define specifics
155
- const instance = useSanityInstance(options)
155
+ const instance = useSanityInstance()
156
156
  trackHookUsage(instance, 'useQuery')
157
157
 
158
158
  // Normalize options: resolve resourceName to resource and strip resourceName
@@ -29,11 +29,11 @@ import {
29
29
  * const activeReleases = useActiveReleases()
30
30
  * ```
31
31
  */
32
- type UseActiveReleases = {
33
- (options?: WithResourceNameSupport<SanityConfig> | undefined): ReleaseDocument[]
32
+ type UseActiveReleasesValue = {
33
+ (options?: {resource?: DocumentResource}): ReleaseDocument[]
34
34
  }
35
35
 
36
- const useActiveReleasesValue: UseActiveReleases = createStateSourceHook({
36
+ const useActiveReleasesValue: UseActiveReleasesValue = createStateSourceHook({
37
37
  getState: getActiveReleasesState as (
38
38
  instance: SanityInstance,
39
39
  options?: {resource?: DocumentResource},
@@ -50,9 +50,9 @@ const useActiveReleasesValue: UseActiveReleases = createStateSourceHook({
50
50
  * @public
51
51
  * @function
52
52
  */
53
- export const useActiveReleases: UseActiveReleases = (
54
- options: WithResourceNameSupport<{resource?: DocumentResource}> | undefined,
55
- ) => {
53
+ export function useActiveReleases(
54
+ options?: WithResourceNameSupport<SanityConfig> | undefined,
55
+ ): ReleaseDocument[] {
56
56
  const normalizedOptions = useNormalizedResourceOptions(options ?? {})
57
57
  return useActiveReleasesValue(normalizedOptions)
58
58
  }
@@ -1,5 +1,4 @@
1
1
  import {
2
- type DatasetHandle,
3
2
  type DocumentResource,
4
3
  getPerspectiveState,
5
4
  type SanityInstance,
@@ -7,11 +6,9 @@ import {
7
6
  } from '@sanity/sdk'
8
7
  import {filter, firstValueFrom} from 'rxjs'
9
8
 
9
+ import {type ResourceHandle} from '../../config/handles'
10
10
  import {createStateSourceHook} from '../helpers/createStateSourceHook'
11
- import {
12
- useNormalizedResourceOptions,
13
- type WithResourceNameSupport,
14
- } from '../helpers/useNormalizedResourceOptions'
11
+ import {useNormalizedResourceOptions} from '../helpers/useNormalizedResourceOptions'
15
12
 
16
13
  /**
17
14
  * @public
@@ -35,11 +32,11 @@ import {
35
32
  *
36
33
  * @returns The perspective for the given perspective handle.
37
34
  */
38
- type UsePerspective = {
39
- (perspectiveHandle: DatasetHandle): string | string[]
35
+ type UsePerspectiveValue = {
36
+ (perspectiveHandle: {resource?: DocumentResource}): string | string[]
40
37
  }
41
38
 
42
- const usePerspectiveValue: UsePerspective = createStateSourceHook({
39
+ const usePerspectiveValue: UsePerspectiveValue = createStateSourceHook({
43
40
  getState: getPerspectiveState as (
44
41
  instance: SanityInstance,
45
42
  perspectiveHandle?: {resource?: DocumentResource},
@@ -54,9 +51,7 @@ const usePerspectiveValue: UsePerspective = createStateSourceHook({
54
51
  * @public
55
52
  * @function
56
53
  */
57
- export const usePerspective: UsePerspective = (
58
- options: WithResourceNameSupport<DatasetHandle> | undefined,
59
- ) => {
60
- const normalizedOptions = useNormalizedResourceOptions(options ?? {})
54
+ export function usePerspective(perspectiveHandle?: ResourceHandle): string | string[] {
55
+ const normalizedOptions = useNormalizedResourceOptions(perspectiveHandle ?? {})
61
56
  return usePerspectiveValue(normalizedOptions)
62
57
  }
@@ -59,7 +59,7 @@ export interface UserResult {
59
59
  * ```
60
60
  */
61
61
  export function useUser(options: GetUserOptions): UserResult {
62
- const instance = useSanityInstance(options)
62
+ const instance = useSanityInstance()
63
63
  trackHookUsage(instance, 'useUser')
64
64
  // Use React's useTransition to avoid UI jank when user options change
65
65
  const [isPending, startTransition] = useTransition()
@@ -69,7 +69,7 @@ export interface UsersResult {
69
69
  * ```
70
70
  */
71
71
  export function useUsers(options?: GetUsersOptions): UsersResult {
72
- const instance = useSanityInstance(options)
72
+ const instance = useSanityInstance()
73
73
  trackHookUsage(instance, 'useUsers')
74
74
  // Use React's useTransition to avoid UI jank when user options change
75
75
  const [isPending, startTransition] = useTransition()