@rangojs/router 0.0.0-experimental.98 → 0.0.0-experimental.98914650
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 +24 -9
- package/dist/bin/rango.js +157 -63
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +1584 -639
- package/package.json +60 -11
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +60 -0
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +222 -30
- package/skills/caching/SKILL.md +263 -8
- package/skills/composability/SKILL.md +27 -2
- package/skills/css/SKILL.md +76 -0
- package/skills/document-cache/SKILL.md +78 -55
- package/skills/handler-use/SKILL.md +3 -1
- package/skills/hooks/SKILL.md +235 -28
- package/skills/host-router/SKILL.md +122 -22
- package/skills/intercept/SKILL.md +29 -5
- package/skills/layout/SKILL.md +13 -9
- package/skills/links/SKILL.md +173 -17
- package/skills/loader/SKILL.md +170 -23
- package/skills/middleware/SKILL.md +16 -10
- package/skills/migrate-nextjs/SKILL.md +38 -16
- package/skills/mime-routes/SKILL.md +27 -0
- package/skills/observability/SKILL.md +137 -0
- package/skills/parallel/SKILL.md +11 -7
- package/skills/prerender/SKILL.md +14 -33
- package/skills/rango/SKILL.md +250 -26
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +114 -47
- package/skills/route/SKILL.md +22 -5
- package/skills/router-setup/SKILL.md +3 -3
- package/skills/server-actions/SKILL.md +78 -42
- package/skills/tailwind/SKILL.md +27 -3
- package/skills/testing/SKILL.md +129 -0
- package/skills/testing/bindings.md +89 -0
- package/skills/testing/cache-prerender.md +124 -0
- package/skills/testing/client-components.md +122 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +92 -0
- package/skills/testing/handles.md +129 -0
- package/skills/testing/loader.md +128 -0
- package/skills/testing/middleware.md +99 -0
- package/skills/testing/render-handler.md +121 -0
- package/skills/testing/response-routes.md +95 -0
- package/skills/testing/reverse-and-types.md +84 -0
- package/skills/testing/server-actions.md +107 -0
- package/skills/testing/server-tree.md +128 -0
- package/skills/testing/setup.md +120 -0
- package/skills/typesafety/SKILL.md +310 -26
- package/skills/use-cache/SKILL.md +36 -5
- package/skills/vercel/SKILL.md +107 -0
- package/skills/view-transitions/SKILL.md +294 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +116 -0
- package/src/__internal.ts +0 -65
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +14 -27
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +37 -143
- package/src/browser/history-state.ts +21 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/navigation-bridge.ts +30 -59
- package/src/browser/navigation-client.ts +96 -84
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +32 -82
- package/src/browser/navigation-transaction.ts +9 -59
- package/src/browser/partial-update.ts +60 -127
- package/src/browser/prefetch/cache.ts +82 -72
- package/src/browser/prefetch/fetch.ts +108 -33
- package/src/browser/prefetch/queue.ts +6 -3
- package/src/browser/rango-state.ts +157 -115
- package/src/browser/react/Link.tsx +0 -2
- package/src/browser/react/NavigationProvider.tsx +41 -48
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/filter-segment-order.ts +0 -2
- package/src/browser/react/index.ts +0 -48
- package/src/browser/react/location-state-shared.ts +166 -8
- package/src/browser/react/location-state.ts +39 -14
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +17 -14
- package/src/browser/react/use-link-status.ts +0 -4
- package/src/browser/react/use-navigation.ts +0 -3
- package/src/browser/react/use-params.ts +3 -6
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +20 -5
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +0 -13
- package/src/browser/response-adapter.ts +52 -1
- package/src/browser/rsc-router.tsx +70 -34
- package/src/browser/scroll-restoration.ts +22 -14
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +168 -44
- package/src/browser/types.ts +36 -21
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +60 -35
- package/src/build/generate-route-types.ts +3 -0
- package/src/build/index.ts +8 -2
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +89 -11
- package/src/build/route-types/codegen.ts +4 -4
- package/src/build/route-types/include-resolution.ts +1 -1
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/per-module-writer.ts +7 -4
- package/src/build/route-types/router-processing.ts +122 -22
- package/src/build/route-types/scan-filter.ts +1 -1
- package/src/build/route-types/source-scan.ts +118 -0
- package/src/build/runtime-discovery.ts +9 -20
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-policy.ts +68 -28
- package/src/cache/cache-runtime.ts +134 -32
- package/src/cache/cache-scope.ts +100 -74
- package/src/cache/cache-tag.ts +98 -0
- package/src/cache/cf/cf-cache-store.ts +2255 -238
- package/src/cache/cf/index.ts +6 -16
- package/src/cache/document-cache.ts +61 -20
- package/src/cache/handle-snapshot.ts +63 -0
- package/src/cache/index.ts +22 -20
- package/src/cache/memory-segment-store.ts +136 -37
- package/src/cache/profile-registry.ts +6 -30
- package/src/cache/read-through-swr.ts +41 -11
- package/src/cache/segment-codec.ts +0 -16
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/types.ts +33 -100
- package/src/cache/vercel/index.ts +11 -0
- package/src/cache/vercel/vercel-cache-store.ts +799 -0
- package/src/client.rsc.tsx +6 -21
- package/src/client.tsx +25 -61
- package/src/component-utils.ts +19 -0
- package/src/context-var.ts +17 -5
- package/src/decode-loader-results.ts +36 -0
- package/src/defer.ts +196 -0
- package/src/deps/ssr.ts +0 -1
- package/src/errors.ts +30 -4
- package/src/handle.ts +31 -23
- package/src/handles/MetaTags.tsx +0 -14
- package/src/handles/breadcrumbs.ts +16 -5
- package/src/handles/meta.ts +0 -39
- package/src/host/cookie-handler.ts +0 -36
- package/src/host/errors.ts +0 -24
- package/src/host/index.ts +8 -2
- package/src/host/pattern-matcher.ts +7 -50
- package/src/host/router.ts +107 -99
- package/src/host/testing.ts +40 -27
- package/src/host/types.ts +37 -4
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +137 -22
- package/src/index.rsc.ts +63 -9
- package/src/index.ts +64 -9
- package/src/internal-debug.ts +2 -4
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +20 -13
- package/src/loader.ts +12 -11
- package/src/missing-id-error.ts +68 -0
- package/src/network-error-thrower.tsx +1 -6
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +10 -11
- package/src/prerender/store.ts +32 -37
- package/src/prerender.ts +61 -6
- package/src/redirect-origin.ts +100 -0
- package/src/response-utils.ts +9 -0
- package/src/reverse.ts +65 -41
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +7 -72
- package/src/route-definition/dsl-helpers.ts +244 -281
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +40 -17
- package/src/route-definition/redirect.ts +43 -9
- package/src/route-definition/resolve-handler-use.ts +6 -0
- package/src/route-definition/use-item-types.ts +32 -0
- package/src/route-map-builder.ts +0 -16
- package/src/route-types.ts +19 -41
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +15 -15
- package/src/router/error-handling.ts +13 -17
- package/src/router/find-match.ts +44 -23
- package/src/router/handler-context.ts +4 -42
- package/src/router/intercept-resolution.ts +14 -19
- package/src/router/lazy-includes.ts +9 -46
- package/src/router/loader-resolution.ts +91 -46
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +18 -29
- package/src/router/match-api.ts +0 -20
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +57 -58
- package/src/router/match-middleware/background-revalidation.ts +0 -7
- package/src/router/match-middleware/cache-lookup.ts +150 -271
- package/src/router/match-middleware/cache-store.ts +3 -33
- package/src/router/match-middleware/intercept-resolution.ts +0 -22
- package/src/router/match-middleware/segment-resolution.ts +0 -22
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +31 -80
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-types.ts +0 -116
- package/src/router/middleware.ts +118 -133
- package/src/router/navigation-snapshot.ts +0 -51
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +20 -58
- package/src/router/prerender-match.ts +99 -63
- package/src/router/preview-match.ts +3 -1
- package/src/router/request-classification.ts +28 -62
- package/src/router/revalidation.ts +50 -56
- package/src/router/route-snapshot.ts +0 -1
- package/src/router/router-context.ts +0 -27
- package/src/router/router-interfaces.ts +68 -35
- package/src/router/router-options.ts +55 -1
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +44 -63
- package/src/router/segment-resolution/helpers.ts +34 -0
- package/src/router/segment-resolution/loader-cache.ts +40 -37
- package/src/router/segment-resolution/revalidation.ts +203 -285
- package/src/router/segment-resolution/static-store.ts +19 -5
- package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
- package/src/router/segment-resolution/view-transition-default.ts +36 -0
- package/src/router/segment-resolution.ts +4 -1
- package/src/router/segment-wrappers.ts +0 -3
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry-otel.ts +0 -20
- package/src/router/telemetry.ts +96 -19
- package/src/router/timeout.ts +0 -20
- package/src/router/trie-matching.ts +87 -47
- package/src/router/types.ts +9 -63
- package/src/router/url-params.ts +0 -5
- package/src/router.ts +80 -41
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +83 -78
- package/src/rsc/helpers.ts +93 -5
- package/src/rsc/index.ts +1 -1
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/manifest-init.ts +28 -41
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +12 -1
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +76 -62
- package/src/rsc/rsc-rendering.ts +41 -60
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +62 -67
- package/src/rsc/ssr-setup.ts +16 -0
- package/src/rsc/types.ts +10 -5
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +4 -20
- package/src/segment-loader-promise.ts +14 -2
- package/src/segment-system.tsx +199 -142
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +150 -51
- package/src/server/cookie-store.ts +80 -5
- package/src/server/handle-store.ts +7 -24
- package/src/server/loader-registry.ts +5 -24
- package/src/server/request-context.ts +165 -87
- package/src/ssr/index.tsx +14 -14
- package/src/static-handler.ts +10 -13
- package/src/testing/cache-status.ts +162 -0
- package/src/testing/collect-handle.ts +40 -0
- package/src/testing/dispatch.ts +618 -0
- package/src/testing/dom.entry.ts +22 -0
- package/src/testing/e2e/fixture.ts +188 -0
- package/src/testing/e2e/index.ts +128 -0
- package/src/testing/e2e/matchers.ts +35 -0
- package/src/testing/e2e/page-helpers.ts +272 -0
- package/src/testing/e2e/parity.ts +387 -0
- package/src/testing/e2e/server.ts +195 -0
- package/src/testing/flight-matchers.ts +97 -0
- package/src/testing/flight-normalize.ts +11 -0
- package/src/testing/flight-runtime.d.ts +57 -0
- package/src/testing/flight-tree.ts +682 -0
- package/src/testing/flight.entry.ts +52 -0
- package/src/testing/flight.ts +232 -0
- package/src/testing/generated-routes.ts +183 -0
- package/src/testing/index.ts +99 -0
- package/src/testing/internal/context.ts +348 -0
- package/src/testing/internal/flight-client-globals.ts +30 -0
- package/src/testing/internal/seed-vars.ts +54 -0
- package/src/testing/render-handler.ts +330 -0
- package/src/testing/render-route.tsx +566 -0
- package/src/testing/run-loader.ts +378 -0
- package/src/testing/run-middleware.ts +205 -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 +305 -0
- package/src/theme/ThemeProvider.tsx +0 -52
- package/src/theme/ThemeScript.tsx +0 -6
- package/src/theme/constants.ts +0 -12
- package/src/theme/index.ts +0 -7
- package/src/theme/theme-context.ts +1 -5
- package/src/theme/theme-script.ts +0 -14
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +0 -35
- package/src/types/cache-types.ts +13 -4
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +54 -41
- package/src/types/handler-context.ts +97 -22
- package/src/types/index.ts +1 -10
- package/src/types/loader-types.ts +6 -3
- package/src/types/request-scope.ts +0 -19
- package/src/types/route-config.ts +6 -50
- package/src/types/route-entry.ts +0 -6
- package/src/types/segments.ts +18 -14
- package/src/urls/include-helper.ts +9 -56
- package/src/urls/index.ts +1 -11
- package/src/urls/path-helper-types.ts +19 -5
- package/src/urls/path-helper.ts +17 -106
- package/src/urls/pattern-types.ts +36 -19
- package/src/urls/response-types.ts +20 -19
- package/src/urls/type-extraction.ts +58 -139
- package/src/urls/urls-function.ts +1 -18
- package/src/use-loader.tsx +292 -107
- package/src/vite/debug.ts +1 -0
- package/src/vite/discovery/bundle-postprocess.ts +8 -7
- package/src/vite/discovery/discover-routers.ts +95 -82
- package/src/vite/discovery/discovery-errors.ts +194 -0
- package/src/vite/discovery/prerender-collection.ts +26 -34
- package/src/vite/discovery/route-types-writer.ts +40 -84
- package/src/vite/discovery/state.ts +39 -1
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +4 -0
- package/src/vite/plugin-types.ts +185 -10
- package/src/vite/plugins/cjs-to-esm.ts +3 -18
- package/src/vite/plugins/client-ref-dedup.ts +0 -11
- package/src/vite/plugins/client-ref-hashing.ts +12 -11
- package/src/vite/plugins/cloudflare-protocol-stub.ts +1 -21
- package/src/vite/plugins/expose-action-id.ts +4 -75
- package/src/vite/plugins/expose-id-utils.ts +3 -54
- package/src/vite/plugins/expose-ids/export-analysis.ts +76 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +6 -74
- package/src/vite/plugins/expose-ids/loader-transform.ts +3 -20
- package/src/vite/plugins/expose-ids/router-transform.ts +0 -13
- package/src/vite/plugins/expose-internal-ids.ts +57 -67
- package/src/vite/plugins/performance-tracks.ts +9 -16
- package/src/vite/plugins/refresh-cmd.ts +1 -1
- package/src/vite/plugins/use-cache-transform.ts +26 -49
- package/src/vite/plugins/vercel-output.ts +258 -0
- package/src/vite/plugins/version-injector.ts +2 -32
- package/src/vite/plugins/version-plugin.ts +32 -23
- package/src/vite/plugins/virtual-entries.ts +35 -17
- package/src/vite/rango.ts +148 -115
- package/src/vite/router-discovery.ts +220 -68
- package/src/vite/utils/ast-handler-extract.ts +15 -31
- package/src/vite/utils/bundle-analysis.ts +10 -15
- package/src/vite/utils/client-chunks.ts +184 -0
- package/src/vite/utils/forward-user-plugins.ts +171 -0
- package/src/vite/utils/manifest-utils.ts +4 -59
- package/src/vite/utils/package-resolution.ts +1 -73
- package/src/vite/utils/prerender-utils.ts +0 -35
- package/src/vite/utils/shared-utils.ts +95 -43
- package/src/browser/action-response-classifier.ts +0 -99
- package/src/browser/react/use-client-cache.ts +0 -58
- package/src/browser/shallow.ts +0 -40
- package/src/handles/index.ts +0 -7
- package/src/router/middleware-cookies.ts +0 -55
|
@@ -19,12 +19,17 @@ import type {
|
|
|
19
19
|
ErrorBoundaryFallbackProps,
|
|
20
20
|
ErrorInfo,
|
|
21
21
|
} from "../types";
|
|
22
|
-
import type { LoaderRevalidationResult, ActionContext } from "./types";
|
|
23
22
|
import { isHandle, collectHandleData, type Handle } from "../handle.js";
|
|
23
|
+
import { withDefer } from "../defer.js";
|
|
24
24
|
import { buildHandleSnapshot } from "../server/handle-store.js";
|
|
25
25
|
import { getFetchableLoader } from "../server/fetchable-loader-store.js";
|
|
26
26
|
import { _getRequestContext } from "../server/request-context.js";
|
|
27
|
-
import {
|
|
27
|
+
import {
|
|
28
|
+
isInsideLoaderScope,
|
|
29
|
+
runInsideLoaderBodyScope,
|
|
30
|
+
isInsidePushCallbackScope,
|
|
31
|
+
runInsidePushCallbackScope,
|
|
32
|
+
} from "../server/context.js";
|
|
28
33
|
import { debugLog } from "./logging.js";
|
|
29
34
|
|
|
30
35
|
/**
|
|
@@ -66,7 +71,9 @@ export function wrapLoaderWithErrorHandling<T>(
|
|
|
66
71
|
) => ErrorInfo,
|
|
67
72
|
onError?: LoaderErrorCallback,
|
|
68
73
|
): Promise<LoaderDataResult<T>> {
|
|
69
|
-
// Extract
|
|
74
|
+
// Extract the trailing token from segmentId (format: "<shortCode>D<i>.<loaderId>").
|
|
75
|
+
// The token is the loader's $$id (hash#export in prod, pathfrag#export in dev),
|
|
76
|
+
// not a clean display name.
|
|
70
77
|
const loaderName = segmentId.split(".").pop() || "unknown";
|
|
71
78
|
|
|
72
79
|
return Promise.resolve(promise)
|
|
@@ -287,6 +294,12 @@ function createLoaderExecutor<TEnv>(
|
|
|
287
294
|
);
|
|
288
295
|
}
|
|
289
296
|
const segmentOrder = reqCtx._renderBarrierSegmentOrder ?? [];
|
|
297
|
+
// The complete snapshot is cached at barrier resolution for
|
|
298
|
+
// non-streaming trees, and by rendered() after handleStore.settled for
|
|
299
|
+
// streaming trees (where the eager snapshot would have been incomplete
|
|
300
|
+
// because loading() handlers were still in flight). Either way it is
|
|
301
|
+
// present by the time a loader reads a handle; the fresh build is only
|
|
302
|
+
// a defensive fallback.
|
|
290
303
|
const snapshot =
|
|
291
304
|
reqCtx._renderBarrierHandleSnapshot ??
|
|
292
305
|
buildHandleSnapshot(reqCtx._handleStore, segmentOrder);
|
|
@@ -308,15 +321,7 @@ function createLoaderExecutor<TEnv>(
|
|
|
308
321
|
);
|
|
309
322
|
}
|
|
310
323
|
|
|
311
|
-
// Guard: reject streaming trees
|
|
312
324
|
const reqCtx = reqCtxRef ?? _getRequestContext();
|
|
313
|
-
if (reqCtx?._treeHasStreaming) {
|
|
314
|
-
throw new Error(
|
|
315
|
-
`ctx.rendered() is not supported when the matched route tree uses loading(). ` +
|
|
316
|
-
`Streaming handlers may not have settled when rendered() resolves. ` +
|
|
317
|
-
`Remove loading() from the route tree or restructure to avoid rendered().`,
|
|
318
|
-
);
|
|
319
|
-
}
|
|
320
325
|
|
|
321
326
|
if (renderedPromise) return renderedPromise;
|
|
322
327
|
|
|
@@ -327,7 +332,10 @@ function createLoaderExecutor<TEnv>(
|
|
|
327
332
|
}
|
|
328
333
|
|
|
329
334
|
// Bidirectional deadlock check: if a handler already started
|
|
330
|
-
// awaiting this loader, calling rendered() would deadlock.
|
|
335
|
+
// awaiting this loader, calling rendered() would deadlock. This is the
|
|
336
|
+
// real cycle guard (it holds for both streaming and non-streaming): the
|
|
337
|
+
// handler blocks segment resolution, which blocks the barrier, which
|
|
338
|
+
// blocks this loader.
|
|
331
339
|
if (reqCtx._handlerLoaderDeps?.has(currentLoaderId)) {
|
|
332
340
|
throw new Error(
|
|
333
341
|
`Deadlock: loader "${currentLoaderId}" called ctx.rendered() but a handler ` +
|
|
@@ -345,7 +353,29 @@ function createLoaderExecutor<TEnv>(
|
|
|
345
353
|
}
|
|
346
354
|
reqCtx._renderBarrierWaiters.add(currentLoaderId);
|
|
347
355
|
|
|
348
|
-
|
|
356
|
+
// Streaming trees (loading()): the barrier resolves once the segment
|
|
357
|
+
// tree is resolved, but loading() handlers stream behind Suspense and
|
|
358
|
+
// their handle pushes are still in flight then. Their async execution
|
|
359
|
+
// IS tracked in the handle store (trackHandler -> store.track), so after
|
|
360
|
+
// the barrier we seal (no further handlers register once the tree is
|
|
361
|
+
// resolved) and wait for settled — every tracked handler, streaming
|
|
362
|
+
// included, has finished pushing. The loader's own segment streams in
|
|
363
|
+
// after, so this does not block the shell; the deadlock guard above
|
|
364
|
+
// keeps a handler from depending on this loader.
|
|
365
|
+
const streaming = reqCtx._treeHasStreaming === true;
|
|
366
|
+
renderedPromise = reqCtx._renderBarrier.then(async () => {
|
|
367
|
+
if (streaming) {
|
|
368
|
+
reqCtx._handleStore.seal();
|
|
369
|
+
await reqCtx._handleStore.settled;
|
|
370
|
+
// The eager snapshot was intentionally left unbuilt for streaming
|
|
371
|
+
// (it would have been incomplete). Build the complete one once, now
|
|
372
|
+
// that the store has settled, so every ctx.use(handle) reads the
|
|
373
|
+
// cached snapshot instead of rebuilding it per call.
|
|
374
|
+
reqCtx._renderBarrierHandleSnapshot ??= buildHandleSnapshot(
|
|
375
|
+
reqCtx._handleStore,
|
|
376
|
+
reqCtx._renderBarrierSegmentOrder ?? [],
|
|
377
|
+
);
|
|
378
|
+
}
|
|
349
379
|
renderedResolved = true;
|
|
350
380
|
});
|
|
351
381
|
return renderedPromise;
|
|
@@ -353,8 +383,19 @@ function createLoaderExecutor<TEnv>(
|
|
|
353
383
|
};
|
|
354
384
|
|
|
355
385
|
const doneLoader = track(`loader:${loader.$$id}`, 2);
|
|
386
|
+
// Run the loader body inside loader scope so request-scoped reads
|
|
387
|
+
// (cookies()/headers() and non-cacheable ctx.get) are exempt from the
|
|
388
|
+
// cache-purity guards: loaders always run fresh, so their reads never leak
|
|
389
|
+
// into a cached segment. DSL loaders are already wrapped by fresh.ts; this
|
|
390
|
+
// also covers handler-invoked loaders (ctx.use(Loader) from a handler),
|
|
391
|
+
// which otherwise execute in the caller's cache scope and would wrongly
|
|
392
|
+
// throw. rendered() gating uses the captured isDslLoader (above), so this
|
|
393
|
+
// does not grant rendered() to handler-invoked loaders. Uses a body-only
|
|
394
|
+
// scope, so isInsideLoaderScope() / barrier / deadlock gating is unchanged.
|
|
356
395
|
const promise = Promise.resolve(
|
|
357
|
-
|
|
396
|
+
runInsideLoaderBodyScope(() =>
|
|
397
|
+
loaderFn(loaderCtx as LoaderContext<any, TEnv>),
|
|
398
|
+
),
|
|
358
399
|
).finally(() => {
|
|
359
400
|
pendingLoaders.delete(loader.$$id);
|
|
360
401
|
doneLoader();
|
|
@@ -390,12 +431,6 @@ export function setupLoaderAccess<TEnv>(
|
|
|
390
431
|
|
|
391
432
|
const useLoader = createLoaderExecutor(ctx, loaderPromises);
|
|
392
433
|
|
|
393
|
-
// Track whether we're inside a handle push callback. Loaders started
|
|
394
|
-
// from push callbacks (e.g. push(async () => ctx.use(Loader))) do NOT
|
|
395
|
-
// block segment resolution, so they must not be registered as handler
|
|
396
|
-
// dependencies for deadlock detection.
|
|
397
|
-
let insideHandlePush = false;
|
|
398
|
-
|
|
399
434
|
ctx.use = ((item: LoaderDefinition<any, any> | Handle<any, any>) => {
|
|
400
435
|
if (isHandle(item)) {
|
|
401
436
|
const handle = item;
|
|
@@ -410,35 +445,40 @@ export function setupLoaderAccess<TEnv>(
|
|
|
410
445
|
);
|
|
411
446
|
}
|
|
412
447
|
|
|
413
|
-
return (
|
|
414
|
-
dataOrFn: unknown | Promise<unknown> | (() => Promise<unknown>)
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
448
|
+
return withDefer(
|
|
449
|
+
(dataOrFn: unknown | Promise<unknown> | (() => Promise<unknown>)) => {
|
|
450
|
+
if (!store) return;
|
|
451
|
+
|
|
452
|
+
if (typeof dataOrFn === "function") {
|
|
453
|
+
// Run the callback inside the push-callback scope so ctx.use(loader)
|
|
454
|
+
// calls it makes — including after its own awaits, for an async
|
|
455
|
+
// callback — are not registered as handler-to-loader deps and do not
|
|
456
|
+
// trip the deadlock guard. A pushed promise value is not tracked by
|
|
457
|
+
// handleStore.settled and does not block segment resolution, so it
|
|
458
|
+
// cannot form a rendered() deadlock. The ALS scope (not a plain
|
|
459
|
+
// boolean) is what survives the callback's awaits.
|
|
460
|
+
const result = runInsidePushCallbackScope(() =>
|
|
461
|
+
(dataOrFn as () => Promise<unknown>)(),
|
|
462
|
+
);
|
|
424
463
|
store.push(handle.$$id, segmentId, result);
|
|
425
|
-
|
|
426
|
-
insideHandlePush = false;
|
|
464
|
+
return;
|
|
427
465
|
}
|
|
428
|
-
return;
|
|
429
|
-
}
|
|
430
466
|
|
|
431
|
-
|
|
432
|
-
|
|
467
|
+
store.push(handle.$$id, segmentId, dataOrFn);
|
|
468
|
+
},
|
|
469
|
+
);
|
|
433
470
|
}
|
|
434
471
|
|
|
435
472
|
// Deadlock guard and handler-to-loader dependency tracking.
|
|
436
473
|
// Skip when inside a DSL loader scope (resolveLoaderData also calls
|
|
437
474
|
// ctx.use() but that's DSL-to-DSL, not handler-to-loader) or when
|
|
438
475
|
// inside a handle push callback (push callbacks don't block segment
|
|
439
|
-
// resolution so they can't cause rendered() deadlocks).
|
|
476
|
+
// resolution so they can't cause rendered() deadlocks). The push-callback
|
|
477
|
+
// check is an ALS scope so it also exempts an ASYNC callback's continuation
|
|
478
|
+
// after its first await — relevant on streaming trees, where the guard
|
|
479
|
+
// state now stays live until handleStore.settled.
|
|
440
480
|
const loader = item as LoaderDefinition<any, any>;
|
|
441
|
-
if (!isInsideLoaderScope() && !
|
|
481
|
+
if (!isInsideLoaderScope() && !isInsidePushCallbackScope()) {
|
|
442
482
|
const reqCtx = reqCtxRef ?? _getRequestContext();
|
|
443
483
|
if (reqCtx) {
|
|
444
484
|
// Direction 1: handler awaits loader that already called rendered()
|
|
@@ -452,13 +492,18 @@ export function setupLoaderAccess<TEnv>(
|
|
|
452
492
|
`Move the data dependency to a loader-to-loader pattern instead.`,
|
|
453
493
|
);
|
|
454
494
|
}
|
|
455
|
-
// Direction 2: track dep so rendered() can detect the deadlock
|
|
456
|
-
//
|
|
457
|
-
//
|
|
458
|
-
//
|
|
459
|
-
//
|
|
460
|
-
//
|
|
461
|
-
|
|
495
|
+
// Direction 2: track dep so rendered() can detect the deadlock if the
|
|
496
|
+
// loader calls it later. Skip once the guard window is CLOSED — for a
|
|
497
|
+
// non-streaming tree that is when the barrier resolves (rendered()
|
|
498
|
+
// resolves immediately), and for a streaming tree it is when
|
|
499
|
+
// handleStore.settled completes (rendered() keeps waiting until then, so
|
|
500
|
+
// a loading() handler resuming after the barrier can still form a
|
|
501
|
+
// cycle). Using the explicit guard-closed flag rather than
|
|
502
|
+
// _renderBarrierSegmentOrder keeps tracking live across the streaming
|
|
503
|
+
// settle wait. (Handle push callbacks are already excluded above via
|
|
504
|
+
// isInsidePushCallbackScope(), so they cannot produce false positives
|
|
505
|
+
// here.)
|
|
506
|
+
if (!reqCtx._renderBarrierGuardClosed) {
|
|
462
507
|
if (!reqCtx._handlerLoaderDeps) reqCtx._handlerLoaderDeps = new Set();
|
|
463
508
|
reqCtx._handlerLoaderDeps.add(loader.$$id);
|
|
464
509
|
}
|
package/src/router/logging.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
2
|
import { INTERNAL_RANGO_DEBUG } from "../internal-debug.js";
|
|
3
3
|
|
|
4
|
-
// -- Revalidation trace types --
|
|
5
|
-
|
|
6
4
|
export interface RevalidationTraceEntry {
|
|
7
5
|
segmentId: string;
|
|
8
6
|
segmentType: string;
|
|
@@ -36,8 +34,6 @@ export interface RevalidationTrace {
|
|
|
36
34
|
entries: RevalidationTraceEntry[];
|
|
37
35
|
}
|
|
38
36
|
|
|
39
|
-
// -- Log context --
|
|
40
|
-
|
|
41
37
|
interface RouterLogContext {
|
|
42
38
|
requestId: string;
|
|
43
39
|
transactionId: string;
|
|
@@ -195,8 +191,6 @@ export function debugWarn(
|
|
|
195
191
|
console.warn(`${prefix} ${message}`);
|
|
196
192
|
}
|
|
197
193
|
|
|
198
|
-
// -- Revalidation trace helpers --
|
|
199
|
-
|
|
200
194
|
export function isTraceActive(): boolean {
|
|
201
195
|
if (!INTERNAL_RANGO_DEBUG) return false;
|
|
202
196
|
const ctx = routerLogContext.getStore();
|
package/src/router/manifest.ts
CHANGED
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Router Manifest Loading
|
|
3
|
-
*
|
|
4
|
-
* Handles lazy loading and validation of route manifests.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
1
|
import { invariant, RouteNotFoundError } from "../errors";
|
|
8
2
|
import { createRouteHelpers } from "../route-definition";
|
|
9
3
|
import {
|
|
@@ -14,6 +8,7 @@ import {
|
|
|
14
8
|
type MetricsStore,
|
|
15
9
|
} from "../server/context";
|
|
16
10
|
import MapRootLayout from "../server/root-layout";
|
|
11
|
+
import { joinPrefix } from "./pattern-matching.js";
|
|
17
12
|
import type { RouteEntry } from "../types";
|
|
18
13
|
import type { UrlPatterns } from "../urls";
|
|
19
14
|
import { VERSION } from "@rangojs/router:version";
|
|
@@ -23,20 +18,19 @@ import { VERSION } from "@rangojs/router:version";
|
|
|
23
18
|
// stable references), so the resulting EntryData tree can be safely cached and reused
|
|
24
19
|
// across requests within the same isolate.
|
|
25
20
|
//
|
|
26
|
-
// Cache is keyed by (VERSION, mountIndex, routeKey, isSSR).
|
|
21
|
+
// Cache is keyed by (VERSION, routerId, mountIndex, routeKey, isSSR). routeKey is
|
|
22
|
+
// REQUIRED in the key: loadManifest() runs the handler with forRoute=routeKey, and
|
|
23
|
+
// path-helper.ts prunes (skips registering) every route except forRoute, so the
|
|
24
|
+
// resulting Store.manifest is pruned to the requested route — NOT the full include.
|
|
25
|
+
// Dropping routeKey would make a sibling route miss and overwrite this entry with its
|
|
26
|
+
// own pruned manifest, so alternating sibling requests would thrash (re-run the
|
|
27
|
+
// handler every time). Running the include handler once per isolate instead of once
|
|
28
|
+
// per route is possible but needs an unpruned manifest cache with prune-on-read — see
|
|
29
|
+
// LP1 in docs/internal/matching-and-lazy-discovery.md. VERSION comes from the
|
|
27
30
|
// @rangojs/router:version virtual module which Vite invalidates on RSC module HMR.
|
|
28
31
|
// When VERSION changes, this module re-evaluates and the cache is recreated empty.
|
|
29
|
-
// Including VERSION in the key is additional defense against stale entries.
|
|
30
32
|
const manifestModuleCache = new Map<string, Map<string, EntryData>>();
|
|
31
33
|
|
|
32
|
-
/**
|
|
33
|
-
* Load manifest from route entry with AsyncLocalStorage context
|
|
34
|
-
* Handles lazy imports, unwrapping, and validation
|
|
35
|
-
*
|
|
36
|
-
* Results are cached at module level after first execution. Subsequent calls
|
|
37
|
-
* for the same (routeKey, isSSR) within the same isolate return cached data
|
|
38
|
-
* without re-executing the DSL handler.
|
|
39
|
-
*/
|
|
40
34
|
/**
|
|
41
35
|
* Clear the module-level manifest cache.
|
|
42
36
|
* Called on HMR to ensure stale handler references are discarded.
|
|
@@ -65,9 +59,11 @@ export async function loadManifest(
|
|
|
65
59
|
|
|
66
60
|
const mountIndex = entry.mountIndex;
|
|
67
61
|
|
|
68
|
-
// Check module-level cache (persists across requests within same isolate)
|
|
62
|
+
// Check module-level cache (persists across requests within same isolate).
|
|
69
63
|
// Include routerId so multi-router setups (host routing) don't share cached
|
|
70
64
|
// EntryData across routers with overlapping mountIndex + routeKey combinations.
|
|
65
|
+
// routeKey is in the key because loadManifest() builds a manifest pruned to
|
|
66
|
+
// forRoute=routeKey (see path-helper.ts) — see the cache comment above.
|
|
71
67
|
const cacheKey = `${VERSION}:${entry.routerId ?? ""}:${mountIndex ?? ""}:${routeKey}:${isSSR ? 1 : 0}`;
|
|
72
68
|
const cached = manifestModuleCache.get(cacheKey);
|
|
73
69
|
if (cached) {
|
|
@@ -88,20 +84,15 @@ export async function loadManifest(
|
|
|
88
84
|
const storeSetupStart = performance.now();
|
|
89
85
|
const Store = getContext().getOrCreateStore(routeKey);
|
|
90
86
|
|
|
91
|
-
// Set mount index in store for unique shortCode prefixes
|
|
92
87
|
Store.mountIndex = mountIndex;
|
|
93
|
-
|
|
94
|
-
// Set isSSR flag so loading() can check if we're in SSR
|
|
95
88
|
Store.isSSR = isSSR;
|
|
96
89
|
|
|
97
|
-
// Attach metrics store to context if provided
|
|
98
90
|
if (metricsStore) {
|
|
99
91
|
Store.metrics = metricsStore;
|
|
100
92
|
}
|
|
101
93
|
|
|
102
94
|
pushMetric?.("manifest:store-setup", storeSetupStart);
|
|
103
95
|
|
|
104
|
-
// Clear manifest before rebuilding to prevent stale entry mutations
|
|
105
96
|
const clearStart = performance.now();
|
|
106
97
|
Store.manifest.clear();
|
|
107
98
|
pushMetric?.("manifest:clear", clearStart);
|
|
@@ -176,7 +167,10 @@ export async function loadManifest(
|
|
|
176
167
|
if (entry.lazy && entry.lazyPatterns) {
|
|
177
168
|
const lazyPatterns = entry.lazyPatterns as UrlPatterns<any>;
|
|
178
169
|
const includePrefix = (entry as any)._lazyPrefix || "";
|
|
179
|
-
|
|
170
|
+
// Slash-collapsing join so a trailing-slash parent prefix does not
|
|
171
|
+
// bake a double slash into the registered route patterns (must match
|
|
172
|
+
// the same join in evaluateLazyEntry / the build-time runWithPrefixes).
|
|
173
|
+
const fullPrefix = joinPrefix(lazyContext?.urlPrefix, includePrefix);
|
|
180
174
|
|
|
181
175
|
if (fullPrefix || lazyContext?.namePrefix) {
|
|
182
176
|
return runWithPrefixes(fullPrefix, lazyContext?.namePrefix, () =>
|
|
@@ -186,20 +180,16 @@ export async function loadManifest(
|
|
|
186
180
|
return lazyPatterns.handler();
|
|
187
181
|
}
|
|
188
182
|
|
|
189
|
-
// Wrap handler execution in root layout so routes get correct parent
|
|
190
|
-
// This ensures all routes are registered with the layout as their parent
|
|
191
183
|
let promiseResult: Promise<any> | null = null;
|
|
192
184
|
const wrappedItems = helpers.layout(MapRootLayout, () => {
|
|
193
185
|
const result = entry.handler();
|
|
194
186
|
if (result instanceof Promise) {
|
|
195
|
-
// Lazy handler detected - capture promise for async handling
|
|
196
187
|
promiseResult = result;
|
|
197
|
-
return [];
|
|
188
|
+
return [];
|
|
198
189
|
}
|
|
199
190
|
return result;
|
|
200
191
|
});
|
|
201
192
|
|
|
202
|
-
// Handle lazy (Promise-based) handlers
|
|
203
193
|
if (promiseResult !== null) {
|
|
204
194
|
const load = await (promiseResult as Promise<any>);
|
|
205
195
|
if (
|
|
@@ -233,7 +223,6 @@ export async function loadManifest(
|
|
|
233
223
|
);
|
|
234
224
|
}
|
|
235
225
|
|
|
236
|
-
// Inline handler - routes were registered with correct parent inside layout
|
|
237
226
|
return [wrappedItems].flat(3);
|
|
238
227
|
},
|
|
239
228
|
);
|
package/src/router/match-api.ts
CHANGED
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Match API
|
|
3
|
-
*
|
|
4
|
-
* Extracted from createRouter closure. Contains match context creation functions
|
|
5
|
-
* and the matchError function for error boundary resolution.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
1
|
import { CacheScope, createCacheScope } from "../cache/cache-scope.js";
|
|
9
2
|
import { RouteNotFoundError } from "../errors";
|
|
10
3
|
import {
|
|
@@ -59,8 +52,6 @@ export async function createMatchContextForFull<TEnv>(
|
|
|
59
52
|
|
|
60
53
|
const metricsStore = deps.getMetricsStore();
|
|
61
54
|
|
|
62
|
-
// Full renders always resolve fresh with isSSR: true because loadManifest
|
|
63
|
-
// keys its cache on isSSR and stamps Store.isSSR for downstream behavior.
|
|
64
55
|
const result = await resolveRoute<TEnv>(pathname, {
|
|
65
56
|
findMatch: (p) => deps.findMatch(p, metricsStore),
|
|
66
57
|
metricsStore,
|
|
@@ -84,12 +75,10 @@ export async function createMatchContextForFull<TEnv>(
|
|
|
84
75
|
|
|
85
76
|
const { matched } = snapshot;
|
|
86
77
|
|
|
87
|
-
// Backward compat: downstream middleware reads matched.pt
|
|
88
78
|
if (snapshot.isPassthrough) {
|
|
89
79
|
matched.pt = true;
|
|
90
80
|
}
|
|
91
81
|
|
|
92
|
-
// Clean URL without internal _rsc* params for userland access
|
|
93
82
|
const cleanUrl = stripInternalParams(url);
|
|
94
83
|
|
|
95
84
|
const handlerContext = createHandlerContext(
|
|
@@ -231,7 +220,6 @@ export async function createMatchContextForPartial<TEnv>(
|
|
|
231
220
|
matched.pt = true;
|
|
232
221
|
}
|
|
233
222
|
|
|
234
|
-
// Navigation state (prev + intercept-source findMatch calls)
|
|
235
223
|
const nav = resolveNavigation(request, url, matched.routeKey, {
|
|
236
224
|
findMatch: deps.findMatch,
|
|
237
225
|
});
|
|
@@ -239,10 +227,6 @@ export async function createMatchContextForPartial<TEnv>(
|
|
|
239
227
|
return null;
|
|
240
228
|
}
|
|
241
229
|
|
|
242
|
-
// Push route-matching metric. On the fresh path this covers all findMatch
|
|
243
|
-
// calls (current + prev + intercept-source). On the reuse path, current-route
|
|
244
|
-
// findMatch was already timed during classification, so this only covers
|
|
245
|
-
// the nav lookups (prev + intercept-source).
|
|
246
230
|
if (metricsStore) {
|
|
247
231
|
const isReuse = !!classifiedRoute;
|
|
248
232
|
metricsStore.metrics.push({
|
|
@@ -259,7 +243,6 @@ export async function createMatchContextForPartial<TEnv>(
|
|
|
259
243
|
});
|
|
260
244
|
}
|
|
261
245
|
|
|
262
|
-
// Clean URL without internal _rsc* params for userland access
|
|
263
246
|
const cleanUrl = stripInternalParams(url);
|
|
264
247
|
|
|
265
248
|
const handlerContext = createHandlerContext(
|
|
@@ -304,9 +287,6 @@ export async function createMatchContextForPartial<TEnv>(
|
|
|
304
287
|
});
|
|
305
288
|
}
|
|
306
289
|
|
|
307
|
-
// Store previous route key on the request context for revalidation
|
|
308
|
-
// fromRouteName. Uses effectiveFromMatch so intercept-source navigations
|
|
309
|
-
// see the intercept origin route, not the plain previous URL route.
|
|
310
290
|
setRequestContextPrevRouteKey(nav.effectiveFromMatch?.routeKey);
|
|
311
291
|
|
|
312
292
|
const interceptSelectorContext: InterceptSelectorContext = {
|
|
@@ -242,25 +242,3 @@ export function createPipelineState(): MatchPipelineState {
|
|
|
242
242
|
slots: {},
|
|
243
243
|
};
|
|
244
244
|
}
|
|
245
|
-
|
|
246
|
-
/**
|
|
247
|
-
* Input parameters for createMatchContext
|
|
248
|
-
*/
|
|
249
|
-
export interface CreateMatchContextInput<TEnv = any> {
|
|
250
|
-
request: Request;
|
|
251
|
-
env: TEnv;
|
|
252
|
-
actionContext?: ActionContext;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/**
|
|
256
|
-
* Result from createMatchContext - either a context or null (fall back to full match)
|
|
257
|
-
*/
|
|
258
|
-
export type CreateMatchContextResult<TEnv = any> =
|
|
259
|
-
| { type: "context"; ctx: MatchContext<TEnv> }
|
|
260
|
-
| { type: "fallback"; reason: string }
|
|
261
|
-
| { type: "error"; error: Error };
|
|
262
|
-
|
|
263
|
-
// Note: createMatchContext() will be implemented in Step J10 when we wire everything together.
|
|
264
|
-
// It requires access to RouterContext (findMatch, loadManifest, etc.) which are closure
|
|
265
|
-
// functions from createRouter(). The implementation will live in router.ts initially
|
|
266
|
-
// and call getRouterContext() to access these dependencies.
|
|
@@ -33,10 +33,13 @@ import type { ErrorBoundaryHandler, NotFoundBoundaryHandler } from "../types";
|
|
|
33
33
|
import type { MiddlewareFn } from "./middleware.js";
|
|
34
34
|
import {
|
|
35
35
|
type TelemetrySink,
|
|
36
|
+
type CacheSegmentSignal,
|
|
36
37
|
safeEmit,
|
|
37
38
|
resolveSink,
|
|
38
39
|
getRequestId,
|
|
40
|
+
buildCacheSignalSegments,
|
|
39
41
|
} from "./telemetry.js";
|
|
42
|
+
import { _getRequestContext } from "../server/request-context.js";
|
|
40
43
|
|
|
41
44
|
export interface MatchHandlerDeps<TEnv = any> {
|
|
42
45
|
buildRouterContext: () => RouterContext<TEnv>;
|
|
@@ -51,6 +54,12 @@ export interface MatchHandlerDeps<TEnv = any> {
|
|
|
51
54
|
isAction: boolean,
|
|
52
55
|
) => { intercept: InterceptEntry; entry: EntryData } | null;
|
|
53
56
|
telemetry?: TelemetrySink;
|
|
57
|
+
/**
|
|
58
|
+
* DEVELOPMENT/TEST ONLY gate for the X-Rango-Cache debug header. When true,
|
|
59
|
+
* match/matchPartial stash a coarse route-level cache signal on the request
|
|
60
|
+
* context for the response-finalization path to emit. Default off.
|
|
61
|
+
*/
|
|
62
|
+
cacheSignalEnabled?: boolean;
|
|
54
63
|
}
|
|
55
64
|
|
|
56
65
|
export interface MatchHandlers<TEnv = any> {
|
|
@@ -80,20 +89,6 @@ export interface MatchHandlers<TEnv = any> {
|
|
|
80
89
|
negotiated?: boolean;
|
|
81
90
|
manifestEntry?: EntryData;
|
|
82
91
|
} | null>;
|
|
83
|
-
createMatchContextForFull: (
|
|
84
|
-
request: Request,
|
|
85
|
-
env: TEnv,
|
|
86
|
-
) => Promise<MatchContext<TEnv> | { type: "redirect"; redirectUrl: string }>;
|
|
87
|
-
createMatchContextForPartial: (
|
|
88
|
-
request: Request,
|
|
89
|
-
env: TEnv,
|
|
90
|
-
actionContext?: {
|
|
91
|
-
actionId?: string;
|
|
92
|
-
actionUrl?: URL;
|
|
93
|
-
actionResult?: any;
|
|
94
|
-
formData?: FormData;
|
|
95
|
-
},
|
|
96
|
-
) => Promise<MatchContext<TEnv> | null>;
|
|
97
92
|
}
|
|
98
93
|
|
|
99
94
|
/**
|
|
@@ -113,6 +108,20 @@ export function createMatchHandlers<TEnv = any>(
|
|
|
113
108
|
} = deps;
|
|
114
109
|
const hasTelemetry = !!deps.telemetry;
|
|
115
110
|
const telemetry = resolveSink(deps.telemetry);
|
|
111
|
+
const cacheSignalEnabled = !!deps.cacheSignalEnabled;
|
|
112
|
+
const buildSignal = (
|
|
113
|
+
routeKey: string,
|
|
114
|
+
state: {
|
|
115
|
+
cacheHit: boolean;
|
|
116
|
+
cacheSource?: "runtime" | "prerender";
|
|
117
|
+
shouldRevalidate?: boolean;
|
|
118
|
+
},
|
|
119
|
+
): CacheSegmentSignal[] => buildCacheSignalSegments(routeKey, state);
|
|
120
|
+
const recordSignalIfEnabled = (segments: CacheSegmentSignal[]): void => {
|
|
121
|
+
if (!cacheSignalEnabled) return;
|
|
122
|
+
const reqCtx = _getRequestContext();
|
|
123
|
+
if (reqCtx) reqCtx._cacheSignal = segments;
|
|
124
|
+
};
|
|
116
125
|
|
|
117
126
|
async function createMatchContextForFull(
|
|
118
127
|
request: Request,
|
|
@@ -145,15 +154,6 @@ export function createMatchHandlers<TEnv = any>(
|
|
|
145
154
|
);
|
|
146
155
|
}
|
|
147
156
|
|
|
148
|
-
/**
|
|
149
|
-
* Match request and return segments (document/SSR requests)
|
|
150
|
-
*
|
|
151
|
-
* Uses generator middleware pipeline for clean separation of concerns:
|
|
152
|
-
* - cache-lookup: Check cache first
|
|
153
|
-
* - segment-resolution: Resolve segments on cache miss
|
|
154
|
-
* - cache-store: Store results in cache
|
|
155
|
-
* - background-revalidation: SWR revalidation
|
|
156
|
-
*/
|
|
157
157
|
async function match(request: Request, env: TEnv): Promise<MatchResult> {
|
|
158
158
|
const requestId = hasTelemetry ? getRequestId(request) : undefined;
|
|
159
159
|
return runWithRouterLogContext({ request, transaction: "match" }, () => {
|
|
@@ -177,7 +177,6 @@ export function createMatchHandlers<TEnv = any>(
|
|
|
177
177
|
|
|
178
178
|
const result = await createMatchContextForFull(request, env);
|
|
179
179
|
|
|
180
|
-
// Handle redirect case
|
|
181
180
|
if ("type" in result && result.type === "redirect") {
|
|
182
181
|
if (hasTelemetry) {
|
|
183
182
|
safeEmit(telemetry, {
|
|
@@ -208,17 +207,24 @@ export function createMatchHandlers<TEnv = any>(
|
|
|
208
207
|
const state = createPipelineState();
|
|
209
208
|
const pipeline = createMatchPartialPipeline(ctx, state);
|
|
210
209
|
const matchResult = await collectMatchResult(pipeline, ctx, state);
|
|
210
|
+
if (hasTelemetry || cacheSignalEnabled) {
|
|
211
|
+
const signalSegments = buildSignal(ctx.routeKey, state);
|
|
212
|
+
recordSignalIfEnabled(signalSegments);
|
|
213
|
+
if (hasTelemetry) {
|
|
214
|
+
safeEmit(telemetry, {
|
|
215
|
+
type: "cache.decision",
|
|
216
|
+
timestamp: performance.now(),
|
|
217
|
+
requestId,
|
|
218
|
+
pathname,
|
|
219
|
+
routeKey: ctx.routeKey,
|
|
220
|
+
hit: state.cacheHit,
|
|
221
|
+
shouldRevalidate: !!state.shouldRevalidate,
|
|
222
|
+
source: state.cacheSource,
|
|
223
|
+
segments: signalSegments,
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
211
227
|
if (hasTelemetry) {
|
|
212
|
-
safeEmit(telemetry, {
|
|
213
|
-
type: "cache.decision",
|
|
214
|
-
timestamp: performance.now(),
|
|
215
|
-
requestId,
|
|
216
|
-
pathname,
|
|
217
|
-
routeKey: ctx.routeKey,
|
|
218
|
-
hit: state.cacheHit,
|
|
219
|
-
shouldRevalidate: !!state.shouldRevalidate,
|
|
220
|
-
source: state.cacheSource,
|
|
221
|
-
});
|
|
222
228
|
safeEmit(telemetry, {
|
|
223
229
|
type: "request.end",
|
|
224
230
|
timestamp: performance.now(),
|
|
@@ -249,7 +255,6 @@ export function createMatchHandlers<TEnv = any>(
|
|
|
249
255
|
});
|
|
250
256
|
}
|
|
251
257
|
if (error instanceof Response) throw error;
|
|
252
|
-
// Report unhandled errors during full match pipeline
|
|
253
258
|
callOnError(error, "routing", {
|
|
254
259
|
request,
|
|
255
260
|
url: ctx.url,
|
|
@@ -284,16 +289,6 @@ export function createMatchHandlers<TEnv = any>(
|
|
|
284
289
|
);
|
|
285
290
|
}
|
|
286
291
|
|
|
287
|
-
/**
|
|
288
|
-
* Match partial request with revalidation
|
|
289
|
-
*
|
|
290
|
-
* Uses generator middleware pipeline for clean separation of concerns:
|
|
291
|
-
* - cache-lookup: Check cache first
|
|
292
|
-
* - segment-resolution: Resolve segments on cache miss
|
|
293
|
-
* - intercept-resolution: Handle intercept routes
|
|
294
|
-
* - cache-store: Store results in cache
|
|
295
|
-
* - background-revalidation: SWR revalidation
|
|
296
|
-
*/
|
|
297
292
|
async function matchPartial(
|
|
298
293
|
request: Request,
|
|
299
294
|
context: TEnv,
|
|
@@ -363,17 +358,24 @@ export function createMatchHandlers<TEnv = any>(
|
|
|
363
358
|
state,
|
|
364
359
|
);
|
|
365
360
|
flushRevalidationTrace();
|
|
361
|
+
if (hasTelemetry || cacheSignalEnabled) {
|
|
362
|
+
const signalSegments = buildSignal(ctx.routeKey, state);
|
|
363
|
+
recordSignalIfEnabled(signalSegments);
|
|
364
|
+
if (hasTelemetry) {
|
|
365
|
+
safeEmit(telemetry, {
|
|
366
|
+
type: "cache.decision",
|
|
367
|
+
timestamp: performance.now(),
|
|
368
|
+
requestId: partialRequestId,
|
|
369
|
+
pathname,
|
|
370
|
+
routeKey: ctx.routeKey,
|
|
371
|
+
hit: state.cacheHit,
|
|
372
|
+
shouldRevalidate: !!state.shouldRevalidate,
|
|
373
|
+
source: state.cacheSource,
|
|
374
|
+
segments: signalSegments,
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
}
|
|
366
378
|
if (hasTelemetry) {
|
|
367
|
-
safeEmit(telemetry, {
|
|
368
|
-
type: "cache.decision",
|
|
369
|
-
timestamp: performance.now(),
|
|
370
|
-
requestId: partialRequestId,
|
|
371
|
-
pathname,
|
|
372
|
-
routeKey: ctx.routeKey,
|
|
373
|
-
hit: state.cacheHit,
|
|
374
|
-
shouldRevalidate: !!state.shouldRevalidate,
|
|
375
|
-
source: state.cacheSource,
|
|
376
|
-
});
|
|
377
379
|
safeEmit(telemetry, {
|
|
378
380
|
type: "request.end",
|
|
379
381
|
timestamp: performance.now(),
|
|
@@ -406,7 +408,6 @@ export function createMatchHandlers<TEnv = any>(
|
|
|
406
408
|
});
|
|
407
409
|
}
|
|
408
410
|
if (error instanceof Response) throw error;
|
|
409
|
-
// Report unhandled errors during partial match pipeline
|
|
410
411
|
callOnError(error, actionContext ? "action" : "revalidation", {
|
|
411
412
|
request,
|
|
412
413
|
url: ctx.url,
|
|
@@ -435,7 +436,5 @@ export function createMatchHandlers<TEnv = any>(
|
|
|
435
436
|
matchPartial: matchPartial,
|
|
436
437
|
matchError: matchError,
|
|
437
438
|
previewMatch: previewMatch,
|
|
438
|
-
createMatchContextForFull: createMatchContextForFull,
|
|
439
|
-
createMatchContextForPartial: createMatchContextForPartial,
|
|
440
439
|
};
|
|
441
440
|
}
|