unnbound-logger-sdk 1.0.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,134 @@
1
+ /**
2
+ * Type definitions for structured logging library
3
+ */
4
+ /**
5
+ * Available log levels
6
+ */
7
+ export type LogLevel = "info" | "debug" | "error" | "warn";
8
+ /**
9
+ * Available log types
10
+ */
11
+ export type LogType = "general" | "httpRequest" | "httpResponse" | "sftpTransaction" | "dbQueryTransaction";
12
+ /**
13
+ * HTTP methods supported for HTTP logging
14
+ */
15
+ export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'OPTIONS' | 'HEAD';
16
+ export interface SerializableError {
17
+ name: string;
18
+ message: string;
19
+ stack?: string;
20
+ }
21
+ export interface Log<T extends LogType = 'general'> {
22
+ level: LogLevel;
23
+ type: T;
24
+ message: string;
25
+ traceId: string;
26
+ requestId: string;
27
+ error?: SerializableError;
28
+ }
29
+ export interface LogTransaction<T extends LogType> extends Log<T> {
30
+ duration: number;
31
+ }
32
+ export interface HttpRequestLog extends LogTransaction<'httpRequest'> {
33
+ httpRequest: {
34
+ url: string;
35
+ method: string;
36
+ headers: Record<string, string>;
37
+ ip?: string;
38
+ body?: unknown;
39
+ };
40
+ }
41
+ export interface HttpResponseLog extends LogTransaction<'httpResponse'> {
42
+ httpResponse: {
43
+ url: string;
44
+ method: string;
45
+ headers: Record<string, string>;
46
+ ip?: string;
47
+ status: number;
48
+ body?: unknown;
49
+ };
50
+ }
51
+ export interface SftpTransactionLog extends LogTransaction<'sftpTransaction'> {
52
+ sftp: {
53
+ host: string;
54
+ username: string;
55
+ operation: 'upload' | 'download' | 'list' | 'delete' | 'rename' | 'stat';
56
+ path: string;
57
+ status: 'success' | 'failure';
58
+ bytesTransferred?: number;
59
+ filesListed?: number;
60
+ sourcePath?: string;
61
+ };
62
+ }
63
+ export interface DbQueryTransactionLog extends LogTransaction<'dbQueryTransaction'> {
64
+ db: {
65
+ instance: string;
66
+ vendor: 'postgres' | 'mysql' | 'mssql' | 'mongodb';
67
+ query?: string;
68
+ status: 'success' | 'failure';
69
+ rowsReturned?: number;
70
+ rowsAffected?: number;
71
+ };
72
+ }
73
+ /**
74
+ * Configuration options for the logger
75
+ */
76
+ export interface LoggerOptions {
77
+ /** Default log level */
78
+ defaultLevel?: LogLevel;
79
+ /** Optional service name to include in logs */
80
+ serviceName?: string;
81
+ /** Optional environment name to include in logs */
82
+ environment?: string;
83
+ /** Optional trace header key */
84
+ traceHeaderKey?: string;
85
+ /** Routes to ignore in trace middleware (supports glob patterns) */
86
+ ignoreTraceRoutes?: string[];
87
+ /** Routes to ignore in axios trace middleware (supports glob patterns) */
88
+ ignoreAxiosTraceRoutes?: string[];
89
+ }
90
+ /**
91
+ * Options for general logs
92
+ */
93
+ export interface GeneralLogOptions {
94
+ /** Log level override */
95
+ level?: LogLevel;
96
+ /** Custom trace ID */
97
+ traceId?: string;
98
+ /** Custom request ID */
99
+ requestId?: string;
100
+ /** Custom metadata */
101
+ [key: string]: unknown;
102
+ }
103
+ /**
104
+ * Options for HTTP request logs
105
+ */
106
+ export interface HttpRequestLogOptions extends GeneralLogOptions {
107
+ /** Start time of the request for duration calculation */
108
+ startTime?: number;
109
+ }
110
+ /**
111
+ * Options for HTTP response logs
112
+ */
113
+ export interface HttpResponseLogOptions extends HttpRequestLogOptions {
114
+ /** Duration of the request in milliseconds */
115
+ duration?: number;
116
+ }
117
+ /**
118
+ * Options for SFTP transaction logs
119
+ */
120
+ export interface SftpTransactionLogOptions extends GeneralLogOptions {
121
+ /** Start time of the transaction for duration calculation */
122
+ startTime?: number;
123
+ /** Duration of the transaction in milliseconds */
124
+ duration?: number;
125
+ }
126
+ /**
127
+ * Options for database query transaction logs
128
+ */
129
+ export interface DbQueryTransactionLogOptions extends GeneralLogOptions {
130
+ /** Start time of the query for duration calculation */
131
+ startTime?: number;
132
+ /** Duration of the query in milliseconds */
133
+ duration?: number;
134
+ }
package/dist/types.js ADDED
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ /**
3
+ * Type definitions for structured logging library
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,105 @@
1
+ import { LogLevel, LoggerOptions, GeneralLogOptions, HttpRequestLogOptions, HttpResponseLogOptions, SftpTransactionLogOptions, DbQueryTransactionLogOptions } from './types';
2
+ import { Request, Response, NextFunction } from 'express';
3
+ import { InternalAxiosRequestConfig } from 'axios';
4
+ /**
5
+ * UnnboundLogger provides typed, structured logging using Pino
6
+ */
7
+ export declare class UnnboundLogger {
8
+ private logger;
9
+ private defaultLevel;
10
+ private serviceName?;
11
+ private environment?;
12
+ private traceHeaderKey;
13
+ private ignoreTraceRoutes;
14
+ private ignoreAxiosTraceRoutes;
15
+ /**
16
+ * Creates a new UnnboundLogger instance
17
+ * @param options - Configuration options for the logger
18
+ */
19
+ constructor(options?: LoggerOptions);
20
+ /**
21
+ * Checks if a path matches any of the ignore patterns
22
+ * @param path - The path to check
23
+ * @param patterns - Array of glob patterns to match against
24
+ * @returns boolean indicating if the path should be ignored
25
+ */
26
+ private shouldIgnorePath;
27
+ /**
28
+ * Logs a general message
29
+ * @param level - Log level
30
+ * @param message - Log message
31
+ * @param options - Additional logging options
32
+ */
33
+ log(level: LogLevel, message: string | Error | Record<string, unknown>, options?: GeneralLogOptions): void;
34
+ /**
35
+ * Logs an error message
36
+ * @param message - Error message or object
37
+ * @param options - Additional logging options
38
+ */
39
+ error(message: string | Error | Record<string, unknown>, options?: GeneralLogOptions): void;
40
+ /**
41
+ * Logs a warning message
42
+ * @param message - Warning message
43
+ * @param options - Additional logging options
44
+ */
45
+ warn(message: string | Error | Record<string, unknown>, options?: GeneralLogOptions): void;
46
+ /**
47
+ * Logs an info message
48
+ * @param message - Info message
49
+ * @param options - Additional logging options
50
+ */
51
+ info(message: string | Error | Record<string, unknown>, options?: GeneralLogOptions): void;
52
+ /**
53
+ * Logs a debug message
54
+ * @param message - Debug message
55
+ * @param options - Additional logging options
56
+ */
57
+ debug(message: string | Error | Record<string, unknown>, options?: GeneralLogOptions): void;
58
+ /**
59
+ * Logs an HTTP request
60
+ * @param req - Express request object
61
+ * @param options - Additional logging options
62
+ * @returns The request ID for correlating with the response
63
+ */
64
+ httpRequest(req: Request, options?: HttpRequestLogOptions): string;
65
+ /**
66
+ * Logs an HTTP response
67
+ * @param res - Express response object
68
+ * @param req - Express request object
69
+ * @param options - Additional logging options
70
+ */
71
+ httpResponse(res: Response, req: Request, options?: HttpResponseLogOptions): void;
72
+ /**
73
+ * Logs an SFTP transaction
74
+ * @param operation - SFTP operation details
75
+ * @param options - Additional logging options
76
+ */
77
+ sftpTransaction(operation: {
78
+ host: string;
79
+ username: string;
80
+ operation: 'upload' | 'download' | 'list' | 'delete' | 'rename' | 'stat';
81
+ path: string;
82
+ status: 'success' | 'failure';
83
+ bytesTransferred?: number;
84
+ filesListed?: number;
85
+ sourcePath?: string;
86
+ }, options?: SftpTransactionLogOptions): void;
87
+ /**
88
+ * Logs a database query transaction
89
+ * @param query - Database query details
90
+ * @param options - Additional logging options
91
+ */
92
+ dbQueryTransaction(query: {
93
+ instance: string;
94
+ vendor: 'postgres' | 'mysql' | 'mssql' | 'mongodb';
95
+ query?: string;
96
+ status: 'success' | 'failure';
97
+ rowsReturned?: number;
98
+ rowsAffected?: number;
99
+ }, options?: DbQueryTransactionLogOptions): void;
100
+ traceMiddleware: (req: Request, res: Response, next: NextFunction) => void;
101
+ axiosTraceMiddleware: {
102
+ onFulfilled: (config: InternalAxiosRequestConfig) => InternalAxiosRequestConfig;
103
+ onRejected: (error: any) => any;
104
+ };
105
+ }
@@ -0,0 +1,292 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.UnnboundLogger = void 0;
7
+ /**
8
+ * Core logger implementation
9
+ */
10
+ const pino_1 = __importDefault(require("pino"));
11
+ const id_generator_1 = require("./utils/id-generator");
12
+ const logger_utils_1 = require("./utils/logger-utils");
13
+ const uuid_1 = require("uuid");
14
+ const trace_context_1 = require("./utils/trace-context");
15
+ const axios_1 = require("axios");
16
+ /**
17
+ * UnnboundLogger provides typed, structured logging using Pino
18
+ */
19
+ class UnnboundLogger {
20
+ /**
21
+ * Creates a new UnnboundLogger instance
22
+ * @param options - Configuration options for the logger
23
+ */
24
+ constructor(options = {}) {
25
+ // Trace middleware
26
+ this.traceMiddleware = (req, res, next) => {
27
+ // Check if the route should be ignored
28
+ if (this.shouldIgnorePath(req.path, this.ignoreTraceRoutes)) {
29
+ return next();
30
+ }
31
+ const traceId = req.header(this.traceHeaderKey) || (0, uuid_1.v4)();
32
+ res.setHeader(this.traceHeaderKey, traceId);
33
+ trace_context_1.traceContext.run(traceId, () => {
34
+ next();
35
+ });
36
+ };
37
+ // Axios trace middleware
38
+ this.axiosTraceMiddleware = {
39
+ onFulfilled: (config) => {
40
+ // Check if the URL should be ignored
41
+ if (config.url && this.shouldIgnorePath(config.url, this.ignoreAxiosTraceRoutes)) {
42
+ return config;
43
+ }
44
+ const traceId = trace_context_1.traceContext.getTraceId();
45
+ if (traceId) {
46
+ const headers = new axios_1.AxiosHeaders(config.headers);
47
+ headers.set(this.traceHeaderKey, traceId);
48
+ config.headers = headers;
49
+ }
50
+ return config;
51
+ },
52
+ onRejected: (error) => {
53
+ return Promise.reject(error);
54
+ }
55
+ };
56
+ this.defaultLevel = options.defaultLevel || 'info';
57
+ this.serviceName = options.serviceName;
58
+ this.environment = options.environment;
59
+ this.traceHeaderKey = options.traceHeaderKey || 'unnbound-trace-id';
60
+ this.ignoreTraceRoutes = options.ignoreTraceRoutes || [];
61
+ this.ignoreAxiosTraceRoutes = options.ignoreAxiosTraceRoutes || [];
62
+ // Create Pino logger
63
+ this.logger = (0, pino_1.default)({
64
+ level: this.defaultLevel,
65
+ base: {
66
+ ...(this.serviceName && { service: this.serviceName }),
67
+ ...(this.environment && { environment: this.environment }),
68
+ },
69
+ formatters: {
70
+ level: (label) => {
71
+ return { level: label };
72
+ },
73
+ },
74
+ timestamp: () => `,"timestamp":"${new Date().toISOString()}"`,
75
+ });
76
+ }
77
+ /**
78
+ * Checks if a path matches any of the ignore patterns
79
+ * @param path - The path to check
80
+ * @param patterns - Array of glob patterns to match against
81
+ * @returns boolean indicating if the path should be ignored
82
+ */
83
+ shouldIgnorePath(path, patterns) {
84
+ return patterns.some(pattern => {
85
+ // Convert glob pattern to regex
86
+ const regexPattern = pattern
87
+ .replace(/\./g, '\\.') // Escape dots
88
+ .replace(/\*/g, '.*') // Convert * to .*
89
+ .replace(/\?/g, '.'); // Convert ? to .
90
+ const regex = new RegExp(`^${regexPattern}$`);
91
+ return regex.test(path);
92
+ });
93
+ }
94
+ /**
95
+ * Logs a general message
96
+ * @param level - Log level
97
+ * @param message - Log message
98
+ * @param options - Additional logging options
99
+ */
100
+ log(level, message, options = {}) {
101
+ const traceId = options.traceId || trace_context_1.traceContext.getTraceId() || (0, id_generator_1.generateUuid)();
102
+ const requestId = options.requestId || (0, id_generator_1.generateUuid)();
103
+ let logEntry;
104
+ let error;
105
+ if (message instanceof Error) {
106
+ error = {
107
+ name: message.name,
108
+ message: message.message,
109
+ stack: message.stack,
110
+ };
111
+ logEntry = {
112
+ level,
113
+ type: 'general',
114
+ message: message.message,
115
+ traceId,
116
+ requestId,
117
+ error,
118
+ };
119
+ }
120
+ else if (typeof message === 'string') {
121
+ logEntry = {
122
+ level,
123
+ type: 'general',
124
+ message,
125
+ traceId,
126
+ requestId,
127
+ };
128
+ }
129
+ else {
130
+ // If message is an object, stringify it
131
+ logEntry = {
132
+ level,
133
+ type: 'general',
134
+ message: JSON.stringify(message),
135
+ traceId,
136
+ requestId,
137
+ };
138
+ }
139
+ this.logger[level](logEntry);
140
+ }
141
+ /**
142
+ * Logs an error message
143
+ * @param message - Error message or object
144
+ * @param options - Additional logging options
145
+ */
146
+ error(message, options = {}) {
147
+ this.log('error', message, options);
148
+ }
149
+ /**
150
+ * Logs a warning message
151
+ * @param message - Warning message
152
+ * @param options - Additional logging options
153
+ */
154
+ warn(message, options = {}) {
155
+ this.log('warn', message, options);
156
+ }
157
+ /**
158
+ * Logs an info message
159
+ * @param message - Info message
160
+ * @param options - Additional logging options
161
+ */
162
+ info(message, options = {}) {
163
+ this.log('info', message, options);
164
+ }
165
+ /**
166
+ * Logs a debug message
167
+ * @param message - Debug message
168
+ * @param options - Additional logging options
169
+ */
170
+ debug(message, options = {}) {
171
+ this.log('debug', message, options);
172
+ }
173
+ /**
174
+ * Logs an HTTP request
175
+ * @param req - Express request object
176
+ * @param options - Additional logging options
177
+ * @returns The request ID for correlating with the response
178
+ */
179
+ httpRequest(req, options = {}) {
180
+ const traceId = options.traceId || trace_context_1.traceContext.getTraceId() || (0, id_generator_1.generateUuid)();
181
+ const requestId = options.requestId || (0, id_generator_1.generateUuid)();
182
+ const startTime = options.startTime || Date.now();
183
+ // Store request metadata in res.locals for later use
184
+ if (req.res) {
185
+ req.res.locals.requestId = requestId;
186
+ req.res.locals.startTime = startTime;
187
+ req.res.locals.traceId = traceId;
188
+ }
189
+ const logEntry = {
190
+ level: options.level || this.defaultLevel,
191
+ type: 'httpRequest',
192
+ message: `${req.method} ${req.originalUrl || req.url}`,
193
+ traceId,
194
+ requestId,
195
+ duration: 0, // Will be updated in response
196
+ httpRequest: {
197
+ url: req.originalUrl || req.url,
198
+ method: req.method,
199
+ headers: (0, logger_utils_1.filterHeaders)(req.headers),
200
+ ip: req.ip,
201
+ body: req.body,
202
+ },
203
+ };
204
+ this.logger[options.level || this.defaultLevel](logEntry);
205
+ return requestId;
206
+ }
207
+ /**
208
+ * Logs an HTTP response
209
+ * @param res - Express response object
210
+ * @param req - Express request object
211
+ * @param options - Additional logging options
212
+ */
213
+ httpResponse(res, req, options = {}) {
214
+ const requestId = res.locals.requestId || options.requestId || (0, id_generator_1.generateUuid)();
215
+ const startTime = res.locals.startTime || options.startTime || Date.now();
216
+ const traceId = res.locals.traceId || options.traceId || trace_context_1.traceContext.getTraceId() || (0, id_generator_1.generateUuid)();
217
+ const duration = options.duration || (Date.now() - startTime);
218
+ // Determine log level based on status code
219
+ let level = options.level || this.defaultLevel;
220
+ if (!options.level) {
221
+ if (res.statusCode >= 500) {
222
+ level = 'error';
223
+ }
224
+ else if (res.statusCode >= 400) {
225
+ level = 'warn';
226
+ }
227
+ else {
228
+ level = 'info';
229
+ }
230
+ }
231
+ const logEntry = {
232
+ level,
233
+ type: 'httpResponse',
234
+ message: `${req.method} ${req.originalUrl || req.url} - ${res.statusCode}`,
235
+ traceId,
236
+ requestId,
237
+ duration,
238
+ httpResponse: {
239
+ url: req.originalUrl || req.url,
240
+ method: req.method,
241
+ headers: (0, logger_utils_1.filterHeaders)(res.getHeaders()),
242
+ ip: req.ip,
243
+ status: res.statusCode,
244
+ body: res.locals.body,
245
+ },
246
+ };
247
+ this.logger[level](logEntry);
248
+ }
249
+ /**
250
+ * Logs an SFTP transaction
251
+ * @param operation - SFTP operation details
252
+ * @param options - Additional logging options
253
+ */
254
+ sftpTransaction(operation, options = {}) {
255
+ const traceId = options.traceId || trace_context_1.traceContext.getTraceId() || (0, id_generator_1.generateUuid)();
256
+ const requestId = options.requestId || (0, id_generator_1.generateUuid)();
257
+ const duration = options.duration || (options.startTime ? Date.now() - options.startTime : 0);
258
+ const level = operation.status === 'success' ? 'info' : 'error';
259
+ const logEntry = {
260
+ level,
261
+ type: 'sftpTransaction',
262
+ message: `SFTP ${operation.operation} ${operation.status} - ${operation.path}`,
263
+ traceId,
264
+ requestId,
265
+ duration,
266
+ sftp: operation,
267
+ };
268
+ this.logger[level](logEntry);
269
+ }
270
+ /**
271
+ * Logs a database query transaction
272
+ * @param query - Database query details
273
+ * @param options - Additional logging options
274
+ */
275
+ dbQueryTransaction(query, options = {}) {
276
+ const traceId = options.traceId || trace_context_1.traceContext.getTraceId() || (0, id_generator_1.generateUuid)();
277
+ const requestId = options.requestId || (0, id_generator_1.generateUuid)();
278
+ const duration = options.duration || (options.startTime ? Date.now() - options.startTime : 0);
279
+ const level = query.status === 'success' ? 'info' : 'error';
280
+ const logEntry = {
281
+ level,
282
+ type: 'dbQueryTransaction',
283
+ message: `DB Query ${query.status} - ${query.vendor}`,
284
+ traceId,
285
+ requestId,
286
+ duration,
287
+ db: query,
288
+ };
289
+ this.logger[level](logEntry);
290
+ }
291
+ }
292
+ exports.UnnboundLogger = UnnboundLogger;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Generates a new UUID v4
3
+ * @returns A UUID v4 string
4
+ */
5
+ export declare function generateUuid(): string;
6
+ /**
7
+ * Generates the current timestamp in ISO format
8
+ * @returns ISO timestamp string
9
+ */
10
+ export declare function generateTimestamp(): string;
11
+ /**
12
+ * Gets or creates a trace ID for a workflow
13
+ * @param workflowId - The workflow ID to get a trace ID for
14
+ * @returns A trace ID associated with the workflow
15
+ */
16
+ export declare function getTraceId(workflowId: string): string;
17
+ /**
18
+ * Clears a trace ID from the map
19
+ * @param workflowId - The workflow ID to clear
20
+ */
21
+ export declare function clearTraceId(workflowId: string): void;
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateUuid = generateUuid;
4
+ exports.generateTimestamp = generateTimestamp;
5
+ exports.getTraceId = getTraceId;
6
+ exports.clearTraceId = clearTraceId;
7
+ /**
8
+ * Utilities for generating unique identifiers
9
+ */
10
+ const uuid_1 = require("uuid");
11
+ /**
12
+ * Generates a new UUID v4
13
+ * @returns A UUID v4 string
14
+ */
15
+ function generateUuid() {
16
+ return (0, uuid_1.v4)();
17
+ }
18
+ /**
19
+ * Generates the current timestamp in ISO format
20
+ * @returns ISO timestamp string
21
+ */
22
+ function generateTimestamp() {
23
+ return new Date().toISOString();
24
+ }
25
+ /**
26
+ * Stores trace IDs by workflow ID for consistent tracking
27
+ */
28
+ const traceIdMap = new Map();
29
+ /**
30
+ * Gets or creates a trace ID for a workflow
31
+ * @param workflowId - The workflow ID to get a trace ID for
32
+ * @returns A trace ID associated with the workflow
33
+ */
34
+ function getTraceId(workflowId) {
35
+ if (!traceIdMap.has(workflowId)) {
36
+ traceIdMap.set(workflowId, generateUuid());
37
+ }
38
+ return traceIdMap.get(workflowId);
39
+ }
40
+ /**
41
+ * Clears a trace ID from the map
42
+ * @param workflowId - The workflow ID to clear
43
+ */
44
+ function clearTraceId(workflowId) {
45
+ traceIdMap.delete(workflowId);
46
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Utility functions for logging
3
+ */
4
+ /**
5
+ * Filters an object of headers, returning a new object with only the allowed headers.
6
+ * @param headers The original headers object.
7
+ * @returns A new object containing only the headers from the allow-list.
8
+ */
9
+ export declare function filterHeaders(headers: Record<string, any>): Record<string, string>;
10
+ /**
11
+ * Safely parses JSON strings, returning the original data if parsing fails or if it's not a string.
12
+ * @param data The data to potentially parse as JSON.
13
+ * @returns Parsed JSON object or the original data.
14
+ */
15
+ export declare function safeJsonParse(data: any): any;
16
+ /**
17
+ * Normalizes IP addresses by removing IPv6 mapping prefix for IPv4 addresses.
18
+ * @param ip The IP address to normalize.
19
+ * @returns Normalized IP address string.
20
+ */
21
+ export declare function normalizeIp(ip: string | undefined): string | undefined;
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ /**
3
+ * Utility functions for logging
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.filterHeaders = filterHeaders;
7
+ exports.safeJsonParse = safeJsonParse;
8
+ exports.normalizeIp = normalizeIp;
9
+ // --- Header Allow-Listing ---
10
+ const ALLOWED_HEADERS = new Set([
11
+ 'content-type',
12
+ 'accept',
13
+ 'user-agent',
14
+ 'host',
15
+ 'x-forwarded-for',
16
+ 'x-request-id',
17
+ 'content-length',
18
+ 'cache-control',
19
+ ]);
20
+ /**
21
+ * Filters an object of headers, returning a new object with only the allowed headers.
22
+ * @param headers The original headers object.
23
+ * @returns A new object containing only the headers from the allow-list.
24
+ */
25
+ function filterHeaders(headers) {
26
+ const filtered = {};
27
+ for (const key in headers) {
28
+ if (ALLOWED_HEADERS.has(key.toLowerCase())) {
29
+ filtered[key] = String(headers[key]);
30
+ }
31
+ }
32
+ return filtered;
33
+ }
34
+ /**
35
+ * Safely parses JSON strings, returning the original data if parsing fails or if it's not a string.
36
+ * @param data The data to potentially parse as JSON.
37
+ * @returns Parsed JSON object or the original data.
38
+ */
39
+ function safeJsonParse(data) {
40
+ if (typeof data === 'string') {
41
+ try {
42
+ return JSON.parse(data);
43
+ }
44
+ catch {
45
+ return data;
46
+ }
47
+ }
48
+ return data;
49
+ }
50
+ /**
51
+ * Normalizes IP addresses by removing IPv6 mapping prefix for IPv4 addresses.
52
+ * @param ip The IP address to normalize.
53
+ * @returns Normalized IP address string.
54
+ */
55
+ function normalizeIp(ip) {
56
+ if (!ip)
57
+ return ip;
58
+ // Remove IPv4-mapped IPv6 prefix (::ffff:) to get clean IPv4 address
59
+ if (ip.startsWith('::ffff:')) {
60
+ return ip.substring(7);
61
+ }
62
+ return ip;
63
+ }