{"version":3,"file":"index.cjs","names":["retryAfter: number","LoggerlessTransport","logEntry: Record<string, any>","chunks: Uint8Array[]","lastError: Error","compressedPayload: Uint8Array | undefined","headers: Record<string, string>"],"sources":["../src/NewRelicTransport.ts"],"sourcesContent":["import type { LoggerlessTransportConfig, LogLayerTransportParams } from \"@loglayer/transport\";\nimport { LoggerlessTransport } from \"@loglayer/transport\";\n\n// Constants defining New Relic's API limits\nconst MAX_PAYLOAD_SIZE = 1_000_000; // 1MB in bytes\nconst MAX_ATTRIBUTES = 255;\nconst MAX_ATTRIBUTE_NAME_LENGTH = 255;\nconst MAX_ATTRIBUTE_VALUE_LENGTH = 4094;\n\n/**\n * Error thrown when log entry validation fails.\n * This includes payload size, attribute count, and attribute name length validations.\n */\nclass ValidationError extends Error {\n  constructor(message: string) {\n    super(message);\n    this.name = \"ValidationError\";\n  }\n}\n\n/**\n * Error thrown when New Relic's API rate limit is exceeded.\n * Contains the retry-after duration specified by the API.\n */\nclass RateLimitError extends Error {\n  constructor(\n    message: string,\n    public retryAfter: number,\n  ) {\n    super(message);\n    this.name = \"RateLimitError\";\n  }\n}\n\n/**\n * Configuration options for the New Relic transport.\n */\nexport interface NewRelicTransportConfig extends LoggerlessTransportConfig {\n  /**\n   * The New Relic API key\n   */\n  apiKey: string;\n  /**\n   * The New Relic Log API endpoint\n   * @default https://log-api.newrelic.com/log/v1\n   */\n  endpoint?: string;\n  /**\n   * Optional callback for error handling\n   */\n  onError?: (err: Error) => void;\n  /**\n   * Optional callback for debugging log entries before they are sent\n   */\n  onDebug?: (entry: Record<string, any>) => void;\n  /**\n   * Whether to use gzip compression\n   * @default true\n   */\n  useCompression?: boolean;\n  /**\n   * Number of retry attempts before giving up\n   * @default 3\n   */\n  maxRetries?: number;\n  /**\n   * Base delay between retries in milliseconds\n   * @default 1000\n   */\n  retryDelay?: number;\n  /**\n   * Whether to respect rate limiting by waiting when a 429 response is received\n   * @default true\n   */\n  respectRateLimit?: boolean;\n}\n\n/**\n * Validates a log entry against New Relic's constraints.\n * - Checks number of attributes (max 255)\n * - Validates attribute name length (max 255 characters)\n * - Truncates attribute values longer than 4094 characters\n *\n * @param logEntry - The log entry to validate\n * @returns The validated (and potentially modified) log entry\n * @throws {ValidationError} If validation fails\n */\nfunction validateLogEntry(logEntry: Record<string, any>) {\n  if (logEntry.attributes) {\n    // Check number of attributes\n    const attributeCount = Object.keys(logEntry.attributes).length;\n    if (attributeCount > MAX_ATTRIBUTES) {\n      throw new ValidationError(\n        `Log entry exceeds maximum number of attributes (${MAX_ATTRIBUTES}). Found: ${attributeCount}`,\n      );\n    }\n\n    // Check attribute names and values\n    for (const [key, value] of Object.entries(logEntry.attributes)) {\n      // Check attribute name length\n      if (key.length > MAX_ATTRIBUTE_NAME_LENGTH) {\n        throw new ValidationError(\n          `Attribute name '${key}' exceeds maximum length (${MAX_ATTRIBUTE_NAME_LENGTH}). Length: ${key.length}`,\n        );\n      }\n\n      // Check string value length\n      if (typeof value === \"string\" && value.length > MAX_ATTRIBUTE_VALUE_LENGTH) {\n        // Truncate the string value to the maximum length\n        logEntry.attributes[key] = value.slice(0, MAX_ATTRIBUTE_VALUE_LENGTH);\n      }\n    }\n  }\n\n  return logEntry;\n}\n\n/**\n * NewRelicTransport is responsible for sending logs to New Relic's Log API.\n * It handles validation, compression, retries, and rate limiting according to New Relic's specifications.\n *\n * Features:\n * - Validates payload size (max 1MB)\n * - Validates number of attributes (max 255)\n * - Validates attribute name length (max 255 characters)\n * - Truncates attribute values longer than 4094 characters\n * - Supports gzip compression\n * - Handles rate limiting with configurable behavior\n * - Implements retry logic with exponential backoff\n */\nexport class NewRelicTransport extends LoggerlessTransport {\n  private apiKey: string;\n  private endpoint: string;\n  private onError?: (err: Error) => void;\n  private onDebug?: (entry: Record<string, any>) => void;\n  private useCompression: boolean;\n  private maxRetries: number;\n  private retryDelay: number;\n  private respectRateLimit: boolean;\n\n  /**\n   * Creates a new instance of NewRelicTransport.\n   *\n   * @param config - Configuration options for the transport\n   * @param config.apiKey - New Relic API key for authentication\n   * @param config.endpoint - Optional custom endpoint URL (defaults to New Relic's Log API endpoint)\n   * @param config.onError - Optional error callback for handling errors\n   * @param config.onDebug - Optional callback for debugging log entries before they are sent\n   * @param config.useCompression - Whether to use gzip compression (defaults to true)\n   * @param config.maxRetries - Maximum number of retry attempts (defaults to 3)\n   * @param config.retryDelay - Base delay between retries in milliseconds (defaults to 1000)\n   * @param config.respectRateLimit - Whether to honor rate limiting headers (defaults to true)\n   */\n  constructor(config: NewRelicTransportConfig) {\n    super(config);\n\n    this.apiKey = config.apiKey;\n    this.endpoint = config.endpoint ?? \"https://log-api.newrelic.com/log/v1\";\n    this.onError = config.onError;\n    this.onDebug = config.onDebug;\n    this.useCompression = config.useCompression ?? true;\n    this.maxRetries = config.maxRetries ?? 3;\n    this.retryDelay = config.retryDelay ?? 1000;\n    this.respectRateLimit = config.respectRateLimit ?? true;\n  }\n\n  /**\n   * Processes and ships log entries to New Relic.\n   *\n   * This method:\n   * 1. Validates the message size\n   * 2. Creates and validates the log entry\n   * 3. Validates the final payload size\n   * 4. Asynchronously sends the log entry to New Relic\n   *\n   * The actual sending is done asynchronously in a fire-and-forget manner to maintain\n   * compatibility with the base transport class while still providing retry and error handling.\n   *\n   * @param params - Log parameters including level, messages, and metadata\n   * @param params.logLevel - The severity level of the log\n   * @param params.messages - Array of message strings to be joined\n   * @param params.data - Optional metadata to include with the log\n   * @param params.hasData - Whether metadata is present\n   * @returns The original messages array\n   * @throws {ValidationError} If the payload exceeds size limits or validation fails\n   */\n  shipToLogger({ logLevel, messages, data, hasData }: LogLayerTransportParams): any[] {\n    try {\n      // Check message size first\n      const message = messages.join(\" \");\n      const messageBytes = new TextEncoder().encode(message).length;\n      if (messageBytes > MAX_PAYLOAD_SIZE) {\n        throw new ValidationError(\n          `Payload size exceeds maximum of ${MAX_PAYLOAD_SIZE} bytes. Size: ${messageBytes} bytes`,\n        );\n      }\n\n      const logEntry: Record<string, any> = {\n        timestamp: Date.now(),\n        level: logLevel,\n        log: message,\n      };\n\n      if (data && hasData) {\n        Object.assign(logEntry, {\n          attributes: data,\n        });\n      }\n\n      const validatedEntry = validateLogEntry(logEntry);\n\n      // Call onDebug callback if defined\n      if (this.onDebug) {\n        this.onDebug(validatedEntry);\n      }\n\n      // Check final payload size\n      const payload = JSON.stringify([validatedEntry]);\n      const payloadBytes = new TextEncoder().encode(payload).length;\n      if (payloadBytes > MAX_PAYLOAD_SIZE) {\n        throw new ValidationError(\n          `Payload size exceeds maximum of ${MAX_PAYLOAD_SIZE} bytes. Size: ${payloadBytes} bytes`,\n        );\n      }\n\n      // Fire and forget the async processing\n      (async () => {\n        try {\n          await sendWithRetry(\n            this.endpoint,\n            this.apiKey,\n            payload,\n            this.useCompression,\n            this.maxRetries,\n            this.retryDelay,\n            this.respectRateLimit,\n          );\n        } catch (error) {\n          if (this.onError) {\n            this.onError(error instanceof Error ? error : new Error(String(error)));\n          }\n          // Re-throw validation errors to prevent further processing\n          if (error instanceof ValidationError) {\n            throw error;\n          }\n        }\n      })();\n    } catch (error) {\n      if (this.onError) {\n        this.onError(error instanceof Error ? error : new Error(String(error)));\n      }\n    }\n\n    return messages;\n  }\n}\n\n/**\n * Compresses data using gzip compression.\n *\n * @param data - The string data to compress\n * @returns A promise that resolves to the compressed data as a Uint8Array\n */\nasync function compressData(data: string): Promise<Uint8Array> {\n  const stream = new CompressionStream(\"gzip\");\n  const writer = stream.writable.getWriter();\n  const encoder = new TextEncoder();\n  const chunks: Uint8Array[] = [];\n\n  await writer.write(encoder.encode(data));\n  await writer.close();\n\n  const reader = stream.readable.getReader();\n\n  while (true) {\n    const { value, done } = await reader.read();\n    if (done) break;\n    chunks.push(value);\n  }\n\n  // Combine all chunks into a single Uint8Array\n  const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0);\n  const result = new Uint8Array(totalLength);\n  let offset = 0;\n  for (const chunk of chunks) {\n    result.set(chunk, offset);\n    offset += chunk.length;\n  }\n\n  return result;\n}\n\n/**\n * Sends a log entry to New Relic with retry logic.\n * Handles rate limiting, compression, and error cases.\n *\n * @param endpoint - The New Relic API endpoint\n * @param apiKey - The New Relic API key\n * @param payload - The JSON payload to send\n * @param useCompression - Whether to use gzip compression\n * @param maxRetries - Maximum number of retry attempts\n * @param retryDelay - Base delay between retries in milliseconds\n * @param respectRateLimit - Whether to honor rate limiting headers\n * @returns A promise that resolves to the API response\n * @throws {ValidationError} If payload validation fails\n * @throws {RateLimitError} If rate limited and not respecting rate limits\n * @throws {Error} If the request fails after all retries\n */\nasync function sendWithRetry(\n  endpoint: string,\n  apiKey: string,\n  payload: string,\n  useCompression: boolean,\n  maxRetries: number,\n  retryDelay: number,\n  respectRateLimit = true,\n): Promise<Response> {\n  // Check payload size before compression\n  const payloadBytes = new TextEncoder().encode(payload).length;\n  if (payloadBytes > MAX_PAYLOAD_SIZE) {\n    throw new ValidationError(`Payload size exceeds maximum of ${MAX_PAYLOAD_SIZE} bytes. Size: ${payloadBytes} bytes`);\n  }\n\n  let lastError: Error;\n  let compressedPayload: Uint8Array | undefined;\n\n  if (useCompression) {\n    compressedPayload = await compressData(payload);\n    // Check compressed payload size\n    if (compressedPayload.length > MAX_PAYLOAD_SIZE) {\n      throw new ValidationError(\n        `Compressed payload size exceeds maximum of ${MAX_PAYLOAD_SIZE} bytes. Size: ${compressedPayload.length} bytes`,\n      );\n    }\n  }\n\n  const headers: Record<string, string> = {\n    \"Content-Type\": \"application/json\",\n    \"Api-Key\": apiKey,\n  };\n\n  if (useCompression) {\n    headers[\"Content-Encoding\"] = \"gzip\";\n  }\n\n  for (let attempt = 0; attempt <= maxRetries; attempt++) {\n    try {\n      const response = await fetch(endpoint, {\n        method: \"POST\",\n        headers,\n        body: useCompression ? compressedPayload : payload,\n      });\n\n      if (response.status === 429) {\n        const retryAfter = Number.parseInt(response.headers.get(\"Retry-After\") || \"60\", 10);\n        if (respectRateLimit) {\n          // Wait for the specified time before retrying\n          await new Promise((resolve) => setTimeout(resolve, retryAfter * 1000));\n          // Don't count rate limit retries against maxRetries\n          attempt--;\n          continue;\n        }\n\n        throw new RateLimitError(`Rate limit exceeded. Retry after ${retryAfter} seconds`, retryAfter);\n      }\n\n      if (!response.ok) {\n        throw new Error(`Failed to send logs to New Relic: ${response.statusText}`);\n      }\n\n      return response;\n    } catch (error) {\n      lastError = error instanceof Error ? error : new Error(String(error));\n\n      // Don't retry validation errors\n      if (error instanceof ValidationError) {\n        throw error;\n      }\n\n      // If we're not respecting rate limits, don't retry rate limit errors\n      if (!respectRateLimit && error instanceof RateLimitError) {\n        throw error;\n      }\n\n      if (attempt === maxRetries) {\n        throw new Error(`Failed to send logs after ${maxRetries} retries: ${lastError.message}`);\n      }\n\n      // For non-rate-limit errors, use exponential backoff with jitter\n      if (!(error instanceof RateLimitError)) {\n        const jitter = Math.random() * 200;\n        const delay = retryDelay * 2 ** attempt + jitter;\n        await new Promise((resolve) => setTimeout(resolve, delay));\n      }\n    }\n  }\n\n  throw lastError!;\n}\n"],"mappings":"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"}