Skip to content

Conversation

@KyleAMathews
Copy link
Collaborator

🎯 Changes

βœ… 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).

BREAKING CHANGE: Mutation handler return values are now deprecated

**Problem:**
Users were confused about the difference between collection handlers
(onInsert, onUpdate, onDelete) and manual actions/transactions. The
magic of returning values like `{ refetch: false }` from handlers was
specific to Query Collections but users expected it to work everywhere,
leading to incorrect mental models.

**Solution:**
1. Deprecate returning values from mutation handlers in TypeScript
   - Changed `TReturn = any` to `TReturn = void` in base handler types
   - Added @deprecated JSDoc tags with migration guidance
   - Updated all handler examples to show manual refetch pattern

2. Update documentation to teach manual patterns:
   - Query Collections: Use `await collection.utils.refetch()`
   - Electric Collections: Return `{ txid }` or use `collection.utils.awaitMatch()`

3. Clarify Electric Collection's special pattern:
   - Electric handlers REQUIRE returning txid or calling awaitMatch
   - This is Electric-specific, not a general pattern
   - Updated JSDoc to emphasize this distinction

**Migration Guide:**

Before (deprecated):
```typescript
onInsert: async ({ transaction }) => {
  await api.create(data)
  return { refetch: false }  // ❌ Deprecated
}
```

After (recommended):
```typescript
onInsert: async ({ transaction, collection }) => {
  await api.create(data)
  await collection.utils.refetch()  // βœ… Explicit and clear
}
```

For Electric Collections (unchanged):
```typescript
onInsert: async ({ transaction }) => {
  const result = await api.create(data)
  return { txid: result.txid }  // βœ… Electric-specific pattern
}
```

**Files Changed:**
- packages/db/src/types.ts: Deprecated return types, updated JSDoc
- packages/electric-db-collection/src/electric.ts: Clarified Electric-specific pattern
- docs/collections/query-collection.md: Removed refetch control examples, added manual patterns
- docs/guides/mutations.md: Updated collection-specific handler patterns

This change reduces confusion and makes the API more explicit and easier to understand.
…alues

Update the guide for creating custom collection options creators to reflect
the deprecation of mutation handler return values.

**Changes:**

1. **Pattern A (User-Provided Handlers):**
   - Clarified that handler return values are deprecated
   - Users should manually trigger refetch/sync within their handlers
   - Added note about Electric-specific exception (txid returns)
   - Simplified example to just pass through handlers

2. **Pattern B (Built-in Handlers):**
   - Updated examples to show handlers completing after sync coordination
   - Removed misleading return statements
   - Added key principle: coordinate sync internally via `await`

3. **Strategy 5 (Query Collection):**
   - Renamed from "Full Refetch" to "Manual Refetch"
   - Updated to show user explicitly calling `collection.utils.refetch()`
   - Clarified that users manage refetch in their own handlers

4. **WebSocket Example:**
   - Added explicit `Promise<void>` return types to handlers
   - Added comments about handlers completing after server confirmation

5. **Electric Example:**
   - Clarified that txid returns are Electric-specific
   - Updated wrapper example to not return the result

6. **Best Practices:**
   - Added new guideline about deprecated handler return values
   - Emphasized Electric is the only exception

These changes align the guide with the new pattern where mutation handlers
don't return values, and sync coordination happens explicitly within handlers
via await or manual refetch calls.
…pattern

BREAKING CHANGE: Electric collection handlers no longer support returning { txid }

**Problem:**
The previous commit deprecated return values for most collections but kept
Electric's `return { txid }` pattern as an exception. This creates confusion
because it's still "magic" - users don't understand why Electric is different
and it maintains the same conceptual problem of implicit behavior.

**Solution:**
Deprecate ALL return value patterns, including Electric's. Electric handlers
should now use `await collection.utils.awaitTxId(txid)` explicitly.

**Changes:**

1. **Base Collection Types** (`packages/db/src/types.ts`):
   - Added Electric example showing manual `await collection.utils.awaitTxId()`
   - Removed mentions of Electric-specific return pattern
   - All handlers consistently show void return with manual sync

2. **Electric Collection** (`packages/electric-db-collection/src/electric.ts`):
   - Deprecated `return { txid }` pattern in all three handlers
   - Updated JSDoc to show manual `await collection.utils.awaitTxId(txid)`
   - Added `@deprecated` tags explaining the migration
   - Updated all examples to use manual await pattern

3. **Electric Documentation** (`docs/collections/electric-collection.md`):
   - Updated "Using Txid" section to show manual awaitTxId
   - Changed "return txid to wait for sync" to "manually wait for txid"
   - All code examples now use `await collection.utils.awaitTxId()`

4. **Collection Options Creator Guide** (`docs/guides/collection-options-creator.md`):
   - Removed "Electric-Specific Exception" note
   - Updated best practices to mention Electric should use awaitTxId
   - No more special cases - all handlers work the same way

5. **Mutations Guide** (`docs/guides/mutations.md`):
   - Updated Electric example to show manual awaitTxId pattern
   - Changed from "return { txid }" to manual await pattern

**Migration Guide:**

Before (deprecated):
```typescript
// Electric Collection
onInsert: async ({ transaction }) => {
  const result = await api.create(data)
  return { txid: result.txid }  // ❌ Deprecated
}
```

