@rangojs/router 0.0.0-experimental.14 → 0.0.0-experimental.141
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +17 -0
- package/README.md +432 -7
- package/dist/bin/rango.js +2073 -213
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +7258 -2714
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +140 -67
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +329 -0
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +487 -0
- package/skills/caching/SKILL.md +357 -25
- 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 +246 -0
- package/skills/css/SKILL.md +76 -0
- package/skills/debug-manifest/SKILL.md +16 -10
- package/skills/document-cache/SKILL.md +87 -62
- package/skills/fonts/SKILL.md +6 -4
- package/skills/handler-use/SKILL.md +364 -0
- package/skills/hooks/SKILL.md +557 -79
- package/skills/host-router/SKILL.md +320 -0
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +207 -15
- package/skills/layout/SKILL.md +146 -6
- package/skills/links/SKILL.md +304 -25
- package/skills/loader/SKILL.md +616 -54
- package/skills/middleware/SKILL.md +217 -37
- package/skills/migrate-nextjs/SKILL.md +611 -0
- package/skills/migrate-react-router/SKILL.md +927 -0
- package/skills/mime-routes/SKILL.md +42 -11
- package/skills/observability/SKILL.md +194 -0
- package/skills/parallel/SKILL.md +284 -3
- package/skills/ppr/SKILL.md +293 -0
- package/skills/prerender/SKILL.md +437 -52
- package/skills/rango/SKILL.md +369 -22
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +263 -121
- package/skills/route/SKILL.md +350 -21
- package/skills/router-setup/SKILL.md +246 -33
- package/skills/scripts/SKILL.md +179 -0
- package/skills/server-actions/SKILL.md +775 -0
- package/skills/shell-manifest/SKILL.md +185 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/tailwind/SKILL.md +27 -3
- package/skills/testing/SKILL.md +126 -222
- package/skills/testing/bindings.md +103 -0
- package/skills/testing/cache-prerender.md +127 -0
- package/skills/testing/client-components.md +124 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +91 -0
- package/skills/testing/handles.md +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 +9 -8
- package/skills/typesafety/SKILL.md +532 -103
- package/skills/use-cache/SKILL.md +367 -0
- 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 +77 -44
- package/src/bin/rango.ts +312 -15
- package/src/browser/action-coordinator.ts +114 -0
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +39 -0
- package/src/browser/app-version.ts +14 -0
- package/src/browser/connection-warmup.ts +134 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +293 -202
- package/src/browser/history-state.ts +101 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/intercept-utils.ts +52 -0
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/link-interceptor.ts +24 -4
- package/src/browser/logging.ts +11 -0
- package/src/browser/merge-segment-loaders.ts +20 -12
- package/src/browser/navigation-bridge.ts +385 -576
- package/src/browser/navigation-client.ts +245 -75
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +184 -118
- package/src/browser/navigation-transaction.ts +247 -0
- package/src/browser/network-error-handler.ts +88 -0
- package/src/browser/partial-update.ts +412 -364
- package/src/browser/prefetch/cache.ts +359 -0
- package/src/browser/prefetch/fetch.ts +452 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +48 -0
- package/src/browser/prefetch/queue.ts +209 -0
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +194 -0
- package/src/browser/react/Link.tsx +275 -68
- package/src/browser/react/NavigationProvider.tsx +265 -109
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/context.ts +11 -0
- package/src/browser/react/filter-segment-order.ts +70 -0
- package/src/browser/react/index.ts +0 -48
- package/src/browser/react/location-state-shared.ts +272 -60
- package/src/browser/react/location-state.ts +90 -20
- package/src/browser/react/mount-context.ts +6 -1
- package/src/browser/react/nonce-context.ts +23 -0
- package/src/browser/react/shallow-equal.ts +27 -0
- package/src/browser/react/use-action.ts +35 -66
- package/src/browser/react/use-handle.ts +39 -126
- package/src/browser/react/use-href.tsx +8 -1
- package/src/browser/react/use-link-status.ts +39 -13
- package/src/browser/react/use-navigation.ts +53 -69
- package/src/browser/react/use-params.ts +75 -0
- package/src/browser/react/use-pathname.ts +47 -0
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +98 -0
- package/src/browser/react/use-search-params.ts +51 -0
- package/src/browser/react/use-segments.ts +72 -99
- package/src/browser/response-adapter.ts +164 -0
- package/src/browser/rsc-router.tsx +300 -72
- package/src/browser/scroll-restoration.ts +138 -50
- package/src/browser/segment-reconciler.ts +243 -0
- package/src/browser/segment-structure-assert.ts +17 -1
- package/src/browser/server-action-bridge.ts +668 -613
- package/src/browser/types.ts +223 -51
- package/src/browser/validate-redirect-origin.ts +56 -0
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +252 -161
- package/src/build/generate-route-types.ts +41 -1038
- package/src/build/index.ts +12 -7
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +225 -42
- package/src/build/route-types/ast-helpers.ts +25 -0
- package/src/build/route-types/ast-route-extraction.ts +105 -0
- package/src/build/route-types/codegen.ts +113 -0
- package/src/build/route-types/include-resolution.ts +812 -0
- package/src/build/route-types/param-extraction.ts +51 -0
- package/src/build/route-types/per-module-writer.ts +144 -0
- package/src/build/route-types/router-processing.ts +695 -0
- package/src/build/route-types/scan-filter.ts +85 -0
- package/src/build/route-types/source-scan.ts +216 -0
- package/src/build/runtime-discovery.ts +223 -0
- package/src/cache/background-task.ts +34 -0
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-key-utils.ts +60 -0
- package/src/cache/cache-policy.ts +199 -0
- package/src/cache/cache-runtime.ts +525 -0
- package/src/cache/cache-scope.ts +298 -332
- package/src/cache/cache-tag.ts +103 -0
- package/src/cache/cf/cf-base64.ts +33 -0
- package/src/cache/cf/cf-cache-constants.ts +127 -0
- package/src/cache/cf/cf-cache-store.ts +2508 -158
- 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 +17 -17
- package/src/cache/document-cache.ts +199 -92
- package/src/cache/handle-capture.ts +81 -0
- package/src/cache/handle-snapshot.ts +111 -0
- package/src/cache/index.ts +24 -35
- package/src/cache/memory-segment-store.ts +363 -30
- package/src/cache/profile-registry.ts +88 -0
- package/src/cache/read-through-swr.ts +178 -0
- package/src/cache/segment-codec.ts +248 -0
- package/src/cache/shell-snapshot.ts +368 -0
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/taint.ts +153 -0
- package/src/cache/types.ts +222 -211
- package/src/cache/vercel/index.ts +11 -0
- package/src/cache/vercel/vercel-cache-store.ts +1113 -0
- package/src/client.rsc.tsx +43 -21
- package/src/client.tsx +131 -347
- package/src/cloudflare/index.ts +11 -0
- package/src/cloudflare/tracing.ts +109 -0
- package/src/component-utils.ts +23 -4
- package/src/components/DefaultDocument.tsx +13 -3
- package/src/context-var.ts +168 -0
- package/src/debug.ts +19 -9
- 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 +106 -10
- package/src/escape-script.ts +52 -0
- package/src/handle.ts +110 -35
- package/src/handles/MetaTags.tsx +83 -59
- package/src/handles/Scripts.tsx +183 -0
- package/src/handles/breadcrumbs.ts +93 -0
- package/src/handles/deferred-resolution.ts +127 -0
- package/src/handles/is-thenable.ts +18 -0
- package/src/handles/meta.ts +44 -53
- package/src/handles/script.ts +244 -0
- package/src/host/cookie-handler.ts +20 -65
- package/src/host/errors.ts +21 -30
- package/src/host/index.ts +13 -9
- package/src/host/pattern-matcher.ts +50 -79
- package/src/host/router.ts +151 -121
- package/src/host/testing.ts +45 -32
- package/src/host/types.ts +52 -11
- package/src/host/utils.ts +2 -2
- package/src/href-client.ts +192 -57
- package/src/index.rsc.ts +173 -35
- package/src/index.ts +241 -73
- package/src/internal-debug.ts +9 -2
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +31 -99
- package/src/loader.ts +30 -12
- package/src/missing-id-error.ts +68 -0
- package/src/outlet-context.ts +1 -1
- package/src/outlet-provider.tsx +41 -0
- package/src/prerender/param-hash.ts +16 -14
- package/src/prerender/store.ts +121 -21
- package/src/prerender.ts +460 -26
- package/src/redirect-origin.ts +100 -0
- package/src/regex-escape.ts +8 -0
- package/src/render-error-thrower.tsx +20 -0
- package/src/response-utils.ts +62 -0
- package/src/reverse.ts +198 -128
- package/src/root-error-boundary.tsx +42 -48
- package/src/route-content-wrapper.tsx +22 -77
- package/src/route-definition/dsl-helpers.ts +1116 -0
- package/src/route-definition/helper-factories.ts +88 -0
- package/src/route-definition/helpers-types.ts +505 -0
- package/src/route-definition/index.ts +54 -0
- package/src/route-definition/redirect.ts +134 -0
- package/src/route-definition/resolve-handler-use.ts +160 -0
- package/src/route-definition/use-item-types.ts +29 -0
- package/src/route-definition.ts +1 -1481
- package/src/route-map-builder.ts +82 -144
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +71 -45
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +263 -0
- package/src/router/debug-manifest.ts +72 -0
- package/src/router/error-handling.ts +54 -27
- package/src/router/find-match.ts +245 -0
- package/src/router/handler-context.ts +377 -125
- package/src/router/instrument.ts +350 -0
- package/src/router/intercept-resolution.ts +59 -28
- package/src/router/lazy-includes.ts +254 -0
- package/src/router/loader-resolution.ts +421 -157
- package/src/router/logging.ts +106 -6
- package/src/router/manifest.ts +131 -57
- package/src/router/match-api.ts +167 -246
- package/src/router/match-context.ts +4 -24
- package/src/router/match-handlers.ts +440 -0
- package/src/router/match-middleware/background-revalidation.ts +117 -93
- package/src/router/match-middleware/cache-lookup.ts +297 -150
- package/src/router/match-middleware/cache-store.ts +123 -51
- package/src/router/match-middleware/intercept-resolution.ts +44 -43
- package/src/router/match-middleware/segment-resolution.ts +64 -22
- package/src/router/match-pipelines.ts +11 -87
- package/src/router/match-result.ts +121 -50
- package/src/router/metrics.ts +219 -28
- package/src/router/middleware-types.ts +93 -0
- package/src/router/middleware.ts +505 -441
- package/src/router/navigation-snapshot.ts +133 -0
- package/src/router/params-util.ts +23 -0
- package/src/router/parse-pattern.ts +115 -0
- package/src/router/pattern-matching.ts +311 -142
- package/src/router/prefetch-cache-ttl.ts +51 -0
- package/src/router/prefetch-limits.ts +37 -0
- package/src/router/prerender-match.ts +547 -0
- package/src/router/preview-match.ts +102 -0
- package/src/router/request-classification.ts +278 -0
- package/src/router/revalidation.ts +203 -62
- package/src/router/route-snapshot.ts +246 -0
- package/src/router/router-context.ts +45 -48
- package/src/router/router-interfaces.ts +554 -0
- package/src/router/router-options.ts +779 -0
- package/src/router/router-registry.ts +21 -0
- package/src/router/segment-resolution/fresh.ts +772 -0
- package/src/router/segment-resolution/helpers.ts +348 -0
- package/src/router/segment-resolution/loader-cache.ts +250 -0
- package/src/router/segment-resolution/loader-mask.ts +44 -0
- package/src/router/segment-resolution/revalidation.ts +1331 -0
- package/src/router/segment-resolution/static-store.ts +81 -0
- 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 +25 -1354
- package/src/router/segment-wrappers.ts +292 -0
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +75 -0
- package/src/router/telemetry-otel.ts +261 -0
- package/src/router/telemetry.ts +377 -0
- package/src/router/timeout.ts +128 -0
- package/src/router/tracing.ts +206 -0
- package/src/router/trie-matching.ts +240 -61
- package/src/router/types.ts +23 -70
- package/src/router/url-params.ts +57 -0
- package/src/router.ts +781 -2378
- package/src/rsc/full-payload.ts +70 -0
- package/src/rsc/handler-context.ts +46 -0
- package/src/rsc/handler.ts +905 -1142
- package/src/rsc/helpers.ts +275 -19
- package/src/rsc/index.ts +2 -25
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +305 -0
- package/src/rsc/manifest-init.ts +77 -0
- package/src/rsc/nonce.ts +14 -0
- package/src/rsc/origin-guard.ts +155 -0
- package/src/rsc/progressive-enhancement.ts +502 -0
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-cache-serve.ts +238 -0
- package/src/rsc/response-error.ts +104 -0
- package/src/rsc/response-route-handler.ts +257 -0
- package/src/rsc/rsc-rendering.ts +527 -0
- package/src/rsc/runtime-warnings.ts +55 -0
- package/src/rsc/server-action.ts +522 -0
- package/src/rsc/shell-capture.ts +897 -0
- package/src/rsc/shell-serve.ts +124 -0
- package/src/rsc/ssr-setup.ts +144 -0
- package/src/rsc/transition-gate.ts +89 -0
- package/src/rsc/types.ts +95 -12
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +99 -82
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +149 -0
- package/src/segment-system.tsx +349 -134
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +459 -85
- package/src/server/cookie-parse.ts +32 -0
- package/src/server/cookie-store.ts +310 -0
- package/src/server/fetchable-loader-store.ts +11 -6
- package/src/server/handle-store.ts +123 -42
- package/src/server/loader-registry.ts +51 -100
- package/src/server/request-context.ts +848 -157
- package/src/server.ts +15 -8
- package/src/ssr/index.tsx +443 -135
- package/src/ssr/ssr-root.tsx +228 -0
- package/src/static-handler.ts +45 -18
- package/src/testing/cache-status.ts +162 -0
- package/src/testing/collect-handle.ts +46 -0
- package/src/testing/dispatch.ts +701 -0
- package/src/testing/dom.entry.ts +22 -0
- package/src/testing/e2e/fixture.ts +188 -0
- package/src/testing/e2e/index.ts +128 -0
- package/src/testing/e2e/matchers.ts +35 -0
- package/src/testing/e2e/page-helpers.ts +272 -0
- package/src/testing/e2e/parity.ts +387 -0
- package/src/testing/e2e/server.ts +195 -0
- package/src/testing/flight-matchers.ts +97 -0
- package/src/testing/flight-normalize.ts +11 -0
- package/src/testing/flight-runtime.d.ts +57 -0
- package/src/testing/flight-tree.ts +682 -0
- package/src/testing/flight.entry.ts +52 -0
- package/src/testing/flight.ts +257 -0
- package/src/testing/generated-routes.ts +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 +76 -98
- package/src/theme/ThemeScript.tsx +12 -14
- package/src/theme/constants.ts +57 -15
- package/src/theme/index.ts +3 -20
- package/src/theme/theme-context.ts +5 -35
- package/src/theme/theme-script.ts +43 -39
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +123 -0
- package/src/types/cache-types.ts +207 -0
- package/src/types/error-types.ts +132 -0
- package/src/types/global-namespace.ts +113 -0
- package/src/types/handler-context.ts +839 -0
- package/src/types/index.ts +81 -0
- package/src/types/loader-types.ts +212 -0
- package/src/types/request-scope.ts +112 -0
- package/src/types/route-config.ts +138 -0
- package/src/types/route-entry.ts +114 -0
- package/src/types/segments.ts +271 -0
- package/src/types.ts +1 -1795
- package/src/urls/include-helper.ts +162 -0
- package/src/urls/include-provider.ts +71 -0
- package/src/urls/index.ts +44 -0
- package/src/urls/path-helper-types.ts +413 -0
- package/src/urls/path-helper.ts +280 -0
- package/src/urls/pattern-types.ts +160 -0
- package/src/urls/response-types.ts +109 -0
- package/src/urls/type-extraction.ts +316 -0
- package/src/urls/urls-function.ts +80 -0
- package/src/urls.ts +1 -1341
- package/src/use-loader.tsx +406 -141
- 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 +182 -0
- package/src/vite/discovery/discover-routers.ts +389 -0
- 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 +467 -0
- package/src/vite/discovery/route-types-writer.ts +214 -0
- package/src/vite/discovery/self-gen-tracking.ts +73 -0
- package/src/vite/discovery/state.ts +161 -0
- package/src/vite/discovery/virtual-module-codegen.ts +183 -0
- package/src/vite/index.ts +23 -2255
- package/src/vite/inject-client-debug.ts +36 -0
- package/src/vite/plugin-types.ts +303 -0
- package/src/vite/plugins/cjs-to-esm.ts +90 -0
- package/src/vite/plugins/client-ref-dedup.ts +120 -0
- package/src/vite/plugins/client-ref-hashing.ts +118 -0
- 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/{expose-action-id.ts → plugins/expose-action-id.ts} +88 -110
- package/src/vite/{expose-id-utils.ts → plugins/expose-id-utils.ts} +89 -79
- package/src/vite/plugins/expose-ids/export-analysis.ts +363 -0
- package/src/vite/plugins/expose-ids/handler-transform.ts +130 -0
- package/src/vite/plugins/expose-ids/loader-transform.ts +64 -0
- package/src/vite/plugins/expose-ids/router-transform.ts +199 -0
- package/src/vite/plugins/expose-ids/types.ts +45 -0
- package/src/vite/plugins/expose-internal-ids.ts +805 -0
- package/src/vite/plugins/performance-tracks.ts +89 -0
- package/src/vite/plugins/refresh-cmd.ts +127 -0
- package/src/vite/plugins/use-cache-transform.ts +313 -0
- package/src/vite/plugins/vercel-output.ts +384 -0
- package/src/vite/plugins/version-injector.ts +94 -0
- package/src/vite/plugins/version-plugin.ts +263 -0
- package/src/vite/plugins/virtual-entries.ts +234 -0
- package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
- package/src/vite/rango.ts +560 -0
- package/src/vite/router-discovery.ts +1638 -0
- package/src/vite/{ast-handler-extract.ts → utils/ast-handler-extract.ts} +200 -37
- package/src/vite/utils/banner.ts +36 -0
- package/src/vite/utils/bundle-analysis.ts +132 -0
- 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 +15 -0
- package/src/vite/utils/package-resolution.ts +89 -0
- package/src/vite/utils/prerender-utils.ts +249 -0
- package/src/vite/utils/shared-utils.ts +269 -0
- package/CLAUDE.md +0 -43
- package/dist/vite/index.named-routes.gen.ts +0 -103
- package/src/browser/lru-cache.ts +0 -69
- package/src/browser/react/use-client-cache.ts +0 -56
- package/src/browser/request-controller.ts +0 -164
- package/src/browser/shallow.ts +0 -35
- package/src/cache/memory-store.ts +0 -253
- package/src/handles/index.ts +0 -6
- package/src/href-context.ts +0 -33
- package/src/network-error-thrower.tsx +0 -21
- package/src/router.gen.ts +0 -6
- package/src/static-handler.gen.ts +0 -5
- package/src/urls.gen.ts +0 -8
- package/src/vite/expose-internal-ids.ts +0 -1167
- package/src/vite/package-resolution.ts +0 -125
- package/src/vite/virtual-entries.ts +0 -114
- /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
package/src/server/context.ts
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
2
|
import type { ReactNode } from "react";
|
|
3
|
-
import type {
|
|
4
|
-
|
|
3
|
+
import type {
|
|
4
|
+
PartialCacheOptions,
|
|
5
|
+
ErrorBoundaryHandler,
|
|
6
|
+
Handler,
|
|
7
|
+
LoaderDefinition,
|
|
8
|
+
MiddlewareFn,
|
|
9
|
+
NotFoundBoundaryHandler,
|
|
10
|
+
ShouldRevalidateFn,
|
|
11
|
+
TransitionConfig,
|
|
12
|
+
} from "../types";
|
|
13
|
+
import { invariant, DslContextError } from "../errors";
|
|
14
|
+
import type { DefaultRouteName } from "../types/global-namespace.js";
|
|
5
15
|
|
|
6
16
|
// ============================================================================
|
|
7
17
|
// Performance Metrics Types
|
|
@@ -13,9 +23,10 @@ import { invariant } from "../errors";
|
|
|
13
23
|
* @internal This type is an implementation detail and may change without notice.
|
|
14
24
|
*/
|
|
15
25
|
export interface PerformanceMetric {
|
|
16
|
-
label: string;
|
|
17
|
-
duration: number;
|
|
18
|
-
startTime: number;
|
|
26
|
+
label: string; // e.g., "route-matching", "loader:UserLoader"
|
|
27
|
+
duration: number; // milliseconds
|
|
28
|
+
startTime: number; // relative to request start
|
|
29
|
+
depth?: number; // nesting level for hierarchical display (0 = top-level)
|
|
19
30
|
}
|
|
20
31
|
|
|
21
32
|
/**
|
|
@@ -29,7 +40,7 @@ export interface MetricsStore {
|
|
|
29
40
|
metrics: PerformanceMetric[];
|
|
30
41
|
}
|
|
31
42
|
// ============================================================================
|
|
32
|
-
//
|
|
43
|
+
// Rango Context
|
|
33
44
|
// ============================================================================
|
|
34
45
|
|
|
35
46
|
/**
|
|
@@ -60,6 +71,10 @@ export type EntryPropCommon = {
|
|
|
60
71
|
};
|
|
61
72
|
|
|
62
73
|
/**
|
|
74
|
+
* Attachments resolved by walking the parent chain, not owned by the entry:
|
|
75
|
+
* middleware composes downward; revalidate and the error/notFound boundaries are
|
|
76
|
+
* resolved by nearest-ancestor lookup. Inherited, not a single execution chain.
|
|
77
|
+
*
|
|
63
78
|
* @internal This type is an implementation detail and may change without notice.
|
|
64
79
|
*/
|
|
65
80
|
export type EntryPropDatas = {
|
|
@@ -69,6 +84,16 @@ export type EntryPropDatas = {
|
|
|
69
84
|
notFoundBoundary: (ReactNode | NotFoundBoundaryHandler)[];
|
|
70
85
|
};
|
|
71
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Render-time presentation fields shared by every entry variant.
|
|
89
|
+
*
|
|
90
|
+
* @internal This type is an implementation detail and may change without notice.
|
|
91
|
+
*/
|
|
92
|
+
export type EntryPropRender = {
|
|
93
|
+
loading?: ReactNode | false;
|
|
94
|
+
transition?: TransitionConfig;
|
|
95
|
+
};
|
|
96
|
+
|
|
72
97
|
/**
|
|
73
98
|
* Loader entry stored in EntryData
|
|
74
99
|
* Contains the loader definition and its revalidation rules
|
|
@@ -105,12 +130,14 @@ export type InterceptSegmentsState = {
|
|
|
105
130
|
* @internal This type is an implementation detail and may change without notice.
|
|
106
131
|
*/
|
|
107
132
|
export type InterceptSelectorContext<TEnv = any> = {
|
|
108
|
-
from: URL;
|
|
109
|
-
to: URL;
|
|
110
|
-
params: Record<string, string>;
|
|
111
|
-
request: Request;
|
|
112
|
-
env: TEnv;
|
|
113
|
-
segments: InterceptSegmentsState;
|
|
133
|
+
from: URL; // Source URL (where user is coming from)
|
|
134
|
+
to: URL; // Destination URL (where user is navigating to)
|
|
135
|
+
params: Record<string, string>; // Matched route params
|
|
136
|
+
request: Request; // The HTTP request object
|
|
137
|
+
env: TEnv; // Platform bindings (Cloudflare env, etc.)
|
|
138
|
+
segments: InterceptSegmentsState; // Client's current segments (where navigating FROM)
|
|
139
|
+
fromRouteName?: DefaultRouteName; // Named route being navigated away from (undefined for unnamed routes)
|
|
140
|
+
toRouteName?: DefaultRouteName; // Named route being navigated to (undefined for unnamed routes)
|
|
114
141
|
};
|
|
115
142
|
|
|
116
143
|
/**
|
|
@@ -119,7 +146,22 @@ export type InterceptSelectorContext<TEnv = any> = {
|
|
|
119
146
|
*
|
|
120
147
|
* @internal This type is an implementation detail and may change without notice.
|
|
121
148
|
*/
|
|
122
|
-
export type InterceptWhenFn<TEnv = any> = (
|
|
149
|
+
export type InterceptWhenFn<TEnv = any> = (
|
|
150
|
+
ctx: InterceptSelectorContext<TEnv>,
|
|
151
|
+
) => boolean;
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Config object passed to intercept() (its 4th argument). `when` gates whether
|
|
155
|
+
* the intercept activates on a soft navigation — a single match-time selector or
|
|
156
|
+
* an array of them (ALL must return true; omit to always activate). This is the
|
|
157
|
+
* intercept counterpart to transition({ when }); both express conditional
|
|
158
|
+
* behavior as a config field rather than a separate DSL helper.
|
|
159
|
+
*
|
|
160
|
+
* @internal This type is an implementation detail and may change without notice.
|
|
161
|
+
*/
|
|
162
|
+
export interface InterceptConfig<TEnv = any> {
|
|
163
|
+
when?: InterceptWhenFn<TEnv> | InterceptWhenFn<TEnv>[];
|
|
164
|
+
}
|
|
123
165
|
|
|
124
166
|
/**
|
|
125
167
|
* Intercept entry stored in EntryData
|
|
@@ -128,8 +170,8 @@ export type InterceptWhenFn<TEnv = any> = (ctx: InterceptSelectorContext<TEnv>)
|
|
|
128
170
|
* @internal This type is an implementation detail and may change without notice.
|
|
129
171
|
*/
|
|
130
172
|
export type InterceptEntry = {
|
|
131
|
-
slotName: `@${string}`;
|
|
132
|
-
routeName: string;
|
|
173
|
+
slotName: `@${string}`; // e.g., "@modal"
|
|
174
|
+
routeName: string; // e.g., "card"
|
|
133
175
|
handler: ReactNode | Handler<any, any, any>;
|
|
134
176
|
middleware: MiddlewareFn<any, any>[];
|
|
135
177
|
revalidate: ShouldRevalidateFn<any, any>[];
|
|
@@ -137,14 +179,34 @@ export type InterceptEntry = {
|
|
|
137
179
|
notFoundBoundary: (ReactNode | NotFoundBoundaryHandler)[];
|
|
138
180
|
loader: LoaderEntry[];
|
|
139
181
|
loading?: ReactNode | false;
|
|
140
|
-
|
|
141
|
-
|
|
182
|
+
transition?: TransitionConfig;
|
|
183
|
+
layout?: ReactNode | Handler<any, any, any>; // Wrapper layout with <Outlet /> for content
|
|
184
|
+
when: InterceptWhenFn[]; // Selector conditions - all must return true to intercept
|
|
142
185
|
};
|
|
143
186
|
|
|
187
|
+
export interface ParallelEntryData
|
|
188
|
+
extends EntryPropCommon, EntryPropDatas, EntryPropSegments, EntryPropRender {
|
|
189
|
+
type: "parallel";
|
|
190
|
+
handler: Record<`@${string}`, Handler<any, any, any> | ReactNode>;
|
|
191
|
+
/** Set when any parallel slot is a Static definition */
|
|
192
|
+
isStaticPrerender?: true;
|
|
193
|
+
/** Per-slot static handler $$ids for build-time store lookup */
|
|
194
|
+
staticHandlerIds?: Record<string, string>;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export type ParallelEntries = Partial<Record<`@${string}`, ParallelEntryData>>;
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* This entry's own structural children plus its owned loaders. `loader` lives
|
|
201
|
+
* here (not in EntryPropDatas) because loaders are owned by the entry, not
|
|
202
|
+
* inherited from ancestors.
|
|
203
|
+
*
|
|
204
|
+
* @internal This type is an implementation detail and may change without notice.
|
|
205
|
+
*/
|
|
144
206
|
export type EntryPropSegments = {
|
|
145
207
|
loader: LoaderEntry[];
|
|
146
208
|
layout: EntryData[];
|
|
147
|
-
parallel:
|
|
209
|
+
parallel: ParallelEntries; // slot -> parallel entry (same entry may back multiple slots)
|
|
148
210
|
intercept: InterceptEntry[]; // intercept definitions for soft navigation
|
|
149
211
|
};
|
|
150
212
|
|
|
@@ -152,46 +214,56 @@ export type EntryData =
|
|
|
152
214
|
| ({
|
|
153
215
|
type: "route";
|
|
154
216
|
handler: Handler<any, any, any>;
|
|
155
|
-
loading?: ReactNode | false;
|
|
156
217
|
/** URL pattern for this route (used by path() in urls()) */
|
|
157
218
|
pattern?: string;
|
|
158
219
|
/** Set when handler is a Prerender definition */
|
|
159
220
|
isPrerender?: true;
|
|
160
221
|
/** Original PrerenderHandlerDefinition (for build-time getParams access) */
|
|
161
|
-
prerenderDef?: {
|
|
222
|
+
prerenderDef?: {
|
|
223
|
+
getParams?: (ctx: any) => Promise<any[]> | any[];
|
|
224
|
+
options?: { concurrency?: number };
|
|
225
|
+
};
|
|
226
|
+
/** Set when route is wrapped with Passthrough() — has a separate live handler */
|
|
227
|
+
isPassthrough?: true;
|
|
228
|
+
/** Live handler for runtime fallback (only set on Passthrough routes) */
|
|
229
|
+
liveHandler?: Handler<any, any, any>;
|
|
162
230
|
/** Set when handler is a Static definition (build-time only) */
|
|
163
231
|
isStaticPrerender?: true;
|
|
232
|
+
/** Static handler $$id for build-time store lookup */
|
|
233
|
+
staticHandlerId?: string;
|
|
164
234
|
/** Response type for non-RSC routes (json, text, image, any) */
|
|
165
235
|
responseType?: string;
|
|
236
|
+
/**
|
|
237
|
+
* PPR (partial pre-rendering) opt-in from the path() `ppr` option. A
|
|
238
|
+
* document-level property of the page route: `true` uses the default
|
|
239
|
+
* shell policy, an object carries ttl/swr/tags. Read by the integrated
|
|
240
|
+
* PPR serve path (rsc/shell-serve.ts resolvePprConfig).
|
|
241
|
+
*/
|
|
242
|
+
ppr?: boolean | import("../urls/pattern-types.js").PartialPrerenderProps;
|
|
166
243
|
} & EntryPropCommon &
|
|
167
244
|
EntryPropDatas &
|
|
168
|
-
EntryPropSegments
|
|
245
|
+
EntryPropSegments &
|
|
246
|
+
EntryPropRender)
|
|
169
247
|
| ({
|
|
170
248
|
type: "layout";
|
|
171
249
|
handler: ReactNode | Handler<any, any, any>;
|
|
172
|
-
loading?: ReactNode | false;
|
|
173
250
|
/** Set when handler is a Static definition (build-time only) */
|
|
174
251
|
isStaticPrerender?: true;
|
|
252
|
+
/** Static handler $$id for build-time store lookup */
|
|
253
|
+
staticHandlerId?: string;
|
|
175
254
|
} & EntryPropCommon &
|
|
176
255
|
EntryPropDatas &
|
|
177
|
-
EntryPropSegments
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
handler: Record<`@${string}`, Handler<any, any, any> | ReactNode>;
|
|
181
|
-
loading?: ReactNode | false;
|
|
182
|
-
/** Set when any parallel slot is a Static definition */
|
|
183
|
-
isStaticPrerender?: true;
|
|
184
|
-
} & EntryPropCommon &
|
|
185
|
-
EntryPropDatas &
|
|
186
|
-
EntryPropSegments)
|
|
256
|
+
EntryPropSegments &
|
|
257
|
+
EntryPropRender)
|
|
258
|
+
| ParallelEntryData
|
|
187
259
|
| ({
|
|
188
260
|
type: "cache";
|
|
189
261
|
/** Cache entries create cache boundaries and render like layouts (with Outlet) */
|
|
190
262
|
handler: ReactNode | Handler<any, any, any>;
|
|
191
|
-
loading?: ReactNode | false;
|
|
192
263
|
} & EntryPropCommon &
|
|
193
264
|
EntryPropDatas &
|
|
194
|
-
EntryPropSegments
|
|
265
|
+
EntryPropSegments &
|
|
266
|
+
EntryPropRender);
|
|
195
267
|
|
|
196
268
|
/**
|
|
197
269
|
* Tracked include info for build-time manifest generation
|
|
@@ -229,13 +301,65 @@ interface HelperContext {
|
|
|
229
301
|
urlPrefix?: string;
|
|
230
302
|
/** Name prefix from include() - applied to all named routes */
|
|
231
303
|
namePrefix?: string;
|
|
304
|
+
/** True when this scope is at root level (no named include boundary above).
|
|
305
|
+
* Routes at root scope allow dot-local reverse to fall back to bare names. */
|
|
306
|
+
rootScoped?: boolean;
|
|
232
307
|
/** Run helper for cleaner middleware code */
|
|
233
308
|
run?: <T>(fn: () => T | Promise<T>) => T | Promise<T>;
|
|
234
309
|
/** Tracked includes for build-time manifest generation */
|
|
235
310
|
trackedIncludes?: TrackedInclude[];
|
|
311
|
+
/** Cache profiles for DSL-time cache("profileName") resolution */
|
|
312
|
+
cacheProfiles?: Record<
|
|
313
|
+
string,
|
|
314
|
+
import("../cache/profile-registry.js").CacheProfile
|
|
315
|
+
>;
|
|
316
|
+
/** True when resolving handlers inside a cache() DSL boundary.
|
|
317
|
+
* Read by ctx.get() to guard non-cacheable variable reads. */
|
|
318
|
+
insideCacheScope?: boolean;
|
|
319
|
+
/**
|
|
320
|
+
* Include scope string applied to direct-descendant shortCodes.
|
|
321
|
+
*
|
|
322
|
+
* Each `include(...)` call allocates a sibling-positional token like `I0`,
|
|
323
|
+
* `I1` from its parent's include counter and stores the composed scope
|
|
324
|
+
* (`${parentScope}I${idx}`) in its lazyContext. When the include's handler
|
|
325
|
+
* evaluates lazily, the store's `includeScope` is set from that context so
|
|
326
|
+
* every direct-descendant shortCode is generated as
|
|
327
|
+
* `${parent.shortCode}${includeScope}${prefix}${index}` — preventing
|
|
328
|
+
* collisions with siblings declared outside the include.
|
|
329
|
+
*
|
|
330
|
+
* The scope is NOT propagated through `store.run(...)`, so layouts /
|
|
331
|
+
* parallels / caches inside the include absorb the scope into their own
|
|
332
|
+
* shortCodes and their children start fresh.
|
|
333
|
+
*/
|
|
334
|
+
includeScope?: string;
|
|
335
|
+
}
|
|
336
|
+
// Use a global symbol key so the AsyncLocalStorage instance survives HMR
|
|
337
|
+
// module re-evaluation. Without this, Vite's RSC module runner may create
|
|
338
|
+
// a new instance when context.ts is re-evaluated, while other modules still
|
|
339
|
+
// hold references to the old instance — causing getStore() to return
|
|
340
|
+
// undefined even inside a run() callback.
|
|
341
|
+
const RSC_CONTEXT_KEY = Symbol.for("rangojs-router:rsc-context");
|
|
342
|
+
export const RangoContext: AsyncLocalStorage<HelperContext> = ((
|
|
343
|
+
globalThis as any
|
|
344
|
+
)[RSC_CONTEXT_KEY] ??= new AsyncLocalStorage<HelperContext>());
|
|
345
|
+
|
|
346
|
+
/** shortCode prefix letter per entry type (e.g. "L0", "R2", "M1C0"). */
|
|
347
|
+
const SHORT_CODE_PREFIX: Record<
|
|
348
|
+
"layout" | "parallel" | "route" | "loader" | "cache",
|
|
349
|
+
string
|
|
350
|
+
> = {
|
|
351
|
+
layout: "L",
|
|
352
|
+
parallel: "P",
|
|
353
|
+
route: "R",
|
|
354
|
+
loader: "D",
|
|
355
|
+
cache: "C",
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
/** Post-increment a named per-store counter, returning the prior value. */
|
|
359
|
+
function bumpCounter(store: HelperContext, key: string): number {
|
|
360
|
+
store.counters[key] ??= 0;
|
|
361
|
+
return store.counters[key]++;
|
|
236
362
|
}
|
|
237
|
-
export const RSCRouterContext: AsyncLocalStorage<HelperContext> =
|
|
238
|
-
new AsyncLocalStorage<HelperContext>();
|
|
239
363
|
|
|
240
364
|
export const getContext = (): {
|
|
241
365
|
context: AsyncLocalStorage<HelperContext>;
|
|
@@ -243,29 +367,29 @@ export const getContext = (): {
|
|
|
243
367
|
getParent: () => EntryData | null;
|
|
244
368
|
getOrCreateStore: (forRoute?: string) => HelperContext;
|
|
245
369
|
getNextIndex: (
|
|
246
|
-
type: (string & {}) | "layout" | "parallel" | "middleware" | "revalidate"
|
|
370
|
+
type: (string & {}) | "layout" | "parallel" | "middleware" | "revalidate",
|
|
247
371
|
) => string;
|
|
248
372
|
getShortCode: (
|
|
249
|
-
type: "layout" | "parallel" | "route" | "loader" | "cache"
|
|
373
|
+
type: "layout" | "parallel" | "route" | "loader" | "cache",
|
|
250
374
|
) => string;
|
|
251
375
|
run: <T>(
|
|
252
376
|
namespace: string,
|
|
253
377
|
parent: EntryData | null,
|
|
254
|
-
callback: (...args: any[]) => T
|
|
378
|
+
callback: (...args: any[]) => T,
|
|
255
379
|
) => T;
|
|
256
380
|
runWithStore: <T>(
|
|
257
381
|
store: HelperContext,
|
|
258
382
|
namespace: string,
|
|
259
383
|
parent: EntryData | null,
|
|
260
|
-
callback: (...args: any[]) => T
|
|
384
|
+
callback: (...args: any[]) => T,
|
|
261
385
|
) => T;
|
|
262
386
|
} => {
|
|
263
|
-
const context =
|
|
387
|
+
const context = RangoContext;
|
|
264
388
|
|
|
265
389
|
return {
|
|
266
390
|
context,
|
|
267
391
|
getOrCreateStore: (forRoute?: string): HelperContext => {
|
|
268
|
-
let store =
|
|
392
|
+
let store = RangoContext.getStore();
|
|
269
393
|
if (!store) {
|
|
270
394
|
store = {
|
|
271
395
|
manifest: new Map<string, EntryData>(),
|
|
@@ -285,7 +409,7 @@ export const getContext = (): {
|
|
|
285
409
|
const store = context.getStore();
|
|
286
410
|
if (!store) {
|
|
287
411
|
throw new Error(
|
|
288
|
-
"
|
|
412
|
+
"Rango context store is not available. Make sure to run within Rango context.",
|
|
289
413
|
);
|
|
290
414
|
}
|
|
291
415
|
return store;
|
|
@@ -299,44 +423,46 @@ export const getContext = (): {
|
|
|
299
423
|
return store.parent;
|
|
300
424
|
},
|
|
301
425
|
getNextIndex: (
|
|
302
|
-
type: (string & {}) | "layout" | "parallel" | "middleware" | "revalidate"
|
|
426
|
+
type: (string & {}) | "layout" | "parallel" | "middleware" | "revalidate",
|
|
303
427
|
) => {
|
|
304
428
|
const store = context.getStore();
|
|
305
|
-
invariant(store, "No context
|
|
306
|
-
store
|
|
307
|
-
const index = store.counters[type];
|
|
308
|
-
store.counters[type] = index + 1;
|
|
309
|
-
return `$${type}.${index}`;
|
|
429
|
+
invariant(store, "No context RangoContext available");
|
|
430
|
+
return `$${type}.${bumpCounter(store, type)}`;
|
|
310
431
|
},
|
|
311
|
-
getShortCode: (
|
|
432
|
+
getShortCode: (
|
|
433
|
+
type: "layout" | "parallel" | "route" | "loader" | "cache",
|
|
434
|
+
) => {
|
|
312
435
|
const store = context.getStore();
|
|
313
|
-
invariant(store, "No context
|
|
436
|
+
invariant(store, "No context RangoContext available");
|
|
314
437
|
|
|
315
438
|
const parent = store.parent;
|
|
316
|
-
const prefix = type
|
|
317
|
-
const mountPrefix =
|
|
439
|
+
const prefix = SHORT_CODE_PREFIX[type];
|
|
440
|
+
const mountPrefix =
|
|
441
|
+
store.mountIndex !== undefined ? `M${store.mountIndex}` : "";
|
|
442
|
+
|
|
443
|
+
const includeScope = store.includeScope ?? "";
|
|
318
444
|
|
|
319
445
|
if (!parent) {
|
|
320
446
|
// Root entry: prefix with mount index and use mount-scoped counter
|
|
321
|
-
const counterKey = mountPrefix
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
store
|
|
325
|
-
return `${mountPrefix}${prefix}${index}`;
|
|
447
|
+
const counterKey = mountPrefix
|
|
448
|
+
? `${mountPrefix}_root_${type}`
|
|
449
|
+
: `root_${type}`;
|
|
450
|
+
return `${mountPrefix}${prefix}${bumpCounter(store, counterKey)}`;
|
|
326
451
|
} else {
|
|
327
|
-
// Child entry: use parent-scoped counter
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
452
|
+
// Child entry: use parent-scoped counter with includeScope appended.
|
|
453
|
+
// When we're evaluating a lazy include's direct children, includeScope
|
|
454
|
+
// is a per-include token like "I0" / "I1I0" that partitions the
|
|
455
|
+
// parent's counter namespace so routes inside one include cannot
|
|
456
|
+
// collide with siblings declared outside it.
|
|
457
|
+
const counterKey = `${parent.shortCode}${includeScope}_${type}`;
|
|
458
|
+
return `${parent.shortCode}${includeScope}${prefix}${bumpCounter(store, counterKey)}`;
|
|
333
459
|
}
|
|
334
460
|
},
|
|
335
461
|
runWithStore: <T>(
|
|
336
462
|
store: HelperContext,
|
|
337
463
|
namespace: string,
|
|
338
464
|
parent: EntryData | null,
|
|
339
|
-
callback: (...args: any[]) => T
|
|
465
|
+
callback: (...args: any[]) => T,
|
|
340
466
|
): T => {
|
|
341
467
|
return context.run(
|
|
342
468
|
{
|
|
@@ -353,23 +479,30 @@ export const getContext = (): {
|
|
|
353
479
|
searchSchemas: store.searchSchemas,
|
|
354
480
|
urlPrefix: store.urlPrefix,
|
|
355
481
|
namePrefix: store.namePrefix,
|
|
482
|
+
rootScoped: store.rootScoped,
|
|
356
483
|
trackedIncludes: store.trackedIncludes,
|
|
484
|
+
cacheProfiles: store.cacheProfiles,
|
|
485
|
+
includeScope: store.includeScope,
|
|
357
486
|
},
|
|
358
|
-
callback
|
|
487
|
+
callback,
|
|
359
488
|
);
|
|
360
489
|
},
|
|
361
490
|
run: <T>(
|
|
362
491
|
namespace: string,
|
|
363
492
|
parent: EntryData | null,
|
|
364
|
-
callback: (...args: any[]) => T
|
|
493
|
+
callback: (...args: any[]) => T,
|
|
365
494
|
) => {
|
|
366
495
|
const store = context.getStore();
|
|
367
496
|
// Preserve parent counters to ensure globally unique shortCodes
|
|
368
497
|
const counters = store?.counters || {};
|
|
369
498
|
const manifest = store ? store.manifest : new Map<string, EntryData>();
|
|
370
499
|
const patterns = store?.patterns || new Map<string, string>();
|
|
371
|
-
const
|
|
372
|
-
const
|
|
500
|
+
const patternsByPrefix = store?.patternsByPrefix;
|
|
501
|
+
const trailingSlash =
|
|
502
|
+
store?.trailingSlash ||
|
|
503
|
+
new Map<string, "never" | "always" | "ignore">();
|
|
504
|
+
const searchSchemas =
|
|
505
|
+
store?.searchSchemas || new Map<string, Record<string, string>>();
|
|
373
506
|
return context.run(
|
|
374
507
|
{
|
|
375
508
|
manifest,
|
|
@@ -381,18 +514,46 @@ export const getContext = (): {
|
|
|
381
514
|
metrics: store?.metrics,
|
|
382
515
|
isSSR: store?.isSSR,
|
|
383
516
|
patterns,
|
|
517
|
+
patternsByPrefix,
|
|
384
518
|
trailingSlash,
|
|
385
519
|
searchSchemas,
|
|
386
520
|
urlPrefix: store?.urlPrefix,
|
|
387
521
|
namePrefix: store?.namePrefix,
|
|
522
|
+
rootScoped: store?.rootScoped,
|
|
388
523
|
trackedIncludes: store?.trackedIncludes,
|
|
524
|
+
cacheProfiles: store?.cacheProfiles,
|
|
389
525
|
},
|
|
390
|
-
callback
|
|
526
|
+
callback,
|
|
391
527
|
);
|
|
392
528
|
},
|
|
393
529
|
};
|
|
394
530
|
};
|
|
395
531
|
|
|
532
|
+
/**
|
|
533
|
+
* Acquire the active DSL build context, throwing `message` if a helper was
|
|
534
|
+
* called outside a urls()/map() builder. Returns the store API and the live
|
|
535
|
+
* HelperContext so callers avoid a second getContext() lookup.
|
|
536
|
+
*/
|
|
537
|
+
export function requireDslContext(message: string): {
|
|
538
|
+
store: ReturnType<typeof getContext>;
|
|
539
|
+
ctx: HelperContext;
|
|
540
|
+
} {
|
|
541
|
+
const store = getContext();
|
|
542
|
+
const ctx = store.context.getStore();
|
|
543
|
+
if (!ctx) {
|
|
544
|
+
// The only reason the store is absent here is that a route-definition helper
|
|
545
|
+
// ran with no active RangoContext — i.e. outside a urls()/map() builder.
|
|
546
|
+
// Record that as the cause so the throw is self-explanatory, not a bare
|
|
547
|
+
// "must be called inside urls()" with no indication of the mechanism.
|
|
548
|
+
throw new DslContextError(message, {
|
|
549
|
+
cause:
|
|
550
|
+
"RangoContext store is undefined: a route-definition helper was called " +
|
|
551
|
+
"outside an active urls()/map() builder.",
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
return { store, ctx };
|
|
555
|
+
}
|
|
556
|
+
|
|
396
557
|
/**
|
|
397
558
|
* Run a callback with specific URL and name prefixes
|
|
398
559
|
* Used by include() to apply prefixes to nested patterns
|
|
@@ -400,9 +561,9 @@ export const getContext = (): {
|
|
|
400
561
|
export function runWithPrefixes<T>(
|
|
401
562
|
urlPrefix: string,
|
|
402
563
|
namePrefix: string | undefined,
|
|
403
|
-
callback: () => T
|
|
564
|
+
callback: () => T,
|
|
404
565
|
): T {
|
|
405
|
-
const store =
|
|
566
|
+
const store = RangoContext.getStore();
|
|
406
567
|
if (!store) {
|
|
407
568
|
throw new Error("runWithPrefixes must be called within router context");
|
|
408
569
|
}
|
|
@@ -418,19 +579,43 @@ export function runWithPrefixes<T>(
|
|
|
418
579
|
} else {
|
|
419
580
|
combinedUrlPrefix = urlPrefix;
|
|
420
581
|
}
|
|
421
|
-
const combinedNamePrefix =
|
|
422
|
-
|
|
423
|
-
?
|
|
424
|
-
|
|
425
|
-
|
|
582
|
+
const combinedNamePrefix =
|
|
583
|
+
namePrefix !== undefined
|
|
584
|
+
? namePrefix === ""
|
|
585
|
+
? store.namePrefix
|
|
586
|
+
: store.namePrefix
|
|
587
|
+
? `${store.namePrefix}.${namePrefix}`
|
|
588
|
+
: namePrefix
|
|
589
|
+
: store.namePrefix;
|
|
590
|
+
|
|
591
|
+
// Track root scope for dot-local reverse resolution.
|
|
592
|
+
//
|
|
593
|
+
// The flag answers: "can this route reach bare names at root scope?"
|
|
594
|
+
// It propagates through the include chain:
|
|
595
|
+
//
|
|
596
|
+
// { name: "" } — transparent: inherit parent, default true
|
|
597
|
+
// { name: "foo" } — inherit parent if already set, else create boundary (false)
|
|
598
|
+
// no name — inherit parent unchanged
|
|
599
|
+
//
|
|
600
|
+
// This means { name: "" } + nested { name: "sub" } keeps rootScoped=true
|
|
601
|
+
// (the outer transparent include establishes root access, and the inner
|
|
602
|
+
// named include inherits it). But a direct { name: "sub" } at root gets
|
|
603
|
+
// rootScoped=false (no prior root-access grant, so it creates a boundary).
|
|
604
|
+
const combinedRootScoped =
|
|
605
|
+
namePrefix === ""
|
|
606
|
+
? (store.rootScoped ?? true)
|
|
607
|
+
: namePrefix !== undefined
|
|
608
|
+
? (store.rootScoped ?? false)
|
|
609
|
+
: store.rootScoped;
|
|
426
610
|
|
|
427
|
-
return
|
|
611
|
+
return RangoContext.run(
|
|
428
612
|
{
|
|
429
613
|
...store,
|
|
430
614
|
urlPrefix: combinedUrlPrefix,
|
|
431
615
|
namePrefix: combinedNamePrefix,
|
|
616
|
+
rootScoped: combinedRootScoped,
|
|
432
617
|
},
|
|
433
|
-
callback
|
|
618
|
+
callback,
|
|
434
619
|
);
|
|
435
620
|
}
|
|
436
621
|
|
|
@@ -438,7 +623,7 @@ export function runWithPrefixes<T>(
|
|
|
438
623
|
* Get current URL prefix from context
|
|
439
624
|
*/
|
|
440
625
|
export function getUrlPrefix(): string {
|
|
441
|
-
const store =
|
|
626
|
+
const store = RangoContext.getStore();
|
|
442
627
|
return store?.urlPrefix || "";
|
|
443
628
|
}
|
|
444
629
|
|
|
@@ -446,13 +631,96 @@ export function getUrlPrefix(): string {
|
|
|
446
631
|
* Get current name prefix from context
|
|
447
632
|
*/
|
|
448
633
|
export function getNamePrefix(): string | undefined {
|
|
449
|
-
const store =
|
|
634
|
+
const store = RangoContext.getStore();
|
|
450
635
|
return store?.namePrefix;
|
|
451
636
|
}
|
|
452
637
|
|
|
638
|
+
/**
|
|
639
|
+
* Get whether the current scope is at root level (no named include boundary above).
|
|
640
|
+
* Returns true at root or inside { name: "" } includes, false inside named includes.
|
|
641
|
+
*/
|
|
642
|
+
export function getRootScoped(): boolean {
|
|
643
|
+
const store = RangoContext.getStore();
|
|
644
|
+
return store?.rootScoped ?? true;
|
|
645
|
+
}
|
|
646
|
+
|
|
453
647
|
// Export HelperContext type for use in other modules
|
|
454
648
|
export type { HelperContext };
|
|
455
649
|
|
|
650
|
+
/**
|
|
651
|
+
* Return an isolated copy of a lazy include's captured parent entry.
|
|
652
|
+
*
|
|
653
|
+
* DSL helpers (loader(), middleware(), etc.) mutate ctx.parent in place.
|
|
654
|
+
* Multiple include() scopes capture the *same* syntheticMapRoot as their
|
|
655
|
+
* parent, so without isolation one include's loaders/middleware leak into
|
|
656
|
+
* every other route that shares that root.
|
|
657
|
+
*
|
|
658
|
+
* The clone is shallow: only the mutable arrays are copied so each
|
|
659
|
+
* include pushes to its own list. The rest of the entry (id, shortCode,
|
|
660
|
+
* parent pointer, handler) stays shared, which is correct and cheap.
|
|
661
|
+
*/
|
|
662
|
+
export function getIsolatedLazyParent(
|
|
663
|
+
captured: EntryData | null | undefined,
|
|
664
|
+
): EntryData | null {
|
|
665
|
+
if (!captured) return null;
|
|
666
|
+
return {
|
|
667
|
+
...captured,
|
|
668
|
+
loader: [...captured.loader],
|
|
669
|
+
middleware: [...captured.middleware],
|
|
670
|
+
revalidate: [...captured.revalidate],
|
|
671
|
+
errorBoundary: [...captured.errorBoundary],
|
|
672
|
+
notFoundBoundary: [...captured.notFoundBoundary],
|
|
673
|
+
layout: [...captured.layout],
|
|
674
|
+
parallel: { ...captured.parallel },
|
|
675
|
+
intercept: [...captured.intercept],
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
export function getParallelEntries(
|
|
680
|
+
parallels: ParallelEntries | EntryData[] | undefined,
|
|
681
|
+
): ParallelEntryData[] {
|
|
682
|
+
if (!parallels) return [];
|
|
683
|
+
if (Array.isArray(parallels)) {
|
|
684
|
+
return parallels.filter(
|
|
685
|
+
(entry): entry is ParallelEntryData => entry.type === "parallel",
|
|
686
|
+
);
|
|
687
|
+
}
|
|
688
|
+
return Object.values(parallels).filter(
|
|
689
|
+
(entry): entry is ParallelEntryData => !!entry,
|
|
690
|
+
);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
export function getParallelSlotEntries(
|
|
694
|
+
parallels: ParallelEntries | EntryData[] | undefined,
|
|
695
|
+
): Array<{ slot: `@${string}`; entry: ParallelEntryData }> {
|
|
696
|
+
if (!parallels) return [];
|
|
697
|
+
|
|
698
|
+
if (Array.isArray(parallels)) {
|
|
699
|
+
return getParallelEntries(parallels).flatMap((entry) =>
|
|
700
|
+
(Object.keys(entry.handler) as `@${string}`[]).map((slot) => ({
|
|
701
|
+
slot,
|
|
702
|
+
entry,
|
|
703
|
+
})),
|
|
704
|
+
);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
return Object.entries(parallels)
|
|
708
|
+
.filter(([, entry]) => !!entry)
|
|
709
|
+
.map(([slot, entry]) => ({
|
|
710
|
+
slot: slot as `@${string}`,
|
|
711
|
+
entry: entry!,
|
|
712
|
+
}));
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
export function getParallelSlotCount(
|
|
716
|
+
parallels: ParallelEntries | EntryData[] | undefined,
|
|
717
|
+
): number {
|
|
718
|
+
if (!parallels) return 0;
|
|
719
|
+
return Array.isArray(parallels)
|
|
720
|
+
? parallels.filter((entry) => entry?.type === "parallel").length
|
|
721
|
+
: Object.keys(parallels).length;
|
|
722
|
+
}
|
|
723
|
+
|
|
456
724
|
// ============================================================================
|
|
457
725
|
// Performance Metrics Helpers
|
|
458
726
|
// ============================================================================
|
|
@@ -468,8 +736,8 @@ export type { HelperContext };
|
|
|
468
736
|
* done(); // Records duration
|
|
469
737
|
* ```
|
|
470
738
|
*/
|
|
471
|
-
export function track(label: string): () => void {
|
|
472
|
-
const store =
|
|
739
|
+
export function track(label: string, depth?: number): () => void {
|
|
740
|
+
const store = RangoContext.getStore();
|
|
473
741
|
|
|
474
742
|
// No-op if context unavailable or metrics not enabled
|
|
475
743
|
if (!store?.metrics?.enabled) {
|
|
@@ -479,7 +747,113 @@ export function track(label: string): () => void {
|
|
|
479
747
|
const startTime = performance.now() - store.metrics.requestStart;
|
|
480
748
|
|
|
481
749
|
return () => {
|
|
482
|
-
const duration =
|
|
483
|
-
|
|
750
|
+
const duration =
|
|
751
|
+
performance.now() - store.metrics!.requestStart - startTime;
|
|
752
|
+
store.metrics!.metrics.push({
|
|
753
|
+
label,
|
|
754
|
+
duration,
|
|
755
|
+
startTime,
|
|
756
|
+
...(depth != null ? { depth } : {}),
|
|
757
|
+
});
|
|
484
758
|
};
|
|
485
759
|
}
|
|
760
|
+
|
|
761
|
+
/**
|
|
762
|
+
* Separate ALS for tracking loader execution scope.
|
|
763
|
+
* Uses a dedicated ALS (not RangoContext) to avoid issues with
|
|
764
|
+
* nested RangoContext.run() calls in Vite's module runner.
|
|
765
|
+
*/
|
|
766
|
+
const LOADER_SCOPE_KEY = Symbol.for("rangojs-router:loader-scope");
|
|
767
|
+
const loaderScopeALS: AsyncLocalStorage<{ active: true }> = ((
|
|
768
|
+
globalThis as any
|
|
769
|
+
)[LOADER_SCOPE_KEY] ??= new AsyncLocalStorage<{ active: true }>());
|
|
770
|
+
|
|
771
|
+
// Purity-only scope: marks that a loader FUNCTION BODY is executing, regardless
|
|
772
|
+
// of how the loader was invoked (DSL via runInsideLoaderScope, or handler-
|
|
773
|
+
// invoked via ctx.use). Consulted ONLY by isInsideCacheScope() to exempt
|
|
774
|
+
// request-scoped reads. It deliberately does NOT affect isInsideLoaderScope(),
|
|
775
|
+
// so rendered()/barrier/deadlock gating (which must distinguish DSL from
|
|
776
|
+
// handler-invoked loaders) is unchanged.
|
|
777
|
+
const LOADER_BODY_SCOPE_KEY = Symbol.for("rangojs-router:loader-body-scope");
|
|
778
|
+
const loaderBodyScopeALS: AsyncLocalStorage<{ active: true }> = ((
|
|
779
|
+
globalThis as any
|
|
780
|
+
)[LOADER_BODY_SCOPE_KEY] ??= new AsyncLocalStorage<{ active: true }>());
|
|
781
|
+
|
|
782
|
+
/**
|
|
783
|
+
* Check if the current execution is inside a cache() DSL boundary.
|
|
784
|
+
* Returns false inside loader execution — loaders are always fresh
|
|
785
|
+
* (never cached), so non-cacheable reads are safe.
|
|
786
|
+
*/
|
|
787
|
+
export function isInsideCacheScope(): boolean {
|
|
788
|
+
if (RangoContext.getStore()?.insideCacheScope !== true) return false;
|
|
789
|
+
// Loaders are always fresh — even inside a cache() boundary, the loader
|
|
790
|
+
// function re-executes on every request. Skip the guard when running
|
|
791
|
+
// inside a loader.
|
|
792
|
+
if (loaderScopeALS.getStore()?.active) return false;
|
|
793
|
+
// Also exempt handler-invoked loaders: their bodies run in a loader-body
|
|
794
|
+
// scope (not the DSL loader scope above), so request-scoped reads inside any
|
|
795
|
+
// loader — however invoked — are safe (loaders always re-run fresh).
|
|
796
|
+
if (loaderBodyScopeALS.getStore()?.active) return false;
|
|
797
|
+
return true;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
/**
|
|
801
|
+
* Check if the current execution is inside a DSL loader scope
|
|
802
|
+
* (wrapped by runInsideLoaderScope). Used by rendered() barrier
|
|
803
|
+
* to distinguish DSL loaders from handler-invoked loaders.
|
|
804
|
+
*/
|
|
805
|
+
export function isInsideLoaderScope(): boolean {
|
|
806
|
+
return loaderScopeALS.getStore()?.active === true;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
/**
|
|
810
|
+
* Run `fn` inside a loader scope. While active, cache-scope guards
|
|
811
|
+
* are bypassed because loaders are always fresh (never cached) and
|
|
812
|
+
* their side effects (setCookie, header, etc.) are safe.
|
|
813
|
+
*/
|
|
814
|
+
export function runInsideLoaderScope<T>(fn: () => T): T {
|
|
815
|
+
return loaderScopeALS.run({ active: true }, fn);
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
/**
|
|
819
|
+
* Run `fn` inside a loader BODY scope. Marks loader-function execution for the
|
|
820
|
+
* cache-purity guard only (isInsideCacheScope), WITHOUT affecting
|
|
821
|
+
* isInsideLoaderScope()/rendered() gating. Applied to every loader body (DSL
|
|
822
|
+
* and handler-invoked via ctx.use) so request-scoped reads inside a loader
|
|
823
|
+
* never trip the cache-scope guards — loaders always run fresh.
|
|
824
|
+
*/
|
|
825
|
+
export function runInsideLoaderBodyScope<T>(fn: () => T): T {
|
|
826
|
+
return loaderBodyScopeALS.run({ active: true }, fn);
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
// Scope for handle PUSH CALLBACKS (push(() => ...), including async ones).
|
|
830
|
+
// A push callback's value is stored as-is; if it is a promise it is NOT tracked
|
|
831
|
+
// by handleStore.settled and does not block segment resolution, so a
|
|
832
|
+
// ctx.use(loader) made from inside such a callback can never form a rendered()
|
|
833
|
+
// deadlock. This is an ALS (not a plain boolean) so the exemption survives the
|
|
834
|
+
// callback's own awaits — an async push callback that resumes after `await`
|
|
835
|
+
// still reads as "inside a push callback" and stays out of the deadlock guard.
|
|
836
|
+
const PUSH_CALLBACK_SCOPE_KEY = Symbol.for(
|
|
837
|
+
"rangojs-router:push-callback-scope",
|
|
838
|
+
);
|
|
839
|
+
const pushCallbackScopeALS: AsyncLocalStorage<{ active: true }> = ((
|
|
840
|
+
globalThis as any
|
|
841
|
+
)[PUSH_CALLBACK_SCOPE_KEY] ??= new AsyncLocalStorage<{ active: true }>());
|
|
842
|
+
|
|
843
|
+
/**
|
|
844
|
+
* Check if the current execution is inside a handle push callback (sync or an
|
|
845
|
+
* async callback's continuation). Used by the handler-to-loader deadlock guard
|
|
846
|
+
* to exempt push-callback continuations.
|
|
847
|
+
*/
|
|
848
|
+
export function isInsidePushCallbackScope(): boolean {
|
|
849
|
+
return pushCallbackScopeALS.getStore()?.active === true;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
/**
|
|
853
|
+
* Run `fn` inside a push-callback scope. Wraps the invocation of a handle push
|
|
854
|
+
* callback so that any ctx.use(loader) it makes — including after one of its own
|
|
855
|
+
* awaits — is exempt from the deadlock guard.
|
|
856
|
+
*/
|
|
857
|
+
export function runInsidePushCallbackScope<T>(fn: () => T): T {
|
|
858
|
+
return pushCallbackScopeALS.run({ active: true }, fn);
|
|
859
|
+
}
|