@rshono/core 1.0.0-rc.2 → 1.0.0-rc.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (208) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +348 -158
  3. package/bin/rshono.mjs +3 -4
  4. package/dist/builder/env-shadow-loader.cjs +123 -8
  5. package/dist/builder/page-entry-loader.cjs +28 -1
  6. package/dist/builder/page-files.d.ts +0 -1
  7. package/dist/builder/page-files.js +7 -3
  8. package/dist/builder/page-files.js.map +1 -1
  9. package/dist/builder/public-env.d.ts +7 -1
  10. package/dist/builder/public-env.js +7 -0
  11. package/dist/builder/public-env.js.map +1 -1
  12. package/dist/builder/react-versions.d.ts +12 -0
  13. package/dist/builder/react-versions.js +74 -0
  14. package/dist/builder/react-versions.js.map +1 -0
  15. package/dist/builder/rspack-config.d.ts +13 -4
  16. package/dist/builder/rspack-config.js +136 -53
  17. package/dist/builder/rspack-config.js.map +1 -1
  18. package/dist/builder/server-only-imports.d.ts +32 -0
  19. package/dist/builder/server-only-imports.js +51 -0
  20. package/dist/builder/server-only-imports.js.map +1 -0
  21. package/dist/cli/build.d.ts +2 -3
  22. package/dist/cli/build.js +51 -13
  23. package/dist/cli/build.js.map +1 -1
  24. package/dist/cli/dev.d.ts +2 -3
  25. package/dist/cli/dev.js +149 -45
  26. package/dist/cli/dev.js.map +1 -1
  27. package/dist/cli/exit.d.ts +12 -0
  28. package/dist/cli/exit.js +16 -0
  29. package/dist/cli/exit.js.map +1 -0
  30. package/dist/cli/index.d.ts +0 -1
  31. package/dist/cli/index.js +91 -30
  32. package/dist/cli/index.js.map +1 -1
  33. package/dist/cli/start.d.ts +0 -1
  34. package/dist/cli/start.js +15 -18
  35. package/dist/cli/start.js.map +1 -1
  36. package/dist/config.d.ts +72 -78
  37. package/dist/config.js +17 -1
  38. package/dist/config.js.map +1 -1
  39. package/dist/deploy/aws-lambda/runtime.d.ts +4 -7
  40. package/dist/deploy/aws-lambda/runtime.js +5 -8
  41. package/dist/deploy/aws-lambda/runtime.js.map +1 -1
  42. package/dist/deploy/build-marker.d.ts +9 -6
  43. package/dist/deploy/build-marker.js +8 -4
  44. package/dist/deploy/build-marker.js.map +1 -1
  45. package/dist/deploy/cloudflare/build.d.ts +0 -1
  46. package/dist/deploy/cloudflare/build.js +17 -13
  47. package/dist/deploy/cloudflare/build.js.map +1 -1
  48. package/dist/deploy/cloudflare/runtime.d.ts +2 -6
  49. package/dist/deploy/cloudflare/runtime.js +73 -68
  50. package/dist/deploy/cloudflare/runtime.js.map +1 -1
  51. package/dist/deploy/contract.d.ts +57 -41
  52. package/dist/deploy/contract.js.map +1 -1
  53. package/dist/deploy/filesystem.d.ts +3 -6
  54. package/dist/deploy/filesystem.js +23 -20
  55. package/dist/deploy/filesystem.js.map +1 -1
  56. package/dist/deploy/node/runtime.d.ts +5 -3
  57. package/dist/deploy/node/runtime.js +23 -9
  58. package/dist/deploy/node/runtime.js.map +1 -1
  59. package/dist/deploy/presets.d.ts +34 -30
  60. package/dist/deploy/presets.js +56 -44
  61. package/dist/deploy/presets.js.map +1 -1
  62. package/dist/deploy/public-paths.d.ts +42 -0
  63. package/dist/deploy/public-paths.js +66 -0
  64. package/dist/deploy/public-paths.js.map +1 -0
  65. package/dist/deploy/vercel/build.d.ts +0 -1
  66. package/dist/deploy/vercel/build.js +25 -16
  67. package/dist/deploy/vercel/build.js.map +1 -1
  68. package/dist/deploy/vercel/runtime.d.ts +10 -7
  69. package/dist/deploy/vercel/runtime.js +48 -11
  70. package/dist/deploy/vercel/runtime.js.map +1 -1
  71. package/dist/index.d.ts +19 -17
  72. package/dist/index.js +20 -10
  73. package/dist/index.js.map +1 -1
  74. package/dist/router.d.ts +212 -120
  75. package/dist/router.js +3 -8
  76. package/dist/router.js.map +1 -1
  77. package/dist/runtime/boundaries.d.ts +62 -48
  78. package/dist/runtime/boundaries.js +32 -34
  79. package/dist/runtime/boundaries.js.map +1 -1
  80. package/dist/runtime/client.d.ts +20 -11
  81. package/dist/runtime/client.js +20 -10
  82. package/dist/runtime/client.js.map +1 -1
  83. package/dist/runtime/context.d.ts +304 -123
  84. package/dist/runtime/context.js +377 -149
  85. package/dist/runtime/context.js.map +1 -1
  86. package/dist/runtime/control.d.ts +15 -1
  87. package/dist/runtime/control.js +24 -0
  88. package/dist/runtime/control.js.map +1 -1
  89. package/dist/runtime/dev-protocol.d.ts +4 -9
  90. package/dist/runtime/dev-protocol.js.map +1 -1
  91. package/dist/runtime/empty-server-app.d.ts +0 -1
  92. package/dist/runtime/entry.client.d.ts +4 -1
  93. package/dist/runtime/entry.client.js +455 -313
  94. package/dist/runtime/entry.client.js.map +1 -1
  95. package/dist/runtime/entry.rsc.d.ts +15 -7
  96. package/dist/runtime/entry.rsc.js +811 -252
  97. package/dist/runtime/entry.rsc.js.map +1 -1
  98. package/dist/runtime/entry.ssr.d.ts +27 -10
  99. package/dist/runtime/entry.ssr.js +30 -48
  100. package/dist/runtime/entry.ssr.js.map +1 -1
  101. package/dist/runtime/failure-document.d.ts +11 -0
  102. package/dist/runtime/failure-document.js +35 -0
  103. package/dist/runtime/failure-document.js.map +1 -0
  104. package/dist/runtime/flight-inject.d.ts +23 -0
  105. package/dist/runtime/flight-inject.js +403 -0
  106. package/dist/runtime/flight-inject.js.map +1 -0
  107. package/dist/runtime/hot-update.d.ts +44 -0
  108. package/dist/runtime/hot-update.js +44 -0
  109. package/dist/runtime/hot-update.js.map +1 -0
  110. package/dist/runtime/navigation.d.ts +46 -54
  111. package/dist/runtime/navigation.js +22 -63
  112. package/dist/runtime/navigation.js.map +1 -1
  113. package/dist/runtime/request.d.ts +42 -8
  114. package/dist/runtime/request.js +84 -7
  115. package/dist/runtime/request.js.map +1 -1
  116. package/dist/runtime/server.d.ts +20 -18
  117. package/dist/runtime/server.js +22 -17
  118. package/dist/runtime/server.js.map +1 -1
  119. package/dist/runtime/validate-entries.d.ts +77 -0
  120. package/dist/runtime/validate-entries.js +405 -0
  121. package/dist/runtime/validate-entries.js.map +1 -0
  122. package/dist/server/headers.d.ts +41 -15
  123. package/dist/server/headers.js +53 -24
  124. package/dist/server/headers.js.map +1 -1
  125. package/dist/server/load-config.d.ts +4 -5
  126. package/dist/server/load-config.js +22 -14
  127. package/dist/server/load-config.js.map +1 -1
  128. package/dist/server/load-env.d.ts +0 -1
  129. package/dist/server/prerendered.d.ts +88 -34
  130. package/dist/server/prerendered.js +184 -24
  131. package/dist/server/prerendered.js.map +1 -1
  132. package/dist/server/server-config.d.ts +35 -50
  133. package/dist/server/server-config.js +27 -74
  134. package/dist/server/server-config.js.map +1 -1
  135. package/dist/server/shutdown.d.ts +5 -4
  136. package/dist/server/shutdown.js +5 -3
  137. package/dist/server/shutdown.js.map +1 -1
  138. package/dist/server/ssg.d.ts +9 -7
  139. package/dist/server/ssg.js +209 -65
  140. package/dist/server/ssg.js.map +1 -1
  141. package/dist/server/static.d.ts +0 -1
  142. package/dist/server/static.js +7 -1
  143. package/dist/server/static.js.map +1 -1
  144. package/package.json +21 -23
  145. package/dist/builder/page-files.d.ts.map +0 -1
  146. package/dist/builder/public-env.d.ts.map +0 -1
  147. package/dist/builder/rspack-config.d.ts.map +0 -1
  148. package/dist/cli/build.d.ts.map +0 -1
  149. package/dist/cli/dev.d.ts.map +0 -1
  150. package/dist/cli/index.d.ts.map +0 -1
  151. package/dist/cli/start.d.ts.map +0 -1
  152. package/dist/config.d.ts.map +0 -1
  153. package/dist/deploy/aws-lambda/runtime.d.ts.map +0 -1
  154. package/dist/deploy/build-marker.d.ts.map +0 -1
  155. package/dist/deploy/bun/runtime.d.ts +0 -11
  156. package/dist/deploy/bun/runtime.d.ts.map +0 -1
  157. package/dist/deploy/bun/runtime.js +0 -22
  158. package/dist/deploy/bun/runtime.js.map +0 -1
  159. package/dist/deploy/cloudflare/build.d.ts.map +0 -1
  160. package/dist/deploy/cloudflare/runtime.d.ts.map +0 -1
  161. package/dist/deploy/contract.d.ts.map +0 -1
  162. package/dist/deploy/deno/runtime.d.ts +0 -11
  163. package/dist/deploy/deno/runtime.d.ts.map +0 -1
  164. package/dist/deploy/deno/runtime.js +0 -16
  165. package/dist/deploy/deno/runtime.js.map +0 -1
  166. package/dist/deploy/filesystem.d.ts.map +0 -1
  167. package/dist/deploy/listen.d.ts +0 -20
  168. package/dist/deploy/listen.d.ts.map +0 -1
  169. package/dist/deploy/listen.js +0 -24
  170. package/dist/deploy/listen.js.map +0 -1
  171. package/dist/deploy/netlify/build.d.ts +0 -8
  172. package/dist/deploy/netlify/build.d.ts.map +0 -1
  173. package/dist/deploy/netlify/build.js +0 -52
  174. package/dist/deploy/netlify/build.js.map +0 -1
  175. package/dist/deploy/netlify/runtime.d.ts +0 -13
  176. package/dist/deploy/netlify/runtime.d.ts.map +0 -1
  177. package/dist/deploy/netlify/runtime.js +0 -24
  178. package/dist/deploy/netlify/runtime.js.map +0 -1
  179. package/dist/deploy/node/runtime.d.ts.map +0 -1
  180. package/dist/deploy/presets.d.ts.map +0 -1
  181. package/dist/deploy/vercel/build.d.ts.map +0 -1
  182. package/dist/deploy/vercel/runtime.d.ts.map +0 -1
  183. package/dist/index.d.ts.map +0 -1
  184. package/dist/router.d.ts.map +0 -1
  185. package/dist/runtime/boundaries.d.ts.map +0 -1
  186. package/dist/runtime/client.d.ts.map +0 -1
  187. package/dist/runtime/context.d.ts.map +0 -1
  188. package/dist/runtime/control.d.ts.map +0 -1
  189. package/dist/runtime/dev-protocol.d.ts.map +0 -1
  190. package/dist/runtime/empty-server-app.d.ts.map +0 -1
  191. package/dist/runtime/entry.client.d.ts.map +0 -1
  192. package/dist/runtime/entry.rsc.d.ts.map +0 -1
  193. package/dist/runtime/entry.ssr.d.ts.map +0 -1
  194. package/dist/runtime/navigation.d.ts.map +0 -1
  195. package/dist/runtime/request.d.ts.map +0 -1
  196. package/dist/runtime/server.d.ts.map +0 -1
  197. package/dist/server/compress.d.ts +0 -15
  198. package/dist/server/compress.d.ts.map +0 -1
  199. package/dist/server/compress.js +0 -76
  200. package/dist/server/compress.js.map +0 -1
  201. package/dist/server/headers.d.ts.map +0 -1
  202. package/dist/server/load-config.d.ts.map +0 -1
  203. package/dist/server/load-env.d.ts.map +0 -1
  204. package/dist/server/prerendered.d.ts.map +0 -1
  205. package/dist/server/server-config.d.ts.map +0 -1
  206. package/dist/server/shutdown.d.ts.map +0 -1
  207. package/dist/server/ssg.d.ts.map +0 -1
  208. package/dist/server/static.d.ts.map +0 -1
