@tanstack/offline-transactions 0.1.2 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/api/OfflineAction.cjs +8 -20
- package/dist/cjs/api/OfflineAction.cjs.map +1 -1
- package/dist/cjs/api/OfflineTransaction.cjs +1 -9
- package/dist/cjs/api/OfflineTransaction.cjs.map +1 -1
- package/dist/cjs/executor/TransactionExecutor.cjs +0 -15
- package/dist/cjs/executor/TransactionExecutor.cjs.map +1 -1
- package/dist/cjs/outbox/OutboxManager.cjs +3 -3
- package/dist/cjs/outbox/OutboxManager.cjs.map +1 -1
- package/dist/cjs/outbox/OutboxManager.d.cts +1 -1
- package/dist/cjs/outbox/TransactionSerializer.cjs +1 -0
- package/dist/cjs/outbox/TransactionSerializer.cjs.map +1 -1
- package/dist/cjs/outbox/TransactionSerializer.d.cts +1 -1
- package/dist/cjs/telemetry/tracer.cjs +17 -81
- package/dist/cjs/telemetry/tracer.cjs.map +1 -1
- package/dist/cjs/telemetry/tracer.d.cts +9 -8
- package/dist/cjs/types.cjs.map +1 -1
- package/dist/cjs/types.d.cts +1 -1
- package/dist/esm/api/OfflineAction.js +8 -20
- package/dist/esm/api/OfflineAction.js.map +1 -1
- package/dist/esm/api/OfflineTransaction.js +1 -9
- package/dist/esm/api/OfflineTransaction.js.map +1 -1
- package/dist/esm/executor/TransactionExecutor.js +0 -15
- package/dist/esm/executor/TransactionExecutor.js.map +1 -1
- package/dist/esm/outbox/OutboxManager.d.ts +1 -1
- package/dist/esm/outbox/OutboxManager.js +3 -3
- package/dist/esm/outbox/OutboxManager.js.map +1 -1
- package/dist/esm/outbox/TransactionSerializer.d.ts +1 -1
- package/dist/esm/outbox/TransactionSerializer.js +1 -0
- package/dist/esm/outbox/TransactionSerializer.js.map +1 -1
- package/dist/esm/telemetry/tracer.d.ts +9 -8
- package/dist/esm/telemetry/tracer.js +17 -81
- package/dist/esm/telemetry/tracer.js.map +1 -1
- package/dist/esm/types.d.ts +1 -1
- package/dist/esm/types.js.map +1 -1
- package/package.json +4 -6
- package/src/api/OfflineAction.ts +9 -25
- package/src/api/OfflineTransaction.ts +1 -12
- package/src/executor/TransactionExecutor.ts +1 -27
- package/src/outbox/OutboxManager.ts +2 -1
- package/src/outbox/TransactionSerializer.ts +6 -2
- package/src/telemetry/tracer.ts +22 -107
- package/src/types.ts +2 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OfflineAction.js","sources":["../../../src/api/OfflineAction.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"OfflineAction.js","sources":["../../../src/api/OfflineAction.ts"],"sourcesContent":["import { OnMutateMustBeSynchronousError } from \"@tanstack/db\"\nimport { OfflineTransaction } from \"./OfflineTransaction\"\nimport type { Transaction } from \"@tanstack/db\"\nimport type {\n CreateOfflineActionOptions,\n OfflineMutationFn,\n OfflineTransaction as OfflineTransactionType,\n} from \"../types\"\n\nfunction isPromiseLike(value: unknown): value is PromiseLike<unknown> {\n return (\n !!value &&\n (typeof value === `object` || typeof value === `function`) &&\n typeof (value as { then?: unknown }).then === `function`\n )\n}\n\nexport function createOfflineAction<T>(\n options: CreateOfflineActionOptions<T>,\n mutationFn: OfflineMutationFn,\n persistTransaction: (tx: OfflineTransactionType) => Promise<void>,\n executor: any\n): (variables: T) => Transaction {\n const { mutationFnName, onMutate } = options\n console.log(`createOfflineAction 2`, options)\n\n return (variables: T): Transaction => {\n const offlineTransaction = new OfflineTransaction(\n {\n mutationFnName,\n autoCommit: false,\n },\n mutationFn,\n persistTransaction,\n executor\n )\n\n const transaction = offlineTransaction.mutate(() => {\n console.log(`mutate`)\n const maybePromise = onMutate(variables) as unknown\n\n if (isPromiseLike(maybePromise)) {\n throw new OnMutateMustBeSynchronousError()\n }\n })\n\n // Immediately commit\n const commitPromise = (async () => {\n try {\n await transaction.commit()\n console.log(`offlineAction committed - success`)\n } catch {\n console.log(`offlineAction commit failed - error`)\n }\n })()\n\n // Don't await - this is fire-and-forget for optimistic actions\n // But catch to prevent unhandled rejection\n commitPromise.catch(() => {\n // Already handled in try/catch above\n })\n\n return transaction\n }\n}\n"],"names":[],"mappings":";;AASA,SAAS,cAAc,OAA+C;AACpE,SACE,CAAC,CAAC,UACD,OAAO,UAAU,YAAY,OAAO,UAAU,eAC/C,OAAQ,MAA6B,SAAS;AAElD;AAEO,SAAS,oBACd,SACA,YACA,oBACA,UAC+B;AAC/B,QAAM,EAAE,gBAAgB,SAAA,IAAa;AACrC,UAAQ,IAAI,yBAAyB,OAAO;AAE5C,SAAO,CAAC,cAA8B;AACpC,UAAM,qBAAqB,IAAI;AAAA,MAC7B;AAAA,QACE;AAAA,QACA,YAAY;AAAA,MAAA;AAAA,MAEd;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAGF,UAAM,cAAc,mBAAmB,OAAO,MAAM;AAClD,cAAQ,IAAI,QAAQ;AACpB,YAAM,eAAe,SAAS,SAAS;AAEvC,UAAI,cAAc,YAAY,GAAG;AAC/B,cAAM,IAAI,+BAAA;AAAA,MACZ;AAAA,IACF,CAAC;AAGD,UAAM,iBAAiB,YAAY;AACjC,UAAI;AACF,cAAM,YAAY,OAAA;AAClB,gBAAQ,IAAI,mCAAmC;AAAA,MACjD,QAAQ;AACN,gBAAQ,IAAI,qCAAqC;AAAA,MACnD;AAAA,IACF,GAAA;AAIA,kBAAc,MAAM,MAAM;AAAA,IAE1B,CAAC;AAED,WAAO;AAAA,EACT;AACF;"}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { trace, context } from "@opentelemetry/api";
|
|
2
1
|
import { createTransaction } from "@tanstack/db";
|
|
3
2
|
import { NonRetriableError } from "../types.js";
|
|
4
3
|
class OfflineTransaction {
|
|
@@ -18,8 +17,6 @@ class OfflineTransaction {
|
|
|
18
17
|
id: this.offlineId,
|
|
19
18
|
autoCommit: false,
|
|
20
19
|
mutationFn: async () => {
|
|
21
|
-
const activeSpan = trace.getSpan(context.active());
|
|
22
|
-
const spanContext = activeSpan?.spanContext();
|
|
23
20
|
const offlineTransaction = {
|
|
24
21
|
id: this.offlineId,
|
|
25
22
|
mutationFnName: this.mutationFnName,
|
|
@@ -30,12 +27,7 @@ class OfflineTransaction {
|
|
|
30
27
|
retryCount: 0,
|
|
31
28
|
nextAttemptAt: Date.now(),
|
|
32
29
|
metadata: this.metadata,
|
|
33
|
-
spanContext:
|
|
34
|
-
traceId: spanContext.traceId,
|
|
35
|
-
spanId: spanContext.spanId,
|
|
36
|
-
traceFlags: spanContext.traceFlags,
|
|
37
|
-
traceState: spanContext.traceState?.serialize()
|
|
38
|
-
} : void 0,
|
|
30
|
+
spanContext: void 0,
|
|
39
31
|
version: 1
|
|
40
32
|
};
|
|
41
33
|
const completionPromise = this.executor.waitForTransactionCompletion(
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OfflineTransaction.js","sources":["../../../src/api/OfflineTransaction.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"OfflineTransaction.js","sources":["../../../src/api/OfflineTransaction.ts"],"sourcesContent":["import { createTransaction } from \"@tanstack/db\"\nimport { NonRetriableError } from \"../types\"\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\n private idempotencyKey: string\n private metadata: Record<string, any>\n private transaction: Transaction | null = null\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 = crypto.randomUUID()\n this.mutationFnName = options.mutationFnName\n this.autoCommit = options.autoCommit ?? true\n this.idempotencyKey = options.idempotencyKey ?? crypto.randomUUID()\n this.metadata = options.metadata ?? {}\n this.persistTransaction = persistTransaction\n this.executor = executor\n }\n\n mutate(callback: () => void): Transaction {\n this.transaction = createTransaction({\n id: this.offlineId,\n autoCommit: false,\n mutationFn: async () => {\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 await this.persistTransaction(offlineTransaction)\n // Now block and wait for the executor to complete the real mutation\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\n this.transaction.mutate(() => {\n callback()\n })\n\n if (this.autoCommit) {\n // Auto-commit for direct OfflineTransaction usage\n this.commit().catch((error) => {\n console.error(`Auto-commit failed:`, error)\n throw error\n })\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 try {\n // Commit the TanStack DB transaction\n // This will trigger the mutationFn which handles persistence and waiting\n await this.transaction.commit()\n return this.transaction\n } catch (error) {\n // Only rollback for NonRetriableError - other errors should allow retry\n if (error instanceof NonRetriableError) {\n this.transaction.rollback()\n }\n throw error\n }\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":[],"mappings":";;AASO,MAAM,mBAAmB;AAAA;AAAA,EAU9B,YACE,SACA,YACA,oBACA,UACA;AATF,SAAQ,cAAkC;AAUxC,SAAK,YAAY,OAAO,WAAA;AACxB,SAAK,iBAAiB,QAAQ;AAC9B,SAAK,aAAa,QAAQ,cAAc;AACxC,SAAK,iBAAiB,QAAQ,kBAAkB,OAAO,WAAA;AACvD,SAAK,WAAW,QAAQ,YAAY,CAAA;AACpC,SAAK,qBAAqB;AAC1B,SAAK,WAAW;AAAA,EAClB;AAAA,EAEA,OAAO,UAAmC;AACxC,SAAK,cAAc,kBAAkB;AAAA,MACnC,IAAI,KAAK;AAAA,MACT,YAAY;AAAA,MACZ,YAAY,YAAY;AAGtB,cAAM,qBAA6C;AAAA,UACjD,IAAI,KAAK;AAAA,UACT,gBAAgB,KAAK;AAAA,UACrB,WAAW,KAAK,YAAa;AAAA,UAC7B,MAAM,KAAK,YAAY,KAAK,YAAa,SAAS;AAAA,UAClD,gBAAgB,KAAK;AAAA,UACrB,+BAAe,KAAA;AAAA,UACf,YAAY;AAAA,UACZ,eAAe,KAAK,IAAA;AAAA,UACpB,UAAU,KAAK;AAAA,UACf,aAAa;AAAA,UACb,SAAS;AAAA,QAAA;AAGX,cAAM,oBAAoB,KAAK,SAAS;AAAA,UACtC,KAAK;AAAA,QAAA;AAGP,YAAI;AACF,gBAAM,KAAK,mBAAmB,kBAAkB;AAEhD,gBAAM;AAAA,QACR,SAAS,OAAO;AACd,gBAAM,kBACJ,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;AAC1D,eAAK,SAAS,kBAAkB,KAAK,WAAW,eAAe;AAC/D,gBAAM;AAAA,QACR;AAEA;AAAA,MACF;AAAA,MACA,UAAU,KAAK;AAAA,IAAA,CAChB;AAED,SAAK,YAAY,OAAO,MAAM;AAC5B,eAAA;AAAA,IACF,CAAC;AAED,QAAI,KAAK,YAAY;AAEnB,WAAK,OAAA,EAAS,MAAM,CAAC,UAAU;AAC7B,gBAAQ,MAAM,uBAAuB,KAAK;AAC1C,cAAM;AAAA,MACR,CAAC;AAAA,IACH;AAEA,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAM,SAA+B;AACnC,QAAI,CAAC,KAAK,aAAa;AACrB,YAAM,IAAI,MAAM,8CAA8C;AAAA,IAChE;AAEA,QAAI;AAGF,YAAM,KAAK,YAAY,OAAA;AACvB,aAAO,KAAK;AAAA,IACd,SAAS,OAAO;AAEd,UAAI,iBAAiB,mBAAmB;AACtC,aAAK,YAAY,SAAA;AAAA,MACnB;AACA,YAAM;AAAA,IACR;AAAA,EACF;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;"}
|
|
@@ -1,19 +1,7 @@
|
|
|
1
|
-
import { createTraceState } from "@opentelemetry/api";
|
|
2
1
|
import { DefaultRetryPolicy } from "../retry/RetryPolicy.js";
|
|
3
2
|
import { NonRetriableError } from "../types.js";
|
|
4
3
|
import { withNestedSpan } from "../telemetry/tracer.js";
|
|
5
4
|
const HANDLED_EXECUTION_ERROR = Symbol(`HandledExecutionError`);
|
|
6
|
-
function toSpanContext(serialized) {
|
|
7
|
-
if (!serialized) {
|
|
8
|
-
return void 0;
|
|
9
|
-
}
|
|
10
|
-
return {
|
|
11
|
-
traceId: serialized.traceId,
|
|
12
|
-
spanId: serialized.spanId,
|
|
13
|
-
traceFlags: serialized.traceFlags,
|
|
14
|
-
traceState: serialized.traceState ? createTraceState(serialized.traceState) : void 0
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
5
|
class TransactionExecutor {
|
|
18
6
|
constructor(scheduler, outbox, config, offlineExecutor) {
|
|
19
7
|
this.isExecuting = false;
|
|
@@ -84,9 +72,6 @@ class TransactionExecutor {
|
|
|
84
72
|
err[HANDLED_EXECUTION_ERROR] = true;
|
|
85
73
|
throw err;
|
|
86
74
|
}
|
|
87
|
-
},
|
|
88
|
-
{
|
|
89
|
-
parentContext: toSpanContext(transaction.spanContext)
|
|
90
75
|
}
|
|
91
76
|
);
|
|
92
77
|
} catch (error) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TransactionExecutor.js","sources":["../../../src/executor/TransactionExecutor.ts"],"sourcesContent":["import { createTraceState } from \"@opentelemetry/api\"\nimport { DefaultRetryPolicy } from \"../retry/RetryPolicy\"\nimport { NonRetriableError } from \"../types\"\nimport { withNestedSpan } from \"../telemetry/tracer\"\nimport type { SpanContext } from \"@opentelemetry/api\"\nimport type { KeyScheduler } from \"./KeyScheduler\"\nimport type { OutboxManager } from \"../outbox/OutboxManager\"\nimport type {\n OfflineConfig,\n OfflineTransaction,\n SerializedSpanContext,\n} from \"../types\"\n\nconst HANDLED_EXECUTION_ERROR = Symbol(`HandledExecutionError`)\n\nfunction toSpanContext(\n serialized?: SerializedSpanContext\n): SpanContext | undefined {\n if (!serialized) {\n return undefined\n }\n\n return {\n traceId: serialized.traceId,\n spanId: serialized.spanId,\n traceFlags: serialized.traceFlags,\n traceState: serialized.traceState\n ? createTraceState(serialized.traceState)\n : undefined,\n }\n}\n\nexport class TransactionExecutor {\n private scheduler: KeyScheduler\n private outbox: OutboxManager\n private config: OfflineConfig\n private retryPolicy: DefaultRetryPolicy\n private isExecuting = false\n private executionPromise: Promise<void> | null = null\n private offlineExecutor: any // Reference to OfflineExecutor for signaling\n private retryTimer: ReturnType<typeof setTimeout> | null = null\n\n constructor(\n scheduler: KeyScheduler,\n outbox: OutboxManager,\n config: OfflineConfig,\n offlineExecutor: any\n ) {\n this.scheduler = scheduler\n this.outbox = outbox\n this.config = config\n this.retryPolicy = new DefaultRetryPolicy(10, config.jitter ?? true)\n this.offlineExecutor = offlineExecutor\n }\n\n async execute(transaction: OfflineTransaction): Promise<void> {\n this.scheduler.schedule(transaction)\n await this.executeAll()\n }\n\n async executeAll(): Promise<void> {\n if (this.isExecuting) {\n return this.executionPromise!\n }\n\n this.isExecuting = true\n this.executionPromise = this.runExecution()\n\n try {\n await this.executionPromise\n } finally {\n this.isExecuting = false\n this.executionPromise = null\n }\n }\n\n private async runExecution(): Promise<void> {\n const maxConcurrency = this.config.maxConcurrency ?? 3\n\n while (this.scheduler.getPendingCount() > 0) {\n const batch = this.scheduler.getNextBatch(maxConcurrency)\n\n if (batch.length === 0) {\n break\n }\n\n const executions = batch.map((transaction) =>\n this.executeTransaction(transaction)\n )\n await Promise.allSettled(executions)\n }\n\n // Schedule next retry after execution completes\n this.scheduleNextRetry()\n }\n\n private async executeTransaction(\n transaction: OfflineTransaction\n ): Promise<void> {\n try {\n await withNestedSpan(\n `transaction.execute`,\n {\n \"transaction.id\": transaction.id,\n \"transaction.mutationFnName\": transaction.mutationFnName,\n \"transaction.retryCount\": transaction.retryCount,\n \"transaction.keyCount\": transaction.keys.length,\n },\n async (span) => {\n this.scheduler.markStarted(transaction)\n\n if (transaction.retryCount > 0) {\n span.setAttribute(`retry.attempt`, transaction.retryCount)\n }\n\n try {\n const result = await this.runMutationFn(transaction)\n\n this.scheduler.markCompleted(transaction)\n await this.outbox.remove(transaction.id)\n\n span.setAttribute(`result`, `success`)\n this.offlineExecutor.resolveTransaction(transaction.id, result)\n } catch (error) {\n const err =\n error instanceof Error ? error : new Error(String(error))\n\n span.setAttribute(`result`, `error`)\n\n await this.handleError(transaction, err)\n ;(err as any)[HANDLED_EXECUTION_ERROR] = true\n throw err\n }\n },\n {\n parentContext: toSpanContext(transaction.spanContext),\n }\n )\n } catch (error) {\n if (\n error instanceof Error &&\n (error as any)[HANDLED_EXECUTION_ERROR] === true\n ) {\n return\n }\n\n throw error\n }\n }\n\n private async runMutationFn(transaction: OfflineTransaction): Promise<void> {\n const mutationFn = this.config.mutationFns[transaction.mutationFnName]\n\n if (!mutationFn) {\n const errorMessage = `Unknown mutation function: ${transaction.mutationFnName}`\n\n if (this.config.onUnknownMutationFn) {\n this.config.onUnknownMutationFn(transaction.mutationFnName, transaction)\n }\n\n throw new NonRetriableError(errorMessage)\n }\n\n // Mutations are already PendingMutation objects with collections attached\n // from the deserializer, so we can use them directly\n const transactionWithMutations = {\n id: transaction.id,\n mutations: transaction.mutations,\n metadata: transaction.metadata ?? {},\n }\n\n await mutationFn({\n transaction: transactionWithMutations as any,\n idempotencyKey: transaction.idempotencyKey,\n })\n }\n\n private async handleError(\n transaction: OfflineTransaction,\n error: Error\n ): Promise<void> {\n return withNestedSpan(\n `transaction.handleError`,\n {\n \"transaction.id\": transaction.id,\n \"error.name\": error.name,\n \"error.message\": error.message,\n },\n async (span) => {\n const shouldRetry = this.retryPolicy.shouldRetry(\n error,\n transaction.retryCount\n )\n\n span.setAttribute(`shouldRetry`, shouldRetry)\n\n if (!shouldRetry) {\n this.scheduler.markCompleted(transaction)\n await this.outbox.remove(transaction.id)\n console.warn(\n `Transaction ${transaction.id} failed permanently:`,\n error\n )\n\n span.setAttribute(`result`, `permanent_failure`)\n // Signal permanent failure to the waiting transaction\n this.offlineExecutor.rejectTransaction(transaction.id, error)\n return\n }\n\n const delay = this.retryPolicy.calculateDelay(transaction.retryCount)\n const updatedTransaction: OfflineTransaction = {\n ...transaction,\n retryCount: transaction.retryCount + 1,\n nextAttemptAt: Date.now() + delay,\n lastError: {\n name: error.name,\n message: error.message,\n stack: error.stack,\n },\n }\n\n span.setAttribute(`retryDelay`, delay)\n span.setAttribute(`nextRetryCount`, updatedTransaction.retryCount)\n\n this.scheduler.markFailed(transaction)\n this.scheduler.updateTransaction(updatedTransaction)\n\n try {\n await this.outbox.update(transaction.id, updatedTransaction)\n span.setAttribute(`result`, `scheduled_retry`)\n } catch (persistError) {\n span.recordException(persistError as Error)\n span.setAttribute(`result`, `persist_failed`)\n throw persistError\n }\n\n // Schedule retry timer\n this.scheduleNextRetry()\n }\n )\n }\n\n async loadPendingTransactions(): Promise<void> {\n const transactions = await this.outbox.getAll()\n let filteredTransactions = transactions\n\n if (this.config.beforeRetry) {\n filteredTransactions = this.config.beforeRetry(transactions)\n }\n\n for (const transaction of filteredTransactions) {\n this.scheduler.schedule(transaction)\n }\n\n // Reset retry delays for all loaded transactions so they can run immediately\n this.resetRetryDelays()\n\n // Schedule retry timer for loaded transactions\n this.scheduleNextRetry()\n\n const removedTransactions = transactions.filter(\n (tx) => !filteredTransactions.some((filtered) => filtered.id === tx.id)\n )\n\n if (removedTransactions.length > 0) {\n await this.outbox.removeMany(removedTransactions.map((tx) => tx.id))\n }\n }\n\n clear(): void {\n this.scheduler.clear()\n this.clearRetryTimer()\n }\n\n getPendingCount(): number {\n return this.scheduler.getPendingCount()\n }\n\n private scheduleNextRetry(): void {\n // Clear existing timer\n this.clearRetryTimer()\n\n // Find the earliest retry time among pending transactions\n const earliestRetryTime = this.getEarliestRetryTime()\n\n if (earliestRetryTime === null) {\n return // No transactions pending retry\n }\n\n const delay = Math.max(0, earliestRetryTime - Date.now())\n\n this.retryTimer = setTimeout(() => {\n this.executeAll().catch((error) => {\n console.warn(`Failed to execute retry batch:`, error)\n })\n }, delay)\n }\n\n private getEarliestRetryTime(): number | null {\n const allTransactions = this.scheduler.getAllPendingTransactions()\n\n if (allTransactions.length === 0) {\n return null\n }\n\n return Math.min(...allTransactions.map((tx) => tx.nextAttemptAt))\n }\n\n private clearRetryTimer(): void {\n if (this.retryTimer) {\n clearTimeout(this.retryTimer)\n this.retryTimer = null\n }\n }\n\n getRunningCount(): number {\n return this.scheduler.getRunningCount()\n }\n\n resetRetryDelays(): void {\n const allTransactions = this.scheduler.getAllPendingTransactions()\n const updatedTransactions = allTransactions.map((transaction) => ({\n ...transaction,\n nextAttemptAt: Date.now(),\n }))\n\n this.scheduler.updateTransactions(updatedTransactions)\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TransactionExecutor.js","sources":["../../../src/executor/TransactionExecutor.ts"],"sourcesContent":["import { DefaultRetryPolicy } from \"../retry/RetryPolicy\"\nimport { NonRetriableError } from \"../types\"\nimport { withNestedSpan } from \"../telemetry/tracer\"\nimport type { KeyScheduler } from \"./KeyScheduler\"\nimport type { OutboxManager } from \"../outbox/OutboxManager\"\nimport type { OfflineConfig, OfflineTransaction } from \"../types\"\n\nconst HANDLED_EXECUTION_ERROR = Symbol(`HandledExecutionError`)\n\nexport class TransactionExecutor {\n private scheduler: KeyScheduler\n private outbox: OutboxManager\n private config: OfflineConfig\n private retryPolicy: DefaultRetryPolicy\n private isExecuting = false\n private executionPromise: Promise<void> | null = null\n private offlineExecutor: any // Reference to OfflineExecutor for signaling\n private retryTimer: ReturnType<typeof setTimeout> | null = null\n\n constructor(\n scheduler: KeyScheduler,\n outbox: OutboxManager,\n config: OfflineConfig,\n offlineExecutor: any\n ) {\n this.scheduler = scheduler\n this.outbox = outbox\n this.config = config\n this.retryPolicy = new DefaultRetryPolicy(10, config.jitter ?? true)\n this.offlineExecutor = offlineExecutor\n }\n\n async execute(transaction: OfflineTransaction): Promise<void> {\n this.scheduler.schedule(transaction)\n await this.executeAll()\n }\n\n async executeAll(): Promise<void> {\n if (this.isExecuting) {\n return this.executionPromise!\n }\n\n this.isExecuting = true\n this.executionPromise = this.runExecution()\n\n try {\n await this.executionPromise\n } finally {\n this.isExecuting = false\n this.executionPromise = null\n }\n }\n\n private async runExecution(): Promise<void> {\n const maxConcurrency = this.config.maxConcurrency ?? 3\n\n while (this.scheduler.getPendingCount() > 0) {\n const batch = this.scheduler.getNextBatch(maxConcurrency)\n\n if (batch.length === 0) {\n break\n }\n\n const executions = batch.map((transaction) =>\n this.executeTransaction(transaction)\n )\n await Promise.allSettled(executions)\n }\n\n // Schedule next retry after execution completes\n this.scheduleNextRetry()\n }\n\n private async executeTransaction(\n transaction: OfflineTransaction\n ): Promise<void> {\n try {\n await withNestedSpan(\n `transaction.execute`,\n {\n \"transaction.id\": transaction.id,\n \"transaction.mutationFnName\": transaction.mutationFnName,\n \"transaction.retryCount\": transaction.retryCount,\n \"transaction.keyCount\": transaction.keys.length,\n },\n async (span) => {\n this.scheduler.markStarted(transaction)\n\n if (transaction.retryCount > 0) {\n span.setAttribute(`retry.attempt`, transaction.retryCount)\n }\n\n try {\n const result = await this.runMutationFn(transaction)\n\n this.scheduler.markCompleted(transaction)\n await this.outbox.remove(transaction.id)\n\n span.setAttribute(`result`, `success`)\n this.offlineExecutor.resolveTransaction(transaction.id, result)\n } catch (error) {\n const err =\n error instanceof Error ? error : new Error(String(error))\n\n span.setAttribute(`result`, `error`)\n\n await this.handleError(transaction, err)\n ;(err as any)[HANDLED_EXECUTION_ERROR] = true\n throw err\n }\n }\n )\n } catch (error) {\n if (\n error instanceof Error &&\n (error as any)[HANDLED_EXECUTION_ERROR] === true\n ) {\n return\n }\n\n throw error\n }\n }\n\n private async runMutationFn(transaction: OfflineTransaction): Promise<void> {\n const mutationFn = this.config.mutationFns[transaction.mutationFnName]\n\n if (!mutationFn) {\n const errorMessage = `Unknown mutation function: ${transaction.mutationFnName}`\n\n if (this.config.onUnknownMutationFn) {\n this.config.onUnknownMutationFn(transaction.mutationFnName, transaction)\n }\n\n throw new NonRetriableError(errorMessage)\n }\n\n // Mutations are already PendingMutation objects with collections attached\n // from the deserializer, so we can use them directly\n const transactionWithMutations = {\n id: transaction.id,\n mutations: transaction.mutations,\n metadata: transaction.metadata ?? {},\n }\n\n await mutationFn({\n transaction: transactionWithMutations as any,\n idempotencyKey: transaction.idempotencyKey,\n })\n }\n\n private async handleError(\n transaction: OfflineTransaction,\n error: Error\n ): Promise<void> {\n return withNestedSpan(\n `transaction.handleError`,\n {\n \"transaction.id\": transaction.id,\n \"error.name\": error.name,\n \"error.message\": error.message,\n },\n async (span) => {\n const shouldRetry = this.retryPolicy.shouldRetry(\n error,\n transaction.retryCount\n )\n\n span.setAttribute(`shouldRetry`, shouldRetry)\n\n if (!shouldRetry) {\n this.scheduler.markCompleted(transaction)\n await this.outbox.remove(transaction.id)\n console.warn(\n `Transaction ${transaction.id} failed permanently:`,\n error\n )\n\n span.setAttribute(`result`, `permanent_failure`)\n // Signal permanent failure to the waiting transaction\n this.offlineExecutor.rejectTransaction(transaction.id, error)\n return\n }\n\n const delay = this.retryPolicy.calculateDelay(transaction.retryCount)\n const updatedTransaction: OfflineTransaction = {\n ...transaction,\n retryCount: transaction.retryCount + 1,\n nextAttemptAt: Date.now() + delay,\n lastError: {\n name: error.name,\n message: error.message,\n stack: error.stack,\n },\n }\n\n span.setAttribute(`retryDelay`, delay)\n span.setAttribute(`nextRetryCount`, updatedTransaction.retryCount)\n\n this.scheduler.markFailed(transaction)\n this.scheduler.updateTransaction(updatedTransaction)\n\n try {\n await this.outbox.update(transaction.id, updatedTransaction)\n span.setAttribute(`result`, `scheduled_retry`)\n } catch (persistError) {\n span.recordException(persistError as Error)\n span.setAttribute(`result`, `persist_failed`)\n throw persistError\n }\n\n // Schedule retry timer\n this.scheduleNextRetry()\n }\n )\n }\n\n async loadPendingTransactions(): Promise<void> {\n const transactions = await this.outbox.getAll()\n let filteredTransactions = transactions\n\n if (this.config.beforeRetry) {\n filteredTransactions = this.config.beforeRetry(transactions)\n }\n\n for (const transaction of filteredTransactions) {\n this.scheduler.schedule(transaction)\n }\n\n // Reset retry delays for all loaded transactions so they can run immediately\n this.resetRetryDelays()\n\n // Schedule retry timer for loaded transactions\n this.scheduleNextRetry()\n\n const removedTransactions = transactions.filter(\n (tx) => !filteredTransactions.some((filtered) => filtered.id === tx.id)\n )\n\n if (removedTransactions.length > 0) {\n await this.outbox.removeMany(removedTransactions.map((tx) => tx.id))\n }\n }\n\n clear(): void {\n this.scheduler.clear()\n this.clearRetryTimer()\n }\n\n getPendingCount(): number {\n return this.scheduler.getPendingCount()\n }\n\n private scheduleNextRetry(): void {\n // Clear existing timer\n this.clearRetryTimer()\n\n // Find the earliest retry time among pending transactions\n const earliestRetryTime = this.getEarliestRetryTime()\n\n if (earliestRetryTime === null) {\n return // No transactions pending retry\n }\n\n const delay = Math.max(0, earliestRetryTime - Date.now())\n\n this.retryTimer = setTimeout(() => {\n this.executeAll().catch((error) => {\n console.warn(`Failed to execute retry batch:`, error)\n })\n }, delay)\n }\n\n private getEarliestRetryTime(): number | null {\n const allTransactions = this.scheduler.getAllPendingTransactions()\n\n if (allTransactions.length === 0) {\n return null\n }\n\n return Math.min(...allTransactions.map((tx) => tx.nextAttemptAt))\n }\n\n private clearRetryTimer(): void {\n if (this.retryTimer) {\n clearTimeout(this.retryTimer)\n this.retryTimer = null\n }\n }\n\n getRunningCount(): number {\n return this.scheduler.getRunningCount()\n }\n\n resetRetryDelays(): void {\n const allTransactions = this.scheduler.getAllPendingTransactions()\n const updatedTransactions = allTransactions.map((transaction) => ({\n ...transaction,\n nextAttemptAt: Date.now(),\n }))\n\n this.scheduler.updateTransactions(updatedTransactions)\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -4,7 +4,7 @@ export declare class OutboxManager {
|
|
|
4
4
|
private storage;
|
|
5
5
|
private serializer;
|
|
6
6
|
private keyPrefix;
|
|
7
|
-
constructor(storage: StorageAdapter, collections: Record<string, Collection
|
|
7
|
+
constructor(storage: StorageAdapter, collections: Record<string, Collection<any, any, any, any, any>>);
|
|
8
8
|
private getStorageKey;
|
|
9
9
|
add(transaction: OfflineTransaction): Promise<void>;
|
|
10
10
|
get(id: string): Promise<OfflineTransaction | null>;
|
|
@@ -25,7 +25,7 @@ class OutboxManager {
|
|
|
25
25
|
);
|
|
26
26
|
}
|
|
27
27
|
async get(id) {
|
|
28
|
-
return withSpan(`outbox.get`, {
|
|
28
|
+
return withSpan(`outbox.get`, {}, async (span) => {
|
|
29
29
|
const key = this.getStorageKey(id);
|
|
30
30
|
const data = await this.storage.get(key);
|
|
31
31
|
if (!data) {
|
|
@@ -78,7 +78,7 @@ class OutboxManager {
|
|
|
78
78
|
);
|
|
79
79
|
}
|
|
80
80
|
async update(id, updates) {
|
|
81
|
-
return withSpan(`outbox.update`, {
|
|
81
|
+
return withSpan(`outbox.update`, {}, async () => {
|
|
82
82
|
const existing = await this.get(id);
|
|
83
83
|
if (!existing) {
|
|
84
84
|
throw new Error(`Transaction ${id} not found`);
|
|
@@ -88,7 +88,7 @@ class OutboxManager {
|
|
|
88
88
|
});
|
|
89
89
|
}
|
|
90
90
|
async remove(id) {
|
|
91
|
-
return withSpan(`outbox.remove`, {
|
|
91
|
+
return withSpan(`outbox.remove`, {}, async () => {
|
|
92
92
|
const key = this.getStorageKey(id);
|
|
93
93
|
await this.storage.delete(key);
|
|
94
94
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OutboxManager.js","sources":["../../../src/outbox/OutboxManager.ts"],"sourcesContent":["import { withSpan } from \"../telemetry/tracer\"\nimport { TransactionSerializer } from \"./TransactionSerializer\"\nimport type { OfflineTransaction, StorageAdapter } from \"../types\"\nimport type { Collection } from \"@tanstack/db\"\n\nexport class OutboxManager {\n private storage: StorageAdapter\n private serializer: TransactionSerializer\n private keyPrefix = `tx:`\n\n constructor(\n storage: StorageAdapter,\n collections: Record<string, Collection
|
|
1
|
+
{"version":3,"file":"OutboxManager.js","sources":["../../../src/outbox/OutboxManager.ts"],"sourcesContent":["import { withSpan } from \"../telemetry/tracer\"\nimport { TransactionSerializer } from \"./TransactionSerializer\"\nimport type { OfflineTransaction, StorageAdapter } from \"../types\"\nimport type { Collection } from \"@tanstack/db\"\n\nexport class OutboxManager {\n private storage: StorageAdapter\n private serializer: TransactionSerializer\n private keyPrefix = `tx:`\n\n constructor(\n storage: StorageAdapter,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n collections: Record<string, Collection<any, any, any, any, any>>\n ) {\n this.storage = storage\n this.serializer = new TransactionSerializer(collections)\n }\n\n private getStorageKey(id: string): string {\n return `${this.keyPrefix}${id}`\n }\n\n async add(transaction: OfflineTransaction): Promise<void> {\n return withSpan(\n `outbox.add`,\n {\n \"transaction.id\": transaction.id,\n \"transaction.mutationFnName\": transaction.mutationFnName,\n \"transaction.keyCount\": transaction.keys.length,\n },\n async () => {\n const key = this.getStorageKey(transaction.id)\n const serialized = this.serializer.serialize(transaction)\n await this.storage.set(key, serialized)\n }\n )\n }\n\n async get(id: string): Promise<OfflineTransaction | null> {\n return withSpan(`outbox.get`, { \"transaction.id\": id }, async (span) => {\n const key = this.getStorageKey(id)\n const data = await this.storage.get(key)\n\n if (!data) {\n span.setAttribute(`result`, `not_found`)\n return null\n }\n\n try {\n const transaction = this.serializer.deserialize(data)\n span.setAttribute(`result`, `found`)\n return transaction\n } catch (error) {\n console.warn(`Failed to deserialize transaction ${id}:`, error)\n span.setAttribute(`result`, `deserialize_error`)\n return null\n }\n })\n }\n\n async getAll(): Promise<Array<OfflineTransaction>> {\n return withSpan(`outbox.getAll`, {}, async (span) => {\n const keys = await this.storage.keys()\n const transactionKeys = keys.filter((key) =>\n key.startsWith(this.keyPrefix)\n )\n\n span.setAttribute(`transactionCount`, transactionKeys.length)\n\n const transactions: Array<OfflineTransaction> = []\n\n for (const key of transactionKeys) {\n const data = await this.storage.get(key)\n if (data) {\n try {\n const transaction = this.serializer.deserialize(data)\n transactions.push(transaction)\n } catch (error) {\n console.warn(\n `Failed to deserialize transaction from key ${key}:`,\n error\n )\n }\n }\n }\n\n return transactions.sort(\n (a, b) => a.createdAt.getTime() - b.createdAt.getTime()\n )\n })\n }\n\n async getByKeys(keys: Array<string>): Promise<Array<OfflineTransaction>> {\n const allTransactions = await this.getAll()\n const keySet = new Set(keys)\n\n return allTransactions.filter((transaction) =>\n transaction.keys.some((key) => keySet.has(key))\n )\n }\n\n async update(\n id: string,\n updates: Partial<OfflineTransaction>\n ): Promise<void> {\n return withSpan(`outbox.update`, { \"transaction.id\": id }, async () => {\n const existing = await this.get(id)\n if (!existing) {\n throw new Error(`Transaction ${id} not found`)\n }\n\n const updated = { ...existing, ...updates }\n await this.add(updated)\n })\n }\n\n async remove(id: string): Promise<void> {\n return withSpan(`outbox.remove`, { \"transaction.id\": id }, async () => {\n const key = this.getStorageKey(id)\n await this.storage.delete(key)\n })\n }\n\n async removeMany(ids: Array<string>): Promise<void> {\n return withSpan(`outbox.removeMany`, { count: ids.length }, async () => {\n await Promise.all(ids.map((id) => this.remove(id)))\n })\n }\n\n async clear(): Promise<void> {\n const keys = await this.storage.keys()\n const transactionKeys = keys.filter((key) => key.startsWith(this.keyPrefix))\n\n await Promise.all(transactionKeys.map((key) => this.storage.delete(key)))\n }\n\n async count(): Promise<number> {\n const keys = await this.storage.keys()\n return keys.filter((key) => key.startsWith(this.keyPrefix)).length\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -3,7 +3,7 @@ import { Collection } from '@tanstack/db';
|
|
|
3
3
|
export declare class TransactionSerializer {
|
|
4
4
|
private collections;
|
|
5
5
|
private collectionIdToKey;
|
|
6
|
-
constructor(collections: Record<string, Collection
|
|
6
|
+
constructor(collections: Record<string, Collection<any, any, any, any, any>>);
|
|
7
7
|
serialize(transaction: OfflineTransaction): string;
|
|
8
8
|
deserialize(data: string): OfflineTransaction;
|
|
9
9
|
private serializeMutation;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TransactionSerializer.js","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
|
|
1
|
+
{"version":3,"file":"TransactionSerializer.js","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 // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private collections: Record<string, Collection<any, any, any, any, any>>\n private collectionIdToKey: Map<string, string>\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\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,\n mutations: transaction.mutations.map((mutation) =>\n this.serializeMutation(mutation)\n ),\n }\n // Convert the whole object to JSON, handling dates\n return JSON.stringify(serialized, (key, value) => {\n if (value instanceof Date) {\n return value.toISOString()\n }\n return value\n })\n }\n\n deserialize(data: string): OfflineTransaction {\n const parsed: SerializedOfflineTransaction = JSON.parse(\n data,\n (key, value) => {\n // Parse ISO date strings back to Date objects\n if (\n typeof value === `string` &&\n /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}/.test(value)\n ) {\n return new Date(value)\n }\n return value\n }\n )\n\n return {\n ...parsed,\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 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 // 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: this.deserializeValue(data.modified),\n original: this.deserializeValue(data.original),\n collection,\n // These fields would need to be reconstructed by the executor\n mutationId: ``, // Will be regenerated\n key: null, // Will be extracted from the data\n changes: {}, // Will be recalculated\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 (value.hasOwnProperty(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 return new Date(value.value)\n }\n\n if (typeof value === `object`) {\n const result: any = Array.isArray(value) ? [] : {}\n for (const key in value) {\n if (value.hasOwnProperty(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":"AAQO,MAAM,sBAAsB;AAAA;AAAA,EAMjC,YACE,aACA;AACA,SAAK,cAAc;AAEnB,SAAK,wCAAwB,IAAA;AAC7B,eAAW,CAAC,KAAK,UAAU,KAAK,OAAO,QAAQ,WAAW,GAAG;AAC3D,WAAK,kBAAkB,IAAI,WAAW,IAAI,GAAG;AAAA,IAC/C;AAAA,EACF;AAAA,EAEA,UAAU,aAAyC;AACjD,UAAM,aAA2C;AAAA,MAC/C,GAAG;AAAA,MACH,WAAW,YAAY;AAAA,MACvB,WAAW,YAAY,UAAU;AAAA,QAAI,CAAC,aACpC,KAAK,kBAAkB,QAAQ;AAAA,MAAA;AAAA,IACjC;AAGF,WAAO,KAAK,UAAU,YAAY,CAAC,KAAK,UAAU;AAChD,UAAI,iBAAiB,MAAM;AACzB,eAAO,MAAM,YAAA;AAAA,MACf;AACA,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAAA,EAEA,YAAY,MAAkC;AAC5C,UAAM,SAAuC,KAAK;AAAA,MAChD;AAAA,MACA,CAAC,KAAK,UAAU;AAEd,YACE,OAAO,UAAU,YACjB,uCAAuC,KAAK,KAAK,GACjD;AACA,iBAAO,IAAI,KAAK,KAAK;AAAA,QACvB;AACA,eAAO;AAAA,MACT;AAAA,IAAA;AAGF,WAAO;AAAA,MACL,GAAG;AAAA,MACH,WAAW,OAAO,UAAU;AAAA,QAAI,CAAC,iBAC/B,KAAK,oBAAoB,YAAY;AAAA,MAAA;AAAA,IACvC;AAAA,EAEJ;AAAA,EAEQ,kBAAkB,UAA+C;AACvE,UAAM,cAAc,KAAK,kBAAkB,IAAI,SAAS,WAAW,EAAE;AACrE,QAAI,CAAC,aAAa;AAChB,YAAM,IAAI;AAAA,QACR,sBAAsB,SAAS,WAAW,EAAE;AAAA,MAAA;AAAA,IAEhD;AAEA,WAAO;AAAA,MACL,WAAW,SAAS;AAAA,MACpB,MAAM,SAAS;AAAA,MACf,UAAU,KAAK,eAAe,SAAS,QAAQ;AAAA,MAC/C,UAAU,KAAK,eAAe,SAAS,QAAQ;AAAA,MAC/C,cAAc;AAAA;AAAA,IAAA;AAAA,EAElB;AAAA,EAEQ,oBAAoB,MAA2C;AACrE,UAAM,aAAa,KAAK,YAAY,KAAK,YAAY;AACrD,QAAI,CAAC,YAAY;AACf,YAAM,IAAI,MAAM,sBAAsB,KAAK,YAAY,YAAY;AAAA,IACrE;AAIA,WAAO;AAAA,MACL,WAAW,KAAK;AAAA,MAChB,MAAM,KAAK;AAAA,MACX,UAAU,KAAK,iBAAiB,KAAK,QAAQ;AAAA,MAC7C,UAAU,KAAK,iBAAiB,KAAK,QAAQ;AAAA,MAC7C;AAAA;AAAA,MAEA,YAAY;AAAA;AAAA,MACZ,KAAK;AAAA;AAAA,MACL,SAAS,CAAA;AAAA;AAAA,MACT,UAAU;AAAA,MACV,cAAc,CAAA;AAAA,MACd,YAAY;AAAA,MACZ,+BAAe,KAAA;AAAA,MACf,+BAAe,KAAA;AAAA,IAAK;AAAA,EAExB;AAAA,EAEQ,eAAe,OAAiB;AACtC,QAAI,UAAU,QAAQ,UAAU,QAAW;AACzC,aAAO;AAAA,IACT;AAEA,QAAI,iBAAiB,MAAM;AACzB,aAAO,EAAE,QAAQ,QAAQ,OAAO,MAAM,cAAY;AAAA,IACpD;AAEA,QAAI,OAAO,UAAU,UAAU;AAC7B,YAAM,SAAc,MAAM,QAAQ,KAAK,IAAI,CAAA,IAAK,CAAA;AAChD,iBAAW,OAAO,OAAO;AACvB,YAAI,MAAM,eAAe,GAAG,GAAG;AAC7B,iBAAO,GAAG,IAAI,KAAK,eAAe,MAAM,GAAG,CAAC;AAAA,QAC9C;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAEA,WAAO;AAAA,EACT;AAAA,EAEQ,iBAAiB,OAAiB;AACxC,QAAI,UAAU,QAAQ,UAAU,QAAW;AACzC,aAAO;AAAA,IACT;AAEA,QAAI,OAAO,UAAU,YAAY,MAAM,WAAW,QAAQ;AACxD,aAAO,IAAI,KAAK,MAAM,KAAK;AAAA,IAC7B;AAEA,QAAI,OAAO,UAAU,UAAU;AAC7B,YAAM,SAAc,MAAM,QAAQ,KAAK,IAAI,CAAA,IAAK,CAAA;AAChD,iBAAW,OAAO,OAAO;AACvB,YAAI,MAAM,eAAe,GAAG,GAAG;AAC7B,iBAAO,GAAG,IAAI,KAAK,iBAAiB,MAAM,GAAG,CAAC;AAAA,QAChD;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,eAAe,OAA+B;AAC5C,WAAO;AAAA,MACL,MAAM,MAAM;AAAA,MACZ,SAAS,MAAM;AAAA,MACf,OAAO,MAAM;AAAA,IAAA;AAAA,EAEjB;AAAA,EAEA,iBAAiB,MAA8B;AAC7C,UAAM,QAAQ,IAAI,MAAM,KAAK,OAAO;AACpC,UAAM,OAAO,KAAK;AAClB,UAAM,QAAQ,KAAK;AACnB,WAAO;AAAA,EACT;AACF;"}
|
|
@@ -1,29 +1,30 @@
|
|
|
1
|
-
import { Span, SpanContext } from '@opentelemetry/api';
|
|
2
1
|
export interface SpanAttrs {
|
|
3
2
|
[key: string]: string | number | boolean | undefined;
|
|
4
3
|
}
|
|
5
4
|
interface WithSpanOptions {
|
|
6
|
-
parentContext?:
|
|
5
|
+
parentContext?: any;
|
|
7
6
|
}
|
|
8
7
|
/**
|
|
9
8
|
* Lightweight span wrapper with error handling.
|
|
10
|
-
*
|
|
9
|
+
* No-op implementation - telemetry has been removed.
|
|
11
10
|
*
|
|
12
11
|
* By default, creates spans at the current context level (siblings).
|
|
13
12
|
* Use withNestedSpan if you want parent-child relationships.
|
|
14
13
|
*/
|
|
15
|
-
export declare function withSpan<T>(name: string, attrs: SpanAttrs, fn: (span:
|
|
14
|
+
export declare function withSpan<T>(name: string, attrs: SpanAttrs, fn: (span: any) => Promise<T>, _options?: WithSpanOptions): Promise<T>;
|
|
16
15
|
/**
|
|
17
16
|
* Like withSpan but propagates context so child spans nest properly.
|
|
18
|
-
*
|
|
17
|
+
* No-op implementation - telemetry has been removed.
|
|
19
18
|
*/
|
|
20
|
-
export declare function withNestedSpan<T>(name: string, attrs: SpanAttrs, fn: (span:
|
|
19
|
+
export declare function withNestedSpan<T>(name: string, attrs: SpanAttrs, fn: (span: any) => Promise<T>, _options?: WithSpanOptions): Promise<T>;
|
|
21
20
|
/**
|
|
22
21
|
* Creates a synchronous span for non-async operations
|
|
22
|
+
* No-op implementation - telemetry has been removed.
|
|
23
23
|
*/
|
|
24
|
-
export declare function withSyncSpan<T>(name: string, attrs: SpanAttrs, fn: (span:
|
|
24
|
+
export declare function withSyncSpan<T>(name: string, attrs: SpanAttrs, fn: (span: any) => T, _options?: WithSpanOptions): T;
|
|
25
25
|
/**
|
|
26
26
|
* Get the current tracer instance
|
|
27
|
+
* No-op implementation - telemetry has been removed.
|
|
27
28
|
*/
|
|
28
|
-
export declare function getTracer():
|
|
29
|
+
export declare function getTracer(): null;
|
|
29
30
|
export {};
|
|
@@ -1,87 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
const parentCtx = getParentContext(options);
|
|
12
|
-
const span = TRACER.startSpan(name, void 0, parentCtx);
|
|
13
|
-
const filteredAttrs = {};
|
|
14
|
-
for (const [key, value] of Object.entries(attrs)) {
|
|
15
|
-
if (value !== void 0) {
|
|
16
|
-
filteredAttrs[key] = value;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
span.setAttributes(filteredAttrs);
|
|
20
|
-
try {
|
|
21
|
-
const result = await fn(span);
|
|
22
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
23
|
-
return result;
|
|
24
|
-
} catch (error) {
|
|
25
|
-
span.setStatus({
|
|
26
|
-
code: SpanStatusCode.ERROR,
|
|
27
|
-
message: error instanceof Error ? error.message : String(error)
|
|
28
|
-
});
|
|
29
|
-
span.recordException(error);
|
|
30
|
-
throw error;
|
|
31
|
-
} finally {
|
|
32
|
-
span.end();
|
|
1
|
+
const noopSpan = {
|
|
2
|
+
setAttribute: () => {
|
|
3
|
+
},
|
|
4
|
+
setAttributes: () => {
|
|
5
|
+
},
|
|
6
|
+
setStatus: () => {
|
|
7
|
+
},
|
|
8
|
+
recordException: () => {
|
|
9
|
+
},
|
|
10
|
+
end: () => {
|
|
33
11
|
}
|
|
12
|
+
};
|
|
13
|
+
async function withSpan(name, attrs, fn, _options) {
|
|
14
|
+
return await fn(noopSpan);
|
|
34
15
|
}
|
|
35
|
-
async function withNestedSpan(name, attrs, fn,
|
|
36
|
-
|
|
37
|
-
const span = TRACER.startSpan(name, void 0, parentCtx);
|
|
38
|
-
const filteredAttrs = {};
|
|
39
|
-
for (const [key, value] of Object.entries(attrs)) {
|
|
40
|
-
if (value !== void 0) {
|
|
41
|
-
filteredAttrs[key] = value;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
span.setAttributes(filteredAttrs);
|
|
45
|
-
const ctx = trace.setSpan(parentCtx, span);
|
|
46
|
-
try {
|
|
47
|
-
const result = await context.with(ctx, () => fn(span));
|
|
48
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
49
|
-
return result;
|
|
50
|
-
} catch (error) {
|
|
51
|
-
span.setStatus({
|
|
52
|
-
code: SpanStatusCode.ERROR,
|
|
53
|
-
message: error instanceof Error ? error.message : String(error)
|
|
54
|
-
});
|
|
55
|
-
span.recordException(error);
|
|
56
|
-
throw error;
|
|
57
|
-
} finally {
|
|
58
|
-
span.end();
|
|
59
|
-
}
|
|
16
|
+
async function withNestedSpan(name, attrs, fn, _options) {
|
|
17
|
+
return await fn(noopSpan);
|
|
60
18
|
}
|
|
61
|
-
function withSyncSpan(name, attrs, fn,
|
|
62
|
-
|
|
63
|
-
const span = TRACER.startSpan(name, void 0, parentCtx);
|
|
64
|
-
const filteredAttrs = {};
|
|
65
|
-
for (const [key, value] of Object.entries(attrs)) {
|
|
66
|
-
if (value !== void 0) {
|
|
67
|
-
filteredAttrs[key] = value;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
span.setAttributes(filteredAttrs);
|
|
71
|
-
try {
|
|
72
|
-
const result = fn(span);
|
|
73
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
74
|
-
return result;
|
|
75
|
-
} catch (error) {
|
|
76
|
-
span.setStatus({
|
|
77
|
-
code: SpanStatusCode.ERROR,
|
|
78
|
-
message: error instanceof Error ? error.message : String(error)
|
|
79
|
-
});
|
|
80
|
-
span.recordException(error);
|
|
81
|
-
throw error;
|
|
82
|
-
} finally {
|
|
83
|
-
span.end();
|
|
84
|
-
}
|
|
19
|
+
function withSyncSpan(name, attrs, fn, _options) {
|
|
20
|
+
return fn(noopSpan);
|
|
85
21
|
}
|
|
86
22
|
export {
|
|
87
23
|
withNestedSpan,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tracer.js","sources":["../../../src/telemetry/tracer.ts"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"tracer.js","sources":["../../../src/telemetry/tracer.ts"],"sourcesContent":["export interface SpanAttrs {\n [key: string]: string | number | boolean | undefined\n}\n\ninterface WithSpanOptions {\n parentContext?: any\n}\n\n// No-op span implementation\nconst noopSpan = {\n setAttribute: () => {},\n setAttributes: () => {},\n setStatus: () => {},\n recordException: () => {},\n end: () => {},\n}\n\n/**\n * Lightweight span wrapper with error handling.\n * No-op implementation - telemetry has been removed.\n *\n * By default, creates spans at the current context level (siblings).\n * Use withNestedSpan if you want parent-child relationships.\n */\nexport async function withSpan<T>(\n name: string,\n attrs: SpanAttrs,\n fn: (span: any) => Promise<T>,\n _options?: WithSpanOptions\n): Promise<T> {\n return await fn(noopSpan)\n}\n\n/**\n * Like withSpan but propagates context so child spans nest properly.\n * No-op implementation - telemetry has been removed.\n */\nexport async function withNestedSpan<T>(\n name: string,\n attrs: SpanAttrs,\n fn: (span: any) => Promise<T>,\n _options?: WithSpanOptions\n): Promise<T> {\n return await fn(noopSpan)\n}\n\n/**\n * Creates a synchronous span for non-async operations\n * No-op implementation - telemetry has been removed.\n */\nexport function withSyncSpan<T>(\n name: string,\n attrs: SpanAttrs,\n fn: (span: any) => T,\n _options?: WithSpanOptions\n): T {\n return fn(noopSpan)\n}\n\n/**\n * Get the current tracer instance\n * No-op implementation - telemetry has been removed.\n */\nexport function getTracer() {\n return null\n}\n"],"names":[],"mappings":"AASA,MAAM,WAAW;AAAA,EACf,cAAc,MAAM;AAAA,EAAC;AAAA,EACrB,eAAe,MAAM;AAAA,EAAC;AAAA,EACtB,WAAW,MAAM;AAAA,EAAC;AAAA,EAClB,iBAAiB,MAAM;AAAA,EAAC;AAAA,EACxB,KAAK,MAAM;AAAA,EAAC;AACd;AASA,eAAsB,SACpB,MACA,OACA,IACA,UACY;AACZ,SAAO,MAAM,GAAG,QAAQ;AAC1B;AAMA,eAAsB,eACpB,MACA,OACA,IACA,UACY;AACZ,SAAO,MAAM,GAAG,QAAQ;AAC1B;AAMO,SAAS,aACd,MACA,OACA,IACA,UACG;AACH,SAAO,GAAG,QAAQ;AACpB;"}
|
package/dist/esm/types.d.ts
CHANGED
|
@@ -58,7 +58,7 @@ export interface StorageDiagnostic {
|
|
|
58
58
|
error?: Error;
|
|
59
59
|
}
|
|
60
60
|
export interface OfflineConfig {
|
|
61
|
-
collections: Record<string, Collection
|
|
61
|
+
collections: Record<string, Collection<any, any, any, any, any>>;
|
|
62
62
|
mutationFns: Record<string, OfflineMutationFn>;
|
|
63
63
|
storage?: StorageAdapter;
|
|
64
64
|
maxConcurrency?: number;
|
package/dist/esm/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sources":["../../src/types.ts"],"sourcesContent":["import type {\n Collection,\n MutationFnParams,\n PendingMutation,\n} from \"@tanstack/db\"\n\n// Extended mutation function that includes idempotency key\nexport type OfflineMutationFnParams<\n T extends object = Record<string, unknown>,\n> = MutationFnParams<T> & {\n idempotencyKey: string\n}\n\nexport type OfflineMutationFn<T extends object = Record<string, unknown>> = (\n params: OfflineMutationFnParams<T>\n) => Promise<any>\n\n// Simplified mutation structure for serialization\nexport interface SerializedMutation {\n globalKey: string\n type: string\n modified: any\n original: any\n collectionId: string\n}\n\nexport interface SerializedError {\n name: string\n message: string\n stack?: string\n}\n\nexport interface SerializedSpanContext {\n traceId: string\n spanId: string\n traceFlags: number\n traceState?: string\n}\n\n// In-memory representation with full PendingMutation objects\nexport interface OfflineTransaction {\n id: string\n mutationFnName: string\n mutations: Array<PendingMutation>\n keys: Array<string>\n idempotencyKey: string\n createdAt: Date\n retryCount: number\n nextAttemptAt: number\n lastError?: SerializedError\n metadata?: Record<string, any>\n spanContext?: SerializedSpanContext\n version: 1\n}\n\n// Serialized representation for storage\nexport interface SerializedOfflineTransaction {\n id: string\n mutationFnName: string\n mutations: Array<SerializedMutation>\n keys: Array<string>\n idempotencyKey: string\n createdAt: Date\n retryCount: number\n nextAttemptAt: number\n lastError?: SerializedError\n metadata?: Record<string, any>\n spanContext?: SerializedSpanContext\n version: 1\n}\n\n// Storage diagnostics and mode\nexport type OfflineMode = `offline` | `online-only`\n\nexport type StorageDiagnosticCode =\n | `STORAGE_AVAILABLE`\n | `INDEXEDDB_UNAVAILABLE`\n | `LOCALSTORAGE_UNAVAILABLE`\n | `STORAGE_BLOCKED`\n | `QUOTA_EXCEEDED`\n | `UNKNOWN_ERROR`\n\nexport interface StorageDiagnostic {\n code: StorageDiagnosticCode\n mode: OfflineMode\n message: string\n error?: Error\n}\n\nexport interface OfflineConfig {\n collections: Record<string, Collection
|
|
1
|
+
{"version":3,"file":"types.js","sources":["../../src/types.ts"],"sourcesContent":["import type {\n Collection,\n MutationFnParams,\n PendingMutation,\n} from \"@tanstack/db\"\n\n// Extended mutation function that includes idempotency key\nexport type OfflineMutationFnParams<\n T extends object = Record<string, unknown>,\n> = MutationFnParams<T> & {\n idempotencyKey: string\n}\n\nexport type OfflineMutationFn<T extends object = Record<string, unknown>> = (\n params: OfflineMutationFnParams<T>\n) => Promise<any>\n\n// Simplified mutation structure for serialization\nexport interface SerializedMutation {\n globalKey: string\n type: string\n modified: any\n original: any\n collectionId: string\n}\n\nexport interface SerializedError {\n name: string\n message: string\n stack?: string\n}\n\nexport interface SerializedSpanContext {\n traceId: string\n spanId: string\n traceFlags: number\n traceState?: string\n}\n\n// In-memory representation with full PendingMutation objects\nexport interface OfflineTransaction {\n id: string\n mutationFnName: string\n mutations: Array<PendingMutation>\n keys: Array<string>\n idempotencyKey: string\n createdAt: Date\n retryCount: number\n nextAttemptAt: number\n lastError?: SerializedError\n metadata?: Record<string, any>\n spanContext?: SerializedSpanContext\n version: 1\n}\n\n// Serialized representation for storage\nexport interface SerializedOfflineTransaction {\n id: string\n mutationFnName: string\n mutations: Array<SerializedMutation>\n keys: Array<string>\n idempotencyKey: string\n createdAt: Date\n retryCount: number\n nextAttemptAt: number\n lastError?: SerializedError\n metadata?: Record<string, any>\n spanContext?: SerializedSpanContext\n version: 1\n}\n\n// Storage diagnostics and mode\nexport type OfflineMode = `offline` | `online-only`\n\nexport type StorageDiagnosticCode =\n | `STORAGE_AVAILABLE`\n | `INDEXEDDB_UNAVAILABLE`\n | `LOCALSTORAGE_UNAVAILABLE`\n | `STORAGE_BLOCKED`\n | `QUOTA_EXCEEDED`\n | `UNKNOWN_ERROR`\n\nexport interface StorageDiagnostic {\n code: StorageDiagnosticCode\n mode: OfflineMode\n message: string\n error?: Error\n}\n\nexport interface OfflineConfig {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n collections: Record<string, Collection<any, any, any, any, any>>\n mutationFns: Record<string, OfflineMutationFn>\n storage?: StorageAdapter\n maxConcurrency?: number\n jitter?: boolean\n beforeRetry?: (\n transactions: Array<OfflineTransaction>\n ) => Array<OfflineTransaction>\n onUnknownMutationFn?: (name: string, tx: OfflineTransaction) => void\n onLeadershipChange?: (isLeader: boolean) => void\n onStorageFailure?: (diagnostic: StorageDiagnostic) => void\n leaderElection?: LeaderElection\n}\n\nexport interface StorageAdapter {\n get: (key: string) => Promise<string | null>\n set: (key: string, value: string) => Promise<void>\n delete: (key: string) => Promise<void>\n keys: () => Promise<Array<string>>\n clear: () => Promise<void>\n}\n\nexport interface RetryPolicy {\n calculateDelay: (retryCount: number) => number\n shouldRetry: (error: Error, retryCount: number) => boolean\n}\n\nexport interface LeaderElection {\n requestLeadership: () => Promise<boolean>\n releaseLeadership: () => void\n isLeader: () => boolean\n onLeadershipChange: (callback: (isLeader: boolean) => void) => () => void\n}\n\nexport interface OnlineDetector {\n subscribe: (callback: () => void) => () => void\n notifyOnline: () => void\n}\n\nexport interface CreateOfflineTransactionOptions {\n id?: string\n mutationFnName: string\n autoCommit?: boolean\n idempotencyKey?: string\n metadata?: Record<string, any>\n}\n\nexport interface CreateOfflineActionOptions<T> {\n mutationFnName: string\n onMutate: (variables: T) => void\n}\n\nexport class NonRetriableError extends Error {\n constructor(message: string) {\n super(message)\n this.name = `NonRetriableError`\n }\n}\n"],"names":[],"mappings":"AA+IO,MAAM,0BAA0B,MAAM;AAAA,EAC3C,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/offline-transactions",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Offline-first transaction capabilities for TanStack DB",
|
|
5
5
|
"author": "TanStack",
|
|
6
6
|
"license": "MIT",
|
|
@@ -42,15 +42,13 @@
|
|
|
42
42
|
"dist",
|
|
43
43
|
"src"
|
|
44
44
|
],
|
|
45
|
-
"dependencies": {
|
|
46
|
-
"@opentelemetry/api": "^1.9.0",
|
|
47
|
-
"@tanstack/db": "0.4.19"
|
|
48
|
-
},
|
|
45
|
+
"dependencies": {},
|
|
49
46
|
"devDependencies": {
|
|
50
47
|
"@types/node": "^20.0.0",
|
|
51
48
|
"eslint": "^8.57.0",
|
|
52
49
|
"typescript": "^5.5.4",
|
|
53
|
-
"vitest": "^3.2.4"
|
|
50
|
+
"vitest": "^3.2.4",
|
|
51
|
+
"@tanstack/db": "0.5.0"
|
|
54
52
|
},
|
|
55
53
|
"peerDependencies": {
|
|
56
54
|
"@tanstack/db": "*"
|
package/src/api/OfflineAction.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { SpanStatusCode, context, trace } from "@opentelemetry/api"
|
|
2
1
|
import { OnMutateMustBeSynchronousError } from "@tanstack/db"
|
|
3
2
|
import { OfflineTransaction } from "./OfflineTransaction"
|
|
4
3
|
import type { Transaction } from "@tanstack/db"
|
|
@@ -45,30 +44,15 @@ export function createOfflineAction<T>(
|
|
|
45
44
|
}
|
|
46
45
|
})
|
|
47
46
|
|
|
48
|
-
// Immediately commit
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
// Return the promise synchronously - this is critical for context propagation in browsers
|
|
58
|
-
return (async () => {
|
|
59
|
-
try {
|
|
60
|
-
await transaction.commit()
|
|
61
|
-
span.setStatus({ code: SpanStatusCode.OK })
|
|
62
|
-
span.end()
|
|
63
|
-
console.log(`ended offlineAction span - success`)
|
|
64
|
-
} catch (error) {
|
|
65
|
-
span.recordException(error as Error)
|
|
66
|
-
span.setStatus({ code: SpanStatusCode.ERROR })
|
|
67
|
-
span.end()
|
|
68
|
-
console.log(`ended offlineAction span - error`)
|
|
69
|
-
}
|
|
70
|
-
})()
|
|
71
|
-
})
|
|
47
|
+
// Immediately commit
|
|
48
|
+
const commitPromise = (async () => {
|
|
49
|
+
try {
|
|
50
|
+
await transaction.commit()
|
|
51
|
+
console.log(`offlineAction committed - success`)
|
|
52
|
+
} catch {
|
|
53
|
+
console.log(`offlineAction commit failed - error`)
|
|
54
|
+
}
|
|
55
|
+
})()
|
|
72
56
|
|
|
73
57
|
// Don't await - this is fire-and-forget for optimistic actions
|
|
74
58
|
// But catch to prevent unhandled rejection
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { context, trace } from "@opentelemetry/api"
|
|
2
1
|
import { createTransaction } from "@tanstack/db"
|
|
3
2
|
import { NonRetriableError } from "../types"
|
|
4
3
|
import type { PendingMutation, Transaction } from "@tanstack/db"
|
|
@@ -40,9 +39,6 @@ export class OfflineTransaction {
|
|
|
40
39
|
mutationFn: async () => {
|
|
41
40
|
// This is the blocking mutationFn that waits for the executor
|
|
42
41
|
// First persist the transaction to the outbox
|
|
43
|
-
const activeSpan = trace.getSpan(context.active())
|
|
44
|
-
const spanContext = activeSpan?.spanContext()
|
|
45
|
-
|
|
46
42
|
const offlineTransaction: OfflineTransactionType = {
|
|
47
43
|
id: this.offlineId,
|
|
48
44
|
mutationFnName: this.mutationFnName,
|
|
@@ -53,14 +49,7 @@ export class OfflineTransaction {
|
|
|
53
49
|
retryCount: 0,
|
|
54
50
|
nextAttemptAt: Date.now(),
|
|
55
51
|
metadata: this.metadata,
|
|
56
|
-
spanContext:
|
|
57
|
-
? {
|
|
58
|
-
traceId: spanContext.traceId,
|
|
59
|
-
spanId: spanContext.spanId,
|
|
60
|
-
traceFlags: spanContext.traceFlags,
|
|
61
|
-
traceState: spanContext.traceState?.serialize(),
|
|
62
|
-
}
|
|
63
|
-
: undefined,
|
|
52
|
+
spanContext: undefined,
|
|
64
53
|
version: 1,
|
|
65
54
|
}
|
|
66
55
|
|