Skip to content

feat(query-db-collection): add manual write methods for direct state updates #303

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: main
Choose a base branch
from

Conversation

KyleAMathews
Copy link
Collaborator

@KyleAMathews KyleAMathews commented Jul 22, 2025

Description

This PR adds manual write methods to @tanstack/query-db-collection to support direct state updates from external sources like WebSockets, server-sent events, or mutation responses.

Problem

Currently, @tanstack/query-db-collection has two limitations:

  1. Performance: Automatic refetching after mutations is inefficient for large datasets
  2. Real-time updates: No way to manually sync data received through WebSockets or other channels

Solution

Added new write* methods that bypass the normal optimistic update flow and directly write to collection state:

  • writeInsert(data) - Write insert directly to collection
  • writeUpdate(data) - Write update directly to collection
  • writeDelete(keys) - Write delete directly to collection
  • writeUpsert(data) - Write upsert directly to collection
  • writeBatch(operations) - Write batch operations directly

The write prefix follows established patterns from Apollo Client (writeQuery, writeFragment) and clearly indicates these methods bypass the normal persistence flow.

Example Usage

// WebSocket integration
ws.on('todo:created', (todo) => {
  todosCollection.utils.writeInsert(todo)
})

ws.on('todo:updated', (todo) => {
  todosCollection.utils.writeUpdate(todo)
})

ws.on('todo:deleted', (id) => {
  todosCollection.utils.writeDelete(id)
})

// Batch updates
ws.on('todos:sync', (changes) => {
  todosCollection.utils.writeBatch(
    changes.map(change => ({
      type: change.op,
      data: change.data
    }))
  )
})

// Mutation response optimization
const mutation = useMutation({
  mutationFn: createTodo,
  onSuccess: (newTodo) => {
    // Skip refetch by directly writing the response
    todosCollection.utils.writeInsert(newTodo)
  }
})

Implementation Details

  • Extracted write logic into a separate manual-sync.ts module for better organization
  • Unified operation handling eliminates code duplication
  • Proper TypeScript types throughout
  • Comprehensive test coverage (98.5%)
  • All write operations update the TanStack Query cache after committing

Breaking Changes

None - this is a new feature that doesn't affect existing APIs.

Testing

Added comprehensive test suite covering:

  • All write operations (insert, update, delete, upsert, batch)
  • Error handling and validation
  • Query cache synchronization
  • Edge cases and batch operations

Closes #294

Copy link

changeset-bot bot commented Jul 22, 2025

🦋 Changeset detected

Latest commit: 650d993

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@tanstack/query-db-collection Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

- Remove unused Collection import
- Fix SyncConfig type parameter issues by removing explicit type annotations
- Fix queryClient.setQueryData type issues with proper casting
- All build errors resolved, package builds successfully

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
Copy link

pkg-pr-new bot commented Jul 22, 2025

More templates

@tanstack/db

npm i https://pkg.pr.new/@tanstack/db@303

@tanstack/db-ivm

npm i https://pkg.pr.new/@tanstack/db-ivm@303

@tanstack/electric-db-collection

npm i https://pkg.pr.new/@tanstack/electric-db-collection@303

@tanstack/query-db-collection

npm i https://pkg.pr.new/@tanstack/query-db-collection@303

@tanstack/react-db

npm i https://pkg.pr.new/@tanstack/react-db@303

@tanstack/solid-db

npm i https://pkg.pr.new/@tanstack/solid-db@303

@tanstack/svelte-db

npm i https://pkg.pr.new/@tanstack/svelte-db@303

@tanstack/trailbase-db-collection

npm i https://pkg.pr.new/@tanstack/trailbase-db-collection@303

@tanstack/vue-db

npm i https://pkg.pr.new/@tanstack/vue-db@303

commit: 650d993

Copy link
Contributor

github-actions bot commented Jul 22, 2025

Size Change: 0 B

Total Size: 58.1 kB

