@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.
Files changed (138) hide show
  1. package/README.md +219 -0
  2. package/dist/cjs/OfflineExecutor.cjs +266 -0
  3. package/dist/cjs/OfflineExecutor.cjs.map +1 -0
  4. package/dist/cjs/OfflineExecutor.d.cts +39 -0
  5. package/dist/cjs/api/OfflineAction.cjs +47 -0
  6. package/dist/cjs/api/OfflineAction.cjs.map +1 -0
  7. package/dist/cjs/api/OfflineAction.d.cts +3 -0
  8. package/dist/cjs/api/OfflineTransaction.cjs +96 -0
  9. package/dist/cjs/api/OfflineTransaction.cjs.map +1 -0
  10. package/dist/cjs/api/OfflineTransaction.d.cts +18 -0
  11. package/dist/cjs/connectivity/OnlineDetector.cjs +73 -0
  12. package/dist/cjs/connectivity/OnlineDetector.cjs.map +1 -0
  13. package/dist/cjs/connectivity/OnlineDetector.d.cts +15 -0
  14. package/dist/cjs/coordination/BroadcastChannelLeader.cjs +146 -0
  15. package/dist/cjs/coordination/BroadcastChannelLeader.cjs.map +1 -0
  16. package/dist/cjs/coordination/BroadcastChannelLeader.d.cts +26 -0
  17. package/dist/cjs/coordination/LeaderElection.cjs +31 -0
  18. package/dist/cjs/coordination/LeaderElection.cjs.map +1 -0
  19. package/dist/cjs/coordination/LeaderElection.d.cts +10 -0
  20. package/dist/cjs/coordination/WebLocksLeader.cjs +71 -0
  21. package/dist/cjs/coordination/WebLocksLeader.cjs.map +1 -0
  22. package/dist/cjs/coordination/WebLocksLeader.d.cts +10 -0
  23. package/dist/cjs/executor/KeyScheduler.cjs +106 -0
  24. package/dist/cjs/executor/KeyScheduler.cjs.map +1 -0
  25. package/dist/cjs/executor/KeyScheduler.d.cts +18 -0
  26. package/dist/cjs/executor/TransactionExecutor.cjs +236 -0
  27. package/dist/cjs/executor/TransactionExecutor.cjs.map +1 -0
  28. package/dist/cjs/executor/TransactionExecutor.d.cts +28 -0
  29. package/dist/cjs/index.cjs +34 -0
  30. package/dist/cjs/index.cjs.map +1 -0
  31. package/dist/cjs/index.d.cts +16 -0
  32. package/dist/cjs/outbox/OutboxManager.cjs +114 -0
  33. package/dist/cjs/outbox/OutboxManager.cjs.map +1 -0
  34. package/dist/cjs/outbox/OutboxManager.d.cts +18 -0
  35. package/dist/cjs/outbox/TransactionSerializer.cjs +135 -0
  36. package/dist/cjs/outbox/TransactionSerializer.cjs.map +1 -0
  37. package/dist/cjs/outbox/TransactionSerializer.d.cts +15 -0
  38. package/dist/cjs/retry/BackoffCalculator.cjs +14 -0
  39. package/dist/cjs/retry/BackoffCalculator.cjs.map +1 -0
  40. package/dist/cjs/retry/BackoffCalculator.d.cts +5 -0
  41. package/dist/cjs/retry/NonRetriableError.d.cts +1 -0
  42. package/dist/cjs/retry/RetryPolicy.cjs +33 -0
  43. package/dist/cjs/retry/RetryPolicy.cjs.map +1 -0
  44. package/dist/cjs/retry/RetryPolicy.d.cts +8 -0
  45. package/dist/cjs/storage/IndexedDBAdapter.cjs +104 -0
  46. package/dist/cjs/storage/IndexedDBAdapter.cjs.map +1 -0
  47. package/dist/cjs/storage/IndexedDBAdapter.d.cts +14 -0
  48. package/dist/cjs/storage/LocalStorageAdapter.cjs +71 -0
  49. package/dist/cjs/storage/LocalStorageAdapter.cjs.map +1 -0
  50. package/dist/cjs/storage/LocalStorageAdapter.d.cts +11 -0
  51. package/dist/cjs/storage/StorageAdapter.cjs +6 -0
  52. package/dist/cjs/storage/StorageAdapter.cjs.map +1 -0
  53. package/dist/cjs/storage/StorageAdapter.d.cts +9 -0
  54. package/dist/cjs/telemetry/tracer.cjs +91 -0
  55. package/dist/cjs/telemetry/tracer.cjs.map +1 -0
  56. package/dist/cjs/telemetry/tracer.d.cts +29 -0
  57. package/dist/cjs/types.cjs +10 -0
  58. package/dist/cjs/types.cjs.map +1 -0
  59. package/dist/cjs/types.d.cts +101 -0
  60. package/dist/esm/OfflineExecutor.d.ts +39 -0
  61. package/dist/esm/OfflineExecutor.js +266 -0
  62. package/dist/esm/OfflineExecutor.js.map +1 -0
  63. package/dist/esm/api/OfflineAction.d.ts +3 -0
  64. package/dist/esm/api/OfflineAction.js +47 -0
  65. package/dist/esm/api/OfflineAction.js.map +1 -0
  66. package/dist/esm/api/OfflineTransaction.d.ts +18 -0
  67. package/dist/esm/api/OfflineTransaction.js +96 -0
  68. package/dist/esm/api/OfflineTransaction.js.map +1 -0
  69. package/dist/esm/connectivity/OnlineDetector.d.ts +15 -0
  70. package/dist/esm/connectivity/OnlineDetector.js +73 -0
  71. package/dist/esm/connectivity/OnlineDetector.js.map +1 -0
  72. package/dist/esm/coordination/BroadcastChannelLeader.d.ts +26 -0
  73. package/dist/esm/coordination/BroadcastChannelLeader.js +146 -0
  74. package/dist/esm/coordination/BroadcastChannelLeader.js.map +1 -0
  75. package/dist/esm/coordination/LeaderElection.d.ts +10 -0
  76. package/dist/esm/coordination/LeaderElection.js +31 -0
  77. package/dist/esm/coordination/LeaderElection.js.map +1 -0
  78. package/dist/esm/coordination/WebLocksLeader.d.ts +10 -0
  79. package/dist/esm/coordination/WebLocksLeader.js +71 -0
  80. package/dist/esm/coordination/WebLocksLeader.js.map +1 -0
  81. package/dist/esm/executor/KeyScheduler.d.ts +18 -0
  82. package/dist/esm/executor/KeyScheduler.js +106 -0
  83. package/dist/esm/executor/KeyScheduler.js.map +1 -0
  84. package/dist/esm/executor/TransactionExecutor.d.ts +28 -0
  85. package/dist/esm/executor/TransactionExecutor.js +236 -0
  86. package/dist/esm/executor/TransactionExecutor.js.map +1 -0
  87. package/dist/esm/index.d.ts +16 -0
  88. package/dist/esm/index.js +34 -0
  89. package/dist/esm/index.js.map +1 -0
  90. package/dist/esm/outbox/OutboxManager.d.ts +18 -0
  91. package/dist/esm/outbox/OutboxManager.js +114 -0
  92. package/dist/esm/outbox/OutboxManager.js.map +1 -0
  93. package/dist/esm/outbox/TransactionSerializer.d.ts +15 -0
  94. package/dist/esm/outbox/TransactionSerializer.js +135 -0
  95. package/dist/esm/outbox/TransactionSerializer.js.map +1 -0
  96. package/dist/esm/retry/BackoffCalculator.d.ts +5 -0
  97. package/dist/esm/retry/BackoffCalculator.js +14 -0
  98. package/dist/esm/retry/BackoffCalculator.js.map +1 -0
  99. package/dist/esm/retry/NonRetriableError.d.ts +1 -0
  100. package/dist/esm/retry/RetryPolicy.d.ts +8 -0
  101. package/dist/esm/retry/RetryPolicy.js +33 -0
  102. package/dist/esm/retry/RetryPolicy.js.map +1 -0
  103. package/dist/esm/storage/IndexedDBAdapter.d.ts +14 -0
  104. package/dist/esm/storage/IndexedDBAdapter.js +104 -0
  105. package/dist/esm/storage/IndexedDBAdapter.js.map +1 -0
  106. package/dist/esm/storage/LocalStorageAdapter.d.ts +11 -0
  107. package/dist/esm/storage/LocalStorageAdapter.js +71 -0
  108. package/dist/esm/storage/LocalStorageAdapter.js.map +1 -0
  109. package/dist/esm/storage/StorageAdapter.d.ts +9 -0
  110. package/dist/esm/storage/StorageAdapter.js +6 -0
  111. package/dist/esm/storage/StorageAdapter.js.map +1 -0
  112. package/dist/esm/telemetry/tracer.d.ts +29 -0
  113. package/dist/esm/telemetry/tracer.js +91 -0
  114. package/dist/esm/telemetry/tracer.js.map +1 -0
  115. package/dist/esm/types.d.ts +101 -0
  116. package/dist/esm/types.js +10 -0
  117. package/dist/esm/types.js.map +1 -0
  118. package/package.json +66 -0
  119. package/src/OfflineExecutor.ts +360 -0
  120. package/src/api/OfflineAction.ts +68 -0
  121. package/src/api/OfflineTransaction.ts +134 -0
  122. package/src/connectivity/OnlineDetector.ts +87 -0
  123. package/src/coordination/BroadcastChannelLeader.ts +181 -0
  124. package/src/coordination/LeaderElection.ts +35 -0
  125. package/src/coordination/WebLocksLeader.ts +82 -0
  126. package/src/executor/KeyScheduler.ts +123 -0
  127. package/src/executor/TransactionExecutor.ts +330 -0
  128. package/src/index.ts +47 -0
  129. package/src/outbox/OutboxManager.ts +141 -0
  130. package/src/outbox/TransactionSerializer.ts +163 -0
  131. package/src/retry/BackoffCalculator.ts +13 -0
  132. package/src/retry/NonRetriableError.ts +1 -0
  133. package/src/retry/RetryPolicy.ts +41 -0
  134. package/src/storage/IndexedDBAdapter.ts +119 -0
  135. package/src/storage/LocalStorageAdapter.ts +79 -0
  136. package/src/storage/StorageAdapter.ts +11 -0
  137. package/src/telemetry/tracer.ts +156 -0
  138. package/src/types.ts +133 -0
