@rangojs/router 0.0.0-experimental.bd6e11bc → 0.0.0-experimental.bdaf10aa
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +8 -4
- package/README.md +296 -887
- package/dist/bin/rango.js +459 -91
- package/dist/testing/vitest.js +36 -2
- package/dist/vite/index.js +1708 -414
- package/package.json +35 -10
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +82 -5
- package/skills/bundle-analysis/SKILL.md +2 -2
- package/skills/cache-guide/SKILL.md +14 -9
- package/skills/caching/SKILL.md +221 -12
- package/skills/catalog.json +271 -0
- package/skills/comparison/SKILL.md +50 -0
- package/skills/comparison/agents/openai.yaml +4 -0
- package/skills/comparison/references/framework-comparison.md +837 -0
- package/skills/composability/SKILL.md +83 -2
- package/skills/css/SKILL.md +76 -0
- package/skills/debug-manifest/SKILL.md +5 -3
- package/skills/defer-hydration/SKILL.md +235 -0
- package/skills/document-cache/SKILL.md +11 -3
- package/skills/fonts/SKILL.md +1 -1
- package/skills/handler-use/SKILL.md +9 -9
- package/skills/hooks/SKILL.md +73 -900
- package/skills/hooks/data.md +273 -0
- package/skills/hooks/handle-and-actions.md +103 -0
- package/skills/hooks/navigation.md +110 -0
- package/skills/hooks/outlets.md +41 -0
- package/skills/hooks/state.md +228 -0
- package/skills/hooks/urls.md +135 -0
- package/skills/host-router/SKILL.md +84 -7
- package/skills/i18n/SKILL.md +1 -1
- package/skills/intercept/SKILL.md +51 -17
- package/skills/layout/SKILL.md +38 -16
- package/skills/links/SKILL.md +1 -1
- package/skills/loader/SKILL.md +48 -20
- package/skills/middleware/SKILL.md +11 -5
- package/skills/migrate-nextjs/SKILL.md +203 -20
- package/skills/migrate-react-router/SKILL.md +59 -675
- package/skills/migrate-react-router/cloudflare-workers.md +129 -0
- package/skills/migrate-react-router/component-migration.md +196 -0
- package/skills/migrate-react-router/data-and-actions.md +225 -0
- package/skills/migrate-react-router/route-mapping.md +271 -0
- package/skills/mime-routes/SKILL.md +3 -3
- package/skills/observability/SKILL.md +70 -5
- package/skills/parallel/SKILL.md +32 -8
- package/skills/ppr/SKILL.md +622 -0
- package/skills/prerender/SKILL.md +59 -28
- package/skills/rango/SKILL.md +124 -50
- package/skills/response-routes/SKILL.md +78 -46
- package/skills/route/SKILL.md +85 -6
- package/skills/router-setup/SKILL.md +41 -6
- package/skills/scripts/SKILL.md +179 -0
- package/skills/server-actions/SKILL.md +28 -3
- package/skills/shell-manifest/SKILL.md +185 -0
- package/skills/streams-and-websockets/SKILL.md +1 -1
- package/skills/tailwind/SKILL.md +28 -4
- package/skills/testing/SKILL.md +68 -654
- package/skills/testing/bindings.md +103 -0
- package/skills/testing/cache-prerender.md +127 -0
- package/skills/testing/client-components.md +124 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +91 -0
- package/skills/testing/handles.md +131 -0
- package/skills/testing/loader.md +128 -0
- package/skills/testing/middleware.md +99 -0
- package/skills/testing/render-handler.md +122 -0
- package/skills/testing/response-routes.md +95 -0
- package/skills/testing/reverse-and-types.md +85 -0
- package/skills/testing/server-actions.md +107 -0
- package/skills/testing/server-tree.md +128 -0
- package/skills/testing/setup.md +123 -0
- package/skills/theme/SKILL.md +1 -1
- package/skills/typesafety/SKILL.md +45 -918
- package/skills/typesafety/env-and-bindings.md +254 -0
- package/skills/typesafety/generated-files-and-cli.md +335 -0
- package/skills/typesafety/params-and-search.md +153 -0
- package/skills/typesafety/route-types.md +209 -0
- package/skills/use-cache/SKILL.md +47 -17
- package/skills/vercel/SKILL.md +128 -0
- package/skills/view-transitions/SKILL.md +44 -1
- package/src/__augment-tests__/augmented.check.ts +2 -3
- package/src/__internal.ts +0 -65
- package/src/browser/action-coordinator.ts +1 -1
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +14 -27
- package/src/browser/connection-warmup.ts +134 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +178 -100
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/logging.ts +28 -0
- package/src/browser/merge-segment-loaders.ts +6 -4
- package/src/browser/navigation-bridge.ts +81 -68
- package/src/browser/navigation-client.ts +115 -70
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +153 -88
- package/src/browser/navigation-transaction.ts +0 -32
- package/src/browser/network-error-handler.ts +34 -7
- package/src/browser/partial-update.ts +157 -144
- package/src/browser/prefetch/cache.ts +148 -81
- package/src/browser/prefetch/fetch.ts +231 -51
- package/src/browser/prefetch/queue.ts +25 -7
- package/src/browser/rango-state.ts +157 -115
- package/src/browser/react/Link.tsx +40 -7
- package/src/browser/react/NavigationProvider.tsx +140 -99
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/filter-segment-order.ts +17 -2
- package/src/browser/react/index.ts +0 -51
- package/src/browser/react/location-state-shared.ts +14 -15
- package/src/browser/react/location-state.ts +0 -1
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +0 -5
- package/src/browser/react/use-href.tsx +8 -1
- package/src/browser/react/use-link-status.ts +33 -8
- package/src/browser/react/use-navigation.ts +10 -5
- package/src/browser/react/use-params.ts +0 -2
- package/src/browser/react/use-router.ts +6 -4
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +0 -13
- package/src/browser/response-adapter.ts +74 -8
- package/src/browser/rsc-router.tsx +97 -22
- package/src/browser/scroll-restoration.ts +15 -8
- package/src/browser/segment-reconciler.ts +31 -21
- package/src/browser/server-action-bridge.ts +216 -38
- package/src/browser/types.ts +94 -22
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/generate-manifest.ts +155 -131
- package/src/build/generate-route-types.ts +1 -1
- package/src/build/index.ts +11 -5
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +152 -22
- package/src/build/route-types/ast-route-extraction.ts +15 -8
- package/src/build/route-types/codegen.ts +12 -1
- package/src/build/route-types/include-resolution.ts +455 -61
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/per-module-writer.ts +15 -2
- package/src/build/route-types/router-processing.ts +77 -41
- package/src/build/route-types/source-scan.ts +105 -7
- package/src/build/runtime-discovery.ts +4 -1
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-key-utils.ts +58 -13
- package/src/cache/cache-policy.ts +108 -34
- package/src/cache/cache-runtime.ts +454 -101
- package/src/cache/cache-scope.ts +159 -54
- package/src/cache/cache-tag.ts +149 -0
- package/src/cache/cf/cf-base64.ts +33 -0
- package/src/cache/cf/cf-cache-constants.ts +127 -0
- package/src/cache/cf/cf-cache-store.ts +2170 -377
- package/src/cache/cf/cf-cache-types.ts +349 -0
- package/src/cache/cf/cf-kv-utils.ts +46 -0
- package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
- package/src/cache/cf/index.ts +6 -16
- package/src/cache/document-cache.ts +126 -41
- package/src/cache/handle-snapshot.ts +70 -0
- package/src/cache/index.ts +23 -20
- package/src/cache/memory-segment-store.ts +243 -37
- package/src/cache/profile-registry.ts +46 -31
- package/src/cache/read-through-swr.ts +56 -12
- package/src/cache/segment-codec.ts +13 -21
- package/src/cache/shell-snapshot.ts +417 -0
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/types.ts +194 -99
- package/src/cache/vercel/index.ts +11 -0
- package/src/cache/vercel/vercel-cache-store.ts +1132 -0
- package/src/client.rsc.tsx +39 -22
- package/src/client.tsx +28 -58
- package/src/cloudflare/index.ts +11 -0
- package/src/cloudflare/tracing.ts +108 -0
- package/src/component-utils.ts +19 -0
- package/src/components/DefaultDocument.tsx +8 -2
- package/src/context-var.ts +13 -1
- package/src/decode-loader-results.ts +18 -2
- package/src/defer.ts +185 -0
- package/src/deps/ssr.ts +0 -1
- package/src/encode-kv.ts +49 -0
- package/src/errors.ts +0 -3
- package/src/escape-script.ts +52 -0
- package/src/handle.ts +57 -40
- package/src/handles/MetaTags.tsx +24 -53
- package/src/handles/Scripts.tsx +183 -0
- package/src/handles/breadcrumbs.ts +35 -8
- package/src/handles/deferred-resolution.ts +127 -0
- package/src/handles/is-thenable.ts +18 -0
- package/src/handles/meta.ts +14 -40
- package/src/handles/script.ts +244 -0
- package/src/host/cookie-handler.ts +9 -60
- package/src/host/errors.ts +13 -22
- package/src/host/index.ts +7 -0
- package/src/host/pattern-matcher.ts +23 -52
- package/src/host/router.ts +1 -65
- package/src/host/testing.ts +40 -27
- package/src/host/types.ts +6 -2
- package/src/href-client.ts +7 -12
- package/src/index.rsc.ts +88 -8
- package/src/index.ts +90 -16
- package/src/internal-debug.ts +11 -10
- package/src/loader.rsc.ts +19 -9
- package/src/loader.ts +12 -4
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +16 -16
- package/src/prerender/store.ts +32 -37
- package/src/prerender.ts +75 -7
- package/src/redirect-origin.ts +114 -0
- package/src/regex-escape.ts +8 -0
- package/src/render-error-thrower.tsx +20 -0
- package/src/response-utils.ts +25 -0
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +13 -49
- package/src/route-definition/dsl-helpers.ts +60 -53
- package/src/route-definition/helper-factories.ts +0 -2
- package/src/route-definition/helpers-types.ts +46 -46
- package/src/route-definition/index.ts +1 -2
- package/src/route-definition/redirect.ts +44 -11
- package/src/route-definition/resolve-handler-use.ts +6 -1
- package/src/route-definition/use-item-types.ts +3 -6
- package/src/route-map-builder.ts +41 -20
- package/src/route-types.ts +0 -5
- package/src/router/content-negotiation.ts +58 -23
- package/src/router/error-handling.ts +44 -17
- package/src/router/find-match.ts +129 -30
- package/src/router/handler-context.ts +6 -1
- package/src/router/instrument.ts +355 -0
- package/src/router/intercept-resolution.ts +35 -2
- package/src/router/lazy-includes.ts +79 -56
- package/src/router/loader-resolution.ts +151 -73
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +74 -40
- package/src/router/match-api.ts +76 -52
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +181 -178
- package/src/router/match-middleware/background-revalidation.ts +40 -24
- package/src/router/match-middleware/cache-lookup.ts +115 -194
- package/src/router/match-middleware/cache-store.ts +61 -50
- package/src/router/match-middleware/intercept-resolution.ts +0 -22
- package/src/router/match-middleware/segment-resolution.ts +0 -22
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +36 -67
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-types.ts +0 -116
- package/src/router/middleware.ts +231 -120
- package/src/router/navigation-snapshot.ts +7 -56
- package/src/router/params-util.ts +23 -0
- package/src/router/parse-pattern.ts +115 -0
- package/src/router/pattern-matching.ts +99 -152
- package/src/router/prefetch-cache-ttl.ts +51 -0
- package/src/router/prefetch-limits.ts +37 -0
- package/src/router/prerender-match.ts +111 -66
- package/src/router/preview-match.ts +3 -1
- package/src/router/request-classification.ts +47 -42
- package/src/router/revalidation.ts +75 -81
- package/src/router/route-snapshot.ts +14 -3
- package/src/router/router-context.ts +6 -29
- package/src/router/router-interfaces.ts +70 -8
- package/src/router/router-options.ts +126 -4
- package/src/router/segment-resolution/fresh.ts +104 -80
- package/src/router/segment-resolution/helpers.ts +86 -6
- package/src/router/segment-resolution/loader-cache.ts +155 -39
- package/src/router/segment-resolution/loader-mask.ts +60 -0
- package/src/router/segment-resolution/loader-snapshot.ts +259 -0
- package/src/router/segment-resolution/mask-nested.ts +83 -0
- package/src/router/segment-resolution/revalidation.ts +215 -304
- package/src/router/segment-resolution/static-store.ts +19 -5
- package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
- package/src/router/segment-resolution/view-transition-default.ts +35 -15
- package/src/router/segment-resolution.ts +5 -1
- package/src/router/segment-wrappers.ts +6 -5
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +54 -35
- package/src/router/telemetry-otel.ts +160 -200
- package/src/router/telemetry.ts +9 -23
- package/src/router/timeout.ts +0 -20
- package/src/router/tracing.ts +215 -0
- package/src/router/trie-matching.ts +171 -64
- package/src/router/types.ts +1 -63
- package/src/router/url-params.ts +13 -5
- package/src/router.ts +119 -48
- package/src/rsc/full-payload.ts +70 -0
- package/src/rsc/handler-context.ts +1 -0
- package/src/rsc/handler.ts +267 -152
- package/src/rsc/helpers.ts +78 -4
- package/src/rsc/index.ts +1 -4
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +114 -38
- package/src/rsc/manifest-init.ts +29 -42
- package/src/rsc/nonce.ts +10 -1
- package/src/rsc/origin-guard.ts +11 -15
- package/src/rsc/progressive-enhancement.ts +120 -13
- package/src/rsc/redirect-guard.ts +100 -0
- package/src/rsc/response-cache-serve.ts +238 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +58 -141
- package/src/rsc/rsc-rendering.ts +492 -49
- package/src/rsc/runtime-warnings.ts +14 -0
- package/src/rsc/server-action.ts +268 -82
- package/src/rsc/shell-capture.ts +1190 -0
- package/src/rsc/shell-serve.ts +181 -0
- package/src/rsc/transition-gate.ts +89 -0
- package/src/rsc/types.ts +45 -3
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +31 -26
- package/src/segment-loader-promise.ts +49 -4
- package/src/segment-system.tsx +260 -95
- package/src/server/context.ts +99 -9
- package/src/server/cookie-parse.ts +32 -0
- package/src/server/cookie-store.ts +125 -2
- package/src/server/handle-store.ts +21 -38
- package/src/server/loader-registry.ts +33 -42
- package/src/server/request-context.ts +379 -138
- package/src/ssr/index.tsx +491 -182
- package/src/ssr/inject-rsc-eager.ts +167 -0
- package/src/ssr/ssr-root.tsx +228 -0
- package/src/static-handler.ts +10 -13
- package/src/testing/cache-status.ts +44 -48
- package/src/testing/collect-handle.ts +14 -31
- package/src/testing/dispatch.ts +533 -160
- package/src/testing/e2e/fixture.ts +45 -11
- package/src/testing/e2e/index.ts +1 -22
- package/src/testing/e2e/matchers.ts +0 -16
- package/src/testing/e2e/parity.ts +85 -4
- package/src/testing/e2e/server.ts +12 -0
- package/src/testing/flight-matchers.ts +7 -14
- package/src/testing/flight-normalize.ts +11 -0
- package/src/testing/flight-runtime.d.ts +36 -0
- package/src/testing/flight-tree.ts +682 -0
- package/src/testing/flight.entry.ts +30 -0
- package/src/testing/flight.ts +145 -70
- package/src/testing/generated-routes.ts +26 -50
- package/src/testing/index.ts +18 -19
- package/src/testing/internal/context.ts +184 -68
- package/src/testing/internal/flight-client-globals.ts +30 -0
- package/src/testing/internal/seed-vars.ts +54 -0
- package/src/testing/render-handler.ts +357 -0
- package/src/testing/render-route.tsx +134 -115
- package/src/testing/run-loader.ts +140 -51
- package/src/testing/run-middleware.ts +59 -33
- package/src/testing/run-transition-when.ts +164 -0
- package/src/testing/vitest-stubs/cloudflare-email.ts +1 -1
- package/src/testing/vitest-stubs/cloudflare-workers.ts +1 -1
- package/src/testing/vitest.ts +138 -16
- package/src/theme/ThemeProvider.tsx +56 -84
- package/src/theme/ThemeScript.tsx +7 -9
- package/src/theme/constants.ts +52 -13
- package/src/theme/index.ts +0 -7
- package/src/theme/theme-context.ts +1 -5
- package/src/theme/theme-script.ts +22 -21
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +0 -35
- package/src/types/cache-types.ts +13 -4
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +15 -15
- package/src/types/handler-context.ts +45 -15
- package/src/types/index.ts +2 -10
- package/src/types/loader-types.ts +6 -3
- package/src/types/request-scope.ts +8 -22
- package/src/types/route-config.ts +20 -52
- package/src/types/route-entry.ts +0 -6
- package/src/types/segments.ts +100 -13
- package/src/urls/include-helper.ts +10 -12
- package/src/urls/include-provider.ts +71 -0
- package/src/urls/index.ts +2 -8
- package/src/urls/path-helper-types.ts +52 -14
- package/src/urls/path-helper.ts +5 -54
- package/src/urls/pattern-types.ts +36 -0
- package/src/urls/type-extraction.ts +76 -42
- package/src/urls/urls-function.ts +0 -14
- package/src/use-loader.tsx +0 -186
- package/src/vercel/index.ts +11 -0
- package/src/vercel/tracing.ts +88 -0
- package/src/vite/discovery/bundle-postprocess.ts +2 -1
- package/src/vite/discovery/dev-prerender-cache.ts +117 -0
- package/src/vite/discovery/discover-routers.ts +34 -43
- package/src/vite/discovery/discovery-errors.ts +61 -0
- package/src/vite/discovery/prerender-collection.ts +33 -46
- package/src/vite/discovery/state.ts +12 -1
- package/src/vite/discovery/virtual-module-codegen.ts +1 -11
- package/src/vite/index.ts +9 -0
- package/src/vite/inject-client-debug.ts +88 -0
- package/src/vite/plugin-types.ts +143 -10
- package/src/vite/plugins/cjs-to-esm.ts +8 -12
- package/src/vite/plugins/client-ref-dedup.ts +0 -11
- package/src/vite/plugins/client-ref-hashing.ts +0 -10
- package/src/vite/plugins/cloudflare-protocol-stub.ts +0 -20
- package/src/vite/plugins/expose-action-id.ts +2 -73
- package/src/vite/plugins/expose-id-utils.ts +85 -56
- package/src/vite/plugins/expose-ids/export-analysis.ts +30 -43
- package/src/vite/plugins/expose-ids/handler-transform.ts +5 -31
- package/src/vite/plugins/expose-ids/loader-transform.ts +12 -20
- package/src/vite/plugins/expose-ids/router-transform.ts +98 -26
- package/src/vite/plugins/expose-internal-ids.ts +10 -1
- package/src/vite/plugins/performance-tracks.ts +0 -3
- package/src/vite/plugins/refresh-cmd.ts +1 -1
- package/src/vite/plugins/use-cache-transform.ts +21 -46
- package/src/vite/plugins/vercel-output.ts +384 -0
- package/src/vite/plugins/version-injector.ts +22 -27
- package/src/vite/plugins/version-plugin.ts +6 -66
- package/src/vite/plugins/virtual-entries.ts +137 -26
- package/src/vite/rango.ts +146 -135
- package/src/vite/router-discovery.ts +189 -48
- package/src/vite/utils/ast-handler-extract.ts +11 -20
- package/src/vite/utils/bundle-analysis.ts +6 -13
- package/src/vite/utils/client-chunks.ts +0 -6
- package/src/vite/utils/directive-prologue.ts +40 -0
- package/src/vite/utils/forward-user-plugins.ts +0 -22
- package/src/vite/utils/manifest-utils.ts +4 -75
- package/src/vite/utils/package-resolution.ts +1 -73
- package/src/vite/utils/prerender-utils.ts +71 -44
- package/src/vite/utils/shared-utils.ts +55 -37
- package/src/browser/react/use-client-cache.ts +0 -58
- package/src/browser/shallow.ts +0 -40
- package/src/handles/index.ts +0 -7
- package/src/network-error-thrower.tsx +0 -23
- package/src/router/middleware-cookies.ts +0 -55
|
@@ -17,8 +17,10 @@ import {
|
|
|
17
17
|
type EntryPropSegments,
|
|
18
18
|
type HelperContext,
|
|
19
19
|
type InterceptEntry,
|
|
20
|
+
type InterceptConfig,
|
|
20
21
|
} from "../server/context";
|
|
21
22
|
import { invariant } from "../errors";
|
|
23
|
+
import { validateUserRouteName } from "../route-name.js";
|
|
22
24
|
import { isCachedFunction } from "../cache/taint.js";
|
|
23
25
|
import { RangoContext } from "../server/context";
|
|
24
26
|
import { isStaticHandler } from "../static-handler.js";
|
|
@@ -35,7 +37,6 @@ import type {
|
|
|
35
37
|
ErrorBoundaryItem,
|
|
36
38
|
NotFoundBoundaryItem,
|
|
37
39
|
LayoutItem,
|
|
38
|
-
WhenItem,
|
|
39
40
|
CacheItem,
|
|
40
41
|
TransitionItem,
|
|
41
42
|
UseItems,
|
|
@@ -264,34 +265,6 @@ const notFoundBoundary: RouteHelpers<any, any>["notFoundBoundary"] = (
|
|
|
264
265
|
return { name, type: "notFoundBoundary" } as NotFoundBoundaryItem;
|
|
265
266
|
};
|
|
266
267
|
|
|
267
|
-
/**
|
|
268
|
-
* When helper - defines a condition for intercept activation
|
|
269
|
-
*
|
|
270
|
-
* Only valid inside intercept() use() callback. The when() function
|
|
271
|
-
* is captured by the intercept and stored in its `when` array.
|
|
272
|
-
* During soft navigation, all when() conditions must return true
|
|
273
|
-
* for the intercept to activate.
|
|
274
|
-
*/
|
|
275
|
-
const when: RouteHelpers<any, any>["when"] = (fn) => {
|
|
276
|
-
const { store, ctx } = requireDslContext(
|
|
277
|
-
"when() must be called inside intercept()",
|
|
278
|
-
);
|
|
279
|
-
|
|
280
|
-
// The when() function needs to be captured by the intercept's tempParent
|
|
281
|
-
// which should have a `when` array. If not present, we're not inside intercept()
|
|
282
|
-
const parent = ctx.parent as any;
|
|
283
|
-
if (!parent || !("when" in parent)) {
|
|
284
|
-
invariant(
|
|
285
|
-
false,
|
|
286
|
-
"when() can only be used inside intercept() use() callback",
|
|
287
|
-
);
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
const name = `$${store.getNextIndex("when")}`;
|
|
291
|
-
parent.when.push(fn);
|
|
292
|
-
return { name, type: "when" } as WhenItem;
|
|
293
|
-
};
|
|
294
|
-
|
|
295
268
|
/**
|
|
296
269
|
* Cache helper - defines caching configuration for segments
|
|
297
270
|
*
|
|
@@ -302,15 +275,15 @@ const when: RouteHelpers<any, any>["when"] = (fn) => {
|
|
|
302
275
|
* Supports these call signatures:
|
|
303
276
|
* - cache() - no args, uses app-level defaults (for loader caching)
|
|
304
277
|
* - cache(() => [...]) - wraps children with app-level defaults
|
|
305
|
-
* - cache('profileName') - uses a named cache profile
|
|
306
|
-
* - cache('profileName', () => [...]) - named profile with children
|
|
307
278
|
* - cache({ ttl: 60 }, () => [...]) - with explicit options
|
|
279
|
+
*
|
|
280
|
+
* Named cache profiles are applied via the `"use cache: <profile>"` directive,
|
|
281
|
+
* not a `cache("profileName")` form in the route tree.
|
|
308
282
|
*/
|
|
309
283
|
const cache: RouteHelpers<any, any>["cache"] = (
|
|
310
284
|
optionsOrChildren?:
|
|
311
285
|
| PartialCacheOptions
|
|
312
286
|
| false
|
|
313
|
-
| string
|
|
314
287
|
| (() => UseItems<AllUseItems>),
|
|
315
288
|
maybeChildren?: () => UseItems<AllUseItems>,
|
|
316
289
|
) => {
|
|
@@ -326,18 +299,6 @@ const cache: RouteHelpers<any, any>["cache"] = (
|
|
|
326
299
|
// cache() - no args, use defaults
|
|
327
300
|
options = {};
|
|
328
301
|
children = undefined;
|
|
329
|
-
} else if (typeof optionsOrChildren === "string") {
|
|
330
|
-
// cache('profileName') or cache('profileName', () => [...])
|
|
331
|
-
// Resolve from context-scoped profiles (set per-router via HelperContext).
|
|
332
|
-
const ctxStore = RangoContext.getStore();
|
|
333
|
-
const profile = ctxStore?.cacheProfiles?.[optionsOrChildren];
|
|
334
|
-
invariant(
|
|
335
|
-
profile,
|
|
336
|
-
`cache("${optionsOrChildren}"): unknown cache profile. ` +
|
|
337
|
-
`Define it in createRouter({ cacheProfiles: { "${optionsOrChildren}": { ttl: ... } } }).`,
|
|
338
|
-
);
|
|
339
|
-
options = { ttl: profile.ttl, swr: profile.swr, tags: profile.tags };
|
|
340
|
-
children = maybeChildren;
|
|
341
302
|
} else if (typeof optionsOrChildren === "function") {
|
|
342
303
|
// cache(() => [...]) - use empty options (will use defaults)
|
|
343
304
|
options = {};
|
|
@@ -393,10 +354,10 @@ const cache: RouteHelpers<any, any>["cache"] = (
|
|
|
393
354
|
return { name: namespace, type: "cache" } as CacheItem;
|
|
394
355
|
}
|
|
395
356
|
|
|
396
|
-
// Inside a loader() use() callback, only the direct form — cache()/cache(opts)
|
|
397
|
-
//
|
|
398
|
-
//
|
|
399
|
-
//
|
|
357
|
+
// Inside a loader() use() callback, only the direct form — cache()/cache(opts)
|
|
358
|
+
// — writes cache config to the loader entry. The wrapper form creates a
|
|
359
|
+
// structural cache boundary with its own children scope, which has no effect
|
|
360
|
+
// on the loader and would silently no-op.
|
|
400
361
|
invariant(
|
|
401
362
|
!(ctx.parent && (ctx.parent as any).type === "loader"),
|
|
402
363
|
"cache() wrapper form is not valid inside loader() use(). Use cache({...}) without children to configure the loader's cache.",
|
|
@@ -536,6 +497,22 @@ const middleware: RouteHelpers<any, any>["middleware"] = (...args: any[]) => {
|
|
|
536
497
|
} as MiddlewareItem;
|
|
537
498
|
};
|
|
538
499
|
|
|
500
|
+
// Slot names become part of segment ids: a parallel/intercept slot is encoded
|
|
501
|
+
// as `${shortCode}.${slotName}`, and loader segments append `D${index}.${loaderId}`.
|
|
502
|
+
// A "." in the slot name collides with that separator -- loaderParentId
|
|
503
|
+
// (segment-system.tsx) strips from the FIRST `D<index>.`, so a name like
|
|
504
|
+
// "@D3.foo" is mis-cut to "@" and the loader's data is silently dropped. Reject
|
|
505
|
+
// the dot at definition time so the failure is loud, not a corrupted tree at
|
|
506
|
+
// runtime. (A bare "D" without a trailing dot -- e.g. "@Detail" -- is fine.)
|
|
507
|
+
function assertValidSlotName(slotName: string): void {
|
|
508
|
+
invariant(
|
|
509
|
+
!slotName.includes("."),
|
|
510
|
+
`Slot name "${slotName}" must not contain ".". The dot is a reserved ` +
|
|
511
|
+
`segment-id separator; a name like "@D3.foo" corrupts loader segment-id ` +
|
|
512
|
+
`parsing and silently drops the loader's data. Rename the slot.`,
|
|
513
|
+
);
|
|
514
|
+
}
|
|
515
|
+
|
|
539
516
|
const parallel: RouteHelpers<any, any>["parallel"] = (slots, use) => {
|
|
540
517
|
const { store, ctx } = requireDslContext(
|
|
541
518
|
"parallel() must be called inside urls()",
|
|
@@ -551,6 +528,7 @@ const parallel: RouteHelpers<any, any>["parallel"] = (slots, use) => {
|
|
|
551
528
|
);
|
|
552
529
|
|
|
553
530
|
const slotNames = Object.keys(slots as Record<string, any>) as `@${string}`[];
|
|
531
|
+
for (const slotName of slotNames) assertValidSlotName(slotName);
|
|
554
532
|
|
|
555
533
|
const namespace = `${ctx.namespace}.$${store.getNextIndex("parallel")}`;
|
|
556
534
|
|
|
@@ -695,8 +673,19 @@ const intercept = (
|
|
|
695
673
|
slotName: `@${string}`,
|
|
696
674
|
routeName: string,
|
|
697
675
|
handler: any,
|
|
676
|
+
configOrUse?: InterceptConfig | (() => any[]),
|
|
698
677
|
use?: () => any[],
|
|
699
678
|
) => {
|
|
679
|
+
// arg4 discrimination: a function is the use() callback (no config); an object
|
|
680
|
+
// is the config carrying `when`. With config given, the use() callback is
|
|
681
|
+
// arg5. Keeps the no-config form intercept(slot, route, handler, () => [...])
|
|
682
|
+
// working unchanged.
|
|
683
|
+
const config: InterceptConfig | undefined =
|
|
684
|
+
typeof configOrUse === "function" || configOrUse == null
|
|
685
|
+
? undefined
|
|
686
|
+
: configOrUse;
|
|
687
|
+
const useFn = typeof configOrUse === "function" ? configOrUse : use;
|
|
688
|
+
|
|
700
689
|
const { store, ctx } = requireDslContext(
|
|
701
690
|
"intercept() must be called inside urls()",
|
|
702
691
|
);
|
|
@@ -710,6 +699,8 @@ const intercept = (
|
|
|
710
699
|
"intercept() cannot be used inside parallel()",
|
|
711
700
|
);
|
|
712
701
|
|
|
702
|
+
assertValidSlotName(slotName);
|
|
703
|
+
|
|
713
704
|
const namespace = `${ctx.namespace}.$${store.getNextIndex("intercept")}.${slotName}`;
|
|
714
705
|
|
|
715
706
|
// Dot-prefixed = local (add include prefix), unprefixed = global (use as-is)
|
|
@@ -732,9 +723,17 @@ const intercept = (
|
|
|
732
723
|
when: [], // Selector conditions for conditional interception
|
|
733
724
|
};
|
|
734
725
|
|
|
726
|
+
// Conditional interception: `when` from the config object — a single selector
|
|
727
|
+
// or an array (ALL must return true to activate). Replaces the former when()
|
|
728
|
+
// use-item captured inside the callback.
|
|
729
|
+
if (config?.when) {
|
|
730
|
+
const selectors = Array.isArray(config.when) ? config.when : [config.when];
|
|
731
|
+
entry.when.push(...selectors);
|
|
732
|
+
}
|
|
733
|
+
|
|
735
734
|
// Merge handler.use defaults with explicit use
|
|
736
735
|
const handlerUseFn = resolveHandlerUse(handler);
|
|
737
|
-
const mergedUse = mergeHandlerUse(handlerUseFn,
|
|
736
|
+
const mergedUse = mergeHandlerUse(handlerUseFn, useFn, "intercept");
|
|
738
737
|
|
|
739
738
|
// Run merged use callback to collect loaders, revalidate, middleware, etc.
|
|
740
739
|
if (mergedUse) {
|
|
@@ -751,7 +750,6 @@ const intercept = (
|
|
|
751
750
|
notFoundBoundary: entry.notFoundBoundary,
|
|
752
751
|
loader: entry.loader,
|
|
753
752
|
layout: capturedLayouts, // Capture layout() calls
|
|
754
|
-
when: entry.when, // Capture when() conditions
|
|
755
753
|
get loading() {
|
|
756
754
|
return entry.loading;
|
|
757
755
|
},
|
|
@@ -890,7 +888,11 @@ const transition = (
|
|
|
890
888
|
"transition() must be called inside urls()",
|
|
891
889
|
);
|
|
892
890
|
|
|
893
|
-
|
|
891
|
+
// Allocate a single index for this transition() call (used in all paths),
|
|
892
|
+
// mirroring cache() — the child form uses it for the name, the wrapper form
|
|
893
|
+
// reuses it for the namespace, so no index is burned.
|
|
894
|
+
const transitionIndex = store.getNextIndex("transition");
|
|
895
|
+
const name = `$${transitionIndex}`;
|
|
894
896
|
|
|
895
897
|
if (!children) {
|
|
896
898
|
// Position 1: child of path() — attach to parent entry
|
|
@@ -903,7 +905,7 @@ const transition = (
|
|
|
903
905
|
}
|
|
904
906
|
|
|
905
907
|
// Position 2: wrapper — create a transparent layout with transition config
|
|
906
|
-
const namespace = `${ctx.namespace}.${
|
|
908
|
+
const namespace = `${ctx.namespace}.${transitionIndex}`;
|
|
907
909
|
const entry = {
|
|
908
910
|
...emptySegmentBase(),
|
|
909
911
|
id: namespace,
|
|
@@ -933,6 +935,12 @@ const route: RouteHelpers<any, any>["route"] = (name, handler, use) => {
|
|
|
933
935
|
"route() must be called inside urls()",
|
|
934
936
|
);
|
|
935
937
|
|
|
938
|
+
// Reject names colliding with reserved internal prefixes ($path_, $prefix_),
|
|
939
|
+
// the same guard path() and include() enforce. Without it such a name
|
|
940
|
+
// type-checks on an untyped router, then silently vanishes from generated
|
|
941
|
+
// route-types and public reverse() (isAutoGeneratedRouteName filters it out).
|
|
942
|
+
validateUserRouteName(name);
|
|
943
|
+
|
|
936
944
|
const namespace = `${ctx.namespace}.${store.getNextIndex("route")}.${name}`;
|
|
937
945
|
|
|
938
946
|
const entry = {
|
|
@@ -1096,7 +1104,6 @@ export {
|
|
|
1096
1104
|
revalidate,
|
|
1097
1105
|
parallel,
|
|
1098
1106
|
intercept,
|
|
1099
|
-
when,
|
|
1100
1107
|
errorBoundary,
|
|
1101
1108
|
notFoundBoundary,
|
|
1102
1109
|
route,
|
|
@@ -8,7 +8,6 @@ import {
|
|
|
8
8
|
revalidate,
|
|
9
9
|
parallel,
|
|
10
10
|
intercept,
|
|
11
|
-
when,
|
|
12
11
|
errorBoundary,
|
|
13
12
|
notFoundBoundary,
|
|
14
13
|
route,
|
|
@@ -40,7 +39,6 @@ function buildRouteHelpers<T extends RouteDefinition, TEnv>(): RouteHelpers<
|
|
|
40
39
|
loading,
|
|
41
40
|
errorBoundary,
|
|
42
41
|
notFoundBoundary,
|
|
43
|
-
when,
|
|
44
42
|
cache,
|
|
45
43
|
transition,
|
|
46
44
|
} as unknown as RouteHelpers<T, TEnv>;
|
|
@@ -29,12 +29,10 @@ import type {
|
|
|
29
29
|
ParallelUseItem,
|
|
30
30
|
InterceptUseItem,
|
|
31
31
|
LoaderUseItem,
|
|
32
|
-
WhenItem,
|
|
33
32
|
CacheItem,
|
|
34
33
|
TransitionItem,
|
|
35
34
|
UseItems,
|
|
36
35
|
} from "../route-types.js";
|
|
37
|
-
import type { InterceptWhenFn } from "../server/context";
|
|
38
36
|
|
|
39
37
|
// Re-export route item types for backward compatibility
|
|
40
38
|
export type {
|
|
@@ -52,12 +50,12 @@ export type {
|
|
|
52
50
|
RouteUseItem,
|
|
53
51
|
ParallelUseItem,
|
|
54
52
|
InterceptUseItem,
|
|
55
|
-
WhenItem,
|
|
56
53
|
CacheItem,
|
|
57
54
|
} from "../route-types.js";
|
|
58
55
|
|
|
59
56
|
// Re-export intercept selector types for use in handlers
|
|
60
57
|
export type {
|
|
58
|
+
InterceptConfig,
|
|
61
59
|
InterceptSelectorContext,
|
|
62
60
|
InterceptSegmentsState,
|
|
63
61
|
InterceptWhenFn,
|
|
@@ -151,8 +149,11 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
|
|
|
151
149
|
* so they take precedence on `loading()` and other last-write-wins
|
|
152
150
|
* fields.
|
|
153
151
|
*/
|
|
154
|
-
|
|
155
|
-
|
|
152
|
+
// Not generic over the slots record: an inferred type parameter makes the
|
|
153
|
+
// object literal an inference site, which suppresses contextual typing of
|
|
154
|
+
// arrow slot handlers (`(ctx) => ...` was implicit any).
|
|
155
|
+
parallel: (
|
|
156
|
+
slots: Record<
|
|
156
157
|
`@${string}`,
|
|
157
158
|
| Handler<any, any, TEnv>
|
|
158
159
|
| ReactNode
|
|
@@ -161,8 +162,6 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
|
|
|
161
162
|
use?: () => UseItems<ParallelUseItem>;
|
|
162
163
|
}
|
|
163
164
|
>,
|
|
164
|
-
>(
|
|
165
|
-
slots: TSlots,
|
|
166
165
|
use?: () => UseItems<ParallelUseItem>,
|
|
167
166
|
) => ParallelItem;
|
|
168
167
|
/**
|
|
@@ -183,10 +182,26 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
|
|
|
183
182
|
* loader(CardModalLoader),
|
|
184
183
|
* revalidate(() => false),
|
|
185
184
|
* ])
|
|
185
|
+
*
|
|
186
|
+
* // Conditional activation via the config object's `when` selector
|
|
187
|
+
* intercept("@modal", "card", <CardModal />, {
|
|
188
|
+
* when: ({ from }) => from.pathname.startsWith("/board"),
|
|
189
|
+
* })
|
|
190
|
+
*
|
|
191
|
+
* // Config + other use-items: config is arg 4, use is arg 5
|
|
192
|
+
* intercept(
|
|
193
|
+
* "@modal",
|
|
194
|
+
* "card",
|
|
195
|
+
* <CardModal />,
|
|
196
|
+
* { when: ({ from }) => from.pathname.startsWith("/board") },
|
|
197
|
+
* () => [loader(CardDetailLoader)],
|
|
198
|
+
* )
|
|
186
199
|
* ```
|
|
187
200
|
* @param slotName - Named slot (prefixed with @) where intercept renders
|
|
188
201
|
* @param routeName - Route name to intercept
|
|
189
202
|
* @param handler - Component or handler for intercepted render
|
|
203
|
+
* @param config - Optional InterceptConfig (e.g. `{ when }`), or the use
|
|
204
|
+
* callback directly when there is no config
|
|
190
205
|
* @param use - Optional callback for loaders, middleware, revalidate, etc.
|
|
191
206
|
*/
|
|
192
207
|
intercept: {
|
|
@@ -195,6 +210,9 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
|
|
|
195
210
|
slotName: `@${string}`,
|
|
196
211
|
routeName: `.${K}`,
|
|
197
212
|
handler: ReactNode | Handler<ExtractRouteParams<T, K>, {}, TEnv>,
|
|
213
|
+
config?:
|
|
214
|
+
| import("../server/context.js").InterceptConfig<TEnv>
|
|
215
|
+
| (() => UseItems<InterceptUseItem>),
|
|
198
216
|
use?: () => UseItems<InterceptUseItem>,
|
|
199
217
|
): InterceptItem;
|
|
200
218
|
// Global: unprefixed, params inferred from global route map
|
|
@@ -202,6 +220,9 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
|
|
|
202
220
|
slotName: `@${string}`,
|
|
203
221
|
routeName: K,
|
|
204
222
|
handler: ReactNode | Handler<K, Rango.GeneratedRouteMap, TEnv>,
|
|
223
|
+
config?:
|
|
224
|
+
| import("../server/context.js").InterceptConfig<TEnv>
|
|
225
|
+
| (() => UseItems<InterceptUseItem>),
|
|
205
226
|
use?: () => UseItems<InterceptUseItem>,
|
|
206
227
|
): InterceptItem;
|
|
207
228
|
};
|
|
@@ -347,40 +368,6 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
|
|
|
347
368
|
notFoundBoundary: (
|
|
348
369
|
fallback: ReactNode | NotFoundBoundaryHandler,
|
|
349
370
|
) => NotFoundBoundaryItem;
|
|
350
|
-
/**
|
|
351
|
-
* Define a condition for when an intercept should activate
|
|
352
|
-
*
|
|
353
|
-
* Only valid inside intercept() use() callback. When multiple when() calls
|
|
354
|
-
* are present, ALL must return true for the intercept to activate.
|
|
355
|
-
* If no when() is defined, the intercept always activates on soft navigation.
|
|
356
|
-
*
|
|
357
|
-
* Context properties:
|
|
358
|
-
* - `from` - Source URL (where user is navigating from)
|
|
359
|
-
* - `to` - Destination URL (where user is navigating to)
|
|
360
|
-
* - `params` - Matched route params
|
|
361
|
-
* - `segments` - Client's current segments with `path` and `ids`
|
|
362
|
-
*
|
|
363
|
-
* ```typescript
|
|
364
|
-
* // Only intercept when coming from the board page
|
|
365
|
-
* intercept("@modal", "card", <CardModal />, () => [
|
|
366
|
-
* when(({ from }) => from.pathname.startsWith("/board")),
|
|
367
|
-
* loader(CardDetailLoader),
|
|
368
|
-
* ])
|
|
369
|
-
*
|
|
370
|
-
* // Use segments to check current route context
|
|
371
|
-
* intercept("@modal", "card", <CardModal />, () => [
|
|
372
|
-
* when(({ segments }) => segments.path[0] === "kanban"),
|
|
373
|
-
* ])
|
|
374
|
-
*
|
|
375
|
-
* // Multiple conditions (AND logic)
|
|
376
|
-
* intercept("@modal", "card", <CardModal />, () => [
|
|
377
|
-
* when(({ from }) => from.pathname.startsWith("/board")),
|
|
378
|
-
* when(({ segments }) => segments.ids.includes("kanban-layout")),
|
|
379
|
-
* ])
|
|
380
|
-
* ```
|
|
381
|
-
* @param fn - Selector function receiving navigation context, returns boolean
|
|
382
|
-
*/
|
|
383
|
-
when: (fn: InterceptWhenFn) => WhenItem;
|
|
384
371
|
/**
|
|
385
372
|
* Define cache configuration for segments
|
|
386
373
|
*
|
|
@@ -441,10 +428,8 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
|
|
|
441
428
|
cache: {
|
|
442
429
|
(): CacheItem;
|
|
443
430
|
(children: () => UseItems<AllUseItems>): CacheItem;
|
|
444
|
-
(profileName: string): CacheItem;
|
|
445
|
-
(profileName: string, use: () => UseItems<AllUseItems>): CacheItem;
|
|
446
431
|
(
|
|
447
|
-
options: PartialCacheOptions | false,
|
|
432
|
+
options: PartialCacheOptions<TEnv> | false,
|
|
448
433
|
use?: () => UseItems<AllUseItems>,
|
|
449
434
|
): CacheItem;
|
|
450
435
|
};
|
|
@@ -474,6 +459,13 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
|
|
|
474
459
|
* transition({}) is startTransition + ViewTransition under the default and
|
|
475
460
|
* startTransition only when the router sets viewTransition: false.
|
|
476
461
|
*
|
|
462
|
+
* Conditional hold: pass `when: (ctx) => boolean` to gate the transition per
|
|
463
|
+
* request. It runs server-side AFTER the route handler (so it can read state
|
|
464
|
+
* the handler set via `ctx.get(...)`); returning false drops this transition
|
|
465
|
+
* for the request, so the navigation streams its loading() skeleton instead of
|
|
466
|
+
* holding. This is a post-handler predicate — distinct from intercept()'s
|
|
467
|
+
* match-time `when` config selector (`intercept(slot, route, Comp, { when })`).
|
|
468
|
+
*
|
|
477
469
|
* ```typescript
|
|
478
470
|
* // Attach to a single route
|
|
479
471
|
* path("/about", AboutPage, { name: "about" }, () => [
|
|
@@ -490,13 +482,21 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
|
|
|
490
482
|
* path("/product/:id", ProductPage, { name: "product" }, () => [
|
|
491
483
|
* transition({ viewTransition: false }),
|
|
492
484
|
* ])
|
|
485
|
+
*
|
|
486
|
+
* // Hold only when the handler decided to (post-handler predicate):
|
|
487
|
+
* path("/product/:id", ProductPage, { name: "product" }, () => [
|
|
488
|
+
* transition({ when: (ctx) => ctx.get(KeepScroll) === true }),
|
|
489
|
+
* ])
|
|
493
490
|
* ```
|
|
494
491
|
* @param config - ViewTransition configuration (enter, exit, update, share,
|
|
495
|
-
* default, name)
|
|
496
|
-
* boundary (createRouter({ viewTransition }) sets the app-wide default)
|
|
492
|
+
* default, name), `viewTransition: "auto" | false` to toggle the router
|
|
493
|
+
* boundary (createRouter({ viewTransition }) sets the app-wide default), and
|
|
494
|
+
* `when: (ctx) => boolean` to gate the transition per request post-handler
|
|
497
495
|
* @param children - Optional callback returning child routes to wrap
|
|
498
496
|
*/
|
|
499
497
|
transition: {
|
|
498
|
+
(): TransitionItem;
|
|
499
|
+
(children: () => UseItems<AllUseItems>): TransitionItem;
|
|
500
500
|
(config: TransitionConfig): TransitionItem;
|
|
501
501
|
(
|
|
502
502
|
config: TransitionConfig,
|
|
@@ -15,8 +15,8 @@ export type {
|
|
|
15
15
|
RouteUseItem,
|
|
16
16
|
ParallelUseItem,
|
|
17
17
|
InterceptUseItem,
|
|
18
|
-
WhenItem,
|
|
19
18
|
CacheItem,
|
|
19
|
+
InterceptConfig,
|
|
20
20
|
InterceptSelectorContext,
|
|
21
21
|
InterceptSegmentsState,
|
|
22
22
|
InterceptWhenFn,
|
|
@@ -30,7 +30,6 @@ export {
|
|
|
30
30
|
revalidate,
|
|
31
31
|
parallel,
|
|
32
32
|
intercept,
|
|
33
|
-
when,
|
|
34
33
|
errorBoundary,
|
|
35
34
|
notFoundBoundary,
|
|
36
35
|
loader,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { LocationStateEntry } from "../browser/react/location-state-shared.js";
|
|
2
2
|
import {
|
|
3
|
-
requireRequestContext,
|
|
4
3
|
getRequestContext,
|
|
5
4
|
_getRequestContext,
|
|
6
5
|
} from "../server/request-context.js";
|
|
6
|
+
import { markExternalRedirect } from "../redirect-origin.js";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Create a soft redirect Response for middleware short-circuit
|
|
@@ -39,6 +39,11 @@ import {
|
|
|
39
39
|
* status: 303,
|
|
40
40
|
* state: [Flash({ text: "Session expired" })],
|
|
41
41
|
* });
|
|
42
|
+
*
|
|
43
|
+
* // Off-host redirect (opt out of the same-origin guard). Without
|
|
44
|
+
* // `external: true`, a cross-origin target is blocked and replaced with the
|
|
45
|
+
* // app root, matching the client's open-redirect protection.
|
|
46
|
+
* return redirect('https://accounts.example.com/oauth', { external: true });
|
|
42
47
|
* ```
|
|
43
48
|
*/
|
|
44
49
|
export function redirect(url: string, status?: number): Response;
|
|
@@ -47,13 +52,18 @@ export function redirect(
|
|
|
47
52
|
options: {
|
|
48
53
|
status?: number;
|
|
49
54
|
state?: LocationStateEntry | LocationStateEntry[];
|
|
55
|
+
external?: boolean;
|
|
50
56
|
},
|
|
51
57
|
): Response;
|
|
52
58
|
export function redirect(
|
|
53
59
|
url: string,
|
|
54
60
|
statusOrOptions?:
|
|
55
61
|
| number
|
|
56
|
-
| {
|
|
62
|
+
| {
|
|
63
|
+
status?: number;
|
|
64
|
+
state?: LocationStateEntry | LocationStateEntry[];
|
|
65
|
+
external?: boolean;
|
|
66
|
+
},
|
|
57
67
|
): Response {
|
|
58
68
|
const status =
|
|
59
69
|
typeof statusOrOptions === "number"
|
|
@@ -61,9 +71,11 @@ export function redirect(
|
|
|
61
71
|
: (statusOrOptions?.status ?? 302);
|
|
62
72
|
const state =
|
|
63
73
|
typeof statusOrOptions === "object" ? statusOrOptions?.state : undefined;
|
|
74
|
+
const external =
|
|
75
|
+
typeof statusOrOptions === "object" ? statusOrOptions?.external : undefined;
|
|
64
76
|
|
|
65
77
|
if (state) {
|
|
66
|
-
const ctx =
|
|
78
|
+
const ctx = getRequestContext();
|
|
67
79
|
ctx.setLocationState(state);
|
|
68
80
|
|
|
69
81
|
if (process.env.NODE_ENV !== "production") {
|
|
@@ -85,17 +97,38 @@ export function redirect(
|
|
|
85
97
|
}
|
|
86
98
|
|
|
87
99
|
// Auto-prefix root-relative URLs with basename for app-local redirects.
|
|
100
|
+
// Treat the URL as already-prefixed when the basename is followed by a path
|
|
101
|
+
// separator, a query, a fragment, or end-of-string, so "/admin?tab=x" and
|
|
102
|
+
// "/admin#frag" are not double-prefixed into "/admin/admin?tab=x".
|
|
88
103
|
const bn = _getRequestContext()?._basename;
|
|
89
104
|
let resolvedUrl = url;
|
|
90
|
-
if (
|
|
105
|
+
if (
|
|
106
|
+
bn &&
|
|
107
|
+
url.startsWith("/") &&
|
|
108
|
+
url !== bn &&
|
|
109
|
+
!url.startsWith(bn + "/") &&
|
|
110
|
+
!url.startsWith(bn + "?") &&
|
|
111
|
+
!url.startsWith(bn + "#")
|
|
112
|
+
) {
|
|
91
113
|
resolvedUrl = url === "/" ? bn : bn + url;
|
|
92
114
|
}
|
|
93
115
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
116
|
+
const headers: Record<string, string> = {
|
|
117
|
+
Location: resolvedUrl,
|
|
118
|
+
"X-RSC-Redirect": "soft",
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const response = new Response(null, { status, headers });
|
|
122
|
+
|
|
123
|
+
// Mark an explicit off-host redirect with an out-of-band brand so the
|
|
124
|
+
// same-origin guard (rsc/redirect-guard.ts) lets it through. The brand is a
|
|
125
|
+
// WeakSet membership on this Response object -- NOT a wire header -- so the
|
|
126
|
+
// opt-in cannot be forged by an attacker-controlled upstream response a
|
|
127
|
+
// proxy-style response route might copy. The internal redirect-rebuild paths
|
|
128
|
+
// transfer the brand; the guard reads and clears it (see markExternalRedirect).
|
|
129
|
+
if (external) {
|
|
130
|
+
markExternalRedirect(response);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return response;
|
|
101
134
|
}
|
|
@@ -100,7 +100,6 @@ const MOUNT_SITE_ALLOWED_TYPES: Record<string, Set<string>> = {
|
|
|
100
100
|
"notFoundBoundary",
|
|
101
101
|
"layout",
|
|
102
102
|
"route",
|
|
103
|
-
"when",
|
|
104
103
|
"transition",
|
|
105
104
|
]),
|
|
106
105
|
// LoaderUseItem — only revalidate + cache can attach to a loader entry
|
|
@@ -139,6 +138,12 @@ export function mergeHandlerUse(
|
|
|
139
138
|
mountSite: string,
|
|
140
139
|
): (() => any[]) | undefined {
|
|
141
140
|
if (!handlerUse && !explicitUse) return undefined;
|
|
141
|
+
// Validation asymmetry (intentional, pre-1.0): only handler.use() items are
|
|
142
|
+
// checked against the mount-site allow-list (validateHandlerUseItems below).
|
|
143
|
+
// Explicit use() items pass through unvalidated on both the explicit-only
|
|
144
|
+
// branch here and the merged branch, so a structurally-valid-but-prohibited
|
|
145
|
+
// item (e.g. middleware() inside a parallel slot) is not rejected at this seam.
|
|
146
|
+
// Documented rather than enforced for now; revisit before 1.0 (#569).
|
|
142
147
|
if (!handlerUse) return explicitUse;
|
|
143
148
|
if (!explicitUse) {
|
|
144
149
|
return () => {
|
|
@@ -1,16 +1,14 @@
|
|
|
1
|
-
import type { AllUseItems
|
|
1
|
+
import type { AllUseItems } from "../route-types.js";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* The set of valid use-item `type` discriminants — the single runtime source of
|
|
5
5
|
* truth for "is this a well-formed use item?" shape validation.
|
|
6
6
|
*
|
|
7
7
|
* Declared via a `Record<...>` so that adding a member to the union without
|
|
8
|
-
* updating this map is a compile error.
|
|
9
|
-
* are valid inside intercept() even though WhenItem is not part of AllUseItems
|
|
10
|
-
* (it lives only in InterceptUseItem). This is shape validation only; per-mount-
|
|
8
|
+
* updating this map is a compile error. This is shape validation only; per-mount-
|
|
11
9
|
* site rules remain the narrower hand-written tables in resolve-handler-use.ts.
|
|
12
10
|
*/
|
|
13
|
-
const USE_ITEM_TYPES: Record<AllUseItems["type"]
|
|
11
|
+
const USE_ITEM_TYPES: Record<AllUseItems["type"], true> = {
|
|
14
12
|
layout: true,
|
|
15
13
|
route: true,
|
|
16
14
|
middleware: true,
|
|
@@ -21,7 +19,6 @@ const USE_ITEM_TYPES: Record<AllUseItems["type"] | WhenItem["type"], true> = {
|
|
|
21
19
|
loading: true,
|
|
22
20
|
errorBoundary: true,
|
|
23
21
|
notFoundBoundary: true,
|
|
24
|
-
when: true,
|
|
25
22
|
cache: true,
|
|
26
23
|
transition: true,
|
|
27
24
|
include: true,
|