@tanstack/offline-transactions 1.0.56 → 1.0.58
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 +137 -23
- package/dist/cjs/OfflineExecutor.cjs +2 -0
- package/dist/cjs/OfflineExecutor.cjs.map +1 -1
- package/dist/cjs/connectivity/OnlineDetector.cjs +1 -1
- package/dist/cjs/connectivity/OnlineDetector.cjs.map +1 -1
- package/dist/cjs/connectivity/ReactNativeOnlineDetector.cjs +1 -7
- package/dist/cjs/connectivity/ReactNativeOnlineDetector.cjs.map +1 -1
- package/dist/cjs/executor/KeyScheduler.cjs +24 -25
- package/dist/cjs/executor/KeyScheduler.cjs.map +1 -1
- package/dist/cjs/executor/KeyScheduler.d.cts +4 -2
- package/dist/cjs/executor/TransactionExecutor.cjs +60 -30
- package/dist/cjs/executor/TransactionExecutor.cjs.map +1 -1
- package/dist/cjs/outbox/OutboxManager.cjs +66 -24
- package/dist/cjs/outbox/OutboxManager.cjs.map +1 -1
- package/dist/cjs/outbox/OutboxManager.d.cts +6 -0
- package/dist/cjs/outbox/TransactionSerializer.cjs +132 -22
- package/dist/cjs/outbox/TransactionSerializer.cjs.map +1 -1
- package/dist/cjs/outbox/TransactionSerializer.d.cts +2 -0
- package/dist/cjs/types.cjs.map +1 -1
- package/dist/cjs/types.d.cts +1 -1
- package/dist/esm/OfflineExecutor.js +2 -0
- package/dist/esm/OfflineExecutor.js.map +1 -1
- package/dist/esm/connectivity/OnlineDetector.js +1 -1
- package/dist/esm/connectivity/OnlineDetector.js.map +1 -1
- package/dist/esm/connectivity/ReactNativeOnlineDetector.js +1 -7
- package/dist/esm/connectivity/ReactNativeOnlineDetector.js.map +1 -1
- package/dist/esm/executor/KeyScheduler.d.ts +4 -2
- package/dist/esm/executor/KeyScheduler.js +24 -25
- package/dist/esm/executor/KeyScheduler.js.map +1 -1
- package/dist/esm/executor/TransactionExecutor.js +60 -30
- package/dist/esm/executor/TransactionExecutor.js.map +1 -1
- package/dist/esm/outbox/OutboxManager.d.ts +6 -0
- package/dist/esm/outbox/OutboxManager.js +67 -25
- package/dist/esm/outbox/OutboxManager.js.map +1 -1
- package/dist/esm/outbox/TransactionSerializer.d.ts +2 -0
- package/dist/esm/outbox/TransactionSerializer.js +132 -22
- package/dist/esm/outbox/TransactionSerializer.js.map +1 -1
- package/dist/esm/types.d.ts +1 -1
- package/dist/esm/types.js.map +1 -1
- package/package.json +2 -2
- package/src/OfflineExecutor.ts +2 -1
- package/src/connectivity/OnlineDetector.ts +6 -1
- package/src/connectivity/ReactNativeOnlineDetector.ts +1 -11
- package/src/executor/KeyScheduler.ts +29 -26
- package/src/executor/TransactionExecutor.ts +71 -43
- package/src/outbox/OutboxManager.ts +82 -28
- package/src/outbox/TransactionSerializer.ts +192 -25
- package/src/types.ts +1 -1
package/README.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# @tanstack/offline-transactions
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This package gives the leader a durable outbox for pending TanStack DB mutations. It retries stored mutations when the server is available. Read the [Offline Transactions guide](../../docs/guides/offline-transactions.md) for setup and lifecycle behavior.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
-
- **Outbox
|
|
7
|
+
- **Outbox**: The leader stores mutations before sending them when `isOfflineEnabled` is true
|
|
8
8
|
- **Automatic Retry**: Configurable retry behavior with exponential backoff + jitter by default
|
|
9
|
-
- **Multi-tab Coordination**: Leader election
|
|
9
|
+
- **Multi-tab Coordination**: Leader election chooses one tab to process the outbox
|
|
10
10
|
- **FIFO Sequential Processing**: Transactions execute one at a time in creation order
|
|
11
11
|
- **Flexible Storage**: IndexedDB with localStorage fallback
|
|
12
12
|
- **Type Safe**: Full TypeScript support with TanStack DB integration
|
|
@@ -25,18 +25,18 @@ npm install @tanstack/offline-transactions
|
|
|
25
25
|
npm install @tanstack/offline-transactions @react-native-community/netinfo
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
The React Native
|
|
28
|
+
The React Native entry point uses `@react-native-community/netinfo` for connectivity detection. Supply a `StorageAdapter` for the outbox. The package does not include an AsyncStorage adapter.
|
|
29
29
|
|
|
30
30
|
## Platform Support
|
|
31
31
|
|
|
32
32
|
This package provides platform-specific implementations for web and React Native environments:
|
|
33
33
|
|
|
34
|
-
- **Web**: Uses browser APIs (`window.online
|
|
34
|
+
- **Web**: Uses browser APIs (`window.online` and `document.visibilitychange` events). Visible tabs allow sync attempts even when `navigator.onLine` is false; hidden tabs follow that hint. Request failures still use the configured retry policy. Visibility is local to each tab and does not transfer leadership from a hidden tab.
|
|
35
35
|
- **React Native**: Uses React Native primitives (`@react-native-community/netinfo` for network status, `AppState` for foreground/background detection)
|
|
36
36
|
|
|
37
37
|
## Quick Start
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
Use the entry point for your platform. React Native also needs a storage adapter, as shown in the [guide](../../docs/guides/offline-transactions.md#react-native-and-expo).
|
|
40
40
|
|
|
41
41
|
**Web:**
|
|
42
42
|
|
|
@@ -50,7 +50,7 @@ import { startOfflineExecutor } from '@tanstack/offline-transactions'
|
|
|
50
50
|
import { startOfflineExecutor } from '@tanstack/offline-transactions/react-native'
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
-
**
|
|
53
|
+
**Web usage:**
|
|
54
54
|
|
|
55
55
|
```typescript
|
|
56
56
|
// Setup offline executor
|
|
@@ -68,13 +68,15 @@ const offline = startOfflineExecutor({
|
|
|
68
68
|
},
|
|
69
69
|
})
|
|
70
70
|
|
|
71
|
+
await offline.waitForInit()
|
|
72
|
+
|
|
71
73
|
// Create offline transactions
|
|
72
74
|
const offlineTx = offline.createOfflineTransaction({
|
|
73
75
|
mutationFnName: 'syncTodos',
|
|
74
76
|
autoCommit: false,
|
|
75
77
|
})
|
|
76
78
|
|
|
77
|
-
offlineTx.mutate(() => {
|
|
79
|
+
const transaction = offlineTx.mutate(() => {
|
|
78
80
|
todoCollection.insert({
|
|
79
81
|
id: crypto.randomUUID(),
|
|
80
82
|
text: 'Buy milk',
|
|
@@ -82,20 +84,25 @@ offlineTx.mutate(() => {
|
|
|
82
84
|
})
|
|
83
85
|
})
|
|
84
86
|
|
|
85
|
-
//
|
|
86
|
-
|
|
87
|
+
// Commit can remain pending while offline. Observe final failure.
|
|
88
|
+
void offlineTx.commit().catch((error) => console.error(error))
|
|
89
|
+
void transaction.isPersisted.promise.catch((error) => console.error(error))
|
|
87
90
|
```
|
|
88
91
|
|
|
92
|
+
On React Native, pass a custom `storage` adapter to `startOfflineExecutor`.
|
|
93
|
+
|
|
89
94
|
## Core Concepts
|
|
90
95
|
|
|
91
|
-
### Outbox
|
|
96
|
+
### Durable Outbox
|
|
92
97
|
|
|
93
|
-
|
|
98
|
+
When `isOfflineEnabled` is true, the executor records a mutation before it sends the mutation to the server. The optimistic change appears before the outbox write settles:
|
|
94
99
|
|
|
95
|
-
1.
|
|
96
|
-
2.
|
|
97
|
-
3. When online,
|
|
98
|
-
4.
|
|
100
|
+
1. The Collection applies an optimistic mutation.
|
|
101
|
+
2. The leader writes the transaction to the outbox.
|
|
102
|
+
3. When online, the executor calls the named mutation function.
|
|
103
|
+
4. After a successful call, the executor attempts to remove the outbox entry.
|
|
104
|
+
|
|
105
|
+
An optimistic change does not prove that the outbox write succeeded. Handle transaction failures, and use an idempotency key on the server because an attempt can run more than once.
|
|
99
106
|
|
|
100
107
|
### Multi-tab Coordination
|
|
101
108
|
|
|
@@ -107,11 +114,11 @@ Only one tab acts as the "leader" to safely manage the outbox:
|
|
|
107
114
|
|
|
108
115
|
### FIFO Sequential Processing
|
|
109
116
|
|
|
110
|
-
|
|
117
|
+
The executor processes one transaction at a time, in creation order:
|
|
111
118
|
|
|
112
119
|
- **Sequential execution**: All transactions execute in FIFO order
|
|
113
120
|
- **Dependency safety**: Avoids conflicts between transactions that may reference each other
|
|
114
|
-
- **Predictable behavior**: Transactions complete in
|
|
121
|
+
- **Predictable behavior**: Transactions complete in creation order
|
|
115
122
|
|
|
116
123
|
## API Reference
|
|
117
124
|
|
|
@@ -200,6 +207,108 @@ tx.mutate(() => {
|
|
|
200
207
|
await tx.commit()
|
|
201
208
|
```
|
|
202
209
|
|
|
210
|
+
## Tracking Submission Status
|
|
211
|
+
|
|
212
|
+
The `Transaction` returned by `mutate()` exposes local transaction state. Its
|
|
213
|
+
`state` starts as `pending`, becomes `persisting` during commit, and settles as
|
|
214
|
+
`completed` or `failed`. A rollback can move it to `failed` before or during
|
|
215
|
+
persistence. `isPersisted.promise` settles at the same success or failure
|
|
216
|
+
boundary.
|
|
217
|
+
|
|
218
|
+
On the offline-execution path, successful settlement means the configured
|
|
219
|
+
`mutationFn` returned. The executor also attempts to remove the durable outbox
|
|
220
|
+
entry. It means the server confirmed or exposed the write only when that
|
|
221
|
+
`mutationFn` explicitly waits for the provider's acknowledgement, read-back, or
|
|
222
|
+
sync observation before returning.
|
|
223
|
+
|
|
224
|
+
```typescript
|
|
225
|
+
const offlineTx = offline.createOfflineTransaction({
|
|
226
|
+
mutationFnName: 'syncTodos',
|
|
227
|
+
autoCommit: false,
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
const tx = offlineTx.mutate(() => {
|
|
231
|
+
todoCollection.insert({ id: '1', text: 'Buy milk', completed: false })
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
console.log(tx.state) // 'pending'
|
|
235
|
+
|
|
236
|
+
try {
|
|
237
|
+
await Promise.all([offlineTx.commit(), tx.isPersisted.promise])
|
|
238
|
+
console.log(tx.state) // 'completed'
|
|
239
|
+
} catch (error) {
|
|
240
|
+
showSubmissionError(error)
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Tracking Every Pending Transaction for an Item
|
|
245
|
+
|
|
246
|
+
An item can have more than one transaction in flight. Track transaction
|
|
247
|
+
identities rather than storing one replaceable boolean or deleting an
|
|
248
|
+
item-keyed entry unconditionally:
|
|
249
|
+
|
|
250
|
+
```typescript
|
|
251
|
+
import type { Transaction } from '@tanstack/db'
|
|
252
|
+
|
|
253
|
+
const pendingByItem = new Map<string, Set<Transaction>>()
|
|
254
|
+
|
|
255
|
+
function trackPending(itemId: string, tx: Transaction) {
|
|
256
|
+
const pending = pendingByItem.get(itemId) ?? new Set<Transaction>()
|
|
257
|
+
pending.add(tx)
|
|
258
|
+
pendingByItem.set(itemId, pending)
|
|
259
|
+
|
|
260
|
+
const removeThisTransaction = () => {
|
|
261
|
+
pending.delete(tx)
|
|
262
|
+
if (pending.size === 0 && pendingByItem.get(itemId) === pending) {
|
|
263
|
+
pendingByItem.delete(itemId)
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Handle fulfillment and rejection so cleanup does not create another
|
|
268
|
+
// rejected Promise chain.
|
|
269
|
+
void tx.isPersisted.promise.then(removeThisTransaction, removeThisTransaction)
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function isPending(itemId: string): boolean {
|
|
273
|
+
return (pendingByItem.get(itemId)?.size ?? 0) > 0
|
|
274
|
+
}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
Using only `pendingItems.delete(itemId)` in an older transaction's completion
|
|
278
|
+
handler is unsafe because it can remove a newer transaction's status. A map that
|
|
279
|
+
represents only "the latest submission" must compare the current entry with the finishing transaction before cleanup:
|
|
280
|
+
|
|
281
|
+
```typescript
|
|
282
|
+
if (latestByItem.get(itemId) === tx) {
|
|
283
|
+
latestByItem.delete(itemId)
|
|
284
|
+
}
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
That latest-only map can still be empty while an older transaction is pending
|
|
288
|
+
if the newer transaction settles first. Use a set as above when the UI must
|
|
289
|
+
answer whether _any_ submission remains pending.
|
|
290
|
+
|
|
291
|
+
### Inspecting Offline Work
|
|
292
|
+
|
|
293
|
+
The executor exposes point-in-time scheduler counts and the durable outbox:
|
|
294
|
+
|
|
295
|
+
```typescript
|
|
296
|
+
// Scheduled entries. The pending count can include the currently running entry.
|
|
297
|
+
const pendingCount = offline.getPendingCount()
|
|
298
|
+
const runningCount = offline.getRunningCount()
|
|
299
|
+
|
|
300
|
+
// Durable entries, including retry metadata.
|
|
301
|
+
const outbox = await offline.peekOutbox()
|
|
302
|
+
for (const entry of outbox) {
|
|
303
|
+
console.log(entry.id, entry.retryCount, entry.lastError)
|
|
304
|
+
}
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
These values describe local executor work, not backend confirmation. A normal
|
|
308
|
+
retriable error leaves the transaction queued. A `NonRetriableError` marks a
|
|
309
|
+
permanent failure, removes the outbox entry, and rolls back its optimistic
|
|
310
|
+
state.
|
|
311
|
+
|
|
203
312
|
## Migration from TanStack DB
|
|
204
313
|
|
|
205
314
|
This package uses explicit offline transactions to provide offline capabilities:
|
|
@@ -218,9 +327,14 @@ const offline = startOfflineExecutor({
|
|
|
218
327
|
},
|
|
219
328
|
})
|
|
220
329
|
|
|
221
|
-
|
|
330
|
+
await offline.waitForInit()
|
|
331
|
+
|
|
332
|
+
const tx = offline.createOfflineTransaction({
|
|
333
|
+
mutationFnName: 'syncTodos',
|
|
334
|
+
autoCommit: false,
|
|
335
|
+
})
|
|
222
336
|
tx.mutate(() => todoCollection.insert({ id: '1', text: 'Buy milk' }))
|
|
223
|
-
await tx.commit() //
|
|
337
|
+
await tx.commit() // Remains pending until the mutation function succeeds.
|
|
224
338
|
```
|
|
225
339
|
|
|
226
340
|
## Platform Support
|
|
@@ -234,10 +348,10 @@ await tx.commit() // Works offline!
|
|
|
234
348
|
|
|
235
349
|
### React Native
|
|
236
350
|
|
|
237
|
-
- **React Native**: 0.
|
|
238
|
-
- **Expo**:
|
|
351
|
+
- **React Native**: 0.70+ (package peer dependency)
|
|
352
|
+
- **Expo**: Use the React Native entry point
|
|
239
353
|
- **Required peer dependency**: `@react-native-community/netinfo` for network connectivity detection
|
|
240
|
-
- **Storage**:
|
|
354
|
+
- **Storage**: Supply a custom `StorageAdapter`, such as the [example AsyncStorage adapter](../../examples/react-native/offline-transactions/src/db/AsyncStorageAdapter.ts)
|
|
241
355
|
|
|
242
356
|
## License
|
|
243
357
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OfflineExecutor.cjs","sources":["../../src/OfflineExecutor.ts"],"sourcesContent":["// Storage adapters\nimport {\n createOptimisticAction,\n createTransaction,\n safeRandomUUID,\n} from '@tanstack/db'\nimport { IndexedDBAdapter } from './storage/IndexedDBAdapter'\nimport { LocalStorageAdapter } from './storage/LocalStorageAdapter'\n\n// Core components\nimport { OutboxManager } from './outbox/OutboxManager'\nimport { KeyScheduler } from './executor/KeyScheduler'\nimport { TransactionExecutor } from './executor/TransactionExecutor'\n\n// Coordination\nimport { WebLocksLeader } from './coordination/WebLocksLeader'\nimport { BroadcastChannelLeader } from './coordination/BroadcastChannelLeader'\n\n// Connectivity\nimport { WebOnlineDetector } from './connectivity/OnlineDetector'\n\n// API\nimport { OfflineTransaction as OfflineTransactionAPI } from './api/OfflineTransaction'\nimport { createOfflineAction } from './api/OfflineAction'\n\n// TanStack DB primitives\n\n// Replay\nimport { withNestedSpan, withSpan } from './telemetry/tracer'\nimport type {\n CreateOfflineActionOptions,\n CreateOfflineTransactionOptions,\n LeaderElection,\n OfflineConfig,\n OfflineMode,\n OfflineTransaction,\n OnlineDetector,\n StorageAdapter,\n StorageDiagnostic,\n} from './types'\nimport type { Transaction } from '@tanstack/db'\n\nexport class OfflineExecutor {\n private config: OfflineConfig\n\n // @ts-expect-error - Set during async initialization in initialize()\n private storage: StorageAdapter | null\n private outbox: OutboxManager | null\n private scheduler: KeyScheduler\n private executor: TransactionExecutor | null\n private leaderElection: LeaderElection | null\n private onlineDetector: OnlineDetector\n private isLeaderState = false\n private disposed = false\n private unsubscribeOnline: (() => void) | null = null\n private unsubscribeLeadership: (() => void) | null = null\n\n // Public diagnostic properties\n public readonly mode: OfflineMode\n public readonly storageDiagnostic: StorageDiagnostic\n\n // Track initialization completion\n private initPromise: Promise<void>\n private initResolve!: () => void\n private initReject!: (error: Error) => void\n\n // Coordination mechanism for blocking transactions\n private pendingTransactionPromises: Map<\n string,\n {\n promise: Promise<any>\n resolve: (result: any) => void\n reject: (error: Error) => void\n }\n > = new Map()\n\n // Track restoration transactions for cleanup when offline transactions complete\n private restorationTransactions: Map<string, Transaction> = new Map()\n\n constructor(config: OfflineConfig) {\n this.config = config\n this.scheduler = new KeyScheduler()\n this.onlineDetector = config.onlineDetector ?? new WebOnlineDetector()\n\n // Initialize as pending - will be set by async initialization\n this.storage = null\n this.outbox = null\n this.executor = null\n this.leaderElection = null\n\n // Temporary diagnostic - will be updated by async initialization\n this.mode = `offline`\n this.storageDiagnostic = {\n code: `STORAGE_AVAILABLE`,\n mode: `offline`,\n message: `Initializing storage...`,\n }\n\n // Create initialization promise\n this.initPromise = new Promise((resolve, reject) => {\n this.initResolve = resolve\n this.initReject = reject\n })\n\n this.initialize()\n }\n\n /**\n * Probe storage availability and create appropriate adapter.\n * Returns null if no storage is available (online-only mode).\n */\n private async createStorage(): Promise<{\n storage: StorageAdapter | null\n diagnostic: StorageDiagnostic\n }> {\n // If user provided custom storage, use it without probing\n if (this.config.storage) {\n return {\n storage: this.config.storage,\n diagnostic: {\n code: `STORAGE_AVAILABLE`,\n mode: `offline`,\n message: `Using custom storage adapter`,\n },\n }\n }\n\n // Probe IndexedDB first\n const idbProbe = await IndexedDBAdapter.probe()\n if (idbProbe.available) {\n return {\n storage: new IndexedDBAdapter(),\n diagnostic: {\n code: `STORAGE_AVAILABLE`,\n mode: `offline`,\n message: `Using IndexedDB for offline storage`,\n },\n }\n }\n\n // IndexedDB failed, try localStorage\n const lsProbe = LocalStorageAdapter.probe()\n if (lsProbe.available) {\n return {\n storage: new LocalStorageAdapter(),\n diagnostic: {\n code: `INDEXEDDB_UNAVAILABLE`,\n mode: `offline`,\n message: `IndexedDB unavailable, using localStorage fallback`,\n error: idbProbe.error,\n },\n }\n }\n\n // Both failed - determine the diagnostic code\n const isSecurityError =\n idbProbe.error?.name === `SecurityError` ||\n lsProbe.error?.name === `SecurityError`\n const isQuotaError =\n idbProbe.error?.name === `QuotaExceededError` ||\n lsProbe.error?.name === `QuotaExceededError`\n\n let code: StorageDiagnostic[`code`]\n let message: string\n\n if (isSecurityError) {\n code = `STORAGE_BLOCKED`\n message = `Storage blocked (private mode or security restrictions). Running in online-only mode.`\n } else if (isQuotaError) {\n code = `QUOTA_EXCEEDED`\n message = `Storage quota exceeded. Running in online-only mode.`\n } else {\n code = `UNKNOWN_ERROR`\n message = `Storage unavailable due to unknown error. Running in online-only mode.`\n }\n\n return {\n storage: null,\n diagnostic: {\n code,\n mode: `online-only`,\n message,\n error: idbProbe.error || lsProbe.error,\n },\n }\n }\n\n private createLeaderElection(): LeaderElection {\n if (this.config.leaderElection) {\n return this.config.leaderElection\n }\n\n if (WebLocksLeader.isSupported()) {\n return new WebLocksLeader()\n } else if (BroadcastChannelLeader.isSupported()) {\n return new BroadcastChannelLeader()\n } else {\n // Fallback: always be leader in environments without multi-tab support\n return {\n requestLeadership: () => Promise.resolve(true),\n releaseLeadership: () => {},\n isLeader: () => true,\n onLeadershipChange: () => () => {},\n }\n }\n }\n\n private setupEventListeners(): void {\n // Only set up leader election listeners if we have storage\n if (this.leaderElection) {\n this.unsubscribeLeadership = this.leaderElection.onLeadershipChange(\n (isLeader) => {\n // A custom elector may repeat the initial result while replay is active.\n if (this.disposed || isLeader === this.isLeaderState) return\n this.isLeaderState = isLeader\n if (!isLeader) this.executor?.pause()\n\n if (this.config.onLeadershipChange) {\n this.config.onLeadershipChange(isLeader)\n }\n\n if (isLeader) {\n this.loadAndReplayTransactions().catch((error) => {\n console.warn(`Failed to load and replay transactions:`, error)\n })\n }\n },\n )\n }\n\n this.unsubscribeOnline = this.onlineDetector.subscribe(() => {\n if (this.isOfflineEnabled && this.executor) {\n this.executor.resetRetryDelays()\n\n if (this.scheduler.getPendingCount() > 0) {\n const barrierPromise = this.executor.executeAll()\n\n for (const collection of Object.values(this.config.collections)) {\n collection.deferDataRefresh = barrierPromise\n }\n\n barrierPromise\n .catch((error) => {\n console.warn(\n `Failed to execute transactions on connectivity change:`,\n error,\n )\n })\n .finally(() => {\n for (const collection of Object.values(this.config.collections)) {\n if (collection.deferDataRefresh === barrierPromise) {\n collection.deferDataRefresh = null\n }\n }\n })\n } else {\n this.executor.executeAll().catch((error) => {\n console.warn(\n `Failed to execute transactions on connectivity change:`,\n error,\n )\n })\n }\n }\n })\n }\n\n private async initialize(): Promise<void> {\n return withSpan(`executor.initialize`, {}, async (span) => {\n try {\n // Probe storage and create adapter\n const { storage, diagnostic } = await this.createStorage()\n if (this.disposed) {\n this.initResolve()\n return\n }\n\n // Cast to writable to set readonly properties\n ;(this as any).storage = storage\n ;(this as any).storageDiagnostic = diagnostic\n ;(this as any).mode = diagnostic.mode\n\n span.setAttribute(`storage.mode`, diagnostic.mode)\n span.setAttribute(`storage.code`, diagnostic.code)\n\n if (!storage) {\n // Online-only mode - notify callback and skip offline setup\n if (this.config.onStorageFailure) {\n this.config.onStorageFailure(diagnostic)\n }\n span.setAttribute(`result`, `online-only`)\n this.initResolve()\n return\n }\n\n // Storage available - set up offline components\n this.outbox = new OutboxManager(storage, this.config.collections)\n this.executor = new TransactionExecutor(\n this.scheduler,\n this.outbox,\n this.config,\n this,\n )\n this.leaderElection = this.createLeaderElection()\n\n // Request leadership first\n const isLeader = await this.leaderElection.requestLeadership()\n // Disposal may have run while leadership was pending.\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (this.disposed) {\n this.leaderElection.releaseLeadership()\n this.initResolve()\n return\n }\n this.isLeaderState = isLeader\n span.setAttribute(`isLeader`, isLeader)\n\n // Set up event listeners after leadership is established\n // This prevents the callback from being called multiple times\n this.setupEventListeners()\n\n // Notify initial leadership state\n if (this.config.onLeadershipChange) {\n this.config.onLeadershipChange(isLeader)\n }\n\n if (isLeader) {\n await this.loadAndReplayTransactions()\n }\n span.setAttribute(`result`, `offline-enabled`)\n this.initResolve()\n } catch (error) {\n console.warn(`Failed to initialize offline executor:`, error)\n span.setAttribute(`result`, `failed`)\n this.initReject(\n error instanceof Error ? error : new Error(String(error)),\n )\n }\n })\n }\n\n private async loadAndReplayTransactions(): Promise<void> {\n if (!this.executor) {\n return\n }\n\n // Startup must observe a failed read instead of reporting an empty outbox.\n await this.executor.loadPendingTransactions()\n\n // Replay completion is separate from initialization and durable admission.\n this.executor.executeAll().catch((error) => {\n console.warn(`Failed to execute transactions:`, error)\n })\n }\n\n get isOfflineEnabled(): boolean {\n return !this.disposed && this.mode === `offline` && this.isLeaderState\n }\n\n /**\n * Wait for the executor to fully initialize.\n * This ensures that pending transactions are loaded and optimistic state is restored.\n */\n async waitForInit(): Promise<void> {\n return this.initPromise\n }\n\n createOfflineTransaction(\n options: CreateOfflineTransactionOptions,\n ): Transaction | OfflineTransactionAPI {\n const mutationFn = this.config.mutationFns[options.mutationFnName]\n\n if (!mutationFn) {\n throw new Error(`Unknown mutation function: ${options.mutationFnName}`)\n }\n\n // Check leadership immediately and use the appropriate primitive\n if (!this.isOfflineEnabled) {\n // Non-leader: use createTransaction directly with the resolved mutation function\n // We need to wrap it to add the idempotency key\n return createTransaction({\n autoCommit: options.autoCommit ?? true,\n mutationFn: (params) =>\n mutationFn({\n ...params,\n idempotencyKey: options.idempotencyKey || safeRandomUUID(),\n }),\n metadata: options.metadata,\n })\n }\n\n // Leader: use OfflineTransaction wrapper for offline persistence\n return new OfflineTransactionAPI(\n options,\n mutationFn,\n this.persistTransaction.bind(this),\n this,\n )\n }\n\n createOfflineAction<T>(options: CreateOfflineActionOptions<T>) {\n const mutationFn = this.config.mutationFns[options.mutationFnName]\n\n if (!mutationFn) {\n throw new Error(`Unknown mutation function: ${options.mutationFnName}`)\n }\n\n // Return a wrapper that checks leadership status at call time\n return (variables: T) => {\n // Check leadership when action is called, not when it's created\n if (!this.isOfflineEnabled) {\n // Non-leader: use createOptimisticAction directly\n const action = createOptimisticAction({\n mutationFn: (vars, params) =>\n mutationFn({\n ...vars,\n ...params,\n idempotencyKey: safeRandomUUID(),\n }),\n onMutate: options.onMutate,\n })\n return action(variables)\n }\n\n // Leader: use the offline action wrapper\n const action = createOfflineAction(\n options,\n mutationFn,\n this.persistTransaction.bind(this),\n this,\n )\n return action(variables)\n }\n }\n\n private async persistTransaction(\n transaction: OfflineTransaction,\n ): Promise<void> {\n // Wait for initialization to complete\n await this.initPromise\n\n return withNestedSpan(\n `executor.persistTransaction`,\n {\n 'transaction.id': transaction.id,\n 'transaction.mutationFnName': transaction.mutationFnName,\n },\n async (span) => {\n if (!this.isOfflineEnabled || !this.outbox || !this.executor) {\n span.setAttribute(`result`, `skipped_not_leader`)\n this.resolveTransaction(transaction.id, undefined)\n return\n }\n\n try {\n await this.outbox.add(transaction)\n // A shared queue error belongs to its execution, not every caller\n // that durably admitted a transaction. Per-ID signals settle callers.\n this.executor.execute(transaction).catch((error) => {\n console.warn(`Failed to execute transactions:`, error)\n })\n span.setAttribute(`result`, `persisted`)\n } catch (error) {\n console.error(\n `Failed to persist offline transaction ${transaction.id}:`,\n error,\n )\n span.setAttribute(`result`, `failed`)\n throw error\n }\n },\n )\n }\n\n // Method for OfflineTransaction to wait for completion\n async waitForTransactionCompletion(transactionId: string): Promise<any> {\n const existing = this.pendingTransactionPromises.get(transactionId)\n if (existing) {\n return existing.promise\n }\n\n const deferred: {\n promise: Promise<any>\n resolve: (result: any) => void\n reject: (error: Error) => void\n } = {} as any\n\n deferred.promise = new Promise((resolve, reject) => {\n deferred.resolve = resolve\n deferred.reject = reject\n })\n\n this.pendingTransactionPromises.set(transactionId, deferred)\n return deferred.promise\n }\n\n // Method for TransactionExecutor to signal completion\n resolveTransaction(transactionId: string, result: any): void {\n const deferred = this.pendingTransactionPromises.get(transactionId)\n if (deferred) {\n deferred.resolve(result)\n this.pendingTransactionPromises.delete(transactionId)\n }\n\n // Clean up the restoration transaction - the sync will provide authoritative data\n this.cleanupRestorationTransaction(transactionId)\n }\n\n // Method for TransactionExecutor to signal failure\n rejectTransaction(transactionId: string, error: Error): void {\n const deferred = this.pendingTransactionPromises.get(transactionId)\n if (deferred) {\n deferred.reject(error)\n this.pendingTransactionPromises.delete(transactionId)\n }\n\n // Clean up the restoration transaction and rollback optimistic state\n this.cleanupRestorationTransaction(transactionId, true)\n }\n\n // Method for TransactionExecutor to register restoration transactions\n registerRestorationTransaction(\n offlineTransactionId: string,\n restorationTransaction: Transaction,\n ): void {\n this.restorationTransactions.set(\n offlineTransactionId,\n restorationTransaction,\n )\n }\n\n private cleanupRestorationTransaction(\n transactionId: string,\n shouldRollback = false,\n ): void {\n const restorationTx = this.restorationTransactions.get(transactionId)\n if (!restorationTx) {\n return\n }\n\n this.restorationTransactions.delete(transactionId)\n\n if (shouldRollback) {\n restorationTx.rollback()\n return\n }\n\n // Mark as completed so recomputeOptimisticState removes it from consideration.\n // The actual data will come from the sync.\n restorationTx.setState(`completed`)\n\n // Remove from each collection's transaction map and recompute\n const touchedCollections = new Set<string>()\n for (const mutation of restorationTx.mutations) {\n // Defensive check for corrupted deserialized data\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (!mutation.collection) {\n continue\n }\n const collectionId = mutation.collection.id\n if (touchedCollections.has(collectionId)) {\n continue\n }\n touchedCollections.add(collectionId)\n mutation.collection._state.transactions.delete(restorationTx.id)\n mutation.collection._state.recomputeOptimisticState(false)\n }\n }\n\n async removeFromOutbox(id: string): Promise<void> {\n if (!this.outbox) {\n return\n }\n await this.outbox.remove(id)\n }\n\n async peekOutbox(): Promise<Array<OfflineTransaction>> {\n if (!this.outbox) {\n return []\n }\n return this.outbox.getAll()\n }\n\n async clearOutbox(): Promise<void> {\n if (!this.outbox || !this.executor) {\n return\n }\n await this.outbox.clear()\n this.executor.clear()\n }\n\n getPendingCount(): number {\n if (!this.executor) {\n return 0\n }\n return this.executor.getPendingCount()\n }\n\n getRunningCount(): number {\n if (!this.executor) {\n return 0\n }\n return this.executor.getRunningCount()\n }\n\n getOnlineDetector(): OnlineDetector {\n return this.onlineDetector\n }\n\n isOnline(): boolean {\n return this.onlineDetector.isOnline()\n }\n\n dispose(): void {\n this.disposed = true\n this.executor?.pause()\n for (const collection of Object.values(this.config.collections)) {\n collection.deferDataRefresh = null\n }\n\n if (this.unsubscribeOnline) {\n this.unsubscribeOnline()\n this.unsubscribeOnline = null\n }\n\n if (this.unsubscribeLeadership) {\n this.unsubscribeLeadership()\n this.unsubscribeLeadership = null\n }\n\n if (this.leaderElection) {\n this.leaderElection.releaseLeadership()\n\n if (`dispose` in this.leaderElection) {\n ;(this.leaderElection as any).dispose()\n }\n }\n\n this.onlineDetector.dispose()\n }\n}\n\nexport function startOfflineExecutor(config: OfflineConfig): OfflineExecutor {\n return new OfflineExecutor(config)\n}\n"],"names":["KeyScheduler","WebOnlineDetector","IndexedDBAdapter","LocalStorageAdapter","WebLocksLeader","BroadcastChannelLeader","withSpan","OutboxManager","TransactionExecutor","createTransaction","safeRandomUUID","OfflineTransactionAPI","action","createOptimisticAction","createOfflineAction","withNestedSpan","OfflineExecutor"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"OfflineExecutor.cjs","sources":["../../src/OfflineExecutor.ts"],"sourcesContent":["// Storage adapters\nimport {\n createOptimisticAction,\n createTransaction,\n safeRandomUUID,\n} from '@tanstack/db'\nimport { IndexedDBAdapter } from './storage/IndexedDBAdapter'\nimport { LocalStorageAdapter } from './storage/LocalStorageAdapter'\n\n// Core components\nimport { OutboxManager } from './outbox/OutboxManager'\nimport { KeyScheduler } from './executor/KeyScheduler'\nimport { TransactionExecutor } from './executor/TransactionExecutor'\n\n// Coordination\nimport { WebLocksLeader } from './coordination/WebLocksLeader'\nimport { BroadcastChannelLeader } from './coordination/BroadcastChannelLeader'\n\n// Connectivity\nimport { WebOnlineDetector } from './connectivity/OnlineDetector'\n\n// API\nimport { OfflineTransaction as OfflineTransactionAPI } from './api/OfflineTransaction'\nimport { createOfflineAction } from './api/OfflineAction'\n\n// TanStack DB primitives\n\n// Replay\nimport { withNestedSpan, withSpan } from './telemetry/tracer'\nimport type {\n CreateOfflineActionOptions,\n CreateOfflineTransactionOptions,\n LeaderElection,\n OfflineConfig,\n OfflineMode,\n OfflineTransaction,\n OnlineDetector,\n StorageAdapter,\n StorageDiagnostic,\n} from './types'\nimport type { Transaction } from '@tanstack/db'\n\nexport class OfflineExecutor {\n private config: OfflineConfig\n\n // @ts-expect-error - Set during async initialization in initialize()\n private storage: StorageAdapter | null\n private outbox: OutboxManager | null\n private scheduler: KeyScheduler\n private executor: TransactionExecutor | null\n private leaderElection: LeaderElection | null\n private onlineDetector: OnlineDetector\n private isLeaderState = false\n private disposed = false\n private unsubscribeOnline: (() => void) | null = null\n private unsubscribeLeadership: (() => void) | null = null\n\n // Public diagnostic properties\n public readonly mode: OfflineMode\n public readonly storageDiagnostic: StorageDiagnostic\n\n // Track initialization completion\n private initPromise: Promise<void>\n private initResolve!: () => void\n private initReject!: (error: Error) => void\n\n // Coordination mechanism for blocking transactions\n private pendingTransactionPromises: Map<\n string,\n {\n promise: Promise<any>\n resolve: (result: any) => void\n reject: (error: Error) => void\n }\n > = new Map()\n\n // Track restoration transactions for cleanup when offline transactions complete\n private restorationTransactions: Map<string, Transaction> = new Map()\n\n constructor(config: OfflineConfig) {\n this.config = config\n this.scheduler = new KeyScheduler()\n this.onlineDetector = config.onlineDetector ?? new WebOnlineDetector()\n\n // Initialize as pending - will be set by async initialization\n this.storage = null\n this.outbox = null\n this.executor = null\n this.leaderElection = null\n\n // Temporary diagnostic - will be updated by async initialization\n this.mode = `offline`\n this.storageDiagnostic = {\n code: `STORAGE_AVAILABLE`,\n mode: `offline`,\n message: `Initializing storage...`,\n }\n\n // Create initialization promise\n this.initPromise = new Promise((resolve, reject) => {\n this.initResolve = resolve\n this.initReject = reject\n })\n // Handle constructor-started rejection; waitForInit still observes it.\n void this.initPromise.catch(() => {})\n this.initialize()\n }\n\n /**\n * Probe storage availability and create appropriate adapter.\n * Returns null if no storage is available (online-only mode).\n */\n private async createStorage(): Promise<{\n storage: StorageAdapter | null\n diagnostic: StorageDiagnostic\n }> {\n // If user provided custom storage, use it without probing\n if (this.config.storage) {\n return {\n storage: this.config.storage,\n diagnostic: {\n code: `STORAGE_AVAILABLE`,\n mode: `offline`,\n message: `Using custom storage adapter`,\n },\n }\n }\n\n // Probe IndexedDB first\n const idbProbe = await IndexedDBAdapter.probe()\n if (idbProbe.available) {\n return {\n storage: new IndexedDBAdapter(),\n diagnostic: {\n code: `STORAGE_AVAILABLE`,\n mode: `offline`,\n message: `Using IndexedDB for offline storage`,\n },\n }\n }\n\n // IndexedDB failed, try localStorage\n const lsProbe = LocalStorageAdapter.probe()\n if (lsProbe.available) {\n return {\n storage: new LocalStorageAdapter(),\n diagnostic: {\n code: `INDEXEDDB_UNAVAILABLE`,\n mode: `offline`,\n message: `IndexedDB unavailable, using localStorage fallback`,\n error: idbProbe.error,\n },\n }\n }\n\n // Both failed - determine the diagnostic code\n const isSecurityError =\n idbProbe.error?.name === `SecurityError` ||\n lsProbe.error?.name === `SecurityError`\n const isQuotaError =\n idbProbe.error?.name === `QuotaExceededError` ||\n lsProbe.error?.name === `QuotaExceededError`\n\n let code: StorageDiagnostic[`code`]\n let message: string\n\n if (isSecurityError) {\n code = `STORAGE_BLOCKED`\n message = `Storage blocked (private mode or security restrictions). Running in online-only mode.`\n } else if (isQuotaError) {\n code = `QUOTA_EXCEEDED`\n message = `Storage quota exceeded. Running in online-only mode.`\n } else {\n code = `UNKNOWN_ERROR`\n message = `Storage unavailable due to unknown error. Running in online-only mode.`\n }\n\n return {\n storage: null,\n diagnostic: {\n code,\n mode: `online-only`,\n message,\n error: idbProbe.error || lsProbe.error,\n },\n }\n }\n\n private createLeaderElection(): LeaderElection {\n if (this.config.leaderElection) {\n return this.config.leaderElection\n }\n\n if (WebLocksLeader.isSupported()) {\n return new WebLocksLeader()\n } else if (BroadcastChannelLeader.isSupported()) {\n return new BroadcastChannelLeader()\n } else {\n // Fallback: always be leader in environments without multi-tab support\n return {\n requestLeadership: () => Promise.resolve(true),\n releaseLeadership: () => {},\n isLeader: () => true,\n onLeadershipChange: () => () => {},\n }\n }\n }\n\n private setupEventListeners(): void {\n // Only set up leader election listeners if we have storage\n if (this.leaderElection) {\n this.unsubscribeLeadership = this.leaderElection.onLeadershipChange(\n (isLeader) => {\n // A custom elector may repeat the initial result while replay is active.\n if (this.disposed || isLeader === this.isLeaderState) return\n this.isLeaderState = isLeader\n if (!isLeader) this.executor?.pause()\n\n if (this.config.onLeadershipChange) {\n this.config.onLeadershipChange(isLeader)\n }\n\n if (isLeader) {\n this.loadAndReplayTransactions().catch((error) => {\n console.warn(`Failed to load and replay transactions:`, error)\n })\n }\n },\n )\n }\n\n this.unsubscribeOnline = this.onlineDetector.subscribe(() => {\n if (this.isOfflineEnabled && this.executor) {\n this.executor.resetRetryDelays()\n\n if (this.scheduler.getPendingCount() > 0) {\n const barrierPromise = this.executor.executeAll()\n\n for (const collection of Object.values(this.config.collections)) {\n collection.deferDataRefresh = barrierPromise\n }\n\n barrierPromise\n .catch((error) => {\n console.warn(\n `Failed to execute transactions on connectivity change:`,\n error,\n )\n })\n .finally(() => {\n for (const collection of Object.values(this.config.collections)) {\n if (collection.deferDataRefresh === barrierPromise) {\n collection.deferDataRefresh = null\n }\n }\n })\n } else {\n this.executor.executeAll().catch((error) => {\n console.warn(\n `Failed to execute transactions on connectivity change:`,\n error,\n )\n })\n }\n }\n })\n }\n\n private async initialize(): Promise<void> {\n return withSpan(`executor.initialize`, {}, async (span) => {\n try {\n // Probe storage and create adapter\n const { storage, diagnostic } = await this.createStorage()\n if (this.disposed) {\n this.initResolve()\n return\n }\n\n // Cast to writable to set readonly properties\n ;(this as any).storage = storage\n ;(this as any).storageDiagnostic = diagnostic\n ;(this as any).mode = diagnostic.mode\n\n span.setAttribute(`storage.mode`, diagnostic.mode)\n span.setAttribute(`storage.code`, diagnostic.code)\n\n if (!storage) {\n // Online-only mode - notify callback and skip offline setup\n if (this.config.onStorageFailure) {\n this.config.onStorageFailure(diagnostic)\n }\n span.setAttribute(`result`, `online-only`)\n this.initResolve()\n return\n }\n\n // Storage available - set up offline components\n this.outbox = new OutboxManager(storage, this.config.collections)\n this.executor = new TransactionExecutor(\n this.scheduler,\n this.outbox,\n this.config,\n this,\n )\n this.leaderElection = this.createLeaderElection()\n\n // Request leadership first\n const isLeader = await this.leaderElection.requestLeadership()\n // Disposal may have run while leadership was pending.\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (this.disposed) {\n this.leaderElection.releaseLeadership()\n this.initResolve()\n return\n }\n this.isLeaderState = isLeader\n span.setAttribute(`isLeader`, isLeader)\n\n // Set up event listeners after leadership is established\n // This prevents the callback from being called multiple times\n this.setupEventListeners()\n\n // Notify initial leadership state\n if (this.config.onLeadershipChange) {\n this.config.onLeadershipChange(isLeader)\n }\n\n if (isLeader) {\n await this.loadAndReplayTransactions()\n }\n span.setAttribute(`result`, `offline-enabled`)\n this.initResolve()\n } catch (error) {\n console.warn(`Failed to initialize offline executor:`, error)\n span.setAttribute(`result`, `failed`)\n this.initReject(\n error instanceof Error ? error : new Error(String(error)),\n )\n }\n })\n }\n\n private async loadAndReplayTransactions(): Promise<void> {\n if (!this.executor) {\n return\n }\n\n // Startup must observe a failed read instead of reporting an empty outbox.\n await this.executor.loadPendingTransactions()\n\n // Replay completion is separate from initialization and durable admission.\n this.executor.executeAll().catch((error) => {\n console.warn(`Failed to execute transactions:`, error)\n })\n }\n\n get isOfflineEnabled(): boolean {\n return !this.disposed && this.mode === `offline` && this.isLeaderState\n }\n\n /**\n * Wait for the executor to fully initialize.\n * This ensures that pending transactions are loaded and optimistic state is restored.\n */\n async waitForInit(): Promise<void> {\n return this.initPromise\n }\n\n createOfflineTransaction(\n options: CreateOfflineTransactionOptions,\n ): Transaction | OfflineTransactionAPI {\n const mutationFn = this.config.mutationFns[options.mutationFnName]\n\n if (!mutationFn) {\n throw new Error(`Unknown mutation function: ${options.mutationFnName}`)\n }\n\n // Check leadership immediately and use the appropriate primitive\n if (!this.isOfflineEnabled) {\n // Non-leader: use createTransaction directly with the resolved mutation function\n // We need to wrap it to add the idempotency key\n return createTransaction({\n autoCommit: options.autoCommit ?? true,\n mutationFn: (params) =>\n mutationFn({\n ...params,\n idempotencyKey: options.idempotencyKey || safeRandomUUID(),\n }),\n metadata: options.metadata,\n })\n }\n\n // Leader: use OfflineTransaction wrapper for offline persistence\n return new OfflineTransactionAPI(\n options,\n mutationFn,\n this.persistTransaction.bind(this),\n this,\n )\n }\n\n createOfflineAction<T>(options: CreateOfflineActionOptions<T>) {\n const mutationFn = this.config.mutationFns[options.mutationFnName]\n\n if (!mutationFn) {\n throw new Error(`Unknown mutation function: ${options.mutationFnName}`)\n }\n\n // Return a wrapper that checks leadership status at call time\n return (variables: T) => {\n // Check leadership when action is called, not when it's created\n if (!this.isOfflineEnabled) {\n // Non-leader: use createOptimisticAction directly\n const action = createOptimisticAction({\n mutationFn: (vars, params) =>\n mutationFn({\n ...vars,\n ...params,\n idempotencyKey: safeRandomUUID(),\n }),\n onMutate: options.onMutate,\n })\n return action(variables)\n }\n\n // Leader: use the offline action wrapper\n const action = createOfflineAction(\n options,\n mutationFn,\n this.persistTransaction.bind(this),\n this,\n )\n return action(variables)\n }\n }\n\n private async persistTransaction(\n transaction: OfflineTransaction,\n ): Promise<void> {\n // Wait for initialization to complete\n await this.initPromise\n\n return withNestedSpan(\n `executor.persistTransaction`,\n {\n 'transaction.id': transaction.id,\n 'transaction.mutationFnName': transaction.mutationFnName,\n },\n async (span) => {\n if (!this.isOfflineEnabled || !this.outbox || !this.executor) {\n span.setAttribute(`result`, `skipped_not_leader`)\n this.resolveTransaction(transaction.id, undefined)\n return\n }\n\n try {\n await this.outbox.add(transaction)\n // A shared queue error belongs to its execution, not every caller\n // that durably admitted a transaction. Per-ID signals settle callers.\n this.executor.execute(transaction).catch((error) => {\n console.warn(`Failed to execute transactions:`, error)\n })\n span.setAttribute(`result`, `persisted`)\n } catch (error) {\n console.error(\n `Failed to persist offline transaction ${transaction.id}:`,\n error,\n )\n span.setAttribute(`result`, `failed`)\n throw error\n }\n },\n )\n }\n\n // Method for OfflineTransaction to wait for completion\n async waitForTransactionCompletion(transactionId: string): Promise<any> {\n const existing = this.pendingTransactionPromises.get(transactionId)\n if (existing) {\n return existing.promise\n }\n\n const deferred: {\n promise: Promise<any>\n resolve: (result: any) => void\n reject: (error: Error) => void\n } = {} as any\n\n deferred.promise = new Promise((resolve, reject) => {\n deferred.resolve = resolve\n deferred.reject = reject\n })\n\n this.pendingTransactionPromises.set(transactionId, deferred)\n return deferred.promise\n }\n\n // Method for TransactionExecutor to signal completion\n resolveTransaction(transactionId: string, result: any): void {\n const deferred = this.pendingTransactionPromises.get(transactionId)\n if (deferred) {\n deferred.resolve(result)\n this.pendingTransactionPromises.delete(transactionId)\n }\n\n // Clean up the restoration transaction - the sync will provide authoritative data\n this.cleanupRestorationTransaction(transactionId)\n }\n\n // Method for TransactionExecutor to signal failure\n rejectTransaction(transactionId: string, error: Error): void {\n const deferred = this.pendingTransactionPromises.get(transactionId)\n if (deferred) {\n deferred.reject(error)\n this.pendingTransactionPromises.delete(transactionId)\n }\n\n // Clean up the restoration transaction and rollback optimistic state\n this.cleanupRestorationTransaction(transactionId, true)\n }\n\n // Method for TransactionExecutor to register restoration transactions\n registerRestorationTransaction(\n offlineTransactionId: string,\n restorationTransaction: Transaction,\n ): void {\n this.restorationTransactions.set(\n offlineTransactionId,\n restorationTransaction,\n )\n }\n\n private cleanupRestorationTransaction(\n transactionId: string,\n shouldRollback = false,\n ): void {\n const restorationTx = this.restorationTransactions.get(transactionId)\n if (!restorationTx) {\n return\n }\n\n this.restorationTransactions.delete(transactionId)\n\n if (shouldRollback) {\n restorationTx.rollback()\n return\n }\n\n // Mark as completed so recomputeOptimisticState removes it from consideration.\n // The actual data will come from the sync.\n restorationTx.setState(`completed`)\n\n // Remove from each collection's transaction map and recompute\n const touchedCollections = new Set<string>()\n for (const mutation of restorationTx.mutations) {\n // Defensive check for corrupted deserialized data\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (!mutation.collection) {\n continue\n }\n const collectionId = mutation.collection.id\n if (touchedCollections.has(collectionId)) {\n continue\n }\n touchedCollections.add(collectionId)\n mutation.collection._state.transactions.delete(restorationTx.id)\n mutation.collection._state.recomputeOptimisticState(false)\n }\n }\n\n async removeFromOutbox(id: string): Promise<void> {\n if (!this.outbox) {\n return\n }\n await this.outbox.remove(id)\n }\n\n async peekOutbox(): Promise<Array<OfflineTransaction>> {\n if (!this.outbox) {\n return []\n }\n return this.outbox.getAll()\n }\n\n async clearOutbox(): Promise<void> {\n if (!this.outbox || !this.executor) {\n return\n }\n await this.outbox.clear()\n this.executor.clear()\n }\n\n getPendingCount(): number {\n if (!this.executor) {\n return 0\n }\n return this.executor.getPendingCount()\n }\n\n getRunningCount(): number {\n if (!this.executor) {\n return 0\n }\n return this.executor.getRunningCount()\n }\n\n getOnlineDetector(): OnlineDetector {\n return this.onlineDetector\n }\n\n isOnline(): boolean {\n return this.onlineDetector.isOnline()\n }\n\n dispose(): void {\n this.disposed = true\n this.executor?.pause()\n for (const collection of Object.values(this.config.collections)) {\n collection.deferDataRefresh = null\n }\n\n if (this.unsubscribeOnline) {\n this.unsubscribeOnline()\n this.unsubscribeOnline = null\n }\n\n if (this.unsubscribeLeadership) {\n this.unsubscribeLeadership()\n this.unsubscribeLeadership = null\n }\n\n if (this.leaderElection) {\n this.leaderElection.releaseLeadership()\n\n if (`dispose` in this.leaderElection) {\n ;(this.leaderElection as any).dispose()\n }\n }\n\n this.onlineDetector.dispose()\n }\n}\n\nexport function startOfflineExecutor(config: OfflineConfig): OfflineExecutor {\n return new OfflineExecutor(config)\n}\n"],"names":["KeyScheduler","WebOnlineDetector","IndexedDBAdapter","LocalStorageAdapter","WebLocksLeader","BroadcastChannelLeader","withSpan","OutboxManager","TransactionExecutor","createTransaction","safeRandomUUID","OfflineTransactionAPI","action","createOptimisticAction","createOfflineAction","withNestedSpan","OfflineExecutor"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OnlineDetector.cjs","sources":["../../../src/connectivity/OnlineDetector.ts"],"sourcesContent":["import type { OnlineDetector } from '../types'\n\n/**\n * Web-based online detector that uses browser APIs.\n * Listens for:\n * - `window.online` event for network connectivity changes\n * - `document.visibilitychange` event for tab/window focus changes\n */\nexport class WebOnlineDetector implements OnlineDetector {\n private listeners: Set<() => void> = new Set()\n private isListening = false\n\n constructor() {\n this.startListening()\n }\n\n private startListening(): void {\n if (this.isListening) {\n return\n }\n\n this.isListening = true\n\n if (typeof window !== `undefined`) {\n window.addEventListener(`online`, this.handleOnline)\n document.addEventListener(`visibilitychange`, this.handleVisibilityChange)\n }\n }\n\n private stopListening(): void {\n if (!this.isListening) {\n return\n }\n\n this.isListening = false\n\n if (typeof window !== `undefined`) {\n window.removeEventListener(`online`, this.handleOnline)\n document.removeEventListener(\n `visibilitychange`,\n this.handleVisibilityChange,\n )\n }\n }\n\n private handleOnline = (): void => {\n this.notifyListeners()\n }\n\n private handleVisibilityChange = (): void => {\n if (document.visibilityState === `visible`) {\n this.notifyListeners()\n }\n }\n\n private notifyListeners(): void {\n for (const listener of this.listeners) {\n try {\n listener()\n } catch (error) {\n console.warn(`OnlineDetector listener error:`, error)\n }\n }\n }\n\n subscribe(callback: () => void): () => void {\n this.listeners.add(callback)\n\n return () => {\n this.listeners.delete(callback)\n\n if (this.listeners.size === 0) {\n this.stopListening()\n }\n }\n }\n\n notifyOnline(): void {\n this.notifyListeners()\n }\n\n isOnline(): boolean {\n if (typeof navigator !== `undefined`) {\n return navigator.onLine\n }\n return true\n }\n\n dispose(): void {\n this.stopListening()\n this.listeners.clear()\n }\n}\n\n/**\n * @deprecated Use `WebOnlineDetector` instead. This alias is kept for backwards compatibility.\n */\nexport const DefaultOnlineDetector = WebOnlineDetector\n"],"names":[],"mappings":";;AAQO,MAAM,kBAA4C;AAAA,EAIvD,cAAc;AAHd,SAAQ,gCAAiC,IAAA;AACzC,SAAQ,cAAc;AAmCtB,SAAQ,eAAe,MAAY;AACjC,WAAK,gBAAA;AAAA,IACP;AAEA,SAAQ,yBAAyB,MAAY;AAC3C,UAAI,SAAS,oBAAoB,WAAW;AAC1C,aAAK,gBAAA;AAAA,MACP;AAAA,IACF;AAxCE,SAAK,eAAA;AAAA,EACP;AAAA,EAEQ,iBAAuB;AAC7B,QAAI,KAAK,aAAa;AACpB;AAAA,IACF;AAEA,SAAK,cAAc;AAEnB,QAAI,OAAO,WAAW,aAAa;AACjC,aAAO,iBAAiB,UAAU,KAAK,YAAY;AACnD,eAAS,iBAAiB,oBAAoB,KAAK,sBAAsB;AAAA,IAC3E;AAAA,EACF;AAAA,EAEQ,gBAAsB;AAC5B,QAAI,CAAC,KAAK,aAAa;AACrB;AAAA,IACF;AAEA,SAAK,cAAc;AAEnB,QAAI,OAAO,WAAW,aAAa;AACjC,aAAO,oBAAoB,UAAU,KAAK,YAAY;AACtD,eAAS;AAAA,QACP;AAAA,QACA,KAAK;AAAA,MAAA;AAAA,IAET;AAAA,EACF;AAAA,EAYQ,kBAAwB;AAC9B,eAAW,YAAY,KAAK,WAAW;AACrC,UAAI;AACF,iBAAA;AAAA,MACF,SAAS,OAAO;AACd,gBAAQ,KAAK,kCAAkC,KAAK;AAAA,MACtD;AAAA,IACF;AAAA,EACF;AAAA,EAEA,UAAU,UAAkC;AAC1C,SAAK,UAAU,IAAI,QAAQ;AAE3B,WAAO,MAAM;AACX,WAAK,UAAU,OAAO,QAAQ;AAE9B,UAAI,KAAK,UAAU,SAAS,GAAG;AAC7B,aAAK,cAAA;AAAA,MACP;AAAA,IACF;AAAA,EACF;AAAA,EAEA,eAAqB;AACnB,SAAK,gBAAA;AAAA,EACP;AAAA,EAEA,WAAoB;AAClB,QAAI,OAAO,cAAc,aAAa;
|
|
1
|
+
{"version":3,"file":"OnlineDetector.cjs","sources":["../../../src/connectivity/OnlineDetector.ts"],"sourcesContent":["import type { OnlineDetector } from '../types'\n\n/**\n * Web-based online detector that uses browser APIs.\n * Listens for:\n * - `window.online` event for network connectivity changes\n * - `document.visibilitychange` event for tab/window focus changes\n */\nexport class WebOnlineDetector implements OnlineDetector {\n private listeners: Set<() => void> = new Set()\n private isListening = false\n\n constructor() {\n this.startListening()\n }\n\n private startListening(): void {\n if (this.isListening) {\n return\n }\n\n this.isListening = true\n\n if (typeof window !== `undefined`) {\n window.addEventListener(`online`, this.handleOnline)\n document.addEventListener(`visibilitychange`, this.handleVisibilityChange)\n }\n }\n\n private stopListening(): void {\n if (!this.isListening) {\n return\n }\n\n this.isListening = false\n\n if (typeof window !== `undefined`) {\n window.removeEventListener(`online`, this.handleOnline)\n document.removeEventListener(\n `visibilitychange`,\n this.handleVisibilityChange,\n )\n }\n }\n\n private handleOnline = (): void => {\n this.notifyListeners()\n }\n\n private handleVisibilityChange = (): void => {\n if (document.visibilityState === `visible`) {\n this.notifyListeners()\n }\n }\n\n private notifyListeners(): void {\n for (const listener of this.listeners) {\n try {\n listener()\n } catch (error) {\n console.warn(`OnlineDetector listener error:`, error)\n }\n }\n }\n\n subscribe(callback: () => void): () => void {\n this.listeners.add(callback)\n\n return () => {\n this.listeners.delete(callback)\n\n if (this.listeners.size === 0) {\n this.stopListening()\n }\n }\n }\n\n notifyOnline(): void {\n this.notifyListeners()\n }\n\n isOnline(): boolean {\n if (typeof navigator !== `undefined`) {\n // A visible tab may reach the server despite a stale browser hint.\n return (\n navigator.onLine ||\n (typeof document !== `undefined` &&\n document.visibilityState === `visible`)\n )\n }\n return true\n }\n\n dispose(): void {\n this.stopListening()\n this.listeners.clear()\n }\n}\n\n/**\n * @deprecated Use `WebOnlineDetector` instead. This alias is kept for backwards compatibility.\n */\nexport const DefaultOnlineDetector = WebOnlineDetector\n"],"names":[],"mappings":";;AAQO,MAAM,kBAA4C;AAAA,EAIvD,cAAc;AAHd,SAAQ,gCAAiC,IAAA;AACzC,SAAQ,cAAc;AAmCtB,SAAQ,eAAe,MAAY;AACjC,WAAK,gBAAA;AAAA,IACP;AAEA,SAAQ,yBAAyB,MAAY;AAC3C,UAAI,SAAS,oBAAoB,WAAW;AAC1C,aAAK,gBAAA;AAAA,MACP;AAAA,IACF;AAxCE,SAAK,eAAA;AAAA,EACP;AAAA,EAEQ,iBAAuB;AAC7B,QAAI,KAAK,aAAa;AACpB;AAAA,IACF;AAEA,SAAK,cAAc;AAEnB,QAAI,OAAO,WAAW,aAAa;AACjC,aAAO,iBAAiB,UAAU,KAAK,YAAY;AACnD,eAAS,iBAAiB,oBAAoB,KAAK,sBAAsB;AAAA,IAC3E;AAAA,EACF;AAAA,EAEQ,gBAAsB;AAC5B,QAAI,CAAC,KAAK,aAAa;AACrB;AAAA,IACF;AAEA,SAAK,cAAc;AAEnB,QAAI,OAAO,WAAW,aAAa;AACjC,aAAO,oBAAoB,UAAU,KAAK,YAAY;AACtD,eAAS;AAAA,QACP;AAAA,QACA,KAAK;AAAA,MAAA;AAAA,IAET;AAAA,EACF;AAAA,EAYQ,kBAAwB;AAC9B,eAAW,YAAY,KAAK,WAAW;AACrC,UAAI;AACF,iBAAA;AAAA,MACF,SAAS,OAAO;AACd,gBAAQ,KAAK,kCAAkC,KAAK;AAAA,MACtD;AAAA,IACF;AAAA,EACF;AAAA,EAEA,UAAU,UAAkC;AAC1C,SAAK,UAAU,IAAI,QAAQ;AAE3B,WAAO,MAAM;AACX,WAAK,UAAU,OAAO,QAAQ;AAE9B,UAAI,KAAK,UAAU,SAAS,GAAG;AAC7B,aAAK,cAAA;AAAA,MACP;AAAA,IACF;AAAA,EACF;AAAA,EAEA,eAAqB;AACnB,SAAK,gBAAA;AAAA,EACP;AAAA,EAEA,WAAoB;AAClB,QAAI,OAAO,cAAc,aAAa;AAEpC,aACE,UAAU,UACT,OAAO,aAAa,eACnB,SAAS,oBAAoB;AAAA,IAEnC;AACA,WAAO;AAAA,EACT;AAAA,EAEA,UAAgB;AACd,SAAK,cAAA;AACL,SAAK,UAAU,MAAA;AAAA,EACjB;AACF;AAKO,MAAM,wBAAwB;;;"}
|
|
@@ -8,7 +8,7 @@ class ReactNativeOnlineDetector {
|
|
|
8
8
|
this.netInfoUnsubscribe = null;
|
|
9
9
|
this.appStateSubscription = null;
|
|
10
10
|
this.isListening = false;
|
|
11
|
-
this.wasConnected =
|
|
11
|
+
this.wasConnected = false;
|
|
12
12
|
this.handleAppStateChange = (nextState) => {
|
|
13
13
|
if (nextState === `active`) {
|
|
14
14
|
this.notifyListeners();
|
|
@@ -21,12 +21,6 @@ class ReactNativeOnlineDetector {
|
|
|
21
21
|
return;
|
|
22
22
|
}
|
|
23
23
|
this.isListening = true;
|
|
24
|
-
if (typeof NetInfo.fetch === `function`) {
|
|
25
|
-
void NetInfo.fetch().then((state) => {
|
|
26
|
-
this.wasConnected = this.toConnectivityState(state);
|
|
27
|
-
}).catch(() => {
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
24
|
this.netInfoUnsubscribe = NetInfo.addEventListener((state) => {
|
|
31
25
|
const isConnected = this.toConnectivityState(state);
|
|
32
26
|
if (isConnected && !this.wasConnected) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ReactNativeOnlineDetector.cjs","sources":["../../../src/connectivity/ReactNativeOnlineDetector.ts"],"sourcesContent":["import NetInfo from '@react-native-community/netinfo'\nimport { AppState } from 'react-native'\nimport type { AppStateStatus, NativeEventSubscription } from 'react-native'\nimport type { OnlineDetector } from '../types'\n\n/**\n * React Native online detector that uses RN APIs.\n * Listens for:\n * - Network connectivity changes via `@react-native-community/netinfo`\n * - App state changes (foreground/background) via `AppState`\n */\nexport class ReactNativeOnlineDetector implements OnlineDetector {\n private listeners: Set<() => void> = new Set()\n private netInfoUnsubscribe: (() => void) | null = null\n private appStateSubscription: NativeEventSubscription | null = null\n private isListening = false\n private wasConnected =
|
|
1
|
+
{"version":3,"file":"ReactNativeOnlineDetector.cjs","sources":["../../../src/connectivity/ReactNativeOnlineDetector.ts"],"sourcesContent":["import NetInfo from '@react-native-community/netinfo'\nimport { AppState } from 'react-native'\nimport type { AppStateStatus, NativeEventSubscription } from 'react-native'\nimport type { OnlineDetector } from '../types'\n\n/**\n * React Native online detector that uses RN APIs.\n * Listens for:\n * - Network connectivity changes via `@react-native-community/netinfo`\n * - App state changes (foreground/background) via `AppState`\n */\nexport class ReactNativeOnlineDetector implements OnlineDetector {\n private listeners: Set<() => void> = new Set()\n private netInfoUnsubscribe: (() => void) | null = null\n private appStateSubscription: NativeEventSubscription | null = null\n private isListening = false\n private wasConnected = false\n\n constructor() {\n this.startListening()\n }\n\n private startListening(): void {\n if (this.isListening) {\n return\n }\n\n this.isListening = true\n\n // Subscribe to network state changes\n this.netInfoUnsubscribe = NetInfo.addEventListener((state) => {\n const isConnected = this.toConnectivityState(state)\n\n // Only notify when transitioning to online\n if (isConnected && !this.wasConnected) {\n this.notifyListeners()\n }\n\n this.wasConnected = isConnected\n })\n\n // Subscribe to app state changes (foreground/background)\n this.appStateSubscription = AppState.addEventListener(\n `change`,\n this.handleAppStateChange,\n )\n }\n\n private handleAppStateChange = (nextState: AppStateStatus): void => {\n // Notify when app becomes active (foreground)\n if (nextState === `active`) {\n this.notifyListeners()\n }\n }\n\n private stopListening(): void {\n if (!this.isListening) {\n return\n }\n\n this.isListening = false\n\n if (this.netInfoUnsubscribe) {\n this.netInfoUnsubscribe()\n this.netInfoUnsubscribe = null\n }\n\n if (this.appStateSubscription) {\n this.appStateSubscription.remove()\n this.appStateSubscription = null\n }\n }\n\n private notifyListeners(): void {\n for (const listener of this.listeners) {\n try {\n listener()\n } catch (error) {\n console.warn(`ReactNativeOnlineDetector listener error:`, error)\n }\n }\n }\n\n subscribe(callback: () => void): () => void {\n this.listeners.add(callback)\n\n return () => {\n this.listeners.delete(callback)\n\n if (this.listeners.size === 0) {\n this.stopListening()\n }\n }\n }\n\n notifyOnline(): void {\n this.notifyListeners()\n }\n\n isOnline(): boolean {\n return this.wasConnected\n }\n\n dispose(): void {\n this.stopListening()\n this.listeners.clear()\n }\n\n private toConnectivityState(state: {\n isConnected: boolean | null\n isInternetReachable: boolean | null\n }): boolean {\n return !!state.isConnected && state.isInternetReachable !== false\n }\n}\n"],"names":["AppState"],"mappings":";;;;AAWO,MAAM,0BAAoD;AAAA,EAO/D,cAAc;AANd,SAAQ,gCAAiC,IAAA;AACzC,SAAQ,qBAA0C;AAClD,SAAQ,uBAAuD;AAC/D,SAAQ,cAAc;AACtB,SAAQ,eAAe;AAgCvB,SAAQ,uBAAuB,CAAC,cAAoC;AAElE,UAAI,cAAc,UAAU;AAC1B,aAAK,gBAAA;AAAA,MACP;AAAA,IACF;AAlCE,SAAK,eAAA;AAAA,EACP;AAAA,EAEQ,iBAAuB;AAC7B,QAAI,KAAK,aAAa;AACpB;AAAA,IACF;AAEA,SAAK,cAAc;AAGnB,SAAK,qBAAqB,QAAQ,iBAAiB,CAAC,UAAU;AAC5D,YAAM,cAAc,KAAK,oBAAoB,KAAK;AAGlD,UAAI,eAAe,CAAC,KAAK,cAAc;AACrC,aAAK,gBAAA;AAAA,MACP;AAEA,WAAK,eAAe;AAAA,IACtB,CAAC;AAGD,SAAK,uBAAuBA,YAAAA,SAAS;AAAA,MACnC;AAAA,MACA,KAAK;AAAA,IAAA;AAAA,EAET;AAAA,EASQ,gBAAsB;AAC5B,QAAI,CAAC,KAAK,aAAa;AACrB;AAAA,IACF;AAEA,SAAK,cAAc;AAEnB,QAAI,KAAK,oBAAoB;AAC3B,WAAK,mBAAA;AACL,WAAK,qBAAqB;AAAA,IAC5B;AAEA,QAAI,KAAK,sBAAsB;AAC7B,WAAK,qBAAqB,OAAA;AAC1B,WAAK,uBAAuB;AAAA,IAC9B;AAAA,EACF;AAAA,EAEQ,kBAAwB;AAC9B,eAAW,YAAY,KAAK,WAAW;AACrC,UAAI;AACF,iBAAA;AAAA,MACF,SAAS,OAAO;AACd,gBAAQ,KAAK,6CAA6C,KAAK;AAAA,MACjE;AAAA,IACF;AAAA,EACF;AAAA,EAEA,UAAU,UAAkC;AAC1C,SAAK,UAAU,IAAI,QAAQ;AAE3B,WAAO,MAAM;AACX,WAAK,UAAU,OAAO,QAAQ;AAE9B,UAAI,KAAK,UAAU,SAAS,GAAG;AAC7B,aAAK,cAAA;AAAA,MACP;AAAA,IACF;AAAA,EACF;AAAA,EAEA,eAAqB;AACnB,SAAK,gBAAA;AAAA,EACP;AAAA,EAEA,WAAoB;AAClB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,UAAgB;AACd,SAAK,cAAA;AACL,SAAK,UAAU,MAAA;AAAA,EACjB;AAAA,EAEQ,oBAAoB,OAGhB;AACV,WAAO,CAAC,CAAC,MAAM,eAAe,MAAM,wBAAwB;AAAA,EAC9D;AACF;;"}
|
|
@@ -4,7 +4,6 @@ const tracer = require("../telemetry/tracer.cjs");
|
|
|
4
4
|
class KeyScheduler {
|
|
5
5
|
constructor() {
|
|
6
6
|
this.pendingTransactions = [];
|
|
7
|
-
this.isRunning = false;
|
|
8
7
|
}
|
|
9
8
|
schedule(transaction) {
|
|
10
9
|
return tracer.withSyncSpan(
|
|
@@ -32,7 +31,7 @@ class KeyScheduler {
|
|
|
32
31
|
`scheduler.getNext`,
|
|
33
32
|
{ pendingCount: this.pendingTransactions.length },
|
|
34
33
|
(span) => {
|
|
35
|
-
if (this.
|
|
34
|
+
if (this.activeTransactionId !== void 0 || this.pendingTransactions.length === 0) {
|
|
36
35
|
span.setAttribute(`result`, `empty`);
|
|
37
36
|
return void 0;
|
|
38
37
|
}
|
|
@@ -51,15 +50,15 @@ class KeyScheduler {
|
|
|
51
50
|
isReadyToRun(transaction) {
|
|
52
51
|
return Date.now() >= transaction.nextAttemptAt;
|
|
53
52
|
}
|
|
54
|
-
markStarted(
|
|
55
|
-
this.
|
|
53
|
+
markStarted(transaction) {
|
|
54
|
+
this.activeTransactionId = transaction.id;
|
|
56
55
|
}
|
|
57
56
|
markCompleted(transaction) {
|
|
58
57
|
this.removeTransaction(transaction);
|
|
59
|
-
this.
|
|
58
|
+
this.activeTransactionId = void 0;
|
|
60
59
|
}
|
|
61
60
|
markFailed(_transaction) {
|
|
62
|
-
this.
|
|
61
|
+
this.activeTransactionId = void 0;
|
|
63
62
|
}
|
|
64
63
|
removeTransaction(transaction) {
|
|
65
64
|
const index = this.pendingTransactions.findIndex(
|
|
@@ -70,38 +69,38 @@ class KeyScheduler {
|
|
|
70
69
|
}
|
|
71
70
|
}
|
|
72
71
|
updateTransaction(transaction) {
|
|
73
|
-
|
|
74
|
-
(tx) => tx.id === transaction.id
|
|
75
|
-
);
|
|
76
|
-
if (index >= 0) {
|
|
77
|
-
this.pendingTransactions[index] = transaction;
|
|
78
|
-
this.pendingTransactions.sort(
|
|
79
|
-
(a, b) => a.createdAt.getTime() - b.createdAt.getTime()
|
|
80
|
-
);
|
|
81
|
-
}
|
|
72
|
+
this.updateTransactions([transaction]);
|
|
82
73
|
}
|
|
83
74
|
getPendingCount() {
|
|
84
75
|
return this.pendingTransactions.length;
|
|
85
76
|
}
|
|
86
77
|
getRunningCount() {
|
|
87
|
-
return this.
|
|
78
|
+
return this.activeTransactionId === void 0 ? 0 : 1;
|
|
88
79
|
}
|
|
89
80
|
clear() {
|
|
90
81
|
this.pendingTransactions = [];
|
|
91
|
-
this.
|
|
82
|
+
this.activeTransactionId = void 0;
|
|
83
|
+
}
|
|
84
|
+
/** @internal Reconcile one replay snapshot without canceling issued work. */
|
|
85
|
+
removePendingTransactions(transactionIds) {
|
|
86
|
+
const ids = new Set(transactionIds);
|
|
87
|
+
if (this.activeTransactionId !== void 0)
|
|
88
|
+
ids.delete(this.activeTransactionId);
|
|
89
|
+
this.pendingTransactions = this.pendingTransactions.filter(
|
|
90
|
+
({ id }) => !ids.has(id)
|
|
91
|
+
);
|
|
92
|
+
return [...ids];
|
|
92
93
|
}
|
|
93
94
|
getAllPendingTransactions() {
|
|
94
95
|
return [...this.pendingTransactions];
|
|
95
96
|
}
|
|
96
97
|
updateTransactions(updatedTransactions) {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
104
|
-
}
|
|
98
|
+
const updatedById = new Map(
|
|
99
|
+
updatedTransactions.map((transaction) => [transaction.id, transaction])
|
|
100
|
+
);
|
|
101
|
+
this.pendingTransactions = this.pendingTransactions.map(
|
|
102
|
+
(transaction) => updatedById.get(transaction.id) ?? transaction
|
|
103
|
+
);
|
|
105
104
|
this.pendingTransactions.sort(
|
|
106
105
|
(a, b) => a.createdAt.getTime() - b.createdAt.getTime()
|
|
107
106
|
);
|
|
@@ -1 +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
|
|
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 activeTransactionId: string | undefined\n\n schedule(transaction: OfflineTransaction): boolean {\n return withSyncSpan(\n `scheduler.schedule`,\n {\n 'transaction.id': transaction.id,\n queueLength: this.pendingTransactions.length,\n },\n () => {\n if (\n this.pendingTransactions.some(\n (pending) => pending.id === transaction.id,\n )\n ) {\n return false\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 return true\n },\n )\n }\n\n getNext(): OfflineTransaction | undefined {\n return withSyncSpan(\n `scheduler.getNext`,\n { pendingCount: this.pendingTransactions.length },\n (span) => {\n if (\n this.activeTransactionId !== undefined ||\n this.pendingTransactions.length === 0\n ) {\n span.setAttribute(`result`, `empty`)\n return undefined\n }\n\n const firstTransaction = this.pendingTransactions[0]!\n\n if (!this.isReadyToRun(firstTransaction)) {\n span.setAttribute(`result`, `waiting_for_first`)\n span.setAttribute(`transaction.id`, firstTransaction.id)\n return undefined\n }\n\n span.setAttribute(`result`, `found`)\n span.setAttribute(`transaction.id`, firstTransaction.id)\n return firstTransaction\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.activeTransactionId = transaction.id\n }\n\n markCompleted(transaction: OfflineTransaction): void {\n this.removeTransaction(transaction)\n this.activeTransactionId = undefined\n }\n\n markFailed(_transaction: OfflineTransaction): void {\n this.activeTransactionId = undefined\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 this.updateTransactions([transaction])\n }\n\n getPendingCount(): number {\n return this.pendingTransactions.length\n }\n\n getRunningCount(): number {\n return this.activeTransactionId === undefined ? 0 : 1\n }\n\n clear(): void {\n this.pendingTransactions = []\n this.activeTransactionId = undefined\n }\n\n /** @internal Reconcile one replay snapshot without canceling issued work. */\n removePendingTransactions(transactionIds: Iterable<string>): Array<string> {\n const ids = new Set(transactionIds)\n if (this.activeTransactionId !== undefined)\n ids.delete(this.activeTransactionId)\n this.pendingTransactions = this.pendingTransactions.filter(\n ({ id }) => !ids.has(id),\n )\n return [...ids]\n }\n\n getAllPendingTransactions(): Array<OfflineTransaction> {\n return [...this.pendingTransactions]\n }\n\n updateTransactions(updatedTransactions: Array<OfflineTransaction>): void {\n const updatedById = new Map(\n updatedTransactions.map((transaction) => [transaction.id, transaction]),\n )\n this.pendingTransactions = this.pendingTransactions.map(\n (transaction) => updatedById.get(transaction.id) ?? transaction,\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":";;;AAGO,MAAM,aAAa;AAAA,EAAnB,cAAA;AACL,SAAQ,sBAAiD,CAAA;AAAA,EAAC;AAAA,EAG1D,SAAS,aAA0C;AACjD,WAAOA,OAAAA;AAAAA,MACL;AAAA,MACA;AAAA,QACE,kBAAkB,YAAY;AAAA,QAC9B,aAAa,KAAK,oBAAoB;AAAA,MAAA;AAAA,MAExC,MAAM;AACJ,YACE,KAAK,oBAAoB;AAAA,UACvB,CAAC,YAAY,QAAQ,OAAO,YAAY;AAAA,QAAA,GAE1C;AACA,iBAAO;AAAA,QACT;AACA,aAAK,oBAAoB,KAAK,WAAW;AAEzC,aAAK,oBAAoB;AAAA,UACvB,CAAC,GAAG,MAAM,EAAE,UAAU,YAAY,EAAE,UAAU,QAAA;AAAA,QAAQ;AAExD,eAAO;AAAA,MACT;AAAA,IAAA;AAAA,EAEJ;AAAA,EAEA,UAA0C;AACxC,WAAOA,OAAAA;AAAAA,MACL;AAAA,MACA,EAAE,cAAc,KAAK,oBAAoB,OAAA;AAAA,MACzC,CAAC,SAAS;AACR,YACE,KAAK,wBAAwB,UAC7B,KAAK,oBAAoB,WAAW,GACpC;AACA,eAAK,aAAa,UAAU,OAAO;AACnC,iBAAO;AAAA,QACT;AAEA,cAAM,mBAAmB,KAAK,oBAAoB,CAAC;AAEnD,YAAI,CAAC,KAAK,aAAa,gBAAgB,GAAG;AACxC,eAAK,aAAa,UAAU,mBAAmB;AAC/C,eAAK,aAAa,kBAAkB,iBAAiB,EAAE;AACvD,iBAAO;AAAA,QACT;AAEA,aAAK,aAAa,UAAU,OAAO;AACnC,aAAK,aAAa,kBAAkB,iBAAiB,EAAE;AACvD,eAAO;AAAA,MACT;AAAA,IAAA;AAAA,EAEJ;AAAA,EAEQ,aAAa,aAA0C;AAC7D,WAAO,KAAK,SAAS,YAAY;AAAA,EACnC;AAAA,EAEA,YAAY,aAAuC;AACjD,SAAK,sBAAsB,YAAY;AAAA,EACzC;AAAA,EAEA,cAAc,aAAuC;AACnD,SAAK,kBAAkB,WAAW;AAClC,SAAK,sBAAsB;AAAA,EAC7B;AAAA,EAEA,WAAW,cAAwC;AACjD,SAAK,sBAAsB;AAAA,EAC7B;AAAA,EAEQ,kBAAkB,aAAuC;AAC/D,UAAM,QAAQ,KAAK,oBAAoB;AAAA,MACrC,CAAC,OAAO,GAAG,OAAO,YAAY;AAAA,IAAA;AAEhC,QAAI,SAAS,GAAG;AACd,WAAK,oBAAoB,OAAO,OAAO,CAAC;AAAA,IAC1C;AAAA,EACF;AAAA,EAEA,kBAAkB,aAAuC;AACvD,SAAK,mBAAmB,CAAC,WAAW,CAAC;AAAA,EACvC;AAAA,EAEA,kBAA0B;AACxB,WAAO,KAAK,oBAAoB;AAAA,EAClC;AAAA,EAEA,kBAA0B;AACxB,WAAO,KAAK,wBAAwB,SAAY,IAAI;AAAA,EACtD;AAAA,EAEA,QAAc;AACZ,SAAK,sBAAsB,CAAA;AAC3B,SAAK,sBAAsB;AAAA,EAC7B;AAAA;AAAA,EAGA,0BAA0B,gBAAiD;AACzE,UAAM,MAAM,IAAI,IAAI,cAAc;AAClC,QAAI,KAAK,wBAAwB;AAC/B,UAAI,OAAO,KAAK,mBAAmB;AACrC,SAAK,sBAAsB,KAAK,oBAAoB;AAAA,MAClD,CAAC,EAAE,GAAA,MAAS,CAAC,IAAI,IAAI,EAAE;AAAA,IAAA;AAEzB,WAAO,CAAC,GAAG,GAAG;AAAA,EAChB;AAAA,EAEA,4BAAuD;AACrD,WAAO,CAAC,GAAG,KAAK,mBAAmB;AAAA,EACrC;AAAA,EAEA,mBAAmB,qBAAsD;AACvE,UAAM,cAAc,IAAI;AAAA,MACtB,oBAAoB,IAAI,CAAC,gBAAgB,CAAC,YAAY,IAAI,WAAW,CAAC;AAAA,IAAA;AAExE,SAAK,sBAAsB,KAAK,oBAAoB;AAAA,MAClD,CAAC,gBAAgB,YAAY,IAAI,YAAY,EAAE,KAAK;AAAA,IAAA;AAGtD,SAAK,oBAAoB;AAAA,MACvB,CAAC,GAAG,MAAM,EAAE,UAAU,YAAY,EAAE,UAAU,QAAA;AAAA,IAAQ;AAAA,EAE1D;AACF;;"}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { OfflineTransaction } from '../types.cjs';
|
|
2
2
|
export declare class KeyScheduler {
|
|
3
3
|
private pendingTransactions;
|
|
4
|
-
private
|
|
4
|
+
private activeTransactionId;
|
|
5
5
|
schedule(transaction: OfflineTransaction): boolean;
|
|
6
6
|
getNext(): OfflineTransaction | undefined;
|
|
7
7
|
private isReadyToRun;
|
|
8
|
-
markStarted(
|
|
8
|
+
markStarted(transaction: OfflineTransaction): void;
|
|
9
9
|
markCompleted(transaction: OfflineTransaction): void;
|
|
10
10
|
markFailed(_transaction: OfflineTransaction): void;
|
|
11
11
|
private removeTransaction;
|
|
@@ -13,6 +13,8 @@ export declare class KeyScheduler {
|
|
|
13
13
|
getPendingCount(): number;
|
|
14
14
|
getRunningCount(): number;
|
|
15
15
|
clear(): void;
|
|
16
|
+
/** @internal Reconcile one replay snapshot without canceling issued work. */
|
|
17
|
+
removePendingTransactions(transactionIds: Iterable<string>): Array<string>;
|
|
16
18
|
getAllPendingTransactions(): Array<OfflineTransaction>;
|
|
17
19
|
updateTransactions(updatedTransactions: Array<OfflineTransaction>): void;
|
|
18
20
|
}
|