@rangojs/router 0.0.0-experimental.8 → 0.0.0-experimental.8a4d0430
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 +884 -4
- package/dist/bin/rango.js +1601 -0
- package/dist/vite/index.js +4474 -867
- package/package.json +60 -51
- package/skills/breadcrumbs/SKILL.md +250 -0
- package/skills/cache-guide/SKILL.md +262 -0
- package/skills/caching/SKILL.md +50 -21
- 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 +388 -38
- package/skills/middleware/SKILL.md +171 -34
- package/skills/mime-routes/SKILL.md +128 -0
- package/skills/parallel/SKILL.md +78 -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 +226 -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 +318 -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 +87 -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 +285 -553
- package/src/browser/navigation-client.ts +124 -71
- package/src/browser/navigation-store.ts +33 -50
- package/src/browser/navigation-transaction.ts +295 -0
- package/src/browser/network-error-handler.ts +61 -0
- package/src/browser/partial-update.ts +258 -308
- 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 +185 -73
- package/src/browser/react/NavigationProvider.tsx +51 -11
- 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 +6 -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 +107 -26
- package/src/browser/scroll-restoration.ts +92 -16
- package/src/browser/segment-reconciler.ts +216 -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 +109 -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 +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 +120 -303
- package/src/cache/cf/cf-cache-store.ts +119 -7
- package/src/cache/cf/index.ts +8 -2
- package/src/cache/document-cache.ts +101 -72
- package/src/cache/handle-capture.ts +81 -0
- package/src/cache/handle-snapshot.ts +41 -0
- package/src/cache/index.ts +0 -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 +98 -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 +17 -7
- 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 +21 -15
- package/src/host/errors.ts +8 -8
- package/src/host/index.ts +4 -7
- package/src/host/pattern-matcher.ts +27 -27
- package/src/host/router.ts +61 -39
- package/src/host/testing.ts +8 -8
- package/src/host/types.ts +15 -7
- package/src/host/utils.ts +1 -1
- 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 -157
- 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 +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 -1428
- package/src/route-map-builder.ts +211 -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 +158 -0
- package/src/router/handler-context.ts +374 -81
- package/src/router/intercept-resolution.ts +395 -0
- package/src/router/lazy-includes.ts +234 -0
- package/src/router/loader-resolution.ts +215 -122
- package/src/router/logging.ts +248 -0
- package/src/router/manifest.ts +148 -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 +80 -93
- package/src/router/match-middleware/cache-lookup.ts +382 -9
- package/src/router/match-middleware/cache-store.ts +51 -22
- package/src/router/match-middleware/intercept-resolution.ts +55 -17
- package/src/router/match-middleware/segment-resolution.ts +24 -6
- package/src/router/match-pipelines.ts +10 -45
- package/src/router/match-result.ts +34 -28
- package/src/router/metrics.ts +235 -15
- package/src/router/middleware-cookies.ts +55 -0
- package/src/router/middleware-types.ts +222 -0
- package/src/router/middleware.ts +324 -367
- 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 +36 -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 +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 +1241 -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 +77 -3
- package/src/router.ts +692 -4257
- 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 +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 +38 -11
- package/src/search-params.ts +230 -0
- package/src/segment-system.tsx +25 -13
- package/src/server/context.ts +182 -51
- 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 +430 -70
- package/src/server.ts +35 -130
- package/src/ssr/index.tsx +100 -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 +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 -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 +110 -0
- package/src/vite/discovery/virtual-module-codegen.ts +203 -0
- package/src/vite/index.ts +11 -1133
- 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/{expose-action-id.ts → plugins/expose-action-id.ts} +72 -51
- 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/{virtual-entries.ts → plugins/virtual-entries.ts} +23 -14
- 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/{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/vite/{version.d.ts → plugins/version.d.ts} +0 -0
|
@@ -21,8 +21,9 @@ import type {
|
|
|
21
21
|
import type { LoaderRevalidationResult, ActionContext } from "./types";
|
|
22
22
|
import { isHandle, type Handle } from "../handle.js";
|
|
23
23
|
import type { HandleStore } from "../server/handle-store.js";
|
|
24
|
-
import { getFetchableLoader } from "../loader.
|
|
25
|
-
import {
|
|
24
|
+
import { getFetchableLoader } from "../server/fetchable-loader-store.js";
|
|
25
|
+
import { _getRequestContext } from "../server/request-context.js";
|
|
26
|
+
import { debugLog } from "./logging.js";
|
|
26
27
|
|
|
27
28
|
/**
|
|
28
29
|
* Internal callback signature for loader error notifications.
|
|
@@ -35,7 +36,7 @@ export type LoaderErrorCallback = (
|
|
|
35
36
|
segmentId: string;
|
|
36
37
|
loaderName: string;
|
|
37
38
|
handledByBoundary: boolean;
|
|
38
|
-
}
|
|
39
|
+
},
|
|
39
40
|
) => void;
|
|
40
41
|
|
|
41
42
|
/**
|
|
@@ -54,14 +55,14 @@ export function wrapLoaderWithErrorHandling<T>(
|
|
|
54
55
|
segmentId: string,
|
|
55
56
|
pathname: string,
|
|
56
57
|
findNearestErrorBoundary: (
|
|
57
|
-
entry: EntryData | null
|
|
58
|
+
entry: EntryData | null,
|
|
58
59
|
) => ReactNode | ErrorBoundaryHandler | null,
|
|
59
60
|
createErrorInfo: (
|
|
60
61
|
error: unknown,
|
|
61
62
|
segmentId: string,
|
|
62
|
-
segmentType: ErrorInfo["segmentType"]
|
|
63
|
+
segmentType: ErrorInfo["segmentType"],
|
|
63
64
|
) => ErrorInfo,
|
|
64
|
-
onError?: LoaderErrorCallback
|
|
65
|
+
onError?: LoaderErrorCallback,
|
|
65
66
|
): Promise<LoaderDataResult<T>> {
|
|
66
67
|
// Extract loader name from segmentId (format: "M1L0D0.loaderName")
|
|
67
68
|
const loaderName = segmentId.split(".").pop() || "unknown";
|
|
@@ -72,7 +73,7 @@ export function wrapLoaderWithErrorHandling<T>(
|
|
|
72
73
|
__loaderResult: true,
|
|
73
74
|
ok: true,
|
|
74
75
|
data,
|
|
75
|
-
})
|
|
76
|
+
}),
|
|
76
77
|
)
|
|
77
78
|
.catch((error): LoaderDataResult<T> => {
|
|
78
79
|
// Find nearest error boundary
|
|
@@ -111,10 +112,10 @@ export function wrapLoaderWithErrorHandling<T>(
|
|
|
111
112
|
renderedFallback = fallback;
|
|
112
113
|
}
|
|
113
114
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
errorInfo.message
|
|
117
|
-
);
|
|
115
|
+
debugLog("loader", "loader error wrapped with boundary fallback", {
|
|
116
|
+
segmentId,
|
|
117
|
+
message: errorInfo.message,
|
|
118
|
+
});
|
|
118
119
|
|
|
119
120
|
return {
|
|
120
121
|
__loaderResult: true,
|
|
@@ -126,61 +127,103 @@ export function wrapLoaderWithErrorHandling<T>(
|
|
|
126
127
|
}
|
|
127
128
|
|
|
128
129
|
/**
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
* For handles: Returns a push function bound to the current segment.
|
|
130
|
+
* Detect cycles in the loader dependency graph using DFS from a given node.
|
|
131
|
+
* Returns the cycle path (array of loader IDs forming the cycle) if one exists,
|
|
132
|
+
* or null if no cycle is found.
|
|
133
133
|
*/
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
134
|
+
function detectLoaderCycle(
|
|
135
|
+
from: string,
|
|
136
|
+
to: string,
|
|
137
|
+
dependsOn: Map<string, Set<string>>,
|
|
138
|
+
): string[] | null {
|
|
139
|
+
// If `to` can reach `from` via the dependency graph, adding the edge
|
|
140
|
+
// from -> to creates a cycle. We search from `to` looking for `from`.
|
|
141
|
+
const visited = new Set<string>();
|
|
142
|
+
const path: string[] = [from, to];
|
|
143
|
+
|
|
144
|
+
function dfs(current: string): string[] | null {
|
|
145
|
+
if (current === from) {
|
|
146
|
+
// Found a cycle: return the path leading back to `from`
|
|
147
|
+
return path;
|
|
148
|
+
}
|
|
149
|
+
if (visited.has(current)) return null;
|
|
150
|
+
visited.add(current);
|
|
142
151
|
|
|
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;
|
|
152
|
+
const deps = dependsOn.get(current);
|
|
153
|
+
if (!deps) return null;
|
|
150
154
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
155
|
+
for (const dep of deps) {
|
|
156
|
+
path.push(dep);
|
|
157
|
+
const cycle = dfs(dep);
|
|
158
|
+
if (cycle) return cycle;
|
|
159
|
+
path.pop();
|
|
160
|
+
}
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
157
163
|
|
|
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;
|
|
164
|
+
return dfs(to);
|
|
165
|
+
}
|
|
163
166
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
167
|
+
/**
|
|
168
|
+
* Creates a memoizing loader executor with cycle detection.
|
|
169
|
+
* Shared by setupLoaderAccess and setupLoaderAccessSilent; only the handle
|
|
170
|
+
* branch differs between the two, so only the loader logic is extracted here.
|
|
171
|
+
*
|
|
172
|
+
* Returns a useLoader(loader, callerLoaderId) function that:
|
|
173
|
+
* - Tracks dependency edges between loaders for cycle detection
|
|
174
|
+
* - Throws immediately (synchronously inside an async fn) on circular deps
|
|
175
|
+
* - Memoizes each loader's promise so it runs at most once per request
|
|
176
|
+
*/
|
|
177
|
+
function createLoaderExecutor<TEnv>(
|
|
178
|
+
ctx: HandlerContext<any, TEnv>,
|
|
179
|
+
loaderPromises: Map<string, Promise<any>>,
|
|
180
|
+
): (
|
|
181
|
+
loader: LoaderDefinition<any, any>,
|
|
182
|
+
callerLoaderId: string | null,
|
|
183
|
+
) => Promise<any> {
|
|
184
|
+
// Capture RequestContext eagerly for cookie access (ALS protection on Cloudflare)
|
|
185
|
+
const reqCtxRef = _getRequestContext();
|
|
186
|
+
|
|
187
|
+
// Dependency graph: loaderId -> set of loader IDs it directly depends on.
|
|
188
|
+
const dependsOn = new Map<string, Set<string>>();
|
|
189
|
+
|
|
190
|
+
// Loaders whose promises have not yet settled.
|
|
191
|
+
// A dependency on a pending loader that closes a cycle means deadlock.
|
|
192
|
+
const pendingLoaders = new Set<string>();
|
|
193
|
+
|
|
194
|
+
function useLoader(
|
|
195
|
+
loader: LoaderDefinition<any, any>,
|
|
196
|
+
callerLoaderId: string | null,
|
|
197
|
+
): Promise<any> {
|
|
198
|
+
// Record the dependency edge and check for cycles before running
|
|
199
|
+
if (callerLoaderId !== null) {
|
|
200
|
+
let deps = dependsOn.get(callerLoaderId);
|
|
201
|
+
if (!deps) {
|
|
202
|
+
deps = new Set();
|
|
203
|
+
dependsOn.set(callerLoaderId, deps);
|
|
204
|
+
}
|
|
168
205
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
206
|
+
// Only relevant when the target is still pending (would deadlock)
|
|
207
|
+
if (pendingLoaders.has(loader.$$id)) {
|
|
208
|
+
const cycle = detectLoaderCycle(callerLoaderId, loader.$$id, dependsOn);
|
|
209
|
+
if (cycle) {
|
|
210
|
+
throw new Error(
|
|
211
|
+
`Circular loader dependency detected: ${cycle.join(" -> ")}. ` +
|
|
212
|
+
`Loaders cannot depend on each other in a cycle. ` +
|
|
213
|
+
`Refactor to break the circular dependency.`,
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
173
217
|
|
|
174
|
-
|
|
175
|
-
|
|
218
|
+
deps.add(loader.$$id);
|
|
219
|
+
}
|
|
176
220
|
|
|
177
221
|
// Return cached promise if already started
|
|
178
222
|
if (loaderPromises.has(loader.$$id)) {
|
|
179
|
-
return loaderPromises.get(loader.$$id)
|
|
223
|
+
return loaderPromises.get(loader.$$id)!;
|
|
180
224
|
}
|
|
181
225
|
|
|
182
226
|
// 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
227
|
let loaderFn = loader.fn;
|
|
185
228
|
if (!loaderFn) {
|
|
186
229
|
const fetchable = getFetchableLoader(loader.$$id);
|
|
@@ -189,122 +232,172 @@ export function setupLoaderAccess<TEnv>(
|
|
|
189
232
|
}
|
|
190
233
|
}
|
|
191
234
|
|
|
192
|
-
// Ensure loader has a function
|
|
193
235
|
if (!loaderFn) {
|
|
194
236
|
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
|
|
237
|
+
`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
238
|
);
|
|
197
239
|
}
|
|
198
240
|
|
|
199
|
-
|
|
241
|
+
pendingLoaders.add(loader.$$id);
|
|
242
|
+
|
|
243
|
+
const currentLoaderId = loader.$$id;
|
|
200
244
|
const loaderCtx: LoaderContext<Record<string, string | undefined>, TEnv> = {
|
|
201
245
|
params: ctx.params,
|
|
246
|
+
routeParams: (ctx.params ?? {}) as Record<string, string>,
|
|
202
247
|
request: ctx.request,
|
|
203
248
|
searchParams: ctx.searchParams,
|
|
249
|
+
search: (ctx as any).search,
|
|
204
250
|
pathname: ctx.pathname,
|
|
205
251
|
url: ctx.url,
|
|
206
252
|
env: ctx.env,
|
|
207
253
|
var: ctx.var,
|
|
208
254
|
get: ctx.get,
|
|
209
255
|
use: <TDep, TDepParams = any>(
|
|
210
|
-
dep: LoaderDefinition<TDep, TDepParams
|
|
256
|
+
dep: LoaderDefinition<TDep, TDepParams>,
|
|
211
257
|
): Promise<TDep> => {
|
|
212
|
-
|
|
213
|
-
return ctx.use(dep);
|
|
258
|
+
return useLoader(dep, currentLoaderId);
|
|
214
259
|
},
|
|
215
|
-
// Default to GET for loaders called through route handlers
|
|
216
260
|
method: "GET",
|
|
217
261
|
body: undefined,
|
|
262
|
+
reverse: ctx.reverse as LoaderContext["reverse"],
|
|
218
263
|
};
|
|
219
264
|
|
|
220
|
-
|
|
221
|
-
const doneLoader = track(`loader:${loader.$$id}`);
|
|
265
|
+
const doneLoader = track(`loader:${loader.$$id}`, 2);
|
|
222
266
|
const promise = Promise.resolve(
|
|
223
|
-
loaderFn(loaderCtx as LoaderContext<any, TEnv>)
|
|
267
|
+
loaderFn(loaderCtx as LoaderContext<any, TEnv>),
|
|
224
268
|
).finally(() => {
|
|
269
|
+
pendingLoaders.delete(loader.$$id);
|
|
225
270
|
doneLoader();
|
|
226
271
|
});
|
|
227
272
|
|
|
228
|
-
// Memoize for subsequent calls
|
|
229
273
|
loaderPromises.set(loader.$$id, promise);
|
|
230
|
-
|
|
231
274
|
return promise;
|
|
232
|
-
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return useLoader;
|
|
233
278
|
}
|
|
234
279
|
|
|
235
280
|
/**
|
|
236
|
-
* Set up
|
|
237
|
-
* Handles are silently ignored (no push to HandleStore).
|
|
238
|
-
* Loaders work normally but with fresh memoization.
|
|
281
|
+
* Set up the use() method on handler context to access loaders and handles.
|
|
239
282
|
*
|
|
240
|
-
*
|
|
241
|
-
*
|
|
283
|
+
* For loaders: Lazily runs loaders, memoizes results per request.
|
|
284
|
+
* For handles: Returns a push function bound to the current segment.
|
|
285
|
+
*
|
|
286
|
+
* Includes cycle detection: tracks dependency edges between loaders and
|
|
287
|
+
* throws on circular dependencies to prevent deadlocks.
|
|
242
288
|
*/
|
|
243
|
-
export function
|
|
289
|
+
export function setupLoaderAccess<TEnv>(
|
|
244
290
|
ctx: HandlerContext<any, TEnv>,
|
|
245
|
-
loaderPromises: Map<string, Promise<any
|
|
291
|
+
loaderPromises: Map<string, Promise<any>>,
|
|
246
292
|
): void {
|
|
293
|
+
// Eagerly capture the HandleStore at setup time (before pipeline async ops).
|
|
294
|
+
// In workerd/Cloudflare, dynamic imports and fetch() in the match pipeline
|
|
295
|
+
// can disrupt AsyncLocalStorage, causing getRequestContext() to return
|
|
296
|
+
// undefined when handlers later call ctx.use(handle). Capturing early
|
|
297
|
+
// ensures the store reference survives ALS disruption.
|
|
298
|
+
const handleStoreRef = _getRequestContext()?._handleStore;
|
|
299
|
+
|
|
300
|
+
const useLoader = createLoaderExecutor(ctx, loaderPromises);
|
|
301
|
+
|
|
247
302
|
ctx.use = ((item: LoaderDefinition<any, any> | Handle<any, any>) => {
|
|
248
|
-
// Handle case: return a no-op push function
|
|
249
303
|
if (isHandle(item)) {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
304
|
+
const handle = item;
|
|
305
|
+
const store = handleStoreRef;
|
|
306
|
+
const segmentId = (ctx as InternalHandlerContext<any, TEnv>)
|
|
307
|
+
._currentSegmentId;
|
|
308
|
+
|
|
309
|
+
if (!segmentId) {
|
|
310
|
+
throw new Error(
|
|
311
|
+
`Handle "${handle.$$id}" used outside of handler context. ` +
|
|
312
|
+
`Handles must be used within route/layout handlers.`,
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return (
|
|
317
|
+
dataOrFn: unknown | Promise<unknown> | (() => Promise<unknown>),
|
|
318
|
+
) => {
|
|
319
|
+
if (!store) return;
|
|
320
|
+
|
|
321
|
+
const valueOrPromise =
|
|
322
|
+
typeof dataOrFn === "function"
|
|
323
|
+
? (dataOrFn as () => Promise<unknown>)()
|
|
324
|
+
: dataOrFn;
|
|
325
|
+
|
|
326
|
+
store.push(handle.$$id, segmentId, valueOrPromise);
|
|
253
327
|
};
|
|
254
328
|
}
|
|
255
329
|
|
|
256
|
-
|
|
257
|
-
|
|
330
|
+
return useLoader(item as LoaderDefinition<any, any>, null);
|
|
331
|
+
}) as typeof ctx.use;
|
|
332
|
+
}
|
|
258
333
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
334
|
+
/**
|
|
335
|
+
* Set up ctx.use() for pre-rendering (build-time).
|
|
336
|
+
* Handles push to HandleStore; loaders throw with a clear error.
|
|
337
|
+
*/
|
|
338
|
+
export function setupBuildUse<TEnv>(ctx: HandlerContext<any, TEnv>): void {
|
|
339
|
+
// Eagerly capture the HandleStore (same ALS protection as setupLoaderAccess).
|
|
340
|
+
const handleStoreRef = _getRequestContext()?._handleStore;
|
|
263
341
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
if (
|
|
267
|
-
const
|
|
268
|
-
|
|
269
|
-
|
|
342
|
+
ctx.use = ((item: LoaderDefinition<any, any> | Handle<any, any>) => {
|
|
343
|
+
// Handle case: return a push function bound to the current segment
|
|
344
|
+
if (isHandle(item)) {
|
|
345
|
+
const handle = item;
|
|
346
|
+
const store = handleStoreRef;
|
|
347
|
+
const segmentId = (ctx as InternalHandlerContext<any, TEnv>)
|
|
348
|
+
._currentSegmentId;
|
|
349
|
+
|
|
350
|
+
if (!segmentId) {
|
|
351
|
+
throw new Error(
|
|
352
|
+
`Handle "${handle.$$id}" used outside of handler context. ` +
|
|
353
|
+
`Handles must be used within route/layout handlers.`,
|
|
354
|
+
);
|
|
270
355
|
}
|
|
271
|
-
}
|
|
272
356
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
357
|
+
return (
|
|
358
|
+
dataOrFn: unknown | Promise<unknown> | (() => Promise<unknown>),
|
|
359
|
+
) => {
|
|
360
|
+
if (!store) return;
|
|
361
|
+
|
|
362
|
+
const valueOrPromise =
|
|
363
|
+
typeof dataOrFn === "function"
|
|
364
|
+
? (dataOrFn as () => Promise<unknown>)()
|
|
365
|
+
: dataOrFn;
|
|
366
|
+
|
|
367
|
+
store.push(handle.$$id, segmentId, valueOrPromise);
|
|
368
|
+
};
|
|
277
369
|
}
|
|
278
370
|
|
|
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
|
-
};
|
|
371
|
+
// Loader case: not available during pre-rendering
|
|
372
|
+
throw new Error(
|
|
373
|
+
"Loaders are not available during pre-rendering. " +
|
|
374
|
+
"Use them on parent layouts with cache() for request-time data, " +
|
|
375
|
+
"or use a passthrough prerender handler.",
|
|
376
|
+
);
|
|
377
|
+
}) as typeof ctx.use;
|
|
378
|
+
}
|
|
297
379
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
380
|
+
/**
|
|
381
|
+
* Set up ctx.use() for proactive caching (silent mode).
|
|
382
|
+
* Handles are silently ignored (no push to HandleStore).
|
|
383
|
+
* Loaders work normally but with fresh memoization and cycle detection.
|
|
384
|
+
*
|
|
385
|
+
* This prevents duplicate handle data (breadcrumbs, meta) from being
|
|
386
|
+
* pushed to the response stream during background proactive caching.
|
|
387
|
+
*/
|
|
388
|
+
export function setupLoaderAccessSilent<TEnv>(
|
|
389
|
+
ctx: HandlerContext<any, TEnv>,
|
|
390
|
+
loaderPromises: Map<string, Promise<any>>,
|
|
391
|
+
): void {
|
|
392
|
+
const useLoader = createLoaderExecutor(ctx, loaderPromises);
|
|
305
393
|
|
|
306
|
-
|
|
307
|
-
|
|
394
|
+
ctx.use = ((item: LoaderDefinition<any, any> | Handle<any, any>) => {
|
|
395
|
+
if (isHandle(item)) {
|
|
396
|
+
// Silent mode - return a no-op so handle data is not pushed during caching
|
|
397
|
+
return (_dataOrFn: unknown) => {};
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return useLoader(item as LoaderDefinition<any, any>, null);
|
|
308
401
|
}) as typeof ctx.use;
|
|
309
402
|
}
|
|
310
403
|
|
|
@@ -320,7 +413,7 @@ export function setupLoaderAccessSilent<TEnv>(
|
|
|
320
413
|
export async function revalidate<T>(
|
|
321
414
|
shouldRevalidate: () => Promise<boolean>,
|
|
322
415
|
onRevalidate: () => Promise<T>,
|
|
323
|
-
onSkip: () => T
|
|
416
|
+
onSkip: () => T,
|
|
324
417
|
): Promise<T> {
|
|
325
418
|
const needsRevalidation = await shouldRevalidate();
|
|
326
419
|
return needsRevalidation ? await onRevalidate() : onSkip();
|