@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.
- package/README.md +9 -2
- package/dist/index.cjs +478 -29
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +478 -29
- package/dist/index.js.map +1 -1
- package/dist/indexeddb-transport.cjs +366 -15
- package/dist/indexeddb-transport.cjs.map +1 -1
- package/dist/indexeddb-transport.d.ts +41 -0
- package/dist/indexeddb-transport.d.ts.map +1 -1
- package/dist/indexeddb-transport.js +367 -16
- package/dist/indexeddb-transport.js.map +1 -1
- package/dist/offline-first-transport.cjs +369 -16
- package/dist/offline-first-transport.cjs.map +1 -1
- package/dist/offline-first-transport.d.ts.map +1 -1
- package/dist/offline-first-transport.js +370 -17
- package/dist/offline-first-transport.js.map +1 -1
- package/dist/zip-export.cjs +109 -13
- package/dist/zip-export.cjs.map +1 -1
- package/dist/zip-export.d.ts +24 -0
- package/dist/zip-export.d.ts.map +1 -1
- package/dist/zip-export.js +109 -13
- package/dist/zip-export.js.map +1 -1
- package/package.json +2 -2
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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")
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
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
|
};
|