{"version":3,"file":"createHttpStatusSink-D3tbAjzC.mjs","names":[],"sources":["../../src/components/ClientOnly.tsx","../../src/components/ServerOnly.tsx","../../src/hooks/useDeferred.tsx","../../src/components/Streamed.tsx","../../src/components/HttpStatusProvider.tsx","../../src/components/HttpStatusCode.tsx","../../src/utils/createHttpStatusSink.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\n\nimport type { ReactNode } from \"react\";\n\nexport interface ClientOnlyProps {\n  readonly children: ReactNode;\n  readonly fallback?: ReactNode;\n}\n\nexport function ClientOnly({\n  children,\n  fallback = null,\n}: ClientOnlyProps): ReactNode {\n  const [mounted, setMounted] = useState(false);\n\n  useEffect(() => {\n    // SSR/hydration boundary: server emits the fallback branch, client matches\n    // it on first paint, then this effect flips state to swap in the children.\n    // The intentional re-render is what makes the markup match across renders.\n    // eslint-disable-next-line @eslint-react/set-state-in-effect -- intentional post-hydration swap\n    setMounted(true);\n  }, []);\n\n  return mounted ? children : fallback;\n}\n","import { useEffect, useState } from \"react\";\n\nimport type { ReactNode } from \"react\";\n\nexport interface ServerOnlyProps {\n  readonly children: ReactNode;\n  readonly fallback?: ReactNode;\n}\n\nexport function ServerOnly({\n  children,\n  fallback = null,\n}: ServerOnlyProps): ReactNode {\n  const [mounted, setMounted] = useState(false);\n\n  useEffect(() => {\n    // SSR/hydration boundary: server emits the children branch, client matches\n    // it on first paint, then this effect flips state to swap in the fallback\n    // (or hide entirely). The intentional re-render keeps markup consistent\n    // across renders.\n    // eslint-disable-next-line @eslint-react/set-state-in-effect -- intentional post-hydration swap\n    setMounted(true);\n  }, []);\n\n  return mounted ? fallback : children;\n}\n","import { NEVER_PROMISE } from \"../constants\";\nimport { useRoute } from \"./useRoute\";\n\ninterface DeferredContext {\n  ssrDataDeferred?: Record<string, Promise<unknown>>;\n}\n\n/**\n * Read a deferred promise published by `defer({ deferred: { <key>: Promise } })`\n * inside an SSR data loader.\n *\n * - **Server render**: returns the actual loader-returned promise. Combine\n *   with `<Suspense>` + `use(promise)` for native streaming via React 19's\n *   `renderToReadableStream`.\n * - **Post-hydration**: returns a registry-backed promise; the inline\n *   `<script>__rrDefer__(\"key\", json)</script>` tags emitted by the server\n *   stream resolve it. `use()` returns synchronously once the registry\n *   settles.\n * - **Unknown key**: returns a never-resolving promise — Suspense boundary\n *   stays in fallback. This surfaces consumer/loader key drift as a visible\n *   loading state instead of a silent runtime error.\n *\n * The hook subscribes to `RouteContext`, so it re-runs on every navigation.\n * Promise reference identity is stable across renders within the same\n * navigation — `use()` will not re-suspend on rerenders.\n */\nexport function useDeferred<T = unknown>(key: string): Promise<T> {\n  const { route } = useRoute();\n  const context = route.context as DeferredContext;\n  const deferred = context.ssrDataDeferred;\n  const promise = deferred?.[key];\n\n  return (promise ?? NEVER_PROMISE) as Promise<T>;\n}\n","import { Suspense } from \"react\";\n\nimport type { ReactNode } from \"react\";\n\nexport interface StreamedProps {\n  /** Shown while any descendant `use(promise)` / `<Await>` is pending. */\n  readonly fallback: ReactNode;\n  readonly children: ReactNode;\n}\n\n/**\n * Cross-adapter alias for `<Suspense fallback={…}>`. Pairs with `<Await>`\n * for symmetry with `<Streamed>` boundaries in the SvelteKit / Solid\n * deferred-data conventions.\n *\n * ```tsx\n * <Streamed fallback={<Spinner />}>\n *   <Await<Review[]> name=\"reviews\">\n *     {(reviews) => <ReviewList items={reviews} />}\n *   </Await>\n * </Streamed>\n * ```\n *\n * The component is a thin wrapper around React's native `<Suspense>` — no\n * additional behaviour. Use plain `<Suspense>` directly if you don't need\n * the cross-framework naming alignment.\n */\nexport function Streamed({ fallback, children }: StreamedProps): ReactNode {\n  return <Suspense fallback={fallback}>{children}</Suspense>;\n}\n","import { createContext } from \"react\";\n\nimport type { HttpStatusSink } from \"../utils/createHttpStatusSink\";\nimport type { ReactNode } from \"react\";\n\nexport const HttpStatusContext = createContext<HttpStatusSink | null>(null);\n\nexport interface HttpStatusProviderProps {\n  readonly sink: HttpStatusSink;\n  readonly children: ReactNode;\n}\n\nexport function HttpStatusProvider({\n  sink,\n  children,\n}: HttpStatusProviderProps): ReactNode {\n  // `<HttpStatusContext.Provider value>` (not the React 19 `<HttpStatusContext value>`\n  // shorthand) — same component file is exported via `/legacy/ssr` for React 18\n  // consumers, where the shorthand throws \"Element type is invalid: expected\n  // a string but got: object\".\n  return (\n    <HttpStatusContext.Provider value={sink}>\n      {children}\n    </HttpStatusContext.Provider>\n  );\n}\n","import { useContext } from \"react\";\n\nimport { HttpStatusContext } from \"./HttpStatusProvider\";\n\nimport type { ReactNode } from \"react\";\n\nexport interface HttpStatusCodeProps {\n  /** HTTP status to apply to the response. Common values: 404, 410, 451, 503. */\n  readonly code: number;\n}\n\n/**\n * Render-time HTTP status declaration. Mount inside a route component (typical\n * use case: a glob `*` route's NotFound page) when the status is decided by\n * the rendered tree rather than a loader.\n *\n * Writes `code` to the nearest `<HttpStatusProvider>`'s sink during render and\n * returns `null`. With no provider mounted (the standard client-side case)\n * the component is a silent no-op — same component tree hydrates without\n * touching the DOM or warning about mismatches.\n *\n * Loader-driven errors (`LoaderNotFound` → 404, `LoaderRedirect` → 30x) keep\n * working as before; this component covers render-time decisions only.\n *\n * Last write wins when several `<HttpStatusCode />` instances mount in the\n * same render pass — sink reflects the last component that ran.\n *\n * ```tsx\n * // entry-server.tsx\n * import { renderToString } from \"react-dom/server\";\n * import { createHttpStatusSink, HttpStatusProvider } from \"@real-router/react/ssr\";\n *\n * const sink = createHttpStatusSink();\n * const html = renderToString(\n *   <HttpStatusProvider sink={sink}>\n *     <RouterProvider router={router}>\n *       <App />\n *     </RouterProvider>\n *   </HttpStatusProvider>,\n * );\n * response.status(sink.code ?? 200).send(html);\n * ```\n *\n * **Streaming SSR (`renderToReadableStream`):** the response status MUST be\n * sent before the first body byte flushes. If `<HttpStatusCode />` is mounted\n * inside a late-resolving `<Suspense>` boundary, the sink write may happen\n * AFTER the headers are already on the wire — the override is then lost.\n * Either mount the component in the shell (above every `<Suspense>` that\n * could delay it) or `await stream.allReady` before reading `sink.code`\n * (which forfeits streaming benefits). For non-streaming SSR\n * (`renderToString`) there is no such ordering concern.\n *\n * **Valid `code` range:** Node's `res.end()` throws `Invalid status code` on\n * `NaN`, `0`, negative values, or values `> 999` — this surfaces as a 5xx /\n * dropped connection, not silent corruption. Pass a real HTTP status integer\n * (commonly 4xx/5xx; 100-999 is what Node accepts).\n */\nexport function HttpStatusCode({ code }: HttpStatusCodeProps): ReactNode {\n  const sink = useContext(HttpStatusContext);\n\n  if (sink) {\n    // Dev-only validation: Node's `res.end()` throws `Invalid status code` on\n    // NaN / 0 / negative / non-integer / >999. Surface the bad value at the\n    // source so the consumer can fix the routing logic, instead of waiting\n    // for the server to crash mid-response. Production builds (Vite, esbuild,\n    // tsdown all replace `process.env.NODE_ENV !== \"production\"` with `false`)\n    // strip the check.\n    if (\n      process.env.NODE_ENV !== \"production\" &&\n      (!Number.isInteger(code) || code < 100 || code > 999)\n    ) {\n      console.error(\n        `[real-router] <HttpStatusCode code={${String(code)}} /> received an invalid HTTP status code. Node's res.end() rejects values that are not an integer in [100, 999] — pass a real HTTP status (commonly 4xx/5xx).`,\n      );\n    }\n\n    sink.code = code;\n  }\n\n  return null;\n}\n","/**\n * Render-scoped HTTP status sink. Created per request on the server, passed to\n * `<HttpStatusProvider sink={...}>`, and read after `renderToString` /\n * `renderToReadableStream` to apply the value to the HTTP response.\n *\n * Last write wins: if the rendered tree mounts more than one\n * `<HttpStatusCode />`, the value reflects the last component that ran during\n * the render pass.\n *\n * No-op on the client — `<HttpStatusCode />` reads the optional context and\n * skips the write when no provider is mounted, so the same component tree can\n * be hydrated without changing behaviour.\n *\n * Constraints:\n * - **Per-request only.** Don't share a sink across requests; the rendered\n *   tree mutates `code` in place. Module-level singletons leak status\n *   between concurrent requests.\n * - **Don't `Object.freeze` the sink.** The component writes to `.code`;\n *   freezing makes the assignment throw under ESM strict mode.\n */\nexport interface HttpStatusSink {\n  code: number | undefined;\n}\n\nexport function createHttpStatusSink(): HttpStatusSink {\n  return { code: undefined };\n}\n"],"mappings":"uLASA,SAAgB,EAAW,CACzB,WACA,WAAW,MACkB,CAC7B,GAAM,CAAC,EAAS,GAAc,EAAS,EAAK,EAU5C,OARA,MAAgB,CAKd,EAAW,EAAI,CACjB,EAAG,CAAC,CAAC,EAEE,EAAU,EAAW,CAC9B,CCfA,SAAgB,EAAW,CACzB,WACA,WAAW,MACkB,CAC7B,GAAM,CAAC,EAAS,GAAc,EAAS,EAAK,EAW5C,OATA,MAAgB,CAMd,EAAW,EAAI,CACjB,EAAG,CAAC,CAAC,EAEE,EAAU,EAAW,CAC9B,CCCA,SAAgB,EAAyB,EAAyB,CAChE,GAAM,CAAE,SAAU,EAAS,EAK3B,OAJgB,EAAM,QACG,kBACE,IAER,CACrB,CCNA,SAAgB,EAAS,CAAE,WAAU,YAAsC,CACzE,OAAO,EAAC,EAAD,CAAoB,WAAW,UAAmB,CAAA,CAC3D,CCxBA,MAAa,EAAoB,EAAqC,IAAI,EAO1E,SAAgB,EAAmB,CACjC,OACA,YACqC,CAKrC,OACE,EAAC,EAAkB,SAAnB,CAA4B,MAAO,EAChC,UACyB,CAAA,CAEhC,CCgCA,SAAgB,EAAe,CAAE,QAAwC,CACvE,IAAM,EAAO,EAAW,CAAiB,EAqBzC,OAnBI,IAQA,QAAQ,IAAI,WAAa,eACxB,CAAC,OAAO,UAAU,CAAI,GAAK,EAAO,KAAO,EAAO,MAEjD,QAAQ,MACN,uCAAuC,OAAO,CAAI,EAAE,+JACtD,EAGF,EAAK,KAAO,GAGP,IACT,CCxDA,SAAgB,GAAuC,CACrD,MAAO,CAAE,KAAM,IAAA,EAAU,CAC3B"}