@loggerjs/browser 0.5.1 → 0.5.3

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.
@@ -3,12 +3,96 @@ let _loggerjs_core = require("@loggerjs/core");
3
3
  //#region src/indexeddb-transport.ts
4
4
  const DEFAULT_DB_NAME = "loggerjs";
5
5
  const DEFAULT_STORE_NAME = "logs";
6
- const DB_VERSION = 2;
6
+ const DEFAULT_SESSION_CONTEXT_KEY = "sessionId";
7
+ const DEFAULT_SPILL_MAX_ENTRIES = 200;
8
+ const DEFAULT_SPILL_MAX_BYTES = 512 * 1024;
9
+ const DEFAULT_SPILL_MAX_ORIGIN_BYTES = 5 * 1024 * 1024;
10
+ const DB_VERSION = 3;
7
11
  const ORDER_INDEX_NAME = "createdAtSeq";
12
+ const SESSION_INDEX_NAME = "sessionId";
13
+ const SESSION_ORDER_INDEX_NAME = "sessionIdCreatedAtSeq";
14
+ const SPILL_SCHEMA = "loggerjs.indexeddb-spill.v1";
8
15
  function normalizePositiveInteger(value, fallback) {
9
16
  if (value === void 0 || !Number.isFinite(value)) return fallback;
10
17
  return Math.max(0, Math.floor(value));
11
18
  }
19
+ let fallbackSessionSeq = 0;
20
+ function defaultPageSessionId() {
21
+ const randomUUID = globalThis.crypto?.randomUUID;
22
+ if (randomUUID) return randomUUID.call(globalThis.crypto);
23
+ return `${Date.now().toString(36)}-${(fallbackSessionSeq++).toString(36)}`;
24
+ }
25
+ function normalizeSessionId(value) {
26
+ if (typeof value !== "string") return void 0;
27
+ const normalized = value.trim();
28
+ return normalized.length > 0 ? normalized : void 0;
29
+ }
30
+ function normalizeSessionOptions(session) {
31
+ if (session === false) return void 0;
32
+ if (typeof session === "string") return {
33
+ contextKey: DEFAULT_SESSION_CONTEXT_KEY,
34
+ id: normalizeSessionId(session) ?? defaultPageSessionId()
35
+ };
36
+ return {
37
+ contextKey: session?.contextKey ?? DEFAULT_SESSION_CONTEXT_KEY,
38
+ getId: session?.getId,
39
+ id: normalizeSessionId(session?.id) ?? defaultPageSessionId()
40
+ };
41
+ }
42
+ function resolveLocalStorage(storage) {
43
+ if (storage) return storage;
44
+ try {
45
+ return globalThis.localStorage;
46
+ } catch {
47
+ return;
48
+ }
49
+ }
50
+ function normalizeLocalStorageSpillOptions(spill, dbName, storeName) {
51
+ if (!spill) return void 0;
52
+ const storage = resolveLocalStorage(spill.storage);
53
+ if (!storage) return void 0;
54
+ const namespace = spill.namespace ?? `${dbName}:${storeName}`;
55
+ return {
56
+ drainOnCreate: spill.drainOnCreate ?? true,
57
+ key: `loggerjs:spill:v1:${namespace}`,
58
+ maxBytes: normalizePositiveInteger(spill.maxBytes, DEFAULT_SPILL_MAX_BYTES),
59
+ maxEntries: normalizePositiveInteger(spill.maxEntries, DEFAULT_SPILL_MAX_ENTRIES),
60
+ maxOriginBytes: normalizePositiveInteger(spill.maxOriginBytes, DEFAULT_SPILL_MAX_ORIGIN_BYTES),
61
+ minLevelValue: spill.minLevel === void 0 ? void 0 : (0, _loggerjs_core.toLevelValue)(spill.minLevel),
62
+ onDrop: spill.onDrop,
63
+ spillOnPageHide: spill.spillOnPageHide ?? true,
64
+ storage
65
+ };
66
+ }
67
+ function storageByteLength(key, value) {
68
+ return 2 * (key.length + value.length);
69
+ }
70
+ function estimateLocalStorageBytes(storage) {
71
+ try {
72
+ let total = 0;
73
+ for (let index = 0; index < storage.length; index += 1) {
74
+ const key = storage.key(index);
75
+ if (key === null) continue;
76
+ total += storageByteLength(key, storage.getItem(key) ?? "");
77
+ }
78
+ return total;
79
+ } catch {
80
+ return;
81
+ }
82
+ }
83
+ function isQuotaExceededError(error) {
84
+ return typeof DOMException !== "undefined" && error instanceof DOMException && (error.name === "QuotaExceededError" || error.name === "NS_ERROR_DOM_QUOTA_REACHED");
85
+ }
86
+ function isSpillLogEvent(value) {
87
+ if (!value || typeof value !== "object") return false;
88
+ const event = value;
89
+ return typeof event.id === "string" && typeof event.time === "number" && typeof event.seq === "number" && typeof event.level === "number" && typeof event.levelName === "string" && typeof event.logger === "string" && typeof event.message === "string";
90
+ }
91
+ function parseSpillPayload(raw) {
92
+ const parsed = JSON.parse(raw);
93
+ if (parsed.schema !== SPILL_SCHEMA || !Array.isArray(parsed.entries)) return void 0;
94
+ return parsed.entries.filter(isSpillLogEvent);
95
+ }
12
96
  function nowMs() {
13
97
  return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
14
98
  }