@@ -1,24 +1,29 @@
1
1
  /**
2
- * `@rshono/core/server` — the request-scoped surface, for use inside server components
3
- * and `'use server'` action modules: {@link getContext} for the URL, cookies,
4
- * params, env and middleware variables, the {@link redirect} and {@link notFound}
5
- * control-flow helpers, and {@link onServerError} for reporting the errors the
6
- * framework catches.
2
+ * `@rshono/core/server` — the request-scoped surface, for server components and `'use server'` action
3
+ * modules: `getRequestContext()` for the URL, cookies, params, env and middleware variables, the
4
+ * `redirect()` and `notFound()` control-flow helpers, and `onServerError()` for reporting the errors
5
+ * the framework catches. Plus `publicUrl(c)` for middleware, which is handed Hono's `c` rather than a
6
+ * request context.
7
7
  *
8
- * Server-only. Importing this from a `'use client'` module is a mistake those
9
- * run in the browser (and are SSR'd without a bound context). Read what you need
10
- * on the server and pass it down as props, or use `useNavigation()` from
11
- * `@rshono/core/client` for URL data.
8
+ * Server-only: a `'use client'` module runs in the browser, with no bound context. Read what you need
9
+ * on the server and pass it down as props, or use `useNavigation()` from `@rshono/core/client`.
12
10
  *
13
- * A barrel rather than `./context.js` itself, which is where all of this is
14
- * implemented: that module also exports the plumbing the framework's own entry
15
- * points need (`runWithContext`, `readParams`, `publicUrl`, `reportServerError`),
16
- * and pointing the `./server` subpath straight at it published every one of them —
17
- * `@internal` in the docs, but present in a consumer's autocomplete and pinned by
18
- * semver all the same. They stay reachable by relative import, which is the only
19
- * way the framework itself ever reaches them.
11
+ * @example
12
+ * ```ts
13
+ * 'use server';
14
+ * import { getRequestContext, redirect } from '@rshono/core/server';
15
+ *
16
+ * export async function logout() {
17
+ * getRequestContext().cookies.delete('session', { path: '/' });
18
+ * redirect('/');
19
+ * }
20
+ * ```
21
+ *
22
+ * @see {@link https://www.rshono.com/docs/api#rshonocoreserver | Docs — `@rshono/core/server`}
20
23
  *
21
24
  * @packageDocumentation
22
25
  */
23
- export { getContext, notFound, onServerError, redirect, } from './context.js';
26
+ // A barrel rather than pointing the subpath at `./context.js`, which also exports the framework's own
27
+ // plumbing — that would put `runWithContext` and friends in a consumer's autocomplete and under semver.
28
+ export { getRequestContext, notFound, onServerError, publicUrl, redirect, } from './context.js';
24
29
  //# sourceMappingURL=server.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/runtime/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EACL,UAAU,EACV,QAAQ,EACR,aAAa,EACb,QAAQ,GAST,MAAM,cAAc,CAAC","sourcesContent":["/**\n * `@rshono/core/server` — the request-scoped surface, for use inside server components\n * and `'use server'` action modules: {@link getContext} for the URL, cookies,\n * params, env and middleware variables, the {@link redirect} and {@link notFound}\n * control-flow helpers, and {@link onServerError} for reporting the errors the\n * framework catches.\n *\n * Server-only. Importing this from a `'use client'` module is a mistake — those\n * run in the browser (and are SSR'd without a bound context). Read what you need\n * on the server and pass it down as props, or use `useNavigation()` from\n * `@rshono/core/client` for URL data.\n *\n * A barrel rather than `./context.js` itself, which is where all of this is\n * implemented: that module also exports the plumbing the framework's own entry\n * points need (`runWithContext`, `readParams`, `publicUrl`, `reportServerError`),\n * and pointing the `./server` subpath straight at it published every one of them —\n * `@internal` in the docs, but present in a consumer's autocomplete and pinned by\n * semver all the same. They stay reachable by relative import, which is the only\n * way the framework itself ever reaches them.\n *\n * @packageDocumentation\n */\n\nexport {\n getContext,\n notFound,\n onServerError,\n redirect,\n // A type, not a value: `Ctx` is handed to you by `getContext()` or the `ctx` page\n // propone instance per request and is never constructed by application code.\n type Ctx,\n type EnvVars,\n type RedirectStatus,\n type ServerErrorContext,\n type ServerErrorHandler,\n type ServerErrorSource,\n} from './context.js';\n"]}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/runtime/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,sGAAsG;AACtG,wGAAwG;AACxG,OAAO,EACL,iBAAiB,EACjB,QAAQ,EACR,aAAa,EACb,SAAS,EACT,QAAQ,GAOT,MAAM,cAAc,CAAC","sourcesContent":["/**\n * `@rshono/core/server` — the request-scoped surface, for server components and `'use server'` action\n * modules: `getRequestContext()` for the URL, cookies, params, env and middleware variables, the\n * `redirect()` and `notFound()` control-flow helpers, and `onServerError()` for reporting the errors\n * the framework catches. Plus `publicUrl(c)` for middleware, which is handed Hono's `c` rather than a\n * request context.\n *\n * Server-only: a `'use client'` module runs in the browser, with no bound context. Read what you need\n * on the server and pass it down as props, or use `useNavigation()` from `@rshono/core/client`.\n *\n * @example\n * ```ts\n * 'use server';\n * import { getRequestContext, redirect } from '@rshono/core/server';\n *\n * export async function logout() {\n * getRequestContext().cookies.delete('session', { path: '/' });\n * redirect('/');\n * }\n * ```\n *\n * @see {@link https://www.rshono.com/docs/api#rshonocoreserver | Docs `@rshono/core/server`}\n *\n * @packageDocumentation\n */\n\n// A barrel rather than pointing the subpath at `./context.js`, which also exports the framework's own\n// plumbingthat would put `runWithContext` and friends in a consumer's autocomplete and under semver.\nexport {\n getRequestContext,\n notFound,\n onServerError,\n publicUrl,\n redirect,\n type EnvVars,\n type RedirectStatus,\n type RequestContext,\n type ServerErrorContext,\n type ServerErrorHandler,\n type ServerErrorSource,\n} from './context.js';\n"]}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * The two modules an app hands the framework — `src/routes.ts` and `src/server.ts` — checked before
