From 734c3fe6b147ecbde7cbfa58026d0bec6ba615e1 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 13 Jan 2026 15:46:40 +0000 Subject: [PATCH 1/6] fix(offline-transactions): serialize and restore mutation.changes field 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 --- .../src/outbox/TransactionSerializer.ts | 7 ++++--- packages/offline-transactions/src/types.ts | 3 ++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/offline-transactions/src/outbox/TransactionSerializer.ts b/packages/offline-transactions/src/outbox/TransactionSerializer.ts index ff196f7f8..64d8ab459 100644 --- a/packages/offline-transactions/src/outbox/TransactionSerializer.ts +++ b/packages/offline-transactions/src/outbox/TransactionSerializer.ts @@ -7,11 +7,11 @@ import type { import type { Collection, PendingMutation } from '@tanstack/db' export class TransactionSerializer { - // eslint-disable-next-line @typescript-eslint/no-explicit-any + private collections: Record> private collectionIdToKey: Map - // eslint-disable-next-line @typescript-eslint/no-explicit-any + constructor( collections: Record>, ) { @@ -76,6 +76,7 @@ export class TransactionSerializer { type: mutation.type, modified: this.serializeValue(mutation.modified), original: this.serializeValue(mutation.original), + changes: this.serializeValue(mutation.changes), collectionId: registryKey, // Store registry key instead of collection.id } } @@ -93,11 +94,11 @@ export class TransactionSerializer { type: data.type as any, modified: this.deserializeValue(data.modified), original: this.deserializeValue(data.original), + changes: this.deserializeValue(data.changes), collection, // These fields would need to be reconstructed by the executor mutationId: ``, // Will be regenerated key: null, // Will be extracted from the data - changes: {}, // Will be recalculated metadata: undefined, syncMetadata: {}, optimistic: true, diff --git a/packages/offline-transactions/src/types.ts b/packages/offline-transactions/src/types.ts index 8cf18cc88..0230e2382 100644 --- a/packages/offline-transactions/src/types.ts +++ b/packages/offline-transactions/src/types.ts @@ -21,6 +21,7 @@ export interface SerializedMutation { type: string modified: any original: any + changes: any collectionId: string } @@ -88,7 +89,7 @@ export interface StorageDiagnostic { } export interface OfflineConfig { - // eslint-disable-next-line @typescript-eslint/no-explicit-any + collections: Record> mutationFns: Record storage?: StorageAdapter From 049076093101c66b48e13032d402543bdc88d24f Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Tue, 13 Jan 2026 15:48:59 +0000 Subject: [PATCH 2/6] ci: apply automated fixes --- .../offline-transactions/src/outbox/TransactionSerializer.ts | 2 -- packages/offline-transactions/src/types.ts | 1 - 2 files changed, 3 deletions(-) diff --git a/packages/offline-transactions/src/outbox/TransactionSerializer.ts b/packages/offline-transactions/src/outbox/TransactionSerializer.ts index 64d8ab459..4cfd4f4da 100644 --- a/packages/offline-transactions/src/outbox/TransactionSerializer.ts +++ b/packages/offline-transactions/src/outbox/TransactionSerializer.ts @@ -7,11 +7,9 @@ import type { import type { Collection, PendingMutation } from '@tanstack/db' export class TransactionSerializer { - private collections: Record> private collectionIdToKey: Map - constructor( collections: Record>, ) { diff --git a/packages/offline-transactions/src/types.ts b/packages/offline-transactions/src/types.ts index 0230e2382..fc5e1d0ff 100644 --- a/packages/offline-transactions/src/types.ts +++ b/packages/offline-transactions/src/types.ts @@ -89,7 +89,6 @@ export interface StorageDiagnostic { } export interface OfflineConfig { - collections: Record> mutationFns: Record storage?: StorageAdapter From 5571732b73f48c0bf4b3f84368c6dde3f679049f Mon Sep 17 00:00:00 2001 From: Kyle Mathews Date: Tue, 13 Jan 2026 08:56:44 -0700 Subject: [PATCH 3/6] fix(offline-transactions): handle legacy data and improve code quality - 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 --- .../src/outbox/TransactionSerializer.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/offline-transactions/src/outbox/TransactionSerializer.ts b/packages/offline-transactions/src/outbox/TransactionSerializer.ts index 4cfd4f4da..1fa3f5be2 100644 --- a/packages/offline-transactions/src/outbox/TransactionSerializer.ts +++ b/packages/offline-transactions/src/outbox/TransactionSerializer.ts @@ -30,7 +30,7 @@ export class TransactionSerializer { ), } // Convert the whole object to JSON, handling dates - return JSON.stringify(serialized, (key, value) => { + return JSON.stringify(serialized, (_key, value) => { if (value instanceof Date) { return value.toISOString() } @@ -41,7 +41,7 @@ export class TransactionSerializer { deserialize(data: string): OfflineTransaction { const parsed: SerializedOfflineTransaction = JSON.parse( data, - (key, value) => { + (_key, value) => { // Parse ISO date strings back to Date objects if ( typeof value === `string` && @@ -92,7 +92,9 @@ export class TransactionSerializer { type: data.type as any, modified: this.deserializeValue(data.modified), original: this.deserializeValue(data.original), - changes: this.deserializeValue(data.changes), + changes: data.changes !== undefined + ? this.deserializeValue(data.changes) + : {}, collection, // These fields would need to be reconstructed by the executor mutationId: ``, // Will be regenerated @@ -117,7 +119,7 @@ export class TransactionSerializer { if (typeof value === `object`) { const result: any = Array.isArray(value) ? [] : {} for (const key in value) { - if (value.hasOwnProperty(key)) { + if (Object.prototype.hasOwnProperty.call(value, key)) { result[key] = this.serializeValue(value[key]) } } @@ -139,7 +141,7 @@ export class TransactionSerializer { if (typeof value === `object`) { const result: any = Array.isArray(value) ? [] : {} for (const key in value) { - if (value.hasOwnProperty(key)) { + if (Object.prototype.hasOwnProperty.call(value, key)) { result[key] = this.deserializeValue(value[key]) } } From e2a6c580593134f7bb249a29d4e07471e18053e5 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Tue, 13 Jan 2026 15:59:10 +0000 Subject: [PATCH 4/6] ci: apply automated fixes --- .../offline-transactions/src/outbox/TransactionSerializer.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/offline-transactions/src/outbox/TransactionSerializer.ts b/packages/offline-transactions/src/outbox/TransactionSerializer.ts index 1fa3f5be2..e1c549c23 100644 --- a/packages/offline-transactions/src/outbox/TransactionSerializer.ts +++ b/packages/offline-transactions/src/outbox/TransactionSerializer.ts @@ -92,9 +92,8 @@ export class TransactionSerializer { type: data.type as any, modified: this.deserializeValue(data.modified), original: this.deserializeValue(data.original), - changes: data.changes !== undefined - ? this.deserializeValue(data.changes) - : {}, + changes: + data.changes !== undefined ? this.deserializeValue(data.changes) : {}, collection, // These fields would need to be reconstructed by the executor mutationId: ``, // Will be regenerated From 5e750bbd71b65487c423223d3293999eb97f07f4 Mon Sep 17 00:00:00 2001 From: Kyle Mathews Date: Tue, 13 Jan 2026 08:59:23 -0700 Subject: [PATCH 5/6] chore: add changeset for offline mutations fix Co-Authored-By: Claude Opus 4.5 --- .changeset/fix-offline-mutations-changes-field.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/fix-offline-mutations-changes-field.md diff --git a/.changeset/fix-offline-mutations-changes-field.md b/.changeset/fix-offline-mutations-changes-field.md new file mode 100644 index 000000000..8fc9eb37f --- /dev/null +++ b/.changeset/fix-offline-mutations-changes-field.md @@ -0,0 +1,5 @@ +--- +'@tanstack/offline-transactions': patch +--- + +Fix mutation.changes field being lost during offline transaction serialization. Previously, the changes field was not included in serialized mutations, causing it to be empty ({}) after app restart. This led to sync functions receiving incomplete data when using mutation.changes for partial updates. From af0f61493c5ac54348f949053fbb56c43f377bea Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Mon, 19 Jan 2026 16:31:43 +0000 Subject: [PATCH 6/6] ci: apply automated fixes --- .claude/settings.local.json | 5 +---- packages/db-collections/package.json | 2 +- pnpm-lock.yaml | 2 ++ 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 26ee58938..f3509559e 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -1,8 +1,5 @@ { "permissions": { - "allow": [ - "Bash(git checkout:*)", - "Bash(npx sherif:*)" - ] + "allow": ["Bash(git checkout:*)", "Bash(npx sherif:*)"] } } diff --git a/packages/db-collections/package.json b/packages/db-collections/package.json index 234715ab8..074afba15 100644 --- a/packages/db-collections/package.json +++ b/packages/db-collections/package.json @@ -2,4 +2,4 @@ "name": "db-collections", "version": "0.0.0", "private": true -} \ No newline at end of file +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a7b7a4ae4..f415ce675 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -842,6 +842,8 @@ importers: specifier: ^3.2.4 version: 3.2.4(@types/debug@4.1.12)(@types/node@24.7.0)(@vitest/ui@3.2.4(vitest@3.2.4))(jiti@2.6.1)(jsdom@27.4.0)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) + packages/db-collections: {} + packages/db-ivm: dependencies: fractional-indexing: