@loggerjs/browser 0.6.0 → 0.7.0
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 +2 -2
- package/dist/chunks/http-capture-utils-DbhpUirh.cjs +83 -0
- package/dist/chunks/http-capture-utils-DbhpUirh.cjs.map +1 -0
- package/dist/chunks/http-capture-utils-_hG5wepA.js +48 -0
- package/dist/chunks/http-capture-utils-_hG5wepA.js.map +1 -0
- package/dist/context-propagation-integration.cjs +5 -4
- package/dist/context-propagation-integration.cjs.map +1 -1
- package/dist/context-propagation-integration.d.ts +2 -1
- package/dist/context-propagation-integration.d.ts.map +1 -1
- package/dist/context-propagation-integration.js +2 -1
- package/dist/context-propagation-integration.js.map +1 -1
- package/dist/fetch-integration.cjs +8 -52
- package/dist/fetch-integration.cjs.map +1 -1
- package/dist/fetch-integration.js +1 -45
- package/dist/fetch-integration.js.map +1 -1
- package/dist/http-transport.cjs +90 -16
- package/dist/http-transport.cjs.map +1 -1
- package/dist/http-transport.d.ts +17 -0
- package/dist/http-transport.d.ts.map +1 -1
- package/dist/http-transport.js +90 -16
- package/dist/http-transport.js.map +1 -1
- package/dist/index.cjs +58 -4167
- package/dist/index.d.ts +243 -17
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +25 -4135
- package/dist/indexeddb-offline-queue.cjs +8 -1
- package/dist/indexeddb-offline-queue.cjs.map +1 -1
- package/dist/indexeddb-offline-queue.d.ts.map +1 -1
- package/dist/indexeddb-offline-queue.js +8 -1
- package/dist/indexeddb-offline-queue.js.map +1 -1
- package/dist/indexeddb-transport.cjs +24 -4
- package/dist/indexeddb-transport.cjs.map +1 -1
- package/dist/indexeddb-transport.d.ts.map +1 -1
- package/dist/indexeddb-transport.js +24 -4
- package/dist/indexeddb-transport.js.map +1 -1
- package/dist/offline-first-transport.cjs +2 -1029
- package/dist/offline-first-transport.cjs.map +1 -1
- package/dist/offline-first-transport.js +2 -1029
- package/dist/offline-first-transport.js.map +1 -1
- package/dist/payload-transforms.cjs +3 -3
- package/dist/payload-transforms.cjs.map +1 -1
- package/dist/payload-transforms.d.ts.map +1 -1
- package/dist/payload-transforms.js +1 -1
- package/dist/payload-transforms.js.map +1 -1
- package/dist/websocket-integration.cjs +5 -32
- package/dist/websocket-integration.cjs.map +1 -1
- package/dist/websocket-integration.js +1 -28
- package/dist/websocket-integration.js.map +1 -1
- package/dist/websocket-transport.cjs +1 -1
- package/dist/websocket-transport.cjs.map +1 -1
- package/dist/websocket-transport.js +2 -2
- package/dist/websocket-transport.js.map +1 -1
- package/dist/xhr-integration.cjs +6 -33
- package/dist/xhr-integration.cjs.map +1 -1
- package/dist/xhr-integration.js +1 -28
- package/dist/xhr-integration.js.map +1 -1
- package/package.json +5 -2
- package/dist/index.cjs.map +0 -1
- package/dist/index.js.map +0 -1
|
@@ -1,1033 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_indexeddb_transport = require("./indexeddb-transport.cjs");
|
|
2
3
|
let _loggerjs_core = require("@loggerjs/core");
|
|
3
|
-
//#region src/indexeddb-transport.ts
|
|
4
|
-
const DEFAULT_DB_NAME = "loggerjs";
|
|
5
|
-
const DEFAULT_STORE_NAME = "logs";
|
|
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 = 4;
|
|
11
|
-
const ORDER_INDEX_NAME = "createdAtSeq";
|
|
12
|
-
const SESSION_INDEX_NAME = "sessionId";
|
|
13
|
-
const SESSION_ORDER_INDEX_NAME = "sessionIdCreatedAtSeq";
|
|
14
|
-
const SESSION_STORE_SUFFIX = "__sessions";
|
|
15
|
-
const SESSION_LAST_SEEN_INDEX_NAME = "lastSeenSessionId";
|
|
16
|
-
const SPILL_SCHEMA = "loggerjs.indexeddb-spill.v1";
|
|
17
|
-
function normalizePositiveInteger(value, fallback) {
|
|
18
|
-
if (value === void 0 || !Number.isFinite(value)) return fallback;
|
|
19
|
-
return Math.max(0, Math.floor(value));
|
|
20
|
-
}
|
|
21
|
-
let fallbackSessionSeq = 0;
|
|
22
|
-
function defaultPageSessionId() {
|
|
23
|
-
const randomUUID = globalThis.crypto?.randomUUID;
|
|
24
|
-
if (randomUUID) return randomUUID.call(globalThis.crypto);
|
|
25
|
-
return `${Date.now().toString(36)}-${(fallbackSessionSeq++).toString(36)}`;
|
|
26
|
-
}
|
|
27
|
-
function normalizeSessionId(value) {
|
|
28
|
-
if (typeof value !== "string") return void 0;
|
|
29
|
-
const normalized = value.trim();
|
|
30
|
-
return normalized.length > 0 ? normalized : void 0;
|
|
31
|
-
}
|
|
32
|
-
function normalizeSessionOptions(session) {
|
|
33
|
-
if (session === false) return void 0;
|
|
34
|
-
if (typeof session === "string") return {
|
|
35
|
-
contextKey: DEFAULT_SESSION_CONTEXT_KEY,
|
|
36
|
-
id: normalizeSessionId(session) ?? defaultPageSessionId()
|
|
37
|
-
};
|
|
38
|
-
return {
|
|
39
|
-
contextKey: session?.contextKey ?? DEFAULT_SESSION_CONTEXT_KEY,
|
|
40
|
-
getId: session?.getId,
|
|
41
|
-
id: normalizeSessionId(session?.id) ?? defaultPageSessionId()
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
function resolveLocalStorage(storage) {
|
|
45
|
-
if (storage) return storage;
|
|
46
|
-
try {
|
|
47
|
-
return globalThis.localStorage;
|
|
48
|
-
} catch {
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
function normalizeLocalStorageSpillOptions(spill, dbName, storeName) {
|
|
53
|
-
if (!spill) return void 0;
|
|
54
|
-
const storage = resolveLocalStorage(spill.storage);
|
|
55
|
-
if (!storage) return void 0;
|
|
56
|
-
const namespace = spill.namespace ?? `${dbName}:${storeName}`;
|
|
57
|
-
return {
|
|
58
|
-
drainOnCreate: spill.drainOnCreate ?? true,
|
|
59
|
-
key: `loggerjs:spill:v1:${namespace}`,
|
|
60
|
-
maxBytes: normalizePositiveInteger(spill.maxBytes, DEFAULT_SPILL_MAX_BYTES),
|
|
61
|
-
maxEntries: normalizePositiveInteger(spill.maxEntries, DEFAULT_SPILL_MAX_ENTRIES),
|
|
62
|
-
maxOriginBytes: normalizePositiveInteger(spill.maxOriginBytes, DEFAULT_SPILL_MAX_ORIGIN_BYTES),
|
|
63
|
-
minLevelValue: spill.minLevel === void 0 ? void 0 : (0, _loggerjs_core.toLevelValue)(spill.minLevel),
|
|
64
|
-
onDrop: spill.onDrop,
|
|
65
|
-
spillOnPageHide: spill.spillOnPageHide ?? true,
|
|
66
|
-
storage
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
function storageByteLength(key, value) {
|
|
70
|
-
return 2 * (key.length + value.length);
|
|
71
|
-
}
|
|
72
|
-
function estimateLocalStorageBytes(storage) {
|
|
73
|
-
try {
|
|
74
|
-
let total = 0;
|
|
75
|
-
for (let index = 0; index < storage.length; index += 1) {
|
|
76
|
-
const key = storage.key(index);
|
|
77
|
-
if (key === null) continue;
|
|
78
|
-
total += storageByteLength(key, storage.getItem(key) ?? "");
|
|
79
|
-
}
|
|
80
|
-
return total;
|
|
81
|
-
} catch {
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
function isQuotaExceededError(error) {
|
|
86
|
-
return typeof DOMException !== "undefined" && error instanceof DOMException && (error.name === "QuotaExceededError" || error.name === "NS_ERROR_DOM_QUOTA_REACHED");
|
|
87
|
-
}
|
|
88
|
-
function isSpillLogEvent(value) {
|
|
89
|
-
if (!value || typeof value !== "object") return false;
|
|
90
|
-
const event = value;
|
|
91
|
-
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";
|
|
92
|
-
}
|
|
93
|
-
function parseSpillPayload(raw) {
|
|
94
|
-
const parsed = JSON.parse(raw);
|
|
95
|
-
if (parsed.schema !== SPILL_SCHEMA || !Array.isArray(parsed.entries)) return void 0;
|
|
96
|
-
return parsed.entries.filter(isSpillLogEvent);
|
|
97
|
-
}
|
|
98
|
-
function nowMs() {
|
|
99
|
-
return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
|
|
100
|
-
}
|
|
101
|
-
function incrementRecord(record, key, amount = 1) {
|
|
102
|
-
record[key] = (record[key] ?? 0) + amount;
|
|
103
|
-
}
|
|
104
|
-
function requestToPromise(request) {
|
|
105
|
-
return new Promise((resolve, reject) => {
|
|
106
|
-
request.addEventListener("success", () => resolve(request.result), { once: true });
|
|
107
|
-
request.addEventListener("error", () => reject(request.error ?? /* @__PURE__ */ new Error("IndexedDB request failed")), { once: true });
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
function transactionToPromise(transaction) {
|
|
111
|
-
return new Promise((resolve, reject) => {
|
|
112
|
-
transaction.addEventListener("complete", () => resolve(), { once: true });
|
|
113
|
-
transaction.addEventListener("abort", () => reject(transaction.error ?? /* @__PURE__ */ new Error("IndexedDB transaction aborted")), { once: true });
|
|
114
|
-
transaction.addEventListener("error", () => reject(transaction.error ?? /* @__PURE__ */ new Error("IndexedDB transaction failed")), { once: true });
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
function payloadByteLength(payload) {
|
|
118
|
-
if (typeof payload !== "string") return payload.byteLength;
|
|
119
|
-
if (typeof TextEncoder !== "undefined") return new TextEncoder().encode(payload).byteLength;
|
|
120
|
-
return new Blob([payload]).size;
|
|
121
|
-
}
|
|
122
|
-
function compareEntries(a, b) {
|
|
123
|
-
if (a.createdAt !== b.createdAt) return a.createdAt - b.createdAt;
|
|
124
|
-
if (a.seq !== b.seq) return a.seq - b.seq;
|
|
125
|
-
return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
|
|
126
|
-
}
|
|
127
|
-
function orderEntries(entries, order = "asc") {
|
|
128
|
-
const sorted = [...entries].sort(compareEntries);
|
|
129
|
-
if (order === "asc") return sorted;
|
|
130
|
-
const reversed = [];
|
|
131
|
-
for (let index = sorted.length - 1; index >= 0; index -= 1) reversed.push(sorted[index]);
|
|
132
|
-
return reversed;
|
|
133
|
-
}
|
|
134
|
-
function indexExists(store, name) {
|
|
135
|
-
return store.indexNames.contains(name);
|
|
136
|
-
}
|
|
137
|
-
function ensureIndex(store, name, keyPath) {
|
|
138
|
-
if (!indexExists(store, name)) store.createIndex(name, keyPath);
|
|
139
|
-
}
|
|
140
|
-
function sessionStoreNameFor(storeName) {
|
|
141
|
-
return `${storeName}${SESSION_STORE_SUFFIX}`;
|
|
142
|
-
}
|
|
143
|
-
function getStoreIndex(store, name) {
|
|
144
|
-
const maybeStore = store;
|
|
145
|
-
if (typeof maybeStore.index !== "function" || !indexExists(store, name)) return void 0;
|
|
146
|
-
return maybeStore.index(name);
|
|
147
|
-
}
|
|
148
|
-
function getStorageBucketManager() {
|
|
149
|
-
return globalThis.navigator?.storageBuckets;
|
|
150
|
-
}
|
|
151
|
-
function storageBucketOpenOptions(options) {
|
|
152
|
-
if (options.storageBucketDurability === void 0 && options.storageBucketPersisted === void 0) return;
|
|
153
|
-
return {
|
|
154
|
-
durability: options.storageBucketDurability,
|
|
155
|
-
persisted: options.storageBucketPersisted
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
function decodeEntry(entry, codec) {
|
|
159
|
-
if (!codec.decode) throw new Error("indexedDbTransport query requires a codec with decode");
|
|
160
|
-
const decoded = codec.decode(entry.payload);
|
|
161
|
-
return Array.isArray(decoded) ? decoded[0] : decoded;
|
|
162
|
-
}
|
|
163
|
-
function shouldKeepEntry(entry, options) {
|
|
164
|
-
if (options.from !== void 0 && entry.createdAt < options.from) return false;
|
|
165
|
-
if (options.to !== void 0 && entry.createdAt > options.to) return false;
|
|
166
|
-
if (options.sessionId !== void 0 && entry.sessionId !== options.sessionId) return false;
|
|
167
|
-
if (options.minLevel !== void 0 && entry.level < (0, _loggerjs_core.toLevelValue)(options.minLevel)) return false;
|
|
168
|
-
if (options.logger !== void 0 && entry.logger !== options.logger) return false;
|
|
169
|
-
if (options.type !== void 0 && entry.type !== options.type) return false;
|
|
170
|
-
return true;
|
|
171
|
-
}
|
|
172
|
-
function addSessionSummary(sessions, entry) {
|
|
173
|
-
if (!entry.sessionId) return;
|
|
174
|
-
const current = sessions.get(entry.sessionId);
|
|
175
|
-
if (current) {
|
|
176
|
-
current.firstSeen = Math.min(current.firstSeen, entry.createdAt);
|
|
177
|
-
current.lastSeen = Math.max(current.lastSeen, entry.createdAt);
|
|
178
|
-
current.count += 1;
|
|
179
|
-
current.byteLength += entry.byteLength;
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
sessions.set(entry.sessionId, {
|
|
183
|
-
byteLength: entry.byteLength,
|
|
184
|
-
count: 1,
|
|
185
|
-
firstSeen: entry.createdAt,
|
|
186
|
-
lastSeen: entry.createdAt,
|
|
187
|
-
sessionId: entry.sessionId
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
function mergeSessionSummary(current, incoming) {
|
|
191
|
-
if (!current) return incoming;
|
|
192
|
-
return {
|
|
193
|
-
byteLength: current.byteLength + incoming.byteLength,
|
|
194
|
-
count: current.count + incoming.count,
|
|
195
|
-
firstSeen: Math.min(current.firstSeen, incoming.firstSeen),
|
|
196
|
-
lastSeen: Math.max(current.lastSeen, incoming.lastSeen),
|
|
197
|
-
sessionId: incoming.sessionId
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
function orderSessions(sessions, order = "desc") {
|
|
201
|
-
const sorted = [...sessions].sort((left, right) => {
|
|
202
|
-
if (left.lastSeen !== right.lastSeen) return left.lastSeen - right.lastSeen;
|
|
203
|
-
return left.sessionId < right.sessionId ? -1 : left.sessionId > right.sessionId ? 1 : 0;
|
|
204
|
-
});
|
|
205
|
-
if (order === "asc") return sorted;
|
|
206
|
-
const reversed = [];
|
|
207
|
-
for (let index = sorted.length - 1; index >= 0; index -= 1) reversed.push(sorted[index]);
|
|
208
|
-
return reversed;
|
|
209
|
-
}
|
|
210
|
-
function orderRangeForQuery(options) {
|
|
211
|
-
if (typeof IDBKeyRange === "undefined") return void 0;
|
|
212
|
-
if (options.sessionId !== void 0) {
|
|
213
|
-
const lower = [
|
|
214
|
-
options.sessionId,
|
|
215
|
-
options.from ?? Number.MIN_SAFE_INTEGER,
|
|
216
|
-
Number.MIN_SAFE_INTEGER,
|
|
217
|
-
""
|
|
218
|
-
];
|
|
219
|
-
const upper = [
|
|
220
|
-
options.sessionId,
|
|
221
|
-
options.to ?? Number.MAX_SAFE_INTEGER,
|
|
222
|
-
Number.MAX_SAFE_INTEGER,
|
|
223
|
-
""
|
|
224
|
-
];
|
|
225
|
-
return IDBKeyRange.bound(lower, upper);
|
|
226
|
-
}
|
|
227
|
-
const lower = options.from === void 0 ? void 0 : [
|
|
228
|
-
options.from,
|
|
229
|
-
Number.MIN_SAFE_INTEGER,
|
|
230
|
-
""
|
|
231
|
-
];
|
|
232
|
-
const upper = options.to === void 0 ? void 0 : [
|
|
233
|
-
options.to,
|
|
234
|
-
Number.MAX_SAFE_INTEGER,
|
|
235
|
-
""
|
|
236
|
-
];
|
|
237
|
-
if (lower && upper) return IDBKeyRange.bound(lower, upper);
|
|
238
|
-
if (lower) return IDBKeyRange.lowerBound(lower);
|
|
239
|
-
if (upper) return IDBKeyRange.upperBound(upper);
|
|
240
|
-
}
|
|
241
|
-
function orderRangeForSessionId(sessionId) {
|
|
242
|
-
if (typeof IDBKeyRange === "undefined") return void 0;
|
|
243
|
-
return IDBKeyRange.bound([
|
|
244
|
-
sessionId,
|
|
245
|
-
Number.MIN_SAFE_INTEGER,
|
|
246
|
-
Number.MIN_SAFE_INTEGER,
|
|
247
|
-
""
|
|
248
|
-
], [
|
|
249
|
-
sessionId,
|
|
250
|
-
Number.MAX_SAFE_INTEGER,
|
|
251
|
-
Number.MAX_SAFE_INTEGER,
|
|
252
|
-
""
|
|
253
|
-
]);
|
|
254
|
-
}
|
|
255
|
-
function migrateSessionSummaries(logStore, sessionStore) {
|
|
256
|
-
const summaries = /* @__PURE__ */ new Map();
|
|
257
|
-
const request = logStore.openCursor();
|
|
258
|
-
request.addEventListener("success", () => {
|
|
259
|
-
const cursor = request.result;
|
|
260
|
-
if (!cursor) {
|
|
261
|
-
for (const summary of summaries.values()) sessionStore.put(summary);
|
|
262
|
-
return;
|
|
263
|
-
}
|
|
264
|
-
addSessionSummary(summaries, cursor.value);
|
|
265
|
-
cursor.continue();
|
|
266
|
-
});
|
|
267
|
-
}
|
|
268
|
-
function iterateCursor(request, visit) {
|
|
269
|
-
return new Promise((resolve, reject) => {
|
|
270
|
-
request.addEventListener("success", () => {
|
|
271
|
-
const cursor = request.result;
|
|
272
|
-
if (!cursor) {
|
|
273
|
-
resolve();
|
|
274
|
-
return;
|
|
275
|
-
}
|
|
276
|
-
try {
|
|
277
|
-
if (visit(cursor) === false) {
|
|
278
|
-
resolve();
|
|
279
|
-
return;
|
|
280
|
-
}
|
|
281
|
-
cursor.continue();
|
|
282
|
-
} catch (error) {
|
|
283
|
-
reject(error);
|
|
284
|
-
}
|
|
285
|
-
}, false);
|
|
286
|
-
request.addEventListener("error", () => reject(request.error ?? /* @__PURE__ */ new Error("IndexedDB cursor failed")), { once: true });
|
|
287
|
-
});
|
|
288
|
-
}
|
|
289
|
-
function indexedDbTransport(options = {}) {
|
|
290
|
-
const dbName = options.dbName ?? DEFAULT_DB_NAME;
|
|
291
|
-
const storeName = options.storeName ?? DEFAULT_STORE_NAME;
|
|
292
|
-
const sessionStoreName = sessionStoreNameFor(storeName);
|
|
293
|
-
const codec = options.codec ?? (0, _loggerjs_core.safeJsonCodec)();
|
|
294
|
-
const batchSize = normalizePositiveInteger(options.batchSize, 100);
|
|
295
|
-
const flushIntervalMs = normalizePositiveInteger(options.flushIntervalMs, 1e3);
|
|
296
|
-
const maxBufferSize = normalizePositiveInteger(options.maxBufferSize, 1e3);
|
|
297
|
-
const maxEntries = normalizePositiveInteger(options.maxEntries, 5e4);
|
|
298
|
-
const maxBytes = normalizePositiveInteger(options.maxBytes, 50 * 1024 * 1024);
|
|
299
|
-
const dropPolicy = options.dropPolicy ?? "drop-oldest";
|
|
300
|
-
const flushOnPageHide = options.flushOnPageHide ?? true;
|
|
301
|
-
const durability = options.durability ?? "default";
|
|
302
|
-
const rootIndexedDB = options.indexedDB ?? globalThis.indexedDB;
|
|
303
|
-
const sessionOptions = normalizeSessionOptions(options.session);
|
|
304
|
-
const spillOptions = normalizeLocalStorageSpillOptions(options.localStorageSpill, dbName, storeName);
|
|
305
|
-
const transactionOptions = durability === "default" ? void 0 : { durability };
|
|
306
|
-
const buffer = [];
|
|
307
|
-
let dbPromise;
|
|
308
|
-
let indexedDBFactoryPromise;
|
|
309
|
-
let timer;
|
|
310
|
-
let flushPromise;
|
|
311
|
-
let clearPromise;
|
|
312
|
-
let pendingFlushBatch;
|
|
313
|
-
let spillDrainPromise;
|
|
314
|
-
let localStorageSpillClearCandidate;
|
|
315
|
-
let lastContext;
|
|
316
|
-
let closed = false;
|
|
317
|
-
const statsState = {
|
|
318
|
-
bufferDepth: 0,
|
|
319
|
-
maxBufferDepth: 0,
|
|
320
|
-
pendingFlush: false,
|
|
321
|
-
enqueued: 0,
|
|
322
|
-
persisted: 0,
|
|
323
|
-
dropped: 0,
|
|
324
|
-
droppedByReason: {},
|
|
325
|
-
persistedDropped: 0,
|
|
326
|
-
persistedDroppedByReason: {},
|
|
327
|
-
flushes: 0,
|
|
328
|
-
flushErrors: 0,
|
|
329
|
-
lastFlushBatchSize: 0,
|
|
330
|
-
lastFlushDurationMs: 0,
|
|
331
|
-
prunes: 0,
|
|
332
|
-
pruneFallbacks: 0,
|
|
333
|
-
lastPruneDurationMs: 0,
|
|
334
|
-
queries: 0,
|
|
335
|
-
queryFallbacks: 0,
|
|
336
|
-
lastQueryDurationMs: 0,
|
|
337
|
-
databaseOpenCount: 0,
|
|
338
|
-
storageBucketFallbacks: 0,
|
|
339
|
-
transactionOptionFallbacks: 0,
|
|
340
|
-
errors: 0,
|
|
341
|
-
errorsByOperation: {},
|
|
342
|
-
localStorageSpillWrites: 0,
|
|
343
|
-
localStorageSpillEntries: 0,
|
|
344
|
-
localStorageSpillDrains: 0,
|
|
345
|
-
localStorageSpillDrainedEntries: 0,
|
|
346
|
-
localStorageSpillDropped: 0,
|
|
347
|
-
localStorageSpillDroppedByReason: {},
|
|
348
|
-
localStorageSpillErrors: 0,
|
|
349
|
-
localStorageSpillErrorsByOperation: {}
|
|
350
|
-
};
|
|
351
|
-
const snapshotStats = () => ({
|
|
352
|
-
...statsState,
|
|
353
|
-
bufferDepth: buffer.length,
|
|
354
|
-
droppedByReason: { ...statsState.droppedByReason },
|
|
355
|
-
errorsByOperation: { ...statsState.errorsByOperation },
|
|
356
|
-
localStorageSpillDroppedByReason: { ...statsState.localStorageSpillDroppedByReason },
|
|
357
|
-
localStorageSpillErrorsByOperation: { ...statsState.localStorageSpillErrorsByOperation },
|
|
358
|
-
pendingFlush: flushPromise !== void 0,
|
|
359
|
-
persistedDroppedByReason: { ...statsState.persistedDroppedByReason }
|
|
360
|
-
});
|
|
361
|
-
const reportInternalError = (error, operation) => {
|
|
362
|
-
statsState.errors += 1;
|
|
363
|
-
incrementRecord(statsState.errorsByOperation, operation);
|
|
364
|
-
lastContext?.reportInternalError(error, {
|
|
365
|
-
operation,
|
|
366
|
-
phase: "transport",
|
|
367
|
-
transport: options.name ?? "indexeddb"
|
|
368
|
-
});
|
|
369
|
-
};
|
|
370
|
-
const dropEvent = (event, reason) => {
|
|
371
|
-
statsState.dropped += 1;
|
|
372
|
-
incrementRecord(statsState.droppedByReason, reason);
|
|
373
|
-
(0, _loggerjs_core.incrementLoggerMetaCounter)("transport.indexeddb.dropped");
|
|
374
|
-
(0, _loggerjs_core.incrementLoggerMetaCounter)(`transport.indexeddb.dropped.${reason}`);
|
|
375
|
-
options.onDrop?.(event, reason);
|
|
376
|
-
};
|
|
377
|
-
const dropPersistedEntry = (entry, reason) => {
|
|
378
|
-
statsState.persistedDropped += 1;
|
|
379
|
-
incrementRecord(statsState.persistedDroppedByReason, reason);
|
|
380
|
-
(0, _loggerjs_core.incrementLoggerMetaCounter)("transport.indexeddb.persisted.dropped");
|
|
381
|
-
(0, _loggerjs_core.incrementLoggerMetaCounter)(`transport.indexeddb.persisted.dropped.${reason}`);
|
|
382
|
-
options.onPersistedDrop?.(entry, reason);
|
|
383
|
-
};
|
|
384
|
-
const reportSpillError = (error, operation) => {
|
|
385
|
-
statsState.localStorageSpillErrors += 1;
|
|
386
|
-
incrementRecord(statsState.localStorageSpillErrorsByOperation, operation);
|
|
387
|
-
reportInternalError(error, `localstorage-spill-${operation}`);
|
|
388
|
-
};
|
|
389
|
-
const dropSpillEvent = (event, reason) => {
|
|
390
|
-
statsState.localStorageSpillDropped += 1;
|
|
391
|
-
incrementRecord(statsState.localStorageSpillDroppedByReason, reason);
|
|
392
|
-
(0, _loggerjs_core.incrementLoggerMetaCounter)("transport.indexeddb.localstorage_spill.dropped");
|
|
393
|
-
(0, _loggerjs_core.incrementLoggerMetaCounter)(`transport.indexeddb.localstorage_spill.dropped.${reason}`);
|
|
394
|
-
spillOptions?.onDrop?.(event, reason);
|
|
395
|
-
};
|
|
396
|
-
const resolveIndexedDBFactory = async () => {
|
|
397
|
-
if (options.indexedDB || !options.storageBucketName) return rootIndexedDB;
|
|
398
|
-
indexedDBFactoryPromise ??= (async () => {
|
|
399
|
-
const manager = getStorageBucketManager();
|
|
400
|
-
if (!manager) {
|
|
401
|
-
statsState.storageBucketFallbacks += 1;
|
|
402
|
-
return rootIndexedDB;
|
|
403
|
-
}
|
|
404
|
-
try {
|
|
405
|
-
const bucket = await manager.open(options.storageBucketName, storageBucketOpenOptions(options));
|
|
406
|
-
if (bucket.indexedDB) return bucket.indexedDB;
|
|
407
|
-
statsState.storageBucketFallbacks += 1;
|
|
408
|
-
return rootIndexedDB;
|
|
409
|
-
} catch (error) {
|
|
410
|
-
statsState.storageBucketFallbacks += 1;
|
|
411
|
-
reportInternalError(error, "storage-bucket-open");
|
|
412
|
-
return rootIndexedDB;
|
|
413
|
-
}
|
|
414
|
-
})();
|
|
415
|
-
return indexedDBFactoryPromise;
|
|
416
|
-
};
|
|
417
|
-
const openDb = () => {
|
|
418
|
-
dbPromise ??= resolveIndexedDBFactory().then((idb) => {
|
|
419
|
-
if (!idb) throw new Error("IndexedDB is not available for indexedDbTransport");
|
|
420
|
-
return new Promise((resolve, reject) => {
|
|
421
|
-
const request = idb.open(dbName, DB_VERSION);
|
|
422
|
-
request.addEventListener("upgradeneeded", (event) => {
|
|
423
|
-
const db = request.result;
|
|
424
|
-
const store = db.objectStoreNames.contains(storeName) ? request.transaction?.objectStore(storeName) : db.createObjectStore(storeName, { keyPath: "id" });
|
|
425
|
-
if (!store) return;
|
|
426
|
-
ensureIndex(store, "createdAt", "createdAt");
|
|
427
|
-
ensureIndex(store, "level", "level");
|
|
428
|
-
ensureIndex(store, "logger", "logger");
|
|
429
|
-
ensureIndex(store, "type", "type");
|
|
430
|
-
ensureIndex(store, ORDER_INDEX_NAME, [
|
|
431
|
-
"createdAt",
|
|
432
|
-
"seq",
|
|
433
|
-
"id"
|
|
434
|
-
]);
|
|
435
|
-
ensureIndex(store, SESSION_INDEX_NAME, "sessionId");
|
|
436
|
-
ensureIndex(store, SESSION_ORDER_INDEX_NAME, [
|
|
437
|
-
"sessionId",
|
|
438
|
-
"createdAt",
|
|
439
|
-
"seq",
|
|
440
|
-
"id"
|
|
441
|
-
]);
|
|
442
|
-
const sessionStore = db.objectStoreNames.contains(sessionStoreName) ? request.transaction?.objectStore(sessionStoreName) : db.createObjectStore(sessionStoreName, { keyPath: "sessionId" });
|
|
443
|
-
if (!sessionStore) return;
|
|
444
|
-
ensureIndex(sessionStore, SESSION_LAST_SEEN_INDEX_NAME, ["lastSeen", "sessionId"]);
|
|
445
|
-
if (event.oldVersion < 4) migrateSessionSummaries(store, sessionStore);
|
|
446
|
-
});
|
|
447
|
-
request.addEventListener("success", () => {
|
|
448
|
-
statsState.databaseOpenCount += 1;
|
|
449
|
-
resolve(request.result);
|
|
450
|
-
}, { once: true });
|
|
451
|
-
request.addEventListener("error", () => reject(request.error ?? /* @__PURE__ */ new Error("IndexedDB open failed")), { once: true });
|
|
452
|
-
});
|
|
453
|
-
});
|
|
454
|
-
return dbPromise;
|
|
455
|
-
};
|
|
456
|
-
const createTransaction = (db, mode, stores = storeName) => {
|
|
457
|
-
if (mode !== "readwrite" || !transactionOptions) return db.transaction(stores, mode);
|
|
458
|
-
try {
|
|
459
|
-
return db.transaction(stores, mode, transactionOptions);
|
|
460
|
-
} catch {
|
|
461
|
-
statsState.transactionOptionFallbacks += 1;
|
|
462
|
-
return db.transaction(stores, mode);
|
|
463
|
-
}
|
|
464
|
-
};
|
|
465
|
-
const withStore = async (mode, run) => {
|
|
466
|
-
const tx = createTransaction(await openDb(), mode);
|
|
467
|
-
const settled = transactionToPromise(tx);
|
|
468
|
-
try {
|
|
469
|
-
const result = await run(tx.objectStore(storeName), tx);
|
|
470
|
-
await settled;
|
|
471
|
-
return result;
|
|
472
|
-
} catch (error) {
|
|
473
|
-
if (tx.error === null) try {
|
|
474
|
-
tx.abort();
|
|
475
|
-
} catch {}
|
|
476
|
-
throw error;
|
|
477
|
-
}
|
|
478
|
-
};
|
|
479
|
-
const withLogAndSessionStores = async (mode, run) => {
|
|
480
|
-
const tx = createTransaction(await openDb(), mode, [storeName, sessionStoreName]);
|
|
481
|
-
const settled = transactionToPromise(tx);
|
|
482
|
-
try {
|
|
483
|
-
const result = await run(tx.objectStore(storeName), tx.objectStore(sessionStoreName), tx);
|
|
484
|
-
await settled;
|
|
485
|
-
return result;
|
|
486
|
-
} catch (error) {
|
|
487
|
-
if (tx.error === null) try {
|
|
488
|
-
tx.abort();
|
|
489
|
-
} catch {}
|
|
490
|
-
throw error;
|
|
491
|
-
}
|
|
492
|
-
};
|
|
493
|
-
const withSessionStore = async (mode, run) => {
|
|
494
|
-
const tx = createTransaction(await openDb(), mode, sessionStoreName);
|
|
495
|
-
const settled = transactionToPromise(tx);
|
|
496
|
-
try {
|
|
497
|
-
const result = await run(tx.objectStore(sessionStoreName), tx);
|
|
498
|
-
await settled;
|
|
499
|
-
return result;
|
|
500
|
-
} catch (error) {
|
|
501
|
-
if (tx.error === null) try {
|
|
502
|
-
tx.abort();
|
|
503
|
-
} catch {}
|
|
504
|
-
throw error;
|
|
505
|
-
}
|
|
506
|
-
};
|
|
507
|
-
const mergeSessionSummaries = async (sessionStore, entries) => {
|
|
508
|
-
const summaries = /* @__PURE__ */ new Map();
|
|
509
|
-
for (const entry of entries) addSessionSummary(summaries, entry);
|
|
510
|
-
for (const summary of summaries.values()) {
|
|
511
|
-
const current = await requestToPromise(sessionStore.get(summary.sessionId));
|
|
512
|
-
await requestToPromise(sessionStore.put(mergeSessionSummary(current, summary)));
|
|
513
|
-
}
|
|
514
|
-
};
|
|
515
|
-
const rebuildSessionSummaries = async (store, sessionStore, sessionIds) => {
|
|
516
|
-
if (sessionIds.size === 0) return;
|
|
517
|
-
const index = getStoreIndex(store, SESSION_ORDER_INDEX_NAME);
|
|
518
|
-
for (const sessionId of sessionIds) {
|
|
519
|
-
const summaries = /* @__PURE__ */ new Map();
|
|
520
|
-
const range = orderRangeForSessionId(sessionId);
|
|
521
|
-
await iterateCursor(index && range ? index.openCursor(range) : store.openCursor(), (cursor) => {
|
|
522
|
-
const entry = cursor.value;
|
|
523
|
-
if (entry.sessionId === sessionId) addSessionSummary(summaries, entry);
|
|
524
|
-
});
|
|
525
|
-
const summary = summaries.get(sessionId);
|
|
526
|
-
if (summary) await requestToPromise(sessionStore.put(summary));
|
|
527
|
-
else await requestToPromise(sessionStore.delete(sessionId));
|
|
528
|
-
}
|
|
529
|
-
};
|
|
530
|
-
const getEntries = async () => orderEntries(await withStore("readonly", (store) => requestToPromise(store.getAll())));
|
|
531
|
-
const countEntries = async () => withStore("readonly", (store) => {
|
|
532
|
-
const maybeStore = store;
|
|
533
|
-
if (typeof maybeStore.count !== "function") return void 0;
|
|
534
|
-
return requestToPromise(maybeStore.count.call(store));
|
|
535
|
-
});
|
|
536
|
-
const deleteEntries = async (entries, reason) => {
|
|
537
|
-
if (entries.length === 0) return;
|
|
538
|
-
await withLogAndSessionStores("readwrite", async (store, sessionStore) => {
|
|
539
|
-
const affectedSessionIds = /* @__PURE__ */ new Set();
|
|
540
|
-
for (const entry of entries) {
|
|
541
|
-
await requestToPromise(store.delete(entry.id));
|
|
542
|
-
if (entry.sessionId) affectedSessionIds.add(entry.sessionId);
|
|
543
|
-
dropPersistedEntry(entry, reason);
|
|
544
|
-
}
|
|
545
|
-
await rebuildSessionSummaries(store, sessionStore, affectedSessionIds);
|
|
546
|
-
});
|
|
547
|
-
};
|
|
548
|
-
const deleteByCursor = async (reason, openCursor, shouldDelete) => withLogAndSessionStores("readwrite", async (store, sessionStore) => {
|
|
549
|
-
const request = openCursor(store);
|
|
550
|
-
if (!request) return void 0;
|
|
551
|
-
let deleted = 0;
|
|
552
|
-
const affectedSessionIds = /* @__PURE__ */ new Set();
|
|
553
|
-
await iterateCursor(request, (cursor) => {
|
|
554
|
-
const entry = cursor.value;
|
|
555
|
-
if (!shouldDelete(entry, deleted)) return false;
|
|
556
|
-
cursor.delete();
|
|
557
|
-
deleted += 1;
|
|
558
|
-
if (entry.sessionId) affectedSessionIds.add(entry.sessionId);
|
|
559
|
-
dropPersistedEntry(entry, reason);
|
|
560
|
-
return true;
|
|
561
|
-
});
|
|
562
|
-
await rebuildSessionSummaries(store, sessionStore, affectedSessionIds);
|
|
563
|
-
return deleted;
|
|
564
|
-
});
|
|
565
|
-
const deleteExpiredByCursor = async (cutoff) => {
|
|
566
|
-
if (typeof IDBKeyRange === "undefined") return void 0;
|
|
567
|
-
const range = IDBKeyRange.upperBound(cutoff, true);
|
|
568
|
-
return deleteByCursor("ttl", (store) => getStoreIndex(store, "createdAt")?.openCursor(range), () => true);
|
|
569
|
-
};
|
|
570
|
-
const deleteOldestByCursor = async (reason, count) => {
|
|
571
|
-
if (count <= 0) return 0;
|
|
572
|
-
return deleteByCursor(reason, (store) => getStoreIndex(store, ORDER_INDEX_NAME)?.openCursor(), (_entry, deleted) => deleted < count);
|
|
573
|
-
};
|
|
574
|
-
const deleteOldestBytesByCursor = async (bytesToFree) => {
|
|
575
|
-
if (bytesToFree <= 0) return 0;
|
|
576
|
-
let freed = 0;
|
|
577
|
-
return deleteByCursor("max-bytes", (store) => getStoreIndex(store, ORDER_INDEX_NAME)?.openCursor(), (entry) => {
|
|
578
|
-
if (freed >= bytesToFree) return false;
|
|
579
|
-
freed += entry.byteLength;
|
|
580
|
-
return true;
|
|
581
|
-
});
|
|
582
|
-
};
|
|
583
|
-
const sumBytesByCursor = async () => withStore("readonly", async (store) => {
|
|
584
|
-
const index = getStoreIndex(store, ORDER_INDEX_NAME);
|
|
585
|
-
if (!index) return void 0;
|
|
586
|
-
let total = 0;
|
|
587
|
-
await iterateCursor(index.openCursor(), (cursor) => {
|
|
588
|
-
total += cursor.value.byteLength;
|
|
589
|
-
});
|
|
590
|
-
return total;
|
|
591
|
-
});
|
|
592
|
-
const queryEntriesByCursor = async (queryOptions) => withStore("readonly", async (store) => {
|
|
593
|
-
const index = getStoreIndex(store, queryOptions.sessionId === void 0 ? ORDER_INDEX_NAME : SESSION_ORDER_INDEX_NAME);
|
|
594
|
-
if (!index) return void 0;
|
|
595
|
-
const entries = [];
|
|
596
|
-
const limit = queryOptions.limit;
|
|
597
|
-
if (limit !== void 0 && limit <= 0) return entries;
|
|
598
|
-
await iterateCursor(index.openCursor(orderRangeForQuery(queryOptions), queryOptions.order === "desc" ? "prev" : "next"), (cursor) => {
|
|
599
|
-
const entry = cursor.value;
|
|
600
|
-
if (!shouldKeepEntry(entry, queryOptions)) return true;
|
|
601
|
-
entries.push(entry);
|
|
602
|
-
return limit === void 0 || entries.length < limit;
|
|
603
|
-
});
|
|
604
|
-
return entries;
|
|
605
|
-
});
|
|
606
|
-
const listSessions = async (sessionQueryOptions = {}) => {
|
|
607
|
-
const limit = sessionQueryOptions.limit;
|
|
608
|
-
if (limit !== void 0 && limit <= 0) return [];
|
|
609
|
-
return withSessionStore("readonly", async (sessionStore) => {
|
|
610
|
-
const sessions = [];
|
|
611
|
-
const index = getStoreIndex(sessionStore, SESSION_LAST_SEEN_INDEX_NAME);
|
|
612
|
-
if (!index) return orderSessions(await requestToPromise(sessionStore.getAll()), sessionQueryOptions.order).slice(0, limit);
|
|
613
|
-
await iterateCursor(index.openCursor(void 0, sessionQueryOptions.order === "asc" ? "next" : "prev"), (cursor) => {
|
|
614
|
-
sessions.push(cursor.value);
|
|
615
|
-
return limit === void 0 || sessions.length < limit;
|
|
616
|
-
});
|
|
617
|
-
return sessions;
|
|
618
|
-
});
|
|
619
|
-
};
|
|
620
|
-
const pruneWithEntries = async () => {
|
|
621
|
-
const now = Date.now();
|
|
622
|
-
let entries = await getEntries();
|
|
623
|
-
const expired = options.ttlMs === void 0 ? [] : entries.filter((entry) => now - entry.createdAt > options.ttlMs);
|
|
624
|
-
if (expired.length > 0) {
|
|
625
|
-
await deleteEntries(expired, "ttl");
|
|
626
|
-
const expiredIds = new Set(expired.map((entry) => entry.id));
|
|
627
|
-
entries = entries.filter((entry) => !expiredIds.has(entry.id));
|
|
628
|
-
}
|
|
629
|
-
if (maxEntries >= 0 && entries.length > maxEntries) {
|
|
630
|
-
await deleteEntries(entries.slice(0, entries.length - maxEntries), "max-entries");
|
|
631
|
-
entries = entries.slice(entries.length - maxEntries);
|
|
632
|
-
}
|
|
633
|
-
if (maxBytes >= 0) {
|
|
634
|
-
let total = entries.reduce((sum, entry) => sum + entry.byteLength, 0);
|
|
635
|
-
const dropped = [];
|
|
636
|
-
for (const entry of entries) {
|
|
637
|
-
if (total <= maxBytes) break;
|
|
638
|
-
total -= entry.byteLength;
|
|
639
|
-
dropped.push(entry);
|
|
640
|
-
}
|
|
641
|
-
await deleteEntries(dropped, "max-bytes");
|
|
642
|
-
}
|
|
643
|
-
};
|
|
644
|
-
const pruneWithCursors = async () => {
|
|
645
|
-
if (options.ttlMs !== void 0) {
|
|
646
|
-
if (await deleteExpiredByCursor(Date.now() - options.ttlMs) === void 0) return false;
|
|
647
|
-
}
|
|
648
|
-
if (maxEntries >= 0) {
|
|
649
|
-
const persistedCount = await countEntries();
|
|
650
|
-
if (persistedCount === void 0) return false;
|
|
651
|
-
if (persistedCount > maxEntries) {
|
|
652
|
-
if (await deleteOldestByCursor("max-entries", persistedCount - maxEntries) === void 0) return false;
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
if (maxBytes >= 0) {
|
|
656
|
-
const totalBytes = await sumBytesByCursor();
|
|
657
|
-
if (totalBytes === void 0) return false;
|
|
658
|
-
if (totalBytes > maxBytes) {
|
|
659
|
-
if (await deleteOldestBytesByCursor(totalBytes - maxBytes) === void 0) return false;
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
return true;
|
|
663
|
-
};
|
|
664
|
-
const prune = async () => {
|
|
665
|
-
const startedAt = nowMs();
|
|
666
|
-
try {
|
|
667
|
-
if (await pruneWithCursors()) return;
|
|
668
|
-
statsState.pruneFallbacks += 1;
|
|
669
|
-
await pruneWithEntries();
|
|
670
|
-
} finally {
|
|
671
|
-
statsState.prunes += 1;
|
|
672
|
-
statsState.lastPruneDurationMs = nowMs() - startedAt;
|
|
673
|
-
}
|
|
674
|
-
};
|
|
675
|
-
const sessionIdForEvent = (event) => {
|
|
676
|
-
if (!sessionOptions) return void 0;
|
|
677
|
-
return normalizeSessionId(sessionOptions.getId?.(event)) ?? normalizeSessionId(event.context?.[sessionOptions.contextKey]) ?? sessionOptions.id;
|
|
678
|
-
};
|
|
679
|
-
const eventWithSessionContext = (event, sessionId) => {
|
|
680
|
-
if (!sessionId || !sessionOptions?.contextKey) return event;
|
|
681
|
-
if (event.context?.[sessionOptions.contextKey] === sessionId) return event;
|
|
682
|
-
return {
|
|
683
|
-
...event,
|
|
684
|
-
context: {
|
|
685
|
-
...event.context,
|
|
686
|
-
[sessionOptions.contextKey]: sessionId
|
|
687
|
-
}
|
|
688
|
-
};
|
|
689
|
-
};
|
|
690
|
-
const eventWithResolvedSession = (event) => {
|
|
691
|
-
const sessionId = sessionIdForEvent(event);
|
|
692
|
-
return {
|
|
693
|
-
event: eventWithSessionContext(event, sessionId),
|
|
694
|
-
sessionId
|
|
695
|
-
};
|
|
696
|
-
};
|
|
697
|
-
const eventToEntry = (event) => {
|
|
698
|
-
const resolved = eventWithResolvedSession(event);
|
|
699
|
-
const payload = codec.encode(resolved.event);
|
|
700
|
-
return {
|
|
701
|
-
id: event.id,
|
|
702
|
-
sessionId: resolved.sessionId,
|
|
703
|
-
seq: event.seq,
|
|
704
|
-
createdAt: event.time,
|
|
705
|
-
level: event.level,
|
|
706
|
-
levelName: event.levelName,
|
|
707
|
-
logger: event.logger,
|
|
708
|
-
type: event.type,
|
|
709
|
-
byteLength: payloadByteLength(payload),
|
|
710
|
-
payload
|
|
711
|
-
};
|
|
712
|
-
};
|
|
713
|
-
const clearLocalStorageSpill = (expectedValue) => {
|
|
714
|
-
if (!spillOptions) return false;
|
|
715
|
-
try {
|
|
716
|
-
if (expectedValue !== void 0 && spillOptions.storage.getItem(spillOptions.key) !== expectedValue) return false;
|
|
717
|
-
spillOptions.storage.removeItem(spillOptions.key);
|
|
718
|
-
if (expectedValue === void 0 || localStorageSpillClearCandidate?.value === expectedValue) localStorageSpillClearCandidate = void 0;
|
|
719
|
-
return true;
|
|
720
|
-
} catch (error) {
|
|
721
|
-
reportSpillError(error, "clear");
|
|
722
|
-
return false;
|
|
723
|
-
}
|
|
724
|
-
};
|
|
725
|
-
const rememberLocalStorageSpill = (value, events) => {
|
|
726
|
-
localStorageSpillClearCandidate = {
|
|
727
|
-
pendingIds: new Set(events.map((event) => event.id)),
|
|
728
|
-
value
|
|
729
|
-
};
|
|
730
|
-
};
|
|
731
|
-
const markLocalStorageSpillPersisted = (events) => {
|
|
732
|
-
const candidate = localStorageSpillClearCandidate;
|
|
733
|
-
if (!candidate || events.length === 0) return;
|
|
734
|
-
for (const event of events) candidate.pendingIds.delete(event.id);
|
|
735
|
-
if (candidate.pendingIds.size === 0) clearLocalStorageSpill(candidate.value);
|
|
736
|
-
};
|
|
737
|
-
const writeBatch = async (events) => {
|
|
738
|
-
if (events.length === 0) return;
|
|
739
|
-
const startedAt = nowMs();
|
|
740
|
-
statsState.lastFlushBatchSize = events.length;
|
|
741
|
-
const entries = events.map(eventToEntry);
|
|
742
|
-
try {
|
|
743
|
-
await withLogAndSessionStores("readwrite", async (store, sessionStore) => {
|
|
744
|
-
const rebuildSessionIds = /* @__PURE__ */ new Set();
|
|
745
|
-
for (const entry of entries) {
|
|
746
|
-
const existing = await requestToPromise(store.get(entry.id));
|
|
747
|
-
if (existing?.sessionId) rebuildSessionIds.add(existing.sessionId);
|
|
748
|
-
if (existing && entry.sessionId) rebuildSessionIds.add(entry.sessionId);
|
|
749
|
-
await requestToPromise(store.put(entry));
|
|
750
|
-
}
|
|
751
|
-
await rebuildSessionSummaries(store, sessionStore, rebuildSessionIds);
|
|
752
|
-
await mergeSessionSummaries(sessionStore, entries.filter((entry) => !entry.sessionId || !rebuildSessionIds.has(entry.sessionId)));
|
|
753
|
-
});
|
|
754
|
-
statsState.persisted += entries.length;
|
|
755
|
-
(0, _loggerjs_core.incrementLoggerMetaCounter)("transport.indexeddb.persisted", entries.length);
|
|
756
|
-
markLocalStorageSpillPersisted(events);
|
|
757
|
-
await prune();
|
|
758
|
-
statsState.flushes += 1;
|
|
759
|
-
} catch (error) {
|
|
760
|
-
statsState.flushErrors += 1;
|
|
761
|
-
throw error;
|
|
762
|
-
} finally {
|
|
763
|
-
statsState.lastFlushDurationMs = nowMs() - startedAt;
|
|
764
|
-
}
|
|
765
|
-
};
|
|
766
|
-
const stringifySpillPayload = (events) => (0, _loggerjs_core.safeJsonStringify)({
|
|
767
|
-
createdAt: Date.now(),
|
|
768
|
-
entries: events,
|
|
769
|
-
schema: SPILL_SCHEMA
|
|
770
|
-
}, { maxArrayLength: spillOptions?.maxEntries ?? DEFAULT_SPILL_MAX_ENTRIES });
|
|
771
|
-
const spillStorageBudget = (currentValue) => {
|
|
772
|
-
if (!spillOptions) return 0;
|
|
773
|
-
const ownBytes = currentValue ? storageByteLength(spillOptions.key, currentValue) : 0;
|
|
774
|
-
const originBytes = estimateLocalStorageBytes(spillOptions.storage);
|
|
775
|
-
if (originBytes === void 0) return spillOptions.maxBytes;
|
|
776
|
-
return Math.max(0, Math.min(spillOptions.maxBytes, spillOptions.maxOriginBytes - originBytes + ownBytes));
|
|
777
|
-
};
|
|
778
|
-
const pendingEventsForSpill = () => {
|
|
779
|
-
const events = [...pendingFlushBatch ?? [], ...buffer];
|
|
780
|
-
const seen = /* @__PURE__ */ new Set();
|
|
781
|
-
const deduped = [];
|
|
782
|
-
for (const event of events) {
|
|
783
|
-
if (seen.has(event.id)) continue;
|
|
784
|
-
seen.add(event.id);
|
|
785
|
-
if (spillOptions?.minLevelValue !== void 0 && event.level < spillOptions.minLevelValue) continue;
|
|
786
|
-
deduped.push(eventWithResolvedSession(event).event);
|
|
787
|
-
}
|
|
788
|
-
return deduped;
|
|
789
|
-
};
|
|
790
|
-
const trimSpillEventsToBudget = (events, budget) => {
|
|
791
|
-
if (!spillOptions) return void 0;
|
|
792
|
-
while (events.length > spillOptions.maxEntries) {
|
|
793
|
-
const dropped = events.shift();
|
|
794
|
-
if (dropped) dropSpillEvent(dropped, "max-entries");
|
|
795
|
-
}
|
|
796
|
-
while (events.length > 0) {
|
|
797
|
-
const value = stringifySpillPayload(events);
|
|
798
|
-
if (storageByteLength(spillOptions.key, value) <= budget) return value;
|
|
799
|
-
const dropped = events.shift();
|
|
800
|
-
if (dropped) dropSpillEvent(dropped, "max-bytes");
|
|
801
|
-
}
|
|
802
|
-
};
|
|
803
|
-
const writeLocalStorageSpill = () => {
|
|
804
|
-
if (!spillOptions) return;
|
|
805
|
-
const pendingEvents = pendingEventsForSpill();
|
|
806
|
-
if (pendingEvents.length === 0) return;
|
|
807
|
-
try {
|
|
808
|
-
const currentValue = spillOptions.storage.getItem(spillOptions.key);
|
|
809
|
-
let events = pendingEvents;
|
|
810
|
-
if (currentValue) {
|
|
811
|
-
const merged = /* @__PURE__ */ new Map();
|
|
812
|
-
try {
|
|
813
|
-
for (const event of parseSpillPayload(currentValue) ?? []) merged.set(event.id, event);
|
|
814
|
-
} catch {}
|
|
815
|
-
for (const event of pendingEvents) merged.set(event.id, event);
|
|
816
|
-
events = [...merged.values()];
|
|
817
|
-
}
|
|
818
|
-
const budget = spillStorageBudget(currentValue);
|
|
819
|
-
const value = trimSpillEventsToBudget(events, budget);
|
|
820
|
-
if (!value) return;
|
|
821
|
-
try {
|
|
822
|
-
spillOptions.storage.setItem(spillOptions.key, value);
|
|
823
|
-
} catch (error) {
|
|
824
|
-
if (!isQuotaExceededError(error)) throw error;
|
|
825
|
-
while (events.length > 0) {
|
|
826
|
-
const dropped = events.shift();
|
|
827
|
-
if (dropped) dropSpillEvent(dropped, "quota");
|
|
828
|
-
const retryValue = trimSpillEventsToBudget(events, budget);
|
|
829
|
-
if (!retryValue) return;
|
|
830
|
-
try {
|
|
831
|
-
spillOptions.storage.setItem(spillOptions.key, retryValue);
|
|
832
|
-
rememberLocalStorageSpill(retryValue, events);
|
|
833
|
-
statsState.localStorageSpillWrites += 1;
|
|
834
|
-
statsState.localStorageSpillEntries += events.length;
|
|
835
|
-
return;
|
|
836
|
-
} catch (retryError) {
|
|
837
|
-
if (!isQuotaExceededError(retryError)) throw retryError;
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
|
-
return;
|
|
841
|
-
}
|
|
842
|
-
rememberLocalStorageSpill(value, events);
|
|
843
|
-
statsState.localStorageSpillWrites += 1;
|
|
844
|
-
statsState.localStorageSpillEntries += events.length;
|
|
845
|
-
} catch (error) {
|
|
846
|
-
reportSpillError(error, "write");
|
|
847
|
-
}
|
|
848
|
-
};
|
|
849
|
-
const drainLocalStorageSpill = async () => {
|
|
850
|
-
if (!spillOptions) return;
|
|
851
|
-
let raw;
|
|
852
|
-
try {
|
|
853
|
-
raw = spillOptions.storage.getItem(spillOptions.key);
|
|
854
|
-
} catch (error) {
|
|
855
|
-
reportSpillError(error, "read");
|
|
856
|
-
return;
|
|
857
|
-
}
|
|
858
|
-
if (!raw) return;
|
|
859
|
-
let events;
|
|
860
|
-
try {
|
|
861
|
-
events = parseSpillPayload(raw);
|
|
862
|
-
} catch (error) {
|
|
863
|
-
reportSpillError(error, "parse");
|
|
864
|
-
clearLocalStorageSpill();
|
|
865
|
-
return;
|
|
866
|
-
}
|
|
867
|
-
if (!events || events.length === 0) {
|
|
868
|
-
clearLocalStorageSpill();
|
|
869
|
-
return;
|
|
870
|
-
}
|
|
871
|
-
await writeBatch(events);
|
|
872
|
-
clearLocalStorageSpill(raw);
|
|
873
|
-
statsState.localStorageSpillDrains += 1;
|
|
874
|
-
statsState.localStorageSpillDrainedEntries += events.length;
|
|
875
|
-
};
|
|
876
|
-
const ensureSpillDrained = async () => {
|
|
877
|
-
const promise = spillDrainPromise;
|
|
878
|
-
if (!promise) return;
|
|
879
|
-
await promise;
|
|
880
|
-
};
|
|
881
|
-
if (spillOptions?.drainOnCreate) spillDrainPromise = drainLocalStorageSpill().catch((error) => reportSpillError(error, "drain")).finally(() => {
|
|
882
|
-
spillDrainPromise = void 0;
|
|
883
|
-
});
|
|
884
|
-
const clearTimer = () => {
|
|
885
|
-
if (timer) clearTimeout(timer);
|
|
886
|
-
timer = void 0;
|
|
887
|
-
};
|
|
888
|
-
const flushPending = async () => {
|
|
889
|
-
if (flushPromise) return flushPromise;
|
|
890
|
-
clearTimer();
|
|
891
|
-
const batch = buffer.splice(0, buffer.length);
|
|
892
|
-
pendingFlushBatch = batch;
|
|
893
|
-
const clearBarrier = clearPromise;
|
|
894
|
-
flushPromise = (async () => {
|
|
895
|
-
await clearBarrier;
|
|
896
|
-
await ensureSpillDrained();
|
|
897
|
-
await writeBatch(batch);
|
|
898
|
-
})().finally(() => {
|
|
899
|
-
if (pendingFlushBatch === batch) pendingFlushBatch = void 0;
|
|
900
|
-
flushPromise = void 0;
|
|
901
|
-
if (buffer.length > 0 && !closed) schedule();
|
|
902
|
-
});
|
|
903
|
-
return flushPromise;
|
|
904
|
-
};
|
|
905
|
-
const schedule = () => {
|
|
906
|
-
if (timer || flushIntervalMs <= 0 || closed) return;
|
|
907
|
-
timer = setTimeout(() => {
|
|
908
|
-
flushPending().catch((error) => reportInternalError(error, "flush"));
|
|
909
|
-
}, flushIntervalMs);
|
|
910
|
-
};
|
|
911
|
-
const onPageHide = () => {
|
|
912
|
-
if (spillOptions?.spillOnPageHide) writeLocalStorageSpill();
|
|
913
|
-
if (flushOnPageHide) flushPending().catch((error) => reportInternalError(error, "pagehide-flush"));
|
|
914
|
-
};
|
|
915
|
-
const onVisibilityChange = () => {
|
|
916
|
-
if (typeof document !== "undefined" && document.visibilityState === "hidden") {
|
|
917
|
-
if (spillOptions?.spillOnPageHide) writeLocalStorageSpill();
|
|
918
|
-
if (flushOnPageHide) flushPending().catch((error) => reportInternalError(error, "visibility-flush"));
|
|
919
|
-
}
|
|
920
|
-
};
|
|
921
|
-
const addVisibilityChangeListener = () => {
|
|
922
|
-
if (typeof document !== "undefined" && typeof document.addEventListener === "function") {
|
|
923
|
-
document.addEventListener("visibilitychange", onVisibilityChange);
|
|
924
|
-
return () => document.removeEventListener("visibilitychange", onVisibilityChange);
|
|
925
|
-
}
|
|
926
|
-
if (typeof globalThis.addEventListener !== "function") return void 0;
|
|
927
|
-
globalThis.addEventListener("visibilitychange", onVisibilityChange);
|
|
928
|
-
return () => globalThis.removeEventListener?.("visibilitychange", onVisibilityChange);
|
|
929
|
-
};
|
|
930
|
-
let removeVisibilityChangeListener;
|
|
931
|
-
if (flushOnPageHide || spillOptions?.spillOnPageHide) {
|
|
932
|
-
globalThis.addEventListener?.("pagehide", onPageHide);
|
|
933
|
-
removeVisibilityChangeListener = addVisibilityChangeListener();
|
|
934
|
-
}
|
|
935
|
-
return {
|
|
936
|
-
name: options.name ?? "indexeddb",
|
|
937
|
-
minLevel: options.minLevel,
|
|
938
|
-
log(event, context) {
|
|
939
|
-
if (closed) return;
|
|
940
|
-
if (options.minLevel !== void 0 && event.level < (0, _loggerjs_core.toLevelValue)(options.minLevel)) return;
|
|
941
|
-
lastContext = context;
|
|
942
|
-
if (maxBufferSize === 0 || buffer.length >= maxBufferSize) {
|
|
943
|
-
if (dropPolicy === "drop-newest") {
|
|
944
|
-
dropEvent(event, "buffer-full");
|
|
945
|
-
return;
|
|
946
|
-
}
|
|
947
|
-
const dropped = buffer.shift();
|
|
948
|
-
if (dropped) dropEvent(dropped, "buffer-full");
|
|
949
|
-
}
|
|
950
|
-
buffer.push(event);
|
|
951
|
-
statsState.enqueued += 1;
|
|
952
|
-
statsState.maxBufferDepth = Math.max(statsState.maxBufferDepth, buffer.length);
|
|
953
|
-
if (buffer.length >= batchSize || flushIntervalMs === 0) flushPending().catch((error) => reportInternalError(error, "flush"));
|
|
954
|
-
else schedule();
|
|
955
|
-
},
|
|
956
|
-
flush() {
|
|
957
|
-
return flushPending();
|
|
958
|
-
},
|
|
959
|
-
async count() {
|
|
960
|
-
await flushPending();
|
|
961
|
-
return await countEntries() ?? (await getEntries()).length;
|
|
962
|
-
},
|
|
963
|
-
async clear() {
|
|
964
|
-
if (clearPromise) return clearPromise;
|
|
965
|
-
const inFlightFlush = flushPromise;
|
|
966
|
-
const inFlightDrain = spillDrainPromise;
|
|
967
|
-
buffer.length = 0;
|
|
968
|
-
clearTimer();
|
|
969
|
-
clearLocalStorageSpill();
|
|
970
|
-
clearPromise = (async () => {
|
|
971
|
-
await inFlightFlush;
|
|
972
|
-
await inFlightDrain;
|
|
973
|
-
await withLogAndSessionStores("readwrite", async (store, sessionStore) => {
|
|
974
|
-
await requestToPromise(store.clear());
|
|
975
|
-
await requestToPromise(sessionStore.clear());
|
|
976
|
-
});
|
|
977
|
-
})().finally(() => {
|
|
978
|
-
clearPromise = void 0;
|
|
979
|
-
});
|
|
980
|
-
return clearPromise;
|
|
981
|
-
},
|
|
982
|
-
async remove(ids) {
|
|
983
|
-
await flushPending();
|
|
984
|
-
const items = typeof ids === "string" ? [ids] : ids;
|
|
985
|
-
if (items.length === 0) return;
|
|
986
|
-
await withLogAndSessionStores("readwrite", async (store, sessionStore) => {
|
|
987
|
-
const affectedSessionIds = /* @__PURE__ */ new Set();
|
|
988
|
-
for (const id of items) {
|
|
989
|
-
const entry = await requestToPromise(store.get(id));
|
|
990
|
-
if (entry?.sessionId) affectedSessionIds.add(entry.sessionId);
|
|
991
|
-
await requestToPromise(store.delete(id));
|
|
992
|
-
}
|
|
993
|
-
await rebuildSessionSummaries(store, sessionStore, affectedSessionIds);
|
|
994
|
-
});
|
|
995
|
-
},
|
|
996
|
-
async *query(queryOptions = {}) {
|
|
997
|
-
await flushPending();
|
|
998
|
-
const startedAt = nowMs();
|
|
999
|
-
const cursorEntries = await queryEntriesByCursor(queryOptions);
|
|
1000
|
-
if (cursorEntries === void 0) statsState.queryFallbacks += 1;
|
|
1001
|
-
const entries = cursorEntries ?? orderEntries(await getEntries(), queryOptions.order ?? "asc").filter((entry) => shouldKeepEntry(entry, queryOptions)).slice(0, queryOptions.limit);
|
|
1002
|
-
statsState.queries += 1;
|
|
1003
|
-
statsState.lastQueryDurationMs = nowMs() - startedAt;
|
|
1004
|
-
for (const entry of entries) {
|
|
1005
|
-
const event = decodeEntry(entry, codec);
|
|
1006
|
-
if (event) yield event;
|
|
1007
|
-
}
|
|
1008
|
-
},
|
|
1009
|
-
async sessions(sessionQueryOptions = {}) {
|
|
1010
|
-
await flushPending();
|
|
1011
|
-
return listSessions(sessionQueryOptions);
|
|
1012
|
-
},
|
|
1013
|
-
stats() {
|
|
1014
|
-
return snapshotStats();
|
|
1015
|
-
},
|
|
1016
|
-
close() {
|
|
1017
|
-
closed = true;
|
|
1018
|
-
clearTimer();
|
|
1019
|
-
globalThis.removeEventListener?.("pagehide", onPageHide);
|
|
1020
|
-
removeVisibilityChangeListener?.();
|
|
1021
|
-
removeVisibilityChangeListener = void 0;
|
|
1022
|
-
return flushPending().finally(() => {
|
|
1023
|
-
const promise = dbPromise;
|
|
1024
|
-
dbPromise = void 0;
|
|
1025
|
-
return promise?.then((db) => db.close());
|
|
1026
|
-
});
|
|
1027
|
-
}
|
|
1028
|
-
};
|
|
1029
|
-
}
|
|
1030
|
-
//#endregion
|
|
1031
4
|
//#region src/offline-first-transport.ts
|
|
1032
5
|
function normalizeBatchSize(value) {
|
|
1033
6
|
if (value === void 0 || !Number.isFinite(value) || value <= 0) return 100;
|
|
@@ -1050,7 +23,7 @@ function offlineFirstTransport(remote, options = {}) {
|
|
|
1050
23
|
const name = options.name ?? `offline-first(${remote.name ?? "transport"})`;
|
|
1051
24
|
const replayBatchSize = normalizeBatchSize(options.replayBatchSize);
|
|
1052
25
|
const queueOptions = options.queueOptions;
|
|
1053
|
-
const queue = options.queue ?? indexedDbTransport({
|
|
26
|
+
const queue = options.queue ?? require_indexeddb_transport.indexedDbTransport({
|
|
1054
27
|
dbName: "loggerjs-offline",
|
|
1055
28
|
flushIntervalMs: 0,
|
|
1056
29
|
name: `${name}:queue`,
|