@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.
Files changed (189) hide show
  1. package/README.md +273 -0
  2. package/bin/rshono.mjs +15 -0
  3. package/dist/builder/env-shadow-loader.cjs +26 -0
  4. package/dist/builder/page-entry-loader.cjs +7 -0
  5. package/dist/builder/page-files.d.ts +2 -0
  6. package/dist/builder/page-files.d.ts.map +1 -0
  7. package/dist/builder/page-files.js +45 -0
  8. package/dist/builder/page-files.js.map +1 -0
  9. package/dist/builder/public-env.d.ts +2 -0
  10. package/dist/builder/public-env.d.ts.map +1 -0
  11. package/dist/builder/public-env.js +5 -0
  12. package/dist/builder/public-env.js.map +1 -0
  13. package/dist/builder/rspack-config.d.ts +14 -0
  14. package/dist/builder/rspack-config.d.ts.map +1 -0
  15. package/dist/builder/rspack-config.js +235 -0
  16. package/dist/builder/rspack-config.js.map +1 -0
  17. package/dist/cli/build.d.ts +11 -0
  18. package/dist/cli/build.d.ts.map +1 -0
  19. package/dist/cli/build.js +64 -0
  20. package/dist/cli/build.js.map +1 -0
  21. package/dist/cli/dev.d.ts +9 -0
  22. package/dist/cli/dev.d.ts.map +1 -0
  23. package/dist/cli/dev.js +210 -0
  24. package/dist/cli/dev.js.map +1 -0
  25. package/dist/cli/index.d.ts +2 -0
  26. package/dist/cli/index.d.ts.map +1 -0
  27. package/dist/cli/index.js +77 -0
  28. package/dist/cli/index.js.map +1 -0
  29. package/dist/cli/start.d.ts +8 -0
  30. package/dist/cli/start.d.ts.map +1 -0
  31. package/dist/cli/start.js +39 -0
  32. package/dist/cli/start.js.map +1 -0
  33. package/dist/config.d.ts +121 -0
  34. package/dist/config.d.ts.map +1 -0
  35. package/dist/config.js +5 -0
  36. package/dist/config.js.map +1 -0
  37. package/dist/deploy/aws-lambda/runtime.d.ts +14 -0
  38. package/dist/deploy/aws-lambda/runtime.d.ts.map +1 -0
  39. package/dist/deploy/aws-lambda/runtime.js +24 -0
  40. package/dist/deploy/aws-lambda/runtime.js.map +1 -0
  41. package/dist/deploy/build-marker.d.ts +15 -0
  42. package/dist/deploy/build-marker.d.ts.map +1 -0
  43. package/dist/deploy/build-marker.js +26 -0
  44. package/dist/deploy/build-marker.js.map +1 -0
  45. package/dist/deploy/bun/runtime.d.ts +11 -0
  46. package/dist/deploy/bun/runtime.d.ts.map +1 -0
  47. package/dist/deploy/bun/runtime.js +22 -0
  48. package/dist/deploy/bun/runtime.js.map +1 -0
  49. package/dist/deploy/cloudflare/build.d.ts +7 -0
  50. package/dist/deploy/cloudflare/build.d.ts.map +1 -0
  51. package/dist/deploy/cloudflare/build.js +74 -0
  52. package/dist/deploy/cloudflare/build.js.map +1 -0
  53. package/dist/deploy/cloudflare/runtime.d.ts +10 -0
  54. package/dist/deploy/cloudflare/runtime.d.ts.map +1 -0
  55. package/dist/deploy/cloudflare/runtime.js +129 -0
  56. package/dist/deploy/cloudflare/runtime.js.map +1 -0
  57. package/dist/deploy/contract.d.ts +65 -0
  58. package/dist/deploy/contract.d.ts.map +1 -0
  59. package/dist/deploy/contract.js +2 -0
  60. package/dist/deploy/contract.js.map +1 -0
  61. package/dist/deploy/deno/runtime.d.ts +11 -0
  62. package/dist/deploy/deno/runtime.d.ts.map +1 -0
  63. package/dist/deploy/deno/runtime.js +16 -0
  64. package/dist/deploy/deno/runtime.js.map +1 -0
  65. package/dist/deploy/filesystem.d.ts +10 -0
  66. package/dist/deploy/filesystem.d.ts.map +1 -0
  67. package/dist/deploy/filesystem.js +47 -0
  68. package/dist/deploy/filesystem.js.map +1 -0
  69. package/dist/deploy/listen.d.ts +20 -0
  70. package/dist/deploy/listen.d.ts.map +1 -0
  71. package/dist/deploy/listen.js +24 -0
  72. package/dist/deploy/listen.js.map +1 -0
  73. package/dist/deploy/netlify/build.d.ts +8 -0
  74. package/dist/deploy/netlify/build.d.ts.map +1 -0
  75. package/dist/deploy/netlify/build.js +52 -0
  76. package/dist/deploy/netlify/build.js.map +1 -0
  77. package/dist/deploy/netlify/runtime.d.ts +13 -0
  78. package/dist/deploy/netlify/runtime.d.ts.map +1 -0
  79. package/dist/deploy/netlify/runtime.js +24 -0
  80. package/dist/deploy/netlify/runtime.js.map +1 -0
  81. package/dist/deploy/node/runtime.d.ts +7 -0
  82. package/dist/deploy/node/runtime.d.ts.map +1 -0
  83. package/dist/deploy/node/runtime.js +36 -0
  84. package/dist/deploy/node/runtime.js.map +1 -0
  85. package/dist/deploy/presets.d.ts +88 -0
  86. package/dist/deploy/presets.d.ts.map +1 -0
  87. package/dist/deploy/presets.js +110 -0
  88. package/dist/deploy/presets.js.map +1 -0
  89. package/dist/deploy/vercel/build.d.ts +3 -0
  90. package/dist/deploy/vercel/build.d.ts.map +1 -0
  91. package/dist/deploy/vercel/build.js +56 -0
  92. package/dist/deploy/vercel/build.js.map +1 -0
  93. package/dist/deploy/vercel/runtime.d.ts +14 -0
  94. package/dist/deploy/vercel/runtime.d.ts.map +1 -0
  95. package/dist/deploy/vercel/runtime.js +26 -0
  96. package/dist/deploy/vercel/runtime.js.map +1 -0
  97. package/dist/index.d.ts +23 -0
  98. package/dist/index.d.ts.map +1 -0
  99. package/dist/index.js +17 -0
  100. package/dist/index.js.map +1 -0
  101. package/dist/router.d.ts +337 -0
  102. package/dist/router.d.ts.map +1 -0
  103. package/dist/router.js +18 -0
  104. package/dist/router.js.map +1 -0
  105. package/dist/runtime/boundaries.d.ts +102 -0
  106. package/dist/runtime/boundaries.d.ts.map +1 -0
  107. package/dist/runtime/boundaries.js +101 -0
  108. package/dist/runtime/boundaries.js.map +1 -0
  109. package/dist/runtime/client.d.ts +16 -0
  110. package/dist/runtime/client.d.ts.map +1 -0
  111. package/dist/runtime/client.js +16 -0
  112. package/dist/runtime/client.js.map +1 -0
  113. package/dist/runtime/context.d.ts +271 -0
  114. package/dist/runtime/context.d.ts.map +1 -0
  115. package/dist/runtime/context.js +360 -0
  116. package/dist/runtime/context.js.map +1 -0
  117. package/dist/runtime/control.d.ts +18 -0
  118. package/dist/runtime/control.d.ts.map +1 -0
  119. package/dist/runtime/control.js +39 -0
  120. package/dist/runtime/control.js.map +1 -0
  121. package/dist/runtime/dev-protocol.d.ts +22 -0
  122. package/dist/runtime/dev-protocol.d.ts.map +1 -0
  123. package/dist/runtime/dev-protocol.js +2 -0
  124. package/dist/runtime/dev-protocol.js.map +1 -0
  125. package/dist/runtime/empty-server-app.d.ts +3 -0
  126. package/dist/runtime/empty-server-app.d.ts.map +1 -0
  127. package/dist/runtime/empty-server-app.js +2 -0
  128. package/dist/runtime/empty-server-app.js.map +1 -0
  129. package/dist/runtime/entry.client.d.ts +2 -0
  130. package/dist/runtime/entry.client.d.ts.map +1 -0
  131. package/dist/runtime/entry.client.js +453 -0
  132. package/dist/runtime/entry.client.js.map +1 -0
  133. package/dist/runtime/entry.rsc.d.ts +32 -0
  134. package/dist/runtime/entry.rsc.d.ts.map +1 -0
  135. package/dist/runtime/entry.rsc.js +466 -0
  136. package/dist/runtime/entry.rsc.js.map +1 -0
  137. package/dist/runtime/entry.ssr.d.ts +25 -0
  138. package/dist/runtime/entry.ssr.d.ts.map +1 -0
  139. package/dist/runtime/entry.ssr.js +86 -0
  140. package/dist/runtime/entry.ssr.js.map +1 -0
  141. package/dist/runtime/navigation.d.ts +114 -0
  142. package/dist/runtime/navigation.d.ts.map +1 -0
  143. package/dist/runtime/navigation.js +112 -0
  144. package/dist/runtime/navigation.js.map +1 -0
  145. package/dist/runtime/request.d.ts +35 -0
  146. package/dist/runtime/request.d.ts.map +1 -0
  147. package/dist/runtime/request.js +39 -0
  148. package/dist/runtime/request.js.map +1 -0
  149. package/dist/runtime/server.d.ts +24 -0
  150. package/dist/runtime/server.d.ts.map +1 -0
  151. package/dist/runtime/server.js +24 -0
  152. package/dist/runtime/server.js.map +1 -0
  153. package/dist/server/compress.d.ts +15 -0
  154. package/dist/server/compress.d.ts.map +1 -0
  155. package/dist/server/compress.js +76 -0
  156. package/dist/server/compress.js.map +1 -0
  157. package/dist/server/headers.d.ts +24 -0
  158. package/dist/server/headers.d.ts.map +1 -0
  159. package/dist/server/headers.js +44 -0
  160. package/dist/server/headers.js.map +1 -0
  161. package/dist/server/load-config.d.ts +7 -0
  162. package/dist/server/load-config.d.ts.map +1 -0
  163. package/dist/server/load-config.js +41 -0
  164. package/dist/server/load-config.js.map +1 -0
  165. package/dist/server/load-env.d.ts +2 -0
  166. package/dist/server/load-env.d.ts.map +1 -0
  167. package/dist/server/load-env.js +10 -0
  168. package/dist/server/load-env.js.map +1 -0
  169. package/dist/server/prerendered.d.ts +67 -0
  170. package/dist/server/prerendered.d.ts.map +1 -0
  171. package/dist/server/prerendered.js +39 -0
  172. package/dist/server/prerendered.js.map +1 -0
  173. package/dist/server/server-config.d.ts +72 -0
  174. package/dist/server/server-config.d.ts.map +1 -0
  175. package/dist/server/server-config.js +94 -0
  176. package/dist/server/server-config.js.map +1 -0
  177. package/dist/server/shutdown.d.ts +7 -0
  178. package/dist/server/shutdown.d.ts.map +1 -0
  179. package/dist/server/shutdown.js +11 -0
  180. package/dist/server/shutdown.js.map +1 -0
  181. package/dist/server/ssg.d.ts +25 -0
  182. package/dist/server/ssg.d.ts.map +1 -0
  183. package/dist/server/ssg.js +160 -0
  184. package/dist/server/ssg.js.map +1 -0
  185. package/dist/server/static.d.ts +14 -0
  186. package/dist/server/static.d.ts.map +1 -0
  187. package/dist/server/static.js +30 -0
  188. package/dist/server/static.js.map +1 -0
  189. 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