@rangojs/router 0.0.0-experimental.5 → 0.0.0-experimental.51
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 +884 -4
- package/dist/bin/rango.js +1606 -0
- package/dist/vite/index.js +4567 -769
- package/package.json +77 -58
- package/skills/breadcrumbs/SKILL.md +250 -0
- package/skills/cache-guide/SKILL.md +294 -0
- package/skills/caching/SKILL.md +93 -23
- package/skills/composability/SKILL.md +172 -0
- package/skills/debug-manifest/SKILL.md +12 -8
- package/skills/document-cache/SKILL.md +18 -16
- package/skills/fonts/SKILL.md +167 -0
- package/skills/hooks/SKILL.md +334 -72
- package/skills/host-router/SKILL.md +218 -0
- package/skills/intercept/SKILL.md +131 -8
- package/skills/layout/SKILL.md +100 -3
- package/skills/links/SKILL.md +89 -30
- package/skills/loader/SKILL.md +403 -43
- package/skills/middleware/SKILL.md +171 -34
- package/skills/mime-routes/SKILL.md +128 -0
- package/skills/parallel/SKILL.md +204 -1
- package/skills/prerender/SKILL.md +643 -0
- package/skills/rango/SKILL.md +85 -16
- package/skills/response-routes/SKILL.md +411 -0
- package/skills/route/SKILL.md +257 -14
- package/skills/router-setup/SKILL.md +123 -30
- package/skills/tailwind/SKILL.md +129 -0
- package/skills/theme/SKILL.md +9 -8
- package/skills/typesafety/SKILL.md +328 -89
- package/skills/use-cache/SKILL.md +324 -0
- package/src/__internal.ts +102 -4
- 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 +92 -64
- package/src/browser/history-state.ts +80 -0
- package/src/browser/intercept-utils.ts +52 -0
- package/src/browser/link-interceptor.ts +24 -4
- package/src/browser/logging.ts +55 -0
- package/src/browser/merge-segment-loaders.ts +20 -12
- package/src/browser/navigation-bridge.ts +282 -557
- package/src/browser/navigation-client.ts +157 -71
- package/src/browser/navigation-store.ts +33 -50
- package/src/browser/navigation-transaction.ts +297 -0
- package/src/browser/network-error-handler.ts +61 -0
- package/src/browser/partial-update.ts +303 -310
- package/src/browser/prefetch/cache.ts +206 -0
- package/src/browser/prefetch/fetch.ts +144 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +48 -0
- package/src/browser/prefetch/queue.ts +144 -0
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +112 -0
- package/src/browser/react/Link.tsx +193 -73
- package/src/browser/react/NavigationProvider.tsx +160 -13
- package/src/browser/react/context.ts +6 -0
- package/src/browser/react/filter-segment-order.ts +11 -0
- package/src/browser/react/index.ts +12 -12
- package/src/browser/react/location-state-shared.ts +95 -53
- package/src/browser/react/location-state.ts +60 -15
- package/src/browser/react/mount-context.ts +24 -1
- 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 +29 -51
- package/src/browser/react/use-client-cache.ts +5 -3
- package/src/browser/react/use-handle.ts +32 -79
- package/src/browser/react/use-href.tsx +2 -2
- package/src/browser/react/use-link-status.ts +6 -5
- package/src/browser/react/use-navigation.ts +22 -63
- 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 +80 -97
- package/src/browser/response-adapter.ts +73 -0
- package/src/browser/rsc-router.tsx +188 -55
- package/src/browser/scroll-restoration.ts +117 -44
- package/src/browser/segment-reconciler.ts +221 -0
- package/src/browser/segment-structure-assert.ts +16 -0
- package/src/browser/server-action-bridge.ts +504 -599
- package/src/browser/shallow.ts +6 -1
- package/src/browser/types.ts +118 -47
- package/src/browser/validate-redirect-origin.ts +29 -0
- package/src/build/generate-manifest.ts +235 -24
- package/src/build/generate-route-types.ts +36 -0
- package/src/build/index.ts +13 -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 +342 -0
- package/src/cache/cache-scope.ts +167 -309
- package/src/cache/cf/cf-cache-store.ts +571 -17
- package/src/cache/cf/index.ts +13 -3
- package/src/cache/document-cache.ts +116 -77
- package/src/cache/handle-capture.ts +81 -0
- package/src/cache/handle-snapshot.ts +41 -0
- package/src/cache/index.ts +1 -15
- package/src/cache/memory-segment-store.ts +191 -13
- 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 +153 -0
- package/src/cache/types.ts +72 -122
- package/src/client.rsc.tsx +3 -1
- package/src/client.tsx +106 -126
- package/src/component-utils.ts +4 -4
- package/src/components/DefaultDocument.tsx +5 -1
- package/src/context-var.ts +86 -0
- package/src/debug.ts +19 -9
- package/src/errors.ts +108 -2
- package/src/handle.ts +15 -29
- package/src/handles/MetaTags.tsx +73 -20
- package/src/handles/breadcrumbs.ts +66 -0
- package/src/handles/index.ts +1 -0
- package/src/handles/meta.ts +30 -13
- 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 +119 -29
- package/src/index.rsc.ts +153 -19
- package/src/index.ts +211 -30
- package/src/internal-debug.ts +11 -0
- package/src/loader.rsc.ts +26 -147
- package/src/loader.ts +27 -10
- package/src/network-error-thrower.tsx +3 -1
- 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 +41 -29
- package/src/route-content-wrapper.tsx +7 -4
- package/src/route-definition/dsl-helpers.ts +959 -0
- package/src/route-definition/helper-factories.ts +200 -0
- package/src/route-definition/helpers-types.ts +431 -0
- package/src/route-definition/index.ts +52 -0
- package/src/route-definition/redirect.ts +93 -0
- package/src/route-definition.ts +1 -1428
- package/src/route-map-builder.ts +217 -123
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +59 -8
- package/src/router/content-negotiation.ts +116 -0
- package/src/router/debug-manifest.ts +72 -0
- package/src/router/error-handling.ts +9 -9
- package/src/router/find-match.ts +160 -0
- package/src/router/handler-context.ts +400 -84
- package/src/router/intercept-resolution.ts +397 -0
- package/src/router/lazy-includes.ts +237 -0
- package/src/router/loader-resolution.ts +222 -123
- package/src/router/logging.ts +251 -0
- package/src/router/manifest.ts +154 -35
- package/src/router/match-api.ts +620 -0
- package/src/router/match-context.ts +5 -3
- package/src/router/match-handlers.ts +440 -0
- package/src/router/match-middleware/background-revalidation.ts +108 -93
- package/src/router/match-middleware/cache-lookup.ts +440 -10
- package/src/router/match-middleware/cache-store.ts +98 -26
- package/src/router/match-middleware/intercept-resolution.ts +57 -17
- package/src/router/match-middleware/segment-resolution.ts +27 -6
- package/src/router/match-pipelines.ts +10 -45
- package/src/router/match-result.ts +55 -33
- package/src/router/metrics.ts +240 -15
- package/src/router/middleware-cookies.ts +55 -0
- package/src/router/middleware-types.ts +226 -0
- package/src/router/middleware.ts +327 -369
- package/src/router/pattern-matching.ts +211 -43
- package/src/router/prerender-match.ts +402 -0
- package/src/router/preview-match.ts +170 -0
- package/src/router/revalidation.ts +137 -38
- package/src/router/router-context.ts +41 -21
- 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 +683 -0
- package/src/router/segment-resolution/helpers.ts +263 -0
- package/src/router/segment-resolution/loader-cache.ts +199 -0
- package/src/router/segment-resolution/revalidation.ts +1301 -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 +77 -3
- package/src/router.ts +665 -4182
- package/src/rsc/handler-context.ts +45 -0
- package/src/rsc/handler.ts +764 -754
- package/src/rsc/helpers.ts +140 -6
- package/src/rsc/index.ts +0 -20
- package/src/rsc/loader-fetch.ts +209 -0
- package/src/rsc/manifest-init.ts +86 -0
- package/src/rsc/nonce.ts +14 -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 +38 -11
- package/src/search-params.ts +230 -0
- package/src/segment-system.tsx +172 -21
- package/src/server/context.ts +278 -58
- package/src/server/cookie-store.ts +190 -0
- package/src/server/fetchable-loader-store.ts +37 -0
- package/src/server/handle-store.ts +94 -15
- package/src/server/loader-registry.ts +15 -56
- package/src/server/request-context.ts +474 -74
- package/src/server.ts +35 -128
- package/src/ssr/index.tsx +101 -31
- package/src/static-handler.ts +114 -0
- package/src/theme/ThemeProvider.tsx +21 -15
- package/src/theme/ThemeScript.tsx +5 -5
- package/src/theme/constants.ts +5 -2
- package/src/theme/index.ts +4 -14
- package/src/theme/theme-context.ts +4 -30
- package/src/theme/theme-script.ts +21 -18
- 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 +777 -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 +150 -0
- package/src/types.ts +1 -1623
- 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 -802
- package/src/use-loader.tsx +85 -77
- 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 +11 -782
- 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/{expose-action-id.ts → plugins/expose-action-id.ts} +72 -53
- 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/{virtual-entries.ts → plugins/virtual-entries.ts} +27 -16
- 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/{package-resolution.ts → utils/package-resolution.ts} +25 -29
- package/src/vite/utils/prerender-utils.ts +189 -0
- package/src/vite/utils/shared-utils.ts +169 -0
- package/CLAUDE.md +0 -43
- package/src/browser/lru-cache.ts +0 -69
- package/src/browser/request-controller.ts +0 -164
- package/src/cache/memory-store.ts +0 -253
- package/src/href-context.ts +0 -33
- package/src/href.ts +0 -255
- package/src/server/route-manifest-cache.ts +0 -173
- package/src/vite/expose-handle-id.ts +0 -209
- package/src/vite/expose-loader-id.ts +0 -426
- package/src/vite/expose-location-state-id.ts +0 -177
- package/src/warmup/connection-warmup.tsx +0 -94
- /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import type { ReactNode } from "react";
|
|
8
8
|
import { track } from "../server/context";
|
|
9
9
|
import type { EntryData } from "../server/context";
|
|
10
|
+
import { contextGet } from "../context-var.js";
|
|
10
11
|
import type {
|
|
11
12
|
ResolvedSegment,
|
|
12
13
|
HandlerContext,
|
|
@@ -21,8 +22,9 @@ import type {
|
|
|
21
22
|
import type { LoaderRevalidationResult, ActionContext } from "./types";
|
|
22
23
|
import { isHandle, type Handle } from "../handle.js";
|
|
23
24
|
import type { HandleStore } from "../server/handle-store.js";
|
|
24
|
-
import { getFetchableLoader } from "../loader.
|
|
25
|
-
import {
|
|
25
|
+
import { getFetchableLoader } from "../server/fetchable-loader-store.js";
|
|
26
|
+
import { _getRequestContext } from "../server/request-context.js";
|
|
27
|
+
import { debugLog } from "./logging.js";
|
|
26
28
|
|
|
27
29
|
/**
|
|
28
30
|
* Internal callback signature for loader error notifications.
|
|
@@ -35,7 +37,7 @@ export type LoaderErrorCallback = (
|
|
|
35
37
|
segmentId: string;
|
|
36
38
|
loaderName: string;
|
|
37
39
|
handledByBoundary: boolean;
|
|
38
|
-
}
|
|
40
|
+
},
|
|
39
41
|
) => void;
|
|
40
42
|
|
|
41
43
|
/**
|
|
@@ -54,14 +56,14 @@ export function wrapLoaderWithErrorHandling<T>(
|
|
|
54
56
|
segmentId: string,
|
|
55
57
|
pathname: string,
|
|
56
58
|
findNearestErrorBoundary: (
|
|
57
|
-
entry: EntryData | null
|
|
59
|
+
entry: EntryData | null,
|
|
58
60
|
) => ReactNode | ErrorBoundaryHandler | null,
|
|
59
61
|
createErrorInfo: (
|
|
60
62
|
error: unknown,
|
|
61
63
|
segmentId: string,
|
|
62
|
-
segmentType: ErrorInfo["segmentType"]
|
|
64
|
+
segmentType: ErrorInfo["segmentType"],
|
|
63
65
|
) => ErrorInfo,
|
|
64
|
-
onError?: LoaderErrorCallback
|
|
66
|
+
onError?: LoaderErrorCallback,
|
|
65
67
|
): Promise<LoaderDataResult<T>> {
|
|
66
68
|
// Extract loader name from segmentId (format: "M1L0D0.loaderName")
|
|
67
69
|
const loaderName = segmentId.split(".").pop() || "unknown";
|
|
@@ -72,7 +74,7 @@ export function wrapLoaderWithErrorHandling<T>(
|
|
|
72
74
|
__loaderResult: true,
|
|
73
75
|
ok: true,
|
|
74
76
|
data,
|
|
75
|
-
})
|
|
77
|
+
}),
|
|
76
78
|
)
|
|
77
79
|
.catch((error): LoaderDataResult<T> => {
|
|
78
80
|
// Find nearest error boundary
|
|
@@ -111,10 +113,10 @@ export function wrapLoaderWithErrorHandling<T>(
|
|
|
111
113
|
renderedFallback = fallback;
|
|
112
114
|
}
|
|
113
115
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
errorInfo.message
|
|
117
|
-
);
|
|
116
|
+
debugLog("loader", "loader error wrapped with boundary fallback", {
|
|
117
|
+
segmentId,
|
|
118
|
+
message: errorInfo.message,
|
|
119
|
+
});
|
|
118
120
|
|
|
119
121
|
return {
|
|
120
122
|
__loaderResult: true,
|
|
@@ -126,61 +128,103 @@ export function wrapLoaderWithErrorHandling<T>(
|
|
|
126
128
|
}
|
|
127
129
|
|
|
128
130
|
/**
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
* For handles: Returns a push function bound to the current segment.
|
|
131
|
+
* Detect cycles in the loader dependency graph using DFS from a given node.
|
|
132
|
+
* Returns the cycle path (array of loader IDs forming the cycle) if one exists,
|
|
133
|
+
* or null if no cycle is found.
|
|
133
134
|
*/
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
135
|
+
function detectLoaderCycle(
|
|
136
|
+
from: string,
|
|
137
|
+
to: string,
|
|
138
|
+
dependsOn: Map<string, Set<string>>,
|
|
139
|
+
): string[] | null {
|
|
140
|
+
// If `to` can reach `from` via the dependency graph, adding the edge
|
|
141
|
+
// from -> to creates a cycle. We search from `to` looking for `from`.
|
|
142
|
+
const visited = new Set<string>();
|
|
143
|
+
const path: string[] = [from, to];
|
|
144
|
+
|
|
145
|
+
function dfs(current: string): string[] | null {
|
|
146
|
+
if (current === from) {
|
|
147
|
+
// Found a cycle: return the path leading back to `from`
|
|
148
|
+
return path;
|
|
149
|
+
}
|
|
150
|
+
if (visited.has(current)) return null;
|
|
151
|
+
visited.add(current);
|
|
142
152
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
// Handle case: return a push function
|
|
146
|
-
if (isHandle(item)) {
|
|
147
|
-
const handle = item;
|
|
148
|
-
const store = getHandleStore();
|
|
149
|
-
const segmentId = (ctx as InternalHandlerContext)._currentSegmentId;
|
|
153
|
+
const deps = dependsOn.get(current);
|
|
154
|
+
if (!deps) return null;
|
|
150
155
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
156
|
+
for (const dep of deps) {
|
|
157
|
+
path.push(dep);
|
|
158
|
+
const cycle = dfs(dep);
|
|
159
|
+
if (cycle) return cycle;
|
|
160
|
+
path.pop();
|
|
161
|
+
}
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
157
164
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
// Promises are pushed directly - RSC will serialize and stream them
|
|
161
|
-
return (dataOrFn: unknown | Promise<unknown> | (() => Promise<unknown>)) => {
|
|
162
|
-
if (!store) return;
|
|
165
|
+
return dfs(to);
|
|
166
|
+
}
|
|
163
167
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
+
/**
|
|
169
|
+
* Creates a memoizing loader executor with cycle detection.
|
|
170
|
+
* Shared by setupLoaderAccess and setupLoaderAccessSilent; only the handle
|
|
171
|
+
* branch differs between the two, so only the loader logic is extracted here.
|
|
172
|
+
*
|
|
173
|
+
* Returns a useLoader(loader, callerLoaderId) function that:
|
|
174
|
+
* - Tracks dependency edges between loaders for cycle detection
|
|
175
|
+
* - Throws immediately (synchronously inside an async fn) on circular deps
|
|
176
|
+
* - Memoizes each loader's promise so it runs at most once per request
|
|
177
|
+
*/
|
|
178
|
+
function createLoaderExecutor<TEnv>(
|
|
179
|
+
ctx: HandlerContext<any, TEnv>,
|
|
180
|
+
loaderPromises: Map<string, Promise<any>>,
|
|
181
|
+
): (
|
|
182
|
+
loader: LoaderDefinition<any, any>,
|
|
183
|
+
callerLoaderId: string | null,
|
|
184
|
+
) => Promise<any> {
|
|
185
|
+
// Capture RequestContext eagerly for cookie access (ALS protection on Cloudflare)
|
|
186
|
+
const reqCtxRef = _getRequestContext();
|
|
187
|
+
|
|
188
|
+
// Dependency graph: loaderId -> set of loader IDs it directly depends on.
|
|
189
|
+
const dependsOn = new Map<string, Set<string>>();
|
|
190
|
+
|
|
191
|
+
// Loaders whose promises have not yet settled.
|
|
192
|
+
// A dependency on a pending loader that closes a cycle means deadlock.
|
|
193
|
+
const pendingLoaders = new Set<string>();
|
|
194
|
+
|
|
195
|
+
function useLoader(
|
|
196
|
+
loader: LoaderDefinition<any, any>,
|
|
197
|
+
callerLoaderId: string | null,
|
|
198
|
+
): Promise<any> {
|
|
199
|
+
// Record the dependency edge and check for cycles before running
|
|
200
|
+
if (callerLoaderId !== null) {
|
|
201
|
+
let deps = dependsOn.get(callerLoaderId);
|
|
202
|
+
if (!deps) {
|
|
203
|
+
deps = new Set();
|
|
204
|
+
dependsOn.set(callerLoaderId, deps);
|
|
205
|
+
}
|
|
168
206
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
207
|
+
// Only relevant when the target is still pending (would deadlock)
|
|
208
|
+
if (pendingLoaders.has(loader.$$id)) {
|
|
209
|
+
const cycle = detectLoaderCycle(callerLoaderId, loader.$$id, dependsOn);
|
|
210
|
+
if (cycle) {
|
|
211
|
+
throw new Error(
|
|
212
|
+
`Circular loader dependency detected: ${cycle.join(" -> ")}. ` +
|
|
213
|
+
`Loaders cannot depend on each other in a cycle. ` +
|
|
214
|
+
`Refactor to break the circular dependency.`,
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
173
218
|
|
|
174
|
-
|
|
175
|
-
|
|
219
|
+
deps.add(loader.$$id);
|
|
220
|
+
}
|
|
176
221
|
|
|
177
222
|
// Return cached promise if already started
|
|
178
223
|
if (loaderPromises.has(loader.$$id)) {
|
|
179
|
-
return loaderPromises.get(loader.$$id)
|
|
224
|
+
return loaderPromises.get(loader.$$id)!;
|
|
180
225
|
}
|
|
181
226
|
|
|
182
227
|
// Get loader function - either from loader object or fetchable registry
|
|
183
|
-
// Fetchable loaders store fn in registry (not on object) to avoid client bundling issues
|
|
184
228
|
let loaderFn = loader.fn;
|
|
185
229
|
if (!loaderFn) {
|
|
186
230
|
const fetchable = getFetchableLoader(loader.$$id);
|
|
@@ -189,122 +233,177 @@ export function setupLoaderAccess<TEnv>(
|
|
|
189
233
|
}
|
|
190
234
|
}
|
|
191
235
|
|
|
192
|
-
// Ensure loader has a function
|
|
193
236
|
if (!loaderFn) {
|
|
194
237
|
throw new Error(
|
|
195
|
-
`Loader "${loader.$$id}" has no function. This usually means the loader was defined without "use server" and the function was not included in the build
|
|
238
|
+
`Loader "${loader.$$id}" has no function. This usually means the loader was defined without "use server" and the function was not included in the build.`,
|
|
196
239
|
);
|
|
197
240
|
}
|
|
198
241
|
|
|
199
|
-
|
|
242
|
+
pendingLoaders.add(loader.$$id);
|
|
243
|
+
|
|
244
|
+
const currentLoaderId = loader.$$id;
|
|
245
|
+
// Loader functions are always fresh (never cached), so they get an
|
|
246
|
+
// unguarded get that bypasses non-cacheable read guards. This applies
|
|
247
|
+
// to ALL loaders — DSL and handler-called — because the loader
|
|
248
|
+
// function itself always re-executes. Also handles nested deps
|
|
249
|
+
// (loaderA → use(loaderB)) since all share this unguarded get.
|
|
200
250
|
const loaderCtx: LoaderContext<Record<string, string | undefined>, TEnv> = {
|
|
201
251
|
params: ctx.params,
|
|
252
|
+
routeParams: (ctx.params ?? {}) as Record<string, string>,
|
|
202
253
|
request: ctx.request,
|
|
203
254
|
searchParams: ctx.searchParams,
|
|
255
|
+
search: (ctx as any).search,
|
|
204
256
|
pathname: ctx.pathname,
|
|
205
257
|
url: ctx.url,
|
|
206
258
|
env: ctx.env,
|
|
207
259
|
var: ctx.var,
|
|
208
|
-
get: ctx.get,
|
|
260
|
+
get: ((keyOrVar: any) => contextGet(ctx.var, keyOrVar)) as typeof ctx.get,
|
|
209
261
|
use: <TDep, TDepParams = any>(
|
|
210
|
-
dep: LoaderDefinition<TDep, TDepParams
|
|
262
|
+
dep: LoaderDefinition<TDep, TDepParams>,
|
|
211
263
|
): Promise<TDep> => {
|
|
212
|
-
|
|
213
|
-
return ctx.use(dep);
|
|
264
|
+
return useLoader(dep, currentLoaderId);
|
|
214
265
|
},
|
|
215
|
-
// Default to GET for loaders called through route handlers
|
|
216
266
|
method: "GET",
|
|
217
267
|
body: undefined,
|
|
268
|
+
reverse: ctx.reverse as LoaderContext["reverse"],
|
|
218
269
|
};
|
|
219
270
|
|
|
220
|
-
|
|
221
|
-
const doneLoader = track(`loader:${loader.$$id}`);
|
|
271
|
+
const doneLoader = track(`loader:${loader.$$id}`, 2);
|
|
222
272
|
const promise = Promise.resolve(
|
|
223
|
-
loaderFn(loaderCtx as LoaderContext<any, TEnv>)
|
|
273
|
+
loaderFn(loaderCtx as LoaderContext<any, TEnv>),
|
|
224
274
|
).finally(() => {
|
|
275
|
+
pendingLoaders.delete(loader.$$id);
|
|
225
276
|
doneLoader();
|
|
226
277
|
});
|
|
227
278
|
|
|
228
|
-
// Memoize for subsequent calls
|
|
229
279
|
loaderPromises.set(loader.$$id, promise);
|
|
230
|
-
|
|
231
280
|
return promise;
|
|
232
|
-
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return useLoader;
|
|
233
284
|
}
|
|
234
285
|
|
|
235
286
|
/**
|
|
236
|
-
* Set up
|
|
237
|
-
* Handles are silently ignored (no push to HandleStore).
|
|
238
|
-
* Loaders work normally but with fresh memoization.
|
|
287
|
+
* Set up the use() method on handler context to access loaders and handles.
|
|
239
288
|
*
|
|
240
|
-
*
|
|
241
|
-
*
|
|
289
|
+
* For loaders: Lazily runs loaders, memoizes results per request.
|
|
290
|
+
* For handles: Returns a push function bound to the current segment.
|
|
291
|
+
*
|
|
292
|
+
* Includes cycle detection: tracks dependency edges between loaders and
|
|
293
|
+
* throws on circular dependencies to prevent deadlocks.
|
|
242
294
|
*/
|
|
243
|
-
export function
|
|
295
|
+
export function setupLoaderAccess<TEnv>(
|
|
244
296
|
ctx: HandlerContext<any, TEnv>,
|
|
245
|
-
loaderPromises: Map<string, Promise<any
|
|
297
|
+
loaderPromises: Map<string, Promise<any>>,
|
|
246
298
|
): void {
|
|
299
|
+
// Eagerly capture the HandleStore at setup time (before pipeline async ops).
|
|
300
|
+
// In workerd/Cloudflare, dynamic imports and fetch() in the match pipeline
|
|
301
|
+
// can disrupt AsyncLocalStorage, causing getRequestContext() to return
|
|
302
|
+
// undefined when handlers later call ctx.use(handle). Capturing early
|
|
303
|
+
// ensures the store reference survives ALS disruption.
|
|
304
|
+
const handleStoreRef = _getRequestContext()?._handleStore;
|
|
305
|
+
|
|
306
|
+
const useLoader = createLoaderExecutor(ctx, loaderPromises);
|
|
307
|
+
|
|
247
308
|
ctx.use = ((item: LoaderDefinition<any, any> | Handle<any, any>) => {
|
|
248
|
-
// Handle case: return a no-op push function
|
|
249
309
|
if (isHandle(item)) {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
310
|
+
const handle = item;
|
|
311
|
+
const store = handleStoreRef;
|
|
312
|
+
const segmentId = (ctx as InternalHandlerContext<any, TEnv>)
|
|
313
|
+
._currentSegmentId;
|
|
314
|
+
|
|
315
|
+
if (!segmentId) {
|
|
316
|
+
throw new Error(
|
|
317
|
+
`Handle "${handle.$$id}" used outside of handler context. ` +
|
|
318
|
+
`Handles must be used within route/layout handlers.`,
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return (
|
|
323
|
+
dataOrFn: unknown | Promise<unknown> | (() => Promise<unknown>),
|
|
324
|
+
) => {
|
|
325
|
+
if (!store) return;
|
|
326
|
+
|
|
327
|
+
const valueOrPromise =
|
|
328
|
+
typeof dataOrFn === "function"
|
|
329
|
+
? (dataOrFn as () => Promise<unknown>)()
|
|
330
|
+
: dataOrFn;
|
|
331
|
+
|
|
332
|
+
store.push(handle.$$id, segmentId, valueOrPromise);
|
|
253
333
|
};
|
|
254
334
|
}
|
|
255
335
|
|
|
256
|
-
|
|
257
|
-
|
|
336
|
+
return useLoader(item as LoaderDefinition<any, any>, null);
|
|
337
|
+
}) as typeof ctx.use;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Set up ctx.use() for pre-rendering (build-time).
|
|
342
|
+
* Handles push to HandleStore; loaders throw with a clear error.
|
|
343
|
+
*/
|
|
344
|
+
export function setupBuildUse<TEnv>(ctx: HandlerContext<any, TEnv>): void {
|
|
345
|
+
// Eagerly capture the HandleStore (same ALS protection as setupLoaderAccess).
|
|
346
|
+
const handleStoreRef = _getRequestContext()?._handleStore;
|
|
258
347
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
348
|
+
ctx.use = ((item: LoaderDefinition<any, any> | Handle<any, any>) => {
|
|
349
|
+
// Handle case: return a push function bound to the current segment
|
|
350
|
+
if (isHandle(item)) {
|
|
351
|
+
const handle = item;
|
|
352
|
+
const store = handleStoreRef;
|
|
353
|
+
const segmentId = (ctx as InternalHandlerContext<any, TEnv>)
|
|
354
|
+
._currentSegmentId;
|
|
263
355
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
loaderFn = fetchable.fn;
|
|
356
|
+
if (!segmentId) {
|
|
357
|
+
throw new Error(
|
|
358
|
+
`Handle "${handle.$$id}" used outside of handler context. ` +
|
|
359
|
+
`Handles must be used within route/layout handlers.`,
|
|
360
|
+
);
|
|
270
361
|
}
|
|
271
|
-
}
|
|
272
362
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
363
|
+
return (
|
|
364
|
+
dataOrFn: unknown | Promise<unknown> | (() => Promise<unknown>),
|
|
365
|
+
) => {
|
|
366
|
+
if (!store) return;
|
|
367
|
+
|
|
368
|
+
const valueOrPromise =
|
|
369
|
+
typeof dataOrFn === "function"
|
|
370
|
+
? (dataOrFn as () => Promise<unknown>)()
|
|
371
|
+
: dataOrFn;
|
|
372
|
+
|
|
373
|
+
store.push(handle.$$id, segmentId, valueOrPromise);
|
|
374
|
+
};
|
|
277
375
|
}
|
|
278
376
|
|
|
279
|
-
//
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
var: ctx.var,
|
|
288
|
-
get: ctx.get,
|
|
289
|
-
use: <TDep, TDepParams = any>(
|
|
290
|
-
dep: LoaderDefinition<TDep, TDepParams>
|
|
291
|
-
): Promise<TDep> => {
|
|
292
|
-
return ctx.use(dep);
|
|
293
|
-
},
|
|
294
|
-
method: "GET",
|
|
295
|
-
body: undefined,
|
|
296
|
-
};
|
|
377
|
+
// Loader case: not available during pre-rendering
|
|
378
|
+
throw new Error(
|
|
379
|
+
"Loaders are not available during pre-rendering. " +
|
|
380
|
+
"Use them on parent layouts with cache() for request-time data, " +
|
|
381
|
+
"or use a passthrough prerender handler.",
|
|
382
|
+
);
|
|
383
|
+
}) as typeof ctx.use;
|
|
384
|
+
}
|
|
297
385
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
386
|
+
/**
|
|
387
|
+
* Set up ctx.use() for proactive caching (silent mode).
|
|
388
|
+
* Handles are silently ignored (no push to HandleStore).
|
|
389
|
+
* Loaders work normally but with fresh memoization and cycle detection.
|
|
390
|
+
*
|
|
391
|
+
* This prevents duplicate handle data (breadcrumbs, meta) from being
|
|
392
|
+
* pushed to the response stream during background proactive caching.
|
|
393
|
+
*/
|
|
394
|
+
export function setupLoaderAccessSilent<TEnv>(
|
|
395
|
+
ctx: HandlerContext<any, TEnv>,
|
|
396
|
+
loaderPromises: Map<string, Promise<any>>,
|
|
397
|
+
): void {
|
|
398
|
+
const useLoader = createLoaderExecutor(ctx, loaderPromises);
|
|
305
399
|
|
|
306
|
-
|
|
307
|
-
|
|
400
|
+
ctx.use = ((item: LoaderDefinition<any, any> | Handle<any, any>) => {
|
|
401
|
+
if (isHandle(item)) {
|
|
402
|
+
// Silent mode - return a no-op so handle data is not pushed during caching
|
|
403
|
+
return (_dataOrFn: unknown) => {};
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return useLoader(item as LoaderDefinition<any, any>, null);
|
|
308
407
|
}) as typeof ctx.use;
|
|
309
408
|
}
|
|
310
409
|
|
|
@@ -320,7 +419,7 @@ export function setupLoaderAccessSilent<TEnv>(
|
|
|
320
419
|
export async function revalidate<T>(
|
|
321
420
|
shouldRevalidate: () => Promise<boolean>,
|
|
322
421
|
onRevalidate: () => Promise<T>,
|
|
323
|
-
onSkip: () => T
|
|
422
|
+
onSkip: () => T,
|
|
324
423
|
): Promise<T> {
|
|
325
424
|
const needsRevalidation = await shouldRevalidate();
|
|
326
425
|
return needsRevalidation ? await onRevalidate() : onSkip();
|