@sanity/sdk-react 0.0.1 → 0.0.3

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 (31) hide show
  1. package/README.md +3 -3
  2. package/dist/index.d.ts +364 -287
  3. package/dist/index.js +125 -149
  4. package/dist/index.js.map +1 -1
  5. package/package.json +5 -5
  6. package/src/_exports/sdk-react.ts +10 -10
  7. package/src/hooks/comlink/useWindowConnection.test.tsx +145 -0
  8. package/src/hooks/comlink/useWindowConnection.ts +32 -30
  9. package/src/hooks/{comlink → dashboard}/useManageFavorite.test.ts +84 -134
  10. package/src/hooks/{comlink → dashboard}/useManageFavorite.ts +4 -39
  11. package/src/hooks/dashboard/useNavigateToStudioDocument.test.ts +2 -73
  12. package/src/hooks/dashboard/useNavigateToStudioDocument.ts +20 -27
  13. package/src/hooks/dashboard/useRecordDocumentHistoryEvent.test.ts +69 -0
  14. package/src/hooks/{comlink → dashboard}/useRecordDocumentHistoryEvent.ts +17 -10
  15. package/src/hooks/dashboard/useStudioWorkspacesByProjectIdDataset.test.tsx +14 -85
  16. package/src/hooks/dashboard/useStudioWorkspacesByProjectIdDataset.ts +33 -8
  17. package/src/hooks/document/useApplyDocumentActions.ts +4 -4
  18. package/src/hooks/document/useDocument.test.ts +8 -10
  19. package/src/hooks/document/useDocument.ts +50 -33
  20. package/src/hooks/document/useDocumentEvent.ts +2 -2
  21. package/src/hooks/document/useDocumentSyncStatus.ts +1 -1
  22. package/src/hooks/document/useEditDocument.ts +15 -15
  23. package/src/hooks/documents/useDocuments.ts +5 -5
  24. package/src/hooks/paginatedDocuments/usePaginatedDocuments.ts +5 -5
  25. package/src/hooks/preview/{usePreview.test.tsx → useDocumentPreview.test.tsx} +5 -5
  26. package/src/hooks/preview/{usePreview.tsx → useDocumentPreview.tsx} +11 -8
  27. package/src/hooks/projection/{useProjection.test.tsx → useDocumentProjection.test.tsx} +5 -5
  28. package/src/hooks/projection/{useProjection.ts → useDocumentProjection.ts} +22 -17
  29. package/src/hooks/query/useQuery.ts +5 -5
  30. package/src/hooks/comlink/useRecordDocumentHistoryEvent.test.ts +0 -85
  31. package/src/hooks/comlink/useWindowConnection.test.ts +0 -135
@@ -8,7 +8,7 @@ import {act, render, screen} from '@testing-library/react'
8
8
  import {Suspense, useRef} from 'react'
9
9
  import {type Mock} from 'vitest'
10
10
 
11
- import {useProjection} from './useProjection'
11
+ import {useDocumentProjection} from './useDocumentProjection'
12
12
 
13
13
  // Mock IntersectionObserver
14
14
  const mockIntersectionObserver = vi.fn()
