|
1 | 1 | # @tanstack/db |
2 | 2 |
|
| 3 | +## 0.4.20 |
| 4 | + |
| 5 | +### Patch Changes |
| 6 | + |
| 7 | +- Fix type inference for findOne() when used with join operations ([#749](https://github.com/TanStack/db/pull/749)) |
| 8 | + |
| 9 | + Previously, using `findOne()` with join operations (leftJoin, innerJoin, etc.) resulted in the query type being inferred as `never`, breaking TypeScript type checking: |
| 10 | + |
| 11 | + ```typescript |
| 12 | + const query = useLiveQuery( |
| 13 | + (q) => |
| 14 | + q |
| 15 | + .from({ todo: todoCollection }) |
| 16 | + .leftJoin({ todoOptions: todoOptionsCollection }, ...) |
| 17 | + .findOne() // Type became 'never' |
| 18 | + ) |
| 19 | + ``` |
| 20 | + |
| 21 | + **The Fix:** |
| 22 | + |
| 23 | + Fixed the `MergeContextWithJoinType` type definition to conditionally include the `singleResult` property only when it's explicitly `true`, avoiding type conflicts when `findOne()` is called after joins: |
| 24 | + |
| 25 | + ```typescript |
| 26 | + // Before (buggy): |
| 27 | + singleResult: TContext['singleResult'] extends true ? true : false |
| 28 | + |
| 29 | + // After (fixed): |
| 30 | + type PreserveSingleResultFlag<TFlag> = [TFlag] extends [true] |
| 31 | + ? { singleResult: true } |
| 32 | + : {} |
| 33 | + |
| 34 | + // Used as: |
| 35 | + } & PreserveSingleResultFlag<TContext['singleResult']> |
| 36 | + ``` |
| 37 | + |
| 38 | + **Why This Works:** |
| 39 | + |
| 40 | + By using a conditional intersection that omits the property entirely when not needed, we avoid type conflicts. Intersecting `{} & { singleResult: true }` cleanly results in `{ singleResult: true }`, whereas the previous approach created conflicting property types resulting in `never`. The tuple wrapper (`[TFlag]`) ensures robust behavior even if the flag type becomes a union in the future. |
| 41 | + |
| 42 | + **Impact:** |
| 43 | + - ✅ `findOne()` now works correctly with all join types |
| 44 | + - ✅ Type inference works properly in `useLiveQuery` and other contexts |
| 45 | + - ✅ Both `findOne()` before and after joins work correctly |
| 46 | + - ✅ All tests pass with no breaking changes (8 new type tests added) |
| 47 | + |
| 48 | +- Add QueryObserver state utilities and convert error utils to getters ([#742](https://github.com/TanStack/db/pull/742)) |
| 49 | + |
| 50 | + Exposes TanStack Query's QueryObserver state through QueryCollectionUtils, providing visibility into sync status beyond just error states. Also converts existing error state utilities from methods to getters for consistency with TanStack DB/Query patterns. |
| 51 | + |
| 52 | + **Breaking Changes:** |
| 53 | + - `lastError()`, `isError()`, and `errorCount()` are now getters instead of methods |
| 54 | + - Before: `collection.utils.lastError()` |
| 55 | + - After: `collection.utils.lastError` |
| 56 | + |
| 57 | + **New Utilities:** |
| 58 | + - `isFetching` - Check if query is currently fetching (initial or background) |
| 59 | + - `isRefetching` - Check if query is refetching in background |
| 60 | + - `isLoading` - Check if query is loading for first time |
| 61 | + - `dataUpdatedAt` - Get timestamp of last successful data update |
| 62 | + - `fetchStatus` - Get current fetch status ('fetching' | 'paused' | 'idle') |
| 63 | + |
| 64 | + **Use Cases:** |
| 65 | + - Show loading indicators during background refetches |
| 66 | + - Implement "Last updated X minutes ago" UI patterns |
| 67 | + - Better understanding of query sync behavior |
| 68 | + |
| 69 | + **Example Usage:** |
| 70 | + |
| 71 | + ```ts |
| 72 | + const collection = queryCollectionOptions({ |
| 73 | + // ... config |
| 74 | + }) |
| 75 | + |
| 76 | + // Check sync status |
| 77 | + if (collection.utils.isFetching) { |
| 78 | + console.log("Syncing with server...") |
| 79 | + } |
| 80 | + |
| 81 | + if (collection.utils.isRefetching) { |
| 82 | + console.log("Background refresh in progress") |
| 83 | + } |
| 84 | + |
| 85 | + // Show last update time |
| 86 | + const lastUpdate = new Date(collection.utils.dataUpdatedAt) |
| 87 | + console.log(`Last synced: ${lastUpdate.toLocaleTimeString()}`) |
| 88 | + |
| 89 | + // Check error state (now using getters) |
| 90 | + if (collection.utils.isError) { |
| 91 | + console.error("Sync failed:", collection.utils.lastError) |
| 92 | + console.log(`Failed ${collection.utils.errorCount} times`) |
| 93 | + } |
| 94 | + ``` |
| 95 | + |
3 | 96 | ## 0.4.19 |
4 | 97 |
|
5 | 98 | ### Patch Changes |
|
0 commit comments