@rangojs/router 0.0.0-experimental.8 → 0.0.0-experimental.8a4d0430

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 (300) hide show
  1. package/AGENTS.md +5 -0
  2. package/README.md +884 -4
  3. package/dist/bin/rango.js +1601 -0
  4. package/dist/vite/index.js +4474 -867
  5. package/package.json +60 -51
  6. package/skills/breadcrumbs/SKILL.md +250 -0
  7. package/skills/cache-guide/SKILL.md +262 -0
  8. package/skills/caching/SKILL.md +50 -21
  9. package/skills/composability/SKILL.md +172 -0
  10. package/skills/debug-manifest/SKILL.md +12 -8
  11. package/skills/document-cache/SKILL.md +18 -16
  12. package/skills/fonts/SKILL.md +167 -0
  13. package/skills/hooks/SKILL.md +334 -72
  14. package/skills/host-router/SKILL.md +218 -0
  15. package/skills/intercept/SKILL.md +131 -8
  16. package/skills/layout/SKILL.md +100 -3
  17. package/skills/links/SKILL.md +89 -30
  18. package/skills/loader/SKILL.md +388 -38
  19. package/skills/middleware/SKILL.md +171 -34
  20. package/skills/mime-routes/SKILL.md +128 -0
  21. package/skills/parallel/SKILL.md +78 -1
  22. package/skills/prerender/SKILL.md +643 -0
  23. package/skills/rango/SKILL.md +85 -16
  24. package/skills/response-routes/SKILL.md +411 -0
  25. package/skills/route/SKILL.md +226 -14
  26. package/skills/router-setup/SKILL.md +123 -30
  27. package/skills/tailwind/SKILL.md +129 -0
  28. package/skills/theme/SKILL.md +9 -8
  29. package/skills/typesafety/SKILL.md +318 -89
  30. package/skills/use-cache/SKILL.md +324 -0
  31. package/src/__internal.ts +102 -4
  32. package/src/bin/rango.ts +321 -0
  33. package/src/browser/action-coordinator.ts +97 -0
  34. package/src/browser/action-response-classifier.ts +99 -0
  35. package/src/browser/event-controller.ts +87 -64
  36. package/src/browser/history-state.ts +80 -0
  37. package/src/browser/intercept-utils.ts +52 -0
  38. package/src/browser/link-interceptor.ts +24 -4
  39. package/src/browser/logging.ts +55 -0
  40. package/src/browser/merge-segment-loaders.ts +20 -12
  41. package/src/browser/navigation-bridge.ts +285 -553
  42. package/src/browser/navigation-client.ts +124 -71
  43. package/src/browser/navigation-store.ts +33 -50
  44. package/src/browser/navigation-transaction.ts +295 -0
  45. package/src/browser/network-error-handler.ts +61 -0
  46. package/src/browser/partial-update.ts +258 -308
  47. package/src/browser/prefetch/cache.ts +146 -0
  48. package/src/browser/prefetch/fetch.ts +135 -0
  49. package/src/browser/prefetch/observer.ts +65 -0
  50. package/src/browser/prefetch/policy.ts +42 -0
  51. package/src/browser/prefetch/queue.ts +88 -0
  52. package/src/browser/rango-state.ts +112 -0
  53. package/src/browser/react/Link.tsx +185 -73
  54. package/src/browser/react/NavigationProvider.tsx +51 -11
  55. package/src/browser/react/context.ts +6 -0
  56. package/src/browser/react/filter-segment-order.ts +11 -0
  57. package/src/browser/react/index.ts +12 -12
  58. package/src/browser/react/location-state-shared.ts +95 -53
  59. package/src/browser/react/location-state.ts +60 -15
  60. package/src/browser/react/mount-context.ts +6 -1
  61. package/src/browser/react/nonce-context.ts +23 -0
  62. package/src/browser/react/shallow-equal.ts +27 -0
  63. package/src/browser/react/use-action.ts +29 -51
  64. package/src/browser/react/use-client-cache.ts +5 -3
  65. package/src/browser/react/use-handle.ts +32 -79
  66. package/src/browser/react/use-href.tsx +2 -2
  67. package/src/browser/react/use-link-status.ts +6 -5
  68. package/src/browser/react/use-navigation.ts +22 -63
  69. package/src/browser/react/use-params.ts +65 -0
  70. package/src/browser/react/use-pathname.ts +47 -0
  71. package/src/browser/react/use-router.ts +63 -0
  72. package/src/browser/react/use-search-params.ts +56 -0
  73. package/src/browser/react/use-segments.ts +80 -97
  74. package/src/browser/response-adapter.ts +73 -0
  75. package/src/browser/rsc-router.tsx +107 -26
  76. package/src/browser/scroll-restoration.ts +92 -16
  77. package/src/browser/segment-reconciler.ts +216 -0
  78. package/src/browser/segment-structure-assert.ts +16 -0
  79. package/src/browser/server-action-bridge.ts +504 -599
  80. package/src/browser/shallow.ts +6 -1
  81. package/src/browser/types.ts +109 -47
  82. package/src/browser/validate-redirect-origin.ts +29 -0
  83. package/src/build/generate-manifest.ts +235 -24
  84. package/src/build/generate-route-types.ts +36 -0
  85. package/src/build/index.ts +13 -0
  86. package/src/build/route-trie.ts +265 -0
  87. package/src/build/route-types/ast-helpers.ts +25 -0
  88. package/src/build/route-types/ast-route-extraction.ts +98 -0
  89. package/src/build/route-types/codegen.ts +102 -0
  90. package/src/build/route-types/include-resolution.ts +411 -0
  91. package/src/build/route-types/param-extraction.ts +48 -0
  92. package/src/build/route-types/per-module-writer.ts +128 -0
  93. package/src/build/route-types/router-processing.ts +469 -0
  94. package/src/build/route-types/scan-filter.ts +78 -0
  95. package/src/build/runtime-discovery.ts +231 -0
  96. package/src/cache/background-task.ts +34 -0
  97. package/src/cache/cache-key-utils.ts +44 -0
  98. package/src/cache/cache-policy.ts +125 -0
  99. package/src/cache/cache-runtime.ts +338 -0
  100. package/src/cache/cache-scope.ts +120 -303
  101. package/src/cache/cf/cf-cache-store.ts +119 -7
  102. package/src/cache/cf/index.ts +8 -2
  103. package/src/cache/document-cache.ts +101 -72
  104. package/src/cache/handle-capture.ts +81 -0
  105. package/src/cache/handle-snapshot.ts +41 -0
  106. package/src/cache/index.ts +0 -15
  107. package/src/cache/memory-segment-store.ts +191 -13
  108. package/src/cache/profile-registry.ts +73 -0
  109. package/src/cache/read-through-swr.ts +134 -0
  110. package/src/cache/segment-codec.ts +256 -0
  111. package/src/cache/taint.ts +98 -0
  112. package/src/cache/types.ts +72 -122
  113. package/src/client.rsc.tsx +3 -1
  114. package/src/client.tsx +106 -126
  115. package/src/component-utils.ts +4 -4
  116. package/src/components/DefaultDocument.tsx +5 -1
  117. package/src/context-var.ts +86 -0
  118. package/src/debug.ts +17 -7
  119. package/src/errors.ts +108 -2
  120. package/src/handle.ts +15 -29
  121. package/src/handles/MetaTags.tsx +73 -20
  122. package/src/handles/breadcrumbs.ts +66 -0
  123. package/src/handles/index.ts +1 -0
  124. package/src/handles/meta.ts +30 -13
  125. package/src/host/cookie-handler.ts +21 -15
  126. package/src/host/errors.ts +8 -8
  127. package/src/host/index.ts +4 -7
  128. package/src/host/pattern-matcher.ts +27 -27
  129. package/src/host/router.ts +61 -39
  130. package/src/host/testing.ts +8 -8
  131. package/src/host/types.ts +15 -7
  132. package/src/host/utils.ts +1 -1
  133. package/src/href-client.ts +119 -29
  134. package/src/index.rsc.ts +153 -19
  135. package/src/index.ts +211 -30
  136. package/src/internal-debug.ts +11 -0
  137. package/src/loader.rsc.ts +26 -157
  138. package/src/loader.ts +27 -10
  139. package/src/network-error-thrower.tsx +3 -1
  140. package/src/outlet-provider.tsx +45 -0
  141. package/src/prerender/param-hash.ts +37 -0
  142. package/src/prerender/store.ts +185 -0
  143. package/src/prerender.ts +463 -0
  144. package/src/reverse.ts +330 -0
  145. package/src/root-error-boundary.tsx +41 -29
  146. package/src/route-content-wrapper.tsx +7 -4
  147. package/src/route-definition/dsl-helpers.ts +934 -0
  148. package/src/route-definition/helper-factories.ts +200 -0
  149. package/src/route-definition/helpers-types.ts +430 -0
  150. package/src/route-definition/index.ts +52 -0
  151. package/src/route-definition/redirect.ts +93 -0
  152. package/src/route-definition.ts +1 -1428
  153. package/src/route-map-builder.ts +211 -123
  154. package/src/route-name.ts +53 -0
  155. package/src/route-types.ts +59 -8
  156. package/src/router/content-negotiation.ts +116 -0
  157. package/src/router/debug-manifest.ts +72 -0
  158. package/src/router/error-handling.ts +9 -9
  159. package/src/router/find-match.ts +158 -0
  160. package/src/router/handler-context.ts +374 -81
  161. package/src/router/intercept-resolution.ts +395 -0
  162. package/src/router/lazy-includes.ts +234 -0
  163. package/src/router/loader-resolution.ts +215 -122
  164. package/src/router/logging.ts +248 -0
  165. package/src/router/manifest.ts +148 -35
  166. package/src/router/match-api.ts +620 -0
  167. package/src/router/match-context.ts +5 -3
  168. package/src/router/match-handlers.ts +440 -0
  169. package/src/router/match-middleware/background-revalidation.ts +80 -93
  170. package/src/router/match-middleware/cache-lookup.ts +382 -9
  171. package/src/router/match-middleware/cache-store.ts +51 -22
  172. package/src/router/match-middleware/intercept-resolution.ts +55 -17
  173. package/src/router/match-middleware/segment-resolution.ts +24 -6
  174. package/src/router/match-pipelines.ts +10 -45
  175. package/src/router/match-result.ts +34 -28
  176. package/src/router/metrics.ts +235 -15
  177. package/src/router/middleware-cookies.ts +55 -0
  178. package/src/router/middleware-types.ts +222 -0
  179. package/src/router/middleware.ts +324 -367
  180. package/src/router/pattern-matching.ts +211 -43
  181. package/src/router/prerender-match.ts +402 -0
  182. package/src/router/preview-match.ts +170 -0
  183. package/src/router/revalidation.ts +137 -38
  184. package/src/router/router-context.ts +36 -21
  185. package/src/router/router-interfaces.ts +452 -0
  186. package/src/router/router-options.ts +592 -0
  187. package/src/router/router-registry.ts +24 -0
  188. package/src/router/segment-resolution/fresh.ts +570 -0
  189. package/src/router/segment-resolution/helpers.ts +263 -0
  190. package/src/router/segment-resolution/loader-cache.ts +198 -0
  191. package/src/router/segment-resolution/revalidation.ts +1241 -0
  192. package/src/router/segment-resolution/static-store.ts +67 -0
  193. package/src/router/segment-resolution.ts +21 -0
  194. package/src/router/segment-wrappers.ts +289 -0
  195. package/src/router/telemetry-otel.ts +299 -0
  196. package/src/router/telemetry.ts +300 -0
  197. package/src/router/timeout.ts +148 -0
  198. package/src/router/trie-matching.ts +239 -0
  199. package/src/router/types.ts +77 -3
  200. package/src/router.ts +692 -4257
  201. package/src/rsc/handler-context.ts +45 -0
  202. package/src/rsc/handler.ts +764 -754
  203. package/src/rsc/helpers.ts +140 -6
  204. package/src/rsc/index.ts +0 -20
  205. package/src/rsc/loader-fetch.ts +209 -0
  206. package/src/rsc/manifest-init.ts +86 -0
  207. package/src/rsc/nonce.ts +14 -0
  208. package/src/rsc/origin-guard.ts +141 -0
  209. package/src/rsc/progressive-enhancement.ts +379 -0
  210. package/src/rsc/response-error.ts +37 -0
  211. package/src/rsc/response-route-handler.ts +347 -0
  212. package/src/rsc/rsc-rendering.ts +235 -0
  213. package/src/rsc/runtime-warnings.ts +42 -0
  214. package/src/rsc/server-action.ts +348 -0
  215. package/src/rsc/ssr-setup.ts +128 -0
  216. package/src/rsc/types.ts +38 -11
  217. package/src/search-params.ts +230 -0
  218. package/src/segment-system.tsx +25 -13
  219. package/src/server/context.ts +182 -51
  220. package/src/server/cookie-store.ts +190 -0
  221. package/src/server/fetchable-loader-store.ts +37 -0
  222. package/src/server/handle-store.ts +94 -15
  223. package/src/server/loader-registry.ts +15 -56
  224. package/src/server/request-context.ts +430 -70
  225. package/src/server.ts +35 -130
  226. package/src/ssr/index.tsx +100 -31
  227. package/src/static-handler.ts +114 -0
  228. package/src/theme/ThemeProvider.tsx +21 -15
  229. package/src/theme/ThemeScript.tsx +5 -5
  230. package/src/theme/constants.ts +5 -2
  231. package/src/theme/index.ts +4 -14
  232. package/src/theme/theme-context.ts +4 -30
  233. package/src/theme/theme-script.ts +21 -18
  234. package/src/types/boundaries.ts +158 -0
  235. package/src/types/cache-types.ts +198 -0
  236. package/src/types/error-types.ts +192 -0
  237. package/src/types/global-namespace.ts +100 -0
  238. package/src/types/handler-context.ts +687 -0
  239. package/src/types/index.ts +88 -0
  240. package/src/types/loader-types.ts +183 -0
  241. package/src/types/route-config.ts +170 -0
  242. package/src/types/route-entry.ts +102 -0
  243. package/src/types/segments.ts +148 -0
  244. package/src/types.ts +1 -1623
  245. package/src/urls/include-helper.ts +197 -0
  246. package/src/urls/index.ts +53 -0
  247. package/src/urls/path-helper-types.ts +339 -0
  248. package/src/urls/path-helper.ts +329 -0
  249. package/src/urls/pattern-types.ts +95 -0
  250. package/src/urls/response-types.ts +106 -0
  251. package/src/urls/type-extraction.ts +372 -0
  252. package/src/urls/urls-function.ts +98 -0
  253. package/src/urls.ts +1 -802
  254. package/src/use-loader.tsx +85 -77
  255. package/src/vite/discovery/bundle-postprocess.ts +184 -0
  256. package/src/vite/discovery/discover-routers.ts +344 -0
  257. package/src/vite/discovery/prerender-collection.ts +385 -0
  258. package/src/vite/discovery/route-types-writer.ts +258 -0
  259. package/src/vite/discovery/self-gen-tracking.ts +47 -0
  260. package/src/vite/discovery/state.ts +110 -0
  261. package/src/vite/discovery/virtual-module-codegen.ts +203 -0
  262. package/src/vite/index.ts +11 -1133
  263. package/src/vite/plugin-types.ts +131 -0
  264. package/src/vite/plugins/cjs-to-esm.ts +93 -0
  265. package/src/vite/plugins/client-ref-dedup.ts +115 -0
  266. package/src/vite/plugins/client-ref-hashing.ts +105 -0
  267. package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +72 -51
  268. package/src/vite/plugins/expose-id-utils.ts +287 -0
  269. package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
  270. package/src/vite/plugins/expose-ids/handler-transform.ts +179 -0
  271. package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
  272. package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
  273. package/src/vite/plugins/expose-ids/types.ts +45 -0
  274. package/src/vite/plugins/expose-internal-ids.ts +569 -0
  275. package/src/vite/plugins/refresh-cmd.ts +65 -0
  276. package/src/vite/plugins/use-cache-transform.ts +323 -0
  277. package/src/vite/plugins/version-injector.ts +83 -0
  278. package/src/vite/plugins/version-plugin.ts +254 -0
  279. package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +23 -14
  280. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  281. package/src/vite/rango.ts +510 -0
  282. package/src/vite/router-discovery.ts +785 -0
  283. package/src/vite/utils/ast-handler-extract.ts +517 -0
  284. package/src/vite/utils/banner.ts +36 -0
  285. package/src/vite/utils/bundle-analysis.ts +137 -0
  286. package/src/vite/utils/manifest-utils.ts +70 -0
  287. package/src/vite/{package-resolution.ts → utils/package-resolution.ts} +25 -29
  288. package/src/vite/utils/prerender-utils.ts +189 -0
  289. package/src/vite/utils/shared-utils.ts +169 -0
  290. package/CLAUDE.md +0 -43
  291. package/src/browser/lru-cache.ts +0 -69
  292. package/src/browser/request-controller.ts +0 -164
  293. package/src/cache/memory-store.ts +0 -253
  294. package/src/href-context.ts +0 -33
  295. package/src/href.ts +0 -255
  296. package/src/server/route-manifest-cache.ts +0 -173
  297. package/src/vite/expose-handle-id.ts +0 -209
  298. package/src/vite/expose-loader-id.ts +0 -426
  299. package/src/vite/expose-location-state-id.ts +0 -177
  300. /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
