@rangojs/router 0.0.0-experimental.19 → 0.0.0-experimental.1c0bdfad
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 +17 -0
- package/README.md +291 -61
- package/dist/bin/rango.js +544 -143
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +3744 -1329
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +67 -13
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +312 -0
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +247 -23
- package/skills/caching/SKILL.md +322 -19
- package/skills/composability/SKILL.md +27 -2
- package/skills/css/SKILL.md +76 -0
- package/skills/debug-manifest/SKILL.md +4 -2
- package/skills/document-cache/SKILL.md +78 -55
- package/skills/handler-use/SKILL.md +364 -0
- package/skills/hooks/SKILL.md +282 -60
- package/skills/host-router/SKILL.md +278 -0
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +50 -6
- package/skills/layout/SKILL.md +35 -9
- package/skills/links/SKILL.md +249 -17
- package/skills/loader/SKILL.md +297 -31
- package/skills/middleware/SKILL.md +52 -13
- package/skills/migrate-nextjs/SKILL.md +584 -0
- package/skills/migrate-react-router/SKILL.md +771 -0
- package/skills/mime-routes/SKILL.md +28 -1
- package/skills/observability/SKILL.md +172 -0
- package/skills/parallel/SKILL.md +203 -7
- package/skills/prerender/SKILL.md +155 -111
- package/skills/rango/SKILL.md +251 -23
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +123 -48
- package/skills/route/SKILL.md +104 -9
- package/skills/router-setup/SKILL.md +124 -11
- package/skills/scripts/SKILL.md +179 -0
- package/skills/server-actions/SKILL.md +775 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/tailwind/SKILL.md +27 -3
- package/skills/testing/SKILL.md +125 -222
- package/skills/testing/bindings.md +103 -0
- package/skills/testing/cache-prerender.md +127 -0
- package/skills/testing/client-components.md +124 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +91 -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 +123 -0
- package/skills/typesafety/SKILL.md +357 -52
- package/skills/use-cache/SKILL.md +46 -14
- 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 +67 -40
- package/src/bin/rango.ts +18 -0
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +39 -0
- package/src/browser/app-version.ts +14 -0
- package/src/browser/connection-warmup.ts +134 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +197 -150
- 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/link-interceptor.ts +4 -0
- package/src/browser/navigation-bridge.ts +200 -30
- package/src/browser/navigation-client.ts +217 -58
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +76 -67
- package/src/browser/navigation-transaction.ts +18 -66
- package/src/browser/network-error-handler.ts +34 -7
- package/src/browser/partial-update.ts +187 -112
- package/src/browser/prefetch/cache.ts +312 -30
- package/src/browser/prefetch/fetch.ts +344 -47
- package/src/browser/prefetch/policy.ts +6 -0
- package/src/browser/prefetch/queue.ts +126 -20
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +158 -76
- package/src/browser/react/Link.tsx +125 -18
- package/src/browser/react/NavigationProvider.tsx +135 -120
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/context.ts +7 -2
- package/src/browser/react/filter-segment-order.ts +66 -7
- package/src/browser/react/index.ts +0 -48
- package/src/browser/react/location-state-shared.ts +178 -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 +23 -69
- package/src/browser/react/use-href.tsx +8 -1
- package/src/browser/react/use-link-status.ts +33 -8
- package/src/browser/react/use-navigation.ts +32 -7
- package/src/browser/react/use-params.ts +20 -10
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +46 -11
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +11 -21
- package/src/browser/response-adapter.ts +80 -5
- package/src/browser/rsc-router.tsx +226 -75
- package/src/browser/scroll-restoration.ts +54 -42
- package/src/browser/segment-reconciler.ts +36 -9
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +619 -442
- package/src/browser/types.ts +115 -11
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +65 -40
- package/src/build/generate-route-types.ts +7 -1
- package/src/build/index.ts +8 -2
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +182 -37
- package/src/build/route-types/ast-route-extraction.ts +15 -8
- package/src/build/route-types/codegen.ts +16 -5
- package/src/build/route-types/include-resolution.ts +125 -24
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/per-module-writer.ts +22 -6
- package/src/build/route-types/router-processing.ts +392 -106
- package/src/build/route-types/scan-filter.ts +9 -2
- package/src/build/route-types/source-scan.ts +216 -0
- package/src/build/runtime-discovery.ts +9 -20
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-key-utils.ts +29 -13
- package/src/cache/cache-policy.ts +108 -34
- package/src/cache/cache-runtime.ts +214 -48
- package/src/cache/cache-scope.ts +236 -89
- package/src/cache/cache-tag.ts +103 -0
- package/src/cache/cf/cf-base64.ts +33 -0
- package/src/cache/cf/cf-cache-constants.ts +127 -0
- package/src/cache/cf/cf-cache-store.ts +2224 -171
- package/src/cache/cf/cf-cache-types.ts +349 -0
- package/src/cache/cf/cf-kv-utils.ts +46 -0
- package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
- package/src/cache/cf/index.ts +11 -17
- package/src/cache/document-cache.ts +89 -27
- package/src/cache/handle-snapshot.ts +70 -0
- package/src/cache/index.ts +11 -20
- package/src/cache/memory-segment-store.ts +136 -37
- package/src/cache/profile-registry.ts +31 -31
- package/src/cache/read-through-swr.ts +41 -11
- package/src/cache/segment-codec.ts +9 -17
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/taint.ts +55 -0
- package/src/cache/types.ts +37 -100
- package/src/client.rsc.tsx +45 -21
- package/src/client.tsx +120 -336
- package/src/cloudflare/index.ts +11 -0
- package/src/cloudflare/tracing.ts +109 -0
- package/src/component-utils.ts +19 -0
- package/src/components/DefaultDocument.tsx +8 -2
- package/src/context-var.ts +84 -2
- package/src/debug.ts +2 -2
- package/src/decode-loader-results.ts +52 -0
- package/src/defer.ts +196 -0
- package/src/deps/ssr.ts +0 -1
- package/src/encode-kv.ts +49 -0
- package/src/errors.ts +30 -4
- package/src/escape-script.ts +52 -0
- package/src/handle.ts +70 -22
- package/src/handles/MetaTags.tsx +56 -19
- package/src/handles/Scripts.tsx +183 -0
- package/src/handles/breadcrumbs.ts +95 -0
- package/src/handles/is-thenable.ts +19 -0
- package/src/handles/meta.ts +51 -40
- package/src/handles/script.ts +244 -0
- package/src/host/cookie-handler.ts +9 -60
- package/src/host/errors.ts +0 -24
- package/src/host/index.ts +8 -5
- package/src/host/pattern-matcher.ts +23 -52
- 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 +79 -29
- package/src/index.ts +149 -65
- package/src/internal-debug.ts +11 -10
- 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/outlet-context.ts +1 -1
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +16 -16
- package/src/prerender/store.ts +63 -26
- package/src/prerender.ts +198 -82
- package/src/redirect-origin.ts +100 -0
- package/src/regex-escape.ts +8 -0
- package/src/render-error-thrower.tsx +20 -0
- package/src/response-utils.ts +62 -0
- package/src/reverse.ts +65 -15
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +7 -72
- package/src/route-definition/dsl-helpers.ts +469 -276
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +113 -37
- package/src/route-definition/index.ts +3 -3
- package/src/route-definition/redirect.ts +53 -12
- package/src/route-definition/resolve-handler-use.ts +161 -0
- package/src/route-definition/use-item-types.ts +32 -0
- package/src/route-map-builder.ts +7 -17
- package/src/route-types.ts +37 -41
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +164 -17
- package/src/router/error-handling.ts +45 -18
- package/src/router/find-match.ts +45 -22
- package/src/router/handler-context.ts +110 -39
- package/src/router/instrument.ts +350 -0
- package/src/router/intercept-resolution.ts +50 -24
- package/src/router/lazy-includes.ts +19 -53
- package/src/router/loader-resolution.ts +274 -56
- package/src/router/logging.ts +5 -8
- package/src/router/manifest.ts +49 -45
- package/src/router/match-api.ts +121 -205
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +58 -58
- package/src/router/match-middleware/background-revalidation.ts +33 -6
- package/src/router/match-middleware/cache-lookup.ts +214 -263
- package/src/router/match-middleware/cache-store.ts +73 -33
- package/src/router/match-middleware/intercept-resolution.ts +8 -28
- package/src/router/match-middleware/segment-resolution.ts +52 -18
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +104 -49
- package/src/router/metrics.ts +217 -26
- package/src/router/middleware-types.ts +24 -110
- package/src/router/middleware.ts +384 -197
- package/src/router/navigation-snapshot.ts +131 -0
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +148 -91
- package/src/router/prefetch-cache-ttl.ts +51 -0
- package/src/router/prerender-match.ts +199 -56
- package/src/router/preview-match.ts +32 -102
- package/src/router/request-classification.ts +276 -0
- package/src/router/revalidation.ts +144 -74
- package/src/router/route-snapshot.ts +244 -0
- package/src/router/router-context.ts +8 -28
- package/src/router/router-interfaces.ts +129 -36
- package/src/router/router-options.ts +185 -23
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +281 -76
- package/src/router/segment-resolution/helpers.ts +116 -31
- package/src/router/segment-resolution/loader-cache.ts +63 -37
- package/src/router/segment-resolution/revalidation.ts +493 -391
- 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 +5 -1
- package/src/router/segment-wrappers.ts +8 -5
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry-otel.ts +161 -199
- package/src/router/telemetry.ts +96 -19
- package/src/router/timeout.ts +0 -20
- package/src/router/tracing.ts +206 -0
- package/src/router/trie-matching.ts +180 -58
- package/src/router/types.ts +10 -63
- package/src/router/url-params.ts +44 -0
- package/src/router.ts +182 -54
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +702 -460
- package/src/rsc/helpers.ts +168 -46
- package/src/rsc/index.ts +2 -25
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +127 -31
- package/src/rsc/manifest-init.ts +33 -42
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +98 -19
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-cache-serve.ts +238 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +99 -189
- package/src/rsc/rsc-rendering.ts +126 -106
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +269 -114
- package/src/rsc/ssr-setup.ts +144 -0
- package/src/rsc/types.ts +34 -6
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +49 -41
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +149 -0
- package/src/segment-system.tsx +281 -129
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +317 -63
- package/src/server/cookie-parse.ts +32 -0
- package/src/server/cookie-store.ts +80 -5
- package/src/server/handle-store.ts +40 -38
- package/src/server/loader-registry.ts +26 -46
- package/src/server/request-context.ts +425 -177
- package/src/server.ts +6 -0
- package/src/ssr/index.tsx +25 -16
- package/src/static-handler.ts +27 -18
- package/src/testing/cache-status.ts +162 -0
- package/src/testing/collect-handle.ts +40 -0
- package/src/testing/dispatch.ts +701 -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 +257 -0
- package/src/testing/generated-routes.ts +183 -0
- package/src/testing/index.ts +99 -0
- package/src/testing/internal/context.ts +371 -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 +343 -0
- package/src/testing/render-route.tsx +581 -0
- package/src/testing/run-loader.ts +385 -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 +20 -58
- package/src/theme/ThemeScript.tsx +7 -9
- package/src/theme/constants.ts +52 -13
- package/src/theme/index.ts +3 -19
- package/src/theme/theme-context.ts +1 -5
- package/src/theme/theme-script.ts +22 -21
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +0 -35
- package/src/types/cache-types.ts +17 -8
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +54 -41
- package/src/types/handler-context.ts +236 -88
- package/src/types/index.ts +1 -10
- package/src/types/loader-types.ts +44 -15
- package/src/types/request-scope.ts +112 -0
- package/src/types/route-config.ts +10 -45
- package/src/types/route-entry.ts +19 -7
- package/src/types/segments.ts +37 -19
- package/src/urls/include-helper.ts +33 -70
- package/src/urls/index.ts +1 -11
- package/src/urls/path-helper-types.ts +58 -11
- package/src/urls/path-helper.ts +57 -111
- package/src/urls/pattern-types.ts +48 -19
- package/src/urls/response-types.ts +25 -22
- package/src/urls/type-extraction.ts +58 -139
- package/src/urls/urls-function.ts +1 -18
- package/src/use-loader.tsx +346 -89
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +64 -91
- package/src/vite/discovery/discover-routers.ts +147 -88
- 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 +247 -145
- 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 +61 -13
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +10 -3
- package/src/vite/inject-client-debug.ts +36 -0
- package/src/vite/plugin-types.ts +155 -65
- package/src/vite/plugins/cjs-to-esm.ts +16 -19
- package/src/vite/plugins/client-ref-dedup.ts +120 -0
- package/src/vite/plugins/client-ref-hashing.ts +28 -15
- 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 +194 -0
- package/src/vite/plugins/expose-action-id.ts +49 -98
- package/src/vite/plugins/expose-id-utils.ts +96 -51
- package/src/vite/plugins/expose-ids/export-analysis.ts +101 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +15 -64
- package/src/vite/plugins/expose-ids/loader-transform.ts +14 -24
- package/src/vite/plugins/expose-ids/router-transform.ts +118 -29
- package/src/vite/plugins/expose-internal-ids.ts +553 -317
- package/src/vite/plugins/performance-tracks.ts +89 -0
- package/src/vite/plugins/refresh-cmd.ts +127 -0
- package/src/vite/plugins/use-cache-transform.ts +73 -83
- package/src/vite/plugins/version-injector.ts +21 -25
- package/src/vite/plugins/version-plugin.ts +46 -37
- package/src/vite/plugins/virtual-entries.ts +13 -18
- package/src/vite/rango.ts +241 -287
- package/src/vite/router-discovery.ts +956 -149
- package/src/vite/utils/ast-handler-extract.ts +26 -35
- package/src/vite/utils/banner.ts +4 -4
- package/src/vite/utils/bundle-analysis.ts +10 -15
- package/src/vite/utils/client-chunks.ts +184 -0
- package/src/vite/utils/directive-prologue.ts +40 -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 +20 -52
- package/src/vite/utils/prerender-utils.ts +141 -34
- package/src/vite/utils/shared-utils.ts +92 -42
- package/CLAUDE.md +0 -5
- 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 -6
- package/src/network-error-thrower.tsx +0 -23
- package/src/route-definition/route-function.ts +0 -119
- package/src/router/middleware-cookies.ts +0 -55
|
@@ -67,10 +67,11 @@
|
|
|
67
67
|
* Keep if:
|
|
68
68
|
* - component !== null (needs rendering)
|
|
69
69
|
* - type === "loader" (carries data even with null component)
|
|
70
|
+
* - client doesn't have the segment (structurally required parent node)
|
|
70
71
|
*
|
|
71
72
|
* Skip if:
|
|
72
|
-
* - component === null AND type !== "loader"
|
|
73
|
-
* - (
|
|
73
|
+
* - component === null AND type !== "loader" AND client has it cached
|
|
74
|
+
* - (Revalidation skip — client already has this segment's UI)
|
|
74
75
|
*
|
|
75
76
|
*
|
|
76
77
|
* INTERCEPT HANDLING
|
|
@@ -108,12 +109,9 @@
|
|
|
108
109
|
*/
|
|
109
110
|
import type { MatchResult, ResolvedSegment } from "../types.js";
|
|
110
111
|
import type { MatchContext, MatchPipelineState } from "./match-context.js";
|
|
111
|
-
import { generateServerTiming, logMetrics } from "./metrics.js";
|
|
112
112
|
import { debugLog } from "./logging.js";
|
|
113
|
+
import { appendMetric } from "./metrics.js";
|
|
113
114
|
|
|
114
|
-
/**
|
|
115
|
-
* Collect all segments from an async generator
|
|
116
|
-
*/
|
|
117
115
|
export async function collectSegments(
|
|
118
116
|
generator: AsyncGenerator<ResolvedSegment>,
|
|
119
117
|
): Promise<ResolvedSegment[]> {
|
|
@@ -125,8 +123,73 @@ export async function collectSegments(
|
|
|
125
123
|
}
|
|
126
124
|
|
|
127
125
|
/**
|
|
128
|
-
*
|
|
126
|
+
* Deduplicate inherited loader segments by loaderId.
|
|
127
|
+
*
|
|
128
|
+
* When a route has loaders and a child layout has parallel slots, the same
|
|
129
|
+
* loader is resolved twice: once for the route and once inherited into the
|
|
130
|
+
* layout (tagged with `_inherited`). The inherited copy is only needed when
|
|
131
|
+
* the route uses `loading()` — in that case, the loader data is inside a
|
|
132
|
+
* LoaderBoundary/Suspense that parallel slots can't reach through. Without
|
|
133
|
+
* loading(), useLoader() traverses parent contexts and finds the data.
|
|
129
134
|
*/
|
|
135
|
+
function deduplicateLoaderSegments(
|
|
136
|
+
segments: ResolvedSegment[],
|
|
137
|
+
logPrefix: string,
|
|
138
|
+
): { segments: ResolvedSegment[]; removedIds: Set<string> } {
|
|
139
|
+
// Single pass: original (non-inherited) loaderIds, all loaderIds grouped by
|
|
140
|
+
// namespace, and namespaces of segments that declare loading().
|
|
141
|
+
const originalLoaders = new Set<string>();
|
|
142
|
+
const loaderIdsByNamespace = new Map<string, string[]>();
|
|
143
|
+
const namespacesWithLoading = new Set<string>();
|
|
144
|
+
for (const s of segments) {
|
|
145
|
+
if (s.type === "loader" && s.loaderId) {
|
|
146
|
+
if (!s._inherited) originalLoaders.add(s.loaderId);
|
|
147
|
+
const ids = loaderIdsByNamespace.get(s.namespace);
|
|
148
|
+
if (ids) ids.push(s.loaderId);
|
|
149
|
+
else loaderIdsByNamespace.set(s.namespace, [s.loaderId]);
|
|
150
|
+
} else if (
|
|
151
|
+
s.type !== "loader" &&
|
|
152
|
+
s.loading !== undefined &&
|
|
153
|
+
s.loading !== false
|
|
154
|
+
) {
|
|
155
|
+
namespacesWithLoading.add(s.namespace);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const loadersWithLoading = new Set<string>();
|
|
160
|
+
for (const ns of namespacesWithLoading) {
|
|
161
|
+
for (const id of loaderIdsByNamespace.get(ns) ?? []) {
|
|
162
|
+
loadersWithLoading.add(id);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const result: ResolvedSegment[] = [];
|
|
167
|
+
const removedIds = new Set<string>();
|
|
168
|
+
|
|
169
|
+
for (const s of segments) {
|
|
170
|
+
if (
|
|
171
|
+
s.type === "loader" &&
|
|
172
|
+
s.loaderId &&
|
|
173
|
+
s._inherited &&
|
|
174
|
+
originalLoaders.has(s.loaderId) &&
|
|
175
|
+
!loadersWithLoading.has(s.loaderId)
|
|
176
|
+
) {
|
|
177
|
+
removedIds.add(s.id);
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
result.push(s);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (removedIds.size > 0) {
|
|
184
|
+
debugLog(
|
|
185
|
+
logPrefix,
|
|
186
|
+
`deduped ${removedIds.size} inherited loader segment(s)`,
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return { segments: result, removedIds };
|
|
191
|
+
}
|
|
192
|
+
|
|
130
193
|
export function buildMatchResult<TEnv>(
|
|
131
194
|
allSegments: ResolvedSegment[],
|
|
132
195
|
ctx: MatchContext<TEnv>,
|
|
@@ -140,11 +203,6 @@ export function buildMatchResult<TEnv>(
|
|
|
140
203
|
let segmentsToRender: ResolvedSegment[];
|
|
141
204
|
|
|
142
205
|
if (ctx.isFullMatch) {
|
|
143
|
-
// Full match (document request) - all segments are rendered
|
|
144
|
-
// Deduplicate by segment ID (defense-in-depth). The primary dedup is in
|
|
145
|
-
// resolveAllSegments, but this guards against any path that bypasses it.
|
|
146
|
-
// include() scopes can produce entries that resolve the same shared layout,
|
|
147
|
-
// and duplicate IDs change the client's React tree depth causing remounts.
|
|
148
206
|
const seen = new Set<string>();
|
|
149
207
|
segmentsToRender = [];
|
|
150
208
|
for (const s of allSegments) {
|
|
@@ -155,63 +213,52 @@ export function buildMatchResult<TEnv>(
|
|
|
155
213
|
}
|
|
156
214
|
allIds = segmentsToRender.map((s) => s.id);
|
|
157
215
|
} else {
|
|
158
|
-
// Partial match (navigation) - filter and handle intercepts
|
|
159
|
-
// When intercepting, tell browser to keep its current segments + add modal
|
|
160
|
-
// This prevents the browser from discarding the current page content
|
|
161
|
-
// If client sent empty segments (HMR recovery), use segment IDs from allSegments
|
|
162
216
|
allIds = ctx.interceptResult
|
|
163
217
|
? ctx.clientSegmentIds.length > 0
|
|
164
218
|
? [...ctx.clientSegmentIds, ...state.interceptSegments.map((s) => s.id)]
|
|
165
|
-
: allSegments.map((s) => s.id)
|
|
219
|
+
: allSegments.map((s) => s.id)
|
|
166
220
|
: [...state.matchedIds, ...state.interceptSegments.map((s) => s.id)];
|
|
167
221
|
|
|
168
|
-
// Deduplicate allIds (defense-in-depth for partial match path)
|
|
169
222
|
allIds = [...new Set(allIds)];
|
|
170
223
|
|
|
171
|
-
|
|
172
|
-
// BUT always include loader segments - they carry data even with null component
|
|
224
|
+
const clientIdSet = new Set(ctx.clientSegmentIds);
|
|
173
225
|
segmentsToRender = allSegments.filter(
|
|
174
|
-
(s) =>
|
|
226
|
+
(s) =>
|
|
227
|
+
s.component !== null || s.type === "loader" || !clientIdSet.has(s.id),
|
|
175
228
|
);
|
|
176
229
|
}
|
|
177
230
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
hasComponent: s.component !== null,
|
|
183
|
-
})),
|
|
184
|
-
});
|
|
185
|
-
debugLog(logPrefix, "segments to render", {
|
|
186
|
-
segmentIds: segmentsToRender.map((s) => s.id),
|
|
187
|
-
});
|
|
231
|
+
const { segments: dedupedSegments, removedIds } = deduplicateLoaderSegments(
|
|
232
|
+
segmentsToRender,
|
|
233
|
+
logPrefix,
|
|
234
|
+
);
|
|
188
235
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
236
|
+
const matchedIds =
|
|
237
|
+
removedIds.size > 0 ? allIds.filter((id) => !removedIds.has(id)) : allIds;
|
|
238
|
+
|
|
239
|
+
const resolvedIds = ctx.isFullMatch
|
|
240
|
+
? allSegments.map((s) => s.id)
|
|
241
|
+
: allSegments.filter((s) => s._handlerRan).map((s) => s.id);
|
|
242
|
+
|
|
243
|
+
const cleanedSegments = dedupedSegments.map((s) => {
|
|
244
|
+
if (s._handlerRan === undefined) return s;
|
|
245
|
+
const { _handlerRan: _drop, ...rest } = s;
|
|
246
|
+
return rest as ResolvedSegment;
|
|
247
|
+
});
|
|
195
248
|
|
|
196
249
|
return {
|
|
197
|
-
segments:
|
|
198
|
-
matched:
|
|
199
|
-
diff:
|
|
250
|
+
segments: cleanedSegments,
|
|
251
|
+
matched: matchedIds,
|
|
252
|
+
diff: cleanedSegments.map((s) => s.id),
|
|
253
|
+
resolvedIds,
|
|
200
254
|
params: ctx.matched.params,
|
|
201
255
|
routeName: ctx.routeKey,
|
|
202
|
-
serverTiming,
|
|
203
256
|
slots: Object.keys(state.slots).length > 0 ? state.slots : undefined,
|
|
204
257
|
routeMiddleware:
|
|
205
258
|
ctx.routeMiddleware.length > 0 ? ctx.routeMiddleware : undefined,
|
|
206
259
|
};
|
|
207
260
|
}
|
|
208
261
|
|
|
209
|
-
/**
|
|
210
|
-
* Collect segments from pipeline and build MatchResult
|
|
211
|
-
*
|
|
212
|
-
* This is the main entry point for building the final result after
|
|
213
|
-
* the pipeline has processed all segments.
|
|
214
|
-
*/
|
|
215
262
|
export async function collectMatchResult<TEnv>(
|
|
216
263
|
pipeline: AsyncGenerator<ResolvedSegment>,
|
|
217
264
|
ctx: MatchContext<TEnv>,
|
|
@@ -219,10 +266,18 @@ export async function collectMatchResult<TEnv>(
|
|
|
219
266
|
): Promise<MatchResult> {
|
|
220
267
|
const allSegments = await collectSegments(pipeline);
|
|
221
268
|
|
|
222
|
-
|
|
269
|
+
const buildStart = performance.now();
|
|
270
|
+
|
|
223
271
|
if (state.segments.length === 0) {
|
|
224
272
|
state.segments = allSegments;
|
|
225
273
|
}
|
|
226
274
|
|
|
227
|
-
|
|
275
|
+
const result = buildMatchResult(allSegments, ctx, state);
|
|
276
|
+
appendMetric(
|
|
277
|
+
ctx.metricsStore,
|
|
278
|
+
"collect-result",
|
|
279
|
+
buildStart,
|
|
280
|
+
performance.now() - buildStart,
|
|
281
|
+
);
|
|
282
|
+
return result;
|
|
228
283
|
}
|
package/src/router/metrics.ts
CHANGED
|
@@ -1,28 +1,192 @@
|
|
|
1
|
-
|
|
2
|
-
* Router Metrics Utilities
|
|
3
|
-
*
|
|
4
|
-
* Performance metrics collection and reporting for RSC Router.
|
|
5
|
-
*/
|
|
1
|
+
import type { MetricsStore, PerformanceMetric } from "../server/context";
|
|
6
2
|
|
|
7
|
-
|
|
3
|
+
const BASE_INDENT = 2;
|
|
4
|
+
const DEPTH_INDENT = 2;
|
|
5
|
+
const TIMELINE_WIDTH = 40;
|
|
6
|
+
|
|
7
|
+
function formatMs(value: number): string {
|
|
8
|
+
return `${value.toFixed(2)}ms`;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function sortMetrics(metrics: PerformanceMetric[]): PerformanceMetric[] {
|
|
12
|
+
return [...metrics].sort((a, b) => {
|
|
13
|
+
if (a.label === "handler:total") return 1;
|
|
14
|
+
if (b.label === "handler:total") return -1;
|
|
15
|
+
return a.startTime - b.startTime;
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface Span {
|
|
20
|
+
startTime: number;
|
|
21
|
+
duration: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function renderTimeline(spans: Span[], total: number): string {
|
|
25
|
+
if (TIMELINE_WIDTH <= 0) {
|
|
26
|
+
return "||";
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const cells = Array(TIMELINE_WIDTH).fill(".");
|
|
30
|
+
|
|
31
|
+
if (!(total > 0)) {
|
|
32
|
+
cells[0] = "#";
|
|
33
|
+
return `|${cells.join("")}|`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
for (const span of spans) {
|
|
37
|
+
const start = Math.max(0, span.startTime);
|
|
38
|
+
const end = Math.max(start, span.startTime + span.duration);
|
|
39
|
+
const startColumn = Math.min(
|
|
40
|
+
TIMELINE_WIDTH - 1,
|
|
41
|
+
Math.floor((start / total) * TIMELINE_WIDTH),
|
|
42
|
+
);
|
|
43
|
+
const endColumn = Math.max(
|
|
44
|
+
startColumn + 1,
|
|
45
|
+
Math.min(
|
|
46
|
+
TIMELINE_WIDTH,
|
|
47
|
+
Math.ceil((Math.min(total, end) / total) * TIMELINE_WIDTH),
|
|
48
|
+
),
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
cells.fill("#", startColumn, endColumn);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return `|${cells.join("")}|`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function createTimelineAxis(total: number): string {
|
|
58
|
+
const totalLabel = formatMs(total);
|
|
59
|
+
return `0ms${" ".repeat(
|
|
60
|
+
Math.max(1, TIMELINE_WIDTH - "0ms".length - totalLabel.length),
|
|
61
|
+
)}${totalLabel}`;
|
|
62
|
+
}
|
|
8
63
|
|
|
9
|
-
/**
|
|
10
|
-
* Create a metrics store for the request if debugPerformance is enabled
|
|
11
|
-
*/
|
|
12
64
|
export function createMetricsStore(
|
|
13
65
|
debugPerformance: boolean,
|
|
66
|
+
requestStart?: number,
|
|
14
67
|
): MetricsStore | undefined {
|
|
15
68
|
if (!debugPerformance) return undefined;
|
|
16
69
|
return {
|
|
17
70
|
enabled: true,
|
|
18
|
-
requestStart: performance.now(),
|
|
71
|
+
requestStart: requestStart ?? performance.now(),
|
|
19
72
|
metrics: [],
|
|
20
73
|
};
|
|
21
74
|
}
|
|
22
75
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
76
|
+
export function appendMetric(
|
|
77
|
+
metricsStore: MetricsStore | undefined,
|
|
78
|
+
label: string,
|
|
79
|
+
start: number,
|
|
80
|
+
duration: number,
|
|
81
|
+
depth?: number,
|
|
82
|
+
): void {
|
|
83
|
+
if (!metricsStore) return;
|
|
84
|
+
metricsStore.metrics.push({
|
|
85
|
+
label,
|
|
86
|
+
duration,
|
|
87
|
+
startTime: start - metricsStore.requestStart,
|
|
88
|
+
depth,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function buildMetricsTiming(
|
|
93
|
+
method: string,
|
|
94
|
+
pathname: string,
|
|
95
|
+
metricsStore: MetricsStore | undefined,
|
|
96
|
+
): string | undefined {
|
|
97
|
+
if (!metricsStore) return undefined;
|
|
98
|
+
logMetrics(method, pathname, metricsStore);
|
|
99
|
+
return generateServerTiming(metricsStore) || undefined;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
interface DisplayRow {
|
|
103
|
+
label: string;
|
|
104
|
+
startTime: number;
|
|
105
|
+
duration: number;
|
|
106
|
+
depth: number | undefined;
|
|
107
|
+
spans: Span[];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function buildDisplayRows(sorted: PerformanceMetric[]): DisplayRow[] {
|
|
111
|
+
const preMap = new Map<string, PerformanceMetric>();
|
|
112
|
+
const postMap = new Map<string, PerformanceMetric>();
|
|
113
|
+
const consumed = new Set<PerformanceMetric>();
|
|
114
|
+
|
|
115
|
+
for (const m of sorted) {
|
|
116
|
+
if (m.label.endsWith(":pre")) {
|
|
117
|
+
preMap.set(m.label.slice(0, -4), m);
|
|
118
|
+
} else if (m.label.endsWith(":post")) {
|
|
119
|
+
postMap.set(m.label.slice(0, -5), m);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const rows: DisplayRow[] = [];
|
|
124
|
+
|
|
125
|
+
for (const m of sorted) {
|
|
126
|
+
if (consumed.has(m)) continue;
|
|
127
|
+
|
|
128
|
+
if (m.label.endsWith(":pre")) {
|
|
129
|
+
const base = m.label.slice(0, -4);
|
|
130
|
+
const post = postMap.get(base);
|
|
131
|
+
if (post) {
|
|
132
|
+
// Merge into a single row with two disjoint spans
|
|
133
|
+
consumed.add(m);
|
|
134
|
+
consumed.add(post);
|
|
135
|
+
rows.push({
|
|
136
|
+
label: base,
|
|
137
|
+
startTime: m.startTime,
|
|
138
|
+
duration: m.duration + post.duration,
|
|
139
|
+
depth: m.depth,
|
|
140
|
+
spans: [
|
|
141
|
+
{ startTime: m.startTime, duration: m.duration },
|
|
142
|
+
{ startTime: post.startTime, duration: post.duration },
|
|
143
|
+
],
|
|
144
|
+
});
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
// Lone :pre — display with base label
|
|
148
|
+
consumed.add(m);
|
|
149
|
+
rows.push({
|
|
150
|
+
label: base,
|
|
151
|
+
startTime: m.startTime,
|
|
152
|
+
duration: m.duration,
|
|
153
|
+
depth: m.depth,
|
|
154
|
+
spans: [{ startTime: m.startTime, duration: m.duration }],
|
|
155
|
+
});
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (m.label.endsWith(":post")) {
|
|
160
|
+
const base = m.label.slice(0, -5);
|
|
161
|
+
if (preMap.has(base)) {
|
|
162
|
+
// Already consumed as part of the pair above
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
// Lone :post — display with base label
|
|
166
|
+
consumed.add(m);
|
|
167
|
+
rows.push({
|
|
168
|
+
label: base,
|
|
169
|
+
startTime: m.startTime,
|
|
170
|
+
duration: m.duration,
|
|
171
|
+
depth: m.depth,
|
|
172
|
+
spans: [{ startTime: m.startTime, duration: m.duration }],
|
|
173
|
+
});
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Regular metric
|
|
178
|
+
rows.push({
|
|
179
|
+
label: m.label,
|
|
180
|
+
startTime: m.startTime,
|
|
181
|
+
duration: m.duration,
|
|
182
|
+
depth: m.depth,
|
|
183
|
+
spans: [{ startTime: m.startTime, duration: m.duration }],
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return rows;
|
|
188
|
+
}
|
|
189
|
+
|
|
26
190
|
export function logMetrics(
|
|
27
191
|
method: string,
|
|
28
192
|
pathname: string,
|
|
@@ -30,32 +194,59 @@ export function logMetrics(
|
|
|
30
194
|
): void {
|
|
31
195
|
const total = performance.now() - metricsStore.requestStart;
|
|
32
196
|
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
197
|
+
const sorted = sortMetrics(metricsStore.metrics);
|
|
198
|
+
const displayRows = buildDisplayRows(sorted);
|
|
199
|
+
|
|
200
|
+
const labels = displayRows.map(
|
|
201
|
+
(r) =>
|
|
202
|
+
`${" ".repeat(BASE_INDENT + (r.depth ?? 0) * DEPTH_INDENT)}${r.label}`,
|
|
203
|
+
);
|
|
204
|
+
const startValues = displayRows.map((r) => formatMs(r.startTime));
|
|
205
|
+
const durationValues = displayRows.map((r) => formatMs(r.duration));
|
|
206
|
+
const startWidth = Math.max(
|
|
207
|
+
"start".length,
|
|
208
|
+
...startValues.map((v) => v.length),
|
|
209
|
+
);
|
|
210
|
+
const durationWidth = Math.max(
|
|
211
|
+
"dur".length,
|
|
212
|
+
...durationValues.map((v) => v.length),
|
|
213
|
+
);
|
|
214
|
+
const spanWidth = Math.max(
|
|
215
|
+
"span".length,
|
|
216
|
+
...labels.map((label) => label.length),
|
|
217
|
+
22,
|
|
218
|
+
);
|
|
219
|
+
const timelinePadding = " ".repeat(
|
|
220
|
+
startWidth + 2 + durationWidth + 2 + spanWidth + 2,
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
console.log(`[RSC Perf] ${method} ${pathname} (${total.toFixed(2)}ms)`);
|
|
224
|
+
console.log(
|
|
225
|
+
`${"start".padStart(startWidth)} ${"dur".padStart(durationWidth)} ${"span".padEnd(spanWidth)} timeline`,
|
|
37
226
|
);
|
|
227
|
+
console.log(`${timelinePadding}${createTimelineAxis(total)}`);
|
|
38
228
|
|
|
39
|
-
|
|
229
|
+
for (let index = 0; index < displayRows.length; index++) {
|
|
230
|
+
const row = displayRows[index];
|
|
231
|
+
const label = labels[index].padEnd(spanWidth);
|
|
232
|
+
const start = formatMs(row.startTime).padStart(startWidth);
|
|
233
|
+
const duration = formatMs(row.duration).padStart(durationWidth);
|
|
40
234
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
235
|
+
console.log(
|
|
236
|
+
`${start} ${duration} ${label} ${renderTimeline(row.spans, total)}`,
|
|
237
|
+
);
|
|
44
238
|
}
|
|
45
239
|
}
|
|
46
240
|
|
|
47
|
-
/**
|
|
48
|
-
* Generate Server-Timing header value from metrics
|
|
49
|
-
* Format: metric-name;dur=X.XX
|
|
50
|
-
*/
|
|
51
241
|
export function generateServerTiming(metricsStore: MetricsStore): string {
|
|
52
242
|
return metricsStore.metrics
|
|
53
243
|
.map((m) => {
|
|
54
244
|
// Convert label to valid Server-Timing name (alphanumeric and hyphens)
|
|
55
|
-
const
|
|
245
|
+
const base = m.label
|
|
56
246
|
.replace(/:/g, "-")
|
|
57
247
|
.replace(/[^a-zA-Z0-9-]/g, "")
|
|
58
248
|
.toLowerCase();
|
|
249
|
+
const name = m.depth ? `d${m.depth}-${base}` : base;
|
|
59
250
|
return `${name};dur=${m.duration.toFixed(2)}`;
|
|
60
251
|
})
|
|
61
252
|
.join(", ");
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Middleware Types
|
|
3
|
-
*
|
|
4
|
-
* Type definitions and interfaces for the middleware system.
|
|
5
|
-
* Separated from execution logic for cleaner imports.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
1
|
import type { ContextVar } from "../context-var.js";
|
|
9
2
|
import type {
|
|
10
3
|
DefaultReverseRouteMap,
|
|
@@ -12,26 +5,24 @@ import type {
|
|
|
12
5
|
DefaultVars,
|
|
13
6
|
} from "../types/global-namespace.js";
|
|
14
7
|
import type { ScopedReverseFunction } from "../reverse.js";
|
|
8
|
+
import type { Theme } from "../theme/types.js";
|
|
9
|
+
import type { LocationStateEntry } from "../browser/react/location-state-shared.js";
|
|
10
|
+
import type { RequestScope } from "../types/request-scope.js";
|
|
15
11
|
|
|
16
|
-
/**
|
|
17
|
-
* Get variable function type
|
|
18
|
-
*/
|
|
19
12
|
type GetVariableFn = {
|
|
20
13
|
<T>(contextVar: ContextVar<T>): T | undefined;
|
|
21
14
|
<K extends keyof DefaultVars>(key: K): DefaultVars[K];
|
|
22
15
|
};
|
|
23
16
|
|
|
24
|
-
/**
|
|
25
|
-
* Set variable function type
|
|
26
|
-
*/
|
|
27
17
|
type SetVariableFn = {
|
|
28
|
-
<T>(contextVar: ContextVar<T>, value: T): void;
|
|
29
|
-
<K extends keyof DefaultVars>(
|
|
18
|
+
<T>(contextVar: ContextVar<T>, value: T, options?: { cache?: boolean }): void;
|
|
19
|
+
<K extends keyof DefaultVars>(
|
|
20
|
+
key: K,
|
|
21
|
+
value: DefaultVars[K],
|
|
22
|
+
options?: { cache?: boolean },
|
|
23
|
+
): void;
|
|
30
24
|
};
|
|
31
25
|
|
|
32
|
-
/**
|
|
33
|
-
* Cookie options for setting cookies
|
|
34
|
-
*/
|
|
35
26
|
export interface CookieOptions {
|
|
36
27
|
domain?: string;
|
|
37
28
|
path?: string;
|
|
@@ -42,137 +33,60 @@ export interface CookieOptions {
|
|
|
42
33
|
sameSite?: "strict" | "lax" | "none";
|
|
43
34
|
}
|
|
44
35
|
|
|
45
|
-
/**
|
|
46
|
-
* Context passed to middleware
|
|
47
|
-
*
|
|
48
|
-
* @template TEnv - Environment type (bindings, variables) - defaults to any for internal flexibility
|
|
49
|
-
* @template TParams - URL params type (typed for route middleware, Record<string, string> for global middleware)
|
|
50
|
-
*/
|
|
51
36
|
export interface MiddlewareContext<
|
|
52
37
|
TEnv = any,
|
|
53
|
-
TParams = Record<string, string>,
|
|
54
|
-
> {
|
|
55
|
-
/** Original request */
|
|
56
|
-
request: Request;
|
|
57
|
-
|
|
58
|
-
/** Parsed URL */
|
|
59
|
-
url: URL;
|
|
60
|
-
|
|
61
|
-
/** URL pathname */
|
|
62
|
-
pathname: string;
|
|
63
|
-
|
|
64
|
-
/** URL search params */
|
|
65
|
-
searchParams: URLSearchParams;
|
|
66
|
-
|
|
67
|
-
/** Platform bindings (Cloudflare, etc.) */
|
|
68
|
-
env: TEnv;
|
|
69
|
-
|
|
70
|
-
/** URL params extracted from route/middleware pattern */
|
|
38
|
+
TParams = Record<string, string | undefined>,
|
|
39
|
+
> extends RequestScope<TEnv> {
|
|
71
40
|
params: TParams;
|
|
72
41
|
|
|
73
|
-
|
|
74
|
-
* Response stub (read-only). Before `next()`, returns the shared response stub
|
|
75
|
-
* where headers and cookies accumulate. After `next()`, returns the downstream response.
|
|
76
|
-
*
|
|
77
|
-
* Use `ctx.header()` to set response headers, or `cookies()` for cookie mutations.
|
|
78
|
-
* To replace the response entirely, return a new `Response` from the middleware.
|
|
79
|
-
*/
|
|
80
|
-
readonly res: Response;
|
|
42
|
+
readonly headers: Headers;
|
|
81
43
|
|
|
82
|
-
/** Get a context variable (shared with route handlers) */
|
|
83
44
|
get: GetVariableFn;
|
|
84
45
|
|
|
85
|
-
/** Set a context variable (shared with route handlers) */
|
|
86
46
|
set: SetVariableFn;
|
|
87
47
|
|
|
88
|
-
/**
|
|
89
|
-
* Set a response header - can be called before or after `next()`
|
|
90
|
-
*
|
|
91
|
-
* When called before `next()`, headers are queued and merged into the final response.
|
|
92
|
-
* When called after `next()`, headers are set directly on the response.
|
|
93
|
-
* Shorthand for `ctx.res.headers.set()`.
|
|
94
|
-
*/
|
|
95
48
|
header(name: string, value: string): void;
|
|
96
49
|
|
|
97
|
-
/**
|
|
98
|
-
* The matched route name, if available and the route has an explicit name.
|
|
99
|
-
* Undefined for global middleware (runs before route matching) or unnamed routes.
|
|
100
|
-
*/
|
|
101
50
|
routeName?: DefaultRouteName;
|
|
102
51
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
52
|
+
debugPerformance(): void;
|
|
53
|
+
|
|
54
|
+
theme?: Theme;
|
|
55
|
+
|
|
56
|
+
setTheme?: (theme: Theme) => void;
|
|
57
|
+
|
|
58
|
+
setLocationState(entries: LocationStateEntry | LocationStateEntry[]): void;
|
|
59
|
+
|
|
107
60
|
reverse: ScopedReverseFunction<
|
|
108
61
|
Record<string, string>,
|
|
109
62
|
DefaultReverseRouteMap
|
|
110
63
|
>;
|
|
111
64
|
}
|
|
112
65
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
* @template TParams - URL params type (typed for route middleware)
|
|
118
|
-
*
|
|
119
|
-
* When using middleware with global augmentation (RSCRouter.Env), explicitly
|
|
120
|
-
* annotate your middleware functions, or the types will be inferred from context:
|
|
121
|
-
*
|
|
122
|
-
* @example
|
|
123
|
-
* ```typescript
|
|
124
|
-
* // With explicit annotation (recommended for reusable middleware)
|
|
125
|
-
* const authMiddleware: MiddlewareFn<AppEnv> = async (ctx, next) => {...}
|
|
126
|
-
*
|
|
127
|
-
* // Types inferred from router.use() call
|
|
128
|
-
* router.use((ctx, next) => {...}) // ctx is typed from router's TEnv
|
|
129
|
-
* ```
|
|
130
|
-
*/
|
|
131
|
-
export type MiddlewareFn<TEnv = any, TParams = Record<string, string>> = (
|
|
66
|
+
export type MiddlewareFn<
|
|
67
|
+
TEnv = any,
|
|
68
|
+
TParams = Record<string, string | undefined>,
|
|
69
|
+
> = (
|
|
132
70
|
ctx: MiddlewareContext<TEnv, TParams>,
|
|
133
71
|
next: () => Promise<Response>,
|
|
134
72
|
) => Response | void | Promise<Response | void>;
|
|
135
73
|
|
|
136
|
-
/**
|
|
137
|
-
* Stored middleware entry with pattern matching info
|
|
138
|
-
* @internal - uses any for internal flexibility
|
|
139
|
-
*/
|
|
140
74
|
export interface MiddlewareEntry<TEnv = any> {
|
|
141
|
-
/** Original pattern string */
|
|
142
75
|
pattern: string | null;
|
|
143
|
-
|
|
144
|
-
/** Compiled regex for matching */
|
|
145
76
|
regex: RegExp | null;
|
|
146
|
-
|
|
147
|
-
/** Param names extracted from pattern */
|
|
148
77
|
paramNames: string[];
|
|
149
|
-
|
|
150
|
-
/** The middleware function */
|
|
151
78
|
handler: MiddlewareFn<TEnv>;
|
|
152
|
-
|
|
153
|
-
/** Mount prefix this middleware is scoped to (null = global) */
|
|
154
|
-
mountPrefix: string | null;
|
|
155
79
|
}
|
|
156
80
|
|
|
157
|
-
/**
|
|
158
|
-
* Mutable response holder - allows ctx.res to be updated after next() is called
|
|
159
|
-
*/
|
|
160
81
|
export interface ResponseHolder {
|
|
161
82
|
response: Response | null;
|
|
162
83
|
}
|
|
163
84
|
|
|
164
|
-
/**
|
|
165
|
-
* Entry type for middleware collection
|
|
166
|
-
* Matches the shape of EntryData used in router.ts
|
|
167
|
-
*/
|
|
168
85
|
export interface MiddlewareCollectableEntry {
|
|
169
86
|
middleware?: MiddlewareFn<any, any>[];
|
|
170
87
|
layout?: MiddlewareCollectableEntry[];
|
|
171
88
|
}
|
|
172
89
|
|
|
173
|
-
/**
|
|
174
|
-
* Collected route middleware with params
|
|
175
|
-
*/
|
|
176
90
|
export interface CollectedMiddleware {
|
|
177
91
|
handler: MiddlewareFn<any, any>;
|
|
178
92
|
params: Record<string, string>;
|