@sanity/sdk-react 2.7.0 → 3.0.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +125 -63
- package/dist/index.d.ts +381 -571
- package/dist/index.js +450 -366
- package/dist/index.js.map +1 -1
- package/package.json +6 -8
- package/src/_exports/index.ts +4 -0
- package/src/_exports/sdk-react.ts +16 -0
- package/src/components/SDKProvider.test.tsx +23 -58
- package/src/components/SDKProvider.tsx +38 -30
- package/src/components/SanityApp.test.tsx +12 -68
- package/src/components/SanityApp.tsx +88 -65
- package/src/components/auth/AuthBoundary.test.tsx +11 -26
- package/src/components/auth/LoginError.test.tsx +5 -0
- package/src/components/auth/LoginError.tsx +23 -2
- package/src/config/handles.ts +53 -0
- package/src/context/ComlinkTokenRefresh.test.tsx +27 -10
- package/src/context/DefaultResourceContext.ts +10 -0
- package/src/context/PerspectiveContext.ts +12 -0
- package/src/context/ResourceProvider.test.tsx +99 -19
- package/src/context/ResourceProvider.tsx +103 -37
- package/src/context/ResourcesContext.tsx +7 -0
- package/src/context/SDKStudioContext.test.tsx +33 -28
- package/src/context/SDKStudioContext.ts +6 -0
- package/src/context/renderSanityApp.test.tsx +49 -151
- package/src/context/renderSanityApp.tsx +8 -12
- package/src/hooks/agent/agentActions.test.tsx +1 -1
- package/src/hooks/agent/agentActions.ts +56 -19
- package/src/hooks/auth/useDashboardOrganizationId.test.tsx +8 -2
- package/src/hooks/auth/useVerifyOrgProjects.test.tsx +32 -8
- package/src/hooks/client/useClient.test.tsx +4 -1
- package/src/hooks/client/useClient.ts +0 -1
- package/src/hooks/context/useDefaultResource.test.tsx +25 -0
- package/src/hooks/context/useDefaultResource.ts +30 -0
- package/src/hooks/context/useSanityInstance.test.tsx +2 -140
- package/src/hooks/context/useSanityInstance.ts +9 -53
- package/src/hooks/dashboard/useDispatchIntent.test.ts +24 -15
- package/src/hooks/dashboard/useDispatchIntent.ts +7 -7
- package/src/hooks/dashboard/useManageFavorite.test.tsx +34 -94
- package/src/hooks/dashboard/useManageFavorite.ts +16 -10
- package/src/hooks/dashboard/useNavigateToStudioDocument.test.ts +7 -5
- package/src/hooks/dashboard/useNavigateToStudioDocument.ts +6 -2
- package/src/hooks/dashboard/useRecordDocumentHistoryEvent.test.ts +2 -0
- package/src/hooks/dashboard/useRecordDocumentHistoryEvent.ts +2 -1
- package/src/hooks/dashboard/utils/useResourceIdFromDocumentHandle.test.ts +17 -38
- package/src/hooks/dashboard/utils/useResourceIdFromDocumentHandle.ts +12 -19
- package/src/hooks/datasets/useDatasets.test.ts +8 -22
- package/src/hooks/datasets/useDatasets.ts +8 -16
- package/src/hooks/document/useApplyDocumentActions.test.ts +98 -52
- package/src/hooks/document/useApplyDocumentActions.ts +35 -37
- package/src/hooks/document/useDocument.test.tsx +8 -37
- package/src/hooks/document/useDocument.ts +78 -129
- package/src/hooks/document/useDocumentEvent.test.tsx +7 -19
- package/src/hooks/document/useDocumentEvent.ts +21 -19
- package/src/hooks/document/useDocumentPermissions.test.tsx +75 -84
- package/src/hooks/document/useDocumentPermissions.ts +41 -28
- package/src/hooks/document/useDocumentSyncStatus.test.ts +13 -3
- package/src/hooks/document/useDocumentSyncStatus.ts +19 -14
- package/src/hooks/document/useEditDocument.test.tsx +28 -70
- package/src/hooks/document/useEditDocument.ts +29 -149
- package/src/hooks/documents/useDocuments.test.tsx +44 -64
- package/src/hooks/documents/useDocuments.ts +19 -25
- package/src/hooks/helpers/createCallbackHook.test.tsx +19 -13
- package/src/hooks/helpers/createStateSourceHook.test.tsx +10 -10
- package/src/hooks/helpers/createStateSourceHook.tsx +2 -4
- package/src/hooks/helpers/useNormalizedResourceOptions.test.ts +65 -0
- package/src/hooks/helpers/useNormalizedResourceOptions.ts +127 -0
- package/src/hooks/paginatedDocuments/usePaginatedDocuments.test.tsx +27 -34
- package/src/hooks/paginatedDocuments/usePaginatedDocuments.ts +19 -20
- package/src/hooks/presence/usePresence.test.tsx +71 -9
- package/src/hooks/presence/usePresence.ts +28 -3
- package/src/hooks/preview/useDocumentPreview.test.tsx +85 -193
- package/src/hooks/preview/useDocumentPreview.tsx +42 -62
- package/src/hooks/projection/useDocumentProjection.test.tsx +9 -37
- package/src/hooks/projection/useDocumentProjection.ts +9 -82
- package/src/hooks/projects/useProject.test.ts +1 -2
- package/src/hooks/projects/useProject.ts +7 -8
- package/src/hooks/query/useQuery.test.tsx +5 -6
- package/src/hooks/query/useQuery.ts +12 -91
- package/src/hooks/releases/useActiveReleases.test.tsx +2 -2
- package/src/hooks/releases/useActiveReleases.ts +25 -13
- package/src/hooks/releases/usePerspective.test.tsx +9 -17
- package/src/hooks/releases/usePerspective.ts +29 -18
- package/src/hooks/users/useUser.test.tsx +9 -3
- package/src/hooks/users/useUser.ts +1 -1
- package/src/hooks/users/useUsers.test.tsx +5 -2
- package/src/hooks/users/useUsers.ts +1 -1
- package/src/context/SourcesContext.tsx +0 -7
- package/src/hooks/helpers/useNormalizedSourceOptions.ts +0 -85
|
@@ -1,84 +1,44 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type ActionsResult,
|
|
3
|
-
type DocumentOptions,
|
|
4
|
-
editDocument,
|
|
5
|
-
getDocumentState,
|
|
6
|
-
type JsonMatch,
|
|
7
|
-
resolveDocument,
|
|
8
|
-
} from '@sanity/sdk'
|
|
9
|
-
import {type SanityDocument} from 'groq'
|
|
1
|
+
import {type ActionsResult, editDocument, getDocumentState, resolveDocument} from '@sanity/sdk'
|
|
10
2
|
import {useCallback} from 'react'
|
|
11
3
|
|
|
4
|
+
import {type DocumentHandle} from '../../config/handles'
|
|
12
5
|
import {useSanityInstance} from '../context/useSanityInstance'
|
|
6
|
+
import {useNormalizedResourceOptions} from '../helpers/useNormalizedResourceOptions'
|
|
13
7
|
import {useApplyDocumentActions} from './useApplyDocumentActions'
|
|
14
8
|
|
|
15
9
|
const ignoredKeys = ['_id', '_type', '_createdAt', '_updatedAt', '_rev']
|
|
16
10
|
|
|
17
11
|
type Updater<TValue> = TValue | ((currentValue: TValue) => TValue)
|
|
18
12
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
*
|
|
24
|
-
* @param options - Document options including `documentId`, `documentType`, and optionally `projectId`/`dataset`.
|
|
25
|
-
* @returns A stable function to update the document state. Accepts either the new document state or an updater function `(currentValue) => nextValue`.
|
|
26
|
-
* Returns a promise resolving to the {@link ActionsResult}.
|
|
27
|
-
*/
|
|
28
|
-
export function useEditDocument<
|
|
29
|
-
TDocumentType extends string = string,
|
|
30
|
-
TDataset extends string = string,
|
|
31
|
-
TProjectId extends string = string,
|
|
32
|
-
>(
|
|
33
|
-
options: DocumentOptions<undefined, TDocumentType, TDataset, TProjectId>,
|
|
34
|
-
): (
|
|
35
|
-
nextValue: Updater<SanityDocument<TDocumentType, `${TProjectId}.${TDataset}`>>,
|
|
36
|
-
) => Promise<ActionsResult<SanityDocument<TDocumentType, `${TProjectId}.${TDataset}`>>>
|
|
37
|
-
|
|
38
|
-
// Overload 2: Path provided, relies on Typegen
|
|
39
|
-
/**
|
|
40
|
-
* @public
|
|
41
|
-
* Edit a specific path within a document, relying on Typegen for the type.
|
|
42
|
-
*
|
|
43
|
-
* @param options - Document options including `documentId`, `documentType`, `path`, and optionally `projectId`/`dataset`.
|
|
44
|
-
* @returns A stable function to update the value at the specified path. Accepts either the new value or an updater function `(currentValue) => nextValue`.
|
|
45
|
-
* Returns a promise resolving to the {@link ActionsResult}.
|
|
46
|
-
*/
|
|
47
|
-
export function useEditDocument<
|
|
48
|
-
TPath extends string = string,
|
|
49
|
-
TDocumentType extends string = string,
|
|
50
|
-
TDataset extends string = string,
|
|
51
|
-
TProjectId extends string = string,
|
|
52
|
-
>(
|
|
53
|
-
options: DocumentOptions<TPath, TDocumentType, TDataset, TProjectId>,
|
|
54
|
-
): (
|
|
55
|
-
nextValue: Updater<JsonMatch<SanityDocument<TDocumentType, `${TProjectId}.${TDataset}`>, TPath>>,
|
|
56
|
-
) => Promise<ActionsResult<SanityDocument<TDocumentType, `${TProjectId}.${TDataset}`>>>
|
|
13
|
+
/** React-layer edit document options: DocumentHandle with optional path */
|
|
14
|
+
type EditDocumentOptions<TPath extends string | undefined = undefined> = DocumentHandle & {
|
|
15
|
+
path?: TPath
|
|
16
|
+
}
|
|
57
17
|
|
|
58
|
-
// Overload
|
|
18
|
+
// Overload 1: Explicit type, no path
|
|
59
19
|
/**
|
|
60
20
|
* @public
|
|
61
21
|
* Edit an entire document with an explicit type `TData`.
|
|
62
22
|
*
|
|
63
|
-
* @param options - Document options including `documentId` and optionally `
|
|
23
|
+
* @param options - Document options including `documentId` and optionally `resource` or `resourceName`.
|
|
64
24
|
* @returns A stable function to update the document state. Accepts either the new document state (`TData`) or an updater function `(currentValue: TData) => nextValue: TData`.
|
|
65
25
|
* Returns a promise resolving to the {@link ActionsResult}.
|
|
66
26
|
*/
|
|
67
27
|
export function useEditDocument<TData>(
|
|
68
|
-
options:
|
|
28
|
+
options: EditDocumentOptions<undefined>,
|
|
69
29
|
): (nextValue: Updater<TData>) => Promise<ActionsResult>
|
|
70
30
|
|
|
71
|
-
// Overload
|
|
31
|
+
// Overload 2: Explicit type, path provided
|
|
72
32
|
/**
|
|
73
33
|
* @public
|
|
74
34
|
* Edit a specific path within a document with an explicit type `TData`.
|
|
75
35
|
*
|
|
76
|
-
* @param options - Document options including `documentId`, `path`, and optionally `
|
|
36
|
+
* @param options - Document options including `documentId`, `path`, and optionally `resource` or `resourceName`.
|
|
77
37
|
* @returns A stable function to update the value at the specified path. Accepts either the new value (`TData`) or an updater function `(currentValue: TData) => nextValue: TData`.
|
|
78
38
|
* Returns a promise resolving to the {@link ActionsResult}.
|
|
79
39
|
*/
|
|
80
40
|
export function useEditDocument<TData>(
|
|
81
|
-
options:
|
|
41
|
+
options: EditDocumentOptions<string>,
|
|
82
42
|
): (nextValue: Updater<TData>) => Promise<ActionsResult>
|
|
83
43
|
|
|
84
44
|
/**
|
|
@@ -94,11 +54,9 @@ export function useEditDocument<TData>(
|
|
|
94
54
|
* - Integrating with the active {@link SanityInstance} context.
|
|
95
55
|
* - Utilizing `useApplyDocumentActions` internally for optimistic updates and transaction handling.
|
|
96
56
|
*
|
|
97
|
-
* It offers
|
|
98
|
-
* 1. **
|
|
99
|
-
* 2. **
|
|
100
|
-
* 3. **Explicit Type (Full Document):** Edit the entire document with a manually specified type.
|
|
101
|
-
* 4. **Explicit Type (Specific Path):** Edit a specific field with a manually specified type.
|
|
57
|
+
* It offers overloads for flexibility:
|
|
58
|
+
* 1. **Explicit Type (Full Document):** Edit the entire document with a manually specified type.
|
|
59
|
+
* 2. **Explicit Type (Specific Path):** Edit a specific field with a manually specified type.
|
|
102
60
|
*
|
|
103
61
|
* **LiveEdit Documents:**
|
|
104
62
|
* For documents using {@link DocumentHandle.liveEdit | liveEdit mode} (set via `liveEdit: true` in the document handle), edits are applied directly to the published document without creating a draft.
|
|
@@ -106,87 +64,7 @@ export function useEditDocument<TData>(
|
|
|
106
64
|
* This hook relies on the document state being loaded. If the document is not yet available
|
|
107
65
|
* (e.g., during initial load), the component using this hook will suspend.
|
|
108
66
|
*
|
|
109
|
-
* @example Basic Usage (
|
|
110
|
-
* ```tsx
|
|
111
|
-
* import {useCallback} from 'react';
|
|
112
|
-
* import {useEditDocument, useDocument, type DocumentHandle} from '@sanity/sdk-react'
|
|
113
|
-
*
|
|
114
|
-
* // Assume 'product' schema has a 'title' field (string)
|
|
115
|
-
* interface ProductEditorProps {
|
|
116
|
-
* productHandle: DocumentHandle<'product'> // Typegen infers 'product' type
|
|
117
|
-
* }
|
|
118
|
-
*
|
|
119
|
-
* function ProductEditor({ productHandle }: ProductEditorProps) {
|
|
120
|
-
* // Fetch the document to display its current state (optional)
|
|
121
|
-
* const {data: product} = useDocument(productHandle);
|
|
122
|
-
* // Get the edit function for the full document
|
|
123
|
-
* const editProduct = useEditDocument(productHandle);
|
|
124
|
-
*
|
|
125
|
-
* // Use useCallback for stable event handlers
|
|
126
|
-
* const handleTitleChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
|
|
127
|
-
* const newTitle = event.target.value;
|
|
128
|
-
* // Use the functional updater for safe partial updates
|
|
129
|
-
* editProduct(prev => ({
|
|
130
|
-
* ...prev,
|
|
131
|
-
* title: newTitle,
|
|
132
|
-
* })).
|
|
133
|
-
* }, [editProduct]);
|
|
134
|
-
*
|
|
135
|
-
* return (
|
|
136
|
-
* <div>
|
|
137
|
-
* <label>
|
|
138
|
-
* Product Title:
|
|
139
|
-
* <input
|
|
140
|
-
* type="text"
|
|
141
|
-
* value={product?.title ?? ''}
|
|
142
|
-
* onChange={handleTitleChange}
|
|
143
|
-
* />
|
|
144
|
-
* </label>
|
|
145
|
-
* </div>
|
|
146
|
-
* );
|
|
147
|
-
* }
|
|
148
|
-
* ```
|
|
149
|
-
*
|
|
150
|
-
* @example Editing a Specific Path (Typegen)
|
|
151
|
-
* ```tsx
|
|
152
|
-
* import React, { useCallback } from 'react';
|
|
153
|
-
* import {useEditDocument, useDocument, type DocumentHandle, type DocumentOptions} from '@sanity/sdk-react'
|
|
154
|
-
*
|
|
155
|
-
* // Assume 'product' schema has a 'price' field (number)
|
|
156
|
-
* interface ProductPriceEditorProps {
|
|
157
|
-
* productHandle: DocumentHandle<'product'>;
|
|
158
|
-
* }
|
|
159
|
-
*
|
|
160
|
-
* function ProductPriceEditor({ productHandle }: ProductPriceEditorProps) {
|
|
161
|
-
* // Construct DocumentOptions internally, combining the handle and a hardcoded path
|
|
162
|
-
* const priceOptions {
|
|
163
|
-
* ...productHandle,
|
|
164
|
-
* path: 'price', // Hardcode the path to edit
|
|
165
|
-
* };
|
|
166
|
-
*
|
|
167
|
-
* // Fetch the current price to display it
|
|
168
|
-
* const {data: currentPrice} = useDocument(priceOptions);
|
|
169
|
-
* // Get the edit function for the specific path 'price'
|
|
170
|
-
* const editPrice = useEditDocument(priceOptions);
|
|
171
|
-
*
|
|
172
|
-
* const handleSetFixedPrice = useCallback(() => {
|
|
173
|
-
* // Update the price directly to a hardcoded value
|
|
174
|
-
* editPrice(99.99)
|
|
175
|
-
* }, [editPrice]);
|
|
176
|
-
*
|
|
177
|
-
* return (
|
|
178
|
-
* <div>
|
|
179
|
-
* <p>Current Price: {currentPrice}</p>
|
|
180
|
-
* <button onClick={handleSetFixedPrice}>
|
|
181
|
-
* Set Price to $99.99
|
|
182
|
-
* </button>
|
|
183
|
-
* </div>
|
|
184
|
-
* );
|
|
185
|
-
* }
|
|
186
|
-
*
|
|
187
|
-
* ```
|
|
188
|
-
*
|
|
189
|
-
* @example Usage with Explicit Types (Full Document)
|
|
67
|
+
* @example Basic Usage with Explicit Types (Full Document)
|
|
190
68
|
* ```tsx
|
|
191
69
|
* import React, { useCallback } from 'react';
|
|
192
70
|
* import {useEditDocument, useDocument, type DocumentHandle, type SanityDocument} from '@sanity/sdk-react'
|
|
@@ -262,20 +140,22 @@ export function useEditDocument<TData>(
|
|
|
262
140
|
export function useEditDocument({
|
|
263
141
|
path,
|
|
264
142
|
...doc
|
|
265
|
-
}:
|
|
266
|
-
const instance = useSanityInstance(
|
|
143
|
+
}: EditDocumentOptions<string | undefined>): (updater: Updater<unknown>) => Promise<ActionsResult> {
|
|
144
|
+
const instance = useSanityInstance()
|
|
145
|
+
const normalizedDoc = useNormalizedResourceOptions(doc)
|
|
146
|
+
|
|
267
147
|
const apply = useApplyDocumentActions()
|
|
268
148
|
const isDocumentReady = useCallback(
|
|
269
|
-
() => getDocumentState(instance,
|
|
270
|
-
[instance,
|
|
149
|
+
() => getDocumentState(instance, normalizedDoc).getCurrent() !== undefined,
|
|
150
|
+
[instance, normalizedDoc],
|
|
271
151
|
)
|
|
272
|
-
if (!isDocumentReady()) throw resolveDocument(instance,
|
|
152
|
+
if (!isDocumentReady()) throw resolveDocument(instance, normalizedDoc)
|
|
273
153
|
|
|
274
154
|
return (updater: Updater<unknown>) => {
|
|
275
155
|
const currentPath = path
|
|
276
156
|
|
|
277
157
|
if (currentPath) {
|
|
278
|
-
const stateWithOptions = getDocumentState(instance, {...
|
|
158
|
+
const stateWithOptions = getDocumentState(instance, {...normalizedDoc, path})
|
|
279
159
|
const currentValue = stateWithOptions.getCurrent()
|
|
280
160
|
|
|
281
161
|
const nextValue =
|
|
@@ -283,10 +163,10 @@ export function useEditDocument({
|
|
|
283
163
|
? (updater as (prev: typeof currentValue) => typeof currentValue)(currentValue)
|
|
284
164
|
: updater
|
|
285
165
|
|
|
286
|
-
return apply(editDocument(
|
|
166
|
+
return apply(editDocument(normalizedDoc, {set: {[currentPath]: nextValue}}))
|
|
287
167
|
}
|
|
288
168
|
|
|
289
|
-
const fullDocState = getDocumentState(instance, {...
|
|
169
|
+
const fullDocState = getDocumentState(instance, {...normalizedDoc, path})
|
|
290
170
|
const current = fullDocState.getCurrent() as object | null | undefined
|
|
291
171
|
const nextValue =
|
|
292
172
|
typeof updater === 'function'
|
|
@@ -308,8 +188,8 @@ export function useEditDocument({
|
|
|
308
188
|
)
|
|
309
189
|
.map((key) =>
|
|
310
190
|
key in nextValue
|
|
311
|
-
? editDocument(
|
|
312
|
-
: editDocument(
|
|
191
|
+
? editDocument(normalizedDoc, {set: {[key]: (nextValue as Record<string, unknown>)[key]}})
|
|
192
|
+
: editDocument(normalizedDoc, {unset: [key]}),
|
|
313
193
|
)
|
|
314
194
|
|
|
315
195
|
return apply(editActions)
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {type DatasetResource} from '@sanity/sdk'
|
|
2
2
|
import {evaluateSync, parse, toJS} from 'groq-js'
|
|
3
3
|
import {describe, vi} from 'vitest'
|
|
4
4
|
|
|
5
|
+
import {act, renderHook} from '../../../test/test-utils'
|
|
5
6
|
import {ResourceProvider} from '../../context/ResourceProvider'
|
|
7
|
+
import {ResourcesContext} from '../../context/ResourcesContext'
|
|
6
8
|
import {useQuery} from '../query/useQuery'
|
|
7
9
|
import {useDocuments} from './useDocuments'
|
|
8
10
|
|
|
@@ -77,25 +79,13 @@ describe('useDocuments', () => {
|
|
|
77
79
|
|
|
78
80
|
it('should respect custom page size', () => {
|
|
79
81
|
const customBatchSize = 2
|
|
80
|
-
const {result} = renderHook(() => useDocuments({batchSize: customBatchSize})
|
|
81
|
-
wrapper: ({children}) => (
|
|
82
|
-
<ResourceProvider projectId="test-project" dataset="test-dataset" fallback={null}>
|
|
83
|
-
{children}
|
|
84
|
-
</ResourceProvider>
|
|
85
|
-
),
|
|
86
|
-
})
|
|
82
|
+
const {result} = renderHook(() => useDocuments({batchSize: customBatchSize}))
|
|
87
83
|
|
|
88
84
|
expect(result.current.data.length).toBe(customBatchSize)
|
|
89
85
|
})
|
|
90
86
|
|
|
91
87
|
it('should filter by document type', () => {
|
|
92
|
-
const {result} = renderHook(() => useDocuments({filter: '_type == "movie"'})
|
|
93
|
-
wrapper: ({children}) => (
|
|
94
|
-
<ResourceProvider projectId="test-project" dataset="test-dataset" fallback={null}>
|
|
95
|
-
{children}
|
|
96
|
-
</ResourceProvider>
|
|
97
|
-
),
|
|
98
|
-
})
|
|
88
|
+
const {result} = renderHook(() => useDocuments({filter: '_type == "movie"'}))
|
|
99
89
|
|
|
100
90
|
expect(result.current.data.every((doc) => doc.documentType === 'movie')).toBe(true)
|
|
101
91
|
expect(result.current.count).toBe(5) // 5 movies in the dataset
|
|
@@ -103,32 +93,18 @@ describe('useDocuments', () => {
|
|
|
103
93
|
|
|
104
94
|
// groq-js doesn't support search filters yet
|
|
105
95
|
it.skip('should apply search filter', () => {
|
|
106
|
-
const {result} = renderHook(() => useDocuments({search: 'inter'})
|
|
107
|
-
wrapper: ({children}) => (
|
|
108
|
-
<ResourceProvider projectId="test-project" dataset="test-dataset" fallback={null}>
|
|
109
|
-
{children}
|
|
110
|
-
</ResourceProvider>
|
|
111
|
-
),
|
|
112
|
-
})
|
|
96
|
+
const {result} = renderHook(() => useDocuments({search: 'inter'}))
|
|
113
97
|
|
|
114
98
|
// Should match "Interstellar"
|
|
115
99
|
expect(result.current.data.some((doc) => doc.documentId === 'movie3')).toBe(true)
|
|
116
100
|
})
|
|
117
101
|
|
|
118
102
|
it('should apply ordering', () => {
|
|
119
|
-
const {result} = renderHook(
|
|
120
|
-
(
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}),
|
|
125
|
-
{
|
|
126
|
-
wrapper: ({children}) => (
|
|
127
|
-
<ResourceProvider projectId="test-project" dataset="test-dataset" fallback={null}>
|
|
128
|
-
{children}
|
|
129
|
-
</ResourceProvider>
|
|
130
|
-
),
|
|
131
|
-
},
|
|
103
|
+
const {result} = renderHook(() =>
|
|
104
|
+
useDocuments({
|
|
105
|
+
filter: '_type == "movie"',
|
|
106
|
+
orderings: [{field: 'releaseYear', direction: 'desc'}],
|
|
107
|
+
}),
|
|
132
108
|
)
|
|
133
109
|
|
|
134
110
|
// First item should be the most recent movie (Interstellar, 2014)
|
|
@@ -137,13 +113,7 @@ describe('useDocuments', () => {
|
|
|
137
113
|
|
|
138
114
|
it('should load more data when loadMore is called', () => {
|
|
139
115
|
const batchSize = 2
|
|
140
|
-
const {result} = renderHook(() => useDocuments({batchSize: batchSize})
|
|
141
|
-
wrapper: ({children}) => (
|
|
142
|
-
<ResourceProvider projectId="test-project" dataset="test-dataset" fallback={null}>
|
|
143
|
-
{children}
|
|
144
|
-
</ResourceProvider>
|
|
145
|
-
),
|
|
146
|
-
})
|
|
116
|
+
const {result} = renderHook(() => useDocuments({batchSize: batchSize}))
|
|
147
117
|
|
|
148
118
|
expect(result.current.data.length).toBe(batchSize)
|
|
149
119
|
|
|
@@ -155,13 +125,7 @@ describe('useDocuments', () => {
|
|
|
155
125
|
})
|
|
156
126
|
|
|
157
127
|
it('should indicate when there is more data to load', () => {
|
|
158
|
-
const {result} = renderHook(() => useDocuments({batchSize: 3})
|
|
159
|
-
wrapper: ({children}) => (
|
|
160
|
-
<ResourceProvider projectId="test-project" dataset="test-dataset" fallback={null}>
|
|
161
|
-
{children}
|
|
162
|
-
</ResourceProvider>
|
|
163
|
-
),
|
|
164
|
-
})
|
|
128
|
+
const {result} = renderHook(() => useDocuments({batchSize: 3}))
|
|
165
129
|
expect(result.current.hasMore).toBe(true)
|
|
166
130
|
// Load all remaining data
|
|
167
131
|
act(() => {
|
|
@@ -174,11 +138,6 @@ describe('useDocuments', () => {
|
|
|
174
138
|
it('should reset limit when filter changes', () => {
|
|
175
139
|
const {result, rerender} = renderHook((props) => useDocuments(props), {
|
|
176
140
|
initialProps: {batchSize: 2, filter: ''},
|
|
177
|
-
wrapper: ({children}) => (
|
|
178
|
-
<ResourceProvider projectId="test-project" dataset="test-dataset" fallback={null}>
|
|
179
|
-
{children}
|
|
180
|
-
</ResourceProvider>
|
|
181
|
-
),
|
|
182
141
|
})
|
|
183
142
|
// Initially, data length equals pageSize (2)
|
|
184
143
|
expect(result.current.data.length).toBe(2)
|
|
@@ -194,23 +153,44 @@ describe('useDocuments', () => {
|
|
|
194
153
|
expect(result.current.data.length).toBe(2)
|
|
195
154
|
})
|
|
196
155
|
|
|
197
|
-
it('should add
|
|
198
|
-
const {result} = renderHook(() => useDocuments({})
|
|
156
|
+
it('should add resources to document handles', () => {
|
|
157
|
+
const {result} = renderHook(() => useDocuments({}))
|
|
158
|
+
|
|
159
|
+
// Check that the first document handle has the projectId and dataset
|
|
160
|
+
expect((result.current.data[0].resource as DatasetResource).projectId).toBe('test')
|
|
161
|
+
expect((result.current.data[0].resource as DatasetResource).dataset).toBe('test')
|
|
162
|
+
|
|
163
|
+
// Verify all document handles have these properties
|
|
164
|
+
expect(
|
|
165
|
+
result.current.data.every(
|
|
166
|
+
(doc) =>
|
|
167
|
+
(doc.resource as DatasetResource).projectId === 'test' &&
|
|
168
|
+
(doc.resource as DatasetResource).dataset === 'test',
|
|
169
|
+
),
|
|
170
|
+
).toBe(true)
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
it('should resolve resourceName to the named dataset resource', () => {
|
|
174
|
+
const resources = {
|
|
175
|
+
default: {projectId: 'test-project', dataset: 'test-dataset'},
|
|
176
|
+
secondary: {projectId: 'secondary-project', dataset: 'secondary-dataset'},
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const {result} = renderHook(() => useDocuments({resourceName: 'secondary'}), {
|
|
199
180
|
wrapper: ({children}) => (
|
|
200
|
-
<ResourceProvider
|
|
201
|
-
{children}
|
|
181
|
+
<ResourceProvider resource={resources.default} fallback={null}>
|
|
182
|
+
<ResourcesContext.Provider value={resources}>{children}</ResourcesContext.Provider>
|
|
202
183
|
</ResourceProvider>
|
|
203
184
|
),
|
|
204
185
|
})
|
|
205
186
|
|
|
206
|
-
|
|
207
|
-
expect(result.current.data[0].
|
|
208
|
-
expect(result.current.data[0].dataset).toBe('test-dataset')
|
|
209
|
-
|
|
210
|
-
// Verify all document handles have these properties
|
|
187
|
+
expect((result.current.data[0].resource as DatasetResource).projectId).toBe('secondary-project')
|
|
188
|
+
expect((result.current.data[0].resource as DatasetResource).dataset).toBe('secondary-dataset')
|
|
211
189
|
expect(
|
|
212
190
|
result.current.data.every(
|
|
213
|
-
(doc) =>
|
|
191
|
+
(doc) =>
|
|
192
|
+
(doc.resource as DatasetResource).projectId === 'secondary-project' &&
|
|
193
|
+
(doc.resource as DatasetResource).dataset === 'secondary-dataset',
|
|
214
194
|
),
|
|
215
195
|
).toBe(true)
|
|
216
196
|
})
|
|
@@ -1,14 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createGroqSearchFilter,
|
|
3
|
-
type DatasetHandle,
|
|
4
|
-
type DocumentHandle,
|
|
5
|
-
type QueryOptions,
|
|
6
|
-
} from '@sanity/sdk'
|
|
1
|
+
import {createGroqSearchFilter, type QueryOptions} from '@sanity/sdk'
|
|
7
2
|
import {type SortOrderingItem} from '@sanity/types'
|
|
8
3
|
import {pick} from 'lodash-es'
|
|
9
4
|
import {useCallback, useEffect, useMemo, useState} from 'react'
|
|
10
5
|
|
|
11
|
-
import {
|
|
6
|
+
import {type DocumentHandle, type ResourceHandle} from '../../config/handles'
|
|
7
|
+
import {useNormalizedResourceOptions} from '../helpers/useNormalizedResourceOptions'
|
|
12
8
|
import {useQuery} from '../query/useQuery'
|
|
13
9
|
|
|
14
10
|
const DEFAULT_BATCH_SIZE = 25
|
|
@@ -24,7 +20,9 @@ export interface DocumentsOptions<
|
|
|
24
20
|
TDataset extends string = string,
|
|
25
21
|
TProjectId extends string = string,
|
|
26
22
|
>
|
|
27
|
-
extends
|
|
23
|
+
extends
|
|
24
|
+
ResourceHandle<TProjectId, TDataset>,
|
|
25
|
+
Pick<QueryOptions<TDocumentType, TDataset, TProjectId>, 'params'> {
|
|
28
26
|
/**
|
|
29
27
|
* Filter documents by their `_type`. Can be a single type or an array of types.
|
|
30
28
|
*/
|
|
@@ -92,18 +90,16 @@ export interface DocumentsResponse<
|
|
|
92
90
|
* @returns An object containing the list of document handles, the loading state, the total count of retrieved document handles, and a function to load more
|
|
93
91
|
*
|
|
94
92
|
* @remarks
|
|
95
|
-
* - The returned document handles include
|
|
93
|
+
* - The returned document handles include resource information from the current Sanity instance
|
|
96
94
|
* - This makes them ready to use with document operations and other document hooks
|
|
97
|
-
* - The hook automatically uses the correct Sanity instance based on the
|
|
95
|
+
* - The hook automatically uses the correct Sanity instance based on the resource in the options
|
|
98
96
|
*
|
|
99
97
|
* @example Basic infinite list with loading more
|
|
100
98
|
* ```tsx
|
|
101
99
|
* import {
|
|
102
100
|
* useDocuments,
|
|
103
|
-
* createDatasetHandle,
|
|
104
|
-
* type DatasetHandle,
|
|
105
101
|
* type DocumentHandle,
|
|
106
|
-
* type
|
|
102
|
+
* type DocumentResource,
|
|
107
103
|
* } from '@sanity/sdk-react'
|
|
108
104
|
* import {Suspense} from 'react'
|
|
109
105
|
*
|
|
@@ -119,14 +115,14 @@ export interface DocumentsResponse<
|
|
|
119
115
|
*
|
|
120
116
|
* // Define props for the list component
|
|
121
117
|
* interface DocumentListProps {
|
|
122
|
-
*
|
|
118
|
+
* resource: DocumentResource
|
|
123
119
|
* documentType: string
|
|
124
120
|
* search?: string
|
|
125
121
|
* }
|
|
126
122
|
*
|
|
127
|
-
* function DocumentList({
|
|
123
|
+
* function DocumentList({resource, documentType, search}: DocumentListProps) {
|
|
128
124
|
* const { data, hasMore, isPending, loadMore, count } = useDocuments({
|
|
129
|
-
*
|
|
125
|
+
* resource,
|
|
130
126
|
* documentType,
|
|
131
127
|
* search,
|
|
132
128
|
* batchSize: 10,
|
|
@@ -140,7 +136,7 @@ export interface DocumentsResponse<
|
|
|
140
136
|
* {data.map((docHandle) => (
|
|
141
137
|
* <li key={docHandle.documentId}>
|
|
142
138
|
* <Suspense fallback="Loading…">
|
|
143
|
-
* <MyDocumentComponent
|
|
139
|
+
* <MyDocumentComponent doc={docHandle} />
|
|
144
140
|
* </Suspense>
|
|
145
141
|
* </li>
|
|
146
142
|
* ))}
|
|
@@ -155,8 +151,7 @@ export interface DocumentsResponse<
|
|
|
155
151
|
* }
|
|
156
152
|
*
|
|
157
153
|
* // Usage:
|
|
158
|
-
* //
|
|
159
|
-
* // <DocumentList dataset={myDatasetHandle} documentType="post" search="Sanity" />
|
|
154
|
+
* // <DocumentList resource={{projectId: 'p1', dataset: 'production'}} documentType="post" search="Sanity" />
|
|
160
155
|
* ```
|
|
161
156
|
*
|
|
162
157
|
* @example Using `filter` and `params` options for narrowing a collection
|
|
@@ -201,13 +196,14 @@ export function useDocuments<
|
|
|
201
196
|
filter,
|
|
202
197
|
orderings,
|
|
203
198
|
documentType,
|
|
204
|
-
...
|
|
199
|
+
...rawOptions
|
|
205
200
|
}: DocumentsOptions<TDocumentType, TDataset, TProjectId>): DocumentsResponse<
|
|
206
201
|
TDocumentType,
|
|
207
202
|
TDataset,
|
|
208
203
|
TProjectId
|
|
209
204
|
> {
|
|
210
|
-
const
|
|
205
|
+
const options =
|
|
206
|
+
useNormalizedResourceOptions<DocumentsOptions<TDocumentType, TDataset, TProjectId>>(rawOptions)
|
|
211
207
|
const [limit, setLimit] = useState(batchSize)
|
|
212
208
|
const documentTypes = useMemo(
|
|
213
209
|
() =>
|
|
@@ -279,10 +275,8 @@ export function useDocuments<
|
|
|
279
275
|
query: `{"count":${countQuery},"data":${dataQuery}}`,
|
|
280
276
|
params: {
|
|
281
277
|
...params,
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
...pick(options, 'projectId', 'dataset', 'perspective'),
|
|
285
|
-
},
|
|
278
|
+
// these are passed back to the user as part of each document handle
|
|
279
|
+
__handle: pick(options, ['resource', 'perspective']),
|
|
286
280
|
__types: documentTypes,
|
|
287
281
|
},
|
|
288
282
|
})
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {type SanityInstance} from '@sanity/sdk'
|
|
2
|
-
import {renderHook} from '@testing-library/react'
|
|
3
2
|
import {describe, expect, it, vi} from 'vitest'
|
|
4
3
|
|
|
4
|
+
import {renderHook} from '../../../test/test-utils'
|
|
5
5
|
import {ResourceProvider} from '../../context/ResourceProvider'
|
|
6
6
|
import {createCallbackHook} from './createCallbackHook'
|
|
7
7
|
|
|
@@ -21,13 +21,7 @@ describe('createCallbackHook', () => {
|
|
|
21
21
|
const useTestHook = createCallbackHook(testCallback)
|
|
22
22
|
|
|
23
23
|
// Render the hook
|
|
24
|
-
const {result, rerender} = renderHook(() => useTestHook()
|
|
25
|
-
wrapper: ({children}) => (
|
|
26
|
-
<ResourceProvider projectId="p" dataset="d" fallback={null}>
|
|
27
|
-
{children}
|
|
28
|
-
</ResourceProvider>
|
|
29
|
-
),
|
|
30
|
-
})
|
|
24
|
+
const {result, rerender} = renderHook(() => useTestHook())
|
|
31
25
|
|
|
32
26
|
// Test the callback with parameters
|
|
33
27
|
const result1 = result.current('test', 123)
|
|
@@ -44,13 +38,17 @@ describe('createCallbackHook', () => {
|
|
|
44
38
|
|
|
45
39
|
it('should create new callback when instance changes', () => {
|
|
46
40
|
// Create a test callback
|
|
47
|
-
const testCallback = (instance: SanityInstance) => instance.config.projectId
|
|
41
|
+
const testCallback = (instance: SanityInstance) => instance.config.studio?.projectId
|
|
48
42
|
|
|
49
43
|
// Create and render our hook with first provider
|
|
50
44
|
const useTestHook = createCallbackHook(testCallback)
|
|
51
45
|
const {result, unmount} = renderHook(() => useTestHook(), {
|
|
52
46
|
wrapper: ({children}) => (
|
|
53
|
-
<ResourceProvider
|
|
47
|
+
<ResourceProvider
|
|
48
|
+
studio={{projectId: 'p1'}}
|
|
49
|
+
resource={{projectId: 'p1', dataset: 'd'}}
|
|
50
|
+
fallback={null}
|
|
51
|
+
>
|
|
54
52
|
{children}
|
|
55
53
|
</ResourceProvider>
|
|
56
54
|
),
|
|
@@ -66,7 +64,11 @@ describe('createCallbackHook', () => {
|
|
|
66
64
|
// Re-render with different provider configuration
|
|
67
65
|
const {result: result2} = renderHook(() => useTestHook(), {
|
|
68
66
|
wrapper: ({children}) => (
|
|
69
|
-
<ResourceProvider
|
|
67
|
+
<ResourceProvider
|
|
68
|
+
studio={{projectId: 'p2'}}
|
|
69
|
+
resource={{projectId: 'p2', dataset: 'd'}}
|
|
70
|
+
fallback={null}
|
|
71
|
+
>
|
|
70
72
|
{children}
|
|
71
73
|
</ResourceProvider>
|
|
72
74
|
),
|
|
@@ -85,7 +87,7 @@ describe('createCallbackHook', () => {
|
|
|
85
87
|
method: string,
|
|
86
88
|
data: object,
|
|
87
89
|
) => ({
|
|
88
|
-
url: `${instance.config.projectId}${path}`,
|
|
90
|
+
url: `${instance.config.studio?.projectId}${path}`,
|
|
89
91
|
method,
|
|
90
92
|
data,
|
|
91
93
|
})
|
|
@@ -93,7 +95,11 @@ describe('createCallbackHook', () => {
|
|
|
93
95
|
const useTestHook = createCallbackHook(testCallback)
|
|
94
96
|
const {result} = renderHook(() => useTestHook(), {
|
|
95
97
|
wrapper: ({children}) => (
|
|
96
|
-
<ResourceProvider
|
|
98
|
+
<ResourceProvider
|
|
99
|
+
studio={{projectId: 'p'}}
|
|
100
|
+
resource={{projectId: 'p', dataset: 'd'}}
|
|
101
|
+
fallback={null}
|
|
102
|
+
>
|
|
97
103
|
{children}
|
|
98
104
|
</ResourceProvider>
|
|
99
105
|
),
|