{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "use-consumption-history",
  "title": "useConsumptionHistory",
  "description": "Fetches consumption history through your own proxy route and shapes it into buckets and totals.",
  "registryDependencies": [
    "https://ui.neon.com/r/consumption.json"
  ],
  "files": [
    {
      "path": "src/hooks/use-consumption-history.ts",
      "content": "\"use client\";\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\n\nimport type {\n  ConsumptionBucket,\n  ConsumptionGranularity,\n  ConsumptionMetricName,\n  ConsumptionPeriod,\n  ConsumptionTotals,\n} from \"@/lib/consumption\";\nimport {\n  CONSUMPTION_METRICS,\n  flattenConsumption,\n  sumBuckets,\n} from \"@/lib/consumption\";\n\n/**\n * Neon refreshes consumption roughly every 15 minutes and the endpoints\n * share a ~50 req/min bucket, so anything faster than this just burns\n * quota on numbers that haven't moved.\n */\nexport const MIN_POLL_INTERVAL_MS = 900_000;\n\nexport interface UseConsumptionHistoryOptions {\n  /**\n   * Your own route handler that proxies the Neon consumption API. The key\n   * is server-only, so the browser must never call console.neon.tech\n   * directly. Defaults to \"/api/consumption\".\n   */\n  endpoint?: string;\n  /** RFC 3339 start of the window. */\n  from: string;\n  /** RFC 3339 end of the window. */\n  to: string;\n  granularity: ConsumptionGranularity;\n  /** Defaults to every metric the v2 project endpoint returns. */\n  metrics?: readonly ConsumptionMetricName[];\n  /** Narrow to specific projects; omit for the whole org. */\n  projectIds?: readonly string[];\n  /** Re-fetch on an interval. Clamped to 15 minutes. */\n  pollIntervalMs?: number;\n  /** Set false to hold the request until you're ready. */\n  enabled?: boolean;\n}\n\nexport interface UseConsumptionHistoryResult {\n  /** One entry per timeframe, oldest first, raw API units. */\n  buckets: ConsumptionBucket[];\n  /** Every metric summed across the window, raw API units. */\n  totals: ConsumptionTotals;\n  /** The untouched response, for per-project or per-branch splitting. */\n  periods: ConsumptionPeriod[];\n  isLoading: boolean;\n  error: string | null;\n  /** Fetched-at timestamp; pair it with a metering-lag notice. */\n  updatedAt: Date | null;\n  refresh: () => void;\n}\n\ninterface ConsumptionResponse {\n  projects?: { project_id: string; periods: ConsumptionPeriod[] }[];\n  branches?: { branch_id: string; periods: ConsumptionPeriod[] }[];\n}\n\nconst buildQuery = (options: UseConsumptionHistoryOptions) => {\n  const params = new URLSearchParams({\n    from: options.from,\n    granularity: options.granularity,\n    metrics: (options.metrics ?? CONSUMPTION_METRICS).join(\",\"),\n    to: options.to,\n  });\n\n  if (options.projectIds?.length) {\n    params.set(\"project_ids\", options.projectIds.join(\",\"));\n  }\n\n  return params.toString();\n};\n\n/** Both v2 shapes nest the same periods; take whichever key came back. */\nconst readPeriods = (payload: ConsumptionResponse): ConsumptionPeriod[] => {\n  const holders = payload.projects ?? payload.branches ?? [];\n\n  return holders.flatMap((holder) => holder.periods);\n};\n\n/**\n * Fetches consumption history through your own proxy route and shapes it\n * for charts and cards: flat buckets, summed totals, and the raw periods.\n *\n * The values stay in raw API units (CU-seconds, byte-hours, branch-hours).\n * Convert at the display edge with `toBillingUnit` and friends so the same\n * numbers can feed a chart, a breakdown, and a cost estimate.\n */\nexport const useConsumptionHistory = (\n  options: UseConsumptionHistoryOptions\n): UseConsumptionHistoryResult => {\n  const {\n    enabled = true,\n    endpoint = \"/api/consumption\",\n    pollIntervalMs,\n  } = options;\n\n  /**\n   * One state object, tagged with the request it answers. Loading is\n   * derived from that tag rather than set at the top of the effect: no\n   * render cascade, and no window where stale data shows without a\n   * pending state on refetch.\n   */\n  const [settled, setSettled] = useState<{\n    key: string;\n    periods: ConsumptionPeriod[];\n    error: string | null;\n    updatedAt: Date;\n  } | null>(null);\n  const [nonce, setNonce] = useState(0);\n\n  const query = buildQuery(options);\n  const requestKey = `${endpoint}?${query}#${nonce}`;\n  const abortRef = useRef<AbortController | null>(null);\n\n  const refresh = useCallback(() => setNonce((value) => value + 1), []);\n\n  useEffect(() => {\n    if (!enabled) {\n      return;\n    }\n\n    const controller = new AbortController();\n    abortRef.current?.abort();\n    abortRef.current = controller;\n\n    const load = async () => {\n      try {\n        const response = await fetch(`${endpoint}?${query}`, {\n          signal: controller.signal,\n        });\n\n        if (!response.ok) {\n          throw new Error(`Consumption request failed (${response.status})`);\n        }\n\n        const payload = (await response.json()) as ConsumptionResponse;\n\n        setSettled({\n          error: null,\n          key: requestKey,\n          periods: readPeriods(payload),\n          updatedAt: new Date(),\n        });\n      } catch (error) {\n        if (controller.signal.aborted) {\n          return;\n        }\n\n        // Keep the last good numbers on screen; the error line says why\n        // they stopped moving.\n        setSettled((previous) => ({\n          error: error instanceof Error ? error.message : \"Request failed\",\n          key: requestKey,\n          periods: previous?.periods ?? [],\n          updatedAt: new Date(),\n        }));\n      }\n    };\n\n    void load();\n\n    const interval =\n      pollIntervalMs === undefined\n        ? null\n        : window.setInterval(\n            () => void load(),\n            Math.max(pollIntervalMs, MIN_POLL_INTERVAL_MS)\n          );\n\n    return () => {\n      controller.abort();\n\n      if (interval !== null) {\n        window.clearInterval(interval);\n      }\n    };\n  }, [enabled, endpoint, query, pollIntervalMs, requestKey]);\n\n  const periods = settled?.periods ?? [];\n  const buckets = flattenConsumption(periods);\n\n  return {\n    buckets,\n    error: settled?.error ?? null,\n    isLoading: enabled && settled?.key !== requestKey,\n    periods,\n    refresh,\n    totals: sumBuckets(buckets),\n    updatedAt: settled?.updatedAt ?? null,\n  };\n};\n",
      "type": "registry:hook"
    }
  ],
  "type": "registry:hook"
}