@rangojs/router 0.0.0-experimental.79 → 0.0.0-experimental.7c7e4327
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 +301 -797
- package/dist/bin/rango.js +603 -145
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +3750 -1160
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +96 -24
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +85 -6
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +228 -33
- package/skills/caching/SKILL.md +336 -19
- 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 +110 -4
- 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 +87 -56
- package/skills/fonts/SKILL.md +1 -1
- package/skills/handler-use/SKILL.md +12 -10
- package/skills/hooks/SKILL.md +73 -691
- 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 +129 -27
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +75 -19
- package/skills/layout/SKILL.md +40 -19
- package/skills/links/SKILL.md +247 -17
- package/skills/loader/SKILL.md +248 -10
- package/skills/middleware/SKILL.md +25 -13
- package/skills/migrate-nextjs/SKILL.md +205 -20
- package/skills/migrate-react-router/SKILL.md +59 -670
- 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 +29 -2
- package/skills/observability/SKILL.md +202 -0
- package/skills/parallel/SKILL.md +40 -10
- package/skills/ppr/SKILL.md +616 -0
- package/skills/prerender/SKILL.md +72 -60
- package/skills/rango/SKILL.md +318 -26
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +138 -49
- package/skills/route/SKILL.md +117 -9
- package/skills/router-setup/SKILL.md +44 -9
- package/skills/scripts/SKILL.md +179 -0
- package/skills/server-actions/SKILL.md +776 -0
- package/skills/shell-manifest/SKILL.md +185 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/tailwind/SKILL.md +28 -4
- package/skills/testing/SKILL.md +130 -0
- 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 -626
- 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 +74 -15
- package/skills/vercel/SKILL.md +128 -0
- package/skills/view-transitions/SKILL.md +337 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +116 -0
- package/src/__internal.ts +0 -65
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +39 -0
- package/src/browser/connection-warmup.ts +134 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +252 -158
- package/src/browser/history-state.ts +21 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/logging.ts +28 -0
- package/src/browser/merge-segment-loaders.ts +6 -4
- package/src/browser/navigation-bridge.ts +94 -25
- package/src/browser/navigation-client.ts +144 -79
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +161 -73
- package/src/browser/navigation-transaction.ts +9 -59
- package/src/browser/network-error-handler.ts +34 -7
- package/src/browser/partial-update.ts +183 -144
- package/src/browser/prefetch/cache.ts +242 -77
- package/src/browser/prefetch/fetch.ts +325 -69
- package/src/browser/prefetch/queue.ts +61 -12
- package/src/browser/rango-state.ts +158 -76
- package/src/browser/react/Link.tsx +58 -20
- package/src/browser/react/NavigationProvider.tsx +202 -120
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/filter-segment-order.ts +66 -7
- package/src/browser/react/index.ts +0 -48
- package/src/browser/react/location-state-shared.ts +178 -8
- package/src/browser/react/location-state.ts +39 -14
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +17 -14
- package/src/browser/react/use-href.tsx +8 -1
- package/src/browser/react/use-link-status.ts +33 -8
- package/src/browser/react/use-navigation.ts +32 -7
- package/src/browser/react/use-params.ts +20 -10
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +25 -3
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +11 -21
- package/src/browser/response-adapter.ts +99 -8
- package/src/browser/rsc-router.tsx +145 -28
- package/src/browser/scroll-restoration.ts +37 -22
- package/src/browser/segment-reconciler.ts +31 -21
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +236 -65
- package/src/browser/types.ts +102 -9
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +203 -154
- package/src/build/generate-route-types.ts +3 -1
- package/src/build/index.ts +11 -3
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +152 -21
- package/src/build/route-types/ast-route-extraction.ts +15 -8
- package/src/build/route-types/codegen.ts +16 -5
- package/src/build/route-types/include-resolution.ts +456 -62
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/per-module-writer.ts +22 -6
- package/src/build/route-types/router-processing.ts +128 -51
- package/src/build/route-types/scan-filter.ts +1 -1
- package/src/build/route-types/source-scan.ts +216 -0
- package/src/build/runtime-discovery.ts +13 -21
- 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 +421 -58
- package/src/cache/cache-scope.ts +187 -96
- 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 +2202 -372
- 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 +180 -99
- package/src/cache/vercel/index.ts +11 -0
- package/src/cache/vercel/vercel-cache-store.ts +1127 -0
- package/src/client.rsc.tsx +41 -21
- package/src/client.tsx +33 -61
- 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 +18 -6
- package/src/decode-loader-results.ts +52 -0
- package/src/defer.ts +185 -0
- package/src/deps/ssr.ts +0 -1
- package/src/encode-kv.ts +49 -0
- package/src/errors.ts +30 -4
- package/src/escape-script.ts +52 -0
- package/src/handle.ts +67 -37
- 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 +9 -2
- package/src/host/pattern-matcher.ts +23 -52
- package/src/host/router.ts +107 -99
- package/src/host/testing.ts +40 -27
- package/src/host/types.ts +37 -4
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +137 -22
- package/src/index.rsc.ts +97 -12
- package/src/index.ts +98 -14
- package/src/internal-debug.ts +11 -10
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +20 -13
- package/src/loader.ts +12 -11
- package/src/missing-id-error.ts +68 -0
- package/src/outlet-context.ts +1 -1
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +16 -16
- package/src/prerender/store.ts +32 -37
- package/src/prerender.ts +78 -10
- 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 +62 -0
- package/src/reverse.ts +65 -39
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +19 -77
- package/src/route-definition/dsl-helpers.ts +304 -309
- package/src/route-definition/helper-factories.ts +28 -140
- package/src/route-definition/helpers-types.ts +87 -59
- package/src/route-definition/index.ts +1 -2
- package/src/route-definition/redirect.ts +44 -11
- package/src/route-definition/resolve-handler-use.ts +12 -1
- package/src/route-definition/use-item-types.ts +29 -0
- package/src/route-map-builder.ts +41 -20
- package/src/route-types.ts +19 -46
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +73 -25
- package/src/router/error-handling.ts +45 -18
- package/src/router/find-match.ts +129 -30
- package/src/router/handler-context.ts +27 -42
- package/src/router/instrument.ts +355 -0
- package/src/router/intercept-resolution.ts +39 -20
- package/src/router/lazy-includes.ts +82 -59
- package/src/router/loader-resolution.ts +167 -72
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +74 -40
- package/src/router/match-api.ts +80 -55
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +211 -165
- package/src/router/match-middleware/background-revalidation.ts +40 -24
- package/src/router/match-middleware/cache-lookup.ts +159 -285
- package/src/router/match-middleware/cache-store.ts +64 -52
- 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 +69 -79
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-types.ts +7 -134
- package/src/router/middleware.ts +298 -172
- 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 +181 -150
- package/src/router/prefetch-cache-ttl.ts +51 -0
- package/src/router/prefetch-limits.ts +37 -0
- package/src/router/prerender-match.ts +112 -67
- package/src/router/preview-match.ts +6 -2
- package/src/router/request-classification.ts +50 -69
- package/src/router/revalidation.ts +123 -73
- package/src/router/route-snapshot.ts +14 -3
- package/src/router/router-context.ts +6 -29
- package/src/router/router-interfaces.ts +115 -36
- package/src/router/router-options.ts +166 -5
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +131 -86
- package/src/router/segment-resolution/helpers.ts +86 -6
- package/src/router/segment-resolution/loader-cache.ts +139 -39
- package/src/router/segment-resolution/loader-mask.ts +67 -0
- package/src/router/segment-resolution/loader-snapshot.ts +251 -0
- package/src/router/segment-resolution/revalidation.ts +272 -320
- 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 +56 -0
- 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 +75 -0
- package/src/router/telemetry-otel.ts +160 -200
- package/src/router/telemetry.ts +105 -20
- package/src/router/timeout.ts +0 -20
- package/src/router/tracing.ts +215 -0
- package/src/router/trie-matching.ts +171 -59
- package/src/router/types.ts +9 -63
- package/src/router/url-params.ts +57 -0
- package/src/router.ts +157 -71
- package/src/rsc/full-payload.ts +70 -0
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +291 -217
- package/src/rsc/helpers.ts +168 -46
- package/src/rsc/index.ts +2 -5
- 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 +39 -25
- package/src/rsc/progressive-enhancement.ts +124 -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 +99 -189
- package/src/rsc/rsc-rendering.ts +421 -76
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +282 -116
- package/src/rsc/shell-capture.ts +1158 -0
- package/src/rsc/shell-serve.ts +150 -0
- package/src/rsc/ssr-setup.ts +16 -0
- package/src/rsc/transition-gate.ts +89 -0
- package/src/rsc/types.ts +53 -5
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +35 -30
- package/src/segment-loader-promise.ts +49 -4
- package/src/segment-system.tsx +350 -149
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +208 -51
- package/src/server/cookie-parse.ts +32 -0
- package/src/server/cookie-store.ts +152 -5
- package/src/server/handle-store.ts +21 -38
- package/src/server/loader-registry.ts +33 -42
- package/src/server/request-context.ts +395 -176
- package/src/ssr/index.tsx +458 -178
- package/src/ssr/ssr-root.tsx +228 -0
- package/src/static-handler.ts +10 -13
- package/src/testing/cache-status.ts +162 -0
- package/src/testing/collect-handle.ts +46 -0
- package/src/testing/dispatch.ts +813 -0
- package/src/testing/dom.entry.ts +22 -0
- package/src/testing/e2e/fixture.ts +188 -0
- package/src/testing/e2e/index.ts +128 -0
- package/src/testing/e2e/matchers.ts +35 -0
- package/src/testing/e2e/page-helpers.ts +272 -0
- package/src/testing/e2e/parity.ts +387 -0
- package/src/testing/e2e/server.ts +195 -0
- package/src/testing/flight-matchers.ts +97 -0
- package/src/testing/flight-normalize.ts +11 -0
- package/src/testing/flight-runtime.d.ts +57 -0
- package/src/testing/flight-tree.ts +682 -0
- package/src/testing/flight.entry.ts +52 -0
- package/src/testing/flight.ts +257 -0
- package/src/testing/generated-routes.ts +199 -0
- package/src/testing/index.ts +105 -0
- package/src/testing/internal/context.ts +371 -0
- package/src/testing/internal/flight-client-globals.ts +30 -0
- package/src/testing/internal/seed-vars.ts +54 -0
- package/src/testing/render-handler.ts +357 -0
- package/src/testing/render-route.tsx +584 -0
- package/src/testing/run-loader.ts +385 -0
- package/src/testing/run-middleware.ts +205 -0
- package/src/testing/run-transition-when.ts +164 -0
- package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
- package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
- package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
- package/src/testing/vitest-stubs/version.ts +5 -0
- package/src/testing/vitest.ts +305 -0
- package/src/theme/ThemeProvider.tsx +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 +54 -41
- package/src/types/handler-context.ts +110 -62
- package/src/types/index.ts +3 -10
- package/src/types/loader-types.ts +11 -9
- package/src/types/request-scope.ts +112 -0
- package/src/types/route-config.ts +20 -52
- package/src/types/route-entry.ts +0 -6
- package/src/types/segments.ts +135 -14
- package/src/urls/include-helper.ts +19 -64
- package/src/urls/include-provider.ts +71 -0
- package/src/urls/index.ts +2 -11
- package/src/urls/path-helper-types.ts +63 -17
- package/src/urls/path-helper.ts +22 -106
- package/src/urls/pattern-types.ts +72 -19
- package/src/urls/response-types.ts +22 -29
- package/src/urls/type-extraction.ts +98 -154
- package/src/urls/urls-function.ts +1 -19
- package/src/use-loader.tsx +292 -107
- package/src/vercel/index.ts +11 -0
- package/src/vercel/tracing.ts +88 -0
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +8 -7
- package/src/vite/discovery/dev-prerender-cache.ts +117 -0
- package/src/vite/discovery/discover-routers.ts +127 -86
- package/src/vite/discovery/discovery-errors.ts +255 -0
- package/src/vite/discovery/gate-state.ts +171 -0
- package/src/vite/discovery/prerender-collection.ts +96 -68
- package/src/vite/discovery/route-types-writer.ts +40 -84
- package/src/vite/discovery/self-gen-tracking.ts +27 -1
- package/src/vite/discovery/state.ts +45 -1
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +4 -0
- package/src/vite/inject-client-debug.ts +88 -0
- package/src/vite/plugin-types.ts +210 -10
- package/src/vite/plugins/cjs-to-esm.ts +16 -19
- package/src/vite/plugins/client-ref-dedup.ts +16 -11
- package/src/vite/plugins/client-ref-hashing.ts +28 -15
- package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
- package/src/vite/plugins/expose-action-id.ts +48 -95
- package/src/vite/plugins/expose-id-utils.ts +88 -55
- package/src/vite/plugins/expose-ids/export-analysis.ts +101 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +11 -90
- package/src/vite/plugins/expose-ids/loader-transform.ts +14 -24
- package/src/vite/plugins/expose-ids/router-transform.ts +118 -29
- package/src/vite/plugins/expose-internal-ids.ts +505 -486
- package/src/vite/plugins/performance-tracks.ts +26 -25
- package/src/vite/plugins/refresh-cmd.ts +1 -1
- package/src/vite/plugins/use-cache-transform.ts +73 -83
- package/src/vite/plugins/vercel-output.ts +384 -0
- package/src/vite/plugins/version-injector.ts +40 -29
- package/src/vite/plugins/version-plugin.ts +37 -40
- package/src/vite/plugins/virtual-entries.ts +138 -27
- package/src/vite/rango.ts +236 -138
- package/src/vite/router-discovery.ts +927 -136
- package/src/vite/utils/ast-handler-extract.ts +26 -35
- package/src/vite/utils/banner.ts +1 -1
- package/src/vite/utils/bundle-analysis.ts +10 -15
- package/src/vite/utils/client-chunks.ts +184 -0
- package/src/vite/utils/directive-prologue.ts +40 -0
- package/src/vite/utils/forward-user-plugins.ts +171 -0
- package/src/vite/utils/manifest-utils.ts +4 -59
- package/src/vite/utils/package-resolution.ts +20 -52
- package/src/vite/utils/prerender-utils.ts +71 -43
- package/src/vite/utils/shared-utils.ts +142 -43
- package/src/browser/action-response-classifier.ts +0 -99
- package/src/browser/react/use-client-cache.ts +0 -58
- package/src/browser/shallow.ts +0 -40
- package/src/handles/index.ts +0 -7
- package/src/network-error-thrower.tsx +0 -23
- package/src/router/middleware-cookies.ts +0 -55
|
@@ -1,14 +1,18 @@
|
|
|
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
|
-
EntryData,
|
|
5
|
-
|
|
4
|
+
type EntryData,
|
|
5
|
+
RangoContext,
|
|
6
6
|
runWithPrefixes,
|
|
7
7
|
getIsolatedLazyParent,
|
|
8
8
|
} from "../server/context";
|
|
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,39 +91,76 @@ 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
|
{
|
|
136
165
|
manifest,
|
|
137
166
|
patterns,
|
|
@@ -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,12 +19,17 @@ import type {
|
|
|
19
19
|
ErrorBoundaryFallbackProps,
|
|
20
20
|
ErrorInfo,
|
|
21
21
|
} from "../types";
|
|
22
|
-
import type { LoaderRevalidationResult, ActionContext } from "./types";
|
|
23
22
|
import { isHandle, collectHandleData, type Handle } from "../handle.js";
|
|
23
|
+
import { withDefer } from "../defer.js";
|
|
24
24
|
import { buildHandleSnapshot } from "../server/handle-store.js";
|
|
25
25
|
import { getFetchableLoader } from "../server/fetchable-loader-store.js";
|
|
26
26
|
import { _getRequestContext } from "../server/request-context.js";
|
|
27
|
-
import {
|
|
27
|
+
import {
|
|
28
|
+
isInsideLoaderScope,
|
|
29
|
+
runInsideLoaderBodyScope,
|
|
30
|
+
isInsidePushCallbackScope,
|
|
31
|
+
runInsidePushCallbackScope,
|
|
32
|
+
} from "../server/context.js";
|
|
28
33
|
import { debugLog } from "./logging.js";
|
|
29
34
|
|
|
30
35
|
/**
|
|
@@ -66,7 +71,9 @@ export function wrapLoaderWithErrorHandling<T>(
|
|
|
66
71
|
) => ErrorInfo,
|
|
67
72
|
onError?: LoaderErrorCallback,
|
|
68
73
|
): Promise<LoaderDataResult<T>> {
|
|
69
|
-
// Extract
|
|
74
|
+
// Extract the trailing token from segmentId (format: "<shortCode>D<i>.<loaderId>").
|
|
75
|
+
// The token is the loader's $$id (hash#export in prod, pathfrag#export in dev),
|
|
76
|
+
// not a clean display name.
|
|
70
77
|
const loaderName = segmentId.split(".").pop() || "unknown";
|
|
71
78
|
|
|
72
79
|
return Promise.resolve(promise)
|
|
@@ -102,16 +109,40 @@ export function wrapLoaderWithErrorHandling<T>(
|
|
|
102
109
|
};
|
|
103
110
|
}
|
|
104
111
|
|
|
105
|
-
// 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.
|
|
106
120
|
let renderedFallback: ReactNode;
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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,
|
|
110
143
|
error: errorInfo,
|
|
144
|
+
fallback: null,
|
|
111
145
|
};
|
|
112
|
-
renderedFallback = fallback(props);
|
|
113
|
-
} else {
|
|
114
|
-
renderedFallback = fallback;
|
|
115
146
|
}
|
|
116
147
|
|
|
117
148
|
debugLog("loader", "loader error wrapped with boundary fallback", {
|
|
@@ -266,7 +297,10 @@ function createLoaderExecutor<TEnv>(
|
|
|
266
297
|
search: (ctx as any).search,
|
|
267
298
|
pathname: ctx.pathname,
|
|
268
299
|
url: ctx.url,
|
|
300
|
+
originalUrl: ctx.originalUrl,
|
|
269
301
|
env: ctx.env,
|
|
302
|
+
waitUntil: ctx.waitUntil.bind(ctx),
|
|
303
|
+
executionContext: ctx.executionContext,
|
|
270
304
|
get: ((keyOrVar: any) =>
|
|
271
305
|
contextGet(variables, keyOrVar)) as typeof ctx.get,
|
|
272
306
|
use: ((item: LoaderDefinition<any, any> | Handle<any, any>) => {
|
|
@@ -284,6 +318,12 @@ function createLoaderExecutor<TEnv>(
|
|
|
284
318
|
);
|
|
285
319
|
}
|
|
286
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.
|
|
287
327
|
const snapshot =
|
|
288
328
|
reqCtx._renderBarrierHandleSnapshot ??
|
|
289
329
|
buildHandleSnapshot(reqCtx._handleStore, segmentOrder);
|
|
@@ -305,15 +345,7 @@ function createLoaderExecutor<TEnv>(
|
|
|
305
345
|
);
|
|
306
346
|
}
|
|
307
347
|
|
|
308
|
-
// Guard: reject streaming trees
|
|
309
348
|
const reqCtx = reqCtxRef ?? _getRequestContext();
|
|
310
|
-
if (reqCtx?._treeHasStreaming) {
|
|
311
|
-
throw new Error(
|
|
312
|
-
`ctx.rendered() is not supported when the matched route tree uses loading(). ` +
|
|
313
|
-
`Streaming handlers may not have settled when rendered() resolves. ` +
|
|
314
|
-
`Remove loading() from the route tree or restructure to avoid rendered().`,
|
|
315
|
-
);
|
|
316
|
-
}
|
|
317
349
|
|
|
318
350
|
if (renderedPromise) return renderedPromise;
|
|
319
351
|
|
|
@@ -324,7 +356,10 @@ function createLoaderExecutor<TEnv>(
|
|
|
324
356
|
}
|
|
325
357
|
|
|
326
358
|
// Bidirectional deadlock check: if a handler already started
|
|
327
|
-
// 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.
|
|
328
363
|
if (reqCtx._handlerLoaderDeps?.has(currentLoaderId)) {
|
|
329
364
|
throw new Error(
|
|
330
365
|
`Deadlock: loader "${currentLoaderId}" called ctx.rendered() but a handler ` +
|
|
@@ -342,20 +377,70 @@ function createLoaderExecutor<TEnv>(
|
|
|
342
377
|
}
|
|
343
378
|
reqCtx._renderBarrierWaiters.add(currentLoaderId);
|
|
344
379
|
|
|
345
|
-
|
|
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
|
+
}
|
|
346
403
|
renderedResolved = true;
|
|
347
404
|
});
|
|
348
405
|
return renderedPromise;
|
|
349
406
|
},
|
|
350
407
|
};
|
|
351
408
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
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
|
+
//
|
|
414
|
+
// Run the loader body inside loader scope so request-scoped reads
|
|
415
|
+
// (cookies()/headers() and non-cacheable ctx.get) are exempt from the
|
|
416
|
+
// cache-purity guards: loaders always run fresh, so their reads never leak
|
|
417
|
+
// into a cached segment. DSL loaders are already wrapped by fresh.ts; this
|
|
418
|
+
// also covers handler-invoked loaders (ctx.use(Loader) from a handler),
|
|
419
|
+
// which otherwise execute in the caller's cache scope and would wrongly
|
|
420
|
+
// throw. rendered() gating uses the captured isDslLoader (above), so this
|
|
421
|
+
// does not grant rendered() to handler-invoked loaders. Uses a body-only
|
|
422
|
+
// scope, so isInsideLoaderScope() / barrier / deadlock gating is unchanged.
|
|
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
|
+
);
|
|
359
444
|
|
|
360
445
|
loaderPromises.set(loader.$$id, promise);
|
|
361
446
|
return promise;
|
|
@@ -387,12 +472,6 @@ export function setupLoaderAccess<TEnv>(
|
|
|
387
472
|
|
|
388
473
|
const useLoader = createLoaderExecutor(ctx, loaderPromises);
|
|
389
474
|
|
|
390
|
-
// Track whether we're inside a handle push callback. Loaders started
|
|
391
|
-
// from push callbacks (e.g. push(async () => ctx.use(Loader))) do NOT
|
|
392
|
-
// block segment resolution, so they must not be registered as handler
|
|
393
|
-
// dependencies for deadlock detection.
|
|
394
|
-
let insideHandlePush = false;
|
|
395
|
-
|
|
396
475
|
ctx.use = ((item: LoaderDefinition<any, any> | Handle<any, any>) => {
|
|
397
476
|
if (isHandle(item)) {
|
|
398
477
|
const handle = item;
|
|
@@ -407,35 +486,40 @@ export function setupLoaderAccess<TEnv>(
|
|
|
407
486
|
);
|
|
408
487
|
}
|
|
409
488
|
|
|
410
|
-
return (
|
|
411
|
-
dataOrFn: unknown | Promise<unknown> | (() => Promise<unknown>)
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
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
|
+
);
|
|
421
504
|
store.push(handle.$$id, segmentId, result);
|
|
422
|
-
|
|
423
|
-
insideHandlePush = false;
|
|
505
|
+
return;
|
|
424
506
|
}
|
|
425
|
-
return;
|
|
426
|
-
}
|
|
427
507
|
|
|
428
|
-
|
|
429
|
-
|
|
508
|
+
store.push(handle.$$id, segmentId, dataOrFn);
|
|
509
|
+
},
|
|
510
|
+
);
|
|
430
511
|
}
|
|
431
512
|
|
|
432
513
|
// Deadlock guard and handler-to-loader dependency tracking.
|
|
433
514
|
// Skip when inside a DSL loader scope (resolveLoaderData also calls
|
|
434
515
|
// ctx.use() but that's DSL-to-DSL, not handler-to-loader) or when
|
|
435
516
|
// inside a handle push callback (push callbacks don't block segment
|
|
436
|
-
// 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.
|
|
437
521
|
const loader = item as LoaderDefinition<any, any>;
|
|
438
|
-
if (!isInsideLoaderScope() && !
|
|
522
|
+
if (!isInsideLoaderScope() && !isInsidePushCallbackScope()) {
|
|
439
523
|
const reqCtx = reqCtxRef ?? _getRequestContext();
|
|
440
524
|
if (reqCtx) {
|
|
441
525
|
// Direction 1: handler awaits loader that already called rendered()
|
|
@@ -449,13 +533,18 @@ export function setupLoaderAccess<TEnv>(
|
|
|
449
533
|
`Move the data dependency to a loader-to-loader pattern instead.`,
|
|
450
534
|
);
|
|
451
535
|
}
|
|
452
|
-
// Direction 2: track dep so rendered() can detect the deadlock
|
|
453
|
-
//
|
|
454
|
-
//
|
|
455
|
-
//
|
|
456
|
-
//
|
|
457
|
-
//
|
|
458
|
-
|
|
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) {
|
|
459
548
|
if (!reqCtx._handlerLoaderDeps) reqCtx._handlerLoaderDeps = new Set();
|
|
460
549
|
reqCtx._handlerLoaderDeps.add(loader.$$id);
|
|
461
550
|
}
|
|
@@ -489,18 +578,21 @@ export function setupBuildUse<TEnv>(ctx: HandlerContext<any, TEnv>): void {
|
|
|
489
578
|
);
|
|
490
579
|
}
|
|
491
580
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
)
|
|
495
|
-
|
|
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;
|
|
496
587
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
588
|
+
const valueOrPromise =
|
|
589
|
+
typeof dataOrFn === "function"
|
|
590
|
+
? (dataOrFn as () => Promise<unknown>)()
|
|
591
|
+
: dataOrFn;
|
|
501
592
|
|
|
502
|
-
|
|
503
|
-
|
|
593
|
+
store.push(handle.$$id, segmentId, valueOrPromise);
|
|
594
|
+
},
|
|
595
|
+
);
|
|
504
596
|
}
|
|
505
597
|
|
|
506
598
|
// Loader case: not available during pre-rendering
|
|
@@ -528,8 +620,11 @@ export function setupLoaderAccessSilent<TEnv>(
|
|
|
528
620
|
|
|
529
621
|
ctx.use = ((item: LoaderDefinition<any, any> | Handle<any, any>) => {
|
|
530
622
|
if (isHandle(item)) {
|
|
531
|
-
// Silent mode - return a no-op so handle data is not pushed during caching
|
|
532
|
-
|
|
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) => {});
|
|
533
628
|
}
|
|
534
629
|
|
|
535
630
|
return useLoader(item as LoaderDefinition<any, any>, null);
|
package/src/router/logging.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
2
|
import { INTERNAL_RANGO_DEBUG } from "../internal-debug.js";
|
|
3
3
|
|
|
4
|
-
// -- Revalidation trace types --
|
|
5
|
-
|
|
6
4
|
export interface RevalidationTraceEntry {
|
|
7
5
|
segmentId: string;
|
|
8
6
|
segmentType: string;
|
|
@@ -36,8 +34,6 @@ export interface RevalidationTrace {
|
|
|
36
34
|
entries: RevalidationTraceEntry[];
|
|
37
35
|
}
|
|
38
36
|
|
|
39
|
-
// -- Log context --
|
|
40
|
-
|
|
41
37
|
interface RouterLogContext {
|
|
42
38
|
requestId: string;
|
|
43
39
|
transactionId: string;
|
|
@@ -195,8 +191,6 @@ export function debugWarn(
|
|
|
195
191
|
console.warn(`${prefix} ${message}`);
|
|
196
192
|
}
|
|
197
193
|
|
|
198
|
-
// -- Revalidation trace helpers --
|
|
199
|
-
|
|
200
194
|
export function isTraceActive(): boolean {
|
|
201
195
|
if (!INTERNAL_RANGO_DEBUG) return false;
|
|
202
196
|
const ctx = routerLogContext.getStore();
|