{"version":3,"file":"index.mjs","names":[],"sources":["../../src/next/storage.ts","../../src/next/handler.ts","../../src/next/middleware.ts","../../src/next/index.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks'\nimport type { RequestLogger } from '../types'\n\nexport const mxllogStorage = new AsyncLocalStorage<RequestLogger>()\n\n/**\n * Get the current request-scoped logger.\n * Must be called inside a `withMxllog()` wrapper.\n *\n * @throws {Error} if called outside of `withMxllog()` context\n *\n * @example\n * ```ts\n * export const POST = withMxllog(async (request) => {\n *   const log = useLogger()\n *   log.set({ user: { id: '123' } })\n *   return Response.json({ ok: true })\n * })\n * ```\n */\nexport function useLogger<T extends object = Record<string, unknown>>(): RequestLogger<T> {\n  const logger = mxllogStorage.getStore()\n  if (!logger) {\n    throw new Error(\n      '[mxllog] useLogger() was called outside of a withMxllog() context. '\n      + 'Wrap your route handler or server action with withMxllog().',\n    )\n  }\n  return logger as RequestLogger<T>\n}\n","import type { DrainContext, EnrichContext, TailSamplingContext, WideEvent } from '../types'\nimport { createRequestLogger, initLogger, isEnabled } from '../logger'\nimport { filterSafeHeaders } from '../utils'\nimport { shouldLog, getServiceForPath } from '../shared/routes'\nimport { MxllogError } from '../error'\nimport type { NextMxllogOptions } from './types'\nimport { mxllogStorage } from './storage'\n\ninterface WithMxllogState {\n  initialized: boolean\n  options: NextMxllogOptions\n}\n\nconst state: WithMxllogState = {\n  initialized: false,\n  options: {},\n}\n\nexport function configureHandler(options: NextMxllogOptions): void {\n  state.options = options\n  state.initialized = true\n\n  // Don't pass drain to initLogger — the global drain fires inside emitWideEvent\n  // which doesn't have request/header context. Instead, we call drain ourselves\n  // in callEnrichAndDrain after enrich, with full context.\n  initLogger({\n    enabled: options.enabled,\n    env: {\n      service: options.service,\n      ...options.env,\n    },\n    pretty: options.pretty,\n    sampling: options.sampling,\n    stringify: options.stringify,\n  })\n}\n\nfunction extractRequestInfo(request: Request): { method: string, path: string, headers: Record<string, string> } {\n  const { method } = request\n  const url = new URL(request.url, 'http://localhost')\n  const path = url.pathname\n\n  const headers: Record<string, string> = {}\n  request.headers.forEach((value, key) => {\n    headers[key] = value\n  })\n\n  return { method, path, headers: filterSafeHeaders(headers) }\n}\n\nasync function callEnrichAndDrain(\n  emittedEvent: WideEvent | null,\n  requestInfo: { method: string, path: string, requestId: string },\n  headers: Record<string, string>,\n  responseStatus?: number,\n): Promise<void> {\n  if (!emittedEvent) return\n\n  const { enrich, drain } = state.options\n\n  const run = async () => {\n    if (enrich) {\n      const enrichCtx: EnrichContext = {\n        event: emittedEvent,\n        request: requestInfo,\n        headers,\n        response: { status: responseStatus },\n      }\n      try {\n        await enrich(enrichCtx)\n      } catch (err) {\n        console.error('[mxllog] enrich failed:', err)\n      }\n    }\n\n    if (drain) {\n      const drainCtx: DrainContext = {\n        event: emittedEvent,\n        request: requestInfo,\n        headers,\n      }\n      try {\n        await drain(drainCtx)\n      } catch (err) {\n        console.error('[mxllog] drain failed:', err)\n      }\n    }\n  }\n\n  // Use next/server after() if available to run enrich+drain after response\n  try {\n    const { after } = await import('next/server')\n    if (typeof after === 'function') {\n      after(run)\n      return\n    }\n  } catch {\n    // next/server not available or after() not exported — run inline\n  }\n\n  // Fallback: fire-and-forget (enrich still awaited for correctness)\n  run().catch(() => {})\n}\n\n/**\n * Wrap a Next.js route handler or server action with mxllog request-scoped logging.\n *\n * @example\n * ```ts\n * // Route handler\n * export const POST = withMxllog(async (request: NextRequest) => {\n *   const log = useLogger()\n *   log.set({ user: { id: '123' } })\n *   return Response.json({ success: true })\n * })\n *\n * // Server action\n * export const checkout = withMxllog(async (formData: FormData) => {\n *   const log = useLogger()\n *   log.set({ action: 'checkout' })\n * })\n * ```\n */\nexport function createWithMxllog(options: NextMxllogOptions) {\n  configureHandler(options)\n\n  return function withMxllog<TArgs extends unknown[], TReturn>(\n    handler: (...args: TArgs) => TReturn,\n  ): (...args: TArgs) => Promise<Awaited<TReturn>> {\n    return async (...args: TArgs): Promise<Awaited<TReturn>> => {\n      if (!isEnabled()) {\n        return await handler(...args) as Awaited<TReturn>\n      }\n\n      // Extract request info from first argument if it's a Request\n      const [firstArg] = args\n      const isRequest = firstArg instanceof Request\n\n      let method = 'UNKNOWN'\n      let path = '/'\n      let headers: Record<string, string> = {}\n      let requestId = crypto.randomUUID()\n\n      if (isRequest) {\n        ({ method, path, headers } = extractRequestInfo(firstArg))\n\n        // Reuse request-id from middleware if present\n        const middlewareRequestId = firstArg.headers.get('x-request-id')\n        if (middlewareRequestId) requestId = middlewareRequestId\n      }\n\n      // Check include/exclude patterns\n      if (!shouldLog(path, state.options.include, state.options.exclude)) {\n        return await handler(...args) as Awaited<TReturn>\n      }\n\n      const logger = createRequestLogger({ method, path, requestId })\n\n      // Apply route-based service configuration\n      const routeService = getServiceForPath(path, state.options.routes)\n      if (routeService) {\n        logger.set({ service: routeService })\n      }\n\n      // Apply start time from middleware if present\n      if (isRequest) {\n        const startHeader = firstArg.headers.get('x-mxllog-start')\n        if (startHeader) {\n          logger.set({ middlewareStart: Number(startHeader) })\n        }\n      }\n\n      try {\n        const result = await mxllogStorage.run(logger, () => handler(...args))\n\n        // Extract response status\n        let { status } = { status: 200 }\n        if (result instanceof Response) {\n          ({ status } = result)\n        }\n        logger.set({ status })\n\n        // Build tail sampling context and call keep callback\n        let forceKeep = false\n        if (state.options.keep) {\n          try {\n            const tailCtx: TailSamplingContext = {\n              status,\n              path,\n              method,\n              context: logger.getContext(),\n              shouldKeep: false,\n            }\n            await state.options.keep(tailCtx)\n            forceKeep = tailCtx.shouldKeep ?? false\n          } catch (err) {\n            console.error('[mxllog] keep callback failed:', err)\n          }\n        }\n\n        const emittedEvent = logger.emit({ _forceKeep: forceKeep })\n        await callEnrichAndDrain(emittedEvent, { method, path, requestId }, headers, status)\n\n        return result as Awaited<TReturn>\n      } catch (error) {\n        logger.error(error instanceof Error ? error : new Error(String(error)))\n\n        const errorStatus = (error as { status?: number }).status\n          ?? (error as { statusCode?: number }).statusCode\n          ?? 500\n        logger.set({ status: errorStatus })\n\n        // Build tail sampling context and call keep callback\n        let forceKeep = false\n        if (state.options.keep) {\n          try {\n            const tailCtx: TailSamplingContext = {\n              status: errorStatus,\n              path,\n              method,\n              context: logger.getContext(),\n              shouldKeep: false,\n            }\n            await state.options.keep(tailCtx)\n            forceKeep = tailCtx.shouldKeep ?? false\n          } catch (err) {\n            console.error('[mxllog] keep callback failed:', err)\n          }\n        }\n\n        const emittedEvent = logger.emit({ _forceKeep: forceKeep })\n        await callEnrichAndDrain(emittedEvent, { method, path, requestId }, headers, errorStatus)\n\n        // Return structured JSON response for MxllogErrors (like H3 does for Nuxt)\n        if (isRequest && error instanceof MxllogError) {\n          return Response.json(error.toJSON(), { status: error.status }) as Awaited<TReturn>\n        }\n\n        throw error\n      }\n    }\n  }\n}\n","import { shouldLog } from '../shared/routes'\nimport type { MxllogMiddlewareConfig } from './types'\n\ntype NextRequest = {\n  nextUrl: { pathname: string }\n  headers: { get(name: string): string | null }\n}\n\ntype NextResponse = {\n  headers: { set(name: string, value: string): void }\n}\n\ntype NextResponseStatic = {\n  next(options?: { request?: { headers: Headers } }): NextResponse\n}\n\n/**\n * Create an mxllog middleware for Next.js.\n * Sets `x-request-id` and `x-mxllog-start` headers so `withMxllog()` can reuse them\n * for timing consistency across the middleware -> handler chain.\n *\n * @example\n * ```ts\n * // middleware.ts\n * import { mxllogMiddleware } from '@safaricom-mxl/log/next'\n * export const middleware = mxllogMiddleware()\n * export const config = { matcher: ['/api/:path*'] }\n * ```\n */\nexport function mxllogMiddleware(config?: MxllogMiddlewareConfig) {\n  return async (request: NextRequest) => {\n    const path = request.nextUrl.pathname\n\n    // Check include/exclude patterns\n    if (!shouldLog(path, config?.include, config?.exclude)) {\n      const { NextResponse: nextResponse } = await import('next/server') as { NextResponse: NextResponseStatic }\n      return nextResponse.next()\n    }\n\n    // Generate or reuse request ID\n    const existingId = request.headers.get('x-request-id')\n    const requestId = existingId || crypto.randomUUID()\n\n    // Forward modified headers to the route handler\n    const requestHeaders = new Headers(request.headers as HeadersInit)\n\n    requestHeaders.set('x-request-id', requestId)\n    requestHeaders.set('x-mxllog-start', String(Date.now()))\n\n    const { NextResponse: nextResponse } = await import('next/server') as { NextResponse: NextResponseStatic }\n    const response = nextResponse.next({\n      request: { headers: requestHeaders },\n    })\n\n    // Also set on response for downstream consumers\n    response.headers.set('x-request-id', requestId)\n\n    return response\n  }\n}\n","import { log } from '../logger'\nimport { createError, createMxllogError } from '../error'\nimport type { NextMxllogOptions } from './types'\nimport { createWithMxllog } from './handler'\nimport { useLogger } from './storage'\n\nexport type { NextMxllogOptions, MxllogMiddlewareConfig } from './types'\n\nexport { mxllogMiddleware } from './middleware'\nexport { useLogger } from './storage'\nexport { log } from '../logger'\nexport { createError, createMxllogError } from '../error'\n\n/**\n * Create an mxllog instance configured for Next.js.\n * Returns all helpers needed for server-side logging.\n *\n * @example\n * ```ts\n * // lib/mxllog.ts\n * import { createMxllog } from '@safaricom-mxl/log/next'\n * import { createAxiomDrain } from '@safaricom-mxl/log/axiom'\n * import { createDrainPipeline } from '@safaricom-mxl/log/pipeline'\n *\n * const pipeline = createDrainPipeline({ batch: { size: 50 } })\n *\n * export const { withMxllog, useLogger, log, createMxllogError } = createMxllog({\n *   service: 'my-app',\n *   sampling: {\n *     rates: { info: 10 },\n *     keep: [{ status: 400 }, { duration: 1000 }],\n *   },\n *   drain: pipeline(createAxiomDrain({\n *     dataset: 'logs',\n *     token: process.env.AXIOM_TOKEN!,\n *   })),\n *   enrich: (ctx) => {\n *     ctx.event.deploymentId = process.env.VERCEL_DEPLOYMENT_ID\n *   },\n * })\n * ```\n */\nexport function createMxllog(options: NextMxllogOptions = {}) {\n  const withMxllog = createWithMxllog(options)\n\n  return {\n    withMxllog,\n    useLogger,\n    log,\n    createError,\n    createMxllogError,\n  }\n}\n"],"mappings":"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"}