@rangojs/router 0.0.0-experimental.79 → 0.0.0-experimental.7c7e4327
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +8 -4
- package/README.md +301 -797
- package/dist/bin/rango.js +603 -145
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +3750 -1160
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +96 -24
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +85 -6
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +228 -33
- package/skills/caching/SKILL.md +336 -19
- package/skills/catalog.json +271 -0
- package/skills/comparison/SKILL.md +50 -0
- package/skills/comparison/agents/openai.yaml +4 -0
- package/skills/comparison/references/framework-comparison.md +837 -0
- package/skills/composability/SKILL.md +110 -4
- package/skills/css/SKILL.md +76 -0
- package/skills/debug-manifest/SKILL.md +5 -3
- package/skills/defer-hydration/SKILL.md +235 -0
- package/skills/document-cache/SKILL.md +87 -56
- package/skills/fonts/SKILL.md +1 -1
- package/skills/handler-use/SKILL.md +12 -10
- package/skills/hooks/SKILL.md +73 -691
- package/skills/hooks/data.md +273 -0
- package/skills/hooks/handle-and-actions.md +103 -0
- package/skills/hooks/navigation.md +110 -0
- package/skills/hooks/outlets.md +41 -0
- package/skills/hooks/state.md +228 -0
- package/skills/hooks/urls.md +135 -0
- package/skills/host-router/SKILL.md +129 -27
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +75 -19
- package/skills/layout/SKILL.md +40 -19
- package/skills/links/SKILL.md +247 -17
- package/skills/loader/SKILL.md +248 -10
- package/skills/middleware/SKILL.md +25 -13
- package/skills/migrate-nextjs/SKILL.md +205 -20
- package/skills/migrate-react-router/SKILL.md +59 -670
- package/skills/migrate-react-router/cloudflare-workers.md +129 -0
- package/skills/migrate-react-router/component-migration.md +196 -0
- package/skills/migrate-react-router/data-and-actions.md +225 -0
- package/skills/migrate-react-router/route-mapping.md +271 -0
- package/skills/mime-routes/SKILL.md +29 -2
- package/skills/observability/SKILL.md +202 -0
- package/skills/parallel/SKILL.md +40 -10
- package/skills/ppr/SKILL.md +616 -0
- package/skills/prerender/SKILL.md +72 -60
- package/skills/rango/SKILL.md +318 -26
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +138 -49
- package/skills/route/SKILL.md +117 -9
- package/skills/router-setup/SKILL.md +44 -9
- package/skills/scripts/SKILL.md +179 -0
- package/skills/server-actions/SKILL.md +776 -0
- package/skills/shell-manifest/SKILL.md +185 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/tailwind/SKILL.md +28 -4
- package/skills/testing/SKILL.md +130 -0
- package/skills/testing/bindings.md +103 -0
- package/skills/testing/cache-prerender.md +127 -0
- package/skills/testing/client-components.md +124 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +91 -0
- package/skills/testing/handles.md +131 -0
- package/skills/testing/loader.md +128 -0
- package/skills/testing/middleware.md +99 -0
- package/skills/testing/render-handler.md +122 -0
- package/skills/testing/response-routes.md +95 -0
- package/skills/testing/reverse-and-types.md +85 -0
- package/skills/testing/server-actions.md +107 -0
- package/skills/testing/server-tree.md +128 -0
- package/skills/testing/setup.md +123 -0
- package/skills/theme/SKILL.md +1 -1
- package/skills/typesafety/SKILL.md +45 -626
- package/skills/typesafety/env-and-bindings.md +254 -0
- package/skills/typesafety/generated-files-and-cli.md +335 -0
- package/skills/typesafety/params-and-search.md +153 -0
- package/skills/typesafety/route-types.md +209 -0
- package/skills/use-cache/SKILL.md +74 -15
- package/skills/vercel/SKILL.md +128 -0
- package/skills/view-transitions/SKILL.md +337 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +116 -0
- package/src/__internal.ts +0 -65
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +39 -0
- package/src/browser/connection-warmup.ts +134 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +252 -158
- package/src/browser/history-state.ts +21 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/logging.ts +28 -0
- package/src/browser/merge-segment-loaders.ts +6 -4
- package/src/browser/navigation-bridge.ts +94 -25
- package/src/browser/navigation-client.ts +144 -79
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +161 -73
- package/src/browser/navigation-transaction.ts +9 -59
- package/src/browser/network-error-handler.ts +34 -7
- package/src/browser/partial-update.ts +183 -144
- package/src/browser/prefetch/cache.ts +242 -77
- package/src/browser/prefetch/fetch.ts +325 -69
- package/src/browser/prefetch/queue.ts +61 -12
- package/src/browser/rango-state.ts +158 -76
- package/src/browser/react/Link.tsx +58 -20
- package/src/browser/react/NavigationProvider.tsx +202 -120
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/filter-segment-order.ts +66 -7
- package/src/browser/react/index.ts +0 -48
- package/src/browser/react/location-state-shared.ts +178 -8
- package/src/browser/react/location-state.ts +39 -14
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +17 -14
- package/src/browser/react/use-href.tsx +8 -1
- package/src/browser/react/use-link-status.ts +33 -8
- package/src/browser/react/use-navigation.ts +32 -7
- package/src/browser/react/use-params.ts +20 -10
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +25 -3
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +11 -21
- package/src/browser/response-adapter.ts +99 -8
- package/src/browser/rsc-router.tsx +145 -28
- package/src/browser/scroll-restoration.ts +37 -22
- package/src/browser/segment-reconciler.ts +31 -21
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +236 -65
- package/src/browser/types.ts +102 -9
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +203 -154
- package/src/build/generate-route-types.ts +3 -1
- package/src/build/index.ts +11 -3
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +152 -21
- package/src/build/route-types/ast-route-extraction.ts +15 -8
- package/src/build/route-types/codegen.ts +16 -5
- package/src/build/route-types/include-resolution.ts +456 -62
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/per-module-writer.ts +22 -6
- package/src/build/route-types/router-processing.ts +128 -51
- package/src/build/route-types/scan-filter.ts +1 -1
- package/src/build/route-types/source-scan.ts +216 -0
- package/src/build/runtime-discovery.ts +13 -21
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-key-utils.ts +58 -13
- package/src/cache/cache-policy.ts +108 -34
- package/src/cache/cache-runtime.ts +421 -58
- package/src/cache/cache-scope.ts +187 -96
- package/src/cache/cache-tag.ts +149 -0
- package/src/cache/cf/cf-base64.ts +33 -0
- package/src/cache/cf/cf-cache-constants.ts +127 -0
- package/src/cache/cf/cf-cache-store.ts +2202 -372
- package/src/cache/cf/cf-cache-types.ts +349 -0
- package/src/cache/cf/cf-kv-utils.ts +46 -0
- package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
- package/src/cache/cf/index.ts +6 -16
- package/src/cache/document-cache.ts +126 -41
- package/src/cache/handle-snapshot.ts +70 -0
- package/src/cache/index.ts +23 -20
- package/src/cache/memory-segment-store.ts +243 -37
- package/src/cache/profile-registry.ts +46 -31
- package/src/cache/read-through-swr.ts +56 -12
- package/src/cache/segment-codec.ts +13 -21
- package/src/cache/shell-snapshot.ts +417 -0
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/types.ts +180 -99
- package/src/cache/vercel/index.ts +11 -0
- package/src/cache/vercel/vercel-cache-store.ts +1127 -0
- package/src/client.rsc.tsx +41 -21
- package/src/client.tsx +33 -61
- package/src/cloudflare/index.ts +11 -0
- package/src/cloudflare/tracing.ts +108 -0
- package/src/component-utils.ts +19 -0
- package/src/components/DefaultDocument.tsx +8 -2
- package/src/context-var.ts +18 -6
- package/src/decode-loader-results.ts +52 -0
- package/src/defer.ts +185 -0
- package/src/deps/ssr.ts +0 -1
- package/src/encode-kv.ts +49 -0
- package/src/errors.ts +30 -4
- package/src/escape-script.ts +52 -0
- package/src/handle.ts +67 -37
- package/src/handles/MetaTags.tsx +24 -53
- package/src/handles/Scripts.tsx +183 -0
- package/src/handles/breadcrumbs.ts +35 -8
- package/src/handles/deferred-resolution.ts +127 -0
- package/src/handles/is-thenable.ts +18 -0
- package/src/handles/meta.ts +14 -40
- package/src/handles/script.ts +244 -0
- package/src/host/cookie-handler.ts +9 -60
- package/src/host/errors.ts +13 -22
- package/src/host/index.ts +9 -2
- package/src/host/pattern-matcher.ts +23 -52
- package/src/host/router.ts +107 -99
- package/src/host/testing.ts +40 -27
- package/src/host/types.ts +37 -4
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +137 -22
- package/src/index.rsc.ts +97 -12
- package/src/index.ts +98 -14
- package/src/internal-debug.ts +11 -10
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +20 -13
- package/src/loader.ts +12 -11
- package/src/missing-id-error.ts +68 -0
- package/src/outlet-context.ts +1 -1
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +16 -16
- package/src/prerender/store.ts +32 -37
- package/src/prerender.ts +78 -10
- package/src/redirect-origin.ts +114 -0
- package/src/regex-escape.ts +8 -0
- package/src/render-error-thrower.tsx +20 -0
- package/src/response-utils.ts +62 -0
- package/src/reverse.ts +65 -39
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +19 -77
- package/src/route-definition/dsl-helpers.ts +304 -309
- package/src/route-definition/helper-factories.ts +28 -140
- package/src/route-definition/helpers-types.ts +87 -59
- package/src/route-definition/index.ts +1 -2
- package/src/route-definition/redirect.ts +44 -11
- package/src/route-definition/resolve-handler-use.ts +12 -1
- package/src/route-definition/use-item-types.ts +29 -0
- package/src/route-map-builder.ts +41 -20
- package/src/route-types.ts +19 -46
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +73 -25
- package/src/router/error-handling.ts +45 -18
- package/src/router/find-match.ts +129 -30
- package/src/router/handler-context.ts +27 -42
- package/src/router/instrument.ts +355 -0
- package/src/router/intercept-resolution.ts +39 -20
- package/src/router/lazy-includes.ts +82 -59
- package/src/router/loader-resolution.ts +167 -72
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +74 -40
- package/src/router/match-api.ts +80 -55
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +211 -165
- package/src/router/match-middleware/background-revalidation.ts +40 -24
- package/src/router/match-middleware/cache-lookup.ts +159 -285
- package/src/router/match-middleware/cache-store.ts +64 -52
- package/src/router/match-middleware/intercept-resolution.ts +0 -22
- package/src/router/match-middleware/segment-resolution.ts +0 -22
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +69 -79
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-types.ts +7 -134
- package/src/router/middleware.ts +298 -172
- package/src/router/navigation-snapshot.ts +7 -56
- package/src/router/params-util.ts +23 -0
- package/src/router/parse-pattern.ts +115 -0
- package/src/router/pattern-matching.ts +181 -150
- package/src/router/prefetch-cache-ttl.ts +51 -0
- package/src/router/prefetch-limits.ts +37 -0
- package/src/router/prerender-match.ts +112 -67
- package/src/router/preview-match.ts +6 -2
- package/src/router/request-classification.ts +50 -69
- package/src/router/revalidation.ts +123 -73
- package/src/router/route-snapshot.ts +14 -3
- package/src/router/router-context.ts +6 -29
- package/src/router/router-interfaces.ts +115 -36
- package/src/router/router-options.ts +166 -5
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +131 -86
- package/src/router/segment-resolution/helpers.ts +86 -6
- package/src/router/segment-resolution/loader-cache.ts +139 -39
- package/src/router/segment-resolution/loader-mask.ts +67 -0
- package/src/router/segment-resolution/loader-snapshot.ts +251 -0
- package/src/router/segment-resolution/revalidation.ts +272 -320
- package/src/router/segment-resolution/static-store.ts +19 -5
- package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
- package/src/router/segment-resolution/view-transition-default.ts +56 -0
- package/src/router/segment-resolution.ts +5 -1
- package/src/router/segment-wrappers.ts +6 -5
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +75 -0
- package/src/router/telemetry-otel.ts +160 -200
- package/src/router/telemetry.ts +105 -20
- package/src/router/timeout.ts +0 -20
- package/src/router/tracing.ts +215 -0
- package/src/router/trie-matching.ts +171 -59
- package/src/router/types.ts +9 -63
- package/src/router/url-params.ts +57 -0
- package/src/router.ts +157 -71
- package/src/rsc/full-payload.ts +70 -0
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +291 -217
- package/src/rsc/helpers.ts +168 -46
- package/src/rsc/index.ts +2 -5
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +114 -38
- package/src/rsc/manifest-init.ts +29 -42
- package/src/rsc/nonce.ts +10 -1
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +124 -13
- package/src/rsc/redirect-guard.ts +100 -0
- package/src/rsc/response-cache-serve.ts +238 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +99 -189
- package/src/rsc/rsc-rendering.ts +421 -76
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +282 -116
- package/src/rsc/shell-capture.ts +1158 -0
- package/src/rsc/shell-serve.ts +150 -0
- package/src/rsc/ssr-setup.ts +16 -0
- package/src/rsc/transition-gate.ts +89 -0
- package/src/rsc/types.ts +53 -5
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +35 -30
- package/src/segment-loader-promise.ts +49 -4
- package/src/segment-system.tsx +350 -149
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +208 -51
- package/src/server/cookie-parse.ts +32 -0
- package/src/server/cookie-store.ts +152 -5
- package/src/server/handle-store.ts +21 -38
- package/src/server/loader-registry.ts +33 -42
- package/src/server/request-context.ts +395 -176
- package/src/ssr/index.tsx +458 -178
- package/src/ssr/ssr-root.tsx +228 -0
- package/src/static-handler.ts +10 -13
- package/src/testing/cache-status.ts +162 -0
- package/src/testing/collect-handle.ts +46 -0
- package/src/testing/dispatch.ts +813 -0
- package/src/testing/dom.entry.ts +22 -0
- package/src/testing/e2e/fixture.ts +188 -0
- package/src/testing/e2e/index.ts +128 -0
- package/src/testing/e2e/matchers.ts +35 -0
- package/src/testing/e2e/page-helpers.ts +272 -0
- package/src/testing/e2e/parity.ts +387 -0
- package/src/testing/e2e/server.ts +195 -0
- package/src/testing/flight-matchers.ts +97 -0
- package/src/testing/flight-normalize.ts +11 -0
- package/src/testing/flight-runtime.d.ts +57 -0
- package/src/testing/flight-tree.ts +682 -0
- package/src/testing/flight.entry.ts +52 -0
- package/src/testing/flight.ts +257 -0
- package/src/testing/generated-routes.ts +199 -0
- package/src/testing/index.ts +105 -0
- package/src/testing/internal/context.ts +371 -0
- package/src/testing/internal/flight-client-globals.ts +30 -0
- package/src/testing/internal/seed-vars.ts +54 -0
- package/src/testing/render-handler.ts +357 -0
- package/src/testing/render-route.tsx +584 -0
- package/src/testing/run-loader.ts +385 -0
- package/src/testing/run-middleware.ts +205 -0
- package/src/testing/run-transition-when.ts +164 -0
- package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
- package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
- package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
- package/src/testing/vitest-stubs/version.ts +5 -0
- package/src/testing/vitest.ts +305 -0
- package/src/theme/ThemeProvider.tsx +56 -84
- package/src/theme/ThemeScript.tsx +7 -9
- package/src/theme/constants.ts +52 -13
- package/src/theme/index.ts +0 -7
- package/src/theme/theme-context.ts +1 -5
- package/src/theme/theme-script.ts +22 -21
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +0 -35
- package/src/types/cache-types.ts +13 -4
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +54 -41
- package/src/types/handler-context.ts +110 -62
- package/src/types/index.ts +3 -10
- package/src/types/loader-types.ts +11 -9
- package/src/types/request-scope.ts +112 -0
- package/src/types/route-config.ts +20 -52
- package/src/types/route-entry.ts +0 -6
- package/src/types/segments.ts +135 -14
- package/src/urls/include-helper.ts +19 -64
- package/src/urls/include-provider.ts +71 -0
- package/src/urls/index.ts +2 -11
- package/src/urls/path-helper-types.ts +63 -17
- package/src/urls/path-helper.ts +22 -106
- package/src/urls/pattern-types.ts +72 -19
- package/src/urls/response-types.ts +22 -29
- package/src/urls/type-extraction.ts +98 -154
- package/src/urls/urls-function.ts +1 -19
- package/src/use-loader.tsx +292 -107
- package/src/vercel/index.ts +11 -0
- package/src/vercel/tracing.ts +88 -0
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +8 -7
- package/src/vite/discovery/dev-prerender-cache.ts +117 -0
- package/src/vite/discovery/discover-routers.ts +127 -86
- package/src/vite/discovery/discovery-errors.ts +255 -0
- package/src/vite/discovery/gate-state.ts +171 -0
- package/src/vite/discovery/prerender-collection.ts +96 -68
- package/src/vite/discovery/route-types-writer.ts +40 -84
- package/src/vite/discovery/self-gen-tracking.ts +27 -1
- package/src/vite/discovery/state.ts +45 -1
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +4 -0
- package/src/vite/inject-client-debug.ts +88 -0
- package/src/vite/plugin-types.ts +210 -10
- package/src/vite/plugins/cjs-to-esm.ts +16 -19
- package/src/vite/plugins/client-ref-dedup.ts +16 -11
- package/src/vite/plugins/client-ref-hashing.ts +28 -15
- package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
- package/src/vite/plugins/expose-action-id.ts +48 -95
- package/src/vite/plugins/expose-id-utils.ts +88 -55
- package/src/vite/plugins/expose-ids/export-analysis.ts +101 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +11 -90
- package/src/vite/plugins/expose-ids/loader-transform.ts +14 -24
- package/src/vite/plugins/expose-ids/router-transform.ts +118 -29
- package/src/vite/plugins/expose-internal-ids.ts +505 -486
- package/src/vite/plugins/performance-tracks.ts +26 -25
- package/src/vite/plugins/refresh-cmd.ts +1 -1
- package/src/vite/plugins/use-cache-transform.ts +73 -83
- package/src/vite/plugins/vercel-output.ts +384 -0
- package/src/vite/plugins/version-injector.ts +40 -29
- package/src/vite/plugins/version-plugin.ts +37 -40
- package/src/vite/plugins/virtual-entries.ts +138 -27
- package/src/vite/rango.ts +236 -138
- package/src/vite/router-discovery.ts +927 -136
- package/src/vite/utils/ast-handler-extract.ts +26 -35
- package/src/vite/utils/banner.ts +1 -1
- package/src/vite/utils/bundle-analysis.ts +10 -15
- package/src/vite/utils/client-chunks.ts +184 -0
- package/src/vite/utils/directive-prologue.ts +40 -0
- package/src/vite/utils/forward-user-plugins.ts +171 -0
- package/src/vite/utils/manifest-utils.ts +4 -59
- package/src/vite/utils/package-resolution.ts +20 -52
- package/src/vite/utils/prerender-utils.ts +71 -43
- package/src/vite/utils/shared-utils.ts +142 -43
- package/src/browser/action-response-classifier.ts +0 -99
- package/src/browser/react/use-client-cache.ts +0 -58
- package/src/browser/shallow.ts +0 -40
- package/src/handles/index.ts +0 -7
- package/src/network-error-thrower.tsx +0 -23
- package/src/router/middleware-cookies.ts +0 -55
|
@@ -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,
|
|
@@ -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,13 +210,19 @@ 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
|
|
201
|
-
<K extends keyof
|
|
219
|
+
<K extends keyof Rango.GeneratedRouteMap & string>(
|
|
202
220
|
slotName: `@${string}`,
|
|
203
221
|
routeName: K,
|
|
204
|
-
handler: ReactNode | Handler<K,
|
|
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
|
};
|
|
@@ -250,8 +271,10 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
|
|
|
250
271
|
* )
|
|
251
272
|
*
|
|
252
273
|
* // Revalidate after specific actions (actionId format: "path/to/file.ts#exportName")
|
|
274
|
+
* // Use `|| undefined` (defer), not `?? false` (hard short-circuit), so the
|
|
275
|
+
* // chain and the segment default still apply when there is no match.
|
|
253
276
|
* revalidate(({ actionId }) =>
|
|
254
|
-
* actionId?.includes("Cart")
|
|
277
|
+
* actionId?.includes("Cart") || undefined
|
|
255
278
|
* )
|
|
256
279
|
*
|
|
257
280
|
* // Soft decision (suggest but allow override)
|
|
@@ -259,7 +282,12 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
|
|
|
259
282
|
* ({ defaultShouldRevalidate: true })
|
|
260
283
|
* )
|
|
261
284
|
* ```
|
|
262
|
-
* @param fn - Function
|
|
285
|
+
* @param fn - Function returning either:
|
|
286
|
+
* - `boolean` (hard decision — short-circuits the chain),
|
|
287
|
+
* - `{ defaultShouldRevalidate: boolean }` (soft — updates the suggestion
|
|
288
|
+
* for downstream revalidators),
|
|
289
|
+
* - or nothing / `null` / `undefined` (defer — leaves the suggestion
|
|
290
|
+
* unchanged and continues to the next revalidator).
|
|
263
291
|
*/
|
|
264
292
|
revalidate: (fn: ShouldRevalidateFn<any, TEnv>) => RevalidateItem;
|
|
265
293
|
/**
|
|
@@ -269,7 +297,7 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
|
|
|
269
297
|
*
|
|
270
298
|
* // With loader-specific revalidation (match by file or export name)
|
|
271
299
|
* loader(CartLoader, () => [
|
|
272
|
-
* revalidate(({ actionId }) => actionId?.includes("Cart")
|
|
300
|
+
* revalidate(({ actionId }) => actionId?.includes("Cart") || undefined),
|
|
273
301
|
* ])
|
|
274
302
|
*
|
|
275
303
|
* // Consume in client components with useLoader()
|
|
@@ -340,40 +368,6 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
|
|
|
340
368
|
notFoundBoundary: (
|
|
341
369
|
fallback: ReactNode | NotFoundBoundaryHandler,
|
|
342
370
|
) => 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
371
|
/**
|
|
378
372
|
* Define cache configuration for segments
|
|
379
373
|
*
|
|
@@ -434,18 +428,43 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
|
|
|
434
428
|
cache: {
|
|
435
429
|
(): CacheItem;
|
|
436
430
|
(children: () => UseItems<AllUseItems>): CacheItem;
|
|
437
|
-
(profileName: string): CacheItem;
|
|
438
|
-
(profileName: string, use: () => UseItems<AllUseItems>): CacheItem;
|
|
439
431
|
(
|
|
440
|
-
options: PartialCacheOptions | false,
|
|
432
|
+
options: PartialCacheOptions<TEnv> | false,
|
|
441
433
|
use?: () => UseItems<AllUseItems>,
|
|
442
434
|
): CacheItem;
|
|
443
435
|
};
|
|
444
436
|
/**
|
|
445
|
-
*
|
|
446
|
-
*
|
|
447
|
-
*
|
|
448
|
-
*
|
|
437
|
+
* Opt a route (or group of routes) into transition-driven navigation.
|
|
438
|
+
*
|
|
439
|
+
* `transition()` does two independent things, and you choose how far to go:
|
|
440
|
+
* 1. startTransition (ALL React versions): the navigation commit is driven
|
|
441
|
+
* through React's startTransition, so a same-route nav (same route,
|
|
442
|
+
* different params, e.g. /product/1 -> /product/2) holds the previous
|
|
443
|
+
* content while the new loader resolves instead of flashing the route's
|
|
444
|
+
* loading() skeleton (see segment-system.tsx inTransitionScope). This is
|
|
445
|
+
* also the precondition for any view-transition animation.
|
|
446
|
+
* 2. <ViewTransition> (experimental React only): the segment content is also
|
|
447
|
+
* wrapped in React's <ViewTransition>, so the held swap cross-fades/morphs.
|
|
448
|
+
* Layered on by default; pass { viewTransition: false } to keep #1 without
|
|
449
|
+
* the router boundary (and place your own <ViewTransition> instead).
|
|
450
|
+
*
|
|
451
|
+
* A view transition cannot fire without a startTransition, so the meaningful
|
|
452
|
+
* choices are (see skills/view-transitions for the full matrix):
|
|
453
|
+
* - no transition() -> neither (remount + skeleton)
|
|
454
|
+
* - transition({ viewTransition: false }) -> startTransition only (hold)
|
|
455
|
+
* - transition({}) / transition({ enter… }) -> startTransition + ViewTransition
|
|
456
|
+
*
|
|
457
|
+
* Precedence: a bare transition({}) inherits createRouter({ viewTransition })
|
|
458
|
+
* (default "auto"); an explicit per-route `viewTransition` always wins. So
|
|
459
|
+
* transition({}) is startTransition + ViewTransition under the default and
|
|
460
|
+
* startTransition only when the router sets viewTransition: false.
|
|
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 })`).
|
|
449
468
|
*
|
|
450
469
|
* ```typescript
|
|
451
470
|
* // Attach to a single route
|
|
@@ -459,16 +478,25 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
|
|
|
459
478
|
* path("/about", AboutPage),
|
|
460
479
|
* ])
|
|
461
480
|
*
|
|
462
|
-
* //
|
|
463
|
-
*
|
|
464
|
-
*
|
|
465
|
-
*
|
|
466
|
-
*
|
|
481
|
+
* // Hold content + drive view transitions, but place no router boundary:
|
|
482
|
+
* path("/product/:id", ProductPage, { name: "product" }, () => [
|
|
483
|
+
* transition({ viewTransition: false }),
|
|
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
|
+
* ])
|
|
467
490
|
* ```
|
|
468
|
-
* @param config - ViewTransition configuration (enter, exit, update, share,
|
|
491
|
+
* @param config - ViewTransition configuration (enter, exit, update, share,
|
|
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
|
|
469
495
|
* @param children - Optional callback returning child routes to wrap
|
|
470
496
|
*/
|
|
471
497
|
transition: {
|
|
498
|
+
(): TransitionItem;
|
|
499
|
+
(children: () => UseItems<AllUseItems>): TransitionItem;
|
|
472
500
|
(config: TransitionConfig): TransitionItem;
|
|
473
501
|
(
|
|
474
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
|
}
|
|
@@ -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
|
+
);
|