ℹ️ View Unchanged
Filename Size
./packages/db/dist/esm/change-events.js 1.13 kB
./packages/db/dist/esm/collection.js 9.8 kB
./packages/db/dist/esm/deferred.js 230 B
./packages/db/dist/esm/errors.js 2.98 kB
./packages/db/dist/esm/index.js 1.51 kB
./packages/db/dist/esm/indexes/auto-index.js 689 B
./packages/db/dist/esm/indexes/base-index.js 605 B
./packages/db/dist/esm/indexes/btree-index.js 1.47 kB
./packages/db/dist/esm/indexes/lazy-index.js 1.25 kB
./packages/db/dist/esm/local-only.js 827 B
./packages/db/dist/esm/local-storage.js 2.03 kB
./packages/db/dist/esm/optimistic-action.js 294 B
./packages/db/dist/esm/proxy.js 4.19 kB
./packages/db/dist/esm/query/builder/functions.js 575 B
./packages/db/dist/esm/query/builder/index.js 3.67 kB
./packages/db/dist/esm/query/builder/ref-proxy.js 890 B
./packages/db/dist/esm/query/compiler/evaluators.js 1.48 kB
./packages/db/dist/esm/query/compiler/expressions.js 631 B
./packages/db/dist/esm/query/compiler/group-by.js 2.03 kB
./packages/db/dist/esm/query/compiler/index.js 1.74 kB
./packages/db/dist/esm/query/compiler/joins.js 1.56 kB
./packages/db/dist/esm/query/compiler/order-by.js 703 B
./packages/db/dist/esm/query/compiler/select.js 655 B
./packages/db/dist/esm/query/ir.js 318 B
./packages/db/dist/esm/query/live-query-collection.js 2.45 kB
./packages/db/dist/esm/query/optimizer.js 2.44 kB
./packages/db/dist/esm/SortedMap.js 1.24 kB
./packages/db/dist/esm/transactions.js 2.28 kB
./packages/db/dist/esm/utils.js 419 B
./packages/db/dist/esm/utils/btree.js 5.93 kB
./packages/db/dist/esm/utils/comparison.js 539 B
./packages/db/dist/esm/utils/index-optimization.js 1.62 kB

compressed-size-action::db-package-size

Copy link
Contributor

github-actions bot commented Jul 22, 2025

Size Change: 0 B

Total Size: 1.05 kB

ℹ️ View Unchanged
Filename Size
./packages/react-db/dist/esm/index.js 152 B
./packages/react-db/dist/esm/useLiveQuery.js 902 B

compressed-size-action::react-db-package-size

@jmarbutt
Copy link

This looks really good to me, I am excited to see this in and put it through the paces a little more.

Copy link
Contributor

@kevin-dp kevin-dp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not happy with the code quality of this PR. It seems to overly complicate certain aspects, duplicate code, etc. I left some comments explaining how we can improve the quality of the code.

I have one major comment though about the implementation of the different sync methods (syncInsert, syncUpdate, syncUpsert, syncDelete, syncBatch). The syncBatch function repeats quite some code from the individual syncInsert/syncUpdate/... methods. We should be able to refactor the code such that syncBatch simply does:

BEGIN
batch.foreach(op => dispatch(op))
COMMIT

The dispatch function just checks the type of the operation and calls syncInsert/syncUpdate/syncUpsert/syncDelete. Currently, those functions also call BEGIN and COMMIT so that's why we don't do that. However this can be solved easily by introducing applyInsert/applyUpdate/applyUpsert/applyDelete methods which apply the operation (without calling BEGIN and COMMIT). The dispatch function would then dispatch to those. And the public method would wrap it in BEGIN ... COMMIT.

Combine that with my suggestion of a single public sync method and you get quite an elegant implementation:

sync(op) {
  BEGIN
  dispatch(op)
  COMMIT
}

syncBatch(ops) {
  BEGIN
  ops.foreach(op => dispatch(op))
  COMMIT
}

KyleAMathews and others added 4 commits July 30, 2025 15:44
- Renamed syncInsert → writeInsert
- Renamed syncUpdate → writeUpdate
- Renamed syncDelete → writeDelete
- Renamed syncUpsert → writeUpsert
- Renamed syncBatch → writeBatch

The 'write' prefix better communicates that these methods directly write to collection state, bypassing the normal optimistic update flow. This follows established patterns from Apollo Client (writeQuery, writeFragment) and avoids confusion with synchronous/asynchronous operations.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
@KyleAMathews KyleAMathews changed the title feat: add manual sync methods to QueryCollectionUtils feat(query-db-collection): add manual write methods for direct state updates Jul 30, 2025
@KyleAMathews
Copy link
Collaborator Author

@kevin-dp give it another look please!

Updated changeset description to use the new writeX method names instead of syncX methods.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add Manual Sync Updates API to @tanstack/query-db-collection
3 participants