Skip to content

fix(db): preserve mutation handler key and utility types - #1849

Merged
KyleAMathews merged 4 commits into
mainfrom
oracle-transaction-sync-types
Sep 18, 2026
Merged

KyleAMathews merged 4 commits into
mainfrom
oracle-transaction-sync-types

Conversation

@KyleAMathews

@KyleAMathews KyleAMathews commented Sep 18, 2026

Copy link
Copy Markdown
Collaborator

Mutation handlers now preserve their collection's exact key and utility types through nested transaction mutations, and Query/Electric utility namespaces reject helpers that do not exist at runtime. This is a type-only correction: emitted runtime JavaScript is byte-identical.

Root cause

Three type boundaries independently erased information:

  • PendingMutation.key was any.
  • TransactionWithMutations retained the row and operation but discarded the concrete collection, so nested mutations lost their key and adapter utilities.
  • CollectionImpl.utils widened utilities to a generic record, while Query and Electric utility interfaces inherited an open string index signature. That made nonexistent cross-adapter helpers type-check as any.

The result was an inconsistent handler contract: the sibling collection parameter could be precise while transaction.mutations[n].collection was not.

Approach

  • Derive each mutation key from its collection type and carry the concrete collection through TransactionWithMutations and all insert/update/delete handler parameters.
  • Preserve TUtils on CollectionImpl, including requiring the corresponding runtime utility object when callers explicitly claim a closed, non-empty utility namespace.
  • Make Query and Electric utility interfaces closed and thread Query utilities through its collection config.
  • Derive Query wrapper parameters from the wrapped handlers to avoid duplicating this generic chain.
  • Add a dedicated mutation-handler type oracle and strengthen the existing Query, Electric, and PowerSync type owners.

Key invariants

  • A direct mutation handler's nested mutations have the same row, key, collection, and utilities as its sibling collection parameter.
  • Branded string and numeric keys never degrade to any.
  • A collection exposes only utilities installed by its adapter: Query has refetch, Electric has awaitTxId, and PowerSync has getMeta.
  • Claiming a concrete utility namespace on CollectionImpl requires a matching runtime utility object.
  • transaction.isPersisted.promise remains the existing local transaction-settlement receipt; it is not redefined as backend confirmation.

Non-goals

  • No new transaction status, confirmation hook, write receipt, or other feature-request contract.
  • No runtime mutation, synchronization, or persistence behavior changes.
  • No change to the historical Collection.update key-input compatibility shape.
  • No broad tightening of unrelated collection-manager variance.
  • No redesign of default key inference or ElectricCollectionConfig key generics; that remains separate design work.

Trade-offs

TransactionWithMutations gains a collection generic so precise information can cross the handler boundary. PendingMutation.key now follows the collection's declared key type instead of any; collections using the default key type—including current Electric options—expose string | number, while callers can declare TKey explicitly or use schema inference for a narrower key. This narrowing and the closed adapter utility interfaces intentionally turn previously accepted unsound code into compile errors, so the changeset classifies the release as minor. The precision adds 574 declaration bytes in total and approximately 1.6% more source-only TypeScript instantiations, with no measured check-time regression.

Verification

Focused type owners and their surrounding packages:

pnpm --filter @tanstack/db test -- tests/mutation-handler-type-oracle.test-d.ts tests/mutation-handler-compatibility.test-d.ts
pnpm --filter @tanstack/query-db-collection test -- tests/query.test-d.ts tests/optimistic-writeback.test.ts
pnpm --filter @tanstack/electric-db-collection test -- tests/electric.test-d.ts
pnpm --filter @tanstack/powersync-db-collection test -- tests/powersync.test-d.ts

pnpm exec tsc --noEmit -p packages/db/tsconfig.json
pnpm exec tsc --noEmit -p packages/query-db-collection/tsconfig.json
pnpm exec tsc --noEmit -p packages/electric-db-collection/tsconfig.json
pnpm exec tsc --noEmit -p packages/powersync-db-collection/tsconfig.json

Also verified:

  • Core oracle plus legacy handler compatibility: 4/4 green.
  • Query, Electric, and PowerSync type owners: 25/25, 11/11, and 3/3 green.
  • Relevant core transaction/mutation and adapter runtime suites green.
  • DB, Query, and Electric builds green; formatting, lint, and diff hygiene green.
  • A focused emitted-declaration consumer compiles with TypeScript 5.4. The repository's full TS 5.4 check still contains unrelated pre-existing errors in older tests.

