@sanity/sdk-react 3.0.0-rc.0 → 3.0.0-rc.2

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.
Files changed (168) hide show
  1. package/README.md +103 -155
  2. package/dist/index.d.ts +2488 -2019
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +4435 -1529
  5. package/dist/index.js.map +1 -1
  6. package/package.json +55 -59
  7. package/src/_exports/index.ts +2 -4
  8. package/src/_exports/sdk-react.ts +30 -18
  9. package/src/components/SDKProvider.test.tsx +173 -32
  10. package/src/components/SDKProvider.tsx +87 -28
  11. package/src/components/SanityApp.test.tsx +68 -12
  12. package/src/components/SanityApp.tsx +93 -87
  13. package/src/components/auth/AuthBoundary.recovery.test.tsx +86 -0
  14. package/src/components/auth/AuthBoundary.test.tsx +7 -7
  15. package/src/components/auth/AuthBoundary.tsx +29 -6
  16. package/src/components/auth/DashboardAccessRequest.tsx +37 -0
  17. package/src/components/auth/LoginError.test.tsx +191 -5
  18. package/src/components/auth/LoginError.tsx +104 -61
  19. package/src/components/errors/ChunkLoadError.test.tsx +59 -0
  20. package/src/components/errors/ChunkLoadError.tsx +56 -0
  21. package/src/components/errors/CorsErrorComponent.tsx +2 -2
  22. package/src/components/errors/chunkReloadStorage.ts +57 -0
  23. package/src/config/handles.ts +29 -27
  24. package/src/constants.ts +5 -0
  25. package/src/context/ComlinkTokenRefresh.test.tsx +10 -27
  26. package/src/context/ComlinkTokenRefresh.tsx +3 -5
  27. package/src/context/DefaultResourceContext.ts +3 -3
  28. package/src/context/OrganizationResourcesProvider.test.tsx +189 -0
  29. package/src/context/OrganizationResourcesProvider.tsx +111 -0
  30. package/src/context/PerspectiveContext.ts +3 -3
  31. package/src/context/ProjectContext.ts +15 -0
  32. package/src/context/ResourceProvider.test.tsx +93 -90
  33. package/src/context/ResourceProvider.tsx +62 -93
  34. package/src/context/SDKStudioContext.test.tsx +28 -33
  35. package/src/context/SanityInstanceProvider.test.tsx +100 -0
  36. package/src/context/SanityInstanceProvider.tsx +71 -0
  37. package/src/context/WorkbenchTokenRefresh.test.tsx +106 -0
  38. package/src/context/WorkbenchTokenRefresh.tsx +61 -0
  39. package/src/context/renderSanityApp.test.tsx +151 -49
  40. package/src/context/renderSanityApp.tsx +12 -8
  41. package/src/context/workbenchToken.ts +63 -0
  42. package/src/hooks/access/useCheckPermissions.test-d.ts +15 -0
  43. package/src/hooks/access/useCheckPermissions.test.tsx +53 -0
  44. package/src/hooks/access/useCheckPermissions.ts +24 -0
  45. package/src/hooks/agent/agentActions.test.tsx +2 -2
  46. package/src/hooks/agent/agentActions.ts +52 -72
  47. package/src/hooks/agent/useAgentResourceContext.ts +1 -1
  48. package/src/hooks/applications/useApplication.test-d.ts +30 -0
  49. package/src/hooks/applications/useApplication.ts +22 -0
  50. package/src/hooks/applications/useApplications.test-d.ts +31 -0
  51. package/src/hooks/applications/useApplications.ts +25 -0
  52. package/src/hooks/applications/useDeleteApplication.test-d.ts +12 -0
  53. package/src/hooks/applications/useDeleteApplication.ts +11 -0
  54. package/src/hooks/applications/useUpdateApplication.test-d.ts +12 -0
  55. package/src/hooks/applications/useUpdateApplication.ts +11 -0
  56. package/src/hooks/auth/useDashboardOrganizationId.test.tsx +2 -8
  57. package/src/hooks/auth/useVerifyOrgProjects.test.tsx +8 -32
  58. package/src/hooks/auth/useVerifyOrgProjects.tsx +13 -6
  59. package/src/hooks/client/useClient.test.tsx +1 -4
  60. package/src/hooks/client/useClient.ts +1 -0
  61. package/src/hooks/comlink/useFrameConnection.test.tsx +3 -2
  62. package/src/hooks/comlink/useFrameConnection.ts +1 -1
  63. package/src/hooks/comlink/useWindowConnection.test.tsx +4 -3
  64. package/src/hooks/comlink/useWindowConnection.ts +2 -3
  65. package/src/hooks/comments/useCommentActions.test.tsx +150 -0
  66. package/src/hooks/comments/useCommentActions.ts +109 -0
  67. package/src/hooks/comments/useCommentList.ts +79 -0
  68. package/src/hooks/comments/useCommentThreads.test.tsx +107 -0
  69. package/src/hooks/comments/useCommentThreads.ts +73 -0
  70. package/src/hooks/comments/useComments.test.tsx +242 -0
  71. package/src/hooks/comments/useComments.ts +59 -0
  72. package/src/hooks/context/useResource.test.tsx +32 -0
  73. package/src/hooks/context/useResource.ts +24 -0
  74. package/src/hooks/context/useSanityInstance.test.tsx +18 -1
  75. package/src/hooks/context/useSanityInstance.ts +6 -6
  76. package/src/hooks/dashboard/useManageFavorite.test.tsx +110 -46
  77. package/src/hooks/dashboard/useManageFavorite.ts +11 -17
  78. package/src/hooks/dashboard/useNavigateToStudioDocument.test.ts +5 -7
  79. package/src/hooks/dashboard/useNavigateToStudioDocument.ts +2 -6
  80. package/src/hooks/dashboard/useRecordDocumentHistoryEvent.test.ts +0 -2
  81. package/src/hooks/dashboard/useRecordDocumentHistoryEvent.ts +2 -2
  82. package/src/hooks/dashboard/useWindowTitle.test.ts +244 -0
  83. package/src/hooks/dashboard/useWindowTitle.ts +114 -0
  84. package/src/hooks/datasets/useDatasets.test.tsx +116 -0
  85. package/src/hooks/datasets/useDatasets.ts +33 -13
  86. package/src/hooks/document/useApplyDocumentActions.test.tsx +48 -0
  87. package/src/hooks/document/useApplyDocumentActions.ts +66 -48
  88. package/src/hooks/document/useCreateDocument.test.tsx +83 -0
  89. package/src/hooks/document/useCreateDocument.ts +117 -0
  90. package/src/hooks/document/useDocument.test.tsx +37 -8
  91. package/src/hooks/document/useDocument.ts +131 -66
  92. package/src/hooks/document/useDocumentEvent.test.tsx +16 -4
  93. package/src/hooks/document/useDocumentEvent.ts +16 -10
  94. package/src/hooks/document/useDocumentPermissions.test.tsx +8 -7
  95. package/src/hooks/document/useDocumentPermissions.ts +71 -39
  96. package/src/hooks/document/useDocumentSyncStatus.test.ts +1 -0
  97. package/src/hooks/document/useDocumentSyncStatus.ts +13 -9
  98. package/src/hooks/document/useEditDocument.test.tsx +21 -14
  99. package/src/hooks/document/useEditDocument.ts +163 -17
  100. package/src/hooks/documents/useDocuments.test.tsx +64 -44
  101. package/src/hooks/documents/useDocuments.ts +31 -20
  102. package/src/hooks/helpers/createCallbackHook.test.tsx +13 -19
  103. package/src/hooks/helpers/createFetcherHook.test.tsx +180 -0
  104. package/src/hooks/helpers/createFetcherHook.ts +69 -0
  105. package/src/hooks/helpers/createMutationHook.test.tsx +125 -0
  106. package/src/hooks/helpers/createMutationHook.tsx +93 -0
  107. package/src/hooks/helpers/createStateSourceHook.test.tsx +10 -10
  108. package/src/hooks/helpers/createStateSourceHook.tsx +2 -1
  109. package/src/hooks/{document/useApplyDocumentActions.test.ts → helpers/useApplyActions.test.tsx} +92 -53
  110. package/src/hooks/helpers/useApplyActions.ts +68 -0
  111. package/src/hooks/helpers/useNormalizedResourceOptions.test.tsx +226 -0
  112. package/src/hooks/helpers/useNormalizedResourceOptions.ts +79 -48
  113. package/src/hooks/helpers/useResolvedProjectId.test.tsx +59 -0
  114. package/src/hooks/helpers/useResolvedProjectId.ts +35 -0
  115. package/src/hooks/helpers/useTrackHookUsage.ts +37 -0
  116. package/src/hooks/installations/useInstallation.test-d.ts +19 -0
  117. package/src/hooks/installations/useInstallation.ts +22 -0
  118. package/src/hooks/installations/useInstallations.test-d.ts +26 -0
  119. package/src/hooks/installations/useInstallations.ts +25 -0
  120. package/src/hooks/organizations/useOrganization.test-d.ts +60 -0
  121. package/src/hooks/organizations/useOrganization.test.ts +63 -0
  122. package/src/hooks/organizations/useOrganization.ts +34 -0
  123. package/src/hooks/organizations/useOrganizations.test-d.ts +68 -0
  124. package/src/hooks/organizations/useOrganizations.test.ts +63 -0
  125. package/src/hooks/organizations/useOrganizations.ts +39 -0
  126. package/src/hooks/paginatedDocuments/usePaginatedDocuments.test.tsx +34 -27
  127. package/src/hooks/paginatedDocuments/usePaginatedDocuments.ts +35 -22
  128. package/src/hooks/presence/usePresence.test.tsx +0 -15
  129. package/src/hooks/presence/usePresence.ts +22 -20
  130. package/src/hooks/presence/usePresenceForDocument.test.tsx +141 -0
  131. package/src/hooks/presence/usePresenceForDocument.ts +104 -0
  132. package/src/hooks/presence/useReportPresence.test.tsx +202 -0
  133. package/src/hooks/presence/useReportPresence.ts +170 -0
  134. package/src/hooks/preview/useDocumentPreview.test.tsx +0 -1
  135. package/src/hooks/preview/useDocumentPreview.tsx +12 -14
  136. package/src/hooks/projection/useDocumentProjection.test.tsx +78 -9
  137. package/src/hooks/projection/useDocumentProjection.ts +81 -2
  138. package/src/hooks/projects/useProject.test-d.ts +56 -0
  139. package/src/hooks/projects/useProject.test.tsx +120 -0
  140. package/src/hooks/projects/useProject.ts +42 -43
  141. package/src/hooks/projects/useProjects.test-d.ts +60 -0
  142. package/src/hooks/projects/useProjects.test.ts +45 -98
  143. package/src/hooks/projects/useProjects.ts +17 -32
  144. package/src/hooks/query/useQuery.test.tsx +6 -5
  145. package/src/hooks/query/useQuery.ts +94 -17
  146. package/src/hooks/releases/useActiveReleases.test.tsx +13 -7
  147. package/src/hooks/releases/useActiveReleases.ts +28 -24
  148. package/src/hooks/releases/useAllReleases.test.tsx +93 -0
  149. package/src/hooks/releases/useAllReleases.ts +62 -0
  150. package/src/hooks/releases/useApplyReleaseActions.test.tsx +66 -0
  151. package/src/hooks/releases/useApplyReleaseActions.ts +82 -0
  152. package/src/hooks/releases/usePerspective.test.tsx +18 -10
  153. package/src/hooks/releases/usePerspective.ts +13 -17
  154. package/src/hooks/users/useUser.test.tsx +3 -9
  155. package/src/hooks/users/useUser.ts +4 -8
  156. package/src/hooks/users/useUsers.test.tsx +103 -7
  157. package/src/hooks/users/useUsers.ts +38 -5
  158. package/src/utils/resolveOrgResources.test.ts +111 -0
  159. package/src/utils/resolveOrgResources.ts +69 -0
  160. package/src/hooks/context/useDefaultResource.test.tsx +0 -25
  161. package/src/hooks/context/useDefaultResource.ts +0 -30
  162. package/src/hooks/dashboard/useDispatchIntent.test.ts +0 -254
  163. package/src/hooks/dashboard/useDispatchIntent.ts +0 -157
  164. package/src/hooks/dashboard/utils/useResourceIdFromDocumentHandle.test.ts +0 -107
  165. package/src/hooks/dashboard/utils/useResourceIdFromDocumentHandle.ts +0 -39
  166. package/src/hooks/datasets/useDatasets.test.ts +0 -66
  167. package/src/hooks/helpers/useNormalizedResourceOptions.test.ts +0 -65
  168. package/src/hooks/projects/useProject.test.ts +0 -79
