Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fix-query-cache-scope-invalidation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/query-db-collection': patch
---

Keep on-demand Query cache ownership and post-write readiness isolated across collections, co-owners, deferred cleanup, errors, and custom query hashes. Active enabled scopes revalidate from post-write provider results, while inactive collection-owned entries are removed without disturbing unrelated or foreign-observed Queries.
26 changes: 14 additions & 12 deletions docs/collections/query-collection.md
Original file line number Diff line number Diff line change
Expand Up @@ -340,9 +340,9 @@ 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.
row ownership pipeline; an error retains the initial rows. Direct writes patch
the eager Query cache in place. On-demand direct writes revalidate scoped
entries as described below.

### Selecting Rows from Wrapped Responses

Expand Down Expand Up @@ -382,7 +382,7 @@ 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.
In eager mode, 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. In on-demand mode, they revalidate active scoped queries and remove inactive or disabled cache entries instead of patching them with the full collection snapshot.

This works automatically for simple wrappers such as:

Expand Down Expand Up @@ -599,7 +599,7 @@ The collection provides these utility methods via `collection.utils`:

## Direct Writes

Direct writes are intended for scenarios where the normal query/mutation flow doesn't fit your needs. They allow you to write directly to the synced data store, bypassing the optimistic update system and query refetch mechanism.
Direct writes are intended for scenarios where the normal query/mutation flow doesn't fit your needs. They write directly to the synced data store and bypass the optimistic update system. Their Query cache behavior depends on the collection's sync mode.

### Understanding the Data Stores

Expand All @@ -615,7 +615,7 @@ Normal collection operations (insert, update, delete) create optimistic mutation
- Rolled back automatically if the server request fails
- Replaced with server data when the query refetches

Direct writes bypass this system entirely and write directly to the synced data store, making them ideal for handling real-time updates from alternative sources.
Direct writes bypass this system entirely and write directly to the synced data store, making them useful for handling real-time updates from alternative sources. Active on-demand queries still refetch so each scoped cache remains authoritative for its own request.

### When to Use Direct Writes

Expand Down Expand Up @@ -654,9 +654,9 @@ These operations:

- Write directly to the synced data store
- Do NOT create optimistic mutations
- Do NOT trigger automatic query refetches
- Update the TanStack Query cache immediately
- Are immediately visible in the UI
- In eager mode, update the full-result TanStack Query cache in place without refetching
- In on-demand mode, refetch active enabled queries and remove inactive or disabled cache entries

### Batch Operations

Expand Down Expand Up @@ -933,13 +933,15 @@ This pattern allows you to:

### Direct Writes and Query Sync

Direct writes update the collection immediately and also update the TanStack Query cache. However, they do not prevent the normal query sync behavior. If your `queryFn` returns data that conflicts with your direct writes, the query data will take precedence.
Direct writes update the collection immediately. In eager mode, they also patch the full-result TanStack Query cache in place.

In on-demand mode, each Query cache entry may represent a different predicate, order, limit, or offset. A full collection snapshot cannot safely replace those scoped results. Direct writes therefore refetch active enabled queries and remove inactive or disabled entries. A successful `queryFn` result remains authoritative and may reconcile or replace a direct write.

To handle this properly:

1. Use `{ refetch: false }` in your persistence handlers when using direct writes
2. Set appropriate `staleTime` to prevent unnecessary refetches
3. Design your `queryFn` to be aware of incremental updates (e.g., only fetch new data)
1. Use `{ refetch: false }` in persistence handlers to avoid the handler's additional refetch after a direct write. On-demand cache revalidation still runs.
2. Make sure an on-demand `queryFn` returns the current server result for its pushed-down predicate, order, limit, and offset.
3. Use eager mode when direct writes must update one complete cached result without a network request.

## Complete Direct Write API Reference

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ id: QueryCollectionUtils
title: QueryCollectionUtils
---

Defined in: [packages/query-db-collection/src/query.ts:261](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L261)
Defined in: [packages/query-db-collection/src/query.ts:262](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L262)

Utility methods available on Query Collections for direct writes and manual operations.
Direct writes bypass the normal query/mutation flow and write directly to the synced data store.
Direct writes bypass optimistic mutations and write to the synced data store.
Eager collections patch Query cache; on-demand collections revalidate scoped entries.

## Extends

Expand Down Expand Up @@ -185,7 +186,7 @@ writeBatch: (callback) => void;

Defined in: [packages/query-db-collection/src/query.ts:278](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L278)

Execute multiple write operations as a single atomic batch to the synced data store
Execute direct writes as one atomic batch, then update or revalidate the Query cache

#### Parameters

Expand All @@ -207,7 +208,7 @@ writeDelete: (keys) => void;

Defined in: [packages/query-db-collection/src/query.ts:274](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L274)

Delete one or more items directly from the synced data store without triggering a query refetch or optimistic update
Delete items without an optimistic update. On-demand queries revalidate their scoped cache entries.

#### Parameters

Expand All @@ -229,7 +230,7 @@ writeInsert: (data) => void;

Defined in: [packages/query-db-collection/src/query.ts:270](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L270)

Insert one or more items directly into the synced data store without triggering a query refetch or optimistic update
Insert items without an optimistic update. On-demand queries revalidate their scoped cache entries.

#### Parameters

Expand All @@ -251,7 +252,7 @@ writeUpdate: (updates) => void;

Defined in: [packages/query-db-collection/src/query.ts:272](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L272)

Update one or more items directly in the synced data store without triggering a query refetch or optimistic update
Update items without an optimistic update. On-demand queries revalidate their scoped cache entries.

#### Parameters

Expand All @@ -273,7 +274,7 @@ writeUpsert: (data) => void;

Defined in: [packages/query-db-collection/src/query.ts:276](https://github.com/TanStack/db/blob/main/packages/query-db-collection/src/query.ts#L276)

Insert or update one or more items directly in the synced data store without triggering a query refetch or optimistic update
Insert or update items without an optimistic update. On-demand queries revalidate their scoped cache entries.

#### Parameters

Expand Down
12 changes: 8 additions & 4 deletions packages/query-db-collection/src/manual-sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export interface SyncContext<
* Handles both direct array caches and wrapped response formats (when `select` is used).
* If not provided, falls back to directly setting the cache with the raw array.
*/
updateCacheData?: (items: Array<TRow>) => void
updateCacheData?: (getItems: () => Array<TRow>) => void
}

interface NormalizedOperation<
Expand Down Expand Up @@ -221,12 +221,16 @@ export function performWriteOperations<
ctx.commit()

// Update query cache after successful commit
const updatedData = Array.from(ctx.collection._state.syncedData.values())
if (ctx.updateCacheData) {
ctx.updateCacheData(updatedData)
ctx.updateCacheData(() =>
Array.from(ctx.collection._state.syncedData.values()),
)
} else {
// Fallback: directly set the cache with raw array (for non-Query Collection consumers)
ctx.queryClient.setQueryData(ctx.queryKey, updatedData)
ctx.queryClient.setQueryData(
ctx.queryKey,
Array.from(ctx.collection._state.syncedData.values()),
)
}
}

Expand Down
Loading
Loading