@sanity/sdk-react 3.0.0-rc.0 → 3.0.0-rc.1
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 +103 -155
- package/dist/index.d.ts +1564 -682
- package/dist/index.js +1118 -612
- package/dist/index.js.map +1 -1
- package/package.json +51 -54
- package/src/_exports/index.ts +2 -4
- package/src/_exports/sdk-react.ts +26 -17
- package/src/components/SDKProvider.test.tsx +173 -32
- package/src/components/SDKProvider.tsx +87 -28
- package/src/components/SanityApp.test.tsx +68 -12
- package/src/components/SanityApp.tsx +94 -87
- package/src/components/auth/AuthBoundary.recovery.test.tsx +86 -0
- package/src/components/auth/AuthBoundary.test.tsx +26 -8
- package/src/components/auth/AuthBoundary.tsx +28 -6
- package/src/components/auth/DashboardAccessRequest.tsx +37 -0
- package/src/components/auth/LoginError.test.tsx +191 -5
- package/src/components/auth/LoginError.tsx +103 -59
- package/src/components/errors/ChunkLoadError.test.tsx +59 -0
- package/src/components/errors/ChunkLoadError.tsx +56 -0
- package/src/components/errors/CorsErrorComponent.tsx +2 -2
- package/src/components/errors/chunkReloadStorage.ts +57 -0
- package/src/config/handles.ts +29 -27
- package/src/constants.ts +5 -0
- package/src/context/ComlinkTokenRefresh.test.tsx +10 -27
- package/src/context/DefaultResourceContext.ts +3 -3
- package/src/context/OrganizationResourcesProvider.test.tsx +189 -0
- package/src/context/OrganizationResourcesProvider.tsx +111 -0
- package/src/context/PerspectiveContext.ts +3 -3
- package/src/context/ProjectContext.ts +15 -0
- package/src/context/ResourceProvider.test.tsx +72 -90
- package/src/context/ResourceProvider.tsx +59 -93
- package/src/context/SDKStudioContext.test.tsx +28 -33
- package/src/context/SanityInstanceProvider.test.tsx +100 -0
- package/src/context/SanityInstanceProvider.tsx +71 -0
- package/src/context/WorkbenchTokenRefresh.test.tsx +106 -0
- package/src/context/WorkbenchTokenRefresh.tsx +61 -0
- package/src/context/renderSanityApp.test.tsx +151 -49
- package/src/context/renderSanityApp.tsx +12 -8
- package/src/context/workbenchToken.ts +63 -0
- package/src/hooks/access/useCheckPermissions.test-d.ts +15 -0
- package/src/hooks/access/useCheckPermissions.test.tsx +53 -0
- package/src/hooks/access/useCheckPermissions.ts +24 -0
- package/src/hooks/agent/agentActions.test.tsx +1 -1
- package/src/hooks/agent/agentActions.ts +51 -71
- package/src/hooks/applications/useApplication.test-d.ts +30 -0
- package/src/hooks/applications/useApplication.ts +22 -0
- package/src/hooks/applications/useApplications.test-d.ts +31 -0
- package/src/hooks/applications/useApplications.ts +25 -0
- package/src/hooks/applications/useCreateUserApplication.test-d.ts +14 -0
- package/src/hooks/applications/useCreateUserApplication.ts +11 -0
- package/src/hooks/applications/useDeleteApplication.test-d.ts +12 -0
- package/src/hooks/applications/useDeleteApplication.ts +11 -0
- package/src/hooks/applications/useDeleteUserApplication.test-d.ts +14 -0
- package/src/hooks/applications/useDeleteUserApplication.ts +11 -0
- package/src/hooks/applications/useUpdateApplication.test-d.ts +12 -0
- package/src/hooks/applications/useUpdateApplication.ts +11 -0
- package/src/hooks/applications/useUpdateUserApplication.test-d.ts +14 -0
- package/src/hooks/applications/useUpdateUserApplication.ts +11 -0
- package/src/hooks/applications/useUserApplication.test-d.ts +11 -0
- package/src/hooks/applications/useUserApplication.ts +14 -0
- package/src/hooks/applications/useUserApplications.test-d.ts +11 -0
- package/src/hooks/applications/useUserApplications.ts +14 -0
- package/src/hooks/auth/useDashboardOrganizationId.test.tsx +2 -8
- package/src/hooks/auth/useVerifyOrgProjects.test.tsx +8 -32
- package/src/hooks/auth/useVerifyOrgProjects.tsx +13 -6
- package/src/hooks/client/useClient.test.tsx +1 -4
- package/src/hooks/client/useClient.ts +1 -0
- package/src/hooks/context/useResource.test.tsx +32 -0
- package/src/hooks/context/useResource.ts +24 -0
- package/src/hooks/context/useSanityInstance.test.tsx +72 -3
- package/src/hooks/context/useSanityInstance.ts +30 -8
- package/src/hooks/dashboard/useManageFavorite.test.tsx +110 -46
- package/src/hooks/dashboard/useManageFavorite.ts +10 -16
- package/src/hooks/dashboard/useNavigateToStudioDocument.test.ts +5 -7
- package/src/hooks/dashboard/useNavigateToStudioDocument.ts +2 -6
- package/src/hooks/dashboard/useRecordDocumentHistoryEvent.test.ts +0 -2
- package/src/hooks/dashboard/useRecordDocumentHistoryEvent.ts +1 -2
- package/src/hooks/dashboard/useWindowTitle.test.ts +244 -0
- package/src/hooks/dashboard/useWindowTitle.ts +114 -0
- package/src/hooks/datasets/useDatasets.test.tsx +116 -0
- package/src/hooks/datasets/useDatasets.ts +33 -13
- package/src/hooks/document/useApplyDocumentActions.test.tsx +48 -0
- package/src/hooks/document/useApplyDocumentActions.ts +66 -48
- package/src/hooks/document/useCreateDocument.test.tsx +83 -0
- package/src/hooks/document/useCreateDocument.ts +117 -0
- package/src/hooks/document/useDocument.test.tsx +37 -8
- package/src/hooks/document/useDocument.ts +131 -66
- package/src/hooks/document/useDocumentEvent.test.tsx +16 -4
- package/src/hooks/document/useDocumentEvent.ts +16 -10
- package/src/hooks/document/useDocumentPermissions.test.tsx +8 -7
- package/src/hooks/document/useDocumentPermissions.ts +71 -39
- package/src/hooks/document/useDocumentSyncStatus.test.ts +1 -0
- package/src/hooks/document/useDocumentSyncStatus.ts +13 -9
- package/src/hooks/document/useEditDocument.test.tsx +21 -14
- package/src/hooks/document/useEditDocument.ts +163 -17
- package/src/hooks/documents/useDocuments.test.tsx +64 -44
- package/src/hooks/documents/useDocuments.ts +36 -20
- package/src/hooks/helpers/createCallbackHook.test.tsx +13 -19
- package/src/hooks/helpers/createFetcherHook.test.tsx +180 -0
- package/src/hooks/helpers/createFetcherHook.ts +69 -0
- package/src/hooks/helpers/createMutationHook.test.tsx +125 -0
- package/src/hooks/helpers/createMutationHook.tsx +93 -0
- package/src/hooks/helpers/createStateSourceHook.test.tsx +10 -10
- package/src/hooks/helpers/createStateSourceHook.tsx +2 -1
- package/src/hooks/{document/useApplyDocumentActions.test.ts → helpers/useApplyActions.test.tsx} +92 -53
- package/src/hooks/helpers/useApplyActions.ts +68 -0
- package/src/hooks/helpers/useNormalizedResourceOptions.test.tsx +253 -0
- package/src/hooks/helpers/useNormalizedResourceOptions.ts +92 -50
- package/src/hooks/helpers/useResolvedProjectId.test.tsx +59 -0
- package/src/hooks/helpers/useResolvedProjectId.ts +35 -0
- package/src/hooks/helpers/useTrackHookUsage.ts +37 -0
- package/src/hooks/installations/useInstallation.test-d.ts +19 -0
- package/src/hooks/installations/useInstallation.ts +22 -0
- package/src/hooks/installations/useInstallations.test-d.ts +26 -0
- package/src/hooks/installations/useInstallations.ts +25 -0
- package/src/hooks/organizations/useOrganization.test-d.ts +60 -0
- package/src/hooks/organizations/useOrganization.test.ts +63 -0
- package/src/hooks/organizations/useOrganization.ts +34 -0
- package/src/hooks/organizations/useOrganizations.test-d.ts +68 -0
- package/src/hooks/organizations/useOrganizations.test.ts +63 -0
- package/src/hooks/organizations/useOrganizations.ts +39 -0
- package/src/hooks/paginatedDocuments/usePaginatedDocuments.test.tsx +34 -27
- package/src/hooks/paginatedDocuments/usePaginatedDocuments.ts +40 -22
- package/src/hooks/presence/usePresence.test.tsx +0 -15
- package/src/hooks/presence/usePresence.ts +9 -20
- package/src/hooks/preview/useDocumentPreview.test.tsx +0 -1
- package/src/hooks/preview/useDocumentPreview.tsx +10 -8
- package/src/hooks/projection/useDocumentProjection.test.tsx +78 -9
- package/src/hooks/projection/useDocumentProjection.ts +81 -2
- package/src/hooks/projects/useProject.test-d.ts +56 -0
- package/src/hooks/projects/useProject.test.tsx +120 -0
- package/src/hooks/projects/useProject.ts +42 -43
- package/src/hooks/projects/useProjects.test-d.ts +60 -0
- package/src/hooks/projects/useProjects.test.ts +45 -98
- package/src/hooks/projects/useProjects.ts +19 -26
- package/src/hooks/query/useQuery.test.tsx +6 -5
- package/src/hooks/query/useQuery.ts +91 -9
- package/src/hooks/releases/useActiveReleases.test.tsx +13 -7
- package/src/hooks/releases/useActiveReleases.ts +28 -24
- package/src/hooks/releases/useAllReleases.test.tsx +93 -0
- package/src/hooks/releases/useAllReleases.ts +62 -0
- package/src/hooks/releases/useApplyReleaseActions.test.tsx +66 -0
- package/src/hooks/releases/useApplyReleaseActions.ts +82 -0
- package/src/hooks/releases/usePerspective.test.tsx +18 -10
- package/src/hooks/releases/usePerspective.ts +13 -17
- package/src/hooks/users/useUser.test.tsx +3 -9
- package/src/hooks/users/useUser.ts +2 -0
- package/src/hooks/users/useUsers.test.tsx +103 -7
- package/src/hooks/users/useUsers.ts +37 -3
- package/src/utils/resolveOrgResources.test.ts +111 -0
- package/src/utils/resolveOrgResources.ts +69 -0
- package/src/hooks/context/useDefaultResource.test.tsx +0 -25
- package/src/hooks/context/useDefaultResource.ts +0 -30
- package/src/hooks/dashboard/useDispatchIntent.test.ts +0 -254
- package/src/hooks/dashboard/useDispatchIntent.ts +0 -157
- package/src/hooks/dashboard/utils/useResourceIdFromDocumentHandle.test.ts +0 -107
- package/src/hooks/dashboard/utils/useResourceIdFromDocumentHandle.ts +0 -39
- package/src/hooks/datasets/useDatasets.test.ts +0 -66
- package/src/hooks/helpers/useNormalizedResourceOptions.test.ts +0 -65
- package/src/hooks/projects/useProject.test.ts +0 -79
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import {SDK_CHANNEL_NAME, SDK_NODE_NAME} from '@sanity/message-protocol'
|
|
2
|
+
import {useEffect, useState} from 'react'
|
|
3
|
+
|
|
4
|
+
import {useWindowConnection} from '../comlink/useWindowConnection'
|
|
5
|
+
|
|
6
|
+
interface ContextResource {
|
|
7
|
+
type: string
|
|
8
|
+
title?: string
|
|
9
|
+
manifest?: {
|
|
10
|
+
title?: string
|
|
11
|
+
} | null
|
|
12
|
+
activeDeployment?: {
|
|
13
|
+
manifest?: {
|
|
14
|
+
title?: string
|
|
15
|
+
} | null
|
|
16
|
+
} | null
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface ContextResponse {
|
|
20
|
+
context: {
|
|
21
|
+
resource: ContextResource
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function resolveAppTitle(resource: ContextResource): string | undefined {
|
|
26
|
+
return resource.manifest?.title || resource.activeDeployment?.manifest?.title || resource.title
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Sets the browser's document title, automatically including the app's name
|
|
31
|
+
* from the manifest.
|
|
32
|
+
*
|
|
33
|
+
* This follows the same convention as Sanity Studio workspaces, where the
|
|
34
|
+
* workspace name is always present in the title:
|
|
35
|
+
*
|
|
36
|
+
* - With a view title: `<viewTitle> | <appTitle>`
|
|
37
|
+
* - Without a view title: `<appTitle>`
|
|
38
|
+
*
|
|
39
|
+
* The Sanity dashboard appends `| Sanity` to produce the final browser tab title.
|
|
40
|
+
*
|
|
41
|
+
* @param viewTitle - An optional view-specific title to prepend to the app title.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```tsx
|
|
45
|
+
* import {useWindowTitle} from '@sanity/sdk-react'
|
|
46
|
+
*
|
|
47
|
+
* function MoviesList() {
|
|
48
|
+
* useWindowTitle('Movies')
|
|
49
|
+
* return <div>...</div>
|
|
50
|
+
* }
|
|
51
|
+
*
|
|
52
|
+
* // Browser tab: "Movies | My App | Sanity"
|
|
53
|
+
* ```
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```tsx
|
|
57
|
+
* // Call without arguments to show just the app title
|
|
58
|
+
* function AppRoot() {
|
|
59
|
+
* useWindowTitle()
|
|
60
|
+
* return <Outlet />
|
|
61
|
+
* }
|
|
62
|
+
*
|
|
63
|
+
* // Browser tab: "My App | Sanity"
|
|
64
|
+
* ```
|
|
65
|
+
*
|
|
66
|
+
* @public
|
|
67
|
+
*/
|
|
68
|
+
export function useWindowTitle(viewTitle?: string): void {
|
|
69
|
+
const [appTitle, setAppTitle] = useState<string | null>(null)
|
|
70
|
+
|
|
71
|
+
const {fetch} = useWindowConnection({
|
|
72
|
+
name: SDK_NODE_NAME,
|
|
73
|
+
connectTo: SDK_CHANNEL_NAME,
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
if (!fetch) return
|
|
78
|
+
|
|
79
|
+
const controller = new AbortController()
|
|
80
|
+
|
|
81
|
+
async function fetchAppTitle(signal: AbortSignal) {
|
|
82
|
+
try {
|
|
83
|
+
const data = await fetch<ContextResponse>('dashboard/v1/context', undefined, {signal})
|
|
84
|
+
// Local development resources do not have a registered title or deployment manifest.
|
|
85
|
+
// In that case, use the title rendered into the iframe HTML by the Sanity CLI.
|
|
86
|
+
const title = resolveAppTitle(data.context.resource) || document.title
|
|
87
|
+
if (title) {
|
|
88
|
+
setAppTitle(title)
|
|
89
|
+
}
|
|
90
|
+
} catch (err: unknown) {
|
|
91
|
+
if (err instanceof Error && err.name === 'AbortError') return
|
|
92
|
+
// eslint-disable-next-line no-console
|
|
93
|
+
console.error('Failed to fetch app title from dashboard context:', err)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
fetchAppTitle(controller.signal)
|
|
98
|
+
|
|
99
|
+
return () => {
|
|
100
|
+
controller.abort()
|
|
101
|
+
}
|
|
102
|
+
}, [fetch])
|
|
103
|
+
|
|
104
|
+
useEffect(() => {
|
|
105
|
+
if (!appTitle) return
|
|
106
|
+
|
|
107
|
+
const previous = document.title
|
|
108
|
+
document.title = viewTitle ? `${viewTitle} | ${appTitle}` : appTitle
|
|
109
|
+
|
|
110
|
+
return () => {
|
|
111
|
+
document.title = previous
|
|
112
|
+
}
|
|
113
|
+
}, [viewTitle, appTitle])
|
|
114
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import {type DatasetsResponse} from '@sanity/client'
|
|
2
|
+
import {
|
|
3
|
+
createSanityInstance,
|
|
4
|
+
getDatasetsState,
|
|
5
|
+
resolveDatasets,
|
|
6
|
+
type StateSource,
|
|
7
|
+
} from '@sanity/sdk'
|
|
8
|
+
import {type ReactNode} from 'react'
|
|
9
|
+
import {type Observable} from 'rxjs'
|
|
10
|
+
import {beforeEach, describe, expect, it, vi} from 'vitest'
|
|
11
|
+
|
|
12
|
+
import {renderHook} from '../../../test/test-utils'
|
|
13
|
+
import {ResourceProvider} from '../../context/ResourceProvider'
|
|
14
|
+
import {SanityInstanceContext} from '../../context/SanityInstanceContext'
|
|
15
|
+
import {useDatasets} from './useDatasets'
|
|
16
|
+
|
|
17
|
+
vi.mock('@sanity/sdk', async (importOriginal) => {
|
|
18
|
+
const original = await importOriginal<typeof import('@sanity/sdk')>()
|
|
19
|
+
return {...original, getDatasetsState: vi.fn(), resolveDatasets: vi.fn()}
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
const stateSource = (
|
|
23
|
+
current: DatasetsResponse | undefined,
|
|
24
|
+
): StateSource<DatasetsResponse | undefined> =>
|
|
25
|
+
({
|
|
26
|
+
getCurrent: vi.fn(() => current),
|
|
27
|
+
subscribe: vi.fn(),
|
|
28
|
+
get observable(): Observable<unknown> {
|
|
29
|
+
throw new Error('Not implemented')
|
|
30
|
+
},
|
|
31
|
+
}) as unknown as StateSource<DatasetsResponse | undefined>
|
|
32
|
+
|
|
33
|
+
const sanityInstance = expect.objectContaining({config: expect.any(Object)})
|
|
34
|
+
|
|
35
|
+
describe('useDatasets', () => {
|
|
36
|
+
beforeEach(() => {
|
|
37
|
+
vi.clearAllMocks()
|
|
38
|
+
vi.mocked(getDatasetsState).mockReturnValue(stateSource([] as unknown as DatasetsResponse))
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('resolves the projectId from the instance config resource', () => {
|
|
42
|
+
// test-utils wraps with ResourceProvider projectId="test" dataset="test".
|
|
43
|
+
renderHook(() => useDatasets())
|
|
44
|
+
expect(getDatasetsState).toHaveBeenCalledWith(
|
|
45
|
+
sanityInstance,
|
|
46
|
+
expect.objectContaining({projectId: 'test'}),
|
|
47
|
+
)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('lets an explicit projectId override the ambient resource', () => {
|
|
51
|
+
renderHook(() => useDatasets({projectId: 'explicit-project'}))
|
|
52
|
+
expect(getDatasetsState).toHaveBeenCalledWith(
|
|
53
|
+
sanityInstance,
|
|
54
|
+
expect.objectContaining({projectId: 'explicit-project'}),
|
|
55
|
+
)
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('resolves the projectId from an explicit resource when the config has none', () => {
|
|
59
|
+
renderHook(() => useDatasets(), {
|
|
60
|
+
wrapper: ({children}: {children: ReactNode}) => (
|
|
61
|
+
<ResourceProvider
|
|
62
|
+
resource={{projectId: 'resource-project', dataset: 'production'}}
|
|
63
|
+
fallback={null}
|
|
64
|
+
>
|
|
65
|
+
{children}
|
|
66
|
+
</ResourceProvider>
|
|
67
|
+
),
|
|
68
|
+
})
|
|
69
|
+
expect(getDatasetsState).toHaveBeenCalledWith(
|
|
70
|
+
sanityInstance,
|
|
71
|
+
expect.objectContaining({projectId: 'resource-project'}),
|
|
72
|
+
)
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it('resolves a dataset-less projectId config for project-scoped use', () => {
|
|
76
|
+
renderHook(() => useDatasets(), {
|
|
77
|
+
wrapper: ({children}: {children: ReactNode}) => (
|
|
78
|
+
<ResourceProvider projectId="config-project" fallback={null}>
|
|
79
|
+
{children}
|
|
80
|
+
</ResourceProvider>
|
|
81
|
+
),
|
|
82
|
+
})
|
|
83
|
+
// A dataset-less config can't form a DatasetResource; the projectId is carried
|
|
84
|
+
// via ProjectContext and injected so project-scoped reads still resolve it.
|
|
85
|
+
expect(getDatasetsState).toHaveBeenCalledWith(
|
|
86
|
+
sanityInstance,
|
|
87
|
+
expect.objectContaining({projectId: 'config-project'}),
|
|
88
|
+
)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('resolves a bare projectId from a ResourceProvider when the parent instance has no config', () => {
|
|
92
|
+
// An instance with no project/dataset config and no ambient
|
|
93
|
+
// resource, then a projectId-only ResourceProvider.
|
|
94
|
+
const emptyInstance = createSanityInstance({})
|
|
95
|
+
renderHook(() => useDatasets(), {
|
|
96
|
+
wrapper: ({children}: {children: ReactNode}) => (
|
|
97
|
+
<SanityInstanceContext.Provider value={emptyInstance}>
|
|
98
|
+
<ResourceProvider projectId="bare-project" fallback={null}>
|
|
99
|
+
{children}
|
|
100
|
+
</ResourceProvider>
|
|
101
|
+
</SanityInstanceContext.Provider>
|
|
102
|
+
),
|
|
103
|
+
})
|
|
104
|
+
expect(getDatasetsState).toHaveBeenCalledWith(
|
|
105
|
+
emptyInstance,
|
|
106
|
+
expect.objectContaining({projectId: 'bare-project'}),
|
|
107
|
+
)
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it('suspends via resolveDatasets until dataset data is available', () => {
|
|
111
|
+
vi.mocked(getDatasetsState).mockReturnValue(stateSource(undefined))
|
|
112
|
+
vi.mocked(resolveDatasets).mockReturnValue(new Promise(() => {}))
|
|
113
|
+
renderHook(() => useDatasets())
|
|
114
|
+
expect(resolveDatasets).toHaveBeenCalled()
|
|
115
|
+
})
|
|
116
|
+
})
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import {type DatasetsResponse} from '@sanity/client'
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
getDatasetsState,
|
|
4
|
+
type ProjectHandle,
|
|
5
|
+
resolveDatasets,
|
|
6
|
+
type SanityInstance,
|
|
7
|
+
type StateSource,
|
|
8
|
+
} from '@sanity/sdk'
|
|
3
9
|
|
|
4
10
|
import {createStateSourceHook} from '../helpers/createStateSourceHook'
|
|
11
|
+
import {useResolvedProjectId} from '../helpers/useResolvedProjectId'
|
|
5
12
|
|
|
6
13
|
type UseDatasets = {
|
|
7
14
|
/**
|
|
@@ -9,12 +16,13 @@ type UseDatasets = {
|
|
|
9
16
|
* Returns metadata for each dataset the current user has access to.
|
|
10
17
|
*
|
|
11
18
|
* @category Datasets
|
|
12
|
-
* @param options -
|
|
13
|
-
*
|
|
19
|
+
* @param options - Optional project/resource to read datasets for. Defaults to
|
|
20
|
+
* the resource named in `ResourceProvider`/`SDKProvider`.
|
|
21
|
+
* @returns The metadata for your the datasets
|
|
14
22
|
*
|
|
15
23
|
* @example
|
|
16
24
|
* ```tsx
|
|
17
|
-
* const datasets = useDatasets(
|
|
25
|
+
* const datasets = useDatasets()
|
|
18
26
|
*
|
|
19
27
|
* return (
|
|
20
28
|
* <select>
|
|
@@ -25,20 +33,32 @@ type UseDatasets = {
|
|
|
25
33
|
* )
|
|
26
34
|
* ```
|
|
27
35
|
*
|
|
36
|
+
* @remarks
|
|
37
|
+
* The `projectId` is resolved in order from:
|
|
38
|
+
* 1. an explicit `projectId` option
|
|
39
|
+
* 2. A legacy ProjectContext (e.g. a `<ResourceProvider projectId="…">` with no dataset), then
|
|
40
|
+
* 3. The active resource (`ResourceProvider`/`SDKProvider`)
|
|
41
|
+
* 4. `instance.config`.
|
|
28
42
|
*/
|
|
29
|
-
(options
|
|
43
|
+
(options?: ProjectHandle): DatasetsResponse
|
|
30
44
|
}
|
|
31
45
|
|
|
32
|
-
|
|
33
|
-
* @public
|
|
34
|
-
* @function
|
|
35
|
-
*/
|
|
36
|
-
export const useDatasets: UseDatasets = createStateSourceHook({
|
|
46
|
+
const useDatasetsBase = createStateSourceHook({
|
|
37
47
|
getState: getDatasetsState as (
|
|
38
48
|
instance: SanityInstance,
|
|
39
|
-
|
|
49
|
+
projectHandle?: ProjectHandle,
|
|
40
50
|
) => StateSource<DatasetsResponse>,
|
|
41
|
-
shouldSuspend: (instance,
|
|
42
|
-
|
|
51
|
+
shouldSuspend: (instance, projectHandle?: ProjectHandle) =>
|
|
52
|
+
// remove `undefined` since we're suspending when that is the case
|
|
53
|
+
getDatasetsState(instance, projectHandle).getCurrent() === undefined,
|
|
43
54
|
suspender: resolveDatasets,
|
|
44
55
|
})
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @public
|
|
59
|
+
* @function
|
|
60
|
+
*/
|
|
61
|
+
export const useDatasets: UseDatasets = (options) => {
|
|
62
|
+
const projectId = useResolvedProjectId(options)
|
|
63
|
+
return useDatasetsBase(projectId ? {...options, projectId} : options)
|
|
64
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import {applyDocumentActions, createSanityInstance} from '@sanity/sdk'
|
|
2
|
+
import {describe, it} from 'vitest'
|
|
3
|
+
|
|
4
|
+
import {renderHook} from '../../../test/test-utils'
|
|
5
|
+
import {useSanityInstance} from '../context/useSanityInstance'
|
|
6
|
+
import {useApplyDocumentActions} from './useApplyDocumentActions'
|
|
7
|
+
|
|
8
|
+
// Resource resolution, mismatch detection, and context fallback are covered
|
|
9
|
+
// by hooks/helpers/useApplyActions.test.tsx — both this hook and
|
|
10
|
+
// useApplyReleaseActions are typed wrappers over that shared implementation.
|
|
11
|
+
// These tests just verify the wrapper forwards document actions through.
|
|
12
|
+
|
|
13
|
+
vi.mock('@sanity/sdk', async (importOriginal) => {
|
|
14
|
+
const original = await importOriginal<typeof import('@sanity/sdk')>()
|
|
15
|
+
return {...original, applyDocumentActions: vi.fn()}
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
vi.mock('../context/useSanityInstance')
|
|
19
|
+
|
|
20
|
+
const instance = createSanityInstance({projectId: 'p', dataset: 'd'})
|
|
21
|
+
|
|
22
|
+
describe('useApplyDocumentActions', () => {
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
vi.resetAllMocks()
|
|
25
|
+
vi.mocked(useSanityInstance).mockReturnValueOnce(instance)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('forwards a document action to applyDocumentActions with the resolved resource', () => {
|
|
29
|
+
const {result} = renderHook(() => useApplyDocumentActions())
|
|
30
|
+
result.current({
|
|
31
|
+
type: 'document.edit',
|
|
32
|
+
documentType: 'post',
|
|
33
|
+
documentId: 'abc',
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
expect(applyDocumentActions).toHaveBeenCalledExactlyOnceWith(instance, {
|
|
37
|
+
actions: [
|
|
38
|
+
{
|
|
39
|
+
type: 'document.edit',
|
|
40
|
+
documentType: 'post',
|
|
41
|
+
documentId: 'abc',
|
|
42
|
+
resource: {projectId: 'test', dataset: 'test'},
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
resource: {projectId: 'test', dataset: 'test'},
|
|
46
|
+
})
|
|
47
|
+
})
|
|
48
|
+
})
|
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
import {type ActionsResult,
|
|
2
|
-
import {
|
|
1
|
+
import {type ActionsResult, type DocumentAction} from '@sanity/sdk'
|
|
2
|
+
import {type SanityDocument} from 'groq'
|
|
3
3
|
|
|
4
4
|
import {type ResourceHandle} from '../../config/handles'
|
|
5
|
-
import {
|
|
6
|
-
import {ResourcesContext} from '../../context/ResourcesContext'
|
|
7
|
-
import {useSanityInstance} from '../context/useSanityInstance'
|
|
8
|
-
import {normalizeResourceOptions} from '../helpers/useNormalizedResourceOptions'
|
|
5
|
+
import {useApplyActions} from '../helpers/useApplyActions'
|
|
9
6
|
// this import is used in an `{@link useEditDocument}`
|
|
10
|
-
// eslint-disable-next-line
|
|
7
|
+
// eslint-disable-next-line import-x/consistent-type-specifier-style
|
|
11
8
|
import type {useEditDocument} from './useEditDocument'
|
|
12
9
|
|
|
13
10
|
/**
|
|
@@ -23,7 +20,7 @@ interface UseApplyDocumentActions {
|
|
|
23
20
|
| DocumentAction<TDocumentType, TDataset, TProjectId>
|
|
24
21
|
| DocumentAction<TDocumentType, TDataset, TProjectId>[],
|
|
25
22
|
options?: ResourceHandle,
|
|
26
|
-
) => Promise<ActionsResult
|
|
23
|
+
) => Promise<ActionsResult<SanityDocument<TDocumentType, `${TProjectId}.${TDataset}`>>>
|
|
27
24
|
}
|
|
28
25
|
|
|
29
26
|
/**
|
|
@@ -148,46 +145,67 @@ interface UseApplyDocumentActions {
|
|
|
148
145
|
* return <button onClick={handleCreateArticle}>Create Article</button>
|
|
149
146
|
* }
|
|
150
147
|
* ```
|
|
148
|
+
*
|
|
149
|
+
* @example Create a new document in a release
|
|
150
|
+
* ```tsx
|
|
151
|
+
* import {
|
|
152
|
+
* createDocument,
|
|
153
|
+
* createDocumentHandle,
|
|
154
|
+
* useApplyDocumentActions
|
|
155
|
+
* } from '@sanity/sdk-react'
|
|
156
|
+
*
|
|
157
|
+
* function CreateArticleButton() {
|
|
158
|
+
* const apply = useApplyDocumentActions()
|
|
159
|
+
*
|
|
160
|
+
* const handleCreateArticle = () => {
|
|
161
|
+
* // Use any valid document ID — not the internal `versions.<releaseName>.<id>` format or "drafts.<id>" format.
|
|
162
|
+
* // New documents must be explicitly created with `createDocument` to become part of a release.
|
|
163
|
+
* const newDocHandle = createDocumentHandle({
|
|
164
|
+
* documentId: crypto.randomUUID(), // or the existing document ID you want to make a release version of
|
|
165
|
+
* documentType: 'article',
|
|
166
|
+
* perspective: {releaseName: 'summer-drop'},
|
|
167
|
+
* })
|
|
168
|
+
*
|
|
169
|
+
* apply(
|
|
170
|
+
* createDocument(newDocHandle, {
|
|
171
|
+
* title: 'New Article',
|
|
172
|
+
* author: 'John Doe',
|
|
173
|
+
* publishedAt: new Date().toISOString(),
|
|
174
|
+
* })
|
|
175
|
+
* )
|
|
176
|
+
* }
|
|
177
|
+
*
|
|
178
|
+
* return <button onClick={handleCreateArticle}>Create Article</button>
|
|
179
|
+
* }
|
|
180
|
+
* ```
|
|
181
|
+
*
|
|
182
|
+
* @example Edit an existing document in a release
|
|
183
|
+
* ```tsx
|
|
184
|
+
* import {
|
|
185
|
+
* editDocument,
|
|
186
|
+
* createDocumentHandle,
|
|
187
|
+
* useApplyDocumentActions
|
|
188
|
+
* } from '@sanity/sdk-react'
|
|
189
|
+
*
|
|
190
|
+
* function EditArticleInReleaseButton({documentId}: {documentId: string}) {
|
|
191
|
+
* const apply = useApplyDocumentActions()
|
|
192
|
+
*
|
|
193
|
+
* const handleEdit = () => {
|
|
194
|
+
* // Pass the document's regular ID — not `versions.<releaseName>.<id>`.
|
|
195
|
+
* // Documents that already have a version in the release can be edited directly with `editDocument`.
|
|
196
|
+
* const docHandle = createDocumentHandle({
|
|
197
|
+
* documentId,
|
|
198
|
+
* documentType: 'article',
|
|
199
|
+
* perspective: {releaseName: 'summer-drop'},
|
|
200
|
+
* })
|
|
201
|
+
*
|
|
202
|
+
* apply(editDocument(docHandle, {title: 'Updated for release'}))
|
|
203
|
+
* }
|
|
204
|
+
*
|
|
205
|
+
* return <button onClick={handleEdit}>Edit in Release</button>
|
|
206
|
+
* }
|
|
207
|
+
* ```
|
|
151
208
|
*/
|
|
152
209
|
export const useApplyDocumentActions: UseApplyDocumentActions = () => {
|
|
153
|
-
|
|
154
|
-
const resources = useContext(ResourcesContext)
|
|
155
|
-
const contextResource = useContext(ResourceContext)
|
|
156
|
-
|
|
157
|
-
return (actionOrActions, options) => {
|
|
158
|
-
const actions = Array.isArray(actionOrActions) ? actionOrActions : [actionOrActions]
|
|
159
|
-
const optionsResource = options
|
|
160
|
-
? normalizeResourceOptions(options, resources, contextResource).resource
|
|
161
|
-
: undefined
|
|
162
|
-
|
|
163
|
-
const normalizedActions = actions.map((action) =>
|
|
164
|
-
normalizeResourceOptions(action, resources, contextResource),
|
|
165
|
-
)
|
|
166
|
-
let resource
|
|
167
|
-
|
|
168
|
-
for (const action of normalizedActions) {
|
|
169
|
-
if (!resource) resource = action.resource
|
|
170
|
-
if (action.resource !== resource) {
|
|
171
|
-
throw new Error(
|
|
172
|
-
`Mismatched resources found in actions. All actions must belong to the same resource. Found "${JSON.stringify(action.resource)}" but expected "${JSON.stringify(resource)}".`,
|
|
173
|
-
)
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
if (optionsResource && resource && optionsResource !== resource) {
|
|
178
|
-
throw new Error(
|
|
179
|
-
`Mismatched resources found in actions. Found top-level resource "${JSON.stringify(optionsResource)}" but expected resource from action handles "${JSON.stringify(resource)}".`,
|
|
180
|
-
)
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const effectiveResource = resource ?? optionsResource
|
|
184
|
-
if (!effectiveResource) {
|
|
185
|
-
throw new Error('No resource found. Provide a resource via the action handle or context.')
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
return applyDocumentActions(instance, {
|
|
189
|
-
actions,
|
|
190
|
-
resource: effectiveResource,
|
|
191
|
-
})
|
|
192
|
-
}
|
|
210
|
+
return useApplyActions() as ReturnType<UseApplyDocumentActions>
|
|
193
211
|
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import {createDocument} from '@sanity/sdk'
|
|
2
|
+
import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest'
|
|
3
|
+
|
|
4
|
+
import {renderHook} from '../../../test/test-utils'
|
|
5
|
+
import {useApplyDocumentActions} from './useApplyDocumentActions'
|
|
6
|
+
import {useCreateDocument} from './useCreateDocument'
|
|
7
|
+
|
|
8
|
+
vi.mock('./useApplyDocumentActions', () => ({
|
|
9
|
+
useApplyDocumentActions: vi.fn(),
|
|
10
|
+
}))
|
|
11
|
+
|
|
12
|
+
const typeHandle = {
|
|
13
|
+
documentType: 'book',
|
|
14
|
+
projectId: 'test',
|
|
15
|
+
dataset: 'test',
|
|
16
|
+
} as const
|
|
17
|
+
|
|
18
|
+
describe('useCreateDocument hook', () => {
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
vi.clearAllMocks()
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
vi.restoreAllMocks()
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('applies a createDocument action with a generated id and initial values', async () => {
|
|
28
|
+
vi.spyOn(crypto, 'randomUUID').mockReturnValue('00000000-0000-0000-0000-000000000000')
|
|
29
|
+
const apply = vi.fn().mockResolvedValue({transactionId: 'tx1'})
|
|
30
|
+
vi.mocked(useApplyDocumentActions).mockReturnValue(apply)
|
|
31
|
+
|
|
32
|
+
const {result} = renderHook(() => useCreateDocument(typeHandle))
|
|
33
|
+
const handle = await result.current({title: 'New Book'})
|
|
34
|
+
|
|
35
|
+
expect(apply).toHaveBeenCalledWith(
|
|
36
|
+
createDocument(
|
|
37
|
+
{...typeHandle, documentId: '00000000-0000-0000-0000-000000000000'},
|
|
38
|
+
{
|
|
39
|
+
title: 'New Book',
|
|
40
|
+
},
|
|
41
|
+
),
|
|
42
|
+
)
|
|
43
|
+
expect(handle).toEqual({...typeHandle, documentId: '00000000-0000-0000-0000-000000000000'})
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('returns a handle carrying the generated id', async () => {
|
|
47
|
+
vi.spyOn(crypto, 'randomUUID').mockReturnValue('11111111-1111-1111-1111-111111111111')
|
|
48
|
+
const apply = vi.fn().mockResolvedValue({transactionId: 'tx2'})
|
|
49
|
+
vi.mocked(useApplyDocumentActions).mockReturnValue(apply)
|
|
50
|
+
|
|
51
|
+
const {result} = renderHook(() => useCreateDocument(typeHandle))
|
|
52
|
+
const handle = await result.current()
|
|
53
|
+
|
|
54
|
+
expect(handle.documentId).toBe('11111111-1111-1111-1111-111111111111')
|
|
55
|
+
expect(handle.documentType).toBe('book')
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('uses the documentId supplied on the handle instead of generating one', async () => {
|
|
59
|
+
const apply = vi.fn().mockResolvedValue({transactionId: 'tx3'})
|
|
60
|
+
vi.mocked(useApplyDocumentActions).mockReturnValue(apply)
|
|
61
|
+
|
|
62
|
+
const {result} = renderHook(() => useCreateDocument({...typeHandle, documentId: 'fixed-id'}))
|
|
63
|
+
const handle = await result.current()
|
|
64
|
+
|
|
65
|
+
expect(handle.documentId).toBe('fixed-id')
|
|
66
|
+
expect(apply).toHaveBeenCalledWith(
|
|
67
|
+
createDocument({...typeHandle, documentId: 'fixed-id'}, undefined),
|
|
68
|
+
)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('uses a per-call documentId override over the handle id', async () => {
|
|
72
|
+
const apply = vi.fn().mockResolvedValue({transactionId: 'tx4'})
|
|
73
|
+
vi.mocked(useApplyDocumentActions).mockReturnValue(apply)
|
|
74
|
+
|
|
75
|
+
const {result} = renderHook(() => useCreateDocument({...typeHandle, documentId: 'handle-id'}))
|
|
76
|
+
const handle = await result.current({title: 'Override'}, {documentId: 'override-id'})
|
|
77
|
+
|
|
78
|
+
expect(handle.documentId).toBe('override-id')
|
|
79
|
+
expect(apply).toHaveBeenCalledWith(
|
|
80
|
+
createDocument({...typeHandle, documentId: 'override-id'}, {title: 'Override'}),
|
|
81
|
+
)
|
|
82
|
+
})
|
|
83
|
+
})
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import {createDocument} from '@sanity/sdk'
|
|
2
|
+
import {type SanityDocument} from 'groq'
|
|
3
|
+
|
|
4
|
+
import {type DocumentHandle, type DocumentTypeHandle} from '../../config/handles'
|
|
5
|
+
import {useSanityInstance} from '../context/useSanityInstance'
|
|
6
|
+
import {trackHookUsage} from '../helpers/useTrackHookUsage'
|
|
7
|
+
import {useApplyDocumentActions} from './useApplyDocumentActions'
|
|
8
|
+
|
|
9
|
+
type IgnoredKey = '_id' | '_type' | '_rev' | '_createdAt' | '_updatedAt'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Optional per-call overrides for {@link useCreateDocument}'s create function.
|
|
13
|
+
* @public
|
|
14
|
+
*/
|
|
15
|
+
export interface CreateDocumentOverrides {
|
|
16
|
+
/**
|
|
17
|
+
* Use this document ID instead of generating one. Overrides any `documentId`
|
|
18
|
+
* supplied on the handle passed to `useCreateDocument`.
|
|
19
|
+
*/
|
|
20
|
+
documentId?: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Overload 1: Typegen — infers the document shape from your schema.
|
|
24
|
+
/**
|
|
25
|
+
* @public
|
|
26
|
+
* Create a new document, relying on Typegen for the initial-value type.
|
|
27
|
+
*
|
|
28
|
+
* @param options - A document-type handle including `documentType`, an optional `documentId`, and optionally `projectId`/`dataset`/`perspective`.
|
|
29
|
+
* @returns A function that creates the document. It accepts optional initial field values and an optional `{documentId}` override,
|
|
30
|
+
* and resolves to the {@link DocumentHandle} of the created document (carrying the generated or supplied id).
|
|
31
|
+
*/
|
|
32
|
+
export function useCreateDocument<
|
|
33
|
+
TDocumentType extends string = string,
|
|
34
|
+
TDataset extends string = string,
|
|
35
|
+
TProjectId extends string = string,
|
|
36
|
+
>(
|
|
37
|
+
options: DocumentTypeHandle<TDocumentType, TDataset, TProjectId>,
|
|
38
|
+
): (
|
|
39
|
+
initialValue?: Partial<
|
|
40
|
+
Omit<SanityDocument<TDocumentType, `${TProjectId}.${TDataset}`>, IgnoredKey>
|
|
41
|
+
>,
|
|
42
|
+
overrides?: CreateDocumentOverrides,
|
|
43
|
+
) => Promise<DocumentHandle<TDocumentType, TDataset, TProjectId>>
|
|
44
|
+
|
|
45
|
+
// Overload 2: Explicit type `TData`.
|
|
46
|
+
/**
|
|
47
|
+
* @public
|
|
48
|
+
* Create a new document with an explicit type `TData`.
|
|
49
|
+
*
|
|
50
|
+
* @param options - A document-type handle including `documentType` and optionally `projectId`/`dataset`/`perspective`.
|
|
51
|
+
* @returns A function that creates the document. It accepts optional initial field values (typed against `TData`) and an
|
|
52
|
+
* optional `{documentId}` override, and resolves to the {@link DocumentHandle} of the created document.
|
|
53
|
+
*/
|
|
54
|
+
export function useCreateDocument<TData extends Record<string, unknown>>(
|
|
55
|
+
options: DocumentTypeHandle,
|
|
56
|
+
): (
|
|
57
|
+
initialValue?: Partial<Omit<TData, IgnoredKey>>,
|
|
58
|
+
overrides?: CreateDocumentOverrides,
|
|
59
|
+
) => Promise<DocumentHandle>
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @public
|
|
63
|
+
* Provides a function to create a new document and returns its handle.
|
|
64
|
+
*
|
|
65
|
+
* @category Documents
|
|
66
|
+
* @remarks
|
|
67
|
+
* This is the create counterpart to {@link useEditDocument}. It wraps
|
|
68
|
+
* {@link useApplyDocumentActions} and the `createDocument` action for the common
|
|
69
|
+
* single-document case, so you don't have to assemble the action by hand.
|
|
70
|
+
*
|
|
71
|
+
* It handles the document ID for you: if you don't supply one (on the handle or
|
|
72
|
+
* via the per-call `{documentId}` override), a UUID is generated. Either way the
|
|
73
|
+
* returned {@link DocumentHandle} carries that id, ready to pass to
|
|
74
|
+
* {@link useDocument}, {@link useEditDocument}, or your router.
|
|
75
|
+
*
|
|
76
|
+
* Unlike {@link useEditDocument}, this hook does not read existing document state,
|
|
77
|
+
* so it never suspends.
|
|
78
|
+
*
|
|
79
|
+
* For atomic create-and-publish, or for creating several documents in a single
|
|
80
|
+
* transaction, use {@link useApplyDocumentActions} with the `createDocument` and
|
|
81
|
+
* `publishDocument` action creators directly.
|
|
82
|
+
*
|
|
83
|
+
* @example Create a document and navigate to it
|
|
84
|
+
* ```tsx
|
|
85
|
+
* import {useCreateDocument} from '@sanity/sdk-react'
|
|
86
|
+
* import {useNavigate} from 'react-router-dom'
|
|
87
|
+
*
|
|
88
|
+
* function CreateArticleButton() {
|
|
89
|
+
* const createArticle = useCreateDocument({documentType: 'article'})
|
|
90
|
+
* const navigate = useNavigate()
|
|
91
|
+
*
|
|
92
|
+
* const handleClick = async () => {
|
|
93
|
+
* const handle = await createArticle({title: 'New Article'})
|
|
94
|
+
* navigate(`/articles/${handle.documentId}`)
|
|
95
|
+
* }
|
|
96
|
+
*
|
|
97
|
+
* return <button onClick={handleClick}>Create Article</button>
|
|
98
|
+
* }
|
|
99
|
+
* ```
|
|
100
|
+
*/
|
|
101
|
+
export function useCreateDocument(
|
|
102
|
+
options: DocumentTypeHandle,
|
|
103
|
+
): (
|
|
104
|
+
initialValue?: Record<string, unknown>,
|
|
105
|
+
overrides?: CreateDocumentOverrides,
|
|
106
|
+
) => Promise<DocumentHandle> {
|
|
107
|
+
const instance = useSanityInstance()
|
|
108
|
+
trackHookUsage(instance, 'useCreateDocument')
|
|
109
|
+
const apply = useApplyDocumentActions()
|
|
110
|
+
|
|
111
|
+
return async (initialValue, overrides) => {
|
|
112
|
+
const documentId = overrides?.documentId ?? options.documentId ?? crypto.randomUUID()
|
|
113
|
+
const handle: DocumentHandle = {...options, documentId}
|
|
114
|
+
await apply(createDocument(handle, initialValue))
|
|
115
|
+
return handle
|
|
116
|
+
}
|
|
117
|
+
}
|