@sanity/sdk-react 2.3.1 → 2.5.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.
@@ -0,0 +1,158 @@
1
+ import {SDK_CHANNEL_NAME, SDK_NODE_NAME} from '@sanity/message-protocol'
2
+ import {type FrameMessage} from '@sanity/sdk'
3
+ import {useCallback} from 'react'
4
+
5
+ import {useWindowConnection} from '../comlink/useWindowConnection'
6
+ import {type DocumentHandleWithSource} from './types'
7
+ import {getResourceIdFromDocumentHandle} from './utils/getResourceIdFromDocumentHandle'
8
+
9
+ /**
10
+ * Message type for sending intents to the dashboard
11
+ * @beta
12
+ */
13
+ interface IntentMessage {
14
+ type: 'dashboard/v1/events/intents/dispatch-intent'
15
+ data: {
16
+ action?: 'edit'
17
+ intentId?: string
18
+ document: {
19
+ id: string
20
+ type: string
21
+ }
22
+ resource?: {
23
+ id: string
24
+ type?: 'media-library' | 'canvas'
25
+ }
26
+ parameters?: Record<string, unknown>
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Return type for the useDispatchIntent hook
32
+ * @beta
33
+ */
34
+ interface DispatchIntent {
35
+ dispatchIntent: () => void
36
+ }
37
+
38
+ /**
39
+ * Parameters for the useDispatchIntent hook
40
+ * @beta
41
+ */
42
+ interface UseDispatchIntentParams {
43
+ action?: 'edit'
44
+ intentId?: string
45
+ documentHandle: DocumentHandleWithSource
46
+ parameters?: Record<string, unknown>
47
+ }
48
+
49
+ /**
50
+ * @beta
51
+ *
52
+ * A hook for dispatching intent messages to the Dashboard with a document handle.
53
+ * This allows applications to signal their intent to pass the referenced document to other applications that have registered the ability to perform specific actions on that document.
54
+ *
55
+ * @param params - Object containing:
56
+ * - `action` - Action to perform (currently only 'edit' is supported). Will prompt a picker if multiple handlers are available.
57
+ * - `intentId` - Specific ID of the intent to dispatch. Either `action` or `intentId` is required.
58
+ * - `documentHandle` - The document handle containing document ID, type, and either:
59
+ * - `projectId` and `dataset` for traditional dataset sources, like `{documentId: '123', documentType: 'book', projectId: 'abc123', dataset: 'production'}`
60
+ * - `source` for media library, canvas, or dataset sources, like `{documentId: '123', documentType: 'sanity.asset', source: mediaLibrarySource('ml123')}` or `{documentId: '123', documentType: 'sanity.canvas.document', source: canvasSource('canvas123')}`
61
+ * - `paremeters` - Optional parameters to include in the dispatch; will be passed to the resolved intent handler
62
+ * @returns An object containing:
63
+ * - `dispatchIntent` - Function to dispatch the intent message
64
+ *
65
+ * @example
66
+ * ```tsx
67
+ * import {useDispatchIntent} from '@sanity/sdk-react'
68
+ * import {Button} from '@sanity/ui'
69
+ * import {Suspense} from 'react'
70
+ *
71
+ * function DispatchIntentButton({documentId, documentType, projectId, dataset}) {
72
+ * const {dispatchIntent} = useDispatchIntent({
73
+ * action: 'edit',
74
+ * documentHandle: {documentId, documentType, projectId, dataset},
75
+ * })
76
+ *
77
+ * return (
78
+ * <Button
79
+ * onClick={() => dispatchIntent()}
80
+ * text="Dispatch Intent"
81
+ * />
82
+ * )
83
+ * }
84
+ *
85
+ * // Wrap the component with Suspense since the hook may suspend
86
+ * function MyDocumentAction({documentId, documentType, projectId, dataset}) {
87
+ * return (
88
+ * <Suspense fallback={<Button text="Loading..." disabled />}>
89
+ * <DispatchIntentButton
90
+ * documentId={documentId}
91
+ * documentType={documentType}
92
+ * projectId={projectId}
93
+ * dataset={dataset}
94
+ * />
95
+ * </Suspense>
96
+ * )
97
+ * }
98
+ * ```
99
+ */
100
+ export function useDispatchIntent(params: UseDispatchIntentParams): DispatchIntent {
101
+ const {action, intentId, documentHandle, parameters} = params
102
+ const {sendMessage} = useWindowConnection<IntentMessage, FrameMessage>({
103
+ name: SDK_NODE_NAME,
104
+ connectTo: SDK_CHANNEL_NAME,
105
+ })
106
+
107
+ const dispatchIntent = useCallback(() => {
108
+ try {
109
+ if (!action && !intentId) {
110
+ throw new Error('useDispatchIntent: Either `action` or `intentId` must be provided.')
111
+ }
112
+
113
+ const {projectId, dataset, source} = documentHandle
114
+
115
+ if (action && intentId) {
116
+ // eslint-disable-next-line no-console -- warn if both action and intentId are provided
117
+ console.warn(
118
+ 'useDispatchIntent: Both `action` and `intentId` were provided. Using `intentId` and ignoring `action`.',
119
+ )
120
+ }
121
+
122
+ if (!source && (!projectId || !dataset)) {
123
+ throw new Error(
124
+ 'useDispatchIntent: Either `source` or both `projectId` and `dataset` must be provided in documentHandle.',
125
+ )
126
+ }
127
+
128
+ const resource = getResourceIdFromDocumentHandle(documentHandle)
129
+
130
+ const message: IntentMessage = {
131
+ type: 'dashboard/v1/events/intents/dispatch-intent',
132
+ data: {
133
+ ...(action && !intentId ? {action} : {}),
134
+ ...(intentId ? {intentId} : {}),
135
+ document: {
136
+ id: documentHandle.documentId,
137
+ type: documentHandle.documentType,
138
+ },
139
+ resource: {
140
+ id: resource.id,
141
+ ...(resource.type ? {type: resource.type} : {}),
142
+ },
143
+ ...(parameters && Object.keys(parameters).length > 0 ? {parameters} : {}),
144
+ },
145
+ }
146
+
147
+ sendMessage(message.type, message.data)
148
+ } catch (error) {
149
+ // eslint-disable-next-line no-console
150
+ console.error('Failed to dispatch intent:', error)
151
+ throw error
152
+ }
153
+ }, [action, intentId, documentHandle, parameters, sendMessage])
154
+
155
+ return {
156
+ dispatchIntent,
157
+ }
158
+ }
@@ -0,0 +1,155 @@
1
+ import {
2
+ canvasSource,
3
+ datasetSource,
4
+ type DocumentHandle,
5
+ type DocumentSource,
6
+ mediaLibrarySource,
7
+ } from '@sanity/sdk'
8
+ import {describe, expect, it} from 'vitest'
9
+
10
+ import {type DocumentHandleWithSource} from '../types'
11
+ import {getResourceIdFromDocumentHandle} from './getResourceIdFromDocumentHandle'
12
+
13
+ describe('getResourceIdFromDocumentHandle', () => {
14
+ describe('with traditional DocumentHandle (projectId/dataset)', () => {
15
+ it('should return resource ID from projectId and dataset', () => {
16
+ const documentHandle: DocumentHandle = {
17
+ documentId: 'test-document-id',
18
+ documentType: 'test-document-type',
19
+ projectId: 'test-project-id',
20
+ dataset: 'test-dataset',
21
+ }
22
+
23
+ const result = getResourceIdFromDocumentHandle(documentHandle)
24
+
25
+ expect(result).toEqual({
26
+ id: 'test-project-id.test-dataset',
27
+ type: undefined,
28
+ })
29
+ })
30
+ })
31
+
32
+ describe('with DocumentHandleWithSource - media library', () => {
33
+ it('should return media library ID and resourceType when media library source is provided', () => {
34
+ const documentHandle: DocumentHandleWithSource = {
35
+ documentId: 'test-asset-id',
36
+ documentType: 'sanity.asset',
37
+ source: mediaLibrarySource('mlPGY7BEqt52'),
38
+ }
39
+
40
+ const result = getResourceIdFromDocumentHandle(documentHandle)
41
+
42
+ expect(result).toEqual({
43
+ id: 'mlPGY7BEqt52',
44
+ type: 'media-library',
45
+ })
46
+ })
47
+
48
+ it('should prioritize source over projectId/dataset when both are provided', () => {
49
+ const documentHandle: DocumentHandleWithSource = {
50
+ documentId: 'test-asset-id',
51
+ documentType: 'sanity.asset',
52
+ projectId: 'test-project-id',
53
+ dataset: 'test-dataset',
54
+ source: mediaLibrarySource('mlPGY7BEqt52'),
55
+ }
56
+
57
+ const result = getResourceIdFromDocumentHandle(documentHandle)
58
+
59
+ expect(result).toEqual({
60
+ id: 'mlPGY7BEqt52',
61
+ type: 'media-library',
62
+ })
63
+ })
64
+ })
65
+
66
+ describe('with DocumentHandleWithSource - canvas', () => {
67
+ it('should return canvas ID and resourceType when canvas source is provided', () => {
68
+ const documentHandle: DocumentHandleWithSource = {
69
+ documentId: 'test-canvas-document-id',
70
+ documentType: 'sanity.canvas.document',
71
+ source: canvasSource('canvas123'),
72
+ }
73
+
74
+ const result = getResourceIdFromDocumentHandle(documentHandle)
75
+
76
+ expect(result).toEqual({
77
+ id: 'canvas123',
78
+ type: 'canvas',
79
+ })
80
+ })
81
+ })
82
+
83
+ describe('with DocumentHandleWithSource - dataset source', () => {
84
+ it('should return dataset resource ID when dataset source is provided', () => {
85
+ const documentHandle: DocumentHandleWithSource = {
86
+ documentId: 'test-document-id',
87
+ documentType: 'test-document-type',
88
+ source: datasetSource('source-project-id', 'source-dataset'),
89
+ }
90
+
91
+ const result = getResourceIdFromDocumentHandle(documentHandle)
92
+
93
+ expect(result).toEqual({
94
+ id: 'source-project-id.source-dataset',
95
+ type: undefined,
96
+ })
97
+ })
98
+
99
+ it('should use dataset source over projectId/dataset when both are provided', () => {
100
+ const documentHandle: DocumentHandleWithSource = {
101
+ documentId: 'test-document-id',
102
+ documentType: 'test-document-type',
103
+ projectId: 'test-project-id',
104
+ dataset: 'test-dataset',
105
+ source: datasetSource('source-project-id', 'source-dataset'),
106
+ }
107
+
108
+ const result = getResourceIdFromDocumentHandle(documentHandle)
109
+
110
+ expect(result).toEqual({
111
+ id: 'source-project-id.source-dataset',
112
+ type: undefined,
113
+ })
114
+ })
115
+ })
116
+
117
+ describe('edge cases', () => {
118
+ it('should handle DocumentHandleWithSource with undefined source', () => {
119
+ const documentHandle: DocumentHandleWithSource = {
120
+ documentId: 'test-document-id',
121
+ documentType: 'test-document-type',
122
+ projectId: 'test-project-id',
123
+ dataset: 'test-dataset',
124
+ source: undefined,
125
+ }
126
+
127
+ const result = getResourceIdFromDocumentHandle(documentHandle)
128
+
129
+ expect(result).toEqual({
130
+ id: 'test-project-id.test-dataset',
131
+ type: undefined,
132
+ })
133
+ })
134
+
135
+ it('should fall back to projectId/dataset when source is not recognized', () => {
136
+ const documentHandle: DocumentHandleWithSource = {
137
+ documentId: 'test-document-id',
138
+ documentType: 'test-document-type',
139
+ projectId: 'test-project-id',
140
+ dataset: 'test-dataset',
141
+ source: {
142
+ __sanity_internal_sourceId: 'unknown-format',
143
+ } as unknown as DocumentSource,
144
+ }
145
+
146
+ const result = getResourceIdFromDocumentHandle(documentHandle)
147
+
148
+ // Falls back to projectId.dataset when source format is not recognized
149
+ expect(result).toEqual({
150
+ id: 'test-project-id.test-dataset',
151
+ type: undefined,
152
+ })
153
+ })
154
+ })
155
+ })
@@ -0,0 +1,53 @@
1
+ import {type DocumentHandle, type DocumentSource} from '@sanity/sdk'
2
+
3
+ import {type DocumentHandleWithSource} from '../types'
4
+
5
+ // Internal constant for accessing source ID
6
+ const SOURCE_ID = '__sanity_internal_sourceId' as const
7
+
8
+ interface DashboardMessageResource {
9
+ id: string
10
+ type?: 'media-library' | 'canvas'
11
+ }
12
+
13
+ const isDocumentHandleWithSource = (
14
+ documentHandle: DocumentHandle | DocumentHandleWithSource,
15
+ ): documentHandle is DocumentHandleWithSource => {
16
+ return 'source' in documentHandle
17
+ }
18
+
19
+ /** Currently only used for dispatching intents to the dashboard,
20
+ * but could easily be extended to other dashboard hooks
21
+ * @beta
22
+ */
23
+ export function getResourceIdFromDocumentHandle(
24
+ documentHandle: DocumentHandle | DocumentHandleWithSource,
25
+ ): DashboardMessageResource {
26
+ let source: DocumentSource | undefined
27
+
28
+ const {projectId, dataset} = documentHandle
29
+ if (isDocumentHandleWithSource(documentHandle)) {
30
+ source = documentHandle.source
31
+ }
32
+ let resourceId: string = projectId + '.' + dataset
33
+ let resourceType: 'media-library' | 'canvas' | undefined
34
+
35
+ if (source) {
36
+ const sourceId = (source as Record<string, unknown>)[SOURCE_ID]
37
+ if (Array.isArray(sourceId)) {
38
+ if (sourceId[0] === 'media-library' || sourceId[0] === 'canvas') {
39
+ resourceType = sourceId[0] as 'media-library' | 'canvas'
40
+ resourceId = sourceId[1] as string
41
+ }
42
+ } else if (sourceId && typeof sourceId === 'object' && 'projectId' in sourceId) {
43
+ const datasetSource = sourceId as {projectId: string; dataset: string}
44
+ // don't create type since it's ambiguous for project / dataset docs
45
+ resourceId = `${datasetSource.projectId}.${datasetSource.dataset}`
46
+ }
47
+ }
48
+
49
+ return {
50
+ id: resourceId,
51
+ type: resourceType,
52
+ }
53
+ }
@@ -1,20 +1,135 @@
1
- import {applyDocumentActions} from '@sanity/sdk'
1
+ import {applyDocumentActions, type SanityInstance} from '@sanity/sdk'
2
2
  import {describe, it} from 'vitest'
3
3
 
4
- import {createCallbackHook} from '../helpers/createCallbackHook'
4
+ import {useSanityInstance} from '../context/useSanityInstance'
5
+ import {useApplyDocumentActions} from './useApplyDocumentActions'
5
6
 
6
- vi.mock('../helpers/createCallbackHook', () => ({
7
- createCallbackHook: vi.fn((cb) => () => cb),
8
- }))
9
7
  vi.mock('@sanity/sdk', async (importOriginal) => {
10
8
  const original = await importOriginal<typeof import('@sanity/sdk')>()
11
9
  return {...original, applyDocumentActions: vi.fn()}
12
10
  })
13
11
 
12
+ vi.mock('../context/useSanityInstance')
13
+
14
+ // These are quite fragile mocks, but they are useful enough for now.
15
+ const instances: Record<string, SanityInstance | undefined> = {
16
+ 'p123.d': {__id: 'p123.d'} as unknown as SanityInstance,
17
+ 'p.d123': {__id: 'p.d123'} as unknown as SanityInstance,
18
+ 'p123.d123': {__id: 'p123.d123'} as unknown as SanityInstance,
19
+ }
20
+
21
+ const instance = {
22
+ match({projectId = 'p', dataset = 'd'}): SanityInstance | undefined {
23
+ return instances[`${projectId}.${dataset}`]
24
+ },
25
+ } as unknown as SanityInstance
26
+
14
27
  describe('useApplyDocumentActions', () => {
15
- it('calls `createCallbackHook` with `applyActions`', async () => {
16
- expect(createCallbackHook).not.toHaveBeenCalled()
17
- await import('./useApplyDocumentActions')
18
- expect(createCallbackHook).toHaveBeenCalledWith(applyDocumentActions)
28
+ beforeEach(() => {
29
+ vi.resetAllMocks()
30
+ vi.mocked(useSanityInstance).mockReturnValueOnce(instance)
31
+ })
32
+
33
+ it('uses the SanityInstance', async () => {
34
+ const apply = useApplyDocumentActions()
35
+ apply({
36
+ type: 'document.edit',
37
+ documentType: 'post',
38
+ documentId: 'abc',
39
+ })
40
+
41
+ expect(applyDocumentActions).toHaveBeenCalledExactlyOnceWith(instance, {
42
+ actions: [
43
+ {
44
+ type: 'document.edit',
45
+ documentType: 'post',
46
+ documentId: 'abc',
47
+ },
48
+ ],
49
+ })
50
+ })
51
+
52
+ it('uses SanityInstance.match when projectId is overrideen', async () => {
53
+ const apply = useApplyDocumentActions()
54
+ apply({
55
+ type: 'document.edit',
56
+ documentType: 'post',
57
+ documentId: 'abc',
58
+
59
+ projectId: 'p123',
60
+ })
61
+
62
+ expect(applyDocumentActions).toHaveBeenCalledExactlyOnceWith(instances['p123.d'], {
63
+ actions: [
64
+ {
65
+ type: 'document.edit',
66
+ documentType: 'post',
67
+ documentId: 'abc',
68
+
69
+ projectId: 'p123',
70
+ },
71
+ ],
72
+ })
73
+ })
74
+
75
+ it('uses SanityInstance when dataset is overrideen', async () => {
76
+ const apply = useApplyDocumentActions()
77
+ apply({
78
+ type: 'document.edit',
79
+ documentType: 'post',
80
+ documentId: 'abc',
81
+
82
+ dataset: 'd123',
83
+ })
84
+
85
+ expect(applyDocumentActions).toHaveBeenCalledExactlyOnceWith(instance, {
86
+ actions: [
87
+ {
88
+ type: 'document.edit',
89
+ documentType: 'post',
90
+ documentId: 'abc',
91
+
92
+ dataset: 'd123',
93
+ },
94
+ ],
95
+ })
96
+ })
97
+
98
+ it('uses SanityInstance.amcth when projectId and dataset is overrideen', async () => {
99
+ const apply = useApplyDocumentActions()
100
+ apply({
101
+ type: 'document.edit',
102
+ documentType: 'post',
103
+ documentId: 'abc',
104
+
105
+ projectId: 'p123',
106
+ dataset: 'd123',
107
+ })
108
+
109
+ expect(applyDocumentActions).toHaveBeenCalledExactlyOnceWith(instances['p123.d123'], {
110
+ actions: [
111
+ {
112
+ type: 'document.edit',
113
+ documentType: 'post',
114
+ documentId: 'abc',
115
+
116
+ projectId: 'p123',
117
+ dataset: 'd123',
118
+ },
119
+ ],
120
+ })
121
+ })
122
+
123
+ it("throws if SanityInstance.match doesn't find anything", async () => {
124
+ const apply = useApplyDocumentActions()
125
+ expect(() => {
126
+ apply({
127
+ type: 'document.edit',
128
+ documentType: 'post',
129
+ documentId: 'abc',
130
+
131
+ projectId: 'other',
132
+ })
133
+ }).toThrow()
19
134
  })
20
135
  })
@@ -6,7 +6,7 @@ import {
6
6
  } from '@sanity/sdk'
7
7
  import {type SanityDocument} from 'groq'
8
8
 
9
- import {createCallbackHook} from '../helpers/createCallbackHook'
9
+ import {useSanityInstance} from '../context/useSanityInstance'
10
10
  // this import is used in an `{@link useEditDocument}`
11
11
  // eslint-disable-next-line unused-imports/no-unused-imports, import/consistent-type-specifier-style
12
12
  import type {useEditDocument} from './useEditDocument'
@@ -118,7 +118,78 @@ interface UseApplyDocumentActions {
118
118
  * )
119
119
  * }
120
120
  * ```
121
+ *
122
+ * @example Create a document with initial field values
123
+ * ```tsx
124
+ * import {
125
+ * createDocument,
126
+ * createDocumentHandle,
127
+ * useApplyDocumentActions
128
+ * } from '@sanity/sdk-react'
129
+ *
130
+ * function CreateArticleButton() {
131
+ * const apply = useApplyDocumentActions()
132
+ *
133
+ * const handleCreateArticle = () => {
134
+ * const newDocHandle = createDocumentHandle({
135
+ * documentId: crypto.randomUUID(),
136
+ * documentType: 'article'
137
+ * })
138
+ *
139
+ * // Create document with initial values in one action
140
+ * apply(
141
+ * createDocument(newDocHandle, {
142
+ * title: 'New Article',
143
+ * author: 'John Doe',
144
+ * publishedAt: new Date().toISOString(),
145
+ * })
146
+ * )
147
+ * }
148
+ *
149
+ * return <button onClick={handleCreateArticle}>Create Article</button>
150
+ * }
151
+ * ```
121
152
  */
122
- export const useApplyDocumentActions = createCallbackHook(
123
- applyDocumentActions,
124
- ) as UseApplyDocumentActions
153
+ export const useApplyDocumentActions: UseApplyDocumentActions = () => {
154
+ const instance = useSanityInstance()
155
+
156
+ return (actionOrActions, options) => {
157
+ const actions = Array.isArray(actionOrActions) ? actionOrActions : [actionOrActions]
158
+
159
+ let projectId
160
+ let dataset
161
+ for (const action of actions) {
162
+ if (action.projectId) {
163
+ if (!projectId) projectId = action.projectId
164
+ if (action.projectId !== projectId) {
165
+ throw new Error(
166
+ `Mismatched project IDs found in actions. All actions must belong to the same project. Found "${action.projectId}" but expected "${projectId}".`,
167
+ )
168
+ }
169
+
170
+ if (action.dataset) {
171
+ if (!dataset) dataset = action.dataset
172
+ if (action.dataset !== dataset) {
173
+ throw new Error(
174
+ `Mismatched datasets found in actions. All actions must belong to the same dataset. Found "${action.dataset}" but expected "${dataset}".`,
175
+ )
176
+ }
177
+ }
178
+ }
179
+ }
180
+
181
+ if (projectId || dataset) {
182
+ const actualInstance = instance.match({projectId, dataset})
183
+ if (!actualInstance) {
184
+ throw new Error(
185
+ `Could not find a matching Sanity instance for the requested action: ${JSON.stringify({projectId, dataset}, null, 2)}.
186
+ Please ensure there is a ResourceProvider component with a matching configuration in the component hierarchy.`,
187
+ )
188
+ }
189
+
190
+ return applyDocumentActions(actualInstance, {actions, ...options})
191
+ }
192
+
193
+ return applyDocumentActions(instance, {actions, ...options})
194
+ }
195
+ }
@@ -97,7 +97,7 @@ describe('usePermissions', () => {
97
97
  })
98
98
 
99
99
  // ResourceProvider handles the instance configuration
100
- expect(getPermissionsState).toHaveBeenCalledWith(expect.any(Object), mockAction)
100
+ expect(getPermissionsState).toHaveBeenCalledWith(expect.any(Object), {actions: [mockAction]})
101
101
  expect(result.current).toEqual(mockPermissionAllowed)
102
102
  })
103
103
 
@@ -140,7 +140,7 @@ describe('usePermissions', () => {
140
140
  ),
141
141
  })
142
142
 
143
- expect(getPermissionsState).toHaveBeenCalledWith(expect.any(Object), actions)
143
+ expect(getPermissionsState).toHaveBeenCalledWith(expect.any(Object), {actions})
144
144
  })
145
145
 
146
146
  it('should throw an error if actions have mismatched project IDs', () => {
@@ -226,7 +226,7 @@ describe('usePermissions', () => {
226
226
 
227
227
  // Now it should render properly
228
228
  await waitFor(() => {
229
- expect(getPermissionsState).toHaveBeenCalledWith(expect.any(Object), mockAction)
229
+ expect(getPermissionsState).toHaveBeenCalledWith(expect.any(Object), {actions: [mockAction]})
230
230
  })
231
231
  })
232
232
 
@@ -84,7 +84,10 @@ import {useSanityInstance} from '../context/useSanityInstance'
84
84
  export function useDocumentPermissions(
85
85
  actionOrActions: DocumentAction | DocumentAction[],
86
86
  ): DocumentPermissionsResult {
87
- const actions = Array.isArray(actionOrActions) ? actionOrActions : [actionOrActions]
87
+ const actions = useMemo(
88
+ () => (Array.isArray(actionOrActions) ? actionOrActions : [actionOrActions]),
89
+ [actionOrActions],
90
+ )
88
91
  // if actions is an array, we need to check that all actions belong to the same project and dataset
89
92
  let projectId
90
93
  let dataset
@@ -111,20 +114,20 @@ export function useDocumentPermissions(
111
114
 
112
115
  const instance = useSanityInstance({projectId, dataset})
113
116
  const isDocumentReady = useCallback(
114
- () => getPermissionsState(instance, actionOrActions).getCurrent() !== undefined,
115
- [actionOrActions, instance],
117
+ () => getPermissionsState(instance, {actions}).getCurrent() !== undefined,
118
+ [actions, instance],
116
119
  )
117
120
  if (!isDocumentReady()) {
118
121
  throw firstValueFrom(
119
- getPermissionsState(instance, actionOrActions).observable.pipe(
122
+ getPermissionsState(instance, {actions}).observable.pipe(
120
123
  filter((result) => result !== undefined),
121
124
  ),
122
125
  )
123
126
  }
124
127
 
125
128
  const {subscribe, getCurrent} = useMemo(
126
- () => getPermissionsState(instance, actionOrActions),
127
- [actionOrActions, instance],
129
+ () => getPermissionsState(instance, {actions}),
130
+ [actions, instance],
128
131
  )
129
132
 
130
133
  return useSyncExternalStore(subscribe, getCurrent) as DocumentPermissionsResult