diff --git a/.changeset/calm-oracles-check-loss.md b/.changeset/calm-oracles-check-loss.md
new file mode 100644
index 0000000000..ec0fcb7153
--- /dev/null
+++ b/.changeset/calm-oracles-check-loss.md
@@ -0,0 +1,17 @@
+---
+'@tanstack/db': patch
+'@tanstack/db-sqlite-persistence-core': patch
+'@tanstack/electric-db-collection': patch
+---
+
+Harden Electric resume and lifecycle handling so partial updates cannot materialize unknown or moved-out rows, stale async work and waiters cannot cross cleanup or restart—including automatic garbage collection—and valid batches behave the same across callback partitions and persistence hydration.
+
+Preserve hydrated baseline rows during persistence reloads, accept complete-row updates from explicit full-replica resumes, retain committed match evidence until reset, and restart persisted resumes when hydration completion cannot be verified.
+
+Replace stale cached rows atomically when an invalid resume falls back to a fresh snapshot. Keep subset acquisitions from restoring logically removed rows, and isolate utilities and tag visibility when collection options are reused while preserving compatible same-collection resume state.
+
+Accept partial updates to complete rows published independently by persistence, while preserving pending removal and reset boundaries. Avoid copying all applied keys at startup or each subset acquisition; presence checks overlay queued writes and buffered messages once per stream callback. Warn once when an older persistence adapter cannot verify hydration for safe resume.
+
+Keep buffered tag move-outs inside the progressive snapshot's existing transaction so later live updates are not discarded behind an orphaned truncate.
+
+Keep copied materialized configs and reentrant match callbacks scoped to their owning collection session. Cold tagged or legacy persisted state now recovers with a full snapshot behind cached rows, including in on-demand mode. Keep the reset marker through interrupted recovery and publish the replacement only after the full snapshot completes; known untagged and compatible warm resumes retain their saved offset.
diff --git a/.changeset/curly-planets-lead.md b/.changeset/curly-planets-lead.md
new file mode 100644
index 0000000000..8f0b2cbeb7
--- /dev/null
+++ b/.changeset/curly-planets-lead.md
@@ -0,0 +1,6 @@
+---
+'@tanstack/powersync-db-collection': minor
+---
+
+Add attachments support via `TanStackDBAttachmentQueue`. This extends the PowerSync SDK's `AttachmentQueue` and backs it with
+a TanStack DB collection, so attachment metadata and related rows commit atomically. Local files and remote uploads/deletes are managed separately.
diff --git a/.changeset/finish-review-recovery.md b/.changeset/finish-review-recovery.md
new file mode 100644
index 0000000000..16d050dae5
--- /dev/null
+++ b/.changeset/finish-review-recovery.md
@@ -0,0 +1,11 @@
+---
+'@tanstack/db': patch
+'@tanstack/db-sqlite-persistence-core': patch
+'@tanstack/powersync-db-collection': patch
+---
+
+Preserve native values, arbitrary class references, and draft cycles during mutation detachment; keep transaction persistence receipts settled after publication errors and avoid restoring an acknowledged direct insert over its server row. Keep a delete/reinsert visible when the old synced row has not yet been replaced.
+
+Retire replaced ordered prefixes without interrupting successful-load bookkeeping if release throws. Retry automatic ordered repair at most twice while retaining stale results and exposing the error; cleanup cancels retries and explicit window retry remains available.
+
+Keep persisted acquisitions independent, avoid retaining one-shot refreshes as permanent demand, and reject upstream load failures without discarding cached rows. Restore PowerSync readiness only after the recovered baseline also removes rows deleted or moved outside active filters during the tracking outage.
diff --git a/.changeset/fix-optimistic-field-reconciliation.md b/.changeset/fix-optimistic-field-reconciliation.md
new file mode 100644
index 0000000000..0d96935419
--- /dev/null
+++ b/.changeset/fix-optimistic-field-reconciliation.md
@@ -0,0 +1,5 @@
+---
+'@tanstack/db': patch
+---
+
+Preserve whole-row optimistic snapshots through sync and truncate. Fix insert-dependent update settlement, local origin tracking, and rollback publication while sibling requests remain pending. Keep source updates beneath an optimistic live-query delete when queued sync batches apply, without changing sync queue timing.
diff --git a/.changeset/fix-trailbase-stream-lifetimes.md b/.changeset/fix-trailbase-stream-lifetimes.md
new file mode 100644
index 0000000000..ad4806160b
--- /dev/null
+++ b/.changeset/fix-trailbase-stream-lifetimes.md
@@ -0,0 +1,5 @@
+---
+'@tanstack/trailbase-db-collection': patch
+---
+
+Fix unhandled rejections and resource leaks when a TrailBase subscription closes, fails, or is cleaned up. Drain buffered events before releasing the reader and prevent a canceled startup from canceling a replacement sync session.
diff --git a/.changeset/lucky-donkeys-repeat.md b/.changeset/lucky-donkeys-repeat.md
new file mode 100644
index 0000000000..91cf7f7438
--- /dev/null
+++ b/.changeset/lucky-donkeys-repeat.md
@@ -0,0 +1,5 @@
+---
+'@tanstack/db': patch
+---
+
+Reclaim collections that start syncing without subscribers, releasing unused live-query subscriptions after a minimum 50ms grace period. Keep pending preloads alive until they settle and refresh retention when preloading ready data; `gcTime: 0` continues to disable automatic GC. Keep detached observer snapshots fresh after empty reloads and allow Node processes to exit while background collection cleanup is pending.
diff --git a/.changeset/powersync-attachment-startup-ownership.md b/.changeset/powersync-attachment-startup-ownership.md
new file mode 100644
index 0000000000..d30adce3d6
--- /dev/null
+++ b/.changeset/powersync-attachment-startup-ownership.md
@@ -0,0 +1,5 @@
+---
+'@tanstack/powersync-db-collection': patch
+---
+
+Load attachment IDs before save/delete in eager and on-demand collections. Preserve existing files when a duplicate save is rejected, reject overlapping saves of the same ID across queues sharing a database, and clean up partial local writes.
diff --git a/.changeset/reject-ignored-page-callback.md b/.changeset/reject-ignored-page-callback.md
new file mode 100644
index 0000000000..e44a9400bd
--- /dev/null
+++ b/.changeset/reject-ignored-page-callback.md
@@ -0,0 +1,7 @@
+---
+'@tanstack/react-db': minor
+'@tanstack/vue-db': minor
+'@tanstack/svelte-db': minor
+---
+
+Remove the ignored `getNextPageParam` option from `useLiveInfiniteQuery` and reject it with a clear error when passed at runtime. Delete this callback from your config; for server pagination, use an on-demand Query Collection whose `queryFn` fulfills `meta.loadSubsetOptions`. Document fixed-server-page loading and clarify that `initialPageParam` labels result pages rather than setting a server cursor.
diff --git a/.github/SSR_RELEASE_PLAN.md b/.github/SSR_RELEASE_PLAN.md
new file mode 100644
index 0000000000..8df2f4fbbf
--- /dev/null
+++ b/.github/SSR_RELEASE_PLAN.md
@@ -0,0 +1,69 @@
+# TanStack DB SSR Release Plan
+
+## Release Goal
+
+Ship TanStack DB SSR as a single coherent story:
+
+- explicit collection-row hydration and live-query result snapshots through
+ `DbClient`
+- React and Svelte provider and descriptor resolution
+- derived live query identity with `queryKey` only when necessary
+- backwards-compatible dependency arrays with dev warnings until 1.0
+- a working TanStack Start demo and E2E proof
+
+## Pre-release Validation
+
+- Run `pnpm --filter @tanstack/db test`.
+- Run `pnpm --filter @tanstack/react-db test`.
+- Run `pnpm --filter @tanstack/svelte-db test`.
+- Run `pnpm --filter @tanstack/react-router-with-db test` (includes type
+ tests).
+- Run `pnpm --filter @tanstack/query-db-collection test`.
+- Run `pnpm --filter @tanstack/db-sqlite-persistence-core test`.
+- Run `pnpm --filter @tanstack/db-example-react-start-ssr-e2e test:e2e`.
+- Run `pnpm --filter @tanstack/db-example-react-next-ssr-e2e test:e2e`.
+- Run `pnpm test:docs`.
+- Run `pnpm test:sherif`.
+- Run `pnpm build`.
+
+## Demo
+
+- Live URL: https://tanstack-db-ssr-demo.netlify.app/ssr-db
+- Deploy `examples/react/start-ssr-e2e` to an SSR-capable host.
+- Verify the deployed `/ssr-db` route serves SSR HTML with hydrated rows.
+- Verify browser hydration succeeds without console/page errors.
+- Verify the streamed collection chunk updates the live query.
+- Verify `/ssr-db-stream` streams a projected result, omits source-only data,
+ and hands off to browser sync.
+- Run `PLAYWRIGHT_BASE_URL=https://tanstack-db-ssr-demo.netlify.app pnpm --filter @tanstack/db-example-react-start-ssr-e2e test:e2e:hosted`.
+- Add the live URL to the PR description and release notes.
+
+## Docs
+
+- Publish the [SSR and Hydration guide](../docs/guides/ssr.md).
+- Link the guide from overview, quick start, live queries, and React overview.
+- Regenerate API reference docs in a dedicated docs-maintenance pass if broad
+ TypeDoc output churn is acceptable.
+- Confirm docs explain when `queryKey` is necessary and when it should be
+ omitted.
+- Confirm docs say dependency arrays warn now and are removed in 1.0.
+
+## Migration Messaging
+
+- Lead with: explicit collection preloads transport normalized rows; live-query
+ preloads transport only their result snapshot.
+- Emphasize that existing apps keep working.
+- State that `createCollection(...)` remains available, but SSR apps should use
+ `collectionOptions(...)` plus `DbClient`.
+- Explain that React dependency arrays are deprecated with a 1.0 removal path.
+- Show `queryKey` only for opaque functional query logic or hot render paths.
+
+## Announcement Checklist
+
+- PR description includes high-level summary, migration cheat sheet, and test
+ commands.
+- Release notes include a "No removals in this release" compatibility section.
+- Discord announcement links the SSR guide and live demo.
+- Example migration diff is available from the Start SSR demo.
+- Follow-up issues are filed for the remaining framework adapters and API
+ reference generation if they are not part of the shipping PR.
diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml
index 3678cd19f2..a340502b46 100644
--- a/.github/workflows/e2e-tests.yml
+++ b/.github/workflows/e2e-tests.yml
@@ -45,6 +45,7 @@ jobs:
run: |
pnpm --filter @tanstack/db-ivm build
pnpm --filter @tanstack/db build
+ pnpm --filter @tanstack/react-db build
pnpm --filter @tanstack/electric-db-collection build
pnpm --filter @tanstack/offline-transactions build
pnpm --filter @tanstack/query-db-collection build
@@ -68,6 +69,21 @@ jobs:
env:
ELECTRIC_URL: http://localhost:3000
+ - name: Install Playwright browsers
+ run: |
+ cd examples/react/start-ssr-e2e
+ pnpm exec playwright install --with-deps chromium
+
+ - name: Run React Start SSR E2E tests
+ run: |
+ cd examples/react/start-ssr-e2e
+ pnpm test:e2e
+
+ - name: Run Next.js SSR E2E tests
+ run: |
+ cd examples/react/next-ssr-e2e
+ pnpm test:e2e
+
- name: Run Node SQLite persisted collection E2E tests
run: |
cd packages/node-db-sqlite-persistence
diff --git a/.gitignore b/.gitignore
index 4ad9ee25d4..3bfb31bb9b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,6 +10,8 @@ yarn.lock
build
coverage
dist
+playwright-report
+test-results
# misc
.DS_Store
@@ -19,6 +21,7 @@ dist
.env.test.local
.env.production.local
.next
+next-env.d.ts
npm-debug.log*
yarn-debug.log*
diff --git a/AGENTS.md b/AGENTS.md
index a92ff46761..aa7ffc7f1e 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -2,6 +2,17 @@
This guide provides principles and patterns for AI agents contributing to the TanStack DB codebase. These guidelines are derived from PR review patterns and reflect the quality standards expected in this project.
+## Required reading: live-query materialization
+
+Before reading, analyzing, or modifying correlated live-query materialization
+code under `packages/db/src/query/live/`, read
+`packages/db/src/query/live/ARCHITECTURE.md` in full. Read it before changing
+the related includes oracle tests as well.
+
+Treat that document's component boundaries and normative laws as constraints.
+If a change intentionally revises an architectural contract, update the
+architecture document in the same pull request.
+
## Table of Contents
1. [Type Safety](#type-safety)
@@ -351,7 +362,8 @@ const dependentBuilders = [] // Accurately describes dependents
### Always Add Tests for Bugs
-**Key Principle:** If you're fixing a bug, add a unit test that reproduces the bug before fixing it. This ensures:
+**Key Principle:** Reproduce a bug in a test before fixing it. Prefer extending
+an oracle as described below over adding an isolated unit test. This ensures:
- The bug is actually fixed
- The bug doesn't regress in the future
@@ -368,6 +380,60 @@ test('ignores snapshot that resolves after up-to-date message', async () => {
})
```
+### Treat Every Review Bug as a Test Gap
+
+When a reviewer agent confirms a bug, it must also ask why the existing tests
+did not catch it. The finding should name the missing test law, state
+transition, generator dimension, adapter boundary, or assertion. If a test or
+oracle should already have caught the bug, identify the false-green model,
+classifier, fixture, or assertion that let it pass. Use that analysis to suggest
+the smallest test or oracle improvement that would catch the same class of bug,
+not only the reported example.
+
+### Keep Oracles Independent
+
+An oracle is useful only when its expected result comes from a source independent
+of the implementation under test. Do not translate production branches, state
+machines, classifiers, or helper functions into a second implementation and call
+that an oracle. Both copies can encode the same wrong assumption.
+
+- Derive expected behavior from public contracts, documented prior behavior,
+ mathematical laws, or a separately specified reference model.
+- Keep the reference model structurally different from production. Do not import
+ the production helper or reuse its classifications to compute expected results.
+- Preserve existing contract tests unless a product or design decision explicitly
+ changes the contract. Rewriting a passing expectation to match new production
+ behavior is a design review, not routine test maintenance.
+- When production work suggests an oracle change, compare the old and new
+ semantics with counterexamples before editing the oracle.
+- Use hostile mutants to prove the oracle rejects plausible wrong designs,
+ including the mistake production currently makes. A green oracle without a
+ demonstrated kill is weak evidence.
+- Use process grammar to explore lifecycle paths, and design grammar to challenge
+ the oracle's reference semantics. More generated traces cannot repair a wrong
+ reference model.
+
+### Prefer Oracle Coverage Over Isolated Regressions
+
+An oracle that checks general laws across generated states and histories is a
+stronger form of coverage than a unit test for one specific example. Prefer
+extending an existing oracle when it can cover the behavior. Add the missing
+model rule, generator dimension, state transition, or observable assertion;
+adding more pinned examples alone does not generalize the oracle.
+
+Use a focused regression to isolate and shrink a failure, then keep it as a
+replay example for the broader oracle where possible. Verify that the expanded
+oracle fails without the fix and passes with it. Keep valuable unit tests, but
+do not treat them as a substitute for applicable oracle coverage. If an oracle
+is not practical for the behavior, explain why a focused test is sufficient.
+
+### Name Tests After Behavior
+
+Test names should state the behavior they prove. Do not put issue or pull
+request numbers in test names; those references become stale and make the test
+suite harder to read. When an external report contains essential context that
+the test cannot express, link it in a nearby comment instead.
+
### Test Corner Cases
Common corner cases to consider:
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000000..cc6f65a9e7
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,146 @@
+# Contributing
+
+## Questions
+
+If you have questions about implementation details, help, or support, please use our dedicated community forum at [GitHub Discussions](https://github.com/TanStack/db/discussions). **PLEASE NOTE:** If you choose to open an issue for your question instead, your issue may be closed and redirected to the forum.
+
+## Reporting issues
+
+If you have found what you think is a bug, please [file an issue](https://github.com/TanStack/db/issues/new/choose). **PLEASE NOTE:** Issues that are identified as implementation questions or non-issues may be closed and redirected to [GitHub Discussions](https://github.com/TanStack/db/discussions).
+
+## Suggesting new features
+
+If you are here to suggest a feature, first create an issue if it does not already exist. From there, we can discuss use cases for the feature and how it could be implemented.
+
+## Development
+
+If you have been assigned to fix an issue or develop a new feature, please follow these steps to get started:
+
+- Fork this repository.
+- Use the Node.js version mentioned in `.nvmrc`.
+
+ ```bash
+ nvm use
+ ```
+
+- Enable [Corepack](https://nodejs.org/api/corepack.html) so the [pnpm](https://pnpm.io/) version mentioned in `package.json` is used.
+
+ ```bash
+ corepack enable
+ ```
+
+- Install dependencies.
+
+ ```bash
+ pnpm install
+ ```
+
+- Build all packages.
+
+ ```bash
+ pnpm build
+ ```
+
+- Run tests.
+
+ ```bash
+ pnpm test
+ ```
+
+- Run linting.
+
+ ```bash
+ pnpm lint
+ ```
+
+- Implement your changes and tests in the relevant package or example.
+- Document your changes in the appropriate doc page.
+- Git stage your required changes and commit them.
+- Submit a PR for review.
+
+### Editing the docs locally and previewing changes
+
+The documentation for all TanStack projects is hosted on [tanstack.com](https://tanstack.com), which is a TanStack Start application (https://github.com/TanStack/tanstack.com). You need to run this app locally to preview your changes in the `TanStack/db` docs.
+
+> [!NOTE]
+> The website fetches doc pages from GitHub in production, and searches for them at `../db/docs` in development. Your local clone of `TanStack/db` needs to be in the same directory as the local clone of `TanStack/tanstack.com`.
+
+You can follow these steps to set up the docs for local development:
+
+1. Make a new directory called `tanstack`.
+
+```sh
+mkdir tanstack
+```
+
+2. Enter that directory and clone the [`TanStack/db`](https://github.com/TanStack/db) and [`TanStack/tanstack.com`](https://github.com/TanStack/tanstack.com) repos.
+
+```sh
+cd tanstack
+git clone git@github.com:TanStack/db.git
+# We probably don't need all the branches and commit history
+# from the `tanstack.com` repo, so let's just create a shallow
+# clone of the latest version of the `main` branch.
+# Read more about shallow clones here:
+# https://github.blog/2020-12-21-get-up-to-speed-with-partial-clone-and-shallow-clone/#user-content-shallow-clones
+git clone git@github.com:TanStack/tanstack.com.git --depth=1 --single-branch --branch=main
+```
+
+> [!NOTE]
+> Your `tanstack` directory should look like this:
+>
+> ```text
+> tanstack/
+> |
+> +-- db/ (<-- this directory cannot be called anything else!)
+> |
+> +-- tanstack.com/
+> ```
+
+3. Enter the `tanstack/tanstack.com` directory, install the dependencies, and run the app in dev mode.
+
+```sh
+cd tanstack.com
+pnpm i
+# The app will run on http://localhost:3000 by default
+pnpm dev
+```
+
+4. Visit http://localhost:3000/db/latest/docs/overview in the browser and see the changes you make in `tanstack/db/docs` there.
+
+> [!WARNING]
+> You will need to update `docs/config.json` if you add a new documentation page.
+
+### Running examples
+
+- Make sure you've installed dependencies in the repo's root directory.
+
+ ```bash
+ pnpm install
+ ```
+
+- If you want to run an example against your local changes, run the relevant package build/watch command from the repo root if needed. Otherwise, examples may run against the latest published TanStack DB release.
+
+- Run the example from the selected example directory.
+
+ ```bash
+ pnpm dev
+ ```
+
+#### Note on standalone execution
+
+If you want to run an example without installing dependencies for the whole repo, follow the instructions from the example's README.md file. It will then run against the latest TanStack DB release.
+
+## Changesets
+
+This repo uses [Changesets](https://github.com/changesets/changesets) to automate releases. If your PR should release a new package version (patch, minor, or major), please run `pnpm changeset` and commit the generated file. If your PR affects docs, examples, styles, etc., you probably don't need to generate a changeset.
+
+## Pull requests
+
+Maintainers merge pull requests by squashing all commits and editing the commit message if necessary using the GitHub user interface.
+
+Use an appropriate commit type. Be especially careful with breaking changes.
+
+## Releases
+
+For each new commit added to `main`, a GitHub Workflow is triggered which runs the [Changesets Action](https://github.com/changesets/action). This generates a preview PR showing the impact of all changesets. When this PR is merged, the package will be published to npm.
diff --git a/README.md b/README.md
index 01e50bf5c1..7bc46637df 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,19 @@
-
+
+
+
+
+
diff --git a/docs/collections/electric-collection.md b/docs/collections/electric-collection.md
index f5a475bb9e..f363661925 100644
--- a/docs/collections/electric-collection.md
+++ b/docs/collections/electric-collection.md
@@ -2,8 +2,6 @@
title: Electric Collection
---
-# Electric Collection
-
Electric collections provide seamless integration between TanStack DB and ElectricSQL, enabling real-time data synchronization with your Postgres database through Electric's sync engine.
## Overview
@@ -335,6 +333,46 @@ await todosCollection.utils.awaitMatch(
)
```
+### Cleanup and resume safety
+
+Transaction evidence and pending `awaitTxId`/`awaitMatch` calls belong to one
+collection lifecycle. Explicit cleanup and automatic garbage collection reject
+pending waits with `StreamAbortedError`; callbacks from the retired stream cannot
+settle waits in a restarted collection. Observe these promises even when the
+component or collection may be disposed before they settle.
+
+Persisted resumes wait for the cached row baseline to finish hydrating. If the
+persistence wrapper cannot verify hydration completion, Electric starts a fresh
+snapshot instead of using the saved offset and handle. It warns once per options
+descriptor; update the persistence adapter alongside Electric to enable safe resume.
+Fresh eager snapshots wait for hydration, then replace the cached rows at the
+snapshot's commit boundary. Rows omitted from that snapshot do not survive in
+the collection or its persisted cache, even when the fresh snapshot is empty.
+
+Tag membership is kept in memory, not restored from cached row headers. A cold
+restart therefore fetches a full snapshot when the saved state needs tags, or
+comes from an older version that did not record whether tags were used. Untagged
+shapes can still resume from their saved offset. Cached rows remain visible until
+the replacement snapshot completes; a partial batch or subset completion cannot
+publish that replacement early. Interrupting recovery leaves a durable reset
+marker so the next start still refetches. This recovery also requests a full shape
+snapshot in on-demand mode, at the cost of fetching more than the active subsets.
+
+An eager or progressive resume cannot apply a partial update to an unknown row.
+The adapter rejects that batch, enters an error state, and records a reset so the
+next sync starts from a full snapshot. This does not silently retry the failed
+stream. Complete updates from an explicit `replica: 'full'` stream remain valid.
+On-demand streams can observe updates outside their loaded subsets; unknown
+partial rows are ignored, while transaction acknowledgement evidence is retained.
+Complete rows published by persistence reloads or another tab are valid baselines
+for subsequent partial updates. Pending deletions and resets still take precedence
+over an older row that remains publicly visible.
+
+Reusing Electric collection options, including a spread of those options, does
+not share transaction waiters or tag visibility between collections. Tag state
+survives a compatible resume of the same collection and clears on a fresh
+snapshot or `must-refetch`.
+
### Helper Functions
The package exports helper functions for use in custom match functions:
diff --git a/docs/collections/local-only-collection.md b/docs/collections/local-only-collection.md
index 17bf51ef4b..145ba72683 100644
--- a/docs/collections/local-only-collection.md
+++ b/docs/collections/local-only-collection.md
@@ -2,8 +2,6 @@
title: LocalOnly Collection
---
-# LocalOnly Collection
-
LocalOnly collections are designed for in-memory client data or UI state that doesn't need to persist across browser sessions or sync across tabs.
## Overview
@@ -192,10 +190,12 @@ export const modalStateCollection = createCollection(
// Use in component
function UserProfileModal() {
- const { data: modals } = useLiveQuery((q) =>
- q.from({ modal: modalStateCollection })
- .where(({ modal }) => eq(modal.id, 'user-profile'))
- )
+ const { data: modals } = useLiveQuery({
+ query: (q) =>
+ q
+ .from({ modal: modalStateCollection })
+ .where(({ modal }) => eq(modal.id, 'user-profile')),
+ })
const modalState = modals[0]
@@ -248,10 +248,12 @@ export const formDraftsCollection = createCollection(
// Use in component
function CreatePostForm() {
- const { data: drafts } = useLiveQuery((q) =>
- q.from({ draft: formDraftsCollection })
- .where(({ draft }) => eq(draft.id, 'new-post'))
- )
+ const { data: drafts } = useLiveQuery({
+ query: (q) =>
+ q
+ .from({ draft: formDraftsCollection })
+ .where(({ draft }) => eq(draft.id, 'new-post')),
+ })
const currentDraft = drafts[0]
diff --git a/docs/collections/local-storage-collection.md b/docs/collections/local-storage-collection.md
index 171e5cb9b4..36eecfe3f1 100644
--- a/docs/collections/local-storage-collection.md
+++ b/docs/collections/local-storage-collection.md
@@ -2,8 +2,6 @@
title: LocalStorage Collection
---
-# LocalStorage Collection
-
LocalStorage collections store small amounts of local-only state that persists across browser sessions and syncs across browser tabs in real-time.
## Overview
@@ -263,10 +261,12 @@ export const userPreferencesCollection = createCollection(
// Use in component
function SettingsPanel() {
- const { data: prefs } = useLiveQuery((q) =>
- q.from({ pref: userPreferencesCollection })
- .where(({ pref }) => eq(pref.id, 'current-user'))
- )
+ const { data: prefs } = useLiveQuery({
+ query: (q) =>
+ q
+ .from({ pref: userPreferencesCollection })
+ .where(({ pref }) => eq(pref.id, 'current-user')),
+ })
const currentPrefs = prefs[0]
diff --git a/docs/collections/powersync-collection.md b/docs/collections/powersync-collection.md
index c8ddbabbbe..b2a1ff8529 100644
--- a/docs/collections/powersync-collection.md
+++ b/docs/collections/powersync-collection.md
@@ -2,8 +2,6 @@
title: PowerSync Collection
---
-# PowerSync Collection
-
PowerSync collections provide seamless integration between TanStack DB and [PowerSync](https://powersync.com), enabling automatic synchronization between your in-memory TanStack DB collections and PowerSync's SQLite database. This gives you offline-ready persistence, real-time sync capabilities, and powerful conflict resolution.
## Overview
@@ -1099,4 +1097,177 @@ const liveQuery = createLiveQueryCollection({
completed: todo.completed,
})),
})
-```
\ No newline at end of file
+```
+
+## Attachments
+
+`@tanstack/powersync-db-collection` ships `TanStackDBAttachmentQueue`, an [`AttachmentQueue`](https://docs.powersync.com/usage/use-case-examples/attachments-files) that commits attachment metadata and related collection mutations (for example, setting `lists.photo_id`) in one database transaction. File I/O is separate: a failed save attempts to remove its local file, while the SDK performs remote uploads and deletes later.
+
+The queue extends PowerSync's `AttachmentQueue`, so the generic concepts are unchanged and documented once in the SDK.
+
+> This section only covers what is specific to the TanStack DB integration. For storage adapters (local and remote), the `AttachmentTable` schema primitive, error-handling/retry semantics, and the `startSync()` / `stopSync()` lifecycle, see the [PowerSync attachments documentation](https://docs.powersync.com/usage/use-case-examples/attachments-files).
+
+### Prerequisites
+
+These are standard PowerSync attachment requirements. See the SDK attachments docs for details.
+
+- An `AttachmentTable` in your schema:
+
+ ```ts
+ import { AttachmentTable, Schema } from "@powersync/web"
+
+ const APP_SCHEMA = new Schema({
+ // ...your tables
+ attachments: new AttachmentTable(),
+ })
+ ```
+
+- A local storage adapter (such as `IndexDBFileSystemStorageAdapter` on web) and a remote storage adapter (an implementation of the SDK's `RemoteStorageAdapter`, for example backed by Supabase Storage). Both are generic to all attachment users. See the SDK docs for the available adapters and the remote-adapter contract.
+
+### 1. Create the attachments collection
+
+This is the piece that makes the integration TanStack-aware: a normal PowerSync collection over the attachments table. The queue reads and writes attachment records through it.
+
+Both eager and on-demand collections work. Before `save` or `delete` opens its mutation, the queue loads the attachment ID through a temporary live query and retains that query until the transaction is confirmed. It does not call `preload()` inside a mutation function or require loading the entire table.
+
+An existing ID, or a concurrent save of that ID through the same PowerSync database object, is rejected before writing the file. This is an in-process guard, not a lock across separate database handles, SDK queues, tabs, or processes. File names retain the SDK's ID-based convention so restart can find files after the app's storage directory moves.
+
+```ts
+import { createCollection } from "@tanstack/react-db"
+import { powerSyncCollectionOptions } from "@tanstack/powersync-db-collection"
+
+const attachmentsCollection = createCollection(
+ powerSyncCollectionOptions({
+ database: db,
+ table: APP_SCHEMA.props.attachments,
+ })
+)
+```
+
+### 2. Construct the queue
+
+Pass your collection as `attachmentsCollection` alongside the standard `AttachmentQueue` options. Only `attachmentsCollection` and `watchAttachments` (below) are specific to this package; `db`, `localStorage`, `remoteStorage`, and `errorHandler` are the usual SDK options.
+
+```ts
+import { TanStackDBAttachmentQueue } from "@tanstack/powersync-db-collection"
+
+const attachmentQueue = new TanStackDBAttachmentQueue({
+ db,
+ attachmentsCollection, // TanStack DB collection over your AttachmentTable
+ localStorage, // SDK local storage adapter
+ remoteStorage, // your RemoteStorageAdapter (see SDK docs)
+ watchAttachments, // see step 3
+ errorHandler, // standard AttachmentQueue error handler (see SDK docs)
+})
+```
+
+Start and stop syncing with the standard `attachmentQueue.startSync()` / `attachmentQueue.stopSync()` lifecycle (see SDK docs), typically inside a React effect or provider.
+
+### 3. Tell the queue which attachments exist (`watchAttachments`)
+
+`watchAttachments` reports the set of attachment IDs your data currently references, so the queue knows what to download and what to archive. With TanStack DB you drive it from a live query: emit the initial state, then re-emit the complete set on every change, and clean up on abort.
+
+```ts
+import {
+ createCollection,
+ isNull,
+ liveQueryCollectionOptions,
+ not,
+} from "@tanstack/db"
+import { WatchedAttachmentItem } from "@powersync/web"
+
+const watchAttachments = async (onUpdate, abortSignal) => {
+ // Every row in your data model that references an attachment.
+ const livePhotoIds = createCollection(
+ liveQueryCollectionOptions({
+ query: (q) =>
+ q
+ .from({ document: listsCollection })
+ .where(({ document }) => not(isNull(document.photo_id)))
+ .select(({ document }) => ({ photo_id: document.photo_id })),
+ })
+ )
+
+ const mapper = (item) =>
+ ({
+ id: item.photo_id,
+ fileExtension: "jpg",
+ }) satisfies WatchedAttachmentItem
+
+ // 1. Report the initial set of referenced attachment IDs.
+ const initialState = await livePhotoIds.stateWhenReady()
+ onUpdate(Array.from(initialState.values()).map(mapper))
+
+ // 2. Re-emit the whole set on every change (the queue expects the holistic state).
+ livePhotoIds.subscribeChanges(() => {
+ onUpdate(livePhotoIds.map(mapper))
+ })
+
+ // 3. Clean up when sync stops.
+ abortSignal.addEventListener("abort", () => livePhotoIds.cleanup(), {
+ once: true,
+ })
+}
+```
+
+### 4. Save an attachment atomically with related data
+
+`save` writes the file, inserts the attachment record into your collection, and runs your `updateHook` mutations in the same transaction. Use the hook to insert or update the row that references the new attachment, so both land together or not at all.
+
+```ts
+await attachmentQueue.save({
+ data, // file bytes (ArrayBuffer / base64, per your local adapter)
+ fileExtension: "jpg",
+ updateHook: (attachmentRecord) => {
+ // Runs in the same transaction as the attachment insert.
+ listsCollection.insert({
+ id: crypto.randomUUID(),
+ name,
+ created_at: new Date(),
+ owner_id: userID,
+ photo_id: attachmentRecord.id, // associate the row with the attachment
+ })
+ },
+})
+```
+
+> `updateHook` must be synchronous, it runs inside the transaction's synchronous `mutate()` block and its return value is not awaited, so any mutation after an `await` escapes the transaction. Do asynchronous work before calling `save` or `delete`.
+
+### 5. Delete an attachment and detach it from the row
+
+`delete` queues the file for deletion and runs your `updateHook` in the same transaction. Clear the foreign key so the row and the attachment stay consistent. As with `save`, the hook must be synchronous.
+
+**Upstream limitation:** the SDK version used by this PR can overwrite a queued deletion when an already-running upload succeeds or fails. The related row is detached, but the SDK can lose the remote deletion or retry the obsolete upload. This integration does not work around that SDK completion race. The [attachment oracle notes](../../packages/powersync-db-collection/tests/ATTACHMENT-ORACLE.md) include runnable native-SDK and integration repros; a green ordinary suite does not establish safety for this overlap.
+
+```ts
+await attachmentQueue.delete({
+ id: photo_id,
+ updateHook: () => {
+ listsCollection.update(listId, (draft) => {
+ draft.photo_id = null
+ })
+ },
+})
+```
+
+### 6. Display attachments via a live-query join
+
+Join your attachments collection into a live query to read the local URI (the locally cached file path) alongside your domain rows:
+
+```ts
+import { eq } from "@tanstack/db"
+
+const { data } = useLiveQuery((q) =>
+ q
+ .from({ lists: listsCollection })
+ .leftJoin({ attachment: attachmentsCollection }, ({ lists, attachment }) =>
+ eq(lists.photo_id, attachment.id)
+ )
+ .select(({ lists, attachment }) => ({
+ id: lists.id,
+ name: lists.name,
+ photo_id: lists.photo_id,
+ attachment_local_uri: attachment?.local_uri,
+ }))
+)
+```
diff --git a/docs/collections/query-collection.md b/docs/collections/query-collection.md
index 73f3511edc..25b5f565de 100644
--- a/docs/collections/query-collection.md
+++ b/docs/collections/query-collection.md
@@ -2,8 +2,6 @@
title: Query Collection
---
-# Query Collection
-
Query collections provide seamless integration between TanStack DB and TanStack Query, enabling automatic synchronization between your local database and remote data sources.
## Overview
@@ -25,22 +23,26 @@ npm install @tanstack/query-db-collection @tanstack/query-core @tanstack/db
```typescript
import { QueryClient } from "@tanstack/query-core"
-import { createCollection } from "@tanstack/db"
+import { DbClient, collectionOptions } from "@tanstack/db"
import { queryCollectionOptions } from "@tanstack/query-db-collection"
const queryClient = new QueryClient()
+const db = new DbClient({ queryClient })
-const todosCollection = createCollection(
+const todosCollection = collectionOptions("todos", (client) =>
queryCollectionOptions({
+ id: "todos",
queryKey: ["todos"],
queryFn: async () => {
const response = await fetch("/api/todos")
return response.json()
},
- queryClient,
+ queryClient: client.requireDependency("queryClient"),
getKey: (item) => item.id,
})
)
+
+const todos = db.collection(todosCollection)
```
## Configuration Options
@@ -54,27 +56,225 @@ The `queryCollectionOptions` function accepts the following options:
- `queryClient`: TanStack Query client instance
- `getKey`: Function to extract the unique key from an item
+### Request-scoped QueryClient
+
+`queryCollectionOptions` needs a `queryClient`. In SSR, TanStack Start, tests,
+or multi-tenant apps, that client is request-local rather than module-global.
+Put it on `DbClient`, then resolve it inside the collection descriptor factory:
+
+```typescript
+import { QueryClient } from "@tanstack/query-core"
+import { DbClient, collectionOptions } from "@tanstack/db"
+import { queryCollectionOptions } from "@tanstack/query-db-collection"
+
+interface Todo {
+ id: string
+ title: string
+}
+
+export const todoCollection = collectionOptions("todos", (client) =>
+ queryCollectionOptions({
+ id: "todos",
+ queryKey: ["todos"],
+ queryFn: async () => {
+ const response = await fetch("/api/todos")
+ return response.json() as Promise>
+ },
+ queryClient: client.requireDependency("queryClient"),
+ getKey: (todo) => todo.id,
+ })
+)
+
+export function createRequestClients() {
+ const queryClient = new QueryClient()
+ const dbClient = new DbClient({ queryClient })
+ return { queryClient, dbClient }
+}
+```
+
+`dbClient.collection(todoCollection)` memoizes one collection instance for that
+descriptor and client. A second `DbClient` materializes fresh adapter state and
+uses its own `QueryClient`.
+
+Passing `queryClient` directly to `queryCollectionOptions` remains supported for
+`createCollection(...)` and existing apps. When a descriptor is materialized,
+an explicit `DbClient` dependency takes precedence; the configured
+`queryClient` is the backwards-compatible fallback.
+
+### Business-Scoped Collection Factories
+
+A tenant, project, account, or route parameter can define a **business scope**:
+the server resource that a collection represents. Include the scope in the
+descriptor id, Query key, and `queryFn`. This extends the
+[request-scoped QueryClient pattern](#request-scoped-queryclient) with an
+explicit scope parameter:
+
+```typescript
+interface Todo {
+ id: string
+ title: string
+ projectId: string
+}
+
+async function fetchProjectTodos(projectId: string): Promise> {
+ const response = await fetch(`/api/projects/${projectId}/todos`)
+ return response.json()
+}
+
+function createProjectTodosDescriptor(
+ projectId: string,
+) {
+ return collectionOptions(`project:${projectId}:todos`, (client) =>
+ queryCollectionOptions({
+ id: `project:${projectId}:todos`,
+ queryKey: ["projects", projectId, "todos"],
+ queryFn: () => fetchProjectTodos(projectId),
+ queryClient: client.requireDependency("queryClient"),
+ getKey: (todo) => todo.id,
+ })
+ )
+}
+```
+
+The scope is part of the descriptor identity. `DbClient` resolves separately
+created descriptors with the same id to the same collection, so a React hook
+can create the descriptor from its current parameters:
+
+```typescript
+export function useProjectTodos(projectId: string) {
+ return useDbClient().collection(createProjectTodosDescriptor(projectId))
+}
+```
+
+Only the first descriptor for an id is materialized. Include every scope value
+that changes the collection in both its descriptor id and Query key. Call
+`await dbClient.cleanup()` when the client scope ends.
+
+A business scope is separate from a **relational subset** requested by a live query. With `syncMode: "on-demand"`, `LoadSubsetOptions` describes predicates, ordering, limits, and offsets within one business-scoped collection. These options reach `queryFn` through `ctx.meta.loadSubsetOptions` and determine the subset Query keys. See [QueryFn and Predicate Push-Down](#queryfn-and-predicate-push-down).
+
+Do not create a collection for each `where`, `orderBy`, or `limit`. Reuse the business-scoped collection and let on-demand loading represent those subsets. Create separate collections only for distinct server resources.
+
### Query Options
-- `select`: Function that lets extract array items when they're wrapped with metadata
+Query Collections use TanStack Query internally and expose supported Query observer options as top-level `queryCollectionOptions` fields.
+
+The following top-level Query Collection options are forwarded to the underlying Query observer:
+
+- `select`: Function that extracts the row array TanStack DB materializes from a wrapped Query response
- `enabled`: Whether the query should automatically run (default: `true`)
-- `refetchInterval`: Refetch interval in milliseconds (default: 0 — set an interval to enable polling refetching)
+- `refetchInterval`: Refetch interval in milliseconds
- `retry`: Retry configuration for failed queries
- `retryDelay`: Delay between retries
- `staleTime`: How long data is considered fresh
-- `meta`: Optional metadata that will be passed to the query function context
+- `gcTime`: How long unused query data stays in the Query cache
+- `refetchOnWindowFocus`: Whether to refetch when the window regains focus
+- `refetchOnReconnect`: Whether to refetch when the network reconnects
+- `refetchOnMount`: Whether to refetch when the observer mounts
+- `networkMode`: Query network mode
+- `initialData`: Initial Query response for eager collections
+- `initialDataUpdatedAt`: Timestamp used by TanStack Query to determine initial data freshness
+- `meta`: Metadata passed to the query function context. Query Collections may add `loadSubsetOptions` for on-demand queries.
+
+```ts
+const todosCollection = createCollection(
+ queryCollectionOptions({
+ queryKey: ["todos"],
+ queryFn: fetchTodos,
+ queryClient,
+ getKey: (todo) => todo.id,
+ refetchOnWindowFocus: true,
+ refetchOnReconnect: true,
+ refetchOnMount: "always",
+ networkMode: "online",
+ })
+)
+```
+
+Top-level `meta` is always merged by Query Collection so it can add on-demand `loadSubsetOptions`. Other supported top-level Query options are only passed to TanStack Query when you define them. If you omit them, `QueryClient.defaultOptions` can still apply.
+
+Some fields are owned or reinterpreted by the collection adapter rather than treated as ordinary Query option pass-through:
+
+- `queryKey`: Identifies the Query cache entry and, in on-demand mode, may be built from load-subset options.
+- `queryFn`: Fetches the complete collection state or the requested on-demand subset.
+- `select`: Extracts array rows from wrapped responses before they are stored in the collection. This is not the same contract as TanStack Query's `select` option.
+- `queryClient`: Supplies the Query client instance used by the collection.
+- `syncMode`: Controls whether the collection syncs eagerly or on demand.
+- `getKey`: Extracts each row's stable TanStack DB key.
+- Mutation handlers such as `onInsert`, `onUpdate`, and `onDelete`.
+
+Some TanStack Query fields are owned or reinterpreted by Query Collection and are intentionally not exposed as ordinary Query observer options:
+
+- `queryKey`, `queryFn`, and `queryClient`
+- `select` (Query Collection uses this for row extraction, not TanStack Query's observer-level `select` contract)
+- `meta` (merged by Query Collection so on-demand `loadSubsetOptions` can be included)
+- `subscribed` (Query Collection owns the observer subscription lifecycle)
+- `structuralSharing` and `notifyOnChangeProps` (managed by Query Collection synchronization)
+
+`placeholderData` is intentionally unsupported. TanStack Query treats placeholder data as observer-local presentation state rather than cached Query data. Materializing it would expose temporary UI data as collection-wide normalized rows. Render placeholders in the consuming UI instead.
+
+### Request Cancellation with `QueryFunctionContext.signal`
+
+TanStack Query passes an `AbortSignal` to `queryFn` through the query function
+context. Forward `ctx.signal` to `fetch` or another abortable client to make the
+request cancellable:
+
+```typescript
+const todosCollection = createCollection(
+ queryCollectionOptions({
+ queryKey: ["todos"],
+ queryFn: async (ctx) => {
+ const response = await fetch("/api/todos", {
+ signal: ctx.signal,
+ })
+
+ if (!response.ok) {
+ throw new Error("Failed to fetch todos")
+ }
+
+ return response.json() as Promise>
+ },
+ queryClient,
+ getKey: (todo) => todo.id,
+ }),
+)
+```
+
+Explicit collection cleanup cancels each exact Query key the collection is
+currently tracking before removing it from the Query cache:
+
+```typescript
+await todosCollection.cleanup()
+```
+
+The underlying request is aborted only when its client consumes `ctx.signal`.
+A client that ignores the signal may continue its request even though the
+collection has been cleaned up.
+
+An unloaded on-demand subset is no longer tracked. A later explicit collection
+cleanup does not revisit its Query key.
+
+Query cache entries are shared within a `QueryClient`. Explicit cleanup can
+affect other consumers using the same exact Query keys.
+
+On-demand subset unloading does not explicitly call
+`queryClient.cancelQueries()`. It removes the subset's Query observer. If this
+was the final observer and the query function consumed `ctx.signal`, TanStack
+Query aborts the request. If the signal was ignored, or another observer still
+uses the same exact Query key, the request may finish and remain cached until
+`gcTime`.
### Using with `queryOptions(...)`
-If your app already uses TanStack Query's `queryOptions` helper (e.g. from `@tanstack/react-query`), you can spread those options into `queryCollectionOptions`. Note that `queryFn` must be explicitly provided since query collections require it both in types and at runtime:
+If your app already uses TanStack Query's `queryOptions` helper (e.g. from `@tanstack/react-query`), you can spread compatible top-level options into `queryCollectionOptions`. Note that `queryFn` must be explicitly provided since query collections require it both in types and at runtime, and Query Collection's `select` option is for row extraction rather than TanStack Query observer-level selection:
```typescript
import { QueryClient } from "@tanstack/query-core"
-import { createCollection } from "@tanstack/db"
+import { DbClient, collectionOptions } from "@tanstack/db"
import { queryCollectionOptions } from "@tanstack/query-db-collection"
import { queryOptions } from "@tanstack/react-query"
const queryClient = new QueryClient()
+const db = new DbClient({ queryClient })
const listOptions = queryOptions({
queryKey: ["todos"],
@@ -84,18 +284,114 @@ const listOptions = queryOptions({
},
})
-const todosCollection = createCollection(
+const todosCollection = collectionOptions("todos", (client) =>
queryCollectionOptions({
+ id: "todos",
...listOptions,
queryFn: (context) => listOptions.queryFn!(context),
- queryClient,
+ queryClient: client.requireDependency("queryClient"),
getKey: (item) => item.id,
}),
)
+
+const todos = db.collection(todosCollection)
```
If `queryFn` is missing at runtime, `queryCollectionOptions` throws `QueryFnRequiredError`.
+### Initial Data
+
+Eager Query Collections support TanStack Query's `initialData` and
+`initialDataUpdatedAt` options. Initial data has the original Query response
+shape, is stored in the Query cache, and is immediately materialized as
+normalized collection rows. TanStack Query uses `initialDataUpdatedAt` together
+with `staleTime` to decide whether to fetch.
+
+```typescript
+const serverRenderedAt = Date.now()
+const initialTodos = [
+ { id: "1", title: "Write documentation" },
+ { id: "2", title: "Ship initial data support" },
+]
+
+const todosCollection = createCollection(
+ queryCollectionOptions({
+ queryKey: ["todos"],
+ queryFn: fetchTodos,
+ queryClient,
+ getKey: (todo) => todo.id,
+ initialData: initialTodos,
+ initialDataUpdatedAt: serverRenderedAt,
+ staleTime: 60_000,
+ }),
+)
+```
+
+An existing cached or hydrated Query response takes precedence over
+`initialData`. Query keys remain the cache identity: two collections using the
+same QueryClient and exact Query key observe one shared Query document, and a
+later collection's initializer does not replace it. Use distinct Query keys for
+independent documents.
+
+Initial data is supported only for eager collections. A collection-wide value
+cannot establish row membership for arbitrary on-demand predicates, ordering,
+limits, and offsets. For `syncMode: "on-demand"`, seed or hydrate the exact
+derived Query cache entries instead.
+
+If a stale initial response triggers a fetch, the initial rows remain available
+while it is in flight. A successful response reconciles them through the normal
+row ownership pipeline; an error retains the initial rows. Direct writes use the
+same Query cache-patching rules as fetched data, and a later successful server
+response may reconcile or replace those writes.
+
+### Selecting Rows from Wrapped Responses
+
+Many APIs return rows inside a response envelope that also contains metadata such as pagination cursors, totals, or request information. Use `select` to extract the row array that TanStack DB should materialize:
+
+```typescript
+interface TodosResponse {
+ items: Array<{ id: string; title: string }>
+ nextCursor?: string
+ total: number
+}
+
+const todosCollection = createCollection(
+ queryCollectionOptions({
+ queryKey: ["todos"],
+ queryFn: async (): Promise => {
+ const response = await fetch("/api/todos")
+ return response.json()
+ },
+ initialData: {
+ items: [{ id: "1", title: "Initial todo" }],
+ nextCursor: undefined,
+ total: 1,
+ },
+ select: (response) => response.items,
+ queryClient,
+ getKey: (item) => item.id,
+ }),
+)
+```
+
+`select` is a query-db-collection row extraction hook. It tells TanStack DB which rows to materialize while the TanStack Query cache keeps the original query response shape. In the example above, `queryClient.getQueryData(["todos"])` still returns the full `TodosResponse`, including `nextCursor` and `total`.
+
+The same projection applies to `initialData`: provide the complete response
+envelope, and Query Collection materializes the rows returned by `select` while
+preserving the envelope in the Query cache.
+
+This differs from TanStack Query's observer-level `select`: query-db-collection uses this option to bridge Query's response object into DB's normalized row store.
+
+Direct write utilities such as `writeInsert`, `writeUpdate`, and `writeDelete` make a best-effort attempt to update the matching row array inside wrapped Query cache entries while preserving wrapper metadata.
+
+This works automatically for simple wrappers such as:
+
+- `{ data: [...] }`
+- `{ items: [...] }`
+- `{ results: [...] }`
+
+Derived projections, such as `select: (response) => response.edges.map((edge) => edge.node)`, are read-side row extraction only. query-db-collection cannot generally reconstruct the original response envelope from updated rows. Refetch or invalidate the query if the wrapped cache must exactly reflect direct writes for a derived projection.
+
### Collection Options
- `id`: Unique identifier for the collection
@@ -113,6 +409,13 @@ If `queryFn` is missing at runtime, `queryCollectionOptions` throws `QueryFnRequ
The `meta` option allows you to pass additional metadata to your query function. By default, Query Collections automatically include `loadSubsetOptions` in the meta object, which contains filtering, sorting, and pagination options for on-demand queries.
+Treat `ctx.meta.loadSubsetOptions` and its nested request data as read-only.
+Do not edit expression nodes, ordering options, Dates, byte arrays, or membership
+arrays. Build separate API parameters instead. Core retains request data without
+cloning it; changing submitted data can make the request disagree with its cache
+key. To change a query constant, supply a new value rather than mutating the old
+one. Cancellation through the request's `AbortSignal` remains supported.
+
### Type-Safe Meta Access
The `ctx.meta.loadSubsetOptions` property is automatically typed as `LoadSubsetOptions` without requiring any additional imports or type assertions:
@@ -445,22 +748,99 @@ const todosCollection = createCollection(
todosCollection.insert({ text: "Buy milk", completed: false })
```
-### Example: Large Dataset Pagination
+### Server pagination with live queries
+
+`useLiveInfiniteQuery` in React, Vue, and Svelte grows a local ordered query
+window. It does not run TanStack Query's `InfiniteQueryObserver`. Query
+Collections use `QueryObserver`, so `queryFn` receives
+`meta.loadSubsetOptions`, not `pageParam`.
+
+The previously ignored `getNextPageParam` option has been removed. Delete it
+from your hook config; passing it at runtime now throws a clear error.
+`initialPageParam` labels result pages only. It does not set a remote offset
+or server cursor.
+
+For server loading, use `syncMode: 'on-demand'` and make `queryFn` fulfill the
+requested filter, order, offset, and limit. Use a deterministic total order
+(for example, a timestamp followed by a unique ID). The loader may request a
+prefix, a suffix, a tie group, or the full filtered source. A request is not
+necessarily one UI page: the hook fetches an extra row to determine
+`hasNextPage`. Returning one capped endpoint page can incorrectly make the
+query appear exhausted even when the server has more rows.
+
+#### Endpoints with fixed-size pages
+
+If your endpoint uses page numbers, drain enough server pages to fulfill each
+request. This example assumes a zero-based page API with a fixed size of 50.
+The endpoint must apply the supplied filters and sorts **before** pagination,
+keep a consistent ordered result while its pages are read, and return
+`nextPage: null` only when it has authoritatively exhausted that result.
+This example uses offset-based pagination. `api.listPosts` translates the full
+`where` expression and `orderBy` options into the endpoint's syntax, and rejects
+unsupported expressions. The separate `cursor` hints are deliberately unused;
+cursor-based adapters must handle those hints alongside `where`, not treat them
+as already included in it. See [QueryFn and Predicate Push-Down](#queryfn-and-predicate-push-down)
+for translation helpers. Do not drop predicates or filter after paginating:
+either changes the requested window.
```typescript
-// Load additional pages without refetching existing data
-const loadMoreTodos = async (page) => {
- const newTodos = await api.getTodos({ page, limit: 50 })
+import { createCollection } from '@tanstack/db'
+import { queryCollectionOptions } from '@tanstack/query-db-collection'
- // Add new items without affecting existing ones
- todosCollection.utils.writeBatch(() => {
- newTodos.forEach((todo) => {
- todosCollection.utils.writeInsert(todo)
- })
- })
-}
+type Post = { id: number; createdAt: number; title: string }
+const serverPageSize = 50
+
+const postsCollection = createCollection(
+ queryCollectionOptions({
+ queryKey: ['posts'],
+ queryClient,
+ syncMode: 'on-demand',
+ getKey: (post: Post) => post.id,
+ queryFn: async (ctx): Promise> => {
+ const { where, orderBy, offset = 0, limit } = ctx.meta?.loadSubsetOptions ?? {}
+ const skip = offset % serverPageSize
+ let page: number | null = Math.floor(offset / serverPageSize)
+ const gathered: Array = []
+
+ while (page !== null && (limit === undefined || gathered.length < skip + limit)) {
+ ctx.signal.throwIfAborted()
+ const response: { rows: Array; nextPage: number | null } =
+ await api.listPosts({
+ page,
+ pageSize: serverPageSize,
+ where,
+ orderBy,
+ signal: ctx.signal,
+ })
+ gathered.push(...response.rows)
+ page = response.nextPage
+ }
+
+ return gathered.slice(skip, limit === undefined ? undefined : skip + limit)
+ },
+ }),
+)
+
+// React example; the collection protocol is the same for Vue and Svelte.
+const { data, fetchNextPage, hasNextPage } = useLiveInfiniteQuery(
+ (q) => q.from({ post: postsCollection })
+ .orderBy(({ post }) => post.createdAt)
+ .orderBy(({ post }) => post.id),
+ { pageSize: 20 },
+)
```
+Reject failed requests instead of returning partial rows as success. An
+unlimited request must drain until the endpoint reports exhaustion. If the
+endpoint uses opaque cursors instead of page numbers, keep that cursor handling
+inside `queryFn` or its adapter; honoring a new offset may require starting at
+the beginning again. The hook does not maintain remote cursor history.
+
+Manually appending rows with `writeUpsert` is a separate, lower-level loading
+strategy. It does not make an eager `queryFn` incremental: a later successful
+refetch still replaces its complete state and can remove appended rows.
+`staleTime: Infinity` does not prevent explicit refetch or invalidation.
+
## Important Behaviors
### Full State Sync
diff --git a/docs/collections/query-initial-placeholder-data-design.md b/docs/collections/query-initial-placeholder-data-design.md
new file mode 100644
index 0000000000..9ee866f325
--- /dev/null
+++ b/docs/collections/query-initial-placeholder-data-design.md
@@ -0,0 +1,249 @@
+# Query Collection initial and placeholder data semantics
+
+## Status and scope
+
+This document defines the semantics for TanStack Query `initialData` and
+`placeholderData` at the `@tanstack/query-db-collection` boundary. It is the
+design follow-up for [RFC #1643](https://github.com/TanStack/db/issues/1643)
+and [issue #346](https://github.com/TanStack/db/issues/346). The accompanying
+implementation adds the approved eager `initialData` behavior without changing
+the persistence format.
+
+The adapter connects two different models:
+
+- TanStack Query owns a document cache and remote-query lifecycle.
+- TanStack DB owns normalized rows, local queries, and optimistic writes.
+- Query Collection projects a Query response into rows and records which Query
+ key owns each materialized row.
+
+`initialData` and `placeholderData` must not be treated as equivalent ways to
+provide an array. In Query Core 5.90.20, `initialData` initializes Query cache
+state with `status: "success"` and a `dataUpdatedAt` timestamp. By contrast,
+`placeholderData` is computed per observer only while Query state is pending;
+it produces an observer result with `isPlaceholderData: true` but is not stored
+in Query state.
+
+## Decision
+
+Support Query-owned `initialData` as an additive, eager-mode option. Materialize
+it immediately through the existing row extraction and ownership pipeline.
+Do not expose or materialize `placeholderData` in the first implementation.
+
+Query Collection can already materialize data that was seeded or hydrated into
+the QueryClient before the observer is created, and QueryClient defaults can
+already provide `initialData` indirectly. That is useful existing behavior, but
+it does not close the configuration gap. Applications commonly share one
+QueryClient across many Query Collections: a client-wide default is too broad,
+while imperative `setQueryData` requires coordinating collection construction,
+exact Query keys, and initialization elsewhere. The additive field supplies a
+collection-local declaration while leaving Query as the cache authority.
+
+The minimal additive API is:
+
+```ts
+initialData?: TQueryData | (() => TQueryData)
+initialDataUpdatedAt?: number | (() => number | undefined)
+```
+
+These remain flat top-level fields. Their types describe the original Query
+response, not the extracted row array. Consequently, wrapped responses use the
+same adapter `select` as network responses:
+
+```ts
+queryCollectionOptions({
+ queryKey: ["todos"],
+ queryFn: fetchTodos,
+ initialData: { items: serverTodos, nextCursor: null },
+ select: (response) => response.items,
+ // ...
+})
+```
+
+Query keys still define cache identity. If two Query Collections on the same
+QueryClient use the same exact key, they observe one shared Query document;
+`initialData` initializes that document only when it does not already exist.
+Collection-local configuration does not create collection-local cache data, and
+later observers must not replace an existing document with their initializer.
+Collections that require independent initial documents must use distinct keys.
+
+This initial API is limited to `syncMode: "eager"`. A single configuration-level
+value cannot lawfully initialize an open-ended family of on-demand subset keys,
+and Query's `initialData` function receives no query key or subset context.
+Applications that already know data for an exact on-demand key should seed or
+hydrate that Query cache entry instead. A future subset-aware initializer would
+need an explicit key/subset argument and a separate design.
+
+`placeholderData` remains Query UI vocabulary. A DB collection has no
+observer-local result surface: materializing a placeholder would make it visible
+to every DB query and mutation, assign it row ownership, and potentially persist
+it. Callers should render placeholders in the consuming UI. A future opt-in
+temporary-row feature, if needed, should be a DB feature with explicit provenance
+and lifecycle rather than Query's `placeholderData` option.
+
+## Authority model
+
+"Authoritative" has two dimensions here. Query owns the authoritative document
+for a Query key, while DB owns the current normalized row state. A server result
+is the newest remote snapshot, but local optimistic transactions can temporarily
+overlay its rows.
+
+| Phase | Query document authority | Materialized row authority | Consequence |
+| --- | --- | --- | --- |
+| No cached data | None | Existing DB/persisted rows, if any | The collection waits for Query; absence is not an empty result. |
+| Initial data | Query cache `initialData` | Its projected rows, subject to normal local overlays | It is a real cached snapshot, not temporary presentation data. |
+| Fetch/refetch in flight | Existing Query document | Existing rows | Loading does not clear rows or ownership. |
+| Server success | Returned response replaces the Query document | Projected server rows reconcile the owning Query key | Missing rows lose this Query owner's lease; shared rows remain. |
+| Fetch/refetch error | Last successful Query document | Existing rows | An error does not retract initial or previously fetched rows. |
+| Placeholder presentation | No Query document | No rows | Placeholder data is never passed to DB. |
+
+`initialDataUpdatedAt` and `staleTime` remain Query-owned. They decide whether a
+fetch starts; the adapter does not reproduce their freshness calculation. An
+initial value is therefore a seed snapshot with ordinary Query authority, not a
+weaker class of row waiting to be promoted. The first successful server result
+reconciles it through the same path as any later refetch.
+
+## Behavior matrix
+
+| Concern | `initialData` | `placeholderData` |
+| --- | --- | --- |
+| Query cache | Stored as successful Query data | Not stored; observer-only |
+| Existing cache entry | Existing cached/hydrated data wins; `initialData` is not reapplied | Not applicable |
+| DB materialization | Immediate in eager mode | Never |
+| Wrapped response | Adapter `select(initialData)` extracts rows; original envelope stays cached | Unsupported |
+| Function value | Evaluated by Query once when the Query is created | Not forwarded or evaluated by the adapter |
+| Ownership | The Query key owns projected rows exactly like a server success | No ownership |
+| Overlapping subsets | Not applicable to the initial eager-only API | Not applicable |
+| Ready state | Initial successful result can make the collection ready synchronously | Cannot make the collection ready |
+| Refetch success | Reconciles additions, updates, and removals normally | N/A |
+| Refetch error | Initial rows and ownership remain; error state is reported | N/A |
+| Cancellation/cleanup | Existing ownership cleanup rules apply; a cancelled fetch does not retract the cached seed | No rows to clean up |
+| Query cache GC/unload | Existing Query-to-row ownership and persisted-retention rules apply | No effect |
+| Query dehydration | Query owns persistence of the initial response | Never persisted |
+| DB persistence/hydration | Rows and owner metadata use the existing format; no provenance tag is added | Never persisted or hydrated |
+| Direct writes before server success | Allowed under the existing write rules below | No target rows exist |
+| QueryClient defaults | Supported for eager `initialData`; see compatibility guard below | Must be suppressed at this adapter boundary |
+
+## Select and writes
+
+Adapter `select` remains a one-way row extractor. It is applied identically to
+initial and network responses, and TanStack Query retains the original response
+shape. Query observer-level `select` remains unsupported.
+
+Direct writes before the first server result follow the current authority rule:
+they update DB immediately and may patch Query cache only when the reverse update
+is lawful. A raw array can be replaced. For a wrapped response, the existing
+best-effort patch is lawful only when `select` returns an array property of the
+cached object by reference, allowing the wrapper to be preserved. A derived
+projection such as `response.edges.map(...)` has no general reverse projection;
+the adapter must leave that Query document unchanged and rely on invalidate or
+refetch. It must never fabricate an envelope around rows.
+
+The next successful remote response remains authoritative for that Query key and
+may overwrite a direct cache patch or normalized row value. Mutation handlers and
+optimistic transaction barriers retain their existing semantics.
+
+## Ownership, persistence, and transitions
+
+Initial rows use the existing `queryToRows` and `rowToQueries` relationship. No
+`seed`, `temporary`, or `placeholder` bit is added to a row. This keeps these
+invariants intact:
+
+1. A successful result, whether initial or fetched, is a complete snapshot for
+ its Query key.
+2. A row is deleted when a snapshot omits it only if no other Query key owns it.
+3. Unload, cache GC, and collection cleanup remove only the relevant ownership.
+4. A failed or cancelled fetch cannot turn the last successful snapshot into an
+ empty snapshot.
+5. Persistence records only Query data and the existing row ownership metadata;
+ observer-only presentation state is never persisted.
+
+The expected transitions are:
+
+- **Initial, fresh:** materialize and become ready; do not fetch until Query's
+ normal freshness triggers say to do so.
+- **Initial, stale:** materialize and become ready while Query fetches; success
+ reconciles the same ownership, and error retains the seed.
+- **Loading without data:** keep the collection's prior independently owned or
+ hydrated rows; do not infer an empty result.
+- **Placeholder to loading/success/error:** the placeholder is UI-only. DB sees
+ no transition until success; error leaves DB unchanged.
+- **Cleanup before network completion:** existing cancellation and readiness
+ listener cleanup apply. A late result must not mutate a cleaned-up collection.
+
+## Defaults and compatibility guard
+
+Query Collection currently constructs a `QueryObserver`, so QueryClient defaults
+can contain semantic fields even when Query Collection does not expose them.
+Implementation must explicitly enforce this design after Query defaults are
+resolved:
+
+- reject an explicitly configured `initialData` in on-demand mode;
+- prevent default `initialData` from initializing on-demand subset observers;
+- prevent explicit or default `placeholderData` from reaching all Query
+ Collection observers;
+- continue to let omitted eager `initialData` and `initialDataUpdatedAt` inherit
+ QueryClient defaults;
+- never copy a function-valued initializer into adapter metadata or persisted
+ ownership metadata. Query Core may own it as an option and stores only its
+ evaluated data in Query state.
+
+Silently materializing default placeholder data would violate the public
+compatibility table even before a top-level field is added. The guard is therefore
+a correctness fix, not support for placeholder semantics.
+
+Other Query-owned options keep their current classification. Query key creation,
+adapter `select`, subscriptions, `notifyOnChangeProps`, and structural sharing
+remain adapter-owned or reinterpreted. No nested `queryOptions`, runtime binding
+API, subset deduplication, or lease manager is introduced by this design.
+
+## Rejected designs
+
+- **Forward both options mechanically.** `result.isSuccess` is true for a
+ placeholder observer result, so the current success handler would normalize
+ presentation-only data and give it durable-looking ownership.
+- **Tag placeholder rows and later promote or delete them.** Tags would have to
+ survive collisions with real rows, overlapping observers, local writes,
+ unload, GC, persistence, and hydration. Query's observer-local placeholder has
+ no collection-wide lifetime that can drive those transitions safely.
+- **Treat initial rows as unowned temporary rows.** Query considers initial data
+ real cached data. Bypassing normal ownership would leak rows or allow cleanup
+ of one Query to remove rows still represented by another.
+- **Seed every on-demand key from one value.** A collection-level initializer
+ cannot prove membership in arbitrary predicate/order/limit subsets.
+- **Create a wrapped response from selected rows.** A read projection is not an
+ inverse. Fabricating metadata, cursors, or edges corrupts Query cache meaning.
+- **Persist initializer functions.** Functions are not structured-clone safe and
+ are runtime configuration, not data.
+
+## Implementation and test sequence
+
+Each behavior PR should begin with the named failing or characterization tests.
+
+1. **Guard the existing boundary.** Add focused tests proving that explicit and
+ QueryClient-default `placeholderData` never materialize, never mark the
+ collection ready, and never survive dehydration as data. Characterize current
+ default `initialData` behavior in eager and on-demand modes. Then suppress
+ placeholder and on-demand initialization when constructing observers.
+2. **Add eager initial data.** Add the two flat typed fields and forward only
+ defined values so QueryClient defaults remain intact. Test static and function
+ values, fresh versus stale timestamps, synchronous readiness, fetch error,
+ cancellation, cleanup, an explicit on-demand configuration error, multiple
+ collections on one QueryClient, and same-key first-initializer-wins behavior.
+3. **Lock projection and writes.** Test raw arrays, direct-property wrapped
+ responses, and derived projections. Verify the full initial envelope remains
+ in Query cache, lawful direct writes preserve it, unlawful reverse projection
+ does not fabricate one, and server success reconciles rows.
+4. **Lock ownership.** Test initial-to-server row removal, overlapping ownership
+ with externally hydrated/persisted rows, GC/unload, remount within `gcTime`,
+ and late notification after cleanup. Reuse the existing ownership machinery;
+ do not add a second seed ownership map.
+5. **Lock persistence.** Dehydrate and `structuredClone` initial raw and wrapped
+ data; hydrate Query and DB state; verify ownership reconciliation and that no
+ function appears in Query metadata or adapter persistence metadata.
+6. **Document the shipped API.** Move the settled behavior into the Query Options,
+ row extraction, direct writes, on-demand, and persistence sections of the user
+ guide. Update RFC #1643 and close or narrow #346 only after these contracts ship.
+
+Placeholder materialization, subset-aware initialization, and a lawful general
+reverse projection are separate future proposals. None is a prerequisite for the
+minimal eager `initialData` API.
diff --git a/docs/collections/rxdb-collection.md b/docs/collections/rxdb-collection.md
index d139ddc7f4..a1ba038c89 100644
--- a/docs/collections/rxdb-collection.md
+++ b/docs/collections/rxdb-collection.md
@@ -2,8 +2,6 @@
title: RxDB Collection
---
-# RxDB Collection
-
RxDB collections provide seamless integration between TanStack DB and [RxDB](https://rxdb.info), enabling automatic synchronization between your in-memory TanStack DB collections and RxDB's local-first database. Giving you offline-ready persistence, and powerful sync capabilities with a wide range of backends.
diff --git a/docs/collections/trailbase-collection.md b/docs/collections/trailbase-collection.md
index 938e714a52..ea1c5ead36 100644
--- a/docs/collections/trailbase-collection.md
+++ b/docs/collections/trailbase-collection.md
@@ -2,8 +2,6 @@
title: TrailBase Collection
---
-# TrailBase Collection
-
TrailBase collections provide seamless integration between TanStack DB and [TrailBase](https://trailbase.io), enabling real-time data synchronization with TrailBase's self-hosted application backend.
## Overview
@@ -194,11 +192,13 @@ export const todosCollection = createCollection(
// Use in component
function TodoList() {
- const { data: todos } = useLiveQuery((q) =>
- q.from({ todo: todosCollection })
- .where(({ todo }) => not(todo.completed))
- .orderBy(({ todo }) => todo.created_at, 'desc')
- )
+ const { data: todos } = useLiveQuery({
+ query: (q) =>
+ q
+ .from({ todo: todosCollection })
+ .where(({ todo }) => not(todo.completed))
+ .orderBy(({ todo }) => todo.created_at, 'desc'),
+ })
const addTodo = (text: string) => {
todosCollection.insert({
diff --git a/docs/community/resources.md b/docs/community/resources.md
index 781c11963e..86ed35d53d 100644
--- a/docs/community/resources.md
+++ b/docs/community/resources.md
@@ -3,8 +3,6 @@ title: Community Resources
id: community-resources
---
-# Community Resources
-
This page contains a curated list of community-created packages, tools, and resources that extend or complement TanStack DB.
## Community Packages
diff --git a/docs/config.json b/docs/config.json
index 09ca7c62ba..94a08ef0fd 100644
--- a/docs/config.json
+++ b/docs/config.json
@@ -30,6 +30,10 @@
"label": "Live Queries",
"to": "guides/live-queries"
},
+ {
+ "label": "SSR and Hydration",
+ "to": "guides/ssr"
+ },
{
"label": "Mutations",
"to": "guides/mutations"
diff --git a/docs/framework/angular/reference/functions/injectLiveQuery.md b/docs/framework/angular/reference/functions/injectLiveQuery.md
index 5eb790a543..efe20157a4 100644
--- a/docs/framework/angular/reference/functions/injectLiveQuery.md
+++ b/docs/framework/angular/reference/functions/injectLiveQuery.md
@@ -11,7 +11,7 @@ title: injectLiveQuery
function injectLiveQuery(options): InjectLiveQueryResult;
```
-Defined in: [index.ts:89](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L89)
+Defined in: [index.ts:93](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L93)
### Type Parameters
@@ -45,7 +45,7 @@ Defined in: [index.ts:89](https://github.com/TanStack/db/blob/main/packages/angu
function injectLiveQuery(options): InjectLiveQueryResult;
```
-Defined in: [index.ts:99](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L99)
+Defined in: [index.ts:103](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L103)
### Type Parameters
@@ -79,7 +79,7 @@ Defined in: [index.ts:99](https://github.com/TanStack/db/blob/main/packages/angu
function injectLiveQuery(queryFn): InjectLiveQueryResult;
```
-Defined in: [index.ts:109](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L109)
+Defined in: [index.ts:113](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L113)
### Type Parameters
@@ -103,7 +103,7 @@ Defined in: [index.ts:109](https://github.com/TanStack/db/blob/main/packages/ang
function injectLiveQuery(queryFn): InjectLiveQueryResult;
```
-Defined in: [index.ts:112](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L112)
+Defined in: [index.ts:116](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L116)
### Type Parameters
@@ -127,7 +127,7 @@ Defined in: [index.ts:112](https://github.com/TanStack/db/blob/main/packages/ang
function injectLiveQuery(config): InjectLiveQueryResult;
```
-Defined in: [index.ts:117](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L117)
+Defined in: [index.ts:121](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L121)
### Type Parameters
@@ -151,7 +151,7 @@ Defined in: [index.ts:117](https://github.com/TanStack/db/blob/main/packages/ang
function injectLiveQuery(liveQueryCollection): InjectLiveQueryResultWithCollection;
```
-Defined in: [index.ts:121](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L121)
+Defined in: [index.ts:125](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L125)
### Type Parameters
@@ -183,7 +183,7 @@ Defined in: [index.ts:121](https://github.com/TanStack/db/blob/main/packages/ang
function injectLiveQuery(liveQueryCollection): InjectLiveQueryResultWithSingleResultCollection;
```
-Defined in: [index.ts:129](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L129)
+Defined in: [index.ts:133](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L133)
### Type Parameters
diff --git a/docs/framework/angular/reference/interfaces/InjectLiveQueryResult.md b/docs/framework/angular/reference/interfaces/InjectLiveQueryResult.md
index 066d6b5bab..cf4d8f3cee 100644
--- a/docs/framework/angular/reference/interfaces/InjectLiveQueryResult.md
+++ b/docs/framework/angular/reference/interfaces/InjectLiveQueryResult.md
@@ -5,7 +5,7 @@ title: InjectLiveQueryResult
# Interface: InjectLiveQueryResult\
-Defined in: [index.ts:32](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L32)
+Defined in: [index.ts:36](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L36)
The result of calling `injectLiveQuery`.
Contains reactive signals for the query state and data.
@@ -27,7 +27,7 @@ collection: Signal<
| null>;
```
-Defined in: [index.ts:38](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L38)
+Defined in: [index.ts:42](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L42)
A signal containing the underlying collection instance (null for disabled queries)
@@ -39,7 +39,7 @@ A signal containing the underlying collection instance (null for disabled querie
data: Signal>;
```
-Defined in: [index.ts:36](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L36)
+Defined in: [index.ts:40](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L40)
A signal containing the results as an array, or single result for findOne queries
@@ -51,7 +51,7 @@ A signal containing the results as an array, or single result for findOne querie
isCleanedUp: Signal;
```
-Defined in: [index.ts:54](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L54)
+Defined in: [index.ts:58](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L58)
A signal indicating whether the collection has been cleaned up
@@ -63,7 +63,7 @@ A signal indicating whether the collection has been cleaned up
isError: Signal;
```
-Defined in: [index.ts:52](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L52)
+Defined in: [index.ts:56](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L56)
A signal indicating whether the collection has an error
@@ -75,7 +75,7 @@ A signal indicating whether the collection has an error
isIdle: Signal;
```
-Defined in: [index.ts:50](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L50)
+Defined in: [index.ts:54](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L54)
A signal indicating whether the collection is idle
@@ -87,7 +87,7 @@ A signal indicating whether the collection is idle
isLoading: Signal;
```
-Defined in: [index.ts:46](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L46)
+Defined in: [index.ts:50](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L50)
A signal indicating whether the collection is currently loading
@@ -99,7 +99,7 @@ A signal indicating whether the collection is currently loading
isReady: Signal;
```
-Defined in: [index.ts:48](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L48)
+Defined in: [index.ts:52](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L52)
A signal indicating whether the collection is ready
@@ -111,7 +111,7 @@ A signal indicating whether the collection is ready
state: Signal