@rangojs/router 0.0.0-experimental.002d056c
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 +9 -0
- package/README.md +899 -0
- package/dist/bin/rango.js +1606 -0
- package/dist/vite/index.js +5153 -0
- package/package.json +177 -0
- package/skills/breadcrumbs/SKILL.md +250 -0
- package/skills/cache-guide/SKILL.md +262 -0
- package/skills/caching/SKILL.md +253 -0
- package/skills/composability/SKILL.md +172 -0
- package/skills/debug-manifest/SKILL.md +112 -0
- package/skills/document-cache/SKILL.md +182 -0
- package/skills/fonts/SKILL.md +167 -0
- package/skills/hooks/SKILL.md +704 -0
- package/skills/host-router/SKILL.md +218 -0
- package/skills/intercept/SKILL.md +313 -0
- package/skills/layout/SKILL.md +310 -0
- package/skills/links/SKILL.md +239 -0
- package/skills/loader/SKILL.md +596 -0
- package/skills/middleware/SKILL.md +339 -0
- package/skills/mime-routes/SKILL.md +128 -0
- package/skills/parallel/SKILL.md +305 -0
- package/skills/prerender/SKILL.md +643 -0
- package/skills/rango/SKILL.md +118 -0
- package/skills/response-routes/SKILL.md +411 -0
- package/skills/route/SKILL.md +385 -0
- package/skills/router-setup/SKILL.md +439 -0
- package/skills/tailwind/SKILL.md +129 -0
- package/skills/theme/SKILL.md +79 -0
- package/skills/typesafety/SKILL.md +623 -0
- package/skills/use-cache/SKILL.md +324 -0
- package/src/__internal.ts +273 -0
- package/src/bin/rango.ts +321 -0
- package/src/browser/action-coordinator.ts +97 -0
- package/src/browser/action-response-classifier.ts +99 -0
- package/src/browser/event-controller.ts +899 -0
- package/src/browser/history-state.ts +80 -0
- package/src/browser/index.ts +18 -0
- package/src/browser/intercept-utils.ts +52 -0
- package/src/browser/link-interceptor.ts +141 -0
- package/src/browser/logging.ts +55 -0
- package/src/browser/merge-segment-loaders.ts +134 -0
- package/src/browser/navigation-bridge.ts +638 -0
- package/src/browser/navigation-client.ts +261 -0
- package/src/browser/navigation-store.ts +806 -0
- package/src/browser/navigation-transaction.ts +297 -0
- package/src/browser/network-error-handler.ts +61 -0
- package/src/browser/partial-update.ts +582 -0
- package/src/browser/prefetch/cache.ts +206 -0
- package/src/browser/prefetch/fetch.ts +145 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +48 -0
- package/src/browser/prefetch/queue.ts +128 -0
- package/src/browser/rango-state.ts +112 -0
- package/src/browser/react/Link.tsx +368 -0
- package/src/browser/react/NavigationProvider.tsx +413 -0
- package/src/browser/react/ScrollRestoration.tsx +94 -0
- package/src/browser/react/context.ts +59 -0
- package/src/browser/react/filter-segment-order.ts +11 -0
- package/src/browser/react/index.ts +52 -0
- package/src/browser/react/location-state-shared.ts +162 -0
- package/src/browser/react/location-state.ts +107 -0
- package/src/browser/react/mount-context.ts +37 -0
- package/src/browser/react/nonce-context.ts +23 -0
- package/src/browser/react/shallow-equal.ts +27 -0
- package/src/browser/react/use-action.ts +218 -0
- package/src/browser/react/use-client-cache.ts +58 -0
- package/src/browser/react/use-handle.ts +162 -0
- package/src/browser/react/use-href.tsx +40 -0
- package/src/browser/react/use-link-status.ts +135 -0
- package/src/browser/react/use-mount.ts +31 -0
- package/src/browser/react/use-navigation.ts +99 -0
- package/src/browser/react/use-params.ts +65 -0
- package/src/browser/react/use-pathname.ts +47 -0
- package/src/browser/react/use-router.ts +63 -0
- package/src/browser/react/use-search-params.ts +56 -0
- package/src/browser/react/use-segments.ts +171 -0
- package/src/browser/response-adapter.ts +73 -0
- package/src/browser/rsc-router.tsx +464 -0
- package/src/browser/scroll-restoration.ts +397 -0
- package/src/browser/segment-reconciler.ts +216 -0
- package/src/browser/segment-structure-assert.ts +83 -0
- package/src/browser/server-action-bridge.ts +667 -0
- package/src/browser/shallow.ts +40 -0
- package/src/browser/types.ts +547 -0
- package/src/browser/validate-redirect-origin.ts +29 -0
- package/src/build/generate-manifest.ts +438 -0
- package/src/build/generate-route-types.ts +36 -0
- package/src/build/index.ts +35 -0
- package/src/build/route-trie.ts +265 -0
- package/src/build/route-types/ast-helpers.ts +25 -0
- package/src/build/route-types/ast-route-extraction.ts +98 -0
- package/src/build/route-types/codegen.ts +102 -0
- package/src/build/route-types/include-resolution.ts +411 -0
- package/src/build/route-types/param-extraction.ts +48 -0
- package/src/build/route-types/per-module-writer.ts +128 -0
- package/src/build/route-types/router-processing.ts +479 -0
- package/src/build/route-types/scan-filter.ts +78 -0
- package/src/build/runtime-discovery.ts +231 -0
- package/src/cache/background-task.ts +34 -0
- package/src/cache/cache-key-utils.ts +44 -0
- package/src/cache/cache-policy.ts +125 -0
- package/src/cache/cache-runtime.ts +338 -0
- package/src/cache/cache-scope.ts +382 -0
- package/src/cache/cf/cf-cache-store.ts +982 -0
- package/src/cache/cf/index.ts +29 -0
- package/src/cache/document-cache.ts +369 -0
- package/src/cache/handle-capture.ts +81 -0
- package/src/cache/handle-snapshot.ts +41 -0
- package/src/cache/index.ts +44 -0
- package/src/cache/memory-segment-store.ts +328 -0
- package/src/cache/profile-registry.ts +73 -0
- package/src/cache/read-through-swr.ts +134 -0
- package/src/cache/segment-codec.ts +256 -0
- package/src/cache/taint.ts +98 -0
- package/src/cache/types.ts +342 -0
- package/src/client.rsc.tsx +85 -0
- package/src/client.tsx +601 -0
- package/src/component-utils.ts +76 -0
- package/src/components/DefaultDocument.tsx +27 -0
- package/src/context-var.ts +86 -0
- package/src/debug.ts +243 -0
- package/src/default-error-boundary.tsx +88 -0
- package/src/deps/browser.ts +8 -0
- package/src/deps/html-stream-client.ts +2 -0
- package/src/deps/html-stream-server.ts +2 -0
- package/src/deps/rsc.ts +10 -0
- package/src/deps/ssr.ts +2 -0
- package/src/errors.ts +365 -0
- package/src/handle.ts +135 -0
- package/src/handles/MetaTags.tsx +246 -0
- package/src/handles/breadcrumbs.ts +66 -0
- package/src/handles/index.ts +7 -0
- package/src/handles/meta.ts +264 -0
- package/src/host/cookie-handler.ts +165 -0
- package/src/host/errors.ts +97 -0
- package/src/host/index.ts +53 -0
- package/src/host/pattern-matcher.ts +214 -0
- package/src/host/router.ts +352 -0
- package/src/host/testing.ts +79 -0
- package/src/host/types.ts +146 -0
- package/src/host/utils.ts +25 -0
- package/src/href-client.ts +222 -0
- package/src/index.rsc.ts +233 -0
- package/src/index.ts +277 -0
- package/src/internal-debug.ts +11 -0
- package/src/loader.rsc.ts +89 -0
- package/src/loader.ts +64 -0
- package/src/network-error-thrower.tsx +23 -0
- package/src/outlet-context.ts +15 -0
- package/src/outlet-provider.tsx +45 -0
- package/src/prerender/param-hash.ts +37 -0
- package/src/prerender/store.ts +185 -0
- package/src/prerender.ts +463 -0
- package/src/reverse.ts +330 -0
- package/src/root-error-boundary.tsx +289 -0
- package/src/route-content-wrapper.tsx +196 -0
- package/src/route-definition/dsl-helpers.ts +934 -0
- package/src/route-definition/helper-factories.ts +200 -0
- package/src/route-definition/helpers-types.ts +430 -0
- package/src/route-definition/index.ts +52 -0
- package/src/route-definition/redirect.ts +93 -0
- package/src/route-definition.ts +1 -0
- package/src/route-map-builder.ts +281 -0
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +259 -0
- package/src/router/content-negotiation.ts +116 -0
- package/src/router/debug-manifest.ts +72 -0
- package/src/router/error-handling.ts +287 -0
- package/src/router/find-match.ts +160 -0
- package/src/router/handler-context.ts +451 -0
- package/src/router/intercept-resolution.ts +397 -0
- package/src/router/lazy-includes.ts +236 -0
- package/src/router/loader-resolution.ts +420 -0
- package/src/router/logging.ts +251 -0
- package/src/router/manifest.ts +269 -0
- package/src/router/match-api.ts +620 -0
- package/src/router/match-context.ts +266 -0
- package/src/router/match-handlers.ts +440 -0
- package/src/router/match-middleware/background-revalidation.ts +223 -0
- package/src/router/match-middleware/cache-lookup.ts +634 -0
- package/src/router/match-middleware/cache-store.ts +295 -0
- package/src/router/match-middleware/index.ts +81 -0
- package/src/router/match-middleware/intercept-resolution.ts +306 -0
- package/src/router/match-middleware/segment-resolution.ts +193 -0
- package/src/router/match-pipelines.ts +179 -0
- package/src/router/match-result.ts +219 -0
- package/src/router/metrics.ts +282 -0
- package/src/router/middleware-cookies.ts +55 -0
- package/src/router/middleware-types.ts +222 -0
- package/src/router/middleware.ts +749 -0
- package/src/router/pattern-matching.ts +563 -0
- package/src/router/prerender-match.ts +402 -0
- package/src/router/preview-match.ts +170 -0
- package/src/router/revalidation.ts +289 -0
- package/src/router/router-context.ts +320 -0
- package/src/router/router-interfaces.ts +452 -0
- package/src/router/router-options.ts +592 -0
- package/src/router/router-registry.ts +24 -0
- package/src/router/segment-resolution/fresh.ts +570 -0
- package/src/router/segment-resolution/helpers.ts +263 -0
- package/src/router/segment-resolution/loader-cache.ts +198 -0
- package/src/router/segment-resolution/revalidation.ts +1242 -0
- package/src/router/segment-resolution/static-store.ts +67 -0
- package/src/router/segment-resolution.ts +21 -0
- package/src/router/segment-wrappers.ts +291 -0
- package/src/router/telemetry-otel.ts +299 -0
- package/src/router/telemetry.ts +300 -0
- package/src/router/timeout.ts +148 -0
- package/src/router/trie-matching.ts +239 -0
- package/src/router/types.ts +170 -0
- package/src/router.ts +1006 -0
- package/src/rsc/handler-context.ts +45 -0
- package/src/rsc/handler.ts +1089 -0
- package/src/rsc/helpers.ts +198 -0
- package/src/rsc/index.ts +36 -0
- package/src/rsc/loader-fetch.ts +209 -0
- package/src/rsc/manifest-init.ts +86 -0
- package/src/rsc/nonce.ts +32 -0
- package/src/rsc/origin-guard.ts +141 -0
- package/src/rsc/progressive-enhancement.ts +379 -0
- package/src/rsc/response-error.ts +37 -0
- package/src/rsc/response-route-handler.ts +347 -0
- package/src/rsc/rsc-rendering.ts +237 -0
- package/src/rsc/runtime-warnings.ts +42 -0
- package/src/rsc/server-action.ts +348 -0
- package/src/rsc/ssr-setup.ts +128 -0
- package/src/rsc/types.ts +263 -0
- package/src/search-params.ts +230 -0
- package/src/segment-system.tsx +454 -0
- package/src/server/context.ts +591 -0
- package/src/server/cookie-store.ts +190 -0
- package/src/server/fetchable-loader-store.ts +37 -0
- package/src/server/handle-store.ts +308 -0
- package/src/server/loader-registry.ts +133 -0
- package/src/server/request-context.ts +920 -0
- package/src/server/root-layout.tsx +10 -0
- package/src/server/tsconfig.json +14 -0
- package/src/server.ts +51 -0
- package/src/ssr/index.tsx +365 -0
- package/src/static-handler.ts +114 -0
- package/src/theme/ThemeProvider.tsx +297 -0
- package/src/theme/ThemeScript.tsx +61 -0
- package/src/theme/constants.ts +62 -0
- package/src/theme/index.ts +48 -0
- package/src/theme/theme-context.ts +44 -0
- package/src/theme/theme-script.ts +155 -0
- package/src/theme/types.ts +182 -0
- package/src/theme/use-theme.ts +44 -0
- package/src/types/boundaries.ts +158 -0
- package/src/types/cache-types.ts +198 -0
- package/src/types/error-types.ts +192 -0
- package/src/types/global-namespace.ts +100 -0
- package/src/types/handler-context.ts +687 -0
- package/src/types/index.ts +88 -0
- package/src/types/loader-types.ts +183 -0
- package/src/types/route-config.ts +170 -0
- package/src/types/route-entry.ts +109 -0
- package/src/types/segments.ts +148 -0
- package/src/types.ts +1 -0
- package/src/urls/include-helper.ts +197 -0
- package/src/urls/index.ts +53 -0
- package/src/urls/path-helper-types.ts +339 -0
- package/src/urls/path-helper.ts +329 -0
- package/src/urls/pattern-types.ts +95 -0
- package/src/urls/response-types.ts +106 -0
- package/src/urls/type-extraction.ts +372 -0
- package/src/urls/urls-function.ts +98 -0
- package/src/urls.ts +1 -0
- package/src/use-loader.tsx +354 -0
- package/src/vite/discovery/bundle-postprocess.ts +184 -0
- package/src/vite/discovery/discover-routers.ts +344 -0
- package/src/vite/discovery/prerender-collection.ts +385 -0
- package/src/vite/discovery/route-types-writer.ts +258 -0
- package/src/vite/discovery/self-gen-tracking.ts +47 -0
- package/src/vite/discovery/state.ts +108 -0
- package/src/vite/discovery/virtual-module-codegen.ts +203 -0
- package/src/vite/index.ts +16 -0
- package/src/vite/plugin-types.ts +48 -0
- package/src/vite/plugins/cjs-to-esm.ts +93 -0
- package/src/vite/plugins/client-ref-dedup.ts +115 -0
- package/src/vite/plugins/client-ref-hashing.ts +105 -0
- package/src/vite/plugins/expose-action-id.ts +363 -0
- package/src/vite/plugins/expose-id-utils.ts +287 -0
- package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
- package/src/vite/plugins/expose-ids/handler-transform.ts +179 -0
- package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
- package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
- package/src/vite/plugins/expose-ids/types.ts +45 -0
- package/src/vite/plugins/expose-internal-ids.ts +569 -0
- package/src/vite/plugins/refresh-cmd.ts +65 -0
- package/src/vite/plugins/use-cache-transform.ts +323 -0
- package/src/vite/plugins/version-injector.ts +83 -0
- package/src/vite/plugins/version-plugin.ts +266 -0
- package/src/vite/plugins/version.d.ts +12 -0
- package/src/vite/plugins/virtual-entries.ts +123 -0
- package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
- package/src/vite/rango.ts +445 -0
- package/src/vite/router-discovery.ts +777 -0
- package/src/vite/utils/ast-handler-extract.ts +517 -0
- package/src/vite/utils/banner.ts +36 -0
- package/src/vite/utils/bundle-analysis.ts +137 -0
- package/src/vite/utils/manifest-utils.ts +70 -0
- package/src/vite/utils/package-resolution.ts +121 -0
- package/src/vite/utils/prerender-utils.ts +189 -0
- package/src/vite/utils/shared-utils.ts +169 -0
package/src/router.ts
ADDED
|
@@ -0,0 +1,1006 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import { createCacheScope } from "./cache/cache-scope.js";
|
|
3
|
+
import {
|
|
4
|
+
setCacheProfiles,
|
|
5
|
+
resolveCacheProfiles,
|
|
6
|
+
} from "./cache/profile-registry.js";
|
|
7
|
+
import { isCachedFunction } from "./cache/taint.js";
|
|
8
|
+
import { assertClientComponent } from "./component-utils.js";
|
|
9
|
+
import { DefaultDocument } from "./components/DefaultDocument.js";
|
|
10
|
+
import type { SerializedManifest } from "./debug.js";
|
|
11
|
+
import { createReverse, type ReverseFunction } from "./reverse.js";
|
|
12
|
+
import {
|
|
13
|
+
registerRouteMap,
|
|
14
|
+
getPrecomputedEntries,
|
|
15
|
+
getRouterManifest,
|
|
16
|
+
getRouterPrecomputedEntries,
|
|
17
|
+
ensureRouterManifest,
|
|
18
|
+
} from "./route-map-builder.js";
|
|
19
|
+
import MapRootLayout from "./server/root-layout.js";
|
|
20
|
+
import type { AllUseItems } from "./route-types.js";
|
|
21
|
+
import type { UrlPatterns } from "./urls.js";
|
|
22
|
+
import {
|
|
23
|
+
EntryData,
|
|
24
|
+
InterceptSelectorContext,
|
|
25
|
+
getContext,
|
|
26
|
+
RSCRouterContext,
|
|
27
|
+
type MetricsStore,
|
|
28
|
+
} from "./server/context";
|
|
29
|
+
import { createHandleStore, type HandleStore } from "./server/handle-store.js";
|
|
30
|
+
import {
|
|
31
|
+
getRequestContext,
|
|
32
|
+
_getRequestContext,
|
|
33
|
+
} from "./server/request-context.js";
|
|
34
|
+
import type {
|
|
35
|
+
ErrorPhase,
|
|
36
|
+
HandlerContext,
|
|
37
|
+
LoaderDataResult,
|
|
38
|
+
ResolvedRouteMap,
|
|
39
|
+
RouteEntry,
|
|
40
|
+
TrailingSlashMode,
|
|
41
|
+
} from "./types";
|
|
42
|
+
|
|
43
|
+
// Extracted router utilities
|
|
44
|
+
import {
|
|
45
|
+
createErrorInfo,
|
|
46
|
+
findNearestErrorBoundary as findErrorBoundary,
|
|
47
|
+
findNearestNotFoundBoundary as findNotFoundBoundary,
|
|
48
|
+
invokeOnError,
|
|
49
|
+
} from "./router/error-handling.js";
|
|
50
|
+
|
|
51
|
+
// Extracted module factories
|
|
52
|
+
import { createSegmentWrappers } from "./router/segment-wrappers.js";
|
|
53
|
+
import { createMatchHandlers } from "./router/match-handlers.js";
|
|
54
|
+
import { buildDebugManifest } from "./router/debug-manifest.js";
|
|
55
|
+
|
|
56
|
+
import type { SegmentResolutionDeps, MatchApiDeps } from "./router/types.js";
|
|
57
|
+
import { createHandlerContext } from "./router/handler-context.js";
|
|
58
|
+
import {
|
|
59
|
+
setupLoaderAccess,
|
|
60
|
+
setupLoaderAccessSilent,
|
|
61
|
+
wrapLoaderWithErrorHandling,
|
|
62
|
+
} from "./router/loader-resolution.js";
|
|
63
|
+
import { loadManifest } from "./router/manifest.js";
|
|
64
|
+
import { createMetricsStore } from "./router/metrics.js";
|
|
65
|
+
import {
|
|
66
|
+
parsePattern,
|
|
67
|
+
type MiddlewareEntry,
|
|
68
|
+
type MiddlewareFn,
|
|
69
|
+
} from "./router/middleware.js";
|
|
70
|
+
import {
|
|
71
|
+
extractStaticPrefix,
|
|
72
|
+
traverseBack,
|
|
73
|
+
} from "./router/pattern-matching.js";
|
|
74
|
+
import { resolveSink, safeEmit, getRequestId } from "./router/telemetry.js";
|
|
75
|
+
import { evaluateRevalidation } from "./router/revalidation.js";
|
|
76
|
+
import {
|
|
77
|
+
type RouterContext,
|
|
78
|
+
runWithRouterContext,
|
|
79
|
+
} from "./router/router-context.js";
|
|
80
|
+
import { resolveThemeConfig } from "./theme/constants.js";
|
|
81
|
+
import { resolveTimeouts } from "./router/timeout.js";
|
|
82
|
+
|
|
83
|
+
// Extracted content negotiation utilities
|
|
84
|
+
import { flattenNamedRoutes } from "./router/content-negotiation.js";
|
|
85
|
+
|
|
86
|
+
// Extracted router types and registry
|
|
87
|
+
import {
|
|
88
|
+
RSC_ROUTER_BRAND,
|
|
89
|
+
RouterRegistry,
|
|
90
|
+
nextRouterAutoId,
|
|
91
|
+
} from "./router/router-registry.js";
|
|
92
|
+
import type {
|
|
93
|
+
RSCRouterOptions,
|
|
94
|
+
RootLayoutProps,
|
|
95
|
+
} from "./router/router-options.js";
|
|
96
|
+
import type {
|
|
97
|
+
RSCRouter,
|
|
98
|
+
RSCRouterInternal,
|
|
99
|
+
RouterRequestInput,
|
|
100
|
+
} from "./router/router-interfaces.js";
|
|
101
|
+
|
|
102
|
+
// Extracted closure functions
|
|
103
|
+
import {
|
|
104
|
+
findLazyIncludes,
|
|
105
|
+
evaluateLazyEntry as _evaluateLazyEntry,
|
|
106
|
+
type LazyEvalDeps,
|
|
107
|
+
} from "./router/lazy-includes.js";
|
|
108
|
+
import { createFindMatch } from "./router/find-match.js";
|
|
109
|
+
import {
|
|
110
|
+
matchForPrerender as _matchForPrerender,
|
|
111
|
+
renderStaticSegment as _renderStaticSegment,
|
|
112
|
+
} from "./router/prerender-match.js";
|
|
113
|
+
|
|
114
|
+
// Re-export public types and values from extracted modules
|
|
115
|
+
export { RSC_ROUTER_BRAND, RouterRegistry } from "./router/router-registry.js";
|
|
116
|
+
export type {
|
|
117
|
+
RSCRouterOptions,
|
|
118
|
+
RootLayoutProps,
|
|
119
|
+
SSRStreamMode,
|
|
120
|
+
SSROptions,
|
|
121
|
+
ResolveStreamingContext,
|
|
122
|
+
} from "./router/router-options.js";
|
|
123
|
+
export type {
|
|
124
|
+
RSCRouter,
|
|
125
|
+
RSCRouterInternal,
|
|
126
|
+
RouterRequestInput,
|
|
127
|
+
} from "./router/router-interfaces.js";
|
|
128
|
+
export { toInternal } from "./router/router-interfaces.js";
|
|
129
|
+
|
|
130
|
+
export function createRouter<TEnv = any>(
|
|
131
|
+
options: RSCRouterOptions<TEnv> = {},
|
|
132
|
+
): RSCRouter<TEnv, {}> {
|
|
133
|
+
const {
|
|
134
|
+
id: userProvidedId,
|
|
135
|
+
$$id: injectedId,
|
|
136
|
+
debugPerformance = false,
|
|
137
|
+
document: documentOption,
|
|
138
|
+
defaultErrorBoundary,
|
|
139
|
+
defaultNotFoundBoundary,
|
|
140
|
+
notFound,
|
|
141
|
+
onError,
|
|
142
|
+
cache,
|
|
143
|
+
cacheProfiles: cacheProfilesOption,
|
|
144
|
+
theme: themeOption,
|
|
145
|
+
urls: urlsOption,
|
|
146
|
+
$$routeNames: staticRouteNames,
|
|
147
|
+
$$sourceFile: injectedSourceFile,
|
|
148
|
+
nonce,
|
|
149
|
+
version,
|
|
150
|
+
prefetchCacheTTL: prefetchCacheTTLOption,
|
|
151
|
+
warmup: warmupOption,
|
|
152
|
+
allowDebugManifest: allowDebugManifestOption = false,
|
|
153
|
+
telemetry: telemetrySink,
|
|
154
|
+
ssr: ssrOption,
|
|
155
|
+
timeout: timeoutShorthand,
|
|
156
|
+
timeouts: timeoutsOption,
|
|
157
|
+
onTimeout,
|
|
158
|
+
originCheck: originCheckOption,
|
|
159
|
+
} = options;
|
|
160
|
+
|
|
161
|
+
// Resolve telemetry sink (no-op when not configured)
|
|
162
|
+
const telemetry = resolveSink(telemetrySink);
|
|
163
|
+
|
|
164
|
+
// Resolve cache profiles: merge user config with guaranteed default profile.
|
|
165
|
+
// This resolved map is both stored on the router (for per-request context)
|
|
166
|
+
// and written to the global registry (for DSL-time cache("profileName")).
|
|
167
|
+
const resolvedCacheProfiles = resolveCacheProfiles(cacheProfilesOption);
|
|
168
|
+
setCacheProfiles(resolvedCacheProfiles);
|
|
169
|
+
|
|
170
|
+
// Source file: prefer Vite-injected path (zero cost), fall back to
|
|
171
|
+
// stack trace parsing for non-Vite environments (e.g. tests).
|
|
172
|
+
let __sourceFile: string | undefined = injectedSourceFile;
|
|
173
|
+
if (!__sourceFile) {
|
|
174
|
+
try {
|
|
175
|
+
const stack = new Error().stack;
|
|
176
|
+
if (stack) {
|
|
177
|
+
const lines = stack.split("\n");
|
|
178
|
+
for (const line of lines) {
|
|
179
|
+
const match = line.match(/\((.+?\.(ts|tsx|js|jsx)):\d+:\d+\)/);
|
|
180
|
+
if (
|
|
181
|
+
match &&
|
|
182
|
+
!match[1].endsWith("/router.ts") &&
|
|
183
|
+
!match[1].includes("@rangojs/router") &&
|
|
184
|
+
!match[1].includes("node_modules")
|
|
185
|
+
) {
|
|
186
|
+
__sourceFile = match[1].startsWith("file:")
|
|
187
|
+
? match[1].slice(5)
|
|
188
|
+
: match[1];
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
} catch {}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Router ID priority: explicit id > Vite-injected $$id > counter fallback.
|
|
197
|
+
// $$id is a hash of filename+line injected by the Vite transform at compile
|
|
198
|
+
// time, so it's stable across build/runtime regardless of module evaluation
|
|
199
|
+
// order (unlike the counter which depends on import order).
|
|
200
|
+
const routerId =
|
|
201
|
+
userProvidedId ?? injectedId ?? `router_${nextRouterAutoId()}`;
|
|
202
|
+
|
|
203
|
+
// Resolve prefetch cache TTL (default: 300 seconds / 5 minutes)
|
|
204
|
+
// Clamp to a non-negative integer for valid Cache-Control max-age.
|
|
205
|
+
const rawTTL =
|
|
206
|
+
prefetchCacheTTLOption !== undefined ? prefetchCacheTTLOption : 300;
|
|
207
|
+
const prefetchCacheTTLSeconds =
|
|
208
|
+
rawTTL === false ? 0 : Math.max(0, Math.floor(rawTTL));
|
|
209
|
+
const prefetchCacheTTL = prefetchCacheTTLSeconds * 1000;
|
|
210
|
+
const prefetchCacheControl: string | false =
|
|
211
|
+
prefetchCacheTTLSeconds === 0
|
|
212
|
+
? false
|
|
213
|
+
: `private, max-age=${prefetchCacheTTLSeconds}`;
|
|
214
|
+
|
|
215
|
+
// Resolve warmup enabled flag (default: true)
|
|
216
|
+
const warmupEnabled = warmupOption !== false;
|
|
217
|
+
|
|
218
|
+
// Resolve theme config (null if theme not enabled)
|
|
219
|
+
const resolvedThemeConfig = themeOption
|
|
220
|
+
? resolveThemeConfig(themeOption)
|
|
221
|
+
: null;
|
|
222
|
+
|
|
223
|
+
// Resolve timeout config (merge shorthand + structured)
|
|
224
|
+
const resolvedTimeouts = resolveTimeouts(timeoutShorthand, timeoutsOption);
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Wrapper for invokeOnError that binds the router's onError callback.
|
|
228
|
+
* Uses the shared utility from router/error-handling.ts for consistent behavior.
|
|
229
|
+
*
|
|
230
|
+
* Deduplicates via per-request WeakSet stored on the ALS request context.
|
|
231
|
+
* A closure-level WeakSet would silently swallow errors if the same object
|
|
232
|
+
* instance is thrown across separate requests (e.g. a singleton error).
|
|
233
|
+
*/
|
|
234
|
+
function callOnError(
|
|
235
|
+
error: unknown,
|
|
236
|
+
phase: ErrorPhase,
|
|
237
|
+
context: Parameters<typeof invokeOnError<TEnv>>[3],
|
|
238
|
+
): void {
|
|
239
|
+
if (error != null && typeof error === "object") {
|
|
240
|
+
const reportedErrors = _getRequestContext()?._reportedErrors;
|
|
241
|
+
if (reportedErrors) {
|
|
242
|
+
if (reportedErrors.has(error)) return;
|
|
243
|
+
reportedErrors.add(error);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
invokeOnError(onError, error, phase, context, "Router");
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Validate document is a client component
|
|
250
|
+
if (documentOption !== undefined) {
|
|
251
|
+
assertClientComponent(documentOption, "document");
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Use default document if none provided (keeps internal name as rootLayout)
|
|
255
|
+
const rootLayout = documentOption ?? DefaultDocument;
|
|
256
|
+
const routesEntries: RouteEntry<TEnv>[] = [];
|
|
257
|
+
let mountIndex = 0;
|
|
258
|
+
|
|
259
|
+
// Store reference to urlpatterns for runtime manifest generation
|
|
260
|
+
let storedUrlPatterns: UrlPatterns<TEnv, any> | null = null;
|
|
261
|
+
|
|
262
|
+
// Global middleware storage
|
|
263
|
+
const globalMiddleware: MiddlewareEntry<TEnv>[] = [];
|
|
264
|
+
|
|
265
|
+
// Helper to add middleware entry
|
|
266
|
+
function addMiddleware(
|
|
267
|
+
patternOrMiddleware: string | MiddlewareFn<TEnv>,
|
|
268
|
+
middleware?: MiddlewareFn<TEnv>,
|
|
269
|
+
mountPrefix: string | null = null,
|
|
270
|
+
): void {
|
|
271
|
+
let pattern: string | null = null;
|
|
272
|
+
let handler: MiddlewareFn<TEnv>;
|
|
273
|
+
|
|
274
|
+
if (typeof patternOrMiddleware === "string") {
|
|
275
|
+
// Pattern + middleware
|
|
276
|
+
pattern = patternOrMiddleware;
|
|
277
|
+
if (!middleware) {
|
|
278
|
+
throw new Error(
|
|
279
|
+
"Middleware function required when pattern is provided",
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
handler = middleware;
|
|
283
|
+
} else {
|
|
284
|
+
// Just middleware (no pattern)
|
|
285
|
+
handler = patternOrMiddleware;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Prevent "use cache" functions from being used as middleware.
|
|
289
|
+
// They return data/JSX and do not call next() — silently accepting
|
|
290
|
+
// them would be a confusing no-op.
|
|
291
|
+
if (isCachedFunction(handler)) {
|
|
292
|
+
throw new Error(
|
|
293
|
+
`A "use cache" function cannot be used as middleware. ` +
|
|
294
|
+
`Cached functions return data and do not participate in the ` +
|
|
295
|
+
`middleware chain. Remove the "use cache" directive or use a ` +
|
|
296
|
+
`regular middleware function instead.`,
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// If mount-scoped, prepend mount prefix to pattern
|
|
301
|
+
let fullPattern = pattern;
|
|
302
|
+
if (mountPrefix && pattern) {
|
|
303
|
+
// e.g., mountPrefix="/blog", pattern="/admin/*" → "/blog/admin/*"
|
|
304
|
+
fullPattern =
|
|
305
|
+
pattern === "*" ? `${mountPrefix}/*` : `${mountPrefix}${pattern}`;
|
|
306
|
+
} else if (mountPrefix && !pattern) {
|
|
307
|
+
// Mount-scoped middleware without pattern applies to all of mount
|
|
308
|
+
fullPattern = `${mountPrefix}/*`;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Parse pattern into regex
|
|
312
|
+
let regex: RegExp | null = null;
|
|
313
|
+
let paramNames: string[] = [];
|
|
314
|
+
if (fullPattern) {
|
|
315
|
+
const parsed = parsePattern(fullPattern);
|
|
316
|
+
regex = parsed.regex;
|
|
317
|
+
paramNames = parsed.paramNames;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
globalMiddleware.push({
|
|
321
|
+
pattern: fullPattern,
|
|
322
|
+
regex,
|
|
323
|
+
paramNames,
|
|
324
|
+
handler,
|
|
325
|
+
mountPrefix,
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Track all registered routes with their prefixes for reverse().
|
|
330
|
+
// Seed from injected NamedRoutes so reverse() works at module load time
|
|
331
|
+
// for routes that come from lazy includes.
|
|
332
|
+
const mergedRouteMap: Record<string, string> =
|
|
333
|
+
flattenNamedRoutes(staticRouteNames);
|
|
334
|
+
|
|
335
|
+
// Track names that came from the static seed so we can silently overwrite
|
|
336
|
+
// them during routes() registration. The gen file may be stale during HMR,
|
|
337
|
+
// so conflicts between seeded and runtime-registered values are expected.
|
|
338
|
+
const seededNames = new Set(Object.keys(mergedRouteMap));
|
|
339
|
+
|
|
340
|
+
// Lazy precomputed entries lookup: rebuilt when per-router data arrives.
|
|
341
|
+
// In production multi-router setups, per-router data is loaded lazily via
|
|
342
|
+
// ensureRouterManifest(). At createRouter() time the data isn't available yet,
|
|
343
|
+
// so we defer building the Map until first use and invalidate when the
|
|
344
|
+
// per-router source changes.
|
|
345
|
+
let precomputedByPrefix: Map<string, Record<string, string>> | null = null;
|
|
346
|
+
let precomputedSource:
|
|
347
|
+
| Array<{ staticPrefix: string; routes: Record<string, string> }>
|
|
348
|
+
| null
|
|
349
|
+
| undefined;
|
|
350
|
+
|
|
351
|
+
function getPrecomputedByPrefix(): Map<
|
|
352
|
+
string,
|
|
353
|
+
Record<string, string>
|
|
354
|
+
> | null {
|
|
355
|
+
const current =
|
|
356
|
+
getRouterPrecomputedEntries(routerId) ?? getPrecomputedEntries();
|
|
357
|
+
if (current !== precomputedSource) {
|
|
358
|
+
precomputedSource = current;
|
|
359
|
+
precomputedByPrefix = current
|
|
360
|
+
? new Map(current.map((e) => [e.staticPrefix, e.routes]))
|
|
361
|
+
: null;
|
|
362
|
+
}
|
|
363
|
+
return precomputedByPrefix;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Wrapper to pass debugPerformance to external createMetricsStore.
|
|
367
|
+
// Also checks per-request flag set by ctx.debugPerformance() in middleware.
|
|
368
|
+
const getMetricsStore = () => {
|
|
369
|
+
const reqCtx = _getRequestContext();
|
|
370
|
+
const enabled = debugPerformance || !!reqCtx?._debugPerformance;
|
|
371
|
+
if (!enabled) return undefined;
|
|
372
|
+
if (!reqCtx) {
|
|
373
|
+
return createMetricsStore(true);
|
|
374
|
+
}
|
|
375
|
+
reqCtx._metricsStore ??= createMetricsStore(true);
|
|
376
|
+
return reqCtx._metricsStore;
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
// Wrapper to pass defaults to error/notFound boundary finders
|
|
380
|
+
const findNearestErrorBoundary = (entry: EntryData | null) =>
|
|
381
|
+
findErrorBoundary(entry, defaultErrorBoundary);
|
|
382
|
+
|
|
383
|
+
const findNearestNotFoundBoundary = (entry: EntryData | null) =>
|
|
384
|
+
findNotFoundBoundary(entry, defaultNotFoundBoundary);
|
|
385
|
+
|
|
386
|
+
// Helper to get handleStore from request context
|
|
387
|
+
const getHandleStore = (): HandleStore | undefined => {
|
|
388
|
+
return _getRequestContext()?._handleStore;
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
// Track a pending handler promise (non-blocking).
|
|
392
|
+
// Attaches a side-effect .catch() to report streaming handler errors to onError
|
|
393
|
+
// without altering the rejection chain (React's streaming error boundary still handles it).
|
|
394
|
+
const trackHandler = <T>(
|
|
395
|
+
promise: Promise<T>,
|
|
396
|
+
errorContext?: {
|
|
397
|
+
segmentId?: string;
|
|
398
|
+
segmentType?: string;
|
|
399
|
+
},
|
|
400
|
+
): Promise<T> => {
|
|
401
|
+
const store = getHandleStore();
|
|
402
|
+
const tracked = store ? store.track(promise) : promise;
|
|
403
|
+
|
|
404
|
+
// Report streaming handler errors to onError as a side-effect.
|
|
405
|
+
// The rejection still propagates to the RSC stream for client error boundaries.
|
|
406
|
+
// Captures request context eagerly (closure) so the catch handler has full context.
|
|
407
|
+
const reqCtx = _getRequestContext();
|
|
408
|
+
if (reqCtx && onError) {
|
|
409
|
+
tracked.catch((error) => {
|
|
410
|
+
callOnError(error, "handler", {
|
|
411
|
+
request: reqCtx.request,
|
|
412
|
+
url: reqCtx.url,
|
|
413
|
+
routeKey: reqCtx._routeName,
|
|
414
|
+
params: reqCtx.params as Record<string, string>,
|
|
415
|
+
env: reqCtx.env as TEnv,
|
|
416
|
+
segmentId: errorContext?.segmentId,
|
|
417
|
+
segmentType: errorContext?.segmentType as any,
|
|
418
|
+
handledByBoundary: true,
|
|
419
|
+
});
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
return tracked;
|
|
424
|
+
};
|
|
425
|
+
|
|
426
|
+
// Wrapper for wrapLoaderWithErrorHandling that uses router's error boundary finder
|
|
427
|
+
// Includes onError callback for loader error notification and telemetry emission.
|
|
428
|
+
function wrapLoaderPromise<T>(
|
|
429
|
+
promise: Promise<T>,
|
|
430
|
+
entry: EntryData,
|
|
431
|
+
segmentId: string,
|
|
432
|
+
pathname: string,
|
|
433
|
+
errorContext?: {
|
|
434
|
+
request: Request;
|
|
435
|
+
url: URL;
|
|
436
|
+
routeKey?: string;
|
|
437
|
+
params?: Record<string, string>;
|
|
438
|
+
env?: TEnv;
|
|
439
|
+
isPartial?: boolean;
|
|
440
|
+
requestStartTime?: number;
|
|
441
|
+
},
|
|
442
|
+
): Promise<LoaderDataResult<T>> {
|
|
443
|
+
const loaderStart = telemetrySink ? performance.now() : 0;
|
|
444
|
+
const loaderRequestId = telemetrySink
|
|
445
|
+
? errorContext?.request
|
|
446
|
+
? getRequestId(errorContext.request)
|
|
447
|
+
: undefined
|
|
448
|
+
: undefined;
|
|
449
|
+
if (telemetrySink) {
|
|
450
|
+
const loaderName = segmentId.split(".").pop() || "unknown";
|
|
451
|
+
safeEmit(telemetry, {
|
|
452
|
+
type: "loader.start",
|
|
453
|
+
timestamp: loaderStart,
|
|
454
|
+
requestId: loaderRequestId,
|
|
455
|
+
segmentId,
|
|
456
|
+
loaderName,
|
|
457
|
+
pathname,
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const result = wrapLoaderWithErrorHandling(
|
|
462
|
+
promise,
|
|
463
|
+
entry,
|
|
464
|
+
segmentId,
|
|
465
|
+
pathname,
|
|
466
|
+
findNearestErrorBoundary,
|
|
467
|
+
createErrorInfo,
|
|
468
|
+
// Invoke onError when loader fails
|
|
469
|
+
errorContext
|
|
470
|
+
? (error, ctx) => {
|
|
471
|
+
callOnError(error, "loader", {
|
|
472
|
+
request: errorContext.request,
|
|
473
|
+
url: errorContext.url,
|
|
474
|
+
routeKey: errorContext.routeKey,
|
|
475
|
+
params: errorContext.params,
|
|
476
|
+
segmentId: ctx.segmentId,
|
|
477
|
+
segmentType: "loader",
|
|
478
|
+
loaderName: ctx.loaderName,
|
|
479
|
+
env: errorContext.env,
|
|
480
|
+
isPartial: errorContext.isPartial,
|
|
481
|
+
handledByBoundary: ctx.handledByBoundary,
|
|
482
|
+
requestStartTime: errorContext.requestStartTime,
|
|
483
|
+
});
|
|
484
|
+
if (telemetrySink) {
|
|
485
|
+
const errorObj =
|
|
486
|
+
error instanceof Error ? error : new Error(String(error));
|
|
487
|
+
safeEmit(telemetry, {
|
|
488
|
+
type: "loader.error",
|
|
489
|
+
timestamp: performance.now(),
|
|
490
|
+
requestId: loaderRequestId,
|
|
491
|
+
segmentId: ctx.segmentId,
|
|
492
|
+
loaderName: ctx.loaderName,
|
|
493
|
+
pathname,
|
|
494
|
+
error: errorObj,
|
|
495
|
+
handledByBoundary: ctx.handledByBoundary,
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
: undefined,
|
|
500
|
+
);
|
|
501
|
+
|
|
502
|
+
// Emit loader.end after the promise settles (fire-and-forget)
|
|
503
|
+
if (telemetrySink) {
|
|
504
|
+
const loaderName = segmentId.split(".").pop() || "unknown";
|
|
505
|
+
result.then((r) => {
|
|
506
|
+
safeEmit(telemetry, {
|
|
507
|
+
type: "loader.end",
|
|
508
|
+
timestamp: performance.now(),
|
|
509
|
+
requestId: loaderRequestId,
|
|
510
|
+
segmentId,
|
|
511
|
+
loaderName,
|
|
512
|
+
pathname,
|
|
513
|
+
durationMs: performance.now() - loaderStart,
|
|
514
|
+
ok: r.ok,
|
|
515
|
+
});
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
return result;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// Dependencies object for extracted segment resolution functions.
|
|
523
|
+
// Captures closure-bound helpers from createRouter.
|
|
524
|
+
const segmentDeps: SegmentResolutionDeps<TEnv> = {
|
|
525
|
+
wrapLoaderPromise,
|
|
526
|
+
trackHandler,
|
|
527
|
+
findNearestErrorBoundary,
|
|
528
|
+
findNearestNotFoundBoundary,
|
|
529
|
+
callOnError,
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
// Match API dependencies
|
|
533
|
+
const matchApiDeps: MatchApiDeps<TEnv> = {
|
|
534
|
+
findMatch: (pathname: string, ms?: any) => findMatch(pathname, ms),
|
|
535
|
+
getMetricsStore,
|
|
536
|
+
findInterceptForRoute: (routeKey, parentEntry, selectorContext, isAction) =>
|
|
537
|
+
findInterceptForRoute(routeKey, parentEntry, selectorContext, isAction),
|
|
538
|
+
callOnError,
|
|
539
|
+
findNearestErrorBoundary,
|
|
540
|
+
// Use per-router manifest when available, otherwise the static named map
|
|
541
|
+
// seeded into mergedRouteMap at router creation.
|
|
542
|
+
getRouteMap: () => getRouterManifest(routerId) ?? mergedRouteMap,
|
|
543
|
+
};
|
|
544
|
+
|
|
545
|
+
// Create segment resolution wrappers bound to segmentDeps
|
|
546
|
+
const {
|
|
547
|
+
resolveAllSegments,
|
|
548
|
+
resolveLoadersOnly,
|
|
549
|
+
resolveLoadersOnlyWithRevalidation,
|
|
550
|
+
buildEntryRevalidateMap,
|
|
551
|
+
resolveAllSegmentsWithRevalidation,
|
|
552
|
+
findInterceptForRoute,
|
|
553
|
+
resolveInterceptEntry,
|
|
554
|
+
resolveInterceptLoadersOnly,
|
|
555
|
+
} = createSegmentWrappers<TEnv>(segmentDeps);
|
|
556
|
+
|
|
557
|
+
// Lazy evaluation deps — captures closure state for extracted evaluateLazyEntry
|
|
558
|
+
const lazyEvalDeps: LazyEvalDeps<TEnv> = {
|
|
559
|
+
routesEntries,
|
|
560
|
+
mergedRouteMap,
|
|
561
|
+
nextMountIndex: () => mountIndex++,
|
|
562
|
+
getPrecomputedByPrefix,
|
|
563
|
+
routerId,
|
|
564
|
+
};
|
|
565
|
+
|
|
566
|
+
function evaluateLazyEntry(entry: RouteEntry<TEnv>): void {
|
|
567
|
+
_evaluateLazyEntry(entry, lazyEvalDeps);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// Create findMatch with single-entry cache, bound to router state
|
|
571
|
+
const findMatch = createFindMatch<TEnv>({
|
|
572
|
+
routesEntries,
|
|
573
|
+
evaluateLazyEntry,
|
|
574
|
+
routerId,
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
// Build a RouterContext once — shared by match, matchPartial, matchForPrerender
|
|
578
|
+
function buildRouterContext(): RouterContext<TEnv> {
|
|
579
|
+
return {
|
|
580
|
+
findMatch,
|
|
581
|
+
loadManifest,
|
|
582
|
+
traverseBack,
|
|
583
|
+
createHandlerContext,
|
|
584
|
+
setupLoaderAccess,
|
|
585
|
+
setupLoaderAccessSilent,
|
|
586
|
+
getContext,
|
|
587
|
+
getMetricsStore,
|
|
588
|
+
createCacheScope,
|
|
589
|
+
findInterceptForRoute,
|
|
590
|
+
resolveAllSegmentsWithRevalidation,
|
|
591
|
+
resolveInterceptEntry,
|
|
592
|
+
evaluateRevalidation,
|
|
593
|
+
getRequestContext,
|
|
594
|
+
resolveAllSegments,
|
|
595
|
+
createHandleStore,
|
|
596
|
+
buildEntryRevalidateMap,
|
|
597
|
+
resolveLoadersOnlyWithRevalidation,
|
|
598
|
+
resolveInterceptLoadersOnly,
|
|
599
|
+
resolveLoadersOnly,
|
|
600
|
+
telemetry: telemetrySink,
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// Prerender/static match deps (bind closure state for extracted functions)
|
|
605
|
+
const prerenderDeps = {
|
|
606
|
+
findMatch,
|
|
607
|
+
buildRouterContext,
|
|
608
|
+
mergedRouteMap,
|
|
609
|
+
resolveAllSegments,
|
|
610
|
+
};
|
|
611
|
+
|
|
612
|
+
async function matchForPrerender(
|
|
613
|
+
pathname: string,
|
|
614
|
+
params: Record<string, string>,
|
|
615
|
+
buildVars?: Record<string, any>,
|
|
616
|
+
isPassthroughRoute?: boolean,
|
|
617
|
+
) {
|
|
618
|
+
return _matchForPrerender(
|
|
619
|
+
pathname,
|
|
620
|
+
params,
|
|
621
|
+
prerenderDeps,
|
|
622
|
+
buildVars,
|
|
623
|
+
isPassthroughRoute,
|
|
624
|
+
);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
async function renderStaticSegment(
|
|
628
|
+
handler: Function,
|
|
629
|
+
handlerId: string,
|
|
630
|
+
routeName?: string,
|
|
631
|
+
) {
|
|
632
|
+
return _renderStaticSegment<TEnv>(
|
|
633
|
+
handler,
|
|
634
|
+
handlerId,
|
|
635
|
+
mergedRouteMap,
|
|
636
|
+
routeName,
|
|
637
|
+
);
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// Create match handler functions bound to router state
|
|
641
|
+
const matchHandlers = createMatchHandlers<TEnv>({
|
|
642
|
+
buildRouterContext,
|
|
643
|
+
callOnError,
|
|
644
|
+
matchApiDeps,
|
|
645
|
+
defaultErrorBoundary,
|
|
646
|
+
findMatch,
|
|
647
|
+
findInterceptForRoute,
|
|
648
|
+
telemetry: telemetrySink,
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
const { match, matchPartial, matchError, previewMatch } = matchHandlers;
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* Router instance
|
|
655
|
+
* The type system tracks accumulated routes through the builder chain
|
|
656
|
+
* Initial TRoutes is {} (empty) to avoid poisoning accumulated types with Record<string, string>
|
|
657
|
+
*/
|
|
658
|
+
const router: RSCRouterInternal<TEnv, {}> = {
|
|
659
|
+
__brand: RSC_ROUTER_BRAND,
|
|
660
|
+
id: routerId,
|
|
661
|
+
|
|
662
|
+
routes(urlPatterns: UrlPatterns<TEnv>): any {
|
|
663
|
+
// Store reference for runtime manifest generation
|
|
664
|
+
storedUrlPatterns = urlPatterns;
|
|
665
|
+
const currentMountIndex = mountIndex++;
|
|
666
|
+
|
|
667
|
+
// Create manifest and patterns maps for route registration
|
|
668
|
+
const manifest = new Map<string, EntryData>();
|
|
669
|
+
const routePatterns = new Map<string, string>();
|
|
670
|
+
const patternsByPrefix = new Map<string, Map<string, string>>();
|
|
671
|
+
const trailingSlashMap = new Map<string, TrailingSlashMode>();
|
|
672
|
+
|
|
673
|
+
// Run the handler once to extract patterns for route matching.
|
|
674
|
+
// Note: loadManifest will re-run the handler to register entries in its context.
|
|
675
|
+
// Lazy includes are detected in the return value and handled separately.
|
|
676
|
+
//
|
|
677
|
+
// Pattern extraction must use the same mountIndex and MapRootLayout root
|
|
678
|
+
// parent as loadManifest so that shortCodes produced here match those at
|
|
679
|
+
// runtime. include() captures the current parent and counters; if those
|
|
680
|
+
// shortCodes diverge from the runtime tree the segment reconciliation on
|
|
681
|
+
// the client will see a full mismatch and remount the entire page.
|
|
682
|
+
const syntheticMapRoot: EntryData = {
|
|
683
|
+
type: "layout",
|
|
684
|
+
id: `#synthetic-maproot-M${currentMountIndex}`,
|
|
685
|
+
shortCode: `M${currentMountIndex}L0`,
|
|
686
|
+
parent: null,
|
|
687
|
+
handler: MapRootLayout,
|
|
688
|
+
middleware: [],
|
|
689
|
+
revalidate: [],
|
|
690
|
+
errorBoundary: [],
|
|
691
|
+
notFoundBoundary: [],
|
|
692
|
+
layout: [],
|
|
693
|
+
parallel: [],
|
|
694
|
+
intercept: [],
|
|
695
|
+
loader: [],
|
|
696
|
+
};
|
|
697
|
+
|
|
698
|
+
let handlerResult: AllUseItems[] = [];
|
|
699
|
+
RSCRouterContext.run(
|
|
700
|
+
{
|
|
701
|
+
manifest,
|
|
702
|
+
patterns: routePatterns,
|
|
703
|
+
patternsByPrefix,
|
|
704
|
+
trailingSlash: trailingSlashMap,
|
|
705
|
+
namespace: "root",
|
|
706
|
+
parent: syntheticMapRoot,
|
|
707
|
+
counters: {},
|
|
708
|
+
mountIndex: currentMountIndex,
|
|
709
|
+
cacheProfiles: resolvedCacheProfiles,
|
|
710
|
+
},
|
|
711
|
+
() => {
|
|
712
|
+
handlerResult = urlPatterns.handler() as AllUseItems[];
|
|
713
|
+
},
|
|
714
|
+
);
|
|
715
|
+
|
|
716
|
+
// Convert trailingSlash map to object for the router
|
|
717
|
+
const trailingSlashConfig =
|
|
718
|
+
trailingSlashMap.size > 0
|
|
719
|
+
? Object.fromEntries(trailingSlashMap)
|
|
720
|
+
: undefined;
|
|
721
|
+
|
|
722
|
+
// Collect route keys that have prerender handlers (for non-trie match path)
|
|
723
|
+
let prerenderRouteKeys: Set<string> | undefined;
|
|
724
|
+
let passthroughRouteKeys: Set<string> | undefined;
|
|
725
|
+
for (const [name, entry] of manifest.entries()) {
|
|
726
|
+
if (entry.type === "route" && entry.isPrerender) {
|
|
727
|
+
if (!prerenderRouteKeys) prerenderRouteKeys = new Set();
|
|
728
|
+
prerenderRouteKeys.add(name);
|
|
729
|
+
if (entry.prerenderDef?.options?.passthrough === true) {
|
|
730
|
+
if (!passthroughRouteKeys) passthroughRouteKeys = new Set();
|
|
731
|
+
passthroughRouteKeys.add(name);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
// Create separate RouteEntry for each URL prefix group
|
|
737
|
+
// This enables prefix-based short-circuit optimization
|
|
738
|
+
if (patternsByPrefix.size > 0) {
|
|
739
|
+
for (const [prefix, prefixPatterns] of patternsByPrefix.entries()) {
|
|
740
|
+
const routesObject: Record<string, string> = {};
|
|
741
|
+
for (const [name, pattern] of prefixPatterns.entries()) {
|
|
742
|
+
routesObject[name] = pattern;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
routesEntries.push({
|
|
746
|
+
// prefix is "" because patterns already include the URL prefix
|
|
747
|
+
// (e.g., "/site/:locale/user1/:id" not just "/user1/:id")
|
|
748
|
+
prefix: "",
|
|
749
|
+
// staticPrefix is the actual prefix for short-circuit optimization
|
|
750
|
+
staticPrefix: extractStaticPrefix(prefix),
|
|
751
|
+
routes: routesObject as ResolvedRouteMap<any>,
|
|
752
|
+
trailingSlash: trailingSlashConfig,
|
|
753
|
+
handler: urlPatterns.handler,
|
|
754
|
+
mountIndex: currentMountIndex,
|
|
755
|
+
routerId,
|
|
756
|
+
cacheProfiles: resolvedCacheProfiles,
|
|
757
|
+
...(prerenderRouteKeys ? { prerenderRouteKeys } : {}),
|
|
758
|
+
...(passthroughRouteKeys ? { passthroughRouteKeys } : {}),
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
} else {
|
|
762
|
+
// Fallback: no prefix grouping, use flat patterns map
|
|
763
|
+
const routesObject: Record<string, string> = {};
|
|
764
|
+
for (const [name, pattern] of routePatterns.entries()) {
|
|
765
|
+
routesObject[name] = pattern;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
routesEntries.push({
|
|
769
|
+
prefix: "",
|
|
770
|
+
staticPrefix: "",
|
|
771
|
+
routes: routesObject as ResolvedRouteMap<any>,
|
|
772
|
+
trailingSlash: trailingSlashConfig,
|
|
773
|
+
handler: urlPatterns.handler,
|
|
774
|
+
mountIndex: currentMountIndex,
|
|
775
|
+
routerId,
|
|
776
|
+
cacheProfiles: resolvedCacheProfiles,
|
|
777
|
+
...(prerenderRouteKeys ? { prerenderRouteKeys } : {}),
|
|
778
|
+
...(passthroughRouteKeys ? { passthroughRouteKeys } : {}),
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
// Build route map from registered patterns
|
|
783
|
+
for (const [name, pattern] of routePatterns.entries()) {
|
|
784
|
+
// Runtime validation: warn if key already exists with different pattern.
|
|
785
|
+
// Skip warning for entries that came from the static seed — the gen file
|
|
786
|
+
// can be stale during HMR, so runtime registration is authoritative.
|
|
787
|
+
const existingPattern = mergedRouteMap[name];
|
|
788
|
+
if (
|
|
789
|
+
existingPattern !== undefined &&
|
|
790
|
+
existingPattern !== pattern &&
|
|
791
|
+
!seededNames.has(name)
|
|
792
|
+
) {
|
|
793
|
+
console.warn(
|
|
794
|
+
`[@rangojs/router] Route name conflict: "${name}" already maps to "${existingPattern}", ` +
|
|
795
|
+
`overwriting with "${pattern}". Use unique route names to avoid this.`,
|
|
796
|
+
);
|
|
797
|
+
}
|
|
798
|
+
mergedRouteMap[name] = pattern;
|
|
799
|
+
seededNames.delete(name);
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
// Detect lazy includes in handler result and create placeholder entries
|
|
803
|
+
const lazyIncludes = findLazyIncludes(handlerResult);
|
|
804
|
+
|
|
805
|
+
// Create placeholder RouteEntry for each lazy include
|
|
806
|
+
for (const lazyInclude of lazyIncludes) {
|
|
807
|
+
// Compute the full URL prefix (combining parent prefix if any)
|
|
808
|
+
const fullPrefix = lazyInclude.context.urlPrefix
|
|
809
|
+
? lazyInclude.context.urlPrefix + lazyInclude.prefix
|
|
810
|
+
: lazyInclude.prefix;
|
|
811
|
+
|
|
812
|
+
const lazyEntry: RouteEntry<TEnv> & { _lazyPrefix?: string } = {
|
|
813
|
+
prefix: "",
|
|
814
|
+
staticPrefix: extractStaticPrefix(fullPrefix),
|
|
815
|
+
routes: {} as ResolvedRouteMap<any>, // Empty until first match
|
|
816
|
+
trailingSlash: trailingSlashConfig,
|
|
817
|
+
handler: urlPatterns.handler,
|
|
818
|
+
mountIndex: mountIndex++,
|
|
819
|
+
routerId,
|
|
820
|
+
// Lazy evaluation fields
|
|
821
|
+
lazy: true,
|
|
822
|
+
lazyPatterns: lazyInclude.patterns,
|
|
823
|
+
lazyContext: lazyInclude.context,
|
|
824
|
+
lazyEvaluated: false,
|
|
825
|
+
_lazyPrefix: lazyInclude.prefix,
|
|
826
|
+
};
|
|
827
|
+
// Insert lazy entry before any entry whose staticPrefix is a
|
|
828
|
+
// prefix of (but shorter than) this lazy entry's staticPrefix.
|
|
829
|
+
// This ensures more specific lazy includes are matched before
|
|
830
|
+
// less specific eager entries (e.g., "/href/nested" before "/href/:id").
|
|
831
|
+
const lazyPrefix = lazyEntry.staticPrefix;
|
|
832
|
+
let insertIndex = routesEntries.length;
|
|
833
|
+
if (lazyPrefix) {
|
|
834
|
+
for (let i = 0; i < routesEntries.length; i++) {
|
|
835
|
+
const existing = routesEntries[i]!;
|
|
836
|
+
if (
|
|
837
|
+
lazyPrefix.startsWith(existing.staticPrefix) &&
|
|
838
|
+
lazyPrefix.length > existing.staticPrefix.length
|
|
839
|
+
) {
|
|
840
|
+
insertIndex = i;
|
|
841
|
+
break;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
routesEntries.splice(insertIndex, 0, lazyEntry);
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
// Auto-register route map for runtime reverse() usage
|
|
849
|
+
registerRouteMap(mergedRouteMap);
|
|
850
|
+
|
|
851
|
+
return router;
|
|
852
|
+
},
|
|
853
|
+
|
|
854
|
+
use(
|
|
855
|
+
patternOrMiddleware: string | MiddlewareFn<TEnv>,
|
|
856
|
+
middleware?: MiddlewareFn<TEnv>,
|
|
857
|
+
): any {
|
|
858
|
+
// Global middleware - no mount prefix
|
|
859
|
+
addMiddleware(patternOrMiddleware, middleware, null);
|
|
860
|
+
return router;
|
|
861
|
+
},
|
|
862
|
+
|
|
863
|
+
// Type-safe URL builder using merged route map
|
|
864
|
+
// Types are tracked through the builder chain via TRoutes parameter
|
|
865
|
+
// Seeded with static route names from the generated file (injected by Vite)
|
|
866
|
+
reverse: createReverse(mergedRouteMap),
|
|
867
|
+
|
|
868
|
+
// Expose accumulated route map for typeof extraction
|
|
869
|
+
// Returns {} initially, but builder chain accumulates specific route types
|
|
870
|
+
get routeMap() {
|
|
871
|
+
return mergedRouteMap as {};
|
|
872
|
+
},
|
|
873
|
+
|
|
874
|
+
// Expose rootLayout for renderSegments
|
|
875
|
+
rootLayout,
|
|
876
|
+
|
|
877
|
+
// Expose onError callback for error handling
|
|
878
|
+
onError,
|
|
879
|
+
|
|
880
|
+
// Expose cache configuration for RSC handler
|
|
881
|
+
cache,
|
|
882
|
+
|
|
883
|
+
// Expose notFound component for RSC handler
|
|
884
|
+
notFound,
|
|
885
|
+
|
|
886
|
+
// Expose resolved theme configuration for NavigationProvider and MetaTags
|
|
887
|
+
themeConfig: resolvedThemeConfig,
|
|
888
|
+
|
|
889
|
+
// Expose resolved cache profiles for per-request resolution
|
|
890
|
+
cacheProfiles: resolvedCacheProfiles,
|
|
891
|
+
|
|
892
|
+
// Expose prefetch cache settings
|
|
893
|
+
prefetchCacheControl,
|
|
894
|
+
prefetchCacheTTL,
|
|
895
|
+
|
|
896
|
+
// Expose warmup enabled flag for handler and client
|
|
897
|
+
warmupEnabled,
|
|
898
|
+
|
|
899
|
+
// Expose router-wide performance debugging for request-level metrics setup
|
|
900
|
+
debugPerformance,
|
|
901
|
+
|
|
902
|
+
// Expose debug manifest flag for handler
|
|
903
|
+
allowDebugManifest: allowDebugManifestOption,
|
|
904
|
+
|
|
905
|
+
// Expose origin check configuration for handler (default: enabled)
|
|
906
|
+
originCheck: originCheckOption ?? true,
|
|
907
|
+
|
|
908
|
+
// Expose SSR configuration for handler
|
|
909
|
+
ssr: ssrOption,
|
|
910
|
+
|
|
911
|
+
// Expose resolved timeouts for RSC handler
|
|
912
|
+
timeouts: resolvedTimeouts,
|
|
913
|
+
onTimeout,
|
|
914
|
+
|
|
915
|
+
// Expose global middleware for RSC handler
|
|
916
|
+
middleware: globalMiddleware,
|
|
917
|
+
|
|
918
|
+
match: (request: Request, input: RouterRequestInput<TEnv> = {}) => {
|
|
919
|
+
const env = input.env ?? ({} as TEnv);
|
|
920
|
+
return match(request, env);
|
|
921
|
+
},
|
|
922
|
+
matchForPrerender,
|
|
923
|
+
renderStaticSegment,
|
|
924
|
+
matchPartial: (
|
|
925
|
+
request: Request,
|
|
926
|
+
input: RouterRequestInput<TEnv> = {},
|
|
927
|
+
actionContext?: Parameters<typeof matchPartial>[2],
|
|
928
|
+
) => {
|
|
929
|
+
const env = input.env ?? ({} as TEnv);
|
|
930
|
+
return matchPartial(request, env, actionContext);
|
|
931
|
+
},
|
|
932
|
+
matchError: (
|
|
933
|
+
request: Request,
|
|
934
|
+
input: RouterRequestInput<TEnv> | undefined,
|
|
935
|
+
error: unknown,
|
|
936
|
+
segmentType?: Parameters<typeof matchError>[3],
|
|
937
|
+
) => {
|
|
938
|
+
const env = input?.env ?? ({} as TEnv);
|
|
939
|
+
return matchError(request, env, error, segmentType);
|
|
940
|
+
},
|
|
941
|
+
previewMatch: (request: Request, input: RouterRequestInput<TEnv> = {}) => {
|
|
942
|
+
const env = input.env ?? ({} as TEnv);
|
|
943
|
+
return previewMatch(request, env);
|
|
944
|
+
},
|
|
945
|
+
|
|
946
|
+
// Expose nonce provider for fetch
|
|
947
|
+
nonce,
|
|
948
|
+
|
|
949
|
+
// Expose version for fetch
|
|
950
|
+
version,
|
|
951
|
+
|
|
952
|
+
// Expose urlpatterns for runtime manifest generation
|
|
953
|
+
get urlpatterns() {
|
|
954
|
+
return storedUrlPatterns ?? undefined;
|
|
955
|
+
},
|
|
956
|
+
|
|
957
|
+
// Expose source file for per-router type generation
|
|
958
|
+
__sourceFile,
|
|
959
|
+
|
|
960
|
+
// RSC request handler (lazily created on first call)
|
|
961
|
+
fetch: (() => {
|
|
962
|
+
// Handler is created on first call and reused
|
|
963
|
+
let handler:
|
|
964
|
+
| ((
|
|
965
|
+
request: Request,
|
|
966
|
+
input: RouterRequestInput<TEnv>,
|
|
967
|
+
) => Promise<Response>)
|
|
968
|
+
| null = null;
|
|
969
|
+
|
|
970
|
+
return async (request: Request, input: RouterRequestInput<TEnv> = {}) => {
|
|
971
|
+
// Trigger lazy import of per-router manifest data before route matching.
|
|
972
|
+
// No-op if data is already loaded or no loader is registered.
|
|
973
|
+
await ensureRouterManifest(routerId);
|
|
974
|
+
if (!handler) {
|
|
975
|
+
// Lazy import deferred to first request to avoid dev mode issues
|
|
976
|
+
const { createRSCHandler } = await import("./rsc/handler.js");
|
|
977
|
+
// Cast: handler.ts still accepts (request, env) — will be updated
|
|
978
|
+
// separately to accept RouterRequestInput.
|
|
979
|
+
handler = createRSCHandler({
|
|
980
|
+
router: router as any,
|
|
981
|
+
cache,
|
|
982
|
+
nonce,
|
|
983
|
+
version,
|
|
984
|
+
}) as (
|
|
985
|
+
request: Request,
|
|
986
|
+
input: RouterRequestInput<TEnv>,
|
|
987
|
+
) => Promise<Response>;
|
|
988
|
+
}
|
|
989
|
+
return handler!(request, input);
|
|
990
|
+
};
|
|
991
|
+
})(),
|
|
992
|
+
|
|
993
|
+
// Debug utility for manifest inspection
|
|
994
|
+
debugManifest: () => buildDebugManifest<TEnv>(routesEntries),
|
|
995
|
+
};
|
|
996
|
+
|
|
997
|
+
// Register router in the global registry for build-time discovery
|
|
998
|
+
RouterRegistry.set(routerId, router);
|
|
999
|
+
|
|
1000
|
+
// If urls option was provided, auto-register them
|
|
1001
|
+
if (urlsOption) {
|
|
1002
|
+
return router.routes(urlsOption) as RSCRouter<TEnv, {}>;
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
return router;
|
|
1006
|
+
}
|