@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
|
@@ -6,10 +6,12 @@ const DEFAULT_SESSION_CONTEXT_KEY = "sessionId";
|
|
|
6
6
|
const DEFAULT_SPILL_MAX_ENTRIES = 200;
|
|
7
7
|
const DEFAULT_SPILL_MAX_BYTES = 512 * 1024;
|
|
8
8
|
const DEFAULT_SPILL_MAX_ORIGIN_BYTES = 5 * 1024 * 1024;
|
|
9
|
-
const DB_VERSION =
|
|
9
|
+
const DB_VERSION = 4;
|
|
10
10
|
const ORDER_INDEX_NAME = "createdAtSeq";
|
|
11
11
|
const SESSION_INDEX_NAME = "sessionId";
|
|
12
12
|
const SESSION_ORDER_INDEX_NAME = "sessionIdCreatedAtSeq";
|
|
13
|
+
const SESSION_STORE_SUFFIX = "__sessions";
|
|
14
|
+
const SESSION_LAST_SEEN_INDEX_NAME = "lastSeenSessionId";
|
|
13
15
|
const SPILL_SCHEMA = "loggerjs.indexeddb-spill.v1";
|
|
14
16
|
function normalizePositiveInteger(value, fallback) {
|
|
15
17
|
if (value === void 0 || !Number.isFinite(value)) return fallback;
|
|
@@ -134,6 +136,9 @@ function indexExists(store, name) {
|
|
|
134
136
|
function ensureIndex(store, name, keyPath) {
|
|
135
137
|
if (!indexExists(store, name)) store.createIndex(name, keyPath);
|
|
136
138
|
}
|
|
139
|
+
function sessionStoreNameFor(storeName) {
|
|
140
|
+
return `${storeName}${SESSION_STORE_SUFFIX}`;
|
|
141
|
+
}
|
|
137
142
|
function getStoreIndex(store, name) {
|
|
138
143
|
const maybeStore = store;
|
|
139
144
|
if (typeof maybeStore.index !== "function" || !indexExists(store, name)) return void 0;
|
|
@@ -163,6 +168,44 @@ function shouldKeepEntry(entry, options) {
|
|
|
163
168
|
if (options.type !== void 0 && entry.type !== options.type) return false;
|
|
164
169
|
return true;
|
|
165
170
|
}
|
|
171
|
+
function addSessionSummary(sessions, entry) {
|
|
172
|
+
if (!entry.sessionId) return;
|
|
173
|
+
const current = sessions.get(entry.sessionId);
|
|
174
|
+
if (current) {
|
|
175
|
+
current.firstSeen = Math.min(current.firstSeen, entry.createdAt);
|
|
176
|
+
current.lastSeen = Math.max(current.lastSeen, entry.createdAt);
|
|
177
|
+
current.count += 1;
|
|
178
|
+
current.byteLength += entry.byteLength;
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
sessions.set(entry.sessionId, {
|
|
182
|
+
byteLength: entry.byteLength,
|
|
183
|
+
count: 1,
|
|
184
|
+
firstSeen: entry.createdAt,
|
|
185
|
+
lastSeen: entry.createdAt,
|
|
186
|
+
sessionId: entry.sessionId
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
function mergeSessionSummary(current, incoming) {
|
|
190
|
+
if (!current) return incoming;
|
|
191
|
+
return {
|
|
192
|
+
byteLength: current.byteLength + incoming.byteLength,
|
|
193
|
+
count: current.count + incoming.count,
|
|
194
|
+
firstSeen: Math.min(current.firstSeen, incoming.firstSeen),
|
|
195
|
+
lastSeen: Math.max(current.lastSeen, incoming.lastSeen),
|
|
196
|
+
sessionId: incoming.sessionId
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
function orderSessions(sessions, order = "desc") {
|
|
200
|
+
const sorted = [...sessions].sort((left, right) => {
|
|
201
|
+
if (left.lastSeen !== right.lastSeen) return left.lastSeen - right.lastSeen;
|
|
202
|
+
return left.sessionId < right.sessionId ? -1 : left.sessionId > right.sessionId ? 1 : 0;
|
|
203
|
+
});
|
|
204
|
+
if (order === "asc") return sorted;
|
|
205
|
+
const reversed = [];
|
|
206
|
+
for (let index = sorted.length - 1; index >= 0; index -= 1) reversed.push(sorted[index]);
|
|
207
|
+
return reversed;
|
|
208
|
+
}
|
|
166
209
|
function orderRangeForQuery(options) {
|
|
167
210
|
if (typeof IDBKeyRange === "undefined") return void 0;
|
|
168
211
|
if (options.sessionId !== void 0) {
|
|
@@ -194,6 +237,33 @@ function orderRangeForQuery(options) {
|
|
|
194
237
|
if (lower) return IDBKeyRange.lowerBound(lower);
|
|
195
238
|
if (upper) return IDBKeyRange.upperBound(upper);
|
|
196
239
|
}
|
|
240
|
+
function orderRangeForSessionId(sessionId) {
|
|
241
|
+
if (typeof IDBKeyRange === "undefined") return void 0;
|
|
242
|
+
return IDBKeyRange.bound([
|
|
243
|
+
sessionId,
|
|
244
|
+
Number.MIN_SAFE_INTEGER,
|
|
245
|
+
Number.MIN_SAFE_INTEGER,
|
|
246
|
+
""
|
|
247
|
+
], [
|
|
248
|
+
sessionId,
|
|
249
|
+
Number.MAX_SAFE_INTEGER,
|
|
250
|
+
Number.MAX_SAFE_INTEGER,
|
|
251
|
+
""
|
|
252
|
+
]);
|
|
253
|
+
}
|
|
254
|
+
function migrateSessionSummaries(logStore, sessionStore) {
|
|
255
|
+
const summaries = /* @__PURE__ */ new Map();
|
|
256
|
+
const request = logStore.openCursor();
|
|
257
|
+
request.addEventListener("success", () => {
|
|
258
|
+
const cursor = request.result;
|
|
259
|
+
if (!cursor) {
|
|
260
|
+
for (const summary of summaries.values()) sessionStore.put(summary);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
addSessionSummary(summaries, cursor.value);
|
|
264
|
+
cursor.continue();
|
|
265
|
+
});
|
|
266
|
+
}
|
|
197
267
|
function iterateCursor(request, visit) {
|
|
198
268
|
return new Promise((resolve, reject) => {
|
|
199
269
|
request.addEventListener("success", () => {
|
|
@@ -218,6 +288,7 @@ function iterateCursor(request, visit) {
|
|
|
218
288
|
function indexedDbTransport(options = {}) {
|
|
219
289
|
const dbName = options.dbName ?? DEFAULT_DB_NAME;
|
|
220
290
|
const storeName = options.storeName ?? DEFAULT_STORE_NAME;
|
|
291
|
+
const sessionStoreName = sessionStoreNameFor(storeName);
|
|
221
292
|
const codec = options.codec ?? safeJsonCodec();
|
|
222
293
|
const batchSize = normalizePositiveInteger(options.batchSize, 100);
|
|
223
294
|
const flushIntervalMs = normalizePositiveInteger(options.flushIntervalMs, 1e3);
|
|
@@ -236,6 +307,7 @@ function indexedDbTransport(options = {}) {
|
|
|
236
307
|
let indexedDBFactoryPromise;
|
|
237
308
|
let timer;
|
|
238
309
|
let flushPromise;
|
|
310
|
+
let clearPromise;
|
|
239
311
|
let pendingFlushBatch;
|
|
240
312
|
let spillDrainPromise;
|
|
241
313
|
let localStorageSpillClearCandidate;
|
|
@@ -346,7 +418,7 @@ function indexedDbTransport(options = {}) {
|
|
|
346
418
|
if (!idb) throw new Error("IndexedDB is not available for indexedDbTransport");
|
|
347
419
|
return new Promise((resolve, reject) => {
|
|
348
420
|
const request = idb.open(dbName, DB_VERSION);
|
|
349
|
-
request.addEventListener("upgradeneeded", () => {
|
|
421
|
+
request.addEventListener("upgradeneeded", (event) => {
|
|
350
422
|
const db = request.result;
|
|
351
423
|
const store = db.objectStoreNames.contains(storeName) ? request.transaction?.objectStore(storeName) : db.createObjectStore(storeName, { keyPath: "id" });
|
|
352
424
|
if (!store) return;
|
|
@@ -366,6 +438,10 @@ function indexedDbTransport(options = {}) {
|
|
|
366
438
|
"seq",
|
|
367
439
|
"id"
|
|
368
440
|
]);
|
|
441
|
+
const sessionStore = db.objectStoreNames.contains(sessionStoreName) ? request.transaction?.objectStore(sessionStoreName) : db.createObjectStore(sessionStoreName, { keyPath: "sessionId" });
|
|
442
|
+
if (!sessionStore) return;
|
|
443
|
+
ensureIndex(sessionStore, SESSION_LAST_SEEN_INDEX_NAME, ["lastSeen", "sessionId"]);
|
|
444
|
+
if (event.oldVersion < 4) migrateSessionSummaries(store, sessionStore);
|
|
369
445
|
});
|
|
370
446
|
request.addEventListener("success", () => {
|
|
371
447
|
statsState.databaseOpenCount += 1;
|
|
@@ -376,13 +452,13 @@ function indexedDbTransport(options = {}) {
|
|
|
376
452
|
});
|
|
377
453
|
return dbPromise;
|
|
378
454
|
};
|
|
379
|
-
const createTransaction = (db, mode) => {
|
|
380
|
-
if (mode !== "readwrite" || !transactionOptions) return db.transaction(
|
|
455
|
+
const createTransaction = (db, mode, stores = storeName) => {
|
|
456
|
+
if (mode !== "readwrite" || !transactionOptions) return db.transaction(stores, mode);
|
|
381
457
|
try {
|
|
382
|
-
return db.transaction(
|
|
458
|
+
return db.transaction(stores, mode, transactionOptions);
|
|
383
459
|
} catch {
|
|
384
460
|
statsState.transactionOptionFallbacks += 1;
|
|
385
|
-
return db.transaction(
|
|
461
|
+
return db.transaction(stores, mode);
|
|
386
462
|
}
|
|
387
463
|
};
|
|
388
464
|
const withStore = async (mode, run) => {
|
|
@@ -399,6 +475,57 @@ function indexedDbTransport(options = {}) {
|
|
|
399
475
|
throw error;
|
|
400
476
|
}
|
|
401
477
|
};
|
|
478
|
+
const withLogAndSessionStores = async (mode, run) => {
|
|
479
|
+
const tx = createTransaction(await openDb(), mode, [storeName, sessionStoreName]);
|
|
480
|
+
const settled = transactionToPromise(tx);
|
|
481
|
+
try {
|
|
482
|
+
const result = await run(tx.objectStore(storeName), tx.objectStore(sessionStoreName), tx);
|
|
483
|
+
await settled;
|
|
484
|
+
return result;
|
|
485
|
+
} catch (error) {
|
|
486
|
+
if (tx.error === null) try {
|
|
487
|
+
tx.abort();
|
|
488
|
+
} catch {}
|
|
489
|
+
throw error;
|
|
490
|
+
}
|
|
491
|
+
};
|
|
492
|
+
const withSessionStore = async (mode, run) => {
|
|
493
|
+
const tx = createTransaction(await openDb(), mode, sessionStoreName);
|
|
494
|
+
const settled = transactionToPromise(tx);
|
|
495
|
+
try {
|
|
496
|
+
const result = await run(tx.objectStore(sessionStoreName), tx);
|
|
497
|
+
await settled;
|
|
498
|
+
return result;
|
|
499
|
+
} catch (error) {
|
|
500
|
+
if (tx.error === null) try {
|
|
501
|
+
tx.abort();
|
|
502
|
+
} catch {}
|
|
503
|
+
throw error;
|
|
504
|
+
}
|
|
505
|
+
};
|
|
506
|
+
const mergeSessionSummaries = async (sessionStore, entries) => {
|
|
507
|
+
const summaries = /* @__PURE__ */ new Map();
|
|
508
|
+
for (const entry of entries) addSessionSummary(summaries, entry);
|
|
509
|
+
for (const summary of summaries.values()) {
|
|
510
|
+
const current = await requestToPromise(sessionStore.get(summary.sessionId));
|
|
511
|
+
await requestToPromise(sessionStore.put(mergeSessionSummary(current, summary)));
|
|
512
|
+
}
|
|
513
|
+
};
|
|
514
|
+
const rebuildSessionSummaries = async (store, sessionStore, sessionIds) => {
|
|
515
|
+
if (sessionIds.size === 0) return;
|
|
516
|
+
const index = getStoreIndex(store, SESSION_ORDER_INDEX_NAME);
|
|
517
|
+
for (const sessionId of sessionIds) {
|
|
518
|
+
const summaries = /* @__PURE__ */ new Map();
|
|
519
|
+
const range = orderRangeForSessionId(sessionId);
|
|
520
|
+
await iterateCursor(index && range ? index.openCursor(range) : store.openCursor(), (cursor) => {
|
|
521
|
+
const entry = cursor.value;
|
|
522
|
+
if (entry.sessionId === sessionId) addSessionSummary(summaries, entry);
|
|
523
|
+
});
|
|
524
|
+
const summary = summaries.get(sessionId);
|
|
525
|
+
if (summary) await requestToPromise(sessionStore.put(summary));
|
|
526
|
+
else await requestToPromise(sessionStore.delete(sessionId));
|
|
527
|
+
}
|
|
528
|
+
};
|
|
402
529
|
const getEntries = async () => orderEntries(await withStore("readonly", (store) => requestToPromise(store.getAll())));
|
|
403
530
|
const countEntries = async () => withStore("readonly", (store) => {
|
|
404
531
|
const maybeStore = store;
|
|
@@ -407,25 +534,31 @@ function indexedDbTransport(options = {}) {
|
|
|
407
534
|
});
|
|
408
535
|
const deleteEntries = async (entries, reason) => {
|
|
409
536
|
if (entries.length === 0) return;
|
|
410
|
-
await
|
|
537
|
+
await withLogAndSessionStores("readwrite", async (store, sessionStore) => {
|
|
538
|
+
const affectedSessionIds = /* @__PURE__ */ new Set();
|
|
411
539
|
for (const entry of entries) {
|
|
412
540
|
await requestToPromise(store.delete(entry.id));
|
|
541
|
+
if (entry.sessionId) affectedSessionIds.add(entry.sessionId);
|
|
413
542
|
dropPersistedEntry(entry, reason);
|
|
414
543
|
}
|
|
544
|
+
await rebuildSessionSummaries(store, sessionStore, affectedSessionIds);
|
|
415
545
|
});
|
|
416
546
|
};
|
|
417
|
-
const deleteByCursor = async (reason, openCursor, shouldDelete) =>
|
|
547
|
+
const deleteByCursor = async (reason, openCursor, shouldDelete) => withLogAndSessionStores("readwrite", async (store, sessionStore) => {
|
|
418
548
|
const request = openCursor(store);
|
|
419
549
|
if (!request) return void 0;
|
|
420
550
|
let deleted = 0;
|
|
551
|
+
const affectedSessionIds = /* @__PURE__ */ new Set();
|
|
421
552
|
await iterateCursor(request, (cursor) => {
|
|
422
553
|
const entry = cursor.value;
|
|
423
554
|
if (!shouldDelete(entry, deleted)) return false;
|
|
424
555
|
cursor.delete();
|
|
425
556
|
deleted += 1;
|
|
557
|
+
if (entry.sessionId) affectedSessionIds.add(entry.sessionId);
|
|
426
558
|
dropPersistedEntry(entry, reason);
|
|
427
559
|
return true;
|
|
428
560
|
});
|
|
561
|
+
await rebuildSessionSummaries(store, sessionStore, affectedSessionIds);
|
|
429
562
|
return deleted;
|
|
430
563
|
});
|
|
431
564
|
const deleteExpiredByCursor = async (cutoff) => {
|
|
@@ -470,29 +603,18 @@ function indexedDbTransport(options = {}) {
|
|
|
470
603
|
return entries;
|
|
471
604
|
});
|
|
472
605
|
const listSessions = async (sessionQueryOptions = {}) => {
|
|
473
|
-
const
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
const
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
} else sessions.set(entry.sessionId, {
|
|
483
|
-
byteLength: entry.byteLength,
|
|
484
|
-
count: 1,
|
|
485
|
-
firstSeen: entry.createdAt,
|
|
486
|
-
lastSeen: entry.createdAt,
|
|
487
|
-
sessionId: entry.sessionId
|
|
606
|
+
const limit = sessionQueryOptions.limit;
|
|
607
|
+
if (limit !== void 0 && limit <= 0) return [];
|
|
608
|
+
return withSessionStore("readonly", async (sessionStore) => {
|
|
609
|
+
const sessions = [];
|
|
610
|
+
const index = getStoreIndex(sessionStore, SESSION_LAST_SEEN_INDEX_NAME);
|
|
611
|
+
if (!index) return orderSessions(await requestToPromise(sessionStore.getAll()), sessionQueryOptions.order).slice(0, limit);
|
|
612
|
+
await iterateCursor(index.openCursor(void 0, sessionQueryOptions.order === "asc" ? "next" : "prev"), (cursor) => {
|
|
613
|
+
sessions.push(cursor.value);
|
|
614
|
+
return limit === void 0 || sessions.length < limit;
|
|
488
615
|
});
|
|
489
|
-
|
|
490
|
-
const ordered = [...sessions.values()].sort((left, right) => {
|
|
491
|
-
if (left.lastSeen !== right.lastSeen) return left.lastSeen - right.lastSeen;
|
|
492
|
-
return left.sessionId < right.sessionId ? -1 : left.sessionId > right.sessionId ? 1 : 0;
|
|
616
|
+
return sessions;
|
|
493
617
|
});
|
|
494
|
-
if (sessionQueryOptions.order !== "asc") ordered.reverse();
|
|
495
|
-
return ordered.slice(0, sessionQueryOptions.limit);
|
|
496
618
|
};
|
|
497
619
|
const pruneWithEntries = async () => {
|
|
498
620
|
const now = Date.now();
|
|
@@ -617,8 +739,16 @@ function indexedDbTransport(options = {}) {
|
|
|
617
739
|
statsState.lastFlushBatchSize = events.length;
|
|
618
740
|
const entries = events.map(eventToEntry);
|
|
619
741
|
try {
|
|
620
|
-
await
|
|
621
|
-
|
|
742
|
+
await withLogAndSessionStores("readwrite", async (store, sessionStore) => {
|
|
743
|
+
const rebuildSessionIds = /* @__PURE__ */ new Set();
|
|
744
|
+
for (const entry of entries) {
|
|
745
|
+
const existing = await requestToPromise(store.get(entry.id));
|
|
746
|
+
if (existing?.sessionId) rebuildSessionIds.add(existing.sessionId);
|
|
747
|
+
if (existing && entry.sessionId) rebuildSessionIds.add(entry.sessionId);
|
|
748
|
+
await requestToPromise(store.put(entry));
|
|
749
|
+
}
|
|
750
|
+
await rebuildSessionSummaries(store, sessionStore, rebuildSessionIds);
|
|
751
|
+
await mergeSessionSummaries(sessionStore, entries.filter((entry) => !entry.sessionId || !rebuildSessionIds.has(entry.sessionId)));
|
|
622
752
|
});
|
|
623
753
|
statsState.persisted += entries.length;
|
|
624
754
|
incrementLoggerMetaCounter("transport.indexeddb.persisted", entries.length);
|
|
@@ -759,7 +889,9 @@ function indexedDbTransport(options = {}) {
|
|
|
759
889
|
clearTimer();
|
|
760
890
|
const batch = buffer.splice(0, buffer.length);
|
|
761
891
|
pendingFlushBatch = batch;
|
|
892
|
+
const clearBarrier = clearPromise;
|
|
762
893
|
flushPromise = (async () => {
|
|
894
|
+
await clearBarrier;
|
|
763
895
|
await ensureSpillDrained();
|
|
764
896
|
await writeBatch(batch);
|
|
765
897
|
})().finally(() => {
|
|
@@ -828,17 +960,36 @@ function indexedDbTransport(options = {}) {
|
|
|
828
960
|
return await countEntries() ?? (await getEntries()).length;
|
|
829
961
|
},
|
|
830
962
|
async clear() {
|
|
963
|
+
if (clearPromise) return clearPromise;
|
|
964
|
+
const inFlightFlush = flushPromise;
|
|
965
|
+
const inFlightDrain = spillDrainPromise;
|
|
831
966
|
buffer.length = 0;
|
|
832
967
|
clearTimer();
|
|
833
968
|
clearLocalStorageSpill();
|
|
834
|
-
|
|
969
|
+
clearPromise = (async () => {
|
|
970
|
+
await inFlightFlush;
|
|
971
|
+
await inFlightDrain;
|
|
972
|
+
await withLogAndSessionStores("readwrite", async (store, sessionStore) => {
|
|
973
|
+
await requestToPromise(store.clear());
|
|
974
|
+
await requestToPromise(sessionStore.clear());
|
|
975
|
+
});
|
|
976
|
+
})().finally(() => {
|
|
977
|
+
clearPromise = void 0;
|
|
978
|
+
});
|
|
979
|
+
return clearPromise;
|
|
835
980
|
},
|
|
836
981
|
async remove(ids) {
|
|
837
982
|
await flushPending();
|
|
838
983
|
const items = typeof ids === "string" ? [ids] : ids;
|
|
839
984
|
if (items.length === 0) return;
|
|
840
|
-
await
|
|
841
|
-
|
|
985
|
+
await withLogAndSessionStores("readwrite", async (store, sessionStore) => {
|
|
986
|
+
const affectedSessionIds = /* @__PURE__ */ new Set();
|
|
987
|
+
for (const id of items) {
|
|
988
|
+
const entry = await requestToPromise(store.get(id));
|
|
989
|
+
if (entry?.sessionId) affectedSessionIds.add(entry.sessionId);
|
|
990
|
+
await requestToPromise(store.delete(id));
|
|
991
|
+
}
|
|
992
|
+
await rebuildSessionSummaries(store, sessionStore, affectedSessionIds);
|
|
842
993
|
});
|
|
843
994
|
},
|
|
844
995
|
async *query(queryOptions = {}) {
|