@rangojs/router 0.0.0-experimental.7 → 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.
- package/AGENTS.md +5 -0
- package/README.md +884 -4
- package/dist/bin/rango.js +1601 -0
- package/dist/vite/index.js +4474 -863
- package/package.json +60 -51
- package/skills/breadcrumbs/SKILL.md +250 -0
- package/skills/cache-guide/SKILL.md +262 -0
- package/skills/caching/SKILL.md +50 -21
- package/skills/composability/SKILL.md +172 -0
- package/skills/debug-manifest/SKILL.md +12 -8
- package/skills/document-cache/SKILL.md +18 -16
- package/skills/fonts/SKILL.md +167 -0
- package/skills/hooks/SKILL.md +334 -72
- package/skills/host-router/SKILL.md +218 -0
- package/skills/intercept/SKILL.md +131 -8
- package/skills/layout/SKILL.md +100 -3
- package/skills/links/SKILL.md +89 -30
- package/skills/loader/SKILL.md +388 -38
- package/skills/middleware/SKILL.md +171 -34
- package/skills/mime-routes/SKILL.md +128 -0
- package/skills/parallel/SKILL.md +78 -1
- package/skills/prerender/SKILL.md +643 -0
- package/skills/rango/SKILL.md +85 -16
- package/skills/response-routes/SKILL.md +411 -0
- package/skills/route/SKILL.md +226 -14
- package/skills/router-setup/SKILL.md +123 -30
- package/skills/tailwind/SKILL.md +129 -0
- package/skills/theme/SKILL.md +9 -8
- package/skills/typesafety/SKILL.md +318 -89
- package/skills/use-cache/SKILL.md +324 -0
- package/src/__internal.ts +102 -4
- 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 +87 -64
- package/src/browser/history-state.ts +80 -0
- package/src/browser/intercept-utils.ts +52 -0
- package/src/browser/link-interceptor.ts +24 -4
- package/src/browser/logging.ts +55 -0
- package/src/browser/merge-segment-loaders.ts +20 -12
- package/src/browser/navigation-bridge.ts +285 -553
- package/src/browser/navigation-client.ts +124 -71
- package/src/browser/navigation-store.ts +33 -50
- package/src/browser/navigation-transaction.ts +295 -0
- package/src/browser/network-error-handler.ts +61 -0
- package/src/browser/partial-update.ts +258 -308
- 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 +185 -73
- package/src/browser/react/NavigationProvider.tsx +51 -11
- package/src/browser/react/context.ts +6 -0
- package/src/browser/react/filter-segment-order.ts +11 -0
- package/src/browser/react/index.ts +12 -12
- package/src/browser/react/location-state-shared.ts +95 -53
- package/src/browser/react/location-state.ts +60 -15
- package/src/browser/react/mount-context.ts +6 -1
- 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 +29 -51
- package/src/browser/react/use-client-cache.ts +5 -3
- package/src/browser/react/use-handle.ts +32 -79
- package/src/browser/react/use-href.tsx +2 -2
- package/src/browser/react/use-link-status.ts +6 -5
- package/src/browser/react/use-navigation.ts +22 -63
- 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 +80 -97
- package/src/browser/response-adapter.ts +73 -0
- package/src/browser/rsc-router.tsx +107 -26
- package/src/browser/scroll-restoration.ts +92 -16
- package/src/browser/segment-reconciler.ts +216 -0
- package/src/browser/segment-structure-assert.ts +16 -0
- package/src/browser/server-action-bridge.ts +504 -599
- package/src/browser/shallow.ts +6 -1
- package/src/browser/types.ts +109 -47
- package/src/browser/validate-redirect-origin.ts +29 -0
- package/src/build/generate-manifest.ts +235 -24
- package/src/build/generate-route-types.ts +36 -0
- package/src/build/index.ts +13 -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 +120 -303
- package/src/cache/cf/cf-cache-store.ts +119 -7
- package/src/cache/cf/index.ts +8 -2
- package/src/cache/document-cache.ts +101 -72
- package/src/cache/handle-capture.ts +81 -0
- package/src/cache/handle-snapshot.ts +41 -0
- package/src/cache/index.ts +0 -15
- package/src/cache/memory-segment-store.ts +191 -13
- 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 +72 -122
- package/src/client.rsc.tsx +3 -1
- package/src/client.tsx +106 -126
- package/src/component-utils.ts +4 -4
- package/src/components/DefaultDocument.tsx +5 -1
- package/src/context-var.ts +86 -0
- package/src/debug.ts +17 -7
- package/src/errors.ts +108 -2
- package/src/handle.ts +15 -29
- package/src/handles/MetaTags.tsx +73 -20
- package/src/handles/breadcrumbs.ts +66 -0
- package/src/handles/index.ts +1 -0
- package/src/handles/meta.ts +30 -13
- package/src/host/cookie-handler.ts +21 -15
- package/src/host/errors.ts +8 -8
- package/src/host/index.ts +4 -7
- package/src/host/pattern-matcher.ts +27 -27
- package/src/host/router.ts +61 -39
- package/src/host/testing.ts +8 -8
- package/src/host/types.ts +15 -7
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +119 -29
- package/src/index.rsc.ts +153 -19
- package/src/index.ts +211 -30
- package/src/internal-debug.ts +11 -0
- package/src/loader.rsc.ts +26 -157
- package/src/loader.ts +27 -10
- package/src/network-error-thrower.tsx +3 -1
- 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 +41 -29
- package/src/route-content-wrapper.tsx +7 -4
- 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 -1428
- package/src/route-map-builder.ts +211 -123
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +59 -8
- package/src/router/content-negotiation.ts +116 -0
- package/src/router/debug-manifest.ts +72 -0
- package/src/router/error-handling.ts +9 -9
- package/src/router/find-match.ts +158 -0
- package/src/router/handler-context.ts +374 -81
- package/src/router/intercept-resolution.ts +395 -0
- package/src/router/lazy-includes.ts +234 -0
- package/src/router/loader-resolution.ts +215 -122
- package/src/router/logging.ts +248 -0
- package/src/router/manifest.ts +148 -35
- package/src/router/match-api.ts +620 -0
- package/src/router/match-context.ts +5 -3
- package/src/router/match-handlers.ts +440 -0
- package/src/router/match-middleware/background-revalidation.ts +80 -93
- package/src/router/match-middleware/cache-lookup.ts +382 -9
- package/src/router/match-middleware/cache-store.ts +51 -22
- package/src/router/match-middleware/intercept-resolution.ts +55 -17
- package/src/router/match-middleware/segment-resolution.ts +24 -6
- package/src/router/match-pipelines.ts +10 -45
- package/src/router/match-result.ts +34 -28
- package/src/router/metrics.ts +235 -15
- package/src/router/middleware-cookies.ts +55 -0
- package/src/router/middleware-types.ts +222 -0
- package/src/router/middleware.ts +324 -367
- package/src/router/pattern-matching.ts +211 -43
- package/src/router/prerender-match.ts +402 -0
- package/src/router/preview-match.ts +170 -0
- package/src/router/revalidation.ts +137 -38
- package/src/router/router-context.ts +36 -21
- 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 +1241 -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 +77 -3
- package/src/router.ts +692 -4257
- package/src/rsc/handler-context.ts +45 -0
- package/src/rsc/handler.ts +764 -754
- package/src/rsc/helpers.ts +140 -6
- package/src/rsc/index.ts +0 -20
- package/src/rsc/loader-fetch.ts +209 -0
- package/src/rsc/manifest-init.ts +86 -0
- package/src/rsc/nonce.ts +14 -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 +38 -11
- package/src/search-params.ts +230 -0
- package/src/segment-system.tsx +25 -13
- package/src/server/context.ts +182 -51
- package/src/server/cookie-store.ts +190 -0
- package/src/server/fetchable-loader-store.ts +37 -0
- package/src/server/handle-store.ts +94 -15
- package/src/server/loader-registry.ts +15 -56
- package/src/server/request-context.ts +430 -70
- package/src/server.ts +35 -130
- package/src/ssr/index.tsx +100 -31
- package/src/static-handler.ts +114 -0
- package/src/theme/ThemeProvider.tsx +21 -15
- package/src/theme/ThemeScript.tsx +5 -5
- package/src/theme/constants.ts +5 -2
- package/src/theme/index.ts +4 -14
- package/src/theme/theme-context.ts +4 -30
- package/src/theme/theme-script.ts +21 -18
- 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 -1623
- 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 -802
- package/src/use-loader.tsx +85 -77
- 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 +11 -1129
- 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/{expose-action-id.ts → plugins/expose-action-id.ts} +72 -51
- 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/{virtual-entries.ts → plugins/virtual-entries.ts} +23 -14
- 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/{package-resolution.ts → utils/package-resolution.ts} +25 -29
- package/src/vite/utils/prerender-utils.ts +189 -0
- package/src/vite/utils/shared-utils.ts +169 -0
- package/CLAUDE.md +0 -43
- package/src/browser/lru-cache.ts +0 -69
- package/src/browser/request-controller.ts +0 -164
- package/src/cache/memory-store.ts +0 -253
- package/src/href-context.ts +0 -33
- package/src/href.ts +0 -255
- package/src/server/route-manifest-cache.ts +0 -173
- package/src/vite/expose-handle-id.ts +0 -209
- package/src/vite/expose-loader-id.ts +0 -426
- package/src/vite/expose-location-state-id.ts +0 -177
- /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
package/src/host/errors.ts
CHANGED
|
@@ -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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 ===
|
|
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(
|
|
131
|
+
if (pattern.startsWith("*.") && !pattern.includes(".", 2)) {
|
|
132
132
|
const tld = pattern.slice(2);
|
|
133
|
-
return isApexDomain(parts) && hostname.endsWith(
|
|
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(
|
|
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(
|
|
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(
|
|
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 !==
|
|
198
|
+
if (!pattern || typeof pattern !== "string") {
|
|
199
199
|
throw new InvalidPatternError(
|
|
200
200
|
pattern,
|
|
201
|
-
|
|
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,
|
|
208
|
+
throw new InvalidPatternError(pattern, "contains whitespace", {
|
|
209
209
|
cause: { pattern },
|
|
210
210
|
});
|
|
211
211
|
}
|
package/src/host/router.ts
CHANGED
|
@@ -14,22 +14,23 @@ import type {
|
|
|
14
14
|
HostPattern,
|
|
15
15
|
RouteEntry,
|
|
16
16
|
HostMatchResult,
|
|
17
|
-
} from
|
|
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
|
|
24
|
+
} from "./pattern-matcher.js";
|
|
24
25
|
import {
|
|
25
26
|
handleCookieOverride,
|
|
26
27
|
createCookieErrorResponse,
|
|
27
|
-
} from
|
|
28
|
+
} from "./cookie-handler.js";
|
|
28
29
|
import {
|
|
29
30
|
HostRouterError,
|
|
30
31
|
NoRouteMatchError,
|
|
31
32
|
InvalidHandlerError,
|
|
32
|
-
} from
|
|
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> =
|
|
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 ?
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 ===
|
|
166
|
-
const result = handler(request,
|
|
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 ===
|
|
184
|
+
if (result && typeof result === "object" && "then" in result) {
|
|
170
185
|
const module = await result;
|
|
171
186
|
if (
|
|
172
|
-
typeof module ===
|
|
187
|
+
typeof module === "object" &&
|
|
173
188
|
module !== null &&
|
|
174
|
-
|
|
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 ===
|
|
196
|
+
typeof defaultExport === "object" &&
|
|
182
197
|
defaultExport !== null &&
|
|
183
|
-
|
|
198
|
+
"match" in defaultExport
|
|
184
199
|
) {
|
|
185
|
-
return (defaultExport as HostRouter).match(request,
|
|
200
|
+
return (defaultExport as HostRouter).match(request, input);
|
|
186
201
|
}
|
|
187
202
|
|
|
188
203
|
// Otherwise treat as handler
|
|
189
|
-
return (defaultExport as Handler)(request,
|
|
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(
|
|
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
|
-
|
|
281
|
+
const fallbackInput = { ...input, error };
|
|
268
282
|
const allMiddleware = [
|
|
269
283
|
...globalMiddleware,
|
|
270
284
|
...fallbackRoute.middleware,
|
|
271
285
|
];
|
|
272
286
|
|
|
273
|
-
return executeMiddleware(
|
|
274
|
-
|
|
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,
|
|
315
|
-
executeHandler(matchedRoute.handler, request,
|
|
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() {
|
|
326
|
-
|
|
343
|
+
get routes() {
|
|
344
|
+
return routes;
|
|
345
|
+
},
|
|
346
|
+
get fallback() {
|
|
347
|
+
return fallbackRoute;
|
|
348
|
+
},
|
|
327
349
|
});
|
|
328
350
|
|
|
329
351
|
return router;
|
package/src/host/testing.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Helper functions for testing host routing.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { matchPattern } from
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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?: (
|
|
108
|
+
validate?: (
|
|
109
|
+
request: Request,
|
|
110
|
+
cookieValue: string,
|
|
111
|
+
input: RouterRequestInput<any>,
|
|
112
|
+
) => string;
|
|
105
113
|
}
|
|
106
114
|
|
|
107
115
|
/**
|