@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
|
@@ -14,7 +14,6 @@ import { traverseBack } from "./pattern-matching.js";
|
|
|
14
14
|
import type { RouteMatchResult } from "./pattern-matching.js";
|
|
15
15
|
import type { RouteSnapshot } from "./route-snapshot.js";
|
|
16
16
|
|
|
17
|
-
// Response type -> MIME type used for Accept header matching
|
|
18
17
|
export const RESPONSE_TYPE_MIME: Record<string, string> = {
|
|
19
18
|
json: "application/json",
|
|
20
19
|
text: "text/plain",
|
|
@@ -23,7 +22,6 @@ export const RESPONSE_TYPE_MIME: Record<string, string> = {
|
|
|
23
22
|
md: "text/markdown",
|
|
24
23
|
};
|
|
25
24
|
|
|
26
|
-
// Reverse lookup: MIME type -> response type tag (e.g. "text/html" -> "html")
|
|
27
25
|
export const MIME_RESPONSE_TYPE: Record<string, string> = Object.fromEntries(
|
|
28
26
|
Object.entries(RESPONSE_TYPE_MIME).map(([tag, mime]) => [mime, tag]),
|
|
29
27
|
);
|
|
@@ -71,12 +69,10 @@ export function parseAcceptTypes(accept: string): AcceptEntry[] {
|
|
|
71
69
|
}
|
|
72
70
|
entries.push({ mime, q, order: i });
|
|
73
71
|
}
|
|
74
|
-
// Sort: highest q first, then lowest client order first (stable)
|
|
75
72
|
entries.sort((a, b) => b.q - a.q || a.order - b.order);
|
|
76
73
|
return entries;
|
|
77
74
|
}
|
|
78
75
|
|
|
79
|
-
// Sentinel response type for RSC routes in negotiation candidates
|
|
80
76
|
export const RSC_RESPONSE_TYPE = "__rsc__";
|
|
81
77
|
|
|
82
78
|
/**
|
|
@@ -89,7 +85,6 @@ export function pickNegotiateVariant(
|
|
|
89
85
|
acceptEntries: AcceptEntry[],
|
|
90
86
|
candidates: Array<{ routeKey: string; responseType: string }>,
|
|
91
87
|
): { routeKey: string; responseType: string } {
|
|
92
|
-
// Build a MIME -> candidate lookup for O(1) matching
|
|
93
88
|
const byCandidateMime = new Map<
|
|
94
89
|
string,
|
|
95
90
|
{ routeKey: string; responseType: string }
|
|
@@ -106,9 +101,7 @@ export function pickNegotiateVariant(
|
|
|
106
101
|
|
|
107
102
|
for (const entry of acceptEntries) {
|
|
108
103
|
if (entry.q === 0) continue;
|
|
109
|
-
// Wildcard matches first candidate
|
|
110
104
|
if (entry.mime === "*/*") return candidates[0]!;
|
|
111
|
-
// Type wildcard (e.g. "text/*") -- match first candidate with that type
|
|
112
105
|
if (entry.mime.endsWith("/*")) {
|
|
113
106
|
const typePrefix = entry.mime.slice(0, entry.mime.indexOf("/"));
|
|
114
107
|
for (const [mime, candidate] of byCandidateMime) {
|
|
@@ -119,7 +112,6 @@ export function pickNegotiateVariant(
|
|
|
119
112
|
const match = byCandidateMime.get(entry.mime);
|
|
120
113
|
if (match) return match;
|
|
121
114
|
}
|
|
122
|
-
// No match -- use first candidate as default
|
|
123
115
|
return candidates[0]!;
|
|
124
116
|
}
|
|
125
117
|
|
|
@@ -135,8 +127,8 @@ export interface NegotiationResult {
|
|
|
135
127
|
manifestEntry: EntryData;
|
|
136
128
|
/** Route middleware for the winning variant */
|
|
137
129
|
routeMiddleware: CollectedMiddleware[];
|
|
138
|
-
/**
|
|
139
|
-
negotiated:
|
|
130
|
+
/** True when negotiation selected a variant; false for a plain response route. */
|
|
131
|
+
negotiated: boolean;
|
|
140
132
|
}
|
|
141
133
|
|
|
142
134
|
/**
|
|
@@ -155,12 +147,24 @@ export async function negotiateRoute(
|
|
|
155
147
|
): Promise<NegotiationResult | null> {
|
|
156
148
|
const { matched, manifestEntry, routeMiddleware, responseType } = snapshot;
|
|
157
149
|
if (!matched.negotiateVariants || matched.negotiateVariants.length === 0) {
|
|
150
|
+
// No variants: a plain response route still yields a result (negotiated:false)
|
|
151
|
+
// so callers don't re-derive it; RSC routes (no responseType/handler) -> null.
|
|
152
|
+
const handler =
|
|
153
|
+
manifestEntry.type === "route" ? manifestEntry.handler : undefined;
|
|
154
|
+
if (responseType && handler) {
|
|
155
|
+
return {
|
|
156
|
+
responseType,
|
|
157
|
+
handler: handler as Function,
|
|
158
|
+
manifestEntry,
|
|
159
|
+
routeMiddleware,
|
|
160
|
+
negotiated: false,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
158
163
|
return null;
|
|
159
164
|
}
|
|
160
165
|
|
|
161
166
|
const acceptEntries = parseAcceptTypes(request.headers.get("accept") || "");
|
|
162
167
|
|
|
163
|
-
// Build candidate list preserving definition order.
|
|
164
168
|
const variants = matched.negotiateVariants;
|
|
165
169
|
let candidates: Array<{ routeKey: string; responseType: string }>;
|
|
166
170
|
if (responseType) {
|
|
@@ -177,12 +181,10 @@ export async function negotiateRoute(
|
|
|
177
181
|
|
|
178
182
|
const variant = pickNegotiateVariant(acceptEntries, candidates);
|
|
179
183
|
|
|
180
|
-
// RSC won negotiation
|
|
181
184
|
if (variant.responseType === RSC_RESPONSE_TYPE) {
|
|
182
185
|
return null;
|
|
183
186
|
}
|
|
184
187
|
|
|
185
|
-
// Primary response-type won — use existing manifest entry and middleware
|
|
186
188
|
if (responseType && variant.routeKey === matched.routeKey) {
|
|
187
189
|
return {
|
|
188
190
|
responseType,
|
|
@@ -192,8 +194,6 @@ export async function negotiateRoute(
|
|
|
192
194
|
negotiated: true,
|
|
193
195
|
};
|
|
194
196
|
}
|
|
195
|
-
|
|
196
|
-
// Different variant won — load its manifest entry
|
|
197
197
|
const negotiateEntry = await loadManifest(
|
|
198
198
|
matched.entry,
|
|
199
199
|
variant.routeKey,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Router Error Handling Utilities
|
|
3
3
|
*
|
|
4
|
-
* Error boundary and not-found boundary handling for
|
|
4
|
+
* Error boundary and not-found boundary handling for Rango.
|
|
5
5
|
* Also includes the shared invokeOnError utility for error callback invocation.
|
|
6
6
|
*/
|
|
7
7
|
|
|
@@ -117,16 +117,10 @@ export function findNearestErrorBoundary(
|
|
|
117
117
|
let current: EntryData | null = entry;
|
|
118
118
|
|
|
119
119
|
while (current) {
|
|
120
|
-
// Check if this entry has error boundaries defined
|
|
121
120
|
if (current.errorBoundary && current.errorBoundary.length > 0) {
|
|
122
|
-
// Return the last error boundary (most recently defined takes precedence)
|
|
123
121
|
return current.errorBoundary[current.errorBoundary.length - 1];
|
|
124
122
|
}
|
|
125
123
|
|
|
126
|
-
// Check orphan layouts for error boundaries
|
|
127
|
-
// Orphan layouts are siblings that render alongside the main route chain
|
|
128
|
-
// They can define error boundaries that catch errors from routes in the same route group
|
|
129
|
-
// Check from first to last (first sibling takes precedence as the "outer" wrapper)
|
|
130
124
|
if (current.layout && current.layout.length > 0) {
|
|
131
125
|
for (const orphan of current.layout) {
|
|
132
126
|
if (orphan.errorBoundary && orphan.errorBoundary.length > 0) {
|
|
@@ -153,11 +147,21 @@ export function findNearestNotFoundBoundary(
|
|
|
153
147
|
let current: EntryData | null = entry;
|
|
154
148
|
|
|
155
149
|
while (current) {
|
|
156
|
-
// Check if this entry has notFound boundaries defined
|
|
157
150
|
if (current.notFoundBoundary && current.notFoundBoundary.length > 0) {
|
|
158
|
-
// Return the last notFound boundary (most recently defined takes precedence)
|
|
159
151
|
return current.notFoundBoundary[current.notFoundBoundary.length - 1];
|
|
160
152
|
}
|
|
153
|
+
|
|
154
|
+
// Check orphan layouts mirroring findNearestErrorBoundary: notFoundBoundary
|
|
155
|
+
// attaches identically (onto parent.notFoundBoundary), and an orphan layout
|
|
156
|
+
// (parent=null) is reachable only via this scan. First sibling is "outer".
|
|
157
|
+
if (current.layout && current.layout.length > 0) {
|
|
158
|
+
for (const orphan of current.layout) {
|
|
159
|
+
if (orphan.notFoundBoundary && orphan.notFoundBoundary.length > 0) {
|
|
160
|
+
return orphan.notFoundBoundary[orphan.notFoundBoundary.length - 1];
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
161
165
|
current = current.parent;
|
|
162
166
|
}
|
|
163
167
|
|
|
@@ -207,22 +211,17 @@ export function createErrorSegment(
|
|
|
207
211
|
entry: EntryData,
|
|
208
212
|
params: Record<string, string>,
|
|
209
213
|
): ResolvedSegment {
|
|
210
|
-
// Determine the component to render
|
|
211
214
|
let component: ReactNode;
|
|
212
215
|
|
|
213
216
|
if (typeof fallback === "function") {
|
|
214
|
-
// ErrorBoundaryHandler - call with error info
|
|
215
217
|
const props: ErrorBoundaryFallbackProps = {
|
|
216
218
|
error: errorInfo,
|
|
217
219
|
};
|
|
218
220
|
component = fallback(props);
|
|
219
221
|
} else {
|
|
220
|
-
// Static ReactNode fallback
|
|
221
222
|
component = fallback;
|
|
222
223
|
}
|
|
223
224
|
|
|
224
|
-
// Error segment uses the same ID as the layout that has the error boundary
|
|
225
|
-
// The error boundary content replaces the layout's outlet content
|
|
226
225
|
return {
|
|
227
226
|
id: entry.shortCode,
|
|
228
227
|
namespace: entry.id,
|
|
@@ -261,17 +260,14 @@ export function createNotFoundSegment(
|
|
|
261
260
|
entry: EntryData,
|
|
262
261
|
params: Record<string, string>,
|
|
263
262
|
): ResolvedSegment {
|
|
264
|
-
// Determine the component to render
|
|
265
263
|
let component: ReactNode;
|
|
266
264
|
|
|
267
265
|
if (typeof fallback === "function") {
|
|
268
|
-
// NotFoundBoundaryHandler - call with props
|
|
269
266
|
const props: NotFoundBoundaryFallbackProps = {
|
|
270
267
|
notFound: notFoundInfo,
|
|
271
268
|
};
|
|
272
269
|
component = fallback(props);
|
|
273
270
|
} else {
|
|
274
|
-
// Static ReactNode fallback
|
|
275
271
|
component = fallback;
|
|
276
272
|
}
|
|
277
273
|
|
package/src/router/find-match.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { tryTrieMatch } from "./trie-matching.js";
|
|
2
|
-
import {
|
|
2
|
+
import { getRouterTrie } from "../route-map-builder.js";
|
|
3
3
|
import {
|
|
4
4
|
findMatch as findRouteMatch,
|
|
5
5
|
isLazyEvaluationNeeded,
|
|
@@ -8,6 +8,16 @@ import {
|
|
|
8
8
|
import type { MetricsStore } from "../server/context";
|
|
9
9
|
import type { RouteEntry } from "../types";
|
|
10
10
|
|
|
11
|
+
// The single-entry cache is module-lifetime, keyed only on pathname, so the same
|
|
12
|
+
// result object is handed to every same-pathname request. ctx.params aliases
|
|
13
|
+
// this object, so handlers mutating it would corrupt the cache for later requests.
|
|
14
|
+
// Clone params; entry/flags are read-only and shared safely.
|
|
15
|
+
function cloneMatchResult<TEnv>(
|
|
16
|
+
r: RouteMatchResult<TEnv> | null,
|
|
17
|
+
): RouteMatchResult<TEnv> | null {
|
|
18
|
+
return r ? { ...r, params: { ...r.params } } : null;
|
|
19
|
+
}
|
|
20
|
+
|
|
11
21
|
export interface FindMatchDeps<TEnv = any> {
|
|
12
22
|
routesEntries: RouteEntry<TEnv>[];
|
|
13
23
|
evaluateLazyEntry: (entry: RouteEntry<TEnv>) => void;
|
|
@@ -27,20 +37,14 @@ export function createFindMatch<TEnv = any>(
|
|
|
27
37
|
let lastFindMatchPathname: string | null = null;
|
|
28
38
|
let lastFindMatchResult: RouteMatchResult<TEnv> | null = null;
|
|
29
39
|
|
|
30
|
-
// Wrapper for findMatch that uses routesEntries
|
|
31
|
-
// Handles lazy evaluation by evaluating lazy entries on first match.
|
|
32
|
-
// Phase 1: try O(path_length) trie match.
|
|
33
|
-
// Phase 2: fall back to regex iteration.
|
|
34
40
|
return function findMatch(
|
|
35
41
|
pathname: string,
|
|
36
42
|
ms?: MetricsStore,
|
|
37
43
|
): RouteMatchResult<TEnv> | null {
|
|
38
|
-
// Return cached result if same pathname (avoids double-match per request)
|
|
39
44
|
if (lastFindMatchPathname === pathname) {
|
|
40
|
-
return lastFindMatchResult;
|
|
45
|
+
return cloneMatchResult(lastFindMatchResult);
|
|
41
46
|
}
|
|
42
47
|
|
|
43
|
-
// Helper to push sub-metrics
|
|
44
48
|
const pushMetric = ms
|
|
45
49
|
? (label: string, start: number) => {
|
|
46
50
|
ms.metrics.push({
|
|
@@ -51,17 +55,15 @@ export function createFindMatch<TEnv = any>(
|
|
|
51
55
|
}
|
|
52
56
|
: undefined;
|
|
53
57
|
|
|
54
|
-
// Phase 1: Try trie match (O(path_length))
|
|
55
|
-
// Only use the per-router trie. The global trie merges routes from ALL
|
|
56
|
-
// routers and must not be used — in multi-router setups (host routing)
|
|
57
|
-
// overlapping paths like "/" would match the wrong app's route.
|
|
58
58
|
const routeTrie = getRouterTrie(deps.routerId);
|
|
59
|
+
let trieMatched = false;
|
|
59
60
|
if (routeTrie) {
|
|
60
61
|
const trieStart = performance.now();
|
|
61
62
|
const trieResult = tryTrieMatch(routeTrie, pathname);
|
|
62
63
|
pushMetric?.("match:trie", trieStart);
|
|
63
64
|
|
|
64
65
|
if (trieResult) {
|
|
66
|
+
trieMatched = true;
|
|
65
67
|
// Find the RouteEntry that contains this route.
|
|
66
68
|
// Multiple entries can share the same staticPrefix (e.g., several
|
|
67
69
|
// include("/", patterns) calls all produce staticPrefix=""). Evaluate
|
|
@@ -83,12 +85,8 @@ export function createFindMatch<TEnv = any>(
|
|
|
83
85
|
}
|
|
84
86
|
}
|
|
85
87
|
|
|
86
|
-
// If no entry had the route in its routes map, use the first matching
|
|
87
|
-
// entry as fallback (handles main entry with inline routes not yet
|
|
88
|
-
// reflected in its routes object).
|
|
89
88
|
if (!entry) entry = fallbackEntry;
|
|
90
89
|
|
|
91
|
-
// If entry not found (nested include not yet discovered), evaluate parent
|
|
92
90
|
if (!entry) {
|
|
93
91
|
const parent = deps.routesEntries.find(
|
|
94
92
|
(e) =>
|
|
@@ -112,9 +110,7 @@ export function createFindMatch<TEnv = any>(
|
|
|
112
110
|
entry,
|
|
113
111
|
routeKey: trieResult.routeKey,
|
|
114
112
|
params: trieResult.params,
|
|
115
|
-
optionalParams: new Set(trieResult.optionalParams || []),
|
|
116
113
|
redirectTo: trieResult.redirectTo,
|
|
117
|
-
ancestry: trieResult.ancestry,
|
|
118
114
|
...(trieResult.pr ? { pr: true } : {}),
|
|
119
115
|
...(trieResult.pt ? { pt: true } : {}),
|
|
120
116
|
...(trieResult.responseType
|
|
@@ -125,17 +121,14 @@ export function createFindMatch<TEnv = any>(
|
|
|
125
121
|
: {}),
|
|
126
122
|
...(trieResult.rscFirst ? { rscFirst: true } : {}),
|
|
127
123
|
};
|
|
128
|
-
return lastFindMatchResult;
|
|
124
|
+
return cloneMatchResult(lastFindMatchResult);
|
|
129
125
|
}
|
|
130
126
|
}
|
|
131
127
|
}
|
|
132
128
|
|
|
133
|
-
// Phase 2: Fall back to existing matching (regex iteration)
|
|
134
129
|
const regexStart = performance.now();
|
|
135
130
|
let result = findRouteMatch(pathname, deps.routesEntries);
|
|
136
131
|
|
|
137
|
-
// If we hit a lazy entry that needs evaluation, evaluate and retry.
|
|
138
|
-
// Cap iterations to prevent infinite loops from pathological nesting.
|
|
139
132
|
const MAX_LAZY_ITERATIONS = 100;
|
|
140
133
|
let iterations = 0;
|
|
141
134
|
while (isLazyEvaluationNeeded(result)) {
|
|
@@ -153,8 +146,36 @@ export function createFindMatch<TEnv = any>(
|
|
|
153
146
|
}
|
|
154
147
|
pushMetric?.("match:regex-fallback", regexStart);
|
|
155
148
|
|
|
149
|
+
// The trie is the single source of truth and is built before findMatch in
|
|
150
|
+
// both dev (handler rebuild) and production (ensureRouterManifest). If the
|
|
151
|
+
// trie was present yet the regex fallback resolved a real match, the trie
|
|
152
|
+
// has a gap (e.g. a route shape it cannot represent) and dev/prod could
|
|
153
|
+
// diverge if the trie were ever absent. Surface it in dev; folded out in
|
|
154
|
+
// production builds.
|
|
155
|
+
//
|
|
156
|
+
// Suppress when the trie DID match (`trieMatched`): that path falls through
|
|
157
|
+
// to the regex fallback only on the first request to a not-yet-spliced lazy
|
|
158
|
+
// entry (e.g. a 2+-level nested include whose deeper parent has not been
|
|
159
|
+
// evaluated). The trie knew the route; runtime lazy discovery simply lagged.
|
|
160
|
+
// That is the supported lazy-include flow, not a trie gap, so warning on it
|
|
161
|
+
// is a false positive (it manufactures bug reports and erodes the signal).
|
|
162
|
+
if (
|
|
163
|
+
process.env.NODE_ENV !== "production" &&
|
|
164
|
+
routeTrie &&
|
|
165
|
+
!trieMatched &&
|
|
166
|
+
result &&
|
|
167
|
+
!isLazyEvaluationNeeded(result)
|
|
168
|
+
) {
|
|
169
|
+
console.warn(
|
|
170
|
+
`[@rangojs/router] Route "${pathname}" resolved via the regex fallback ` +
|
|
171
|
+
`even though the route trie was present. The trie should be the single ` +
|
|
172
|
+
`matching source of truth; this indicates a trie gap. Please report this ` +
|
|
173
|
+
`with your route configuration.`,
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
156
177
|
lastFindMatchPathname = pathname;
|
|
157
178
|
lastFindMatchResult = result;
|
|
158
|
-
return result;
|
|
179
|
+
return cloneMatchResult(result);
|
|
159
180
|
};
|
|
160
181
|
}
|
|
@@ -18,7 +18,7 @@ import { isInsideCacheScope } from "../server/context.js";
|
|
|
18
18
|
import { NOCACHE_SYMBOL, assertNotInsideCacheExec } from "../cache/taint.js";
|
|
19
19
|
import { isAutoGeneratedRouteName } from "../route-name.js";
|
|
20
20
|
import { PRERENDER_PASSTHROUGH } from "../prerender.js";
|
|
21
|
-
import {
|
|
21
|
+
import { substitutePatternParams } from "./substitute-pattern-params.js";
|
|
22
22
|
import { fireAndForgetWaitUntil } from "../types/request-scope.js";
|
|
23
23
|
|
|
24
24
|
/**
|
|
@@ -160,51 +160,14 @@ export function createReverseFunction(
|
|
|
160
160
|
);
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
-
let result = pattern;
|
|
164
|
-
|
|
165
163
|
// Merge current request params as defaults, explicit params override
|
|
166
164
|
const effectiveParams = currentParams
|
|
167
165
|
? { ...currentParams, ...hrefParams }
|
|
168
166
|
: hrefParams;
|
|
169
167
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
let hadOmittedOptional = false;
|
|
174
|
-
// First pass: optional params (trailing ?)
|
|
175
|
-
result = result.replace(
|
|
176
|
-
/:([a-zA-Z_][a-zA-Z0-9_]*)(\([^)]*\))?(\?)/g,
|
|
177
|
-
(_, key) => {
|
|
178
|
-
const value = effectiveParams[key];
|
|
179
|
-
// Empty string is treated as omitted — the trie matcher fills
|
|
180
|
-
// unmatched optional params with "" (not undefined), so reverse
|
|
181
|
-
// must collapse those segments instead of leaving empty slots.
|
|
182
|
-
if (value === undefined || value === "") {
|
|
183
|
-
hadOmittedOptional = true;
|
|
184
|
-
return "";
|
|
185
|
-
}
|
|
186
|
-
return encodePathSegment(value);
|
|
187
|
-
},
|
|
188
|
-
);
|
|
189
|
-
// Second pass: required params (no trailing ?)
|
|
190
|
-
result = result.replace(
|
|
191
|
-
/:([a-zA-Z_][a-zA-Z0-9_]*)(\([^)]*\))?(?!\?)/g,
|
|
192
|
-
(_, key) => {
|
|
193
|
-
const value = effectiveParams[key];
|
|
194
|
-
if (value === undefined) {
|
|
195
|
-
throw new Error(`Missing param "${key}" for route "${name}"`);
|
|
196
|
-
}
|
|
197
|
-
return encodePathSegment(value);
|
|
198
|
-
},
|
|
199
|
-
);
|
|
200
|
-
// Clean up slashes only when an optional param was actually omitted,
|
|
201
|
-
// so intentional trailing-slash patterns like "/blog/" are preserved.
|
|
202
|
-
if (hadOmittedOptional) {
|
|
203
|
-
const hadTrailingSlash = pattern.length > 1 && pattern.endsWith("/");
|
|
204
|
-
result = result.replace(/\/\/+/g, "/").replace(/\/+$/, "") || "/";
|
|
205
|
-
if (hadTrailingSlash && !result.endsWith("/")) result += "/";
|
|
206
|
-
}
|
|
207
|
-
}
|
|
168
|
+
let result = effectiveParams
|
|
169
|
+
? substitutePatternParams(pattern, effectiveParams, name)
|
|
170
|
+
: pattern;
|
|
208
171
|
|
|
209
172
|
// Append search params as query string
|
|
210
173
|
if (search) {
|
|
@@ -21,7 +21,10 @@ import { getRequestContext } from "../server/request-context.js";
|
|
|
21
21
|
import { executeInterceptMiddleware } from "./middleware.js";
|
|
22
22
|
import { createReverseFunction } from "./handler-context.js";
|
|
23
23
|
import { getGlobalRouteMap } from "../route-map-builder.js";
|
|
24
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
handleHandlerResult,
|
|
26
|
+
warnOnStreamedResponse,
|
|
27
|
+
} from "./segment-resolution.js";
|
|
25
28
|
import type { SegmentResolutionDeps } from "./types.js";
|
|
26
29
|
import { debugLog } from "./logging.js";
|
|
27
30
|
import { runInsideLoaderScope } from "../server/context.js";
|
|
@@ -66,28 +69,14 @@ export function findInterceptForRoute(
|
|
|
66
69
|
let current: EntryData | null = fromEntry;
|
|
67
70
|
|
|
68
71
|
while (current) {
|
|
69
|
-
|
|
70
|
-
|
|
72
|
+
// current first, then its sibling layouts — same order as before.
|
|
73
|
+
for (const source of [current, ...current.layout]) {
|
|
74
|
+
for (const intercept of source.intercept) {
|
|
71
75
|
if (
|
|
72
76
|
intercept.routeName === targetRouteKey &&
|
|
73
77
|
evaluateInterceptWhen(intercept, selectorContext, isAction)
|
|
74
78
|
) {
|
|
75
|
-
return { intercept, entry:
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (current.layout && current.layout.length > 0) {
|
|
81
|
-
for (const siblingLayout of current.layout) {
|
|
82
|
-
if (siblingLayout.intercept && siblingLayout.intercept.length > 0) {
|
|
83
|
-
for (const intercept of siblingLayout.intercept) {
|
|
84
|
-
if (
|
|
85
|
-
intercept.routeName === targetRouteKey &&
|
|
86
|
-
evaluateInterceptWhen(intercept, selectorContext, isAction)
|
|
87
|
-
) {
|
|
88
|
-
return { intercept, entry: siblingLayout };
|
|
89
|
-
}
|
|
90
|
-
}
|
|
79
|
+
return { intercept, entry: source };
|
|
91
80
|
}
|
|
92
81
|
}
|
|
93
82
|
}
|
|
@@ -242,6 +231,12 @@ export async function resolveInterceptEntry<TEnv>(
|
|
|
242
231
|
let loaderDataPromise: Promise<any[]> | any[] | undefined;
|
|
243
232
|
|
|
244
233
|
if (interceptEntry.loading && loaderPromises.length > 0) {
|
|
234
|
+
if (handlerResult instanceof Promise) {
|
|
235
|
+
warnOnStreamedResponse(
|
|
236
|
+
handlerResult,
|
|
237
|
+
`intercept ${interceptEntry.slotName}`,
|
|
238
|
+
);
|
|
239
|
+
}
|
|
245
240
|
component =
|
|
246
241
|
handlerResult instanceof Promise
|
|
247
242
|
? handlerResult
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { registerRouteMap } from "../route-map-builder.js";
|
|
2
|
-
import { extractStaticPrefix } from "./pattern-matching.js";
|
|
2
|
+
import { extractStaticPrefix, joinPrefix } from "./pattern-matching.js";
|
|
3
3
|
import {
|
|
4
4
|
type EntryData,
|
|
5
|
-
|
|
5
|
+
RangoContext,
|
|
6
6
|
runWithPrefixes,
|
|
7
7
|
getIsolatedLazyParent,
|
|
8
8
|
} from "../server/context";
|
|
@@ -18,9 +18,6 @@ export interface LazyEvalDeps<TEnv = any> {
|
|
|
18
18
|
routerId?: string;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
// Detect lazy includes in handler result and create placeholder entries
|
|
22
|
-
// Lazy includes are IncludeItem with lazy: true and _lazyContext
|
|
23
|
-
// Moved to outer scope so it can be reused by evaluateLazyEntry for nested includes
|
|
24
21
|
export function findLazyIncludes<TEnv = any>(
|
|
25
22
|
items: AllUseItems[],
|
|
26
23
|
): Array<{
|
|
@@ -56,7 +53,6 @@ export function findLazyIncludes<TEnv = any>(
|
|
|
56
53
|
});
|
|
57
54
|
}
|
|
58
55
|
}
|
|
59
|
-
// Recursively check nested items (in layouts, etc.)
|
|
60
56
|
if ((item as any).uses && Array.isArray((item as any).uses)) {
|
|
61
57
|
lazyItems.push(...findLazyIncludes((item as any).uses));
|
|
62
58
|
}
|
|
@@ -78,14 +74,6 @@ export function evaluateLazyEntry<TEnv = any>(
|
|
|
78
74
|
return;
|
|
79
75
|
}
|
|
80
76
|
|
|
81
|
-
// Check for pre-computed routes from build-time data.
|
|
82
|
-
// Only leaf nodes (no nested includes) are precomputed, so entries with
|
|
83
|
-
// nested lazy includes fall through to the handler below.
|
|
84
|
-
// When multiple entries share the same staticPrefix (e.g., several
|
|
85
|
-
// include("/", ...) calls), the precomputed data merges all their routes
|
|
86
|
-
// into one entry. Assigning that merged set to the first matching entry
|
|
87
|
-
// causes findMatch to pick the wrong handler for routes belonging to a
|
|
88
|
-
// different include. Skip the shortcut when the prefix is shared.
|
|
89
77
|
const currentPrecomputed = deps.getPrecomputedByPrefix();
|
|
90
78
|
if (currentPrecomputed) {
|
|
91
79
|
const routes = currentPrecomputed.get(entry.staticPrefix);
|
|
@@ -105,25 +93,18 @@ export function evaluateLazyEntry<TEnv = any>(
|
|
|
105
93
|
}
|
|
106
94
|
}
|
|
107
95
|
|
|
108
|
-
// Mark as evaluated immediately to prevent concurrent evaluation.
|
|
109
|
-
// JS is single-threaded but handlers.handler() could theoretically yield,
|
|
110
|
-
// and the while-loop in findMatch retries after evaluation.
|
|
111
96
|
entry.lazyEvaluated = true;
|
|
112
97
|
|
|
113
98
|
const lazyPatterns = entry.lazyPatterns as UrlPatterns<TEnv>;
|
|
114
99
|
const lazyContext = entry.lazyContext;
|
|
115
100
|
|
|
116
|
-
// Create a new context for evaluating the lazy patterns
|
|
117
101
|
const manifest = new Map<string, EntryData>();
|
|
118
102
|
const patterns = new Map<string, string>();
|
|
119
103
|
const patternsByPrefix = new Map<string, Map<string, string>>();
|
|
120
104
|
const trailingSlashMap = new Map<string, TrailingSlashMode>();
|
|
121
105
|
|
|
122
|
-
// Capture the handler result to detect nested lazy includes
|
|
123
106
|
let handlerResult: AllUseItems[] = [];
|
|
124
107
|
|
|
125
|
-
// Merge captured counters from include() to maintain consistent
|
|
126
|
-
// shortCode indices with sibling entries from pattern extraction
|
|
127
108
|
const lazyCounters: Record<string, number> = {};
|
|
128
109
|
if (lazyContext?.counters) {
|
|
129
110
|
for (const [key, value] of Object.entries(lazyContext.counters)) {
|
|
@@ -131,7 +112,7 @@ export function evaluateLazyEntry<TEnv = any>(
|
|
|
131
112
|
}
|
|
132
113
|
}
|
|
133
114
|
|
|
134
|
-
|
|
115
|
+
RangoContext.run(
|
|
135
116
|
{
|
|
136
117
|
manifest,
|
|
137
118
|
patterns,
|
|
@@ -145,10 +126,8 @@ export function evaluateLazyEntry<TEnv = any>(
|
|
|
145
126
|
includeScope: lazyContext?.includeScope,
|
|
146
127
|
},
|
|
147
128
|
() => {
|
|
148
|
-
// Run the lazy patterns handler with the original context prefixes
|
|
149
|
-
// The prefix comes from the IncludeItem stored in lazyPatterns
|
|
150
129
|
const includePrefix = (entry as any)._lazyPrefix || "";
|
|
151
|
-
const fullPrefix = (lazyContext?.urlPrefix
|
|
130
|
+
const fullPrefix = joinPrefix(lazyContext?.urlPrefix, includePrefix);
|
|
152
131
|
|
|
153
132
|
if (fullPrefix || lazyContext?.namePrefix) {
|
|
154
133
|
runWithPrefixes(fullPrefix, lazyContext?.namePrefix, () => {
|
|
@@ -160,11 +139,9 @@ export function evaluateLazyEntry<TEnv = any>(
|
|
|
160
139
|
},
|
|
161
140
|
);
|
|
162
141
|
|
|
163
|
-
// Populate the entry's routes from the patterns
|
|
164
142
|
const routesObject: Record<string, string> = {};
|
|
165
143
|
for (const [name, pattern] of patterns.entries()) {
|
|
166
144
|
routesObject[name] = pattern;
|
|
167
|
-
// Also add to merged route map for reverse() support
|
|
168
145
|
const existingPattern = deps.mergedRouteMap[name];
|
|
169
146
|
if (existingPattern !== undefined && existingPattern !== pattern) {
|
|
170
147
|
console.warn(
|
|
@@ -175,46 +152,33 @@ export function evaluateLazyEntry<TEnv = any>(
|
|
|
175
152
|
deps.mergedRouteMap[name] = pattern;
|
|
176
153
|
}
|
|
177
154
|
|
|
178
|
-
// Update the entry in-place
|
|
179
155
|
entry.routes = routesObject as ResolvedRouteMap<any>;
|
|
180
156
|
|
|
181
|
-
// Note: Do NOT clear lazyPatterns/lazyContext here.
|
|
182
|
-
// loadManifest() needs them on every request to re-run the handler
|
|
183
|
-
// in the correct AsyncLocalStorage context (Store.manifest).
|
|
184
|
-
|
|
185
|
-
// Update trailing slash config if available
|
|
186
157
|
if (trailingSlashMap.size > 0) {
|
|
187
158
|
entry.trailingSlash = Object.fromEntries(trailingSlashMap);
|
|
188
159
|
}
|
|
189
160
|
|
|
190
|
-
// Detect nested lazy includes and register them as new entries
|
|
191
161
|
const nestedLazyIncludes = findLazyIncludes(handlerResult);
|
|
192
162
|
for (const lazyInclude of nestedLazyIncludes) {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
163
|
+
const fullPrefix = joinPrefix(
|
|
164
|
+
lazyInclude.context.urlPrefix,
|
|
165
|
+
lazyInclude.prefix,
|
|
166
|
+
);
|
|
197
167
|
|
|
198
168
|
const nestedEntry: RouteEntry<TEnv> & { _lazyPrefix?: string } = {
|
|
199
169
|
prefix: "",
|
|
200
170
|
staticPrefix: extractStaticPrefix(fullPrefix),
|
|
201
|
-
routes: {} as ResolvedRouteMap<any>,
|
|
171
|
+
routes: {} as ResolvedRouteMap<any>,
|
|
202
172
|
trailingSlash: entry.trailingSlash,
|
|
203
173
|
handler: (lazyInclude.patterns as UrlPatterns<TEnv>).handler,
|
|
204
174
|
mountIndex: deps.nextMountIndex(),
|
|
205
175
|
routerId: deps.routerId,
|
|
206
|
-
// Lazy evaluation fields
|
|
207
176
|
lazy: true,
|
|
208
177
|
lazyPatterns: lazyInclude.patterns,
|
|
209
178
|
lazyContext: lazyInclude.context,
|
|
210
179
|
lazyEvaluated: false,
|
|
211
|
-
// Store the include prefix for evaluation
|
|
212
180
|
_lazyPrefix: lazyInclude.prefix,
|
|
213
181
|
};
|
|
214
|
-
// Insert nested lazy entry before any entry whose staticPrefix is a
|
|
215
|
-
// prefix of (but shorter than) this lazy entry's staticPrefix.
|
|
216
|
-
// This ensures more specific lazy includes are matched before
|
|
217
|
-
// less specific eager entries (e.g., "/href/nested" before "/href/:id").
|
|
218
182
|
const nestedPrefix = nestedEntry.staticPrefix;
|
|
219
183
|
let insertIndex = deps.routesEntries.length;
|
|
220
184
|
if (nestedPrefix) {
|
|
@@ -232,6 +196,5 @@ export function evaluateLazyEntry<TEnv = any>(
|
|
|
232
196
|
deps.routesEntries.splice(insertIndex, 0, nestedEntry);
|
|
233
197
|
}
|
|
234
198
|
|
|
235
|
-
// Re-register route map for runtime reverse() usage
|
|
236
199
|
registerRouteMap(deps.mergedRouteMap);
|
|
237
200
|
}
|