@sanity/sdk-react 2.9.0 → 2.10.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 (44) hide show
  1. package/dist/index.d.ts +92 -26
  2. package/dist/index.js +304 -193
  3. package/dist/index.js.map +1 -1
  4. package/package.json +9 -11
  5. package/src/_exports/sdk-react.ts +4 -0
  6. package/src/components/SDKProvider.tsx +36 -8
  7. package/src/components/SanityApp.tsx +2 -2
  8. package/src/components/auth/AuthBoundary.tsx +8 -1
  9. package/src/components/auth/DashboardAccessRequest.tsx +37 -0
  10. package/src/components/auth/LoginError.test.tsx +191 -5
  11. package/src/components/auth/LoginError.tsx +100 -56
  12. package/src/components/errors/ChunkLoadError.test.tsx +59 -0
  13. package/src/components/errors/ChunkLoadError.tsx +56 -0
  14. package/src/components/errors/chunkReloadStorage.ts +57 -0
  15. package/src/context/ResourceProvider.tsx +5 -4
  16. package/src/context/ResourcesContext.tsx +7 -0
  17. package/src/context/SanityInstanceProvider.test.tsx +100 -0
  18. package/src/context/SanityInstanceProvider.tsx +71 -0
  19. package/src/hooks/auth/useVerifyOrgProjects.tsx +13 -6
  20. package/src/hooks/dashboard/useDispatchIntent.test.ts +6 -6
  21. package/src/hooks/dashboard/useDispatchIntent.ts +6 -6
  22. package/src/hooks/dashboard/utils/useResourceIdFromDocumentHandle.test.ts +15 -15
  23. package/src/hooks/dashboard/utils/useResourceIdFromDocumentHandle.ts +13 -13
  24. package/src/hooks/document/useApplyDocumentActions.test.ts +10 -10
  25. package/src/hooks/document/useApplyDocumentActions.ts +17 -17
  26. package/src/hooks/document/useDocument.ts +5 -5
  27. package/src/hooks/document/useDocumentEvent.ts +4 -4
  28. package/src/hooks/document/useDocumentPermissions.test.tsx +10 -10
  29. package/src/hooks/document/useDocumentPermissions.ts +8 -8
  30. package/src/hooks/document/useDocumentSyncStatus.ts +2 -2
  31. package/src/hooks/document/useEditDocument.ts +2 -2
  32. package/src/hooks/documents/useDocuments.ts +9 -6
  33. package/src/hooks/helpers/useNormalizedResourceOptions.ts +131 -0
  34. package/src/hooks/helpers/useTrackHookUsage.ts +2 -2
  35. package/src/hooks/paginatedDocuments/usePaginatedDocuments.ts +9 -8
  36. package/src/hooks/presence/usePresence.test.tsx +56 -9
  37. package/src/hooks/presence/usePresence.ts +23 -4
  38. package/src/hooks/preview/useDocumentPreview.tsx +8 -7
  39. package/src/hooks/projection/useDocumentProjection.ts +6 -6
  40. package/src/hooks/query/useQuery.ts +10 -9
  41. package/src/hooks/releases/useActiveReleases.ts +10 -10
  42. package/src/hooks/releases/usePerspective.ts +9 -9
  43. package/src/context/SourcesContext.tsx +0 -7
  44. package/src/hooks/helpers/useNormalizedSourceOptions.ts +0 -107
