@mihari/logger-core 0.1.0 → 0.2.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.
@@ -0,0 +1,122 @@
1
+ import { TransportOptions, CompressFn, LogEntry, TransportResponse, BatchOptions, MihariConfig, LogLevel } from '@mihari/logger-types';
2
+ export { BatchOptions, CompressFn, LogEntry, LogLevel, MihariConfig, TransportError, TransportOptions, TransportResponse } from '@mihari/logger-types';
3
+
4
+ declare class HttpTransport {
5
+ private readonly token;
6
+ private readonly endpoint;
7
+ private readonly compression;
8
+ private readonly maxRetries;
9
+ private compressFn;
10
+ constructor(options: TransportOptions);
11
+ /**
12
+ * Sets the compression function. This allows node and browser
13
+ * environments to inject their own gzip implementation.
14
+ */
15
+ setCompressFn(fn: CompressFn): void;
16
+ /**
17
+ * Sends an array of log entries to the ingestion endpoint with
18
+ * retry logic using exponential backoff.
19
+ */
20
+ send(logs: readonly LogEntry[]): Promise<TransportResponse>;
21
+ private doFetch;
22
+ }
23
+
24
+ type FlushCallback = (logs: readonly LogEntry[]) => Promise<void>;
25
+ declare class Batcher {
26
+ private queue;
27
+ private readonly batchSize;
28
+ private readonly flushIntervalMs;
29
+ private readonly maxQueueSize;
30
+ private readonly onFlush;
31
+ private timer;
32
+ constructor(options: BatchOptions, onFlush: FlushCallback);
33
+ /**
34
+ * Adds a log entry to the queue. Triggers a flush if the
35
+ * batch size threshold is reached.
36
+ */
37
+ add(entry: LogEntry): void;
38
+ /**
39
+ * Flushes all queued log entries by calling the onFlush callback.
40
+ * Returns a promise that resolves when the flush is complete.
41
+ */
42
+ flush(): Promise<void>;
43
+ /**
44
+ * Returns the number of entries currently in the queue.
45
+ */
46
+ get size(): number;
47
+ /**
48
+ * Stops the periodic flush timer and flushes remaining entries.
49
+ */
50
+ shutdown(): Promise<void>;
51
+ private startTimer;
52
+ private stopTimer;
53
+ }
54
+
55
+ declare class MihariClient {
56
+ protected readonly config: MihariConfig;
57
+ protected readonly transport: HttpTransport;
58
+ protected readonly batcher: Batcher;
59
+ constructor(config: MihariConfig);
60
+ /**
61
+ * Sets the compression function used by the transport layer.
62
+ */
63
+ setCompressFn(fn: CompressFn): void;
64
+ /**
65
+ * Logs a debug-level message.
66
+ */
67
+ debug(message: string, metadata?: Record<string, unknown>): void;
68
+ /**
69
+ * Logs an info-level message.
70
+ */
71
+ info(message: string, metadata?: Record<string, unknown>): void;
72
+ /**
73
+ * Logs a warn-level message.
74
+ */
75
+ warn(message: string, metadata?: Record<string, unknown>): void;
76
+ /**
77
+ * Logs an error-level message.
78
+ */
79
+ error(message: string, metadata?: Record<string, unknown>): void;
80
+ /**
81
+ * Logs a fatal-level message.
82
+ */
83
+ fatal(message: string, metadata?: Record<string, unknown>): void;
84
+ /**
85
+ * Flushes all pending log entries.
86
+ */
87
+ flush(): Promise<void>;
88
+ /**
89
+ * Shuts down the client, flushing remaining entries.
90
+ */
91
+ shutdown(): Promise<void>;
92
+ /**
93
+ * Creates a log entry and adds it to the batch queue.
94
+ * Subclasses can override getDefaultMetadata() to inject
95
+ * environment-specific fields.
96
+ */
97
+ log(level: LogLevel, message: string, metadata?: Record<string, unknown>): void;
98
+ /**
99
+ * Returns default metadata to include with every log entry.
100
+ * Override in subclasses to add environment-specific fields.
101
+ */
102
+ protected getDefaultMetadata(): Record<string, unknown>;
103
+ }
104
+
105
+ /**
106
+ * Returns the current timestamp in ISO 8601 format.
107
+ */
108
+ declare function isoTimestamp(): string;
109
+ /**
110
+ * Detects whether the current runtime is a browser environment.
111
+ */
112
+ declare function isBrowser(): boolean;
113
+ /**
114
+ * Detects whether the current runtime is Node.js.
115
+ */
116
+ declare function isNode(): boolean;
117
+ /**
118
+ * Waits for the specified number of milliseconds.
119
+ */
120
+ declare function sleep(ms: number): Promise<void>;
121
+
122
+ export { Batcher, type FlushCallback, HttpTransport, MihariClient, isBrowser, isNode, isoTimestamp, sleep };
package/dist/index.d.ts CHANGED
@@ -1,7 +1,122 @@
1
- export { MihariClient } from "./client";
2
- export { HttpTransport } from "./transport";
3
- export { Batcher } from "./batcher";
4
- export type { FlushCallback } from "./batcher";
5
- export { isoTimestamp, isBrowser, isNode, sleep } from "./utils";
6
- export { LogLevel, LogEntry, MihariConfig, TransportOptions, BatchOptions, TransportResponse, TransportError, CompressFn, } from "@mihari/logger-types";
7
- //# sourceMappingURL=index.d.ts.map
1
+ import { TransportOptions, CompressFn, LogEntry, TransportResponse, BatchOptions, MihariConfig, LogLevel } from '@mihari/logger-types';
2
+ export { BatchOptions, CompressFn, LogEntry, LogLevel, MihariConfig, TransportError, TransportOptions, TransportResponse } from '@mihari/logger-types';
3
+
4
+ declare class HttpTransport {
5
+ private readonly token;
6
+ private readonly endpoint;
7
+ private readonly compression;
8
+ private readonly maxRetries;
9
+ private compressFn;
10
+ constructor(options: TransportOptions);
11
+ /**
12
+ * Sets the compression function. This allows node and browser
13
+ * environments to inject their own gzip implementation.
14
+ */
15
+ setCompressFn(fn: CompressFn): void;
16
+ /**
17
+ * Sends an array of log entries to the ingestion endpoint with
18
+ * retry logic using exponential backoff.
19
+ */
20
+ send(logs: readonly LogEntry[]): Promise<TransportResponse>;
21
+ private doFetch;
22
+ }
23
+
24
+ type FlushCallback = (logs: readonly LogEntry[]) => Promise<void>;
25
+ declare class Batcher {
26
+ private queue;
27
+ private readonly batchSize;
28
+ private readonly flushIntervalMs;
29
+ private readonly maxQueueSize;
30
+ private readonly onFlush;
31
+ private timer;
32
+ constructor(options: BatchOptions, onFlush: FlushCallback);
33
+ /**
34
+ * Adds a log entry to the queue. Triggers a flush if the
35
+ * batch size threshold is reached.
36
+ */
37
+ add(entry: LogEntry): void;
38
+ /**
39
+ * Flushes all queued log entries by calling the onFlush callback.
40
+ * Returns a promise that resolves when the flush is complete.
41
+ */
42
+ flush(): Promise<void>;
43
+ /**
44
+ * Returns the number of entries currently in the queue.
45
+ */
46
+ get size(): number;
47
+ /**
48
+ * Stops the periodic flush timer and flushes remaining entries.
49
+ */
50
+ shutdown(): Promise<void>;
51
+ private startTimer;
52
+ private stopTimer;
53
+ }
54
+
55
+ declare class MihariClient {
56
+ protected readonly config: MihariConfig;
57
+ protected readonly transport: HttpTransport;
58
+ protected readonly batcher: Batcher;
59
+ constructor(config: MihariConfig);
60
+ /**
61
+ * Sets the compression function used by the transport layer.
62
+ */
63
+ setCompressFn(fn: CompressFn): void;
64
+ /**
65
+ * Logs a debug-level message.
66
+ */
67
+ debug(message: string, metadata?: Record<string, unknown>): void;
68
+ /**
69
+ * Logs an info-level message.
70
+ */
71
+ info(message: string, metadata?: Record<string, unknown>): void;
72
+ /**
73
+ * Logs a warn-level message.
74
+ */
75
+ warn(message: string, metadata?: Record<string, unknown>): void;
76
+ /**
77
+ * Logs an error-level message.
78
+ */
79
+ error(message: string, metadata?: Record<string, unknown>): void;
80
+ /**
81
+ * Logs a fatal-level message.
82
+ */
83
+ fatal(message: string, metadata?: Record<string, unknown>): void;
84
+ /**
85
+ * Flushes all pending log entries.
86
+ */
87
+ flush(): Promise<void>;
88
+ /**
89
+ * Shuts down the client, flushing remaining entries.
90
+ */
91
+ shutdown(): Promise<void>;
92
+ /**
93
+ * Creates a log entry and adds it to the batch queue.
94
+ * Subclasses can override getDefaultMetadata() to inject
95
+ * environment-specific fields.
96
+ */
97
+ log(level: LogLevel, message: string, metadata?: Record<string, unknown>): void;
98
+ /**
99
+ * Returns default metadata to include with every log entry.
100
+ * Override in subclasses to add environment-specific fields.
101
+ */
102
+ protected getDefaultMetadata(): Record<string, unknown>;
103
+ }
104
+
105
+ /**
106
+ * Returns the current timestamp in ISO 8601 format.
107
+ */
108
+ declare function isoTimestamp(): string;
109
+ /**
110
+ * Detects whether the current runtime is a browser environment.
111
+ */
112
+ declare function isBrowser(): boolean;
113
+ /**
114
+ * Detects whether the current runtime is Node.js.
115
+ */
116
+ declare function isNode(): boolean;
117
+ /**
118
+ * Waits for the specified number of milliseconds.
119
+ */
120
+ declare function sleep(ms: number): Promise<void>;
121
+
122
+ export { Batcher, type FlushCallback, HttpTransport, MihariClient, isBrowser, isNode, isoTimestamp, sleep };
package/dist/index.js CHANGED
@@ -1,17 +1,321 @@
1
1
  "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.LogLevel = exports.sleep = exports.isNode = exports.isBrowser = exports.isoTimestamp = exports.Batcher = exports.HttpTransport = exports.MihariClient = void 0;
