@tanstack/offline-transactions 0.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/README.md +219 -0
- package/dist/cjs/OfflineExecutor.cjs +266 -0
- package/dist/cjs/OfflineExecutor.cjs.map +1 -0
- package/dist/cjs/OfflineExecutor.d.cts +39 -0
- package/dist/cjs/api/OfflineAction.cjs +47 -0
- package/dist/cjs/api/OfflineAction.cjs.map +1 -0
- package/dist/cjs/api/OfflineAction.d.cts +3 -0
- package/dist/cjs/api/OfflineTransaction.cjs +96 -0
- package/dist/cjs/api/OfflineTransaction.cjs.map +1 -0
- package/dist/cjs/api/OfflineTransaction.d.cts +18 -0
- package/dist/cjs/connectivity/OnlineDetector.cjs +73 -0
- package/dist/cjs/connectivity/OnlineDetector.cjs.map +1 -0
- package/dist/cjs/connectivity/OnlineDetector.d.cts +15 -0
- package/dist/cjs/coordination/BroadcastChannelLeader.cjs +146 -0
- package/dist/cjs/coordination/BroadcastChannelLeader.cjs.map +1 -0
- package/dist/cjs/coordination/BroadcastChannelLeader.d.cts +26 -0
- package/dist/cjs/coordination/LeaderElection.cjs +31 -0
- package/dist/cjs/coordination/LeaderElection.cjs.map +1 -0
- package/dist/cjs/coordination/LeaderElection.d.cts +10 -0
- package/dist/cjs/coordination/WebLocksLeader.cjs +71 -0
- package/dist/cjs/coordination/WebLocksLeader.cjs.map +1 -0
- package/dist/cjs/coordination/WebLocksLeader.d.cts +10 -0
- package/dist/cjs/executor/KeyScheduler.cjs +106 -0
- package/dist/cjs/executor/KeyScheduler.cjs.map +1 -0
- package/dist/cjs/executor/KeyScheduler.d.cts +18 -0
- package/dist/cjs/executor/TransactionExecutor.cjs +236 -0
- package/dist/cjs/executor/TransactionExecutor.cjs.map +1 -0
- package/dist/cjs/executor/TransactionExecutor.d.cts +28 -0
- package/dist/cjs/index.cjs +34 -0
- package/dist/cjs/index.cjs.map +1 -0
- package/dist/cjs/index.d.cts +16 -0
- package/dist/cjs/outbox/OutboxManager.cjs +114 -0
- package/dist/cjs/outbox/OutboxManager.cjs.map +1 -0
- package/dist/cjs/outbox/OutboxManager.d.cts +18 -0
- package/dist/cjs/outbox/TransactionSerializer.cjs +135 -0
- package/dist/cjs/outbox/TransactionSerializer.cjs.map +1 -0
- package/dist/cjs/outbox/TransactionSerializer.d.cts +15 -0
- package/dist/cjs/retry/BackoffCalculator.cjs +14 -0
- package/dist/cjs/retry/BackoffCalculator.cjs.map +1 -0
- package/dist/cjs/retry/BackoffCalculator.d.cts +5 -0
- package/dist/cjs/retry/NonRetriableError.d.cts +1 -0
- package/dist/cjs/retry/RetryPolicy.cjs +33 -0
- package/dist/cjs/retry/RetryPolicy.cjs.map +1 -0
- package/dist/cjs/retry/RetryPolicy.d.cts +8 -0
- package/dist/cjs/storage/IndexedDBAdapter.cjs +104 -0
- package/dist/cjs/storage/IndexedDBAdapter.cjs.map +1 -0
- package/dist/cjs/storage/IndexedDBAdapter.d.cts +14 -0
- package/dist/cjs/storage/LocalStorageAdapter.cjs +71 -0
- package/dist/cjs/storage/LocalStorageAdapter.cjs.map +1 -0
- package/dist/cjs/storage/LocalStorageAdapter.d.cts +11 -0
- package/dist/cjs/storage/StorageAdapter.cjs +6 -0
- package/dist/cjs/storage/StorageAdapter.cjs.map +1 -0
- package/dist/cjs/storage/StorageAdapter.d.cts +9 -0
- package/dist/cjs/telemetry/tracer.cjs +91 -0
- package/dist/cjs/telemetry/tracer.cjs.map +1 -0
- package/dist/cjs/telemetry/tracer.d.cts +29 -0
- package/dist/cjs/types.cjs +10 -0
- package/dist/cjs/types.cjs.map +1 -0
- package/dist/cjs/types.d.cts +101 -0
- package/dist/esm/OfflineExecutor.d.ts +39 -0
- package/dist/esm/OfflineExecutor.js +266 -0
- package/dist/esm/OfflineExecutor.js.map +1 -0
- package/dist/esm/api/OfflineAction.d.ts +3 -0
- package/dist/esm/api/OfflineAction.js +47 -0
- package/dist/esm/api/OfflineAction.js.map +1 -0
- package/dist/esm/api/OfflineTransaction.d.ts +18 -0
- package/dist/esm/api/OfflineTransaction.js +96 -0
- package/dist/esm/api/OfflineTransaction.js.map +1 -0
- package/dist/esm/connectivity/OnlineDetector.d.ts +15 -0
- package/dist/esm/connectivity/OnlineDetector.js +73 -0
- package/dist/esm/connectivity/OnlineDetector.js.map +1 -0
- package/dist/esm/coordination/BroadcastChannelLeader.d.ts +26 -0
- package/dist/esm/coordination/BroadcastChannelLeader.js +146 -0
- package/dist/esm/coordination/BroadcastChannelLeader.js.map +1 -0
- package/dist/esm/coordination/LeaderElection.d.ts +10 -0
- package/dist/esm/coordination/LeaderElection.js +31 -0
- package/dist/esm/coordination/LeaderElection.js.map +1 -0
- package/dist/esm/coordination/WebLocksLeader.d.ts +10 -0
- package/dist/esm/coordination/WebLocksLeader.js +71 -0
- package/dist/esm/coordination/WebLocksLeader.js.map +1 -0
- package/dist/esm/executor/KeyScheduler.d.ts +18 -0
- package/dist/esm/executor/KeyScheduler.js +106 -0
- package/dist/esm/executor/KeyScheduler.js.map +1 -0
- package/dist/esm/executor/TransactionExecutor.d.ts +28 -0
- package/dist/esm/executor/TransactionExecutor.js +236 -0
- package/dist/esm/executor/TransactionExecutor.js.map +1 -0
- package/dist/esm/index.d.ts +16 -0
- package/dist/esm/index.js +34 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/outbox/OutboxManager.d.ts +18 -0
- package/dist/esm/outbox/OutboxManager.js +114 -0
- package/dist/esm/outbox/OutboxManager.js.map +1 -0
- package/dist/esm/outbox/TransactionSerializer.d.ts +15 -0
- package/dist/esm/outbox/TransactionSerializer.js +135 -0
- package/dist/esm/outbox/TransactionSerializer.js.map +1 -0
- package/dist/esm/retry/BackoffCalculator.d.ts +5 -0
- package/dist/esm/retry/BackoffCalculator.js +14 -0
- package/dist/esm/retry/BackoffCalculator.js.map +1 -0
- package/dist/esm/retry/NonRetriableError.d.ts +1 -0
- package/dist/esm/retry/RetryPolicy.d.ts +8 -0
- package/dist/esm/retry/RetryPolicy.js +33 -0
- package/dist/esm/retry/RetryPolicy.js.map +1 -0
- package/dist/esm/storage/IndexedDBAdapter.d.ts +14 -0
- package/dist/esm/storage/IndexedDBAdapter.js +104 -0
- package/dist/esm/storage/IndexedDBAdapter.js.map +1 -0
- package/dist/esm/storage/LocalStorageAdapter.d.ts +11 -0
- package/dist/esm/storage/LocalStorageAdapter.js +71 -0
- package/dist/esm/storage/LocalStorageAdapter.js.map +1 -0
- package/dist/esm/storage/StorageAdapter.d.ts +9 -0
- package/dist/esm/storage/StorageAdapter.js +6 -0
- package/dist/esm/storage/StorageAdapter.js.map +1 -0
- package/dist/esm/telemetry/tracer.d.ts +29 -0
- package/dist/esm/telemetry/tracer.js +91 -0
- package/dist/esm/telemetry/tracer.js.map +1 -0
- package/dist/esm/types.d.ts +101 -0
- package/dist/esm/types.js +10 -0
- package/dist/esm/types.js.map +1 -0
- package/package.json +66 -0
- package/src/OfflineExecutor.ts +360 -0
- package/src/api/OfflineAction.ts +68 -0
- package/src/api/OfflineTransaction.ts +134 -0
- package/src/connectivity/OnlineDetector.ts +87 -0
- package/src/coordination/BroadcastChannelLeader.ts +181 -0
- package/src/coordination/LeaderElection.ts +35 -0
- package/src/coordination/WebLocksLeader.ts +82 -0
- package/src/executor/KeyScheduler.ts +123 -0
- package/src/executor/TransactionExecutor.ts +330 -0
- package/src/index.ts +47 -0
- package/src/outbox/OutboxManager.ts +141 -0
- package/src/outbox/TransactionSerializer.ts +163 -0
- package/src/retry/BackoffCalculator.ts +13 -0
- package/src/retry/NonRetriableError.ts +1 -0
- package/src/retry/RetryPolicy.ts +41 -0
- package/src/storage/IndexedDBAdapter.ts +119 -0
- package/src/storage/LocalStorageAdapter.ts +79 -0
- package/src/storage/StorageAdapter.ts +11 -0
- package/src/telemetry/tracer.ts +156 -0
- package/src/types.ts +133 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"KeyScheduler.cjs","sources":["../../../src/executor/KeyScheduler.ts"],"sourcesContent":["import { withSyncSpan } from \"../telemetry/tracer\"\nimport type { OfflineTransaction } from \"../types\"\n\nexport class KeyScheduler {\n private pendingTransactions: Array<OfflineTransaction> = []\n private isRunning = false\n\n schedule(transaction: OfflineTransaction): void {\n withSyncSpan(\n `scheduler.schedule`,\n {\n \"transaction.id\": transaction.id,\n queueLength: this.pendingTransactions.length,\n },\n () => {\n this.pendingTransactions.push(transaction)\n // Sort by creation time to maintain FIFO order\n this.pendingTransactions.sort(\n (a, b) => a.createdAt.getTime() - b.createdAt.getTime()\n )\n }\n )\n }\n\n getNextBatch(_maxConcurrency: number): Array<OfflineTransaction> {\n return withSyncSpan(\n `scheduler.getNextBatch`,\n { pendingCount: this.pendingTransactions.length },\n (span) => {\n // For sequential processing, we ignore maxConcurrency and only process one transaction at a time\n if (this.isRunning || this.pendingTransactions.length === 0) {\n span.setAttribute(`result`, `empty`)\n return []\n }\n\n // Find the first transaction that's ready to run\n const readyTransaction = this.pendingTransactions.find((tx) =>\n this.isReadyToRun(tx)\n )\n\n if (readyTransaction) {\n span.setAttribute(`result`, `found`)\n span.setAttribute(`transaction.id`, readyTransaction.id)\n } else {\n span.setAttribute(`result`, `none_ready`)\n }\n\n return readyTransaction ? [readyTransaction] : []\n }\n )\n }\n\n private isReadyToRun(transaction: OfflineTransaction): boolean {\n return Date.now() >= transaction.nextAttemptAt\n }\n\n markStarted(_transaction: OfflineTransaction): void {\n this.isRunning = true\n }\n\n markCompleted(transaction: OfflineTransaction): void {\n this.removeTransaction(transaction)\n this.isRunning = false\n }\n\n markFailed(_transaction: OfflineTransaction): void {\n this.isRunning = false\n }\n\n private removeTransaction(transaction: OfflineTransaction): void {\n const index = this.pendingTransactions.findIndex(\n (tx) => tx.id === transaction.id\n )\n if (index >= 0) {\n this.pendingTransactions.splice(index, 1)\n }\n }\n\n updateTransaction(transaction: OfflineTransaction): void {\n const index = this.pendingTransactions.findIndex(\n (tx) => tx.id === transaction.id\n )\n if (index >= 0) {\n this.pendingTransactions[index] = transaction\n // Re-sort to maintain FIFO order after update\n this.pendingTransactions.sort(\n (a, b) => a.createdAt.getTime() - b.createdAt.getTime()\n )\n }\n }\n\n getPendingCount(): number {\n return this.pendingTransactions.length\n }\n\n getRunningCount(): number {\n return this.isRunning ? 1 : 0\n }\n\n clear(): void {\n this.pendingTransactions = []\n this.isRunning = false\n }\n\n getAllPendingTransactions(): Array<OfflineTransaction> {\n return [...this.pendingTransactions]\n }\n\n updateTransactions(updatedTransactions: Array<OfflineTransaction>): void {\n for (const updatedTx of updatedTransactions) {\n const index = this.pendingTransactions.findIndex(\n (tx) => tx.id === updatedTx.id\n )\n if (index >= 0) {\n this.pendingTransactions[index] = updatedTx\n }\n }\n // Re-sort to maintain FIFO order after updates\n this.pendingTransactions.sort(\n (a, b) => a.createdAt.getTime() - b.createdAt.getTime()\n )\n }\n}\n"],"names":["withSyncSpan"],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { OfflineTransaction } from '../types.cjs';
|
|
2
|
+
export declare class KeyScheduler {
|
|
3
|
+
private pendingTransactions;
|
|
4
|
+
private isRunning;
|
|
5
|
+
schedule(transaction: OfflineTransaction): void;
|
|
6
|
+
getNextBatch(_maxConcurrency: number): Array<OfflineTransaction>;
|
|
7
|
+
private isReadyToRun;
|
|
8
|
+
markStarted(_transaction: OfflineTransaction): void;
|
|
9
|
+
markCompleted(transaction: OfflineTransaction): void;
|
|
10
|
+
markFailed(_transaction: OfflineTransaction): void;
|
|
11
|
+
private removeTransaction;
|
|
12
|
+
updateTransaction(transaction: OfflineTransaction): void;
|
|
13
|
+
getPendingCount(): number;
|
|
14
|
+
getRunningCount(): number;
|
|
15
|
+
clear(): void;
|
|
16
|
+
getAllPendingTransactions(): Array<OfflineTransaction>;
|
|
17
|
+
updateTransactions(updatedTransactions: Array<OfflineTransaction>): void;
|
|
18
|
+
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const api = require("@opentelemetry/api");
|
|
4
|
+
const RetryPolicy = require("../retry/RetryPolicy.cjs");
|
|
5
|
+
const types = require("../types.cjs");
|
|
6
|
+
const tracer = require("../telemetry/tracer.cjs");
|
|
7
|
+
const HANDLED_EXECUTION_ERROR = Symbol(`HandledExecutionError`);
|
|
8
|
+
function toSpanContext(serialized) {
|
|
9
|
+
if (!serialized) {
|
|
10
|
+
return void 0;
|
|
11
|
+
}
|
|
12
|
+
return {
|
|
13
|
+
traceId: serialized.traceId,
|
|
14
|
+
spanId: serialized.spanId,
|
|
15
|
+
traceFlags: serialized.traceFlags,
|
|
16
|
+
traceState: serialized.traceState ? api.createTraceState(serialized.traceState) : void 0
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
class TransactionExecutor {
|
|
20
|
+
constructor(scheduler, outbox, config, offlineExecutor) {
|
|
21
|
+
this.isExecuting = false;
|
|
22
|
+
this.executionPromise = null;
|
|
23
|
+
this.retryTimer = null;
|
|
24
|
+
this.scheduler = scheduler;
|
|
25
|
+
this.outbox = outbox;
|
|
26
|
+
this.config = config;
|
|
27
|
+
this.retryPolicy = new RetryPolicy.DefaultRetryPolicy(10, config.jitter ?? true);
|
|
28
|
+
this.offlineExecutor = offlineExecutor;
|
|
29
|
+
}
|
|
30
|
+
async execute(transaction) {
|
|
31
|
+
this.scheduler.schedule(transaction);
|
|
32
|
+
await this.executeAll();
|
|
33
|
+
}
|
|
34
|
+
async executeAll() {
|
|
35
|
+
if (this.isExecuting) {
|
|
36
|
+
return this.executionPromise;
|
|
37
|
+
}
|
|
38
|
+
this.isExecuting = true;
|
|
39
|
+
this.executionPromise = this.runExecution();
|
|
40
|
+
try {
|
|
41
|
+
await this.executionPromise;
|
|
42
|
+
} finally {
|
|
43
|
+
this.isExecuting = false;
|
|
44
|
+
this.executionPromise = null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async runExecution() {
|
|
48
|
+
const maxConcurrency = this.config.maxConcurrency ?? 3;
|
|
49
|
+
while (this.scheduler.getPendingCount() > 0) {
|
|
50
|
+
const batch = this.scheduler.getNextBatch(maxConcurrency);
|
|
51
|
+
if (batch.length === 0) {
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
const executions = batch.map(
|
|
55
|
+
(transaction) => this.executeTransaction(transaction)
|
|
56
|
+
);
|
|
57
|
+
await Promise.allSettled(executions);
|
|
58
|
+
}
|
|
59
|
+
this.scheduleNextRetry();
|
|
60
|
+
}
|
|
61
|
+
async executeTransaction(transaction) {
|
|
62
|
+
try {
|
|
63
|
+
await tracer.withNestedSpan(
|
|
64
|
+
`transaction.execute`,
|
|
65
|
+
{
|
|
66
|
+
"transaction.id": transaction.id,
|
|
67
|
+
"transaction.mutationFnName": transaction.mutationFnName,
|
|
68
|
+
"transaction.retryCount": transaction.retryCount,
|
|
69
|
+
"transaction.keyCount": transaction.keys.length
|
|
70
|
+
},
|
|
71
|
+
async (span) => {
|
|
72
|
+
this.scheduler.markStarted(transaction);
|
|
73
|
+
if (transaction.retryCount > 0) {
|
|
74
|
+
span.setAttribute(`retry.attempt`, transaction.retryCount);
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
const result = await this.runMutationFn(transaction);
|
|
78
|
+
this.scheduler.markCompleted(transaction);
|
|
79
|
+
await this.outbox.remove(transaction.id);
|
|
80
|
+
span.setAttribute(`result`, `success`);
|
|
81
|
+
this.offlineExecutor.resolveTransaction(transaction.id, result);
|
|
82
|
+
} catch (error) {
|
|
83
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
84
|
+
span.setAttribute(`result`, `error`);
|
|
85
|
+
await this.handleError(transaction, err);
|
|
86
|
+
err[HANDLED_EXECUTION_ERROR] = true;
|
|
87
|
+
throw err;
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
parentContext: toSpanContext(transaction.spanContext)
|
|
92
|
+
}
|
|
93
|
+
);
|
|
94
|
+
} catch (error) {
|
|
95
|
+
if (error instanceof Error && error[HANDLED_EXECUTION_ERROR] === true) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
throw error;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
async runMutationFn(transaction) {
|
|
102
|
+
const mutationFn = this.config.mutationFns[transaction.mutationFnName];
|
|
103
|
+
if (!mutationFn) {
|
|
104
|
+
const errorMessage = `Unknown mutation function: ${transaction.mutationFnName}`;
|
|
105
|
+
if (this.config.onUnknownMutationFn) {
|
|
106
|
+
this.config.onUnknownMutationFn(transaction.mutationFnName, transaction);
|
|
107
|
+
}
|
|
108
|
+
throw new types.NonRetriableError(errorMessage);
|
|
109
|
+
}
|
|
110
|
+
const transactionWithMutations = {
|
|
111
|
+
id: transaction.id,
|
|
112
|
+
mutations: transaction.mutations,
|
|
113
|
+
metadata: transaction.metadata ?? {}
|
|
114
|
+
};
|
|
115
|
+
await mutationFn({
|
|
116
|
+
transaction: transactionWithMutations,
|
|
117
|
+
idempotencyKey: transaction.idempotencyKey
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
async handleError(transaction, error) {
|
|
121
|
+
return tracer.withNestedSpan(
|
|
122
|
+
`transaction.handleError`,
|
|
123
|
+
{
|
|
124
|
+
"transaction.id": transaction.id,
|
|
125
|
+
"error.name": error.name,
|
|
126
|
+
"error.message": error.message
|
|
127
|
+
},
|
|
128
|
+
async (span) => {
|
|
129
|
+
const shouldRetry = this.retryPolicy.shouldRetry(
|
|
130
|
+
error,
|
|
131
|
+
transaction.retryCount
|
|
132
|
+
);
|
|
133
|
+
span.setAttribute(`shouldRetry`, shouldRetry);
|
|
134
|
+
if (!shouldRetry) {
|
|
135
|
+
this.scheduler.markCompleted(transaction);
|
|
136
|
+
await this.outbox.remove(transaction.id);
|
|
137
|
+
console.warn(
|
|
138
|
+
`Transaction ${transaction.id} failed permanently:`,
|
|
139
|
+
error
|
|
140
|
+
);
|
|
141
|
+
span.setAttribute(`result`, `permanent_failure`);
|
|
142
|
+
this.offlineExecutor.rejectTransaction(transaction.id, error);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
const delay = this.retryPolicy.calculateDelay(transaction.retryCount);
|
|
146
|
+
const updatedTransaction = {
|
|
147
|
+
...transaction,
|
|
148
|
+
retryCount: transaction.retryCount + 1,
|
|
149
|
+
nextAttemptAt: Date.now() + delay,
|
|
150
|
+
lastError: {
|
|
151
|
+
name: error.name,
|
|
152
|
+
message: error.message,
|
|
153
|
+
stack: error.stack
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
span.setAttribute(`retryDelay`, delay);
|
|
157
|
+
span.setAttribute(`nextRetryCount`, updatedTransaction.retryCount);
|
|
158
|
+
this.scheduler.markFailed(transaction);
|
|
159
|
+
this.scheduler.updateTransaction(updatedTransaction);
|
|
160
|
+
try {
|
|
161
|
+
await this.outbox.update(transaction.id, updatedTransaction);
|
|
162
|
+
span.setAttribute(`result`, `scheduled_retry`);
|
|
163
|
+
} catch (persistError) {
|
|
164
|
+
span.recordException(persistError);
|
|
165
|
+
span.setAttribute(`result`, `persist_failed`);
|
|
166
|
+
throw persistError;
|
|
167
|
+
}
|
|
168
|
+
this.scheduleNextRetry();
|
|
169
|
+
}
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
async loadPendingTransactions() {
|
|
173
|
+
const transactions = await this.outbox.getAll();
|
|
174
|
+
let filteredTransactions = transactions;
|
|
175
|
+
if (this.config.beforeRetry) {
|
|
176
|
+
filteredTransactions = this.config.beforeRetry(transactions);
|
|
177
|
+
}
|
|
178
|
+
for (const transaction of filteredTransactions) {
|
|
179
|
+
this.scheduler.schedule(transaction);
|
|
180
|
+
}
|
|
181
|
+
this.resetRetryDelays();
|
|
182
|
+
this.scheduleNextRetry();
|
|
183
|
+
const removedTransactions = transactions.filter(
|
|
184
|
+
(tx) => !filteredTransactions.some((filtered) => filtered.id === tx.id)
|
|
185
|
+
);
|
|
186
|
+
if (removedTransactions.length > 0) {
|
|
187
|
+
await this.outbox.removeMany(removedTransactions.map((tx) => tx.id));
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
clear() {
|
|
191
|
+
this.scheduler.clear();
|
|
192
|
+
this.clearRetryTimer();
|
|
193
|
+
}
|
|
194
|
+
getPendingCount() {
|
|
195
|
+
return this.scheduler.getPendingCount();
|
|
196
|
+
}
|
|
197
|
+
scheduleNextRetry() {
|
|
198
|
+
this.clearRetryTimer();
|
|
199
|
+
const earliestRetryTime = this.getEarliestRetryTime();
|
|
200
|
+
if (earliestRetryTime === null) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
const delay = Math.max(0, earliestRetryTime - Date.now());
|
|
204
|
+
this.retryTimer = setTimeout(() => {
|
|
205
|
+
this.executeAll().catch((error) => {
|
|
206
|
+
console.warn(`Failed to execute retry batch:`, error);
|
|
207
|
+
});
|
|
208
|
+
}, delay);
|
|
209
|
+
}
|
|
210
|
+
getEarliestRetryTime() {
|
|
211
|
+
const allTransactions = this.scheduler.getAllPendingTransactions();
|
|
212
|
+
if (allTransactions.length === 0) {
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
return Math.min(...allTransactions.map((tx) => tx.nextAttemptAt));
|
|
216
|
+
}
|
|
217
|
+
clearRetryTimer() {
|
|
218
|
+
if (this.retryTimer) {
|
|
219
|
+
clearTimeout(this.retryTimer);
|
|
220
|
+
this.retryTimer = null;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
getRunningCount() {
|
|
224
|
+
return this.scheduler.getRunningCount();
|
|
225
|
+
}
|
|
226
|
+
resetRetryDelays() {
|
|
227
|
+
const allTransactions = this.scheduler.getAllPendingTransactions();
|
|
228
|
+
const updatedTransactions = allTransactions.map((transaction) => ({
|
|
229
|
+
...transaction,
|
|
230
|
+
nextAttemptAt: Date.now()
|
|
231
|
+
}));
|
|
232
|
+
this.scheduler.updateTransactions(updatedTransactions);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
exports.TransactionExecutor = TransactionExecutor;
|
|
236
|
+
//# sourceMappingURL=TransactionExecutor.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TransactionExecutor.cjs","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":["createTraceState","DefaultRetryPolicy","withNestedSpan","NonRetriableError"],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { KeyScheduler } from './KeyScheduler.cjs';
|
|
2
|
+
import { OutboxManager } from '../outbox/OutboxManager.cjs';
|
|
3
|
+
import { OfflineConfig, OfflineTransaction } from '../types.cjs';
|
|
4
|
+
export declare class TransactionExecutor {
|
|
5
|
+
private scheduler;
|
|
6
|
+
private outbox;
|
|
7
|
+
private config;
|
|
8
|
+
private retryPolicy;
|
|
9
|
+
private isExecuting;
|
|
10
|
+
private executionPromise;
|
|
11
|
+
private offlineExecutor;
|
|
12
|
+
private retryTimer;
|
|
13
|
+
constructor(scheduler: KeyScheduler, outbox: OutboxManager, config: OfflineConfig, offlineExecutor: any);
|
|
14
|
+
execute(transaction: OfflineTransaction): Promise<void>;
|
|
15
|
+
executeAll(): Promise<void>;
|
|
16
|
+
private runExecution;
|
|
17
|
+
private executeTransaction;
|
|
18
|
+
private runMutationFn;
|
|
19
|
+
private handleError;
|
|
20
|
+
loadPendingTransactions(): Promise<void>;
|
|
21
|
+
clear(): void;
|
|
22
|
+
getPendingCount(): number;
|
|
23
|
+
private scheduleNextRetry;
|
|
24
|
+
private getEarliestRetryTime;
|
|
25
|
+
private clearRetryTimer;
|
|
26
|
+
getRunningCount(): number;
|
|
27
|
+
resetRetryDelays(): void;
|
|
28
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const OfflineExecutor = require("./OfflineExecutor.cjs");
|
|
4
|
+
const types = require("./types.cjs");
|
|
5
|
+
const IndexedDBAdapter = require("./storage/IndexedDBAdapter.cjs");
|
|
6
|
+
const LocalStorageAdapter = require("./storage/LocalStorageAdapter.cjs");
|
|
7
|
+
const RetryPolicy = require("./retry/RetryPolicy.cjs");
|
|
8
|
+
const BackoffCalculator = require("./retry/BackoffCalculator.cjs");
|
|
9
|
+
const WebLocksLeader = require("./coordination/WebLocksLeader.cjs");
|
|
10
|
+
const BroadcastChannelLeader = require("./coordination/BroadcastChannelLeader.cjs");
|
|
11
|
+
const OnlineDetector = require("./connectivity/OnlineDetector.cjs");
|
|
12
|
+
const OfflineTransaction = require("./api/OfflineTransaction.cjs");
|
|
13
|
+
const OfflineAction = require("./api/OfflineAction.cjs");
|
|
14
|
+
const OutboxManager = require("./outbox/OutboxManager.cjs");
|
|
15
|
+
const TransactionSerializer = require("./outbox/TransactionSerializer.cjs");
|
|
16
|
+
const KeyScheduler = require("./executor/KeyScheduler.cjs");
|
|
17
|
+
const TransactionExecutor = require("./executor/TransactionExecutor.cjs");
|
|
18
|
+
exports.OfflineExecutor = OfflineExecutor.OfflineExecutor;
|
|
19
|
+
exports.startOfflineExecutor = OfflineExecutor.startOfflineExecutor;
|
|
20
|
+
exports.NonRetriableError = types.NonRetriableError;
|
|
21
|
+
exports.IndexedDBAdapter = IndexedDBAdapter.IndexedDBAdapter;
|
|
22
|
+
exports.LocalStorageAdapter = LocalStorageAdapter.LocalStorageAdapter;
|
|
23
|
+
exports.DefaultRetryPolicy = RetryPolicy.DefaultRetryPolicy;
|
|
24
|
+
exports.BackoffCalculator = BackoffCalculator.BackoffCalculator;
|
|
25
|
+
exports.WebLocksLeader = WebLocksLeader.WebLocksLeader;
|
|
26
|
+
exports.BroadcastChannelLeader = BroadcastChannelLeader.BroadcastChannelLeader;
|
|
27
|
+
exports.DefaultOnlineDetector = OnlineDetector.DefaultOnlineDetector;
|
|
28
|
+
exports.OfflineTransactionAPI = OfflineTransaction.OfflineTransaction;
|
|
29
|
+
exports.createOfflineAction = OfflineAction.createOfflineAction;
|
|
30
|
+
exports.OutboxManager = OutboxManager.OutboxManager;
|
|
31
|
+
exports.TransactionSerializer = TransactionSerializer.TransactionSerializer;
|
|
32
|
+
exports.KeyScheduler = KeyScheduler.KeyScheduler;
|
|
33
|
+
exports.TransactionExecutor = TransactionExecutor.TransactionExecutor;
|
|
34
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export { OfflineExecutor, startOfflineExecutor } from './OfflineExecutor.cjs';
|
|
2
|
+
export type { OfflineTransaction, OfflineConfig, StorageAdapter, RetryPolicy, LeaderElection, OnlineDetector, CreateOfflineTransactionOptions, CreateOfflineActionOptions, SerializedError, SerializedMutation, } from './types.cjs';
|
|
3
|
+
export { NonRetriableError } from './types.cjs';
|
|
4
|
+
export { IndexedDBAdapter } from './storage/IndexedDBAdapter.cjs';
|
|
5
|
+
export { LocalStorageAdapter } from './storage/LocalStorageAdapter.cjs';
|
|
6
|
+
export { DefaultRetryPolicy } from './retry/RetryPolicy.cjs';
|
|
7
|
+
export { BackoffCalculator } from './retry/BackoffCalculator.cjs';
|
|
8
|
+
export { WebLocksLeader } from './coordination/WebLocksLeader.cjs';
|
|
9
|
+
export { BroadcastChannelLeader } from './coordination/BroadcastChannelLeader.cjs';
|
|
10
|
+
export { DefaultOnlineDetector } from './connectivity/OnlineDetector.cjs';
|
|
11
|
+
export { OfflineTransaction as OfflineTransactionAPI } from './api/OfflineTransaction.cjs';
|
|
12
|
+
export { createOfflineAction } from './api/OfflineAction.cjs';
|
|
13
|
+
export { OutboxManager } from './outbox/OutboxManager.cjs';
|
|
14
|
+
export { TransactionSerializer } from './outbox/TransactionSerializer.cjs';
|
|
15
|
+
export { KeyScheduler } from './executor/KeyScheduler.cjs';
|
|
16
|
+
export { TransactionExecutor } from './executor/TransactionExecutor.cjs';
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const tracer = require("../telemetry/tracer.cjs");
|
|
4
|
+
const TransactionSerializer = require("./TransactionSerializer.cjs");
|
|
5
|
+
class OutboxManager {
|
|
6
|
+
constructor(storage, collections) {
|
|
7
|
+
this.keyPrefix = `tx:`;
|
|
8
|
+
this.storage = storage;
|
|
9
|
+
this.serializer = new TransactionSerializer.TransactionSerializer(collections);
|
|
10
|
+
}
|
|
11
|
+
getStorageKey(id) {
|
|
12
|
+
return `${this.keyPrefix}${id}`;
|
|
13
|
+
}
|
|
14
|
+
async add(transaction) {
|
|
15
|
+
return tracer.withSpan(
|
|
16
|
+
`outbox.add`,
|
|
17
|
+
{
|
|
18
|
+
"transaction.id": transaction.id,
|
|
19
|
+
"transaction.mutationFnName": transaction.mutationFnName,
|
|
20
|
+
"transaction.keyCount": transaction.keys.length
|
|
21
|
+
},
|
|
22
|
+
async () => {
|
|
23
|
+
const key = this.getStorageKey(transaction.id);
|
|
24
|
+
const serialized = this.serializer.serialize(transaction);
|
|
25
|
+
await this.storage.set(key, serialized);
|
|
26
|
+
}
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
async get(id) {
|
|
30
|
+
return tracer.withSpan(`outbox.get`, { "transaction.id": id }, async (span) => {
|
|
31
|
+
const key = this.getStorageKey(id);
|
|
32
|
+
const data = await this.storage.get(key);
|
|
33
|
+
if (!data) {
|
|
34
|
+
span.setAttribute(`result`, `not_found`);
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
const transaction = this.serializer.deserialize(data);
|
|
39
|
+
span.setAttribute(`result`, `found`);
|
|
40
|
+
return transaction;
|
|
41
|
+
} catch (error) {
|
|
42
|
+
console.warn(`Failed to deserialize transaction ${id}:`, error);
|
|
43
|
+
span.setAttribute(`result`, `deserialize_error`);
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
async getAll() {
|
|
49
|
+
return tracer.withSpan(`outbox.getAll`, {}, async (span) => {
|
|
50
|
+
const keys = await this.storage.keys();
|
|
51
|
+
const transactionKeys = keys.filter(
|
|
52
|
+
(key) => key.startsWith(this.keyPrefix)
|
|
53
|
+
);
|
|
54
|
+
span.setAttribute(`transactionCount`, transactionKeys.length);
|
|
55
|
+
const transactions = [];
|
|
56
|
+
for (const key of transactionKeys) {
|
|
57
|
+
const data = await this.storage.get(key);
|
|
58
|
+
if (data) {
|
|
59
|
+
try {
|
|
60
|
+
const transaction = this.serializer.deserialize(data);
|
|
61
|
+
transactions.push(transaction);
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.warn(
|
|
64
|
+
`Failed to deserialize transaction from key ${key}:`,
|
|
65
|
+
error
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return transactions.sort(
|
|
71
|
+
(a, b) => a.createdAt.getTime() - b.createdAt.getTime()
|
|
72
|
+
);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
async getByKeys(keys) {
|
|
76
|
+
const allTransactions = await this.getAll();
|
|
77
|
+
const keySet = new Set(keys);
|
|
78
|
+
return allTransactions.filter(
|
|
79
|
+
(transaction) => transaction.keys.some((key) => keySet.has(key))
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
async update(id, updates) {
|
|
83
|
+
return tracer.withSpan(`outbox.update`, { "transaction.id": id }, async () => {
|
|
84
|
+
const existing = await this.get(id);
|
|
85
|
+
if (!existing) {
|
|
86
|
+
throw new Error(`Transaction ${id} not found`);
|
|
87
|
+
}
|
|
88
|
+
const updated = { ...existing, ...updates };
|
|
89
|
+
await this.add(updated);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
async remove(id) {
|
|
93
|
+
return tracer.withSpan(`outbox.remove`, { "transaction.id": id }, async () => {
|
|
94
|
+
const key = this.getStorageKey(id);
|
|
95
|
+
await this.storage.delete(key);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
async removeMany(ids) {
|
|
99
|
+
return tracer.withSpan(`outbox.removeMany`, { count: ids.length }, async () => {
|
|
100
|
+
await Promise.all(ids.map((id) => this.remove(id)));
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
async clear() {
|
|
104
|
+
const keys = await this.storage.keys();
|
|
105
|
+
const transactionKeys = keys.filter((key) => key.startsWith(this.keyPrefix));
|
|
106
|
+
await Promise.all(transactionKeys.map((key) => this.storage.delete(key)));
|
|
107
|
+
}
|
|
108
|
+
async count() {
|
|
109
|
+
const keys = await this.storage.keys();
|
|
110
|
+
return keys.filter((key) => key.startsWith(this.keyPrefix)).length;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
exports.OutboxManager = OutboxManager;
|
|
114
|
+
//# sourceMappingURL=OutboxManager.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"OutboxManager.cjs","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>\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":["TransactionSerializer","withSpan"],"mappings":";;;;AAKO,MAAM,cAAc;AAAA,EAKzB,YACE,SACA,aACA;AALF,SAAQ,YAAY;AAMlB,SAAK,UAAU;AACf,SAAK,aAAa,IAAIA,sBAAAA,sBAAsB,WAAW;AAAA,EACzD;AAAA,EAEQ,cAAc,IAAoB;AACxC,WAAO,GAAG,KAAK,SAAS,GAAG,EAAE;AAAA,EAC/B;AAAA,EAEA,MAAM,IAAI,aAAgD;AACxD,WAAOC,OAAAA;AAAAA,MACL;AAAA,MACA;AAAA,QACE,kBAAkB,YAAY;AAAA,QAC9B,8BAA8B,YAAY;AAAA,QAC1C,wBAAwB,YAAY,KAAK;AAAA,MAAA;AAAA,MAE3C,YAAY;AACV,cAAM,MAAM,KAAK,cAAc,YAAY,EAAE;AAC7C,cAAM,aAAa,KAAK,WAAW,UAAU,WAAW;AACxD,cAAM,KAAK,QAAQ,IAAI,KAAK,UAAU;AAAA,MACxC;AAAA,IAAA;AAAA,EAEJ;AAAA,EAEA,MAAM,IAAI,IAAgD;AACxD,WAAOA,OAAAA,SAAS,cAAc,EAAE,kBAAkB,GAAA,GAAM,OAAO,SAAS;AACtE,YAAM,MAAM,KAAK,cAAc,EAAE;AACjC,YAAM,OAAO,MAAM,KAAK,QAAQ,IAAI,GAAG;AAEvC,UAAI,CAAC,MAAM;AACT,aAAK,aAAa,UAAU,WAAW;AACvC,eAAO;AAAA,MACT;AAEA,UAAI;AACF,cAAM,cAAc,KAAK,WAAW,YAAY,IAAI;AACpD,aAAK,aAAa,UAAU,OAAO;AACnC,eAAO;AAAA,MACT,SAAS,OAAO;AACd,gBAAQ,KAAK,qCAAqC,EAAE,KAAK,KAAK;AAC9D,aAAK,aAAa,UAAU,mBAAmB;AAC/C,eAAO;AAAA,MACT;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,SAA6C;AACjD,WAAOA,OAAAA,SAAS,iBAAiB,CAAA,GAAI,OAAO,SAAS;AACnD,YAAM,OAAO,MAAM,KAAK,QAAQ,KAAA;AAChC,YAAM,kBAAkB,KAAK;AAAA,QAAO,CAAC,QACnC,IAAI,WAAW,KAAK,SAAS;AAAA,MAAA;AAG/B,WAAK,aAAa,oBAAoB,gBAAgB,MAAM;AAE5D,YAAM,eAA0C,CAAA;AAEhD,iBAAW,OAAO,iBAAiB;AACjC,cAAM,OAAO,MAAM,KAAK,QAAQ,IAAI,GAAG;AACvC,YAAI,MAAM;AACR,cAAI;AACF,kBAAM,cAAc,KAAK,WAAW,YAAY,IAAI;AACpD,yBAAa,KAAK,WAAW;AAAA,UAC/B,SAAS,OAAO;AACd,oBAAQ;AAAA,cACN,8CAA8C,GAAG;AAAA,cACjD;AAAA,YAAA;AAAA,UAEJ;AAAA,QACF;AAAA,MACF;AAEA,aAAO,aAAa;AAAA,QAClB,CAAC,GAAG,MAAM,EAAE,UAAU,YAAY,EAAE,UAAU,QAAA;AAAA,MAAQ;AAAA,IAE1D,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,UAAU,MAAyD;AACvE,UAAM,kBAAkB,MAAM,KAAK,OAAA;AACnC,UAAM,SAAS,IAAI,IAAI,IAAI;AAE3B,WAAO,gBAAgB;AAAA,MAAO,CAAC,gBAC7B,YAAY,KAAK,KAAK,CAAC,QAAQ,OAAO,IAAI,GAAG,CAAC;AAAA,IAAA;AAAA,EAElD;AAAA,EAEA,MAAM,OACJ,IACA,SACe;AACf,WAAOA,OAAAA,SAAS,iBAAiB,EAAE,kBAAkB,GAAA,GAAM,YAAY;AACrE,YAAM,WAAW,MAAM,KAAK,IAAI,EAAE;AAClC,UAAI,CAAC,UAAU;AACb,cAAM,IAAI,MAAM,eAAe,EAAE,YAAY;AAAA,MAC/C;AAEA,YAAM,UAAU,EAAE,GAAG,UAAU,GAAG,QAAA;AAClC,YAAM,KAAK,IAAI,OAAO;AAAA,IACxB,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,OAAO,IAA2B;AACtC,WAAOA,OAAAA,SAAS,iBAAiB,EAAE,kBAAkB,GAAA,GAAM,YAAY;AACrE,YAAM,MAAM,KAAK,cAAc,EAAE;AACjC,YAAM,KAAK,QAAQ,OAAO,GAAG;AAAA,IAC/B,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,WAAW,KAAmC;AAClD,WAAOA,OAAAA,SAAS,qBAAqB,EAAE,OAAO,IAAI,OAAA,GAAU,YAAY;AACtE,YAAM,QAAQ,IAAI,IAAI,IAAI,CAAC,OAAO,KAAK,OAAO,EAAE,CAAC,CAAC;AAAA,IACpD,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,QAAuB;AAC3B,UAAM,OAAO,MAAM,KAAK,QAAQ,KAAA;AAChC,UAAM,kBAAkB,KAAK,OAAO,CAAC,QAAQ,IAAI,WAAW,KAAK,SAAS,CAAC;AAE3E,UAAM,QAAQ,IAAI,gBAAgB,IAAI,CAAC,QAAQ,KAAK,QAAQ,OAAO,GAAG,CAAC,CAAC;AAAA,EAC1E;AAAA,EAEA,MAAM,QAAyB;AAC7B,UAAM,OAAO,MAAM,KAAK,QAAQ,KAAA;AAChC,WAAO,KAAK,OAAO,CAAC,QAAQ,IAAI,WAAW,KAAK,SAAS,CAAC,EAAE;AAAA,EAC9D;AACF;;"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { OfflineTransaction, StorageAdapter } from '../types.cjs';
|
|
2
|
+
import { Collection } from '@tanstack/db';
|
|
3
|
+
export declare class OutboxManager {
|
|
4
|
+
private storage;
|
|
5
|
+
private serializer;
|
|
6
|
+
private keyPrefix;
|
|
7
|
+
constructor(storage: StorageAdapter, collections: Record<string, Collection>);
|
|
8
|
+
private getStorageKey;
|
|
9
|
+
add(transaction: OfflineTransaction): Promise<void>;
|
|
10
|
+
get(id: string): Promise<OfflineTransaction | null>;
|
|
11
|
+
getAll(): Promise<Array<OfflineTransaction>>;
|
|
12
|
+
getByKeys(keys: Array<string>): Promise<Array<OfflineTransaction>>;
|
|
13
|
+
update(id: string, updates: Partial<OfflineTransaction>): Promise<void>;
|
|
14
|
+
remove(id: string): Promise<void>;
|
|
15
|
+
removeMany(ids: Array<string>): Promise<void>;
|
|
16
|
+
clear(): Promise<void>;
|
|
17
|
+
count(): Promise<number>;
|
|
18
|
+
}
|