@rangojs/router 0.0.0-experimental.eb0645d3 → 0.0.0-experimental.f1468e3c
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 -0
- package/README.md +126 -16
- package/dist/bin/rango.js +319 -95
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +2724 -1053
- package/package.json +68 -14
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +64 -2
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +224 -32
- package/skills/caching/SKILL.md +279 -17
- package/skills/composability/SKILL.md +27 -3
- package/skills/css/SKILL.md +76 -0
- package/skills/debug-manifest/SKILL.md +4 -2
- package/skills/document-cache/SKILL.md +78 -55
- package/skills/handler-use/SKILL.md +11 -9
- package/skills/hooks/SKILL.md +243 -29
- package/skills/host-router/SKILL.md +83 -23
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +68 -19
- package/skills/layout/SKILL.md +13 -9
- package/skills/links/SKILL.md +190 -23
- package/skills/loader/SKILL.md +235 -9
- package/skills/middleware/SKILL.md +18 -10
- package/skills/migrate-nextjs/SKILL.md +43 -19
- package/skills/migrate-react-router/SKILL.md +8 -2
- package/skills/mime-routes/SKILL.md +28 -1
- package/skills/observability/SKILL.md +172 -0
- package/skills/parallel/SKILL.md +18 -7
- package/skills/prerender/SKILL.md +65 -60
- package/skills/rango/SKILL.md +251 -24
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +115 -48
- package/skills/route/SKILL.md +46 -5
- package/skills/router-setup/SKILL.md +30 -8
- package/skills/scripts/SKILL.md +179 -0
- package/skills/server-actions/SKILL.md +775 -0
- package/skills/tailwind/SKILL.md +27 -3
- package/skills/testing/SKILL.md +130 -0
- package/skills/testing/bindings.md +103 -0
- package/skills/testing/cache-prerender.md +127 -0
- package/skills/testing/client-components.md +124 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +91 -0
- package/skills/testing/handles.md +129 -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 +84 -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/typesafety/SKILL.md +322 -29
- package/skills/use-cache/SKILL.md +57 -14
- package/skills/view-transitions/SKILL.md +337 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +116 -0
- package/src/__internal.ts +0 -65
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +39 -0
- package/src/browser/connection-warmup.ts +134 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +192 -150
- package/src/browser/history-state.ts +21 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/navigation-bridge.ts +94 -25
- package/src/browser/navigation-client.ts +121 -84
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +115 -67
- package/src/browser/navigation-transaction.ts +9 -59
- package/src/browser/network-error-handler.ts +34 -7
- package/src/browser/partial-update.ts +147 -128
- package/src/browser/prefetch/cache.ts +107 -56
- package/src/browser/prefetch/fetch.ts +204 -34
- package/src/browser/prefetch/queue.ts +6 -3
- package/src/browser/rango-state.ts +158 -76
- package/src/browser/react/Link.tsx +30 -7
- package/src/browser/react/NavigationProvider.tsx +283 -118
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/deferred-handle-resolution.ts +75 -0
- package/src/browser/react/filter-segment-order.ts +66 -7
- package/src/browser/react/index.ts +0 -48
- package/src/browser/react/location-state-shared.ts +178 -8
- package/src/browser/react/location-state.ts +39 -14
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +17 -14
- package/src/browser/react/use-href.tsx +8 -1
- package/src/browser/react/use-link-status.ts +33 -8
- package/src/browser/react/use-navigation.ts +10 -5
- package/src/browser/react/use-params.ts +11 -11
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +25 -3
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +11 -21
- package/src/browser/response-adapter.ts +99 -8
- package/src/browser/rsc-router.tsx +91 -24
- package/src/browser/scroll-restoration.ts +30 -17
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +214 -55
- package/src/browser/types.ts +80 -9
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +60 -35
- package/src/build/generate-route-types.ts +2 -1
- package/src/build/index.ts +8 -2
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +117 -14
- package/src/build/route-types/ast-route-extraction.ts +15 -8
- package/src/build/route-types/codegen.ts +16 -5
- package/src/build/route-types/include-resolution.ts +117 -23
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/per-module-writer.ts +22 -6
- package/src/build/route-types/router-processing.ts +55 -28
- package/src/build/route-types/scan-filter.ts +1 -1
- package/src/build/route-types/source-scan.ts +216 -0
- package/src/build/runtime-discovery.ts +9 -20
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-key-utils.ts +29 -13
- package/src/cache/cache-policy.ts +108 -34
- package/src/cache/cache-runtime.ts +224 -41
- package/src/cache/cache-scope.ts +188 -82
- 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 +1989 -378
- 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 +89 -21
- package/src/cache/handle-snapshot.ts +70 -0
- package/src/cache/index.ts +10 -20
- package/src/cache/memory-segment-store.ts +136 -37
- package/src/cache/profile-registry.ts +46 -31
- package/src/cache/read-through-swr.ts +56 -12
- package/src/cache/segment-codec.ts +9 -17
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/types.ts +37 -100
- package/src/client.rsc.tsx +44 -21
- package/src/client.tsx +36 -61
- package/src/cloudflare/index.ts +11 -0
- package/src/cloudflare/tracing.ts +109 -0
- package/src/component-utils.ts +19 -0
- package/src/components/DefaultDocument.tsx +8 -2
- package/src/context-var.ts +18 -6
- package/src/decode-loader-results.ts +52 -0
- package/src/defer.ts +196 -0
- package/src/deps/ssr.ts +0 -1
- package/src/encode-kv.ts +49 -0
- package/src/errors.ts +30 -4
- package/src/escape-script.ts +52 -0
- package/src/handle.ts +31 -23
- package/src/handles/MetaTags.tsx +62 -19
- package/src/handles/Scripts.tsx +183 -0
- package/src/handles/breadcrumbs.ts +37 -8
- package/src/handles/is-thenable.ts +19 -0
- package/src/handles/meta.ts +51 -40
- package/src/handles/script.ts +244 -0
- package/src/host/cookie-handler.ts +9 -60
- package/src/host/errors.ts +0 -24
- package/src/host/index.ts +8 -2
- package/src/host/pattern-matcher.ts +23 -52
- package/src/host/router.ts +107 -99
- package/src/host/testing.ts +40 -27
- package/src/host/types.ts +37 -4
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +137 -22
- package/src/index.rsc.ts +96 -12
- package/src/index.ts +94 -14
- package/src/internal-debug.ts +11 -10
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +20 -13
- package/src/loader.ts +12 -11
- package/src/missing-id-error.ts +68 -0
- package/src/outlet-context.ts +1 -1
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +16 -16
- package/src/prerender/store.ts +32 -37
- package/src/prerender.ts +61 -6
- 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 +34 -0
- package/src/reverse.ts +65 -40
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +19 -77
- package/src/route-definition/dsl-helpers.ts +304 -309
- package/src/route-definition/helper-factories.ts +28 -140
- package/src/route-definition/helpers-types.ts +82 -55
- package/src/route-definition/index.ts +1 -2
- package/src/route-definition/redirect.ts +44 -11
- package/src/route-definition/resolve-handler-use.ts +12 -1
- package/src/route-definition/use-item-types.ts +29 -0
- package/src/route-map-builder.ts +0 -16
- package/src/route-types.ts +19 -46
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +73 -25
- package/src/router/error-handling.ts +45 -18
- package/src/router/find-match.ts +44 -23
- package/src/router/handler-context.ts +27 -43
- package/src/router/instrument.ts +350 -0
- package/src/router/intercept-resolution.ts +39 -20
- package/src/router/lazy-includes.ts +10 -47
- package/src/router/loader-resolution.ts +155 -72
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +18 -29
- package/src/router/match-api.ts +9 -24
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +58 -58
- package/src/router/match-middleware/background-revalidation.ts +40 -24
- package/src/router/match-middleware/cache-lookup.ts +159 -285
- package/src/router/match-middleware/cache-store.ts +64 -52
- package/src/router/match-middleware/intercept-resolution.ts +0 -22
- package/src/router/match-middleware/segment-resolution.ts +0 -22
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +44 -74
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-types.ts +7 -134
- package/src/router/middleware.ts +247 -166
- package/src/router/navigation-snapshot.ts +0 -51
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +85 -94
- package/src/router/prefetch-cache-ttl.ts +51 -0
- package/src/router/prerender-match.ts +104 -65
- package/src/router/preview-match.ts +3 -1
- package/src/router/request-classification.ts +28 -62
- package/src/router/revalidation.ts +123 -73
- package/src/router/route-snapshot.ts +0 -1
- package/src/router/router-context.ts +3 -28
- package/src/router/router-interfaces.ts +83 -35
- package/src/router/router-options.ts +136 -5
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +97 -84
- package/src/router/segment-resolution/helpers.ts +86 -6
- package/src/router/segment-resolution/loader-cache.ts +76 -39
- package/src/router/segment-resolution/revalidation.ts +272 -320
- package/src/router/segment-resolution/static-store.ts +19 -5
- package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
- package/src/router/segment-resolution/view-transition-default.ts +56 -0
- package/src/router/segment-resolution.ts +5 -1
- package/src/router/segment-wrappers.ts +6 -5
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry-otel.ts +161 -199
- package/src/router/telemetry.ts +96 -19
- package/src/router/timeout.ts +0 -20
- package/src/router/tracing.ts +206 -0
- package/src/router/trie-matching.ts +162 -64
- package/src/router/types.ts +9 -63
- package/src/router/url-params.ts +0 -5
- package/src/router.ts +110 -55
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +264 -220
- package/src/rsc/helpers.ts +100 -6
- package/src/rsc/index.ts +2 -5
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +114 -38
- package/src/rsc/manifest-init.ts +28 -41
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +117 -11
- package/src/rsc/redirect-guard.ts +99 -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 +88 -188
- package/src/rsc/rsc-rendering.ts +98 -76
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +281 -117
- package/src/rsc/ssr-setup.ts +16 -0
- package/src/rsc/transition-gate.ts +89 -0
- package/src/rsc/types.ts +23 -5
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +35 -30
- package/src/segment-loader-promise.ts +31 -4
- package/src/segment-system.tsx +254 -143
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +163 -51
- package/src/server/cookie-parse.ts +32 -0
- package/src/server/cookie-store.ts +80 -5
- package/src/server/handle-store.ts +21 -38
- package/src/server/loader-registry.ts +33 -42
- package/src/server/request-context.ts +287 -178
- package/src/ssr/index.tsx +21 -16
- package/src/static-handler.ts +10 -13
- package/src/testing/cache-status.ts +162 -0
- package/src/testing/collect-handle.ts +40 -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 +183 -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 +581 -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 +20 -58
- package/src/theme/ThemeScript.tsx +7 -9
- package/src/theme/constants.ts +52 -13
- package/src/theme/index.ts +0 -7
- package/src/theme/theme-context.ts +1 -5
- package/src/theme/theme-script.ts +22 -21
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +0 -35
- package/src/types/cache-types.ts +13 -4
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +54 -41
- package/src/types/handler-context.ts +110 -62
- package/src/types/index.ts +3 -10
- package/src/types/loader-types.ts +11 -9
- package/src/types/request-scope.ts +112 -0
- package/src/types/route-config.ts +6 -50
- package/src/types/route-entry.ts +0 -6
- package/src/types/segments.ts +135 -14
- package/src/urls/include-helper.ts +9 -56
- package/src/urls/index.ts +1 -11
- package/src/urls/path-helper-types.ts +29 -12
- package/src/urls/path-helper.ts +17 -106
- package/src/urls/pattern-types.ts +36 -19
- package/src/urls/response-types.ts +22 -29
- package/src/urls/type-extraction.ts +58 -139
- package/src/urls/urls-function.ts +1 -19
- package/src/use-loader.tsx +292 -107
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +8 -7
- package/src/vite/discovery/discover-routers.ts +126 -85
- package/src/vite/discovery/discovery-errors.ts +194 -0
- package/src/vite/discovery/gate-state.ts +171 -0
- package/src/vite/discovery/prerender-collection.ts +96 -68
- package/src/vite/discovery/route-types-writer.ts +40 -84
- package/src/vite/discovery/self-gen-tracking.ts +27 -1
- package/src/vite/discovery/state.ts +44 -0
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +2 -0
- package/src/vite/inject-client-debug.ts +36 -0
- package/src/vite/plugin-types.ts +126 -8
- package/src/vite/plugins/cjs-to-esm.ts +16 -19
- package/src/vite/plugins/client-ref-dedup.ts +16 -11
- package/src/vite/plugins/client-ref-hashing.ts +28 -15
- package/src/vite/plugins/cloudflare-protocol-stub.ts +1 -21
- package/src/vite/plugins/expose-action-id.ts +48 -95
- package/src/vite/plugins/expose-id-utils.ts +88 -55
- package/src/vite/plugins/expose-ids/export-analysis.ts +101 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +11 -90
- package/src/vite/plugins/expose-ids/loader-transform.ts +14 -24
- package/src/vite/plugins/expose-ids/router-transform.ts +118 -29
- package/src/vite/plugins/expose-internal-ids.ts +505 -486
- package/src/vite/plugins/performance-tracks.ts +26 -25
- package/src/vite/plugins/refresh-cmd.ts +1 -1
- package/src/vite/plugins/use-cache-transform.ts +73 -83
- package/src/vite/plugins/version-injector.ts +40 -29
- package/src/vite/plugins/version-plugin.ts +37 -40
- package/src/vite/plugins/virtual-entries.ts +39 -25
- package/src/vite/rango.ts +109 -118
- package/src/vite/router-discovery.ts +718 -119
- package/src/vite/utils/ast-handler-extract.ts +26 -35
- package/src/vite/utils/banner.ts +1 -1
- package/src/vite/utils/bundle-analysis.ts +10 -15
- package/src/vite/utils/client-chunks.ts +184 -0
- package/src/vite/utils/directive-prologue.ts +40 -0
- package/src/vite/utils/forward-user-plugins.ts +171 -0
- package/src/vite/utils/manifest-utils.ts +4 -59
- package/src/vite/utils/package-resolution.ts +20 -52
- package/src/vite/utils/prerender-utils.ts +54 -39
- package/src/vite/utils/shared-utils.ts +90 -41
- package/src/browser/action-response-classifier.ts +0 -99
- package/src/browser/react/use-client-cache.ts +0 -58
- package/src/browser/shallow.ts +0 -40
- package/src/handles/index.ts +0 -7
- package/src/network-error-thrower.tsx +0 -23
- package/src/router/middleware-cookies.ts +0 -55
|
@@ -8,125 +8,41 @@ import {
|
|
|
8
8
|
revalidate,
|
|
9
9
|
parallel,
|
|
10
10
|
intercept,
|
|
11
|
-
when,
|
|
12
11
|
errorBoundary,
|
|
13
12
|
notFoundBoundary,
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
route,
|
|
14
|
+
loader,
|
|
15
|
+
loading,
|
|
16
|
+
transition,
|
|
18
17
|
} from "./dsl-helpers.js";
|
|
19
18
|
import RootLayout from "../server/root-layout";
|
|
20
19
|
import { invariant } from "../errors";
|
|
21
20
|
|
|
22
|
-
/*
|
|
23
|
-
* Create revalidate helper
|
|
24
|
-
*/
|
|
25
|
-
const createRevalidateHelper = <TEnv>(): RouteHelpers<
|
|
26
|
-
any,
|
|
27
|
-
TEnv
|
|
28
|
-
>["revalidate"] => {
|
|
29
|
-
return revalidate as RouteHelpers<any, TEnv>["revalidate"];
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Create errorBoundary helper
|
|
34
|
-
*/
|
|
35
|
-
const createErrorBoundaryHelper = <TEnv>(): RouteHelpers<
|
|
36
|
-
any,
|
|
37
|
-
TEnv
|
|
38
|
-
>["errorBoundary"] => {
|
|
39
|
-
return errorBoundary as RouteHelpers<any, TEnv>["errorBoundary"];
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Create notFoundBoundary helper
|
|
44
|
-
*/
|
|
45
|
-
const createNotFoundBoundaryHelper = <TEnv>(): RouteHelpers<
|
|
46
|
-
any,
|
|
47
|
-
TEnv
|
|
48
|
-
>["notFoundBoundary"] => {
|
|
49
|
-
return notFoundBoundary as RouteHelpers<any, TEnv>["notFoundBoundary"];
|
|
50
|
-
};
|
|
51
|
-
|
|
52
21
|
/**
|
|
53
|
-
*
|
|
22
|
+
* Assemble the RouteHelpers object. The helpers are the DSL functions
|
|
23
|
+
* themselves; the single cast erases the phantom generics (and the extra
|
|
24
|
+
* `route` key) that the per-router RouteHelpers<T, TEnv> type carries but the
|
|
25
|
+
* runtime functions do not.
|
|
54
26
|
*/
|
|
55
|
-
|
|
56
|
-
|
|
27
|
+
function buildRouteHelpers<T extends RouteDefinition, TEnv>(): RouteHelpers<
|
|
28
|
+
T,
|
|
57
29
|
TEnv
|
|
58
|
-
>
|
|
59
|
-
return
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
TEnv,
|
|
75
|
-
>(): RouteHelpers<T, TEnv>["intercept"] => {
|
|
76
|
-
return intercept as RouteHelpers<T, TEnv>["intercept"];
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Create loader helper
|
|
81
|
-
*/
|
|
82
|
-
const createLoaderHelper = <TEnv>(): RouteHelpers<any, TEnv>["loader"] => {
|
|
83
|
-
return loaderFn as RouteHelpers<any, TEnv>["loader"];
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Create loading helper
|
|
88
|
-
*/
|
|
89
|
-
const createLoadingHelper = (): RouteHelpers<any, any>["loading"] => {
|
|
90
|
-
return loadingFn;
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Create route helper
|
|
95
|
-
*/
|
|
96
|
-
const createRouteHelper = <
|
|
97
|
-
const T extends RouteDefinition,
|
|
98
|
-
TEnv,
|
|
99
|
-
>(): RouteHelpers<T, TEnv>["route"] => {
|
|
100
|
-
return routeFn as unknown as RouteHelpers<T, TEnv>["route"];
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Create layout helper
|
|
105
|
-
*/
|
|
106
|
-
const createLayoutHelper = <TEnv>(): RouteHelpers<any, TEnv>["layout"] => {
|
|
107
|
-
return layout as RouteHelpers<any, TEnv>["layout"];
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Create when helper for intercept conditions
|
|
112
|
-
*/
|
|
113
|
-
const createWhenHelper = (): RouteHelpers<any, any>["when"] => {
|
|
114
|
-
return when;
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Create cache helper for cache configuration
|
|
119
|
-
*/
|
|
120
|
-
const createCacheHelper = (): RouteHelpers<any, any>["cache"] => {
|
|
121
|
-
return cache;
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Create transition helper
|
|
126
|
-
*/
|
|
127
|
-
const createTransitionHelper = (): RouteHelpers<any, any>["transition"] => {
|
|
128
|
-
return transitionFn as RouteHelpers<any, any>["transition"];
|
|
129
|
-
};
|
|
30
|
+
> {
|
|
31
|
+
return {
|
|
32
|
+
route,
|
|
33
|
+
layout,
|
|
34
|
+
parallel,
|
|
35
|
+
intercept,
|
|
36
|
+
middleware,
|
|
37
|
+
revalidate,
|
|
38
|
+
loader,
|
|
39
|
+
loading,
|
|
40
|
+
errorBoundary,
|
|
41
|
+
notFoundBoundary,
|
|
42
|
+
cache,
|
|
43
|
+
transition,
|
|
44
|
+
} as unknown as RouteHelpers<T, TEnv>;
|
|
45
|
+
}
|
|
130
46
|
|
|
131
47
|
/**
|
|
132
48
|
* Branded type for route handlers that carries the route type info.
|
|
@@ -152,21 +68,7 @@ export function map<const T extends RouteDefinition, TEnv = DefaultEnv>(
|
|
|
152
68
|
"map() expects a builder function as its argument",
|
|
153
69
|
);
|
|
154
70
|
// Create helpers
|
|
155
|
-
const helpers
|
|
156
|
-
route: createRouteHelper<T, TEnv>(),
|
|
157
|
-
layout: createLayoutHelper<TEnv>(),
|
|
158
|
-
parallel: createParallelHelper<TEnv>(),
|
|
159
|
-
intercept: createInterceptHelper<T, TEnv>(),
|
|
160
|
-
middleware: createMiddlewareHelper<TEnv>(),
|
|
161
|
-
revalidate: createRevalidateHelper<TEnv>(),
|
|
162
|
-
loader: createLoaderHelper<TEnv>(),
|
|
163
|
-
loading: createLoadingHelper(),
|
|
164
|
-
errorBoundary: createErrorBoundaryHelper<TEnv>(),
|
|
165
|
-
notFoundBoundary: createNotFoundBoundaryHelper<TEnv>(),
|
|
166
|
-
when: createWhenHelper(),
|
|
167
|
-
cache: createCacheHelper(),
|
|
168
|
-
transition: createTransitionHelper(),
|
|
169
|
-
};
|
|
71
|
+
const helpers = buildRouteHelpers<T, TEnv>();
|
|
170
72
|
|
|
171
73
|
return [layout(RootLayout, () => builder(helpers))].flat(3);
|
|
172
74
|
};
|
|
@@ -182,19 +84,5 @@ export function createRouteHelpers<
|
|
|
182
84
|
T extends RouteDefinition,
|
|
183
85
|
TEnv,
|
|
184
86
|
>(): RouteHelpers<T, TEnv> {
|
|
185
|
-
return
|
|
186
|
-
route: createRouteHelper<T, TEnv>(),
|
|
187
|
-
layout: createLayoutHelper<TEnv>(),
|
|
188
|
-
parallel: createParallelHelper<TEnv>(),
|
|
189
|
-
intercept: createInterceptHelper<T, TEnv>(),
|
|
190
|
-
middleware: createMiddlewareHelper<TEnv>(),
|
|
191
|
-
revalidate: createRevalidateHelper<TEnv>(),
|
|
192
|
-
loader: createLoaderHelper<TEnv>(),
|
|
193
|
-
loading: createLoadingHelper(),
|
|
194
|
-
errorBoundary: createErrorBoundaryHelper<TEnv>(),
|
|
195
|
-
notFoundBoundary: createNotFoundBoundaryHelper<TEnv>(),
|
|
196
|
-
when: createWhenHelper(),
|
|
197
|
-
cache: createCacheHelper(),
|
|
198
|
-
transition: createTransitionHelper(),
|
|
199
|
-
};
|
|
87
|
+
return buildRouteHelpers<T, TEnv>();
|
|
200
88
|
}
|
|
@@ -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,
|
|
@@ -183,10 +181,26 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
|
|
|
183
181
|
* loader(CardModalLoader),
|
|
184
182
|
* revalidate(() => false),
|
|
185
183
|
* ])
|
|
184
|
+
*
|
|
185
|
+
* // Conditional activation via the config object's `when` selector
|
|
186
|
+
* intercept("@modal", "card", <CardModal />, {
|
|
187
|
+
* when: ({ from }) => from.pathname.startsWith("/board"),
|
|
188
|
+
* })
|
|
189
|
+
*
|
|
190
|
+
* // Config + other use-items: config is arg 4, use is arg 5
|
|
191
|
+
* intercept(
|
|
192
|
+
* "@modal",
|
|
193
|
+
* "card",
|
|
194
|
+
* <CardModal />,
|
|
195
|
+
* { when: ({ from }) => from.pathname.startsWith("/board") },
|
|
196
|
+
* () => [loader(CardDetailLoader)],
|
|
197
|
+
* )
|
|
186
198
|
* ```
|
|
187
199
|
* @param slotName - Named slot (prefixed with @) where intercept renders
|
|
188
200
|
* @param routeName - Route name to intercept
|
|
189
201
|
* @param handler - Component or handler for intercepted render
|
|
202
|
+
* @param config - Optional InterceptConfig (e.g. `{ when }`), or the use
|
|
203
|
+
* callback directly when there is no config
|
|
190
204
|
* @param use - Optional callback for loaders, middleware, revalidate, etc.
|
|
191
205
|
*/
|
|
192
206
|
intercept: {
|
|
@@ -195,13 +209,19 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
|
|
|
195
209
|
slotName: `@${string}`,
|
|
196
210
|
routeName: `.${K}`,
|
|
197
211
|
handler: ReactNode | Handler<ExtractRouteParams<T, K>, {}, TEnv>,
|
|
212
|
+
config?:
|
|
213
|
+
| import("../server/context.js").InterceptConfig<TEnv>
|
|
214
|
+
| (() => UseItems<InterceptUseItem>),
|
|
198
215
|
use?: () => UseItems<InterceptUseItem>,
|
|
199
216
|
): InterceptItem;
|
|
200
217
|
// Global: unprefixed, params inferred from global route map
|
|
201
|
-
<K extends keyof
|
|
218
|
+
<K extends keyof Rango.GeneratedRouteMap & string>(
|
|
202
219
|
slotName: `@${string}`,
|
|
203
220
|
routeName: K,
|
|
204
|
-
handler: ReactNode | Handler<K,
|
|
221
|
+
handler: ReactNode | Handler<K, Rango.GeneratedRouteMap, TEnv>,
|
|
222
|
+
config?:
|
|
223
|
+
| import("../server/context.js").InterceptConfig<TEnv>
|
|
224
|
+
| (() => UseItems<InterceptUseItem>),
|
|
205
225
|
use?: () => UseItems<InterceptUseItem>,
|
|
206
226
|
): InterceptItem;
|
|
207
227
|
};
|
|
@@ -250,8 +270,10 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
|
|
|
250
270
|
* )
|
|
251
271
|
*
|
|
252
272
|
* // Revalidate after specific actions (actionId format: "path/to/file.ts#exportName")
|
|
273
|
+
* // Use `|| undefined` (defer), not `?? false` (hard short-circuit), so the
|
|
274
|
+
* // chain and the segment default still apply when there is no match.
|
|
253
275
|
* revalidate(({ actionId }) =>
|
|
254
|
-
* actionId?.includes("Cart")
|
|
276
|
+
* actionId?.includes("Cart") || undefined
|
|
255
277
|
* )
|
|
256
278
|
*
|
|
257
279
|
* // Soft decision (suggest but allow override)
|
|
@@ -259,7 +281,12 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
|
|
|
259
281
|
* ({ defaultShouldRevalidate: true })
|
|
260
282
|
* )
|
|
261
283
|
* ```
|
|
262
|
-
* @param fn - Function
|
|
284
|
+
* @param fn - Function returning either:
|
|
285
|
+
* - `boolean` (hard decision — short-circuits the chain),
|
|
286
|
+
* - `{ defaultShouldRevalidate: boolean }` (soft — updates the suggestion
|
|
287
|
+
* for downstream revalidators),
|
|
288
|
+
* - or nothing / `null` / `undefined` (defer — leaves the suggestion
|
|
289
|
+
* unchanged and continues to the next revalidator).
|
|
263
290
|
*/
|
|
264
291
|
revalidate: (fn: ShouldRevalidateFn<any, TEnv>) => RevalidateItem;
|
|
265
292
|
/**
|
|
@@ -269,7 +296,7 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
|
|
|
269
296
|
*
|
|
270
297
|
* // With loader-specific revalidation (match by file or export name)
|
|
271
298
|
* loader(CartLoader, () => [
|
|
272
|
-
* revalidate(({ actionId }) => actionId?.includes("Cart")
|
|
299
|
+
* revalidate(({ actionId }) => actionId?.includes("Cart") || undefined),
|
|
273
300
|
* ])
|
|
274
301
|
*
|
|
275
302
|
* // Consume in client components with useLoader()
|
|
@@ -340,40 +367,6 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
|
|
|
340
367
|
notFoundBoundary: (
|
|
341
368
|
fallback: ReactNode | NotFoundBoundaryHandler,
|
|
342
369
|
) => NotFoundBoundaryItem;
|
|
343
|
-
/**
|
|
344
|
-
* Define a condition for when an intercept should activate
|
|
345
|
-
*
|
|
346
|
-
* Only valid inside intercept() use() callback. When multiple when() calls
|
|
347
|
-
* are present, ALL must return true for the intercept to activate.
|
|
348
|
-
* If no when() is defined, the intercept always activates on soft navigation.
|
|
349
|
-
*
|
|
350
|
-
* Context properties:
|
|
351
|
-
* - `from` - Source URL (where user is navigating from)
|
|
352
|
-
* - `to` - Destination URL (where user is navigating to)
|
|
353
|
-
* - `params` - Matched route params
|
|
354
|
-
* - `segments` - Client's current segments with `path` and `ids`
|
|
355
|
-
*
|
|
356
|
-
* ```typescript
|
|
357
|
-
* // Only intercept when coming from the board page
|
|
358
|
-
* intercept("@modal", "card", <CardModal />, () => [
|
|
359
|
-
* when(({ from }) => from.pathname.startsWith("/board")),
|
|
360
|
-
* loader(CardDetailLoader),
|
|
361
|
-
* ])
|
|
362
|
-
*
|
|
363
|
-
* // Use segments to check current route context
|
|
364
|
-
* intercept("@modal", "card", <CardModal />, () => [
|
|
365
|
-
* when(({ segments }) => segments.path[0] === "kanban"),
|
|
366
|
-
* ])
|
|
367
|
-
*
|
|
368
|
-
* // Multiple conditions (AND logic)
|
|
369
|
-
* intercept("@modal", "card", <CardModal />, () => [
|
|
370
|
-
* when(({ from }) => from.pathname.startsWith("/board")),
|
|
371
|
-
* when(({ segments }) => segments.ids.includes("kanban-layout")),
|
|
372
|
-
* ])
|
|
373
|
-
* ```
|
|
374
|
-
* @param fn - Selector function receiving navigation context, returns boolean
|
|
375
|
-
*/
|
|
376
|
-
when: (fn: InterceptWhenFn) => WhenItem;
|
|
377
370
|
/**
|
|
378
371
|
* Define cache configuration for segments
|
|
379
372
|
*
|
|
@@ -434,18 +427,43 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
|
|
|
434
427
|
cache: {
|
|
435
428
|
(): CacheItem;
|
|
436
429
|
(children: () => UseItems<AllUseItems>): CacheItem;
|
|
437
|
-
(profileName: string): CacheItem;
|
|
438
|
-
(profileName: string, use: () => UseItems<AllUseItems>): CacheItem;
|
|
439
430
|
(
|
|
440
|
-
options: PartialCacheOptions | false,
|
|
431
|
+
options: PartialCacheOptions<TEnv> | false,
|
|
441
432
|
use?: () => UseItems<AllUseItems>,
|
|
442
433
|
): CacheItem;
|
|
443
434
|
};
|
|
444
435
|
/**
|
|
445
|
-
*
|
|
446
|
-
*
|
|
447
|
-
*
|
|
448
|
-
*
|
|
436
|
+
* Opt a route (or group of routes) into transition-driven navigation.
|
|
437
|
+
*
|
|
438
|
+
* `transition()` does two independent things, and you choose how far to go:
|
|
439
|
+
* 1. startTransition (ALL React versions): the navigation commit is driven
|
|
440
|
+
* through React's startTransition, so a same-route nav (same route,
|
|
441
|
+
* different params, e.g. /product/1 -> /product/2) holds the previous
|
|
442
|
+
* content while the new loader resolves instead of flashing the route's
|
|
443
|
+
* loading() skeleton (see segment-system.tsx inTransitionScope). This is
|
|
444
|
+
* also the precondition for any view-transition animation.
|
|
445
|
+
* 2. <ViewTransition> (experimental React only): the segment content is also
|
|
446
|
+
* wrapped in React's <ViewTransition>, so the held swap cross-fades/morphs.
|
|
447
|
+
* Layered on by default; pass { viewTransition: false } to keep #1 without
|
|
448
|
+
* the router boundary (and place your own <ViewTransition> instead).
|
|
449
|
+
*
|
|
450
|
+
* A view transition cannot fire without a startTransition, so the meaningful
|
|
451
|
+
* choices are (see skills/view-transitions for the full matrix):
|
|
452
|
+
* - no transition() -> neither (remount + skeleton)
|
|
453
|
+
* - transition({ viewTransition: false }) -> startTransition only (hold)
|
|
454
|
+
* - transition({}) / transition({ enter… }) -> startTransition + ViewTransition
|
|
455
|
+
*
|
|
456
|
+
* Precedence: a bare transition({}) inherits createRouter({ viewTransition })
|
|
457
|
+
* (default "auto"); an explicit per-route `viewTransition` always wins. So
|
|
458
|
+
* transition({}) is startTransition + ViewTransition under the default and
|
|
459
|
+
* startTransition only when the router sets viewTransition: false.
|
|
460
|
+
*
|
|
461
|
+
* Conditional hold: pass `when: (ctx) => boolean` to gate the transition per
|
|
462
|
+
* request. It runs server-side AFTER the route handler (so it can read state
|
|
463
|
+
* the handler set via `ctx.get(...)`); returning false drops this transition
|
|
464
|
+
* for the request, so the navigation streams its loading() skeleton instead of
|
|
465
|
+
* holding. This is a post-handler predicate — distinct from intercept()'s
|
|
466
|
+
* match-time `when` config selector (`intercept(slot, route, Comp, { when })`).
|
|
449
467
|
*
|
|
450
468
|
* ```typescript
|
|
451
469
|
* // Attach to a single route
|
|
@@ -459,16 +477,25 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
|
|
|
459
477
|
* path("/about", AboutPage),
|
|
460
478
|
* ])
|
|
461
479
|
*
|
|
462
|
-
* //
|
|
463
|
-
*
|
|
464
|
-
*
|
|
465
|
-
*
|
|
466
|
-
*
|
|
480
|
+
* // Hold content + drive view transitions, but place no router boundary:
|
|
481
|
+
* path("/product/:id", ProductPage, { name: "product" }, () => [
|
|
482
|
+
* transition({ viewTransition: false }),
|
|
483
|
+
* ])
|
|
484
|
+
*
|
|
485
|
+
* // Hold only when the handler decided to (post-handler predicate):
|
|
486
|
+
* path("/product/:id", ProductPage, { name: "product" }, () => [
|
|
487
|
+
* transition({ when: (ctx) => ctx.get(KeepScroll) === true }),
|
|
488
|
+
* ])
|
|
467
489
|
* ```
|
|
468
|
-
* @param config - ViewTransition configuration (enter, exit, update, share,
|
|
490
|
+
* @param config - ViewTransition configuration (enter, exit, update, share,
|
|
491
|
+
* default, name), `viewTransition: "auto" | false` to toggle the router
|
|
492
|
+
* boundary (createRouter({ viewTransition }) sets the app-wide default), and
|
|
493
|
+
* `when: (ctx) => boolean` to gate the transition per request post-handler
|
|
469
494
|
* @param children - Optional callback returning child routes to wrap
|
|
470
495
|
*/
|
|
471
496
|
transition: {
|
|
497
|
+
(): TransitionItem;
|
|
498
|
+
(children: () => UseItems<AllUseItems>): TransitionItem;
|
|
472
499
|
(config: TransitionConfig): TransitionItem;
|
|
473
500
|
(
|
|
474
501
|
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
|
}
|
|
@@ -21,6 +21,10 @@ export function resolveHandlerUse(handler: unknown): (() => any[]) | undefined {
|
|
|
21
21
|
if (isStaticHandler(handler)) {
|
|
22
22
|
return (handler as any).use;
|
|
23
23
|
}
|
|
24
|
+
// Loader definitions from createLoader() — branded objects with optional .use
|
|
25
|
+
if (typeof handler === "object" && (handler as any).__brand === "loader") {
|
|
26
|
+
return (handler as any).use;
|
|
27
|
+
}
|
|
24
28
|
// Plain handler function
|
|
25
29
|
if (typeof handler === "function") {
|
|
26
30
|
return (handler as any).use;
|
|
@@ -96,9 +100,10 @@ const MOUNT_SITE_ALLOWED_TYPES: Record<string, Set<string>> = {
|
|
|
96
100
|
"notFoundBoundary",
|
|
97
101
|
"layout",
|
|
98
102
|
"route",
|
|
99
|
-
"when",
|
|
100
103
|
"transition",
|
|
101
104
|
]),
|
|
105
|
+
// LoaderUseItem — only revalidate + cache can attach to a loader entry
|
|
106
|
+
loader: new Set(["revalidate", "cache"]),
|
|
102
107
|
};
|
|
103
108
|
|
|
104
109
|
/**
|
|
@@ -133,6 +138,12 @@ export function mergeHandlerUse(
|
|
|
133
138
|
mountSite: string,
|
|
134
139
|
): (() => any[]) | undefined {
|
|
135
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).
|
|
136
147
|
if (!handlerUse) return explicitUse;
|
|
137
148
|
if (!explicitUse) {
|
|
138
149
|
return () => {
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { AllUseItems } from "../route-types.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The set of valid use-item `type` discriminants — the single runtime source of
|
|
5
|
+
* truth for "is this a well-formed use item?" shape validation.
|
|
6
|
+
*
|
|
7
|
+
* Declared via a `Record<...>` so that adding a member to the union without
|
|
8
|
+
* updating this map is a compile error. This is shape validation only; per-mount-
|
|
9
|
+
* site rules remain the narrower hand-written tables in resolve-handler-use.ts.
|
|
10
|
+
*/
|
|
11
|
+
const USE_ITEM_TYPES: Record<AllUseItems["type"], true> = {
|
|
12
|
+
layout: true,
|
|
13
|
+
route: true,
|
|
14
|
+
middleware: true,
|
|
15
|
+
revalidate: true,
|
|
16
|
+
parallel: true,
|
|
17
|
+
intercept: true,
|
|
18
|
+
loader: true,
|
|
19
|
+
loading: true,
|
|
20
|
+
errorBoundary: true,
|
|
21
|
+
notFoundBoundary: true,
|
|
22
|
+
cache: true,
|
|
23
|
+
transition: true,
|
|
24
|
+
include: true,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const ALL_USE_ITEM_TYPES: ReadonlySet<string> = new Set(
|
|
28
|
+
Object.keys(USE_ITEM_TYPES),
|
|
29
|
+
);
|
package/src/route-map-builder.ts
CHANGED
|
@@ -8,15 +8,10 @@
|
|
|
8
8
|
* See docs/manifests.md for the full data flow.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
// Singleton route map instance - populated incrementally as routes are encountered
|
|
12
11
|
let globalRouteMap: Record<string, string> = {};
|
|
13
12
|
|
|
14
|
-
// Cached complete manifest - includes all routes (including lazy includes)
|
|
15
|
-
// Set from runtime cache or build-time import
|
|
16
13
|
let cachedManifest: Record<string, string> | null = null;
|
|
17
14
|
|
|
18
|
-
// Pre-computed route entries from build-time prefix tree leaf nodes.
|
|
19
|
-
// Used by evaluateLazyEntry() to skip running the handler for route matching.
|
|
20
15
|
let cachedPrecomputedEntries: Array<{
|
|
21
16
|
staticPrefix: string;
|
|
22
17
|
routes: Record<string, string>;
|
|
@@ -43,7 +38,6 @@ export function registerRouteMap(map: Record<string, string>): void {
|
|
|
43
38
|
* @internal
|
|
44
39
|
*/
|
|
45
40
|
export function getGlobalRouteMap(): Record<string, string> {
|
|
46
|
-
// Cached manifest is complete (includes lazy routes), so prefer it
|
|
47
41
|
if (cachedManifest) {
|
|
48
42
|
return cachedManifest;
|
|
49
43
|
}
|
|
@@ -231,10 +225,6 @@ export function waitForManifestReady(): Promise<void> | null {
|
|
|
231
225
|
return manifestReadyPromise;
|
|
232
226
|
}
|
|
233
227
|
|
|
234
|
-
// ============================================================================
|
|
235
|
-
// Route Scope Registry
|
|
236
|
-
// ============================================================================
|
|
237
|
-
|
|
238
228
|
// Tracks whether each route is at root scope (no named include boundary above).
|
|
239
229
|
// Used by dot-local reverse resolution to decide whether bare-name fallback
|
|
240
230
|
// is allowed after scoped lookups are exhausted.
|
|
@@ -259,14 +249,8 @@ export function isRouteRootScoped(routeName: string): boolean | undefined {
|
|
|
259
249
|
return rootScopeRoutes.get(routeName);
|
|
260
250
|
}
|
|
261
251
|
|
|
262
|
-
// ============================================================================
|
|
263
|
-
// Search Schema Registry
|
|
264
|
-
// ============================================================================
|
|
265
|
-
|
|
266
252
|
import type { SearchSchema } from "./search-params.js";
|
|
267
253
|
|
|
268
|
-
// Global search schema map: route name -> search schema descriptor.
|
|
269
|
-
// Populated by path() when a search option is provided.
|
|
270
254
|
const globalSearchSchemas: Map<string, SearchSchema> = new Map();
|
|
271
255
|
|
|
272
256
|
export function registerSearchSchema(
|