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