@@ -74,6 +158,7 @@ function decodeEntry(entry, codec) {
74
158
  function shouldKeepEntry(entry, options) {
75
159
  if (options.from !== void 0 && entry.createdAt < options.from) return false;
76
160
  if (options.to !== void 0 && entry.createdAt > options.to) return false;
161
+ if (options.sessionId !== void 0 && entry.sessionId !== options.sessionId) return false;
77
162
  if (options.minLevel !== void 0 && entry.level < (0, _loggerjs_core.toLevelValue)(options.minLevel)) return false;
78
163
  if (options.logger !== void 0 && entry.logger !== options.logger) return false;
79
164
  if (options.type !== void 0 && entry.type !== options.type) return false;
@@ -81,6 +166,21 @@ function shouldKeepEntry(entry, options) {
81
166
  }
82
167
  function orderRangeForQuery(options) {
83
168
  if (typeof IDBKeyRange === "undefined") return void 0;
169
+ if (options.sessionId !== void 0) {
170
+ const lower = [
171
+ options.sessionId,
172
+ options.from ?? Number.MIN_SAFE_INTEGER,
173
+ Number.MIN_SAFE_INTEGER,
174
+ ""
175
+ ];
176
+ const upper = [
177
+ options.sessionId,
178
+ options.to ?? Number.MAX_SAFE_INTEGER,
179
+ Number.MAX_SAFE_INTEGER,
180
+ "￿"
181
+ ];
182
+ return IDBKeyRange.bound(lower, upper);
183
+ }
84
184
  const lower = options.from === void 0 ? void 0 : [
85
185
  options.from,
86
186
  Number.MIN_SAFE_INTEGER,
@@ -129,12 +229,17 @@ function indexedDbTransport(options = {}) {
129
229
  const flushOnPageHide = options.flushOnPageHide ?? true;
130
230
  const durability = options.durability ?? "default";
131
231
  const rootIndexedDB = options.indexedDB ?? globalThis.indexedDB;
232
+ const sessionOptions = normalizeSessionOptions(options.session);
233
+ const spillOptions = normalizeLocalStorageSpillOptions(options.localStorageSpill, dbName, storeName);
132
234
  const transactionOptions = durability === "default" ? void 0 : { durability };
133
235
  const buffer = [];
134
236
  let dbPromise;
135
237
  let indexedDBFactoryPromise;
136
238
  let timer;
137
239
  let flushPromise;
240
+ let pendingFlushBatch;
241
+ let spillDrainPromise;
242
+ let localStorageSpillClearCandidate;
138
243
  let lastContext;
139
244
  let closed = false;
140
245
  const statsState = {
@@ -161,13 +266,23 @@ function indexedDbTransport(options = {}) {
161
266
  storageBucketFallbacks: 0,
162
267
  transactionOptionFallbacks: 0,
163
268
  errors: 0,
164
- errorsByOperation: {}
269
+ errorsByOperation: {},
270
+ localStorageSpillWrites: 0,
271
+ localStorageSpillEntries: 0,
272
+ localStorageSpillDrains: 0,
273
+ localStorageSpillDrainedEntries: 0,
274
+ localStorageSpillDropped: 0,
275
+ localStorageSpillDroppedByReason: {},
276
+ localStorageSpillErrors: 0,
277
+ localStorageSpillErrorsByOperation: {}
165
278
  };
166
279
  const snapshotStats = () => ({
167
280
  ...statsState,
168
281
  bufferDepth: buffer.length,
169
282
  droppedByReason: { ...statsState.droppedByReason },
170
283
  errorsByOperation: { ...statsState.errorsByOperation },
284
+ localStorageSpillDroppedByReason: { ...statsState.localStorageSpillDroppedByReason },
285
+ localStorageSpillErrorsByOperation: { ...statsState.localStorageSpillErrorsByOperation },
171
286
  pendingFlush: flushPromise !== void 0,
172
287
  persistedDroppedByReason: { ...statsState.persistedDroppedByReason }
173
288
  });
@@ -194,6 +309,18 @@ function indexedDbTransport(options = {}) {
194
309
  (0, _loggerjs_core.incrementLoggerMetaCounter)(`transport.indexeddb.persisted.dropped.${reason}`);
195
310
  options.onPersistedDrop?.(entry, reason);
196
311
  };
312
+ const reportSpillError = (error, operation) => {
313
+ statsState.localStorageSpillErrors += 1;
314
+ incrementRecord(statsState.localStorageSpillErrorsByOperation, operation);
315
+ reportInternalError(error, `localstorage-spill-${operation}`);
316
+ };
317
+ const dropSpillEvent = (event, reason) => {
318
+ statsState.localStorageSpillDropped += 1;
319
+ incrementRecord(statsState.localStorageSpillDroppedByReason, reason);
320
+ (0, _loggerjs_core.incrementLoggerMetaCounter)("transport.indexeddb.localstorage_spill.dropped");
321
+ (0, _loggerjs_core.incrementLoggerMetaCounter)(`transport.indexeddb.localstorage_spill.dropped.${reason}`);
322
+ spillOptions?.onDrop?.(event, reason);
323
+ };
197
324
  const resolveIndexedDBFactory = async () => {
198
325
  if (options.indexedDB || !options.storageBucketName) return rootIndexedDB;
199
326
  indexedDBFactoryPromise ??= (async () => {
@@ -233,6 +360,13 @@ function indexedDbTransport(options = {}) {
233
360
  "seq",
234
361
  "id"
235
362
  ]);
363
+ ensureIndex(store, SESSION_INDEX_NAME, "sessionId");
364
+ ensureIndex(store, SESSION_ORDER_INDEX_NAME, [
365
+ "sessionId",
366
+ "createdAt",
367
+ "seq",
368
+ "id"
369
+ ]);
236
370
  });
237
371
  request.addEventListener("success", () => {
238
372
  statsState.databaseOpenCount += 1;
@@ -323,7 +457,7 @@ function indexedDbTransport(options = {}) {
323
457
  return total;
324
458
  });
325
459
  const queryEntriesByCursor = async (queryOptions) => withStore("readonly", async (store) => {
326
- const index = getStoreIndex(store, ORDER_INDEX_NAME);
460
+ const index = getStoreIndex(store, queryOptions.sessionId === void 0 ? ORDER_INDEX_NAME : SESSION_ORDER_INDEX_NAME);
327
461
  if (!index) return void 0;
328
462
  const entries = [];
329
463
  const limit = queryOptions.limit;
@@ -336,6 +470,31 @@ function indexedDbTransport(options = {}) {
336
470
  });
337
471
  return entries;
338
472
  });
473
+ const listSessions = async (sessionQueryOptions = {}) => {
474
+ const sessions = /* @__PURE__ */ new Map();
475
+ for (const entry of await getEntries()) {
476
+ if (!entry.sessionId) continue;
477
+ const current = sessions.get(entry.sessionId);
478
+ if (current) {
479
+ current.firstSeen = Math.min(current.firstSeen, entry.createdAt);
480
+ current.lastSeen = Math.max(current.lastSeen, entry.createdAt);
481
+ current.count += 1;
482
+ current.byteLength += entry.byteLength;
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
489
+ });
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;
494
+ });
495
+ if (sessionQueryOptions.order !== "asc") ordered.reverse();
496
+ return ordered.slice(0, sessionQueryOptions.limit);
497
+ };
339
498
  const pruneWithEntries = async () => {
340
499
  const now = Date.now();
341
500
  let entries = await getEntries();
@@ -391,10 +550,34 @@ function indexedDbTransport(options = {}) {
391
550
  statsState.lastPruneDurationMs = nowMs() - startedAt;
392
551
  }
393
552
  };
553
+ const sessionIdForEvent = (event) => {
554
+ if (!sessionOptions) return void 0;
555
+ return normalizeSessionId(sessionOptions.getId?.(event)) ?? normalizeSessionId(event.context?.[sessionOptions.contextKey]) ?? sessionOptions.id;
556
+ };
557
+ const eventWithSessionContext = (event, sessionId) => {
558
+ if (!sessionId || !sessionOptions?.contextKey) return event;
559
+ if (event.context?.[sessionOptions.contextKey] === sessionId) return event;
560
+ return {
561
+ ...event,
562
+ context: {
563
+ ...event.context,
564
+ [sessionOptions.contextKey]: sessionId
565
+ }
566
+ };
567
+ };
568
+ const eventWithResolvedSession = (event) => {
569
+ const sessionId = sessionIdForEvent(event);
570
+ return {
571
+ event: eventWithSessionContext(event, sessionId),
572
+ sessionId
573
+ };
574
+ };
394
575
  const eventToEntry = (event) => {
395
- const payload = codec.encode(event);
576
+ const resolved = eventWithResolvedSession(event);
577
+ const payload = codec.encode(resolved.event);
396
578
  return {
397
579
  id: event.id,
580
+ sessionId: resolved.sessionId,
398
581
  seq: event.seq,
399
582
  createdAt: event.time,
400
583
  level: event.level,
@@ -405,6 +588,30 @@ function indexedDbTransport(options = {}) {
405
588
  payload
406
589
  };
407
590
  };
591
+ const clearLocalStorageSpill = (expectedValue) => {
592
+ if (!spillOptions) return false;
593
+ try {
594
+ if (expectedValue !== void 0 && spillOptions.storage.getItem(spillOptions.key) !== expectedValue) return false;
595
+ spillOptions.storage.removeItem(spillOptions.key);
596
+ if (expectedValue === void 0 || localStorageSpillClearCandidate?.value === expectedValue) localStorageSpillClearCandidate = void 0;
597
+ return true;
598
+ } catch (error) {
599
+ reportSpillError(error, "clear");
600
+ return false;
601
+ }
602
+ };
603
+ const rememberLocalStorageSpill = (value, events) => {
604
+ localStorageSpillClearCandidate = {
605
+ pendingIds: new Set(events.map((event) => event.id)),
606
+ value
607
+ };
608
+ };
609
+ const markLocalStorageSpillPersisted = (events) => {
610
+ const candidate = localStorageSpillClearCandidate;
611
+ if (!candidate || events.length === 0) return;
612
+ for (const event of events) candidate.pendingIds.delete(event.id);
613
+ if (candidate.pendingIds.size === 0) clearLocalStorageSpill(candidate.value);
614
+ };
408
615
  const writeBatch = async (events) => {
409
616
  if (events.length === 0) return;
410
617
  const startedAt = nowMs();
@@ -416,6 +623,7 @@ function indexedDbTransport(options = {}) {
416
623
  });
417
624
  statsState.persisted += entries.length;
418
625
  (0, _loggerjs_core.incrementLoggerMetaCounter)("transport.indexeddb.persisted", entries.length);
626
+ markLocalStorageSpillPersisted(events);
419
627
  await prune();
420
628
  statsState.flushes += 1;
421
629
  } catch (error) {
@@ -425,6 +633,124 @@ function indexedDbTransport(options = {}) {
425
633
  statsState.lastFlushDurationMs = nowMs() - startedAt;
426
634
  }
427
635
  };
636
+ const stringifySpillPayload = (events) => (0, _loggerjs_core.safeJsonStringify)({
637
+ createdAt: Date.now(),
638
+ entries: events,
639
+ schema: SPILL_SCHEMA
640
+ }, { maxArrayLength: spillOptions?.maxEntries ?? DEFAULT_SPILL_MAX_ENTRIES });
641
+ const spillStorageBudget = (currentValue) => {
642
+ if (!spillOptions) return 0;
643
+ const ownBytes = currentValue ? storageByteLength(spillOptions.key, currentValue) : 0;
644
+ const originBytes = estimateLocalStorageBytes(spillOptions.storage);
645
+ if (originBytes === void 0) return spillOptions.maxBytes;
646
+ return Math.max(0, Math.min(spillOptions.maxBytes, spillOptions.maxOriginBytes - originBytes + ownBytes));
647
+ };
648
+ const pendingEventsForSpill = () => {
649
+ const events = [...pendingFlushBatch ?? [], ...buffer];
650
+ const seen = /* @__PURE__ */ new Set();
651
+ const deduped = [];
652
+ for (const event of events) {
653
+ if (seen.has(event.id)) continue;
654
+ seen.add(event.id);
655
+ if (spillOptions?.minLevelValue !== void 0 && event.level < spillOptions.minLevelValue) continue;
656
+ deduped.push(eventWithResolvedSession(event).event);
657
+ }
658
+ return deduped;
659
+ };
660
+ const trimSpillEventsToBudget = (events, budget) => {
661
+ if (!spillOptions) return void 0;
662
+ while (events.length > spillOptions.maxEntries) {
663
+ const dropped = events.shift();
664
+ if (dropped) dropSpillEvent(dropped, "max-entries");
665
+ }
666
+ while (events.length > 0) {
667
+ const value = stringifySpillPayload(events);
668
+ if (storageByteLength(spillOptions.key, value) <= budget) return value;
669
+ const dropped = events.shift();
670
+ if (dropped) dropSpillEvent(dropped, "max-bytes");
671
+ }
672
+ };
673
+ const writeLocalStorageSpill = () => {
674
+ if (!spillOptions) return;
675
+ const pendingEvents = pendingEventsForSpill();
676
+ if (pendingEvents.length === 0) return;
677
+ try {
678
+ const currentValue = spillOptions.storage.getItem(spillOptions.key);
679
+ let events = pendingEvents;
680
+ if (currentValue) {
681
+ const merged = /* @__PURE__ */ new Map();
682
+ try {
683
+ for (const event of parseSpillPayload(currentValue) ?? []) merged.set(event.id, event);
684
+ } catch {}
685
+ for (const event of pendingEvents) merged.set(event.id, event);
686
+ events = [...merged.values()];
687
+ }
688
+ const budget = spillStorageBudget(currentValue);
689
+ const value = trimSpillEventsToBudget(events, budget);
690
+ if (!value) return;
691
+ try {
692
+ spillOptions.storage.setItem(spillOptions.key, value);
693
+ } catch (error) {
694
+ if (!isQuotaExceededError(error)) throw error;
695
+ while (events.length > 0) {
696
+ const dropped = events.shift();
697
+ if (dropped) dropSpillEvent(dropped, "quota");
698
+ const retryValue = trimSpillEventsToBudget(events, budget);
699
+ if (!retryValue) return;
700
+ try {
701
+ spillOptions.storage.setItem(spillOptions.key, retryValue);
702
+ rememberLocalStorageSpill(retryValue, events);
703
+ statsState.localStorageSpillWrites += 1;
704
+ statsState.localStorageSpillEntries += events.length;
705
+ return;
706
+ } catch (retryError) {
707
+ if (!isQuotaExceededError(retryError)) throw retryError;
708
+ }
709
+ }
710
+ return;
711
+ }
712
+ rememberLocalStorageSpill(value, events);
713
+ statsState.localStorageSpillWrites += 1;
714
+ statsState.localStorageSpillEntries += events.length;
715
+ } catch (error) {
716
+ reportSpillError(error, "write");
717
+ }
718
+ };
719
+ const drainLocalStorageSpill = async () => {
720
+ if (!spillOptions) return;
721
+ let raw;
722
+ try {
723
+ raw = spillOptions.storage.getItem(spillOptions.key);
724
+ } catch (error) {
725
+ reportSpillError(error, "read");
726
+ return;
727
+ }
728
+ if (!raw) return;
729
+ let events;
730
+ try {
731
+ events = parseSpillPayload(raw);
732
+ } catch (error) {
733
+ reportSpillError(error, "parse");
734
+ clearLocalStorageSpill();
735
+ return;
736
+ }
737
+ if (!events || events.length === 0) {
738
+ clearLocalStorageSpill();
739
+ return;
740
+ }
741
+ await writeBatch(events);
742
+ clearLocalStorageSpill(raw);
743
+ statsState.localStorageSpillDrains += 1;
744
+ statsState.localStorageSpillDrainedEntries += events.length;
745
+ };
746
+ const ensureSpillDrained = async () => {
747
+ const promise = spillDrainPromise;
748
+ if (!promise) return;
749
+ await promise;
750
+ };
751
+ if (spillOptions?.drainOnCreate) spillDrainPromise = drainLocalStorageSpill().catch((error) => reportSpillError(error, "drain")).finally(() => {
752
+ spillDrainPromise = void 0;
753
+ });
428
754
  const clearTimer = () => {
429
755
  if (timer) clearTimeout(timer);
430
756
  timer = void 0;
@@ -432,7 +758,13 @@ function indexedDbTransport(options = {}) {
432
758
  const flushPending = async () => {
433
759
  if (flushPromise) return flushPromise;
434
760
  clearTimer();
435
- flushPromise = writeBatch(buffer.splice(0, buffer.length)).finally(() => {
761
+ const batch = buffer.splice(0, buffer.length);
762
+ pendingFlushBatch = batch;
763
+ flushPromise = (async () => {
764
+ await ensureSpillDrained();
765
+ await writeBatch(batch);
766
+ })().finally(() => {
767
+ if (pendingFlushBatch === batch) pendingFlushBatch = void 0;
436
768
  flushPromise = void 0;
437
769
  if (buffer.length > 0 && !closed) schedule();
438
770
  });
@@ -445,14 +777,28 @@ function indexedDbTransport(options = {}) {
445
777
  }, flushIntervalMs);
446
778
  };
447
779
  const onPageHide = () => {
448
- flushPending().catch((error) => reportInternalError(error, "pagehide-flush"));
780
+ if (spillOptions?.spillOnPageHide) writeLocalStorageSpill();
781
+ if (flushOnPageHide) flushPending().catch((error) => reportInternalError(error, "pagehide-flush"));
449
782
  };
450
783
  const onVisibilityChange = () => {
451
- if (typeof document !== "undefined" && document.visibilityState === "hidden") flushPending().catch((error) => reportInternalError(error, "visibility-flush"));
784
+ if (typeof document !== "undefined" && document.visibilityState === "hidden") {
785
+ if (spillOptions?.spillOnPageHide) writeLocalStorageSpill();
786
+ if (flushOnPageHide) flushPending().catch((error) => reportInternalError(error, "visibility-flush"));
787
+ }
788
+ };
789
+ const addVisibilityChangeListener = () => {
790
+ if (typeof document !== "undefined" && typeof document.addEventListener === "function") {
791
+ document.addEventListener("visibilitychange", onVisibilityChange);
792
+ return () => document.removeEventListener("visibilitychange", onVisibilityChange);
793
+ }
794
+ if (typeof globalThis.addEventListener !== "function") return void 0;
795
+ globalThis.addEventListener("visibilitychange", onVisibilityChange);
796
+ return () => globalThis.removeEventListener?.("visibilitychange", onVisibilityChange);
452
797
  };
453
- if (flushOnPageHide) {
798
+ let removeVisibilityChangeListener;
799
+ if (flushOnPageHide || spillOptions?.spillOnPageHide) {
454
800
  globalThis.addEventListener?.("pagehide", onPageHide);
455
- globalThis.addEventListener?.("visibilitychange", onVisibilityChange);
801
+ removeVisibilityChangeListener = addVisibilityChangeListener();
456
802
  }
457
803
  return {
458
804
  name: options.name ?? "indexeddb",
@@ -485,6 +831,7 @@ function indexedDbTransport(options = {}) {
485
831
  async clear() {
486
832
  buffer.length = 0;
487
833
  clearTimer();
834
+ clearLocalStorageSpill();
488
835
  await withStore("readwrite", (store) => requestToPromise(store.clear()));
489
836
  },
490
837
  async remove(ids) {
@@ -508,6 +855,10 @@ function indexedDbTransport(options = {}) {
508
855
  if (event) yield event;
509
856
  }
510
857
  },
858
+ async sessions(sessionQueryOptions = {}) {
859
+ await flushPending();
860
+ return listSessions(sessionQueryOptions);
861
+ },
511
862
  stats() {
512
863
  return snapshotStats();
513
864
  },
@@ -515,12 +866,12 @@ function indexedDbTransport(options = {}) {
515
866
  closed = true;
516
867
  clearTimer();
517
868
  globalThis.removeEventListener?.("pagehide", onPageHide);
518
- globalThis.removeEventListener?.("visibilitychange", onVisibilityChange);
519
- const flushed = flushPending();
520
- const promise = dbPromise;
521
- dbPromise = void 0;
522
- return flushed.finally(() => {
523
- promise?.then((db) => db.close());
869
+ removeVisibilityChangeListener?.();
870
+ removeVisibilityChangeListener = void 0;
871
+ return flushPending().finally(() => {
872
+ const promise = dbPromise;
873
+ dbPromise = void 0;
874
+ return promise?.then((db) => db.close());
524
875
  });
525
876
  }
526
877
  };
@@ -547,12 +898,14 @@ function isBrowserOnline(options) {
547
898
  function offlineFirstTransport(remote, options = {}) {
548
899
  const name = options.name ?? `offline-first(${remote.name ?? "transport"})`;
549
900
  const replayBatchSize = normalizeBatchSize(options.replayBatchSize);
901
+ const queueOptions = options.queueOptions;
550
902
  const queue = options.queue ?? indexedDbTransport({
551
903
  dbName: "loggerjs-offline",
552
904
  flushIntervalMs: 0,
553
905
  name: `${name}:queue`,
554
906
  storeName: "offline-events",
555
- ...options.queueOptions
907
+ ...queueOptions,
908
+ session: queueOptions?.session ?? false
556
909
  });
557
910
  const reliableRemote = (0, _loggerjs_core.retryTransport)(remote, {
558
911
  maxRetries: 2,