@loggerjs/browser 0.5.0 → 0.5.1

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.
Files changed (103) hide show
  1. package/dist/broadcast-channel-transport.cjs +115 -0
  2. package/dist/broadcast-channel-transport.cjs.map +1 -0
  3. package/dist/broadcast-channel-transport.js +114 -0
  4. package/dist/broadcast-channel-transport.js.map +1 -0
  5. package/dist/console-integration.cjs +98 -0
  6. package/dist/console-integration.cjs.map +1 -0
  7. package/dist/console-integration.js +97 -0
  8. package/dist/console-integration.js.map +1 -0
  9. package/dist/context-propagation-integration.cjs +78 -0
  10. package/dist/context-propagation-integration.cjs.map +1 -0
  11. package/dist/context-propagation-integration.js +77 -0
  12. package/dist/context-propagation-integration.js.map +1 -0
  13. package/dist/error-integration.cjs +128 -0
  14. package/dist/error-integration.cjs.map +1 -0
  15. package/dist/error-integration.js +127 -0
  16. package/dist/error-integration.js.map +1 -0
  17. package/dist/fetch-integration.cjs +120 -0
  18. package/dist/fetch-integration.cjs.map +1 -0
  19. package/dist/fetch-integration.js +119 -0
  20. package/dist/fetch-integration.js.map +1 -0
  21. package/dist/framework-error-integration.cjs +102 -0
  22. package/dist/framework-error-integration.cjs.map +1 -0
  23. package/dist/framework-error-integration.js +101 -0
  24. package/dist/framework-error-integration.js.map +1 -0
  25. package/dist/framework-router-integrations.cjs +88 -0
  26. package/dist/framework-router-integrations.cjs.map +1 -0
  27. package/dist/framework-router-integrations.js +84 -0
  28. package/dist/framework-router-integrations.js.map +1 -0
  29. package/dist/http-transport.cjs +301 -0
  30. package/dist/http-transport.cjs.map +1 -0
  31. package/dist/http-transport.js +299 -0
  32. package/dist/http-transport.js.map +1 -0
  33. package/dist/index.cjs.map +1 -1
  34. package/dist/index.js.map +1 -1
  35. package/dist/indexeddb-offline-queue.cjs +105 -0
  36. package/dist/indexeddb-offline-queue.cjs.map +1 -0
  37. package/dist/indexeddb-offline-queue.js +104 -0
  38. package/dist/indexeddb-offline-queue.js.map +1 -0
  39. package/dist/indexeddb-transport.cjs +531 -0
  40. package/dist/indexeddb-transport.cjs.map +1 -0
  41. package/dist/indexeddb-transport.js +530 -0
  42. package/dist/indexeddb-transport.js.map +1 -0
  43. package/dist/offline-first-transport.cjs +675 -0
  44. package/dist/offline-first-transport.cjs.map +1 -0
  45. package/dist/offline-first-transport.js +674 -0
  46. package/dist/offline-first-transport.js.map +1 -0
  47. package/dist/page-lifecycle.cjs +39 -0
  48. package/dist/page-lifecycle.cjs.map +1 -0
  49. package/dist/page-lifecycle.js +38 -0
  50. package/dist/page-lifecycle.js.map +1 -0
  51. package/dist/payload-transforms.cjs +30 -0
  52. package/dist/payload-transforms.cjs.map +1 -0
  53. package/dist/payload-transforms.js +29 -0
  54. package/dist/payload-transforms.js.map +1 -0
  55. package/dist/performance-integration.cjs +135 -0
  56. package/dist/performance-integration.cjs.map +1 -0
  57. package/dist/performance-integration.js +133 -0
  58. package/dist/performance-integration.js.map +1 -0
  59. package/dist/reporting-integration.cjs +110 -0
  60. package/dist/reporting-integration.cjs.map +1 -0
  61. package/dist/reporting-integration.js +109 -0
  62. package/dist/reporting-integration.js.map +1 -0
  63. package/dist/router-integration.cjs +97 -0
  64. package/dist/router-integration.cjs.map +1 -0
  65. package/dist/router-integration.js +96 -0
  66. package/dist/router-integration.js.map +1 -0
  67. package/dist/runtime-host-integration.cjs +181 -0
  68. package/dist/runtime-host-integration.cjs.map +1 -0
  69. package/dist/runtime-host-integration.js +180 -0
  70. package/dist/runtime-host-integration.js.map +1 -0
  71. package/dist/service-worker-integration.cjs +109 -0
  72. package/dist/service-worker-integration.cjs.map +1 -0
  73. package/dist/service-worker-integration.js +108 -0
  74. package/dist/service-worker-integration.js.map +1 -0
  75. package/dist/service-worker-transport.cjs +184 -0
  76. package/dist/service-worker-transport.cjs.map +1 -0
  77. package/dist/service-worker-transport.js +183 -0
  78. package/dist/service-worker-transport.js.map +1 -0
  79. package/dist/user-action-integration.cjs +100 -0
  80. package/dist/user-action-integration.cjs.map +1 -0
  81. package/dist/user-action-integration.js +99 -0
  82. package/dist/user-action-integration.js.map +1 -0
  83. package/dist/web-vitals-integration.cjs +157 -0
  84. package/dist/web-vitals-integration.cjs.map +1 -0
  85. package/dist/web-vitals-integration.js +156 -0
  86. package/dist/web-vitals-integration.js.map +1 -0
  87. package/dist/websocket-integration.cjs +158 -0
  88. package/dist/websocket-integration.cjs.map +1 -0
  89. package/dist/websocket-integration.js +157 -0
  90. package/dist/websocket-integration.js.map +1 -0
  91. package/dist/websocket-transport.cjs +187 -0
  92. package/dist/websocket-transport.cjs.map +1 -0
  93. package/dist/websocket-transport.js +186 -0
  94. package/dist/websocket-transport.js.map +1 -0
  95. package/dist/xhr-integration.cjs +107 -0
  96. package/dist/xhr-integration.cjs.map +1 -0
  97. package/dist/xhr-integration.js +106 -0
  98. package/dist/xhr-integration.js.map +1 -0
  99. package/dist/zip-export.cjs +241 -0
  100. package/dist/zip-export.cjs.map +1 -0
  101. package/dist/zip-export.js +238 -0
  102. package/dist/zip-export.js.map +1 -0
  103. package/package.json +104 -77
