Skip to content

Conversation

@KyleAMathews
Copy link
Collaborator

@KyleAMathews KyleAMathews commented Dec 2, 2025

🎯 Changes

This PR makes optimistic mutations opt-in via a plugin pattern, enabling tree-shaking to eliminate ~25% of bundle size for read-only collections.

Key Changes

  1. Plugin-based mutations API: Collections now require explicitly importing and passing the mutations plugin to enable mutation capabilities
  2. TypeScript enforcement: The type system prevents using onInsert, onUpdate, onDelete handlers without the mutations plugin
  3. Runtime validation: Attempting to call .insert(), .update(), or .delete() without the mutations plugin throws MutationsNotEnabledError
  4. Tree-shaking enabled: Read-only collections can completely eliminate mutation code from their bundles

Bundle Size Impact

  • Read-only collection: ~58.5 KB (minified)
  • With mutations: ~78.3 KB (minified)
  • Savings: ~19.8 KB (~25% reduction)

Migration Example

Before:

import { createCollection } from "@tanstack/db"

const collection = createCollection({
  sync: { sync: () => {} },
  onInsert: async (params) => { /* ... */ },
})

After:

import { createCollection, mutations } from "@tanstack/db"

const collection = createCollection({
  mutations, // Add the mutations plugin
  sync: { sync: () => {} },
  onInsert: async (params) => { /* ... */ },
})

Read-only (no mutations needed):

import { createCollection } from "@tanstack/db"

const collection = createCollection({
  sync: { sync: () => {} },
  // No mutations plugin = ~20KB smaller bundle
})

✅ Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested this code locally with pnpm test:pr.

🚀 Release Impact

  • This change affects published code, and I have generated a changeset.
  • This change is docs/CI/dev-only (no release).

📦 Affected Packages

  • @tanstack/db - Core API change
  • @tanstack/electric-db-collection - Updated to use mutations plugin
  • @tanstack/powersync-db-collection - Updated to use mutations plugin
  • @tanstack/query-db-collection - Updated to use mutations plugin
  • @tanstack/rxdb-db-collection - Updated to use mutations plugin
  • @tanstack/trailbase-db-collection - Updated to use mutations plugin

🔍 Technical Details

The implementation uses a plugin object that provides a _createManager method, which is called conditionally when the plugin is present. Modern bundlers can tree-shake the entire mutations module chain when the plugin is not imported:

  • collection/mutations.js - CollectionMutationsManager
  • proxy.js - Change tracking proxy
  • transactions.js - Transaction system

Total eliminated: ~43KB raw → ~20KB minified

Introduce discriminated union types for collection configuration that
enforce mutation handler usage based on the `mutations` flag:

- Add `MutableCollectionConfig` (mutations: true) - allows onInsert,
  onUpdate, onDelete handlers
- Add `ReadOnlyCollectionConfig` (mutations?: false) - disallows
  mutation handlers with TypeScript errors
- Make `CollectionConfig` a union of both types

TypeScript now errors when trying to use onInsert/onUpdate/onDelete
without explicitly setting `mutations: true`:

```typescript
// ✅ Works - read-only, no handlers
const readOnly = createCollection({
  getKey: (t) => t.id,
  sync: { ... }
})

// ✅ Works - mutations enabled with handlers
const mutable = createCollection({
  getKey: (t) => t.id,
  mutations: true,
  onInsert: async ({ transaction }) => { ... }
})

// ❌ TypeScript Error: "onInsert" requires mutations: true
const broken = createCollection({
  getKey: (t) => t.id,
  onInsert: async () => { ... } // Error!
})
```

This is the first step toward making optimistic mutation code opt-in
for bundle size reduction. Tree-shaking analysis shows ~50KB of
mutation-specific code that could be eliminated for read-only use cases.
…rror

Add runtime enforcement of the `mutations: true` config flag:

- `MutationsNotEnabledError` thrown when calling insert/update/delete
  on collections without `mutations: true`
- CollectionMutationsManager only instantiated when mutations enabled
- localStorageCollectionOptions now includes `mutations: true` by default

Update all tests to include `mutations: true` where mutation methods
are used, ensuring both compile-time and runtime safety.
Change the mutations config from `mutations: true` to a plugin-based approach
using `import { mutations } from "@tanstack/db"` and passing it to the collection
config. This enables tree-shaking to exclude mutation code for read-only collections.

Key changes:
- Add MutationsPlugin interface and mutations export
- Update collection to use plugin's _createManager for mutation setup
- Update all adapters (powersync, rxdb, query, trailbase, electric) to use mutations plugin
- Update all tests to import and use mutations plugin
- Tree-shaking results: 58.5KB (read-only) vs 78.3KB (with mutations) = ~25% reduction
@changeset-bot
Copy link

changeset-bot bot commented Dec 2, 2025

🦋 Changeset detected

Latest commit: 41a64fc

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

This PR includes changesets to release 13 packages
Name Type
@tanstack/db Minor
@tanstack/electric-db-collection Minor
@tanstack/powersync-db-collection Minor
@tanstack/query-db-collection Minor
@tanstack/rxdb-db-collection Minor
@tanstack/trailbase-db-collection Minor
@tanstack/angular-db Patch
@tanstack/db-collection-e2e Patch
@tanstack/react-db Patch
@tanstack/solid-db Patch
@tanstack/svelte-db Patch
@tanstack/vue-db Patch
@tanstack/db-example-solid-todo 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

