@rangojs/router 0.0.0-experimental.32 → 0.0.0-experimental.3232cd17
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 +4 -0
- package/README.md +198 -44
- package/dist/bin/rango.js +287 -105
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +3248 -1117
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +73 -21
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +107 -1
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +245 -21
- package/skills/caching/SKILL.md +302 -6
- 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 +364 -0
- package/skills/hooks/SKILL.md +270 -30
- package/skills/host-router/SKILL.md +82 -22
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +49 -5
- package/skills/layout/SKILL.md +35 -9
- package/skills/links/SKILL.md +249 -17
- package/skills/loader/SKILL.md +294 -30
- package/skills/middleware/SKILL.md +52 -13
- package/skills/migrate-nextjs/SKILL.md +584 -0
- package/skills/migrate-react-router/SKILL.md +769 -0
- package/skills/mime-routes/SKILL.md +27 -0
- package/skills/observability/SKILL.md +137 -0
- package/skills/parallel/SKILL.md +203 -7
- package/skills/prerender/SKILL.md +123 -100
- package/skills/rango/SKILL.md +250 -22
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +122 -47
- package/skills/route/SKILL.md +97 -5
- package/skills/router-setup/SKILL.md +90 -5
- 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 +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 +329 -27
- package/skills/use-cache/SKILL.md +36 -5
- 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/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/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +86 -147
- 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 +148 -19
- package/src/browser/navigation-client.ts +187 -67
- 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/partial-update.ts +123 -94
- package/src/browser/prefetch/cache.ts +214 -36
- package/src/browser/prefetch/fetch.ts +260 -38
- 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 +93 -11
- package/src/browser/react/NavigationProvider.tsx +115 -34
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/context.ts +7 -2
- package/src/browser/react/filter-segment-order.ts +49 -7
- 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 +23 -69
- package/src/browser/react/use-link-status.ts +0 -4
- package/src/browser/react/use-navigation.ts +22 -5
- 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 +52 -1
- package/src/browser/rsc-router.tsx +215 -76
- package/src/browser/scroll-restoration.ts +46 -39
- package/src/browser/segment-reconciler.ts +36 -9
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +176 -50
- package/src/browser/types.ts +95 -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 +5 -0
- package/src/build/index.ts +8 -2
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +137 -32
- package/src/build/route-types/codegen.ts +4 -4
- package/src/build/route-types/include-resolution.ts +9 -2
- 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 +278 -96
- package/src/build/route-types/scan-filter.ts +9 -2
- 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 +149 -43
- package/src/cache/cache-scope.ts +148 -81
- package/src/cache/cache-tag.ts +98 -0
- package/src/cache/cf/cf-cache-store.ts +2550 -93
- package/src/cache/cf/index.ts +11 -17
- package/src/cache/document-cache.ts +78 -27
- package/src/cache/handle-snapshot.ts +63 -0
- package/src/cache/index.ts +23 -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/taint.ts +55 -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 +108 -290
- package/src/component-utils.ts +19 -0
- package/src/context-var.ts +84 -2
- package/src/debug.ts +2 -2
- 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 +70 -22
- 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 +52 -26
- package/src/index.ts +100 -38
- 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-context.ts +1 -1
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +10 -11
- package/src/prerender/store.ts +37 -41
- package/src/prerender.ts +198 -82
- package/src/redirect-origin.ts +100 -0
- package/src/response-utils.ts +37 -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 +437 -274
- 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 -0
- package/src/route-definition/redirect.ts +52 -10
- 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 +108 -9
- package/src/router/error-handling.ts +13 -17
- package/src/router/find-match.ts +45 -22
- package/src/router/handler-context.ts +83 -41
- package/src/router/intercept-resolution.ts +25 -23
- package/src/router/lazy-includes.ts +19 -53
- package/src/router/loader-resolution.ts +213 -30
- package/src/router/logging.ts +5 -8
- package/src/router/manifest.ts +49 -45
- package/src/router/match-api.ts +120 -204
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +58 -58
- package/src/router/match-middleware/background-revalidation.ts +27 -6
- package/src/router/match-middleware/cache-lookup.ts +205 -249
- package/src/router/match-middleware/cache-store.ts +45 -32
- 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 -40
- package/src/router/metrics.ts +5 -34
- package/src/router/middleware-types.ts +13 -142
- package/src/router/middleware.ts +173 -143
- package/src/router/navigation-snapshot.ts +131 -0
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +109 -63
- package/src/router/prerender-match.ts +190 -54
- package/src/router/preview-match.ts +32 -102
- package/src/router/request-classification.ts +276 -0
- package/src/router/revalidation.ts +63 -55
- package/src/router/route-snapshot.ts +244 -0
- package/src/router/router-context.ts +6 -28
- package/src/router/router-interfaces.ts +100 -35
- package/src/router/router-options.ts +91 -11
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +242 -75
- package/src/router/segment-resolution/helpers.ts +63 -24
- package/src/router/segment-resolution/loader-cache.ts +41 -37
- package/src/router/segment-resolution/revalidation.ts +456 -372
- 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 +2 -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 +91 -46
- package/src/router/types.ts +10 -63
- package/src/router/url-params.ts +44 -0
- package/src/router.ts +134 -43
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +492 -383
- package/src/rsc/helpers.ts +162 -46
- package/src/rsc/index.ts +1 -1
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +23 -3
- package/src/rsc/manifest-init.ts +33 -42
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +30 -3
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +90 -63
- package/src/rsc/rsc-rendering.ts +56 -54
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +74 -67
- package/src/rsc/ssr-setup.ts +18 -2
- package/src/rsc/types.ts +25 -6
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +4 -20
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +134 -0
- package/src/segment-system.tsx +272 -129
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +309 -61
- package/src/server/cookie-store.ts +80 -5
- package/src/server/handle-store.ts +26 -24
- package/src/server/loader-registry.ts +10 -28
- package/src/server/request-context.ts +338 -126
- package/src/ssr/index.tsx +23 -15
- 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 +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 +17 -8
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +54 -41
- package/src/types/handler-context.ts +233 -81
- package/src/types/index.ts +1 -10
- package/src/types/loader-types.ts +44 -15
- package/src/types/request-scope.ts +107 -0
- package/src/types/route-config.ts +6 -50
- package/src/types/route-entry.ts +19 -7
- package/src/types/segments.ts +37 -14
- 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 +36 -38
- package/src/vite/discovery/discover-routers.ts +130 -85
- 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 +192 -99
- 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 +51 -6
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +8 -0
- package/src/vite/plugin-types.ts +187 -69
- package/src/vite/plugins/cjs-to-esm.ts +8 -18
- package/src/vite/plugins/client-ref-dedup.ts +16 -11
- 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 +11 -50
- package/src/vite/plugins/expose-ids/export-analysis.ts +76 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +10 -48
- package/src/vite/plugins/expose-ids/loader-transform.ts +3 -20
- package/src/vite/plugins/expose-ids/router-transform.ts +20 -16
- package/src/vite/plugins/expose-internal-ids.ts +554 -317
- package/src/vite/plugins/performance-tracks.ts +89 -0
- package/src/vite/plugins/refresh-cmd.ts +89 -27
- package/src/vite/plugins/use-cache-transform.ts +73 -83
- package/src/vite/plugins/vercel-output.ts +258 -0
- package/src/vite/plugins/version-injector.ts +21 -25
- package/src/vite/plugins/version-plugin.ts +41 -20
- package/src/vite/plugins/virtual-entries.ts +2 -17
- package/src/vite/rango.ts +257 -289
- package/src/vite/router-discovery.ts +930 -140
- package/src/vite/utils/ast-handler-extract.ts +15 -31
- 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/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 +27 -29
- package/src/vite/utils/shared-utils.ts +92 -42
- 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
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
|
}
|
package/src/host/types.ts
CHANGED
|
@@ -35,12 +35,24 @@ export type Middleware = (
|
|
|
35
35
|
*/
|
|
36
36
|
export type HostPattern = string | string[];
|
|
37
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Whether a route entry is an inline request handler or a lazy module mount.
|
|
40
|
+
*
|
|
41
|
+
* Stored on the entry so discovery and runtime act on the consumer's declared
|
|
42
|
+
* intent instead of inferring it from the function's shape (arity/return value),
|
|
43
|
+
* which is ambiguous: a lazy loader may declare an ignored param, and an inline
|
|
44
|
+
* handler may be async. `.map()` registers `"handler"`, `.lazy()` registers
|
|
45
|
+
* `"lazy"`.
|
|
46
|
+
*/
|
|
47
|
+
export type RouteEntryKind = "handler" | "lazy";
|
|
48
|
+
|
|
38
49
|
/**
|
|
39
50
|
* Result from testing a hostname against patterns
|
|
40
51
|
*/
|
|
41
52
|
export interface HostMatchResult {
|
|
42
53
|
pattern: string;
|
|
43
54
|
handler: Handler | LazyHandler;
|
|
55
|
+
kind: RouteEntryKind;
|
|
44
56
|
}
|
|
45
57
|
|
|
46
58
|
/**
|
|
@@ -53,9 +65,24 @@ export interface HostRouteBuilder {
|
|
|
53
65
|
use(...middleware: Middleware[]): HostRouteBuilder;
|
|
54
66
|
|
|
55
67
|
/**
|
|
56
|
-
* Map to
|
|
68
|
+
* Map to an inline request handler `(request, input) => Response`.
|
|
69
|
+
*
|
|
70
|
+
* For a lazily-imported sub-app or handler module, use {@link lazy} instead -
|
|
71
|
+
* `.map(() => import(...))` is rejected (the return type is not a `Response`)
|
|
72
|
+
* and would not be discovered at build time.
|
|
73
|
+
*/
|
|
74
|
+
map(handler: Handler): HostRouter;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Mount a lazily-imported handler or host router:
|
|
78
|
+
* `.lazy(() => import("./sub-app"))`.
|
|
79
|
+
*
|
|
80
|
+
* The loader takes no arguments and resolves to a module whose `default`
|
|
81
|
+
* export is a request `Handler` or a nested `HostRouter`. Only `.lazy()`
|
|
82
|
+
* entries are invoked during build-time discovery to trigger the sub-app's
|
|
83
|
+
* `createRouter()` registration.
|
|
57
84
|
*/
|
|
58
|
-
|
|
85
|
+
lazy(handler: LazyHandler): HostRouter;
|
|
59
86
|
}
|
|
60
87
|
|
|
61
88
|
/**
|
|
@@ -83,9 +110,13 @@ export interface HostRouter {
|
|
|
83
110
|
fallback(): HostRouteBuilder;
|
|
84
111
|
|
|
85
112
|
/**
|
|
86
|
-
* 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.
|
|
87
118
|
*/
|
|
88
|
-
test(hostname: string): HostMatchResult | null;
|
|
119
|
+
test(hostname: string, pathname?: string): HostMatchResult | null;
|
|
89
120
|
}
|
|
90
121
|
|
|
91
122
|
/**
|
|
@@ -134,6 +165,8 @@ export interface RouteEntry {
|
|
|
134
165
|
patterns: string[];
|
|
135
166
|
middleware: Middleware[];
|
|
136
167
|
handler: Handler | LazyHandler;
|
|
168
|
+
/** Whether `handler` is an inline request handler or a lazy module mount. */
|
|
169
|
+
kind: RouteEntryKind;
|
|
137
170
|
isFallback?: boolean;
|
|
138
171
|
}
|
|
139
172
|
|
package/src/host/utils.ts
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* app: ['*', 'www.*']
|
|
16
16
|
* });
|
|
17
17
|
*
|
|
18
|
-
* router.host(hosts.admin).
|
|
18
|
+
* router.host(hosts.admin).lazy(() => import("./apps/admin")); // Type-safe!
|
|
19
19
|
* ```
|
|
20
20
|
*/
|
|
21
21
|
export function defineHosts<T extends Record<string, string | string[]>>(
|