@rangojs/router 0.0.0-experimental.0f44aca1
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 +5 -0
- package/README.md +899 -0
- package/dist/bin/rango.js +1601 -0
- package/dist/vite/index.js +5214 -0
- package/package.json +176 -0
- package/skills/breadcrumbs/SKILL.md +250 -0
- package/skills/cache-guide/SKILL.md +262 -0
- package/skills/caching/SKILL.md +220 -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 +645 -0
- package/src/browser/navigation-client.ts +215 -0
- package/src/browser/navigation-store.ts +806 -0
- package/src/browser/navigation-transaction.ts +295 -0
- package/src/browser/network-error-handler.ts +61 -0
- package/src/browser/partial-update.ts +550 -0
- package/src/browser/prefetch/cache.ts +146 -0
- package/src/browser/prefetch/fetch.ts +135 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +42 -0
- package/src/browser/prefetch/queue.ts +88 -0
- package/src/browser/rango-state.ts +112 -0
- package/src/browser/react/Link.tsx +360 -0
- package/src/browser/react/NavigationProvider.tsx +386 -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 +431 -0
- package/src/browser/scroll-restoration.ts +400 -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 +538 -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 +469 -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 +540 -0
- package/src/cache/cf/index.ts +25 -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 +43 -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 +275 -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 +158 -0
- package/src/router/handler-context.ts +451 -0
- package/src/router/intercept-resolution.ts +395 -0
- package/src/router/lazy-includes.ts +234 -0
- package/src/router/loader-resolution.ts +420 -0
- package/src/router/logging.ts +248 -0
- package/src/router/manifest.ts +267 -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 +192 -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 +748 -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 +316 -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 +1239 -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 +289 -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 +1002 -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 +235 -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 +914 -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 +102 -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 +110 -0
- package/src/vite/discovery/virtual-module-codegen.ts +203 -0
- package/src/vite/index.ts +16 -0
- package/src/vite/plugin-types.ts +131 -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 +365 -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 +254 -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 +510 -0
- package/src/vite/router-discovery.ts +785 -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
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Helpers for Segment Resolution
|
|
3
|
+
*
|
|
4
|
+
* Common utilities used by both fresh and revalidation resolution paths:
|
|
5
|
+
* - Handler result processing (Response vs ReactNode)
|
|
6
|
+
* - Static handler interception (build-time pre-rendered components)
|
|
7
|
+
* - Layout handler resolution with static fallback
|
|
8
|
+
* - Error boundary segment creation
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { ReactNode } from "react";
|
|
12
|
+
import { DataNotFoundError } from "../../errors";
|
|
13
|
+
import {
|
|
14
|
+
createErrorInfo,
|
|
15
|
+
createErrorSegment,
|
|
16
|
+
createNotFoundInfo,
|
|
17
|
+
createNotFoundSegment,
|
|
18
|
+
} from "../error-handling.js";
|
|
19
|
+
import { getRequestContext } from "../../server/request-context.js";
|
|
20
|
+
import { DefaultErrorFallback } from "../../default-error-boundary.js";
|
|
21
|
+
import type { EntryData } from "../../server/context";
|
|
22
|
+
import type { ResolvedSegment, ErrorInfo, HandlerContext } from "../../types";
|
|
23
|
+
import type { SegmentResolutionDeps } from "../types.js";
|
|
24
|
+
import { debugLog } from "../logging.js";
|
|
25
|
+
import { tryStaticLookup } from "./static-store.js";
|
|
26
|
+
import type { TelemetrySink } from "../telemetry.js";
|
|
27
|
+
import { resolveSink, safeEmit, getRequestId } from "../telemetry.js";
|
|
28
|
+
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
// Handler result processing
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Handle Response returns from handlers.
|
|
35
|
+
* When a handler returns a Response (e.g., redirect), throw it to trigger
|
|
36
|
+
* the short-circuit mechanism. Otherwise return the ReactNode.
|
|
37
|
+
*/
|
|
38
|
+
export function handleHandlerResult(
|
|
39
|
+
result: ReactNode | Response | Promise<ReactNode> | Promise<Response>,
|
|
40
|
+
): ReactNode {
|
|
41
|
+
if (result instanceof Response) {
|
|
42
|
+
throw result;
|
|
43
|
+
}
|
|
44
|
+
if (result instanceof Promise) {
|
|
45
|
+
return result.then((resolved) => {
|
|
46
|
+
if (resolved instanceof Response) {
|
|
47
|
+
throw resolved;
|
|
48
|
+
}
|
|
49
|
+
return resolved;
|
|
50
|
+
}) as ReactNode;
|
|
51
|
+
}
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
// Static handler interception
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Try to resolve a component from the build-time static store.
|
|
61
|
+
* Returns undefined synchronously when the entry is not a static prerender,
|
|
62
|
+
* avoiding unnecessary promise wrapping on the hot path.
|
|
63
|
+
*/
|
|
64
|
+
export function tryStaticHandler(
|
|
65
|
+
entry: EntryData,
|
|
66
|
+
segmentId: string,
|
|
67
|
+
): Promise<ReactNode | undefined> | undefined {
|
|
68
|
+
const entryAny = entry as any;
|
|
69
|
+
if (entryAny.isStaticPrerender && entryAny.staticHandlerId) {
|
|
70
|
+
return tryStaticLookup(entryAny.staticHandlerId, segmentId);
|
|
71
|
+
}
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Try to resolve a parallel slot component from the build-time static store.
|
|
77
|
+
* Returns undefined synchronously when no static handler ID exists for the slot.
|
|
78
|
+
*/
|
|
79
|
+
export function tryStaticSlot(
|
|
80
|
+
parallelEntry: EntryData,
|
|
81
|
+
slot: string,
|
|
82
|
+
segmentId: string,
|
|
83
|
+
): Promise<ReactNode | undefined> | undefined {
|
|
84
|
+
const slotStaticId = (parallelEntry as any).staticHandlerIds?.[slot];
|
|
85
|
+
if (slotStaticId) {
|
|
86
|
+
return tryStaticLookup(slotStaticId, segmentId);
|
|
87
|
+
}
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Resolve a layout or cache entry's handler component.
|
|
93
|
+
* Checks build-time static store first, then invokes the handler.
|
|
94
|
+
*/
|
|
95
|
+
export async function resolveLayoutComponent<TEnv>(
|
|
96
|
+
entry: EntryData,
|
|
97
|
+
context: HandlerContext<any, TEnv>,
|
|
98
|
+
): Promise<ReactNode> {
|
|
99
|
+
const component = await tryStaticHandler(entry, entry.shortCode);
|
|
100
|
+
if (component !== undefined) return component;
|
|
101
|
+
return typeof entry.handler === "function"
|
|
102
|
+
? handleHandlerResult(await entry.handler(context))
|
|
103
|
+
: (entry.handler as ReactNode);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ---------------------------------------------------------------------------
|
|
107
|
+
// Error boundary segment creation
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Context for error reporting in segment resolution.
|
|
112
|
+
* When provided, callOnError is invoked with this context.
|
|
113
|
+
*/
|
|
114
|
+
export interface ErrorReportContext {
|
|
115
|
+
request?: Request;
|
|
116
|
+
url?: URL;
|
|
117
|
+
routeKey?: string;
|
|
118
|
+
env?: any;
|
|
119
|
+
isPartial?: boolean;
|
|
120
|
+
requestStartTime?: number;
|
|
121
|
+
telemetry?: TelemetrySink;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Handle a caught error during segment resolution by creating an
|
|
126
|
+
* error or not-found segment with the nearest boundary.
|
|
127
|
+
*
|
|
128
|
+
* Called by resolveWithErrorBoundary to produce error/notFound segments.
|
|
129
|
+
*/
|
|
130
|
+
export function catchSegmentError<TEnv>(
|
|
131
|
+
error: unknown,
|
|
132
|
+
entry: EntryData,
|
|
133
|
+
params: Record<string, string>,
|
|
134
|
+
deps: SegmentResolutionDeps<TEnv>,
|
|
135
|
+
report?: ErrorReportContext,
|
|
136
|
+
pathname?: string,
|
|
137
|
+
): ResolvedSegment {
|
|
138
|
+
const reportError = (
|
|
139
|
+
handledByBoundary: boolean,
|
|
140
|
+
metadata?: Record<string, unknown>,
|
|
141
|
+
) => {
|
|
142
|
+
if (!report) return;
|
|
143
|
+
deps.callOnError(error, "handler", {
|
|
144
|
+
request: report.request as Request,
|
|
145
|
+
url: report.url,
|
|
146
|
+
routeKey: report.routeKey,
|
|
147
|
+
params,
|
|
148
|
+
segmentId: entry.shortCode,
|
|
149
|
+
segmentType: entry.type as any,
|
|
150
|
+
env: report.env,
|
|
151
|
+
isPartial: report.isPartial,
|
|
152
|
+
handledByBoundary,
|
|
153
|
+
metadata,
|
|
154
|
+
requestStartTime: report.requestStartTime,
|
|
155
|
+
});
|
|
156
|
+
if (report.telemetry) {
|
|
157
|
+
const errorObj =
|
|
158
|
+
error instanceof Error ? error : new Error(String(error));
|
|
159
|
+
safeEmit(resolveSink(report.telemetry), {
|
|
160
|
+
type: "handler.error",
|
|
161
|
+
timestamp: performance.now(),
|
|
162
|
+
requestId: report.request ? getRequestId(report.request) : undefined,
|
|
163
|
+
segmentId: entry.shortCode,
|
|
164
|
+
segmentType: entry.type,
|
|
165
|
+
error: errorObj,
|
|
166
|
+
handledByBoundary,
|
|
167
|
+
pathname,
|
|
168
|
+
routeKey: report.routeKey,
|
|
169
|
+
params,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const setResponseStatus = (status: number) => {
|
|
175
|
+
const reqCtx = getRequestContext();
|
|
176
|
+
if (reqCtx) {
|
|
177
|
+
reqCtx._setStatus(status);
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
if (error instanceof DataNotFoundError) {
|
|
182
|
+
const notFoundFallback = deps.findNearestNotFoundBoundary(entry);
|
|
183
|
+
|
|
184
|
+
if (notFoundFallback) {
|
|
185
|
+
const notFoundInfo = createNotFoundInfo(
|
|
186
|
+
error,
|
|
187
|
+
entry.shortCode,
|
|
188
|
+
entry.type,
|
|
189
|
+
pathname,
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
reportError(true, {
|
|
193
|
+
notFound: true,
|
|
194
|
+
message: notFoundInfo.message,
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
debugLog("segment", "notFound boundary handled error", {
|
|
198
|
+
segmentId: entry.shortCode,
|
|
199
|
+
message: notFoundInfo.message,
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
setResponseStatus(404);
|
|
203
|
+
|
|
204
|
+
return createNotFoundSegment(
|
|
205
|
+
notFoundInfo,
|
|
206
|
+
notFoundFallback,
|
|
207
|
+
entry,
|
|
208
|
+
params,
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const fallback = deps.findNearestErrorBoundary(entry);
|
|
214
|
+
const segmentType: ErrorInfo["segmentType"] = entry.type;
|
|
215
|
+
const errorInfo = createErrorInfo(error, entry.shortCode, segmentType);
|
|
216
|
+
const effectiveFallback = fallback ?? DefaultErrorFallback;
|
|
217
|
+
|
|
218
|
+
reportError(!!effectiveFallback);
|
|
219
|
+
|
|
220
|
+
debugLog("segment", "error boundary handled error", {
|
|
221
|
+
segmentId: entry.shortCode,
|
|
222
|
+
boundary: fallback ? "custom" : "default",
|
|
223
|
+
message: errorInfo.message,
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
setResponseStatus(500);
|
|
227
|
+
|
|
228
|
+
return createErrorSegment(errorInfo, effectiveFallback, entry, params);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Generic error boundary wrapper for segment resolution.
|
|
233
|
+
* Catches non-Response errors and produces an error/notFound segment
|
|
234
|
+
* via catchSegmentError. Response throws (e.g. redirects) are re-thrown.
|
|
235
|
+
*
|
|
236
|
+
* The caller provides a `wrapError` callback to shape the error segment
|
|
237
|
+
* into the expected return type (e.g. ResolvedSegment[] for the fresh
|
|
238
|
+
* path, or SegmentRevalidationResult for the revalidation path).
|
|
239
|
+
*/
|
|
240
|
+
export async function resolveWithErrorBoundary<TEnv, TResult>(
|
|
241
|
+
entry: EntryData,
|
|
242
|
+
params: Record<string, string>,
|
|
243
|
+
resolveFn: () => Promise<TResult>,
|
|
244
|
+
wrapError: (segment: ResolvedSegment) => TResult,
|
|
245
|
+
deps: SegmentResolutionDeps<TEnv>,
|
|
246
|
+
report?: ErrorReportContext,
|
|
247
|
+
pathname?: string,
|
|
248
|
+
): Promise<TResult> {
|
|
249
|
+
try {
|
|
250
|
+
return await resolveFn();
|
|
251
|
+
} catch (error) {
|
|
252
|
+
if (error instanceof Response) throw error;
|
|
253
|
+
const segment = catchSegmentError(
|
|
254
|
+
error,
|
|
255
|
+
entry,
|
|
256
|
+
params,
|
|
257
|
+
deps,
|
|
258
|
+
report,
|
|
259
|
+
pathname,
|
|
260
|
+
);
|
|
261
|
+
return wrapError(segment);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loader-Level Caching
|
|
3
|
+
*
|
|
4
|
+
* When a LoaderEntry has a cache config (set via loader(Fn, () => [cache({...})])),
|
|
5
|
+
* this module wraps the loader execution with cache lookup/store using the
|
|
6
|
+
* getItem()/setItem() methods on SegmentCacheStore.
|
|
7
|
+
*
|
|
8
|
+
* Cache key resolution (3-tier, matching CacheScope.resolveKey):
|
|
9
|
+
* 1. options.key(requestCtx) — full override
|
|
10
|
+
* 2. store.keyGenerator(requestCtx, defaultKey) — store-level modification
|
|
11
|
+
* 3. loader:{loaderId}:{pathname}:{sortedParams} — default
|
|
12
|
+
*
|
|
13
|
+
* Values are serialized via RSC Flight (serializeResult/deserializeResult),
|
|
14
|
+
* supporting ReactNode, Promises, null, and all RSC-serializable types.
|
|
15
|
+
*
|
|
16
|
+
* On hit: returns cached data directly, skips loader execution.
|
|
17
|
+
* On stale hit (SWR): returns stale data, schedules background revalidation.
|
|
18
|
+
* On miss: executes loader, schedules non-blocking cache write.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import type { LoaderEntry } from "../../server/context.js";
|
|
22
|
+
import type { HandlerContext } from "../../types.js";
|
|
23
|
+
import { INTERNAL_RANGO_DEBUG } from "../../internal-debug.js";
|
|
24
|
+
import { getRequestContext } from "../../server/request-context.js";
|
|
25
|
+
import { sortedRouteParams } from "../../cache/cache-key-utils.js";
|
|
26
|
+
import {
|
|
27
|
+
resolveTtl,
|
|
28
|
+
resolveSwrWindow,
|
|
29
|
+
resolveCacheKey,
|
|
30
|
+
resolveCacheStore,
|
|
31
|
+
DEFAULT_ROUTE_TTL,
|
|
32
|
+
} from "../../cache/cache-policy.js";
|
|
33
|
+
import { readThroughItem } from "../../cache/read-through-swr.js";
|
|
34
|
+
// Lazy-loaded to avoid pulling @vitejs/plugin-rsc/rsc into modules that
|
|
35
|
+
// import segment-resolution but never use loader caching.
|
|
36
|
+
let _serializeResult: typeof import("../../cache/segment-codec.js").serializeResult;
|
|
37
|
+
let _deserializeResult: typeof import("../../cache/segment-codec.js").deserializeResult;
|
|
38
|
+
async function getCodec() {
|
|
39
|
+
if (!_serializeResult) {
|
|
40
|
+
const mod = await import("../../cache/segment-codec.js");
|
|
41
|
+
_serializeResult = mod.serializeResult;
|
|
42
|
+
_deserializeResult = mod.deserializeResult;
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
serializeResult: _serializeResult,
|
|
46
|
+
deserializeResult: _deserializeResult,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function debugLoaderCacheLog(message: string): void {
|
|
51
|
+
if (INTERNAL_RANGO_DEBUG) {
|
|
52
|
+
console.log(message);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function getDefaultLoaderCacheKey(
|
|
57
|
+
loaderId: string,
|
|
58
|
+
pathname: string,
|
|
59
|
+
params: Record<string, string>,
|
|
60
|
+
): string {
|
|
61
|
+
const paramStr = sortedRouteParams(params);
|
|
62
|
+
const base = paramStr ? `${pathname}:${paramStr}` : pathname;
|
|
63
|
+
return `loader:${loaderId}:${base}`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Resolve cache key using the shared 3-tier priority.
|
|
68
|
+
*/
|
|
69
|
+
async function resolveLoaderKey(
|
|
70
|
+
loaderEntry: LoaderEntry,
|
|
71
|
+
store: import("../../cache/types.js").SegmentCacheStore,
|
|
72
|
+
loaderId: string,
|
|
73
|
+
pathname: string,
|
|
74
|
+
params: Record<string, string>,
|
|
75
|
+
): Promise<string> {
|
|
76
|
+
const options = loaderEntry.cache!.options;
|
|
77
|
+
const defaultKey = getDefaultLoaderCacheKey(loaderId, pathname, params);
|
|
78
|
+
if (options === false) return defaultKey;
|
|
79
|
+
return resolveCacheKey(options.key, store, defaultKey, "LoaderCache");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Resolve tags from cache options (static array or function).
|
|
84
|
+
* Fails open: a thrown tag callback falls back to no tags rather than
|
|
85
|
+
* aborting the request. Tags are additive metadata (not identity), so
|
|
86
|
+
* a missing tag does not cause cache collisions.
|
|
87
|
+
*/
|
|
88
|
+
function resolveTags(loaderEntry: LoaderEntry): string[] | undefined {
|
|
89
|
+
const options = loaderEntry.cache?.options;
|
|
90
|
+
if (!options || !options.tags) return undefined;
|
|
91
|
+
|
|
92
|
+
if (typeof options.tags === "function") {
|
|
93
|
+
const requestCtx = getRequestContext();
|
|
94
|
+
if (!requestCtx) return undefined;
|
|
95
|
+
try {
|
|
96
|
+
return options.tags(requestCtx);
|
|
97
|
+
} catch (error) {
|
|
98
|
+
console.error(
|
|
99
|
+
`[LoaderCache] Tags function failed, caching without tags:`,
|
|
100
|
+
error,
|
|
101
|
+
);
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return options.tags;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function getLoaderStore(
|
|
110
|
+
loaderEntry: LoaderEntry,
|
|
111
|
+
): import("../../cache/types.js").SegmentCacheStore | null {
|
|
112
|
+
const cacheConfig = loaderEntry.cache;
|
|
113
|
+
if (!cacheConfig || cacheConfig.options === false) return null;
|
|
114
|
+
return resolveCacheStore(cacheConfig.options.store);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Resolve loader data with optional caching.
|
|
119
|
+
*
|
|
120
|
+
* When the LoaderEntry has no cache config, delegates directly to ctx.use(loader).
|
|
121
|
+
* When cached, checks store first and stores on miss via waitUntil.
|
|
122
|
+
*/
|
|
123
|
+
export function resolveLoaderData<TEnv>(
|
|
124
|
+
loaderEntry: LoaderEntry,
|
|
125
|
+
ctx: HandlerContext<any, TEnv>,
|
|
126
|
+
pathname: string,
|
|
127
|
+
): Promise<any> {
|
|
128
|
+
const cacheConfig = loaderEntry.cache;
|
|
129
|
+
|
|
130
|
+
// No cache config or disabled — run fresh (zero overhead path)
|
|
131
|
+
if (!cacheConfig || cacheConfig.options === false) {
|
|
132
|
+
return ctx.use(loaderEntry.loader);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const store = getLoaderStore(loaderEntry);
|
|
136
|
+
if (!store?.getItem || !store?.setItem) {
|
|
137
|
+
return ctx.use(loaderEntry.loader);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Evaluate runtime condition if provided
|
|
141
|
+
const options = cacheConfig.options;
|
|
142
|
+
if (options.condition) {
|
|
143
|
+
const requestCtx = getRequestContext();
|
|
144
|
+
if (requestCtx && !options.condition(requestCtx)) {
|
|
145
|
+
return ctx.use(loaderEntry.loader);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const loaderId = loaderEntry.loader.$$id;
|
|
150
|
+
const ttl = resolveTtl(options.ttl, store.defaults, DEFAULT_ROUTE_TTL);
|
|
151
|
+
const swrWindow = resolveSwrWindow(options.swr, store.defaults);
|
|
152
|
+
const swr = swrWindow || undefined;
|
|
153
|
+
const tags = resolveTags(loaderEntry);
|
|
154
|
+
|
|
155
|
+
// Wrap ctx.use() so cache HIT primes the handler's memoization map.
|
|
156
|
+
// ctx.use() closes over the match context's loaderPromises (not request context's).
|
|
157
|
+
// By intercepting ctx.use(), we inject cached data into the correct map.
|
|
158
|
+
const originalUse = ctx.use;
|
|
159
|
+
const dataPromise = (async () => {
|
|
160
|
+
const codec = await getCodec();
|
|
161
|
+
const key = await resolveLoaderKey(
|
|
162
|
+
loaderEntry,
|
|
163
|
+
store,
|
|
164
|
+
loaderId,
|
|
165
|
+
pathname,
|
|
166
|
+
ctx.params,
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
return readThroughItem({
|
|
170
|
+
getItem: (k) => store.getItem!(k),
|
|
171
|
+
setItem: (k, v, o) => store.setItem!(k, v, o),
|
|
172
|
+
key,
|
|
173
|
+
execute: () => originalUse(loaderEntry.loader),
|
|
174
|
+
serialize: (d) => codec.serializeResult(d),
|
|
175
|
+
deserialize: (v) => codec.deserializeResult(v),
|
|
176
|
+
storeOptions: { ttl, swr, tags },
|
|
177
|
+
onHit: () => debugLoaderCacheLog(`[LoaderCache] HIT: ${key}`),
|
|
178
|
+
onStale: () => debugLoaderCacheLog(`[LoaderCache] STALE: ${key}`),
|
|
179
|
+
onMiss: () => debugLoaderCacheLog(`[LoaderCache] MISS: ${key}`),
|
|
180
|
+
onCached: () => debugLoaderCacheLog(`[LoaderCache] Cached: ${key}`),
|
|
181
|
+
host: getRequestContext(),
|
|
182
|
+
});
|
|
183
|
+
})();
|
|
184
|
+
|
|
185
|
+
// Temporarily replace ctx.use() so the handler's call returns cached data.
|
|
186
|
+
// This is needed because ctx.use() closes over the match context's loaderPromises
|
|
187
|
+
// map which is separate from the request context. By wrapping use(), we intercept
|
|
188
|
+
// the handler's call and return the shared dataPromise.
|
|
189
|
+
const wrappedUse = ((item: any) => {
|
|
190
|
+
if (item === loaderEntry.loader || item?.$$id === loaderId) {
|
|
191
|
+
return dataPromise;
|
|
192
|
+
}
|
|
193
|
+
return originalUse(item);
|
|
194
|
+
}) as typeof ctx.use;
|
|
195
|
+
ctx.use = wrappedUse;
|
|
196
|
+
|
|
197
|
+
return dataPromise;
|
|
198
|
+
}
|