@@ -0,0 +1,63 @@
1
+ import {from, type Observable, of} from 'rxjs'
2
+ import {catchError, switchMap} from 'rxjs/operators'
3
+
4
+ // The workbench host installs its shared message bus on this well-known global
5
+ // symbol before it loads federated remotes. It must match the key used by
6
+ // `@sanity/workbench` (`Symbol.for('sanity.os.bus')`).
7
+ const OS_BUS_KEY = Symbol.for('sanity.os.bus')
8
+
9
+ /**
10
+ * Whether this app is running as a federated remote inside the workbench.
11
+ *
12
+ * Federation shares the host's realm, so the installed bus is visible on
13
+ * `globalThis`. This is `false` in a standalone app, where we must never import
14
+ * `@sanity/workbench` (it would install a bus and add bundle weight for no
15
+ * reason). Note: this is a different embedding model to the Core UI iframe,
16
+ * which is detected separately via the dashboard context — that signal is not
17
+ * set on the federation path.
18
+ *
19
+ * @internal
20
+ */
21
+ export function isWorkbenchEnvironment(): boolean {
22
+ return typeof globalThis === 'object' && OS_BUS_KEY in globalThis
23
+ }
24
+
25
+ /**
26
+ * Observes the session token issued by the workbench "OS", tracking the OS auth
27
+ * state over time.
28
+ *
29
+ * Returns `undefined` when the app is not embedded in the workbench, so the
30
+ * caller uses its normal auth flow. Inside the workbench, subscribes to the
31
+ * `auth.token` state topic, emitting the current token — or `null` when the OS
32
+ * is signed out — and re-emitting as the OS auth state changes, so sign-in/out
33
+ * propagates instead of being captured once. Any bus error is treated as "no
34
+ * token" (`null`). The token is used in-memory only and never persisted.
35
+ *
36
+ * @internal
37
+ */
38
+ export function observeWorkbenchToken(): Observable<string | null> | undefined {
39
+ if (!isWorkbenchEnvironment()) return undefined
40
+
41
+ return from(import('@sanity/workbench')).pipe(
42
+ switchMap(({os}) => os.subscribe('auth.token')),
43
+ // Any failure (importing the host bundle, or the subscription) means "no OS token".
44
+ catchError(() => of(null)),
45
+ )
46
+ }
47
+
48
+ /**
49
+ * Asks the workbench "OS" to reissue the session token, e.g. after its current
50
+ * one was rejected with a 401. Fire-and-forget: the reissued token arrives via
51
+ * the `auth.token` subscription in {@link observeWorkbenchToken}. No-op outside
52
+ * the workbench.
53
+ *
54
+ * @internal
55
+ */
56
+ export function refreshWorkbenchToken(): void {
57
+ if (!isWorkbenchEnvironment()) return
58
+
59
+ void import('@sanity/workbench').then(
60
+ ({os}) => os.emit('auth.token.refresh', undefined),
61
+ () => {},
62
+ )
63
+ }
@@ -0,0 +1,15 @@
1
+ import {expectTypeOf, test} from 'vitest'
2
+
3
+ import {type FetcherHookResult} from '../helpers/createFetcherHook'
4
+ import {useCheckPermissions} from './useCheckPermissions'
5
+
6
+ test('useCheckPermissions — the result is keyed by exactly the permissions passed', () => {
7
+ const result = useCheckPermissions('project', 'proj_1', ['read', 'write'])
8
+ expectTypeOf(result).toEqualTypeOf<FetcherHookResult<Record<'read' | 'write', boolean>>>()
9
+ expectTypeOf(result.data).toEqualTypeOf<Record<'read' | 'write', boolean>>()
10
+ })
11
+
12
+ test('useCheckPermissions — rejects an unknown resource type', () => {
13
+ // @ts-expect-error — resourceType must be an AccessResourceType
14
+ void useCheckPermissions('not-a-resource', 'id', ['read'])
15
+ })
@@ -0,0 +1,53 @@
1
+ import {checkPermissions, type StateSource} from '@sanity/sdk'
2
+ import {type FetcherSnapshot} from '@sanity/sdk/_internal'
3
+ import {type Observable} from 'rxjs'
4
+ import {beforeEach, describe, expect, it, vi} from 'vitest'
5
+
6
+ import {renderHook} from '../../../test/test-utils'
7
+ import {useCheckPermissions} from './useCheckPermissions'
8
+
9
+ vi.mock('@sanity/sdk', async (importOriginal) => {
10
+ const original = await importOriginal<typeof import('@sanity/sdk')>()
11
+ return {
12
+ ...original,
13
+ checkPermissions: {getState: vi.fn(), resolveState: vi.fn(), refetch: vi.fn()},
14
+ }
15
+ })
16
+
17
+ const stateSource = (
18
+ current: Record<string, boolean>,
19
+ ): StateSource<FetcherSnapshot<Record<string, boolean>>> => {
20
+ const snapshot = {
21
+ status: 'success',
22
+ data: current,
23
+ error: undefined,
24
+ isFetching: false,
25
+ dataUpdatedAt: 1,
26
+ }
27
+ return {
28
+ getCurrent: vi.fn(() => snapshot),
29
+ subscribe: vi.fn(() => () => {}),
30
+ get observable(): Observable<unknown> {
31
+ throw new Error('Not implemented')
32
+ },
33
+ } as unknown as StateSource<FetcherSnapshot<Record<string, boolean>>>
34
+ }
35
+
36
+ const sanityInstance = expect.objectContaining({config: expect.any(Object)})
37
+
38
+ describe('useCheckPermissions', () => {
39
+ beforeEach(() => {
40
+ vi.clearAllMocks()
41
+ vi.mocked(checkPermissions.getState).mockReturnValue(stateSource({'sanity.project.read': true}))
42
+ })
43
+
44
+ it('forwards every param to the fetcher and returns the permission map', () => {
45
+ const {result} = renderHook(() =>
46
+ useCheckPermissions('project', 'proj_1', ['sanity.project.read']),
47
+ )
48
+ expect(checkPermissions.getState).toHaveBeenCalledWith(sanityInstance, 'project', 'proj_1', [
49
+ 'sanity.project.read',
50
+ ])
51
+ expect(result.current.data).toEqual({'sanity.project.read': true})
52
+ })
53
+ })
@@ -0,0 +1,24 @@
1
+ import {type AccessResourceType, checkPermissions} from '@sanity/sdk'
2
+
3
+ import {createFetcherHook, type FetcherHookResult} from '../helpers/createFetcherHook'
4
+
5
+ /**
6
+ * Checks whether the current user holds the given permissions on a resource.
7
+ *
8
+ * The hook suspends until the first fetch succeeds, so `data` is always present.
9
+ * `data` maps each requested permission to whether the user has it — keyed by
10
+ * exactly the permission strings you passed.
11
+ *
12
+ * @public
13
+ * @param resourceType - The type of resource (e.g. `'project'`, `'organization'`).
14
+ * @param resourceId - The resource id.
15
+ * @param permissions - The permission names to check.
16
+ * @returns The result envelope `{data, isFetching, error, refetch}`.
17
+ */
18
+ export const useCheckPermissions = createFetcherHook(checkPermissions) as <
19
+ Permission extends string,
20
+ >(
21
+ resourceType: AccessResourceType,
22
+ resourceId: string,
23
+ permissions: Permission[],
24
+ ) => FetcherHookResult<Record<Permission, boolean>>
@@ -12,7 +12,7 @@ import {
12
12
  useAgentTranslate,
13
13
  } from './agentActions'
