@sanity/sdk-react 2.7.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 (88) hide show
  1. package/README.md +125 -63
  2. package/dist/index.d.ts +381 -571
  3. package/dist/index.js +450 -366
  4. package/dist/index.js.map +1 -1
  5. package/package.json +6 -8
  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 +11 -26
  13. package/src/components/auth/LoginError.test.tsx +5 -0
  14. package/src/components/auth/LoginError.tsx +23 -2
  15. package/src/config/handles.ts +53 -0
  16. package/src/context/ComlinkTokenRefresh.test.tsx +27 -10
  17. package/src/context/DefaultResourceContext.ts +10 -0
  18. package/src/context/PerspectiveContext.ts +12 -0
  19. package/src/context/ResourceProvider.test.tsx +99 -19
  20. package/src/context/ResourceProvider.tsx +103 -37
  21. package/src/context/ResourcesContext.tsx +7 -0
  22. package/src/context/SDKStudioContext.test.tsx +33 -28
  23. package/src/context/SDKStudioContext.ts +6 -0
  24. package/src/context/renderSanityApp.test.tsx +49 -151
  25. package/src/context/renderSanityApp.tsx +8 -12
  26. package/src/hooks/agent/agentActions.test.tsx +1 -1
  27. package/src/hooks/agent/agentActions.ts +56 -19
  28. package/src/hooks/auth/useDashboardOrganizationId.test.tsx +8 -2
  29. package/src/hooks/auth/useVerifyOrgProjects.test.tsx +32 -8
  30. package/src/hooks/client/useClient.test.tsx +4 -1
  31. package/src/hooks/client/useClient.ts +0 -1
  32. package/src/hooks/context/useDefaultResource.test.tsx +25 -0
  33. package/src/hooks/context/useDefaultResource.ts +30 -0
  34. package/src/hooks/context/useSanityInstance.test.tsx +2 -140
  35. package/src/hooks/context/useSanityInstance.ts +9 -53
  36. package/src/hooks/dashboard/useDispatchIntent.test.ts +24 -15
  37. package/src/hooks/dashboard/useDispatchIntent.ts +7 -7
  38. package/src/hooks/dashboard/useManageFavorite.test.tsx +34 -94
  39. package/src/hooks/dashboard/useManageFavorite.ts +16 -10
  40. package/src/hooks/dashboard/useNavigateToStudioDocument.test.ts +7 -5
  41. package/src/hooks/dashboard/useNavigateToStudioDocument.ts +6 -2
  42. package/src/hooks/dashboard/useRecordDocumentHistoryEvent.test.ts +2 -0
  43. package/src/hooks/dashboard/useRecordDocumentHistoryEvent.ts +2 -1
  44. package/src/hooks/dashboard/utils/useResourceIdFromDocumentHandle.test.ts +17 -38
  45. package/src/hooks/dashboard/utils/useResourceIdFromDocumentHandle.ts +12 -19
  46. package/src/hooks/datasets/useDatasets.test.ts +8 -22
  47. package/src/hooks/datasets/useDatasets.ts +8 -16
  48. package/src/hooks/document/useApplyDocumentActions.test.ts +98 -52
  49. package/src/hooks/document/useApplyDocumentActions.ts +35 -37
  50. package/src/hooks/document/useDocument.test.tsx +8 -37
  51. package/src/hooks/document/useDocument.ts +78 -129
  52. package/src/hooks/document/useDocumentEvent.test.tsx +7 -19
  53. package/src/hooks/document/useDocumentEvent.ts +21 -19
  54. package/src/hooks/document/useDocumentPermissions.test.tsx +75 -84
  55. package/src/hooks/document/useDocumentPermissions.ts +41 -28
  56. package/src/hooks/document/useDocumentSyncStatus.test.ts +13 -3
  57. package/src/hooks/document/useDocumentSyncStatus.ts +19 -14
  58. package/src/hooks/document/useEditDocument.test.tsx +28 -70
  59. package/src/hooks/document/useEditDocument.ts +29 -149
  60. package/src/hooks/documents/useDocuments.test.tsx +44 -64
  61. package/src/hooks/documents/useDocuments.ts +19 -25
  62. package/src/hooks/helpers/createCallbackHook.test.tsx +19 -13
  63. package/src/hooks/helpers/createStateSourceHook.test.tsx +10 -10
  64. package/src/hooks/helpers/createStateSourceHook.tsx +2 -4
  65. package/src/hooks/helpers/useNormalizedResourceOptions.test.ts +65 -0
  66. package/src/hooks/helpers/useNormalizedResourceOptions.ts +127 -0
  67. package/src/hooks/paginatedDocuments/usePaginatedDocuments.test.tsx +27 -34
  68. package/src/hooks/paginatedDocuments/usePaginatedDocuments.ts +19 -20
  69. package/src/hooks/presence/usePresence.test.tsx +71 -9
  70. package/src/hooks/presence/usePresence.ts +28 -3
  71. package/src/hooks/preview/useDocumentPreview.test.tsx +85 -193
  72. package/src/hooks/preview/useDocumentPreview.tsx +42 -62
  73. package/src/hooks/projection/useDocumentProjection.test.tsx +9 -37
  74. package/src/hooks/projection/useDocumentProjection.ts +9 -82
  75. package/src/hooks/projects/useProject.test.ts +1 -2
  76. package/src/hooks/projects/useProject.ts +7 -8
  77. package/src/hooks/query/useQuery.test.tsx +5 -6
  78. package/src/hooks/query/useQuery.ts +12 -91
  79. package/src/hooks/releases/useActiveReleases.test.tsx +2 -2
  80. package/src/hooks/releases/useActiveReleases.ts +25 -13
  81. package/src/hooks/releases/usePerspective.test.tsx +9 -17
  82. package/src/hooks/releases/usePerspective.ts +29 -18
  83. package/src/hooks/users/useUser.test.tsx +9 -3
  84. package/src/hooks/users/useUser.ts +1 -1
  85. package/src/hooks/users/useUsers.test.tsx +5 -2
  86. package/src/hooks/users/useUsers.ts +1 -1
  87. package/src/context/SourcesContext.tsx +0 -7
  88. package/src/hooks/helpers/useNormalizedSourceOptions.ts +0 -85
