@rshono/core 1.0.0-rc.6 → 1.0.0-rc.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +195 -168
- package/dist/builder/page-files.d.ts.map +1 -1
- package/dist/builder/page-files.js +7 -3
- package/dist/builder/page-files.js.map +1 -1
- package/dist/builder/public-env.d.ts +6 -0
- package/dist/builder/public-env.d.ts.map +1 -1
- package/dist/builder/public-env.js +6 -0
- package/dist/builder/public-env.js.map +1 -1
- package/dist/builder/rspack-config.d.ts.map +1 -1
- package/dist/builder/rspack-config.js +16 -19
- package/dist/builder/rspack-config.js.map +1 -1
- package/dist/cli/dev.d.ts.map +1 -1
- package/dist/cli/dev.js +63 -35
- package/dist/cli/dev.js.map +1 -1
- package/dist/cli/index.js +3 -4
- package/dist/cli/index.js.map +1 -1
- package/dist/config.d.ts +56 -55
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +17 -1
- package/dist/config.js.map +1 -1
- package/dist/deploy/contract.d.ts +15 -10
- package/dist/deploy/contract.d.ts.map +1 -1
- package/dist/deploy/contract.js.map +1 -1
- package/dist/deploy/filesystem.d.ts.map +1 -1
- package/dist/deploy/filesystem.js +6 -7
- package/dist/deploy/filesystem.js.map +1 -1
- package/dist/deploy/node/runtime.d.ts +5 -5
- package/dist/deploy/node/runtime.d.ts.map +1 -1
- package/dist/deploy/node/runtime.js +7 -11
- package/dist/deploy/node/runtime.js.map +1 -1
- package/dist/deploy/presets.d.ts +2 -6
- package/dist/deploy/presets.d.ts.map +1 -1
- package/dist/deploy/presets.js +2 -6
- package/dist/deploy/presets.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/dist/router.d.ts +74 -36
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +2 -3
- package/dist/router.js.map +1 -1
- package/dist/runtime/boundaries.d.ts +10 -0
- package/dist/runtime/boundaries.d.ts.map +1 -1
- package/dist/runtime/boundaries.js +6 -0
- package/dist/runtime/boundaries.js.map +1 -1
- package/dist/runtime/client.d.ts +5 -3
- package/dist/runtime/client.d.ts.map +1 -1
- package/dist/runtime/client.js +5 -3
- package/dist/runtime/client.js.map +1 -1
- package/dist/runtime/context.d.ts +195 -45
- package/dist/runtime/context.d.ts.map +1 -1
- package/dist/runtime/context.js +277 -59
- package/dist/runtime/context.js.map +1 -1
- package/dist/runtime/control.d.ts.map +1 -1
- package/dist/runtime/control.js +7 -0
- package/dist/runtime/control.js.map +1 -1
- package/dist/runtime/entry.client.js +29 -47
- package/dist/runtime/entry.client.js.map +1 -1
- package/dist/runtime/entry.rsc.d.ts.map +1 -1
- package/dist/runtime/entry.rsc.js +109 -140
- package/dist/runtime/entry.rsc.js.map +1 -1
- package/dist/runtime/flight-inject.d.ts +4 -4
- package/dist/runtime/flight-inject.js +2 -2
- package/dist/runtime/flight-inject.js.map +1 -1
- package/dist/runtime/navigation.d.ts +11 -0
- package/dist/runtime/navigation.d.ts.map +1 -1
- package/dist/runtime/navigation.js +3 -0
- package/dist/runtime/navigation.js.map +1 -1
- package/dist/runtime/request.d.ts +3 -3
- package/dist/runtime/request.js +3 -3
- package/dist/runtime/request.js.map +1 -1
- package/dist/runtime/server.d.ts +7 -12
- package/dist/runtime/server.d.ts.map +1 -1
- package/dist/runtime/server.js +15 -12
- package/dist/runtime/server.js.map +1 -1
- package/dist/server/prerendered.d.ts +20 -35
- package/dist/server/prerendered.d.ts.map +1 -1
- package/dist/server/prerendered.js +8 -18
- package/dist/server/prerendered.js.map +1 -1
- package/dist/server/server-config.d.ts +10 -29
- package/dist/server/server-config.d.ts.map +1 -1
- package/dist/server/server-config.js +4 -70
- package/dist/server/server-config.js.map +1 -1
- package/dist/server/ssg.d.ts.map +1 -1
- package/dist/server/ssg.js +10 -11
- package/dist/server/ssg.js.map +1 -1
- package/package.json +1 -1
package/dist/router.d.ts
CHANGED
|
@@ -19,6 +19,8 @@ type UnionToIntersection<U> = (U extends unknown ? (k: U) => void : never) exten
|
|
|
19
19
|
* ```ts
|
|
20
20
|
* type P = PathParams<'/users/:id/posts/:postId'>; // { id: string; postId: string }
|
|
21
21
|
* ```
|
|
22
|
+
*
|
|
23
|
+
* @see {@link https://hono.dev/docs/api/routing#path-parameter | Hono — path parameters}
|
|
22
24
|
*/
|
|
23
25
|
export type PathParams<P extends string> = ParamKeys<P> extends never ? Record<string, never> : Simplify<UnionToIntersection<ParamKeyToRecord<ParamKeys<P>>>>;
|
|
24
26
|
/**
|
|
@@ -47,6 +49,8 @@ export type PathParams<P extends string> = ParamKeys<P> extends never ? Record<s
|
|
|
47
49
|
* return <Layout>{user.name} — {tab}</Layout>;
|
|
48
50
|
* }
|
|
49
51
|
* ```
|
|
52
|
+
*
|
|
53
|
+
* @see {@link https://www.rshono.com/docs/pages#page-props | Docs — page props}
|
|
50
54
|
*/
|
|
51
55
|
export interface PageProps<Path extends string = string, E extends Env = Env> {
|
|
52
56
|
/**
|
|
@@ -55,14 +59,14 @@ export interface PageProps<Path extends string = string, E extends Env = Env> {
|
|
|
55
59
|
* the rest off it.
|
|
56
60
|
*
|
|
57
61
|
* A fresh instance per request that nothing else holds, so mutating it is local
|
|
58
|
-
* to the page
|
|
59
|
-
*
|
|
62
|
+
* to the page. It is not serializable, so a `'use client'` component has to be
|
|
63
|
+
* handed `url.href` rather than `url`.
|
|
60
64
|
*
|
|
61
|
-
* On a
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
65
|
+
* On a `render: 'static'` route this is the build-time URL — rendered once
|
|
66
|
+
* against `siteUrl` with no query string, and that one file answers every
|
|
67
|
+
* request whatever its own query. So `url.searchParams` is always empty there:
|
|
68
|
+
* read the query from `useNavigation().url` in a `'use client'` component
|
|
69
|
+
* instead, or mark the route `render: 'dynamic'`.
|
|
66
70
|
*/
|
|
67
71
|
url: URL;
|
|
68
72
|
/** Matched route params for this request, e.g. `{ id: '42' }` for `/profile/:id`. */
|
|
@@ -72,25 +76,18 @@ export interface PageProps<Path extends string = string, E extends Env = Env> {
|
|
|
72
76
|
* page so cookies, headers, env and middleware variables are reachable without
|
|
73
77
|
* an import.
|
|
74
78
|
*
|
|
75
|
-
* Server-only
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
* plain objects … can be passed to Client Components"*. Read what you need on
|
|
83
|
-
* the server and pass plain values down.
|
|
84
|
-
* - Spreading the page's props instead (`<Counter {...props} />`) drops `ctx`
|
|
85
|
-
* silently rather than failing, since the spread copies enumerables only.
|
|
86
|
-
* (That spread still fails, mind — on `url`, which is enumerable and just as
|
|
87
|
-
* unserializable. Pass the values you need.)
|
|
88
|
-
* - `Object.keys(props)`, `JSON.stringify(props)` and friends don't see it.
|
|
79
|
+
* Server-only and never serialized: React puts a server component's *output* on
|
|
80
|
+
* the wire, not its props. It is also deliberately **non-enumerable**, so
|
|
81
|
+
* `Object.keys(props)`, `JSON.stringify(props)` and a `{...props}` spread all
|
|
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.
|
|
89
86
|
*
|
|
90
87
|
* Reading it on a `render: 'static'` route throws: a prerendered page has no
|
|
91
|
-
* per-request context at build time. Mark the route `render: 'dynamic'
|
|
92
|
-
* the `url` / `params` props
|
|
93
|
-
*
|
|
88
|
+
* per-request context at build time. Mark the route `render: 'dynamic'`, or use
|
|
89
|
+
* the `url` / `params` props — available either way, with the build-time caveat
|
|
90
|
+
* noted on `url`.
|
|
94
91
|
*
|
|
95
92
|
* @example
|
|
96
93
|
* ```tsx
|
|
@@ -112,6 +109,9 @@ export interface PageProps<Path extends string = string, E extends Env = Env> {
|
|
|
112
109
|
* belong in `'use client'` components the page imports — only those ship JS.
|
|
113
110
|
*
|
|
114
111
|
* @typeParam P - The component's props; for a page these are {@link PageProps}.
|
|
112
|
+
*
|
|
113
|
+
* @see {@link https://react.dev/reference/rsc/server-components | React — Server Components}
|
|
114
|
+
* @see {@link https://www.rshono.com/docs/pages | Docs — pages}
|
|
115
115
|
*/
|
|
116
116
|
export type PageComponent<P = any> = (props: P) => ReactNode | Promise<ReactNode>;
|
|
117
117
|
/**
|
|
@@ -126,9 +126,17 @@ export type PageComponent<P = any> = (props: P) => ReactNode | Promise<ReactNode
|
|
|
126
126
|
*
|
|
127
127
|
* export const handler: Handler = (c) => c.json({ ok: true });
|
|
128
128
|
* ```
|
|
129
|
+
*
|
|
130
|
+
* @see {@link https://www.rshono.com/docs/routing#endpoint-routes | Docs — endpoint routes}
|
|
129
131
|
*/
|
|
130
132
|
export interface EndpointServerModule {
|
|
131
|
-
/**
|
|
133
|
+
/**
|
|
134
|
+
* A Hono {@link Handler} handling every request matched by the route. It is passed Hono's
|
|
135
|
+
* `Context`, so the request, response builders (`c.json`, `c.text`, `c.body`) and middleware
|
|
136
|
+
* variables are all reached through it.
|
|
137
|
+
*
|
|
138
|
+
* @see {@link https://hono.dev/docs/api/context | Hono — Context}
|
|
139
|
+
*/
|
|
132
140
|
handler: Handler;
|
|
133
141
|
}
|
|
134
142
|
/**
|
|
@@ -143,7 +151,12 @@ export interface EndpointServerModule {
|
|
|
143
151
|
export interface PageRoute {
|
|
144
152
|
/** Discriminates a page from an endpoint; optional because `'page'` is the default. */
|
|
145
153
|
type?: 'page';
|
|
146
|
-
/**
|
|
154
|
+
/**
|
|
155
|
+
* Hono-style path pattern, e.g. `/`, `/profile/:id`, `/files/*`. Routes are matched in
|
|
156
|
+
* declaration order.
|
|
157
|
+
*
|
|
158
|
+
* @see {@link https://hono.dev/docs/api/routing | Hono — routing}
|
|
159
|
+
*/
|
|
147
160
|
path: string;
|
|
148
161
|
/**
|
|
149
162
|
* Dynamic import of the page module, whose default export is the
|
|
@@ -156,6 +169,8 @@ export interface PageRoute {
|
|
|
156
169
|
* component up any other way — a variable, a barrel re-export, a computed
|
|
157
170
|
* specifier — add `'use server-entry'` as the first line of the page module
|
|
158
171
|
* yourself; the framework throws a descriptive error when neither happened.
|
|
172
|
+
*
|
|
173
|
+
* @see {@link https://www.rshono.com/docs/pages#the-use-server-entry-directive | Docs — the `'use server-entry'` directive}
|
|
159
174
|
*/
|
|
160
175
|
component: () => Promise<{
|
|
161
176
|
default: PageComponent;
|
|
@@ -180,6 +195,8 @@ export interface PageRoute {
|
|
|
180
195
|
* staticPaths: async () => (await db.docs.all()).map((d) => ({ slug: d.slug })),
|
|
181
196
|
* }
|
|
182
197
|
* ```
|
|
198
|
+
*
|
|
199
|
+
* @see {@link https://www.rshono.com/docs/routing#static-rendering | Docs — static rendering}
|
|
183
200
|
*/
|
|
184
201
|
staticPaths?: () => Array<Record<string, string>> | Promise<Array<Record<string, string>>>;
|
|
185
202
|
}
|
|
@@ -192,11 +209,17 @@ export interface PageRoute {
|
|
|
192
209
|
* ```ts
|
|
193
210
|
* { type: 'endpoint', path: '/api/health', server: () => import('./health') }
|
|
194
211
|
* ```
|
|
212
|
+
*
|
|
213
|
+
* @see {@link https://www.rshono.com/docs/routing#endpoint-routes | Docs — endpoint routes}
|
|
195
214
|
*/
|
|
196
215
|
export interface EndpointRoute {
|
|
197
216
|
/** Marks this route as an endpoint rather than a page. Required. */
|
|
198
217
|
type: 'endpoint';
|
|
199
|
-
/**
|
|
218
|
+
/**
|
|
219
|
+
* Hono-style path pattern, e.g. `/api/health`, `/api/users/:id`.
|
|
220
|
+
*
|
|
221
|
+
* @see {@link https://hono.dev/docs/api/routing | Hono — routing}
|
|
222
|
+
*/
|
|
200
223
|
path: string;
|
|
201
224
|
/** HTTP method to match. Defaults to `'all'` — every method. */
|
|
202
225
|
method?: HTTPMethod;
|
|
@@ -211,9 +234,8 @@ export type Route = PageRoute | EndpointRoute;
|
|
|
211
234
|
* Type guard narrowing a {@link Route} to a {@link PageRoute}. Because `type` is
|
|
212
235
|
* optional on page routes, anything not explicitly `'endpoint'` is a page.
|
|
213
236
|
*
|
|
214
|
-
* Framework internal —
|
|
215
|
-
*
|
|
216
|
-
* route table; an app declares its routes rather than walking them.
|
|
237
|
+
* Framework internal — not re-exported from `index.ts`. The request renderer and
|
|
238
|
+
* the SSG pass use it to split the route table.
|
|
217
239
|
*
|
|
218
240
|
* @internal
|
|
219
241
|
*/
|
|
@@ -256,6 +278,7 @@ export interface ErrorPageInfo {
|
|
|
256
278
|
* ```
|
|
257
279
|
*/
|
|
258
280
|
export type ErrorPageProps<E extends Env = Env> = PageProps<string, E> & {
|
|
281
|
+
/** The error that failed the request, redacted in production — see {@link ErrorPageInfo}. */
|
|
259
282
|
error: ErrorPageInfo;
|
|
260
283
|
};
|
|
261
284
|
/**
|
|
@@ -264,6 +287,8 @@ export type ErrorPageProps<E extends Env = Env> = PageProps<string, E> & {
|
|
|
264
287
|
*
|
|
265
288
|
* @typeParam TRoutes - Inferred tuple of route literals, which is what makes the
|
|
266
289
|
* per-route `path` → props check possible.
|
|
290
|
+
*
|
|
291
|
+
* @see {@link https://www.rshono.com/docs/routing#notfound-and-error | Docs — notFound and error pages}
|
|
267
292
|
*/
|
|
268
293
|
export interface RouteConfig<TRoutes extends readonly Route[] = readonly Route[]> {
|
|
269
294
|
/** Every page and endpoint in the app, matched in order. */
|
|
@@ -297,8 +322,10 @@ type ValidateRoutes<TRoutes extends readonly Route[]> = {
|
|
|
297
322
|
* PageProps<'/…'>`. Fix it by matching the page's `PageProps<Path>` type
|
|
298
323
|
* argument to the path it's mounted at.
|
|
299
324
|
*
|
|
300
|
-
*
|
|
301
|
-
*
|
|
325
|
+
* A bare {@link Route} array is accepted as shorthand — see the second overload.
|
|
326
|
+
*
|
|
327
|
+
* @param config - A {@link RouteConfig}: the `routes` array plus the optional
|
|
328
|
+
* `notFound` and `error` pages.
|
|
302
329
|
* @returns The config, unchanged and fully typed.
|
|
303
330
|
*
|
|
304
331
|
* @example
|
|
@@ -323,14 +350,25 @@ type ValidateRoutes<TRoutes extends readonly Route[]> = {
|
|
|
323
350
|
* });
|
|
324
351
|
* ```
|
|
325
352
|
*
|
|
326
|
-
* @
|
|
327
|
-
* ```ts
|
|
328
|
-
* export const routes = defineRoutes([{ path: '/', component: () => import('./components/home') }]);
|
|
329
|
-
* ```
|
|
353
|
+
* @see {@link https://www.rshono.com/docs/routing | Docs — routing}
|
|
330
354
|
*/
|
|
331
355
|
export declare function defineRoutes<const TRoutes extends readonly Route[]>(config: RouteConfig<TRoutes> & {
|
|
332
356
|
routes: ValidateRoutes<TRoutes>;
|
|
333
357
|
}): RouteConfig<TRoutes>;
|
|
358
|
+
/**
|
|
359
|
+
* Array shorthand for {@link defineRoutes} — equivalent to `defineRoutes({ routes })`, for an app
|
|
360
|
+
* with no `notFound` or `error` page.
|
|
361
|
+
*
|
|
362
|
+
* @param routes - The {@link Route} array; each page is checked against its own `path`.
|
|
363
|
+
* @returns A {@link RouteConfig} wrapping them.
|
|
364
|
+
*
|
|
365
|
+
* @example
|
|
366
|
+
* ```ts
|
|
367
|
+
* export const routes = defineRoutes([{ path: '/', component: () => import('./components/home') }]);
|
|
368
|
+
* ```
|
|
369
|
+
*
|
|
370
|
+
* @see {@link https://www.rshono.com/docs/routing | Docs — routing}
|
|
371
|
+
*/
|
|
334
372
|
export declare function defineRoutes<const TRoutes extends readonly Route[]>(routes: TRoutes & ValidateRoutes<TRoutes>): RouteConfig<TRoutes>;
|
|
335
373
|
export {};
|
|
336
374
|
//# sourceMappingURL=router.d.ts.map
|
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;AAGvC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D,KAAK,QAAQ,CAAC,CAAC,IAAI;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CAAE,GAAG,EAAE,CAAC;AACjD,KAAK,mBAAmB,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,OAAO,GAAG,CAAC,CAAC,EAAE,CAAC,KAAK,IAAI,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,EAAE,MAAM,CAAC,KAAK,IAAI,GAAG,CAAC,GAAG,KAAK,CAAC;AAEpH
|
|
1
|
+
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"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"}
|
package/dist/router.js
CHANGED
|
@@ -2,9 +2,8 @@
|
|
|
2
2
|
* Type guard narrowing a {@link Route} to a {@link PageRoute}. Because `type` is
|
|
3
3
|
* optional on page routes, anything not explicitly `'endpoint'` is a page.
|
|
4
4
|
*
|
|
5
|
-
* Framework internal —
|
|
6
|
-
*
|
|
7
|
-
* route table; an app declares its routes rather than walking them.
|
|
5
|
+
* Framework internal — not re-exported from `index.ts`. The request renderer and
|
|
6
|
+
* the SSG pass use it to split the route table.
|
|
8
7
|
*
|
|
9
8
|
* @internal
|
|
10
9
|
*/
|
package/dist/router.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"router.js","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AA0NA;;;;;;;;;GASG;AACH,MAAM,UAAU,WAAW,CAAC,KAAY;IACtC,OAAO,KAAK,CAAC,IAAI,KAAK,UAAU,CAAC;AACnC,CAAC;AAwHD,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 */\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 */\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 — but note it is *not* serializable, so a `'use client'` component\n * has to be handed `url.href` rather than `url`.\n *\n * On a prerendered page it is the build-time URL: a `render: 'static'` route is\n * rendered once, against `siteUrl` and with no query string, and that one file\n * then answers every request whatever its own query. So `url.searchParams` is\n * always empty there — read the query from `useNavigation().url` in a\n * `'use client'` 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 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 renders a server component and puts\n * its *output* on the wire, not its props. It is also deliberately a\n * non-enumerable property, which has three consequences worth knowing:\n *\n * - It **cannot be handed to a `'use client'` component** — it wraps the live\n * request and response, which do not exist in the browser. Passing it\n * explicitly (`<Counter ctx={ctx} />`) fails the render with React's *\"Only\n * plain objects … can be passed to Client Components\"*. Read what you need on\n * the server and pass plain values down.\n * - Spreading the page's props instead (`<Counter {...props} />`) drops `ctx`\n * silently rather than failing, since the spread copies enumerables only.\n * (That spread still fails, mind — on `url`, which is enumerable and just as\n * unserializable. Pass the values you need.)\n * - `Object.keys(props)`, `JSON.stringify(props)` and friends don't see it.\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, which are available either way — with the\n * build-time caveats 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 */\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 */\nexport interface EndpointServerModule {\n /** A Hono {@link Handler} handling every request matched by the route. */\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 /** Hono-style path pattern, e.g. `/`, `/profile/:id`, `/files/*`. */\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 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 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 */\nexport interface EndpointRoute {\n /** Marks this route as an endpoint rather than a page. Required. */\n type: 'endpoint';\n /** Hono-style path pattern, e.g. `/api/health`, `/api/users/:id`. */\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 — deliberately absent from `index.ts`, so it is not part of the\n * `@rshono/core` surface. The request renderer and the builder use it to split the\n * route table; an app declares its routes rather than walking them.\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> & { error: ErrorPageInfo };\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 */\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 check is about the *path* matching the page's\n// `params`, and pinning the Env to the default would additionally demand that a page declaring its\n// own (`PageProps<'/x', MyEnv>`, to type `ctx.var`) accept a `RequestContext<Env>` — which it doesn't, so every\n// 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 * @param config - A {@link RouteConfig}, or a bare {@link Route} array as\n * shorthand when there are no `notFound` / `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 * @example Array shorthand\n * ```ts\n * export const routes = defineRoutes([{ path: '/', component: () => import('./components/home') }]);\n * ```\n */\nexport function defineRoutes<const TRoutes extends readonly Route[]>(\n config: RouteConfig<TRoutes> & { routes: ValidateRoutes<TRoutes> },\n): RouteConfig<TRoutes>;\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":"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"]}
|
|
@@ -9,6 +9,7 @@ import { Component, type ReactNode } from 'react';
|
|
|
9
9
|
* server component, pass a `ReactNode`.
|
|
10
10
|
*/
|
|
11
11
|
export type ErrorFallback = ReactNode | ((error: Error, reset: () => void) => ReactNode);
|
|
12
|
+
/** Props for {@link CatchBoundary}. */
|
|
12
13
|
export interface CatchBoundaryProps {
|
|
13
14
|
/**
|
|
14
15
|
* Rendered in place of the children after one of them throws. Omit it to
|
|
@@ -24,6 +25,7 @@ export interface CatchBoundaryProps {
|
|
|
24
25
|
* recover when the user navigates away: `resetKeys={[useNavigation().url.pathname]}`.
|
|
25
26
|
*/
|
|
26
27
|
resetKeys?: readonly unknown[];
|
|
28
|
+
/** The subtree this boundary protects. */
|
|
27
29
|
children: ReactNode;
|
|
28
30
|
}
|
|
29
31
|
interface CatchBoundaryState {
|
|
@@ -52,6 +54,9 @@ interface CatchBoundaryState {
|
|
|
52
54
|
* <RiskyWidget />
|
|
53
55
|
* </CatchBoundary>
|
|
54
56
|
* ```
|
|
57
|
+
*
|
|
58
|
+
* @see {@link https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary | React — error boundaries}
|
|
59
|
+
* @see {@link https://www.rshono.com/docs/api#rshonocoreclient | Docs — `@rshono/core/client`}
|
|
55
60
|
*/
|
|
56
61
|
export declare class CatchBoundary extends Component<CatchBoundaryProps, CatchBoundaryState> {
|
|
57
62
|
state: CatchBoundaryState;
|
|
@@ -61,6 +66,7 @@ export declare class CatchBoundary extends Component<CatchBoundaryProps, CatchBo
|
|
|
61
66
|
reset: () => void;
|
|
62
67
|
render(): ReactNode;
|
|
63
68
|
}
|
|
69
|
+
/** Props for {@link AsyncBoundary}. */
|
|
64
70
|
export interface AsyncBoundaryProps {
|
|
65
71
|
/**
|
|
66
72
|
* Suspense fallback, shown while the children (or their data) are still
|
|
@@ -75,6 +81,7 @@ export interface AsyncBoundaryProps {
|
|
|
75
81
|
onError?: (error: Error) => void;
|
|
76
82
|
/** Clears the error fallback when any value changes — see {@link CatchBoundaryProps.resetKeys}. */
|
|
77
83
|
resetKeys?: readonly unknown[];
|
|
84
|
+
/** The subtree this boundary suspends on and protects. */
|
|
78
85
|
children: ReactNode;
|
|
79
86
|
}
|
|
80
87
|
/**
|
|
@@ -100,6 +107,9 @@ export interface AsyncBoundaryProps {
|
|
|
100
107
|
* <SlowServerComponent />
|
|
101
108
|
* </AsyncBoundary>
|
|
102
109
|
* ```
|
|
110
|
+
*
|
|
111
|
+
* @see {@link https://react.dev/reference/react/Suspense | React — `<Suspense>`}
|
|
112
|
+
* @see {@link https://www.rshono.com/docs/api#rshonocoreclient | Docs — `@rshono/core/client`}
|
|
103
113
|
*/
|
|
104
114
|
export declare function AsyncBoundary({ loading, error, onError, resetKeys, children }: AsyncBoundaryProps): ReactNode;
|
|
105
115
|
export {};
|
|
@@ -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;AAa5D;;;;;;;;GAQG;AACH,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,IAAI,KAAK,SAAS,CAAC,CAAC;AAEzF,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,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED,UAAU,kBAAkB;IAC1B,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;CACrB;AAMD
|
|
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;AAa5D;;;;;;;;GAQG;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;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;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;;;;;OAKG;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;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,aAAa,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,EAAE,kBAAkB,GAAG,SAAS,CAM7G"}
|
|
@@ -37,6 +37,9 @@ function keysChanged(a, b) {
|
|
|
37
37
|
* <RiskyWidget />
|
|
38
38
|
* </CatchBoundary>
|
|
39
39
|
* ```
|
|
40
|
+
*
|
|
41
|
+
* @see {@link https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary | React — error boundaries}
|
|
42
|
+
* @see {@link https://www.rshono.com/docs/api#rshonocoreclient | Docs — `@rshono/core/client`}
|
|
40
43
|
*/
|
|
41
44
|
export class CatchBoundary extends Component {
|
|
42
45
|
state = { error: null };
|
|
@@ -93,6 +96,9 @@ export class CatchBoundary extends Component {
|
|
|
93
96
|
* <SlowServerComponent />
|
|
94
97
|
* </AsyncBoundary>
|
|
95
98
|
* ```
|
|
99
|
+
*
|
|
100
|
+
* @see {@link https://react.dev/reference/react/Suspense | React — `<Suspense>`}
|
|
101
|
+
* @see {@link https://www.rshono.com/docs/api#rshonocoreclient | Docs — `@rshono/core/client`}
|
|
96
102
|
*/
|
|
97
103
|
export function AsyncBoundary({ loading, error, onError, resetKeys, children }) {
|
|
98
104
|
return (_jsx(CatchBoundary, { fallback: error, onError: onError, resetKeys: resetKeys, children: _jsx(Suspense, { fallback: loading, children: children }) }));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"boundaries.js","sourceRoot":"","sources":["../../src/runtime/boundaries.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAkB,MAAM,OAAO,CAAC;AAC5D,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE/C;;;;;GAKG;AACH,SAAS,cAAc,CAAC,KAAc;IACpC,OAAO,eAAe,CAAE,KAAqC,EAAE,MAAM,CAAC,CAAC;AACzE,CAAC;
|
|
1
|
+
{"version":3,"file":"boundaries.js","sourceRoot":"","sources":["../../src/runtime/boundaries.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAkB,MAAM,OAAO,CAAC;AAC5D,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE/C;;;;;GAKG;AACH,SAAS,cAAc,CAAC,KAAc;IACpC,OAAO,eAAe,CAAE,KAAqC,EAAE,MAAM,CAAC,CAAC;AACzE,CAAC;AAqCD,SAAS,WAAW,CAAC,CAAqB,EAAE,CAAqB;IAC/D,OAAO,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAChF,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,OAAO,aAAc,SAAQ,SAAiD;IAClF,KAAK,GAAuB,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IAE5C,MAAM,CAAC,wBAAwB,CAAC,KAAY;QAC1C,OAAO,EAAE,KAAK,EAAE,CAAC;IACnB,CAAC;IAED,iBAAiB,CAAC,KAAY;QAC5B,IAAI,cAAc,CAAC,KAAK,CAAC;YAAE,OAAO,CAAC,sCAAsC;QACzE,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC;IAC9B,CAAC;IAED,kBAAkB,CAAC,IAAwB;QACzC,MAAM,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC;QACjC,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK,IAAI,IAAI,CAAC,SAAS,IAAI,SAAS,IAAI,WAAW,CAAC,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,EAAE,CAAC;YAC9F,IAAI,CAAC,KAAK,EAAE,CAAC;QACf,CAAC;IACH,CAAC;IAED,KAAK,GAAG,GAAS,EAAE;QACjB,IAAI,CAAC,QAAQ,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACjC,CAAC,CAAC;IAEF,MAAM;QACJ,MAAM,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC;QAC7B,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;YACnB,IAAI,cAAc,CAAC,KAAK,CAAC;gBAAE,MAAM,KAAK,CAAC,CAAC,sDAAsD;YAC9F,MAAM,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC;YAChC,IAAI,QAAQ,KAAK,SAAS;gBAAE,MAAM,KAAK,CAAC,CAAC,qDAAqD;YAC9F,OAAO,OAAO,QAAQ,KAAK,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;QACjF,CAAC;QACD,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC;IAC7B,CAAC;CACF;AAqBD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,UAAU,aAAa,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAsB;IAChG,OAAO,CACL,KAAC,aAAa,IAAC,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,YACpE,KAAC,QAAQ,IAAC,QAAQ,EAAE,OAAO,YAAG,QAAQ,GAAY,GACpC,CACjB,CAAC;AACJ,CAAC","sourcesContent":["'use client';\n\nimport { Component, Suspense, type ReactNode } from 'react';\nimport { isControlDigest } from './control.js';\n\n/**\n * `redirect()` and `notFound()` reach the browser as a thrown error carrying a control digest.\n * They are navigation, not failure, so no boundary may absorb one — otherwise a `redirect()` from\n * a component inside a `<CatchBoundary>` would render \"something went wrong\" instead of navigating.\n * They're re-thrown to the root, where the runtime turns the digest into a real navigation.\n */\nfunction isControlError(error: unknown): boolean {\n return isControlDigest((error as { digest?: unknown } | null)?.digest);\n}\n\n/**\n * What a {@link CatchBoundary} / {@link AsyncBoundary} renders once a child throws.\n * Either a static node, or a render function that also gets a `reset` callback\n * to clear the error and re-render the children (e.g. a \"Try again\" button).\n *\n * The render-function form only works when the boundary is used from a `'use\n * client'` component — functions can't cross the server→client boundary. From a\n * server component, pass a `ReactNode`.\n */\nexport type ErrorFallback = ReactNode | ((error: Error, reset: () => void) => ReactNode);\n\n/** Props for {@link CatchBoundary}. */\nexport interface CatchBoundaryProps {\n /**\n * Rendered in place of the children after one of them throws. Omit it to\n * report the error via `onError` and re-throw to the next boundary out (or\n * the global error page) instead of handling it here.\n */\n fallback?: ErrorFallback;\n /** Called with the caught error (for logging / reporting). */\n onError?: (error: Error) => void;\n /**\n * When any value in this array changes while the boundary is showing its\n * fallback, the error is cleared automatically. Pass the current pathname to\n * recover when the user navigates away: `resetKeys={[useNavigation().url.pathname]}`.\n */\n resetKeys?: readonly unknown[];\n /** The subtree this boundary protects. */\n children: ReactNode;\n}\n\ninterface CatchBoundaryState {\n error: Error | null;\n}\n\nfunction keysChanged(a: readonly unknown[], b: readonly unknown[]): boolean {\n return a.length !== b.length || a.some((value, i) => !Object.is(value, b[i]));\n}\n\n/**\n * A general-purpose error boundary. Catches errors thrown while rendering its\n * children — a client island that blew up, or a server component that rejected\n * on a soft navigation — and renders `fallback` in their place instead of\n * tearing down the whole page.\n *\n * It's a `'use client'` component (React error boundaries must be), so drop it\n * anywhere in the tree from a server or client component. Use {@link AsyncBoundary}\n * when you also want a Suspense loading fallback in the same wrapper.\n *\n * @example\n * ```tsx\n * import { CatchBoundary } from '@rshono/core/client';\n *\n * <CatchBoundary fallback={(error, reset) => (\n * <div role=\"alert\">\n * <p>{error.message}</p>\n * <button onClick={reset}>Try again</button>\n * </div>\n * )}>\n * <RiskyWidget />\n * </CatchBoundary>\n * ```\n *\n * @see {@link https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary | React — error boundaries}\n * @see {@link https://www.rshono.com/docs/api#rshonocoreclient | Docs — `@rshono/core/client`}\n */\nexport class CatchBoundary extends Component<CatchBoundaryProps, CatchBoundaryState> {\n state: CatchBoundaryState = { error: null };\n\n static getDerivedStateFromError(error: Error): CatchBoundaryState {\n return { error };\n }\n\n componentDidCatch(error: Error): void {\n if (isControlError(error)) return; // a redirect isn't an error to report\n this.props.onError?.(error);\n }\n\n componentDidUpdate(prev: CatchBoundaryProps): void {\n const { resetKeys } = this.props;\n if (this.state.error && prev.resetKeys && resetKeys && keysChanged(prev.resetKeys, resetKeys)) {\n this.reset();\n }\n }\n\n reset = (): void => {\n this.setState({ error: null });\n };\n\n render(): ReactNode {\n const { error } = this.state;\n if (error !== null) {\n if (isControlError(error)) throw error; // navigation in flight — never show a fallback for it\n const { fallback } = this.props;\n if (fallback === undefined) throw error; // no local fallback → propagate to an outer boundary\n return typeof fallback === 'function' ? fallback(error, this.reset) : fallback;\n }\n return this.props.children;\n }\n}\n\n/** Props for {@link AsyncBoundary}. */\nexport interface AsyncBoundaryProps {\n /**\n * Suspense fallback, shown while the children (or their data) are still\n * loading. Required — a loading state is the reason to reach for this over\n * {@link CatchBoundary}, so showing nothing is an explicit `loading={null}`\n * rather than something you get by leaving the prop off.\n */\n loading: ReactNode;\n /** Error fallback, shown if a child throws. See {@link ErrorFallback}. */\n error?: ErrorFallback;\n /** Called with the caught error. */\n onError?: (error: Error) => void;\n /** Clears the error fallback when any value changes — see {@link CatchBoundaryProps.resetKeys}. */\n resetKeys?: readonly unknown[];\n /** The subtree this boundary suspends on and protects. */\n children: ReactNode;\n}\n\n/**\n * A loading + error boundary in one wrapper — the common case for an async\n * section of a page. It always renders the same shape:\n *\n * ```tsx\n * <CatchBoundary fallback={error}>\n * <Suspense fallback={loading}>{children}</Suspense>\n * </CatchBoundary>\n * ```\n *\n * so `error` catches anything the children throw (including while suspended) and\n * `loading` shows until they resolve. `error` is optional: omit it and thrown\n * errors propagate to the next boundary out (or the global error page) rather\n * than being caught here.\n *\n * @example\n * ```tsx\n * import { AsyncBoundary } from '@rshono/core/client';\n *\n * <AsyncBoundary loading={<Spinner />} error={(e, reset) => <Retry onClick={reset} />}>\n * <SlowServerComponent />\n * </AsyncBoundary>\n * ```\n *\n * @see {@link https://react.dev/reference/react/Suspense | React — `<Suspense>`}\n * @see {@link https://www.rshono.com/docs/api#rshonocoreclient | Docs — `@rshono/core/client`}\n */\nexport function AsyncBoundary({ loading, error, onError, resetKeys, children }: AsyncBoundaryProps): ReactNode {\n return (\n <CatchBoundary fallback={error} onError={onError} resetKeys={resetKeys}>\n <Suspense fallback={loading}>{children}</Suspense>\n </CatchBoundary>\n );\n}\n"]}
|
package/dist/runtime/client.d.ts
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `@rshono/core/client` — the browser-side surface, for use from `'use client'`
|
|
3
|
-
* modules:
|
|
4
|
-
*
|
|
3
|
+
* modules: `useNavigation()` for the current URL and soft navigation, and
|
|
4
|
+
* `<AsyncBoundary>` / `<CatchBoundary>` as components.
|
|
5
5
|
*
|
|
6
6
|
* Every export is itself a `'use client'` module, so a server component can
|
|
7
|
-
* render
|
|
7
|
+
* render `<AsyncBoundary>` directly — but the hook needs a client component. In a
|
|
8
8
|
* server component, read the same request data from `getRequestContext()` in
|
|
9
9
|
* `@rshono/core/server`.
|
|
10
10
|
*
|
|
11
|
+
* @see {@link https://www.rshono.com/docs/api#rshonocoreclient | Docs — `@rshono/core/client`}
|
|
12
|
+
*
|
|
11
13
|
* @packageDocumentation
|
|
12
14
|
*/
|
|
13
15
|
export { useNavigation, type NavigationRouter, type NavigationState } from './navigation.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/runtime/client.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/runtime/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,aAAa,EAAE,KAAK,gBAAgB,EAAE,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAC7F,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,KAAK,kBAAkB,EAAE,KAAK,aAAa,EAAE,MAAM,iBAAiB,CAAC"}
|
package/dist/runtime/client.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `@rshono/core/client` — the browser-side surface, for use from `'use client'`
|
|
3
|
-
* modules:
|
|
4
|
-
*
|
|
3
|
+
* modules: `useNavigation()` for the current URL and soft navigation, and
|
|
4
|
+
* `<AsyncBoundary>` / `<CatchBoundary>` as components.
|
|
5
5
|
*
|
|
6
6
|
* Every export is itself a `'use client'` module, so a server component can
|
|
7
|
-
* render
|
|
7
|
+
* render `<AsyncBoundary>` directly — but the hook needs a client component. In a
|
|
8
8
|
* server component, read the same request data from `getRequestContext()` in
|
|
9
9
|
* `@rshono/core/server`.
|
|
10
10
|
*
|
|
11
|
+
* @see {@link https://www.rshono.com/docs/api#rshonocoreclient | Docs — `@rshono/core/client`}
|
|
12
|
+
*
|
|
11
13
|
* @packageDocumentation
|
|
12
14
|
*/
|
|
13
15
|
export { useNavigation } from './navigation.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/runtime/client.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/runtime/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,aAAa,EAA+C,MAAM,iBAAiB,CAAC;AAC7F,OAAO,EAAE,aAAa,EAAE,aAAa,EAAwE,MAAM,iBAAiB,CAAC","sourcesContent":["/**\n * `@rshono/core/client` — the browser-side surface, for use from `'use client'`\n * modules: `useNavigation()` for the current URL and soft navigation, and\n * `<AsyncBoundary>` / `<CatchBoundary>` as components.\n *\n * Every export is itself a `'use client'` module, so a server component can\n * render `<AsyncBoundary>` directly — but the hook needs a client component. In a\n * server component, read the same request data from `getRequestContext()` in\n * `@rshono/core/server`.\n *\n * @see {@link https://www.rshono.com/docs/api#rshonocoreclient | Docs — `@rshono/core/client`}\n *\n * @packageDocumentation\n */\n\nexport { useNavigation, type NavigationRouter, type NavigationState } from './navigation.js';\nexport { AsyncBoundary, CatchBoundary, type AsyncBoundaryProps, type CatchBoundaryProps, type ErrorFallback } from './boundaries.js';\n"]}
|