@@ -0,0 +1,59 @@
1
+ import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest'
2
+
3
+ import {render, screen} from '../../../test/test-utils'
4
+ import {ChunkLoadError} from './ChunkLoadError'
5
+ import {CHUNK_RELOAD_STORAGE_KEY} from './chunkReloadStorage'
6
+
7
+ const noop = (): void => {}
8
+
9
+ describe('ChunkLoadError', () => {
10
+ const reloadSpy = vi.fn()
11
+ const originalLocation = window.location
12
+
13
+ beforeEach(() => {
14
+ reloadSpy.mockReset()
15
+ window.sessionStorage.clear()
16
+ Object.defineProperty(window, 'location', {
17
+ configurable: true,
18
+ value: {...originalLocation, reload: reloadSpy},
19
+ })
20
+ })
21
+
22
+ afterEach(() => {
23
+ Object.defineProperty(window, 'location', {configurable: true, value: originalLocation})
24
+ window.sessionStorage.clear()
25
+ })
26
+
27
+ it('triggers an automatic reload and renders nothing on the first occurrence', () => {
28
+ render(
29
+ <ChunkLoadError
30
+ error={new Error('Failed to fetch dynamically imported module')}
31
+ resetErrorBoundary={noop}
32
+ />,
33
+ )
34
+
35
+ expect(reloadSpy).toHaveBeenCalledTimes(1)
36
+ expect(window.sessionStorage.getItem(CHUNK_RELOAD_STORAGE_KEY)).toBe('1')
37
+ expect(screen.queryByText(/new version/i)).toBeNull()
38
+ })
39
+
40
+ it('renders the manual reload UI when the flag is already set', () => {
41
+ window.sessionStorage.setItem(CHUNK_RELOAD_STORAGE_KEY, '1')
42
+
43
+ render(
44
+ <ChunkLoadError
45
+ error={new Error('Failed to fetch dynamically imported module')}
46
+ resetErrorBoundary={noop}
47
+ />,
48
+ )
49
+
50
+ expect(reloadSpy).not.toHaveBeenCalled()
51
+ expect(screen.getByText('A new version is available')).toBeInTheDocument()
52
+
53
+ const button = screen.getByRole('button', {name: 'Reload page'})
54
+ button.click()
55
+
56
+ expect(reloadSpy).toHaveBeenCalledTimes(1)
57
+ expect(window.sessionStorage.getItem(CHUNK_RELOAD_STORAGE_KEY)).toBeNull()
58
+ })
59
+ })
@@ -0,0 +1,56 @@
1
+ import {useEffect} from 'react'
2
+ import {type FallbackProps} from 'react-error-boundary'
3
+
4
+ import {clearChunkReloadFlag, readChunkReloadFlag, setChunkReloadFlag} from './chunkReloadStorage'
5
+ import {Error} from './Error'
6
+
7
+ function reload(): void {
8
+ try {
9
+ window.location.reload()
10
+ } catch {
11
+ // No-op: nothing useful we can do if reload itself throws.
12
+ }
13
+ }
14
+
15
+ /**
16
+ * Default fallback rendered when a dynamic-import or chunk-load error
17
+ * bubbles up to the SDK's top-level error boundary.
18
+ *
19
+ * On the first occurrence in a session we set a flag and trigger
20
+ * window.location.reload(), since chunk-load errors almost always indicate a
21
+ * stale tab that simply needs a fresh index.html. If the flag is already set
22
+ * we render a manual reload UI instead, which prevents an infinite reload
23
+ * loop in the rare case the error is genuinely unrecoverable (network
24
+ * outage, CSP, etc.).
25
+ *
26
+ * @internal
27
+ */
28
+ export function ChunkLoadError(_props: FallbackProps): React.ReactNode {
29
+ const alreadyAttempted = readChunkReloadFlag()
30
+
31
+ useEffect(() => {
32
+ if (alreadyAttempted) return
33
+ setChunkReloadFlag()
34
+ reload()
35
+ }, [alreadyAttempted])
36
+
37
+ if (!alreadyAttempted) {
38
+ // Render nothing during the brief window before the page reloads so the
39
+ // user does not see a flash of error UI.
40
+ return null
41
+ }
42
+
43
+ return (
44
+ <Error
45
+ heading="A new version is available"
46
+ description="The page tried to load an asset that no longer exists. Reload to continue with the latest version."
47
+ cta={{
48
+ text: 'Reload page',
49
+ onClick: () => {
50
+ clearChunkReloadFlag()
51
+ reload()
52
+ },
53
+ }}
54
+ />
55
+ )
56
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Session-storage key tracking whether the SDK has already attempted an
3
+ * automatic reload in response to a chunk-load error during this session.
4
+ *
5
+ * @internal
6
+ */
7
+ export const CHUNK_RELOAD_STORAGE_KEY = '__sanity_sdk_chunk_reload_attempted'
8
+
9
+ /**
10
+ * Returns true when this session has already triggered an automatic reload.
11
+ * Returns false if session storage is unreadable.
12
+ *
13
+ * @internal
14
+ */
15
+ export function readChunkReloadFlag(): boolean {
16
+ try {
17
+ if (typeof window === 'undefined' || typeof window.sessionStorage === 'undefined') {
18
+ return false
19
+ }
20
+ return window.sessionStorage.getItem(CHUNK_RELOAD_STORAGE_KEY) !== null
21
+ } catch {
22
+ return false
23
+ }
24
+ }
25
+
26
+ /**
27
+ * Marks the session as having attempted an automatic reload, so the next
28
+ * chunk-load error renders the manual reload UI instead of looping.
29
+ *
30
+ * @internal
31
+ */
32
+ export function setChunkReloadFlag(): void {
33
+ try {
34
+ if (typeof window === 'undefined' || typeof window.sessionStorage === 'undefined') return
35
+ window.sessionStorage.setItem(CHUNK_RELOAD_STORAGE_KEY, '1')
36
+ } catch {
37
+ // Storage may be unavailable (private mode quotas, disabled cookies).
38
+ // Falling through means the user sees the manual-reload UI instead of an
39
+ // automatic reload, which is the correct degradation.
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Clears the chunk-reload flag. Called from SDKProvider once the SDK
45
+ * mounts successfully past the error boundary so a future incident in the
46
+ * same session can trigger another automatic reload.
47
+ *
48
+ * @internal
49
+ */
50
+ export function clearChunkReloadFlag(): void {
51
+ try {
52
+ if (typeof window === 'undefined' || typeof window.sessionStorage === 'undefined') return
53
+ window.sessionStorage.removeItem(CHUNK_RELOAD_STORAGE_KEY)
54
+ } catch {
55
+ // No-op: see setChunkReloadFlag.
56
+ }
57
+ }
@@ -1,8 +1,9 @@
1
1
  import {createSanityInstance, type SanityConfig, type SanityInstance} from '@sanity/sdk'
2
2
  import {initTelemetry} from '@sanity/sdk/_internal'
3
- import {Suspense, useContext, useEffect, useMemo, useRef} from 'react'
3
+ import {useContext, useEffect, useMemo, useRef} from 'react'
4
4
 
5
5
  import {SanityInstanceContext} from './SanityInstanceContext'
6
+ import {SanityInstanceProvider} from './SanityInstanceProvider'
6
7
 
7
8
  const DEFAULT_FALLBACK = (
8
9
  <>
@@ -110,8 +111,8 @@ export function ResourceProvider({
110
111
  }, [instance])
111
112
 
112
113
  return (
113
- <SanityInstanceContext.Provider value={instance}>
114
- <Suspense fallback={fallback ?? DEFAULT_FALLBACK}>{children}</Suspense>
115
- </SanityInstanceContext.Provider>
114
+ <SanityInstanceProvider instance={instance} fallback={fallback ?? DEFAULT_FALLBACK}>
115
+ {children}
116
+ </SanityInstanceProvider>
116
117
  )
117
118
  }
@@ -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>>({})
@@ -0,0 +1,100 @@
1
+ import {createSanityInstance, type SanityInstance} from '@sanity/sdk'
2
+ import {act, render, screen} from '@testing-library/react'
3
+ import {use, useEffect} from 'react'
4
+ import {describe, expect, it, vi} from 'vitest'
5
+
6
+ import {SanityInstanceContext} from './SanityInstanceContext'
7
+ import {SanityInstanceProvider} from './SanityInstanceProvider'
8
+
9
+ function promiseWithResolvers<T = void>(): {
10
+ promise: Promise<T>
11
+ resolve: (t: T) => void
12
+ reject: (error: unknown) => void
13
+ } {
14
+ let resolve!: (t: T) => void
15
+ let reject!: (error: unknown) => void
16
+ const promise = new Promise<T>((res, rej) => {
17
+ resolve = res
18
+ reject = rej
19
+ })
20
+ return {resolve, reject, promise}
21
+ }
22
+
23
+ describe('SanityInstanceProvider', () => {
24
+ it('renders children', () => {
25
+ const instance = createSanityInstance({projectId: 'test', dataset: 'test'})
26
+
27
+ render(
28
+ <SanityInstanceProvider instance={instance} fallback={<div>Loading...</div>}>
29
+ <div data-testid="test-child">Child Component</div>
30
+ </SanityInstanceProvider>,
31
+ )
32
+
33
+ expect(screen.getByTestId('test-child')).toBeInTheDocument()
34
+ instance.dispose()
35
+ })
36
+
37
+ it('provides the given instance via context', async () => {
38
+ const instance = createSanityInstance({projectId: 'test', dataset: 'test'})
39
+ const {promise, resolve} = promiseWithResolvers<SanityInstance | null>()
40
+
41
+ const CaptureInstance = () => {
42
+ const ctx = use(SanityInstanceContext)
43
+ useEffect(() => resolve(ctx), [ctx])
44
+ return null
45
+ }
46
+
47
+ render(
48
+ <SanityInstanceProvider instance={instance} fallback={null}>
49
+ <CaptureInstance />
50
+ </SanityInstanceProvider>,
51
+ )
52
+
53
+ const provided = await promise
54
+ expect(provided).toBe(instance)
55
+ instance.dispose()
56
+ })
57
+
58
+ it('shows fallback during suspense', async () => {
59
+ const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
60
+ const instance = createSanityInstance({projectId: 'test', dataset: 'test'})
61
+ const {promise, resolve} = promiseWithResolvers()
62
+
63
+ function SuspendingChild(): React.ReactNode {
64
+ throw promise
65
+ }
66
+
67
+ render(
68
+ <SanityInstanceProvider
69
+ instance={instance}
70
+ fallback={<div data-testid="fallback">Loading...</div>}
71
+ >
72
+ <SuspendingChild />
73
+ </SanityInstanceProvider>,
74
+ )
75
+
76
+ expect(screen.getByTestId('fallback')).toBeInTheDocument()
77
+ act(() => {
78
+ resolve()
79
+ })
80
+ await new Promise((r) => setTimeout(r, 0))
81
+ instance.dispose()
82
+ consoleSpy.mockRestore()
83
+ })
84
+
85
+ it('does not dispose the instance on unmount', async () => {
86
+ const instance = createSanityInstance({projectId: 'test', dataset: 'test'})
87
+
88
+ const {unmount} = render(
89
+ <SanityInstanceProvider instance={instance} fallback={null}>
90
+ <div />
91
+ </SanityInstanceProvider>,
92
+ )
93
+
94
+ unmount()
95
+ await new Promise((r) => setTimeout(r, 0))
96
+
97
+ expect(instance.isDisposed()).toBe(false)
98
+ instance.dispose()
99
+ })
100
+ })
@@ -0,0 +1,71 @@
1
+ import {type SanityInstance} from '@sanity/sdk'
2
+ import {Suspense} from 'react'
3
+
4
+ import {SanityInstanceContext} from './SanityInstanceContext'
5
+
6
+ /**
7
+ * Props for the SanityInstanceProvider component
8
+ * @public
9
+ */
10
+ export interface SanityInstanceProviderProps {
11
+ /**
12
+ * A pre-created SanityInstance to provide to child components.
13
+ * The caller owns the instance lifecycle — SanityInstanceProvider
14
+ * will not dispose it on unmount.
15
+ */
16
+ instance: SanityInstance
17
+ /**
18
+ * React node to show while content is loading.
19
+ * Used as the fallback for the internal Suspense boundary.
20
+ */
21
+ fallback: React.ReactNode
22
+ children: React.ReactNode
23
+ }
24
+
25
+ /**
26
+ * Provides an externally-created Sanity instance to child components through React Context.
27
+ *
28
+ * @internal
29
+ *
30
+ * @remarks
31
+ * Unlike {@link ResourceProvider}, this component does not create or dispose a SanityInstance.
32
+ * The caller is responsible for creating the instance via `createSanityInstance` and disposing
33
+ * it when appropriate. This is useful when a non-React system layer (e.g. a state machine)
34
+ * owns the instance and the React tree should consume it without managing its lifecycle.
35
+ *
36
+ * All SDK hooks (`useSanityInstance`, `useDocuments`, etc.) will read from the provided instance.
37
+ *
38
+ * @example Providing a pre-created instance
39
+ * ```tsx
40
+ * import { createSanityInstance, type SanityConfig } from '@sanity/sdk'
41
+ * import { SanityInstanceProvider } from '@sanity/sdk-react'
42
+ *
43
+ * const config: SanityConfig = {
44
+ * projectId: 'my-project-id',
45
+ * dataset: 'production',
46
+ * }
47
+ *
48
+ * const instance = createSanityInstance(config)
49
+ *
50
+ * function App() {
51
+ * return (
52
+ * <SanityInstanceProvider instance={instance} fallback={<div>Loading...</div>}>
53
+ * <MyApp />
54
+ * </SanityInstanceProvider>
55
+ * )
56
+ * }
57
+ * ```
58
+ *
59
+ * @category Components
60
+ */
61
+ export function SanityInstanceProvider({
62
+ instance,
63
+ fallback,
64
+ children,
65
+ }: SanityInstanceProviderProps): React.ReactNode {
66
+ return (
67
+ <SanityInstanceContext.Provider value={instance}>
68
+ <Suspense fallback={fallback}>{children}</Suspense>
69
+ </SanityInstanceContext.Provider>
70
+ )
71
+ }
@@ -27,13 +27,20 @@ export function useVerifyOrgProjects(disabled = false, projectIds?: string[]): s
27
27
  const instance = useSanityInstance()
28
28
  const [error, setError] = useState<string | null>(null)
29
29
 
30
+ const isInactive = disabled || !projectIds || projectIds.length === 0
31
+
32
+ // Reset stale errors when verification turns off so the next activation
33
+ // doesn't briefly leak the previous result.
34
+ const [prevInactive, setPrevInactive] = useState(isInactive)
35
+ if (prevInactive !== isInactive) {
36
+ setPrevInactive(isInactive)
37
+ if (isInactive) setError(null)
38
+ }
39
+
30
40
  useEffect(() => {
31
- if (disabled || !projectIds || projectIds.length === 0) {
32
- if (error !== null) setError(null)
33
- return
34
- }
41
+ if (isInactive) return
35
42
 
36
- const verificationObservable$ = observeOrganizationVerificationState(instance, projectIds)
43
+ const verificationObservable$ = observeOrganizationVerificationState(instance, projectIds!)
37
44
 
38
45
  const subscription = verificationObservable$.subscribe((result: OrgVerificationResult) => {
39
46
  setError(result.error)
@@ -42,7 +49,7 @@ export function useVerifyOrgProjects(disabled = false, projectIds?: string[]): s
42
49
  return () => {
43
50
  subscription.unsubscribe()
44
51
  }
45
- }, [instance, disabled, error, projectIds])
52
+ }, [instance, isInactive, projectIds])
46
53
 
47
54
  return error
48
55
  }
@@ -167,11 +167,11 @@ describe('useDispatchIntent', () => {
167
167
  })
168
168
  })
