@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.
- package/dist/{CacheSettingsManager-uz-kbnRH.d.ts → CacheSettingsManager-0H_7thHW.d.ts} +21 -3
- package/dist/attachments/index.d.ts +30 -30
- package/dist/attachments/index.js +13 -4
- package/dist/{background-sync-CVR3PkFi.d.ts → background-sync-BujnI3IR.d.ts} +1 -1
- package/dist/{chunk-RE5HWLCB.js → chunk-2RDWLXJW.js} +322 -103
- package/dist/chunk-2RDWLXJW.js.map +1 -0
- package/dist/{chunk-P4HZA6ZT.js → chunk-4665ZSE5.js} +2 -2
- package/dist/chunk-4665ZSE5.js.map +1 -0
- package/dist/{chunk-XOY2CJ67.js → chunk-4F5B5CZ7.js} +3 -3
- package/dist/chunk-5WRI5ZAA.js +31 -0
- package/dist/{chunk-BC2SRII2.js → chunk-65A3SYJZ.js} +14 -1
- package/dist/chunk-65A3SYJZ.js.map +1 -0
- package/dist/chunk-6SZ64KCZ.js +755 -0
- package/dist/chunk-6SZ64KCZ.js.map +1 -0
- package/dist/{chunk-C2ACBYBZ.js → chunk-74TBHWJ4.js} +10 -96
- package/dist/{chunk-C2ACBYBZ.js.map → chunk-74TBHWJ4.js.map} +1 -1
- package/dist/chunk-ANXWYQEJ.js +1 -0
- package/dist/chunk-ANXWYQEJ.js.map +1 -0
- package/dist/{chunk-CAB26E6F.js → chunk-C4J4MLER.js} +29 -24
- package/dist/chunk-C4J4MLER.js.map +1 -0
- package/dist/{chunk-C5ODS3XH.js → chunk-EOW7JK7Q.js} +9 -16
- package/dist/chunk-EOW7JK7Q.js.map +1 -0
- package/dist/chunk-HRAVPIAZ.js +220 -0
- package/dist/chunk-HRAVPIAZ.js.map +1 -0
- package/dist/{chunk-XAEII4ZX.js → chunk-NUGQOTEM.js} +32 -4
- package/dist/chunk-NUGQOTEM.js.map +1 -0
- package/dist/chunk-OGUFUZSY.js +5415 -0
- package/dist/chunk-OGUFUZSY.js.map +1 -0
- package/dist/{chunk-JCGOZVWL.js → chunk-P4D6BQ4X.js} +115 -576
- package/dist/chunk-P4D6BQ4X.js.map +1 -0
- package/dist/{chunk-CACKC6XG.js → chunk-PGEDE6IM.js} +136 -89
- package/dist/chunk-PGEDE6IM.js.map +1 -0
- package/dist/{chunk-A4IBBWGO.js → chunk-RALHHPTU.js} +1 -1
- package/dist/chunk-RIDSPLE5.js +42 -0
- package/dist/chunk-RIDSPLE5.js.map +1 -0
- package/dist/{chunk-Z6VOBGTU.js → chunk-UOMHWUHV.js} +2 -12
- package/dist/chunk-UOMHWUHV.js.map +1 -0
- package/dist/{chunk-QREWE3NR.js → chunk-YONQYTVH.js} +2 -2
- package/dist/chunk-ZAN22NGL.js +13 -0
- package/dist/chunk-ZAN22NGL.js.map +1 -0
- package/dist/config/index.d.ts +200 -0
- package/dist/config/index.js +23 -0
- package/dist/config/index.js.map +1 -0
- package/dist/connector/index.d.ts +23 -5
- package/dist/connector/index.js +4 -1
- package/dist/core/index.d.ts +2 -2
- package/dist/core/index.js +1 -0
- package/dist/error/index.js +1 -0
- package/dist/generator/index.js +2 -0
- package/dist/generator/index.js.map +1 -1
- package/dist/index.d.ts +19 -16
- package/dist/index.js +68 -36
- package/dist/index.native.d.ts +18 -14
- package/dist/index.native.js +73 -34
- package/dist/index.web.d.ts +17 -14
- package/dist/index.web.js +68 -36
- package/dist/maintenance/index.d.ts +2 -2
- package/dist/maintenance/index.js +3 -2
- package/dist/platform/index.d.ts +1 -1
- package/dist/platform/index.js +2 -0
- package/dist/platform/index.js.map +1 -1
- package/dist/platform/index.native.d.ts +1 -1
- package/dist/platform/index.native.js +1 -0
- package/dist/platform/index.web.d.ts +1 -1
- package/dist/platform/index.web.js +1 -0
- package/dist/pol-attachment-queue-DqBvLAEY.d.ts +255 -0
- package/dist/provider/index.d.ts +149 -114
- package/dist/provider/index.js +9 -14
- package/dist/provider/index.native.d.ts +108 -0
- package/dist/provider/index.native.js +121 -0
- package/dist/provider/index.native.js.map +1 -0
- package/dist/provider/index.web.d.ts +16 -0
- package/dist/provider/index.web.js +112 -0
- package/dist/provider/index.web.js.map +1 -0
- package/dist/react/index.d.ts +16 -65
- package/dist/react/index.js +2 -9
- package/dist/storage/index.d.ts +5 -4
- package/dist/storage/index.js +12 -9
- package/dist/storage/index.native.d.ts +5 -4
- package/dist/storage/index.native.js +8 -5
- package/dist/storage/index.web.d.ts +5 -4
- package/dist/storage/index.web.js +11 -8
- package/dist/storage/upload/index.d.ts +4 -3
- package/dist/storage/upload/index.js +4 -2
- package/dist/storage/upload/index.native.d.ts +4 -3
- package/dist/storage/upload/index.native.js +4 -2
- package/dist/storage/upload/index.web.d.ts +2 -1
- package/dist/storage/upload/index.web.js +4 -2
- package/dist/{supabase-connector-C4YpH_l3.d.ts → supabase-connector-HMxBA9Kg.d.ts} +2 -2
- package/dist/sync/index.d.ts +155 -20
- package/dist/sync/index.js +13 -3
- package/dist/{types-CyvBaAl8.d.ts → types-6QHGELuY.d.ts} +4 -1
- package/dist/{types-Dv1uf0LZ.d.ts → types-B9MptP7E.d.ts} +7 -10
- package/dist/types-BhAEsJj-.d.ts +330 -0
- package/dist/{types-D0WcHrq6.d.ts → types-CGMibJKD.d.ts} +8 -0
- package/dist/{types-CpM2_LhU.d.ts → types-DqJnP50o.d.ts} +6 -1
- package/dist/{pol-attachment-queue-BE2HU3Us.d.ts → types-JCEhw2Lf.d.ts} +139 -346
- package/package.json +18 -4
- package/dist/chunk-654ERHA7.js +0 -1
- package/dist/chunk-BC2SRII2.js.map +0 -1
- package/dist/chunk-C5ODS3XH.js.map +0 -1
- package/dist/chunk-CAB26E6F.js.map +0 -1
- package/dist/chunk-CACKC6XG.js.map +0 -1
- package/dist/chunk-FNYQFILT.js +0 -44
- package/dist/chunk-FNYQFILT.js.map +0 -1
- package/dist/chunk-JCGOZVWL.js.map +0 -1
- package/dist/chunk-P4HZA6ZT.js.map +0 -1
- package/dist/chunk-RBPWEOIV.js +0 -358
- package/dist/chunk-RBPWEOIV.js.map +0 -1
- package/dist/chunk-RE5HWLCB.js.map +0 -1
- package/dist/chunk-XAEII4ZX.js.map +0 -1
- package/dist/chunk-Z6VOBGTU.js.map +0 -1
- /package/dist/{chunk-XOY2CJ67.js.map → chunk-4F5B5CZ7.js.map} +0 -0
- /package/dist/{chunk-654ERHA7.js.map → chunk-5WRI5ZAA.js.map} +0 -0
- /package/dist/{chunk-A4IBBWGO.js.map → chunk-RALHHPTU.js.map} +0 -0
- /package/dist/{chunk-QREWE3NR.js.map → chunk-YONQYTVH.js.map} +0 -0
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
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
|
|
117
|
-
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
127
|
-
|
|
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.
|
|
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
|
|
134
|
-
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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.
|
|
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.
|
|
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
|
|
700
|
+
const removedIds = [];
|
|
573
701
|
this._failedTransactions = this._failedTransactions.filter((failure) => {
|
|
574
702
|
const hasSuccessfulEntry = failure.entries.some((entry) => entryIdSet.has(entry.id));
|
|
575
|
-
|
|
703
|
+
if (hasSuccessfulEntry) {
|
|
704
|
+
removedIds.push(failure.id);
|
|
705
|
+
return false;
|
|
706
|
+
}
|
|
707
|
+
return true;
|
|
576
708
|
});
|
|
577
|
-
if (
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
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
|
|
648
|
-
this._failedTransactions = this._failedTransactions.filter((f) =>
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
*
|
|
747
|
-
* This prevents race conditions from multiple rapid persist calls.
|
|
889
|
+
* Insert a completed transaction into SQLite.
|
|
748
890
|
*/
|
|
749
|
-
|
|
750
|
-
if (this.
|
|
751
|
-
|
|
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
|
-
*
|
|
907
|
+
* Delete a completed transaction from SQLite.
|
|
760
908
|
*/
|
|
761
|
-
async
|
|
909
|
+
async _deleteCompletedTransaction(id) {
|
|
910
|
+
if (!this._db) return;
|
|
762
911
|
try {
|
|
763
|
-
await
|
|
912
|
+
await this._db.execute(`DELETE FROM ${LOCAL_TABLE_NAMES.COMPLETED_TRANSACTIONS} WHERE id = ?`, [id]);
|
|
913
|
+
this._resetPersistenceErrors();
|
|
764
914
|
} catch (err) {
|
|
765
|
-
this.
|
|
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-
|
|
1574
|
+
//# sourceMappingURL=chunk-2RDWLXJW.js.map
|