@rangojs/router 0.0.0-experimental.97 → 0.0.0-experimental.98914650
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -9
- package/dist/bin/rango.js +157 -63
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +1584 -639
- package/package.json +71 -21
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +60 -0
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +222 -30
- package/skills/caching/SKILL.md +263 -8
- package/skills/composability/SKILL.md +27 -2
- package/skills/css/SKILL.md +76 -0
- package/skills/document-cache/SKILL.md +78 -55
- package/skills/handler-use/SKILL.md +3 -1
- package/skills/hooks/SKILL.md +235 -28
- package/skills/host-router/SKILL.md +122 -22
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +29 -5
- package/skills/layout/SKILL.md +13 -9
- package/skills/links/SKILL.md +173 -17
- package/skills/loader/SKILL.md +170 -23
- package/skills/middleware/SKILL.md +16 -10
- package/skills/migrate-nextjs/SKILL.md +38 -16
- package/skills/mime-routes/SKILL.md +27 -0
- package/skills/observability/SKILL.md +137 -0
- package/skills/parallel/SKILL.md +11 -7
- package/skills/prerender/SKILL.md +14 -33
- package/skills/rango/SKILL.md +250 -25
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +114 -47
- package/skills/route/SKILL.md +42 -5
- package/skills/router-setup/SKILL.md +3 -3
- package/skills/server-actions/SKILL.md +78 -42
- package/skills/tailwind/SKILL.md +27 -3
- package/skills/testing/SKILL.md +129 -0
- package/skills/testing/bindings.md +89 -0
- package/skills/testing/cache-prerender.md +124 -0
- package/skills/testing/client-components.md +122 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +92 -0
- package/skills/testing/handles.md +129 -0
- package/skills/testing/loader.md +128 -0
- package/skills/testing/middleware.md +99 -0
- package/skills/testing/render-handler.md +121 -0
- package/skills/testing/response-routes.md +95 -0
- package/skills/testing/reverse-and-types.md +84 -0
- package/skills/testing/server-actions.md +107 -0
- package/skills/testing/server-tree.md +128 -0
- package/skills/testing/setup.md +120 -0
- package/skills/typesafety/SKILL.md +316 -26
- package/skills/use-cache/SKILL.md +36 -5
- package/skills/vercel/SKILL.md +107 -0
- package/skills/view-transitions/SKILL.md +294 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +116 -0
- package/src/__internal.ts +0 -65
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +14 -27
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +37 -143
- package/src/browser/history-state.ts +21 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/navigation-bridge.ts +30 -59
- package/src/browser/navigation-client.ts +96 -84
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +32 -82
- package/src/browser/navigation-transaction.ts +9 -59
- package/src/browser/partial-update.ts +60 -127
- package/src/browser/prefetch/cache.ts +82 -72
- package/src/browser/prefetch/fetch.ts +108 -33
- package/src/browser/prefetch/queue.ts +6 -3
- package/src/browser/rango-state.ts +157 -115
- package/src/browser/react/Link.tsx +0 -2
- package/src/browser/react/NavigationProvider.tsx +41 -48
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/filter-segment-order.ts +0 -2
- package/src/browser/react/index.ts +0 -48
- package/src/browser/react/location-state-shared.ts +166 -8
- package/src/browser/react/location-state.ts +39 -14
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +17 -14
- package/src/browser/react/use-link-status.ts +0 -4
- package/src/browser/react/use-navigation.ts +0 -3
- package/src/browser/react/use-params.ts +11 -11
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +20 -5
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +0 -13
- package/src/browser/response-adapter.ts +52 -1
- package/src/browser/rsc-router.tsx +70 -34
- package/src/browser/scroll-restoration.ts +22 -14
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +168 -44
- package/src/browser/types.ts +36 -21
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +60 -35
- package/src/build/generate-route-types.ts +3 -0
- package/src/build/index.ts +8 -2
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +89 -10
- package/src/build/route-types/codegen.ts +4 -4
- package/src/build/route-types/include-resolution.ts +1 -1
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/per-module-writer.ts +7 -4
- package/src/build/route-types/router-processing.ts +122 -22
- package/src/build/route-types/scan-filter.ts +1 -1
- package/src/build/route-types/source-scan.ts +118 -0
- package/src/build/runtime-discovery.ts +9 -20
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-policy.ts +68 -28
- package/src/cache/cache-runtime.ts +134 -32
- package/src/cache/cache-scope.ts +100 -74
- package/src/cache/cache-tag.ts +98 -0
- package/src/cache/cf/cf-cache-store.ts +2255 -238
- package/src/cache/cf/index.ts +6 -16
- package/src/cache/document-cache.ts +61 -20
- package/src/cache/handle-snapshot.ts +63 -0
- package/src/cache/index.ts +22 -20
- package/src/cache/memory-segment-store.ts +136 -37
- package/src/cache/profile-registry.ts +6 -30
- package/src/cache/read-through-swr.ts +41 -11
- package/src/cache/segment-codec.ts +0 -16
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/types.ts +33 -100
- package/src/cache/vercel/index.ts +11 -0
- package/src/cache/vercel/vercel-cache-store.ts +799 -0
- package/src/client.rsc.tsx +6 -21
- package/src/client.tsx +25 -61
- package/src/component-utils.ts +19 -0
- package/src/context-var.ts +17 -5
- package/src/decode-loader-results.ts +36 -0
- package/src/defer.ts +196 -0
- package/src/deps/ssr.ts +0 -1
- package/src/errors.ts +30 -4
- package/src/handle.ts +31 -23
- package/src/handles/MetaTags.tsx +0 -14
- package/src/handles/breadcrumbs.ts +16 -5
- package/src/handles/meta.ts +0 -39
- package/src/host/cookie-handler.ts +0 -36
- package/src/host/errors.ts +0 -24
- package/src/host/index.ts +8 -2
- package/src/host/pattern-matcher.ts +7 -50
- package/src/host/router.ts +107 -99
- package/src/host/testing.ts +40 -27
- package/src/host/types.ts +37 -4
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +137 -22
- package/src/index.rsc.ts +63 -9
- package/src/index.ts +64 -9
- package/src/internal-debug.ts +2 -4
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +20 -13
- package/src/loader.ts +12 -11
- package/src/missing-id-error.ts +68 -0
- package/src/network-error-thrower.tsx +1 -6
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +10 -11
- package/src/prerender/store.ts +32 -37
- package/src/prerender.ts +61 -6
- package/src/redirect-origin.ts +100 -0
- package/src/response-utils.ts +9 -0
- package/src/reverse.ts +65 -40
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +7 -72
- package/src/route-definition/dsl-helpers.ts +244 -281
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +40 -17
- package/src/route-definition/redirect.ts +43 -9
- package/src/route-definition/resolve-handler-use.ts +6 -0
- package/src/route-definition/use-item-types.ts +32 -0
- package/src/route-map-builder.ts +0 -16
- package/src/route-types.ts +19 -41
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +15 -15
- package/src/router/error-handling.ts +13 -17
- package/src/router/find-match.ts +44 -23
- package/src/router/handler-context.ts +4 -41
- package/src/router/intercept-resolution.ts +14 -19
- package/src/router/lazy-includes.ts +9 -46
- package/src/router/loader-resolution.ts +91 -46
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +18 -29
- package/src/router/match-api.ts +0 -20
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +57 -58
- package/src/router/match-middleware/background-revalidation.ts +0 -7
- package/src/router/match-middleware/cache-lookup.ts +150 -271
- package/src/router/match-middleware/cache-store.ts +3 -33
- package/src/router/match-middleware/intercept-resolution.ts +0 -22
- package/src/router/match-middleware/segment-resolution.ts +0 -22
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +31 -80
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-types.ts +5 -112
- package/src/router/middleware.ts +118 -133
- package/src/router/navigation-snapshot.ts +0 -51
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +62 -67
- package/src/router/prerender-match.ts +99 -63
- package/src/router/preview-match.ts +3 -1
- package/src/router/request-classification.ts +28 -62
- package/src/router/revalidation.ts +50 -56
- package/src/router/route-snapshot.ts +0 -1
- package/src/router/router-context.ts +0 -27
- package/src/router/router-interfaces.ts +68 -35
- package/src/router/router-options.ts +55 -1
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +44 -63
- package/src/router/segment-resolution/helpers.ts +34 -0
- package/src/router/segment-resolution/loader-cache.ts +40 -37
- package/src/router/segment-resolution/revalidation.ts +203 -285
- package/src/router/segment-resolution/static-store.ts +19 -5
- package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
- package/src/router/segment-resolution/view-transition-default.ts +36 -0
- package/src/router/segment-resolution.ts +4 -1
- package/src/router/segment-wrappers.ts +0 -3
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry-otel.ts +0 -20
- package/src/router/telemetry.ts +96 -19
- package/src/router/timeout.ts +0 -20
- package/src/router/trie-matching.ts +87 -48
- package/src/router/types.ts +9 -63
- package/src/router/url-params.ts +0 -5
- package/src/router.ts +80 -41
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +83 -78
- package/src/rsc/helpers.ts +93 -5
- package/src/rsc/index.ts +1 -1
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/manifest-init.ts +28 -41
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +12 -1
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +76 -62
- package/src/rsc/rsc-rendering.ts +41 -60
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +62 -67
- package/src/rsc/ssr-setup.ts +16 -0
- package/src/rsc/types.ts +10 -5
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +4 -20
- package/src/segment-loader-promise.ts +14 -2
- package/src/segment-system.tsx +199 -142
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +150 -51
- package/src/server/cookie-store.ts +80 -5
- package/src/server/handle-store.ts +7 -24
- package/src/server/loader-registry.ts +5 -24
- package/src/server/request-context.ts +165 -87
- package/src/ssr/index.tsx +14 -14
- package/src/static-handler.ts +10 -13
- package/src/testing/cache-status.ts +162 -0
- package/src/testing/collect-handle.ts +40 -0
- package/src/testing/dispatch.ts +618 -0
- package/src/testing/dom.entry.ts +22 -0
- package/src/testing/e2e/fixture.ts +188 -0
- package/src/testing/e2e/index.ts +128 -0
- package/src/testing/e2e/matchers.ts +35 -0
- package/src/testing/e2e/page-helpers.ts +272 -0
- package/src/testing/e2e/parity.ts +387 -0
- package/src/testing/e2e/server.ts +195 -0
- package/src/testing/flight-matchers.ts +97 -0
- package/src/testing/flight-normalize.ts +11 -0
- package/src/testing/flight-runtime.d.ts +57 -0
- package/src/testing/flight-tree.ts +682 -0
- package/src/testing/flight.entry.ts +52 -0
- package/src/testing/flight.ts +232 -0
- package/src/testing/generated-routes.ts +183 -0
- package/src/testing/index.ts +99 -0
- package/src/testing/internal/context.ts +348 -0
- package/src/testing/internal/flight-client-globals.ts +30 -0
- package/src/testing/internal/seed-vars.ts +54 -0
- package/src/testing/render-handler.ts +330 -0
- package/src/testing/render-route.tsx +566 -0
- package/src/testing/run-loader.ts +378 -0
- package/src/testing/run-middleware.ts +205 -0
- package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
- package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
- package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
- package/src/testing/vitest-stubs/version.ts +5 -0
- package/src/testing/vitest.ts +305 -0
- package/src/theme/ThemeProvider.tsx +0 -52
- package/src/theme/ThemeScript.tsx +0 -6
- package/src/theme/constants.ts +0 -12
- package/src/theme/index.ts +0 -7
- package/src/theme/theme-context.ts +1 -5
- package/src/theme/theme-script.ts +0 -14
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +0 -35
- package/src/types/cache-types.ts +13 -4
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +54 -41
- package/src/types/handler-context.ts +97 -22
- package/src/types/index.ts +1 -10
- package/src/types/loader-types.ts +6 -3
- package/src/types/request-scope.ts +0 -19
- package/src/types/route-config.ts +6 -50
- package/src/types/route-entry.ts +0 -6
- package/src/types/segments.ts +18 -14
- package/src/urls/include-helper.ts +9 -56
- package/src/urls/index.ts +1 -11
- package/src/urls/path-helper-types.ts +19 -5
- package/src/urls/path-helper.ts +17 -106
- package/src/urls/pattern-types.ts +36 -19
- package/src/urls/response-types.ts +20 -19
- package/src/urls/type-extraction.ts +58 -139
- package/src/urls/urls-function.ts +1 -18
- package/src/use-loader.tsx +292 -107
- package/src/vite/debug.ts +1 -0
- package/src/vite/discovery/bundle-postprocess.ts +8 -7
- package/src/vite/discovery/discover-routers.ts +95 -82
- package/src/vite/discovery/discovery-errors.ts +194 -0
- package/src/vite/discovery/prerender-collection.ts +26 -34
- package/src/vite/discovery/route-types-writer.ts +40 -84
- package/src/vite/discovery/state.ts +39 -1
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +4 -0
- package/src/vite/plugin-types.ts +185 -10
- package/src/vite/plugins/cjs-to-esm.ts +3 -18
- package/src/vite/plugins/client-ref-dedup.ts +0 -11
- package/src/vite/plugins/client-ref-hashing.ts +12 -11
- package/src/vite/plugins/cloudflare-protocol-stub.ts +1 -21
- package/src/vite/plugins/expose-action-id.ts +4 -75
- package/src/vite/plugins/expose-id-utils.ts +3 -54
- package/src/vite/plugins/expose-ids/export-analysis.ts +76 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +6 -74
- package/src/vite/plugins/expose-ids/loader-transform.ts +3 -20
- package/src/vite/plugins/expose-ids/router-transform.ts +0 -13
- package/src/vite/plugins/expose-internal-ids.ts +57 -67
- package/src/vite/plugins/performance-tracks.ts +9 -16
- package/src/vite/plugins/refresh-cmd.ts +1 -1
- package/src/vite/plugins/use-cache-transform.ts +26 -49
- package/src/vite/plugins/vercel-output.ts +258 -0
- package/src/vite/plugins/version-injector.ts +2 -32
- package/src/vite/plugins/version-plugin.ts +32 -23
- package/src/vite/plugins/virtual-entries.ts +35 -17
- package/src/vite/rango.ts +148 -115
- package/src/vite/router-discovery.ts +220 -68
- package/src/vite/utils/ast-handler-extract.ts +15 -31
- package/src/vite/utils/bundle-analysis.ts +10 -15
- package/src/vite/utils/client-chunks.ts +184 -0
- package/src/vite/utils/forward-user-plugins.ts +171 -0
- package/src/vite/utils/manifest-utils.ts +4 -59
- package/src/vite/utils/package-resolution.ts +1 -73
- package/src/vite/utils/prerender-utils.ts +0 -34
- package/src/vite/utils/shared-utils.ts +95 -43
- package/src/browser/action-response-classifier.ts +0 -99
- package/src/browser/react/use-client-cache.ts +0 -58
- package/src/browser/shallow.ts +0 -40
- package/src/handles/index.ts +0 -7
- package/src/router/middleware-cookies.ts +0 -55
|
@@ -12,15 +12,18 @@
|
|
|
12
12
|
* - `**.example.com` - any depth subdomain
|
|
13
13
|
* - `admin.*` - admin subdomain of any apex
|
|
14
14
|
* - `example.com/admin` - specific domain with path prefix
|
|
15
|
+
*
|
|
16
|
+
* Apex vs subdomain is classified purely by dot-part COUNT (apex == exactly 2
|
|
17
|
+
* parts) — there is no Public Suffix List. A registrable domain under a
|
|
18
|
+
* multi-label public suffix (example.co.uk, shop.com.au) has 3+ parts and is
|
|
19
|
+
* therefore treated as a SUBDOMAIN, not an apex: `.`/`*` will NOT match it and
|
|
20
|
+
* `*.` WILL. If registrable-domain accuracy matters for a host-router consumer,
|
|
21
|
+
* supply an explicit apex/host hint rather than relying on the part count.
|
|
15
22
|
*/
|
|
16
23
|
|
|
17
24
|
import { InvalidPatternError } from "./errors.js";
|
|
18
25
|
|
|
19
|
-
/**
|
|
20
|
-
* Normalize a pattern by removing trailing slashes from paths
|
|
21
|
-
*/
|
|
22
26
|
export function normalizePattern(pattern: string): string {
|
|
23
|
-
// If pattern has a path component, remove trailing slash
|
|
24
27
|
const slashIndex = pattern.indexOf("/");
|
|
25
28
|
if (slashIndex !== -1) {
|
|
26
29
|
const domain = pattern.slice(0, slashIndex);
|
|
@@ -30,9 +33,6 @@ export function normalizePattern(pattern: string): string {
|
|
|
30
33
|
return pattern;
|
|
31
34
|
}
|
|
32
35
|
|
|
33
|
-
/**
|
|
34
|
-
* Parse hostname and path from request URL
|
|
35
|
-
*/
|
|
36
36
|
export function parseRequest(request: Request): {
|
|
37
37
|
hostname: string;
|
|
38
38
|
pathname: string;
|
|
@@ -46,26 +46,14 @@ export function parseRequest(request: Request): {
|
|
|
46
46
|
return { hostname, pathname, parts };
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
/**
|
|
50
|
-
* Count subdomain levels (0 for apex, 1+ for subdomains)
|
|
51
|
-
*/
|
|
52
49
|
function getSubdomainLevel(parts: string[]): number {
|
|
53
|
-
// Apex domain has 2 parts (example.com)
|
|
54
|
-
// Single subdomain has 3 parts (www.example.com)
|
|
55
|
-
// Multi-level has 4+ parts (a.b.example.com)
|
|
56
50
|
return Math.max(0, parts.length - 2);
|
|
57
51
|
}
|
|
58
52
|
|
|
59
|
-
/**
|
|
60
|
-
* Check if hostname is an apex domain (no subdomains)
|
|
61
|
-
*/
|
|
62
53
|
function isApexDomain(parts: string[]): boolean {
|
|
63
54
|
return parts.length === 2;
|
|
64
55
|
}
|
|
65
56
|
|
|
66
|
-
/**
|
|
67
|
-
* Match a single pattern against hostname and path
|
|
68
|
-
*/
|
|
69
57
|
export function matchPattern(
|
|
70
58
|
pattern: string,
|
|
71
59
|
hostname: string,
|
|
@@ -74,19 +62,16 @@ export function matchPattern(
|
|
|
74
62
|
): boolean {
|
|
75
63
|
const normalized = normalizePattern(pattern);
|
|
76
64
|
|
|
77
|
-
// Check if pattern has path component
|
|
78
65
|
const slashIndex = normalized.indexOf("/");
|
|
79
66
|
const hasPath = slashIndex !== -1;
|
|
80
67
|
const domainPattern = hasPath ? normalized.slice(0, slashIndex) : normalized;
|
|
81
68
|
const pathPattern = hasPath ? normalized.slice(slashIndex) : null;
|
|
82
69
|
|
|
83
|
-
// First match domain
|
|
84
70
|
const domainMatch = matchDomainPattern(domainPattern, hostname, parts);
|
|
85
71
|
if (!domainMatch) {
|
|
86
72
|
return false;
|
|
87
73
|
}
|
|
88
74
|
|
|
89
|
-
// Then match path (prefix match)
|
|
90
75
|
if (pathPattern) {
|
|
91
76
|
return pathname === pathPattern || pathname.startsWith(pathPattern + "/");
|
|
92
77
|
}
|
|
@@ -94,81 +79,62 @@ export function matchPattern(
|
|
|
94
79
|
return true;
|
|
95
80
|
}
|
|
96
81
|
|
|
97
|
-
/**
|
|
98
|
-
* Match domain pattern against hostname
|
|
99
|
-
*/
|
|
100
82
|
function matchDomainPattern(
|
|
101
83
|
pattern: string,
|
|
102
84
|
hostname: string,
|
|
103
85
|
parts: string[],
|
|
104
86
|
): boolean {
|
|
105
|
-
// Exact match
|
|
106
87
|
if (pattern === hostname) {
|
|
107
88
|
return true;
|
|
108
89
|
}
|
|
109
90
|
|
|
110
|
-
// `.` or `*` - any apex domain
|
|
111
91
|
if (pattern === "." || pattern === "*") {
|
|
112
92
|
return isApexDomain(parts);
|
|
113
93
|
}
|
|
114
94
|
|
|
115
|
-
// `**` - any domain (apex + all subdomains)
|
|
116
95
|
if (pattern === "**") {
|
|
117
96
|
return true;
|
|
118
97
|
}
|
|
119
98
|
|
|
120
|
-
// `*.` - any single-level subdomain
|
|
121
99
|
if (pattern === "*.") {
|
|
122
100
|
return getSubdomainLevel(parts) === 1;
|
|
123
101
|
}
|
|
124
102
|
|
|
125
|
-
// `**.` - any multi-level subdomain (2+ levels)
|
|
126
103
|
if (pattern === "**.") {
|
|
127
104
|
return getSubdomainLevel(parts) >= 2;
|
|
128
105
|
}
|
|
129
106
|
|
|
130
|
-
// `*.tld` - any apex domain with specific TLD (e.g., *.com)
|
|
131
107
|
if (pattern.startsWith("*.") && !pattern.includes(".", 2)) {
|
|
132
108
|
const tld = pattern.slice(2);
|
|
133
109
|
return isApexDomain(parts) && hostname.endsWith("." + tld);
|
|
134
110
|
}
|
|
135
111
|
|
|
136
|
-
// `*.example.com` - single subdomain of specific domain
|
|
137
112
|
if (pattern.startsWith("*.")) {
|
|
138
113
|
const baseDomain = pattern.slice(2);
|
|
139
114
|
if (hostname.endsWith("." + baseDomain)) {
|
|
140
|
-
// Count parts: if pattern is *.example.com (3 parts),
|
|
141
|
-
// hostname should have exactly 4 parts (www.example.com)
|
|
142
115
|
const patternParts = baseDomain.split(".");
|
|
143
116
|
return parts.length === patternParts.length + 1;
|
|
144
117
|
}
|
|
145
118
|
return false;
|
|
146
119
|
}
|
|
147
120
|
|
|
148
|
-
// `**.example.com` - any depth subdomain of specific domain
|
|
149
121
|
if (pattern.startsWith("**.")) {
|
|
150
122
|
const baseDomain = pattern.slice(3);
|
|
151
123
|
if (hostname.endsWith("." + baseDomain)) {
|
|
152
124
|
const patternParts = baseDomain.split(".");
|
|
153
|
-
// Must have more parts than the base domain (i.e., has subdomains)
|
|
154
125
|
return parts.length > patternParts.length;
|
|
155
126
|
}
|
|
156
127
|
return false;
|
|
157
128
|
}
|
|
158
129
|
|
|
159
|
-
// `subdomain.*` - specific subdomain of any apex domain
|
|
160
|
-
// e.g., admin.* matches admin.example.com, admin.google.com
|
|
161
130
|
if (pattern.endsWith(".*")) {
|
|
162
131
|
const subdomain = pattern.slice(0, -2);
|
|
163
|
-
// Must be single-level subdomain (3 parts total)
|
|
164
132
|
if (parts.length === 3 && parts[0] === subdomain) {
|
|
165
133
|
return true;
|
|
166
134
|
}
|
|
167
135
|
return false;
|
|
168
136
|
}
|
|
169
137
|
|
|
170
|
-
// `subdomain.**` - specific subdomain of any domain (including multi-level)
|
|
171
|
-
// e.g., admin.** matches admin.example.com, admin.sub.example.com
|
|
172
138
|
if (pattern.endsWith(".**")) {
|
|
173
139
|
const subdomain = pattern.slice(0, -3);
|
|
174
140
|
if (parts.length >= 3 && parts[0] === subdomain) {
|
|
@@ -177,11 +143,8 @@ function matchDomainPattern(
|
|
|
177
143
|
return false;
|
|
178
144
|
}
|
|
179
145
|
|
|
180
|
-
// `subdomain.` - specific subdomain of any apex domain (no wildcard)
|
|
181
|
-
// e.g., admin. matches admin.example.com, admin.google.com
|
|
182
146
|
if (pattern.endsWith(".") && !pattern.includes("*")) {
|
|
183
147
|
const subdomain = pattern.slice(0, -1);
|
|
184
|
-
// Must be exactly 3 parts (subdomain.domain.tld)
|
|
185
148
|
if (parts.length === 3 && parts[0] === subdomain) {
|
|
186
149
|
return true;
|
|
187
150
|
}
|
|
@@ -191,9 +154,6 @@ function matchDomainPattern(
|
|
|
191
154
|
return false;
|
|
192
155
|
}
|
|
193
156
|
|
|
194
|
-
/**
|
|
195
|
-
* Validate pattern format
|
|
196
|
-
*/
|
|
197
157
|
export function validatePattern(pattern: string): void {
|
|
198
158
|
if (!pattern || typeof pattern !== "string") {
|
|
199
159
|
throw new InvalidPatternError(
|
|
@@ -203,12 +163,9 @@ export function validatePattern(pattern: string): void {
|
|
|
203
163
|
);
|
|
204
164
|
}
|
|
205
165
|
|
|
206
|
-
// Check for invalid characters (spaces, etc.)
|
|
207
166
|
if (/\s/.test(pattern)) {
|
|
208
167
|
throw new InvalidPatternError(pattern, "contains whitespace", {
|
|
209
168
|
cause: { pattern },
|
|
210
169
|
});
|
|
211
170
|
}
|
|
212
|
-
|
|
213
|
-
// Additional validation can be added here
|
|
214
171
|
}
|
package/src/host/router.ts
CHANGED
|
@@ -32,29 +32,37 @@ import {
|
|
|
32
32
|
InvalidHandlerError,
|
|
33
33
|
} from "./errors.js";
|
|
34
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
35
|
export interface HostRouterRegistryEntry {
|
|
41
36
|
routes: RouteEntry[];
|
|
42
37
|
fallback: RouteEntry | null;
|
|
43
38
|
}
|
|
44
39
|
|
|
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
40
|
export const HostRouterRegistry: Map<string, HostRouterRegistryEntry> =
|
|
51
41
|
new Map();
|
|
52
42
|
|
|
53
43
|
let hostRouterAutoId = 0;
|
|
54
44
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
45
|
+
function isThenable(value: unknown): value is PromiseLike<unknown> {
|
|
46
|
+
return (
|
|
47
|
+
value !== null &&
|
|
48
|
+
(typeof value === "object" || typeof value === "function") &&
|
|
49
|
+
typeof (value as { then?: unknown }).then === "function"
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function looksLikeLazyModule(value: unknown): boolean {
|
|
54
|
+
if (value === null || typeof value !== "object" || !("default" in value)) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
const defaultExport = (value as { default: unknown }).default;
|
|
58
|
+
return (
|
|
59
|
+
typeof defaultExport === "function" ||
|
|
60
|
+
(typeof defaultExport === "object" &&
|
|
61
|
+
defaultExport !== null &&
|
|
62
|
+
"match" in defaultExport)
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
58
66
|
export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
59
67
|
const routes: RouteEntry[] = [];
|
|
60
68
|
const globalMiddleware: Middleware[] = [];
|
|
@@ -68,48 +76,54 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
|
68
76
|
}
|
|
69
77
|
}
|
|
70
78
|
|
|
71
|
-
/**
|
|
72
|
-
* Create a route builder for chaining
|
|
73
|
-
*/
|
|
74
79
|
function createRouteBuilder(
|
|
75
80
|
patterns: string[],
|
|
76
81
|
isFallback = false,
|
|
77
82
|
): HostRouteBuilder {
|
|
78
83
|
const middleware: Middleware[] = [];
|
|
79
84
|
|
|
85
|
+
function register(
|
|
86
|
+
handler: Handler | LazyHandler,
|
|
87
|
+
kind: RouteEntry["kind"],
|
|
88
|
+
): HostRouter {
|
|
89
|
+
const entry: RouteEntry = {
|
|
90
|
+
patterns,
|
|
91
|
+
middleware,
|
|
92
|
+
handler,
|
|
93
|
+
kind,
|
|
94
|
+
isFallback,
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
if (isFallback) {
|
|
98
|
+
fallbackRoute = entry;
|
|
99
|
+
} else {
|
|
100
|
+
routes.push(entry);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
log(
|
|
104
|
+
`Registered ${isFallback ? "fallback" : "route"} (${kind}):`,
|
|
105
|
+
patterns.join(", "),
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
return router;
|
|
109
|
+
}
|
|
110
|
+
|
|
80
111
|
return {
|
|
81
112
|
use(...mw: Middleware[]): HostRouteBuilder {
|
|
82
113
|
middleware.push(...mw);
|
|
83
114
|
return this;
|
|
84
115
|
},
|
|
85
116
|
|
|
86
|
-
map(handler: Handler
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
);
|
|
117
|
+
map(handler: Handler): HostRouter {
|
|
118
|
+
return register(handler, "handler");
|
|
119
|
+
},
|
|
104
120
|
|
|
105
|
-
|
|
121
|
+
lazy(handler: LazyHandler): HostRouter {
|
|
122
|
+
return register(handler, "lazy");
|
|
106
123
|
},
|
|
107
124
|
};
|
|
108
125
|
}
|
|
109
126
|
|
|
110
|
-
/**
|
|
111
|
-
* Find matching route for hostname and path
|
|
112
|
-
*/
|
|
113
127
|
function findMatchingRoute(
|
|
114
128
|
hostname: string,
|
|
115
129
|
pathname: string,
|
|
@@ -128,9 +142,6 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
|
128
142
|
return null;
|
|
129
143
|
}
|
|
130
144
|
|
|
131
|
-
/**
|
|
132
|
-
* Execute middleware chain
|
|
133
|
-
*/
|
|
134
145
|
async function executeMiddleware(
|
|
135
146
|
middleware: Middleware[],
|
|
136
147
|
request: Request,
|
|
@@ -149,8 +160,6 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
|
149
160
|
return finalHandler();
|
|
150
161
|
}
|
|
151
162
|
|
|
152
|
-
// Guard against double next() calls — a second call would
|
|
153
|
-
// re-enter the downstream chain and run handlers/side-effects twice.
|
|
154
163
|
let nextCalled = false;
|
|
155
164
|
const guardedNext = (): Promise<Response> => {
|
|
156
165
|
if (nextCalled) {
|
|
@@ -168,62 +177,73 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
|
168
177
|
return next();
|
|
169
178
|
}
|
|
170
179
|
|
|
171
|
-
/**
|
|
172
|
-
* Execute handler (lazy or direct)
|
|
173
|
-
*/
|
|
174
180
|
async function executeHandler(
|
|
175
|
-
|
|
181
|
+
entry: RouteEntry,
|
|
176
182
|
request: Request,
|
|
177
183
|
input: RouterRequestInput<any>,
|
|
178
184
|
): Promise<Response> {
|
|
179
|
-
|
|
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
|
-
}
|
|
185
|
+
const { handler, kind } = entry;
|
|
202
186
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
|
|
187
|
+
if (typeof handler !== "function") {
|
|
188
|
+
throw new InvalidHandlerError(handler, {
|
|
189
|
+
cause: { handlerType: typeof handler },
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (kind === "lazy") {
|
|
194
|
+
return executeLazyMount(handler as LazyHandler, request, input);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const result = (handler as Handler)(request, input);
|
|
198
|
+
|
|
199
|
+
if (isThenable(result)) {
|
|
200
|
+
const awaited = await result;
|
|
201
|
+
if (looksLikeLazyModule(awaited)) {
|
|
202
|
+
throw new HostRouterError(
|
|
203
|
+
".map() is for inline request handlers; use .lazy(() => import(...)) for lazy host mounts.",
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
return awaited as Response;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return result;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async function executeLazyMount(
|
|
213
|
+
loader: LazyHandler,
|
|
214
|
+
request: Request,
|
|
215
|
+
input: RouterRequestInput<any>,
|
|
216
|
+
): Promise<Response> {
|
|
217
|
+
const module = await loader();
|
|
218
|
+
|
|
219
|
+
if (typeof module === "object" && module !== null && "default" in module) {
|
|
220
|
+
const defaultExport = (module as { default: Handler | HostRouter })
|
|
221
|
+
.default;
|
|
222
|
+
|
|
223
|
+
if (
|
|
224
|
+
typeof defaultExport === "object" &&
|
|
225
|
+
defaultExport !== null &&
|
|
226
|
+
"match" in defaultExport
|
|
227
|
+
) {
|
|
228
|
+
return (defaultExport as HostRouter).match(request, input);
|
|
208
229
|
}
|
|
209
230
|
|
|
210
|
-
|
|
211
|
-
return result as Response | Promise<Response>;
|
|
231
|
+
return (defaultExport as Handler)(request, input);
|
|
212
232
|
}
|
|
213
233
|
|
|
214
|
-
throw new InvalidHandlerError(
|
|
215
|
-
cause: {
|
|
234
|
+
throw new InvalidHandlerError(loader, {
|
|
235
|
+
cause: {
|
|
236
|
+
reason:
|
|
237
|
+
"lazy mount did not resolve to a module with a default export; " +
|
|
238
|
+
"use .lazy(() => import('./sub-app')) where the module default-exports a handler or host router",
|
|
239
|
+
},
|
|
216
240
|
});
|
|
217
241
|
}
|
|
218
242
|
|
|
219
|
-
/**
|
|
220
|
-
* Router instance
|
|
221
|
-
*/
|
|
222
243
|
const router: HostRouter = {
|
|
223
244
|
host(patterns: HostPattern): HostRouteBuilder {
|
|
224
245
|
const patternsArray = Array.isArray(patterns) ? patterns : [patterns];
|
|
225
246
|
|
|
226
|
-
// Validate and normalize patterns
|
|
227
247
|
const normalized = patternsArray.map((p) => {
|
|
228
248
|
validatePattern(p);
|
|
229
249
|
return normalizePattern(p);
|
|
@@ -242,9 +262,8 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
|
242
262
|
return createRouteBuilder([], true);
|
|
243
263
|
},
|
|
244
264
|
|
|
245
|
-
test(hostname: string): HostMatchResult | null {
|
|
265
|
+
test(hostname: string, pathname = "/"): HostMatchResult | null {
|
|
246
266
|
const parts = hostname.split(".");
|
|
247
|
-
const pathname = "/";
|
|
248
267
|
|
|
249
268
|
for (const route of routes) {
|
|
250
269
|
for (const pattern of route.patterns) {
|
|
@@ -252,6 +271,7 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
|
252
271
|
return {
|
|
253
272
|
pattern,
|
|
254
273
|
handler: route.handler,
|
|
274
|
+
kind: route.kind,
|
|
255
275
|
};
|
|
256
276
|
}
|
|
257
277
|
}
|
|
@@ -269,14 +289,11 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
|
269
289
|
let effectiveHostname: string;
|
|
270
290
|
|
|
271
291
|
try {
|
|
272
|
-
// Handle cookie override (may throw HostRouterError)
|
|
273
292
|
effectiveHostname = handleCookieOverride(request, hostOverride, input);
|
|
274
293
|
} catch (error) {
|
|
275
|
-
// If it's a HostRouterError from cookie override
|
|
276
294
|
if (error instanceof HostRouterError) {
|
|
277
295
|
log(`Cookie override error: ${error.message}`);
|
|
278
296
|
|
|
279
|
-
// If fallback exists, use it
|
|
280
297
|
if (fallbackRoute) {
|
|
281
298
|
const fallbackInput = { ...input, error };
|
|
282
299
|
const allMiddleware = [
|
|
@@ -288,12 +305,10 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
|
288
305
|
allMiddleware,
|
|
289
306
|
request,
|
|
290
307
|
fallbackInput,
|
|
291
|
-
() =>
|
|
292
|
-
executeHandler(fallbackRoute!.handler, request, fallbackInput),
|
|
308
|
+
() => executeHandler(fallbackRoute!, request, fallbackInput),
|
|
293
309
|
);
|
|
294
310
|
}
|
|
295
311
|
|
|
296
|
-
// Otherwise return error response with cookie deletion
|
|
297
312
|
if (hostOverride) {
|
|
298
313
|
return createCookieErrorResponse(
|
|
299
314
|
hostOverride.cookieName,
|
|
@@ -302,7 +317,6 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
|
302
317
|
}
|
|
303
318
|
}
|
|
304
319
|
|
|
305
|
-
// Re-throw non-HostRouterErrors
|
|
306
320
|
throw error;
|
|
307
321
|
}
|
|
308
322
|
|
|
@@ -312,7 +326,6 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
|
312
326
|
log(`Cookie override: ${effectiveHostname}`);
|
|
313
327
|
}
|
|
314
328
|
|
|
315
|
-
// Find matching route
|
|
316
329
|
const matchedRoute = findMatchingRoute(effectiveHostname, pathname);
|
|
317
330
|
|
|
318
331
|
if (!matchedRoute) {
|
|
@@ -325,19 +338,14 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
|
325
338
|
});
|
|
326
339
|
}
|
|
327
340
|
|
|
328
|
-
// Combine global and route-specific middleware
|
|
329
341
|
const allMiddleware = [...globalMiddleware, ...matchedRoute.middleware];
|
|
330
342
|
|
|
331
|
-
// Execute middleware chain and handler
|
|
332
343
|
return executeMiddleware(allMiddleware, request, input, () =>
|
|
333
|
-
executeHandler(matchedRoute
|
|
344
|
+
executeHandler(matchedRoute, request, input),
|
|
334
345
|
);
|
|
335
346
|
},
|
|
336
347
|
};
|
|
337
348
|
|
|
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
349
|
const registryId = `host-router-${hostRouterAutoId++}`;
|
|
342
350
|
HostRouterRegistry.set(registryId, {
|
|
343
351
|
get routes() {
|
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 "./pattern-matcher.js";
|
|
7
|
+
import { matchPattern, parseRequest } from "./pattern-matcher.js";
|
|
8
8
|
|
|
9
9
|
export interface CreateTestRequestOptions {
|
|
10
10
|
host: string;
|
|
@@ -14,18 +14,6 @@ export interface CreateTestRequestOptions {
|
|
|
14
14
|
headers?: Record<string, string>;
|
|
15
15
|
}
|
|
16
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
17
|
export function createTestRequest(options: CreateTestRequestOptions): Request {
|
|
30
18
|
const {
|
|
31
19
|
host,
|
|
@@ -38,7 +26,6 @@ export function createTestRequest(options: CreateTestRequestOptions): Request {
|
|
|
38
26
|
const url = `http://${host}${path}`;
|
|
39
27
|
const requestHeaders = new Headers(headers);
|
|
40
28
|
|
|
41
|
-
// Add cookies if provided
|
|
42
29
|
if (Object.keys(cookies).length > 0) {
|
|
43
30
|
const cookieString = Object.entries(cookies)
|
|
44
31
|
.map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
|
|
@@ -52,28 +39,54 @@ export function createTestRequest(options: CreateTestRequestOptions): Request {
|
|
|
52
39
|
});
|
|
53
40
|
}
|
|
54
41
|
|
|
42
|
+
function matchPatterns(
|
|
43
|
+
pattern: string | string[],
|
|
44
|
+
hostname: string,
|
|
45
|
+
pathname: string,
|
|
46
|
+
parts: string[],
|
|
47
|
+
): boolean {
|
|
48
|
+
const patterns = Array.isArray(pattern) ? pattern : [pattern];
|
|
49
|
+
return patterns.some((p) => matchPattern(p, hostname, pathname, parts));
|
|
50
|
+
}
|
|
51
|
+
|
|
55
52
|
/**
|
|
56
|
-
* Test if a pattern matches a hostname
|
|
53
|
+
* Test if a pattern matches a hostname (and, for path-based patterns, a pathname).
|
|
54
|
+
*
|
|
55
|
+
* `pathname` defaults to `"/"`, so a host-only pattern works with two args. Pass
|
|
56
|
+
* the third arg to test a path-based pattern (`**.workers.dev/admin`,
|
|
57
|
+
* `localhost/shop`) — without it those patterns can never match.
|
|
57
58
|
*
|
|
58
59
|
* @example
|
|
59
60
|
* ```ts
|
|
60
|
-
* expect(testPattern(
|
|
61
|
-
* expect(testPattern([
|
|
61
|
+
* expect(testPattern("admin.*", "admin.example.com")).toBe(true);
|
|
62
|
+
* expect(testPattern(["*", "www.*"], "example.com")).toBe(true);
|
|
63
|
+
* expect(testPattern("**.workers.dev/admin", "foo.workers.dev", "/admin")).toBe(true);
|
|
62
64
|
* ```
|
|
63
65
|
*/
|
|
64
66
|
export function testPattern(
|
|
65
67
|
pattern: string | string[],
|
|
66
68
|
hostname: string,
|
|
69
|
+
pathname: string = "/",
|
|
67
70
|
): boolean {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const pathname = "/";
|
|
71
|
-
|
|
72
|
-
for (const p of patterns) {
|
|
73
|
-
if (matchPattern(p, hostname, pathname, parts)) {
|
|
74
|
-
return true;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
71
|
+
return matchPatterns(pattern, hostname, pathname, hostname.split("."));
|
|
72
|
+
}
|
|
77
73
|
|
|
78
|
-
|
|
74
|
+
/**
|
|
75
|
+
* Test if a pattern matches a `Request` — the hostname AND pathname are taken
|
|
76
|
+
* from the request URL (via the same `parseRequest` the host router uses), so a
|
|
77
|
+
* path-based pattern is tested against a real request without splitting the URL
|
|
78
|
+
* by hand.
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* ```ts
|
|
82
|
+
* const req = new Request("https://foo.workers.dev/admin");
|
|
83
|
+
* expect(matchesHost("**.workers.dev/admin", req)).toBe(true);
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
86
|
+
export function matchesHost(
|
|
87
|
+
pattern: string | string[],
|
|
88
|
+
request: Request,
|
|
89
|
+
): boolean {
|
|
90
|
+
const { hostname, pathname, parts } = parseRequest(request);
|
|
91
|
+
return matchPatterns(pattern, hostname, pathname, parts);
|
|
79
92
|
}
|