@sanity/sdk-react 3.0.0-rc.0 → 3.0.0-rc.1

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 (160) hide show
  1. package/README.md +103 -155
  2. package/dist/index.d.ts +1564 -682
  3. package/dist/index.js +1118 -612
  4. package/dist/index.js.map +1 -1
  5. package/package.json +51 -54
  6. package/src/_exports/index.ts +2 -4
  7. package/src/_exports/sdk-react.ts +26 -17
  8. package/src/components/SDKProvider.test.tsx +173 -32
  9. package/src/components/SDKProvider.tsx +87 -28
  10. package/src/components/SanityApp.test.tsx +68 -12
  11. package/src/components/SanityApp.tsx +94 -87
  12. package/src/components/auth/AuthBoundary.recovery.test.tsx +86 -0
  13. package/src/components/auth/AuthBoundary.test.tsx +26 -8
  14. package/src/components/auth/AuthBoundary.tsx +28 -6
  15. package/src/components/auth/DashboardAccessRequest.tsx +37 -0
  16. package/src/components/auth/LoginError.test.tsx +191 -5
  17. package/src/components/auth/LoginError.tsx +103 -59
  18. package/src/components/errors/ChunkLoadError.test.tsx +59 -0
  19. package/src/components/errors/ChunkLoadError.tsx +56 -0
  20. package/src/components/errors/CorsErrorComponent.tsx +2 -2
  21. package/src/components/errors/chunkReloadStorage.ts +57 -0
  22. package/src/config/handles.ts +29 -27
  23. package/src/constants.ts +5 -0
  24. package/src/context/ComlinkTokenRefresh.test.tsx +10 -27
  25. package/src/context/DefaultResourceContext.ts +3 -3
  26. package/src/context/OrganizationResourcesProvider.test.tsx +189 -0
  27. package/src/context/OrganizationResourcesProvider.tsx +111 -0
  28. package/src/context/PerspectiveContext.ts +3 -3
  29. package/src/context/ProjectContext.ts +15 -0
  30. package/src/context/ResourceProvider.test.tsx +72 -90
  31. package/src/context/ResourceProvider.tsx +59 -93
  32. package/src/context/SDKStudioContext.test.tsx +28 -33
  33. package/src/context/SanityInstanceProvider.test.tsx +100 -0
  34. package/src/context/SanityInstanceProvider.tsx +71 -0
  35. package/src/context/WorkbenchTokenRefresh.test.tsx +106 -0
  36. package/src/context/WorkbenchTokenRefresh.tsx +61 -0
  37. package/src/context/renderSanityApp.test.tsx +151 -49
  38. package/src/context/renderSanityApp.tsx +12 -8
  39. package/src/context/workbenchToken.ts +63 -0
  40. package/src/hooks/access/useCheckPermissions.test-d.ts +15 -0
  41. package/src/hooks/access/useCheckPermissions.test.tsx +53 -0
  42. package/src/hooks/access/useCheckPermissions.ts +24 -0
  43. package/src/hooks/agent/agentActions.test.tsx +1 -1
  44. package/src/hooks/agent/agentActions.ts +51 -71
  45. package/src/hooks/applications/useApplication.test-d.ts +30 -0
  46. package/src/hooks/applications/useApplication.ts +22 -0
  47. package/src/hooks/applications/useApplications.test-d.ts +31 -0
  48. package/src/hooks/applications/useApplications.ts +25 -0
  49. package/src/hooks/applications/useCreateUserApplication.test-d.ts +14 -0
  50. package/src/hooks/applications/useCreateUserApplication.ts +11 -0
  51. package/src/hooks/applications/useDeleteApplication.test-d.ts +12 -0
  52. package/src/hooks/applications/useDeleteApplication.ts +11 -0
  53. package/src/hooks/applications/useDeleteUserApplication.test-d.ts +14 -0
  54. package/src/hooks/applications/useDeleteUserApplication.ts +11 -0
  55. package/src/hooks/applications/useUpdateApplication.test-d.ts +12 -0
  56. package/src/hooks/applications/useUpdateApplication.ts +11 -0
  57. package/src/hooks/applications/useUpdateUserApplication.test-d.ts +14 -0
  58. package/src/hooks/applications/useUpdateUserApplication.ts +11 -0
  59. package/src/hooks/applications/useUserApplication.test-d.ts +11 -0
  60. package/src/hooks/applications/useUserApplication.ts +14 -0
  61. package/src/hooks/applications/useUserApplications.test-d.ts +11 -0
  62. package/src/hooks/applications/useUserApplications.ts +14 -0
  63. package/src/hooks/auth/useDashboardOrganizationId.test.tsx +2 -8
  64. package/src/hooks/auth/useVerifyOrgProjects.test.tsx +8 -32
  65. package/src/hooks/auth/useVerifyOrgProjects.tsx +13 -6
  66. package/src/hooks/client/useClient.test.tsx +1 -4
  67. package/src/hooks/client/useClient.ts +1 -0
  68. package/src/hooks/context/useResource.test.tsx +32 -0
  69. package/src/hooks/context/useResource.ts +24 -0
  70. package/src/hooks/context/useSanityInstance.test.tsx +72 -3
  71. package/src/hooks/context/useSanityInstance.ts +30 -8
  72. package/src/hooks/dashboard/useManageFavorite.test.tsx +110 -46
  73. package/src/hooks/dashboard/useManageFavorite.ts +10 -16
  74. package/src/hooks/dashboard/useNavigateToStudioDocument.test.ts +5 -7
  75. package/src/hooks/dashboard/useNavigateToStudioDocument.ts +2 -6
  76. package/src/hooks/dashboard/useRecordDocumentHistoryEvent.test.ts +0 -2
  77. package/src/hooks/dashboard/useRecordDocumentHistoryEvent.ts +1 -2
  78. package/src/hooks/dashboard/useWindowTitle.test.ts +244 -0
  79. package/src/hooks/dashboard/useWindowTitle.ts +114 -0
  80. package/src/hooks/datasets/useDatasets.test.tsx +116 -0
  81. package/src/hooks/datasets/useDatasets.ts +33 -13
  82. package/src/hooks/document/useApplyDocumentActions.test.tsx +48 -0
  83. package/src/hooks/document/useApplyDocumentActions.ts +66 -48
  84. package/src/hooks/document/useCreateDocument.test.tsx +83 -0
  85. package/src/hooks/document/useCreateDocument.ts +117 -0
  86. package/src/hooks/document/useDocument.test.tsx +37 -8
  87. package/src/hooks/document/useDocument.ts +131 -66
  88. package/src/hooks/document/useDocumentEvent.test.tsx +16 -4
  89. package/src/hooks/document/useDocumentEvent.ts +16 -10
  90. package/src/hooks/document/useDocumentPermissions.test.tsx +8 -7
  91. package/src/hooks/document/useDocumentPermissions.ts +71 -39
  92. package/src/hooks/document/useDocumentSyncStatus.test.ts +1 -0
  93. package/src/hooks/document/useDocumentSyncStatus.ts +13 -9
  94. package/src/hooks/document/useEditDocument.test.tsx +21 -14
  95. package/src/hooks/document/useEditDocument.ts +163 -17
  96. package/src/hooks/documents/useDocuments.test.tsx +64 -44
  97. package/src/hooks/documents/useDocuments.ts +36 -20
  98. package/src/hooks/helpers/createCallbackHook.test.tsx +13 -19
  99. package/src/hooks/helpers/createFetcherHook.test.tsx +180 -0
  100. package/src/hooks/helpers/createFetcherHook.ts +69 -0
  101. package/src/hooks/helpers/createMutationHook.test.tsx +125 -0
  102. package/src/hooks/helpers/createMutationHook.tsx +93 -0
  103. package/src/hooks/helpers/createStateSourceHook.test.tsx +10 -10
  104. package/src/hooks/helpers/createStateSourceHook.tsx +2 -1
  105. package/src/hooks/{document/useApplyDocumentActions.test.ts → helpers/useApplyActions.test.tsx} +92 -53
  106. package/src/hooks/helpers/useApplyActions.ts +68 -0
  107. package/src/hooks/helpers/useNormalizedResourceOptions.test.tsx +253 -0
  108. package/src/hooks/helpers/useNormalizedResourceOptions.ts +92 -50
  109. package/src/hooks/helpers/useResolvedProjectId.test.tsx +59 -0
  110. package/src/hooks/helpers/useResolvedProjectId.ts +35 -0
  111. package/src/hooks/helpers/useTrackHookUsage.ts +37 -0
  112. package/src/hooks/installations/useInstallation.test-d.ts +19 -0
  113. package/src/hooks/installations/useInstallation.ts +22 -0
  114. package/src/hooks/installations/useInstallations.test-d.ts +26 -0
  115. package/src/hooks/installations/useInstallations.ts +25 -0
  116. package/src/hooks/organizations/useOrganization.test-d.ts +60 -0
  117. package/src/hooks/organizations/useOrganization.test.ts +63 -0
  118. package/src/hooks/organizations/useOrganization.ts +34 -0
  119. package/src/hooks/organizations/useOrganizations.test-d.ts +68 -0
  120. package/src/hooks/organizations/useOrganizations.test.ts +63 -0
  121. package/src/hooks/organizations/useOrganizations.ts +39 -0
  122. package/src/hooks/paginatedDocuments/usePaginatedDocuments.test.tsx +34 -27
  123. package/src/hooks/paginatedDocuments/usePaginatedDocuments.ts +40 -22
  124. package/src/hooks/presence/usePresence.test.tsx +0 -15
  125. package/src/hooks/presence/usePresence.ts +9 -20
  126. package/src/hooks/preview/useDocumentPreview.test.tsx +0 -1
  127. package/src/hooks/preview/useDocumentPreview.tsx +10 -8
  128. package/src/hooks/projection/useDocumentProjection.test.tsx +78 -9
  129. package/src/hooks/projection/useDocumentProjection.ts +81 -2
  130. package/src/hooks/projects/useProject.test-d.ts +56 -0
  131. package/src/hooks/projects/useProject.test.tsx +120 -0
  132. package/src/hooks/projects/useProject.ts +42 -43
  133. package/src/hooks/projects/useProjects.test-d.ts +60 -0
  134. package/src/hooks/projects/useProjects.test.ts +45 -98
  135. package/src/hooks/projects/useProjects.ts +19 -26
  136. package/src/hooks/query/useQuery.test.tsx +6 -5
  137. package/src/hooks/query/useQuery.ts +91 -9
  138. package/src/hooks/releases/useActiveReleases.test.tsx +13 -7
  139. package/src/hooks/releases/useActiveReleases.ts +28 -24
  140. package/src/hooks/releases/useAllReleases.test.tsx +93 -0
  141. package/src/hooks/releases/useAllReleases.ts +62 -0
  142. package/src/hooks/releases/useApplyReleaseActions.test.tsx +66 -0
  143. package/src/hooks/releases/useApplyReleaseActions.ts +82 -0
  144. package/src/hooks/releases/usePerspective.test.tsx +18 -10
  145. package/src/hooks/releases/usePerspective.ts +13 -17
  146. package/src/hooks/users/useUser.test.tsx +3 -9
  147. package/src/hooks/users/useUser.ts +2 -0
  148. package/src/hooks/users/useUsers.test.tsx +103 -7
  149. package/src/hooks/users/useUsers.ts +37 -3
  150. package/src/utils/resolveOrgResources.test.ts +111 -0
  151. package/src/utils/resolveOrgResources.ts +69 -0
  152. package/src/hooks/context/useDefaultResource.test.tsx +0 -25
  153. package/src/hooks/context/useDefaultResource.ts +0 -30
  154. package/src/hooks/dashboard/useDispatchIntent.test.ts +0 -254
  155. package/src/hooks/dashboard/useDispatchIntent.ts +0 -157
  156. package/src/hooks/dashboard/utils/useResourceIdFromDocumentHandle.test.ts +0 -107
  157. package/src/hooks/dashboard/utils/useResourceIdFromDocumentHandle.ts +0 -39
  158. package/src/hooks/datasets/useDatasets.test.ts +0 -66
  159. package/src/hooks/helpers/useNormalizedResourceOptions.test.ts +0 -65
  160. package/src/hooks/projects/useProject.test.ts +0 -79
