@rangojs/router 0.0.0-experimental.19 → 0.0.0-experimental.1c0bdfad
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 +17 -0
- package/README.md +291 -61
- package/dist/bin/rango.js +544 -143
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +3744 -1329
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +67 -13
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +312 -0
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +247 -23
- package/skills/caching/SKILL.md +322 -19
- package/skills/composability/SKILL.md +27 -2
- package/skills/css/SKILL.md +76 -0
- package/skills/debug-manifest/SKILL.md +4 -2
- package/skills/document-cache/SKILL.md +78 -55
- package/skills/handler-use/SKILL.md +364 -0
- package/skills/hooks/SKILL.md +282 -60
- package/skills/host-router/SKILL.md +278 -0
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +50 -6
- package/skills/layout/SKILL.md +35 -9
- package/skills/links/SKILL.md +249 -17
- package/skills/loader/SKILL.md +297 -31
- package/skills/middleware/SKILL.md +52 -13
- package/skills/migrate-nextjs/SKILL.md +584 -0
- package/skills/migrate-react-router/SKILL.md +771 -0
- package/skills/mime-routes/SKILL.md +28 -1
- package/skills/observability/SKILL.md +172 -0
- package/skills/parallel/SKILL.md +203 -7
- package/skills/prerender/SKILL.md +155 -111
- package/skills/rango/SKILL.md +251 -23
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +123 -48
- package/skills/route/SKILL.md +104 -9
- package/skills/router-setup/SKILL.md +124 -11
- package/skills/scripts/SKILL.md +179 -0
- package/skills/server-actions/SKILL.md +775 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/tailwind/SKILL.md +27 -3
- package/skills/testing/SKILL.md +125 -222
- 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 +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 +123 -0
- package/skills/typesafety/SKILL.md +357 -52
- package/skills/use-cache/SKILL.md +46 -14
- 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 +67 -40
- package/src/bin/rango.ts +18 -0
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +39 -0
- package/src/browser/app-version.ts +14 -0
- package/src/browser/connection-warmup.ts +134 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +197 -150
- 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/link-interceptor.ts +4 -0
- package/src/browser/navigation-bridge.ts +200 -30
- package/src/browser/navigation-client.ts +217 -58
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +76 -67
- package/src/browser/navigation-transaction.ts +18 -66
- package/src/browser/network-error-handler.ts +34 -7
- package/src/browser/partial-update.ts +187 -112
- package/src/browser/prefetch/cache.ts +312 -30
- package/src/browser/prefetch/fetch.ts +344 -47
- package/src/browser/prefetch/policy.ts +6 -0
- package/src/browser/prefetch/queue.ts +126 -20
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +158 -76
- package/src/browser/react/Link.tsx +125 -18
- package/src/browser/react/NavigationProvider.tsx +135 -120
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/context.ts +7 -2
- package/src/browser/react/filter-segment-order.ts +66 -7
- package/src/browser/react/index.ts +0 -48
- package/src/browser/react/location-state-shared.ts +178 -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 +23 -69
- 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 +32 -7
- package/src/browser/react/use-params.ts +20 -10
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +46 -11
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +11 -21
- package/src/browser/response-adapter.ts +80 -5
- package/src/browser/rsc-router.tsx +226 -75
- package/src/browser/scroll-restoration.ts +54 -42
- package/src/browser/segment-reconciler.ts +36 -9
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +619 -442
- package/src/browser/types.ts +115 -11
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +65 -40
- package/src/build/generate-route-types.ts +7 -1
- package/src/build/index.ts +8 -2
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +182 -37
- package/src/build/route-types/ast-route-extraction.ts +15 -8
- package/src/build/route-types/codegen.ts +16 -5
- package/src/build/route-types/include-resolution.ts +125 -24
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/per-module-writer.ts +22 -6
- package/src/build/route-types/router-processing.ts +392 -106
- package/src/build/route-types/scan-filter.ts +9 -2
- package/src/build/route-types/source-scan.ts +216 -0
- package/src/build/runtime-discovery.ts +9 -20
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-key-utils.ts +29 -13
- package/src/cache/cache-policy.ts +108 -34
- package/src/cache/cache-runtime.ts +214 -48
- package/src/cache/cache-scope.ts +236 -89
- package/src/cache/cache-tag.ts +103 -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 +2224 -171
- 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 +11 -17
- package/src/cache/document-cache.ts +89 -27
- package/src/cache/handle-snapshot.ts +70 -0
- package/src/cache/index.ts +11 -20
- package/src/cache/memory-segment-store.ts +136 -37
- package/src/cache/profile-registry.ts +31 -31
- package/src/cache/read-through-swr.ts +41 -11
- package/src/cache/segment-codec.ts +9 -17
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/taint.ts +55 -0
- package/src/cache/types.ts +37 -100
- package/src/client.rsc.tsx +45 -21
- package/src/client.tsx +120 -336
- package/src/cloudflare/index.ts +11 -0
- package/src/cloudflare/tracing.ts +109 -0
- package/src/component-utils.ts +19 -0
- package/src/components/DefaultDocument.tsx +8 -2
- package/src/context-var.ts +84 -2
- package/src/debug.ts +2 -2
- package/src/decode-loader-results.ts +52 -0
- package/src/defer.ts +196 -0
- package/src/deps/ssr.ts +0 -1
- package/src/encode-kv.ts +49 -0
- package/src/errors.ts +30 -4
- package/src/escape-script.ts +52 -0
- package/src/handle.ts +70 -22
- package/src/handles/MetaTags.tsx +56 -19
- package/src/handles/Scripts.tsx +183 -0
- package/src/handles/breadcrumbs.ts +95 -0
- package/src/handles/is-thenable.ts +19 -0
- package/src/handles/meta.ts +51 -40
- package/src/handles/script.ts +244 -0
- package/src/host/cookie-handler.ts +9 -60
- package/src/host/errors.ts +0 -24
- package/src/host/index.ts +8 -5
- package/src/host/pattern-matcher.ts +23 -52
- 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 +79 -29
- package/src/index.ts +149 -65
- package/src/internal-debug.ts +11 -10
- 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/outlet-context.ts +1 -1
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +16 -16
- package/src/prerender/store.ts +63 -26
- package/src/prerender.ts +198 -82
- package/src/redirect-origin.ts +100 -0
- package/src/regex-escape.ts +8 -0
- package/src/render-error-thrower.tsx +20 -0
- package/src/response-utils.ts +62 -0
- package/src/reverse.ts +65 -15
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +7 -72
- package/src/route-definition/dsl-helpers.ts +469 -276
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +113 -37
- package/src/route-definition/index.ts +3 -3
- package/src/route-definition/redirect.ts +53 -12
- package/src/route-definition/resolve-handler-use.ts +161 -0
- package/src/route-definition/use-item-types.ts +32 -0
- package/src/route-map-builder.ts +7 -17
- package/src/route-types.ts +37 -41
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +164 -17
- package/src/router/error-handling.ts +45 -18
- package/src/router/find-match.ts +45 -22
- package/src/router/handler-context.ts +110 -39
- package/src/router/instrument.ts +350 -0
- package/src/router/intercept-resolution.ts +50 -24
- package/src/router/lazy-includes.ts +19 -53
- package/src/router/loader-resolution.ts +274 -56
- package/src/router/logging.ts +5 -8
- package/src/router/manifest.ts +49 -45
- package/src/router/match-api.ts +121 -205
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +58 -58
- package/src/router/match-middleware/background-revalidation.ts +33 -6
- package/src/router/match-middleware/cache-lookup.ts +214 -263
- package/src/router/match-middleware/cache-store.ts +73 -33
- package/src/router/match-middleware/intercept-resolution.ts +8 -28
- package/src/router/match-middleware/segment-resolution.ts +52 -18
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +104 -49
- package/src/router/metrics.ts +217 -26
- package/src/router/middleware-types.ts +24 -110
- package/src/router/middleware.ts +384 -197
- package/src/router/navigation-snapshot.ts +131 -0
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +148 -91
- package/src/router/prefetch-cache-ttl.ts +51 -0
- package/src/router/prerender-match.ts +199 -56
- package/src/router/preview-match.ts +32 -102
- package/src/router/request-classification.ts +276 -0
- package/src/router/revalidation.ts +144 -74
- package/src/router/route-snapshot.ts +244 -0
- package/src/router/router-context.ts +8 -28
- package/src/router/router-interfaces.ts +129 -36
- package/src/router/router-options.ts +185 -23
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +281 -76
- package/src/router/segment-resolution/helpers.ts +116 -31
- package/src/router/segment-resolution/loader-cache.ts +63 -37
- package/src/router/segment-resolution/revalidation.ts +493 -391
- 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 +5 -1
- package/src/router/segment-wrappers.ts +8 -5
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry-otel.ts +161 -199
- package/src/router/telemetry.ts +96 -19
- package/src/router/timeout.ts +0 -20
- package/src/router/tracing.ts +206 -0
- package/src/router/trie-matching.ts +180 -58
- package/src/router/types.ts +10 -63
- package/src/router/url-params.ts +44 -0
- package/src/router.ts +182 -54
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +702 -460
- package/src/rsc/helpers.ts +168 -46
- package/src/rsc/index.ts +2 -25
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +127 -31
- package/src/rsc/manifest-init.ts +33 -42
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +98 -19
- package/src/rsc/redirect-guard.ts +99 -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 +99 -189
- package/src/rsc/rsc-rendering.ts +126 -106
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +269 -114
- package/src/rsc/ssr-setup.ts +144 -0
- package/src/rsc/types.ts +34 -6
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +49 -41
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +149 -0
- package/src/segment-system.tsx +281 -129
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +317 -63
- package/src/server/cookie-parse.ts +32 -0
- package/src/server/cookie-store.ts +80 -5
- package/src/server/handle-store.ts +40 -38
- package/src/server/loader-registry.ts +26 -46
- package/src/server/request-context.ts +425 -177
- package/src/server.ts +6 -0
- package/src/ssr/index.tsx +25 -16
- package/src/static-handler.ts +27 -18
- package/src/testing/cache-status.ts +162 -0
- package/src/testing/collect-handle.ts +40 -0
- package/src/testing/dispatch.ts +701 -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 +257 -0
- package/src/testing/generated-routes.ts +183 -0
- package/src/testing/index.ts +99 -0
- package/src/testing/internal/context.ts +371 -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 +343 -0
- package/src/testing/render-route.tsx +581 -0
- package/src/testing/run-loader.ts +385 -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 +20 -58
- package/src/theme/ThemeScript.tsx +7 -9
- package/src/theme/constants.ts +52 -13
- package/src/theme/index.ts +3 -19
- 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 +17 -8
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +54 -41
- package/src/types/handler-context.ts +236 -88
- package/src/types/index.ts +1 -10
- package/src/types/loader-types.ts +44 -15
- package/src/types/request-scope.ts +112 -0
- package/src/types/route-config.ts +10 -45
- package/src/types/route-entry.ts +19 -7
- package/src/types/segments.ts +37 -19
- package/src/urls/include-helper.ts +33 -70
- package/src/urls/index.ts +1 -11
- package/src/urls/path-helper-types.ts +58 -11
- package/src/urls/path-helper.ts +57 -111
- package/src/urls/pattern-types.ts +48 -19
- package/src/urls/response-types.ts +25 -22
- package/src/urls/type-extraction.ts +58 -139
- package/src/urls/urls-function.ts +1 -18
- package/src/use-loader.tsx +346 -89
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +64 -91
- package/src/vite/discovery/discover-routers.ts +147 -88
- package/src/vite/discovery/discovery-errors.ts +194 -0
- package/src/vite/discovery/gate-state.ts +171 -0
- package/src/vite/discovery/prerender-collection.ts +247 -145
- package/src/vite/discovery/route-types-writer.ts +40 -84
- package/src/vite/discovery/self-gen-tracking.ts +27 -1
- package/src/vite/discovery/state.ts +61 -13
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +10 -3
- package/src/vite/inject-client-debug.ts +36 -0
- package/src/vite/plugin-types.ts +155 -65
- package/src/vite/plugins/cjs-to-esm.ts +16 -19
- package/src/vite/plugins/client-ref-dedup.ts +120 -0
- package/src/vite/plugins/client-ref-hashing.ts +28 -15
- package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
- package/src/vite/plugins/expose-action-id.ts +49 -98
- package/src/vite/plugins/expose-id-utils.ts +96 -51
- package/src/vite/plugins/expose-ids/export-analysis.ts +101 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +15 -64
- package/src/vite/plugins/expose-ids/loader-transform.ts +14 -24
- package/src/vite/plugins/expose-ids/router-transform.ts +118 -29
- package/src/vite/plugins/expose-internal-ids.ts +553 -317
- package/src/vite/plugins/performance-tracks.ts +89 -0
- package/src/vite/plugins/refresh-cmd.ts +127 -0
- package/src/vite/plugins/use-cache-transform.ts +73 -83
- package/src/vite/plugins/version-injector.ts +21 -25
- package/src/vite/plugins/version-plugin.ts +46 -37
- package/src/vite/plugins/virtual-entries.ts +13 -18
- package/src/vite/rango.ts +241 -287
- package/src/vite/router-discovery.ts +956 -149
- package/src/vite/utils/ast-handler-extract.ts +26 -35
- package/src/vite/utils/banner.ts +4 -4
- package/src/vite/utils/bundle-analysis.ts +10 -15
- package/src/vite/utils/client-chunks.ts +184 -0
- package/src/vite/utils/directive-prologue.ts +40 -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 +20 -52
- package/src/vite/utils/prerender-utils.ts +141 -34
- package/src/vite/utils/shared-utils.ts +92 -42
- package/CLAUDE.md +0 -5
- 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 -6
- package/src/network-error-thrower.tsx +0 -23
- package/src/route-definition/route-function.ts +0 -119
- package/src/router/middleware-cookies.ts +0 -55
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cookie Override Handler
|
|
3
|
-
*
|
|
4
|
-
* Manages cookie-based host override for development environments.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
1
|
import type { HostOverrideConfig } from "./types.js";
|
|
8
2
|
import type { RouterRequestInput } from "../router/router-interfaces.js";
|
|
9
3
|
import { matchPattern, parseRequest } from "./pattern-matcher.js";
|
|
@@ -12,52 +6,21 @@ import {
|
|
|
12
6
|
InvalidHostnameError,
|
|
13
7
|
HostValidationError,
|
|
14
8
|
} from "./errors.js";
|
|
9
|
+
import { parseCookiesFromHeader } from "../server/cookie-parse.js";
|
|
15
10
|
|
|
16
|
-
/**
|
|
17
|
-
* Parse cookies from request
|
|
18
|
-
*/
|
|
19
11
|
export function parseCookies(request: Request): Record<string, string> {
|
|
20
|
-
|
|
21
|
-
if (!cookieHeader) {
|
|
22
|
-
return {};
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const cookies: Record<string, string> = {};
|
|
26
|
-
const pairs = cookieHeader.split(";");
|
|
27
|
-
|
|
28
|
-
for (const pair of pairs) {
|
|
29
|
-
const [name, ...rest] = pair.trim().split("=");
|
|
30
|
-
if (name && rest.length > 0) {
|
|
31
|
-
const value = rest.join("=");
|
|
32
|
-
try {
|
|
33
|
-
cookies[name] = decodeURIComponent(value);
|
|
34
|
-
} catch {
|
|
35
|
-
cookies[name] = value;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return cookies;
|
|
12
|
+
return parseCookiesFromHeader(request.headers.get("cookie"));
|
|
41
13
|
}
|
|
42
14
|
|
|
43
|
-
/**
|
|
44
|
-
* Get cookie value from request
|
|
45
|
-
*/
|
|
46
15
|
export function getCookie(request: Request, name: string): string | undefined {
|
|
47
16
|
const cookies = parseCookies(request);
|
|
48
17
|
return cookies[name];
|
|
49
18
|
}
|
|
50
19
|
|
|
51
|
-
/**
|
|
52
|
-
* Create Set-Cookie header to delete a cookie
|
|
53
|
-
*/
|
|
54
20
|
export function createDeleteCookieHeader(name: string): string {
|
|
55
21
|
return `${name}=; Max-Age=0; Path=/; Secure; HttpOnly`;
|
|
56
22
|
}
|
|
57
23
|
|
|
58
|
-
/**
|
|
59
|
-
* Create error response with cookie deletion
|
|
60
|
-
*/
|
|
61
24
|
export function createCookieErrorResponse(
|
|
62
25
|
cookieName: string,
|
|
63
26
|
message: string,
|
|
@@ -77,9 +40,6 @@ export function createCookieErrorResponse(
|
|
|
77
40
|
);
|
|
78
41
|
}
|
|
79
42
|
|
|
80
|
-
/**
|
|
81
|
-
* Check if current host is allowed to use override
|
|
82
|
-
*/
|
|
83
43
|
export function isHostAllowed(
|
|
84
44
|
request: Request,
|
|
85
45
|
allowedHosts: string[],
|
|
@@ -95,12 +55,6 @@ export function isHostAllowed(
|
|
|
95
55
|
return false;
|
|
96
56
|
}
|
|
97
57
|
|
|
98
|
-
/**
|
|
99
|
-
* Handle cookie override logic
|
|
100
|
-
*
|
|
101
|
-
* Returns overridden hostname if valid, original hostname if no override.
|
|
102
|
-
* Throws errors for invalid overrides.
|
|
103
|
-
*/
|
|
104
58
|
export function handleCookieOverride(
|
|
105
59
|
request: Request,
|
|
106
60
|
config: HostOverrideConfig | undefined,
|
|
@@ -115,51 +69,46 @@ export function handleCookieOverride(
|
|
|
115
69
|
const cookieValue = getCookie(request, cookieName);
|
|
116
70
|
const { hostname: originalHostname } = parseRequest(request);
|
|
117
71
|
|
|
118
|
-
// No cookie - return original hostname
|
|
119
72
|
if (!cookieValue) {
|
|
120
73
|
return originalHostname;
|
|
121
74
|
}
|
|
122
75
|
|
|
123
|
-
// Check if current host is allowed
|
|
124
76
|
const allowed = isHostAllowed(request, allowedHosts);
|
|
125
77
|
|
|
126
|
-
// If not allowed, throw error
|
|
127
78
|
if (!allowed) {
|
|
128
79
|
throw new HostOverrideNotAllowedError(originalHostname, cookieName, {
|
|
129
80
|
cause: { cookieValue, currentHost: originalHostname },
|
|
130
81
|
});
|
|
131
82
|
}
|
|
132
83
|
|
|
133
|
-
// If allowed and has custom validation, run it
|
|
134
84
|
if (validate) {
|
|
135
85
|
try {
|
|
136
86
|
const validatedHostname = validate(request, cookieValue, input);
|
|
137
87
|
return validatedHostname;
|
|
138
88
|
} catch (error) {
|
|
139
|
-
// Wrap in HostValidationError
|
|
140
89
|
const message = error instanceof Error ? error.message : String(error);
|
|
141
90
|
throw new HostValidationError(message, error);
|
|
142
91
|
}
|
|
143
92
|
}
|
|
144
93
|
|
|
145
|
-
//
|
|
94
|
+
// URL.hostname ASCII-lowercases the host, so compare the cookie value against
|
|
95
|
+
// its canonical lowercase form (a mixed-case host is valid) and reject only
|
|
96
|
+
// when it carries a path/port. Return the canonical host so downstream
|
|
97
|
+
// matching, which assumes lowercase, sees a consistent value.
|
|
146
98
|
try {
|
|
147
|
-
// Try to construct a URL with the hostname to validate it
|
|
148
99
|
const testUrl = new URL(`https://${cookieValue}`);
|
|
149
100
|
|
|
150
|
-
|
|
151
|
-
if (testUrl.hostname !== cookieValue) {
|
|
101
|
+
if (testUrl.hostname !== cookieValue.toLowerCase()) {
|
|
152
102
|
throw new InvalidHostnameError(cookieValue, {
|
|
153
103
|
cause: { original: cookieValue, normalized: testUrl.hostname },
|
|
154
104
|
});
|
|
155
105
|
}
|
|
106
|
+
|
|
107
|
+
return testUrl.hostname;
|
|
156
108
|
} catch (error) {
|
|
157
|
-
// If URL constructor failed, throw InvalidHostnameError with cause
|
|
158
109
|
if (error instanceof InvalidHostnameError) {
|
|
159
110
|
throw error;
|
|
160
111
|
}
|
|
161
112
|
throw new InvalidHostnameError(cookieValue, { cause: error });
|
|
162
113
|
}
|
|
163
|
-
|
|
164
|
-
return cookieValue;
|
|
165
114
|
}
|
package/src/host/errors.ts
CHANGED
|
@@ -4,16 +4,10 @@
|
|
|
4
4
|
* All host router errors extend HostRouterError for easy instance checking.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
/**
|
|
8
|
-
* Error options with cause
|
|
9
|
-
*/
|
|
10
7
|
interface ErrorOptions {
|
|
11
8
|
cause?: unknown;
|
|
12
9
|
}
|
|
13
10
|
|
|
14
|
-
/**
|
|
15
|
-
* Base error class for all host router errors
|
|
16
|
-
*/
|
|
17
11
|
export class HostRouterError extends Error {
|
|
18
12
|
cause?: unknown;
|
|
19
13
|
|
|
@@ -27,9 +21,6 @@ export class HostRouterError extends Error {
|
|
|
27
21
|
}
|
|
28
22
|
}
|
|
29
23
|
|
|
30
|
-
/**
|
|
31
|
-
* Error thrown when pattern validation fails
|
|
32
|
-
*/
|
|
33
24
|
export class InvalidPatternError extends HostRouterError {
|
|
34
25
|
constructor(pattern: string, reason: string, options?: ErrorOptions) {
|
|
35
26
|
super(`Invalid pattern "${pattern}": ${reason}`, options);
|
|
@@ -38,9 +29,6 @@ export class InvalidPatternError extends HostRouterError {
|
|
|
38
29
|
}
|
|
39
30
|
}
|
|
40
31
|
|
|
41
|
-
/**
|
|
42
|
-
* Error thrown when cookie override is not allowed
|
|
43
|
-
*/
|
|
44
32
|
export class HostOverrideNotAllowedError extends HostRouterError {
|
|
45
33
|
constructor(currentHost: string, cookieName: string, options?: ErrorOptions) {
|
|
46
34
|
super(
|
|
@@ -52,9 +40,6 @@ export class HostOverrideNotAllowedError extends HostRouterError {
|
|
|
52
40
|
}
|
|
53
41
|
}
|
|
54
42
|
|
|
55
|
-
/**
|
|
56
|
-
* Error thrown when cookie hostname is invalid
|
|
57
|
-
*/
|
|
58
43
|
export class InvalidHostnameError extends HostRouterError {
|
|
59
44
|
constructor(hostname: string, options?: ErrorOptions) {
|
|
60
45
|
super(`Invalid hostname format: "${hostname}"`, options);
|
|
@@ -63,9 +48,6 @@ export class InvalidHostnameError extends HostRouterError {
|
|
|
63
48
|
}
|
|
64
49
|
}
|
|
65
50
|
|
|
66
|
-
/**
|
|
67
|
-
* Error thrown when custom validation fails
|
|
68
|
-
*/
|
|
69
51
|
export class HostValidationError extends HostRouterError {
|
|
70
52
|
constructor(message: string, cause?: unknown) {
|
|
71
53
|
super(message, { cause });
|
|
@@ -74,9 +56,6 @@ export class HostValidationError extends HostRouterError {
|
|
|
74
56
|
}
|
|
75
57
|
}
|
|
76
58
|
|
|
77
|
-
/**
|
|
78
|
-
* Error thrown when no route matches
|
|
79
|
-
*/
|
|
80
59
|
export class NoRouteMatchError extends HostRouterError {
|
|
81
60
|
constructor(hostname: string, pathname: string, options?: ErrorOptions) {
|
|
82
61
|
super(`No route matched for ${hostname}${pathname}`, options);
|
|
@@ -85,9 +64,6 @@ export class NoRouteMatchError extends HostRouterError {
|
|
|
85
64
|
}
|
|
86
65
|
}
|
|
87
66
|
|
|
88
|
-
/**
|
|
89
|
-
* Error thrown when handler type is invalid
|
|
90
|
-
*/
|
|
91
67
|
export class InvalidHandlerError extends HostRouterError {
|
|
92
68
|
constructor(handler: unknown, options?: ErrorOptions) {
|
|
93
69
|
super(`Invalid handler type: ${typeof handler}`, options);
|
package/src/host/index.ts
CHANGED
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
*
|
|
12
12
|
* const router = createHostRouter();
|
|
13
13
|
*
|
|
14
|
-
* router.host(['.']).
|
|
15
|
-
* router.host(['admin.*']).
|
|
14
|
+
* router.host(['.']).lazy(() => import('./apps/main'));
|
|
15
|
+
* router.host(['admin.*']).lazy(() => import('./apps/admin'));
|
|
16
16
|
*
|
|
17
17
|
* export default {
|
|
18
18
|
* fetch(request) {
|
|
@@ -20,14 +20,17 @@
|
|
|
20
20
|
* }
|
|
21
21
|
* };
|
|
22
22
|
* ```
|
|
23
|
+
*
|
|
24
|
+
* The host surface (`Handler`, `Middleware`, `match`, `HostOverrideConfig.validate`)
|
|
25
|
+
* types `input` as `RouterRequestInput<any>` by design: a host router fans out to
|
|
26
|
+
* heterogeneous sub-apps with differing env/vars shapes, so there is no single
|
|
27
|
+
* `TEnv`/`TVars` to thread through. `input.env`/`input.vars` are therefore `any`
|
|
28
|
+
* here; the typed env shape lives on each sub-app's `createRouter<TEnv>()`.
|
|
23
29
|
*/
|
|
24
30
|
|
|
25
31
|
// Core router
|
|
26
32
|
export { createHostRouter } from "./router.js";
|
|
27
33
|
|
|
28
|
-
// Host router registry for build-time discovery
|
|
29
|
-
export { HostRouterRegistry, type HostRouterRegistryEntry } from "./router.js";
|
|
30
|
-
|
|
31
34
|
// Utilities
|
|
32
35
|
export { defineHosts } from "./utils.js";
|
|
33
36
|
|
|
@@ -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
|
}
|