@rangojs/router 0.0.0-experimental.32 → 0.0.0-experimental.3232cd17
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 +4 -0
- package/README.md +198 -44
- package/dist/bin/rango.js +287 -105
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +3248 -1117
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +73 -21
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +107 -1
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +245 -21
- package/skills/caching/SKILL.md +302 -6
- package/skills/composability/SKILL.md +27 -2
- package/skills/css/SKILL.md +76 -0
- package/skills/document-cache/SKILL.md +78 -55
- package/skills/handler-use/SKILL.md +364 -0
- package/skills/hooks/SKILL.md +270 -30
- package/skills/host-router/SKILL.md +82 -22
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +49 -5
- package/skills/layout/SKILL.md +35 -9
- package/skills/links/SKILL.md +249 -17
- package/skills/loader/SKILL.md +294 -30
- package/skills/middleware/SKILL.md +52 -13
- package/skills/migrate-nextjs/SKILL.md +584 -0
- package/skills/migrate-react-router/SKILL.md +769 -0
- package/skills/mime-routes/SKILL.md +27 -0
- package/skills/observability/SKILL.md +137 -0
- package/skills/parallel/SKILL.md +203 -7
- package/skills/prerender/SKILL.md +123 -100
- package/skills/rango/SKILL.md +250 -22
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +122 -47
- package/skills/route/SKILL.md +97 -5
- package/skills/router-setup/SKILL.md +90 -5
- package/skills/server-actions/SKILL.md +775 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/tailwind/SKILL.md +27 -3
- package/skills/testing/SKILL.md +129 -0
- package/skills/testing/bindings.md +89 -0
- package/skills/testing/cache-prerender.md +124 -0
- package/skills/testing/client-components.md +122 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +92 -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 +121 -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 +120 -0
- package/skills/typesafety/SKILL.md +329 -27
- package/skills/use-cache/SKILL.md +36 -5
- package/skills/view-transitions/SKILL.md +294 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +116 -0
- package/src/__internal.ts +67 -40
- 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/app-version.ts +14 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +86 -147
- 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/link-interceptor.ts +4 -0
- package/src/browser/navigation-bridge.ts +148 -19
- package/src/browser/navigation-client.ts +187 -67
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +76 -67
- package/src/browser/navigation-transaction.ts +18 -66
- package/src/browser/partial-update.ts +123 -94
- package/src/browser/prefetch/cache.ts +214 -36
- package/src/browser/prefetch/fetch.ts +260 -38
- package/src/browser/prefetch/policy.ts +6 -0
- package/src/browser/prefetch/queue.ts +126 -20
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +158 -76
- package/src/browser/react/Link.tsx +93 -11
- package/src/browser/react/NavigationProvider.tsx +115 -34
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/context.ts +7 -2
- package/src/browser/react/filter-segment-order.ts +49 -7
- package/src/browser/react/index.ts +0 -48
- package/src/browser/react/location-state-shared.ts +166 -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 +23 -69
- package/src/browser/react/use-link-status.ts +0 -4
- package/src/browser/react/use-navigation.ts +22 -5
- 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 +46 -11
- 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 +52 -1
- package/src/browser/rsc-router.tsx +215 -76
- package/src/browser/scroll-restoration.ts +46 -39
- package/src/browser/segment-reconciler.ts +36 -9
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +176 -50
- package/src/browser/types.ts +95 -11
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +65 -40
- package/src/build/generate-route-types.ts +5 -0
- package/src/build/index.ts +8 -2
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +137 -32
- package/src/build/route-types/codegen.ts +4 -4
- package/src/build/route-types/include-resolution.ts +9 -2
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/per-module-writer.ts +7 -4
- package/src/build/route-types/router-processing.ts +278 -96
- package/src/build/route-types/scan-filter.ts +9 -2
- package/src/build/route-types/source-scan.ts +118 -0
- package/src/build/runtime-discovery.ts +9 -20
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-policy.ts +68 -28
- package/src/cache/cache-runtime.ts +149 -43
- package/src/cache/cache-scope.ts +148 -81
- package/src/cache/cache-tag.ts +98 -0
- package/src/cache/cf/cf-cache-store.ts +2550 -93
- package/src/cache/cf/index.ts +11 -17
- package/src/cache/document-cache.ts +78 -27
- package/src/cache/handle-snapshot.ts +63 -0
- package/src/cache/index.ts +23 -20
- package/src/cache/memory-segment-store.ts +136 -37
- package/src/cache/profile-registry.ts +6 -30
- package/src/cache/read-through-swr.ts +41 -11
- package/src/cache/segment-codec.ts +0 -16
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/taint.ts +55 -0
- package/src/cache/types.ts +33 -100
- package/src/cache/vercel/index.ts +11 -0
- package/src/cache/vercel/vercel-cache-store.ts +799 -0
- package/src/client.rsc.tsx +6 -21
- package/src/client.tsx +108 -290
- package/src/component-utils.ts +19 -0
- package/src/context-var.ts +84 -2
- package/src/debug.ts +2 -2
- package/src/decode-loader-results.ts +36 -0
- package/src/defer.ts +196 -0
- package/src/deps/ssr.ts +0 -1
- package/src/errors.ts +30 -4
- package/src/handle.ts +70 -22
- package/src/handles/MetaTags.tsx +0 -14
- package/src/handles/breadcrumbs.ts +16 -5
- package/src/handles/meta.ts +0 -39
- package/src/host/cookie-handler.ts +0 -36
- package/src/host/errors.ts +0 -24
- package/src/host/index.ts +8 -2
- package/src/host/pattern-matcher.ts +7 -50
- 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 +52 -26
- package/src/index.ts +100 -38
- package/src/internal-debug.ts +2 -4
- 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/network-error-thrower.tsx +1 -6
- package/src/outlet-context.ts +1 -1
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +10 -11
- package/src/prerender/store.ts +37 -41
- package/src/prerender.ts +198 -82
- package/src/redirect-origin.ts +100 -0
- package/src/response-utils.ts +37 -0
- package/src/reverse.ts +65 -15
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +7 -72
- package/src/route-definition/dsl-helpers.ts +437 -274
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +113 -37
- package/src/route-definition/index.ts +3 -0
- package/src/route-definition/redirect.ts +52 -10
- package/src/route-definition/resolve-handler-use.ts +161 -0
- package/src/route-definition/use-item-types.ts +32 -0
- package/src/route-map-builder.ts +7 -17
- package/src/route-types.ts +37 -41
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +108 -9
- package/src/router/error-handling.ts +13 -17
- package/src/router/find-match.ts +45 -22
- package/src/router/handler-context.ts +83 -41
- package/src/router/intercept-resolution.ts +25 -23
- package/src/router/lazy-includes.ts +19 -53
- package/src/router/loader-resolution.ts +213 -30
- package/src/router/logging.ts +5 -8
- package/src/router/manifest.ts +49 -45
- package/src/router/match-api.ts +120 -204
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +58 -58
- package/src/router/match-middleware/background-revalidation.ts +27 -6
- package/src/router/match-middleware/cache-lookup.ts +205 -249
- package/src/router/match-middleware/cache-store.ts +45 -32
- package/src/router/match-middleware/intercept-resolution.ts +8 -28
- package/src/router/match-middleware/segment-resolution.ts +52 -18
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +104 -40
- package/src/router/metrics.ts +5 -34
- package/src/router/middleware-types.ts +13 -142
- package/src/router/middleware.ts +173 -143
- package/src/router/navigation-snapshot.ts +131 -0
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +109 -63
- package/src/router/prerender-match.ts +190 -54
- package/src/router/preview-match.ts +32 -102
- package/src/router/request-classification.ts +276 -0
- package/src/router/revalidation.ts +63 -55
- package/src/router/route-snapshot.ts +244 -0
- package/src/router/router-context.ts +6 -28
- package/src/router/router-interfaces.ts +100 -35
- package/src/router/router-options.ts +91 -11
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +242 -75
- package/src/router/segment-resolution/helpers.ts +63 -24
- package/src/router/segment-resolution/loader-cache.ts +41 -37
- package/src/router/segment-resolution/revalidation.ts +456 -372
- 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 +36 -0
- package/src/router/segment-resolution.ts +4 -1
- package/src/router/segment-wrappers.ts +2 -3
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry-otel.ts +0 -20
- package/src/router/telemetry.ts +96 -19
- package/src/router/timeout.ts +0 -20
- package/src/router/trie-matching.ts +91 -46
- package/src/router/types.ts +10 -63
- package/src/router/url-params.ts +44 -0
- package/src/router.ts +134 -43
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +492 -383
- package/src/rsc/helpers.ts +162 -46
- package/src/rsc/index.ts +1 -1
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +23 -3
- package/src/rsc/manifest-init.ts +33 -42
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +30 -3
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +90 -63
- package/src/rsc/rsc-rendering.ts +56 -54
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +74 -67
- package/src/rsc/ssr-setup.ts +18 -2
- package/src/rsc/types.ts +25 -6
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +4 -20
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +134 -0
- package/src/segment-system.tsx +272 -129
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +309 -61
- package/src/server/cookie-store.ts +80 -5
- package/src/server/handle-store.ts +26 -24
- package/src/server/loader-registry.ts +10 -28
- package/src/server/request-context.ts +338 -126
- package/src/ssr/index.tsx +23 -15
- package/src/static-handler.ts +27 -18
- package/src/testing/cache-status.ts +162 -0
- package/src/testing/collect-handle.ts +40 -0
- package/src/testing/dispatch.ts +618 -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 +232 -0
- package/src/testing/generated-routes.ts +183 -0
- package/src/testing/index.ts +99 -0
- package/src/testing/internal/context.ts +348 -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 +330 -0
- package/src/testing/render-route.tsx +566 -0
- package/src/testing/run-loader.ts +378 -0
- package/src/testing/run-middleware.ts +205 -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 +0 -52
- package/src/theme/ThemeScript.tsx +0 -6
- package/src/theme/constants.ts +0 -12
- package/src/theme/index.ts +0 -7
- package/src/theme/theme-context.ts +1 -5
- package/src/theme/theme-script.ts +0 -14
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +0 -35
- package/src/types/cache-types.ts +17 -8
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +54 -41
- package/src/types/handler-context.ts +233 -81
- package/src/types/index.ts +1 -10
- package/src/types/loader-types.ts +44 -15
- package/src/types/request-scope.ts +107 -0
- package/src/types/route-config.ts +6 -50
- package/src/types/route-entry.ts +19 -7
- package/src/types/segments.ts +37 -14
- package/src/urls/include-helper.ts +33 -70
- package/src/urls/index.ts +1 -11
- package/src/urls/path-helper-types.ts +58 -11
- package/src/urls/path-helper.ts +57 -111
- package/src/urls/pattern-types.ts +48 -19
- package/src/urls/response-types.ts +25 -22
- package/src/urls/type-extraction.ts +58 -139
- package/src/urls/urls-function.ts +1 -18
- package/src/use-loader.tsx +346 -89
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +36 -38
- package/src/vite/discovery/discover-routers.ts +130 -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 +192 -99
- 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 +51 -6
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +8 -0
- package/src/vite/plugin-types.ts +187 -69
- package/src/vite/plugins/cjs-to-esm.ts +8 -18
- 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 +49 -98
- package/src/vite/plugins/expose-id-utils.ts +11 -50
- package/src/vite/plugins/expose-ids/export-analysis.ts +76 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +10 -48
- package/src/vite/plugins/expose-ids/loader-transform.ts +3 -20
- package/src/vite/plugins/expose-ids/router-transform.ts +20 -16
- package/src/vite/plugins/expose-internal-ids.ts +554 -317
- package/src/vite/plugins/performance-tracks.ts +89 -0
- package/src/vite/plugins/refresh-cmd.ts +89 -27
- package/src/vite/plugins/use-cache-transform.ts +73 -83
- package/src/vite/plugins/vercel-output.ts +258 -0
- package/src/vite/plugins/version-injector.ts +21 -25
- package/src/vite/plugins/version-plugin.ts +41 -20
- package/src/vite/plugins/virtual-entries.ts +2 -17
- package/src/vite/rango.ts +257 -289
- package/src/vite/router-discovery.ts +930 -140
- package/src/vite/utils/ast-handler-extract.ts +15 -31
- package/src/vite/utils/banner.ts +4 -4
- package/src/vite/utils/bundle-analysis.ts +10 -15
- package/src/vite/utils/client-chunks.ts +184 -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 +27 -29
- package/src/vite/utils/shared-utils.ts +92 -42
- 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/router/middleware-cookies.ts +0 -55
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime-neutral same-origin redirect rule.
|
|
3
|
+
*
|
|
4
|
+
* Shared by the client redirect guard (`browser/validate-redirect-origin.ts`,
|
|
5
|
+
* which validates redirect targets the client JS is about to navigate to) and
|
|
6
|
+
* the server outgoing-redirect guard (`rsc/redirect-guard.ts`, which validates
|
|
7
|
+
* every browser-followed `Location` header before it leaves the handler). Kept
|
|
8
|
+
* at the `src/` root so both layers import the ONE rule and cannot drift -- a
|
|
9
|
+
* cross-origin target blocked on the JS/fetch path is blocked identically on the
|
|
10
|
+
* no-JS (PE) and full-page document paths.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Resolve a redirect target against the current origin.
|
|
15
|
+
*
|
|
16
|
+
* Returns the canonical (normalized) same-origin href -- which also collapses
|
|
17
|
+
* protocol-relative (`//evil.com`) and other ambiguous forms -- or `null` when
|
|
18
|
+
* the target resolves to a different origin or is unparseable. Pure: no logging,
|
|
19
|
+
* no side effects.
|
|
20
|
+
*/
|
|
21
|
+
export function resolveSameOriginRedirect(
|
|
22
|
+
url: string,
|
|
23
|
+
currentOrigin: string,
|
|
24
|
+
): string | null {
|
|
25
|
+
try {
|
|
26
|
+
const target = new URL(url, currentOrigin);
|
|
27
|
+
if (target.origin !== currentOrigin) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
return target.href;
|
|
31
|
+
} catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Validate an explicit off-origin redirect target (`redirect(url, { external:
|
|
38
|
+
* true })`).
|
|
39
|
+
*
|
|
40
|
+
* `external` opts out of the same-origin rule, but NOT out of scheme safety:
|
|
41
|
+
* only `http:`/`https:` targets are allowed. A redirect ultimately reaches the
|
|
42
|
+
* browser via `window.location.assign()` on the SPA/action client paths, so a
|
|
43
|
+
* forged or mistaken `redirect("javascript:...", { external: true })` would be a
|
|
44
|
+
* scriptable navigation if the scheme were not checked here. Returns the
|
|
45
|
+
* normalized href for an http(s) target (same- or cross-origin), or `null`
|
|
46
|
+
* otherwise. Pure: no logging, no side effects.
|
|
47
|
+
*/
|
|
48
|
+
export function resolveExternalRedirect(
|
|
49
|
+
url: string,
|
|
50
|
+
currentOrigin: string,
|
|
51
|
+
): string | null {
|
|
52
|
+
try {
|
|
53
|
+
const target = new URL(url, currentOrigin);
|
|
54
|
+
if (target.protocol !== "http:" && target.protocol !== "https:") {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
return target.href;
|
|
58
|
+
} catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Out-of-band brand for `redirect(url, { external: true })`.
|
|
65
|
+
*
|
|
66
|
+
* The external opt-in MUST be settable only by app code calling `redirect(...,
|
|
67
|
+
* { external: true })`, never by an attacker. An earlier design carried the
|
|
68
|
+
* opt-in as a wire header (`x-rango-redirect-external`), but a wire header is
|
|
69
|
+
* forgeable: a proxy-style response route that copies an attacker-controlled
|
|
70
|
+
* upstream response's headers would let `302 Location: https://evil` plus that
|
|
71
|
+
* header bypass the same-origin guard without app code ever opting in. So the
|
|
72
|
+
* opt-in is now an out-of-band brand on the Response object itself, tracked in a
|
|
73
|
+
* `WeakSet` that cannot cross the wire. `redirect()` brands the Response; the
|
|
74
|
+
* small set of internal redirect-rebuild paths (middleware `mergeResponse`,
|
|
75
|
+
* `carryOverRedirectHeaders`, the response-route rewrap) transfer the brand onto
|
|
76
|
+
* the rebuilt Response; the guard and the SPA intercept read it. An upstream
|
|
77
|
+
* Response an app proxies is never branded, so its forged header is inert.
|
|
78
|
+
*
|
|
79
|
+
* Fail-closed: if a rebuild path ever drops the brand, the redirect is
|
|
80
|
+
* neutralized to the app root rather than allowed off-host.
|
|
81
|
+
*/
|
|
82
|
+
const externalRedirects = new WeakSet<Response>();
|
|
83
|
+
|
|
84
|
+
/** Brand a Response as an explicit `{ external: true }` redirect (out-of-band). */
|
|
85
|
+
export function markExternalRedirect(response: Response): void {
|
|
86
|
+
externalRedirects.add(response);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Read the out-of-band `{ external: true }` brand off a Response. */
|
|
90
|
+
export function isExternalRedirect(response: Response): boolean {
|
|
91
|
+
return externalRedirects.has(response);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Reserved internal header name. No longer a trust signal -- the external
|
|
96
|
+
* opt-in is the out-of-band brand above. It is kept only so the redirect-rebuild
|
|
97
|
+
* paths and the guard can defensively strip any value (e.g. one forged by a
|
|
98
|
+
* proxied upstream) and guarantee it never reaches the browser.
|
|
99
|
+
*/
|
|
100
|
+
export const EXTERNAL_REDIRECT_MARKER: string = "x-rango-redirect-external";
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime-neutral Response shape utilities.
|
|
3
|
+
*
|
|
4
|
+
* Kept at the src/ root so both `router/` and `rsc/` can depend on it
|
|
5
|
+
* without creating a cross-layer import cycle.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* True when a Response represents a WebSocket upgrade handoff and must not
|
|
10
|
+
* be reconstructed or mutated:
|
|
11
|
+
*
|
|
12
|
+
* - Status 101 (Switching Protocols) is outside the standard Response
|
|
13
|
+
* constructor's 200–599 range, so `new Response(body, { status: 101 })`
|
|
14
|
+
* throws RangeError on Node/undici and any spec-compliant runtime.
|
|
15
|
+
* - Cloudflare's workerd attaches a non-standard `webSocket` property on
|
|
16
|
+
* the upgrade Response (e.g. from `acceptWebSocket`/`handleWebSocketUpgrade`
|
|
17
|
+
* or the `agents` library's `routeAgentRequest`). That property is dropped
|
|
18
|
+
* by a `new Response(...)` copy, breaking the upgrade even on workerd
|
|
19
|
+
* where the status range is relaxed.
|
|
20
|
+
*
|
|
21
|
+
* Callers should short-circuit header/body merges for these responses.
|
|
22
|
+
*/
|
|
23
|
+
export function isWebSocketUpgradeResponse(response: Response): boolean {
|
|
24
|
+
return (
|
|
25
|
+
response.status === 101 ||
|
|
26
|
+
(response as unknown as { webSocket?: unknown }).webSocket != null
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Location truthiness (not presence) so an empty `Location: ""` is not a redirect.
|
|
31
|
+
export function isRedirectResponse(response: Response): boolean {
|
|
32
|
+
return (
|
|
33
|
+
response.status >= 300 &&
|
|
34
|
+
response.status < 400 &&
|
|
35
|
+
Boolean(response.headers.get("Location"))
|
|
36
|
+
);
|
|
37
|
+
}
|
package/src/reverse.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ExtractParams } from "./types.js";
|
|
2
2
|
import type { SearchSchema, ResolveSearchSchema } from "./search-params.js";
|
|
3
3
|
import { serializeSearchParams } from "./search-params.js";
|
|
4
|
+
import { substitutePatternParams } from "./router/substitute-pattern-params.js";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Sanitize prefix string by removing leading slash
|
|
@@ -218,6 +219,67 @@ export type ExtractLocalRoutes<TPatterns> = TPatterns extends {
|
|
|
218
219
|
? TPatterns
|
|
219
220
|
: Record<string, string>;
|
|
220
221
|
|
|
222
|
+
/**
|
|
223
|
+
* Params accepted by `useReverse(routes)`. The route's own params are
|
|
224
|
+
* required, and additional string keys are permitted so callers can
|
|
225
|
+
* override values that would otherwise be auto-filled from the matched
|
|
226
|
+
* route's `useParams()` (e.g. an enclosing `:tenantId` mount segment).
|
|
227
|
+
*/
|
|
228
|
+
export type LocalReverseParams<TPattern extends string> =
|
|
229
|
+
ExtractParams<TPattern> & {
|
|
230
|
+
readonly [extra: string]: string | undefined;
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Type-safe local reverse function.
|
|
235
|
+
*
|
|
236
|
+
* Returned by `useReverse(routes)` on the client. The route map is the
|
|
237
|
+
* exposure boundary (a generated `routes` from a `urls()` module) and the
|
|
238
|
+
* scope is implicit from that import. Names may be written with or without a
|
|
239
|
+
* leading dot — `reverse("post")` and `reverse(".post")` are identical. The dot
|
|
240
|
+
* is a cosmetic readability convention (and parity with `ctx.reverse(".name")`);
|
|
241
|
+
* there is no separate global namespace here, so it carries no meaning.
|
|
242
|
+
*
|
|
243
|
+
* @example
|
|
244
|
+
* ```typescript
|
|
245
|
+
* const reverse = useReverse(blogRoutes);
|
|
246
|
+
* reverse("index"); // ✓ no params (dot optional)
|
|
247
|
+
* reverse(".index"); // ✓ identical to the above
|
|
248
|
+
* reverse("post", { postId: "hello" }); // ✓ with params
|
|
249
|
+
* reverse("search", {}, { q: "hi" }); // ✓ with search schema
|
|
250
|
+
* reverse("typo"); // ✗ compile error
|
|
251
|
+
* ```
|
|
252
|
+
*/
|
|
253
|
+
export type LocalReverseFunction<TLocalRoutes> = {
|
|
254
|
+
/**
|
|
255
|
+
* Route without params (leading dot optional)
|
|
256
|
+
*/
|
|
257
|
+
<TName extends keyof TLocalRoutes & string>(
|
|
258
|
+
name: IsEmptyObject<
|
|
259
|
+
ExtractParams<RoutePatternFor<TLocalRoutes, TName>>
|
|
260
|
+
> extends true
|
|
261
|
+
? TName | `.${TName}`
|
|
262
|
+
: never,
|
|
263
|
+
): string;
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Route with params (leading dot optional)
|
|
267
|
+
*/
|
|
268
|
+
<TName extends keyof TLocalRoutes & string>(
|
|
269
|
+
name: TName | `.${TName}`,
|
|
270
|
+
params: LocalReverseParams<RoutePatternFor<TLocalRoutes, TName>>,
|
|
271
|
+
): string;
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Route with params and search (leading dot optional)
|
|
275
|
+
*/
|
|
276
|
+
<TName extends keyof TLocalRoutes & string>(
|
|
277
|
+
name: TName | `.${TName}`,
|
|
278
|
+
params: LocalReverseParams<RoutePatternFor<TLocalRoutes, TName>>,
|
|
279
|
+
search: ResolveSearchSchema<ExtractSearchSchema<TLocalRoutes, TName>>,
|
|
280
|
+
): string;
|
|
281
|
+
};
|
|
282
|
+
|
|
221
283
|
/**
|
|
222
284
|
* Extract the response data type for a named route from a UrlPatterns instance.
|
|
223
285
|
* Re-exported from urls.ts for consumer convenience.
|
|
@@ -301,21 +363,9 @@ export function createReverse<TRoutes extends Record<string, string>>(
|
|
|
301
363
|
throw new Error(`Unknown route: ${name}`);
|
|
302
364
|
}
|
|
303
365
|
|
|
304
|
-
let result =
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
// Strip constraint syntax: :param(a|b) -> use "param" as key
|
|
308
|
-
result = result.replace(
|
|
309
|
-
/:([a-zA-Z_][a-zA-Z0-9_]*)(\([^)]*\))?\??/g,
|
|
310
|
-
(_, key) => {
|
|
311
|
-
const value = params[key];
|
|
312
|
-
if (value === undefined) {
|
|
313
|
-
throw new Error(`Missing param "${key}" for route "${name}"`);
|
|
314
|
-
}
|
|
315
|
-
return encodeURIComponent(value);
|
|
316
|
-
},
|
|
317
|
-
);
|
|
318
|
-
}
|
|
366
|
+
let result = params
|
|
367
|
+
? substitutePatternParams(pattern, params, name)
|
|
368
|
+
: pattern;
|
|
319
369
|
|
|
320
370
|
// Append search params as query string
|
|
321
371
|
if (search) {
|
|
@@ -3,26 +3,17 @@
|
|
|
3
3
|
import { Component, useState, type ReactNode } from "react";
|
|
4
4
|
import type { ClientErrorBoundaryFallbackProps } from "./types.js";
|
|
5
5
|
|
|
6
|
-
/**
|
|
7
|
-
* Check if an error is a network-related error
|
|
8
|
-
*/
|
|
9
6
|
function isNetworkError(error: Error): boolean {
|
|
10
7
|
return error.name === "NetworkError";
|
|
11
8
|
}
|
|
12
9
|
|
|
13
|
-
/**
|
|
14
|
-
* Network error fallback UI with retry functionality
|
|
15
|
-
* Shows a connection-specific message and allows retrying via page refresh
|
|
16
|
-
*/
|
|
17
10
|
function NetworkErrorFallback({
|
|
18
11
|
error,
|
|
19
|
-
reset,
|
|
20
12
|
}: ClientErrorBoundaryFallbackProps): ReactNode {
|
|
21
13
|
const [isRetrying, setIsRetrying] = useState(false);
|
|
22
14
|
|
|
23
15
|
const handleRetry = (): void => {
|
|
24
16
|
setIsRetrying(true);
|
|
25
|
-
// Refresh the page to retry the request
|
|
26
17
|
window.location.reload();
|
|
27
18
|
};
|
|
28
19
|
|
|
@@ -42,7 +33,6 @@ function NetworkErrorFallback({
|
|
|
42
33
|
marginBottom: "1rem",
|
|
43
34
|
}}
|
|
44
35
|
>
|
|
45
|
-
{/* Simple cloud with x icon using CSS */}
|
|
46
36
|
<span style={{ color: "#9ca3af" }}>☁</span>
|
|
47
37
|
</div>
|
|
48
38
|
<h1
|
|
@@ -101,10 +91,6 @@ function NetworkErrorFallback({
|
|
|
101
91
|
);
|
|
102
92
|
}
|
|
103
93
|
|
|
104
|
-
/**
|
|
105
|
-
* Default fallback UI for root error boundary
|
|
106
|
-
* This is shown when an unhandled error bubbles up to the root
|
|
107
|
-
*/
|
|
108
94
|
function RootErrorFallback({
|
|
109
95
|
error,
|
|
110
96
|
reset,
|
|
@@ -230,7 +216,6 @@ export class RootErrorBoundary extends Component<
|
|
|
230
216
|
}
|
|
231
217
|
|
|
232
218
|
componentDidMount(): void {
|
|
233
|
-
// Listen for popstate (back/forward navigation) to reset error state
|
|
234
219
|
window.addEventListener("popstate", this.handlePopState);
|
|
235
220
|
}
|
|
236
221
|
|
|
@@ -247,15 +232,13 @@ export class RootErrorBoundary extends Component<
|
|
|
247
232
|
}
|
|
248
233
|
|
|
249
234
|
componentDidUpdate(prevProps: { children: ReactNode }): void {
|
|
250
|
-
// Reset error
|
|
251
|
-
// This allows the app to recover after navigation away from an errored route
|
|
235
|
+
// Reset error on children change (navigation).
|
|
252
236
|
if (this.state.hasError && prevProps.children !== this.props.children) {
|
|
253
237
|
this.setState({ hasError: false, error: null });
|
|
254
238
|
}
|
|
255
239
|
}
|
|
256
240
|
|
|
257
241
|
handlePopState = (): void => {
|
|
258
|
-
// Reset error state on back/forward navigation
|
|
259
242
|
if (this.state.hasError) {
|
|
260
243
|
this.setState({ hasError: false, error: null });
|
|
261
244
|
}
|
|
@@ -276,7 +259,6 @@ export class RootErrorBoundary extends Component<
|
|
|
276
259
|
segmentType: "route" as const,
|
|
277
260
|
};
|
|
278
261
|
|
|
279
|
-
// Use specialized fallback for network errors
|
|
280
262
|
if (isNetworkError(this.state.error)) {
|
|
281
263
|
return <NetworkErrorFallback error={errorInfo} reset={this.reset} />;
|
|
282
264
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import type { ReactNode } from "react";
|
|
3
|
-
import { Suspense, use
|
|
3
|
+
import { Suspense, use } from "react";
|
|
4
4
|
import { invariant } from "./errors";
|
|
5
5
|
import { OutletProvider } from "./outlet-provider.js";
|
|
6
6
|
import type { ResolvedSegment } from "./types.js";
|
|
7
|
-
import {
|
|
7
|
+
import { decodeLoaderResults } from "./decode-loader-results.js";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Stable async wrapper component for route content
|
|
@@ -26,10 +26,6 @@ export function RouteContentWrapper({
|
|
|
26
26
|
fallback?: ReactNode;
|
|
27
27
|
segmentId?: string;
|
|
28
28
|
}): ReactNode {
|
|
29
|
-
if (!content) {
|
|
30
|
-
// Already resolved
|
|
31
|
-
return content as ReactNode;
|
|
32
|
-
}
|
|
33
29
|
return (
|
|
34
30
|
<Suspense
|
|
35
31
|
fallback={fallback ?? null}
|
|
@@ -40,37 +36,6 @@ export function RouteContentWrapper({
|
|
|
40
36
|
);
|
|
41
37
|
}
|
|
42
38
|
|
|
43
|
-
export function RouteContentWrapperCallback<T>({
|
|
44
|
-
resolve,
|
|
45
|
-
fallback,
|
|
46
|
-
children,
|
|
47
|
-
}: {
|
|
48
|
-
resolve: Promise<T> | T;
|
|
49
|
-
fallback?: ReactNode;
|
|
50
|
-
children: (data: T) => ReactNode;
|
|
51
|
-
}): ReactNode {
|
|
52
|
-
const id = useId();
|
|
53
|
-
invariant(children, "RouteContentWrapperCallback requires children");
|
|
54
|
-
invariant(
|
|
55
|
-
typeof children === "function",
|
|
56
|
-
"RouteContentWrapperCallback requires children to be a function",
|
|
57
|
-
);
|
|
58
|
-
invariant(
|
|
59
|
-
resolve !== undefined,
|
|
60
|
-
"RouteContentWrapperCallback requires resolve",
|
|
61
|
-
);
|
|
62
|
-
return (
|
|
63
|
-
<Suspense
|
|
64
|
-
fallback={fallback ?? null}
|
|
65
|
-
key={"route-content-suspense-callback-" + id}
|
|
66
|
-
>
|
|
67
|
-
<SuspenderCallback resolve={resolve} key={id}>
|
|
68
|
-
{children}
|
|
69
|
-
</SuspenderCallback>
|
|
70
|
-
</Suspense>
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
39
|
const Suspender = ({
|
|
75
40
|
content,
|
|
76
41
|
}: {
|
|
@@ -81,18 +46,6 @@ const Suspender = ({
|
|
|
81
46
|
return use(content);
|
|
82
47
|
};
|
|
83
48
|
|
|
84
|
-
const SuspenderCallback = <T,>({
|
|
85
|
-
resolve,
|
|
86
|
-
children,
|
|
87
|
-
}: {
|
|
88
|
-
resolve: Promise<T> | T;
|
|
89
|
-
children: (data: T) => ReactNode;
|
|
90
|
-
}): ReactNode => {
|
|
91
|
-
return resolve instanceof Promise
|
|
92
|
-
? children(use(resolve))
|
|
93
|
-
: children(resolve);
|
|
94
|
-
};
|
|
95
|
-
|
|
96
49
|
/**
|
|
97
50
|
* LoaderBoundary - Client component that resolves loader promises and renders OutletProvider
|
|
98
51
|
*
|
|
@@ -159,28 +112,10 @@ function LoaderResolver({
|
|
|
159
112
|
? use(loaderDataPromise)
|
|
160
113
|
: loaderDataPromise;
|
|
161
114
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
loaderIds.forEach((id, i) => {
|
|
167
|
-
const result = resolvedData[i];
|
|
168
|
-
|
|
169
|
-
if (isLoaderDataResult(result)) {
|
|
170
|
-
if (result.ok) {
|
|
171
|
-
loaderData[id] = result.data;
|
|
172
|
-
} else {
|
|
173
|
-
if (result.fallback) {
|
|
174
|
-
loaderErrorFallback = result.fallback;
|
|
175
|
-
} else {
|
|
176
|
-
throw new Error(result.error.message);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
} else {
|
|
180
|
-
// Legacy format - direct data
|
|
181
|
-
loaderData[id] = result;
|
|
182
|
-
}
|
|
183
|
-
});
|
|
115
|
+
const { loaderData, errorFallback } = decodeLoaderResults(
|
|
116
|
+
resolvedData,
|
|
117
|
+
loaderIds,
|
|
118
|
+
);
|
|
184
119
|
|
|
185
120
|
return (
|
|
186
121
|
<OutletProvider
|
|
@@ -190,7 +125,7 @@ function LoaderResolver({
|
|
|
190
125
|
parallel={parallel}
|
|
191
126
|
loaderData={Object.keys(loaderData).length > 0 ? loaderData : undefined}
|
|
192
127
|
>
|
|
193
|
-
{
|
|
128
|
+
{errorFallback ?? children}
|
|
194
129
|
</OutletProvider>
|
|
195
130
|
);
|
|
196
131
|
}
|