@sanity/sdk-react 2.9.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 (72) hide show
  1. package/dist/index.d.ts +338 -215
  2. package/dist/index.js +564 -342
  3. package/dist/index.js.map +1 -1
  4. package/package.json +9 -14
  5. package/src/_exports/index.ts +2 -0
  6. package/src/_exports/sdk-react.ts +8 -0
  7. package/src/components/SDKProvider.test.tsx +5 -12
  8. package/src/components/SDKProvider.tsx +58 -28
  9. package/src/components/SanityApp.tsx +2 -2
  10. package/src/components/auth/AuthBoundary.tsx +8 -1
  11. package/src/components/auth/DashboardAccessRequest.tsx +37 -0
  12. package/src/components/auth/LoginError.test.tsx +191 -5
  13. package/src/components/auth/LoginError.tsx +100 -56
  14. package/src/components/errors/ChunkLoadError.test.tsx +59 -0
  15. package/src/components/errors/ChunkLoadError.tsx +56 -0
  16. package/src/components/errors/chunkReloadStorage.ts +57 -0
  17. package/src/config/handles.ts +55 -0
  18. package/src/constants.ts +5 -0
  19. package/src/context/DefaultResourceContext.ts +10 -0
  20. package/src/context/PerspectiveContext.ts +12 -0
  21. package/src/context/ResourceProvider.test.tsx +2 -2
  22. package/src/context/ResourceProvider.tsx +56 -51
  23. package/src/context/ResourcesContext.tsx +7 -0
  24. package/src/context/SanityInstanceProvider.test.tsx +100 -0
  25. package/src/context/SanityInstanceProvider.tsx +71 -0
  26. package/src/hooks/agent/agentActions.ts +55 -38
  27. package/src/hooks/auth/useVerifyOrgProjects.tsx +13 -6
  28. package/src/hooks/context/useResource.test.tsx +32 -0
  29. package/src/hooks/context/useResource.ts +24 -0
  30. package/src/hooks/context/useSanityInstance.test.tsx +42 -111
  31. package/src/hooks/context/useSanityInstance.ts +28 -50
  32. package/src/hooks/dashboard/useDispatchIntent.test.ts +11 -7
  33. package/src/hooks/dashboard/useDispatchIntent.ts +7 -7
  34. package/src/hooks/dashboard/useManageFavorite.test.tsx +16 -12
  35. package/src/hooks/dashboard/utils/useResourceIdFromDocumentHandle.test.ts +15 -15
  36. package/src/hooks/dashboard/utils/useResourceIdFromDocumentHandle.ts +13 -17
  37. package/src/hooks/document/{useApplyDocumentActions.test.ts → useApplyDocumentActions.test.tsx} +46 -81
  38. package/src/hooks/document/useApplyDocumentActions.ts +33 -67
  39. package/src/hooks/document/useDocument.ts +4 -6
  40. package/src/hooks/document/useDocumentEvent.ts +8 -7
  41. package/src/hooks/document/useDocumentPermissions.test.tsx +60 -152
  42. package/src/hooks/document/useDocumentPermissions.ts +78 -55
  43. package/src/hooks/document/useDocumentSyncStatus.ts +2 -2
  44. package/src/hooks/document/useEditDocument.test.tsx +25 -60
  45. package/src/hooks/document/useEditDocument.ts +3 -3
  46. package/src/hooks/documents/useDocuments.ts +19 -11
  47. package/src/hooks/helpers/createStateSourceHook.tsx +1 -2
  48. package/src/hooks/helpers/useNormalizedResourceOptions.test.tsx +253 -0
  49. package/src/hooks/helpers/useNormalizedResourceOptions.ts +169 -0
  50. package/src/hooks/helpers/useTrackHookUsage.ts +2 -2
  51. package/src/hooks/organizations/useOrganization.test-d.ts +53 -0
  52. package/src/hooks/organizations/useOrganization.test.ts +65 -0
  53. package/src/hooks/organizations/useOrganization.ts +40 -0
  54. package/src/hooks/organizations/useOrganizations.test-d.ts +55 -0
  55. package/src/hooks/organizations/useOrganizations.test.ts +85 -0
  56. package/src/hooks/organizations/useOrganizations.ts +45 -0
  57. package/src/hooks/paginatedDocuments/usePaginatedDocuments.ts +29 -14
  58. package/src/hooks/presence/usePresence.test.tsx +56 -9
  59. package/src/hooks/presence/usePresence.ts +16 -4
  60. package/src/hooks/preview/useDocumentPreview.tsx +8 -10
  61. package/src/hooks/projection/useDocumentProjection.ts +7 -9
  62. package/src/hooks/projects/useProject.test-d.ts +49 -0
  63. package/src/hooks/projects/useProject.ts +33 -41
  64. package/src/hooks/projects/useProjects.test-d.ts +49 -0
  65. package/src/hooks/projects/useProjects.ts +17 -23
  66. package/src/hooks/query/useQuery.ts +11 -10
  67. package/src/hooks/releases/useActiveReleases.ts +14 -14
  68. package/src/hooks/releases/usePerspective.ts +11 -16
  69. package/src/hooks/users/useUser.ts +1 -1
  70. package/src/hooks/users/useUsers.ts +1 -1
  71. package/src/context/SourcesContext.tsx +0 -7
  72. package/src/hooks/helpers/useNormalizedSourceOptions.ts +0 -107