4
- var client_1 = require("./client");
5
- Object.defineProperty(exports, "MihariClient", { enumerable: true, get: function () { return client_1.MihariClient; } });
6
- var transport_1 = require("./transport");
7
- Object.defineProperty(exports, "HttpTransport", { enumerable: true, get: function () { return transport_1.HttpTransport; } });
8
- var batcher_1 = require("./batcher");
9
- Object.defineProperty(exports, "Batcher", { enumerable: true, get: function () { return batcher_1.Batcher; } });
10
- var utils_1 = require("./utils");
11
- Object.defineProperty(exports, "isoTimestamp", { enumerable: true, get: function () { return utils_1.isoTimestamp; } });
12
- Object.defineProperty(exports, "isBrowser", { enumerable: true, get: function () { return utils_1.isBrowser; } });
13
- Object.defineProperty(exports, "isNode", { enumerable: true, get: function () { return utils_1.isNode; } });
14
- Object.defineProperty(exports, "sleep", { enumerable: true, get: function () { return utils_1.sleep; } });
15
- var logger_types_1 = require("@mihari/logger-types");
16
- Object.defineProperty(exports, "LogLevel", { enumerable: true, get: function () { return logger_types_1.LogLevel; } });
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ BatchOptions: () => import_logger_types2.BatchOptions,
24
+ Batcher: () => Batcher,
25
+ CompressFn: () => import_logger_types2.CompressFn,
26
+ HttpTransport: () => HttpTransport,
27
+ LogEntry: () => import_logger_types2.LogEntry,
28
+ LogLevel: () => import_logger_types2.LogLevel,
29
+ MihariClient: () => MihariClient,
30
+ MihariConfig: () => import_logger_types2.MihariConfig,
31
+ TransportError: () => import_logger_types2.TransportError,
32
+ TransportOptions: () => import_logger_types2.TransportOptions,
33
+ TransportResponse: () => import_logger_types2.TransportResponse,
34
+ isBrowser: () => isBrowser,
35
+ isNode: () => isNode,
36
+ isoTimestamp: () => isoTimestamp,
37
+ sleep: () => sleep
38
+ });
39
+ module.exports = __toCommonJS(index_exports);
40
+
41
+ // src/client.ts
42
+ var import_logger_types = require("@mihari/logger-types");
43
+
44
+ // src/utils.ts
45
+ function isoTimestamp() {
46
+ return (/* @__PURE__ */ new Date()).toISOString();
47
+ }
48
+ function isBrowser() {
49
+ return typeof globalThis !== "undefined" && typeof globalThis.window !== "undefined" && typeof globalThis.document !== "undefined";
50
+ }
51
+ function isNode() {
52
+ return typeof process !== "undefined" && process.versions != null && process.versions.node != null;
53
+ }
54
+ function sleep(ms) {
55
+ return new Promise((resolve) => setTimeout(resolve, ms));
56
+ }
57
+
58
+ // src/transport.ts
59
+ var DEFAULT_RETRIES = 3;
60
+ var RETRY_BASE_DELAY_MS = 1e3;
61
+ var HttpTransport = class {
62
+ constructor(options) {
63
+ this.compressFn = null;
64
+ this.token = options.token;
65
+ this.endpoint = options.endpoint.replace(/\/+$/, "");
66
+ this.compression = options.compression ?? true;
67
+ this.maxRetries = options.retries ?? DEFAULT_RETRIES;
68
+ }
69
+ /**
70
+ * Sets the compression function. This allows node and browser
71
+ * environments to inject their own gzip implementation.
72
+ */
73
+ setCompressFn(fn) {
74
+ this.compressFn = fn;
75
+ }
76
+ /**
77
+ * Sends an array of log entries to the ingestion endpoint with
78
+ * retry logic using exponential backoff.
79
+ */
80
+ async send(logs) {
81
+ const payload = JSON.stringify(logs);
82
+ let body = payload;
83
+ const headers = {
84
+ "Authorization": `Bearer ${this.token}`,
85
+ "Content-Type": "application/json"
86
+ };
87
+ if (this.compression && this.compressFn) {
88
+ const encoded = new TextEncoder().encode(payload);
89
+ body = await this.compressFn(encoded);
90
+ headers["Content-Encoding"] = "gzip";
91
+ }
92
+ let lastError = null;
93
+ for (let attempt = 0; attempt < this.maxRetries; attempt++) {
94
+ try {
95
+ const response = await this.doFetch(body, headers);
96
+ if (response.status === 202) {
97
+ return response.json;
98
+ }
99
+ if (response.status === 401) {
100
+ throw new Error("Invalid or missing authentication token");
101
+ }
102
+ if (response.status === 400) {
103
+ throw new Error("No valid logs found");
104
+ }
105
+ throw new Error(`Unexpected response status: ${response.status}`);
106
+ } catch (err) {
107
+ lastError = err instanceof Error ? err : new Error(String(err));
108
+ if (lastError.message === "Invalid or missing authentication token" || lastError.message === "No valid logs found") {
109
+ throw lastError;
110
+ }
111
+ if (attempt < this.maxRetries - 1) {
112
+ const delay = RETRY_BASE_DELAY_MS * Math.pow(2, attempt);
113
+ await sleep(delay);
114
+ }
115
+ }
116
+ }
117
+ throw lastError ?? new Error("Transport failed after retries");
118
+ }
119
+ async doFetch(body, headers) {
120
+ const res = await fetch(`${this.endpoint}`, {
121
+ method: "POST",
122
+ headers,
123
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
124
+ body
125
+ });
126
+ const json = await res.json().catch(() => ({}));
127
+ return { status: res.status, json };
128
+ }
129
+ };
130
+
131
+ // src/batcher.ts
132
+ var DEFAULT_BATCH_SIZE = 10;
133
+ var DEFAULT_FLUSH_INTERVAL_MS = 5e3;
134
+ var DEFAULT_MAX_QUEUE_SIZE = 1e3;
135
+ var Batcher = class {
136
+ constructor(options, onFlush) {
137
+ this.queue = [];
138
+ this.timer = null;
139
+ this.batchSize = options.batchSize ?? DEFAULT_BATCH_SIZE;
140
+ this.flushIntervalMs = options.flushInterval ?? DEFAULT_FLUSH_INTERVAL_MS;
141
+ this.maxQueueSize = options.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE;
142
+ this.onFlush = onFlush;
143
+ this.startTimer();
144
+ }
145
+ /**
146
+ * Adds a log entry to the queue. Triggers a flush if the
147
+ * batch size threshold is reached.
148
+ */
149
+ add(entry) {
150
+ if (this.queue.length >= this.maxQueueSize) {
151
+ this.queue.shift();
152
+ }
153
+ this.queue = [...this.queue, entry];
154
+ if (this.queue.length >= this.batchSize) {
155
+ void this.flush();
156
+ }
157
+ }
158
+ /**
159
+ * Flushes all queued log entries by calling the onFlush callback.
160
+ * Returns a promise that resolves when the flush is complete.
161
+ */
162
+ async flush() {
163
+ if (this.queue.length === 0) {
164
+ return;
165
+ }
166
+ const batch = [...this.queue];
167
+ this.queue = [];
168
+ try {
169
+ await this.onFlush(batch);
170
+ } catch (err) {
171
+ const combined = [...batch, ...this.queue];
172
+ this.queue = combined.slice(0, this.maxQueueSize);
173
+ throw err;
174
+ }
175
+ }
176
+ /**
177
+ * Returns the number of entries currently in the queue.
178
+ */
179
+ get size() {
180
+ return this.queue.length;
181
+ }
182
+ /**
183
+ * Stops the periodic flush timer and flushes remaining entries.
184
+ */
185
+ async shutdown() {
186
+ this.stopTimer();
187
+ await this.flush();
188
+ }
189
+ startTimer() {
190
+ if (this.flushIntervalMs > 0) {
191
+ this.timer = setInterval(() => {
192
+ void this.flush();
193
+ }, this.flushIntervalMs);
194
+ if (typeof this.timer === "object" && "unref" in this.timer) {
195
+ this.timer.unref();
196
+ }
197
+ }
198
+ }
199
+ stopTimer() {
200
+ if (this.timer !== null) {
201
+ clearInterval(this.timer);
202
+ this.timer = null;
203
+ }
204
+ }
205
+ };
206
+
207
+ // src/client.ts
208
+ var MihariClient = class {
209
+ constructor(config) {
210
+ this.config = config;
211
+ this.transport = new HttpTransport({
212
+ token: config.token,
213
+ endpoint: config.endpoint,
214
+ compression: config.compression,
215
+ retries: config.retries
216
+ });
217
+ this.batcher = new Batcher(
218
+ {
219
+ batchSize: config.batchSize,
220
+ flushInterval: config.flushInterval,
221
+ maxQueueSize: config.maxQueueSize
222
+ },
223
+ async (logs) => {
224
+ await this.transport.send(logs);
225
+ }
226
+ );
227
+ }
228
+ /**
229
+ * Sets the compression function used by the transport layer.
230
+ */
231
+ setCompressFn(fn) {
232
+ this.transport.setCompressFn(fn);
233
+ }
234
+ /**
235
+ * Logs a debug-level message.
236
+ */
237
+ debug(message, metadata) {
238
+ this.log(import_logger_types.LogLevel.Debug, message, metadata);
239
+ }
240
+ /**
241
+ * Logs an info-level message.
242
+ */
243
+ info(message, metadata) {
244
+ this.log(import_logger_types.LogLevel.Info, message, metadata);
245
+ }
246
+ /**
247
+ * Logs a warn-level message.
248
+ */
249
+ warn(message, metadata) {
250
+ this.log(import_logger_types.LogLevel.Warn, message, metadata);
251
+ }
252
+ /**
253
+ * Logs an error-level message.
254
+ */
255
+ error(message, metadata) {
256
+ this.log(import_logger_types.LogLevel.Error, message, metadata);
257
+ }
258
+ /**
259
+ * Logs a fatal-level message.
260
+ */
261
+ fatal(message, metadata) {
262
+ this.log(import_logger_types.LogLevel.Fatal, message, metadata);
263
+ }
264
+ /**
265
+ * Flushes all pending log entries.
266
+ */
267
+ async flush() {
268
+ await this.batcher.flush();
269
+ }
270
+ /**
271
+ * Shuts down the client, flushing remaining entries.
272
+ */
273
+ async shutdown() {
274
+ await this.batcher.shutdown();
275
+ }
276
+ /**
277
+ * Creates a log entry and adds it to the batch queue.
278
+ * Subclasses can override getDefaultMetadata() to inject
279
+ * environment-specific fields.
280
+ */
281
+ log(level, message, metadata) {
282
+ const entry = {
283
+ dt: isoTimestamp(),
284
+ level,
285
+ message,
286
+ ...this.getDefaultMetadata(),
287
+ ...this.config.metadata ?? {},
288
+ ...metadata ?? {}
289
+ };
290
+ this.batcher.add(entry);
291
+ }
292
+ /**
293
+ * Returns default metadata to include with every log entry.
294
+ * Override in subclasses to add environment-specific fields.
295
+ */
296
+ getDefaultMetadata() {
297
+ return {};
298
+ }
299
+ };
300
+
301
+ // src/index.ts
302
+ var import_logger_types2 = require("@mihari/logger-types");
303
+ // Annotate the CommonJS export names for ESM import in node:
304
+ 0 && (module.exports = {
305
+ BatchOptions,
306
+ Batcher,
307
+ CompressFn,
308
+ HttpTransport,
309
+ LogEntry,
310
+ LogLevel,
311
+ MihariClient,
312
+ MihariConfig,
313
+ TransportError,
314
+ TransportOptions,
315
+ TransportResponse,
316
+ isBrowser,
317
+ isNode,
318
+ isoTimestamp,
319
+ sleep
320
+ });
17
321
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,mCAAwC;AAA/B,sGAAA,YAAY,OAAA;AACrB,yCAA4C;AAAnC,0GAAA,aAAa,OAAA;AACtB,qCAAoC;AAA3B,kGAAA,OAAO,OAAA;AAEhB,iCAAiE;AAAxD,qGAAA,YAAY,OAAA;AAAE,kGAAA,SAAS,OAAA;AAAE,+FAAA,MAAM,OAAA;AAAE,8FAAA,KAAK,OAAA;AAC/C,qDAS8B;AAR5B,wGAAA,QAAQ,OAAA"}
1
+ {"version":3,"sources":["../src/index.ts","../src/client.ts","../src/utils.ts","../src/transport.ts","../src/batcher.ts"],"sourcesContent":["export { MihariClient } from \"./client\";\nexport { HttpTransport } from \"./transport\";\nexport { Batcher } from \"./batcher\";\nexport type { FlushCallback } from \"./batcher\";\nexport { isoTimestamp, isBrowser, isNode, sleep } from \"./utils\";\nexport {\n LogLevel,\n LogEntry,\n MihariConfig,\n TransportOptions,\n BatchOptions,\n TransportResponse,\n TransportError,\n CompressFn,\n} from \"@mihari/logger-types\";\n","import { LogLevel, LogEntry, MihariConfig, CompressFn } from \"@mihari/logger-types\";\nimport { HttpTransport } from \"./transport\";\nimport { Batcher } from \"./batcher\";\nimport { isoTimestamp } from \"./utils\";\n\nexport class MihariClient {\n protected readonly config: MihariConfig;\n protected readonly transport: HttpTransport;\n protected readonly batcher: Batcher;\n\n constructor(config: MihariConfig) {\n this.config = config;\n\n this.transport = new HttpTransport({\n token: config.token,\n endpoint: config.endpoint,\n compression: config.compression,\n retries: config.retries,\n });\n\n this.batcher = new Batcher(\n {\n batchSize: config.batchSize,\n flushInterval: config.flushInterval,\n maxQueueSize: config.maxQueueSize,\n },\n async (logs) => {\n await this.transport.send(logs);\n }\n );\n }\n\n /**\n * Sets the compression function used by the transport layer.\n */\n setCompressFn(fn: CompressFn): void {\n this.transport.setCompressFn(fn);\n }\n\n /**\n * Logs a debug-level message.\n */\n debug(message: string, metadata?: Record<string, unknown>): void {\n this.log(LogLevel.Debug, message, metadata);\n }\n\n /**\n * Logs an info-level message.\n */\n info(message: string, metadata?: Record<string, unknown>): void {\n this.log(LogLevel.Info, message, metadata);\n }\n\n /**\n * Logs a warn-level message.\n */\n warn(message: string, metadata?: Record<string, unknown>): void {\n this.log(LogLevel.Warn, message, metadata);\n }\n\n /**\n * Logs an error-level message.\n */\n error(message: string, metadata?: Record<string, unknown>): void {\n this.log(LogLevel.Error, message, metadata);\n }\n\n /**\n * Logs a fatal-level message.\n */\n fatal(message: string, metadata?: Record<string, unknown>): void {\n this.log(LogLevel.Fatal, message, metadata);\n }\n\n /**\n * Flushes all pending log entries.\n */\n async flush(): Promise<void> {\n await this.batcher.flush();\n }\n\n /**\n * Shuts down the client, flushing remaining entries.\n */\n async shutdown(): Promise<void> {\n await this.batcher.shutdown();\n }\n\n /**\n * Creates a log entry and adds it to the batch queue.\n * Subclasses can override getDefaultMetadata() to inject\n * environment-specific fields.\n */\n public log(\n level: LogLevel,\n message: string,\n metadata?: Record<string, unknown>\n ): void {\n const entry: LogEntry = {\n dt: isoTimestamp(),\n level,\n message,\n ...this.getDefaultMetadata(),\n ...(this.config.metadata ?? {}),\n ...(metadata ?? {}),\n };\n\n this.batcher.add(entry);\n }\n\n /**\n * Returns default metadata to include with every log entry.\n * Override in subclasses to add environment-specific fields.\n */\n protected getDefaultMetadata(): Record<string, unknown> {\n return {};\n }\n}\n","/**\n * Returns the current timestamp in ISO 8601 format.\n */\nexport function isoTimestamp(): string {\n return new Date().toISOString();\n}\n\n/**\n * Detects whether the current runtime is a browser environment.\n */\nexport function isBrowser(): boolean {\n return (\n typeof globalThis !== \"undefined\" &&\n typeof (globalThis as Record<string, unknown>).window !== \"undefined\" &&\n typeof (globalThis as Record<string, unknown>).document !== \"undefined\"\n );\n}\n\n/**\n * Detects whether the current runtime is Node.js.\n */\nexport function isNode(): boolean {\n return (\n typeof process !== \"undefined\" &&\n process.versions != null &&\n process.versions.node != null\n );\n}\n\n/**\n * Waits for the specified number of milliseconds.\n */\nexport function sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","import { LogEntry, TransportOptions, TransportResponse, CompressFn } from \"@mihari/logger-types\";\nimport { sleep } from \"./utils\";\n\nconst DEFAULT_RETRIES = 3;\nconst RETRY_BASE_DELAY_MS = 1000;\n\nexport class HttpTransport {\n private readonly token: string;\n private readonly endpoint: string;\n private readonly compression: boolean;\n private readonly maxRetries: number;\n private compressFn: CompressFn | null = null;\n\n constructor(options: TransportOptions) {\n this.token = options.token;\n this.endpoint = options.endpoint.replace(/\\/+$/, \"\");\n this.compression = options.compression ?? true;\n this.maxRetries = options.retries ?? DEFAULT_RETRIES;\n }\n\n /**\n * Sets the compression function. This allows node and browser\n * environments to inject their own gzip implementation.\n */\n setCompressFn(fn: CompressFn): void {\n this.compressFn = fn;\n }\n\n /**\n * Sends an array of log entries to the ingestion endpoint with\n * retry logic using exponential backoff.\n */\n async send(logs: readonly LogEntry[]): Promise<TransportResponse> {\n const payload = JSON.stringify(logs);\n let body: string | Uint8Array = payload;\n const headers: Record<string, string> = {\n \"Authorization\": `Bearer ${this.token}`,\n \"Content-Type\": \"application/json\",\n };\n\n if (this.compression && this.compressFn) {\n const encoded = new TextEncoder().encode(payload);\n body = await this.compressFn(encoded);\n headers[\"Content-Encoding\"] = \"gzip\";\n }\n\n let lastError: Error | null = null;\n\n for (let attempt = 0; attempt < this.maxRetries; attempt++) {\n try {\n const response = await this.doFetch(body, headers);\n\n if (response.status === 202) {\n return response.json as TransportResponse;\n }\n\n if (response.status === 401) {\n throw new Error(\"Invalid or missing authentication token\");\n }\n\n if (response.status === 400) {\n throw new Error(\"No valid logs found\");\n }\n\n throw new Error(`Unexpected response status: ${response.status}`);\n } catch (err) {\n lastError = err instanceof Error ? err : new Error(String(err));\n\n // Do not retry auth or validation errors\n if (\n lastError.message === \"Invalid or missing authentication token\" ||\n lastError.message === \"No valid logs found\"\n ) {\n throw lastError;\n }\n\n if (attempt < this.maxRetries - 1) {\n const delay = RETRY_BASE_DELAY_MS * Math.pow(2, attempt);\n await sleep(delay);\n }\n }\n }\n\n throw lastError ?? new Error(\"Transport failed after retries\");\n }\n\n private async doFetch(\n body: string | Uint8Array,\n headers: Record<string, string>\n ): Promise<{ status: number; json: unknown }> {\n // Use global fetch (available in Node 18+ and all modern browsers)\n const res = await fetch(`${this.endpoint}`, {\n method: \"POST\",\n headers,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n body: body as any,\n });\n\n const json = await res.json().catch(() => ({}));\n return { status: res.status, json };\n }\n}\n","import { LogEntry, BatchOptions } from \"@mihari/logger-types\";\n\nconst DEFAULT_BATCH_SIZE = 10;\nconst DEFAULT_FLUSH_INTERVAL_MS = 5000;\nconst DEFAULT_MAX_QUEUE_SIZE = 1000;\n\nexport type FlushCallback = (logs: readonly LogEntry[]) => Promise<void>;\n\nexport class Batcher {\n private queue: LogEntry[] = [];\n private readonly batchSize: number;\n private readonly flushIntervalMs: number;\n private readonly maxQueueSize: number;\n private readonly onFlush: FlushCallback;\n private timer: ReturnType<typeof setInterval> | null = null;\n\n constructor(options: BatchOptions, onFlush: FlushCallback) {\n this.batchSize = options.batchSize ?? DEFAULT_BATCH_SIZE;\n this.flushIntervalMs = options.flushInterval ?? DEFAULT_FLUSH_INTERVAL_MS;\n this.maxQueueSize = options.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE;\n this.onFlush = onFlush;\n this.startTimer();\n }\n\n /**\n * Adds a log entry to the queue. Triggers a flush if the\n * batch size threshold is reached.\n */\n add(entry: LogEntry): void {\n if (this.queue.length >= this.maxQueueSize) {\n // Drop the oldest entry when the queue is full\n this.queue.shift();\n }\n\n this.queue = [...this.queue, entry];\n\n if (this.queue.length >= this.batchSize) {\n void this.flush();\n }\n }\n\n /**\n * Flushes all queued log entries by calling the onFlush callback.\n * Returns a promise that resolves when the flush is complete.\n */\n async flush(): Promise<void> {\n if (this.queue.length === 0) {\n return;\n }\n\n const batch = [...this.queue];\n this.queue = [];\n\n try {\n await this.onFlush(batch);\n } catch (err) {\n // Re-add entries to the front of the queue on failure,\n // respecting the max queue size\n const combined = [...batch, ...this.queue];\n this.queue = combined.slice(0, this.maxQueueSize);\n // Rethrow so callers know the flush failed\n throw err;\n }\n }\n\n /**\n * Returns the number of entries currently in the queue.\n */\n get size(): number {\n return this.queue.length;\n }\n\n /**\n * Stops the periodic flush timer and flushes remaining entries.\n */\n async shutdown(): Promise<void> {\n this.stopTimer();\n await this.flush();\n }\n\n private startTimer(): void {\n if (this.flushIntervalMs > 0) {\n this.timer = setInterval(() => {\n void this.flush();\n }, this.flushIntervalMs);\n\n // Allow the Node.js process to exit even if the timer is active\n if (typeof this.timer === \"object\" && \"unref\" in this.timer) {\n this.timer.unref();\n }\n }\n }\n\n private stopTimer(): void {\n if (this.timer !== null) {\n clearInterval(this.timer);\n this.timer = null;\n }\n }\n}\n"],"mappings":"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","names":["import_logger_types"]}