@sanity/sdk-react 0.0.0-alpha.21 → 0.0.0-alpha.23

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 (71) hide show
  1. package/dist/index.d.ts +502 -3460
  2. package/dist/index.js +400 -465
  3. package/dist/index.js.map +1 -1
  4. package/package.json +17 -15
  5. package/src/_exports/index.ts +4 -5
  6. package/src/components/SDKProvider.test.tsx +78 -54
  7. package/src/components/SDKProvider.tsx +31 -26
  8. package/src/components/SanityApp.test.tsx +121 -15
  9. package/src/components/SanityApp.tsx +26 -15
  10. package/src/components/auth/AuthBoundary.test.tsx +32 -14
  11. package/src/components/auth/AuthBoundary.tsx +53 -23
  12. package/src/components/auth/LoginCallback.test.tsx +19 -6
  13. package/src/components/auth/LoginCallback.tsx +2 -11
  14. package/src/components/auth/LoginError.test.tsx +12 -4
  15. package/src/components/auth/LoginError.tsx +13 -21
  16. package/src/components/auth/LoginFooter.test.tsx +7 -3
  17. package/src/context/ResourceProvider.test.tsx +157 -0
  18. package/src/context/ResourceProvider.tsx +111 -0
  19. package/src/context/SanityInstanceContext.ts +1 -1
  20. package/src/hooks/auth/useLoginUrl.tsx +14 -0
  21. package/src/hooks/client/useClient.ts +2 -1
  22. package/src/hooks/comlink/useManageFavorite.test.ts +16 -8
  23. package/src/hooks/comlink/useManageFavorite.ts +37 -13
  24. package/src/hooks/comlink/useRecordDocumentHistoryEvent.test.ts +8 -4
  25. package/src/hooks/comlink/useRecordDocumentHistoryEvent.ts +10 -8
  26. package/src/hooks/context/useSanityInstance.test.tsx +157 -15
  27. package/src/hooks/context/useSanityInstance.ts +66 -26
  28. package/src/hooks/dashboard/useNavigateToStudioDocument.test.ts +13 -31
  29. package/src/hooks/dashboard/useNavigateToStudioDocument.ts +12 -15
  30. package/src/hooks/dashboard/{useStudioWorkspacesByResourceId.test.tsx → useStudioWorkspacesByProjectIdDataset.test.tsx} +13 -13
  31. package/src/hooks/dashboard/{useStudioWorkspacesByResourceId.ts → useStudioWorkspacesByProjectIdDataset.ts} +10 -9
  32. package/src/hooks/datasets/useDatasets.ts +15 -4
  33. package/src/hooks/document/useApplyDocumentActions.test.ts +4 -9
  34. package/src/hooks/document/useApplyDocumentActions.ts +6 -31
  35. package/src/hooks/document/useDocument.test.ts +2 -2
  36. package/src/hooks/document/useDocument.ts +40 -19
  37. package/src/hooks/document/useDocumentEvent.test.ts +2 -3
  38. package/src/hooks/document/useDocumentEvent.ts +7 -11
  39. package/src/hooks/document/useDocumentPermissions.test.ts +204 -0
  40. package/src/hooks/document/useDocumentPermissions.ts +31 -23
  41. package/src/hooks/document/useDocumentSyncStatus.ts +5 -4
  42. package/src/hooks/document/useEditDocument.test.ts +2 -3
  43. package/src/hooks/document/useEditDocument.ts +43 -29
  44. package/src/hooks/documents/useDocuments.test.tsx +30 -3
  45. package/src/hooks/documents/useDocuments.ts +20 -7
  46. package/src/hooks/helpers/createCallbackHook.test.tsx +2 -2
  47. package/src/hooks/helpers/createCallbackHook.tsx +2 -3
  48. package/src/hooks/helpers/createStateSourceHook.test.tsx +1 -1
  49. package/src/hooks/helpers/createStateSourceHook.tsx +5 -8
  50. package/src/hooks/paginatedDocuments/usePaginatedDocuments.test.tsx +43 -18
  51. package/src/hooks/paginatedDocuments/usePaginatedDocuments.ts +36 -50
  52. package/src/hooks/preview/usePreview.test.tsx +66 -7
  53. package/src/hooks/preview/usePreview.tsx +17 -12
  54. package/src/hooks/projection/useProjection.test.tsx +68 -3
  55. package/src/hooks/projection/useProjection.ts +21 -24
  56. package/src/hooks/projects/useProject.ts +7 -4
  57. package/src/hooks/query/useQuery.ts +32 -14
  58. package/src/hooks/users/useUsers.test.tsx +330 -0
  59. package/src/hooks/users/useUsers.ts +65 -52
  60. package/src/components/Login/LoginLinks.test.tsx +0 -90
  61. package/src/components/Login/LoginLinks.tsx +0 -58
  62. package/src/components/auth/Login.test.tsx +0 -27
  63. package/src/components/auth/Login.tsx +0 -39
  64. package/src/components/auth/LoginLayout.test.tsx +0 -19
  65. package/src/components/auth/LoginLayout.tsx +0 -69
  66. package/src/components/auth/authTestHelpers.tsx +0 -11
  67. package/src/context/SanityProvider.test.tsx +0 -25
  68. package/src/context/SanityProvider.tsx +0 -50
  69. package/src/hooks/auth/useLoginUrls.test.tsx +0 -68
  70. package/src/hooks/auth/useLoginUrls.tsx +0 -52
  71. package/src/hooks/users/useUsers.test.ts +0 -163
@@ -8,7 +8,7 @@ import {useNavigateToStudioDocument} from './useNavigateToStudioDocument'
8
8
  // Mock dependencies
9
9
  const mockSendMessage = vi.fn()
10
10
  const mockFetch = vi.fn()
11
- let mockWorkspacesByResourceId = {}
11
+ let mockWorkspacesByProjectIdAndDataset = {}
12
12
  let mockWorkspacesIsConnected = true
13
13
  let mockStatusCallback: ((status: Status) => void) | null = null
14
14
 
@@ -24,10 +24,10 @@ vi.mock('../comlink/useWindowConnection', () => {
24
24
  }
25
25
  })
26
26
 
27
- vi.mock('./useStudioWorkspacesByResourceId', () => {
27
+ vi.mock('./useStudioWorkspacesByProjectIdDataset', () => {
28
28
  return {
29
- useStudioWorkspacesByResourceId: () => ({
30
- workspacesByResourceId: mockWorkspacesByResourceId,
29
+ useStudioWorkspacesByProjectIdDataset: () => ({
30
+ workspacesByProjectIdAndDataset: mockWorkspacesByProjectIdAndDataset,
31
31
  error: null,
32
32
  isConnected: mockWorkspacesIsConnected,
33
33
  }),
@@ -36,9 +36,10 @@ vi.mock('./useStudioWorkspacesByResourceId', () => {
36
36
 
37
37
  describe('useNavigateToStudioDocument', () => {
38
38
  const mockDocumentHandle: DocumentHandle = {
39
- _id: 'doc123',
40
- _type: 'article',
41
- resourceId: 'document:project1.dataset1:doc123',
39
+ documentId: 'doc123',
40
+ documentType: 'article',
41
+ projectId: 'project1',
42
+ dataset: 'dataset1',
42
43
  }
43
44
 
44
45
  const mockWorkspace = {
@@ -53,7 +54,7 @@ describe('useNavigateToStudioDocument', () => {
53
54
 
54
55
  beforeEach(() => {
55
56
  vi.resetAllMocks()
56
- mockWorkspacesByResourceId = {
57
+ mockWorkspacesByProjectIdAndDataset = {
57
58
  'project1:dataset1': [mockWorkspace],
58
59
  }
59
60
  mockWorkspacesIsConnected = true
@@ -95,7 +96,7 @@ describe('useNavigateToStudioDocument', () => {
95
96
  })
96
97
 
97
98
  it('does not send message when not connected', () => {
98
- mockWorkspacesByResourceId = {}
99
+ mockWorkspacesByProjectIdAndDataset = {}
99
100
  mockWorkspacesIsConnected = false
100
101
 
101
102
  const {result} = renderHook(() => useNavigateToStudioDocument(mockDocumentHandle))
@@ -111,7 +112,7 @@ describe('useNavigateToStudioDocument', () => {
111
112
  })
112
113
 
113
114
  it('does not send message when no workspace is found', () => {
114
- mockWorkspacesByResourceId = {}
115
+ mockWorkspacesByProjectIdAndDataset = {}
115
116
  mockWorkspacesIsConnected = true
116
117
 
117
118
  const {result} = renderHook(() => useNavigateToStudioDocument(mockDocumentHandle))
@@ -126,30 +127,11 @@ describe('useNavigateToStudioDocument', () => {
126
127
  expect(mockSendMessage).not.toHaveBeenCalled()
127
128
  })
128
129
 
129
- it('handles invalid resourceId format', () => {
130
- const invalidDocHandle: DocumentHandle = {
131
- _id: 'doc123',
132
- _type: 'article',
133
- resourceId: 'document:project1.invalid:doc123' as `document:${string}.${string}:${string}`,
134
- }
135
-
136
- const {result} = renderHook(() => useNavigateToStudioDocument(invalidDocHandle))
137
-
138
- // Simulate connection
139
- act(() => {
140
- mockStatusCallback?.('connected')
141
- })
142
-
143
- result.current.navigateToStudioDocument()
144
-
145
- expect(mockSendMessage).not.toHaveBeenCalled()
146
- })
147
-
148
130
  it('warns when multiple workspaces are found', () => {
149
131
  const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
150
132
  const mockWorkspace2 = {...mockWorkspace, _ref: 'workspace2'}
151
133
 
152
- mockWorkspacesByResourceId = {
134
+ mockWorkspacesByProjectIdAndDataset = {
153
135
  'project1:dataset1': [mockWorkspace, mockWorkspace2],
154
136
  }
155
137
 
@@ -164,7 +146,7 @@ describe('useNavigateToStudioDocument', () => {
164
146
 
165
147
  expect(consoleSpy).toHaveBeenCalledWith(
166
148
  'Multiple workspaces found for document',
167
- mockDocumentHandle.resourceId,
149
+ mockDocumentHandle,
168
150
  )
169
151
  expect(mockSendMessage).toHaveBeenCalledWith(
170
152
  'dashboard/v1/bridge/navigate-to-resource',
@@ -4,7 +4,7 @@ import {type DocumentHandle} from '@sanity/sdk'
4
4
  import {useCallback, useState} from 'react'
5
5
 
6
6
  import {useWindowConnection} from '../comlink/useWindowConnection'
7
- import {useStudioWorkspacesByResourceId} from './useStudioWorkspacesByResourceId'
7
+ import {useStudioWorkspacesByProjectIdDataset} from './useStudioWorkspacesByProjectIdDataset'
8
8
 
9
9
  interface NavigateToResourceMessage {
10
10
  type: 'dashboard/v1/bridge/navigate-to-resource'
@@ -66,8 +66,8 @@ interface NavigateToStudioResult {
66
66
  export function useNavigateToStudioDocument(
67
67
  documentHandle: DocumentHandle,
68
68
  ): NavigateToStudioResult {
69
- const {workspacesByResourceId, isConnected: workspacesConnected} =
70
- useStudioWorkspacesByResourceId()
69
+ const {workspacesByProjectIdAndDataset, isConnected: workspacesConnected} =
70
+ useStudioWorkspacesByProjectIdDataset()
71
71
  const [status, setStatus] = useState<Status>('idle')
72
72
  const {sendMessage} = useWindowConnection<NavigateToResourceMessage, never>({
73
73
  name: SDK_NODE_NAME,
@@ -76,28 +76,25 @@ export function useNavigateToStudioDocument(
76
76
  })
77
77
 
78
78
  const navigateToStudioDocument = useCallback(() => {
79
- if (!workspacesConnected || status !== 'connected' || !documentHandle.resourceId) {
80
- return
81
- }
79
+ const {projectId, dataset} = documentHandle
82
80
 
83
- // Extract projectId and dataset from the resourceId (current format: document:projectId.dataset:documentId)
84
- const [, projectAndDataset] = documentHandle.resourceId.split(':')
85
- const [projectId, dataset] = projectAndDataset.split('.')
86
- if (!projectId || !dataset) {
81
+ if (!workspacesConnected || status !== 'connected' || !projectId || !dataset) {
87
82
  return
88
83
  }
89
84
 
90
85
  // Find the workspace for this document
91
- const workspaces = workspacesByResourceId[`${projectId}:${dataset}`]
86
+ const workspaces = workspacesByProjectIdAndDataset[`${projectId}:${dataset}`]
92
87
  if (!workspaces?.length) {
93
88
  // eslint-disable-next-line no-console
94
- console.warn('No workspace found for document', documentHandle.resourceId)
89
+ console.warn(
90
+ `No workspace found for document with projectId: ${projectId} and dataset: ${dataset}`,
91
+ )
95
92
  return
96
93
  }
97
94
 
98
95
  if (workspaces.length > 1) {
99
96
  // eslint-disable-next-line no-console
100
- console.warn('Multiple workspaces found for document', documentHandle.resourceId)
97
+ console.warn('Multiple workspaces found for document', documentHandle)
101
98
  // eslint-disable-next-line no-console
102
99
  console.warn('Using the first one', workspaces[0])
103
100
  }
@@ -109,12 +106,12 @@ export function useNavigateToStudioDocument(
109
106
  data: {
110
107
  resourceId: workspace._ref,
111
108
  resourceType: 'studio',
112
- path: `/intent/edit/id=${documentHandle._id};type=${documentHandle._type}`,
109
+ path: `/intent/edit/id=${documentHandle.documentId};type=${documentHandle.documentType}`,
113
110
  },
114
111
  }
115
112
 
116
113
  sendMessage(message.type, message.data)
117
- }, [documentHandle, workspacesConnected, status, sendMessage, workspacesByResourceId])
114
+ }, [documentHandle, workspacesConnected, status, workspacesByProjectIdAndDataset, sendMessage])
118
115
 
119
116
  return {
120
117
  navigateToStudioDocument,
@@ -3,7 +3,7 @@ import {renderHook, waitFor} from '@testing-library/react'
3
3
  import {describe, expect, it, vi} from 'vitest'
4
4
 
5
5
  import {useWindowConnection, type WindowConnection} from '../comlink/useWindowConnection'
6
- import {useStudioWorkspacesByResourceId} from './useStudioWorkspacesByResourceId'
6
+ import {useStudioWorkspacesByProjectIdDataset} from './useStudioWorkspacesByProjectIdDataset'
7
7
 
8
8
  vi.mock('../comlink/useWindowConnection', () => ({
9
9
  useWindowConnection: vi.fn(),
@@ -72,13 +72,13 @@ describe('useStudioWorkspacesByResourceId', () => {
72
72
  } as unknown as WindowConnection<Message>
73
73
  })
74
74
 
75
- const {result} = renderHook(() => useStudioWorkspacesByResourceId())
75
+ const {result} = renderHook(() => useStudioWorkspacesByProjectIdDataset())
76
76
 
77
77
  // Call onStatus with 'idle' to simulate not connected
78
78
  if (capturedOnStatus) capturedOnStatus('idle')
79
79
 
80
80
  expect(result.current).toEqual({
81
- workspacesByResourceId: {},
81
+ workspacesByProjectIdAndDataset: {},
82
82
  error: null,
83
83
  isConnected: false,
84
84
  })
@@ -97,13 +97,13 @@ describe('useStudioWorkspacesByResourceId', () => {
97
97
  } as unknown as WindowConnection<Message>
98
98
  })
99
99
 
100
- const {result} = renderHook(() => useStudioWorkspacesByResourceId())
100
+ const {result} = renderHook(() => useStudioWorkspacesByProjectIdDataset())
101
101
 
102
102
  // Call onStatus with 'connected' to simulate connected state
103
103
  if (capturedOnStatus) capturedOnStatus('connected')
104
104
 
105
105
  await waitFor(() => {
106
- expect(result.current.workspacesByResourceId).toEqual({
106
+ expect(result.current.workspacesByProjectIdAndDataset).toEqual({
107
107
  'project1:dataset1': [
108
108
  {
109
109
  name: 'workspace1',
@@ -160,13 +160,13 @@ describe('useStudioWorkspacesByResourceId', () => {
160
160
  } as unknown as WindowConnection<Message>
161
161
  })
162
162
 
163
- const {result} = renderHook(() => useStudioWorkspacesByResourceId())
163
+ const {result} = renderHook(() => useStudioWorkspacesByProjectIdDataset())
164
164
 
165
165
  // Call onStatus with 'connected' to simulate connected state
166
166
  if (capturedOnStatus) capturedOnStatus('connected')
167
167
 
168
168
  await waitFor(() => {
169
- expect(result.current.workspacesByResourceId).toEqual({})
169
+ expect(result.current.workspacesByProjectIdAndDataset).toEqual({})
170
170
  expect(result.current.error).toBe('Failed to fetch workspaces')
171
171
  expect(result.current.isConnected).toBe(true)
172
172
  })
@@ -187,13 +187,13 @@ describe('useStudioWorkspacesByResourceId', () => {
187
187
  } as unknown as WindowConnection<Message>
188
188
  })
189
189
 
190
- const {result} = renderHook(() => useStudioWorkspacesByResourceId())
190
+ const {result} = renderHook(() => useStudioWorkspacesByProjectIdDataset())
191
191
 
192
192
  // Call onStatus with 'connected' to simulate connected state
193
193
  if (capturedOnStatus) capturedOnStatus('connected')
194
194
 
195
195
  await waitFor(() => {
196
- expect(result.current.workspacesByResourceId).toEqual({})
196
+ expect(result.current.workspacesByProjectIdAndDataset).toEqual({})
197
197
  expect(result.current.error).toBeNull()
198
198
  expect(result.current.isConnected).toBe(true)
199
199
  })
@@ -221,13 +221,13 @@ describe('useStudioWorkspacesByResourceId', () => {
221
221
  } as unknown as WindowConnection<Message>
222
222
  })
223
223
 
224
- const {result} = renderHook(() => useStudioWorkspacesByResourceId())
224
+ const {result} = renderHook(() => useStudioWorkspacesByProjectIdDataset())
225
225
 
226
226
  // Call onStatus with 'connected' to simulate connected state
227
227
  if (capturedOnStatus) capturedOnStatus('connected')
228
228
 
229
229
  await waitFor(() => {
230
- expect(result.current.workspacesByResourceId).toEqual({})
230
+ expect(result.current.workspacesByProjectIdAndDataset).toEqual({})
231
231
  expect(result.current.error).toBeNull()
232
232
  expect(result.current.isConnected).toBe(true)
233
233
  })
@@ -264,13 +264,13 @@ describe('useStudioWorkspacesByResourceId', () => {
264
264
  } as unknown as WindowConnection<Message>
265
265
  })
266
266
 
267
- const {result} = renderHook(() => useStudioWorkspacesByResourceId())
267
+ const {result} = renderHook(() => useStudioWorkspacesByProjectIdDataset())
268
268
 
269
269
  // Call onStatus with 'connected' to simulate connected state
270
270
  if (capturedOnStatus) capturedOnStatus('connected')
271
271
 
272
272
  await waitFor(() => {
273
- expect(result.current.workspacesByResourceId).toEqual({})
273
+ expect(result.current.workspacesByProjectIdAndDataset).toEqual({})
274
274
  expect(result.current.error).toBeNull()
275
275
  expect(result.current.isConnected).toBe(true)
276
276
  })
@@ -14,12 +14,12 @@ interface Workspace {
14
14
  _ref: string
15
15
  }
16
16
 
17
- interface WorkspacesByResourceId {
18
- [key: string]: Workspace[] // key format: `${projectId}:${dataset}`
17
+ interface WorkspacesByProjectIdDataset {
18
+ [key: `${string}:${string}`]: Workspace[] // key format: `${projectId}:${dataset}`
19
19
  }
20
20
 
21
21
  interface StudioWorkspacesResult {
22
- workspacesByResourceId: WorkspacesByResourceId
22
+ workspacesByProjectIdAndDataset: WorkspacesByProjectIdDataset
23
23
  error: string | null
24
24
  isConnected: boolean
25
25
  }
@@ -28,8 +28,9 @@ interface StudioWorkspacesResult {
28
28
  * Hook that fetches studio workspaces and organizes them by projectId:dataset
29
29
  * @internal
30
30
  */
31
- export function useStudioWorkspacesByResourceId(): StudioWorkspacesResult {
32
- const [workspacesByResourceId, setWorkspacesByResourceId] = useState<WorkspacesByResourceId>({})
31
+ export function useStudioWorkspacesByProjectIdDataset(): StudioWorkspacesResult {
32
+ const [workspacesByProjectIdAndDataset, setWorkspacesByProjectIdAndDataset] =
33
+ useState<WorkspacesByProjectIdDataset>({})
33
34
  const [status, setStatus] = useState<Status>('idle')
34
35
  const [error, setError] = useState<string | null>(null)
35
36
 
@@ -50,13 +51,13 @@ export function useStudioWorkspacesByResourceId(): StudioWorkspacesResult {
50
51
  context: {availableResources: Array<{projectId: string; workspaces: Workspace[]}>}
51
52
  }>('dashboard/v1/bridge/context', undefined, {signal})
52
53
 
53
- const workspaceMap: WorkspacesByResourceId = {}
54
+ const workspaceMap: WorkspacesByProjectIdDataset = {}
54
55
 
55
56
  data.context.availableResources.forEach((resource) => {
56
57
  if (!resource.projectId || !resource.workspaces?.length) return
57
58
 
58
59
  resource.workspaces.forEach((workspace) => {
59
- const key = `${resource.projectId}:${workspace.dataset}`
60
+ const key = `${resource.projectId}:${workspace.dataset}` as const
60
61
  if (!workspaceMap[key]) {
61
62
  workspaceMap[key] = []
62
63
  }
@@ -64,7 +65,7 @@ export function useStudioWorkspacesByResourceId(): StudioWorkspacesResult {
64
65
  })
65
66
  })
66
67
 
67
- setWorkspacesByResourceId(workspaceMap)
68
+ setWorkspacesByProjectIdAndDataset(workspaceMap)
68
69
  setError(null)
69
70
  } catch (err: unknown) {
70
71
  if (err instanceof Error) {
@@ -85,7 +86,7 @@ export function useStudioWorkspacesByResourceId(): StudioWorkspacesResult {
85
86
  }, [fetch, status])
86
87
 
87
88
  return {
88
- workspacesByResourceId,
89
+ workspacesByProjectIdAndDataset,
89
90
  error,
90
91
  isConnected: status === 'connected',
91
92
  }
@@ -1,5 +1,11 @@
1
1
  import {type DatasetsResponse} from '@sanity/client'
2
- import {getDatasetsState, resolveDatasets, type SanityInstance, type StateSource} from '@sanity/sdk'
2
+ import {
3
+ getDatasetsState,
4
+ type ProjectHandle,
5
+ resolveDatasets,
6
+ type SanityInstance,
7
+ type StateSource,
8
+ } from '@sanity/sdk'
3
9
 
4
10
  import {createStateSourceHook} from '../helpers/createStateSourceHook'
5
11
 
@@ -33,8 +39,13 @@ type UseDatasets = {
33
39
  * @function
34
40
  */
35
41
  export const useDatasets: UseDatasets = createStateSourceHook({
36
- // remove `undefined` since we're suspending when that is the case
37
- getState: getDatasetsState as (instance: SanityInstance) => StateSource<DatasetsResponse>,
38
- shouldSuspend: (instance) => getDatasetsState(instance).getCurrent() === undefined,
42
+ getState: getDatasetsState as (
43
+ instance: SanityInstance,
44
+ projectHandle?: ProjectHandle,
45
+ ) => StateSource<DatasetsResponse>,
46
+ shouldSuspend: (instance, projectHandle?: ProjectHandle) =>
47
+ // remove `undefined` since we're suspending when that is the case
48
+ getDatasetsState(instance, projectHandle).getCurrent() === undefined,
39
49
  suspender: resolveDatasets,
50
+ getConfig: (projectHandle?: ProjectHandle) => projectHandle,
40
51
  })
@@ -1,4 +1,4 @@
1
- import {applyDocumentActions, createDocument, type ResourceId} from '@sanity/sdk'
1
+ import {applyDocumentActions} from '@sanity/sdk'
2
2
  import {describe, it} from 'vitest'
3
3
 
4
4
  import {createCallbackHook} from '../helpers/createCallbackHook'
@@ -12,14 +12,9 @@ vi.mock('@sanity/sdk', async (importOriginal) => {
12
12
  })
13
13
 
14
14
  describe('useApplyDocumentActions', () => {
15
- it('calls `createCallbackHook` with `applyDocumentActions`', async () => {
16
- const {useApplyDocumentActions} = await import('./useApplyDocumentActions')
17
- const resourceId: ResourceId = 'project1.dataset1'
15
+ it('calls `createCallbackHook` with `applyActions`', async () => {
18
16
  expect(createCallbackHook).not.toHaveBeenCalled()
19
-
20
- expect(applyDocumentActions).not.toHaveBeenCalled()
21
- const apply = useApplyDocumentActions(resourceId)
22
- apply(createDocument({_type: 'author'}))
23
- expect(applyDocumentActions).toHaveBeenCalledWith(createDocument({_type: 'author'}))
17
+ await import('./useApplyDocumentActions')
18
+ expect(createCallbackHook).toHaveBeenCalledWith(applyDocumentActions)
24
19
  })
25
20
  })
@@ -1,11 +1,4 @@
1
- import {
2
- type ActionsResult,
3
- applyDocumentActions,
4
- type ApplyDocumentActionsOptions,
5
- type DocumentAction,
6
- type ResourceId,
7
- } from '@sanity/sdk'
8
- import {type SanityDocument} from '@sanity/types'
1
+ import {applyDocumentActions} from '@sanity/sdk'
9
2
 
10
3
  import {createCallbackHook} from '../helpers/createCallbackHook'
11
4
 
@@ -16,7 +9,7 @@ import {createCallbackHook} from '../helpers/createCallbackHook'
16
9
  * Provides a callback for applying one or more actions to a document.
17
10
  *
18
11
  * @category Documents
19
- * @param resourceId - The resource ID of the document to apply actions to. If not provided, the document will use the default resource.
12
+ * @param dataset - An optional dataset handle with projectId and dataset. If not provided, the nearest SanityInstance from context will be used.
20
13
  * @returns A function that takes one more more {@link DocumentAction}s and returns a promise that resolves to an {@link ActionsResult}.
21
14
  * @example Publish or unpublish a document
22
15
  * ```
@@ -24,7 +17,7 @@ import {createCallbackHook} from '../helpers/createCallbackHook'
24
17
  * import { useApplyDocumentActions } from '@sanity/sdk-react'
25
18
  *
26
19
  * const apply = useApplyDocumentActions()
27
- * const myDocument = { _id: 'my-document-id', _type: 'my-document-type' }
20
+ * const myDocument = { documentId: 'my-document-id', documentType: 'my-document-type' }
28
21
  *
29
22
  * return (
30
23
  * <button onClick={() => apply(publishDocument(myDocument))}>Publish</button>
@@ -40,7 +33,7 @@ import {createCallbackHook} from '../helpers/createCallbackHook'
40
33
  * const apply = useApplyDocumentActions()
41
34
  *
42
35
  * const handleCreateAndPublish = () => {
43
- * const handle = { _id: window.crypto.randomUUID(), _type: 'my-document-type' }
36
+ * const handle = { documentId: window.crypto.randomUUID(), documentType: 'my-document-type' }
44
37
  * apply([
45
38
  * createDocument(handle),
46
39
  * publishDocument(handle),
@@ -49,27 +42,9 @@ import {createCallbackHook} from '../helpers/createCallbackHook'
49
42
  *
50
43
  * return (
51
44
  * <button onClick={handleCreateAndPublish}>
52
- * Im feeling lucky
45
+ * I'm feeling lucky
53
46
  * </button>
54
47
  * )
55
48
  * ```
56
49
  */
57
- export function useApplyDocumentActions(
58
- resourceId?: ResourceId,
59
- ): <TDocument extends SanityDocument>(
60
- action: DocumentAction<TDocument> | DocumentAction<TDocument>[],
61
- options?: ApplyDocumentActionsOptions,
62
- ) => Promise<ActionsResult<TDocument>>
63
-
64
- /** @beta */
65
- export function useApplyDocumentActions(
66
- resourceId?: ResourceId,
67
- ): (
68
- action: DocumentAction | DocumentAction[],
69
- options?: ApplyDocumentActionsOptions,
70
- ) => Promise<ActionsResult> {
71
- return _useApplyDocumentActions(resourceId)()
72
- }
73
-
74
- const _useApplyDocumentActions = (resourceId?: ResourceId) =>
75
- createCallbackHook(applyDocumentActions, resourceId)
50
+ export const useApplyDocumentActions = createCallbackHook(applyDocumentActions)
@@ -46,7 +46,7 @@ describe('useDocument hook', () => {
46
46
  subscribe,
47
47
  } as unknown as StateSource<unknown>)
48
48
 
49
- const {result} = renderHook(() => useDocument({_id: 'doc1', _type: 'book'}))
49
+ const {result} = renderHook(() => useDocument({documentId: 'doc1', documentType: 'book'}))
50
50
 
51
51
  expect(result.current).toEqual(doc)
52
52
  expect(getCurrent).toHaveBeenCalled()
@@ -69,7 +69,7 @@ describe('useDocument hook', () => {
69
69
  // Render the hook and capture the thrown promise.
70
70
  const {result} = renderHook(() => {
71
71
  try {
72
- return useDocument({_id: 'doc1', _type: 'book'})
72
+ return useDocument({documentId: 'doc1', documentType: 'book'})
73
73
  } catch (e) {
74
74
  return e
75
75
  }
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  type DocumentHandle,
3
3
  getDocumentState,
4
- getResourceId,
5
4
  type JsonMatch,
6
5
  type JsonMatchPath,
7
6
  resolveDocument,
8
7
  } from '@sanity/sdk'
9
8
  import {type SanityDocument} from '@sanity/types'
9
+ import {identity} from 'rxjs'
10
10
 
11
11
  import {createStateSourceHook} from '../helpers/createStateSourceHook'
12
12
 
@@ -16,20 +16,26 @@ import {createStateSourceHook} from '../helpers/createStateSourceHook'
16
16
  * ## useDocument(doc, path)
17
17
  * Read and subscribe to nested values in a document
18
18
  * @category Documents
19
- * @param doc - The document to read state from. If you pass a `DocumentHandle` with a `resourceId` in the DocumentResourceId format (`document:projectId.dataset:documentId`)
20
- * the document will be read from the specified Sanity project and dataset that is included in the handle. If no `resourceId` is provided, the default project and dataset from your `SanityApp` configuration will be used.
19
+ * @param doc - The document to read state from, specified as a DocumentHandle
21
20
  * @param path - The path to the nested value to read from
22
21
  * @returns The value at the specified path
23
22
  * @example
24
23
  * ```tsx
25
- * import {type DocumentHandle, useDocument} from '@sanity/sdk-react'
24
+ * import {useDocument} from '@sanity/sdk-react'
26
25
  *
27
- * function OrderLink({documentHandle}: {documentHandle: DocumentHandle}) {
26
+ * const documentHandle = {
27
+ * documentId: 'order-123',
28
+ * documentType: 'order',
29
+ * projectId: 'abc123',
30
+ * dataset: 'production'
31
+ * }
32
+ *
33
+ * function OrderLink() {
28
34
  * const title = useDocument(documentHandle, 'title')
29
35
  * const id = useDocument(documentHandle, '_id')
30
36
  *
31
37
  * return (
32
- * <a href=`/order/${id}`>Order {title} today!</a>
38
+ * <a href={`/order/${id}`}>Order {title} today!</a>
33
39
  * )
34
40
  * }
35
41
  * ```
@@ -44,11 +50,11 @@ export function useDocument<
44
50
  * @beta
45
51
  * ## useDocument(doc)
46
52
  * Read and subscribe to an entire document
47
- * @param doc - The document to read state from
53
+ * @param doc - The document to read state from, specified as a DocumentHandle
48
54
  * @returns The document state as an object
49
55
  * @example
50
56
  * ```tsx
51
- * import {type SanityDocument, type DocumentHandle, useDocument} from '@sanity/sdk-react'
57
+ * import {type SanityDocument, useDocument} from '@sanity/sdk-react'
52
58
  *
53
59
  * interface Book extends SanityDocument {
54
60
  * title: string
@@ -56,19 +62,30 @@ export function useDocument<
56
62
  * summary: string
57
63
  * }
58
64
  *
59
- * function DocumentView({documentHandle}: {documentHandle: DocumentHandle}) {
65
+ * const documentHandle = {
66
+ * documentId: 'book-123',
67
+ * documentType: 'book',
68
+ * projectId: 'abc123',
69
+ * dataset: 'production'
70
+ * }
71
+ *
72
+ * function DocumentView() {
60
73
  * const book = useDocument<Book>(documentHandle)
61
74
  *
75
+ * if (!book) {
76
+ * return <div>Loading...</div>
77
+ * }
78
+ *
62
79
  * return (
63
80
  * <article>
64
- * <h1>{book?.title}</h1>
65
- * <address>By {book?.author}</address>
81
+ * <h1>{book.title}</h1>
82
+ * <address>By {book.author}</address>
66
83
  *
67
84
  * <h2>Summary</h2>
68
- * {book?.summary}
85
+ * {book.summary}
69
86
  *
70
87
  * <h2>Order</h2>
71
- * <a href=`/order/${book._id}`>Order {book?.title} today!</a>
88
+ * <a href={`/order/${book._id}`}>Order {book.title} today!</a>
72
89
  * </article>
73
90
  * )
74
91
  * }
@@ -81,17 +98,21 @@ export function useDocument<TDocument extends SanityDocument>(
81
98
 
82
99
  /**
83
100
  * @beta
84
- * Reads and subscribes to a documents realtime state, incorporating both local and remote changes.
85
- * When called with a `path` argument, the hook will return the nested values state.
86
- * When called without a `path` argument, the entire documents state will be returned.
101
+ * Reads and subscribes to a document's realtime state, incorporating both local and remote changes.
102
+ * When called with a `path` argument, the hook will return the nested value's state.
103
+ * When called without a `path` argument, the entire document's state will be returned.
87
104
  *
88
105
  * @remarks
89
106
  * `useDocument` is designed to be used within a realtime context in which local updates to documents
90
107
  * need to be displayed before they are persisted to the remote copy. This can be useful within a collaborative
91
108
  * or realtime editing interface where local changes need to be reflected immediately.
92
109
  *
110
+ * The hook automatically uses the correct Sanity instance based on the project and dataset
111
+ * specified in the DocumentHandle. This makes it easy to work with documents from different
112
+ * projects or datasets in the same component.
113
+ *
93
114
  * However, this hook can be too resource intensive for applications where static document values simply
94
- * need to be displayed (or when changes to documents dont need to be reflected immediately);
115
+ * need to be displayed (or when changes to documents don't need to be reflected immediately);
95
116
  * consider using `usePreview` or `useQuery` for these use cases instead. These hooks leverage the Sanity
96
117
  * Live Content API to provide a more efficient way to read and subscribe to document state.
97
118
  */
@@ -101,7 +122,7 @@ export function useDocument(doc: DocumentHandle, path?: string): unknown {
101
122
 
102
123
  const _useDocument = createStateSourceHook<[doc: DocumentHandle, path?: string], unknown>({
103
124
  getState: getDocumentState,
104
- shouldSuspend: (instance, doc) => getDocumentState(instance, doc._id).getCurrent() === undefined,
125
+ shouldSuspend: (instance, doc) => getDocumentState(instance, doc).getCurrent() === undefined,
105
126
  suspender: resolveDocument,
106
- getResourceId: (doc) => getResourceId(doc.resourceId),
127
+ getConfig: identity,
107
128
  })
@@ -22,9 +22,8 @@ vi.mock('../context/useSanityInstance', () => ({
22
22
 
23
23
  const instance = createSanityInstance({projectId: 'p', dataset: 'd'})
24
24
  const docHandle: DocumentHandle = {
25
- _id: 'doc1',
26
- _type: 'book',
27
- resourceId: 'document:p.d:doc1',
25
+ documentId: 'doc1',
26
+ documentType: 'book',
28
27
  }
29
28
 
30
29
  describe('useDocumentEvent hook', () => {