@@ -61,7 +61,7 @@ function TestComponent({
61
61
  projection: ValidProjection
62
62
  }) {
63
63
  const ref = useRef(null)
64
- const {data, isPending} = useProjection<ProjectionResult>({...document, projection, ref})
64
+ const {data, isPending} = useDocumentProjection<ProjectionResult>({...document, projection, ref})
65
65
 
66
66
  return (
67
67
  <div ref={ref}>
@@ -72,7 +72,7 @@ function TestComponent({
72
72
  )
73
73
  }
74
74
 
75
- describe('useProjection', () => {
75
+ describe('useDocumentProjection', () => {
76
76
  let getCurrent: Mock
77
77
  let subscribe: Mock
78
78
 
@@ -228,7 +228,7 @@ describe('useProjection', () => {
228
228
  projection,
229
229
  ...docHandle
230
230
  }: DocumentHandle & {projection: ValidProjection}) {
231
- const {data} = useProjection<ProjectionResult>({...docHandle, projection}) // No ref provided
231
+ const {data} = useDocumentProjection<ProjectionResult>({...docHandle, projection}) // No ref provided
232
232
  return (
233
233
  <div>
234
234
  <h1>{data.title}</h1>
@@ -261,7 +261,7 @@ describe('useProjection', () => {
261
261
  ...docHandle
262
262
  }: DocumentHandle & {projection: ValidProjection}) {
263
263
  const ref = useRef({}) // ref.current is not an HTML element
264
- const {data} = useProjection<ProjectionResult>({...docHandle, projection, ref})
264
+ const {data} = useDocumentProjection<ProjectionResult>({...docHandle, projection, ref})
265
265
  return (
266
266
  <div>
267
267
  <h1>{data.title}</h1>
@@ -14,7 +14,7 @@ import {useSanityInstance} from '../context/useSanityInstance'
14
14
  * @public
15
15
  * @category Types
16
16
  */
17
- export interface UseProjectionOptions<
17
+ export interface useDocumentProjectionOptions<
18
18
  TProjection extends ValidProjection = ValidProjection,
19
19
  TDocumentType extends string = string,
20
20
  TDataset extends string = string,
@@ -32,7 +32,7 @@ export interface UseProjectionOptions<
32
32
  * @public
33
33
  * @category Types
34
34
  */
35
- export interface UseProjectionResults<TData> {
35
+ export interface useDocumentProjectionResults<TData> {
36
36
  /** The projected data */
37
37
  data: TData
38
38
  /** True if the projection is currently being resolved */
@@ -59,7 +59,7 @@ export interface UseProjectionResults<TData> {
59
59
 
60
60
  // Overload 1: Relies on Typegen
61
61
  /**
62
- * @beta
62
+ * @public
63
63
  * Fetch a projection, relying on Typegen for the return type based on the handle and projection.
64
64
  *
65
65
  * @category Documents
@@ -69,7 +69,7 @@ export interface UseProjectionResults<TData> {
69
69
  * @example Using Typegen for a book preview
70
70
  * ```tsx
71
71
  * // ProjectionComponent.tsx
72
- * import {useProjection, type DocumentHandle} from '@sanity/sdk-react'
72
+ * import {useDocumentProjection, type DocumentHandle} from '@sanity/sdk-react'
73
73
  * import {useRef} from 'react'
74
74
  * import {defineProjection} from 'groq'
75
75
  *
@@ -90,7 +90,7 @@ export interface UseProjectionResults<TData> {
90
90
  *
91
91
  * // Spread the doc handle into the options
92
92
  * // Typegen infers the return type based on 'book' and the projection
93
- * const { data } = useProjection({
93
+ * const { data } = useDocumentProjection({
94
94
  * ...doc, // Pass the handle properties
95
95
  * ref,
96
96
  * projection: myProjection,
@@ -114,18 +114,20 @@ export interface UseProjectionResults<TData> {
114
114
  * // </Suspense>
115
115
  * ```
116
116
  */
117
- export function useProjection<
117
+ export function useDocumentProjection<
118
118
  TProjection extends ValidProjection = ValidProjection,
119
119
  TDocumentType extends string = string,
120
120
  TDataset extends string = string,
121
121
  TProjectId extends string = string,
122
122
  >(
123
- options: UseProjectionOptions<TProjection, TDocumentType, TDataset, TProjectId>,
124
- ): UseProjectionResults<SanityProjectionResult<TProjection, TDocumentType, TDataset, TProjectId>>
123
+ options: useDocumentProjectionOptions<TProjection, TDocumentType, TDataset, TProjectId>,
124
+ ): useDocumentProjectionResults<
125
+ SanityProjectionResult<TProjection, TDocumentType, `${TProjectId}.${TDataset}`>
126
+ >
125
127
 
126
128
  // Overload 2: Explicit type provided
127
129
  /**
128
- * @beta
130
+ * @public
129
131
  * Fetch a projection with an explicitly defined return type `TData`.
130
132
  *
131
133
  * @param options - Options including the document handle properties (`documentId`, etc.) and the `projection`.
@@ -133,7 +135,7 @@ export function useProjection<
133
135
  *
134
136
  * @example Explicitly typing the projection result
135
137
  * ```tsx
136
- * import {useProjection, type DocumentHandle} from '@sanity/sdk-react'
138
+ * import {useDocumentProjection, type DocumentHandle} from '@sanity/sdk-react'
137
139
  * import {useRef} from 'react'
138
140
  *
139
141
  * interface SimpleBookPreview {
@@ -147,7 +149,7 @@ export function useProjection<
147
149
  *
148
150
  * function BookPreview({ doc }: BookPreviewProps) {
149
151
  * const ref = useRef(null)
150
- * const { data } = useProjection<SimpleBookPreview>({
152
+ * const { data } = useDocumentProjection<SimpleBookPreview>({
151
153
  * ...doc,
152
154
  * ref,
153
155
  * projection: `{ title, 'authorName': author->name }`
@@ -169,16 +171,16 @@ export function useProjection<
169
171
  * // </Suspense>
170
172
  * ```
171
173
  */
172
- export function useProjection<TData extends object>(
173
- options: UseProjectionOptions, // Uses base options type
174
- ): UseProjectionResults<TData>
174
+ export function useDocumentProjection<TData extends object>(
175
+ options: useDocumentProjectionOptions, // Uses base options type
176
+ ): useDocumentProjectionResults<TData>
175
177
 
176
178
  // Implementation (no JSDoc needed here as it's covered by overloads)
177
- export function useProjection<TData extends object>({
179
+ export function useDocumentProjection<TData extends object>({
178
180
  ref,
179
181
  projection,
180
182
  ...docHandle
181
- }: UseProjectionOptions): UseProjectionResults<TData> {
183
+ }: useDocumentProjectionOptions): useDocumentProjectionResults<TData> {
182
184
  const instance = useSanityInstance()
183
185
  const stateSource = getProjectionState<TData>(instance, {...docHandle, projection})
184
186
 
@@ -228,5 +230,8 @@ export function useProjection<TData extends object>({
228
230
  [stateSource, ref],
229
231
  )
230
232
 
231
- return useSyncExternalStore(subscribe, stateSource.getCurrent) as UseProjectionResults<TData>
233
+ return useSyncExternalStore(
234
+ subscribe,
235
+ stateSource.getCurrent,
236
+ ) as useDocumentProjectionResults<TData>
232
237
  }
@@ -12,7 +12,7 @@ import {useSanityInstance} from '../context/useSanityInstance'
12
12
 
13
13
  // Overload 1: Inferred Type (using Typegen)
14
14
  /**
15
- * @beta
15
+ * @public
16
16
  * Executes a GROQ query, inferring the result type from the query string and options.
17
17
  * Leverages Sanity Typegen if configured for enhanced type safety.
18
18
  *
@@ -74,14 +74,14 @@ export function useQuery<
74
74
  options: QueryOptions<TQuery, TDataset, TProjectId>,
75
75
  ): {
76
76
  /** The query result, typed based on the GROQ query string */
77
- data: SanityQueryResult<TQuery, TDataset, TProjectId>
77
+ data: SanityQueryResult<TQuery, `${TProjectId}.${TDataset}`>
78
78
  /** True if a query transition is in progress */
79
79
  isPending: boolean
80
80
  }
81
81
 
82
82
  // Overload 2: Explicit Type Provided
83
83
  /**
84
- * @beta
84
+ * @public
85
85
  * Executes a GROQ query with an explicitly provided result type `TData`.
86
86
  *
87
87
  * @param options - Configuration for the query, including `query`, optional `params`, `projectId`, `dataset`, etc.
@@ -116,7 +116,7 @@ export function useQuery<TData>(options: QueryOptions): {
116
116
  }
117
117
 
118
118
  /**
119
- * @beta
119
+ * @public
120
120
  * Fetches data and subscribes to real-time updates using a GROQ query.
121
121
  *
122
122
  * @remarks
@@ -182,7 +182,7 @@ export function useQuery(options: QueryOptions): {data: unknown; isPending: bool
182
182
  // the captured signal remains unchanged for this suspended render.
183
183
  // Thus, the promise thrown here uses a stable abort signal, ensuring correct behavior.
184
184
  const currentSignal = ref.current.signal
185
- // eslint-disable-next-line react-compiler/react-compiler
185
+
186
186
  throw resolveQuery(instance, {...deferred, signal: currentSignal})
187
187
  }
188
188
 
@@ -1,85 +0,0 @@
1
- import {type Message, type Node, type Status} from '@sanity/comlink'
2
- import {getOrCreateNode} from '@sanity/sdk'
3
- import {beforeEach, describe, expect, it, vi} from 'vitest'
4
-
5
- import {act, renderHook} from '../../../test/test-utils'
6
- import {useRecordDocumentHistoryEvent} from './useRecordDocumentHistoryEvent'
7
-
8
- vi.mock(import('@sanity/sdk'), async (importOriginal) => {
9
- const actual = await importOriginal()
10
- return {
11
- ...actual,
12
- getOrCreateNode: vi.fn(),
13
- releaseNode: vi.fn(),
14
- }
15
- })
16
-
17
- describe('useRecordDocumentHistoryEvent', () => {
18
- let node: Node<Message, Message>
19
- let statusCallback: ((status: Status) => void) | null = null
20
-
21
- const mockDocumentHandle = {
22
- documentId: 'mock-id',
23
- documentType: 'mock-type',
24
- resourceType: 'studio' as const,
25
- resourceId: 'mock-resource-id',
26
- }
27
-
28
- function createMockNode() {
29
- return {
30
- on: vi.fn(() => () => {}),
31
- post: vi.fn(),
32
- stop: vi.fn(),
33
- onStatus: vi.fn((callback) => {
34
- statusCallback = callback
35
- return () => {}
36
- }),
37
- } as unknown as Node<Message, Message>
38
- }
39
-
40
- beforeEach(() => {
41
- statusCallback = null
42
- node = createMockNode()
43
- vi.mocked(getOrCreateNode).mockReturnValue(node)
44
- })
45
-
46
- it('should initialize with correct connection status', () => {
47
- const {result} = renderHook(() => useRecordDocumentHistoryEvent(mockDocumentHandle))
48
-
49
- expect(result.current.isConnected).toBe(false)
50
-
51
- act(() => {
52
- statusCallback?.('connected')
53
- })
54
-
55
- expect(result.current.isConnected).toBe(true)
56
- })
57
-
58
- it('should send correct message when recording events', () => {
59
- const {result} = renderHook(() => useRecordDocumentHistoryEvent(mockDocumentHandle))
60
-
61
- result.current.recordEvent('viewed')
62
-
63
- expect(node.post).toHaveBeenCalledWith('dashboard/v1/events/history', {
64
- eventType: 'viewed',
65
- document: {
66
- id: 'mock-id',
67
- type: 'mock-type',
68
- resource: {
69
- id: 'mock-resource-id',
70
- type: 'studio',
71
- },
72
- },
73
- })
74
- })
75
-
76
- it('should handle errors when sending messages', () => {
77
- vi.mocked(node.post).mockImplementation(() => {
78
- throw new Error('Failed to send message')
79
- })
80
-
81
- const {result} = renderHook(() => useRecordDocumentHistoryEvent(mockDocumentHandle))
82
-
83
- expect(() => result.current.recordEvent('viewed')).toThrow('Failed to send message')
84
- })
85
- })
@@ -1,135 +0,0 @@
1
- import {type Message, type Node, type Status} from '@sanity/comlink'
2
- import {getOrCreateNode, releaseNode} from '@sanity/sdk'
3
- import {beforeEach, describe, expect, it, vi} from 'vitest'
4
-
5
- import {act, renderHook} from '../../../test/test-utils'
6
- import {useWindowConnection} from './useWindowConnection'
7
-
8
- vi.mock(import('@sanity/sdk'), async (importOriginal) => {
9
- const actual = await importOriginal()
10
- return {
11
- ...actual,
12
- getOrCreateNode: vi.fn(),
13
- createNode: vi.fn(),
14
- releaseNode: vi.fn(),
15
- }
16
- })
17
- interface TestMessage {
18
- type: 'TEST_MESSAGE'
19
- data: {someData: string}
20
- }
21
-
22
- interface AnotherMessage {
23
- type: 'ANOTHER_MESSAGE'
24
- data: {otherData: number}
25
- }
26
-
27
- type TestMessages = TestMessage | AnotherMessage
28
-
29
- describe('useWindowConnection', () => {
30
- let node: Node<Message, Message>
31
- let statusCallback: ((status: Status) => void) | null = null
32
-
33
- function createMockNode() {
34
- return {
35
- // return unsubscribe function
36
- on: vi.fn(() => () => {}),
37
- post: vi.fn(),
38
- stop: vi.fn(),
39
- onStatus: vi.fn((callback) => {
40
- statusCallback = callback
41
- return () => {}
42
- }),
43
- } as unknown as Node<Message, Message>
44
- }
45
-
46
- beforeEach(() => {
47
- statusCallback = null
48
- node = createMockNode()
49
- vi.mocked(getOrCreateNode).mockReturnValue(node as unknown as Node<Message, Message>)
50
- })
51
-
52
- it('should call onStatus callback when status changes', () => {
53
- const onStatusMock = vi.fn()
54
- renderHook(() =>
55
- useWindowConnection<TestMessages, TestMessages>({
56
- name: 'test',
57
- connectTo: 'window',
58
- onStatus: onStatusMock,
59
- }),
60
- )
61
-
62
- act(() => {
63
- statusCallback?.('connected')
64
- })
65
- expect(onStatusMock).toHaveBeenCalledWith('connected')
66
-
67
- act(() => {
68
- statusCallback?.('disconnected')
69
- })
70
- expect(onStatusMock).toHaveBeenCalledWith('disconnected')
71
- })
72
-
73
- it('should not throw if onStatus is not provided', () => {
74
- renderHook(() =>
75
- useWindowConnection<TestMessages, TestMessages>({
76
- name: 'test',
77
- connectTo: 'window',
78
- }),
79
- )
80
-
81
- expect(() => {
82
- act(() => {
83
- statusCallback?.('connected')
84
- })
85
- }).not.toThrow()
86
- })
87
-
88
- it('should register message handlers', () => {
89
- const mockHandler = vi.fn()
90
- const mockData = {someData: 'test'}
91
-
92
- renderHook(() =>
93
- useWindowConnection<TestMessages, TestMessages>({
94
- name: 'test',
95
- connectTo: 'window',
96
- onMessage: {
97
- TEST_MESSAGE: mockHandler,
98
- ANOTHER_MESSAGE: vi.fn(),
99
- },
100
- }),
101
- )
102
-
103
- const onCallback = vi.mocked(node.on).mock.calls[0][1]
104
- onCallback(mockData)
105
-
106
- expect(mockHandler).toHaveBeenCalledWith(mockData)
107
- })
108
-
109
- it('should send messages through the node', () => {
110
- const {result} = renderHook(() =>
111
- useWindowConnection<TestMessages, TestMessages>({
112
- name: 'test',
113
- connectTo: 'window',
114
- }),
115
- )
116
-
117
- result.current.sendMessage('TEST_MESSAGE', {someData: 'test'})
118
- expect(node.post).toHaveBeenCalledWith('TEST_MESSAGE', {someData: 'test'})
119
-
120
- result.current.sendMessage('ANOTHER_MESSAGE', {otherData: 123})
121
- expect(node.post).toHaveBeenCalledWith('ANOTHER_MESSAGE', {otherData: 123})
122
- })
123
-
124
- it('should cleanup on unmount', () => {
125
- const {unmount} = renderHook(() =>
126
- useWindowConnection<TestMessages, TestMessages>({
127
- name: 'test',
128
- connectTo: 'window',
129
- }),
130
- )
131
-
132
- unmount()
133
- expect(releaseNode).toHaveBeenCalled()
134
- })
135
- })