@pol-studios/powersync 1.0.25 → 1.0.32
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 +0 -1
- 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-ChCXW-EV.d.ts → background-sync-BujnI3IR.d.ts} +1 -1
- package/dist/{chunk-55DKCJV4.js → chunk-2RDWLXJW.js} +408 -78
- 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-BGBQYQV3.js → chunk-65A3SYJZ.js} +193 -299
- 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-YSTEESEG.js → chunk-74TBHWJ4.js} +122 -11
- package/dist/chunk-74TBHWJ4.js.map +1 -0
- 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-VB737IVN.js → chunk-P4D6BQ4X.js} +328 -706
- 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-WGHNIAF7.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 -2
- 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 +88 -46
- package/dist/index.native.d.ts +18 -14
- package/dist/index.native.js +93 -44
- package/dist/index.web.d.ts +17 -14
- package/dist/index.web.js +88 -46
- 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 +319 -124
- package/dist/provider/index.js +21 -16
- 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-D2oIl2t8.d.ts → supabase-connector-HMxBA9Kg.d.ts} +23 -25
- package/dist/sync/index.d.ts +183 -11
- package/dist/sync/index.js +13 -3
- package/dist/{types-CyvBaAl8.d.ts → types-6QHGELuY.d.ts} +4 -1
- package/dist/{types-CDqWh56B.d.ts → types-B9MptP7E.d.ts} +13 -1
- package/dist/types-BhAEsJj-.d.ts +330 -0
- package/dist/{types-D0WcHrq6.d.ts → types-CGMibJKD.d.ts} +8 -0
- package/dist/{types-DiBvmGEi.d.ts → types-DqJnP50o.d.ts} +22 -24
- package/dist/{pol-attachment-queue-BE2HU3Us.d.ts → types-JCEhw2Lf.d.ts} +139 -346
- package/package.json +18 -4
- package/dist/chunk-24RDMMCL.js +0 -44
- package/dist/chunk-24RDMMCL.js.map +0 -1
- package/dist/chunk-55DKCJV4.js.map +0 -1
- package/dist/chunk-654ERHA7.js +0 -1
- package/dist/chunk-BGBQYQV3.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-P4HZA6ZT.js.map +0 -1
- package/dist/chunk-TIFL2KWE.js +0 -358
- package/dist/chunk-TIFL2KWE.js.map +0 -1
- package/dist/chunk-VB737IVN.js.map +0 -1
- package/dist/chunk-XAEII4ZX.js.map +0 -1
- package/dist/chunk-YSTEESEG.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-WGHNIAF7.js.map → chunk-YONQYTVH.js.map} +0 -0
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
DEFAULT_SYNC_STATUS
|
|
5
|
-
} from "./chunk-24RDMMCL.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;
|
|
@@ -73,10 +108,89 @@ var SyncStatusTracker = class _SyncStatusTracker {
|
|
|
73
108
|
lastUpdated: /* @__PURE__ */ new Date()
|
|
74
109
|
};
|
|
75
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
|
+
}
|
|
76
187
|
// ─── Initialization ────────────────────────────────────────────────────────
|
|
77
188
|
/**
|
|
78
189
|
* Initialize the tracker by loading persisted state.
|
|
79
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.
|
|
80
194
|
*/
|
|
81
195
|
async init() {
|
|
82
196
|
try {
|
|
@@ -105,46 +219,81 @@ var SyncStatusTracker = class _SyncStatusTracker {
|
|
|
105
219
|
} catch (err) {
|
|
106
220
|
this.logger.warn("[StatusTracker] Failed to load auto-offline flag:", err);
|
|
107
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;
|
|
108
241
|
try {
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
|
|
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");
|
|
122
256
|
} catch (err) {
|
|
123
|
-
this.
|
|
257
|
+
this._trackPersistenceError("loadCompletedTransactions", err);
|
|
258
|
+
this.logger.warn("[StatusTracker] Failed to load completed transactions from SQLite:", err);
|
|
124
259
|
}
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Load failed transactions from SQLite local-only table.
|
|
263
|
+
*/
|
|
264
|
+
async _loadFailedTransactionsFromDb() {
|
|
265
|
+
if (!this._db) return;
|
|
125
266
|
try {
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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");
|
|
144
293
|
} catch (err) {
|
|
145
|
-
this.
|
|
294
|
+
this._trackPersistenceError("loadFailedTransactions", err);
|
|
295
|
+
this.logger.warn("[StatusTracker] Failed to load failed transactions from SQLite:", err);
|
|
146
296
|
}
|
|
147
|
-
this.cleanupStaleFailures();
|
|
148
297
|
}
|
|
149
298
|
/**
|
|
150
299
|
* Dispose the tracker and clear timers.
|
|
@@ -194,6 +343,73 @@ var SyncStatusTracker = class _SyncStatusTracker {
|
|
|
194
343
|
getSyncMode() {
|
|
195
344
|
return this._state.syncMode;
|
|
196
345
|
}
|
|
346
|
+
/**
|
|
347
|
+
* Get the reason why uploads are currently blocked.
|
|
348
|
+
* Returns 'none' if uploads are ready to proceed.
|
|
349
|
+
*
|
|
350
|
+
* Priority order (first match wins):
|
|
351
|
+
* 1. offline_mode - User explicitly chose offline mode
|
|
352
|
+
* 2. auto_offline - System went offline automatically (network loss with auto-offline flag)
|
|
353
|
+
* 3. pull_only_mode - Pull-only mode active
|
|
354
|
+
* 4. network_unreachable - No internet connection
|
|
355
|
+
* 5. disconnected - PowerSync not connected
|
|
356
|
+
* 6. connecting - PowerSync is connecting
|
|
357
|
+
* 7. uploading - Actively uploading
|
|
358
|
+
* 8. none - Ready to sync
|
|
359
|
+
*/
|
|
360
|
+
getUploadBlockReason() {
|
|
361
|
+
const status = this._state.status;
|
|
362
|
+
const syncMode = this._state.syncMode;
|
|
363
|
+
if (syncMode === "offline") {
|
|
364
|
+
if (this._isAutoOffline) {
|
|
365
|
+
return "auto_offline";
|
|
366
|
+
}
|
|
367
|
+
return "offline_mode";
|
|
368
|
+
}
|
|
369
|
+
if (syncMode === "pull-only") {
|
|
370
|
+
return "pull_only_mode";
|
|
371
|
+
}
|
|
372
|
+
if (!this._networkReachable) {
|
|
373
|
+
return "network_unreachable";
|
|
374
|
+
}
|
|
375
|
+
if (!status.connected && !status.connecting) {
|
|
376
|
+
return "disconnected";
|
|
377
|
+
}
|
|
378
|
+
if (status.connecting) {
|
|
379
|
+
return "connecting";
|
|
380
|
+
}
|
|
381
|
+
if (status.uploading) {
|
|
382
|
+
return "uploading";
|
|
383
|
+
}
|
|
384
|
+
return "none";
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Get a human-readable description of why uploads are blocked.
|
|
388
|
+
* Returns null if uploads are not blocked.
|
|
389
|
+
*/
|
|
390
|
+
getUploadBlockDescription() {
|
|
391
|
+
const reason = this.getUploadBlockReason();
|
|
392
|
+
switch (reason) {
|
|
393
|
+
case "offline_mode":
|
|
394
|
+
return "Offline mode is enabled";
|
|
395
|
+
case "auto_offline":
|
|
396
|
+
return "Went offline due to network loss";
|
|
397
|
+
case "pull_only_mode":
|
|
398
|
+
return "Pull-only mode is active";
|
|
399
|
+
case "network_unreachable":
|
|
400
|
+
return "No internet connection";
|
|
401
|
+
case "disconnected":
|
|
402
|
+
return "Not connected to sync service";
|
|
403
|
+
case "connecting":
|
|
404
|
+
return "Connecting to sync service";
|
|
405
|
+
case "uploading":
|
|
406
|
+
return "Upload in progress";
|
|
407
|
+
case "none":
|
|
408
|
+
return null;
|
|
409
|
+
default:
|
|
410
|
+
return null;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
197
413
|
/**
|
|
198
414
|
* Check if uploads are allowed based on current sync mode and network reachability.
|
|
199
415
|
*/
|
|
@@ -418,34 +634,42 @@ var SyncStatusTracker = class _SyncStatusTracker {
|
|
|
418
634
|
const existingIds = f.entries.map((e) => e.id).sort().join(",");
|
|
419
635
|
return existingIds === entryIds;
|
|
420
636
|
});
|
|
637
|
+
let failedTransaction;
|
|
421
638
|
if (existingIndex !== -1) {
|
|
422
639
|
const existing = this._failedTransactions[existingIndex];
|
|
423
|
-
|
|
640
|
+
const newRetryCount = existing.retryCount + 1;
|
|
641
|
+
failedTransaction = {
|
|
424
642
|
...existing,
|
|
425
643
|
error,
|
|
426
|
-
retryCount:
|
|
644
|
+
retryCount: newRetryCount,
|
|
427
645
|
lastFailedAt: now,
|
|
428
646
|
isPermanent
|
|
429
647
|
};
|
|
648
|
+
this._failedTransactions[existingIndex] = failedTransaction;
|
|
430
649
|
} else {
|
|
431
|
-
const
|
|
650
|
+
const retryCount = preserveMetadata?.retryCount ?? 1;
|
|
651
|
+
failedTransaction = {
|
|
432
652
|
id: generateFailureId(normalizedEntries),
|
|
433
653
|
entries: normalizedEntries,
|
|
434
654
|
error,
|
|
435
|
-
retryCount
|
|
655
|
+
retryCount,
|
|
436
656
|
firstFailedAt: preserveMetadata?.firstFailedAt ?? now,
|
|
437
657
|
lastFailedAt: now,
|
|
438
658
|
isPermanent,
|
|
439
659
|
affectedEntityIds,
|
|
440
660
|
affectedTables
|
|
441
661
|
};
|
|
442
|
-
this._failedTransactions.push(
|
|
662
|
+
this._failedTransactions.push(failedTransaction);
|
|
443
663
|
if (this._failedTransactions.length > this._maxStoredFailures) {
|
|
444
664
|
this._failedTransactions.sort((a, b) => a.firstFailedAt.getTime() - b.firstFailedAt.getTime());
|
|
665
|
+
const removed = this._failedTransactions.slice(0, this._failedTransactions.length - this._maxStoredFailures);
|
|
445
666
|
this._failedTransactions = this._failedTransactions.slice(-this._maxStoredFailures);
|
|
667
|
+
for (const r of removed) {
|
|
668
|
+
void this._deleteFailedTransaction(r.id);
|
|
669
|
+
}
|
|
446
670
|
}
|
|
447
671
|
}
|
|
448
|
-
this.
|
|
672
|
+
void this._insertFailedTransaction(failedTransaction);
|
|
449
673
|
this._notifyFailureListeners();
|
|
450
674
|
this._notifyListeners();
|
|
451
675
|
}
|
|
@@ -456,20 +680,40 @@ var SyncStatusTracker = class _SyncStatusTracker {
|
|
|
456
680
|
const initialLength = this._failedTransactions.length;
|
|
457
681
|
this._failedTransactions = this._failedTransactions.filter((f) => f.id !== failureId);
|
|
458
682
|
if (this._failedTransactions.length !== initialLength) {
|
|
459
|
-
this.
|
|
683
|
+
void this._deleteFailedTransaction(failureId);
|
|
460
684
|
this._notifyFailureListeners();
|
|
461
685
|
this._notifyListeners();
|
|
462
686
|
}
|
|
463
687
|
}
|
|
464
688
|
/**
|
|
465
|
-
* Clear
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
689
|
+
* Clear failures for successfully synced entries.
|
|
690
|
+
* Removes any failed transaction that contains entries with the given IDs.
|
|
691
|
+
*
|
|
692
|
+
* This is called when `onTransactionSuccess` fires - if any entry in a failed
|
|
693
|
+
* transaction has now succeeded, we remove that entire failure record.
|
|
694
|
+
*
|
|
695
|
+
* @param entryIds - Array of CrudEntry.id values that succeeded
|
|
696
|
+
*/
|
|
697
|
+
clearSuccessfulEntries(entryIds) {
|
|
698
|
+
if (entryIds.length === 0 || this._failedTransactions.length === 0) return;
|
|
699
|
+
const entryIdSet = new Set(entryIds);
|
|
700
|
+
const removedIds = [];
|
|
701
|
+
this._failedTransactions = this._failedTransactions.filter((failure) => {
|
|
702
|
+
const hasSuccessfulEntry = failure.entries.some((entry) => entryIdSet.has(entry.id));
|
|
703
|
+
if (hasSuccessfulEntry) {
|
|
704
|
+
removedIds.push(failure.id);
|
|
705
|
+
return false;
|
|
706
|
+
}
|
|
707
|
+
return true;
|
|
708
|
+
});
|
|
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
|
+
}
|
|
714
|
+
this._notifyFailureListeners();
|
|
715
|
+
this._notifyListeners();
|
|
716
|
+
}
|
|
473
717
|
}
|
|
474
718
|
/**
|
|
475
719
|
* Remove a failed transaction from tracking and return its entries.
|
|
@@ -488,8 +732,8 @@ var SyncStatusTracker = class _SyncStatusTracker {
|
|
|
488
732
|
return null;
|
|
489
733
|
}
|
|
490
734
|
this._failedTransactions = this._failedTransactions.filter((f) => f.id !== failureId);
|
|
735
|
+
void this._deleteFailedTransaction(failureId);
|
|
491
736
|
this._notifyFailureListeners();
|
|
492
|
-
this._schedulePersist();
|
|
493
737
|
this.logger.info("[StatusTracker] Retrieved failure for retry:", failureId, "entries:", failure.entries.length);
|
|
494
738
|
return failure.entries;
|
|
495
739
|
}
|
|
@@ -533,11 +777,19 @@ var SyncStatusTracker = class _SyncStatusTracker {
|
|
|
533
777
|
*/
|
|
534
778
|
cleanupStaleFailures() {
|
|
535
779
|
const cutoff = Date.now() - this._failureTTLMs;
|
|
536
|
-
const
|
|
537
|
-
this._failedTransactions = this._failedTransactions.filter((f) =>
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
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
|
+
}
|
|
541
793
|
this._notifyFailureListeners();
|
|
542
794
|
this._notifyListeners();
|
|
543
795
|
}
|
|
@@ -563,7 +815,8 @@ var SyncStatusTracker = class _SyncStatusTracker {
|
|
|
563
815
|
if (this._completedTransactions.length > MAX_COMPLETED_TRANSACTIONS) {
|
|
564
816
|
this._completedTransactions = this._completedTransactions.slice(0, MAX_COMPLETED_TRANSACTIONS);
|
|
565
817
|
}
|
|
566
|
-
this.
|
|
818
|
+
void this._insertCompletedTransaction(completed);
|
|
819
|
+
void this._enforceCompletedTransactionsLimit();
|
|
567
820
|
this._notifyCompletedListeners();
|
|
568
821
|
this.logger.debug(`[StatusTracker] Recorded completed transaction: ${completed.id} (${entries.length} entries)`);
|
|
569
822
|
}
|
|
@@ -579,7 +832,7 @@ var SyncStatusTracker = class _SyncStatusTracker {
|
|
|
579
832
|
clearCompletedHistory() {
|
|
580
833
|
if (this._completedTransactions.length === 0) return;
|
|
581
834
|
this._completedTransactions = [];
|
|
582
|
-
this.
|
|
835
|
+
void this._deleteAllCompletedTransactions();
|
|
583
836
|
this._notifyCompletedListeners();
|
|
584
837
|
this.logger.debug("[StatusTracker] Cleared completed transaction history");
|
|
585
838
|
}
|
|
@@ -590,7 +843,7 @@ var SyncStatusTracker = class _SyncStatusTracker {
|
|
|
590
843
|
const initialLength = this._completedTransactions.length;
|
|
591
844
|
this._completedTransactions = this._completedTransactions.filter((c) => c.id !== completedId);
|
|
592
845
|
if (this._completedTransactions.length !== initialLength) {
|
|
593
|
-
this.
|
|
846
|
+
void this._deleteCompletedTransaction(completedId);
|
|
594
847
|
this._notifyCompletedListeners();
|
|
595
848
|
this.logger.debug("[StatusTracker] Cleared completed transaction:", completedId);
|
|
596
849
|
}
|
|
@@ -631,27 +884,100 @@ var SyncStatusTracker = class _SyncStatusTracker {
|
|
|
631
884
|
return this._lastNotificationTime;
|
|
632
885
|
}
|
|
633
886
|
// ─── Private Methods ───────────────────────────────────────────────────────
|
|
887
|
+
// ─── SQLite Persistence Methods ─────────────────────────────────────────────
|
|
634
888
|
/**
|
|
635
|
-
*
|
|
636
|
-
* This prevents race conditions from multiple rapid persist calls.
|
|
889
|
+
* Insert a completed transaction into SQLite.
|
|
637
890
|
*/
|
|
638
|
-
|
|
639
|
-
if (this.
|
|
640
|
-
|
|
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);
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
/**
|
|
907
|
+
* Delete a completed transaction from SQLite.
|
|
908
|
+
*/
|
|
909
|
+
async _deleteCompletedTransaction(id) {
|
|
910
|
+
if (!this._db) return;
|
|
911
|
+
try {
|
|
912
|
+
await this._db.execute(`DELETE FROM ${LOCAL_TABLE_NAMES.COMPLETED_TRANSACTIONS} WHERE id = ?`, [id]);
|
|
913
|
+
this._resetPersistenceErrors();
|
|
914
|
+
} catch (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);
|
|
641
930
|
}
|
|
642
|
-
this._persistDebounceTimer = setTimeout(() => {
|
|
643
|
-
this._persistDebounceTimer = null;
|
|
644
|
-
this._persistTransactions();
|
|
645
|
-
}, 100);
|
|
646
931
|
}
|
|
647
932
|
/**
|
|
648
|
-
*
|
|
933
|
+
* Insert a failed transaction into SQLite.
|
|
649
934
|
*/
|
|
650
|
-
async
|
|
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;
|
|
651
956
|
try {
|
|
652
|
-
await
|
|
957
|
+
await this._db.execute(`DELETE FROM ${LOCAL_TABLE_NAMES.FAILED_TRANSACTIONS} WHERE id = ?`, [id]);
|
|
958
|
+
this._resetPersistenceErrors();
|
|
653
959
|
} catch (err) {
|
|
654
|
-
this.
|
|
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);
|
|
655
981
|
}
|
|
656
982
|
}
|
|
657
983
|
_hasStatusChanged(newStatus) {
|
|
@@ -1237,8 +1563,12 @@ var HealthMonitor = class {
|
|
|
1237
1563
|
};
|
|
1238
1564
|
|
|
1239
1565
|
export {
|
|
1566
|
+
DEFAULT_SYNC_STATUS,
|
|
1567
|
+
DEFAULT_CONNECTION_HEALTH,
|
|
1568
|
+
DEFAULT_SYNC_METRICS,
|
|
1569
|
+
DEFAULT_SYNC_CONFIG,
|
|
1240
1570
|
SyncStatusTracker,
|
|
1241
1571
|
MetricsCollector,
|
|
1242
1572
|
HealthMonitor
|
|
1243
1573
|
};
|
|
1244
|
-
//# sourceMappingURL=chunk-
|
|
1574
|
+
//# sourceMappingURL=chunk-2RDWLXJW.js.map
|