@@ -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>[]
@@ -10,19 +10,19 @@ import {useEffect, useMemo, useRef, useState, useSyncExternalStore, useTransitio
10
10
 
11
11
  import {useSanityInstance} from '../context/useSanityInstance'
12
12
  import {
13
- useNormalizedSourceOptions,
14
- type WithSourceNameSupport,
15
- } from '../helpers/useNormalizedSourceOptions'
13
+ useNormalizedResourceOptions,
14
+ type WithResourceNameSupport,
15
+ } from '../helpers/useNormalizedResourceOptions'
16
16
  import {trackHookUsage} from '../helpers/useTrackHookUsage'
17
17
  /**
18
- * Hook options for useQuery, supporting both direct source and sourceName.
18
+ * Hook options for useQuery, supporting both direct resource and resourceName.
19
19
  * @beta
20
20
  */
21
21
  type UseQueryOptions<
22
22
  TQuery extends string = string,
23
23
  TDataset extends string = string,
24
24
  TProjectId extends string = string,
25
- > = WithSourceNameSupport<QueryOptions<TQuery, TDataset, TProjectId>>
25
+ > = WithResourceNameSupport<QueryOptions<TQuery, TDataset, TProjectId>>
26
26
 
27
27
  // Overload 1: Inferred Type (using Typegen)
28
28
  /**
@@ -122,7 +122,7 @@ export function useQuery<
122
122
  * }
123
123
  * ```
124
124
  */
