UNPKG

@tanstack/offline-transactions

Version:

Offline-first transaction capabilities for TanStack DB

1 lines • 7.18 kB
{"version":3,"file":"OfflineTransaction.cjs","sources":["../../../src/api/OfflineTransaction.ts"],"sourcesContent":["import { createTransaction, safeRandomUUID } from '@tanstack/db'\nimport type { PendingMutation, Transaction } from '@tanstack/db'\nimport type {\n CreateOfflineTransactionOptions,\n OfflineMutationFn,\n OfflineTransaction as OfflineTransactionType,\n} from '../types'\n\nexport class OfflineTransaction {\n private offlineId: string\n private mutationFnName: string\n private autoCommit: boolean | undefined\n private idempotencyKey: string\n private metadata: Record<string, any>\n private transaction: Transaction | null = null\n /** The mutation function ran, so the executor settles the waiter. */\n private persisting = false\n private persistTransaction: (tx: OfflineTransactionType) => Promise<void>\n private executor: any // Will be typed properly - reference to OfflineExecutor\n\n constructor(\n options: CreateOfflineTransactionOptions,\n mutationFn: OfflineMutationFn,\n persistTransaction: (tx: OfflineTransactionType) => Promise<void>,\n executor: any,\n ) {\n this.offlineId = safeRandomUUID()\n this.mutationFnName = options.mutationFnName\n // The TanStack DB transaction applies the default.\n this.autoCommit = options.autoCommit\n this.idempotencyKey = options.idempotencyKey ?? safeRandomUUID()\n this.metadata = options.metadata ?? {}\n this.persistTransaction = persistTransaction\n this.executor = executor\n }\n\n mutate(callback: () => void): Transaction {\n // One offline transaction is one transaction, and its id is unique among\n // live transactions. Repeated calls add to it while it is pending. With\n // autoCommit, it commits after this callback and reports a failure\n // through `when('settled')`, so a later call throws: it is no longer pending.\n if (!this.transaction) {\n this.transaction = createTransaction({\n id: this.offlineId,\n autoCommit: this.autoCommit,\n mutationFn: async () => {\n this.persisting = true\n // This is the blocking mutationFn that waits for the executor\n // First persist the transaction to the outbox\n const offlineTransaction: OfflineTransactionType = {\n id: this.offlineId,\n mutationFnName: this.mutationFnName,\n mutations: this.transaction!.mutations,\n keys: this.extractKeys(this.transaction!.mutations),\n idempotencyKey: this.idempotencyKey,\n createdAt: new Date(),\n retryCount: 0,\n nextAttemptAt: Date.now(),\n metadata: this.metadata,\n spanContext: undefined,\n version: 1,\n }\n\n const completionPromise = this.executor.waitForTransactionCompletion(\n this.offlineId,\n )\n\n try {\n // Persistence also drives the shared queue. This transaction can finish\n // before that queue drains; observe both promises from the outset.\n await Promise.race([\n this.persistTransaction(offlineTransaction),\n completionPromise,\n ])\n // A queue pause (offline or retry) is not transaction completion.\n await completionPromise\n } catch (error) {\n const normalizedError =\n error instanceof Error ? error : new Error(String(error))\n this.executor.rejectTransaction(this.offlineId, normalizedError)\n throw error\n }\n\n return\n },\n metadata: this.metadata,\n })\n // A transaction without mutations completes, and one rolled back before\n // it commits fails, without its mutation function, so the executor never\n // settles its waiter. Settle that waiter here with the same outcome. Once\n // the mutation function runs, only the executor settles it.\n void this.transaction.when(`settled`).then(\n () => {\n if (!this.persisting)\n this.executor.resolveTransaction(this.offlineId, undefined)\n },\n (error: unknown) => {\n if (!this.persisting)\n this.executor.rejectTransaction(\n this.offlineId,\n error instanceof Error\n ? error\n : new Error(`Offline transaction was rolled back`),\n )\n },\n )\n }\n\n this.transaction.mutate(() => {\n callback()\n })\n\n return this.transaction\n }\n\n async commit(): Promise<Transaction> {\n if (!this.transaction) {\n throw new Error(`No mutations to commit. Call mutate() first.`)\n }\n\n // The mutationFn persists the transaction and waits for the executor. A\n // commit that rejects has already rolled the transaction back. A sibling's\n // failure can also roll it back while it waits, and then commit() resolves,\n // so `when('settled')` is the receipt.\n await this.transaction.commit()\n return this.transaction\n }\n\n rollback(): void {\n if (this.transaction) {\n this.transaction.rollback()\n }\n }\n\n private extractKeys(mutations: Array<PendingMutation>): Array<string> {\n return mutations.map((mutation) => mutation.globalKey)\n }\n\n get id(): string {\n return this.offlineId\n }\n}\n"],"names":["safeRandomUUID","createTransaction"],"mappings":";;;AAQO,MAAM,mBAAmB;AAAA;AAAA,EAY9B,YACE,SACA,YACA,oBACA,UACA;AAXF,SAAQ,cAAkC;AAE1C,SAAQ,aAAa;AAUnB,SAAK,YAAYA,kBAAA;AACjB,SAAK,iBAAiB,QAAQ;AAE9B,SAAK,aAAa,QAAQ;AAC1B,SAAK,iBAAiB,QAAQ,kBAAkBA,GAAAA,eAAA;AAChD,SAAK,WAAW,QAAQ,YAAY,CAAA;AACpC,SAAK,qBAAqB;AAC1B,SAAK,WAAW;AAAA,EAClB;AAAA,EAEA,OAAO,UAAmC;AAKxC,QAAI,CAAC,KAAK,aAAa;AACrB,WAAK,cAAcC,qBAAkB;AAAA,QACnC,IAAI,KAAK;AAAA,QACT,YAAY,KAAK;AAAA,QACjB,YAAY,YAAY;AACtB,eAAK,aAAa;AAGlB,gBAAM,qBAA6C;AAAA,YACjD,IAAI,KAAK;AAAA,YACT,gBAAgB,KAAK;AAAA,YACrB,WAAW,KAAK,YAAa;AAAA,YAC7B,MAAM,KAAK,YAAY,KAAK,YAAa,SAAS;AAAA,YAClD,gBAAgB,KAAK;AAAA,YACrB,+BAAe,KAAA;AAAA,YACf,YAAY;AAAA,YACZ,eAAe,KAAK,IAAA;AAAA,YACpB,UAAU,KAAK;AAAA,YACf,aAAa;AAAA,YACb,SAAS;AAAA,UAAA;AAGX,gBAAM,oBAAoB,KAAK,SAAS;AAAA,YACtC,KAAK;AAAA,UAAA;AAGP,cAAI;AAGF,kBAAM,QAAQ,KAAK;AAAA,cACjB,KAAK,mBAAmB,kBAAkB;AAAA,cAC1C;AAAA,YAAA,CACD;AAED,kBAAM;AAAA,UACR,SAAS,OAAO;AACd,kBAAM,kBACJ,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;AAC1D,iBAAK,SAAS,kBAAkB,KAAK,WAAW,eAAe;AAC/D,kBAAM;AAAA,UACR;AAEA;AAAA,QACF;AAAA,QACA,UAAU,KAAK;AAAA,MAAA,CAChB;AAKD,WAAK,KAAK,YAAY,KAAK,SAAS,EAAE;AAAA,QACpC,MAAM;AACJ,cAAI,CAAC,KAAK;AACR,iBAAK,SAAS,mBAAmB,KAAK,WAAW,MAAS;AAAA,QAC9D;AAAA,QACA,CAAC,UAAmB;AAClB,cAAI,CAAC,KAAK;AACR,iBAAK,SAAS;AAAA,cACZ,KAAK;AAAA,cACL,iBAAiB,QACb,QACA,IAAI,MAAM,qCAAqC;AAAA,YAAA;AAAA,QAEzD;AAAA,MAAA;AAAA,IAEJ;AAEA,SAAK,YAAY,OAAO,MAAM;AAC5B,eAAA;AAAA,IACF,CAAC;AAED,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAM,SAA+B;AACnC,QAAI,CAAC,KAAK,aAAa;AACrB,YAAM,IAAI,MAAM,8CAA8C;AAAA,IAChE;AAMA,UAAM,KAAK,YAAY,OAAA;AACvB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,WAAiB;AACf,QAAI,KAAK,aAAa;AACpB,WAAK,YAAY,SAAA;AAAA,IACnB;AAAA,EACF;AAAA,EAEQ,YAAY,WAAkD;AACpE,WAAO,UAAU,IAAI,CAAC,aAAa,SAAS,SAAS;AAAA,EACvD;AAAA,EAEA,IAAI,KAAa;AACf,WAAO,KAAK;AAAA,EACd;AACF;;"}