{"version":3,"file":"pipeline.mjs","names":[],"sources":["../src/pipeline.ts"],"sourcesContent":["export interface DrainPipelineOptions<T = unknown> {\n  batch?: {\n    /** Maximum number of events per batch sent to the drain function. @default 50 */\n    size?: number\n    /** Maximum time (ms) an event can stay buffered before a flush is triggered, even if the batch is not full. @default 5000 */\n    intervalMs?: number\n  }\n  retry?: {\n    /** Total number of attempts (including the initial one) before dropping the batch. @default 3 */\n    maxAttempts?: number\n    /** Delay strategy between retry attempts. @default 'exponential' */\n    backoff?: 'exponential' | 'linear' | 'fixed'\n    /** Base delay (ms) for the first retry. Scaled by the backoff strategy on subsequent retries. @default 1000 */\n    initialDelayMs?: number\n    /** Upper bound (ms) for any single retry delay. @default 30000 */\n    maxDelayMs?: number\n  }\n  /** Maximum number of events held in the buffer. When exceeded, the oldest event is dropped. @default 1000 */\n  maxBufferSize?: number\n  /** Called when a batch is dropped after all retry attempts are exhausted, or when the buffer overflows. */\n  onDropped?: (events: T[], error?: Error) => void\n}\n\nexport interface PipelineDrainFn<T> {\n  (ctx: T): void\n  /** Flush all buffered events. Call on server shutdown. */\n  flush: () => Promise<void>\n  readonly pending: number\n}\n\n/**\n * Create a drain pipeline that batches events, retries on failure, and manages buffer overflow.\n *\n * Returns a higher-order function: pass your drain adapter to get a hook-compatible function.\n *\n * @example\n * ```ts\n * const pipeline = createDrainPipeline({ batch: { size: 50 } })\n * const drain = pipeline(async (batch) => {\n *   await sendToBackend(batch)\n * })\n *\n * // Use as a hook\n * nitroApp.hooks.hook('mxllog:drain', drain)\n *\n * // Flush on shutdown\n * nitroApp.hooks.hook('close', () => drain.flush())\n * ```\n */\nexport function createDrainPipeline<T = unknown>(options?: DrainPipelineOptions<T>): (drain: (batch: T[]) => void | Promise<void>) => PipelineDrainFn<T> {\n  const batchSize = options?.batch?.size ?? 50\n  const intervalMs = options?.batch?.intervalMs ?? 5000\n  const maxBufferSize = options?.maxBufferSize ?? 1000\n  const maxAttempts = options?.retry?.maxAttempts ?? 3\n  const backoffStrategy = options?.retry?.backoff ?? 'exponential'\n  const initialDelayMs = options?.retry?.initialDelayMs ?? 1000\n  const maxDelayMs = options?.retry?.maxDelayMs ?? 30000\n  const onDropped = options?.onDropped\n\n  if (batchSize <= 0 || !Number.isFinite(batchSize)) {\n    throw new Error(`[mxllog/pipeline] batch.size must be a positive finite number, got: ${batchSize}`)\n  }\n  if (intervalMs <= 0 || !Number.isFinite(intervalMs)) {\n    throw new Error(`[mxllog/pipeline] batch.intervalMs must be a positive finite number, got: ${intervalMs}`)\n  }\n  if (maxBufferSize <= 0 || !Number.isFinite(maxBufferSize)) {\n    throw new Error(`[mxllog/pipeline] maxBufferSize must be a positive finite number, got: ${maxBufferSize}`)\n  }\n  if (maxAttempts <= 0 || !Number.isFinite(maxAttempts)) {\n    throw new Error(`[mxllog/pipeline] retry.maxAttempts must be a positive finite number, got: ${maxAttempts}`)\n  }\n  if (initialDelayMs < 0 || !Number.isFinite(initialDelayMs)) {\n    throw new Error(`[mxllog/pipeline] retry.initialDelayMs must be a non-negative finite number, got: ${initialDelayMs}`)\n  }\n  if (maxDelayMs < 0 || !Number.isFinite(maxDelayMs)) {\n    throw new Error(`[mxllog/pipeline] retry.maxDelayMs must be a non-negative finite number, got: ${maxDelayMs}`)\n  }\n\n  return (drain: (batch: T[]) => void | Promise<void>): PipelineDrainFn<T> => {\n    const buffer: T[] = []\n    let timer: ReturnType<typeof setTimeout> | null = null\n    let activeFlush: Promise<void> | null = null\n\n    function clearTimer(): void {\n      if (timer !== null) {\n        clearTimeout(timer)\n        timer = null\n      }\n    }\n\n    function scheduleFlush(): void {\n      if (timer !== null || activeFlush) return\n      timer = setTimeout(() => {\n        timer = null\n        if (!activeFlush) startFlush()\n      }, intervalMs)\n    }\n\n    function getRetryDelay(attempt: number): number {\n      let delay: number\n      switch (backoffStrategy) {\n        case 'linear':\n          delay = initialDelayMs * attempt\n          break\n        case 'fixed':\n          delay = initialDelayMs\n          break\n        case 'exponential':\n        default:\n          delay = initialDelayMs * 2 ** (attempt - 1)\n          break\n      }\n      return Math.min(delay, maxDelayMs)\n    }\n\n    async function sendWithRetry(batch: T[]): Promise<void> {\n      let lastError: Error | undefined\n      for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n        try {\n          await drain(batch)\n          return\n        } catch (error) {\n          lastError = error instanceof Error ? error : new Error(String(error))\n          if (attempt < maxAttempts) {\n            await new Promise<void>(r => setTimeout(r, getRetryDelay(attempt)))\n          }\n        }\n      }\n      onDropped?.(batch, lastError)\n    }\n\n    async function drainBuffer(): Promise<void> {\n      while (buffer.length > 0) {\n        const batch = buffer.splice(0, batchSize)\n        await sendWithRetry(batch)\n      }\n    }\n\n    function startFlush(): void {\n      if (activeFlush) return\n      activeFlush = drainBuffer().finally(() => {\n        activeFlush = null\n        if (buffer.length >= batchSize) {\n          startFlush()\n        } else if (buffer.length > 0) {\n          scheduleFlush()\n        }\n      })\n    }\n\n    function push(ctx: T): void {\n      if (buffer.length >= maxBufferSize) {\n        const dropped = buffer.splice(0, 1)\n        onDropped?.(dropped)\n      }\n      buffer.push(ctx)\n\n      if (buffer.length >= batchSize) {\n        clearTimer()\n        startFlush()\n      } else if (!activeFlush) {\n        scheduleFlush()\n      }\n    }\n\n    async function flush(): Promise<void> {\n      clearTimer()\n      if (activeFlush) {\n        await activeFlush\n      }\n      // Snapshot the buffer length to avoid infinite loop if push() is called during flush\n      const snapshot = buffer.length\n      if (snapshot > 0) {\n        const toFlush = buffer.splice(0, snapshot)\n        while (toFlush.length > 0) {\n          const batch = toFlush.splice(0, batchSize)\n          await sendWithRetry(batch)\n        }\n      }\n    }\n\n    const hookFn = push as PipelineDrainFn<T>\n    hookFn.flush = flush\n    Object.defineProperty(hookFn, 'pending', {\n      get: () => buffer.length,\n      enumerable: true,\n    })\n\n    return hookFn\n  }\n}\n"],"mappings":"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"}