@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
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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")
|
|
784
|
+
if (typeof document !== "undefined" && document.visibilityState === "hidden") {
|
|
785
|
+
if (spillOptions?.spillOnPageHide) writeLocalStorageSpill();
|
|
786
|
+
if (flushOnPageHide) flushPending().catch((error) => reportInternalError(error, "visibility-flush"));
|
|
787
|
+
}
|
|
452
788
|
};
|
|
453
|
-
|
|
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);
|
|
797
|
+
};
|
|
798
|
+
let removeVisibilityChangeListener;
|
|
799
|
+
if (flushOnPageHide || spillOptions?.spillOnPageHide) {
|
|
454
800
|
globalThis.addEventListener?.("pagehide", onPageHide);
|
|
455
|
-
|
|
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
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
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
|
};
|