After (recommended):
```typescript
// Electric Collection
onInsert: async ({ transaction, collection }) => {
  const result = await api.create(data)
  await collection.utils.awaitTxId(result.txid)  // βœ… Explicit and clear
}
```

**Benefits:**
1. **No more magic**: All collections follow the same explicit pattern
2. **Clearer mental model**: Users understand they're waiting for sync
3. **Consistent API**: No special cases to remember
4. **Better visibility**: `await` makes the async nature explicit
5. **Same control**: Users have same power, just more explicit

All collections now have a consistent pattern: handlers coordinate sync
explicitly via await, not implicitly via return values.
@changeset-bot
Copy link

changeset-bot bot commented Nov 17, 2025

πŸ¦‹ Changeset detected

Latest commit: 98bb88b

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

This PR includes changesets to release 16 packages
Name Type
@tanstack/db Major
@tanstack/electric-db-collection Major
@tanstack/query-db-collection Major
@tanstack/angular-db Patch
@tanstack/db-collection-e2e Patch
@tanstack/offline-transactions Major
@tanstack/powersync-db-collection Patch
@tanstack/react-db Patch
@tanstack/rxdb-db-collection Patch
@tanstack/solid-db Patch
@tanstack/svelte-db Patch
@tanstack/trailbase-db-collection Patch
@tanstack/vue-db Patch
todos Patch
@tanstack/db-example-paced-mutations-demo 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

@KyleAMathews KyleAMathews marked this pull request as draft November 17, 2025 23:12
@pkg-pr-new
Copy link

pkg-pr-new bot commented Nov 17, 2025

More templates

@tanstack/angular-db

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

@tanstack/db

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

@tanstack/db-ivm

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

@tanstack/electric-db-collection

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

@tanstack/offline-transactions

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

@tanstack/powersync-db-collection

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

@tanstack/query-db-collection

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

@tanstack/react-db

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

@tanstack/rxdb-db-collection

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

@tanstack/solid-db

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

@tanstack/svelte-db

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

@tanstack/trailbase-db-collection

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

@tanstack/vue-db

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

commit: 98bb88b

@github-actions
Copy link
Contributor

github-actions bot commented Nov 17, 2025

Size Change: 0 B

Total Size: 85.8 kB

ℹ️ 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/index.js 3.24 kB
./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/mutations.js 2.26 kB
./packages/db/dist/esm/collection/state.js 3.43 kB
./packages/db/dist/esm/collection/subscription.js 2.42 kB
./packages/db/dist/esm/collection/sync.js 2.12 kB
./packages/db/dist/esm/deferred.js 207 B
./packages/db/dist/esm/errors.js 4.11 kB
./packages/db/dist/esm/event-emitter.js 748 B
./packages/db/dist/esm/index.js 2.63 kB
./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/local-storage.js 2.1 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.22 kB
./packages/db/dist/esm/query/builder/functions.js 733 B
./packages/db/dist/esm/query/builder/index.js 3.84 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-config-builder.js 5.26 kB
./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.88 kB
./packages/db/dist/esm/query/subset-dedupe.js 921 B
./packages/db/dist/esm/scheduler.js 1.21 kB
./packages/db/dist/esm/SortedMap.js 1.18 kB
./packages/db/dist/esm/strategies/debounceStrategy.js 237 B
./packages/db/dist/esm/strategies/queueStrategy.js 422 B
./packages/db/dist/esm/strategies/throttleStrategy.js 236 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 Nov 17, 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

…entation

Cleaned up language throughout mutation handler docs and JSDoc to be more direct.
Changed phrases like "manually wait for" to "wait for" since there's only one way to do it.
@samwillis
Copy link
Collaborator

I'm in favour of this change, it makes everything much more explicit. My only hesitation is on the query collection where it becomes a little more verbose with the need to refetch the majority of the time... but that explicitness does ensure that the user is aware of what is actually happening.

on the whole, I think it's worth making this change.

// Users handle sync coordination in their own handlers
onInsert: config.onInsert,
onUpdate: config.onUpdate,
onDelete: config.onDelete

Choose a reason for hiding this comment

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

This is proof that this is a cleaner approach. Well done.

@KyleAMathews KyleAMathews changed the title Remove magic return APIs from collection handlers Deprecate magic return APIs from collection handlers Nov 19, 2025
Based on external code review feedback, this commit implements a soft deprecation
strategy for mutation handler return values:

Runtime changes:
- Add console warnings when QueryCollection handlers return { refetch }
- Add console warnings when Electric handlers return { txid }
- Keep runtime functionality intact for backward compatibility
- Runtime support will be removed in v1.0 RC

Type improvements:
- Mark TReturn generic as @internal and deprecated across all mutation types
- Clarify that it exists only for backward compatibility

Documentation improvements:
- Clarify Electric JSDoc: handlers return Promise<void> but must not RESOLVE
  until synchronization is complete (avoiding "void but not void" confusion)
- Add timeout error handling example showing policy choices (rollback vs
  eventual consistency)
- Update changeset to clearly communicate this is a soft deprecation

This aligns with the review recommendation for a gradual migration path with
clear runtime feedback to help users migrate to the new explicit patterns.
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.

5 participants