@rangojs/router 0.0.0-experimental.8 → 0.0.0-experimental.80
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 +942 -4
- package/dist/bin/rango.js +1689 -0
- package/dist/vite/index.js +4960 -935
- package/package.json +70 -60
- 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/handler-use/SKILL.md +362 -0
- package/skills/hooks/SKILL.md +334 -72
- package/skills/host-router/SKILL.md +218 -0
- package/skills/intercept/SKILL.md +151 -8
- package/skills/layout/SKILL.md +122 -3
- package/skills/links/SKILL.md +92 -31
- package/skills/loader/SKILL.md +404 -44
- package/skills/middleware/SKILL.md +205 -37
- package/skills/migrate-nextjs/SKILL.md +560 -0
- package/skills/migrate-react-router/SKILL.md +764 -0
- package/skills/mime-routes/SKILL.md +128 -0
- package/skills/parallel/SKILL.md +263 -1
- package/skills/prerender/SKILL.md +685 -0
- package/skills/rango/SKILL.md +87 -16
- package/skills/response-routes/SKILL.md +411 -0
- package/skills/route/SKILL.md +281 -14
- package/skills/router-setup/SKILL.md +210 -32
- 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/app-version.ts +14 -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 +317 -560
- package/src/browser/navigation-client.ts +206 -68
- package/src/browser/navigation-store.ts +73 -55
- package/src/browser/navigation-transaction.ts +297 -0
- package/src/browser/network-error-handler.ts +61 -0
- package/src/browser/partial-update.ts +343 -316
- package/src/browser/prefetch/cache.ts +216 -0
- package/src/browser/prefetch/fetch.ts +206 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +48 -0
- package/src/browser/prefetch/queue.ts +160 -0
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +112 -0
- package/src/browser/react/Link.tsx +253 -74
- package/src/browser/react/NavigationProvider.tsx +87 -11
- package/src/browser/react/context.ts +11 -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 +30 -126
- 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 +44 -65
- 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 +76 -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 +214 -58
- package/src/browser/scroll-restoration.ts +127 -52
- package/src/browser/segment-reconciler.ts +243 -0
- package/src/browser/segment-structure-assert.ts +16 -0
- package/src/browser/server-action-bridge.ts +510 -603
- package/src/browser/shallow.ts +6 -1
- package/src/browser/types.ts +141 -48
- package/src/browser/validate-redirect-origin.ts +29 -0
- package/src/build/generate-manifest.ts +235 -24
- package/src/build/generate-route-types.ts +39 -0
- package/src/build/index.ts +13 -0
- package/src/build/route-trie.ts +291 -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 +418 -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 +618 -0
- package/src/build/route-types/scan-filter.ts +85 -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 +135 -301
- package/src/component-utils.ts +4 -4
- package/src/components/DefaultDocument.tsx +5 -1
- package/src/context-var.ts +156 -0
- package/src/debug.ts +19 -9
- package/src/errors.ts +108 -2
- package/src/handle.ts +55 -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 +155 -19
- package/src/index.ts +251 -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 +186 -0
- package/src/prerender.ts +524 -0
- package/src/reverse.ts +354 -0
- package/src/root-error-boundary.tsx +41 -29
- package/src/route-content-wrapper.tsx +7 -4
- package/src/route-definition/dsl-helpers.ts +1121 -0
- package/src/route-definition/helper-factories.ts +200 -0
- package/src/route-definition/helpers-types.ts +478 -0
- package/src/route-definition/index.ts +55 -0
- package/src/route-definition/redirect.ts +101 -0
- package/src/route-definition/resolve-handler-use.ts +149 -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 +77 -8
- package/src/router/content-negotiation.ts +215 -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 +438 -86
- package/src/router/intercept-resolution.ts +402 -0
- package/src/router/lazy-includes.ts +237 -0
- package/src/router/loader-resolution.ts +356 -128
- package/src/router/logging.ts +251 -0
- package/src/router/manifest.ts +163 -35
- package/src/router/match-api.ts +555 -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 +460 -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 +80 -6
- package/src/router/match-pipelines.ts +10 -45
- package/src/router/match-result.ts +135 -35
- package/src/router/metrics.ts +240 -15
- package/src/router/middleware-cookies.ts +55 -0
- package/src/router/middleware-types.ts +220 -0
- package/src/router/middleware.ts +324 -369
- package/src/router/navigation-snapshot.ts +182 -0
- package/src/router/pattern-matching.ts +211 -43
- package/src/router/prerender-match.ts +502 -0
- package/src/router/preview-match.ts +98 -0
- package/src/router/request-classification.ts +310 -0
- package/src/router/revalidation.ts +137 -38
- package/src/router/route-snapshot.ts +245 -0
- package/src/router/router-context.ts +41 -21
- package/src/router/router-interfaces.ts +484 -0
- package/src/router/router-options.ts +618 -0
- package/src/router/router-registry.ts +24 -0
- package/src/router/segment-resolution/fresh.ts +748 -0
- package/src/router/segment-resolution/helpers.ts +268 -0
- package/src/router/segment-resolution/loader-cache.ts +199 -0
- package/src/router/segment-resolution/revalidation.ts +1379 -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 +78 -3
- package/src/router.ts +740 -4252
- package/src/rsc/handler-context.ts +45 -0
- package/src/rsc/handler.ts +907 -797
- package/src/rsc/helpers.ts +140 -6
- package/src/rsc/index.ts +0 -20
- package/src/rsc/loader-fetch.ts +229 -0
- package/src/rsc/manifest-init.ts +90 -0
- package/src/rsc/nonce.ts +14 -0
- package/src/rsc/origin-guard.ts +141 -0
- package/src/rsc/progressive-enhancement.ts +391 -0
- package/src/rsc/response-error.ts +37 -0
- package/src/rsc/response-route-handler.ts +347 -0
- package/src/rsc/rsc-rendering.ts +246 -0
- package/src/rsc/runtime-warnings.ts +42 -0
- package/src/rsc/server-action.ts +356 -0
- package/src/rsc/ssr-setup.ts +128 -0
- package/src/rsc/types.ts +46 -11
- package/src/search-params.ts +230 -0
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +122 -0
- package/src/segment-system.tsx +134 -36
- package/src/server/context.ts +341 -61
- package/src/server/cookie-store.ts +190 -0
- package/src/server/fetchable-loader-store.ts +37 -0
- package/src/server/handle-store.ts +113 -15
- package/src/server/loader-registry.ts +24 -64
- package/src/server/request-context.ts +607 -81
- package/src/server.ts +35 -130
- package/src/ssr/index.tsx +103 -30
- package/src/static-handler.ts +126 -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 +791 -0
- package/src/types/index.ts +88 -0
- package/src/types/loader-types.ts +210 -0
- package/src/types/route-config.ts +170 -0
- package/src/types/route-entry.ts +120 -0
- package/src/types/segments.ts +150 -0
- package/src/types.ts +1 -1623
- package/src/urls/include-helper.ts +207 -0
- package/src/urls/index.ts +53 -0
- package/src/urls/path-helper-types.ts +372 -0
- package/src/urls/path-helper.ts +364 -0
- package/src/urls/pattern-types.ts +107 -0
- package/src/urls/response-types.ts +116 -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 +161 -81
- package/src/vite/discovery/bundle-postprocess.ts +181 -0
- package/src/vite/discovery/discover-routers.ts +348 -0
- package/src/vite/discovery/prerender-collection.ts +439 -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 +117 -0
- package/src/vite/discovery/virtual-module-codegen.ts +203 -0
- package/src/vite/index.ts +15 -1133
- package/src/vite/plugin-types.ts +103 -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 +299 -0
- package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
- package/src/vite/plugins/expose-ids/handler-transform.ts +209 -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 +786 -0
- package/src/vite/plugins/performance-tracks.ts +88 -0
- package/src/vite/plugins/refresh-cmd.ts +127 -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} +23 -14
- package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
- package/src/vite/rango.ts +462 -0
- package/src/vite/router-discovery.ts +918 -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 +221 -0
- package/src/vite/utils/shared-utils.ts +170 -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
|
@@ -104,6 +104,8 @@ import type { ResolvedSegment } from "../../types.js";
|
|
|
104
104
|
import { getRequestContext } from "../../server/request-context.js";
|
|
105
105
|
import type { MatchContext, MatchPipelineState } from "../match-context.js";
|
|
106
106
|
import { getRouterContext } from "../router-context.js";
|
|
107
|
+
import { debugLog, debugWarn, getOrCreateRequestId } from "../logging.js";
|
|
108
|
+
import { INTERNAL_RANGO_DEBUG } from "../../internal-debug.js";
|
|
107
109
|
import type { GeneratorMiddleware } from "./cache-lookup.js";
|
|
108
110
|
|
|
109
111
|
/**
|
|
@@ -119,6 +121,8 @@ export function withCacheStore<TEnv>(
|
|
|
119
121
|
return async function* (
|
|
120
122
|
source: AsyncGenerator<ResolvedSegment>,
|
|
121
123
|
): AsyncGenerator<ResolvedSegment> {
|
|
124
|
+
const ms = ctx.metricsStore;
|
|
125
|
+
|
|
122
126
|
// Collect all segments while passing them through
|
|
123
127
|
const allSegments: ResolvedSegment[] = [];
|
|
124
128
|
for await (const segment of source) {
|
|
@@ -126,6 +130,9 @@ export function withCacheStore<TEnv>(
|
|
|
126
130
|
yield segment;
|
|
127
131
|
}
|
|
128
132
|
|
|
133
|
+
// Measure own work only (after source iteration completes)
|
|
134
|
+
const ownStart = performance.now();
|
|
135
|
+
|
|
129
136
|
// Skip caching if:
|
|
130
137
|
// 1. Cache miss but cache scope is disabled
|
|
131
138
|
// 2. This is an action (actions don't cache)
|
|
@@ -137,38 +144,54 @@ export function withCacheStore<TEnv>(
|
|
|
137
144
|
state.cacheHit ||
|
|
138
145
|
ctx.request.method !== "GET"
|
|
139
146
|
) {
|
|
147
|
+
if (ms) {
|
|
148
|
+
ms.metrics.push({
|
|
149
|
+
label: "pipeline:cache-store",
|
|
150
|
+
duration: performance.now() - ownStart,
|
|
151
|
+
startTime: ownStart - ms.requestStart,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
140
154
|
return;
|
|
141
155
|
}
|
|
142
156
|
|
|
143
157
|
const {
|
|
144
158
|
createHandlerContext,
|
|
145
|
-
|
|
159
|
+
setupLoaderAccess,
|
|
146
160
|
resolveAllSegments,
|
|
147
161
|
resolveInterceptEntry,
|
|
162
|
+
createHandleStore,
|
|
148
163
|
} = getRouterContext<TEnv>();
|
|
149
164
|
|
|
150
165
|
// Combine main segments with intercept segments
|
|
151
166
|
const allSegmentsToCache = [...allSegments, ...state.interceptSegments];
|
|
152
167
|
|
|
153
|
-
// Check if any non-loader segments have null components
|
|
154
|
-
//
|
|
168
|
+
// Check if any non-loader segments have null components from revalidation
|
|
169
|
+
// skip (client already had them). Segments where the handler intentionally
|
|
170
|
+
// returned null are not revalidation skips — re-rendering them will still
|
|
171
|
+
// produce null, so proactive caching would be wasted work.
|
|
172
|
+
const clientIdSet = new Set(ctx.clientSegmentIds);
|
|
155
173
|
const hasNullComponents = allSegmentsToCache.some(
|
|
156
|
-
(s) =>
|
|
174
|
+
(s) =>
|
|
175
|
+
s.component === null && s.type !== "loader" && clientIdSet.has(s.id),
|
|
157
176
|
);
|
|
158
177
|
|
|
159
178
|
const requestCtx = getRequestContext();
|
|
160
179
|
if (!requestCtx) return;
|
|
161
180
|
|
|
162
181
|
const cacheScope = ctx.cacheScope;
|
|
182
|
+
const reqId = INTERNAL_RANGO_DEBUG
|
|
183
|
+
? getOrCreateRequestId(ctx.request)
|
|
184
|
+
: undefined;
|
|
163
185
|
|
|
164
186
|
// Register onResponse callback to skip caching for non-200 responses
|
|
165
187
|
// Note: error/notFound status codes are set elsewhere (not caching-specific)
|
|
166
188
|
requestCtx.onResponse((response) => {
|
|
167
189
|
// Only cache successful responses
|
|
168
190
|
if (response.status !== 200) {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
191
|
+
debugLog("cacheStore", "skipping cache for non-200 response", {
|
|
192
|
+
status: response.status,
|
|
193
|
+
pathname: ctx.pathname,
|
|
194
|
+
});
|
|
172
195
|
return response;
|
|
173
196
|
}
|
|
174
197
|
|
|
@@ -176,31 +199,43 @@ export function withCacheStore<TEnv>(
|
|
|
176
199
|
// Proactive caching: render all segments fresh in background
|
|
177
200
|
// This ensures cache has complete components for future requests
|
|
178
201
|
requestCtx.waitUntil(async () => {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
202
|
+
// Prevent background metrics from polluting foreground timeline.
|
|
203
|
+
const savedMetrics = ctx.Store.metrics;
|
|
204
|
+
ctx.Store.metrics = undefined;
|
|
205
|
+
|
|
206
|
+
const start = performance.now();
|
|
207
|
+
debugLog("cacheStore", "proactive caching started", {
|
|
208
|
+
pathname: ctx.pathname,
|
|
209
|
+
});
|
|
210
|
+
// Swap to a fresh HandleStore so handle.push() calls from
|
|
211
|
+
// proactive resolution are captured (not silenced). The original
|
|
212
|
+
// store's stream is already sent by waitUntil time.
|
|
213
|
+
// cacheRoute reads from requestCtx._handleStore, so this ensures
|
|
214
|
+
// complete handle data (e.g. breadcrumbs) is cached.
|
|
215
|
+
const originalHandleStore = requestCtx._handleStore;
|
|
216
|
+
requestCtx._handleStore = createHandleStore();
|
|
182
217
|
try {
|
|
183
218
|
// Create fresh context for proactive caching
|
|
184
|
-
// This prevents handle data from polluting the response stream
|
|
185
219
|
const proactiveHandlerContext = createHandlerContext(
|
|
186
220
|
ctx.matched.params,
|
|
187
221
|
ctx.request,
|
|
188
222
|
ctx.url.searchParams,
|
|
189
223
|
ctx.pathname,
|
|
190
224
|
ctx.url,
|
|
191
|
-
ctx.
|
|
225
|
+
ctx.env,
|
|
192
226
|
ctx.routeMap,
|
|
193
|
-
ctx.matched.routeKey
|
|
227
|
+
ctx.matched.routeKey,
|
|
228
|
+
ctx.matched.responseType,
|
|
229
|
+
ctx.matched.pt === true,
|
|
194
230
|
);
|
|
195
231
|
const proactiveLoaderPromises = new Map<string, Promise<any>>();
|
|
196
232
|
|
|
197
|
-
//
|
|
198
|
-
|
|
199
|
-
proactiveHandlerContext,
|
|
200
|
-
proactiveLoaderPromises,
|
|
201
|
-
);
|
|
233
|
+
// Use normal loader access so handle data is captured
|
|
234
|
+
setupLoaderAccess(proactiveHandlerContext, proactiveLoaderPromises);
|
|
202
235
|
|
|
203
|
-
// Re-resolve ALL segments without revalidation
|
|
236
|
+
// Re-resolve ALL segments without revalidation.
|
|
237
|
+
// Skip DSL loaders — they are never cached (cacheRoute filters them)
|
|
238
|
+
// and are always resolved fresh on each request.
|
|
204
239
|
const Store = ctx.Store;
|
|
205
240
|
const freshSegments = await Store.run(() =>
|
|
206
241
|
resolveAllSegments(
|
|
@@ -209,6 +244,7 @@ export function withCacheStore<TEnv>(
|
|
|
209
244
|
ctx.matched.params,
|
|
210
245
|
proactiveHandlerContext,
|
|
211
246
|
proactiveLoaderPromises,
|
|
247
|
+
{ skipLoaders: true },
|
|
212
248
|
),
|
|
213
249
|
);
|
|
214
250
|
|
|
@@ -231,36 +267,72 @@ export function withCacheStore<TEnv>(
|
|
|
231
267
|
...freshSegments,
|
|
232
268
|
...freshInterceptSegments,
|
|
233
269
|
];
|
|
270
|
+
requestCtx._handleStore.seal();
|
|
234
271
|
await cacheScope.cacheRoute(
|
|
235
272
|
ctx.pathname,
|
|
236
273
|
ctx.matched.params,
|
|
237
274
|
completeSegments,
|
|
238
275
|
ctx.isIntercept,
|
|
239
276
|
);
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
277
|
+
if (INTERNAL_RANGO_DEBUG) {
|
|
278
|
+
const dur = performance.now() - start;
|
|
279
|
+
console.log(
|
|
280
|
+
`[RSC Background][req:${reqId}] Proactive cache ${ctx.pathname} (${dur.toFixed(2)}ms) segments=${completeSegments.length}`,
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
debugLog("cacheStore", "proactive caching complete", {
|
|
284
|
+
pathname: ctx.pathname,
|
|
285
|
+
});
|
|
243
286
|
} catch (error) {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
287
|
+
if (INTERNAL_RANGO_DEBUG) {
|
|
288
|
+
const dur = performance.now() - start;
|
|
289
|
+
console.log(
|
|
290
|
+
`[RSC Background][req:${reqId}] Proactive cache ${ctx.pathname} FAILED (${dur.toFixed(2)}ms) error=${String(error)}`,
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
debugWarn("cacheStore", "proactive caching failed", {
|
|
294
|
+
pathname: ctx.pathname,
|
|
295
|
+
error: String(error),
|
|
296
|
+
});
|
|
297
|
+
} finally {
|
|
298
|
+
requestCtx._handleStore = originalHandleStore;
|
|
299
|
+
ctx.Store.metrics = savedMetrics;
|
|
248
300
|
}
|
|
249
301
|
});
|
|
250
302
|
} else {
|
|
251
303
|
// All segments have components - cache directly
|
|
252
304
|
// Schedule caching in waitUntil since cacheRoute is now async (key resolution)
|
|
305
|
+
if (INTERNAL_RANGO_DEBUG) {
|
|
306
|
+
console.log(
|
|
307
|
+
`[RSC CacheStore][req:${reqId}] Direct cache path: scheduling cacheRoute for ${ctx.pathname} (${allSegmentsToCache.length} segments, hasNullComponents=${hasNullComponents})`,
|
|
308
|
+
);
|
|
309
|
+
}
|
|
253
310
|
requestCtx.waitUntil(async () => {
|
|
311
|
+
const start = performance.now();
|
|
254
312
|
await cacheScope.cacheRoute(
|
|
255
313
|
ctx.pathname,
|
|
256
314
|
ctx.matched.params,
|
|
257
315
|
allSegmentsToCache,
|
|
258
316
|
ctx.isIntercept,
|
|
259
317
|
);
|
|
318
|
+
if (INTERNAL_RANGO_DEBUG) {
|
|
319
|
+
const dur = performance.now() - start;
|
|
320
|
+
console.log(
|
|
321
|
+
`[RSC Background][req:${reqId}] Cache store ${ctx.pathname} (${dur.toFixed(2)}ms) segments=${allSegmentsToCache.length}`,
|
|
322
|
+
);
|
|
323
|
+
}
|
|
260
324
|
});
|
|
261
325
|
}
|
|
262
326
|
|
|
263
327
|
return response;
|
|
264
328
|
});
|
|
329
|
+
|
|
330
|
+
if (ms) {
|
|
331
|
+
ms.metrics.push({
|
|
332
|
+
label: "pipeline:cache-store",
|
|
333
|
+
duration: performance.now() - ownStart,
|
|
334
|
+
startTime: ownStart - ms.requestStart,
|
|
335
|
+
});
|
|
336
|
+
}
|
|
265
337
|
};
|
|
266
338
|
}
|
|
@@ -105,6 +105,7 @@ import type { ResolvedSegment } from "../../types.js";
|
|
|
105
105
|
import type { MatchContext, MatchPipelineState } from "../match-context.js";
|
|
106
106
|
import { getRouterContext } from "../router-context.js";
|
|
107
107
|
import type { GeneratorMiddleware } from "./cache-lookup.js";
|
|
108
|
+
import { debugLog } from "../logging.js";
|
|
108
109
|
|
|
109
110
|
/**
|
|
110
111
|
* Creates intercept resolution middleware
|
|
@@ -117,11 +118,13 @@ import type { GeneratorMiddleware } from "./cache-lookup.js";
|
|
|
117
118
|
*/
|
|
118
119
|
export function withInterceptResolution<TEnv>(
|
|
119
120
|
ctx: MatchContext<TEnv>,
|
|
120
|
-
state: MatchPipelineState
|
|
121
|
+
state: MatchPipelineState,
|
|
121
122
|
): GeneratorMiddleware<ResolvedSegment> {
|
|
122
123
|
return async function* (
|
|
123
|
-
source: AsyncGenerator<ResolvedSegment
|
|
124
|
+
source: AsyncGenerator<ResolvedSegment>,
|
|
124
125
|
): AsyncGenerator<ResolvedSegment> {
|
|
126
|
+
const ms = ctx.metricsStore;
|
|
127
|
+
|
|
125
128
|
// First, yield all segments from the source (main segment resolution or cache)
|
|
126
129
|
const segments: ResolvedSegment[] = [];
|
|
127
130
|
for await (const segment of source) {
|
|
@@ -129,8 +132,18 @@ export function withInterceptResolution<TEnv>(
|
|
|
129
132
|
yield segment;
|
|
130
133
|
}
|
|
131
134
|
|
|
135
|
+
// Measure own work only (after source iteration completes)
|
|
136
|
+
const ownStart = performance.now();
|
|
137
|
+
|
|
132
138
|
// Skip intercept resolution for full match (document requests don't have intercepts)
|
|
133
139
|
if (ctx.isFullMatch) {
|
|
140
|
+
if (ms) {
|
|
141
|
+
ms.metrics.push({
|
|
142
|
+
label: "pipeline:intercept",
|
|
143
|
+
duration: performance.now() - ownStart,
|
|
144
|
+
startTime: ownStart - ms.requestStart,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
134
147
|
return;
|
|
135
148
|
}
|
|
136
149
|
|
|
@@ -149,6 +162,13 @@ export function withInterceptResolution<TEnv>(
|
|
|
149
162
|
if (ctx.interceptResult && state.cacheHit && ctx.isIntercept) {
|
|
150
163
|
await handleCacheHitIntercept(ctx, state, segments);
|
|
151
164
|
}
|
|
165
|
+
if (ms) {
|
|
166
|
+
ms.metrics.push({
|
|
167
|
+
label: "pipeline:intercept",
|
|
168
|
+
duration: performance.now() - ownStart,
|
|
169
|
+
startTime: ownStart - ms.requestStart,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
152
172
|
return;
|
|
153
173
|
}
|
|
154
174
|
|
|
@@ -156,9 +176,10 @@ export function withInterceptResolution<TEnv>(
|
|
|
156
176
|
const { resolveInterceptEntry } = getRouterContext<TEnv>();
|
|
157
177
|
|
|
158
178
|
const slotName = ctx.interceptResult!.intercept.slotName;
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
179
|
+
debugLog("matchPartial.intercept", "intercept resolved", {
|
|
180
|
+
routeName: ctx.localRouteName,
|
|
181
|
+
slotName,
|
|
182
|
+
});
|
|
162
183
|
|
|
163
184
|
// Resolve intercept entry (middleware, loaders, handler)
|
|
164
185
|
const Store = ctx.Store;
|
|
@@ -178,8 +199,8 @@ export function withInterceptResolution<TEnv>(
|
|
|
178
199
|
routeKey: ctx.routeKey,
|
|
179
200
|
actionContext: ctx.actionContext,
|
|
180
201
|
stale: ctx.stale,
|
|
181
|
-
}
|
|
182
|
-
)
|
|
202
|
+
},
|
|
203
|
+
),
|
|
183
204
|
);
|
|
184
205
|
|
|
185
206
|
// Update state
|
|
@@ -193,6 +214,14 @@ export function withInterceptResolution<TEnv>(
|
|
|
193
214
|
for (const segment of interceptSegments) {
|
|
194
215
|
yield segment;
|
|
195
216
|
}
|
|
217
|
+
|
|
218
|
+
if (ms) {
|
|
219
|
+
ms.metrics.push({
|
|
220
|
+
label: "pipeline:intercept",
|
|
221
|
+
duration: performance.now() - ownStart,
|
|
222
|
+
startTime: ownStart - ms.requestStart,
|
|
223
|
+
});
|
|
224
|
+
}
|
|
196
225
|
};
|
|
197
226
|
}
|
|
198
227
|
|
|
@@ -204,7 +233,7 @@ export function withInterceptResolution<TEnv>(
|
|
|
204
233
|
async function handleCacheHitIntercept<TEnv>(
|
|
205
234
|
ctx: MatchContext<TEnv>,
|
|
206
235
|
state: MatchPipelineState,
|
|
207
|
-
segments: ResolvedSegment[]
|
|
236
|
+
segments: ResolvedSegment[],
|
|
208
237
|
): Promise<void> {
|
|
209
238
|
if (!ctx.interceptResult) return;
|
|
210
239
|
|
|
@@ -214,7 +243,7 @@ async function handleCacheHitIntercept<TEnv>(
|
|
|
214
243
|
|
|
215
244
|
// Find intercept segments from cached segments (namespace starts with "intercept:")
|
|
216
245
|
const interceptSegments = segments.filter((s) =>
|
|
217
|
-
s.namespace?.startsWith("intercept:")
|
|
246
|
+
s.namespace?.startsWith("intercept:"),
|
|
218
247
|
);
|
|
219
248
|
state.interceptSegments = interceptSegments;
|
|
220
249
|
|
|
@@ -238,25 +267,36 @@ async function handleCacheHitIntercept<TEnv>(
|
|
|
238
267
|
routeKey: ctx.routeKey,
|
|
239
268
|
actionContext: ctx.actionContext,
|
|
240
269
|
stale: ctx.stale,
|
|
241
|
-
}
|
|
242
|
-
)
|
|
270
|
+
},
|
|
271
|
+
),
|
|
243
272
|
);
|
|
244
273
|
|
|
245
274
|
// Update intercept segment's loaderDataPromise with fresh data
|
|
246
275
|
if (freshLoaderResult) {
|
|
247
276
|
const interceptMainSegment = interceptSegments.find(
|
|
248
|
-
(s) => s.type === "parallel" && s.slot
|
|
277
|
+
(s) => s.type === "parallel" && s.slot,
|
|
249
278
|
);
|
|
250
279
|
if (interceptMainSegment) {
|
|
251
|
-
interceptMainSegment.loaderDataPromise =
|
|
280
|
+
interceptMainSegment.loaderDataPromise =
|
|
281
|
+
freshLoaderResult.loaderDataPromise;
|
|
252
282
|
interceptMainSegment.loaderIds = freshLoaderResult.loaderIds;
|
|
253
|
-
|
|
254
|
-
|
|
283
|
+
debugLog(
|
|
284
|
+
"matchPartial.intercept",
|
|
285
|
+
"cache hit with fresh intercept loaders",
|
|
286
|
+
{
|
|
287
|
+
routeName: ctx.localRouteName,
|
|
288
|
+
slotName,
|
|
289
|
+
},
|
|
255
290
|
);
|
|
256
291
|
}
|
|
257
292
|
} else {
|
|
258
|
-
|
|
259
|
-
|
|
293
|
+
debugLog(
|
|
294
|
+
"matchPartial.intercept",
|
|
295
|
+
"cache hit without intercept loader revalidation",
|
|
296
|
+
{
|
|
297
|
+
routeName: ctx.localRouteName,
|
|
298
|
+
slotName,
|
|
299
|
+
},
|
|
260
300
|
);
|
|
261
301
|
}
|
|
262
302
|
}
|
|
@@ -87,10 +87,49 @@
|
|
|
87
87
|
* if (state.cacheHit) return; // Now we can check
|
|
88
88
|
*/
|
|
89
89
|
import type { ResolvedSegment } from "../../types.js";
|
|
90
|
+
import type { EntryData } from "../../server/context.js";
|
|
91
|
+
import { _getRequestContext } from "../../server/request-context.js";
|
|
90
92
|
import type { MatchContext, MatchPipelineState } from "../match-context.js";
|
|
91
93
|
import { getRouterContext } from "../router-context.js";
|
|
92
94
|
import type { GeneratorMiddleware } from "./cache-lookup.js";
|
|
93
95
|
|
|
96
|
+
/**
|
|
97
|
+
* Check whether any entry in the tree uses loading() (streaming).
|
|
98
|
+
* Matches the router's streaming semantics in fresh.ts: streaming is
|
|
99
|
+
* enabled when `loading` is defined AND not `false`. `loading: false`
|
|
100
|
+
* explicitly disables streaming; `undefined` means no loading at all.
|
|
101
|
+
*/
|
|
102
|
+
export function treeHasStreaming(entries: EntryData[]): boolean {
|
|
103
|
+
for (const entry of entries) {
|
|
104
|
+
if (
|
|
105
|
+
"loading" in entry &&
|
|
106
|
+
entry.loading !== undefined &&
|
|
107
|
+
entry.loading !== false
|
|
108
|
+
)
|
|
109
|
+
return true;
|
|
110
|
+
if (entry.layout) {
|
|
111
|
+
if (treeHasStreaming(entry.layout)) return true;
|
|
112
|
+
}
|
|
113
|
+
if (entry.parallel) {
|
|
114
|
+
for (const key in entry.parallel) {
|
|
115
|
+
const parallelEntry = entry.parallel[key as `@${string}`];
|
|
116
|
+
if (parallelEntry) {
|
|
117
|
+
if (
|
|
118
|
+
"loading" in parallelEntry &&
|
|
119
|
+
parallelEntry.loading !== undefined &&
|
|
120
|
+
parallelEntry.loading !== false
|
|
121
|
+
)
|
|
122
|
+
return true;
|
|
123
|
+
if (parallelEntry.layout) {
|
|
124
|
+
if (treeHasStreaming(parallelEntry.layout)) return true;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
|
|
94
133
|
/**
|
|
95
134
|
* Creates segment resolution middleware
|
|
96
135
|
*
|
|
@@ -99,22 +138,40 @@ import type { GeneratorMiddleware } from "./cache-lookup.js";
|
|
|
99
138
|
*/
|
|
100
139
|
export function withSegmentResolution<TEnv>(
|
|
101
140
|
ctx: MatchContext<TEnv>,
|
|
102
|
-
state: MatchPipelineState
|
|
141
|
+
state: MatchPipelineState,
|
|
103
142
|
): GeneratorMiddleware<ResolvedSegment> {
|
|
104
143
|
return async function* (
|
|
105
|
-
source: AsyncGenerator<ResolvedSegment
|
|
144
|
+
source: AsyncGenerator<ResolvedSegment>,
|
|
106
145
|
): AsyncGenerator<ResolvedSegment> {
|
|
146
|
+
const ms = ctx.metricsStore;
|
|
147
|
+
|
|
107
148
|
// IMPORTANT: Always iterate source first to give cache-lookup a chance
|
|
108
149
|
// to run and set state.cacheHit. Without this, cache-lookup never executes!
|
|
109
150
|
for await (const segment of source) {
|
|
110
151
|
yield segment;
|
|
111
152
|
}
|
|
112
153
|
|
|
154
|
+
// Measure own work only (after source iteration completes)
|
|
155
|
+
const ownStart = performance.now();
|
|
156
|
+
|
|
113
157
|
// If cache hit, segments were already yielded by cache lookup
|
|
158
|
+
// (render barrier is resolved on the cache-hit path)
|
|
114
159
|
if (state.cacheHit) {
|
|
160
|
+
if (ms) {
|
|
161
|
+
ms.metrics.push({
|
|
162
|
+
label: "pipeline:segment-resolve",
|
|
163
|
+
duration: performance.now() - ownStart,
|
|
164
|
+
startTime: ownStart - ms.requestStart,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
115
167
|
return;
|
|
116
168
|
}
|
|
117
169
|
|
|
170
|
+
const reqCtx = _getRequestContext();
|
|
171
|
+
if (reqCtx && reqCtx._treeHasStreaming === undefined) {
|
|
172
|
+
reqCtx._treeHasStreaming = treeHasStreaming(ctx.entries);
|
|
173
|
+
}
|
|
174
|
+
|
|
118
175
|
const { resolveAllSegmentsWithRevalidation, resolveAllSegments } =
|
|
119
176
|
getRouterContext<TEnv>();
|
|
120
177
|
|
|
@@ -128,14 +185,18 @@ export function withSegmentResolution<TEnv>(
|
|
|
128
185
|
ctx.routeKey,
|
|
129
186
|
ctx.matched.params,
|
|
130
187
|
ctx.handlerContext,
|
|
131
|
-
ctx.loaderPromises
|
|
132
|
-
)
|
|
188
|
+
ctx.loaderPromises,
|
|
189
|
+
),
|
|
133
190
|
);
|
|
134
191
|
|
|
135
192
|
// Update state with resolved segments
|
|
136
193
|
state.segments = segments;
|
|
137
194
|
state.matchedIds = segments.map((s: { id: string }) => s.id);
|
|
138
195
|
|
|
196
|
+
if (reqCtx) {
|
|
197
|
+
reqCtx._resolveRenderBarrier(segments);
|
|
198
|
+
}
|
|
199
|
+
|
|
139
200
|
// Yield all resolved segments
|
|
140
201
|
for (const segment of segments) {
|
|
141
202
|
yield segment;
|
|
@@ -157,18 +218,31 @@ export function withSegmentResolution<TEnv>(
|
|
|
157
218
|
ctx.actionContext,
|
|
158
219
|
ctx.interceptResult,
|
|
159
220
|
ctx.localRouteName,
|
|
160
|
-
ctx.pathname
|
|
161
|
-
|
|
221
|
+
ctx.pathname,
|
|
222
|
+
ctx.stale,
|
|
223
|
+
),
|
|
162
224
|
);
|
|
163
225
|
|
|
164
226
|
// Update state with resolved segments
|
|
165
227
|
state.segments = result.segments;
|
|
166
228
|
state.matchedIds = result.matchedIds;
|
|
167
229
|
|
|
230
|
+
if (reqCtx) {
|
|
231
|
+
reqCtx._resolveRenderBarrier(result.segments);
|
|
232
|
+
}
|
|
233
|
+
|
|
168
234
|
// Yield all resolved segments
|
|
169
235
|
for (const segment of result.segments) {
|
|
170
236
|
yield segment;
|
|
171
237
|
}
|
|
172
238
|
}
|
|
239
|
+
|
|
240
|
+
if (ms) {
|
|
241
|
+
ms.metrics.push({
|
|
242
|
+
label: "pipeline:segment-resolve",
|
|
243
|
+
duration: performance.now() - ownStart,
|
|
244
|
+
startTime: ownStart - ms.requestStart,
|
|
245
|
+
});
|
|
246
|
+
}
|
|
173
247
|
};
|
|
174
248
|
}
|
|
@@ -86,19 +86,14 @@
|
|
|
86
86
|
* -> output: cached segments + fresh loader data
|
|
87
87
|
*
|
|
88
88
|
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
* 2. createMatchPartialPipeline (Partial Match)
|
|
98
|
-
* - Used for client-side navigation
|
|
99
|
-
* - Includes revalidation for SWR
|
|
100
|
-
* - Compares with previous params/URL
|
|
101
|
-
* - Supports intercepts (soft navigation modals)
|
|
89
|
+
* PIPELINE VARIANT
|
|
90
|
+
* ================
|
|
91
|
+
*
|
|
92
|
+
* createMatchPartialPipeline handles both full (document) and partial
|
|
93
|
+
* (navigation) requests. The middleware steps adapt based on ctx.isFullMatch:
|
|
94
|
+
* - cache-lookup/store work for both
|
|
95
|
+
* - background-revalidation is a no-op for full matches (no stale state)
|
|
96
|
+
* - intercept-resolution is a no-op for full matches (no previous navigation)
|
|
102
97
|
*/
|
|
103
98
|
import type { ResolvedSegment } from "../types.js";
|
|
104
99
|
import type { MatchContext, MatchPipelineState } from "./match-context.js";
|
|
@@ -163,7 +158,7 @@ export async function* empty<T>(): AsyncGenerator<T> {
|
|
|
163
158
|
*/
|
|
164
159
|
export function createMatchPartialPipeline<TEnv>(
|
|
165
160
|
ctx: MatchContext<TEnv>,
|
|
166
|
-
state: MatchPipelineState
|
|
161
|
+
state: MatchPipelineState,
|
|
167
162
|
): AsyncGenerator<ResolvedSegment> {
|
|
168
163
|
// Build the middleware chain
|
|
169
164
|
const pipeline = compose<ResolvedSegment>(
|
|
@@ -176,39 +171,9 @@ export function createMatchPartialPipeline<TEnv>(
|
|
|
176
171
|
// Resolves segments on cache miss
|
|
177
172
|
withSegmentResolution(ctx, state),
|
|
178
173
|
// Innermost - checks cache first
|
|
179
|
-
withCacheLookup(ctx, state)
|
|
174
|
+
withCacheLookup(ctx, state),
|
|
180
175
|
);
|
|
181
176
|
|
|
182
177
|
// Start with empty source - cache lookup or segment resolution will produce segments
|
|
183
178
|
return pipeline(empty());
|
|
184
179
|
}
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Create the full match pipeline (simpler, no revalidation)
|
|
188
|
-
*
|
|
189
|
-
* Used for document requests (initial page load) where we don't need
|
|
190
|
-
* revalidation logic since there's no previous state to compare against.
|
|
191
|
-
*/
|
|
192
|
-
export function createMatchPipeline<TEnv>(
|
|
193
|
-
ctx: MatchContext<TEnv>,
|
|
194
|
-
state: MatchPipelineState
|
|
195
|
-
): AsyncGenerator<ResolvedSegment> {
|
|
196
|
-
// For full match, we only need:
|
|
197
|
-
// 1. Cache lookup
|
|
198
|
-
// 2. Segment resolution (without revalidation)
|
|
199
|
-
// 3. Intercept resolution
|
|
200
|
-
// 4. Cache store
|
|
201
|
-
|
|
202
|
-
// Note: Full match uses different resolution logic (resolveAllSegments instead of
|
|
203
|
-
// resolveAllSegmentsWithRevalidation). This will be handled by the segment resolution
|
|
204
|
-
// middleware checking ctx.isFullMatch or similar flag.
|
|
205
|
-
|
|
206
|
-
const pipeline = compose<ResolvedSegment>(
|
|
207
|
-
withCacheStore(ctx, state),
|
|
208
|
-
withInterceptResolution(ctx, state),
|
|
209
|
-
withSegmentResolution(ctx, state),
|
|
210
|
-
withCacheLookup(ctx, state)
|
|
211
|
-
);
|
|
212
|
-
|
|
213
|
-
return pipeline(empty());
|
|
214
|
-
}
|