Hostile-mutant evidence

The permanent owners reject all three original failure modes: restoring key: any, dropping the concrete collection from handler transactions, or reopening Query/Electric utilities with UtilsRecord. The initial base probe produced 25 diagnostics across insert/update/delete, branded and numeric keys, exact IsAny checks, wrong-key assignments, and cross-adapter utility controls.

Output and compiler diagnostics

  • Runtime artifacts: byte-identical for @tanstack/db, Query Collection, and Electric Collection.
  • Declaration output: DB +498 B, Query +142 B, Electric -66 B; net +574 B across formats.
  • Source-only TS 5.9: 164,069 -> 166,715 instantiations (+2,646, about 1.6%); check time 0.70 s -> 0.68 s.
  • Full package compiler including the new oracle: 4,046,553 instantiations, 7.51 s check time.

Files changed

  • packages/db/src/types.ts: preserve collection, key, and utility types in mutation handler transactions.
  • packages/db/src/collection/{index,mutations}.ts: retain concrete utilities and thread precise collections through runtime-owned handler calls.
  • packages/db/tests/mutation-handler-type-oracle.test-d.ts: add the cross-operation type oracle and hostile controls.
  • packages/query-db-collection/src/query.ts: close and propagate Query utilities; derive wrapper parameter types.
  • packages/electric-db-collection/src/electric.ts: close the Electric utility namespace.
  • Query, Electric, and PowerSync *.test-d.ts owners: assert nested handler precision and cross-adapter rejection.
  • .changeset/tighten-transaction-handler-types.md: release notes for the corrected public types.

Summary by CodeRabbit

  • Bug Fixes
    • Improved TypeScript inference for collection mutation handlers and nested transactions.
    • Preserved collection-specific key and utility types across insert, update, and delete operations.
    • Improved typing for mutation keys, collection utilities, transaction state, and persisted transaction results.
    • Prevented Query, Electric, and PowerSync collections from exposing unsupported adapter utilities.
    • Added validation to reject incorrect keys and cross-adapter utility access.
    • Default-key collections now expose mutation keys as string | number, while explicitly typed collections retain their declared key type.

@coderabbitai

coderabbitai Bot commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 17bc242a-95c5-4f9b-b559-ec9dd144003e

📥 Commits

Reviewing files that changed from the base of the PR and between 49787d0 and 904f19f.

📒 Files selected for processing (2)
  • .changeset/tighten-transaction-handler-types.md
  • packages/db/tests/mutation-handler-type-oracle.test-d.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • .changeset/tighten-transaction-handler-types.md

Included review availability: Your plan provides up to 8 included reviews per hour; 6 remain after this review.


📝 Walkthrough

Walkthrough

Collection and transaction types now preserve declared collection keys and adapter utilities through mutation handlers. Electric and Query utility interfaces are closed. Type-level tests cover core, Electric, PowerSync, and Query collections. A changeset marks three packages for minor releases.

Changes

Mutation Handler Type Preservation

