{"version":3,"sources":["../src/serverStore.ts","../src/interceptor.ts","../src/routeHandler.ts"],"sourcesContent":["/**\n * @file serverStore.ts\n * @description Server-side in-memory cache for SSR API request logs.\n *\n * Uses `globalThis` so the array survives Turbopack / Webpack Fast Refresh\n * module re-evaluation. Without this, every HMR update would wipe the cache\n * and the polling client would see an empty list.\n *\n * ⚠️  This file must NEVER be imported from client-side code.\n *     It is intentionally free of any 'use client' directive and relies on\n *     Next.js server/client bundle splitting to stay server-only.\n */\n\nimport type { ApiRequest } from './types';\n\n// ─── GlobalThis Augmentation ──────────────────────────────────────────────────\n\ndeclare global {\n  // eslint-disable-next-line no-var\n  var __apiDebuggerRequests: ApiRequest[] | undefined;\n}\n\n// ─── Lazy initialisation ──────────────────────────────────────────────────────\n\n/**\n * Returns the global SSR request array, initialising it on first access.\n * Using a getter function (rather than a module-level `const`) ensures that\n * every call always reads the *current* globalThis reference even after HMR.\n */\nfunction getStore(): ApiRequest[] {\n  if (!globalThis.__apiDebuggerRequests) {\n    globalThis.__apiDebuggerRequests = [];\n  }\n  return globalThis.__apiDebuggerRequests;\n}\n\n// ─── Public API ───────────────────────────────────────────────────────────────\n\n/** Returns a **copy** of all currently stored SSR requests (newest first). */\nexport function getServerRequests(): ApiRequest[] {\n  return [...getStore()];\n}\n\n/**\n * Appends a new SSR request to the global cache.\n * Respects the optional `maxRequests` cap (defaults to 200 server-side\n * to match reasonable SSR traffic while keeping memory bounded).\n */\nexport function addServerRequest(\n  request: ApiRequest,\n  maxRequests = 200,\n): void {\n  const store = getStore();\n  store.unshift(request);\n  if (store.length > maxRequests) {\n    store.length = maxRequests;\n  }\n}\n\n/**\n * Patches an existing SSR request by ID (used in `onResponse` to update\n * `status`, `statusCode`, and `duration`).\n */\nexport function updateServerRequest(\n  id: string,\n  patch: Partial<Pick<ApiRequest, 'status' | 'statusCode' | 'duration'>>,\n): void {\n  const store = getStore();\n  const idx = store.findIndex((r) => r.id === id);\n  if (idx !== -1) {\n    store[idx] = { ...store[idx]!, ...patch } as ApiRequest;\n  }\n}\n\n/** Removes all SSR requests from the global cache. */\nexport function clearServerRequests(): void {\n  globalThis.__apiDebuggerRequests = [];\n}\n","/**\n * @file interceptor.ts\n * @description Factory function that creates an openapi-fetch-compatible middleware\n * for the API Visual Debugger.\n *\n * Usage:\n *   import { createDebugInterceptor } from '@/lib/api-debugger';\n *   client.use(createDebugInterceptor({ position: 'bottom-right', urlFilters: ['vrid'] }));\n *\n * CORS Safety:\n *   We intentionally avoid writing the tracking ID to HTTP headers.\n *   Instead we mutate the in-memory Request object: (request as any)._debugId = id\n *   This prevents the browser from sending a CORS preflight for a custom header.\n *\n * SSR / Client routing:\n *   - typeof window === 'undefined'  →  server environment  →  writes to serverStore\n *   - typeof window !== 'undefined'  →  browser environment →  writes to Zustand store\n *\n *   The import of `serverStore` at the top of this file is intentionally static.\n *   Next.js compiles separate server and client bundles; because this file is used\n *   server-side (via Server Components / server actions) the import is safe and\n *   will NOT be included in the browser bundle.\n */\n\nimport {\n  addServerRequest,\n  getServerRequests,\n  updateServerRequest,\n} from './serverStore';\nimport { useApiDebuggerStore } from './store';\nimport { ApiRequest, DebuggerConfig, DEFAULT_CONFIG } from './types';\nimport {\n  captureStackTrace,\n  formatStackFrames,\n  matchesUrlFilters,\n  parseStackTrace,\n} from './utils';\n\nimport type { Middleware } from 'openapi-fetch';\n\n// ─── Microtask-batched store writer ───────────────────────────────────────────\n\ntype Patch = Partial<Pick<ApiRequest, 'status' | 'statusCode' | 'duration'>>;\n\nlet pendingAdds: ApiRequest[] = [];\nlet pendingUpdates: Array<{ id: string; patch: Patch }> = [];\nlet flushScheduled = false;\n\n// ─── Duplicate-instance safeguard ─────────────────────────────────────────────\n\nconst PROCESSED_SYMBOL: symbol =\n    (globalThis as any).__apiDebuggerProcessedSymbol ??\n    ((globalThis as any).__apiDebuggerProcessedSymbol = Symbol.for(\n        '__apiDebuggerProcessed',\n    ));\n\nfunction scheduleClientFlush(): void {\n  if (flushScheduled) return;\n  flushScheduled = true;\n  queueMicrotask(() => {\n    flushScheduled = false;\n    if (pendingAdds.length === 0 && pendingUpdates.length === 0) return;\n    const adds = pendingAdds;\n    const updates = pendingUpdates;\n    pendingAdds = [];\n    pendingUpdates = [];\n    useApiDebuggerStore.getState().applyBatch(adds, updates);\n  });\n}\n\n// ─── Internal helpers ─────────────────────────────────────────────────────────\n\nfunction generateId(): string {\n  return `${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;\n}\n\n// ─── Factory ──────────────────────────────────────────────────────────────────\n\nexport function createDebugInterceptor(\n    config?: Partial<DebuggerConfig>,\n): Middleware {\n  const resolvedConfig: DebuggerConfig = { ...DEFAULT_CONFIG, ...config };\n\n  // ── Disabled short-circuit ────────────────────────────────────────────────\n  if (!resolvedConfig.enabled) {\n    return {\n      onRequest: ({ request }) => request,\n      onResponse: ({ response }) => response,\n    };\n  }\n\n  // Only seed the client Zustand store — the server has no UI to update.\n  if (typeof window !== 'undefined') {\n    (globalThis as any).__apiDebuggerClearOnReload = resolvedConfig.clearOnReload;\n\n    useApiDebuggerStore.getState().setConfig(resolvedConfig);\n\n    const persistApi = (useApiDebuggerStore as unknown as {\n      persist?: { rehydrate: () => Promise<void> | void };\n    }).persist;\n    if (persistApi && !(globalThis as any).__apiDebuggerHydrated) {\n      (globalThis as any).__apiDebuggerHydrated = true;\n      void persistApi.rehydrate();\n    }\n\n    if (\n        resolvedConfig.clearOnReload &&\n        !(globalThis as any).__apiDebuggerClearedOnMount\n    ) {\n      (globalThis as any).__apiDebuggerClearedOnMount = true;\n      useApiDebuggerStore.getState().clearRequests();\n    }\n  }\n\n  return {\n    // ── onRequest ────────────────────────────────────────────────────────────\n    onRequest({ request }) {\n      if ((request as any)[PROCESSED_SYMBOL]) return request;\n      (request as any)[PROCESSED_SYMBOL] = true;\n\n      if (!matchesUrlFilters(request.url, resolvedConfig.urlFilters)) {\n        return request;\n      }\n\n      // 1. Захоплюємо стек ДО перевірки на logToConsole\n      const rawStack = captureStackTrace();\n\n      // 2. Виводимо наш очищений стек замість console.trace()\n      if (resolvedConfig.logToConsole) {\n        console.groupCollapsed(`📡 API: ${request.method} ${request.url}`);\n        console.trace('Trace');\n        console.groupEnd();\n      }\n\n      const isServer = typeof window === 'undefined';\n      const id = generateId();\n\n      const entry: ApiRequest = {\n        id,\n        url: request.url,\n        method: request.method,\n        status: 'pending',\n        statusCode: null,\n        startedAt: Date.now(),\n        duration: null,\n        environment: isServer ? 'SSR' : 'Client',\n        stackTrace: rawStack,\n        stackFrames: undefined,\n        trigger: undefined,\n      };\n\n      if (isServer) {\n        // ── SSR path: write to the global server-side cache ──────────────────\n        addServerRequest(entry, resolvedConfig.maxRequests);\n      } else {\n        // ── Client path: queue for microtask-batched flush ───────────────────\n        pendingAdds.push(entry);\n        scheduleClientFlush();\n      }\n\n      // Зберігаємо ID у request для onResponse\n      (request as any)._debugId = id;\n\n      return request;\n    },\n\n    // ── onResponse ───────────────────────────────────────────────────────────\n    onResponse({ request, response }) {\n      // Дістаємо ID, який ми зберегли в onRequest\n      const id = (request as any)._debugId as string | undefined;\n\n      // Якщо ID немає, значить запит був відфільтрований — просто повертаємо response\n      if (!id) return response;\n\n      const isServer = typeof window === 'undefined';\n\n      if (isServer) {\n        // ── SSR path: look up startedAt from serverStore, then patch it ──────\n        const startedAt =\n            getServerRequests().find((r) => r.id === id)?.startedAt ?? Date.now();\n\n        updateServerRequest(id, {\n          status: response.ok ? 'success' : 'error',\n          statusCode: response.status,\n          duration: Date.now() - startedAt,\n        });\n      } else {\n        // ── Client path: look up startedAt from the live store OR the\n        // pending-adds buffer\n        const pendingEntry = pendingAdds.find((r) => r.id === id);\n        const startedAt =\n            pendingEntry?.startedAt ??\n            useApiDebuggerStore.getState().requests.find((r) => r.id === id)\n                ?.startedAt ??\n            Date.now();\n\n        pendingUpdates.push({\n          id,\n          patch: {\n            status: response.ok ? 'success' : 'error',\n            statusCode: response.status,\n            duration: Date.now() - startedAt,\n          },\n        });\n        scheduleClientFlush();\n      }\n\n      return response;\n    },\n  };\n}","import { NextResponse } from 'next/server';\nimport { clearServerRequests, getServerRequests } from './serverStore';\n\n/**\n * Factory that returns the { GET, DELETE } handlers for the SSR debug bridge.\n *\n * @param options.enabled - When provided, explicitly toggles the route on/off.\n *                          When omitted, falls back to\n *                          `process.env.NODE_ENV === 'development'`.\n */\nexport function createApiDebuggerRouteHandler(options?: { enabled?: boolean }) {\n    const isEnabled = options?.enabled ?? process.env.NODE_ENV === 'development';\n\n    return {\n        GET: () => {\n            if (!isEnabled) {\n                return NextResponse.json({ error: 'Not found' }, { status: 404 });\n            }\n            return NextResponse.json(getServerRequests(), {\n                headers: { 'Cache-Control': 'no-store, no-cache, must-revalidate' },\n            });\n        },\n        DELETE: () => {\n            if (!isEnabled) {\n                return NextResponse.json({ error: 'Not found' }, { status: 404 });\n            }\n            clearServerRequests();\n            return new NextResponse(null, { status: 204 });\n        },\n    };\n}\n"],"mappings":"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","names":[]}