169
169
 
170
- it('should send intent message with media library source', () => {
170
+ it('should send intent message with media library resource', () => {
171
171
  const mockMediaLibraryHandle = {
172
172
  documentId: 'test-asset-id',
173
173
  documentType: 'sanity.asset',
174
- sourceName: 'media-library',
174
+ resourceName: 'media-library',
175
175
  } as const
176
176
 
177
177
  const {result} = renderHook(() =>
@@ -196,11 +196,11 @@ describe('useDispatchIntent', () => {
196
196
  })
197
197
  })
198
198
 
199
- it('should send intent message with canvas source', () => {
199
+ it('should send intent message with canvas resource', () => {
200
200
  const mockCanvasHandle = {
201
201
  documentId: 'test-canvas-document-id',
202
202
  documentType: 'sanity.canvas.document',
203
- sourceName: 'canvas',
203
+ resourceName: 'canvas',
204
204
  } as const
205
205
 
206
206
  const {result} = renderHook(() =>
@@ -226,7 +226,7 @@ describe('useDispatchIntent', () => {
226
226
  })
227
227
 
228
228
  describe('error handling', () => {
229
- it('should throw error when neither source nor projectId/dataset is provided', () => {
229
+ it('should throw error when neither resource nor projectId/dataset is provided', () => {
230
230
  const invalidHandle = {
231
231
  documentId: 'test-document-id',
232
232
  documentType: 'test-document-type',
@@ -240,7 +240,7 @@ describe('useDispatchIntent', () => {
240
240
  )
241
241
 
242
242
  expect(() => result.current.dispatchIntent()).toThrow(
243
- 'useDispatchIntent: Unable to determine resource. Either `source`, `sourceName`, or both `projectId` and `dataset` must be provided in documentHandle.',
243
+ 'useDispatchIntent: Unable to determine resource. Either `resource`, `resourceName`, or both `projectId` and `dataset` must be provided in documentHandle.',
244
244
  )
245
245
  })
246
246
  })
@@ -3,7 +3,7 @@ import {type DocumentHandle, type FrameMessage} from '@sanity/sdk'
3
3
  import {useCallback} from 'react'
4
4
 
5
5
  import {useWindowConnection} from '../comlink/useWindowConnection'
6
- import {type WithSourceNameSupport} from '../helpers/useNormalizedSourceOptions'
6
+ import {type WithResourceNameSupport} from '../helpers/useNormalizedResourceOptions'
7
7
  import {useResourceIdFromDocumentHandle} from './utils/useResourceIdFromDocumentHandle'
8
8
 
9
9
  /**
@@ -42,7 +42,7 @@ interface DispatchIntent {
42
42
  interface UseDispatchIntentParams {
43
43
  action?: 'edit'
44
44
  intentId?: string
45
- documentHandle: WithSourceNameSupport<DocumentHandle>
45
+ documentHandle: WithResourceNameSupport<DocumentHandle>
46
46
  parameters?: Record<string, unknown>
47
47
  }
48
48
 
@@ -56,8 +56,8 @@ interface UseDispatchIntentParams {
56
56
  * - `action` - Action to perform (currently only 'edit' is supported). Will prompt a picker if multiple handlers are available.
57
57
  * - `intentId` - Specific ID of the intent to dispatch. Either `action` or `intentId` is required.
58
58
  * - `documentHandle` - The document handle containing document ID, type, and either:
59
- * - `projectId` and `dataset` for traditional dataset sources, like `{documentId: '123', documentType: 'book', projectId: 'abc123', dataset: 'production'}`
60
- * - `source` for media library, canvas, or dataset sources, like `{documentId: '123', documentType: 'sanity.asset', source: mediaLibrarySource('ml123')}` or `{documentId: '123', documentType: 'sanity.canvas.document', source: canvasSource('canvas123')}`
59
+ * - `projectId` and `dataset` for traditional dataset resources, like `{documentId: '123', documentType: 'book', projectId: 'abc123', dataset: 'production'}`
60
+ * - `resource` for media library, canvas, or dataset resources, like `{documentId: '123', documentType: 'sanity.asset', resource: mediaLibrarySource('ml123')}` or `{documentId: '123', documentType: 'sanity.canvas.document', resource: canvasSource('canvas123')}`
61
61
  * - `paremeters` - Optional parameters to include in the dispatch; will be passed to the resolved intent handler
62
62
  * @returns An object containing:
63
63
  * - `dispatchIntent` - Function to dispatch the intent message
@@ -119,10 +119,10 @@ export function useDispatchIntent(params: UseDispatchIntentParams): DispatchInte
119
119
  )
120
120
  }
121
121
 
122
- // Validate that we have a resource ID (which is computed from source/sourceName or projectId+dataset)
122
+ // Validate that we have a resource ID (which is computed from resource/resourceName or projectId+dataset)
123
123
  if (!resource.id) {
124
124
  throw new Error(
125
- 'useDispatchIntent: Unable to determine resource. Either `source`, `sourceName`, or both `projectId` and `dataset` must be provided in documentHandle.',
125
+ 'useDispatchIntent: Unable to determine resource. Either `resource`, `resourceName`, or both `projectId` and `dataset` must be provided in documentHandle.',
126
126
  )
127
127
  }
128
128
 
@@ -23,11 +23,11 @@ describe('getResourceIdFromDocumentHandle', () => {
23
23
  })
24
24
 
25
25
  describe('with DocumentHandleWithSource - media library', () => {
26
- it('should return media library ID and resourceType when media library source is provided', () => {
26
+ it('should return media library ID and resourceType when media library resource is provided', () => {
27
27
  const documentHandle = {
28
28
  documentId: 'test-asset-id',
29
29
  documentType: 'sanity.asset',
30
- sourceName: 'media-library',
30
+ resourceName: 'media-library',
31
31
  } as const
32
32
 
33
33
  const {result} = renderHook(() => useResourceIdFromDocumentHandle(documentHandle))
@@ -38,13 +38,13 @@ describe('getResourceIdFromDocumentHandle', () => {
38
38
  })
39
39
  })
40
40
 
41
- it('should prioritize source over projectId/dataset when both are provided', () => {
41
+ it('should prioritize resource over projectId/dataset when both are provided', () => {
42
42
  const documentHandle = {
43
43
  documentId: 'test-asset-id',
44
44
  documentType: 'sanity.asset',
45
45
  projectId: 'test-project-id',
46
46
  dataset: 'test-dataset',
47
- sourceName: 'media-library',
47
+ resourceName: 'media-library',
48
48
  }
49
49
 
50
50
  const {result} = renderHook(() => useResourceIdFromDocumentHandle(documentHandle))
@@ -57,11 +57,11 @@ describe('getResourceIdFromDocumentHandle', () => {
57
57
  })
58
58
 
59
59
  describe('with DocumentHandleWithSource - canvas', () => {
60
- it('should return canvas ID and resourceType when canvas source is provided', () => {
60
+ it('should return canvas ID and resourceType when canvas resource is provided', () => {
61
61
  const documentHandle = {
62
62
  documentId: 'test-canvas-document-id',
63
63
  documentType: 'sanity.canvas.document',
64
- sourceName: 'canvas',
64
+ resourceName: 'canvas',
65
65
  }
66
66
 
67
67
  const {result} = renderHook(() => useResourceIdFromDocumentHandle(documentHandle))
@@ -73,48 +73,48 @@ describe('getResourceIdFromDocumentHandle', () => {
73
73
  })
74
74
  })
75
75
 
76
- describe('with DocumentHandleWithSource - dataset source', () => {
77
- it('should return dataset resource ID when dataset source is provided', () => {
76
+ describe('with DocumentHandleWithSource - dataset resource', () => {
77
+ it('should return dataset resource ID when dataset resource is provided', () => {
78
78
  const documentHandle = {
79
79
  documentId: 'test-document-id',
80
80
  documentType: 'test-document-type',
81
- sourceName: 'dataset',
81
+ resourceName: 'dataset',
82
82
  }
83
83
 
84
84
  const {result} = renderHook(() => useResourceIdFromDocumentHandle(documentHandle))
85
85
 
86
86
  expect(result.current).toEqual({
87
- id: 'source-project-id.source-dataset',
87
+ id: 'resource-project-id.resource-dataset',
88
88
  type: undefined,
89
89
  })
90
90
  })
91
91
 
92
- it('should use dataset source over projectId/dataset when both are provided', () => {
92
+ it('should use dataset resource over projectId/dataset when both are provided', () => {
93
93
  const documentHandle = {
94
94
  documentId: 'test-document-id',
95
95
  documentType: 'test-document-type',
96
96
  projectId: 'test-project-id',
97
97
  dataset: 'test-dataset',
98
- sourceName: 'dataset',
98
+ resourceName: 'dataset',
99
99
  }
100
100
 
101
101
  const {result} = renderHook(() => useResourceIdFromDocumentHandle(documentHandle))
102
102
 
103
103
  expect(result.current).toEqual({
104
- id: 'source-project-id.source-dataset',
104
+ id: 'resource-project-id.resource-dataset',
105
105
  type: undefined,
106
106
  })
107
107
  })
108
108
  })
109
109
 
110
110
  describe('edge cases', () => {
111
- it('should handle DocumentHandleWithSource with undefined source', () => {
111
+ it('should handle DocumentHandleWithSource with undefined resource', () => {
112
112
  const documentHandle = {
113
113
  documentId: 'test-document-id',
114
114
  documentType: 'test-document-type',
115
115
  projectId: 'test-project-id',
116
116
  dataset: 'test-dataset',
117
- sourceName: undefined,
117
+ resourceName: undefined,
118
118
  }
119
119
 
120
120
  const {result} = renderHook(() => useResourceIdFromDocumentHandle(documentHandle))
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  type DocumentHandle,
3
- isCanvasSource,
4
- isDatasetSource,
5
- isMediaLibrarySource,
3
+ isCanvasResource,
4
+ isDatasetResource,
5
+ isMediaLibraryResource,
6
6
  } from '@sanity/sdk'
7
7
 
8
- import {useNormalizedSourceOptions} from '../../helpers/useNormalizedSourceOptions'
8
+ import {useNormalizedResourceOptions} from '../../helpers/useNormalizedResourceOptions'
9
9
 
10
10
  interface DashboardMessageResource {
11
11
  id: string
@@ -18,23 +18,23 @@ interface DashboardMessageResource {
18
18
  export function useResourceIdFromDocumentHandle(
19
19
  documentHandle: DocumentHandle,
20
20
  ): DashboardMessageResource {
21
- const options = useNormalizedSourceOptions(documentHandle)
22
- const {projectId, dataset, source} = options
21
+ const options = useNormalizedResourceOptions(documentHandle)
22
+ const {projectId, dataset, resource} = options
23
23
  let resourceId: string = ''
24
24
  let resourceType: 'media-library' | 'canvas' | undefined
25
25
  if (projectId && dataset) {
26
26
  resourceId = `${projectId}.${dataset}`
27
27
  }
28
28
 
29
- if (source) {
30
- if (isDatasetSource(source)) {
31
- resourceId = `${source.projectId}.${source.dataset}`
29
+ if (resource) {
30
+ if (isDatasetResource(resource)) {
31
+ resourceId = `${resource.projectId}.${resource.dataset}`
32
32
  resourceType = undefined
33
- } else if (isMediaLibrarySource(source)) {
34
- resourceId = source.mediaLibraryId
33
+ } else if (isMediaLibraryResource(resource)) {
34
+ resourceId = resource.mediaLibraryId
35
35
  resourceType = 'media-library'
36
- } else if (isCanvasSource(source)) {
37
- resourceId = source.canvasId
36
+ } else if (isCanvasResource(resource)) {
37
+ resourceId = resource.canvasId
38
38
  resourceType = 'canvas'
39
39
  }
40
40
  }