@tanstack/react-db 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -0
- package/dist/cjs/DbProvider.cjs +23 -0
- package/dist/cjs/DbProvider.cjs.map +1 -0
- package/dist/cjs/DbProvider.d.cts +9 -0
- package/dist/cjs/HydrationBoundary.cjs +16 -0
- package/dist/cjs/HydrationBoundary.cjs.map +1 -0
- package/dist/cjs/HydrationBoundary.d.cts +7 -0
- package/dist/cjs/index.cjs +6 -0
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +4 -1
- package/dist/cjs/live-query-internals.cjs +19 -0
- package/dist/cjs/live-query-internals.cjs.map +1 -0
- package/dist/cjs/live-query-internals.d.cts +9 -0
- package/dist/cjs/useLiveInfiniteQuery.cjs +76 -8
- package/dist/cjs/useLiveInfiniteQuery.cjs.map +1 -1
- package/dist/cjs/useLiveInfiniteQuery.d.cts +12 -58
- package/dist/cjs/useLiveQuery.cjs +263 -41
- package/dist/cjs/useLiveQuery.cjs.map +1 -1
- package/dist/cjs/useLiveQuery.d.cts +79 -33
- package/dist/cjs/useLiveSuspenseQuery.cjs +19 -5
- package/dist/cjs/useLiveSuspenseQuery.cjs.map +1 -1
- package/dist/cjs/useLiveSuspenseQuery.d.cts +22 -19
- package/dist/esm/DbProvider.d.ts +9 -0
- package/dist/esm/DbProvider.js +23 -0
- package/dist/esm/DbProvider.js.map +1 -0
- package/dist/esm/HydrationBoundary.d.ts +7 -0
- package/dist/esm/HydrationBoundary.js +16 -0
- package/dist/esm/HydrationBoundary.js.map +1 -0
- package/dist/esm/index.d.ts +4 -1
- package/dist/esm/index.js +6 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/live-query-internals.d.ts +9 -0
- package/dist/esm/live-query-internals.js +19 -0
- package/dist/esm/live-query-internals.js.map +1 -0
- package/dist/esm/useLiveInfiniteQuery.d.ts +12 -58
- package/dist/esm/useLiveInfiniteQuery.js +76 -8
- package/dist/esm/useLiveInfiniteQuery.js.map +1 -1
- package/dist/esm/useLiveQuery.d.ts +79 -33
- package/dist/esm/useLiveQuery.js +265 -43
- package/dist/esm/useLiveQuery.js.map +1 -1
- package/dist/esm/useLiveSuspenseQuery.d.ts +22 -19
- package/dist/esm/useLiveSuspenseQuery.js +19 -5
- package/dist/esm/useLiveSuspenseQuery.js.map +1 -1
- package/package.json +2 -2
- package/skills/react-db/SKILL.md +119 -90
- package/src/DbProvider.tsx +32 -0
- package/src/HydrationBoundary.tsx +25 -0
- package/src/index.ts +8 -1
- package/src/live-query-internals.ts +36 -0
- package/src/useLiveInfiniteQuery.ts +119 -65
- package/src/useLiveQuery.ts +441 -92
- package/src/useLiveSuspenseQuery.ts +56 -29
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
1
3
|
import { useRef } from 'react'
|
|
2
4
|
import { useLiveQuery } from './useLiveQuery'
|
|
5
|
+
import { getLiveQueryResultInfo } from './live-query-internals'
|
|
6
|
+
import type { UseLiveQueryConfig } from './useLiveQuery'
|
|
3
7
|
import type {
|
|
4
8
|
Collection,
|
|
5
9
|
Context,
|
|
@@ -15,18 +19,19 @@ import type {
|
|
|
15
19
|
/**
|
|
16
20
|
* Create a live query with React Suspense support
|
|
17
21
|
* @param queryFn - Query function that defines what data to fetch
|
|
18
|
-
* @param deps -
|
|
22
|
+
* @param deps - Deprecated array of dependencies that trigger query re-execution when changed
|
|
19
23
|
* @returns Object with reactive data and state - data is guaranteed to be defined
|
|
20
24
|
* @throws Promise when data is loading (caught by Suspense boundary)
|
|
21
25
|
* @throws Error when collection fails (caught by Error boundary)
|
|
22
26
|
* @example
|
|
23
27
|
* // Basic usage with Suspense
|
|
24
28
|
* function TodoList() {
|
|
25
|
-
* const { data } = useLiveSuspenseQuery(
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
29
|
+
* const { data } = useLiveSuspenseQuery({
|
|
30
|
+
* query: (q) =>
|
|
31
|
+
* q.from({ todos: todosCollection })
|
|
32
|
+
* .where(({ todos }) => eq(todos.completed, false))
|
|
33
|
+
* .select(({ todos }) => ({ id: todos.id, text: todos.text }))
|
|
34
|
+
* })
|
|
30
35
|
*
|
|
31
36
|
* return (
|
|
32
37
|
* <ul>
|
|
@@ -53,12 +58,11 @@ import type {
|
|
|
53
58
|
* // data is guaranteed to be the single item (or undefined if not found)
|
|
54
59
|
*
|
|
55
60
|
* @example
|
|
56
|
-
* //
|
|
57
|
-
* const { data } = useLiveSuspenseQuery(
|
|
58
|
-
* (q) => q.from({ todos: todosCollection })
|
|
61
|
+
* // Structured captured values are included in derived query identity and trigger re-suspension
|
|
62
|
+
* const { data } = useLiveSuspenseQuery({
|
|
63
|
+
* query: (q) => q.from({ todos: todosCollection })
|
|
59
64
|
* .where(({ todos }) => gt(todos.priority, minPriority)),
|
|
60
|
-
*
|
|
61
|
-
* )
|
|
65
|
+
* })
|
|
62
66
|
*
|
|
63
67
|
* @example
|
|
64
68
|
* // With Error boundary
|
|
@@ -87,9 +91,9 @@ import type {
|
|
|
87
91
|
* ✅ **Use conditional rendering instead:**
|
|
88
92
|
* ```ts
|
|
89
93
|
* function Profile({ userId }: { userId: string }) {
|
|
90
|
-
* const { data } = useLiveSuspenseQuery(
|
|
91
|
-
* (q) => q.from({ users }).where(({ users }) => eq(users.id, userId))
|
|
92
|
-
* )
|
|
94
|
+
* const { data } = useLiveSuspenseQuery({
|
|
95
|
+
* query: (q) => q.from({ users }).where(({ users }) => eq(users.id, userId)),
|
|
96
|
+
* })
|
|
93
97
|
* return <div>{data.name}</div>
|
|
94
98
|
* }
|
|
95
99
|
*
|
|
@@ -97,12 +101,9 @@ import type {
|
|
|
97
101
|
* {userId ? <Profile userId={userId} /> : <div>No user</div>}
|
|
98
102
|
* ```
|
|
99
103
|
*
|
|
100
|
-
* ✅ **
|
|
104
|
+
* ✅ **For optional inputs, conditionally render a component with complete query inputs:**
|
|
101
105
|
* ```ts
|
|
102
|
-
*
|
|
103
|
-
* (q) => userId ? q.from({ users }) : undefined, // ✅ Supported!
|
|
104
|
-
* [userId]
|
|
105
|
-
* )
|
|
106
|
+
* {userId ? <Profile userId={userId} /> : <div>No user</div>}
|
|
106
107
|
* ```
|
|
107
108
|
*/
|
|
108
109
|
// Overload 1: Accept query function that always returns QueryBuilder
|
|
@@ -116,6 +117,15 @@ export function useLiveSuspenseQuery<TContext extends Context>(
|
|
|
116
117
|
}
|
|
117
118
|
|
|
118
119
|
// Overload 2: Accept config object
|
|
120
|
+
export function useLiveSuspenseQuery<TContext extends Context>(
|
|
121
|
+
config: UseLiveQueryConfig<TContext>,
|
|
122
|
+
): {
|
|
123
|
+
state: Map<string | number, GetResult<TContext>>
|
|
124
|
+
data: InferResultType<TContext>
|
|
125
|
+
collection: Collection<GetResult<TContext>, string | number, {}>
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Overload 3: Accept legacy config object
|
|
119
129
|
export function useLiveSuspenseQuery<TContext extends Context>(
|
|
120
130
|
config: LiveQueryCollectionConfig<TContext>,
|
|
121
131
|
deps?: Array<unknown>,
|
|
@@ -125,7 +135,7 @@ export function useLiveSuspenseQuery<TContext extends Context>(
|
|
|
125
135
|
collection: Collection<GetResult<TContext>, string | number, {}>
|
|
126
136
|
}
|
|
127
137
|
|
|
128
|
-
// Overload
|
|
138
|
+
// Overload 4: Accept pre-created live query collection
|
|
129
139
|
export function useLiveSuspenseQuery<
|
|
130
140
|
TResult extends object,
|
|
131
141
|
TKey extends string | number,
|
|
@@ -138,7 +148,7 @@ export function useLiveSuspenseQuery<
|
|
|
138
148
|
collection: Collection<TResult, TKey, TUtils>
|
|
139
149
|
}
|
|
140
150
|
|
|
141
|
-
// Overload
|
|
151
|
+
// Overload 5: Accept pre-created live query collection with singleResult: true
|
|
142
152
|
export function useLiveSuspenseQuery<
|
|
143
153
|
TResult extends object,
|
|
144
154
|
TKey extends string | number,
|
|
@@ -154,16 +164,20 @@ export function useLiveSuspenseQuery<
|
|
|
154
164
|
// Implementation - uses useLiveQuery internally and adds Suspense logic
|
|
155
165
|
export function useLiveSuspenseQuery(
|
|
156
166
|
configOrQueryOrCollection: any,
|
|
157
|
-
deps
|
|
167
|
+
deps?: Array<unknown>,
|
|
158
168
|
) {
|
|
159
169
|
const promiseRef = useRef<Promise<void> | null>(null)
|
|
160
170
|
const collectionRef = useRef<Collection<any, any, any> | null>(null)
|
|
161
171
|
const hasBeenReadyRef = useRef(false)
|
|
162
172
|
|
|
163
173
|
// Use useLiveQuery to handle collection management and reactivity
|
|
164
|
-
const result =
|
|
174
|
+
const result =
|
|
175
|
+
deps === undefined
|
|
176
|
+
? useLiveQuery(configOrQueryOrCollection)
|
|
177
|
+
: useLiveQuery(configOrQueryOrCollection, deps)
|
|
178
|
+
const queryInfo = getLiveQueryResultInfo(result)
|
|
165
179
|
|
|
166
|
-
// Reset promise and ready state when
|
|
180
|
+
// Reset promise and ready state when query identity changes
|
|
167
181
|
if (collectionRef.current !== result.collection) {
|
|
168
182
|
promiseRef.current = null
|
|
169
183
|
collectionRef.current = result.collection
|
|
@@ -183,16 +197,20 @@ export function useLiveSuspenseQuery(
|
|
|
183
197
|
)
|
|
184
198
|
}
|
|
185
199
|
|
|
186
|
-
// It’s not recommended to suspend a render based on a store value returned by useSyncExternalStore.
|
|
187
|
-
// result.status is the snapshot from syncExternalStore. We read the fresh status from the collection reference instead.
|
|
188
200
|
const collectionStatus = result.collection.status
|
|
189
201
|
|
|
190
202
|
// Track when we reach ready state
|
|
191
|
-
if (
|
|
203
|
+
if (result.isReady) {
|
|
192
204
|
hasBeenReadyRef.current = true
|
|
193
205
|
promiseRef.current = null
|
|
194
206
|
}
|
|
195
207
|
|
|
208
|
+
const observerError = queryInfo.observer.getError()
|
|
209
|
+
if (observerError !== undefined && !hasBeenReadyRef.current) {
|
|
210
|
+
promiseRef.current = null
|
|
211
|
+
throw observerError
|
|
212
|
+
}
|
|
213
|
+
|
|
196
214
|
// Only throw errors during initial load (before first ready)
|
|
197
215
|
// After success, errors surface as stale data (matches TanStack Query behavior)
|
|
198
216
|
if (collectionStatus === `error` && !hasBeenReadyRef.current) {
|
|
@@ -202,10 +220,19 @@ export function useLiveSuspenseQuery(
|
|
|
202
220
|
throw new Error(`Collection "${result.collection.id}" failed to load`)
|
|
203
221
|
}
|
|
204
222
|
|
|
205
|
-
if (
|
|
223
|
+
if (!hasBeenReadyRef.current && (result.isLoading || result.isIdle)) {
|
|
224
|
+
if (queryInfo.client?._isSsrStreamingEnabled() && !queryInfo.queryHash) {
|
|
225
|
+
const reason = queryInfo.identityError
|
|
226
|
+
? `${queryInfo.identityError.reason} at ${queryInfo.identityError.path}`
|
|
227
|
+
: `the query has no stable identity`
|
|
228
|
+
throw new Error(
|
|
229
|
+
`Cannot stream this live query during SSR because ${reason}. Provide an explicit serializable queryKey.`,
|
|
230
|
+
)
|
|
231
|
+
}
|
|
232
|
+
|
|
206
233
|
// Create or reuse promise for current collection
|
|
207
234
|
if (!promiseRef.current) {
|
|
208
|
-
promiseRef.current =
|
|
235
|
+
promiseRef.current = queryInfo.observer.preload()
|
|
209
236
|
}
|
|
210
237
|
// THROW PROMISE - React Suspense catches this (React 18+ required)
|
|
211
238
|
// Note: We don't check React version here. In React <18, this will be caught
|