@tanstack/react-db 0.5.12 → 1.0.0-rc.1.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.
Files changed (40) hide show
  1. package/dist/cjs/live-query-internals.cjs.map +1 -1
  2. package/dist/cjs/live-query-internals.d.cts +3 -1
  3. package/dist/cjs/source-id-bindings.cjs +9 -42
  4. package/dist/cjs/source-id-bindings.cjs.map +1 -1
  5. package/dist/cjs/source-id-bindings.d.cts +6 -17
  6. package/dist/cjs/useLiveInfiniteQuery.cjs +93 -173
  7. package/dist/cjs/useLiveInfiniteQuery.cjs.map +1 -1
  8. package/dist/cjs/useLiveInfiniteQuery.d.cts +3 -14
  9. package/dist/cjs/useLiveQuery.cjs +187 -224
  10. package/dist/cjs/useLiveQuery.cjs.map +1 -1
  11. package/dist/cjs/useLiveQuery.d.cts +37 -136
  12. package/dist/cjs/useLiveSuspenseQuery.cjs +71 -64
  13. package/dist/cjs/useLiveSuspenseQuery.cjs.map +1 -1
  14. package/dist/cjs/useLiveSuspenseQuery.d.cts +10 -9
  15. package/dist/cjs/usePacedMutations.cjs.map +1 -1
  16. package/dist/cjs/usePacedMutations.d.cts +1 -1
  17. package/dist/esm/live-query-internals.d.ts +3 -1
  18. package/dist/esm/live-query-internals.js.map +1 -1
  19. package/dist/esm/source-id-bindings.d.ts +6 -17
  20. package/dist/esm/source-id-bindings.js +9 -40
  21. package/dist/esm/source-id-bindings.js.map +1 -1
  22. package/dist/esm/useLiveInfiniteQuery.d.ts +3 -14
  23. package/dist/esm/useLiveInfiniteQuery.js +95 -175
  24. package/dist/esm/useLiveInfiniteQuery.js.map +1 -1
  25. package/dist/esm/useLiveQuery.d.ts +37 -136
  26. package/dist/esm/useLiveQuery.js +189 -226
  27. package/dist/esm/useLiveQuery.js.map +1 -1
  28. package/dist/esm/useLiveSuspenseQuery.d.ts +10 -9
  29. package/dist/esm/useLiveSuspenseQuery.js +71 -64
  30. package/dist/esm/useLiveSuspenseQuery.js.map +1 -1
  31. package/dist/esm/usePacedMutations.d.ts +1 -1
  32. package/dist/esm/usePacedMutations.js.map +1 -1
  33. package/package.json +3 -3
  34. package/skills/react-db/SKILL.md +24 -15
  35. package/src/live-query-internals.ts +3 -0
  36. package/src/source-id-bindings.ts +16 -62
  37. package/src/useLiveInfiniteQuery.ts +138 -296
  38. package/src/useLiveQuery.ts +329 -495
  39. package/src/useLiveSuspenseQuery.ts +122 -132
  40. package/src/usePacedMutations.ts +1 -1
@@ -9,8 +9,8 @@ description: >
9
9
  useLiveInfiniteQuery for cursor-based pagination (pageSize, fetchNextPage,
10
10
  hasNextPage, isFetchingNextPage). usePacedMutations for debounced React
11
11
  state updates. Return shape: data, state, collection, status, isLoading,
12
- isReady, isError. Import from @tanstack/react-db (re-exports all of
13
- @tanstack/db).
12
+ isReady, isLoadingError, isCrashed, isSyncError, health. Import from
13
+ @tanstack/react-db (re-exports all of @tanstack/db).
14
14
  type: framework
15
15
  library: db
16
16
  framework: react
