@sanity/sdk-react 2.8.0 → 3.0.0-rc.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 (87) hide show
  1. package/README.md +125 -63
  2. package/dist/index.d.ts +381 -571
  3. package/dist/index.js +435 -366
  4. package/dist/index.js.map +1 -1
  5. package/package.json +7 -9
  6. package/src/_exports/index.ts +4 -0
  7. package/src/_exports/sdk-react.ts +16 -0
  8. package/src/components/SDKProvider.test.tsx +23 -58
  9. package/src/components/SDKProvider.tsx +38 -30
  10. package/src/components/SanityApp.test.tsx +12 -68
  11. package/src/components/SanityApp.tsx +88 -65
  12. package/src/components/auth/AuthBoundary.test.tsx +8 -26
  13. package/src/components/auth/LoginError.tsx +5 -5
  14. package/src/config/handles.ts +53 -0
  15. package/src/context/ComlinkTokenRefresh.test.tsx +27 -10
  16. package/src/context/DefaultResourceContext.ts +10 -0
  17. package/src/context/PerspectiveContext.ts +12 -0
  18. package/src/context/ResourceProvider.test.tsx +99 -19
  19. package/src/context/ResourceProvider.tsx +103 -37
  20. package/src/context/ResourcesContext.tsx +7 -0
  21. package/src/context/SDKStudioContext.test.tsx +33 -28
  22. package/src/context/SDKStudioContext.ts +6 -0
  23. package/src/context/renderSanityApp.test.tsx +49 -151
  24. package/src/context/renderSanityApp.tsx +8 -12
  25. package/src/hooks/agent/agentActions.test.tsx +1 -1
  26. package/src/hooks/agent/agentActions.ts +56 -19
  27. package/src/hooks/auth/useDashboardOrganizationId.test.tsx +8 -2
  28. package/src/hooks/auth/useVerifyOrgProjects.test.tsx +32 -8
  29. package/src/hooks/client/useClient.test.tsx +4 -1
  30. package/src/hooks/client/useClient.ts +0 -1
  31. package/src/hooks/context/useDefaultResource.test.tsx +25 -0
  32. package/src/hooks/context/useDefaultResource.ts +30 -0
  33. package/src/hooks/context/useSanityInstance.test.tsx +2 -140
  34. package/src/hooks/context/useSanityInstance.ts +9 -53
  35. package/src/hooks/dashboard/useDispatchIntent.test.ts +24 -15
  36. package/src/hooks/dashboard/useDispatchIntent.ts +7 -7
  37. package/src/hooks/dashboard/useManageFavorite.test.tsx +34 -94
  38. package/src/hooks/dashboard/useManageFavorite.ts +16 -10
  39. package/src/hooks/dashboard/useNavigateToStudioDocument.test.ts +7 -5
  40. package/src/hooks/dashboard/useNavigateToStudioDocument.ts +6 -2
  41. package/src/hooks/dashboard/useRecordDocumentHistoryEvent.test.ts +2 -0
  42. package/src/hooks/dashboard/useRecordDocumentHistoryEvent.ts +2 -1
  43. package/src/hooks/dashboard/utils/useResourceIdFromDocumentHandle.test.ts +17 -38
  44. package/src/hooks/dashboard/utils/useResourceIdFromDocumentHandle.ts +12 -19
  45. package/src/hooks/datasets/useDatasets.test.ts +8 -22
  46. package/src/hooks/datasets/useDatasets.ts +8 -16
  47. package/src/hooks/document/useApplyDocumentActions.test.ts +98 -52
  48. package/src/hooks/document/useApplyDocumentActions.ts +35 -37
  49. package/src/hooks/document/useDocument.test.tsx +8 -37
  50. package/src/hooks/document/useDocument.ts +78 -129
  51. package/src/hooks/document/useDocumentEvent.test.tsx +7 -19
  52. package/src/hooks/document/useDocumentEvent.ts +21 -19
  53. package/src/hooks/document/useDocumentPermissions.test.tsx +75 -84
  54. package/src/hooks/document/useDocumentPermissions.ts +41 -28
  55. package/src/hooks/document/useDocumentSyncStatus.test.ts +13 -3
  56. package/src/hooks/document/useDocumentSyncStatus.ts +19 -14
  57. package/src/hooks/document/useEditDocument.test.tsx +28 -70
  58. package/src/hooks/document/useEditDocument.ts +29 -149
  59. package/src/hooks/documents/useDocuments.test.tsx +44 -64
  60. package/src/hooks/documents/useDocuments.ts +19 -25
  61. package/src/hooks/helpers/createCallbackHook.test.tsx +19 -13
  62. package/src/hooks/helpers/createStateSourceHook.test.tsx +10 -10
  63. package/src/hooks/helpers/createStateSourceHook.tsx +2 -4
  64. package/src/hooks/helpers/useNormalizedResourceOptions.test.ts +65 -0
  65. package/src/hooks/helpers/useNormalizedResourceOptions.ts +127 -0
  66. package/src/hooks/paginatedDocuments/usePaginatedDocuments.test.tsx +27 -34
  67. package/src/hooks/paginatedDocuments/usePaginatedDocuments.ts +19 -20
  68. package/src/hooks/presence/usePresence.test.tsx +71 -9
  69. package/src/hooks/presence/usePresence.ts +28 -3
  70. package/src/hooks/preview/useDocumentPreview.test.tsx +85 -193
  71. package/src/hooks/preview/useDocumentPreview.tsx +42 -62
  72. package/src/hooks/projection/useDocumentProjection.test.tsx +9 -37
  73. package/src/hooks/projection/useDocumentProjection.ts +9 -82
  74. package/src/hooks/projects/useProject.test.ts +1 -2
  75. package/src/hooks/projects/useProject.ts +7 -8
  76. package/src/hooks/query/useQuery.test.tsx +5 -6
  77. package/src/hooks/query/useQuery.ts +12 -91
  78. package/src/hooks/releases/useActiveReleases.test.tsx +2 -2
  79. package/src/hooks/releases/useActiveReleases.ts +25 -13
  80. package/src/hooks/releases/usePerspective.test.tsx +9 -17
  81. package/src/hooks/releases/usePerspective.ts +29 -18
  82. package/src/hooks/users/useUser.test.tsx +9 -3
  83. package/src/hooks/users/useUser.ts +1 -1
  84. package/src/hooks/users/useUsers.test.tsx +5 -2
  85. package/src/hooks/users/useUsers.ts +1 -1
  86. package/src/context/SourcesContext.tsx +0 -7
  87. package/src/hooks/helpers/useNormalizedSourceOptions.ts +0 -85
