@pol-studios/powersync 1.0.30 → 1.0.33

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 (116) hide show
  1. package/dist/{CacheSettingsManager-uz-kbnRH.d.ts → CacheSettingsManager-0H_7thHW.d.ts} +21 -3
  2. package/dist/attachments/index.d.ts +30 -30
  3. package/dist/attachments/index.js +13 -4
  4. package/dist/{background-sync-CVR3PkFi.d.ts → background-sync-BujnI3IR.d.ts} +1 -1
  5. package/dist/{chunk-RE5HWLCB.js → chunk-2RDWLXJW.js} +322 -103
  6. package/dist/chunk-2RDWLXJW.js.map +1 -0
  7. package/dist/{chunk-P4HZA6ZT.js → chunk-4665ZSE5.js} +2 -2
  8. package/dist/chunk-4665ZSE5.js.map +1 -0
  9. package/dist/{chunk-XOY2CJ67.js → chunk-4F5B5CZ7.js} +3 -3
  10. package/dist/chunk-5WRI5ZAA.js +31 -0
  11. package/dist/{chunk-BC2SRII2.js → chunk-65A3SYJZ.js} +14 -1
  12. package/dist/chunk-65A3SYJZ.js.map +1 -0
  13. package/dist/chunk-6SZ64KCZ.js +755 -0
  14. package/dist/chunk-6SZ64KCZ.js.map +1 -0
  15. package/dist/{chunk-C2ACBYBZ.js → chunk-74TBHWJ4.js} +10 -96
  16. package/dist/{chunk-C2ACBYBZ.js.map → chunk-74TBHWJ4.js.map} +1 -1
  17. package/dist/chunk-ANXWYQEJ.js +1 -0
  18. package/dist/chunk-ANXWYQEJ.js.map +1 -0
  19. package/dist/{chunk-CAB26E6F.js → chunk-C4J4MLER.js} +29 -24
  20. package/dist/chunk-C4J4MLER.js.map +1 -0
  21. package/dist/{chunk-C5ODS3XH.js → chunk-EOW7JK7Q.js} +9 -16
  22. package/dist/chunk-EOW7JK7Q.js.map +1 -0
  23. package/dist/chunk-HRAVPIAZ.js +220 -0
  24. package/dist/chunk-HRAVPIAZ.js.map +1 -0
  25. package/dist/{chunk-XAEII4ZX.js → chunk-NUGQOTEM.js} +32 -4
  26. package/dist/chunk-NUGQOTEM.js.map +1 -0
  27. package/dist/chunk-OGUFUZSY.js +5415 -0
  28. package/dist/chunk-OGUFUZSY.js.map +1 -0
  29. package/dist/{chunk-JCGOZVWL.js → chunk-P4D6BQ4X.js} +115 -576
  30. package/dist/chunk-P4D6BQ4X.js.map +1 -0
  31. package/dist/{chunk-CACKC6XG.js → chunk-PGEDE6IM.js} +136 -89
  32. package/dist/chunk-PGEDE6IM.js.map +1 -0
  33. package/dist/{chunk-A4IBBWGO.js → chunk-RALHHPTU.js} +1 -1
  34. package/dist/chunk-RIDSPLE5.js +42 -0
  35. package/dist/chunk-RIDSPLE5.js.map +1 -0
  36. package/dist/{chunk-Z6VOBGTU.js → chunk-UOMHWUHV.js} +2 -12
  37. package/dist/chunk-UOMHWUHV.js.map +1 -0
  38. package/dist/{chunk-QREWE3NR.js → chunk-YONQYTVH.js} +2 -2
  39. package/dist/chunk-ZAN22NGL.js +13 -0
  40. package/dist/chunk-ZAN22NGL.js.map +1 -0
  41. package/dist/config/index.d.ts +200 -0
  42. package/dist/config/index.js +23 -0
  43. package/dist/config/index.js.map +1 -0
  44. package/dist/connector/index.d.ts +23 -5
  45. package/dist/connector/index.js +4 -1
  46. package/dist/core/index.d.ts +2 -2
  47. package/dist/core/index.js +1 -0
  48. package/dist/error/index.js +1 -0
  49. package/dist/generator/index.js +2 -0
  50. package/dist/generator/index.js.map +1 -1
  51. package/dist/index.d.ts +19 -16
  52. package/dist/index.js +68 -36
  53. package/dist/index.native.d.ts +18 -14
  54. package/dist/index.native.js +73 -34
  55. package/dist/index.web.d.ts +17 -14
  56. package/dist/index.web.js +68 -36
  57. package/dist/maintenance/index.d.ts +2 -2
  58. package/dist/maintenance/index.js +3 -2
  59. package/dist/platform/index.d.ts +1 -1
  60. package/dist/platform/index.js +2 -0
  61. package/dist/platform/index.js.map +1 -1
  62. package/dist/platform/index.native.d.ts +1 -1
  63. package/dist/platform/index.native.js +1 -0
  64. package/dist/platform/index.web.d.ts +1 -1
  65. package/dist/platform/index.web.js +1 -0
  66. package/dist/pol-attachment-queue-DqBvLAEY.d.ts +255 -0
  67. package/dist/provider/index.d.ts +149 -114
  68. package/dist/provider/index.js +9 -14
  69. package/dist/provider/index.native.d.ts +108 -0
  70. package/dist/provider/index.native.js +121 -0
  71. package/dist/provider/index.native.js.map +1 -0
  72. package/dist/provider/index.web.d.ts +16 -0
  73. package/dist/provider/index.web.js +112 -0
  74. package/dist/provider/index.web.js.map +1 -0
  75. package/dist/react/index.d.ts +16 -65
  76. package/dist/react/index.js +2 -9
  77. package/dist/storage/index.d.ts +5 -4
  78. package/dist/storage/index.js +12 -9
  79. package/dist/storage/index.native.d.ts +5 -4
  80. package/dist/storage/index.native.js +8 -5
  81. package/dist/storage/index.web.d.ts +5 -4
  82. package/dist/storage/index.web.js +11 -8
  83. package/dist/storage/upload/index.d.ts +4 -3
  84. package/dist/storage/upload/index.js +4 -2
  85. package/dist/storage/upload/index.native.d.ts +4 -3
  86. package/dist/storage/upload/index.native.js +4 -2
  87. package/dist/storage/upload/index.web.d.ts +2 -1
  88. package/dist/storage/upload/index.web.js +4 -2
  89. package/dist/{supabase-connector-C4YpH_l3.d.ts → supabase-connector-HMxBA9Kg.d.ts} +2 -2
  90. package/dist/sync/index.d.ts +155 -20
  91. package/dist/sync/index.js +13 -3
  92. package/dist/{types-CyvBaAl8.d.ts → types-6QHGELuY.d.ts} +4 -1
  93. package/dist/{types-Dv1uf0LZ.d.ts → types-B9MptP7E.d.ts} +7 -10
  94. package/dist/types-BhAEsJj-.d.ts +330 -0
  95. package/dist/{types-D0WcHrq6.d.ts → types-CGMibJKD.d.ts} +8 -0
  96. package/dist/{types-CpM2_LhU.d.ts → types-DqJnP50o.d.ts} +6 -1
  97. package/dist/{pol-attachment-queue-BE2HU3Us.d.ts → types-JCEhw2Lf.d.ts} +139 -346
  98. package/package.json +18 -4
  99. package/dist/chunk-654ERHA7.js +0 -1
  100. package/dist/chunk-BC2SRII2.js.map +0 -1
  101. package/dist/chunk-C5ODS3XH.js.map +0 -1
  102. package/dist/chunk-CAB26E6F.js.map +0 -1
  103. package/dist/chunk-CACKC6XG.js.map +0 -1
  104. package/dist/chunk-FNYQFILT.js +0 -44
  105. package/dist/chunk-FNYQFILT.js.map +0 -1
  106. package/dist/chunk-JCGOZVWL.js.map +0 -1
  107. package/dist/chunk-P4HZA6ZT.js.map +0 -1
  108. package/dist/chunk-RBPWEOIV.js +0 -358
  109. package/dist/chunk-RBPWEOIV.js.map +0 -1
  110. package/dist/chunk-RE5HWLCB.js.map +0 -1
  111. package/dist/chunk-XAEII4ZX.js.map +0 -1
  112. package/dist/chunk-Z6VOBGTU.js.map +0 -1
  113. /package/dist/{chunk-XOY2CJ67.js.map → chunk-4F5B5CZ7.js.map} +0 -0
  114. /package/dist/{chunk-654ERHA7.js.map → chunk-5WRI5ZAA.js.map} +0 -0
  115. /package/dist/{chunk-A4IBBWGO.js.map → chunk-RALHHPTU.js.map} +0 -0
  116. /package/dist/{chunk-QREWE3NR.js.map → chunk-YONQYTVH.js.map} +0 -0
