@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,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@rshono/core/client` — the browser-side surface, for use from `'use client'`
|
|
3
|
+
* modules: {@link useNavigation} for the current URL and soft navigation, and
|
|
4
|
+
* {@link Boundary} / {@link ErrorBoundary} / {@link NavigationProgress} as
|
|
5
|
+
* components.
|
|
6
|
+
*
|
|
7
|
+
* Every export is itself a `'use client'` module, so a server component can
|
|
8
|
+
* render {@link Boundary} or {@link NavigationProgress} directly — but the hook
|
|
9
|
+
* needs a client component. In a server component, read the same request data
|
|
10
|
+
* from `getContext()` in `@rshono/core/server`.
|
|
11
|
+
*
|
|
12
|
+
* @packageDocumentation
|
|
13
|
+
*/
|
|
14
|
+
export { NavigationProgress, useNavigation } from './navigation.js';
|
|
15
|
+
export { Boundary, ErrorBoundary } from './boundaries.js';
|
|
16
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/runtime/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAA8D,MAAM,iBAAiB,CAAC;AAChI,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAmE,MAAM,iBAAiB,CAAC","sourcesContent":["/**\n * `@rshono/core/client` — the browser-side surface, for use from `'use client'`\n * modules: {@link useNavigation} for the current URL and soft navigation, and\n * {@link Boundary} / {@link ErrorBoundary} / {@link NavigationProgress} as\n * components.\n *\n * Every export is itself a `'use client'` module, so a server component can\n * render {@link Boundary} or {@link NavigationProgress} directly — but the hook\n * needs a client component. In a server component, read the same request data\n * from `getContext()` in `@rshono/core/server`.\n *\n * @packageDocumentation\n */\n\nexport { NavigationProgress, useNavigation, type Navigation, type NavigationProgressProps, type Router } from './navigation.js';\nexport { Boundary, ErrorBoundary, type BoundaryProps, type ErrorBoundaryProps, type ErrorFallback } from './boundaries.js';\n"]}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The request context: {@link getContext} and the {@link Ctx} wrapper it returns,
|
|
3
|
+
* the {@link redirect} / {@link notFound} control-flow helpers, and the
|
|
4
|
+
* {@link onServerError} reporting funnel — plus the `@internal` plumbing that binds
|
|
5
|
+
* a request to the async context in the first place.
|
|
6
|
+
*
|
|
7
|
+
* The public half of this module is re-exported by `runtime/server.ts`, which is
|
|
8
|
+
* what the `@rshono/core/server` subpath resolves to; import *that* from an app. Nothing
|
|
9
|
+
* here is safe in a `'use client'` module — those run in the browser, with no bound
|
|
10
|
+
* request context.
|
|
11
|
+
*/
|
|
12
|
+
import type { Context, Env, HonoRequest } from 'hono';
|
|
13
|
+
import type { CookieOptions } from 'hono/utils/cookie';
|
|
14
|
+
/**
|
|
15
|
+
* HTTP status codes accepted by {@link redirect}.
|
|
16
|
+
*
|
|
17
|
+
* - `301` Moved Permanently, `308` Permanent Redirect — cacheable, permanent.
|
|
18
|
+
* - `302` Found, `307` Temporary Redirect — temporary.
|
|
19
|
+
* - `303` See Other — the default; forces a `GET` on the target, which is what
|
|
20
|
+
* you almost always want after a form action (post/redirect/get).
|
|
21
|
+
*/
|
|
22
|
+
export type RedirectStatus = 301 | 302 | 303 | 307 | 308;
|
|
23
|
+
/**
|
|
24
|
+
* Runs `fn` with the given Hono {@link Context} bound as the ambient request
|
|
25
|
+
* context, so that {@link getContext} resolves to it anywhere in the call tree.
|
|
26
|
+
*
|
|
27
|
+
* Framework internal — the request handler wraps every render and action in
|
|
28
|
+
* this. Application code should reach for {@link getContext} instead.
|
|
29
|
+
*
|
|
30
|
+
* @internal
|
|
31
|
+
*/
|
|
32
|
+
export declare function runWithContext<T>(c: Context, fn: () => T): T;
|
|
33
|
+
/**
|
|
34
|
+
* Reads the matched route params, returning an empty object when there is no
|
|
35
|
+
* active route match (rather than throwing). Shared by {@link Ctx.params} and the
|
|
36
|
+
* request renderer so the fallback behaviour stays in one place.
|
|
37
|
+
*
|
|
38
|
+
* Framework internal — read params from {@link Ctx.params} or a page's
|
|
39
|
+
* `PageProps` instead.
|
|
40
|
+
*
|
|
41
|
+
* @internal
|
|
42
|
+
*/
|
|
43
|
+
export declare function readParams(c: Context): Record<string, string>;
|
|
44
|
+
/**
|
|
45
|
+
* Resolves the browser-facing {@link URL} for a request.
|
|
46
|
+
*
|
|
47
|
+
* `c.req.url` reflects the internal address the server was reached on, which is wrong behind a
|
|
48
|
+
* proxy or load balancer. `X-Forwarded-Host` / `X-Forwarded-Proto` fix that up — **but only when
|
|
49
|
+
* `trustProxy` is enabled in `rshono.config.ts`** (always the case under `rshono dev`). Those
|
|
50
|
+
* headers are client-supplied: honouring them unconditionally lets anyone who can reach the server
|
|
51
|
+
* dictate the origin of every absolute URL the app builds — canonical tags, emails, redirects — and
|
|
52
|
+
* poison a shared cache with them. So the default is to ignore them entirely.
|
|
53
|
+
*
|
|
54
|
+
* Framework internal — prefer {@link Ctx.url}, which caches the result per request.
|
|
55
|
+
*
|
|
56
|
+
* @internal
|
|
57
|
+
*/
|
|
58
|
+
export declare function publicUrl(c: Context): URL;
|
|
59
|
+
/**
|
|
60
|
+
* The environment available to a request: Cloudflare/Workers `Bindings` merged
|
|
61
|
+
* with process env vars. Values not declared in `Bindings` are typed as
|
|
62
|
+
* `string | undefined`. See {@link Ctx.env}.
|
|
63
|
+
*/
|
|
64
|
+
export type EnvVars<E extends Env> = E['Bindings'] & Record<string, string | undefined>;
|
|
65
|
+
/**
|
|
66
|
+
* Ergonomic, read-mostly wrapper around Hono's {@link Context} for use inside
|
|
67
|
+
* server components and server actions.
|
|
68
|
+
*
|
|
69
|
+
* Obtain one with {@link getContext}, or — in a page component — take it straight
|
|
70
|
+
* off the `ctx` prop, which is this same object. Never construct it yourself. One
|
|
71
|
+
* instance is reused for the lifetime of a request, so its lazy getters
|
|
72
|
+
* ({@link Ctx.url}, {@link Ctx.env}) are computed at most once.
|
|
73
|
+
*
|
|
74
|
+
* @typeParam E - The Hono {@link Env} describing this app's `Bindings` and
|
|
75
|
+
* `Variables`, so {@link Ctx.var} and {@link Ctx.env} stay typed.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```tsx
|
|
79
|
+
* import { getContext } from '@rshono/core/server';
|
|
80
|
+
*
|
|
81
|
+
* export default async function Whoami() {
|
|
82
|
+
* const ctx = getContext();
|
|
83
|
+
* const session = ctx.cookies.get('session');
|
|
84
|
+
* return <p>{ctx.url.pathname} — {session ?? 'anonymous'}</p>;
|
|
85
|
+
* }
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
export declare class Ctx<E extends Env = Env> {
|
|
89
|
+
#private;
|
|
90
|
+
constructor(c: Context<E>);
|
|
91
|
+
/**
|
|
92
|
+
* The underlying Hono {@link Context}. Escape hatch for anything this wrapper does not expose.
|
|
93
|
+
*
|
|
94
|
+
* A getter over a private field rather than a plain property, so it is not an *own enumerable*
|
|
95
|
+
* one — which matters more than it looks. React's diagnostic for a value that cannot be sent to a
|
|
96
|
+
* client component (`describeObjectForErrorMessage`) walks `Object.keys` recursively with no depth
|
|
97
|
+
* limit and no cycle guard, and the Hono context graph reaches the socket and the whole server
|
|
98
|
+
* through `req.raw` and `env`. While this was a plain property, passing a `Ctx` to a `'use client'`
|
|
99
|
+
* component blew the stack *inside that message builder* — so React's actual, accurate "you cannot
|
|
100
|
+
* pass this" error never got printed. Hidden from `Object.keys`, the walk stops here.
|
|
101
|
+
*/
|
|
102
|
+
get raw(): Context<E>;
|
|
103
|
+
/** The parsed Hono request (`c.req`) — headers, body parsing, param access, etc. */
|
|
104
|
+
get req(): HonoRequest;
|
|
105
|
+
/**
|
|
106
|
+
* The browser-facing request URL, proxy-header aware (see {@link publicUrl}) —
|
|
107
|
+
* read `url.pathname`, `url.searchParams` and the rest off it. Parsed once and
|
|
108
|
+
* cached, so the same instance comes back on every read within a request; treat
|
|
109
|
+
* it as read-only for that reason.
|
|
110
|
+
*/
|
|
111
|
+
get url(): URL;
|
|
112
|
+
/** The HTTP method of the request, e.g. `GET` or `POST`. */
|
|
113
|
+
get method(): string;
|
|
114
|
+
/**
|
|
115
|
+
* Matched route params, e.g. `{ id }` for a `/users/[id]` route. Returns an
|
|
116
|
+
* empty object when there is no active route match (rather than throwing).
|
|
117
|
+
*/
|
|
118
|
+
get params(): Record<string, string>;
|
|
119
|
+
/**
|
|
120
|
+
* Typed variables set by middleware via `c.set('user', …)`, read here as
|
|
121
|
+
* `ctx.var.user`. Type them by parameterising this class's {@link Env}.
|
|
122
|
+
*/
|
|
123
|
+
get var(): Readonly<E['Variables']>;
|
|
124
|
+
/**
|
|
125
|
+
* Environment for the request: process env vars merged with runtime bindings
|
|
126
|
+
* (bindings win on conflict). Computed once and cached.
|
|
127
|
+
*
|
|
128
|
+
* @example `const key = getContext().env.STRIPE_SECRET_KEY;`
|
|
129
|
+
*/
|
|
130
|
+
get env(): EnvVars<E>;
|
|
131
|
+
/** Sets a response header. Thin pass-through to `c.header(name, value)`. */
|
|
132
|
+
header(name: string, value: string): void;
|
|
133
|
+
/**
|
|
134
|
+
* Read and write request/response cookies.
|
|
135
|
+
*
|
|
136
|
+
* @example
|
|
137
|
+
* ```ts
|
|
138
|
+
* const ctx = getContext();
|
|
139
|
+
* ctx.cookies.get('session'); // string | undefined
|
|
140
|
+
* ctx.cookies.set('session', id, { httpOnly: true, sameSite: 'Lax', path: '/' });
|
|
141
|
+
* ctx.cookies.delete('session', { path: '/' });
|
|
142
|
+
* ```
|
|
143
|
+
*/
|
|
144
|
+
cookies: {
|
|
145
|
+
/** Reads a single cookie by name, or `undefined` if absent. */
|
|
146
|
+
get: (name: string) => string | undefined;
|
|
147
|
+
/** Reads every cookie as a `{ name: value }` record. */
|
|
148
|
+
all: () => Record<string, string>;
|
|
149
|
+
/** Sets a cookie on the response. See Hono's {@link CookieOptions} for `path`, `httpOnly`, `maxAge`, etc. */
|
|
150
|
+
set: (name: string, value: string, options?: CookieOptions) => void;
|
|
151
|
+
/** Clears a cookie. Pass the same `path`/`domain` it was set with so the browser matches it. */
|
|
152
|
+
delete: (name: string, options?: CookieOptions) => void;
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Returns the {@link Ctx} for the current request.
|
|
157
|
+
*
|
|
158
|
+
* This is the primary entry point for reading request data from a server
|
|
159
|
+
* component or server action — the URL, cookies, params, env, and middleware
|
|
160
|
+
* variables. The returned wrapper is memoised per request, so repeated calls in
|
|
161
|
+
* the same request are cheap and return the same instance.
|
|
162
|
+
*
|
|
163
|
+
* A **page** component is handed the very same object as its `ctx` prop, so this
|
|
164
|
+
* import is for everywhere else: a nested server component, or a `'use server'`
|
|
165
|
+
* action module — neither of which receives props from the framework.
|
|
166
|
+
*
|
|
167
|
+
* @typeParam E - The app's Hono {@link Env}, to type {@link Ctx.var} and {@link Ctx.env}.
|
|
168
|
+
* @throws If called at module load, where there is no ambient context to resolve.
|
|
169
|
+
* @throws If called while prerendering a `render: 'static'` route, which has no
|
|
170
|
+
* per-request context at build time — mark the route `render: 'dynamic'` instead.
|
|
171
|
+
*
|
|
172
|
+
* @example
|
|
173
|
+
* ```ts
|
|
174
|
+
* 'use server';
|
|
175
|
+
* import { getContext, redirect } from '@rshono/core/server';
|
|
176
|
+
*
|
|
177
|
+
* export async function login(form: FormData) {
|
|
178
|
+
* getContext().cookies.set('session', String(form.get('email')), { httpOnly: true });
|
|
179
|
+
* redirect('/dashboard');
|
|
180
|
+
* }
|
|
181
|
+
* ```
|
|
182
|
+
*/
|
|
183
|
+
export declare function getContext<E extends Env = Env>(): Ctx<E>;
|
|
184
|
+
/**
|
|
185
|
+
* Redirects the request to `location` by throwing a control signal that the
|
|
186
|
+
* framework catches and turns into an HTTP redirect response.
|
|
187
|
+
*
|
|
188
|
+
* Because it throws, it never returns — TypeScript narrows away any code after
|
|
189
|
+
* the call, and you do not need to `return` it. Do not wrap it in a `try/catch`
|
|
190
|
+
* that swallows the signal.
|
|
191
|
+
*
|
|
192
|
+
* @param location - Absolute path or URL to redirect to, e.g. `/dashboard`.
|
|
193
|
+
* @param status - Redirect {@link RedirectStatus}; defaults to `303` (See Other),
|
|
194
|
+
* the correct choice after a form action so the browser follows up with a `GET`.
|
|
195
|
+
*
|
|
196
|
+
* @example
|
|
197
|
+
* ```ts
|
|
198
|
+
* const session = getContext().cookies.get('session');
|
|
199
|
+
* if (!session) redirect('/login');
|
|
200
|
+
* // session is defined below this line
|
|
201
|
+
* ```
|
|
202
|
+
*/
|
|
203
|
+
export declare function redirect(location: string, status?: RedirectStatus): never;
|
|
204
|
+
/**
|
|
205
|
+
* Aborts the current render with a 404, rendering the app's not-found page.
|
|
206
|
+
*
|
|
207
|
+
* Like {@link redirect}, this throws a control signal and never returns, so
|
|
208
|
+
* TypeScript narrows away everything after the call. Do not catch-and-swallow it.
|
|
209
|
+
*
|
|
210
|
+
* @example
|
|
211
|
+
* ```tsx
|
|
212
|
+
* const user = await db.user.find(getContext().params.id);
|
|
213
|
+
* if (!user) notFound();
|
|
214
|
+
* return <Profile user={user} />; // user is non-null here
|
|
215
|
+
* ```
|
|
216
|
+
*/
|
|
217
|
+
export declare function notFound(): never;
|
|
218
|
+
/**
|
|
219
|
+
* Which stage of a request produced an error handed to an {@link ServerErrorHandler}.
|
|
220
|
+
*
|
|
221
|
+
* - `action` — a `'use server'` function threw. React sends the client an opaque marker with no
|
|
222
|
+
* message in production, so this is the only place the real error is visible.
|
|
223
|
+
* - `render` — a server component threw while the flight payload was being produced.
|
|
224
|
+
* - `ssr` — SSR failed before the HTML shell could be sent, so the `error` page was unreachable too.
|
|
225
|
+
* - `request` — anything else that reached the top-level handler, including a thrown endpoint route.
|
|
226
|
+
*/
|
|
227
|
+
export type ServerErrorSource = 'action' | 'render' | 'ssr' | 'request';
|
|
228
|
+
/** What an {@link ServerErrorHandler} is told about an error, beyond the error itself. */
|
|
229
|
+
export interface ServerErrorContext {
|
|
230
|
+
/** The stage that produced it — see {@link ServerErrorSource}. */
|
|
231
|
+
source: ServerErrorSource;
|
|
232
|
+
/** The request being served, for the URL, method and headers. */
|
|
233
|
+
request: Request;
|
|
234
|
+
}
|
|
235
|
+
/** Handler registered with {@link onServerError}. Called for the side effect; its return value is ignored. */
|
|
236
|
+
export type ServerErrorHandler = (error: unknown, context: ServerErrorContext) => void;
|
|
237
|
+
/**
|
|
238
|
+
* Registers a handler for every error the framework catches, so they can reach an error tracker
|
|
239
|
+
* (Sentry, Datadog, a log pipeline) instead of only `stderr`.
|
|
240
|
+
*
|
|
241
|
+
* Call it **once, at the top level of `src/server.ts`** — that module is imported as the server
|
|
242
|
+
* starts, before any request is served. Registering again replaces the previous handler.
|
|
243
|
+
*
|
|
244
|
+
* Errors are still written to `stderr` either way, so a handler adds a destination rather than
|
|
245
|
+
* replacing one. A handler that throws is caught and logged: reporting must never be able to fail
|
|
246
|
+
* a request.
|
|
247
|
+
*
|
|
248
|
+
* @example
|
|
249
|
+
* ```ts
|
|
250
|
+
* // src/server.ts
|
|
251
|
+
* import * as Sentry from '@sentry/node';
|
|
252
|
+
* import { onServerError } from '@rshono/core/server';
|
|
253
|
+
*
|
|
254
|
+
* onServerError((error, { source, request }) => {
|
|
255
|
+
* Sentry.captureException(error, { tags: { source }, extra: { url: request.url } });
|
|
256
|
+
* });
|
|
257
|
+
* ```
|
|
258
|
+
*/
|
|
259
|
+
export declare function onServerError(handler: ServerErrorHandler): void;
|
|
260
|
+
/**
|
|
261
|
+
* Logs an error and forwards it to the registered {@link ServerErrorHandler}.
|
|
262
|
+
*
|
|
263
|
+
* Framework internal — the single funnel every caught server-side error goes through, so that
|
|
264
|
+
* adding a reporting destination is one registration rather than a hook per call site.
|
|
265
|
+
*
|
|
266
|
+
* @internal
|
|
267
|
+
*/
|
|
268
|
+
export declare function reportServerError(error: unknown, info: ServerErrorContext & {
|
|
269
|
+
message: string;
|
|
270
|
+
}): void;
|
|
271
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/runtime/context.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
/// <reference path="../types/rshono-config.d.ts" />
|
|
2
|
+
/**
|
|
3
|
+
* The request context: {@link getContext} and the {@link Ctx} wrapper it returns,
|
|
4
|
+
* the {@link redirect} / {@link notFound} control-flow helpers, and the
|
|
5
|
+
* {@link onServerError} reporting funnel — plus the `@internal` plumbing that binds
|
|
6
|
+
* a request to the async context in the first place.
|
|
7
|
+
*
|
|
8
|
+
* The public half of this module is re-exported by `runtime/server.ts`, which is
|
|
9
|
+
* what the `@rshono/core/server` subpath resolves to; import *that* from an app. Nothing
|
|
10
|
+
* here is safe in a `'use client'` module — those run in the browser, with no bound
|
|
11
|
+
* request context.
|
|
12
|
+
*/
|
|
13
|
+
import { deleteCookie, getCookie, setCookie } from 'hono/cookie';
|
|
14
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
15
|
+
import { NotFoundSignal, RedirectSignal } from './control.js';
|
|
16
|
+
const contextStorage = new AsyncLocalStorage();
|
|
17
|
+
/** One {@link Ctx} per Hono {@link Context}, so repeated `getContext()` calls in a request share its lazy getters. */
|
|
18
|
+
const wrappers = new WeakMap();
|
|
19
|
+
/**
|
|
20
|
+
* `process.env`, snapshotted on first read.
|
|
21
|
+
*
|
|
22
|
+
* It is not a plain object — every enumeration crosses into the host environment, which made
|
|
23
|
+
* spreading it (~20µs) by far the most expensive thing {@link Ctx.env} did, once per request that
|
|
24
|
+
* touched it. Snapshotted lazily rather than at module load because `loadEnvFiles()` runs *after*
|
|
25
|
+
* this module is imported, so an eager copy would miss everything from `.env`. The trade-off: a
|
|
26
|
+
* `process.env` mutation after the first `ctx.env` read is not picked up.
|
|
27
|
+
*/
|
|
28
|
+
let envSnapshot;
|
|
29
|
+
function processEnv() {
|
|
30
|
+
return (envSnapshot ??= typeof process !== 'undefined' && process.env ? { ...process.env } : {});
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* True when this process is the SSG build prerendering `render: 'static'` routes,
|
|
34
|
+
* rather than a server handling real requests. `build.ts` sets `RSHONO_PRERENDER`
|
|
35
|
+
* before importing the app bundle and starting the prerender pass; the app bundle
|
|
36
|
+
* inlines its own copy of this module, so a shared `process.env` (not a module-level
|
|
37
|
+
* flag) is what reliably crosses that boundary. Read by {@link getContext} to turn a
|
|
38
|
+
* static route's request-context read into a clear build-time error instead of
|
|
39
|
+
* silently baking synthetic build-time values (a `localhost` URL, no cookies, build
|
|
40
|
+
* env) into the snapshot.
|
|
41
|
+
*/
|
|
42
|
+
const prerendering = typeof process !== 'undefined' && !!process.env?.RSHONO_PRERENDER;
|
|
43
|
+
/**
|
|
44
|
+
* Runs `fn` with the given Hono {@link Context} bound as the ambient request
|
|
45
|
+
* context, so that {@link getContext} resolves to it anywhere in the call tree.
|
|
46
|
+
*
|
|
47
|
+
* Framework internal — the request handler wraps every render and action in
|
|
48
|
+
* this. Application code should reach for {@link getContext} instead.
|
|
49
|
+
*
|
|
50
|
+
* @internal
|
|
51
|
+
*/
|
|
52
|
+
export function runWithContext(c, fn) {
|
|
53
|
+
return contextStorage.run(c, fn);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Reads the matched route params, returning an empty object when there is no
|
|
57
|
+
* active route match (rather than throwing). Shared by {@link Ctx.params} and the
|
|
58
|
+
* request renderer so the fallback behaviour stays in one place.
|
|
59
|
+
*
|
|
60
|
+
* Framework internal — read params from {@link Ctx.params} or a page's
|
|
61
|
+
* `PageProps` instead.
|
|
62
|
+
*
|
|
63
|
+
* @internal
|
|
64
|
+
*/
|
|
65
|
+
export function readParams(c) {
|
|
66
|
+
try {
|
|
67
|
+
return c.req.param();
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return {};
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/** A proxy chain appends to these headers, so the client-facing value is the first entry. */
|
|
74
|
+
function firstForwardedValue(header) {
|
|
75
|
+
const first = header?.split(',')[0]?.trim();
|
|
76
|
+
return first || undefined;
|
|
77
|
+
}
|
|
78
|
+
// DefinePlugin inlines the config into the server bundle, but this module is the public
|
|
79
|
+
// `@rshono/core/server` entry and could be loaded by tooling that doesn't (a unit test, a one-off script).
|
|
80
|
+
// Read through `typeof` so that degrades to the safe answer — don't trust — instead of a ReferenceError.
|
|
81
|
+
const trustProxy = typeof __RSHONO_CONFIG__ !== 'undefined' && __RSHONO_CONFIG__.trustProxy;
|
|
82
|
+
/**
|
|
83
|
+
* Resolves the browser-facing {@link URL} for a request.
|
|
84
|
+
*
|
|
85
|
+
* `c.req.url` reflects the internal address the server was reached on, which is wrong behind a
|
|
86
|
+
* proxy or load balancer. `X-Forwarded-Host` / `X-Forwarded-Proto` fix that up — **but only when
|
|
87
|
+
* `trustProxy` is enabled in `rshono.config.ts`** (always the case under `rshono dev`). Those
|
|
88
|
+
* headers are client-supplied: honouring them unconditionally lets anyone who can reach the server
|
|
89
|
+
* dictate the origin of every absolute URL the app builds — canonical tags, emails, redirects — and
|
|
90
|
+
* poison a shared cache with them. So the default is to ignore them entirely.
|
|
91
|
+
*
|
|
92
|
+
* Framework internal — prefer {@link Ctx.url}, which caches the result per request.
|
|
93
|
+
*
|
|
94
|
+
* @internal
|
|
95
|
+
*/
|
|
96
|
+
export function publicUrl(c) {
|
|
97
|
+
const url = new URL(c.req.url);
|
|
98
|
+
if (!trustProxy)
|
|
99
|
+
return url;
|
|
100
|
+
const forwardedHost = firstForwardedValue(c.req.header('x-forwarded-host'));
|
|
101
|
+
// Parsed rather than assigned to `url.host`, because that setter *keeps the existing port* when
|
|
102
|
+
// the new value has none — leaving the internal port on the public URL (`example.com:3000`).
|
|
103
|
+
const forwarded = forwardedHost ? URL.parse(`http://${forwardedHost}`) : null;
|
|
104
|
+
if (forwarded) {
|
|
105
|
+
url.hostname = forwarded.hostname;
|
|
106
|
+
url.port = forwarded.port; // '' when the forwarded host carries no port, which clears it
|
|
107
|
+
}
|
|
108
|
+
// Restricted to the two schemes a browser can actually have requested; anything else (a proxy
|
|
109
|
+
// sending junk, or a client trying its luck) leaves the scheme alone.
|
|
110
|
+
const forwardedProto = firstForwardedValue(c.req.header('x-forwarded-proto'));
|
|
111
|
+
if (forwardedProto === 'http' || forwardedProto === 'https')
|
|
112
|
+
url.protocol = forwardedProto;
|
|
113
|
+
return url;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Ergonomic, read-mostly wrapper around Hono's {@link Context} for use inside
|
|
117
|
+
* server components and server actions.
|
|
118
|
+
*
|
|
119
|
+
* Obtain one with {@link getContext}, or — in a page component — take it straight
|
|
120
|
+
* off the `ctx` prop, which is this same object. Never construct it yourself. One
|
|
121
|
+
* instance is reused for the lifetime of a request, so its lazy getters
|
|
122
|
+
* ({@link Ctx.url}, {@link Ctx.env}) are computed at most once.
|
|
123
|
+
*
|
|
124
|
+
* @typeParam E - The Hono {@link Env} describing this app's `Bindings` and
|
|
125
|
+
* `Variables`, so {@link Ctx.var} and {@link Ctx.env} stay typed.
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* ```tsx
|
|
129
|
+
* import { getContext } from '@rshono/core/server';
|
|
130
|
+
*
|
|
131
|
+
* export default async function Whoami() {
|
|
132
|
+
* const ctx = getContext();
|
|
133
|
+
* const session = ctx.cookies.get('session');
|
|
134
|
+
* return <p>{ctx.url.pathname} — {session ?? 'anonymous'}</p>;
|
|
135
|
+
* }
|
|
136
|
+
* ```
|
|
137
|
+
*/
|
|
138
|
+
export class Ctx {
|
|
139
|
+
#raw;
|
|
140
|
+
#url;
|
|
141
|
+
#env;
|
|
142
|
+
constructor(c) {
|
|
143
|
+
this.#raw = c;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* The underlying Hono {@link Context}. Escape hatch for anything this wrapper does not expose.
|
|
147
|
+
*
|
|
148
|
+
* A getter over a private field rather than a plain property, so it is not an *own enumerable*
|
|
149
|
+
* one — which matters more than it looks. React's diagnostic for a value that cannot be sent to a
|
|
150
|
+
* client component (`describeObjectForErrorMessage`) walks `Object.keys` recursively with no depth
|
|
151
|
+
* limit and no cycle guard, and the Hono context graph reaches the socket and the whole server
|
|
152
|
+
* through `req.raw` and `env`. While this was a plain property, passing a `Ctx` to a `'use client'`
|
|
153
|
+
* component blew the stack *inside that message builder* — so React's actual, accurate "you cannot
|
|
154
|
+
* pass this" error never got printed. Hidden from `Object.keys`, the walk stops here.
|
|
155
|
+
*/
|
|
156
|
+
get raw() {
|
|
157
|
+
return this.#raw;
|
|
158
|
+
}
|
|
159
|
+
/** The parsed Hono request (`c.req`) — headers, body parsing, param access, etc. */
|
|
160
|
+
get req() {
|
|
161
|
+
return this.#raw.req;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* The browser-facing request URL, proxy-header aware (see {@link publicUrl}) —
|
|
165
|
+
* read `url.pathname`, `url.searchParams` and the rest off it. Parsed once and
|
|
166
|
+
* cached, so the same instance comes back on every read within a request; treat
|
|
167
|
+
* it as read-only for that reason.
|
|
168
|
+
*/
|
|
169
|
+
get url() {
|
|
170
|
+
return (this.#url ??= publicUrl(this.#raw));
|
|
171
|
+
}
|
|
172
|
+
/** The HTTP method of the request, e.g. `GET` or `POST`. */
|
|
173
|
+
get method() {
|
|
174
|
+
return this.#raw.req.method;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Matched route params, e.g. `{ id }` for a `/users/[id]` route. Returns an
|
|
178
|
+
* empty object when there is no active route match (rather than throwing).
|
|
179
|
+
*/
|
|
180
|
+
get params() {
|
|
181
|
+
return readParams(this.#raw);
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Typed variables set by middleware via `c.set('user', …)`, read here as
|
|
185
|
+
* `ctx.var.user`. Type them by parameterising this class's {@link Env}.
|
|
186
|
+
*/
|
|
187
|
+
get var() {
|
|
188
|
+
return this.#raw.var;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Environment for the request: process env vars merged with runtime bindings
|
|
192
|
+
* (bindings win on conflict). Computed once and cached.
|
|
193
|
+
*
|
|
194
|
+
* @example `const key = getContext().env.STRIPE_SECRET_KEY;`
|
|
195
|
+
*/
|
|
196
|
+
get env() {
|
|
197
|
+
if (this.#env)
|
|
198
|
+
return this.#env;
|
|
199
|
+
const bindings = this.#raw.env;
|
|
200
|
+
// The snapshot is shared, so hand it back as-is when there are no bindings to merge over it.
|
|
201
|
+
return (this.#env = (bindings ? { ...processEnv(), ...bindings } : processEnv()));
|
|
202
|
+
}
|
|
203
|
+
/** Sets a response header. Thin pass-through to `c.header(name, value)`. */
|
|
204
|
+
header(name, value) {
|
|
205
|
+
this.#raw.header(name, value);
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Read and write request/response cookies.
|
|
209
|
+
*
|
|
210
|
+
* @example
|
|
211
|
+
* ```ts
|
|
212
|
+
* const ctx = getContext();
|
|
213
|
+
* ctx.cookies.get('session'); // string | undefined
|
|
214
|
+
* ctx.cookies.set('session', id, { httpOnly: true, sameSite: 'Lax', path: '/' });
|
|
215
|
+
* ctx.cookies.delete('session', { path: '/' });
|
|
216
|
+
* ```
|
|
217
|
+
*/
|
|
218
|
+
cookies = {
|
|
219
|
+
/** Reads a single cookie by name, or `undefined` if absent. */
|
|
220
|
+
get: (name) => getCookie(this.#raw, name),
|
|
221
|
+
/** Reads every cookie as a `{ name: value }` record. */
|
|
222
|
+
all: () => getCookie(this.#raw),
|
|
223
|
+
/** Sets a cookie on the response. See Hono's {@link CookieOptions} for `path`, `httpOnly`, `maxAge`, etc. */
|
|
224
|
+
set: (name, value, options) => setCookie(this.#raw, name, value, options),
|
|
225
|
+
/** Clears a cookie. Pass the same `path`/`domain` it was set with so the browser matches it. */
|
|
226
|
+
delete: (name, options) => {
|
|
227
|
+
deleteCookie(this.#raw, name, options);
|
|
228
|
+
},
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Returns the {@link Ctx} for the current request.
|
|
233
|
+
*
|
|
234
|
+
* This is the primary entry point for reading request data from a server
|
|
235
|
+
* component or server action — the URL, cookies, params, env, and middleware
|
|
236
|
+
* variables. The returned wrapper is memoised per request, so repeated calls in
|
|
237
|
+
* the same request are cheap and return the same instance.
|
|
238
|
+
*
|
|
239
|
+
* A **page** component is handed the very same object as its `ctx` prop, so this
|
|
240
|
+
* import is for everywhere else: a nested server component, or a `'use server'`
|
|
241
|
+
* action module — neither of which receives props from the framework.
|
|
242
|
+
*
|
|
243
|
+
* @typeParam E - The app's Hono {@link Env}, to type {@link Ctx.var} and {@link Ctx.env}.
|
|
244
|
+
* @throws If called at module load, where there is no ambient context to resolve.
|
|
245
|
+
* @throws If called while prerendering a `render: 'static'` route, which has no
|
|
246
|
+
* per-request context at build time — mark the route `render: 'dynamic'` instead.
|
|
247
|
+
*
|
|
248
|
+
* @example
|
|
249
|
+
* ```ts
|
|
250
|
+
* 'use server';
|
|
251
|
+
* import { getContext, redirect } from '@rshono/core/server';
|
|
252
|
+
*
|
|
253
|
+
* export async function login(form: FormData) {
|
|
254
|
+
* getContext().cookies.set('session', String(form.get('email')), { httpOnly: true });
|
|
255
|
+
* redirect('/dashboard');
|
|
256
|
+
* }
|
|
257
|
+
* ```
|
|
258
|
+
*/
|
|
259
|
+
export function getContext() {
|
|
260
|
+
if (prerendering) {
|
|
261
|
+
throw new Error("[rshono] getContext() was called while prerendering a `render: 'static'` route. A static page " +
|
|
262
|
+
'is rendered once at build time, so it has no per-request context to read (URL, cookies, ' +
|
|
263
|
+
"headers, env). Change this route to `render: 'dynamic'` so it renders per request, or remove " +
|
|
264
|
+
'the getContext() call.');
|
|
265
|
+
}
|
|
266
|
+
const c = contextStorage.getStore();
|
|
267
|
+
if (!c) {
|
|
268
|
+
throw new Error('[rshono] getContext() was called outside a request. It only works inside a server component or a server action, not at module load.');
|
|
269
|
+
}
|
|
270
|
+
let ctx = wrappers.get(c);
|
|
271
|
+
if (!ctx) {
|
|
272
|
+
ctx = new Ctx(c);
|
|
273
|
+
wrappers.set(c, ctx);
|
|
274
|
+
}
|
|
275
|
+
return ctx;
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Redirects the request to `location` by throwing a control signal that the
|
|
279
|
+
* framework catches and turns into an HTTP redirect response.
|
|
280
|
+
*
|
|
281
|
+
* Because it throws, it never returns — TypeScript narrows away any code after
|
|
282
|
+
* the call, and you do not need to `return` it. Do not wrap it in a `try/catch`
|
|
283
|
+
* that swallows the signal.
|
|
284
|
+
*
|
|
285
|
+
* @param location - Absolute path or URL to redirect to, e.g. `/dashboard`.
|
|
286
|
+
* @param status - Redirect {@link RedirectStatus}; defaults to `303` (See Other),
|
|
287
|
+
* the correct choice after a form action so the browser follows up with a `GET`.
|
|
288
|
+
*
|
|
289
|
+
* @example
|
|
290
|
+
* ```ts
|
|
291
|
+
* const session = getContext().cookies.get('session');
|
|
292
|
+
* if (!session) redirect('/login');
|
|
293
|
+
* // session is defined below this line
|
|
294
|
+
* ```
|
|
295
|
+
*/
|
|
296
|
+
export function redirect(location, status = 303) {
|
|
297
|
+
throw new RedirectSignal(location, status);
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Aborts the current render with a 404, rendering the app's not-found page.
|
|
301
|
+
*
|
|
302
|
+
* Like {@link redirect}, this throws a control signal and never returns, so
|
|
303
|
+
* TypeScript narrows away everything after the call. Do not catch-and-swallow it.
|
|
304
|
+
*
|
|
305
|
+
* @example
|
|
306
|
+
* ```tsx
|
|
307
|
+
* const user = await db.user.find(getContext().params.id);
|
|
308
|
+
* if (!user) notFound();
|
|
309
|
+
* return <Profile user={user} />; // user is non-null here
|
|
310
|
+
* ```
|
|
311
|
+
*/
|
|
312
|
+
export function notFound() {
|
|
313
|
+
throw new NotFoundSignal();
|
|
314
|
+
}
|
|
315
|
+
let errorHandler;
|
|
316
|
+
/**
|
|
317
|
+
* Registers a handler for every error the framework catches, so they can reach an error tracker
|
|
318
|
+
* (Sentry, Datadog, a log pipeline) instead of only `stderr`.
|
|
319
|
+
*
|
|
320
|
+
* Call it **once, at the top level of `src/server.ts`** — that module is imported as the server
|
|
321
|
+
* starts, before any request is served. Registering again replaces the previous handler.
|
|
322
|
+
*
|
|
323
|
+
* Errors are still written to `stderr` either way, so a handler adds a destination rather than
|
|
324
|
+
* replacing one. A handler that throws is caught and logged: reporting must never be able to fail
|
|
325
|
+
* a request.
|
|
326
|
+
*
|
|
327
|
+
* @example
|
|
328
|
+
* ```ts
|
|
329
|
+
* // src/server.ts
|
|
330
|
+
* import * as Sentry from '@sentry/node';
|
|
331
|
+
* import { onServerError } from '@rshono/core/server';
|
|
332
|
+
*
|
|
333
|
+
* onServerError((error, { source, request }) => {
|
|
334
|
+
* Sentry.captureException(error, { tags: { source }, extra: { url: request.url } });
|
|
335
|
+
* });
|
|
336
|
+
* ```
|
|
337
|
+
*/
|
|
338
|
+
export function onServerError(handler) {
|
|
339
|
+
errorHandler = handler;
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Logs an error and forwards it to the registered {@link ServerErrorHandler}.
|
|
343
|
+
*
|
|
344
|
+
* Framework internal — the single funnel every caught server-side error goes through, so that
|
|
345
|
+
* adding a reporting destination is one registration rather than a hook per call site.
|
|
346
|
+
*
|
|
347
|
+
* @internal
|
|
348
|
+
*/
|
|
349
|
+
export function reportServerError(error, info) {
|
|
350
|
+
console.error(info.message, error);
|
|
351
|
+
if (!errorHandler)
|
|
352
|
+
return;
|
|
353
|
+
try {
|
|
354
|
+
errorHandler(error, { source: info.source, request: info.request });
|
|
355
|
+
}
|
|
356
|
+
catch (handlerError) {
|
|
357
|
+
console.error('[rshono] the onServerError handler threw:', handlerError);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
//# sourceMappingURL=context.js.map
|