logwell 0.1.6 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -21,6 +21,7 @@ Official TypeScript SDK for [Logwell](https://github.com/divkix/logwell) - a sel
21
21
  - **Automatic batching** - Configurable batch size and flush intervals
22
22
  - **Retry with backoff** - Exponential backoff on transient failures
23
23
  - **Child loggers** - Request-scoped context propagation
24
+ - **Source location capture** - Opt-in file/line number tracking
24
25
  - **Lightweight** - < 10KB gzipped
25
26
 
26
27
  ## Installation
@@ -81,6 +82,7 @@ interface LogwellConfig {
81
82
  flushInterval?: number; // Auto-flush interval in ms (default: 5000)
82
83
  maxQueueSize?: number; // Max queue size (default: 1000)
83
84
  maxRetries?: number; // Retry attempts (default: 3)
85
+ captureSourceLocation?: boolean; // Capture file/line (default: false)
84
86
 
85
87
  // Callbacks
86
88
  onError?: (error: Error) => void; // Called on send failures
@@ -134,6 +136,23 @@ requestLogger.info('Request received', { path: req.path });
134
136
  logger.queueSize: number
135
137
  ```
136
138
 
139
+ ### Source Location Capture
140
+
141
+ Enable automatic file and line number capture for debugging:
142
+
143
+ ```typescript
144
+ const logger = new Logwell({
145
+ apiKey: 'lw_xxx',
146
+ endpoint: 'https://logs.example.com',
147
+ captureSourceLocation: true, // opt-in
148
+ });
149
+
150
+ logger.info('User logged in');
151
+ // Log includes: sourceFile: '/app/src/auth.ts', lineNumber: 42
152
+ ```
153
+
154
+ > **Note:** This feature has performance overhead (creates Error object per log). Disabled by default. Works across Node.js, Bun, and browsers.
155
+
137
156
  ## Usage Examples
138
157
 
139
158
  ### Express.js Middleware
package/dist/index.cjs CHANGED
@@ -1,2 +1,3 @@
1
- 'use strict';var i=class r extends Error{constructor(t,n,o,h=false){super(t);this.code=n;this.statusCode=o;this.retryable=h;this.name="LogwellError",Error.captureStackTrace?.(this,r);}};var s={batchSize:50,flushInterval:5e3,maxQueueSize:1e3,maxRetries:3},d=/^lw_[A-Za-z0-9_-]{32}$/;function f(r){return !r||typeof r!="string"?false:d.test(r)}function g(r){try{return new URL(r),!0}catch{return false}}function p(r){if(!r.apiKey)throw new i("apiKey is required","INVALID_CONFIG");if(!r.endpoint)throw new i("endpoint is required","INVALID_CONFIG");if(!f(r.apiKey))throw new i("Invalid API key format. Expected: lw_[32 characters]","INVALID_CONFIG");if(!g(r.endpoint))throw new i("Invalid endpoint URL","INVALID_CONFIG");if(r.batchSize!==void 0&&r.batchSize<=0)throw new i("batchSize must be positive","INVALID_CONFIG");if(r.flushInterval!==void 0&&r.flushInterval<=0)throw new i("flushInterval must be positive","INVALID_CONFIG");if(r.maxQueueSize!==void 0&&r.maxQueueSize<=0)throw new i("maxQueueSize must be positive","INVALID_CONFIG");if(r.maxRetries!==void 0&&r.maxRetries<0)throw new i("maxRetries must be non-negative","INVALID_CONFIG");return {apiKey:r.apiKey,endpoint:r.endpoint,service:r.service,batchSize:r.batchSize??s.batchSize,flushInterval:r.flushInterval??s.flushInterval,maxQueueSize:r.maxQueueSize??s.maxQueueSize,maxRetries:r.maxRetries??s.maxRetries,onError:r.onError,onFlush:r.onFlush}}var a=class{constructor(e,t){this.sendBatch=e;this.config=t;}queue=[];flushTimer=null;flushing=false;stopped=false;get size(){return this.queue.length}add(e){if(!this.stopped){if(this.queue.length>=this.config.maxQueueSize){let t=this.queue.shift();this.config.onError?.(new i(`Queue overflow. Dropped log: ${t?.message.substring(0,50)}...`,"QUEUE_OVERFLOW"));}this.queue.push(e),!this.flushTimer&&!this.stopped&&this.startTimer(),this.queue.length>=this.config.batchSize&&this.flush();}}async flush(){if(this.flushing||this.queue.length===0)return null;this.flushing=true,this.stopTimer();let e=this.queue.splice(0),t=e.length;try{let n=await this.sendBatch(e);return this.config.onFlush?.(t),this.queue.length>0&&!this.stopped&&this.startTimer(),n}catch(n){return this.queue.unshift(...e),this.config.onError?.(n),this.stopped||this.startTimer(),null}finally{this.flushing=false;}}async shutdown(){this.stopped||(this.stopped=true,this.stopTimer(),this.queue.length>0&&(this.flushing=false,await this.flush()));}startTimer(){this.flushTimer=setTimeout(()=>{this.flush();},this.config.flushInterval);}stopTimer(){this.flushTimer&&(clearTimeout(this.flushTimer),this.flushTimer=null);}};function c(r,e=100){let t=Math.min(e*2**r,1e4),n=Math.random()*t*.3;return new Promise(o=>setTimeout(o,t+n))}var u=class{constructor(e){this.config=e;this.ingestUrl=`${e.endpoint}/v1/ingest`;}ingestUrl;async send(e){let t=new i("Max retries exceeded","NETWORK_ERROR",void 0,true);for(let n=0;n<=this.config.maxRetries;n++)try{return await this.doRequest(e)}catch(o){if(t=o,!t.retryable)throw t;n<this.config.maxRetries&&await c(n);}throw t}async doRequest(e){let t;try{t=await fetch(this.ingestUrl,{method:"POST",headers:{Authorization:`Bearer ${this.config.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify(e)});}catch(n){throw new i(`Network error: ${n.message}`,"NETWORK_ERROR",void 0,true)}if(!t.ok){let n=await this.tryParseError(t);throw this.createError(t.status,n)}return await t.json()}async tryParseError(e){try{let t=await e.json();return t.message||t.error||"Unknown error"}catch{return `HTTP ${e.status}`}}createError(e,t){switch(e){case 401:return new i(`Unauthorized: ${t}`,"UNAUTHORIZED",e,false);case 400:return new i(`Validation error: ${t}`,"VALIDATION_ERROR",e,false);case 429:return new i(`Rate limited: ${t}`,"RATE_LIMITED",e,true);default:return e>=500?new i(`Server error: ${t}`,"SERVER_ERROR",e,true):new i(`HTTP error ${e}: ${t}`,"SERVER_ERROR",e,false)}}};var l=class r{config;queue;transport;parentMetadata;stopped=false;constructor(e,t,n){if(this.config=p(e),this.parentMetadata=n,this.transport=new u({endpoint:this.config.endpoint,apiKey:this.config.apiKey,maxRetries:this.config.maxRetries}),t)this.queue=t;else {let o={batchSize:this.config.batchSize,flushInterval:this.config.flushInterval,maxQueueSize:this.config.maxQueueSize,onError:this.config.onError,onFlush:this.config.onFlush};this.queue=new a(h=>this.transport.send(h),o);}}get queueSize(){return this.queue.size}log(e){if(this.stopped)return;let t={...e,timestamp:e.timestamp??new Date().toISOString(),service:e.service??this.config.service,metadata:this.mergeMetadata(e.metadata)};this.queue.add(t);}debug(e,t){this.log({level:"debug",message:e,metadata:t});}info(e,t){this.log({level:"info",message:e,metadata:t});}warn(e,t){this.log({level:"warn",message:e,metadata:t});}error(e,t){this.log({level:"error",message:e,metadata:t});}fatal(e,t){this.log({level:"fatal",message:e,metadata:t});}async flush(){return this.queue.flush()}async shutdown(){this.stopped=true,await this.queue.shutdown();}child(e){let t={...this.config,service:e.service??this.config.service},n={...this.parentMetadata,...e.metadata};return new r(t,this.queue,n)}mergeMetadata(e){if(!(!this.parentMetadata&&!e))return {...this.parentMetadata,...e}}};exports.Logwell=l;exports.LogwellError=i;//# sourceMappingURL=index.cjs.map
1
+ 'use strict';var o=class r extends Error{constructor(t,n,i,s=false){super(t);this.code=n;this.statusCode=i;this.retryable=s;this.name="LogwellError",Error.captureStackTrace?.(this,r);}};var u={batchSize:50,flushInterval:5e3,maxQueueSize:1e3,maxRetries:3,captureSourceLocation:false},m=/^lw_[A-Za-z0-9_-]{32}$/;function R(r){return !r||typeof r!="string"?false:m.test(r)}function E(r){try{return new URL(r),!0}catch{return false}}function p(r){if(!r.apiKey)throw new o("apiKey is required","INVALID_CONFIG");if(!r.endpoint)throw new o("endpoint is required","INVALID_CONFIG");if(!R(r.apiKey))throw new o("Invalid API key format. Expected: lw_[32 characters]","INVALID_CONFIG");if(!E(r.endpoint))throw new o("Invalid endpoint URL","INVALID_CONFIG");if(r.batchSize!==void 0&&r.batchSize<=0)throw new o("batchSize must be positive","INVALID_CONFIG");if(r.flushInterval!==void 0&&r.flushInterval<=0)throw new o("flushInterval must be positive","INVALID_CONFIG");if(r.maxQueueSize!==void 0&&r.maxQueueSize<=0)throw new o("maxQueueSize must be positive","INVALID_CONFIG");if(r.maxRetries!==void 0&&r.maxRetries<0)throw new o("maxRetries must be non-negative","INVALID_CONFIG");return {apiKey:r.apiKey,endpoint:r.endpoint,service:r.service,batchSize:r.batchSize??u.batchSize,flushInterval:r.flushInterval??u.flushInterval,maxQueueSize:r.maxQueueSize??u.maxQueueSize,maxRetries:r.maxRetries??u.maxRetries,captureSourceLocation:r.captureSourceLocation??u.captureSourceLocation,onError:r.onError,onFlush:r.onFlush}}var l=class{constructor(e,t){this.sendBatch=e;this.config=t;}queue=[];flushTimer=null;flushing=false;stopped=false;get size(){return this.queue.length}add(e){if(!this.stopped){if(this.queue.length>=this.config.maxQueueSize){let t=this.queue.shift();this.config.onError?.(new o(`Queue overflow. Dropped log: ${t?.message.substring(0,50)}...`,"QUEUE_OVERFLOW"));}this.queue.push(e),!this.flushTimer&&!this.stopped&&this.startTimer(),this.queue.length>=this.config.batchSize&&this.flush();}}async flush(){if(this.flushing||this.queue.length===0)return null;this.flushing=true,this.stopTimer();let e=this.queue.splice(0),t=e.length;try{let n=await this.sendBatch(e);return this.config.onFlush?.(t),this.queue.length>0&&!this.stopped&&this.startTimer(),n}catch(n){return this.queue.unshift(...e),this.config.onError?.(n),this.stopped||this.startTimer(),null}finally{this.flushing=false;}}async shutdown(){this.stopped||(this.stopped=true,this.stopTimer(),this.queue.length>0&&(this.flushing=false,await this.flush()));}startTimer(){this.flushTimer=setTimeout(()=>{this.flush();},this.config.flushInterval);}stopTimer(){this.flushTimer&&(clearTimeout(this.flushTimer),this.flushTimer=null);}};var w=/\((.+):(\d+):\d+\)\s*$/,v=/^\s*at\s+(.+):(\d+):\d+$/,L=/^[^@]*@(.+):(\d+):\d+$/;function I(r){if(!r)return;let e=w.exec(r);if(e){let t=e[1],n=e[2];if(t&&n){let i=parseInt(n,10);if(!Number.isNaN(i))return {sourceFile:t,lineNumber:i}}}if(e=v.exec(r),e){let t=e[1],n=e[2];if(t&&n){let i=parseInt(n,10);if(!Number.isNaN(i))return {sourceFile:t,lineNumber:i}}}if(e=L.exec(r),e){let t=e[1],n=e[2];if(t&&n){let i=parseInt(n,10);if(!Number.isNaN(i))return {sourceFile:t,lineNumber:i}}}}function f(r){let t=new Error().stack;if(!t)return;let n=t.split(`
2
+ `),i=n[0]||"",g=(!i.includes("@")&&!/^\s*at\s/.test(i)?1:0)+1+r,h=n[g];if(h!==void 0)return I(h)}function y(r,e=100){let t=Math.min(e*2**r,1e4),n=Math.random()*t*.3;return new Promise(i=>setTimeout(i,t+n))}var c=class{constructor(e){this.config=e;this.ingestUrl=`${e.endpoint}/v1/ingest`;}ingestUrl;async send(e){let t=new o("Max retries exceeded","NETWORK_ERROR",void 0,true);for(let n=0;n<=this.config.maxRetries;n++)try{return await this.doRequest(e)}catch(i){if(t=i,!t.retryable)throw t;n<this.config.maxRetries&&await y(n);}throw t}async doRequest(e){let t;try{t=await fetch(this.ingestUrl,{method:"POST",headers:{Authorization:`Bearer ${this.config.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify(e)});}catch(n){throw new o(`Network error: ${n.message}`,"NETWORK_ERROR",void 0,true)}if(!t.ok){let n=await this.tryParseError(t);throw this.createError(t.status,n)}return await t.json()}async tryParseError(e){try{let t=await e.json();return t.message||t.error||"Unknown error"}catch{return `HTTP ${e.status}`}}createError(e,t){switch(e){case 401:return new o(`Unauthorized: ${t}`,"UNAUTHORIZED",e,false);case 400:return new o(`Validation error: ${t}`,"VALIDATION_ERROR",e,false);case 429:return new o(`Rate limited: ${t}`,"RATE_LIMITED",e,true);default:return e>=500?new o(`Server error: ${t}`,"SERVER_ERROR",e,true):new o(`HTTP error ${e}: ${t}`,"SERVER_ERROR",e,false)}}};var d=class r{config;queue;transport;parentMetadata;stopped=false;constructor(e,t,n){if(this.config=p(e),this.parentMetadata=n,this.transport=new c({endpoint:this.config.endpoint,apiKey:this.config.apiKey,maxRetries:this.config.maxRetries}),t)this.queue=t;else {let i={batchSize:this.config.batchSize,flushInterval:this.config.flushInterval,maxQueueSize:this.config.maxQueueSize,onError:this.config.onError,onFlush:this.config.onFlush};this.queue=new l(s=>this.transport.send(s),i);}}get queueSize(){return this.queue.size}_addLog(e,t){if(this.stopped)return;let n,i;if(this.config.captureSourceLocation){let a=f(t);a&&(n=a.sourceFile,i=a.lineNumber);}let s={...e,timestamp:e.timestamp??new Date().toISOString(),service:e.service??this.config.service,metadata:this.mergeMetadata(e.metadata),...n!==void 0&&{sourceFile:n},...i!==void 0&&{lineNumber:i}};this.queue.add(s);}log(e){this._addLog(e,2);}debug(e,t){this._addLog({level:"debug",message:e,metadata:t},2);}info(e,t){this._addLog({level:"info",message:e,metadata:t},2);}warn(e,t){this._addLog({level:"warn",message:e,metadata:t},2);}error(e,t){this._addLog({level:"error",message:e,metadata:t},2);}fatal(e,t){this._addLog({level:"fatal",message:e,metadata:t},2);}async flush(){return this.queue.flush()}async shutdown(){this.stopped=true,await this.queue.shutdown();}child(e){let t={...this.config,service:e.service??this.config.service},n={...this.parentMetadata,...e.metadata};return new r(t,this.queue,n)}mergeMetadata(e){if(!(!this.parentMetadata&&!e))return {...this.parentMetadata,...e}}};exports.Logwell=d;exports.LogwellError=o;//# sourceMappingURL=index.cjs.map
2
3
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts","../src/config.ts","../src/queue.ts","../src/transport.ts","../src/client.ts"],"names":["LogwellError","_LogwellError","message","code","statusCode","retryable","DEFAULT_CONFIG","API_KEY_REGEX","validateApiKeyFormat","apiKey","isValidUrl","url","validateConfig","config","BatchQueue","sendBatch","entry","dropped","batch","count","response","error","delay","attempt","baseDelay","ms","jitter","resolve","HttpTransport","logs","lastError","errorBody","body","status","Logwell","_Logwell","existingQueue","parentMetadata","queueConfig","fullEntry","metadata","options","childConfig","childMetadata","entryMetadata"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Error codes for Logwell SDK errors\n */\nexport type LogwellErrorCode =\n | 'NETWORK_ERROR'\n | 'UNAUTHORIZED'\n | 'VALIDATION_ERROR'\n | 'RATE_LIMITED'\n | 'SERVER_ERROR'\n | 'QUEUE_OVERFLOW'\n | 'INVALID_CONFIG';\n\n/**\n * Custom error class for Logwell SDK errors\n *\n * @example\n * ```ts\n * throw new LogwellError('Invalid API key', 'UNAUTHORIZED', 401, false);\n * ```\n */\nexport class LogwellError extends Error {\n /**\n * Creates a new LogwellError\n *\n * @param message - Human-readable error message\n * @param code - Error code for programmatic handling\n * @param statusCode - HTTP status code if applicable\n * @param retryable - Whether the operation can be retried\n */\n constructor(\n message: string,\n public readonly code: LogwellErrorCode,\n public readonly statusCode?: number,\n public readonly retryable: boolean = false,\n ) {\n super(message);\n this.name = 'LogwellError';\n\n // Maintains proper stack trace for where our error was thrown (V8 only)\n // Use type assertion to avoid global augmentation (required for JSR compatibility)\n const ErrorWithCapture = Error as unknown as {\n captureStackTrace?: (target: object, ctor?: NewableFunction) => void;\n };\n ErrorWithCapture.captureStackTrace?.(this, LogwellError);\n }\n}\n","import { LogwellError } from './errors';\nimport type { LogwellConfig } from './types';\n\n/**\n * Default configuration values\n */\nexport const DEFAULT_CONFIG = {\n batchSize: 50,\n flushInterval: 5000,\n maxQueueSize: 1000,\n maxRetries: 3,\n} as const;\n\n/**\n * API key format regex: lw_[32 alphanumeric chars including - and _]\n */\nexport const API_KEY_REGEX = /^lw_[A-Za-z0-9_-]{32}$/;\n\n/**\n * Validates API key format\n *\n * @param apiKey - API key to validate\n * @returns true if valid format, false otherwise\n */\nexport function validateApiKeyFormat(apiKey: string): boolean {\n if (!apiKey || typeof apiKey !== 'string') {\n return false;\n }\n return API_KEY_REGEX.test(apiKey);\n}\n\n/**\n * Validates a URL string\n *\n * @param url - URL string to validate\n * @returns true if valid URL, false otherwise\n */\nfunction isValidUrl(url: string): boolean {\n try {\n new URL(url);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Validates configuration and returns merged config with defaults\n *\n * @param config - Partial configuration to validate\n * @returns Complete configuration with defaults applied\n * @throws LogwellError if configuration is invalid\n */\nexport function validateConfig(config: Partial<LogwellConfig>): LogwellConfig {\n // Validate required fields\n if (!config.apiKey) {\n throw new LogwellError('apiKey is required', 'INVALID_CONFIG');\n }\n\n if (!config.endpoint) {\n throw new LogwellError('endpoint is required', 'INVALID_CONFIG');\n }\n\n // Validate API key format\n if (!validateApiKeyFormat(config.apiKey)) {\n throw new LogwellError(\n 'Invalid API key format. Expected: lw_[32 characters]',\n 'INVALID_CONFIG',\n );\n }\n\n // Validate endpoint URL\n if (!isValidUrl(config.endpoint)) {\n throw new LogwellError('Invalid endpoint URL', 'INVALID_CONFIG');\n }\n\n // Validate numeric options\n if (config.batchSize !== undefined && config.batchSize <= 0) {\n throw new LogwellError('batchSize must be positive', 'INVALID_CONFIG');\n }\n\n if (config.flushInterval !== undefined && config.flushInterval <= 0) {\n throw new LogwellError('flushInterval must be positive', 'INVALID_CONFIG');\n }\n\n if (config.maxQueueSize !== undefined && config.maxQueueSize <= 0) {\n throw new LogwellError('maxQueueSize must be positive', 'INVALID_CONFIG');\n }\n\n if (config.maxRetries !== undefined && config.maxRetries < 0) {\n throw new LogwellError('maxRetries must be non-negative', 'INVALID_CONFIG');\n }\n\n // Return merged config with defaults\n return {\n apiKey: config.apiKey,\n endpoint: config.endpoint,\n service: config.service,\n batchSize: config.batchSize ?? DEFAULT_CONFIG.batchSize,\n flushInterval: config.flushInterval ?? DEFAULT_CONFIG.flushInterval,\n maxQueueSize: config.maxQueueSize ?? DEFAULT_CONFIG.maxQueueSize,\n maxRetries: config.maxRetries ?? DEFAULT_CONFIG.maxRetries,\n onError: config.onError,\n onFlush: config.onFlush,\n };\n}\n","import { LogwellError } from './errors';\nimport type { IngestResponse, LogEntry } from './types';\n\n/**\n * Callback type for sending batched logs\n */\nexport type SendBatchFn = (logs: LogEntry[]) => Promise<IngestResponse>;\n\n/**\n * Queue configuration options\n */\nexport interface QueueConfig {\n batchSize: number;\n flushInterval: number;\n maxQueueSize: number;\n onError?: (error: Error) => void;\n onFlush?: (count: number) => void;\n}\n\n/**\n * Batch queue for buffering and sending logs\n *\n * Features:\n * - Automatic flush on batch size threshold\n * - Automatic flush on time interval\n * - Queue overflow protection (drops oldest)\n * - Re-queue on send failure\n * - Graceful shutdown\n */\nexport class BatchQueue {\n private queue: LogEntry[] = [];\n private flushTimer: ReturnType<typeof setTimeout> | null = null;\n private flushing = false;\n private stopped = false;\n\n constructor(\n private sendBatch: SendBatchFn,\n private config: QueueConfig,\n ) {}\n\n /**\n * Current number of logs in the queue\n */\n get size(): number {\n return this.queue.length;\n }\n\n /**\n * Add a log entry to the queue\n *\n * Triggers flush if batch size is reached.\n * Drops oldest log if queue overflows.\n */\n add(entry: LogEntry): void {\n if (this.stopped) {\n return;\n }\n\n // Handle queue overflow\n if (this.queue.length >= this.config.maxQueueSize) {\n const dropped = this.queue.shift();\n this.config.onError?.(\n new LogwellError(\n `Queue overflow. Dropped log: ${dropped?.message.substring(0, 50)}...`,\n 'QUEUE_OVERFLOW',\n ),\n );\n }\n\n this.queue.push(entry);\n\n // Start timer on first entry\n if (!this.flushTimer && !this.stopped) {\n this.startTimer();\n }\n\n // Flush immediately if batch size reached\n if (this.queue.length >= this.config.batchSize) {\n void this.flush();\n }\n }\n\n /**\n * Flush all queued logs immediately\n *\n * @returns Response from the server, or null if queue was empty\n */\n async flush(): Promise<IngestResponse | null> {\n // Prevent concurrent flushes\n if (this.flushing || this.queue.length === 0) {\n return null;\n }\n\n this.flushing = true;\n this.stopTimer();\n\n // Take current batch\n const batch = this.queue.splice(0);\n const count = batch.length;\n\n try {\n const response = await this.sendBatch(batch);\n this.config.onFlush?.(count);\n\n // Restart timer if more logs remain (added during flush)\n if (this.queue.length > 0 && !this.stopped) {\n this.startTimer();\n }\n\n return response;\n } catch (error) {\n // Re-queue failed logs at the front\n this.queue.unshift(...batch);\n this.config.onError?.(error as Error);\n\n // Restart timer to retry\n if (!this.stopped) {\n this.startTimer();\n }\n\n return null;\n } finally {\n this.flushing = false;\n }\n }\n\n /**\n * Flush remaining logs and stop the queue\n */\n async shutdown(): Promise<void> {\n if (this.stopped) {\n return;\n }\n\n this.stopped = true;\n this.stopTimer();\n\n // Flush all remaining logs\n if (this.queue.length > 0) {\n this.flushing = false; // Reset flushing flag\n await this.flush();\n }\n }\n\n private startTimer(): void {\n this.flushTimer = setTimeout(() => {\n void this.flush();\n }, this.config.flushInterval);\n }\n\n private stopTimer(): void {\n if (this.flushTimer) {\n clearTimeout(this.flushTimer);\n this.flushTimer = null;\n }\n }\n}\n","import { LogwellError } from './errors';\nimport type { IngestResponse, LogEntry } from './types';\n\n/**\n * Transport configuration\n */\nexport interface TransportConfig {\n endpoint: string;\n apiKey: string;\n maxRetries: number;\n timeout?: number;\n}\n\n/**\n * Delay helper with exponential backoff\n */\nfunction delay(attempt: number, baseDelay = 100): Promise<void> {\n const ms = Math.min(baseDelay * 2 ** attempt, 10000);\n const jitter = Math.random() * ms * 0.3;\n return new Promise((resolve) => setTimeout(resolve, ms + jitter));\n}\n\n/**\n * HTTP transport for sending logs to Logwell server\n *\n * Features:\n * - Automatic retry with exponential backoff\n * - Error classification with retryable flag\n * - Proper error handling for all HTTP status codes\n */\nexport class HttpTransport {\n private readonly ingestUrl: string;\n\n constructor(private config: TransportConfig) {\n this.ingestUrl = `${config.endpoint}/v1/ingest`;\n }\n\n /**\n * Send logs to the Logwell server\n *\n * @param logs - Array of log entries to send\n * @returns Response with accepted/rejected counts\n * @throws LogwellError on failure after all retries\n */\n async send(logs: LogEntry[]): Promise<IngestResponse> {\n let lastError: LogwellError = new LogwellError(\n 'Max retries exceeded',\n 'NETWORK_ERROR',\n undefined,\n true,\n );\n\n for (let attempt = 0; attempt <= this.config.maxRetries; attempt++) {\n try {\n return await this.doRequest(logs);\n } catch (error) {\n lastError = error as LogwellError;\n\n // Don't retry non-retryable errors\n if (!lastError.retryable) {\n throw lastError;\n }\n\n // Don't delay after the last attempt\n if (attempt < this.config.maxRetries) {\n await delay(attempt);\n }\n }\n }\n\n throw lastError;\n }\n\n private async doRequest(logs: LogEntry[]): Promise<IngestResponse> {\n let response: Response;\n\n try {\n response = await fetch(this.ingestUrl, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${this.config.apiKey}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(logs),\n });\n } catch (error) {\n // Network error (fetch failed)\n throw new LogwellError(\n `Network error: ${(error as Error).message}`,\n 'NETWORK_ERROR',\n undefined,\n true,\n );\n }\n\n // Handle error responses\n if (!response.ok) {\n const errorBody = await this.tryParseError(response);\n throw this.createError(response.status, errorBody);\n }\n\n // Parse successful response\n return (await response.json()) as IngestResponse;\n }\n\n private async tryParseError(response: Response): Promise<string> {\n try {\n const body = await response.json();\n return body.message || body.error || 'Unknown error';\n } catch {\n return `HTTP ${response.status}`;\n }\n }\n\n private createError(status: number, message: string): LogwellError {\n switch (status) {\n case 401:\n return new LogwellError(`Unauthorized: ${message}`, 'UNAUTHORIZED', status, false);\n case 400:\n return new LogwellError(`Validation error: ${message}`, 'VALIDATION_ERROR', status, false);\n case 429:\n return new LogwellError(`Rate limited: ${message}`, 'RATE_LIMITED', status, true);\n default:\n if (status >= 500) {\n return new LogwellError(`Server error: ${message}`, 'SERVER_ERROR', status, true);\n }\n return new LogwellError(`HTTP error ${status}: ${message}`, 'SERVER_ERROR', status, false);\n }\n }\n}\n","import { validateConfig } from './config';\nimport { BatchQueue, type QueueConfig } from './queue';\nimport { HttpTransport } from './transport';\nimport type { IngestResponse, LogEntry, LogwellConfig } from './types';\n\n/**\n * Child logger options\n */\nexport interface ChildLoggerOptions {\n service?: string;\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Internal resolved config with all defaults applied\n */\ninterface ResolvedConfig {\n apiKey: string;\n endpoint: string;\n service?: string;\n batchSize: number;\n flushInterval: number;\n maxQueueSize: number;\n maxRetries: number;\n onError?: (error: Error) => void;\n onFlush?: (count: number) => void;\n}\n\n/**\n * Asserts that config has all required fields after validation\n */\nfunction asResolvedConfig(config: LogwellConfig): ResolvedConfig {\n return config as ResolvedConfig;\n}\n\n/**\n * Main Logwell client class\n *\n * Provides methods for logging at different levels with automatic\n * batching, retry, and queue management.\n *\n * @example\n * ```ts\n * const logger = new Logwell({\n * apiKey: 'lw_xxx',\n * endpoint: 'https://logs.example.com',\n * service: 'my-app',\n * });\n *\n * logger.info('User logged in', { userId: '123' });\n * await logger.shutdown();\n * ```\n */\nexport class Logwell {\n private readonly config: ResolvedConfig;\n private readonly queue: BatchQueue;\n private readonly transport: HttpTransport;\n private readonly parentMetadata?: Record<string, unknown>;\n private stopped = false;\n\n constructor(config: LogwellConfig);\n constructor(config: LogwellConfig, queue: BatchQueue, parentMetadata?: Record<string, unknown>);\n constructor(\n config: LogwellConfig,\n existingQueue?: BatchQueue,\n parentMetadata?: Record<string, unknown>,\n ) {\n // Validate and apply defaults\n this.config = asResolvedConfig(validateConfig(config));\n this.parentMetadata = parentMetadata;\n\n // Create transport\n this.transport = new HttpTransport({\n endpoint: this.config.endpoint,\n apiKey: this.config.apiKey,\n maxRetries: this.config.maxRetries,\n });\n\n // Use existing queue (for child loggers) or create new one\n if (existingQueue) {\n this.queue = existingQueue;\n } else {\n const queueConfig: QueueConfig = {\n batchSize: this.config.batchSize,\n flushInterval: this.config.flushInterval,\n maxQueueSize: this.config.maxQueueSize,\n onError: this.config.onError,\n onFlush: this.config.onFlush,\n };\n this.queue = new BatchQueue((logs) => this.transport.send(logs), queueConfig);\n }\n }\n\n /**\n * Current number of logs waiting in the queue\n */\n get queueSize(): number {\n return this.queue.size;\n }\n\n /**\n * Log a message at the specified level\n */\n log(entry: LogEntry): void {\n if (this.stopped) return;\n\n const fullEntry: LogEntry = {\n ...entry,\n timestamp: entry.timestamp ?? new Date().toISOString(),\n service: entry.service ?? this.config.service,\n metadata: this.mergeMetadata(entry.metadata),\n };\n\n this.queue.add(fullEntry);\n }\n\n /**\n * Log a debug message\n */\n debug(message: string, metadata?: Record<string, unknown>): void {\n this.log({ level: 'debug', message, metadata });\n }\n\n /**\n * Log an info message\n */\n info(message: string, metadata?: Record<string, unknown>): void {\n this.log({ level: 'info', message, metadata });\n }\n\n /**\n * Log a warning message\n */\n warn(message: string, metadata?: Record<string, unknown>): void {\n this.log({ level: 'warn', message, metadata });\n }\n\n /**\n * Log an error message\n */\n error(message: string, metadata?: Record<string, unknown>): void {\n this.log({ level: 'error', message, metadata });\n }\n\n /**\n * Log a fatal error message\n */\n fatal(message: string, metadata?: Record<string, unknown>): void {\n this.log({ level: 'fatal', message, metadata });\n }\n\n /**\n * Flush all queued logs immediately\n *\n * @returns Response from the server, or null if queue was empty\n */\n async flush(): Promise<IngestResponse | null> {\n return this.queue.flush();\n }\n\n /**\n * Flush remaining logs and stop the client\n *\n * Call this before process exit to ensure all logs are sent.\n */\n async shutdown(): Promise<void> {\n this.stopped = true;\n await this.queue.shutdown();\n }\n\n /**\n * Create a child logger with additional context\n *\n * Child loggers share the same queue as the parent,\n * but can have their own service name and default metadata.\n *\n * @example\n * ```ts\n * const requestLogger = logger.child({\n * metadata: { requestId: req.id },\n * });\n * requestLogger.info('Request received');\n * ```\n */\n child(options: ChildLoggerOptions): Logwell {\n const childConfig: LogwellConfig = {\n ...this.config,\n service: options.service ?? this.config.service,\n };\n\n const childMetadata = {\n ...this.parentMetadata,\n ...options.metadata,\n };\n\n return new Logwell(childConfig, this.queue, childMetadata);\n }\n\n private mergeMetadata(\n entryMetadata?: Record<string, unknown>,\n ): Record<string, unknown> | undefined {\n if (!this.parentMetadata && !entryMetadata) {\n return undefined;\n }\n return {\n ...this.parentMetadata,\n ...entryMetadata,\n };\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/errors.ts","../src/config.ts","../src/queue.ts","../src/source-location.ts","../src/transport.ts","../src/client.ts"],"names":["LogwellError","_LogwellError","message","code","statusCode","retryable","DEFAULT_CONFIG","API_KEY_REGEX","validateApiKeyFormat","apiKey","isValidUrl","url","validateConfig","config","BatchQueue","sendBatch","entry","dropped","batch","count","response","error","V8_PAREN_REGEX","V8_BARE_REGEX","SPIDERMONKEY_REGEX","parseStackFrame","frameLine","match","sourceFile","lineStr","lineNumber","captureSourceLocation","skipFrames","stack","lines","firstLine","targetFrameIndex","targetFrame","delay","attempt","baseDelay","ms","jitter","resolve","HttpTransport","logs","lastError","errorBody","body","status","Logwell","_Logwell","existingQueue","parentMetadata","queueConfig","location","fullEntry","metadata","options","childConfig","childMetadata","entryMetadata"],"mappings":"aAoBO,IAAMA,CAAAA,CAAN,MAAMC,CAAAA,SAAqB,KAAM,CAStC,WAAA,CACEC,CAAAA,CACgBC,CAAAA,CACAC,CAAAA,CACAC,EAAqB,KAAA,CACrC,CACA,KAAA,CAAMH,CAAO,EAJG,IAAA,CAAA,IAAA,CAAAC,CAAAA,CACA,IAAA,CAAA,UAAA,CAAAC,CAAAA,CACA,IAAA,CAAA,SAAA,CAAAC,CAAAA,CAGhB,IAAA,CAAK,IAAA,CAAO,eAIa,KAAA,CAGR,iBAAA,GAAoB,IAAA,CAAMJ,CAAY,EACzD,CACF,ECvCO,IAAMK,CAAAA,CAAiB,CAC5B,SAAA,CAAW,EAAA,CACX,aAAA,CAAe,GAAA,CACf,YAAA,CAAc,GAAA,CACd,UAAA,CAAY,CAAA,CACZ,sBAAuB,KACzB,CAAA,CAKaC,CAAAA,CAAgB,wBAAA,CAQtB,SAASC,CAAAA,CAAqBC,CAAAA,CAAyB,CAC5D,OAAI,CAACA,CAAAA,EAAU,OAAOA,CAAAA,EAAW,QAAA,CACxB,KAAA,CAEFF,CAAAA,CAAc,IAAA,CAAKE,CAAM,CAClC,CAQA,SAASC,CAAAA,CAAWC,CAAAA,CAAsB,CACxC,GAAI,CACF,OAAA,IAAI,GAAA,CAAIA,CAAG,CAAA,CACJ,CAAA,CACT,CAAA,KAAQ,CACN,OAAO,MACT,CACF,CASO,SAASC,CAAAA,CAAeC,CAAAA,CAA+C,CAE5E,GAAI,CAACA,CAAAA,CAAO,MAAA,CACV,MAAM,IAAIb,EAAa,oBAAA,CAAsB,gBAAgB,CAAA,CAG/D,GAAI,CAACa,CAAAA,CAAO,QAAA,CACV,MAAM,IAAIb,CAAAA,CAAa,sBAAA,CAAwB,gBAAgB,CAAA,CAIjE,GAAI,CAACQ,CAAAA,CAAqBK,CAAAA,CAAO,MAAM,EACrC,MAAM,IAAIb,CAAAA,CACR,sDAAA,CACA,gBACF,CAAA,CAIF,GAAI,CAACU,EAAWG,CAAAA,CAAO,QAAQ,CAAA,CAC7B,MAAM,IAAIb,CAAAA,CAAa,sBAAA,CAAwB,gBAAgB,CAAA,CAIjE,GAAIa,CAAAA,CAAO,SAAA,GAAc,MAAA,EAAaA,CAAAA,CAAO,SAAA,EAAa,CAAA,CACxD,MAAM,IAAIb,EAAa,4BAAA,CAA8B,gBAAgB,CAAA,CAGvE,GAAIa,EAAO,aAAA,GAAkB,MAAA,EAAaA,CAAAA,CAAO,aAAA,EAAiB,EAChE,MAAM,IAAIb,CAAAA,CAAa,gCAAA,CAAkC,gBAAgB,CAAA,CAG3E,GAAIa,CAAAA,CAAO,eAAiB,MAAA,EAAaA,CAAAA,CAAO,YAAA,EAAgB,CAAA,CAC9D,MAAM,IAAIb,CAAAA,CAAa,+BAAA,CAAiC,gBAAgB,EAG1E,GAAIa,CAAAA,CAAO,UAAA,GAAe,MAAA,EAAaA,CAAAA,CAAO,UAAA,CAAa,CAAA,CACzD,MAAM,IAAIb,CAAAA,CAAa,iCAAA,CAAmC,gBAAgB,CAAA,CAI5E,OAAO,CACL,MAAA,CAAQa,CAAAA,CAAO,MAAA,CACf,SAAUA,CAAAA,CAAO,QAAA,CACjB,OAAA,CAASA,CAAAA,CAAO,OAAA,CAChB,SAAA,CAAWA,CAAAA,CAAO,SAAA,EAAaP,EAAe,SAAA,CAC9C,aAAA,CAAeO,CAAAA,CAAO,aAAA,EAAiBP,EAAe,aAAA,CACtD,YAAA,CAAcO,CAAAA,CAAO,YAAA,EAAgBP,EAAe,YAAA,CACpD,UAAA,CAAYO,CAAAA,CAAO,UAAA,EAAcP,CAAAA,CAAe,UAAA,CAChD,qBAAA,CAAuBO,CAAAA,CAAO,uBAAyBP,CAAAA,CAAe,qBAAA,CACtE,OAAA,CAASO,CAAAA,CAAO,QAChB,OAAA,CAASA,CAAAA,CAAO,OAClB,CACF,CC9EO,IAAMC,CAAAA,CAAN,KAAiB,CAMtB,WAAA,CACUC,CAAAA,CACAF,CAAAA,CACR,CAFQ,eAAAE,CAAAA,CACA,IAAA,CAAA,MAAA,CAAAF,EACP,CARK,MAAoB,EAAC,CACrB,UAAA,CAAmD,IAAA,CACnD,SAAW,KAAA,CACX,OAAA,CAAU,KAAA,CAUlB,IAAI,IAAA,EAAe,CACjB,OAAO,IAAA,CAAK,MAAM,MACpB,CAQA,GAAA,CAAIG,CAAAA,CAAuB,CACzB,GAAI,CAAA,IAAA,CAAK,OAAA,CAKT,CAAA,GAAI,KAAK,KAAA,CAAM,MAAA,EAAU,IAAA,CAAK,MAAA,CAAO,YAAA,CAAc,CACjD,IAAMC,CAAAA,CAAU,KAAK,KAAA,CAAM,KAAA,EAAM,CACjC,IAAA,CAAK,OAAO,OAAA,GACV,IAAIjB,CAAAA,CACF,CAAA,6BAAA,EAAgCiB,GAAS,OAAA,CAAQ,SAAA,CAAU,CAAA,CAAG,EAAE,CAAC,CAAA,GAAA,CAAA,CACjE,gBACF,CACF,EACF,CAEA,IAAA,CAAK,KAAA,CAAM,IAAA,CAAKD,CAAK,EAGjB,CAAC,IAAA,CAAK,UAAA,EAAc,CAAC,KAAK,OAAA,EAC5B,IAAA,CAAK,UAAA,EAAW,CAId,IAAA,CAAK,KAAA,CAAM,MAAA,EAAU,IAAA,CAAK,OAAO,SAAA,EAC9B,IAAA,CAAK,KAAA,GAAM,CAEpB,CAOA,MAAM,KAAA,EAAwC,CAE5C,GAAI,KAAK,QAAA,EAAY,IAAA,CAAK,KAAA,CAAM,MAAA,GAAW,CAAA,CACzC,OAAO,IAAA,CAGT,IAAA,CAAK,SAAW,IAAA,CAChB,IAAA,CAAK,SAAA,EAAU,CAGf,IAAME,CAAAA,CAAQ,IAAA,CAAK,KAAA,CAAM,MAAA,CAAO,CAAC,CAAA,CAC3BC,CAAAA,CAAQD,CAAAA,CAAM,MAAA,CAEpB,GAAI,CACF,IAAME,CAAAA,CAAW,MAAM,IAAA,CAAK,SAAA,CAAUF,CAAK,CAAA,CAC3C,YAAK,MAAA,CAAO,OAAA,GAAUC,CAAK,CAAA,CAGvB,KAAK,KAAA,CAAM,MAAA,CAAS,CAAA,EAAK,CAAC,IAAA,CAAK,OAAA,EACjC,IAAA,CAAK,UAAA,GAGAC,CACT,CAAA,MAASC,CAAAA,CAAO,CAEd,YAAK,KAAA,CAAM,OAAA,CAAQ,GAAGH,CAAK,EAC3B,IAAA,CAAK,MAAA,CAAO,OAAA,GAAUG,CAAc,CAAA,CAG/B,IAAA,CAAK,OAAA,EACR,IAAA,CAAK,YAAW,CAGX,IACT,CAAA,OAAE,CACA,KAAK,QAAA,CAAW,MAClB,CACF,CAKA,MAAM,QAAA,EAA0B,CAC1B,IAAA,CAAK,OAAA,GAIT,IAAA,CAAK,OAAA,CAAU,IAAA,CACf,IAAA,CAAK,WAAU,CAGX,IAAA,CAAK,KAAA,CAAM,MAAA,CAAS,IACtB,IAAA,CAAK,QAAA,CAAW,KAAA,CAChB,MAAM,KAAK,KAAA,EAAM,CAAA,EAErB,CAEQ,UAAA,EAAmB,CACzB,IAAA,CAAK,UAAA,CAAa,UAAA,CAAW,IAAM,CAC5B,IAAA,CAAK,KAAA,GACZ,EAAG,IAAA,CAAK,MAAA,CAAO,aAAa,EAC9B,CAEQ,SAAA,EAAkB,CACpB,IAAA,CAAK,UAAA,GACP,aAAa,IAAA,CAAK,UAAU,CAAA,CAC5B,IAAA,CAAK,WAAa,IAAA,EAEtB,CACF,CAAA,CCxIA,IAAMC,EAAiB,wBAAA,CAUjBC,CAAAA,CAAgB,0BAAA,CAWhBC,CAAAA,CAAqB,yBAepB,SAASC,CAAAA,CAAgBC,CAAAA,CAA+C,CAC7E,GAAI,CAACA,CAAAA,CACH,OAIF,IAAIC,CAAAA,CAAQL,CAAAA,CAAe,IAAA,CAAKI,CAAS,EACzC,GAAIC,CAAAA,CAAO,CACT,IAAMC,EAAaD,CAAAA,CAAM,CAAC,CAAA,CACpBE,CAAAA,CAAUF,CAAAA,CAAM,CAAC,CAAA,CACvB,GAAIC,GAAcC,CAAAA,CAAS,CACzB,IAAMC,CAAAA,CAAa,SAASD,CAAAA,CAAS,EAAE,CAAA,CACvC,GAAI,CAAC,MAAA,CAAO,KAAA,CAAMC,CAAU,CAAA,CAC1B,OAAO,CAAE,UAAA,CAAAF,CAAAA,CAAY,WAAAE,CAAW,CAEpC,CACF,CAIA,GADAH,CAAAA,CAAQJ,CAAAA,CAAc,IAAA,CAAKG,CAAS,EAChCC,CAAAA,CAAO,CACT,IAAMC,CAAAA,CAAaD,CAAAA,CAAM,CAAC,CAAA,CACpBE,CAAAA,CAAUF,EAAM,CAAC,CAAA,CACvB,GAAIC,CAAAA,EAAcC,EAAS,CACzB,IAAMC,CAAAA,CAAa,QAAA,CAASD,EAAS,EAAE,CAAA,CACvC,GAAI,CAAC,MAAA,CAAO,KAAA,CAAMC,CAAU,CAAA,CAC1B,OAAO,CAAE,UAAA,CAAAF,CAAAA,CAAY,UAAA,CAAAE,CAAW,CAEpC,CACF,CAIA,GADAH,EAAQH,CAAAA,CAAmB,IAAA,CAAKE,CAAS,CAAA,CACrCC,CAAAA,CAAO,CACT,IAAMC,CAAAA,CAAaD,EAAM,CAAC,CAAA,CACpBE,CAAAA,CAAUF,CAAAA,CAAM,CAAC,CAAA,CACvB,GAAIC,CAAAA,EAAcC,CAAAA,CAAS,CACzB,IAAMC,CAAAA,CAAa,QAAA,CAASD,CAAAA,CAAS,EAAE,CAAA,CACvC,GAAI,CAAC,OAAO,KAAA,CAAMC,CAAU,CAAA,CAC1B,OAAO,CAAE,UAAA,CAAAF,CAAAA,CAAY,UAAA,CAAAE,CAAW,CAEpC,CACF,CAGF,CAeO,SAASC,EAAsBC,CAAAA,CAAgD,CAEpF,IAAMC,CAAAA,CADQ,IAAI,KAAA,EAAM,CACJ,KAAA,CAEpB,GAAI,CAACA,CAAAA,CACH,OAGF,IAAMC,CAAAA,CAAQD,EAAM,KAAA,CAAM;AAAA,CAAI,EAKxBE,CAAAA,CAAYD,CAAAA,CAAM,CAAC,CAAA,EAAK,GAMxBE,CAAAA,CAAAA,CALiB,CAACD,CAAAA,CAAU,QAAA,CAAS,GAAG,CAAA,EAAK,CAAC,UAAA,CAAW,IAAA,CAAKA,CAAS,CAAA,CAIvC,CAAA,CAAI,CAAA,EACF,CAAA,CAAIH,EAEtCK,CAAAA,CAAcH,CAAAA,CAAME,CAAgB,CAAA,CAC1C,GAAIC,CAAAA,GAAgB,MAAA,CAIpB,OAAOZ,CAAAA,CAAgBY,CAAW,CACpC,CC/HA,SAASC,CAAAA,CAAMC,EAAiBC,CAAAA,CAAY,GAAA,CAAoB,CAC9D,IAAMC,EAAK,IAAA,CAAK,GAAA,CAAID,CAAAA,CAAY,CAAA,EAAKD,EAAS,GAAK,CAAA,CAC7CG,CAAAA,CAAS,IAAA,CAAK,QAAO,CAAID,CAAAA,CAAK,EAAA,CACpC,OAAO,IAAI,OAAA,CAASE,CAAAA,EAAY,UAAA,CAAWA,CAAAA,CAASF,EAAKC,CAAM,CAAC,CAClE,CAUO,IAAME,CAAAA,CAAN,KAAoB,CAGzB,WAAA,CAAoB/B,EAAyB,CAAzB,IAAA,CAAA,MAAA,CAAAA,CAAAA,CAClB,IAAA,CAAK,UAAY,CAAA,EAAGA,CAAAA,CAAO,QAAQ,CAAA,UAAA,EACrC,CAJiB,SAAA,CAajB,MAAM,IAAA,CAAKgC,CAAAA,CAA2C,CACpD,IAAIC,CAAAA,CAA0B,IAAI9C,CAAAA,CAChC,uBACA,eAAA,CACA,MAAA,CACA,IACF,CAAA,CAEA,QAASuC,CAAAA,CAAU,CAAA,CAAGA,CAAAA,EAAW,IAAA,CAAK,OAAO,UAAA,CAAYA,CAAAA,EAAAA,CACvD,GAAI,CACF,OAAO,MAAM,IAAA,CAAK,SAAA,CAAUM,CAAI,CAClC,CAAA,MAASxB,CAAAA,CAAO,CAId,GAHAyB,EAAYzB,CAAAA,CAGR,CAACyB,CAAAA,CAAU,SAAA,CACb,MAAMA,CAAAA,CAIJP,CAAAA,CAAU,IAAA,CAAK,MAAA,CAAO,YACxB,MAAMD,CAAAA,CAAMC,CAAO,EAEvB,CAGF,MAAMO,CACR,CAEA,MAAc,UAAUD,CAAAA,CAA2C,CACjE,IAAIzB,CAAAA,CAEJ,GAAI,CACFA,CAAAA,CAAW,MAAM,KAAA,CAAM,KAAK,SAAA,CAAW,CACrC,MAAA,CAAQ,MAAA,CACR,QAAS,CACP,aAAA,CAAe,UAAU,IAAA,CAAK,MAAA,CAAO,MAAM,CAAA,CAAA,CAC3C,cAAA,CAAgB,kBAClB,CAAA,CACA,KAAM,IAAA,CAAK,SAAA,CAAUyB,CAAI,CAC3B,CAAC,EACH,CAAA,MAASxB,CAAAA,CAAO,CAEd,MAAM,IAAIrB,CAAAA,CACR,CAAA,eAAA,EAAmBqB,CAAAA,CAAgB,OAAO,CAAA,CAAA,CAC1C,eAAA,CACA,MAAA,CACA,IACF,CACF,CAGA,GAAI,CAACD,CAAAA,CAAS,GAAI,CAChB,IAAM2B,CAAAA,CAAY,MAAM,KAAK,aAAA,CAAc3B,CAAQ,CAAA,CACnD,MAAM,KAAK,WAAA,CAAYA,CAAAA,CAAS,MAAA,CAAQ2B,CAAS,CACnD,CAGA,OAAQ,MAAM3B,CAAAA,CAAS,MACzB,CAEA,MAAc,aAAA,CAAcA,EAAqC,CAC/D,GAAI,CACF,IAAM4B,EAAO,MAAM5B,CAAAA,CAAS,IAAA,EAAK,CACjC,OAAO4B,CAAAA,CAAK,OAAA,EAAWA,CAAAA,CAAK,KAAA,EAAS,eACvC,CAAA,KAAQ,CACN,OAAO,CAAA,KAAA,EAAQ5B,EAAS,MAAM,CAAA,CAChC,CACF,CAEQ,WAAA,CAAY6B,EAAgB/C,CAAAA,CAA+B,CACjE,OAAQ+C,CAAAA,EACN,KAAK,GAAA,CACH,OAAO,IAAIjD,EAAa,CAAA,cAAA,EAAiBE,CAAO,CAAA,CAAA,CAAI,cAAA,CAAgB+C,EAAQ,KAAK,CAAA,CACnF,KAAK,GAAA,CACH,OAAO,IAAIjD,CAAAA,CAAa,CAAA,kBAAA,EAAqBE,CAAO,GAAI,kBAAA,CAAoB+C,CAAAA,CAAQ,KAAK,CAAA,CAC3F,KAAK,GAAA,CACH,OAAO,IAAIjD,CAAAA,CAAa,iBAAiBE,CAAO,CAAA,CAAA,CAAI,cAAA,CAAgB+C,CAAAA,CAAQ,IAAI,CAAA,CAClF,QACE,OAAIA,CAAAA,EAAU,IACL,IAAIjD,CAAAA,CAAa,CAAA,cAAA,EAAiBE,CAAO,GAAI,cAAA,CAAgB+C,CAAAA,CAAQ,IAAI,CAAA,CAE3E,IAAIjD,CAAAA,CAAa,CAAA,WAAA,EAAciD,CAAM,CAAA,EAAA,EAAK/C,CAAO,CAAA,CAAA,CAAI,cAAA,CAAgB+C,CAAAA,CAAQ,KAAK,CAC7F,CACF,CACF,CAAA,CC1EO,IAAMC,EAAN,MAAMC,CAAQ,CACF,MAAA,CACA,MACA,SAAA,CACA,cAAA,CACT,QAAU,KAAA,CAIlB,WAAA,CACEtC,EACAuC,CAAAA,CACAC,CAAAA,CACA,CAaA,GAXA,KAAK,MAAA,CAA0BzC,CAAAA,CAAeC,CAAM,CAAA,CACpD,KAAK,cAAA,CAAiBwC,CAAAA,CAGtB,IAAA,CAAK,SAAA,CAAY,IAAIT,CAAAA,CAAc,CACjC,QAAA,CAAU,IAAA,CAAK,OAAO,QAAA,CACtB,MAAA,CAAQ,IAAA,CAAK,MAAA,CAAO,OACpB,UAAA,CAAY,IAAA,CAAK,MAAA,CAAO,UAC1B,CAAC,CAAA,CAGGQ,CAAAA,CACF,IAAA,CAAK,KAAA,CAAQA,OACR,CACL,IAAME,CAAAA,CAA2B,CAC/B,UAAW,IAAA,CAAK,MAAA,CAAO,SAAA,CACvB,aAAA,CAAe,KAAK,MAAA,CAAO,aAAA,CAC3B,YAAA,CAAc,IAAA,CAAK,OAAO,YAAA,CAC1B,OAAA,CAAS,IAAA,CAAK,MAAA,CAAO,QACrB,OAAA,CAAS,IAAA,CAAK,MAAA,CAAO,OACvB,EACA,IAAA,CAAK,KAAA,CAAQ,IAAIxC,CAAAA,CAAY+B,GAAS,IAAA,CAAK,SAAA,CAAU,IAAA,CAAKA,CAAI,EAAGS,CAAW,EAC9E,CACF,CAKA,IAAI,SAAA,EAAoB,CACtB,OAAO,IAAA,CAAK,MAAM,IACpB,CAOQ,OAAA,CAAQtC,CAAAA,CAAiBgB,EAA0B,CACzD,GAAI,IAAA,CAAK,OAAA,CAAS,OAElB,IAAIJ,CAAAA,CACAE,CAAAA,CAEJ,GAAI,KAAK,MAAA,CAAO,qBAAA,CAAuB,CACrC,IAAMyB,EAAWxB,CAAAA,CAAsBC,CAAU,CAAA,CAC7CuB,CAAAA,GACF3B,EAAa2B,CAAAA,CAAS,UAAA,CACtBzB,CAAAA,CAAayB,CAAAA,CAAS,YAE1B,CAEA,IAAMC,CAAAA,CAAsB,CAC1B,GAAGxC,CAAAA,CACH,SAAA,CAAWA,CAAAA,CAAM,SAAA,EAAa,IAAI,IAAA,EAAK,CAAE,WAAA,EAAY,CACrD,QAASA,CAAAA,CAAM,OAAA,EAAW,IAAA,CAAK,MAAA,CAAO,QACtC,QAAA,CAAU,IAAA,CAAK,aAAA,CAAcA,CAAAA,CAAM,QAAQ,CAAA,CAC3C,GAAIY,CAAAA,GAAe,MAAA,EAAa,CAAE,UAAA,CAAAA,CAAW,CAAA,CAC7C,GAAIE,IAAe,MAAA,EAAa,CAAE,UAAA,CAAAA,CAAW,CAC/C,CAAA,CAEA,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI0B,CAAS,EAC1B,CAKA,IAAIxC,CAAAA,CAAuB,CACzB,KAAK,OAAA,CAAQA,CAAAA,CAAO,CAAC,EACvB,CAKA,KAAA,CAAMd,CAAAA,CAAiBuD,CAAAA,CAA0C,CAC/D,KAAK,OAAA,CAAQ,CAAE,KAAA,CAAO,OAAA,CAAS,QAAAvD,CAAAA,CAAS,QAAA,CAAAuD,CAAS,CAAA,CAAG,CAAC,EACvD,CAKA,IAAA,CAAKvD,CAAAA,CAAiBuD,EAA0C,CAC9D,IAAA,CAAK,OAAA,CAAQ,CAAE,MAAO,MAAA,CAAQ,OAAA,CAAAvD,CAAAA,CAAS,QAAA,CAAAuD,CAAS,CAAA,CAAG,CAAC,EACtD,CAKA,KAAKvD,CAAAA,CAAiBuD,CAAAA,CAA0C,CAC9D,IAAA,CAAK,QAAQ,CAAE,KAAA,CAAO,MAAA,CAAQ,OAAA,CAAAvD,EAAS,QAAA,CAAAuD,CAAS,CAAA,CAAG,CAAC,EACtD,CAKA,KAAA,CAAMvD,CAAAA,CAAiBuD,CAAAA,CAA0C,CAC/D,IAAA,CAAK,OAAA,CAAQ,CAAE,KAAA,CAAO,QAAS,OAAA,CAAAvD,CAAAA,CAAS,QAAA,CAAAuD,CAAS,EAAG,CAAC,EACvD,CAKA,KAAA,CAAMvD,EAAiBuD,CAAAA,CAA0C,CAC/D,KAAK,OAAA,CAAQ,CAAE,MAAO,OAAA,CAAS,OAAA,CAAAvD,CAAAA,CAAS,QAAA,CAAAuD,CAAS,CAAA,CAAG,CAAC,EACvD,CAOA,MAAM,KAAA,EAAwC,CAC5C,OAAO,IAAA,CAAK,MAAM,KAAA,EACpB,CAOA,MAAM,UAA0B,CAC9B,IAAA,CAAK,OAAA,CAAU,IAAA,CACf,MAAM,IAAA,CAAK,KAAA,CAAM,QAAA,GACnB,CAgBA,KAAA,CAAMC,CAAAA,CAAsC,CAC1C,IAAMC,EAA6B,CACjC,GAAG,IAAA,CAAK,MAAA,CACR,QAASD,CAAAA,CAAQ,OAAA,EAAW,IAAA,CAAK,MAAA,CAAO,OAC1C,CAAA,CAEME,CAAAA,CAAgB,CACpB,GAAG,KAAK,cAAA,CACR,GAAGF,CAAAA,CAAQ,QACb,EAEA,OAAO,IAAIP,CAAAA,CAAQQ,CAAAA,CAAa,KAAK,KAAA,CAAOC,CAAa,CAC3D,CAEQ,cACNC,CAAAA,CACqC,CACrC,GAAI,EAAA,CAAC,KAAK,cAAA,EAAkB,CAACA,CAAAA,CAAAA,CAG7B,OAAO,CACL,GAAG,IAAA,CAAK,eACR,GAAGA,CACL,CACF,CACF","file":"index.cjs","sourcesContent":["/**\n * Error codes for Logwell SDK errors\n */\nexport type LogwellErrorCode =\n | 'NETWORK_ERROR'\n | 'UNAUTHORIZED'\n | 'VALIDATION_ERROR'\n | 'RATE_LIMITED'\n | 'SERVER_ERROR'\n | 'QUEUE_OVERFLOW'\n | 'INVALID_CONFIG';\n\n/**\n * Custom error class for Logwell SDK errors\n *\n * @example\n * ```ts\n * throw new LogwellError('Invalid API key', 'UNAUTHORIZED', 401, false);\n * ```\n */\nexport class LogwellError extends Error {\n /**\n * Creates a new LogwellError\n *\n * @param message - Human-readable error message\n * @param code - Error code for programmatic handling\n * @param statusCode - HTTP status code if applicable\n * @param retryable - Whether the operation can be retried\n */\n constructor(\n message: string,\n public readonly code: LogwellErrorCode,\n public readonly statusCode?: number,\n public readonly retryable: boolean = false,\n ) {\n super(message);\n this.name = 'LogwellError';\n\n // Maintains proper stack trace for where our error was thrown (V8 only)\n // Use type assertion to avoid global augmentation (required for JSR compatibility)\n const ErrorWithCapture = Error as unknown as {\n captureStackTrace?: (target: object, ctor?: NewableFunction) => void;\n };\n ErrorWithCapture.captureStackTrace?.(this, LogwellError);\n }\n}\n","import { LogwellError } from './errors';\nimport type { LogwellConfig } from './types';\n\n/**\n * Default configuration values\n */\nexport const DEFAULT_CONFIG = {\n batchSize: 50,\n flushInterval: 5000,\n maxQueueSize: 1000,\n maxRetries: 3,\n captureSourceLocation: false,\n} as const;\n\n/**\n * API key format regex: lw_[32 alphanumeric chars including - and _]\n */\nexport const API_KEY_REGEX = /^lw_[A-Za-z0-9_-]{32}$/;\n\n/**\n * Validates API key format\n *\n * @param apiKey - API key to validate\n * @returns true if valid format, false otherwise\n */\nexport function validateApiKeyFormat(apiKey: string): boolean {\n if (!apiKey || typeof apiKey !== 'string') {\n return false;\n }\n return API_KEY_REGEX.test(apiKey);\n}\n\n/**\n * Validates a URL string\n *\n * @param url - URL string to validate\n * @returns true if valid URL, false otherwise\n */\nfunction isValidUrl(url: string): boolean {\n try {\n new URL(url);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Validates configuration and returns merged config with defaults\n *\n * @param config - Partial configuration to validate\n * @returns Complete configuration with defaults applied\n * @throws LogwellError if configuration is invalid\n */\nexport function validateConfig(config: Partial<LogwellConfig>): LogwellConfig {\n // Validate required fields\n if (!config.apiKey) {\n throw new LogwellError('apiKey is required', 'INVALID_CONFIG');\n }\n\n if (!config.endpoint) {\n throw new LogwellError('endpoint is required', 'INVALID_CONFIG');\n }\n\n // Validate API key format\n if (!validateApiKeyFormat(config.apiKey)) {\n throw new LogwellError(\n 'Invalid API key format. Expected: lw_[32 characters]',\n 'INVALID_CONFIG',\n );\n }\n\n // Validate endpoint URL\n if (!isValidUrl(config.endpoint)) {\n throw new LogwellError('Invalid endpoint URL', 'INVALID_CONFIG');\n }\n\n // Validate numeric options\n if (config.batchSize !== undefined && config.batchSize <= 0) {\n throw new LogwellError('batchSize must be positive', 'INVALID_CONFIG');\n }\n\n if (config.flushInterval !== undefined && config.flushInterval <= 0) {\n throw new LogwellError('flushInterval must be positive', 'INVALID_CONFIG');\n }\n\n if (config.maxQueueSize !== undefined && config.maxQueueSize <= 0) {\n throw new LogwellError('maxQueueSize must be positive', 'INVALID_CONFIG');\n }\n\n if (config.maxRetries !== undefined && config.maxRetries < 0) {\n throw new LogwellError('maxRetries must be non-negative', 'INVALID_CONFIG');\n }\n\n // Return merged config with defaults\n return {\n apiKey: config.apiKey,\n endpoint: config.endpoint,\n service: config.service,\n batchSize: config.batchSize ?? DEFAULT_CONFIG.batchSize,\n flushInterval: config.flushInterval ?? DEFAULT_CONFIG.flushInterval,\n maxQueueSize: config.maxQueueSize ?? DEFAULT_CONFIG.maxQueueSize,\n maxRetries: config.maxRetries ?? DEFAULT_CONFIG.maxRetries,\n captureSourceLocation: config.captureSourceLocation ?? DEFAULT_CONFIG.captureSourceLocation,\n onError: config.onError,\n onFlush: config.onFlush,\n };\n}\n","import { LogwellError } from './errors';\nimport type { IngestResponse, LogEntry } from './types';\n\n/**\n * Callback type for sending batched logs\n */\nexport type SendBatchFn = (logs: LogEntry[]) => Promise<IngestResponse>;\n\n/**\n * Queue configuration options\n */\nexport interface QueueConfig {\n batchSize: number;\n flushInterval: number;\n maxQueueSize: number;\n onError?: (error: Error) => void;\n onFlush?: (count: number) => void;\n}\n\n/**\n * Batch queue for buffering and sending logs\n *\n * Features:\n * - Automatic flush on batch size threshold\n * - Automatic flush on time interval\n * - Queue overflow protection (drops oldest)\n * - Re-queue on send failure\n * - Graceful shutdown\n */\nexport class BatchQueue {\n private queue: LogEntry[] = [];\n private flushTimer: ReturnType<typeof setTimeout> | null = null;\n private flushing = false;\n private stopped = false;\n\n constructor(\n private sendBatch: SendBatchFn,\n private config: QueueConfig,\n ) {}\n\n /**\n * Current number of logs in the queue\n */\n get size(): number {\n return this.queue.length;\n }\n\n /**\n * Add a log entry to the queue\n *\n * Triggers flush if batch size is reached.\n * Drops oldest log if queue overflows.\n */\n add(entry: LogEntry): void {\n if (this.stopped) {\n return;\n }\n\n // Handle queue overflow\n if (this.queue.length >= this.config.maxQueueSize) {\n const dropped = this.queue.shift();\n this.config.onError?.(\n new LogwellError(\n `Queue overflow. Dropped log: ${dropped?.message.substring(0, 50)}...`,\n 'QUEUE_OVERFLOW',\n ),\n );\n }\n\n this.queue.push(entry);\n\n // Start timer on first entry\n if (!this.flushTimer && !this.stopped) {\n this.startTimer();\n }\n\n // Flush immediately if batch size reached\n if (this.queue.length >= this.config.batchSize) {\n void this.flush();\n }\n }\n\n /**\n * Flush all queued logs immediately\n *\n * @returns Response from the server, or null if queue was empty\n */\n async flush(): Promise<IngestResponse | null> {\n // Prevent concurrent flushes\n if (this.flushing || this.queue.length === 0) {\n return null;\n }\n\n this.flushing = true;\n this.stopTimer();\n\n // Take current batch\n const batch = this.queue.splice(0);\n const count = batch.length;\n\n try {\n const response = await this.sendBatch(batch);\n this.config.onFlush?.(count);\n\n // Restart timer if more logs remain (added during flush)\n if (this.queue.length > 0 && !this.stopped) {\n this.startTimer();\n }\n\n return response;\n } catch (error) {\n // Re-queue failed logs at the front\n this.queue.unshift(...batch);\n this.config.onError?.(error as Error);\n\n // Restart timer to retry\n if (!this.stopped) {\n this.startTimer();\n }\n\n return null;\n } finally {\n this.flushing = false;\n }\n }\n\n /**\n * Flush remaining logs and stop the queue\n */\n async shutdown(): Promise<void> {\n if (this.stopped) {\n return;\n }\n\n this.stopped = true;\n this.stopTimer();\n\n // Flush all remaining logs\n if (this.queue.length > 0) {\n this.flushing = false; // Reset flushing flag\n await this.flush();\n }\n }\n\n private startTimer(): void {\n this.flushTimer = setTimeout(() => {\n void this.flush();\n }, this.config.flushInterval);\n }\n\n private stopTimer(): void {\n if (this.flushTimer) {\n clearTimeout(this.flushTimer);\n this.flushTimer = null;\n }\n }\n}\n","/**\n * Source location information captured from stack trace\n */\nexport interface SourceLocation {\n sourceFile: string;\n lineNumber: number;\n}\n\n/**\n * V8 format with parentheses (Node.js, Bun, Chrome):\n * Handles all named function variants including:\n * - \" at functionName (/path/to/file.ts:42:15)\"\n * - \" at async functionName (/path/to/file.ts:42:15)\"\n * - \" at new ClassName (/path/to/file.ts:42:15)\"\n * - \" at Object.method [as alias] (/path/to/file.ts:42:15)\"\n *\n * Captures:\n * - Group 1: file path (including protocols like webpack://, file://, http://)\n * - Group 2: line number\n */\nconst V8_PAREN_REGEX = /\\((.+):(\\d+):\\d+\\)\\s*$/;\n\n/**\n * V8 format without parentheses (anonymous functions):\n * \" at /path/to/file.ts:42:15\"\n *\n * Captures:\n * - Group 1: file path\n * - Group 2: line number\n */\nconst V8_BARE_REGEX = /^\\s*at\\s+(.+):(\\d+):\\d+$/;\n\n/**\n * SpiderMonkey/JSC format regex (Firefox, Safari):\n * \"functionName@/path/to/file.ts:42:15\"\n * \"@/path/to/file.ts:42:15\"\n *\n * Captures:\n * - Group 1: file path\n * - Group 2: line number\n */\nconst SPIDERMONKEY_REGEX = /^[^@]*@(.+):(\\d+):\\d+$/;\n\n/**\n * Parses a single stack frame line to extract source location.\n *\n * Supports:\n * - V8 format (Node.js, Bun, Chrome)\n * - SpiderMonkey format (Firefox)\n * - JSC format (Safari)\n * - Windows paths (C:\\, UNC paths)\n * - Bundler paths (webpack://, file://, http://, https://)\n *\n * @param frameLine - Single line from Error.stack\n * @returns Source location or undefined if parsing fails\n */\nexport function parseStackFrame(frameLine: string): SourceLocation | undefined {\n if (!frameLine) {\n return undefined;\n }\n\n // Try V8 format with parentheses first (most common, handles all edge cases)\n let match = V8_PAREN_REGEX.exec(frameLine);\n if (match) {\n const sourceFile = match[1];\n const lineStr = match[2];\n if (sourceFile && lineStr) {\n const lineNumber = parseInt(lineStr, 10);\n if (!Number.isNaN(lineNumber)) {\n return { sourceFile, lineNumber };\n }\n }\n }\n\n // Try V8 format without parentheses (anonymous functions)\n match = V8_BARE_REGEX.exec(frameLine);\n if (match) {\n const sourceFile = match[1];\n const lineStr = match[2];\n if (sourceFile && lineStr) {\n const lineNumber = parseInt(lineStr, 10);\n if (!Number.isNaN(lineNumber)) {\n return { sourceFile, lineNumber };\n }\n }\n }\n\n // Try SpiderMonkey/JSC format (Firefox/Safari)\n match = SPIDERMONKEY_REGEX.exec(frameLine);\n if (match) {\n const sourceFile = match[1];\n const lineStr = match[2];\n if (sourceFile && lineStr) {\n const lineNumber = parseInt(lineStr, 10);\n if (!Number.isNaN(lineNumber)) {\n return { sourceFile, lineNumber };\n }\n }\n }\n\n return undefined;\n}\n\n/**\n * Captures the source location of the caller by parsing the stack trace.\n *\n * @param skipFrames - Number of stack frames to skip (0 = immediate caller)\n * @returns Source location or undefined if capture fails\n *\n * @example\n * // In a logging function that calls this\n * function log(message: string) {\n * const location = captureSourceLocation(1); // Skip log() frame\n * // location.sourceFile = file where log() was called\n * }\n */\nexport function captureSourceLocation(skipFrames: number): SourceLocation | undefined {\n const error = new Error();\n const stack = error.stack;\n\n if (!stack) {\n return undefined;\n }\n\n const lines = stack.split('\\n');\n\n // Detect stack format:\n // - V8 (Node/Bun/Chrome): Has \"Error\" header line, frames start with \"at\"\n // - SpiderMonkey/JSC (Firefox/Safari): No header, frames contain \"@\"\n const firstLine = lines[0] || '';\n const hasErrorHeader = !firstLine.includes('@') && !/^\\s*at\\s/.test(firstLine);\n\n // Calculate target frame index:\n // Skip: header (if present) + captureSourceLocation frame + skipFrames\n const headerOffset = hasErrorHeader ? 1 : 0;\n const targetFrameIndex = headerOffset + 1 + skipFrames;\n\n const targetFrame = lines[targetFrameIndex];\n if (targetFrame === undefined) {\n return undefined;\n }\n\n return parseStackFrame(targetFrame);\n}\n","import { LogwellError } from './errors';\nimport type { IngestResponse, LogEntry } from './types';\n\n/**\n * Transport configuration\n */\nexport interface TransportConfig {\n endpoint: string;\n apiKey: string;\n maxRetries: number;\n timeout?: number;\n}\n\n/**\n * Delay helper with exponential backoff\n */\nfunction delay(attempt: number, baseDelay = 100): Promise<void> {\n const ms = Math.min(baseDelay * 2 ** attempt, 10000);\n const jitter = Math.random() * ms * 0.3;\n return new Promise((resolve) => setTimeout(resolve, ms + jitter));\n}\n\n/**\n * HTTP transport for sending logs to Logwell server\n *\n * Features:\n * - Automatic retry with exponential backoff\n * - Error classification with retryable flag\n * - Proper error handling for all HTTP status codes\n */\nexport class HttpTransport {\n private readonly ingestUrl: string;\n\n constructor(private config: TransportConfig) {\n this.ingestUrl = `${config.endpoint}/v1/ingest`;\n }\n\n /**\n * Send logs to the Logwell server\n *\n * @param logs - Array of log entries to send\n * @returns Response with accepted/rejected counts\n * @throws LogwellError on failure after all retries\n */\n async send(logs: LogEntry[]): Promise<IngestResponse> {\n let lastError: LogwellError = new LogwellError(\n 'Max retries exceeded',\n 'NETWORK_ERROR',\n undefined,\n true,\n );\n\n for (let attempt = 0; attempt <= this.config.maxRetries; attempt++) {\n try {\n return await this.doRequest(logs);\n } catch (error) {\n lastError = error as LogwellError;\n\n // Don't retry non-retryable errors\n if (!lastError.retryable) {\n throw lastError;\n }\n\n // Don't delay after the last attempt\n if (attempt < this.config.maxRetries) {\n await delay(attempt);\n }\n }\n }\n\n throw lastError;\n }\n\n private async doRequest(logs: LogEntry[]): Promise<IngestResponse> {\n let response: Response;\n\n try {\n response = await fetch(this.ingestUrl, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${this.config.apiKey}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(logs),\n });\n } catch (error) {\n // Network error (fetch failed)\n throw new LogwellError(\n `Network error: ${(error as Error).message}`,\n 'NETWORK_ERROR',\n undefined,\n true,\n );\n }\n\n // Handle error responses\n if (!response.ok) {\n const errorBody = await this.tryParseError(response);\n throw this.createError(response.status, errorBody);\n }\n\n // Parse successful response\n return (await response.json()) as IngestResponse;\n }\n\n private async tryParseError(response: Response): Promise<string> {\n try {\n const body = await response.json();\n return body.message || body.error || 'Unknown error';\n } catch {\n return `HTTP ${response.status}`;\n }\n }\n\n private createError(status: number, message: string): LogwellError {\n switch (status) {\n case 401:\n return new LogwellError(`Unauthorized: ${message}`, 'UNAUTHORIZED', status, false);\n case 400:\n return new LogwellError(`Validation error: ${message}`, 'VALIDATION_ERROR', status, false);\n case 429:\n return new LogwellError(`Rate limited: ${message}`, 'RATE_LIMITED', status, true);\n default:\n if (status >= 500) {\n return new LogwellError(`Server error: ${message}`, 'SERVER_ERROR', status, true);\n }\n return new LogwellError(`HTTP error ${status}: ${message}`, 'SERVER_ERROR', status, false);\n }\n }\n}\n","import { validateConfig } from './config';\nimport { BatchQueue, type QueueConfig } from './queue';\nimport { captureSourceLocation } from './source-location';\nimport { HttpTransport } from './transport';\nimport type { IngestResponse, LogEntry, LogwellConfig } from './types';\n\n/**\n * Child logger options\n */\nexport interface ChildLoggerOptions {\n service?: string;\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Internal resolved config with all defaults applied\n */\ninterface ResolvedConfig {\n apiKey: string;\n endpoint: string;\n service?: string;\n batchSize: number;\n flushInterval: number;\n maxQueueSize: number;\n maxRetries: number;\n captureSourceLocation: boolean;\n onError?: (error: Error) => void;\n onFlush?: (count: number) => void;\n}\n\n/**\n * Asserts that config has all required fields after validation\n */\nfunction asResolvedConfig(config: LogwellConfig): ResolvedConfig {\n return config as ResolvedConfig;\n}\n\n/**\n * Main Logwell client class\n *\n * Provides methods for logging at different levels with automatic\n * batching, retry, and queue management.\n *\n * @example\n * ```ts\n * const logger = new Logwell({\n * apiKey: 'lw_xxx',\n * endpoint: 'https://logs.example.com',\n * service: 'my-app',\n * });\n *\n * logger.info('User logged in', { userId: '123' });\n * await logger.shutdown();\n * ```\n */\nexport class Logwell {\n private readonly config: ResolvedConfig;\n private readonly queue: BatchQueue;\n private readonly transport: HttpTransport;\n private readonly parentMetadata?: Record<string, unknown>;\n private stopped = false;\n\n constructor(config: LogwellConfig);\n constructor(config: LogwellConfig, queue: BatchQueue, parentMetadata?: Record<string, unknown>);\n constructor(\n config: LogwellConfig,\n existingQueue?: BatchQueue,\n parentMetadata?: Record<string, unknown>,\n ) {\n // Validate and apply defaults\n this.config = asResolvedConfig(validateConfig(config));\n this.parentMetadata = parentMetadata;\n\n // Create transport\n this.transport = new HttpTransport({\n endpoint: this.config.endpoint,\n apiKey: this.config.apiKey,\n maxRetries: this.config.maxRetries,\n });\n\n // Use existing queue (for child loggers) or create new one\n if (existingQueue) {\n this.queue = existingQueue;\n } else {\n const queueConfig: QueueConfig = {\n batchSize: this.config.batchSize,\n flushInterval: this.config.flushInterval,\n maxQueueSize: this.config.maxQueueSize,\n onError: this.config.onError,\n onFlush: this.config.onFlush,\n };\n this.queue = new BatchQueue((logs) => this.transport.send(logs), queueConfig);\n }\n }\n\n /**\n * Current number of logs waiting in the queue\n */\n get queueSize(): number {\n return this.queue.size;\n }\n\n /**\n * Internal log method with source location capture.\n * @param entry - The log entry\n * @param skipFrames - Number of frames to skip for source location (2 for public methods)\n */\n private _addLog(entry: LogEntry, skipFrames: number): void {\n if (this.stopped) return;\n\n let sourceFile: string | undefined;\n let lineNumber: number | undefined;\n\n if (this.config.captureSourceLocation) {\n const location = captureSourceLocation(skipFrames);\n if (location) {\n sourceFile = location.sourceFile;\n lineNumber = location.lineNumber;\n }\n }\n\n const fullEntry: LogEntry = {\n ...entry,\n timestamp: entry.timestamp ?? new Date().toISOString(),\n service: entry.service ?? this.config.service,\n metadata: this.mergeMetadata(entry.metadata),\n ...(sourceFile !== undefined && { sourceFile }),\n ...(lineNumber !== undefined && { lineNumber }),\n };\n\n this.queue.add(fullEntry);\n }\n\n /**\n * Log a message at the specified level\n */\n log(entry: LogEntry): void {\n this._addLog(entry, 2);\n }\n\n /**\n * Log a debug message\n */\n debug(message: string, metadata?: Record<string, unknown>): void {\n this._addLog({ level: 'debug', message, metadata }, 2);\n }\n\n /**\n * Log an info message\n */\n info(message: string, metadata?: Record<string, unknown>): void {\n this._addLog({ level: 'info', message, metadata }, 2);\n }\n\n /**\n * Log a warning message\n */\n warn(message: string, metadata?: Record<string, unknown>): void {\n this._addLog({ level: 'warn', message, metadata }, 2);\n }\n\n /**\n * Log an error message\n */\n error(message: string, metadata?: Record<string, unknown>): void {\n this._addLog({ level: 'error', message, metadata }, 2);\n }\n\n /**\n * Log a fatal error message\n */\n fatal(message: string, metadata?: Record<string, unknown>): void {\n this._addLog({ level: 'fatal', message, metadata }, 2);\n }\n\n /**\n * Flush all queued logs immediately\n *\n * @returns Response from the server, or null if queue was empty\n */\n async flush(): Promise<IngestResponse | null> {\n return this.queue.flush();\n }\n\n /**\n * Flush remaining logs and stop the client\n *\n * Call this before process exit to ensure all logs are sent.\n */\n async shutdown(): Promise<void> {\n this.stopped = true;\n await this.queue.shutdown();\n }\n\n /**\n * Create a child logger with additional context\n *\n * Child loggers share the same queue as the parent,\n * but can have their own service name and default metadata.\n *\n * @example\n * ```ts\n * const requestLogger = logger.child({\n * metadata: { requestId: req.id },\n * });\n * requestLogger.info('Request received');\n * ```\n */\n child(options: ChildLoggerOptions): Logwell {\n const childConfig: LogwellConfig = {\n ...this.config,\n service: options.service ?? this.config.service,\n };\n\n const childMetadata = {\n ...this.parentMetadata,\n ...options.metadata,\n };\n\n return new Logwell(childConfig, this.queue, childMetadata);\n }\n\n private mergeMetadata(\n entryMetadata?: Record<string, unknown>,\n ): Record<string, unknown> | undefined {\n if (!this.parentMetadata && !entryMetadata) {\n return undefined;\n }\n return {\n ...this.parentMetadata,\n ...entryMetadata,\n };\n }\n}\n"]}
package/dist/index.d.cts CHANGED
@@ -11,6 +11,10 @@ interface LogEntry {
11
11
  timestamp?: string;
12
12
  service?: string;
13
13
  metadata?: Record<string, unknown>;
14
+ /** Source file path where the log was called */
15
+ sourceFile?: string;
16
+ /** Line number in the source file where the log was called */
17
+ lineNumber?: number;
14
18
  }
15
19
  /**
16
20
  * SDK configuration options
@@ -30,6 +34,8 @@ interface LogwellConfig {
30
34
  maxQueueSize?: number;
31
35
  /** Max retry attempts (default: 3) */
32
36
  maxRetries?: number;
37
+ /** Capture source file and line number (default: false). Has performance overhead when enabled. */
38
+ captureSourceLocation?: boolean;
33
39
  /** Called on send failures */
34
40
  onError?: (error: Error) => void;
35
41
  /** Called after successful flush */
@@ -138,6 +144,12 @@ declare class Logwell {
138
144
  * Current number of logs waiting in the queue
139
145
  */
140
146
  get queueSize(): number;
147
+ /**
148
+ * Internal log method with source location capture.
149
+ * @param entry - The log entry
150
+ * @param skipFrames - Number of frames to skip for source location (2 for public methods)
151
+ */
152
+ private _addLog;
141
153
  /**
142
154
  * Log a message at the specified level
143
155
  */
package/dist/index.d.ts CHANGED
@@ -11,6 +11,10 @@ interface LogEntry {
11
11
  timestamp?: string;
12
12
  service?: string;
13
13
  metadata?: Record<string, unknown>;
14
+ /** Source file path where the log was called */
15
+ sourceFile?: string;
16
+ /** Line number in the source file where the log was called */
17
+ lineNumber?: number;
14
18
  }
15
19
  /**
16
20
  * SDK configuration options
@@ -30,6 +34,8 @@ interface LogwellConfig {
30
34
  maxQueueSize?: number;
31
35
  /** Max retry attempts (default: 3) */
32
36
  maxRetries?: number;
37
+ /** Capture source file and line number (default: false). Has performance overhead when enabled. */
38
+ captureSourceLocation?: boolean;
33
39
  /** Called on send failures */
34
40
  onError?: (error: Error) => void;
35
41
  /** Called after successful flush */
@@ -138,6 +144,12 @@ declare class Logwell {
138
144
  * Current number of logs waiting in the queue
139
145
  */
140
146
  get queueSize(): number;
147
+ /**
148
+ * Internal log method with source location capture.
149
+ * @param entry - The log entry
150
+ * @param skipFrames - Number of frames to skip for source location (2 for public methods)
151
+ */
152
+ private _addLog;
141
153
  /**
142
154
  * Log a message at the specified level
143
155
  */
package/dist/index.js CHANGED
@@ -1,2 +1,3 @@
1
- var i=class r extends Error{constructor(t,n,o,h=false){super(t);this.code=n;this.statusCode=o;this.retryable=h;this.name="LogwellError",Error.captureStackTrace?.(this,r);}};var s={batchSize:50,flushInterval:5e3,maxQueueSize:1e3,maxRetries:3},d=/^lw_[A-Za-z0-9_-]{32}$/;function f(r){return !r||typeof r!="string"?false:d.test(r)}function g(r){try{return new URL(r),!0}catch{return false}}function p(r){if(!r.apiKey)throw new i("apiKey is required","INVALID_CONFIG");if(!r.endpoint)throw new i("endpoint is required","INVALID_CONFIG");if(!f(r.apiKey))throw new i("Invalid API key format. Expected: lw_[32 characters]","INVALID_CONFIG");if(!g(r.endpoint))throw new i("Invalid endpoint URL","INVALID_CONFIG");if(r.batchSize!==void 0&&r.batchSize<=0)throw new i("batchSize must be positive","INVALID_CONFIG");if(r.flushInterval!==void 0&&r.flushInterval<=0)throw new i("flushInterval must be positive","INVALID_CONFIG");if(r.maxQueueSize!==void 0&&r.maxQueueSize<=0)throw new i("maxQueueSize must be positive","INVALID_CONFIG");if(r.maxRetries!==void 0&&r.maxRetries<0)throw new i("maxRetries must be non-negative","INVALID_CONFIG");return {apiKey:r.apiKey,endpoint:r.endpoint,service:r.service,batchSize:r.batchSize??s.batchSize,flushInterval:r.flushInterval??s.flushInterval,maxQueueSize:r.maxQueueSize??s.maxQueueSize,maxRetries:r.maxRetries??s.maxRetries,onError:r.onError,onFlush:r.onFlush}}var a=class{constructor(e,t){this.sendBatch=e;this.config=t;}queue=[];flushTimer=null;flushing=false;stopped=false;get size(){return this.queue.length}add(e){if(!this.stopped){if(this.queue.length>=this.config.maxQueueSize){let t=this.queue.shift();this.config.onError?.(new i(`Queue overflow. Dropped log: ${t?.message.substring(0,50)}...`,"QUEUE_OVERFLOW"));}this.queue.push(e),!this.flushTimer&&!this.stopped&&this.startTimer(),this.queue.length>=this.config.batchSize&&this.flush();}}async flush(){if(this.flushing||this.queue.length===0)return null;this.flushing=true,this.stopTimer();let e=this.queue.splice(0),t=e.length;try{let n=await this.sendBatch(e);return this.config.onFlush?.(t),this.queue.length>0&&!this.stopped&&this.startTimer(),n}catch(n){return this.queue.unshift(...e),this.config.onError?.(n),this.stopped||this.startTimer(),null}finally{this.flushing=false;}}async shutdown(){this.stopped||(this.stopped=true,this.stopTimer(),this.queue.length>0&&(this.flushing=false,await this.flush()));}startTimer(){this.flushTimer=setTimeout(()=>{this.flush();},this.config.flushInterval);}stopTimer(){this.flushTimer&&(clearTimeout(this.flushTimer),this.flushTimer=null);}};function c(r,e=100){let t=Math.min(e*2**r,1e4),n=Math.random()*t*.3;return new Promise(o=>setTimeout(o,t+n))}var u=class{constructor(e){this.config=e;this.ingestUrl=`${e.endpoint}/v1/ingest`;}ingestUrl;async send(e){let t=new i("Max retries exceeded","NETWORK_ERROR",void 0,true);for(let n=0;n<=this.config.maxRetries;n++)try{return await this.doRequest(e)}catch(o){if(t=o,!t.retryable)throw t;n<this.config.maxRetries&&await c(n);}throw t}async doRequest(e){let t;try{t=await fetch(this.ingestUrl,{method:"POST",headers:{Authorization:`Bearer ${this.config.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify(e)});}catch(n){throw new i(`Network error: ${n.message}`,"NETWORK_ERROR",void 0,true)}if(!t.ok){let n=await this.tryParseError(t);throw this.createError(t.status,n)}return await t.json()}async tryParseError(e){try{let t=await e.json();return t.message||t.error||"Unknown error"}catch{return `HTTP ${e.status}`}}createError(e,t){switch(e){case 401:return new i(`Unauthorized: ${t}`,"UNAUTHORIZED",e,false);case 400:return new i(`Validation error: ${t}`,"VALIDATION_ERROR",e,false);case 429:return new i(`Rate limited: ${t}`,"RATE_LIMITED",e,true);default:return e>=500?new i(`Server error: ${t}`,"SERVER_ERROR",e,true):new i(`HTTP error ${e}: ${t}`,"SERVER_ERROR",e,false)}}};var l=class r{config;queue;transport;parentMetadata;stopped=false;constructor(e,t,n){if(this.config=p(e),this.parentMetadata=n,this.transport=new u({endpoint:this.config.endpoint,apiKey:this.config.apiKey,maxRetries:this.config.maxRetries}),t)this.queue=t;else {let o={batchSize:this.config.batchSize,flushInterval:this.config.flushInterval,maxQueueSize:this.config.maxQueueSize,onError:this.config.onError,onFlush:this.config.onFlush};this.queue=new a(h=>this.transport.send(h),o);}}get queueSize(){return this.queue.size}log(e){if(this.stopped)return;let t={...e,timestamp:e.timestamp??new Date().toISOString(),service:e.service??this.config.service,metadata:this.mergeMetadata(e.metadata)};this.queue.add(t);}debug(e,t){this.log({level:"debug",message:e,metadata:t});}info(e,t){this.log({level:"info",message:e,metadata:t});}warn(e,t){this.log({level:"warn",message:e,metadata:t});}error(e,t){this.log({level:"error",message:e,metadata:t});}fatal(e,t){this.log({level:"fatal",message:e,metadata:t});}async flush(){return this.queue.flush()}async shutdown(){this.stopped=true,await this.queue.shutdown();}child(e){let t={...this.config,service:e.service??this.config.service},n={...this.parentMetadata,...e.metadata};return new r(t,this.queue,n)}mergeMetadata(e){if(!(!this.parentMetadata&&!e))return {...this.parentMetadata,...e}}};export{l as Logwell,i as LogwellError};//# sourceMappingURL=index.js.map
1
+ var o=class r extends Error{constructor(t,n,i,s=false){super(t);this.code=n;this.statusCode=i;this.retryable=s;this.name="LogwellError",Error.captureStackTrace?.(this,r);}};var u={batchSize:50,flushInterval:5e3,maxQueueSize:1e3,maxRetries:3,captureSourceLocation:false},m=/^lw_[A-Za-z0-9_-]{32}$/;function R(r){return !r||typeof r!="string"?false:m.test(r)}function E(r){try{return new URL(r),!0}catch{return false}}function p(r){if(!r.apiKey)throw new o("apiKey is required","INVALID_CONFIG");if(!r.endpoint)throw new o("endpoint is required","INVALID_CONFIG");if(!R(r.apiKey))throw new o("Invalid API key format. Expected: lw_[32 characters]","INVALID_CONFIG");if(!E(r.endpoint))throw new o("Invalid endpoint URL","INVALID_CONFIG");if(r.batchSize!==void 0&&r.batchSize<=0)throw new o("batchSize must be positive","INVALID_CONFIG");if(r.flushInterval!==void 0&&r.flushInterval<=0)throw new o("flushInterval must be positive","INVALID_CONFIG");if(r.maxQueueSize!==void 0&&r.maxQueueSize<=0)throw new o("maxQueueSize must be positive","INVALID_CONFIG");if(r.maxRetries!==void 0&&r.maxRetries<0)throw new o("maxRetries must be non-negative","INVALID_CONFIG");return {apiKey:r.apiKey,endpoint:r.endpoint,service:r.service,batchSize:r.batchSize??u.batchSize,flushInterval:r.flushInterval??u.flushInterval,maxQueueSize:r.maxQueueSize??u.maxQueueSize,maxRetries:r.maxRetries??u.maxRetries,captureSourceLocation:r.captureSourceLocation??u.captureSourceLocation,onError:r.onError,onFlush:r.onFlush}}var l=class{constructor(e,t){this.sendBatch=e;this.config=t;}queue=[];flushTimer=null;flushing=false;stopped=false;get size(){return this.queue.length}add(e){if(!this.stopped){if(this.queue.length>=this.config.maxQueueSize){let t=this.queue.shift();this.config.onError?.(new o(`Queue overflow. Dropped log: ${t?.message.substring(0,50)}...`,"QUEUE_OVERFLOW"));}this.queue.push(e),!this.flushTimer&&!this.stopped&&this.startTimer(),this.queue.length>=this.config.batchSize&&this.flush();}}async flush(){if(this.flushing||this.queue.length===0)return null;this.flushing=true,this.stopTimer();let e=this.queue.splice(0),t=e.length;try{let n=await this.sendBatch(e);return this.config.onFlush?.(t),this.queue.length>0&&!this.stopped&&this.startTimer(),n}catch(n){return this.queue.unshift(...e),this.config.onError?.(n),this.stopped||this.startTimer(),null}finally{this.flushing=false;}}async shutdown(){this.stopped||(this.stopped=true,this.stopTimer(),this.queue.length>0&&(this.flushing=false,await this.flush()));}startTimer(){this.flushTimer=setTimeout(()=>{this.flush();},this.config.flushInterval);}stopTimer(){this.flushTimer&&(clearTimeout(this.flushTimer),this.flushTimer=null);}};var w=/\((.+):(\d+):\d+\)\s*$/,v=/^\s*at\s+(.+):(\d+):\d+$/,L=/^[^@]*@(.+):(\d+):\d+$/;function I(r){if(!r)return;let e=w.exec(r);if(e){let t=e[1],n=e[2];if(t&&n){let i=parseInt(n,10);if(!Number.isNaN(i))return {sourceFile:t,lineNumber:i}}}if(e=v.exec(r),e){let t=e[1],n=e[2];if(t&&n){let i=parseInt(n,10);if(!Number.isNaN(i))return {sourceFile:t,lineNumber:i}}}if(e=L.exec(r),e){let t=e[1],n=e[2];if(t&&n){let i=parseInt(n,10);if(!Number.isNaN(i))return {sourceFile:t,lineNumber:i}}}}function f(r){let t=new Error().stack;if(!t)return;let n=t.split(`
2
+ `),i=n[0]||"",g=(!i.includes("@")&&!/^\s*at\s/.test(i)?1:0)+1+r,h=n[g];if(h!==void 0)return I(h)}function y(r,e=100){let t=Math.min(e*2**r,1e4),n=Math.random()*t*.3;return new Promise(i=>setTimeout(i,t+n))}var c=class{constructor(e){this.config=e;this.ingestUrl=`${e.endpoint}/v1/ingest`;}ingestUrl;async send(e){let t=new o("Max retries exceeded","NETWORK_ERROR",void 0,true);for(let n=0;n<=this.config.maxRetries;n++)try{return await this.doRequest(e)}catch(i){if(t=i,!t.retryable)throw t;n<this.config.maxRetries&&await y(n);}throw t}async doRequest(e){let t;try{t=await fetch(this.ingestUrl,{method:"POST",headers:{Authorization:`Bearer ${this.config.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify(e)});}catch(n){throw new o(`Network error: ${n.message}`,"NETWORK_ERROR",void 0,true)}if(!t.ok){let n=await this.tryParseError(t);throw this.createError(t.status,n)}return await t.json()}async tryParseError(e){try{let t=await e.json();return t.message||t.error||"Unknown error"}catch{return `HTTP ${e.status}`}}createError(e,t){switch(e){case 401:return new o(`Unauthorized: ${t}`,"UNAUTHORIZED",e,false);case 400:return new o(`Validation error: ${t}`,"VALIDATION_ERROR",e,false);case 429:return new o(`Rate limited: ${t}`,"RATE_LIMITED",e,true);default:return e>=500?new o(`Server error: ${t}`,"SERVER_ERROR",e,true):new o(`HTTP error ${e}: ${t}`,"SERVER_ERROR",e,false)}}};var d=class r{config;queue;transport;parentMetadata;stopped=false;constructor(e,t,n){if(this.config=p(e),this.parentMetadata=n,this.transport=new c({endpoint:this.config.endpoint,apiKey:this.config.apiKey,maxRetries:this.config.maxRetries}),t)this.queue=t;else {let i={batchSize:this.config.batchSize,flushInterval:this.config.flushInterval,maxQueueSize:this.config.maxQueueSize,onError:this.config.onError,onFlush:this.config.onFlush};this.queue=new l(s=>this.transport.send(s),i);}}get queueSize(){return this.queue.size}_addLog(e,t){if(this.stopped)return;let n,i;if(this.config.captureSourceLocation){let a=f(t);a&&(n=a.sourceFile,i=a.lineNumber);}let s={...e,timestamp:e.timestamp??new Date().toISOString(),service:e.service??this.config.service,metadata:this.mergeMetadata(e.metadata),...n!==void 0&&{sourceFile:n},...i!==void 0&&{lineNumber:i}};this.queue.add(s);}log(e){this._addLog(e,2);}debug(e,t){this._addLog({level:"debug",message:e,metadata:t},2);}info(e,t){this._addLog({level:"info",message:e,metadata:t},2);}warn(e,t){this._addLog({level:"warn",message:e,metadata:t},2);}error(e,t){this._addLog({level:"error",message:e,metadata:t},2);}fatal(e,t){this._addLog({level:"fatal",message:e,metadata:t},2);}async flush(){return this.queue.flush()}async shutdown(){this.stopped=true,await this.queue.shutdown();}child(e){let t={...this.config,service:e.service??this.config.service},n={...this.parentMetadata,...e.metadata};return new r(t,this.queue,n)}mergeMetadata(e){if(!(!this.parentMetadata&&!e))return {...this.parentMetadata,...e}}};export{d as Logwell,o as LogwellError};//# sourceMappingURL=index.js.map
2
3
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts","../src/config.ts","../src/queue.ts","../src/transport.ts","../src/client.ts"],"names":["LogwellError","_LogwellError","message","code","statusCode","retryable","DEFAULT_CONFIG","API_KEY_REGEX","validateApiKeyFormat","apiKey","isValidUrl","url","validateConfig","config","BatchQueue","sendBatch","entry","dropped","batch","count","response","error","delay","attempt","baseDelay","ms","jitter","resolve","HttpTransport","logs","lastError","errorBody","body","status","Logwell","_Logwell","existingQueue","parentMetadata","queueConfig","fullEntry","metadata","options","childConfig","childMetadata","entryMetadata"],"mappings":"AAoBO,IAAMA,CAAAA,CAAN,MAAMC,CAAAA,SAAqB,KAAM,CAStC,WAAA,CACEC,CAAAA,CACgBC,CAAAA,CACAC,CAAAA,CACAC,CAAAA,CAAqB,KAAA,CACrC,CACA,KAAA,CAAMH,CAAO,CAAA,CAJG,IAAA,CAAA,IAAA,CAAAC,CAAAA,CACA,IAAA,CAAA,UAAA,CAAAC,CAAAA,CACA,IAAA,CAAA,SAAA,CAAAC,CAAAA,CAGhB,IAAA,CAAK,IAAA,CAAO,cAAA,CAIa,KAAA,CAGR,iBAAA,GAAoB,IAAA,CAAMJ,CAAY,EACzD,CACF,ECvCO,IAAMK,CAAAA,CAAiB,CAC5B,SAAA,CAAW,EAAA,CACX,aAAA,CAAe,GAAA,CACf,YAAA,CAAc,GAAA,CACd,UAAA,CAAY,CACd,CAAA,CAKaC,CAAAA,CAAgB,wBAAA,CAQtB,SAASC,CAAAA,CAAqBC,CAAAA,CAAyB,CAC5D,OAAI,CAACA,CAAAA,EAAU,OAAOA,CAAAA,EAAW,QAAA,CACxB,KAAA,CAEFF,CAAAA,CAAc,IAAA,CAAKE,CAAM,CAClC,CAQA,SAASC,CAAAA,CAAWC,CAAAA,CAAsB,CACxC,GAAI,CACF,OAAA,IAAI,GAAA,CAAIA,CAAG,CAAA,CACJ,CAAA,CACT,CAAA,KAAQ,CACN,OAAO,MACT,CACF,CASO,SAASC,CAAAA,CAAeC,CAAAA,CAA+C,CAE5E,GAAI,CAACA,CAAAA,CAAO,MAAA,CACV,MAAM,IAAIb,CAAAA,CAAa,oBAAA,CAAsB,gBAAgB,CAAA,CAG/D,GAAI,CAACa,CAAAA,CAAO,QAAA,CACV,MAAM,IAAIb,CAAAA,CAAa,sBAAA,CAAwB,gBAAgB,CAAA,CAIjE,GAAI,CAACQ,CAAAA,CAAqBK,CAAAA,CAAO,MAAM,CAAA,CACrC,MAAM,IAAIb,CAAAA,CACR,sDAAA,CACA,gBACF,CAAA,CAIF,GAAI,CAACU,CAAAA,CAAWG,CAAAA,CAAO,QAAQ,CAAA,CAC7B,MAAM,IAAIb,CAAAA,CAAa,uBAAwB,gBAAgB,CAAA,CAIjE,GAAIa,CAAAA,CAAO,SAAA,GAAc,MAAA,EAAaA,CAAAA,CAAO,SAAA,EAAa,CAAA,CACxD,MAAM,IAAIb,CAAAA,CAAa,4BAAA,CAA8B,gBAAgB,CAAA,CAGvE,GAAIa,CAAAA,CAAO,aAAA,GAAkB,MAAA,EAAaA,CAAAA,CAAO,aAAA,EAAiB,CAAA,CAChE,MAAM,IAAIb,CAAAA,CAAa,gCAAA,CAAkC,gBAAgB,CAAA,CAG3E,GAAIa,CAAAA,CAAO,eAAiB,MAAA,EAAaA,CAAAA,CAAO,YAAA,EAAgB,CAAA,CAC9D,MAAM,IAAIb,CAAAA,CAAa,+BAAA,CAAiC,gBAAgB,CAAA,CAG1E,GAAIa,CAAAA,CAAO,UAAA,GAAe,MAAA,EAAaA,CAAAA,CAAO,UAAA,CAAa,CAAA,CACzD,MAAM,IAAIb,CAAAA,CAAa,iCAAA,CAAmC,gBAAgB,CAAA,CAI5E,OAAO,CACL,MAAA,CAAQa,CAAAA,CAAO,MAAA,CACf,QAAA,CAAUA,CAAAA,CAAO,SACjB,OAAA,CAASA,CAAAA,CAAO,OAAA,CAChB,SAAA,CAAWA,CAAAA,CAAO,SAAA,EAAaP,CAAAA,CAAe,SAAA,CAC9C,aAAA,CAAeO,CAAAA,CAAO,aAAA,EAAiBP,CAAAA,CAAe,aAAA,CACtD,YAAA,CAAcO,CAAAA,CAAO,YAAA,EAAgBP,CAAAA,CAAe,YAAA,CACpD,UAAA,CAAYO,CAAAA,CAAO,UAAA,EAAcP,CAAAA,CAAe,UAAA,CAChD,OAAA,CAASO,CAAAA,CAAO,OAAA,CAChB,OAAA,CAASA,CAAAA,CAAO,OAClB,CACF,CC5EO,IAAMC,CAAAA,CAAN,KAAiB,CAMtB,WAAA,CACUC,CAAAA,CACAF,CAAAA,CACR,CAFQ,IAAA,CAAA,SAAA,CAAAE,CAAAA,CACA,IAAA,CAAA,MAAA,CAAAF,EACP,CARK,KAAA,CAAoB,EAAC,CACrB,UAAA,CAAmD,IAAA,CACnD,QAAA,CAAW,KAAA,CACX,OAAA,CAAU,KAAA,CAUlB,IAAI,IAAA,EAAe,CACjB,OAAO,IAAA,CAAK,KAAA,CAAM,MACpB,CAQA,GAAA,CAAIG,EAAuB,CACzB,GAAI,CAAA,IAAA,CAAK,OAAA,CAKT,CAAA,GAAI,IAAA,CAAK,KAAA,CAAM,MAAA,EAAU,IAAA,CAAK,MAAA,CAAO,YAAA,CAAc,CACjD,IAAMC,CAAAA,CAAU,IAAA,CAAK,KAAA,CAAM,KAAA,EAAM,CACjC,IAAA,CAAK,MAAA,CAAO,OAAA,GACV,IAAIjB,CAAAA,CACF,CAAA,6BAAA,EAAgCiB,CAAAA,EAAS,OAAA,CAAQ,SAAA,CAAU,CAAA,CAAG,EAAE,CAAC,MACjE,gBACF,CACF,EACF,CAEA,IAAA,CAAK,KAAA,CAAM,IAAA,CAAKD,CAAK,CAAA,CAGjB,CAAC,IAAA,CAAK,UAAA,EAAc,CAAC,IAAA,CAAK,OAAA,EAC5B,IAAA,CAAK,UAAA,EAAW,CAId,IAAA,CAAK,KAAA,CAAM,MAAA,EAAU,IAAA,CAAK,MAAA,CAAO,SAAA,EAC9B,IAAA,CAAK,KAAA,GAAM,CAEpB,CAOA,MAAM,KAAA,EAAwC,CAE5C,GAAI,IAAA,CAAK,QAAA,EAAY,IAAA,CAAK,KAAA,CAAM,MAAA,GAAW,CAAA,CACzC,OAAO,IAAA,CAGT,IAAA,CAAK,QAAA,CAAW,IAAA,CAChB,IAAA,CAAK,SAAA,EAAU,CAGf,IAAME,CAAAA,CAAQ,IAAA,CAAK,KAAA,CAAM,MAAA,CAAO,CAAC,CAAA,CAC3BC,CAAAA,CAAQD,CAAAA,CAAM,MAAA,CAEpB,GAAI,CACF,IAAME,CAAAA,CAAW,MAAM,IAAA,CAAK,SAAA,CAAUF,CAAK,CAAA,CAC3C,OAAA,IAAA,CAAK,MAAA,CAAO,OAAA,GAAUC,CAAK,CAAA,CAGvB,IAAA,CAAK,KAAA,CAAM,MAAA,CAAS,CAAA,EAAK,CAAC,IAAA,CAAK,OAAA,EACjC,IAAA,CAAK,UAAA,EAAW,CAGXC,CACT,CAAA,MAASC,CAAAA,CAAO,CAEd,OAAA,IAAA,CAAK,KAAA,CAAM,OAAA,CAAQ,GAAGH,CAAK,CAAA,CAC3B,IAAA,CAAK,MAAA,CAAO,OAAA,GAAUG,CAAc,CAAA,CAG/B,IAAA,CAAK,OAAA,EACR,IAAA,CAAK,UAAA,EAAW,CAGX,IACT,CAAA,OAAE,CACA,IAAA,CAAK,QAAA,CAAW,MAClB,CACF,CAKA,MAAM,QAAA,EAA0B,CAC1B,IAAA,CAAK,OAAA,GAIT,IAAA,CAAK,OAAA,CAAU,IAAA,CACf,IAAA,CAAK,SAAA,EAAU,CAGX,IAAA,CAAK,KAAA,CAAM,MAAA,CAAS,CAAA,GACtB,IAAA,CAAK,QAAA,CAAW,KAAA,CAChB,MAAM,IAAA,CAAK,KAAA,EAAM,CAAA,EAErB,CAEQ,UAAA,EAAmB,CACzB,IAAA,CAAK,UAAA,CAAa,UAAA,CAAW,IAAM,CAC5B,IAAA,CAAK,KAAA,GACZ,CAAA,CAAG,IAAA,CAAK,MAAA,CAAO,aAAa,EAC9B,CAEQ,SAAA,EAAkB,CACpB,IAAA,CAAK,UAAA,GACP,YAAA,CAAa,IAAA,CAAK,UAAU,CAAA,CAC5B,IAAA,CAAK,WAAa,IAAA,EAEtB,CACF,CAAA,CC5IA,SAASC,CAAAA,CAAMC,CAAAA,CAAiBC,CAAAA,CAAY,GAAA,CAAoB,CAC9D,IAAMC,CAAAA,CAAK,IAAA,CAAK,GAAA,CAAID,CAAAA,CAAY,CAAA,EAAKD,CAAAA,CAAS,GAAK,CAAA,CAC7CG,CAAAA,CAAS,IAAA,CAAK,MAAA,EAAO,CAAID,CAAAA,CAAK,EAAA,CACpC,OAAO,IAAI,OAAA,CAASE,CAAAA,EAAY,UAAA,CAAWA,CAAAA,CAASF,CAAAA,CAAKC,CAAM,CAAC,CAClE,CAUO,IAAME,CAAAA,CAAN,KAAoB,CAGzB,WAAA,CAAoBf,CAAAA,CAAyB,CAAzB,IAAA,CAAA,MAAA,CAAAA,CAAAA,CAClB,IAAA,CAAK,SAAA,CAAY,CAAA,EAAGA,CAAAA,CAAO,QAAQ,CAAA,UAAA,EACrC,CAJiB,SAAA,CAajB,MAAM,IAAA,CAAKgB,CAAAA,CAA2C,CACpD,IAAIC,CAAAA,CAA0B,IAAI9B,CAAAA,CAChC,sBAAA,CACA,eAAA,CACA,OACA,IACF,CAAA,CAEA,IAAA,IAASuB,CAAAA,CAAU,CAAA,CAAGA,CAAAA,EAAW,IAAA,CAAK,MAAA,CAAO,UAAA,CAAYA,CAAAA,EAAAA,CACvD,GAAI,CACF,OAAO,MAAM,IAAA,CAAK,SAAA,CAAUM,CAAI,CAClC,CAAA,MAASR,CAAAA,CAAO,CAId,GAHAS,CAAAA,CAAYT,CAAAA,CAGR,CAACS,CAAAA,CAAU,SAAA,CACb,MAAMA,CAAAA,CAIJP,CAAAA,CAAU,KAAK,MAAA,CAAO,UAAA,EACxB,MAAMD,CAAAA,CAAMC,CAAO,EAEvB,CAGF,MAAMO,CACR,CAEA,MAAc,SAAA,CAAUD,CAAAA,CAA2C,CACjE,IAAIT,CAAAA,CAEJ,GAAI,CACFA,CAAAA,CAAW,MAAM,KAAA,CAAM,IAAA,CAAK,SAAA,CAAW,CACrC,MAAA,CAAQ,MAAA,CACR,OAAA,CAAS,CACP,aAAA,CAAe,CAAA,OAAA,EAAU,KAAK,MAAA,CAAO,MAAM,CAAA,CAAA,CAC3C,cAAA,CAAgB,kBAClB,CAAA,CACA,IAAA,CAAM,IAAA,CAAK,SAAA,CAAUS,CAAI,CAC3B,CAAC,EACH,CAAA,MAASR,CAAAA,CAAO,CAEd,MAAM,IAAIrB,CAAAA,CACR,CAAA,eAAA,EAAmBqB,CAAAA,CAAgB,OAAO,CAAA,CAAA,CAC1C,eAAA,CACA,MAAA,CACA,IACF,CACF,CAGA,GAAI,CAACD,CAAAA,CAAS,EAAA,CAAI,CAChB,IAAMW,CAAAA,CAAY,MAAM,IAAA,CAAK,aAAA,CAAcX,CAAQ,CAAA,CACnD,MAAM,IAAA,CAAK,WAAA,CAAYA,CAAAA,CAAS,MAAA,CAAQW,CAAS,CACnD,CAGA,OAAQ,MAAMX,CAAAA,CAAS,IAAA,EACzB,CAEA,MAAc,aAAA,CAAcA,CAAAA,CAAqC,CAC/D,GAAI,CACF,IAAMY,CAAAA,CAAO,MAAMZ,CAAAA,CAAS,IAAA,EAAK,CACjC,OAAOY,CAAAA,CAAK,OAAA,EAAWA,CAAAA,CAAK,KAAA,EAAS,eACvC,CAAA,KAAQ,CACN,OAAO,CAAA,KAAA,EAAQZ,CAAAA,CAAS,MAAM,CAAA,CAChC,CACF,CAEQ,WAAA,CAAYa,CAAAA,CAAgB/B,CAAAA,CAA+B,CACjE,OAAQ+B,CAAAA,EACN,KAAK,GAAA,CACH,OAAO,IAAIjC,CAAAA,CAAa,CAAA,cAAA,EAAiBE,CAAO,CAAA,CAAA,CAAI,cAAA,CAAgB+B,CAAAA,CAAQ,KAAK,CAAA,CACnF,KAAK,GAAA,CACH,OAAO,IAAIjC,CAAAA,CAAa,CAAA,kBAAA,EAAqBE,CAAO,CAAA,CAAA,CAAI,kBAAA,CAAoB+B,CAAAA,CAAQ,KAAK,CAAA,CAC3F,KAAK,GAAA,CACH,OAAO,IAAIjC,CAAAA,CAAa,CAAA,cAAA,EAAiBE,CAAO,CAAA,CAAA,CAAI,cAAA,CAAgB+B,CAAAA,CAAQ,IAAI,CAAA,CAClF,QACE,OAAIA,CAAAA,EAAU,GAAA,CACL,IAAIjC,CAAAA,CAAa,CAAA,cAAA,EAAiBE,CAAO,CAAA,CAAA,CAAI,cAAA,CAAgB+B,CAAAA,CAAQ,IAAI,CAAA,CAE3E,IAAIjC,CAAAA,CAAa,CAAA,WAAA,EAAciC,CAAM,CAAA,EAAA,EAAK/B,CAAO,CAAA,CAAA,CAAI,cAAA,CAAgB+B,CAAAA,CAAQ,KAAK,CAC7F,CACF,CACF,CAAA,CC5EO,IAAMC,CAAAA,CAAN,MAAMC,CAAQ,CACF,MAAA,CACA,KAAA,CACA,SAAA,CACA,cAAA,CACT,OAAA,CAAU,KAAA,CAIlB,WAAA,CACEtB,CAAAA,CACAuB,CAAAA,CACAC,CAAAA,CACA,CAaA,GAXA,IAAA,CAAK,MAAA,CAA0BzB,CAAAA,CAAeC,CAAM,CAAA,CACpD,IAAA,CAAK,cAAA,CAAiBwB,CAAAA,CAGtB,IAAA,CAAK,SAAA,CAAY,IAAIT,CAAAA,CAAc,CACjC,QAAA,CAAU,IAAA,CAAK,MAAA,CAAO,QAAA,CACtB,MAAA,CAAQ,IAAA,CAAK,MAAA,CAAO,OACpB,UAAA,CAAY,IAAA,CAAK,MAAA,CAAO,UAC1B,CAAC,CAAA,CAGGQ,CAAAA,CACF,IAAA,CAAK,KAAA,CAAQA,CAAAA,CAAAA,KACR,CACL,IAAME,CAAAA,CAA2B,CAC/B,SAAA,CAAW,IAAA,CAAK,MAAA,CAAO,SAAA,CACvB,aAAA,CAAe,IAAA,CAAK,MAAA,CAAO,aAAA,CAC3B,YAAA,CAAc,IAAA,CAAK,MAAA,CAAO,YAAA,CAC1B,OAAA,CAAS,IAAA,CAAK,MAAA,CAAO,OAAA,CACrB,QAAS,IAAA,CAAK,MAAA,CAAO,OACvB,CAAA,CACA,IAAA,CAAK,KAAA,CAAQ,IAAIxB,CAAAA,CAAYe,CAAAA,EAAS,IAAA,CAAK,SAAA,CAAU,IAAA,CAAKA,CAAI,CAAA,CAAGS,CAAW,EAC9E,CACF,CAKA,IAAI,SAAA,EAAoB,CACtB,OAAO,IAAA,CAAK,KAAA,CAAM,IACpB,CAKA,GAAA,CAAItB,CAAAA,CAAuB,CACzB,GAAI,KAAK,OAAA,CAAS,OAElB,IAAMuB,CAAAA,CAAsB,CAC1B,GAAGvB,CAAAA,CACH,SAAA,CAAWA,CAAAA,CAAM,SAAA,EAAa,IAAI,IAAA,EAAK,CAAE,WAAA,EAAY,CACrD,OAAA,CAASA,CAAAA,CAAM,OAAA,EAAW,IAAA,CAAK,MAAA,CAAO,OAAA,CACtC,QAAA,CAAU,IAAA,CAAK,aAAA,CAAcA,CAAAA,CAAM,QAAQ,CAC7C,CAAA,CAEA,IAAA,CAAK,KAAA,CAAM,GAAA,CAAIuB,CAAS,EAC1B,CAKA,KAAA,CAAMrC,CAAAA,CAAiBsC,CAAAA,CAA0C,CAC/D,IAAA,CAAK,GAAA,CAAI,CAAE,KAAA,CAAO,OAAA,CAAS,OAAA,CAAAtC,CAAAA,CAAS,QAAA,CAAAsC,CAAS,CAAC,EAChD,CAKA,IAAA,CAAKtC,CAAAA,CAAiBsC,CAAAA,CAA0C,CAC9D,IAAA,CAAK,GAAA,CAAI,CAAE,KAAA,CAAO,MAAA,CAAQ,OAAA,CAAAtC,CAAAA,CAAS,QAAA,CAAAsC,CAAS,CAAC,EAC/C,CAKA,IAAA,CAAKtC,CAAAA,CAAiBsC,CAAAA,CAA0C,CAC9D,IAAA,CAAK,GAAA,CAAI,CAAE,KAAA,CAAO,MAAA,CAAQ,OAAA,CAAAtC,CAAAA,CAAS,QAAA,CAAAsC,CAAS,CAAC,EAC/C,CAKA,KAAA,CAAMtC,CAAAA,CAAiBsC,CAAAA,CAA0C,CAC/D,IAAA,CAAK,GAAA,CAAI,CAAE,KAAA,CAAO,OAAA,CAAS,OAAA,CAAAtC,CAAAA,CAAS,QAAA,CAAAsC,CAAS,CAAC,EAChD,CAKA,KAAA,CAAMtC,CAAAA,CAAiBsC,CAAAA,CAA0C,CAC/D,IAAA,CAAK,GAAA,CAAI,CAAE,KAAA,CAAO,OAAA,CAAS,OAAA,CAAAtC,CAAAA,CAAS,QAAA,CAAAsC,CAAS,CAAC,EAChD,CAOA,MAAM,KAAA,EAAwC,CAC5C,OAAO,IAAA,CAAK,KAAA,CAAM,KAAA,EACpB,CAOA,MAAM,QAAA,EAA0B,CAC9B,IAAA,CAAK,OAAA,CAAU,IAAA,CACf,MAAM,IAAA,CAAK,KAAA,CAAM,QAAA,GACnB,CAgBA,KAAA,CAAMC,CAAAA,CAAsC,CAC1C,IAAMC,CAAAA,CAA6B,CACjC,GAAG,IAAA,CAAK,MAAA,CACR,OAAA,CAASD,CAAAA,CAAQ,OAAA,EAAW,IAAA,CAAK,MAAA,CAAO,OAC1C,CAAA,CAEME,CAAAA,CAAgB,CACpB,GAAG,IAAA,CAAK,cAAA,CACR,GAAGF,CAAAA,CAAQ,QACb,CAAA,CAEA,OAAO,IAAIN,CAAAA,CAAQO,CAAAA,CAAa,IAAA,CAAK,KAAA,CAAOC,CAAa,CAC3D,CAEQ,aAAA,CACNC,CAAAA,CACqC,CACrC,GAAI,EAAA,CAAC,IAAA,CAAK,cAAA,EAAkB,CAACA,CAAAA,CAAAA,CAG7B,OAAO,CACL,GAAG,IAAA,CAAK,cAAA,CACR,GAAGA,CACL,CACF,CACF","file":"index.js","sourcesContent":["/**\n * Error codes for Logwell SDK errors\n */\nexport type LogwellErrorCode =\n | 'NETWORK_ERROR'\n | 'UNAUTHORIZED'\n | 'VALIDATION_ERROR'\n | 'RATE_LIMITED'\n | 'SERVER_ERROR'\n | 'QUEUE_OVERFLOW'\n | 'INVALID_CONFIG';\n\n/**\n * Custom error class for Logwell SDK errors\n *\n * @example\n * ```ts\n * throw new LogwellError('Invalid API key', 'UNAUTHORIZED', 401, false);\n * ```\n */\nexport class LogwellError extends Error {\n /**\n * Creates a new LogwellError\n *\n * @param message - Human-readable error message\n * @param code - Error code for programmatic handling\n * @param statusCode - HTTP status code if applicable\n * @param retryable - Whether the operation can be retried\n */\n constructor(\n message: string,\n public readonly code: LogwellErrorCode,\n public readonly statusCode?: number,\n public readonly retryable: boolean = false,\n ) {\n super(message);\n this.name = 'LogwellError';\n\n // Maintains proper stack trace for where our error was thrown (V8 only)\n // Use type assertion to avoid global augmentation (required for JSR compatibility)\n const ErrorWithCapture = Error as unknown as {\n captureStackTrace?: (target: object, ctor?: NewableFunction) => void;\n };\n ErrorWithCapture.captureStackTrace?.(this, LogwellError);\n }\n}\n","import { LogwellError } from './errors';\nimport type { LogwellConfig } from './types';\n\n/**\n * Default configuration values\n */\nexport const DEFAULT_CONFIG = {\n batchSize: 50,\n flushInterval: 5000,\n maxQueueSize: 1000,\n maxRetries: 3,\n} as const;\n\n/**\n * API key format regex: lw_[32 alphanumeric chars including - and _]\n */\nexport const API_KEY_REGEX = /^lw_[A-Za-z0-9_-]{32}$/;\n\n/**\n * Validates API key format\n *\n * @param apiKey - API key to validate\n * @returns true if valid format, false otherwise\n */\nexport function validateApiKeyFormat(apiKey: string): boolean {\n if (!apiKey || typeof apiKey !== 'string') {\n return false;\n }\n return API_KEY_REGEX.test(apiKey);\n}\n\n/**\n * Validates a URL string\n *\n * @param url - URL string to validate\n * @returns true if valid URL, false otherwise\n */\nfunction isValidUrl(url: string): boolean {\n try {\n new URL(url);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Validates configuration and returns merged config with defaults\n *\n * @param config - Partial configuration to validate\n * @returns Complete configuration with defaults applied\n * @throws LogwellError if configuration is invalid\n */\nexport function validateConfig(config: Partial<LogwellConfig>): LogwellConfig {\n // Validate required fields\n if (!config.apiKey) {\n throw new LogwellError('apiKey is required', 'INVALID_CONFIG');\n }\n\n if (!config.endpoint) {\n throw new LogwellError('endpoint is required', 'INVALID_CONFIG');\n }\n\n // Validate API key format\n if (!validateApiKeyFormat(config.apiKey)) {\n throw new LogwellError(\n 'Invalid API key format. Expected: lw_[32 characters]',\n 'INVALID_CONFIG',\n );\n }\n\n // Validate endpoint URL\n if (!isValidUrl(config.endpoint)) {\n throw new LogwellError('Invalid endpoint URL', 'INVALID_CONFIG');\n }\n\n // Validate numeric options\n if (config.batchSize !== undefined && config.batchSize <= 0) {\n throw new LogwellError('batchSize must be positive', 'INVALID_CONFIG');\n }\n\n if (config.flushInterval !== undefined && config.flushInterval <= 0) {\n throw new LogwellError('flushInterval must be positive', 'INVALID_CONFIG');\n }\n\n if (config.maxQueueSize !== undefined && config.maxQueueSize <= 0) {\n throw new LogwellError('maxQueueSize must be positive', 'INVALID_CONFIG');\n }\n\n if (config.maxRetries !== undefined && config.maxRetries < 0) {\n throw new LogwellError('maxRetries must be non-negative', 'INVALID_CONFIG');\n }\n\n // Return merged config with defaults\n return {\n apiKey: config.apiKey,\n endpoint: config.endpoint,\n service: config.service,\n batchSize: config.batchSize ?? DEFAULT_CONFIG.batchSize,\n flushInterval: config.flushInterval ?? DEFAULT_CONFIG.flushInterval,\n maxQueueSize: config.maxQueueSize ?? DEFAULT_CONFIG.maxQueueSize,\n maxRetries: config.maxRetries ?? DEFAULT_CONFIG.maxRetries,\n onError: config.onError,\n onFlush: config.onFlush,\n };\n}\n","import { LogwellError } from './errors';\nimport type { IngestResponse, LogEntry } from './types';\n\n/**\n * Callback type for sending batched logs\n */\nexport type SendBatchFn = (logs: LogEntry[]) => Promise<IngestResponse>;\n\n/**\n * Queue configuration options\n */\nexport interface QueueConfig {\n batchSize: number;\n flushInterval: number;\n maxQueueSize: number;\n onError?: (error: Error) => void;\n onFlush?: (count: number) => void;\n}\n\n/**\n * Batch queue for buffering and sending logs\n *\n * Features:\n * - Automatic flush on batch size threshold\n * - Automatic flush on time interval\n * - Queue overflow protection (drops oldest)\n * - Re-queue on send failure\n * - Graceful shutdown\n */\nexport class BatchQueue {\n private queue: LogEntry[] = [];\n private flushTimer: ReturnType<typeof setTimeout> | null = null;\n private flushing = false;\n private stopped = false;\n\n constructor(\n private sendBatch: SendBatchFn,\n private config: QueueConfig,\n ) {}\n\n /**\n * Current number of logs in the queue\n */\n get size(): number {\n return this.queue.length;\n }\n\n /**\n * Add a log entry to the queue\n *\n * Triggers flush if batch size is reached.\n * Drops oldest log if queue overflows.\n */\n add(entry: LogEntry): void {\n if (this.stopped) {\n return;\n }\n\n // Handle queue overflow\n if (this.queue.length >= this.config.maxQueueSize) {\n const dropped = this.queue.shift();\n this.config.onError?.(\n new LogwellError(\n `Queue overflow. Dropped log: ${dropped?.message.substring(0, 50)}...`,\n 'QUEUE_OVERFLOW',\n ),\n );\n }\n\n this.queue.push(entry);\n\n // Start timer on first entry\n if (!this.flushTimer && !this.stopped) {\n this.startTimer();\n }\n\n // Flush immediately if batch size reached\n if (this.queue.length >= this.config.batchSize) {\n void this.flush();\n }\n }\n\n /**\n * Flush all queued logs immediately\n *\n * @returns Response from the server, or null if queue was empty\n */\n async flush(): Promise<IngestResponse | null> {\n // Prevent concurrent flushes\n if (this.flushing || this.queue.length === 0) {\n return null;\n }\n\n this.flushing = true;\n this.stopTimer();\n\n // Take current batch\n const batch = this.queue.splice(0);\n const count = batch.length;\n\n try {\n const response = await this.sendBatch(batch);\n this.config.onFlush?.(count);\n\n // Restart timer if more logs remain (added during flush)\n if (this.queue.length > 0 && !this.stopped) {\n this.startTimer();\n }\n\n return response;\n } catch (error) {\n // Re-queue failed logs at the front\n this.queue.unshift(...batch);\n this.config.onError?.(error as Error);\n\n // Restart timer to retry\n if (!this.stopped) {\n this.startTimer();\n }\n\n return null;\n } finally {\n this.flushing = false;\n }\n }\n\n /**\n * Flush remaining logs and stop the queue\n */\n async shutdown(): Promise<void> {\n if (this.stopped) {\n return;\n }\n\n this.stopped = true;\n this.stopTimer();\n\n // Flush all remaining logs\n if (this.queue.length > 0) {\n this.flushing = false; // Reset flushing flag\n await this.flush();\n }\n }\n\n private startTimer(): void {\n this.flushTimer = setTimeout(() => {\n void this.flush();\n }, this.config.flushInterval);\n }\n\n private stopTimer(): void {\n if (this.flushTimer) {\n clearTimeout(this.flushTimer);\n this.flushTimer = null;\n }\n }\n}\n","import { LogwellError } from './errors';\nimport type { IngestResponse, LogEntry } from './types';\n\n/**\n * Transport configuration\n */\nexport interface TransportConfig {\n endpoint: string;\n apiKey: string;\n maxRetries: number;\n timeout?: number;\n}\n\n/**\n * Delay helper with exponential backoff\n */\nfunction delay(attempt: number, baseDelay = 100): Promise<void> {\n const ms = Math.min(baseDelay * 2 ** attempt, 10000);\n const jitter = Math.random() * ms * 0.3;\n return new Promise((resolve) => setTimeout(resolve, ms + jitter));\n}\n\n/**\n * HTTP transport for sending logs to Logwell server\n *\n * Features:\n * - Automatic retry with exponential backoff\n * - Error classification with retryable flag\n * - Proper error handling for all HTTP status codes\n */\nexport class HttpTransport {\n private readonly ingestUrl: string;\n\n constructor(private config: TransportConfig) {\n this.ingestUrl = `${config.endpoint}/v1/ingest`;\n }\n\n /**\n * Send logs to the Logwell server\n *\n * @param logs - Array of log entries to send\n * @returns Response with accepted/rejected counts\n * @throws LogwellError on failure after all retries\n */\n async send(logs: LogEntry[]): Promise<IngestResponse> {\n let lastError: LogwellError = new LogwellError(\n 'Max retries exceeded',\n 'NETWORK_ERROR',\n undefined,\n true,\n );\n\n for (let attempt = 0; attempt <= this.config.maxRetries; attempt++) {\n try {\n return await this.doRequest(logs);\n } catch (error) {\n lastError = error as LogwellError;\n\n // Don't retry non-retryable errors\n if (!lastError.retryable) {\n throw lastError;\n }\n\n // Don't delay after the last attempt\n if (attempt < this.config.maxRetries) {\n await delay(attempt);\n }\n }\n }\n\n throw lastError;\n }\n\n private async doRequest(logs: LogEntry[]): Promise<IngestResponse> {\n let response: Response;\n\n try {\n response = await fetch(this.ingestUrl, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${this.config.apiKey}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(logs),\n });\n } catch (error) {\n // Network error (fetch failed)\n throw new LogwellError(\n `Network error: ${(error as Error).message}`,\n 'NETWORK_ERROR',\n undefined,\n true,\n );\n }\n\n // Handle error responses\n if (!response.ok) {\n const errorBody = await this.tryParseError(response);\n throw this.createError(response.status, errorBody);\n }\n\n // Parse successful response\n return (await response.json()) as IngestResponse;\n }\n\n private async tryParseError(response: Response): Promise<string> {\n try {\n const body = await response.json();\n return body.message || body.error || 'Unknown error';\n } catch {\n return `HTTP ${response.status}`;\n }\n }\n\n private createError(status: number, message: string): LogwellError {\n switch (status) {\n case 401:\n return new LogwellError(`Unauthorized: ${message}`, 'UNAUTHORIZED', status, false);\n case 400:\n return new LogwellError(`Validation error: ${message}`, 'VALIDATION_ERROR', status, false);\n case 429:\n return new LogwellError(`Rate limited: ${message}`, 'RATE_LIMITED', status, true);\n default:\n if (status >= 500) {\n return new LogwellError(`Server error: ${message}`, 'SERVER_ERROR', status, true);\n }\n return new LogwellError(`HTTP error ${status}: ${message}`, 'SERVER_ERROR', status, false);\n }\n }\n}\n","import { validateConfig } from './config';\nimport { BatchQueue, type QueueConfig } from './queue';\nimport { HttpTransport } from './transport';\nimport type { IngestResponse, LogEntry, LogwellConfig } from './types';\n\n/**\n * Child logger options\n */\nexport interface ChildLoggerOptions {\n service?: string;\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Internal resolved config with all defaults applied\n */\ninterface ResolvedConfig {\n apiKey: string;\n endpoint: string;\n service?: string;\n batchSize: number;\n flushInterval: number;\n maxQueueSize: number;\n maxRetries: number;\n onError?: (error: Error) => void;\n onFlush?: (count: number) => void;\n}\n\n/**\n * Asserts that config has all required fields after validation\n */\nfunction asResolvedConfig(config: LogwellConfig): ResolvedConfig {\n return config as ResolvedConfig;\n}\n\n/**\n * Main Logwell client class\n *\n * Provides methods for logging at different levels with automatic\n * batching, retry, and queue management.\n *\n * @example\n * ```ts\n * const logger = new Logwell({\n * apiKey: 'lw_xxx',\n * endpoint: 'https://logs.example.com',\n * service: 'my-app',\n * });\n *\n * logger.info('User logged in', { userId: '123' });\n * await logger.shutdown();\n * ```\n */\nexport class Logwell {\n private readonly config: ResolvedConfig;\n private readonly queue: BatchQueue;\n private readonly transport: HttpTransport;\n private readonly parentMetadata?: Record<string, unknown>;\n private stopped = false;\n\n constructor(config: LogwellConfig);\n constructor(config: LogwellConfig, queue: BatchQueue, parentMetadata?: Record<string, unknown>);\n constructor(\n config: LogwellConfig,\n existingQueue?: BatchQueue,\n parentMetadata?: Record<string, unknown>,\n ) {\n // Validate and apply defaults\n this.config = asResolvedConfig(validateConfig(config));\n this.parentMetadata = parentMetadata;\n\n // Create transport\n this.transport = new HttpTransport({\n endpoint: this.config.endpoint,\n apiKey: this.config.apiKey,\n maxRetries: this.config.maxRetries,\n });\n\n // Use existing queue (for child loggers) or create new one\n if (existingQueue) {\n this.queue = existingQueue;\n } else {\n const queueConfig: QueueConfig = {\n batchSize: this.config.batchSize,\n flushInterval: this.config.flushInterval,\n maxQueueSize: this.config.maxQueueSize,\n onError: this.config.onError,\n onFlush: this.config.onFlush,\n };\n this.queue = new BatchQueue((logs) => this.transport.send(logs), queueConfig);\n }\n }\n\n /**\n * Current number of logs waiting in the queue\n */\n get queueSize(): number {\n return this.queue.size;\n }\n\n /**\n * Log a message at the specified level\n */\n log(entry: LogEntry): void {\n if (this.stopped) return;\n\n const fullEntry: LogEntry = {\n ...entry,\n timestamp: entry.timestamp ?? new Date().toISOString(),\n service: entry.service ?? this.config.service,\n metadata: this.mergeMetadata(entry.metadata),\n };\n\n this.queue.add(fullEntry);\n }\n\n /**\n * Log a debug message\n */\n debug(message: string, metadata?: Record<string, unknown>): void {\n this.log({ level: 'debug', message, metadata });\n }\n\n /**\n * Log an info message\n */\n info(message: string, metadata?: Record<string, unknown>): void {\n this.log({ level: 'info', message, metadata });\n }\n\n /**\n * Log a warning message\n */\n warn(message: string, metadata?: Record<string, unknown>): void {\n this.log({ level: 'warn', message, metadata });\n }\n\n /**\n * Log an error message\n */\n error(message: string, metadata?: Record<string, unknown>): void {\n this.log({ level: 'error', message, metadata });\n }\n\n /**\n * Log a fatal error message\n */\n fatal(message: string, metadata?: Record<string, unknown>): void {\n this.log({ level: 'fatal', message, metadata });\n }\n\n /**\n * Flush all queued logs immediately\n *\n * @returns Response from the server, or null if queue was empty\n */\n async flush(): Promise<IngestResponse | null> {\n return this.queue.flush();\n }\n\n /**\n * Flush remaining logs and stop the client\n *\n * Call this before process exit to ensure all logs are sent.\n */\n async shutdown(): Promise<void> {\n this.stopped = true;\n await this.queue.shutdown();\n }\n\n /**\n * Create a child logger with additional context\n *\n * Child loggers share the same queue as the parent,\n * but can have their own service name and default metadata.\n *\n * @example\n * ```ts\n * const requestLogger = logger.child({\n * metadata: { requestId: req.id },\n * });\n * requestLogger.info('Request received');\n * ```\n */\n child(options: ChildLoggerOptions): Logwell {\n const childConfig: LogwellConfig = {\n ...this.config,\n service: options.service ?? this.config.service,\n };\n\n const childMetadata = {\n ...this.parentMetadata,\n ...options.metadata,\n };\n\n return new Logwell(childConfig, this.queue, childMetadata);\n }\n\n private mergeMetadata(\n entryMetadata?: Record<string, unknown>,\n ): Record<string, unknown> | undefined {\n if (!this.parentMetadata && !entryMetadata) {\n return undefined;\n }\n return {\n ...this.parentMetadata,\n ...entryMetadata,\n };\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/errors.ts","../src/config.ts","../src/queue.ts","../src/source-location.ts","../src/transport.ts","../src/client.ts"],"names":["LogwellError","_LogwellError","message","code","statusCode","retryable","DEFAULT_CONFIG","API_KEY_REGEX","validateApiKeyFormat","apiKey","isValidUrl","url","validateConfig","config","BatchQueue","sendBatch","entry","dropped","batch","count","response","error","V8_PAREN_REGEX","V8_BARE_REGEX","SPIDERMONKEY_REGEX","parseStackFrame","frameLine","match","sourceFile","lineStr","lineNumber","captureSourceLocation","skipFrames","stack","lines","firstLine","targetFrameIndex","targetFrame","delay","attempt","baseDelay","ms","jitter","resolve","HttpTransport","logs","lastError","errorBody","body","status","Logwell","_Logwell","existingQueue","parentMetadata","queueConfig","location","fullEntry","metadata","options","childConfig","childMetadata","entryMetadata"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Error codes for Logwell SDK errors\n */\nexport type LogwellErrorCode =\n | 'NETWORK_ERROR'\n | 'UNAUTHORIZED'\n | 'VALIDATION_ERROR'\n | 'RATE_LIMITED'\n | 'SERVER_ERROR'\n | 'QUEUE_OVERFLOW'\n | 'INVALID_CONFIG';\n\n/**\n * Custom error class for Logwell SDK errors\n *\n * @example\n * ```ts\n * throw new LogwellError('Invalid API key', 'UNAUTHORIZED', 401, false);\n * ```\n */\nexport class LogwellError extends Error {\n /**\n * Creates a new LogwellError\n *\n * @param message - Human-readable error message\n * @param code - Error code for programmatic handling\n * @param statusCode - HTTP status code if applicable\n * @param retryable - Whether the operation can be retried\n */\n constructor(\n message: string,\n public readonly code: LogwellErrorCode,\n public readonly statusCode?: number,\n public readonly retryable: boolean = false,\n ) {\n super(message);\n this.name = 'LogwellError';\n\n // Maintains proper stack trace for where our error was thrown (V8 only)\n // Use type assertion to avoid global augmentation (required for JSR compatibility)\n const ErrorWithCapture = Error as unknown as {\n captureStackTrace?: (target: object, ctor?: NewableFunction) => void;\n };\n ErrorWithCapture.captureStackTrace?.(this, LogwellError);\n }\n}\n","import { LogwellError } from './errors';\nimport type { LogwellConfig } from './types';\n\n/**\n * Default configuration values\n */\nexport const DEFAULT_CONFIG = {\n batchSize: 50,\n flushInterval: 5000,\n maxQueueSize: 1000,\n maxRetries: 3,\n captureSourceLocation: false,\n} as const;\n\n/**\n * API key format regex: lw_[32 alphanumeric chars including - and _]\n */\nexport const API_KEY_REGEX = /^lw_[A-Za-z0-9_-]{32}$/;\n\n/**\n * Validates API key format\n *\n * @param apiKey - API key to validate\n * @returns true if valid format, false otherwise\n */\nexport function validateApiKeyFormat(apiKey: string): boolean {\n if (!apiKey || typeof apiKey !== 'string') {\n return false;\n }\n return API_KEY_REGEX.test(apiKey);\n}\n\n/**\n * Validates a URL string\n *\n * @param url - URL string to validate\n * @returns true if valid URL, false otherwise\n */\nfunction isValidUrl(url: string): boolean {\n try {\n new URL(url);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Validates configuration and returns merged config with defaults\n *\n * @param config - Partial configuration to validate\n * @returns Complete configuration with defaults applied\n * @throws LogwellError if configuration is invalid\n */\nexport function validateConfig(config: Partial<LogwellConfig>): LogwellConfig {\n // Validate required fields\n if (!config.apiKey) {\n throw new LogwellError('apiKey is required', 'INVALID_CONFIG');\n }\n\n if (!config.endpoint) {\n throw new LogwellError('endpoint is required', 'INVALID_CONFIG');\n }\n\n // Validate API key format\n if (!validateApiKeyFormat(config.apiKey)) {\n throw new LogwellError(\n 'Invalid API key format. Expected: lw_[32 characters]',\n 'INVALID_CONFIG',\n );\n }\n\n // Validate endpoint URL\n if (!isValidUrl(config.endpoint)) {\n throw new LogwellError('Invalid endpoint URL', 'INVALID_CONFIG');\n }\n\n // Validate numeric options\n if (config.batchSize !== undefined && config.batchSize <= 0) {\n throw new LogwellError('batchSize must be positive', 'INVALID_CONFIG');\n }\n\n if (config.flushInterval !== undefined && config.flushInterval <= 0) {\n throw new LogwellError('flushInterval must be positive', 'INVALID_CONFIG');\n }\n\n if (config.maxQueueSize !== undefined && config.maxQueueSize <= 0) {\n throw new LogwellError('maxQueueSize must be positive', 'INVALID_CONFIG');\n }\n\n if (config.maxRetries !== undefined && config.maxRetries < 0) {\n throw new LogwellError('maxRetries must be non-negative', 'INVALID_CONFIG');\n }\n\n // Return merged config with defaults\n return {\n apiKey: config.apiKey,\n endpoint: config.endpoint,\n service: config.service,\n batchSize: config.batchSize ?? DEFAULT_CONFIG.batchSize,\n flushInterval: config.flushInterval ?? DEFAULT_CONFIG.flushInterval,\n maxQueueSize: config.maxQueueSize ?? DEFAULT_CONFIG.maxQueueSize,\n maxRetries: config.maxRetries ?? DEFAULT_CONFIG.maxRetries,\n captureSourceLocation: config.captureSourceLocation ?? DEFAULT_CONFIG.captureSourceLocation,\n onError: config.onError,\n onFlush: config.onFlush,\n };\n}\n","import { LogwellError } from './errors';\nimport type { IngestResponse, LogEntry } from './types';\n\n/**\n * Callback type for sending batched logs\n */\nexport type SendBatchFn = (logs: LogEntry[]) => Promise<IngestResponse>;\n\n/**\n * Queue configuration options\n */\nexport interface QueueConfig {\n batchSize: number;\n flushInterval: number;\n maxQueueSize: number;\n onError?: (error: Error) => void;\n onFlush?: (count: number) => void;\n}\n\n/**\n * Batch queue for buffering and sending logs\n *\n * Features:\n * - Automatic flush on batch size threshold\n * - Automatic flush on time interval\n * - Queue overflow protection (drops oldest)\n * - Re-queue on send failure\n * - Graceful shutdown\n */\nexport class BatchQueue {\n private queue: LogEntry[] = [];\n private flushTimer: ReturnType<typeof setTimeout> | null = null;\n private flushing = false;\n private stopped = false;\n\n constructor(\n private sendBatch: SendBatchFn,\n private config: QueueConfig,\n ) {}\n\n /**\n * Current number of logs in the queue\n */\n get size(): number {\n return this.queue.length;\n }\n\n /**\n * Add a log entry to the queue\n *\n * Triggers flush if batch size is reached.\n * Drops oldest log if queue overflows.\n */\n add(entry: LogEntry): void {\n if (this.stopped) {\n return;\n }\n\n // Handle queue overflow\n if (this.queue.length >= this.config.maxQueueSize) {\n const dropped = this.queue.shift();\n this.config.onError?.(\n new LogwellError(\n `Queue overflow. Dropped log: ${dropped?.message.substring(0, 50)}...`,\n 'QUEUE_OVERFLOW',\n ),\n );\n }\n\n this.queue.push(entry);\n\n // Start timer on first entry\n if (!this.flushTimer && !this.stopped) {\n this.startTimer();\n }\n\n // Flush immediately if batch size reached\n if (this.queue.length >= this.config.batchSize) {\n void this.flush();\n }\n }\n\n /**\n * Flush all queued logs immediately\n *\n * @returns Response from the server, or null if queue was empty\n */\n async flush(): Promise<IngestResponse | null> {\n // Prevent concurrent flushes\n if (this.flushing || this.queue.length === 0) {\n return null;\n }\n\n this.flushing = true;\n this.stopTimer();\n\n // Take current batch\n const batch = this.queue.splice(0);\n const count = batch.length;\n\n try {\n const response = await this.sendBatch(batch);\n this.config.onFlush?.(count);\n\n // Restart timer if more logs remain (added during flush)\n if (this.queue.length > 0 && !this.stopped) {\n this.startTimer();\n }\n\n return response;\n } catch (error) {\n // Re-queue failed logs at the front\n this.queue.unshift(...batch);\n this.config.onError?.(error as Error);\n\n // Restart timer to retry\n if (!this.stopped) {\n this.startTimer();\n }\n\n return null;\n } finally {\n this.flushing = false;\n }\n }\n\n /**\n * Flush remaining logs and stop the queue\n */\n async shutdown(): Promise<void> {\n if (this.stopped) {\n return;\n }\n\n this.stopped = true;\n this.stopTimer();\n\n // Flush all remaining logs\n if (this.queue.length > 0) {\n this.flushing = false; // Reset flushing flag\n await this.flush();\n }\n }\n\n private startTimer(): void {\n this.flushTimer = setTimeout(() => {\n void this.flush();\n }, this.config.flushInterval);\n }\n\n private stopTimer(): void {\n if (this.flushTimer) {\n clearTimeout(this.flushTimer);\n this.flushTimer = null;\n }\n }\n}\n","/**\n * Source location information captured from stack trace\n */\nexport interface SourceLocation {\n sourceFile: string;\n lineNumber: number;\n}\n\n/**\n * V8 format with parentheses (Node.js, Bun, Chrome):\n * Handles all named function variants including:\n * - \" at functionName (/path/to/file.ts:42:15)\"\n * - \" at async functionName (/path/to/file.ts:42:15)\"\n * - \" at new ClassName (/path/to/file.ts:42:15)\"\n * - \" at Object.method [as alias] (/path/to/file.ts:42:15)\"\n *\n * Captures:\n * - Group 1: file path (including protocols like webpack://, file://, http://)\n * - Group 2: line number\n */\nconst V8_PAREN_REGEX = /\\((.+):(\\d+):\\d+\\)\\s*$/;\n\n/**\n * V8 format without parentheses (anonymous functions):\n * \" at /path/to/file.ts:42:15\"\n *\n * Captures:\n * - Group 1: file path\n * - Group 2: line number\n */\nconst V8_BARE_REGEX = /^\\s*at\\s+(.+):(\\d+):\\d+$/;\n\n/**\n * SpiderMonkey/JSC format regex (Firefox, Safari):\n * \"functionName@/path/to/file.ts:42:15\"\n * \"@/path/to/file.ts:42:15\"\n *\n * Captures:\n * - Group 1: file path\n * - Group 2: line number\n */\nconst SPIDERMONKEY_REGEX = /^[^@]*@(.+):(\\d+):\\d+$/;\n\n/**\n * Parses a single stack frame line to extract source location.\n *\n * Supports:\n * - V8 format (Node.js, Bun, Chrome)\n * - SpiderMonkey format (Firefox)\n * - JSC format (Safari)\n * - Windows paths (C:\\, UNC paths)\n * - Bundler paths (webpack://, file://, http://, https://)\n *\n * @param frameLine - Single line from Error.stack\n * @returns Source location or undefined if parsing fails\n */\nexport function parseStackFrame(frameLine: string): SourceLocation | undefined {\n if (!frameLine) {\n return undefined;\n }\n\n // Try V8 format with parentheses first (most common, handles all edge cases)\n let match = V8_PAREN_REGEX.exec(frameLine);\n if (match) {\n const sourceFile = match[1];\n const lineStr = match[2];\n if (sourceFile && lineStr) {\n const lineNumber = parseInt(lineStr, 10);\n if (!Number.isNaN(lineNumber)) {\n return { sourceFile, lineNumber };\n }\n }\n }\n\n // Try V8 format without parentheses (anonymous functions)\n match = V8_BARE_REGEX.exec(frameLine);\n if (match) {\n const sourceFile = match[1];\n const lineStr = match[2];\n if (sourceFile && lineStr) {\n const lineNumber = parseInt(lineStr, 10);\n if (!Number.isNaN(lineNumber)) {\n return { sourceFile, lineNumber };\n }\n }\n }\n\n // Try SpiderMonkey/JSC format (Firefox/Safari)\n match = SPIDERMONKEY_REGEX.exec(frameLine);\n if (match) {\n const sourceFile = match[1];\n const lineStr = match[2];\n if (sourceFile && lineStr) {\n const lineNumber = parseInt(lineStr, 10);\n if (!Number.isNaN(lineNumber)) {\n return { sourceFile, lineNumber };\n }\n }\n }\n\n return undefined;\n}\n\n/**\n * Captures the source location of the caller by parsing the stack trace.\n *\n * @param skipFrames - Number of stack frames to skip (0 = immediate caller)\n * @returns Source location or undefined if capture fails\n *\n * @example\n * // In a logging function that calls this\n * function log(message: string) {\n * const location = captureSourceLocation(1); // Skip log() frame\n * // location.sourceFile = file where log() was called\n * }\n */\nexport function captureSourceLocation(skipFrames: number): SourceLocation | undefined {\n const error = new Error();\n const stack = error.stack;\n\n if (!stack) {\n return undefined;\n }\n\n const lines = stack.split('\\n');\n\n // Detect stack format:\n // - V8 (Node/Bun/Chrome): Has \"Error\" header line, frames start with \"at\"\n // - SpiderMonkey/JSC (Firefox/Safari): No header, frames contain \"@\"\n const firstLine = lines[0] || '';\n const hasErrorHeader = !firstLine.includes('@') && !/^\\s*at\\s/.test(firstLine);\n\n // Calculate target frame index:\n // Skip: header (if present) + captureSourceLocation frame + skipFrames\n const headerOffset = hasErrorHeader ? 1 : 0;\n const targetFrameIndex = headerOffset + 1 + skipFrames;\n\n const targetFrame = lines[targetFrameIndex];\n if (targetFrame === undefined) {\n return undefined;\n }\n\n return parseStackFrame(targetFrame);\n}\n","import { LogwellError } from './errors';\nimport type { IngestResponse, LogEntry } from './types';\n\n/**\n * Transport configuration\n */\nexport interface TransportConfig {\n endpoint: string;\n apiKey: string;\n maxRetries: number;\n timeout?: number;\n}\n\n/**\n * Delay helper with exponential backoff\n */\nfunction delay(attempt: number, baseDelay = 100): Promise<void> {\n const ms = Math.min(baseDelay * 2 ** attempt, 10000);\n const jitter = Math.random() * ms * 0.3;\n return new Promise((resolve) => setTimeout(resolve, ms + jitter));\n}\n\n/**\n * HTTP transport for sending logs to Logwell server\n *\n * Features:\n * - Automatic retry with exponential backoff\n * - Error classification with retryable flag\n * - Proper error handling for all HTTP status codes\n */\nexport class HttpTransport {\n private readonly ingestUrl: string;\n\n constructor(private config: TransportConfig) {\n this.ingestUrl = `${config.endpoint}/v1/ingest`;\n }\n\n /**\n * Send logs to the Logwell server\n *\n * @param logs - Array of log entries to send\n * @returns Response with accepted/rejected counts\n * @throws LogwellError on failure after all retries\n */\n async send(logs: LogEntry[]): Promise<IngestResponse> {\n let lastError: LogwellError = new LogwellError(\n 'Max retries exceeded',\n 'NETWORK_ERROR',\n undefined,\n true,\n );\n\n for (let attempt = 0; attempt <= this.config.maxRetries; attempt++) {\n try {\n return await this.doRequest(logs);\n } catch (error) {\n lastError = error as LogwellError;\n\n // Don't retry non-retryable errors\n if (!lastError.retryable) {\n throw lastError;\n }\n\n // Don't delay after the last attempt\n if (attempt < this.config.maxRetries) {\n await delay(attempt);\n }\n }\n }\n\n throw lastError;\n }\n\n private async doRequest(logs: LogEntry[]): Promise<IngestResponse> {\n let response: Response;\n\n try {\n response = await fetch(this.ingestUrl, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${this.config.apiKey}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(logs),\n });\n } catch (error) {\n // Network error (fetch failed)\n throw new LogwellError(\n `Network error: ${(error as Error).message}`,\n 'NETWORK_ERROR',\n undefined,\n true,\n );\n }\n\n // Handle error responses\n if (!response.ok) {\n const errorBody = await this.tryParseError(response);\n throw this.createError(response.status, errorBody);\n }\n\n // Parse successful response\n return (await response.json()) as IngestResponse;\n }\n\n private async tryParseError(response: Response): Promise<string> {\n try {\n const body = await response.json();\n return body.message || body.error || 'Unknown error';\n } catch {\n return `HTTP ${response.status}`;\n }\n }\n\n private createError(status: number, message: string): LogwellError {\n switch (status) {\n case 401:\n return new LogwellError(`Unauthorized: ${message}`, 'UNAUTHORIZED', status, false);\n case 400:\n return new LogwellError(`Validation error: ${message}`, 'VALIDATION_ERROR', status, false);\n case 429:\n return new LogwellError(`Rate limited: ${message}`, 'RATE_LIMITED', status, true);\n default:\n if (status >= 500) {\n return new LogwellError(`Server error: ${message}`, 'SERVER_ERROR', status, true);\n }\n return new LogwellError(`HTTP error ${status}: ${message}`, 'SERVER_ERROR', status, false);\n }\n }\n}\n","import { validateConfig } from './config';\nimport { BatchQueue, type QueueConfig } from './queue';\nimport { captureSourceLocation } from './source-location';\nimport { HttpTransport } from './transport';\nimport type { IngestResponse, LogEntry, LogwellConfig } from './types';\n\n/**\n * Child logger options\n */\nexport interface ChildLoggerOptions {\n service?: string;\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Internal resolved config with all defaults applied\n */\ninterface ResolvedConfig {\n apiKey: string;\n endpoint: string;\n service?: string;\n batchSize: number;\n flushInterval: number;\n maxQueueSize: number;\n maxRetries: number;\n captureSourceLocation: boolean;\n onError?: (error: Error) => void;\n onFlush?: (count: number) => void;\n}\n\n/**\n * Asserts that config has all required fields after validation\n */\nfunction asResolvedConfig(config: LogwellConfig): ResolvedConfig {\n return config as ResolvedConfig;\n}\n\n/**\n * Main Logwell client class\n *\n * Provides methods for logging at different levels with automatic\n * batching, retry, and queue management.\n *\n * @example\n * ```ts\n * const logger = new Logwell({\n * apiKey: 'lw_xxx',\n * endpoint: 'https://logs.example.com',\n * service: 'my-app',\n * });\n *\n * logger.info('User logged in', { userId: '123' });\n * await logger.shutdown();\n * ```\n */\nexport class Logwell {\n private readonly config: ResolvedConfig;\n private readonly queue: BatchQueue;\n private readonly transport: HttpTransport;\n private readonly parentMetadata?: Record<string, unknown>;\n private stopped = false;\n\n constructor(config: LogwellConfig);\n constructor(config: LogwellConfig, queue: BatchQueue, parentMetadata?: Record<string, unknown>);\n constructor(\n config: LogwellConfig,\n existingQueue?: BatchQueue,\n parentMetadata?: Record<string, unknown>,\n ) {\n // Validate and apply defaults\n this.config = asResolvedConfig(validateConfig(config));\n this.parentMetadata = parentMetadata;\n\n // Create transport\n this.transport = new HttpTransport({\n endpoint: this.config.endpoint,\n apiKey: this.config.apiKey,\n maxRetries: this.config.maxRetries,\n });\n\n // Use existing queue (for child loggers) or create new one\n if (existingQueue) {\n this.queue = existingQueue;\n } else {\n const queueConfig: QueueConfig = {\n batchSize: this.config.batchSize,\n flushInterval: this.config.flushInterval,\n maxQueueSize: this.config.maxQueueSize,\n onError: this.config.onError,\n onFlush: this.config.onFlush,\n };\n this.queue = new BatchQueue((logs) => this.transport.send(logs), queueConfig);\n }\n }\n\n /**\n * Current number of logs waiting in the queue\n */\n get queueSize(): number {\n return this.queue.size;\n }\n\n /**\n * Internal log method with source location capture.\n * @param entry - The log entry\n * @param skipFrames - Number of frames to skip for source location (2 for public methods)\n */\n private _addLog(entry: LogEntry, skipFrames: number): void {\n if (this.stopped) return;\n\n let sourceFile: string | undefined;\n let lineNumber: number | undefined;\n\n if (this.config.captureSourceLocation) {\n const location = captureSourceLocation(skipFrames);\n if (location) {\n sourceFile = location.sourceFile;\n lineNumber = location.lineNumber;\n }\n }\n\n const fullEntry: LogEntry = {\n ...entry,\n timestamp: entry.timestamp ?? new Date().toISOString(),\n service: entry.service ?? this.config.service,\n metadata: this.mergeMetadata(entry.metadata),\n ...(sourceFile !== undefined && { sourceFile }),\n ...(lineNumber !== undefined && { lineNumber }),\n };\n\n this.queue.add(fullEntry);\n }\n\n /**\n * Log a message at the specified level\n */\n log(entry: LogEntry): void {\n this._addLog(entry, 2);\n }\n\n /**\n * Log a debug message\n */\n debug(message: string, metadata?: Record<string, unknown>): void {\n this._addLog({ level: 'debug', message, metadata }, 2);\n }\n\n /**\n * Log an info message\n */\n info(message: string, metadata?: Record<string, unknown>): void {\n this._addLog({ level: 'info', message, metadata }, 2);\n }\n\n /**\n * Log a warning message\n */\n warn(message: string, metadata?: Record<string, unknown>): void {\n this._addLog({ level: 'warn', message, metadata }, 2);\n }\n\n /**\n * Log an error message\n */\n error(message: string, metadata?: Record<string, unknown>): void {\n this._addLog({ level: 'error', message, metadata }, 2);\n }\n\n /**\n * Log a fatal error message\n */\n fatal(message: string, metadata?: Record<string, unknown>): void {\n this._addLog({ level: 'fatal', message, metadata }, 2);\n }\n\n /**\n * Flush all queued logs immediately\n *\n * @returns Response from the server, or null if queue was empty\n */\n async flush(): Promise<IngestResponse | null> {\n return this.queue.flush();\n }\n\n /**\n * Flush remaining logs and stop the client\n *\n * Call this before process exit to ensure all logs are sent.\n */\n async shutdown(): Promise<void> {\n this.stopped = true;\n await this.queue.shutdown();\n }\n\n /**\n * Create a child logger with additional context\n *\n * Child loggers share the same queue as the parent,\n * but can have their own service name and default metadata.\n *\n * @example\n * ```ts\n * const requestLogger = logger.child({\n * metadata: { requestId: req.id },\n * });\n * requestLogger.info('Request received');\n * ```\n */\n child(options: ChildLoggerOptions): Logwell {\n const childConfig: LogwellConfig = {\n ...this.config,\n service: options.service ?? this.config.service,\n };\n\n const childMetadata = {\n ...this.parentMetadata,\n ...options.metadata,\n };\n\n return new Logwell(childConfig, this.queue, childMetadata);\n }\n\n private mergeMetadata(\n entryMetadata?: Record<string, unknown>,\n ): Record<string, unknown> | undefined {\n if (!this.parentMetadata && !entryMetadata) {\n return undefined;\n }\n return {\n ...this.parentMetadata,\n ...entryMetadata,\n };\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "logwell",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "Official TypeScript SDK for Logwell logging platform",
5
5
  "keywords": [
6
6
  "logging",
@@ -65,12 +65,12 @@
65
65
  "@arethetypeswrong/cli": "^0.18.2",
66
66
  "@biomejs/biome": "2.3.11",
67
67
  "@size-limit/preset-small-lib": "^12.0.0",
68
- "@vitest/coverage-v8": "^4.0.0",
68
+ "@vitest/coverage-v8": "^4.0.17",
69
69
  "msw": "^2.7.0",
70
70
  "size-limit": "^12.0.0",
71
71
  "tsup": "^8.4.0",
72
72
  "typescript": "^5.9.3",
73
- "vitest": "^4.0.16"
73
+ "vitest": "^4.0.17"
74
74
  },
75
75
  "size-limit": [
76
76
  {