@rangojs/router 0.0.0-experimental.97 → 0.0.0-experimental.98914650
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -9
- package/dist/bin/rango.js +157 -63
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +1584 -639
- package/package.json +71 -21
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +60 -0
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +222 -30
- package/skills/caching/SKILL.md +263 -8
- package/skills/composability/SKILL.md +27 -2
- package/skills/css/SKILL.md +76 -0
- package/skills/document-cache/SKILL.md +78 -55
- package/skills/handler-use/SKILL.md +3 -1
- package/skills/hooks/SKILL.md +235 -28
- package/skills/host-router/SKILL.md +122 -22
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +29 -5
- package/skills/layout/SKILL.md +13 -9
- package/skills/links/SKILL.md +173 -17
- package/skills/loader/SKILL.md +170 -23
- package/skills/middleware/SKILL.md +16 -10
- package/skills/migrate-nextjs/SKILL.md +38 -16
- package/skills/mime-routes/SKILL.md +27 -0
- package/skills/observability/SKILL.md +137 -0
- package/skills/parallel/SKILL.md +11 -7
- package/skills/prerender/SKILL.md +14 -33
- package/skills/rango/SKILL.md +250 -25
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +114 -47
- package/skills/route/SKILL.md +42 -5
- package/skills/router-setup/SKILL.md +3 -3
- package/skills/server-actions/SKILL.md +78 -42
- package/skills/tailwind/SKILL.md +27 -3
- package/skills/testing/SKILL.md +129 -0
- package/skills/testing/bindings.md +89 -0
- package/skills/testing/cache-prerender.md +124 -0
- package/skills/testing/client-components.md +122 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +92 -0
- package/skills/testing/handles.md +129 -0
- package/skills/testing/loader.md +128 -0
- package/skills/testing/middleware.md +99 -0
- package/skills/testing/render-handler.md +121 -0
- package/skills/testing/response-routes.md +95 -0
- package/skills/testing/reverse-and-types.md +84 -0
- package/skills/testing/server-actions.md +107 -0
- package/skills/testing/server-tree.md +128 -0
- package/skills/testing/setup.md +120 -0
- package/skills/typesafety/SKILL.md +316 -26
- package/skills/use-cache/SKILL.md +36 -5
- package/skills/vercel/SKILL.md +107 -0
- package/skills/view-transitions/SKILL.md +294 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +116 -0
- package/src/__internal.ts +0 -65
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +14 -27
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +37 -143
- package/src/browser/history-state.ts +21 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/navigation-bridge.ts +30 -59
- package/src/browser/navigation-client.ts +96 -84
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +32 -82
- package/src/browser/navigation-transaction.ts +9 -59
- package/src/browser/partial-update.ts +60 -127
- package/src/browser/prefetch/cache.ts +82 -72
- package/src/browser/prefetch/fetch.ts +108 -33
- package/src/browser/prefetch/queue.ts +6 -3
- package/src/browser/rango-state.ts +157 -115
- package/src/browser/react/Link.tsx +0 -2
- package/src/browser/react/NavigationProvider.tsx +41 -48
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/filter-segment-order.ts +0 -2
- package/src/browser/react/index.ts +0 -48
- package/src/browser/react/location-state-shared.ts +166 -8
- package/src/browser/react/location-state.ts +39 -14
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +17 -14
- package/src/browser/react/use-link-status.ts +0 -4
- package/src/browser/react/use-navigation.ts +0 -3
- package/src/browser/react/use-params.ts +11 -11
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +20 -5
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +0 -13
- package/src/browser/response-adapter.ts +52 -1
- package/src/browser/rsc-router.tsx +70 -34
- package/src/browser/scroll-restoration.ts +22 -14
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +168 -44
- package/src/browser/types.ts +36 -21
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +60 -35
- package/src/build/generate-route-types.ts +3 -0
- package/src/build/index.ts +8 -2
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +89 -10
- package/src/build/route-types/codegen.ts +4 -4
- package/src/build/route-types/include-resolution.ts +1 -1
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/per-module-writer.ts +7 -4
- package/src/build/route-types/router-processing.ts +122 -22
- package/src/build/route-types/scan-filter.ts +1 -1
- package/src/build/route-types/source-scan.ts +118 -0
- package/src/build/runtime-discovery.ts +9 -20
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-policy.ts +68 -28
- package/src/cache/cache-runtime.ts +134 -32
- package/src/cache/cache-scope.ts +100 -74
- package/src/cache/cache-tag.ts +98 -0
- package/src/cache/cf/cf-cache-store.ts +2255 -238
- package/src/cache/cf/index.ts +6 -16
- package/src/cache/document-cache.ts +61 -20
- package/src/cache/handle-snapshot.ts +63 -0
- package/src/cache/index.ts +22 -20
- package/src/cache/memory-segment-store.ts +136 -37
- package/src/cache/profile-registry.ts +6 -30
- package/src/cache/read-through-swr.ts +41 -11
- package/src/cache/segment-codec.ts +0 -16
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/types.ts +33 -100
- package/src/cache/vercel/index.ts +11 -0
- package/src/cache/vercel/vercel-cache-store.ts +799 -0
- package/src/client.rsc.tsx +6 -21
- package/src/client.tsx +25 -61
- package/src/component-utils.ts +19 -0
- package/src/context-var.ts +17 -5
- package/src/decode-loader-results.ts +36 -0
- package/src/defer.ts +196 -0
- package/src/deps/ssr.ts +0 -1
- package/src/errors.ts +30 -4
- package/src/handle.ts +31 -23
- package/src/handles/MetaTags.tsx +0 -14
- package/src/handles/breadcrumbs.ts +16 -5
- package/src/handles/meta.ts +0 -39
- package/src/host/cookie-handler.ts +0 -36
- package/src/host/errors.ts +0 -24
- package/src/host/index.ts +8 -2
- package/src/host/pattern-matcher.ts +7 -50
- package/src/host/router.ts +107 -99
- package/src/host/testing.ts +40 -27
- package/src/host/types.ts +37 -4
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +137 -22
- package/src/index.rsc.ts +63 -9
- package/src/index.ts +64 -9
- package/src/internal-debug.ts +2 -4
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +20 -13
- package/src/loader.ts +12 -11
- package/src/missing-id-error.ts +68 -0
- package/src/network-error-thrower.tsx +1 -6
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +10 -11
- package/src/prerender/store.ts +32 -37
- package/src/prerender.ts +61 -6
- package/src/redirect-origin.ts +100 -0
- package/src/response-utils.ts +9 -0
- package/src/reverse.ts +65 -40
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +7 -72
- package/src/route-definition/dsl-helpers.ts +244 -281
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +40 -17
- package/src/route-definition/redirect.ts +43 -9
- package/src/route-definition/resolve-handler-use.ts +6 -0
- package/src/route-definition/use-item-types.ts +32 -0
- package/src/route-map-builder.ts +0 -16
- package/src/route-types.ts +19 -41
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +15 -15
- package/src/router/error-handling.ts +13 -17
- package/src/router/find-match.ts +44 -23
- package/src/router/handler-context.ts +4 -41
- package/src/router/intercept-resolution.ts +14 -19
- package/src/router/lazy-includes.ts +9 -46
- package/src/router/loader-resolution.ts +91 -46
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +18 -29
- package/src/router/match-api.ts +0 -20
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +57 -58
- package/src/router/match-middleware/background-revalidation.ts +0 -7
- package/src/router/match-middleware/cache-lookup.ts +150 -271
- package/src/router/match-middleware/cache-store.ts +3 -33
- package/src/router/match-middleware/intercept-resolution.ts +0 -22
- package/src/router/match-middleware/segment-resolution.ts +0 -22
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +31 -80
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-types.ts +5 -112
- package/src/router/middleware.ts +118 -133
- package/src/router/navigation-snapshot.ts +0 -51
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +62 -67
- package/src/router/prerender-match.ts +99 -63
- package/src/router/preview-match.ts +3 -1
- package/src/router/request-classification.ts +28 -62
- package/src/router/revalidation.ts +50 -56
- package/src/router/route-snapshot.ts +0 -1
- package/src/router/router-context.ts +0 -27
- package/src/router/router-interfaces.ts +68 -35
- package/src/router/router-options.ts +55 -1
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +44 -63
- package/src/router/segment-resolution/helpers.ts +34 -0
- package/src/router/segment-resolution/loader-cache.ts +40 -37
- package/src/router/segment-resolution/revalidation.ts +203 -285
- package/src/router/segment-resolution/static-store.ts +19 -5
- package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
- package/src/router/segment-resolution/view-transition-default.ts +36 -0
- package/src/router/segment-resolution.ts +4 -1
- package/src/router/segment-wrappers.ts +0 -3
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry-otel.ts +0 -20
- package/src/router/telemetry.ts +96 -19
- package/src/router/timeout.ts +0 -20
- package/src/router/trie-matching.ts +87 -48
- package/src/router/types.ts +9 -63
- package/src/router/url-params.ts +0 -5
- package/src/router.ts +80 -41
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +83 -78
- package/src/rsc/helpers.ts +93 -5
- package/src/rsc/index.ts +1 -1
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/manifest-init.ts +28 -41
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +12 -1
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +76 -62
- package/src/rsc/rsc-rendering.ts +41 -60
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +62 -67
- package/src/rsc/ssr-setup.ts +16 -0
- package/src/rsc/types.ts +10 -5
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +4 -20
- package/src/segment-loader-promise.ts +14 -2
- package/src/segment-system.tsx +199 -142
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +150 -51
- package/src/server/cookie-store.ts +80 -5
- package/src/server/handle-store.ts +7 -24
- package/src/server/loader-registry.ts +5 -24
- package/src/server/request-context.ts +165 -87
- package/src/ssr/index.tsx +14 -14
- package/src/static-handler.ts +10 -13
- package/src/testing/cache-status.ts +162 -0
- package/src/testing/collect-handle.ts +40 -0
- package/src/testing/dispatch.ts +618 -0
- package/src/testing/dom.entry.ts +22 -0
- package/src/testing/e2e/fixture.ts +188 -0
- package/src/testing/e2e/index.ts +128 -0
- package/src/testing/e2e/matchers.ts +35 -0
- package/src/testing/e2e/page-helpers.ts +272 -0
- package/src/testing/e2e/parity.ts +387 -0
- package/src/testing/e2e/server.ts +195 -0
- package/src/testing/flight-matchers.ts +97 -0
- package/src/testing/flight-normalize.ts +11 -0
- package/src/testing/flight-runtime.d.ts +57 -0
- package/src/testing/flight-tree.ts +682 -0
- package/src/testing/flight.entry.ts +52 -0
- package/src/testing/flight.ts +232 -0
- package/src/testing/generated-routes.ts +183 -0
- package/src/testing/index.ts +99 -0
- package/src/testing/internal/context.ts +348 -0
- package/src/testing/internal/flight-client-globals.ts +30 -0
- package/src/testing/internal/seed-vars.ts +54 -0
- package/src/testing/render-handler.ts +330 -0
- package/src/testing/render-route.tsx +566 -0
- package/src/testing/run-loader.ts +378 -0
- package/src/testing/run-middleware.ts +205 -0
- package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
- package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
- package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
- package/src/testing/vitest-stubs/version.ts +5 -0
- package/src/testing/vitest.ts +305 -0
- package/src/theme/ThemeProvider.tsx +0 -52
- package/src/theme/ThemeScript.tsx +0 -6
- package/src/theme/constants.ts +0 -12
- package/src/theme/index.ts +0 -7
- package/src/theme/theme-context.ts +1 -5
- package/src/theme/theme-script.ts +0 -14
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +0 -35
- package/src/types/cache-types.ts +13 -4
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +54 -41
- package/src/types/handler-context.ts +97 -22
- package/src/types/index.ts +1 -10
- package/src/types/loader-types.ts +6 -3
- package/src/types/request-scope.ts +0 -19
- package/src/types/route-config.ts +6 -50
- package/src/types/route-entry.ts +0 -6
- package/src/types/segments.ts +18 -14
- package/src/urls/include-helper.ts +9 -56
- package/src/urls/index.ts +1 -11
- package/src/urls/path-helper-types.ts +19 -5
- package/src/urls/path-helper.ts +17 -106
- package/src/urls/pattern-types.ts +36 -19
- package/src/urls/response-types.ts +20 -19
- package/src/urls/type-extraction.ts +58 -139
- package/src/urls/urls-function.ts +1 -18
- package/src/use-loader.tsx +292 -107
- package/src/vite/debug.ts +1 -0
- package/src/vite/discovery/bundle-postprocess.ts +8 -7
- package/src/vite/discovery/discover-routers.ts +95 -82
- package/src/vite/discovery/discovery-errors.ts +194 -0
- package/src/vite/discovery/prerender-collection.ts +26 -34
- package/src/vite/discovery/route-types-writer.ts +40 -84
- package/src/vite/discovery/state.ts +39 -1
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +4 -0
- package/src/vite/plugin-types.ts +185 -10
- package/src/vite/plugins/cjs-to-esm.ts +3 -18
- package/src/vite/plugins/client-ref-dedup.ts +0 -11
- package/src/vite/plugins/client-ref-hashing.ts +12 -11
- package/src/vite/plugins/cloudflare-protocol-stub.ts +1 -21
- package/src/vite/plugins/expose-action-id.ts +4 -75
- package/src/vite/plugins/expose-id-utils.ts +3 -54
- package/src/vite/plugins/expose-ids/export-analysis.ts +76 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +6 -74
- package/src/vite/plugins/expose-ids/loader-transform.ts +3 -20
- package/src/vite/plugins/expose-ids/router-transform.ts +0 -13
- package/src/vite/plugins/expose-internal-ids.ts +57 -67
- package/src/vite/plugins/performance-tracks.ts +9 -16
- package/src/vite/plugins/refresh-cmd.ts +1 -1
- package/src/vite/plugins/use-cache-transform.ts +26 -49
- package/src/vite/plugins/vercel-output.ts +258 -0
- package/src/vite/plugins/version-injector.ts +2 -32
- package/src/vite/plugins/version-plugin.ts +32 -23
- package/src/vite/plugins/virtual-entries.ts +35 -17
- package/src/vite/rango.ts +148 -115
- package/src/vite/router-discovery.ts +220 -68
- package/src/vite/utils/ast-handler-extract.ts +15 -31
- package/src/vite/utils/bundle-analysis.ts +10 -15
- package/src/vite/utils/client-chunks.ts +184 -0
- package/src/vite/utils/forward-user-plugins.ts +171 -0
- package/src/vite/utils/manifest-utils.ts +4 -59
- package/src/vite/utils/package-resolution.ts +1 -73
- package/src/vite/utils/prerender-utils.ts +0 -34
- package/src/vite/utils/shared-utils.ts +95 -43
- package/src/browser/action-response-classifier.ts +0 -99
- package/src/browser/react/use-client-cache.ts +0 -58
- package/src/browser/shallow.ts +0 -40
- package/src/handles/index.ts +0 -7
- package/src/router/middleware-cookies.ts +0 -55
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared route-param comparison helpers.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Shallow equality for two route-param records. Same-reference is a fast path;
|
|
7
|
+
* otherwise compares key count then each value.
|
|
8
|
+
*/
|
|
9
|
+
export function paramsEqual(
|
|
10
|
+
a: Record<string, string>,
|
|
11
|
+
b: Record<string, string>,
|
|
12
|
+
): boolean {
|
|
13
|
+
if (a === b) return true;
|
|
14
|
+
|
|
15
|
+
const keysA = Object.keys(a);
|
|
16
|
+
if (keysA.length !== Object.keys(b).length) return false;
|
|
17
|
+
|
|
18
|
+
for (const key of keysA) {
|
|
19
|
+
if (a[key] !== b[key]) return false;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
@@ -33,13 +33,6 @@ export interface ParsedSegment {
|
|
|
33
33
|
*/
|
|
34
34
|
export function parsePattern(pattern: string): ParsedSegment[] {
|
|
35
35
|
const segments: ParsedSegment[] = [];
|
|
36
|
-
// Match: /segment where segment can be:
|
|
37
|
-
// - static text
|
|
38
|
-
// - :param
|
|
39
|
-
// - :param?
|
|
40
|
-
// - :param(a|b)
|
|
41
|
-
// - :param(a|b)?
|
|
42
|
-
// - *
|
|
43
36
|
const segmentRegex =
|
|
44
37
|
/\/(:([a-zA-Z_][a-zA-Z0-9_]*)(\(([^)]+)\))?(\?)?([^/]*)|(\*)|([^/]+))/g;
|
|
45
38
|
|
|
@@ -81,7 +74,6 @@ export function parsePattern(pattern: string): ParsedSegment[] {
|
|
|
81
74
|
export interface CompiledPattern {
|
|
82
75
|
regex: RegExp;
|
|
83
76
|
paramNames: string[];
|
|
84
|
-
optionalParams: Set<string>;
|
|
85
77
|
hasTrailingSlash: boolean;
|
|
86
78
|
/**
|
|
87
79
|
* Param-name → allowed values for constrained params (e.g. `:lang(en|gb)`).
|
|
@@ -149,7 +141,6 @@ export function compilePattern(pattern: string): CompiledPattern {
|
|
|
149
141
|
|
|
150
142
|
const segments = parsePattern(normalizedPattern);
|
|
151
143
|
const paramNames: string[] = [];
|
|
152
|
-
const optionalParams = new Set<string>();
|
|
153
144
|
let constraints: Record<string, string[]> | undefined;
|
|
154
145
|
|
|
155
146
|
let regexPattern = "";
|
|
@@ -171,7 +162,6 @@ export function compilePattern(pattern: string): CompiledPattern {
|
|
|
171
162
|
}
|
|
172
163
|
|
|
173
164
|
if (segment.optional) {
|
|
174
|
-
optionalParams.add(segment.value);
|
|
175
165
|
// Optional: make the whole /segment optional
|
|
176
166
|
regexPattern += `(?:/${valuePattern}${suffixPattern})?`;
|
|
177
167
|
} else {
|
|
@@ -183,11 +173,24 @@ export function compilePattern(pattern: string): CompiledPattern {
|
|
|
183
173
|
}
|
|
184
174
|
}
|
|
185
175
|
|
|
186
|
-
// Handle root path
|
|
187
176
|
if (regexPattern === "") {
|
|
188
177
|
regexPattern = "/";
|
|
189
178
|
}
|
|
190
179
|
|
|
180
|
+
// Patterns of only optional segments (e.g. `/:locale?`, `/:a?/:b?`) need
|
|
181
|
+
// an explicit `/` alternative so a bare `/` matches the absent form. The
|
|
182
|
+
// optional template `(?:/X)?` matches `/X` or empty string, but pathnames
|
|
183
|
+
// are never empty. Arises from `include("/:locale?", routes)` + inner
|
|
184
|
+
// `path("/")`. Skip when an explicit trailing slash already anchors the
|
|
185
|
+
// match.
|
|
186
|
+
const hasOnlyOptionalSegments =
|
|
187
|
+
!hasTrailingSlash &&
|
|
188
|
+
segments.length > 0 &&
|
|
189
|
+
segments.every((segment) => segment.type === "param" && segment.optional);
|
|
190
|
+
if (hasOnlyOptionalSegments) {
|
|
191
|
+
regexPattern = `(?:/|${regexPattern})`;
|
|
192
|
+
}
|
|
193
|
+
|
|
191
194
|
// Add trailing slash to regex if pattern has one
|
|
192
195
|
if (hasTrailingSlash) {
|
|
193
196
|
regexPattern += "/";
|
|
@@ -196,7 +199,6 @@ export function compilePattern(pattern: string): CompiledPattern {
|
|
|
196
199
|
return {
|
|
197
200
|
regex: new RegExp(`^${regexPattern}$`),
|
|
198
201
|
paramNames,
|
|
199
|
-
optionalParams,
|
|
200
202
|
hasTrailingSlash,
|
|
201
203
|
...(constraints ? { constraints } : {}),
|
|
202
204
|
};
|
|
@@ -205,7 +207,9 @@ export function compilePattern(pattern: string): CompiledPattern {
|
|
|
205
207
|
/**
|
|
206
208
|
* Validate decoded params against a compiled pattern's constraints.
|
|
207
209
|
* Returns false if any constrained param has a non-empty value not in the
|
|
208
|
-
* allowed list (
|
|
210
|
+
* allowed list. Absent optionals (key missing or `undefined`) are allowed;
|
|
211
|
+
* `""` is also tolerated as "absent" so user-provided params or fixtures
|
|
212
|
+
* that pass empty strings explicitly behave the same way.
|
|
209
213
|
*/
|
|
210
214
|
function satisfiesConstraints(
|
|
211
215
|
params: Record<string, string>,
|
|
@@ -232,6 +236,27 @@ function escapeRegex(str: string): string {
|
|
|
232
236
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
233
237
|
}
|
|
234
238
|
|
|
239
|
+
/**
|
|
240
|
+
* Build the named-params record from a regex match. Optional segments that
|
|
241
|
+
* didn't capture leave the corresponding group `undefined`; we skip those
|
|
242
|
+
* keys so `ctx.params.<name>` reads as `undefined` rather than `""`. This
|
|
243
|
+
* keeps the runtime aligned with the `ExtractParams` type and matches the
|
|
244
|
+
* trie matcher's contract (see `trie-matching.ts:validateAndBuild`).
|
|
245
|
+
*/
|
|
246
|
+
function buildParamsFromMatch(
|
|
247
|
+
match: RegExpExecArray,
|
|
248
|
+
paramNames: string[],
|
|
249
|
+
): Record<string, string> {
|
|
250
|
+
const params: Record<string, string> = {};
|
|
251
|
+
paramNames.forEach((name, index) => {
|
|
252
|
+
const captured = match[index + 1];
|
|
253
|
+
if (captured !== undefined) {
|
|
254
|
+
params[name] = safeDecodeURIComponent(captured);
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
return params;
|
|
258
|
+
}
|
|
259
|
+
|
|
235
260
|
/**
|
|
236
261
|
* Extract the static prefix from a route pattern.
|
|
237
262
|
* Returns everything before the first param/wildcard.
|
|
@@ -248,7 +273,6 @@ function escapeRegex(str: string): string {
|
|
|
248
273
|
export function extractStaticPrefix(pattern: string): string {
|
|
249
274
|
if (!pattern || pattern === "/") return "";
|
|
250
275
|
|
|
251
|
-
// Find the first occurrence of : or *
|
|
252
276
|
const paramIndex = pattern.indexOf(":");
|
|
253
277
|
const wildcardIndex = pattern.indexOf("*");
|
|
254
278
|
|
|
@@ -262,16 +286,13 @@ export function extractStaticPrefix(pattern: string): string {
|
|
|
262
286
|
}
|
|
263
287
|
|
|
264
288
|
if (cutIndex === -1) {
|
|
265
|
-
// No params or wildcards - entire pattern is static
|
|
266
289
|
return pattern;
|
|
267
290
|
}
|
|
268
291
|
|
|
269
292
|
if (cutIndex === 0) {
|
|
270
|
-
// Pattern starts with : or * - no static prefix
|
|
271
293
|
return "";
|
|
272
294
|
}
|
|
273
295
|
|
|
274
|
-
// Find the last / before the param
|
|
275
296
|
const lastSlash = pattern.lastIndexOf("/", cutIndex - 1);
|
|
276
297
|
if (lastSlash === -1 || lastSlash === 0) {
|
|
277
298
|
return "";
|
|
@@ -280,11 +301,28 @@ export function extractStaticPrefix(pattern: string): string {
|
|
|
280
301
|
return pattern.slice(0, lastSlash);
|
|
281
302
|
}
|
|
282
303
|
|
|
304
|
+
/**
|
|
305
|
+
* Join a URL prefix to a sub-prefix, collapsing the duplicate slash when the
|
|
306
|
+
* base ends with "/" and the sub-prefix starts with "/". This mirrors the
|
|
307
|
+
* canonical join in `include()` (urls/include-helper.ts) and `runWithPrefixes`
|
|
308
|
+
* (server/context.ts) so a nested lazy include's runtime staticPrefix matches
|
|
309
|
+
* the build-time trie's `sp` (e.g. `include("/parent/", …)` containing
|
|
310
|
+
* `include("/child", …)` resolves to `/parent/child`, not `/parent//child`).
|
|
311
|
+
*/
|
|
312
|
+
export function joinPrefix(base: string | undefined, prefix: string): string {
|
|
313
|
+
if (!base) return prefix;
|
|
314
|
+
return base.endsWith("/") && prefix.startsWith("/")
|
|
315
|
+
? base + prefix.slice(1)
|
|
316
|
+
: base + prefix;
|
|
317
|
+
}
|
|
318
|
+
|
|
283
319
|
/**
|
|
284
320
|
* Match a pathname against registered routes
|
|
285
321
|
*
|
|
286
|
-
* Note: Optional params that are absent in the path
|
|
287
|
-
*
|
|
322
|
+
* Note: Optional params that are absent in the path are omitted from the
|
|
323
|
+
* returned `params` (read as `undefined`), matching the trie matcher and
|
|
324
|
+
* the `ExtractParams<"/:locale?/...">` type. Use the pattern definition to
|
|
325
|
+
* determine which keys are optional.
|
|
288
326
|
*
|
|
289
327
|
* Trailing slash handling (priority order):
|
|
290
328
|
* 1. Per-route `trailingSlash` config from route()
|
|
@@ -302,10 +340,7 @@ export interface RouteMatchResult<TEnv = any> {
|
|
|
302
340
|
entry: RouteEntry<TEnv>;
|
|
303
341
|
routeKey: string;
|
|
304
342
|
params: Record<string, string>;
|
|
305
|
-
optionalParams: Set<string>;
|
|
306
343
|
redirectTo?: string;
|
|
307
|
-
/** Ancestry shortCodes for layout pruning (from trie match) */
|
|
308
|
-
ancestry?: string[];
|
|
309
344
|
/** Route has pre-rendered data available (from trie) */
|
|
310
345
|
pr?: true;
|
|
311
346
|
/** Passthrough: handler kept for live fallback on unknown params (from trie) */
|
|
@@ -383,8 +418,6 @@ export function findMatch<TEnv>(
|
|
|
383
418
|
: pathname + "/";
|
|
384
419
|
|
|
385
420
|
for (const entry of routesEntries) {
|
|
386
|
-
// Short-circuit: skip entry if pathname doesn't start with static prefix
|
|
387
|
-
// staticPrefix is pre-computed at registration time, so this is O(1)
|
|
388
421
|
if (entry.staticPrefix && !pathname.startsWith(entry.staticPrefix)) {
|
|
389
422
|
if (effectiveDebug) {
|
|
390
423
|
debugStats.entriesSkipped++;
|
|
@@ -396,8 +429,6 @@ export function findMatch<TEnv>(
|
|
|
396
429
|
continue;
|
|
397
430
|
}
|
|
398
431
|
|
|
399
|
-
// Check if this is a lazy entry that needs evaluation
|
|
400
|
-
// When staticPrefix matches but routes are not yet populated, signal caller to evaluate
|
|
401
432
|
if (entry.lazy && !entry.lazyEvaluated) {
|
|
402
433
|
if (effectiveDebug) {
|
|
403
434
|
debugLog("findMatch", "lazy entry requires evaluation", {
|
|
@@ -418,7 +449,6 @@ export function findMatch<TEnv>(
|
|
|
418
449
|
debugStats.routesChecked++;
|
|
419
450
|
}
|
|
420
451
|
|
|
421
|
-
// Join prefix and pattern, handling edge cases
|
|
422
452
|
let fullPattern: string;
|
|
423
453
|
if (entry.prefix === "" || entry.prefix === "/") {
|
|
424
454
|
fullPattern = pattern;
|
|
@@ -428,19 +458,12 @@ export function findMatch<TEnv>(
|
|
|
428
458
|
fullPattern = entry.prefix + pattern;
|
|
429
459
|
}
|
|
430
460
|
|
|
431
|
-
const {
|
|
432
|
-
|
|
433
|
-
paramNames,
|
|
434
|
-
optionalParams,
|
|
435
|
-
hasTrailingSlash,
|
|
436
|
-
constraints,
|
|
437
|
-
} = getCompiledPattern(fullPattern);
|
|
461
|
+
const { regex, paramNames, hasTrailingSlash, constraints } =
|
|
462
|
+
getCompiledPattern(fullPattern);
|
|
438
463
|
|
|
439
|
-
// Get trailing slash mode for this route (per-route config or pattern-based)
|
|
440
464
|
const trailingSlashMode: TrailingSlashMode | undefined =
|
|
441
465
|
entry.trailingSlash?.[routeKey];
|
|
442
466
|
|
|
443
|
-
// Prerender flag from entry metadata (set by urls() for prerender handlers)
|
|
444
467
|
const prFlag = entry.prerenderRouteKeys?.has(routeKey)
|
|
445
468
|
? { pr: true as const }
|
|
446
469
|
: {};
|
|
@@ -448,16 +471,10 @@ export function findMatch<TEnv>(
|
|
|
448
471
|
? { pt: true as const }
|
|
449
472
|
: {};
|
|
450
473
|
|
|
451
|
-
// Try exact match first
|
|
452
474
|
const match = regex.exec(pathname);
|
|
453
475
|
if (match) {
|
|
454
|
-
const params
|
|
455
|
-
paramNames.forEach((name, index) => {
|
|
456
|
-
params[name] = safeDecodeURIComponent(match[index + 1] ?? "");
|
|
457
|
-
});
|
|
476
|
+
const params = buildParamsFromMatch(match, paramNames);
|
|
458
477
|
|
|
459
|
-
// Validate constraints against decoded values; a failure falls
|
|
460
|
-
// through to the next route so other patterns can still match.
|
|
461
478
|
if (!satisfiesConstraints(params, constraints)) {
|
|
462
479
|
continue;
|
|
463
480
|
}
|
|
@@ -470,29 +487,24 @@ export function findMatch<TEnv>(
|
|
|
470
487
|
});
|
|
471
488
|
}
|
|
472
489
|
|
|
473
|
-
// Check if trailing slash mode requires redirect even on exact match
|
|
474
490
|
if (
|
|
475
491
|
trailingSlashMode === "always" &&
|
|
476
492
|
!pathnameHasTrailingSlash &&
|
|
477
493
|
pathname !== "/"
|
|
478
494
|
) {
|
|
479
|
-
// Mode says always have trailing slash, but pathname doesn't have it
|
|
480
495
|
return {
|
|
481
496
|
entry,
|
|
482
497
|
routeKey,
|
|
483
498
|
params,
|
|
484
|
-
optionalParams,
|
|
485
499
|
redirectTo: pathname + "/",
|
|
486
500
|
...prFlag,
|
|
487
501
|
...ptFlag,
|
|
488
502
|
};
|
|
489
503
|
} else if (trailingSlashMode === "never" && pathnameHasTrailingSlash) {
|
|
490
|
-
// Mode says never have trailing slash, but pathname has it
|
|
491
504
|
return {
|
|
492
505
|
entry,
|
|
493
506
|
routeKey,
|
|
494
507
|
params,
|
|
495
|
-
optionalParams,
|
|
496
508
|
redirectTo: pathname.slice(0, -1),
|
|
497
509
|
...prFlag,
|
|
498
510
|
...ptFlag,
|
|
@@ -503,43 +515,33 @@ export function findMatch<TEnv>(
|
|
|
503
515
|
entry,
|
|
504
516
|
routeKey,
|
|
505
517
|
params,
|
|
506
|
-
optionalParams,
|
|
507
518
|
...prFlag,
|
|
508
519
|
...ptFlag,
|
|
509
520
|
};
|
|
510
521
|
}
|
|
511
522
|
|
|
512
|
-
// Try alternate pathname (opposite trailing slash)
|
|
513
523
|
const altMatch = regex.exec(alternatePathname);
|
|
514
524
|
if (altMatch) {
|
|
515
|
-
const params
|
|
516
|
-
paramNames.forEach((name, index) => {
|
|
517
|
-
params[name] = safeDecodeURIComponent(altMatch[index + 1] ?? "");
|
|
518
|
-
});
|
|
525
|
+
const params = buildParamsFromMatch(altMatch, paramNames);
|
|
519
526
|
|
|
520
527
|
if (!satisfiesConstraints(params, constraints)) {
|
|
521
528
|
continue;
|
|
522
529
|
}
|
|
523
530
|
|
|
524
|
-
// Determine redirect behavior based on mode
|
|
525
531
|
if (trailingSlashMode === "ignore") {
|
|
526
|
-
// Match without redirect
|
|
527
532
|
return {
|
|
528
533
|
entry,
|
|
529
534
|
routeKey,
|
|
530
535
|
params,
|
|
531
|
-
optionalParams,
|
|
532
536
|
...prFlag,
|
|
533
537
|
...ptFlag,
|
|
534
538
|
};
|
|
535
539
|
} else if (trailingSlashMode === "never") {
|
|
536
|
-
// Redirect to no trailing slash
|
|
537
540
|
if (pathnameHasTrailingSlash) {
|
|
538
541
|
return {
|
|
539
542
|
entry,
|
|
540
543
|
routeKey,
|
|
541
544
|
params,
|
|
542
|
-
optionalParams,
|
|
543
545
|
redirectTo: alternatePathname,
|
|
544
546
|
...prFlag,
|
|
545
547
|
...ptFlag,
|
|
@@ -549,18 +551,15 @@ export function findMatch<TEnv>(
|
|
|
549
551
|
entry,
|
|
550
552
|
routeKey,
|
|
551
553
|
params,
|
|
552
|
-
optionalParams,
|
|
553
554
|
...prFlag,
|
|
554
555
|
...ptFlag,
|
|
555
556
|
};
|
|
556
557
|
} else if (trailingSlashMode === "always") {
|
|
557
|
-
// Redirect to with trailing slash
|
|
558
558
|
if (!pathnameHasTrailingSlash) {
|
|
559
559
|
return {
|
|
560
560
|
entry,
|
|
561
561
|
routeKey,
|
|
562
562
|
params,
|
|
563
|
-
optionalParams,
|
|
564
563
|
redirectTo: alternatePathname,
|
|
565
564
|
...prFlag,
|
|
566
565
|
...ptFlag,
|
|
@@ -570,13 +569,10 @@ export function findMatch<TEnv>(
|
|
|
570
569
|
entry,
|
|
571
570
|
routeKey,
|
|
572
571
|
params,
|
|
573
|
-
optionalParams,
|
|
574
572
|
...prFlag,
|
|
575
573
|
...ptFlag,
|
|
576
574
|
};
|
|
577
575
|
} else {
|
|
578
|
-
// No explicit mode - use pattern-based detection
|
|
579
|
-
// Redirect to canonical form (what the pattern defines)
|
|
580
576
|
const canonicalPath = hasTrailingSlash
|
|
581
577
|
? alternatePathname
|
|
582
578
|
: pathname.slice(0, -1);
|
|
@@ -584,7 +580,6 @@ export function findMatch<TEnv>(
|
|
|
584
580
|
entry,
|
|
585
581
|
routeKey,
|
|
586
582
|
params,
|
|
587
|
-
optionalParams,
|
|
588
583
|
redirectTo: canonicalPath,
|
|
589
584
|
...prFlag,
|
|
590
585
|
...ptFlag,
|
|
@@ -605,7 +600,7 @@ export function* traverseBack(entry: EntryData): Generator<EntryData> {
|
|
|
605
600
|
let current: EntryData | null = entry;
|
|
606
601
|
const items = [] as EntryData[];
|
|
607
602
|
while (current !== null) {
|
|
608
|
-
items.push(current);
|
|
603
|
+
items.push(current);
|
|
609
604
|
current = current.parent;
|
|
610
605
|
}
|
|
611
606
|
for (let i = items.length - 1; i >= 0; i--) {
|