@@ -0,0 +1,105 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let _loggerjs_core = require("@loggerjs/core");
3
+ //#region src/indexeddb-offline-queue.ts
4
+ const DEFAULT_DB_NAME = "loggerjs";
5
+ const DEFAULT_STORE_NAME = "browser-http-offline";
6
+ function normalizeMaxEntries(value) {
7
+ if (value === void 0 || !Number.isFinite(value)) return 1e3;
8
+ return Math.max(0, Math.floor(value));
9
+ }
10
+ function requestToPromise(request) {
11
+ return new Promise((resolve, reject) => {
12
+ request.addEventListener("success", () => resolve(request.result), { once: true });
13
+ request.addEventListener("error", () => reject(request.error ?? /* @__PURE__ */ new Error("IndexedDB request failed")), { once: true });
14
+ });
15
+ }
16
+ function compareEntries(a, b) {
17
+ if (a.createdAt !== b.createdAt) return a.createdAt - b.createdAt;
18
+ return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
19
+ }
20
+ function orderEntries(entries) {
21
+ const ordered = [];
22
+ for (const entry of entries) {
23
+ let index = 0;
24
+ while (index < ordered.length && compareEntries(ordered[index], entry) <= 0) index += 1;
25
+ ordered.splice(index, 0, entry);
26
+ }
27
+ return ordered;
28
+ }
29
+ function indexedDbBrowserHttpOfflineQueue(options = {}) {
30
+ const dbName = options.dbName ?? DEFAULT_DB_NAME;
31
+ const storeName = options.storeName ?? DEFAULT_STORE_NAME;
32
+ const maxEntries = normalizeMaxEntries(options.maxEntries);
33
+ const dropPolicy = options.dropPolicy ?? "drop-oldest";
34
+ const idb = options.indexedDB ?? globalThis.indexedDB;
35
+ let dbPromise;
36
+ const drop = (entry, reason) => {
37
+ (0, _loggerjs_core.incrementLoggerMetaCounter)("transport.offline.dropped");
38
+ (0, _loggerjs_core.incrementLoggerMetaCounter)(`transport.offline.dropped.${reason}`);
39
+ options.onDrop?.(entry, reason);
40
+ };
41
+ const openDb = () => {
42
+ if (!idb) throw new Error("IndexedDB is not available for loggerjs offline queue");
43
+ dbPromise ??= new Promise((resolve, reject) => {
44
+ const request = idb.open(dbName, 1);
45
+ request.addEventListener("upgradeneeded", () => {
46
+ const db = request.result;
47
+ if (!db.objectStoreNames.contains(storeName)) db.createObjectStore(storeName, { keyPath: "id" });
48
+ });
49
+ request.addEventListener("success", () => resolve(request.result), { once: true });
50
+ request.addEventListener("error", () => reject(request.error ?? /* @__PURE__ */ new Error("IndexedDB open failed")), { once: true });
51
+ });
52
+ return dbPromise;
53
+ };
54
+ const withStore = async (mode, run) => {
55
+ return run((await openDb()).transaction(storeName, mode).objectStore(storeName));
56
+ };
57
+ const getEntries = async () => orderEntries(await withStore("readonly", (store) => requestToPromise(store.getAll())));
58
+ const putEntry = (entry) => withStore("readwrite", (store) => requestToPromise(store.put(entry)));
59
+ const deleteEntry = (id) => withStore("readwrite", (store) => requestToPromise(store.delete(id)));
60
+ return {
61
+ async enqueue(entry) {
62
+ if (maxEntries === 0) {
63
+ drop(entry, "queue-full");
64
+ return;
65
+ }
66
+ const entries = await getEntries();
67
+ if (entries.length >= maxEntries) {
68
+ if (dropPolicy === "drop-newest") {
69
+ drop(entry, "queue-full");
70
+ return;
71
+ }
72
+ const dropCount = entries.length - maxEntries + 1;
73
+ for (let index = 0; index < dropCount; index += 1) {
74
+ const dropped = entries[index];
75
+ if (!dropped) continue;
76
+ await deleteEntry(dropped.id);
77
+ drop(dropped, "queue-full");
78
+ }
79
+ }
80
+ await putEntry(entry);
81
+ },
82
+ async replay(send) {
83
+ const entries = await getEntries();
84
+ for (const entry of entries) {
85
+ await send(entry);
86
+ await deleteEntry(entry.id);
87
+ }
88
+ },
89
+ async size() {
90
+ return (await getEntries()).length;
91
+ },
92
+ clear() {
93
+ return withStore("readwrite", (store) => requestToPromise(store.clear()));
94
+ },
95
+ close() {
96
+ const promise = dbPromise;
97
+ dbPromise = void 0;
98
+ promise?.then((db) => db.close());
99
+ }
100
+ };
101
+ }
102
+ //#endregion
103
+ exports.indexedDbBrowserHttpOfflineQueue = indexedDbBrowserHttpOfflineQueue;
104
+
105
+ //# sourceMappingURL=indexeddb-offline-queue.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"indexeddb-offline-queue.cjs","names":[],"sources":["../src/indexeddb-offline-queue.ts"],"sourcesContent":["import { incrementLoggerMetaCounter } from \"@loggerjs/core\";\nimport type {\n BrowserHttpDropPolicy,\n BrowserHttpOfflineEntry,\n BrowserHttpOfflineQueue,\n} from \"./http-transport\";\n\nexport interface IndexedDbBrowserHttpOfflineQueueOptions {\n dbName?: string;\n storeName?: string;\n maxEntries?: number;\n dropPolicy?: BrowserHttpDropPolicy;\n indexedDB?: IDBFactory;\n onDrop?: (entry: BrowserHttpOfflineEntry, reason: string) => void;\n}\n\nexport interface IndexedDbBrowserHttpOfflineQueue extends BrowserHttpOfflineQueue {\n size: () => Promise<number>;\n clear: () => Promise<void>;\n close: () => void;\n}\n\nconst DEFAULT_DB_NAME = \"loggerjs\";\nconst DEFAULT_STORE_NAME = \"browser-http-offline\";\n\nfunction normalizeMaxEntries(value: number | undefined): number {\n if (value === undefined || !Number.isFinite(value)) return 1000;\n return Math.max(0, Math.floor(value));\n}\n\nfunction requestToPromise<T>(request: IDBRequest<T>): Promise<T> {\n return new Promise((resolve, reject) => {\n request.addEventListener(\"success\", () => resolve(request.result), { once: true });\n request.addEventListener(\n \"error\",\n () => reject(request.error ?? new Error(\"IndexedDB request failed\")),\n { once: true },\n );\n });\n}\n\nfunction compareEntries(a: BrowserHttpOfflineEntry, b: BrowserHttpOfflineEntry): number {\n if (a.createdAt !== b.createdAt) return a.createdAt - b.createdAt;\n return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;\n}\n\nfunction orderEntries(entries: BrowserHttpOfflineEntry[]): BrowserHttpOfflineEntry[] {\n const ordered: BrowserHttpOfflineEntry[] = [];\n for (const entry of entries) {\n let index = 0;\n while (\n index < ordered.length &&\n compareEntries(ordered[index] as BrowserHttpOfflineEntry, entry) <= 0\n ) {\n index += 1;\n }\n ordered.splice(index, 0, entry);\n }\n return ordered;\n}\n\nexport function indexedDbBrowserHttpOfflineQueue(\n options: IndexedDbBrowserHttpOfflineQueueOptions = {},\n): IndexedDbBrowserHttpOfflineQueue {\n const dbName = options.dbName ?? DEFAULT_DB_NAME;\n const storeName = options.storeName ?? DEFAULT_STORE_NAME;\n const maxEntries = normalizeMaxEntries(options.maxEntries);\n const dropPolicy = options.dropPolicy ?? \"drop-oldest\";\n const idb = options.indexedDB ?? globalThis.indexedDB;\n let dbPromise: Promise<IDBDatabase> | undefined;\n\n const drop = (entry: BrowserHttpOfflineEntry, reason: string) => {\n incrementLoggerMetaCounter(\"transport.offline.dropped\");\n incrementLoggerMetaCounter(`transport.offline.dropped.${reason}`);\n options.onDrop?.(entry, reason);\n };\n\n const openDb = () => {\n if (!idb) throw new Error(\"IndexedDB is not available for loggerjs offline queue\");\n dbPromise ??= new Promise<IDBDatabase>((resolve, reject) => {\n const request = idb.open(dbName, 1);\n request.addEventListener(\"upgradeneeded\", () => {\n const db = request.result;\n if (!db.objectStoreNames.contains(storeName)) {\n db.createObjectStore(storeName, { keyPath: \"id\" });\n }\n });\n request.addEventListener(\"success\", () => resolve(request.result), { once: true });\n request.addEventListener(\n \"error\",\n () => reject(request.error ?? new Error(\"IndexedDB open failed\")),\n { once: true },\n );\n });\n return dbPromise;\n };\n\n const withStore = async <T>(\n mode: IDBTransactionMode,\n run: (store: IDBObjectStore) => Promise<T>,\n ): Promise<T> => {\n const db = await openDb();\n const tx = db.transaction(storeName, mode);\n return run(tx.objectStore(storeName));\n };\n\n const getEntries = async () =>\n orderEntries(\n await withStore(\"readonly\", (store) =>\n requestToPromise(store.getAll() as IDBRequest<BrowserHttpOfflineEntry[]>),\n ),\n );\n\n const putEntry = (entry: BrowserHttpOfflineEntry) =>\n withStore(\"readwrite\", (store) => requestToPromise(store.put(entry)));\n\n const deleteEntry = (id: string) =>\n withStore(\"readwrite\", (store) => requestToPromise(store.delete(id)));\n\n const queue: IndexedDbBrowserHttpOfflineQueue = {\n async enqueue(entry) {\n if (maxEntries === 0) {\n drop(entry, \"queue-full\");\n return;\n }\n\n const entries = await getEntries();\n if (entries.length >= maxEntries) {\n if (dropPolicy === \"drop-newest\") {\n drop(entry, \"queue-full\");\n return;\n }\n const dropCount = entries.length - maxEntries + 1;\n for (let index = 0; index < dropCount; index += 1) {\n const dropped = entries[index];\n if (!dropped) continue;\n // oxlint-disable-next-line no-await-in-loop -- Drops must be durable before adding the new entry.\n await deleteEntry(dropped.id);\n drop(dropped, \"queue-full\");\n }\n }\n\n await putEntry(entry);\n },\n async replay(send) {\n const entries = await getEntries();\n for (const entry of entries) {\n // oxlint-disable-next-line no-await-in-loop -- Replay must preserve order and delete only after send succeeds.\n await send(entry);\n // oxlint-disable-next-line no-await-in-loop -- The entry must remain durable until send resolves.\n await deleteEntry(entry.id);\n }\n },\n async size() {\n return (await getEntries()).length;\n },\n clear() {\n return withStore(\"readwrite\", (store) => requestToPromise(store.clear()));\n },\n close() {\n const promise = dbPromise;\n dbPromise = undefined;\n void promise?.then((db) => db.close());\n },\n };\n\n return queue;\n}\n"],"mappings":"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"}
@@ -0,0 +1,104 @@
1
+ import { incrementLoggerMetaCounter } from "@loggerjs/core";
2
+ //#region src/indexeddb-offline-queue.ts
3
+ const DEFAULT_DB_NAME = "loggerjs";
4
+ const DEFAULT_STORE_NAME = "browser-http-offline";
5
+ function normalizeMaxEntries(value) {
6
+ if (value === void 0 || !Number.isFinite(value)) return 1e3;
7
+ return Math.max(0, Math.floor(value));
8
+ }
9
+ function requestToPromise(request) {
10
+ return new Promise((resolve, reject) => {
11
+ request.addEventListener("success", () => resolve(request.result), { once: true });
12
+ request.addEventListener("error", () => reject(request.error ?? /* @__PURE__ */ new Error("IndexedDB request failed")), { once: true });
13
+ });
14
+ }
15
+ function compareEntries(a, b) {
16
+ if (a.createdAt !== b.createdAt) return a.createdAt - b.createdAt;
17
+ return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
18
+ }
19
+ function orderEntries(entries) {
20
+ const ordered = [];
21
+ for (const entry of entries) {
22
+ let index = 0;
23
+ while (index < ordered.length && compareEntries(ordered[index], entry) <= 0) index += 1;
24
+ ordered.splice(index, 0, entry);
25
+ }
26
+ return ordered;
27
+ }
28
+ function indexedDbBrowserHttpOfflineQueue(options = {}) {
29
+ const dbName = options.dbName ?? DEFAULT_DB_NAME;
30
+ const storeName = options.storeName ?? DEFAULT_STORE_NAME;
31
+ const maxEntries = normalizeMaxEntries(options.maxEntries);
32
+ const dropPolicy = options.dropPolicy ?? "drop-oldest";
33
+ const idb = options.indexedDB ?? globalThis.indexedDB;
34
+ let dbPromise;
35
+ const drop = (entry, reason) => {
36
+ incrementLoggerMetaCounter("transport.offline.dropped");
37
+ incrementLoggerMetaCounter(`transport.offline.dropped.${reason}`);
38
+ options.onDrop?.(entry, reason);
39
+ };
40
+ const openDb = () => {
41
+ if (!idb) throw new Error("IndexedDB is not available for loggerjs offline queue");
42
+ dbPromise ??= new Promise((resolve, reject) => {
43
+ const request = idb.open(dbName, 1);
44
+ request.addEventListener("upgradeneeded", () => {
45
+ const db = request.result;
46
+ if (!db.objectStoreNames.contains(storeName)) db.createObjectStore(storeName, { keyPath: "id" });
47
+ });
48
+ request.addEventListener("success", () => resolve(request.result), { once: true });
49
+ request.addEventListener("error", () => reject(request.error ?? /* @__PURE__ */ new Error("IndexedDB open failed")), { once: true });
50
+ });
51
+ return dbPromise;
52
+ };
53
+ const withStore = async (mode, run) => {
54
+ return run((await openDb()).transaction(storeName, mode).objectStore(storeName));
55
+ };
56
+ const getEntries = async () => orderEntries(await withStore("readonly", (store) => requestToPromise(store.getAll())));
57
+ const putEntry = (entry) => withStore("readwrite", (store) => requestToPromise(store.put(entry)));
58
+ const deleteEntry = (id) => withStore("readwrite", (store) => requestToPromise(store.delete(id)));
59
+ return {
60
+ async enqueue(entry) {
61
+ if (maxEntries === 0) {
62
+ drop(entry, "queue-full");
63
+ return;
64
+ }
65
+ const entries = await getEntries();
66
+ if (entries.length >= maxEntries) {
67
+ if (dropPolicy === "drop-newest") {
68
+ drop(entry, "queue-full");
69
+ return;
70
+ }
71
+ const dropCount = entries.length - maxEntries + 1;
72
+ for (let index = 0; index < dropCount; index += 1) {
73
+ const dropped = entries[index];
74
+ if (!dropped) continue;
75
+ await deleteEntry(dropped.id);
76
+ drop(dropped, "queue-full");
77
+ }
78
+ }
79
+ await putEntry(entry);
80
+ },
81
+ async replay(send) {
82
+ const entries = await getEntries();
83
+ for (const entry of entries) {
84
+ await send(entry);
85
+ await deleteEntry(entry.id);
86
+ }
87
+ },
88
+ async size() {
89
+ return (await getEntries()).length;
90
+ },
91
+ clear() {
92
+ return withStore("readwrite", (store) => requestToPromise(store.clear()));
93
+ },
94
+ close() {
95
+ const promise = dbPromise;
96
+ dbPromise = void 0;
97
+ promise?.then((db) => db.close());
98
+ }
99
+ };
100
+ }
101
+ //#endregion
102
+ export { indexedDbBrowserHttpOfflineQueue };
103
+
104
+ //# sourceMappingURL=indexeddb-offline-queue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"indexeddb-offline-queue.js","names":[],"sources":["../src/indexeddb-offline-queue.ts"],"sourcesContent":["import { incrementLoggerMetaCounter } from \"@loggerjs/core\";\nimport type {\n BrowserHttpDropPolicy,\n BrowserHttpOfflineEntry,\n BrowserHttpOfflineQueue,\n} from \"./http-transport\";\n\nexport interface IndexedDbBrowserHttpOfflineQueueOptions {\n dbName?: string;\n storeName?: string;\n maxEntries?: number;\n dropPolicy?: BrowserHttpDropPolicy;\n indexedDB?: IDBFactory;\n onDrop?: (entry: BrowserHttpOfflineEntry, reason: string) => void;\n}\n\nexport interface IndexedDbBrowserHttpOfflineQueue extends BrowserHttpOfflineQueue {\n size: () => Promise<number>;\n clear: () => Promise<void>;\n close: () => void;\n}\n\nconst DEFAULT_DB_NAME = \"loggerjs\";\nconst DEFAULT_STORE_NAME = \"browser-http-offline\";\n\nfunction normalizeMaxEntries(value: number | undefined): number {\n if (value === undefined || !Number.isFinite(value)) return 1000;\n return Math.max(0, Math.floor(value));\n}\n\nfunction requestToPromise<T>(request: IDBRequest<T>): Promise<T> {\n return new Promise((resolve, reject) => {\n request.addEventListener(\"success\", () => resolve(request.result), { once: true });\n request.addEventListener(\n \"error\",\n () => reject(request.error ?? new Error(\"IndexedDB request failed\")),\n { once: true },\n );\n });\n}\n\nfunction compareEntries(a: BrowserHttpOfflineEntry, b: BrowserHttpOfflineEntry): number {\n if (a.createdAt !== b.createdAt) return a.createdAt - b.createdAt;\n return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;\n}\n\nfunction orderEntries(entries: BrowserHttpOfflineEntry[]): BrowserHttpOfflineEntry[] {\n const ordered: BrowserHttpOfflineEntry[] = [];\n for (const entry of entries) {\n let index = 0;\n while (\n index < ordered.length &&\n compareEntries(ordered[index] as BrowserHttpOfflineEntry, entry) <= 0\n ) {\n index += 1;\n }\n ordered.splice(index, 0, entry);\n }\n return ordered;\n}\n\nexport function indexedDbBrowserHttpOfflineQueue(\n options: IndexedDbBrowserHttpOfflineQueueOptions = {},\n): IndexedDbBrowserHttpOfflineQueue {\n const dbName = options.dbName ?? DEFAULT_DB_NAME;\n const storeName = options.storeName ?? DEFAULT_STORE_NAME;\n const maxEntries = normalizeMaxEntries(options.maxEntries);\n const dropPolicy = options.dropPolicy ?? \"drop-oldest\";\n const idb = options.indexedDB ?? globalThis.indexedDB;\n let dbPromise: Promise<IDBDatabase> | undefined;\n\n const drop = (entry: BrowserHttpOfflineEntry, reason: string) => {\n incrementLoggerMetaCounter(\"transport.offline.dropped\");\n incrementLoggerMetaCounter(`transport.offline.dropped.${reason}`);\n options.onDrop?.(entry, reason);\n };\n\n const openDb = () => {\n if (!idb) throw new Error(\"IndexedDB is not available for loggerjs offline queue\");\n dbPromise ??= new Promise<IDBDatabase>((resolve, reject) => {\n const request = idb.open(dbName, 1);\n request.addEventListener(\"upgradeneeded\", () => {\n const db = request.result;\n if (!db.objectStoreNames.contains(storeName)) {\n db.createObjectStore(storeName, { keyPath: \"id\" });\n }\n });\n request.addEventListener(\"success\", () => resolve(request.result), { once: true });\n request.addEventListener(\n \"error\",\n () => reject(request.error ?? new Error(\"IndexedDB open failed\")),\n { once: true },\n );\n });\n return dbPromise;\n };\n\n const withStore = async <T>(\n mode: IDBTransactionMode,\n run: (store: IDBObjectStore) => Promise<T>,\n ): Promise<T> => {\n const db = await openDb();\n const tx = db.transaction(storeName, mode);\n return run(tx.objectStore(storeName));\n };\n\n const getEntries = async () =>\n orderEntries(\n await withStore(\"readonly\", (store) =>\n requestToPromise(store.getAll() as IDBRequest<BrowserHttpOfflineEntry[]>),\n ),\n );\n\n const putEntry = (entry: BrowserHttpOfflineEntry) =>\n withStore(\"readwrite\", (store) => requestToPromise(store.put(entry)));\n\n const deleteEntry = (id: string) =>\n withStore(\"readwrite\", (store) => requestToPromise(store.delete(id)));\n\n const queue: IndexedDbBrowserHttpOfflineQueue = {\n async enqueue(entry) {\n if (maxEntries === 0) {\n drop(entry, \"queue-full\");\n return;\n }\n\n const entries = await getEntries();\n if (entries.length >= maxEntries) {\n if (dropPolicy === \"drop-newest\") {\n drop(entry, \"queue-full\");\n return;\n }\n const dropCount = entries.length - maxEntries + 1;\n for (let index = 0; index < dropCount; index += 1) {\n const dropped = entries[index];\n if (!dropped) continue;\n // oxlint-disable-next-line no-await-in-loop -- Drops must be durable before adding the new entry.\n await deleteEntry(dropped.id);\n drop(dropped, \"queue-full\");\n }\n }\n\n await putEntry(entry);\n },\n async replay(send) {\n const entries = await getEntries();\n for (const entry of entries) {\n // oxlint-disable-next-line no-await-in-loop -- Replay must preserve order and delete only after send succeeds.\n await send(entry);\n // oxlint-disable-next-line no-await-in-loop -- The entry must remain durable until send resolves.\n await deleteEntry(entry.id);\n }\n },\n async size() {\n return (await getEntries()).length;\n },\n clear() {\n return withStore(\"readwrite\", (store) => requestToPromise(store.clear()));\n },\n close() {\n const promise = dbPromise;\n dbPromise = undefined;\n void promise?.then((db) => db.close());\n },\n };\n\n return queue;\n}\n"],"mappings":";;AAsBA,MAAM,kBAAkB;AACxB,MAAM,qBAAqB;AAE3B,SAAS,oBAAoB,OAAmC;CAC9D,IAAI,UAAU,KAAA,KAAa,CAAC,OAAO,SAAS,KAAK,GAAG,OAAO;CAC3D,OAAO,KAAK,IAAI,GAAG,KAAK,MAAM,KAAK,CAAC;AACtC;AAEA,SAAS,iBAAoB,SAAoC;CAC/D,OAAO,IAAI,SAAS,SAAS,WAAW;EACtC,QAAQ,iBAAiB,iBAAiB,QAAQ,QAAQ,MAAM,GAAG,EAAE,MAAM,KAAK,CAAC;EACjF,QAAQ,iBACN,eACM,OAAO,QAAQ,yBAAS,IAAI,MAAM,0BAA0B,CAAC,GACnE,EAAE,MAAM,KAAK,CACf;CACF,CAAC;AACH;AAEA,SAAS,eAAe,GAA4B,GAAoC;CACtF,IAAI,EAAE,cAAc,EAAE,WAAW,OAAO,EAAE,YAAY,EAAE;CACxD,OAAO,EAAE,KAAK,EAAE,KAAK,KAAK,EAAE,KAAK,EAAE,KAAK,IAAI;AAC9C;AAEA,SAAS,aAAa,SAA+D;CACnF,MAAM,UAAqC,CAAC;CAC5C,KAAK,MAAM,SAAS,SAAS;EAC3B,IAAI,QAAQ;EACZ,OACE,QAAQ,QAAQ,UAChB,eAAe,QAAQ,QAAmC,KAAK,KAAK,GAEpE,SAAS;EAEX,QAAQ,OAAO,OAAO,GAAG,KAAK;CAChC;CACA,OAAO;AACT;AAEA,SAAgB,iCACd,UAAmD,CAAC,GAClB;CAClC,MAAM,SAAS,QAAQ,UAAU;CACjC,MAAM,YAAY,QAAQ,aAAa;CACvC,MAAM,aAAa,oBAAoB,QAAQ,UAAU;CACzD,MAAM,aAAa,QAAQ,cAAc;CACzC,MAAM,MAAM,QAAQ,aAAa,WAAW;CAC5C,IAAI;CAEJ,MAAM,QAAQ,OAAgC,WAAmB;EAC/D,2BAA2B,2BAA2B;EACtD,2BAA2B,6BAA6B,QAAQ;EAChE,QAAQ,SAAS,OAAO,MAAM;CAChC;CAEA,MAAM,eAAe;EACnB,IAAI,CAAC,KAAK,MAAM,IAAI,MAAM,uDAAuD;EACjF,cAAc,IAAI,SAAsB,SAAS,WAAW;GAC1D,MAAM,UAAU,IAAI,KAAK,QAAQ,CAAC;GAClC,QAAQ,iBAAiB,uBAAuB;IAC9C,MAAM,KAAK,QAAQ;IACnB,IAAI,CAAC,GAAG,iBAAiB,SAAS,SAAS,GACzC,GAAG,kBAAkB,WAAW,EAAE,SAAS,KAAK,CAAC;GAErD,CAAC;GACD,QAAQ,iBAAiB,iBAAiB,QAAQ,QAAQ,MAAM,GAAG,EAAE,MAAM,KAAK,CAAC;GACjF,QAAQ,iBACN,eACM,OAAO,QAAQ,yBAAS,IAAI,MAAM,uBAAuB,CAAC,GAChE,EAAE,MAAM,KAAK,CACf;EACF,CAAC;EACD,OAAO;CACT;CAEA,MAAM,YAAY,OAChB,MACA,QACe;EAGf,OAAO,KADI,MADM,OAAO,EAAA,CACV,YAAY,WAAW,IACzB,CAAC,CAAC,YAAY,SAAS,CAAC;CACtC;CAEA,MAAM,aAAa,YACjB,aACE,MAAM,UAAU,aAAa,UAC3B,iBAAiB,MAAM,OAAO,CAA0C,CAC1E,CACF;CAEF,MAAM,YAAY,UAChB,UAAU,cAAc,UAAU,iBAAiB,MAAM,IAAI,KAAK,CAAC,CAAC;CAEtE,MAAM,eAAe,OACnB,UAAU,cAAc,UAAU,iBAAiB,MAAM,OAAO,EAAE,CAAC,CAAC;CAiDtE,OAAO;EA9CL,MAAM,QAAQ,OAAO;GACnB,IAAI,eAAe,GAAG;IACpB,KAAK,OAAO,YAAY;IACxB;GACF;GAEA,MAAM,UAAU,MAAM,WAAW;GACjC,IAAI,QAAQ,UAAU,YAAY;IAChC,IAAI,eAAe,eAAe;KAChC,KAAK,OAAO,YAAY;KACxB;IACF;IACA,MAAM,YAAY,QAAQ,SAAS,aAAa;IAChD,KAAK,IAAI,QAAQ,GAAG,QAAQ,WAAW,SAAS,GAAG;KACjD,MAAM,UAAU,QAAQ;KACxB,IAAI,CAAC,SAAS;KAEd,MAAM,YAAY,QAAQ,EAAE;KAC5B,KAAK,SAAS,YAAY;IAC5B;GACF;GAEA,MAAM,SAAS,KAAK;EACtB;EACA,MAAM,OAAO,MAAM;GACjB,MAAM,UAAU,MAAM,WAAW;GACjC,KAAK,MAAM,SAAS,SAAS;IAE3B,MAAM,KAAK,KAAK;IAEhB,MAAM,YAAY,MAAM,EAAE;GAC5B;EACF;EACA,MAAM,OAAO;GACX,QAAQ,MAAM,WAAW,EAAA,CAAG;EAC9B;EACA,QAAQ;GACN,OAAO,UAAU,cAAc,UAAU,iBAAiB,MAAM,MAAM,CAAC,CAAC;EAC1E;EACA,QAAQ;GACN,MAAM,UAAU;GAChB,YAAY,KAAA;GACZ,SAAc,MAAM,OAAO,GAAG,MAAM,CAAC;EACvC;CAGS;AACb"}