Skip to content

Conversation

@KyleAMathews
Copy link
Collaborator

@KyleAMathews KyleAMathews commented Jan 13, 2026

Summary

Fix mutation.changes being lost when offline transactions are persisted and restored. After app restart, sync functions that rely on mutation.changes for partial updates would receive an empty object {}, causing server writes to fail with missing data.

Root Cause

The changes field was never added to the serialization flow:

  1. SerializedMutation interface was missing the changes field
  2. serializeMutation() didn't serialize changes
  3. deserializeMutation() hardcoded changes: {} with a misleading comment "Will be recalculated"

The comment was wrong—changes was never recalculated anywhere. This meant any mutation function using the common pattern api.update(id, mutation.changes) would send empty updates after restart.

Approach

Add changes to the serialization round-trip, mirroring how modified and original are already handled:

// Serialize
changes: this.serializeValue(mutation.changes),

// Deserialize (with legacy data fallback)
changes: data.changes !== undefined 
  ? this.deserializeValue(data.changes) 
  : {},

The legacy fallback handles data serialized before this fix—without it, deserializeValue(undefined) would return undefined, causing TypeError when downstream code accesses properties on changes.

Key Invariants

  • mutation.changes must survive the serialize → persist → deserialize cycle unchanged
  • Legacy data (missing changes field) must deserialize without errors

Non-goals

  • Not adding explicit version migration—the fallback to {} matches the previous (broken) behavior, so existing data continues to work as before
  • Not refactoring the broader silent-failure patterns in OutboxManager—that's pre-existing and out of scope

Verification

cd packages/offline-transactions && pnpm test

All 26 tests pass. The existing "replays persisted transactions on startup" e2e test exercises this code path.

Files Changed

File Change
types.ts Add changes: any to SerializedMutation interface
TransactionSerializer.ts Serialize/deserialize changes field with legacy fallback
TransactionSerializer.ts Code quality: safer hasOwnProperty, unused param prefixes

The changes field was not included in SerializedMutation, causing it to
be lost during serialization. On deserialization, changes was hardcoded
to {} with a comment "Will be recalculated" but was never actually
recalculated.

This caused any sync function using mutation.changes to receive an empty
object, leading to server writes failing with missing data after app
restart.

The fix:
- Add changes field to SerializedMutation interface
- Serialize changes in serializeMutation using serializeValue
- Deserialize changes in deserializeMutation using deserializeValue
@changeset-bot
Copy link

changeset-bot bot commented Jan 13, 2026

🦋 Changeset detected

Latest commit: 5e750bb

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

This PR includes changesets to release 1 package
Name Type
@tanstack/offline-transactions 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 Jan 13, 2026

More templates

@tanstack/angular-db

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

@tanstack/db

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

@tanstack/db-ivm

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

@tanstack/electric-db-collection

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

@tanstack/offline-transactions

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

@tanstack/powersync-db-collection

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

@tanstack/query-db-collection

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

@tanstack/react-db

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

@tanstack/rxdb-db-collection

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

@tanstack/solid-db

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

@tanstack/svelte-db

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

@tanstack/trailbase-db-collection

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

@tanstack/vue-db

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

commit: 5e750bb

@github-actions
Copy link
Contributor

github-actions bot commented Jan 13, 2026

Size Change: 0 B

Total Size: 90.5 kB

ℹ️ View Unchanged
Filename Size
./packages/db/dist/esm/collection/change-events.js 1.39 kB
./packages/db/dist/esm/collection/changes.js 1.19 kB
./packages/db/dist/esm/collection/events.js 388 B
./packages/db/dist/esm/collection/index.js 3.32 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.34 kB
./packages/db/dist/esm/collection/state.js 3.46 kB
./packages/db/dist/esm/collection/subscription.js 3.62 kB
./packages/db/dist/esm/collection/sync.js 2.38 kB
./packages/db/dist/esm/deferred.js 207 B
./packages/db/dist/esm/errors.js 4.49 kB
./packages/db/dist/esm/event-emitter.js 748 B
./packages/db/dist/esm/index.js 2.69 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.93 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.75 kB
./packages/db/dist/esm/query/builder/functions.js 733 B
./packages/db/dist/esm/query/builder/index.js 4.08 kB
./packages/db/dist/esm/query/builder/ref-proxy.js 1.05 kB
./packages/db/dist/esm/query/compiler/evaluators.js 1.42 kB
./packages/db/dist/esm/query/compiler/expressions.js 430 B
./packages/db/dist/esm/query/compiler/group-by.js 1.87 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.45 kB
./packages/db/dist/esm/query/compiler/select.js 1.06 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.4 kB
./packages/db/dist/esm/query/live/collection-registry.js 264 B
./packages/db/dist/esm/query/live/collection-subscriber.js 1.93 kB
./packages/db/dist/esm/query/live/internal.js 145 B
./packages/db/dist/esm/query/optimizer.js 2.56 kB
./packages/db/dist/esm/query/predicate-utils.js 2.97 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.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 2.9 kB
./packages/db/dist/esm/utils.js 924 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/cursor.js 457 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 Jan 13, 2026

Size Change: 0 B

Total Size: 3.47 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.12 kB
./packages/react-db/dist/esm/useLiveSuspenseQuery.js 559 B
./packages/react-db/dist/esm/usePacedMutations.js 401 B

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

KyleAMathews and others added 3 commits January 13, 2026 08:56
- Add fallback for deserializing data saved before changes field existed
- Use Object.prototype.hasOwnProperty.call for safer property checks
- Prefix unused JSON callback params with underscore

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

Labels

None yet

Projects

Status: Ready for review

Development

Successfully merging this pull request may close these issues.

3 participants