From 48aebacc8a33e9e01e3e50fb1de63ea46caca89f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 28 Oct 2026 22:29:27 +0000 Subject: [PATCH] ci: Version Packages --- .changeset/bright-cups-infer.md | 7 -- .changeset/browser-sqlite-separate-wasm.md | 5 - .changeset/convex-collection.md | 8 -- .changeset/db-query-identity-laws.md | 9 -- .changeset/defer-term-behind-queued-reads.md | 10 -- .../electric-cleanup-stream-listeners.md | 5 - .../gc-waits-for-persisting-transactions.md | 5 - .../keep-live-query-through-failed-replay.md | 11 -- .changeset/on-demand-network-first.md | 11 -- .changeset/opt-in-operators.md | 18 ---- .../ordered-loader-superseded-failure.md | 9 -- .changeset/persisted-reload-keeps-status.md | 5 - .changeset/preact-db-adapter.md | 5 - .changeset/react-db-strip-dev-warnings.md | 5 - .changeset/react-db-unhashable-throws.md | 7 -- .../reserved-commit-defers-every-hydration.md | 13 --- .../resume-independent-subscription-demand.md | 18 ---- .changeset/skip-complete-collection-setup.md | 7 -- .changeset/sortedmap-merge-restore.md | 12 --- .changeset/source-health-channel.md | 43 -------- .changeset/sqlite-connection-closed-error.md | 6 -- .changeset/svelte-db-query-identity.md | 7 -- .changeset/sync-metadata-session-reads.md | 9 -- .changeset/tidy-opfs-close.md | 5 - .changeset/vue-db-window-binding.md | 5 - examples/angular/todos/CHANGELOG.md | 8 ++ examples/angular/todos/package.json | 6 +- examples/electron/offline-first/CHANGELOG.md | 11 ++ examples/electron/offline-first/package.json | 12 +-- .../offline-transactions/CHANGELOG.md | 11 ++ .../offline-transactions/package.json | 12 +-- .../react-native/shopping-list/CHANGELOG.md | 11 ++ .../react-native/shopping-list/package.json | 12 +-- .../react/action-enforcement/CHANGELOG.md | 9 ++ .../react/action-enforcement/package.json | 6 +- examples/react/next-ssr-e2e/CHANGELOG.md | 8 ++ examples/react/next-ssr-e2e/package.json | 6 +- .../react/offline-transactions/package.json | 10 +- .../react/paced-mutations-demo/CHANGELOG.md | 8 ++ .../react/paced-mutations-demo/package.json | 6 +- examples/react/projects/package.json | 4 +- examples/react/start-ssr-e2e/CHANGELOG.md | 8 ++ examples/react/start-ssr-e2e/package.json | 6 +- examples/react/todo/CHANGELOG.md | 10 ++ examples/react/todo/package.json | 10 +- examples/solid/todo/CHANGELOG.md | 10 ++ examples/solid/todo/package.json | 10 +- packages/angular-db/CHANGELOG.md | 46 ++++++++ packages/angular-db/package.json | 2 +- .../CHANGELOG.md | 13 +++ .../package.json | 2 +- .../CHANGELOG.md | 7 ++ .../e2e/app/CHANGELOG.md | 8 ++ .../e2e/app/package.json | 2 +- .../package.json | 2 +- .../CHANGELOG.md | 7 ++ .../package.json | 2 +- packages/convex-db-collection/CHANGELOG.md | 15 +++ packages/convex-db-collection/package.json | 2 +- packages/db-ivm/CHANGELOG.md | 12 +++ packages/db-ivm/package.json | 2 +- .../db-sqlite-persistence-core/CHANGELOG.md | 75 +++++++++++++ .../db-sqlite-persistence-core/package.json | 2 +- packages/db/CHANGELOG.md | 100 ++++++++++++++++++ packages/db/package.json | 2 +- packages/electric-db-collection/CHANGELOG.md | 47 ++++++++ packages/electric-db-collection/package.json | 2 +- .../CHANGELOG.md | 13 +++ .../package.json | 2 +- .../expo-db-sqlite-persistence/CHANGELOG.md | 7 ++ .../e2e/expo-runtime-app/CHANGELOG.md | 8 ++ .../e2e/expo-runtime-app/package.json | 2 +- .../expo-db-sqlite-persistence/package.json | 2 +- .../node-db-sqlite-persistence/CHANGELOG.md | 7 ++ .../node-db-sqlite-persistence/package.json | 2 +- packages/offline-transactions/CHANGELOG.md | 13 +++ packages/offline-transactions/package.json | 2 +- packages/powersync-db-collection/CHANGELOG.md | 37 +++++++ packages/powersync-db-collection/package.json | 2 +- packages/preact-db/CHANGELOG.md | 49 +++++++++ packages/preact-db/package.json | 2 +- packages/query-db-collection/CHANGELOG.md | 41 +++++++ packages/query-db-collection/package.json | 2 +- packages/react-db/CHANGELOG.md | 64 +++++++++++ packages/react-db/package.json | 2 +- .../CHANGELOG.md | 7 ++ .../package.json | 2 +- packages/react-router-with-db/CHANGELOG.md | 16 +++ packages/react-router-with-db/package.json | 2 +- packages/rxdb-db-collection/CHANGELOG.md | 37 +++++++ packages/rxdb-db-collection/package.json | 2 +- packages/solid-db/CHANGELOG.md | 46 ++++++++ packages/solid-db/package.json | 2 +- packages/svelte-db/CHANGELOG.md | 62 +++++++++++ packages/svelte-db/package.json | 2 +- .../tauri-db-sqlite-persistence/CHANGELOG.md | 7 ++ .../e2e/app/CHANGELOG.md | 8 ++ .../e2e/app/package.json | 2 +- .../tauri-db-sqlite-persistence/package.json | 2 +- packages/trailbase-db-collection/CHANGELOG.md | 37 +++++++ packages/trailbase-db-collection/package.json | 2 +- packages/vue-db/CHANGELOG.md | 48 +++++++++ packages/vue-db/package.json | 2 +- pnpm-lock.yaml | 80 +++++++------- 104 files changed, 1049 insertions(+), 363 deletions(-) delete mode 100644 .changeset/bright-cups-infer.md delete mode 100644 .changeset/browser-sqlite-separate-wasm.md delete mode 100644 .changeset/convex-collection.md delete mode 100644 .changeset/db-query-identity-laws.md delete mode 100644 .changeset/defer-term-behind-queued-reads.md delete mode 100644 .changeset/electric-cleanup-stream-listeners.md delete mode 100644 .changeset/gc-waits-for-persisting-transactions.md delete mode 100644 .changeset/keep-live-query-through-failed-replay.md delete mode 100644 .changeset/on-demand-network-first.md delete mode 100644 .changeset/opt-in-operators.md delete mode 100644 .changeset/ordered-loader-superseded-failure.md delete mode 100644 .changeset/persisted-reload-keeps-status.md delete mode 100644 .changeset/preact-db-adapter.md delete mode 100644 .changeset/react-db-strip-dev-warnings.md delete mode 100644 .changeset/react-db-unhashable-throws.md delete mode 100644 .changeset/reserved-commit-defers-every-hydration.md delete mode 100644 .changeset/resume-independent-subscription-demand.md delete mode 100644 .changeset/skip-complete-collection-setup.md delete mode 100644 .changeset/sortedmap-merge-restore.md delete mode 100644 .changeset/source-health-channel.md delete mode 100644 .changeset/sqlite-connection-closed-error.md delete mode 100644 .changeset/svelte-db-query-identity.md delete mode 100644 .changeset/sync-metadata-session-reads.md delete mode 100644 .changeset/tidy-opfs-close.md delete mode 100644 .changeset/vue-db-window-binding.md create mode 100644 examples/react/action-enforcement/CHANGELOG.md create mode 100644 packages/convex-db-collection/CHANGELOG.md create mode 100644 packages/preact-db/CHANGELOG.md diff --git a/.changeset/bright-cups-infer.md b/.changeset/bright-cups-infer.md deleted file mode 100644 index 9e56befd92..0000000000 --- a/.changeset/bright-cups-infer.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@tanstack/db-sqlite-persistence-core': patch ---- - -Fix type inference for schema-free collection options wrapped with persistedCollectionOptions and passed to createCollection. - -Reject caller utilities that use the reserved local persistence names acceptMutations, getLeadershipState, or forceReloadSubset, instead of silently replacing them. diff --git a/.changeset/browser-sqlite-separate-wasm.md b/.changeset/browser-sqlite-separate-wasm.md deleted file mode 100644 index f9195dc3aa..0000000000 --- a/.changeset/browser-sqlite-separate-wasm.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@tanstack/browser-db-sqlite-persistence': patch ---- - -Ship wa-sqlite WebAssembly as a separate asset that the browser loads automatically, instead of inlining it in the OPFS worker. In Vite development, the package finds its assets without configuration; production builds drop that lookup. An install that Vite cannot resolve by package name, or a worker from another installed version of the package, now reports `PersistenceUnavailableError`, and excluding the package from pre-bundling fixes it. diff --git a/.changeset/convex-collection.md b/.changeset/convex-collection.md deleted file mode 100644 index 96323503c2..0000000000 --- a/.changeset/convex-collection.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -'@tanstack/convex-db-collection': minor -'@tanstack/offline-transactions': minor ---- - -Add a Convex collection adapter with typed query subscriptions, full-row sync, automatic reactive pagination with on-demand windows for a fixed ordered query, durable offline mutation replay, optimistic mutation handlers, and support for both Convex JavaScript and React clients. - -Add opt-in mutation codecs to the durable outbox while preserving the default wire format and retaining unreadable codec records for recovery. diff --git a/.changeset/db-query-identity-laws.md b/.changeset/db-query-identity-laws.md deleted file mode 100644 index 8020f09d16..0000000000 --- a/.changeset/db-query-identity-laws.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -'@tanstack/db': minor ---- - -Collect the helpers the framework adapters share into one adapter layer, exported from `@tanstack/db` and marked unstable for external use. It adds `identifyLiveQuery`, which checks a prepared query's identity and source Collection IDs for a hook that compares query identity, and `bindLiveQueryWindow`, the window binding every `useLiveInfiniteQuery` now uses. The window helpers that binding replaces are no longer exported. - -Add `SourceCollectionIdReuseError`, which a mounted hook that compares query identity throws when a different source Collection appears under a Collection ID it already read. - -Add `rejectLiveQueryInput` and `releaseDeferredOnReclaim` for framework hooks. The first releases the sync starts a rejected query deferred and rethrows the rejection. The second releases a render's deferred sync starts when garbage collection reclaims the live query it built, so a render that never commits, because a sibling threw or it suspended, does not keep its sources deferred. Each released source follows its own start policy, so `startSyncImmediate()` or another hook can still start it. diff --git a/.changeset/defer-term-behind-queued-reads.md b/.changeset/defer-term-behind-queued-reads.md deleted file mode 100644 index ff603970d4..0000000000 --- a/.changeset/defer-term-behind-queued-reads.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -'@tanstack/db-sqlite-persistence-core': minor -'@tanstack/electron-db-sqlite-persistence': patch ---- - -Reloaded persisted collections now read their rows before they write a leader election term. A collection still takes its leader lock when it subscribes, but it reserves its durable term only after the database work already queued on the shared scheduler has settled, or as soon as something needs a leader during that election: a local write, a follower request, a sync commit during startup, or `ensureLeadership`. A need that an earlier leader already served does not let a later takeover skip the wait. A tab that takes over treats the departed leader's route as gone, so a sync commit queued behind one of its reads goes to the new leader and settles without waiting out an RPC timeout. The Electron renderer applies the same rule to its own hydrations. The renderer now has a hydration scope, so its startup runs as one hydration, as a Browser tab's does, and a restart keeps its metadata and row reads in one scope, so a term cannot run between them. - -`ensureLeadership` now resolves when this node becomes leader, keeps its place in the Web Locks queue instead of replacing its lock request, and rejects if the coordinator is disposed. - -Migration for custom persistence adapters: `runInRegularScope` is now a `RegularScope`, which must also provide `afterQueuedWork()`. It resolves once every operation queued or running on the adapter's scheduler at the call has settled. An adapter without a scheduler can return a promise that settles with its running hydrations. A coordinator now rejects an adapter whose `runInRegularScope` lacks `afterQueuedWork` with `InvalidPersistenceAdapterError`. diff --git a/.changeset/electric-cleanup-stream-listeners.md b/.changeset/electric-cleanup-stream-listeners.md deleted file mode 100644 index 4faea3325e..0000000000 --- a/.changeset/electric-cleanup-stream-listeners.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@tanstack/electric-db-collection': patch ---- - -Release the shape stream's visibility listener when a sync run is cleaned up. The Electric client registers a `visibilitychange` listener for each `ShapeStream` (an `AppState` subscription on React Native, from `@electric-sql/client` 1.5.23) and removes it only in `unsubscribeAll()`, so aborting the stream left the listener behind, and the listener kept the stream reachable. An app that cleans up and recreates collections, or whose on-demand cache rotates its provider session, gained one retained stream each time. diff --git a/.changeset/gc-waits-for-persisting-transactions.md b/.changeset/gc-waits-for-persisting-transactions.md deleted file mode 100644 index f5dcb11836..0000000000 --- a/.changeset/gc-waits-for-persisting-transactions.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@tanstack/db': patch ---- - -Garbage collection no longer cleans up a collection while a transaction that touches it is persisting. Cleaning it up dropped the transaction's optimistic rows while its mutation handler still ran, and rejected waits such as Electric's `awaitTxId`, so a write could fail after the screen that made it unmounted. The `gcTime` delay now starts again when the last such transaction completes or fails, cancels obsolete queued idle cleanup, and preserves the remaining startup grace. Rolling back a manual transaction that stayed pending preserves the existing deadline. Explicit `cleanup()` still starts immediately; a transaction that settles afterward no longer restores another transaction's cleared optimistic rows. diff --git a/.changeset/keep-live-query-through-failed-replay.md b/.changeset/keep-live-query-through-failed-replay.md deleted file mode 100644 index 12b98fdbe7..0000000000 --- a/.changeset/keep-live-query-through-failed-replay.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -'@tanstack/db': patch ---- - -A query that joins and includes the same source has two subscriptions to it. Their truncate replays now share one barrier, so neither publishes while the other still loads. When a lazy demand of either fails while a failed replay of the source holds the query's results, the query keeps its last complete result until a later replay of that source succeeds. Before, retiring the other replay's failed demand could publish the window without that demand's rows. - -Some sources refetch and then report that their pages shifted. An ordered window over such a source no longer skips a row tied with the refetched first page's new end. When the report shows that the page's end moved, the window repairs its first page. It no longer trusts a row that an older tie request installed. - -A window move made while a truncate replay holds a live query's results now applies after that replay, even when a newer truncate discards it. Before, the move could reject, and an older move's window stayed. A reload of the query or of a source still ends the move. - -An ordered, limited query over a source without an order index can read too few rows to fill its window. A join that drops rows can cause this. It no longer reads the whole source then. It reads a prefix twice as long instead. It grows again while that adds rows, so every request stays bounded. diff --git a/.changeset/on-demand-network-first.md b/.changeset/on-demand-network-first.md deleted file mode 100644 index 443926353a..0000000000 --- a/.changeset/on-demand-network-first.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -'@tanstack/db-sqlite-persistence-core': minor ---- - -`initialRender: { strategy: 'network-first' }` now works for on-demand persisted Collections, including Electric's `progressive` mode. Previously the configuration threw `InvalidPersistedCollectionConfigError`. - -A subset load that settles online records persisted demand coverage for its exact demand. In a later session, a load of the same demand settles on its rows from SQLite once `networkTimeoutMs` (default 3 seconds) has passed, or at once if the network load fails. The network load keeps running, and its rows publish when they arrive. If it fails after that, the query stays ready. A demand that never settled online still waits for the network. Coverage proves that the rows were complete when they were loaded, not that they are still current. A source truncate, a snapshot transaction, scoped recovery, or a cache-generation rotation clears it. A load that a reset made obsolete, a load that was released before it settled, and a load during the rest of a scoped-recovery sync run record none. Coverage also ends when SQLite holds fewer rows for the demand than when it settled, for example after a source delete or Query DB retention expiry. A `refetch: true` load always waits for the network. An ordered window with a `limit` stays covered only while SQLite holds the same rows for it. - -A query that joins eager and on-demand sources becomes ready when every eager source is ready, which network-first allows from its restored rows after the deadline, and every demand the query reaches on an on-demand source has settled. A network failure alone does not settle an on-demand demand that is not covered. - -This applies only with the default single-process coordinator. With a multi-tab coordinator, such as `BrowserCollectionCoordinator`, on-demand loads still wait for the network. diff --git a/.changeset/opt-in-operators.md b/.changeset/opt-in-operators.md deleted file mode 100644 index 507da726cc..0000000000 --- a/.changeset/opt-in-operators.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -'@tanstack/db': minor -'@tanstack/react-db': patch -'@tanstack/preact-db': patch -'@tanstack/svelte-db': patch -'@tanstack/vue-db': patch -'@tanstack/solid-db': patch -'@tanstack/angular-db': patch ---- - -Breaking: grouping, includes, and pooling are now opt-in. An app bundles an operator's code only when it imports the value for that operator. Joins do not change. A typical app is about 8 kB smaller after gzip. This app has a query collection, a filtered live query, and React `useLiveQuery`. - -- An include needs `include()`, `toArray()`, or `materialize()`. Change `issues: q.from(...)` in `select()` to `issues: include(q.from(...))`. A subquery in `select()` without one of these throws `IncludeOperatorRequiredError`. -- Aggregates such as `count()` need no change. A `groupBy` with no aggregate takes `groupKeys()`: `.groupBy(groupKeys(({ todo }) => todo.status))`. Without it, the query throws `GroupingOperatorRequiredError`. -- Hooks do not pool live queries by default. To share one partition among many equality-filtered queries, pass `pool: pooledLiveQueries` in the hook's query config: `useLiveQuery({ query, pool: pooledLiveQueries })`. Results do not change. Angular's `{ params, query }` form takes `pool` the same way. Any other `pool` value, such as the old boolean, throws `InvalidPoolOptionError`, also for a disabled query. For adapter authors: the `pool` option of `resolveLiveQueryValue` is now `pooledLiveQueries` or `false`, not a boolean. An adapter that reads `pool` from its own options checks it with `configPool` first. -- Code that builds query IR by hand must supply the operator code. `IR.Aggregate` takes `groupingCompiler` as its third argument. `IR.IncludesSubquery` takes `includesRuntime` as its fifth argument, before `parentFilters`. Import both from `@tanstack/db`. IR without them throws `GroupingOperatorRequiredError` or `IncludeOperatorRequiredError` when it compiles, unless another aggregate or `groupKeys()` in the query supplies the grouping compiler. Queries that the query builder builds need no change. -- Operator code is part of query identity. A hook that re-renders a grouped query without `groupKeys()` now throws `GroupingOperatorRequiredError`. Before, React and Preact kept the grouped query. -- Vue: when a changed query fails to compile, `useLiveQuery` no longer shows the rows of the previous query. diff --git a/.changeset/ordered-loader-superseded-failure.md b/.changeset/ordered-loader-superseded-failure.md deleted file mode 100644 index 333468eaad..0000000000 --- a/.changeset/ordered-loader-superseded-failure.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -'@tanstack/db': patch ---- - -Keep ordered live queries accurate and loading when source work overlaps. A window with `orderBy` and `limit` no longer stops loading, keeps old rows, or holds acquisitions in these cases: a failed or superseded request, a truncate replay, a source change during a repair, or an on-demand joined source. Recovery after a failure uses one full-source read, and a window move whose load succeeded now resolves. - -When an adapter's `unloadSubset` throws, TanStack DB now reports the error as an uncaught error and continues as if the release succeeded. The error is no longer a subset failure. It does not set `lastError` or `lastSubsetError`, fail a truncate replay, reject `setWindow()`, or dispose an Effect. `unsubscribe()`, `releaseSnapshot()`, and Effect `dispose()` no longer throw or reject with it. - -The newest `setWindow()` call now wins. A pending source recovery that defers two calls no longer lets the older one set the window. A superseded call that succeeds now resolves only after the newest call settles, so awaiting it shows the window that won. diff --git a/.changeset/persisted-reload-keeps-status.md b/.changeset/persisted-reload-keeps-status.md deleted file mode 100644 index 99c3bd75c9..0000000000 --- a/.changeset/persisted-reload-keeps-status.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@tanstack/db-sqlite-persistence-core': patch ---- - -Keep a persisted Collection's status when another tab's commit reloads its rows. Before, a reload from durable rows could mark a tab `ready` while its own source was still loading, or move a tab whose initial sync failed from `error` to `ready`. Now only the Collection's own source changes its status. diff --git a/.changeset/preact-db-adapter.md b/.changeset/preact-db-adapter.md deleted file mode 100644 index d9429c4be5..0000000000 --- a/.changeset/preact-db-adapter.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@tanstack/preact-db': minor ---- - -Add `@tanstack/preact-db`, a Preact adapter with `useLiveQuery` and `useLiveInfiniteQuery`. Both compare query identity derived from the query IR on each render, require a `queryKey` for functional query variants, and pass the shared live-query and infinite-query conformance suites. Queries read concrete source Collections; `DbProvider` and collection descriptors are not supported yet. diff --git a/.changeset/react-db-strip-dev-warnings.md b/.changeset/react-db-strip-dev-warnings.md deleted file mode 100644 index 3285cf853a..0000000000 --- a/.changeset/react-db-strip-dev-warnings.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@tanstack/react-db': patch ---- - -Production builds no longer include the development warnings of `useLiveQuery`. A typical app's bundle is about 1.6 kB smaller before compression and 0.66 kB smaller after gzip. diff --git a/.changeset/react-db-unhashable-throws.md b/.changeset/react-db-unhashable-throws.md deleted file mode 100644 index d5782ecf19..0000000000 --- a/.changeset/react-db-unhashable-throws.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@tanstack/react-db': minor ---- - -**Breaking (1.0):** a live query whose IR has no stable identity, such as one using `fn.where`, `fn.select`, or `fn.having`, now throws `UnhashableQueryIRError` unless it supplies a nonempty `queryKey`. It previously warned in development and kept its first compilation. An empty `queryKey` now counts as none, as it already did for a `DbClient` preload, so a hook and a preload agree on the query's identity. A dependency array does not replace `queryKey`: in that mode the hook checks identity and claims source Collection IDs each time it binds a live query, including one that `useLiveSuspenseQuery` takes from its cache. A reused source Collection ID now throws `SourceCollectionIdReuseError`. - -A rejected render now releases every sync start it deferred for a `DbClient` descriptor, so a later hook or `startSyncImmediate()` can start that source. A render that never commits, because a sibling threw or it suspended, releases them when its live query is reclaimed. diff --git a/.changeset/reserved-commit-defers-every-hydration.md b/.changeset/reserved-commit-defers-every-hydration.md deleted file mode 100644 index 24a1f7499a..0000000000 --- a/.changeset/reserved-commit-defers-every-hydration.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -'@tanstack/db-sqlite-persistence-core': patch -'@tanstack/db': patch -'@tanstack/electric-db-collection': patch ---- - -Keep a sync transaction that reserved its commit turn from failing when a persisted hydration starts while it is open. Before, only a subset load admitted during the reservation waited for the commit. A subset load admitted earlier, a reload or reset from a peer tab's notice, sequence-gap recovery, or a hydration that a shared scheduler held past its turn advanced the hydration cycle, and the commit then rejected with `a persisted sync transaction cannot cross a hydration cycle`. Electric reserves every stream transaction, so a deploy that made several tabs refetch at once could put their Collections into `error`. Each of these hydrations now waits outside the apply mutex until the reserved transaction commits, then starts. While it waits, readers keep the rows from before it. The `reserveCommitTurn` contract now covers every persisted hydration. - -A peer reset now applies in arrival order with peer commit notices, so a commit from before the reset can no longer publish a row that the reset removed. - -Electric now abandons its open transaction when its stream stops for good, so hydrations that wait for that transaction can run. - -An abandoned reservation now releases the subset loads that waited for it, as a commit does, so they run. Before, a load admitted while the turn was reserved rejected with `SyncTransactionAbortedError` when the source abandoned the transaction. diff --git a/.changeset/resume-independent-subscription-demand.md b/.changeset/resume-independent-subscription-demand.md deleted file mode 100644 index 2afee066f3..0000000000 --- a/.changeset/resume-independent-subscription-demand.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -'@tanstack/db': patch -'@tanstack/react-db': patch -'@tanstack/svelte-db': patch -'@tanstack/react-router-with-db': patch ---- - -Start the required Collection sync run when an ordinary subscriber follows an abandoned suspended render. Deferred acquisition still starts no idle source sync run or acquisition attempt. - -Ordinary subscription admission and deferred acquisition resumption throw during cleanup, including automatic GC. Effects created then return disposed handles. Failed setup releases partial ownership, and throwing callbacks cannot prevent healthy observers receiving cleanup status. Subscribers surviving cleanup retain demand for an explicit restart. - -SSR captures each reader's rows and Collection status. Hydration preserves that view through browser preloads, runtime errors, sibling commits, and delayed streamed records. Missing records reject when transport ends. Known pending results survive normal record-stream close. A new stream after client cleanup starts independently of prior failures. - -Empty loading SSR needs no preload. Rendering rows or readiness requires matching query preparation or explicit source Collection preparation. Streaming Suspense prepares its result. Unsupported preparation throws with guidance. Prepared non-streaming Suspense remains supported. The DbClient query hash includes an infinite query's initial limit and offset. - -Apps without `DbClient` exclude its SSR implementation. Its API stays unchanged. - -Svelte roots initialized with `hydrate()` must set `hydrating` on `DbProvider`. Nested providers inherit it. Each provider clears initial hydration after mount so new children read live rows. diff --git a/.changeset/skip-complete-collection-setup.md b/.changeset/skip-complete-collection-setup.md deleted file mode 100644 index 4ca3961c97..0000000000 --- a/.changeset/skip-complete-collection-setup.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@tanstack/db-sqlite-persistence-core': patch ---- - -Reloads no longer repeat setup that is already complete. A reopened persisted collection checks its tables, indexes, triggers, and bookkeeping rows in the same query that reads its registration, and runs its nine setup statements only when something is missing. Each adapter checks database-wide setup with one query and runs it only when a table, index, or migrated column is missing, instead of about 19 statements on every load. The write-only global `schema_version` row, which adapters with different schema versions overwrote on every load, is no longer created or written. - -A tab no longer rereads all of a collection's rows after its own write. It used to record the write's stream position before that write's commit notice arrived, so the notice looked like a gap and triggered `pullSince` and a full row scan. diff --git a/.changeset/sortedmap-merge-restore.md b/.changeset/sortedmap-merge-restore.md deleted file mode 100644 index 7e308e8d1d..0000000000 --- a/.changeset/sortedmap-merge-restore.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -'@tanstack/db': minor -'@tanstack/db-ivm': patch ---- - -Breaking: Collection keys now always match the declared key type, a string or a number. - -- A grouped live query that groups by one field now gives a `null`, `undefined`, boolean, or bigint group value a string key. Before, the key was the raw value, so `$key` was `null` for the `null` group. Read a group's key from its `$key`. String and number group values keep their keys. -- `sync.write` throws `UndefinedKeyError` or `InvalidKeyError` when `getKey` returns a key that is not a string or a number. Optimistic mutations already threw these errors. For a bigint key, the development message now shows the key instead of failing with a `TypeError`. -- `@tanstack/db` no longer exports `SortedMap`, and `SortedMap` no longer has `forEach`. `SortedMap` is the internal ordered map of Collection state. -- When one publication applies 512 or more sync operations, the Collection now merges the changed keys into the existing order. Before, it sorted all rows again. With 50,000 rows, a `compare` option, and 600 operations in each sync transaction, the load takes 181 ms instead of 1,589 ms. -- Keyed `MultiSet.consolidate` groups entries in nested maps. It no longer encodes each value as a string. diff --git a/.changeset/source-health-channel.md b/.changeset/source-health-channel.md deleted file mode 100644 index 2ae77bce79..0000000000 --- a/.changeset/source-health-channel.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -'@tanstack/db': minor -'@tanstack/db-sqlite-persistence-core': minor -'@tanstack/electric-db-collection': minor -'@tanstack/powersync-db-collection': minor -'@tanstack/query-db-collection': minor -'@tanstack/rxdb-db-collection': minor -'@tanstack/trailbase-db-collection': minor -'@tanstack/react-db': minor -'@tanstack/preact-db': minor -'@tanstack/vue-db': minor -'@tanstack/solid-db': minor -'@tanstack/svelte-db': minor -'@tanstack/angular-db': minor ---- - -Collections now answer two questions separately: can the data be shown (`status`), and is it still kept up to date (`health`). Once a query has shown data, a network or backend failure never takes it off the screen. - -- `status` gains `crashed`: local storage broke (a closed IndexedDB connection, a failed SQLite persistence write or restore, a throwing sync function, or an invariant failure). Nothing recovers it in place, and `reload()` rejects. To recover, call `cleanup({ dependents: true })`, then start the Collection again (for example with `preload()`); it starts with no rows. It reaches every live query and Effect above it. `error` now means only "the first load failed". -- Every Collection has `collection.health`, a health node with `status`, `failureReason` (the adapter is still trying), `error` (the adapter stopped, or the first load failed), `reloading`, and `hasErrors`. A live query's `health` has one node per source alias, in query order, and keeps object identity while unchanged. `health:change` fires when it changes. `findProblems(health)` lists the nodes that have a problem, in query order. Each problem (`HealthProblem`) shows its newest failure in `message`, `cause`, and `at`, and lists every failure still open in `errors`, oldest first: each `reportError()` of the sync run until `reportHealthy()`, or each failed request for more data on a live query's node. One error value is one failure: reporting it again changes nothing, and `reportHealthy(error)` removes just that failure, so a query Collection whose failing Query recovers removes only that Query's failures. After a live query is ready, a failed request for more data (a page, a joined row, or a source reload's replay of its data) is an `error` on the query's own node; the source stays healthy. -- `collection.reload()` starts a new sync run and keeps the rows, status, and error on screen until the reload completes: when the new run's snapshot (`begin({ snapshot: true })`) or `truncate()` applies, or at its `markReady()`, which keeps the rows when no snapshot came. On a live query it reloads every Collection below it that shows an `error`, through nested live queries, then the query itself when its own node has an `error`, including a failed request for more data. It rejects on a `crashed` Collection. When the cleanup of the sync run that a reload retired rejects, the Collection crashes, also if the rejection comes later. A query in `error` stays there until its `reload()`. -- `cleanup()` rejects with `CollectionHasSubscribersError` while a live query with subscribers or an Effect reads the Collection. `cleanup({ dependents: true })` ends them first. `collection.subscribers` lists every dependent. -- A local-only Collection's `reload()` keeps its rows, including the user's writes: `initialData` loads only when the Collection starts, or starts again after cleanup. -- Persisted Collections have one status. With `initialRender: { strategy: 'network-first' }`, restored rows make the Collection `ready` once the deadline passes or the remote fails; an empty restore stays `loading`. Only a local storage failure crashes a persisted Collection: a failed remote subset request is a failure reason, which the next successful remote request clears. - -Breaking changes and migration: - -| Before | After | -| ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | -| hook `isError` | `isLoadingError` (first load failed) or `isCrashed` | -| — | `isSyncError`: `ready` and the health tree has a problem | -| `status: 'error'` after a crash | `status: 'crashed'` | -| adapter `markError(error)` | `fail(error)`. After readiness it sets the node's `error` and keeps the rows. Later calls throw. | -| `cleanup()` then `preload()` to recover | `reload()` | -| adapter `begin({ direct: true })` | `begin()`: every write applies on the rows the Collection holds, also during a reload | -| a reload's first commit replaced every row | `begin({ snapshot: true })` or `truncate()` in the new run replaces them | -| adapter `markReady()` after `fail()` threw | it does nothing | -| `cleanup()` while live queries or Effects read the Collection | rejects; unmount first or pass `{ dependents: true }` | -| `persistedStatus`, `isPersistedReady`, `persistedError`, `preloadForInitialRender()`, `PERSISTED_READINESS` | `status` and `health` | -| query-db `utils.lastError`, `utils.isError`, `utils.errorCount`, `utils.clearError()` | `collection.health`, `reload()` | -| infinite-query hooks' `error` (React, Preact, Vue, Svelte) | the root node's `error` in `health`; `fetchNextPage()` still resolves | - -Custom sync adapters: keep the run's readiness in your `sync` function. A reload calls `sync` again while `collection.status` still shows the previous run's status and the Collection still holds the previous run's rows. The new run's writes apply on those rows; it replaces every row only with a snapshot, a transaction begun with `begin({ snapshot: true })`, or with its own `truncate()`. The reload completes when that replacement applies, or at the new run's `markReady()`, which keeps the rows when no snapshot came. So send the new run's full state as a snapshot, or write against the kept rows: an `insert` of a key the Collection holds throws `DuplicateKeySyncError`, during a reload as at any time, so write `update` for a kept row, also from a replayed load after the reload completed. A reload keeps every row its new run does not rewrite: core never deletes kept rows on its own, so removing a row the backend deleted is the adapter's job, through a snapshot, a `delete`, or row ownership ([#2269](https://github.com/TanStack/db/issues/2269)). An on-demand `loadSubset` result is partial and is never a snapshot. A live query's own reload keeps its rows and its window until its new run's result is complete. `reportError()` and `reportHealthy()` throw before the first `markReady()` and after `fail()`. `markReady()` after the run is ready, or after its `fail()`, does nothing, so an adapter needs no flag of its own to guard it. A second `fail()` in the same sync run does nothing. A first-ready callback that throws while `sync` runs is reported uncaught, and the Collection stays ready. `reload()` called from inside `sync` or a `loadSubset` call rejects, unless a reload is already in flight, whose promise it returns. Calls after a crash do nothing. For an on-demand Collection, a reload aborts the retired run's `loadSubset` calls and asks the new run's `loadSubset` for the current demand as soon as `sync` returns. Honor the `signal`: when core aborts a load, that load is over for core, and its later resolve or reject decides nothing, holds no readiness, and no replay waits for it. A `loadSubset` that throws synchronously is a rejection: a failed load, which `requestSnapshot()` and `setWindow()` report without throwing. A truncate's or reload's replay decides every request error of the query's demand: its success clears them all, and its failure replaces them with the replay's failures. `useLiveSuspenseQuery` now throws an Error for a failed first load or a crash: the cause itself when it is an Error, otherwise an Error with the problem's message whose `cause` holds the reported value. diff --git a/.changeset/sqlite-connection-closed-error.md b/.changeset/sqlite-connection-closed-error.md deleted file mode 100644 index dfcfc04c29..0000000000 --- a/.changeset/sqlite-connection-closed-error.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@tanstack/db-sqlite-persistence-core': patch -'@tanstack/browser-db-sqlite-persistence': patch ---- - -Add `SQLiteConnectionClosedError`. A browser OPFS connection now rejects requests made after close starts with this error instead of `InvalidPersistedCollectionConfigError`. diff --git a/.changeset/svelte-db-query-identity.md b/.changeset/svelte-db-query-identity.md deleted file mode 100644 index e60be451c1..0000000000 --- a/.changeset/svelte-db-query-identity.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@tanstack/svelte-db': minor ---- - -**Breaking (1.0):** a `useLiveQuery` backed by a `DbClient` matches queries by identity hash, so it now throws `UnhashableQueryIRError` for a query without a stable IR identity unless it supplies a nonempty `queryKey`. It previously compiled the query without a hydration key. While such a hook is mounted, a different source Collection under a Collection ID it already read throws `SourceCollectionIdReuseError`, and every result property then throws instead of returning the previous query's state. Queries without a `DbClient` are unchanged. - -A rejected client-backed query now releases every sync start it deferred, so `startSyncImmediate()` or another hook can still start that source. A setup that throws after the hook returns releases them when its live query is reclaimed. `useLiveInfiniteQuery` now uses the shared window binding from `@tanstack/db`. diff --git a/.changeset/sync-metadata-session-reads.md b/.changeset/sync-metadata-session-reads.md deleted file mode 100644 index d0d355e49d..0000000000 --- a/.changeset/sync-metadata-session-reads.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -'@tanstack/db': patch -'@tanstack/db-sqlite-persistence-core': patch -'@tanstack/query-db-collection': patch ---- - -Fix sync metadata reads that missed committed sync writes while an optimistic transaction held them. A sync source now reads its own writes in commit order: `metadata.row.get`, `metadata.collection.get`, and `list()` include committed transactions that have not applied yet, then the open transactions. A transaction begun inside an open one is independent, and a hold no longer changes commit order. A source insert over a different source row is a duplicate even when a seed or a hold is present. - -Persisted collections treat cached rows as seeds, so a later `DbClient` seed can replace them, also after another tab's notice makes the collection reload from storage. A reload from another tab's notice keeps a seed accepted during a pending transaction, keeps row metadata of rows that do not exist, and keeps a seed's row metadata unless the notice names it. An update over a `DbClient` seed now stores the whole row the collection shows, so a reload or restart keeps the seed's fields. With `rowUpdateMode: 'full'`, an update now replaces its stored row, so a reload or restart no longer brings back fields the update dropped. A reload deletes rows that storage lacks also while a pending transaction holds them. After a notice that requires a full reload, a later notice that names its rows keeps a seed's row metadata again. A schema reset from another tab clears every row and its metadata; in on-demand mode it keeps the `DbClient` seeds the collection holds and still replays demand. Rows that storage holds after a reset keep their metadata, and an equal re-insert of one keeps it too. A commit whose signal aborts before the persisted wrapper applies it no longer takes a `DbClient` seed's place. An insert in the persisted wrapper replaces the row it lands on and writes an `insert` mutation to storage. An equal re-insert keeps the row's metadata without writing it, and the wrapper no longer slows down quadratically with the size of a transaction. diff --git a/.changeset/tidy-opfs-close.md b/.changeset/tidy-opfs-close.md deleted file mode 100644 index 00e386700c..0000000000 --- a/.changeset/tidy-opfs-close.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@tanstack/browser-db-sqlite-persistence': patch ---- - -Reject pending OPFS requests when closing the SQLite connection, including when the worker reports a close error. Close now stops accepting requests when it starts, and concurrent `close()` calls share one outcome. The OPFS worker handles one request at a time, so close waits for earlier queries instead of failing while one waits for the OPFS access handle. diff --git a/.changeset/vue-db-window-binding.md b/.changeset/vue-db-window-binding.md deleted file mode 100644 index 136a0feb96..0000000000 --- a/.changeset/vue-db-window-binding.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@tanstack/vue-db': patch ---- - -`useLiveInfiniteQuery` now uses the shared window binding from `@tanstack/db`. A window collection it creates starts syncing when the query compiles, as in the other adapters, and an update that changes nothing keeps the current controller. diff --git a/examples/angular/todos/CHANGELOG.md b/examples/angular/todos/CHANGELOG.md index c83d3b83d0..25323a4d0c 100644 --- a/examples/angular/todos/CHANGELOG.md +++ b/examples/angular/todos/CHANGELOG.md @@ -1,5 +1,13 @@ # todos +## 0.0.28 + +### Patch Changes + +- Updated dependencies [[`5649a07`](https://github.com/TanStack/db/commit/5649a07d468117891193c2e4cde9c7706308b1cd), [`7538afd`](https://github.com/TanStack/db/commit/7538afd2fa838340aaa63fca8b221154eb0b1572), [`a07cd73`](https://github.com/TanStack/db/commit/a07cd73739c1f135e658f38621de27f336004d11), [`6c1ca8f`](https://github.com/TanStack/db/commit/6c1ca8f8f9efe437d3cea61b5b9d0a086e2a5e34), [`30175be`](https://github.com/TanStack/db/commit/30175be483fa70799da579e2c407fb7ed9f2b435), [`4c39add`](https://github.com/TanStack/db/commit/4c39add293adb65b4f5537628849f97083a31531), [`1640de8`](https://github.com/TanStack/db/commit/1640de834bca0588f91432ea80a0f4569157e9dc), [`367ea7c`](https://github.com/TanStack/db/commit/367ea7c11e7dee5ca48ba524e55da2a6935d55fb), [`7d9ed02`](https://github.com/TanStack/db/commit/7d9ed02e085d11e871e4b6424d47e3c0d7073023), [`45eeef8`](https://github.com/TanStack/db/commit/45eeef81a7c64966473315558f46e8a53b00e462)]: + - @tanstack/db@0.16.0 + - @tanstack/angular-db@0.3.0 + ## 0.0.27 ### Patch Changes diff --git a/examples/angular/todos/package.json b/examples/angular/todos/package.json index 5ea357659f..c788d34e20 100644 --- a/examples/angular/todos/package.json +++ b/examples/angular/todos/package.json @@ -1,6 +1,6 @@ { "name": "todos", - "version": "0.0.27", + "version": "0.0.28", "scripts": { "ng": "ng", "start": "ng serve", @@ -27,8 +27,8 @@ "@angular/forms": "^20.3.34", "@angular/platform-browser": "^20.3.34", "@angular/router": "^20.3.34", - "@tanstack/angular-db": "^0.2.12", - "@tanstack/db": "^0.15.2", + "@tanstack/angular-db": "^0.3.0", + "@tanstack/db": "^0.16.0", "rxjs": "^7.8.2", "tslib": "^2.8.1", "zone.js": "~0.15.0" diff --git a/examples/electron/offline-first/CHANGELOG.md b/examples/electron/offline-first/CHANGELOG.md index f33f83cb96..f933b81093 100644 --- a/examples/electron/offline-first/CHANGELOG.md +++ b/examples/electron/offline-first/CHANGELOG.md @@ -1,5 +1,16 @@ # offline-first-electron +## 1.0.8 + +### Patch Changes + +- Updated dependencies [[`db476b8`](https://github.com/TanStack/db/commit/db476b8ed9755e0c0b10b833d187354b4c63585c), [`205ab71`](https://github.com/TanStack/db/commit/205ab7158d81680c3cc4f06cd9ebe62b30911a5a), [`6c1ca8f`](https://github.com/TanStack/db/commit/6c1ca8f8f9efe437d3cea61b5b9d0a086e2a5e34), [`3920346`](https://github.com/TanStack/db/commit/39203466a73648809fb33161c7319d0c60fbb799), [`5649a07`](https://github.com/TanStack/db/commit/5649a07d468117891193c2e4cde9c7706308b1cd), [`1640de8`](https://github.com/TanStack/db/commit/1640de834bca0588f91432ea80a0f4569157e9dc), [`7d9ed02`](https://github.com/TanStack/db/commit/7d9ed02e085d11e871e4b6424d47e3c0d7073023), [`45eeef8`](https://github.com/TanStack/db/commit/45eeef81a7c64966473315558f46e8a53b00e462)]: + - @tanstack/offline-transactions@1.2.0 + - @tanstack/electron-db-sqlite-persistence@0.2.14 + - @tanstack/react-db@0.6.0 + - @tanstack/query-db-collection@1.5.0 + - @tanstack/node-db-sqlite-persistence@0.2.38 + ## 1.0.7 ### Patch Changes diff --git a/examples/electron/offline-first/package.json b/examples/electron/offline-first/package.json index 4d297aa367..8c4133a9e1 100644 --- a/examples/electron/offline-first/package.json +++ b/examples/electron/offline-first/package.json @@ -1,6 +1,6 @@ { "name": "offline-first-electron", - "version": "1.0.7", + "version": "1.0.8", "private": true, "type": "module", "main": "electron/main.ts", @@ -13,11 +13,11 @@ "postinstall": "prebuild-install --runtime electron --target 41.0.0 --arch arm64 || echo 'prebuild-install failed, try: npx @electron/rebuild'" }, "dependencies": { - "@tanstack/electron-db-sqlite-persistence": "^0.2.13", - "@tanstack/node-db-sqlite-persistence": "^0.2.37", - "@tanstack/offline-transactions": "^1.1.4", - "@tanstack/query-db-collection": "^1.4.8", - "@tanstack/react-db": "^0.5.12", + "@tanstack/electron-db-sqlite-persistence": "^0.2.14", + "@tanstack/node-db-sqlite-persistence": "^0.2.38", + "@tanstack/offline-transactions": "^1.2.0", + "@tanstack/query-db-collection": "^1.5.0", + "@tanstack/react-db": "^0.6.0", "@tanstack/react-query": "^5.90.20", "better-sqlite3": "^12.6.2", "react": "^19.2.4", diff --git a/examples/react-native/offline-transactions/CHANGELOG.md b/examples/react-native/offline-transactions/CHANGELOG.md index 543cb7ebe1..c2559932ad 100644 --- a/examples/react-native/offline-transactions/CHANGELOG.md +++ b/examples/react-native/offline-transactions/CHANGELOG.md @@ -1,5 +1,16 @@ # offline-transactions-react-native +## 1.0.15 + +### Patch Changes + +- Updated dependencies [[`db476b8`](https://github.com/TanStack/db/commit/db476b8ed9755e0c0b10b833d187354b4c63585c), [`5649a07`](https://github.com/TanStack/db/commit/5649a07d468117891193c2e4cde9c7706308b1cd), [`7538afd`](https://github.com/TanStack/db/commit/7538afd2fa838340aaa63fca8b221154eb0b1572), [`a07cd73`](https://github.com/TanStack/db/commit/a07cd73739c1f135e658f38621de27f336004d11), [`6c1ca8f`](https://github.com/TanStack/db/commit/6c1ca8f8f9efe437d3cea61b5b9d0a086e2a5e34), [`30175be`](https://github.com/TanStack/db/commit/30175be483fa70799da579e2c407fb7ed9f2b435), [`3920346`](https://github.com/TanStack/db/commit/39203466a73648809fb33161c7319d0c60fbb799), [`5649a07`](https://github.com/TanStack/db/commit/5649a07d468117891193c2e4cde9c7706308b1cd), [`4c39add`](https://github.com/TanStack/db/commit/4c39add293adb65b4f5537628849f97083a31531), [`1640de8`](https://github.com/TanStack/db/commit/1640de834bca0588f91432ea80a0f4569157e9dc), [`367ea7c`](https://github.com/TanStack/db/commit/367ea7c11e7dee5ca48ba524e55da2a6935d55fb), [`7d9ed02`](https://github.com/TanStack/db/commit/7d9ed02e085d11e871e4b6424d47e3c0d7073023), [`45eeef8`](https://github.com/TanStack/db/commit/45eeef81a7c64966473315558f46e8a53b00e462)]: + - @tanstack/offline-transactions@1.2.0 + - @tanstack/db@0.16.0 + - @tanstack/react-db@0.6.0 + - @tanstack/query-db-collection@1.5.0 + - @tanstack/react-native-db-sqlite-persistence@0.2.38 + ## 1.0.14 ### Patch Changes diff --git a/examples/react-native/offline-transactions/package.json b/examples/react-native/offline-transactions/package.json index 66ff5ac625..16349945bb 100644 --- a/examples/react-native/offline-transactions/package.json +++ b/examples/react-native/offline-transactions/package.json @@ -1,6 +1,6 @@ { "name": "offline-transactions-react-native", - "version": "1.0.14", + "version": "1.0.15", "private": true, "main": "expo-router/entry", "scripts": { @@ -15,11 +15,11 @@ "@op-engineering/op-sqlite": "^15.2.5", "@react-native-async-storage/async-storage": "2.1.2", "@react-native-community/netinfo": "11.4.1", - "@tanstack/db": "^0.15.2", - "@tanstack/offline-transactions": "^1.1.4", - "@tanstack/query-db-collection": "^1.4.8", - "@tanstack/react-db": "^0.5.12", - "@tanstack/react-native-db-sqlite-persistence": "^0.2.37", + "@tanstack/db": "^0.16.0", + "@tanstack/offline-transactions": "^1.2.0", + "@tanstack/query-db-collection": "^1.5.0", + "@tanstack/react-db": "^0.6.0", + "@tanstack/react-native-db-sqlite-persistence": "^0.2.38", "@tanstack/react-query": "^5.90.20", "expo": "~53.0.26", "expo-constants": "~17.1.0", diff --git a/examples/react-native/shopping-list/CHANGELOG.md b/examples/react-native/shopping-list/CHANGELOG.md index 15a492a58a..2dc4b5088f 100644 --- a/examples/react-native/shopping-list/CHANGELOG.md +++ b/examples/react-native/shopping-list/CHANGELOG.md @@ -1,5 +1,16 @@ # shopping-list-react-native +## 1.0.15 + +### Patch Changes + +- Updated dependencies [[`db476b8`](https://github.com/TanStack/db/commit/db476b8ed9755e0c0b10b833d187354b4c63585c), [`5649a07`](https://github.com/TanStack/db/commit/5649a07d468117891193c2e4cde9c7706308b1cd), [`b8d8ee0`](https://github.com/TanStack/db/commit/b8d8ee07c95f51d84c880f6bb7371a84d2858394), [`7538afd`](https://github.com/TanStack/db/commit/7538afd2fa838340aaa63fca8b221154eb0b1572), [`a07cd73`](https://github.com/TanStack/db/commit/a07cd73739c1f135e658f38621de27f336004d11), [`6c1ca8f`](https://github.com/TanStack/db/commit/6c1ca8f8f9efe437d3cea61b5b9d0a086e2a5e34), [`30175be`](https://github.com/TanStack/db/commit/30175be483fa70799da579e2c407fb7ed9f2b435), [`3920346`](https://github.com/TanStack/db/commit/39203466a73648809fb33161c7319d0c60fbb799), [`5649a07`](https://github.com/TanStack/db/commit/5649a07d468117891193c2e4cde9c7706308b1cd), [`4c39add`](https://github.com/TanStack/db/commit/4c39add293adb65b4f5537628849f97083a31531), [`1640de8`](https://github.com/TanStack/db/commit/1640de834bca0588f91432ea80a0f4569157e9dc), [`367ea7c`](https://github.com/TanStack/db/commit/367ea7c11e7dee5ca48ba524e55da2a6935d55fb), [`7d9ed02`](https://github.com/TanStack/db/commit/7d9ed02e085d11e871e4b6424d47e3c0d7073023), [`45eeef8`](https://github.com/TanStack/db/commit/45eeef81a7c64966473315558f46e8a53b00e462)]: + - @tanstack/offline-transactions@1.2.0 + - @tanstack/db@0.16.0 + - @tanstack/electric-db-collection@0.7.0 + - @tanstack/react-db@0.6.0 + - @tanstack/react-native-db-sqlite-persistence@0.2.38 + ## 1.0.14 ### Patch Changes diff --git a/examples/react-native/shopping-list/package.json b/examples/react-native/shopping-list/package.json index 3db2e0d6ff..000830fbf8 100644 --- a/examples/react-native/shopping-list/package.json +++ b/examples/react-native/shopping-list/package.json @@ -1,6 +1,6 @@ { "name": "shopping-list-react-native", - "version": "1.0.14", + "version": "1.0.15", "private": true, "main": "expo-router/entry", "scripts": { @@ -18,11 +18,11 @@ "@op-engineering/op-sqlite": "^15.2.5", "@react-native-async-storage/async-storage": "2.1.2", "@react-native-community/netinfo": "11.4.1", - "@tanstack/db": "^0.15.2", - "@tanstack/electric-db-collection": "^0.6.3", - "@tanstack/offline-transactions": "^1.1.4", - "@tanstack/react-db": "^0.5.12", - "@tanstack/react-native-db-sqlite-persistence": "^0.2.37", + "@tanstack/db": "^0.16.0", + "@tanstack/electric-db-collection": "^0.7.0", + "@tanstack/offline-transactions": "^1.2.0", + "@tanstack/react-db": "^0.6.0", + "@tanstack/react-native-db-sqlite-persistence": "^0.2.38", "@tanstack/react-query": "^5.90.20", "expo": "~53.0.26", "expo-constants": "~17.1.0", diff --git a/examples/react/action-enforcement/CHANGELOG.md b/examples/react/action-enforcement/CHANGELOG.md new file mode 100644 index 0000000000..92ca823d40 --- /dev/null +++ b/examples/react/action-enforcement/CHANGELOG.md @@ -0,0 +1,9 @@ +# @tanstack/db-example-react-action-enforcement + +## 0.0.2 + +### Patch Changes + +- Updated dependencies [[`6c1ca8f`](https://github.com/TanStack/db/commit/6c1ca8f8f9efe437d3cea61b5b9d0a086e2a5e34), [`3920346`](https://github.com/TanStack/db/commit/39203466a73648809fb33161c7319d0c60fbb799), [`5649a07`](https://github.com/TanStack/db/commit/5649a07d468117891193c2e4cde9c7706308b1cd), [`1640de8`](https://github.com/TanStack/db/commit/1640de834bca0588f91432ea80a0f4569157e9dc), [`7d9ed02`](https://github.com/TanStack/db/commit/7d9ed02e085d11e871e4b6424d47e3c0d7073023), [`45eeef8`](https://github.com/TanStack/db/commit/45eeef81a7c64966473315558f46e8a53b00e462)]: + - @tanstack/react-db@0.6.0 + - @tanstack/query-db-collection@1.5.0 diff --git a/examples/react/action-enforcement/package.json b/examples/react/action-enforcement/package.json index 52403a30ee..9505b0a118 100644 --- a/examples/react/action-enforcement/package.json +++ b/examples/react/action-enforcement/package.json @@ -1,7 +1,7 @@ { "name": "@tanstack/db-example-react-action-enforcement", "private": true, - "version": "0.0.1", + "version": "0.0.2", "type": "module", "scripts": { "dev": "vite", @@ -12,8 +12,8 @@ }, "dependencies": { "@tanstack/query-core": "^5.90.20", - "@tanstack/query-db-collection": "^1.4.8", - "@tanstack/react-db": "^0.5.12", + "@tanstack/query-db-collection": "^1.5.0", + "@tanstack/react-db": "^0.6.0", "react": "^19.2.4", "react-dom": "^19.2.4" }, diff --git a/examples/react/next-ssr-e2e/CHANGELOG.md b/examples/react/next-ssr-e2e/CHANGELOG.md index ef70649e17..b8f6b6906e 100644 --- a/examples/react/next-ssr-e2e/CHANGELOG.md +++ b/examples/react/next-ssr-e2e/CHANGELOG.md @@ -1,5 +1,13 @@ # @tanstack/db-example-react-next-ssr-e2e +## 0.0.10 + +### Patch Changes + +- Updated dependencies [[`5649a07`](https://github.com/TanStack/db/commit/5649a07d468117891193c2e4cde9c7706308b1cd), [`7538afd`](https://github.com/TanStack/db/commit/7538afd2fa838340aaa63fca8b221154eb0b1572), [`a07cd73`](https://github.com/TanStack/db/commit/a07cd73739c1f135e658f38621de27f336004d11), [`6c1ca8f`](https://github.com/TanStack/db/commit/6c1ca8f8f9efe437d3cea61b5b9d0a086e2a5e34), [`30175be`](https://github.com/TanStack/db/commit/30175be483fa70799da579e2c407fb7ed9f2b435), [`3920346`](https://github.com/TanStack/db/commit/39203466a73648809fb33161c7319d0c60fbb799), [`5649a07`](https://github.com/TanStack/db/commit/5649a07d468117891193c2e4cde9c7706308b1cd), [`4c39add`](https://github.com/TanStack/db/commit/4c39add293adb65b4f5537628849f97083a31531), [`1640de8`](https://github.com/TanStack/db/commit/1640de834bca0588f91432ea80a0f4569157e9dc), [`367ea7c`](https://github.com/TanStack/db/commit/367ea7c11e7dee5ca48ba524e55da2a6935d55fb), [`7d9ed02`](https://github.com/TanStack/db/commit/7d9ed02e085d11e871e4b6424d47e3c0d7073023), [`45eeef8`](https://github.com/TanStack/db/commit/45eeef81a7c64966473315558f46e8a53b00e462)]: + - @tanstack/db@0.16.0 + - @tanstack/react-db@0.6.0 + ## 0.0.9 ### Patch Changes diff --git a/examples/react/next-ssr-e2e/package.json b/examples/react/next-ssr-e2e/package.json index 21333d2492..d05cbf1ee7 100644 --- a/examples/react/next-ssr-e2e/package.json +++ b/examples/react/next-ssr-e2e/package.json @@ -1,7 +1,7 @@ { "name": "@tanstack/db-example-react-next-ssr-e2e", "private": true, - "version": "0.0.9", + "version": "0.0.10", "scripts": { "build": "next build", "dev": "next dev", @@ -9,8 +9,8 @@ "test:e2e": "pnpm --filter @tanstack/db build && pnpm --filter @tanstack/react-db build && playwright test" }, "dependencies": { - "@tanstack/db": "^0.15.2", - "@tanstack/react-db": "^0.5.12", + "@tanstack/db": "^0.16.0", + "@tanstack/react-db": "^0.6.0", "next": "^16.3.1", "react": "^19.2.4", "react-dom": "^19.2.4" diff --git a/examples/react/offline-transactions/package.json b/examples/react/offline-transactions/package.json index c6a878047c..2f64ac8b5c 100644 --- a/examples/react/offline-transactions/package.json +++ b/examples/react/offline-transactions/package.json @@ -8,11 +8,11 @@ "build": "vite build && tsc --noEmit" }, "dependencies": { - "@tanstack/browser-db-sqlite-persistence": "^0.2.37", - "@tanstack/db": "^0.15.2", - "@tanstack/offline-transactions": "^1.1.4", - "@tanstack/query-db-collection": "^1.4.8", - "@tanstack/react-db": "^0.5.12", + "@tanstack/browser-db-sqlite-persistence": "^0.2.38", + "@tanstack/db": "^0.16.0", + "@tanstack/offline-transactions": "^1.2.0", + "@tanstack/query-db-collection": "^1.5.0", + "@tanstack/react-db": "^0.6.0", "@tanstack/react-query": "^5.90.20", "@tanstack/react-router": "^1.170.41", "@tanstack/react-router-devtools": "^1.159.5", diff --git a/examples/react/paced-mutations-demo/CHANGELOG.md b/examples/react/paced-mutations-demo/CHANGELOG.md index aa33798bc7..f7140214e6 100644 --- a/examples/react/paced-mutations-demo/CHANGELOG.md +++ b/examples/react/paced-mutations-demo/CHANGELOG.md @@ -1,5 +1,13 @@ # @tanstack/db-example-paced-mutations-demo +## 0.0.20 + +### Patch Changes + +- Updated dependencies [[`5649a07`](https://github.com/TanStack/db/commit/5649a07d468117891193c2e4cde9c7706308b1cd), [`7538afd`](https://github.com/TanStack/db/commit/7538afd2fa838340aaa63fca8b221154eb0b1572), [`a07cd73`](https://github.com/TanStack/db/commit/a07cd73739c1f135e658f38621de27f336004d11), [`6c1ca8f`](https://github.com/TanStack/db/commit/6c1ca8f8f9efe437d3cea61b5b9d0a086e2a5e34), [`30175be`](https://github.com/TanStack/db/commit/30175be483fa70799da579e2c407fb7ed9f2b435), [`3920346`](https://github.com/TanStack/db/commit/39203466a73648809fb33161c7319d0c60fbb799), [`5649a07`](https://github.com/TanStack/db/commit/5649a07d468117891193c2e4cde9c7706308b1cd), [`4c39add`](https://github.com/TanStack/db/commit/4c39add293adb65b4f5537628849f97083a31531), [`1640de8`](https://github.com/TanStack/db/commit/1640de834bca0588f91432ea80a0f4569157e9dc), [`367ea7c`](https://github.com/TanStack/db/commit/367ea7c11e7dee5ca48ba524e55da2a6935d55fb), [`7d9ed02`](https://github.com/TanStack/db/commit/7d9ed02e085d11e871e4b6424d47e3c0d7073023), [`45eeef8`](https://github.com/TanStack/db/commit/45eeef81a7c64966473315558f46e8a53b00e462)]: + - @tanstack/db@0.16.0 + - @tanstack/react-db@0.6.0 + ## 0.0.19 ### Patch Changes diff --git a/examples/react/paced-mutations-demo/package.json b/examples/react/paced-mutations-demo/package.json index a1b3ae0fc2..bea68e6c28 100644 --- a/examples/react/paced-mutations-demo/package.json +++ b/examples/react/paced-mutations-demo/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/db-example-paced-mutations-demo", - "version": "0.0.19", + "version": "0.0.20", "private": true, "type": "module", "scripts": { @@ -9,8 +9,8 @@ "preview": "vite preview" }, "dependencies": { - "@tanstack/db": "^0.15.2", - "@tanstack/react-db": "^0.5.12", + "@tanstack/db": "^0.16.0", + "@tanstack/react-db": "^0.6.0", "mitt": "^3.0.1", "react": "^19.2.4", "react-dom": "^19.2.4" diff --git a/examples/react/projects/package.json b/examples/react/projects/package.json index 1a38ff93fe..5f2a23bad5 100644 --- a/examples/react/projects/package.json +++ b/examples/react/projects/package.json @@ -17,8 +17,8 @@ "dependencies": { "@tailwindcss/vite": "^4.1.18", "@tanstack/query-core": "^5.90.20", - "@tanstack/query-db-collection": "^1.4.8", - "@tanstack/react-db": "^0.5.12", + "@tanstack/query-db-collection": "^1.5.0", + "@tanstack/react-db": "^0.6.0", "@tanstack/react-router": "^1.170.41", "@tanstack/react-router-devtools": "^1.159.5", "@tanstack/react-router-with-query": "^1.130.17", diff --git a/examples/react/start-ssr-e2e/CHANGELOG.md b/examples/react/start-ssr-e2e/CHANGELOG.md index f9f6ce3a29..26c677eadd 100644 --- a/examples/react/start-ssr-e2e/CHANGELOG.md +++ b/examples/react/start-ssr-e2e/CHANGELOG.md @@ -1,5 +1,13 @@ # @tanstack/db-example-react-start-ssr-e2e +## 0.0.4 + +### Patch Changes + +- Updated dependencies [[`6c1ca8f`](https://github.com/TanStack/db/commit/6c1ca8f8f9efe437d3cea61b5b9d0a086e2a5e34), [`3920346`](https://github.com/TanStack/db/commit/39203466a73648809fb33161c7319d0c60fbb799), [`5649a07`](https://github.com/TanStack/db/commit/5649a07d468117891193c2e4cde9c7706308b1cd), [`1640de8`](https://github.com/TanStack/db/commit/1640de834bca0588f91432ea80a0f4569157e9dc), [`7d9ed02`](https://github.com/TanStack/db/commit/7d9ed02e085d11e871e4b6424d47e3c0d7073023)]: + - @tanstack/react-db@0.6.0 + - @tanstack/react-router-with-db@0.1.1 + ## 0.0.3 ### Patch Changes diff --git a/examples/react/start-ssr-e2e/package.json b/examples/react/start-ssr-e2e/package.json index 0e514c957e..16ed3b1bd5 100644 --- a/examples/react/start-ssr-e2e/package.json +++ b/examples/react/start-ssr-e2e/package.json @@ -1,7 +1,7 @@ { "name": "@tanstack/db-example-react-start-ssr-e2e", "private": true, - "version": "0.0.3", + "version": "0.0.4", "type": "module", "scripts": { "build": "vite build", @@ -10,9 +10,9 @@ "test:e2e:hosted": "playwright test" }, "dependencies": { - "@tanstack/react-db": "^0.5.12", + "@tanstack/react-db": "^0.6.0", "@tanstack/react-router": "^1.170.41", - "@tanstack/react-router-with-db": "^0.1.0", + "@tanstack/react-router-with-db": "^0.1.1", "@tanstack/react-start": "^1.168.60", "react": "^19.2.4", "react-dom": "^19.2.4", diff --git a/examples/react/todo/CHANGELOG.md b/examples/react/todo/CHANGELOG.md index 6c6500991b..7260c2c88d 100644 --- a/examples/react/todo/CHANGELOG.md +++ b/examples/react/todo/CHANGELOG.md @@ -1,5 +1,15 @@ # examples/react/todo +## 0.1.32 + +### Patch Changes + +- Updated dependencies [[`b8d8ee0`](https://github.com/TanStack/db/commit/b8d8ee07c95f51d84c880f6bb7371a84d2858394), [`6c1ca8f`](https://github.com/TanStack/db/commit/6c1ca8f8f9efe437d3cea61b5b9d0a086e2a5e34), [`3920346`](https://github.com/TanStack/db/commit/39203466a73648809fb33161c7319d0c60fbb799), [`5649a07`](https://github.com/TanStack/db/commit/5649a07d468117891193c2e4cde9c7706308b1cd), [`4c39add`](https://github.com/TanStack/db/commit/4c39add293adb65b4f5537628849f97083a31531), [`1640de8`](https://github.com/TanStack/db/commit/1640de834bca0588f91432ea80a0f4569157e9dc), [`7d9ed02`](https://github.com/TanStack/db/commit/7d9ed02e085d11e871e4b6424d47e3c0d7073023), [`45eeef8`](https://github.com/TanStack/db/commit/45eeef81a7c64966473315558f46e8a53b00e462)]: + - @tanstack/electric-db-collection@0.7.0 + - @tanstack/react-db@0.6.0 + - @tanstack/query-db-collection@1.5.0 + - @tanstack/trailbase-db-collection@0.2.0 + ## 0.1.31 ### Patch Changes diff --git a/examples/react/todo/package.json b/examples/react/todo/package.json index 3d1df4122c..7f06991316 100644 --- a/examples/react/todo/package.json +++ b/examples/react/todo/package.json @@ -1,15 +1,15 @@ { "name": "@tanstack/db-example-react-todo", "private": true, - "version": "0.1.31", + "version": "0.1.32", "dependencies": { - "@tanstack/electric-db-collection": "^0.6.3", + "@tanstack/electric-db-collection": "^0.7.0", "@tanstack/query-core": "^5.90.20", - "@tanstack/query-db-collection": "^1.4.8", - "@tanstack/react-db": "^0.5.12", + "@tanstack/query-db-collection": "^1.5.0", + "@tanstack/react-db": "^0.6.0", "@tanstack/react-router": "^1.170.41", "@tanstack/react-start": "^1.168.60", - "@tanstack/trailbase-db-collection": "^0.1.123", + "@tanstack/trailbase-db-collection": "^0.2.0", "cors": "^2.8.6", "drizzle-orm": "^0.45.1", "drizzle-zod": "^0.8.3", diff --git a/examples/solid/todo/CHANGELOG.md b/examples/solid/todo/CHANGELOG.md index d3f34d27fb..69bab49323 100644 --- a/examples/solid/todo/CHANGELOG.md +++ b/examples/solid/todo/CHANGELOG.md @@ -1,5 +1,15 @@ # examples/react/todo +## 0.0.40 + +### Patch Changes + +- Updated dependencies [[`b8d8ee0`](https://github.com/TanStack/db/commit/b8d8ee07c95f51d84c880f6bb7371a84d2858394), [`6c1ca8f`](https://github.com/TanStack/db/commit/6c1ca8f8f9efe437d3cea61b5b9d0a086e2a5e34), [`4c39add`](https://github.com/TanStack/db/commit/4c39add293adb65b4f5537628849f97083a31531), [`7d9ed02`](https://github.com/TanStack/db/commit/7d9ed02e085d11e871e4b6424d47e3c0d7073023), [`45eeef8`](https://github.com/TanStack/db/commit/45eeef81a7c64966473315558f46e8a53b00e462)]: + - @tanstack/electric-db-collection@0.7.0 + - @tanstack/solid-db@0.4.0 + - @tanstack/query-db-collection@1.5.0 + - @tanstack/trailbase-db-collection@0.2.0 + ## 0.0.39 ### Patch Changes diff --git a/examples/solid/todo/package.json b/examples/solid/todo/package.json index 5479af674f..c7f95e9cf5 100644 --- a/examples/solid/todo/package.json +++ b/examples/solid/todo/package.json @@ -1,15 +1,15 @@ { "name": "@tanstack/db-example-solid-todo", "private": true, - "version": "0.0.39", + "version": "0.0.40", "dependencies": { - "@tanstack/electric-db-collection": "^0.6.3", + "@tanstack/electric-db-collection": "^0.7.0", "@tanstack/query-core": "^5.90.20", - "@tanstack/query-db-collection": "^1.4.8", - "@tanstack/solid-db": "^0.3.12", + "@tanstack/query-db-collection": "^1.5.0", + "@tanstack/solid-db": "^0.4.0", "@tanstack/solid-router": "^1.170.38", "@tanstack/solid-start": "^1.168.57", - "@tanstack/trailbase-db-collection": "^0.1.123", + "@tanstack/trailbase-db-collection": "^0.2.0", "cors": "^2.8.6", "drizzle-orm": "^0.45.1", "drizzle-zod": "^0.8.3", diff --git a/packages/angular-db/CHANGELOG.md b/packages/angular-db/CHANGELOG.md index 5ed1327d4f..7f514208f9 100644 --- a/packages/angular-db/CHANGELOG.md +++ b/packages/angular-db/CHANGELOG.md @@ -1,5 +1,51 @@ # @tanstack/angular-db +## 0.3.0 + +### Minor Changes + +- Collections now answer two questions separately: can the data be shown (`status`), and is it still kept up to date (`health`). Once a query has shown data, a network or backend failure never takes it off the screen. ([#2168](https://github.com/TanStack/db/pull/2168)) + + - `status` gains `crashed`: local storage broke (a closed IndexedDB connection, a failed SQLite persistence write or restore, a throwing sync function, or an invariant failure). Nothing recovers it in place, and `reload()` rejects. To recover, call `cleanup({ dependents: true })`, then start the Collection again (for example with `preload()`); it starts with no rows. It reaches every live query and Effect above it. `error` now means only "the first load failed". + - Every Collection has `collection.health`, a health node with `status`, `failureReason` (the adapter is still trying), `error` (the adapter stopped, or the first load failed), `reloading`, and `hasErrors`. A live query's `health` has one node per source alias, in query order, and keeps object identity while unchanged. `health:change` fires when it changes. `findProblems(health)` lists the nodes that have a problem, in query order. Each problem (`HealthProblem`) shows its newest failure in `message`, `cause`, and `at`, and lists every failure still open in `errors`, oldest first: each `reportError()` of the sync run until `reportHealthy()`, or each failed request for more data on a live query's node. One error value is one failure: reporting it again changes nothing, and `reportHealthy(error)` removes just that failure, so a query Collection whose failing Query recovers removes only that Query's failures. After a live query is ready, a failed request for more data (a page, a joined row, or a source reload's replay of its data) is an `error` on the query's own node; the source stays healthy. + - `collection.reload()` starts a new sync run and keeps the rows, status, and error on screen until the reload completes: when the new run's snapshot (`begin({ snapshot: true })`) or `truncate()` applies, or at its `markReady()`, which keeps the rows when no snapshot came. On a live query it reloads every Collection below it that shows an `error`, through nested live queries, then the query itself when its own node has an `error`, including a failed request for more data. It rejects on a `crashed` Collection. When the cleanup of the sync run that a reload retired rejects, the Collection crashes, also if the rejection comes later. A query in `error` stays there until its `reload()`. + - `cleanup()` rejects with `CollectionHasSubscribersError` while a live query with subscribers or an Effect reads the Collection. `cleanup({ dependents: true })` ends them first. `collection.subscribers` lists every dependent. + - A local-only Collection's `reload()` keeps its rows, including the user's writes: `initialData` loads only when the Collection starts, or starts again after cleanup. + - Persisted Collections have one status. With `initialRender: { strategy: 'network-first' }`, restored rows make the Collection `ready` once the deadline passes or the remote fails; an empty restore stays `loading`. Only a local storage failure crashes a persisted Collection: a failed remote subset request is a failure reason, which the next successful remote request clears. + + Breaking changes and migration: + + | Before | After | + | ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | + | hook `isError` | `isLoadingError` (first load failed) or `isCrashed` | + | — | `isSyncError`: `ready` and the health tree has a problem | + | `status: 'error'` after a crash | `status: 'crashed'` | + | adapter `markError(error)` | `fail(error)`. After readiness it sets the node's `error` and keeps the rows. Later calls throw. | + | `cleanup()` then `preload()` to recover | `reload()` | + | adapter `begin({ direct: true })` | `begin()`: every write applies on the rows the Collection holds, also during a reload | + | a reload's first commit replaced every row | `begin({ snapshot: true })` or `truncate()` in the new run replaces them | + | adapter `markReady()` after `fail()` threw | it does nothing | + | `cleanup()` while live queries or Effects read the Collection | rejects; unmount first or pass `{ dependents: true }` | + | `persistedStatus`, `isPersistedReady`, `persistedError`, `preloadForInitialRender()`, `PERSISTED_READINESS` | `status` and `health` | + | query-db `utils.lastError`, `utils.isError`, `utils.errorCount`, `utils.clearError()` | `collection.health`, `reload()` | + | infinite-query hooks' `error` (React, Preact, Vue, Svelte) | the root node's `error` in `health`; `fetchNextPage()` still resolves | + + Custom sync adapters: keep the run's readiness in your `sync` function. A reload calls `sync` again while `collection.status` still shows the previous run's status and the Collection still holds the previous run's rows. The new run's writes apply on those rows; it replaces every row only with a snapshot, a transaction begun with `begin({ snapshot: true })`, or with its own `truncate()`. The reload completes when that replacement applies, or at the new run's `markReady()`, which keeps the rows when no snapshot came. So send the new run's full state as a snapshot, or write against the kept rows: an `insert` of a key the Collection holds throws `DuplicateKeySyncError`, during a reload as at any time, so write `update` for a kept row, also from a replayed load after the reload completed. A reload keeps every row its new run does not rewrite: core never deletes kept rows on its own, so removing a row the backend deleted is the adapter's job, through a snapshot, a `delete`, or row ownership ([#2269](https://github.com/TanStack/db/issues/2269)). An on-demand `loadSubset` result is partial and is never a snapshot. A live query's own reload keeps its rows and its window until its new run's result is complete. `reportError()` and `reportHealthy()` throw before the first `markReady()` and after `fail()`. `markReady()` after the run is ready, or after its `fail()`, does nothing, so an adapter needs no flag of its own to guard it. A second `fail()` in the same sync run does nothing. A first-ready callback that throws while `sync` runs is reported uncaught, and the Collection stays ready. `reload()` called from inside `sync` or a `loadSubset` call rejects, unless a reload is already in flight, whose promise it returns. Calls after a crash do nothing. For an on-demand Collection, a reload aborts the retired run's `loadSubset` calls and asks the new run's `loadSubset` for the current demand as soon as `sync` returns. Honor the `signal`: when core aborts a load, that load is over for core, and its later resolve or reject decides nothing, holds no readiness, and no replay waits for it. A `loadSubset` that throws synchronously is a rejection: a failed load, which `requestSnapshot()` and `setWindow()` report without throwing. A truncate's or reload's replay decides every request error of the query's demand: its success clears them all, and its failure replaces them with the replay's failures. `useLiveSuspenseQuery` now throws an Error for a failed first load or a crash: the cause itself when it is an Error, otherwise an Error with the problem's message whose `cause` holds the reported value. + +### Patch Changes + +- Breaking: grouping, includes, and pooling are now opt-in. An app bundles an operator's code only when it imports the value for that operator. Joins do not change. A typical app is about 8 kB smaller after gzip. This app has a query collection, a filtered live query, and React `useLiveQuery`. ([#2232](https://github.com/TanStack/db/pull/2232)) + + - An include needs `include()`, `toArray()`, or `materialize()`. Change `issues: q.from(...)` in `select()` to `issues: include(q.from(...))`. A subquery in `select()` without one of these throws `IncludeOperatorRequiredError`. + - Aggregates such as `count()` need no change. A `groupBy` with no aggregate takes `groupKeys()`: `.groupBy(groupKeys(({ todo }) => todo.status))`. Without it, the query throws `GroupingOperatorRequiredError`. + - Hooks do not pool live queries by default. To share one partition among many equality-filtered queries, pass `pool: pooledLiveQueries` in the hook's query config: `useLiveQuery({ query, pool: pooledLiveQueries })`. Results do not change. Angular's `{ params, query }` form takes `pool` the same way. Any other `pool` value, such as the old boolean, throws `InvalidPoolOptionError`, also for a disabled query. For adapter authors: the `pool` option of `resolveLiveQueryValue` is now `pooledLiveQueries` or `false`, not a boolean. An adapter that reads `pool` from its own options checks it with `configPool` first. + - Code that builds query IR by hand must supply the operator code. `IR.Aggregate` takes `groupingCompiler` as its third argument. `IR.IncludesSubquery` takes `includesRuntime` as its fifth argument, before `parentFilters`. Import both from `@tanstack/db`. IR without them throws `GroupingOperatorRequiredError` or `IncludeOperatorRequiredError` when it compiles, unless another aggregate or `groupKeys()` in the query supplies the grouping compiler. Queries that the query builder builds need no change. + - Operator code is part of query identity. A hook that re-renders a grouped query without `groupKeys()` now throws `GroupingOperatorRequiredError`. Before, React and Preact kept the grouped query. + - Vue: when a changed query fails to compile, `useLiveQuery` no longer shows the rows of the previous query. + +- Updated dependencies [[`5649a07`](https://github.com/TanStack/db/commit/5649a07d468117891193c2e4cde9c7706308b1cd), [`7538afd`](https://github.com/TanStack/db/commit/7538afd2fa838340aaa63fca8b221154eb0b1572), [`a07cd73`](https://github.com/TanStack/db/commit/a07cd73739c1f135e658f38621de27f336004d11), [`6c1ca8f`](https://github.com/TanStack/db/commit/6c1ca8f8f9efe437d3cea61b5b9d0a086e2a5e34), [`30175be`](https://github.com/TanStack/db/commit/30175be483fa70799da579e2c407fb7ed9f2b435), [`4c39add`](https://github.com/TanStack/db/commit/4c39add293adb65b4f5537628849f97083a31531), [`1640de8`](https://github.com/TanStack/db/commit/1640de834bca0588f91432ea80a0f4569157e9dc), [`367ea7c`](https://github.com/TanStack/db/commit/367ea7c11e7dee5ca48ba524e55da2a6935d55fb), [`7d9ed02`](https://github.com/TanStack/db/commit/7d9ed02e085d11e871e4b6424d47e3c0d7073023), [`45eeef8`](https://github.com/TanStack/db/commit/45eeef81a7c64966473315558f46e8a53b00e462)]: + - @tanstack/db@0.16.0 + ## 0.2.12 ### Patch Changes diff --git a/packages/angular-db/package.json b/packages/angular-db/package.json index 8616ca2c9e..ef73c2d403 100644 --- a/packages/angular-db/package.json +++ b/packages/angular-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/angular-db", - "version": "0.2.12", + "version": "0.3.0", "description": "Angular integration for @tanstack/db", "author": "Ethan McDaniel", "license": "MIT", diff --git a/packages/browser-db-sqlite-persistence/CHANGELOG.md b/packages/browser-db-sqlite-persistence/CHANGELOG.md index 03ff8b9bca..85b8d02f4a 100644 --- a/packages/browser-db-sqlite-persistence/CHANGELOG.md +++ b/packages/browser-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,18 @@ # @tanstack/browser-db-sqlite-persistence +## 0.2.38 + +### Patch Changes + +- Ship wa-sqlite WebAssembly as a separate asset that the browser loads automatically, instead of inlining it in the OPFS worker. In Vite development, the package finds its assets without configuration; production builds drop that lookup. An install that Vite cannot resolve by package name, or a worker from another installed version of the package, now reports `PersistenceUnavailableError`, and excluding the package from pre-bundling fixes it. ([#2103](https://github.com/TanStack/db/pull/2103)) + +- Add `SQLiteConnectionClosedError`. A browser OPFS connection now rejects requests made after close starts with this error instead of `InvalidPersistedCollectionConfigError`. ([#2213](https://github.com/TanStack/db/pull/2213)) + +- Reject pending OPFS requests when closing the SQLite connection, including when the worker reports a close error. Close now stops accepting requests when it starts, and concurrent `close()` calls share one outcome. The OPFS worker handles one request at a time, so close waits for earlier queries instead of failing while one waits for the OPFS access handle. ([#2213](https://github.com/TanStack/db/pull/2213)) + +- Updated dependencies [[`45c3329`](https://github.com/TanStack/db/commit/45c33292b2c4f09a63f856a829c1e15ca2b470aa), [`205ab71`](https://github.com/TanStack/db/commit/205ab7158d81680c3cc4f06cd9ebe62b30911a5a), [`6bf0e86`](https://github.com/TanStack/db/commit/6bf0e86dbc12e903ef28bb6711968ad06b5d5036), [`4c39add`](https://github.com/TanStack/db/commit/4c39add293adb65b4f5537628849f97083a31531), [`4c39add`](https://github.com/TanStack/db/commit/4c39add293adb65b4f5537628849f97083a31531), [`205ab71`](https://github.com/TanStack/db/commit/205ab7158d81680c3cc4f06cd9ebe62b30911a5a), [`7d9ed02`](https://github.com/TanStack/db/commit/7d9ed02e085d11e871e4b6424d47e3c0d7073023), [`b052e91`](https://github.com/TanStack/db/commit/b052e91c79201ee376771f99503566e736e3d0dd), [`45eeef8`](https://github.com/TanStack/db/commit/45eeef81a7c64966473315558f46e8a53b00e462)]: + - @tanstack/db-sqlite-persistence-core@0.5.0 + ## 0.2.37 ### Patch Changes diff --git a/packages/browser-db-sqlite-persistence/package.json b/packages/browser-db-sqlite-persistence/package.json index 3aaff33bf2..d7eac47b8d 100644 --- a/packages/browser-db-sqlite-persistence/package.json +++ b/packages/browser-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/browser-db-sqlite-persistence", - "version": "0.2.37", + "version": "0.2.38", "description": "Browser wa-sqlite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/capacitor-db-sqlite-persistence/CHANGELOG.md b/packages/capacitor-db-sqlite-persistence/CHANGELOG.md index 92c0ec9de3..e5edb1cf22 100644 --- a/packages/capacitor-db-sqlite-persistence/CHANGELOG.md +++ b/packages/capacitor-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/capacitor-db-sqlite-persistence +## 0.2.38 + +### Patch Changes + +- Updated dependencies [[`45c3329`](https://github.com/TanStack/db/commit/45c33292b2c4f09a63f856a829c1e15ca2b470aa), [`205ab71`](https://github.com/TanStack/db/commit/205ab7158d81680c3cc4f06cd9ebe62b30911a5a), [`6bf0e86`](https://github.com/TanStack/db/commit/6bf0e86dbc12e903ef28bb6711968ad06b5d5036), [`4c39add`](https://github.com/TanStack/db/commit/4c39add293adb65b4f5537628849f97083a31531), [`4c39add`](https://github.com/TanStack/db/commit/4c39add293adb65b4f5537628849f97083a31531), [`205ab71`](https://github.com/TanStack/db/commit/205ab7158d81680c3cc4f06cd9ebe62b30911a5a), [`7d9ed02`](https://github.com/TanStack/db/commit/7d9ed02e085d11e871e4b6424d47e3c0d7073023), [`b052e91`](https://github.com/TanStack/db/commit/b052e91c79201ee376771f99503566e736e3d0dd), [`45eeef8`](https://github.com/TanStack/db/commit/45eeef81a7c64966473315558f46e8a53b00e462)]: + - @tanstack/db-sqlite-persistence-core@0.5.0 + ## 0.2.37 ### Patch Changes diff --git a/packages/capacitor-db-sqlite-persistence/e2e/app/CHANGELOG.md b/packages/capacitor-db-sqlite-persistence/e2e/app/CHANGELOG.md index 8ac985f902..1910611f12 100644 --- a/packages/capacitor-db-sqlite-persistence/e2e/app/CHANGELOG.md +++ b/packages/capacitor-db-sqlite-persistence/e2e/app/CHANGELOG.md @@ -1,5 +1,13 @@ # @tanstack/capacitor-db-sqlite-persistence-e2e-app +## 0.0.50 + +### Patch Changes + +- Updated dependencies [[`5649a07`](https://github.com/TanStack/db/commit/5649a07d468117891193c2e4cde9c7706308b1cd), [`7538afd`](https://github.com/TanStack/db/commit/7538afd2fa838340aaa63fca8b221154eb0b1572), [`a07cd73`](https://github.com/TanStack/db/commit/a07cd73739c1f135e658f38621de27f336004d11), [`6c1ca8f`](https://github.com/TanStack/db/commit/6c1ca8f8f9efe437d3cea61b5b9d0a086e2a5e34), [`30175be`](https://github.com/TanStack/db/commit/30175be483fa70799da579e2c407fb7ed9f2b435), [`4c39add`](https://github.com/TanStack/db/commit/4c39add293adb65b4f5537628849f97083a31531), [`1640de8`](https://github.com/TanStack/db/commit/1640de834bca0588f91432ea80a0f4569157e9dc), [`367ea7c`](https://github.com/TanStack/db/commit/367ea7c11e7dee5ca48ba524e55da2a6935d55fb), [`7d9ed02`](https://github.com/TanStack/db/commit/7d9ed02e085d11e871e4b6424d47e3c0d7073023), [`45eeef8`](https://github.com/TanStack/db/commit/45eeef81a7c64966473315558f46e8a53b00e462)]: + - @tanstack/db@0.16.0 + - @tanstack/capacitor-db-sqlite-persistence@0.2.38 + ## 0.0.49 ### Patch Changes diff --git a/packages/capacitor-db-sqlite-persistence/e2e/app/package.json b/packages/capacitor-db-sqlite-persistence/e2e/app/package.json index 25ab8107f9..8c2fd51c32 100644 --- a/packages/capacitor-db-sqlite-persistence/e2e/app/package.json +++ b/packages/capacitor-db-sqlite-persistence/e2e/app/package.json @@ -1,7 +1,7 @@ { "name": "@tanstack/capacitor-db-sqlite-persistence-e2e-app", "private": true, - "version": "0.0.49", + "version": "0.0.50", "type": "module", "scripts": { "build": "vite build", diff --git a/packages/capacitor-db-sqlite-persistence/package.json b/packages/capacitor-db-sqlite-persistence/package.json index cf1567aefb..bcad15d848 100644 --- a/packages/capacitor-db-sqlite-persistence/package.json +++ b/packages/capacitor-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/capacitor-db-sqlite-persistence", - "version": "0.2.37", + "version": "0.2.38", "description": "Capacitor SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/cloudflare-durable-objects-db-sqlite-persistence/CHANGELOG.md b/packages/cloudflare-durable-objects-db-sqlite-persistence/CHANGELOG.md index b41cc10004..caf59747bf 100644 --- a/packages/cloudflare-durable-objects-db-sqlite-persistence/CHANGELOG.md +++ b/packages/cloudflare-durable-objects-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/cloudflare-durable-objects-db-sqlite-persistence +## 0.2.38 + +### Patch Changes + +- Updated dependencies [[`45c3329`](https://github.com/TanStack/db/commit/45c33292b2c4f09a63f856a829c1e15ca2b470aa), [`205ab71`](https://github.com/TanStack/db/commit/205ab7158d81680c3cc4f06cd9ebe62b30911a5a), [`6bf0e86`](https://github.com/TanStack/db/commit/6bf0e86dbc12e903ef28bb6711968ad06b5d5036), [`4c39add`](https://github.com/TanStack/db/commit/4c39add293adb65b4f5537628849f97083a31531), [`4c39add`](https://github.com/TanStack/db/commit/4c39add293adb65b4f5537628849f97083a31531), [`205ab71`](https://github.com/TanStack/db/commit/205ab7158d81680c3cc4f06cd9ebe62b30911a5a), [`7d9ed02`](https://github.com/TanStack/db/commit/7d9ed02e085d11e871e4b6424d47e3c0d7073023), [`b052e91`](https://github.com/TanStack/db/commit/b052e91c79201ee376771f99503566e736e3d0dd), [`45eeef8`](https://github.com/TanStack/db/commit/45eeef81a7c64966473315558f46e8a53b00e462)]: + - @tanstack/db-sqlite-persistence-core@0.5.0 + ## 0.2.37 ### Patch Changes diff --git a/packages/cloudflare-durable-objects-db-sqlite-persistence/package.json b/packages/cloudflare-durable-objects-db-sqlite-persistence/package.json index 8d3f6a0b00..995d765e9f 100644 --- a/packages/cloudflare-durable-objects-db-sqlite-persistence/package.json +++ b/packages/cloudflare-durable-objects-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/cloudflare-durable-objects-db-sqlite-persistence", - "version": "0.2.37", + "version": "0.2.38", "description": "Cloudflare Durable Object SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/convex-db-collection/CHANGELOG.md b/packages/convex-db-collection/CHANGELOG.md new file mode 100644 index 0000000000..6696ed4dba --- /dev/null +++ b/packages/convex-db-collection/CHANGELOG.md @@ -0,0 +1,15 @@ +# @tanstack/convex-db-collection + +## 0.2.0 + +### Minor Changes + +- Add a Convex collection adapter with typed query subscriptions, full-row sync, automatic reactive pagination with on-demand windows for a fixed ordered query, durable offline mutation replay, optimistic mutation handlers, and support for both Convex JavaScript and React clients. ([#2230](https://github.com/TanStack/db/pull/2230)) + + Add opt-in mutation codecs to the durable outbox while preserving the default wire format and retaining unreadable codec records for recovery. + +### Patch Changes + +- Updated dependencies [[`db476b8`](https://github.com/TanStack/db/commit/db476b8ed9755e0c0b10b833d187354b4c63585c), [`5649a07`](https://github.com/TanStack/db/commit/5649a07d468117891193c2e4cde9c7706308b1cd), [`7538afd`](https://github.com/TanStack/db/commit/7538afd2fa838340aaa63fca8b221154eb0b1572), [`a07cd73`](https://github.com/TanStack/db/commit/a07cd73739c1f135e658f38621de27f336004d11), [`6c1ca8f`](https://github.com/TanStack/db/commit/6c1ca8f8f9efe437d3cea61b5b9d0a086e2a5e34), [`30175be`](https://github.com/TanStack/db/commit/30175be483fa70799da579e2c407fb7ed9f2b435), [`4c39add`](https://github.com/TanStack/db/commit/4c39add293adb65b4f5537628849f97083a31531), [`1640de8`](https://github.com/TanStack/db/commit/1640de834bca0588f91432ea80a0f4569157e9dc), [`367ea7c`](https://github.com/TanStack/db/commit/367ea7c11e7dee5ca48ba524e55da2a6935d55fb), [`7d9ed02`](https://github.com/TanStack/db/commit/7d9ed02e085d11e871e4b6424d47e3c0d7073023), [`45eeef8`](https://github.com/TanStack/db/commit/45eeef81a7c64966473315558f46e8a53b00e462)]: + - @tanstack/offline-transactions@1.2.0 + - @tanstack/db@0.16.0 diff --git a/packages/convex-db-collection/package.json b/packages/convex-db-collection/package.json index 4a3c1bbbb8..3473b4e16f 100644 --- a/packages/convex-db-collection/package.json +++ b/packages/convex-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/convex-db-collection", - "version": "0.1.0", + "version": "0.2.0", "description": "Convex collection for TanStack DB", "author": "Tanner Linsley", "license": "MIT", diff --git a/packages/db-ivm/CHANGELOG.md b/packages/db-ivm/CHANGELOG.md index 0397d463ea..944aef7a5e 100644 --- a/packages/db-ivm/CHANGELOG.md +++ b/packages/db-ivm/CHANGELOG.md @@ -1,5 +1,17 @@ # @tanstack/db-ivm +## 0.2.1 + +### Patch Changes + +- Breaking: Collection keys now always match the declared key type, a string or a number. ([#2266](https://github.com/TanStack/db/pull/2266)) + + - A grouped live query that groups by one field now gives a `null`, `undefined`, boolean, or bigint group value a string key. Before, the key was the raw value, so `$key` was `null` for the `null` group. Read a group's key from its `$key`. String and number group values keep their keys. + - `sync.write` throws `UndefinedKeyError` or `InvalidKeyError` when `getKey` returns a key that is not a string or a number. Optimistic mutations already threw these errors. For a bigint key, the development message now shows the key instead of failing with a `TypeError`. + - `@tanstack/db` no longer exports `SortedMap`, and `SortedMap` no longer has `forEach`. `SortedMap` is the internal ordered map of Collection state. + - When one publication applies 512 or more sync operations, the Collection now merges the changed keys into the existing order. Before, it sorted all rows again. With 50,000 rows, a `compare` option, and 600 operations in each sync transaction, the load takes 181 ms instead of 1,589 ms. + - Keyed `MultiSet.consolidate` groups entries in nested maps. It no longer encodes each value as a string. + ## 0.2.0 ### Minor Changes diff --git a/packages/db-ivm/package.json b/packages/db-ivm/package.json index 2d4a83edcb..6b5509e3f4 100644 --- a/packages/db-ivm/package.json +++ b/packages/db-ivm/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/db-ivm", - "version": "0.2.0", + "version": "0.2.1", "description": "Incremental View Maintenance for TanStack DB based on Differential Dataflow", "author": "Sam Willis", "license": "MIT", diff --git a/packages/db-sqlite-persistence-core/CHANGELOG.md b/packages/db-sqlite-persistence-core/CHANGELOG.md index c03fed43aa..cef308d92e 100644 --- a/packages/db-sqlite-persistence-core/CHANGELOG.md +++ b/packages/db-sqlite-persistence-core/CHANGELOG.md @@ -1,5 +1,80 @@ # @tanstack/db-sqlite-persistence-core +## 0.5.0 + +### Minor Changes + +- Reloaded persisted collections now read their rows before they write a leader election term. A collection still takes its leader lock when it subscribes, but it reserves its durable term only after the database work already queued on the shared scheduler has settled, or as soon as something needs a leader during that election: a local write, a follower request, a sync commit during startup, or `ensureLeadership`. A need that an earlier leader already served does not let a later takeover skip the wait. A tab that takes over treats the departed leader's route as gone, so a sync commit queued behind one of its reads goes to the new leader and settles without waiting out an RPC timeout. The Electron renderer applies the same rule to its own hydrations. The renderer now has a hydration scope, so its startup runs as one hydration, as a Browser tab's does, and a restart keeps its metadata and row reads in one scope, so a term cannot run between them. ([#2219](https://github.com/TanStack/db/pull/2219)) + + `ensureLeadership` now resolves when this node becomes leader, keeps its place in the Web Locks queue instead of replacing its lock request, and rejects if the coordinator is disposed. + + Migration for custom persistence adapters: `runInRegularScope` is now a `RegularScope`, which must also provide `afterQueuedWork()`. It resolves once every operation queued or running on the adapter's scheduler at the call has settled. An adapter without a scheduler can return a promise that settles with its running hydrations. A coordinator now rejects an adapter whose `runInRegularScope` lacks `afterQueuedWork` with `InvalidPersistenceAdapterError`. + +- `initialRender: { strategy: 'network-first' }` now works for on-demand persisted Collections, including Electric's `progressive` mode. Previously the configuration threw `InvalidPersistedCollectionConfigError`. ([#2263](https://github.com/TanStack/db/pull/2263)) + + A subset load that settles online records persisted demand coverage for its exact demand. In a later session, a load of the same demand settles on its rows from SQLite once `networkTimeoutMs` (default 3 seconds) has passed, or at once if the network load fails. The network load keeps running, and its rows publish when they arrive. If it fails after that, the query stays ready. A demand that never settled online still waits for the network. Coverage proves that the rows were complete when they were loaded, not that they are still current. A source truncate, a snapshot transaction, scoped recovery, or a cache-generation rotation clears it. A load that a reset made obsolete, a load that was released before it settled, and a load during the rest of a scoped-recovery sync run record none. Coverage also ends when SQLite holds fewer rows for the demand than when it settled, for example after a source delete or Query DB retention expiry. A `refetch: true` load always waits for the network. An ordered window with a `limit` stays covered only while SQLite holds the same rows for it. + + A query that joins eager and on-demand sources becomes ready when every eager source is ready, which network-first allows from its restored rows after the deadline, and every demand the query reaches on an on-demand source has settled. A network failure alone does not settle an on-demand demand that is not covered. + + This applies only with the default single-process coordinator. With a multi-tab coordinator, such as `BrowserCollectionCoordinator`, on-demand loads still wait for the network. + +- Collections now answer two questions separately: can the data be shown (`status`), and is it still kept up to date (`health`). Once a query has shown data, a network or backend failure never takes it off the screen. ([#2168](https://github.com/TanStack/db/pull/2168)) + + - `status` gains `crashed`: local storage broke (a closed IndexedDB connection, a failed SQLite persistence write or restore, a throwing sync function, or an invariant failure). Nothing recovers it in place, and `reload()` rejects. To recover, call `cleanup({ dependents: true })`, then start the Collection again (for example with `preload()`); it starts with no rows. It reaches every live query and Effect above it. `error` now means only "the first load failed". + - Every Collection has `collection.health`, a health node with `status`, `failureReason` (the adapter is still trying), `error` (the adapter stopped, or the first load failed), `reloading`, and `hasErrors`. A live query's `health` has one node per source alias, in query order, and keeps object identity while unchanged. `health:change` fires when it changes. `findProblems(health)` lists the nodes that have a problem, in query order. Each problem (`HealthProblem`) shows its newest failure in `message`, `cause`, and `at`, and lists every failure still open in `errors`, oldest first: each `reportError()` of the sync run until `reportHealthy()`, or each failed request for more data on a live query's node. One error value is one failure: reporting it again changes nothing, and `reportHealthy(error)` removes just that failure, so a query Collection whose failing Query recovers removes only that Query's failures. After a live query is ready, a failed request for more data (a page, a joined row, or a source reload's replay of its data) is an `error` on the query's own node; the source stays healthy. + - `collection.reload()` starts a new sync run and keeps the rows, status, and error on screen until the reload completes: when the new run's snapshot (`begin({ snapshot: true })`) or `truncate()` applies, or at its `markReady()`, which keeps the rows when no snapshot came. On a live query it reloads every Collection below it that shows an `error`, through nested live queries, then the query itself when its own node has an `error`, including a failed request for more data. It rejects on a `crashed` Collection. When the cleanup of the sync run that a reload retired rejects, the Collection crashes, also if the rejection comes later. A query in `error` stays there until its `reload()`. + - `cleanup()` rejects with `CollectionHasSubscribersError` while a live query with subscribers or an Effect reads the Collection. `cleanup({ dependents: true })` ends them first. `collection.subscribers` lists every dependent. + - A local-only Collection's `reload()` keeps its rows, including the user's writes: `initialData` loads only when the Collection starts, or starts again after cleanup. + - Persisted Collections have one status. With `initialRender: { strategy: 'network-first' }`, restored rows make the Collection `ready` once the deadline passes or the remote fails; an empty restore stays `loading`. Only a local storage failure crashes a persisted Collection: a failed remote subset request is a failure reason, which the next successful remote request clears. + + Breaking changes and migration: + + | Before | After | + | ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | + | hook `isError` | `isLoadingError` (first load failed) or `isCrashed` | + | — | `isSyncError`: `ready` and the health tree has a problem | + | `status: 'error'` after a crash | `status: 'crashed'` | + | adapter `markError(error)` | `fail(error)`. After readiness it sets the node's `error` and keeps the rows. Later calls throw. | + | `cleanup()` then `preload()` to recover | `reload()` | + | adapter `begin({ direct: true })` | `begin()`: every write applies on the rows the Collection holds, also during a reload | + | a reload's first commit replaced every row | `begin({ snapshot: true })` or `truncate()` in the new run replaces them | + | adapter `markReady()` after `fail()` threw | it does nothing | + | `cleanup()` while live queries or Effects read the Collection | rejects; unmount first or pass `{ dependents: true }` | + | `persistedStatus`, `isPersistedReady`, `persistedError`, `preloadForInitialRender()`, `PERSISTED_READINESS` | `status` and `health` | + | query-db `utils.lastError`, `utils.isError`, `utils.errorCount`, `utils.clearError()` | `collection.health`, `reload()` | + | infinite-query hooks' `error` (React, Preact, Vue, Svelte) | the root node's `error` in `health`; `fetchNextPage()` still resolves | + + Custom sync adapters: keep the run's readiness in your `sync` function. A reload calls `sync` again while `collection.status` still shows the previous run's status and the Collection still holds the previous run's rows. The new run's writes apply on those rows; it replaces every row only with a snapshot, a transaction begun with `begin({ snapshot: true })`, or with its own `truncate()`. The reload completes when that replacement applies, or at the new run's `markReady()`, which keeps the rows when no snapshot came. So send the new run's full state as a snapshot, or write against the kept rows: an `insert` of a key the Collection holds throws `DuplicateKeySyncError`, during a reload as at any time, so write `update` for a kept row, also from a replayed load after the reload completed. A reload keeps every row its new run does not rewrite: core never deletes kept rows on its own, so removing a row the backend deleted is the adapter's job, through a snapshot, a `delete`, or row ownership ([#2269](https://github.com/TanStack/db/issues/2269)). An on-demand `loadSubset` result is partial and is never a snapshot. A live query's own reload keeps its rows and its window until its new run's result is complete. `reportError()` and `reportHealthy()` throw before the first `markReady()` and after `fail()`. `markReady()` after the run is ready, or after its `fail()`, does nothing, so an adapter needs no flag of its own to guard it. A second `fail()` in the same sync run does nothing. A first-ready callback that throws while `sync` runs is reported uncaught, and the Collection stays ready. `reload()` called from inside `sync` or a `loadSubset` call rejects, unless a reload is already in flight, whose promise it returns. Calls after a crash do nothing. For an on-demand Collection, a reload aborts the retired run's `loadSubset` calls and asks the new run's `loadSubset` for the current demand as soon as `sync` returns. Honor the `signal`: when core aborts a load, that load is over for core, and its later resolve or reject decides nothing, holds no readiness, and no replay waits for it. A `loadSubset` that throws synchronously is a rejection: a failed load, which `requestSnapshot()` and `setWindow()` report without throwing. A truncate's or reload's replay decides every request error of the query's demand: its success clears them all, and its failure replaces them with the replay's failures. `useLiveSuspenseQuery` now throws an Error for a failed first load or a crash: the cause itself when it is an Error, otherwise an Error with the problem's message whose `cause` holds the reported value. + +### Patch Changes + +- Fix type inference for schema-free collection options wrapped with persistedCollectionOptions and passed to createCollection. ([#2259](https://github.com/TanStack/db/pull/2259)) + + Reject caller utilities that use the reserved local persistence names acceptMutations, getLeadershipState, or forceReloadSubset, instead of silently replacing them. + +- Keep a persisted Collection's status when another tab's commit reloads its rows. Before, a reload from durable rows could mark a tab `ready` while its own source was still loading, or move a tab whose initial sync failed from `error` to `ready`. Now only the Collection's own source changes its status. ([#2265](https://github.com/TanStack/db/pull/2265)) + +- Keep a sync transaction that reserved its commit turn from failing when a persisted hydration starts while it is open. Before, only a subset load admitted during the reservation waited for the commit. A subset load admitted earlier, a reload or reset from a peer tab's notice, sequence-gap recovery, or a hydration that a shared scheduler held past its turn advanced the hydration cycle, and the commit then rejected with `a persisted sync transaction cannot cross a hydration cycle`. Electric reserves every stream transaction, so a deploy that made several tabs refetch at once could put their Collections into `error`. Each of these hydrations now waits outside the apply mutex until the reserved transaction commits, then starts. While it waits, readers keep the rows from before it. The `reserveCommitTurn` contract now covers every persisted hydration. ([#2265](https://github.com/TanStack/db/pull/2265)) + + A peer reset now applies in arrival order with peer commit notices, so a commit from before the reset can no longer publish a row that the reset removed. + + Electric now abandons its open transaction when its stream stops for good, so hydrations that wait for that transaction can run. + + An abandoned reservation now releases the subset loads that waited for it, as a commit does, so they run. Before, a load admitted while the turn was reserved rejected with `SyncTransactionAbortedError` when the source abandoned the transaction. + +- Reloads no longer repeat setup that is already complete. A reopened persisted collection checks its tables, indexes, triggers, and bookkeeping rows in the same query that reads its registration, and runs its nine setup statements only when something is missing. Each adapter checks database-wide setup with one query and runs it only when a table, index, or migrated column is missing, instead of about 19 statements on every load. The write-only global `schema_version` row, which adapters with different schema versions overwrote on every load, is no longer created or written. ([#2219](https://github.com/TanStack/db/pull/2219)) + + A tab no longer rereads all of a collection's rows after its own write. It used to record the write's stream position before that write's commit notice arrived, so the notice looked like a gap and triggered `pullSince` and a full row scan. + +- Add `SQLiteConnectionClosedError`. A browser OPFS connection now rejects requests made after close starts with this error instead of `InvalidPersistedCollectionConfigError`. ([#2213](https://github.com/TanStack/db/pull/2213)) + +- Fix sync metadata reads that missed committed sync writes while an optimistic transaction held them. A sync source now reads its own writes in commit order: `metadata.row.get`, `metadata.collection.get`, and `list()` include committed transactions that have not applied yet, then the open transactions. A transaction begun inside an open one is independent, and a hold no longer changes commit order. A source insert over a different source row is a duplicate even when a seed or a hold is present. ([#2262](https://github.com/TanStack/db/pull/2262)) + + Persisted collections treat cached rows as seeds, so a later `DbClient` seed can replace them, also after another tab's notice makes the collection reload from storage. A reload from another tab's notice keeps a seed accepted during a pending transaction, keeps row metadata of rows that do not exist, and keeps a seed's row metadata unless the notice names it. An update over a `DbClient` seed now stores the whole row the collection shows, so a reload or restart keeps the seed's fields. With `rowUpdateMode: 'full'`, an update now replaces its stored row, so a reload or restart no longer brings back fields the update dropped. A reload deletes rows that storage lacks also while a pending transaction holds them. After a notice that requires a full reload, a later notice that names its rows keeps a seed's row metadata again. A schema reset from another tab clears every row and its metadata; in on-demand mode it keeps the `DbClient` seeds the collection holds and still replays demand. Rows that storage holds after a reset keep their metadata, and an equal re-insert of one keeps it too. A commit whose signal aborts before the persisted wrapper applies it no longer takes a `DbClient` seed's place. An insert in the persisted wrapper replaces the row it lands on and writes an `insert` mutation to storage. An equal re-insert keeps the row's metadata without writing it, and the wrapper no longer slows down quadratically with the size of a transaction. + +- Updated dependencies [[`5649a07`](https://github.com/TanStack/db/commit/5649a07d468117891193c2e4cde9c7706308b1cd), [`7538afd`](https://github.com/TanStack/db/commit/7538afd2fa838340aaa63fca8b221154eb0b1572), [`a07cd73`](https://github.com/TanStack/db/commit/a07cd73739c1f135e658f38621de27f336004d11), [`6c1ca8f`](https://github.com/TanStack/db/commit/6c1ca8f8f9efe437d3cea61b5b9d0a086e2a5e34), [`30175be`](https://github.com/TanStack/db/commit/30175be483fa70799da579e2c407fb7ed9f2b435), [`4c39add`](https://github.com/TanStack/db/commit/4c39add293adb65b4f5537628849f97083a31531), [`1640de8`](https://github.com/TanStack/db/commit/1640de834bca0588f91432ea80a0f4569157e9dc), [`367ea7c`](https://github.com/TanStack/db/commit/367ea7c11e7dee5ca48ba524e55da2a6935d55fb), [`7d9ed02`](https://github.com/TanStack/db/commit/7d9ed02e085d11e871e4b6424d47e3c0d7073023), [`45eeef8`](https://github.com/TanStack/db/commit/45eeef81a7c64966473315558f46e8a53b00e462)]: + - @tanstack/db@0.16.0 + ## 0.4.12 ### Patch Changes diff --git a/packages/db-sqlite-persistence-core/package.json b/packages/db-sqlite-persistence-core/package.json index c4628e7862..46229f8682 100644 --- a/packages/db-sqlite-persistence-core/package.json +++ b/packages/db-sqlite-persistence-core/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/db-sqlite-persistence-core", - "version": "0.4.12", + "version": "0.5.0", "description": "SQLite persisted collection core for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/db/CHANGELOG.md b/packages/db/CHANGELOG.md index cbf122ab29..0b3f9265af 100644 --- a/packages/db/CHANGELOG.md +++ b/packages/db/CHANGELOG.md @@ -1,5 +1,105 @@ # @tanstack/db +## 0.16.0 + +### Minor Changes + +- Collect the helpers the framework adapters share into one adapter layer, exported from `@tanstack/db` and marked unstable for external use. It adds `identifyLiveQuery`, which checks a prepared query's identity and source Collection IDs for a hook that compares query identity, and `bindLiveQueryWindow`, the window binding every `useLiveInfiniteQuery` now uses. The window helpers that binding replaces are no longer exported. ([#2235](https://github.com/TanStack/db/pull/2235)) + + Add `SourceCollectionIdReuseError`, which a mounted hook that compares query identity throws when a different source Collection appears under a Collection ID it already read. + + Add `rejectLiveQueryInput` and `releaseDeferredOnReclaim` for framework hooks. The first releases the sync starts a rejected query deferred and rethrows the rejection. The second releases a render's deferred sync starts when garbage collection reclaims the live query it built, so a render that never commits, because a sibling threw or it suspended, does not keep its sources deferred. Each released source follows its own start policy, so `startSyncImmediate()` or another hook can still start it. + +- Breaking: grouping, includes, and pooling are now opt-in. An app bundles an operator's code only when it imports the value for that operator. Joins do not change. A typical app is about 8 kB smaller after gzip. This app has a query collection, a filtered live query, and React `useLiveQuery`. ([#2232](https://github.com/TanStack/db/pull/2232)) + + - An include needs `include()`, `toArray()`, or `materialize()`. Change `issues: q.from(...)` in `select()` to `issues: include(q.from(...))`. A subquery in `select()` without one of these throws `IncludeOperatorRequiredError`. + - Aggregates such as `count()` need no change. A `groupBy` with no aggregate takes `groupKeys()`: `.groupBy(groupKeys(({ todo }) => todo.status))`. Without it, the query throws `GroupingOperatorRequiredError`. + - Hooks do not pool live queries by default. To share one partition among many equality-filtered queries, pass `pool: pooledLiveQueries` in the hook's query config: `useLiveQuery({ query, pool: pooledLiveQueries })`. Results do not change. Angular's `{ params, query }` form takes `pool` the same way. Any other `pool` value, such as the old boolean, throws `InvalidPoolOptionError`, also for a disabled query. For adapter authors: the `pool` option of `resolveLiveQueryValue` is now `pooledLiveQueries` or `false`, not a boolean. An adapter that reads `pool` from its own options checks it with `configPool` first. + - Code that builds query IR by hand must supply the operator code. `IR.Aggregate` takes `groupingCompiler` as its third argument. `IR.IncludesSubquery` takes `includesRuntime` as its fifth argument, before `parentFilters`. Import both from `@tanstack/db`. IR without them throws `GroupingOperatorRequiredError` or `IncludeOperatorRequiredError` when it compiles, unless another aggregate or `groupKeys()` in the query supplies the grouping compiler. Queries that the query builder builds need no change. + - Operator code is part of query identity. A hook that re-renders a grouped query without `groupKeys()` now throws `GroupingOperatorRequiredError`. Before, React and Preact kept the grouped query. + - Vue: when a changed query fails to compile, `useLiveQuery` no longer shows the rows of the previous query. + +- Breaking: Collection keys now always match the declared key type, a string or a number. ([#2266](https://github.com/TanStack/db/pull/2266)) + + - A grouped live query that groups by one field now gives a `null`, `undefined`, boolean, or bigint group value a string key. Before, the key was the raw value, so `$key` was `null` for the `null` group. Read a group's key from its `$key`. String and number group values keep their keys. + - `sync.write` throws `UndefinedKeyError` or `InvalidKeyError` when `getKey` returns a key that is not a string or a number. Optimistic mutations already threw these errors. For a bigint key, the development message now shows the key instead of failing with a `TypeError`. + - `@tanstack/db` no longer exports `SortedMap`, and `SortedMap` no longer has `forEach`. `SortedMap` is the internal ordered map of Collection state. + - When one publication applies 512 or more sync operations, the Collection now merges the changed keys into the existing order. Before, it sorted all rows again. With 50,000 rows, a `compare` option, and 600 operations in each sync transaction, the load takes 181 ms instead of 1,589 ms. + - Keyed `MultiSet.consolidate` groups entries in nested maps. It no longer encodes each value as a string. + +- Collections now answer two questions separately: can the data be shown (`status`), and is it still kept up to date (`health`). Once a query has shown data, a network or backend failure never takes it off the screen. ([#2168](https://github.com/TanStack/db/pull/2168)) + + - `status` gains `crashed`: local storage broke (a closed IndexedDB connection, a failed SQLite persistence write or restore, a throwing sync function, or an invariant failure). Nothing recovers it in place, and `reload()` rejects. To recover, call `cleanup({ dependents: true })`, then start the Collection again (for example with `preload()`); it starts with no rows. It reaches every live query and Effect above it. `error` now means only "the first load failed". + - Every Collection has `collection.health`, a health node with `status`, `failureReason` (the adapter is still trying), `error` (the adapter stopped, or the first load failed), `reloading`, and `hasErrors`. A live query's `health` has one node per source alias, in query order, and keeps object identity while unchanged. `health:change` fires when it changes. `findProblems(health)` lists the nodes that have a problem, in query order. Each problem (`HealthProblem`) shows its newest failure in `message`, `cause`, and `at`, and lists every failure still open in `errors`, oldest first: each `reportError()` of the sync run until `reportHealthy()`, or each failed request for more data on a live query's node. One error value is one failure: reporting it again changes nothing, and `reportHealthy(error)` removes just that failure, so a query Collection whose failing Query recovers removes only that Query's failures. After a live query is ready, a failed request for more data (a page, a joined row, or a source reload's replay of its data) is an `error` on the query's own node; the source stays healthy. + - `collection.reload()` starts a new sync run and keeps the rows, status, and error on screen until the reload completes: when the new run's snapshot (`begin({ snapshot: true })`) or `truncate()` applies, or at its `markReady()`, which keeps the rows when no snapshot came. On a live query it reloads every Collection below it that shows an `error`, through nested live queries, then the query itself when its own node has an `error`, including a failed request for more data. It rejects on a `crashed` Collection. When the cleanup of the sync run that a reload retired rejects, the Collection crashes, also if the rejection comes later. A query in `error` stays there until its `reload()`. + - `cleanup()` rejects with `CollectionHasSubscribersError` while a live query with subscribers or an Effect reads the Collection. `cleanup({ dependents: true })` ends them first. `collection.subscribers` lists every dependent. + - A local-only Collection's `reload()` keeps its rows, including the user's writes: `initialData` loads only when the Collection starts, or starts again after cleanup. + - Persisted Collections have one status. With `initialRender: { strategy: 'network-first' }`, restored rows make the Collection `ready` once the deadline passes or the remote fails; an empty restore stays `loading`. Only a local storage failure crashes a persisted Collection: a failed remote subset request is a failure reason, which the next successful remote request clears. + + Breaking changes and migration: + + | Before | After | + | ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | + | hook `isError` | `isLoadingError` (first load failed) or `isCrashed` | + | — | `isSyncError`: `ready` and the health tree has a problem | + | `status: 'error'` after a crash | `status: 'crashed'` | + | adapter `markError(error)` | `fail(error)`. After readiness it sets the node's `error` and keeps the rows. Later calls throw. | + | `cleanup()` then `preload()` to recover | `reload()` | + | adapter `begin({ direct: true })` | `begin()`: every write applies on the rows the Collection holds, also during a reload | + | a reload's first commit replaced every row | `begin({ snapshot: true })` or `truncate()` in the new run replaces them | + | adapter `markReady()` after `fail()` threw | it does nothing | + | `cleanup()` while live queries or Effects read the Collection | rejects; unmount first or pass `{ dependents: true }` | + | `persistedStatus`, `isPersistedReady`, `persistedError`, `preloadForInitialRender()`, `PERSISTED_READINESS` | `status` and `health` | + | query-db `utils.lastError`, `utils.isError`, `utils.errorCount`, `utils.clearError()` | `collection.health`, `reload()` | + | infinite-query hooks' `error` (React, Preact, Vue, Svelte) | the root node's `error` in `health`; `fetchNextPage()` still resolves | + + Custom sync adapters: keep the run's readiness in your `sync` function. A reload calls `sync` again while `collection.status` still shows the previous run's status and the Collection still holds the previous run's rows. The new run's writes apply on those rows; it replaces every row only with a snapshot, a transaction begun with `begin({ snapshot: true })`, or with its own `truncate()`. The reload completes when that replacement applies, or at the new run's `markReady()`, which keeps the rows when no snapshot came. So send the new run's full state as a snapshot, or write against the kept rows: an `insert` of a key the Collection holds throws `DuplicateKeySyncError`, during a reload as at any time, so write `update` for a kept row, also from a replayed load after the reload completed. A reload keeps every row its new run does not rewrite: core never deletes kept rows on its own, so removing a row the backend deleted is the adapter's job, through a snapshot, a `delete`, or row ownership ([#2269](https://github.com/TanStack/db/issues/2269)). An on-demand `loadSubset` result is partial and is never a snapshot. A live query's own reload keeps its rows and its window until its new run's result is complete. `reportError()` and `reportHealthy()` throw before the first `markReady()` and after `fail()`. `markReady()` after the run is ready, or after its `fail()`, does nothing, so an adapter needs no flag of its own to guard it. A second `fail()` in the same sync run does nothing. A first-ready callback that throws while `sync` runs is reported uncaught, and the Collection stays ready. `reload()` called from inside `sync` or a `loadSubset` call rejects, unless a reload is already in flight, whose promise it returns. Calls after a crash do nothing. For an on-demand Collection, a reload aborts the retired run's `loadSubset` calls and asks the new run's `loadSubset` for the current demand as soon as `sync` returns. Honor the `signal`: when core aborts a load, that load is over for core, and its later resolve or reject decides nothing, holds no readiness, and no replay waits for it. A `loadSubset` that throws synchronously is a rejection: a failed load, which `requestSnapshot()` and `setWindow()` report without throwing. A truncate's or reload's replay decides every request error of the query's demand: its success clears them all, and its failure replaces them with the replay's failures. `useLiveSuspenseQuery` now throws an Error for a failed first load or a crash: the cause itself when it is an Error, otherwise an Error with the problem's message whose `cause` holds the reported value. + +### Patch Changes + +- Garbage collection no longer cleans up a collection while a transaction that touches it is persisting. Cleaning it up dropped the transaction's optimistic rows while its mutation handler still ran, and rejected waits such as Electric's `awaitTxId`, so a write could fail after the screen that made it unmounted. The `gcTime` delay now starts again when the last such transaction completes or fails, cancels obsolete queued idle cleanup, and preserves the remaining startup grace. Rolling back a manual transaction that stayed pending preserves the existing deadline. Explicit `cleanup()` still starts immediately; a transaction that settles afterward no longer restores another transaction's cleared optimistic rows. ([#2258](https://github.com/TanStack/db/pull/2258)) + +- A query that joins and includes the same source has two subscriptions to it. Their truncate replays now share one barrier, so neither publishes while the other still loads. When a lazy demand of either fails while a failed replay of the source holds the query's results, the query keeps its last complete result until a later replay of that source succeeds. Before, retiring the other replay's failed demand could publish the window without that demand's rows. ([#2231](https://github.com/TanStack/db/pull/2231)) + + Some sources refetch and then report that their pages shifted. An ordered window over such a source no longer skips a row tied with the refetched first page's new end. When the report shows that the page's end moved, the window repairs its first page. It no longer trusts a row that an older tie request installed. + + A window move made while a truncate replay holds a live query's results now applies after that replay, even when a newer truncate discards it. Before, the move could reject, and an older move's window stayed. A reload of the query or of a source still ends the move. + + An ordered, limited query over a source without an order index can read too few rows to fill its window. A join that drops rows can cause this. It no longer reads the whole source then. It reads a prefix twice as long instead. It grows again while that adds rows, so every request stays bounded. + +- Keep ordered live queries accurate and loading when source work overlaps. A window with `orderBy` and `limit` no longer stops loading, keeps old rows, or holds acquisitions in these cases: a failed or superseded request, a truncate replay, a source change during a repair, or an on-demand joined source. Recovery after a failure uses one full-source read, and a window move whose load succeeded now resolves. ([#2194](https://github.com/TanStack/db/pull/2194)) + + When an adapter's `unloadSubset` throws, TanStack DB now reports the error as an uncaught error and continues as if the release succeeded. The error is no longer a subset failure. It does not set `lastError` or `lastSubsetError`, fail a truncate replay, reject `setWindow()`, or dispose an Effect. `unsubscribe()`, `releaseSnapshot()`, and Effect `dispose()` no longer throw or reject with it. + + The newest `setWindow()` call now wins. A pending source recovery that defers two calls no longer lets the older one set the window. A superseded call that succeeds now resolves only after the newest call settles, so awaiting it shows the window that won. + +- Keep a sync transaction that reserved its commit turn from failing when a persisted hydration starts while it is open. Before, only a subset load admitted during the reservation waited for the commit. A subset load admitted earlier, a reload or reset from a peer tab's notice, sequence-gap recovery, or a hydration that a shared scheduler held past its turn advanced the hydration cycle, and the commit then rejected with `a persisted sync transaction cannot cross a hydration cycle`. Electric reserves every stream transaction, so a deploy that made several tabs refetch at once could put their Collections into `error`. Each of these hydrations now waits outside the apply mutex until the reserved transaction commits, then starts. While it waits, readers keep the rows from before it. The `reserveCommitTurn` contract now covers every persisted hydration. ([#2265](https://github.com/TanStack/db/pull/2265)) + + A peer reset now applies in arrival order with peer commit notices, so a commit from before the reset can no longer publish a row that the reset removed. + + Electric now abandons its open transaction when its stream stops for good, so hydrations that wait for that transaction can run. + + An abandoned reservation now releases the subset loads that waited for it, as a commit does, so they run. Before, a load admitted while the turn was reserved rejected with `SyncTransactionAbortedError` when the source abandoned the transaction. + +- Start the required Collection sync run when an ordinary subscriber follows an abandoned suspended render. Deferred acquisition still starts no idle source sync run or acquisition attempt. ([#2229](https://github.com/TanStack/db/pull/2229)) + + Ordinary subscription admission and deferred acquisition resumption throw during cleanup, including automatic GC. Effects created then return disposed handles. Failed setup releases partial ownership, and throwing callbacks cannot prevent healthy observers receiving cleanup status. Subscribers surviving cleanup retain demand for an explicit restart. + + SSR captures each reader's rows and Collection status. Hydration preserves that view through browser preloads, runtime errors, sibling commits, and delayed streamed records. Missing records reject when transport ends. Known pending results survive normal record-stream close. A new stream after client cleanup starts independently of prior failures. + + Empty loading SSR needs no preload. Rendering rows or readiness requires matching query preparation or explicit source Collection preparation. Streaming Suspense prepares its result. Unsupported preparation throws with guidance. Prepared non-streaming Suspense remains supported. The DbClient query hash includes an infinite query's initial limit and offset. + + Apps without `DbClient` exclude its SSR implementation. Its API stays unchanged. + + Svelte roots initialized with `hydrate()` must set `hydrating` on `DbProvider`. Nested providers inherit it. Each provider clears initial hydration after mount so new children read live rows. + +- Fix sync metadata reads that missed committed sync writes while an optimistic transaction held them. A sync source now reads its own writes in commit order: `metadata.row.get`, `metadata.collection.get`, and `list()` include committed transactions that have not applied yet, then the open transactions. A transaction begun inside an open one is independent, and a hold no longer changes commit order. A source insert over a different source row is a duplicate even when a seed or a hold is present. ([#2262](https://github.com/TanStack/db/pull/2262)) + + Persisted collections treat cached rows as seeds, so a later `DbClient` seed can replace them, also after another tab's notice makes the collection reload from storage. A reload from another tab's notice keeps a seed accepted during a pending transaction, keeps row metadata of rows that do not exist, and keeps a seed's row metadata unless the notice names it. An update over a `DbClient` seed now stores the whole row the collection shows, so a reload or restart keeps the seed's fields. With `rowUpdateMode: 'full'`, an update now replaces its stored row, so a reload or restart no longer brings back fields the update dropped. A reload deletes rows that storage lacks also while a pending transaction holds them. After a notice that requires a full reload, a later notice that names its rows keeps a seed's row metadata again. A schema reset from another tab clears every row and its metadata; in on-demand mode it keeps the `DbClient` seeds the collection holds and still replays demand. Rows that storage holds after a reset keep their metadata, and an equal re-insert of one keeps it too. A commit whose signal aborts before the persisted wrapper applies it no longer takes a `DbClient` seed's place. An insert in the persisted wrapper replaces the row it lands on and writes an `insert` mutation to storage. An equal re-insert keeps the row's metadata without writing it, and the wrapper no longer slows down quadratically with the size of a transaction. + +- Updated dependencies [[`367ea7c`](https://github.com/TanStack/db/commit/367ea7c11e7dee5ca48ba524e55da2a6935d55fb)]: + - @tanstack/db-ivm@0.2.1 + ## 0.15.2 ### Patch Changes diff --git a/packages/db/package.json b/packages/db/package.json index 440a6fc943..2d7236c951 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/db", - "version": "0.15.2", + "version": "0.16.0", "description": "A reactive client store for building super fast apps on sync", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/electric-db-collection/CHANGELOG.md b/packages/electric-db-collection/CHANGELOG.md index 5255462e45..6901da7a48 100644 --- a/packages/electric-db-collection/CHANGELOG.md +++ b/packages/electric-db-collection/CHANGELOG.md @@ -1,5 +1,52 @@ # @tanstack/electric-db-collection +## 0.7.0 + +### Minor Changes + +- Collections now answer two questions separately: can the data be shown (`status`), and is it still kept up to date (`health`). Once a query has shown data, a network or backend failure never takes it off the screen. ([#2168](https://github.com/TanStack/db/pull/2168)) + + - `status` gains `crashed`: local storage broke (a closed IndexedDB connection, a failed SQLite persistence write or restore, a throwing sync function, or an invariant failure). Nothing recovers it in place, and `reload()` rejects. To recover, call `cleanup({ dependents: true })`, then start the Collection again (for example with `preload()`); it starts with no rows. It reaches every live query and Effect above it. `error` now means only "the first load failed". + - Every Collection has `collection.health`, a health node with `status`, `failureReason` (the adapter is still trying), `error` (the adapter stopped, or the first load failed), `reloading`, and `hasErrors`. A live query's `health` has one node per source alias, in query order, and keeps object identity while unchanged. `health:change` fires when it changes. `findProblems(health)` lists the nodes that have a problem, in query order. Each problem (`HealthProblem`) shows its newest failure in `message`, `cause`, and `at`, and lists every failure still open in `errors`, oldest first: each `reportError()` of the sync run until `reportHealthy()`, or each failed request for more data on a live query's node. One error value is one failure: reporting it again changes nothing, and `reportHealthy(error)` removes just that failure, so a query Collection whose failing Query recovers removes only that Query's failures. After a live query is ready, a failed request for more data (a page, a joined row, or a source reload's replay of its data) is an `error` on the query's own node; the source stays healthy. + - `collection.reload()` starts a new sync run and keeps the rows, status, and error on screen until the reload completes: when the new run's snapshot (`begin({ snapshot: true })`) or `truncate()` applies, or at its `markReady()`, which keeps the rows when no snapshot came. On a live query it reloads every Collection below it that shows an `error`, through nested live queries, then the query itself when its own node has an `error`, including a failed request for more data. It rejects on a `crashed` Collection. When the cleanup of the sync run that a reload retired rejects, the Collection crashes, also if the rejection comes later. A query in `error` stays there until its `reload()`. + - `cleanup()` rejects with `CollectionHasSubscribersError` while a live query with subscribers or an Effect reads the Collection. `cleanup({ dependents: true })` ends them first. `collection.subscribers` lists every dependent. + - A local-only Collection's `reload()` keeps its rows, including the user's writes: `initialData` loads only when the Collection starts, or starts again after cleanup. + - Persisted Collections have one status. With `initialRender: { strategy: 'network-first' }`, restored rows make the Collection `ready` once the deadline passes or the remote fails; an empty restore stays `loading`. Only a local storage failure crashes a persisted Collection: a failed remote subset request is a failure reason, which the next successful remote request clears. + + Breaking changes and migration: + + | Before | After | + | ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | + | hook `isError` | `isLoadingError` (first load failed) or `isCrashed` | + | — | `isSyncError`: `ready` and the health tree has a problem | + | `status: 'error'` after a crash | `status: 'crashed'` | + | adapter `markError(error)` | `fail(error)`. After readiness it sets the node's `error` and keeps the rows. Later calls throw. | + | `cleanup()` then `preload()` to recover | `reload()` | + | adapter `begin({ direct: true })` | `begin()`: every write applies on the rows the Collection holds, also during a reload | + | a reload's first commit replaced every row | `begin({ snapshot: true })` or `truncate()` in the new run replaces them | + | adapter `markReady()` after `fail()` threw | it does nothing | + | `cleanup()` while live queries or Effects read the Collection | rejects; unmount first or pass `{ dependents: true }` | + | `persistedStatus`, `isPersistedReady`, `persistedError`, `preloadForInitialRender()`, `PERSISTED_READINESS` | `status` and `health` | + | query-db `utils.lastError`, `utils.isError`, `utils.errorCount`, `utils.clearError()` | `collection.health`, `reload()` | + | infinite-query hooks' `error` (React, Preact, Vue, Svelte) | the root node's `error` in `health`; `fetchNextPage()` still resolves | + + Custom sync adapters: keep the run's readiness in your `sync` function. A reload calls `sync` again while `collection.status` still shows the previous run's status and the Collection still holds the previous run's rows. The new run's writes apply on those rows; it replaces every row only with a snapshot, a transaction begun with `begin({ snapshot: true })`, or with its own `truncate()`. The reload completes when that replacement applies, or at the new run's `markReady()`, which keeps the rows when no snapshot came. So send the new run's full state as a snapshot, or write against the kept rows: an `insert` of a key the Collection holds throws `DuplicateKeySyncError`, during a reload as at any time, so write `update` for a kept row, also from a replayed load after the reload completed. A reload keeps every row its new run does not rewrite: core never deletes kept rows on its own, so removing a row the backend deleted is the adapter's job, through a snapshot, a `delete`, or row ownership ([#2269](https://github.com/TanStack/db/issues/2269)). An on-demand `loadSubset` result is partial and is never a snapshot. A live query's own reload keeps its rows and its window until its new run's result is complete. `reportError()` and `reportHealthy()` throw before the first `markReady()` and after `fail()`. `markReady()` after the run is ready, or after its `fail()`, does nothing, so an adapter needs no flag of its own to guard it. A second `fail()` in the same sync run does nothing. A first-ready callback that throws while `sync` runs is reported uncaught, and the Collection stays ready. `reload()` called from inside `sync` or a `loadSubset` call rejects, unless a reload is already in flight, whose promise it returns. Calls after a crash do nothing. For an on-demand Collection, a reload aborts the retired run's `loadSubset` calls and asks the new run's `loadSubset` for the current demand as soon as `sync` returns. Honor the `signal`: when core aborts a load, that load is over for core, and its later resolve or reject decides nothing, holds no readiness, and no replay waits for it. A `loadSubset` that throws synchronously is a rejection: a failed load, which `requestSnapshot()` and `setWindow()` report without throwing. A truncate's or reload's replay decides every request error of the query's demand: its success clears them all, and its failure replaces them with the replay's failures. `useLiveSuspenseQuery` now throws an Error for a failed first load or a crash: the cause itself when it is an Error, otherwise an Error with the problem's message whose `cause` holds the reported value. + +### Patch Changes + +- Release the shape stream's visibility listener when a sync run is cleaned up. The Electric client registers a `visibilitychange` listener for each `ShapeStream` (an `AppState` subscription on React Native, from `@electric-sql/client` 1.5.23) and removes it only in `unsubscribeAll()`, so aborting the stream left the listener behind, and the listener kept the stream reachable. An app that cleans up and recreates collections, or whose on-demand cache rotates its provider session, gained one retained stream each time. ([#2257](https://github.com/TanStack/db/pull/2257)) + +- Keep a sync transaction that reserved its commit turn from failing when a persisted hydration starts while it is open. Before, only a subset load admitted during the reservation waited for the commit. A subset load admitted earlier, a reload or reset from a peer tab's notice, sequence-gap recovery, or a hydration that a shared scheduler held past its turn advanced the hydration cycle, and the commit then rejected with `a persisted sync transaction cannot cross a hydration cycle`. Electric reserves every stream transaction, so a deploy that made several tabs refetch at once could put their Collections into `error`. Each of these hydrations now waits outside the apply mutex until the reserved transaction commits, then starts. While it waits, readers keep the rows from before it. The `reserveCommitTurn` contract now covers every persisted hydration. ([#2265](https://github.com/TanStack/db/pull/2265)) + + A peer reset now applies in arrival order with peer commit notices, so a commit from before the reset can no longer publish a row that the reset removed. + + Electric now abandons its open transaction when its stream stops for good, so hydrations that wait for that transaction can run. + + An abandoned reservation now releases the subset loads that waited for it, as a commit does, so they run. Before, a load admitted while the turn was reserved rejected with `SyncTransactionAbortedError` when the source abandoned the transaction. + +- Updated dependencies [[`5649a07`](https://github.com/TanStack/db/commit/5649a07d468117891193c2e4cde9c7706308b1cd), [`7538afd`](https://github.com/TanStack/db/commit/7538afd2fa838340aaa63fca8b221154eb0b1572), [`a07cd73`](https://github.com/TanStack/db/commit/a07cd73739c1f135e658f38621de27f336004d11), [`6c1ca8f`](https://github.com/TanStack/db/commit/6c1ca8f8f9efe437d3cea61b5b9d0a086e2a5e34), [`30175be`](https://github.com/TanStack/db/commit/30175be483fa70799da579e2c407fb7ed9f2b435), [`4c39add`](https://github.com/TanStack/db/commit/4c39add293adb65b4f5537628849f97083a31531), [`1640de8`](https://github.com/TanStack/db/commit/1640de834bca0588f91432ea80a0f4569157e9dc), [`367ea7c`](https://github.com/TanStack/db/commit/367ea7c11e7dee5ca48ba524e55da2a6935d55fb), [`7d9ed02`](https://github.com/TanStack/db/commit/7d9ed02e085d11e871e4b6424d47e3c0d7073023), [`45eeef8`](https://github.com/TanStack/db/commit/45eeef81a7c64966473315558f46e8a53b00e462)]: + - @tanstack/db@0.16.0 + ## 0.6.3 ### Patch Changes diff --git a/packages/electric-db-collection/package.json b/packages/electric-db-collection/package.json index f7fc2059e3..4e418abdad 100644 --- a/packages/electric-db-collection/package.json +++ b/packages/electric-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/electric-db-collection", - "version": "0.6.3", + "version": "0.7.0", "description": "ElectricSQL collection for TanStack DB", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/electron-db-sqlite-persistence/CHANGELOG.md b/packages/electron-db-sqlite-persistence/CHANGELOG.md index 9464c08b7d..124eb12252 100644 --- a/packages/electron-db-sqlite-persistence/CHANGELOG.md +++ b/packages/electron-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,18 @@ # @tanstack/electron-db-sqlite-persistence +## 0.2.14 + +### Patch Changes + +- Reloaded persisted collections now read their rows before they write a leader election term. A collection still takes its leader lock when it subscribes, but it reserves its durable term only after the database work already queued on the shared scheduler has settled, or as soon as something needs a leader during that election: a local write, a follower request, a sync commit during startup, or `ensureLeadership`. A need that an earlier leader already served does not let a later takeover skip the wait. A tab that takes over treats the departed leader's route as gone, so a sync commit queued behind one of its reads goes to the new leader and settles without waiting out an RPC timeout. The Electron renderer applies the same rule to its own hydrations. The renderer now has a hydration scope, so its startup runs as one hydration, as a Browser tab's does, and a restart keeps its metadata and row reads in one scope, so a term cannot run between them. ([#2219](https://github.com/TanStack/db/pull/2219)) + + `ensureLeadership` now resolves when this node becomes leader, keeps its place in the Web Locks queue instead of replacing its lock request, and rejects if the coordinator is disposed. + + Migration for custom persistence adapters: `runInRegularScope` is now a `RegularScope`, which must also provide `afterQueuedWork()`. It resolves once every operation queued or running on the adapter's scheduler at the call has settled. An adapter without a scheduler can return a promise that settles with its running hydrations. A coordinator now rejects an adapter whose `runInRegularScope` lacks `afterQueuedWork` with `InvalidPersistenceAdapterError`. + +- Updated dependencies [[`45c3329`](https://github.com/TanStack/db/commit/45c33292b2c4f09a63f856a829c1e15ca2b470aa), [`205ab71`](https://github.com/TanStack/db/commit/205ab7158d81680c3cc4f06cd9ebe62b30911a5a), [`6bf0e86`](https://github.com/TanStack/db/commit/6bf0e86dbc12e903ef28bb6711968ad06b5d5036), [`4c39add`](https://github.com/TanStack/db/commit/4c39add293adb65b4f5537628849f97083a31531), [`4c39add`](https://github.com/TanStack/db/commit/4c39add293adb65b4f5537628849f97083a31531), [`205ab71`](https://github.com/TanStack/db/commit/205ab7158d81680c3cc4f06cd9ebe62b30911a5a), [`7d9ed02`](https://github.com/TanStack/db/commit/7d9ed02e085d11e871e4b6424d47e3c0d7073023), [`b052e91`](https://github.com/TanStack/db/commit/b052e91c79201ee376771f99503566e736e3d0dd), [`45eeef8`](https://github.com/TanStack/db/commit/45eeef81a7c64966473315558f46e8a53b00e462)]: + - @tanstack/db-sqlite-persistence-core@0.5.0 + ## 0.2.13 ### Patch Changes diff --git a/packages/electron-db-sqlite-persistence/package.json b/packages/electron-db-sqlite-persistence/package.json index 72e61efa40..8fb1179d1a 100644 --- a/packages/electron-db-sqlite-persistence/package.json +++ b/packages/electron-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/electron-db-sqlite-persistence", - "version": "0.2.13", + "version": "0.2.14", "description": "Electron SQLite persisted collection bridge for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/expo-db-sqlite-persistence/CHANGELOG.md b/packages/expo-db-sqlite-persistence/CHANGELOG.md index bffabf87cc..3a7e64db3b 100644 --- a/packages/expo-db-sqlite-persistence/CHANGELOG.md +++ b/packages/expo-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/expo-db-sqlite-persistence +## 0.2.38 + +### Patch Changes + +- Updated dependencies [[`45c3329`](https://github.com/TanStack/db/commit/45c33292b2c4f09a63f856a829c1e15ca2b470aa), [`205ab71`](https://github.com/TanStack/db/commit/205ab7158d81680c3cc4f06cd9ebe62b30911a5a), [`6bf0e86`](https://github.com/TanStack/db/commit/6bf0e86dbc12e903ef28bb6711968ad06b5d5036), [`4c39add`](https://github.com/TanStack/db/commit/4c39add293adb65b4f5537628849f97083a31531), [`4c39add`](https://github.com/TanStack/db/commit/4c39add293adb65b4f5537628849f97083a31531), [`205ab71`](https://github.com/TanStack/db/commit/205ab7158d81680c3cc4f06cd9ebe62b30911a5a), [`7d9ed02`](https://github.com/TanStack/db/commit/7d9ed02e085d11e871e4b6424d47e3c0d7073023), [`b052e91`](https://github.com/TanStack/db/commit/b052e91c79201ee376771f99503566e736e3d0dd), [`45eeef8`](https://github.com/TanStack/db/commit/45eeef81a7c64966473315558f46e8a53b00e462)]: + - @tanstack/db-sqlite-persistence-core@0.5.0 + ## 0.2.37 ### Patch Changes diff --git a/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/CHANGELOG.md b/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/CHANGELOG.md index 4989b1e9a2..7dfe9b0eac 100644 --- a/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/CHANGELOG.md +++ b/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/CHANGELOG.md @@ -1,5 +1,13 @@ # @tanstack/expo-db-sqlite-persistence-e2e-app +## 0.0.50 + +### Patch Changes + +- Updated dependencies [[`5649a07`](https://github.com/TanStack/db/commit/5649a07d468117891193c2e4cde9c7706308b1cd), [`7538afd`](https://github.com/TanStack/db/commit/7538afd2fa838340aaa63fca8b221154eb0b1572), [`a07cd73`](https://github.com/TanStack/db/commit/a07cd73739c1f135e658f38621de27f336004d11), [`6c1ca8f`](https://github.com/TanStack/db/commit/6c1ca8f8f9efe437d3cea61b5b9d0a086e2a5e34), [`30175be`](https://github.com/TanStack/db/commit/30175be483fa70799da579e2c407fb7ed9f2b435), [`4c39add`](https://github.com/TanStack/db/commit/4c39add293adb65b4f5537628849f97083a31531), [`1640de8`](https://github.com/TanStack/db/commit/1640de834bca0588f91432ea80a0f4569157e9dc), [`367ea7c`](https://github.com/TanStack/db/commit/367ea7c11e7dee5ca48ba524e55da2a6935d55fb), [`7d9ed02`](https://github.com/TanStack/db/commit/7d9ed02e085d11e871e4b6424d47e3c0d7073023), [`45eeef8`](https://github.com/TanStack/db/commit/45eeef81a7c64966473315558f46e8a53b00e462)]: + - @tanstack/db@0.16.0 + - @tanstack/expo-db-sqlite-persistence@0.2.38 + ## 0.0.49 ### Patch Changes diff --git a/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/package.json b/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/package.json index 6ced8992ce..08336df4fb 100644 --- a/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/package.json +++ b/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/package.json @@ -1,7 +1,7 @@ { "name": "@tanstack/expo-db-sqlite-persistence-e2e-app", "private": true, - "version": "0.0.49", + "version": "0.0.50", "main": "index.js", "scripts": { "start": "expo start", diff --git a/packages/expo-db-sqlite-persistence/package.json b/packages/expo-db-sqlite-persistence/package.json index 0d310be2c4..d7768f0b2c 100644 --- a/packages/expo-db-sqlite-persistence/package.json +++ b/packages/expo-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/expo-db-sqlite-persistence", - "version": "0.2.37", + "version": "0.2.38", "description": "Expo SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/node-db-sqlite-persistence/CHANGELOG.md b/packages/node-db-sqlite-persistence/CHANGELOG.md index 2c37b1802b..4077e50302 100644 --- a/packages/node-db-sqlite-persistence/CHANGELOG.md +++ b/packages/node-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/node-db-sqlite-persistence +## 0.2.38 + +### Patch Changes + +- Updated dependencies [[`45c3329`](https://github.com/TanStack/db/commit/45c33292b2c4f09a63f856a829c1e15ca2b470aa), [`205ab71`](https://github.com/TanStack/db/commit/205ab7158d81680c3cc4f06cd9ebe62b30911a5a), [`6bf0e86`](https://github.com/TanStack/db/commit/6bf0e86dbc12e903ef28bb6711968ad06b5d5036), [`4c39add`](https://github.com/TanStack/db/commit/4c39add293adb65b4f5537628849f97083a31531), [`4c39add`](https://github.com/TanStack/db/commit/4c39add293adb65b4f5537628849f97083a31531), [`205ab71`](https://github.com/TanStack/db/commit/205ab7158d81680c3cc4f06cd9ebe62b30911a5a), [`7d9ed02`](https://github.com/TanStack/db/commit/7d9ed02e085d11e871e4b6424d47e3c0d7073023), [`b052e91`](https://github.com/TanStack/db/commit/b052e91c79201ee376771f99503566e736e3d0dd), [`45eeef8`](https://github.com/TanStack/db/commit/45eeef81a7c64966473315558f46e8a53b00e462)]: + - @tanstack/db-sqlite-persistence-core@0.5.0 + ## 0.2.37 ### Patch Changes diff --git a/packages/node-db-sqlite-persistence/package.json b/packages/node-db-sqlite-persistence/package.json index d8d8c04e41..225f9bdef3 100644 --- a/packages/node-db-sqlite-persistence/package.json +++ b/packages/node-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/node-db-sqlite-persistence", - "version": "0.2.37", + "version": "0.2.38", "description": "Node SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/offline-transactions/CHANGELOG.md b/packages/offline-transactions/CHANGELOG.md index fe5f3411b8..300dd9521a 100644 --- a/packages/offline-transactions/CHANGELOG.md +++ b/packages/offline-transactions/CHANGELOG.md @@ -1,5 +1,18 @@ # @tanstack/offline-transactions +## 1.2.0 + +### Minor Changes + +- Add a Convex collection adapter with typed query subscriptions, full-row sync, automatic reactive pagination with on-demand windows for a fixed ordered query, durable offline mutation replay, optimistic mutation handlers, and support for both Convex JavaScript and React clients. ([#2230](https://github.com/TanStack/db/pull/2230)) + + Add opt-in mutation codecs to the durable outbox while preserving the default wire format and retaining unreadable codec records for recovery. + +### Patch Changes + +- Updated dependencies [[`5649a07`](https://github.com/TanStack/db/commit/5649a07d468117891193c2e4cde9c7706308b1cd), [`7538afd`](https://github.com/TanStack/db/commit/7538afd2fa838340aaa63fca8b221154eb0b1572), [`a07cd73`](https://github.com/TanStack/db/commit/a07cd73739c1f135e658f38621de27f336004d11), [`6c1ca8f`](https://github.com/TanStack/db/commit/6c1ca8f8f9efe437d3cea61b5b9d0a086e2a5e34), [`30175be`](https://github.com/TanStack/db/commit/30175be483fa70799da579e2c407fb7ed9f2b435), [`4c39add`](https://github.com/TanStack/db/commit/4c39add293adb65b4f5537628849f97083a31531), [`1640de8`](https://github.com/TanStack/db/commit/1640de834bca0588f91432ea80a0f4569157e9dc), [`367ea7c`](https://github.com/TanStack/db/commit/367ea7c11e7dee5ca48ba524e55da2a6935d55fb), [`7d9ed02`](https://github.com/TanStack/db/commit/7d9ed02e085d11e871e4b6424d47e3c0d7073023), [`45eeef8`](https://github.com/TanStack/db/commit/45eeef81a7c64966473315558f46e8a53b00e462)]: + - @tanstack/db@0.16.0 + ## 1.1.4 ### Patch Changes diff --git a/packages/offline-transactions/package.json b/packages/offline-transactions/package.json index 3855887c5f..0085687696 100644 --- a/packages/offline-transactions/package.json +++ b/packages/offline-transactions/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/offline-transactions", - "version": "1.1.4", + "version": "1.2.0", "description": "Offline-first transaction capabilities for TanStack DB", "author": "TanStack", "license": "MIT", diff --git a/packages/powersync-db-collection/CHANGELOG.md b/packages/powersync-db-collection/CHANGELOG.md index 0d49dd1a49..2d946c7649 100644 --- a/packages/powersync-db-collection/CHANGELOG.md +++ b/packages/powersync-db-collection/CHANGELOG.md @@ -1,5 +1,42 @@ # @tanstack/powersync-db-collection +## 0.3.0 + +### Minor Changes + +- Collections now answer two questions separately: can the data be shown (`status`), and is it still kept up to date (`health`). Once a query has shown data, a network or backend failure never takes it off the screen. ([#2168](https://github.com/TanStack/db/pull/2168)) + + - `status` gains `crashed`: local storage broke (a closed IndexedDB connection, a failed SQLite persistence write or restore, a throwing sync function, or an invariant failure). Nothing recovers it in place, and `reload()` rejects. To recover, call `cleanup({ dependents: true })`, then start the Collection again (for example with `preload()`); it starts with no rows. It reaches every live query and Effect above it. `error` now means only "the first load failed". + - Every Collection has `collection.health`, a health node with `status`, `failureReason` (the adapter is still trying), `error` (the adapter stopped, or the first load failed), `reloading`, and `hasErrors`. A live query's `health` has one node per source alias, in query order, and keeps object identity while unchanged. `health:change` fires when it changes. `findProblems(health)` lists the nodes that have a problem, in query order. Each problem (`HealthProblem`) shows its newest failure in `message`, `cause`, and `at`, and lists every failure still open in `errors`, oldest first: each `reportError()` of the sync run until `reportHealthy()`, or each failed request for more data on a live query's node. One error value is one failure: reporting it again changes nothing, and `reportHealthy(error)` removes just that failure, so a query Collection whose failing Query recovers removes only that Query's failures. After a live query is ready, a failed request for more data (a page, a joined row, or a source reload's replay of its data) is an `error` on the query's own node; the source stays healthy. + - `collection.reload()` starts a new sync run and keeps the rows, status, and error on screen until the reload completes: when the new run's snapshot (`begin({ snapshot: true })`) or `truncate()` applies, or at its `markReady()`, which keeps the rows when no snapshot came. On a live query it reloads every Collection below it that shows an `error`, through nested live queries, then the query itself when its own node has an `error`, including a failed request for more data. It rejects on a `crashed` Collection. When the cleanup of the sync run that a reload retired rejects, the Collection crashes, also if the rejection comes later. A query in `error` stays there until its `reload()`. + - `cleanup()` rejects with `CollectionHasSubscribersError` while a live query with subscribers or an Effect reads the Collection. `cleanup({ dependents: true })` ends them first. `collection.subscribers` lists every dependent. + - A local-only Collection's `reload()` keeps its rows, including the user's writes: `initialData` loads only when the Collection starts, or starts again after cleanup. + - Persisted Collections have one status. With `initialRender: { strategy: 'network-first' }`, restored rows make the Collection `ready` once the deadline passes or the remote fails; an empty restore stays `loading`. Only a local storage failure crashes a persisted Collection: a failed remote subset request is a failure reason, which the next successful remote request clears. + + Breaking changes and migration: + + | Before | After | + | ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | + | hook `isError` | `isLoadingError` (first load failed) or `isCrashed` | + | — | `isSyncError`: `ready` and the health tree has a problem | + | `status: 'error'` after a crash | `status: 'crashed'` | + | adapter `markError(error)` | `fail(error)`. After readiness it sets the node's `error` and keeps the rows. Later calls throw. | + | `cleanup()` then `preload()` to recover | `reload()` | + | adapter `begin({ direct: true })` | `begin()`: every write applies on the rows the Collection holds, also during a reload | + | a reload's first commit replaced every row | `begin({ snapshot: true })` or `truncate()` in the new run replaces them | + | adapter `markReady()` after `fail()` threw | it does nothing | + | `cleanup()` while live queries or Effects read the Collection | rejects; unmount first or pass `{ dependents: true }` | + | `persistedStatus`, `isPersistedReady`, `persistedError`, `preloadForInitialRender()`, `PERSISTED_READINESS` | `status` and `health` | + | query-db `utils.lastError`, `utils.isError`, `utils.errorCount`, `utils.clearError()` | `collection.health`, `reload()` | + | infinite-query hooks' `error` (React, Preact, Vue, Svelte) | the root node's `error` in `health`; `fetchNextPage()` still resolves | + + Custom sync adapters: keep the run's readiness in your `sync` function. A reload calls `sync` again while `collection.status` still shows the previous run's status and the Collection still holds the previous run's rows. The new run's writes apply on those rows; it replaces every row only with a snapshot, a transaction begun with `begin({ snapshot: true })`, or with its own `truncate()`. The reload completes when that replacement applies, or at the new run's `markReady()`, which keeps the rows when no snapshot came. So send the new run's full state as a snapshot, or write against the kept rows: an `insert` of a key the Collection holds throws `DuplicateKeySyncError`, during a reload as at any time, so write `update` for a kept row, also from a replayed load after the reload completed. A reload keeps every row its new run does not rewrite: core never deletes kept rows on its own, so removing a row the backend deleted is the adapter's job, through a snapshot, a `delete`, or row ownership ([#2269](https://github.com/TanStack/db/issues/2269)). An on-demand `loadSubset` result is partial and is never a snapshot. A live query's own reload keeps its rows and its window until its new run's result is complete. `reportError()` and `reportHealthy()` throw before the first `markReady()` and after `fail()`. `markReady()` after the run is ready, or after its `fail()`, does nothing, so an adapter needs no flag of its own to guard it. A second `fail()` in the same sync run does nothing. A first-ready callback that throws while `sync` runs is reported uncaught, and the Collection stays ready. `reload()` called from inside `sync` or a `loadSubset` call rejects, unless a reload is already in flight, whose promise it returns. Calls after a crash do nothing. For an on-demand Collection, a reload aborts the retired run's `loadSubset` calls and asks the new run's `loadSubset` for the current demand as soon as `sync` returns. Honor the `signal`: when core aborts a load, that load is over for core, and its later resolve or reject decides nothing, holds no readiness, and no replay waits for it. A `loadSubset` that throws synchronously is a rejection: a failed load, which `requestSnapshot()` and `setWindow()` report without throwing. A truncate's or reload's replay decides every request error of the query's demand: its success clears them all, and its failure replaces them with the replay's failures. `useLiveSuspenseQuery` now throws an Error for a failed first load or a crash: the cause itself when it is an Error, otherwise an Error with the problem's message whose `cause` holds the reported value. + +### Patch Changes + +- Updated dependencies [[`5649a07`](https://github.com/TanStack/db/commit/5649a07d468117891193c2e4cde9c7706308b1cd), [`7538afd`](https://github.com/TanStack/db/commit/7538afd2fa838340aaa63fca8b221154eb0b1572), [`a07cd73`](https://github.com/TanStack/db/commit/a07cd73739c1f135e658f38621de27f336004d11), [`6c1ca8f`](https://github.com/TanStack/db/commit/6c1ca8f8f9efe437d3cea61b5b9d0a086e2a5e34), [`30175be`](https://github.com/TanStack/db/commit/30175be483fa70799da579e2c407fb7ed9f2b435), [`4c39add`](https://github.com/TanStack/db/commit/4c39add293adb65b4f5537628849f97083a31531), [`1640de8`](https://github.com/TanStack/db/commit/1640de834bca0588f91432ea80a0f4569157e9dc), [`367ea7c`](https://github.com/TanStack/db/commit/367ea7c11e7dee5ca48ba524e55da2a6935d55fb), [`7d9ed02`](https://github.com/TanStack/db/commit/7d9ed02e085d11e871e4b6424d47e3c0d7073023), [`45eeef8`](https://github.com/TanStack/db/commit/45eeef81a7c64966473315558f46e8a53b00e462)]: + - @tanstack/db@0.16.0 + ## 0.2.13 ### Patch Changes diff --git a/packages/powersync-db-collection/package.json b/packages/powersync-db-collection/package.json index da109832ef..8e3c0e08ad 100644 --- a/packages/powersync-db-collection/package.json +++ b/packages/powersync-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/powersync-db-collection", - "version": "0.2.13", + "version": "0.3.0", "description": "PowerSync collection for TanStack DB", "author": "POWERSYNC", "license": "MIT", diff --git a/packages/preact-db/CHANGELOG.md b/packages/preact-db/CHANGELOG.md new file mode 100644 index 0000000000..302b65f6c6 --- /dev/null +++ b/packages/preact-db/CHANGELOG.md @@ -0,0 +1,49 @@ +# @tanstack/preact-db + +## 0.1.0 + +### Minor Changes + +- Add `@tanstack/preact-db`, a Preact adapter with `useLiveQuery` and `useLiveInfiniteQuery`. Both compare query identity derived from the query IR on each render, require a `queryKey` for functional query variants, and pass the shared live-query and infinite-query conformance suites. Queries read concrete source Collections; `DbProvider` and collection descriptors are not supported yet. ([#2235](https://github.com/TanStack/db/pull/2235)) + +- Collections now answer two questions separately: can the data be shown (`status`), and is it still kept up to date (`health`). Once a query has shown data, a network or backend failure never takes it off the screen. ([#2168](https://github.com/TanStack/db/pull/2168)) + + - `status` gains `crashed`: local storage broke (a closed IndexedDB connection, a failed SQLite persistence write or restore, a throwing sync function, or an invariant failure). Nothing recovers it in place, and `reload()` rejects. To recover, call `cleanup({ dependents: true })`, then start the Collection again (for example with `preload()`); it starts with no rows. It reaches every live query and Effect above it. `error` now means only "the first load failed". + - Every Collection has `collection.health`, a health node with `status`, `failureReason` (the adapter is still trying), `error` (the adapter stopped, or the first load failed), `reloading`, and `hasErrors`. A live query's `health` has one node per source alias, in query order, and keeps object identity while unchanged. `health:change` fires when it changes. `findProblems(health)` lists the nodes that have a problem, in query order. Each problem (`HealthProblem`) shows its newest failure in `message`, `cause`, and `at`, and lists every failure still open in `errors`, oldest first: each `reportError()` of the sync run until `reportHealthy()`, or each failed request for more data on a live query's node. One error value is one failure: reporting it again changes nothing, and `reportHealthy(error)` removes just that failure, so a query Collection whose failing Query recovers removes only that Query's failures. After a live query is ready, a failed request for more data (a page, a joined row, or a source reload's replay of its data) is an `error` on the query's own node; the source stays healthy. + - `collection.reload()` starts a new sync run and keeps the rows, status, and error on screen until the reload completes: when the new run's snapshot (`begin({ snapshot: true })`) or `truncate()` applies, or at its `markReady()`, which keeps the rows when no snapshot came. On a live query it reloads every Collection below it that shows an `error`, through nested live queries, then the query itself when its own node has an `error`, including a failed request for more data. It rejects on a `crashed` Collection. When the cleanup of the sync run that a reload retired rejects, the Collection crashes, also if the rejection comes later. A query in `error` stays there until its `reload()`. + - `cleanup()` rejects with `CollectionHasSubscribersError` while a live query with subscribers or an Effect reads the Collection. `cleanup({ dependents: true })` ends them first. `collection.subscribers` lists every dependent. + - A local-only Collection's `reload()` keeps its rows, including the user's writes: `initialData` loads only when the Collection starts, or starts again after cleanup. + - Persisted Collections have one status. With `initialRender: { strategy: 'network-first' }`, restored rows make the Collection `ready` once the deadline passes or the remote fails; an empty restore stays `loading`. Only a local storage failure crashes a persisted Collection: a failed remote subset request is a failure reason, which the next successful remote request clears. + + Breaking changes and migration: + + | Before | After | + | ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | + | hook `isError` | `isLoadingError` (first load failed) or `isCrashed` | + | — | `isSyncError`: `ready` and the health tree has a problem | + | `status: 'error'` after a crash | `status: 'crashed'` | + | adapter `markError(error)` | `fail(error)`. After readiness it sets the node's `error` and keeps the rows. Later calls throw. | + | `cleanup()` then `preload()` to recover | `reload()` | + | adapter `begin({ direct: true })` | `begin()`: every write applies on the rows the Collection holds, also during a reload | + | a reload's first commit replaced every row | `begin({ snapshot: true })` or `truncate()` in the new run replaces them | + | adapter `markReady()` after `fail()` threw | it does nothing | + | `cleanup()` while live queries or Effects read the Collection | rejects; unmount first or pass `{ dependents: true }` | + | `persistedStatus`, `isPersistedReady`, `persistedError`, `preloadForInitialRender()`, `PERSISTED_READINESS` | `status` and `health` | + | query-db `utils.lastError`, `utils.isError`, `utils.errorCount`, `utils.clearError()` | `collection.health`, `reload()` | + | infinite-query hooks' `error` (React, Preact, Vue, Svelte) | the root node's `error` in `health`; `fetchNextPage()` still resolves | + + Custom sync adapters: keep the run's readiness in your `sync` function. A reload calls `sync` again while `collection.status` still shows the previous run's status and the Collection still holds the previous run's rows. The new run's writes apply on those rows; it replaces every row only with a snapshot, a transaction begun with `begin({ snapshot: true })`, or with its own `truncate()`. The reload completes when that replacement applies, or at the new run's `markReady()`, which keeps the rows when no snapshot came. So send the new run's full state as a snapshot, or write against the kept rows: an `insert` of a key the Collection holds throws `DuplicateKeySyncError`, during a reload as at any time, so write `update` for a kept row, also from a replayed load after the reload completed. A reload keeps every row its new run does not rewrite: core never deletes kept rows on its own, so removing a row the backend deleted is the adapter's job, through a snapshot, a `delete`, or row ownership ([#2269](https://github.com/TanStack/db/issues/2269)). An on-demand `loadSubset` result is partial and is never a snapshot. A live query's own reload keeps its rows and its window until its new run's result is complete. `reportError()` and `reportHealthy()` throw before the first `markReady()` and after `fail()`. `markReady()` after the run is ready, or after its `fail()`, does nothing, so an adapter needs no flag of its own to guard it. A second `fail()` in the same sync run does nothing. A first-ready callback that throws while `sync` runs is reported uncaught, and the Collection stays ready. `reload()` called from inside `sync` or a `loadSubset` call rejects, unless a reload is already in flight, whose promise it returns. Calls after a crash do nothing. For an on-demand Collection, a reload aborts the retired run's `loadSubset` calls and asks the new run's `loadSubset` for the current demand as soon as `sync` returns. Honor the `signal`: when core aborts a load, that load is over for core, and its later resolve or reject decides nothing, holds no readiness, and no replay waits for it. A `loadSubset` that throws synchronously is a rejection: a failed load, which `requestSnapshot()` and `setWindow()` report without throwing. A truncate's or reload's replay decides every request error of the query's demand: its success clears them all, and its failure replaces them with the replay's failures. `useLiveSuspenseQuery` now throws an Error for a failed first load or a crash: the cause itself when it is an Error, otherwise an Error with the problem's message whose `cause` holds the reported value. + +### Patch Changes + +- Breaking: grouping, includes, and pooling are now opt-in. An app bundles an operator's code only when it imports the value for that operator. Joins do not change. A typical app is about 8 kB smaller after gzip. This app has a query collection, a filtered live query, and React `useLiveQuery`. ([#2232](https://github.com/TanStack/db/pull/2232)) + + - An include needs `include()`, `toArray()`, or `materialize()`. Change `issues: q.from(...)` in `select()` to `issues: include(q.from(...))`. A subquery in `select()` without one of these throws `IncludeOperatorRequiredError`. + - Aggregates such as `count()` need no change. A `groupBy` with no aggregate takes `groupKeys()`: `.groupBy(groupKeys(({ todo }) => todo.status))`. Without it, the query throws `GroupingOperatorRequiredError`. + - Hooks do not pool live queries by default. To share one partition among many equality-filtered queries, pass `pool: pooledLiveQueries` in the hook's query config: `useLiveQuery({ query, pool: pooledLiveQueries })`. Results do not change. Angular's `{ params, query }` form takes `pool` the same way. Any other `pool` value, such as the old boolean, throws `InvalidPoolOptionError`, also for a disabled query. For adapter authors: the `pool` option of `resolveLiveQueryValue` is now `pooledLiveQueries` or `false`, not a boolean. An adapter that reads `pool` from its own options checks it with `configPool` first. + - Code that builds query IR by hand must supply the operator code. `IR.Aggregate` takes `groupingCompiler` as its third argument. `IR.IncludesSubquery` takes `includesRuntime` as its fifth argument, before `parentFilters`. Import both from `@tanstack/db`. IR without them throws `GroupingOperatorRequiredError` or `IncludeOperatorRequiredError` when it compiles, unless another aggregate or `groupKeys()` in the query supplies the grouping compiler. Queries that the query builder builds need no change. + - Operator code is part of query identity. A hook that re-renders a grouped query without `groupKeys()` now throws `GroupingOperatorRequiredError`. Before, React and Preact kept the grouped query. + - Vue: when a changed query fails to compile, `useLiveQuery` no longer shows the rows of the previous query. + +- Updated dependencies [[`5649a07`](https://github.com/TanStack/db/commit/5649a07d468117891193c2e4cde9c7706308b1cd), [`7538afd`](https://github.com/TanStack/db/commit/7538afd2fa838340aaa63fca8b221154eb0b1572), [`a07cd73`](https://github.com/TanStack/db/commit/a07cd73739c1f135e658f38621de27f336004d11), [`6c1ca8f`](https://github.com/TanStack/db/commit/6c1ca8f8f9efe437d3cea61b5b9d0a086e2a5e34), [`30175be`](https://github.com/TanStack/db/commit/30175be483fa70799da579e2c407fb7ed9f2b435), [`4c39add`](https://github.com/TanStack/db/commit/4c39add293adb65b4f5537628849f97083a31531), [`1640de8`](https://github.com/TanStack/db/commit/1640de834bca0588f91432ea80a0f4569157e9dc), [`367ea7c`](https://github.com/TanStack/db/commit/367ea7c11e7dee5ca48ba524e55da2a6935d55fb), [`7d9ed02`](https://github.com/TanStack/db/commit/7d9ed02e085d11e871e4b6424d47e3c0d7073023), [`45eeef8`](https://github.com/TanStack/db/commit/45eeef81a7c64966473315558f46e8a53b00e462)]: + - @tanstack/db@0.16.0 diff --git a/packages/preact-db/package.json b/packages/preact-db/package.json index 117fa5e5c0..f054f413df 100644 --- a/packages/preact-db/package.json +++ b/packages/preact-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/preact-db", - "version": "0.0.0", + "version": "0.1.0", "description": "Preact integration for @tanstack/db", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/query-db-collection/CHANGELOG.md b/packages/query-db-collection/CHANGELOG.md index dc387cff6e..322e486710 100644 --- a/packages/query-db-collection/CHANGELOG.md +++ b/packages/query-db-collection/CHANGELOG.md @@ -1,5 +1,46 @@ # @tanstack/query-db-collection +## 1.5.0 + +### Minor Changes + +- Collections now answer two questions separately: can the data be shown (`status`), and is it still kept up to date (`health`). Once a query has shown data, a network or backend failure never takes it off the screen. ([#2168](https://github.com/TanStack/db/pull/2168)) + + - `status` gains `crashed`: local storage broke (a closed IndexedDB connection, a failed SQLite persistence write or restore, a throwing sync function, or an invariant failure). Nothing recovers it in place, and `reload()` rejects. To recover, call `cleanup({ dependents: true })`, then start the Collection again (for example with `preload()`); it starts with no rows. It reaches every live query and Effect above it. `error` now means only "the first load failed". + - Every Collection has `collection.health`, a health node with `status`, `failureReason` (the adapter is still trying), `error` (the adapter stopped, or the first load failed), `reloading`, and `hasErrors`. A live query's `health` has one node per source alias, in query order, and keeps object identity while unchanged. `health:change` fires when it changes. `findProblems(health)` lists the nodes that have a problem, in query order. Each problem (`HealthProblem`) shows its newest failure in `message`, `cause`, and `at`, and lists every failure still open in `errors`, oldest first: each `reportError()` of the sync run until `reportHealthy()`, or each failed request for more data on a live query's node. One error value is one failure: reporting it again changes nothing, and `reportHealthy(error)` removes just that failure, so a query Collection whose failing Query recovers removes only that Query's failures. After a live query is ready, a failed request for more data (a page, a joined row, or a source reload's replay of its data) is an `error` on the query's own node; the source stays healthy. + - `collection.reload()` starts a new sync run and keeps the rows, status, and error on screen until the reload completes: when the new run's snapshot (`begin({ snapshot: true })`) or `truncate()` applies, or at its `markReady()`, which keeps the rows when no snapshot came. On a live query it reloads every Collection below it that shows an `error`, through nested live queries, then the query itself when its own node has an `error`, including a failed request for more data. It rejects on a `crashed` Collection. When the cleanup of the sync run that a reload retired rejects, the Collection crashes, also if the rejection comes later. A query in `error` stays there until its `reload()`. + - `cleanup()` rejects with `CollectionHasSubscribersError` while a live query with subscribers or an Effect reads the Collection. `cleanup({ dependents: true })` ends them first. `collection.subscribers` lists every dependent. + - A local-only Collection's `reload()` keeps its rows, including the user's writes: `initialData` loads only when the Collection starts, or starts again after cleanup. + - Persisted Collections have one status. With `initialRender: { strategy: 'network-first' }`, restored rows make the Collection `ready` once the deadline passes or the remote fails; an empty restore stays `loading`. Only a local storage failure crashes a persisted Collection: a failed remote subset request is a failure reason, which the next successful remote request clears. + + Breaking changes and migration: + + | Before | After | + | ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | + | hook `isError` | `isLoadingError` (first load failed) or `isCrashed` | + | — | `isSyncError`: `ready` and the health tree has a problem | + | `status: 'error'` after a crash | `status: 'crashed'` | + | adapter `markError(error)` | `fail(error)`. After readiness it sets the node's `error` and keeps the rows. Later calls throw. | + | `cleanup()` then `preload()` to recover | `reload()` | + | adapter `begin({ direct: true })` | `begin()`: every write applies on the rows the Collection holds, also during a reload | + | a reload's first commit replaced every row | `begin({ snapshot: true })` or `truncate()` in the new run replaces them | + | adapter `markReady()` after `fail()` threw | it does nothing | + | `cleanup()` while live queries or Effects read the Collection | rejects; unmount first or pass `{ dependents: true }` | + | `persistedStatus`, `isPersistedReady`, `persistedError`, `preloadForInitialRender()`, `PERSISTED_READINESS` | `status` and `health` | + | query-db `utils.lastError`, `utils.isError`, `utils.errorCount`, `utils.clearError()` | `collection.health`, `reload()` | + | infinite-query hooks' `error` (React, Preact, Vue, Svelte) | the root node's `error` in `health`; `fetchNextPage()` still resolves | + + Custom sync adapters: keep the run's readiness in your `sync` function. A reload calls `sync` again while `collection.status` still shows the previous run's status and the Collection still holds the previous run's rows. The new run's writes apply on those rows; it replaces every row only with a snapshot, a transaction begun with `begin({ snapshot: true })`, or with its own `truncate()`. The reload completes when that replacement applies, or at the new run's `markReady()`, which keeps the rows when no snapshot came. So send the new run's full state as a snapshot, or write against the kept rows: an `insert` of a key the Collection holds throws `DuplicateKeySyncError`, during a reload as at any time, so write `update` for a kept row, also from a replayed load after the reload completed. A reload keeps every row its new run does not rewrite: core never deletes kept rows on its own, so removing a row the backend deleted is the adapter's job, through a snapshot, a `delete`, or row ownership ([#2269](https://github.com/TanStack/db/issues/2269)). An on-demand `loadSubset` result is partial and is never a snapshot. A live query's own reload keeps its rows and its window until its new run's result is complete. `reportError()` and `reportHealthy()` throw before the first `markReady()` and after `fail()`. `markReady()` after the run is ready, or after its `fail()`, does nothing, so an adapter needs no flag of its own to guard it. A second `fail()` in the same sync run does nothing. A first-ready callback that throws while `sync` runs is reported uncaught, and the Collection stays ready. `reload()` called from inside `sync` or a `loadSubset` call rejects, unless a reload is already in flight, whose promise it returns. Calls after a crash do nothing. For an on-demand Collection, a reload aborts the retired run's `loadSubset` calls and asks the new run's `loadSubset` for the current demand as soon as `sync` returns. Honor the `signal`: when core aborts a load, that load is over for core, and its later resolve or reject decides nothing, holds no readiness, and no replay waits for it. A `loadSubset` that throws synchronously is a rejection: a failed load, which `requestSnapshot()` and `setWindow()` report without throwing. A truncate's or reload's replay decides every request error of the query's demand: its success clears them all, and its failure replaces them with the replay's failures. `useLiveSuspenseQuery` now throws an Error for a failed first load or a crash: the cause itself when it is an Error, otherwise an Error with the problem's message whose `cause` holds the reported value. + +### Patch Changes + +- Fix sync metadata reads that missed committed sync writes while an optimistic transaction held them. A sync source now reads its own writes in commit order: `metadata.row.get`, `metadata.collection.get`, and `list()` include committed transactions that have not applied yet, then the open transactions. A transaction begun inside an open one is independent, and a hold no longer changes commit order. A source insert over a different source row is a duplicate even when a seed or a hold is present. ([#2262](https://github.com/TanStack/db/pull/2262)) + + Persisted collections treat cached rows as seeds, so a later `DbClient` seed can replace them, also after another tab's notice makes the collection reload from storage. A reload from another tab's notice keeps a seed accepted during a pending transaction, keeps row metadata of rows that do not exist, and keeps a seed's row metadata unless the notice names it. An update over a `DbClient` seed now stores the whole row the collection shows, so a reload or restart keeps the seed's fields. With `rowUpdateMode: 'full'`, an update now replaces its stored row, so a reload or restart no longer brings back fields the update dropped. A reload deletes rows that storage lacks also while a pending transaction holds them. After a notice that requires a full reload, a later notice that names its rows keeps a seed's row metadata again. A schema reset from another tab clears every row and its metadata; in on-demand mode it keeps the `DbClient` seeds the collection holds and still replays demand. Rows that storage holds after a reset keep their metadata, and an equal re-insert of one keeps it too. A commit whose signal aborts before the persisted wrapper applies it no longer takes a `DbClient` seed's place. An insert in the persisted wrapper replaces the row it lands on and writes an `insert` mutation to storage. An equal re-insert keeps the row's metadata without writing it, and the wrapper no longer slows down quadratically with the size of a transaction. + +- Updated dependencies [[`5649a07`](https://github.com/TanStack/db/commit/5649a07d468117891193c2e4cde9c7706308b1cd), [`7538afd`](https://github.com/TanStack/db/commit/7538afd2fa838340aaa63fca8b221154eb0b1572), [`a07cd73`](https://github.com/TanStack/db/commit/a07cd73739c1f135e658f38621de27f336004d11), [`6c1ca8f`](https://github.com/TanStack/db/commit/6c1ca8f8f9efe437d3cea61b5b9d0a086e2a5e34), [`30175be`](https://github.com/TanStack/db/commit/30175be483fa70799da579e2c407fb7ed9f2b435), [`4c39add`](https://github.com/TanStack/db/commit/4c39add293adb65b4f5537628849f97083a31531), [`1640de8`](https://github.com/TanStack/db/commit/1640de834bca0588f91432ea80a0f4569157e9dc), [`367ea7c`](https://github.com/TanStack/db/commit/367ea7c11e7dee5ca48ba524e55da2a6935d55fb), [`7d9ed02`](https://github.com/TanStack/db/commit/7d9ed02e085d11e871e4b6424d47e3c0d7073023), [`45eeef8`](https://github.com/TanStack/db/commit/45eeef81a7c64966473315558f46e8a53b00e462)]: + - @tanstack/db@0.16.0 + ## 1.4.8 ### Patch Changes diff --git a/packages/query-db-collection/package.json b/packages/query-db-collection/package.json index bb8363dfea..cd920dcbeb 100644 --- a/packages/query-db-collection/package.json +++ b/packages/query-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/query-db-collection", - "version": "1.4.8", + "version": "1.5.0", "description": "TanStack Query collection for TanStack DB", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/react-db/CHANGELOG.md b/packages/react-db/CHANGELOG.md index e064fe3037..c4594cab4d 100644 --- a/packages/react-db/CHANGELOG.md +++ b/packages/react-db/CHANGELOG.md @@ -1,5 +1,69 @@ # @tanstack/react-db +## 0.6.0 + +### Minor Changes + +- **Breaking (1.0):** a live query whose IR has no stable identity, such as one using `fn.where`, `fn.select`, or `fn.having`, now throws `UnhashableQueryIRError` unless it supplies a nonempty `queryKey`. It previously warned in development and kept its first compilation. An empty `queryKey` now counts as none, as it already did for a `DbClient` preload, so a hook and a preload agree on the query's identity. A dependency array does not replace `queryKey`: in that mode the hook checks identity and claims source Collection IDs each time it binds a live query, including one that `useLiveSuspenseQuery` takes from its cache. A reused source Collection ID now throws `SourceCollectionIdReuseError`. ([#2235](https://github.com/TanStack/db/pull/2235)) + + A rejected render now releases every sync start it deferred for a `DbClient` descriptor, so a later hook or `startSyncImmediate()` can start that source. A render that never commits, because a sibling threw or it suspended, releases them when its live query is reclaimed. + +- Collections now answer two questions separately: can the data be shown (`status`), and is it still kept up to date (`health`). Once a query has shown data, a network or backend failure never takes it off the screen. ([#2168](https://github.com/TanStack/db/pull/2168)) + + - `status` gains `crashed`: local storage broke (a closed IndexedDB connection, a failed SQLite persistence write or restore, a throwing sync function, or an invariant failure). Nothing recovers it in place, and `reload()` rejects. To recover, call `cleanup({ dependents: true })`, then start the Collection again (for example with `preload()`); it starts with no rows. It reaches every live query and Effect above it. `error` now means only "the first load failed". + - Every Collection has `collection.health`, a health node with `status`, `failureReason` (the adapter is still trying), `error` (the adapter stopped, or the first load failed), `reloading`, and `hasErrors`. A live query's `health` has one node per source alias, in query order, and keeps object identity while unchanged. `health:change` fires when it changes. `findProblems(health)` lists the nodes that have a problem, in query order. Each problem (`HealthProblem`) shows its newest failure in `message`, `cause`, and `at`, and lists every failure still open in `errors`, oldest first: each `reportError()` of the sync run until `reportHealthy()`, or each failed request for more data on a live query's node. One error value is one failure: reporting it again changes nothing, and `reportHealthy(error)` removes just that failure, so a query Collection whose failing Query recovers removes only that Query's failures. After a live query is ready, a failed request for more data (a page, a joined row, or a source reload's replay of its data) is an `error` on the query's own node; the source stays healthy. + - `collection.reload()` starts a new sync run and keeps the rows, status, and error on screen until the reload completes: when the new run's snapshot (`begin({ snapshot: true })`) or `truncate()` applies, or at its `markReady()`, which keeps the rows when no snapshot came. On a live query it reloads every Collection below it that shows an `error`, through nested live queries, then the query itself when its own node has an `error`, including a failed request for more data. It rejects on a `crashed` Collection. When the cleanup of the sync run that a reload retired rejects, the Collection crashes, also if the rejection comes later. A query in `error` stays there until its `reload()`. + - `cleanup()` rejects with `CollectionHasSubscribersError` while a live query with subscribers or an Effect reads the Collection. `cleanup({ dependents: true })` ends them first. `collection.subscribers` lists every dependent. + - A local-only Collection's `reload()` keeps its rows, including the user's writes: `initialData` loads only when the Collection starts, or starts again after cleanup. + - Persisted Collections have one status. With `initialRender: { strategy: 'network-first' }`, restored rows make the Collection `ready` once the deadline passes or the remote fails; an empty restore stays `loading`. Only a local storage failure crashes a persisted Collection: a failed remote subset request is a failure reason, which the next successful remote request clears. + + Breaking changes and migration: + + | Before | After | + | ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | + | hook `isError` | `isLoadingError` (first load failed) or `isCrashed` | + | — | `isSyncError`: `ready` and the health tree has a problem | + | `status: 'error'` after a crash | `status: 'crashed'` | + | adapter `markError(error)` | `fail(error)`. After readiness it sets the node's `error` and keeps the rows. Later calls throw. | + | `cleanup()` then `preload()` to recover | `reload()` | + | adapter `begin({ direct: true })` | `begin()`: every write applies on the rows the Collection holds, also during a reload | + | a reload's first commit replaced every row | `begin({ snapshot: true })` or `truncate()` in the new run replaces them | + | adapter `markReady()` after `fail()` threw | it does nothing | + | `cleanup()` while live queries or Effects read the Collection | rejects; unmount first or pass `{ dependents: true }` | + | `persistedStatus`, `isPersistedReady`, `persistedError`, `preloadForInitialRender()`, `PERSISTED_READINESS` | `status` and `health` | + | query-db `utils.lastError`, `utils.isError`, `utils.errorCount`, `utils.clearError()` | `collection.health`, `reload()` | + | infinite-query hooks' `error` (React, Preact, Vue, Svelte) | the root node's `error` in `health`; `fetchNextPage()` still resolves | + + Custom sync adapters: keep the run's readiness in your `sync` function. A reload calls `sync` again while `collection.status` still shows the previous run's status and the Collection still holds the previous run's rows. The new run's writes apply on those rows; it replaces every row only with a snapshot, a transaction begun with `begin({ snapshot: true })`, or with its own `truncate()`. The reload completes when that replacement applies, or at the new run's `markReady()`, which keeps the rows when no snapshot came. So send the new run's full state as a snapshot, or write against the kept rows: an `insert` of a key the Collection holds throws `DuplicateKeySyncError`, during a reload as at any time, so write `update` for a kept row, also from a replayed load after the reload completed. A reload keeps every row its new run does not rewrite: core never deletes kept rows on its own, so removing a row the backend deleted is the adapter's job, through a snapshot, a `delete`, or row ownership ([#2269](https://github.com/TanStack/db/issues/2269)). An on-demand `loadSubset` result is partial and is never a snapshot. A live query's own reload keeps its rows and its window until its new run's result is complete. `reportError()` and `reportHealthy()` throw before the first `markReady()` and after `fail()`. `markReady()` after the run is ready, or after its `fail()`, does nothing, so an adapter needs no flag of its own to guard it. A second `fail()` in the same sync run does nothing. A first-ready callback that throws while `sync` runs is reported uncaught, and the Collection stays ready. `reload()` called from inside `sync` or a `loadSubset` call rejects, unless a reload is already in flight, whose promise it returns. Calls after a crash do nothing. For an on-demand Collection, a reload aborts the retired run's `loadSubset` calls and asks the new run's `loadSubset` for the current demand as soon as `sync` returns. Honor the `signal`: when core aborts a load, that load is over for core, and its later resolve or reject decides nothing, holds no readiness, and no replay waits for it. A `loadSubset` that throws synchronously is a rejection: a failed load, which `requestSnapshot()` and `setWindow()` report without throwing. A truncate's or reload's replay decides every request error of the query's demand: its success clears them all, and its failure replaces them with the replay's failures. `useLiveSuspenseQuery` now throws an Error for a failed first load or a crash: the cause itself when it is an Error, otherwise an Error with the problem's message whose `cause` holds the reported value. + +### Patch Changes + +- Breaking: grouping, includes, and pooling are now opt-in. An app bundles an operator's code only when it imports the value for that operator. Joins do not change. A typical app is about 8 kB smaller after gzip. This app has a query collection, a filtered live query, and React `useLiveQuery`. ([#2232](https://github.com/TanStack/db/pull/2232)) + + - An include needs `include()`, `toArray()`, or `materialize()`. Change `issues: q.from(...)` in `select()` to `issues: include(q.from(...))`. A subquery in `select()` without one of these throws `IncludeOperatorRequiredError`. + - Aggregates such as `count()` need no change. A `groupBy` with no aggregate takes `groupKeys()`: `.groupBy(groupKeys(({ todo }) => todo.status))`. Without it, the query throws `GroupingOperatorRequiredError`. + - Hooks do not pool live queries by default. To share one partition among many equality-filtered queries, pass `pool: pooledLiveQueries` in the hook's query config: `useLiveQuery({ query, pool: pooledLiveQueries })`. Results do not change. Angular's `{ params, query }` form takes `pool` the same way. Any other `pool` value, such as the old boolean, throws `InvalidPoolOptionError`, also for a disabled query. For adapter authors: the `pool` option of `resolveLiveQueryValue` is now `pooledLiveQueries` or `false`, not a boolean. An adapter that reads `pool` from its own options checks it with `configPool` first. + - Code that builds query IR by hand must supply the operator code. `IR.Aggregate` takes `groupingCompiler` as its third argument. `IR.IncludesSubquery` takes `includesRuntime` as its fifth argument, before `parentFilters`. Import both from `@tanstack/db`. IR without them throws `GroupingOperatorRequiredError` or `IncludeOperatorRequiredError` when it compiles, unless another aggregate or `groupKeys()` in the query supplies the grouping compiler. Queries that the query builder builds need no change. + - Operator code is part of query identity. A hook that re-renders a grouped query without `groupKeys()` now throws `GroupingOperatorRequiredError`. Before, React and Preact kept the grouped query. + - Vue: when a changed query fails to compile, `useLiveQuery` no longer shows the rows of the previous query. + +- Production builds no longer include the development warnings of `useLiveQuery`. A typical app's bundle is about 1.6 kB smaller before compression and 0.66 kB smaller after gzip. ([#2227](https://github.com/TanStack/db/pull/2227)) + +- Start the required Collection sync run when an ordinary subscriber follows an abandoned suspended render. Deferred acquisition still starts no idle source sync run or acquisition attempt. ([#2229](https://github.com/TanStack/db/pull/2229)) + + Ordinary subscription admission and deferred acquisition resumption throw during cleanup, including automatic GC. Effects created then return disposed handles. Failed setup releases partial ownership, and throwing callbacks cannot prevent healthy observers receiving cleanup status. Subscribers surviving cleanup retain demand for an explicit restart. + + SSR captures each reader's rows and Collection status. Hydration preserves that view through browser preloads, runtime errors, sibling commits, and delayed streamed records. Missing records reject when transport ends. Known pending results survive normal record-stream close. A new stream after client cleanup starts independently of prior failures. + + Empty loading SSR needs no preload. Rendering rows or readiness requires matching query preparation or explicit source Collection preparation. Streaming Suspense prepares its result. Unsupported preparation throws with guidance. Prepared non-streaming Suspense remains supported. The DbClient query hash includes an infinite query's initial limit and offset. + + Apps without `DbClient` exclude its SSR implementation. Its API stays unchanged. + + Svelte roots initialized with `hydrate()` must set `hydrating` on `DbProvider`. Nested providers inherit it. Each provider clears initial hydration after mount so new children read live rows. + +- Updated dependencies [[`5649a07`](https://github.com/TanStack/db/commit/5649a07d468117891193c2e4cde9c7706308b1cd), [`7538afd`](https://github.com/TanStack/db/commit/7538afd2fa838340aaa63fca8b221154eb0b1572), [`a07cd73`](https://github.com/TanStack/db/commit/a07cd73739c1f135e658f38621de27f336004d11), [`6c1ca8f`](https://github.com/TanStack/db/commit/6c1ca8f8f9efe437d3cea61b5b9d0a086e2a5e34), [`30175be`](https://github.com/TanStack/db/commit/30175be483fa70799da579e2c407fb7ed9f2b435), [`4c39add`](https://github.com/TanStack/db/commit/4c39add293adb65b4f5537628849f97083a31531), [`1640de8`](https://github.com/TanStack/db/commit/1640de834bca0588f91432ea80a0f4569157e9dc), [`367ea7c`](https://github.com/TanStack/db/commit/367ea7c11e7dee5ca48ba524e55da2a6935d55fb), [`7d9ed02`](https://github.com/TanStack/db/commit/7d9ed02e085d11e871e4b6424d47e3c0d7073023), [`45eeef8`](https://github.com/TanStack/db/commit/45eeef81a7c64966473315558f46e8a53b00e462)]: + - @tanstack/db@0.16.0 + ## 0.5.12 ### Patch Changes diff --git a/packages/react-db/package.json b/packages/react-db/package.json index e8cc6eeee4..809456c708 100644 --- a/packages/react-db/package.json +++ b/packages/react-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/react-db", - "version": "0.5.12", + "version": "0.6.0", "description": "React integration for @tanstack/db", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/react-native-db-sqlite-persistence/CHANGELOG.md b/packages/react-native-db-sqlite-persistence/CHANGELOG.md index 21ea7b69eb..f16cbf36b1 100644 --- a/packages/react-native-db-sqlite-persistence/CHANGELOG.md +++ b/packages/react-native-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/react-native-db-sqlite-persistence +## 0.2.38 + +### Patch Changes + +- Updated dependencies [[`45c3329`](https://github.com/TanStack/db/commit/45c33292b2c4f09a63f856a829c1e15ca2b470aa), [`205ab71`](https://github.com/TanStack/db/commit/205ab7158d81680c3cc4f06cd9ebe62b30911a5a), [`6bf0e86`](https://github.com/TanStack/db/commit/6bf0e86dbc12e903ef28bb6711968ad06b5d5036), [`4c39add`](https://github.com/TanStack/db/commit/4c39add293adb65b4f5537628849f97083a31531), [`4c39add`](https://github.com/TanStack/db/commit/4c39add293adb65b4f5537628849f97083a31531), [`205ab71`](https://github.com/TanStack/db/commit/205ab7158d81680c3cc4f06cd9ebe62b30911a5a), [`7d9ed02`](https://github.com/TanStack/db/commit/7d9ed02e085d11e871e4b6424d47e3c0d7073023), [`b052e91`](https://github.com/TanStack/db/commit/b052e91c79201ee376771f99503566e736e3d0dd), [`45eeef8`](https://github.com/TanStack/db/commit/45eeef81a7c64966473315558f46e8a53b00e462)]: + - @tanstack/db-sqlite-persistence-core@0.5.0 + ## 0.2.37 ### Patch Changes diff --git a/packages/react-native-db-sqlite-persistence/package.json b/packages/react-native-db-sqlite-persistence/package.json index 62aa57249e..88f28fe909 100644 --- a/packages/react-native-db-sqlite-persistence/package.json +++ b/packages/react-native-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/react-native-db-sqlite-persistence", - "version": "0.2.37", + "version": "0.2.38", "description": "React Native and Expo SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/react-router-with-db/CHANGELOG.md b/packages/react-router-with-db/CHANGELOG.md index de46d8d59f..de73be673c 100644 --- a/packages/react-router-with-db/CHANGELOG.md +++ b/packages/react-router-with-db/CHANGELOG.md @@ -1,5 +1,21 @@ # @tanstack/react-router-with-db +## 0.1.1 + +### Patch Changes + +- Start the required Collection sync run when an ordinary subscriber follows an abandoned suspended render. Deferred acquisition still starts no idle source sync run or acquisition attempt. ([#2229](https://github.com/TanStack/db/pull/2229)) + + Ordinary subscription admission and deferred acquisition resumption throw during cleanup, including automatic GC. Effects created then return disposed handles. Failed setup releases partial ownership, and throwing callbacks cannot prevent healthy observers receiving cleanup status. Subscribers surviving cleanup retain demand for an explicit restart. + + SSR captures each reader's rows and Collection status. Hydration preserves that view through browser preloads, runtime errors, sibling commits, and delayed streamed records. Missing records reject when transport ends. Known pending results survive normal record-stream close. A new stream after client cleanup starts independently of prior failures. + + Empty loading SSR needs no preload. Rendering rows or readiness requires matching query preparation or explicit source Collection preparation. Streaming Suspense prepares its result. Unsupported preparation throws with guidance. Prepared non-streaming Suspense remains supported. The DbClient query hash includes an infinite query's initial limit and offset. + + Apps without `DbClient` exclude its SSR implementation. Its API stays unchanged. + + Svelte roots initialized with `hydrate()` must set `hydrating` on `DbProvider`. Nested providers inherit it. Each provider clears initial hydration after mount so new children read live rows. + ## 0.1.0 ### Minor Changes diff --git a/packages/react-router-with-db/package.json b/packages/react-router-with-db/package.json index b0b47d56fc..7bd7cae9d9 100644 --- a/packages/react-router-with-db/package.json +++ b/packages/react-router-with-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/react-router-with-db", - "version": "0.1.0", + "version": "0.1.1", "description": "TanStack Router SSR integration for TanStack DB", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/rxdb-db-collection/CHANGELOG.md b/packages/rxdb-db-collection/CHANGELOG.md index 0c98ff9c9a..f3292caa0c 100644 --- a/packages/rxdb-db-collection/CHANGELOG.md +++ b/packages/rxdb-db-collection/CHANGELOG.md @@ -1,5 +1,42 @@ # @tanstack/rxdb-db-collection +## 0.2.0 + +### Minor Changes + +- Collections now answer two questions separately: can the data be shown (`status`), and is it still kept up to date (`health`). Once a query has shown data, a network or backend failure never takes it off the screen. ([#2168](https://github.com/TanStack/db/pull/2168)) + + - `status` gains `crashed`: local storage broke (a closed IndexedDB connection, a failed SQLite persistence write or restore, a throwing sync function, or an invariant failure). Nothing recovers it in place, and `reload()` rejects. To recover, call `cleanup({ dependents: true })`, then start the Collection again (for example with `preload()`); it starts with no rows. It reaches every live query and Effect above it. `error` now means only "the first load failed". + - Every Collection has `collection.health`, a health node with `status`, `failureReason` (the adapter is still trying), `error` (the adapter stopped, or the first load failed), `reloading`, and `hasErrors`. A live query's `health` has one node per source alias, in query order, and keeps object identity while unchanged. `health:change` fires when it changes. `findProblems(health)` lists the nodes that have a problem, in query order. Each problem (`HealthProblem`) shows its newest failure in `message`, `cause`, and `at`, and lists every failure still open in `errors`, oldest first: each `reportError()` of the sync run until `reportHealthy()`, or each failed request for more data on a live query's node. One error value is one failure: reporting it again changes nothing, and `reportHealthy(error)` removes just that failure, so a query Collection whose failing Query recovers removes only that Query's failures. After a live query is ready, a failed request for more data (a page, a joined row, or a source reload's replay of its data) is an `error` on the query's own node; the source stays healthy. + - `collection.reload()` starts a new sync run and keeps the rows, status, and error on screen until the reload completes: when the new run's snapshot (`begin({ snapshot: true })`) or `truncate()` applies, or at its `markReady()`, which keeps the rows when no snapshot came. On a live query it reloads every Collection below it that shows an `error`, through nested live queries, then the query itself when its own node has an `error`, including a failed request for more data. It rejects on a `crashed` Collection. When the cleanup of the sync run that a reload retired rejects, the Collection crashes, also if the rejection comes later. A query in `error` stays there until its `reload()`. + - `cleanup()` rejects with `CollectionHasSubscribersError` while a live query with subscribers or an Effect reads the Collection. `cleanup({ dependents: true })` ends them first. `collection.subscribers` lists every dependent. + - A local-only Collection's `reload()` keeps its rows, including the user's writes: `initialData` loads only when the Collection starts, or starts again after cleanup. + - Persisted Collections have one status. With `initialRender: { strategy: 'network-first' }`, restored rows make the Collection `ready` once the deadline passes or the remote fails; an empty restore stays `loading`. Only a local storage failure crashes a persisted Collection: a failed remote subset request is a failure reason, which the next successful remote request clears. + + Breaking changes and migration: + + | Before | After | + | ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | + | hook `isError` | `isLoadingError` (first load failed) or `isCrashed` | + | — | `isSyncError`: `ready` and the health tree has a problem | + | `status: 'error'` after a crash | `status: 'crashed'` | + | adapter `markError(error)` | `fail(error)`. After readiness it sets the node's `error` and keeps the rows. Later calls throw. | + | `cleanup()` then `preload()` to recover | `reload()` | + | adapter `begin({ direct: true })` | `begin()`: every write applies on the rows the Collection holds, also during a reload | + | a reload's first commit replaced every row | `begin({ snapshot: true })` or `truncate()` in the new run replaces them | + | adapter `markReady()` after `fail()` threw | it does nothing | + | `cleanup()` while live queries or Effects read the Collection | rejects; unmount first or pass `{ dependents: true }` | + | `persistedStatus`, `isPersistedReady`, `persistedError`, `preloadForInitialRender()`, `PERSISTED_READINESS` | `status` and `health` | + | query-db `utils.lastError`, `utils.isError`, `utils.errorCount`, `utils.clearError()` | `collection.health`, `reload()` | + | infinite-query hooks' `error` (React, Preact, Vue, Svelte) | the root node's `error` in `health`; `fetchNextPage()` still resolves | + + Custom sync adapters: keep the run's readiness in your `sync` function. A reload calls `sync` again while `collection.status` still shows the previous run's status and the Collection still holds the previous run's rows. The new run's writes apply on those rows; it replaces every row only with a snapshot, a transaction begun with `begin({ snapshot: true })`, or with its own `truncate()`. The reload completes when that replacement applies, or at the new run's `markReady()`, which keeps the rows when no snapshot came. So send the new run's full state as a snapshot, or write against the kept rows: an `insert` of a key the Collection holds throws `DuplicateKeySyncError`, during a reload as at any time, so write `update` for a kept row, also from a replayed load after the reload completed. A reload keeps every row its new run does not rewrite: core never deletes kept rows on its own, so removing a row the backend deleted is the adapter's job, through a snapshot, a `delete`, or row ownership ([#2269](https://github.com/TanStack/db/issues/2269)). An on-demand `loadSubset` result is partial and is never a snapshot. A live query's own reload keeps its rows and its window until its new run's result is complete. `reportError()` and `reportHealthy()` throw before the first `markReady()` and after `fail()`. `markReady()` after the run is ready, or after its `fail()`, does nothing, so an adapter needs no flag of its own to guard it. A second `fail()` in the same sync run does nothing. A first-ready callback that throws while `sync` runs is reported uncaught, and the Collection stays ready. `reload()` called from inside `sync` or a `loadSubset` call rejects, unless a reload is already in flight, whose promise it returns. Calls after a crash do nothing. For an on-demand Collection, a reload aborts the retired run's `loadSubset` calls and asks the new run's `loadSubset` for the current demand as soon as `sync` returns. Honor the `signal`: when core aborts a load, that load is over for core, and its later resolve or reject decides nothing, holds no readiness, and no replay waits for it. A `loadSubset` that throws synchronously is a rejection: a failed load, which `requestSnapshot()` and `setWindow()` report without throwing. A truncate's or reload's replay decides every request error of the query's demand: its success clears them all, and its failure replaces them with the replay's failures. `useLiveSuspenseQuery` now throws an Error for a failed first load or a crash: the cause itself when it is an Error, otherwise an Error with the problem's message whose `cause` holds the reported value. + +### Patch Changes + +- Updated dependencies [[`5649a07`](https://github.com/TanStack/db/commit/5649a07d468117891193c2e4cde9c7706308b1cd), [`7538afd`](https://github.com/TanStack/db/commit/7538afd2fa838340aaa63fca8b221154eb0b1572), [`a07cd73`](https://github.com/TanStack/db/commit/a07cd73739c1f135e658f38621de27f336004d11), [`6c1ca8f`](https://github.com/TanStack/db/commit/6c1ca8f8f9efe437d3cea61b5b9d0a086e2a5e34), [`30175be`](https://github.com/TanStack/db/commit/30175be483fa70799da579e2c407fb7ed9f2b435), [`4c39add`](https://github.com/TanStack/db/commit/4c39add293adb65b4f5537628849f97083a31531), [`1640de8`](https://github.com/TanStack/db/commit/1640de834bca0588f91432ea80a0f4569157e9dc), [`367ea7c`](https://github.com/TanStack/db/commit/367ea7c11e7dee5ca48ba524e55da2a6935d55fb), [`7d9ed02`](https://github.com/TanStack/db/commit/7d9ed02e085d11e871e4b6424d47e3c0d7073023), [`45eeef8`](https://github.com/TanStack/db/commit/45eeef81a7c64966473315558f46e8a53b00e462)]: + - @tanstack/db@0.16.0 + ## 0.1.111 ### Patch Changes diff --git a/packages/rxdb-db-collection/package.json b/packages/rxdb-db-collection/package.json index f0bb54d37a..80b0ed74b2 100644 --- a/packages/rxdb-db-collection/package.json +++ b/packages/rxdb-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/rxdb-db-collection", - "version": "0.1.111", + "version": "0.2.0", "description": "Reactive, Offline-First adapter for TanStack DB using RxDB. Sync, Replication and Local-First support.", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/solid-db/CHANGELOG.md b/packages/solid-db/CHANGELOG.md index 73701dfbae..072e45df15 100644 --- a/packages/solid-db/CHANGELOG.md +++ b/packages/solid-db/CHANGELOG.md @@ -1,5 +1,51 @@ # @tanstack/react-db +## 0.4.0 + +### Minor Changes + +- Collections now answer two questions separately: can the data be shown (`status`), and is it still kept up to date (`health`). Once a query has shown data, a network or backend failure never takes it off the screen. ([#2168](https://github.com/TanStack/db/pull/2168)) + + - `status` gains `crashed`: local storage broke (a closed IndexedDB connection, a failed SQLite persistence write or restore, a throwing sync function, or an invariant failure). Nothing recovers it in place, and `reload()` rejects. To recover, call `cleanup({ dependents: true })`, then start the Collection again (for example with `preload()`); it starts with no rows. It reaches every live query and Effect above it. `error` now means only "the first load failed". + - Every Collection has `collection.health`, a health node with `status`, `failureReason` (the adapter is still trying), `error` (the adapter stopped, or the first load failed), `reloading`, and `hasErrors`. A live query's `health` has one node per source alias, in query order, and keeps object identity while unchanged. `health:change` fires when it changes. `findProblems(health)` lists the nodes that have a problem, in query order. Each problem (`HealthProblem`) shows its newest failure in `message`, `cause`, and `at`, and lists every failure still open in `errors`, oldest first: each `reportError()` of the sync run until `reportHealthy()`, or each failed request for more data on a live query's node. One error value is one failure: reporting it again changes nothing, and `reportHealthy(error)` removes just that failure, so a query Collection whose failing Query recovers removes only that Query's failures. After a live query is ready, a failed request for more data (a page, a joined row, or a source reload's replay of its data) is an `error` on the query's own node; the source stays healthy. + - `collection.reload()` starts a new sync run and keeps the rows, status, and error on screen until the reload completes: when the new run's snapshot (`begin({ snapshot: true })`) or `truncate()` applies, or at its `markReady()`, which keeps the rows when no snapshot came. On a live query it reloads every Collection below it that shows an `error`, through nested live queries, then the query itself when its own node has an `error`, including a failed request for more data. It rejects on a `crashed` Collection. When the cleanup of the sync run that a reload retired rejects, the Collection crashes, also if the rejection comes later. A query in `error` stays there until its `reload()`. + - `cleanup()` rejects with `CollectionHasSubscribersError` while a live query with subscribers or an Effect reads the Collection. `cleanup({ dependents: true })` ends them first. `collection.subscribers` lists every dependent. + - A local-only Collection's `reload()` keeps its rows, including the user's writes: `initialData` loads only when the Collection starts, or starts again after cleanup. + - Persisted Collections have one status. With `initialRender: { strategy: 'network-first' }`, restored rows make the Collection `ready` once the deadline passes or the remote fails; an empty restore stays `loading`. Only a local storage failure crashes a persisted Collection: a failed remote subset request is a failure reason, which the next successful remote request clears. + + Breaking changes and migration: + + | Before | After | + | ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | + | hook `isError` | `isLoadingError` (first load failed) or `isCrashed` | + | — | `isSyncError`: `ready` and the health tree has a problem | + | `status: 'error'` after a crash | `status: 'crashed'` | + | adapter `markError(error)` | `fail(error)`. After readiness it sets the node's `error` and keeps the rows. Later calls throw. | + | `cleanup()` then `preload()` to recover | `reload()` | + | adapter `begin({ direct: true })` | `begin()`: every write applies on the rows the Collection holds, also during a reload | + | a reload's first commit replaced every row | `begin({ snapshot: true })` or `truncate()` in the new run replaces them | + | adapter `markReady()` after `fail()` threw | it does nothing | + | `cleanup()` while live queries or Effects read the Collection | rejects; unmount first or pass `{ dependents: true }` | + | `persistedStatus`, `isPersistedReady`, `persistedError`, `preloadForInitialRender()`, `PERSISTED_READINESS` | `status` and `health` | + | query-db `utils.lastError`, `utils.isError`, `utils.errorCount`, `utils.clearError()` | `collection.health`, `reload()` | + | infinite-query hooks' `error` (React, Preact, Vue, Svelte) | the root node's `error` in `health`; `fetchNextPage()` still resolves | + + Custom sync adapters: keep the run's readiness in your `sync` function. A reload calls `sync` again while `collection.status` still shows the previous run's status and the Collection still holds the previous run's rows. The new run's writes apply on those rows; it replaces every row only with a snapshot, a transaction begun with `begin({ snapshot: true })`, or with its own `truncate()`. The reload completes when that replacement applies, or at the new run's `markReady()`, which keeps the rows when no snapshot came. So send the new run's full state as a snapshot, or write against the kept rows: an `insert` of a key the Collection holds throws `DuplicateKeySyncError`, during a reload as at any time, so write `update` for a kept row, also from a replayed load after the reload completed. A reload keeps every row its new run does not rewrite: core never deletes kept rows on its own, so removing a row the backend deleted is the adapter's job, through a snapshot, a `delete`, or row ownership ([#2269](https://github.com/TanStack/db/issues/2269)). An on-demand `loadSubset` result is partial and is never a snapshot. A live query's own reload keeps its rows and its window until its new run's result is complete. `reportError()` and `reportHealthy()` throw before the first `markReady()` and after `fail()`. `markReady()` after the run is ready, or after its `fail()`, does nothing, so an adapter needs no flag of its own to guard it. A second `fail()` in the same sync run does nothing. A first-ready callback that throws while `sync` runs is reported uncaught, and the Collection stays ready. `reload()` called from inside `sync` or a `loadSubset` call rejects, unless a reload is already in flight, whose promise it returns. Calls after a crash do nothing. For an on-demand Collection, a reload aborts the retired run's `loadSubset` calls and asks the new run's `loadSubset` for the current demand as soon as `sync` returns. Honor the `signal`: when core aborts a load, that load is over for core, and its later resolve or reject decides nothing, holds no readiness, and no replay waits for it. A `loadSubset` that throws synchronously is a rejection: a failed load, which `requestSnapshot()` and `setWindow()` report without throwing. A truncate's or reload's replay decides every request error of the query's demand: its success clears them all, and its failure replaces them with the replay's failures. `useLiveSuspenseQuery` now throws an Error for a failed first load or a crash: the cause itself when it is an Error, otherwise an Error with the problem's message whose `cause` holds the reported value. + +### Patch Changes + +- Breaking: grouping, includes, and pooling are now opt-in. An app bundles an operator's code only when it imports the value for that operator. Joins do not change. A typical app is about 8 kB smaller after gzip. This app has a query collection, a filtered live query, and React `useLiveQuery`. ([#2232](https://github.com/TanStack/db/pull/2232)) + + - An include needs `include()`, `toArray()`, or `materialize()`. Change `issues: q.from(...)` in `select()` to `issues: include(q.from(...))`. A subquery in `select()` without one of these throws `IncludeOperatorRequiredError`. + - Aggregates such as `count()` need no change. A `groupBy` with no aggregate takes `groupKeys()`: `.groupBy(groupKeys(({ todo }) => todo.status))`. Without it, the query throws `GroupingOperatorRequiredError`. + - Hooks do not pool live queries by default. To share one partition among many equality-filtered queries, pass `pool: pooledLiveQueries` in the hook's query config: `useLiveQuery({ query, pool: pooledLiveQueries })`. Results do not change. Angular's `{ params, query }` form takes `pool` the same way. Any other `pool` value, such as the old boolean, throws `InvalidPoolOptionError`, also for a disabled query. For adapter authors: the `pool` option of `resolveLiveQueryValue` is now `pooledLiveQueries` or `false`, not a boolean. An adapter that reads `pool` from its own options checks it with `configPool` first. + - Code that builds query IR by hand must supply the operator code. `IR.Aggregate` takes `groupingCompiler` as its third argument. `IR.IncludesSubquery` takes `includesRuntime` as its fifth argument, before `parentFilters`. Import both from `@tanstack/db`. IR without them throws `GroupingOperatorRequiredError` or `IncludeOperatorRequiredError` when it compiles, unless another aggregate or `groupKeys()` in the query supplies the grouping compiler. Queries that the query builder builds need no change. + - Operator code is part of query identity. A hook that re-renders a grouped query without `groupKeys()` now throws `GroupingOperatorRequiredError`. Before, React and Preact kept the grouped query. + - Vue: when a changed query fails to compile, `useLiveQuery` no longer shows the rows of the previous query. + +- Updated dependencies [[`5649a07`](https://github.com/TanStack/db/commit/5649a07d468117891193c2e4cde9c7706308b1cd), [`7538afd`](https://github.com/TanStack/db/commit/7538afd2fa838340aaa63fca8b221154eb0b1572), [`a07cd73`](https://github.com/TanStack/db/commit/a07cd73739c1f135e658f38621de27f336004d11), [`6c1ca8f`](https://github.com/TanStack/db/commit/6c1ca8f8f9efe437d3cea61b5b9d0a086e2a5e34), [`30175be`](https://github.com/TanStack/db/commit/30175be483fa70799da579e2c407fb7ed9f2b435), [`4c39add`](https://github.com/TanStack/db/commit/4c39add293adb65b4f5537628849f97083a31531), [`1640de8`](https://github.com/TanStack/db/commit/1640de834bca0588f91432ea80a0f4569157e9dc), [`367ea7c`](https://github.com/TanStack/db/commit/367ea7c11e7dee5ca48ba524e55da2a6935d55fb), [`7d9ed02`](https://github.com/TanStack/db/commit/7d9ed02e085d11e871e4b6424d47e3c0d7073023), [`45eeef8`](https://github.com/TanStack/db/commit/45eeef81a7c64966473315558f46e8a53b00e462)]: + - @tanstack/db@0.16.0 + ## 0.3.12 ### Patch Changes diff --git a/packages/solid-db/package.json b/packages/solid-db/package.json index 2f860bf7cc..221ca04dc6 100644 --- a/packages/solid-db/package.json +++ b/packages/solid-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/solid-db", - "version": "0.3.12", + "version": "0.4.0", "description": "Solid integration for @tanstack/db", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/svelte-db/CHANGELOG.md b/packages/svelte-db/CHANGELOG.md index 4f822e9900..73f6b1ed18 100644 --- a/packages/svelte-db/CHANGELOG.md +++ b/packages/svelte-db/CHANGELOG.md @@ -1,5 +1,67 @@ # @tanstack/svelte-db +## 0.6.0 + +### Minor Changes + +- Collections now answer two questions separately: can the data be shown (`status`), and is it still kept up to date (`health`). Once a query has shown data, a network or backend failure never takes it off the screen. ([#2168](https://github.com/TanStack/db/pull/2168)) + + - `status` gains `crashed`: local storage broke (a closed IndexedDB connection, a failed SQLite persistence write or restore, a throwing sync function, or an invariant failure). Nothing recovers it in place, and `reload()` rejects. To recover, call `cleanup({ dependents: true })`, then start the Collection again (for example with `preload()`); it starts with no rows. It reaches every live query and Effect above it. `error` now means only "the first load failed". + - Every Collection has `collection.health`, a health node with `status`, `failureReason` (the adapter is still trying), `error` (the adapter stopped, or the first load failed), `reloading`, and `hasErrors`. A live query's `health` has one node per source alias, in query order, and keeps object identity while unchanged. `health:change` fires when it changes. `findProblems(health)` lists the nodes that have a problem, in query order. Each problem (`HealthProblem`) shows its newest failure in `message`, `cause`, and `at`, and lists every failure still open in `errors`, oldest first: each `reportError()` of the sync run until `reportHealthy()`, or each failed request for more data on a live query's node. One error value is one failure: reporting it again changes nothing, and `reportHealthy(error)` removes just that failure, so a query Collection whose failing Query recovers removes only that Query's failures. After a live query is ready, a failed request for more data (a page, a joined row, or a source reload's replay of its data) is an `error` on the query's own node; the source stays healthy. + - `collection.reload()` starts a new sync run and keeps the rows, status, and error on screen until the reload completes: when the new run's snapshot (`begin({ snapshot: true })`) or `truncate()` applies, or at its `markReady()`, which keeps the rows when no snapshot came. On a live query it reloads every Collection below it that shows an `error`, through nested live queries, then the query itself when its own node has an `error`, including a failed request for more data. It rejects on a `crashed` Collection. When the cleanup of the sync run that a reload retired rejects, the Collection crashes, also if the rejection comes later. A query in `error` stays there until its `reload()`. + - `cleanup()` rejects with `CollectionHasSubscribersError` while a live query with subscribers or an Effect reads the Collection. `cleanup({ dependents: true })` ends them first. `collection.subscribers` lists every dependent. + - A local-only Collection's `reload()` keeps its rows, including the user's writes: `initialData` loads only when the Collection starts, or starts again after cleanup. + - Persisted Collections have one status. With `initialRender: { strategy: 'network-first' }`, restored rows make the Collection `ready` once the deadline passes or the remote fails; an empty restore stays `loading`. Only a local storage failure crashes a persisted Collection: a failed remote subset request is a failure reason, which the next successful remote request clears. + + Breaking changes and migration: + + | Before | After | + | ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | + | hook `isError` | `isLoadingError` (first load failed) or `isCrashed` | + | — | `isSyncError`: `ready` and the health tree has a problem | + | `status: 'error'` after a crash | `status: 'crashed'` | + | adapter `markError(error)` | `fail(error)`. After readiness it sets the node's `error` and keeps the rows. Later calls throw. | + | `cleanup()` then `preload()` to recover | `reload()` | + | adapter `begin({ direct: true })` | `begin()`: every write applies on the rows the Collection holds, also during a reload | + | a reload's first commit replaced every row | `begin({ snapshot: true })` or `truncate()` in the new run replaces them | + | adapter `markReady()` after `fail()` threw | it does nothing | + | `cleanup()` while live queries or Effects read the Collection | rejects; unmount first or pass `{ dependents: true }` | + | `persistedStatus`, `isPersistedReady`, `persistedError`, `preloadForInitialRender()`, `PERSISTED_READINESS` | `status` and `health` | + | query-db `utils.lastError`, `utils.isError`, `utils.errorCount`, `utils.clearError()` | `collection.health`, `reload()` | + | infinite-query hooks' `error` (React, Preact, Vue, Svelte) | the root node's `error` in `health`; `fetchNextPage()` still resolves | + + Custom sync adapters: keep the run's readiness in your `sync` function. A reload calls `sync` again while `collection.status` still shows the previous run's status and the Collection still holds the previous run's rows. The new run's writes apply on those rows; it replaces every row only with a snapshot, a transaction begun with `begin({ snapshot: true })`, or with its own `truncate()`. The reload completes when that replacement applies, or at the new run's `markReady()`, which keeps the rows when no snapshot came. So send the new run's full state as a snapshot, or write against the kept rows: an `insert` of a key the Collection holds throws `DuplicateKeySyncError`, during a reload as at any time, so write `update` for a kept row, also from a replayed load after the reload completed. A reload keeps every row its new run does not rewrite: core never deletes kept rows on its own, so removing a row the backend deleted is the adapter's job, through a snapshot, a `delete`, or row ownership ([#2269](https://github.com/TanStack/db/issues/2269)). An on-demand `loadSubset` result is partial and is never a snapshot. A live query's own reload keeps its rows and its window until its new run's result is complete. `reportError()` and `reportHealthy()` throw before the first `markReady()` and after `fail()`. `markReady()` after the run is ready, or after its `fail()`, does nothing, so an adapter needs no flag of its own to guard it. A second `fail()` in the same sync run does nothing. A first-ready callback that throws while `sync` runs is reported uncaught, and the Collection stays ready. `reload()` called from inside `sync` or a `loadSubset` call rejects, unless a reload is already in flight, whose promise it returns. Calls after a crash do nothing. For an on-demand Collection, a reload aborts the retired run's `loadSubset` calls and asks the new run's `loadSubset` for the current demand as soon as `sync` returns. Honor the `signal`: when core aborts a load, that load is over for core, and its later resolve or reject decides nothing, holds no readiness, and no replay waits for it. A `loadSubset` that throws synchronously is a rejection: a failed load, which `requestSnapshot()` and `setWindow()` report without throwing. A truncate's or reload's replay decides every request error of the query's demand: its success clears them all, and its failure replaces them with the replay's failures. `useLiveSuspenseQuery` now throws an Error for a failed first load or a crash: the cause itself when it is an Error, otherwise an Error with the problem's message whose `cause` holds the reported value. + +- **Breaking (1.0):** a `useLiveQuery` backed by a `DbClient` matches queries by identity hash, so it now throws `UnhashableQueryIRError` for a query without a stable IR identity unless it supplies a nonempty `queryKey`. It previously compiled the query without a hydration key. While such a hook is mounted, a different source Collection under a Collection ID it already read throws `SourceCollectionIdReuseError`, and every result property then throws instead of returning the previous query's state. Queries without a `DbClient` are unchanged. ([#2235](https://github.com/TanStack/db/pull/2235)) + + A rejected client-backed query now releases every sync start it deferred, so `startSyncImmediate()` or another hook can still start that source. A setup that throws after the hook returns releases them when its live query is reclaimed. `useLiveInfiniteQuery` now uses the shared window binding from `@tanstack/db`. + +### Patch Changes + +- Breaking: grouping, includes, and pooling are now opt-in. An app bundles an operator's code only when it imports the value for that operator. Joins do not change. A typical app is about 8 kB smaller after gzip. This app has a query collection, a filtered live query, and React `useLiveQuery`. ([#2232](https://github.com/TanStack/db/pull/2232)) + + - An include needs `include()`, `toArray()`, or `materialize()`. Change `issues: q.from(...)` in `select()` to `issues: include(q.from(...))`. A subquery in `select()` without one of these throws `IncludeOperatorRequiredError`. + - Aggregates such as `count()` need no change. A `groupBy` with no aggregate takes `groupKeys()`: `.groupBy(groupKeys(({ todo }) => todo.status))`. Without it, the query throws `GroupingOperatorRequiredError`. + - Hooks do not pool live queries by default. To share one partition among many equality-filtered queries, pass `pool: pooledLiveQueries` in the hook's query config: `useLiveQuery({ query, pool: pooledLiveQueries })`. Results do not change. Angular's `{ params, query }` form takes `pool` the same way. Any other `pool` value, such as the old boolean, throws `InvalidPoolOptionError`, also for a disabled query. For adapter authors: the `pool` option of `resolveLiveQueryValue` is now `pooledLiveQueries` or `false`, not a boolean. An adapter that reads `pool` from its own options checks it with `configPool` first. + - Code that builds query IR by hand must supply the operator code. `IR.Aggregate` takes `groupingCompiler` as its third argument. `IR.IncludesSubquery` takes `includesRuntime` as its fifth argument, before `parentFilters`. Import both from `@tanstack/db`. IR without them throws `GroupingOperatorRequiredError` or `IncludeOperatorRequiredError` when it compiles, unless another aggregate or `groupKeys()` in the query supplies the grouping compiler. Queries that the query builder builds need no change. + - Operator code is part of query identity. A hook that re-renders a grouped query without `groupKeys()` now throws `GroupingOperatorRequiredError`. Before, React and Preact kept the grouped query. + - Vue: when a changed query fails to compile, `useLiveQuery` no longer shows the rows of the previous query. + +- Start the required Collection sync run when an ordinary subscriber follows an abandoned suspended render. Deferred acquisition still starts no idle source sync run or acquisition attempt. ([#2229](https://github.com/TanStack/db/pull/2229)) + + Ordinary subscription admission and deferred acquisition resumption throw during cleanup, including automatic GC. Effects created then return disposed handles. Failed setup releases partial ownership, and throwing callbacks cannot prevent healthy observers receiving cleanup status. Subscribers surviving cleanup retain demand for an explicit restart. + + SSR captures each reader's rows and Collection status. Hydration preserves that view through browser preloads, runtime errors, sibling commits, and delayed streamed records. Missing records reject when transport ends. Known pending results survive normal record-stream close. A new stream after client cleanup starts independently of prior failures. + + Empty loading SSR needs no preload. Rendering rows or readiness requires matching query preparation or explicit source Collection preparation. Streaming Suspense prepares its result. Unsupported preparation throws with guidance. Prepared non-streaming Suspense remains supported. The DbClient query hash includes an infinite query's initial limit and offset. + + Apps without `DbClient` exclude its SSR implementation. Its API stays unchanged. + + Svelte roots initialized with `hydrate()` must set `hydrating` on `DbProvider`. Nested providers inherit it. Each provider clears initial hydration after mount so new children read live rows. + +- Updated dependencies [[`5649a07`](https://github.com/TanStack/db/commit/5649a07d468117891193c2e4cde9c7706308b1cd), [`7538afd`](https://github.com/TanStack/db/commit/7538afd2fa838340aaa63fca8b221154eb0b1572), [`a07cd73`](https://github.com/TanStack/db/commit/a07cd73739c1f135e658f38621de27f336004d11), [`6c1ca8f`](https://github.com/TanStack/db/commit/6c1ca8f8f9efe437d3cea61b5b9d0a086e2a5e34), [`30175be`](https://github.com/TanStack/db/commit/30175be483fa70799da579e2c407fb7ed9f2b435), [`4c39add`](https://github.com/TanStack/db/commit/4c39add293adb65b4f5537628849f97083a31531), [`1640de8`](https://github.com/TanStack/db/commit/1640de834bca0588f91432ea80a0f4569157e9dc), [`367ea7c`](https://github.com/TanStack/db/commit/367ea7c11e7dee5ca48ba524e55da2a6935d55fb), [`7d9ed02`](https://github.com/TanStack/db/commit/7d9ed02e085d11e871e4b6424d47e3c0d7073023), [`45eeef8`](https://github.com/TanStack/db/commit/45eeef81a7c64966473315558f46e8a53b00e462)]: + - @tanstack/db@0.16.0 + ## 0.5.12 ### Patch Changes diff --git a/packages/svelte-db/package.json b/packages/svelte-db/package.json index 661171de0b..3a679133ca 100644 --- a/packages/svelte-db/package.json +++ b/packages/svelte-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/svelte-db", - "version": "0.5.12", + "version": "0.6.0", "description": "Svelte integration for @tanstack/db", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/tauri-db-sqlite-persistence/CHANGELOG.md b/packages/tauri-db-sqlite-persistence/CHANGELOG.md index 4d7619529a..571f962cb0 100644 --- a/packages/tauri-db-sqlite-persistence/CHANGELOG.md +++ b/packages/tauri-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/tauri-db-sqlite-persistence +## 0.2.38 + +### Patch Changes + +- Updated dependencies [[`45c3329`](https://github.com/TanStack/db/commit/45c33292b2c4f09a63f856a829c1e15ca2b470aa), [`205ab71`](https://github.com/TanStack/db/commit/205ab7158d81680c3cc4f06cd9ebe62b30911a5a), [`6bf0e86`](https://github.com/TanStack/db/commit/6bf0e86dbc12e903ef28bb6711968ad06b5d5036), [`4c39add`](https://github.com/TanStack/db/commit/4c39add293adb65b4f5537628849f97083a31531), [`4c39add`](https://github.com/TanStack/db/commit/4c39add293adb65b4f5537628849f97083a31531), [`205ab71`](https://github.com/TanStack/db/commit/205ab7158d81680c3cc4f06cd9ebe62b30911a5a), [`7d9ed02`](https://github.com/TanStack/db/commit/7d9ed02e085d11e871e4b6424d47e3c0d7073023), [`b052e91`](https://github.com/TanStack/db/commit/b052e91c79201ee376771f99503566e736e3d0dd), [`45eeef8`](https://github.com/TanStack/db/commit/45eeef81a7c64966473315558f46e8a53b00e462)]: + - @tanstack/db-sqlite-persistence-core@0.5.0 + ## 0.2.37 ### Patch Changes diff --git a/packages/tauri-db-sqlite-persistence/e2e/app/CHANGELOG.md b/packages/tauri-db-sqlite-persistence/e2e/app/CHANGELOG.md index 5b919bdfd4..d94d32d5d0 100644 --- a/packages/tauri-db-sqlite-persistence/e2e/app/CHANGELOG.md +++ b/packages/tauri-db-sqlite-persistence/e2e/app/CHANGELOG.md @@ -1,5 +1,13 @@ # @tanstack/tauri-db-sqlite-persistence-e2e-app +## 0.0.50 + +### Patch Changes + +- Updated dependencies [[`5649a07`](https://github.com/TanStack/db/commit/5649a07d468117891193c2e4cde9c7706308b1cd), [`7538afd`](https://github.com/TanStack/db/commit/7538afd2fa838340aaa63fca8b221154eb0b1572), [`a07cd73`](https://github.com/TanStack/db/commit/a07cd73739c1f135e658f38621de27f336004d11), [`6c1ca8f`](https://github.com/TanStack/db/commit/6c1ca8f8f9efe437d3cea61b5b9d0a086e2a5e34), [`30175be`](https://github.com/TanStack/db/commit/30175be483fa70799da579e2c407fb7ed9f2b435), [`4c39add`](https://github.com/TanStack/db/commit/4c39add293adb65b4f5537628849f97083a31531), [`1640de8`](https://github.com/TanStack/db/commit/1640de834bca0588f91432ea80a0f4569157e9dc), [`367ea7c`](https://github.com/TanStack/db/commit/367ea7c11e7dee5ca48ba524e55da2a6935d55fb), [`7d9ed02`](https://github.com/TanStack/db/commit/7d9ed02e085d11e871e4b6424d47e3c0d7073023), [`45eeef8`](https://github.com/TanStack/db/commit/45eeef81a7c64966473315558f46e8a53b00e462)]: + - @tanstack/db@0.16.0 + - @tanstack/tauri-db-sqlite-persistence@0.2.38 + ## 0.0.49 ### Patch Changes diff --git a/packages/tauri-db-sqlite-persistence/e2e/app/package.json b/packages/tauri-db-sqlite-persistence/e2e/app/package.json index 0a4b6a7f51..7412131850 100644 --- a/packages/tauri-db-sqlite-persistence/e2e/app/package.json +++ b/packages/tauri-db-sqlite-persistence/e2e/app/package.json @@ -1,7 +1,7 @@ { "name": "@tanstack/tauri-db-sqlite-persistence-e2e-app", "private": true, - "version": "0.0.49", + "version": "0.0.50", "type": "module", "scripts": { "build": "vite build", diff --git a/packages/tauri-db-sqlite-persistence/package.json b/packages/tauri-db-sqlite-persistence/package.json index 6374e98e0f..f8a4d27f9d 100644 --- a/packages/tauri-db-sqlite-persistence/package.json +++ b/packages/tauri-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/tauri-db-sqlite-persistence", - "version": "0.2.37", + "version": "0.2.38", "description": "Tauri SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/trailbase-db-collection/CHANGELOG.md b/packages/trailbase-db-collection/CHANGELOG.md index 7ee6eeb41a..06fe08b730 100644 --- a/packages/trailbase-db-collection/CHANGELOG.md +++ b/packages/trailbase-db-collection/CHANGELOG.md @@ -1,5 +1,42 @@ # @tanstack/trailbase-db-collection +## 0.2.0 + +### Minor Changes + +- Collections now answer two questions separately: can the data be shown (`status`), and is it still kept up to date (`health`). Once a query has shown data, a network or backend failure never takes it off the screen. ([#2168](https://github.com/TanStack/db/pull/2168)) + + - `status` gains `crashed`: local storage broke (a closed IndexedDB connection, a failed SQLite persistence write or restore, a throwing sync function, or an invariant failure). Nothing recovers it in place, and `reload()` rejects. To recover, call `cleanup({ dependents: true })`, then start the Collection again (for example with `preload()`); it starts with no rows. It reaches every live query and Effect above it. `error` now means only "the first load failed". + - Every Collection has `collection.health`, a health node with `status`, `failureReason` (the adapter is still trying), `error` (the adapter stopped, or the first load failed), `reloading`, and `hasErrors`. A live query's `health` has one node per source alias, in query order, and keeps object identity while unchanged. `health:change` fires when it changes. `findProblems(health)` lists the nodes that have a problem, in query order. Each problem (`HealthProblem`) shows its newest failure in `message`, `cause`, and `at`, and lists every failure still open in `errors`, oldest first: each `reportError()` of the sync run until `reportHealthy()`, or each failed request for more data on a live query's node. One error value is one failure: reporting it again changes nothing, and `reportHealthy(error)` removes just that failure, so a query Collection whose failing Query recovers removes only that Query's failures. After a live query is ready, a failed request for more data (a page, a joined row, or a source reload's replay of its data) is an `error` on the query's own node; the source stays healthy. + - `collection.reload()` starts a new sync run and keeps the rows, status, and error on screen until the reload completes: when the new run's snapshot (`begin({ snapshot: true })`) or `truncate()` applies, or at its `markReady()`, which keeps the rows when no snapshot came. On a live query it reloads every Collection below it that shows an `error`, through nested live queries, then the query itself when its own node has an `error`, including a failed request for more data. It rejects on a `crashed` Collection. When the cleanup of the sync run that a reload retired rejects, the Collection crashes, also if the rejection comes later. A query in `error` stays there until its `reload()`. + - `cleanup()` rejects with `CollectionHasSubscribersError` while a live query with subscribers or an Effect reads the Collection. `cleanup({ dependents: true })` ends them first. `collection.subscribers` lists every dependent. + - A local-only Collection's `reload()` keeps its rows, including the user's writes: `initialData` loads only when the Collection starts, or starts again after cleanup. + - Persisted Collections have one status. With `initialRender: { strategy: 'network-first' }`, restored rows make the Collection `ready` once the deadline passes or the remote fails; an empty restore stays `loading`. Only a local storage failure crashes a persisted Collection: a failed remote subset request is a failure reason, which the next successful remote request clears. + + Breaking changes and migration: + + | Before | After | + | ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | + | hook `isError` | `isLoadingError` (first load failed) or `isCrashed` | + | — | `isSyncError`: `ready` and the health tree has a problem | + | `status: 'error'` after a crash | `status: 'crashed'` | + | adapter `markError(error)` | `fail(error)`. After readiness it sets the node's `error` and keeps the rows. Later calls throw. | + | `cleanup()` then `preload()` to recover | `reload()` | + | adapter `begin({ direct: true })` | `begin()`: every write applies on the rows the Collection holds, also during a reload | + | a reload's first commit replaced every row | `begin({ snapshot: true })` or `truncate()` in the new run replaces them | + | adapter `markReady()` after `fail()` threw | it does nothing | + | `cleanup()` while live queries or Effects read the Collection | rejects; unmount first or pass `{ dependents: true }` | + | `persistedStatus`, `isPersistedReady`, `persistedError`, `preloadForInitialRender()`, `PERSISTED_READINESS` | `status` and `health` | + | query-db `utils.lastError`, `utils.isError`, `utils.errorCount`, `utils.clearError()` | `collection.health`, `reload()` | + | infinite-query hooks' `error` (React, Preact, Vue, Svelte) | the root node's `error` in `health`; `fetchNextPage()` still resolves | + + Custom sync adapters: keep the run's readiness in your `sync` function. A reload calls `sync` again while `collection.status` still shows the previous run's status and the Collection still holds the previous run's rows. The new run's writes apply on those rows; it replaces every row only with a snapshot, a transaction begun with `begin({ snapshot: true })`, or with its own `truncate()`. The reload completes when that replacement applies, or at the new run's `markReady()`, which keeps the rows when no snapshot came. So send the new run's full state as a snapshot, or write against the kept rows: an `insert` of a key the Collection holds throws `DuplicateKeySyncError`, during a reload as at any time, so write `update` for a kept row, also from a replayed load after the reload completed. A reload keeps every row its new run does not rewrite: core never deletes kept rows on its own, so removing a row the backend deleted is the adapter's job, through a snapshot, a `delete`, or row ownership ([#2269](https://github.com/TanStack/db/issues/2269)). An on-demand `loadSubset` result is partial and is never a snapshot. A live query's own reload keeps its rows and its window until its new run's result is complete. `reportError()` and `reportHealthy()` throw before the first `markReady()` and after `fail()`. `markReady()` after the run is ready, or after its `fail()`, does nothing, so an adapter needs no flag of its own to guard it. A second `fail()` in the same sync run does nothing. A first-ready callback that throws while `sync` runs is reported uncaught, and the Collection stays ready. `reload()` called from inside `sync` or a `loadSubset` call rejects, unless a reload is already in flight, whose promise it returns. Calls after a crash do nothing. For an on-demand Collection, a reload aborts the retired run's `loadSubset` calls and asks the new run's `loadSubset` for the current demand as soon as `sync` returns. Honor the `signal`: when core aborts a load, that load is over for core, and its later resolve or reject decides nothing, holds no readiness, and no replay waits for it. A `loadSubset` that throws synchronously is a rejection: a failed load, which `requestSnapshot()` and `setWindow()` report without throwing. A truncate's or reload's replay decides every request error of the query's demand: its success clears them all, and its failure replaces them with the replay's failures. `useLiveSuspenseQuery` now throws an Error for a failed first load or a crash: the cause itself when it is an Error, otherwise an Error with the problem's message whose `cause` holds the reported value. + +### Patch Changes + +- Updated dependencies [[`5649a07`](https://github.com/TanStack/db/commit/5649a07d468117891193c2e4cde9c7706308b1cd), [`7538afd`](https://github.com/TanStack/db/commit/7538afd2fa838340aaa63fca8b221154eb0b1572), [`a07cd73`](https://github.com/TanStack/db/commit/a07cd73739c1f135e658f38621de27f336004d11), [`6c1ca8f`](https://github.com/TanStack/db/commit/6c1ca8f8f9efe437d3cea61b5b9d0a086e2a5e34), [`30175be`](https://github.com/TanStack/db/commit/30175be483fa70799da579e2c407fb7ed9f2b435), [`4c39add`](https://github.com/TanStack/db/commit/4c39add293adb65b4f5537628849f97083a31531), [`1640de8`](https://github.com/TanStack/db/commit/1640de834bca0588f91432ea80a0f4569157e9dc), [`367ea7c`](https://github.com/TanStack/db/commit/367ea7c11e7dee5ca48ba524e55da2a6935d55fb), [`7d9ed02`](https://github.com/TanStack/db/commit/7d9ed02e085d11e871e4b6424d47e3c0d7073023), [`45eeef8`](https://github.com/TanStack/db/commit/45eeef81a7c64966473315558f46e8a53b00e462)]: + - @tanstack/db@0.16.0 + ## 0.1.123 ### Patch Changes diff --git a/packages/trailbase-db-collection/package.json b/packages/trailbase-db-collection/package.json index 0af52d40ee..843c552a96 100644 --- a/packages/trailbase-db-collection/package.json +++ b/packages/trailbase-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/trailbase-db-collection", - "version": "0.1.123", + "version": "0.2.0", "description": "TrailBase collection for TanStack DB", "author": "Sebastian Jeltsch", "license": "MIT", diff --git a/packages/vue-db/CHANGELOG.md b/packages/vue-db/CHANGELOG.md index 74fb3918b3..ac694e12b2 100644 --- a/packages/vue-db/CHANGELOG.md +++ b/packages/vue-db/CHANGELOG.md @@ -1,5 +1,53 @@ # @tanstack/vue-db +## 0.4.0 + +### Minor Changes + +- Collections now answer two questions separately: can the data be shown (`status`), and is it still kept up to date (`health`). Once a query has shown data, a network or backend failure never takes it off the screen. ([#2168](https://github.com/TanStack/db/pull/2168)) + + - `status` gains `crashed`: local storage broke (a closed IndexedDB connection, a failed SQLite persistence write or restore, a throwing sync function, or an invariant failure). Nothing recovers it in place, and `reload()` rejects. To recover, call `cleanup({ dependents: true })`, then start the Collection again (for example with `preload()`); it starts with no rows. It reaches every live query and Effect above it. `error` now means only "the first load failed". + - Every Collection has `collection.health`, a health node with `status`, `failureReason` (the adapter is still trying), `error` (the adapter stopped, or the first load failed), `reloading`, and `hasErrors`. A live query's `health` has one node per source alias, in query order, and keeps object identity while unchanged. `health:change` fires when it changes. `findProblems(health)` lists the nodes that have a problem, in query order. Each problem (`HealthProblem`) shows its newest failure in `message`, `cause`, and `at`, and lists every failure still open in `errors`, oldest first: each `reportError()` of the sync run until `reportHealthy()`, or each failed request for more data on a live query's node. One error value is one failure: reporting it again changes nothing, and `reportHealthy(error)` removes just that failure, so a query Collection whose failing Query recovers removes only that Query's failures. After a live query is ready, a failed request for more data (a page, a joined row, or a source reload's replay of its data) is an `error` on the query's own node; the source stays healthy. + - `collection.reload()` starts a new sync run and keeps the rows, status, and error on screen until the reload completes: when the new run's snapshot (`begin({ snapshot: true })`) or `truncate()` applies, or at its `markReady()`, which keeps the rows when no snapshot came. On a live query it reloads every Collection below it that shows an `error`, through nested live queries, then the query itself when its own node has an `error`, including a failed request for more data. It rejects on a `crashed` Collection. When the cleanup of the sync run that a reload retired rejects, the Collection crashes, also if the rejection comes later. A query in `error` stays there until its `reload()`. + - `cleanup()` rejects with `CollectionHasSubscribersError` while a live query with subscribers or an Effect reads the Collection. `cleanup({ dependents: true })` ends them first. `collection.subscribers` lists every dependent. + - A local-only Collection's `reload()` keeps its rows, including the user's writes: `initialData` loads only when the Collection starts, or starts again after cleanup. + - Persisted Collections have one status. With `initialRender: { strategy: 'network-first' }`, restored rows make the Collection `ready` once the deadline passes or the remote fails; an empty restore stays `loading`. Only a local storage failure crashes a persisted Collection: a failed remote subset request is a failure reason, which the next successful remote request clears. + + Breaking changes and migration: + + | Before | After | + | ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | + | hook `isError` | `isLoadingError` (first load failed) or `isCrashed` | + | — | `isSyncError`: `ready` and the health tree has a problem | + | `status: 'error'` after a crash | `status: 'crashed'` | + | adapter `markError(error)` | `fail(error)`. After readiness it sets the node's `error` and keeps the rows. Later calls throw. | + | `cleanup()` then `preload()` to recover | `reload()` | + | adapter `begin({ direct: true })` | `begin()`: every write applies on the rows the Collection holds, also during a reload | + | a reload's first commit replaced every row | `begin({ snapshot: true })` or `truncate()` in the new run replaces them | + | adapter `markReady()` after `fail()` threw | it does nothing | + | `cleanup()` while live queries or Effects read the Collection | rejects; unmount first or pass `{ dependents: true }` | + | `persistedStatus`, `isPersistedReady`, `persistedError`, `preloadForInitialRender()`, `PERSISTED_READINESS` | `status` and `health` | + | query-db `utils.lastError`, `utils.isError`, `utils.errorCount`, `utils.clearError()` | `collection.health`, `reload()` | + | infinite-query hooks' `error` (React, Preact, Vue, Svelte) | the root node's `error` in `health`; `fetchNextPage()` still resolves | + + Custom sync adapters: keep the run's readiness in your `sync` function. A reload calls `sync` again while `collection.status` still shows the previous run's status and the Collection still holds the previous run's rows. The new run's writes apply on those rows; it replaces every row only with a snapshot, a transaction begun with `begin({ snapshot: true })`, or with its own `truncate()`. The reload completes when that replacement applies, or at the new run's `markReady()`, which keeps the rows when no snapshot came. So send the new run's full state as a snapshot, or write against the kept rows: an `insert` of a key the Collection holds throws `DuplicateKeySyncError`, during a reload as at any time, so write `update` for a kept row, also from a replayed load after the reload completed. A reload keeps every row its new run does not rewrite: core never deletes kept rows on its own, so removing a row the backend deleted is the adapter's job, through a snapshot, a `delete`, or row ownership ([#2269](https://github.com/TanStack/db/issues/2269)). An on-demand `loadSubset` result is partial and is never a snapshot. A live query's own reload keeps its rows and its window until its new run's result is complete. `reportError()` and `reportHealthy()` throw before the first `markReady()` and after `fail()`. `markReady()` after the run is ready, or after its `fail()`, does nothing, so an adapter needs no flag of its own to guard it. A second `fail()` in the same sync run does nothing. A first-ready callback that throws while `sync` runs is reported uncaught, and the Collection stays ready. `reload()` called from inside `sync` or a `loadSubset` call rejects, unless a reload is already in flight, whose promise it returns. Calls after a crash do nothing. For an on-demand Collection, a reload aborts the retired run's `loadSubset` calls and asks the new run's `loadSubset` for the current demand as soon as `sync` returns. Honor the `signal`: when core aborts a load, that load is over for core, and its later resolve or reject decides nothing, holds no readiness, and no replay waits for it. A `loadSubset` that throws synchronously is a rejection: a failed load, which `requestSnapshot()` and `setWindow()` report without throwing. A truncate's or reload's replay decides every request error of the query's demand: its success clears them all, and its failure replaces them with the replay's failures. `useLiveSuspenseQuery` now throws an Error for a failed first load or a crash: the cause itself when it is an Error, otherwise an Error with the problem's message whose `cause` holds the reported value. + +### Patch Changes + +- Breaking: grouping, includes, and pooling are now opt-in. An app bundles an operator's code only when it imports the value for that operator. Joins do not change. A typical app is about 8 kB smaller after gzip. This app has a query collection, a filtered live query, and React `useLiveQuery`. ([#2232](https://github.com/TanStack/db/pull/2232)) + + - An include needs `include()`, `toArray()`, or `materialize()`. Change `issues: q.from(...)` in `select()` to `issues: include(q.from(...))`. A subquery in `select()` without one of these throws `IncludeOperatorRequiredError`. + - Aggregates such as `count()` need no change. A `groupBy` with no aggregate takes `groupKeys()`: `.groupBy(groupKeys(({ todo }) => todo.status))`. Without it, the query throws `GroupingOperatorRequiredError`. + - Hooks do not pool live queries by default. To share one partition among many equality-filtered queries, pass `pool: pooledLiveQueries` in the hook's query config: `useLiveQuery({ query, pool: pooledLiveQueries })`. Results do not change. Angular's `{ params, query }` form takes `pool` the same way. Any other `pool` value, such as the old boolean, throws `InvalidPoolOptionError`, also for a disabled query. For adapter authors: the `pool` option of `resolveLiveQueryValue` is now `pooledLiveQueries` or `false`, not a boolean. An adapter that reads `pool` from its own options checks it with `configPool` first. + - Code that builds query IR by hand must supply the operator code. `IR.Aggregate` takes `groupingCompiler` as its third argument. `IR.IncludesSubquery` takes `includesRuntime` as its fifth argument, before `parentFilters`. Import both from `@tanstack/db`. IR without them throws `GroupingOperatorRequiredError` or `IncludeOperatorRequiredError` when it compiles, unless another aggregate or `groupKeys()` in the query supplies the grouping compiler. Queries that the query builder builds need no change. + - Operator code is part of query identity. A hook that re-renders a grouped query without `groupKeys()` now throws `GroupingOperatorRequiredError`. Before, React and Preact kept the grouped query. + - Vue: when a changed query fails to compile, `useLiveQuery` no longer shows the rows of the previous query. + +- `useLiveInfiniteQuery` now uses the shared window binding from `@tanstack/db`. A window collection it creates starts syncing when the query compiles, as in the other adapters, and an update that changes nothing keeps the current controller. ([#2235](https://github.com/TanStack/db/pull/2235)) + +- Updated dependencies [[`5649a07`](https://github.com/TanStack/db/commit/5649a07d468117891193c2e4cde9c7706308b1cd), [`7538afd`](https://github.com/TanStack/db/commit/7538afd2fa838340aaa63fca8b221154eb0b1572), [`a07cd73`](https://github.com/TanStack/db/commit/a07cd73739c1f135e658f38621de27f336004d11), [`6c1ca8f`](https://github.com/TanStack/db/commit/6c1ca8f8f9efe437d3cea61b5b9d0a086e2a5e34), [`30175be`](https://github.com/TanStack/db/commit/30175be483fa70799da579e2c407fb7ed9f2b435), [`4c39add`](https://github.com/TanStack/db/commit/4c39add293adb65b4f5537628849f97083a31531), [`1640de8`](https://github.com/TanStack/db/commit/1640de834bca0588f91432ea80a0f4569157e9dc), [`367ea7c`](https://github.com/TanStack/db/commit/367ea7c11e7dee5ca48ba524e55da2a6935d55fb), [`7d9ed02`](https://github.com/TanStack/db/commit/7d9ed02e085d11e871e4b6424d47e3c0d7073023), [`45eeef8`](https://github.com/TanStack/db/commit/45eeef81a7c64966473315558f46e8a53b00e462)]: + - @tanstack/db@0.16.0 + ## 0.3.12 ### Patch Changes diff --git a/packages/vue-db/package.json b/packages/vue-db/package.json index a6ec622aa0..d5e6b7942a 100644 --- a/packages/vue-db/package.json +++ b/packages/vue-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/vue-db", - "version": "0.3.12", + "version": "0.4.0", "description": "Vue integration for @tanstack/db", "author": "Kyle Mathews", "license": "MIT", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4cfa0e4632..3cf99ccc62 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -150,10 +150,10 @@ importers: specifier: ^20.3.34 version: 20.3.34(@angular/common@20.3.34(@angular/core@20.3.34(@angular/compiler@20.3.34)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.34(@angular/compiler@20.3.34)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.34(@angular/common@20.3.34(@angular/core@20.3.34(@angular/compiler@20.3.34)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.34(@angular/compiler@20.3.34)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) '@tanstack/angular-db': - specifier: ^0.2.12 + specifier: ^0.3.0 version: link:../../../packages/angular-db '@tanstack/db': - specifier: ^0.15.2 + specifier: ^0.16.0 version: link:../../../packages/db rxjs: specifier: ^7.8.2 @@ -190,19 +190,19 @@ importers: examples/electron/offline-first: dependencies: '@tanstack/electron-db-sqlite-persistence': - specifier: ^0.2.13 + specifier: ^0.2.14 version: link:../../../packages/electron-db-sqlite-persistence '@tanstack/node-db-sqlite-persistence': - specifier: ^0.2.37 + specifier: ^0.2.38 version: link:../../../packages/node-db-sqlite-persistence '@tanstack/offline-transactions': - specifier: ^1.1.4 + specifier: ^1.2.0 version: link:../../../packages/offline-transactions '@tanstack/query-db-collection': - specifier: ^1.4.8 + specifier: ^1.5.0 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^0.5.12 + specifier: ^0.6.0 version: link:../../../packages/react-db '@tanstack/react-query': specifier: ^5.90.20 @@ -281,19 +281,19 @@ importers: specifier: 11.4.1 version: 11.4.1(react-native@0.79.6(@babel/core@7.29.7)(@types/react@19.2.13)(react@19.0.0)) '@tanstack/db': - specifier: ^0.15.2 + specifier: ^0.16.0 version: link:../../../packages/db '@tanstack/offline-transactions': - specifier: ^1.1.4 + specifier: ^1.2.0 version: link:../../../packages/offline-transactions '@tanstack/query-db-collection': - specifier: ^1.4.8 + specifier: ^1.5.0 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^0.5.12 + specifier: ^0.6.0 version: link:../../../packages/react-db '@tanstack/react-native-db-sqlite-persistence': - specifier: ^0.2.37 + specifier: ^0.2.38 version: link:../../../packages/react-native-db-sqlite-persistence '@tanstack/react-query': specifier: ^5.90.20 @@ -378,19 +378,19 @@ importers: specifier: 11.4.1 version: 11.4.1(react-native@0.79.6(@babel/core@7.29.7)(@types/react@19.2.13)(react@19.0.0)) '@tanstack/db': - specifier: ^0.15.2 + specifier: ^0.16.0 version: link:../../../packages/db '@tanstack/electric-db-collection': - specifier: ^0.6.3 + specifier: ^0.7.0 version: link:../../../packages/electric-db-collection '@tanstack/offline-transactions': - specifier: ^1.1.4 + specifier: ^1.2.0 version: link:../../../packages/offline-transactions '@tanstack/react-db': - specifier: ^0.5.12 + specifier: ^0.6.0 version: link:../../../packages/react-db '@tanstack/react-native-db-sqlite-persistence': - specifier: ^0.2.37 + specifier: ^0.2.38 version: link:../../../packages/react-native-db-sqlite-persistence '@tanstack/react-query': specifier: ^5.90.20 @@ -466,10 +466,10 @@ importers: specifier: ^5.90.20 version: 5.90.20 '@tanstack/query-db-collection': - specifier: ^1.4.8 + specifier: ^1.5.0 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^0.5.12 + specifier: ^0.6.0 version: link:../../../packages/react-db react: specifier: ^19.2.4 @@ -518,10 +518,10 @@ importers: examples/react/next-ssr-e2e: dependencies: '@tanstack/db': - specifier: ^0.15.2 + specifier: ^0.16.0 version: link:../../../packages/db '@tanstack/react-db': - specifier: ^0.5.12 + specifier: ^0.6.0 version: link:../../../packages/react-db next: specifier: ^16.3.1 @@ -552,19 +552,19 @@ importers: examples/react/offline-transactions: dependencies: '@tanstack/browser-db-sqlite-persistence': - specifier: ^0.2.37 + specifier: ^0.2.38 version: link:../../../packages/browser-db-sqlite-persistence '@tanstack/db': - specifier: ^0.15.2 + specifier: ^0.16.0 version: link:../../../packages/db '@tanstack/offline-transactions': - specifier: ^1.1.4 + specifier: ^1.2.0 version: link:../../../packages/offline-transactions '@tanstack/query-db-collection': - specifier: ^1.4.8 + specifier: ^1.5.0 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^0.5.12 + specifier: ^0.6.0 version: link:../../../packages/react-db '@tanstack/react-query': specifier: ^5.90.20 @@ -622,10 +622,10 @@ importers: examples/react/paced-mutations-demo: dependencies: '@tanstack/db': - specifier: ^0.15.2 + specifier: ^0.16.0 version: link:../../../packages/db '@tanstack/react-db': - specifier: ^0.5.12 + specifier: ^0.6.0 version: link:../../../packages/react-db mitt: specifier: ^3.0.1 @@ -662,10 +662,10 @@ importers: specifier: ^5.90.20 version: 5.90.20 '@tanstack/query-db-collection': - specifier: ^1.4.8 + specifier: ^1.5.0 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^0.5.12 + specifier: ^0.6.0 version: link:../../../packages/react-db '@tanstack/react-router': specifier: ^1.170.41 @@ -795,13 +795,13 @@ importers: examples/react/start-ssr-e2e: dependencies: '@tanstack/react-db': - specifier: ^0.5.12 + specifier: ^0.6.0 version: link:../../../packages/react-db '@tanstack/react-router': specifier: ^1.170.41 version: 1.170.41(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@tanstack/react-router-with-db': - specifier: ^0.1.0 + specifier: ^0.1.1 version: link:../../../packages/react-router-with-db '@tanstack/react-start': specifier: ^1.168.60 @@ -841,16 +841,16 @@ importers: examples/react/todo: dependencies: '@tanstack/electric-db-collection': - specifier: ^0.6.3 + specifier: ^0.7.0 version: link:../../../packages/electric-db-collection '@tanstack/query-core': specifier: ^5.90.20 version: 5.90.20 '@tanstack/query-db-collection': - specifier: ^1.4.8 + specifier: ^1.5.0 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^0.5.12 + specifier: ^0.6.0 version: link:../../../packages/react-db '@tanstack/react-router': specifier: ^1.170.41 @@ -859,7 +859,7 @@ importers: specifier: ^1.168.60 version: 1.168.60(esbuild@0.28.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rollup@4.64.12)(vite-plugin-solid@2.11.10(@testing-library/jest-dom@6.9.1)(solid-js@1.9.11)(vite@7.3.7(@types/node@25.2.2)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.90.0)(terser@5.51.2)(tsx@4.21.0)(yaml@2.8.1)))(vite@7.3.7(@types/node@25.2.2)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.90.0)(terser@5.51.2)(tsx@4.21.0)(yaml@2.8.1))(webpack@5.112.0(esbuild@0.28.1)(lightningcss@1.33.0)) '@tanstack/trailbase-db-collection': - specifier: ^0.1.123 + specifier: ^0.2.0 version: link:../../../packages/trailbase-db-collection cors: specifier: ^2.8.6 @@ -962,16 +962,16 @@ importers: examples/solid/todo: dependencies: '@tanstack/electric-db-collection': - specifier: ^0.6.3 + specifier: ^0.7.0 version: link:../../../packages/electric-db-collection '@tanstack/query-core': specifier: ^5.90.20 version: 5.90.20 '@tanstack/query-db-collection': - specifier: ^1.4.8 + specifier: ^1.5.0 version: link:../../../packages/query-db-collection '@tanstack/solid-db': - specifier: ^0.3.12 + specifier: ^0.4.0 version: link:../../../packages/solid-db '@tanstack/solid-router': specifier: ^1.170.38 @@ -980,7 +980,7 @@ importers: specifier: ^1.168.57 version: 1.168.57(@tanstack/react-router@1.170.41(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(esbuild@0.28.1)(rollup@4.64.12)(solid-js@1.9.11)(vite-plugin-solid@2.11.10(@testing-library/jest-dom@6.9.1)(solid-js@1.9.11)(vite@7.3.7(@types/node@25.2.2)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.90.0)(terser@5.51.2)(tsx@4.21.0)(yaml@2.8.1)))(vite@7.3.7(@types/node@25.2.2)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.90.0)(terser@5.51.2)(tsx@4.21.0)(yaml@2.8.1))(webpack@5.112.0(esbuild@0.28.1)(lightningcss@1.33.0)) '@tanstack/trailbase-db-collection': - specifier: ^0.1.123 + specifier: ^0.2.0 version: link:../../../packages/trailbase-db-collection cors: specifier: ^2.8.6