UNPKG

@tanstack/offline-transactions

Version:

Offline-first transaction capabilities for TanStack DB

1 lines 8.4 kB
{"version":3,"file":"TransactionSerializer.cjs","sources":["../../../src/outbox/TransactionSerializer.ts"],"sourcesContent":["import type {\n OfflineTransaction,\n SerializedError,\n SerializedMutation,\n SerializedOfflineTransaction,\n} from '../types'\nimport type { Collection, PendingMutation } from '@tanstack/db'\n\nexport class TransactionSerializer {\n private collections: Record<string, Collection<any, any, any, any, any>>\n private collectionIdToKey: Map<string, string>\n\n constructor(\n collections: Record<string, Collection<any, any, any, any, any>>,\n ) {\n this.collections = collections\n // Create reverse lookup from collection.id to registry key\n this.collectionIdToKey = new Map()\n for (const [key, collection] of Object.entries(collections)) {\n this.collectionIdToKey.set(collection.id, key)\n }\n }\n\n serialize(transaction: OfflineTransaction): string {\n const serialized: SerializedOfflineTransaction = {\n ...transaction,\n createdAt: transaction.createdAt.toISOString(),\n mutations: transaction.mutations.map((mutation) =>\n this.serializeMutation(mutation),\n ),\n }\n return JSON.stringify(serialized)\n }\n\n deserialize(data: string): OfflineTransaction {\n // Parse without a reviver - let deserializeValue handle dates in mutation data\n // using the { __type: 'Date' } marker system\n const parsed: SerializedOfflineTransaction = JSON.parse(data)\n\n const createdAt = new Date(parsed.createdAt)\n if (isNaN(createdAt.getTime())) {\n throw new Error(\n `Failed to deserialize transaction: invalid createdAt value \"${parsed.createdAt}\"`,\n )\n }\n\n return {\n ...parsed,\n createdAt,\n mutations: parsed.mutations.map((mutationData) =>\n this.deserializeMutation(mutationData),\n ),\n }\n }\n\n private serializeMutation(mutation: PendingMutation): SerializedMutation {\n const registryKey = this.collectionIdToKey.get(mutation.collection.id)\n if (!registryKey) {\n throw new Error(\n `Collection with id ${mutation.collection.id} not found in registry`,\n )\n }\n\n return {\n globalKey: mutation.globalKey,\n type: mutation.type,\n modified: this.serializeValue(mutation.modified),\n original: this.serializeValue(mutation.original),\n changes: this.serializeValue(mutation.changes),\n collectionId: registryKey, // Store registry key instead of collection.id\n }\n }\n\n private deserializeMutation(data: SerializedMutation): PendingMutation {\n const collection = this.collections[data.collectionId]\n if (!collection) {\n throw new Error(`Collection with id ${data.collectionId} not found`)\n }\n\n const modified = this.deserializeValue(data.modified)\n\n // Extract the key from the modified data using the collection's getKey function\n // This is needed for optimistic state restoration to work correctly\n const key = modified ? collection.getKeyFromItem(modified) : null\n\n // Create a partial PendingMutation - we can't fully reconstruct it but\n // we provide what we can. The executor will need to handle the rest.\n return {\n globalKey: data.globalKey,\n type: data.type as any,\n modified,\n original: this.deserializeValue(data.original),\n changes: this.deserializeValue(data.changes) ?? {},\n collection,\n // These fields would need to be reconstructed by the executor\n mutationId: ``, // Will be regenerated\n key,\n metadata: undefined,\n syncMetadata: {},\n optimistic: true,\n createdAt: new Date(),\n updatedAt: new Date(),\n } as PendingMutation\n }\n\n private serializeValue(value: any): any {\n if (value === null || value === undefined) {\n return value\n }\n\n if (value instanceof Date) {\n return { __type: `Date`, value: value.toISOString() }\n }\n\n if (typeof value === `object`) {\n const result: any = Array.isArray(value) ? [] : {}\n for (const key in value) {\n if (Object.prototype.hasOwnProperty.call(value, key)) {\n result[key] = this.serializeValue(value[key])\n }\n }\n return result\n }\n\n return value\n }\n\n private deserializeValue(value: any): any {\n if (value === null || value === undefined) {\n return value\n }\n\n if (typeof value === `object` && value.__type === `Date`) {\n if (value.value === undefined || value.value === null) {\n throw new Error(`Corrupted Date marker: missing value field`)\n }\n const date = new Date(value.value)\n if (isNaN(date.getTime())) {\n throw new Error(\n `Failed to deserialize Date marker: invalid date value \"${value.value}\"`,\n )\n }\n return date\n }\n\n if (typeof value === `object`) {\n const result: any = Array.isArray(value) ? [] : {}\n for (const key in value) {\n if (Object.prototype.hasOwnProperty.call(value, key)) {\n result[key] = this.deserializeValue(value[key])\n }\n }\n return result\n }\n\n return value\n }\n\n serializeError(error: Error): SerializedError {\n return {\n name: error.name,\n message: error.message,\n stack: error.stack,\n }\n }\n\n deserializeError(data: SerializedError): Error {\n const error = new Error(data.message)\n error.name = data.name\n error.stack = data.stack\n return error\n }\n}\n"],"names":[],"mappings":"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"}