@sanity/sdk-react 2.7.0 → 2.9.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.
- package/dist/index.d.ts +144 -25
- package/dist/index.js +248 -139
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
- package/src/_exports/sdk-react.ts +1 -0
- package/src/components/SanityApp.tsx +1 -0
- package/src/components/auth/AuthBoundary.test.tsx +3 -0
- package/src/components/auth/LoginError.test.tsx +5 -0
- package/src/components/auth/LoginError.tsx +22 -1
- package/src/context/ResourceProvider.test.tsx +7 -1
- package/src/context/ResourceProvider.tsx +6 -0
- package/src/context/SDKStudioContext.ts +6 -0
- package/src/hooks/dashboard/useDispatchIntent.test.ts +2 -0
- package/src/hooks/dashboard/useWindowTitle.test.ts +213 -0
- package/src/hooks/dashboard/useWindowTitle.ts +112 -0
- package/src/hooks/document/useApplyDocumentActions.test.ts +113 -10
- package/src/hooks/document/useApplyDocumentActions.ts +99 -3
- package/src/hooks/document/useDocument.ts +22 -6
- package/src/hooks/document/useDocumentEvent.test.tsx +3 -3
- package/src/hooks/document/useDocumentEvent.ts +10 -3
- package/src/hooks/document/useDocumentPermissions.test.tsx +86 -2
- package/src/hooks/document/useDocumentPermissions.ts +22 -0
- package/src/hooks/document/useDocumentSyncStatus.test.ts +13 -2
- package/src/hooks/document/useDocumentSyncStatus.ts +14 -5
- package/src/hooks/document/useEditDocument.ts +34 -8
- package/src/hooks/documents/useDocuments.ts +2 -0
- package/src/hooks/helpers/useNormalizedSourceOptions.ts +50 -28
- package/src/hooks/helpers/useTrackHookUsage.ts +37 -0
- package/src/hooks/paginatedDocuments/usePaginatedDocuments.ts +2 -0
- package/src/hooks/presence/usePresence.ts +2 -0
- package/src/hooks/preview/useDocumentPreview.test.tsx +84 -193
- package/src/hooks/preview/useDocumentPreview.tsx +39 -55
- package/src/hooks/projection/useDocumentProjection.ts +2 -0
- package/src/hooks/query/useQuery.ts +2 -0
- package/src/hooks/releases/useActiveReleases.ts +32 -13
- package/src/hooks/releases/usePerspective.ts +26 -14
- package/src/hooks/users/useUser.ts +2 -0
- package/src/hooks/users/useUsers.ts +2 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {applyDocumentActions, type SanityInstance} from '@sanity/sdk'
|
|
2
2
|
import {describe, it} from 'vitest'
|
|
3
3
|
|
|
4
|
+
import {renderHook} from '../../../test/test-utils'
|
|
4
5
|
import {useSanityInstance} from '../context/useSanityInstance'
|
|
5
6
|
import {useApplyDocumentActions} from './useApplyDocumentActions'
|
|
6
7
|
|
|
@@ -31,8 +32,8 @@ describe('useApplyDocumentActions', () => {
|
|
|
31
32
|
})
|
|
32
33
|
|
|
33
34
|
it('uses the SanityInstance', async () => {
|
|
34
|
-
const
|
|
35
|
-
|
|
35
|
+
const {result} = renderHook(() => useApplyDocumentActions())
|
|
36
|
+
result.current({
|
|
36
37
|
type: 'document.edit',
|
|
37
38
|
documentType: 'post',
|
|
38
39
|
documentId: 'abc',
|
|
@@ -50,8 +51,8 @@ describe('useApplyDocumentActions', () => {
|
|
|
50
51
|
})
|
|
51
52
|
|
|
52
53
|
it('uses SanityInstance.match when projectId is overrideen', async () => {
|
|
53
|
-
const
|
|
54
|
-
|
|
54
|
+
const {result} = renderHook(() => useApplyDocumentActions())
|
|
55
|
+
result.current({
|
|
55
56
|
type: 'document.edit',
|
|
56
57
|
documentType: 'post',
|
|
57
58
|
documentId: 'abc',
|
|
@@ -73,8 +74,8 @@ describe('useApplyDocumentActions', () => {
|
|
|
73
74
|
})
|
|
74
75
|
|
|
75
76
|
it('uses SanityInstance when dataset is overrideen', async () => {
|
|
76
|
-
const
|
|
77
|
-
|
|
77
|
+
const {result} = renderHook(() => useApplyDocumentActions())
|
|
78
|
+
result.current({
|
|
78
79
|
type: 'document.edit',
|
|
79
80
|
documentType: 'post',
|
|
80
81
|
documentId: 'abc',
|
|
@@ -96,8 +97,8 @@ describe('useApplyDocumentActions', () => {
|
|
|
96
97
|
})
|
|
97
98
|
|
|
98
99
|
it('uses SanityInstance.amcth when projectId and dataset is overrideen', async () => {
|
|
99
|
-
const
|
|
100
|
-
|
|
100
|
+
const {result} = renderHook(() => useApplyDocumentActions())
|
|
101
|
+
result.current({
|
|
101
102
|
type: 'document.edit',
|
|
102
103
|
documentType: 'post',
|
|
103
104
|
documentId: 'abc',
|
|
@@ -121,9 +122,9 @@ describe('useApplyDocumentActions', () => {
|
|
|
121
122
|
})
|
|
122
123
|
|
|
123
124
|
it("throws if SanityInstance.match doesn't find anything", async () => {
|
|
124
|
-
const
|
|
125
|
+
const {result} = renderHook(() => useApplyDocumentActions())
|
|
125
126
|
expect(() => {
|
|
126
|
-
|
|
127
|
+
result.current({
|
|
127
128
|
type: 'document.edit',
|
|
128
129
|
documentType: 'post',
|
|
129
130
|
documentId: 'abc',
|
|
@@ -132,4 +133,106 @@ describe('useApplyDocumentActions', () => {
|
|
|
132
133
|
})
|
|
133
134
|
}).toThrow()
|
|
134
135
|
})
|
|
136
|
+
|
|
137
|
+
it('throws when actions have mismatched project IDs', async () => {
|
|
138
|
+
const {result} = renderHook(() => useApplyDocumentActions())
|
|
139
|
+
expect(() => {
|
|
140
|
+
result.current([
|
|
141
|
+
{
|
|
142
|
+
type: 'document.edit',
|
|
143
|
+
documentType: 'post',
|
|
144
|
+
documentId: 'abc',
|
|
145
|
+
projectId: 'p123',
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
type: 'document.edit',
|
|
149
|
+
documentType: 'post',
|
|
150
|
+
documentId: 'def',
|
|
151
|
+
projectId: 'p456',
|
|
152
|
+
},
|
|
153
|
+
])
|
|
154
|
+
}).toThrow(/Mismatched project IDs found in actions/)
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
it('throws when actions have mismatched datasets', async () => {
|
|
158
|
+
const {result} = renderHook(() => useApplyDocumentActions())
|
|
159
|
+
expect(() => {
|
|
160
|
+
result.current([
|
|
161
|
+
{
|
|
162
|
+
type: 'document.edit',
|
|
163
|
+
documentType: 'post',
|
|
164
|
+
documentId: 'abc',
|
|
165
|
+
projectId: 'p',
|
|
166
|
+
dataset: 'd1',
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
type: 'document.edit',
|
|
170
|
+
documentType: 'post',
|
|
171
|
+
documentId: 'def',
|
|
172
|
+
projectId: 'p',
|
|
173
|
+
dataset: 'd2',
|
|
174
|
+
},
|
|
175
|
+
])
|
|
176
|
+
}).toThrow(/Mismatched datasets found in actions/)
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
it('throws when actions have mismatched sources', async () => {
|
|
180
|
+
const {result} = renderHook(() => useApplyDocumentActions())
|
|
181
|
+
expect(() => {
|
|
182
|
+
result.current([
|
|
183
|
+
{
|
|
184
|
+
type: 'document.edit',
|
|
185
|
+
documentType: 'post',
|
|
186
|
+
documentId: 'abc',
|
|
187
|
+
source: {projectId: 'p', dataset: 'd1'},
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
type: 'document.edit',
|
|
191
|
+
documentType: 'post',
|
|
192
|
+
documentId: 'def',
|
|
193
|
+
source: {projectId: 'p', dataset: 'd2'},
|
|
194
|
+
},
|
|
195
|
+
])
|
|
196
|
+
}).toThrow(/Mismatched sources found in actions/)
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
it('throws when mixing projectId and source (projectId first)', async () => {
|
|
200
|
+
const {result} = renderHook(() => useApplyDocumentActions())
|
|
201
|
+
expect(() => {
|
|
202
|
+
result.current([
|
|
203
|
+
{
|
|
204
|
+
type: 'document.edit',
|
|
205
|
+
documentType: 'post',
|
|
206
|
+
documentId: 'abc',
|
|
207
|
+
projectId: 'p',
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
type: 'document.edit',
|
|
211
|
+
documentType: 'post',
|
|
212
|
+
documentId: 'def',
|
|
213
|
+
source: {projectId: 'p', dataset: 'd'},
|
|
214
|
+
},
|
|
215
|
+
])
|
|
216
|
+
}).toThrow(/Mismatches between projectId\/dataset options and source/)
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
it('throws when mixing source and projectId (source first)', async () => {
|
|
220
|
+
const {result} = renderHook(() => useApplyDocumentActions())
|
|
221
|
+
expect(() => {
|
|
222
|
+
result.current([
|
|
223
|
+
{
|
|
224
|
+
type: 'document.edit',
|
|
225
|
+
documentType: 'post',
|
|
226
|
+
documentId: 'abc',
|
|
227
|
+
source: {projectId: 'p', dataset: 'd'},
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
type: 'document.edit',
|
|
231
|
+
documentType: 'post',
|
|
232
|
+
documentId: 'def',
|
|
233
|
+
projectId: 'p',
|
|
234
|
+
},
|
|
235
|
+
])
|
|
236
|
+
}).toThrow(/Mismatches between projectId\/dataset options and source/)
|
|
237
|
+
})
|
|
135
238
|
})
|
|
@@ -5,8 +5,14 @@ import {
|
|
|
5
5
|
type DocumentAction,
|
|
6
6
|
} from '@sanity/sdk'
|
|
7
7
|
import {type SanityDocument} from 'groq'
|
|
8
|
+
import {useContext} from 'react'
|
|
8
9
|
|
|
10
|
+
import {SourcesContext} from '../../context/SourcesContext'
|
|
9
11
|
import {useSanityInstance} from '../context/useSanityInstance'
|
|
12
|
+
import {
|
|
13
|
+
normalizeSourceOptions,
|
|
14
|
+
type WithSourceNameSupport,
|
|
15
|
+
} from '../helpers/useNormalizedSourceOptions'
|
|
10
16
|
// this import is used in an `{@link useEditDocument}`
|
|
11
17
|
// eslint-disable-next-line unused-imports/no-unused-imports, import/consistent-type-specifier-style
|
|
12
18
|
import type {useEditDocument} from './useEditDocument'
|
|
@@ -23,7 +29,7 @@ interface UseApplyDocumentActions {
|
|
|
23
29
|
action:
|
|
24
30
|
| DocumentAction<TDocumentType, TDataset, TProjectId>
|
|
25
31
|
| DocumentAction<TDocumentType, TDataset, TProjectId>[],
|
|
26
|
-
options?: ApplyDocumentActionsOptions
|
|
32
|
+
options?: WithSourceNameSupport<ApplyDocumentActionsOptions>,
|
|
27
33
|
) => Promise<ActionsResult<SanityDocument<TDocumentType, `${TProjectId}.${TDataset}`>>>
|
|
28
34
|
}
|
|
29
35
|
|
|
@@ -149,17 +155,85 @@ interface UseApplyDocumentActions {
|
|
|
149
155
|
* return <button onClick={handleCreateArticle}>Create Article</button>
|
|
150
156
|
* }
|
|
151
157
|
* ```
|
|
158
|
+
*
|
|
159
|
+
* @example Create a new document in a release
|
|
160
|
+
* ```tsx
|
|
161
|
+
* import {
|
|
162
|
+
* createDocument,
|
|
163
|
+
* createDocumentHandle,
|
|
164
|
+
* useApplyDocumentActions
|
|
165
|
+
* } from '@sanity/sdk-react'
|
|
166
|
+
*
|
|
167
|
+
* function CreateArticleButton() {
|
|
168
|
+
* const apply = useApplyDocumentActions()
|
|
169
|
+
*
|
|
170
|
+
* const handleCreateArticle = () => {
|
|
171
|
+
* // Use any valid document ID — not the internal `versions.<releaseName>.<id>` format or "drafts.<id>" format.
|
|
172
|
+
* // New documents must be explicitly created with `createDocument` to become part of a release.
|
|
173
|
+
* const newDocHandle = createDocumentHandle({
|
|
174
|
+
* documentId: crypto.randomUUID(), // or the existing document ID you want to make a release version of
|
|
175
|
+
* documentType: 'article',
|
|
176
|
+
* perspective: {releaseName: 'summer-drop'},
|
|
177
|
+
* })
|
|
178
|
+
*
|
|
179
|
+
* apply(
|
|
180
|
+
* createDocument(newDocHandle, {
|
|
181
|
+
* title: 'New Article',
|
|
182
|
+
* author: 'John Doe',
|
|
183
|
+
* publishedAt: new Date().toISOString(),
|
|
184
|
+
* })
|
|
185
|
+
* )
|
|
186
|
+
* }
|
|
187
|
+
*
|
|
188
|
+
* return <button onClick={handleCreateArticle}>Create Article</button>
|
|
189
|
+
* }
|
|
190
|
+
* ```
|
|
191
|
+
*
|
|
192
|
+
* @example Edit an existing document in a release
|
|
193
|
+
* ```tsx
|
|
194
|
+
* import {
|
|
195
|
+
* editDocument,
|
|
196
|
+
* createDocumentHandle,
|
|
197
|
+
* useApplyDocumentActions
|
|
198
|
+
* } from '@sanity/sdk-react'
|
|
199
|
+
*
|
|
200
|
+
* function EditArticleInReleaseButton({documentId}: {documentId: string}) {
|
|
201
|
+
* const apply = useApplyDocumentActions()
|
|
202
|
+
*
|
|
203
|
+
* const handleEdit = () => {
|
|
204
|
+
* // Pass the document's regular ID — not `versions.<releaseName>.<id>`.
|
|
205
|
+
* // Documents that already have a version in the release can be edited directly with `editDocument`.
|
|
206
|
+
* const docHandle = createDocumentHandle({
|
|
207
|
+
* documentId,
|
|
208
|
+
* documentType: 'article',
|
|
209
|
+
* perspective: {releaseName: 'summer-drop'},
|
|
210
|
+
* })
|
|
211
|
+
*
|
|
212
|
+
* apply(editDocument(docHandle, {title: 'Updated for release'}))
|
|
213
|
+
* }
|
|
214
|
+
*
|
|
215
|
+
* return <button onClick={handleEdit}>Edit in Release</button>
|
|
216
|
+
* }
|
|
217
|
+
* ```
|
|
152
218
|
*/
|
|
153
219
|
export const useApplyDocumentActions: UseApplyDocumentActions = () => {
|
|
154
220
|
const instance = useSanityInstance()
|
|
221
|
+
const sources = useContext(SourcesContext)
|
|
155
222
|
|
|
156
223
|
return (actionOrActions, options) => {
|
|
157
224
|
const actions = Array.isArray(actionOrActions) ? actionOrActions : [actionOrActions]
|
|
225
|
+
const normalizedOptions = options ? normalizeSourceOptions(options, sources) : undefined
|
|
158
226
|
|
|
159
227
|
let projectId
|
|
160
228
|
let dataset
|
|
229
|
+
let source
|
|
161
230
|
for (const action of actions) {
|
|
162
231
|
if (action.projectId) {
|
|
232
|
+
if (source) {
|
|
233
|
+
throw new Error(
|
|
234
|
+
`Mismatches between projectId/dataset options and source in actions. Found projectId "${action.projectId}" and dataset "${action.dataset}" but expected source "${source}".`,
|
|
235
|
+
)
|
|
236
|
+
}
|
|
163
237
|
if (!projectId) projectId = action.projectId
|
|
164
238
|
if (action.projectId !== projectId) {
|
|
165
239
|
throw new Error(
|
|
@@ -176,6 +250,20 @@ export const useApplyDocumentActions: UseApplyDocumentActions = () => {
|
|
|
176
250
|
}
|
|
177
251
|
}
|
|
178
252
|
}
|
|
253
|
+
|
|
254
|
+
if (action.source) {
|
|
255
|
+
if (!source) source = action.source
|
|
256
|
+
if (action.source !== source) {
|
|
257
|
+
throw new Error(
|
|
258
|
+
`Mismatched sources found in actions. All actions must belong to the same source. Found "${action.source}" but expected "${source}".`,
|
|
259
|
+
)
|
|
260
|
+
}
|
|
261
|
+
if (projectId || dataset) {
|
|
262
|
+
throw new Error(
|
|
263
|
+
`Mismatches between projectId/dataset options and source in actions. Found "${action.source}" but expected project "${projectId}" and dataset "${dataset}".`,
|
|
264
|
+
)
|
|
265
|
+
}
|
|
266
|
+
}
|
|
179
267
|
}
|
|
180
268
|
|
|
181
269
|
if (projectId || dataset) {
|
|
@@ -187,9 +275,17 @@ export const useApplyDocumentActions: UseApplyDocumentActions = () => {
|
|
|
187
275
|
)
|
|
188
276
|
}
|
|
189
277
|
|
|
190
|
-
return applyDocumentActions(actualInstance, {
|
|
278
|
+
return applyDocumentActions(actualInstance, {
|
|
279
|
+
actions,
|
|
280
|
+
source,
|
|
281
|
+
...normalizedOptions,
|
|
282
|
+
})
|
|
191
283
|
}
|
|
192
284
|
|
|
193
|
-
return applyDocumentActions(instance, {
|
|
285
|
+
return applyDocumentActions(instance, {
|
|
286
|
+
actions,
|
|
287
|
+
source,
|
|
288
|
+
...normalizedOptions,
|
|
289
|
+
})
|
|
194
290
|
}
|
|
195
291
|
}
|
|
@@ -3,6 +3,11 @@ import {type SanityDocument} from 'groq'
|
|
|
3
3
|
import {identity} from 'rxjs'
|
|
4
4
|
|
|
5
5
|
import {createStateSourceHook} from '../helpers/createStateSourceHook'
|
|
6
|
+
import {
|
|
7
|
+
useNormalizedSourceOptions,
|
|
8
|
+
type WithSourceNameSupport,
|
|
9
|
+
} from '../helpers/useNormalizedSourceOptions'
|
|
10
|
+
import {useTrackHookUsage} from '../helpers/useTrackHookUsage'
|
|
6
11
|
// used in an `{@link useDocumentProjection}` and `{@link useQuery}`
|
|
7
12
|
// eslint-disable-next-line import/consistent-type-specifier-style, unused-imports/no-unused-imports
|
|
8
13
|
import type {useDocumentProjection} from '../projection/useDocumentProjection'
|
|
@@ -33,10 +38,17 @@ const wrapHookWithData = <TParams extends unknown[], TReturn>(
|
|
|
33
38
|
return useHook
|
|
34
39
|
}
|
|
35
40
|
|
|
41
|
+
type UseDocumentOptions<
|
|
42
|
+
TPath extends string | undefined = undefined,
|
|
43
|
+
TDocumentType extends string = string,
|
|
44
|
+
TDataset extends string = string,
|
|
45
|
+
TProjectId extends string = string,
|
|
46
|
+
> = WithSourceNameSupport<DocumentOptions<TPath, TDocumentType, TDataset, TProjectId>>
|
|
47
|
+
|
|
36
48
|
interface UseDocument {
|
|
37
49
|
/** @internal */
|
|
38
50
|
<TDocumentType extends string, TDataset extends string, TProjectId extends string = string>(
|
|
39
|
-
options:
|
|
51
|
+
options: UseDocumentOptions<undefined, TDocumentType, TDataset, TProjectId>,
|
|
40
52
|
): {data: SanityDocument<TDocumentType, `${TProjectId}.${TDataset}`> | null}
|
|
41
53
|
|
|
42
54
|
/** @internal */
|
|
@@ -46,7 +58,7 @@ interface UseDocument {
|
|
|
46
58
|
TDataset extends string = string,
|
|
47
59
|
TProjectId extends string = string,
|
|
48
60
|
>(
|
|
49
|
-
options:
|
|
61
|
+
options: UseDocumentOptions<TPath, TDocumentType>,
|
|
50
62
|
): {
|
|
51
63
|
data: JsonMatch<SanityDocument<TDocumentType, `${TProjectId}.${TDataset}`>, TPath> | undefined
|
|
52
64
|
}
|
|
@@ -124,7 +136,7 @@ interface UseDocument {
|
|
|
124
136
|
TDataset extends string = string,
|
|
125
137
|
TProjectId extends string = string,
|
|
126
138
|
>(
|
|
127
|
-
options:
|
|
139
|
+
options: UseDocumentOptions<TPath, TDocumentType>,
|
|
128
140
|
): TPath extends string
|
|
129
141
|
? {
|
|
130
142
|
data:
|
|
@@ -194,13 +206,13 @@ interface UseDocument {
|
|
|
194
206
|
* @inlineType DocumentOptions
|
|
195
207
|
*/
|
|
196
208
|
<TData, TPath extends string>(
|
|
197
|
-
options:
|
|
209
|
+
options: UseDocumentOptions<TPath>,
|
|
198
210
|
): TPath extends string ? {data: TData | undefined} : {data: TData | null}
|
|
199
211
|
|
|
200
212
|
/**
|
|
201
213
|
* @internal
|
|
202
214
|
*/
|
|
203
|
-
(options:
|
|
215
|
+
(options: UseDocumentOptions): {data: unknown}
|
|
204
216
|
}
|
|
205
217
|
|
|
206
218
|
/**
|
|
@@ -226,4 +238,8 @@ interface UseDocument {
|
|
|
226
238
|
*
|
|
227
239
|
* @function
|
|
228
240
|
*/
|
|
229
|
-
export const useDocument = wrapHookWithData(
|
|
241
|
+
export const useDocument = wrapHookWithData((options: UseDocumentOptions) => {
|
|
242
|
+
useTrackHookUsage('useDocument')
|
|
243
|
+
const normalizedOptions = useNormalizedSourceOptions(options)
|
|
244
|
+
return useDocumentValue(normalizedOptions)
|
|
245
|
+
}) as UseDocument
|
|
@@ -37,11 +37,11 @@ describe('useDocumentEvent hook', () => {
|
|
|
37
37
|
expect(vi.mocked(subscribeDocumentEvents)).toHaveBeenCalledTimes(1)
|
|
38
38
|
expect(vi.mocked(subscribeDocumentEvents).mock.calls[0][0]).toEqual(expect.any(Object))
|
|
39
39
|
|
|
40
|
-
const
|
|
41
|
-
expect(typeof
|
|
40
|
+
const options = vi.mocked(subscribeDocumentEvents).mock.calls[0][1]
|
|
41
|
+
expect(typeof options.eventHandler).toBe('function')
|
|
42
42
|
|
|
43
43
|
const event = {type: 'edited', documentId: 'doc1', outgoing: {}} as DocumentEvent
|
|
44
|
-
|
|
44
|
+
options.eventHandler(event)
|
|
45
45
|
expect(handleEvent).toHaveBeenCalledWith(event)
|
|
46
46
|
})
|
|
47
47
|
|
|
@@ -2,6 +2,8 @@ import {type DatasetHandle, type DocumentEvent, subscribeDocumentEvents} from '@
|
|
|
2
2
|
import {useCallback, useEffect, useInsertionEffect, useRef} from 'react'
|
|
3
3
|
|
|
4
4
|
import {useSanityInstance} from '../context/useSanityInstance'
|
|
5
|
+
import {useNormalizedSourceOptions} from '../helpers/useNormalizedSourceOptions'
|
|
6
|
+
import {useTrackHookUsage} from '../helpers/useTrackHookUsage'
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* @public
|
|
@@ -75,8 +77,10 @@ export function useDocumentEvent<
|
|
|
75
77
|
// Single options object parameter
|
|
76
78
|
options: UseDocumentEventOptions<TDataset, TProjectId>,
|
|
77
79
|
): void {
|
|
80
|
+
useTrackHookUsage('useDocumentEvent')
|
|
78
81
|
// Destructure handler and datasetHandle from options
|
|
79
|
-
const
|
|
82
|
+
const normalizedOptions = useNormalizedSourceOptions(options)
|
|
83
|
+
const {onEvent, ...datasetHandle} = normalizedOptions
|
|
80
84
|
const ref = useRef(onEvent)
|
|
81
85
|
|
|
82
86
|
useInsertionEffect(() => {
|
|
@@ -89,6 +93,9 @@ export function useDocumentEvent<
|
|
|
89
93
|
|
|
90
94
|
const instance = useSanityInstance(datasetHandle)
|
|
91
95
|
useEffect(() => {
|
|
92
|
-
return subscribeDocumentEvents(instance,
|
|
93
|
-
|
|
96
|
+
return subscribeDocumentEvents(instance, {
|
|
97
|
+
eventHandler: stableHandler,
|
|
98
|
+
source: datasetHandle.source,
|
|
99
|
+
})
|
|
100
|
+
}, [instance, datasetHandle.source, stableHandler])
|
|
94
101
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {type DocumentAction, type DocumentPermissionsResult, getPermissionsState} from '@sanity/sdk'
|
|
2
2
|
import {act, renderHook, waitFor} from '@testing-library/react'
|
|
3
|
-
import {BehaviorSubject, firstValueFrom} from 'rxjs'
|
|
3
|
+
import {BehaviorSubject, firstValueFrom, Observable} from 'rxjs'
|
|
4
4
|
import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest'
|
|
5
5
|
|
|
6
6
|
import {ResourceProvider} from '../../context/ResourceProvider'
|
|
@@ -68,7 +68,8 @@ describe('usePermissions', () => {
|
|
|
68
68
|
|
|
69
69
|
// Set up the getPermissionsState mock
|
|
70
70
|
vi.mocked(getPermissionsState).mockReturnValue({
|
|
71
|
-
observable:
|
|
71
|
+
observable:
|
|
72
|
+
permissionsSubject.asObservable() as unknown as Observable<DocumentPermissionsResult>,
|
|
72
73
|
subscribe: mockSubscribe,
|
|
73
74
|
getCurrent: mockGetCurrent,
|
|
74
75
|
})
|
|
@@ -182,6 +183,89 @@ describe('usePermissions', () => {
|
|
|
182
183
|
}).toThrow(/Mismatched datasets found in actions/)
|
|
183
184
|
})
|
|
184
185
|
|
|
186
|
+
it('should throw an error if actions have mismatched sources', () => {
|
|
187
|
+
const actions = [
|
|
188
|
+
{
|
|
189
|
+
type: 'document.publish' as const,
|
|
190
|
+
documentId: 'doc1',
|
|
191
|
+
documentType: 'article',
|
|
192
|
+
source: {projectId: 'p1', dataset: 'd1'},
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
type: 'document.publish' as const,
|
|
196
|
+
documentId: 'doc2',
|
|
197
|
+
documentType: 'article',
|
|
198
|
+
source: {projectId: 'p2', dataset: 'd2'},
|
|
199
|
+
},
|
|
200
|
+
]
|
|
201
|
+
|
|
202
|
+
expect(() => {
|
|
203
|
+
renderHook(() => useDocumentPermissions(actions), {
|
|
204
|
+
wrapper: ({children}) => (
|
|
205
|
+
<ResourceProvider
|
|
206
|
+
projectId={mockAction.projectId}
|
|
207
|
+
dataset={mockAction.dataset}
|
|
208
|
+
fallback={null}
|
|
209
|
+
>
|
|
210
|
+
{children}
|
|
211
|
+
</ResourceProvider>
|
|
212
|
+
),
|
|
213
|
+
})
|
|
214
|
+
}).toThrow(/Mismatched sources found in actions/)
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
it('should throw an error when mixing projectId and source (projectId first)', () => {
|
|
218
|
+
const actions = [
|
|
219
|
+
mockAction,
|
|
220
|
+
{
|
|
221
|
+
type: 'document.publish' as const,
|
|
222
|
+
documentId: 'doc2',
|
|
223
|
+
documentType: 'article',
|
|
224
|
+
source: {projectId: 'p', dataset: 'd'},
|
|
225
|
+
},
|
|
226
|
+
]
|
|
227
|
+
|
|
228
|
+
expect(() => {
|
|
229
|
+
renderHook(() => useDocumentPermissions(actions), {
|
|
230
|
+
wrapper: ({children}) => (
|
|
231
|
+
<ResourceProvider
|
|
232
|
+
projectId={mockAction.projectId}
|
|
233
|
+
dataset={mockAction.dataset}
|
|
234
|
+
fallback={null}
|
|
235
|
+
>
|
|
236
|
+
{children}
|
|
237
|
+
</ResourceProvider>
|
|
238
|
+
),
|
|
239
|
+
})
|
|
240
|
+
}).toThrow(/Mismatches between projectId\/dataset options and source/)
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
it('should throw an error when mixing source and projectId (source first)', () => {
|
|
244
|
+
const actions = [
|
|
245
|
+
{
|
|
246
|
+
type: 'document.publish' as const,
|
|
247
|
+
documentId: 'doc1',
|
|
248
|
+
documentType: 'article',
|
|
249
|
+
source: {projectId: 'p', dataset: 'd'},
|
|
250
|
+
},
|
|
251
|
+
mockAction,
|
|
252
|
+
]
|
|
253
|
+
|
|
254
|
+
expect(() => {
|
|
255
|
+
renderHook(() => useDocumentPermissions(actions), {
|
|
256
|
+
wrapper: ({children}) => (
|
|
257
|
+
<ResourceProvider
|
|
258
|
+
projectId={mockAction.projectId}
|
|
259
|
+
dataset={mockAction.dataset}
|
|
260
|
+
fallback={null}
|
|
261
|
+
>
|
|
262
|
+
{children}
|
|
263
|
+
</ResourceProvider>
|
|
264
|
+
),
|
|
265
|
+
})
|
|
266
|
+
}).toThrow(/Mismatches between projectId\/dataset options and source/)
|
|
267
|
+
})
|
|
268
|
+
|
|
185
269
|
it('should wait for permissions to be ready before rendering', async () => {
|
|
186
270
|
// Set up initial value as undefined (not ready)
|
|
187
271
|
act(() => {
|
|
@@ -3,6 +3,7 @@ import {useCallback, useMemo, useSyncExternalStore} from 'react'
|
|
|
3
3
|
import {filter, firstValueFrom} from 'rxjs'
|
|
4
4
|
|
|
5
5
|
import {useSanityInstance} from '../context/useSanityInstance'
|
|
6
|
+
import {trackHookUsage} from '../helpers/useTrackHookUsage'
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
*
|
|
@@ -91,9 +92,15 @@ export function useDocumentPermissions(
|
|
|
91
92
|
// if actions is an array, we need to check that all actions belong to the same project and dataset
|
|
92
93
|
let projectId
|
|
93
94
|
let dataset
|
|
95
|
+
let source
|
|
94
96
|
|
|
95
97
|
for (const action of actions) {
|
|
96
98
|
if (action.projectId) {
|
|
99
|
+
if (source) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
`Mismatches between projectId/dataset options and source in actions. Found projectId "${action.projectId}" and dataset "${action.dataset}" but expected source "${source}".`,
|
|
102
|
+
)
|
|
103
|
+
}
|
|
97
104
|
if (!projectId) projectId = action.projectId
|
|
98
105
|
if (action.projectId !== projectId) {
|
|
99
106
|
throw new Error(
|
|
@@ -110,9 +117,24 @@ export function useDocumentPermissions(
|
|
|
110
117
|
}
|
|
111
118
|
}
|
|
112
119
|
}
|
|
120
|
+
|
|
121
|
+
if (action.source) {
|
|
122
|
+
if (!source) source = action.source
|
|
123
|
+
if (action.source !== source) {
|
|
124
|
+
throw new Error(
|
|
125
|
+
`Mismatched sources found in actions. All actions must belong to the same source. Found "${action.source}" but expected "${source}".`,
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
if (projectId || dataset) {
|
|
129
|
+
throw new Error(
|
|
130
|
+
`Mismatches between projectId/dataset options and source in actions. Found "${action.source}" but expected project "${projectId}" and dataset "${dataset}".`,
|
|
131
|
+
)
|
|
132
|
+
}
|
|
133
|
+
}
|
|
113
134
|
}
|
|
114
135
|
|
|
115
136
|
const instance = useSanityInstance({projectId, dataset})
|
|
137
|
+
trackHookUsage(instance, 'useDocumentPermissions')
|
|
116
138
|
const isDocumentReady = useCallback(
|
|
117
139
|
() => getPermissionsState(instance, {actions}).getCurrent() !== undefined,
|
|
118
140
|
[actions, instance],
|
|
@@ -1,15 +1,25 @@
|
|
|
1
1
|
import {getDocumentSyncStatus} from '@sanity/sdk'
|
|
2
2
|
import {describe, it} from 'vitest'
|
|
3
3
|
|
|
4
|
+
import {renderHook} from '../../../test/test-utils'
|
|
4
5
|
import {createStateSourceHook} from '../helpers/createStateSourceHook'
|
|
5
6
|
|
|
6
7
|
const mockHook = vi.fn()
|
|
7
8
|
vi.mock('../helpers/createStateSourceHook', () => ({createStateSourceHook: vi.fn(() => mockHook)}))
|
|
8
|
-
vi.mock('@sanity/sdk', () =>
|
|
9
|
+
vi.mock('@sanity/sdk', async (importOriginal) => {
|
|
10
|
+
const original = await importOriginal<typeof import('@sanity/sdk')>()
|
|
11
|
+
return {
|
|
12
|
+
...original,
|
|
13
|
+
getDocumentSyncStatus: vi.fn(),
|
|
14
|
+
}
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
vi.mock('../context/useSanityInstance')
|
|
9
18
|
|
|
10
19
|
describe('useDocumentSyncStatus', () => {
|
|
11
20
|
it('calls `createStateSourceHook` with `getDocumentSyncStatus`', async () => {
|
|
12
21
|
const {useDocumentSyncStatus} = await import('./useDocumentSyncStatus')
|
|
22
|
+
renderHook(() => useDocumentSyncStatus({documentId: '1', documentType: 'test'}))
|
|
13
23
|
expect(createStateSourceHook).toHaveBeenCalledWith(
|
|
14
24
|
expect.objectContaining({
|
|
15
25
|
getState: getDocumentSyncStatus,
|
|
@@ -18,6 +28,7 @@ describe('useDocumentSyncStatus', () => {
|
|
|
18
28
|
getConfig: expect.any(Function),
|
|
19
29
|
}),
|
|
20
30
|
)
|
|
21
|
-
|
|
31
|
+
// Verify that the hook was created and can be called
|
|
32
|
+
expect(mockHook).toHaveBeenCalled()
|
|
22
33
|
})
|
|
23
34
|
})
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type DocumentHandle,
|
|
3
|
+
type DocumentOptions,
|
|
3
4
|
getDocumentSyncStatus,
|
|
4
5
|
resolveDocument,
|
|
5
6
|
type SanityInstance,
|
|
@@ -8,6 +9,7 @@ import {
|
|
|
8
9
|
import {identity} from 'rxjs'
|
|
9
10
|
|
|
10
11
|
import {createStateSourceHook} from '../helpers/createStateSourceHook'
|
|
12
|
+
import {useNormalizedSourceOptions} from '../helpers/useNormalizedSourceOptions'
|
|
11
13
|
|
|
12
14
|
type UseDocumentSyncStatus = {
|
|
13
15
|
/**
|
|
@@ -45,11 +47,7 @@ type UseDocumentSyncStatus = {
|
|
|
45
47
|
(doc: DocumentHandle): boolean
|
|
46
48
|
}
|
|
47
49
|
|
|
48
|
-
|
|
49
|
-
* @public
|
|
50
|
-
* @function
|
|
51
|
-
*/
|
|
52
|
-
export const useDocumentSyncStatus: UseDocumentSyncStatus = createStateSourceHook({
|
|
50
|
+
const useDocumentSyncStatusValue = createStateSourceHook({
|
|
53
51
|
getState: getDocumentSyncStatus as (
|
|
54
52
|
instance: SanityInstance,
|
|
55
53
|
doc: DocumentHandle,
|
|
@@ -59,3 +57,14 @@ export const useDocumentSyncStatus: UseDocumentSyncStatus = createStateSourceHoo
|
|
|
59
57
|
suspender: (instance, doc: DocumentHandle) => resolveDocument(instance, doc),
|
|
60
58
|
getConfig: identity,
|
|
61
59
|
})
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @public
|
|
63
|
+
* @function
|
|
64
|
+
*/
|
|
65
|
+
export const useDocumentSyncStatus: UseDocumentSyncStatus = (
|
|
66
|
+
options: DocumentOptions<string | undefined>,
|
|
67
|
+
) => {
|
|
68
|
+
const normalizedOptions = useNormalizedSourceOptions(options)
|
|
69
|
+
return useDocumentSyncStatusValue(normalizedOptions)
|
|
70
|
+
}
|