14
14
 
15
- vi.mock('@sanity/sdk', async (orig) => {
15
+ vi.mock('@sanity/sdk/agent', async (orig) => {
16
16
  const actual = await orig()
17
17
  return {
18
18
  ...(actual as Record<string, any>),
@@ -26,7 +26,7 @@ vi.mock('@sanity/sdk', async (orig) => {
26
26
 
27
27
  describe('agent action hooks', () => {
28
28
  const wrapper = ({children}: {children: React.ReactNode}) => (
29
- <ResourceProvider resource={{projectId: 'p', dataset: 'd'}} fallback={null}>
29
+ <ResourceProvider projectId="p" dataset="d" fallback={null}>
30
30
  {children}
31
31
  </ResourceProvider>
32
32
  )
@@ -7,23 +7,17 @@ import {
7
7
  agentPrompt,
8
8
  type AgentPromptOptions,
9
9
  type AgentPromptResult,
10
- type AgentResourceOptions,
11
10
  agentTransform,
12
11
  type AgentTransformOptions,
13
12
  agentTranslate,
14
13
  type AgentTranslateOptions,
15
- type SanityInstance,
16
- } from '@sanity/sdk'
17
- import {useCallback, useContext} from 'react'
14
+ } from '@sanity/sdk/agent'
15
+ import {useCallback} from 'react'
18
16
  import {firstValueFrom} from 'rxjs'
19
17
 
20
- import {ResourceContext} from '../../context/DefaultResourceContext'
21
- import {ResourcesContext} from '../../context/ResourcesContext'
18
+ import {type ResourceHandle} from '../../config/handles'
22
19
  import {useSanityInstance} from '../context/useSanityInstance'
23
- import {
24
- normalizeResourceOptions,
25
- type WithResourceNameSupport,
26
- } from '../helpers/useNormalizedResourceOptions'
20
+ import {useNormalizedResourceOptions} from '../helpers/useNormalizedResourceOptions'
27
21
 
28
22
  interface Subscription {
29
23
  unsubscribe(): void
@@ -44,26 +38,6 @@ interface Subscribable<T> {
44
38
  ): Subscription
45
39
  }
46
40
 
47
- function useAgentCallback<TOptions extends object, TReturn>(
48
- action: (instance: SanityInstance, options: TOptions & AgentResourceOptions) => TReturn,
49
- ): (options: TOptions & WithResourceNameSupport<AgentResourceOptions>) => TReturn {
50
- const instance = useSanityInstance()
51
- const resources = useContext(ResourcesContext)
52
- const contextResource = useContext(ResourceContext)
53
- return useCallback(
54
- (options: TOptions & WithResourceNameSupport<AgentResourceOptions>) => {
55
- const {resource} = normalizeResourceOptions(
56
- {resource: options.resource, resourceName: options.resourceName},
57
- resources,
58
- contextResource,
59
- )
60
- const {resourceName: _, ...rest} = options
61
- return action(instance, {...rest, resource} as TOptions & AgentResourceOptions)
62
- },
63
- [instance, resources, contextResource, action],
64
- )
65
- }
66
-
67
41
  /**
68
42
  * @alpha
69
43
  * Generates content for a document (or specific fields) via Sanity Agent Actions.
@@ -131,10 +105,16 @@ function useAgentCallback<TOptions extends object, TReturn>(
131
105
  *
132
106
  * @category Agent Actions
133
107
  */
134
- export function useAgentGenerate(): (
135
- options: AgentGenerateOptions & WithResourceNameSupport<AgentResourceOptions>,
136
- ) => Subscribable<unknown> {
137
- return useAgentCallback<AgentGenerateOptions, Subscribable<unknown>>(agentGenerate)
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
+ )
138
118
  }
139
119
 
140
120
  /**
@@ -208,10 +188,16 @@ export function useAgentGenerate(): (
208
188
  *
209
189
  * @category Agent Actions
210
190
  */
211
- export function useAgentTransform(): (
212
- options: AgentTransformOptions & WithResourceNameSupport<AgentResourceOptions>,
213
- ) => Subscribable<unknown> {
214
- return useAgentCallback<AgentTransformOptions, Subscribable<unknown>>(agentTransform)
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
+ )
215
201
  }
216
202
 
217
203
  /**
@@ -304,21 +290,16 @@ export function useAgentTransform(): (
304
290
  *
305
291
  * @category Agent Actions
306
292
  */
307
- export function useAgentTranslate(): (
308
- options: AgentTranslateOptions & WithResourceNameSupport<AgentResourceOptions>,
309
- ) => Subscribable<unknown> {
310
- return useAgentCallback<AgentTranslateOptions, Subscribable<unknown>>(agentTranslate)
311
- }
312
-
313
- /**
314
- * @internal
315
- * Adapter to convert the agentPrompt observable to a Promise.
316
- */
317
- function promptAdapter(
318
- instance: SanityInstance,
319
- options: AgentPromptOptions & AgentResourceOptions,
320
- ): Promise<AgentPromptResult> {
321
- 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
+ )
322
303
  }
323
304
 
324
305
  /**
@@ -415,21 +396,15 @@ function promptAdapter(
415
396
  *
416
397
  * @category Agent Actions
417
398
  */
418
- export function useAgentPrompt(): (
419
- options: AgentPromptOptions & WithResourceNameSupport<AgentResourceOptions>,
420
- ) => Promise<AgentPromptResult> {
421
- return useAgentCallback<AgentPromptOptions, Promise<AgentPromptResult>>(promptAdapter)
422
- }
423
-
424
- /**
425
- * @internal
426
- * Adapter to convert the agentPatch observable to a Promise.
427
- */
428
- function patchAdapter(
429
- instance: SanityInstance,
430
- options: AgentPatchOptions & AgentResourceOptions,
431
- ): Promise<AgentPatchResult> {
432
- 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
+ )
433
408
  }
434
409
 
435
410
  /**
@@ -581,8 +556,13 @@ function patchAdapter(
581
556
  *
582
557
  * @category Agent Actions
583
558
  */
584
- export function useAgentPatch(): (
585
- options: AgentPatchOptions & WithResourceNameSupport<AgentResourceOptions>,
586
- ) => Promise<AgentPatchResult> {
587
- return useAgentCallback<AgentPatchOptions, Promise<AgentPatchResult>>(patchAdapter)
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
+ )
588
568
  }
@@ -1,5 +1,5 @@
1
1
  import {type Events, SDK_CHANNEL_NAME, SDK_NODE_NAME} from '@sanity/message-protocol'
2
- import {type FrameMessage} from '@sanity/sdk'
2
+ import {type FrameMessage} from '@sanity/sdk/comlink'
3
3
  import {useCallback, useEffect, useRef} from 'react'
4
4
 
5
5
  import {useWindowConnection} from '../comlink/useWindowConnection'
@@ -0,0 +1,30 @@
1
+ import {
2
+ type Application,
3
+ type ApplicationDeployment,
4
+ type ApplicationStudioConfig,
5
+ } from '@sanity/sdk'
6
+ import {expectTypeOf, test} from 'vitest'
7
+
8
+ import {type FetcherHookResult} from '../helpers/createFetcherHook'
9
+ import {useApplication} from './useApplication'
10
+
11
+ test('useApplication — no include: the base application', () => {
12
+ const result = useApplication('app_1')
13
+ expectTypeOf(result).toEqualTypeOf<FetcherHookResult<Application<never>>>()
14
+ expectTypeOf<
15
+ Extract<keyof typeof result.data, 'activeDeployment' | 'config'>
16
+ >().toEqualTypeOf<never>()
17
+ })
18
+
19
+ test('useApplication — config.studio include adds config.studio', () => {
20
+ const result = useApplication('app_1', {include: ['config.studio']})
21
+ expectTypeOf(result.data).toEqualTypeOf<Application<'config.studio'>>()
22
+ expectTypeOf(result.data.config.studio).toEqualTypeOf<ApplicationStudioConfig>()
23
+ })
24
+
25
+ test('useApplication — a deployment-child include forces the deployment in', () => {
26
+ const result = useApplication('app_1', {include: ['activeDeployment']})
27
+ expectTypeOf(
28
+ result.data.activeDeployment,
29
+ ).toEqualTypeOf<ApplicationDeployment<'activeDeployment'> | null>()
30
+ })
@@ -0,0 +1,22 @@
1
+ import {type Application, application, type ApplicationInclude} from '@sanity/sdk'
2
+
3
+ import {createFetcherHook, type FetcherHookResult} from '../helpers/createFetcherHook'
4
+
5
+ /**
6
+ * Returns a single application by id.
7
+ *
8
+ * The hook suspends until the first fetch succeeds, so `data` is always present.
9
+ * The `include` tokens you pass shape `data`: each requested token adds its
10
+ * field, and omitted ones are absent from the type.
11
+ *
12
+ * @public
13
+ * @param applicationId - The application id.
14
+ * @param options - Optional `include` list to expand related resources.
15
+ * @returns The result envelope `{data, isFetching, error, refetch}`.
16
+ */
17
+ export const useApplication = createFetcherHook(application) as <
18
+ Include extends ApplicationInclude = never,
19
+ >(
20
+ applicationId: string,
21
+ options?: {include?: Include[]},
22
+ ) => FetcherHookResult<Application<Include>>
@@ -0,0 +1,31 @@
1
+ import {type Application, type ApplicationInterface, type ApplicationsResponse} from '@sanity/sdk'
2
+ import {expectTypeOf, test} from 'vitest'
3
+
4
+ import {type FetcherHookResult} from '../helpers/createFetcherHook'
5
+ import {useApplications} from './useApplications'
6
+
7
+ test('useApplications — no include: base items, no deployment or config', () => {
8
+ const result = useApplications({organizationId: 'org_1'})
9
+ expectTypeOf(result).toEqualTypeOf<FetcherHookResult<ApplicationsResponse<never>>>()
10
+ type Item = (typeof result.data.data)[number]
11
+ expectTypeOf<Extract<keyof Item, 'activeDeployment' | 'config'>>().toEqualTypeOf<never>()
12
+ })
13
+
14
+ test('useApplications — include shapes each item', () => {
15
+ const result = useApplications({organizationId: 'org_1', include: ['interfaces']})
16
+ expectTypeOf(result.data).toEqualTypeOf<ApplicationsResponse<'interfaces'>>()
17
+ type Item = (typeof result.data.data)[number]
18
+ expectTypeOf<NonNullable<Item['activeDeployment']>['interfaces']>().toEqualTypeOf<
19
+ ApplicationInterface[]
20
+ >()
21
+ })
22
+
23
+ test('useApplications — result data is the current include-narrowed application', () => {
24
+ const result = useApplications({organizationId: 'org_1', include: ['config.studio']})
25
+ expectTypeOf(result.data.data).toEqualTypeOf<Application<'config.studio'>[]>()
26
+ })
27
+
28
+ test('useApplications — requires an organizationId', () => {
29
+ // @ts-expect-error — organizationId is required
30
+ void useApplications({})
31
+ })
@@ -0,0 +1,25 @@
1
+ import {
2
+ type ApplicationInclude,
3
+ applications,
4
+ type ApplicationsOptions,
5
+ type ApplicationsResponse,
6
+ } from '@sanity/sdk'
7
+
8
+ import {createFetcherHook, type FetcherHookResult} from '../helpers/createFetcherHook'
9
+
10
+ /**
11
+ * Returns the applications matching the given options.
12
+ *
13
+ * The hook suspends until the first fetch succeeds, so `data` is always present.
14
+ * The `include` tokens you pass shape `data.data`: each requested token adds its
15
+ * field, and omitted ones are absent from the type.
16
+ *
17
+ * @public
18
+ * @param options - Filter and include options for the applications list.
19
+ * @returns The result envelope `{data, isFetching, error, refetch}`.
20
+ */
21
+ export const useApplications = createFetcherHook(applications) as <
22
+ Include extends ApplicationInclude = never,
23
+ >(
24
+ options: ApplicationsOptions<Include>,
25
+ ) => FetcherHookResult<ApplicationsResponse<Include>>
@@ -0,0 +1,12 @@
1
+ import {type DeleteApplicationInput, type DeletedResult} from '@sanity/sdk'
2
+ import {expectTypeOf, test} from 'vitest'
3
+
4
+ import {type MutationHookResult} from '../helpers/createMutationHook'
5
+ import {useDeleteApplication} from './useDeleteApplication'
6
+
7
+ test('useDeleteApplication — returns the mutation envelope', () => {
8
+ const result = useDeleteApplication()
9
+ expectTypeOf(result).toEqualTypeOf<MutationHookResult<DeleteApplicationInput, DeletedResult>>()
10
+ expectTypeOf(result.mutate).parameter(0).toEqualTypeOf<DeleteApplicationInput>()
11
+ expectTypeOf(result.data).toEqualTypeOf<DeletedResult | undefined>()
12
+ })
@@ -0,0 +1,11 @@
1
+ import {deleteApplication} from '@sanity/sdk'
2
+
3
+ import {createMutationHook} from '../helpers/createMutationHook'
4
+
5
+ /**
6
+ * Soft-deletes an application.
7
+ *
8
+ * @internal
9
+ * @returns The mutation envelope `{mutate, isPending, error, data, reset}`.
10
+ */
11
+ export const useDeleteApplication = createMutationHook(deleteApplication)
@@ -0,0 +1,12 @@
1
+ import {type Application, type UpdateApplicationInput} from '@sanity/sdk'
2
+ import {expectTypeOf, test} from 'vitest'
3
+
4
+ import {type MutationHookResult} from '../helpers/createMutationHook'
5
+ import {useUpdateApplication} from './useUpdateApplication'
6
+
7
+ test('useUpdateApplication — returns the mutation envelope', () => {
8
+ const result = useUpdateApplication()
9
+ expectTypeOf(result).toEqualTypeOf<MutationHookResult<UpdateApplicationInput, Application>>()
10
+ expectTypeOf(result.mutate).parameter(0).toEqualTypeOf<UpdateApplicationInput>()
11
+ expectTypeOf(result.data).toEqualTypeOf<Application | undefined>()
12
+ })
@@ -0,0 +1,11 @@
1
+ import {updateApplication} from '@sanity/sdk'
2
+
3
+ import {createMutationHook} from '../helpers/createMutationHook'
4
+
5
+ /**
6
+ * Updates an application's mutable properties (title, icon, visibility).
7
+ *
8
+ * @internal
9
+ * @returns The mutation envelope `{mutate, isPending, error, data, reset}`.
10
+ */
11
+ export const useUpdateApplication = createMutationHook(updateApplication)
@@ -22,10 +22,7 @@ describe('useDashboardOrganizationId', () => {
22
22
 
23
23
  const {result} = renderHook(() => useDashboardOrganizationId(), {
24
24
  wrapper: ({children}) => (
25
- <ResourceProvider
26
- resource={{projectId: 'test-project', dataset: 'test-dataset'}}
27
- fallback={null}
28
- >
25
+ <ResourceProvider projectId="test-project" dataset="test-dataset" fallback={null}>
29
26
  {children}
30
27
  </ResourceProvider>
31
28
  ),
@@ -44,10 +41,7 @@ describe('useDashboardOrganizationId', () => {
44
41
 
45
42
  const {result} = renderHook(() => useDashboardOrganizationId(), {
46
43
  wrapper: ({children}) => (
47
- <ResourceProvider
48
- resource={{projectId: 'test-project', dataset: 'test-dataset'}}
49
- fallback={null}
50
- >
44
+ <ResourceProvider projectId="test-project" dataset="test-dataset" fallback={null}>
51
45
  {children}
52
46
  </ResourceProvider>
53
47
  ),
@@ -26,10 +26,7 @@ describe('useVerifyOrgProjects', () => {
26
26
  it('should return null and not observe state if disabled', () => {
27
27
  const {result} = renderHook(() => useVerifyOrgProjects(true, testProjectIds), {
28
28
  wrapper: ({children}) => (
29
- <ResourceProvider
30
- resource={{projectId: 'test-project', dataset: 'test-dataset'}}
31
- fallback={null}
32
- >
29
+ <ResourceProvider projectId="test-project" dataset="test-dataset" fallback={null}>
33
30
  {children}
34
31
  </ResourceProvider>
35
32
  ),
@@ -42,10 +39,7 @@ describe('useVerifyOrgProjects', () => {
42
39
  it('should return null and not observe state if projectIds is missing or empty', () => {
43
40
  const {result: resultUndefined} = renderHook(() => useVerifyOrgProjects(false, undefined), {
44
41
  wrapper: ({children}) => (
45
- <ResourceProvider
46
- resource={{projectId: 'test-project', dataset: 'test-dataset'}}
47
- fallback={null}
48
- >
42
+ <ResourceProvider projectId="test-project" dataset="test-dataset" fallback={null}>
49
43
  {children}
50
44
  </ResourceProvider>
51
45
  ),
@@ -55,10 +49,7 @@ describe('useVerifyOrgProjects', () => {
55
49
 
56
50
  const {result: resultEmpty} = renderHook(() => useVerifyOrgProjects(false, []), {
57
51
  wrapper: ({children}) => (
58
- <ResourceProvider
59
- resource={{projectId: 'test-project', dataset: 'test-dataset'}}
60
- fallback={null}
61
- >
52
+ <ResourceProvider projectId="test-project" dataset="test-dataset" fallback={null}>
62
53
  {children}
63
54
  </ResourceProvider>
64
55
  ),
@@ -73,10 +64,7 @@ describe('useVerifyOrgProjects', () => {
73
64
 
74
65
  const {result} = renderHook(() => useVerifyOrgProjects(false, testProjectIds), {
75
66
  wrapper: ({children}) => (
76
- <ResourceProvider
77
- resource={{projectId: 'test-project', dataset: 'test-dataset'}}
78
- fallback={null}
79
- >
67
+ <ResourceProvider projectId="test-project" dataset="test-dataset" fallback={null}>
80
68
  {children}
81
69
  </ResourceProvider>
82
70
  ),
@@ -92,10 +80,7 @@ describe('useVerifyOrgProjects', () => {
92
80
 
93
81
  const {result} = renderHook(() => useVerifyOrgProjects(false, testProjectIds), {
94
82
  wrapper: ({children}) => (
95
- <ResourceProvider
96
- resource={{projectId: 'test-project', dataset: 'test-dataset'}}
97
- fallback={null}
98
- >
83
+ <ResourceProvider projectId="test-project" dataset="test-dataset" fallback={null}>
99
84
  {children}
100
85
  </ResourceProvider>
101
86
  ),
@@ -117,10 +102,7 @@ describe('useVerifyOrgProjects', () => {
117
102
 
118
103
  const {result} = renderHook(() => useVerifyOrgProjects(false, testProjectIds), {
119
104
  wrapper: ({children}) => (
120
- <ResourceProvider
121
- resource={{projectId: 'test-project', dataset: 'test-dataset'}}
122
- fallback={null}
123
- >
105
+ <ResourceProvider projectId="test-project" dataset="test-dataset" fallback={null}>
124
106
  {children}
125
107
  </ResourceProvider>
126
108
  ),
@@ -142,10 +124,7 @@ describe('useVerifyOrgProjects', () => {
142
124
 
143
125
  const {unmount} = renderHook(() => useVerifyOrgProjects(false, testProjectIds), {
144
126
  wrapper: ({children}) => (
145
- <ResourceProvider
146
- resource={{projectId: 'test-project', dataset: 'test-dataset'}}
147
- fallback={null}
148
- >
127
+ <ResourceProvider projectId="test-project" dataset="test-dataset" fallback={null}>
149
128
  {children}
150
129
  </ResourceProvider>
151
130
  ),
@@ -173,10 +152,7 @@ describe('useVerifyOrgProjects', () => {
173
152
  {
174
153
  initialProps: {disabled: false, pIds: testProjectIds},
175
154
  wrapper: ({children}) => (
176
- <ResourceProvider
177
- resource={{projectId: 'test-project', dataset: 'test-dataset'}}
178
- fallback={null}
179
- >
155
+ <ResourceProvider projectId="test-project" dataset="test-dataset" fallback={null}>
180
156
  {children}
181
157
  </ResourceProvider>
182
158
  ),