@rshono/core 1.0.0-rc.12 → 1.0.0-rc.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/rshono.mjs +3 -4
- package/dist/builder/env-shadow-loader.cjs +5 -5
- package/dist/builder/page-files.js +5 -5
- package/dist/builder/page-files.js.map +1 -1
- package/dist/builder/public-env.d.ts +5 -4
- package/dist/builder/public-env.d.ts.map +1 -1
- package/dist/builder/public-env.js +5 -4
- package/dist/builder/public-env.js.map +1 -1
- package/dist/builder/rspack-config.d.ts +5 -11
- package/dist/builder/rspack-config.d.ts.map +1 -1
- package/dist/builder/rspack-config.js +28 -50
- package/dist/builder/rspack-config.js.map +1 -1
- package/dist/cli/build.js +2 -2
- package/dist/cli/build.js.map +1 -1
- package/dist/cli/dev.d.ts.map +1 -1
- package/dist/cli/dev.js +30 -40
- package/dist/cli/dev.js.map +1 -1
- package/dist/cli/index.js +4 -4
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/start.d.ts.map +1 -1
- package/dist/cli/start.js +2 -3
- package/dist/cli/start.js.map +1 -1
- package/dist/config.d.ts +28 -31
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +2 -2
- package/dist/config.js.map +1 -1
- package/dist/deploy/aws-lambda/runtime.d.ts +4 -6
- package/dist/deploy/aws-lambda/runtime.d.ts.map +1 -1
- package/dist/deploy/aws-lambda/runtime.js +5 -8
- package/dist/deploy/aws-lambda/runtime.js.map +1 -1
- package/dist/deploy/build-marker.d.ts +3 -5
- package/dist/deploy/build-marker.d.ts.map +1 -1
- package/dist/deploy/build-marker.js +3 -5
- package/dist/deploy/build-marker.js.map +1 -1
- package/dist/deploy/cloudflare/build.d.ts.map +1 -1
- package/dist/deploy/cloudflare/build.js +6 -10
- package/dist/deploy/cloudflare/build.js.map +1 -1
- package/dist/deploy/cloudflare/runtime.d.ts +2 -5
- package/dist/deploy/cloudflare/runtime.d.ts.map +1 -1
- package/dist/deploy/cloudflare/runtime.js +19 -32
- package/dist/deploy/cloudflare/runtime.js.map +1 -1
- package/dist/deploy/contract.d.ts +25 -42
- package/dist/deploy/contract.d.ts.map +1 -1
- package/dist/deploy/contract.js.map +1 -1
- package/dist/deploy/filesystem.d.ts +3 -5
- package/dist/deploy/filesystem.d.ts.map +1 -1
- package/dist/deploy/filesystem.js +7 -11
- package/dist/deploy/filesystem.js.map +1 -1
- package/dist/deploy/node/runtime.d.ts +4 -5
- package/dist/deploy/node/runtime.d.ts.map +1 -1
- package/dist/deploy/node/runtime.js +9 -15
- package/dist/deploy/node/runtime.js.map +1 -1
- package/dist/deploy/presets.d.ts +19 -29
- package/dist/deploy/presets.d.ts.map +1 -1
- package/dist/deploy/presets.js +18 -25
- package/dist/deploy/presets.js.map +1 -1
- package/dist/deploy/vercel/build.d.ts.map +1 -1
- package/dist/deploy/vercel/build.js +9 -12
- package/dist/deploy/vercel/build.js.map +1 -1
- package/dist/deploy/vercel/runtime.d.ts +4 -7
- package/dist/deploy/vercel/runtime.d.ts.map +1 -1
- package/dist/deploy/vercel/runtime.js +4 -7
- package/dist/deploy/vercel/runtime.js.map +1 -1
- package/dist/index.d.ts +13 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -12
- package/dist/index.js.map +1 -1
- package/dist/router.d.ts +65 -93
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +2 -5
- package/dist/router.js.map +1 -1
- package/dist/runtime/boundaries.d.ts +24 -30
- package/dist/runtime/boundaries.d.ts.map +1 -1
- package/dist/runtime/boundaries.js +15 -22
- package/dist/runtime/boundaries.js.map +1 -1
- package/dist/runtime/client.d.ts +16 -7
- package/dist/runtime/client.d.ts.map +1 -1
- package/dist/runtime/client.js +16 -7
- package/dist/runtime/client.js.map +1 -1
- package/dist/runtime/context.d.ts +89 -118
- package/dist/runtime/context.d.ts.map +1 -1
- package/dist/runtime/context.js +107 -167
- package/dist/runtime/context.js.map +1 -1
- package/dist/runtime/control.js +3 -3
- package/dist/runtime/control.js.map +1 -1
- package/dist/runtime/dev-protocol.d.ts +4 -8
- package/dist/runtime/dev-protocol.d.ts.map +1 -1
- package/dist/runtime/dev-protocol.js.map +1 -1
- package/dist/runtime/entry.client.js +69 -105
- package/dist/runtime/entry.client.js.map +1 -1
- package/dist/runtime/entry.rsc.d.ts +5 -6
- package/dist/runtime/entry.rsc.d.ts.map +1 -1
- package/dist/runtime/entry.rsc.js +67 -122
- package/dist/runtime/entry.rsc.js.map +1 -1
- package/dist/runtime/entry.ssr.d.ts +7 -12
- package/dist/runtime/entry.ssr.d.ts.map +1 -1
- package/dist/runtime/entry.ssr.js +13 -24
- package/dist/runtime/entry.ssr.js.map +1 -1
- package/dist/runtime/flight-inject.d.ts +10 -17
- package/dist/runtime/flight-inject.d.ts.map +1 -1
- package/dist/runtime/flight-inject.js +35 -53
- package/dist/runtime/flight-inject.js.map +1 -1
- package/dist/runtime/hot-update.d.ts +20 -27
- package/dist/runtime/hot-update.d.ts.map +1 -1
- package/dist/runtime/hot-update.js +17 -23
- package/dist/runtime/hot-update.js.map +1 -1
- package/dist/runtime/navigation.d.ts +14 -19
- package/dist/runtime/navigation.d.ts.map +1 -1
- package/dist/runtime/navigation.js +9 -13
- package/dist/runtime/navigation.js.map +1 -1
- package/dist/runtime/request.d.ts +4 -6
- package/dist/runtime/request.d.ts.map +1 -1
- package/dist/runtime/request.js +2 -3
- package/dist/runtime/request.js.map +1 -1
- package/dist/runtime/server.d.ts +18 -10
- package/dist/runtime/server.d.ts.map +1 -1
- package/dist/runtime/server.js +21 -19
- package/dist/runtime/server.js.map +1 -1
- package/dist/server/headers.d.ts +8 -15
- package/dist/server/headers.d.ts.map +1 -1
- package/dist/server/headers.js +8 -15
- package/dist/server/headers.js.map +1 -1
- package/dist/server/load-config.d.ts +2 -2
- package/dist/server/load-config.d.ts.map +1 -1
- package/dist/server/load-config.js +6 -9
- package/dist/server/load-config.js.map +1 -1
- package/dist/server/prerendered.d.ts +30 -43
- package/dist/server/prerendered.d.ts.map +1 -1
- package/dist/server/prerendered.js +20 -29
- package/dist/server/prerendered.js.map +1 -1
- package/dist/server/server-config.d.ts +13 -28
- package/dist/server/server-config.d.ts.map +1 -1
- package/dist/server/server-config.js +5 -10
- package/dist/server/server-config.js.map +1 -1
- package/dist/server/shutdown.d.ts +2 -3
- package/dist/server/shutdown.d.ts.map +1 -1
- package/dist/server/shutdown.js +2 -3
- package/dist/server/shutdown.js.map +1 -1
- package/dist/server/ssg.d.ts +3 -6
- package/dist/server/ssg.d.ts.map +1 -1
- package/dist/server/ssg.js +11 -20
- package/dist/server/ssg.js.map +1 -1
- package/package.json +1 -1
package/dist/router.d.ts
CHANGED
|
@@ -7,11 +7,11 @@ type Simplify<T> = {
|
|
|
7
7
|
} & {};
|
|
8
8
|
type UnionToIntersection<U> = (U extends unknown ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
|
|
9
9
|
/**
|
|
10
|
-
* The `params` record implied by a route path pattern — one required `string` key
|
|
11
|
-
*
|
|
10
|
+
* The `params` record implied by a route path pattern — one required `string` key per `:param`
|
|
11
|
+
* segment, `Record<string, never>` for a path with none. Paths use Hono's syntax, so `:id`,
|
|
12
|
+
* `:id{[0-9]+}` and `*` all work.
|
|
12
13
|
*
|
|
13
|
-
*
|
|
14
|
-
* name this type directly; {@link PageProps} applies it for you.
|
|
14
|
+
* You rarely name this directly; {@link PageProps} applies it for you.
|
|
15
15
|
*
|
|
16
16
|
* @typeParam P - The literal route path, e.g. `'/users/:id/posts/:postId'`.
|
|
17
17
|
*
|
|
@@ -24,16 +24,12 @@ type UnionToIntersection<U> = (U extends unknown ? (k: U) => void : never) exten
|
|
|
24
24
|
*/
|
|
25
25
|
export type PathParams<P extends string> = ParamKeys<P> extends never ? Record<string, never> : Simplify<UnionToIntersection<ParamKeyToRecord<ParamKeys<P>>>>;
|
|
26
26
|
/**
|
|
27
|
-
* Props every page component receives. Pass the route's path as the type
|
|
28
|
-
*
|
|
29
|
-
* an open `Record<string, string>`.
|
|
27
|
+
* Props every page component receives. Pass the route's path as the type argument to get `params`
|
|
28
|
+
* typed key-by-key; without it `params` falls back to an open `Record<string, string>`.
|
|
30
29
|
*
|
|
31
|
-
* `defineRoutes` checks each page's props against `PageProps<path
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* The location props (`url` and `params`) mirror what a `'use client'` component
|
|
35
|
-
* gets from `useNavigation()` — same names, same types — so moving a read across
|
|
36
|
-
* the server/client line is a copy-paste.
|
|
30
|
+
* `defineRoutes` checks each page's props against `PageProps<path>`, so a mismatched path literal is
|
|
31
|
+
* a type error at the route definition. `url` and `params` mirror what `useNavigation()` gives a
|
|
32
|
+
* `'use client'` component, so a read moves across the server/client line unchanged.
|
|
37
33
|
*
|
|
38
34
|
* @typeParam Path - The literal path this page is mounted at, e.g. `'/profile/:id'`.
|
|
39
35
|
* @typeParam E - The app's Hono {@link Env}, to type {@link RequestContext.var} and
|
|
@@ -54,40 +50,27 @@ export type PathParams<P extends string> = ParamKeys<P> extends never ? Record<s
|
|
|
54
50
|
*/
|
|
55
51
|
export interface PageProps<Path extends string = string, E extends Env = Env> {
|
|
56
52
|
/**
|
|
57
|
-
* The absolute browser-facing request {@link URL}, proxy-header aware
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
* A fresh instance per request that nothing else holds, so mutating it is local
|
|
62
|
-
* to the page. It is not serializable, so a `'use client'` component has to be
|
|
63
|
-
* handed `url.href` rather than `url`.
|
|
53
|
+
* The absolute browser-facing request {@link URL}, proxy-header aware (`X-Forwarded-Host` /
|
|
54
|
+
* `-Proto`). A fresh instance per request, so mutating it is local to the page; it is not
|
|
55
|
+
* serializable, so hand a `'use client'` component `url.href` rather than `url`.
|
|
64
56
|
*
|
|
65
|
-
* On a `render: 'static'` route this is the build-time URL — rendered once
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
* read the query from `useNavigation().url` in a `'use client'` component
|
|
69
|
-
* instead, or mark the route `render: 'dynamic'`.
|
|
57
|
+
* On a `render: 'static'` route this is the build-time URL — rendered once against `siteUrl`, so
|
|
58
|
+
* `url.searchParams` is always empty. Read the query from `useNavigation().url` in a `'use client'`
|
|
59
|
+
* component instead, or mark the route `render: 'dynamic'`.
|
|
70
60
|
*/
|
|
71
61
|
url: URL;
|
|
72
62
|
/** Matched route params for this request, e.g. `{ id: '42' }` for `/profile/:id`. */
|
|
73
63
|
params: string extends Path ? Record<string, string> : PathParams<Path>;
|
|
74
64
|
/**
|
|
75
|
-
* The request context — the
|
|
76
|
-
*
|
|
77
|
-
* an import.
|
|
65
|
+
* The request context — the object `getRequestContext()` returns, handed to the page so cookies,
|
|
66
|
+
* headers, env and middleware variables are reachable without an import.
|
|
78
67
|
*
|
|
79
|
-
* Server-only
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
* skip it. Handing it to a `'use client'` component directly
|
|
83
|
-
* (`<Counter ctx={ctx} />`) fails the render — it wraps the live request and
|
|
84
|
-
* response, which do not exist in the browser. Read what you need here and pass
|
|
85
|
-
* plain values down.
|
|
68
|
+
* Server-only, non-enumerable (a `{...props}` spread and `JSON.stringify` both skip it) and never
|
|
69
|
+
* serialized. Passing it to a `'use client'` component fails the render, because it wraps the live
|
|
70
|
+
* request — read what you need here and pass plain values down.
|
|
86
71
|
*
|
|
87
|
-
* Reading it on a `render: 'static'` route throws: a prerendered page has no
|
|
88
|
-
*
|
|
89
|
-
* the `url` / `params` props — available either way, with the build-time caveat
|
|
90
|
-
* noted on `url`.
|
|
72
|
+
* Reading it on a `render: 'static'` route throws: a prerendered page has no per-request context.
|
|
73
|
+
* Mark the route `render: 'dynamic'`, or use the `url` / `params` props.
|
|
91
74
|
*
|
|
92
75
|
* @example
|
|
93
76
|
* ```tsx
|
|
@@ -101,12 +84,11 @@ export interface PageProps<Path extends string = string, E extends Env = Env> {
|
|
|
101
84
|
ctx: RequestContext<E>;
|
|
102
85
|
}
|
|
103
86
|
/**
|
|
104
|
-
* A page: a React **server component**
|
|
105
|
-
*
|
|
106
|
-
* data directly.
|
|
87
|
+
* A page: a React **server component** rendering the entire document (`<html>…</html>`), usually via
|
|
88
|
+
* a shared layout. It may be `async` and await data directly.
|
|
107
89
|
*
|
|
108
|
-
* Each page module
|
|
109
|
-
*
|
|
90
|
+
* Each page module default-exports exactly one. Interactive parts belong in `'use client'` components
|
|
91
|
+
* the page imports — only those ship JS.
|
|
110
92
|
*
|
|
111
93
|
* @typeParam P - The component's props; for a page these are {@link PageProps}.
|
|
112
94
|
*
|
|
@@ -115,9 +97,9 @@ export interface PageProps<Path extends string = string, E extends Env = Env> {
|
|
|
115
97
|
*/
|
|
116
98
|
export type PageComponent<P = any> = (props: P) => ReactNode | Promise<ReactNode>;
|
|
117
99
|
/**
|
|
118
|
-
* The shape an `{ type: 'endpoint' }` route's server module must have: a single
|
|
119
|
-
*
|
|
120
|
-
* safe
|
|
100
|
+
* The shape an `{ type: 'endpoint' }` route's server module must have: a single named `handler`
|
|
101
|
+
* export. It only ever loads on the server, so importing a database client or reading secrets from it
|
|
102
|
+
* is safe.
|
|
121
103
|
*
|
|
122
104
|
* @example
|
|
123
105
|
* ```ts
|
|
@@ -131,9 +113,9 @@ export type PageComponent<P = any> = (props: P) => ReactNode | Promise<ReactNode
|
|
|
131
113
|
*/
|
|
132
114
|
export interface EndpointServerModule {
|
|
133
115
|
/**
|
|
134
|
-
* A Hono {@link Handler}
|
|
135
|
-
*
|
|
136
|
-
*
|
|
116
|
+
* A Hono {@link Handler} for every request the route matches. It is passed Hono's `Context`, so the
|
|
117
|
+
* request, the response builders (`c.json`, `c.text`, `c.body`) and middleware variables are all
|
|
118
|
+
* reached through it.
|
|
137
119
|
*
|
|
138
120
|
* @see {@link https://hono.dev/docs/api/context | Hono — Context}
|
|
139
121
|
*/
|
|
@@ -159,16 +141,17 @@ export interface PageRoute {
|
|
|
159
141
|
*/
|
|
160
142
|
path: string;
|
|
161
143
|
/**
|
|
162
|
-
* Dynamic import of the page module, whose default export is the
|
|
163
|
-
* {@link PageComponent}.
|
|
144
|
+
* Dynamic import of the page module, whose default export is the {@link PageComponent}.
|
|
164
145
|
*
|
|
165
|
-
* Write it inline as
|
|
166
|
-
* `
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
146
|
+
* Write it inline as `() => import('…')`: the framework detects that exact form and injects the
|
|
147
|
+
* `'use server-entry'` directive that attaches the page's client JS and CSS. Wire the component up
|
|
148
|
+
* any other way — a variable, a barrel re-export, a computed specifier — and you have to put
|
|
149
|
+
* `'use server-entry'` on the page module's first line yourself.
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* ```ts
|
|
153
|
+
* component: () => import('./components/profile')
|
|
154
|
+
* ```
|
|
172
155
|
*
|
|
173
156
|
* @see {@link https://www.rshono.com/docs/pages#the-use-server-entry-directive | Docs — the `'use server-entry'` directive}
|
|
174
157
|
*/
|
|
@@ -178,13 +161,11 @@ export interface PageRoute {
|
|
|
178
161
|
/** `'static'` prerenders the route at build time; `'dynamic'` (the default) renders per request. */
|
|
179
162
|
render?: 'static' | 'dynamic';
|
|
180
163
|
/**
|
|
181
|
-
* For a `render: 'static'` route with params: the param sets to prerender, one
|
|
182
|
-
*
|
|
183
|
-
* database or read the filesystem.
|
|
164
|
+
* For a `render: 'static'` route with params: the param sets to prerender, one page each. Runs at
|
|
165
|
+
* build time on the server, so it may hit a database or read the filesystem.
|
|
184
166
|
*
|
|
185
|
-
* A parameterised static route without
|
|
186
|
-
*
|
|
187
|
-
* params can't be prerendered.
|
|
167
|
+
* A parameterised static route without this falls back to rendering per request, with a build
|
|
168
|
+
* warning. Wildcard (`*`), optional and regex params cannot be prerendered.
|
|
188
169
|
*
|
|
189
170
|
* @example
|
|
190
171
|
* ```ts
|
|
@@ -231,19 +212,15 @@ export type HTTPMethod = 'get' | 'post' | 'put' | 'patch' | 'delete' | 'head' |
|
|
|
231
212
|
/** Any entry in the `routes` array: a {@link PageRoute} or an {@link EndpointRoute}. */
|
|
232
213
|
export type Route = PageRoute | EndpointRoute;
|
|
233
214
|
/**
|
|
234
|
-
*
|
|
235
|
-
*
|
|
236
|
-
*
|
|
237
|
-
* Framework internal — not re-exported from `index.ts`. The request renderer and
|
|
238
|
-
* the SSG pass use it to split the route table.
|
|
215
|
+
* Narrows a {@link Route} to a {@link PageRoute} — `type` is optional on page routes, so anything not
|
|
216
|
+
* explicitly `'endpoint'` is one.
|
|
239
217
|
*
|
|
240
218
|
* @internal
|
|
241
219
|
*/
|
|
242
220
|
export declare function isPageRoute(route: Route): route is PageRoute;
|
|
243
221
|
/**
|
|
244
|
-
* A page the framework falls back to rather than routes to — `notFound` and
|
|
245
|
-
*
|
|
246
|
-
* `component`, without a path of its own.
|
|
222
|
+
* A page the framework falls back to rather than routes to — `notFound` and `error` in
|
|
223
|
+
* {@link RouteConfig}. Same contract as a {@link PageRoute} `component`, without a path of its own.
|
|
247
224
|
*/
|
|
248
225
|
export interface FallbackPage {
|
|
249
226
|
/** Dynamic import of the page module; its default export is the {@link PageComponent}. */
|
|
@@ -252,9 +229,8 @@ export interface FallbackPage {
|
|
|
252
229
|
}>;
|
|
253
230
|
}
|
|
254
231
|
/**
|
|
255
|
-
* The error detail handed to the `error` page. Redacted in production
|
|
256
|
-
*
|
|
257
|
-
* you get the real message plus the stack.
|
|
232
|
+
* The error detail handed to the `error` page. Redacted in production — a generic
|
|
233
|
+
* `'Internal Server Error'` and no `stack`; in dev, the real message and stack.
|
|
258
234
|
*/
|
|
259
235
|
export interface ErrorPageInfo {
|
|
260
236
|
/** The thrown error's message in dev; `'Internal Server Error'` in production. */
|
|
@@ -263,8 +239,8 @@ export interface ErrorPageInfo {
|
|
|
263
239
|
stack?: string;
|
|
264
240
|
}
|
|
265
241
|
/**
|
|
266
|
-
* Props for the `error` page declared in {@link RouteConfig.error} — the usual
|
|
267
|
-
*
|
|
242
|
+
* Props for the `error` page declared in {@link RouteConfig.error} — the usual {@link PageProps} plus
|
|
243
|
+
* the redaction-aware {@link ErrorPageInfo}.
|
|
268
244
|
*
|
|
269
245
|
* @typeParam E - The app's Hono {@link Env}, forwarded to {@link PageProps.ctx}.
|
|
270
246
|
*
|
|
@@ -303,29 +279,25 @@ type ValidateRoute<R> = R extends {
|
|
|
303
279
|
component: () => Promise<{
|
|
304
280
|
default: PageComponent<infer CP>;
|
|
305
281
|
}>;
|
|
306
|
-
} ? [
|
|
282
|
+
} ? [
|
|
283
|
+
PageProps<P, any>
|
|
284
|
+
] extends [CP] ? R : R & {
|
|
307
285
|
component: `component props are not satisfied by PageProps<'${P}'>`;
|
|
308
286
|
} : R;
|
|
309
287
|
type ValidateRoutes<TRoutes extends readonly Route[]> = {
|
|
310
288
|
[K in keyof TRoutes]: ValidateRoute<TRoutes[K]>;
|
|
311
289
|
};
|
|
312
290
|
/**
|
|
313
|
-
* Declares the app's route table.
|
|
314
|
-
*
|
|
315
|
-
*
|
|
316
|
-
* `routes.ts` only ever runs on the server, so importing server-only modules
|
|
317
|
-
* from it (e.g. inside `staticPaths`) is safe.
|
|
318
|
-
*
|
|
319
|
-
* Beyond typing the config, this cross-checks every page against its own path:
|
|
320
|
-
* if a component's props aren't satisfied by `PageProps<'<its path>'>`, the
|
|
321
|
-
* `component` field errors with `component props are not satisfied by
|
|
322
|
-
* PageProps<'/…'>`. Fix it by matching the page's `PageProps<Path>` type
|
|
323
|
-
* argument to the path it's mounted at.
|
|
291
|
+
* Declares the app's route table. Export the result as `routes` from `src/routes.ts` — the one file
|
|
292
|
+
* rshono requires. It only ever runs on the server, so importing server-only modules from it (inside
|
|
293
|
+
* `staticPaths`, say) is safe.
|
|
324
294
|
*
|
|
325
|
-
*
|
|
295
|
+
* Beyond typing the config, every page is cross-checked against its own path: props not satisfied by
|
|
296
|
+
* `PageProps<'<its path>'>` make the `component` field a type error. A bare {@link Route} array is
|
|
297
|
+
* accepted as shorthand — see the second overload.
|
|
326
298
|
*
|
|
327
|
-
* @param config - A {@link RouteConfig}: the `routes` array plus the optional
|
|
328
|
-
*
|
|
299
|
+
* @param config - A {@link RouteConfig}: the `routes` array plus the optional `notFound` and `error`
|
|
300
|
+
* pages.
|
|
329
301
|
* @returns The config, unchanged and fully typed.
|
|
330
302
|
*
|
|
331
303
|
* @example
|
package/dist/router.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AACzC,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC9D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"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"}
|
package/dist/router.js
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* Framework internal — not re-exported from `index.ts`. The request renderer and
|
|
6
|
-
* the SSG pass use it to split the route table.
|
|
2
|
+
* Narrows a {@link Route} to a {@link PageRoute} — `type` is optional on page routes, so anything not
|
|
3
|
+
* explicitly `'endpoint'` is one.
|
|
7
4
|
*
|
|
8
5
|
* @internal
|
|
9
6
|
*/
|
package/dist/router.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"router.js","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AAiPA;;;;;;;;GAQG;AACH,MAAM,UAAU,WAAW,CAAC,KAAY;IACtC,OAAO,KAAK,CAAC,IAAI,KAAK,UAAU,CAAC;AACnC,CAAC;AA0ID,MAAM,UAAU,YAAY,CAAC,KAAqC;IAChE,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC,CAAE,KAAqB,CAAC;AAC3E,CAAC","sourcesContent":["import type { Env, Handler } from 'hono';\nimport type { ParamKeys, ParamKeyToRecord } from 'hono/types';\nimport type { ReactNode } from 'react';\n// Type-only, so this stays a build-time module: the import is erased and none of `context.ts`'s\n// runtime machinery (AsyncLocalStorage, hono/cookie) is pulled in by importing `@rshono/core`.\nimport type { RequestContext } from './runtime/context.js';\n\ntype Simplify<T> = { [K in keyof T]: T[K] } & {};\ntype UnionToIntersection<U> = (U extends unknown ? (k: U) => void : never) extends (k: infer I) => void ? I : never;\n\n/**\n * The `params` record implied by a route path pattern — one required `string` key\n * per `:param` segment, `Record<string, never>` for a path with no params.\n *\n * Paths use Hono's syntax, so `:id`, `:id{[0-9]+}` and `*` all work. You rarely\n * name this type directly; {@link PageProps} applies it for you.\n *\n * @typeParam P - The literal route path, e.g. `'/users/:id/posts/:postId'`.\n *\n * @example\n * ```ts\n * type P = PathParams<'/users/:id/posts/:postId'>; // { id: string; postId: string }\n * ```\n *\n * @see {@link https://hono.dev/docs/api/routing#path-parameter | Hono — path parameters}\n */\nexport type PathParams<P extends string> =\n ParamKeys<P> extends never ? Record<string, never> : Simplify<UnionToIntersection<ParamKeyToRecord<ParamKeys<P>>>>;\n\n/**\n * Props every page component receives. Pass the route's path as the type\n * argument to get `params` typed key-by-key; without it `params` falls back to\n * an open `Record<string, string>`.\n *\n * `defineRoutes` checks each page's props against `PageProps<path>` at compile\n * time, so a mismatched path literal is a type error at the route definition.\n *\n * The location props (`url` and `params`) mirror what a `'use client'` component\n * gets from `useNavigation()` — same names, same types — so moving a read across\n * the server/client line is a copy-paste.\n *\n * @typeParam Path - The literal path this page is mounted at, e.g. `'/profile/:id'`.\n * @typeParam E - The app's Hono {@link Env}, to type {@link RequestContext.var} and\n * {@link RequestContext.env} on {@link PageProps.ctx}.\n *\n * @example\n * ```tsx\n * import type { PageProps } from '@rshono/core';\n *\n * export default async function Profile({ params, url }: PageProps<'/profile/:id'>) {\n * const user = await db.getUser(params.id); // params.id is string\n * const tab = url.searchParams.get('tab') ?? 'overview';\n * return <Layout>{user.name} — {tab}</Layout>;\n * }\n * ```\n *\n * @see {@link https://www.rshono.com/docs/pages#page-props | Docs — page props}\n */\nexport interface PageProps<Path extends string = string, E extends Env = Env> {\n /**\n * The absolute browser-facing request {@link URL}, proxy-header aware\n * (`X-Forwarded-Host` / `-Proto`). Read `url.pathname`, `url.searchParams` and\n * the rest off it.\n *\n * A fresh instance per request that nothing else holds, so mutating it is local\n * to the page. It is not serializable, so a `'use client'` component has to be\n * handed `url.href` rather than `url`.\n *\n * On a `render: 'static'` route this is the build-time URL — rendered once\n * against `siteUrl` with no query string, and that one file answers every\n * request whatever its own query. So `url.searchParams` is always empty there:\n * read the query from `useNavigation().url` in a `'use client'` component\n * instead, or mark the route `render: 'dynamic'`.\n */\n url: URL;\n /** Matched route params for this request, e.g. `{ id: '42' }` for `/profile/:id`. */\n params: string extends Path ? Record<string, string> : PathParams<Path>;\n /**\n * The request context — the very object `getRequestContext()` returns, handed to the\n * page so cookies, headers, env and middleware variables are reachable without\n * an import.\n *\n * Server-only and never serialized: React puts a server component's *output* on\n * the wire, not its props. It is also deliberately **non-enumerable**, so\n * `Object.keys(props)`, `JSON.stringify(props)` and a `{...props}` spread all\n * skip it. Handing it to a `'use client'` component directly\n * (`<Counter ctx={ctx} />`) fails the render — it wraps the live request and\n * response, which do not exist in the browser. Read what you need here and pass\n * plain values down.\n *\n * Reading it on a `render: 'static'` route throws: a prerendered page has no\n * per-request context at build time. Mark the route `render: 'dynamic'`, or use\n * the `url` / `params` props — available either way, with the build-time caveat\n * noted on `url`.\n *\n * @example\n * ```tsx\n * export default function Dashboard({ ctx }: PageProps) {\n * const session = ctx.cookies.get('session');\n * if (!session) redirect('/login');\n * return <Layout>Signed in as {session}</Layout>;\n * }\n * ```\n */\n ctx: RequestContext<E>;\n}\n\n/**\n * A page: a React **server component** that renders the entire document\n * (`<html>…</html>`), usually via a shared layout. It may be `async` and await\n * data directly.\n *\n * Each page module must default-export exactly one of these. Interactive parts\n * belong in `'use client'` components the page imports — only those ship JS.\n *\n * @typeParam P - The component's props; for a page these are {@link PageProps}.\n *\n * @see {@link https://react.dev/reference/rsc/server-components | React — Server Components}\n * @see {@link https://www.rshono.com/docs/pages | Docs — pages}\n */\nexport type PageComponent<P = any> = (props: P) => ReactNode | Promise<ReactNode>;\n\n/**\n * The shape an `{ type: 'endpoint' }` route's server module must have: a single\n * named `handler` export. The module only ever loads on the server, so it is\n * safe to import a database client or read secrets from it.\n *\n * @example\n * ```ts\n * // src/health.ts\n * import type { Handler } from 'hono';\n *\n * export const handler: Handler = (c) => c.json({ ok: true });\n * ```\n *\n * @see {@link https://www.rshono.com/docs/routing#endpoint-routes | Docs — endpoint routes}\n */\nexport interface EndpointServerModule {\n /**\n * A Hono {@link Handler} handling every request matched by the route. It is passed Hono's\n * `Context`, so the request, response builders (`c.json`, `c.text`, `c.body`) and middleware\n * variables are all reached through it.\n *\n * @see {@link https://hono.dev/docs/api/context | Hono — Context}\n */\n handler: Handler;\n}\n\n/**\n * A page route — a path rendered by a server component. This is the default\n * route kind, so `type` can be omitted.\n *\n * @example\n * ```ts\n * { path: '/profile/:id', component: () => import('./components/profile') }\n * ```\n */\nexport interface PageRoute {\n /** Discriminates a page from an endpoint; optional because `'page'` is the default. */\n type?: 'page';\n /**\n * Hono-style path pattern, e.g. `/`, `/profile/:id`, `/files/*`. Routes are matched in\n * declaration order.\n *\n * @see {@link https://hono.dev/docs/api/routing | Hono — routing}\n */\n path: string;\n /**\n * Dynamic import of the page module, whose default export is the\n * {@link PageComponent}.\n *\n * Write it inline as shown — the framework detects that exact\n * `() => import('…')` form and injects Rspack's `'use server-entry'`\n * directive into the module for you (that directive is what attaches the\n * page's client JS/CSS, giving per-page code splitting). If you wire the\n * component up any other way — a variable, a barrel re-export, a computed\n * specifier — add `'use server-entry'` as the first line of the page module\n * yourself; the framework throws a descriptive error when neither happened.\n *\n * @see {@link https://www.rshono.com/docs/pages#the-use-server-entry-directive | Docs — the `'use server-entry'` directive}\n */\n component: () => Promise<{ default: PageComponent }>;\n /** `'static'` prerenders the route at build time; `'dynamic'` (the default) renders per request. */\n render?: 'static' | 'dynamic';\n /**\n * For a `render: 'static'` route with params: the param sets to prerender, one\n * HTML file each. Runs at build time only, on the server, so it may hit a\n * database or read the filesystem.\n *\n * A parameterised static route without `staticPaths` falls back to rendering\n * per request (with a build warning). Wildcard (`*`), optional and regex\n * params can't be prerendered.\n *\n * @example\n * ```ts\n * {\n * path: '/docs/:slug',\n * render: 'static',\n * component: () => import('./components/documentation'),\n * staticPaths: async () => (await db.docs.all()).map((d) => ({ slug: d.slug })),\n * }\n * ```\n *\n * @see {@link https://www.rshono.com/docs/routing#static-rendering | Docs — static rendering}\n */\n staticPaths?: () => Array<Record<string, string>> | Promise<Array<Record<string, string>>>;\n}\n\n/**\n * An endpoint route — a path served by a raw Hono handler instead of a React\n * component. Use it for JSON APIs, webhooks, redirects, feeds, or anything that\n * isn't an HTML page.\n *\n * @example\n * ```ts\n * { type: 'endpoint', path: '/api/health', server: () => import('./health') }\n * ```\n *\n * @see {@link https://www.rshono.com/docs/routing#endpoint-routes | Docs — endpoint routes}\n */\nexport interface EndpointRoute {\n /** Marks this route as an endpoint rather than a page. Required. */\n type: 'endpoint';\n /**\n * Hono-style path pattern, e.g. `/api/health`, `/api/users/:id`.\n *\n * @see {@link https://hono.dev/docs/api/routing | Hono — routing}\n */\n path: string;\n /** HTTP method to match. Defaults to `'all'` — every method. */\n method?: HTTPMethod;\n /** Dynamic import of the {@link EndpointServerModule} exporting `handler`. */\n server: () => Promise<EndpointServerModule>;\n}\n\n/** HTTP methods an {@link EndpointRoute} can match. `'all'` matches every method. */\nexport type HTTPMethod = 'get' | 'post' | 'put' | 'patch' | 'delete' | 'head' | 'options' | 'all';\n\n/** Any entry in the `routes` array: a {@link PageRoute} or an {@link EndpointRoute}. */\nexport type Route = PageRoute | EndpointRoute;\n\n/**\n * Type guard narrowing a {@link Route} to a {@link PageRoute}. Because `type` is\n * optional on page routes, anything not explicitly `'endpoint'` is a page.\n *\n * Framework internal — not re-exported from `index.ts`. The request renderer and\n * the SSG pass use it to split the route table.\n *\n * @internal\n */\nexport function isPageRoute(route: Route): route is PageRoute {\n return route.type !== 'endpoint';\n}\n\n/**\n * A page the framework falls back to rather than routes to — `notFound` and\n * `error` in {@link RouteConfig}. Same contract as a {@link PageRoute}\n * `component`, without a path of its own.\n */\nexport interface FallbackPage {\n /** Dynamic import of the page module; its default export is the {@link PageComponent}. */\n component: () => Promise<{ default: PageComponent }>;\n}\n\n/**\n * The error detail handed to the `error` page. Redacted in production: the\n * message is a generic `'Internal Server Error'` and there is no `stack`. In dev\n * you get the real message plus the stack.\n */\nexport interface ErrorPageInfo {\n /** The thrown error's message in dev; `'Internal Server Error'` in production. */\n message: string;\n /** The stack trace. Present in dev only. */\n stack?: string;\n}\n\n/**\n * Props for the `error` page declared in {@link RouteConfig.error} — the usual\n * {@link PageProps} plus the redaction-aware {@link ErrorPageInfo}.\n *\n * @typeParam E - The app's Hono {@link Env}, forwarded to {@link PageProps.ctx}.\n *\n * @example\n * ```tsx\n * import type { ErrorPageProps } from '@rshono/core';\n *\n * export default function ServerError({ error }: ErrorPageProps) {\n * return <html><body><h1>Something went wrong</h1><p>{error.message}</p></body></html>;\n * }\n * ```\n */\nexport type ErrorPageProps<E extends Env = Env> = PageProps<string, E> & {\n /** The error that failed the request, redacted in production — see {@link ErrorPageInfo}. */\n error: ErrorPageInfo;\n};\n\n/**\n * The object form accepted by {@link defineRoutes}: the route table plus the two\n * optional framework-owned pages.\n *\n * @typeParam TRoutes - Inferred tuple of route literals, which is what makes the\n * per-route `path` → props check possible.\n *\n * @see {@link https://www.rshono.com/docs/routing#notfound-and-error | Docs — notFound and error pages}\n */\nexport interface RouteConfig<TRoutes extends readonly Route[] = readonly Route[]> {\n /** Every page and endpoint in the app, matched in order. */\n routes: TRoutes;\n /** Page rendered with a 404 status for unmatched paths and for `notFound()` calls. */\n notFound?: FallbackPage;\n /** Page rendered with a 500 status when a request throws. Receives {@link ErrorPageProps}. */\n error?: FallbackPage;\n}\n\n// `PageProps<P, any>`, not `PageProps<P>`: this checks the *path* against the page's `params`, and\n// pinning the Env to the default would additionally demand that a page declaring its own\n// (`PageProps<'/x', MyEnv>`, to type `ctx.var`) accept a `RequestContext<Env>` — which it doesn't, so\n// every such page would fail its own route check. `any` makes `ctx` compatible either way.\ntype ValidateRoute<R> = R extends {\n path: infer P extends string;\n component: () => Promise<{ default: PageComponent<infer CP> }>;\n}\n ? [PageProps<P, any>] extends [CP]\n ? R\n : R & { component: `component props are not satisfied by PageProps<'${P}'>` }\n : R;\n\ntype ValidateRoutes<TRoutes extends readonly Route[]> = { [K in keyof TRoutes]: ValidateRoute<TRoutes[K]> };\n\n/**\n * Declares the app's route table. Default-export the result as `routes` from\n * `src/routes.ts` — the one file rshono requires.\n *\n * `routes.ts` only ever runs on the server, so importing server-only modules\n * from it (e.g. inside `staticPaths`) is safe.\n *\n * Beyond typing the config, this cross-checks every page against its own path:\n * if a component's props aren't satisfied by `PageProps<'<its path>'>`, the\n * `component` field errors with `component props are not satisfied by\n * PageProps<'/…'>`. Fix it by matching the page's `PageProps<Path>` type\n * argument to the path it's mounted at.\n *\n * A bare {@link Route} array is accepted as shorthand — see the second overload.\n *\n * @param config - A {@link RouteConfig}: the `routes` array plus the optional\n * `notFound` and `error` pages.\n * @returns The config, unchanged and fully typed.\n *\n * @example\n * ```ts\n * // src/routes.ts\n * import { defineRoutes } from '@rshono/core';\n *\n * export const routes = defineRoutes({\n * routes: [\n * { path: '/', component: () => import('./components/home') },\n * { path: '/profile/:id', component: () => import('./components/profile') },\n * {\n * path: '/docs/:slug',\n * render: 'static',\n * component: () => import('./components/documentation'),\n * staticPaths: async () => [{ slug: 'getting-started' }, { slug: 'deployment' }],\n * },\n * { type: 'endpoint', path: '/api/health', server: () => import('./health') },\n * ],\n * notFound: { component: () => import('./components/404') },\n * error: { component: () => import('./components/500') },\n * });\n * ```\n *\n * @see {@link https://www.rshono.com/docs/routing | Docs — routing}\n */\nexport function defineRoutes<const TRoutes extends readonly Route[]>(\n config: RouteConfig<TRoutes> & { routes: ValidateRoutes<TRoutes> },\n): RouteConfig<TRoutes>;\n/**\n * Array shorthand for {@link defineRoutes} — equivalent to `defineRoutes({ routes })`, for an app\n * with no `notFound` or `error` page.\n *\n * @param routes - The {@link Route} array; each page is checked against its own `path`.\n * @returns A {@link RouteConfig} wrapping them.\n *\n * @example\n * ```ts\n * export const routes = defineRoutes([{ path: '/', component: () => import('./components/home') }]);\n * ```\n *\n * @see {@link https://www.rshono.com/docs/routing | Docs — routing}\n */\nexport function defineRoutes<const TRoutes extends readonly Route[]>(routes: TRoutes & ValidateRoutes<TRoutes>): RouteConfig<TRoutes>;\nexport function defineRoutes(input: readonly Route[] | RouteConfig): RouteConfig {\n return Array.isArray(input) ? { routes: input } : (input as RouteConfig);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"router.js","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AAgOA;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,KAAY;IACtC,OAAO,KAAK,CAAC,IAAI,KAAK,UAAU,CAAC;AACnC,CAAC;AAiID,MAAM,UAAU,YAAY,CAAC,KAAqC;IAChE,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC,CAAE,KAAqB,CAAC;AAC3E,CAAC","sourcesContent":["import type { Env, Handler } from 'hono';\nimport type { ParamKeys, ParamKeyToRecord } from 'hono/types';\nimport type { ReactNode } from 'react';\n// Type-only, so importing `@rshono/core` pulls in none of `context.ts`'s runtime machinery.\nimport type { RequestContext } from './runtime/context.js';\n\ntype Simplify<T> = { [K in keyof T]: T[K] } & {};\ntype UnionToIntersection<U> = (U extends unknown ? (k: U) => void : never) extends (k: infer I) => void ? I : never;\n\n/**\n * The `params` record implied by a route path pattern — one required `string` key per `:param`\n * segment, `Record<string, never>` for a path with none. Paths use Hono's syntax, so `:id`,\n * `:id{[0-9]+}` and `*` all work.\n *\n * You rarely name this directly; {@link PageProps} applies it for you.\n *\n * @typeParam P - The literal route path, e.g. `'/users/:id/posts/:postId'`.\n *\n * @example\n * ```ts\n * type P = PathParams<'/users/:id/posts/:postId'>; // { id: string; postId: string }\n * ```\n *\n * @see {@link https://hono.dev/docs/api/routing#path-parameter | Hono — path parameters}\n */\nexport type PathParams<P extends string> =\n ParamKeys<P> extends never ? Record<string, never> : Simplify<UnionToIntersection<ParamKeyToRecord<ParamKeys<P>>>>;\n\n/**\n * Props every page component receives. Pass the route's path as the type argument to get `params`\n * typed key-by-key; without it `params` falls back to an open `Record<string, string>`.\n *\n * `defineRoutes` checks each page's props against `PageProps<path>`, so a mismatched path literal is\n * a type error at the route definition. `url` and `params` mirror what `useNavigation()` gives a\n * `'use client'` component, so a read moves across the server/client line unchanged.\n *\n * @typeParam Path - The literal path this page is mounted at, e.g. `'/profile/:id'`.\n * @typeParam E - The app's Hono {@link Env}, to type {@link RequestContext.var} and\n * {@link RequestContext.env} on {@link PageProps.ctx}.\n *\n * @example\n * ```tsx\n * import type { PageProps } from '@rshono/core';\n *\n * export default async function Profile({ params, url }: PageProps<'/profile/:id'>) {\n * const user = await db.getUser(params.id); // params.id is string\n * const tab = url.searchParams.get('tab') ?? 'overview';\n * return <Layout>{user.name} — {tab}</Layout>;\n * }\n * ```\n *\n * @see {@link https://www.rshono.com/docs/pages#page-props | Docs — page props}\n */\nexport interface PageProps<Path extends string = string, E extends Env = Env> {\n /**\n * The absolute browser-facing request {@link URL}, proxy-header aware (`X-Forwarded-Host` /\n * `-Proto`). A fresh instance per request, so mutating it is local to the page; it is not\n * serializable, so hand a `'use client'` component `url.href` rather than `url`.\n *\n * On a `render: 'static'` route this is the build-time URL — rendered once against `siteUrl`, so\n * `url.searchParams` is always empty. Read the query from `useNavigation().url` in a `'use client'`\n * component instead, or mark the route `render: 'dynamic'`.\n */\n url: URL;\n /** Matched route params for this request, e.g. `{ id: '42' }` for `/profile/:id`. */\n params: string extends Path ? Record<string, string> : PathParams<Path>;\n /**\n * The request context — the object `getRequestContext()` returns, handed to the page so cookies,\n * headers, env and middleware variables are reachable without an import.\n *\n * Server-only, non-enumerable (a `{...props}` spread and `JSON.stringify` both skip it) and never\n * serialized. Passing it to a `'use client'` component fails the render, because it wraps the live\n * request — read what you need here and pass plain values down.\n *\n * Reading it on a `render: 'static'` route throws: a prerendered page has no per-request context.\n * Mark the route `render: 'dynamic'`, or use the `url` / `params` props.\n *\n * @example\n * ```tsx\n * export default function Dashboard({ ctx }: PageProps) {\n * const session = ctx.cookies.get('session');\n * if (!session) redirect('/login');\n * return <Layout>Signed in as {session}</Layout>;\n * }\n * ```\n */\n ctx: RequestContext<E>;\n}\n\n/**\n * A page: a React **server component** rendering the entire document (`<html>…</html>`), usually via\n * a shared layout. It may be `async` and await data directly.\n *\n * Each page module default-exports exactly one. Interactive parts belong in `'use client'` components\n * the page imports — only those ship JS.\n *\n * @typeParam P - The component's props; for a page these are {@link PageProps}.\n *\n * @see {@link https://react.dev/reference/rsc/server-components | React — Server Components}\n * @see {@link https://www.rshono.com/docs/pages | Docs — pages}\n */\n// `any`, not `unknown`: this default is what an unparameterised `PageComponent` means in a user's own\n// annotation, and `unknown` props would reject every component that declares the props it actually takes.\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type PageComponent<P = any> = (props: P) => ReactNode | Promise<ReactNode>;\n\n/**\n * The shape an `{ type: 'endpoint' }` route's server module must have: a single named `handler`\n * export. It only ever loads on the server, so importing a database client or reading secrets from it\n * is safe.\n *\n * @example\n * ```ts\n * // src/health.ts\n * import type { Handler } from 'hono';\n *\n * export const handler: Handler = (c) => c.json({ ok: true });\n * ```\n *\n * @see {@link https://www.rshono.com/docs/routing#endpoint-routes | Docs — endpoint routes}\n */\nexport interface EndpointServerModule {\n /**\n * A Hono {@link Handler} for every request the route matches. It is passed Hono's `Context`, so the\n * request, the response builders (`c.json`, `c.text`, `c.body`) and middleware variables are all\n * reached through it.\n *\n * @see {@link https://hono.dev/docs/api/context | Hono — Context}\n */\n handler: Handler;\n}\n\n/**\n * A page route — a path rendered by a server component. This is the default\n * route kind, so `type` can be omitted.\n *\n * @example\n * ```ts\n * { path: '/profile/:id', component: () => import('./components/profile') }\n * ```\n */\nexport interface PageRoute {\n /** Discriminates a page from an endpoint; optional because `'page'` is the default. */\n type?: 'page';\n /**\n * Hono-style path pattern, e.g. `/`, `/profile/:id`, `/files/*`. Routes are matched in\n * declaration order.\n *\n * @see {@link https://hono.dev/docs/api/routing | Hono — routing}\n */\n path: string;\n /**\n * Dynamic import of the page module, whose default export is the {@link PageComponent}.\n *\n * Write it inline as `() => import('…')`: the framework detects that exact form and injects the\n * `'use server-entry'` directive that attaches the page's client JS and CSS. Wire the component up\n * any other way — a variable, a barrel re-export, a computed specifier — and you have to put\n * `'use server-entry'` on the page module's first line yourself.\n *\n * @example\n * ```ts\n * component: () => import('./components/profile')\n * ```\n *\n * @see {@link https://www.rshono.com/docs/pages#the-use-server-entry-directive | Docs — the `'use server-entry'` directive}\n */\n component: () => Promise<{ default: PageComponent }>;\n /** `'static'` prerenders the route at build time; `'dynamic'` (the default) renders per request. */\n render?: 'static' | 'dynamic';\n /**\n * For a `render: 'static'` route with params: the param sets to prerender, one page each. Runs at\n * build time on the server, so it may hit a database or read the filesystem.\n *\n * A parameterised static route without this falls back to rendering per request, with a build\n * warning. Wildcard (`*`), optional and regex params cannot be prerendered.\n *\n * @example\n * ```ts\n * {\n * path: '/docs/:slug',\n * render: 'static',\n * component: () => import('./components/documentation'),\n * staticPaths: async () => (await db.docs.all()).map((d) => ({ slug: d.slug })),\n * }\n * ```\n *\n * @see {@link https://www.rshono.com/docs/routing#static-rendering | Docs — static rendering}\n */\n staticPaths?: () => Array<Record<string, string>> | Promise<Array<Record<string, string>>>;\n}\n\n/**\n * An endpoint route — a path served by a raw Hono handler instead of a React\n * component. Use it for JSON APIs, webhooks, redirects, feeds, or anything that\n * isn't an HTML page.\n *\n * @example\n * ```ts\n * { type: 'endpoint', path: '/api/health', server: () => import('./health') }\n * ```\n *\n * @see {@link https://www.rshono.com/docs/routing#endpoint-routes | Docs — endpoint routes}\n */\nexport interface EndpointRoute {\n /** Marks this route as an endpoint rather than a page. Required. */\n type: 'endpoint';\n /**\n * Hono-style path pattern, e.g. `/api/health`, `/api/users/:id`.\n *\n * @see {@link https://hono.dev/docs/api/routing | Hono — routing}\n */\n path: string;\n /** HTTP method to match. Defaults to `'all'` — every method. */\n method?: HTTPMethod;\n /** Dynamic import of the {@link EndpointServerModule} exporting `handler`. */\n server: () => Promise<EndpointServerModule>;\n}\n\n/** HTTP methods an {@link EndpointRoute} can match. `'all'` matches every method. */\nexport type HTTPMethod = 'get' | 'post' | 'put' | 'patch' | 'delete' | 'head' | 'options' | 'all';\n\n/** Any entry in the `routes` array: a {@link PageRoute} or an {@link EndpointRoute}. */\nexport type Route = PageRoute | EndpointRoute;\n\n/**\n * Narrows a {@link Route} to a {@link PageRoute} — `type` is optional on page routes, so anything not\n * explicitly `'endpoint'` is one.\n *\n * @internal\n */\nexport function isPageRoute(route: Route): route is PageRoute {\n return route.type !== 'endpoint';\n}\n\n/**\n * A page the framework falls back to rather than routes to — `notFound` and `error` in\n * {@link RouteConfig}. Same contract as a {@link PageRoute} `component`, without a path of its own.\n */\nexport interface FallbackPage {\n /** Dynamic import of the page module; its default export is the {@link PageComponent}. */\n component: () => Promise<{ default: PageComponent }>;\n}\n\n/**\n * The error detail handed to the `error` page. Redacted in production — a generic\n * `'Internal Server Error'` and no `stack`; in dev, the real message and stack.\n */\nexport interface ErrorPageInfo {\n /** The thrown error's message in dev; `'Internal Server Error'` in production. */\n message: string;\n /** The stack trace. Present in dev only. */\n stack?: string;\n}\n\n/**\n * Props for the `error` page declared in {@link RouteConfig.error} — the usual {@link PageProps} plus\n * the redaction-aware {@link ErrorPageInfo}.\n *\n * @typeParam E - The app's Hono {@link Env}, forwarded to {@link PageProps.ctx}.\n *\n * @example\n * ```tsx\n * import type { ErrorPageProps } from '@rshono/core';\n *\n * export default function ServerError({ error }: ErrorPageProps) {\n * return <html><body><h1>Something went wrong</h1><p>{error.message}</p></body></html>;\n * }\n * ```\n */\nexport type ErrorPageProps<E extends Env = Env> = PageProps<string, E> & {\n /** The error that failed the request, redacted in production — see {@link ErrorPageInfo}. */\n error: ErrorPageInfo;\n};\n\n/**\n * The object form accepted by {@link defineRoutes}: the route table plus the two\n * optional framework-owned pages.\n *\n * @typeParam TRoutes - Inferred tuple of route literals, which is what makes the\n * per-route `path` → props check possible.\n *\n * @see {@link https://www.rshono.com/docs/routing#notfound-and-error | Docs — notFound and error pages}\n */\nexport interface RouteConfig<TRoutes extends readonly Route[] = readonly Route[]> {\n /** Every page and endpoint in the app, matched in order. */\n routes: TRoutes;\n /** Page rendered with a 404 status for unmatched paths and for `notFound()` calls. */\n notFound?: FallbackPage;\n /** Page rendered with a 500 status when a request throws. Receives {@link ErrorPageProps}. */\n error?: FallbackPage;\n}\n\n// `PageProps<P, any>`, not `PageProps<P>`: only the *path* is being checked, and pinning the Env would\n// fail every page that declares its own (`PageProps<'/x', MyEnv>`, to type `ctx.var`).\ntype ValidateRoute<R> = R extends {\n path: infer P extends string;\n component: () => Promise<{ default: PageComponent<infer CP> }>;\n}\n ? // eslint-disable-next-line @typescript-eslint/no-explicit-any -- the Env is deliberately unpinned; see above.\n [PageProps<P, any>] extends [CP]\n ? R\n : R & { component: `component props are not satisfied by PageProps<'${P}'>` }\n : R;\n\ntype ValidateRoutes<TRoutes extends readonly Route[]> = { [K in keyof TRoutes]: ValidateRoute<TRoutes[K]> };\n\n/**\n * Declares the app's route table. Export the result as `routes` from `src/routes.ts` — the one file\n * rshono requires. It only ever runs on the server, so importing server-only modules from it (inside\n * `staticPaths`, say) is safe.\n *\n * Beyond typing the config, every page is cross-checked against its own path: props not satisfied by\n * `PageProps<'<its path>'>` make the `component` field a type error. A bare {@link Route} array is\n * accepted as shorthand — see the second overload.\n *\n * @param config - A {@link RouteConfig}: the `routes` array plus the optional `notFound` and `error`\n * pages.\n * @returns The config, unchanged and fully typed.\n *\n * @example\n * ```ts\n * // src/routes.ts\n * import { defineRoutes } from '@rshono/core';\n *\n * export const routes = defineRoutes({\n * routes: [\n * { path: '/', component: () => import('./components/home') },\n * { path: '/profile/:id', component: () => import('./components/profile') },\n * {\n * path: '/docs/:slug',\n * render: 'static',\n * component: () => import('./components/documentation'),\n * staticPaths: async () => [{ slug: 'getting-started' }, { slug: 'deployment' }],\n * },\n * { type: 'endpoint', path: '/api/health', server: () => import('./health') },\n * ],\n * notFound: { component: () => import('./components/404') },\n * error: { component: () => import('./components/500') },\n * });\n * ```\n *\n * @see {@link https://www.rshono.com/docs/routing | Docs — routing}\n */\nexport function defineRoutes<const TRoutes extends readonly Route[]>(\n config: RouteConfig<TRoutes> & { routes: ValidateRoutes<TRoutes> },\n): RouteConfig<TRoutes>;\n/**\n * Array shorthand for {@link defineRoutes} — equivalent to `defineRoutes({ routes })`, for an app\n * with no `notFound` or `error` page.\n *\n * @param routes - The {@link Route} array; each page is checked against its own `path`.\n * @returns A {@link RouteConfig} wrapping them.\n *\n * @example\n * ```ts\n * export const routes = defineRoutes([{ path: '/', component: () => import('./components/home') }]);\n * ```\n *\n * @see {@link https://www.rshono.com/docs/routing | Docs — routing}\n */\nexport function defineRoutes<const TRoutes extends readonly Route[]>(routes: TRoutes & ValidateRoutes<TRoutes>): RouteConfig<TRoutes>;\nexport function defineRoutes(input: readonly Route[] | RouteConfig): RouteConfig {\n return Array.isArray(input) ? { routes: input } : (input as RouteConfig);\n}\n"]}
|
|
@@ -1,28 +1,27 @@
|
|
|
1
1
|
import { Component, type ReactNode } from 'react';
|
|
2
2
|
/**
|
|
3
|
-
* What a {@link CatchBoundary} / {@link AsyncBoundary} renders once a child throws
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* What a {@link CatchBoundary} / {@link AsyncBoundary} renders once a child throws: either a static
|
|
4
|
+
* node, or a render function given the error and a `reset` callback that clears it and re-renders the
|
|
5
|
+
* children (a "Try again" button, say).
|
|
6
6
|
*
|
|
7
|
-
* The
|
|
8
|
-
*
|
|
9
|
-
* server component, pass a `ReactNode`.
|
|
7
|
+
* The function form only works from a `'use client'` component — functions can't cross the
|
|
8
|
+
* server→client boundary. From a server component, pass a `ReactNode`.
|
|
10
9
|
*/
|
|
11
10
|
export type ErrorFallback = ReactNode | ((error: Error, reset: () => void) => ReactNode);
|
|
12
11
|
/** Props for {@link CatchBoundary}. */
|
|
13
12
|
export interface CatchBoundaryProps {
|
|
14
13
|
/**
|
|
15
|
-
* Rendered in place of the children after one of them throws. Omit it to
|
|
16
|
-
*
|
|
17
|
-
*
|
|
14
|
+
* Rendered in place of the children after one of them throws. Omit it to report the error via
|
|
15
|
+
* `onError` and re-throw to the next boundary out — or the app's `error` page — instead of handling it
|
|
16
|
+
* here.
|
|
18
17
|
*/
|
|
19
18
|
fallback?: ErrorFallback;
|
|
20
|
-
/** Called with the caught error
|
|
19
|
+
/** Called with the caught error, for logging or reporting. */
|
|
21
20
|
onError?: (error: Error) => void;
|
|
22
21
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
22
|
+
* Clears the error automatically when any value in this array changes while the fallback is showing.
|
|
23
|
+
* Pass the current pathname to recover when the user navigates away:
|
|
24
|
+
* `resetKeys={[useNavigation().url.pathname]}`.
|
|
26
25
|
*/
|
|
27
26
|
resetKeys?: readonly unknown[];
|
|
28
27
|
/** The subtree this boundary protects. */
|
|
@@ -32,14 +31,12 @@ interface CatchBoundaryState {
|
|
|
32
31
|
error: Error | null;
|
|
33
32
|
}
|
|
34
33
|
/**
|
|
35
|
-
* A general-purpose error boundary
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* tearing down the whole page.
|
|
34
|
+
* A general-purpose error boundary: catches what its children throw — a client island that blew up, a
|
|
35
|
+
* server component that rejected on a soft navigation — and renders `fallback` in their place rather
|
|
36
|
+
* than tearing down the page.
|
|
39
37
|
*
|
|
40
|
-
* It
|
|
41
|
-
*
|
|
42
|
-
* when you also want a Suspense loading fallback in the same wrapper.
|
|
38
|
+
* It is a `'use client'` component (React error boundaries must be), so a server component can render
|
|
39
|
+
* it too. Reach for {@link AsyncBoundary} when you also want a Suspense loading fallback.
|
|
43
40
|
*
|
|
44
41
|
* @example
|
|
45
42
|
* ```tsx
|
|
@@ -69,10 +66,9 @@ export declare class CatchBoundary extends Component<CatchBoundaryProps, CatchBo
|
|
|
69
66
|
/** Props for {@link AsyncBoundary}. */
|
|
70
67
|
export interface AsyncBoundaryProps {
|
|
71
68
|
/**
|
|
72
|
-
* Suspense fallback, shown while the children
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
* rather than something you get by leaving the prop off.
|
|
69
|
+
* Suspense fallback, shown while the children or their data are still loading. Required — a loading
|
|
70
|
+
* state is the reason to reach for this over {@link CatchBoundary}, so showing nothing is an explicit
|
|
71
|
+
* `loading={null}`.
|
|
76
72
|
*/
|
|
77
73
|
loading: ReactNode;
|
|
78
74
|
/** Error fallback, shown if a child throws. See {@link ErrorFallback}. */
|
|
@@ -85,8 +81,8 @@ export interface AsyncBoundaryProps {
|
|
|
85
81
|
children: ReactNode;
|
|
86
82
|
}
|
|
87
83
|
/**
|
|
88
|
-
* A loading
|
|
89
|
-
*
|
|
84
|
+
* A loading and error boundary in one wrapper — the common case for an async section of a page. It
|
|
85
|
+
* always renders the same shape:
|
|
90
86
|
*
|
|
91
87
|
* ```tsx
|
|
92
88
|
* <CatchBoundary fallback={error}>
|
|
@@ -94,10 +90,8 @@ export interface AsyncBoundaryProps {
|
|
|
94
90
|
* </CatchBoundary>
|
|
95
91
|
* ```
|
|
96
92
|
*
|
|
97
|
-
* so `
|
|
98
|
-
*
|
|
99
|
-
* errors propagate to the next boundary out (or the global error page) rather
|
|
100
|
-
* than being caught here.
|
|
93
|
+
* so `loading` shows until the children resolve and `error` catches whatever they throw, suspended or
|
|
94
|
+
* not. `error` is optional: omit it and errors propagate to the next boundary out.
|
|
101
95
|
*
|
|
102
96
|
* @example
|
|
103
97
|
* ```tsx
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"boundaries.d.ts","sourceRoot":"","sources":["../../src/runtime/boundaries.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAY,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"boundaries.d.ts","sourceRoot":"","sources":["../../src/runtime/boundaries.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAY,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAU5D;;;;;;;GAOG;AACH,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,IAAI,KAAK,SAAS,CAAC,CAAC;AAEzF,uCAAuC;AACvC,MAAM,WAAW,kBAAkB;IACjC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,8DAA8D;IAC9D,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC;;;;OAIG;IACH,SAAS,CAAC,EAAE,SAAS,OAAO,EAAE,CAAC;IAC/B,0CAA0C;IAC1C,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED,UAAU,kBAAkB;IAC1B,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;CACrB;AAMD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,qBAAa,aAAc,SAAQ,SAAS,CAAC,kBAAkB,EAAE,kBAAkB,CAAC;IAClF,KAAK,EAAE,kBAAkB,CAAmB;IAE5C,MAAM,CAAC,wBAAwB,CAAC,KAAK,EAAE,KAAK,GAAG,kBAAkB,CAEhE;IAED,iBAAiB,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAGpC;IAED,kBAAkB,CAAC,IAAI,EAAE,kBAAkB,GAAG,IAAI,CAKjD;IAED,KAAK,QAAO,IAAI,CAEd;IAEF,MAAM,IAAI,SAAS,CASlB;CACF;AAED,uCAAuC;AACvC,MAAM,WAAW,kBAAkB;IACjC;;;;OAIG;IACH,OAAO,EAAE,SAAS,CAAC;IACnB,0EAA0E;IAC1E,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,oCAAoC;IACpC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,mGAAmG;IACnG,SAAS,CAAC,EAAE,SAAS,OAAO,EAAE,CAAC;IAC/B,0DAA0D;IAC1D,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,aAAa,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,EAAE,kBAAkB,GAAG,SAAS,CAM7G"}
|
|
@@ -2,12 +2,9 @@
|
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { Component, Suspense } from 'react';
|
|
4
4
|
import { isControlDigest } from './control.js';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
* a component inside a `<CatchBoundary>` would render "something went wrong" instead of navigating.
|
|
9
|
-
* They're re-thrown to the root, where the runtime turns the digest into a real navigation.
|
|
10
|
-
*/
|
|
5
|
+
// `redirect()` and `notFound()` reach the browser as a thrown error carrying a control digest. They are
|
|
6
|
+
// navigation, not failure, so no boundary absorbs one — they are re-thrown to the root, where the
|
|
7
|
+
// runtime turns the digest into a real navigation.
|
|
11
8
|
function isControlError(error) {
|
|
12
9
|
return isControlDigest(error?.digest);
|
|
13
10
|
}
|
|
@@ -15,14 +12,12 @@ function keysChanged(a, b) {
|
|
|
15
12
|
return a.length !== b.length || a.some((value, i) => !Object.is(value, b[i]));
|
|
16
13
|
}
|
|
17
14
|
/**
|
|
18
|
-
* A general-purpose error boundary
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* tearing down the whole page.
|
|
15
|
+
* A general-purpose error boundary: catches what its children throw — a client island that blew up, a
|
|
16
|
+
* server component that rejected on a soft navigation — and renders `fallback` in their place rather
|
|
17
|
+
* than tearing down the page.
|
|
22
18
|
*
|
|
23
|
-
* It
|
|
24
|
-
*
|
|
25
|
-
* when you also want a Suspense loading fallback in the same wrapper.
|
|
19
|
+
* It is a `'use client'` component (React error boundaries must be), so a server component can render
|
|
20
|
+
* it too. Reach for {@link AsyncBoundary} when you also want a Suspense loading fallback.
|
|
26
21
|
*
|
|
27
22
|
* @example
|
|
28
23
|
* ```tsx
|
|
@@ -48,7 +43,7 @@ export class CatchBoundary extends Component {
|
|
|
48
43
|
}
|
|
49
44
|
componentDidCatch(error) {
|
|
50
45
|
if (isControlError(error))
|
|
51
|
-
return;
|
|
46
|
+
return;
|
|
52
47
|
this.props.onError?.(error);
|
|
53
48
|
}
|
|
54
49
|
componentDidUpdate(prev) {
|
|
@@ -64,18 +59,18 @@ export class CatchBoundary extends Component {
|
|
|
64
59
|
const { error } = this.state;
|
|
65
60
|
if (error !== null) {
|
|
66
61
|
if (isControlError(error))
|
|
67
|
-
throw error;
|
|
62
|
+
throw error;
|
|
68
63
|
const { fallback } = this.props;
|
|
69
64
|
if (fallback === undefined)
|
|
70
|
-
throw error; //
|
|
65
|
+
throw error; // propagate to an outer boundary
|
|
71
66
|
return typeof fallback === 'function' ? fallback(error, this.reset) : fallback;
|
|
72
67
|
}
|
|
73
68
|
return this.props.children;
|
|
74
69
|
}
|
|
75
70
|
}
|
|
76
71
|
/**
|
|
77
|
-
* A loading
|
|
78
|
-
*
|
|
72
|
+
* A loading and error boundary in one wrapper — the common case for an async section of a page. It
|
|
73
|
+
* always renders the same shape:
|
|
79
74
|
*
|
|
80
75
|
* ```tsx
|
|
81
76
|
* <CatchBoundary fallback={error}>
|
|
@@ -83,10 +78,8 @@ export class CatchBoundary extends Component {
|
|
|
83
78
|
* </CatchBoundary>
|
|
84
79
|
* ```
|
|
85
80
|
*
|
|
86
|
-
* so `
|
|
87
|
-
*
|
|
88
|
-
* errors propagate to the next boundary out (or the global error page) rather
|
|
89
|
-
* than being caught here.
|
|
81
|
+
* so `loading` shows until the children resolve and `error` catches whatever they throw, suspended or
|
|
82
|
+
* not. `error` is optional: omit it and errors propagate to the next boundary out.
|
|
90
83
|
*
|
|
91
84
|
* @example
|
|
92
85
|
* ```tsx
|