@rshono/core 1.0.0-rc.0
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.
- package/README.md +273 -0
- package/bin/rshono.mjs +15 -0
- package/dist/builder/env-shadow-loader.cjs +26 -0
- package/dist/builder/page-entry-loader.cjs +7 -0
- package/dist/builder/page-files.d.ts +2 -0
- package/dist/builder/page-files.d.ts.map +1 -0
- package/dist/builder/page-files.js +45 -0
- package/dist/builder/page-files.js.map +1 -0
- package/dist/builder/public-env.d.ts +2 -0
- package/dist/builder/public-env.d.ts.map +1 -0
- package/dist/builder/public-env.js +5 -0
- package/dist/builder/public-env.js.map +1 -0
- package/dist/builder/rspack-config.d.ts +14 -0
- package/dist/builder/rspack-config.d.ts.map +1 -0
- package/dist/builder/rspack-config.js +235 -0
- package/dist/builder/rspack-config.js.map +1 -0
- package/dist/cli/build.d.ts +11 -0
- package/dist/cli/build.d.ts.map +1 -0
- package/dist/cli/build.js +64 -0
- package/dist/cli/build.js.map +1 -0
- package/dist/cli/dev.d.ts +9 -0
- package/dist/cli/dev.d.ts.map +1 -0
- package/dist/cli/dev.js +210 -0
- package/dist/cli/dev.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +77 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/start.d.ts +8 -0
- package/dist/cli/start.d.ts.map +1 -0
- package/dist/cli/start.js +39 -0
- package/dist/cli/start.js.map +1 -0
- package/dist/config.d.ts +121 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +5 -0
- package/dist/config.js.map +1 -0
- package/dist/deploy/aws-lambda/runtime.d.ts +14 -0
- package/dist/deploy/aws-lambda/runtime.d.ts.map +1 -0
- package/dist/deploy/aws-lambda/runtime.js +24 -0
- package/dist/deploy/aws-lambda/runtime.js.map +1 -0
- package/dist/deploy/build-marker.d.ts +15 -0
- package/dist/deploy/build-marker.d.ts.map +1 -0
- package/dist/deploy/build-marker.js +26 -0
- package/dist/deploy/build-marker.js.map +1 -0
- package/dist/deploy/bun/runtime.d.ts +11 -0
- package/dist/deploy/bun/runtime.d.ts.map +1 -0
- package/dist/deploy/bun/runtime.js +22 -0
- package/dist/deploy/bun/runtime.js.map +1 -0
- package/dist/deploy/cloudflare/build.d.ts +7 -0
- package/dist/deploy/cloudflare/build.d.ts.map +1 -0
- package/dist/deploy/cloudflare/build.js +74 -0
- package/dist/deploy/cloudflare/build.js.map +1 -0
- package/dist/deploy/cloudflare/runtime.d.ts +10 -0
- package/dist/deploy/cloudflare/runtime.d.ts.map +1 -0
- package/dist/deploy/cloudflare/runtime.js +129 -0
- package/dist/deploy/cloudflare/runtime.js.map +1 -0
- package/dist/deploy/contract.d.ts +65 -0
- package/dist/deploy/contract.d.ts.map +1 -0
- package/dist/deploy/contract.js +2 -0
- package/dist/deploy/contract.js.map +1 -0
- package/dist/deploy/deno/runtime.d.ts +11 -0
- package/dist/deploy/deno/runtime.d.ts.map +1 -0
- package/dist/deploy/deno/runtime.js +16 -0
- package/dist/deploy/deno/runtime.js.map +1 -0
- package/dist/deploy/filesystem.d.ts +10 -0
- package/dist/deploy/filesystem.d.ts.map +1 -0
- package/dist/deploy/filesystem.js +47 -0
- package/dist/deploy/filesystem.js.map +1 -0
- package/dist/deploy/listen.d.ts +20 -0
- package/dist/deploy/listen.d.ts.map +1 -0
- package/dist/deploy/listen.js +24 -0
- package/dist/deploy/listen.js.map +1 -0
- package/dist/deploy/netlify/build.d.ts +8 -0
- package/dist/deploy/netlify/build.d.ts.map +1 -0
- package/dist/deploy/netlify/build.js +52 -0
- package/dist/deploy/netlify/build.js.map +1 -0
- package/dist/deploy/netlify/runtime.d.ts +13 -0
- package/dist/deploy/netlify/runtime.d.ts.map +1 -0
- package/dist/deploy/netlify/runtime.js +24 -0
- package/dist/deploy/netlify/runtime.js.map +1 -0
- package/dist/deploy/node/runtime.d.ts +7 -0
- package/dist/deploy/node/runtime.d.ts.map +1 -0
- package/dist/deploy/node/runtime.js +36 -0
- package/dist/deploy/node/runtime.js.map +1 -0
- package/dist/deploy/presets.d.ts +88 -0
- package/dist/deploy/presets.d.ts.map +1 -0
- package/dist/deploy/presets.js +110 -0
- package/dist/deploy/presets.js.map +1 -0
- package/dist/deploy/vercel/build.d.ts +3 -0
- package/dist/deploy/vercel/build.d.ts.map +1 -0
- package/dist/deploy/vercel/build.js +56 -0
- package/dist/deploy/vercel/build.js.map +1 -0
- package/dist/deploy/vercel/runtime.d.ts +14 -0
- package/dist/deploy/vercel/runtime.d.ts.map +1 -0
- package/dist/deploy/vercel/runtime.js +26 -0
- package/dist/deploy/vercel/runtime.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/router.d.ts +337 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.js +18 -0
- package/dist/router.js.map +1 -0
- package/dist/runtime/boundaries.d.ts +102 -0
- package/dist/runtime/boundaries.d.ts.map +1 -0
- package/dist/runtime/boundaries.js +101 -0
- package/dist/runtime/boundaries.js.map +1 -0
- package/dist/runtime/client.d.ts +16 -0
- package/dist/runtime/client.d.ts.map +1 -0
- package/dist/runtime/client.js +16 -0
- package/dist/runtime/client.js.map +1 -0
- package/dist/runtime/context.d.ts +271 -0
- package/dist/runtime/context.d.ts.map +1 -0
- package/dist/runtime/context.js +360 -0
- package/dist/runtime/context.js.map +1 -0
- package/dist/runtime/control.d.ts +18 -0
- package/dist/runtime/control.d.ts.map +1 -0
- package/dist/runtime/control.js +39 -0
- package/dist/runtime/control.js.map +1 -0
- package/dist/runtime/dev-protocol.d.ts +22 -0
- package/dist/runtime/dev-protocol.d.ts.map +1 -0
- package/dist/runtime/dev-protocol.js +2 -0
- package/dist/runtime/dev-protocol.js.map +1 -0
- package/dist/runtime/empty-server-app.d.ts +3 -0
- package/dist/runtime/empty-server-app.d.ts.map +1 -0
- package/dist/runtime/empty-server-app.js +2 -0
- package/dist/runtime/empty-server-app.js.map +1 -0
- package/dist/runtime/entry.client.d.ts +2 -0
- package/dist/runtime/entry.client.d.ts.map +1 -0
- package/dist/runtime/entry.client.js +453 -0
- package/dist/runtime/entry.client.js.map +1 -0
- package/dist/runtime/entry.rsc.d.ts +32 -0
- package/dist/runtime/entry.rsc.d.ts.map +1 -0
- package/dist/runtime/entry.rsc.js +466 -0
- package/dist/runtime/entry.rsc.js.map +1 -0
- package/dist/runtime/entry.ssr.d.ts +25 -0
- package/dist/runtime/entry.ssr.d.ts.map +1 -0
- package/dist/runtime/entry.ssr.js +86 -0
- package/dist/runtime/entry.ssr.js.map +1 -0
- package/dist/runtime/navigation.d.ts +114 -0
- package/dist/runtime/navigation.d.ts.map +1 -0
- package/dist/runtime/navigation.js +112 -0
- package/dist/runtime/navigation.js.map +1 -0
- package/dist/runtime/request.d.ts +35 -0
- package/dist/runtime/request.d.ts.map +1 -0
- package/dist/runtime/request.js +39 -0
- package/dist/runtime/request.js.map +1 -0
- package/dist/runtime/server.d.ts +24 -0
- package/dist/runtime/server.d.ts.map +1 -0
- package/dist/runtime/server.js +24 -0
- package/dist/runtime/server.js.map +1 -0
- package/dist/server/compress.d.ts +15 -0
- package/dist/server/compress.d.ts.map +1 -0
- package/dist/server/compress.js +76 -0
- package/dist/server/compress.js.map +1 -0
- package/dist/server/headers.d.ts +24 -0
- package/dist/server/headers.d.ts.map +1 -0
- package/dist/server/headers.js +44 -0
- package/dist/server/headers.js.map +1 -0
- package/dist/server/load-config.d.ts +7 -0
- package/dist/server/load-config.d.ts.map +1 -0
- package/dist/server/load-config.js +41 -0
- package/dist/server/load-config.js.map +1 -0
- package/dist/server/load-env.d.ts +2 -0
- package/dist/server/load-env.d.ts.map +1 -0
- package/dist/server/load-env.js +10 -0
- package/dist/server/load-env.js.map +1 -0
- package/dist/server/prerendered.d.ts +67 -0
- package/dist/server/prerendered.d.ts.map +1 -0
- package/dist/server/prerendered.js +39 -0
- package/dist/server/prerendered.js.map +1 -0
- package/dist/server/server-config.d.ts +72 -0
- package/dist/server/server-config.d.ts.map +1 -0
- package/dist/server/server-config.js +94 -0
- package/dist/server/server-config.js.map +1 -0
- package/dist/server/shutdown.d.ts +7 -0
- package/dist/server/shutdown.d.ts.map +1 -0
- package/dist/server/shutdown.js +11 -0
- package/dist/server/shutdown.js.map +1 -0
- package/dist/server/ssg.d.ts +25 -0
- package/dist/server/ssg.d.ts.map +1 -0
- package/dist/server/ssg.js +160 -0
- package/dist/server/ssg.js.map +1 -0
- package/dist/server/static.d.ts +14 -0
- package/dist/server/static.d.ts.map +1 -0
- package/dist/server/static.js +30 -0
- package/dist/server/static.js.map +1 -0
- package/package.json +86 -0
|
@@ -0,0 +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 getContext} and the {@link Ctx} 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, HonoRequest } 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 */\nexport type RedirectStatus = 301 | 302 | 303 | 307 | 308;\n\nconst contextStorage = new AsyncLocalStorage<Context>();\n\n/** One {@link Ctx} per Hono {@link Context}, so repeated `getContext()` calls in a request share its lazy getters. */\nconst wrappers = new WeakMap<Context, Ctx>();\n\n/**\n * `process.env`, snapshotted on first read.\n *\n * It is not a plain object — every enumeration crosses into the host environment, which made\n * spreading it (~20µs) by far the most expensive thing {@link Ctx.env} did, once per request that\n * touched it. Snapshotted lazily rather than at module load because `loadEnvFiles()` runs *after*\n * this module is imported, so an eager copy would miss everything from `.env`. The trade-off: a\n * `process.env` mutation after the first `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,\n * rather than a server handling real requests. `build.ts` sets `RSHONO_PRERENDER`\n * before importing the app bundle and starting the prerender pass; the app bundle\n * inlines its own copy of this module, so a shared `process.env` (not a module-level\n * flag) is what reliably crosses that boundary. Read by {@link getContext} to turn a\n * static route's request-context read into a clear build-time error instead of\n * silently baking synthetic build-time values (a `localhost` URL, no cookies, build\n * env) into the snapshot.\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 getContext} 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 getContext} instead.\n *\n * @internal\n */\nexport function runWithContext<T>(c: Context, fn: () => T): T {\n return contextStorage.run(c, fn);\n}\n\n/**\n * Reads the matched route params, returning an empty object when there is no\n * active route match (rather than throwing). Shared by {@link Ctx.params} and the\n * request renderer so the fallback behaviour stays in one place.\n *\n * Framework internal — read params from {@link Ctx.params} or a page's\n * `PageProps` instead.\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.\n *\n * `c.req.url` reflects the internal address the server was reached on, which is wrong behind a\n * proxy or load balancer. `X-Forwarded-Host` / `X-Forwarded-Proto` fix that up — **but only when\n * `trustProxy` is enabled in `rshono.config.ts`** (always the case under `rshono dev`). Those\n * headers are client-supplied: honouring them unconditionally lets anyone who can reach the server\n * dictate the origin of every absolute URL the app builds — canonical tags, emails, redirects — and\n * poison a shared cache with them. So the default is to ignore them entirely.\n *\n * Framework internal — prefer {@link Ctx.url}, which caches the result per request.\n *\n * @internal\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 Ctx.env}.\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 getContext}, 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 Ctx.url}, {@link Ctx.env}) are computed at most once.\n *\n * @typeParam E - The Hono {@link Env} describing this app's `Bindings` and\n * `Variables`, so {@link Ctx.var} and {@link Ctx.env} stay typed.\n *\n * @example\n * ```tsx\n * import { getContext } from '@rshono/core/server';\n *\n * export default async function Whoami() {\n * const ctx = getContext();\n * const session = ctx.cookies.get('session');\n * return <p>{ctx.url.pathname} — {session ?? 'anonymous'}</p>;\n * }\n * ```\n */\nexport class Ctx<E extends Env = Env> {\n #raw: Context<E>;\n #url?: URL;\n #env?: EnvVars<E>;\n\n constructor(c: Context<E>) {\n this.#raw = c;\n }\n\n /**\n * The underlying Hono {@link Context}. Escape hatch for anything this wrapper does not expose.\n *\n * A getter over a private field rather than a plain property, so it is not an *own enumerable*\n * one — which matters more than it looks. React's diagnostic for a value that cannot be sent to a\n * client component (`describeObjectForErrorMessage`) walks `Object.keys` recursively with no depth\n * limit and no cycle guard, and the Hono context graph reaches the socket and the whole server\n * through `req.raw` and `env`. While this was a plain property, passing a `Ctx` to a `'use client'`\n * component blew the stack *inside that message builder* — so React's actual, accurate \"you cannot\n * pass this\" error never got printed. Hidden from `Object.keys`, the walk stops here.\n */\n get raw(): Context<E> {\n return this.#raw;\n }\n\n /** The parsed Hono request (`c.req`) — headers, body parsing, param access, etc. */\n get req(): HonoRequest {\n return this.#raw.req;\n }\n\n /**\n * The browser-facing request URL, proxy-header aware (see {@link publicUrl}) —\n * read `url.pathname`, `url.searchParams` and the rest off it. Parsed once and\n * cached, so the same instance comes back on every read within a request; treat\n * it as read-only for that reason.\n */\n get url(): URL {\n return (this.#url ??= publicUrl(this.#raw as Context));\n }\n\n /** The HTTP method of the request, e.g. `GET` or `POST`. */\n get method(): string {\n return this.#raw.req.method;\n }\n\n /**\n * Matched route params, e.g. `{ id }` for a `/users/[id]` route. Returns an\n * empty object when there is no active route match (rather than throwing).\n */\n get params(): Record<string, string> {\n return readParams(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 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 = getContext().env.STRIPE_SECRET_KEY;`\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 /** Sets a response header. Thin pass-through to `c.header(name, value)`. */\n header(name: string, value: string): void {\n this.#raw.header(name, value);\n }\n\n /**\n * Read and write request/response cookies.\n *\n * @example\n * ```ts\n * const ctx = getContext();\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 cookies = {\n /** Reads a single cookie by name, or `undefined` if absent. */\n get: (name: string): string | undefined => getCookie(this.#raw, name),\n /** Reads every cookie as a `{ name: value }` record. */\n all: (): Record<string, string> => getCookie(this.#raw),\n /** Sets a cookie on the response. See Hono's {@link CookieOptions} for `path`, `httpOnly`, `maxAge`, etc. */\n set: (name: string, value: string, options?: CookieOptions): void => setCookie(this.#raw, name, value, options),\n /** Clears a cookie. Pass the same `path`/`domain` it was set with so the browser matches it. */\n delete: (name: string, options?: CookieOptions): void => {\n deleteCookie(this.#raw, name, options);\n },\n };\n}\n\n/**\n * Returns the {@link Ctx} for the current request.\n *\n * This is the primary entry point for reading request data from a server\n * component or server action — the URL, cookies, params, env, and middleware\n * variables. The returned wrapper is memoised per request, so repeated calls in\n * the same request are cheap and return the same instance.\n *\n * A **page** component is handed the very same object as its `ctx` prop, so this\n * import is for everywhere else: a nested server component, or a `'use server'`\n * action module — neither of which receives props from the framework.\n *\n * @typeParam E - The app's Hono {@link Env}, to type {@link Ctx.var} and {@link Ctx.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 { getContext, redirect } from '@rshono/core/server';\n *\n * export async function login(form: FormData) {\n * getContext().cookies.set('session', String(form.get('email')), { httpOnly: true });\n * redirect('/dashboard');\n * }\n * ```\n */\nexport function getContext<E extends Env = Env>(): Ctx<E> {\n if (prerendering) {\n throw new Error(\n \"[rshono] getContext() 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 getContext() call.',\n );\n }\n const c = contextStorage.getStore();\n if (!c) {\n throw new Error(\n '[rshono] getContext() 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 Ctx(c);\n wrappers.set(c, ctx);\n }\n return ctx as unknown as Ctx<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 = getContext().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 * const user = await db.user.find(getContext().params.id);\n * if (!user) notFound();\n * return <Profile user={user} />; // user is non-null here\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 */\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"]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare class RedirectSignal extends Error {
|
|
2
|
+
readonly location: string;
|
|
3
|
+
readonly status: number;
|
|
4
|
+
readonly digest: string;
|
|
5
|
+
constructor(location: string, status: number);
|
|
6
|
+
}
|
|
7
|
+
export declare class NotFoundSignal extends Error {
|
|
8
|
+
readonly digest = "RSHONO_NOT_FOUND";
|
|
9
|
+
constructor();
|
|
10
|
+
}
|
|
11
|
+
export type ControlSignal = RedirectSignal | NotFoundSignal;
|
|
12
|
+
export declare function isControlSignal(error: unknown): error is ControlSignal;
|
|
13
|
+
export declare function isControlDigest(digest: unknown): digest is string;
|
|
14
|
+
export declare function parseRedirectDigest(digest: string): {
|
|
15
|
+
location: string;
|
|
16
|
+
status: number;
|
|
17
|
+
} | null;
|
|
18
|
+
//# sourceMappingURL=control.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"control.d.ts","sourceRoot":"","sources":["../../src/runtime/control.ts"],"names":[],"mappings":"AAGA,qBAAa,cAAe,SAAQ,KAAK;IAGrC,QAAQ,CAAC,QAAQ,EAAE,MAAM;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM;IAHzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,YACW,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EAKxB;CACF;AAED,qBAAa,cAAe,SAAQ,KAAK;IACvC,QAAQ,CAAC,MAAM,sBAAoB;IACnC,cAGC;CACF;AAED,MAAM,MAAM,aAAa,GAAG,cAAc,GAAG,cAAc,CAAC;AAE5D,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,aAAa,CAEtE;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,IAAI,MAAM,CAEjE;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAQ/F"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const REDIRECT_PREFIX = 'RSHONO_REDIRECT;';
|
|
2
|
+
const NOT_FOUND_DIGEST = 'RSHONO_NOT_FOUND';
|
|
3
|
+
export class RedirectSignal extends Error {
|
|
4
|
+
location;
|
|
5
|
+
status;
|
|
6
|
+
digest;
|
|
7
|
+
constructor(location, status) {
|
|
8
|
+
super(`[rshono] redirect to ${location}`);
|
|
9
|
+
this.location = location;
|
|
10
|
+
this.status = status;
|
|
11
|
+
this.name = 'RedirectSignal';
|
|
12
|
+
this.digest = `${REDIRECT_PREFIX}${status};${encodeURIComponent(location)}`;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export class NotFoundSignal extends Error {
|
|
16
|
+
digest = NOT_FOUND_DIGEST;
|
|
17
|
+
constructor() {
|
|
18
|
+
super('[rshono] notFound');
|
|
19
|
+
this.name = 'NotFoundSignal';
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export function isControlSignal(error) {
|
|
23
|
+
return error instanceof RedirectSignal || error instanceof NotFoundSignal;
|
|
24
|
+
}
|
|
25
|
+
export function isControlDigest(digest) {
|
|
26
|
+
return typeof digest === 'string' && (digest === NOT_FOUND_DIGEST || digest.startsWith(REDIRECT_PREFIX));
|
|
27
|
+
}
|
|
28
|
+
export function parseRedirectDigest(digest) {
|
|
29
|
+
if (!digest.startsWith(REDIRECT_PREFIX))
|
|
30
|
+
return null;
|
|
31
|
+
const rest = digest.slice(REDIRECT_PREFIX.length);
|
|
32
|
+
const sep = rest.indexOf(';');
|
|
33
|
+
if (sep === -1)
|
|
34
|
+
return null;
|
|
35
|
+
const status = Number(rest.slice(0, sep));
|
|
36
|
+
const location = decodeURIComponent(rest.slice(sep + 1));
|
|
37
|
+
return { location, status: Number.isFinite(status) ? status : 307 };
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=control.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"control.js","sourceRoot":"","sources":["../../src/runtime/control.ts"],"names":[],"mappings":"AAAA,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":["const 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"]}
|
|
@@ -0,0 +1,22 @@
|
|
|
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.
|
|
6
|
+
*
|
|
7
|
+
* - `hello` — sent on (re)connect with the latest build hash; a mismatch means
|
|
8
|
+
* events were missed while disconnected, so the client resyncs.
|
|
9
|
+
* - `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.
|
|
12
|
+
*/
|
|
13
|
+
export type DevMessage = {
|
|
14
|
+
type: 'hello';
|
|
15
|
+
hash?: string;
|
|
16
|
+
} | {
|
|
17
|
+
type: 'client-built';
|
|
18
|
+
hash: string;
|
|
19
|
+
} | {
|
|
20
|
+
type: 'rsc-update';
|
|
21
|
+
};
|
|
22
|
+
//# sourceMappingURL=dev-protocol.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dev-protocol.d.ts","sourceRoot":"","sources":["../../src/runtime/dev-protocol.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,UAAU,GAClB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAChC;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACtC;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,CAAC"}
|
|
@@ -0,0 +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 =\n | { type: 'hello'; hash?: string }\n | { type: 'client-built'; hash: string }\n | { type: 'rsc-update' };\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"empty-server-app.d.ts","sourceRoot":"","sources":["../../src/runtime/empty-server-app.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"empty-server-app.js","sourceRoot":"","sources":["../../src/runtime/empty-server-app.ts"],"names":[],"mappings":"AAAA,eAAe,IAAI,CAAC","sourcesContent":["export default null;\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry.client.d.ts","sourceRoot":"","sources":["../../src/runtime/entry.client.tsx"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { hydrateRoot } from 'react-dom/client';
|
|
4
|
+
import { createFromFetch, createFromReadableStream, createTemporaryReferenceSet, encodeReply, setServerCallback, } from 'react-server-dom-rspack/client.browser';
|
|
5
|
+
import { rscStream } from 'rsc-html-stream/client';
|
|
6
|
+
import { isControlDigest, parseRedirectDigest } from './control.js';
|
|
7
|
+
import { RouterContext } from './navigation.js';
|
|
8
|
+
import { createRscRequest } from './request.js';
|
|
9
|
+
const isDev = process.env.NODE_ENV === 'development';
|
|
10
|
+
/**
|
|
11
|
+
* Guarantees somewhere to attach the fatal overlay. React's root container is the whole `document`,
|
|
12
|
+
* so by the time an uncaught error has torn the tree down, `<body>` — or even `<html>` — may be gone.
|
|
13
|
+
*/
|
|
14
|
+
function overlayHost() {
|
|
15
|
+
if (!document.documentElement)
|
|
16
|
+
document.appendChild(document.createElement('html'));
|
|
17
|
+
if (!document.body)
|
|
18
|
+
document.documentElement.appendChild(document.createElement('body'));
|
|
19
|
+
return document.body;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Replaces the white screen of death with something readable.
|
|
23
|
+
*
|
|
24
|
+
* Because the root container is `document`, an uncaught render error leaves a genuinely blank page
|
|
25
|
+
* with the reason only in the console — so this paints the reason over it instead. In development
|
|
26
|
+
* that's the full stack; in production it's a generic notice plus a reload button, since the tree is
|
|
27
|
+
* unrecoverable and reloading is the only way forward.
|
|
28
|
+
*
|
|
29
|
+
* Written with DOM calls rather than React (the renderer is what just failed) and `textContent`
|
|
30
|
+
* rather than `innerHTML` (an error message is untrusted input).
|
|
31
|
+
*/
|
|
32
|
+
function showFatal(error, componentStack) {
|
|
33
|
+
// Queued rather than run inline: React's teardown happens after this callback returns, and would
|
|
34
|
+
// remove a node appended synchronously along with the rest of the tree.
|
|
35
|
+
setTimeout(() => {
|
|
36
|
+
const host = overlayHost();
|
|
37
|
+
host.querySelector('[data-rshono-fatal]')?.remove();
|
|
38
|
+
const box = document.createElement('div');
|
|
39
|
+
box.setAttribute('data-rshono-fatal', '');
|
|
40
|
+
box.setAttribute('role', 'alert'); // the page is gone; announce it rather than leaving silence
|
|
41
|
+
box.style.cssText =
|
|
42
|
+
'position:fixed;inset:0;z-index:2147483647;overflow:auto;padding:1.5rem;background:#18181b;color:#f4f4f5;' +
|
|
43
|
+
'font:14px/1.6 ui-monospace,SFMono-Regular,Menlo,monospace;text-align:left';
|
|
44
|
+
const title = document.createElement('div');
|
|
45
|
+
title.textContent = isDev ? 'Unhandled error' : 'Something went wrong';
|
|
46
|
+
title.style.cssText = 'font-size:1.0625rem;font-weight:700;color:#f87171;margin:0 0 0.75rem';
|
|
47
|
+
box.appendChild(title);
|
|
48
|
+
if (isDev) {
|
|
49
|
+
const detail = document.createElement('pre');
|
|
50
|
+
detail.style.cssText = 'margin:0;white-space:pre-wrap;word-break:break-word';
|
|
51
|
+
detail.textContent =
|
|
52
|
+
(error instanceof Error ? (error.stack ?? `${error.name}: ${error.message}`) : String(error)) +
|
|
53
|
+
(componentStack ? `\n\nComponent stack:${componentStack}` : '');
|
|
54
|
+
box.appendChild(detail);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
const message = document.createElement('p');
|
|
58
|
+
message.textContent = 'This page hit an unexpected error and can’t continue.';
|
|
59
|
+
message.style.cssText = 'margin:0 0 1rem;color:#d4d4d8';
|
|
60
|
+
box.appendChild(message);
|
|
61
|
+
}
|
|
62
|
+
const reload = document.createElement('button');
|
|
63
|
+
reload.textContent = 'Reload page';
|
|
64
|
+
reload.style.cssText =
|
|
65
|
+
'margin-top:1.25rem;padding:0.5rem 1rem;font:inherit;color:#18181b;background:#f4f4f5;border:0;border-radius:4px;cursor:pointer';
|
|
66
|
+
reload.addEventListener('click', () => window.location.reload());
|
|
67
|
+
box.appendChild(reload);
|
|
68
|
+
host.appendChild(box);
|
|
69
|
+
}, 0);
|
|
70
|
+
}
|
|
71
|
+
// In-memory flight-payload cache keyed by same-origin path+search. `data-prefetch`
|
|
72
|
+
// links warm it on hover/focus; a navigation to a warmed URL resolves instantly and
|
|
73
|
+
// clears the entry (a prefetch is used at most once, so re-visits always re-fetch).
|
|
74
|
+
const payloadCache = new Map();
|
|
75
|
+
// Bounded so a long session over a link-dense app can't grow it without limit. Insertion-ordered,
|
|
76
|
+
// so the first key is the least recently warmed.
|
|
77
|
+
const MAX_WARMED_PAYLOADS = 8;
|
|
78
|
+
function cacheKey(href) {
|
|
79
|
+
const url = new URL(href, location.href);
|
|
80
|
+
if (url.origin !== location.origin)
|
|
81
|
+
return null;
|
|
82
|
+
return url.pathname + url.search;
|
|
83
|
+
}
|
|
84
|
+
function requestPayload(href) {
|
|
85
|
+
return createFromFetch(fetch(createRscRequest(new URL(href, location.href).href)));
|
|
86
|
+
}
|
|
87
|
+
function warmPayload(href) {
|
|
88
|
+
const key = cacheKey(href);
|
|
89
|
+
if (!key || key === cacheKey(location.href) || payloadCache.has(key))
|
|
90
|
+
return;
|
|
91
|
+
const promise = requestPayload(href);
|
|
92
|
+
payloadCache.set(key, promise);
|
|
93
|
+
// One entry in means at most one out, and the first key is the oldest.
|
|
94
|
+
if (payloadCache.size > MAX_WARMED_PAYLOADS)
|
|
95
|
+
payloadCache.delete(payloadCache.keys().next().value);
|
|
96
|
+
// Don't cache failures, and swallow the rejection until (or unless) a nav awaits it.
|
|
97
|
+
promise.catch(() => {
|
|
98
|
+
if (payloadCache.get(key) === promise)
|
|
99
|
+
payloadCache.delete(key);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
function takePayload(href) {
|
|
103
|
+
const key = cacheKey(href);
|
|
104
|
+
if (key) {
|
|
105
|
+
const cached = payloadCache.get(key);
|
|
106
|
+
if (cached) {
|
|
107
|
+
payloadCache.delete(key);
|
|
108
|
+
return cached;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return requestPayload(href);
|
|
112
|
+
}
|
|
113
|
+
async function main() {
|
|
114
|
+
const cspMeta = document.querySelector('meta[property="csp-nonce"]');
|
|
115
|
+
if (cspMeta?.nonce)
|
|
116
|
+
__webpack_nonce__ = cspMeta.nonce;
|
|
117
|
+
// Both are replaced by BrowserRoot's own on mount. The defaults matter: `setServerCallback` is
|
|
118
|
+
// registered before hydration, so an action or refresh firing in that window would otherwise call
|
|
119
|
+
// an unassigned binding. Until there's a root to update, a full reload is the honest fallback.
|
|
120
|
+
let setPayload = () => {
|
|
121
|
+
window.location.reload();
|
|
122
|
+
};
|
|
123
|
+
// Runs work inside the nav transition so useNavigation().pending stays true across the round-trip.
|
|
124
|
+
let startNav = (run) => {
|
|
125
|
+
void run();
|
|
126
|
+
};
|
|
127
|
+
const initialPayload = await createFromReadableStream(rscStream);
|
|
128
|
+
function push(href) {
|
|
129
|
+
const target = new URL(href, window.location.href);
|
|
130
|
+
if (target.origin !== window.location.origin) {
|
|
131
|
+
window.location.assign(target.href);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
window.history.pushState(null, '', target.href);
|
|
135
|
+
}
|
|
136
|
+
function replace(href) {
|
|
137
|
+
const target = new URL(href, window.location.href);
|
|
138
|
+
if (target.origin !== window.location.origin) {
|
|
139
|
+
window.location.replace(target.href);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
window.history.replaceState(null, '', target.href);
|
|
143
|
+
}
|
|
144
|
+
const back = () => window.history.back();
|
|
145
|
+
const forward = () => window.history.forward();
|
|
146
|
+
// A refresh keeps the URL, so it can't ride the history patch like push/replace — it drives the flight re-fetch directly (bypassing any warmed cache to get fresh data).
|
|
147
|
+
const refresh = () => startNav(async () => {
|
|
148
|
+
try {
|
|
149
|
+
await fetchRscPayload(true);
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
window.location.reload();
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
/**
|
|
156
|
+
* Turns a control-signal digest — how `redirect()` / `notFound()` reach the browser — into a real
|
|
157
|
+
* navigation. Returns false for anything else, so callers can fall through to their own handling.
|
|
158
|
+
*
|
|
159
|
+
* `hard` forces a full document load, for signals that surfaced *through React* (a nested
|
|
160
|
+
* component's redirect, reported via the root error handlers). React unmounts the root on an
|
|
161
|
+
* uncaught error, so there is no live tree left to soft-navigate with. A signal caught earlier —
|
|
162
|
+
* a top-level payload rejection — still swaps the payload in place.
|
|
163
|
+
*/
|
|
164
|
+
function handleControlDigest(error, { hard = false } = {}) {
|
|
165
|
+
const digest = error?.digest;
|
|
166
|
+
if (!isControlDigest(digest))
|
|
167
|
+
return false;
|
|
168
|
+
const redirect = parseRedirectDigest(digest);
|
|
169
|
+
if (!redirect) {
|
|
170
|
+
window.location.reload();
|
|
171
|
+
}
|
|
172
|
+
else if (hard) {
|
|
173
|
+
window.location.assign(new URL(redirect.location, window.location.href).href);
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
push(redirect.location);
|
|
177
|
+
}
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
180
|
+
async function fetchRscPayload(force = false) {
|
|
181
|
+
let payload;
|
|
182
|
+
try {
|
|
183
|
+
payload = await (force ? requestPayload(window.location.href) : takePayload(window.location.href));
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
if (handleControlDigest(error))
|
|
187
|
+
return;
|
|
188
|
+
throw error;
|
|
189
|
+
}
|
|
190
|
+
if (payload.redirect)
|
|
191
|
+
return push(payload.redirect);
|
|
192
|
+
setPayload(payload);
|
|
193
|
+
}
|
|
194
|
+
function BrowserRoot() {
|
|
195
|
+
const [payload, setPayloadState] = React.useState(initialPayload);
|
|
196
|
+
const [pending, startTransition] = React.useTransition();
|
|
197
|
+
React.useEffect(() => {
|
|
198
|
+
setPayload = (v) => setPayloadState(v);
|
|
199
|
+
startNav = (run) => startTransition(run);
|
|
200
|
+
}, [startTransition]);
|
|
201
|
+
React.useEffect(() => listenNavigation((restoreScroll) => startNav(async () => {
|
|
202
|
+
try {
|
|
203
|
+
await fetchRscPayload();
|
|
204
|
+
restoreScroll();
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
window.location.reload();
|
|
208
|
+
}
|
|
209
|
+
}), warmPayload), []);
|
|
210
|
+
const router = React.useMemo(() => ({ push, replace, back, forward, refresh, pending }), [pending]);
|
|
211
|
+
return _jsx(RouterContext.Provider, { value: router, children: payload.root });
|
|
212
|
+
}
|
|
213
|
+
setServerCallback(async (id, args) => {
|
|
214
|
+
const temporaryReferences = createTemporaryReferenceSet();
|
|
215
|
+
const request = createRscRequest(window.location.href, {
|
|
216
|
+
id,
|
|
217
|
+
body: await encodeReply(args, { temporaryReferences }),
|
|
218
|
+
});
|
|
219
|
+
// The action is about to mutate who-knows-what, so anything warmed up to now is pre-mutation
|
|
220
|
+
// data. Cleared before the round-trip so it happens even if the action throws.
|
|
221
|
+
payloadCache.clear();
|
|
222
|
+
let payload;
|
|
223
|
+
try {
|
|
224
|
+
payload = await createFromFetch(fetch(request), { temporaryReferences });
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
if (handleControlDigest(error))
|
|
228
|
+
return undefined;
|
|
229
|
+
throw error;
|
|
230
|
+
}
|
|
231
|
+
if (payload.redirect) {
|
|
232
|
+
push(payload.redirect);
|
|
233
|
+
return undefined;
|
|
234
|
+
}
|
|
235
|
+
React.startTransition(() => setPayload(payload));
|
|
236
|
+
if (payload.notFound)
|
|
237
|
+
return undefined;
|
|
238
|
+
const result = payload.returnValue;
|
|
239
|
+
if (!result.ok)
|
|
240
|
+
throw result.error;
|
|
241
|
+
return result.value;
|
|
242
|
+
});
|
|
243
|
+
// A `redirect()` / `notFound()` from a component *below* the page root can only reach us through
|
|
244
|
+
// React: it rides the flight payload as an error at that component's position, and boundaries
|
|
245
|
+
// re-throw it (see boundaries.tsx) so it lands here rather than rendering an error fallback.
|
|
246
|
+
//
|
|
247
|
+
// Anything that isn't a control signal falls back to what React would have done on its own —
|
|
248
|
+
// console for a caught error, `reportError` (i.e. window.onerror, so error-reporting tools still
|
|
249
|
+
// see it) for an uncaught one. Overriding these hooks means opting out of that default, so it has
|
|
250
|
+
// to be put back by hand.
|
|
251
|
+
hydrateRoot(document, _jsx(BrowserRoot, {}), {
|
|
252
|
+
formState: initialPayload.formState,
|
|
253
|
+
onCaughtError: (error, errorInfo) => {
|
|
254
|
+
if (handleControlDigest(error, { hard: true }))
|
|
255
|
+
return;
|
|
256
|
+
// A boundary handled this and the tree is intact, so no overlay: whatever fallback the app
|
|
257
|
+
// chose is the right thing to have on screen.
|
|
258
|
+
console.error(error, errorInfo.componentStack ?? '');
|
|
259
|
+
},
|
|
260
|
+
onUncaughtError: (error, errorInfo) => {
|
|
261
|
+
if (handleControlDigest(error, { hard: true }))
|
|
262
|
+
return;
|
|
263
|
+
// Nothing caught it, so React tears the root down — and the root is `document`. This is the
|
|
264
|
+
// white screen; paint the reason over it.
|
|
265
|
+
globalThis.reportError(error);
|
|
266
|
+
showFatal(error, errorInfo.componentStack);
|
|
267
|
+
},
|
|
268
|
+
});
|
|
269
|
+
if (import.meta.webpackHot) {
|
|
270
|
+
// Server code may have changed, so drop any warmed payloads and re-fetch fresh.
|
|
271
|
+
initDevRefresh(() => {
|
|
272
|
+
payloadCache.clear();
|
|
273
|
+
return fetchRscPayload(true);
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
/** Hover/focus dwell time before a `data-prefetch` link warms its payload. */
|
|
278
|
+
const PREFETCH_DELAY_MS = 120;
|
|
279
|
+
// An `<a>` we intercept for soft navigation: same-origin, same tab, not a download,
|
|
280
|
+
// and not explicitly opted out with `data-native` (which forces a full browser navigation).
|
|
281
|
+
function isRouterLink(link) {
|
|
282
|
+
return (!!link.href &&
|
|
283
|
+
(!link.target || link.target === '_self') &&
|
|
284
|
+
link.origin === location.origin &&
|
|
285
|
+
!link.hasAttribute('download') &&
|
|
286
|
+
!link.hasAttribute('data-native'));
|
|
287
|
+
}
|
|
288
|
+
function listenNavigation(onNavigation, prefetch) {
|
|
289
|
+
// Scroll restoration. We tag each history entry with a stable numeric key in its
|
|
290
|
+
// `history.state` and remember scrollY per key, so back/forward restores the exact
|
|
291
|
+
// position while push scrolls to the top. `manual` hands restoration to us.
|
|
292
|
+
const scrollByKey = new Map();
|
|
293
|
+
let seq = 0;
|
|
294
|
+
const prevRestoration = window.history.scrollRestoration;
|
|
295
|
+
try {
|
|
296
|
+
window.history.scrollRestoration = 'manual';
|
|
297
|
+
}
|
|
298
|
+
catch { }
|
|
299
|
+
const keyOf = () => {
|
|
300
|
+
const state = window.history.state;
|
|
301
|
+
return state && typeof state.__rshonoScroll === 'number' ? state.__rshonoScroll : null;
|
|
302
|
+
};
|
|
303
|
+
const tag = (state, key) => ({ ...state, __rshonoScroll: key });
|
|
304
|
+
if (keyOf() === null) {
|
|
305
|
+
window.history.replaceState(tag(window.history.state, seq++), '');
|
|
306
|
+
}
|
|
307
|
+
let scrollRaf = 0;
|
|
308
|
+
const onScroll = () => {
|
|
309
|
+
if (scrollRaf)
|
|
310
|
+
return;
|
|
311
|
+
scrollRaf = requestAnimationFrame(() => {
|
|
312
|
+
scrollRaf = 0;
|
|
313
|
+
const key = keyOf();
|
|
314
|
+
if (key !== null)
|
|
315
|
+
scrollByKey.set(key, window.scrollY);
|
|
316
|
+
});
|
|
317
|
+
};
|
|
318
|
+
window.addEventListener('scroll', onScroll, { passive: true });
|
|
319
|
+
const restoreScrollFor = (type) => () => {
|
|
320
|
+
if (type === 'replace')
|
|
321
|
+
return;
|
|
322
|
+
const key = keyOf();
|
|
323
|
+
requestAnimationFrame(() => {
|
|
324
|
+
const y = type === 'pop' && key !== null ? (scrollByKey.get(key) ?? 0) : 0;
|
|
325
|
+
window.scrollTo(0, y);
|
|
326
|
+
});
|
|
327
|
+
};
|
|
328
|
+
const notify = (type) => onNavigation(restoreScrollFor(type));
|
|
329
|
+
const onPopState = () => notify('pop');
|
|
330
|
+
window.addEventListener('popstate', onPopState);
|
|
331
|
+
const oldPushState = window.history.pushState;
|
|
332
|
+
window.history.pushState = function (state, unused, url) {
|
|
333
|
+
const res = oldPushState.call(this, tag(state, seq++), unused, url);
|
|
334
|
+
notify('push');
|
|
335
|
+
return res;
|
|
336
|
+
};
|
|
337
|
+
const oldReplaceState = window.history.replaceState;
|
|
338
|
+
window.history.replaceState = function (state, unused, url) {
|
|
339
|
+
const res = oldReplaceState.call(this, tag(state, keyOf() ?? seq++), unused, url);
|
|
340
|
+
notify('replace');
|
|
341
|
+
return res;
|
|
342
|
+
};
|
|
343
|
+
function onClick(e) {
|
|
344
|
+
const link = e.target.closest('a');
|
|
345
|
+
if (link &&
|
|
346
|
+
link instanceof HTMLAnchorElement &&
|
|
347
|
+
isRouterLink(link) &&
|
|
348
|
+
e.button === 0 &&
|
|
349
|
+
!e.metaKey &&
|
|
350
|
+
!e.ctrlKey &&
|
|
351
|
+
!e.altKey &&
|
|
352
|
+
!e.shiftKey &&
|
|
353
|
+
!e.defaultPrevented) {
|
|
354
|
+
if (link.hash && link.pathname === location.pathname && link.search === location.search)
|
|
355
|
+
return;
|
|
356
|
+
e.preventDefault();
|
|
357
|
+
history.pushState(null, '', link.href);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
document.addEventListener('click', onClick);
|
|
361
|
+
// A prefetch is a full server render, so it waits out a short dwell time on one shared timer:
|
|
362
|
+
// sweeping the cursor across a list of prefetch links costs one request (for the link the pointer
|
|
363
|
+
// settled on), not one per link.
|
|
364
|
+
let prefetchTimer;
|
|
365
|
+
function onPrefetch(e) {
|
|
366
|
+
const target = e.target;
|
|
367
|
+
if (!(target instanceof Element))
|
|
368
|
+
return;
|
|
369
|
+
const link = target.closest('a[data-prefetch]');
|
|
370
|
+
if (!(link instanceof HTMLAnchorElement) || !isRouterLink(link))
|
|
371
|
+
return;
|
|
372
|
+
const { href } = link;
|
|
373
|
+
clearTimeout(prefetchTimer);
|
|
374
|
+
prefetchTimer = setTimeout(() => prefetch(href), PREFETCH_DELAY_MS);
|
|
375
|
+
}
|
|
376
|
+
document.addEventListener('pointerover', onPrefetch);
|
|
377
|
+
document.addEventListener('focusin', onPrefetch);
|
|
378
|
+
return () => {
|
|
379
|
+
clearTimeout(prefetchTimer);
|
|
380
|
+
document.removeEventListener('click', onClick);
|
|
381
|
+
document.removeEventListener('pointerover', onPrefetch);
|
|
382
|
+
document.removeEventListener('focusin', onPrefetch);
|
|
383
|
+
window.removeEventListener('popstate', onPopState);
|
|
384
|
+
window.removeEventListener('scroll', onScroll);
|
|
385
|
+
window.history.pushState = oldPushState;
|
|
386
|
+
window.history.replaceState = oldReplaceState;
|
|
387
|
+
try {
|
|
388
|
+
window.history.scrollRestoration = prevRestoration;
|
|
389
|
+
}
|
|
390
|
+
catch { }
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Dev-only refresh client (stripped from prod bundles: the whole call is
|
|
395
|
+
* guarded by import.meta.webpackHot). Listens to the CLI's SSE endpoint:
|
|
396
|
+
*
|
|
397
|
+
* client-built → hot-apply the waiting updates (react-refresh keeps
|
|
398
|
+
* component state); any failure falls back to reload.
|
|
399
|
+
* rsc-update → server component code changed: re-fetch the flight
|
|
400
|
+
* payload for the current URL, state preserved.
|
|
401
|
+
* hello → sent on (re)connect with the latest build hash; a
|
|
402
|
+
* mismatch means events were missed — resync.
|
|
403
|
+
*/
|
|
404
|
+
function initDevRefresh(fetchRscPayload) {
|
|
405
|
+
let connectedOnce = false;
|
|
406
|
+
async function applyClientUpdate(hash) {
|
|
407
|
+
const hot = import.meta.webpackHot;
|
|
408
|
+
if (hash === __webpack_hash__)
|
|
409
|
+
return;
|
|
410
|
+
if (hot.status() !== 'idle') {
|
|
411
|
+
window.location.reload();
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
try {
|
|
415
|
+
await hot.check(true);
|
|
416
|
+
if (hash !== __webpack_hash__)
|
|
417
|
+
await applyClientUpdate(hash);
|
|
418
|
+
}
|
|
419
|
+
catch (error) {
|
|
420
|
+
console.warn('[rshono] hot update failed, reloading:', error);
|
|
421
|
+
window.location.reload();
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
const source = new EventSource('/_rshono/hmr');
|
|
425
|
+
source.onmessage = async (event) => {
|
|
426
|
+
const message = JSON.parse(event.data);
|
|
427
|
+
switch (message.type) {
|
|
428
|
+
case 'hello':
|
|
429
|
+
if (connectedOnce) {
|
|
430
|
+
if (message.hash && message.hash !== __webpack_hash__)
|
|
431
|
+
await applyClientUpdate(message.hash);
|
|
432
|
+
await fetchRscPayload().catch(() => window.location.reload());
|
|
433
|
+
}
|
|
434
|
+
connectedOnce = true;
|
|
435
|
+
break;
|
|
436
|
+
case 'client-built':
|
|
437
|
+
if (message.hash)
|
|
438
|
+
await applyClientUpdate(message.hash);
|
|
439
|
+
break;
|
|
440
|
+
case 'rsc-update':
|
|
441
|
+
console.log('[rshono] server components updated');
|
|
442
|
+
await fetchRscPayload().catch(() => window.location.reload());
|
|
443
|
+
break;
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
// Bootstrap failures (a truncated or malformed initial flight payload, most likely) would otherwise
|
|
448
|
+
// be an unhandled rejection: nothing hydrates, nothing is reported, and the page just sits there.
|
|
449
|
+
main().catch((error) => {
|
|
450
|
+
console.error('[rshono] the client runtime failed to start:', error);
|
|
451
|
+
showFatal(error);
|
|
452
|
+
});
|
|
453
|
+
//# sourceMappingURL=entry.client.js.map
|