@@ -0,0 +1,236 @@
1
+ import { createTraceState } from "@opentelemetry/api";
2
+ import { DefaultRetryPolicy } from "../retry/RetryPolicy.js";
3
+ import { NonRetriableError } from "../types.js";
4
+ import { withNestedSpan } from "../telemetry/tracer.js";
5
+ 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
+ class TransactionExecutor {
18
+ constructor(scheduler, outbox, config, offlineExecutor) {
19
+ this.isExecuting = false;
20
+ this.executionPromise = null;
21
+ this.retryTimer = null;
22
+ this.scheduler = scheduler;
23
+ this.outbox = outbox;
24
+ this.config = config;
25
+ this.retryPolicy = new DefaultRetryPolicy(10, config.jitter ?? true);
26
+ this.offlineExecutor = offlineExecutor;
27
+ }
28
+ async execute(transaction) {
29
+ this.scheduler.schedule(transaction);
30
+ await this.executeAll();
31
+ }
32
+ async executeAll() {
33
+ if (this.isExecuting) {
34
+ return this.executionPromise;
35
+ }
36
+ this.isExecuting = true;
37
+ this.executionPromise = this.runExecution();
38
+ try {
39
+ await this.executionPromise;
40
+ } finally {
41
+ this.isExecuting = false;
42
+ this.executionPromise = null;
43
+ }
44
+ }
45
+ async runExecution() {
46
+ const maxConcurrency = this.config.maxConcurrency ?? 3;
47
+ while (this.scheduler.getPendingCount() > 0) {
48
+ const batch = this.scheduler.getNextBatch(maxConcurrency);
49
+ if (batch.length === 0) {
50
+ break;
51
+ }
52
+ const executions = batch.map(
53
+ (transaction) => this.executeTransaction(transaction)
54
+ );
55
+ await Promise.allSettled(executions);
56
+ }
57
+ this.scheduleNextRetry();
58
+ }
59
+ async executeTransaction(transaction) {
60
+ try {
61
+ await withNestedSpan(
62
+ `transaction.execute`,
63
+ {
64
+ "transaction.id": transaction.id,
65
+ "transaction.mutationFnName": transaction.mutationFnName,
66
+ "transaction.retryCount": transaction.retryCount,
67
+ "transaction.keyCount": transaction.keys.length
68
+ },
69
+ async (span) => {
70
+ this.scheduler.markStarted(transaction);
71
+ if (transaction.retryCount > 0) {
72
+ span.setAttribute(`retry.attempt`, transaction.retryCount);
73
+ }
74
+ try {
75
+ const result = await this.runMutationFn(transaction);
76
+ this.scheduler.markCompleted(transaction);
77
+ await this.outbox.remove(transaction.id);
78
+ span.setAttribute(`result`, `success`);
79
+ this.offlineExecutor.resolveTransaction(transaction.id, result);
80
+ } catch (error) {
81
+ const err = error instanceof Error ? error : new Error(String(error));
82
+ span.setAttribute(`result`, `error`);
83
+ await this.handleError(transaction, err);
84
+ err[HANDLED_EXECUTION_ERROR] = true;
85
+ throw err;
86
+ }
87
+ },
88
+ {
89
+ parentContext: toSpanContext(transaction.spanContext)
90
+ }
91
+ );
92
+ } catch (error) {
93
+ if (error instanceof Error && error[HANDLED_EXECUTION_ERROR] === true) {
94
+ return;
95
+ }
96
+ throw error;
97
+ }
98
+ }
99
+ async runMutationFn(transaction) {
100
+ const mutationFn = this.config.mutationFns[transaction.mutationFnName];
101
+ if (!mutationFn) {
102
+ const errorMessage = `Unknown mutation function: ${transaction.mutationFnName}`;
103
+ if (this.config.onUnknownMutationFn) {
104
+ this.config.onUnknownMutationFn(transaction.mutationFnName, transaction);
105
+ }
106
+ throw new NonRetriableError(errorMessage);
107
+ }
108
+ const transactionWithMutations = {
109
+ id: transaction.id,
110
+ mutations: transaction.mutations,
111
+ metadata: transaction.metadata ?? {}
112
+ };
113
+ await mutationFn({
114
+ transaction: transactionWithMutations,
115
+ idempotencyKey: transaction.idempotencyKey
116
+ });
117
+ }
118
+ async handleError(transaction, error) {
119
+ return withNestedSpan(
120
+ `transaction.handleError`,
121
+ {
122
+ "transaction.id": transaction.id,
123
+ "error.name": error.name,
124
+ "error.message": error.message
125
+ },
126
+ async (span) => {
127
+ const shouldRetry = this.retryPolicy.shouldRetry(
128
+ error,
129
+ transaction.retryCount
130
+ );
131
+ span.setAttribute(`shouldRetry`, shouldRetry);
132
+ if (!shouldRetry) {
133
+ this.scheduler.markCompleted(transaction);
134
+ await this.outbox.remove(transaction.id);
135
+ console.warn(
136
+ `Transaction ${transaction.id} failed permanently:`,
137
+ error
138
+ );
139
+ span.setAttribute(`result`, `permanent_failure`);
140
+ this.offlineExecutor.rejectTransaction(transaction.id, error);
141
+ return;
142
+ }
143
+ const delay = this.retryPolicy.calculateDelay(transaction.retryCount);
144
+ const updatedTransaction = {
145
+ ...transaction,
146
+ retryCount: transaction.retryCount + 1,
147
+ nextAttemptAt: Date.now() + delay,
148
+ lastError: {
149
+ name: error.name,
150
+ message: error.message,
151
+ stack: error.stack
152
+ }
153
+ };
154
+ span.setAttribute(`retryDelay`, delay);
155
+ span.setAttribute(`nextRetryCount`, updatedTransaction.retryCount);
156
+ this.scheduler.markFailed(transaction);
157
+ this.scheduler.updateTransaction(updatedTransaction);
158
+ try {
159
+ await this.outbox.update(transaction.id, updatedTransaction);
160
+ span.setAttribute(`result`, `scheduled_retry`);
161
+ } catch (persistError) {
162
+ span.recordException(persistError);
163
+ span.setAttribute(`result`, `persist_failed`);
164
+ throw persistError;
165
+ }
166
+ this.scheduleNextRetry();
167
+ }
168
+ );
169
+ }
170
+ async loadPendingTransactions() {
171
+ const transactions = await this.outbox.getAll();
172
+ let filteredTransactions = transactions;
173
+ if (this.config.beforeRetry) {
174
+ filteredTransactions = this.config.beforeRetry(transactions);
175
+ }
176
+ for (const transaction of filteredTransactions) {
177
+ this.scheduler.schedule(transaction);
178
+ }
179
+ this.resetRetryDelays();
180
+ this.scheduleNextRetry();
181
+ const removedTransactions = transactions.filter(
182
+ (tx) => !filteredTransactions.some((filtered) => filtered.id === tx.id)
183
+ );
184
+ if (removedTransactions.length > 0) {
185
+ await this.outbox.removeMany(removedTransactions.map((tx) => tx.id));
186
+ }
187
+ }
188
+ clear() {
189
+ this.scheduler.clear();
190
+ this.clearRetryTimer();
191
+ }
192
+ getPendingCount() {
193
+ return this.scheduler.getPendingCount();
194
+ }
195
+ scheduleNextRetry() {
196
+ this.clearRetryTimer();
197
+ const earliestRetryTime = this.getEarliestRetryTime();
198
+ if (earliestRetryTime === null) {
199
+ return;
200
+ }
201
+ const delay = Math.max(0, earliestRetryTime - Date.now());
202
+ this.retryTimer = setTimeout(() => {
203
+ this.executeAll().catch((error) => {
204
+ console.warn(`Failed to execute retry batch:`, error);
205
+ });
206
+ }, delay);
207
+ }
208
+ getEarliestRetryTime() {
209
+ const allTransactions = this.scheduler.getAllPendingTransactions();
210
+ if (allTransactions.length === 0) {
211
+ return null;
212
+ }
213
+ return Math.min(...allTransactions.map((tx) => tx.nextAttemptAt));
214
+ }
215
+ clearRetryTimer() {
216
+ if (this.retryTimer) {
217
+ clearTimeout(this.retryTimer);
218
+ this.retryTimer = null;
219
+ }
220
+ }
221
+ getRunningCount() {
222
+ return this.scheduler.getRunningCount();
223
+ }
224
+ resetRetryDelays() {
225
+ const allTransactions = this.scheduler.getAllPendingTransactions();
226
+ const updatedTransactions = allTransactions.map((transaction) => ({
227
+ ...transaction,
228
+ nextAttemptAt: Date.now()
229
+ }));
230
+ this.scheduler.updateTransactions(updatedTransactions);
231
+ }
232
+ }
233
+ export {
234
+ TransactionExecutor
235
+ };
236
+ //# sourceMappingURL=TransactionExecutor.js.map
@@ -0,0 +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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"}
@@ -0,0 +1,16 @@
1
+ export { OfflineExecutor, startOfflineExecutor } from './OfflineExecutor.js';
2
+ export type { OfflineTransaction, OfflineConfig, StorageAdapter, RetryPolicy, LeaderElection, OnlineDetector, CreateOfflineTransactionOptions, CreateOfflineActionOptions, SerializedError, SerializedMutation, } from './types.js';
3
+ export { NonRetriableError } from './types.js';
4
+ export { IndexedDBAdapter } from './storage/IndexedDBAdapter.js';
5
+ export { LocalStorageAdapter } from './storage/LocalStorageAdapter.js';
6
+ export { DefaultRetryPolicy } from './retry/RetryPolicy.js';
7
+ export { BackoffCalculator } from './retry/BackoffCalculator.js';
8
+ export { WebLocksLeader } from './coordination/WebLocksLeader.js';
9
+ export { BroadcastChannelLeader } from './coordination/BroadcastChannelLeader.js';
10
+ export { DefaultOnlineDetector } from './connectivity/OnlineDetector.js';
11
+ export { OfflineTransaction as OfflineTransactionAPI } from './api/OfflineTransaction.js';
12
+ export { createOfflineAction } from './api/OfflineAction.js';
13
+ export { OutboxManager } from './outbox/OutboxManager.js';
14
+ export { TransactionSerializer } from './outbox/TransactionSerializer.js';
15
+ export { KeyScheduler } from './executor/KeyScheduler.js';
16
+ export { TransactionExecutor } from './executor/TransactionExecutor.js';
@@ -0,0 +1,34 @@
1
+ import { OfflineExecutor, startOfflineExecutor } from "./OfflineExecutor.js";
2
+ import { NonRetriableError } from "./types.js";
3
+ import { IndexedDBAdapter } from "./storage/IndexedDBAdapter.js";
4
+ import { LocalStorageAdapter } from "./storage/LocalStorageAdapter.js";
5
+ import { DefaultRetryPolicy } from "./retry/RetryPolicy.js";
6
+ import { BackoffCalculator } from "./retry/BackoffCalculator.js";
7
+ import { WebLocksLeader } from "./coordination/WebLocksLeader.js";
8
+ import { BroadcastChannelLeader } from "./coordination/BroadcastChannelLeader.js";
9
+ import { DefaultOnlineDetector } from "./connectivity/OnlineDetector.js";
10
+ import { OfflineTransaction } from "./api/OfflineTransaction.js";
11
+ import { createOfflineAction } from "./api/OfflineAction.js";
12
+ import { OutboxManager } from "./outbox/OutboxManager.js";
13
+ import { TransactionSerializer } from "./outbox/TransactionSerializer.js";
14
+ import { KeyScheduler } from "./executor/KeyScheduler.js";
15
+ import { TransactionExecutor } from "./executor/TransactionExecutor.js";
16
+ export {
17
+ BackoffCalculator,
18
+ BroadcastChannelLeader,
19
+ DefaultOnlineDetector,
20
+ DefaultRetryPolicy,
21
+ IndexedDBAdapter,
22
+ KeyScheduler,
23
+ LocalStorageAdapter,
24
+ NonRetriableError,
25
+ OfflineExecutor,
26
+ OfflineTransaction as OfflineTransactionAPI,
27
+ OutboxManager,
28
+ TransactionExecutor,
29
+ TransactionSerializer,
30
+ WebLocksLeader,
31
+ createOfflineAction,
32
+ startOfflineExecutor
33
+ };
34
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;"}
@@ -0,0 +1,18 @@
1
+ import { OfflineTransaction, StorageAdapter } from '../types.js';
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
+ }
@@ -0,0 +1,114 @@
1
+ import { withSpan } from "../telemetry/tracer.js";
2
+ import { TransactionSerializer } from "./TransactionSerializer.js";
3
+ class OutboxManager {
4
+ constructor(storage, collections) {
5
+ this.keyPrefix = `tx:`;
6
+ this.storage = storage;
7
+ this.serializer = new TransactionSerializer(collections);
8
+ }
9
+ getStorageKey(id) {
10
+ return `${this.keyPrefix}${id}`;
11
+ }
12
+ async add(transaction) {
13
+ return withSpan(
14
+ `outbox.add`,
15
+ {
16
+ "transaction.id": transaction.id,
17
+ "transaction.mutationFnName": transaction.mutationFnName,
18
+ "transaction.keyCount": transaction.keys.length
19
+ },
20
+ async () => {
21
+ const key = this.getStorageKey(transaction.id);
22
+ const serialized = this.serializer.serialize(transaction);
23
+ await this.storage.set(key, serialized);
24
+ }
25
+ );
26
+ }
27
+ async get(id) {
28
+ return withSpan(`outbox.get`, { "transaction.id": id }, async (span) => {
29
+ const key = this.getStorageKey(id);
30
+ const data = await this.storage.get(key);
31
+ if (!data) {
32
+ span.setAttribute(`result`, `not_found`);
33
+ return null;
34
+ }
35
+ try {
36
+ const transaction = this.serializer.deserialize(data);
37
+ span.setAttribute(`result`, `found`);
38
+ return transaction;
39
+ } catch (error) {
40
+ console.warn(`Failed to deserialize transaction ${id}:`, error);
41
+ span.setAttribute(`result`, `deserialize_error`);
42
+ return null;
43
+ }
44
+ });
45
+ }
46
+ async getAll() {
47
+ return withSpan(`outbox.getAll`, {}, async (span) => {
48
+ const keys = await this.storage.keys();
49
+ const transactionKeys = keys.filter(
50
+ (key) => key.startsWith(this.keyPrefix)
51
+ );
52
+ span.setAttribute(`transactionCount`, transactionKeys.length);
53
+ const transactions = [];
54
+ for (const key of transactionKeys) {
55
+ const data = await this.storage.get(key);
56
+ if (data) {
57
+ try {
58
+ const transaction = this.serializer.deserialize(data);
59
+ transactions.push(transaction);
60
+ } catch (error) {
61
+ console.warn(
62
+ `Failed to deserialize transaction from key ${key}:`,
63
+ error
64
+ );
65
+ }
66
+ }
67
+ }
68
+ return transactions.sort(
69
+ (a, b) => a.createdAt.getTime() - b.createdAt.getTime()
70
+ );
71
+ });
72
+ }
73
+ async getByKeys(keys) {
74
+ const allTransactions = await this.getAll();
75
+ const keySet = new Set(keys);
76
+ return allTransactions.filter(
77
+ (transaction) => transaction.keys.some((key) => keySet.has(key))
78
+ );
79
+ }
80
+ async update(id, updates) {
81
+ return withSpan(`outbox.update`, { "transaction.id": id }, async () => {
82
+ const existing = await this.get(id);
83
+ if (!existing) {
84
+ throw new Error(`Transaction ${id} not found`);
85
+ }
86
+ const updated = { ...existing, ...updates };
87
+ await this.add(updated);
88
+ });
89
+ }
90
+ async remove(id) {
91
+ return withSpan(`outbox.remove`, { "transaction.id": id }, async () => {
92
+ const key = this.getStorageKey(id);
93
+ await this.storage.delete(key);
94
+ });
95
+ }
96
+ async removeMany(ids) {
97
+ return withSpan(`outbox.removeMany`, { count: ids.length }, async () => {
98
+ await Promise.all(ids.map((id) => this.remove(id)));
99
+ });
100
+ }
101
+ async clear() {
102
+ const keys = await this.storage.keys();
103
+ const transactionKeys = keys.filter((key) => key.startsWith(this.keyPrefix));
104
+ await Promise.all(transactionKeys.map((key) => this.storage.delete(key)));
105
+ }
106
+ async count() {
107
+ const keys = await this.storage.keys();
108
+ return keys.filter((key) => key.startsWith(this.keyPrefix)).length;
109
+ }
110
+ }
111
+ export {
112
+ OutboxManager
113
+ };
114
+ //# sourceMappingURL=OutboxManager.js.map
@@ -0,0 +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>\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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"}
@@ -0,0 +1,15 @@
1
+ import { OfflineTransaction, SerializedError } from '../types.js';
2
+ import { Collection } from '@tanstack/db';
3
+ export declare class TransactionSerializer {
4
+ private collections;
5
+ private collectionIdToKey;
6
+ constructor(collections: Record<string, Collection>);
7
+ serialize(transaction: OfflineTransaction): string;
8
+ deserialize(data: string): OfflineTransaction;
9
+ private serializeMutation;
10
+ private deserializeMutation;
11
+ private serializeValue;
12
+ private deserializeValue;
13
+ serializeError(error: Error): SerializedError;
14
+ deserializeError(data: SerializedError): Error;
15
+ }
@@ -0,0 +1,135 @@
1
+ class TransactionSerializer {
2
+ constructor(collections) {
3
+ this.collections = collections;
4
+ this.collectionIdToKey = /* @__PURE__ */ new Map();
5
+ for (const [key, collection] of Object.entries(collections)) {
6
+ this.collectionIdToKey.set(collection.id, key);
7
+ }
8
+ }
9
+ serialize(transaction) {
10
+ const serialized = {
11
+ ...transaction,
12
+ createdAt: transaction.createdAt,
13
+ mutations: transaction.mutations.map(
14
+ (mutation) => this.serializeMutation(mutation)
15
+ )
16
+ };
17
+ return JSON.stringify(serialized, (key, value) => {
18
+ if (value instanceof Date) {
19
+ return value.toISOString();
20
+ }
21
+ return value;
22
+ });
23
+ }
24
+ deserialize(data) {
25
+ const parsed = JSON.parse(
26
+ data,
27
+ (key, value) => {
28
+ if (typeof value === `string` && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(value)) {
29
+ return new Date(value);
30
+ }
31
+ return value;
32
+ }
33
+ );
34
+ return {
35
+ ...parsed,
36
+ mutations: parsed.mutations.map(
37
+ (mutationData) => this.deserializeMutation(mutationData)
38
+ )
39
+ };
40
+ }
41
+ serializeMutation(mutation) {
42
+ const registryKey = this.collectionIdToKey.get(mutation.collection.id);
43
+ if (!registryKey) {
44
+ throw new Error(
45
+ `Collection with id ${mutation.collection.id} not found in registry`
46
+ );
47
+ }
48
+ return {
49
+ globalKey: mutation.globalKey,
50
+ type: mutation.type,
51
+ modified: this.serializeValue(mutation.modified),
52
+ original: this.serializeValue(mutation.original),
53
+ collectionId: registryKey
54
+ // Store registry key instead of collection.id
55
+ };
56
+ }
57
+ deserializeMutation(data) {
58
+ const collection = this.collections[data.collectionId];
59
+ if (!collection) {
60
+ throw new Error(`Collection with id ${data.collectionId} not found`);
61
+ }
62
+ return {
63
+ globalKey: data.globalKey,
64
+ type: data.type,
65
+ modified: this.deserializeValue(data.modified),
66
+ original: this.deserializeValue(data.original),
67
+ collection,
68
+ // These fields would need to be reconstructed by the executor
69
+ mutationId: ``,
70
+ // Will be regenerated
71
+ key: null,
72
+ // Will be extracted from the data
73
+ changes: {},
74
+ // Will be recalculated
75
+ metadata: void 0,
76
+ syncMetadata: {},
77
+ optimistic: true,
78
+ createdAt: /* @__PURE__ */ new Date(),
79
+ updatedAt: /* @__PURE__ */ new Date()
80
+ };
81
+ }
82
+ serializeValue(value) {
83
+ if (value === null || value === void 0) {
84
+ return value;
85
+ }
86
+ if (value instanceof Date) {
87
+ return { __type: `Date`, value: value.toISOString() };
88
+ }
89
+ if (typeof value === `object`) {
90
+ const result = Array.isArray(value) ? [] : {};
91
+ for (const key in value) {
92
+ if (value.hasOwnProperty(key)) {
93
+ result[key] = this.serializeValue(value[key]);
94
+ }
95
+ }
96
+ return result;
97
+ }
98
+ return value;
99
+ }
100
+ deserializeValue(value) {
101
+ if (value === null || value === void 0) {
102
+ return value;
103
+ }
104
+ if (typeof value === `object` && value.__type === `Date`) {
105
+ return new Date(value.value);
106
+ }
107
+ if (typeof value === `object`) {
108
+ const result = Array.isArray(value) ? [] : {};
109
+ for (const key in value) {
110
+ if (value.hasOwnProperty(key)) {
111
+ result[key] = this.deserializeValue(value[key]);
112
+ }
113
+ }
114
+ return result;
115
+ }
116
+ return value;
117
+ }
118
+ serializeError(error) {
119
+ return {
120
+ name: error.name,
121
+ message: error.message,
122
+ stack: error.stack
123
+ };
124
+ }
125
+ deserializeError(data) {
126
+ const error = new Error(data.message);
127
+ error.name = data.name;
128
+ error.stack = data.stack;
129
+ return error;
130
+ }
131
+ }
132
+ export {
133
+ TransactionSerializer
134
+ };
135
+ //# sourceMappingURL=TransactionSerializer.js.map
@@ -0,0 +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>\n private collectionIdToKey: Map<string, string>\n\n constructor(collections: Record<string, Collection>) {\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,EAIjC,YAAY,aAAyC;AACnD,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;"}
@@ -0,0 +1,5 @@
1
+ export declare class BackoffCalculator {
2
+ private jitter;
3
+ constructor(jitter?: boolean);
4
+ calculate(retryCount: number): number;
5
+ }
@@ -0,0 +1,14 @@
1
+ class BackoffCalculator {
2
+ constructor(jitter = true) {
3
+ this.jitter = jitter;
4
+ }
5
+ calculate(retryCount) {
6
+ const baseDelay = Math.min(1e3 * Math.pow(2, retryCount), 6e4);
7
+ const jitterMultiplier = this.jitter ? Math.random() * 0.3 : 0;
8
+ return Math.floor(baseDelay * (1 + jitterMultiplier));
9
+ }
10
+ }
11
+ export {
12
+ BackoffCalculator
13
+ };
14
+ //# sourceMappingURL=BackoffCalculator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BackoffCalculator.js","sources":["../../../src/retry/BackoffCalculator.ts"],"sourcesContent":["export class BackoffCalculator {\n private jitter: boolean\n\n constructor(jitter = true) {\n this.jitter = jitter\n }\n\n calculate(retryCount: number): number {\n const baseDelay = Math.min(1000 * Math.pow(2, retryCount), 60000)\n const jitterMultiplier = this.jitter ? Math.random() * 0.3 : 0\n return Math.floor(baseDelay * (1 + jitterMultiplier))\n }\n}\n"],"names":[],"mappings":"AAAO,MAAM,kBAAkB;AAAA,EAG7B,YAAY,SAAS,MAAM;AACzB,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,UAAU,YAA4B;AACpC,UAAM,YAAY,KAAK,IAAI,MAAO,KAAK,IAAI,GAAG,UAAU,GAAG,GAAK;AAChE,UAAM,mBAAmB,KAAK,SAAS,KAAK,OAAA,IAAW,MAAM;AAC7D,WAAO,KAAK,MAAM,aAAa,IAAI,iBAAiB;AAAA,EACtD;AACF;"}