@rangojs/router 0.0.0-experimental.b9cb8739 → 0.0.0-experimental.bd6e11bc
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +196 -43
- package/dist/bin/rango.js +277 -99
- package/dist/testing/vitest.js +48 -0
- package/dist/vite/index.js +2779 -1064
- package/dist/vite/index.js.bak +5448 -0
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +57 -11
- package/skills/breadcrumbs/SKILL.md +3 -1
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +243 -21
- package/skills/caching/SKILL.md +155 -6
- package/skills/composability/SKILL.md +27 -2
- package/skills/document-cache/SKILL.md +78 -55
- package/skills/handler-use/SKILL.md +364 -0
- package/skills/hooks/SKILL.md +229 -20
- package/skills/host-router/SKILL.md +45 -20
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +46 -4
- package/skills/layout/SKILL.md +28 -7
- package/skills/links/SKILL.md +249 -17
- package/skills/loader/SKILL.md +273 -53
- package/skills/middleware/SKILL.md +49 -12
- package/skills/migrate-nextjs/SKILL.md +562 -0
- package/skills/migrate-react-router/SKILL.md +769 -0
- package/skills/mime-routes/SKILL.md +27 -0
- package/skills/observability/SKILL.md +137 -0
- package/skills/parallel/SKILL.md +197 -6
- package/skills/prerender/SKILL.md +123 -100
- package/skills/rango/SKILL.md +242 -22
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +66 -9
- package/skills/route/SKILL.md +88 -4
- package/skills/router-setup/SKILL.md +90 -5
- package/skills/server-actions/SKILL.md +751 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/testing/SKILL.md +716 -0
- package/skills/typesafety/SKILL.md +329 -27
- package/skills/use-cache/SKILL.md +34 -5
- package/skills/view-transitions/SKILL.md +294 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +117 -0
- package/src/__internal.ts +1 -1
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/app-shell.ts +52 -0
- package/src/browser/app-version.ts +14 -0
- package/src/browser/event-controller.ts +91 -70
- package/src/browser/history-state.ts +21 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/navigation-bridge.ts +102 -16
- package/src/browser/navigation-client.ts +164 -59
- package/src/browser/navigation-store.ts +75 -17
- package/src/browser/navigation-transaction.ts +21 -37
- package/src/browser/partial-update.ts +139 -38
- package/src/browser/prefetch/cache.ts +175 -15
- package/src/browser/prefetch/fetch.ts +180 -33
- package/src/browser/prefetch/queue.ts +123 -20
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +53 -13
- package/src/browser/react/Link.tsx +81 -9
- package/src/browser/react/NavigationProvider.tsx +110 -33
- package/src/browser/react/context.ts +7 -2
- package/src/browser/react/filter-segment-order.ts +51 -7
- package/src/browser/react/index.ts +3 -0
- package/src/browser/react/location-state-shared.ts +175 -4
- package/src/browser/react/location-state.ts +39 -13
- package/src/browser/react/use-handle.ts +23 -64
- package/src/browser/react/use-navigation.ts +22 -2
- package/src/browser/react/use-params.ts +20 -8
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +43 -10
- package/src/browser/react/use-segments.ts +11 -8
- package/src/browser/response-adapter.ts +25 -0
- package/src/browser/rsc-router.tsx +191 -74
- package/src/browser/scroll-restoration.ts +41 -14
- package/src/browser/segment-reconciler.ts +36 -9
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +31 -36
- package/src/browser/types.ts +57 -5
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +65 -40
- package/src/build/generate-route-types.ts +5 -0
- package/src/build/index.ts +2 -0
- package/src/build/route-trie.ts +52 -25
- package/src/build/route-types/codegen.ts +4 -4
- package/src/build/route-types/include-resolution.ts +9 -2
- package/src/build/route-types/per-module-writer.ts +7 -4
- package/src/build/route-types/router-processing.ts +278 -88
- package/src/build/route-types/scan-filter.ts +9 -2
- package/src/build/route-types/source-scan.ts +118 -0
- package/src/build/runtime-discovery.ts +9 -20
- package/src/cache/cache-runtime.ts +15 -11
- package/src/cache/cache-scope.ts +76 -49
- package/src/cache/cf/cf-cache-store.ts +501 -18
- package/src/cache/cf/index.ts +5 -1
- package/src/cache/document-cache.ts +17 -7
- package/src/cache/index.ts +1 -0
- package/src/cache/taint.ts +55 -0
- package/src/client.rsc.tsx +3 -0
- package/src/client.tsx +94 -238
- package/src/context-var.ts +72 -2
- package/src/debug.ts +2 -2
- package/src/decode-loader-results.ts +36 -0
- package/src/errors.ts +30 -1
- package/src/handle.ts +65 -12
- package/src/host/index.ts +2 -2
- package/src/host/router.ts +129 -57
- package/src/host/types.ts +31 -2
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +140 -20
- package/src/index.rsc.ts +12 -5
- package/src/index.ts +61 -11
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +2 -5
- package/src/loader.ts +3 -10
- package/src/missing-id-error.ts +68 -0
- package/src/outlet-context.ts +1 -1
- package/src/prerender/store.ts +5 -4
- package/src/prerender.ts +141 -80
- package/src/response-utils.ts +37 -0
- package/src/reverse.ts +65 -15
- package/src/route-content-wrapper.tsx +6 -28
- package/src/route-definition/dsl-helpers.ts +435 -260
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +110 -34
- package/src/route-definition/index.ts +3 -0
- package/src/route-definition/redirect.ts +11 -3
- package/src/route-definition/resolve-handler-use.ts +155 -0
- package/src/route-definition/use-item-types.ts +32 -0
- package/src/route-map-builder.ts +7 -1
- package/src/route-types.ts +37 -41
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +113 -1
- package/src/router/error-handling.ts +1 -1
- package/src/router/find-match.ts +4 -2
- package/src/router/handler-context.ts +77 -38
- package/src/router/intercept-resolution.ts +15 -22
- package/src/router/lazy-includes.ts +12 -9
- package/src/router/loader-resolution.ts +174 -22
- package/src/router/logging.ts +5 -2
- package/src/router/manifest.ts +31 -16
- package/src/router/match-api.ts +128 -192
- package/src/router/match-handlers.ts +63 -20
- package/src/router/match-middleware/background-revalidation.ts +30 -2
- package/src/router/match-middleware/cache-lookup.ts +136 -106
- package/src/router/match-middleware/cache-store.ts +54 -10
- package/src/router/match-middleware/intercept-resolution.ts +9 -7
- package/src/router/match-middleware/segment-resolution.ts +61 -5
- package/src/router/match-result.ts +125 -10
- package/src/router/metrics.ts +7 -2
- package/src/router/middleware-types.ts +21 -34
- package/src/router/middleware.ts +103 -90
- package/src/router/navigation-snapshot.ts +182 -0
- package/src/router/pattern-matching.ts +101 -17
- package/src/router/prerender-match.ts +110 -10
- package/src/router/preview-match.ts +32 -102
- package/src/router/request-classification.ts +286 -0
- package/src/router/revalidation.ts +58 -2
- package/src/router/route-snapshot.ts +245 -0
- package/src/router/router-context.ts +6 -1
- package/src/router/router-interfaces.ts +77 -28
- package/src/router/router-options.ts +76 -11
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +223 -24
- package/src/router/segment-resolution/helpers.ts +29 -24
- package/src/router/segment-resolution/loader-cache.ts +1 -0
- package/src/router/segment-resolution/revalidation.ts +466 -285
- package/src/router/segment-resolution/view-transition-default.ts +36 -0
- package/src/router/segment-wrappers.ts +2 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry.ts +99 -0
- package/src/router/trie-matching.ts +18 -13
- package/src/router/types.ts +9 -0
- package/src/router/url-params.ts +49 -0
- package/src/router.ts +91 -23
- package/src/rsc/handler-context.ts +2 -2
- package/src/rsc/handler.ts +440 -381
- package/src/rsc/helpers.ts +91 -43
- package/src/rsc/index.ts +1 -1
- package/src/rsc/loader-fetch.ts +23 -3
- package/src/rsc/manifest-init.ts +5 -1
- package/src/rsc/origin-guard.ts +28 -10
- package/src/rsc/progressive-enhancement.ts +18 -2
- package/src/rsc/response-route-handler.ts +46 -53
- package/src/rsc/rsc-rendering.ts +41 -48
- package/src/rsc/runtime-warnings.ts +9 -10
- package/src/rsc/server-action.ts +25 -37
- package/src/rsc/ssr-setup.ts +18 -2
- package/src/rsc/types.ts +17 -3
- package/src/search-params.ts +4 -4
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +122 -0
- package/src/segment-system.tsx +219 -67
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +277 -61
- package/src/server/cookie-store.ts +28 -4
- package/src/server/handle-store.ts +19 -0
- package/src/server/loader-registry.ts +9 -8
- package/src/server/request-context.ts +204 -60
- package/src/ssr/index.tsx +9 -1
- package/src/static-handler.ts +19 -7
- package/src/testing/cache-status.ts +166 -0
- package/src/testing/collect-handle.ts +63 -0
- package/src/testing/dispatch.ts +440 -0
- package/src/testing/dom.entry.ts +22 -0
- package/src/testing/e2e/fixture.ts +154 -0
- package/src/testing/e2e/index.ts +149 -0
- package/src/testing/e2e/matchers.ts +51 -0
- package/src/testing/e2e/page-helpers.ts +272 -0
- package/src/testing/e2e/parity.ts +306 -0
- package/src/testing/e2e/server.ts +183 -0
- package/src/testing/flight-matchers.ts +104 -0
- package/src/testing/flight-runtime.d.ts +21 -0
- package/src/testing/flight.entry.ts +22 -0
- package/src/testing/flight.ts +182 -0
- package/src/testing/generated-routes.ts +223 -0
- package/src/testing/index.ts +106 -0
- package/src/testing/internal/context.ts +255 -0
- package/src/testing/render-route.tsx +565 -0
- package/src/testing/run-loader.ts +296 -0
- package/src/testing/run-middleware.ts +179 -0
- package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
- package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
- package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
- package/src/testing/vitest-stubs/version.ts +5 -0
- package/src/testing/vitest.ts +183 -0
- package/src/types/cache-types.ts +4 -4
- package/src/types/global-namespace.ts +39 -26
- package/src/types/handler-context.ts +194 -72
- package/src/types/index.ts +1 -0
- package/src/types/loader-types.ts +41 -15
- package/src/types/request-scope.ts +126 -0
- package/src/types/route-entry.ts +19 -1
- package/src/types/segments.ts +37 -1
- package/src/urls/include-helper.ts +34 -67
- package/src/urls/index.ts +0 -3
- package/src/urls/path-helper-types.ts +50 -9
- package/src/urls/path-helper.ts +63 -63
- package/src/urls/pattern-types.ts +48 -19
- package/src/urls/response-types.ts +25 -22
- package/src/urls/type-extraction.ts +26 -116
- package/src/urls/urls-function.ts +1 -5
- package/src/use-loader.tsx +487 -44
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +34 -37
- package/src/vite/discovery/discover-routers.ts +105 -51
- package/src/vite/discovery/discovery-errors.ts +194 -0
- package/src/vite/discovery/gate-state.ts +171 -0
- package/src/vite/discovery/prerender-collection.ts +188 -93
- package/src/vite/discovery/route-types-writer.ts +40 -84
- package/src/vite/discovery/self-gen-tracking.ts +27 -1
- package/src/vite/discovery/state.ts +46 -6
- package/src/vite/discovery/virtual-module-codegen.ts +13 -23
- package/src/vite/index.ts +6 -0
- package/src/vite/plugin-types.ts +111 -72
- package/src/vite/plugins/cjs-to-esm.ts +8 -7
- package/src/vite/plugins/client-ref-dedup.ts +16 -0
- package/src/vite/plugins/client-ref-hashing.ts +28 -5
- package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
- package/src/vite/plugins/expose-action-id.ts +55 -33
- package/src/vite/plugins/expose-id-utils.ts +24 -8
- package/src/vite/plugins/expose-ids/export-analysis.ts +100 -20
- package/src/vite/plugins/expose-ids/handler-transform.ts +12 -35
- package/src/vite/plugins/expose-ids/loader-transform.ts +3 -5
- package/src/vite/plugins/expose-ids/router-transform.ts +20 -3
- package/src/vite/plugins/expose-internal-ids.ts +544 -317
- package/src/vite/plugins/performance-tracks.ts +92 -0
- package/src/vite/plugins/refresh-cmd.ts +88 -26
- package/src/vite/plugins/use-cache-transform.ts +65 -50
- package/src/vite/plugins/version-injector.ts +39 -23
- package/src/vite/plugins/version-plugin.ts +72 -3
- package/src/vite/plugins/virtual-entries.ts +2 -2
- package/src/vite/rango.ts +265 -226
- package/src/vite/router-discovery.ts +920 -137
- package/src/vite/utils/ast-handler-extract.ts +15 -15
- package/src/vite/utils/banner.ts +4 -4
- package/src/vite/utils/bundle-analysis.ts +4 -2
- package/src/vite/utils/client-chunks.ts +190 -0
- package/src/vite/utils/forward-user-plugins.ts +193 -0
- package/src/vite/utils/manifest-utils.ts +21 -5
- package/src/vite/utils/package-resolution.ts +41 -1
- package/src/vite/utils/prerender-utils.ts +38 -5
- package/src/vite/utils/shared-utils.ts +109 -27
- package/src/browser/action-response-classifier.ts +0 -99
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { registerRouteMap } from "../route-map-builder.js";
|
|
2
2
|
import { extractStaticPrefix } from "./pattern-matching.js";
|
|
3
3
|
import {
|
|
4
|
-
EntryData,
|
|
5
|
-
|
|
4
|
+
type EntryData,
|
|
5
|
+
RangoContext,
|
|
6
6
|
runWithPrefixes,
|
|
7
|
+
getIsolatedLazyParent,
|
|
7
8
|
} from "../server/context";
|
|
8
9
|
import type { UrlPatterns } from "../urls.js";
|
|
9
10
|
import type { AllUseItems, IncludeItem } from "../route-types.js";
|
|
@@ -14,6 +15,7 @@ export interface LazyEvalDeps<TEnv = any> {
|
|
|
14
15
|
mergedRouteMap: Record<string, string>;
|
|
15
16
|
nextMountIndex: () => number;
|
|
16
17
|
getPrecomputedByPrefix: () => Map<string, Record<string, string>> | null;
|
|
18
|
+
routerId?: string;
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
// Detect lazy includes in handler result and create placeholder entries
|
|
@@ -123,24 +125,24 @@ export function evaluateLazyEntry<TEnv = any>(
|
|
|
123
125
|
// Merge captured counters from include() to maintain consistent
|
|
124
126
|
// shortCode indices with sibling entries from pattern extraction
|
|
125
127
|
const lazyCounters: Record<string, number> = {};
|
|
126
|
-
if (lazyContext
|
|
127
|
-
const
|
|
128
|
-
for (const [key, value] of Object.entries(captured)) {
|
|
128
|
+
if (lazyContext?.counters) {
|
|
129
|
+
for (const [key, value] of Object.entries(lazyContext.counters)) {
|
|
129
130
|
lazyCounters[key] = value;
|
|
130
131
|
}
|
|
131
132
|
}
|
|
132
133
|
|
|
133
|
-
|
|
134
|
+
RangoContext.run(
|
|
134
135
|
{
|
|
135
136
|
manifest,
|
|
136
137
|
patterns,
|
|
137
138
|
patternsByPrefix,
|
|
138
139
|
trailingSlash: trailingSlashMap,
|
|
139
140
|
namespace: "lazy",
|
|
140
|
-
parent: (lazyContext?.parent as EntryData | null)
|
|
141
|
+
parent: getIsolatedLazyParent(lazyContext?.parent as EntryData | null),
|
|
141
142
|
counters: lazyCounters,
|
|
142
|
-
cacheProfiles:
|
|
143
|
-
rootScoped:
|
|
143
|
+
cacheProfiles: lazyContext?.cacheProfiles,
|
|
144
|
+
rootScoped: lazyContext?.rootScoped,
|
|
145
|
+
includeScope: lazyContext?.includeScope,
|
|
144
146
|
},
|
|
145
147
|
() => {
|
|
146
148
|
// Run the lazy patterns handler with the original context prefixes
|
|
@@ -200,6 +202,7 @@ export function evaluateLazyEntry<TEnv = any>(
|
|
|
200
202
|
trailingSlash: entry.trailingSlash,
|
|
201
203
|
handler: (lazyInclude.patterns as UrlPatterns<TEnv>).handler,
|
|
202
204
|
mountIndex: deps.nextMountIndex(),
|
|
205
|
+
routerId: deps.routerId,
|
|
203
206
|
// Lazy evaluation fields
|
|
204
207
|
lazy: true,
|
|
205
208
|
lazyPatterns: lazyInclude.patterns,
|
|
@@ -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,
|
|
@@ -19,10 +20,14 @@ import type {
|
|
|
19
20
|
ErrorInfo,
|
|
20
21
|
} from "../types";
|
|
21
22
|
import type { LoaderRevalidationResult, ActionContext } from "./types";
|
|
22
|
-
import { isHandle, type Handle } from "../handle.js";
|
|
23
|
-
import
|
|
23
|
+
import { isHandle, collectHandleData, type Handle } from "../handle.js";
|
|
24
|
+
import { buildHandleSnapshot } from "../server/handle-store.js";
|
|
24
25
|
import { getFetchableLoader } from "../server/fetchable-loader-store.js";
|
|
25
26
|
import { _getRequestContext } from "../server/request-context.js";
|
|
27
|
+
import {
|
|
28
|
+
isInsideLoaderScope,
|
|
29
|
+
runInsideLoaderBodyScope,
|
|
30
|
+
} from "../server/context.js";
|
|
26
31
|
import { debugLog } from "./logging.js";
|
|
27
32
|
|
|
28
33
|
/**
|
|
@@ -241,6 +246,21 @@ function createLoaderExecutor<TEnv>(
|
|
|
241
246
|
pendingLoaders.add(loader.$$id);
|
|
242
247
|
|
|
243
248
|
const currentLoaderId = loader.$$id;
|
|
249
|
+
const variables = (ctx as InternalHandlerContext<any, TEnv>)._variables;
|
|
250
|
+
|
|
251
|
+
// Capture whether this loader is being started from a DSL loader scope
|
|
252
|
+
// (runInsideLoaderScope in fresh.ts). Handler-invoked loaders are NOT
|
|
253
|
+
// inside loader scope. This determines whether rendered() is allowed.
|
|
254
|
+
const isDslLoader = isInsideLoaderScope();
|
|
255
|
+
|
|
256
|
+
let renderedResolved = false;
|
|
257
|
+
let renderedPromise: Promise<void> | null = null;
|
|
258
|
+
|
|
259
|
+
// Loader functions are always fresh (never cached), so they get an
|
|
260
|
+
// unguarded get that bypasses non-cacheable read guards. This applies
|
|
261
|
+
// to ALL loaders — DSL and handler-called — because the loader
|
|
262
|
+
// function itself always re-executes. Also handles nested deps
|
|
263
|
+
// (loaderA → use(loaderB)) since all share this unguarded get.
|
|
244
264
|
const loaderCtx: LoaderContext<Record<string, string | undefined>, TEnv> = {
|
|
245
265
|
params: ctx.params,
|
|
246
266
|
routeParams: (ctx.params ?? {}) as Record<string, string>,
|
|
@@ -249,22 +269,106 @@ function createLoaderExecutor<TEnv>(
|
|
|
249
269
|
search: (ctx as any).search,
|
|
250
270
|
pathname: ctx.pathname,
|
|
251
271
|
url: ctx.url,
|
|
272
|
+
originalUrl: ctx.originalUrl,
|
|
252
273
|
env: ctx.env,
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
274
|
+
waitUntil: ctx.waitUntil.bind(ctx),
|
|
275
|
+
executionContext: ctx.executionContext,
|
|
276
|
+
get: ((keyOrVar: any) =>
|
|
277
|
+
contextGet(variables, keyOrVar)) as typeof ctx.get,
|
|
278
|
+
use: ((item: LoaderDefinition<any, any> | Handle<any, any>) => {
|
|
279
|
+
if (isHandle(item)) {
|
|
280
|
+
if (!renderedResolved) {
|
|
281
|
+
throw new Error(
|
|
282
|
+
`ctx.use(handle) in a loader requires "await ctx.rendered()" first. ` +
|
|
283
|
+
`Handle "${item.$$id}" cannot be read until the render tree has settled.`,
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
const reqCtx = reqCtxRef ?? _getRequestContext();
|
|
287
|
+
if (!reqCtx) {
|
|
288
|
+
throw new Error(
|
|
289
|
+
`ctx.use(handle) failed: request context not available.`,
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
const segmentOrder = reqCtx._renderBarrierSegmentOrder ?? [];
|
|
293
|
+
const snapshot =
|
|
294
|
+
reqCtx._renderBarrierHandleSnapshot ??
|
|
295
|
+
buildHandleSnapshot(reqCtx._handleStore, segmentOrder);
|
|
296
|
+
return collectHandleData(item, snapshot, segmentOrder);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Loader case
|
|
300
|
+
return useLoader(item as LoaderDefinition<any, any>, currentLoaderId);
|
|
301
|
+
}) as LoaderContext["use"],
|
|
260
302
|
method: "GET",
|
|
261
303
|
body: undefined,
|
|
262
304
|
reverse: ctx.reverse as LoaderContext["reverse"],
|
|
305
|
+
rendered: (): Promise<void> => {
|
|
306
|
+
// Guard: only DSL loaders may use rendered()
|
|
307
|
+
if (!isDslLoader) {
|
|
308
|
+
throw new Error(
|
|
309
|
+
`ctx.rendered() is only available in DSL loaders (registered via loader() in urls()). ` +
|
|
310
|
+
`Handler-invoked loaders (ctx.use(Loader) inside a handler) cannot use rendered().`,
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Guard: reject streaming trees
|
|
315
|
+
const reqCtx = reqCtxRef ?? _getRequestContext();
|
|
316
|
+
if (reqCtx?._treeHasStreaming) {
|
|
317
|
+
throw new Error(
|
|
318
|
+
`ctx.rendered() is not supported when the matched route tree uses loading(). ` +
|
|
319
|
+
`Streaming handlers may not have settled when rendered() resolves. ` +
|
|
320
|
+
`Remove loading() from the route tree or restructure to avoid rendered().`,
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (renderedPromise) return renderedPromise;
|
|
325
|
+
|
|
326
|
+
if (!reqCtx) {
|
|
327
|
+
throw new Error(
|
|
328
|
+
`ctx.rendered() failed: request context not available.`,
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Bidirectional deadlock check: if a handler already started
|
|
333
|
+
// awaiting this loader, calling rendered() would deadlock.
|
|
334
|
+
if (reqCtx._handlerLoaderDeps?.has(currentLoaderId)) {
|
|
335
|
+
throw new Error(
|
|
336
|
+
`Deadlock: loader "${currentLoaderId}" called ctx.rendered() but a handler ` +
|
|
337
|
+
`is already awaiting this loader via ctx.use(). The handler blocks ` +
|
|
338
|
+
`segment resolution, which blocks the barrier, which blocks this loader. ` +
|
|
339
|
+
`Move the data dependency to a loader-to-loader pattern instead.`,
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Register this loader as waiting for the barrier so that
|
|
344
|
+
// setupLoaderAccess can detect deadlocks when a handler
|
|
345
|
+
// tries to await the same loader via ctx.use().
|
|
346
|
+
if (!reqCtx._renderBarrierWaiters) {
|
|
347
|
+
reqCtx._renderBarrierWaiters = new Set();
|
|
348
|
+
}
|
|
349
|
+
reqCtx._renderBarrierWaiters.add(currentLoaderId);
|
|
350
|
+
|
|
351
|
+
renderedPromise = reqCtx._renderBarrier.then(() => {
|
|
352
|
+
renderedResolved = true;
|
|
353
|
+
});
|
|
354
|
+
return renderedPromise;
|
|
355
|
+
},
|
|
263
356
|
};
|
|
264
357
|
|
|
265
358
|
const doneLoader = track(`loader:${loader.$$id}`, 2);
|
|
359
|
+
// Run the loader body inside loader scope so request-scoped reads
|
|
360
|
+
// (cookies()/headers() and non-cacheable ctx.get) are exempt from the
|
|
361
|
+
// cache-purity guards: loaders always run fresh, so their reads never leak
|
|
362
|
+
// into a cached segment. DSL loaders are already wrapped by fresh.ts; this
|
|
363
|
+
// also covers handler-invoked loaders (ctx.use(Loader) from a handler),
|
|
364
|
+
// which otherwise execute in the caller's cache scope and would wrongly
|
|
365
|
+
// throw. rendered() gating uses the captured isDslLoader (above), so this
|
|
366
|
+
// does not grant rendered() to handler-invoked loaders. Uses a body-only
|
|
367
|
+
// scope, so isInsideLoaderScope() / barrier / deadlock gating is unchanged.
|
|
266
368
|
const promise = Promise.resolve(
|
|
267
|
-
|
|
369
|
+
runInsideLoaderBodyScope(() =>
|
|
370
|
+
loaderFn(loaderCtx as LoaderContext<any, TEnv>),
|
|
371
|
+
),
|
|
268
372
|
).finally(() => {
|
|
269
373
|
pendingLoaders.delete(loader.$$id);
|
|
270
374
|
doneLoader();
|
|
@@ -290,15 +394,22 @@ export function setupLoaderAccess<TEnv>(
|
|
|
290
394
|
ctx: HandlerContext<any, TEnv>,
|
|
291
395
|
loaderPromises: Map<string, Promise<any>>,
|
|
292
396
|
): void {
|
|
293
|
-
// Eagerly capture the HandleStore at setup time
|
|
294
|
-
// In workerd/Cloudflare, dynamic imports and
|
|
295
|
-
// can disrupt AsyncLocalStorage, causing
|
|
296
|
-
// undefined when handlers later call
|
|
297
|
-
//
|
|
298
|
-
const
|
|
397
|
+
// Eagerly capture the request context and HandleStore at setup time
|
|
398
|
+
// (before pipeline async ops). In workerd/Cloudflare, dynamic imports and
|
|
399
|
+
// fetch() in the match pipeline can disrupt AsyncLocalStorage, causing
|
|
400
|
+
// getRequestContext() to return undefined when handlers later call
|
|
401
|
+
// ctx.use(handle). Capturing early ensures references survive ALS disruption.
|
|
402
|
+
const reqCtxRef = _getRequestContext();
|
|
403
|
+
const handleStoreRef = reqCtxRef?._handleStore;
|
|
299
404
|
|
|
300
405
|
const useLoader = createLoaderExecutor(ctx, loaderPromises);
|
|
301
406
|
|
|
407
|
+
// Track whether we're inside a handle push callback. Loaders started
|
|
408
|
+
// from push callbacks (e.g. push(async () => ctx.use(Loader))) do NOT
|
|
409
|
+
// block segment resolution, so they must not be registered as handler
|
|
410
|
+
// dependencies for deadlock detection.
|
|
411
|
+
let insideHandlePush = false;
|
|
412
|
+
|
|
302
413
|
ctx.use = ((item: LoaderDefinition<any, any> | Handle<any, any>) => {
|
|
303
414
|
if (isHandle(item)) {
|
|
304
415
|
const handle = item;
|
|
@@ -318,16 +429,57 @@ export function setupLoaderAccess<TEnv>(
|
|
|
318
429
|
) => {
|
|
319
430
|
if (!store) return;
|
|
320
431
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
432
|
+
if (typeof dataOrFn === "function") {
|
|
433
|
+
// Mark scope so ctx.use(loader) calls inside the callback
|
|
434
|
+
// are not registered as handler-to-loader deps.
|
|
435
|
+
insideHandlePush = true;
|
|
436
|
+
try {
|
|
437
|
+
const result = (dataOrFn as () => Promise<unknown>)();
|
|
438
|
+
store.push(handle.$$id, segmentId, result);
|
|
439
|
+
} finally {
|
|
440
|
+
insideHandlePush = false;
|
|
441
|
+
}
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
325
444
|
|
|
326
|
-
store.push(handle.$$id, segmentId,
|
|
445
|
+
store.push(handle.$$id, segmentId, dataOrFn);
|
|
327
446
|
};
|
|
328
447
|
}
|
|
329
448
|
|
|
330
|
-
|
|
449
|
+
// Deadlock guard and handler-to-loader dependency tracking.
|
|
450
|
+
// Skip when inside a DSL loader scope (resolveLoaderData also calls
|
|
451
|
+
// ctx.use() but that's DSL-to-DSL, not handler-to-loader) or when
|
|
452
|
+
// inside a handle push callback (push callbacks don't block segment
|
|
453
|
+
// resolution so they can't cause rendered() deadlocks).
|
|
454
|
+
const loader = item as LoaderDefinition<any, any>;
|
|
455
|
+
if (!isInsideLoaderScope() && !insideHandlePush) {
|
|
456
|
+
const reqCtx = reqCtxRef ?? _getRequestContext();
|
|
457
|
+
if (reqCtx) {
|
|
458
|
+
// Direction 1: handler awaits loader that already called rendered()
|
|
459
|
+
if (
|
|
460
|
+
loaderPromises.has(loader.$$id) &&
|
|
461
|
+
reqCtx._renderBarrierWaiters?.has(loader.$$id)
|
|
462
|
+
) {
|
|
463
|
+
throw new Error(
|
|
464
|
+
`Deadlock: handler is awaiting loader "${loader.$$id}" which called ctx.rendered(). ` +
|
|
465
|
+
`The loader is waiting for segment resolution, but the handler blocks resolution. ` +
|
|
466
|
+
`Move the data dependency to a loader-to-loader pattern instead.`,
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
// Direction 2: track dep so rendered() can detect the deadlock
|
|
470
|
+
// if the loader calls it later. Skip when the barrier has already
|
|
471
|
+
// resolved — no deadlock is possible (rendered() resolves immediately).
|
|
472
|
+
// _renderBarrierSegmentOrder is undefined before resolution, string[]
|
|
473
|
+
// after. This also prevents false positives from handle push callbacks
|
|
474
|
+
// that resume after their first await (post-barrier-resolution).
|
|
475
|
+
if (reqCtx._renderBarrierSegmentOrder === undefined) {
|
|
476
|
+
if (!reqCtx._handlerLoaderDeps) reqCtx._handlerLoaderDeps = new Set();
|
|
477
|
+
reqCtx._handlerLoaderDeps.add(loader.$$id);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
return useLoader(loader, null);
|
|
331
483
|
}) as typeof ctx.use;
|
|
332
484
|
}
|
|
333
485
|
|
package/src/router/logging.ts
CHANGED
|
@@ -12,7 +12,10 @@ export interface RevalidationTraceEntry {
|
|
|
12
12
|
| "cache-hit"
|
|
13
13
|
| "loader"
|
|
14
14
|
| "parallel"
|
|
15
|
-
| "orphan-layout"
|
|
15
|
+
| "orphan-layout"
|
|
16
|
+
| "route-handler"
|
|
17
|
+
| "layout-handler"
|
|
18
|
+
| "intercept-loader";
|
|
16
19
|
defaultShouldRevalidate: boolean;
|
|
17
20
|
finalShouldRevalidate: boolean;
|
|
18
21
|
reason: string;
|
|
@@ -71,7 +74,7 @@ function getHeaderRequestId(request: Request): string | null {
|
|
|
71
74
|
return trimmed.length > 0 ? trimmed : null;
|
|
72
75
|
}
|
|
73
76
|
|
|
74
|
-
function getOrCreateRequestId(request: Request): string {
|
|
77
|
+
export function getOrCreateRequestId(request: Request): string {
|
|
75
78
|
const existing = requestIds.get(request);
|
|
76
79
|
if (existing) return existing;
|
|
77
80
|
|
package/src/router/manifest.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { createRouteHelpers } from "../route-definition";
|
|
|
9
9
|
import {
|
|
10
10
|
getContext,
|
|
11
11
|
runWithPrefixes,
|
|
12
|
+
getIsolatedLazyParent,
|
|
12
13
|
type EntryData,
|
|
13
14
|
type MetricsStore,
|
|
14
15
|
} from "../server/context";
|
|
@@ -65,7 +66,9 @@ export async function loadManifest(
|
|
|
65
66
|
const mountIndex = entry.mountIndex;
|
|
66
67
|
|
|
67
68
|
// Check module-level cache (persists across requests within same isolate)
|
|
68
|
-
|
|
69
|
+
// Include routerId so multi-router setups (host routing) don't share cached
|
|
70
|
+
// EntryData across routers with overlapping mountIndex + routeKey combinations.
|
|
71
|
+
const cacheKey = `${VERSION}:${entry.routerId ?? ""}:${mountIndex ?? ""}:${routeKey}:${isSSR ? 1 : 0}`;
|
|
69
72
|
const cached = manifestModuleCache.get(cacheKey);
|
|
70
73
|
if (cached) {
|
|
71
74
|
const cacheStart = performance.now();
|
|
@@ -112,36 +115,48 @@ export async function loadManifest(
|
|
|
112
115
|
// This ensures routes are registered under the correct layout hierarchy
|
|
113
116
|
const lazyContext =
|
|
114
117
|
entry.lazy && entry.lazyPatterns ? entry.lazyContext : null;
|
|
115
|
-
const parentForContext =
|
|
116
|
-
(
|
|
118
|
+
const parentForContext = lazyContext
|
|
119
|
+
? getIsolatedLazyParent(
|
|
120
|
+
(lazyContext.parent as EntryData | null) ?? Store.parent,
|
|
121
|
+
)
|
|
122
|
+
: Store.parent;
|
|
117
123
|
|
|
118
124
|
// For lazy entries, merge captured counters from include() so the
|
|
119
125
|
// handler's entries get shortCode indices after sibling entries that
|
|
120
126
|
// were created during pattern extraction. This prevents shortCode
|
|
121
127
|
// collisions between lazy and non-lazy entries under the same parent
|
|
122
128
|
// (e.g., ArticlesLayout and BlogLayout both under NavLayout).
|
|
123
|
-
if (lazyContext
|
|
124
|
-
const
|
|
125
|
-
for (const [key, value] of Object.entries(captured)) {
|
|
129
|
+
if (lazyContext?.counters) {
|
|
130
|
+
for (const [key, value] of Object.entries(lazyContext.counters)) {
|
|
126
131
|
Store.counters[key] = Math.max(Store.counters[key] ?? 0, value);
|
|
127
132
|
}
|
|
128
133
|
}
|
|
129
134
|
|
|
130
135
|
// Propagate cache profiles for DSL-time cache("profileName") resolution.
|
|
131
136
|
// Non-lazy entries carry profiles directly; lazy entries carry them
|
|
132
|
-
// in the captured lazyContext from include() time.
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
137
|
+
// in the captured lazyContext from include() time. Always write
|
|
138
|
+
// (including clearing to undefined) so a prior lazy build's profile
|
|
139
|
+
// map cannot leak into a later non-lazy build on the same ALS-backed
|
|
140
|
+
// Store — which would otherwise let cache("name") resolve a profile
|
|
141
|
+
// from an unrelated entry.
|
|
142
|
+
Store.cacheProfiles = entry.cacheProfiles ?? lazyContext?.cacheProfiles;
|
|
138
143
|
|
|
139
144
|
// Propagate rootScoped from lazyContext so that routes inside
|
|
140
145
|
// nested { name: "sub" } under { name: "" } keep inherited root scope
|
|
141
|
-
// when the manifest is rebuilt on each request.
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
146
|
+
// when the manifest is rebuilt on each request. Always write
|
|
147
|
+
// (including clearing to undefined, which makes getRootScoped()
|
|
148
|
+
// return its true default) so a prior lazy build's scope cannot leak
|
|
149
|
+
// into a later non-lazy build on the same ALS-backed Store — which
|
|
150
|
+
// would otherwise mis-register plain routes as non-root-scoped and
|
|
151
|
+
// break dot-local reverse resolution.
|
|
152
|
+
Store.rootScoped = lazyContext?.rootScoped;
|
|
153
|
+
|
|
154
|
+
// Propagate includeScope from lazyContext so that direct-descendant
|
|
155
|
+
// shortCodes of this include use the correct scoped counter namespace
|
|
156
|
+
// on every manifest rebuild. Always write (including clearing to
|
|
157
|
+
// undefined) so a prior lazy build's scope cannot leak into a later
|
|
158
|
+
// non-lazy build on the same ALS-backed Store.
|
|
159
|
+
Store.includeScope = lazyContext?.includeScope;
|
|
145
160
|
|
|
146
161
|
const handlerExecStart = performance.now();
|
|
147
162
|
const useItems = await getContext().runWithStore(
|