@loggerjs/browser 0.5.3 → 0.5.4
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 +31 -0
- package/dist/index.cjs +185 -34
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +185 -34
- package/dist/index.js.map +1 -1
- package/dist/indexeddb-transport.cjs +185 -34
- package/dist/indexeddb-transport.cjs.map +1 -1
- package/dist/indexeddb-transport.d.ts.map +1 -1
- package/dist/indexeddb-transport.js +185 -34
- package/dist/indexeddb-transport.js.map +1 -1
- package/dist/offline-first-transport.cjs +185 -34
- package/dist/offline-first-transport.cjs.map +1 -1
- package/dist/offline-first-transport.js +185 -34
- package/dist/offline-first-transport.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -898,10 +898,12 @@ const DEFAULT_SESSION_CONTEXT_KEY$1 = "sessionId";
|
|
|
898
898
|
const DEFAULT_SPILL_MAX_ENTRIES = 200;
|
|
899
899
|
const DEFAULT_SPILL_MAX_BYTES = 512 * 1024;
|
|
900
900
|
const DEFAULT_SPILL_MAX_ORIGIN_BYTES = 5 * 1024 * 1024;
|
|
901
|
-
const DB_VERSION =
|
|
901
|
+
const DB_VERSION = 4;
|
|
902
902
|
const ORDER_INDEX_NAME = "createdAtSeq";
|
|
903
903
|
const SESSION_INDEX_NAME = "sessionId";
|
|
904
904
|
const SESSION_ORDER_INDEX_NAME = "sessionIdCreatedAtSeq";
|
|
905
|
+
const SESSION_STORE_SUFFIX = "__sessions";
|
|
906
|
+
const SESSION_LAST_SEEN_INDEX_NAME = "lastSeenSessionId";
|
|
905
907
|
const SPILL_SCHEMA = "loggerjs.indexeddb-spill.v1";
|
|
906
908
|
function normalizePositiveInteger(value, fallback) {
|
|
907
909
|
if (value === void 0 || !Number.isFinite(value)) return fallback;
|
|
@@ -1026,6 +1028,9 @@ function indexExists(store, name) {
|
|
|
1026
1028
|
function ensureIndex(store, name, keyPath) {
|
|
1027
1029
|
if (!indexExists(store, name)) store.createIndex(name, keyPath);
|
|
1028
1030
|
}
|
|
1031
|
+
function sessionStoreNameFor(storeName) {
|
|
1032
|
+
return `${storeName}${SESSION_STORE_SUFFIX}`;
|
|
1033
|
+
}
|
|
1029
1034
|
function getStoreIndex(store, name) {
|
|
1030
1035
|
const maybeStore = store;
|
|
1031
1036
|
if (typeof maybeStore.index !== "function" || !indexExists(store, name)) return void 0;
|
|
@@ -1055,6 +1060,44 @@ function shouldKeepEntry(entry, options) {
|
|
|
1055
1060
|
if (options.type !== void 0 && entry.type !== options.type) return false;
|
|
1056
1061
|
return true;
|
|
1057
1062
|
}
|
|
1063
|
+
function addSessionSummary(sessions, entry) {
|
|
1064
|
+
if (!entry.sessionId) return;
|
|
1065
|
+
const current = sessions.get(entry.sessionId);
|
|
1066
|
+
if (current) {
|
|
1067
|
+
current.firstSeen = Math.min(current.firstSeen, entry.createdAt);
|
|
1068
|
+
current.lastSeen = Math.max(current.lastSeen, entry.createdAt);
|
|
1069
|
+
current.count += 1;
|
|
1070
|
+
current.byteLength += entry.byteLength;
|
|
1071
|
+
return;
|
|
1072
|
+
}
|
|
1073
|
+
sessions.set(entry.sessionId, {
|
|
1074
|
+
byteLength: entry.byteLength,
|
|
1075
|
+
count: 1,
|
|
1076
|
+
firstSeen: entry.createdAt,
|
|
1077
|
+
lastSeen: entry.createdAt,
|
|
1078
|
+
sessionId: entry.sessionId
|
|
1079
|
+
});
|
|
1080
|
+
}
|
|
1081
|
+
function mergeSessionSummary(current, incoming) {
|
|
1082
|
+
if (!current) return incoming;
|
|
1083
|
+
return {
|
|
1084
|
+
byteLength: current.byteLength + incoming.byteLength,
|
|
1085
|
+
count: current.count + incoming.count,
|
|
1086
|
+
firstSeen: Math.min(current.firstSeen, incoming.firstSeen),
|
|
1087
|
+
lastSeen: Math.max(current.lastSeen, incoming.lastSeen),
|
|
1088
|
+
sessionId: incoming.sessionId
|
|
1089
|
+
};
|
|
1090
|
+
}
|
|
1091
|
+
function orderSessions(sessions, order = "desc") {
|
|
1092
|
+
const sorted = [...sessions].sort((left, right) => {
|
|
1093
|
+
if (left.lastSeen !== right.lastSeen) return left.lastSeen - right.lastSeen;
|
|
1094
|
+
return left.sessionId < right.sessionId ? -1 : left.sessionId > right.sessionId ? 1 : 0;
|
|
1095
|
+
});
|
|
1096
|
+
if (order === "asc") return sorted;
|
|
1097
|
+
const reversed = [];
|
|
1098
|
+
for (let index = sorted.length - 1; index >= 0; index -= 1) reversed.push(sorted[index]);
|
|
1099
|
+
return reversed;
|
|
1100
|
+
}
|
|
1058
1101
|
function orderRangeForQuery(options) {
|
|
1059
1102
|
if (typeof IDBKeyRange === "undefined") return void 0;
|
|
1060
1103
|
if (options.sessionId !== void 0) {
|
|
@@ -1086,6 +1129,33 @@ function orderRangeForQuery(options) {
|
|
|
1086
1129
|
if (lower) return IDBKeyRange.lowerBound(lower);
|
|
1087
1130
|
if (upper) return IDBKeyRange.upperBound(upper);
|
|
1088
1131
|
}
|
|
1132
|
+
function orderRangeForSessionId(sessionId) {
|
|
1133
|
+
if (typeof IDBKeyRange === "undefined") return void 0;
|
|
1134
|
+
return IDBKeyRange.bound([
|
|
1135
|
+
sessionId,
|
|
1136
|
+
Number.MIN_SAFE_INTEGER,
|
|
1137
|
+
Number.MIN_SAFE_INTEGER,
|
|
1138
|
+
""
|
|
1139
|
+
], [
|
|
1140
|
+
sessionId,
|
|
1141
|
+
Number.MAX_SAFE_INTEGER,
|
|
1142
|
+
Number.MAX_SAFE_INTEGER,
|
|
1143
|
+
""
|
|
1144
|
+
]);
|
|
1145
|
+
}
|
|
1146
|
+
function migrateSessionSummaries(logStore, sessionStore) {
|
|
1147
|
+
const summaries = /* @__PURE__ */ new Map();
|
|
1148
|
+
const request = logStore.openCursor();
|
|
1149
|
+
request.addEventListener("success", () => {
|
|
1150
|
+
const cursor = request.result;
|
|
1151
|
+
if (!cursor) {
|
|
1152
|
+
for (const summary of summaries.values()) sessionStore.put(summary);
|
|
1153
|
+
return;
|
|
1154
|
+
}
|
|
1155
|
+
addSessionSummary(summaries, cursor.value);
|
|
1156
|
+
cursor.continue();
|
|
1157
|
+
});
|
|
1158
|
+
}
|
|
1089
1159
|
function iterateCursor(request, visit) {
|
|
1090
1160
|
return new Promise((resolve, reject) => {
|
|
1091
1161
|
request.addEventListener("success", () => {
|
|
@@ -1110,6 +1180,7 @@ function iterateCursor(request, visit) {
|
|
|
1110
1180
|
function indexedDbTransport(options = {}) {
|
|
1111
1181
|
const dbName = options.dbName ?? DEFAULT_DB_NAME;
|
|
1112
1182
|
const storeName = options.storeName ?? DEFAULT_STORE_NAME;
|
|
1183
|
+
const sessionStoreName = sessionStoreNameFor(storeName);
|
|
1113
1184
|
const codec = options.codec ?? safeJsonCodec();
|
|
1114
1185
|
const batchSize = normalizePositiveInteger(options.batchSize, 100);
|
|
1115
1186
|
const flushIntervalMs = normalizePositiveInteger(options.flushIntervalMs, 1e3);
|
|
@@ -1128,6 +1199,7 @@ function indexedDbTransport(options = {}) {
|
|
|
1128
1199
|
let indexedDBFactoryPromise;
|
|
1129
1200
|
let timer;
|
|
1130
1201
|
let flushPromise;
|
|
1202
|
+
let clearPromise;
|
|
1131
1203
|
let pendingFlushBatch;
|
|
1132
1204
|
let spillDrainPromise;
|
|
1133
1205
|
let localStorageSpillClearCandidate;
|
|
@@ -1238,7 +1310,7 @@ function indexedDbTransport(options = {}) {
|
|
|
1238
1310
|
if (!idb) throw new Error("IndexedDB is not available for indexedDbTransport");
|
|
1239
1311
|
return new Promise((resolve, reject) => {
|
|
1240
1312
|
const request = idb.open(dbName, DB_VERSION);
|
|
1241
|
-
request.addEventListener("upgradeneeded", () => {
|
|
1313
|
+
request.addEventListener("upgradeneeded", (event) => {
|
|
1242
1314
|
const db = request.result;
|
|
1243
1315
|
const store = db.objectStoreNames.contains(storeName) ? request.transaction?.objectStore(storeName) : db.createObjectStore(storeName, { keyPath: "id" });
|
|
1244
1316
|
if (!store) return;
|
|
@@ -1258,6 +1330,10 @@ function indexedDbTransport(options = {}) {
|
|
|
1258
1330
|
"seq",
|
|
1259
1331
|
"id"
|
|
1260
1332
|
]);
|
|
1333
|
+
const sessionStore = db.objectStoreNames.contains(sessionStoreName) ? request.transaction?.objectStore(sessionStoreName) : db.createObjectStore(sessionStoreName, { keyPath: "sessionId" });
|
|
1334
|
+
if (!sessionStore) return;
|
|
1335
|
+
ensureIndex(sessionStore, SESSION_LAST_SEEN_INDEX_NAME, ["lastSeen", "sessionId"]);
|
|
1336
|
+
if (event.oldVersion < 4) migrateSessionSummaries(store, sessionStore);
|
|
1261
1337
|
});
|
|
1262
1338
|
request.addEventListener("success", () => {
|
|
1263
1339
|
statsState.databaseOpenCount += 1;
|
|
@@ -1268,13 +1344,13 @@ function indexedDbTransport(options = {}) {
|
|
|
1268
1344
|
});
|
|
1269
1345
|
return dbPromise;
|
|
1270
1346
|
};
|
|
1271
|
-
const createTransaction = (db, mode) => {
|
|
1272
|
-
if (mode !== "readwrite" || !transactionOptions) return db.transaction(
|
|
1347
|
+
const createTransaction = (db, mode, stores = storeName) => {
|
|
1348
|
+
if (mode !== "readwrite" || !transactionOptions) return db.transaction(stores, mode);
|
|
1273
1349
|
try {
|
|
1274
|
-
return db.transaction(
|
|
1350
|
+
return db.transaction(stores, mode, transactionOptions);
|
|
1275
1351
|
} catch {
|
|
1276
1352
|
statsState.transactionOptionFallbacks += 1;
|
|
1277
|
-
return db.transaction(
|
|
1353
|
+
return db.transaction(stores, mode);
|
|
1278
1354
|
}
|
|
1279
1355
|
};
|
|
1280
1356
|
const withStore = async (mode, run) => {
|
|
@@ -1291,6 +1367,57 @@ function indexedDbTransport(options = {}) {
|
|
|
1291
1367
|
throw error;
|
|
1292
1368
|
}
|
|
1293
1369
|
};
|
|
1370
|
+
const withLogAndSessionStores = async (mode, run) => {
|
|
1371
|
+
const tx = createTransaction(await openDb(), mode, [storeName, sessionStoreName]);
|
|
1372
|
+
const settled = transactionToPromise(tx);
|
|
1373
|
+
try {
|
|
1374
|
+
const result = await run(tx.objectStore(storeName), tx.objectStore(sessionStoreName), tx);
|
|
1375
|
+
await settled;
|
|
1376
|
+
return result;
|
|
1377
|
+
} catch (error) {
|
|
1378
|
+
if (tx.error === null) try {
|
|
1379
|
+
tx.abort();
|
|
1380
|
+
} catch {}
|
|
1381
|
+
throw error;
|
|
1382
|
+
}
|
|
1383
|
+
};
|
|
1384
|
+
const withSessionStore = async (mode, run) => {
|
|
1385
|
+
const tx = createTransaction(await openDb(), mode, sessionStoreName);
|
|
1386
|
+
const settled = transactionToPromise(tx);
|
|
1387
|
+
try {
|
|
1388
|
+
const result = await run(tx.objectStore(sessionStoreName), tx);
|
|
1389
|
+
await settled;
|
|
1390
|
+
return result;
|
|
1391
|
+
} catch (error) {
|
|
1392
|
+
if (tx.error === null) try {
|
|
1393
|
+
tx.abort();
|
|
1394
|
+
} catch {}
|
|
1395
|
+
throw error;
|
|
1396
|
+
}
|
|
1397
|
+
};
|
|
1398
|
+
const mergeSessionSummaries = async (sessionStore, entries) => {
|
|
1399
|
+
const summaries = /* @__PURE__ */ new Map();
|
|
1400
|
+
for (const entry of entries) addSessionSummary(summaries, entry);
|
|
1401
|
+
for (const summary of summaries.values()) {
|
|
1402
|
+
const current = await requestToPromise(sessionStore.get(summary.sessionId));
|
|
1403
|
+
await requestToPromise(sessionStore.put(mergeSessionSummary(current, summary)));
|
|
1404
|
+
}
|
|
1405
|
+
};
|
|
1406
|
+
const rebuildSessionSummaries = async (store, sessionStore, sessionIds) => {
|
|
1407
|
+
if (sessionIds.size === 0) return;
|
|
1408
|
+
const index = getStoreIndex(store, SESSION_ORDER_INDEX_NAME);
|
|
1409
|
+
for (const sessionId of sessionIds) {
|
|
1410
|
+
const summaries = /* @__PURE__ */ new Map();
|
|
1411
|
+
const range = orderRangeForSessionId(sessionId);
|
|
1412
|
+
await iterateCursor(index && range ? index.openCursor(range) : store.openCursor(), (cursor) => {
|
|
1413
|
+
const entry = cursor.value;
|
|
1414
|
+
if (entry.sessionId === sessionId) addSessionSummary(summaries, entry);
|
|
1415
|
+
});
|
|
1416
|
+
const summary = summaries.get(sessionId);
|
|
1417
|
+
if (summary) await requestToPromise(sessionStore.put(summary));
|
|
1418
|
+
else await requestToPromise(sessionStore.delete(sessionId));
|
|
1419
|
+
}
|
|
1420
|
+
};
|
|
1294
1421
|
const getEntries = async () => orderEntries(await withStore("readonly", (store) => requestToPromise(store.getAll())));
|
|
1295
1422
|
const countEntries = async () => withStore("readonly", (store) => {
|
|
1296
1423
|
const maybeStore = store;
|
|
@@ -1299,25 +1426,31 @@ function indexedDbTransport(options = {}) {
|
|
|
1299
1426
|
});
|
|
1300
1427
|
const deleteEntries = async (entries, reason) => {
|
|
1301
1428
|
if (entries.length === 0) return;
|
|
1302
|
-
await
|
|
1429
|
+
await withLogAndSessionStores("readwrite", async (store, sessionStore) => {
|
|
1430
|
+
const affectedSessionIds = /* @__PURE__ */ new Set();
|
|
1303
1431
|
for (const entry of entries) {
|
|
1304
1432
|
await requestToPromise(store.delete(entry.id));
|
|
1433
|
+
if (entry.sessionId) affectedSessionIds.add(entry.sessionId);
|
|
1305
1434
|
dropPersistedEntry(entry, reason);
|
|
1306
1435
|
}
|
|
1436
|
+
await rebuildSessionSummaries(store, sessionStore, affectedSessionIds);
|
|
1307
1437
|
});
|
|
1308
1438
|
};
|
|
1309
|
-
const deleteByCursor = async (reason, openCursor, shouldDelete) =>
|
|
1439
|
+
const deleteByCursor = async (reason, openCursor, shouldDelete) => withLogAndSessionStores("readwrite", async (store, sessionStore) => {
|
|
1310
1440
|
const request = openCursor(store);
|
|
1311
1441
|
if (!request) return void 0;
|
|
1312
1442
|
let deleted = 0;
|
|
1443
|
+
const affectedSessionIds = /* @__PURE__ */ new Set();
|
|
1313
1444
|
await iterateCursor(request, (cursor) => {
|
|
1314
1445
|
const entry = cursor.value;
|
|
1315
1446
|
if (!shouldDelete(entry, deleted)) return false;
|
|
1316
1447
|
cursor.delete();
|
|
1317
1448
|
deleted += 1;
|
|
1449
|
+
if (entry.sessionId) affectedSessionIds.add(entry.sessionId);
|
|
1318
1450
|
dropPersistedEntry(entry, reason);
|
|
1319
1451
|
return true;
|
|
1320
1452
|
});
|
|
1453
|
+
await rebuildSessionSummaries(store, sessionStore, affectedSessionIds);
|
|
1321
1454
|
return deleted;
|
|
1322
1455
|
});
|
|
1323
1456
|
const deleteExpiredByCursor = async (cutoff) => {
|
|
@@ -1362,29 +1495,18 @@ function indexedDbTransport(options = {}) {
|
|
|
1362
1495
|
return entries;
|
|
1363
1496
|
});
|
|
1364
1497
|
const listSessions = async (sessionQueryOptions = {}) => {
|
|
1365
|
-
const
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
const
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
} else sessions.set(entry.sessionId, {
|
|
1375
|
-
byteLength: entry.byteLength,
|
|
1376
|
-
count: 1,
|
|
1377
|
-
firstSeen: entry.createdAt,
|
|
1378
|
-
lastSeen: entry.createdAt,
|
|
1379
|
-
sessionId: entry.sessionId
|
|
1498
|
+
const limit = sessionQueryOptions.limit;
|
|
1499
|
+
if (limit !== void 0 && limit <= 0) return [];
|
|
1500
|
+
return withSessionStore("readonly", async (sessionStore) => {
|
|
1501
|
+
const sessions = [];
|
|
1502
|
+
const index = getStoreIndex(sessionStore, SESSION_LAST_SEEN_INDEX_NAME);
|
|
1503
|
+
if (!index) return orderSessions(await requestToPromise(sessionStore.getAll()), sessionQueryOptions.order).slice(0, limit);
|
|
1504
|
+
await iterateCursor(index.openCursor(void 0, sessionQueryOptions.order === "asc" ? "next" : "prev"), (cursor) => {
|
|
1505
|
+
sessions.push(cursor.value);
|
|
1506
|
+
return limit === void 0 || sessions.length < limit;
|
|
1380
1507
|
});
|
|
1381
|
-
|
|
1382
|
-
const ordered = [...sessions.values()].sort((left, right) => {
|
|
1383
|
-
if (left.lastSeen !== right.lastSeen) return left.lastSeen - right.lastSeen;
|
|
1384
|
-
return left.sessionId < right.sessionId ? -1 : left.sessionId > right.sessionId ? 1 : 0;
|
|
1508
|
+
return sessions;
|
|
1385
1509
|
});
|
|
1386
|
-
if (sessionQueryOptions.order !== "asc") ordered.reverse();
|
|
1387
|
-
return ordered.slice(0, sessionQueryOptions.limit);
|
|
1388
1510
|
};
|
|
1389
1511
|
const pruneWithEntries = async () => {
|
|
1390
1512
|
const now = Date.now();
|
|
@@ -1509,8 +1631,16 @@ function indexedDbTransport(options = {}) {
|
|
|
1509
1631
|
statsState.lastFlushBatchSize = events.length;
|
|
1510
1632
|
const entries = events.map(eventToEntry);
|
|
1511
1633
|
try {
|
|
1512
|
-
await
|
|
1513
|
-
|
|
1634
|
+
await withLogAndSessionStores("readwrite", async (store, sessionStore) => {
|
|
1635
|
+
const rebuildSessionIds = /* @__PURE__ */ new Set();
|
|
1636
|
+
for (const entry of entries) {
|
|
1637
|
+
const existing = await requestToPromise(store.get(entry.id));
|
|
1638
|
+
if (existing?.sessionId) rebuildSessionIds.add(existing.sessionId);
|
|
1639
|
+
if (existing && entry.sessionId) rebuildSessionIds.add(entry.sessionId);
|
|
1640
|
+
await requestToPromise(store.put(entry));
|
|
1641
|
+
}
|
|
1642
|
+
await rebuildSessionSummaries(store, sessionStore, rebuildSessionIds);
|
|
1643
|
+
await mergeSessionSummaries(sessionStore, entries.filter((entry) => !entry.sessionId || !rebuildSessionIds.has(entry.sessionId)));
|
|
1514
1644
|
});
|
|
1515
1645
|
statsState.persisted += entries.length;
|
|
1516
1646
|
incrementLoggerMetaCounter("transport.indexeddb.persisted", entries.length);
|
|
@@ -1651,7 +1781,9 @@ function indexedDbTransport(options = {}) {
|
|
|
1651
1781
|
clearTimer();
|
|
1652
1782
|
const batch = buffer.splice(0, buffer.length);
|
|
1653
1783
|
pendingFlushBatch = batch;
|
|
1784
|
+
const clearBarrier = clearPromise;
|
|
1654
1785
|
flushPromise = (async () => {
|
|
1786
|
+
await clearBarrier;
|
|
1655
1787
|
await ensureSpillDrained();
|
|
1656
1788
|
await writeBatch(batch);
|
|
1657
1789
|
})().finally(() => {
|
|
@@ -1720,17 +1852,36 @@ function indexedDbTransport(options = {}) {
|
|
|
1720
1852
|
return await countEntries() ?? (await getEntries()).length;
|
|
1721
1853
|
},
|
|
1722
1854
|
async clear() {
|
|
1855
|
+
if (clearPromise) return clearPromise;
|
|
1856
|
+
const inFlightFlush = flushPromise;
|
|
1857
|
+
const inFlightDrain = spillDrainPromise;
|
|
1723
1858
|
buffer.length = 0;
|
|
1724
1859
|
clearTimer();
|
|
1725
1860
|
clearLocalStorageSpill();
|
|
1726
|
-
|
|
1861
|
+
clearPromise = (async () => {
|
|
1862
|
+
await inFlightFlush;
|
|
1863
|
+
await inFlightDrain;
|
|
1864
|
+
await withLogAndSessionStores("readwrite", async (store, sessionStore) => {
|
|
1865
|
+
await requestToPromise(store.clear());
|
|
1866
|
+
await requestToPromise(sessionStore.clear());
|
|
1867
|
+
});
|
|
1868
|
+
})().finally(() => {
|
|
1869
|
+
clearPromise = void 0;
|
|
1870
|
+
});
|
|
1871
|
+
return clearPromise;
|
|
1727
1872
|
},
|
|
1728
1873
|
async remove(ids) {
|
|
1729
1874
|
await flushPending();
|
|
1730
1875
|
const items = typeof ids === "string" ? [ids] : ids;
|
|
1731
1876
|
if (items.length === 0) return;
|
|
1732
|
-
await
|
|
1733
|
-
|
|
1877
|
+
await withLogAndSessionStores("readwrite", async (store, sessionStore) => {
|
|
1878
|
+
const affectedSessionIds = /* @__PURE__ */ new Set();
|
|
1879
|
+
for (const id of items) {
|
|
1880
|
+
const entry = await requestToPromise(store.get(id));
|
|
1881
|
+
if (entry?.sessionId) affectedSessionIds.add(entry.sessionId);
|
|
1882
|
+
await requestToPromise(store.delete(id));
|
|
1883
|
+
}
|
|
1884
|
+
await rebuildSessionSummaries(store, sessionStore, affectedSessionIds);
|
|
1734
1885
|
});
|
|
1735
1886
|
},
|
|
1736
1887
|
async *query(queryOptions = {}) {
|