@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
package/dist/chunk-RBPWEOIV.js
DELETED
|
@@ -1,358 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createPolAttachmentQueue
|
|
3
|
-
} from "./chunk-CACKC6XG.js";
|
|
4
|
-
import {
|
|
5
|
-
DEFAULT_SYNC_STATUS
|
|
6
|
-
} from "./chunk-FNYQFILT.js";
|
|
7
|
-
import {
|
|
8
|
-
SupabaseConnector
|
|
9
|
-
} from "./chunk-BC2SRII2.js";
|
|
10
|
-
import {
|
|
11
|
-
classifySupabaseError,
|
|
12
|
-
createSyncError
|
|
13
|
-
} from "./chunk-I2AYMY5O.js";
|
|
14
|
-
|
|
15
|
-
// src/react/provider.tsx
|
|
16
|
-
import { useEffect, useState, useRef, useMemo } from "react";
|
|
17
|
-
|
|
18
|
-
// src/react/contexts.ts
|
|
19
|
-
import { createContext } from "react";
|
|
20
|
-
var PowerSyncContext = createContext(null);
|
|
21
|
-
PowerSyncContext.displayName = "PowerSyncContext";
|
|
22
|
-
var SyncStatusContext = createContext(null);
|
|
23
|
-
SyncStatusContext.displayName = "SyncStatusContext";
|
|
24
|
-
var AttachmentQueueContext = createContext(null);
|
|
25
|
-
AttachmentQueueContext.displayName = "AttachmentQueueContext";
|
|
26
|
-
|
|
27
|
-
// src/react/provider.tsx
|
|
28
|
-
import { jsx } from "react/jsx-runtime";
|
|
29
|
-
var defaultLogger = {
|
|
30
|
-
debug: (...args) => console.debug("[PowerSync]", ...args),
|
|
31
|
-
info: (...args) => console.info("[PowerSync]", ...args),
|
|
32
|
-
warn: (...args) => console.warn("[PowerSync]", ...args),
|
|
33
|
-
error: (...args) => console.error("[PowerSync]", ...args)
|
|
34
|
-
};
|
|
35
|
-
function SlimPowerSyncProvider({
|
|
36
|
-
config,
|
|
37
|
-
auth,
|
|
38
|
-
storage,
|
|
39
|
-
attachments,
|
|
40
|
-
platform: customPlatform,
|
|
41
|
-
onError,
|
|
42
|
-
onReady,
|
|
43
|
-
onSyncStatusChange,
|
|
44
|
-
children
|
|
45
|
-
}) {
|
|
46
|
-
const {
|
|
47
|
-
schema,
|
|
48
|
-
powerSyncUrl,
|
|
49
|
-
dbFilename = "powersync.db"
|
|
50
|
-
} = config;
|
|
51
|
-
const [db, setDb] = useState(null);
|
|
52
|
-
const [platform, setPlatform] = useState(customPlatform ?? null);
|
|
53
|
-
const [isReady, setIsReady] = useState(false);
|
|
54
|
-
const [isInitializing, setIsInitializing] = useState(true);
|
|
55
|
-
const [error, setError] = useState(null);
|
|
56
|
-
const [syncStatus, setSyncStatus] = useState(DEFAULT_SYNC_STATUS);
|
|
57
|
-
const [attachmentQueue, setAttachmentQueue] = useState(null);
|
|
58
|
-
const [attachmentQueueReady, setAttachmentQueueReady] = useState(!attachments);
|
|
59
|
-
const connectorRef = useRef(null);
|
|
60
|
-
const initializingRef = useRef(false);
|
|
61
|
-
const dbClosedRef = useRef(false);
|
|
62
|
-
const onErrorRef = useRef(onError);
|
|
63
|
-
const onReadyRef = useRef(onReady);
|
|
64
|
-
const onSyncStatusChangeRef = useRef(onSyncStatusChange);
|
|
65
|
-
useEffect(() => {
|
|
66
|
-
onErrorRef.current = onError;
|
|
67
|
-
}, [onError]);
|
|
68
|
-
useEffect(() => {
|
|
69
|
-
onReadyRef.current = onReady;
|
|
70
|
-
}, [onReady]);
|
|
71
|
-
useEffect(() => {
|
|
72
|
-
onSyncStatusChangeRef.current = onSyncStatusChange;
|
|
73
|
-
}, [onSyncStatusChange]);
|
|
74
|
-
const logger = platform?.logger ?? defaultLogger;
|
|
75
|
-
useEffect(() => {
|
|
76
|
-
if (customPlatform) {
|
|
77
|
-
setPlatform(customPlatform);
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
import("./platform/index.native.js").then(({
|
|
81
|
-
createNativePlatformAdapter
|
|
82
|
-
}) => {
|
|
83
|
-
setPlatform(createNativePlatformAdapter(defaultLogger));
|
|
84
|
-
}).catch((err) => {
|
|
85
|
-
logger.error("Failed to load platform adapter:", err);
|
|
86
|
-
setError(new Error("Failed to load platform adapter"));
|
|
87
|
-
setIsInitializing(false);
|
|
88
|
-
});
|
|
89
|
-
}, [customPlatform]);
|
|
90
|
-
useEffect(() => {
|
|
91
|
-
if (!platform || initializingRef.current) return;
|
|
92
|
-
initializingRef.current = true;
|
|
93
|
-
const controller = {
|
|
94
|
-
cancelled: false
|
|
95
|
-
};
|
|
96
|
-
const initDatabase = async () => {
|
|
97
|
-
try {
|
|
98
|
-
dbClosedRef.current = false;
|
|
99
|
-
logger.info("Initializing database...");
|
|
100
|
-
const database = await platform.createDatabase({
|
|
101
|
-
dbFilename,
|
|
102
|
-
schema
|
|
103
|
-
});
|
|
104
|
-
if (controller.cancelled) {
|
|
105
|
-
await database.close();
|
|
106
|
-
initializingRef.current = false;
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
logger.info("Database initialized");
|
|
110
|
-
setDb(database);
|
|
111
|
-
setIsReady(true);
|
|
112
|
-
setIsInitializing(false);
|
|
113
|
-
onReadyRef.current?.();
|
|
114
|
-
} catch (err_0) {
|
|
115
|
-
const initError = err_0 instanceof Error ? err_0 : new Error(String(err_0));
|
|
116
|
-
logger.error("Initialization failed:", initError);
|
|
117
|
-
if (!controller.cancelled) {
|
|
118
|
-
setError(initError);
|
|
119
|
-
setIsInitializing(false);
|
|
120
|
-
const syncError = createSyncError(classifySupabaseError(initError), initError.message);
|
|
121
|
-
onErrorRef.current?.(syncError);
|
|
122
|
-
}
|
|
123
|
-
initializingRef.current = false;
|
|
124
|
-
}
|
|
125
|
-
};
|
|
126
|
-
initDatabase();
|
|
127
|
-
return () => {
|
|
128
|
-
controller.cancelled = true;
|
|
129
|
-
initializingRef.current = false;
|
|
130
|
-
};
|
|
131
|
-
}, [platform, dbFilename, schema, logger]);
|
|
132
|
-
useEffect(() => {
|
|
133
|
-
if (!db || !platform) return;
|
|
134
|
-
const connectPowerSync = async () => {
|
|
135
|
-
try {
|
|
136
|
-
const session = await auth.getSession();
|
|
137
|
-
if (!session) {
|
|
138
|
-
logger.debug("No session, skipping connect");
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
if (connectorRef.current) {
|
|
142
|
-
connectorRef.current.destroy();
|
|
143
|
-
connectorRef.current = null;
|
|
144
|
-
}
|
|
145
|
-
const connector = new SupabaseConnector({
|
|
146
|
-
supabaseClient: {
|
|
147
|
-
auth: {
|
|
148
|
-
getSession: async () => {
|
|
149
|
-
const s = await auth.getSession();
|
|
150
|
-
return {
|
|
151
|
-
data: {
|
|
152
|
-
session: s ? {
|
|
153
|
-
access_token: s.accessToken,
|
|
154
|
-
expires_at: s.expiresAt ? s.expiresAt.getTime() / 1e3 : void 0,
|
|
155
|
-
user: s.user
|
|
156
|
-
} : null
|
|
157
|
-
},
|
|
158
|
-
error: null
|
|
159
|
-
};
|
|
160
|
-
},
|
|
161
|
-
refreshSession: async () => {
|
|
162
|
-
const s_0 = await auth.refreshSession();
|
|
163
|
-
return {
|
|
164
|
-
data: {
|
|
165
|
-
session: {
|
|
166
|
-
access_token: s_0.accessToken,
|
|
167
|
-
expires_at: s_0.expiresAt ? s_0.expiresAt.getTime() / 1e3 : void 0,
|
|
168
|
-
user: s_0.user
|
|
169
|
-
}
|
|
170
|
-
},
|
|
171
|
-
error: null
|
|
172
|
-
};
|
|
173
|
-
},
|
|
174
|
-
onAuthStateChange: (cb) => {
|
|
175
|
-
const unsub = auth.onAuthStateChange((s_1) => {
|
|
176
|
-
cb("TOKEN_REFRESHED", s_1 ? {
|
|
177
|
-
access_token: s_1.accessToken,
|
|
178
|
-
expires_at: s_1.expiresAt ? s_1.expiresAt.getTime() / 1e3 : void 0,
|
|
179
|
-
user: s_1.user
|
|
180
|
-
} : null);
|
|
181
|
-
});
|
|
182
|
-
return {
|
|
183
|
-
data: {
|
|
184
|
-
subscription: {
|
|
185
|
-
unsubscribe: unsub
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
},
|
|
192
|
-
powerSyncUrl,
|
|
193
|
-
logger,
|
|
194
|
-
onTransactionFailure: (_entries, err_2, classified) => {
|
|
195
|
-
const syncError_1 = createSyncError(classified, err_2.message);
|
|
196
|
-
onErrorRef.current?.(syncError_1);
|
|
197
|
-
}
|
|
198
|
-
});
|
|
199
|
-
connectorRef.current = connector;
|
|
200
|
-
logger.info("Connecting to PowerSync...");
|
|
201
|
-
await db.connect(connector);
|
|
202
|
-
logger.info("Connected to PowerSync");
|
|
203
|
-
} catch (err_1) {
|
|
204
|
-
const connectError = err_1 instanceof Error ? err_1 : new Error(String(err_1));
|
|
205
|
-
logger.error("Connection failed:", connectError);
|
|
206
|
-
const syncError_0 = createSyncError(classifySupabaseError(connectError), connectError.message);
|
|
207
|
-
onErrorRef.current?.(syncError_0);
|
|
208
|
-
}
|
|
209
|
-
};
|
|
210
|
-
connectPowerSync();
|
|
211
|
-
const unsubscribe = auth.onAuthStateChange(async (session_0) => {
|
|
212
|
-
if (session_0 && db && !dbClosedRef.current) {
|
|
213
|
-
logger.debug("Session changed, reconnecting...");
|
|
214
|
-
await connectPowerSync();
|
|
215
|
-
} else if (!session_0 && db) {
|
|
216
|
-
logger.debug("Session cleared, disconnecting...");
|
|
217
|
-
await db.disconnect();
|
|
218
|
-
}
|
|
219
|
-
});
|
|
220
|
-
return () => {
|
|
221
|
-
unsubscribe();
|
|
222
|
-
};
|
|
223
|
-
}, [db, auth, powerSyncUrl, platform, logger]);
|
|
224
|
-
useEffect(() => {
|
|
225
|
-
if (!db) return;
|
|
226
|
-
const unsubscribe_0 = db.registerListener({
|
|
227
|
-
statusChanged: (status) => {
|
|
228
|
-
const newStatus = {
|
|
229
|
-
connected: status?.connected ?? false,
|
|
230
|
-
connecting: status?.connecting ?? false,
|
|
231
|
-
hasSynced: status?.hasSynced ?? false,
|
|
232
|
-
lastSyncedAt: status?.lastSyncedAt ?? null,
|
|
233
|
-
uploading: status?.dataFlowStatus?.uploading ?? false,
|
|
234
|
-
downloading: status?.dataFlowStatus?.downloading ?? false,
|
|
235
|
-
downloadProgress: status?.downloadProgress ? {
|
|
236
|
-
current: status.downloadProgress.downloadedOperations ?? 0,
|
|
237
|
-
target: status.downloadProgress.totalOperations ?? 0,
|
|
238
|
-
percentage: status.downloadProgress.downloadedFraction ? status.downloadProgress.downloadedFraction * 100 : 0
|
|
239
|
-
} : null,
|
|
240
|
-
failedTransactions: [],
|
|
241
|
-
hasUploadErrors: false,
|
|
242
|
-
permanentErrorCount: 0
|
|
243
|
-
};
|
|
244
|
-
setSyncStatus(newStatus);
|
|
245
|
-
onSyncStatusChangeRef.current?.(newStatus);
|
|
246
|
-
}
|
|
247
|
-
});
|
|
248
|
-
return unsubscribe_0;
|
|
249
|
-
}, [db]);
|
|
250
|
-
useEffect(() => {
|
|
251
|
-
if (!db || !platform || !attachments || !storage) {
|
|
252
|
-
setAttachmentQueueReady(!attachments);
|
|
253
|
-
return;
|
|
254
|
-
}
|
|
255
|
-
const initQueue = async () => {
|
|
256
|
-
try {
|
|
257
|
-
logger.info("Initializing attachment queue...");
|
|
258
|
-
const queue = await createPolAttachmentQueue(db, platform, {
|
|
259
|
-
bucket: attachments.bucket,
|
|
260
|
-
watchIds: attachments.watchIds,
|
|
261
|
-
skipDownload: attachments.skipDownload,
|
|
262
|
-
remoteStorage: {
|
|
263
|
-
downloadFile: async (path) => {
|
|
264
|
-
const result = await storage.download(path);
|
|
265
|
-
if (result.type === "file") {
|
|
266
|
-
return result.uri;
|
|
267
|
-
}
|
|
268
|
-
return result.data;
|
|
269
|
-
},
|
|
270
|
-
resolveBucket: storage.resolveBucket
|
|
271
|
-
},
|
|
272
|
-
onUploadComplete: attachments.onUploadComplete ? async (record) => {
|
|
273
|
-
attachments.onUploadComplete?.(record);
|
|
274
|
-
} : void 0,
|
|
275
|
-
onUploadFailed: attachments.onUploadFailed ? (record_0, err_4) => {
|
|
276
|
-
attachments.onUploadFailed?.(record_0, err_4);
|
|
277
|
-
} : void 0,
|
|
278
|
-
cache: attachments.maxCacheBytes ? {
|
|
279
|
-
maxSize: attachments.maxCacheBytes
|
|
280
|
-
} : void 0
|
|
281
|
-
});
|
|
282
|
-
setAttachmentQueue(queue);
|
|
283
|
-
setAttachmentQueueReady(true);
|
|
284
|
-
logger.info("Attachment queue initialized");
|
|
285
|
-
} catch (err_3) {
|
|
286
|
-
logger.error("Failed to initialize attachment queue:", err_3);
|
|
287
|
-
setAttachmentQueueReady(true);
|
|
288
|
-
}
|
|
289
|
-
};
|
|
290
|
-
initQueue();
|
|
291
|
-
}, [db, platform, attachments, storage, logger]);
|
|
292
|
-
useEffect(() => {
|
|
293
|
-
return () => {
|
|
294
|
-
dbClosedRef.current = true;
|
|
295
|
-
if (connectorRef.current) {
|
|
296
|
-
connectorRef.current.destroy();
|
|
297
|
-
connectorRef.current = null;
|
|
298
|
-
}
|
|
299
|
-
if (db) {
|
|
300
|
-
db.disconnect().catch(() => {
|
|
301
|
-
});
|
|
302
|
-
db.close().catch(() => {
|
|
303
|
-
});
|
|
304
|
-
}
|
|
305
|
-
};
|
|
306
|
-
}, [db]);
|
|
307
|
-
const powerSyncValue = useMemo(() => ({
|
|
308
|
-
db,
|
|
309
|
-
isReady,
|
|
310
|
-
isInitializing,
|
|
311
|
-
error,
|
|
312
|
-
platform
|
|
313
|
-
}), [db, isReady, isInitializing, error, platform]);
|
|
314
|
-
const syncStatusValue = useMemo(() => ({
|
|
315
|
-
status: syncStatus,
|
|
316
|
-
lastSyncedAt: syncStatus.lastSyncedAt
|
|
317
|
-
}), [syncStatus]);
|
|
318
|
-
const attachmentQueueValue = useMemo(() => ({
|
|
319
|
-
queue: attachmentQueue,
|
|
320
|
-
isReady: attachmentQueueReady
|
|
321
|
-
}), [attachmentQueue, attachmentQueueReady]);
|
|
322
|
-
return /* @__PURE__ */ jsx(PowerSyncContext.Provider, { value: powerSyncValue, children: /* @__PURE__ */ jsx(SyncStatusContext.Provider, { value: syncStatusValue, children: /* @__PURE__ */ jsx(AttachmentQueueContext.Provider, { value: attachmentQueueValue, children }) }) });
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
// src/react/index.ts
|
|
326
|
-
import { useContext } from "react";
|
|
327
|
-
function usePowerSyncContext() {
|
|
328
|
-
const context = useContext(PowerSyncContext);
|
|
329
|
-
if (!context) {
|
|
330
|
-
throw new Error("usePowerSyncContext must be used within a PowerSyncProvider");
|
|
331
|
-
}
|
|
332
|
-
return context;
|
|
333
|
-
}
|
|
334
|
-
function useSyncStatusContext() {
|
|
335
|
-
const context = useContext(SyncStatusContext);
|
|
336
|
-
if (!context) {
|
|
337
|
-
throw new Error("useSyncStatusContext must be used within a PowerSyncProvider");
|
|
338
|
-
}
|
|
339
|
-
return context;
|
|
340
|
-
}
|
|
341
|
-
function useAttachmentQueueContext() {
|
|
342
|
-
const context = useContext(AttachmentQueueContext);
|
|
343
|
-
if (!context) {
|
|
344
|
-
throw new Error("useAttachmentQueueContext must be used within a PowerSyncProvider");
|
|
345
|
-
}
|
|
346
|
-
return context;
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
export {
|
|
350
|
-
PowerSyncContext,
|
|
351
|
-
SyncStatusContext,
|
|
352
|
-
AttachmentQueueContext,
|
|
353
|
-
SlimPowerSyncProvider,
|
|
354
|
-
usePowerSyncContext,
|
|
355
|
-
useSyncStatusContext,
|
|
356
|
-
useAttachmentQueueContext
|
|
357
|
-
};
|
|
358
|
-
//# sourceMappingURL=chunk-RBPWEOIV.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/react/provider.tsx","../src/react/contexts.ts","../src/react/index.ts"],"sourcesContent":["/**\n * Slim PowerSyncProvider for @pol-studios/powersync\n *\n * A simplified, composable provider that:\n * - Has zero @pol/db coupling\n * - Uses AuthProvider interface for any auth backend\n * - Delegates domain logic to callbacks\n * - Is ~150 lines vs 1400+ in the legacy provider\n *\n * For backwards compatibility, the legacy OfflineDataProvider is still available.\n */\n\nimport React, { useEffect, useState, useRef, useMemo, useCallback } from 'react';\nimport type { PowerSyncProviderProps, SyncStatusContextValue } from './types';\nimport type { AbstractPowerSyncDatabase, SyncStatus, SyncError } from '../core/types';\nimport type { PlatformAdapter } from '../platform/types';\nimport { SupabaseConnector } from '../connector/supabase-connector';\nimport { createPolAttachmentQueue, type PolAttachmentQueue } from '../attachments/pol-attachment-queue';\nimport { classifySupabaseError, createSyncError } from '../core/errors';\nimport { PowerSyncContext, SyncStatusContext, AttachmentQueueContext } from './contexts';\nimport { DEFAULT_SYNC_STATUS } from '../provider/types';\n\n/**\n * Default console logger for when no platform is provided.\n */\nconst defaultLogger = {\n debug: (...args: unknown[]) => console.debug('[PowerSync]', ...args),\n info: (...args: unknown[]) => console.info('[PowerSync]', ...args),\n warn: (...args: unknown[]) => console.warn('[PowerSync]', ...args),\n error: (...args: unknown[]) => console.error('[PowerSync]', ...args)\n};\n\n/**\n * Slim PowerSyncProvider - minimal, composable, no domain coupling.\n *\n * This provider handles:\n * - Database initialization via platform adapter\n * - Connector setup using AuthProvider\n * - Attachment queue initialization (if configured)\n * - Sync status tracking\n * - Error classification and callbacks\n *\n * @example\n * ```tsx\n * import { PowerSyncProvider, createSupabaseAuth, createSupabaseStorage } from '@pol-studios/powersync/react';\n *\n * function App() {\n * const auth = useMemo(() => createSupabaseAuth(supabase), [supabase]);\n * const storage = useMemo(() => createSupabaseStorage({\n * client: supabase,\n * defaultBucket: 'attachments',\n * }), [supabase]);\n *\n * return (\n * <PowerSyncProvider\n * config={{ schema: AppSchema, powerSyncUrl: URL, dbFilename: 'app.db' }}\n * auth={auth}\n * storage={storage}\n * attachments={{ watch: { table: 'Media', idColumn: 'storagePath' } }}\n * onError={(error) => showToast(error.userMessage)}\n * onReady={() => console.log('Ready!')}\n * >\n * <MyApp />\n * </PowerSyncProvider>\n * );\n * }\n * ```\n */\nexport function SlimPowerSyncProvider({\n config,\n auth,\n storage,\n attachments,\n platform: customPlatform,\n onError,\n onReady,\n onSyncStatusChange,\n children\n}: PowerSyncProviderProps): React.ReactElement {\n const {\n schema,\n powerSyncUrl,\n dbFilename = 'powersync.db'\n } = config;\n\n // ─── State ───────────────────────────────────────────────────────────────────\n const [db, setDb] = useState<AbstractPowerSyncDatabase | null>(null);\n const [platform, setPlatform] = useState<PlatformAdapter | null>(customPlatform ?? null);\n const [isReady, setIsReady] = useState(false);\n const [isInitializing, setIsInitializing] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n const [syncStatus, setSyncStatus] = useState<SyncStatus>(DEFAULT_SYNC_STATUS);\n const [attachmentQueue, setAttachmentQueue] = useState<PolAttachmentQueue | null>(null);\n const [attachmentQueueReady, setAttachmentQueueReady] = useState(!attachments);\n\n // ─── Refs ────────────────────────────────────────────────────────────────────\n const connectorRef = useRef<SupabaseConnector | null>(null);\n const initializingRef = useRef(false);\n const dbClosedRef = useRef(false);\n\n // Callback refs to avoid stale closures\n const onErrorRef = useRef(onError);\n const onReadyRef = useRef(onReady);\n const onSyncStatusChangeRef = useRef(onSyncStatusChange);\n useEffect(() => {\n onErrorRef.current = onError;\n }, [onError]);\n useEffect(() => {\n onReadyRef.current = onReady;\n }, [onReady]);\n useEffect(() => {\n onSyncStatusChangeRef.current = onSyncStatusChange;\n }, [onSyncStatusChange]);\n const logger = platform?.logger ?? defaultLogger;\n\n // ─── Create Platform Adapter (lazy) ──────────────────────────────────────────\n useEffect(() => {\n if (customPlatform) {\n setPlatform(customPlatform);\n return;\n }\n\n // Lazy import native platform adapter\n import('../platform/index.native').then(({\n createNativePlatformAdapter\n }) => {\n setPlatform(createNativePlatformAdapter(defaultLogger));\n }).catch(err => {\n logger.error('Failed to load platform adapter:', err);\n setError(new Error('Failed to load platform adapter'));\n setIsInitializing(false);\n });\n }, [customPlatform]);\n\n // ─── Initialize Database ─────────────────────────────────────────────────────\n useEffect(() => {\n if (!platform || initializingRef.current) return;\n initializingRef.current = true;\n const controller = {\n cancelled: false\n };\n const initDatabase = async () => {\n try {\n dbClosedRef.current = false;\n logger.info('Initializing database...');\n const database = await platform.createDatabase({\n dbFilename,\n schema\n });\n if (controller.cancelled) {\n await database.close();\n initializingRef.current = false;\n return;\n }\n logger.info('Database initialized');\n setDb(database);\n setIsReady(true);\n setIsInitializing(false);\n onReadyRef.current?.();\n } catch (err_0) {\n const initError = err_0 instanceof Error ? err_0 : new Error(String(err_0));\n logger.error('Initialization failed:', initError);\n if (!controller.cancelled) {\n setError(initError);\n setIsInitializing(false);\n const syncError = createSyncError(classifySupabaseError(initError), initError.message);\n onErrorRef.current?.(syncError);\n }\n initializingRef.current = false;\n }\n };\n initDatabase();\n return () => {\n controller.cancelled = true;\n initializingRef.current = false;\n };\n }, [platform, dbFilename, schema, logger]);\n\n // ─── Connect to PowerSync ────────────────────────────────────────────────────\n useEffect(() => {\n if (!db || !platform) return;\n const connectPowerSync = async () => {\n try {\n // Check for existing session\n const session = await auth.getSession();\n if (!session) {\n logger.debug('No session, skipping connect');\n return;\n }\n\n // Destroy previous connector if exists\n if (connectorRef.current) {\n connectorRef.current.destroy();\n connectorRef.current = null;\n }\n\n // Create connector using AuthProvider\n const connector = new SupabaseConnector({\n supabaseClient: {\n auth: {\n getSession: async () => {\n const s = await auth.getSession();\n return {\n data: {\n session: s ? {\n access_token: s.accessToken,\n expires_at: s.expiresAt ? s.expiresAt.getTime() / 1000 : undefined,\n user: s.user\n } : null\n },\n error: null\n };\n },\n refreshSession: async () => {\n const s_0 = await auth.refreshSession();\n return {\n data: {\n session: {\n access_token: s_0.accessToken,\n expires_at: s_0.expiresAt ? s_0.expiresAt.getTime() / 1000 : undefined,\n user: s_0.user\n }\n },\n error: null\n };\n },\n onAuthStateChange: cb => {\n const unsub = auth.onAuthStateChange(s_1 => {\n cb('TOKEN_REFRESHED', s_1 ? {\n access_token: s_1.accessToken,\n expires_at: s_1.expiresAt ? s_1.expiresAt.getTime() / 1000 : undefined,\n user: s_1.user\n } : null);\n });\n return {\n data: {\n subscription: {\n unsubscribe: unsub\n }\n }\n };\n }\n }\n } as any,\n powerSyncUrl,\n logger,\n onTransactionFailure: (_entries, err_2, classified) => {\n const syncError_1 = createSyncError(classified, err_2.message);\n onErrorRef.current?.(syncError_1);\n }\n });\n connectorRef.current = connector;\n logger.info('Connecting to PowerSync...');\n await db.connect(connector);\n logger.info('Connected to PowerSync');\n } catch (err_1) {\n const connectError = err_1 instanceof Error ? err_1 : new Error(String(err_1));\n logger.error('Connection failed:', connectError);\n const syncError_0 = createSyncError(classifySupabaseError(connectError), connectError.message);\n onErrorRef.current?.(syncError_0);\n }\n };\n connectPowerSync();\n\n // Subscribe to auth changes for reconnection\n const unsubscribe = auth.onAuthStateChange(async session_0 => {\n if (session_0 && db && !dbClosedRef.current) {\n logger.debug('Session changed, reconnecting...');\n await connectPowerSync();\n } else if (!session_0 && db) {\n logger.debug('Session cleared, disconnecting...');\n await db.disconnect();\n }\n });\n return () => {\n unsubscribe();\n };\n }, [db, auth, powerSyncUrl, platform, logger]);\n\n // ─── Sync Status Listener ────────────────────────────────────────────────────\n useEffect(() => {\n if (!db) return;\n const unsubscribe_0 = db.registerListener({\n statusChanged: (status: any) => {\n const newStatus: SyncStatus = {\n connected: status?.connected ?? false,\n connecting: status?.connecting ?? false,\n hasSynced: status?.hasSynced ?? false,\n lastSyncedAt: status?.lastSyncedAt ?? null,\n uploading: status?.dataFlowStatus?.uploading ?? false,\n downloading: status?.dataFlowStatus?.downloading ?? false,\n downloadProgress: status?.downloadProgress ? {\n current: status.downloadProgress.downloadedOperations ?? 0,\n target: status.downloadProgress.totalOperations ?? 0,\n percentage: status.downloadProgress.downloadedFraction ? status.downloadProgress.downloadedFraction * 100 : 0\n } : null,\n failedTransactions: [],\n hasUploadErrors: false,\n permanentErrorCount: 0\n };\n setSyncStatus(newStatus);\n onSyncStatusChangeRef.current?.(newStatus);\n }\n });\n return unsubscribe_0;\n }, [db]);\n\n // ─── Initialize Attachment Queue ─────────────────────────────────────────────\n useEffect(() => {\n if (!db || !platform || !attachments || !storage) {\n setAttachmentQueueReady(!attachments);\n return;\n }\n const initQueue = async () => {\n try {\n logger.info('Initializing attachment queue...');\n const queue = await createPolAttachmentQueue(db as any, platform, {\n bucket: attachments.bucket,\n watchIds: attachments.watchIds,\n skipDownload: attachments.skipDownload,\n remoteStorage: {\n downloadFile: async path => {\n const result = await storage.download(path);\n if (result.type === 'file') {\n return result.uri;\n }\n return result.data;\n },\n resolveBucket: storage.resolveBucket\n },\n onUploadComplete: attachments.onUploadComplete ? async record => {\n attachments.onUploadComplete?.(record);\n } : undefined,\n onUploadFailed: attachments.onUploadFailed ? (record_0, err_4) => {\n attachments.onUploadFailed?.(record_0, err_4);\n } : undefined,\n cache: attachments.maxCacheBytes ? {\n maxSize: attachments.maxCacheBytes\n } : undefined\n });\n setAttachmentQueue(queue);\n setAttachmentQueueReady(true);\n logger.info('Attachment queue initialized');\n } catch (err_3) {\n logger.error('Failed to initialize attachment queue:', err_3);\n setAttachmentQueueReady(true); // Mark as ready (failed) so consumers can check\n }\n };\n initQueue();\n }, [db, platform, attachments, storage, logger]);\n\n // ─── Cleanup ─────────────────────────────────────────────────────────────────\n useEffect(() => {\n return () => {\n dbClosedRef.current = true;\n if (connectorRef.current) {\n connectorRef.current.destroy();\n connectorRef.current = null;\n }\n if (db) {\n db.disconnect().catch(() => {});\n db.close().catch(() => {});\n }\n };\n }, [db]);\n\n // ─── Context Values ──────────────────────────────────────────────────────────\n const powerSyncValue = useMemo(() => ({\n db,\n isReady,\n isInitializing,\n error,\n platform: platform!\n }), [db, isReady, isInitializing, error, platform]);\n const syncStatusValue = useMemo<SyncStatusContextValue>(() => ({\n status: syncStatus,\n lastSyncedAt: syncStatus.lastSyncedAt\n }), [syncStatus]);\n const attachmentQueueValue = useMemo(() => ({\n queue: attachmentQueue,\n isReady: attachmentQueueReady\n }), [attachmentQueue, attachmentQueueReady]);\n\n // ─── Render ──────────────────────────────────────────────────────────────────\n return <PowerSyncContext.Provider value={powerSyncValue}>\n <SyncStatusContext.Provider value={syncStatusValue}>\n <AttachmentQueueContext.Provider value={attachmentQueueValue}>\n {children}\n </AttachmentQueueContext.Provider>\n </SyncStatusContext.Provider>\n </PowerSyncContext.Provider>;\n}\n\n// Re-export as default for convenience\nexport { SlimPowerSyncProvider as PowerSyncProvider };","/**\n * React Contexts for the slim PowerSyncProvider\n *\n * This module creates the React contexts used by the slim provider.\n * These contexts are separate from the legacy provider contexts to\n * allow gradual migration.\n */\n\nimport { createContext } from 'react';\nimport type { PowerSyncContextValue, SyncStatusContextValue, AttachmentQueueContextValue } from './types';\n\n// ─── PowerSync Context ───────────────────────────────────────────────────────\n\n/**\n * Main context for PowerSync database instance and initialization state.\n *\n * Provides access to:\n * - PowerSync database instance\n * - Initialization state (isReady, isInitializing, error)\n * - Platform adapter\n *\n * @example\n * ```typescript\n * import { useContext } from 'react';\n * import { PowerSyncContext } from '@pol-studios/powersync/react';\n *\n * function MyComponent() {\n * const { db, isReady, error } = useContext(PowerSyncContext);\n * if (!isReady) return <Loading />;\n * if (error) return <Error message={error.message} />;\n * // Use db...\n * }\n * ```\n */\nexport const PowerSyncContext = createContext<PowerSyncContextValue | null>(null);\nPowerSyncContext.displayName = 'PowerSyncContext';\n\n// ─── Sync Status Context ─────────────────────────────────────────────────────\n\n/**\n * Context for sync status updates.\n *\n * Provides access to:\n * - Current sync status (connected, syncing, etc.)\n * - Last synced timestamp\n *\n * @example\n * ```typescript\n * import { useContext } from 'react';\n * import { SyncStatusContext } from '@pol-studios/powersync/react';\n *\n * function SyncIndicator() {\n * const { status, lastSyncedAt } = useContext(SyncStatusContext);\n * return (\n * <View>\n * {status.connected ? 'Connected' : 'Offline'}\n * {lastSyncedAt && <Text>Last synced: {lastSyncedAt.toISOString()}</Text>}\n * </View>\n * );\n * }\n * ```\n */\nexport const SyncStatusContext = createContext<SyncStatusContextValue | null>(null);\nSyncStatusContext.displayName = 'SyncStatusContext';\n\n// ─── Attachment Queue Context ────────────────────────────────────────────────\n\n/**\n * Context for the attachment queue (if configured).\n *\n * Provides access to:\n * - AttachmentQueue instance for checking stats, pausing/resuming downloads\n * - Ready state for the attachment queue\n *\n * @example\n * ```typescript\n * import { useContext } from 'react';\n * import { AttachmentQueueContext } from '@pol-studios/powersync/react';\n *\n * function AttachmentStatus() {\n * const { queue, isReady } = useContext(AttachmentQueueContext);\n * if (!isReady || !queue) return null;\n *\n * const stats = await queue.getStats();\n * return <Text>Downloaded: {stats.syncedCount}/{stats.totalExpected}</Text>;\n * }\n * ```\n */\nexport const AttachmentQueueContext = createContext<AttachmentQueueContextValue | null>(null);\nAttachmentQueueContext.displayName = 'AttachmentQueueContext';","/**\n * React Module for @pol-studios/powersync\n *\n * This module exports the new slim PowerSyncProvider and related contexts/types.\n * For backwards compatibility, the legacy OfflineDataProvider is still available\n * from the main package export.\n *\n * @example\n * ```typescript\n * import {\n * PowerSyncProvider,\n * usePowerSyncContext,\n * useSyncStatusContext,\n * useAttachmentQueueContext,\n * } from '@pol-studios/powersync/react';\n * ```\n */\n\n// ─── Provider ────────────────────────────────────────────────────────────────\n\nexport { SlimPowerSyncProvider, PowerSyncProvider } from './provider';\n\n// ─── Contexts ────────────────────────────────────────────────────────────────\n\nexport { PowerSyncContext, SyncStatusContext, AttachmentQueueContext } from './contexts';\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\nexport type { PowerSyncConfig, PowerSyncProviderProps, PowerSyncContextValue, SyncStatusContextValue, AttachmentQueueContextValue } from './types';\n\n// ─── Hooks (convenience wrappers) ────────────────────────────────────────────\n\nimport { useContext } from 'react';\nimport { PowerSyncContext, SyncStatusContext, AttachmentQueueContext } from './contexts';\nimport type { PowerSyncContextValue, SyncStatusContextValue, AttachmentQueueContextValue } from './types';\n\n/**\n * Hook to access the PowerSync context.\n * Throws if used outside of a PowerSyncProvider.\n *\n * @example\n * ```typescript\n * function MyComponent() {\n * const { db, isReady, error } = usePowerSyncContext();\n * if (!isReady) return <Loading />;\n * if (error) return <Error message={error.message} />;\n * // Use db...\n * }\n * ```\n */\nexport function usePowerSyncContext() {\n const context = useContext(PowerSyncContext);\n if (!context) {\n throw new Error(\"usePowerSyncContext must be used within a PowerSyncProvider\");\n }\n return context;\n}\n\n/**\n * Hook to access sync status.\n * Throws if used outside of a PowerSyncProvider.\n *\n * @example\n * ```typescript\n * function SyncIndicator() {\n * const { status, lastSyncedAt } = useSyncStatusContext();\n * return <Text>{status.connected ? 'Online' : 'Offline'}</Text>;\n * }\n * ```\n */\nexport function useSyncStatusContext() {\n const context = useContext(SyncStatusContext);\n if (!context) {\n throw new Error(\"useSyncStatusContext must be used within a PowerSyncProvider\");\n }\n return context;\n}\n\n/**\n * Hook to access the attachment queue.\n * Throws if used outside of a PowerSyncProvider.\n *\n * @example\n * ```typescript\n * function AttachmentStatus() {\n * const { queue, isReady } = useAttachmentQueueContext();\n * if (!isReady || !queue) return null;\n * // Use queue...\n * }\n * ```\n */\nexport function useAttachmentQueueContext() {\n const context = useContext(AttachmentQueueContext);\n if (!context) {\n throw new Error(\"useAttachmentQueueContext must be used within a PowerSyncProvider\");\n }\n return context;\n}"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/sync/status-tracker.ts","../src/sync/metrics-collector.ts","../src/sync/health-monitor.ts"],"sourcesContent":["/**\n * Sync Status Tracker for @pol-studios/powersync\n *\n * Tracks and normalizes PowerSync status changes, providing a consistent\n * interface for status updates with throttling support.\n */\n\nimport type { SyncStatus, DownloadProgress, CrudEntry, FailedTransaction, SyncError, CompletedTransaction, SyncMode, UploadBlockReason } from '../core/types';\nimport { generateFailureId } from '../core/errors';\nimport type { LoggerAdapter, AsyncStorageAdapter } from '../platform/types';\nimport type { SyncStatusState, SyncStatusTrackerOptions, PowerSyncRawStatus, Unsubscribe } from './types';\nimport { STORAGE_KEY_PAUSED, STORAGE_KEY_SYNC_MODE, STORAGE_KEY_AUTO_OFFLINE, STATUS_NOTIFY_THROTTLE_MS } from '../core/constants';\nconst STORAGE_KEY_COMPLETED_TRANSACTIONS = '@pol-powersync:completed_transactions';\nconst STORAGE_KEY_FAILED_TRANSACTIONS = '@pol-powersync:failed_transactions';\n\n// Maximum number of completed transactions to retain (oldest entries evicted first)\nconst MAX_COMPLETED_TRANSACTIONS = 1000;\nimport { DEFAULT_SYNC_STATUS } from '../provider/types';\n\n/**\n * Tracks sync status from PowerSync and provides normalized updates.\n *\n * Features:\n * - Normalizes raw PowerSync status to a consistent format\n * - Throttles notifications to prevent UI thrashing\n * - Tracks pending mutations count\n * - Persists and restores paused state\n *\n * @example\n * ```typescript\n * const tracker = new SyncStatusTracker({\n * storage,\n * logger,\n * onStatusChange: (status) => console.log('Status:', status),\n * });\n *\n * // Register with PowerSync\n * db.registerListener({\n * statusChanged: (rawStatus) => tracker.handleStatusChange(rawStatus),\n * });\n *\n * // Get current status\n * const status = tracker.getStatus();\n * ```\n */\nexport class SyncStatusTracker {\n private readonly storage: AsyncStorageAdapter;\n private readonly logger: LoggerAdapter;\n private readonly notifyThrottleMs: number;\n private readonly onStatusChange?: (status: SyncStatus) => void;\n private _state: SyncStatusState;\n private _pendingMutations: CrudEntry[] = [];\n private _lastNotifyTime = 0;\n private _notifyTimer: ReturnType<typeof setTimeout> | null = null;\n private _listeners = new Set<(status: SyncStatus) => void>();\n private _syncModeListeners = new Set<(mode: SyncMode) => void>();\n\n // Force next upload flag for \"Sync Now\" functionality\n private _forceNextUpload = false;\n\n // Network reachability gate - blocks uploads instantly when network is unreachable\n private _networkReachable = true;\n private _networkRestoreTimer: ReturnType<typeof setTimeout> | null = null;\n private readonly _networkRestoreDelayMs = 1500; // 1.5 seconds delay before restoring\n\n // Debounce timer for persist operations to avoid race conditions\n private _persistDebounceTimer: ReturnType<typeof setTimeout> | null = null;\n\n // Track download progress separately to preserve it when offline\n private _lastProgress: DownloadProgress | null = null;\n\n // Exponential backoff configuration for retry timing\n private readonly _backoffBaseMs = 1000; // 1 second base\n private readonly _backoffMaxMs = 60000; // 60 seconds max\n private readonly _backoffMultiplier = 2; // Double each retry\n\n // Failed transaction tracking\n private _failedTransactions: FailedTransaction[] = [];\n private readonly _maxStoredFailures = 50;\n private readonly _failureTTLMs = 24 * 60 * 60 * 1000; // 24 hours\n private _failureListeners = new Set<(failures: FailedTransaction[]) => void>();\n\n // Completed transaction tracking (limited to MAX_COMPLETED_TRANSACTIONS entries)\n private _completedTransactions: CompletedTransaction[] = [];\n private _completedListeners = new Set<(completed: CompletedTransaction[]) => void>();\n\n // Track when notifications were last displayed/dismissed for \"auto-dismiss on display\"\n // This allows filtering completed transactions to only show new ones since last display\n private _lastNotificationTime: number = Date.now();\n\n // Auto-offline flag: tracks whether offline mode was set automatically (network loss)\n // vs manually (user chose offline). Persisted so auto-restore works after app restart.\n private _isAutoOffline = false;\n constructor(storage: AsyncStorageAdapter, logger: LoggerAdapter, options: SyncStatusTrackerOptions = {}) {\n this.storage = storage;\n this.logger = logger;\n this.notifyThrottleMs = options.notifyThrottleMs ?? STATUS_NOTIFY_THROTTLE_MS;\n this.onStatusChange = options.onStatusChange;\n this._state = {\n status: {\n ...DEFAULT_SYNC_STATUS\n },\n syncMode: 'push-pull',\n lastUpdated: new Date()\n };\n }\n\n // ─── Initialization ────────────────────────────────────────────────────────\n\n /**\n * Initialize the tracker by loading persisted state.\n * Includes migration from old isPaused boolean to new syncMode.\n */\n async init(): Promise<void> {\n try {\n // Try to load new sync mode first\n const modeValue = await this.storage.getItem(STORAGE_KEY_SYNC_MODE);\n if (modeValue && ['push-pull', 'pull-only', 'offline'].includes(modeValue)) {\n this._state.syncMode = modeValue as SyncMode;\n this.logger.debug('[StatusTracker] Loaded sync mode:', this._state.syncMode);\n } else {\n // Migrate from old isPaused boolean\n const pausedValue = await this.storage.getItem(STORAGE_KEY_PAUSED);\n if (pausedValue === 'true') {\n this._state.syncMode = 'offline';\n await this.storage.setItem(STORAGE_KEY_SYNC_MODE, 'offline');\n this.logger.debug('[StatusTracker] Migrated isPaused=true to syncMode=offline');\n } else {\n this._state.syncMode = 'push-pull';\n }\n // Clean up old key\n await this.storage.removeItem(STORAGE_KEY_PAUSED);\n }\n } catch (err) {\n this.logger.warn('[StatusTracker] Failed to load sync mode:', err);\n }\n\n // Load persisted auto-offline flag\n try {\n const autoOfflineValue = await this.storage.getItem(STORAGE_KEY_AUTO_OFFLINE);\n this._isAutoOffline = autoOfflineValue === 'true';\n this.logger.debug('[StatusTracker] Loaded isAutoOffline:', this._isAutoOffline);\n } catch (err) {\n this.logger.warn('[StatusTracker] Failed to load auto-offline flag:', err);\n }\n\n // Load persisted completed transactions\n try {\n const completedJson = await this.storage.getItem(STORAGE_KEY_COMPLETED_TRANSACTIONS);\n if (completedJson) {\n const parsed = JSON.parse(completedJson) as Array<Omit<CompletedTransaction, 'completedAt'> & {\n completedAt: string;\n }>;\n this._completedTransactions = parsed.map(item => {\n const remappedEntries = item.entries.map(e => this.remapEntry(e)).filter((e): e is CrudEntry => e !== null);\n return {\n ...item,\n completedAt: new Date(item.completedAt),\n entries: remappedEntries\n };\n }).filter(item => !isNaN(item.completedAt.getTime()) && item.entries.length > 0);\n this.logger.debug('[StatusTracker] Loaded', this._completedTransactions.length, 'completed transactions');\n }\n } catch (err) {\n this.logger.warn('[StatusTracker] Failed to load completed transactions:', err);\n }\n\n // Load persisted failed transactions\n try {\n const failedJson = await this.storage.getItem(STORAGE_KEY_FAILED_TRANSACTIONS);\n if (failedJson) {\n const parsed = JSON.parse(failedJson) as Array<Omit<FailedTransaction, 'firstFailedAt' | 'lastFailedAt' | 'error'> & {\n firstFailedAt: string;\n lastFailedAt: string;\n error: Omit<SyncError, 'timestamp'> & {\n timestamp: string;\n };\n }>;\n this._failedTransactions = parsed.map(item => {\n const remappedEntries = item.entries.map(e => this.remapEntry(e)).filter((e): e is CrudEntry => e !== null);\n return {\n ...item,\n firstFailedAt: new Date(item.firstFailedAt),\n lastFailedAt: new Date(item.lastFailedAt),\n error: {\n ...item.error,\n timestamp: new Date(item.error.timestamp)\n },\n entries: remappedEntries\n };\n }).filter(item => !isNaN(item.firstFailedAt.getTime()) && !isNaN(item.lastFailedAt.getTime()) && item.entries.length > 0);\n this.logger.debug('[StatusTracker] Loaded', this._failedTransactions.length, 'failed transactions');\n }\n } catch (err) {\n this.logger.warn('[StatusTracker] Failed to load failed transactions:', err);\n }\n\n // Clean up any stale failures that were persisted\n this.cleanupStaleFailures();\n }\n\n /**\n * Dispose the tracker and clear timers.\n */\n dispose(): void {\n if (this._notifyTimer) {\n clearTimeout(this._notifyTimer);\n this._notifyTimer = null;\n }\n if (this._persistDebounceTimer) {\n clearTimeout(this._persistDebounceTimer);\n this._persistDebounceTimer = null;\n }\n if (this._networkRestoreTimer) {\n clearTimeout(this._networkRestoreTimer);\n this._networkRestoreTimer = null;\n }\n this._listeners.clear();\n this._syncModeListeners.clear();\n this._failureListeners.clear();\n this._completedListeners.clear();\n }\n\n // ─── Status Getters ────────────────────────────────────────────────────────\n\n /**\n * Get the current sync status.\n */\n getStatus(): SyncStatus {\n const baseStatus = this._state.status;\n\n // Build the status with failed transaction info\n const status: SyncStatus = {\n ...baseStatus,\n failedTransactions: this._failedTransactions,\n hasUploadErrors: this._failedTransactions.length > 0,\n permanentErrorCount: this._failedTransactions.filter(f => f.isPermanent).length\n };\n\n // If offline, use saved progress instead of live (which would be null)\n if (this._state.syncMode === 'offline' && this._lastProgress) {\n return {\n ...status,\n downloadProgress: this._lastProgress\n };\n }\n return status;\n }\n\n // ─── Sync Mode Getters ────────────────────────────────────────────────────────\n\n /**\n * Get the current sync mode.\n */\n getSyncMode(): SyncMode {\n return this._state.syncMode;\n }\n\n /**\n * Get the reason why uploads are currently blocked.\n * Returns 'none' if uploads are ready to proceed.\n *\n * Priority order (first match wins):\n * 1. offline_mode - User explicitly chose offline mode\n * 2. auto_offline - System went offline automatically (network loss with auto-offline flag)\n * 3. pull_only_mode - Pull-only mode active\n * 4. network_unreachable - No internet connection\n * 5. disconnected - PowerSync not connected\n * 6. connecting - PowerSync is connecting\n * 7. uploading - Actively uploading\n * 8. none - Ready to sync\n */\n getUploadBlockReason(): UploadBlockReason {\n const status = this._state.status;\n const syncMode = this._state.syncMode;\n\n // Check sync mode first\n if (syncMode === 'offline') {\n // Distinguish between manual offline and auto-offline\n if (this._isAutoOffline) {\n return 'auto_offline';\n }\n return 'offline_mode';\n }\n if (syncMode === 'pull-only') {\n return 'pull_only_mode';\n }\n\n // Check network reachability\n if (!this._networkReachable) {\n return 'network_unreachable';\n }\n\n // Check PowerSync connection state\n if (!status.connected && !status.connecting) {\n return 'disconnected';\n }\n if (status.connecting) {\n return 'connecting';\n }\n\n // Check if currently uploading\n if (status.uploading) {\n return 'uploading';\n }\n\n // Ready to sync\n return 'none';\n }\n\n /**\n * Get a human-readable description of why uploads are blocked.\n * Returns null if uploads are not blocked.\n */\n getUploadBlockDescription(): string | null {\n const reason = this.getUploadBlockReason();\n switch (reason) {\n case 'offline_mode':\n return 'Offline mode is enabled';\n case 'auto_offline':\n return 'Went offline due to network loss';\n case 'pull_only_mode':\n return 'Pull-only mode is active';\n case 'network_unreachable':\n return 'No internet connection';\n case 'disconnected':\n return 'Not connected to sync service';\n case 'connecting':\n return 'Connecting to sync service';\n case 'uploading':\n return 'Upload in progress';\n case 'none':\n return null;\n default:\n return null;\n }\n }\n\n /**\n * Check if uploads are allowed based on current sync mode and network reachability.\n */\n canUpload(): boolean {\n return this._networkReachable && this._state.syncMode === 'push-pull';\n }\n\n /**\n * Check if downloads are allowed based on current sync mode.\n */\n canDownload(): boolean {\n return this._state.syncMode !== 'offline';\n }\n\n /**\n * Set the force next upload flag for \"Sync Now\" functionality.\n */\n setForceNextUpload(force: boolean): void {\n this._forceNextUpload = force;\n this.logger.debug('[StatusTracker] Force next upload set to:', force);\n }\n\n /**\n * Clear the force next upload flag.\n * Should be called after all pending uploads have been processed.\n */\n clearForceNextUpload(): void {\n if (this._forceNextUpload) {\n this._forceNextUpload = false;\n this.logger.debug('[StatusTracker] Force next upload flag cleared');\n }\n }\n\n /**\n * Check if upload should proceed, considering force flag and network reachability.\n * NOTE: Does NOT auto-reset the flag - caller must use clearForceNextUpload()\n * after all uploads are complete. This prevents race conditions when\n * PowerSync calls uploadData() multiple times for multiple transactions.\n */\n shouldUpload(): boolean {\n // Force flag bypasses all gates (user explicitly requested sync)\n if (this._forceNextUpload) {\n return true;\n }\n // Network gate - instant block when unreachable (0ms, no timeouts)\n if (!this._networkReachable) {\n return false;\n }\n return this._state.syncMode === 'push-pull';\n }\n\n /**\n * Set network reachability state.\n * - When unreachable: Instantly blocks uploads (0ms)\n * - When reachable: Delayed restore (1-2 seconds) to avoid flickering on brief disconnects\n */\n setNetworkReachable(reachable: boolean): void {\n // Clear any pending restore timer\n if (this._networkRestoreTimer) {\n clearTimeout(this._networkRestoreTimer);\n this._networkRestoreTimer = null;\n }\n if (!reachable) {\n // Instant block when network becomes unreachable\n if (this._networkReachable) {\n this._networkReachable = false;\n this.logger.debug('[StatusTracker] Network unreachable - uploads blocked instantly');\n }\n } else {\n // Delayed restore when network becomes reachable\n if (!this._networkReachable) {\n this.logger.debug('[StatusTracker] Network reachable - scheduling delayed restore');\n this._networkRestoreTimer = setTimeout(() => {\n this._networkRestoreTimer = null;\n this._networkReachable = true;\n this.logger.debug('[StatusTracker] Network restored - uploads enabled');\n }, this._networkRestoreDelayMs);\n }\n }\n }\n\n /**\n * Get current network reachability state.\n */\n isNetworkReachable(): boolean {\n return this._networkReachable;\n }\n\n /**\n * Get pending mutations.\n */\n getPendingMutations(): CrudEntry[] {\n return this._pendingMutations;\n }\n\n /**\n * Get pending mutation count.\n */\n getPendingCount(): number {\n return this._pendingMutations.length;\n }\n\n // ─── Status Updates ────────────────────────────────────────────────────────\n\n /**\n * Handle a raw status update from PowerSync.\n */\n handleStatusChange(rawStatus: PowerSyncRawStatus): void {\n const progress = rawStatus.downloadProgress;\n const dataFlow = rawStatus.dataFlowStatus;\n\n // Build normalized download progress\n let downloadProgress: DownloadProgress | null = null;\n if (progress && progress.totalOperations && progress.totalOperations > 0) {\n downloadProgress = {\n current: progress.downloadedOperations ?? 0,\n target: progress.totalOperations,\n percentage: Math.round((progress.downloadedFraction ?? 0) * 100)\n };\n // Save progress for when paused\n this._lastProgress = downloadProgress;\n }\n\n // Build normalized status (failed transaction fields are added in getStatus())\n const newStatus: SyncStatus = {\n connected: rawStatus.connected ?? false,\n connecting: rawStatus.connecting ?? false,\n hasSynced: rawStatus.hasSynced ?? false,\n lastSyncedAt: rawStatus.lastSyncedAt ?? null,\n uploading: dataFlow?.uploading ?? false,\n downloading: dataFlow?.downloading ?? false,\n downloadProgress,\n // These are computed from _failedTransactions in getStatus()\n failedTransactions: [],\n hasUploadErrors: false,\n permanentErrorCount: 0\n };\n\n // Check if status actually changed\n const changed = this._hasStatusChanged(newStatus);\n this._state = {\n status: newStatus,\n syncMode: this._state.syncMode,\n lastUpdated: new Date()\n };\n if (changed) {\n this._notifyListeners();\n }\n }\n\n /**\n * Update pending mutations from a CRUD transaction.\n */\n updatePendingMutations(mutations: CrudEntry[]): void {\n this._pendingMutations = mutations;\n }\n\n /**\n * Valid sync modes for runtime validation.\n */\n private static readonly VALID_SYNC_MODES: SyncMode[] = ['push-pull', 'pull-only', 'offline'];\n\n /**\n * Set the sync mode.\n */\n async setSyncMode(mode: SyncMode): Promise<void> {\n // Runtime validation\n if (!SyncStatusTracker.VALID_SYNC_MODES.includes(mode)) {\n this.logger.warn('[StatusTracker] Invalid sync mode, ignoring:', mode);\n return;\n }\n if (this._state.syncMode === mode) return;\n const previousMode = this._state.syncMode;\n this._state.syncMode = mode;\n\n // Persist to storage\n try {\n await this.storage.setItem(STORAGE_KEY_SYNC_MODE, mode);\n this.logger.info('[StatusTracker] Sync mode changed:', previousMode, '->', mode);\n } catch (err) {\n this.logger.warn('[StatusTracker] Failed to persist sync mode:', err);\n }\n\n // Notify sync mode listeners\n for (const listener of this._syncModeListeners) {\n try {\n listener(mode);\n } catch (err) {\n this.logger.warn('[StatusTracker] Sync mode listener error:', err);\n }\n }\n this._notifyListeners(true);\n }\n\n // ─── Auto-Offline Management ──────────────────────────────────────────────\n\n /**\n * Get whether offline mode was set automatically (network loss) vs manually.\n * Used to determine if sync should auto-resume when network returns.\n */\n getIsAutoOffline(): boolean {\n return this._isAutoOffline;\n }\n\n /**\n * Set the auto-offline flag and persist it.\n * @param isAuto - true if offline was set automatically, false if user chose offline\n */\n async setIsAutoOffline(isAuto: boolean): Promise<void> {\n if (this._isAutoOffline === isAuto) return;\n this._isAutoOffline = isAuto;\n\n // Persist to storage\n try {\n await this.storage.setItem(STORAGE_KEY_AUTO_OFFLINE, isAuto ? 'true' : 'false');\n this.logger.debug('[StatusTracker] Auto-offline flag changed:', isAuto);\n } catch (err) {\n this.logger.warn('[StatusTracker] Failed to persist auto-offline flag:', err);\n }\n }\n\n // ─── Subscriptions ─────────────────────────────────────────────────────────\n\n /**\n * Subscribe to status changes.\n * @returns Unsubscribe function\n */\n onStatusUpdate(listener: (status: SyncStatus) => void): Unsubscribe {\n this._listeners.add(listener);\n // Immediately call with current status\n listener(this.getStatus());\n return () => {\n this._listeners.delete(listener);\n };\n }\n\n /**\n * Subscribe to sync mode changes.\n * @returns Unsubscribe function\n */\n onSyncModeChange(listener: (mode: SyncMode) => void): Unsubscribe {\n this._syncModeListeners.add(listener);\n listener(this._state.syncMode);\n return () => {\n this._syncModeListeners.delete(listener);\n };\n }\n\n // ─── Failed Transaction Tracking ────────────────────────────────────────────\n\n /**\n * Compute exponential backoff delay for a given retry count.\n * Uses base 1s, max 60s, multiplier 2.\n *\n * @param retryCount - Number of retries (1-based)\n * @returns Backoff delay in milliseconds\n */\n private _computeBackoffMs(retryCount: number): number {\n // retryCount is 1-based, so for first retry we use base delay\n const exponent = Math.max(0, retryCount - 1);\n const delay = this._backoffBaseMs * Math.pow(this._backoffMultiplier, exponent);\n return Math.min(delay, this._backoffMaxMs);\n }\n\n /**\n * Record a transaction failure.\n * If a failure for the same entries already exists, updates the retry count.\n * Otherwise, creates a new failure record.\n *\n * @param preserveMetadata - Optional. If provided, preserves retryCount and firstFailedAt from a previous failure.\n */\n recordTransactionFailure(entries: CrudEntry[], error: SyncError, isPermanent: boolean, affectedEntityIds: string[], affectedTables: string[], preserveMetadata?: {\n retryCount: number;\n firstFailedAt: Date;\n }): void {\n const now = new Date();\n\n // Normalize entries to plain objects to avoid CrudEntry.toJSON() remapping issues\n const normalizedEntries = this.normalizeEntries(entries);\n const entryIds = normalizedEntries.map(e => e.id).sort().join(',');\n\n // Check if a failure for these entries already exists\n const existingIndex = this._failedTransactions.findIndex(f => {\n const existingIds = f.entries.map(e => e.id).sort().join(',');\n return existingIds === entryIds;\n });\n if (existingIndex !== -1) {\n // Update existing failure\n const existing = this._failedTransactions[existingIndex];\n const newRetryCount = existing.retryCount + 1;\n\n // Compute backoff (no backoff for permanent errors)\n const backoffMs = isPermanent ? undefined : this._computeBackoffMs(newRetryCount);\n const nextRetryAt = backoffMs ? new Date(now.getTime() + backoffMs) : undefined;\n this._failedTransactions[existingIndex] = {\n ...existing,\n error,\n retryCount: newRetryCount,\n lastFailedAt: now,\n isPermanent,\n backoffMs,\n nextRetryAt\n };\n } else {\n // Create new failure record\n const retryCount = preserveMetadata?.retryCount ?? 1;\n\n // Compute backoff (no backoff for permanent errors)\n const backoffMs = isPermanent ? undefined : this._computeBackoffMs(retryCount);\n const nextRetryAt = backoffMs ? new Date(now.getTime() + backoffMs) : undefined;\n const newFailure: FailedTransaction = {\n id: generateFailureId(normalizedEntries),\n entries: normalizedEntries,\n error,\n retryCount,\n firstFailedAt: preserveMetadata?.firstFailedAt ?? now,\n lastFailedAt: now,\n isPermanent,\n affectedEntityIds,\n affectedTables,\n backoffMs,\n nextRetryAt\n };\n this._failedTransactions.push(newFailure);\n\n // Enforce max stored failures (remove oldest)\n if (this._failedTransactions.length > this._maxStoredFailures) {\n this._failedTransactions.sort((a, b) => a.firstFailedAt.getTime() - b.firstFailedAt.getTime());\n this._failedTransactions = this._failedTransactions.slice(-this._maxStoredFailures);\n }\n }\n this._schedulePersist();\n this._notifyFailureListeners();\n this._notifyListeners();\n }\n\n /**\n * Clear a specific failure by ID.\n */\n clearFailure(failureId: string): void {\n const initialLength = this._failedTransactions.length;\n this._failedTransactions = this._failedTransactions.filter(f => f.id !== failureId);\n if (this._failedTransactions.length !== initialLength) {\n this._schedulePersist();\n this._notifyFailureListeners();\n this._notifyListeners();\n }\n }\n\n /**\n * Clear failures for successfully synced entries.\n * Removes any failed transaction that contains entries with the given IDs.\n *\n * This is called when `onTransactionSuccess` fires - if any entry in a failed\n * transaction has now succeeded, we remove that entire failure record.\n *\n * @param entryIds - Array of CrudEntry.id values that succeeded\n */\n clearSuccessfulEntries(entryIds: string[]): void {\n if (entryIds.length === 0 || this._failedTransactions.length === 0) return;\n const entryIdSet = new Set(entryIds);\n const initialLength = this._failedTransactions.length;\n\n // Remove failures where ANY entry has succeeded\n // If one entry in a transaction succeeds, the whole transaction succeeded\n this._failedTransactions = this._failedTransactions.filter(failure => {\n const hasSuccessfulEntry = failure.entries.some(entry => entryIdSet.has(entry.id));\n return !hasSuccessfulEntry;\n });\n if (this._failedTransactions.length !== initialLength) {\n const clearedCount = initialLength - this._failedTransactions.length;\n this.logger.debug(`[StatusTracker] Cleared ${clearedCount} failures for successful entries`);\n this._schedulePersist();\n this._notifyFailureListeners();\n this._notifyListeners();\n }\n }\n\n /**\n * Remove a failed transaction from tracking and return its entries.\n * This is a \"pop\" operation - the failure is removed from the list.\n *\n * Note: The actual CRUD entries remain in PowerSync's ps_crud table\n * until successfully uploaded. This just removes from our tracking.\n *\n * @param failureId - The failure ID to remove\n * @returns The CrudEntry[] that were in the failure, or null if not found\n */\n takeFailureForRetry(failureId: string): CrudEntry[] | null {\n const failure = this._failedTransactions.find(f => f.id === failureId);\n if (!failure) {\n this.logger.warn('[StatusTracker] Failure not found for retry:', failureId);\n return null;\n }\n\n // Remove from failed list\n this._failedTransactions = this._failedTransactions.filter(f => f.id !== failureId);\n this._notifyFailureListeners();\n this._schedulePersist();\n this.logger.info('[StatusTracker] Retrieved failure for retry:', failureId, 'entries:', failure.entries.length);\n return failure.entries;\n }\n\n /**\n * Get failures affecting a specific entity.\n */\n getFailuresForEntity(entityId: string): FailedTransaction[] {\n return this._failedTransactions.filter(f => f.affectedEntityIds.includes(entityId));\n }\n\n /**\n * Get all failed transactions.\n */\n getFailedTransactions(): FailedTransaction[] {\n return [...this._failedTransactions];\n }\n\n /**\n * Check if there are any upload errors.\n */\n hasUploadErrors(): boolean {\n return this._failedTransactions.length > 0;\n }\n\n /**\n * Get count of permanent errors.\n */\n getPermanentErrorCount(): number {\n return this._failedTransactions.filter(f => f.isPermanent).length;\n }\n\n /**\n * Subscribe to failure changes.\n * @returns Unsubscribe function\n */\n onFailureChange(listener: (failures: FailedTransaction[]) => void): Unsubscribe {\n this._failureListeners.add(listener);\n // Immediately call with current failures\n listener(this.getFailedTransactions());\n return () => {\n this._failureListeners.delete(listener);\n };\n }\n\n /**\n * Clean up stale failures (older than TTL).\n */\n cleanupStaleFailures(): void {\n const cutoff = Date.now() - this._failureTTLMs;\n const initialLength = this._failedTransactions.length;\n this._failedTransactions = this._failedTransactions.filter(f => f.lastFailedAt.getTime() > cutoff);\n if (this._failedTransactions.length !== initialLength) {\n this.logger.debug(`[StatusTracker] Cleaned up ${initialLength - this._failedTransactions.length} stale failures`);\n this._schedulePersist();\n this._notifyFailureListeners();\n this._notifyListeners();\n }\n }\n\n // ─── Completed Transaction Tracking ────────────────────────────────────────\n\n /**\n * Record a successfully completed transaction.\n * Creates a CompletedTransaction record and adds it to history.\n */\n recordTransactionComplete(entries: CrudEntry[]): void {\n // Normalize entries to plain objects to avoid CrudEntry.toJSON() remapping issues\n const normalizedEntries = this.normalizeEntries(entries);\n\n // Extract unique affected tables\n const affectedTables = [...new Set(normalizedEntries.map(e => e.table))];\n\n // Extract unique affected entity IDs\n const affectedEntityIds = [...new Set(normalizedEntries.map(e => e.id))];\n\n // Generate unique ID\n const id = `completed_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;\n const completed: CompletedTransaction = {\n id,\n entries: normalizedEntries,\n completedAt: new Date(),\n affectedTables,\n affectedEntityIds\n };\n\n // Add to front of array (most recent first)\n this._completedTransactions.unshift(completed);\n\n // Limit completed history to prevent unbounded memory growth (oldest entries evicted)\n if (this._completedTransactions.length > MAX_COMPLETED_TRANSACTIONS) {\n this._completedTransactions = this._completedTransactions.slice(0, MAX_COMPLETED_TRANSACTIONS);\n }\n this._schedulePersist();\n this._notifyCompletedListeners();\n this.logger.debug(`[StatusTracker] Recorded completed transaction: ${completed.id} (${entries.length} entries)`);\n }\n\n /**\n * Get all completed transactions.\n */\n getCompletedTransactions(): CompletedTransaction[] {\n return [...this._completedTransactions];\n }\n\n /**\n * Clear completed transaction history.\n */\n clearCompletedHistory(): void {\n if (this._completedTransactions.length === 0) return;\n this._completedTransactions = [];\n this._schedulePersist();\n this._notifyCompletedListeners();\n this.logger.debug('[StatusTracker] Cleared completed transaction history');\n }\n\n /**\n * Clear a specific completed transaction by ID.\n */\n clearCompletedItem(completedId: string): void {\n const initialLength = this._completedTransactions.length;\n this._completedTransactions = this._completedTransactions.filter(c => c.id !== completedId);\n if (this._completedTransactions.length !== initialLength) {\n this._schedulePersist();\n this._notifyCompletedListeners();\n this.logger.debug('[StatusTracker] Cleared completed transaction:', completedId);\n }\n }\n\n /**\n * Subscribe to completed transaction changes.\n * @returns Unsubscribe function\n */\n onCompletedChange(listener: (completed: CompletedTransaction[]) => void): Unsubscribe {\n this._completedListeners.add(listener);\n // Immediately call with current completed transactions\n listener(this.getCompletedTransactions());\n return () => {\n this._completedListeners.delete(listener);\n };\n }\n\n // ─── Notification Tracking ─────────────────────────────────────────────────\n\n /**\n * Get completed transactions that occurred AFTER the last notification time.\n * This is used for displaying \"X changes synced\" notifications to avoid\n * showing stale counts from historical completed transactions.\n */\n getNewCompletedTransactions(): CompletedTransaction[] {\n return this._completedTransactions.filter(tx => tx.completedAt.getTime() > this._lastNotificationTime);\n }\n\n /**\n * Mark notifications as seen by updating the last notification time.\n * Call this when the notification is displayed or dismissed.\n */\n markNotificationsAsSeen(): void {\n this._lastNotificationTime = Date.now();\n this.logger.debug('[StatusTracker] Notifications marked as seen');\n // Notify listeners so UI can update (newCompletedTransactions will now be empty)\n this._notifyCompletedListeners();\n }\n\n /**\n * Get the timestamp of when notifications were last displayed/dismissed.\n */\n getLastNotificationTime(): number {\n return this._lastNotificationTime;\n }\n\n // ─── Private Methods ───────────────────────────────────────────────────────\n\n /**\n * Schedule a debounced persist operation.\n * This prevents race conditions from multiple rapid persist calls.\n */\n private _schedulePersist(): void {\n if (this._persistDebounceTimer) {\n clearTimeout(this._persistDebounceTimer);\n }\n this._persistDebounceTimer = setTimeout(() => {\n this._persistDebounceTimer = null;\n this._persistTransactions();\n }, 100); // 100ms debounce\n }\n\n /**\n * Persist completed and failed transactions to storage.\n */\n private async _persistTransactions(): Promise<void> {\n try {\n 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))]);\n } catch (err) {\n this.logger.warn('[StatusTracker] Failed to persist transactions:', err);\n }\n }\n private _hasStatusChanged(newStatus: SyncStatus): boolean {\n const old = this._state.status;\n return old.connected !== newStatus.connected || old.connecting !== newStatus.connecting || old.hasSynced !== newStatus.hasSynced || old.uploading !== newStatus.uploading || old.downloading !== newStatus.downloading || old.lastSyncedAt?.getTime() !== newStatus.lastSyncedAt?.getTime() || old.downloadProgress?.current !== newStatus.downloadProgress?.current || old.downloadProgress?.target !== newStatus.downloadProgress?.target;\n }\n\n /**\n * Notify all listeners of status changes with throttling.\n *\n * Uses a \"dirty\" flag pattern: when throttled, we schedule a timer\n * but get the CURRENT state when the timer fires, not the stale state\n * from when the timer was scheduled. This ensures rapid state changes\n * during the throttle window aren't lost.\n */\n private _notifyListeners(forceImmediate = false): void {\n const now = Date.now();\n const timeSinceLastNotify = now - this._lastNotifyTime;\n\n // If a timer is already scheduled, don't reschedule - just let it fire\n // and it will pick up the current (latest) state at that time\n if (this._notifyTimer && !forceImmediate) {\n return; // Already scheduled, will get current state when it fires\n }\n if (this._notifyTimer) {\n clearTimeout(this._notifyTimer);\n this._notifyTimer = null;\n }\n const notify = () => {\n this._notifyTimer = null;\n this._lastNotifyTime = Date.now();\n // Get CURRENT state at notification time, not stale state\n const status = this.getStatus();\n\n // Call the main callback\n this.onStatusChange?.(status);\n\n // Call all listeners\n for (const listener of this._listeners) {\n try {\n listener(status);\n } catch (err) {\n this.logger.warn('[StatusTracker] Listener error:', err);\n }\n }\n };\n if (forceImmediate || timeSinceLastNotify >= this.notifyThrottleMs) {\n notify();\n } else {\n const delayMs = this.notifyThrottleMs - timeSinceLastNotify;\n this._notifyTimer = setTimeout(notify, delayMs);\n }\n }\n private _notifyFailureListeners(): void {\n const failures = this.getFailedTransactions();\n for (const listener of this._failureListeners) {\n try {\n listener(failures);\n } catch (err) {\n this.logger.warn('[StatusTracker] Failure listener error:', err);\n }\n }\n }\n private _notifyCompletedListeners(): void {\n const completed = this.getCompletedTransactions();\n for (const listener of this._completedListeners) {\n try {\n listener(completed);\n } catch (err) {\n this.logger.warn('[StatusTracker] Completed listener error:', err);\n }\n }\n }\n\n /**\n * Remap a CrudEntry from persisted JSON (handles toJSON() property remapping).\n * PowerSync's CrudEntry.toJSON() remaps: opData→data, table→type, clientId→op_id, transactionId→tx_id\n *\n * @returns The remapped CrudEntry, or null if critical fields (table, id) are missing\n */\n private remapEntry(entry: any): CrudEntry | null {\n const table = entry.table ?? entry.type;\n const id = entry.id;\n\n // Validate critical fields\n if (!table || typeof table !== 'string') {\n this.logger.warn('[StatusTracker] Invalid CrudEntry: missing or invalid table field', entry);\n return null;\n }\n if (!id || typeof id !== 'string') {\n this.logger.warn('[StatusTracker] Invalid CrudEntry: missing or invalid id field', entry);\n return null;\n }\n return {\n id,\n clientId: entry.clientId ?? entry.op_id ?? 0,\n op: entry.op,\n table,\n opData: entry.opData ?? entry.data,\n transactionId: entry.transactionId ?? entry.tx_id\n };\n }\n\n /**\n * Normalize CrudEntry array to plain objects to avoid CrudEntry.toJSON() remapping issues.\n * PowerSync's CrudEntry.toJSON() remaps property names which breaks deserialization.\n */\n private normalizeEntries(entries: CrudEntry[]): CrudEntry[] {\n return entries.map(e => ({\n id: e.id,\n clientId: e.clientId,\n op: e.op,\n table: e.table,\n opData: e.opData,\n transactionId: e.transactionId\n }));\n }\n}","/**\n * Sync Metrics Collector for @pol-studios/powersync\n *\n * Collects and persists sync operation metrics for monitoring and debugging.\n */\n\nimport type { SyncMetrics, SyncError, SyncErrorType } from '../core/types';\nimport type { AsyncStorageAdapter, LoggerAdapter } from '../platform/types';\nimport type { MetricsCollectorOptions, SyncOperationData, Unsubscribe } from './types';\nimport { STORAGE_KEY_METRICS } from '../core/constants';\nimport { classifyError } from '../core/errors';\nimport { DEFAULT_SYNC_METRICS } from '../provider/types';\n\n/**\n * Collects sync metrics including operation counts, timing, and errors.\n *\n * Features:\n * - Tracks sync operation success/failure rates\n * - Calculates average sync duration\n * - Monitors data transfer amounts\n * - Persists metrics to storage for continuity across sessions\n * - Records last error for debugging\n *\n * @example\n * ```typescript\n * const collector = new MetricsCollector({\n * storage,\n * logger,\n * onMetricsChange: (metrics) => updateUI(metrics),\n * });\n *\n * // Start tracking a sync\n * const startTime = Date.now();\n *\n * // On sync complete\n * collector.recordSync({\n * durationMs: Date.now() - startTime,\n * success: true,\n * operationsDownloaded: 150,\n * });\n *\n * // Get current metrics\n * const metrics = collector.getMetrics();\n * ```\n */\nexport class MetricsCollector {\n private readonly storage: AsyncStorageAdapter;\n private readonly logger: LoggerAdapter;\n private readonly storageKey: string;\n private readonly persistMetrics: boolean;\n private readonly onMetricsChange?: (metrics: SyncMetrics) => void;\n private _metrics: SyncMetrics;\n private _listeners = new Set<(metrics: SyncMetrics) => void>();\n private _initialized = false;\n\n // Track active sync for timing\n private _syncStartTime: number | null = null;\n private _wasSyncing = false;\n\n // Debounced persistence\n private _persistTimeout: ReturnType<typeof setTimeout> | null = null;\n private readonly PERSIST_DEBOUNCE_MS = 100;\n constructor(storage: AsyncStorageAdapter, logger: LoggerAdapter, options: MetricsCollectorOptions = {}) {\n this.storage = storage;\n this.logger = logger;\n this.storageKey = options.storageKey ?? STORAGE_KEY_METRICS;\n this.persistMetrics = options.persistMetrics ?? true;\n this.onMetricsChange = options.onMetricsChange;\n this._metrics = {\n ...DEFAULT_SYNC_METRICS\n };\n }\n\n // ─── Initialization ────────────────────────────────────────────────────────\n\n /**\n * Initialize the collector by loading persisted metrics.\n */\n async init(): Promise<void> {\n if (this._initialized) return;\n try {\n const stored = await this.storage.getItem(this.storageKey);\n if (stored) {\n const parsed = JSON.parse(stored);\n // Restore Date objects\n if (parsed.lastError?.timestamp) {\n parsed.lastError.timestamp = new Date(parsed.lastError.timestamp);\n }\n this._metrics = {\n ...DEFAULT_SYNC_METRICS,\n ...parsed\n };\n this.logger.debug('[MetricsCollector] Loaded persisted metrics');\n }\n } catch (err) {\n this.logger.warn('[MetricsCollector] Failed to load metrics:', err);\n }\n this._initialized = true;\n }\n\n /**\n * Dispose the collector.\n */\n dispose(): void {\n if (this._persistTimeout) {\n clearTimeout(this._persistTimeout);\n this._persistTimeout = null;\n }\n this._listeners.clear();\n }\n\n // ─── Getters ───────────────────────────────────────────────────────────────\n\n /**\n * Get current sync metrics.\n */\n getMetrics(): SyncMetrics {\n return {\n ...this._metrics\n };\n }\n\n // ─── Recording ─────────────────────────────────────────────────────────────\n\n /**\n * Record a completed sync operation.\n */\n async recordSync(data: SyncOperationData): Promise<void> {\n const {\n durationMs,\n success,\n operationsDownloaded,\n operationsUploaded,\n error\n } = data;\n const totalSyncs = this._metrics.totalSyncs + 1;\n const successfulSyncs = this._metrics.successfulSyncs + (success ? 1 : 0);\n const failedSyncs = this._metrics.failedSyncs + (success ? 0 : 1);\n\n // Calculate running average duration (only for successful syncs)\n let averageSyncDuration = this._metrics.averageSyncDuration;\n if (success) {\n if (averageSyncDuration !== null) {\n averageSyncDuration = (averageSyncDuration * (successfulSyncs - 1) + durationMs) / successfulSyncs;\n } else {\n averageSyncDuration = durationMs;\n }\n }\n\n // Estimate data transfer (rough approximation: ~100 bytes per operation)\n const bytesPerOp = 100;\n const downloaded = (operationsDownloaded ?? 0) * bytesPerOp;\n const uploaded = (operationsUploaded ?? 0) * bytesPerOp;\n\n // Build error record if failed\n let lastError: SyncError | null = this._metrics.lastError;\n if (!success && error) {\n const errorType = classifyError(error);\n lastError = {\n type: errorType,\n message: error.message,\n userMessage: error.message,\n // Use original message as user message for metrics\n timestamp: new Date(),\n isPermanent: false\n };\n }\n this._metrics = {\n totalSyncs,\n successfulSyncs,\n failedSyncs,\n lastSyncDuration: durationMs,\n averageSyncDuration: averageSyncDuration !== null ? Math.round(averageSyncDuration) : null,\n totalDataDownloaded: this._metrics.totalDataDownloaded + downloaded,\n totalDataUploaded: this._metrics.totalDataUploaded + uploaded,\n lastError\n };\n this._schedulePersist();\n this._notifyListeners();\n }\n\n /**\n * Record a sync error without a full sync operation.\n */\n async recordError(error: Error): Promise<void> {\n this._metrics = {\n ...this._metrics,\n failedSyncs: this._metrics.failedSyncs + 1,\n lastError: {\n type: classifyError(error),\n message: error.message,\n userMessage: error.message,\n // Use original message as user message for metrics\n timestamp: new Date(),\n isPermanent: false\n }\n };\n this._schedulePersist();\n this._notifyListeners();\n }\n\n /**\n * Record an upload operation.\n */\n async recordUpload(operationCount: number): Promise<void> {\n const bytesPerOp = 100;\n this._metrics = {\n ...this._metrics,\n totalDataUploaded: this._metrics.totalDataUploaded + operationCount * bytesPerOp\n };\n this._schedulePersist();\n this._notifyListeners();\n }\n\n /**\n * Clear all metrics and start fresh.\n */\n async reset(): Promise<void> {\n this._metrics = {\n ...DEFAULT_SYNC_METRICS\n };\n await this._persist();\n this._notifyListeners();\n }\n\n // ─── Sync Timing Helpers ───────────────────────────────────────────────────\n\n /**\n * Called when sync starts (downloading becomes true).\n */\n markSyncStart(): void {\n if (!this._wasSyncing) {\n this._syncStartTime = Date.now();\n this._wasSyncing = true;\n }\n }\n\n /**\n * Called when sync ends (downloading becomes false).\n * Returns the duration if a sync was in progress.\n */\n markSyncEnd(): number | null {\n if (this._wasSyncing && this._syncStartTime !== null) {\n const duration = Date.now() - this._syncStartTime;\n this._syncStartTime = null;\n this._wasSyncing = false;\n return duration;\n }\n return null;\n }\n\n /**\n * Check if sync is currently in progress.\n */\n isSyncInProgress(): boolean {\n return this._wasSyncing;\n }\n\n // ─── Subscriptions ─────────────────────────────────────────────────────────\n\n /**\n * Subscribe to metrics changes.\n * @returns Unsubscribe function\n */\n onMetricsUpdate(listener: (metrics: SyncMetrics) => void): Unsubscribe {\n this._listeners.add(listener);\n listener(this.getMetrics());\n return () => {\n this._listeners.delete(listener);\n };\n }\n\n // ─── Private Methods ───────────────────────────────────────────────────────\n\n private _schedulePersist(): void {\n if (this._persistTimeout) return; // Already scheduled\n\n this._persistTimeout = setTimeout(() => {\n this._persistTimeout = null;\n this._persist().catch(err => {\n this.logger.warn('[MetricsCollector] Persist failed:', err);\n });\n }, this.PERSIST_DEBOUNCE_MS);\n }\n private async _persist(): Promise<void> {\n if (!this.persistMetrics) return;\n try {\n await this.storage.setItem(this.storageKey, JSON.stringify(this._metrics));\n } catch (err) {\n this.logger.warn('[MetricsCollector] Failed to persist metrics:', err);\n }\n }\n private _notifyListeners(): void {\n const metrics = this.getMetrics();\n\n // Call main callback\n this.onMetricsChange?.(metrics);\n\n // Call all listeners\n for (const listener of this._listeners) {\n try {\n listener(metrics);\n } catch (err) {\n this.logger.warn('[MetricsCollector] Listener error:', err);\n }\n }\n }\n}","/**\n * Connection Health Monitor for @pol-studios/powersync\n *\n * Monitors database connection health with periodic checks and latency tracking.\n */\n\nimport type { ConnectionHealth } from '../core/types';\nimport type { AbstractPowerSyncDatabase } from '../core/types';\nimport type { LoggerAdapter } from '../platform/types';\nimport type { HealthMonitorOptions, HealthCheckResult, Unsubscribe } from './types';\nimport { HEALTH_CHECK_INTERVAL_MS, HEALTH_CHECK_TIMEOUT_MS, LATENCY_DEGRADED_THRESHOLD_MS, MAX_CONSECUTIVE_FAILURES } from '../core/constants';\nimport { DEFAULT_CONNECTION_HEALTH } from '../provider/types';\n\n/**\n * Monitors connection health with periodic checks.\n *\n * Features:\n * - Periodic health checks with configurable interval\n * - Latency measurement and degraded state detection\n * - Consecutive failure tracking\n * - Auto-recovery detection\n *\n * @example\n * ```typescript\n * const monitor = new HealthMonitor(db, logger, {\n * checkIntervalMs: 30000,\n * onHealthChange: (health) => {\n * if (health.status === 'degraded') {\n * showWarning('Connection is slow');\n * }\n * },\n * });\n *\n * // Start monitoring\n * monitor.start();\n *\n * // Get current health\n * const health = monitor.getHealth();\n *\n * // Stop when done\n * monitor.stop();\n * ```\n */\nexport class HealthMonitor {\n private readonly logger: LoggerAdapter;\n private readonly checkIntervalMs: number;\n private readonly checkTimeoutMs: number;\n private readonly degradedThresholdMs: number;\n private readonly maxConsecutiveFailures: number;\n private readonly onHealthChange?: (health: ConnectionHealth) => void;\n private _db: AbstractPowerSyncDatabase | null = null;\n private _health: ConnectionHealth;\n private _intervalId: ReturnType<typeof setInterval> | null = null;\n private _listeners = new Set<(health: ConnectionHealth) => void>();\n private _running = false;\n private _paused = false;\n private _pendingTimers = new Set<ReturnType<typeof setTimeout>>();\n constructor(logger: LoggerAdapter, options: HealthMonitorOptions = {}) {\n this.logger = logger;\n this.checkIntervalMs = options.checkIntervalMs ?? HEALTH_CHECK_INTERVAL_MS;\n this.checkTimeoutMs = options.checkTimeoutMs ?? HEALTH_CHECK_TIMEOUT_MS;\n this.degradedThresholdMs = options.degradedThresholdMs ?? LATENCY_DEGRADED_THRESHOLD_MS;\n this.maxConsecutiveFailures = options.maxConsecutiveFailures ?? MAX_CONSECUTIVE_FAILURES;\n this.onHealthChange = options.onHealthChange;\n this._health = {\n ...DEFAULT_CONNECTION_HEALTH\n };\n }\n\n // ─── Lifecycle ─────────────────────────────────────────────────────────────\n\n /**\n * Set the database instance to monitor.\n */\n setDatabase(db: AbstractPowerSyncDatabase | null): void {\n this._db = db;\n if (!db) {\n // Reset health when database is cleared\n this._updateHealth({\n status: 'disconnected',\n latency: null,\n lastHealthCheck: new Date(),\n consecutiveFailures: 0,\n reconnectAttempts: this._health.reconnectAttempts\n });\n }\n }\n\n /**\n * Start the health monitor.\n */\n start(): void {\n if (this._running) return;\n this.logger.info('[HealthMonitor] Starting');\n this._running = true;\n\n // Perform initial check\n this._checkHealth().catch(err => {\n this.logger.warn('[HealthMonitor] Initial check error:', err);\n });\n\n // Set up periodic checks\n this._intervalId = setInterval(() => {\n if (!this._paused) {\n this._checkHealth().catch(err => {\n this.logger.warn('[HealthMonitor] Periodic check error:', err);\n });\n }\n }, this.checkIntervalMs);\n }\n\n /**\n * Stop the health monitor.\n */\n stop(): void {\n if (!this._running) return;\n this.logger.info('[HealthMonitor] Stopping');\n this._running = false;\n if (this._intervalId) {\n clearInterval(this._intervalId);\n this._intervalId = null;\n }\n }\n\n /**\n * Pause health checks temporarily.\n */\n pause(): void {\n this._paused = true;\n // Clear any pending timeout timers to prevent leaks\n this._pendingTimers.forEach(clearTimeout);\n this._pendingTimers.clear();\n this._updateHealth({\n ...this._health,\n status: 'disconnected'\n });\n }\n\n /**\n * Resume health checks.\n */\n resume(): void {\n this._paused = false;\n // Perform immediate check on resume\n this._checkHealth().catch(err => {\n this.logger.warn('[HealthMonitor] Resume check error:', err);\n });\n }\n\n /**\n * Dispose the monitor and clear all resources.\n */\n dispose(): void {\n this.stop();\n // Clear any pending timeout timers to prevent leaks\n this._pendingTimers.forEach(clearTimeout);\n this._pendingTimers.clear();\n this._listeners.clear();\n }\n\n // ─── Getters ───────────────────────────────────────────────────────────────\n\n /**\n * Get current connection health.\n */\n getHealth(): ConnectionHealth {\n return {\n ...this._health\n };\n }\n\n /**\n * Check if the monitor is running.\n */\n isRunning(): boolean {\n return this._running;\n }\n\n // ─── Manual Checks ─────────────────────────────────────────────────────────\n\n /**\n * Perform an immediate health check.\n * @returns The result of the health check\n */\n async checkNow(): Promise<HealthCheckResult> {\n return this._checkHealth();\n }\n\n /**\n * Record a reconnection attempt.\n */\n recordReconnectAttempt(): void {\n this._updateHealth({\n ...this._health,\n reconnectAttempts: this._health.reconnectAttempts + 1\n });\n }\n\n /**\n * Reset reconnection attempts counter (call on successful connection).\n */\n resetReconnectAttempts(): void {\n if (this._health.reconnectAttempts > 0) {\n this._updateHealth({\n ...this._health,\n reconnectAttempts: 0\n });\n }\n }\n\n // ─── Subscriptions ─────────────────────────────────────────────────────────\n\n /**\n * Subscribe to health changes.\n * @returns Unsubscribe function\n */\n onHealthUpdate(listener: (health: ConnectionHealth) => void): Unsubscribe {\n this._listeners.add(listener);\n listener(this.getHealth());\n return () => {\n this._listeners.delete(listener);\n };\n }\n\n // ─── Private Methods ───────────────────────────────────────────────────────\n\n private async _checkHealth(): Promise<HealthCheckResult> {\n if (!this._db || this._paused) {\n return {\n success: false,\n error: new Error('Database not available or paused'),\n timestamp: new Date()\n };\n }\n const startTime = Date.now();\n const timestamp = new Date();\n try {\n // Execute a simple query with timeout\n await this._withTimeout(this._db.get('SELECT 1'), this.checkTimeoutMs);\n const latencyMs = Date.now() - startTime;\n\n // Determine status based on latency\n const status: ConnectionHealth['status'] = latencyMs < this.degradedThresholdMs ? 'healthy' : 'degraded';\n this._updateHealth({\n status,\n latency: latencyMs,\n lastHealthCheck: timestamp,\n consecutiveFailures: 0,\n reconnectAttempts: this._health.reconnectAttempts\n });\n return {\n success: true,\n latencyMs,\n timestamp\n };\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n this.logger.warn('[HealthMonitor] Health check failed:', error.message);\n const consecutiveFailures = this._health.consecutiveFailures + 1;\n const status: ConnectionHealth['status'] = consecutiveFailures >= this.maxConsecutiveFailures ? 'disconnected' : 'degraded';\n this._updateHealth({\n status,\n latency: null,\n lastHealthCheck: timestamp,\n consecutiveFailures,\n reconnectAttempts: this._health.reconnectAttempts\n });\n return {\n success: false,\n error,\n timestamp\n };\n }\n }\n private _updateHealth(health: ConnectionHealth): void {\n const changed = this._hasHealthChanged(health);\n this._health = health;\n if (changed) {\n this._notifyListeners();\n }\n }\n private _hasHealthChanged(newHealth: ConnectionHealth): boolean {\n const old = this._health;\n return old.status !== newHealth.status || old.latency !== newHealth.latency || old.consecutiveFailures !== newHealth.consecutiveFailures || old.reconnectAttempts !== newHealth.reconnectAttempts;\n }\n private _notifyListeners(): void {\n const health = this.getHealth();\n\n // Call main callback\n this.onHealthChange?.(health);\n\n // Call all listeners\n for (const listener of this._listeners) {\n try {\n listener(health);\n } catch (err) {\n this.logger.warn('[HealthMonitor] Listener error:', err);\n }\n }\n }\n private _withTimeout<T>(promise: Promise<T>, timeoutMs: number): Promise<T> {\n return new Promise((resolve, reject) => {\n const timer = setTimeout(() => {\n this._pendingTimers.delete(timer);\n reject(new Error(`Health check timeout after ${timeoutMs}ms`));\n }, timeoutMs);\n this._pendingTimers.add(timer);\n promise.then(result => {\n this._pendingTimers.delete(timer);\n clearTimeout(timer);\n resolve(result);\n }, error => {\n this._pendingTimers.delete(timer);\n clearTimeout(timer);\n reject(error);\n });\n });\n }\n}"],"mappings":"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","names":[]}
|