@@ -69,9 +69,12 @@ const {
69
69
  status,
70
70
  isLoading,
71
71
  isReady,
72
- isError,
72
+ isLoadingError, // the first load failed: nothing to show
73
+ isCrashed, // status 'crashed': local storage broke
73
74
  isIdle,
74
75
  isCleanedUp,
76
+ isSyncError, // data is shown, but a sync problem is in health
77
+ health, // health tree: one node for the query and one per source
75
78
  } = useLiveQuery({
76
79
  query: (q) =>
77
80
  q
@@ -86,7 +89,7 @@ const { data } = useLiveQuery({
86
89
  })
87
90
 
88
91
  // Pre-created collection (from route loader)
89
- const { data } = useLiveQuery(preloadedCollection)
92
+ const { data } = useLiveQuery(preloadedTodos)
90
93
 
91
94
  // Conditional query — derived identity handles enabled/disabled transitions
92
95
  const { data, status } = useLiveQuery({
@@ -100,6 +103,10 @@ const { data, status } = useLiveQuery({
100
103
  // When disabled: status='disabled', data=undefined
101
104
  ```
102
105
 
106
+ `isLoadingError` means the first load failed and there is nothing to show; offer `collection.reload()`. `isCrashed` means local storage broke; only `cleanup({ dependents: true })` and a fresh start recover. After the query is ready, sync problems never change `status`: read `isSyncError` or the `health` tree to show stale-data warnings. `isError` and node `phase` were removed.
107
+
108
+ Many queries of one shape, such as one per list row, can share one partition. Pass `pool: pooledLiveQueries` (from `@tanstack/db`) in the config form: `useLiveQuery({ query, pool: pooledLiveQueries })`. Pooling applies to queries that filter one eager source with `eq(field, literal)`. It is off by default, and Suspense and `DbClient` queries never pool.
109
+
103
110
  ### useLiveSuspenseQuery
104
111
 
105
112
  ```tsx
@@ -171,7 +178,7 @@ const mutate = usePacedMutations({
171
178
 
172
179
  ## Includes (Hierarchical Data)
173
180
 
174
- When a query uses includes (subqueries in `select`), each child field is a live `Collection` by default. Subscribe to it with `useLiveQuery` in a subcomponent:
181
+ When a query uses `include()` for a subquery in `select`, each child field is a live `Collection`. Subscribe to it with `useLiveQuery` in a subcomponent:
175
182
 
176
183
  ```tsx
177
184
  function ProjectList() {
@@ -180,10 +187,12 @@ function ProjectList() {
180
187
  q.from({ p: projectsCollection }).select(({ p }) => ({
181
188
  id: p.id,
182
189
  name: p.name,
183
- issues: q
184
- .from({ i: issuesCollection })
185
- .where(({ i }) => eq(i.projectId, p.id))
186
- .select(({ i }) => ({ id: i.id, title: i.title })),
190
+ issues: include(
191
+ q
192
+ .from({ i: issuesCollection })
193
+ .where(({ i }) => eq(i.projectId, p.id))
194
+ .select(({ i }) => ({ id: i.id, title: i.title })),
195
+ ),
187
196
  })),
188
197
  })
189
198
 
@@ -241,8 +250,8 @@ See db-core/live-queries/SKILL.md for full includes rules (correlation condition
241
250
 
242
251
  Live query results include computed, read-only virtual properties on every row:
243
252
 
244
- - `$synced`: `true` when no pending local optimistic write affects the row;
245
- `false` while one does. It does not prove backend confirmation.
253
+ - `$hasPendingWrites`: `true` while a pending local optimistic write affects
254
+ the row; `false` otherwise. It does not prove backend confirmation.
246
255
  - `$origin`: `"local"` if the last confirmed change came from this client, otherwise `"remote"`.
247
256
  - `$key`: the row key for the result.
248
257
  - `$collectionId`: the source collection ID.
@@ -254,7 +263,7 @@ const { data } = useLiveQuery({
254
263
  query: (q) =>
255
264
  q
256
265
  .from({ todo: todoCollection })
257
- .where(({ todo }) => eq(todo.$synced, false)),
266
+ .where(({ todo }) => eq(todo.$hasPendingWrites, true)),
258
267
  })
259
268
  // Shows rows with pending local optimistic writes
260
269
  ```
@@ -294,9 +303,9 @@ const { data } = useLiveQuery({
294
303
  })
295
304
  ```
296
305
 
297
- Before 1.0, opaque IR warns and keeps legacy mount-stable identity. Slow or
298
- repeated derived identity work also warns once. Both point to the same
299
- `queryKey` escape hatch; unhashable IR without a key will throw in 1.0.
306
+ Unhashable IR without `queryKey` throws `UnhashableQueryIRError`, even with a
307
+ dependency array. Slow or repeated derived identity work warns once and points
308
+ to the same `queryKey` escape hatch.
300
309
 
301
310
  ### Suspense + Error Boundary
302
311
 
@@ -1,5 +1,6 @@
1
1
  import type {
2
2
  DbClient,
3
+ DeferredLiveQueryCollections,
3
4
  LiveQueryObserver,
4
5
  UnhashableQueryIRError,
5
6
  } from '@tanstack/db'
@@ -11,6 +12,8 @@ export type LiveQueryResultInfo = {
11
12
  queryHash: string | undefined
12
13
  identityError: UnhashableQueryIRError | undefined
13
14
  observer: LiveQueryObserver<object, string | number>
15
+ /** Sync starts this render deferred, released if the render throws. */
16
+ deferredCollections: DeferredLiveQueryCollections
14
17
  }
15
18
 
16
19
  type ResultWithInfo = {
@@ -1,75 +1,29 @@
1
1
  import { getPreparedLiveQuerySources } from '@tanstack/db'
2
2
  import type { DbClient } from '@tanstack/db'
3
3
 
4
- export { resumeDeferredLiveQueryCollections as resumeSyncStarts } from '@tanstack/db'
5
-
6
- const sourceObjectTokens = new WeakMap<object, number>()
7
- let nextSourceObjectToken = 0
8
-
9
- export function getSourceObjectToken(source: object): number {
10
- let id = sourceObjectTokens.get(source)
11
- if (id === undefined) {
12
- id = ++nextSourceObjectToken
13
- sourceObjectTokens.set(source, id)
4
+ /**
5
+ * Reports a descriptor source that no DbClient bound, naming the provider a
6
+ * React tree needs. Returns `undefined` when a client can bind descriptors.
7
+ */
8
+ export function getUnboundDescriptorReporter(
9
+ client: DbClient | undefined,
10
+ hookName: string,
11
+ ): ((alias: string) => never) | undefined {
12
+ if (client) return undefined
13
+ return (alias) => {
14
+ throw new Error(
15
+ `[${hookName}] Collection descriptor "${alias}" requires a DbClient when the query is consumed. Wrap this component in <DbProvider client={client}> or use a concrete Collection.`,
16
+ )
14
17
  }
15
- return id
16
18
  }
17
19
 
18
20
  export function getPreparedSources(
19
21
  preparedValue: unknown,
20
- client?: DbClient,
21
- hookName?: string,
22
+ client: DbClient | undefined,
23
+ hookName: string,
22
24
  ): Array<{ id: string }> {
23
25
  return getPreparedLiveQuerySources(
24
26
  preparedValue,
25
- !client && hookName
26
- ? (alias) => {
27
- throw new Error(
28
- `[${hookName}] Collection descriptor "${alias}" requires a DbClient when the query is consumed. Wrap this component in <DbProvider client={client}> or use a concrete Collection.`,
29
- )
30
- }
31
- : undefined,
27
+ getUnboundDescriptorReporter(client, hookName),
32
28
  )
33
29
  }
34
-
35
- /** The source object each Collection ID named while one hook is mounted. */
36
- export type SourceIdBindings = {
37
- unscoped: Map<string, number>
38
- byClient: WeakMap<DbClient, Map<string, number>>
39
- }
40
-
41
- export function createSourceIdBindings(): SourceIdBindings {
42
- return { unscoped: new Map(), byClient: new WeakMap() }
43
- }
44
-
45
- /**
46
- * Derived query identity names each source by its Collection ID. While a hook
47
- * is mounted, an ID must keep naming one source object in each client scope,
48
- * or the hook could reuse a live query that reads the old source. Claims the
49
- * prepared value's sources, or calls `release` and throws on a collision so a
50
- * rejected render keeps no sync-start deferral.
51
- */
52
- export function claimSourceIds(
53
- bindings: SourceIdBindings,
54
- preparedValue: unknown,
55
- client: DbClient | undefined,
56
- hookName: string,
57
- release: () => void,
58
- ): void {
59
- const prior = client ? bindings.byClient.get(client) : bindings.unscoped
60
- const seen = new Map<string, number>()
61
- for (const source of getPreparedSources(preparedValue, client, hookName)) {
62
- const token = getSourceObjectToken(source)
63
- const previous = seen.get(source.id) ?? prior?.get(source.id)
64
- if (previous !== undefined && previous !== token) {
65
- release()
66
- throw new Error(
67
- `[${hookName}] Source Collection "${source.id}" was replaced by a different Collection with the same ID while this hook is mounted. Unmount the hook and clean up its previous source and client scope before reusing the ID.`,
68
- )
69
- }
70
- seen.set(source.id, token)
71
- }
72
- const claimed = prior ?? new Map<string, number>()
73
- for (const [id, token] of seen) claimed.set(id, token)
74
- if (client) bindings.byClient.set(client, claimed)
75
- }