@sanity/sdk-react 2.10.0 → 2.11.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 +257 -200
- package/dist/index.js +364 -253
- package/dist/index.js.map +1 -1
- package/package.json +6 -9
- package/src/_exports/index.ts +2 -0
- package/src/_exports/sdk-react.ts +4 -0
- package/src/components/SDKProvider.test.tsx +5 -12
- package/src/components/SDKProvider.tsx +26 -24
- package/src/config/handles.ts +55 -0
- package/src/constants.ts +5 -0
- package/src/context/DefaultResourceContext.ts +10 -0
- package/src/context/PerspectiveContext.ts +12 -0
- package/src/context/ResourceProvider.test.tsx +2 -2
- package/src/context/ResourceProvider.tsx +53 -49
- package/src/hooks/agent/agentActions.ts +55 -38
- package/src/hooks/context/useResource.test.tsx +32 -0
- package/src/hooks/context/useResource.ts +24 -0
- package/src/hooks/context/useSanityInstance.test.tsx +42 -111
- package/src/hooks/context/useSanityInstance.ts +28 -50
- package/src/hooks/dashboard/useDispatchIntent.test.ts +5 -1
- package/src/hooks/dashboard/useDispatchIntent.ts +3 -3
- package/src/hooks/dashboard/useManageFavorite.test.tsx +16 -12
- package/src/hooks/dashboard/utils/useResourceIdFromDocumentHandle.ts +1 -5
- package/src/hooks/document/{useApplyDocumentActions.test.ts → useApplyDocumentActions.test.tsx} +42 -77
- package/src/hooks/document/useApplyDocumentActions.ts +28 -62
- package/src/hooks/document/useDocument.ts +3 -5
- package/src/hooks/document/useDocumentEvent.ts +4 -3
- package/src/hooks/document/useDocumentPermissions.test.tsx +58 -150
- package/src/hooks/document/useDocumentPermissions.ts +78 -55
- package/src/hooks/document/useEditDocument.test.tsx +25 -60
- package/src/hooks/document/useEditDocument.ts +1 -1
- package/src/hooks/documents/useDocuments.ts +13 -8
- package/src/hooks/helpers/createStateSourceHook.tsx +1 -2
- package/src/hooks/helpers/useNormalizedResourceOptions.test.tsx +253 -0
- package/src/hooks/helpers/useNormalizedResourceOptions.ts +85 -47
- package/src/hooks/organizations/useOrganization.test-d.ts +53 -0
- package/src/hooks/organizations/useOrganization.test.ts +65 -0
- package/src/hooks/organizations/useOrganization.ts +40 -0
- package/src/hooks/organizations/useOrganizations.test-d.ts +55 -0
- package/src/hooks/organizations/useOrganizations.test.ts +85 -0
- package/src/hooks/organizations/useOrganizations.ts +45 -0
- package/src/hooks/paginatedDocuments/usePaginatedDocuments.ts +23 -9
- package/src/hooks/presence/usePresence.ts +4 -11
- package/src/hooks/preview/useDocumentPreview.tsx +4 -7
- package/src/hooks/projection/useDocumentProjection.ts +5 -7
- package/src/hooks/projects/useProject.test-d.ts +49 -0
- package/src/hooks/projects/useProject.ts +33 -41
- package/src/hooks/projects/useProjects.test-d.ts +49 -0
- package/src/hooks/projects/useProjects.ts +17 -23
- package/src/hooks/query/useQuery.ts +1 -1
- package/src/hooks/releases/useActiveReleases.ts +6 -6
- package/src/hooks/releases/usePerspective.ts +7 -12
- package/src/hooks/users/useUser.ts +1 -1
- package/src/hooks/users/useUsers.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanity/sdk-react",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.11.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Sanity SDK React toolkit for Content OS",
|
|
6
6
|
"keywords": [
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"react-compiler-runtime": "19.1.0-rc.2",
|
|
51
51
|
"react-error-boundary": "^5.0.0",
|
|
52
52
|
"rxjs": "^7.8.2",
|
|
53
|
-
"@sanity/sdk": "2.
|
|
53
|
+
"@sanity/sdk": "2.11.0"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"@sanity/browserslist-config": "^1.0.5",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"@types/react": "^19.2.7",
|
|
64
64
|
"@types/react-dom": "^19.2.3",
|
|
65
65
|
"@vitejs/plugin-react": "^4.7.0",
|
|
66
|
-
"@vitest/coverage-v8": "
|
|
66
|
+
"@vitest/coverage-v8": "4.1.5",
|
|
67
67
|
"babel-plugin-react-compiler": "19.1.0-rc.1",
|
|
68
68
|
"eslint": "^9.22.0",
|
|
69
69
|
"groq-js": "^1.22.0",
|
|
@@ -74,20 +74,17 @@
|
|
|
74
74
|
"rollup-plugin-visualizer": "^5.14.0",
|
|
75
75
|
"typescript": "^5.8.3",
|
|
76
76
|
"vite": "^7.0.0",
|
|
77
|
-
"vitest": "^
|
|
78
|
-
"@repo/config-eslint": "0.0.0",
|
|
77
|
+
"vitest": "^4.1.4",
|
|
79
78
|
"@repo/config-test": "0.0.1",
|
|
79
|
+
"@repo/package.bundle": "3.82.0",
|
|
80
80
|
"@repo/package.config": "0.0.1",
|
|
81
81
|
"@repo/tsconfig": "0.0.1",
|
|
82
|
-
"@repo/
|
|
82
|
+
"@repo/config-eslint": "0.0.0"
|
|
83
83
|
},
|
|
84
84
|
"peerDependencies": {
|
|
85
85
|
"react": "^18.0.0 || ^19.0.0",
|
|
86
86
|
"react-dom": "^18.0.0 || ^19.0.0"
|
|
87
87
|
},
|
|
88
|
-
"engines": {
|
|
89
|
-
"node": ">=20.19"
|
|
90
|
-
},
|
|
91
88
|
"publishConfig": {
|
|
92
89
|
"access": "public"
|
|
93
90
|
},
|
package/src/_exports/index.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
export {AuthBoundary, type AuthBoundaryProps} from '../components/auth/AuthBoundary'
|
|
5
5
|
export {SanityApp, type SanityAppProps} from '../components/SanityApp'
|
|
6
6
|
export {SDKProvider, type SDKProviderProps} from '../components/SDKProvider'
|
|
7
|
+
export {type DocumentHandle, type DocumentTypeHandle, type ResourceHandle} from '../config/handles'
|
|
7
8
|
export {ComlinkTokenRefreshProvider} from '../context/ComlinkTokenRefresh'
|
|
8
9
|
export {renderSanityApp} from '../context/renderSanityApp'
|
|
9
10
|
export {ResourceProvider, type ResourceProviderProps} from '../context/ResourceProvider'
|
|
@@ -44,6 +45,7 @@ export {
|
|
|
44
45
|
type WindowConnection,
|
|
45
46
|
type WindowMessageHandler,
|
|
46
47
|
} from '../hooks/comlink/useWindowConnection'
|
|
48
|
+
export {useResource} from '../hooks/context/useResource'
|
|
47
49
|
export {useSanityInstance} from '../hooks/context/useSanityInstance'
|
|
48
50
|
export {useDashboardNavigate} from '../hooks/dashboard/useDashboardNavigate'
|
|
49
51
|
export {useDispatchIntent} from '../hooks/dashboard/useDispatchIntent'
|
|
@@ -67,6 +69,8 @@ export {
|
|
|
67
69
|
type DocumentsResponse,
|
|
68
70
|
useDocuments,
|
|
69
71
|
} from '../hooks/documents/useDocuments'
|
|
72
|
+
export {useOrganization} from '../hooks/organizations/useOrganization'
|
|
73
|
+
export {useOrganizations} from '../hooks/organizations/useOrganizations'
|
|
70
74
|
export {
|
|
71
75
|
type PaginatedDocumentsOptions,
|
|
72
76
|
type PaginatedDocumentsResponse,
|
|
@@ -62,7 +62,7 @@ describe('SDKProvider', () => {
|
|
|
62
62
|
})
|
|
63
63
|
})
|
|
64
64
|
|
|
65
|
-
it('renders
|
|
65
|
+
it('renders a single ResourceProvider using the first config when multiple configs are provided', () => {
|
|
66
66
|
const configs = [
|
|
67
67
|
{
|
|
68
68
|
projectId: 'project-1',
|
|
@@ -80,22 +80,15 @@ describe('SDKProvider', () => {
|
|
|
80
80
|
</SDKProvider>,
|
|
81
81
|
)
|
|
82
82
|
|
|
83
|
-
// Should create
|
|
83
|
+
// Should create a single ResourceProvider using the first config
|
|
84
84
|
const providers = getAllByTestId('resource-provider')
|
|
85
|
-
expect(providers.length).toBe(
|
|
85
|
+
expect(providers.length).toBe(1)
|
|
86
86
|
|
|
87
|
-
// Should create an AuthBoundary inside
|
|
87
|
+
// Should create an AuthBoundary inside
|
|
88
88
|
expect(getByTestId('auth-boundary')).toBeInTheDocument()
|
|
89
89
|
|
|
90
|
-
// Verify
|
|
91
|
-
// The first provider contains config[1]
|
|
90
|
+
// Verify the provider uses the first config
|
|
92
91
|
expect(JSON.parse(providers[0].getAttribute('data-config') || '{}')).toEqual({
|
|
93
|
-
projectId: 'project-2',
|
|
94
|
-
dataset: 'staging',
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
// The second provider contains config[0]
|
|
98
|
-
expect(JSON.parse(providers[1].getAttribute('data-config') || '{}')).toEqual({
|
|
99
92
|
projectId: 'project-1',
|
|
100
93
|
dataset: 'production',
|
|
101
94
|
})
|
|
@@ -2,6 +2,7 @@ import {type DocumentResource, isImportError, type SanityConfig} from '@sanity/s
|
|
|
2
2
|
import {type ReactElement, type ReactNode, useEffect, useMemo} from 'react'
|
|
3
3
|
import {ErrorBoundary, type FallbackProps} from 'react-error-boundary'
|
|
4
4
|
|
|
5
|
+
import {DEFAULT_RESOURCE_NAME} from '../constants'
|
|
5
6
|
import {ResourceProvider} from '../context/ResourceProvider'
|
|
6
7
|
import {ResourcesContext} from '../context/ResourcesContext'
|
|
7
8
|
import {AuthBoundary, type AuthBoundaryProps} from './auth/AuthBoundary'
|
|
@@ -34,8 +35,6 @@ function ResetChunkReloadFlagOnMount(): null {
|
|
|
34
35
|
* @internal
|
|
35
36
|
*
|
|
36
37
|
* Top-level context provider that provides access to the Sanity SDK.
|
|
37
|
-
* Creates a hierarchy of ResourceProviders, each providing a SanityInstance that can be
|
|
38
|
-
* accessed by hooks. The first configuration in the array becomes the default instance.
|
|
39
38
|
*/
|
|
40
39
|
export function SDKProvider({
|
|
41
40
|
children,
|
|
@@ -43,35 +42,38 @@ export function SDKProvider({
|
|
|
43
42
|
fallback,
|
|
44
43
|
...props
|
|
45
44
|
}: SDKProviderProps): ReactElement {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const
|
|
49
|
-
const projectIds = configs.map((c) => c.projectId).filter((id): id is string => !!id)
|
|
45
|
+
const allConfigs = Array.isArray(config) ? config : [config]
|
|
46
|
+
const resolvedConfig = allConfigs[0]
|
|
47
|
+
const projectIds = allConfigs.map((c) => c.projectId).filter((id): id is string => !!id)
|
|
50
48
|
|
|
51
|
-
//
|
|
52
|
-
|
|
49
|
+
// Extract static fields so the memo below doesn't take a reference dependency
|
|
50
|
+
// on `config` — inline config objects change identity on every render.
|
|
51
|
+
const singleConfig = Array.isArray(config) ? null : config
|
|
52
|
+
const defaultProjectId = singleConfig?.projectId
|
|
53
|
+
const defaultDataset = singleConfig?.dataset
|
|
53
54
|
|
|
54
|
-
//
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
55
|
+
// For a single config, synthesize a 'default' resource from its projectId/dataset
|
|
56
|
+
// so that hooks can resolve it via resourceName: 'default' or fall back to it
|
|
57
|
+
// automatically when no resource info is provided.
|
|
58
|
+
const resourcesValue = useMemo(() => {
|
|
59
|
+
const explicit = props.resources ?? {}
|
|
60
|
+
if (defaultProjectId && defaultDataset && !Object.hasOwn(explicit, DEFAULT_RESOURCE_NAME)) {
|
|
61
|
+
return {
|
|
62
|
+
[DEFAULT_RESOURCE_NAME]: {projectId: defaultProjectId, dataset: defaultDataset},
|
|
63
|
+
...explicit,
|
|
64
|
+
}
|
|
62
65
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
<ResourceProvider {...configs[index]} fallback={fallback}>
|
|
66
|
-
{createNestedProviders(index + 1)}
|
|
67
|
-
</ResourceProvider>
|
|
68
|
-
)
|
|
69
|
-
}
|
|
66
|
+
return explicit
|
|
67
|
+
}, [defaultProjectId, defaultDataset, props.resources])
|
|
70
68
|
|
|
71
69
|
return (
|
|
72
70
|
<ErrorBoundary FallbackComponent={ChunkAwareFallback}>
|
|
73
71
|
<ResetChunkReloadFlagOnMount />
|
|
74
|
-
{
|
|
72
|
+
<ResourceProvider {...resolvedConfig} fallback={fallback}>
|
|
73
|
+
<AuthBoundary {...props} projectIds={projectIds}>
|
|
74
|
+
<ResourcesContext.Provider value={resourcesValue}>{children}</ResourcesContext.Provider>
|
|
75
|
+
</AuthBoundary>
|
|
76
|
+
</ResourceProvider>
|
|
75
77
|
</ErrorBoundary>
|
|
76
78
|
)
|
|
77
79
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type DatasetHandle,
|
|
3
|
+
type DocumentHandle as CoreDocumentHandle,
|
|
4
|
+
type DocumentTypeHandle as CoreDocumentTypeHandle,
|
|
5
|
+
} from '@sanity/sdk'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* React SDK resource handle — extends the core DatasetHandle with `resourceName`
|
|
9
|
+
* for context-based resource resolution.
|
|
10
|
+
*
|
|
11
|
+
* Use this (or its subtypes) as the options type for custom hooks that need to
|
|
12
|
+
* accept a resource. It accepts a `resource` object, a `resourceName` registered
|
|
13
|
+
* via the `resources` prop on `<SanityApp>`, or a bare `projectId`/`dataset` pair
|
|
14
|
+
* for backward compatibility.
|
|
15
|
+
*
|
|
16
|
+
* @public
|
|
17
|
+
*/
|
|
18
|
+
export interface ResourceHandle<
|
|
19
|
+
TDataset extends string = string,
|
|
20
|
+
TProjectId extends string = string,
|
|
21
|
+
> extends DatasetHandle<TDataset, TProjectId> {
|
|
22
|
+
/**
|
|
23
|
+
* Name of a resource registered via the `resources` prop on `<SanityApp>`.
|
|
24
|
+
* Resolved to a `DocumentResource` at the React layer.
|
|
25
|
+
*/
|
|
26
|
+
resourceName?: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* React SDK document-type handle. Adds `resourceName` to the core `DocumentTypeHandle`.
|
|
31
|
+
* @public
|
|
32
|
+
*/
|
|
33
|
+
export interface DocumentTypeHandle<
|
|
34
|
+
TDocumentType extends string = string,
|
|
35
|
+
TDataset extends string = string,
|
|
36
|
+
TProjectId extends string = string,
|
|
37
|
+
> extends CoreDocumentTypeHandle<TDocumentType, TDataset, TProjectId> {
|
|
38
|
+
resourceName?: string
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* React SDK document handle. Adds `resourceName` to the core `DocumentHandle`.
|
|
43
|
+
*
|
|
44
|
+
* Import from `@sanity/sdk-react` (not `@sanity/sdk`) when writing option types
|
|
45
|
+
* for hooks — this version understands `resourceName` resolution.
|
|
46
|
+
*
|
|
47
|
+
* @public
|
|
48
|
+
*/
|
|
49
|
+
export interface DocumentHandle<
|
|
50
|
+
TDocumentType extends string = string,
|
|
51
|
+
TDataset extends string = string,
|
|
52
|
+
TProjectId extends string = string,
|
|
53
|
+
> extends CoreDocumentHandle<TDocumentType, TDataset, TProjectId> {
|
|
54
|
+
resourceName?: string
|
|
55
|
+
}
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import {type DocumentResource} from '@sanity/sdk'
|
|
2
|
+
import {createContext} from 'react'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Provides the active DocumentResource for a subtree.
|
|
6
|
+
* Set by ResourceProvider; read by useNormalizedResourceOptions as a fallback
|
|
7
|
+
* when hooks receive no explicit resource or resourceName.
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
10
|
+
export const ResourceContext = createContext<DocumentResource | undefined>(undefined)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import {type PerspectiveHandle} from '@sanity/sdk'
|
|
2
|
+
import {createContext} from 'react'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Provides the active perspective for a subtree.
|
|
6
|
+
* Set by ResourceProvider; injected by useNormalizedResourceOptions when
|
|
7
|
+
* the hook's options don't include an explicit perspective.
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
10
|
+
export const PerspectiveContext = createContext<PerspectiveHandle['perspective'] | undefined>(
|
|
11
|
+
undefined,
|
|
12
|
+
)
|
|
@@ -78,7 +78,7 @@ describe('ResourceProvider', () => {
|
|
|
78
78
|
})
|
|
79
79
|
})
|
|
80
80
|
|
|
81
|
-
it('
|
|
81
|
+
it('reuses instance when parent context exists', async () => {
|
|
82
82
|
const parentConfig: SanityConfig = {...testConfig, dataset: 'parent-dataset'}
|
|
83
83
|
const child = promiseWithResolvers<SanityInstance | null>()
|
|
84
84
|
|
|
@@ -97,7 +97,7 @@ describe('ResourceProvider', () => {
|
|
|
97
97
|
)
|
|
98
98
|
|
|
99
99
|
const childInstance = await child.promise
|
|
100
|
-
expect(childInstance?.config).toEqual(
|
|
100
|
+
expect(childInstance?.config).toEqual(parentConfig)
|
|
101
101
|
expect(childInstance?.isDisposed()).toBe(false)
|
|
102
102
|
})
|
|
103
103
|
|
|
@@ -1,7 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
createSanityInstance,
|
|
3
|
+
type DatasetResource,
|
|
4
|
+
type DocumentResource,
|
|
5
|
+
isDatasetResource,
|
|
6
|
+
type SanityConfig,
|
|
7
|
+
type SanityInstance,
|
|
8
|
+
} from '@sanity/sdk'
|
|
2
9
|
import {initTelemetry} from '@sanity/sdk/_internal'
|
|
3
|
-
import {useContext, useEffect, useMemo, useRef} from 'react'
|
|
10
|
+
import {useContext, useEffect, useMemo, useRef, useState} from 'react'
|
|
4
11
|
|
|
12
|
+
import {ResourceContext} from './DefaultResourceContext'
|
|
13
|
+
import {PerspectiveContext} from './PerspectiveContext'
|
|
5
14
|
import {SanityInstanceContext} from './SanityInstanceContext'
|
|
6
15
|
import {SanityInstanceProvider} from './SanityInstanceProvider'
|
|
7
16
|
|
|
@@ -17,73 +26,63 @@ const DEFAULT_FALLBACK = (
|
|
|
17
26
|
*/
|
|
18
27
|
export interface ResourceProviderProps extends SanityConfig {
|
|
19
28
|
/**
|
|
20
|
-
*
|
|
21
|
-
*
|
|
29
|
+
* The document resource (project/dataset, media library, or canvas)
|
|
30
|
+
* for this subtree. Hooks that don't specify an explicit resource will
|
|
31
|
+
* use this value.
|
|
32
|
+
*/
|
|
33
|
+
resource?: DocumentResource
|
|
34
|
+
/**
|
|
35
|
+
* React node to show while content is loading.
|
|
36
|
+
* Used as the fallback for the internal Suspense boundary.
|
|
22
37
|
*/
|
|
23
38
|
fallback: React.ReactNode
|
|
24
39
|
children: React.ReactNode
|
|
25
40
|
}
|
|
26
41
|
|
|
27
42
|
/**
|
|
28
|
-
* Provides
|
|
43
|
+
* Provides Sanity configuration to child components through React Context.
|
|
29
44
|
*
|
|
30
45
|
* @internal
|
|
31
46
|
*
|
|
32
|
-
* @
|
|
33
|
-
* The ResourceProvider creates a hierarchical structure of Sanity instances:
|
|
34
|
-
* - When used as a root provider, it creates a new Sanity instance with the given config
|
|
35
|
-
* - When nested inside another ResourceProvider, it creates a child instance that
|
|
36
|
-
* inherits and extends the parent's configuration
|
|
37
|
-
*
|
|
38
|
-
* Features:
|
|
39
|
-
* - Automatically manages the lifecycle of Sanity instances
|
|
40
|
-
* - Disposes instances when the component unmounts
|
|
41
|
-
* - Includes a Suspense boundary for data loading
|
|
42
|
-
* - Enables hierarchical configuration inheritance
|
|
43
|
-
*
|
|
44
|
-
* Use this component to:
|
|
45
|
-
* - Set up project/dataset configuration for an application
|
|
46
|
-
* - Override specific configuration values in a section of your app
|
|
47
|
-
* - Create isolated instance hierarchies for different features
|
|
48
|
-
*
|
|
49
|
-
* @example Creating a root provider
|
|
47
|
+
* @example
|
|
50
48
|
* ```tsx
|
|
51
49
|
* <ResourceProvider
|
|
52
|
-
* projectId
|
|
53
|
-
* dataset="production"
|
|
50
|
+
* resource={{ projectId: 'your-project-id', dataset: 'production' }}
|
|
54
51
|
* fallback={<LoadingSpinner />}
|
|
55
52
|
* >
|
|
56
53
|
* <YourApp />
|
|
57
54
|
* </ResourceProvider>
|
|
58
55
|
* ```
|
|
59
|
-
*
|
|
60
|
-
* @example Creating nested providers with configuration inheritance
|
|
61
|
-
* ```tsx
|
|
62
|
-
* // Root provider with production config with nested provider for preview features with custom dataset
|
|
63
|
-
* <ResourceProvider projectId="abc123" dataset="production" fallback={<Loading />}>
|
|
64
|
-
* <div>...Main app content</div>
|
|
65
|
-
* <Dashboard />
|
|
66
|
-
* <ResourceProvider dataset="preview" fallback={<Loading />}>
|
|
67
|
-
* <PreviewFeatures />
|
|
68
|
-
* </ResourceProvider>
|
|
69
|
-
* </ResourceProvider>
|
|
70
|
-
* ```
|
|
71
56
|
*/
|
|
72
57
|
export function ResourceProvider({
|
|
73
58
|
children,
|
|
74
59
|
fallback,
|
|
60
|
+
resource,
|
|
75
61
|
...config
|
|
76
62
|
}: ResourceProviderProps): React.ReactNode {
|
|
77
|
-
const
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
63
|
+
const parentPerspective = useContext(PerspectiveContext)
|
|
64
|
+
const parentResource = useContext(ResourceContext)
|
|
65
|
+
const parentInstance = useContext(SanityInstanceContext)
|
|
66
|
+
|
|
67
|
+
const {projectId, dataset, perspective} = config
|
|
68
|
+
|
|
69
|
+
const [instance] = useState<SanityInstance>(() => parentInstance ?? createSanityInstance(config))
|
|
82
70
|
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
71
|
+
const configResource: DatasetResource | undefined = useMemo(() => {
|
|
72
|
+
if (projectId && dataset) {
|
|
73
|
+
return {projectId, dataset}
|
|
74
|
+
}
|
|
75
|
+
return undefined
|
|
76
|
+
}, [projectId, dataset])
|
|
77
|
+
|
|
78
|
+
const effectiveResource = useMemo(() => {
|
|
79
|
+
return resource ?? configResource ?? parentResource
|
|
80
|
+
}, [resource, configResource, parentResource])
|
|
81
|
+
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
if (effectiveResource && isDatasetResource(effectiveResource))
|
|
84
|
+
initTelemetry(instance, effectiveResource.projectId)
|
|
85
|
+
}, [instance, effectiveResource])
|
|
87
86
|
|
|
88
87
|
// Ref to hold the scheduled disposal timer.
|
|
89
88
|
const disposal = useRef<{
|
|
@@ -102,17 +101,22 @@ export function ResourceProvider({
|
|
|
102
101
|
disposal.current = {
|
|
103
102
|
instance,
|
|
104
103
|
timeoutId: setTimeout(() => {
|
|
105
|
-
|
|
104
|
+
// don't dispose the parent instance when this unmounts
|
|
105
|
+
if (!instance.isDisposed() && instance !== parentInstance) {
|
|
106
106
|
instance.dispose()
|
|
107
107
|
}
|
|
108
108
|
}, 0),
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
|
-
}, [instance])
|
|
111
|
+
}, [instance, parentInstance])
|
|
112
112
|
|
|
113
113
|
return (
|
|
114
114
|
<SanityInstanceProvider instance={instance} fallback={fallback ?? DEFAULT_FALLBACK}>
|
|
115
|
-
{
|
|
115
|
+
<ResourceContext.Provider value={effectiveResource}>
|
|
116
|
+
<PerspectiveContext.Provider value={perspective ?? parentPerspective}>
|
|
117
|
+
{children}
|
|
118
|
+
</PerspectiveContext.Provider>
|
|
119
|
+
</ResourceContext.Provider>
|
|
116
120
|
</SanityInstanceProvider>
|
|
117
121
|
)
|
|
118
122
|
}
|
|
@@ -11,11 +11,13 @@ import {
|
|
|
11
11
|
type AgentTransformOptions,
|
|
12
12
|
agentTranslate,
|
|
13
13
|
type AgentTranslateOptions,
|
|
14
|
-
type SanityInstance,
|
|
15
14
|
} from '@sanity/sdk'
|
|
15
|
+
import {useCallback} from 'react'
|
|
16
16
|
import {firstValueFrom} from 'rxjs'
|
|
17
17
|
|
|
18
|
-
import {
|
|
18
|
+
import {type ResourceHandle} from '../../config/handles'
|
|
19
|
+
import {useSanityInstance} from '../context/useSanityInstance'
|
|
20
|
+
import {useNormalizedResourceOptions} from '../helpers/useNormalizedResourceOptions'
|
|
19
21
|
|
|
20
22
|
interface Subscription {
|
|
21
23
|
unsubscribe(): void
|
|
@@ -103,10 +105,17 @@ interface Subscribable<T> {
|
|
|
103
105
|
*
|
|
104
106
|
* @category Agent Actions
|
|
105
107
|
*/
|
|
106
|
-
export
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
108
|
+
export function useAgentGenerate(
|
|
109
|
+
resourceHandle?: ResourceHandle,
|
|
110
|
+
): (options: AgentGenerateOptions) => Subscribable<unknown> {
|
|
111
|
+
const instance = useSanityInstance()
|
|
112
|
+
const {resource} = useNormalizedResourceOptions(resourceHandle ?? {})
|
|
113
|
+
return useCallback(
|
|
114
|
+
(options: AgentGenerateOptions) =>
|
|
115
|
+
agentGenerate(instance, options, resource) as unknown as Subscribable<unknown>,
|
|
116
|
+
[instance, resource],
|
|
117
|
+
)
|
|
118
|
+
}
|
|
110
119
|
|
|
111
120
|
/**
|
|
112
121
|
* @alpha
|
|
@@ -179,10 +188,17 @@ export const useAgentGenerate: () => (options: AgentGenerateOptions) => Subscrib
|
|
|
179
188
|
*
|
|
180
189
|
* @category Agent Actions
|
|
181
190
|
*/
|
|
182
|
-
export
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
191
|
+
export function useAgentTransform(
|
|
192
|
+
resourceHandle?: ResourceHandle,
|
|
193
|
+
): (options: AgentTransformOptions) => Subscribable<unknown> {
|
|
194
|
+
const instance = useSanityInstance()
|
|
195
|
+
const {resource} = useNormalizedResourceOptions(resourceHandle ?? {})
|
|
196
|
+
return useCallback(
|
|
197
|
+
(options: AgentTransformOptions) =>
|
|
198
|
+
agentTransform(instance, options, resource) as unknown as Subscribable<unknown>,
|
|
199
|
+
[instance, resource],
|
|
200
|
+
)
|
|
201
|
+
}
|
|
186
202
|
|
|
187
203
|
/**
|
|
188
204
|
* @alpha
|
|
@@ -274,20 +290,16 @@ export const useAgentTransform: () => (options: AgentTransformOptions) => Subscr
|
|
|
274
290
|
*
|
|
275
291
|
* @category Agent Actions
|
|
276
292
|
*/
|
|
277
|
-
export
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
instance: SanityInstance,
|
|
288
|
-
options: AgentPromptOptions,
|
|
289
|
-
): Promise<AgentPromptResult> {
|
|
290
|
-
return firstValueFrom(agentPrompt(instance, options))
|
|
293
|
+
export function useAgentTranslate(
|
|
294
|
+
resourceHandle?: ResourceHandle,
|
|
295
|
+
): (options: AgentTranslateOptions) => Subscribable<unknown> {
|
|
296
|
+
const instance = useSanityInstance()
|
|
297
|
+
const {resource} = useNormalizedResourceOptions(resourceHandle ?? {})
|
|
298
|
+
return useCallback(
|
|
299
|
+
(options: AgentTranslateOptions) =>
|
|
300
|
+
agentTranslate(instance, options, resource) as unknown as Subscribable<unknown>,
|
|
301
|
+
[instance, resource],
|
|
302
|
+
)
|
|
291
303
|
}
|
|
292
304
|
|
|
293
305
|
/**
|
|
@@ -384,18 +396,15 @@ function promptAdapter(
|
|
|
384
396
|
*
|
|
385
397
|
* @category Agent Actions
|
|
386
398
|
*/
|
|
387
|
-
export
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
options: AgentPatchOptions,
|
|
397
|
-
): Promise<AgentPatchResult> {
|
|
398
|
-
return firstValueFrom(agentPatch(instance, options))
|
|
399
|
+
export function useAgentPrompt(
|
|
400
|
+
resourceHandle?: ResourceHandle,
|
|
401
|
+
): (options: AgentPromptOptions) => Promise<AgentPromptResult> {
|
|
402
|
+
const instance = useSanityInstance()
|
|
403
|
+
const {resource} = useNormalizedResourceOptions(resourceHandle ?? {})
|
|
404
|
+
return useCallback(
|
|
405
|
+
(options: AgentPromptOptions) => firstValueFrom(agentPrompt(instance, options, resource)),
|
|
406
|
+
[instance, resource],
|
|
407
|
+
)
|
|
399
408
|
}
|
|
400
409
|
|
|
401
410
|
/**
|
|
@@ -547,5 +556,13 @@ function patchAdapter(
|
|
|
547
556
|
*
|
|
548
557
|
* @category Agent Actions
|
|
549
558
|
*/
|
|
550
|
-
export
|
|
551
|
-
|
|
559
|
+
export function useAgentPatch(
|
|
560
|
+
resourceHandle?: ResourceHandle,
|
|
561
|
+
): (options: AgentPatchOptions) => Promise<AgentPatchResult> {
|
|
562
|
+
const instance = useSanityInstance()
|
|
563
|
+
const {resource} = useNormalizedResourceOptions(resourceHandle ?? {})
|
|
564
|
+
return useCallback(
|
|
565
|
+
(options: AgentPatchOptions) => firstValueFrom(agentPatch(instance, options, resource)),
|
|
566
|
+
[instance, resource],
|
|
567
|
+
)
|
|
568
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import {renderHook as reactRenderHook} from '@testing-library/react'
|
|
2
|
+
import {type ReactNode} from 'react'
|
|
3
|
+
import {describe, expect, it} from 'vitest'
|
|
4
|
+
|
|
5
|
+
import {renderHook} from '../../../test/test-utils'
|
|
6
|
+
import {ResourceProvider} from '../../context/ResourceProvider'
|
|
7
|
+
import {useResource} from './useResource'
|
|
8
|
+
|
|
9
|
+
describe('useResource', () => {
|
|
10
|
+
it('returns the resource from the instance config when no explicit resource is set', () => {
|
|
11
|
+
// test-utils wraps with ResourceProvider projectId="test" dataset="test"
|
|
12
|
+
const {result} = renderHook(() => useResource())
|
|
13
|
+
expect(result.current).toEqual({projectId: 'test', dataset: 'test'})
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('returns the explicit resource when ResourceProvider has a resource prop', () => {
|
|
17
|
+
const resource = {projectId: 'explicit-project', dataset: 'explicit-dataset'}
|
|
18
|
+
const {result} = reactRenderHook(() => useResource(), {
|
|
19
|
+
wrapper: ({children}: {children: ReactNode}) => (
|
|
20
|
+
<ResourceProvider resource={resource} fallback={null}>
|
|
21
|
+
{children}
|
|
22
|
+
</ResourceProvider>
|
|
23
|
+
),
|
|
24
|
+
})
|
|
25
|
+
expect(result.current).toEqual(resource)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('returns undefined when no resource or instance config is available', () => {
|
|
29
|
+
const {result} = reactRenderHook(() => useResource())
|
|
30
|
+
expect(result.current).toBeUndefined()
|
|
31
|
+
})
|
|
32
|
+
})
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import {type DocumentResource} from '@sanity/sdk'
|
|
2
|
+
|
|
3
|
+
import {useEffectiveContextResource} from '../helpers/useNormalizedResourceOptions'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Returns the currently active `DocumentResource` for the nearest resource context.
|
|
7
|
+
*
|
|
8
|
+
* Resolves in priority order:
|
|
9
|
+
* 1. A `resource` prop on the nearest `<ResourceProvider>`
|
|
10
|
+
* 2. The `projectId`/`dataset` from the current `SanityInstance` config
|
|
11
|
+
* 3. `undefined` when neither is available
|
|
12
|
+
*
|
|
13
|
+
* @public
|
|
14
|
+
* @category Platform
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```tsx
|
|
18
|
+
* const resource = useResource()
|
|
19
|
+
* console.log(resource?.projectId, resource?.dataset)
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export function useResource(): DocumentResource | undefined {
|
|
23
|
+
return useEffectiveContextResource()
|
|
24
|
+
}
|