@@ -1,4 +1,9 @@
1
- import {type DocumentSource, isStudioConfig, type SanityConfig} from '@sanity/sdk'
1
+ import {
2
+ DEFAULT_RESOURCE_NAME,
3
+ type DocumentResource,
4
+ isStudioConfig,
5
+ type SanityConfig,
6
+ } from '@sanity/sdk'
2
7
  import {type ReactElement, useContext, useEffect, useMemo} from 'react'
3
8
 
4
9
  import {SDKStudioContext, type StudioWorkspaceHandle} from '../context/SDKStudioContext'
@@ -11,15 +16,17 @@ import {isInIframe, isLocalUrl} from './utils'
11
16
  */
12
17
  export interface SanityAppProps {
13
18
  /**
14
- * One or more SanityConfig objects providing a project ID and dataset name.
15
- * Optional when `SanityApp` is rendered inside an `SDKStudioContext` provider
16
- * (e.g. inside Sanity Studio) — the config is derived from the workspace
17
- * automatically.
19
+ * Core configuration for the SDK instance (auth, studio, perspective).
20
+ * Optional when `SanityApp` is rendered inside an `SDKStudioContext`
21
+ * provider (e.g. inside Sanity Studio) — the config is derived from
22
+ * the workspace automatically.
18
23
  */
19
- config?: SanityConfig | SanityConfig[]
20
- /** @deprecated use the `config` prop instead. */
21
- sanityConfigs?: SanityConfig[]
22
- sources?: Record<string, DocumentSource>
24
+ config?: SanityConfig
25
+ /**
26
+ * Named document resources for the application. The resource keyed `"default"`
27
+ * is used automatically when no explicit resource is specified in hooks.
28
+ */
29
+ resources?: Record<string, DocumentResource>
23
30
  children: React.ReactNode
24
31
  /* Fallback content to show when child components are suspending. Same as the `fallback` prop for React Suspense. */
25
32
  fallback: React.ReactNode
@@ -28,16 +35,22 @@ export interface SanityAppProps {
28
35
  const REDIRECT_URL = 'https://sanity.io/welcome'
29
36
 
30
37
  /**
31
- * Derive a SanityConfig from a Studio workspace handle.
32
- * Maps the workspace's projectId, dataset, and reactive auth token into
33
- * the SDK's config shape.
38
+ * Derive a SanityConfig and resources map from a Studio workspace handle.
34
39
  */
35
- function deriveConfigFromWorkspace(workspace: StudioWorkspaceHandle): SanityConfig {
40
+ function deriveFromWorkspace(workspace: StudioWorkspaceHandle): {
41
+ config: SanityConfig
42
+ resources: Record<string, DocumentResource>
43
+ } {
36
44
  return {
37
- projectId: workspace.projectId,
38
- dataset: workspace.dataset,
39
- studio: {
40
- auth: workspace.auth.token ? {token: workspace.auth.token} : undefined,
45
+ config: {
46
+ studio: {
47
+ authenticated: workspace.authenticated,
48
+ auth: workspace.auth.token ? {token: workspace.auth.token} : undefined,
49
+ projectId: workspace.projectId,
50
+ },
51
+ },
52
+ resources: {
53
+ [DEFAULT_RESOURCE_NAME]: {projectId: workspace.projectId, dataset: workspace.dataset},
41
54
  },
42
55
  }
43
56
  }
@@ -49,57 +62,49 @@ function deriveConfigFromWorkspace(workspace: StudioWorkspaceHandle): SanityConf
49
62
  * as well as application context and state which is used by the Sanity React hooks. Your application
50
63
  * must be wrapped with the SanityApp component to function properly.
51
64
  *
52
- * The `config` prop on the SanityApp component accepts either a single {@link SanityConfig} object, or an array of them.
53
- * This allows your app to work with one or more of your organization's datasets.
54
- *
55
- * When rendered inside a Sanity Studio that provides `SDKStudioContext`, the `config` prop is
56
- * optional — `SanityApp` will automatically derive `projectId`, `dataset`, and auth from the
57
- * Studio workspace.
65
+ * The `config` prop accepts a {@link SanityConfig} object. Use the `resources` prop to declare
66
+ * one or more named data resources for your app.
58
67
  *
59
- * @remarks
60
- * When passing multiple SanityConfig objects to the `config` prop, the first configuration in the array becomes the default
61
- * configuration used by the App SDK Hooks.
68
+ * When rendered inside a Sanity Studio that provides `SDKStudioContext`, the `config` and `resources`
69
+ * props are optional `SanityApp` will automatically derive them from the Studio workspace.
62
70
  *
63
- * When both `config` and `SDKStudioContext` are available, the explicit `config` takes precedence.
71
+ * When both `config` and `SDKStudioContext` are available, the explicit props take precedence.
64
72
  *
65
73
  * @category Components
66
74
  * @param props - Your Sanity configuration and the React children to render
67
75
  * @returns Your Sanity application, integrated with your Sanity configuration and application context
68
76
  *
69
- * @example
77
+ * @example Single project
70
78
  * ```tsx
71
- * import { SanityApp, type SanityConfig } from '@sanity/sdk-react'
79
+ * import { SanityApp } from '@sanity/sdk-react'
72
80
  *
73
- * import MyAppRoot from './Root'
74
- *
75
- * // Single project configuration
76
- * const mySanityConfig: SanityConfig = {
77
- * projectId: 'my-project-id',
78
- * dataset: 'production',
81
+ * export default function MyApp() {
82
+ * return (
83
+ * <SanityApp
84
+ * resources={{
85
+ * default: { projectId: 'my-project-id', dataset: 'production' },
86
+ * }}
87
+ * fallback={<div>Loading…</div>}
88
+ * >
89
+ * <MyAppRoot />
90
+ * </SanityApp>
91
+ * )
79
92
  * }
93
+ * ```
80
94
  *
81
- * // Or multiple project configurations
82
- * const multipleConfigs: SanityConfig[] = [
83
- * // Configuration for your main project. This will be used as the default project for hooks.
84
- * {
85
- * projectId: 'marketing-website-project',
86
- * dataset: 'production',
87
- * },
88
- * // Configuration for a separate blog project
89
- * {
90
- * projectId: 'blog-project',
91
- * dataset: 'production',
92
- * },
93
- * // Configuration for a separate ecommerce project
94
- * {
95
- * projectId: 'ecommerce-project',
96
- * dataset: 'production',
97
- * }
98
- * ]
95
+ * @example Multiple resources
96
+ * ```tsx
97
+ * import { SanityApp } from '@sanity/sdk-react'
99
98
  *
100
99
  * export default function MyApp() {
101
100
  * return (
102
- * <SanityApp config={mySanityConfig} fallback={<div>Loading…</div>}>
101
+ * <SanityApp
102
+ * resources={{
103
+ * default: { projectId: 'abc123', dataset: 'production' },
104
+ * 'blog-project': { projectId: 'def456', dataset: 'production' },
105
+ * }}
106
+ * fallback={<div>Loading…</div>}
107
+ * >
103
108
  * <MyAppRoot />
104
109
  * </SanityApp>
105
110
  * )
@@ -110,29 +115,42 @@ export function SanityApp({
110
115
  children,
111
116
  fallback,
112
117
  config: configProp,
118
+ resources: resourcesProp,
113
119
  ...props
114
120
  }: SanityAppProps): ReactElement {
115
121
  const studioWorkspace = useContext(SDKStudioContext)
116
122
 
117
- // Derive config: explicit config takes precedence, then Studio context
118
- const resolvedConfig = useMemo(() => {
119
- if (configProp) return configProp
120
- if (studioWorkspace) return deriveConfigFromWorkspace(studioWorkspace)
121
- return []
122
- }, [configProp, studioWorkspace])
123
+ const derived = useMemo(() => {
124
+ if (studioWorkspace && !configProp && !resourcesProp) {
125
+ return deriveFromWorkspace(studioWorkspace)
126
+ }
127
+ return null
128
+ }, [configProp, resourcesProp, studioWorkspace])
129
+
130
+ const resolvedConfig = useMemo<SanityConfig>(() => {
131
+ if (configProp) {
132
+ return configProp
133
+ }
134
+ if (derived) return derived.config
135
+ return {}
136
+ }, [configProp, derived])
137
+
138
+ const resolvedResources = useMemo<Record<string, DocumentResource>>(() => {
139
+ if (resourcesProp) return resourcesProp
140
+ if (derived) return derived.resources
141
+ return {}
142
+ }, [resourcesProp, derived])
123
143
 
124
144
  useEffect(() => {
125
145
  let timeout: NodeJS.Timeout | undefined
126
- const primaryConfig = Array.isArray(resolvedConfig) ? resolvedConfig[0] : resolvedConfig
127
146
  const shouldRedirectWithoutConfig =
128
- configProp === undefined && !studioWorkspace && !primaryConfig
147
+ configProp === undefined && !studioWorkspace && !resolvedConfig
129
148
 
130
149
  if (
131
150
  !isInIframe() &&
132
151
  !isLocalUrl(window) &&
133
- (shouldRedirectWithoutConfig || (!!primaryConfig && !isStudioConfig(primaryConfig)))
152
+ (shouldRedirectWithoutConfig || (!!resolvedConfig && !isStudioConfig(resolvedConfig)))
134
153
  ) {
135
- // If the app is not running in an iframe and is not a local url, redirect to core.
136
154
  timeout = setTimeout(() => {
137
155
  // eslint-disable-next-line no-console
138
156
  console.warn('Redirecting to core', REDIRECT_URL)
@@ -143,7 +161,12 @@ export function SanityApp({
143
161
  }, [configProp, resolvedConfig, studioWorkspace])
144
162
 
145
163
  return (
146
- <SDKProvider {...props} fallback={fallback} config={resolvedConfig}>
164
+ <SDKProvider
165
+ {...props}
166
+ fallback={fallback}
167
+ config={resolvedConfig}
168
+ resources={resolvedResources}
169
+ >
147
170
  {children}
148
171
  </SDKProvider>
149
172
  )
@@ -1,4 +1,4 @@
1
- import {AuthStateType, type SanityConfig} from '@sanity/sdk'
1
+ import {AuthStateType} from '@sanity/sdk'
2
2
  import {render, screen, waitFor} from '@testing-library/react'
3
3
  import React from 'react'
4
4
  import {type FallbackProps} from 'react-error-boundary'
@@ -110,24 +110,6 @@ describe('AuthBoundary', () => {
110
110
  const mockUseVerifyOrgProjects = vi.mocked(useVerifyOrgProjects)
111
111
  const testProjectIds = ['proj-test'] // Example project ID for tests
112
112
 
113
- // Mock Sanity instance
114
- const mockSanityInstance = {
115
- instanceId: 'test-instance-id',
116
- config: {
117
- projectId: 'test-project',
118
- dataset: 'test-dataset',
119
- },
120
- isDisposed: () => false,
121
- dispose: () => {},
122
- onDispose: () => () => {},
123
- getParent: () => undefined,
124
- createChild: (config: SanityConfig) => ({
125
- ...mockSanityInstance,
126
- config: {...mockSanityInstance.config, ...config},
127
- }),
128
- match: () => undefined,
129
- }
130
-
131
113
  beforeEach(() => {
132
114
  vi.clearAllMocks()
133
115
  consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
@@ -166,7 +148,7 @@ describe('AuthBoundary', () => {
166
148
  isExchangingToken: false,
167
149
  })
168
150
  const {container} = render(
169
- <ResourceProvider projectId="p" dataset="d" fallback={null}>
151
+ <ResourceProvider resource={{projectId: 'p', dataset: 'd'}} fallback={null}>
170
152
  <AuthBoundary projectIds={testProjectIds}>Protected Content</AuthBoundary>
171
153
  </ResourceProvider>,
172
154
  )
@@ -183,7 +165,7 @@ describe('AuthBoundary', () => {
183
165
  token: 'exampleToken',
184
166
  })
185
167
  render(
186
- <ResourceProvider projectId="p" dataset="d" fallback={null}>
168
+ <ResourceProvider resource={{projectId: 'p', dataset: 'd'}} fallback={null}>
187
169
  <AuthBoundary projectIds={testProjectIds}>Protected Content</AuthBoundary>
188
170
  </ResourceProvider>,
189
171
  )
@@ -197,7 +179,7 @@ describe('AuthBoundary', () => {
197
179
  error: new Error('test error'),
198
180
  })
199
181
  render(
200
- <ResourceProvider projectId="p" dataset="d" fallback={null}>
182
+ <ResourceProvider resource={{projectId: 'p', dataset: 'd'}} fallback={null}>
201
183
  <AuthBoundary projectIds={testProjectIds}>Protected Content</AuthBoundary>
202
184
  </ResourceProvider>,
203
185
  )
@@ -214,7 +196,7 @@ describe('AuthBoundary', () => {
214
196
 
215
197
  it('renders children when logged in and org verification passes', () => {
216
198
  render(
217
- <ResourceProvider projectId="p" dataset="d" fallback={null}>
199
+ <ResourceProvider resource={{projectId: 'p', dataset: 'd'}} fallback={null}>
218
200
  <AuthBoundary projectIds={testProjectIds}>Protected Content</AuthBoundary>
219
201
  </ResourceProvider>,
220
202
  )
@@ -236,7 +218,7 @@ describe('AuthBoundary', () => {
236
218
 
237
219
  // Need to catch the error thrown during render. ErrorBoundary mock handles this.
238
220
  render(
239
- <ResourceProvider projectId="p" dataset="d" fallback={null}>
221
+ <ResourceProvider resource={{projectId: 'p', dataset: 'd'}} fallback={null}>
240
222
  <AuthBoundary verifyOrganization={true} projectIds={testProjectIds}>
241
223
  <div>Protected Content</div>
242
224
  </AuthBoundary>
@@ -268,7 +250,7 @@ describe('AuthBoundary', () => {
268
250
  })
269
251
 
270
252
  render(
271
- <ResourceProvider projectId="p" dataset="d" fallback={null}>
253
+ <ResourceProvider resource={{projectId: 'p', dataset: 'd'}} fallback={null}>
272
254
  <AuthBoundary verifyOrganization={false} projectIds={testProjectIds}>
273
255
  <div>Protected Content</div>
274
256
  </AuthBoundary>
@@ -293,7 +275,7 @@ describe('AuthBoundary', () => {
293
275
  mockUseVerifyOrgProjects.mockImplementation(() => null)
294
276
 
295
277
  render(
296
- <ResourceProvider projectId="p" dataset="d" fallback={null}>
278
+ <ResourceProvider resource={{projectId: 'p', dataset: 'd'}} fallback={null}>
297
279
  <AuthBoundary projectIds={testProjectIds}>
298
280
  <div>Protected Content</div>
299
281
  </AuthBoundary>
@@ -4,15 +4,16 @@ import {
4
4
  AuthStateType,
5
5
  getClientErrorApiBody,
6
6
  getClientErrorApiDescription,
7
+ isDatasetResource,
7
8
  isProjectUserNotFoundClientError,
8
9
  } from '@sanity/sdk'
9
- import {useCallback, useEffect, useState} from 'react'
10
+ import {useCallback, useContext, useEffect, useState} from 'react'
10
11
  import {type FallbackProps} from 'react-error-boundary'
11
12
 
13
+ import {ResourceContext} from '../../context/DefaultResourceContext'
12
14
  import {useAuthState} from '../../hooks/auth/useAuthState'
13
15
  import {useLogOut} from '../../hooks/auth/useLogOut'
14
16
  import {useWindowConnection} from '../../hooks/comlink/useWindowConnection'
15
- import {useSanityInstance} from '../../hooks/context/useSanityInstance'
16
17
  import {Error} from '../errors/Error'
17
18
  import {AuthError} from './AuthError'
18
19
  import {ConfigurationError} from './ConfigurationError'
@@ -39,9 +40,8 @@ export function LoginError({error, resetErrorBoundary}: LoginErrorProps): React.
39
40
 
40
41
  const logout = useLogOut()
41
42
  const authState = useAuthState()
42
- const {
43
- config: {projectId},
44
- } = useSanityInstance()
43
+ const resource = useContext(ResourceContext)
44
+ const projectId = resource && isDatasetResource(resource) ? resource.projectId : undefined
45
45
 
46
46
  const [authErrorMessage, setAuthErrorMessage] = useState(
47
47
  'Please try again or contact support if the problem persists.',
@@ -0,0 +1,53 @@
1
+ import {type DocumentResource, type PerspectiveHandle} from '@sanity/sdk'
2
+
3
+ // React-layer types — shadow core equivalents when imported from @sanity/sdk-react.
4
+ // resource is optional here and resolved from context by normalization,
5
+ // whereas core's DocumentHandle/ResourceHandle require resource explicitly.
6
+
7
+ /**
8
+ * SDK React ResourceHandle with optional explicit resource field.
9
+ * Resource is resolved from context when not provided.
10
+ * When a `resourceName` is provided, the resource will be resolved from the context using the `ResourcesContext`,
11
+ * if there is a matching resource by that name.
12
+ * @public
13
+ */
14
+ export interface ResourceHandle<
15
+ TProjectId extends string = string,
16
+ TDataset extends string = string,
17
+ > {
18
+ resource?: DocumentResource<TProjectId, TDataset>
19
+ resourceName?: string
20
+ perspective?: PerspectiveHandle['perspective']
21
+ }
22
+
23
+ /**
24
+ * SDK React DocumentTypeHandle with optional explicit resource field.
25
+ * Resource is resolved from context when not provided.
26
+ * When a `resourceName` is provided, the resource will be resolved from the context using the `ResourcesContext`,
27
+ * if there is a matching resource by that name.
28
+ * @public
29
+ */
30
+ export interface DocumentTypeHandle<
31
+ TDocumentType extends string = string,
32
+ TDataset extends string = string,
33
+ TProjectId extends string = string,
34
+ > extends ResourceHandle<TProjectId, TDataset> {
35
+ documentType: TDocumentType
36
+ documentId?: string
37
+ liveEdit?: boolean
38
+ }
39
+
40
+ /**
41
+ * SDK React DocumentHandle with optional explicit resource field.
42
+ * Resource is resolved from context when not provided.
43
+ * When a `resourceName` is provided, the resource will be resolved from the context using the `ResourcesContext`,
44
+ * if there is a matching resource by that name.
45
+ * @public
46
+ */
47
+ export interface DocumentHandle<
48
+ TDocumentType extends string = string,
49
+ TDataset extends string = string,
50
+ TProjectId extends string = string,
51
+ > extends DocumentTypeHandle<TDocumentType, TDataset, TProjectId> {
52
+ documentId: string
53
+ }
@@ -56,7 +56,10 @@ describe('ComlinkTokenRefresh', () => {
56
56
  it('should not request new token on 401 if not in dashboard', async () => {
57
57
  mockUseAuthState.mockReturnValue({type: AuthStateType.LOGGED_IN})
58
58
  const {rerender} = render(
59
- <ResourceProvider projectId="test-project" dataset="test-dataset" fallback={null}>
59
+ <ResourceProvider
60
+ resource={{projectId: 'test-project', dataset: 'test-dataset'}}
61
+ fallback={null}
62
+ >
60
63
  <ComlinkTokenRefreshProvider>
61
64
  <div>Test</div>
62
65
  </ComlinkTokenRefreshProvider>
@@ -69,7 +72,10 @@ describe('ComlinkTokenRefresh', () => {
69
72
  })
70
73
  act(() => {
71
74
  rerender(
72
- <ResourceProvider projectId="test-project" dataset="test-dataset" fallback={null}>
75
+ <ResourceProvider
76
+ resource={{projectId: 'test-project', dataset: 'test-dataset'}}
77
+ fallback={null}
78
+ >
73
79
  <ComlinkTokenRefreshProvider>
74
80
  <div>Test</div>
75
81
  </ComlinkTokenRefreshProvider>
@@ -92,7 +98,10 @@ describe('ComlinkTokenRefresh', () => {
92
98
  it('should initialize useWindowConnection with correct parameters when not in studio mode', () => {
93
99
  // Simulate studio mode disabled by default
94
100
  render(
95
- <ResourceProvider projectId="test-project" dataset="test-dataset" fallback={null}>
101
+ <ResourceProvider
102
+ resource={{projectId: 'test-project', dataset: 'test-dataset'}}
103
+ fallback={null}
104
+ >
96
105
  <ComlinkTokenRefreshProvider>
97
106
  <div>Test</div>
98
107
  </ComlinkTokenRefreshProvider>
@@ -124,7 +133,10 @@ describe('ComlinkTokenRefresh', () => {
124
133
  document.body.appendChild(errorContainer)
125
134
 
126
135
  render(
127
- <ResourceProvider projectId="test-project" dataset="test-dataset" fallback={null}>
136
+ <ResourceProvider
137
+ resource={{projectId: 'test-project', dataset: 'test-dataset'}}
138
+ fallback={null}
139
+ >
128
140
  <ComlinkTokenRefreshProvider>
129
141
  <div>Test</div>
130
142
  </ComlinkTokenRefreshProvider>
@@ -138,11 +150,10 @@ describe('ComlinkTokenRefresh', () => {
138
150
  expect(mockSetAuthToken).toHaveBeenCalledWith(expect.any(Object), 'new-token')
139
151
  expect(mockFetch).toHaveBeenCalledTimes(1)
140
152
  expect(mockFetch).toHaveBeenCalledWith('dashboard/v1/auth/tokens/create')
141
- // Assert setAuthToken was called with instance matching provider config
153
+ // Assert setAuthToken was called with a SanityInstance
142
154
  const instanceArg = mockSetAuthToken.mock.calls[0][0]
143
- expect(instanceArg.config).toEqual(
144
- expect.objectContaining({projectId: 'test-project', dataset: 'test-dataset'}),
145
- )
155
+ expect(instanceArg).toHaveProperty('instanceId')
156
+ expect(instanceArg).toHaveProperty('config')
146
157
  // Unauthorized error container should be removed
147
158
  expect(document.getElementById('__sanityError')).toBeNull()
148
159
  })
@@ -155,7 +166,10 @@ describe('ComlinkTokenRefresh', () => {
155
166
  mockFetch.mockResolvedValueOnce({token: null})
156
167
 
157
168
  render(
158
- <ResourceProvider projectId="test-project" dataset="test-dataset" fallback={null}>
169
+ <ResourceProvider
170
+ resource={{projectId: 'test-project', dataset: 'test-dataset'}}
171
+ fallback={null}
172
+ >
159
173
  <ComlinkTokenRefreshProvider>
160
174
  <div>Test</div>
161
175
  </ComlinkTokenRefreshProvider>
@@ -177,7 +191,10 @@ describe('ComlinkTokenRefresh', () => {
177
191
  mockFetch.mockRejectedValueOnce(new Error('Fetch failed'))
178
192
 
179
193
  render(
180
- <ResourceProvider projectId="test-project" dataset="test-dataset" fallback={null}>
194
+ <ResourceProvider
195
+ resource={{projectId: 'test-project', dataset: 'test-dataset'}}
196
+ fallback={null}
197
+ >
181
198
  <ComlinkTokenRefreshProvider>
182
199
  <div>Test</div>
183
200
  </ComlinkTokenRefreshProvider>
@@ -0,0 +1,10 @@
1
+ import {type DocumentResource} from '@sanity/sdk'
2
+ import {createContext} from 'react'
3
+
4
+ /**
5
+ * Provides the active resource for a subtree.
6
+ * Set by `ResourceProvider` so hooks resolve the correct project/dataset
7
+ * without requiring an explicit `resource` option.
8
+ * @internal
9
+ */
10
+ export const ResourceContext = createContext<DocumentResource | undefined>(undefined)
@@ -0,0 +1,12 @@
1
+ import {type PerspectiveHandle} from '@sanity/sdk'
2
+ import {createContext} from 'react'
3
+
4
+ /**
5
+ * Provides a perspective override for nested subtrees.
6
+ * Set by `ResourceProvider` so hooks resolve the correct perspective
7
+ * without requiring an explicit `perspective` option.
8
+ * @internal
9
+ */
10
+ export const PerspectiveContext = createContext<PerspectiveHandle['perspective'] | undefined>(
11
+ undefined,
12
+ )