@sanity/sdk-react 0.0.0-alpha.3 → 0.0.0-alpha.31
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 +6 -100
- package/dist/index.d.ts +2390 -2
- package/dist/index.js +1119 -2
- package/dist/index.js.map +1 -1
- package/package.json +35 -49
- package/src/_exports/index.ts +2 -10
- package/src/_exports/sdk-react.ts +73 -0
- package/src/components/SDKProvider.test.tsx +103 -0
- package/src/components/SDKProvider.tsx +52 -0
- package/src/components/SanityApp.test.tsx +244 -0
- package/src/components/SanityApp.tsx +106 -0
- package/src/components/auth/AuthBoundary.test.tsx +204 -29
- package/src/components/auth/AuthBoundary.tsx +96 -19
- package/src/components/auth/ConfigurationError.ts +22 -0
- package/src/components/auth/LoginCallback.test.tsx +22 -24
- package/src/components/auth/LoginCallback.tsx +6 -16
- package/src/components/auth/LoginError.test.tsx +11 -18
- package/src/components/auth/LoginError.tsx +43 -25
- package/src/components/utils.ts +22 -0
- package/src/context/ResourceProvider.test.tsx +157 -0
- package/src/context/ResourceProvider.tsx +111 -0
- package/src/context/SanityInstanceContext.ts +4 -0
- package/src/hooks/_synchronous-groq-js.mjs +4 -0
- package/src/hooks/auth/useAuthState.tsx +4 -5
- package/src/hooks/auth/useAuthToken.tsx +1 -1
- package/src/hooks/auth/useCurrentUser.tsx +28 -4
- package/src/hooks/auth/useDashboardOrganizationId.test.tsx +42 -0
- package/src/hooks/auth/useDashboardOrganizationId.tsx +30 -0
- package/src/hooks/auth/useHandleAuthCallback.test.tsx +16 -0
- package/src/hooks/auth/{useHandleCallback.tsx → useHandleAuthCallback.tsx} +7 -6
- package/src/hooks/auth/useLogOut.test.tsx +2 -2
- package/src/hooks/auth/useLogOut.tsx +1 -1
- package/src/hooks/auth/useLoginUrl.tsx +14 -0
- package/src/hooks/auth/useVerifyOrgProjects.test.tsx +136 -0
- package/src/hooks/auth/useVerifyOrgProjects.tsx +48 -0
- package/src/hooks/client/useClient.ts +13 -33
- package/src/hooks/comlink/useFrameConnection.test.tsx +167 -0
- package/src/hooks/comlink/useFrameConnection.ts +107 -0
- package/src/hooks/comlink/useManageFavorite.test.ts +368 -0
- package/src/hooks/comlink/useManageFavorite.ts +210 -0
- package/src/hooks/comlink/useRecordDocumentHistoryEvent.test.ts +85 -0
- package/src/hooks/comlink/useRecordDocumentHistoryEvent.ts +115 -0
- package/src/hooks/comlink/useWindowConnection.test.ts +135 -0
- package/src/hooks/comlink/useWindowConnection.ts +123 -0
- package/src/hooks/context/useSanityInstance.test.tsx +157 -15
- package/src/hooks/context/useSanityInstance.ts +68 -11
- package/src/hooks/dashboard/useNavigateToStudioDocument.test.ts +276 -0
- package/src/hooks/dashboard/useNavigateToStudioDocument.ts +139 -0
- package/src/hooks/dashboard/useStudioWorkspacesByProjectIdDataset.test.tsx +291 -0
- package/src/hooks/dashboard/useStudioWorkspacesByProjectIdDataset.ts +101 -0
- package/src/hooks/datasets/useDatasets.test.ts +80 -0
- package/src/hooks/datasets/useDatasets.ts +52 -0
- package/src/hooks/document/useApplyDocumentActions.test.ts +20 -0
- package/src/hooks/document/useApplyDocumentActions.ts +124 -0
- package/src/hooks/document/useDocument.test.ts +118 -0
- package/src/hooks/document/useDocument.ts +212 -0
- package/src/hooks/document/useDocumentEvent.test.ts +62 -0
- package/src/hooks/document/useDocumentEvent.ts +94 -0
- package/src/hooks/document/useDocumentPermissions.test.ts +204 -0
- package/src/hooks/document/useDocumentPermissions.ts +131 -0
- package/src/hooks/document/useDocumentSyncStatus.test.ts +23 -0
- package/src/hooks/document/useDocumentSyncStatus.ts +61 -0
- package/src/hooks/document/useEditDocument.test.ts +196 -0
- package/src/hooks/document/useEditDocument.ts +314 -0
- package/src/hooks/documents/useDocuments.test.tsx +179 -0
- package/src/hooks/documents/useDocuments.ts +300 -0
- package/src/hooks/helpers/createCallbackHook.test.tsx +2 -2
- package/src/hooks/helpers/createCallbackHook.tsx +1 -1
- package/src/hooks/helpers/createStateSourceHook.test.tsx +67 -1
- package/src/hooks/helpers/createStateSourceHook.tsx +27 -11
- package/src/hooks/paginatedDocuments/usePaginatedDocuments.test.tsx +284 -0
- package/src/hooks/paginatedDocuments/usePaginatedDocuments.ts +353 -0
- package/src/hooks/preview/usePreview.test.tsx +85 -17
- package/src/hooks/preview/usePreview.tsx +81 -22
- package/src/hooks/projection/useProjection.test.tsx +283 -0
- package/src/hooks/projection/useProjection.ts +232 -0
- package/src/hooks/projects/useProject.test.ts +80 -0
- package/src/hooks/projects/useProject.ts +51 -0
- package/src/hooks/projects/useProjects.test.ts +77 -0
- package/src/hooks/projects/useProjects.ts +45 -0
- package/src/hooks/query/useQuery.test.tsx +188 -0
- package/src/hooks/query/useQuery.ts +193 -0
- package/src/hooks/releases/useActiveReleases.test.tsx +84 -0
- package/src/hooks/releases/useActiveReleases.ts +39 -0
- package/src/hooks/releases/usePerspective.test.tsx +120 -0
- package/src/hooks/releases/usePerspective.ts +49 -0
- package/src/hooks/users/useUsers.test.tsx +330 -0
- package/src/hooks/users/useUsers.ts +120 -0
- package/src/utils/getEnv.ts +21 -0
- package/src/version.ts +8 -0
- package/src/vite-env.d.ts +10 -0
- package/dist/_chunks-es/useLogOut.js +0 -44
- package/dist/_chunks-es/useLogOut.js.map +0 -1
- package/dist/assets/bundle-CcAyERuZ.css +0 -11
- package/dist/components.d.ts +0 -259
- package/dist/components.js +0 -301
- package/dist/components.js.map +0 -1
- package/dist/hooks.d.ts +0 -186
- package/dist/hooks.js +0 -81
- package/dist/hooks.js.map +0 -1
- package/src/_exports/components.ts +0 -13
- package/src/_exports/hooks.ts +0 -9
- package/src/components/DocumentGridLayout/DocumentGridLayout.stories.tsx +0 -113
- package/src/components/DocumentGridLayout/DocumentGridLayout.test.tsx +0 -42
- package/src/components/DocumentGridLayout/DocumentGridLayout.tsx +0 -21
- package/src/components/DocumentListLayout/DocumentListLayout.stories.tsx +0 -105
- package/src/components/DocumentListLayout/DocumentListLayout.test.tsx +0 -42
- package/src/components/DocumentListLayout/DocumentListLayout.tsx +0 -12
- package/src/components/DocumentPreviewLayout/DocumentPreviewLayout.md +0 -49
- package/src/components/DocumentPreviewLayout/DocumentPreviewLayout.stories.tsx +0 -39
- package/src/components/DocumentPreviewLayout/DocumentPreviewLayout.test.tsx +0 -30
- package/src/components/DocumentPreviewLayout/DocumentPreviewLayout.tsx +0 -171
- package/src/components/Login/LoginLinks.test.tsx +0 -100
- package/src/components/Login/LoginLinks.tsx +0 -73
- package/src/components/auth/Login.test.tsx +0 -41
- package/src/components/auth/Login.tsx +0 -45
- package/src/components/auth/LoginFooter.test.tsx +0 -29
- package/src/components/auth/LoginFooter.tsx +0 -65
- package/src/components/auth/LoginLayout.test.tsx +0 -33
- package/src/components/auth/LoginLayout.tsx +0 -81
- package/src/components/context/SanityProvider.test.tsx +0 -25
- package/src/components/context/SanityProvider.tsx +0 -42
- package/src/css/css.config.js +0 -220
- package/src/css/paramour.css +0 -2347
- package/src/css/styles.css +0 -11
- package/src/hooks/auth/useHandleCallback.test.tsx +0 -16
- package/src/hooks/auth/useLoginUrls.test.tsx +0 -68
- package/src/hooks/auth/useLoginUrls.tsx +0 -51
- package/src/hooks/client/useClient.test.tsx +0 -130
- package/src/hooks/documentCollection/useDocuments.test.ts +0 -130
- package/src/hooks/documentCollection/useDocuments.ts +0 -87
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createGroqSearchFilter,
|
|
3
|
+
type DatasetHandle,
|
|
4
|
+
type DocumentHandle,
|
|
5
|
+
type QueryOptions,
|
|
6
|
+
} from '@sanity/sdk'
|
|
7
|
+
import {type SortOrderingItem} from '@sanity/types'
|
|
8
|
+
import {pick} from 'lodash-es'
|
|
9
|
+
import {useCallback, useEffect, useMemo, useState} from 'react'
|
|
10
|
+
|
|
11
|
+
import {useSanityInstance} from '../context/useSanityInstance'
|
|
12
|
+
import {useQuery} from '../query/useQuery'
|
|
13
|
+
|
|
14
|
+
const DEFAULT_BATCH_SIZE = 25
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Configuration options for the useDocuments hook
|
|
18
|
+
*
|
|
19
|
+
* @beta
|
|
20
|
+
* @category Types
|
|
21
|
+
*/
|
|
22
|
+
export interface DocumentsOptions<
|
|
23
|
+
TDocumentType extends string = string,
|
|
24
|
+
TDataset extends string = string,
|
|
25
|
+
TProjectId extends string = string,
|
|
26
|
+
> extends DatasetHandle<TDataset, TProjectId>,
|
|
27
|
+
Pick<QueryOptions, 'perspective' | 'params'> {
|
|
28
|
+
/**
|
|
29
|
+
* Filter documents by their `_type`. Can be a single type or an array of types.
|
|
30
|
+
*/
|
|
31
|
+
documentType?: TDocumentType | TDocumentType[]
|
|
32
|
+
/**
|
|
33
|
+
* GROQ filter expression to apply to the query
|
|
34
|
+
*/
|
|
35
|
+
filter?: string
|
|
36
|
+
/**
|
|
37
|
+
* Number of items to load per batch (defaults to 25)
|
|
38
|
+
*/
|
|
39
|
+
batchSize?: number
|
|
40
|
+
/**
|
|
41
|
+
* Sorting configuration for the results
|
|
42
|
+
*/
|
|
43
|
+
orderings?: SortOrderingItem[]
|
|
44
|
+
/**
|
|
45
|
+
* Text search query to filter results
|
|
46
|
+
*/
|
|
47
|
+
search?: string
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Return value from the useDocuments hook
|
|
52
|
+
*
|
|
53
|
+
* @beta
|
|
54
|
+
* @category Types
|
|
55
|
+
*/
|
|
56
|
+
export interface DocumentsResponse<
|
|
57
|
+
TDocumentType extends string = string,
|
|
58
|
+
TDataset extends string = string,
|
|
59
|
+
TProjectId extends string = string,
|
|
60
|
+
> {
|
|
61
|
+
/**
|
|
62
|
+
* Array of document handles for the current batch
|
|
63
|
+
*/
|
|
64
|
+
data: DocumentHandle<TDocumentType, TDataset, TProjectId>[]
|
|
65
|
+
/**
|
|
66
|
+
* Whether there are more items available to load
|
|
67
|
+
*/
|
|
68
|
+
hasMore: boolean
|
|
69
|
+
/**
|
|
70
|
+
* Total count of items matching the query
|
|
71
|
+
*/
|
|
72
|
+
count: number
|
|
73
|
+
/**
|
|
74
|
+
* Whether a query is currently in progress
|
|
75
|
+
*/
|
|
76
|
+
isPending: boolean
|
|
77
|
+
/**
|
|
78
|
+
* Function to load the next batch of results
|
|
79
|
+
*/
|
|
80
|
+
loadMore: () => void
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Retrieves batches of {@link DocumentHandle}s, narrowed by optional filters, text searches, and custom ordering,
|
|
85
|
+
* with infinite scrolling support. The number of document handles returned per batch is customizable,
|
|
86
|
+
* and additional batches can be loaded using the supplied `loadMore` function.
|
|
87
|
+
*
|
|
88
|
+
* @beta
|
|
89
|
+
* @category Documents
|
|
90
|
+
* @param options - Configuration options for the infinite list
|
|
91
|
+
* @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
|
|
92
|
+
*
|
|
93
|
+
* @remarks
|
|
94
|
+
* - The returned document handles include projectId and dataset information from the current Sanity instance
|
|
95
|
+
* - This makes them ready to use with document operations and other document hooks
|
|
96
|
+
* - The hook automatically uses the correct Sanity instance based on the projectId and dataset in the options
|
|
97
|
+
*
|
|
98
|
+
* @example Basic infinite list with loading more
|
|
99
|
+
* ```tsx
|
|
100
|
+
* import {
|
|
101
|
+
* useDocuments,
|
|
102
|
+
* createDatasetHandle,
|
|
103
|
+
* type DatasetHandle,
|
|
104
|
+
* type DocumentHandle,
|
|
105
|
+
* type SortOrderingItem
|
|
106
|
+
* } from '@sanity/sdk-react'
|
|
107
|
+
* import {Suspense} from 'react'
|
|
108
|
+
*
|
|
109
|
+
* // Define a component to display a single document (using useProjection for efficiency)
|
|
110
|
+
* function MyDocumentComponent({doc}: {doc: DocumentHandle}) {
|
|
111
|
+
* const {data} = useProjection<{title?: string}>({
|
|
112
|
+
* ...doc, // Pass the full handle
|
|
113
|
+
* projection: '{title}'
|
|
114
|
+
* })
|
|
115
|
+
*
|
|
116
|
+
* return <>{data?.title || 'Untitled'}</>
|
|
117
|
+
* }
|
|
118
|
+
*
|
|
119
|
+
* // Define props for the list component
|
|
120
|
+
* interface DocumentListProps {
|
|
121
|
+
* dataset: DatasetHandle
|
|
122
|
+
* documentType: string
|
|
123
|
+
* search?: string
|
|
124
|
+
* }
|
|
125
|
+
*
|
|
126
|
+
* function DocumentList({dataset, documentType, search}: DocumentListProps) {
|
|
127
|
+
* const { data, hasMore, isPending, loadMore, count } = useDocuments({
|
|
128
|
+
* ...dataset,
|
|
129
|
+
* documentType,
|
|
130
|
+
* search,
|
|
131
|
+
* batchSize: 10,
|
|
132
|
+
* orderings: [{field: '_createdAt', direction: 'desc'}],
|
|
133
|
+
* })
|
|
134
|
+
*
|
|
135
|
+
* return (
|
|
136
|
+
* <div>
|
|
137
|
+
* <p>Total documents: {count}</p>
|
|
138
|
+
* <ol>
|
|
139
|
+
* {data.map((docHandle) => (
|
|
140
|
+
* <li key={docHandle.documentId}>
|
|
141
|
+
* <Suspense fallback="Loading…">
|
|
142
|
+
* <MyDocumentComponent docHandle={docHandle} />
|
|
143
|
+
* </Suspense>
|
|
144
|
+
* </li>
|
|
145
|
+
* ))}
|
|
146
|
+
* </ol>
|
|
147
|
+
* {hasMore && (
|
|
148
|
+
* <button onClick={loadMore}>
|
|
149
|
+
* {isPending ? 'Loading...' : 'Load More'}
|
|
150
|
+
* </button>
|
|
151
|
+
* )}
|
|
152
|
+
* </div>
|
|
153
|
+
* )
|
|
154
|
+
* }
|
|
155
|
+
*
|
|
156
|
+
* // Usage:
|
|
157
|
+
* // const myDatasetHandle = createDatasetHandle({ projectId: 'p1', dataset: 'production' })
|
|
158
|
+
* // <DocumentList dataset={myDatasetHandle} documentType="post" search="Sanity" />
|
|
159
|
+
* ```
|
|
160
|
+
*
|
|
161
|
+
* @example Using `filter` and `params` options for narrowing a collection
|
|
162
|
+
* ```tsx
|
|
163
|
+
* import {useState} from 'react'
|
|
164
|
+
* import {useDocuments} from '@sanity/sdk-react'
|
|
165
|
+
*
|
|
166
|
+
* export default function FilteredAuthors() {
|
|
167
|
+
* const [max, setMax] = useState(2)
|
|
168
|
+
* const {data} = useDocuments({
|
|
169
|
+
* documentType: 'author',
|
|
170
|
+
* filter: 'length(books) <= $max',
|
|
171
|
+
* params: {max},
|
|
172
|
+
* })
|
|
173
|
+
*
|
|
174
|
+
* return (
|
|
175
|
+
* <>
|
|
176
|
+
* <input
|
|
177
|
+
* id="maxBooks"
|
|
178
|
+
* type="number"
|
|
179
|
+
* value={max}
|
|
180
|
+
* onChange={e => setMax(e.currentTarget.value)}
|
|
181
|
+
* />
|
|
182
|
+
* {data.map(author => (
|
|
183
|
+
* <Suspense key={author.documentId}>
|
|
184
|
+
* <MyAuthorComponent documentHandle={author} />
|
|
185
|
+
* </Suspense>
|
|
186
|
+
* ))}
|
|
187
|
+
* </>
|
|
188
|
+
* )
|
|
189
|
+
* }
|
|
190
|
+
* ```
|
|
191
|
+
*/
|
|
192
|
+
export function useDocuments<
|
|
193
|
+
TDocumentType extends string = string,
|
|
194
|
+
TDataset extends string = string,
|
|
195
|
+
TProjectId extends string = string,
|
|
196
|
+
>({
|
|
197
|
+
batchSize = DEFAULT_BATCH_SIZE,
|
|
198
|
+
params,
|
|
199
|
+
search,
|
|
200
|
+
filter,
|
|
201
|
+
orderings,
|
|
202
|
+
documentType,
|
|
203
|
+
...options
|
|
204
|
+
}: DocumentsOptions<TDocumentType, TDataset, TProjectId>): DocumentsResponse<
|
|
205
|
+
TDocumentType,
|
|
206
|
+
TDataset,
|
|
207
|
+
TProjectId
|
|
208
|
+
> {
|
|
209
|
+
const instance = useSanityInstance(options)
|
|
210
|
+
const [limit, setLimit] = useState(batchSize)
|
|
211
|
+
const documentTypes = useMemo(
|
|
212
|
+
() =>
|
|
213
|
+
(Array.isArray(documentType) ? documentType : [documentType]).filter(
|
|
214
|
+
(i): i is TDocumentType => typeof i === 'string',
|
|
215
|
+
),
|
|
216
|
+
[documentType],
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
// Reset the limit to the current batchSize whenever any query parameters
|
|
220
|
+
// (filter, search, params, orderings) or batchSize changes
|
|
221
|
+
const key = JSON.stringify({
|
|
222
|
+
filter,
|
|
223
|
+
search,
|
|
224
|
+
params,
|
|
225
|
+
orderings,
|
|
226
|
+
batchSize,
|
|
227
|
+
types: documentTypes,
|
|
228
|
+
...options,
|
|
229
|
+
})
|
|
230
|
+
useEffect(() => {
|
|
231
|
+
setLimit(batchSize)
|
|
232
|
+
}, [key, batchSize])
|
|
233
|
+
|
|
234
|
+
const filterClause = useMemo(() => {
|
|
235
|
+
const conditions: string[] = []
|
|
236
|
+
const trimmedSearch = search?.trim()
|
|
237
|
+
|
|
238
|
+
// Add search query filter if specified
|
|
239
|
+
if (trimmedSearch) {
|
|
240
|
+
const searchFilter = createGroqSearchFilter(trimmedSearch)
|
|
241
|
+
if (searchFilter) {
|
|
242
|
+
conditions.push(searchFilter)
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Add type filter if specified
|
|
247
|
+
if (documentTypes?.length) {
|
|
248
|
+
conditions.push(`(_type in $__types)`)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Add additional filter if specified
|
|
252
|
+
if (filter) {
|
|
253
|
+
conditions.push(`(${filter})`)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return conditions.length ? `[${conditions.join(' && ')}]` : ''
|
|
257
|
+
}, [filter, search, documentTypes])
|
|
258
|
+
|
|
259
|
+
const orderClause = orderings
|
|
260
|
+
? `| order(${orderings
|
|
261
|
+
.map((ordering) =>
|
|
262
|
+
[ordering.field, ordering.direction.toLowerCase()]
|
|
263
|
+
.map((str) => str.trim())
|
|
264
|
+
.filter(Boolean)
|
|
265
|
+
.join(' '),
|
|
266
|
+
)
|
|
267
|
+
.join(',')})`
|
|
268
|
+
: ''
|
|
269
|
+
|
|
270
|
+
const dataQuery = `*${filterClause}${orderClause}[0...${limit}]{"documentId":_id,"documentType":_type,...$__handle}`
|
|
271
|
+
const countQuery = `count(*${filterClause})`
|
|
272
|
+
|
|
273
|
+
const {
|
|
274
|
+
data: {count, data},
|
|
275
|
+
isPending,
|
|
276
|
+
} = useQuery<{count: number; data: DocumentHandle<TDocumentType, TDataset, TProjectId>[]}>({
|
|
277
|
+
...options,
|
|
278
|
+
query: `{"count":${countQuery},"data":${dataQuery}}`,
|
|
279
|
+
params: {
|
|
280
|
+
...params,
|
|
281
|
+
__handle: {
|
|
282
|
+
...pick(instance.config, 'projectId', 'dataset', 'perspective'),
|
|
283
|
+
...pick(options, 'projectId', 'dataset', 'perspective'),
|
|
284
|
+
},
|
|
285
|
+
__types: documentTypes,
|
|
286
|
+
},
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
// Now use the correctly typed variables
|
|
290
|
+
const hasMore = data.length < count
|
|
291
|
+
|
|
292
|
+
const loadMore = useCallback(() => {
|
|
293
|
+
setLimit((prev) => Math.min(prev + batchSize, count))
|
|
294
|
+
}, [count, batchSize])
|
|
295
|
+
|
|
296
|
+
return useMemo(
|
|
297
|
+
() => ({data, hasMore, count, isPending, loadMore}),
|
|
298
|
+
[count, data, hasMore, isPending, loadMore],
|
|
299
|
+
)
|
|
300
|
+
}
|
|
@@ -55,7 +55,7 @@ describe('createCallbackHook', () => {
|
|
|
55
55
|
vi.mocked(useSanityInstance).mockReturnValueOnce(mockInstance1)
|
|
56
56
|
|
|
57
57
|
// Create a test callback
|
|
58
|
-
const testCallback = (instance: SanityInstance) => instance.
|
|
58
|
+
const testCallback = (instance: SanityInstance) => instance.config.projectId
|
|
59
59
|
|
|
60
60
|
// Create and render our hook
|
|
61
61
|
const useTestHook = createCallbackHook(testCallback)
|
|
@@ -87,7 +87,7 @@ describe('createCallbackHook', () => {
|
|
|
87
87
|
method: string,
|
|
88
88
|
data: object,
|
|
89
89
|
) => ({
|
|
90
|
-
url: `${instance.
|
|
90
|
+
url: `${instance.config.projectId}${path}`,
|
|
91
91
|
method,
|
|
92
92
|
data,
|
|
93
93
|
})
|
|
@@ -72,7 +72,7 @@ describe('createStateSourceHook', () => {
|
|
|
72
72
|
|
|
73
73
|
const stateSourceFactory = vi.fn((instance: SanityInstance) => ({
|
|
74
74
|
subscribe: vi.fn(),
|
|
75
|
-
getCurrent: () => instance.
|
|
75
|
+
getCurrent: () => instance.config.projectId,
|
|
76
76
|
observable: throwError(() => new Error('unexpected usage of observable')),
|
|
77
77
|
}))
|
|
78
78
|
|
|
@@ -127,4 +127,70 @@ describe('createStateSourceHook', () => {
|
|
|
127
127
|
expect(result.current).toEqual('test_123')
|
|
128
128
|
expect(stateSourceFactory).toHaveBeenCalledWith(mockInstance, 'test', 123)
|
|
129
129
|
})
|
|
130
|
+
|
|
131
|
+
it('should throw suspender promise when shouldSuspend is true', () => {
|
|
132
|
+
const mockInstance = createSanityInstance({projectId: 'p', dataset: 'd'})
|
|
133
|
+
vi.mocked(useSanityInstance).mockReturnValue(mockInstance)
|
|
134
|
+
|
|
135
|
+
const mockGetState = vi.fn().mockReturnValue({
|
|
136
|
+
subscribe: vi.fn(),
|
|
137
|
+
getCurrent: vi.fn().mockReturnValue('state'),
|
|
138
|
+
observable: throwError(() => new Error('unexpected usage of observable')),
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
const mockShouldSuspend = vi.fn().mockReturnValue(true)
|
|
142
|
+
const mockSuspender = vi.fn().mockReturnValue(Promise.resolve())
|
|
143
|
+
|
|
144
|
+
const options = {
|
|
145
|
+
getState: mockGetState,
|
|
146
|
+
shouldSuspend: mockShouldSuspend,
|
|
147
|
+
suspender: mockSuspender,
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const useTestHook = createStateSourceHook(options)
|
|
151
|
+
const {result} = renderHook(() => {
|
|
152
|
+
try {
|
|
153
|
+
useTestHook('param1', 2)
|
|
154
|
+
} catch (e) {
|
|
155
|
+
return e
|
|
156
|
+
}
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
expect(mockShouldSuspend).toHaveBeenCalledWith(mockInstance, 'param1', 2)
|
|
160
|
+
expect(mockSuspender).toHaveBeenCalledWith(mockInstance, 'param1', 2)
|
|
161
|
+
expect(result.current).toBe(mockSuspender.mock.results[0].value)
|
|
162
|
+
expect(mockGetState).not.toHaveBeenCalled()
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
it('should not suspend when shouldSuspend returns false', () => {
|
|
166
|
+
const mockInstance = createSanityInstance({projectId: 'p', dataset: 'd'})
|
|
167
|
+
vi.mocked(useSanityInstance).mockReturnValue(mockInstance)
|
|
168
|
+
|
|
169
|
+
const mockState = {value: 'test'}
|
|
170
|
+
const mockSubscribe = vi.fn()
|
|
171
|
+
const mockGetCurrent = vi.fn(() => mockState)
|
|
172
|
+
|
|
173
|
+
const mockGetState = vi.fn().mockReturnValue({
|
|
174
|
+
subscribe: mockSubscribe,
|
|
175
|
+
getCurrent: mockGetCurrent,
|
|
176
|
+
observable: throwError(() => new Error('unexpected usage of observable')),
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
const mockShouldSuspend = vi.fn().mockReturnValue(false)
|
|
180
|
+
const mockSuspender = vi.fn()
|
|
181
|
+
|
|
182
|
+
const options = {
|
|
183
|
+
getState: mockGetState,
|
|
184
|
+
shouldSuspend: mockShouldSuspend,
|
|
185
|
+
suspender: mockSuspender,
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const useTestHook = createStateSourceHook(options)
|
|
189
|
+
const {result} = renderHook(() => useTestHook('param', 123))
|
|
190
|
+
|
|
191
|
+
expect(mockShouldSuspend).toHaveBeenCalledWith(mockInstance, 'param', 123)
|
|
192
|
+
expect(mockSuspender).not.toHaveBeenCalled()
|
|
193
|
+
expect(mockGetState).toHaveBeenCalledWith(mockInstance, 'param', 123)
|
|
194
|
+
expect(result.current).toBe(mockState)
|
|
195
|
+
})
|
|
130
196
|
})
|
|
@@ -1,20 +1,36 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import {
|
|
1
|
+
import {type SanityConfig, type SanityInstance, type StateSource} from '@sanity/sdk'
|
|
2
|
+
import {useSyncExternalStore} from 'react'
|
|
3
3
|
|
|
4
4
|
import {useSanityInstance} from '../context/useSanityInstance'
|
|
5
5
|
|
|
6
|
+
type StateSourceFactory<TParams extends unknown[], TState> = (
|
|
7
|
+
instance: SanityInstance,
|
|
8
|
+
...params: TParams
|
|
9
|
+
) => StateSource<TState>
|
|
10
|
+
|
|
11
|
+
interface CreateStateSourceHookOptions<TParams extends unknown[], TState> {
|
|
12
|
+
getState: StateSourceFactory<TParams, TState>
|
|
13
|
+
shouldSuspend?: (instance: SanityInstance, ...params: TParams) => boolean
|
|
14
|
+
suspender?: (instance: SanityInstance, ...params: TParams) => Promise<unknown>
|
|
15
|
+
getConfig?: (...params: TParams) => SanityConfig | undefined
|
|
16
|
+
}
|
|
17
|
+
|
|
6
18
|
export function createStateSourceHook<TParams extends unknown[], TState>(
|
|
7
|
-
|
|
19
|
+
options: StateSourceFactory<TParams, TState> | CreateStateSourceHookOptions<TParams, TState>,
|
|
8
20
|
): (...params: TParams) => TState {
|
|
21
|
+
const getState = typeof options === 'function' ? options : options.getState
|
|
22
|
+
const getConfig = 'getConfig' in options ? options.getConfig : undefined
|
|
23
|
+
const suspense = 'shouldSuspend' in options && 'suspender' in options ? options : undefined
|
|
24
|
+
|
|
9
25
|
function useHook(...params: TParams) {
|
|
10
|
-
const instance = useSanityInstance()
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
return useSyncExternalStore(subscribe, getCurrent)
|
|
26
|
+
const instance = useSanityInstance(getConfig?.(...params))
|
|
27
|
+
|
|
28
|
+
if (suspense?.suspender && suspense?.shouldSuspend?.(instance, ...params)) {
|
|
29
|
+
throw suspense.suspender(instance, ...params)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const state = getState(instance, ...params)
|
|
33
|
+
return useSyncExternalStore(state.subscribe, state.getCurrent)
|
|
18
34
|
}
|
|
19
35
|
|
|
20
36
|
return useHook
|