{"version":3,"file":"ssr.mjs","names":[],"sources":["../../src/components/ClientOnly.tsx","../../src/components/ServerOnly.tsx","../../src/hooks/useDeferred.tsx","../../src/components/Await.tsx","../../src/components/Streamed.tsx","../../src/components/HttpStatusProvider.tsx","../../src/components/HttpStatusCode.tsx","../../src/utils/createHttpStatusSink.ts"],"sourcesContent":["import { useEffect, useState } from \"preact/hooks\";\n\nimport type { ComponentChildren } from \"preact\";\n\nexport interface ClientOnlyProps {\n  readonly children: ComponentChildren;\n  readonly fallback?: ComponentChildren;\n}\n\nexport function ClientOnly({\n  children,\n  fallback = null,\n}: ClientOnlyProps): ComponentChildren {\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 \"preact/hooks\";\n\nimport type { ComponentChildren } from \"preact\";\n\nexport interface ServerOnlyProps {\n  readonly children: ComponentChildren;\n  readonly fallback?: ComponentChildren;\n}\n\nexport function ServerOnly({\n  children,\n  fallback = null,\n}: ServerOnlyProps): ComponentChildren {\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 { useRoute } from \"./useRoute\";\n\ninterface DeferredContext {\n  ssrDataDeferred?: Record<string, Promise<unknown>>;\n}\n\nconst NEVER_PROMISE = new Promise<never>(() => {\n  // Intentionally never resolves — surfaces a forever-pending Suspense boundary\n  // when a key is requested that the loader never declared.\n});\n\n/**\n * Read a deferred promise published by `defer({ deferred: { <key>: Promise } })`\n * inside an SSR data loader. Mirror of `@real-router/react/ssr` `useDeferred`\n * — same `state.context.ssrDataDeferred` contract, same NEVER-on-missing\n * fallback. Pair with `<Await>` (this package) which adds Preact-side\n * promise-status tracking since Preact 10 has no `use(promise)` analogue.\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 { useDeferred } from \"../hooks/useDeferred\";\n\nimport type { ComponentChildren } from \"preact\";\n\ninterface TrackedPromise<T> extends Promise<T> {\n  status?: \"pending\" | \"fulfilled\" | \"rejected\";\n  value?: T;\n  reason?: unknown;\n}\n\n/**\n * Preact's `Suspense` (from `preact/compat`) catches a thrown thenable and\n * re-runs the boundary's render once it settles. For deterministic re-renders\n * we tag the promise with `.status` / `.value` / `.reason` on first access so\n * the second render-pass can return the value synchronously instead of\n * throwing again.\n *\n * The same tag layout is used by React 19's internal `use(promise)` cache,\n * so promises that already carry the tag (e.g. emitted by a Suspense-aware\n * data lib) are reused as-is.\n */\nfunction track<T>(promise: Promise<T>): TrackedPromise<T> {\n  const tracked = promise as TrackedPromise<T>;\n\n  if (tracked.status !== undefined) {\n    return tracked;\n  }\n\n  tracked.status = \"pending\";\n  promise.then(\n    (value) => {\n      /* v8 ignore next 4 -- @preserve: the `.status === \"pending\"` guard\n         protects against external mutation between `track()` and the .then\n         microtask; covered branch is the always-true case in our control. */\n      if (tracked.status === \"pending\") {\n        tracked.status = \"fulfilled\";\n        tracked.value = value;\n      }\n    },\n    /* v8 ignore start -- @preserve: rejection .then handler — tested\n       end-to-end via the React adapter's e2e ssr-streaming Scenario 10\n       (id=4 reviews promise rejects on the wire); covering it in unit tests\n       requires Preact's Suspense to surface the rejection through render,\n       which doesn't compose cleanly with vitest's unhandled-rejection\n       detector. Behaviour is symmetric to the success handler above. */\n    (error: unknown) => {\n      if (tracked.status === \"pending\") {\n        tracked.status = \"rejected\";\n        tracked.reason = error;\n      }\n    },\n    /* v8 ignore stop */\n  );\n\n  return tracked;\n}\n\nexport interface AwaitProps<T> {\n  /** Deferred key declared in the loader's `defer({ deferred: { <name>: ... } })`. */\n  readonly name: string;\n  /** Render the resolved value. Suspends while pending; throws inside the\n   * nearest Error Boundary on rejection. */\n  readonly children: (value: T) => ComponentChildren;\n}\n\n/**\n * Reads `useDeferred(name)` and hands the resolved value to the render-prop\n * via Preact's `<Suspense>`-throwing convention. Wrap in `<Streamed>` (or\n * `<Suspense>` from `preact/compat`).\n *\n * ```tsx\n * <Streamed fallback={<Spinner />}>\n *   <Await<Review[]> name=\"reviews\">\n *     {(reviews) => <ReviewList items={reviews} />}\n *   </Await>\n * </Streamed>\n * ```\n */\nexport function Await<T = unknown>({\n  name,\n  children,\n}: AwaitProps<T>): ComponentChildren {\n  const promise = useDeferred<T>(name);\n  const tracked = track(promise);\n\n  if (tracked.status === \"fulfilled\") {\n    return children(tracked.value as T);\n  }\n\n  if (tracked.status === \"rejected\") {\n    throw tracked.reason;\n  }\n\n  // Suspense catches the thrown thenable and waits for resolution. ESLint\n  // complains because Promises aren't Errors, but Preact's Suspense (like\n  // React's pre-`use()` Suspense convention) explicitly expects a thenable.\n  // eslint-disable-next-line @typescript-eslint/only-throw-error -- Suspense thenable convention\n  throw promise;\n}\n","import { Suspense } from \"preact/compat\";\n\nimport type { ComponentChildren } from \"preact\";\n\nexport interface StreamedProps {\n  /** Shown while any descendant `<Await>` / `use(promise)`-equivalent suspends. */\n  readonly fallback: ComponentChildren;\n  readonly children: ComponentChildren;\n}\n\n/**\n * Cross-adapter alias for `<Suspense fallback={…}>` from `preact/compat`.\n * Pairs with `<Await>` for symmetry with the React/Solid/Svelte/Vue/Angular\n * SSR streaming naming.\n *\n * Preact's `Suspense` is part of `preact/compat` (experimental). For\n * production streaming the preact-render-to-string toolchain is required.\n */\nexport function Streamed({\n  fallback,\n  children,\n}: StreamedProps): ComponentChildren {\n  return <Suspense fallback={fallback}>{children}</Suspense>;\n}\n","import { createContext } from \"preact\";\n\nimport type { HttpStatusSink } from \"../utils/createHttpStatusSink\";\nimport type { ComponentChildren } from \"preact\";\n\nexport const HttpStatusContext = createContext<HttpStatusSink | null>(null);\n\nexport interface HttpStatusProviderProps {\n  readonly sink: HttpStatusSink;\n  readonly children: ComponentChildren;\n}\n\nexport function HttpStatusProvider({\n  sink,\n  children,\n}: HttpStatusProviderProps): ComponentChildren {\n  return (\n    <HttpStatusContext.Provider value={sink}>\n      {children}\n    </HttpStatusContext.Provider>\n  );\n}\n","import { useContext } from \"preact/hooks\";\n\nimport { HttpStatusContext } from \"./HttpStatusProvider\";\n\nimport type { ComponentChildren } from \"preact\";\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 \"preact-render-to-string\";\n * import { createHttpStatusSink, HttpStatusProvider } from \"@real-router/preact/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 * Mount the component in the shell (above every `<Suspense>` that could\n * delay it). For non-streaming SSR (`renderToString` / `renderToStringAsync`)\n * 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({\n  code,\n}: HttpStatusCodeProps): ComponentChildren {\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` (or the\n * Preact streaming helper) 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":"4OASA,SAAgB,EAAW,CACzB,WACA,WAAW,MAC0B,CACrC,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,MAC0B,CACrC,GAAM,CAAC,EAAS,GAAc,EAAS,EAAK,EAW5C,OATA,MAAgB,CAMd,EAAW,EAAI,CACjB,EAAG,CAAC,CAAC,EAEE,EAAU,EAAW,CAC9B,CCnBA,MAAM,EAAgB,IAAI,YAAqB,CAG/C,CAAC,EASD,SAAgB,EAAyB,EAAyB,CAChE,GAAM,CAAE,SAAU,EAAS,EAK3B,OAJgB,EAAM,QACG,kBACE,IAER,CACrB,CCJA,SAAS,EAAS,EAAwC,CACxD,IAAM,EAAU,EAgChB,OA9BI,EAAQ,SAAW,IAAA,IAIvB,EAAQ,OAAS,UACjB,EAAQ,KACL,GAAU,CAIL,EAAQ,SAAW,YACrB,EAAQ,OAAS,YACjB,EAAQ,MAAQ,EAEpB,EAOC,GAAmB,CACd,EAAQ,SAAW,YACrB,EAAQ,OAAS,WACjB,EAAQ,OAAS,EAErB,CAEF,EAEO,GA7BE,CA8BX,CAuBA,SAAgB,EAAmB,CACjC,OACA,YACmC,CACnC,IAAM,EAAU,EAAe,CAAI,EAC7B,EAAU,EAAM,CAAO,EAE7B,GAAI,EAAQ,SAAW,YACrB,OAAO,EAAS,EAAQ,KAAU,EAWpC,MARI,EAAQ,SAAW,WACf,EAAQ,OAOV,CACR,CChFA,SAAgB,EAAS,CACvB,WACA,YACmC,CACnC,OAAO,EAAC,EAAD,CAAoB,WAAW,UAAmB,CAAA,CAC3D,CClBA,MAAa,EAAoB,EAAqC,IAAI,EAO1E,SAAgB,EAAmB,CACjC,OACA,YAC6C,CAC7C,OACE,EAAC,EAAkB,SAAnB,CAA4B,MAAO,EAChC,UACyB,CAAA,CAEhC,CCmCA,SAAgB,EAAe,CAC7B,QACyC,CACzC,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,CCzDA,SAAgB,GAAuC,CACrD,MAAO,CAAE,KAAM,IAAA,EAAU,CAC3B"}