Layer / File(s) Summary
Core collection and transaction type propagation
packages/db/src/collection/index.ts, packages/db/src/collection/mutations.ts, packages/db/src/collection/{lifecycle,state,sync}.ts, packages/db/src/types.ts, packages/db/src/query/builder/types.ts
Collection configuration, mutation handlers, transactions, pending mutations, and manager types now preserve concrete key and utility types.
Adapter utility contracts
packages/electric-db-collection/src/electric.ts, packages/query-db-collection/src/query.ts
Electric and Query utility interfaces no longer expose unavailable utilities through UtilsRecord. Query handler wrappers derive parameter types from configured handlers.
Type validation and release metadata
packages/db/tests/*, packages/electric-db-collection/tests/*, packages/powersync-db-collection/tests/*, packages/query-db-collection/tests/*, .changeset/*
Type-level tests verify key and utility inference across collection adapters. The changeset marks three packages for minor releases.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Bug fix

Merge Risk: ⚪ Minimal · up to 904f1

No actionable risk remains from the reviewed change; it is ready to merge.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 3 functions across 13 files. (1 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly identifies the primary change: preserving mutation handler key and utility types.
Description check ✅ Passed The description provides detailed change motivation, approach, scope, trade-offs, verification results, and release impact. It does not use the template headings or checklist format, but it includes t…
Full details: Docstring Coverage

Explanation

Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 3 functions across 13 files. (1 skipped: 1 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@pkg-pr-new

pkg-pr-new Bot commented Sep 18, 2026

Copy link
Copy Markdown
More templates

@tanstack/angular-db

npm i https://pkg.pr.new/@tanstack/angular-db@1849

@tanstack/browser-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/browser-db-sqlite-persistence@1849

@tanstack/capacitor-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/capacitor-db-sqlite-persistence@1849

@tanstack/cloudflare-durable-objects-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/cloudflare-durable-objects-db-sqlite-persistence@1849

@tanstack/db

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

@tanstack/db-ivm

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

@tanstack/db-sqlite-persistence-core

npm i https://pkg.pr.new/@tanstack/db-sqlite-persistence-core@1849

@tanstack/electric-db-collection

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

@tanstack/electron-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/electron-db-sqlite-persistence@1849

@tanstack/expo-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/expo-db-sqlite-persistence@1849

@tanstack/node-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/node-db-sqlite-persistence@1849

@tanstack/offline-transactions

npm i https://pkg.pr.new/@tanstack/offline-transactions@1849

@tanstack/powersync-db-collection

npm i https://pkg.pr.new/@tanstack/powersync-db-collection@1849

@tanstack/query-db-collection

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

@tanstack/react-db

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

@tanstack/react-native-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/react-native-db-sqlite-persistence@1849

@tanstack/react-router-with-db

npm i https://pkg.pr.new/@tanstack/react-router-with-db@1849

@tanstack/rxdb-db-collection

npm i https://pkg.pr.new/@tanstack/rxdb-db-collection@1849

@tanstack/solid-db

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

@tanstack/svelte-db

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

@tanstack/tauri-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/tauri-db-sqlite-persistence@1849

@tanstack/trailbase-db-collection

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

@tanstack/vue-db

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

commit: 904f19f

@github-actions

Copy link
Copy Markdown
Contributor

Size Change: +6 B (0%)

Total Size: 165 kB

📦 View Changed
Filename Size Change
packages/db/dist/esm/collection/index.js 4.63 kB +6 B (+0.13%)
ℹ️ View Unchanged
Filename Size
packages/db/dist/esm/client.js 3.66 kB
packages/db/dist/esm/collection-options.js 236 B
packages/db/dist/esm/collection/change-events.js 1.44 kB
packages/db/dist/esm/collection/changes.js 2.25 kB
packages/db/dist/esm/collection/cleanup-queue.js 794 B
packages/db/dist/esm/collection/events.js 481 B
packages/db/dist/esm/collection/indexes.js 1.99 kB
packages/db/dist/esm/collection/lifecycle.js 2.15 kB
packages/db/dist/esm/collection/mutations.js 2.61 kB
packages/db/dist/esm/collection/state.js 6.51 kB
packages/db/dist/esm/collection/subscription.js 8.72 kB
packages/db/dist/esm/collection/sync.js 4.62 kB
packages/db/dist/esm/collection/transaction-metadata.js 144 B
packages/db/dist/esm/deferred.js 207 B
packages/db/dist/esm/errors.js 5.26 kB
packages/db/dist/esm/event-emitter.js 964 B
packages/db/dist/esm/index.js 3.71 kB
packages/db/dist/esm/indexes/auto-index.js 829 B
packages/db/dist/esm/indexes/base-index.js 1.14 kB
packages/db/dist/esm/indexes/basic-index.js 2.07 kB
packages/db/dist/esm/indexes/btree-index.js 2.26 kB
packages/db/dist/esm/indexes/index-registry.js 820 B
packages/db/dist/esm/indexes/reverse-index.js 376 B
packages/db/dist/esm/live-query-adapter.js 318 B
packages/db/dist/esm/live-query-observer.js 3.69 kB
packages/db/dist/esm/live-query-options.js 702 B
packages/db/dist/esm/live-query-window-controller.js 4.36 kB
packages/db/dist/esm/local-only.js 989 B
packages/db/dist/esm/local-storage.js 2.17 kB
packages/db/dist/esm/optimistic-action.js 359 B
packages/db/dist/esm/paced-mutations.js 496 B
packages/db/dist/esm/proxy.js 3.32 kB
packages/db/dist/esm/query/builder/functions.js 1.47 kB
packages/db/dist/esm/query/builder/index.js 6.69 kB
packages/db/dist/esm/query/builder/query-ir.js 116 B
packages/db/dist/esm/query/builder/ref-proxy.js 1.24 kB
packages/db/dist/esm/query/compiler/evaluators.js 1.92 kB
packages/db/dist/esm/query/compiler/expressions.js 560 B
packages/db/dist/esm/query/compiler/group-by.js 4.13 kB
packages/db/dist/esm/query/compiler/index.js 9.06 kB
packages/db/dist/esm/query/compiler/joins.js 2.95 kB
packages/db/dist/esm/query/compiler/lazy-targets.js 1.1 kB
packages/db/dist/esm/query/compiler/order-by.js 1.91 kB
packages/db/dist/esm/query/compiler/parent-routes.js 319 B
packages/db/dist/esm/query/compiler/route-metadata.js 1.24 kB
packages/db/dist/esm/query/compiler/select.js 1.58 kB
packages/db/dist/esm/query/effect.js 4.6 kB
packages/db/dist/esm/query/equality-value-identity.js 591 B
packages/db/dist/esm/query/expression-helpers.js 1.43 kB
packages/db/dist/esm/query/ir-stable-identity.js 4.04 kB
packages/db/dist/esm/query/ir.js 1.59 kB
packages/db/dist/esm/query/live-query-collection.js 391 B
packages/db/dist/esm/query/live/bucket-facade-adapter.js 2.73 kB
packages/db/dist/esm/query/live/collection-config-builder.js 6.97 kB
packages/db/dist/esm/query/live/collection-registry.js 264 B
packages/db/dist/esm/query/live/collection-subscriber.js 2.25 kB
packages/db/dist/esm/query/live/internal.js 145 B
packages/db/dist/esm/query/live/materialized-pipeline.js 2.32 kB
packages/db/dist/esm/query/live/ordered-source-loader.js 3.14 kB
packages/db/dist/esm/query/live/subset-demand-controller.js 1.26 kB
packages/db/dist/esm/query/live/utils.js 1.14 kB
packages/db/dist/esm/query/optimizer.js 2.91 kB
packages/db/dist/esm/query/query-once.js 359 B
packages/db/dist/esm/query/runtime-reference-identity.js 572 B
packages/db/dist/esm/query/subset-dedupe.js 486 B
packages/db/dist/esm/scheduler.js 1.34 kB
packages/db/dist/esm/SortedMap.js 1.3 kB
packages/db/dist/esm/strategies/debounceStrategy.js 247 B
packages/db/dist/esm/strategies/queueStrategy.js 428 B
packages/db/dist/esm/strategies/throttleStrategy.js 246 B
packages/db/dist/esm/transactions.js 3.71 kB
packages/db/dist/esm/utils.js 1.08 kB
packages/db/dist/esm/utils/array-utils.js 270 B
packages/db/dist/esm/utils/browser-polyfills.js 304 B
packages/db/dist/esm/utils/btree.js 4.51 kB
packages/db/dist/esm/utils/callbacks.js 174 B
packages/db/dist/esm/utils/comparison.js 1.49 kB
packages/db/dist/esm/utils/cursor.js 676 B
packages/db/dist/esm/utils/error.js 167 B
packages/db/dist/esm/utils/get-or-create.js 155 B
packages/db/dist/esm/utils/index-optimization.js 2.42 kB
packages/db/dist/esm/utils/type-guards.js 230 B
packages/db/dist/esm/utils/uuid.js 449 B
packages/db/dist/esm/virtual-props.js 360 B

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

@github-actions

Copy link
Copy Markdown
Contributor

Size Change: 0 B

Total Size: 7.34 kB

ℹ️ View Unchanged
Filename Size
packages/react-db/dist/esm/DbProvider.js 317 B
packages/react-db/dist/esm/HydrationBoundary.js 263 B
packages/react-db/dist/esm/index.js 330 B
packages/react-db/dist/esm/live-query-internals.js 282 B
packages/react-db/dist/esm/useLiveInfiniteQuery.js 1.9 kB
packages/react-db/dist/esm/useLiveQuery.js 2.68 kB
packages/react-db/dist/esm/useLiveQueryEffect.js 355 B
packages/react-db/dist/esm/useLiveSuspenseQuery.js 812 B
packages/react-db/dist/esm/usePacedMutations.js 401 B

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

@KyleAMathews
KyleAMathews merged commit 71ad428 into main Sep 18, 2026
11 checks passed
@KyleAMathews
KyleAMathews deleted the oracle-transaction-sync-types branch September 18, 2026 21:09
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.

1 participant