@@ -1,254 +0,0 @@
1
- import {type DocumentHandle} from '@sanity/sdk'
2
- import {beforeEach, describe, expect, it, vi} from 'vitest'
3
-
4
- import {renderHook} from '../../../test/test-utils'
5
- import {useDispatchIntent} from './useDispatchIntent'
6
-
7
- // Mock the useWindowConnection hook
8
- const mockSendMessage = vi.fn()
9
- vi.mock('../comlink/useWindowConnection', () => ({
10
- useWindowConnection: vi.fn(() => ({
11
- sendMessage: mockSendMessage,
12
- })),
13
- }))
14
-
15
- describe('useDispatchIntent', () => {
16
- const mockDocumentHandle: DocumentHandle = {
17
- documentId: 'test-document-id',
18
- documentType: 'test-document-type',
19
- resource: {projectId: 'test-project-id', dataset: 'test-dataset'},
20
- }
21
-
22
- beforeEach(() => {
23
- vi.clearAllMocks()
24
- // Reset mock implementation to default behavior
25
- mockSendMessage.mockImplementation(() => {})
26
- })
27
-
28
- it('should return dispatchIntent function', () => {
29
- const {result} = renderHook(() =>
30
- useDispatchIntent({action: 'edit', documentHandle: mockDocumentHandle}),
31
- )
32
-
33
- expect(result.current).toEqual({
34
- dispatchIntent: expect.any(Function),
35
- })
36
- })
37
-
38
- it('should throw error when neither action nor intentId is provided', () => {
39
- const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
40
- const {result} = renderHook(() => useDispatchIntent({documentHandle: mockDocumentHandle}))
41
-
42
- expect(() => result.current.dispatchIntent()).toThrow(
43
- 'useDispatchIntent: Either `action` or `intentId` must be provided.',
44
- )
45
- consoleErrorSpy.mockRestore()
46
- })
47
-
48
- it('should handle errors gracefully', () => {
49
- const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
50
- mockSendMessage.mockImplementation(() => {
51
- throw new Error('Test error')
52
- })
53
-
54
- const {result} = renderHook(() =>
55
- useDispatchIntent({action: 'edit', documentHandle: mockDocumentHandle}),
56
- )
57
-
58
- expect(() => result.current.dispatchIntent()).toThrow('Test error')
59
- expect(consoleErrorSpy).toHaveBeenCalledWith('Failed to dispatch intent:', expect.any(Error))
60
-
61
- consoleErrorSpy.mockRestore()
62
- })
63
-
64
- it('should use memoized dispatchIntent function', () => {
65
- const params = {action: 'edit' as const, documentHandle: mockDocumentHandle}
66
- const {result, rerender} = renderHook(
67
- ({params: hookParams}: {params: typeof params}) => useDispatchIntent(hookParams),
68
- {
69
- initialProps: {params},
70
- },
71
- )
72
-
73
- const firstDispatchIntent = result.current.dispatchIntent
74
-
75
- // Rerender with the same params
76
- rerender({params: {action: 'edit' as const, documentHandle: mockDocumentHandle}})
77
-
78
- expect(result.current.dispatchIntent).toBe(firstDispatchIntent)
79
- })
80
-
81
- it('should create new dispatchIntent function when documentHandle changes', () => {
82
- const {result, rerender} = renderHook(
83
- (params: {action: 'edit'; documentHandle: DocumentHandle}) => useDispatchIntent(params),
84
- {
85
- initialProps: {action: 'edit' as const, documentHandle: mockDocumentHandle},
86
- },
87
- )
88
-
89
- const firstDispatchIntent = result.current.dispatchIntent
90
-
91
- const newDocumentHandle: DocumentHandle = {
92
- documentId: 'new-document-id',
93
- documentType: 'new-document-type',
94
- resource: {projectId: 'new-project-id', dataset: 'new-dataset'},
95
- }
96
-
97
- rerender({action: 'edit' as const, documentHandle: newDocumentHandle})
98
-
99
- expect(result.current.dispatchIntent).not.toBe(firstDispatchIntent)
100
- })
101
-
102
- it('should warn if both action and intentId are provided', () => {
103
- const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
104
- const {result} = renderHook(() =>
105
- useDispatchIntent({
106
- action: 'edit' as const,
107
- intentId: 'custom-intent' as never, // test runtime error when both are provided
108
- documentHandle: mockDocumentHandle,
109
- }),
110
- )
111
- result.current.dispatchIntent()
112
- expect(consoleWarnSpy).toHaveBeenCalledWith(
113
- 'useDispatchIntent: Both `action` and `intentId` were provided. Using `intentId` and ignoring `action`.',
114
- )
115
- consoleWarnSpy.mockRestore()
116
- })
117
-
118
- it('should send intent message with action and params when both are provided', () => {
119
- const intentParams = {view: 'editor', tab: 'content'}
120
- const {result} = renderHook(() =>
121
- useDispatchIntent({
122
- action: 'edit',
123
- documentHandle: mockDocumentHandle,
124
- parameters: intentParams,
125
- }),
126
- )
127
-
128
- result.current.dispatchIntent()
129
-
130
- expect(mockSendMessage).toHaveBeenCalledWith('dashboard/v1/events/intents/dispatch-intent', {
131
- action: 'edit',
132
- document: {
133
- id: 'test-document-id',
134
- type: 'test-document-type',
135
- },
136
- resource: {
137
- id: 'test-project-id.test-dataset',
138
- },
139
- parameters: intentParams,
140
- })
141
- })
142
-
143
- it('should send intent message with intentId and params when both are provided', () => {
144
- const intentParams = {view: 'editor', tab: 'content'}
145
- const {result} = renderHook(() =>
146
- useDispatchIntent({
147
- intentId: 'custom-intent',
148
- documentHandle: mockDocumentHandle,
149
- parameters: intentParams,
150
- }),
151
- )
152
-
153
- result.current.dispatchIntent()
154
-
155
- expect(mockSendMessage).toHaveBeenCalledWith('dashboard/v1/events/intents/dispatch-intent', {
156
- intentId: 'custom-intent',
157
- document: {
158
- id: 'test-document-id',
159
- type: 'test-document-type',
160
- },
161
- resource: {
162
- id: 'test-project-id.test-dataset',
163
- },
164
- parameters: intentParams,
165
- })
166
- })
167
-
168
- it('should send intent message with media library resource', () => {
169
- const mockMediaLibraryHandle = {
170
- documentId: 'test-asset-id',
171
- documentType: 'sanity.asset',
172
- resourceName: 'media-library',
173
- } as const
174
-
175
- const {result} = renderHook(() =>
176
- useDispatchIntent({
177
- action: 'edit',
178
- documentHandle: mockMediaLibraryHandle,
179
- }),
180
- )
181
-
182
- result.current.dispatchIntent()
183
-
184
- expect(mockSendMessage).toHaveBeenCalledWith('dashboard/v1/events/intents/dispatch-intent', {
185
- action: 'edit',
186
- document: {
187
- id: 'test-asset-id',
188
- type: 'sanity.asset',
189
- },
190
- resource: {
191
- id: 'media-library-id',
192
- type: 'media-library',
193
- },
194
- })
195
- })
196
-
197
- it('should send intent message with canvas resource', () => {
198
- const mockCanvasHandle = {
199
- documentId: 'test-canvas-document-id',
200
- documentType: 'sanity.canvas.document',
201
- resourceName: 'canvas',
202
- } as const
203
-
204
- const {result} = renderHook(() =>
205
- useDispatchIntent({
206
- action: 'edit',
207
- documentHandle: mockCanvasHandle,
208
- }),
209
- )
210
-
211
- result.current.dispatchIntent()
212
-
213
- expect(mockSendMessage).toHaveBeenCalledWith('dashboard/v1/events/intents/dispatch-intent', {
214
- action: 'edit',
215
- document: {
216
- id: 'test-canvas-document-id',
217
- type: 'sanity.canvas.document',
218
- },
219
- resource: {
220
- id: 'canvas-id',
221
- type: 'canvas',
222
- },
223
- })
224
- })
225
-
226
- describe('default resource fallback', () => {
227
- it('should use default resource from context when handle has no explicit targeting', () => {
228
- const minimalHandle = {
229
- documentId: 'test-document-id',
230
- documentType: 'test-document-type',
231
- }
232
-
233
- const {result} = renderHook(() =>
234
- useDispatchIntent({
235
- action: 'edit',
236
- documentHandle: minimalHandle as unknown as DocumentHandle,
237
- }),
238
- )
239
-
240
- result.current.dispatchIntent()
241
-
242
- expect(mockSendMessage).toHaveBeenCalledWith('dashboard/v1/events/intents/dispatch-intent', {
243
- action: 'edit',
244
- document: {
245
- id: 'test-document-id',
246
- type: 'test-document-type',
247
- },
248
- resource: {
249
- id: 'test.test',
250
- },
251
- })
252
- })
253
- })
254
- })
@@ -1,157 +0,0 @@
1
- import {SDK_CHANNEL_NAME, SDK_NODE_NAME} from '@sanity/message-protocol'
2
- import {type FrameMessage} from '@sanity/sdk'
3
- import {useCallback} from 'react'
4
-
5
- import {type DocumentHandle} from '../../config/handles'
6
- import {useWindowConnection} from '../comlink/useWindowConnection'
7
- import {useResourceIdFromDocumentHandle} from './utils/useResourceIdFromDocumentHandle'
8
-
9
- /**
10
- * Message type for sending intents to the dashboard
11
- * @beta
12
- */
13
- interface IntentMessage {
14
- type: 'dashboard/v1/events/intents/dispatch-intent'
15
- data: {
16
- action?: 'edit'
17
- intentId?: string
18
- document: {
19
- id: string
20
- type: string
21
- }
22
- resource?: {
23
- id: string
24
- type?: 'media-library' | 'canvas'
25
- }
26
- parameters?: Record<string, unknown>
27
- }
28
- }
29
-
30
- /**
31
- * Return type for the useDispatchIntent hook
32
- * @beta
33
- */
34
- interface DispatchIntent {
35
- dispatchIntent: () => void
36
- }
37
-
38
- /**
39
- * Parameters for the useDispatchIntent hook
40
- * @beta
41
- */
42
- interface UseDispatchIntentParams {
43
- action?: 'edit'
44
- intentId?: string
45
- documentHandle: DocumentHandle
46
- parameters?: Record<string, unknown>
47
- }
48
-
49
- /**
50
- * @beta
51
- *
52
- * A hook for dispatching intent messages to the Dashboard with a document handle.
53
- * This allows applications to signal their intent to pass the referenced document to other applications that have registered the ability to perform specific actions on that document.
54
- *
55
- * @param params - Object containing:
56
- * - `action` - Action to perform (currently only 'edit' is supported). Will prompt a picker if multiple handlers are available.
57
- * - `intentId` - Specific ID of the intent to dispatch. Either `action` or `intentId` is required.
58
- * - `documentHandle` - The document handle containing document ID, type, and either:
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: mediaLibraryResource('ml123')}` or `{documentId: '123', documentType: 'sanity.canvas.document', resource: canvasResource('canvas123')}`
61
- * - `paremeters` - Optional parameters to include in the dispatch; will be passed to the resolved intent handler
62
- * @returns An object containing:
63
- * - `dispatchIntent` - Function to dispatch the intent message
64
- *
65
- * @example
66
- * ```tsx
67
- * import {useDispatchIntent} from '@sanity/sdk-react'
68
- * import {Button} from '@sanity/ui'
69
- * import {Suspense} from 'react'
70
- *
71
- * function DispatchIntentButton({documentId, documentType, projectId, dataset}) {
72
- * const {dispatchIntent} = useDispatchIntent({
73
- * action: 'edit',
74
- * documentHandle: {documentId, documentType, projectId, dataset},
75
- * })
76
- *
77
- * return (
78
- * <Button
79
- * onClick={() => dispatchIntent()}
80
- * text="Dispatch Intent"
81
- * />
82
- * )
83
- * }
84
- *
85
- * // Wrap the component with Suspense since the hook may suspend
86
- * function MyDocumentAction({documentId, documentType, projectId, dataset}) {
87
- * return (
88
- * <Suspense fallback={<Button text="Loading..." disabled />}>
89
- * <DispatchIntentButton
90
- * documentId={documentId}
91
- * documentType={documentType}
92
- * projectId={projectId}
93
- * dataset={dataset}
94
- * />
95
- * </Suspense>
96
- * )
97
- * }
98
- * ```
99
- */
100
- export function useDispatchIntent(params: UseDispatchIntentParams): DispatchIntent {
101
- const {action, intentId, documentHandle, parameters} = params
102
- const {sendMessage} = useWindowConnection<IntentMessage, FrameMessage>({
103
- name: SDK_NODE_NAME,
104
- connectTo: SDK_CHANNEL_NAME,
105
- })
106
-
107
- const resource = useResourceIdFromDocumentHandle(documentHandle)
108
-
109
- const dispatchIntent = useCallback(() => {
110
- try {
111
- if (!action && !intentId) {
112
- throw new Error('useDispatchIntent: Either `action` or `intentId` must be provided.')
113
- }
114
-
115
- if (action && intentId) {
116
- // eslint-disable-next-line no-console -- warn if both action and intentId are provided
117
- console.warn(
118
- 'useDispatchIntent: Both `action` and `intentId` were provided. Using `intentId` and ignoring `action`.',
119
- )
120
- }
121
-
122
- // Validate that we have a resource ID (which is computed from resource/resourceName or projectId+dataset)
123
- if (!resource.id) {
124
- throw new Error(
125
- 'useDispatchIntent: Unable to determine resource. Either `resource`, `resourceName`, or both `projectId` and `dataset` must be provided in documentHandle.',
126
- )
127
- }
128
-
129
- const message: IntentMessage = {
130
- type: 'dashboard/v1/events/intents/dispatch-intent',
131
- data: {
132
- ...(action && !intentId ? {action} : {}),
133
- ...(intentId ? {intentId} : {}),
134
- document: {
135
- id: documentHandle.documentId,
136
- type: documentHandle.documentType,
137
- },
138
- resource: {
139
- id: resource.id,
140
- ...(resource.type ? {type: resource.type} : {}),
141
- },
142
- ...(parameters && Object.keys(parameters).length > 0 ? {parameters} : {}),
143
- },
144
- }
145
-
146
- sendMessage(message.type, message.data)
147
- } catch (error) {
148
- // eslint-disable-next-line no-console
149
- console.error('Failed to dispatch intent:', error)
150
- throw error
151
- }
152
- }, [action, intentId, documentHandle, parameters, sendMessage, resource.id, resource.type])
153
-
154
- return {
155
- dispatchIntent,
156
- }
157
- }
@@ -1,107 +0,0 @@
1
- import {describe, expect, it} from 'vitest'
2
-
3
- import {renderHook} from '../../../../test/test-utils'
4
- import {useResourceIdFromDocumentHandle} from './useResourceIdFromDocumentHandle'
5
-
6
- describe('getResourceIdFromDocumentHandle', () => {
7
- describe('with DocumentHandle using resource object', () => {
8
- it('should return resource ID from resource object with projectId and dataset', () => {
9
- const documentHandle = {
10
- documentId: 'test-document-id',
11
- documentType: 'test-document-type',
12
- resource: {projectId: 'test-project-id', dataset: 'test-dataset'},
13
- }
14
-
15
- const {result} = renderHook(() => useResourceIdFromDocumentHandle(documentHandle))
16
-
17
- expect(result.current).toEqual({
18
- id: 'test-project-id.test-dataset',
19
- type: undefined,
20
- })
21
- })
22
- })
23
-
24
- describe('with DocumentHandle - media library resource', () => {
25
- it('should return media library ID and resourceType when media library resource is provided', () => {
26
- const documentHandle = {
27
- documentId: 'test-asset-id',
28
- documentType: 'sanity.asset',
29
- resourceName: 'media-library',
30
- } as const
31
-
32
- const {result} = renderHook(() => useResourceIdFromDocumentHandle(documentHandle))
33
-
34
- expect(result.current).toEqual({
35
- id: 'media-library-id',
36
- type: 'media-library',
37
- })
38
- })
39
-
40
- it('should use resourceName when provided', () => {
41
- const documentHandle = {
42
- documentId: 'test-asset-id',
43
- documentType: 'sanity.asset',
44
- resourceName: 'media-library',
45
- }
46
-
47
- const {result} = renderHook(() => useResourceIdFromDocumentHandle(documentHandle))
48
-
49
- expect(result.current).toEqual({
50
- id: 'media-library-id',
51
- type: 'media-library',
52
- })
53
- })
54
- })
55
-
56
- describe('with DocumentHandle - canvas resource', () => {
57
- it('should return canvas ID and resourceType when canvas resource is provided', () => {
58
- const documentHandle = {
59
- documentId: 'test-canvas-document-id',
60
- documentType: 'sanity.canvas.document',
61
- resourceName: 'canvas',
62
- }
63
-
64
- const {result} = renderHook(() => useResourceIdFromDocumentHandle(documentHandle))
65
-
66
- expect(result.current).toEqual({
67
- id: 'canvas-id',
68
- type: 'canvas',
69
- })
70
- })
71
- })
72
-
73
- describe('with DocumentHandle - dataset resource', () => {
74
- it('should return dataset resource ID when dataset resource is provided', () => {
75
- const documentHandle = {
76
- documentId: 'test-document-id',
77
- documentType: 'test-document-type',
78
- resourceName: 'dataset',
79
- }
80
-
81
- const {result} = renderHook(() => useResourceIdFromDocumentHandle(documentHandle))
82
-
83
- expect(result.current).toEqual({
84
- id: 'source-project-id.source-dataset',
85
- type: undefined,
86
- })
87
- })
88
- })
89
-
90
- describe('edge cases', () => {
91
- it('should handle DocumentHandle with explicit resource and no resourceName', () => {
92
- const documentHandle = {
93
- documentId: 'test-document-id',
94
- documentType: 'test-document-type',
95
- resource: {projectId: 'test-project-id', dataset: 'test-dataset'},
96
- resourceName: undefined,
97
- }
98
-
99
- const {result} = renderHook(() => useResourceIdFromDocumentHandle(documentHandle))
100
-
101
- expect(result.current).toEqual({
102
- id: 'test-project-id.test-dataset',
103
- type: undefined,
104
- })
105
- })
106
- })
107
- })
@@ -1,39 +0,0 @@
1
- import {isCanvasResource, isDatasetResource, isMediaLibraryResource} from '@sanity/sdk'
2
-
3
- import {type DocumentHandle} from '../../../config/handles'
4
- import {useNormalizedResourceOptions} from '../../helpers/useNormalizedResourceOptions'
5
-
6
- interface DashboardMessageResource {
7
- id: string
8
- type?: 'media-library' | 'canvas'
9
- }
10
- /** Currently only used for dispatching intents to the dashboard,
11
- * but could easily be extended to other dashboard hooks
12
- * @beta
13
- */
14
- export function useResourceIdFromDocumentHandle(
15
- documentHandle: DocumentHandle,
16
- ): DashboardMessageResource {
17
- const options = useNormalizedResourceOptions(documentHandle)
18
- const {resource} = options
19
- let resourceId: string = ''
20
- let resourceType: 'media-library' | 'canvas' | undefined
21
-
22
- if (resource) {
23
- if (isDatasetResource(resource)) {
24
- resourceId = `${resource.projectId}.${resource.dataset}`
25
- resourceType = undefined
26
- } else if (isMediaLibraryResource(resource)) {
27
- resourceId = resource.mediaLibraryId
28
- resourceType = 'media-library'
29
- } else if (isCanvasResource(resource)) {
30
- resourceId = resource.canvasId
31
- resourceType = 'canvas'
32
- }
33
- }
34
-
35
- return {
36
- id: resourceId,
37
- type: resourceType,
38
- }
39
- }
@@ -1,66 +0,0 @@
1
- import {getDatasetsState, type SanityInstance} from '@sanity/sdk'
2
- import {beforeEach, describe, expect, it, vi} from 'vitest'
3
-
4
- import {createStateSourceHook} from '../helpers/createStateSourceHook'
5
-
6
- // Mock dependencies
7
- vi.mock('@sanity/sdk', () => ({
8
- getDatasetsState: vi.fn(() => ({
9
- getCurrent: vi.fn(() => undefined),
10
- })),
11
- resolveDatasets: vi.fn(),
12
- }))
13
- vi.mock('../helpers/createStateSourceHook', () => ({
14
- createStateSourceHook: vi.fn(),
15
- }))
16
-
17
- describe('useDatasets', () => {
18
- beforeEach(() => {
19
- vi.resetModules()
20
- vi.mock('@sanity/sdk', () => ({
21
- getDatasetsState: vi.fn(() => ({
22
- getCurrent: vi.fn(() => undefined),
23
- })),
24
- resolveDatasets: vi.fn(),
25
- }))
26
- vi.mock('../helpers/createStateSourceHook', () => ({
27
- createStateSourceHook: vi.fn(),
28
- }))
29
- })
30
-
31
- it('should call createStateSourceHook with correct arguments on import', async () => {
32
- await import('./useDatasets')
33
-
34
- expect(createStateSourceHook).toHaveBeenCalled()
35
- expect(createStateSourceHook).toHaveBeenCalledWith(
36
- expect.objectContaining({
37
- getState: expect.any(Function),
38
- shouldSuspend: expect.any(Function),
39
- suspender: expect.any(Function),
40
- }),
41
- )
42
- })
43
-
44
- it('shouldSuspend should call getDatasetsState and getCurrent', async () => {
45
- await import('./useDatasets')
46
-
47
- const mockCreateStateSourceHook = createStateSourceHook as ReturnType<typeof vi.fn>
48
- expect(mockCreateStateSourceHook.mock.calls.length).toBeGreaterThan(0)
49
- const createStateSourceHookArgs = mockCreateStateSourceHook.mock.calls[0][0]
50
- const shouldSuspend = createStateSourceHookArgs.shouldSuspend
51
-
52
- const mockInstance = {} as SanityInstance
53
- const mockOptions = {projectId: 'test-project'}
54
-
55
- const result = shouldSuspend(mockInstance, mockOptions)
56
-
57
- const mockGetDatasetsState = getDatasetsState as ReturnType<typeof vi.fn>
58
- expect(mockGetDatasetsState).toHaveBeenCalledWith(mockInstance, mockOptions)
59
-
60
- expect(mockGetDatasetsState.mock.results.length).toBeGreaterThan(0)
61
- const getDatasetsStateMockResult = mockGetDatasetsState.mock.results[0].value
62
- expect(getDatasetsStateMockResult.getCurrent).toHaveBeenCalled()
63
-
64
- expect(result).toBe(true)
65
- })
66
- })
@@ -1,65 +0,0 @@
1
- import {describe, expect, it} from 'vitest'
2
-
3
- import {normalizeResourceOptions} from './useNormalizedResourceOptions'
4
-
5
- describe('normalizeResourceOptions', () => {
6
- it('throws when both resource and resourceName are provided together', () => {
7
- const resource = {projectId: 'p', dataset: 'd'}
8
-
9
- expect(() =>
10
- normalizeResourceOptions(
11
- {resource, resourceName: 'my-resource'},
12
- {'my-resource': resource},
13
- undefined,
14
- ),
15
- ).toThrow(/cannot be used together/)
16
- })
17
-
18
- it('throws when no resource can be resolved from any source', () => {
19
- expect(() =>
20
- normalizeResourceOptions(
21
- {},
22
- {}, // no named resources
23
- undefined, // no context resource
24
- ),
25
- ).toThrow(/resource is required/)
26
- })
27
-
28
- it('uses explicit resource when provided', () => {
29
- const resource = {projectId: 'project-a', dataset: 'staging'}
30
-
31
- const normalized = normalizeResourceOptions(
32
- {resource},
33
- {},
34
- {projectId: 'default', dataset: 'prod'},
35
- )
36
-
37
- expect(normalized).toEqual({resource: {projectId: 'project-a', dataset: 'staging'}})
38
- })
39
-
40
- it('resolves resource from resourceName', () => {
41
- const resource = {projectId: 'p', dataset: 'd'}
42
-
43
- const normalized = normalizeResourceOptions(
44
- {resourceName: 'my-resource'},
45
- {'my-resource': resource},
46
- undefined,
47
- )
48
-
49
- expect(normalized).toEqual({resource})
50
- })
51
-
52
- it('throws when resourceName is not found in resources map', () => {
53
- expect(() => normalizeResourceOptions({resourceName: 'missing'}, {}, undefined)).toThrow(
54
- /no resource named/,
55
- )
56
- })
57
-
58
- it('falls back to context resource when neither resource nor resourceName is provided', () => {
59
- const contextResource = {projectId: 'default', dataset: 'prod'}
60
-
61
- const normalized = normalizeResourceOptions({}, {}, contextResource)
62
-
63
- expect(normalized).toEqual({resource: contextResource})
64
- })
65
- })