@@ -1,14 +1,15 @@
1
- import {type SanityConfig, type SanityInstance} from '@sanity/sdk'
1
+ import {type DocumentResource, type SanityConfig, type SanityInstance} from '@sanity/sdk'
2
2
  import {act, render, screen} from '@testing-library/react'
3
- import {StrictMode, use, useEffect} from 'react'
4
- import {describe, expect, it} from 'vitest'
3
+ import {StrictMode, use, useContext, useEffect} from 'react'
4
+ import {describe, expect, it, vi} from 'vitest'
5
5
 
6
+ import {ResourceContext} from './DefaultResourceContext'
7
+ import {PerspectiveContext} from './PerspectiveContext'
6
8
  import {ResourceProvider} from './ResourceProvider'
7
9
  import {SanityInstanceContext} from './SanityInstanceContext'
8
10
 
9
- const testConfig: SanityConfig = {
10
- projectId: 'test-project',
11
- dataset: 'test-dataset',
11
+ const testConfig = {
12
+ resource: {projectId: 'test-project', dataset: 'test-dataset'} as DocumentResource,
12
13
  }
13
14
 
14
15
  function promiseWithResolvers<T = void>(): {
@@ -37,6 +38,7 @@ describe('ResourceProvider', () => {
37
38
  })
38
39
 
39
40
  it('shows fallback during loading', async () => {
41
+ const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
40
42
  const {promise, resolve} = promiseWithResolvers()
41
43
  function SuspendingChild(): React.ReactNode {
42
44
  throw promise
@@ -52,6 +54,8 @@ describe('ResourceProvider', () => {
52
54
  act(() => {
53
55
  resolve()
54
56
  })
57
+ await new Promise((r) => setTimeout(r, 0))
58
+ consoleSpy.mockRestore()
55
59
  })
56
60
 
57
61
  it('creates root instance when no parent context exists', async () => {
@@ -70,32 +74,105 @@ describe('ResourceProvider', () => {
70
74
  )
71
75
 
72
76
  await expect(promise).resolves.toMatchObject({
73
- config: testConfig,
77
+ config: {} as SanityConfig,
74
78
  isDisposed: expect.any(Function),
75
79
  })
76
80
  })
77
81
 
78
- it('creates child instance when parent context exists', async () => {
79
- const parentConfig: SanityConfig = {...testConfig, dataset: 'parent-dataset'}
80
- const child = promiseWithResolvers<SanityInstance | null>()
82
+ it('provides ResourceContext and PerspectiveContext at root', async () => {
83
+ const captured = promiseWithResolvers<{
84
+ resource: DocumentResource | undefined
85
+ perspective: unknown
86
+ }>()
81
87
 
82
- const CaptureInstance = () => {
83
- const childInstance = use(SanityInstanceContext)
84
- useEffect(() => child.resolve(childInstance), [childInstance])
88
+ const CaptureContexts = () => {
89
+ const resource = useContext(ResourceContext)
90
+ const perspective = useContext(PerspectiveContext)
91
+ useEffect(() => captured.resolve({resource, perspective}), [resource, perspective])
85
92
  return null
86
93
  }
87
94
 
88
95
  render(
89
- <ResourceProvider {...parentConfig} fallback={null}>
90
- <ResourceProvider {...testConfig} fallback={null}>
91
- <CaptureInstance />
96
+ <ResourceProvider
97
+ resource={{projectId: 'abc', dataset: 'prod'}}
98
+ perspective="drafts"
99
+ fallback={null}
100
+ >
101
+ <CaptureContexts />
102
+ </ResourceProvider>,
103
+ )
104
+
105
+ const result = await captured.promise
106
+ expect(result.resource).toEqual({projectId: 'abc', dataset: 'prod'})
107
+ expect(result.perspective).toBe('drafts')
108
+ })
109
+
110
+ it('nested provider overrides resource and perspective via context', async () => {
111
+ const captured = promiseWithResolvers<{
112
+ instance: SanityInstance | null
113
+ resource: DocumentResource | undefined
114
+ perspective: unknown
115
+ }>()
116
+
117
+ const CaptureAll = () => {
118
+ const instance = use(SanityInstanceContext)
119
+ const resource = useContext(ResourceContext)
120
+ const perspective = useContext(PerspectiveContext)
121
+ useEffect(
122
+ () => captured.resolve({instance, resource, perspective}),
123
+ [instance, resource, perspective],
124
+ )
125
+ return null
126
+ }
127
+
128
+ render(
129
+ <ResourceProvider resource={{projectId: 'parent-proj', dataset: 'parent-ds'}} fallback={null}>
130
+ <ResourceProvider
131
+ resource={{projectId: 'child-proj', dataset: 'child-ds'}}
132
+ perspective="drafts"
133
+ fallback={null}
134
+ >
135
+ <CaptureAll />
92
136
  </ResourceProvider>
93
137
  </ResourceProvider>,
94
138
  )
95
139
 
96
- const childInstance = await child.promise
97
- expect(childInstance?.config).toEqual(testConfig)
98
- expect(childInstance?.isDisposed()).toBe(false)
140
+ const result = await captured.promise
141
+ // Instance is the parent's (nested provider does not create a new one)
142
+ expect(result.instance?.instanceId).toBeDefined()
143
+ // Resource and perspective come from the nested provider's context
144
+ expect(result.resource).toEqual({projectId: 'child-proj', dataset: 'child-ds'})
145
+ expect(result.perspective).toBe('drafts')
146
+ })
147
+
148
+ it('nested provider inherits parent context when not overridden', async () => {
149
+ const captured = promiseWithResolvers<{
150
+ resource: DocumentResource | undefined
151
+ perspective: unknown
152
+ }>()
153
+
154
+ const CaptureContexts = () => {
155
+ const resource = useContext(ResourceContext)
156
+ const perspective = useContext(PerspectiveContext)
157
+ useEffect(() => captured.resolve({resource, perspective}), [resource, perspective])
158
+ return null
159
+ }
160
+
161
+ render(
162
+ <ResourceProvider
163
+ resource={{projectId: 'parent-proj', dataset: 'parent-ds'}}
164
+ perspective="drafts"
165
+ fallback={null}
166
+ >
167
+ <ResourceProvider fallback={null}>
168
+ <CaptureContexts />
169
+ </ResourceProvider>
170
+ </ResourceProvider>,
171
+ )
172
+
173
+ const result = await captured.promise
174
+ expect(result.resource).toEqual({projectId: 'parent-proj', dataset: 'parent-ds'})
175
+ expect(result.perspective).toBe('drafts')
99
176
  })
100
177
 
101
178
  it('disposes instance when unmounted', async () => {
@@ -141,6 +218,7 @@ describe('ResourceProvider', () => {
141
218
  })
142
219
 
143
220
  it('uses default fallback when none provided', async () => {
221
+ const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
144
222
  const {promise, resolve} = promiseWithResolvers()
145
223
  function SuspendingChild(): React.ReactNode {
146
224
  throw promise
@@ -157,5 +235,7 @@ describe('ResourceProvider', () => {
157
235
  act(() => {
158
236
  resolve()
159
237
  })
238
+ await new Promise((r) => setTimeout(r, 0))
239
+ consoleSpy.mockRestore()
160
240
  })
161
241
  })
@@ -1,6 +1,14 @@
1
- import {createSanityInstance, type SanityConfig, type SanityInstance} from '@sanity/sdk'
1
+ import {
2
+ createSanityInstance,
3
+ type DocumentResource,
4
+ type PerspectiveHandle,
5
+ type SanityConfig,
6
+ type SanityInstance,
7
+ } from '@sanity/sdk'
2
8
  import {Suspense, useContext, useEffect, useMemo, useRef} from 'react'
3
9
 
10
+ import {ResourceContext} from './DefaultResourceContext'
11
+ import {PerspectiveContext} from './PerspectiveContext'
4
12
  import {SanityInstanceContext} from './SanityInstanceContext'
5
13
 
6
14
  const DEFAULT_FALLBACK = (
@@ -10,74 +18,102 @@ const DEFAULT_FALLBACK = (
10
18
  )
11
19
 
12
20
  /**
13
- * Props for the ResourceProvider component
21
+ * Props for the ResourceProvider component.
22
+ *
23
+ * Extends `SanityConfig` (minus `defaultResource`) so new config fields are
24
+ * automatically forwarded. The `resource` prop replaces `defaultResource`
25
+ * with a name that better describes its role at the React layer.
26
+ *
14
27
  * @internal
15
28
  */
16
- export interface ResourceProviderProps extends SanityConfig {
29
+ export interface ResourceProviderProps extends Omit<SanityConfig, 'defaultResource'> {
30
+ /**
31
+ * The document resource (project/dataset, media library, or canvas)
32
+ * for this subtree. Hooks that don't specify an explicit resource will
33
+ * use this value.
34
+ */
35
+ resource?: DocumentResource
17
36
  /**
18
- * React node to show while content is loading
19
- * Used as the fallback for the internal Suspense boundary
37
+ * React node to show while content is loading.
38
+ * Used as the fallback for the internal Suspense boundary.
20
39
  */
21
40
  fallback: React.ReactNode
22
41
  children: React.ReactNode
23
42
  }
24
43
 
25
44
  /**
26
- * Provides a Sanity instance to child components through React Context
45
+ * Provides Sanity configuration to child components through React Context.
27
46
  *
28
47
  * @internal
29
48
  *
30
49
  * @remarks
31
- * The ResourceProvider creates a hierarchical structure of Sanity instances:
32
- * - When used as a root provider, it creates a new Sanity instance with the given config
33
- * - When nested inside another ResourceProvider, it creates a child instance that
34
- * inherits and extends the parent's configuration
50
+ * - **Root usage** (no parent instance): creates a `SanityInstance` with the
51
+ * given config and provides it via `SanityInstanceContext`.
52
+ * - **Nested usage** (inside an existing provider): sets
53
+ * `ResourceContext` and `PerspectiveContext` so hooks in the subtree
54
+ * resolve the correct resource/perspective without creating a new instance.
35
55
  *
36
- * Features:
37
- * - Automatically manages the lifecycle of Sanity instances
38
- * - Disposes instances when the component unmounts
39
- * - Includes a Suspense boundary for data loading
40
- * - Enables hierarchical configuration inheritance
41
- *
42
- * Use this component to:
43
- * - Set up project/dataset configuration for an application
44
- * - Override specific configuration values in a section of your app
45
- * - Create isolated instance hierarchies for different features
46
- *
47
- * @example Creating a root provider
56
+ * @example Root provider
48
57
  * ```tsx
49
58
  * <ResourceProvider
50
- * projectId="your-project-id"
51
- * dataset="production"
59
+ * resource={{ projectId: 'your-project-id', dataset: 'production' }}
52
60
  * fallback={<LoadingSpinner />}
53
61
  * >
54
62
  * <YourApp />
55
63
  * </ResourceProvider>
56
64
  * ```
57
65
  *
58
- * @example Creating nested providers with configuration inheritance
66
+ * @example Nested override
59
67
  * ```tsx
60
- * // Root provider with production config with nested provider for preview features with custom dataset
61
- * <ResourceProvider projectId="abc123" dataset="production" fallback={<Loading />}>
62
- * <div>...Main app content</div>
63
- * <Dashboard />
64
- * <ResourceProvider dataset="preview" fallback={<Loading />}>
65
- * <PreviewFeatures />
66
- * </ResourceProvider>
68
+ * <ResourceProvider
69
+ * resource={{ projectId: 'other-project', dataset: 'staging' }}
70
+ * fallback={<LoadingSpinner />}
71
+ * >
72
+ * <SubSection />
67
73
  * </ResourceProvider>
68
74
  * ```
69
75
  */
70
76
  export function ResourceProvider({
71
77
  children,
72
78
  fallback,
73
- ...config
79
+ resource,
80
+ ...rest
74
81
  }: ResourceProviderProps): React.ReactNode {
75
82
  const parent = useContext(SanityInstanceContext)
76
- const instance = useMemo(
77
- () => (parent ? parent.createChild(config) : createSanityInstance(config)),
78
- [config, parent],
83
+ const {perspective, auth, studio} = rest
84
+ const config: SanityConfig = useMemo(
85
+ () => ({perspective, auth, studio}),
86
+ [perspective, auth, studio],
79
87
  )
80
88
 
89
+ if (parent) {
90
+ return (
91
+ <NestedResourceProvider resource={resource} perspective={perspective} fallback={fallback}>
92
+ {children}
93
+ </NestedResourceProvider>
94
+ )
95
+ }
96
+
97
+ return (
98
+ <RootResourceProvider config={config} resource={resource} fallback={fallback}>
99
+ {children}
100
+ </RootResourceProvider>
101
+ )
102
+ }
103
+
104
+ function RootResourceProvider({
105
+ children,
106
+ fallback,
107
+ config,
108
+ resource,
109
+ }: {
110
+ children: React.ReactNode
111
+ fallback: React.ReactNode
112
+ config: SanityConfig
113
+ resource?: DocumentResource
114
+ }): React.ReactNode {
115
+ const instance = useMemo(() => createSanityInstance(config), [config])
116
+
81
117
  // Ref to hold the scheduled disposal timer.
82
118
  const disposal = useRef<{
83
119
  instance: SanityInstance
@@ -105,7 +141,37 @@ export function ResourceProvider({
105
141
 
106
142
  return (
107
143
  <SanityInstanceContext.Provider value={instance}>
108
- <Suspense fallback={fallback ?? DEFAULT_FALLBACK}>{children}</Suspense>
144
+ <ResourceContext.Provider value={resource}>
145
+ <PerspectiveContext.Provider value={config.perspective}>
146
+ <Suspense fallback={fallback ?? DEFAULT_FALLBACK}>{children}</Suspense>
147
+ </PerspectiveContext.Provider>
148
+ </ResourceContext.Provider>
109
149
  </SanityInstanceContext.Provider>
110
150
  )
111
151
  }
152
+
153
+ function NestedResourceProvider({
154
+ children,
155
+ fallback,
156
+ resource,
157
+ perspective,
158
+ }: {
159
+ children: React.ReactNode
160
+ fallback: React.ReactNode
161
+ resource?: DocumentResource
162
+ perspective?: PerspectiveHandle['perspective']
163
+ }): React.ReactNode {
164
+ const parentResource = useContext(ResourceContext)
165
+ const parentPerspective = useContext(PerspectiveContext)
166
+
167
+ const resolvedResource = resource ?? parentResource
168
+ const resolvedPerspective = perspective ?? parentPerspective
169
+
170
+ return (
171
+ <ResourceContext.Provider value={resolvedResource}>
172
+ <PerspectiveContext.Provider value={resolvedPerspective}>
173
+ <Suspense fallback={fallback ?? DEFAULT_FALLBACK}>{children}</Suspense>
174
+ </PerspectiveContext.Provider>
175
+ </ResourceContext.Provider>
176
+ )
177
+ }
@@ -0,0 +1,7 @@
1
+ import {type DocumentResource} from '@sanity/sdk'
2
+ import {createContext} from 'react'
3
+
4
+ /** Context for resources.
5
+ * @beta
6
+ */
7
+ export const ResourcesContext = createContext<Record<string, DocumentResource>>({})
@@ -5,7 +5,7 @@ import {beforeEach, describe, expect, it, vi} from 'vitest'
5
5
  import {SanityApp} from '../components/SanityApp'
6
6
  import {SDKStudioContext, type StudioWorkspaceHandle} from './SDKStudioContext'
7
7
 
8
- // Mock SDKProvider to capture the config it receives
8
+ // Mock SDKProvider to capture the config and resources it receives
9
9
  const mockSDKProvider = vi.hoisted(() => vi.fn())
10
10
  vi.mock('../components/SDKProvider', () => ({
11
11
  SDKProvider: mockSDKProvider.mockImplementation(({children}) => (
@@ -45,24 +45,31 @@ describe('SDKStudioContext', () => {
45
45
 
46
46
  expect(mockSDKProvider).toHaveBeenCalled()
47
47
  const receivedConfig = mockSDKProvider.mock.calls[0][0].config as SanityConfig
48
+ // config carries studio auth only; no resource fields
48
49
  expect(receivedConfig).toMatchObject({
49
- projectId: 'studio-project-id',
50
- dataset: 'production',
51
50
  studio: {
52
51
  auth: {token: mockWorkspace.auth.token},
53
52
  },
54
53
  })
54
+ expect(receivedConfig).not.toHaveProperty('defaultResource')
55
+ // resource goes into the resources map, not config
56
+ const receivedResources = mockSDKProvider.mock.calls[0][0].resources
57
+ expect(receivedResources).toEqual({
58
+ default: {projectId: 'studio-project-id', dataset: 'production'},
59
+ })
55
60
  })
56
61
 
57
62
  it('explicit config takes precedence over SDKStudioContext', () => {
58
- const explicitConfig: SanityConfig = {
59
- projectId: 'explicit-project',
60
- dataset: 'staging',
61
- }
63
+ const explicitConfig: SanityConfig = {}
64
+ const explicitResources = {default: {projectId: 'explicit-project', dataset: 'staging'}}
62
65
 
63
66
  render(
64
67
  <SDKStudioContext.Provider value={mockWorkspace}>
65
- <SanityApp config={explicitConfig} fallback={<div>Loading</div>}>
68
+ <SanityApp
69
+ config={explicitConfig}
70
+ resources={explicitResources}
71
+ fallback={<div>Loading</div>}
72
+ >
66
73
  <div>Child</div>
67
74
  </SanityApp>
68
75
  </SDKStudioContext.Provider>,
@@ -70,32 +77,31 @@ describe('SDKStudioContext', () => {
70
77
 
71
78
  expect(mockSDKProvider).toHaveBeenCalled()
72
79
  const receivedConfig = mockSDKProvider.mock.calls[0][0].config as SanityConfig
73
- expect(receivedConfig).toMatchObject({
74
- projectId: 'explicit-project',
75
- dataset: 'staging',
76
- })
77
- // Should NOT have studio config from the context
80
+ expect(receivedConfig).toEqual(explicitConfig)
78
81
  expect(receivedConfig.studio).toBeUndefined()
82
+ const receivedResources = mockSDKProvider.mock.calls[0][0].resources
83
+ expect(receivedResources).toEqual(explicitResources)
79
84
  })
80
85
 
81
86
  it('SanityApp works without SDKStudioContext (standalone mode)', () => {
82
- const standaloneConfig: SanityConfig = {
83
- projectId: 'standalone-project',
84
- dataset: 'production',
85
- }
87
+ const standaloneConfig: SanityConfig = {}
88
+ const standaloneResources = {default: {projectId: 'standalone-project', dataset: 'production'}}
86
89
 
87
90
  render(
88
- <SanityApp config={standaloneConfig} fallback={<div>Loading</div>}>
91
+ <SanityApp
92
+ config={standaloneConfig}
93
+ resources={standaloneResources}
94
+ fallback={<div>Loading</div>}
95
+ >
89
96
  <div>Child</div>
90
97
  </SanityApp>,
91
98
  )
92
99
 
93
100
  expect(mockSDKProvider).toHaveBeenCalled()
94
101
  const receivedConfig = mockSDKProvider.mock.calls[0][0].config as SanityConfig
95
- expect(receivedConfig).toMatchObject({
96
- projectId: 'standalone-project',
97
- dataset: 'production',
98
- })
102
+ expect(receivedConfig).toEqual(standaloneConfig)
103
+ const receivedResources = mockSDKProvider.mock.calls[0][0].resources
104
+ expect(receivedResources).toEqual(standaloneResources)
99
105
  })
100
106
 
101
107
  it('handles workspace without auth.token (older Studio)', () => {
@@ -115,12 +121,11 @@ describe('SDKStudioContext', () => {
115
121
 
116
122
  expect(mockSDKProvider).toHaveBeenCalled()
117
123
  const receivedConfig = mockSDKProvider.mock.calls[0][0].config as SanityConfig
118
- expect(receivedConfig).toMatchObject({
119
- projectId: 'older-studio',
120
- dataset: 'production',
121
- })
122
- // studio config should be present but auth.token should be undefined
123
- expect(receivedConfig.studio).toBeDefined()
124
+ expect(receivedConfig).toMatchObject({studio: expect.any(Object)})
124
125
  expect(receivedConfig.studio?.auth).toBeUndefined()
126
+ const receivedResources = mockSDKProvider.mock.calls[0][0].resources
127
+ expect(receivedResources).toEqual({
128
+ default: {projectId: 'older-studio', dataset: 'production'},
129
+ })
125
130
  })
126
131
  })
@@ -13,6 +13,12 @@ export interface StudioWorkspaceHandle {
13
13
  projectId: string
14
14
  /** The dataset name for this workspace. */
15
15
  dataset: string
16
+ /**
17
+ * Whether the Studio has determined the user is authenticated.
18
+ * When `true` and the token source emits `null`, the SDK infers
19
+ * cookie-based auth is in use and skips the logged-out state.
20
+ */
21
+ authenticated?: boolean
16
22
  /** Authentication state for this workspace. */
17
23
  auth: {
18
24
  /**