{"version":3,"file":"ssr.mjs","names":["HTTP_STATUS_KEY","ClientOnly","defineComponent","mounted","ref","onMounted","slots","ServerOnly","NEVER_PROMISE","useDeferred","useRoute","route","key","Await","value","props","Streamed","h","Suspense","renderNull","HttpStatusCode","sink","inject","HttpStatusProvider","provide","createHttpStatusSink"],"sources":["../../src/components/ClientOnly.ts","../../src/components/ServerOnly.ts","../../src/composables/useDeferred.ts","../../src/components/Await.ts","../../src/components/Streamed.ts","../../src/components/HttpStatusCode.ts","../../src/components/HttpStatusProvider.ts","../../src/utils/createHttpStatusSink.ts"],"sourcesContent":["import { defineComponent, onMounted, ref } from \"vue\";\n\nexport const ClientOnly = defineComponent({\n  name: \"ClientOnly\",\n  setup(_, { slots }) {\n    const mounted = ref(false);\n\n    onMounted(() => {\n      mounted.value = true;\n    });\n\n    return () => (mounted.value ? slots.default?.() : slots.fallback?.());\n  },\n});\n\nexport type ClientOnlyProps = InstanceType<typeof ClientOnly>[\"$props\"];\n","import { defineComponent, onMounted, ref } from \"vue\";\n\nexport const ServerOnly = defineComponent({\n  name: \"ServerOnly\",\n  setup(_, { slots }) {\n    const mounted = ref(false);\n\n    onMounted(() => {\n      mounted.value = true;\n    });\n\n    return () => (mounted.value ? slots.fallback?.() : slots.default?.());\n  },\n});\n\nexport type ServerOnlyProps = InstanceType<typeof ServerOnly>[\"$props\"];\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. Returns the Promise for use inside `async setup()`\n * (Vue's native Suspense pattern) or paired with `<Await name=\"key\">`.\n *\n * ```ts\n * // Vue async setup pattern\n * export default defineComponent({\n *   async setup() {\n *     const reviews = await useDeferred<Review[]>(\"reviews\");\n *     return () => h(\"div\", reviews.map(...));\n *   },\n * });\n * ```\n *\n * Returns a forever-pending promise when the key is missing — surfaces\n * loader/consumer key drift as a visible Suspense fallback rather than a\n * silent runtime error.\n */\nexport function useDeferred<T = unknown>(key: string): Promise<T> {\n  const { route } = useRoute();\n  const context = route.value.context as DeferredContext;\n  const deferred = context.ssrDataDeferred;\n\n  return (deferred?.[key] ?? NEVER_PROMISE) as Promise<T>;\n}\n","import { defineComponent } from \"vue\";\n\nimport { useDeferred } from \"../composables/useDeferred\";\n\n/**\n * Reads `useDeferred(name)` and hands the resolved value to the `default`\n * scoped slot via Vue's native `async setup()` Suspense pattern. Wrap in\n * `<Streamed>` (or Vue's `<Suspense>`).\n *\n * ```vue-html\n * <Streamed>\n *   <Await name=\"reviews\" v-slot=\"{ value }\">\n *     <ReviewList :items=\"value\" />\n *   </Await>\n *   <template #fallback>\n *     <Spinner />\n *   </template>\n * </Streamed>\n * ```\n *\n * Or with the render function:\n *\n * ```ts\n * h(Await, { name: \"reviews\" }, {\n *   default: ({ value }: { value: Review[] }) => h(ReviewList, { items: value }),\n * });\n * ```\n *\n * Implementation: `async setup()` awaits the deferred promise. Vue's\n * `<Suspense>` boundary catches the pending promise and shows the fallback\n * until resolution. Rejection bubbles to the nearest `onErrorCaptured`\n * handler.\n */\nexport const Await = defineComponent({\n  name: \"Await\",\n  props: {\n    /** Deferred key declared in the loader's `defer({ deferred: { <name>: ... } })`. */\n    name: { type: String, required: true },\n  },\n  async setup(props, { slots }) {\n    const value = await useDeferred(props.name);\n\n    return () => slots.default?.({ value });\n  },\n});\n\nexport type AwaitProps = InstanceType<typeof Await>[\"$props\"];\n","import { defineComponent, h, Suspense } from \"vue\";\n\n/**\n * Cross-adapter alias for Vue's native `<Suspense>`. Symmetric naming with\n * the React/Preact/Solid/Svelte/Angular `<Streamed>` components.\n *\n * Slots:\n * - `default` — content (may contain `<Await>` or `async setup()` children).\n * - `fallback` — shown while any descendant suspends.\n *\n * Vue's `<Suspense>` is **blocking** under SSR (no out-of-order placeholder\n * resolution) — render of HTML after `<Streamed>` waits for every\n * `async setup()` inside. This matches Vue 3's stable streaming behaviour\n * (vs React 19 / Solid which support OOO resolution).\n */\nexport const Streamed = defineComponent({\n  name: \"Streamed\",\n  setup(_, { slots }) {\n    return () =>\n      h(\n        Suspense,\n        {},\n        {\n          default: () => slots.default?.(),\n          fallback: () => slots.fallback?.(),\n        },\n      );\n  },\n});\n\nexport type StreamedProps = InstanceType<typeof Streamed>[\"$props\"];\n","import { defineComponent, inject } from \"vue\";\n\nimport { HTTP_STATUS_KEY } from \"../context\";\n\n// Module-scope render function — returns null since the component emits no DOM.\n// Hoisted to satisfy `unicorn/consistent-function-scoping` and to avoid\n// re-creating the closure on every component instantiation.\nconst renderNull = (): null => null;\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 `setup()`\n * and renders nothing. With no provider mounted (the standard client-side\n * case) the component is a silent no-op — same component tree hydrates\n * without 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 * ```vue-html\n * <HttpStatusProvider :sink=\"sink\">\n *   <RouterProvider :router=\"router\">\n *     <App />\n *   </RouterProvider>\n * </HttpStatusProvider>\n *\n * <!-- inside NotFound.vue -->\n * <HttpStatusCode :code=\"404\" />\n * ```\n *\n * **`renderToWebStream` (streaming SSR):** Vue 3 `<Suspense>` is\n * chunked-blocking — Vue waits for every `async setup()` inside a boundary\n * before emitting the chunks past it. So in practice `<HttpStatusCode />`\n * inside a `<Suspense>` boundary still writes to the sink before the\n * response headers flush. Still, prefer mounting in the shell to avoid\n * coupling the contract to that particular Vue 3 streaming behaviour. With\n * `renderToString` there is no ordering concern at all.\n *\n * **Hydration symmetry:** `<HttpStatusProvider>` wraps a render slot, so\n * Vue emits a fragment marker pair (`<!--[-->` / `<!--]-->`) around its\n * children server-side. Mount the same `<HttpStatusProvider>` on the\n * client (with a throwaway sink) to keep the marker count balanced — see\n * the `ssr/` example's `entry-client.ts`. Otherwise hydration logs\n * \"Hydration completed but contains mismatches\".\n *\n * **Valid `code` range:** Node's `res.end()` throws `Invalid status code`\n * on `NaN`, `0`, negative values, or values `> 999` — this surfaces as a\n * 5xx / dropped connection, not silent corruption. Pass a real HTTP status\n * integer (commonly 4xx/5xx; 100-999 is what Node accepts).\n */\nexport const HttpStatusCode = defineComponent({\n  name: \"HttpStatusCode\",\n  props: {\n    /** HTTP status to apply to the response. Common values: 404, 410, 451, 503. */\n    code: { type: Number, required: true },\n  },\n  setup(props) {\n    const sink = inject(HTTP_STATUS_KEY, null);\n\n    if (sink) {\n      // Dev-only validation: Node's `res.end()` throws `Invalid status code`\n      // on NaN / 0 / negative / non-integer / >999. Surface the bad value at\n      // the source so the consumer can fix the routing logic, instead of\n      // waiting for the server to crash mid-response. Production builds (Vite,\n      // esbuild, tsdown all replace `process.env.NODE_ENV !== \"production\"`\n      // with `false`) strip the check.\n      if (\n        process.env.NODE_ENV !== \"production\" &&\n        (!Number.isInteger(props.code) || props.code < 100 || props.code > 999)\n      ) {\n        console.error(\n          `[real-router] <HttpStatusCode :code=\"${String(props.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 = props.code;\n    }\n\n    return renderNull;\n  },\n});\n\nexport type HttpStatusCodeProps = InstanceType<typeof HttpStatusCode>[\"$props\"];\n","import { defineComponent, provide } from \"vue\";\n\nimport { HTTP_STATUS_KEY } from \"../context\";\n\nimport type { HttpStatusSink } from \"../utils/createHttpStatusSink\";\nimport type { PropType } from \"vue\";\n\nexport const HttpStatusProvider = defineComponent({\n  name: \"HttpStatusProvider\",\n  props: {\n    sink: { type: Object as PropType<HttpStatusSink>, required: true },\n  },\n  setup(props, { slots }) {\n    provide(HTTP_STATUS_KEY, props.sink);\n\n    return () => slots.default?.();\n  },\n});\n\nexport type HttpStatusProviderProps = InstanceType<\n  typeof HttpStatusProvider\n>[\"$props\"];\n","/**\n * Render-scoped HTTP status sink. Created per request on the server, passed to\n * `<HttpStatusProvider :sink=\"...\">`, and read after `renderToString` /\n * `renderToWebStream` 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 injected sink\n * and skips the write when no provider is mounted, so the same component tree\n * can 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 * - **Hydration symmetry:** mount `<HttpStatusProvider>` on both server and\n *   client (with a throwaway client sink). Vue emits `<!--[-->` / `<!--]-->`\n *   fragment markers around the provider's slot; an extra provider on one\n *   side trips Vue with \"Hydration completed but contains mismatches\".\n */\nexport interface HttpStatusSink {\n  code: number | undefined;\n}\n\nexport function createHttpStatusSink(): HttpStatusSink {\n  return { code: undefined };\n}\n"],"mappings":"A,0JAEA,MAAaC,EAAaC,EAAgB,CACxC,KAAM,aACN,MAAM,EAAG,CAAE,SAAS,CAClB,IAAMC,EAAUC,EAAI,EAAK,EAMzB,OAJAC,MAAgB,CACd,EAAQ,MAAQ,EAClB,CAAC,MAEaF,EAAQ,MAAQG,EAAM,UAAU,EAAIA,EAAM,WAAW,CACrE,CACF,CAAC,ECXYC,EAAaL,EAAgB,CACxC,KAAM,aACN,MAAM,EAAG,CAAE,SAAS,CAClB,IAAMC,EAAUC,EAAI,EAAK,EAMzB,OAJAC,MAAgB,CACd,EAAQ,MAAQ,EAClB,CAAC,MAEaF,EAAQ,MAAQG,EAAM,WAAW,EAAIA,EAAM,UAAU,CACrE,CACF,CAAC,ECPKE,EAAgB,IAAI,YAAqB,CAG/C,CAAC,EAqBD,SAAgBC,EAAyB,EAAyB,CAChE,GAAM,CAAE,SAAUC,EAAS,EAI3B,OAHgBC,EAAM,MAAM,QACH,kBAENC,IAAQJ,CAC7B,CCHA,MAAaK,EAAQX,EAAgB,CACnC,KAAM,QACN,MAAO,CAEL,KAAM,CAAE,KAAM,OAAQ,SAAU,EAAK,CACvC,EACA,MAAM,MAAM,EAAO,CAAE,SAAS,CAC5B,IAAMY,EAAQ,MAAML,EAAYM,EAAM,IAAI,EAE1C,UAAaT,EAAM,UAAU,CAAE,OAAM,CAAC,CACxC,CACF,CAAC,EC7BYU,EAAWd,EAAgB,CACtC,KAAM,WACN,MAAM,EAAG,CAAE,SAAS,CAClB,UACEe,EACEC,EACA,CAAC,EACD,CACE,YAAeZ,EAAM,UAAU,EAC/B,aAAgBA,EAAM,WAAW,CACnC,CACF,CACJ,CACF,CAAC,ECrBKa,MAAyB,KAiDlBC,EAAiBlB,EAAgB,CAC5C,KAAM,iBACN,MAAO,CAEL,KAAM,CAAE,KAAM,OAAQ,SAAU,EAAK,CACvC,EACA,MAAM,EAAO,CACX,IAAMmB,EAAOC,EAAOtB,EAAiB,IAAI,EAqBzC,OAnBIqB,IAQA,QAAQ,IAAI,WAAa,eACxB,CAAC,OAAO,UAAUN,EAAM,IAAI,GAAKA,EAAM,KAAO,KAAOA,EAAM,KAAO,MAEnE,QAAQ,MACN,wCAAwC,OAAOA,EAAM,IAAI,EAAE,+JAC7D,EAGF,EAAK,KAAOA,EAAM,MAGbI,CACT,CACF,CAAC,EC/EYI,EAAqBrB,EAAgB,CAChD,KAAM,qBACN,MAAO,CACL,KAAM,CAAE,KAAM,OAAoC,SAAU,EAAK,CACnE,EACA,MAAM,EAAO,CAAE,SAAS,CAGtB,OAFAsB,EAAQxB,EAAiBe,EAAM,IAAI,MAEtBT,EAAM,UAAU,CAC/B,CACF,CAAC,ECWD,SAAgBmB,GAAuC,CACrD,MAAO,CAAE,KAAM,IAAA,EAAU,CAC3B,C"}