@@ -22,7 +22,7 @@ export class HostRouterError extends Error {
22
22
  if (options?.cause) {
23
23
  this.cause = options.cause;
24
24
  }
25
- this.name = 'HostRouterError';
25
+ this.name = "HostRouterError";
26
26
  Object.setPrototypeOf(this, HostRouterError.prototype);
27
27
  }
28
28
  }
@@ -33,7 +33,7 @@ export class HostRouterError extends Error {
33
33
  export class InvalidPatternError extends HostRouterError {
34
34
  constructor(pattern: string, reason: string, options?: ErrorOptions) {
35
35
  super(`Invalid pattern "${pattern}": ${reason}`, options);
36
- this.name = 'InvalidPatternError';
36
+ this.name = "InvalidPatternError";
37
37
  Object.setPrototypeOf(this, InvalidPatternError.prototype);
38
38
  }
39
39
  }
@@ -45,9 +45,9 @@ export class HostOverrideNotAllowedError extends HostRouterError {
45
45
  constructor(currentHost: string, cookieName: string, options?: ErrorOptions) {
46
46
  super(
47
47
  `Host override not allowed on "${currentHost}" (cookie: ${cookieName})`,
48
- options
48
+ options,
49
49
  );
50
- this.name = 'HostOverrideNotAllowedError';
50
+ this.name = "HostOverrideNotAllowedError";
51
51
  Object.setPrototypeOf(this, HostOverrideNotAllowedError.prototype);
52
52
  }
53
53
  }
@@ -58,7 +58,7 @@ export class HostOverrideNotAllowedError extends HostRouterError {
58
58
  export class InvalidHostnameError extends HostRouterError {
59
59
  constructor(hostname: string, options?: ErrorOptions) {
60
60
  super(`Invalid hostname format: "${hostname}"`, options);
61
- this.name = 'InvalidHostnameError';
61
+ this.name = "InvalidHostnameError";
62
62
  Object.setPrototypeOf(this, InvalidHostnameError.prototype);
63
63
  }
64
64
  }
@@ -69,7 +69,7 @@ export class InvalidHostnameError extends HostRouterError {
69
69
  export class HostValidationError extends HostRouterError {
70
70
  constructor(message: string, cause?: unknown) {
71
71
  super(message, { cause });
72
- this.name = 'HostValidationError';
72
+ this.name = "HostValidationError";
73
73
  Object.setPrototypeOf(this, HostValidationError.prototype);
74
74
  }
75
75
  }
@@ -80,7 +80,7 @@ export class HostValidationError extends HostRouterError {
80
80
  export class NoRouteMatchError extends HostRouterError {
81
81
  constructor(hostname: string, pathname: string, options?: ErrorOptions) {
82
82
  super(`No route matched for ${hostname}${pathname}`, options);
83
- this.name = 'NoRouteMatchError';
83
+ this.name = "NoRouteMatchError";
84
84
  Object.setPrototypeOf(this, NoRouteMatchError.prototype);
85
85
  }
86
86
  }
@@ -91,7 +91,7 @@ export class NoRouteMatchError extends HostRouterError {
91
91
  export class InvalidHandlerError extends HostRouterError {
92
92
  constructor(handler: unknown, options?: ErrorOptions) {
93
93
  super(`Invalid handler type: ${typeof handler}`, options);
94
- this.name = 'InvalidHandlerError';
94
+ this.name = "InvalidHandlerError";
95
95
  Object.setPrototypeOf(this, InvalidHandlerError.prototype);
96
96
  }
97
97
  }
package/src/host/index.ts CHANGED
@@ -23,13 +23,10 @@
23
23
  */
24
24
 
25
25
  // Core router
26
- export { createHostRouter } from './router.js';
27
-
28
- // Host router registry for build-time discovery
29
- export { HostRouterRegistry, type HostRouterRegistryEntry } from './router.js';
26
+ export { createHostRouter } from "./router.js";
30
27
 
31
28
  // Utilities
32
- export { defineHosts } from './utils.js';
29
+ export { defineHosts } from "./utils.js";
33
30
 
34
31
  // Errors
35
32
  export {
@@ -40,7 +37,7 @@ export {
40
37
  HostValidationError,
41
38
  NoRouteMatchError,
42
39
  InvalidHandlerError,
43
- } from './errors.js';
40
+ } from "./errors.js";
44
41
 
45
42
  // Types
46
43
  export type {
@@ -53,4 +50,4 @@ export type {
53
50
  HostPattern,
54
51
  HostMatchResult,
55
52
  HostOverrideConfig,
56
- } from './types.js';
53
+ } from "./types.js";
@@ -14,17 +14,17 @@
14
14
  * - `example.com/admin` - specific domain with path prefix
15
15
  */
16
16
 
17
- import { InvalidPatternError } from './errors.js';
17
+ import { InvalidPatternError } from "./errors.js";
18
18
 
19
19
  /**
20
20
  * Normalize a pattern by removing trailing slashes from paths
21
21
  */
22
22
  export function normalizePattern(pattern: string): string {
23
23
  // If pattern has a path component, remove trailing slash
24
- const slashIndex = pattern.indexOf('/');
24
+ const slashIndex = pattern.indexOf("/");
25
25
  if (slashIndex !== -1) {
26
26
  const domain = pattern.slice(0, slashIndex);
27
- const path = pattern.slice(slashIndex).replace(/\/$/, '');
27
+ const path = pattern.slice(slashIndex).replace(/\/$/, "");
28
28
  return domain + path;
29
29
  }
30
30
  return pattern;
@@ -41,7 +41,7 @@ export function parseRequest(request: Request): {
41
41
  const url = new URL(request.url);
42
42
  const hostname = url.hostname;
43
43
  const pathname = url.pathname;
44
- const parts = hostname.split('.');
44
+ const parts = hostname.split(".");
45
45
 
46
46
  return { hostname, pathname, parts };
47
47
  }
@@ -70,12 +70,12 @@ export function matchPattern(
70
70
  pattern: string,
71
71
  hostname: string,
72
72
  pathname: string,
73
- parts: string[]
73
+ parts: string[],
74
74
  ): boolean {
75
75
  const normalized = normalizePattern(pattern);
76
76
 
77
77
  // Check if pattern has path component
78
- const slashIndex = normalized.indexOf('/');
78
+ const slashIndex = normalized.indexOf("/");
79
79
  const hasPath = slashIndex !== -1;
80
80
  const domainPattern = hasPath ? normalized.slice(0, slashIndex) : normalized;
81
81
  const pathPattern = hasPath ? normalized.slice(slashIndex) : null;
@@ -88,7 +88,7 @@ export function matchPattern(
88
88
 
89
89
  // Then match path (prefix match)
90
90
  if (pathPattern) {
91
- return pathname === pathPattern || pathname.startsWith(pathPattern + '/');
91
+ return pathname === pathPattern || pathname.startsWith(pathPattern + "/");
92
92
  }
93
93
 
94
94
  return true;
@@ -100,7 +100,7 @@ export function matchPattern(
100
100
  function matchDomainPattern(
101
101
  pattern: string,
102
102
  hostname: string,
103
- parts: string[]
103
+ parts: string[],
104
104
  ): boolean {
105
105
  // Exact match
106
106
  if (pattern === hostname) {
@@ -108,48 +108,48 @@ function matchDomainPattern(
108
108
  }
109
109
 
110
110
  // `.` or `*` - any apex domain
111
- if (pattern === '.' || pattern === '*') {
111
+ if (pattern === "." || pattern === "*") {
112
112
  return isApexDomain(parts);
113
113
  }
114
114
 
115
115
  // `**` - any domain (apex + all subdomains)
116
- if (pattern === '**') {
116
+ if (pattern === "**") {
117
117
  return true;
118
118
  }
119
119
 
120
120
  // `*.` - any single-level subdomain
121
- if (pattern === '*.') {
121
+ if (pattern === "*.") {
122
122
  return getSubdomainLevel(parts) === 1;
123
123
  }
124
124
 
125
125
  // `**.` - any multi-level subdomain (2+ levels)
126
- if (pattern === '**.') {
126
+ if (pattern === "**.") {
127
127
  return getSubdomainLevel(parts) >= 2;
128
128
  }
129
129
 
130
130
  // `*.tld` - any apex domain with specific TLD (e.g., *.com)
131
- if (pattern.startsWith('*.') && !pattern.includes('.', 2)) {
131
+ if (pattern.startsWith("*.") && !pattern.includes(".", 2)) {
132
132
  const tld = pattern.slice(2);
133
- return isApexDomain(parts) && hostname.endsWith('.' + tld);
133
+ return isApexDomain(parts) && hostname.endsWith("." + tld);
134
134
  }
135
135
 
136
136
  // `*.example.com` - single subdomain of specific domain
137
- if (pattern.startsWith('*.')) {
137
+ if (pattern.startsWith("*.")) {
138
138
  const baseDomain = pattern.slice(2);
139
- if (hostname.endsWith('.' + baseDomain)) {
139
+ if (hostname.endsWith("." + baseDomain)) {
140
140
  // Count parts: if pattern is *.example.com (3 parts),
141
141
  // hostname should have exactly 4 parts (www.example.com)
142
- const patternParts = baseDomain.split('.');
142
+ const patternParts = baseDomain.split(".");
143
143
  return parts.length === patternParts.length + 1;
144
144
  }
145
145
  return false;
146
146
  }
147
147
 
148
148
  // `**.example.com` - any depth subdomain of specific domain
149
- if (pattern.startsWith('**.')) {
149
+ if (pattern.startsWith("**.")) {
150
150
  const baseDomain = pattern.slice(3);
151
- if (hostname.endsWith('.' + baseDomain)) {
152
- const patternParts = baseDomain.split('.');
151
+ if (hostname.endsWith("." + baseDomain)) {
152
+ const patternParts = baseDomain.split(".");
153
153
  // Must have more parts than the base domain (i.e., has subdomains)
154
154
  return parts.length > patternParts.length;
155
155
  }
@@ -158,7 +158,7 @@ function matchDomainPattern(
158
158
 
159
159
  // `subdomain.*` - specific subdomain of any apex domain
160
160
  // e.g., admin.* matches admin.example.com, admin.google.com
161
- if (pattern.endsWith('.*')) {
161
+ if (pattern.endsWith(".*")) {
162
162
  const subdomain = pattern.slice(0, -2);
163
163
  // Must be single-level subdomain (3 parts total)
164
164
  if (parts.length === 3 && parts[0] === subdomain) {
@@ -169,7 +169,7 @@ function matchDomainPattern(
169
169
 
170
170
  // `subdomain.**` - specific subdomain of any domain (including multi-level)
171
171
  // e.g., admin.** matches admin.example.com, admin.sub.example.com
172
- if (pattern.endsWith('.**')) {
172
+ if (pattern.endsWith(".**")) {
173
173
  const subdomain = pattern.slice(0, -3);
174
174
  if (parts.length >= 3 && parts[0] === subdomain) {
175
175
  return true;
@@ -179,7 +179,7 @@ function matchDomainPattern(
179
179
 
180
180
  // `subdomain.` - specific subdomain of any apex domain (no wildcard)
181
181
  // e.g., admin. matches admin.example.com, admin.google.com
182
- if (pattern.endsWith('.') && !pattern.includes('*')) {
182
+ if (pattern.endsWith(".") && !pattern.includes("*")) {
183
183
  const subdomain = pattern.slice(0, -1);
184
184
  // Must be exactly 3 parts (subdomain.domain.tld)
185
185
  if (parts.length === 3 && parts[0] === subdomain) {
@@ -195,17 +195,17 @@ function matchDomainPattern(
195
195
  * Validate pattern format
196
196
  */
197
197
  export function validatePattern(pattern: string): void {
198
- if (!pattern || typeof pattern !== 'string') {
198
+ if (!pattern || typeof pattern !== "string") {
199
199
  throw new InvalidPatternError(
200
200
  pattern,
201
- 'Pattern must be a non-empty string',
202
- { cause: { type: typeof pattern, value: pattern } }
201
+ "Pattern must be a non-empty string",
202
+ { cause: { type: typeof pattern, value: pattern } },
203
203
  );
204
204
  }
205
205
 
206
206
  // Check for invalid characters (spaces, etc.)
207
207
  if (/\s/.test(pattern)) {
208
- throw new InvalidPatternError(pattern, 'contains whitespace', {
208
+ throw new InvalidPatternError(pattern, "contains whitespace", {
209
209
  cause: { pattern },
210
210
  });
211
211
  }
@@ -14,22 +14,23 @@ import type {
14
14
  HostPattern,
15
15
  RouteEntry,
16
16
  HostMatchResult,
17
- } from './types.js';
17
+ } from "./types.js";
18
+ import type { RouterRequestInput } from "../router/router-interfaces.js";
18
19
  import {
19
20
  matchPattern,
20
21
  parseRequest,
21
22
  normalizePattern,
22
23
  validatePattern,
23
- } from './pattern-matcher.js';
24
+ } from "./pattern-matcher.js";
24
25
  import {
25
26
  handleCookieOverride,
26
27
  createCookieErrorResponse,
27
- } from './cookie-handler.js';
28
+ } from "./cookie-handler.js";
28
29
  import {
29
30
  HostRouterError,
30
31
  NoRouteMatchError,
31
32
  InvalidHandlerError,
32
- } from './errors.js';
33
+ } from "./errors.js";
33
34
 
34
35
  /**
35
36
  * Registry entry for a host router instance.
@@ -46,7 +47,8 @@ export interface HostRouterRegistryEntry {
46
47
  * Populated by createHostRouter() so the build-time discovery plugin can find
47
48
  * host routers and resolve their lazy handlers to trigger sub-app createRouter() calls.
48
49
  */
49
- export const HostRouterRegistry: Map<string, HostRouterRegistryEntry> = new Map();
50
+ export const HostRouterRegistry: Map<string, HostRouterRegistryEntry> =
51
+ new Map();
50
52
 
51
53
  let hostRouterAutoId = 0;
52
54
 
@@ -71,7 +73,7 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
71
73
  */
72
74
  function createRouteBuilder(
73
75
  patterns: string[],
74
- isFallback = false
76
+ isFallback = false,
75
77
  ): HostRouteBuilder {
76
78
  const middleware: Middleware[] = [];
77
79
 
@@ -96,8 +98,8 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
96
98
  }
97
99
 
98
100
  log(
99
- `Registered ${isFallback ? 'fallback' : 'route'}:`,
100
- patterns.join(', ')
101
+ `Registered ${isFallback ? "fallback" : "route"}:`,
102
+ patterns.join(", "),
101
103
  );
102
104
 
103
105
  return router;
@@ -110,9 +112,9 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
110
112
  */
111
113
  function findMatchingRoute(
112
114
  hostname: string,
113
- pathname: string
115
+ pathname: string,
114
116
  ): RouteEntry | null {
115
- const parts = hostname.split('.');
117
+ const parts = hostname.split(".");
116
118
 
117
119
  for (const route of routes) {
118
120
  for (const pattern of route.patterns) {
@@ -132,8 +134,8 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
132
134
  async function executeMiddleware(
133
135
  middleware: Middleware[],
134
136
  request: Request,
135
- context: any,
136
- finalHandler: () => Promise<Response>
137
+ input: RouterRequestInput<any>,
138
+ finalHandler: () => Promise<Response>,
137
139
  ): Promise<Response> {
138
140
  let index = 0;
139
141
 
@@ -147,7 +149,20 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
147
149
  return finalHandler();
148
150
  }
149
151
 
150
- return mw(request, context, next);
152
+ // Guard against double next() calls — a second call would
153
+ // re-enter the downstream chain and run handlers/side-effects twice.
154
+ let nextCalled = false;
155
+ const guardedNext = (): Promise<Response> => {
156
+ if (nextCalled) {
157
+ throw new Error(
158
+ `[HostRouter] Middleware called next() more than once.`,
159
+ );
160
+ }
161
+ nextCalled = true;
162
+ return next();
163
+ };
164
+
165
+ return mw(request, input, guardedNext);
151
166
  }
152
167
 
153
168
  return next();
@@ -159,34 +174,34 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
159
174
  async function executeHandler(
160
175
  handler: Handler | LazyHandler,
161
176
  request: Request,
162
- context: any
177
+ input: RouterRequestInput<any>,
163
178
  ): Promise<Response> {
164
179
  // Check if it's a lazy handler (function that returns promise)
165
- if (typeof handler === 'function') {
166
- const result = handler(request, context);
180
+ if (typeof handler === "function") {
181
+ const result = handler(request, input);
167
182
 
168
183
  // If it returns a promise with default export
169
- if (result && typeof result === 'object' && 'then' in result) {
184
+ if (result && typeof result === "object" && "then" in result) {
170
185
  const module = await result;
171
186
  if (
172
- typeof module === 'object' &&
187
+ typeof module === "object" &&
173
188
  module !== null &&
174
- 'default' in module
189
+ "default" in module
175
190
  ) {
176
191
  const defaultExport = (module as { default: Handler | HostRouter })
177
192
  .default;
178
193
 
179
194
  // If default export is a router with match method
180
195
  if (
181
- typeof defaultExport === 'object' &&
196
+ typeof defaultExport === "object" &&
182
197
  defaultExport !== null &&
183
- 'match' in defaultExport
198
+ "match" in defaultExport
184
199
  ) {
185
- return (defaultExport as HostRouter).match(request, context);
200
+ return (defaultExport as HostRouter).match(request, input);
186
201
  }
187
202
 
188
203
  // Otherwise treat as handler
189
- return (defaultExport as Handler)(request, context);
204
+ return (defaultExport as Handler)(request, input);
190
205
  }
191
206
  // If promise resolves to Response
192
207
  return result as Promise<Response>;
@@ -228,8 +243,8 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
228
243
  },
229
244
 
230
245
  test(hostname: string): HostMatchResult | null {
231
- const parts = hostname.split('.');
232
- const pathname = '/';
246
+ const parts = hostname.split(".");
247
+ const pathname = "/";
233
248
 
234
249
  for (const route of routes) {
235
250
  for (const pattern of route.patterns) {
@@ -245,18 +260,17 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
245
260
  return null;
246
261
  },
247
262
 
248
- async match(request: Request, context: any = {}): Promise<Response> {
263
+ async match(
264
+ request: Request,
265
+ input: RouterRequestInput<any> = {},
266
+ ): Promise<Response> {
249
267
  log(`Request: ${request.url}`);
250
268
 
251
269
  let effectiveHostname: string;
252
270
 
253
271
  try {
254
272
  // Handle cookie override (may throw HostRouterError)
255
- effectiveHostname = handleCookieOverride(
256
- request,
257
- hostOverride,
258
- context
259
- );
273
+ effectiveHostname = handleCookieOverride(request, hostOverride, input);
260
274
  } catch (error) {
261
275
  // If it's a HostRouterError from cookie override
262
276
  if (error instanceof HostRouterError) {
@@ -264,14 +278,18 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
264
278
 
265
279
  // If fallback exists, use it
266
280
  if (fallbackRoute) {
267
- context.error = error;
281
+ const fallbackInput = { ...input, error };
268
282
  const allMiddleware = [
269
283
  ...globalMiddleware,
270
284
  ...fallbackRoute.middleware,
271
285
  ];
272
286
 
273
- return executeMiddleware(allMiddleware, request, context, () =>
274
- executeHandler(fallbackRoute!.handler, request, context)
287
+ return executeMiddleware(
288
+ allMiddleware,
289
+ request,
290
+ fallbackInput,
291
+ () =>
292
+ executeHandler(fallbackRoute!.handler, request, fallbackInput),
275
293
  );
276
294
  }
277
295
 
@@ -279,7 +297,7 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
279
297
  if (hostOverride) {
280
298
  return createCookieErrorResponse(
281
299
  hostOverride.cookieName,
282
- error.message
300
+ error.message,
283
301
  );
284
302
  }
285
303
  }
@@ -311,8 +329,8 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
311
329
  const allMiddleware = [...globalMiddleware, ...matchedRoute.middleware];
312
330
 
313
331
  // Execute middleware chain and handler
314
- return executeMiddleware(allMiddleware, request, context, () =>
315
- executeHandler(matchedRoute.handler, request, context)
332
+ return executeMiddleware(allMiddleware, request, input, () =>
333
+ executeHandler(matchedRoute.handler, request, input),
316
334
  );
317
335
  },
318
336
  };
@@ -322,8 +340,12 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
322
340
  // added via .host().map() after this point.
323
341
  const registryId = `host-router-${hostRouterAutoId++}`;
324
342
  HostRouterRegistry.set(registryId, {
325
- get routes() { return routes; },
326
- get fallback() { return fallbackRoute; },
343
+ get routes() {
344
+ return routes;
345
+ },
346
+ get fallback() {
347
+ return fallbackRoute;
348
+ },
327
349
  });
328
350
 
329
351
  return router;
@@ -4,7 +4,7 @@
4
4
  * Helper functions for testing host routing.
5
5
  */
6
6
 
7
- import { matchPattern } from './pattern-matcher.js';
7
+ import { matchPattern } from "./pattern-matcher.js";
8
8
 
9
9
  export interface CreateTestRequestOptions {
10
10
  host: string;
@@ -29,8 +29,8 @@ export interface CreateTestRequestOptions {
29
29
  export function createTestRequest(options: CreateTestRequestOptions): Request {
30
30
  const {
31
31
  host,
32
- path = '/',
33
- method = 'GET',
32
+ path = "/",
33
+ method = "GET",
34
34
  cookies = {},
35
35
  headers = {},
36
36
  } = options;
@@ -42,8 +42,8 @@ export function createTestRequest(options: CreateTestRequestOptions): Request {
42
42
  if (Object.keys(cookies).length > 0) {
43
43
  const cookieString = Object.entries(cookies)
44
44
  .map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
45
- .join('; ');
46
- requestHeaders.set('cookie', cookieString);
45
+ .join("; ");
46
+ requestHeaders.set("cookie", cookieString);
47
47
  }
48
48
 
49
49
  return new Request(url, {
@@ -63,11 +63,11 @@ export function createTestRequest(options: CreateTestRequestOptions): Request {
63
63
  */
64
64
  export function testPattern(
65
65
  pattern: string | string[],
66
- hostname: string
66
+ hostname: string,
67
67
  ): boolean {
68
68
  const patterns = Array.isArray(pattern) ? pattern : [pattern];
69
- const parts = hostname.split('.');
70
- const pathname = '/';
69
+ const parts = hostname.split(".");
70
+ const pathname = "/";
71
71
 
72
72
  for (const p of patterns) {
73
73
  if (matchPattern(p, hostname, pathname, parts)) {
package/src/host/types.ts CHANGED
@@ -4,12 +4,15 @@
4
4
  * Type definitions for the host-based routing system.
5
5
  */
6
6
 
7
+ import type { RouterRequestInput } from "../router/router-interfaces.js";
8
+
7
9
  /**
8
- * Handler function that processes a request and returns a response
10
+ * Handler function that processes a request and returns a response.
11
+ * The input parameter receives the same RouterRequestInput passed to match().
9
12
  */
10
13
  export type Handler = (
11
14
  request: Request,
12
- context: any
15
+ input: RouterRequestInput<any>,
13
16
  ) => Response | Promise<Response>;
14
17
 
15
18
  /**
@@ -18,12 +21,13 @@ export type Handler = (
18
21
  export type LazyHandler = () => Promise<{ default: Handler | HostRouter }>;
19
22
 
20
23
  /**
21
- * Middleware function that can intercept and modify requests/responses
24
+ * Middleware function that can intercept and modify requests/responses.
25
+ * The input parameter receives the same RouterRequestInput passed to match().
22
26
  */
23
27
  export type Middleware = (
24
28
  request: Request,
25
- context: any,
26
- next: () => Promise<Response>
29
+ input: RouterRequestInput<any>,
30
+ next: () => Promise<Response>,
27
31
  ) => Promise<Response>;
28
32
 
29
33
  /**
@@ -71,7 +75,7 @@ export interface HostRouter {
71
75
  /**
72
76
  * Match an incoming request
73
77
  */
74
- match(request: Request, context?: any): Promise<Response>;
78
+ match(request: Request, input?: RouterRequestInput<any>): Promise<Response>;
75
79
 
76
80
  /**
77
81
  * Register fallback handler for allowed hosts without valid cookie
@@ -101,7 +105,11 @@ export interface HostOverrideConfig {
101
105
  /**
102
106
  * Optional validation function
103
107
  */
104
- validate?: (request: Request, cookieValue: string, context: any) => string;
108
+ validate?: (
109
+ request: Request,
110
+ cookieValue: string,
111
+ input: RouterRequestInput<any>,
112
+ ) => string;
105
113
  }
106
114
 
107
115
  /**
package/src/host/utils.ts CHANGED
@@ -19,7 +19,7 @@
19
19
  * ```
20
20
  */
21
21
  export function defineHosts<T extends Record<string, string | string[]>>(
22
- hosts: T
22
+ hosts: T,
23
23
  ): Readonly<T> {
24
24
  return Object.freeze(hosts);
25
25
  }