@rangojs/router 0.0.0-experimental.bd6e11bc → 0.0.0-experimental.bdaf10aa
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 +8 -4
- package/README.md +296 -887
- package/dist/bin/rango.js +459 -91
- package/dist/testing/vitest.js +36 -2
- package/dist/vite/index.js +1708 -414
- package/package.json +35 -10
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +82 -5
- package/skills/bundle-analysis/SKILL.md +2 -2
- package/skills/cache-guide/SKILL.md +14 -9
- package/skills/caching/SKILL.md +221 -12
- package/skills/catalog.json +271 -0
- package/skills/comparison/SKILL.md +50 -0
- package/skills/comparison/agents/openai.yaml +4 -0
- package/skills/comparison/references/framework-comparison.md +837 -0
- package/skills/composability/SKILL.md +83 -2
- package/skills/css/SKILL.md +76 -0
- package/skills/debug-manifest/SKILL.md +5 -3
- package/skills/defer-hydration/SKILL.md +235 -0
- package/skills/document-cache/SKILL.md +11 -3
- package/skills/fonts/SKILL.md +1 -1
- package/skills/handler-use/SKILL.md +9 -9
- package/skills/hooks/SKILL.md +73 -900
- package/skills/hooks/data.md +273 -0
- package/skills/hooks/handle-and-actions.md +103 -0
- package/skills/hooks/navigation.md +110 -0
- package/skills/hooks/outlets.md +41 -0
- package/skills/hooks/state.md +228 -0
- package/skills/hooks/urls.md +135 -0
- package/skills/host-router/SKILL.md +84 -7
- package/skills/i18n/SKILL.md +1 -1
- package/skills/intercept/SKILL.md +51 -17
- package/skills/layout/SKILL.md +38 -16
- package/skills/links/SKILL.md +1 -1
- package/skills/loader/SKILL.md +48 -20
- package/skills/middleware/SKILL.md +11 -5
- package/skills/migrate-nextjs/SKILL.md +203 -20
- package/skills/migrate-react-router/SKILL.md +59 -675
- package/skills/migrate-react-router/cloudflare-workers.md +129 -0
- package/skills/migrate-react-router/component-migration.md +196 -0
- package/skills/migrate-react-router/data-and-actions.md +225 -0
- package/skills/migrate-react-router/route-mapping.md +271 -0
- package/skills/mime-routes/SKILL.md +3 -3
- package/skills/observability/SKILL.md +70 -5
- package/skills/parallel/SKILL.md +32 -8
- package/skills/ppr/SKILL.md +622 -0
- package/skills/prerender/SKILL.md +59 -28
- package/skills/rango/SKILL.md +124 -50
- package/skills/response-routes/SKILL.md +78 -46
- package/skills/route/SKILL.md +85 -6
- package/skills/router-setup/SKILL.md +41 -6
- package/skills/scripts/SKILL.md +179 -0
- package/skills/server-actions/SKILL.md +28 -3
- package/skills/shell-manifest/SKILL.md +185 -0
- package/skills/streams-and-websockets/SKILL.md +1 -1
- package/skills/tailwind/SKILL.md +28 -4
- package/skills/testing/SKILL.md +68 -654
- 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 +131 -0
- package/skills/testing/loader.md +128 -0
- package/skills/testing/middleware.md +99 -0
- package/skills/testing/render-handler.md +122 -0
- package/skills/testing/response-routes.md +95 -0
- package/skills/testing/reverse-and-types.md +85 -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/theme/SKILL.md +1 -1
- package/skills/typesafety/SKILL.md +45 -918
- package/skills/typesafety/env-and-bindings.md +254 -0
- package/skills/typesafety/generated-files-and-cli.md +335 -0
- package/skills/typesafety/params-and-search.md +153 -0
- package/skills/typesafety/route-types.md +209 -0
- package/skills/use-cache/SKILL.md +47 -17
- package/skills/vercel/SKILL.md +128 -0
- package/skills/view-transitions/SKILL.md +44 -1
- package/src/__augment-tests__/augmented.check.ts +2 -3
- package/src/__internal.ts +0 -65
- package/src/browser/action-coordinator.ts +1 -1
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +14 -27
- package/src/browser/connection-warmup.ts +134 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +178 -100
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/logging.ts +28 -0
- package/src/browser/merge-segment-loaders.ts +6 -4
- package/src/browser/navigation-bridge.ts +81 -68
- package/src/browser/navigation-client.ts +115 -70
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +153 -88
- package/src/browser/navigation-transaction.ts +0 -32
- package/src/browser/network-error-handler.ts +34 -7
- package/src/browser/partial-update.ts +157 -144
- package/src/browser/prefetch/cache.ts +148 -81
- package/src/browser/prefetch/fetch.ts +231 -51
- package/src/browser/prefetch/queue.ts +25 -7
- package/src/browser/rango-state.ts +157 -115
- package/src/browser/react/Link.tsx +40 -7
- package/src/browser/react/NavigationProvider.tsx +140 -99
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/filter-segment-order.ts +17 -2
- package/src/browser/react/index.ts +0 -51
- package/src/browser/react/location-state-shared.ts +14 -15
- package/src/browser/react/location-state.ts +0 -1
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +0 -5
- 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 +10 -5
- package/src/browser/react/use-params.ts +0 -2
- package/src/browser/react/use-router.ts +6 -4
- 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 +74 -8
- package/src/browser/rsc-router.tsx +97 -22
- package/src/browser/scroll-restoration.ts +15 -8
- package/src/browser/segment-reconciler.ts +31 -21
- package/src/browser/server-action-bridge.ts +216 -38
- package/src/browser/types.ts +94 -22
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/generate-manifest.ts +155 -131
- package/src/build/generate-route-types.ts +1 -1
- package/src/build/index.ts +11 -5
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +152 -22
- package/src/build/route-types/ast-route-extraction.ts +15 -8
- package/src/build/route-types/codegen.ts +12 -1
- package/src/build/route-types/include-resolution.ts +455 -61
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/per-module-writer.ts +15 -2
- package/src/build/route-types/router-processing.ts +77 -41
- package/src/build/route-types/source-scan.ts +105 -7
- package/src/build/runtime-discovery.ts +4 -1
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-key-utils.ts +58 -13
- package/src/cache/cache-policy.ts +108 -34
- package/src/cache/cache-runtime.ts +454 -101
- package/src/cache/cache-scope.ts +159 -54
- package/src/cache/cache-tag.ts +149 -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 +2170 -377
- 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 +6 -16
- package/src/cache/document-cache.ts +126 -41
- package/src/cache/handle-snapshot.ts +70 -0
- package/src/cache/index.ts +23 -20
- package/src/cache/memory-segment-store.ts +243 -37
- package/src/cache/profile-registry.ts +46 -31
- package/src/cache/read-through-swr.ts +56 -12
- package/src/cache/segment-codec.ts +13 -21
- package/src/cache/shell-snapshot.ts +417 -0
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/types.ts +194 -99
- package/src/cache/vercel/index.ts +11 -0
- package/src/cache/vercel/vercel-cache-store.ts +1132 -0
- package/src/client.rsc.tsx +39 -22
- package/src/client.tsx +28 -58
- package/src/cloudflare/index.ts +11 -0
- package/src/cloudflare/tracing.ts +108 -0
- package/src/component-utils.ts +19 -0
- package/src/components/DefaultDocument.tsx +8 -2
- package/src/context-var.ts +13 -1
- package/src/decode-loader-results.ts +18 -2
- package/src/defer.ts +185 -0
- package/src/deps/ssr.ts +0 -1
- package/src/encode-kv.ts +49 -0
- package/src/errors.ts +0 -3
- package/src/escape-script.ts +52 -0
- package/src/handle.ts +57 -40
- package/src/handles/MetaTags.tsx +24 -53
- package/src/handles/Scripts.tsx +183 -0
- package/src/handles/breadcrumbs.ts +35 -8
- package/src/handles/deferred-resolution.ts +127 -0
- package/src/handles/is-thenable.ts +18 -0
- package/src/handles/meta.ts +14 -40
- package/src/handles/script.ts +244 -0
- package/src/host/cookie-handler.ts +9 -60
- package/src/host/errors.ts +13 -22
- package/src/host/index.ts +7 -0
- package/src/host/pattern-matcher.ts +23 -52
- package/src/host/router.ts +1 -65
- package/src/host/testing.ts +40 -27
- package/src/host/types.ts +6 -2
- package/src/href-client.ts +7 -12
- package/src/index.rsc.ts +88 -8
- package/src/index.ts +90 -16
- package/src/internal-debug.ts +11 -10
- package/src/loader.rsc.ts +19 -9
- package/src/loader.ts +12 -4
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +16 -16
- package/src/prerender/store.ts +32 -37
- package/src/prerender.ts +75 -7
- package/src/redirect-origin.ts +114 -0
- package/src/regex-escape.ts +8 -0
- package/src/render-error-thrower.tsx +20 -0
- package/src/response-utils.ts +25 -0
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +13 -49
- package/src/route-definition/dsl-helpers.ts +60 -53
- package/src/route-definition/helper-factories.ts +0 -2
- package/src/route-definition/helpers-types.ts +46 -46
- package/src/route-definition/index.ts +1 -2
- package/src/route-definition/redirect.ts +44 -11
- package/src/route-definition/resolve-handler-use.ts +6 -1
- package/src/route-definition/use-item-types.ts +3 -6
- package/src/route-map-builder.ts +41 -20
- package/src/route-types.ts +0 -5
- package/src/router/content-negotiation.ts +58 -23
- package/src/router/error-handling.ts +44 -17
- package/src/router/find-match.ts +129 -30
- package/src/router/handler-context.ts +6 -1
- package/src/router/instrument.ts +355 -0
- package/src/router/intercept-resolution.ts +35 -2
- package/src/router/lazy-includes.ts +79 -56
- package/src/router/loader-resolution.ts +151 -73
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +74 -40
- package/src/router/match-api.ts +76 -52
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +181 -178
- package/src/router/match-middleware/background-revalidation.ts +40 -24
- package/src/router/match-middleware/cache-lookup.ts +115 -194
- package/src/router/match-middleware/cache-store.ts +61 -50
- 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 +36 -67
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-types.ts +0 -116
- package/src/router/middleware.ts +231 -120
- package/src/router/navigation-snapshot.ts +7 -56
- package/src/router/params-util.ts +23 -0
- package/src/router/parse-pattern.ts +115 -0
- package/src/router/pattern-matching.ts +99 -152
- package/src/router/prefetch-cache-ttl.ts +51 -0
- package/src/router/prefetch-limits.ts +37 -0
- package/src/router/prerender-match.ts +111 -66
- package/src/router/preview-match.ts +3 -1
- package/src/router/request-classification.ts +47 -42
- package/src/router/revalidation.ts +75 -81
- package/src/router/route-snapshot.ts +14 -3
- package/src/router/router-context.ts +6 -29
- package/src/router/router-interfaces.ts +70 -8
- package/src/router/router-options.ts +126 -4
- package/src/router/segment-resolution/fresh.ts +104 -80
- package/src/router/segment-resolution/helpers.ts +86 -6
- package/src/router/segment-resolution/loader-cache.ts +155 -39
- package/src/router/segment-resolution/loader-mask.ts +60 -0
- package/src/router/segment-resolution/loader-snapshot.ts +259 -0
- package/src/router/segment-resolution/mask-nested.ts +83 -0
- package/src/router/segment-resolution/revalidation.ts +215 -304
- 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 +35 -15
- package/src/router/segment-resolution.ts +5 -1
- package/src/router/segment-wrappers.ts +6 -5
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +54 -35
- package/src/router/telemetry-otel.ts +160 -200
- package/src/router/telemetry.ts +9 -23
- package/src/router/timeout.ts +0 -20
- package/src/router/tracing.ts +215 -0
- package/src/router/trie-matching.ts +171 -64
- package/src/router/types.ts +1 -63
- package/src/router/url-params.ts +13 -5
- package/src/router.ts +119 -48
- package/src/rsc/full-payload.ts +70 -0
- package/src/rsc/handler-context.ts +1 -0
- package/src/rsc/handler.ts +267 -152
- package/src/rsc/helpers.ts +78 -4
- package/src/rsc/index.ts +1 -4
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +114 -38
- package/src/rsc/manifest-init.ts +29 -42
- package/src/rsc/nonce.ts +10 -1
- package/src/rsc/origin-guard.ts +11 -15
- package/src/rsc/progressive-enhancement.ts +120 -13
- package/src/rsc/redirect-guard.ts +100 -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 +58 -141
- package/src/rsc/rsc-rendering.ts +492 -49
- package/src/rsc/runtime-warnings.ts +14 -0
- package/src/rsc/server-action.ts +268 -82
- package/src/rsc/shell-capture.ts +1190 -0
- package/src/rsc/shell-serve.ts +181 -0
- package/src/rsc/transition-gate.ts +89 -0
- package/src/rsc/types.ts +45 -3
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +31 -26
- package/src/segment-loader-promise.ts +49 -4
- package/src/segment-system.tsx +260 -95
- package/src/server/context.ts +99 -9
- package/src/server/cookie-parse.ts +32 -0
- package/src/server/cookie-store.ts +125 -2
- package/src/server/handle-store.ts +21 -38
- package/src/server/loader-registry.ts +33 -42
- package/src/server/request-context.ts +379 -138
- package/src/ssr/index.tsx +491 -182
- package/src/ssr/inject-rsc-eager.ts +167 -0
- package/src/ssr/ssr-root.tsx +228 -0
- package/src/static-handler.ts +10 -13
- package/src/testing/cache-status.ts +44 -48
- package/src/testing/collect-handle.ts +14 -31
- package/src/testing/dispatch.ts +533 -160
- package/src/testing/e2e/fixture.ts +45 -11
- package/src/testing/e2e/index.ts +1 -22
- package/src/testing/e2e/matchers.ts +0 -16
- package/src/testing/e2e/parity.ts +85 -4
- package/src/testing/e2e/server.ts +12 -0
- package/src/testing/flight-matchers.ts +7 -14
- package/src/testing/flight-normalize.ts +11 -0
- package/src/testing/flight-runtime.d.ts +36 -0
- package/src/testing/flight-tree.ts +682 -0
- package/src/testing/flight.entry.ts +30 -0
- package/src/testing/flight.ts +145 -70
- package/src/testing/generated-routes.ts +26 -50
- package/src/testing/index.ts +18 -19
- package/src/testing/internal/context.ts +184 -68
- 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 +357 -0
- package/src/testing/render-route.tsx +134 -115
- package/src/testing/run-loader.ts +140 -51
- package/src/testing/run-middleware.ts +59 -33
- package/src/testing/run-transition-when.ts +164 -0
- package/src/testing/vitest-stubs/cloudflare-email.ts +1 -1
- package/src/testing/vitest-stubs/cloudflare-workers.ts +1 -1
- package/src/testing/vitest.ts +138 -16
- package/src/theme/ThemeProvider.tsx +56 -84
- package/src/theme/ThemeScript.tsx +7 -9
- package/src/theme/constants.ts +52 -13
- package/src/theme/index.ts +0 -7
- 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 +13 -4
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +15 -15
- package/src/types/handler-context.ts +45 -15
- package/src/types/index.ts +2 -10
- package/src/types/loader-types.ts +6 -3
- package/src/types/request-scope.ts +8 -22
- package/src/types/route-config.ts +20 -52
- package/src/types/route-entry.ts +0 -6
- package/src/types/segments.ts +100 -13
- package/src/urls/include-helper.ts +10 -12
- package/src/urls/include-provider.ts +71 -0
- package/src/urls/index.ts +2 -8
- package/src/urls/path-helper-types.ts +52 -14
- package/src/urls/path-helper.ts +5 -54
- package/src/urls/pattern-types.ts +36 -0
- package/src/urls/type-extraction.ts +76 -42
- package/src/urls/urls-function.ts +0 -14
- package/src/use-loader.tsx +0 -186
- package/src/vercel/index.ts +11 -0
- package/src/vercel/tracing.ts +88 -0
- package/src/vite/discovery/bundle-postprocess.ts +2 -1
- package/src/vite/discovery/dev-prerender-cache.ts +117 -0
- package/src/vite/discovery/discover-routers.ts +34 -43
- package/src/vite/discovery/discovery-errors.ts +61 -0
- package/src/vite/discovery/prerender-collection.ts +33 -46
- package/src/vite/discovery/state.ts +12 -1
- package/src/vite/discovery/virtual-module-codegen.ts +1 -11
- package/src/vite/index.ts +9 -0
- package/src/vite/inject-client-debug.ts +88 -0
- package/src/vite/plugin-types.ts +143 -10
- package/src/vite/plugins/cjs-to-esm.ts +8 -12
- package/src/vite/plugins/client-ref-dedup.ts +0 -11
- package/src/vite/plugins/client-ref-hashing.ts +0 -10
- package/src/vite/plugins/cloudflare-protocol-stub.ts +0 -20
- package/src/vite/plugins/expose-action-id.ts +2 -73
- package/src/vite/plugins/expose-id-utils.ts +85 -56
- package/src/vite/plugins/expose-ids/export-analysis.ts +30 -43
- package/src/vite/plugins/expose-ids/handler-transform.ts +5 -31
- package/src/vite/plugins/expose-ids/loader-transform.ts +12 -20
- package/src/vite/plugins/expose-ids/router-transform.ts +98 -26
- package/src/vite/plugins/expose-internal-ids.ts +10 -1
- package/src/vite/plugins/performance-tracks.ts +0 -3
- package/src/vite/plugins/refresh-cmd.ts +1 -1
- package/src/vite/plugins/use-cache-transform.ts +21 -46
- package/src/vite/plugins/vercel-output.ts +384 -0
- package/src/vite/plugins/version-injector.ts +22 -27
- package/src/vite/plugins/version-plugin.ts +6 -66
- package/src/vite/plugins/virtual-entries.ts +137 -26
- package/src/vite/rango.ts +146 -135
- package/src/vite/router-discovery.ts +189 -48
- package/src/vite/utils/ast-handler-extract.ts +11 -20
- package/src/vite/utils/bundle-analysis.ts +6 -13
- package/src/vite/utils/client-chunks.ts +0 -6
- package/src/vite/utils/directive-prologue.ts +40 -0
- package/src/vite/utils/forward-user-plugins.ts +0 -22
- package/src/vite/utils/manifest-utils.ts +4 -75
- package/src/vite/utils/package-resolution.ts +1 -73
- package/src/vite/utils/prerender-utils.ts +71 -44
- package/src/vite/utils/shared-utils.ts +55 -37
- 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/network-error-thrower.tsx +0 -23
- package/src/router/middleware-cookies.ts +0 -55
|
@@ -11,6 +11,7 @@ import type { UrlPatterns } from "../urls.js";
|
|
|
11
11
|
import type { UrlBuilder } from "../urls/pattern-types.js";
|
|
12
12
|
import type { NamedRouteEntry } from "./content-negotiation.js";
|
|
13
13
|
import type { TelemetrySink } from "./telemetry.js";
|
|
14
|
+
import type { RouterTracingConfig } from "./tracing.js";
|
|
14
15
|
import type { RouterTimeouts, OnTimeoutCallback } from "./timeout.js";
|
|
15
16
|
|
|
16
17
|
/**
|
|
@@ -496,6 +497,51 @@ export interface RangoOptions<TEnv = any> {
|
|
|
496
497
|
*/
|
|
497
498
|
prefetchCacheTTL?: number | false;
|
|
498
499
|
|
|
500
|
+
/**
|
|
501
|
+
* Maximum number of decoded prefetch payloads the client keeps in its
|
|
502
|
+
* in-memory prefetch cache. When the cache is full the oldest entry is
|
|
503
|
+
* evicted (FIFO) to make room for a new prefetch.
|
|
504
|
+
*
|
|
505
|
+
* Each entry retains a fully decoded RSC payload (and the route's client
|
|
506
|
+
* chunks pulled in while decoding), so this is the lever on client-side
|
|
507
|
+
* prefetch memory: a higher value warms more routes at the cost of more
|
|
508
|
+
* retained payloads. Staleness is bounded separately by `prefetchCacheTTL`;
|
|
509
|
+
* this bounds the entry COUNT.
|
|
510
|
+
*
|
|
511
|
+
* Values below 1 (or non-finite) fall back to the default. To turn
|
|
512
|
+
* prefetching off entirely, set `prefetchCacheTTL: false` instead.
|
|
513
|
+
*
|
|
514
|
+
* @default 100
|
|
515
|
+
*/
|
|
516
|
+
prefetchCacheSize?: number;
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* Maximum number of speculative prefetch requests (viewport/render strategy)
|
|
520
|
+
* the client runs concurrently. Hover prefetches bypass this queue and fire
|
|
521
|
+
* immediately; this caps only the background, idle-gated queue so prefetches
|
|
522
|
+
* never saturate the browser's connection pool.
|
|
523
|
+
*
|
|
524
|
+
* Values below 1 (or non-finite) fall back to the default.
|
|
525
|
+
*
|
|
526
|
+
* @default 2
|
|
527
|
+
*/
|
|
528
|
+
prefetchConcurrency?: number;
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* Prefix for the rango state cookie name. The resolved name is
|
|
532
|
+
* `{prefix}_{routerId}`; the prefix is sanitized to cookie-name-safe
|
|
533
|
+
* characters (`[A-Za-z0-9-]`) and an empty result falls back to the default.
|
|
534
|
+
*
|
|
535
|
+
* The rango state cookie keys the client's prefetch / HTTP caches. Overriding
|
|
536
|
+
* the prefix lets you align it with cookie-naming policies or consent-manager
|
|
537
|
+
* classification lists, or avoid colliding with an existing `rango-state`
|
|
538
|
+
* cookie. It is not a full-name override: the `_{routerId}` suffix is what
|
|
539
|
+
* keeps sibling apps on one origin from clobbering each other's state.
|
|
540
|
+
*
|
|
541
|
+
* @default "rango-state"
|
|
542
|
+
*/
|
|
543
|
+
stateCookiePrefix?: string;
|
|
544
|
+
|
|
499
545
|
/**
|
|
500
546
|
* Enable connection warmup to keep TCP+TLS alive after idle periods.
|
|
501
547
|
*
|
|
@@ -507,6 +553,29 @@ export interface RangoOptions<TEnv = any> {
|
|
|
507
553
|
*/
|
|
508
554
|
warmup?: boolean;
|
|
509
555
|
|
|
556
|
+
/**
|
|
557
|
+
* Wrap the hydrated client tree in `React.StrictMode`.
|
|
558
|
+
*
|
|
559
|
+
* The Rango browser entry hydrates the app inside `<React.StrictMode>` by
|
|
560
|
+
* default. StrictMode double-invokes render and (in development) mounts,
|
|
561
|
+
* unmounts, then remounts every effect to surface impure renders and missing
|
|
562
|
+
* effect cleanup. Production builds treat StrictMode as a no-op, so this flag
|
|
563
|
+
* only changes development behavior in a normal app.
|
|
564
|
+
*
|
|
565
|
+
* Set to `false` to hydrate without the StrictMode wrapper. The main reason to
|
|
566
|
+
* opt out is to isolate StrictMode's intentional double-render/double-effect
|
|
567
|
+
* from genuine re-renders when measuring client-hook stability — with
|
|
568
|
+
* StrictMode off, render counts are exact in development too.
|
|
569
|
+
*
|
|
570
|
+
* The value is resolved server-side at router creation and shipped to the
|
|
571
|
+
* client in the initial payload metadata; the browser entry reads it once at
|
|
572
|
+
* hydration. Changing it does not affect the SSR HTML (StrictMode emits no
|
|
573
|
+
* DOM), so toggling it never causes a hydration mismatch.
|
|
574
|
+
*
|
|
575
|
+
* @default true
|
|
576
|
+
*/
|
|
577
|
+
strictMode?: boolean;
|
|
578
|
+
|
|
510
579
|
/**
|
|
511
580
|
* Shorthand timeout (ms) applied to both action execution and render start.
|
|
512
581
|
* Does NOT apply to streamIdleMs.
|
|
@@ -559,11 +628,14 @@ export interface RangoOptions<TEnv = any> {
|
|
|
559
628
|
onTimeout?: OnTimeoutCallback<TEnv>;
|
|
560
629
|
|
|
561
630
|
/**
|
|
562
|
-
* Telemetry sink for structured lifecycle
|
|
631
|
+
* Telemetry sink for structured, discrete lifecycle EVENTS: request
|
|
632
|
+
* start/end/error, loader start/end/error, handler errors, cache decisions,
|
|
633
|
+
* revalidation decisions, timeouts, origin rejections.
|
|
563
634
|
*
|
|
564
|
-
*
|
|
565
|
-
* loader
|
|
566
|
-
*
|
|
635
|
+
* This is the EVENT surface. Phase-duration SPANS (request/middleware/action/
|
|
636
|
+
* handler/loader/render/ssr timing wired into a tracing backend) come from the
|
|
637
|
+
* separate `tracing` option below — a sink does not emit them, because async-context nesting
|
|
638
|
+
* cannot be faithfully reconstructed from after-the-fact start/end events.
|
|
567
639
|
*
|
|
568
640
|
* No-op when not configured (zero overhead).
|
|
569
641
|
*
|
|
@@ -576,6 +648,18 @@ export interface RangoOptions<TEnv = any> {
|
|
|
576
648
|
* });
|
|
577
649
|
* ```
|
|
578
650
|
*
|
|
651
|
+
* @example OpenTelemetry — pair the event sink with the tracing slot
|
|
652
|
+
* ```typescript
|
|
653
|
+
* import { createOTelTracing, createOTelSink } from "@rangojs/router";
|
|
654
|
+
* import { trace } from "@opentelemetry/api";
|
|
655
|
+
*
|
|
656
|
+
* const tracer = trace.getTracer("my-app");
|
|
657
|
+
* const router = createRouter({
|
|
658
|
+
* tracing: createOTelTracing(tracer), // phase spans
|
|
659
|
+
* telemetry: createOTelSink(tracer), // discrete-fact events
|
|
660
|
+
* });
|
|
661
|
+
* ```
|
|
662
|
+
*
|
|
579
663
|
* @example Custom sink
|
|
580
664
|
* ```typescript
|
|
581
665
|
* const router = createRouter({
|
|
@@ -589,6 +673,44 @@ export interface RangoOptions<TEnv = any> {
|
|
|
589
673
|
*/
|
|
590
674
|
telemetry?: TelemetrySink;
|
|
591
675
|
|
|
676
|
+
/**
|
|
677
|
+
* Span tracing for the router's performance phases (request, middleware, action,
|
|
678
|
+
* loaders, render, ssr). Connects the same phases shown in the
|
|
679
|
+
* `debugPerformance` timeline to the host platform's tracing system. This is
|
|
680
|
+
* the SPAN surface (the `telemetry` option above is the event surface).
|
|
681
|
+
*
|
|
682
|
+
* Two factories produce a config, both for this slot:
|
|
683
|
+
* - `createOTelTracing(tracer)` from `@rangojs/router` — any platform with an
|
|
684
|
+
* OpenTelemetry SDK (including Node). Bridges the phases onto
|
|
685
|
+
* `tracer.startActiveSpan`.
|
|
686
|
+
* - `createCloudflareTracing()` from `@rangojs/router/cloudflare` — Cloudflare
|
|
687
|
+
* Workers native custom spans, alongside the automatic KV/D1/fetch spans.
|
|
688
|
+
*
|
|
689
|
+
* When tracing is unset — or off-platform (no OTel SDK / no Cloudflare tracing
|
|
690
|
+
* destination) — every span call falls through to the work directly, so the
|
|
691
|
+
* request behaves exactly as if tracing were off.
|
|
692
|
+
*
|
|
693
|
+
* @example OpenTelemetry
|
|
694
|
+
* ```typescript
|
|
695
|
+
* import { createOTelTracing } from "@rangojs/router";
|
|
696
|
+
* import { trace } from "@opentelemetry/api";
|
|
697
|
+
*
|
|
698
|
+
* const router = createRouter({
|
|
699
|
+
* tracing: createOTelTracing(trace.getTracer("my-app")),
|
|
700
|
+
* });
|
|
701
|
+
* ```
|
|
702
|
+
*
|
|
703
|
+
* @example Cloudflare
|
|
704
|
+
* ```typescript
|
|
705
|
+
* import { createCloudflareTracing } from "@rangojs/router/cloudflare";
|
|
706
|
+
*
|
|
707
|
+
* const router = createRouter({
|
|
708
|
+
* tracing: createCloudflareTracing({ spans: { ssr: false } }),
|
|
709
|
+
* });
|
|
710
|
+
* ```
|
|
711
|
+
*/
|
|
712
|
+
tracing?: RouterTracingConfig;
|
|
713
|
+
|
|
592
714
|
/**
|
|
593
715
|
* SSR configuration options.
|
|
594
716
|
*
|
|
@@ -19,67 +19,29 @@ import type {
|
|
|
19
19
|
} from "../../types";
|
|
20
20
|
import type { SegmentResolutionDeps } from "../types.js";
|
|
21
21
|
import { resolveLoaderData } from "./loader-cache.js";
|
|
22
|
-
import {
|
|
23
|
-
|
|
22
|
+
import {
|
|
23
|
+
isShellCaptureActive,
|
|
24
|
+
entryLoadingMasksLoaders,
|
|
25
|
+
} from "./loader-mask.js";
|
|
24
26
|
import {
|
|
25
27
|
handleHandlerResult,
|
|
26
28
|
tryStaticHandler,
|
|
27
29
|
tryStaticSlot,
|
|
28
30
|
resolveLayoutComponent,
|
|
29
31
|
resolveWithErrorBoundary,
|
|
32
|
+
warnOnStreamedResponse,
|
|
33
|
+
buildLoaderErrorContext,
|
|
30
34
|
} from "./helpers.js";
|
|
31
35
|
import { applyViewTransitionDefault } from "./view-transition-default.js";
|
|
32
36
|
import { getRouterContext } from "../router-context.js";
|
|
33
|
-
import {
|
|
37
|
+
import { observeStreamedHandler } from "./streamed-handler-telemetry.js";
|
|
38
|
+
import { observeHandler } from "../instrument.js";
|
|
34
39
|
import {
|
|
35
40
|
track,
|
|
36
41
|
RangoContext,
|
|
37
42
|
runInsideLoaderScope,
|
|
38
43
|
} from "../../server/context.js";
|
|
39
44
|
|
|
40
|
-
// ---------------------------------------------------------------------------
|
|
41
|
-
// Streamed handler telemetry
|
|
42
|
-
// ---------------------------------------------------------------------------
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Attach a fire-and-forget rejection observer to a streamed handler promise.
|
|
46
|
-
* React catches the actual error via its error boundary; this only emits
|
|
47
|
-
* the handler.error telemetry event.
|
|
48
|
-
*/
|
|
49
|
-
function observeStreamedHandler(
|
|
50
|
-
promise: Promise<ReactNode>,
|
|
51
|
-
segmentId: string,
|
|
52
|
-
segmentType: string,
|
|
53
|
-
pathname?: string,
|
|
54
|
-
routeKey?: string,
|
|
55
|
-
params?: Record<string, string>,
|
|
56
|
-
): void {
|
|
57
|
-
let routerCtx;
|
|
58
|
-
try {
|
|
59
|
-
routerCtx = getRouterContext();
|
|
60
|
-
} catch {
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
if (!routerCtx?.telemetry) return;
|
|
64
|
-
const sink = resolveSink(routerCtx.telemetry);
|
|
65
|
-
const reqId = routerCtx.requestId;
|
|
66
|
-
promise.catch((err: unknown) => {
|
|
67
|
-
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
68
|
-
safeEmit(sink, {
|
|
69
|
-
type: "handler.error",
|
|
70
|
-
timestamp: performance.now(),
|
|
71
|
-
requestId: reqId,
|
|
72
|
-
segmentId,
|
|
73
|
-
segmentType,
|
|
74
|
-
error: errorObj,
|
|
75
|
-
handledByBoundary: true,
|
|
76
|
-
pathname,
|
|
77
|
-
routeKey,
|
|
78
|
-
params,
|
|
79
|
-
});
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
|
|
83
45
|
// ---------------------------------------------------------------------------
|
|
84
46
|
// Fresh path (full match, no revalidation)
|
|
85
47
|
// ---------------------------------------------------------------------------
|
|
@@ -101,9 +63,33 @@ export async function resolveLoaders<TEnv>(
|
|
|
101
63
|
const shortCode = shortCodeOverride ?? entry.shortCode;
|
|
102
64
|
const hasLoading = "loading" in entry && entry.loading !== undefined;
|
|
103
65
|
const loadingDisabled = hasLoading && entry.loading === false;
|
|
104
|
-
const ms = _getRequestContext()?._metricsStore;
|
|
105
66
|
|
|
106
|
-
|
|
67
|
+
// Emit the streaming (non-awaiting) loader shape when loading is enabled OR
|
|
68
|
+
// during a PPR shell capture. In capture, LIVE-lane loaders are masked with
|
|
69
|
+
// never-resolving promises (loader-mask.ts); the loading-disabled branch below
|
|
70
|
+
// AWAITS the loader promises, which would hang the capture render's match()
|
|
71
|
+
// forever on those masked promises. Forcing the streaming shape lets match()
|
|
72
|
+
// complete so the prerender can postpone the loader subtrees as holes. The
|
|
73
|
+
// `!loadingDisabled` short-circuit keeps the ALS check off the hot path (only
|
|
74
|
+
// loading-disabled entries consult it), so normal requests are unchanged.
|
|
75
|
+
const emitStreaming = !loadingDisabled || isShellCaptureActive();
|
|
76
|
+
|
|
77
|
+
// PPR lane decision for this entry's loaders (loader-container-bake): an
|
|
78
|
+
// entry WITHOUT renderable loading() puts its loaders on the BAKE lane —
|
|
79
|
+
// executed at capture (container bakes, nested pending promises hole at the
|
|
80
|
+
// consumer's Suspense) and overlay-pinned from the shell snapshot on a HIT.
|
|
81
|
+
// Renderable loading() keeps the LIVE lane (masked at capture, always
|
|
82
|
+
// fresh). Computed per entry; resolveLoaderData applies the policy.
|
|
83
|
+
const bakeLane = !entryLoadingMasksLoaders(entry.loading);
|
|
84
|
+
|
|
85
|
+
// Error context for wrapLoaderPromise: without it, a throwing DSL loader never
|
|
86
|
+
// fires createRouter({ onError }) (phase "loader") nor emits the loader.error
|
|
87
|
+
// telemetry event — wrapLoaderPromise only builds the onError/telemetry path
|
|
88
|
+
// when errorContext is supplied. Built from ctx so the live render path reports
|
|
89
|
+
// loader failures the same way handlers/actions/routing/fetchable-loaders do.
|
|
90
|
+
const errorContext = buildLoaderErrorContext(ctx);
|
|
91
|
+
|
|
92
|
+
if (emitStreaming) {
|
|
107
93
|
// Streaming loaders: promises kick off now, settle during RSC serialization.
|
|
108
94
|
const segments = loaderEntries.map((loaderEntry, i) => {
|
|
109
95
|
const { loader } = loaderEntry;
|
|
@@ -118,11 +104,17 @@ export async function resolveLoaders<TEnv>(
|
|
|
118
104
|
loaderId: loader.$$id,
|
|
119
105
|
loaderData: deps.wrapLoaderPromise(
|
|
120
106
|
runInsideLoaderScope(() =>
|
|
121
|
-
resolveLoaderData(
|
|
107
|
+
resolveLoaderData(
|
|
108
|
+
loaderEntry,
|
|
109
|
+
ctx,
|
|
110
|
+
ctx.pathname,
|
|
111
|
+
bakeLane ? segmentId : null,
|
|
112
|
+
),
|
|
122
113
|
),
|
|
123
114
|
entry,
|
|
124
115
|
segmentId,
|
|
125
116
|
ctx.pathname,
|
|
117
|
+
errorContext,
|
|
126
118
|
),
|
|
127
119
|
belongsToRoute,
|
|
128
120
|
};
|
|
@@ -133,46 +125,51 @@ export async function resolveLoaders<TEnv>(
|
|
|
133
125
|
|
|
134
126
|
// Loading disabled: still start all loaders in parallel, but only emit
|
|
135
127
|
// settled promises so handlers don't stream loading placeholders.
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
128
|
+
//
|
|
129
|
+
// Wrap each loader promise with wrapLoaderPromise BEFORE awaiting. The wrapped
|
|
130
|
+
// promise resolves to a LoaderDataResult and never rejects, routing a failed
|
|
131
|
+
// loader to its own per-loader error boundary. Awaiting the RAW promises here
|
|
132
|
+
// instead would (1) propagate a rejection to the segment-level boundary,
|
|
133
|
+
// collapsing the whole entry and discarding successful sibling data, and
|
|
134
|
+
// (2) leave the other in-flight raw promises without a .catch, producing
|
|
135
|
+
// unhandled rejections. Mirrors the loading path and intercept-resolution.
|
|
136
|
+
const pendingLoaderData = loaderEntries.map((loaderEntry, i) => {
|
|
137
|
+
const { loader } = loaderEntry;
|
|
138
|
+
const segmentId = `${shortCode}D${i}.${loader.$$id}`;
|
|
139
|
+
const wrapped = deps.wrapLoaderPromise(
|
|
140
|
+
runInsideLoaderScope(() =>
|
|
141
|
+
resolveLoaderData(
|
|
142
|
+
loaderEntry,
|
|
143
|
+
ctx,
|
|
144
|
+
ctx.pathname,
|
|
145
|
+
bakeLane ? segmentId : null,
|
|
146
|
+
),
|
|
147
|
+
),
|
|
148
|
+
entry,
|
|
149
|
+
segmentId,
|
|
150
|
+
ctx.pathname,
|
|
151
|
+
errorContext,
|
|
140
152
|
);
|
|
141
|
-
return {
|
|
153
|
+
return { wrapped, segmentId };
|
|
142
154
|
});
|
|
143
|
-
await Promise.all(pendingLoaderData.map((p) => p.
|
|
155
|
+
await Promise.all(pendingLoaderData.map((p) => p.wrapped));
|
|
144
156
|
|
|
145
157
|
return loaderEntries.map((loaderEntry, i) => {
|
|
146
158
|
const { loader } = loaderEntry;
|
|
147
|
-
const segmentId = `${shortCode}D${i}.${loader.$$id}`;
|
|
148
159
|
const pending = pendingLoaderData[i]!;
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
const batchEnd = performance.now();
|
|
154
|
-
appendMetric(
|
|
155
|
-
ms,
|
|
156
|
-
`loader:${loader.$$id}`,
|
|
157
|
-
pending.start,
|
|
158
|
-
batchEnd - pending.start,
|
|
159
|
-
2,
|
|
160
|
-
);
|
|
161
|
-
}
|
|
160
|
+
// The "loader:<id>" perf metric is recorded by observePhase at the single
|
|
161
|
+
// loader-metering site (useLoader, reached via ctx.use during
|
|
162
|
+
// resolveLoaderData), with the real per-loader duration rather than a
|
|
163
|
+
// Promise.all batch ceiling.
|
|
162
164
|
return {
|
|
163
|
-
id: segmentId,
|
|
165
|
+
id: pending.segmentId,
|
|
164
166
|
namespace: entry.id,
|
|
165
167
|
type: "loader" as const,
|
|
166
168
|
index: i,
|
|
167
169
|
component: null,
|
|
168
170
|
params: ctx.params,
|
|
169
171
|
loaderId: loader.$$id,
|
|
170
|
-
loaderData:
|
|
171
|
-
pending.promise,
|
|
172
|
-
entry,
|
|
173
|
-
segmentId,
|
|
174
|
-
ctx.pathname,
|
|
175
|
-
),
|
|
172
|
+
loaderData: pending.wrapped,
|
|
176
173
|
belongsToRoute,
|
|
177
174
|
};
|
|
178
175
|
});
|
|
@@ -184,6 +181,15 @@ export async function resolveLoaders<TEnv>(
|
|
|
184
181
|
export interface ResolveSegmentOptions {
|
|
185
182
|
/** When true, skip resolveLoaders() calls (used for pre-rendering) */
|
|
186
183
|
skipLoaders?: boolean;
|
|
184
|
+
/**
|
|
185
|
+
* When true, a thrown render error is re-thrown instead of being converted
|
|
186
|
+
* into an error-boundary segment. Set only by the pre-render path so a
|
|
187
|
+
* build-time render failure (and a `throw new Skip()` inside a render fn)
|
|
188
|
+
* surfaces to the build instead of being silently baked into a frozen error
|
|
189
|
+
* page served as a 200 (issue #587). The live request path leaves this unset,
|
|
190
|
+
* so error boundaries keep catching at request time.
|
|
191
|
+
*/
|
|
192
|
+
throwOnError?: boolean;
|
|
187
193
|
}
|
|
188
194
|
|
|
189
195
|
/**
|
|
@@ -228,6 +234,7 @@ export async function resolveSegment<TEnv>(
|
|
|
228
234
|
transition: applyViewTransitionDefault(
|
|
229
235
|
entry.transition,
|
|
230
236
|
deps.viewTransitionDefault,
|
|
237
|
+
entry.shortCode,
|
|
231
238
|
),
|
|
232
239
|
params,
|
|
233
240
|
belongsToRoute: false,
|
|
@@ -295,8 +302,11 @@ export async function resolveSegment<TEnv>(
|
|
|
295
302
|
!context.build && entry.liveHandler ? entry.liveHandler : entry.handler;
|
|
296
303
|
const doneRouteHandler = track(`handler:${entry.id}`, 2);
|
|
297
304
|
if (entry.loading) {
|
|
298
|
-
const result = handleHandlerResult(
|
|
305
|
+
const result = handleHandlerResult(
|
|
306
|
+
observeHandler(entry.id, handler, context),
|
|
307
|
+
);
|
|
299
308
|
if (result instanceof Promise) {
|
|
309
|
+
warnOnStreamedResponse(result, entry.id);
|
|
300
310
|
result.finally(doneRouteHandler).catch(() => {});
|
|
301
311
|
const tracked = deps.trackHandler(result, {
|
|
302
312
|
segmentId: entry.shortCode,
|
|
@@ -316,7 +326,9 @@ export async function resolveSegment<TEnv>(
|
|
|
316
326
|
component = result;
|
|
317
327
|
}
|
|
318
328
|
} else {
|
|
319
|
-
component = handleHandlerResult(
|
|
329
|
+
component = handleHandlerResult(
|
|
330
|
+
await observeHandler(entry.id, handler, context),
|
|
331
|
+
);
|
|
320
332
|
doneRouteHandler();
|
|
321
333
|
}
|
|
322
334
|
}
|
|
@@ -366,6 +378,7 @@ export async function resolveSegment<TEnv>(
|
|
|
366
378
|
transition: applyViewTransitionDefault(
|
|
367
379
|
entry.transition,
|
|
368
380
|
deps.viewTransitionDefault,
|
|
381
|
+
entry.shortCode,
|
|
369
382
|
),
|
|
370
383
|
params,
|
|
371
384
|
belongsToRoute: true,
|
|
@@ -453,6 +466,7 @@ export async function resolveOrphanLayout<TEnv>(
|
|
|
453
466
|
transition: applyViewTransitionDefault(
|
|
454
467
|
orphan.transition,
|
|
455
468
|
deps.viewTransitionDefault,
|
|
469
|
+
orphan.shortCode,
|
|
456
470
|
),
|
|
457
471
|
...(orphan.mountPath ? { mountPath: orphan.mountPath } : {}),
|
|
458
472
|
});
|
|
@@ -541,7 +555,9 @@ export async function resolveParallelEntry<TEnv>(
|
|
|
541
555
|
parallelEntry.loading !== undefined && parallelEntry.loading !== false;
|
|
542
556
|
if (hasLoadingFallback) {
|
|
543
557
|
const result =
|
|
544
|
-
typeof handler === "function"
|
|
558
|
+
typeof handler === "function"
|
|
559
|
+
? observeHandler(`${parallelEntry.id}.${slot}`, handler, context)
|
|
560
|
+
: handler;
|
|
545
561
|
if (result instanceof Promise) {
|
|
546
562
|
result.finally(doneParallelHandler).catch(() => {});
|
|
547
563
|
const tracked = deps.trackHandler(result, {
|
|
@@ -563,7 +579,13 @@ export async function resolveParallelEntry<TEnv>(
|
|
|
563
579
|
}
|
|
564
580
|
} else {
|
|
565
581
|
component =
|
|
566
|
-
typeof handler === "function"
|
|
582
|
+
typeof handler === "function"
|
|
583
|
+
? await observeHandler(
|
|
584
|
+
`${parallelEntry.id}.${slot}`,
|
|
585
|
+
handler,
|
|
586
|
+
context,
|
|
587
|
+
)
|
|
588
|
+
: handler;
|
|
567
589
|
doneParallelHandler();
|
|
568
590
|
}
|
|
569
591
|
}
|
|
@@ -578,6 +600,7 @@ export async function resolveParallelEntry<TEnv>(
|
|
|
578
600
|
transition: applyViewTransitionDefault(
|
|
579
601
|
parallelEntry.transition,
|
|
580
602
|
deps.viewTransitionDefault,
|
|
603
|
+
`${parentShortCode}.${slot}`,
|
|
581
604
|
),
|
|
582
605
|
params,
|
|
583
606
|
slot,
|
|
@@ -667,6 +690,7 @@ export async function resolveAllSegments<TEnv>(
|
|
|
667
690
|
deps,
|
|
668
691
|
{ request: safeRequest, url: context.url, routeKey, telemetry },
|
|
669
692
|
context.pathname,
|
|
693
|
+
options?.throwOnError,
|
|
670
694
|
);
|
|
671
695
|
doneEntry();
|
|
672
696
|
// Deduplicate by segment ID. include() scopes can produce entries that
|
|
@@ -19,13 +19,48 @@ import {
|
|
|
19
19
|
import { getRequestContext } from "../../server/request-context.js";
|
|
20
20
|
import { DefaultErrorFallback } from "../../default-error-boundary.js";
|
|
21
21
|
import type { EntryData } from "../../server/context";
|
|
22
|
-
import type {
|
|
22
|
+
import type {
|
|
23
|
+
ResolvedSegment,
|
|
24
|
+
ErrorInfo,
|
|
25
|
+
HandlerContext,
|
|
26
|
+
InternalHandlerContext,
|
|
27
|
+
} from "../../types";
|
|
23
28
|
import type { SegmentResolutionDeps } from "../types.js";
|
|
24
29
|
import { debugLog } from "../logging.js";
|
|
25
30
|
import { tryStaticLookup } from "./static-store.js";
|
|
31
|
+
import { observeHandler } from "../instrument.js";
|
|
26
32
|
import type { TelemetrySink } from "../telemetry.js";
|
|
27
33
|
import { resolveSink, safeEmit, getRequestId } from "../telemetry.js";
|
|
28
34
|
|
|
35
|
+
/** The errorContext shape wrapLoaderPromise expects as its 5th argument. */
|
|
36
|
+
type LoaderErrorContext<TEnv> = NonNullable<
|
|
37
|
+
Parameters<SegmentResolutionDeps<TEnv>["wrapLoaderPromise"]>[4]
|
|
38
|
+
>;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Build the errorContext passed to wrapLoaderPromise so a throwing DSL loader
|
|
42
|
+
* fires createRouter({ onError }) (phase "loader") and emits the loader.error
|
|
43
|
+
* telemetry event. wrapLoaderPromise only wires the onError/telemetry path when
|
|
44
|
+
* this 5th argument is present; every real call site previously omitted it, so
|
|
45
|
+
* loaders were the one phase whose failures were silently dropped (handlers,
|
|
46
|
+
* actions, routing, rendering, and fetchable loaders all reported correctly).
|
|
47
|
+
*
|
|
48
|
+
* The fields come off the handler context, which already carries the request,
|
|
49
|
+
* url, params, env, and (on the internal shape) the matched route name.
|
|
50
|
+
*/
|
|
51
|
+
export function buildLoaderErrorContext<TEnv>(
|
|
52
|
+
ctx: HandlerContext<any, TEnv>,
|
|
53
|
+
): LoaderErrorContext<TEnv> {
|
|
54
|
+
const internal = ctx as InternalHandlerContext<any, TEnv>;
|
|
55
|
+
return {
|
|
56
|
+
request: ctx.request,
|
|
57
|
+
url: ctx.url,
|
|
58
|
+
routeKey: internal._routeName,
|
|
59
|
+
params: ctx.params as Record<string, string>,
|
|
60
|
+
env: ctx.env,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
29
64
|
// ---------------------------------------------------------------------------
|
|
30
65
|
// Handler result processing
|
|
31
66
|
// ---------------------------------------------------------------------------
|
|
@@ -52,6 +87,40 @@ export function handleHandlerResult(
|
|
|
52
87
|
return result;
|
|
53
88
|
}
|
|
54
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Dev-only: warn when a handler on a route that declares loading() resolves or
|
|
92
|
+
* rejects with a Response (e.g. redirect()).
|
|
93
|
+
*
|
|
94
|
+
* On a non-loading route a returned/thrown Response short-circuits to an HTTP
|
|
95
|
+
* redirect. But when the route declares loading(), the handler result is
|
|
96
|
+
* streamed (not awaited at the resolution boundary), so the Response surfaces
|
|
97
|
+
* only during RSC serialization and is rendered into the stream instead of
|
|
98
|
+
* becoming a 302/308 — a silent failure mode. Issue redirects from middleware,
|
|
99
|
+
* a loader, or a synchronous handler return instead. Compiled out in production.
|
|
100
|
+
*/
|
|
101
|
+
export function warnOnStreamedResponse(
|
|
102
|
+
result: Promise<unknown>,
|
|
103
|
+
entryId: string,
|
|
104
|
+
): void {
|
|
105
|
+
if (process.env.NODE_ENV === "production") return;
|
|
106
|
+
// A Response can surface either as a rejection (handleHandlerResult rethrows a
|
|
107
|
+
// resolved Response) or as a resolved value (the raw parallel-slot handler is
|
|
108
|
+
// not run through handleHandlerResult). Check both so every streamed path is
|
|
109
|
+
// covered. Each handler is an independent observer; it does not consume the
|
|
110
|
+
// rejection for the trackHandler/observeStreamedHandler chains.
|
|
111
|
+
const check = (value: unknown) => {
|
|
112
|
+
if (value instanceof Response) {
|
|
113
|
+
console.warn(
|
|
114
|
+
`[rango] Handler for "${entryId}" returned a Response (e.g. ` +
|
|
115
|
+
`redirect()), but it declares loading(): the Response is rendered ` +
|
|
116
|
+
`into the RSC stream, NOT sent as an HTTP redirect. Issue redirects ` +
|
|
117
|
+
`from middleware, a loader, or a synchronous handler return.`,
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
result.then(check, check);
|
|
122
|
+
}
|
|
123
|
+
|
|
55
124
|
// ---------------------------------------------------------------------------
|
|
56
125
|
// Static handler interception
|
|
57
126
|
// ---------------------------------------------------------------------------
|
|
@@ -96,11 +165,16 @@ export async function resolveLayoutComponent<TEnv>(
|
|
|
96
165
|
entry: EntryData,
|
|
97
166
|
context: HandlerContext<any, TEnv>,
|
|
98
167
|
): Promise<ReactNode> {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
168
|
+
// Static/prerender hit: no handler runs, so emit no rango.handler span.
|
|
169
|
+
const staticComponent = await tryStaticHandler(entry, entry.shortCode);
|
|
170
|
+
if (staticComponent !== undefined) return staticComponent;
|
|
171
|
+
const handler = entry.handler;
|
|
172
|
+
if (typeof handler !== "function") return handler as ReactNode;
|
|
173
|
+
// Wrap ONLY the handler call in the rango.handler span (the perf metric is owned
|
|
174
|
+
// by track("handler:<id>") at the call site). handleHandlerResult stays OUTSIDE
|
|
175
|
+
// the span so a handler that returns a Response (redirect control flow, which it
|
|
176
|
+
// rethrows) is not recorded as a span error — mirrors the route-handler sites.
|
|
177
|
+
return handleHandlerResult(await observeHandler(entry.id, handler, context));
|
|
104
178
|
}
|
|
105
179
|
|
|
106
180
|
// ---------------------------------------------------------------------------
|
|
@@ -250,11 +324,17 @@ export async function resolveWithErrorBoundary<TEnv, TResult>(
|
|
|
250
324
|
deps: SegmentResolutionDeps<TEnv>,
|
|
251
325
|
report?: ErrorReportContext,
|
|
252
326
|
pathname?: string,
|
|
327
|
+
throwOnError?: boolean,
|
|
253
328
|
): Promise<TResult> {
|
|
254
329
|
try {
|
|
255
330
|
return await resolveFn();
|
|
256
331
|
} catch (error) {
|
|
257
332
|
if (error instanceof Response) throw error;
|
|
333
|
+
// Pre-render surfaces render failures to the build instead of baking the
|
|
334
|
+
// error boundary as a frozen 200 (issue #587). A `throw new Skip()` in a
|
|
335
|
+
// render fn also propagates here so the build can skip that URL rather than
|
|
336
|
+
// bake its error page. The live request path leaves throwOnError unset.
|
|
337
|
+
if (throwOnError) throw error;
|
|
258
338
|
const segment = catchSegmentError(
|
|
259
339
|
error,
|
|
260
340
|
entry,
|