Skip to content

Conversation

@KyleAMathews
Copy link
Collaborator

@KyleAMathews KyleAMathews commented Jan 13, 2026

Summary

Fixes date field corruption that occurred after app restart. String values that happened to match ISO date format (e.g., eventId: "2024-01-15T10:30:00.000Z") were being incorrectly converted to Date objects during deserialization, corrupting user data.


Root Cause

The JSON.parse reviver in deserialize() used a regex to detect ISO date strings and eagerly converted ALL matches to Date objects:

JSON.parse(data, (key, value) => {
  if (typeof value === 'string' && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(value)) {
    return new Date(value)  // Corrupts string fields that look like dates!
  }
  return value
})

This ignored that the serializer already has an explicit marker system for Date objects:

  • serializeValue() wraps actual Dates as { __type: 'Date', value: '...' }
  • deserializeValue() only converts values with that marker back to Dates

The reviver was converting things it shouldn't, bypassing the marker system entirely.

Approach

  1. Remove the eager reviver - Let JSON.parse return raw data
  2. Handle createdAt explicitly - The only top-level Date field gets explicit conversion
  3. Trust the marker system - Mutation data uses { __type: 'Date' } markers, handled by existing deserializeValue()
  4. Fix the type - SerializedOfflineTransaction.createdAt is now string (matches JSON-parsed reality)
  5. Add validation - Throw on Invalid Dates rather than silently corrupting data

Key Invariants

  1. String values are never converted to Dates unless explicitly wrapped with { __type: 'Date' }
  2. Actual Date objects round-trip correctly via the marker system
  3. Invalid dates fail fast with clear error messages, not silent corruption

Non-goals

  • Did not add date validation to OutboxManager's catch blocks (separate concern)
  • Did not add nominal typing for ISO strings (adds complexity without clear benefit)

Trade-offs

Alternative considered: Keep the reviver but add an exclusion list for fields that shouldn't be converted.

Why rejected: Fragile - requires maintaining a list of every string field that might contain ISO-formatted values. The marker system already solves this correctly.


Verification

cd packages/offline-transactions && pnpm test

The new test file covers:

  • Plain ISO strings preserved as strings (the bug scenario)
  • Actual Date objects restored via marker system
  • Mixed Date objects and ISO strings handled correctly
  • Nested ISO string values not corrupted
  • Top-level createdAt correctly restored as Date

Files Changed

File Changes
TransactionSerializer.ts Remove reviver, add explicit createdAt conversion, add date validation
types.ts SerializedOfflineTransaction.createdAt: Datestring
TransactionSerializer.test.ts New test file with 5 tests covering date handling

…ing conversion

The JSON.parse reviver in deserialize() was converting ALL ISO date
strings to Date objects, including plain string values that happened
to match the ISO format. This corrupted user data after app restarts.

The serializer already has an explicit date marker system:
- serializeValue() wraps Date objects as { __type: 'Date', value: '...' }
- deserializeValue() restores dates from those markers

The fix removes the eager reviver and only converts the top-level
createdAt field, letting the marker system handle dates in mutation data.

Adds TransactionSerializer.test.ts with tests covering:
- Plain ISO strings preserved as strings
- Actual Date objects correctly restored via marker system
- Mixed Date objects and ISO strings handled correctly
- Nested ISO string values not corrupted
- Top-level createdAt correctly restored as Date
@changeset-bot
Copy link

changeset-bot bot commented Jan 13, 2026

🦋 Changeset detected

Latest commit: 8fdbb2e

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@1127

@tanstack/db

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

@tanstack/db-ivm

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

@tanstack/electric-db-collection

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

@tanstack/offline-transactions

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

@tanstack/powersync-db-collection

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

@tanstack/query-db-collection

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

@tanstack/react-db

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

@tanstack/rxdb-db-collection

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

@tanstack/solid-db

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

@tanstack/svelte-db

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

@tanstack/trailbase-db-collection

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

@tanstack/vue-db

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

commit: 8fdbb2e

@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
Copy link
Collaborator Author

Code review

Found 1 issue:

  1. Missing changeset file. This PR modifies published package code (@tanstack/offline-transactions) to fix a bug, but no changeset file was included. The project uses changesets for versioning and the PR template requires: "This change affects published code, and I have generated a changeset."

## ✅ Checklist

Run pnpm changeset to generate a changeset file describing this patch fix.

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

KyleAMathews and others added 4 commits January 13, 2026 11:38
- Fix SerializedOfflineTransaction.createdAt type to string (matches JSON-parsed reality)
- Remove JSON.stringify replacer in favor of explicit createdAt conversion
- Remove unnecessary double cast in deserialize

Co-Authored-By: Claude Opus 4.5 <[email protected]>
…ures

- Validate createdAt in deserialize() throws on Invalid Date
- Validate Date markers have value field before deserializing
- Validate Date marker values produce valid dates
- Remove stray whitespace in types.ts

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