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