@loglayer/transport-http 1.0.7 → 1.1.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.
package/dist/index.cjs CHANGED
@@ -1,5 +1,4 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } var _class;// src/HttpTransport.ts
2
- var _transport = require('@loglayer/transport');
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } var _class;// src/errors.ts
3
2
  var HttpTransportError = class extends Error {
4
3
  constructor(message, status, response) {
5
4
  super(message);
@@ -24,6 +23,11 @@ var LogSizeError = class extends Error {
24
23
  this.name = "LogSizeError";
25
24
  }
26
25
  };
26
+
27
+ // src/HttpTransport.ts
28
+ var _transport = require('@loglayer/transport');
29
+
30
+ // src/utils.ts
27
31
  async function compressData(data) {
28
32
  const encoder = new TextEncoder();
29
33
  const dataBytes = encoder.encode(data);
@@ -50,7 +54,7 @@ async function compressData(data) {
50
54
  }
51
55
  throw new Error("Gzip compression not supported in this environment");
52
56
  }
53
- async function sendWithRetry(url, method, headers, payload, maxRetries, retryDelay, respectRateLimit = true) {
57
+ async function sendWithRetry(url, method, headers, payload, maxRetries, retryDelay, respectRateLimit = true, onDebugReqRes, onError) {
54
58
  let lastError;
55
59
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
56
60
  try {
@@ -59,11 +63,43 @@ async function sendWithRetry(url, method, headers, payload, maxRetries, retryDel
59
63
  headers,
60
64
  body: payload
61
65
  });
66
+ if (onDebugReqRes) {
67
+ try {
68
+ const responseBody = await response.clone().text();
69
+ const responseHeaders = {};
70
+ response.headers.forEach((value, key) => {
71
+ responseHeaders[key] = value;
72
+ });
73
+ onDebugReqRes({
74
+ req: {
75
+ url,
76
+ method,
77
+ headers,
78
+ body: payload
79
+ },
80
+ res: {
81
+ status: response.status,
82
+ statusText: response.statusText,
83
+ headers: responseHeaders,
84
+ body: responseBody
85
+ }
86
+ });
87
+ } catch (debugError) {
88
+ if (onError) {
89
+ onError(new Error(`Debug callback error: ${debugError}`));
90
+ }
91
+ }
92
+ }
62
93
  if (response.status === 429 && respectRateLimit) {
63
94
  const retryAfter = response.headers.get("retry-after");
64
95
  const waitTime = retryAfter ? Number.parseInt(retryAfter, 10) * 1e3 : retryDelay;
65
96
  throw new RateLimitError(`Rate limit exceeded. Retry after ${waitTime}ms`, waitTime);
66
97
  }
98
+ if (response.status < 200 || response.status >= 300) {
99
+ if (onError) {
100
+ onError(new Error(`HTTP request failed with status ${response.status}: ${response.statusText}`));
101
+ }
102
+ }
67
103
  if (!response.ok) {
68
104
  throw new HttpTransportError(`HTTP ${response.status}: ${response.statusText}`, response.status, response);
69
105
  }
@@ -83,6 +119,8 @@ async function sendWithRetry(url, method, headers, payload, maxRetries, retryDel
83
119
  }
84
120
  throw lastError;
85
121
  }
122
+
123
+ // src/HttpTransport.ts
86
124
  var HttpTransport = (_class = class extends _transport.LoggerlessTransport {
87
125
 
88
126
 
@@ -101,6 +139,9 @@ var HttpTransport = (_class = class extends _transport.LoggerlessTransport {
101
139
 
102
140
 
103
141
 
142
+
143
+
144
+
104
145
 
105
146
 
106
147
  // Batch management
@@ -123,6 +164,7 @@ var HttpTransport = (_class = class extends _transport.LoggerlessTransport {
123
164
  this.batchContentType = _nullishCoalesce(config.batchContentType, () => ( "application/json"));
124
165
  this.onError = config.onError;
125
166
  this.onDebug = config.onDebug;
167
+ this.onDebugReqRes = config.onDebugReqRes;
126
168
  this.payloadTemplate = config.payloadTemplate;
127
169
  this.compression = _nullishCoalesce(config.compression, () => ( false));
128
170
  this.maxRetries = _nullishCoalesce(config.maxRetries, () => ( 3));
@@ -132,6 +174,11 @@ var HttpTransport = (_class = class extends _transport.LoggerlessTransport {
132
174
  this.batchSize = _nullishCoalesce(config.batchSize, () => ( 100));
133
175
  this.batchSendTimeout = _nullishCoalesce(config.batchSendTimeout, () => ( 5e3));
134
176
  this.batchSendDelimiter = _nullishCoalesce(config.batchSendDelimiter, () => ( "\n"));
177
+ this.batchMode = _nullishCoalesce(config.batchMode, () => ( "delimiter"));
178
+ this.batchFieldName = config.batchFieldName;
179
+ if (this.batchMode === "field" && !this.batchFieldName) {
180
+ throw new Error("batchFieldName is required when batchMode is 'field'");
181
+ }
135
182
  this.maxLogSize = _nullishCoalesce(config.maxLogSize, () => ( 1048576));
136
183
  this.maxPayloadSize = _nullishCoalesce(config.maxPayloadSize, () => ( 5242880));
137
184
  this.enableNextJsEdgeCompat = _nullishCoalesce(config.enableNextJsEdgeCompat, () => ( false));
@@ -241,7 +288,23 @@ var HttpTransport = (_class = class extends _transport.LoggerlessTransport {
241
288
  * Sends a batch of payloads to the HTTP endpoint
242
289
  */
243
290
  async sendBatch(batch) {
244
- const batchPayload = batch.join(this.batchSendDelimiter);
291
+ let batchPayload;
292
+ switch (this.batchMode) {
293
+ case "array": {
294
+ const batchEntries = batch.map((payload) => JSON.parse(payload));
295
+ batchPayload = JSON.stringify(batchEntries);
296
+ break;
297
+ }
298
+ case "field": {
299
+ const fieldEntries = batch.map((payload) => JSON.parse(payload));
300
+ const batchObject = { [this.batchFieldName]: fieldEntries };
301
+ batchPayload = JSON.stringify(batchObject);
302
+ break;
303
+ }
304
+ default:
305
+ batchPayload = batch.join(this.batchSendDelimiter);
306
+ break;
307
+ }
245
308
  await this.sendPayload(batchPayload, this.batchContentType);
246
309
  }
247
310
  /**
@@ -270,11 +333,16 @@ var HttpTransport = (_class = class extends _transport.LoggerlessTransport {
270
333
  finalPayload,
271
334
  this.maxRetries,
272
335
  this.retryDelay,
273
- this.respectRateLimit
336
+ this.respectRateLimit,
337
+ this.onDebugReqRes,
338
+ this.onError
274
339
  );
275
340
  }
276
341
  }, _class);
277
342
 
278
343
 
279
- exports.HttpTransport = HttpTransport;
344
+
345
+
346
+
347
+ exports.HttpTransport = HttpTransport; exports.HttpTransportError = HttpTransportError; exports.LogSizeError = LogSizeError; exports.RateLimitError = RateLimitError;
280
348
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/runner/work/loglayer/loglayer/packages/transports/http/dist/index.cjs","../src/HttpTransport.ts"],"names":[],"mappings":"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","file":"/home/runner/work/loglayer/loglayer/packages/transports/http/dist/index.cjs","sourcesContent":[null,"import type { LoggerlessTransportConfig, LogLayerTransportParams } from \"@loglayer/transport\";\nimport { LoggerlessTransport } from \"@loglayer/transport\";\n\n/**\n * Error thrown when HTTP request fails\n */\nclass HttpTransportError extends Error {\n constructor(\n message: string,\n public status?: number,\n public response?: Response,\n ) {\n super(message);\n this.name = \"HttpTransportError\";\n }\n}\n\n/**\n * Error thrown when rate limit is exceeded\n */\nclass RateLimitError extends Error {\n constructor(\n message: string,\n public retryAfter: number,\n ) {\n super(message);\n this.name = \"RateLimitError\";\n }\n}\n\n/**\n * Error thrown when log entry exceeds size limits\n */\nclass LogSizeError extends Error {\n constructor(\n message: string,\n public logEntry: Record<string, any>,\n public size: number,\n public limit: number,\n ) {\n super(message);\n this.name = \"LogSizeError\";\n }\n}\n\n/**\n * Configuration options for the HTTP transport.\n */\nexport interface HttpTransportConfig extends LoggerlessTransportConfig {\n /**\n * The URL to send logs to\n */\n url: string;\n /**\n * HTTP method to use for requests\n * @default \"POST\"\n */\n method?: string;\n /**\n * Headers to include in the request. Can be an object or a function that returns headers.\n */\n headers?: Record<string, string> | (() => Record<string, string>);\n /**\n * Content type for single log requests. User-specified headers take precedence.\n * @default \"application/json\"\n */\n contentType?: string;\n /**\n * Content type for batch log requests. User-specified headers take precedence.\n * @default \"application/json\"\n */\n batchContentType?: string;\n /**\n * Optional callback for error handling\n */\n onError?: (err: Error) => void;\n /**\n * Optional callback for debugging log entries before they are sent\n */\n onDebug?: (entry: Record<string, any>) => void;\n /**\n * Function to transform log data into the payload format\n */\n payloadTemplate: (data: { logLevel: string; message: string; data?: Record<string, any> }) => string;\n /**\n * Whether to use gzip compression\n * @default false\n */\n compression?: boolean;\n /**\n * Number of retry attempts before giving up\n * @default 3\n */\n maxRetries?: number;\n /**\n * Base delay between retries in milliseconds\n * @default 1000\n */\n retryDelay?: number;\n /**\n * Whether to respect rate limiting by waiting when a 429 response is received\n * @default true\n */\n respectRateLimit?: boolean;\n /**\n * Whether to enable batch sending\n * @default true\n */\n enableBatchSend?: boolean;\n /**\n * Number of log entries to batch before sending\n * @default 100\n */\n batchSize?: number;\n /**\n * Timeout in milliseconds for sending batches regardless of size\n * @default 5000\n */\n batchSendTimeout?: number;\n /**\n * Delimiter to use between log entries in batch mode\n * @default \"\\n\"\n */\n batchSendDelimiter?: string;\n /**\n * Maximum size of a single log entry in bytes\n * @default 1048576 (1MB)\n */\n maxLogSize?: number;\n /**\n * Maximum size of the payload (uncompressed) in bytes\n * @default 5242880 (5MB)\n */\n maxPayloadSize?: number;\n /**\n * Whether to enable Next.js Edge Runtime compatibility mode\n * When enabled, TextEncoder and compression are disabled\n * @default false\n */\n enableNextJsEdgeCompat?: boolean;\n}\n\n/**\n * Compresses data using gzip compression\n */\nasync function compressData(data: string): Promise<Uint8Array> {\n const encoder = new TextEncoder();\n const dataBytes = encoder.encode(data);\n\n // Use the CompressionStream API if available (modern browsers)\n if (typeof CompressionStream !== \"undefined\") {\n const stream = new CompressionStream(\"gzip\");\n const writer = stream.writable.getWriter();\n const reader = stream.readable.getReader();\n\n await writer.write(dataBytes);\n await writer.close();\n\n const chunks: Uint8Array[] = [];\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n chunks.push(value);\n }\n\n const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0);\n const result = new Uint8Array(totalLength);\n let offset = 0;\n for (const chunk of chunks) {\n result.set(chunk, offset);\n offset += chunk.length;\n }\n\n return result;\n }\n\n // Fallback for Node.js or environments without CompressionStream\n // In a real implementation, you might want to use a library like 'zlib' for Node.js\n throw new Error(\"Gzip compression not supported in this environment\");\n}\n\n/**\n * Sends HTTP request with retry logic and rate limiting\n */\nasync function sendWithRetry(\n url: string,\n method: string,\n headers: Record<string, string>,\n payload: string | Uint8Array,\n maxRetries: number,\n retryDelay: number,\n respectRateLimit = true,\n): Promise<Response> {\n let lastError: Error;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n const response = await fetch(url, {\n method,\n headers,\n body: payload,\n });\n\n // Handle rate limiting\n if (response.status === 429 && respectRateLimit) {\n const retryAfter = response.headers.get(\"retry-after\");\n const waitTime = retryAfter ? Number.parseInt(retryAfter, 10) * 1000 : retryDelay;\n\n throw new RateLimitError(`Rate limit exceeded. Retry after ${waitTime}ms`, waitTime);\n }\n\n // Handle other errors\n if (!response.ok) {\n throw new HttpTransportError(`HTTP ${response.status}: ${response.statusText}`, response.status, response);\n }\n\n return response;\n } catch (error) {\n lastError = error as Error;\n\n // If it's a rate limit error and we should respect it, wait and retry\n if (error instanceof RateLimitError && respectRateLimit) {\n await new Promise((resolve) => setTimeout(resolve, error.retryAfter));\n continue;\n }\n\n // If it's the last attempt, throw the error\n if (attempt === maxRetries) {\n throw error;\n }\n\n // Wait before retrying with exponential backoff\n const waitTime = retryDelay * 2 ** attempt;\n await new Promise((resolve) => setTimeout(resolve, waitTime));\n }\n }\n\n throw lastError!;\n}\n\n/**\n * HttpTransport is responsible for sending logs to any HTTP endpoint.\n * It supports batching, compression, retries, and rate limiting.\n *\n * Features:\n * - Configurable HTTP method and headers\n * - Custom payload template function\n * - Gzip compression support\n * - Retry logic with exponential backoff\n * - Rate limiting support\n * - Batch sending with configurable size and timeout\n * - Error and debug callbacks\n * - Log size validation\n * - Payload size tracking for batching\n */\nexport class HttpTransport extends LoggerlessTransport {\n private url: string;\n private method: string;\n private headers: Record<string, string> | (() => Record<string, string>);\n private contentType: string;\n private batchContentType: string;\n private onError?: (err: Error) => void;\n private onDebug?: (entry: Record<string, any>) => void;\n private payloadTemplate: (data: { logLevel: string; message: string; data?: Record<string, any> }) => string;\n private compression: boolean;\n private maxRetries: number;\n private retryDelay: number;\n private respectRateLimit: boolean;\n private enableBatchSend: boolean;\n private batchSize: number;\n private batchSendTimeout: number;\n private batchSendDelimiter: string;\n private maxLogSize: number;\n private maxPayloadSize: number;\n private enableNextJsEdgeCompat: boolean;\n\n // Batch management\n private batchQueue: string[] = [];\n private batchTimeout?: NodeJS.Timeout;\n private isProcessingBatch = false;\n private currentBatchSize = 0; // Track uncompressed size of current batch\n\n /**\n * Creates a new instance of HttpTransport.\n *\n * @param config - Configuration options for the transport\n */\n constructor(config: HttpTransportConfig) {\n super(config);\n\n this.url = config.url;\n this.method = config.method ?? \"POST\";\n this.headers = config.headers ?? {};\n this.contentType = config.contentType ?? \"application/json\";\n this.batchContentType = config.batchContentType ?? \"application/json\";\n this.onError = config.onError;\n this.onDebug = config.onDebug;\n this.payloadTemplate = config.payloadTemplate;\n this.compression = config.compression ?? false;\n this.maxRetries = config.maxRetries ?? 3;\n this.retryDelay = config.retryDelay ?? 1000;\n this.respectRateLimit = config.respectRateLimit ?? true;\n this.enableBatchSend = config.enableBatchSend ?? true;\n this.batchSize = config.batchSize ?? 100;\n this.batchSendTimeout = config.batchSendTimeout ?? 5000;\n this.batchSendDelimiter = config.batchSendDelimiter ?? \"\\n\";\n this.maxLogSize = config.maxLogSize ?? 1048576; // 1MB\n this.maxPayloadSize = config.maxPayloadSize ?? 5242880; // 5MB\n this.enableNextJsEdgeCompat = config.enableNextJsEdgeCompat ?? false;\n }\n\n /**\n * Processes and ships log entries to the HTTP endpoint.\n *\n * @param params - Log parameters including level, messages, and metadata\n * @returns The original messages array\n */\n shipToLogger({ logLevel, messages, data, hasData }: LogLayerTransportParams): any[] {\n try {\n const message = messages.join(\" \");\n\n const payloadTemplate = {\n logLevel,\n message,\n };\n\n if (hasData) {\n payloadTemplate[\"data\"] = data;\n }\n\n const payload = this.payloadTemplate(payloadTemplate);\n\n if (this.onDebug) {\n this.onDebug({ logLevel, message, data });\n }\n\n // Check log entry size\n let logEntrySize: number;\n\n if (this.enableNextJsEdgeCompat || typeof TextEncoder === \"undefined\") {\n // Fallback for environments without TextEncoder (like Next.js Edge Runtime)\n logEntrySize = Buffer.byteLength(payload, \"utf8\");\n } else {\n logEntrySize = new TextEncoder().encode(payload).length;\n }\n\n if (logEntrySize > this.maxLogSize) {\n const error = new LogSizeError(\n `Log entry exceeds maximum size of ${this.maxLogSize} bytes. Size: ${logEntrySize} bytes`,\n { logLevel, message, data },\n logEntrySize,\n this.maxLogSize,\n );\n\n if (this.onError) {\n this.onError(error);\n }\n return messages;\n }\n\n if (this.enableBatchSend) {\n this.addToBatch(payload, logEntrySize);\n } else {\n // Send immediately\n this.sendPayload(payload).catch((err) => {\n if (this.onError) {\n this.onError(err);\n }\n });\n }\n } catch (error) {\n if (this.onError) {\n this.onError(error as Error);\n }\n }\n\n return messages;\n }\n\n /**\n * Adds a payload to the batch queue and triggers sending if conditions are met\n */\n private addToBatch(payload: string, logEntrySize: number): void {\n // Check if adding this entry would exceed payload size limit\n const payloadSizeWithEntry = this.currentBatchSize + logEntrySize + this.batchSendDelimiter.length;\n const payloadSizeThreshold = this.maxPayloadSize * 0.9; // 90% of max payload size\n\n // Force send if adding this entry would exceed 90% of max payload size\n if (payloadSizeWithEntry > payloadSizeThreshold && this.batchQueue.length > 0) {\n this.processBatch();\n }\n\n this.batchQueue.push(payload);\n this.currentBatchSize += logEntrySize + this.batchSendDelimiter.length;\n\n // Start batch timeout if not already running\n if (!this.batchTimeout) {\n this.batchTimeout = setTimeout(() => {\n this.processBatch();\n }, this.batchSendTimeout);\n }\n\n // Send immediately if batch size is reached\n if (this.batchQueue.length >= this.batchSize) {\n this.processBatch();\n }\n }\n\n /**\n * Processes the current batch and sends it to the HTTP endpoint\n */\n private async processBatch(): Promise<void> {\n if (this.isProcessingBatch || this.batchQueue.length === 0) {\n return;\n }\n\n this.isProcessingBatch = true;\n\n // Clear the timeout\n if (this.batchTimeout) {\n clearTimeout(this.batchTimeout);\n this.batchTimeout = undefined;\n }\n\n // Get the current batch\n const batch = this.batchQueue.splice(0, this.batchSize);\n\n // Reset batch size counter\n this.currentBatchSize = 0;\n\n try {\n await this.sendBatch(batch);\n } catch (error) {\n if (this.onError) {\n this.onError(error as Error);\n }\n } finally {\n this.isProcessingBatch = false;\n\n // If there are more items in the queue, process them\n if (this.batchQueue.length > 0) {\n this.processBatch();\n }\n }\n }\n\n /**\n * Sends a batch of payloads to the HTTP endpoint\n */\n private async sendBatch(batch: string[]): Promise<void> {\n const batchPayload = batch.join(this.batchSendDelimiter);\n await this.sendPayload(batchPayload, this.batchContentType);\n }\n\n /**\n * Sends a single payload to the HTTP endpoint\n */\n private async sendPayload(payload: string, contentType?: string): Promise<void> {\n // Get headers\n const headers: Record<string, string> = typeof this.headers === \"function\" ? this.headers() : { ...this.headers };\n\n // Set content type - user headers take precedence\n if (!headers[\"content-type\"]) {\n headers[\"content-type\"] = contentType ?? this.contentType;\n }\n\n let finalPayload: string | Uint8Array = payload;\n\n // Apply compression if enabled and not in Next.js Edge compatibility mode\n if (this.compression && !this.enableNextJsEdgeCompat) {\n try {\n finalPayload = await compressData(payload);\n headers[\"content-encoding\"] = \"gzip\";\n } catch (error) {\n // If compression fails, fall back to uncompressed\n if (this.onError) {\n this.onError(new Error(`Compression failed: ${error}`));\n }\n }\n }\n\n await sendWithRetry(\n this.url,\n this.method,\n headers,\n finalPayload,\n this.maxRetries,\n this.retryDelay,\n this.respectRateLimit,\n );\n }\n}\n"]}
1
+ {"version":3,"sources":["/home/runner/work/loglayer/loglayer/packages/transports/http/dist/index.cjs","../src/errors.ts","../src/HttpTransport.ts","../src/utils.ts"],"names":[],"mappings":"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","file":"/home/runner/work/loglayer/loglayer/packages/transports/http/dist/index.cjs","sourcesContent":[null,"/**\n * Error thrown when HTTP request fails\n */\nexport class HttpTransportError extends Error {\n constructor(\n message: string,\n public status?: number,\n public response?: Response,\n ) {\n super(message);\n this.name = \"HttpTransportError\";\n }\n}\n\n/**\n * Error thrown when rate limit is exceeded\n */\nexport class RateLimitError extends Error {\n constructor(\n message: string,\n public retryAfter: number,\n ) {\n super(message);\n this.name = \"RateLimitError\";\n }\n}\n\n/**\n * Error thrown when log entry exceeds size limits\n */\nexport class LogSizeError extends Error {\n constructor(\n message: string,\n public logEntry: Record<string, any>,\n public size: number,\n public limit: number,\n ) {\n super(message);\n this.name = \"LogSizeError\";\n }\n}\n","import type { LoggerlessTransportConfig, LogLayerTransportParams } from \"@loglayer/transport\";\nimport { LoggerlessTransport } from \"@loglayer/transport\";\nimport { LogSizeError } from \"./errors.js\";\nimport { compressData, sendWithRetry } from \"./utils.js\";\n\n/**\n * Configuration options for the HTTP transport.\n */\nexport interface HttpTransportConfig extends LoggerlessTransportConfig {\n /**\n * The URL to send logs to\n */\n url: string;\n /**\n * HTTP method to use for requests\n * @default \"POST\"\n */\n method?: string;\n /**\n * Headers to include in the request. Can be an object or a function that returns headers.\n */\n headers?: Record<string, string> | (() => Record<string, string>);\n /**\n * Content type for single log requests. User-specified headers take precedence.\n * @default \"application/json\"\n */\n contentType?: string;\n /**\n * Content type for batch log requests. User-specified headers take precedence.\n * @default \"application/json\"\n */\n batchContentType?: string;\n /**\n * Optional callback for error handling\n */\n onError?: (err: Error) => void;\n /**\n * Optional callback for debugging log entries before they are sent\n */\n onDebug?: (entry: Record<string, any>) => void;\n /**\n * Optional callback for debugging HTTP requests and responses\n */\n onDebugReqRes?: (reqRes: {\n req: { url: string; method: string; headers: Record<string, string>; body: string | Uint8Array };\n res: { status: number; statusText: string; headers: Record<string, string>; body: string };\n }) => void;\n /**\n * Function to transform log data into the payload format\n */\n payloadTemplate: (data: { logLevel: string; message: string; data?: Record<string, any> }) => string;\n /**\n * Whether to use gzip compression\n * @default false\n */\n compression?: boolean;\n /**\n * Number of retry attempts before giving up\n * @default 3\n */\n maxRetries?: number;\n /**\n * Base delay between retries in milliseconds\n * @default 1000\n */\n retryDelay?: number;\n /**\n * Whether to respect rate limiting by waiting when a 429 response is received\n * @default true\n */\n respectRateLimit?: boolean;\n /**\n * Whether to enable batch sending\n * @default true\n */\n enableBatchSend?: boolean;\n /**\n * Number of log entries to batch before sending\n * @default 100\n */\n batchSize?: number;\n /**\n * Timeout in milliseconds for sending batches regardless of size\n * @default 5000\n */\n batchSendTimeout?: number;\n /**\n * Delimiter to use between log entries in batch mode\n * @default \"\\n\"\n */\n batchSendDelimiter?: string;\n /**\n * Batch mode for sending multiple log entries\n * - \"delimiter\": Join entries with a delimiter (default)\n * - \"field\": Wrap entries in an object with a field name\n * - \"array\": Send entries as a plain JSON array\n * @default \"delimiter\"\n */\n batchMode?: \"delimiter\" | \"field\" | \"array\";\n /**\n * Field name to wrap batch entries in when batchMode is \"field\" (e.g., \"batch\" for Logflare)\n * @default undefined\n */\n batchFieldName?: string;\n /**\n * Maximum size of a single log entry in bytes\n * @default 1048576 (1MB)\n */\n maxLogSize?: number;\n /**\n * Maximum size of the payload (uncompressed) in bytes\n * @default 5242880 (5MB)\n */\n maxPayloadSize?: number;\n /**\n * Whether to enable Next.js Edge Runtime compatibility mode\n * When enabled, TextEncoder and compression are disabled\n * @default false\n */\n enableNextJsEdgeCompat?: boolean;\n}\n\n/**\n * HttpTransport is responsible for sending logs to any HTTP endpoint.\n * It supports batching, compression, retries, and rate limiting.\n *\n * Features:\n * - Configurable HTTP method and headers\n * - Custom payload template function\n * - Gzip compression support\n * - Retry logic with exponential backoff\n * - Rate limiting support\n * - Batch sending with configurable size and timeout\n * - Error and debug callbacks\n * - Log size validation\n * - Payload size tracking for batching\n */\nexport class HttpTransport extends LoggerlessTransport {\n private url: string;\n private method: string;\n private headers: Record<string, string> | (() => Record<string, string>);\n private contentType: string;\n private batchContentType: string;\n private onError?: (err: Error) => void;\n private onDebug?: (entry: Record<string, any>) => void;\n private onDebugReqRes?: (reqRes: {\n req: { url: string; method: string; headers: Record<string, string>; body: string | Uint8Array };\n res: { status: number; statusText: string; headers: Record<string, string>; body: string };\n }) => void;\n private payloadTemplate: (data: { logLevel: string; message: string; data?: Record<string, any> }) => string;\n private compression: boolean;\n private maxRetries: number;\n private retryDelay: number;\n private respectRateLimit: boolean;\n private enableBatchSend: boolean;\n private batchSize: number;\n private batchSendTimeout: number;\n private batchSendDelimiter: string;\n private batchMode: \"delimiter\" | \"field\" | \"array\";\n private batchFieldName?: string;\n private maxLogSize: number;\n private maxPayloadSize: number;\n private enableNextJsEdgeCompat: boolean;\n\n // Batch management\n private batchQueue: string[] = [];\n private batchTimeout?: NodeJS.Timeout;\n private isProcessingBatch = false;\n private currentBatchSize = 0; // Track uncompressed size of current batch\n\n /**\n * Creates a new instance of HttpTransport.\n *\n * @param config - Configuration options for the transport\n */\n constructor(config: HttpTransportConfig) {\n super(config);\n\n this.url = config.url;\n this.method = config.method ?? \"POST\";\n this.headers = config.headers ?? {};\n this.contentType = config.contentType ?? \"application/json\";\n this.batchContentType = config.batchContentType ?? \"application/json\";\n this.onError = config.onError;\n this.onDebug = config.onDebug;\n this.onDebugReqRes = config.onDebugReqRes;\n this.payloadTemplate = config.payloadTemplate;\n this.compression = config.compression ?? false;\n this.maxRetries = config.maxRetries ?? 3;\n this.retryDelay = config.retryDelay ?? 1000;\n this.respectRateLimit = config.respectRateLimit ?? true;\n this.enableBatchSend = config.enableBatchSend ?? true;\n this.batchSize = config.batchSize ?? 100;\n this.batchSendTimeout = config.batchSendTimeout ?? 5000;\n this.batchSendDelimiter = config.batchSendDelimiter ?? \"\\n\";\n this.batchMode = config.batchMode ?? \"delimiter\";\n this.batchFieldName = config.batchFieldName;\n\n // Validate that batchFieldName is provided when batchMode is \"field\"\n if (this.batchMode === \"field\" && !this.batchFieldName) {\n throw new Error(\"batchFieldName is required when batchMode is 'field'\");\n }\n this.maxLogSize = config.maxLogSize ?? 1048576; // 1MB\n this.maxPayloadSize = config.maxPayloadSize ?? 5242880; // 5MB\n this.enableNextJsEdgeCompat = config.enableNextJsEdgeCompat ?? false;\n }\n\n /**\n * Processes and ships log entries to the HTTP endpoint.\n *\n * @param params - Log parameters including level, messages, and metadata\n * @returns The original messages array\n */\n shipToLogger({ logLevel, messages, data, hasData }: LogLayerTransportParams): any[] {\n try {\n const message = messages.join(\" \");\n\n const payloadTemplate = {\n logLevel,\n message,\n };\n\n if (hasData) {\n payloadTemplate[\"data\"] = data;\n }\n\n const payload = this.payloadTemplate(payloadTemplate);\n\n if (this.onDebug) {\n this.onDebug({ logLevel, message, data });\n }\n\n // Check log entry size\n let logEntrySize: number;\n\n if (this.enableNextJsEdgeCompat || typeof TextEncoder === \"undefined\") {\n // Fallback for environments without TextEncoder (like Next.js Edge Runtime)\n logEntrySize = Buffer.byteLength(payload, \"utf8\");\n } else {\n logEntrySize = new TextEncoder().encode(payload).length;\n }\n\n if (logEntrySize > this.maxLogSize) {\n const error = new LogSizeError(\n `Log entry exceeds maximum size of ${this.maxLogSize} bytes. Size: ${logEntrySize} bytes`,\n { logLevel, message, data },\n logEntrySize,\n this.maxLogSize,\n );\n\n if (this.onError) {\n this.onError(error);\n }\n return messages;\n }\n\n if (this.enableBatchSend) {\n this.addToBatch(payload, logEntrySize);\n } else {\n // Send immediately\n this.sendPayload(payload).catch((err) => {\n if (this.onError) {\n this.onError(err);\n }\n });\n }\n } catch (error) {\n if (this.onError) {\n this.onError(error as Error);\n }\n }\n\n return messages;\n }\n\n /**\n * Adds a payload to the batch queue and triggers sending if conditions are met\n */\n private addToBatch(payload: string, logEntrySize: number): void {\n // Check if adding this entry would exceed payload size limit\n const payloadSizeWithEntry = this.currentBatchSize + logEntrySize + this.batchSendDelimiter.length;\n const payloadSizeThreshold = this.maxPayloadSize * 0.9; // 90% of max payload size\n\n // Force send if adding this entry would exceed 90% of max payload size\n if (payloadSizeWithEntry > payloadSizeThreshold && this.batchQueue.length > 0) {\n this.processBatch();\n }\n\n this.batchQueue.push(payload);\n this.currentBatchSize += logEntrySize + this.batchSendDelimiter.length;\n\n // Start batch timeout if not already running\n if (!this.batchTimeout) {\n this.batchTimeout = setTimeout(() => {\n this.processBatch();\n }, this.batchSendTimeout);\n }\n\n // Send immediately if batch size is reached\n if (this.batchQueue.length >= this.batchSize) {\n this.processBatch();\n }\n }\n\n /**\n * Processes the current batch and sends it to the HTTP endpoint\n */\n private async processBatch(): Promise<void> {\n if (this.isProcessingBatch || this.batchQueue.length === 0) {\n return;\n }\n\n this.isProcessingBatch = true;\n\n // Clear the timeout\n if (this.batchTimeout) {\n clearTimeout(this.batchTimeout);\n this.batchTimeout = undefined;\n }\n\n // Get the current batch\n const batch = this.batchQueue.splice(0, this.batchSize);\n\n // Reset batch size counter\n this.currentBatchSize = 0;\n\n try {\n await this.sendBatch(batch);\n } catch (error) {\n if (this.onError) {\n this.onError(error as Error);\n }\n } finally {\n this.isProcessingBatch = false;\n\n // If there are more items in the queue, process them\n if (this.batchQueue.length > 0) {\n this.processBatch();\n }\n }\n }\n\n /**\n * Sends a batch of payloads to the HTTP endpoint\n */\n private async sendBatch(batch: string[]): Promise<void> {\n let batchPayload: string;\n\n switch (this.batchMode) {\n case \"array\": {\n // Send batch entries as a plain JSON array\n const batchEntries = batch.map((payload) => JSON.parse(payload));\n batchPayload = JSON.stringify(batchEntries);\n break;\n }\n case \"field\": {\n // Parse each payload as JSON and create a batch object\n const fieldEntries = batch.map((payload) => JSON.parse(payload));\n const batchObject = { [this.batchFieldName!]: fieldEntries };\n batchPayload = JSON.stringify(batchObject);\n break;\n }\n default:\n // Use delimiter-based batching (default behavior)\n batchPayload = batch.join(this.batchSendDelimiter);\n break;\n }\n\n await this.sendPayload(batchPayload, this.batchContentType);\n }\n\n /**\n * Sends a single payload to the HTTP endpoint\n */\n private async sendPayload(payload: string, contentType?: string): Promise<void> {\n // Get headers\n const headers: Record<string, string> = typeof this.headers === \"function\" ? this.headers() : { ...this.headers };\n\n // Set content type - user headers take precedence\n if (!headers[\"content-type\"]) {\n headers[\"content-type\"] = contentType ?? this.contentType;\n }\n\n let finalPayload: string | Uint8Array = payload;\n\n // Apply compression if enabled and not in Next.js Edge compatibility mode\n if (this.compression && !this.enableNextJsEdgeCompat) {\n try {\n finalPayload = await compressData(payload);\n headers[\"content-encoding\"] = \"gzip\";\n } catch (error) {\n // If compression fails, fall back to uncompressed\n if (this.onError) {\n this.onError(new Error(`Compression failed: ${error}`));\n }\n }\n }\n\n await sendWithRetry(\n this.url,\n this.method,\n headers,\n finalPayload,\n this.maxRetries,\n this.retryDelay,\n this.respectRateLimit,\n this.onDebugReqRes,\n this.onError,\n );\n }\n}\n","import { HttpTransportError, RateLimitError } from \"./errors.js\";\n\n/**\n * Compresses data using gzip compression\n */\nexport async function compressData(data: string): Promise<Uint8Array> {\n const encoder = new TextEncoder();\n const dataBytes = encoder.encode(data);\n\n // Use the CompressionStream API if available (modern browsers)\n if (typeof CompressionStream !== \"undefined\") {\n const stream = new CompressionStream(\"gzip\");\n const writer = stream.writable.getWriter();\n const reader = stream.readable.getReader();\n\n await writer.write(dataBytes);\n await writer.close();\n\n const chunks: Uint8Array[] = [];\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n chunks.push(value);\n }\n\n const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0);\n const result = new Uint8Array(totalLength);\n let offset = 0;\n for (const chunk of chunks) {\n result.set(chunk, offset);\n offset += chunk.length;\n }\n\n return result;\n }\n\n // Fallback for Node.js or environments without CompressionStream\n // In a real implementation, you might want to use a library like 'zlib' for Node.js\n throw new Error(\"Gzip compression not supported in this environment\");\n}\n\n/**\n * Sends HTTP request with retry logic and rate limiting\n */\nexport async function sendWithRetry(\n url: string,\n method: string,\n headers: Record<string, string>,\n payload: string | Uint8Array,\n maxRetries: number,\n retryDelay: number,\n respectRateLimit = true,\n onDebugReqRes?: (reqRes: {\n req: { url: string; method: string; headers: Record<string, string>; body: string | Uint8Array };\n res: { status: number; statusText: string; headers: Record<string, string>; body: string };\n }) => void,\n onError?: (err: Error) => void,\n): Promise<Response> {\n let lastError: Error;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n const response = await fetch(url, {\n method,\n headers,\n body: payload,\n });\n\n // Call debug callback if provided\n if (onDebugReqRes) {\n try {\n const responseBody = await response.clone().text();\n const responseHeaders: Record<string, string> = {};\n response.headers.forEach((value, key) => {\n responseHeaders[key] = value;\n });\n\n onDebugReqRes({\n req: {\n url,\n method,\n headers,\n body: payload,\n },\n res: {\n status: response.status,\n statusText: response.statusText,\n headers: responseHeaders,\n body: responseBody,\n },\n });\n } catch (debugError) {\n // Don't let debug callback errors break the main flow\n if (onError) {\n onError(new Error(`Debug callback error: ${debugError}`));\n }\n }\n }\n\n // Handle rate limiting\n if (response.status === 429 && respectRateLimit) {\n const retryAfter = response.headers.get(\"retry-after\");\n const waitTime = retryAfter ? Number.parseInt(retryAfter, 10) * 1000 : retryDelay;\n\n throw new RateLimitError(`Rate limit exceeded. Retry after ${waitTime}ms`, waitTime);\n }\n\n // Check for non-2xx status codes and log if onError is enabled\n if (response.status < 200 || response.status >= 300) {\n if (onError) {\n onError(new Error(`HTTP request failed with status ${response.status}: ${response.statusText}`));\n }\n }\n\n // Handle other errors\n if (!response.ok) {\n throw new HttpTransportError(`HTTP ${response.status}: ${response.statusText}`, response.status, response);\n }\n\n return response;\n } catch (error) {\n lastError = error as Error;\n\n // If it's a rate limit error and we should respect it, wait and retry\n if (error instanceof RateLimitError && respectRateLimit) {\n await new Promise((resolve) => setTimeout(resolve, error.retryAfter));\n continue;\n }\n\n // If it's the last attempt, throw the error\n if (attempt === maxRetries) {\n throw error;\n }\n\n // Wait before retrying with exponential backoff\n const waitTime = retryDelay * 2 ** attempt;\n await new Promise((resolve) => setTimeout(resolve, waitTime));\n }\n }\n\n throw lastError!;\n}\n"]}
package/dist/index.d.cts CHANGED
@@ -1,5 +1,30 @@
1
1
  import { LoggerlessTransportConfig, LoggerlessTransport, LogLayerTransportParams } from '@loglayer/transport';
2
2
 
3
+ /**
4
+ * Error thrown when HTTP request fails
5
+ */
6
+ declare class HttpTransportError extends Error {
7
+ status?: number;
8
+ response?: Response;
9
+ constructor(message: string, status?: number, response?: Response);
10
+ }
11
+ /**
12
+ * Error thrown when rate limit is exceeded
13
+ */
14
+ declare class RateLimitError extends Error {
15
+ retryAfter: number;
16
+ constructor(message: string, retryAfter: number);
17
+ }
18
+ /**
19
+ * Error thrown when log entry exceeds size limits
20
+ */
21
+ declare class LogSizeError extends Error {
22
+ logEntry: Record<string, any>;
23
+ size: number;
24
+ limit: number;
25
+ constructor(message: string, logEntry: Record<string, any>, size: number, limit: number);
26
+ }
27
+
3
28
  /**
4
29
  * Configuration options for the HTTP transport.
5
30
  */
@@ -35,6 +60,23 @@ interface HttpTransportConfig extends LoggerlessTransportConfig {
35
60
  * Optional callback for debugging log entries before they are sent
36
61
  */
37
62
  onDebug?: (entry: Record<string, any>) => void;
63
+ /**
64
+ * Optional callback for debugging HTTP requests and responses
65
+ */
66
+ onDebugReqRes?: (reqRes: {
67
+ req: {
68
+ url: string;
69
+ method: string;
70
+ headers: Record<string, string>;
71
+ body: string | Uint8Array;
72
+ };
73
+ res: {
74
+ status: number;
75
+ statusText: string;
76
+ headers: Record<string, string>;
77
+ body: string;
78
+ };
79
+ }) => void;
38
80
  /**
39
81
  * Function to transform log data into the payload format
40
82
  */
@@ -83,6 +125,19 @@ interface HttpTransportConfig extends LoggerlessTransportConfig {
83
125
  * @default "\n"
84
126
  */
85
127
  batchSendDelimiter?: string;
128
+ /**
129
+ * Batch mode for sending multiple log entries
130
+ * - "delimiter": Join entries with a delimiter (default)
131
+ * - "field": Wrap entries in an object with a field name
132
+ * - "array": Send entries as a plain JSON array
133
+ * @default "delimiter"
134
+ */
135
+ batchMode?: "delimiter" | "field" | "array";
136
+ /**
137
+ * Field name to wrap batch entries in when batchMode is "field" (e.g., "batch" for Logflare)
138
+ * @default undefined
139
+ */
140
+ batchFieldName?: string;
86
141
  /**
87
142
  * Maximum size of a single log entry in bytes
88
143
  * @default 1048576 (1MB)
@@ -123,6 +178,7 @@ declare class HttpTransport extends LoggerlessTransport {
123
178
  private batchContentType;
124
179
  private onError?;
125
180
  private onDebug?;
181
+ private onDebugReqRes?;
126
182
  private payloadTemplate;
127
183
  private compression;
128
184
  private maxRetries;
@@ -132,6 +188,8 @@ declare class HttpTransport extends LoggerlessTransport {
132
188
  private batchSize;
133
189
  private batchSendTimeout;
134
190
  private batchSendDelimiter;
191
+ private batchMode;
192
+ private batchFieldName?;
135
193
  private maxLogSize;
136
194
  private maxPayloadSize;
137
195
  private enableNextJsEdgeCompat;
@@ -170,4 +228,4 @@ declare class HttpTransport extends LoggerlessTransport {
170
228
  private sendPayload;
171
229
  }
172
230
 
173
- export { HttpTransport, type HttpTransportConfig };
231
+ export { HttpTransport, type HttpTransportConfig, HttpTransportError, LogSizeError, RateLimitError };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,30 @@
1
1
  import { LoggerlessTransportConfig, LoggerlessTransport, LogLayerTransportParams } from '@loglayer/transport';
2
2
 
3
+ /**
4
+ * Error thrown when HTTP request fails
5
+ */
6
+ declare class HttpTransportError extends Error {
7
+ status?: number;
8
+ response?: Response;
9
+ constructor(message: string, status?: number, response?: Response);
10
+ }
11
+ /**
12
+ * Error thrown when rate limit is exceeded
13
+ */
14
+ declare class RateLimitError extends Error {
15
+ retryAfter: number;
16
+ constructor(message: string, retryAfter: number);
17
+ }
18
+ /**
19
+ * Error thrown when log entry exceeds size limits
20
+ */
21
+ declare class LogSizeError extends Error {
22
+ logEntry: Record<string, any>;
23
+ size: number;
24
+ limit: number;
25
+ constructor(message: string, logEntry: Record<string, any>, size: number, limit: number);
26
+ }
27
+
3
28
  /**
4
29
  * Configuration options for the HTTP transport.
5
30
  */
@@ -35,6 +60,23 @@ interface HttpTransportConfig extends LoggerlessTransportConfig {
35
60
  * Optional callback for debugging log entries before they are sent
36
61
  */
37
62
  onDebug?: (entry: Record<string, any>) => void;
63
+ /**
64
+ * Optional callback for debugging HTTP requests and responses
65
+ */
66
+ onDebugReqRes?: (reqRes: {
67
+ req: {
68
+ url: string;
69
+ method: string;
70
+ headers: Record<string, string>;
71
+ body: string | Uint8Array;
72
+ };
73
+ res: {
74
+ status: number;
75
+ statusText: string;
76
+ headers: Record<string, string>;
77
+ body: string;
78
+ };
79
+ }) => void;
38
80
  /**
39
81
  * Function to transform log data into the payload format
40
82
  */
@@ -83,6 +125,19 @@ interface HttpTransportConfig extends LoggerlessTransportConfig {
83
125
  * @default "\n"
84
126
  */
85
127
  batchSendDelimiter?: string;
128
+ /**
129
+ * Batch mode for sending multiple log entries
130
+ * - "delimiter": Join entries with a delimiter (default)
131
+ * - "field": Wrap entries in an object with a field name
132
+ * - "array": Send entries as a plain JSON array
133
+ * @default "delimiter"
134
+ */
135
+ batchMode?: "delimiter" | "field" | "array";
136
+ /**
137
+ * Field name to wrap batch entries in when batchMode is "field" (e.g., "batch" for Logflare)
138
+ * @default undefined
139
+ */
140
+ batchFieldName?: string;
86
141
  /**
87
142
  * Maximum size of a single log entry in bytes
88
143
  * @default 1048576 (1MB)
@@ -123,6 +178,7 @@ declare class HttpTransport extends LoggerlessTransport {
123
178
  private batchContentType;
124
179
  private onError?;
125
180
  private onDebug?;
181
+ private onDebugReqRes?;
126
182
  private payloadTemplate;
127
183
  private compression;
128
184
  private maxRetries;
@@ -132,6 +188,8 @@ declare class HttpTransport extends LoggerlessTransport {
132
188
  private batchSize;
133
189
  private batchSendTimeout;
134
190
  private batchSendDelimiter;
191
+ private batchMode;
192
+ private batchFieldName?;
135
193
  private maxLogSize;
136
194
  private maxPayloadSize;
137
195
  private enableNextJsEdgeCompat;
@@ -170,4 +228,4 @@ declare class HttpTransport extends LoggerlessTransport {
170
228
  private sendPayload;
171
229
  }
172
230
 
173
- export { HttpTransport, type HttpTransportConfig };
231
+ export { HttpTransport, type HttpTransportConfig, HttpTransportError, LogSizeError, RateLimitError };
package/dist/index.js CHANGED
@@ -1,5 +1,4 @@
1
- // src/HttpTransport.ts
2
- import { LoggerlessTransport } from "@loglayer/transport";
1
+ // src/errors.ts
3
2
  var HttpTransportError = class extends Error {
4
3
  constructor(message, status, response) {
5
4
  super(message);
@@ -24,6 +23,11 @@ var LogSizeError = class extends Error {
24
23
  this.name = "LogSizeError";
25
24
  }
26
25
  };
26
+
27
+ // src/HttpTransport.ts
28
+ import { LoggerlessTransport } from "@loglayer/transport";
29
+
30
+ // src/utils.ts
27
31
  async function compressData(data) {
28
32
  const encoder = new TextEncoder();
29
33
  const dataBytes = encoder.encode(data);
@@ -50,7 +54,7 @@ async function compressData(data) {
50
54
  }
51
55
  throw new Error("Gzip compression not supported in this environment");
52
56
  }
53
- async function sendWithRetry(url, method, headers, payload, maxRetries, retryDelay, respectRateLimit = true) {
57
+ async function sendWithRetry(url, method, headers, payload, maxRetries, retryDelay, respectRateLimit = true, onDebugReqRes, onError) {
54
58
  let lastError;
55
59
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
56
60
  try {
@@ -59,11 +63,43 @@ async function sendWithRetry(url, method, headers, payload, maxRetries, retryDel
59
63
  headers,
60
64
  body: payload
61
65
  });
66
+ if (onDebugReqRes) {
67
+ try {
68
+ const responseBody = await response.clone().text();
69
+ const responseHeaders = {};
70
+ response.headers.forEach((value, key) => {
71
+ responseHeaders[key] = value;
72
+ });
73
+ onDebugReqRes({
74
+ req: {
75
+ url,
76
+ method,
77
+ headers,
78
+ body: payload
79
+ },
80
+ res: {
81
+ status: response.status,
82
+ statusText: response.statusText,
83
+ headers: responseHeaders,
84
+ body: responseBody
85
+ }
86
+ });
87
+ } catch (debugError) {
88
+ if (onError) {
89
+ onError(new Error(`Debug callback error: ${debugError}`));
90
+ }
91
+ }
92
+ }
62
93
  if (response.status === 429 && respectRateLimit) {
63
94
  const retryAfter = response.headers.get("retry-after");
64
95
  const waitTime = retryAfter ? Number.parseInt(retryAfter, 10) * 1e3 : retryDelay;
65
96
  throw new RateLimitError(`Rate limit exceeded. Retry after ${waitTime}ms`, waitTime);
66
97
  }
98
+ if (response.status < 200 || response.status >= 300) {
99
+ if (onError) {
100
+ onError(new Error(`HTTP request failed with status ${response.status}: ${response.statusText}`));
101
+ }
102
+ }
67
103
  if (!response.ok) {
68
104
  throw new HttpTransportError(`HTTP ${response.status}: ${response.statusText}`, response.status, response);
69
105
  }
@@ -83,6 +119,8 @@ async function sendWithRetry(url, method, headers, payload, maxRetries, retryDel
83
119
  }
84
120
  throw lastError;
85
121
  }
122
+
123
+ // src/HttpTransport.ts
86
124
  var HttpTransport = class extends LoggerlessTransport {
87
125
  url;
88
126
  method;
@@ -91,6 +129,7 @@ var HttpTransport = class extends LoggerlessTransport {
91
129
  batchContentType;
92
130
  onError;
93
131
  onDebug;
132
+ onDebugReqRes;
94
133
  payloadTemplate;
95
134
  compression;
96
135
  maxRetries;
@@ -100,6 +139,8 @@ var HttpTransport = class extends LoggerlessTransport {
100
139
  batchSize;
101
140
  batchSendTimeout;
102
141
  batchSendDelimiter;
142
+ batchMode;
143
+ batchFieldName;
103
144
  maxLogSize;
104
145
  maxPayloadSize;
105
146
  enableNextJsEdgeCompat;
@@ -123,6 +164,7 @@ var HttpTransport = class extends LoggerlessTransport {
123
164
  this.batchContentType = config.batchContentType ?? "application/json";
124
165
  this.onError = config.onError;
125
166
  this.onDebug = config.onDebug;
167
+ this.onDebugReqRes = config.onDebugReqRes;
126
168
  this.payloadTemplate = config.payloadTemplate;
127
169
  this.compression = config.compression ?? false;
128
170
  this.maxRetries = config.maxRetries ?? 3;
@@ -132,6 +174,11 @@ var HttpTransport = class extends LoggerlessTransport {
132
174
  this.batchSize = config.batchSize ?? 100;
133
175
  this.batchSendTimeout = config.batchSendTimeout ?? 5e3;
134
176
  this.batchSendDelimiter = config.batchSendDelimiter ?? "\n";
177
+ this.batchMode = config.batchMode ?? "delimiter";
178
+ this.batchFieldName = config.batchFieldName;
179
+ if (this.batchMode === "field" && !this.batchFieldName) {
180
+ throw new Error("batchFieldName is required when batchMode is 'field'");
181
+ }
135
182
  this.maxLogSize = config.maxLogSize ?? 1048576;
136
183
  this.maxPayloadSize = config.maxPayloadSize ?? 5242880;
137
184
  this.enableNextJsEdgeCompat = config.enableNextJsEdgeCompat ?? false;
@@ -241,7 +288,23 @@ var HttpTransport = class extends LoggerlessTransport {
241
288
  * Sends a batch of payloads to the HTTP endpoint
242
289
  */
243
290
  async sendBatch(batch) {
244
- const batchPayload = batch.join(this.batchSendDelimiter);
291
+ let batchPayload;
292
+ switch (this.batchMode) {
293
+ case "array": {
294
+ const batchEntries = batch.map((payload) => JSON.parse(payload));
295
+ batchPayload = JSON.stringify(batchEntries);
296
+ break;
297
+ }
298
+ case "field": {
299
+ const fieldEntries = batch.map((payload) => JSON.parse(payload));
300
+ const batchObject = { [this.batchFieldName]: fieldEntries };
301
+ batchPayload = JSON.stringify(batchObject);
302
+ break;
303
+ }
304
+ default:
305
+ batchPayload = batch.join(this.batchSendDelimiter);
306
+ break;
307
+ }
245
308
  await this.sendPayload(batchPayload, this.batchContentType);
246
309
  }
247
310
  /**
@@ -270,11 +333,16 @@ var HttpTransport = class extends LoggerlessTransport {
270
333
  finalPayload,
271
334
  this.maxRetries,
272
335
  this.retryDelay,
273
- this.respectRateLimit
336
+ this.respectRateLimit,
337
+ this.onDebugReqRes,
338
+ this.onError
274
339
  );
275
340
  }
276
341
  };
277
342
  export {
278
- HttpTransport
343
+ HttpTransport,
344
+ HttpTransportError,
345
+ LogSizeError,
346
+ RateLimitError
279
347
  };
280
348
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/HttpTransport.ts"],"sourcesContent":["import type { LoggerlessTransportConfig, LogLayerTransportParams } from \"@loglayer/transport\";\nimport { LoggerlessTransport } from \"@loglayer/transport\";\n\n/**\n * Error thrown when HTTP request fails\n */\nclass HttpTransportError extends Error {\n constructor(\n message: string,\n public status?: number,\n public response?: Response,\n ) {\n super(message);\n this.name = \"HttpTransportError\";\n }\n}\n\n/**\n * Error thrown when rate limit is exceeded\n */\nclass RateLimitError extends Error {\n constructor(\n message: string,\n public retryAfter: number,\n ) {\n super(message);\n this.name = \"RateLimitError\";\n }\n}\n\n/**\n * Error thrown when log entry exceeds size limits\n */\nclass LogSizeError extends Error {\n constructor(\n message: string,\n public logEntry: Record<string, any>,\n public size: number,\n public limit: number,\n ) {\n super(message);\n this.name = \"LogSizeError\";\n }\n}\n\n/**\n * Configuration options for the HTTP transport.\n */\nexport interface HttpTransportConfig extends LoggerlessTransportConfig {\n /**\n * The URL to send logs to\n */\n url: string;\n /**\n * HTTP method to use for requests\n * @default \"POST\"\n */\n method?: string;\n /**\n * Headers to include in the request. Can be an object or a function that returns headers.\n */\n headers?: Record<string, string> | (() => Record<string, string>);\n /**\n * Content type for single log requests. User-specified headers take precedence.\n * @default \"application/json\"\n */\n contentType?: string;\n /**\n * Content type for batch log requests. User-specified headers take precedence.\n * @default \"application/json\"\n */\n batchContentType?: string;\n /**\n * Optional callback for error handling\n */\n onError?: (err: Error) => void;\n /**\n * Optional callback for debugging log entries before they are sent\n */\n onDebug?: (entry: Record<string, any>) => void;\n /**\n * Function to transform log data into the payload format\n */\n payloadTemplate: (data: { logLevel: string; message: string; data?: Record<string, any> }) => string;\n /**\n * Whether to use gzip compression\n * @default false\n */\n compression?: boolean;\n /**\n * Number of retry attempts before giving up\n * @default 3\n */\n maxRetries?: number;\n /**\n * Base delay between retries in milliseconds\n * @default 1000\n */\n retryDelay?: number;\n /**\n * Whether to respect rate limiting by waiting when a 429 response is received\n * @default true\n */\n respectRateLimit?: boolean;\n /**\n * Whether to enable batch sending\n * @default true\n */\n enableBatchSend?: boolean;\n /**\n * Number of log entries to batch before sending\n * @default 100\n */\n batchSize?: number;\n /**\n * Timeout in milliseconds for sending batches regardless of size\n * @default 5000\n */\n batchSendTimeout?: number;\n /**\n * Delimiter to use between log entries in batch mode\n * @default \"\\n\"\n */\n batchSendDelimiter?: string;\n /**\n * Maximum size of a single log entry in bytes\n * @default 1048576 (1MB)\n */\n maxLogSize?: number;\n /**\n * Maximum size of the payload (uncompressed) in bytes\n * @default 5242880 (5MB)\n */\n maxPayloadSize?: number;\n /**\n * Whether to enable Next.js Edge Runtime compatibility mode\n * When enabled, TextEncoder and compression are disabled\n * @default false\n */\n enableNextJsEdgeCompat?: boolean;\n}\n\n/**\n * Compresses data using gzip compression\n */\nasync function compressData(data: string): Promise<Uint8Array> {\n const encoder = new TextEncoder();\n const dataBytes = encoder.encode(data);\n\n // Use the CompressionStream API if available (modern browsers)\n if (typeof CompressionStream !== \"undefined\") {\n const stream = new CompressionStream(\"gzip\");\n const writer = stream.writable.getWriter();\n const reader = stream.readable.getReader();\n\n await writer.write(dataBytes);\n await writer.close();\n\n const chunks: Uint8Array[] = [];\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n chunks.push(value);\n }\n\n const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0);\n const result = new Uint8Array(totalLength);\n let offset = 0;\n for (const chunk of chunks) {\n result.set(chunk, offset);\n offset += chunk.length;\n }\n\n return result;\n }\n\n // Fallback for Node.js or environments without CompressionStream\n // In a real implementation, you might want to use a library like 'zlib' for Node.js\n throw new Error(\"Gzip compression not supported in this environment\");\n}\n\n/**\n * Sends HTTP request with retry logic and rate limiting\n */\nasync function sendWithRetry(\n url: string,\n method: string,\n headers: Record<string, string>,\n payload: string | Uint8Array,\n maxRetries: number,\n retryDelay: number,\n respectRateLimit = true,\n): Promise<Response> {\n let lastError: Error;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n const response = await fetch(url, {\n method,\n headers,\n body: payload,\n });\n\n // Handle rate limiting\n if (response.status === 429 && respectRateLimit) {\n const retryAfter = response.headers.get(\"retry-after\");\n const waitTime = retryAfter ? Number.parseInt(retryAfter, 10) * 1000 : retryDelay;\n\n throw new RateLimitError(`Rate limit exceeded. Retry after ${waitTime}ms`, waitTime);\n }\n\n // Handle other errors\n if (!response.ok) {\n throw new HttpTransportError(`HTTP ${response.status}: ${response.statusText}`, response.status, response);\n }\n\n return response;\n } catch (error) {\n lastError = error as Error;\n\n // If it's a rate limit error and we should respect it, wait and retry\n if (error instanceof RateLimitError && respectRateLimit) {\n await new Promise((resolve) => setTimeout(resolve, error.retryAfter));\n continue;\n }\n\n // If it's the last attempt, throw the error\n if (attempt === maxRetries) {\n throw error;\n }\n\n // Wait before retrying with exponential backoff\n const waitTime = retryDelay * 2 ** attempt;\n await new Promise((resolve) => setTimeout(resolve, waitTime));\n }\n }\n\n throw lastError!;\n}\n\n/**\n * HttpTransport is responsible for sending logs to any HTTP endpoint.\n * It supports batching, compression, retries, and rate limiting.\n *\n * Features:\n * - Configurable HTTP method and headers\n * - Custom payload template function\n * - Gzip compression support\n * - Retry logic with exponential backoff\n * - Rate limiting support\n * - Batch sending with configurable size and timeout\n * - Error and debug callbacks\n * - Log size validation\n * - Payload size tracking for batching\n */\nexport class HttpTransport extends LoggerlessTransport {\n private url: string;\n private method: string;\n private headers: Record<string, string> | (() => Record<string, string>);\n private contentType: string;\n private batchContentType: string;\n private onError?: (err: Error) => void;\n private onDebug?: (entry: Record<string, any>) => void;\n private payloadTemplate: (data: { logLevel: string; message: string; data?: Record<string, any> }) => string;\n private compression: boolean;\n private maxRetries: number;\n private retryDelay: number;\n private respectRateLimit: boolean;\n private enableBatchSend: boolean;\n private batchSize: number;\n private batchSendTimeout: number;\n private batchSendDelimiter: string;\n private maxLogSize: number;\n private maxPayloadSize: number;\n private enableNextJsEdgeCompat: boolean;\n\n // Batch management\n private batchQueue: string[] = [];\n private batchTimeout?: NodeJS.Timeout;\n private isProcessingBatch = false;\n private currentBatchSize = 0; // Track uncompressed size of current batch\n\n /**\n * Creates a new instance of HttpTransport.\n *\n * @param config - Configuration options for the transport\n */\n constructor(config: HttpTransportConfig) {\n super(config);\n\n this.url = config.url;\n this.method = config.method ?? \"POST\";\n this.headers = config.headers ?? {};\n this.contentType = config.contentType ?? \"application/json\";\n this.batchContentType = config.batchContentType ?? \"application/json\";\n this.onError = config.onError;\n this.onDebug = config.onDebug;\n this.payloadTemplate = config.payloadTemplate;\n this.compression = config.compression ?? false;\n this.maxRetries = config.maxRetries ?? 3;\n this.retryDelay = config.retryDelay ?? 1000;\n this.respectRateLimit = config.respectRateLimit ?? true;\n this.enableBatchSend = config.enableBatchSend ?? true;\n this.batchSize = config.batchSize ?? 100;\n this.batchSendTimeout = config.batchSendTimeout ?? 5000;\n this.batchSendDelimiter = config.batchSendDelimiter ?? \"\\n\";\n this.maxLogSize = config.maxLogSize ?? 1048576; // 1MB\n this.maxPayloadSize = config.maxPayloadSize ?? 5242880; // 5MB\n this.enableNextJsEdgeCompat = config.enableNextJsEdgeCompat ?? false;\n }\n\n /**\n * Processes and ships log entries to the HTTP endpoint.\n *\n * @param params - Log parameters including level, messages, and metadata\n * @returns The original messages array\n */\n shipToLogger({ logLevel, messages, data, hasData }: LogLayerTransportParams): any[] {\n try {\n const message = messages.join(\" \");\n\n const payloadTemplate = {\n logLevel,\n message,\n };\n\n if (hasData) {\n payloadTemplate[\"data\"] = data;\n }\n\n const payload = this.payloadTemplate(payloadTemplate);\n\n if (this.onDebug) {\n this.onDebug({ logLevel, message, data });\n }\n\n // Check log entry size\n let logEntrySize: number;\n\n if (this.enableNextJsEdgeCompat || typeof TextEncoder === \"undefined\") {\n // Fallback for environments without TextEncoder (like Next.js Edge Runtime)\n logEntrySize = Buffer.byteLength(payload, \"utf8\");\n } else {\n logEntrySize = new TextEncoder().encode(payload).length;\n }\n\n if (logEntrySize > this.maxLogSize) {\n const error = new LogSizeError(\n `Log entry exceeds maximum size of ${this.maxLogSize} bytes. Size: ${logEntrySize} bytes`,\n { logLevel, message, data },\n logEntrySize,\n this.maxLogSize,\n );\n\n if (this.onError) {\n this.onError(error);\n }\n return messages;\n }\n\n if (this.enableBatchSend) {\n this.addToBatch(payload, logEntrySize);\n } else {\n // Send immediately\n this.sendPayload(payload).catch((err) => {\n if (this.onError) {\n this.onError(err);\n }\n });\n }\n } catch (error) {\n if (this.onError) {\n this.onError(error as Error);\n }\n }\n\n return messages;\n }\n\n /**\n * Adds a payload to the batch queue and triggers sending if conditions are met\n */\n private addToBatch(payload: string, logEntrySize: number): void {\n // Check if adding this entry would exceed payload size limit\n const payloadSizeWithEntry = this.currentBatchSize + logEntrySize + this.batchSendDelimiter.length;\n const payloadSizeThreshold = this.maxPayloadSize * 0.9; // 90% of max payload size\n\n // Force send if adding this entry would exceed 90% of max payload size\n if (payloadSizeWithEntry > payloadSizeThreshold && this.batchQueue.length > 0) {\n this.processBatch();\n }\n\n this.batchQueue.push(payload);\n this.currentBatchSize += logEntrySize + this.batchSendDelimiter.length;\n\n // Start batch timeout if not already running\n if (!this.batchTimeout) {\n this.batchTimeout = setTimeout(() => {\n this.processBatch();\n }, this.batchSendTimeout);\n }\n\n // Send immediately if batch size is reached\n if (this.batchQueue.length >= this.batchSize) {\n this.processBatch();\n }\n }\n\n /**\n * Processes the current batch and sends it to the HTTP endpoint\n */\n private async processBatch(): Promise<void> {\n if (this.isProcessingBatch || this.batchQueue.length === 0) {\n return;\n }\n\n this.isProcessingBatch = true;\n\n // Clear the timeout\n if (this.batchTimeout) {\n clearTimeout(this.batchTimeout);\n this.batchTimeout = undefined;\n }\n\n // Get the current batch\n const batch = this.batchQueue.splice(0, this.batchSize);\n\n // Reset batch size counter\n this.currentBatchSize = 0;\n\n try {\n await this.sendBatch(batch);\n } catch (error) {\n if (this.onError) {\n this.onError(error as Error);\n }\n } finally {\n this.isProcessingBatch = false;\n\n // If there are more items in the queue, process them\n if (this.batchQueue.length > 0) {\n this.processBatch();\n }\n }\n }\n\n /**\n * Sends a batch of payloads to the HTTP endpoint\n */\n private async sendBatch(batch: string[]): Promise<void> {\n const batchPayload = batch.join(this.batchSendDelimiter);\n await this.sendPayload(batchPayload, this.batchContentType);\n }\n\n /**\n * Sends a single payload to the HTTP endpoint\n */\n private async sendPayload(payload: string, contentType?: string): Promise<void> {\n // Get headers\n const headers: Record<string, string> = typeof this.headers === \"function\" ? this.headers() : { ...this.headers };\n\n // Set content type - user headers take precedence\n if (!headers[\"content-type\"]) {\n headers[\"content-type\"] = contentType ?? this.contentType;\n }\n\n let finalPayload: string | Uint8Array = payload;\n\n // Apply compression if enabled and not in Next.js Edge compatibility mode\n if (this.compression && !this.enableNextJsEdgeCompat) {\n try {\n finalPayload = await compressData(payload);\n headers[\"content-encoding\"] = \"gzip\";\n } catch (error) {\n // If compression fails, fall back to uncompressed\n if (this.onError) {\n this.onError(new Error(`Compression failed: ${error}`));\n }\n }\n }\n\n await sendWithRetry(\n this.url,\n this.method,\n headers,\n finalPayload,\n this.maxRetries,\n this.retryDelay,\n this.respectRateLimit,\n );\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/errors.ts","../src/HttpTransport.ts","../src/utils.ts"],"sourcesContent":["/**\n * Error thrown when HTTP request fails\n */\nexport class HttpTransportError extends Error {\n constructor(\n message: string,\n public status?: number,\n public response?: Response,\n ) {\n super(message);\n this.name = \"HttpTransportError\";\n }\n}\n\n/**\n * Error thrown when rate limit is exceeded\n */\nexport class RateLimitError extends Error {\n constructor(\n message: string,\n public retryAfter: number,\n ) {\n super(message);\n this.name = \"RateLimitError\";\n }\n}\n\n/**\n * Error thrown when log entry exceeds size limits\n */\nexport class LogSizeError extends Error {\n constructor(\n message: string,\n public logEntry: Record<string, any>,\n public size: number,\n public limit: number,\n ) {\n super(message);\n this.name = \"LogSizeError\";\n }\n}\n","import type { LoggerlessTransportConfig, LogLayerTransportParams } from \"@loglayer/transport\";\nimport { LoggerlessTransport } from \"@loglayer/transport\";\nimport { LogSizeError } from \"./errors.js\";\nimport { compressData, sendWithRetry } from \"./utils.js\";\n\n/**\n * Configuration options for the HTTP transport.\n */\nexport interface HttpTransportConfig extends LoggerlessTransportConfig {\n /**\n * The URL to send logs to\n */\n url: string;\n /**\n * HTTP method to use for requests\n * @default \"POST\"\n */\n method?: string;\n /**\n * Headers to include in the request. Can be an object or a function that returns headers.\n */\n headers?: Record<string, string> | (() => Record<string, string>);\n /**\n * Content type for single log requests. User-specified headers take precedence.\n * @default \"application/json\"\n */\n contentType?: string;\n /**\n * Content type for batch log requests. User-specified headers take precedence.\n * @default \"application/json\"\n */\n batchContentType?: string;\n /**\n * Optional callback for error handling\n */\n onError?: (err: Error) => void;\n /**\n * Optional callback for debugging log entries before they are sent\n */\n onDebug?: (entry: Record<string, any>) => void;\n /**\n * Optional callback for debugging HTTP requests and responses\n */\n onDebugReqRes?: (reqRes: {\n req: { url: string; method: string; headers: Record<string, string>; body: string | Uint8Array };\n res: { status: number; statusText: string; headers: Record<string, string>; body: string };\n }) => void;\n /**\n * Function to transform log data into the payload format\n */\n payloadTemplate: (data: { logLevel: string; message: string; data?: Record<string, any> }) => string;\n /**\n * Whether to use gzip compression\n * @default false\n */\n compression?: boolean;\n /**\n * Number of retry attempts before giving up\n * @default 3\n */\n maxRetries?: number;\n /**\n * Base delay between retries in milliseconds\n * @default 1000\n */\n retryDelay?: number;\n /**\n * Whether to respect rate limiting by waiting when a 429 response is received\n * @default true\n */\n respectRateLimit?: boolean;\n /**\n * Whether to enable batch sending\n * @default true\n */\n enableBatchSend?: boolean;\n /**\n * Number of log entries to batch before sending\n * @default 100\n */\n batchSize?: number;\n /**\n * Timeout in milliseconds for sending batches regardless of size\n * @default 5000\n */\n batchSendTimeout?: number;\n /**\n * Delimiter to use between log entries in batch mode\n * @default \"\\n\"\n */\n batchSendDelimiter?: string;\n /**\n * Batch mode for sending multiple log entries\n * - \"delimiter\": Join entries with a delimiter (default)\n * - \"field\": Wrap entries in an object with a field name\n * - \"array\": Send entries as a plain JSON array\n * @default \"delimiter\"\n */\n batchMode?: \"delimiter\" | \"field\" | \"array\";\n /**\n * Field name to wrap batch entries in when batchMode is \"field\" (e.g., \"batch\" for Logflare)\n * @default undefined\n */\n batchFieldName?: string;\n /**\n * Maximum size of a single log entry in bytes\n * @default 1048576 (1MB)\n */\n maxLogSize?: number;\n /**\n * Maximum size of the payload (uncompressed) in bytes\n * @default 5242880 (5MB)\n */\n maxPayloadSize?: number;\n /**\n * Whether to enable Next.js Edge Runtime compatibility mode\n * When enabled, TextEncoder and compression are disabled\n * @default false\n */\n enableNextJsEdgeCompat?: boolean;\n}\n\n/**\n * HttpTransport is responsible for sending logs to any HTTP endpoint.\n * It supports batching, compression, retries, and rate limiting.\n *\n * Features:\n * - Configurable HTTP method and headers\n * - Custom payload template function\n * - Gzip compression support\n * - Retry logic with exponential backoff\n * - Rate limiting support\n * - Batch sending with configurable size and timeout\n * - Error and debug callbacks\n * - Log size validation\n * - Payload size tracking for batching\n */\nexport class HttpTransport extends LoggerlessTransport {\n private url: string;\n private method: string;\n private headers: Record<string, string> | (() => Record<string, string>);\n private contentType: string;\n private batchContentType: string;\n private onError?: (err: Error) => void;\n private onDebug?: (entry: Record<string, any>) => void;\n private onDebugReqRes?: (reqRes: {\n req: { url: string; method: string; headers: Record<string, string>; body: string | Uint8Array };\n res: { status: number; statusText: string; headers: Record<string, string>; body: string };\n }) => void;\n private payloadTemplate: (data: { logLevel: string; message: string; data?: Record<string, any> }) => string;\n private compression: boolean;\n private maxRetries: number;\n private retryDelay: number;\n private respectRateLimit: boolean;\n private enableBatchSend: boolean;\n private batchSize: number;\n private batchSendTimeout: number;\n private batchSendDelimiter: string;\n private batchMode: \"delimiter\" | \"field\" | \"array\";\n private batchFieldName?: string;\n private maxLogSize: number;\n private maxPayloadSize: number;\n private enableNextJsEdgeCompat: boolean;\n\n // Batch management\n private batchQueue: string[] = [];\n private batchTimeout?: NodeJS.Timeout;\n private isProcessingBatch = false;\n private currentBatchSize = 0; // Track uncompressed size of current batch\n\n /**\n * Creates a new instance of HttpTransport.\n *\n * @param config - Configuration options for the transport\n */\n constructor(config: HttpTransportConfig) {\n super(config);\n\n this.url = config.url;\n this.method = config.method ?? \"POST\";\n this.headers = config.headers ?? {};\n this.contentType = config.contentType ?? \"application/json\";\n this.batchContentType = config.batchContentType ?? \"application/json\";\n this.onError = config.onError;\n this.onDebug = config.onDebug;\n this.onDebugReqRes = config.onDebugReqRes;\n this.payloadTemplate = config.payloadTemplate;\n this.compression = config.compression ?? false;\n this.maxRetries = config.maxRetries ?? 3;\n this.retryDelay = config.retryDelay ?? 1000;\n this.respectRateLimit = config.respectRateLimit ?? true;\n this.enableBatchSend = config.enableBatchSend ?? true;\n this.batchSize = config.batchSize ?? 100;\n this.batchSendTimeout = config.batchSendTimeout ?? 5000;\n this.batchSendDelimiter = config.batchSendDelimiter ?? \"\\n\";\n this.batchMode = config.batchMode ?? \"delimiter\";\n this.batchFieldName = config.batchFieldName;\n\n // Validate that batchFieldName is provided when batchMode is \"field\"\n if (this.batchMode === \"field\" && !this.batchFieldName) {\n throw new Error(\"batchFieldName is required when batchMode is 'field'\");\n }\n this.maxLogSize = config.maxLogSize ?? 1048576; // 1MB\n this.maxPayloadSize = config.maxPayloadSize ?? 5242880; // 5MB\n this.enableNextJsEdgeCompat = config.enableNextJsEdgeCompat ?? false;\n }\n\n /**\n * Processes and ships log entries to the HTTP endpoint.\n *\n * @param params - Log parameters including level, messages, and metadata\n * @returns The original messages array\n */\n shipToLogger({ logLevel, messages, data, hasData }: LogLayerTransportParams): any[] {\n try {\n const message = messages.join(\" \");\n\n const payloadTemplate = {\n logLevel,\n message,\n };\n\n if (hasData) {\n payloadTemplate[\"data\"] = data;\n }\n\n const payload = this.payloadTemplate(payloadTemplate);\n\n if (this.onDebug) {\n this.onDebug({ logLevel, message, data });\n }\n\n // Check log entry size\n let logEntrySize: number;\n\n if (this.enableNextJsEdgeCompat || typeof TextEncoder === \"undefined\") {\n // Fallback for environments without TextEncoder (like Next.js Edge Runtime)\n logEntrySize = Buffer.byteLength(payload, \"utf8\");\n } else {\n logEntrySize = new TextEncoder().encode(payload).length;\n }\n\n if (logEntrySize > this.maxLogSize) {\n const error = new LogSizeError(\n `Log entry exceeds maximum size of ${this.maxLogSize} bytes. Size: ${logEntrySize} bytes`,\n { logLevel, message, data },\n logEntrySize,\n this.maxLogSize,\n );\n\n if (this.onError) {\n this.onError(error);\n }\n return messages;\n }\n\n if (this.enableBatchSend) {\n this.addToBatch(payload, logEntrySize);\n } else {\n // Send immediately\n this.sendPayload(payload).catch((err) => {\n if (this.onError) {\n this.onError(err);\n }\n });\n }\n } catch (error) {\n if (this.onError) {\n this.onError(error as Error);\n }\n }\n\n return messages;\n }\n\n /**\n * Adds a payload to the batch queue and triggers sending if conditions are met\n */\n private addToBatch(payload: string, logEntrySize: number): void {\n // Check if adding this entry would exceed payload size limit\n const payloadSizeWithEntry = this.currentBatchSize + logEntrySize + this.batchSendDelimiter.length;\n const payloadSizeThreshold = this.maxPayloadSize * 0.9; // 90% of max payload size\n\n // Force send if adding this entry would exceed 90% of max payload size\n if (payloadSizeWithEntry > payloadSizeThreshold && this.batchQueue.length > 0) {\n this.processBatch();\n }\n\n this.batchQueue.push(payload);\n this.currentBatchSize += logEntrySize + this.batchSendDelimiter.length;\n\n // Start batch timeout if not already running\n if (!this.batchTimeout) {\n this.batchTimeout = setTimeout(() => {\n this.processBatch();\n }, this.batchSendTimeout);\n }\n\n // Send immediately if batch size is reached\n if (this.batchQueue.length >= this.batchSize) {\n this.processBatch();\n }\n }\n\n /**\n * Processes the current batch and sends it to the HTTP endpoint\n */\n private async processBatch(): Promise<void> {\n if (this.isProcessingBatch || this.batchQueue.length === 0) {\n return;\n }\n\n this.isProcessingBatch = true;\n\n // Clear the timeout\n if (this.batchTimeout) {\n clearTimeout(this.batchTimeout);\n this.batchTimeout = undefined;\n }\n\n // Get the current batch\n const batch = this.batchQueue.splice(0, this.batchSize);\n\n // Reset batch size counter\n this.currentBatchSize = 0;\n\n try {\n await this.sendBatch(batch);\n } catch (error) {\n if (this.onError) {\n this.onError(error as Error);\n }\n } finally {\n this.isProcessingBatch = false;\n\n // If there are more items in the queue, process them\n if (this.batchQueue.length > 0) {\n this.processBatch();\n }\n }\n }\n\n /**\n * Sends a batch of payloads to the HTTP endpoint\n */\n private async sendBatch(batch: string[]): Promise<void> {\n let batchPayload: string;\n\n switch (this.batchMode) {\n case \"array\": {\n // Send batch entries as a plain JSON array\n const batchEntries = batch.map((payload) => JSON.parse(payload));\n batchPayload = JSON.stringify(batchEntries);\n break;\n }\n case \"field\": {\n // Parse each payload as JSON and create a batch object\n const fieldEntries = batch.map((payload) => JSON.parse(payload));\n const batchObject = { [this.batchFieldName!]: fieldEntries };\n batchPayload = JSON.stringify(batchObject);\n break;\n }\n default:\n // Use delimiter-based batching (default behavior)\n batchPayload = batch.join(this.batchSendDelimiter);\n break;\n }\n\n await this.sendPayload(batchPayload, this.batchContentType);\n }\n\n /**\n * Sends a single payload to the HTTP endpoint\n */\n private async sendPayload(payload: string, contentType?: string): Promise<void> {\n // Get headers\n const headers: Record<string, string> = typeof this.headers === \"function\" ? this.headers() : { ...this.headers };\n\n // Set content type - user headers take precedence\n if (!headers[\"content-type\"]) {\n headers[\"content-type\"] = contentType ?? this.contentType;\n }\n\n let finalPayload: string | Uint8Array = payload;\n\n // Apply compression if enabled and not in Next.js Edge compatibility mode\n if (this.compression && !this.enableNextJsEdgeCompat) {\n try {\n finalPayload = await compressData(payload);\n headers[\"content-encoding\"] = \"gzip\";\n } catch (error) {\n // If compression fails, fall back to uncompressed\n if (this.onError) {\n this.onError(new Error(`Compression failed: ${error}`));\n }\n }\n }\n\n await sendWithRetry(\n this.url,\n this.method,\n headers,\n finalPayload,\n this.maxRetries,\n this.retryDelay,\n this.respectRateLimit,\n this.onDebugReqRes,\n this.onError,\n );\n }\n}\n","import { HttpTransportError, RateLimitError } from \"./errors.js\";\n\n/**\n * Compresses data using gzip compression\n */\nexport async function compressData(data: string): Promise<Uint8Array> {\n const encoder = new TextEncoder();\n const dataBytes = encoder.encode(data);\n\n // Use the CompressionStream API if available (modern browsers)\n if (typeof CompressionStream !== \"undefined\") {\n const stream = new CompressionStream(\"gzip\");\n const writer = stream.writable.getWriter();\n const reader = stream.readable.getReader();\n\n await writer.write(dataBytes);\n await writer.close();\n\n const chunks: Uint8Array[] = [];\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n chunks.push(value);\n }\n\n const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0);\n const result = new Uint8Array(totalLength);\n let offset = 0;\n for (const chunk of chunks) {\n result.set(chunk, offset);\n offset += chunk.length;\n }\n\n return result;\n }\n\n // Fallback for Node.js or environments without CompressionStream\n // In a real implementation, you might want to use a library like 'zlib' for Node.js\n throw new Error(\"Gzip compression not supported in this environment\");\n}\n\n/**\n * Sends HTTP request with retry logic and rate limiting\n */\nexport async function sendWithRetry(\n url: string,\n method: string,\n headers: Record<string, string>,\n payload: string | Uint8Array,\n maxRetries: number,\n retryDelay: number,\n respectRateLimit = true,\n onDebugReqRes?: (reqRes: {\n req: { url: string; method: string; headers: Record<string, string>; body: string | Uint8Array };\n res: { status: number; statusText: string; headers: Record<string, string>; body: string };\n }) => void,\n onError?: (err: Error) => void,\n): Promise<Response> {\n let lastError: Error;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n const response = await fetch(url, {\n method,\n headers,\n body: payload,\n });\n\n // Call debug callback if provided\n if (onDebugReqRes) {\n try {\n const responseBody = await response.clone().text();\n const responseHeaders: Record<string, string> = {};\n response.headers.forEach((value, key) => {\n responseHeaders[key] = value;\n });\n\n onDebugReqRes({\n req: {\n url,\n method,\n headers,\n body: payload,\n },\n res: {\n status: response.status,\n statusText: response.statusText,\n headers: responseHeaders,\n body: responseBody,\n },\n });\n } catch (debugError) {\n // Don't let debug callback errors break the main flow\n if (onError) {\n onError(new Error(`Debug callback error: ${debugError}`));\n }\n }\n }\n\n // Handle rate limiting\n if (response.status === 429 && respectRateLimit) {\n const retryAfter = response.headers.get(\"retry-after\");\n const waitTime = retryAfter ? Number.parseInt(retryAfter, 10) * 1000 : retryDelay;\n\n throw new RateLimitError(`Rate limit exceeded. Retry after ${waitTime}ms`, waitTime);\n }\n\n // Check for non-2xx status codes and log if onError is enabled\n if (response.status < 200 || response.status >= 300) {\n if (onError) {\n onError(new Error(`HTTP request failed with status ${response.status}: ${response.statusText}`));\n }\n }\n\n // Handle other errors\n if (!response.ok) {\n throw new HttpTransportError(`HTTP ${response.status}: ${response.statusText}`, response.status, response);\n }\n\n return response;\n } catch (error) {\n lastError = error as Error;\n\n // If it's a rate limit error and we should respect it, wait and retry\n if (error instanceof RateLimitError && respectRateLimit) {\n await new Promise((resolve) => setTimeout(resolve, error.retryAfter));\n continue;\n }\n\n // If it's the last attempt, throw the error\n if (attempt === maxRetries) {\n throw error;\n }\n\n // Wait before retrying with exponential backoff\n const waitTime = retryDelay * 2 ** attempt;\n await new Promise((resolve) => setTimeout(resolve, waitTime));\n }\n }\n\n throw lastError!;\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@loglayer/transport-http",
3
3
  "description": "HTTP transport for the LogLayer logging library.",
4
- "version": "1.0.7",
4
+ "version": "1.1.1",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
7
7
  "module": "./dist/index.js",
@@ -41,7 +41,7 @@
41
41
  "tsup": "8.5.0",
42
42
  "typescript": "5.9.3",
43
43
  "vitest": "3.2.4",
44
- "loglayer": "6.8.2",
44
+ "loglayer": "6.8.3",
45
45
  "@internal/tsconfig": "2.1.0"
46
46
  },
47
47
  "bugs": "https://github.com/loglayer/loglayer/issues",