@loggerjs/browser 0.5.2 → 0.5.4
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 +40 -2
- package/dist/index.cjs +641 -41
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +641 -41
- package/dist/index.js.map +1 -1
- package/dist/indexeddb-transport.cjs +529 -27
- 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 +530 -28
- package/dist/indexeddb-transport.js.map +1 -1
- package/dist/offline-first-transport.cjs +532 -28
- 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 +533 -29
- 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
package/dist/index.cjs
CHANGED
|
@@ -894,12 +894,98 @@ function indexedDbBrowserHttpOfflineQueue(options = {}) {
|
|
|
894
894
|
//#region src/indexeddb-transport.ts
|
|
895
895
|
const DEFAULT_DB_NAME = "loggerjs";
|
|
896
896
|
const DEFAULT_STORE_NAME = "logs";
|
|
897
|
-
const
|
|
897
|
+
const DEFAULT_SESSION_CONTEXT_KEY$1 = "sessionId";
|
|
898
|
+
const DEFAULT_SPILL_MAX_ENTRIES = 200;
|
|
899
|
+
const DEFAULT_SPILL_MAX_BYTES = 512 * 1024;
|
|
900
|
+
const DEFAULT_SPILL_MAX_ORIGIN_BYTES = 5 * 1024 * 1024;
|
|
901
|
+
const DB_VERSION = 4;
|
|
898
902
|
const ORDER_INDEX_NAME = "createdAtSeq";
|
|
903
|
+
const SESSION_INDEX_NAME = "sessionId";
|
|
904
|
+
const SESSION_ORDER_INDEX_NAME = "sessionIdCreatedAtSeq";
|
|
905
|
+
const SESSION_STORE_SUFFIX = "__sessions";
|
|
906
|
+
const SESSION_LAST_SEEN_INDEX_NAME = "lastSeenSessionId";
|
|
907
|
+
const SPILL_SCHEMA = "loggerjs.indexeddb-spill.v1";
|
|
899
908
|
function normalizePositiveInteger(value, fallback) {
|
|
900
909
|
if (value === void 0 || !Number.isFinite(value)) return fallback;
|
|
901
910
|
return Math.max(0, Math.floor(value));
|
|
902
911
|
}
|
|
912
|
+
let fallbackSessionSeq = 0;
|
|
913
|
+
function defaultPageSessionId() {
|
|
914
|
+
const randomUUID = globalThis.crypto?.randomUUID;
|
|
915
|
+
if (randomUUID) return randomUUID.call(globalThis.crypto);
|
|
916
|
+
return `${Date.now().toString(36)}-${(fallbackSessionSeq++).toString(36)}`;
|
|
917
|
+
}
|
|
918
|
+
function normalizeSessionId(value) {
|
|
919
|
+
if (typeof value !== "string") return void 0;
|
|
920
|
+
const normalized = value.trim();
|
|
921
|
+
return normalized.length > 0 ? normalized : void 0;
|
|
922
|
+
}
|
|
923
|
+
function normalizeSessionOptions(session) {
|
|
924
|
+
if (session === false) return void 0;
|
|
925
|
+
if (typeof session === "string") return {
|
|
926
|
+
contextKey: DEFAULT_SESSION_CONTEXT_KEY$1,
|
|
927
|
+
id: normalizeSessionId(session) ?? defaultPageSessionId()
|
|
928
|
+
};
|
|
929
|
+
return {
|
|
930
|
+
contextKey: session?.contextKey ?? DEFAULT_SESSION_CONTEXT_KEY$1,
|
|
931
|
+
getId: session?.getId,
|
|
932
|
+
id: normalizeSessionId(session?.id) ?? defaultPageSessionId()
|
|
933
|
+
};
|
|
934
|
+
}
|
|
935
|
+
function resolveLocalStorage(storage) {
|
|
936
|
+
if (storage) return storage;
|
|
937
|
+
try {
|
|
938
|
+
return globalThis.localStorage;
|
|
939
|
+
} catch {
|
|
940
|
+
return;
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
function normalizeLocalStorageSpillOptions(spill, dbName, storeName) {
|
|
944
|
+
if (!spill) return void 0;
|
|
945
|
+
const storage = resolveLocalStorage(spill.storage);
|
|
946
|
+
if (!storage) return void 0;
|
|
947
|
+
const namespace = spill.namespace ?? `${dbName}:${storeName}`;
|
|
948
|
+
return {
|
|
949
|
+
drainOnCreate: spill.drainOnCreate ?? true,
|
|
950
|
+
key: `loggerjs:spill:v1:${namespace}`,
|
|
951
|
+
maxBytes: normalizePositiveInteger(spill.maxBytes, DEFAULT_SPILL_MAX_BYTES),
|
|
952
|
+
maxEntries: normalizePositiveInteger(spill.maxEntries, DEFAULT_SPILL_MAX_ENTRIES),
|
|
953
|
+
maxOriginBytes: normalizePositiveInteger(spill.maxOriginBytes, DEFAULT_SPILL_MAX_ORIGIN_BYTES),
|
|
954
|
+
minLevelValue: spill.minLevel === void 0 ? void 0 : (0, _loggerjs_core.toLevelValue)(spill.minLevel),
|
|
955
|
+
onDrop: spill.onDrop,
|
|
956
|
+
spillOnPageHide: spill.spillOnPageHide ?? true,
|
|
957
|
+
storage
|
|
958
|
+
};
|
|
959
|
+
}
|
|
960
|
+
function storageByteLength(key, value) {
|
|
961
|
+
return 2 * (key.length + value.length);
|
|
962
|
+
}
|
|
963
|
+
function estimateLocalStorageBytes(storage) {
|
|
964
|
+
try {
|
|
965
|
+
let total = 0;
|
|
966
|
+
for (let index = 0; index < storage.length; index += 1) {
|
|
967
|
+
const key = storage.key(index);
|
|
968
|
+
if (key === null) continue;
|
|
969
|
+
total += storageByteLength(key, storage.getItem(key) ?? "");
|
|
970
|
+
}
|
|
971
|
+
return total;
|
|
972
|
+
} catch {
|
|
973
|
+
return;
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
function isQuotaExceededError(error) {
|
|
977
|
+
return typeof DOMException !== "undefined" && error instanceof DOMException && (error.name === "QuotaExceededError" || error.name === "NS_ERROR_DOM_QUOTA_REACHED");
|
|
978
|
+
}
|
|
979
|
+
function isSpillLogEvent(value) {
|
|
980
|
+
if (!value || typeof value !== "object") return false;
|
|
981
|
+
const event = value;
|
|
982
|
+
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";
|
|
983
|
+
}
|
|
984
|
+
function parseSpillPayload(raw) {
|
|
985
|
+
const parsed = JSON.parse(raw);
|
|
986
|
+
if (parsed.schema !== SPILL_SCHEMA || !Array.isArray(parsed.entries)) return void 0;
|
|
987
|
+
return parsed.entries.filter(isSpillLogEvent);
|
|
988
|
+
}
|
|
903
989
|
function nowMs$1() {
|
|
904
990
|
return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
|
|
905
991
|
}
|
|
@@ -942,6 +1028,9 @@ function indexExists(store, name) {
|
|
|
942
1028
|
function ensureIndex(store, name, keyPath) {
|
|
943
1029
|
if (!indexExists(store, name)) store.createIndex(name, keyPath);
|
|
944
1030
|
}
|
|
1031
|
+
function sessionStoreNameFor(storeName) {
|
|
1032
|
+
return `${storeName}${SESSION_STORE_SUFFIX}`;
|
|
1033
|
+
}
|
|
945
1034
|
function getStoreIndex(store, name) {
|
|
946
1035
|
const maybeStore = store;
|
|
947
1036
|
if (typeof maybeStore.index !== "function" || !indexExists(store, name)) return void 0;
|
|
@@ -965,13 +1054,67 @@ function decodeEntry(entry, codec) {
|
|
|
965
1054
|
function shouldKeepEntry(entry, options) {
|
|
966
1055
|
if (options.from !== void 0 && entry.createdAt < options.from) return false;
|
|
967
1056
|
if (options.to !== void 0 && entry.createdAt > options.to) return false;
|
|
1057
|
+
if (options.sessionId !== void 0 && entry.sessionId !== options.sessionId) return false;
|
|
968
1058
|
if (options.minLevel !== void 0 && entry.level < (0, _loggerjs_core.toLevelValue)(options.minLevel)) return false;
|
|
969
1059
|
if (options.logger !== void 0 && entry.logger !== options.logger) return false;
|
|
970
1060
|
if (options.type !== void 0 && entry.type !== options.type) return false;
|
|
971
1061
|
return true;
|
|
972
1062
|
}
|
|
1063
|
+
function addSessionSummary(sessions, entry) {
|
|
1064
|
+
if (!entry.sessionId) return;
|
|
1065
|
+
const current = sessions.get(entry.sessionId);
|
|
1066
|
+
if (current) {
|
|
1067
|
+
current.firstSeen = Math.min(current.firstSeen, entry.createdAt);
|
|
1068
|
+
current.lastSeen = Math.max(current.lastSeen, entry.createdAt);
|
|
1069
|
+
current.count += 1;
|
|
1070
|
+
current.byteLength += entry.byteLength;
|
|
1071
|
+
return;
|
|
1072
|
+
}
|
|
1073
|
+
sessions.set(entry.sessionId, {
|
|
1074
|
+
byteLength: entry.byteLength,
|
|
1075
|
+
count: 1,
|
|
1076
|
+
firstSeen: entry.createdAt,
|
|
1077
|
+
lastSeen: entry.createdAt,
|
|
1078
|
+
sessionId: entry.sessionId
|
|
1079
|
+
});
|
|
1080
|
+
}
|
|
1081
|
+
function mergeSessionSummary(current, incoming) {
|
|
1082
|
+
if (!current) return incoming;
|
|
1083
|
+
return {
|
|
1084
|
+
byteLength: current.byteLength + incoming.byteLength,
|
|
1085
|
+
count: current.count + incoming.count,
|
|
1086
|
+
firstSeen: Math.min(current.firstSeen, incoming.firstSeen),
|
|
1087
|
+
lastSeen: Math.max(current.lastSeen, incoming.lastSeen),
|
|
1088
|
+
sessionId: incoming.sessionId
|
|
1089
|
+
};
|
|
1090
|
+
}
|
|
1091
|
+
function orderSessions(sessions, order = "desc") {
|
|
1092
|
+
const sorted = [...sessions].sort((left, right) => {
|
|
1093
|
+
if (left.lastSeen !== right.lastSeen) return left.lastSeen - right.lastSeen;
|
|
1094
|
+
return left.sessionId < right.sessionId ? -1 : left.sessionId > right.sessionId ? 1 : 0;
|
|
1095
|
+
});
|
|
1096
|
+
if (order === "asc") return sorted;
|
|
1097
|
+
const reversed = [];
|
|
1098
|
+
for (let index = sorted.length - 1; index >= 0; index -= 1) reversed.push(sorted[index]);
|
|
1099
|
+
return reversed;
|
|
1100
|
+
}
|
|
973
1101
|
function orderRangeForQuery(options) {
|
|
974
1102
|
if (typeof IDBKeyRange === "undefined") return void 0;
|
|
1103
|
+
if (options.sessionId !== void 0) {
|
|
1104
|
+
const lower = [
|
|
1105
|
+
options.sessionId,
|
|
1106
|
+
options.from ?? Number.MIN_SAFE_INTEGER,
|
|
1107
|
+
Number.MIN_SAFE_INTEGER,
|
|
1108
|
+
""
|
|
1109
|
+
];
|
|
1110
|
+
const upper = [
|
|
1111
|
+
options.sessionId,
|
|
1112
|
+
options.to ?? Number.MAX_SAFE_INTEGER,
|
|
1113
|
+
Number.MAX_SAFE_INTEGER,
|
|
1114
|
+
""
|
|
1115
|
+
];
|
|
1116
|
+
return IDBKeyRange.bound(lower, upper);
|
|
1117
|
+
}
|
|
975
1118
|
const lower = options.from === void 0 ? void 0 : [
|
|
976
1119
|
options.from,
|
|
977
1120
|
Number.MIN_SAFE_INTEGER,
|
|
@@ -986,6 +1129,33 @@ function orderRangeForQuery(options) {
|
|
|
986
1129
|
if (lower) return IDBKeyRange.lowerBound(lower);
|
|
987
1130
|
if (upper) return IDBKeyRange.upperBound(upper);
|
|
988
1131
|
}
|
|
1132
|
+
function orderRangeForSessionId(sessionId) {
|
|
1133
|
+
if (typeof IDBKeyRange === "undefined") return void 0;
|
|
1134
|
+
return IDBKeyRange.bound([
|
|
1135
|
+
sessionId,
|
|
1136
|
+
Number.MIN_SAFE_INTEGER,
|
|
1137
|
+
Number.MIN_SAFE_INTEGER,
|
|
1138
|
+
""
|
|
1139
|
+
], [
|
|
1140
|
+
sessionId,
|
|
1141
|
+
Number.MAX_SAFE_INTEGER,
|
|
1142
|
+
Number.MAX_SAFE_INTEGER,
|
|
1143
|
+
""
|
|
1144
|
+
]);
|
|
1145
|
+
}
|
|
1146
|
+
function migrateSessionSummaries(logStore, sessionStore) {
|
|
1147
|
+
const summaries = /* @__PURE__ */ new Map();
|
|
1148
|
+
const request = logStore.openCursor();
|
|
1149
|
+
request.addEventListener("success", () => {
|
|
1150
|
+
const cursor = request.result;
|
|
1151
|
+
if (!cursor) {
|
|
1152
|
+
for (const summary of summaries.values()) sessionStore.put(summary);
|
|
1153
|
+
return;
|
|
1154
|
+
}
|
|
1155
|
+
addSessionSummary(summaries, cursor.value);
|
|
1156
|
+
cursor.continue();
|
|
1157
|
+
});
|
|
1158
|
+
}
|
|
989
1159
|
function iterateCursor(request, visit) {
|
|
990
1160
|
return new Promise((resolve, reject) => {
|
|
991
1161
|
request.addEventListener("success", () => {
|
|
@@ -1010,6 +1180,7 @@ function iterateCursor(request, visit) {
|
|
|
1010
1180
|
function indexedDbTransport(options = {}) {
|
|
1011
1181
|
const dbName = options.dbName ?? DEFAULT_DB_NAME;
|
|
1012
1182
|
const storeName = options.storeName ?? DEFAULT_STORE_NAME;
|
|
1183
|
+
const sessionStoreName = sessionStoreNameFor(storeName);
|
|
1013
1184
|
const codec = options.codec ?? (0, _loggerjs_core.safeJsonCodec)();
|
|
1014
1185
|
const batchSize = normalizePositiveInteger(options.batchSize, 100);
|
|
1015
1186
|
const flushIntervalMs = normalizePositiveInteger(options.flushIntervalMs, 1e3);
|
|
@@ -1020,12 +1191,18 @@ function indexedDbTransport(options = {}) {
|
|
|
1020
1191
|
const flushOnPageHide = options.flushOnPageHide ?? true;
|
|
1021
1192
|
const durability = options.durability ?? "default";
|
|
1022
1193
|
const rootIndexedDB = options.indexedDB ?? globalThis.indexedDB;
|
|
1194
|
+
const sessionOptions = normalizeSessionOptions(options.session);
|
|
1195
|
+
const spillOptions = normalizeLocalStorageSpillOptions(options.localStorageSpill, dbName, storeName);
|
|
1023
1196
|
const transactionOptions = durability === "default" ? void 0 : { durability };
|
|
1024
1197
|
const buffer = [];
|
|
1025
1198
|
let dbPromise;
|
|
1026
1199
|
let indexedDBFactoryPromise;
|
|
1027
1200
|
let timer;
|
|
1028
1201
|
let flushPromise;
|
|
1202
|
+
let clearPromise;
|
|
1203
|
+
let pendingFlushBatch;
|
|
1204
|
+
let spillDrainPromise;
|
|
1205
|
+
let localStorageSpillClearCandidate;
|
|
1029
1206
|
let lastContext;
|
|
1030
1207
|
let closed = false;
|
|
1031
1208
|
const statsState = {
|
|
@@ -1052,13 +1229,23 @@ function indexedDbTransport(options = {}) {
|
|
|
1052
1229
|
storageBucketFallbacks: 0,
|
|
1053
1230
|
transactionOptionFallbacks: 0,
|
|
1054
1231
|
errors: 0,
|
|
1055
|
-
errorsByOperation: {}
|
|
1232
|
+
errorsByOperation: {},
|
|
1233
|
+
localStorageSpillWrites: 0,
|
|
1234
|
+
localStorageSpillEntries: 0,
|
|
1235
|
+
localStorageSpillDrains: 0,
|
|
1236
|
+
localStorageSpillDrainedEntries: 0,
|
|
1237
|
+
localStorageSpillDropped: 0,
|
|
1238
|
+
localStorageSpillDroppedByReason: {},
|
|
1239
|
+
localStorageSpillErrors: 0,
|
|
1240
|
+
localStorageSpillErrorsByOperation: {}
|
|
1056
1241
|
};
|
|
1057
1242
|
const snapshotStats = () => ({
|
|
1058
1243
|
...statsState,
|
|
1059
1244
|
bufferDepth: buffer.length,
|
|
1060
1245
|
droppedByReason: { ...statsState.droppedByReason },
|
|
1061
1246
|
errorsByOperation: { ...statsState.errorsByOperation },
|
|
1247
|
+
localStorageSpillDroppedByReason: { ...statsState.localStorageSpillDroppedByReason },
|
|
1248
|
+
localStorageSpillErrorsByOperation: { ...statsState.localStorageSpillErrorsByOperation },
|
|
1062
1249
|
pendingFlush: flushPromise !== void 0,
|
|
1063
1250
|
persistedDroppedByReason: { ...statsState.persistedDroppedByReason }
|
|
1064
1251
|
});
|
|
@@ -1085,6 +1272,18 @@ function indexedDbTransport(options = {}) {
|
|
|
1085
1272
|
(0, _loggerjs_core.incrementLoggerMetaCounter)(`transport.indexeddb.persisted.dropped.${reason}`);
|
|
1086
1273
|
options.onPersistedDrop?.(entry, reason);
|
|
1087
1274
|
};
|
|
1275
|
+
const reportSpillError = (error, operation) => {
|
|
1276
|
+
statsState.localStorageSpillErrors += 1;
|
|
1277
|
+
incrementRecord(statsState.localStorageSpillErrorsByOperation, operation);
|
|
1278
|
+
reportInternalError(error, `localstorage-spill-${operation}`);
|
|
1279
|
+
};
|
|
1280
|
+
const dropSpillEvent = (event, reason) => {
|
|
1281
|
+
statsState.localStorageSpillDropped += 1;
|
|
1282
|
+
incrementRecord(statsState.localStorageSpillDroppedByReason, reason);
|
|
1283
|
+
(0, _loggerjs_core.incrementLoggerMetaCounter)("transport.indexeddb.localstorage_spill.dropped");
|
|
1284
|
+
(0, _loggerjs_core.incrementLoggerMetaCounter)(`transport.indexeddb.localstorage_spill.dropped.${reason}`);
|
|
1285
|
+
spillOptions?.onDrop?.(event, reason);
|
|
1286
|
+
};
|
|
1088
1287
|
const resolveIndexedDBFactory = async () => {
|
|
1089
1288
|
if (options.indexedDB || !options.storageBucketName) return rootIndexedDB;
|
|
1090
1289
|
indexedDBFactoryPromise ??= (async () => {
|
|
@@ -1111,7 +1310,7 @@ function indexedDbTransport(options = {}) {
|
|
|
1111
1310
|
if (!idb) throw new Error("IndexedDB is not available for indexedDbTransport");
|
|
1112
1311
|
return new Promise((resolve, reject) => {
|
|
1113
1312
|
const request = idb.open(dbName, DB_VERSION);
|
|
1114
|
-
request.addEventListener("upgradeneeded", () => {
|
|
1313
|
+
request.addEventListener("upgradeneeded", (event) => {
|
|
1115
1314
|
const db = request.result;
|
|
1116
1315
|
const store = db.objectStoreNames.contains(storeName) ? request.transaction?.objectStore(storeName) : db.createObjectStore(storeName, { keyPath: "id" });
|
|
1117
1316
|
if (!store) return;
|
|
@@ -1124,6 +1323,17 @@ function indexedDbTransport(options = {}) {
|
|
|
1124
1323
|
"seq",
|
|
1125
1324
|
"id"
|
|
1126
1325
|
]);
|
|
1326
|
+
ensureIndex(store, SESSION_INDEX_NAME, "sessionId");
|
|
1327
|
+
ensureIndex(store, SESSION_ORDER_INDEX_NAME, [
|
|
1328
|
+
"sessionId",
|
|
1329
|
+
"createdAt",
|
|
1330
|
+
"seq",
|
|
1331
|
+
"id"
|
|
1332
|
+
]);
|
|
1333
|
+
const sessionStore = db.objectStoreNames.contains(sessionStoreName) ? request.transaction?.objectStore(sessionStoreName) : db.createObjectStore(sessionStoreName, { keyPath: "sessionId" });
|
|
1334
|
+
if (!sessionStore) return;
|
|
1335
|
+
ensureIndex(sessionStore, SESSION_LAST_SEEN_INDEX_NAME, ["lastSeen", "sessionId"]);
|
|
1336
|
+
if (event.oldVersion < 4) migrateSessionSummaries(store, sessionStore);
|
|
1127
1337
|
});
|
|
1128
1338
|
request.addEventListener("success", () => {
|
|
1129
1339
|
statsState.databaseOpenCount += 1;
|
|
@@ -1134,13 +1344,13 @@ function indexedDbTransport(options = {}) {
|
|
|
1134
1344
|
});
|
|
1135
1345
|
return dbPromise;
|
|
1136
1346
|
};
|
|
1137
|
-
const createTransaction = (db, mode) => {
|
|
1138
|
-
if (mode !== "readwrite" || !transactionOptions) return db.transaction(
|
|
1347
|
+
const createTransaction = (db, mode, stores = storeName) => {
|
|
1348
|
+
if (mode !== "readwrite" || !transactionOptions) return db.transaction(stores, mode);
|
|
1139
1349
|
try {
|
|
1140
|
-
return db.transaction(
|
|
1350
|
+
return db.transaction(stores, mode, transactionOptions);
|
|
1141
1351
|
} catch {
|
|
1142
1352
|
statsState.transactionOptionFallbacks += 1;
|
|
1143
|
-
return db.transaction(
|
|
1353
|
+
return db.transaction(stores, mode);
|
|
1144
1354
|
}
|
|
1145
1355
|
};
|
|
1146
1356
|
const withStore = async (mode, run) => {
|
|
@@ -1157,6 +1367,57 @@ function indexedDbTransport(options = {}) {
|
|
|
1157
1367
|
throw error;
|
|
1158
1368
|
}
|
|
1159
1369
|
};
|
|
1370
|
+
const withLogAndSessionStores = async (mode, run) => {
|
|
1371
|
+
const tx = createTransaction(await openDb(), mode, [storeName, sessionStoreName]);
|
|
1372
|
+
const settled = transactionToPromise(tx);
|
|
1373
|
+
try {
|
|
1374
|
+
const result = await run(tx.objectStore(storeName), tx.objectStore(sessionStoreName), tx);
|
|
1375
|
+
await settled;
|
|
1376
|
+
return result;
|
|
1377
|
+
} catch (error) {
|
|
1378
|
+
if (tx.error === null) try {
|
|
1379
|
+
tx.abort();
|
|
1380
|
+
} catch {}
|
|
1381
|
+
throw error;
|
|
1382
|
+
}
|
|
1383
|
+
};
|
|
1384
|
+
const withSessionStore = async (mode, run) => {
|
|
1385
|
+
const tx = createTransaction(await openDb(), mode, sessionStoreName);
|
|
1386
|
+
const settled = transactionToPromise(tx);
|
|
1387
|
+
try {
|
|
1388
|
+
const result = await run(tx.objectStore(sessionStoreName), tx);
|
|
1389
|
+
await settled;
|
|
1390
|
+
return result;
|
|
1391
|
+
} catch (error) {
|
|
1392
|
+
if (tx.error === null) try {
|
|
1393
|
+
tx.abort();
|
|
1394
|
+
} catch {}
|
|
1395
|
+
throw error;
|
|
1396
|
+
}
|
|
1397
|
+
};
|
|
1398
|
+
const mergeSessionSummaries = async (sessionStore, entries) => {
|
|
1399
|
+
const summaries = /* @__PURE__ */ new Map();
|
|
1400
|
+
for (const entry of entries) addSessionSummary(summaries, entry);
|
|
1401
|
+
for (const summary of summaries.values()) {
|
|
1402
|
+
const current = await requestToPromise(sessionStore.get(summary.sessionId));
|
|
1403
|
+
await requestToPromise(sessionStore.put(mergeSessionSummary(current, summary)));
|
|
1404
|
+
}
|
|
1405
|
+
};
|
|
1406
|
+
const rebuildSessionSummaries = async (store, sessionStore, sessionIds) => {
|
|
1407
|
+
if (sessionIds.size === 0) return;
|
|
1408
|
+
const index = getStoreIndex(store, SESSION_ORDER_INDEX_NAME);
|
|
1409
|
+
for (const sessionId of sessionIds) {
|
|
1410
|
+
const summaries = /* @__PURE__ */ new Map();
|
|
1411
|
+
const range = orderRangeForSessionId(sessionId);
|
|
1412
|
+
await iterateCursor(index && range ? index.openCursor(range) : store.openCursor(), (cursor) => {
|
|
1413
|
+
const entry = cursor.value;
|
|
1414
|
+
if (entry.sessionId === sessionId) addSessionSummary(summaries, entry);
|
|
1415
|
+
});
|
|
1416
|
+
const summary = summaries.get(sessionId);
|
|
1417
|
+
if (summary) await requestToPromise(sessionStore.put(summary));
|
|
1418
|
+
else await requestToPromise(sessionStore.delete(sessionId));
|
|
1419
|
+
}
|
|
1420
|
+
};
|
|
1160
1421
|
const getEntries = async () => orderEntries(await withStore("readonly", (store) => requestToPromise(store.getAll())));
|
|
1161
1422
|
const countEntries = async () => withStore("readonly", (store) => {
|
|
1162
1423
|
const maybeStore = store;
|
|
@@ -1165,25 +1426,31 @@ function indexedDbTransport(options = {}) {
|
|
|
1165
1426
|
});
|
|
1166
1427
|
const deleteEntries = async (entries, reason) => {
|
|
1167
1428
|
if (entries.length === 0) return;
|
|
1168
|
-
await
|
|
1429
|
+
await withLogAndSessionStores("readwrite", async (store, sessionStore) => {
|
|
1430
|
+
const affectedSessionIds = /* @__PURE__ */ new Set();
|
|
1169
1431
|
for (const entry of entries) {
|
|
1170
1432
|
await requestToPromise(store.delete(entry.id));
|
|
1433
|
+
if (entry.sessionId) affectedSessionIds.add(entry.sessionId);
|
|
1171
1434
|
dropPersistedEntry(entry, reason);
|
|
1172
1435
|
}
|
|
1436
|
+
await rebuildSessionSummaries(store, sessionStore, affectedSessionIds);
|
|
1173
1437
|
});
|
|
1174
1438
|
};
|
|
1175
|
-
const deleteByCursor = async (reason, openCursor, shouldDelete) =>
|
|
1439
|
+
const deleteByCursor = async (reason, openCursor, shouldDelete) => withLogAndSessionStores("readwrite", async (store, sessionStore) => {
|
|
1176
1440
|
const request = openCursor(store);
|
|
1177
1441
|
if (!request) return void 0;
|
|
1178
1442
|
let deleted = 0;
|
|
1443
|
+
const affectedSessionIds = /* @__PURE__ */ new Set();
|
|
1179
1444
|
await iterateCursor(request, (cursor) => {
|
|
1180
1445
|
const entry = cursor.value;
|
|
1181
1446
|
if (!shouldDelete(entry, deleted)) return false;
|
|
1182
1447
|
cursor.delete();
|
|
1183
1448
|
deleted += 1;
|
|
1449
|
+
if (entry.sessionId) affectedSessionIds.add(entry.sessionId);
|
|
1184
1450
|
dropPersistedEntry(entry, reason);
|
|
1185
1451
|
return true;
|
|
1186
1452
|
});
|
|
1453
|
+
await rebuildSessionSummaries(store, sessionStore, affectedSessionIds);
|
|
1187
1454
|
return deleted;
|
|
1188
1455
|
});
|
|
1189
1456
|
const deleteExpiredByCursor = async (cutoff) => {
|
|
@@ -1214,7 +1481,7 @@ function indexedDbTransport(options = {}) {
|
|
|
1214
1481
|
return total;
|
|
1215
1482
|
});
|
|
1216
1483
|
const queryEntriesByCursor = async (queryOptions) => withStore("readonly", async (store) => {
|
|
1217
|
-
const index = getStoreIndex(store, ORDER_INDEX_NAME);
|
|
1484
|
+
const index = getStoreIndex(store, queryOptions.sessionId === void 0 ? ORDER_INDEX_NAME : SESSION_ORDER_INDEX_NAME);
|
|
1218
1485
|
if (!index) return void 0;
|
|
1219
1486
|
const entries = [];
|
|
1220
1487
|
const limit = queryOptions.limit;
|
|
@@ -1227,6 +1494,20 @@ function indexedDbTransport(options = {}) {
|
|
|
1227
1494
|
});
|
|
1228
1495
|
return entries;
|
|
1229
1496
|
});
|
|
1497
|
+
const listSessions = async (sessionQueryOptions = {}) => {
|
|
1498
|
+
const limit = sessionQueryOptions.limit;
|
|
1499
|
+
if (limit !== void 0 && limit <= 0) return [];
|
|
1500
|
+
return withSessionStore("readonly", async (sessionStore) => {
|
|
1501
|
+
const sessions = [];
|
|
1502
|
+
const index = getStoreIndex(sessionStore, SESSION_LAST_SEEN_INDEX_NAME);
|
|
1503
|
+
if (!index) return orderSessions(await requestToPromise(sessionStore.getAll()), sessionQueryOptions.order).slice(0, limit);
|
|
1504
|
+
await iterateCursor(index.openCursor(void 0, sessionQueryOptions.order === "asc" ? "next" : "prev"), (cursor) => {
|
|
1505
|
+
sessions.push(cursor.value);
|
|
1506
|
+
return limit === void 0 || sessions.length < limit;
|
|
1507
|
+
});
|
|
1508
|
+
return sessions;
|
|
1509
|
+
});
|
|
1510
|
+
};
|
|
1230
1511
|
const pruneWithEntries = async () => {
|
|
1231
1512
|
const now = Date.now();
|
|
1232
1513
|
let entries = await getEntries();
|
|
@@ -1282,10 +1563,34 @@ function indexedDbTransport(options = {}) {
|
|
|
1282
1563
|
statsState.lastPruneDurationMs = nowMs$1() - startedAt;
|
|
1283
1564
|
}
|
|
1284
1565
|
};
|
|
1566
|
+
const sessionIdForEvent = (event) => {
|
|
1567
|
+
if (!sessionOptions) return void 0;
|
|
1568
|
+
return normalizeSessionId(sessionOptions.getId?.(event)) ?? normalizeSessionId(event.context?.[sessionOptions.contextKey]) ?? sessionOptions.id;
|
|
1569
|
+
};
|
|
1570
|
+
const eventWithSessionContext = (event, sessionId) => {
|
|
1571
|
+
if (!sessionId || !sessionOptions?.contextKey) return event;
|
|
1572
|
+
if (event.context?.[sessionOptions.contextKey] === sessionId) return event;
|
|
1573
|
+
return {
|
|
1574
|
+
...event,
|
|
1575
|
+
context: {
|
|
1576
|
+
...event.context,
|
|
1577
|
+
[sessionOptions.contextKey]: sessionId
|
|
1578
|
+
}
|
|
1579
|
+
};
|
|
1580
|
+
};
|
|
1581
|
+
const eventWithResolvedSession = (event) => {
|
|
1582
|
+
const sessionId = sessionIdForEvent(event);
|
|
1583
|
+
return {
|
|
1584
|
+
event: eventWithSessionContext(event, sessionId),
|
|
1585
|
+
sessionId
|
|
1586
|
+
};
|
|
1587
|
+
};
|
|
1285
1588
|
const eventToEntry = (event) => {
|
|
1286
|
-
const
|
|
1589
|
+
const resolved = eventWithResolvedSession(event);
|
|
1590
|
+
const payload = codec.encode(resolved.event);
|
|
1287
1591
|
return {
|
|
1288
1592
|
id: event.id,
|
|
1593
|
+
sessionId: resolved.sessionId,
|
|
1289
1594
|
seq: event.seq,
|
|
1290
1595
|
createdAt: event.time,
|
|
1291
1596
|
level: event.level,
|
|
@@ -1296,17 +1601,50 @@ function indexedDbTransport(options = {}) {
|
|
|
1296
1601
|
payload
|
|
1297
1602
|
};
|
|
1298
1603
|
};
|
|
1604
|
+
const clearLocalStorageSpill = (expectedValue) => {
|
|
1605
|
+
if (!spillOptions) return false;
|
|
1606
|
+
try {
|
|
1607
|
+
if (expectedValue !== void 0 && spillOptions.storage.getItem(spillOptions.key) !== expectedValue) return false;
|
|
1608
|
+
spillOptions.storage.removeItem(spillOptions.key);
|
|
1609
|
+
if (expectedValue === void 0 || localStorageSpillClearCandidate?.value === expectedValue) localStorageSpillClearCandidate = void 0;
|
|
1610
|
+
return true;
|
|
1611
|
+
} catch (error) {
|
|
1612
|
+
reportSpillError(error, "clear");
|
|
1613
|
+
return false;
|
|
1614
|
+
}
|
|
1615
|
+
};
|
|
1616
|
+
const rememberLocalStorageSpill = (value, events) => {
|
|
1617
|
+
localStorageSpillClearCandidate = {
|
|
1618
|
+
pendingIds: new Set(events.map((event) => event.id)),
|
|
1619
|
+
value
|
|
1620
|
+
};
|
|
1621
|
+
};
|
|
1622
|
+
const markLocalStorageSpillPersisted = (events) => {
|
|
1623
|
+
const candidate = localStorageSpillClearCandidate;
|
|
1624
|
+
if (!candidate || events.length === 0) return;
|
|
1625
|
+
for (const event of events) candidate.pendingIds.delete(event.id);
|
|
1626
|
+
if (candidate.pendingIds.size === 0) clearLocalStorageSpill(candidate.value);
|
|
1627
|
+
};
|
|
1299
1628
|
const writeBatch = async (events) => {
|
|
1300
1629
|
if (events.length === 0) return;
|
|
1301
1630
|
const startedAt = nowMs$1();
|
|
1302
1631
|
statsState.lastFlushBatchSize = events.length;
|
|
1303
1632
|
const entries = events.map(eventToEntry);
|
|
1304
1633
|
try {
|
|
1305
|
-
await
|
|
1306
|
-
|
|
1634
|
+
await withLogAndSessionStores("readwrite", async (store, sessionStore) => {
|
|
1635
|
+
const rebuildSessionIds = /* @__PURE__ */ new Set();
|
|
1636
|
+
for (const entry of entries) {
|
|
1637
|
+
const existing = await requestToPromise(store.get(entry.id));
|
|
1638
|
+
if (existing?.sessionId) rebuildSessionIds.add(existing.sessionId);
|
|
1639
|
+
if (existing && entry.sessionId) rebuildSessionIds.add(entry.sessionId);
|
|
1640
|
+
await requestToPromise(store.put(entry));
|
|
1641
|
+
}
|
|
1642
|
+
await rebuildSessionSummaries(store, sessionStore, rebuildSessionIds);
|
|
1643
|
+
await mergeSessionSummaries(sessionStore, entries.filter((entry) => !entry.sessionId || !rebuildSessionIds.has(entry.sessionId)));
|
|
1307
1644
|
});
|
|
1308
1645
|
statsState.persisted += entries.length;
|
|
1309
1646
|
(0, _loggerjs_core.incrementLoggerMetaCounter)("transport.indexeddb.persisted", entries.length);
|
|
1647
|
+
markLocalStorageSpillPersisted(events);
|
|
1310
1648
|
await prune();
|
|
1311
1649
|
statsState.flushes += 1;
|
|
1312
1650
|
} catch (error) {
|
|
@@ -1316,6 +1654,124 @@ function indexedDbTransport(options = {}) {
|
|
|
1316
1654
|
statsState.lastFlushDurationMs = nowMs$1() - startedAt;
|
|
1317
1655
|
}
|
|
1318
1656
|
};
|
|
1657
|
+
const stringifySpillPayload = (events) => (0, _loggerjs_core.safeJsonStringify)({
|
|
1658
|
+
createdAt: Date.now(),
|
|
1659
|
+
entries: events,
|
|
1660
|
+
schema: SPILL_SCHEMA
|
|
1661
|
+
}, { maxArrayLength: spillOptions?.maxEntries ?? DEFAULT_SPILL_MAX_ENTRIES });
|
|
1662
|
+
const spillStorageBudget = (currentValue) => {
|
|
1663
|
+
if (!spillOptions) return 0;
|
|
1664
|
+
const ownBytes = currentValue ? storageByteLength(spillOptions.key, currentValue) : 0;
|
|
1665
|
+
const originBytes = estimateLocalStorageBytes(spillOptions.storage);
|
|
1666
|
+
if (originBytes === void 0) return spillOptions.maxBytes;
|
|
1667
|
+
return Math.max(0, Math.min(spillOptions.maxBytes, spillOptions.maxOriginBytes - originBytes + ownBytes));
|
|
1668
|
+
};
|
|
1669
|
+
const pendingEventsForSpill = () => {
|
|
1670
|
+
const events = [...pendingFlushBatch ?? [], ...buffer];
|
|
1671
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1672
|
+
const deduped = [];
|
|
1673
|
+
for (const event of events) {
|
|
1674
|
+
if (seen.has(event.id)) continue;
|
|
1675
|
+
seen.add(event.id);
|
|
1676
|
+
if (spillOptions?.minLevelValue !== void 0 && event.level < spillOptions.minLevelValue) continue;
|
|
1677
|
+
deduped.push(eventWithResolvedSession(event).event);
|
|
1678
|
+
}
|
|
1679
|
+
return deduped;
|
|
1680
|
+
};
|
|
1681
|
+
const trimSpillEventsToBudget = (events, budget) => {
|
|
1682
|
+
if (!spillOptions) return void 0;
|
|
1683
|
+
while (events.length > spillOptions.maxEntries) {
|
|
1684
|
+
const dropped = events.shift();
|
|
1685
|
+
if (dropped) dropSpillEvent(dropped, "max-entries");
|
|
1686
|
+
}
|
|
1687
|
+
while (events.length > 0) {
|
|
1688
|
+
const value = stringifySpillPayload(events);
|
|
1689
|
+
if (storageByteLength(spillOptions.key, value) <= budget) return value;
|
|
1690
|
+
const dropped = events.shift();
|
|
1691
|
+
if (dropped) dropSpillEvent(dropped, "max-bytes");
|
|
1692
|
+
}
|
|
1693
|
+
};
|
|
1694
|
+
const writeLocalStorageSpill = () => {
|
|
1695
|
+
if (!spillOptions) return;
|
|
1696
|
+
const pendingEvents = pendingEventsForSpill();
|
|
1697
|
+
if (pendingEvents.length === 0) return;
|
|
1698
|
+
try {
|
|
1699
|
+
const currentValue = spillOptions.storage.getItem(spillOptions.key);
|
|
1700
|
+
let events = pendingEvents;
|
|
1701
|
+
if (currentValue) {
|
|
1702
|
+
const merged = /* @__PURE__ */ new Map();
|
|
1703
|
+
try {
|
|
1704
|
+
for (const event of parseSpillPayload(currentValue) ?? []) merged.set(event.id, event);
|
|
1705
|
+
} catch {}
|
|
1706
|
+
for (const event of pendingEvents) merged.set(event.id, event);
|
|
1707
|
+
events = [...merged.values()];
|
|
1708
|
+
}
|
|
1709
|
+
const budget = spillStorageBudget(currentValue);
|
|
1710
|
+
const value = trimSpillEventsToBudget(events, budget);
|
|
1711
|
+
if (!value) return;
|
|
1712
|
+
try {
|
|
1713
|
+
spillOptions.storage.setItem(spillOptions.key, value);
|
|
1714
|
+
} catch (error) {
|
|
1715
|
+
if (!isQuotaExceededError(error)) throw error;
|
|
1716
|
+
while (events.length > 0) {
|
|
1717
|
+
const dropped = events.shift();
|
|
1718
|
+
if (dropped) dropSpillEvent(dropped, "quota");
|
|
1719
|
+
const retryValue = trimSpillEventsToBudget(events, budget);
|
|
1720
|
+
if (!retryValue) return;
|
|
1721
|
+
try {
|
|
1722
|
+
spillOptions.storage.setItem(spillOptions.key, retryValue);
|
|
1723
|
+
rememberLocalStorageSpill(retryValue, events);
|
|
1724
|
+
statsState.localStorageSpillWrites += 1;
|
|
1725
|
+
statsState.localStorageSpillEntries += events.length;
|
|
1726
|
+
return;
|
|
1727
|
+
} catch (retryError) {
|
|
1728
|
+
if (!isQuotaExceededError(retryError)) throw retryError;
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
return;
|
|
1732
|
+
}
|
|
1733
|
+
rememberLocalStorageSpill(value, events);
|
|
1734
|
+
statsState.localStorageSpillWrites += 1;
|
|
1735
|
+
statsState.localStorageSpillEntries += events.length;
|
|
1736
|
+
} catch (error) {
|
|
1737
|
+
reportSpillError(error, "write");
|
|
1738
|
+
}
|
|
1739
|
+
};
|
|
1740
|
+
const drainLocalStorageSpill = async () => {
|
|
1741
|
+
if (!spillOptions) return;
|
|
1742
|
+
let raw;
|
|
1743
|
+
try {
|
|
1744
|
+
raw = spillOptions.storage.getItem(spillOptions.key);
|
|
1745
|
+
} catch (error) {
|
|
1746
|
+
reportSpillError(error, "read");
|
|
1747
|
+
return;
|
|
1748
|
+
}
|
|
1749
|
+
if (!raw) return;
|
|
1750
|
+
let events;
|
|
1751
|
+
try {
|
|
1752
|
+
events = parseSpillPayload(raw);
|
|
1753
|
+
} catch (error) {
|
|
1754
|
+
reportSpillError(error, "parse");
|
|
1755
|
+
clearLocalStorageSpill();
|
|
1756
|
+
return;
|
|
1757
|
+
}
|
|
1758
|
+
if (!events || events.length === 0) {
|
|
1759
|
+
clearLocalStorageSpill();
|
|
1760
|
+
return;
|
|
1761
|
+
}
|
|
1762
|
+
await writeBatch(events);
|
|
1763
|
+
clearLocalStorageSpill(raw);
|
|
1764
|
+
statsState.localStorageSpillDrains += 1;
|
|
1765
|
+
statsState.localStorageSpillDrainedEntries += events.length;
|
|
1766
|
+
};
|
|
1767
|
+
const ensureSpillDrained = async () => {
|
|
1768
|
+
const promise = spillDrainPromise;
|
|
1769
|
+
if (!promise) return;
|
|
1770
|
+
await promise;
|
|
1771
|
+
};
|
|
1772
|
+
if (spillOptions?.drainOnCreate) spillDrainPromise = drainLocalStorageSpill().catch((error) => reportSpillError(error, "drain")).finally(() => {
|
|
1773
|
+
spillDrainPromise = void 0;
|
|
1774
|
+
});
|
|
1319
1775
|
const clearTimer = () => {
|
|
1320
1776
|
if (timer) clearTimeout(timer);
|
|
1321
1777
|
timer = void 0;
|
|
@@ -1323,7 +1779,15 @@ function indexedDbTransport(options = {}) {
|
|
|
1323
1779
|
const flushPending = async () => {
|
|
1324
1780
|
if (flushPromise) return flushPromise;
|
|
1325
1781
|
clearTimer();
|
|
1326
|
-
|
|
1782
|
+
const batch = buffer.splice(0, buffer.length);
|
|
1783
|
+
pendingFlushBatch = batch;
|
|
1784
|
+
const clearBarrier = clearPromise;
|
|
1785
|
+
flushPromise = (async () => {
|
|
1786
|
+
await clearBarrier;
|
|
1787
|
+
await ensureSpillDrained();
|
|
1788
|
+
await writeBatch(batch);
|
|
1789
|
+
})().finally(() => {
|
|
1790
|
+
if (pendingFlushBatch === batch) pendingFlushBatch = void 0;
|
|
1327
1791
|
flushPromise = void 0;
|
|
1328
1792
|
if (buffer.length > 0 && !closed) schedule();
|
|
1329
1793
|
});
|
|
@@ -1336,14 +1800,28 @@ function indexedDbTransport(options = {}) {
|
|
|
1336
1800
|
}, flushIntervalMs);
|
|
1337
1801
|
};
|
|
1338
1802
|
const onPageHide = () => {
|
|
1339
|
-
|
|
1803
|
+
if (spillOptions?.spillOnPageHide) writeLocalStorageSpill();
|
|
1804
|
+
if (flushOnPageHide) flushPending().catch((error) => reportInternalError(error, "pagehide-flush"));
|
|
1340
1805
|
};
|
|
1341
1806
|
const onVisibilityChange = () => {
|
|
1342
|
-
if (typeof document !== "undefined" && document.visibilityState === "hidden")
|
|
1807
|
+
if (typeof document !== "undefined" && document.visibilityState === "hidden") {
|
|
1808
|
+
if (spillOptions?.spillOnPageHide) writeLocalStorageSpill();
|
|
1809
|
+
if (flushOnPageHide) flushPending().catch((error) => reportInternalError(error, "visibility-flush"));
|
|
1810
|
+
}
|
|
1811
|
+
};
|
|
1812
|
+
const addVisibilityChangeListener = () => {
|
|
1813
|
+
if (typeof document !== "undefined" && typeof document.addEventListener === "function") {
|
|
1814
|
+
document.addEventListener("visibilitychange", onVisibilityChange);
|
|
1815
|
+
return () => document.removeEventListener("visibilitychange", onVisibilityChange);
|
|
1816
|
+
}
|
|
1817
|
+
if (typeof globalThis.addEventListener !== "function") return void 0;
|
|
1818
|
+
globalThis.addEventListener("visibilitychange", onVisibilityChange);
|
|
1819
|
+
return () => globalThis.removeEventListener?.("visibilitychange", onVisibilityChange);
|
|
1343
1820
|
};
|
|
1344
|
-
|
|
1821
|
+
let removeVisibilityChangeListener;
|
|
1822
|
+
if (flushOnPageHide || spillOptions?.spillOnPageHide) {
|
|
1345
1823
|
globalThis.addEventListener?.("pagehide", onPageHide);
|
|
1346
|
-
|
|
1824
|
+
removeVisibilityChangeListener = addVisibilityChangeListener();
|
|
1347
1825
|
}
|
|
1348
1826
|
return {
|
|
1349
1827
|
name: options.name ?? "indexeddb",
|
|
@@ -1374,16 +1852,36 @@ function indexedDbTransport(options = {}) {
|
|
|
1374
1852
|
return await countEntries() ?? (await getEntries()).length;
|
|
1375
1853
|
},
|
|
1376
1854
|
async clear() {
|
|
1855
|
+
if (clearPromise) return clearPromise;
|
|
1856
|
+
const inFlightFlush = flushPromise;
|
|
1857
|
+
const inFlightDrain = spillDrainPromise;
|
|
1377
1858
|
buffer.length = 0;
|
|
1378
1859
|
clearTimer();
|
|
1379
|
-
|
|
1860
|
+
clearLocalStorageSpill();
|
|
1861
|
+
clearPromise = (async () => {
|
|
1862
|
+
await inFlightFlush;
|
|
1863
|
+
await inFlightDrain;
|
|
1864
|
+
await withLogAndSessionStores("readwrite", async (store, sessionStore) => {
|
|
1865
|
+
await requestToPromise(store.clear());
|
|
1866
|
+
await requestToPromise(sessionStore.clear());
|
|
1867
|
+
});
|
|
1868
|
+
})().finally(() => {
|
|
1869
|
+
clearPromise = void 0;
|
|
1870
|
+
});
|
|
1871
|
+
return clearPromise;
|
|
1380
1872
|
},
|
|
1381
1873
|
async remove(ids) {
|
|
1382
1874
|
await flushPending();
|
|
1383
1875
|
const items = typeof ids === "string" ? [ids] : ids;
|
|
1384
1876
|
if (items.length === 0) return;
|
|
1385
|
-
await
|
|
1386
|
-
|
|
1877
|
+
await withLogAndSessionStores("readwrite", async (store, sessionStore) => {
|
|
1878
|
+
const affectedSessionIds = /* @__PURE__ */ new Set();
|
|
1879
|
+
for (const id of items) {
|
|
1880
|
+
const entry = await requestToPromise(store.get(id));
|
|
1881
|
+
if (entry?.sessionId) affectedSessionIds.add(entry.sessionId);
|
|
1882
|
+
await requestToPromise(store.delete(id));
|
|
1883
|
+
}
|
|
1884
|
+
await rebuildSessionSummaries(store, sessionStore, affectedSessionIds);
|
|
1387
1885
|
});
|
|
1388
1886
|
},
|
|
1389
1887
|
async *query(queryOptions = {}) {
|
|
@@ -1399,6 +1897,10 @@ function indexedDbTransport(options = {}) {
|
|
|
1399
1897
|
if (event) yield event;
|
|
1400
1898
|
}
|
|
1401
1899
|
},
|
|
1900
|
+
async sessions(sessionQueryOptions = {}) {
|
|
1901
|
+
await flushPending();
|
|
1902
|
+
return listSessions(sessionQueryOptions);
|
|
1903
|
+
},
|
|
1402
1904
|
stats() {
|
|
1403
1905
|
return snapshotStats();
|
|
1404
1906
|
},
|
|
@@ -1406,12 +1908,12 @@ function indexedDbTransport(options = {}) {
|
|
|
1406
1908
|
closed = true;
|
|
1407
1909
|
clearTimer();
|
|
1408
1910
|
globalThis.removeEventListener?.("pagehide", onPageHide);
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
promise?.then((db) => db.close());
|
|
1911
|
+
removeVisibilityChangeListener?.();
|
|
1912
|
+
removeVisibilityChangeListener = void 0;
|
|
1913
|
+
return flushPending().finally(() => {
|
|
1914
|
+
const promise = dbPromise;
|
|
1915
|
+
dbPromise = void 0;
|
|
1916
|
+
return promise?.then((db) => db.close());
|
|
1415
1917
|
});
|
|
1416
1918
|
}
|
|
1417
1919
|
};
|
|
@@ -1438,12 +1940,14 @@ function isBrowserOnline(options) {
|
|
|
1438
1940
|
function offlineFirstTransport(remote, options = {}) {
|
|
1439
1941
|
const name = options.name ?? `offline-first(${remote.name ?? "transport"})`;
|
|
1440
1942
|
const replayBatchSize = normalizeBatchSize(options.replayBatchSize);
|
|
1943
|
+
const queueOptions = options.queueOptions;
|
|
1441
1944
|
const queue = options.queue ?? indexedDbTransport({
|
|
1442
1945
|
dbName: "loggerjs-offline",
|
|
1443
1946
|
flushIntervalMs: 0,
|
|
1444
1947
|
name: `${name}:queue`,
|
|
1445
1948
|
storeName: "offline-events",
|
|
1446
|
-
...
|
|
1949
|
+
...queueOptions,
|
|
1950
|
+
session: queueOptions?.session ?? false
|
|
1447
1951
|
});
|
|
1448
1952
|
const reliableRemote = (0, _loggerjs_core.retryTransport)(remote, {
|
|
1449
1953
|
maxRetries: 2,
|
|
@@ -1570,6 +2074,7 @@ const ZIP_MAX_UINT32 = 4294967295;
|
|
|
1570
2074
|
const LOCAL_FILE_HEADER_SIGNATURE = 67324752;
|
|
1571
2075
|
const CENTRAL_DIRECTORY_SIGNATURE = 33639248;
|
|
1572
2076
|
const END_OF_CENTRAL_DIRECTORY_SIGNATURE = 101010256;
|
|
2077
|
+
const DEFAULT_SESSION_CONTEXT_KEY = "sessionId";
|
|
1573
2078
|
let crc32Table;
|
|
1574
2079
|
function getCrc32Table() {
|
|
1575
2080
|
if (crc32Table) return crc32Table;
|
|
@@ -1709,6 +2214,56 @@ function normalizeMaxEvents(value) {
|
|
|
1709
2214
|
if (!Number.isFinite(value)) return void 0;
|
|
1710
2215
|
return Math.max(0, Math.floor(value));
|
|
1711
2216
|
}
|
|
2217
|
+
function normalizeSessionExportOptions(value, logFileName) {
|
|
2218
|
+
if (!value) return void 0;
|
|
2219
|
+
const options = value === true ? {} : value;
|
|
2220
|
+
return {
|
|
2221
|
+
contextKey: options.contextKey ?? DEFAULT_SESSION_CONTEXT_KEY,
|
|
2222
|
+
directory: options.directory ?? "sessions",
|
|
2223
|
+
fallbackSessionId: options.fallbackSessionId ?? "unknown",
|
|
2224
|
+
logFileName: options.logFileName ?? logFileName
|
|
2225
|
+
};
|
|
2226
|
+
}
|
|
2227
|
+
function normalizeRecentExportOptions(value, format) {
|
|
2228
|
+
if (!value) return void 0;
|
|
2229
|
+
const options = value === true ? {} : value;
|
|
2230
|
+
return {
|
|
2231
|
+
logFileName: options.logFileName ?? (format === "json" ? "recent.json" : "recent.ndjson"),
|
|
2232
|
+
maxEvents: normalizeMaxEvents(options.maxEvents) ?? 100
|
|
2233
|
+
};
|
|
2234
|
+
}
|
|
2235
|
+
function logContentForLines(lines, format) {
|
|
2236
|
+
if (format === "json") return `[${lines.join(",")}]`;
|
|
2237
|
+
return lines.length > 0 ? `${lines.join("\n")}\n` : "";
|
|
2238
|
+
}
|
|
2239
|
+
function eventTimeRange(items) {
|
|
2240
|
+
let from;
|
|
2241
|
+
let to;
|
|
2242
|
+
for (const item of items) {
|
|
2243
|
+
from = from === void 0 ? item.event.time : Math.min(from, item.event.time);
|
|
2244
|
+
to = to === void 0 ? item.event.time : Math.max(to, item.event.time);
|
|
2245
|
+
}
|
|
2246
|
+
return from === void 0 || to === void 0 ? void 0 : {
|
|
2247
|
+
from,
|
|
2248
|
+
to
|
|
2249
|
+
};
|
|
2250
|
+
}
|
|
2251
|
+
function sessionIdForEvent(event, options) {
|
|
2252
|
+
const value = event.context?.[options.contextKey];
|
|
2253
|
+
return typeof value === "string" && value.length > 0 ? value : options.fallbackSessionId;
|
|
2254
|
+
}
|
|
2255
|
+
function safeZipPathSegment(value) {
|
|
2256
|
+
const encoded = encodeURIComponent(value).replace(/%/g, "_");
|
|
2257
|
+
if (!encoded || encoded === "." || encoded === "..") return "_";
|
|
2258
|
+
return encoded;
|
|
2259
|
+
}
|
|
2260
|
+
function sortByEventTime(items) {
|
|
2261
|
+
return [...items].sort((left, right) => {
|
|
2262
|
+
if (left.event.time !== right.event.time) return left.event.time - right.event.time;
|
|
2263
|
+
if (left.event.seq !== right.event.seq) return left.event.seq - right.event.seq;
|
|
2264
|
+
return left.event.id < right.event.id ? -1 : left.event.id > right.event.id ? 1 : 0;
|
|
2265
|
+
});
|
|
2266
|
+
}
|
|
1712
2267
|
function createLogZipBlob(files, options = {}) {
|
|
1713
2268
|
const normalizedFiles = createNormalizedZipFiles(files, options);
|
|
1714
2269
|
const parts = [];
|
|
@@ -1731,27 +2286,72 @@ async function exportLogsToZip(source, options = {}) {
|
|
|
1731
2286
|
const createdAt = options.createdAt ?? Date.now();
|
|
1732
2287
|
const logFileName = options.logFileName ?? (format === "json" ? "logs.json" : "logs.ndjson");
|
|
1733
2288
|
const maxEvents = normalizeMaxEvents(options.maxEvents);
|
|
2289
|
+
const sessionExportOptions = normalizeSessionExportOptions(options.groupBySession, logFileName);
|
|
2290
|
+
const recentExportOptions = normalizeRecentExportOptions(options.includeRecent, format);
|
|
1734
2291
|
const serialize = options.serializeEvent ?? ((event) => (0, _loggerjs_core.safeJsonStringify)(event, options.stringify ?? {}));
|
|
1735
|
-
const
|
|
2292
|
+
const exported = [];
|
|
1736
2293
|
let skippedCount = 0;
|
|
1737
|
-
let from;
|
|
1738
|
-
let to;
|
|
1739
2294
|
for await (const event of resolveSource(source, options.query)) {
|
|
1740
|
-
if (maxEvents !== void 0 &&
|
|
2295
|
+
if (maxEvents !== void 0 && exported.length >= maxEvents) break;
|
|
1741
2296
|
const mapped = options.mapEvent ? options.mapEvent(event) : event;
|
|
1742
2297
|
if (!mapped) {
|
|
1743
2298
|
skippedCount += 1;
|
|
1744
2299
|
continue;
|
|
1745
2300
|
}
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
2301
|
+
exported.push({
|
|
2302
|
+
event: mapped,
|
|
2303
|
+
line: serialize(mapped)
|
|
2304
|
+
});
|
|
1749
2305
|
}
|
|
1750
2306
|
const files = [{
|
|
1751
2307
|
name: logFileName,
|
|
1752
|
-
content:
|
|
2308
|
+
content: logContentForLines(exported.map((item) => item.line), format),
|
|
1753
2309
|
lastModified: createdAt
|
|
1754
2310
|
}];
|
|
2311
|
+
const sessionManifests = [];
|
|
2312
|
+
if (sessionExportOptions) {
|
|
2313
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2314
|
+
for (const item of exported) {
|
|
2315
|
+
const sessionId = sessionIdForEvent(item.event, sessionExportOptions);
|
|
2316
|
+
const group = groups.get(sessionId) ?? [];
|
|
2317
|
+
group.push(item);
|
|
2318
|
+
groups.set(sessionId, group);
|
|
2319
|
+
}
|
|
2320
|
+
const usedFileNames = new Set(files.map((file) => normalizeZipFileName(file.name)));
|
|
2321
|
+
const sortedGroups = [...groups.entries()];
|
|
2322
|
+
sortedGroups.sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0);
|
|
2323
|
+
for (const [sessionId, group] of sortedGroups) {
|
|
2324
|
+
let fileName = normalizeZipFileName(`${sessionExportOptions.directory}/${safeZipPathSegment(sessionId)}/${sessionExportOptions.logFileName}`);
|
|
2325
|
+
let suffix = 1;
|
|
2326
|
+
while (usedFileNames.has(fileName)) {
|
|
2327
|
+
fileName = normalizeZipFileName(`${sessionExportOptions.directory}/${safeZipPathSegment(sessionId)}-${suffix}/${sessionExportOptions.logFileName}`);
|
|
2328
|
+
suffix += 1;
|
|
2329
|
+
}
|
|
2330
|
+
usedFileNames.add(fileName);
|
|
2331
|
+
const orderedGroup = sortByEventTime(group);
|
|
2332
|
+
files.push({
|
|
2333
|
+
name: fileName,
|
|
2334
|
+
content: logContentForLines(orderedGroup.map((item) => item.line), format),
|
|
2335
|
+
lastModified: createdAt
|
|
2336
|
+
});
|
|
2337
|
+
sessionManifests.push({
|
|
2338
|
+
logCount: orderedGroup.length,
|
|
2339
|
+
logFileName: fileName,
|
|
2340
|
+
sessionId,
|
|
2341
|
+
timeRange: eventTimeRange(orderedGroup)
|
|
2342
|
+
});
|
|
2343
|
+
}
|
|
2344
|
+
}
|
|
2345
|
+
let recentLogFileName;
|
|
2346
|
+
if (recentExportOptions) {
|
|
2347
|
+
const recentItems = recentExportOptions.maxEvents <= 0 ? [] : sortByEventTime(exported).slice(-recentExportOptions.maxEvents);
|
|
2348
|
+
recentLogFileName = normalizeZipFileName(recentExportOptions.logFileName);
|
|
2349
|
+
files.push({
|
|
2350
|
+
name: recentLogFileName,
|
|
2351
|
+
content: logContentForLines(recentItems.map((item) => item.line), format),
|
|
2352
|
+
lastModified: createdAt
|
|
2353
|
+
});
|
|
2354
|
+
}
|
|
1755
2355
|
if (options.includeManifest ?? true) {
|
|
1756
2356
|
const manifest = {
|
|
1757
2357
|
schema: "loggerjs.log-export.v1",
|
|
@@ -1759,13 +2359,13 @@ async function exportLogsToZip(source, options = {}) {
|
|
|
1759
2359
|
source: options.source,
|
|
1760
2360
|
format,
|
|
1761
2361
|
logFileName: normalizeZipFileName(logFileName),
|
|
1762
|
-
logCount:
|
|
2362
|
+
logCount: exported.length,
|
|
1763
2363
|
skippedCount,
|
|
1764
2364
|
query: options.query,
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
2365
|
+
recentLogFileName,
|
|
2366
|
+
sessionCount: sessionExportOptions ? sessionManifests.length : void 0,
|
|
2367
|
+
sessions: sessionExportOptions ? sessionManifests : void 0,
|
|
2368
|
+
timeRange: eventTimeRange(exported)
|
|
1769
2369
|
};
|
|
1770
2370
|
files.push({
|
|
1771
2371
|
name: options.manifestFileName ?? "manifest.json",
|