-
Notifications
You must be signed in to change notification settings - Fork 122
Deprecate magic return APIs from collection handlers #843
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
base: main
Are you sure you want to change the base?
Deprecate magic return APIs from collection handlers #843
Conversation
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 detectedLatest commit: 98bb88b The changes in this PR will be included in the next version bump. This PR includes changesets to release 16 packages
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 |
More templates
@tanstack/angular-db
@tanstack/db
@tanstack/db-ivm
@tanstack/electric-db-collection
@tanstack/offline-transactions
@tanstack/powersync-db-collection
@tanstack/query-db-collection
@tanstack/react-db
@tanstack/rxdb-db-collection
@tanstack/solid-db
@tanstack/svelte-db
@tanstack/trailbase-db-collection
@tanstack/vue-db
commit: |
|
Size Change: 0 B Total Size: 85.8 kB βΉοΈ View Unchanged
|
|
Size Change: 0 B Total Size: 3.34 kB βΉοΈ View Unchanged
|
β¦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.
|
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 |
There was a problem hiding this comment.
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.
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.
π― Changes
β Checklist
pnpm test:pr.π Release Impact