@rangojs/router 0.0.0-experimental.0f44aca1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +5 -0
- package/README.md +899 -0
- package/dist/bin/rango.js +1601 -0
- package/dist/vite/index.js +5214 -0
- package/package.json +176 -0
- package/skills/breadcrumbs/SKILL.md +250 -0
- package/skills/cache-guide/SKILL.md +262 -0
- package/skills/caching/SKILL.md +220 -0
- package/skills/composability/SKILL.md +172 -0
- package/skills/debug-manifest/SKILL.md +112 -0
- package/skills/document-cache/SKILL.md +182 -0
- package/skills/fonts/SKILL.md +167 -0
- package/skills/hooks/SKILL.md +704 -0
- package/skills/host-router/SKILL.md +218 -0
- package/skills/intercept/SKILL.md +313 -0
- package/skills/layout/SKILL.md +310 -0
- package/skills/links/SKILL.md +239 -0
- package/skills/loader/SKILL.md +596 -0
- package/skills/middleware/SKILL.md +339 -0
- package/skills/mime-routes/SKILL.md +128 -0
- package/skills/parallel/SKILL.md +305 -0
- package/skills/prerender/SKILL.md +643 -0
- package/skills/rango/SKILL.md +118 -0
- package/skills/response-routes/SKILL.md +411 -0
- package/skills/route/SKILL.md +385 -0
- package/skills/router-setup/SKILL.md +439 -0
- package/skills/tailwind/SKILL.md +129 -0
- package/skills/theme/SKILL.md +79 -0
- package/skills/typesafety/SKILL.md +623 -0
- package/skills/use-cache/SKILL.md +324 -0
- package/src/__internal.ts +273 -0
- package/src/bin/rango.ts +321 -0
- package/src/browser/action-coordinator.ts +97 -0
- package/src/browser/action-response-classifier.ts +99 -0
- package/src/browser/event-controller.ts +899 -0
- package/src/browser/history-state.ts +80 -0
- package/src/browser/index.ts +18 -0
- package/src/browser/intercept-utils.ts +52 -0
- package/src/browser/link-interceptor.ts +141 -0
- package/src/browser/logging.ts +55 -0
- package/src/browser/merge-segment-loaders.ts +134 -0
- package/src/browser/navigation-bridge.ts +645 -0
- package/src/browser/navigation-client.ts +215 -0
- package/src/browser/navigation-store.ts +806 -0
- package/src/browser/navigation-transaction.ts +295 -0
- package/src/browser/network-error-handler.ts +61 -0
- package/src/browser/partial-update.ts +550 -0
- package/src/browser/prefetch/cache.ts +146 -0
- package/src/browser/prefetch/fetch.ts +135 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +42 -0
- package/src/browser/prefetch/queue.ts +88 -0
- package/src/browser/rango-state.ts +112 -0
- package/src/browser/react/Link.tsx +360 -0
- package/src/browser/react/NavigationProvider.tsx +386 -0
- package/src/browser/react/ScrollRestoration.tsx +94 -0
- package/src/browser/react/context.ts +59 -0
- package/src/browser/react/filter-segment-order.ts +11 -0
- package/src/browser/react/index.ts +52 -0
- package/src/browser/react/location-state-shared.ts +162 -0
- package/src/browser/react/location-state.ts +107 -0
- package/src/browser/react/mount-context.ts +37 -0
- package/src/browser/react/nonce-context.ts +23 -0
- package/src/browser/react/shallow-equal.ts +27 -0
- package/src/browser/react/use-action.ts +218 -0
- package/src/browser/react/use-client-cache.ts +58 -0
- package/src/browser/react/use-handle.ts +162 -0
- package/src/browser/react/use-href.tsx +40 -0
- package/src/browser/react/use-link-status.ts +135 -0
- package/src/browser/react/use-mount.ts +31 -0
- package/src/browser/react/use-navigation.ts +99 -0
- package/src/browser/react/use-params.ts +65 -0
- package/src/browser/react/use-pathname.ts +47 -0
- package/src/browser/react/use-router.ts +63 -0
- package/src/browser/react/use-search-params.ts +56 -0
- package/src/browser/react/use-segments.ts +171 -0
- package/src/browser/response-adapter.ts +73 -0
- package/src/browser/rsc-router.tsx +431 -0
- package/src/browser/scroll-restoration.ts +400 -0
- package/src/browser/segment-reconciler.ts +216 -0
- package/src/browser/segment-structure-assert.ts +83 -0
- package/src/browser/server-action-bridge.ts +667 -0
- package/src/browser/shallow.ts +40 -0
- package/src/browser/types.ts +538 -0
- package/src/browser/validate-redirect-origin.ts +29 -0
- package/src/build/generate-manifest.ts +438 -0
- package/src/build/generate-route-types.ts +36 -0
- package/src/build/index.ts +35 -0
- package/src/build/route-trie.ts +265 -0
- package/src/build/route-types/ast-helpers.ts +25 -0
- package/src/build/route-types/ast-route-extraction.ts +98 -0
- package/src/build/route-types/codegen.ts +102 -0
- package/src/build/route-types/include-resolution.ts +411 -0
- package/src/build/route-types/param-extraction.ts +48 -0
- package/src/build/route-types/per-module-writer.ts +128 -0
- package/src/build/route-types/router-processing.ts +469 -0
- package/src/build/route-types/scan-filter.ts +78 -0
- package/src/build/runtime-discovery.ts +231 -0
- package/src/cache/background-task.ts +34 -0
- package/src/cache/cache-key-utils.ts +44 -0
- package/src/cache/cache-policy.ts +125 -0
- package/src/cache/cache-runtime.ts +338 -0
- package/src/cache/cache-scope.ts +382 -0
- package/src/cache/cf/cf-cache-store.ts +540 -0
- package/src/cache/cf/index.ts +25 -0
- package/src/cache/document-cache.ts +369 -0
- package/src/cache/handle-capture.ts +81 -0
- package/src/cache/handle-snapshot.ts +41 -0
- package/src/cache/index.ts +43 -0
- package/src/cache/memory-segment-store.ts +328 -0
- package/src/cache/profile-registry.ts +73 -0
- package/src/cache/read-through-swr.ts +134 -0
- package/src/cache/segment-codec.ts +256 -0
- package/src/cache/taint.ts +98 -0
- package/src/cache/types.ts +342 -0
- package/src/client.rsc.tsx +85 -0
- package/src/client.tsx +601 -0
- package/src/component-utils.ts +76 -0
- package/src/components/DefaultDocument.tsx +27 -0
- package/src/context-var.ts +86 -0
- package/src/debug.ts +243 -0
- package/src/default-error-boundary.tsx +88 -0
- package/src/deps/browser.ts +8 -0
- package/src/deps/html-stream-client.ts +2 -0
- package/src/deps/html-stream-server.ts +2 -0
- package/src/deps/rsc.ts +10 -0
- package/src/deps/ssr.ts +2 -0
- package/src/errors.ts +365 -0
- package/src/handle.ts +135 -0
- package/src/handles/MetaTags.tsx +246 -0
- package/src/handles/breadcrumbs.ts +66 -0
- package/src/handles/index.ts +7 -0
- package/src/handles/meta.ts +264 -0
- package/src/host/cookie-handler.ts +165 -0
- package/src/host/errors.ts +97 -0
- package/src/host/index.ts +53 -0
- package/src/host/pattern-matcher.ts +214 -0
- package/src/host/router.ts +352 -0
- package/src/host/testing.ts +79 -0
- package/src/host/types.ts +146 -0
- package/src/host/utils.ts +25 -0
- package/src/href-client.ts +222 -0
- package/src/index.rsc.ts +233 -0
- package/src/index.ts +277 -0
- package/src/internal-debug.ts +11 -0
- package/src/loader.rsc.ts +89 -0
- package/src/loader.ts +64 -0
- package/src/network-error-thrower.tsx +23 -0
- package/src/outlet-context.ts +15 -0
- package/src/outlet-provider.tsx +45 -0
- package/src/prerender/param-hash.ts +37 -0
- package/src/prerender/store.ts +185 -0
- package/src/prerender.ts +463 -0
- package/src/reverse.ts +330 -0
- package/src/root-error-boundary.tsx +289 -0
- package/src/route-content-wrapper.tsx +196 -0
- package/src/route-definition/dsl-helpers.ts +934 -0
- package/src/route-definition/helper-factories.ts +200 -0
- package/src/route-definition/helpers-types.ts +430 -0
- package/src/route-definition/index.ts +52 -0
- package/src/route-definition/redirect.ts +93 -0
- package/src/route-definition.ts +1 -0
- package/src/route-map-builder.ts +275 -0
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +259 -0
- package/src/router/content-negotiation.ts +116 -0
- package/src/router/debug-manifest.ts +72 -0
- package/src/router/error-handling.ts +287 -0
- package/src/router/find-match.ts +158 -0
- package/src/router/handler-context.ts +451 -0
- package/src/router/intercept-resolution.ts +395 -0
- package/src/router/lazy-includes.ts +234 -0
- package/src/router/loader-resolution.ts +420 -0
- package/src/router/logging.ts +248 -0
- package/src/router/manifest.ts +267 -0
- package/src/router/match-api.ts +620 -0
- package/src/router/match-context.ts +266 -0
- package/src/router/match-handlers.ts +440 -0
- package/src/router/match-middleware/background-revalidation.ts +223 -0
- package/src/router/match-middleware/cache-lookup.ts +634 -0
- package/src/router/match-middleware/cache-store.ts +295 -0
- package/src/router/match-middleware/index.ts +81 -0
- package/src/router/match-middleware/intercept-resolution.ts +306 -0
- package/src/router/match-middleware/segment-resolution.ts +192 -0
- package/src/router/match-pipelines.ts +179 -0
- package/src/router/match-result.ts +219 -0
- package/src/router/metrics.ts +282 -0
- package/src/router/middleware-cookies.ts +55 -0
- package/src/router/middleware-types.ts +222 -0
- package/src/router/middleware.ts +748 -0
- package/src/router/pattern-matching.ts +563 -0
- package/src/router/prerender-match.ts +402 -0
- package/src/router/preview-match.ts +170 -0
- package/src/router/revalidation.ts +289 -0
- package/src/router/router-context.ts +316 -0
- package/src/router/router-interfaces.ts +452 -0
- package/src/router/router-options.ts +592 -0
- package/src/router/router-registry.ts +24 -0
- package/src/router/segment-resolution/fresh.ts +570 -0
- package/src/router/segment-resolution/helpers.ts +263 -0
- package/src/router/segment-resolution/loader-cache.ts +198 -0
- package/src/router/segment-resolution/revalidation.ts +1239 -0
- package/src/router/segment-resolution/static-store.ts +67 -0
- package/src/router/segment-resolution.ts +21 -0
- package/src/router/segment-wrappers.ts +289 -0
- package/src/router/telemetry-otel.ts +299 -0
- package/src/router/telemetry.ts +300 -0
- package/src/router/timeout.ts +148 -0
- package/src/router/trie-matching.ts +239 -0
- package/src/router/types.ts +170 -0
- package/src/router.ts +1002 -0
- package/src/rsc/handler-context.ts +45 -0
- package/src/rsc/handler.ts +1089 -0
- package/src/rsc/helpers.ts +198 -0
- package/src/rsc/index.ts +36 -0
- package/src/rsc/loader-fetch.ts +209 -0
- package/src/rsc/manifest-init.ts +86 -0
- package/src/rsc/nonce.ts +32 -0
- package/src/rsc/origin-guard.ts +141 -0
- package/src/rsc/progressive-enhancement.ts +379 -0
- package/src/rsc/response-error.ts +37 -0
- package/src/rsc/response-route-handler.ts +347 -0
- package/src/rsc/rsc-rendering.ts +235 -0
- package/src/rsc/runtime-warnings.ts +42 -0
- package/src/rsc/server-action.ts +348 -0
- package/src/rsc/ssr-setup.ts +128 -0
- package/src/rsc/types.ts +263 -0
- package/src/search-params.ts +230 -0
- package/src/segment-system.tsx +454 -0
- package/src/server/context.ts +591 -0
- package/src/server/cookie-store.ts +190 -0
- package/src/server/fetchable-loader-store.ts +37 -0
- package/src/server/handle-store.ts +308 -0
- package/src/server/loader-registry.ts +133 -0
- package/src/server/request-context.ts +914 -0
- package/src/server/root-layout.tsx +10 -0
- package/src/server/tsconfig.json +14 -0
- package/src/server.ts +51 -0
- package/src/ssr/index.tsx +365 -0
- package/src/static-handler.ts +114 -0
- package/src/theme/ThemeProvider.tsx +297 -0
- package/src/theme/ThemeScript.tsx +61 -0
- package/src/theme/constants.ts +62 -0
- package/src/theme/index.ts +48 -0
- package/src/theme/theme-context.ts +44 -0
- package/src/theme/theme-script.ts +155 -0
- package/src/theme/types.ts +182 -0
- package/src/theme/use-theme.ts +44 -0
- package/src/types/boundaries.ts +158 -0
- package/src/types/cache-types.ts +198 -0
- package/src/types/error-types.ts +192 -0
- package/src/types/global-namespace.ts +100 -0
- package/src/types/handler-context.ts +687 -0
- package/src/types/index.ts +88 -0
- package/src/types/loader-types.ts +183 -0
- package/src/types/route-config.ts +170 -0
- package/src/types/route-entry.ts +102 -0
- package/src/types/segments.ts +148 -0
- package/src/types.ts +1 -0
- package/src/urls/include-helper.ts +197 -0
- package/src/urls/index.ts +53 -0
- package/src/urls/path-helper-types.ts +339 -0
- package/src/urls/path-helper.ts +329 -0
- package/src/urls/pattern-types.ts +95 -0
- package/src/urls/response-types.ts +106 -0
- package/src/urls/type-extraction.ts +372 -0
- package/src/urls/urls-function.ts +98 -0
- package/src/urls.ts +1 -0
- package/src/use-loader.tsx +354 -0
- package/src/vite/discovery/bundle-postprocess.ts +184 -0
- package/src/vite/discovery/discover-routers.ts +344 -0
- package/src/vite/discovery/prerender-collection.ts +385 -0
- package/src/vite/discovery/route-types-writer.ts +258 -0
- package/src/vite/discovery/self-gen-tracking.ts +47 -0
- package/src/vite/discovery/state.ts +110 -0
- package/src/vite/discovery/virtual-module-codegen.ts +203 -0
- package/src/vite/index.ts +16 -0
- package/src/vite/plugin-types.ts +131 -0
- package/src/vite/plugins/cjs-to-esm.ts +93 -0
- package/src/vite/plugins/client-ref-dedup.ts +115 -0
- package/src/vite/plugins/client-ref-hashing.ts +105 -0
- package/src/vite/plugins/expose-action-id.ts +365 -0
- package/src/vite/plugins/expose-id-utils.ts +287 -0
- package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
- package/src/vite/plugins/expose-ids/handler-transform.ts +179 -0
- package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
- package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
- package/src/vite/plugins/expose-ids/types.ts +45 -0
- package/src/vite/plugins/expose-internal-ids.ts +569 -0
- package/src/vite/plugins/refresh-cmd.ts +65 -0
- package/src/vite/plugins/use-cache-transform.ts +323 -0
- package/src/vite/plugins/version-injector.ts +83 -0
- package/src/vite/plugins/version-plugin.ts +254 -0
- package/src/vite/plugins/version.d.ts +12 -0
- package/src/vite/plugins/virtual-entries.ts +123 -0
- package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
- package/src/vite/rango.ts +510 -0
- package/src/vite/router-discovery.ts +785 -0
- package/src/vite/utils/ast-handler-extract.ts +517 -0
- package/src/vite/utils/banner.ts +36 -0
- package/src/vite/utils/bundle-analysis.ts +137 -0
- package/src/vite/utils/manifest-utils.ts +70 -0
- package/src/vite/utils/package-resolution.ts +121 -0
- package/src/vite/utils/prerender-utils.ts +189 -0
- package/src/vite/utils/shared-utils.ts +169 -0
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host Router Implementation
|
|
3
|
+
*
|
|
4
|
+
* Main router that handles host-based routing with middleware and cookie override.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type {
|
|
8
|
+
HostRouter,
|
|
9
|
+
HostRouteBuilder,
|
|
10
|
+
HostRouterOptions,
|
|
11
|
+
Handler,
|
|
12
|
+
LazyHandler,
|
|
13
|
+
Middleware,
|
|
14
|
+
HostPattern,
|
|
15
|
+
RouteEntry,
|
|
16
|
+
HostMatchResult,
|
|
17
|
+
} from "./types.js";
|
|
18
|
+
import type { RouterRequestInput } from "../router/router-interfaces.js";
|
|
19
|
+
import {
|
|
20
|
+
matchPattern,
|
|
21
|
+
parseRequest,
|
|
22
|
+
normalizePattern,
|
|
23
|
+
validatePattern,
|
|
24
|
+
} from "./pattern-matcher.js";
|
|
25
|
+
import {
|
|
26
|
+
handleCookieOverride,
|
|
27
|
+
createCookieErrorResponse,
|
|
28
|
+
} from "./cookie-handler.js";
|
|
29
|
+
import {
|
|
30
|
+
HostRouterError,
|
|
31
|
+
NoRouteMatchError,
|
|
32
|
+
InvalidHandlerError,
|
|
33
|
+
} from "./errors.js";
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Registry entry for a host router instance.
|
|
37
|
+
* Stores references to the live routes array and fallback, so the discovery
|
|
38
|
+
* plugin can iterate handlers registered after createHostRouter() returns.
|
|
39
|
+
*/
|
|
40
|
+
export interface HostRouterRegistryEntry {
|
|
41
|
+
routes: RouteEntry[];
|
|
42
|
+
fallback: RouteEntry | null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Global registry for host routers (parallel to RouterRegistry for RSC routers).
|
|
47
|
+
* Populated by createHostRouter() so the build-time discovery plugin can find
|
|
48
|
+
* host routers and resolve their lazy handlers to trigger sub-app createRouter() calls.
|
|
49
|
+
*/
|
|
50
|
+
export const HostRouterRegistry: Map<string, HostRouterRegistryEntry> =
|
|
51
|
+
new Map();
|
|
52
|
+
|
|
53
|
+
let hostRouterAutoId = 0;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Create a host router
|
|
57
|
+
*/
|
|
58
|
+
export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
59
|
+
const routes: RouteEntry[] = [];
|
|
60
|
+
const globalMiddleware: Middleware[] = [];
|
|
61
|
+
let fallbackRoute: RouteEntry | null = null;
|
|
62
|
+
|
|
63
|
+
const { debug = false, hostOverride } = options;
|
|
64
|
+
|
|
65
|
+
function log(message: string, ...args: any[]): void {
|
|
66
|
+
if (debug) {
|
|
67
|
+
console.log(`[HostRouter] ${message}`, ...args);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Create a route builder for chaining
|
|
73
|
+
*/
|
|
74
|
+
function createRouteBuilder(
|
|
75
|
+
patterns: string[],
|
|
76
|
+
isFallback = false,
|
|
77
|
+
): HostRouteBuilder {
|
|
78
|
+
const middleware: Middleware[] = [];
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
use(...mw: Middleware[]): HostRouteBuilder {
|
|
82
|
+
middleware.push(...mw);
|
|
83
|
+
return this;
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
map(handler: Handler | LazyHandler): HostRouter {
|
|
87
|
+
const entry: RouteEntry = {
|
|
88
|
+
patterns,
|
|
89
|
+
middleware,
|
|
90
|
+
handler,
|
|
91
|
+
isFallback,
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
if (isFallback) {
|
|
95
|
+
fallbackRoute = entry;
|
|
96
|
+
} else {
|
|
97
|
+
routes.push(entry);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
log(
|
|
101
|
+
`Registered ${isFallback ? "fallback" : "route"}:`,
|
|
102
|
+
patterns.join(", "),
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
return router;
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Find matching route for hostname and path
|
|
112
|
+
*/
|
|
113
|
+
function findMatchingRoute(
|
|
114
|
+
hostname: string,
|
|
115
|
+
pathname: string,
|
|
116
|
+
): RouteEntry | null {
|
|
117
|
+
const parts = hostname.split(".");
|
|
118
|
+
|
|
119
|
+
for (const route of routes) {
|
|
120
|
+
for (const pattern of route.patterns) {
|
|
121
|
+
if (matchPattern(pattern, hostname, pathname, parts)) {
|
|
122
|
+
log(`Matched pattern: "${pattern}"`);
|
|
123
|
+
return route;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Execute middleware chain
|
|
133
|
+
*/
|
|
134
|
+
async function executeMiddleware(
|
|
135
|
+
middleware: Middleware[],
|
|
136
|
+
request: Request,
|
|
137
|
+
input: RouterRequestInput<any>,
|
|
138
|
+
finalHandler: () => Promise<Response>,
|
|
139
|
+
): Promise<Response> {
|
|
140
|
+
let index = 0;
|
|
141
|
+
|
|
142
|
+
async function next(): Promise<Response> {
|
|
143
|
+
if (index >= middleware.length) {
|
|
144
|
+
return finalHandler();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const mw = middleware[index++];
|
|
148
|
+
if (!mw) {
|
|
149
|
+
return finalHandler();
|
|
150
|
+
}
|
|
151
|
+
|
|
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);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return next();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Execute handler (lazy or direct)
|
|
173
|
+
*/
|
|
174
|
+
async function executeHandler(
|
|
175
|
+
handler: Handler | LazyHandler,
|
|
176
|
+
request: Request,
|
|
177
|
+
input: RouterRequestInput<any>,
|
|
178
|
+
): Promise<Response> {
|
|
179
|
+
// Check if it's a lazy handler (function that returns promise)
|
|
180
|
+
if (typeof handler === "function") {
|
|
181
|
+
const result = handler(request, input);
|
|
182
|
+
|
|
183
|
+
// If it returns a promise with default export
|
|
184
|
+
if (result && typeof result === "object" && "then" in result) {
|
|
185
|
+
const module = await result;
|
|
186
|
+
if (
|
|
187
|
+
typeof module === "object" &&
|
|
188
|
+
module !== null &&
|
|
189
|
+
"default" in module
|
|
190
|
+
) {
|
|
191
|
+
const defaultExport = (module as { default: Handler | HostRouter })
|
|
192
|
+
.default;
|
|
193
|
+
|
|
194
|
+
// If default export is a router with match method
|
|
195
|
+
if (
|
|
196
|
+
typeof defaultExport === "object" &&
|
|
197
|
+
defaultExport !== null &&
|
|
198
|
+
"match" in defaultExport
|
|
199
|
+
) {
|
|
200
|
+
return (defaultExport as HostRouter).match(request, input);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Otherwise treat as handler
|
|
204
|
+
return (defaultExport as Handler)(request, input);
|
|
205
|
+
}
|
|
206
|
+
// If promise resolves to Response
|
|
207
|
+
return result as Promise<Response>;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Direct handler
|
|
211
|
+
return result as Response | Promise<Response>;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
throw new InvalidHandlerError(handler, {
|
|
215
|
+
cause: { handlerType: typeof handler },
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Router instance
|
|
221
|
+
*/
|
|
222
|
+
const router: HostRouter = {
|
|
223
|
+
host(patterns: HostPattern): HostRouteBuilder {
|
|
224
|
+
const patternsArray = Array.isArray(patterns) ? patterns : [patterns];
|
|
225
|
+
|
|
226
|
+
// Validate and normalize patterns
|
|
227
|
+
const normalized = patternsArray.map((p) => {
|
|
228
|
+
validatePattern(p);
|
|
229
|
+
return normalizePattern(p);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
return createRouteBuilder(normalized, false);
|
|
233
|
+
},
|
|
234
|
+
|
|
235
|
+
use(...middleware: Middleware[]): HostRouter {
|
|
236
|
+
globalMiddleware.push(...middleware);
|
|
237
|
+
log(`Registered global middleware (${middleware.length})`);
|
|
238
|
+
return router;
|
|
239
|
+
},
|
|
240
|
+
|
|
241
|
+
fallback(): HostRouteBuilder {
|
|
242
|
+
return createRouteBuilder([], true);
|
|
243
|
+
},
|
|
244
|
+
|
|
245
|
+
test(hostname: string): HostMatchResult | null {
|
|
246
|
+
const parts = hostname.split(".");
|
|
247
|
+
const pathname = "/";
|
|
248
|
+
|
|
249
|
+
for (const route of routes) {
|
|
250
|
+
for (const pattern of route.patterns) {
|
|
251
|
+
if (matchPattern(pattern, hostname, pathname, parts)) {
|
|
252
|
+
return {
|
|
253
|
+
pattern,
|
|
254
|
+
handler: route.handler,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return null;
|
|
261
|
+
},
|
|
262
|
+
|
|
263
|
+
async match(
|
|
264
|
+
request: Request,
|
|
265
|
+
input: RouterRequestInput<any> = {},
|
|
266
|
+
): Promise<Response> {
|
|
267
|
+
log(`Request: ${request.url}`);
|
|
268
|
+
|
|
269
|
+
let effectiveHostname: string;
|
|
270
|
+
|
|
271
|
+
try {
|
|
272
|
+
// Handle cookie override (may throw HostRouterError)
|
|
273
|
+
effectiveHostname = handleCookieOverride(request, hostOverride, input);
|
|
274
|
+
} catch (error) {
|
|
275
|
+
// If it's a HostRouterError from cookie override
|
|
276
|
+
if (error instanceof HostRouterError) {
|
|
277
|
+
log(`Cookie override error: ${error.message}`);
|
|
278
|
+
|
|
279
|
+
// If fallback exists, use it
|
|
280
|
+
if (fallbackRoute) {
|
|
281
|
+
const fallbackInput = { ...input, error };
|
|
282
|
+
const allMiddleware = [
|
|
283
|
+
...globalMiddleware,
|
|
284
|
+
...fallbackRoute.middleware,
|
|
285
|
+
];
|
|
286
|
+
|
|
287
|
+
return executeMiddleware(
|
|
288
|
+
allMiddleware,
|
|
289
|
+
request,
|
|
290
|
+
fallbackInput,
|
|
291
|
+
() =>
|
|
292
|
+
executeHandler(fallbackRoute!.handler, request, fallbackInput),
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Otherwise return error response with cookie deletion
|
|
297
|
+
if (hostOverride) {
|
|
298
|
+
return createCookieErrorResponse(
|
|
299
|
+
hostOverride.cookieName,
|
|
300
|
+
error.message,
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Re-throw non-HostRouterErrors
|
|
306
|
+
throw error;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const { pathname } = parseRequest(request);
|
|
310
|
+
|
|
311
|
+
if (effectiveHostname !== parseRequest(request).hostname) {
|
|
312
|
+
log(`Cookie override: ${effectiveHostname}`);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Find matching route
|
|
316
|
+
const matchedRoute = findMatchingRoute(effectiveHostname, pathname);
|
|
317
|
+
|
|
318
|
+
if (!matchedRoute) {
|
|
319
|
+
log(`No route matched`);
|
|
320
|
+
throw new NoRouteMatchError(effectiveHostname, pathname, {
|
|
321
|
+
cause: {
|
|
322
|
+
hostname: effectiveHostname,
|
|
323
|
+
pathname,
|
|
324
|
+
},
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Combine global and route-specific middleware
|
|
329
|
+
const allMiddleware = [...globalMiddleware, ...matchedRoute.middleware];
|
|
330
|
+
|
|
331
|
+
// Execute middleware chain and handler
|
|
332
|
+
return executeMiddleware(allMiddleware, request, input, () =>
|
|
333
|
+
executeHandler(matchedRoute.handler, request, input),
|
|
334
|
+
);
|
|
335
|
+
},
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
// Register in the global HostRouterRegistry for build-time discovery.
|
|
339
|
+
// The routes array and fallbackRoute ref are live - they reflect routes
|
|
340
|
+
// added via .host().map() after this point.
|
|
341
|
+
const registryId = `host-router-${hostRouterAutoId++}`;
|
|
342
|
+
HostRouterRegistry.set(registryId, {
|
|
343
|
+
get routes() {
|
|
344
|
+
return routes;
|
|
345
|
+
},
|
|
346
|
+
get fallback() {
|
|
347
|
+
return fallbackRoute;
|
|
348
|
+
},
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
return router;
|
|
352
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Testing Utilities for Host Router
|
|
3
|
+
*
|
|
4
|
+
* Helper functions for testing host routing.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { matchPattern } from "./pattern-matcher.js";
|
|
8
|
+
|
|
9
|
+
export interface CreateTestRequestOptions {
|
|
10
|
+
host: string;
|
|
11
|
+
path?: string;
|
|
12
|
+
method?: string;
|
|
13
|
+
cookies?: Record<string, string>;
|
|
14
|
+
headers?: Record<string, string>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Create a test request with specific host and cookies
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```ts
|
|
22
|
+
* const request = createTestRequest({
|
|
23
|
+
* host: 'admin.example.com',
|
|
24
|
+
* path: '/dashboard',
|
|
25
|
+
* cookies: { 'x-requested-host': 'api.example.com' }
|
|
26
|
+
* });
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export function createTestRequest(options: CreateTestRequestOptions): Request {
|
|
30
|
+
const {
|
|
31
|
+
host,
|
|
32
|
+
path = "/",
|
|
33
|
+
method = "GET",
|
|
34
|
+
cookies = {},
|
|
35
|
+
headers = {},
|
|
36
|
+
} = options;
|
|
37
|
+
|
|
38
|
+
const url = `http://${host}${path}`;
|
|
39
|
+
const requestHeaders = new Headers(headers);
|
|
40
|
+
|
|
41
|
+
// Add cookies if provided
|
|
42
|
+
if (Object.keys(cookies).length > 0) {
|
|
43
|
+
const cookieString = Object.entries(cookies)
|
|
44
|
+
.map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
|
|
45
|
+
.join("; ");
|
|
46
|
+
requestHeaders.set("cookie", cookieString);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return new Request(url, {
|
|
50
|
+
method,
|
|
51
|
+
headers: requestHeaders,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Test if a pattern matches a hostname
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```ts
|
|
60
|
+
* expect(testPattern('admin.*', 'admin.example.com')).toBe(true);
|
|
61
|
+
* expect(testPattern(['*', 'www.*'], 'example.com')).toBe(true);
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
export function testPattern(
|
|
65
|
+
pattern: string | string[],
|
|
66
|
+
hostname: string,
|
|
67
|
+
): boolean {
|
|
68
|
+
const patterns = Array.isArray(pattern) ? pattern : [pattern];
|
|
69
|
+
const parts = hostname.split(".");
|
|
70
|
+
const pathname = "/";
|
|
71
|
+
|
|
72
|
+
for (const p of patterns) {
|
|
73
|
+
if (matchPattern(p, hostname, pathname, parts)) {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host Router Types
|
|
3
|
+
*
|
|
4
|
+
* Type definitions for the host-based routing system.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { RouterRequestInput } from "../router/router-interfaces.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Handler function that processes a request and returns a response.
|
|
11
|
+
* The input parameter receives the same RouterRequestInput passed to match().
|
|
12
|
+
*/
|
|
13
|
+
export type Handler = (
|
|
14
|
+
request: Request,
|
|
15
|
+
input: RouterRequestInput<any>,
|
|
16
|
+
) => Response | Promise<Response>;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Lazy handler that dynamically imports a module with a default handler or router
|
|
20
|
+
*/
|
|
21
|
+
export type LazyHandler = () => Promise<{ default: Handler | HostRouter }>;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Middleware function that can intercept and modify requests/responses.
|
|
25
|
+
* The input parameter receives the same RouterRequestInput passed to match().
|
|
26
|
+
*/
|
|
27
|
+
export type Middleware = (
|
|
28
|
+
request: Request,
|
|
29
|
+
input: RouterRequestInput<any>,
|
|
30
|
+
next: () => Promise<Response>,
|
|
31
|
+
) => Promise<Response>;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Host pattern - can be a string or array of strings
|
|
35
|
+
*/
|
|
36
|
+
export type HostPattern = string | string[];
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Result from testing a hostname against patterns
|
|
40
|
+
*/
|
|
41
|
+
export interface HostMatchResult {
|
|
42
|
+
pattern: string;
|
|
43
|
+
handler: Handler | LazyHandler;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Host route builder for chaining middleware and handler
|
|
48
|
+
*/
|
|
49
|
+
export interface HostRouteBuilder {
|
|
50
|
+
/**
|
|
51
|
+
* Add middleware to this host pattern
|
|
52
|
+
*/
|
|
53
|
+
use(...middleware: Middleware[]): HostRouteBuilder;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Map to a handler or lazy import
|
|
57
|
+
*/
|
|
58
|
+
map(handler: Handler | LazyHandler): HostRouter;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Main host router interface
|
|
63
|
+
*/
|
|
64
|
+
export interface HostRouter {
|
|
65
|
+
/**
|
|
66
|
+
* Register a host pattern
|
|
67
|
+
*/
|
|
68
|
+
host(patterns: HostPattern): HostRouteBuilder;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Register global middleware
|
|
72
|
+
*/
|
|
73
|
+
use(...middleware: Middleware[]): HostRouter;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Match an incoming request
|
|
77
|
+
*/
|
|
78
|
+
match(request: Request, input?: RouterRequestInput<any>): Promise<Response>;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Register fallback handler for allowed hosts without valid cookie
|
|
82
|
+
*/
|
|
83
|
+
fallback(): HostRouteBuilder;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Test which handler would match a hostname
|
|
87
|
+
*/
|
|
88
|
+
test(hostname: string): HostMatchResult | null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Host override configuration
|
|
93
|
+
*/
|
|
94
|
+
export interface HostOverrideConfig {
|
|
95
|
+
/**
|
|
96
|
+
* Cookie name to read for host override
|
|
97
|
+
*/
|
|
98
|
+
cookieName: string;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Hosts that are allowed to use override
|
|
102
|
+
*/
|
|
103
|
+
allowedHosts: string[];
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Optional validation function
|
|
107
|
+
*/
|
|
108
|
+
validate?: (
|
|
109
|
+
request: Request,
|
|
110
|
+
cookieValue: string,
|
|
111
|
+
input: RouterRequestInput<any>,
|
|
112
|
+
) => string;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Host router options
|
|
117
|
+
*/
|
|
118
|
+
export interface HostRouterOptions {
|
|
119
|
+
/**
|
|
120
|
+
* Enable debug logging
|
|
121
|
+
*/
|
|
122
|
+
debug?: boolean;
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Cookie-based host override configuration
|
|
126
|
+
*/
|
|
127
|
+
hostOverride?: HostOverrideConfig;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Internal route entry
|
|
132
|
+
*/
|
|
133
|
+
export interface RouteEntry {
|
|
134
|
+
patterns: string[];
|
|
135
|
+
middleware: Middleware[];
|
|
136
|
+
handler: Handler | LazyHandler;
|
|
137
|
+
isFallback?: boolean;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Pattern match result (internal)
|
|
142
|
+
*/
|
|
143
|
+
export interface PatternMatchResult {
|
|
144
|
+
matched: boolean;
|
|
145
|
+
routeEntry?: RouteEntry;
|
|
146
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host Router Utilities
|
|
3
|
+
*
|
|
4
|
+
* Helper functions for type-safe pattern definitions.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Define hosts with type safety
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* const hosts = defineHosts({
|
|
13
|
+
* admin: 'admin.*',
|
|
14
|
+
* api: 'api.*',
|
|
15
|
+
* app: ['*', 'www.*']
|
|
16
|
+
* });
|
|
17
|
+
*
|
|
18
|
+
* router.host(hosts.admin).map(...); // Type-safe!
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export function defineHosts<T extends Record<string, string | string[]>>(
|
|
22
|
+
hosts: T,
|
|
23
|
+
): Readonly<T> {
|
|
24
|
+
return Object.freeze(hosts);
|
|
25
|
+
}
|