@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
|
@@ -21,7 +21,11 @@ import { getRequestContext } from "../server/request-context.js";
|
|
|
21
21
|
import { executeInterceptMiddleware } from "./middleware.js";
|
|
22
22
|
import { createReverseFunction } from "./handler-context.js";
|
|
23
23
|
import { getGlobalRouteMap } from "../route-map-builder.js";
|
|
24
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
handleHandlerResult,
|
|
26
|
+
warnOnStreamedResponse,
|
|
27
|
+
buildLoaderErrorContext,
|
|
28
|
+
} from "./segment-resolution.js";
|
|
25
29
|
import type { SegmentResolutionDeps } from "./types.js";
|
|
26
30
|
import { debugLog } from "./logging.js";
|
|
27
31
|
import { runInsideLoaderScope } from "../server/context.js";
|
|
@@ -109,10 +113,25 @@ export async function resolveInterceptEntry<TEnv>(
|
|
|
109
113
|
};
|
|
110
114
|
stale?: boolean;
|
|
111
115
|
},
|
|
116
|
+
options?: {
|
|
117
|
+
/**
|
|
118
|
+
* Skip the intercept's middleware execution. Set ONLY by the post-response
|
|
119
|
+
* background re-render paths (proactive caching, stale background
|
|
120
|
+
* revalidation), whose sole purpose is to re-render the segment tree to
|
|
121
|
+
* populate the cache. The foreground request already ran the intercept
|
|
122
|
+
* middleware before the response was sent — it validated auth, set cookies,
|
|
123
|
+
* and wrote context vars into the request context's shared `_variables`,
|
|
124
|
+
* which the background render reuses. Re-running middleware here would fire
|
|
125
|
+
* its side effects a SECOND time, and a middleware that short-circuits with
|
|
126
|
+
* a Response would `throw` and silently abort the cache write. Never set on
|
|
127
|
+
* the foreground path.
|
|
128
|
+
*/
|
|
129
|
+
skipMiddleware?: boolean;
|
|
130
|
+
},
|
|
112
131
|
): Promise<ResolvedSegment[]> {
|
|
113
132
|
const segments: ResolvedSegment[] = [];
|
|
114
133
|
|
|
115
|
-
if (interceptEntry.middleware.length > 0) {
|
|
134
|
+
if (!options?.skipMiddleware && interceptEntry.middleware.length > 0) {
|
|
116
135
|
const requestCtx = getRequestContext();
|
|
117
136
|
if (!requestCtx?.res) {
|
|
118
137
|
throw new Error(
|
|
@@ -202,6 +221,9 @@ export async function resolveInterceptEntry<TEnv>(
|
|
|
202
221
|
parentEntry,
|
|
203
222
|
segmentId,
|
|
204
223
|
context.pathname,
|
|
224
|
+
// Report a throwing intercept loader to onError + loader.error telemetry,
|
|
225
|
+
// matching the fresh/revalidation paths.
|
|
226
|
+
buildLoaderErrorContext(context),
|
|
205
227
|
),
|
|
206
228
|
);
|
|
207
229
|
}
|
|
@@ -228,6 +250,12 @@ export async function resolveInterceptEntry<TEnv>(
|
|
|
228
250
|
let loaderDataPromise: Promise<any[]> | any[] | undefined;
|
|
229
251
|
|
|
230
252
|
if (interceptEntry.loading && loaderPromises.length > 0) {
|
|
253
|
+
if (handlerResult instanceof Promise) {
|
|
254
|
+
warnOnStreamedResponse(
|
|
255
|
+
handlerResult,
|
|
256
|
+
`intercept ${interceptEntry.slotName}`,
|
|
257
|
+
);
|
|
258
|
+
}
|
|
231
259
|
component =
|
|
232
260
|
handlerResult instanceof Promise
|
|
233
261
|
? handlerResult
|
|
@@ -369,6 +397,11 @@ export async function resolveInterceptLoadersOnly<TEnv>(
|
|
|
369
397
|
parentEntry,
|
|
370
398
|
segmentId,
|
|
371
399
|
context.pathname,
|
|
400
|
+
// Report a throwing intercept loader to onError + loader.error telemetry,
|
|
401
|
+
// matching the fresh/revalidation paths. resolveInterceptLoadersOnly is
|
|
402
|
+
// only called on the cache-hit partial-update path (handleCacheHitIntercept),
|
|
403
|
+
// so flag isPartial:true exactly like revalidation.ts's partial path.
|
|
404
|
+
{ ...buildLoaderErrorContext(context), isPartial: true },
|
|
372
405
|
),
|
|
373
406
|
);
|
|
374
407
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { registerRouteMap } from "../route-map-builder.js";
|
|
2
|
-
import { extractStaticPrefix } from "./pattern-matching.js";
|
|
2
|
+
import { extractStaticPrefix, joinPrefix } from "./pattern-matching.js";
|
|
3
3
|
import {
|
|
4
4
|
type EntryData,
|
|
5
5
|
RangoContext,
|
|
@@ -9,6 +9,10 @@ import {
|
|
|
9
9
|
import type { UrlPatterns } from "../urls.js";
|
|
10
10
|
import type { AllUseItems, IncludeItem } from "../route-types.js";
|
|
11
11
|
import type { ResolvedRouteMap, RouteEntry, TrailingSlashMode } from "../types";
|
|
12
|
+
import {
|
|
13
|
+
isIncludeProvider,
|
|
14
|
+
resolveIncludeModule,
|
|
15
|
+
} from "../urls/include-provider.js";
|
|
12
16
|
|
|
13
17
|
export interface LazyEvalDeps<TEnv = any> {
|
|
14
18
|
routesEntries: RouteEntry<TEnv>[];
|
|
@@ -18,9 +22,6 @@ export interface LazyEvalDeps<TEnv = any> {
|
|
|
18
22
|
routerId?: string;
|
|
19
23
|
}
|
|
20
24
|
|
|
21
|
-
// Detect lazy includes in handler result and create placeholder entries
|
|
22
|
-
// Lazy includes are IncludeItem with lazy: true and _lazyContext
|
|
23
|
-
// Moved to outer scope so it can be reused by evaluateLazyEntry for nested includes
|
|
24
25
|
export function findLazyIncludes<TEnv = any>(
|
|
25
26
|
items: AllUseItems[],
|
|
26
27
|
): Array<{
|
|
@@ -56,7 +57,6 @@ export function findLazyIncludes<TEnv = any>(
|
|
|
56
57
|
});
|
|
57
58
|
}
|
|
58
59
|
}
|
|
59
|
-
// Recursively check nested items (in layouts, etc.)
|
|
60
60
|
if ((item as any).uses && Array.isArray((item as any).uses)) {
|
|
61
61
|
lazyItems.push(...findLazyIncludes((item as any).uses));
|
|
62
62
|
}
|
|
@@ -73,19 +73,11 @@ export function findLazyIncludes<TEnv = any>(
|
|
|
73
73
|
export function evaluateLazyEntry<TEnv = any>(
|
|
74
74
|
entry: RouteEntry<TEnv>,
|
|
75
75
|
deps: LazyEvalDeps<TEnv>,
|
|
76
|
-
): void {
|
|
76
|
+
): void | Promise<void> {
|
|
77
77
|
if (!entry.lazy || entry.lazyEvaluated || !entry.lazyPatterns) {
|
|
78
78
|
return;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
// Check for pre-computed routes from build-time data.
|
|
82
|
-
// Only leaf nodes (no nested includes) are precomputed, so entries with
|
|
83
|
-
// nested lazy includes fall through to the handler below.
|
|
84
|
-
// When multiple entries share the same staticPrefix (e.g., several
|
|
85
|
-
// include("/", ...) calls), the precomputed data merges all their routes
|
|
86
|
-
// into one entry. Assigning that merged set to the first matching entry
|
|
87
|
-
// causes findMatch to pick the wrong handler for routes belonging to a
|
|
88
|
-
// different include. Skip the shortcut when the prefix is shared.
|
|
89
81
|
const currentPrecomputed = deps.getPrecomputedByPrefix();
|
|
90
82
|
if (currentPrecomputed) {
|
|
91
83
|
const routes = currentPrecomputed.get(entry.staticPrefix);
|
|
@@ -99,37 +91,74 @@ export function evaluateLazyEntry<TEnv = any>(
|
|
|
99
91
|
for (const [name, pattern] of Object.entries(routes)) {
|
|
100
92
|
deps.mergedRouteMap[name] = pattern;
|
|
101
93
|
}
|
|
102
|
-
|
|
94
|
+
// Register only this entry's routes (the delta): the full
|
|
95
|
+
// mergedRouteMap is seeded from the generated manifest at
|
|
96
|
+
// createRouter() time and already registered there — re-passing it
|
|
97
|
+
// made this request-path call O(total routes) (issue #666).
|
|
98
|
+
registerRouteMap(routes);
|
|
103
99
|
return;
|
|
104
100
|
}
|
|
105
101
|
}
|
|
106
102
|
}
|
|
107
103
|
|
|
108
|
-
//
|
|
109
|
-
//
|
|
110
|
-
//
|
|
111
|
-
|
|
104
|
+
// Async provider (`() => import("./routes")`): the route module is evaluated
|
|
105
|
+
// off the startup path, on the first request reaching this prefix. Concurrent
|
|
106
|
+
// first-hits share one in-flight promise so the import + expansion run exactly
|
|
107
|
+
// once. The eager path below stays fully synchronous (no Promise), so the
|
|
108
|
+
// per-entry match loop pays no microtask for normal includes.
|
|
109
|
+
const lazyPatterns = entry.lazyPatterns;
|
|
110
|
+
if (isIncludeProvider(lazyPatterns)) {
|
|
111
|
+
const inflight = (entry as { _lazyInflight?: Promise<void> })._lazyInflight;
|
|
112
|
+
if (inflight) return inflight;
|
|
113
|
+
const work = (async () => {
|
|
114
|
+
const resolved = resolveIncludeModule(
|
|
115
|
+
await lazyPatterns(),
|
|
116
|
+
entry.staticPrefix,
|
|
117
|
+
);
|
|
118
|
+
// Cache the resolved patterns: any later re-entry expands synchronously.
|
|
119
|
+
entry.lazyPatterns = resolved as unknown as UrlPatterns<TEnv>;
|
|
120
|
+
runExpansion(entry, deps, resolved as UrlPatterns<TEnv>);
|
|
121
|
+
})();
|
|
122
|
+
(entry as { _lazyInflight?: Promise<void> })._lazyInflight = work;
|
|
123
|
+
const clear = () => {
|
|
124
|
+
(entry as { _lazyInflight?: Promise<void> })._lazyInflight = undefined;
|
|
125
|
+
};
|
|
126
|
+
// On failure, clear the flag (lazyEvaluated stays false) so a later request
|
|
127
|
+
// can retry the import rather than wedging the route permanently.
|
|
128
|
+
work.then(clear, clear);
|
|
129
|
+
return work;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
runExpansion(entry, deps, lazyPatterns as UrlPatterns<TEnv>);
|
|
133
|
+
}
|
|
112
134
|
|
|
113
|
-
|
|
135
|
+
/**
|
|
136
|
+
* Synchronously expand a lazy entry's (already-resolved) patterns into routes
|
|
137
|
+
* and splice any nested lazy includes as new entries. Runs once per entry.
|
|
138
|
+
*/
|
|
139
|
+
function runExpansion<TEnv = any>(
|
|
140
|
+
entry: RouteEntry<TEnv>,
|
|
141
|
+
deps: LazyEvalDeps<TEnv>,
|
|
142
|
+
lazyPatterns: UrlPatterns<TEnv>,
|
|
143
|
+
): void {
|
|
144
|
+
// lazyEvaluated is set at the END, only after the handler ran and the routes
|
|
145
|
+
// (and any nested includes) were spliced. Setting it up-front would mark the
|
|
146
|
+
// entry done even if the handler throws mid-expansion: the async provider
|
|
147
|
+
// path clears _lazyInflight on rejection so a later request can retry, but a
|
|
148
|
+
// premature lazyEvaluated=true would make evaluateLazyEntry short-circuit
|
|
149
|
+
// (line ~77) forever, wedging the route at 404 until the isolate restarts.
|
|
114
150
|
const lazyContext = entry.lazyContext;
|
|
115
151
|
|
|
116
|
-
// Create a new context for evaluating the lazy patterns
|
|
117
152
|
const manifest = new Map<string, EntryData>();
|
|
118
153
|
const patterns = new Map<string, string>();
|
|
119
154
|
const patternsByPrefix = new Map<string, Map<string, string>>();
|
|
120
155
|
const trailingSlashMap = new Map<string, TrailingSlashMode>();
|
|
121
156
|
|
|
122
|
-
// Capture the handler result to detect nested lazy includes
|
|
123
157
|
let handlerResult: AllUseItems[] = [];
|
|
124
158
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
if (lazyContext?.counters) {
|
|
129
|
-
for (const [key, value] of Object.entries(lazyContext.counters)) {
|
|
130
|
-
lazyCounters[key] = value;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
159
|
+
const lazyCounters: Record<string, number> = lazyContext?.counters
|
|
160
|
+
? { ...lazyContext.counters }
|
|
161
|
+
: {};
|
|
133
162
|
|
|
134
163
|
RangoContext.run(
|
|
135
164
|
{
|
|
@@ -145,10 +174,8 @@ export function evaluateLazyEntry<TEnv = any>(
|
|
|
145
174
|
includeScope: lazyContext?.includeScope,
|
|
146
175
|
},
|
|
147
176
|
() => {
|
|
148
|
-
// Run the lazy patterns handler with the original context prefixes
|
|
149
|
-
// The prefix comes from the IncludeItem stored in lazyPatterns
|
|
150
177
|
const includePrefix = (entry as any)._lazyPrefix || "";
|
|
151
|
-
const fullPrefix = (lazyContext?.urlPrefix
|
|
178
|
+
const fullPrefix = joinPrefix(lazyContext?.urlPrefix, includePrefix);
|
|
152
179
|
|
|
153
180
|
if (fullPrefix || lazyContext?.namePrefix) {
|
|
154
181
|
runWithPrefixes(fullPrefix, lazyContext?.namePrefix, () => {
|
|
@@ -160,11 +187,9 @@ export function evaluateLazyEntry<TEnv = any>(
|
|
|
160
187
|
},
|
|
161
188
|
);
|
|
162
189
|
|
|
163
|
-
// Populate the entry's routes from the patterns
|
|
164
190
|
const routesObject: Record<string, string> = {};
|
|
165
191
|
for (const [name, pattern] of patterns.entries()) {
|
|
166
192
|
routesObject[name] = pattern;
|
|
167
|
-
// Also add to merged route map for reverse() support
|
|
168
193
|
const existingPattern = deps.mergedRouteMap[name];
|
|
169
194
|
if (existingPattern !== undefined && existingPattern !== pattern) {
|
|
170
195
|
console.warn(
|
|
@@ -175,46 +200,38 @@ export function evaluateLazyEntry<TEnv = any>(
|
|
|
175
200
|
deps.mergedRouteMap[name] = pattern;
|
|
176
201
|
}
|
|
177
202
|
|
|
178
|
-
// Update the entry in-place
|
|
179
203
|
entry.routes = routesObject as ResolvedRouteMap<any>;
|
|
180
204
|
|
|
181
|
-
// Note: Do NOT clear lazyPatterns/lazyContext here.
|
|
182
|
-
// loadManifest() needs them on every request to re-run the handler
|
|
183
|
-
// in the correct AsyncLocalStorage context (Store.manifest).
|
|
184
|
-
|
|
185
|
-
// Update trailing slash config if available
|
|
186
205
|
if (trailingSlashMap.size > 0) {
|
|
187
206
|
entry.trailingSlash = Object.fromEntries(trailingSlashMap);
|
|
188
207
|
}
|
|
189
208
|
|
|
190
|
-
// Detect nested lazy includes and register them as new entries
|
|
191
209
|
const nestedLazyIncludes = findLazyIncludes(handlerResult);
|
|
192
210
|
for (const lazyInclude of nestedLazyIncludes) {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
211
|
+
const fullPrefix = joinPrefix(
|
|
212
|
+
lazyInclude.context.urlPrefix,
|
|
213
|
+
lazyInclude.prefix,
|
|
214
|
+
);
|
|
197
215
|
|
|
198
216
|
const nestedEntry: RouteEntry<TEnv> & { _lazyPrefix?: string } = {
|
|
199
217
|
prefix: "",
|
|
200
218
|
staticPrefix: extractStaticPrefix(fullPrefix),
|
|
201
|
-
routes: {} as ResolvedRouteMap<any>,
|
|
219
|
+
routes: {} as ResolvedRouteMap<any>,
|
|
202
220
|
trailingSlash: entry.trailingSlash,
|
|
203
|
-
handler
|
|
221
|
+
// include entries don't invoke their own handler (real handlers come from
|
|
222
|
+
// the expanded routes); use the parent's placeholder. A provider has no
|
|
223
|
+
// `.handler` until resolved, so never read it here.
|
|
224
|
+
handler: isIncludeProvider(lazyInclude.patterns)
|
|
225
|
+
? entry.handler
|
|
226
|
+
: (lazyInclude.patterns as UrlPatterns<TEnv>).handler,
|
|
204
227
|
mountIndex: deps.nextMountIndex(),
|
|
205
228
|
routerId: deps.routerId,
|
|
206
|
-
// Lazy evaluation fields
|
|
207
229
|
lazy: true,
|
|
208
230
|
lazyPatterns: lazyInclude.patterns,
|
|
209
231
|
lazyContext: lazyInclude.context,
|
|
210
232
|
lazyEvaluated: false,
|
|
211
|
-
// Store the include prefix for evaluation
|
|
212
233
|
_lazyPrefix: lazyInclude.prefix,
|
|
213
234
|
};
|
|
214
|
-
// Insert nested lazy entry before any entry whose staticPrefix is a
|
|
215
|
-
// prefix of (but shorter than) this lazy entry's staticPrefix.
|
|
216
|
-
// This ensures more specific lazy includes are matched before
|
|
217
|
-
// less specific eager entries (e.g., "/href/nested" before "/href/:id").
|
|
218
235
|
const nestedPrefix = nestedEntry.staticPrefix;
|
|
219
236
|
let insertIndex = deps.routesEntries.length;
|
|
220
237
|
if (nestedPrefix) {
|
|
@@ -232,6 +249,12 @@ export function evaluateLazyEntry<TEnv = any>(
|
|
|
232
249
|
deps.routesEntries.splice(insertIndex, 0, nestedEntry);
|
|
233
250
|
}
|
|
234
251
|
|
|
235
|
-
//
|
|
236
|
-
registerRouteMap(
|
|
252
|
+
// Delta only — see the matching comment on the precomputed branch above and
|
|
253
|
+
// the WHY block on registerRouteMap (issue #666).
|
|
254
|
+
registerRouteMap(routesObject);
|
|
255
|
+
|
|
256
|
+
// Expansion fully succeeded (handler ran, routes + nested includes spliced) —
|
|
257
|
+
// mark done now so a mid-expansion throw above leaves lazyEvaluated=false and
|
|
258
|
+
// the entry retriable.
|
|
259
|
+
entry.lazyEvaluated = true;
|
|
237
260
|
}
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { ReactNode } from "react";
|
|
8
|
-
import { track } from "../server/context";
|
|
9
8
|
import type { EntryData } from "../server/context";
|
|
9
|
+
import { observePhase, PHASES } from "./instrument.js";
|
|
10
10
|
import { contextGet } from "../context-var.js";
|
|
11
11
|
import type {
|
|
12
12
|
ResolvedSegment,
|
|
@@ -19,14 +19,16 @@ import type {
|
|
|
19
19
|
ErrorBoundaryFallbackProps,
|
|
20
20
|
ErrorInfo,
|
|
21
21
|
} from "../types";
|
|
22
|
-
import type { LoaderRevalidationResult, ActionContext } from "./types";
|
|
23
22
|
import { isHandle, collectHandleData, type Handle } from "../handle.js";
|
|
23
|
+
import { withDefer } from "../defer.js";
|
|
24
24
|
import { buildHandleSnapshot } from "../server/handle-store.js";
|
|
25
25
|
import { getFetchableLoader } from "../server/fetchable-loader-store.js";
|
|
26
26
|
import { _getRequestContext } from "../server/request-context.js";
|
|
27
27
|
import {
|
|
28
28
|
isInsideLoaderScope,
|
|
29
29
|
runInsideLoaderBodyScope,
|
|
30
|
+
isInsidePushCallbackScope,
|
|
31
|
+
runInsidePushCallbackScope,
|
|
30
32
|
} from "../server/context.js";
|
|
31
33
|
import { debugLog } from "./logging.js";
|
|
32
34
|
|
|
@@ -69,7 +71,9 @@ export function wrapLoaderWithErrorHandling<T>(
|
|
|
69
71
|
) => ErrorInfo,
|
|
70
72
|
onError?: LoaderErrorCallback,
|
|
71
73
|
): Promise<LoaderDataResult<T>> {
|
|
72
|
-
// Extract
|
|
74
|
+
// Extract the trailing token from segmentId (format: "<shortCode>D<i>.<loaderId>").
|
|
75
|
+
// The token is the loader's $$id (hash#export in prod, pathfrag#export in dev),
|
|
76
|
+
// not a clean display name.
|
|
73
77
|
const loaderName = segmentId.split(".").pop() || "unknown";
|
|
74
78
|
|
|
75
79
|
return Promise.resolve(promise)
|
|
@@ -105,16 +109,40 @@ export function wrapLoaderWithErrorHandling<T>(
|
|
|
105
109
|
};
|
|
106
110
|
}
|
|
107
111
|
|
|
108
|
-
// Render fallback on server
|
|
112
|
+
// Render fallback on server. The user ErrorBoundaryHandler may throw
|
|
113
|
+
// synchronously; if it does we must NOT let that rejection escape — the
|
|
114
|
+
// wrapped LoaderDataResult promise is contracted to never reject (see
|
|
115
|
+
// segment-resolution/fresh.ts `await Promise.all(...wrapped)`), and a
|
|
116
|
+
// rejection here would collapse the whole entry and discard healthy
|
|
117
|
+
// sibling loader data. On a fallback-render throw, fall back to the
|
|
118
|
+
// no-boundary result (fallback: null) so the client throws the ORIGINAL
|
|
119
|
+
// error, and the wrapped promise still resolves to a LoaderDataResult.
|
|
109
120
|
let renderedFallback: ReactNode;
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
121
|
+
try {
|
|
122
|
+
if (typeof fallback === "function") {
|
|
123
|
+
// ErrorBoundaryHandler - call with error info
|
|
124
|
+
const props: ErrorBoundaryFallbackProps = {
|
|
125
|
+
error: errorInfo,
|
|
126
|
+
};
|
|
127
|
+
renderedFallback = fallback(props);
|
|
128
|
+
} else {
|
|
129
|
+
renderedFallback = fallback;
|
|
130
|
+
}
|
|
131
|
+
} catch (fallbackError) {
|
|
132
|
+
debugLog("loader", "error boundary fallback render threw", {
|
|
133
|
+
segmentId,
|
|
134
|
+
message: errorInfo.message,
|
|
135
|
+
fallbackError:
|
|
136
|
+
fallbackError instanceof Error
|
|
137
|
+
? fallbackError.message
|
|
138
|
+
: String(fallbackError),
|
|
139
|
+
});
|
|
140
|
+
return {
|
|
141
|
+
__loaderResult: true,
|
|
142
|
+
ok: false,
|
|
113
143
|
error: errorInfo,
|
|
144
|
+
fallback: null,
|
|
114
145
|
};
|
|
115
|
-
renderedFallback = fallback(props);
|
|
116
|
-
} else {
|
|
117
|
-
renderedFallback = fallback;
|
|
118
146
|
}
|
|
119
147
|
|
|
120
148
|
debugLog("loader", "loader error wrapped with boundary fallback", {
|
|
@@ -290,6 +318,12 @@ function createLoaderExecutor<TEnv>(
|
|
|
290
318
|
);
|
|
291
319
|
}
|
|
292
320
|
const segmentOrder = reqCtx._renderBarrierSegmentOrder ?? [];
|
|
321
|
+
// The complete snapshot is cached at barrier resolution for
|
|
322
|
+
// non-streaming trees, and by rendered() after handleStore.settled for
|
|
323
|
+
// streaming trees (where the eager snapshot would have been incomplete
|
|
324
|
+
// because loading() handlers were still in flight). Either way it is
|
|
325
|
+
// present by the time a loader reads a handle; the fresh build is only
|
|
326
|
+
// a defensive fallback.
|
|
293
327
|
const snapshot =
|
|
294
328
|
reqCtx._renderBarrierHandleSnapshot ??
|
|
295
329
|
buildHandleSnapshot(reqCtx._handleStore, segmentOrder);
|
|
@@ -311,15 +345,7 @@ function createLoaderExecutor<TEnv>(
|
|
|
311
345
|
);
|
|
312
346
|
}
|
|
313
347
|
|
|
314
|
-
// Guard: reject streaming trees
|
|
315
348
|
const reqCtx = reqCtxRef ?? _getRequestContext();
|
|
316
|
-
if (reqCtx?._treeHasStreaming) {
|
|
317
|
-
throw new Error(
|
|
318
|
-
`ctx.rendered() is not supported when the matched route tree uses loading(). ` +
|
|
319
|
-
`Streaming handlers may not have settled when rendered() resolves. ` +
|
|
320
|
-
`Remove loading() from the route tree or restructure to avoid rendered().`,
|
|
321
|
-
);
|
|
322
|
-
}
|
|
323
349
|
|
|
324
350
|
if (renderedPromise) return renderedPromise;
|
|
325
351
|
|
|
@@ -330,7 +356,10 @@ function createLoaderExecutor<TEnv>(
|
|
|
330
356
|
}
|
|
331
357
|
|
|
332
358
|
// Bidirectional deadlock check: if a handler already started
|
|
333
|
-
// awaiting this loader, calling rendered() would deadlock.
|
|
359
|
+
// awaiting this loader, calling rendered() would deadlock. This is the
|
|
360
|
+
// real cycle guard (it holds for both streaming and non-streaming): the
|
|
361
|
+
// handler blocks segment resolution, which blocks the barrier, which
|
|
362
|
+
// blocks this loader.
|
|
334
363
|
if (reqCtx._handlerLoaderDeps?.has(currentLoaderId)) {
|
|
335
364
|
throw new Error(
|
|
336
365
|
`Deadlock: loader "${currentLoaderId}" called ctx.rendered() but a handler ` +
|
|
@@ -348,14 +377,40 @@ function createLoaderExecutor<TEnv>(
|
|
|
348
377
|
}
|
|
349
378
|
reqCtx._renderBarrierWaiters.add(currentLoaderId);
|
|
350
379
|
|
|
351
|
-
|
|
380
|
+
// Streaming trees (loading()): the barrier resolves once the segment
|
|
381
|
+
// tree is resolved, but loading() handlers stream behind Suspense and
|
|
382
|
+
// their handle pushes are still in flight then. Their async execution
|
|
383
|
+
// IS tracked in the handle store (trackHandler -> store.track), so after
|
|
384
|
+
// the barrier we seal (no further handlers register once the tree is
|
|
385
|
+
// resolved) and wait for settled — every tracked handler, streaming
|
|
386
|
+
// included, has finished pushing. The loader's own segment streams in
|
|
387
|
+
// after, so this does not block the shell; the deadlock guard above
|
|
388
|
+
// keeps a handler from depending on this loader.
|
|
389
|
+
const streaming = reqCtx._treeHasStreaming === true;
|
|
390
|
+
renderedPromise = reqCtx._renderBarrier.then(async () => {
|
|
391
|
+
if (streaming) {
|
|
392
|
+
reqCtx._handleStore.seal();
|
|
393
|
+
await reqCtx._handleStore.settled;
|
|
394
|
+
// The eager snapshot was intentionally left unbuilt for streaming
|
|
395
|
+
// (it would have been incomplete). Build the complete one once, now
|
|
396
|
+
// that the store has settled, so every ctx.use(handle) reads the
|
|
397
|
+
// cached snapshot instead of rebuilding it per call.
|
|
398
|
+
reqCtx._renderBarrierHandleSnapshot ??= buildHandleSnapshot(
|
|
399
|
+
reqCtx._handleStore,
|
|
400
|
+
reqCtx._renderBarrierSegmentOrder ?? [],
|
|
401
|
+
);
|
|
402
|
+
}
|
|
352
403
|
renderedResolved = true;
|
|
353
404
|
});
|
|
354
405
|
return renderedPromise;
|
|
355
406
|
},
|
|
356
407
|
};
|
|
357
408
|
|
|
358
|
-
|
|
409
|
+
// Meter this loader once via observePhase (loader:<id> perf metric +
|
|
410
|
+
// rango.loader span); loaderFn runs inside the span callback so its KV/D1/
|
|
411
|
+
// fetch spans nest under it. This is one of the observePhase loader funnels —
|
|
412
|
+
// see instrument.ts for the single-metering contract.
|
|
413
|
+
//
|
|
359
414
|
// Run the loader body inside loader scope so request-scoped reads
|
|
360
415
|
// (cookies()/headers() and non-cacheable ctx.get) are exempt from the
|
|
361
416
|
// cache-purity guards: loaders always run fresh, so their reads never leak
|
|
@@ -365,14 +420,27 @@ function createLoaderExecutor<TEnv>(
|
|
|
365
420
|
// throw. rendered() gating uses the captured isDslLoader (above), so this
|
|
366
421
|
// does not grant rendered() to handler-invoked loaders. Uses a body-only
|
|
367
422
|
// scope, so isInsideLoaderScope() / barrier / deadlock gating is unchanged.
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
423
|
+
//
|
|
424
|
+
// `handlerInvoked` (!isDslLoader) rides on the scope for the CONSUMPTION-
|
|
425
|
+
// LANE RULE: a handler-consumed loader's value is a BAKED copy in every
|
|
426
|
+
// shared artifact (cache(), "use cache", the PPR shell), so its identity
|
|
427
|
+
// reads are exempt from the shell-capture guard — same allowance the
|
|
428
|
+
// cache-purity guards give it. DSL segment loaders keep their lane
|
|
429
|
+
// machinery (live = masked at capture, bake = guarded). A DSL loader's
|
|
430
|
+
// nested deps inherit isDslLoader=false only when the CHAIN started in a
|
|
431
|
+
// handler; a chain started by the segment funnel stays DSL (the loader
|
|
432
|
+
// scope ALS survives the body's awaits).
|
|
433
|
+
const promise = observePhase(PHASES.loader(loader.$$id), () =>
|
|
434
|
+
Promise.resolve(
|
|
435
|
+
runInsideLoaderBodyScope(
|
|
436
|
+
() => loaderFn(loaderCtx as LoaderContext<any, TEnv>),
|
|
437
|
+
loader.$$id,
|
|
438
|
+
!isDslLoader,
|
|
439
|
+
),
|
|
440
|
+
).finally(() => {
|
|
441
|
+
pendingLoaders.delete(loader.$$id);
|
|
442
|
+
}),
|
|
443
|
+
);
|
|
376
444
|
|
|
377
445
|
loaderPromises.set(loader.$$id, promise);
|
|
378
446
|
return promise;
|
|
@@ -404,12 +472,6 @@ export function setupLoaderAccess<TEnv>(
|
|
|
404
472
|
|
|
405
473
|
const useLoader = createLoaderExecutor(ctx, loaderPromises);
|
|
406
474
|
|
|
407
|
-
// Track whether we're inside a handle push callback. Loaders started
|
|
408
|
-
// from push callbacks (e.g. push(async () => ctx.use(Loader))) do NOT
|
|
409
|
-
// block segment resolution, so they must not be registered as handler
|
|
410
|
-
// dependencies for deadlock detection.
|
|
411
|
-
let insideHandlePush = false;
|
|
412
|
-
|
|
413
475
|
ctx.use = ((item: LoaderDefinition<any, any> | Handle<any, any>) => {
|
|
414
476
|
if (isHandle(item)) {
|
|
415
477
|
const handle = item;
|
|
@@ -424,35 +486,40 @@ export function setupLoaderAccess<TEnv>(
|
|
|
424
486
|
);
|
|
425
487
|
}
|
|
426
488
|
|
|
427
|
-
return (
|
|
428
|
-
dataOrFn: unknown | Promise<unknown> | (() => Promise<unknown>)
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
489
|
+
return withDefer(
|
|
490
|
+
(dataOrFn: unknown | Promise<unknown> | (() => Promise<unknown>)) => {
|
|
491
|
+
if (!store) return;
|
|
492
|
+
|
|
493
|
+
if (typeof dataOrFn === "function") {
|
|
494
|
+
// Run the callback inside the push-callback scope so ctx.use(loader)
|
|
495
|
+
// calls it makes — including after its own awaits, for an async
|
|
496
|
+
// callback — are not registered as handler-to-loader deps and do not
|
|
497
|
+
// trip the deadlock guard. A pushed promise value is not tracked by
|
|
498
|
+
// handleStore.settled and does not block segment resolution, so it
|
|
499
|
+
// cannot form a rendered() deadlock. The ALS scope (not a plain
|
|
500
|
+
// boolean) is what survives the callback's awaits.
|
|
501
|
+
const result = runInsidePushCallbackScope(() =>
|
|
502
|
+
(dataOrFn as () => Promise<unknown>)(),
|
|
503
|
+
);
|
|
438
504
|
store.push(handle.$$id, segmentId, result);
|
|
439
|
-
|
|
440
|
-
insideHandlePush = false;
|
|
505
|
+
return;
|
|
441
506
|
}
|
|
442
|
-
return;
|
|
443
|
-
}
|
|
444
507
|
|
|
445
|
-
|
|
446
|
-
|
|
508
|
+
store.push(handle.$$id, segmentId, dataOrFn);
|
|
509
|
+
},
|
|
510
|
+
);
|
|
447
511
|
}
|
|
448
512
|
|
|
449
513
|
// Deadlock guard and handler-to-loader dependency tracking.
|
|
450
514
|
// Skip when inside a DSL loader scope (resolveLoaderData also calls
|
|
451
515
|
// ctx.use() but that's DSL-to-DSL, not handler-to-loader) or when
|
|
452
516
|
// inside a handle push callback (push callbacks don't block segment
|
|
453
|
-
// resolution so they can't cause rendered() deadlocks).
|
|
517
|
+
// resolution so they can't cause rendered() deadlocks). The push-callback
|
|
518
|
+
// check is an ALS scope so it also exempts an ASYNC callback's continuation
|
|
519
|
+
// after its first await — relevant on streaming trees, where the guard
|
|
520
|
+
// state now stays live until handleStore.settled.
|
|
454
521
|
const loader = item as LoaderDefinition<any, any>;
|
|
455
|
-
if (!isInsideLoaderScope() && !
|
|
522
|
+
if (!isInsideLoaderScope() && !isInsidePushCallbackScope()) {
|
|
456
523
|
const reqCtx = reqCtxRef ?? _getRequestContext();
|
|
457
524
|
if (reqCtx) {
|
|
458
525
|
// Direction 1: handler awaits loader that already called rendered()
|
|
@@ -466,13 +533,18 @@ export function setupLoaderAccess<TEnv>(
|
|
|
466
533
|
`Move the data dependency to a loader-to-loader pattern instead.`,
|
|
467
534
|
);
|
|
468
535
|
}
|
|
469
|
-
// Direction 2: track dep so rendered() can detect the deadlock
|
|
470
|
-
//
|
|
471
|
-
//
|
|
472
|
-
//
|
|
473
|
-
//
|
|
474
|
-
//
|
|
475
|
-
|
|
536
|
+
// Direction 2: track dep so rendered() can detect the deadlock if the
|
|
537
|
+
// loader calls it later. Skip once the guard window is CLOSED — for a
|
|
538
|
+
// non-streaming tree that is when the barrier resolves (rendered()
|
|
539
|
+
// resolves immediately), and for a streaming tree it is when
|
|
540
|
+
// handleStore.settled completes (rendered() keeps waiting until then, so
|
|
541
|
+
// a loading() handler resuming after the barrier can still form a
|
|
542
|
+
// cycle). Using the explicit guard-closed flag rather than
|
|
543
|
+
// _renderBarrierSegmentOrder keeps tracking live across the streaming
|
|
544
|
+
// settle wait. (Handle push callbacks are already excluded above via
|
|
545
|
+
// isInsidePushCallbackScope(), so they cannot produce false positives
|
|
546
|
+
// here.)
|
|
547
|
+
if (!reqCtx._renderBarrierGuardClosed) {
|
|
476
548
|
if (!reqCtx._handlerLoaderDeps) reqCtx._handlerLoaderDeps = new Set();
|
|
477
549
|
reqCtx._handlerLoaderDeps.add(loader.$$id);
|
|
478
550
|
}
|
|
@@ -506,18 +578,21 @@ export function setupBuildUse<TEnv>(ctx: HandlerContext<any, TEnv>): void {
|
|
|
506
578
|
);
|
|
507
579
|
}
|
|
508
580
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
)
|
|
512
|
-
|
|
581
|
+
// Wrap with withDefer so ctx.use(Handle).defer(...) works on the build /
|
|
582
|
+
// prerender path, matching production setupLoaderAccess. Without it a
|
|
583
|
+
// prerender handler calling .defer() throws "defer is not a function".
|
|
584
|
+
return withDefer(
|
|
585
|
+
(dataOrFn: unknown | Promise<unknown> | (() => Promise<unknown>)) => {
|
|
586
|
+
if (!store) return;
|
|
513
587
|
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
588
|
+
const valueOrPromise =
|
|
589
|
+
typeof dataOrFn === "function"
|
|
590
|
+
? (dataOrFn as () => Promise<unknown>)()
|
|
591
|
+
: dataOrFn;
|
|
518
592
|
|
|
519
|
-
|
|
520
|
-
|
|
593
|
+
store.push(handle.$$id, segmentId, valueOrPromise);
|
|
594
|
+
},
|
|
595
|
+
);
|
|
521
596
|
}
|
|
522
597
|
|
|
523
598
|
// Loader case: not available during pre-rendering
|
|
@@ -545,8 +620,11 @@ export function setupLoaderAccessSilent<TEnv>(
|
|
|
545
620
|
|
|
546
621
|
ctx.use = ((item: LoaderDefinition<any, any> | Handle<any, any>) => {
|
|
547
622
|
if (isHandle(item)) {
|
|
548
|
-
// Silent mode - return a no-op so handle data is not pushed during caching
|
|
549
|
-
|
|
623
|
+
// Silent mode - return a no-op so handle data is not pushed during caching.
|
|
624
|
+
// Wrap with withDefer so ctx.use(Handle).defer(...) still resolves to a
|
|
625
|
+
// callable resolver (also a no-op here), matching production's push shape
|
|
626
|
+
// instead of throwing "defer is not a function".
|
|
627
|
+
return withDefer((_dataOrFn: unknown) => {});
|
|
550
628
|
}
|
|
551
629
|
|
|
552
630
|
return useLoader(item as LoaderDefinition<any, any>, null);
|