@rshono/core 1.0.0-rc.12 → 1.0.0-rc.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/bin/rshono.mjs +3 -4
  2. package/dist/builder/env-shadow-loader.cjs +5 -5
  3. package/dist/builder/page-files.js +5 -5
  4. package/dist/builder/page-files.js.map +1 -1
  5. package/dist/builder/public-env.d.ts +5 -4
  6. package/dist/builder/public-env.d.ts.map +1 -1
  7. package/dist/builder/public-env.js +5 -4
  8. package/dist/builder/public-env.js.map +1 -1
  9. package/dist/builder/rspack-config.d.ts +5 -11
  10. package/dist/builder/rspack-config.d.ts.map +1 -1
  11. package/dist/builder/rspack-config.js +28 -50
  12. package/dist/builder/rspack-config.js.map +1 -1
  13. package/dist/cli/build.js +2 -2
  14. package/dist/cli/build.js.map +1 -1
  15. package/dist/cli/dev.d.ts.map +1 -1
  16. package/dist/cli/dev.js +30 -40
  17. package/dist/cli/dev.js.map +1 -1
  18. package/dist/cli/index.js +4 -4
  19. package/dist/cli/index.js.map +1 -1
  20. package/dist/cli/start.d.ts.map +1 -1
  21. package/dist/cli/start.js +2 -3
  22. package/dist/cli/start.js.map +1 -1
  23. package/dist/config.d.ts +28 -31
  24. package/dist/config.d.ts.map +1 -1
  25. package/dist/config.js +2 -2
  26. package/dist/config.js.map +1 -1
  27. package/dist/deploy/aws-lambda/runtime.d.ts +4 -6
  28. package/dist/deploy/aws-lambda/runtime.d.ts.map +1 -1
  29. package/dist/deploy/aws-lambda/runtime.js +5 -8
  30. package/dist/deploy/aws-lambda/runtime.js.map +1 -1
  31. package/dist/deploy/build-marker.d.ts +3 -5
  32. package/dist/deploy/build-marker.d.ts.map +1 -1
  33. package/dist/deploy/build-marker.js +3 -5
  34. package/dist/deploy/build-marker.js.map +1 -1
  35. package/dist/deploy/cloudflare/build.d.ts.map +1 -1
  36. package/dist/deploy/cloudflare/build.js +6 -10
  37. package/dist/deploy/cloudflare/build.js.map +1 -1
  38. package/dist/deploy/cloudflare/runtime.d.ts +2 -5
  39. package/dist/deploy/cloudflare/runtime.d.ts.map +1 -1
  40. package/dist/deploy/cloudflare/runtime.js +19 -32
  41. package/dist/deploy/cloudflare/runtime.js.map +1 -1
  42. package/dist/deploy/contract.d.ts +25 -42
  43. package/dist/deploy/contract.d.ts.map +1 -1
  44. package/dist/deploy/contract.js.map +1 -1
  45. package/dist/deploy/filesystem.d.ts +3 -5
  46. package/dist/deploy/filesystem.d.ts.map +1 -1
  47. package/dist/deploy/filesystem.js +7 -11
  48. package/dist/deploy/filesystem.js.map +1 -1
  49. package/dist/deploy/node/runtime.d.ts +4 -5
  50. package/dist/deploy/node/runtime.d.ts.map +1 -1
  51. package/dist/deploy/node/runtime.js +9 -15
  52. package/dist/deploy/node/runtime.js.map +1 -1
  53. package/dist/deploy/presets.d.ts +19 -29
  54. package/dist/deploy/presets.d.ts.map +1 -1
  55. package/dist/deploy/presets.js +18 -25
  56. package/dist/deploy/presets.js.map +1 -1
  57. package/dist/deploy/vercel/build.d.ts.map +1 -1
  58. package/dist/deploy/vercel/build.js +9 -12
  59. package/dist/deploy/vercel/build.js.map +1 -1
  60. package/dist/deploy/vercel/runtime.d.ts +4 -7
  61. package/dist/deploy/vercel/runtime.d.ts.map +1 -1
  62. package/dist/deploy/vercel/runtime.js +4 -7
  63. package/dist/deploy/vercel/runtime.js.map +1 -1
  64. package/dist/index.d.ts +13 -9
  65. package/dist/index.d.ts.map +1 -1
  66. package/dist/index.js +15 -12
  67. package/dist/index.js.map +1 -1
  68. package/dist/router.d.ts +65 -93
  69. package/dist/router.d.ts.map +1 -1
  70. package/dist/router.js +2 -5
  71. package/dist/router.js.map +1 -1
  72. package/dist/runtime/boundaries.d.ts +24 -30
  73. package/dist/runtime/boundaries.d.ts.map +1 -1
  74. package/dist/runtime/boundaries.js +15 -22
  75. package/dist/runtime/boundaries.js.map +1 -1
  76. package/dist/runtime/client.d.ts +16 -7
  77. package/dist/runtime/client.d.ts.map +1 -1
  78. package/dist/runtime/client.js +16 -7
  79. package/dist/runtime/client.js.map +1 -1
  80. package/dist/runtime/context.d.ts +89 -118
  81. package/dist/runtime/context.d.ts.map +1 -1
  82. package/dist/runtime/context.js +107 -167
  83. package/dist/runtime/context.js.map +1 -1
  84. package/dist/runtime/control.js +3 -3
  85. package/dist/runtime/control.js.map +1 -1
  86. package/dist/runtime/dev-protocol.d.ts +4 -8
  87. package/dist/runtime/dev-protocol.d.ts.map +1 -1
  88. package/dist/runtime/dev-protocol.js.map +1 -1
  89. package/dist/runtime/entry.client.js +69 -105
  90. package/dist/runtime/entry.client.js.map +1 -1
  91. package/dist/runtime/entry.rsc.d.ts +5 -6
  92. package/dist/runtime/entry.rsc.d.ts.map +1 -1
  93. package/dist/runtime/entry.rsc.js +67 -122
  94. package/dist/runtime/entry.rsc.js.map +1 -1
  95. package/dist/runtime/entry.ssr.d.ts +7 -12
  96. package/dist/runtime/entry.ssr.d.ts.map +1 -1
  97. package/dist/runtime/entry.ssr.js +13 -24
  98. package/dist/runtime/entry.ssr.js.map +1 -1
  99. package/dist/runtime/flight-inject.d.ts +10 -17
  100. package/dist/runtime/flight-inject.d.ts.map +1 -1
  101. package/dist/runtime/flight-inject.js +35 -53
  102. package/dist/runtime/flight-inject.js.map +1 -1
  103. package/dist/runtime/hot-update.d.ts +20 -27
  104. package/dist/runtime/hot-update.d.ts.map +1 -1
  105. package/dist/runtime/hot-update.js +17 -23
  106. package/dist/runtime/hot-update.js.map +1 -1
  107. package/dist/runtime/navigation.d.ts +14 -19
  108. package/dist/runtime/navigation.d.ts.map +1 -1
  109. package/dist/runtime/navigation.js +9 -13
  110. package/dist/runtime/navigation.js.map +1 -1
  111. package/dist/runtime/request.d.ts +4 -6
  112. package/dist/runtime/request.d.ts.map +1 -1
  113. package/dist/runtime/request.js +2 -3
  114. package/dist/runtime/request.js.map +1 -1
  115. package/dist/runtime/server.d.ts +18 -10
  116. package/dist/runtime/server.d.ts.map +1 -1
  117. package/dist/runtime/server.js +21 -19
  118. package/dist/runtime/server.js.map +1 -1
  119. package/dist/server/headers.d.ts +8 -15
  120. package/dist/server/headers.d.ts.map +1 -1
  121. package/dist/server/headers.js +8 -15
  122. package/dist/server/headers.js.map +1 -1
  123. package/dist/server/load-config.d.ts +2 -2
  124. package/dist/server/load-config.d.ts.map +1 -1
  125. package/dist/server/load-config.js +6 -9
  126. package/dist/server/load-config.js.map +1 -1
  127. package/dist/server/prerendered.d.ts +30 -43
  128. package/dist/server/prerendered.d.ts.map +1 -1
  129. package/dist/server/prerendered.js +20 -29
  130. package/dist/server/prerendered.js.map +1 -1
  131. package/dist/server/server-config.d.ts +13 -28
  132. package/dist/server/server-config.d.ts.map +1 -1
  133. package/dist/server/server-config.js +5 -10
  134. package/dist/server/server-config.js.map +1 -1
  135. package/dist/server/shutdown.d.ts +2 -3
  136. package/dist/server/shutdown.d.ts.map +1 -1
  137. package/dist/server/shutdown.js +2 -3
  138. package/dist/server/shutdown.js.map +1 -1
  139. package/dist/server/ssg.d.ts +3 -6
  140. package/dist/server/ssg.d.ts.map +1 -1
  141. package/dist/server/ssg.js +11 -20
  142. package/dist/server/ssg.js.map +1 -1
  143. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/runtime/context.ts"],"names":[],"mappings":"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","sourcesContent":["/// <reference path=\"../types/rshono-config.d.ts\" />\n/**\n * The request context: {@link getRequestContext} and the {@link RequestContext} wrapper it returns,\n * the {@link redirect} / {@link notFound} control-flow helpers, and the\n * {@link onServerError} reporting funnel — plus the `@internal` plumbing that binds\n * a request to the async context in the first place.\n *\n * The public half of this module is re-exported by `runtime/server.ts`, which is\n * what the `@rshono/core/server` subpath resolves to; import *that* from an app. Nothing\n * here is safe in a `'use client'` module — those run in the browser, with no bound\n * request context.\n */\n\nimport type { Context, Env } from 'hono';\nimport { deleteCookie, getCookie, setCookie } from 'hono/cookie';\nimport type { CookieOptions } from 'hono/utils/cookie';\nimport { AsyncLocalStorage } from 'node:async_hooks';\nimport { NotFoundSignal, RedirectSignal } from './control.js';\n\n/**\n * HTTP status codes accepted by {@link redirect}.\n *\n * - `301` Moved Permanently, `308` Permanent Redirect — cacheable, permanent.\n * - `302` Found, `307` Temporary Redirect — temporary.\n * - `303` See Other — the default; forces a `GET` on the target, which is what\n * you almost always want after a form action (post/redirect/get).\n *\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status#redirection_messages | MDN — redirection status codes}\n */\nexport type RedirectStatus = 301 | 302 | 303 | 307 | 308;\n\nconst contextStorage = new AsyncLocalStorage<Context>();\n\n/** One {@link RequestContext} per Hono {@link Context}, so repeated `getRequestContext()` calls in a request share its lazy getters. */\nconst wrappers = new WeakMap<Context, RequestContext>();\n\n/**\n * Requests whose page render has begun — the point past which nothing can change the response head.\n *\n * A `WeakSet` keyed on the Hono {@link Context} rather than a field on {@link RequestContext},\n * so marking a request costs nothing for the pages that never read their context: the wrapper is\n * built lazily by {@link getRequestContext} and this must not be what forces it into existence.\n */\nconst rendering = new WeakSet<Context>();\n\n/**\n * Marks the request as having entered its page render, which is what makes\n * {@link RequestContext.setHeader} and `ctx.cookies.set()` start throwing.\n *\n * Framework internal — `renderComponent` calls this immediately before handing the page to React.\n * Everything that legitimately writes to the response (middleware, a `'use server'` action, an\n * endpoint route) has already run by then, so none of them are affected.\n *\n * @internal\n */\nexport function beginPageRender(c: Context): void {\n rendering.add(c);\n}\n\n/**\n * The shared explanation for a response mutation that arrived too late, thrown by\n * {@link RequestContext.setHeader} and the `cookies` writers.\n *\n * Refusing beats the alternative, which was silent *and* inconsistent: a page setting a cookie got it\n * on a full page load and lost it on a soft navigation, because the flight stream's response head is\n * committed before the page component's first line runs. Nothing inside the render can fix that, so\n * the message says where the write does belong instead.\n */\nfunction tooLateToWrite(call: string): never {\n throw new Error(\n `[rshono] ${call} was called while rendering a page, which is too late to affect the response. ` +\n 'A page streams, so its response head is already committed by the time the component runs — the ' +\n 'write would land on a full page load and be silently dropped on a soft navigation. Do it from a ' +\n \"'use server' action instead; or, in middleware and { type: 'endpoint' } routes — which are handed \" +\n \"Hono's `c` directly and run outside the request context — with `c.header(…)` / `setCookie(c, …)`.\",\n );\n}\n\n/** The shared explanation for a Hono `Context` member that a page has no way to use. See the stubs on {@link RequestContext}. */\nfunction notOnContext(call: string, instead: string): never {\n throw new Error(\n `[rshono] ctx.${call} does not exist. A page returns JSX and the framework builds the response from it, ` +\n `so Hono's response builders have nothing to return to. ${instead}`,\n );\n}\n\n/**\n * `process.env`, snapshotted on first read.\n *\n * Enumerating it crosses into the host environment, which made the spread (~20µs) by far the most\n * expensive thing {@link RequestContext.env} did, once per request that touched it. Lazily rather\n * than at module load, because `loadEnvFiles()` runs *after* this module is imported and an eager\n * copy would miss everything from `.env`. The trade-off: a `process.env` mutation after the first\n * `ctx.env` read is not picked up.\n */\nlet envSnapshot: Record<string, string | undefined> | undefined;\n\nfunction processEnv(): Record<string, string | undefined> {\n return (envSnapshot ??= typeof process !== 'undefined' && process.env ? { ...process.env } : {});\n}\n\n/**\n * True when this process is the SSG build prerendering `render: 'static'` routes rather than a server\n * handling real requests. `build.ts` sets `RSHONO_PRERENDER` before importing the app bundle, which\n * inlines its own copy of this module — so `process.env` is what crosses that boundary, not a\n * module-level flag. {@link getRequestContext} reads it to fail loudly instead of baking synthetic\n * build-time values (a `localhost` URL, no cookies, build env) into the prerendered page.\n */\nconst prerendering = typeof process !== 'undefined' && !!process.env?.RSHONO_PRERENDER;\n\n/**\n * Runs `fn` with the given Hono {@link Context} bound as the ambient request\n * context, so that {@link getRequestContext} resolves to it anywhere in the call tree.\n *\n * Framework internal — the request handler wraps every render and action in\n * this. Application code should reach for {@link getRequestContext} instead.\n *\n * @internal\n */\nexport function runWithContext<T>(c: Context, fn: () => T): T {\n return contextStorage.run(c, fn);\n}\n\n/**\n * The matched route params, or an empty object when there is no active match.\n *\n * Framework internal — the request renderer calls this to build a page's `params` prop, and\n * {@link RequestContext.params} caches it. Read them from that prop, or from `ctx.params`.\n *\n * @internal\n */\nexport function readParams(c: Context): Record<string, string> {\n try {\n return c.req.param();\n } catch {\n return {};\n }\n}\n\n/** A proxy chain appends to these headers, so the client-facing value is the first entry. */\nfunction firstForwardedValue(header: string | undefined): string | undefined {\n const first = header?.split(',')[0]?.trim();\n return first || undefined;\n}\n\n// DefinePlugin inlines the config into the server bundle, but this module is the public\n// `@rshono/core/server` entry and could be loaded by tooling that doesn't (a unit test, a one-off script).\n// Read through `typeof` so that degrades to the safe answer — don't trust — instead of a ReferenceError.\nconst trustProxy = typeof __RSHONO_CONFIG__ !== 'undefined' && __RSHONO_CONFIG__.trustProxy;\n\n/**\n * Resolves the browser-facing {@link URL} for a request, from a Hono {@link Context}.\n *\n * `c.req.url` reflects the internal address the server was reached on, which is wrong behind a proxy.\n * `X-Forwarded-Host` / `X-Forwarded-Proto` fix that up — **but only when `trustProxy` is enabled in\n * `rshono.config.ts`** (always so under `rshono dev`). They are client-supplied, so honouring them\n * unconditionally would let anyone who can reach the server dictate the origin of every absolute URL\n * the app builds, and poison a shared cache with it.\n *\n * This is the form for **middleware**, which is handed `c` and runs outside the request context. In\n * a server component or a `'use server'` action prefer {@link RequestContext.url}, which is this same\n * value cached per request.\n *\n * Its main use is giving Hono's own middleware the origin the browser actually used, since they all\n * read `c.req.url` on their own and so see the internal one:\n *\n * @example\n * ```ts\n * // src/server.ts — a CSRF check that still works behind a proxy that rewrites Host\n * import { publicUrl } from '@rshono/core/server';\n * import { csrf } from 'hono/csrf';\n *\n * server.use(csrf({ origin: (origin, c) => origin === publicUrl(c).origin }));\n * ```\n *\n * A fresh instance per call, so mutating it disturbs nothing else.\n *\n * @param c - The Hono {@link Context} for the request.\n * @returns The browser-facing URL — proxy-corrected under `trustProxy`, `c.req.url` otherwise.\n *\n * @see {@link https://www.rshono.com/docs/configuration#proxy-headers | Docs — proxy headers}\n */\nexport function publicUrl(c: Context): URL {\n const url = new URL(c.req.url);\n if (!trustProxy) return url;\n\n const forwardedHost = firstForwardedValue(c.req.header('x-forwarded-host'));\n // Parsed rather than assigned to `url.host`, because that setter *keeps the existing port* when\n // the new value has none — leaving the internal port on the public URL (`example.com:3000`).\n const forwarded = forwardedHost ? URL.parse(`http://${forwardedHost}`) : null;\n if (forwarded) {\n url.hostname = forwarded.hostname;\n url.port = forwarded.port; // '' when the forwarded host carries no port, which clears it\n }\n\n // Restricted to the two schemes a browser can actually have requested; anything else (a proxy\n // sending junk, or a client trying its luck) leaves the scheme alone.\n const forwardedProto = firstForwardedValue(c.req.header('x-forwarded-proto'));\n if (forwardedProto === 'http' || forwardedProto === 'https') url.protocol = forwardedProto;\n\n return url;\n}\n\n/**\n * The environment available to a request: Cloudflare/Workers `Bindings` merged\n * with process env vars. Values not declared in `Bindings` are typed as\n * `string | undefined`. See {@link RequestContext.env}.\n *\n * @see {@link https://hono.dev/docs/getting-started/cloudflare-workers#bindings | Hono — bindings}\n */\nexport type EnvVars<E extends Env> = E['Bindings'] & Record<string, string | undefined>;\n\n/**\n * Ergonomic, read-mostly wrapper around Hono's {@link Context} for use inside\n * server components and server actions.\n *\n * Obtain one with {@link getRequestContext}, or — in a page component — take it straight\n * off the `ctx` prop, which is this same object. Never construct it yourself. One\n * instance is reused for the lifetime of a request, so its lazy getters\n * ({@link RequestContext.url}, {@link RequestContext.env}) are computed at most once.\n *\n * @typeParam E - The Hono {@link Env} describing this app's `Bindings` and\n * `Variables`, so {@link RequestContext.var} and {@link RequestContext.env} stay typed.\n *\n * @example\n * ```tsx\n * import { getRequestContext } from '@rshono/core/server';\n *\n * export default async function Whoami() {\n * const ctx = getRequestContext();\n * const session = ctx.cookies.get('session');\n * return <p>{ctx.url.pathname} — {session ?? 'anonymous'}</p>;\n * }\n * ```\n *\n * @see {@link https://www.rshono.com/docs/api#rshonocoreserver | Docs — `@rshono/core/server`}\n * @see {@link https://hono.dev/docs/api/context | Hono — Context}, reachable in full via {@link RequestContext.hono}\n */\nexport class RequestContext<E extends Env = Env> {\n #raw: Context<E>;\n #url?: URL;\n #env?: EnvVars<E>;\n #params?: Record<string, string>;\n\n /**\n * Framework internal — one instance is created per request and handed to you by\n * {@link getRequestContext} or the `ctx` page prop. Application code never calls this.\n *\n * @internal\n */\n constructor(c: Context<E>) {\n this.#raw = c;\n }\n\n /**\n * The underlying Hono {@link Context} — the escape hatch for what this wrapper does not expose,\n * such as `executionCtx.waitUntil()` on Workers.\n *\n * Its response builders (`redirect`, `notFound`, `json`, `body`, `status`, …) still do nothing from\n * inside a page, for the reason the stubs on this class explain: reaching them through here\n * bypasses the error, it does not make them work.\n *\n * @example\n * ```ts\n * getRequestContext().hono.executionCtx.waitUntil(logAsync()); // Workers\n * ```\n *\n * @see {@link https://hono.dev/docs/api/context | Hono — Context}\n */\n // A prototype getter rather than a plain property, so it is not *own enumerable*. React's\n // diagnostic for a value that cannot be sent to a client component walks `Object.keys` recursively\n // with no depth limit and no cycle guard, and the Hono context graph reaches the socket and the\n // whole server through `req.raw` and `env` — as a plain property this blew the stack inside the\n // message builder, so React's accurate \"you cannot pass this\" error never got printed. Every member\n // here is a getter or method for the same reason; `cookies` is the one own enumerable property, and\n // it is a shallow object of four functions.\n get hono(): Context<E> {\n return this.#raw;\n }\n\n /**\n * The parsed request — method, headers, path params, query and the body readers. Hono's\n * {@link Context.req}, unwrapped, so `ctx.req.header('authorization')` rather than\n * `ctx.hono.req.header(…)`.\n *\n * Reads only. Setting a *response* header is {@link RequestContext.setHeader}, deliberately in a\n * different place — Hono's `c.header()` writing the response while `c.req.header()` reads the\n * request is a well-worn source of confusion.\n *\n * @example\n * ```ts\n * const ctx = getRequestContext();\n * ctx.req.method; // 'GET'\n * ctx.req.header('authorization'); // string | undefined\n * ctx.req.query('tab'); // string | undefined\n * ```\n *\n * @see {@link https://hono.dev/docs/api/request | Hono — HonoRequest}\n */\n get req(): Context<E>['req'] {\n return this.#raw.req;\n }\n\n /**\n * Matched route params for this request, e.g. `{ id: '42' }` for `/profile/:id`. Empty when there\n * is no active route match.\n *\n * A **page** is handed the same record as its `params` prop, typed key-by-key from its route path,\n * and that is the better read where it is available. This is for everywhere else — a nested server\n * component, or a `'use server'` action — which get no props from the framework.\n */\n get params(): Record<string, string> {\n return (this.#params ??= readParams(this.#raw as Context));\n }\n\n /**\n * The browser-facing request URL — read `url.pathname`, `url.searchParams` and the\n * rest off it. Parsed once and cached, so the same instance comes back on every\n * read within a request; treat it as read-only for that reason.\n *\n * `X-Forwarded-Host` / `-Proto` are honoured only when `trustProxy` is enabled in\n * `rshono.config.ts`, since any client can send them.\n *\n * @see {@link https://www.rshono.com/docs/configuration#proxy-headers | Docs — proxy headers}\n */\n get url(): URL {\n return (this.#url ??= publicUrl(this.#raw as Context));\n }\n\n /**\n * Typed variables set by middleware via `c.set('user', …)`, read here as\n * `ctx.var.user`. Type them by parameterising this class's {@link Env}.\n *\n * @example\n * ```ts\n * type AppEnv = { Variables: { user: { id: string } } };\n * const { user } = getRequestContext<AppEnv>().var; // typed, set by your middleware\n * ```\n *\n * @see {@link https://hono.dev/docs/api/context#var | Hono — c.var}\n * @see {@link https://www.rshono.com/docs/hono#typing-the-context | Docs — typing the context}\n */\n get var(): Readonly<E['Variables']> {\n return this.#raw.var;\n }\n\n /**\n * Environment for the request: process env vars merged with runtime bindings\n * (bindings win on conflict). Computed once and cached.\n *\n * @example `const key = getRequestContext().env.STRIPE_SECRET_KEY;`\n *\n * @see {@link https://hono.dev/docs/api/context#env | Hono — c.env}\n * @see {@link https://www.rshono.com/docs/configuration#environment-and-secrets | Docs — environment and secrets}\n */\n get env(): EnvVars<E> {\n if (this.#env) return this.#env;\n const bindings = this.#raw.env as Record<string, unknown> | undefined;\n // The snapshot is shared, so hand it back as-is when there are no bindings to merge over it.\n return (this.#env = (bindings ? { ...processEnv(), ...bindings } : processEnv()) as EnvVars<E>);\n }\n\n /**\n * Read and write request/response cookies.\n *\n * @example\n * ```ts\n * const ctx = getRequestContext();\n * ctx.cookies.get('session'); // string | undefined\n * ctx.cookies.set('session', id, { httpOnly: true, sameSite: 'Lax', path: '/' });\n * ctx.cookies.delete('session', { path: '/' });\n * ```\n *\n * @see {@link https://hono.dev/docs/helpers/cookie | Hono — cookie helper}, which this wraps\n */\n cookies = {\n /** Reads a single cookie by name, or `undefined` if absent. Safe anywhere, a page included. */\n get: (name: string): string | undefined => getCookie(this.#raw, name),\n /** Reads every cookie as a `{ name: value }` record. Safe anywhere, a page included. */\n all: (): Record<string, string> => getCookie(this.#raw),\n /**\n * Sets a cookie on the response. See Hono's {@link CookieOptions} for `path`, `httpOnly`,\n * `maxAge`, etc.\n *\n * **Throws inside a page render** — see {@link RequestContext.setHeader}, of which a `Set-Cookie`\n * is a special case. Set cookies from a `'use server'` action, or with Hono's `setCookie(c, …)`\n * in middleware and endpoint routes.\n *\n * @throws If called while a page is rendering, where it could not reach the browser reliably.\n *\n * @see {@link https://hono.dev/docs/helpers/cookie#options | Hono — cookie options}\n */\n set: (name: string, value: string, options?: CookieOptions): void => {\n this.#assertWritable('ctx.cookies.set()');\n setCookie(this.#raw, name, value, options);\n },\n /**\n * Clears a cookie. Pass the same `path`/`domain` it was set with so the browser matches it.\n * Throws inside a page render, exactly as `set` does.\n *\n * @throws If called while a page is rendering.\n */\n delete: (name: string, options?: CookieOptions): void => {\n this.#assertWritable('ctx.cookies.delete()');\n deleteCookie(this.#raw, name, options);\n },\n };\n\n /** Guards every write that has to reach the response head. See {@link tooLateToWrite}. */\n #assertWritable(call: string): void {\n if (rendering.has(this.#raw as Context)) tooLateToWrite(call);\n }\n\n /**\n * Sets a header on the response — from a `'use server'` action, which is the one place a request\n * context exists *and* the response is still open.\n *\n * From inside a page it throws: a page streams, so by then the response head is committed. Hono's\n * `c.header()` fails there silently and inconsistently — landing on a full page load, vanishing on\n * a soft navigation — so this refuses rather than doing it half the time.\n *\n * Middleware and `{ type: 'endpoint' }` routes run outside the request context but are handed\n * Hono's `c` directly, so they use `c.header(…)`. That is also where a header belonging to the\n * *page* rather than to one action goes — `Cache-Control`, `X-Robots-Tag` — since middleware runs\n * before the render.\n *\n * @param name - Header name, case-insensitive.\n * @param value - Header value.\n * @param options - `{ append: true }` to add another value rather than replace.\n * @throws If called while a page is rendering, where it could not reach the browser reliably.\n *\n * @example\n * ```ts\n * 'use server';\n * export async function logout() {\n * const ctx = getRequestContext();\n * ctx.cookies.delete('session', { path: '/' });\n * ctx.setHeader('clear-site-data', '\"cache\", \"storage\"');\n * redirect('/');\n * }\n * ```\n */\n setHeader(name: string, value: string, options?: { append?: boolean }): void {\n this.#assertWritable('ctx.setHeader()');\n this.#raw.header(name, value, options);\n }\n\n // Hono's response builders, restated as errors naming what to use instead. A page returns JSX and\n // `renderComponent` builds the response from it, so every one of these is a silent no-op through\n // `ctx.hono`. `@deprecated` is the compile-time signal — an editor strikes them through in\n // autocomplete — and the thrown message is the one that explains. Each takes `...args: unknown[]`\n // it never reads so that `ctx.redirect('/dashboard')` reaches that message rather than stopping at\n // \"Expected 0 arguments, but got 1\".\n\n /** @deprecated Not available on a page's context — use `redirect()` from `@rshono/core/server`. */\n redirect(...args: unknown[]): never {\n return notOnContext(\n 'redirect(location, status?)',\n \"Use `redirect()` from '@rshono/core/server', which throws a signal the framework turns into a real redirect.\",\n );\n }\n\n /** @deprecated Not available on a page's context — use `notFound()` from `@rshono/core/server`. */\n notFound(...args: unknown[]): never {\n return notOnContext('notFound()', \"Use `notFound()` from '@rshono/core/server', which aborts the render and shows the app's not-found page.\");\n }\n\n /** @deprecated A page renders JSX. For a JSON response, use an `{ type: 'endpoint' }` route. */\n json(...args: unknown[]): never {\n return notOnContext('json(object)', \"For a JSON response use an { type: 'endpoint' } route; to read the request body use `ctx.req.json()`.\");\n }\n\n /** @deprecated A page renders JSX. For a text response, use an `{ type: 'endpoint' }` route. */\n text(...args: unknown[]): never {\n return notOnContext('text(string)', \"For a text response use an { type: 'endpoint' } route; to read the request body use `ctx.req.text()`.\");\n }\n\n /** @deprecated A page renders JSX, which the framework turns into HTML for you. */\n html(...args: unknown[]): never {\n return notOnContext('html(string)', \"A page's JSX is already its HTML; for a hand-built HTML response use an { type: 'endpoint' } route.\");\n }\n\n /** @deprecated Not available on a page's context. To read the request body, use `ctx.req`. */\n body(...args: unknown[]): never {\n return notOnContext(\n 'body(data, …)',\n \"To read the *request* body use `ctx.req.json()` / `ctx.req.text()` / `ctx.req.formData()`; to build a response, use an { type: 'endpoint' } route.\",\n );\n }\n\n /** @deprecated A page's status is set by the framework — use `notFound()`, or an endpoint route. */\n status(...args: unknown[]): never {\n return notOnContext(\n 'status(code)',\n \"A page's status is the framework's: 200, 404 via `notFound()`, 500 when it throws. For any other code use an { type: 'endpoint' } route.\",\n );\n }\n\n /** @deprecated Renamed — use `ctx.setHeader(name, value)`, which is valid from a `'use server'` action. */\n header(...args: unknown[]): never {\n return notOnContext(\n 'header(name, value)',\n \"Use `ctx.setHeader(name, value)` from a 'use server' action, or `c.header(…)` in middleware — a page renders too late to set one.\",\n );\n }\n}\n\n/**\n * Returns the {@link RequestContext} for the current request — the URL, cookies, params, env and\n * middleware variables, read from a server component or a server action. Memoised per request, so\n * repeated calls return the same instance.\n *\n * A **page** component is handed that same object as its `ctx` prop, so this import is for everywhere\n * else: a nested server component, or a `'use server'` action module.\n *\n * @typeParam E - The app's Hono {@link Env}, to type {@link RequestContext.var} and {@link RequestContext.env}.\n * @throws If called at module load, where there is no ambient context to resolve.\n * @throws If called while prerendering a `render: 'static'` route, which has no\n * per-request context at build time — mark the route `render: 'dynamic'` instead.\n *\n * @example\n * ```ts\n * 'use server';\n * import { getRequestContext, redirect } from '@rshono/core/server';\n *\n * export async function login(form: FormData) {\n * getRequestContext().cookies.set('session', String(form.get('email')), { httpOnly: true });\n * redirect('/dashboard');\n * }\n * ```\n *\n * @see {@link https://www.rshono.com/docs/api#rshonocoreserver | Docs — `@rshono/core/server`}\n */\nexport function getRequestContext<E extends Env = Env>(): RequestContext<E> {\n if (prerendering) {\n throw new Error(\n \"[rshono] getRequestContext() was called while prerendering a `render: 'static'` route. A static page \" +\n 'is rendered once at build time, so it has no per-request context to read (URL, cookies, ' +\n \"headers, env). Change this route to `render: 'dynamic'` so it renders per request, or remove \" +\n 'the getRequestContext() call.',\n );\n }\n const c = contextStorage.getStore();\n if (!c) {\n throw new Error(\n '[rshono] getRequestContext() was called outside a request. It only works inside a server component or a server action, not at module load.',\n );\n }\n let ctx = wrappers.get(c);\n if (!ctx) {\n ctx = new RequestContext(c);\n wrappers.set(c, ctx);\n }\n return ctx as unknown as RequestContext<E>;\n}\n\n/**\n * Redirects the request to `location` by throwing a control signal that the\n * framework catches and turns into an HTTP redirect response.\n *\n * Because it throws, it never returns — TypeScript narrows away any code after\n * the call, and you do not need to `return` it. Do not wrap it in a `try/catch`\n * that swallows the signal.\n *\n * @param location - Absolute path or URL to redirect to, e.g. `/dashboard`.\n * @param status - Redirect {@link RedirectStatus}; defaults to `303` (See Other),\n * the correct choice after a form action so the browser follows up with a `GET`.\n *\n * @example\n * ```ts\n * const session = getRequestContext().cookies.get('session');\n * if (!session) redirect('/login');\n * // session is defined below this line\n * ```\n */\nexport function redirect(location: string, status: RedirectStatus = 303): never {\n throw new RedirectSignal(location, status);\n}\n\n/**\n * Aborts the current render with a 404, rendering the app's not-found page.\n *\n * Like {@link redirect}, this throws a control signal and never returns, so\n * TypeScript narrows away everything after the call. Do not catch-and-swallow it.\n *\n * @example\n * ```tsx\n * export default async function Page({ params }: PageProps<'/users/:id'>) {\n * const user = await db.user.find(params.id);\n * if (!user) notFound();\n * return <Profile user={user} />; // user is non-null here\n * }\n * ```\n */\nexport function notFound(): never {\n throw new NotFoundSignal();\n}\n\n/**\n * Which stage of a request produced an error handed to an {@link ServerErrorHandler}.\n *\n * - `action` — a `'use server'` function threw. React sends the client an opaque marker with no\n * message in production, so this is the only place the real error is visible.\n * - `render` — a server component threw while the flight payload was being produced.\n * - `ssr` — SSR failed before the HTML shell could be sent, so the `error` page was unreachable too.\n * - `request` — anything else that reached the top-level handler, including a thrown endpoint route.\n */\nexport type ServerErrorSource = 'action' | 'render' | 'ssr' | 'request';\n\n/** What an {@link ServerErrorHandler} is told about an error, beyond the error itself. */\nexport interface ServerErrorContext {\n /** The stage that produced it — see {@link ServerErrorSource}. */\n source: ServerErrorSource;\n /** The request being served, for the URL, method and headers. */\n request: Request;\n}\n\n/** Handler registered with {@link onServerError}. Called for the side effect; its return value is ignored. */\nexport type ServerErrorHandler = (error: unknown, context: ServerErrorContext) => void;\n\nlet errorHandler: ServerErrorHandler | undefined;\n\n/**\n * Registers a handler for every error the framework catches, so they can reach an error tracker\n * (Sentry, Datadog, a log pipeline) instead of only `stderr`.\n *\n * Call it **once, at the top level of `src/server.ts`** — that module is imported as the server\n * starts, before any request is served. Registering again replaces the previous handler.\n *\n * Errors are still written to `stderr` either way, so a handler adds a destination rather than\n * replacing one. A handler that throws is caught and logged: reporting must never be able to fail\n * a request.\n *\n * @example\n * ```ts\n * // src/server.ts\n * import * as Sentry from '@sentry/node';\n * import { onServerError } from '@rshono/core/server';\n *\n * onServerError((error, { source, request }) => {\n * Sentry.captureException(error, { tags: { source }, extra: { url: request.url } });\n * });\n * ```\n *\n * @see {@link https://www.rshono.com/docs/hono#error-reporting | Docs — error reporting}\n */\nexport function onServerError(handler: ServerErrorHandler): void {\n errorHandler = handler;\n}\n\n/**\n * Logs an error and forwards it to the registered {@link ServerErrorHandler}.\n *\n * Framework internal — the single funnel every caught server-side error goes through, so that\n * adding a reporting destination is one registration rather than a hook per call site.\n *\n * @internal\n */\nexport function reportServerError(error: unknown, info: ServerErrorContext & { message: string }): void {\n console.error(info.message, error);\n if (!errorHandler) return;\n try {\n errorHandler(error, { source: info.source, request: info.request });\n } catch (handlerError) {\n console.error('[rshono] the onServerError handler threw:', handlerError);\n }\n}\n"]}
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/runtime/context.ts"],"names":[],"mappings":"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","sourcesContent":["// `__RSHONO_CONFIG__` is a global const, and an `import` cannot bring one into scope — a path reference is\n// the only way to reach it, which is the whole reason that file is separate. See its header.\n// eslint-disable-next-line @typescript-eslint/triple-slash-reference\n/// <reference path=\"../types/rshono-config.d.ts\" />\n/**\n * The request context: {@link getRequestContext} and the {@link RequestContext} it returns, the\n * {@link redirect} / {@link notFound} control-flow helpers, and the {@link onServerError} reporting\n * funnel — plus the `@internal` plumbing that binds a request to the async context.\n *\n * The public half is re-exported by `runtime/server.ts`, which is what `@rshono/core/server` resolves\n * to; an app imports that.\n */\n\nimport type { Context, Env } from 'hono';\nimport { deleteCookie, getCookie, setCookie } from 'hono/cookie';\nimport type { CookieOptions } from 'hono/utils/cookie';\nimport { AsyncLocalStorage } from 'node:async_hooks';\nimport { NotFoundSignal, RedirectSignal } from './control.js';\n\n/**\n * HTTP status codes accepted by {@link redirect}.\n *\n * - `301` Moved Permanently, `308` Permanent Redirect — cacheable, permanent.\n * - `302` Found, `307` Temporary Redirect — temporary.\n * - `303` See Other — the default; forces a `GET` on the target, which is what you almost always want\n * after a form action (post/redirect/get).\n *\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status#redirection_messages | MDN — redirection status codes}\n */\nexport type RedirectStatus = 301 | 302 | 303 | 307 | 308;\n\nconst contextStorage = new AsyncLocalStorage<Context>();\n\n/** One {@link RequestContext} per Hono {@link Context}, so repeated `getRequestContext()` calls share its lazy getters. */\nconst wrappers = new WeakMap<Context, RequestContext>();\n\n// Keyed on the Hono context rather than held as a field, so marking a request never forces the lazily\n// built `RequestContext` wrapper into existence.\nconst rendering = new WeakSet<Context>();\n\n/**\n * Marks the request as having entered its page render, which is what makes\n * {@link RequestContext.setHeader} and `ctx.cookies.set()` start throwing.\n *\n * @internal\n */\nexport function beginPageRender(c: Context): void {\n rendering.add(c);\n}\n\n/** The shared refusal for a response write that arrived too late — the message names where it belongs instead. */\nfunction tooLateToWrite(call: string): never {\n throw new Error(\n `[rshono] ${call} was called while rendering a page, which is too late to affect the response. ` +\n 'A page streams, so its response head is already committed by the time the component runs — the ' +\n 'write would land on a full page load and be silently dropped on a soft navigation. Do it from a ' +\n \"'use server' action instead; or, in middleware and { type: 'endpoint' } routes — which are handed \" +\n \"Hono's `c` directly and run outside the request context — with `c.header(…)` / `setCookie(c, …)`.\",\n );\n}\n\n/** The shared refusal for a Hono `Context` member a page has no way to use. See the stubs on {@link RequestContext}. */\nfunction notOnContext(call: string, instead: string): never {\n throw new Error(\n `[rshono] ctx.${call} does not exist. A page returns JSX and the framework builds the response from it, ` +\n `so Hono's response builders have nothing to return to. ${instead}`,\n );\n}\n\n// Snapshotted rather than spread per request: enumerating `process.env` crosses into the host\n// environment (~20µs). Lazily, because `loadEnvFiles()` runs after this module is imported — so a\n// mutation after the first `ctx.env` read is not picked up.\nlet envSnapshot: Record<string, string | undefined> | undefined;\n\nfunction processEnv(): Record<string, string | undefined> {\n return (envSnapshot ??= typeof process !== 'undefined' && process.env ? { ...process.env } : {});\n}\n\n// Set by `build.ts` before it imports the app bundle, which inlines its own copy of this module — so\n// `process.env` is what crosses that boundary rather than a module-level flag.\nconst prerendering = typeof process !== 'undefined' && !!process.env?.RSHONO_PRERENDER;\n\n/**\n * Runs `fn` with `c` bound as the ambient request context, so {@link getRequestContext} resolves to it\n * anywhere in the call tree.\n *\n * @internal\n */\nexport function runWithContext<T>(c: Context, fn: () => T): T {\n return contextStorage.run(c, fn);\n}\n\n/**\n * The matched route params, or an empty object when there is no active match.\n *\n * @internal\n */\nexport function readParams(c: Context): Record<string, string> {\n try {\n return c.req.param();\n } catch {\n return {};\n }\n}\n\n/** A proxy chain appends to these headers, so the client-facing value is the first entry. */\nfunction firstForwardedValue(header: string | undefined): string | undefined {\n const first = header?.split(',')[0]?.trim();\n return first || undefined;\n}\n\n// Read through `typeof`: DefinePlugin inlines this into the server bundle, but the module is also the\n// public `@rshono/core/server` entry, which tooling can load without one. Absent means don't trust.\nconst trustProxy = typeof __RSHONO_CONFIG__ !== 'undefined' && __RSHONO_CONFIG__.trustProxy;\n\n/**\n * The browser-facing {@link URL} for a request, resolved from Hono's {@link Context} — a fresh\n * instance per call.\n *\n * `c.req.url` is the internal address the server was reached on, which is wrong behind a proxy;\n * `X-Forwarded-Host` / `-Proto` correct it, but only when `trustProxy` is enabled in\n * `rshono.config.ts` — they are client-supplied, so trusting them unconditionally would let anyone\n * dictate the origin of every absolute URL the app builds.\n *\n * This is the form for **middleware**, which is handed `c` and runs outside the request context — and\n * so the way to give Hono's own middleware the origin the browser actually used. In a server component\n * or action, prefer {@link RequestContext.url}, the same value cached per request.\n *\n * @param c - The Hono {@link Context} for the request.\n * @returns The browser-facing URL — proxy-corrected under `trustProxy`, `c.req.url` otherwise.\n *\n * @example\n * ```ts\n * // src/server.ts — a CSRF check that still works behind a proxy that rewrites Host\n * import { publicUrl } from '@rshono/core/server';\n * import { csrf } from 'hono/csrf';\n *\n * server.use(csrf({ origin: (origin, c) => origin === publicUrl(c).origin }));\n * ```\n *\n * @see {@link https://www.rshono.com/docs/configuration#proxy-headers | Docs — proxy headers}\n */\nexport function publicUrl(c: Context): URL {\n const url = new URL(c.req.url);\n if (!trustProxy) return url;\n\n const forwardedHost = firstForwardedValue(c.req.header('x-forwarded-host'));\n // Parsed, not assigned to `url.host`: that setter keeps the existing port when the new value has none.\n const forwarded = forwardedHost ? URL.parse(`http://${forwardedHost}`) : null;\n if (forwarded) {\n url.hostname = forwarded.hostname;\n url.port = forwarded.port;\n }\n\n // Only the two schemes a browser could have requested; anything else leaves the scheme alone.\n const forwardedProto = firstForwardedValue(c.req.header('x-forwarded-proto'));\n if (forwardedProto === 'http' || forwardedProto === 'https') url.protocol = forwardedProto;\n\n return url;\n}\n\n/**\n * The environment available to a request: Workers `Bindings` merged with process env vars. Values not\n * declared in `Bindings` are typed as `string | undefined`. See {@link RequestContext.env}.\n *\n * @see {@link https://hono.dev/docs/getting-started/cloudflare-workers#bindings | Hono — bindings}\n */\nexport type EnvVars<E extends Env> = E['Bindings'] & Record<string, string | undefined>;\n\n/**\n * Read-mostly wrapper around Hono's {@link Context}, for server components and server actions.\n *\n * Obtain one with {@link getRequestContext}, or take it off a page's `ctx` prop — the same object.\n * Never construct it yourself. One instance is reused for the whole request, so its lazy getters\n * ({@link RequestContext.url}, {@link RequestContext.env}) are computed at most once.\n *\n * @typeParam E - The Hono {@link Env} describing this app's `Bindings` and `Variables`, so\n * {@link RequestContext.var} and {@link RequestContext.env} stay typed.\n *\n * @example\n * ```tsx\n * import { getRequestContext } from '@rshono/core/server';\n *\n * export default async function Whoami() {\n * const ctx = getRequestContext();\n * const session = ctx.cookies.get('session');\n * return <p>{ctx.url.pathname} — {session ?? 'anonymous'}</p>;\n * }\n * ```\n *\n * @see {@link https://www.rshono.com/docs/api#rshonocoreserver | Docs — `@rshono/core/server`}\n * @see {@link https://hono.dev/docs/api/context | Hono — Context}, reachable in full via {@link RequestContext.hono}\n */\nexport class RequestContext<E extends Env = Env> {\n #raw: Context<E>;\n #url?: URL;\n #env?: EnvVars<E>;\n #params?: Record<string, string>;\n\n /**\n * One instance is created per request and handed out by {@link getRequestContext} or the `ctx` page\n * prop. Application code never calls this.\n *\n * @internal\n */\n constructor(c: Context<E>) {\n this.#raw = c;\n }\n\n /**\n * The underlying Hono {@link Context} — the escape hatch for what this wrapper does not expose, such\n * as `executionCtx.waitUntil()` on Workers.\n *\n * Its response builders (`redirect`, `json`, `body`, `status`, …) still do nothing from inside a\n * page: reaching them through here bypasses the errors the stubs on this class throw, it does not\n * make them work.\n *\n * @example\n * ```ts\n * getRequestContext().hono.executionCtx.waitUntil(logAsync()); // Workers\n * ```\n *\n * @see {@link https://hono.dev/docs/api/context | Hono — Context}\n */\n // Every member here is a getter or method so that none is *own enumerable*: React's \"you cannot pass\n // this to a client component\" diagnostic walks `Object.keys` recursively with no cycle guard, and the\n // Hono context graph reaches the socket through `req.raw`. `cookies` is the one own property, and it\n // is a shallow object of four functions.\n get hono(): Context<E> {\n return this.#raw;\n }\n\n /**\n * The parsed request — method, headers, path params, query and the body readers. Hono's\n * {@link Context.req}, unwrapped, so `ctx.req.header('authorization')` rather than\n * `ctx.hono.req.header(…)`.\n *\n * Reads only; setting a *response* header is {@link RequestContext.setHeader}, deliberately spelled\n * differently.\n *\n * @example\n * ```ts\n * const ctx = getRequestContext();\n * ctx.req.method; // 'GET'\n * ctx.req.header('authorization'); // string | undefined\n * ctx.req.query('tab'); // string | undefined\n * ```\n *\n * @see {@link https://hono.dev/docs/api/request | Hono — HonoRequest}\n */\n get req(): Context<E>['req'] {\n return this.#raw.req;\n }\n\n /**\n * Matched route params for this request, e.g. `{ id: '42' }` for `/profile/:id`. Empty when no route\n * matched.\n *\n * A page is handed the same record as its `params` prop, typed key-by-key from its route path, and\n * that is the better read where it exists. This is for everywhere else — a nested server component,\n * or a `'use server'` action.\n */\n get params(): Record<string, string> {\n return (this.#params ??= readParams(this.#raw as Context));\n }\n\n /**\n * The browser-facing request URL. Parsed once and cached, so every read within a request returns the\n * same instance — treat it as read-only.\n *\n * `X-Forwarded-Host` / `-Proto` are honoured only when `trustProxy` is enabled in\n * `rshono.config.ts`, since any client can send them.\n *\n * @example `const tab = getRequestContext().url.searchParams.get('tab');`\n *\n * @see {@link https://www.rshono.com/docs/configuration#proxy-headers | Docs — proxy headers}\n */\n get url(): URL {\n return (this.#url ??= publicUrl(this.#raw as Context));\n }\n\n /**\n * Typed variables set by middleware via `c.set('user', …)`, read here as `ctx.var.user`. Type them by\n * parameterising this class's {@link Env}.\n *\n * @example\n * ```ts\n * type AppEnv = { Variables: { user: { id: string } } };\n * const { user } = getRequestContext<AppEnv>().var; // typed, set by your middleware\n * ```\n *\n * @see {@link https://hono.dev/docs/api/context#var | Hono — c.var}\n * @see {@link https://www.rshono.com/docs/hono#typing-the-context | Docs — typing the context}\n */\n get var(): Readonly<E['Variables']> {\n return this.#raw.var;\n }\n\n /**\n * Environment for the request: process env vars merged with runtime bindings, which win on conflict.\n * Computed once and cached.\n *\n * @example `const key = getRequestContext().env.STRIPE_SECRET_KEY;`\n *\n * @see {@link https://hono.dev/docs/api/context#env | Hono — c.env}\n * @see {@link https://www.rshono.com/docs/configuration#environment-and-secrets | Docs — environment and secrets}\n */\n get env(): EnvVars<E> {\n if (this.#env) return this.#env;\n const bindings = this.#raw.env as Record<string, unknown> | undefined;\n return (this.#env = (bindings ? { ...processEnv(), ...bindings } : processEnv()) as EnvVars<E>);\n }\n\n /**\n * Read and write request/response cookies.\n *\n * @example\n * ```ts\n * const ctx = getRequestContext();\n * ctx.cookies.get('session'); // string | undefined\n * ctx.cookies.set('session', id, { httpOnly: true, sameSite: 'Lax', path: '/' });\n * ctx.cookies.delete('session', { path: '/' });\n * ```\n *\n * @see {@link https://hono.dev/docs/helpers/cookie | Hono — cookie helper}, which this wraps\n */\n cookies = {\n /** Reads a single cookie by name, or `undefined` if absent. Safe anywhere, a page included. */\n get: (name: string): string | undefined => getCookie(this.#raw, name),\n /** Reads every cookie as a `{ name: value }` record. Safe anywhere, a page included. */\n all: (): Record<string, string> => getCookie(this.#raw),\n /**\n * Sets a cookie on the response. See Hono's {@link CookieOptions} for `path`, `httpOnly`, `maxAge`\n * and the rest.\n *\n * **Throws inside a page render** — a `Set-Cookie` is a special case of\n * {@link RequestContext.setHeader}. Set cookies from a `'use server'` action, or with Hono's\n * `setCookie(c, …)` in middleware and endpoint routes.\n *\n * @throws If called while a page is rendering, where it could not reach the browser reliably.\n *\n * @see {@link https://hono.dev/docs/helpers/cookie#options | Hono — cookie options}\n */\n set: (name: string, value: string, options?: CookieOptions): void => {\n this.#assertWritable('ctx.cookies.set()');\n setCookie(this.#raw, name, value, options);\n },\n /**\n * Clears a cookie. Pass the same `path`/`domain` it was set with so the browser matches it.\n * Throws inside a page render, exactly as `set` does.\n *\n * @throws If called while a page is rendering.\n */\n delete: (name: string, options?: CookieOptions): void => {\n this.#assertWritable('ctx.cookies.delete()');\n deleteCookie(this.#raw, name, options);\n },\n };\n\n #assertWritable(call: string): void {\n if (rendering.has(this.#raw as Context)) tooLateToWrite(call);\n }\n\n /**\n * Sets a header on the response — from a `'use server'` action, which is the one place a request\n * context exists *and* the response is still open.\n *\n * From inside a page it throws: a page streams, so its response head is already committed by then,\n * and the write would land on a full page load but vanish on a soft navigation.\n *\n * Middleware and `{ type: 'endpoint' }` routes are handed Hono's `c` directly and use `c.header(…)`.\n * That is also where a header belonging to the *page* goes — `Cache-Control`, `X-Robots-Tag` — since\n * middleware runs before the render.\n *\n * @param name - Header name, case-insensitive.\n * @param value - Header value.\n * @param options - `{ append: true }` to add another value rather than replace.\n * @throws If called while a page is rendering, where it could not reach the browser reliably.\n *\n * @example\n * ```ts\n * 'use server';\n * export async function logout() {\n * const ctx = getRequestContext();\n * ctx.cookies.delete('session', { path: '/' });\n * ctx.setHeader('clear-site-data', '\"cache\", \"storage\"');\n * redirect('/');\n * }\n * ```\n */\n setHeader(name: string, value: string, options?: { append?: boolean }): void {\n this.#assertWritable('ctx.setHeader()');\n this.#raw.header(name, value, options);\n }\n\n // Hono's response builders, restated as errors naming what to use instead — through `ctx.hono` every\n // one of them is a silent no-op from a page. `@deprecated` strikes them through in autocomplete; the\n // unread `..._args` is so `ctx.redirect('/x')` reaches the thrown message rather than an arity error.\n\n /** @deprecated Not available on a page's context — use `redirect()` from `@rshono/core/server`. */\n redirect(..._args: unknown[]): never {\n return notOnContext(\n 'redirect(location, status?)',\n \"Use `redirect()` from '@rshono/core/server', which throws a signal the framework turns into a real redirect.\",\n );\n }\n\n /** @deprecated Not available on a page's context — use `notFound()` from `@rshono/core/server`. */\n notFound(..._args: unknown[]): never {\n return notOnContext('notFound()', \"Use `notFound()` from '@rshono/core/server', which aborts the render and shows the app's not-found page.\");\n }\n\n /** @deprecated A page renders JSX. For a JSON response, use an `{ type: 'endpoint' }` route. */\n json(..._args: unknown[]): never {\n return notOnContext('json(object)', \"For a JSON response use an { type: 'endpoint' } route; to read the request body use `ctx.req.json()`.\");\n }\n\n /** @deprecated A page renders JSX. For a text response, use an `{ type: 'endpoint' }` route. */\n text(..._args: unknown[]): never {\n return notOnContext('text(string)', \"For a text response use an { type: 'endpoint' } route; to read the request body use `ctx.req.text()`.\");\n }\n\n /** @deprecated A page renders JSX, which the framework turns into HTML for you. */\n html(..._args: unknown[]): never {\n return notOnContext('html(string)', \"A page's JSX is already its HTML; for a hand-built HTML response use an { type: 'endpoint' } route.\");\n }\n\n /** @deprecated Not available on a page's context. To read the request body, use `ctx.req`. */\n body(..._args: unknown[]): never {\n return notOnContext(\n 'body(data, …)',\n \"To read the *request* body use `ctx.req.json()` / `ctx.req.text()` / `ctx.req.formData()`; to build a response, use an { type: 'endpoint' } route.\",\n );\n }\n\n /** @deprecated A page's status is set by the framework — use `notFound()`, or an endpoint route. */\n status(..._args: unknown[]): never {\n return notOnContext(\n 'status(code)',\n \"A page's status is the framework's: 200, 404 via `notFound()`, 500 when it throws. For any other code use an { type: 'endpoint' } route.\",\n );\n }\n\n /** @deprecated Renamed — use `ctx.setHeader(name, value)`, which is valid from a `'use server'` action. */\n header(..._args: unknown[]): never {\n return notOnContext(\n 'header(name, value)',\n \"Use `ctx.setHeader(name, value)` from a 'use server' action, or `c.header(…)` in middleware — a page renders too late to set one.\",\n );\n }\n}\n\n/**\n * The {@link RequestContext} for the current request — URL, cookies, params, env and middleware\n * variables — read from a server component or a server action. Memoised per request, so repeated calls\n * return the same instance.\n *\n * A page is handed that same object as its `ctx` prop, so this import is for everywhere else: a nested\n * server component, or a `'use server'` action module.\n *\n * @typeParam E - The app's Hono {@link Env}, to type {@link RequestContext.var} and {@link RequestContext.env}.\n * @throws If called at module load, where there is no ambient context to resolve.\n * @throws If called while prerendering a `render: 'static'` route, which has no\n * per-request context at build time — mark the route `render: 'dynamic'` instead.\n *\n * @example\n * ```ts\n * 'use server';\n * import { getRequestContext, redirect } from '@rshono/core/server';\n *\n * export async function login(form: FormData) {\n * getRequestContext().cookies.set('session', String(form.get('email')), { httpOnly: true });\n * redirect('/dashboard');\n * }\n * ```\n *\n * @see {@link https://www.rshono.com/docs/api#rshonocoreserver | Docs — `@rshono/core/server`}\n */\nexport function getRequestContext<E extends Env = Env>(): RequestContext<E> {\n if (prerendering) {\n throw new Error(\n \"[rshono] getRequestContext() was called while prerendering a `render: 'static'` route. A static page \" +\n 'is rendered once at build time, so it has no per-request context to read (URL, cookies, ' +\n \"headers, env). Change this route to `render: 'dynamic'` so it renders per request, or remove \" +\n 'the getRequestContext() call.',\n );\n }\n const c = contextStorage.getStore();\n if (!c) {\n throw new Error(\n '[rshono] getRequestContext() was called outside a request. It only works inside a server component or a server action, not at module load.',\n );\n }\n let ctx = wrappers.get(c);\n if (!ctx) {\n ctx = new RequestContext(c);\n wrappers.set(c, ctx);\n }\n return ctx as unknown as RequestContext<E>;\n}\n\n/**\n * Redirects the request to `location`, by throwing a control signal the framework turns into an HTTP\n * redirect.\n *\n * Because it throws it never returns, so TypeScript narrows away everything after the call and there\n * is nothing to `return`. Don't wrap it in a `try/catch` that swallows the signal.\n *\n * @param location - Absolute path or URL to redirect to, e.g. `/dashboard`.\n * @param status - Redirect {@link RedirectStatus}; defaults to `303` (See Other), which is what makes\n * the browser follow up with a `GET` after a form action.\n *\n * @example\n * ```ts\n * const session = getRequestContext().cookies.get('session');\n * if (!session) redirect('/login');\n * // session is defined below this line\n * ```\n */\nexport function redirect(location: string, status: RedirectStatus = 303): never {\n throw new RedirectSignal(location, status);\n}\n\n/**\n * Aborts the current render with a 404, rendering the app's `notFound` page.\n *\n * Like {@link redirect} it throws a control signal and never returns, so TypeScript narrows away\n * everything after the call. Don't catch-and-swallow it.\n *\n * @example\n * ```tsx\n * export default async function Page({ params }: PageProps<'/users/:id'>) {\n * const user = await db.user.find(params.id);\n * if (!user) notFound();\n * return <Profile user={user} />; // user is non-null here\n * }\n * ```\n */\nexport function notFound(): never {\n throw new NotFoundSignal();\n}\n\n/**\n * Which stage of a request produced an error handed to a {@link ServerErrorHandler}.\n *\n * - `action` — a `'use server'` function threw. React sends the client an opaque marker with no\n * message in production, so this is the only place the real error is visible.\n * - `render` — a server component threw while the flight payload was being produced.\n * - `ssr` — SSR failed before the HTML shell could be sent, so the `error` page was unreachable too.\n * - `request` — anything else that reached the top-level handler, a thrown endpoint route included.\n */\nexport type ServerErrorSource = 'action' | 'render' | 'ssr' | 'request';\n\n/** What an {@link ServerErrorHandler} is told about an error, beyond the error itself. */\nexport interface ServerErrorContext {\n /** The stage that produced it — see {@link ServerErrorSource}. */\n source: ServerErrorSource;\n /** The request being served, for the URL, method and headers. */\n request: Request;\n}\n\n/** Handler registered with {@link onServerError}. Called for the side effect; its return value is ignored. */\nexport type ServerErrorHandler = (error: unknown, context: ServerErrorContext) => void;\n\nlet errorHandler: ServerErrorHandler | undefined;\n\n/**\n * Registers a handler for every error the framework catches, so they can reach an error tracker\n * (Sentry, Datadog, a log pipeline) instead of only `stderr`.\n *\n * Call it once, at the top level of `src/server.ts`, which is imported as the server starts.\n * Registering again replaces the previous handler. Errors still go to `stderr` either way, and a\n * handler that throws is caught and logged — reporting can never fail a request.\n *\n * @example\n * ```ts\n * // src/server.ts\n * import * as Sentry from '@sentry/node';\n * import { onServerError } from '@rshono/core/server';\n *\n * onServerError((error, { source, request }) => {\n * Sentry.captureException(error, { tags: { source }, extra: { url: request.url } });\n * });\n * ```\n *\n * @see {@link https://www.rshono.com/docs/hono#error-reporting | Docs — error reporting}\n */\nexport function onServerError(handler: ServerErrorHandler): void {\n errorHandler = handler;\n}\n\n/**\n * Logs an error and forwards it to the registered {@link ServerErrorHandler} — the single funnel every\n * caught server-side error goes through.\n *\n * @internal\n */\nexport function reportServerError(error: unknown, info: ServerErrorContext & { message: string }): void {\n console.error(info.message, error);\n if (!errorHandler) return;\n try {\n errorHandler(error, { source: info.source, request: info.request });\n } catch (handlerError) {\n console.error('[rshono] the onServerError handler threw:', handlerError);\n }\n}\n"]}
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * `redirect()` and `notFound()` as thrown errors, and the `digest` string that carries them.
3
3
  *