125
- export function useQuery<TData>(options: WithSourceNameSupport<QueryOptions>): {
125
+ export function useQuery<TData>(options: WithResourceNameSupport<QueryOptions>): {
126
126
  /** The query result, cast to the provided type TData */
127
127
  data: TData
128
128
  /** True if another query is resolving in the background (suspense handles the initial loading state) */
@@ -147,16 +147,16 @@ export function useQuery<TData>(options: WithSourceNameSupport<QueryOptions>): {
147
147
  *
148
148
  * @category GROQ
149
149
  */
150
- export function useQuery(options: WithSourceNameSupport<QueryOptions>): {
150
+ export function useQuery(options: WithResourceNameSupport<QueryOptions>): {
151
151
  data: unknown
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
- // Normalize options: resolve sourceName to source and strip sourceName
159
- const normalized = useNormalizedSourceOptions(options)
158
+ // Normalize options: resolve resourceName to resource and strip resourceName
159
+ const normalized = useNormalizedResourceOptions(options)
160
160
 
161
161
  // Use React's useTransition to avoid UI jank when queries change
162
162
  const [isPending, startTransition] = useTransition()
@@ -204,6 +204,7 @@ export function useQuery(options: WithSourceNameSupport<QueryOptions>): {
204
204
  const currentSignal = ref.current.signal
205
205
  const deferred = parseQueryKey(deferredQueryKey)
206
206
 
207
+ // eslint-disable-next-line react-hooks/refs -- intentional during suspended render; see comment above.
207
208
  throw resolveQuery(instance, {...deferred, signal: currentSignal})
208
209
  }
209
210
 
@@ -1,5 +1,5 @@
1
1
  import {
2
- type DocumentSource,
2
+ type DocumentResource,
3
3
  getActiveReleasesState,
4
4
  type ReleaseDocument,
5
5
  type SanityConfig,
@@ -10,9 +10,9 @@ import {filter, firstValueFrom} from 'rxjs'
10
10
 
11
11
  import {createStateSourceHook} from '../helpers/createStateSourceHook'
12
12
  import {
13
- useNormalizedSourceOptions,
14
- type WithSourceNameSupport,
15
- } from '../helpers/useNormalizedSourceOptions'
13
+ useNormalizedResourceOptions,
14
+ type WithResourceNameSupport,
15
+ } from '../helpers/useNormalizedResourceOptions'
16
16
 
17
17
  /**
18
18
  * @public
@@ -29,18 +29,18 @@ import {
29
29
  * const activeReleases = useActiveReleases()
30
30
  * ```
31
31
  */
32
- type UseActiveReleases = {
33
- (options?: WithSourceNameSupport<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
- options?: {source?: DocumentSource},
39
+ options?: {resource?: DocumentResource},
40
40
  ) => StateSource<ReleaseDocument[]>,
41
- shouldSuspend: (instance: SanityInstance, options?: {source?: DocumentSource}) =>
41
+ shouldSuspend: (instance: SanityInstance, options?: {resource?: DocumentResource}) =>
42
42
  getActiveReleasesState(instance, options ?? {}).getCurrent() === undefined,
43
- suspender: (instance: SanityInstance, options?: {source?: DocumentSource}) =>
43
+ suspender: (instance: SanityInstance, options?: {resource?: DocumentResource}) =>
44
44
  firstValueFrom(
45
45
  getActiveReleasesState(instance, options ?? {}).observable.pipe(filter(Boolean)),
46
46
  ),
@@ -50,9 +50,9 @@ const useActiveReleasesValue: UseActiveReleases = createStateSourceHook({
50
50
  * @public
51
51
  * @function
52
52
  */
53
- export const useActiveReleases: UseActiveReleases = (
54
- options: WithSourceNameSupport<{source?: DocumentSource}> | undefined,
55
- ) => {
56
- const normalizedOptions = useNormalizedSourceOptions(options ?? {})
53
+ export function useActiveReleases(
54
+ options?: WithResourceNameSupport<SanityConfig> | undefined,
55
+ ): ReleaseDocument[] {
56
+ const normalizedOptions = useNormalizedResourceOptions(options ?? {})
57
57
  return useActiveReleasesValue(normalizedOptions)
58
58
  }
@@ -1,17 +1,14 @@
1
1
  import {
2
- type DatasetHandle,
3
- type DocumentSource,
2
+ type DocumentResource,
4
3
  getPerspectiveState,
5
4
  type SanityInstance,
6
5
  type StateSource,
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
- useNormalizedSourceOptions,
13
- type WithSourceNameSupport,
14
- } from '../helpers/useNormalizedSourceOptions'
11
+ import {useNormalizedResourceOptions} from '../helpers/useNormalizedResourceOptions'
15
12
 
16
13
  /**
17
14
  * @public
@@ -35,18 +32,18 @@ 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
- perspectiveHandle?: {source?: DocumentSource},
42
+ perspectiveHandle?: {resource?: DocumentResource},
46
43
  ) => StateSource<string | string[]>,
47
- shouldSuspend: (instance: SanityInstance, options: {source?: DocumentSource}): boolean =>
44
+ shouldSuspend: (instance: SanityInstance, options: {resource?: DocumentResource}): boolean =>
48
45
  getPerspectiveState(instance, options).getCurrent() === undefined,
49
- suspender: (instance: SanityInstance, _options?: {source?: DocumentSource}) =>
46
+ suspender: (instance: SanityInstance, _options?: {resource?: DocumentResource}) =>
50
47
  firstValueFrom(getPerspectiveState(instance, _options ?? {}).observable.pipe(filter(Boolean))),
51
48
  })
52
49
 
@@ -54,9 +51,7 @@ const usePerspectiveValue: UsePerspective = createStateSourceHook({
54
51
  * @public
55
52
  * @function
56
53
  */
57
- export const usePerspective: UsePerspective = (
58
- options: WithSourceNameSupport<DatasetHandle> | undefined,
59
- ) => {
60
- const normalizedOptions = useNormalizedSourceOptions(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()
@@ -1,7 +0,0 @@
1
- import {type DocumentSource} from '@sanity/sdk'
2
- import {createContext} from 'react'
3
-
4
- /** Context for sources.
5
- * @beta
6
- */
7
- export const SourcesContext = createContext<Record<string, DocumentSource>>({})
@@ -1,107 +0,0 @@
1
- import {type DocumentSource} from '@sanity/sdk'
2
- import {useContext} from 'react'
3
-
4
- import {SourcesContext} from '../../context/SourcesContext'
5
-
6
- /**
7
- * Adds React hook support (sourceName resolution) to core types.
8
- * This wrapper allows hooks to accept `sourceName` as a convenience,
9
- * which is then resolved to a `DocumentSource` at the React layer.
10
- * For now, we are trying to avoid source name resolution in core --
11
- * functions having sources explicitly passed will reduce complexity.
12
- *
13
- * @typeParam T - The core type to extend (must have optional `source` field)
14
- * @beta
15
- */
16
- export type WithSourceNameSupport<T extends {source?: DocumentSource}> = T & {
17
- /**
18
- * Optional name of a source to resolve from context.
19
- * If provided, will be resolved to a `DocumentSource` via `SourcesContext`.
20
- * @beta
21
- */
22
- sourceName?: string
23
- }
24
-
25
- /**
26
- * Pure function that normalizes options by resolving `sourceName` to a `DocumentSource`
27
- * using the provided sources map. Use this when options are only available at call time
28
- * (e.g. inside a callback) and you cannot call the {@link useNormalizedSourceOptions} hook.
29
- *
30
- * @typeParam T - The options type (must include optional source field)
31
- * @param options - Options that may include `sourceName` and/or `source`
32
- * @param sources - Map of source names to DocumentSource (e.g. from SourcesContext)
33
- * @returns Normalized options with `sourceName` removed and `source` resolved
34
- * @internal
35
- */
36
- export function normalizeSourceOptions<T extends {source?: DocumentSource; sourceName?: string}>(
37
- options: T,
38
- sources: Record<string, DocumentSource>,
39
- ): Omit<T, 'sourceName'> {
40
- const {sourceName, ...rest} = options
41
-
42
- if (!sourceName && !options.source) {
43
- return options
44
- }
45
-
46
- if (sourceName && Object.hasOwn(options, 'source')) {
47
- throw new Error(
48
- `Source name ${JSON.stringify(sourceName)} and source ${JSON.stringify(options.source)} cannot be used together.`,
49
- )
50
- }
51
-
52
- let resolvedSource: DocumentSource | undefined
53
-
54
- if (options.source) {
55
- resolvedSource = options.source
56
- }
57
-
58
- if (sourceName && !Object.hasOwn(sources, sourceName)) {
59
- throw new Error(
60
- `There's no source named ${JSON.stringify(sourceName)} in context. Please use <SourceProvider>.`,
61
- )
62
- }
63
-
64
- if (sourceName && sources[sourceName]) {
65
- resolvedSource = sources[sourceName]
66
- }
67
-
68
- return {
69
- ...rest,
70
- source: resolvedSource,
71
- }
72
- }
73
-
74
- /**
75
- * Normalizes hook options by resolving `sourceName` to a `DocumentSource`.
76
- * This hook ensures that options passed to core layer functions only contain
77
- * `source` (never `sourceName`), preventing duplicate cache keys and maintaining
78
- * clean separation between React and core layers.
79
- *
80
- * @typeParam T - The options type (must include optional source field)
81
- * @param options - Hook options that may include `sourceName` and/or `source`
82
- * @returns Normalized options with `sourceName` removed and `source` resolved
83
- *
84
- * @remarks
85
- * Resolution priority:
86
- * 1. If `sourceName` is provided, resolves it via `SourcesContext` and uses that
87
- * 2. Otherwise, uses the inline `source` if provided
88
- * 3. If neither is provided, returns options without a source field
89
- *
90
- * @example
91
- * ```tsx
92
- * function useQuery(options: WithSourceNameSupport<QueryOptions>) {
93
- * const instance = useSanityInstance(options)
94
- * const normalized = useNormalizedOptions(options)
95
- * // normalized now has source but never sourceName
96
- * const queryKey = getQueryKey(normalized)
97
- * }
98
- * ```
99
- *
100
- * @beta
101
- */
102
- export function useNormalizedSourceOptions<
103
- T extends {source?: DocumentSource; sourceName?: string},
104
- >(options: T): Omit<T, 'sourceName'> {
105
- const sources = useContext(SourcesContext)
106
- return normalizeSourceOptions(options, sources)
107
- }