@@ -1,8 +1,6 @@
1
1
  import {
2
- DEFAULT_CONNECTION_HEALTH,
3
- DEFAULT_SYNC_METRICS,
4
- DEFAULT_SYNC_STATUS
5
- } from "./chunk-FNYQFILT.js";
2
+ LOCAL_TABLE_NAMES
3
+ } from "./chunk-OGUFUZSY.js";
6
4
  import {
7
5
  HEALTH_CHECK_INTERVAL_MS,
8
6
  HEALTH_CHECK_TIMEOUT_MS,
@@ -19,15 +17,52 @@ import {
19
17
  generateFailureId
20
18
  } from "./chunk-I2AYMY5O.js";
21
19
 
20
+ // src/provider/types.ts
21
+ var DEFAULT_SYNC_STATUS = {
22
+ connected: false,
23
+ connecting: false,
24
+ hasSynced: false,
25
+ lastSyncedAt: null,
26
+ uploading: false,
27
+ downloading: false,
28
+ downloadProgress: null,
29
+ failedTransactions: [],
30
+ hasUploadErrors: false,
31
+ permanentErrorCount: 0
32
+ };
33
+ var DEFAULT_CONNECTION_HEALTH = {
34
+ status: "disconnected",
35
+ latency: null,
36
+ lastHealthCheck: null,
37
+ consecutiveFailures: 0,
38
+ reconnectAttempts: 0
39
+ };
40
+ var DEFAULT_SYNC_METRICS = {
41
+ totalSyncs: 0,
42
+ successfulSyncs: 0,
43
+ failedSyncs: 0,
44
+ lastSyncDuration: null,
45
+ averageSyncDuration: null,
46
+ totalDataDownloaded: 0,
47
+ totalDataUploaded: 0,
48
+ lastError: null
49
+ };
50
+ var DEFAULT_SYNC_CONFIG = {
51
+ autoConnect: true,
52
+ syncInterval: 0,
53
+ enableHealthMonitoring: true,
54
+ enableMetrics: true
55
+ };
56
+
22
57
  // src/sync/status-tracker.ts
23
- var STORAGE_KEY_COMPLETED_TRANSACTIONS = "@pol-powersync:completed_transactions";
24
- var STORAGE_KEY_FAILED_TRANSACTIONS = "@pol-powersync:failed_transactions";
25
58
  var MAX_COMPLETED_TRANSACTIONS = 1e3;
26
59
  var SyncStatusTracker = class _SyncStatusTracker {
27
60
  storage;
28
61
  logger;
29
62
  notifyThrottleMs;
30
63
  onStatusChange;
64
+ // PowerSync database for local-only table persistence
65
+ _db = null;
31
66
  _state;
32
67
  _pendingMutations = [];
33
68
  _lastNotifyTime = 0;
@@ -45,13 +80,6 @@ var SyncStatusTracker = class _SyncStatusTracker {
45
80
  _persistDebounceTimer = null;
46
81
  // Track download progress separately to preserve it when offline
47
82
  _lastProgress = null;
48
- // Exponential backoff configuration for retry timing
49
- _backoffBaseMs = 1e3;
50
- // 1 second base
51
- _backoffMaxMs = 6e4;
52
- // 60 seconds max
53
- _backoffMultiplier = 2;
54
- // Double each retry
55
83
  // Failed transaction tracking
56
84
  _failedTransactions = [];
57
85
  _maxStoredFailures = 50;
@@ -80,10 +108,89 @@ var SyncStatusTracker = class _SyncStatusTracker {
80
108
  lastUpdated: /* @__PURE__ */ new Date()
81
109
  };
82
110
  }
111
+ // ─── Database Setup ─────────────────────────────────────────────────────────
112
+ // Flag to prevent operations during initialization
113
+ _dbInitializing = false;
114
+ _dbReady = false;
115
+ // Track persistence errors for degraded mode detection
116
+ _persistenceErrorCount = 0;
117
+ _maxPersistenceErrors = 5;
118
+ /**
119
+ * Set the PowerSync database instance AND initialize transactions atomically.
120
+ * This prevents race conditions between setDatabase() and initTransactions().
121
+ *
122
+ * @param db - The PowerSync database instance
123
+ * @returns Promise that resolves when initialization is complete
124
+ */
125
+ async setDatabaseAndInit(db) {
126
+ if (this._dbInitializing) {
127
+ this.logger.warn("[StatusTracker] Database initialization already in progress");
128
+ return;
129
+ }
130
+ this._dbInitializing = true;
131
+ this._dbReady = false;
132
+ this._db = db;
133
+ this.logger.debug("[StatusTracker] Database set, initializing transactions...");
134
+ try {
135
+ await this.initTransactions();
136
+ this._dbReady = true;
137
+ this.logger.debug("[StatusTracker] Database ready for local-only table persistence");
138
+ } finally {
139
+ this._dbInitializing = false;
140
+ }
141
+ }
142
+ /**
143
+ * Set the PowerSync database instance for local-only table persistence.
144
+ * @deprecated Use setDatabaseAndInit() instead to avoid race conditions.
145
+ */
146
+ setDatabase(db) {
147
+ this._db = db;
148
+ this.logger.debug("[StatusTracker] Database set for local-only table persistence");
149
+ }
150
+ /**
151
+ * Get the PowerSync database instance.
152
+ */
153
+ getDatabase() {
154
+ return this._db;
155
+ }
156
+ /**
157
+ * Check if the database is ready for persistence operations.
158
+ */
159
+ isDatabaseReady() {
160
+ return this._dbReady && this._db !== null;
161
+ }
162
+ /**
163
+ * Check if persistence is in a degraded state (too many errors).
164
+ */
165
+ isPersistenceDegraded() {
166
+ return this._persistenceErrorCount >= this._maxPersistenceErrors;
167
+ }
168
+ /**
169
+ * Track a persistence error and log if we enter degraded mode.
170
+ */
171
+ _trackPersistenceError(operation, error) {
172
+ this._persistenceErrorCount++;
173
+ this.logger.warn(`[StatusTracker] Persistence error (${this._persistenceErrorCount}/${this._maxPersistenceErrors}):`, operation, error);
174
+ if (this._persistenceErrorCount === this._maxPersistenceErrors) {
175
+ this.logger.error("[StatusTracker] Persistence degraded - too many errors. Transaction history may not persist.");
176
+ }
177
+ }
178
+ /**
179
+ * Reset persistence error count (e.g., after successful operation).
180
+ */
181
+ _resetPersistenceErrors() {
182
+ if (this._persistenceErrorCount > 0) {
183
+ this._persistenceErrorCount = 0;
184
+ this.logger.debug("[StatusTracker] Persistence recovered");
185
+ }
186
+ }
83
187
  // ─── Initialization ────────────────────────────────────────────────────────
84
188
  /**
85
189
  * Initialize the tracker by loading persisted state.
86
190
  * Includes migration from old isPaused boolean to new syncMode.
191
+ *
192
+ * Note: Transaction data is loaded from SQLite after the database is set.
193
+ * Call initTransactions() after setDatabase() to load transaction history.
87
194
  */
88
195
  async init() {
89
196
  try {
@@ -112,46 +219,81 @@ var SyncStatusTracker = class _SyncStatusTracker {
112
219
  } catch (err) {
113
220
  this.logger.warn("[StatusTracker] Failed to load auto-offline flag:", err);
114
221
  }
222
+ }
223
+ /**
224
+ * Initialize transactions from SQLite local-only tables.
225
+ * Must be called after setDatabase() has been called.
226
+ */
227
+ async initTransactions() {
228
+ if (!this._db) {
229
+ this.logger.warn("[StatusTracker] Cannot init transactions: database not set");
230
+ return;
231
+ }
232
+ await this._loadCompletedTransactionsFromDb();
233
+ await this._loadFailedTransactionsFromDb();
234
+ this.cleanupStaleFailures();
235
+ }
236
+ /**
237
+ * Load completed transactions from SQLite local-only table.
238
+ */
239
+ async _loadCompletedTransactionsFromDb() {
240
+ if (!this._db) return;
115
241
  try {
116
- const completedJson = await this.storage.getItem(STORAGE_KEY_COMPLETED_TRANSACTIONS);
117
- if (completedJson) {
118
- const parsed = JSON.parse(completedJson);
119
- this._completedTransactions = parsed.map((item) => {
120
- const remappedEntries = item.entries.map((e) => this.remapEntry(e)).filter((e) => e !== null);
121
- return {
122
- ...item,
123
- completedAt: new Date(item.completedAt),
124
- entries: remappedEntries
125
- };
126
- }).filter((item) => !isNaN(item.completedAt.getTime()) && item.entries.length > 0);
127
- this.logger.debug("[StatusTracker] Loaded", this._completedTransactions.length, "completed transactions");
128
- }
242
+ const rows = await this._db.getAll(`SELECT * FROM ${LOCAL_TABLE_NAMES.COMPLETED_TRANSACTIONS} ORDER BY completedAt DESC LIMIT ?`, [MAX_COMPLETED_TRANSACTIONS]);
243
+ this._completedTransactions = rows.map((row) => {
244
+ const entries = JSON.parse(row.entries);
245
+ const remappedEntries = entries.map((e) => this.remapEntry(e)).filter((e) => e !== null);
246
+ return {
247
+ id: row.id,
248
+ entries: remappedEntries,
249
+ affectedEntityIds: JSON.parse(row.affectedEntityIds),
250
+ affectedTables: JSON.parse(row.affectedTables),
251
+ completedAt: new Date(row.completedAt)
252
+ };
253
+ }).filter((item) => !isNaN(item.completedAt.getTime()) && item.entries.length > 0);
254
+ this._resetPersistenceErrors();
255
+ this.logger.debug("[StatusTracker] Loaded", this._completedTransactions.length, "completed transactions from SQLite");
129
256
  } catch (err) {
130
- this.logger.warn("[StatusTracker] Failed to load completed transactions:", err);
257
+ this._trackPersistenceError("loadCompletedTransactions", err);
258
+ this.logger.warn("[StatusTracker] Failed to load completed transactions from SQLite:", err);
131
259
  }
260
+ }
261
+ /**
262
+ * Load failed transactions from SQLite local-only table.
263
+ */
264
+ async _loadFailedTransactionsFromDb() {
265
+ if (!this._db) return;
132
266
  try {
133
- const failedJson = await this.storage.getItem(STORAGE_KEY_FAILED_TRANSACTIONS);
134
- if (failedJson) {
135
- const parsed = JSON.parse(failedJson);
136
- this._failedTransactions = parsed.map((item) => {
137
- const remappedEntries = item.entries.map((e) => this.remapEntry(e)).filter((e) => e !== null);
138
- return {
139
- ...item,
140
- firstFailedAt: new Date(item.firstFailedAt),
141
- lastFailedAt: new Date(item.lastFailedAt),
142
- error: {
143
- ...item.error,
144
- timestamp: new Date(item.error.timestamp)
145
- },
146
- entries: remappedEntries
147
- };
148
- }).filter((item) => !isNaN(item.firstFailedAt.getTime()) && !isNaN(item.lastFailedAt.getTime()) && item.entries.length > 0);
149
- this.logger.debug("[StatusTracker] Loaded", this._failedTransactions.length, "failed transactions");
150
- }
267
+ const rows = await this._db.getAll(`SELECT * FROM ${LOCAL_TABLE_NAMES.FAILED_TRANSACTIONS} ORDER BY lastFailedAt DESC`);
268
+ this._failedTransactions = rows.map((row) => {
269
+ const entries = JSON.parse(row.entries);
270
+ const remappedEntries = entries.map((e) => this.remapEntry(e)).filter((e) => e !== null);
271
+ const error = {
272
+ type: row.errorType,
273
+ message: row.errorMessage,
274
+ userMessage: row.errorUserMessage,
275
+ timestamp: new Date(row.errorTimestamp),
276
+ isPermanent: row.errorIsPermanent === 1,
277
+ pgCode: row.errorPgCode ?? void 0
278
+ };
279
+ return {
280
+ id: row.id,
281
+ entries: remappedEntries,
282
+ error,
283
+ affectedEntityIds: JSON.parse(row.affectedEntityIds),
284
+ affectedTables: JSON.parse(row.affectedTables),
285
+ retryCount: row.retryCount,
286
+ isPermanent: row.isPermanent === 1,
287
+ firstFailedAt: new Date(row.firstFailedAt),
288
+ lastFailedAt: new Date(row.lastFailedAt)
289
+ };
290
+ }).filter((item) => !isNaN(item.firstFailedAt.getTime()) && !isNaN(item.lastFailedAt.getTime()) && item.entries.length > 0);
291
+ this._resetPersistenceErrors();
292
+ this.logger.debug("[StatusTracker] Loaded", this._failedTransactions.length, "failed transactions from SQLite");
151
293
  } catch (err) {
152
- this.logger.warn("[StatusTracker] Failed to load failed transactions:", err);
294
+ this._trackPersistenceError("loadFailedTransactions", err);
295
+ this.logger.warn("[StatusTracker] Failed to load failed transactions from SQLite:", err);
153
296
  }
154
- this.cleanupStaleFailures();
155
297
  }
156
298
  /**
157
299
  * Dispose the tracker and clear timers.
@@ -477,18 +619,6 @@ var SyncStatusTracker = class _SyncStatusTracker {
477
619
  };
478
620
  }
479
621
  // ─── Failed Transaction Tracking ────────────────────────────────────────────
480
- /**
481
- * Compute exponential backoff delay for a given retry count.
482
- * Uses base 1s, max 60s, multiplier 2.
483
- *
484
- * @param retryCount - Number of retries (1-based)
485
- * @returns Backoff delay in milliseconds
486
- */
487
- _computeBackoffMs(retryCount) {
488
- const exponent = Math.max(0, retryCount - 1);
489
- const delay = this._backoffBaseMs * Math.pow(this._backoffMultiplier, exponent);
490
- return Math.min(delay, this._backoffMaxMs);
491
- }
492
622
  /**
493
623
  * Record a transaction failure.
494
624
  * If a failure for the same entries already exists, updates the retry count.
@@ -504,25 +634,21 @@ var SyncStatusTracker = class _SyncStatusTracker {
504
634
  const existingIds = f.entries.map((e) => e.id).sort().join(",");
505
635
  return existingIds === entryIds;
506
636
  });
637
+ let failedTransaction;
507
638
  if (existingIndex !== -1) {
508
639
  const existing = this._failedTransactions[existingIndex];
509
640
  const newRetryCount = existing.retryCount + 1;
510
- const backoffMs = isPermanent ? void 0 : this._computeBackoffMs(newRetryCount);
511
- const nextRetryAt = backoffMs ? new Date(now.getTime() + backoffMs) : void 0;
512
- this._failedTransactions[existingIndex] = {
641
+ failedTransaction = {
513
642
  ...existing,
514
643
  error,
515
644
  retryCount: newRetryCount,
516
645
  lastFailedAt: now,
517
- isPermanent,
518
- backoffMs,
519
- nextRetryAt
646
+ isPermanent
520
647
  };
648
+ this._failedTransactions[existingIndex] = failedTransaction;
521
649
  } else {
522
650
  const retryCount = preserveMetadata?.retryCount ?? 1;
523
- const backoffMs = isPermanent ? void 0 : this._computeBackoffMs(retryCount);
524
- const nextRetryAt = backoffMs ? new Date(now.getTime() + backoffMs) : void 0;
525
- const newFailure = {
651
+ failedTransaction = {
526
652
  id: generateFailureId(normalizedEntries),
527
653
  entries: normalizedEntries,
528
654
  error,
@@ -531,17 +657,19 @@ var SyncStatusTracker = class _SyncStatusTracker {
531
657
  lastFailedAt: now,
532
658
  isPermanent,
533
659
  affectedEntityIds,
534
- affectedTables,
535
- backoffMs,
536
- nextRetryAt
660
+ affectedTables
537
661
  };
538
- this._failedTransactions.push(newFailure);
662
+ this._failedTransactions.push(failedTransaction);
539
663
  if (this._failedTransactions.length > this._maxStoredFailures) {
540
664
  this._failedTransactions.sort((a, b) => a.firstFailedAt.getTime() - b.firstFailedAt.getTime());
665
+ const removed = this._failedTransactions.slice(0, this._failedTransactions.length - this._maxStoredFailures);
541
666
  this._failedTransactions = this._failedTransactions.slice(-this._maxStoredFailures);
667
+ for (const r of removed) {
668
+ void this._deleteFailedTransaction(r.id);
669
+ }
542
670
  }
543
671
  }
544
- this._schedulePersist();
672
+ void this._insertFailedTransaction(failedTransaction);
545
673
  this._notifyFailureListeners();
546
674
  this._notifyListeners();
547
675
  }
@@ -552,7 +680,7 @@ var SyncStatusTracker = class _SyncStatusTracker {
552
680
  const initialLength = this._failedTransactions.length;
553
681
  this._failedTransactions = this._failedTransactions.filter((f) => f.id !== failureId);
554
682
  if (this._failedTransactions.length !== initialLength) {
555
- this._schedulePersist();
683
+ void this._deleteFailedTransaction(failureId);
556
684
  this._notifyFailureListeners();
557
685
  this._notifyListeners();
558
686
  }
@@ -569,15 +697,20 @@ var SyncStatusTracker = class _SyncStatusTracker {
569
697
  clearSuccessfulEntries(entryIds) {
570
698
  if (entryIds.length === 0 || this._failedTransactions.length === 0) return;
571
699
  const entryIdSet = new Set(entryIds);
572
- const initialLength = this._failedTransactions.length;
700
+ const removedIds = [];
573
701
  this._failedTransactions = this._failedTransactions.filter((failure) => {
574
702
  const hasSuccessfulEntry = failure.entries.some((entry) => entryIdSet.has(entry.id));
575
- return !hasSuccessfulEntry;
703
+ if (hasSuccessfulEntry) {
704
+ removedIds.push(failure.id);
705
+ return false;
706
+ }
707
+ return true;
576
708
  });
577
- if (this._failedTransactions.length !== initialLength) {
578
- const clearedCount = initialLength - this._failedTransactions.length;
579
- this.logger.debug(`[StatusTracker] Cleared ${clearedCount} failures for successful entries`);
580
- this._schedulePersist();
709
+ if (removedIds.length > 0) {
710
+ this.logger.debug(`[StatusTracker] Cleared ${removedIds.length} failures for successful entries`);
711
+ for (const id of removedIds) {
712
+ void this._deleteFailedTransaction(id);
713
+ }
581
714
  this._notifyFailureListeners();
582
715
  this._notifyListeners();
583
716
  }
@@ -599,8 +732,8 @@ var SyncStatusTracker = class _SyncStatusTracker {
599
732
  return null;
600
733
  }
601
734
  this._failedTransactions = this._failedTransactions.filter((f) => f.id !== failureId);
735
+ void this._deleteFailedTransaction(failureId);
602
736
  this._notifyFailureListeners();
603
- this._schedulePersist();
604
737
  this.logger.info("[StatusTracker] Retrieved failure for retry:", failureId, "entries:", failure.entries.length);
605
738
  return failure.entries;
606
739
  }
@@ -644,11 +777,19 @@ var SyncStatusTracker = class _SyncStatusTracker {
644
777
  */
645
778
  cleanupStaleFailures() {
646
779
  const cutoff = Date.now() - this._failureTTLMs;
647
- const initialLength = this._failedTransactions.length;
648
- this._failedTransactions = this._failedTransactions.filter((f) => f.lastFailedAt.getTime() > cutoff);
649
- if (this._failedTransactions.length !== initialLength) {
650
- this.logger.debug(`[StatusTracker] Cleaned up ${initialLength - this._failedTransactions.length} stale failures`);
651
- this._schedulePersist();
780
+ const removedIds = [];
781
+ this._failedTransactions = this._failedTransactions.filter((f) => {
782
+ if (f.lastFailedAt.getTime() <= cutoff) {
783
+ removedIds.push(f.id);
784
+ return false;
785
+ }
786
+ return true;
787
+ });
788
+ if (removedIds.length > 0) {
789
+ this.logger.debug(`[StatusTracker] Cleaned up ${removedIds.length} stale failures`);
790
+ for (const id of removedIds) {
791
+ void this._deleteFailedTransaction(id);
792
+ }
652
793
  this._notifyFailureListeners();
653
794
  this._notifyListeners();
654
795
  }
@@ -674,7 +815,8 @@ var SyncStatusTracker = class _SyncStatusTracker {
674
815
  if (this._completedTransactions.length > MAX_COMPLETED_TRANSACTIONS) {
675
816
  this._completedTransactions = this._completedTransactions.slice(0, MAX_COMPLETED_TRANSACTIONS);
676
817
  }
677
- this._schedulePersist();
818
+ void this._insertCompletedTransaction(completed);
819
+ void this._enforceCompletedTransactionsLimit();
678
820
  this._notifyCompletedListeners();
679
821
  this.logger.debug(`[StatusTracker] Recorded completed transaction: ${completed.id} (${entries.length} entries)`);
680
822
  }
@@ -690,7 +832,7 @@ var SyncStatusTracker = class _SyncStatusTracker {
690
832
  clearCompletedHistory() {
691
833
  if (this._completedTransactions.length === 0) return;
692
834
  this._completedTransactions = [];
693
- this._schedulePersist();
835
+ void this._deleteAllCompletedTransactions();
694
836
  this._notifyCompletedListeners();
695
837
  this.logger.debug("[StatusTracker] Cleared completed transaction history");
696
838
  }
@@ -701,7 +843,7 @@ var SyncStatusTracker = class _SyncStatusTracker {
701
843
  const initialLength = this._completedTransactions.length;
702
844
  this._completedTransactions = this._completedTransactions.filter((c) => c.id !== completedId);
703
845
  if (this._completedTransactions.length !== initialLength) {
704
- this._schedulePersist();
846
+ void this._deleteCompletedTransaction(completedId);
705
847
  this._notifyCompletedListeners();
706
848
  this.logger.debug("[StatusTracker] Cleared completed transaction:", completedId);
707
849
  }
@@ -742,27 +884,100 @@ var SyncStatusTracker = class _SyncStatusTracker {
742
884
  return this._lastNotificationTime;
743
885
  }
744
886
  // ─── Private Methods ───────────────────────────────────────────────────────
887
+ // ─── SQLite Persistence Methods ─────────────────────────────────────────────
745
888
  /**
746
- * Schedule a debounced persist operation.
747
- * This prevents race conditions from multiple rapid persist calls.
889
+ * Insert a completed transaction into SQLite.
748
890
  */
749
- _schedulePersist() {
750
- if (this._persistDebounceTimer) {
751
- clearTimeout(this._persistDebounceTimer);
891
+ async _insertCompletedTransaction(transaction) {
892
+ if (!this._db) {
893
+ this.logger.warn("[StatusTracker] Cannot persist completed transaction: database not set");
894
+ return;
895
+ }
896
+ try {
897
+ await this._db.execute(`INSERT OR REPLACE INTO ${LOCAL_TABLE_NAMES.COMPLETED_TRANSACTIONS}
898
+ (id, entries, affectedEntityIds, affectedTables, completedAt)
899
+ VALUES (?, ?, ?, ?, ?)`, [transaction.id, JSON.stringify(transaction.entries), JSON.stringify(transaction.affectedEntityIds), JSON.stringify(transaction.affectedTables), transaction.completedAt.toISOString()]);
900
+ this._resetPersistenceErrors();
901
+ } catch (err) {
902
+ this._trackPersistenceError("insertCompletedTransaction", err);
903
+ this.logger.warn("[StatusTracker] Failed to insert completed transaction:", err);
752
904
  }
753
- this._persistDebounceTimer = setTimeout(() => {
754
- this._persistDebounceTimer = null;
755
- this._persistTransactions();
756
- }, 100);
757
905
  }
758
906
  /**
759
- * Persist completed and failed transactions to storage.
907
+ * Delete a completed transaction from SQLite.
760
908
  */
761
- async _persistTransactions() {
909
+ async _deleteCompletedTransaction(id) {
910
+ if (!this._db) return;
762
911
  try {
763
- await Promise.all([this.storage.setItem(STORAGE_KEY_COMPLETED_TRANSACTIONS, JSON.stringify(this._completedTransactions)), this.storage.setItem(STORAGE_KEY_FAILED_TRANSACTIONS, JSON.stringify(this._failedTransactions))]);
912
+ await this._db.execute(`DELETE FROM ${LOCAL_TABLE_NAMES.COMPLETED_TRANSACTIONS} WHERE id = ?`, [id]);
913
+ this._resetPersistenceErrors();
764
914
  } catch (err) {
765
- this.logger.warn("[StatusTracker] Failed to persist transactions:", err);
915
+ this._trackPersistenceError("deleteCompletedTransaction", err);
916
+ this.logger.warn("[StatusTracker] Failed to delete completed transaction:", err);
917
+ }
918
+ }
919
+ /**
920
+ * Delete all completed transactions from SQLite.
921
+ */
922
+ async _deleteAllCompletedTransactions() {
923
+ if (!this._db) return;
924
+ try {
925
+ await this._db.execute(`DELETE FROM ${LOCAL_TABLE_NAMES.COMPLETED_TRANSACTIONS}`);
926
+ this._resetPersistenceErrors();
927
+ } catch (err) {
928
+ this._trackPersistenceError("deleteAllCompletedTransactions", err);
929
+ this.logger.warn("[StatusTracker] Failed to delete all completed transactions:", err);
930
+ }
931
+ }
932
+ /**
933
+ * Insert a failed transaction into SQLite.
934
+ */
935
+ async _insertFailedTransaction(transaction) {
936
+ if (!this._db) {
937
+ this.logger.warn("[StatusTracker] Cannot persist failed transaction: database not set");
938
+ return;
939
+ }
940
+ try {
941
+ await this._db.execute(`INSERT OR REPLACE INTO ${LOCAL_TABLE_NAMES.FAILED_TRANSACTIONS}
942
+ (id, entries, errorType, errorMessage, errorUserMessage, errorPgCode, errorTimestamp, errorIsPermanent,
943
+ affectedEntityIds, affectedTables, retryCount, isPermanent, firstFailedAt, lastFailedAt)
944
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [transaction.id, JSON.stringify(transaction.entries), transaction.error.type, transaction.error.message, transaction.error.userMessage, transaction.error.pgCode ?? null, transaction.error.timestamp.toISOString(), transaction.error.isPermanent ? 1 : 0, JSON.stringify(transaction.affectedEntityIds), JSON.stringify(transaction.affectedTables), transaction.retryCount, transaction.isPermanent ? 1 : 0, transaction.firstFailedAt.toISOString(), transaction.lastFailedAt.toISOString()]);
945
+ this._resetPersistenceErrors();
946
+ } catch (err) {
947
+ this._trackPersistenceError("insertFailedTransaction", err);
948
+ this.logger.warn("[StatusTracker] Failed to insert failed transaction:", err);
949
+ }
950
+ }
951
+ /**
952
+ * Delete a failed transaction from SQLite.
953
+ */
954
+ async _deleteFailedTransaction(id) {
955
+ if (!this._db) return;
956
+ try {
957
+ await this._db.execute(`DELETE FROM ${LOCAL_TABLE_NAMES.FAILED_TRANSACTIONS} WHERE id = ?`, [id]);
958
+ this._resetPersistenceErrors();
959
+ } catch (err) {
960
+ this._trackPersistenceError("deleteFailedTransaction", err);
961
+ this.logger.warn("[StatusTracker] Failed to delete failed transaction:", err);
962
+ }
963
+ }
964
+ /**
965
+ * Enforce the max completed transactions limit in SQLite.
966
+ * Removes oldest entries beyond the limit.
967
+ */
968
+ async _enforceCompletedTransactionsLimit() {
969
+ if (!this._db) return;
970
+ try {
971
+ await this._db.execute(`DELETE FROM ${LOCAL_TABLE_NAMES.COMPLETED_TRANSACTIONS}
972
+ WHERE id NOT IN (
973
+ SELECT id FROM ${LOCAL_TABLE_NAMES.COMPLETED_TRANSACTIONS}
974
+ ORDER BY completedAt DESC
975
+ LIMIT ?
976
+ )`, [MAX_COMPLETED_TRANSACTIONS]);
977
+ this._resetPersistenceErrors();
978
+ } catch (err) {
979
+ this._trackPersistenceError("enforceCompletedTransactionsLimit", err);
980
+ this.logger.warn("[StatusTracker] Failed to enforce completed transactions limit:", err);
766
981
  }
767
982
  }
768
983
  _hasStatusChanged(newStatus) {
@@ -1348,8 +1563,12 @@ var HealthMonitor = class {
1348
1563
  };
1349
1564
 
1350
1565
  export {
1566
+ DEFAULT_SYNC_STATUS,
1567
+ DEFAULT_CONNECTION_HEALTH,
1568
+ DEFAULT_SYNC_METRICS,
1569
+ DEFAULT_SYNC_CONFIG,
1351
1570
  SyncStatusTracker,
1352
1571
  MetricsCollector,
1353
1572
  HealthMonitor
1354
1573
  };
1355
- //# sourceMappingURL=chunk-RE5HWLCB.js.map
1574
+ //# sourceMappingURL=chunk-2RDWLXJW.js.map