3
+ * anything is built on them.
4
+ *
5
+ * Both arrive through a build-time alias that carries no type the compiler can hold on to, so they used to
6
+ * be cast and the mistake found later, somewhere else: a `routes` array that skipped `defineRoutes` surfaced
7
+ * as `TypeError: nN is not iterable` from a minified bundle, and a `src/server.ts` exporting the wrong thing
8
+ * as `Cannot read properties of undefined (reading 'map')` from inside Hono. Neither names the file to open.
9
+ * The mistakes that produced no error at all were worse: a duplicated `path` whose second entry silently
10
+ * never ran, a `staticPaths` on a route that renders per request.
11
+ *
12
+ * The route *table* is checked at module load — during `rshono build`, which imports the server bundle for
13
+ * the prerender pass; at `rshono dev` startup; and when a deployed server boots. The modules the table points
14
+ * *at* cannot be, because they load lazily: {@link assertPageModule} and {@link assertEndpointModule} run on
15
+ * first request, and {@link assertRouteModules} runs both against every route during `rshono build`.
16
+ */
17
+ import type { Handler, Hono } from 'hono';
18
+ import type { ServerEntry } from 'react-server-dom-rspack/server';
19
+ import { type PageComponent, type RouteConfig } from '../router.js';
20
+ /**
21
+ * Normalises and checks what `src/routes.ts` exported as `routes`.
22
+ *
23
+ * A bare array is accepted alongside a {@link RouteConfig}, because the docs present both and
24
+ * `defineRoutes` is an identity function over the array form — which makes leaving it off look equivalent
25
+ * right up to the point the build fails somewhere unrelated.
26
+ */
27
+ export declare function validateRoutesModule(exported: unknown): RouteConfig;
28
+ /**
29
+ * The Hono app `src/server.ts` default-exports, or `null` for an app that has no `src/server.ts` — the
30
+ * fallback module the alias resolves to instead default-exports exactly that.
31
+ *
32
+ * Duck-typed rather than `instanceof Hono`: what the framework does with the value is `app.route('/', …)`,
33
+ * which reads `routes` off it and mounts the handlers, so those are what have to be there.
34
+ */
35
+ export declare function validateServerApp(exported: unknown): Hono | null;
36
+ /**
37
+ * The server component a page module must default-export, checked rather than cast.
38
+ *
39
+ * Two structural mistakes land here, and neither can serve *any* request, which is why they are also checked
40
+ * at build time by {@link assertRouteModules}:
41
+ *
42
+ * - no default export at all — a named `export function Page`, or a file that exports its layout instead;
43
+ * - a `'use client'` page. That module's default export is a client reference, so it carries no
44
+ * `entryJsFiles` — the list of scripts the document has to boot, recorded only for a *server* entry.
45
+ * Rendering it anyway would produce a page with no client runtime, which is worse than a message.
46
+ */
47
+ export declare function assertPageModule(mod: {
48
+ default?: unknown;
49
+ }, label: string): ServerEntry<PageComponent>;
50
+ /**
51
+ * The Hono handler an endpoint module must export as `handler`.
52
+ *
53
+ * The page half of this fork has been checked since it existed; this half used to destructure and call, which
54
+ * turns the one mistake people actually make — a method-named export, the shape other frameworks ask for —
55
+ * into `TypeError: r is not a function` from a minified frame. That is verbatim the failure mode this file's
56
+ * header names as the reason it exists.
57
+ */
58
+ export declare function assertEndpointModule(mod: {
59
+ handler?: unknown;
60
+ }, label: string): Handler;
61
+ /**
62
+ * Resolves every route's own module once and runs the two checks above against it — what `rshono build` does
63
+ * once the bundle exists, so a route that cannot serve *any* request fails the build instead of exiting 0 and
64
+ * answering 500 in production for the life of the deployment.
65
+ *
66
+ * Every route is checked, not just the first that fails: these are all fixed by editing a file, and a build
67
+ * that names one of four broken routes costs three more builds to get through.
68
+ *
69
+ * A module that throws while *evaluating* is warned about rather than raised. Checking a module's shape means
70
+ * importing it, and whether an import succeeds is a question about the environment, not about the module: a
71
+ * page whose module scope reads a secret, opens a connection or touches a file that only exists in production
72
+ * fails here and works per request. Failing the build on that would make "every route module must be
73
+ * importable during the build" a new requirement on apps, to catch a mistake this cannot tell from a
74
+ * deliberate one. A `render: 'static'` route is the exception, and needs nothing here: it promised to render
75
+ * at build time, so the prerender pass demands the import and fails the build when it throws.
76
+ */
77
+ export declare function assertRouteModules(config: RouteConfig): Promise<void>;
@@ -0,0 +1,405 @@
1
+ /**
2
+ * The two modules an app hands the framework — `src/routes.ts` and `src/server.ts` — checked before
3
+ * anything is built on them.
4
+ *
5
+ * Both arrive through a build-time alias that carries no type the compiler can hold on to, so they used to
6
+ * be cast and the mistake found later, somewhere else: a `routes` array that skipped `defineRoutes` surfaced
7
+ * as `TypeError: nN is not iterable` from a minified bundle, and a `src/server.ts` exporting the wrong thing
8
+ * as `Cannot read properties of undefined (reading 'map')` from inside Hono. Neither names the file to open.
9
+ * The mistakes that produced no error at all were worse: a duplicated `path` whose second entry silently
10
+ * never ran, a `staticPaths` on a route that renders per request.
11
+ *
12
+ * The route *table* is checked at module load — during `rshono build`, which imports the server bundle for
13
+ * the prerender pass; at `rshono dev` startup; and when a deployed server boots. The modules the table points
14
+ * *at* cannot be, because they load lazily: {@link assertPageModule} and {@link assertEndpointModule} run on
15
+ * first request, and {@link assertRouteModules} runs both against every route during `rshono build`.
16
+ */
17
+ import { isPageRoute } from '../router.js';
18
+ /** Every method an endpoint route can name, `'all'` aside — which is also what `'all'` is expanded to below. */
19
+ const CONCRETE_METHODS = ['get', 'post', 'put', 'patch', 'delete', 'options'];
20
+ const METHODS = new Set([...CONCRETE_METHODS, 'all']);
21
+ /** What a page route's `render` accepts. */
22
+ const RENDER_MODES = ['static', 'dynamic'];
23
+ /**
24
+ * The one path prefix the framework claims on every deploy target: the hashed client bundle, mounted by
25
+ * `runtime.mountStaticAssets` ahead of the route table, ending in a terminal 404 — so nothing under it
26
+ * reaches a page. Written out here rather than imported, because the mounts live in the deploy runtimes
27
+ * (`deploy/filesystem.ts`, `deploy/cloudflare/runtime.ts`) and in `cli/dev.ts`, none of which this module
28
+ * can pull in.
29
+ *
30
+ * It is the only one. `/_rshono/hmr` is dev-only and an exact path rather than a prefix, and Cloudflare's
31
+ * `__ssg` is an asset path no app would name.
32
+ */
33
+ const RESERVED_PREFIX = '/_static';
34
+ function fail(message) {
35
+ throw new Error(`[rshono] ${message}`);
36
+ }
37
+ /**
38
+ * How a route is named in a message. Always by position, because that is what a duplicate `path` needs to be
39
+ * told apart, and by path as well wherever there is one.
40
+ */
41
+ function label(route, index) {
42
+ const path = route?.path;
43
+ return typeof path === 'string' ? `routes[${index}] ("${path}")` : `routes[${index}]`;
44
+ }
45
+ /** Refuses a key that belongs to the *other* kind of route — see {@link validateRoute}. */
46
+ function refuseForeignKeys(route, name, keys, advice) {
47
+ for (const key of keys) {
48
+ if (route[key] !== undefined)
49
+ fail(`src/routes.ts: ${name} has \`${key}\`, which ${advice}`);
50
+ }
51
+ }
52
+ /**
53
+ * Checks one entry of the route table.
54
+ *
55
+ * The cross-kind key checks are the ones TypeScript cannot make: excess-property checking against a union
56
+ * accepts any key present in *some* member, so `staticPaths` on an `{ type: 'endpoint' }` route type-checks
57
+ * and is then ignored at runtime — which is indistinguishable from prerendering that quietly does nothing.
58
+ */
59
+ function validateRoute(route, index) {
60
+ if (route === null || typeof route !== 'object') {
61
+ fail(`src/routes.ts: routes[${index}] is ${route === null ? 'null' : typeof route}, not a route object.`);
62
+ }
63
+ const entry = route;
64
+ const name = label(route, index);
65
+ if (typeof entry.path !== 'string' || !entry.path.startsWith('/')) {
66
+ fail(`src/routes.ts: ${name} needs a \`path\` starting with "/".`);
67
+ }
68
+ if (entry.type !== undefined && entry.type !== 'endpoint') {
69
+ fail(`src/routes.ts: ${name} has type ${JSON.stringify(entry.type)} — the only \`type\` is 'endpoint', and a page route omits it.`);
70
+ }
71
+ if (entry.type === 'endpoint') {
72
+ if (typeof entry.server !== 'function') {
73
+ fail(`src/routes.ts: ${name} is an endpoint, so it needs \`server\` — a function importing the module that exports \`handler\`.`);
74
+ }
75
+ if (entry.method !== undefined)
76
+ validateMethod(entry.method, name);
77
+ refuseForeignKeys(entry, name, ['component', 'render', 'staticPaths'], "only a page route has — an endpoint's `server` handler decides its own response.");
78
+ }
79
+ else {
80
+ if (typeof entry.component !== 'function') {
81
+ fail(`src/routes.ts: ${name} needs \`component\` — a function importing the page module, e.g. \`() => import('./pages/home')\`.`);
82
+ }
83
+ if (entry.render !== undefined && (typeof entry.render !== 'string' || !RENDER_MODES.includes(entry.render))) {
84
+ fail(`src/routes.ts: ${name} has render ${JSON.stringify(entry.render)} — it is 'static' or 'dynamic'.`);
85
+ }
86
+ if (entry.staticPaths !== undefined) {
87
+ if (typeof entry.staticPaths !== 'function')
88
+ fail(`src/routes.ts: ${name} has a \`staticPaths\` that is not a function.`);
89
+ if (entry.render !== 'static') {
90
+ fail(`src/routes.ts: ${name} has \`staticPaths\` but is not \`render: 'static'\`, so it renders per request and the paths are never built.`);
91
+ }
92
+ }
93
+ refuseForeignKeys(entry, name, ['server', 'method'], "only an endpoint route has — add `type: 'endpoint'` if that is what this is.");
94
+ }
95
+ }
96
+ /**
97
+ * Checks an endpoint's `method`, which is one method or a list of them.
98
+ *
99
+ * A list is checked member by member so the message names the bad one rather than printing the array, and
100
+ * `'all'` is refused inside one: a list meaning every method is a list its author did not mean to write.
101
+ */
102
+ function validateMethod(method, name) {
103
+ if (Array.isArray(method)) {
104
+ if (method.length === 0)
105
+ fail(`src/routes.ts: ${name} has an empty \`method\` list — omit it to answer every method.`);
106
+ for (const entry of method) {
107
+ if (entry === 'all') {
108
+ fail(`src/routes.ts: ${name} has 'all' inside a \`method\` list — use \`method: 'all'\`, or list the methods it answers.`);
109
+ }
110
+ validateMethod(entry, name);
111
+ }
112
+ return;
113
+ }
114
+ if (typeof method !== 'string' || !METHODS.has(method)) {
115
+ const meantHead = typeof method === 'string' && method.toLowerCase() === 'head';
116
+ fail(`src/routes.ts: ${name} has method ${JSON.stringify(method)}, which is not one of ${[...METHODS].join(', ')}.` +
117
+ (meantHead ? " A HEAD is dispatched as a GET, so use 'get' — a route registered for HEAD alone answers neither." : ''));
118
+ }
119
+ }
120
+ /** The methods a route answers, with `'all'` expanded and a list flattened, so two can be compared. */
121
+ function methodsOf(route) {
122
+ // A page is registered for GET and POST: the POST is how a `<form action={serverAction}>` reaches it.
123
+ if (isPageRoute(route))
124
+ return ['get', 'post'];
125
+ const methods = [route.method ?? 'all'].flat();
126
+ return methods.includes('all') ? CONCRETE_METHODS : methods;
127
+ }
128
+ /**
129
+ * A parameter's *name*, which is not part of the pattern Hono matches on. Anchored to a segment boundary so
130
+ * a `:` inside a `{regex}` constraint is not mistaken for one, and stopped by `{` and `?` — both of which
131
+ * *are* part of the pattern and have to survive into the key.
132
+ */
133
+ const PARAM_NAME = /(?<=\/):[^/{?]+/g;
134
+ /**
135
+ * What Hono matches on, rather than what was typed — so two spellings of one pattern hash alike.
136
+ *
137
+ * - **A parameter's name is not part of it.** `/u/:id` and `/u/:name` are one route, and whichever is
138
+ * registered second never runs. A `{regex}` constraint is kept, because that one *is* part of the
139
+ * pattern: `/a/:id{[0-9]+}` and `/a/:name` are genuinely different routes and both can answer.
140
+ * - **A `*` before the last segment matches exactly one non-empty segment** — the same thing an
141
+ * unconstrained parameter matches. Checked against Hono 4.13: `/a/[*]/c` and `/a/:id/c` both answer
142
+ * `/a/b/c`, both 404 `/a//c`, and both 404 `/a/b/x/c`.
143
+ *
144
+ * A *trailing* `*` is left alone: it claims a whole subtree rather than one segment, which is
145
+ * {@link subtreeOf}'s half of the job.
146
+ */
147
+ function patternKey(path) {
148
+ const segments = path.replace(PARAM_NAME, ':').split('/');
149
+ return segments.map((segment, index) => (segment === '*' && index < segments.length - 1 ? ':' : segment)).join('/');
150
+ }
151
+ /**
152
+ * Every key a route answers. More than one only for a trailing optional parameter: `/a/:id?` answers
153
+ * `/a/x` *and* `/a`, and — checked — neither `/a/` nor `/a/x/y`. Both have to be spoken for before it is
154
+ * dead, which is why this is a list rather than a key.
155
+ */
156
+ function keysFor(path) {
157
+ const key = patternKey(path);
158
+ if (!key.endsWith('/:?'))
159
+ return [key];
160
+ return [key.slice(0, -1), key.slice(0, -3) || '/'];
161
+ }
162
+ /**
163
+ * The subtree a trailing `*` claims, or `null` for a path without one. `/a/*` answers `/a/b` and
164
+ * `/a/b/c/d`, and the bare `/a` as well — but not `/ab`, so the boundary is the `/` and a prefix test has
165
+ * to respect it.
166
+ */
167
+ function subtreeOf(key) {
168
+ return key.endsWith('/*') ? key.slice(0, -2) : null;
169
+ }
170
+ /** Whether the subtree claimed by a trailing `*` at `prefix` contains `key`. */
171
+ function covers(prefix, key) {
172
+ return key === prefix || key.startsWith(`${prefix}/`);
173
+ }
174
+ /**
175
+ * Whether a path sits inside {@link RESERVED_PREFIX}'s subtree, which is the prefix and everything below
176
+ * it. The boundary is the `/`: `/_staticky` is an ordinary path and answers normally.
177
+ */
178
+ function isReserved(path) {
179
+ return path === RESERVED_PREFIX || path.startsWith(`${RESERVED_PREFIX}/`);
180
+ }
181
+ /**
182
+ * Refuses a route the table already answers in full, or that the framework's own mount already answers.
183
+ * Hono matches in registration order, so a later entry whose every method and path is spoken for is dead
184
+ * code — and the build that produced it exits 0 with nothing to say, which is how a duplicated `path`
185
+ * survives.
186
+ *
187
+ * The framework's mount is checked first and by name. The rest of this function compares the app's routes
188
+ * with *each other*, so the prefixes the framework registers ahead of them were invisible to it: a route at
189
+ * `/_static/thing` built clean and then 404'd, on every target and in dev. Only a literal path is refused —
190
+ * a parameterised route that happens to match under the prefix, `/:section/thing` or a root `/*`, still
191
+ * answers everything else and is none of this rule's business.
192
+ *
193
+ * Method by method, and only when *all* of them are taken: one path split between a `'get'` endpoint and a
194
+ * `'post'` one shadows nothing, and neither does a catch-all registered behind a route that claims one
195
+ * method of it — that one still answers the rest.
196
+ *
197
+ * Keyed on the *pattern* rather than on the path as written, because a dead route need not be a duplicated
198
+ * string. Two shapes reached a build this way: `/u/:id` followed by `/u/:name`, which is the same route
199
+ * twice under two spellings, and a wildcard registered *ahead* of a concrete path — `/a/*` then `/a/b` —
200
+ * where the second can never be reached. The doc comment above used to discuss only the opposite order, a
201
+ * catch-all registered behind a route, which is the case that is fine.
202
+ *
203
+ * **Where the line is drawn**, for whoever widens this next. A `{regex}` constraint is kept in the key and
204
+ * compared as text, so `/a/:id{[0-9]+}` then `/a/:n{[0-9]+}` is caught and `/a/:id{[0-9]+}` then
205
+ * `/a/:id{\d+}` is not — the second pair is equivalent, and the later route is dead, and this accepts it.
206
+ * Deciding regex equivalence in general is undecidable-adjacent and not a route validator's job. The same
207
+ * goes for a constraint containing a `/`, which the key carries through unchanged rather than interpreting.
208
+ * Both leave the error on the safe side: an unreachable route slips through, and a route that can still
209
+ * answer is never refused. Anything added here should keep that asymmetry — a false refusal fails a build
210
+ * that was correct, which is much worse than the hole it would close.
211
+ */
212
+ function assertNothingIsShadowed(routes) {
213
+ /** `"<method> <key>"` → the first route answering it. */
214
+ const claimed = new Map();
215
+ /** The trailing-`*` routes already registered, which claim a whole subtree rather than single keys. */
216
+ const subtrees = [];
217
+ const claimantOf = (method, key) => claimed.get(`${method} ${key}`) ?? subtrees.find((subtree) => subtree.method === method && covers(subtree.prefix, key));
218
+ routes.forEach((route, index) => {
219
+ const name = label(route, index);
220
+ if (isReserved(route.path)) {
221
+ fail(`src/routes.ts: ${name} would never run — the framework serves the client bundle at ${RESERVED_PREFIX}, ` +
222
+ `mounted ahead of the route table and answering that whole subtree, so ${RESERVED_PREFIX} is reserved on every ` +
223
+ 'deploy target and under `rshono dev`. Give the route a path of its own.');
224
+ }
225
+ const methods = methodsOf(route);
226
+ const keys = keysFor(route.path);
227
+ const claimants = methods.flatMap((method) => keys.map((key) => claimantOf(method, key)));
228
+ if (claimants.every((claimant) => claimant !== undefined)) {
229
+ const by = [...new Set(claimants.map((claimant) => claimant.name))].join(' and ');
230
+ const verbs = methods.map((method) => method.toUpperCase()).join(', ');
231
+ // Only when the two are spelled differently, which is the part that reads as a false alarm: an exact
232
+ // duplicate needs no explaining, and this sentence would only get in its way.
233
+ const why = claimants.some((claimant) => claimant.path === route.path)
234
+ ? ''
235
+ : ' Hono matches on the pattern, not the spelling — a parameter’s name is not part of it, and a trailing `*` answers its whole subtree, the bare prefix included.';
236
+ fail(`src/routes.ts: ${name} would never run — ${by} already answers ${verbs} ${route.path}, and Hono matches in registration order.${why}`);
237
+ }
238
+ for (const method of methods) {
239
+ for (const key of keys) {
240
+ if (!claimed.has(`${method} ${key}`))
241
+ claimed.set(`${method} ${key}`, { name, path: route.path });
242
+ }
243
+ const prefix = subtreeOf(patternKey(route.path));
244
+ if (prefix !== null)
245
+ subtrees.push({ method, prefix, name, path: route.path });
246
+ }
247
+ });
248
+ }
249
+ function validateFallbackPage(page, field) {
250
+ if (page === undefined || page === null)
251
+ return;
252
+ if (typeof page !== 'object' || typeof page.component !== 'function') {
253
+ fail(`src/routes.ts: \`${field}\` must be a page — \`{ component: () => import('./pages/${field}') }\`.`);
254
+ }
255
+ }
256
+ /**
257
+ * Normalises and checks what `src/routes.ts` exported as `routes`.
258
+ *
259
+ * A bare array is accepted alongside a {@link RouteConfig}, because the docs present both and
260
+ * `defineRoutes` is an identity function over the array form — which makes leaving it off look equivalent
261
+ * right up to the point the build fails somewhere unrelated.
262
+ */
263
+ export function validateRoutesModule(exported) {
264
+ const config = (Array.isArray(exported) ? { routes: exported } : exported);
265
+ if (config === null || config === undefined || typeof config !== 'object' || !Array.isArray(config.routes)) {
266
+ fail('src/routes.ts must export `routes` as a route array, or as a { routes, notFound?, error? } object — ' +
267
+ 'wrap it in `defineRoutes(…)` to have TypeScript check it for you.');
268
+ }
269
+ config.routes.forEach(validateRoute);
270
+ assertNothingIsShadowed(config.routes);
271
+ validateFallbackPage(config.notFound, 'notFound');
272
+ validateFallbackPage(config.error, 'error');
273
+ return config;
274
+ }
275
+ /**
276
+ * The Hono app `src/server.ts` default-exports, or `null` for an app that has no `src/server.ts` — the
277
+ * fallback module the alias resolves to instead default-exports exactly that.
278
+ *
279
+ * Duck-typed rather than `instanceof Hono`: what the framework does with the value is `app.route('/', …)`,
280
+ * which reads `routes` off it and mounts the handlers, so those are what have to be there.
281
+ */
282
+ export function validateServerApp(exported) {
283
+ const app = exported?.default;
284
+ if (app === null || app === undefined)
285
+ return null;
286
+ if (typeof app !== 'object' || typeof app.fetch !== 'function' || !Array.isArray(app.routes)) {
287
+ fail('src/server.ts must `export default` a Hono app — `const server = new Hono(); … export default server;`.');
288
+ }
289
+ return app;
290
+ }
291
+ /**
292
+ * The server component a page module must default-export, checked rather than cast.
293
+ *
294
+ * Two structural mistakes land here, and neither can serve *any* request, which is why they are also checked
295
+ * at build time by {@link assertRouteModules}:
296
+ *
297
+ * - no default export at all — a named `export function Page`, or a file that exports its layout instead;
298
+ * - a `'use client'` page. That module's default export is a client reference, so it carries no
299
+ * `entryJsFiles` — the list of scripts the document has to boot, recorded only for a *server* entry.
300
+ * Rendering it anyway would produce a page with no client runtime, which is worse than a message.
301
+ */
302
+ export function assertPageModule(mod, label) {
303
+ const Page = mod.default;
304
+ if (typeof Page !== 'function') {
305
+ fail(`The page module for ${label} must default-export a server component.`);
306
+ }
307
+ if (!Page.entryJsFiles) {
308
+ fail(`The page component for ${label} is missing its client-asset info ('use server-entry'). ` +
309
+ "The directive is added automatically for inline `component: () => import('…')` thunks in routes.ts. " +
310
+ "If this page is wired up another way, put 'use server-entry' on the first line of the page module yourself — " +
311
+ "and make sure the page is a server component (a 'use client' page must be wrapped by a server component instead).");
312
+ }
313
+ return Page;
314
+ }
315
+ /**
316
+ * The Hono handler an endpoint module must export as `handler`.
317
+ *
318
+ * The page half of this fork has been checked since it existed; this half used to destructure and call, which
319
+ * turns the one mistake people actually make — a method-named export, the shape other frameworks ask for —
320
+ * into `TypeError: r is not a function` from a minified frame. That is verbatim the failure mode this file's
321
+ * header names as the reason it exists.
322
+ */
323
+ export function assertEndpointModule(mod, label) {
324
+ const handler = mod.handler;
325
+ if (typeof handler !== 'function') {
326
+ fail(`The endpoint module for ${label} must export \`handler\` — \`export const handler: Handler = (c) => c.json({ ok: true });\`. ` +
327
+ 'One `handler` answers every method the route declares, so a method-named export (`GET`, `POST`) is never read.');
328
+ }
329
+ return handler;
330
+ }
331
+ /**
332
+ * Resolves every route's own module once and runs the two checks above against it — what `rshono build` does
333
+ * once the bundle exists, so a route that cannot serve *any* request fails the build instead of exiting 0 and
334
+ * answering 500 in production for the life of the deployment.
335
+ *
336
+ * Every route is checked, not just the first that fails: these are all fixed by editing a file, and a build
337
+ * that names one of four broken routes costs three more builds to get through.
338
+ *
339
+ * A module that throws while *evaluating* is warned about rather than raised. Checking a module's shape means
340
+ * importing it, and whether an import succeeds is a question about the environment, not about the module: a
341
+ * page whose module scope reads a secret, opens a connection or touches a file that only exists in production
342
+ * fails here and works per request. Failing the build on that would make "every route module must be
343
+ * importable during the build" a new requirement on apps, to catch a mistake this cannot tell from a
344
+ * deliberate one. A `render: 'static'` route is the exception, and needs nothing here: it promised to render
345
+ * at build time, so the prerender pass demands the import and fails the build when it throws.
346
+ */
347
+ export async function assertRouteModules(config) {
348
+ const failures = [];
349
+ /**
350
+ * Loads one route's module, or reports why it could not be checked. `null` — never a silent skip: the
351
+ * whole point of running at build time is saying what was and was not looked at.
352
+ */
353
+ const load = async (thunk, label) => {
354
+ try {
355
+ return await thunk();
356
+ }
357
+ catch (error) {
358
+ const reason = error instanceof Error ? error.message : String(error);
359
+ console.warn(` ⚠ ${label} could not be loaded at build time, so its module was not checked — ${reason}`);
360
+ return null;
361
+ }
362
+ };
363
+ /** Keeps a check's message instead of raising it, so every broken route is named rather than the first. */
364
+ const check = (assert) => {
365
+ try {
366
+ assert();
367
+ }
368
+ catch (error) {
369
+ failures.push(error instanceof Error ? error.message : String(error));
370
+ }
371
+ };
372
+ for (const route of config.routes) {
373
+ const label = `"${route.path}"`;
374
+ if (isPageRoute(route)) {
375
+ const mod = await load(route.component, label);
376
+ if (mod)
377
+ check(() => assertPageModule(mod, label));
378
+ }
379
+ else {
380
+ const mod = await load(route.server, label);
381
+ if (mod)
382
+ check(() => assertEndpointModule(mod, label));
383
+ }
384
+ }
385
+ for (const [page, label] of [
386
+ [config.notFound, 'the notFound page'],
387
+ [config.error, 'the error page'],
388
+ ]) {
389
+ if (!page)
390
+ continue;
391
+ const mod = await load(page.component, label);
392
+ if (mod)
393
+ check(() => assertPageModule(mod, label));
394
+ }
395
+ // One failure — overwhelmingly the common case — is raised as itself, so the message reads the way it
396
+ // does on the request path. Several are gathered under a count, each stripped of the prefix the whole
397
+ // list now carries once.
398
+ if (failures.length === 1)
399
+ throw new Error(failures[0]);
400
+ if (failures.length > 1) {
401
+ fail(`${failures.length} route modules cannot serve a request:\n` +
402
+ failures.map((message) => ` • ${message.replace(/^\[rshono\] /, '')}`).join('\n'));
403
+ }
404
+ }
405
+ //# sourceMappingURL=validate-entries.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-entries.js","sourceRoot":"","sources":["../../src/runtime/validate-entries.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The two modules an app hands the framework — `src/routes.ts` and `src/server.ts` — checked before\n * anything is built on them.\n *\n * Both arrive through a build-time alias that carries no type the compiler can hold on to, so they used to\n * be cast and the mistake found later, somewhere else: a `routes` array that skipped `defineRoutes` surfaced\n * as `TypeError: nN is not iterable` from a minified bundle, and a `src/server.ts` exporting the wrong thing\n * as `Cannot read properties of undefined (reading 'map')` from inside Hono. Neither names the file to open.\n * The mistakes that produced no error at all were worse: a duplicated `path` whose second entry silently\n * never ran, a `staticPaths` on a route that renders per request.\n *\n * The route *table* is checked at module load — during `rshono build`, which imports the server bundle for\n * the prerender pass; at `rshono dev` startup; and when a deployed server boots. The modules the table points\n * *at* cannot be, because they load lazily: {@link assertPageModule} and {@link assertEndpointModule} run on\n * first request, and {@link assertRouteModules} runs both against every route during `rshono build`.\n */\n\nimport type { Handler, Hono } from 'hono';\nimport type { ServerEntry } from 'react-server-dom-rspack/server';\nimport { isPageRoute, type PageComponent, type Route, type RouteConfig } from '../router.js';\n\n/** Every method an endpoint route can name, `'all'` aside — which is also what `'all'` is expanded to below. */\nconst CONCRETE_METHODS = ['get', 'post', 'put', 'patch', 'delete', 'options'];\nconst METHODS = new Set([...CONCRETE_METHODS, 'all']);\n\n/** What a page route's `render` accepts. */\nconst RENDER_MODES = ['static', 'dynamic'];\n\n/**\n * The one path prefix the framework claims on every deploy target: the hashed client bundle, mounted by\n * `runtime.mountStaticAssets` ahead of the route table, ending in a terminal 404 — so nothing under it\n * reaches a page. Written out here rather than imported, because the mounts live in the deploy runtimes\n * (`deploy/filesystem.ts`, `deploy/cloudflare/runtime.ts`) and in `cli/dev.ts`, none of which this module\n * can pull in.\n *\n * It is the only one. `/_rshono/hmr` is dev-only and an exact path rather than a prefix, and Cloudflare's\n * `__ssg` is an asset path no app would name.\n */\nconst RESERVED_PREFIX = '/_static';\n\nfunction fail(message: string): never {\n throw new Error(`[rshono] ${message}`);\n}\n\n/**\n * How a route is named in a message. Always by position, because that is what a duplicate `path` needs to be\n * told apart, and by path as well wherever there is one.\n */\nfunction label(route: unknown, index: number): string {\n const path = (route as { path?: unknown } | null)?.path;\n return typeof path === 'string' ? `routes[${index}] (\"${path}\")` : `routes[${index}]`;\n}\n\n/** Refuses a key that belongs to the *other* kind of route — see {@link validateRoute}. */\nfunction refuseForeignKeys(route: Record<string, unknown>, name: string, keys: readonly string[], advice: string): void {\n for (const key of keys) {\n if (route[key] !== undefined) fail(`src/routes.ts: ${name} has \\`${key}\\`, which ${advice}`);\n }\n}\n\n/**\n * Checks one entry of the route table.\n *\n * The cross-kind key checks are the ones TypeScript cannot make: excess-property checking against a union\n * accepts any key present in *some* member, so `staticPaths` on an `{ type: 'endpoint' }` route type-checks\n * and is then ignored at runtime — which is indistinguishable from prerendering that quietly does nothing.\n */\nfunction validateRoute(route: unknown, index: number): void {\n if (route === null || typeof route !== 'object') {\n fail(`src/routes.ts: routes[${index}] is ${route === null ? 'null' : typeof route}, not a route object.`);\n }\n const entry = route as Record<string, unknown>;\n const name = label(route, index);\n\n if (typeof entry.path !== 'string' || !entry.path.startsWith('/')) {\n fail(`src/routes.ts: ${name} needs a \\`path\\` starting with \"/\".`);\n }\n if (entry.type !== undefined && entry.type !== 'endpoint') {\n fail(`src/routes.ts: ${name} has type ${JSON.stringify(entry.type)} — the only \\`type\\` is 'endpoint', and a page route omits it.`);\n }\n\n if (entry.type === 'endpoint') {\n if (typeof entry.server !== 'function') {\n fail(`src/routes.ts: ${name} is an endpoint, so it needs \\`server\\` — a function importing the module that exports \\`handler\\`.`);\n }\n if (entry.method !== undefined) validateMethod(entry.method, name);\n refuseForeignKeys(\n entry,\n name,\n ['component', 'render', 'staticPaths'],\n \"only a page route has — an endpoint's `server` handler decides its own response.\",\n );\n } else {\n if (typeof entry.component !== 'function') {\n fail(`src/routes.ts: ${name} needs \\`component\\` — a function importing the page module, e.g. \\`() => import('./pages/home')\\`.`);\n }\n if (entry.render !== undefined && (typeof entry.render !== 'string' || !RENDER_MODES.includes(entry.render))) {\n fail(`src/routes.ts: ${name} has render ${JSON.stringify(entry.render)} — it is 'static' or 'dynamic'.`);\n }\n if (entry.staticPaths !== undefined) {\n if (typeof entry.staticPaths !== 'function') fail(`src/routes.ts: ${name} has a \\`staticPaths\\` that is not a function.`);\n if (entry.render !== 'static') {\n fail(`src/routes.ts: ${name} has \\`staticPaths\\` but is not \\`render: 'static'\\`, so it renders per request and the paths are never built.`);\n }\n }\n refuseForeignKeys(entry, name, ['server', 'method'], \"only an endpoint route has — add `type: 'endpoint'` if that is what this is.\");\n }\n}\n\n/**\n * Checks an endpoint's `method`, which is one method or a list of them.\n *\n * A list is checked member by member so the message names the bad one rather than printing the array, and\n * `'all'` is refused inside one: a list meaning every method is a list its author did not mean to write.\n */\nfunction validateMethod(method: unknown, name: string): void {\n if (Array.isArray(method)) {\n if (method.length === 0) fail(`src/routes.ts: ${name} has an empty \\`method\\` list — omit it to answer every method.`);\n for (const entry of method as unknown[]) {\n if (entry === 'all') {\n fail(`src/routes.ts: ${name} has 'all' inside a \\`method\\` list — use \\`method: 'all'\\`, or list the methods it answers.`);\n }\n validateMethod(entry, name);\n }\n return;\n }\n if (typeof method !== 'string' || !METHODS.has(method)) {\n const meantHead = typeof method === 'string' && method.toLowerCase() === 'head';\n fail(\n `src/routes.ts: ${name} has method ${JSON.stringify(method)}, which is not one of ${[...METHODS].join(', ')}.` +\n (meantHead ? \" A HEAD is dispatched as a GET, so use 'get' — a route registered for HEAD alone answers neither.\" : ''),\n );\n }\n}\n\n/** The methods a route answers, with `'all'` expanded and a list flattened, so two can be compared. */\nfunction methodsOf(route: Route): readonly string[] {\n // A page is registered for GET and POST: the POST is how a `<form action={serverAction}>` reaches it.\n if (isPageRoute(route)) return ['get', 'post'];\n const methods = [route.method ?? 'all'].flat();\n return methods.includes('all') ? CONCRETE_METHODS : methods;\n}\n\n/**\n * A parameter's *name*, which is not part of the pattern Hono matches on. Anchored to a segment boundary so\n * a `:` inside a `{regex}` constraint is not mistaken for one, and stopped by `{` and `?` — both of which\n * *are* part of the pattern and have to survive into the key.\n */\nconst PARAM_NAME = /(?<=\\/):[^/{?]+/g;\n\n/**\n * What Hono matches on, rather than what was typed — so two spellings of one pattern hash alike.\n *\n * - **A parameter's name is not part of it.** `/u/:id` and `/u/:name` are one route, and whichever is\n * registered second never runs. A `{regex}` constraint is kept, because that one *is* part of the\n * pattern: `/a/:id{[0-9]+}` and `/a/:name` are genuinely different routes and both can answer.\n * - **A `*` before the last segment matches exactly one non-empty segment** — the same thing an\n * unconstrained parameter matches. Checked against Hono 4.13: `/a/[*]/c` and `/a/:id/c` both answer\n * `/a/b/c`, both 404 `/a//c`, and both 404 `/a/b/x/c`.\n *\n * A *trailing* `*` is left alone: it claims a whole subtree rather than one segment, which is\n * {@link subtreeOf}'s half of the job.\n */\nfunction patternKey(path: string): string {\n const segments = path.replace(PARAM_NAME, ':').split('/');\n return segments.map((segment, index) => (segment === '*' && index < segments.length - 1 ? ':' : segment)).join('/');\n}\n\n/**\n * Every key a route answers. More than one only for a trailing optional parameter: `/a/:id?` answers\n * `/a/x` *and* `/a`, and — checked — neither `/a/` nor `/a/x/y`. Both have to be spoken for before it is\n * dead, which is why this is a list rather than a key.\n */\nfunction keysFor(path: string): readonly string[] {\n const key = patternKey(path);\n if (!key.endsWith('/:?')) return [key];\n return [key.slice(0, -1), key.slice(0, -3) || '/'];\n}\n\n/**\n * The subtree a trailing `*` claims, or `null` for a path without one. `/a/*` answers `/a/b` and\n * `/a/b/c/d`, and the bare `/a` as well — but not `/ab`, so the boundary is the `/` and a prefix test has\n * to respect it.\n */\nfunction subtreeOf(key: string): string | null {\n return key.endsWith('/*') ? key.slice(0, -2) : null;\n}\n\n/** Whether the subtree claimed by a trailing `*` at `prefix` contains `key`. */\nfunction covers(prefix: string, key: string): boolean {\n return key === prefix || key.startsWith(`${prefix}/`);\n}\n\n/**\n * Whether a path sits inside {@link RESERVED_PREFIX}'s subtree, which is the prefix and everything below\n * it. The boundary is the `/`: `/_staticky` is an ordinary path and answers normally.\n */\nfunction isReserved(path: string): boolean {\n return path === RESERVED_PREFIX || path.startsWith(`${RESERVED_PREFIX}/`);\n}\n\n/**\n * Refuses a route the table already answers in full, or that the framework's own mount already answers.\n * Hono matches in registration order, so a later entry whose every method and path is spoken for is dead\n * code — and the build that produced it exits 0 with nothing to say, which is how a duplicated `path`\n * survives.\n *\n * The framework's mount is checked first and by name. The rest of this function compares the app's routes\n * with *each other*, so the prefixes the framework registers ahead of them were invisible to it: a route at\n * `/_static/thing` built clean and then 404'd, on every target and in dev. Only a literal path is refused —\n * a parameterised route that happens to match under the prefix, `/:section/thing` or a root `/*`, still\n * answers everything else and is none of this rule's business.\n *\n * Method by method, and only when *all* of them are taken: one path split between a `'get'` endpoint and a\n * `'post'` one shadows nothing, and neither does a catch-all registered behind a route that claims one\n * method of it — that one still answers the rest.\n *\n * Keyed on the *pattern* rather than on the path as written, because a dead route need not be a duplicated\n * string. Two shapes reached a build this way: `/u/:id` followed by `/u/:name`, which is the same route\n * twice under two spellings, and a wildcard registered *ahead* of a concrete path — `/a/*` then `/a/b` —\n * where the second can never be reached. The doc comment above used to discuss only the opposite order, a\n * catch-all registered behind a route, which is the case that is fine.\n *\n * **Where the line is drawn**, for whoever widens this next. A `{regex}` constraint is kept in the key and\n * compared as text, so `/a/:id{[0-9]+}` then `/a/:n{[0-9]+}` is caught and `/a/:id{[0-9]+}` then\n * `/a/:id{\\d+}` is not — the second pair is equivalent, and the later route is dead, and this accepts it.\n * Deciding regex equivalence in general is undecidable-adjacent and not a route validator's job. The same\n * goes for a constraint containing a `/`, which the key carries through unchanged rather than interpreting.\n * Both leave the error on the safe side: an unreachable route slips through, and a route that can still\n * answer is never refused. Anything added here should keep that asymmetry — a false refusal fails a build\n * that was correct, which is much worse than the hole it would close.\n */\nfunction assertNothingIsShadowed(routes: readonly Route[]): void {\n /** `\"<method> <key>\"` → the first route answering it. */\n const claimed = new Map<string, { name: string; path: string }>();\n /** The trailing-`*` routes already registered, which claim a whole subtree rather than single keys. */\n const subtrees: { method: string; prefix: string; name: string; path: string }[] = [];\n\n const claimantOf = (method: string, key: string) =>\n claimed.get(`${method} ${key}`) ?? subtrees.find((subtree) => subtree.method === method && covers(subtree.prefix, key));\n\n routes.forEach((route, index) => {\n const name = label(route, index);\n if (isReserved(route.path)) {\n fail(\n `src/routes.ts: ${name} would never run — the framework serves the client bundle at ${RESERVED_PREFIX}, ` +\n `mounted ahead of the route table and answering that whole subtree, so ${RESERVED_PREFIX} is reserved on every ` +\n 'deploy target and under `rshono dev`. Give the route a path of its own.',\n );\n }\n const methods = methodsOf(route);\n const keys = keysFor(route.path);\n const claimants = methods.flatMap((method) => keys.map((key) => claimantOf(method, key)));\n if (claimants.every((claimant) => claimant !== undefined)) {\n const by = [...new Set(claimants.map((claimant) => claimant.name))].join(' and ');\n const verbs = methods.map((method) => method.toUpperCase()).join(', ');\n // Only when the two are spelled differently, which is the part that reads as a false alarm: an exact\n // duplicate needs no explaining, and this sentence would only get in its way.\n const why = claimants.some((claimant) => claimant.path === route.path)\n ? ''\n : ' Hono matches on the pattern, not the spelling — a parameter’s name is not part of it, and a trailing `*` answers its whole subtree, the bare prefix included.';\n fail(`src/routes.ts: ${name} would never run — ${by} already answers ${verbs} ${route.path}, and Hono matches in registration order.${why}`);\n }\n for (const method of methods) {\n for (const key of keys) {\n if (!claimed.has(`${method} ${key}`)) claimed.set(`${method} ${key}`, { name, path: route.path });\n }\n const prefix = subtreeOf(patternKey(route.path));\n if (prefix !== null) subtrees.push({ method, prefix, name, path: route.path });\n }\n });\n}\n\nfunction validateFallbackPage(page: unknown, field: 'notFound' | 'error'): void {\n if (page === undefined || page === null) return;\n if (typeof page !== 'object' || typeof (page as { component?: unknown }).component !== 'function') {\n fail(`src/routes.ts: \\`${field}\\` must be a page — \\`{ component: () => import('./pages/${field}') }\\`.`);\n }\n}\n\n/**\n * Normalises and checks what `src/routes.ts` exported as `routes`.\n *\n * A bare array is accepted alongside a {@link RouteConfig}, because the docs present both and\n * `defineRoutes` is an identity function over the array form — which makes leaving it off look equivalent\n * right up to the point the build fails somewhere unrelated.\n */\nexport function validateRoutesModule(exported: unknown): RouteConfig {\n const config = (Array.isArray(exported) ? { routes: exported as Route[] } : exported) as RouteConfig | null | undefined;\n if (config === null || config === undefined || typeof config !== 'object' || !Array.isArray(config.routes)) {\n fail(\n 'src/routes.ts must export `routes` as a route array, or as a { routes, notFound?, error? } object — ' +\n 'wrap it in `defineRoutes(…)` to have TypeScript check it for you.',\n );\n }\n\n config.routes.forEach(validateRoute);\n assertNothingIsShadowed(config.routes);\n validateFallbackPage(config.notFound, 'notFound');\n validateFallbackPage(config.error, 'error');\n return config;\n}\n\n/**\n * The Hono app `src/server.ts` default-exports, or `null` for an app that has no `src/server.ts` — the\n * fallback module the alias resolves to instead default-exports exactly that.\n *\n * Duck-typed rather than `instanceof Hono`: what the framework does with the value is `app.route('/', …)`,\n * which reads `routes` off it and mounts the handlers, so those are what have to be there.\n */\nexport function validateServerApp(exported: unknown): Hono | null {\n const app = (exported as { default?: unknown } | null)?.default;\n if (app === null || app === undefined) return null;\n if (typeof app !== 'object' || typeof (app as Hono).fetch !== 'function' || !Array.isArray((app as Hono).routes)) {\n fail('src/server.ts must `export default` a Hono app — `const server = new Hono(); … export default server;`.');\n }\n return app as Hono;\n}\n\n/**\n * The server component a page module must default-export, checked rather than cast.\n *\n * Two structural mistakes land here, and neither can serve *any* request, which is why they are also checked\n * at build time by {@link assertRouteModules}:\n *\n * - no default export at all — a named `export function Page`, or a file that exports its layout instead;\n * - a `'use client'` page. That module's default export is a client reference, so it carries no\n * `entryJsFiles` — the list of scripts the document has to boot, recorded only for a *server* entry.\n * Rendering it anyway would produce a page with no client runtime, which is worse than a message.\n */\nexport function assertPageModule(mod: { default?: unknown }, label: string): ServerEntry<PageComponent> {\n const Page = mod.default as ServerEntry<PageComponent> | undefined;\n if (typeof Page !== 'function') {\n fail(`The page module for ${label} must default-export a server component.`);\n }\n if (!Page.entryJsFiles) {\n fail(\n `The page component for ${label} is missing its client-asset info ('use server-entry'). ` +\n \"The directive is added automatically for inline `component: () => import('…')` thunks in routes.ts. \" +\n \"If this page is wired up another way, put 'use server-entry' on the first line of the page module yourself — \" +\n \"and make sure the page is a server component (a 'use client' page must be wrapped by a server component instead).\",\n );\n }\n return Page;\n}\n\n/**\n * The Hono handler an endpoint module must export as `handler`.\n *\n * The page half of this fork has been checked since it existed; this half used to destructure and call, which\n * turns the one mistake people actually make — a method-named export, the shape other frameworks ask for —\n * into `TypeError: r is not a function` from a minified frame. That is verbatim the failure mode this file's\n * header names as the reason it exists.\n */\nexport function assertEndpointModule(mod: { handler?: unknown }, label: string): Handler {\n const handler = mod.handler;\n if (typeof handler !== 'function') {\n fail(\n `The endpoint module for ${label} must export \\`handler\\` — \\`export const handler: Handler = (c) => c.json({ ok: true });\\`. ` +\n 'One `handler` answers every method the route declares, so a method-named export (`GET`, `POST`) is never read.',\n );\n }\n return handler as Handler;\n}\n\n/**\n * Resolves every route's own module once and runs the two checks above against it — what `rshono build` does\n * once the bundle exists, so a route that cannot serve *any* request fails the build instead of exiting 0 and\n * answering 500 in production for the life of the deployment.\n *\n * Every route is checked, not just the first that fails: these are all fixed by editing a file, and a build\n * that names one of four broken routes costs three more builds to get through.\n *\n * A module that throws while *evaluating* is warned about rather than raised. Checking a module's shape means\n * importing it, and whether an import succeeds is a question about the environment, not about the module: a\n * page whose module scope reads a secret, opens a connection or touches a file that only exists in production\n * fails here and works per request. Failing the build on that would make \"every route module must be\n * importable during the build\" a new requirement on apps, to catch a mistake this cannot tell from a\n * deliberate one. A `render: 'static'` route is the exception, and needs nothing here: it promised to render\n * at build time, so the prerender pass demands the import and fails the build when it throws.\n */\nexport async function assertRouteModules(config: RouteConfig): Promise<void> {\n const failures: string[] = [];\n\n /**\n * Loads one route's module, or reports why it could not be checked. `null` — never a silent skip: the\n * whole point of running at build time is saying what was and was not looked at.\n */\n const load = async <T>(thunk: () => Promise<T>, label: string): Promise<T | null> => {\n try {\n return await thunk();\n } catch (error) {\n const reason = error instanceof Error ? error.message : String(error);\n console.warn(` ⚠ ${label} could not be loaded at build time, so its module was not checked — ${reason}`);\n return null;\n }\n };\n\n /** Keeps a check's message instead of raising it, so every broken route is named rather than the first. */\n const check = (assert: () => void): void => {\n try {\n assert();\n } catch (error) {\n failures.push(error instanceof Error ? error.message : String(error));\n }\n };\n\n for (const route of config.routes) {\n const label = `\"${route.path}\"`;\n if (isPageRoute(route)) {\n const mod = await load(route.component, label);\n if (mod) check(() => assertPageModule(mod, label));\n } else {\n const mod = await load(route.server, label);\n if (mod) check(() => assertEndpointModule(mod, label));\n }\n }\n for (const [page, label] of [\n [config.notFound, 'the notFound page'],\n [config.error, 'the error page'],\n ] as const) {\n if (!page) continue;\n const mod = await load(page.component, label);\n if (mod) check(() => assertPageModule(mod, label));\n }\n\n // One failure — overwhelmingly the common case — is raised as itself, so the message reads the way it\n // does on the request path. Several are gathered under a count, each stripped of the prefix the whole\n // list now carries once.\n if (failures.length === 1) throw new Error(failures[0]);\n if (failures.length > 1) {\n fail(\n `${failures.length} route modules cannot serve a request:\\n` +\n failures.map((message) => ` • ${message.replace(/^\\[rshono\\] /, '')}`).join('\\n'),\n );\n }\n}\n"]}