@pkg-pr-new
Copy link

pkg-pr-new bot commented Dec 2, 2025

More templates

@tanstack/angular-db

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

@tanstack/db

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

@tanstack/db-ivm

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

@tanstack/electric-db-collection

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

@tanstack/offline-transactions

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

@tanstack/powersync-db-collection

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

@tanstack/query-db-collection

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

@tanstack/react-db

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

@tanstack/rxdb-db-collection

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

@tanstack/solid-db

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

@tanstack/svelte-db

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

@tanstack/trailbase-db-collection

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

@tanstack/vue-db

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

commit: 41a64fc

@github-actions
Copy link
Contributor

github-actions bot commented Dec 2, 2025

Size Change: +287 B (+0.33%)

Total Size: 87.4 kB

Filename Size Change
./packages/db/dist/esm/collection/index.js 3.27 kB +30 B (+0.93%)
./packages/db/dist/esm/collection/mutations.js 2.36 kB +59 B (+2.56%)
./packages/db/dist/esm/errors.js 4.26 kB +68 B (+1.62%)
./packages/db/dist/esm/index.js 2.69 kB +46 B (+1.74%)
./packages/db/dist/esm/local-storage.js 2.12 kB +24 B (+1.14%)
./packages/db/dist/esm/query/live/collection-config-builder.js 5.38 kB +60 B (+1.13%)
ℹ️ View Unchanged
Filename Size
./packages/db/dist/esm/collection/change-events.js 1.38 kB
./packages/db/dist/esm/collection/changes.js 977 B
./packages/db/dist/esm/collection/events.js 388 B
./packages/db/dist/esm/collection/indexes.js 1.1 kB
./packages/db/dist/esm/collection/lifecycle.js 1.67 kB
./packages/db/dist/esm/collection/state.js 3.43 kB
./packages/db/dist/esm/collection/subscription.js 2.55 kB
./packages/db/dist/esm/collection/sync.js 2.37 kB
./packages/db/dist/esm/deferred.js 207 B
./packages/db/dist/esm/event-emitter.js 748 B
./packages/db/dist/esm/indexes/auto-index.js 742 B
./packages/db/dist/esm/indexes/base-index.js 766 B
./packages/db/dist/esm/indexes/btree-index.js 1.87 kB
./packages/db/dist/esm/indexes/lazy-index.js 1.1 kB
./packages/db/dist/esm/indexes/reverse-index.js 513 B
./packages/db/dist/esm/local-only.js 837 B
./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.75 kB
./packages/db/dist/esm/query/builder/functions.js 733 B
./packages/db/dist/esm/query/builder/index.js 3.96 kB
./packages/db/dist/esm/query/builder/ref-proxy.js 917 B
./packages/db/dist/esm/query/compiler/evaluators.js 1.35 kB
./packages/db/dist/esm/query/compiler/expressions.js 430 B
./packages/db/dist/esm/query/compiler/group-by.js 1.8 kB
./packages/db/dist/esm/query/compiler/index.js 1.96 kB
./packages/db/dist/esm/query/compiler/joins.js 2 kB
./packages/db/dist/esm/query/compiler/order-by.js 1.25 kB
./packages/db/dist/esm/query/compiler/select.js 1.07 kB
./packages/db/dist/esm/query/expression-helpers.js 1.43 kB
./packages/db/dist/esm/query/ir.js 673 B
./packages/db/dist/esm/query/live-query-collection.js 360 B
./packages/db/dist/esm/query/live/collection-registry.js 264 B
./packages/db/dist/esm/query/live/collection-subscriber.js 1.74 kB
./packages/db/dist/esm/query/live/internal.js 130 B
./packages/db/dist/esm/query/optimizer.js 2.56 kB
./packages/db/dist/esm/query/predicate-utils.js 2.91 kB
./packages/db/dist/esm/query/subset-dedupe.js 921 B
./packages/db/dist/esm/scheduler.js 1.3 kB
./packages/db/dist/esm/SortedMap.js 1.18 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 2.9 kB
./packages/db/dist/esm/utils.js 881 B
./packages/db/dist/esm/utils/browser-polyfills.js 304 B
./packages/db/dist/esm/utils/btree.js 5.61 kB
./packages/db/dist/esm/utils/comparison.js 852 B
./packages/db/dist/esm/utils/index-optimization.js 1.51 kB
./packages/db/dist/esm/utils/type-guards.js 157 B

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

@github-actions
Copy link
Contributor

github-actions bot commented Dec 2, 2025

Size Change: 0 B

Total Size: 3.34 kB

ℹ️ View Unchanged
Filename Size
./packages/react-db/dist/esm/index.js 225 B
./packages/react-db/dist/esm/useLiveInfiniteQuery.js 1.17 kB
./packages/react-db/dist/esm/useLiveQuery.js 1.11 kB
./packages/react-db/dist/esm/useLiveSuspenseQuery.js 431 B
./packages/react-db/dist/esm/usePacedMutations.js 401 B

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

- Add comprehensive changeset documenting the breaking change for opt-in mutations
- Fix type test by explicitly passing ElectricCollectionUtils type parameter to createCollection
- Update PR description with bundle size metrics and migration guide
Instead of trying to assign the entire utils object to ElectricCollectionUtils type,
check that the individual utility methods (awaitTxId, awaitMatch) exist and are
properly typed. This matches the pattern used in other adapter tests.
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.

3 participants