{"version":3,"sources":["../src/koa.ts","../src/utils.ts"],"sourcesContent":["import type { SnapWyrConfig, LogEntry } from '@snapwyr/core';\nimport { generateRequestId } from '@snapwyr/core';\nimport { logRequest } from './utils.js';\n\ninterface KoaRequest {\n  method: string;\n  url: string;\n  body?: unknown;\n}\ninterface KoaContext {\n  method: string;\n  url: string;\n  status: number;\n  request: KoaRequest;\n  body?: unknown;\n  set: (field: string, val: string) => void;\n}\ntype KoaNext = () => Promise<void>;\ntype KoaMiddleware = (ctx: KoaContext, next: KoaNext) => Promise<void>;\n\nexport function snapwyr(config: SnapWyrConfig = {}): KoaMiddleware {\n  return async function (ctx, next) {\n    if (config.enabled === false) {\n      await next();\n      return;\n    }\n\n    const id = generateRequestId();\n    const startTime = Date.now();\n\n    if (config.requestId)\n      try {\n        ctx.set('X-Request-ID', id);\n      } catch {}\n\n    await next();\n\n    const duration = Date.now() - startTime;\n    const status = ctx.status || 200;\n\n    if (\n      config.statusCodes &&\n      config.statusCodes.length > 0 &&\n      !config.statusCodes.includes(status)\n    )\n      return;\n\n    let requestBody: string | undefined, responseBody: string | undefined;\n    if (config.logBody) {\n      try {\n        if (ctx.request.body) {\n          requestBody =\n            typeof ctx.request.body === 'string'\n              ? ctx.request.body\n              : JSON.stringify(ctx.request.body);\n          if (config.bodySizeLimit)\n            requestBody = requestBody.slice(0, config.bodySizeLimit);\n        }\n        if (ctx.body) {\n          responseBody =\n            typeof ctx.body === 'string' ? ctx.body : JSON.stringify(ctx.body);\n          if (config.bodySizeLimit)\n            responseBody = responseBody.slice(0, config.bodySizeLimit);\n        }\n      } catch {}\n    }\n\n    logRequest({\n      id,\n      method: ctx.method,\n      status,\n      duration,\n      url: ctx.url,\n      startTime,\n      config,\n      requestBody: config.logBody ? requestBody : undefined,\n      responseBody: config.logBody ? responseBody : undefined,\n    });\n  };\n}\n\nexport type { SnapWyrConfig, LogEntry };\n","import type { SnapWyrConfig, LogEntry } from '@snapwyr/core';\nimport {\n  snapwyr as coreEmitter,\n  getByteSize,\n  formatBytes,\n  redactSensitiveData,\n} from '@snapwyr/core';\n\nexport interface LogParams {\n  id: string;\n  method: string;\n  status: number;\n  duration: number;\n  url: string;\n  startTime: number;\n  config: SnapWyrConfig;\n  requestBody?: string;\n  responseBody?: string;\n  error?: string;\n}\n\nexport function logRequest(params: LogParams): void {\n  const { id, method, status, duration, url, startTime, config, error } =\n    params;\n  let { requestBody, responseBody } = params;\n\n  if (config.silent) return;\n\n  const showTimestamp = config.showTimestamp !== false;\n  const format = config.format || 'pretty';\n  const slowThreshold = config.slowThreshold ?? 1000;\n  const isSlow = duration >= slowThreshold;\n\n  const requestSize = requestBody ? getByteSize(requestBody) : 0;\n  const responseSize = responseBody ? getByteSize(responseBody) : 0;\n  const totalSize = requestSize + responseSize;\n\n  if (config.redact && config.redact.length > 0) {\n    if (requestBody)\n      requestBody = redactSensitiveData(requestBody, config.redact);\n    if (responseBody)\n      responseBody = redactSensitiveData(responseBody, config.redact);\n  }\n\n  const logEntry: LogEntry = {\n    id,\n    timestamp: new Date(startTime).toISOString(),\n    method: method.toUpperCase(),\n    url,\n    status,\n    duration,\n    slow: isSlow,\n  };\n\n  if (config.prefix) logEntry.prefix = config.prefix;\n  if (error) logEntry.error = error;\n  if (requestBody) logEntry.requestBody = requestBody;\n  if (responseBody) logEntry.responseBody = responseBody;\n  if (config.sizeTracking) {\n    logEntry.requestSize = requestSize;\n    logEntry.responseSize = responseSize;\n    logEntry.totalSize = totalSize;\n  }\n\n  try {\n    coreEmitter.emit('request', {\n      id,\n      method: method.toUpperCase(),\n      url,\n      status,\n      duration,\n      timestamp: startTime,\n      requestBody,\n      responseBody,\n      error,\n      requestSize: config.sizeTracking ? requestSize : undefined,\n      responseSize: config.sizeTracking ? responseSize : undefined,\n      direction: 'incoming',\n    });\n  } catch {}\n\n  if (config.transport) {\n    config.transport(logEntry);\n  }\n\n  if (format === 'json') {\n    console.log(JSON.stringify(logEntry));\n    return;\n  }\n\n  const useEmoji = config.emoji === true;\n  let statusEmoji = '';\n  if (useEmoji) {\n    if (error || status >= 500) statusEmoji = '✗ ';\n    else if (status >= 400) statusEmoji = '⚠ ';\n    else if (status >= 300) statusEmoji = '↪ ';\n    else statusEmoji = '✓ ';\n  }\n\n  const statusColor =\n    status >= 500\n      ? '\\x1b[31m'\n      : status >= 400\n        ? '\\x1b[33m'\n        : status >= 300\n          ? '\\x1b[36m'\n          : '\\x1b[32m';\n  const durationColor = isSlow\n    ? '\\x1b[31m'\n    : duration < 100\n      ? '\\x1b[32m'\n      : '\\x1b[33m';\n  const methodColors: Record<string, string> = {\n    GET: '\\x1b[34m',\n    POST: '\\x1b[32m',\n    PUT: '\\x1b[33m',\n    PATCH: '\\x1b[35m',\n    DELETE: '\\x1b[31m',\n  };\n  const methodColor = methodColors[method] || '';\n  const reset = '\\x1b[0m';\n  const dim = '\\x1b[2m';\n  const bold = '\\x1b[1m';\n  const timestamp = showTimestamp\n    ? new Date(startTime).toISOString().slice(11, 23) + ' '\n    : '';\n  const slowIndicator = isSlow ? ` ${bold}[SLOW]${reset}` : '';\n  const requestIdDisplay = config.requestId ? `${dim}[${id}]${reset} ` : '';\n  const sizeDisplay = config.sizeTracking\n    ? `${dim}${formatBytes(totalSize)}${reset} `\n    : '';\n\n  const parts = [\n    config.prefix ? `${dim}${config.prefix} ${reset}` : '',\n    requestIdDisplay,\n    `${dim}${timestamp}${reset}`,\n    `${methodColor}${method.padEnd(6)}${reset}`,\n    `${statusColor}${statusEmoji}${status}${reset}`,\n    `${durationColor}${duration}ms${reset}${slowIndicator}`,\n    sizeDisplay,\n    `${dim}${url}${reset}`,\n  ].filter(Boolean);\n\n  if (error) parts.push(`\\n  ${dim}Error: ${error}${reset}`);\n  if (requestBody) parts.push(`\\n  ${dim}Request: ${requestBody}${reset}`);\n  if (responseBody) parts.push(`\\n  ${dim}Response: ${responseBody}${reset}`);\n\n  console.log(parts.join(' '));\n}\n"],"mappings":"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","names":[]}