4
- * The digest is the only part that survives React: an error thrown inside a server component reaches
5
- * the browser as a digest with no message, so the redirect target has to be encoded into it. The
6
- * server catches the class (`isControlSignal`), the client matches the string (`isControlDigest`).
4
+ * The digest is the only part that survives React — a server component's error reaches the browser as a
5
+ * digest with no message — so the redirect target is encoded into it. The server matches the class, the
6
+ * client matches the string.
7
7
  */
8
8
  const REDIRECT_PREFIX = 'RSHONO_REDIRECT;';
9
9
  const NOT_FOUND_DIGEST = 'RSHONO_NOT_FOUND';
@@ -1 +1 @@
1
- {"version":3,"file":"control.js","sourceRoot":"","sources":["../../src/runtime/control.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,eAAe,GAAG,kBAAkB,CAAC;AAC3C,MAAM,gBAAgB,GAAG,kBAAkB,CAAC;AAE5C,MAAM,OAAO,cAAe,SAAQ,KAAK;IAG5B,QAAQ;IACR,MAAM;IAHR,MAAM,CAAS;IACxB,YACW,QAAgB,EAChB,MAAc;QAEvB,KAAK,CAAC,wBAAwB,QAAQ,EAAE,CAAC,CAAC;wBAHjC,QAAQ;sBACR,MAAM;QAGf,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;QAC7B,IAAI,CAAC,MAAM,GAAG,GAAG,eAAe,GAAG,MAAM,IAAI,kBAAkB,CAAC,QAAQ,CAAC,EAAE,CAAC;IAC9E,CAAC;CACF;AAED,MAAM,OAAO,cAAe,SAAQ,KAAK;IAC9B,MAAM,GAAG,gBAAgB,CAAC;IACnC;QACE,KAAK,CAAC,mBAAmB,CAAC,CAAC;QAC3B,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;IAC/B,CAAC;CACF;AAID,MAAM,UAAU,eAAe,CAAC,KAAc;IAC5C,OAAO,KAAK,YAAY,cAAc,IAAI,KAAK,YAAY,cAAc,CAAC;AAC5E,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,MAAe;IAC7C,OAAO,OAAO,MAAM,KAAK,QAAQ,IAAI,CAAC,MAAM,KAAK,gBAAgB,IAAI,MAAM,CAAC,UAAU,CAAC,eAAe,CAAC,CAAC,CAAC;AAC3G,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,MAAc;IAChD,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,eAAe,CAAC;QAAE,OAAO,IAAI,CAAC;IACrD,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC;IAClD,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC9B,IAAI,GAAG,KAAK,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAC5B,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;IAC1C,MAAM,QAAQ,GAAG,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;IACzD,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;AACtE,CAAC","sourcesContent":["/**\n * `redirect()` and `notFound()` as thrown errors, and the `digest` string that carries them.\n *\n * The digest is the only part that survives React: an error thrown inside a server component reaches\n * the browser as a digest with no message, so the redirect target has to be encoded into it. The\n * server catches the class (`isControlSignal`), the client matches the string (`isControlDigest`).\n */\nconst REDIRECT_PREFIX = 'RSHONO_REDIRECT;';\nconst NOT_FOUND_DIGEST = 'RSHONO_NOT_FOUND';\n\nexport class RedirectSignal extends Error {\n readonly digest: string;\n constructor(\n readonly location: string,\n readonly status: number,\n ) {\n super(`[rshono] redirect to ${location}`);\n this.name = 'RedirectSignal';\n this.digest = `${REDIRECT_PREFIX}${status};${encodeURIComponent(location)}`;\n }\n}\n\nexport class NotFoundSignal extends Error {\n readonly digest = NOT_FOUND_DIGEST;\n constructor() {\n super('[rshono] notFound');\n this.name = 'NotFoundSignal';\n }\n}\n\nexport type ControlSignal = RedirectSignal | NotFoundSignal;\n\nexport function isControlSignal(error: unknown): error is ControlSignal {\n return error instanceof RedirectSignal || error instanceof NotFoundSignal;\n}\n\nexport function isControlDigest(digest: unknown): digest is string {\n return typeof digest === 'string' && (digest === NOT_FOUND_DIGEST || digest.startsWith(REDIRECT_PREFIX));\n}\n\nexport function parseRedirectDigest(digest: string): { location: string; status: number } | null {\n if (!digest.startsWith(REDIRECT_PREFIX)) return null;\n const rest = digest.slice(REDIRECT_PREFIX.length);\n const sep = rest.indexOf(';');\n if (sep === -1) return null;\n const status = Number(rest.slice(0, sep));\n const location = decodeURIComponent(rest.slice(sep + 1));\n return { location, status: Number.isFinite(status) ? status : 307 };\n}\n"]}
1
+ {"version":3,"file":"control.js","sourceRoot":"","sources":["../../src/runtime/control.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,eAAe,GAAG,kBAAkB,CAAC;AAC3C,MAAM,gBAAgB,GAAG,kBAAkB,CAAC;AAE5C,MAAM,OAAO,cAAe,SAAQ,KAAK;IAG5B,QAAQ;IACR,MAAM;IAHR,MAAM,CAAS;IACxB,YACW,QAAgB,EAChB,MAAc;QAEvB,KAAK,CAAC,wBAAwB,QAAQ,EAAE,CAAC,CAAC;wBAHjC,QAAQ;sBACR,MAAM;QAGf,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;QAC7B,IAAI,CAAC,MAAM,GAAG,GAAG,eAAe,GAAG,MAAM,IAAI,kBAAkB,CAAC,QAAQ,CAAC,EAAE,CAAC;IAC9E,CAAC;CACF;AAED,MAAM,OAAO,cAAe,SAAQ,KAAK;IAC9B,MAAM,GAAG,gBAAgB,CAAC;IACnC;QACE,KAAK,CAAC,mBAAmB,CAAC,CAAC;QAC3B,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;IAC/B,CAAC;CACF;AAID,MAAM,UAAU,eAAe,CAAC,KAAc;IAC5C,OAAO,KAAK,YAAY,cAAc,IAAI,KAAK,YAAY,cAAc,CAAC;AAC5E,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,MAAe;IAC7C,OAAO,OAAO,MAAM,KAAK,QAAQ,IAAI,CAAC,MAAM,KAAK,gBAAgB,IAAI,MAAM,CAAC,UAAU,CAAC,eAAe,CAAC,CAAC,CAAC;AAC3G,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,MAAc;IAChD,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,eAAe,CAAC;QAAE,OAAO,IAAI,CAAC;IACrD,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC;IAClD,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC9B,IAAI,GAAG,KAAK,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAC5B,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;IAC1C,MAAM,QAAQ,GAAG,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;IACzD,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;AACtE,CAAC","sourcesContent":["/**\n * `redirect()` and `notFound()` as thrown errors, and the `digest` string that carries them.\n *\n * The digest is the only part that survives React — a server component's error reaches the browser as a\n * digest with no message — so the redirect target is encoded into it. The server matches the class, the\n * client matches the string.\n */\nconst REDIRECT_PREFIX = 'RSHONO_REDIRECT;';\nconst NOT_FOUND_DIGEST = 'RSHONO_NOT_FOUND';\n\nexport class RedirectSignal extends Error {\n readonly digest: string;\n constructor(\n readonly location: string,\n readonly status: number,\n ) {\n super(`[rshono] redirect to ${location}`);\n this.name = 'RedirectSignal';\n this.digest = `${REDIRECT_PREFIX}${status};${encodeURIComponent(location)}`;\n }\n}\n\nexport class NotFoundSignal extends Error {\n readonly digest = NOT_FOUND_DIGEST;\n constructor() {\n super('[rshono] notFound');\n this.name = 'NotFoundSignal';\n }\n}\n\nexport type ControlSignal = RedirectSignal | NotFoundSignal;\n\nexport function isControlSignal(error: unknown): error is ControlSignal {\n return error instanceof RedirectSignal || error instanceof NotFoundSignal;\n}\n\nexport function isControlDigest(digest: unknown): digest is string {\n return typeof digest === 'string' && (digest === NOT_FOUND_DIGEST || digest.startsWith(REDIRECT_PREFIX));\n}\n\nexport function parseRedirectDigest(digest: string): { location: string; status: number } | null {\n if (!digest.startsWith(REDIRECT_PREFIX)) return null;\n const rest = digest.slice(REDIRECT_PREFIX.length);\n const sep = rest.indexOf(';');\n if (sep === -1) return null;\n const status = Number(rest.slice(0, sep));\n const location = decodeURIComponent(rest.slice(sep + 1));\n return { location, status: Number.isFinite(status) ? status : 307 };\n}\n"]}
@@ -1,14 +1,10 @@
1
1
  /**
2
- * The messages the dev server pushes over the `/_rshono/hmr` SSE channel, as a
3
- * single closed union shared by both ends — the producer (`cli/dev.ts`) and the
4
- * consumer (`runtime/entry.client.tsx`). Adding a variant here forces both sides
5
- * to handle it, so the wire protocol can't drift.
2
+ * The messages the dev server pushes over the `/_rshono/hmr` SSE channel — one closed union shared by the
3
+ * producer (`cli/dev.ts`) and the consumer (`runtime/entry.client.tsx`), so the wire protocol cannot drift.
6
4
  *
7
- * - `hello` — sent on (re)connect with the latest build hash; a mismatch means
8
- * events were missed while disconnected, so the client resyncs.
5
+ * - `hello` — sent on (re)connect with the latest build hash; a mismatch means events were missed.
9
6
  * - `client-built` — the client bundle rebuilt; the client hot-applies the update.
10
- * - `rsc-update` — server component code changed; the client re-fetches the flight
11
- * payload for the current URL in place.
7
+ * - `rsc-update` — server component code changed; the client re-fetches the flight payload in place.
12
8
  */
13
9
  export type DevMessage = {
14
10
  type: 'hello';
@@ -1 +1 @@
1
- {"version":3,"file":"dev-protocol.d.ts","sourceRoot":"","sources":["../../src/runtime/dev-protocol.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,UAAU,GAAG;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,CAAC"}
1
+ {"version":3,"file":"dev-protocol.d.ts","sourceRoot":"","sources":["../../src/runtime/dev-protocol.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,MAAM,UAAU,GAAG;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"dev-protocol.js","sourceRoot":"","sources":["../../src/runtime/dev-protocol.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * The messages the dev server pushes over the `/_rshono/hmr` SSE channel, as a\n * single closed union shared by both ends — the producer (`cli/dev.ts`) and the\n * consumer (`runtime/entry.client.tsx`). Adding a variant here forces both sides\n * to handle it, so the wire protocol can't drift.\n *\n * - `hello` — sent on (re)connect with the latest build hash; a mismatch means\n * events were missed while disconnected, so the client resyncs.\n * - `client-built` — the client bundle rebuilt; the client hot-applies the update.\n * - `rsc-update` — server component code changed; the client re-fetches the flight\n * payload for the current URL in place.\n */\nexport type DevMessage = { type: 'hello'; hash?: string } | { type: 'client-built'; hash: string } | { type: 'rsc-update' };\n"]}
1
+ {"version":3,"file":"dev-protocol.js","sourceRoot":"","sources":["../../src/runtime/dev-protocol.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * The messages the dev server pushes over the `/_rshono/hmr` SSE channel — one closed union shared by the\n * producer (`cli/dev.ts`) and the consumer (`runtime/entry.client.tsx`), so the wire protocol cannot drift.\n *\n * - `hello` — sent on (re)connect with the latest build hash; a mismatch means events were missed.\n * - `client-built` — the client bundle rebuilt; the client hot-applies the update.\n * - `rsc-update` — server component code changed; the client re-fetches the flight payload in place.\n */\nexport type DevMessage = { type: 'hello'; hash?: string } | { type: 'client-built'; hash: string } | { type: 'rsc-update' };\n"]}
@@ -3,13 +3,13 @@ import React from 'react';
3
3
  import { hydrateRoot } from 'react-dom/client';
4
4
  import { createFromFetch, createFromReadableStream, createTemporaryReferenceSet, encodeReply, setServerCallback, } from 'react-server-dom-rspack/client.browser';
5
5
  import { isControlDigest, parseRedirectDigest } from './control.js';
6
- // Dev-only, and reached only from inside an `import.meta.webpackHot` branch — which a production
7
- // build compiles to `false`, leaving this module unreferenced and dropped.
6
+ // Dev-only: its one caller sits behind `import.meta.webpackHot`, which a production build compiles to
7
+ // `false` — so this module is dropped there.
8
8
  import { walkHotUpdates } from './hot-update.js';
9
9
  import { RouterContext } from './navigation.js';
10
10
  import { createRscRequest } from './request.js';
11
11
  const isDev = process.env.NODE_ENV === 'development';
12
- /** The flight payload the document carried, read back out of `__FLIGHT_DATA` — see `flight-inject.ts`, which writes it. */
12
+ /** The flight payload the document carried, read back out of `__FLIGHT_DATA` — see `flight-inject.ts`. */
13
13
  function readFlightPayload() {
14
14
  const encoder = new TextEncoder();
15
15
  // Assigned synchronously by `start`, which `new ReadableStream` runs before it returns.
@@ -18,14 +18,13 @@ function readFlightPayload() {
18
18
  start: (c) => void (controller = c),
19
19
  });
20
20
  const enqueue = (chunk) => controller.enqueue(typeof chunk === 'string' ? encoder.encode(chunk) : chunk);
21
- // Payload scripts interleave with the document, so some have already run by the time this module
22
- // is evaluated — those are in the array — and the rest run after it, arriving through `push`.
21
+ // Payload scripts interleave with the document: the ones that already ran are in the array, the rest
22
+ // arrive through `push`.
23
23
  const data = (self.__FLIGHT_DATA ??= []);
24
24
  for (const chunk of data)
25
25
  enqueue(chunk);
26
26
  data.push = enqueue;
27
- // The last payload script lands before the document finishes parsing, so that is what says there
28
- // is no more of it to come.
27
+ // The last payload script lands before parsing finishes, so that is what closes the stream.
29
28
  if (document.readyState === 'loading') {
30
29
  document.addEventListener('DOMContentLoaded', () => controller.close(), { once: true });
31
30
  }
@@ -36,10 +35,7 @@ function readFlightPayload() {
36
35
  }
37
36
  /** Created at module evaluation, not inside `main()`, so no chunk can be pushed before it is watching. */
38
37
  const flightStream = readFlightPayload();
39
- /**
40
- * Guarantees somewhere to attach the fatal overlay. React's root container is the whole `document`,
41
- * so by the time an uncaught error has torn the tree down, `<body>` — or even `<html>` — may be gone.
42
- */
38
+ /** Guarantees somewhere to attach the fatal overlay: the root container is `document`, so a teardown can take `<body>` with it. */
43
39
  function overlayHost() {
44
40
  if (!document.documentElement)
45
41
  document.appendChild(document.createElement('html'));
@@ -48,25 +44,20 @@ function overlayHost() {
48
44
  return document.body;
49
45
  }
50
46
  /**
51
- * Replaces the white screen of death with something readable.
47
+ * Paints the reason for an uncaught render error over the blank page it leaves behind — the full stack in
48
+ * dev, a generic notice and a reload button in production.
52
49
  *
53
- * Because the root container is `document`, an uncaught render error leaves a genuinely blank page
54
- * with the reason only in the console — so this paints the reason over it instead. In development
55
- * that's the full stack; in production it's a generic notice plus a reload button, since the tree is
56
- * unrecoverable and reloading is the only way forward.
57
- *
58
- * Written with DOM calls rather than React (the renderer is what just failed) and `textContent`
59
- * rather than `innerHTML` (an error message is untrusted input).
50
+ * DOM calls rather than React (the renderer is what just failed), and `textContent` rather than
51
+ * `innerHTML` (an error message is untrusted input).
60
52
  */
61
53
  function showFatal(error, componentStack) {
62
- // Queued rather than run inline: React's teardown happens after this callback returns, and would
63
- // remove a node appended synchronously along with the rest of the tree.
54
+ // Queued: React's teardown runs after this callback returns and would remove a node appended inline.
64
55
  setTimeout(() => {
65
56
  const host = overlayHost();
66
57
  host.querySelector('[data-rshono-fatal]')?.remove();
67
58
  const box = document.createElement('div');
68
59
  box.setAttribute('data-rshono-fatal', '');
69
- box.setAttribute('role', 'alert'); // the page is gone; announce it rather than leaving silence
60
+ box.setAttribute('role', 'alert');
70
61
  box.style.cssText =
71
62
  'position:fixed;inset:0;z-index:2147483647;overflow:auto;padding:1.5rem;background:#18181b;color:#f4f4f5;' +
72
63
  'font:14px/1.6 ui-monospace,SFMono-Regular,Menlo,monospace;text-align:left';
@@ -98,22 +89,22 @@ function showFatal(error, componentStack) {
98
89
  }, 0);
99
90
  }
100
91
  /**
101
- * Asks a URL for its flight payload.
102
- *
103
- * Deliberately uncached: every navigation fetches at the moment it is asked for, so a payload can
104
- * never be staler than the click that wanted it, and the browser's own HTTP cache is what makes a
105
- * repeat visit cheap.
92
+ * Asks a URL for its flight payload. Deliberately uncached — a payload can never be staler than the click
93
+ * that wanted it, and the browser's own HTTP cache is what makes a repeat visit cheap.
106
94
  */
107
95
  function requestPayload(href) {
108
96
  return createFromFetch(fetch(createRscRequest(new URL(href, location.href).href)));
109
97
  }
110
98
  async function main() {
99
+ // The assertion is load-bearing under the compiler that builds this: TypeScript 7 declares `nonce` on
100
+ // HTMLElement, 6 declares it on Element. ESLint runs the older lib — where the narrowing is redundant —
101
+ // so it reports an assertion that `tsc` requires. Believe `typecheck`, not the rule.
102
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
111
103
  const cspMeta = document.querySelector('meta[property="csp-nonce"]');
112
104
  if (cspMeta?.nonce)
113
105
  __webpack_nonce__ = cspMeta.nonce;
114
- // Both are replaced by BrowserRoot's own on mount. The defaults matter: `setServerCallback` is
115
- // registered before hydration, so an action or refresh firing in that window would otherwise call
116
- // an unassigned binding. Until there's a root to update, a full reload is the honest fallback.
106
+ // Both are replaced by BrowserRoot's own on mount. The defaults cover the window before hydration, where
107
+ // `setServerCallback` is already registered but there is no root to update — a reload is the honest answer.
117
108
  let setPayload = () => {
118
109
  window.location.reload();
119
110
  };
@@ -138,8 +129,7 @@ async function main() {
138
129
  }
139
130
  window.history.replaceState(null, '', target.href);
140
131
  }
141
- // A refresh keeps the URL, so it can't ride the history patch like push/replace — it drives the
142
- // flight re-fetch directly.
132
+ // A refresh keeps the URL, so it can't ride the history patch like push/replace and drives the re-fetch itself.
143
133
  const refresh = () => startNav(async () => {
144
134
  try {
145
135
  await fetchRscPayload();
@@ -150,12 +140,10 @@ async function main() {
150
140
  });
151
141
  /**
152
142
  * Turns a control-signal digest — how `redirect()` / `notFound()` reach the browser — into a real
153
- * navigation. Returns false for anything else, so callers can fall through to their own handling.
143
+ * navigation. Returns false for anything else, so callers fall through to their own handling.
154
144
  *
155
- * `hard` forces a full document load, for signals that surfaced *through React* (a nested
156
- * component's redirect, reported via the root error handlers). React unmounts the root on an
157
- * uncaught error, so there is no live tree left to soft-navigate with. A signal caught earlier —
158
- * a top-level payload rejection — still swaps the payload in place.
145
+ * `hard` forces a full document load, for signals that surfaced *through React*: it unmounts the root on
146
+ * an uncaught error, leaving no live tree to soft-navigate with.
159
147
  */
160
148
  function handleControlDigest(error, { hard = false } = {}) {
161
149
  const digest = error?.digest;
@@ -197,11 +185,8 @@ async function main() {
197
185
  startNav = (run) => startTransition(run);
198
186
  }, [startTransition]);
199
187
  /**
200
- * Scrolls where the navigation asked, once React has put its payload in the DOM.
201
- *
202
- * This has to wait for the commit rather than the fetch: a `#hash` target does not exist until the
203
- * new tree does, and until then the page a scroll would move is still the outgoing one. A layout
204
- * effect runs before the browser paints, so the pre-scroll position is never on screen.
188
+ * Scrolls where the navigation asked, once React has put its payload in the DOM — a `#hash` target does
189
+ * not exist until the new tree does. A layout effect, so the pre-scroll position is never painted.
205
190
  */
206
191
  React.useLayoutEffect(() => {
207
192
  const scroll = pendingScroll.current;
@@ -254,27 +239,23 @@ async function main() {
254
239
  throw result.error;
255
240
  return result.value;
256
241
  });
257
- // A `redirect()` / `notFound()` from a component *below* the page root reaches us through React: it
258
- // rides the flight payload as an error at that component's position, and boundaries re-throw it
259
- // (see boundaries.tsx) so it lands here rather than rendering an error fallback.
242
+ // A `redirect()` / `notFound()` from a component below the page root reaches us through React: it rides the
243
+ // flight payload as an error, and boundaries re-throw it so it lands here rather than in a fallback.
260
244
  //
261
- // Installing these hooks opts out of React's own defaults, so anything that isn't a control signal
262
- // has to be put back by hand: console for a caught error, `reportError` (i.e. window.onerror, so
263
- // error-reporting tools still see it) for an uncaught one.
245
+ // Installing these hooks opts out of React's own defaults, so everything that isn't a control signal has to
246
+ // be put back by hand — `reportError` rather than a bare log, so error-reporting tools still see it.
264
247
  hydrateRoot(document, _jsx(BrowserRoot, {}), {
265
248
  formState: initialPayload.formState,
266
249
  onCaughtError: (error, errorInfo) => {
267
250
  if (handleControlDigest(error, { hard: true }))
268
251
  return;
269
- // A boundary handled this and the tree is intact, so no overlay: whatever fallback the app
270
- // chose is the right thing to have on screen.
252
+ // A boundary handled it and the tree is intact, so no overlay over the app's own fallback.
271
253
  console.error(error, errorInfo.componentStack ?? '');
272
254
  },
273
255
  onUncaughtError: (error, errorInfo) => {
274
256
  if (handleControlDigest(error, { hard: true }))
275
257
  return;
276
- // Nothing caught it, so React tears the root down — and the root is `document`. This is the
277
- // white screen; paint the reason over it.
258
+ // Nothing caught it, so React tears the root down — and the root is `document`.
278
259
  globalThis.reportError(error);
279
260
  showFatal(error, errorInfo.componentStack);
280
261
  },
@@ -283,10 +264,7 @@ async function main() {
283
264
  initDevRefresh(fetchRscPayload);
284
265
  }
285
266
  }
286
- /**
287
- * Runs teardown in reverse and empties the list, so a second call is a no-op. Collecting these as
288
- * setup goes keeps each undo next to the thing it undoes.
289
- */
267
+ /** Runs teardown in reverse and empties the list, so a second call is a no-op. */
290
268
  function disposeAll(undo) {
291
269
  for (const dispose of undo.splice(0).reverse())
292
270
  dispose();
@@ -301,10 +279,8 @@ function isRouterLink(link) {
301
279
  !link.hasAttribute('data-native'));
302
280
  }
303
281
  /**
304
- * Upgrades the app's anchors: a plain left-click becomes a soft navigation.
305
- *
306
- * Kept apart from `listenNavigation` because the two share no state — a click here only calls
307
- * `history.pushState`, which is where that function picks the navigation up.
282
+ * Upgrades the app's anchors: a plain left-click becomes a soft navigation. It shares no state with
283
+ * `listenNavigation` — a click only calls `history.pushState`, which is where that picks the navigation up.
308
284
  */
309
285
  function listenLinks() {
310
286
  const undo = [];
@@ -330,11 +306,8 @@ function listenLinks() {
330
306
  return () => disposeAll(undo);
331
307
  }
332
308
  /**
333
- * The element the current `#fragment` names, if it is on the page.
334
- *
335
- * A fragment is percent-encoded and an `id` attribute is not, so it has to be decoded first. A
336
- * hand-written URL can carry a `%` that is not an escape, which throws — take the fragment literally
337
- * then, since that is the closest thing to an id it could have meant.
309
+ * The element the current `#fragment` names, if it is on the page. A fragment is percent-encoded and an `id`
310
+ * is not, so it is decoded first — and taken literally when a hand-written `%` makes that throw.
338
311
  */
339
312
  function fragmentTarget() {
340
313
  const fragment = location.hash.slice(1);
@@ -344,37 +317,36 @@ function fragmentTarget() {
344
317
  try {
345
318
  id = decodeURIComponent(fragment);
346
319
  }
347
- catch { }
320
+ catch {
321
+ // Malformed escape — the literal fragment is the better guess at the id than nothing.
322
+ }
348
323
  return document.getElementById(id);
349
324
  }
350
325
  function listenNavigation(onNavigation) {
351
326
  const undo = [];
352
- // Scroll restoration is the browser's. `auto` is set explicitly rather than left at the default,
353
- // because it is a statement: the browser remembers a traversal's offset for us, and nothing here
327
+ // Set explicitly as a statement of intent: the browser remembers a traversal's offset, and nothing here
354
328
  // tracks one.
355
329
  const prevRestoration = window.history.scrollRestoration;
356
330
  try {
357
331
  window.history.scrollRestoration = 'auto';
358
332
  }
359
- catch { }
333
+ catch {
334
+ // Not settable in every browser, and only a preference — the navigation still works without it.
335
+ }
360
336
  undo.push(() => {
361
337
  try {
362
338
  window.history.scrollRestoration = prevRestoration;
363
339
  }
364
- catch { }
340
+ catch {
341
+ // As above: if it could not be set, it cannot be put back either.
342
+ }
365
343
  });
366
344
  /**
367
- * A push is not a real navigation to the browser, so nothing resets the scroll offset — without
368
- * this a click through to a new page lands wherever the last one was scrolled to. A `#hash` names
369
- * where to land instead, and one naming nothing on the page falls back to the top, as a browser
370
- * does. `replace` keeps its position deliberately, and a traversal is the browser's to restore.
371
- *
372
- * `scrollIntoView` rather than `scrollTo`, because it is the algorithm a browser's own fragment jump
373
- * uses — so `scroll-padding-top` still applies. Neither call passes a `behavior`, leaving
374
- * `scroll-behavior: smooth` the app's to ask for.
345
+ * A push is not a real navigation to the browser, so nothing resets the scroll offset. A `#hash` names
346
+ * where to land instead; `replace` keeps its position, and a traversal is the browser's to restore.
375
347
  *
376
- * The caller decides *when* this runs; for a navigation that fetched a payload it has to be after
377
- * the commit — see the layout effect in `BrowserRoot`.
348
+ * `scrollIntoView` is the algorithm a browser's own fragment jump uses, so `scroll-padding-top` still
349
+ * applies. Neither call passes a `behavior`, leaving `scroll-behavior: smooth` the app's to ask for.
378
350
  */
379
351
  const afterRenderFor = (type) => () => {
380
352
  if (type !== 'push')
@@ -386,16 +358,12 @@ function listenNavigation(onNavigation) {
386
358
  window.scrollTo(0, 0);
387
359
  };
388
360
  const documentUrl = () => location.pathname + location.search;
389
- // What the payload on screen was rendered for. Only the document part: the server never sees the
390
- // fragment, so two URLs differing by one render identically.
361
+ // What the payload on screen was rendered for. The document part only: the server never sees the fragment.
391
362
  let renderedUrl = documentUrl();
392
363
  /**
393
- * A navigation that moves only the fragment — `#a` → `#b`, or back out of a same-page anchor —
394
- * leaves the document unchanged, so the payload already on screen is the right one. Fetching
395
- * another would be a wasted round-trip that re-renders the page out from under the jump.
396
- *
397
- * `router.refresh()` is unaffected: it drives the re-fetch directly rather than through here, and
398
- * remains the way to ask for fresh data at an unchanged URL.
364
+ * A navigation that moves only the fragment leaves the document unchanged, so the payload on screen is
365
+ * already the right one — fetching another would re-render the page out from under the jump.
366
+ * `router.refresh()` is unaffected, and remains the way to ask for fresh data at an unchanged URL.
399
367
  */
400
368
  const notify = (type) => {
401
369
  const afterRender = afterRenderFor(type);
@@ -409,6 +377,9 @@ function listenNavigation(onNavigation) {
409
377
  const onPopState = () => notify('pop');
410
378
  window.addEventListener('popstate', onPopState);
411
379
  undo.push(() => window.removeEventListener('popstate', onPopState));
380
+ // Saved unbound on purpose, and called back with `.call(this, …)` below — patching `history` is the only
381
+ // way to see a navigation the app makes itself, and the receiver is restored at every call site.
382
+ // eslint-disable-next-line @typescript-eslint/unbound-method
412
383
  const oldPushState = window.history.pushState;
413
384
  window.history.pushState = function (state, unused, url) {
414
385
  const res = oldPushState.call(this, state, unused, url);
@@ -418,6 +389,7 @@ function listenNavigation(onNavigation) {
418
389
  undo.push(() => {
419
390
  window.history.pushState = oldPushState;
420
391
  });
392
+ // eslint-disable-next-line @typescript-eslint/unbound-method -- as with `pushState` above.
421
393
  const oldReplaceState = window.history.replaceState;
422
394
  window.history.replaceState = function (state, unused, url) {
423
395
  const res = oldReplaceState.call(this, state, unused, url);
@@ -430,23 +402,17 @@ function listenNavigation(onNavigation) {
430
402
  return () => disposeAll(undo);
431
403
  }
432
404
  /**
433
- * Dev-only refresh client (stripped from prod bundles: the whole call is
434
- * guarded by import.meta.webpackHot). Listens to the CLI's SSE endpoint:
405
+ * Dev-only refresh client, listening to the CLI's SSE endpoint:
435
406
  *
436
- * client-built → hot-apply the waiting updates (react-refresh keeps
437
- * component state); anything the page can't be patched
438
- * up to falls back to a reload — see walkHotUpdates.
439
- * rsc-update → server component code changed: re-fetch the flight
440
- * payload for the current URL, state preserved.
441
- * hello → sent on (re)connect with the latest build hash; a
442
- * mismatch means events were missed — resync.
407
+ * client-built → hot-apply the waiting updates; anything the page can't be patched up to reloads.
408
+ * rsc-update → server component code changed: re-fetch the flight payload, state preserved.
409
+ * hello → sent on (re)connect with the latest build hash; a mismatch means a missed event.
443
410
  */
444
411
  function initDevRefresh(fetchRscPayload) {
445
412
  const hot = import.meta.webpackHot;
446
413
  let connectedOnce = false;
447
414
  /** The newest build the dev server has announced — what {@link applyClientUpdate} walks towards. */
448
415
  let targetHash;
449
- /** Gives up on patching the page and takes the whole document from the dev server instead. */
450
416
  function reload(reason, error) {
451
417
  console.warn(`[rshono] ${reason} — reloading`, ...(error === undefined ? [] : [error]));
452
418
  window.location.reload();
@@ -477,19 +443,17 @@ function initDevRefresh(fetchRscPayload) {
477
443
  }
478
444
  }
479
445
  const source = new EventSource('/_rshono/hmr');
480
- // Chained rather than handled as they arrive: a burst of saves puts several frames on the wire
481
- // inside the time one `hot.check` takes, and two of those overlapping is an error webpack throws
482
- // on ("check() is only allowed in idle status") — which would turn every burst into a full reload.
483
- // Nothing is dropped by queueing, because `targetHash` is shared: whichever handler runs next
484
- // walks to the newest build rather than to the one its own frame named.
446
+ // Chained rather than handled as they arrive: `hot.check` may only run from `idle`, and a burst of saves
447
+ // puts several frames on the wire inside the time one takes. Queueing drops nothing, because `targetHash`
448
+ // is shared — whichever handler runs next walks to the newest build.
485
449
  let queue = Promise.resolve();
486
450
  source.onmessage = (event) => {
487
451
  const message = JSON.parse(event.data);
488
452
  queue = queue.then(() => handle(message)).catch((error) => reload('the dev client failed', error));
489
453
  };
490
454
  }
491
- // Bootstrap failures (a truncated or malformed initial flight payload, most likely) would otherwise
492
- // be an unhandled rejection: nothing hydrates, nothing is reported, and the page just sits there.
455
+ // A bootstrap failure — a truncated initial payload, most likely — would otherwise be an unhandled
456
+ // rejection: nothing hydrates, nothing is reported, and the page just sits there.
493
457
  main().catch((error) => {
494
458
  console.error('[rshono] the client runtime failed to start:', error);
495
459
  showFatal(error);