@rangojs/router 0.0.0-experimental.bd6e11bc → 0.0.0-experimental.bdaf10aa
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 +8 -4
- package/README.md +296 -887
- package/dist/bin/rango.js +459 -91
- package/dist/testing/vitest.js +36 -2
- package/dist/vite/index.js +1708 -414
- package/package.json +35 -10
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +82 -5
- package/skills/bundle-analysis/SKILL.md +2 -2
- package/skills/cache-guide/SKILL.md +14 -9
- package/skills/caching/SKILL.md +221 -12
- package/skills/catalog.json +271 -0
- package/skills/comparison/SKILL.md +50 -0
- package/skills/comparison/agents/openai.yaml +4 -0
- package/skills/comparison/references/framework-comparison.md +837 -0
- package/skills/composability/SKILL.md +83 -2
- package/skills/css/SKILL.md +76 -0
- package/skills/debug-manifest/SKILL.md +5 -3
- package/skills/defer-hydration/SKILL.md +235 -0
- package/skills/document-cache/SKILL.md +11 -3
- package/skills/fonts/SKILL.md +1 -1
- package/skills/handler-use/SKILL.md +9 -9
- package/skills/hooks/SKILL.md +73 -900
- package/skills/hooks/data.md +273 -0
- package/skills/hooks/handle-and-actions.md +103 -0
- package/skills/hooks/navigation.md +110 -0
- package/skills/hooks/outlets.md +41 -0
- package/skills/hooks/state.md +228 -0
- package/skills/hooks/urls.md +135 -0
- package/skills/host-router/SKILL.md +84 -7
- package/skills/i18n/SKILL.md +1 -1
- package/skills/intercept/SKILL.md +51 -17
- package/skills/layout/SKILL.md +38 -16
- package/skills/links/SKILL.md +1 -1
- package/skills/loader/SKILL.md +48 -20
- package/skills/middleware/SKILL.md +11 -5
- package/skills/migrate-nextjs/SKILL.md +203 -20
- package/skills/migrate-react-router/SKILL.md +59 -675
- package/skills/migrate-react-router/cloudflare-workers.md +129 -0
- package/skills/migrate-react-router/component-migration.md +196 -0
- package/skills/migrate-react-router/data-and-actions.md +225 -0
- package/skills/migrate-react-router/route-mapping.md +271 -0
- package/skills/mime-routes/SKILL.md +3 -3
- package/skills/observability/SKILL.md +70 -5
- package/skills/parallel/SKILL.md +32 -8
- package/skills/ppr/SKILL.md +622 -0
- package/skills/prerender/SKILL.md +59 -28
- package/skills/rango/SKILL.md +124 -50
- package/skills/response-routes/SKILL.md +78 -46
- package/skills/route/SKILL.md +85 -6
- package/skills/router-setup/SKILL.md +41 -6
- package/skills/scripts/SKILL.md +179 -0
- package/skills/server-actions/SKILL.md +28 -3
- package/skills/shell-manifest/SKILL.md +185 -0
- package/skills/streams-and-websockets/SKILL.md +1 -1
- package/skills/tailwind/SKILL.md +28 -4
- package/skills/testing/SKILL.md +68 -654
- package/skills/testing/bindings.md +103 -0
- package/skills/testing/cache-prerender.md +127 -0
- package/skills/testing/client-components.md +124 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +91 -0
- package/skills/testing/handles.md +131 -0
- package/skills/testing/loader.md +128 -0
- package/skills/testing/middleware.md +99 -0
- package/skills/testing/render-handler.md +122 -0
- package/skills/testing/response-routes.md +95 -0
- package/skills/testing/reverse-and-types.md +85 -0
- package/skills/testing/server-actions.md +107 -0
- package/skills/testing/server-tree.md +128 -0
- package/skills/testing/setup.md +123 -0
- package/skills/theme/SKILL.md +1 -1
- package/skills/typesafety/SKILL.md +45 -918
- package/skills/typesafety/env-and-bindings.md +254 -0
- package/skills/typesafety/generated-files-and-cli.md +335 -0
- package/skills/typesafety/params-and-search.md +153 -0
- package/skills/typesafety/route-types.md +209 -0
- package/skills/use-cache/SKILL.md +47 -17
- package/skills/vercel/SKILL.md +128 -0
- package/skills/view-transitions/SKILL.md +44 -1
- package/src/__augment-tests__/augmented.check.ts +2 -3
- package/src/__internal.ts +0 -65
- package/src/browser/action-coordinator.ts +1 -1
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +14 -27
- package/src/browser/connection-warmup.ts +134 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +178 -100
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/logging.ts +28 -0
- package/src/browser/merge-segment-loaders.ts +6 -4
- package/src/browser/navigation-bridge.ts +81 -68
- package/src/browser/navigation-client.ts +115 -70
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +153 -88
- package/src/browser/navigation-transaction.ts +0 -32
- package/src/browser/network-error-handler.ts +34 -7
- package/src/browser/partial-update.ts +157 -144
- package/src/browser/prefetch/cache.ts +148 -81
- package/src/browser/prefetch/fetch.ts +231 -51
- package/src/browser/prefetch/queue.ts +25 -7
- package/src/browser/rango-state.ts +157 -115
- package/src/browser/react/Link.tsx +40 -7
- package/src/browser/react/NavigationProvider.tsx +140 -99
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/filter-segment-order.ts +17 -2
- package/src/browser/react/index.ts +0 -51
- package/src/browser/react/location-state-shared.ts +14 -15
- package/src/browser/react/location-state.ts +0 -1
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +0 -5
- package/src/browser/react/use-href.tsx +8 -1
- package/src/browser/react/use-link-status.ts +33 -8
- package/src/browser/react/use-navigation.ts +10 -5
- package/src/browser/react/use-params.ts +0 -2
- package/src/browser/react/use-router.ts +6 -4
- 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 +74 -8
- package/src/browser/rsc-router.tsx +97 -22
- package/src/browser/scroll-restoration.ts +15 -8
- package/src/browser/segment-reconciler.ts +31 -21
- package/src/browser/server-action-bridge.ts +216 -38
- package/src/browser/types.ts +94 -22
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/generate-manifest.ts +155 -131
- package/src/build/generate-route-types.ts +1 -1
- package/src/build/index.ts +11 -5
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +152 -22
- package/src/build/route-types/ast-route-extraction.ts +15 -8
- package/src/build/route-types/codegen.ts +12 -1
- package/src/build/route-types/include-resolution.ts +455 -61
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/per-module-writer.ts +15 -2
- package/src/build/route-types/router-processing.ts +77 -41
- package/src/build/route-types/source-scan.ts +105 -7
- package/src/build/runtime-discovery.ts +4 -1
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-key-utils.ts +58 -13
- package/src/cache/cache-policy.ts +108 -34
- package/src/cache/cache-runtime.ts +454 -101
- package/src/cache/cache-scope.ts +159 -54
- package/src/cache/cache-tag.ts +149 -0
- package/src/cache/cf/cf-base64.ts +33 -0
- package/src/cache/cf/cf-cache-constants.ts +127 -0
- package/src/cache/cf/cf-cache-store.ts +2170 -377
- package/src/cache/cf/cf-cache-types.ts +349 -0
- package/src/cache/cf/cf-kv-utils.ts +46 -0
- package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
- package/src/cache/cf/index.ts +6 -16
- package/src/cache/document-cache.ts +126 -41
- package/src/cache/handle-snapshot.ts +70 -0
- package/src/cache/index.ts +23 -20
- package/src/cache/memory-segment-store.ts +243 -37
- package/src/cache/profile-registry.ts +46 -31
- package/src/cache/read-through-swr.ts +56 -12
- package/src/cache/segment-codec.ts +13 -21
- package/src/cache/shell-snapshot.ts +417 -0
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/types.ts +194 -99
- package/src/cache/vercel/index.ts +11 -0
- package/src/cache/vercel/vercel-cache-store.ts +1132 -0
- package/src/client.rsc.tsx +39 -22
- package/src/client.tsx +28 -58
- package/src/cloudflare/index.ts +11 -0
- package/src/cloudflare/tracing.ts +108 -0
- package/src/component-utils.ts +19 -0
- package/src/components/DefaultDocument.tsx +8 -2
- package/src/context-var.ts +13 -1
- package/src/decode-loader-results.ts +18 -2
- package/src/defer.ts +185 -0
- package/src/deps/ssr.ts +0 -1
- package/src/encode-kv.ts +49 -0
- package/src/errors.ts +0 -3
- package/src/escape-script.ts +52 -0
- package/src/handle.ts +57 -40
- package/src/handles/MetaTags.tsx +24 -53
- package/src/handles/Scripts.tsx +183 -0
- package/src/handles/breadcrumbs.ts +35 -8
- package/src/handles/deferred-resolution.ts +127 -0
- package/src/handles/is-thenable.ts +18 -0
- package/src/handles/meta.ts +14 -40
- package/src/handles/script.ts +244 -0
- package/src/host/cookie-handler.ts +9 -60
- package/src/host/errors.ts +13 -22
- package/src/host/index.ts +7 -0
- package/src/host/pattern-matcher.ts +23 -52
- package/src/host/router.ts +1 -65
- package/src/host/testing.ts +40 -27
- package/src/host/types.ts +6 -2
- package/src/href-client.ts +7 -12
- package/src/index.rsc.ts +88 -8
- package/src/index.ts +90 -16
- package/src/internal-debug.ts +11 -10
- package/src/loader.rsc.ts +19 -9
- package/src/loader.ts +12 -4
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +16 -16
- package/src/prerender/store.ts +32 -37
- package/src/prerender.ts +75 -7
- package/src/redirect-origin.ts +114 -0
- package/src/regex-escape.ts +8 -0
- package/src/render-error-thrower.tsx +20 -0
- package/src/response-utils.ts +25 -0
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +13 -49
- package/src/route-definition/dsl-helpers.ts +60 -53
- package/src/route-definition/helper-factories.ts +0 -2
- package/src/route-definition/helpers-types.ts +46 -46
- package/src/route-definition/index.ts +1 -2
- package/src/route-definition/redirect.ts +44 -11
- package/src/route-definition/resolve-handler-use.ts +6 -1
- package/src/route-definition/use-item-types.ts +3 -6
- package/src/route-map-builder.ts +41 -20
- package/src/route-types.ts +0 -5
- package/src/router/content-negotiation.ts +58 -23
- package/src/router/error-handling.ts +44 -17
- package/src/router/find-match.ts +129 -30
- package/src/router/handler-context.ts +6 -1
- package/src/router/instrument.ts +355 -0
- package/src/router/intercept-resolution.ts +35 -2
- package/src/router/lazy-includes.ts +79 -56
- package/src/router/loader-resolution.ts +151 -73
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +74 -40
- package/src/router/match-api.ts +76 -52
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +181 -178
- package/src/router/match-middleware/background-revalidation.ts +40 -24
- package/src/router/match-middleware/cache-lookup.ts +115 -194
- package/src/router/match-middleware/cache-store.ts +61 -50
- 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 +36 -67
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-types.ts +0 -116
- package/src/router/middleware.ts +231 -120
- package/src/router/navigation-snapshot.ts +7 -56
- package/src/router/params-util.ts +23 -0
- package/src/router/parse-pattern.ts +115 -0
- package/src/router/pattern-matching.ts +99 -152
- package/src/router/prefetch-cache-ttl.ts +51 -0
- package/src/router/prefetch-limits.ts +37 -0
- package/src/router/prerender-match.ts +111 -66
- package/src/router/preview-match.ts +3 -1
- package/src/router/request-classification.ts +47 -42
- package/src/router/revalidation.ts +75 -81
- package/src/router/route-snapshot.ts +14 -3
- package/src/router/router-context.ts +6 -29
- package/src/router/router-interfaces.ts +70 -8
- package/src/router/router-options.ts +126 -4
- package/src/router/segment-resolution/fresh.ts +104 -80
- package/src/router/segment-resolution/helpers.ts +86 -6
- package/src/router/segment-resolution/loader-cache.ts +155 -39
- package/src/router/segment-resolution/loader-mask.ts +60 -0
- package/src/router/segment-resolution/loader-snapshot.ts +259 -0
- package/src/router/segment-resolution/mask-nested.ts +83 -0
- package/src/router/segment-resolution/revalidation.ts +215 -304
- 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 +35 -15
- package/src/router/segment-resolution.ts +5 -1
- package/src/router/segment-wrappers.ts +6 -5
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +54 -35
- package/src/router/telemetry-otel.ts +160 -200
- package/src/router/telemetry.ts +9 -23
- package/src/router/timeout.ts +0 -20
- package/src/router/tracing.ts +215 -0
- package/src/router/trie-matching.ts +171 -64
- package/src/router/types.ts +1 -63
- package/src/router/url-params.ts +13 -5
- package/src/router.ts +119 -48
- package/src/rsc/full-payload.ts +70 -0
- package/src/rsc/handler-context.ts +1 -0
- package/src/rsc/handler.ts +267 -152
- package/src/rsc/helpers.ts +78 -4
- package/src/rsc/index.ts +1 -4
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +114 -38
- package/src/rsc/manifest-init.ts +29 -42
- package/src/rsc/nonce.ts +10 -1
- package/src/rsc/origin-guard.ts +11 -15
- package/src/rsc/progressive-enhancement.ts +120 -13
- package/src/rsc/redirect-guard.ts +100 -0
- package/src/rsc/response-cache-serve.ts +238 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +58 -141
- package/src/rsc/rsc-rendering.ts +492 -49
- package/src/rsc/runtime-warnings.ts +14 -0
- package/src/rsc/server-action.ts +268 -82
- package/src/rsc/shell-capture.ts +1190 -0
- package/src/rsc/shell-serve.ts +181 -0
- package/src/rsc/transition-gate.ts +89 -0
- package/src/rsc/types.ts +45 -3
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +31 -26
- package/src/segment-loader-promise.ts +49 -4
- package/src/segment-system.tsx +260 -95
- package/src/server/context.ts +99 -9
- package/src/server/cookie-parse.ts +32 -0
- package/src/server/cookie-store.ts +125 -2
- package/src/server/handle-store.ts +21 -38
- package/src/server/loader-registry.ts +33 -42
- package/src/server/request-context.ts +379 -138
- package/src/ssr/index.tsx +491 -182
- package/src/ssr/inject-rsc-eager.ts +167 -0
- package/src/ssr/ssr-root.tsx +228 -0
- package/src/static-handler.ts +10 -13
- package/src/testing/cache-status.ts +44 -48
- package/src/testing/collect-handle.ts +14 -31
- package/src/testing/dispatch.ts +533 -160
- package/src/testing/e2e/fixture.ts +45 -11
- package/src/testing/e2e/index.ts +1 -22
- package/src/testing/e2e/matchers.ts +0 -16
- package/src/testing/e2e/parity.ts +85 -4
- package/src/testing/e2e/server.ts +12 -0
- package/src/testing/flight-matchers.ts +7 -14
- package/src/testing/flight-normalize.ts +11 -0
- package/src/testing/flight-runtime.d.ts +36 -0
- package/src/testing/flight-tree.ts +682 -0
- package/src/testing/flight.entry.ts +30 -0
- package/src/testing/flight.ts +145 -70
- package/src/testing/generated-routes.ts +26 -50
- package/src/testing/index.ts +18 -19
- package/src/testing/internal/context.ts +184 -68
- 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 +357 -0
- package/src/testing/render-route.tsx +134 -115
- package/src/testing/run-loader.ts +140 -51
- package/src/testing/run-middleware.ts +59 -33
- package/src/testing/run-transition-when.ts +164 -0
- package/src/testing/vitest-stubs/cloudflare-email.ts +1 -1
- package/src/testing/vitest-stubs/cloudflare-workers.ts +1 -1
- package/src/testing/vitest.ts +138 -16
- package/src/theme/ThemeProvider.tsx +56 -84
- package/src/theme/ThemeScript.tsx +7 -9
- package/src/theme/constants.ts +52 -13
- package/src/theme/index.ts +0 -7
- package/src/theme/theme-context.ts +1 -5
- package/src/theme/theme-script.ts +22 -21
- 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 +15 -15
- package/src/types/handler-context.ts +45 -15
- package/src/types/index.ts +2 -10
- package/src/types/loader-types.ts +6 -3
- package/src/types/request-scope.ts +8 -22
- package/src/types/route-config.ts +20 -52
- package/src/types/route-entry.ts +0 -6
- package/src/types/segments.ts +100 -13
- package/src/urls/include-helper.ts +10 -12
- package/src/urls/include-provider.ts +71 -0
- package/src/urls/index.ts +2 -8
- package/src/urls/path-helper-types.ts +52 -14
- package/src/urls/path-helper.ts +5 -54
- package/src/urls/pattern-types.ts +36 -0
- package/src/urls/type-extraction.ts +76 -42
- package/src/urls/urls-function.ts +0 -14
- package/src/use-loader.tsx +0 -186
- package/src/vercel/index.ts +11 -0
- package/src/vercel/tracing.ts +88 -0
- package/src/vite/discovery/bundle-postprocess.ts +2 -1
- package/src/vite/discovery/dev-prerender-cache.ts +117 -0
- package/src/vite/discovery/discover-routers.ts +34 -43
- package/src/vite/discovery/discovery-errors.ts +61 -0
- package/src/vite/discovery/prerender-collection.ts +33 -46
- package/src/vite/discovery/state.ts +12 -1
- package/src/vite/discovery/virtual-module-codegen.ts +1 -11
- package/src/vite/index.ts +9 -0
- package/src/vite/inject-client-debug.ts +88 -0
- package/src/vite/plugin-types.ts +143 -10
- package/src/vite/plugins/cjs-to-esm.ts +8 -12
- package/src/vite/plugins/client-ref-dedup.ts +0 -11
- package/src/vite/plugins/client-ref-hashing.ts +0 -10
- package/src/vite/plugins/cloudflare-protocol-stub.ts +0 -20
- package/src/vite/plugins/expose-action-id.ts +2 -73
- package/src/vite/plugins/expose-id-utils.ts +85 -56
- package/src/vite/plugins/expose-ids/export-analysis.ts +30 -43
- package/src/vite/plugins/expose-ids/handler-transform.ts +5 -31
- package/src/vite/plugins/expose-ids/loader-transform.ts +12 -20
- package/src/vite/plugins/expose-ids/router-transform.ts +98 -26
- package/src/vite/plugins/expose-internal-ids.ts +10 -1
- package/src/vite/plugins/performance-tracks.ts +0 -3
- package/src/vite/plugins/refresh-cmd.ts +1 -1
- package/src/vite/plugins/use-cache-transform.ts +21 -46
- package/src/vite/plugins/vercel-output.ts +384 -0
- package/src/vite/plugins/version-injector.ts +22 -27
- package/src/vite/plugins/version-plugin.ts +6 -66
- package/src/vite/plugins/virtual-entries.ts +137 -26
- package/src/vite/rango.ts +146 -135
- package/src/vite/router-discovery.ts +189 -48
- package/src/vite/utils/ast-handler-extract.ts +11 -20
- package/src/vite/utils/bundle-analysis.ts +6 -13
- package/src/vite/utils/client-chunks.ts +0 -6
- package/src/vite/utils/directive-prologue.ts +40 -0
- package/src/vite/utils/forward-user-plugins.ts +0 -22
- package/src/vite/utils/manifest-utils.ts +4 -75
- package/src/vite/utils/package-resolution.ts +1 -73
- package/src/vite/utils/prerender-utils.ts +71 -44
- package/src/vite/utils/shared-utils.ts +55 -37
- 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/network-error-thrower.tsx +0 -23
- 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,30 @@ 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
|
+
// Hosts are case-insensitive (RFC 3986): lowercase the domain literal and the
|
|
68
|
+
// request host once so matching folds case. Wildcards (*, **, .) are
|
|
69
|
+
// unaffected by lowercasing. The path is left untouched (paths are
|
|
70
|
+
// case-sensitive).
|
|
71
|
+
const domainPattern = (
|
|
72
|
+
hasPath ? normalized.slice(0, slashIndex) : normalized
|
|
73
|
+
).toLowerCase();
|
|
81
74
|
const pathPattern = hasPath ? normalized.slice(slashIndex) : null;
|
|
82
75
|
|
|
83
|
-
|
|
84
|
-
const
|
|
76
|
+
const lowerHostname = hostname.toLowerCase();
|
|
77
|
+
const lowerParts =
|
|
78
|
+
lowerHostname === hostname ? parts : lowerHostname.split(".");
|
|
79
|
+
|
|
80
|
+
const domainMatch = matchDomainPattern(
|
|
81
|
+
domainPattern,
|
|
82
|
+
lowerHostname,
|
|
83
|
+
lowerParts,
|
|
84
|
+
);
|
|
85
85
|
if (!domainMatch) {
|
|
86
86
|
return false;
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
// Then match path (prefix match)
|
|
90
89
|
if (pathPattern) {
|
|
91
90
|
return pathname === pathPattern || pathname.startsWith(pathPattern + "/");
|
|
92
91
|
}
|
|
@@ -94,81 +93,62 @@ export function matchPattern(
|
|
|
94
93
|
return true;
|
|
95
94
|
}
|
|
96
95
|
|
|
97
|
-
/**
|
|
98
|
-
* Match domain pattern against hostname
|
|
99
|
-
*/
|
|
100
96
|
function matchDomainPattern(
|
|
101
97
|
pattern: string,
|
|
102
98
|
hostname: string,
|
|
103
99
|
parts: string[],
|
|
104
100
|
): boolean {
|
|
105
|
-
// Exact match
|
|
106
101
|
if (pattern === hostname) {
|
|
107
102
|
return true;
|
|
108
103
|
}
|
|
109
104
|
|
|
110
|
-
// `.` or `*` - any apex domain
|
|
111
105
|
if (pattern === "." || pattern === "*") {
|
|
112
106
|
return isApexDomain(parts);
|
|
113
107
|
}
|
|
114
108
|
|
|
115
|
-
// `**` - any domain (apex + all subdomains)
|
|
116
109
|
if (pattern === "**") {
|
|
117
110
|
return true;
|
|
118
111
|
}
|
|
119
112
|
|
|
120
|
-
// `*.` - any single-level subdomain
|
|
121
113
|
if (pattern === "*.") {
|
|
122
114
|
return getSubdomainLevel(parts) === 1;
|
|
123
115
|
}
|
|
124
116
|
|
|
125
|
-
// `**.` - any multi-level subdomain (2+ levels)
|
|
126
117
|
if (pattern === "**.") {
|
|
127
118
|
return getSubdomainLevel(parts) >= 2;
|
|
128
119
|
}
|
|
129
120
|
|
|
130
|
-
// `*.tld` - any apex domain with specific TLD (e.g., *.com)
|
|
131
121
|
if (pattern.startsWith("*.") && !pattern.includes(".", 2)) {
|
|
132
122
|
const tld = pattern.slice(2);
|
|
133
123
|
return isApexDomain(parts) && hostname.endsWith("." + tld);
|
|
134
124
|
}
|
|
135
125
|
|
|
136
|
-
// `*.example.com` - single subdomain of specific domain
|
|
137
126
|
if (pattern.startsWith("*.")) {
|
|
138
127
|
const baseDomain = pattern.slice(2);
|
|
139
128
|
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
129
|
const patternParts = baseDomain.split(".");
|
|
143
130
|
return parts.length === patternParts.length + 1;
|
|
144
131
|
}
|
|
145
132
|
return false;
|
|
146
133
|
}
|
|
147
134
|
|
|
148
|
-
// `**.example.com` - any depth subdomain of specific domain
|
|
149
135
|
if (pattern.startsWith("**.")) {
|
|
150
136
|
const baseDomain = pattern.slice(3);
|
|
151
137
|
if (hostname.endsWith("." + baseDomain)) {
|
|
152
138
|
const patternParts = baseDomain.split(".");
|
|
153
|
-
// Must have more parts than the base domain (i.e., has subdomains)
|
|
154
139
|
return parts.length > patternParts.length;
|
|
155
140
|
}
|
|
156
141
|
return false;
|
|
157
142
|
}
|
|
158
143
|
|
|
159
|
-
// `subdomain.*` - specific subdomain of any apex domain
|
|
160
|
-
// e.g., admin.* matches admin.example.com, admin.google.com
|
|
161
144
|
if (pattern.endsWith(".*")) {
|
|
162
145
|
const subdomain = pattern.slice(0, -2);
|
|
163
|
-
// Must be single-level subdomain (3 parts total)
|
|
164
146
|
if (parts.length === 3 && parts[0] === subdomain) {
|
|
165
147
|
return true;
|
|
166
148
|
}
|
|
167
149
|
return false;
|
|
168
150
|
}
|
|
169
151
|
|
|
170
|
-
// `subdomain.**` - specific subdomain of any domain (including multi-level)
|
|
171
|
-
// e.g., admin.** matches admin.example.com, admin.sub.example.com
|
|
172
152
|
if (pattern.endsWith(".**")) {
|
|
173
153
|
const subdomain = pattern.slice(0, -3);
|
|
174
154
|
if (parts.length >= 3 && parts[0] === subdomain) {
|
|
@@ -177,11 +157,8 @@ function matchDomainPattern(
|
|
|
177
157
|
return false;
|
|
178
158
|
}
|
|
179
159
|
|
|
180
|
-
// `subdomain.` - specific subdomain of any apex domain (no wildcard)
|
|
181
|
-
// e.g., admin. matches admin.example.com, admin.google.com
|
|
182
160
|
if (pattern.endsWith(".") && !pattern.includes("*")) {
|
|
183
161
|
const subdomain = pattern.slice(0, -1);
|
|
184
|
-
// Must be exactly 3 parts (subdomain.domain.tld)
|
|
185
162
|
if (parts.length === 3 && parts[0] === subdomain) {
|
|
186
163
|
return true;
|
|
187
164
|
}
|
|
@@ -191,9 +168,6 @@ function matchDomainPattern(
|
|
|
191
168
|
return false;
|
|
192
169
|
}
|
|
193
170
|
|
|
194
|
-
/**
|
|
195
|
-
* Validate pattern format
|
|
196
|
-
*/
|
|
197
171
|
export function validatePattern(pattern: string): void {
|
|
198
172
|
if (!pattern || typeof pattern !== "string") {
|
|
199
173
|
throw new InvalidPatternError(
|
|
@@ -203,12 +177,9 @@ export function validatePattern(pattern: string): void {
|
|
|
203
177
|
);
|
|
204
178
|
}
|
|
205
179
|
|
|
206
|
-
// Check for invalid characters (spaces, etc.)
|
|
207
180
|
if (/\s/.test(pattern)) {
|
|
208
181
|
throw new InvalidPatternError(pattern, "contains whitespace", {
|
|
209
182
|
cause: { pattern },
|
|
210
183
|
});
|
|
211
184
|
}
|
|
212
|
-
|
|
213
|
-
// Additional validation can be added here
|
|
214
185
|
}
|
package/src/host/router.ts
CHANGED
|
@@ -32,27 +32,16 @@ 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
|
-
/** Whether a value is thenable (a Promise or Promise-like). */
|
|
56
45
|
function isThenable(value: unknown): value is PromiseLike<unknown> {
|
|
57
46
|
return (
|
|
58
47
|
value !== null &&
|
|
@@ -61,12 +50,6 @@ function isThenable(value: unknown): value is PromiseLike<unknown> {
|
|
|
61
50
|
);
|
|
62
51
|
}
|
|
63
52
|
|
|
64
|
-
/**
|
|
65
|
-
* Whether a resolved value looks like a module namespace from a lazy import -
|
|
66
|
-
* an object with a `default` export that is a function (a Handler) or a host
|
|
67
|
-
* router (an object with `match`). Used to detect a `.map(() => import(...))`
|
|
68
|
-
* misuse: an inline handler should return a Response, not a module.
|
|
69
|
-
*/
|
|
70
53
|
function looksLikeLazyModule(value: unknown): boolean {
|
|
71
54
|
if (value === null || typeof value !== "object" || !("default" in value)) {
|
|
72
55
|
return false;
|
|
@@ -80,9 +63,6 @@ function looksLikeLazyModule(value: unknown): boolean {
|
|
|
80
63
|
);
|
|
81
64
|
}
|
|
82
65
|
|
|
83
|
-
/**
|
|
84
|
-
* Create a host router
|
|
85
|
-
*/
|
|
86
66
|
export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
87
67
|
const routes: RouteEntry[] = [];
|
|
88
68
|
const globalMiddleware: Middleware[] = [];
|
|
@@ -96,9 +76,6 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
|
96
76
|
}
|
|
97
77
|
}
|
|
98
78
|
|
|
99
|
-
/**
|
|
100
|
-
* Create a route builder for chaining
|
|
101
|
-
*/
|
|
102
79
|
function createRouteBuilder(
|
|
103
80
|
patterns: string[],
|
|
104
81
|
isFallback = false,
|
|
@@ -147,9 +124,6 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
|
147
124
|
};
|
|
148
125
|
}
|
|
149
126
|
|
|
150
|
-
/**
|
|
151
|
-
* Find matching route for hostname and path
|
|
152
|
-
*/
|
|
153
127
|
function findMatchingRoute(
|
|
154
128
|
hostname: string,
|
|
155
129
|
pathname: string,
|
|
@@ -168,9 +142,6 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
|
168
142
|
return null;
|
|
169
143
|
}
|
|
170
144
|
|
|
171
|
-
/**
|
|
172
|
-
* Execute middleware chain
|
|
173
|
-
*/
|
|
174
145
|
async function executeMiddleware(
|
|
175
146
|
middleware: Middleware[],
|
|
176
147
|
request: Request,
|
|
@@ -189,8 +160,6 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
|
189
160
|
return finalHandler();
|
|
190
161
|
}
|
|
191
162
|
|
|
192
|
-
// Guard against double next() calls — a second call would
|
|
193
|
-
// re-enter the downstream chain and run handlers/side-effects twice.
|
|
194
163
|
let nextCalled = false;
|
|
195
164
|
const guardedNext = (): Promise<Response> => {
|
|
196
165
|
if (nextCalled) {
|
|
@@ -208,15 +177,6 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
|
208
177
|
return next();
|
|
209
178
|
}
|
|
210
179
|
|
|
211
|
-
/**
|
|
212
|
-
* Execute a route entry, branching on its declared kind:
|
|
213
|
-
* - "lazy": await the loader, then delegate to the default export
|
|
214
|
-
* (a nested HostRouter via `.match`, or a request Handler directly).
|
|
215
|
-
* - "handler": call the inline handler with the request. A `.map()` handler
|
|
216
|
-
* that resolves to a module namespace (`{ default }`) is almost certainly
|
|
217
|
-
* a misused lazy import, so it is rejected with a clear message rather
|
|
218
|
-
* than silently returning a module object as the response.
|
|
219
|
-
*/
|
|
220
180
|
async function executeHandler(
|
|
221
181
|
entry: RouteEntry,
|
|
222
182
|
request: Request,
|
|
@@ -236,8 +196,6 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
|
236
196
|
|
|
237
197
|
const result = (handler as Handler)(request, input);
|
|
238
198
|
|
|
239
|
-
// Inline handlers may be async; await to obtain the Response and to run the
|
|
240
|
-
// misuse guard below.
|
|
241
199
|
if (isThenable(result)) {
|
|
242
200
|
const awaited = await result;
|
|
243
201
|
if (looksLikeLazyModule(awaited)) {
|
|
@@ -251,10 +209,6 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
|
251
209
|
return result;
|
|
252
210
|
}
|
|
253
211
|
|
|
254
|
-
/**
|
|
255
|
-
* Resolve a `.lazy()` mount: invoke the zero-arg loader, then dispatch to the
|
|
256
|
-
* module's default export.
|
|
257
|
-
*/
|
|
258
212
|
async function executeLazyMount(
|
|
259
213
|
loader: LazyHandler,
|
|
260
214
|
request: Request,
|
|
@@ -266,7 +220,6 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
|
266
220
|
const defaultExport = (module as { default: Handler | HostRouter })
|
|
267
221
|
.default;
|
|
268
222
|
|
|
269
|
-
// Default export is a nested host router
|
|
270
223
|
if (
|
|
271
224
|
typeof defaultExport === "object" &&
|
|
272
225
|
defaultExport !== null &&
|
|
@@ -275,7 +228,6 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
|
275
228
|
return (defaultExport as HostRouter).match(request, input);
|
|
276
229
|
}
|
|
277
230
|
|
|
278
|
-
// Otherwise treat the default export as a request handler
|
|
279
231
|
return (defaultExport as Handler)(request, input);
|
|
280
232
|
}
|
|
281
233
|
|
|
@@ -288,14 +240,10 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
|
288
240
|
});
|
|
289
241
|
}
|
|
290
242
|
|
|
291
|
-
/**
|
|
292
|
-
* Router instance
|
|
293
|
-
*/
|
|
294
243
|
const router: HostRouter = {
|
|
295
244
|
host(patterns: HostPattern): HostRouteBuilder {
|
|
296
245
|
const patternsArray = Array.isArray(patterns) ? patterns : [patterns];
|
|
297
246
|
|
|
298
|
-
// Validate and normalize patterns
|
|
299
247
|
const normalized = patternsArray.map((p) => {
|
|
300
248
|
validatePattern(p);
|
|
301
249
|
return normalizePattern(p);
|
|
@@ -314,9 +262,8 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
|
314
262
|
return createRouteBuilder([], true);
|
|
315
263
|
},
|
|
316
264
|
|
|
317
|
-
test(hostname: string): HostMatchResult | null {
|
|
265
|
+
test(hostname: string, pathname = "/"): HostMatchResult | null {
|
|
318
266
|
const parts = hostname.split(".");
|
|
319
|
-
const pathname = "/";
|
|
320
267
|
|
|
321
268
|
for (const route of routes) {
|
|
322
269
|
for (const pattern of route.patterns) {
|
|
@@ -342,14 +289,11 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
|
342
289
|
let effectiveHostname: string;
|
|
343
290
|
|
|
344
291
|
try {
|
|
345
|
-
// Handle cookie override (may throw HostRouterError)
|
|
346
292
|
effectiveHostname = handleCookieOverride(request, hostOverride, input);
|
|
347
293
|
} catch (error) {
|
|
348
|
-
// If it's a HostRouterError from cookie override
|
|
349
294
|
if (error instanceof HostRouterError) {
|
|
350
295
|
log(`Cookie override error: ${error.message}`);
|
|
351
296
|
|
|
352
|
-
// If fallback exists, use it
|
|
353
297
|
if (fallbackRoute) {
|
|
354
298
|
const fallbackInput = { ...input, error };
|
|
355
299
|
const allMiddleware = [
|
|
@@ -365,7 +309,6 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
|
365
309
|
);
|
|
366
310
|
}
|
|
367
311
|
|
|
368
|
-
// Otherwise return error response with cookie deletion
|
|
369
312
|
if (hostOverride) {
|
|
370
313
|
return createCookieErrorResponse(
|
|
371
314
|
hostOverride.cookieName,
|
|
@@ -374,7 +317,6 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
|
374
317
|
}
|
|
375
318
|
}
|
|
376
319
|
|
|
377
|
-
// Re-throw non-HostRouterErrors
|
|
378
320
|
throw error;
|
|
379
321
|
}
|
|
380
322
|
|
|
@@ -384,7 +326,6 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
|
384
326
|
log(`Cookie override: ${effectiveHostname}`);
|
|
385
327
|
}
|
|
386
328
|
|
|
387
|
-
// Find matching route
|
|
388
329
|
const matchedRoute = findMatchingRoute(effectiveHostname, pathname);
|
|
389
330
|
|
|
390
331
|
if (!matchedRoute) {
|
|
@@ -397,19 +338,14 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
|
397
338
|
});
|
|
398
339
|
}
|
|
399
340
|
|
|
400
|
-
// Combine global and route-specific middleware
|
|
401
341
|
const allMiddleware = [...globalMiddleware, ...matchedRoute.middleware];
|
|
402
342
|
|
|
403
|
-
// Execute middleware chain and handler
|
|
404
343
|
return executeMiddleware(allMiddleware, request, input, () =>
|
|
405
344
|
executeHandler(matchedRoute, request, input),
|
|
406
345
|
);
|
|
407
346
|
},
|
|
408
347
|
};
|
|
409
348
|
|
|
410
|
-
// Register in the global HostRouterRegistry for build-time discovery.
|
|
411
|
-
// The routes array and fallbackRoute ref are live - they reflect routes
|
|
412
|
-
// added via .host().map()/.lazy() after this point.
|
|
413
349
|
const registryId = `host-router-${hostRouterAutoId++}`;
|
|
414
350
|
HostRouterRegistry.set(registryId, {
|
|
415
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
|
}
|
package/src/host/types.ts
CHANGED
|
@@ -110,9 +110,13 @@ export interface HostRouter {
|
|
|
110
110
|
fallback(): HostRouteBuilder;
|
|
111
111
|
|
|
112
112
|
/**
|
|
113
|
-
* Test which handler would match a hostname
|
|
113
|
+
* Test which handler would match a hostname (and optional pathname).
|
|
114
|
+
*
|
|
115
|
+
* `pathname` defaults to `"/"`. Pass it to probe path-prefixed patterns
|
|
116
|
+
* such as `host(["example.com/admin"])`, which only match when the request
|
|
117
|
+
* path is under the prefix.
|
|
114
118
|
*/
|
|
115
|
-
test(hostname: string): HostMatchResult | null;
|
|
119
|
+
test(hostname: string, pathname?: string): HostMatchResult | null;
|
|
116
120
|
}
|
|
117
121
|
|
|
118
122
|
/**
|
package/src/href-client.ts
CHANGED
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
|
|
17
17
|
import type { GetRegisteredRoutes } from "./types.js";
|
|
18
18
|
import type { JsonSerialize } from "./serialize.js";
|
|
19
|
-
import type { ResponseEnvelope } from "./urls.js";
|
|
20
19
|
|
|
21
20
|
/**
|
|
22
21
|
* Parse constraint values into a union type for paths
|
|
@@ -163,16 +162,16 @@ type ResponsePayloadForKey<
|
|
|
163
162
|
}[keyof TRoutes];
|
|
164
163
|
|
|
165
164
|
/**
|
|
166
|
-
* Public response type for a route, keyed by pattern or concrete path.
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
* `
|
|
165
|
+
* Public response type for a route, keyed by pattern or concrete path. JSON
|
|
166
|
+
* response routes send the handler's return value verbatim (bare), so the
|
|
167
|
+
* payload is wrapped only in `JsonSerialize` to describe the JSON **wire** value
|
|
168
|
+
* a consumer receives from `fetch().then(r => r.json())` — e.g. a handler
|
|
169
|
+
* returning `{ createdAt: Date }` resolves here to `{ createdAt: string }`.
|
|
171
170
|
*/
|
|
172
171
|
export type PathResponse<
|
|
173
172
|
TPath extends string,
|
|
174
173
|
TRoutes = GetRegisteredRoutes,
|
|
175
|
-
> =
|
|
174
|
+
> = JsonSerialize<ResponsePayloadFor<TPath, TRoutes>>;
|
|
176
175
|
|
|
177
176
|
/**
|
|
178
177
|
* Strip trailing slash from a path (e.g., "/blog/" -> "/blog" | "/blog/")
|
|
@@ -256,7 +255,7 @@ declare global {
|
|
|
256
255
|
*
|
|
257
256
|
* The payload is the JSON **wire** shape (via `Rango.JsonSerialize`), not the
|
|
258
257
|
* handler's raw return — a handler returning `{ createdAt: Date }` resolves
|
|
259
|
-
* here to `
|
|
258
|
+
* here to `{ createdAt: string }` (bare, no envelope), matching what
|
|
260
259
|
* `fetch().then(r => r.json())` actually yields.
|
|
261
260
|
*
|
|
262
261
|
* Only resolves once `Rango.RegisteredRoutes` carries response metadata (the
|
|
@@ -299,13 +298,9 @@ declare global {
|
|
|
299
298
|
*/
|
|
300
299
|
export function href<T extends ValidPaths>(path: T, mount?: string): string {
|
|
301
300
|
if (mount && mount !== "/") {
|
|
302
|
-
// Strip trailing slash from mount to avoid double-slash when joining
|
|
303
301
|
const normalizedMount = mount.endsWith("/") ? mount.slice(0, -1) : mount;
|
|
304
302
|
return normalizedMount + path;
|
|
305
303
|
}
|
|
306
|
-
// ValidPaths is built from template literals so T does extend string at
|
|
307
|
-
// runtime, but the inference can fail past a certain route-union complexity
|
|
308
|
-
// and TypeScript reports T as not assignable to string.
|
|
309
304
|
return path as string;
|
|
310
305
|
}
|
|
311
306
|
|