@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
|
@@ -1,37 +1,104 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Problem Details (RFC 9457) Builder
|
|
3
3
|
*
|
|
4
|
-
* Builds a
|
|
5
|
-
*
|
|
4
|
+
* Builds a problem+json error body from a caught error, controlling what
|
|
5
|
+
* information is exposed based on error type and environment.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { RouterError } from "../errors.js";
|
|
9
|
-
import type {
|
|
9
|
+
import type { ProblemDetails } from "../urls.js";
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
12
|
+
* HTTP reason phrases for the problem `title` member. Inlined because the
|
|
13
|
+
* router targets edge/worker runtimes without node's `http.STATUS_CODES`;
|
|
14
|
+
* covers the full standard 4xx/5xx range, with a generic fallback for any
|
|
15
|
+
* non-standard status a handler might set.
|
|
16
|
+
*/
|
|
17
|
+
const STATUS_PHRASES: Record<number, string> = {
|
|
18
|
+
400: "Bad Request",
|
|
19
|
+
401: "Unauthorized",
|
|
20
|
+
402: "Payment Required",
|
|
21
|
+
403: "Forbidden",
|
|
22
|
+
404: "Not Found",
|
|
23
|
+
405: "Method Not Allowed",
|
|
24
|
+
406: "Not Acceptable",
|
|
25
|
+
407: "Proxy Authentication Required",
|
|
26
|
+
408: "Request Timeout",
|
|
27
|
+
409: "Conflict",
|
|
28
|
+
410: "Gone",
|
|
29
|
+
411: "Length Required",
|
|
30
|
+
412: "Precondition Failed",
|
|
31
|
+
413: "Payload Too Large",
|
|
32
|
+
414: "URI Too Long",
|
|
33
|
+
415: "Unsupported Media Type",
|
|
34
|
+
416: "Range Not Satisfiable",
|
|
35
|
+
417: "Expectation Failed",
|
|
36
|
+
418: "I'm a Teapot",
|
|
37
|
+
421: "Misdirected Request",
|
|
38
|
+
422: "Unprocessable Entity",
|
|
39
|
+
423: "Locked",
|
|
40
|
+
424: "Failed Dependency",
|
|
41
|
+
425: "Too Early",
|
|
42
|
+
426: "Upgrade Required",
|
|
43
|
+
428: "Precondition Required",
|
|
44
|
+
429: "Too Many Requests",
|
|
45
|
+
431: "Request Header Fields Too Large",
|
|
46
|
+
451: "Unavailable For Legal Reasons",
|
|
47
|
+
500: "Internal Server Error",
|
|
48
|
+
501: "Not Implemented",
|
|
49
|
+
502: "Bad Gateway",
|
|
50
|
+
503: "Service Unavailable",
|
|
51
|
+
504: "Gateway Timeout",
|
|
52
|
+
505: "HTTP Version Not Supported",
|
|
53
|
+
506: "Variant Also Negotiates",
|
|
54
|
+
507: "Insufficient Storage",
|
|
55
|
+
508: "Loop Detected",
|
|
56
|
+
510: "Not Extended",
|
|
57
|
+
511: "Network Authentication Required",
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
function statusPhrase(status: number): string {
|
|
61
|
+
return STATUS_PHRASES[status] ?? "Error";
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Build an RFC 9457 problem+json body from a caught error.
|
|
66
|
+
* RouterError messages/codes are always exposed (developer-crafted).
|
|
14
67
|
* Standard Error messages are hidden in production.
|
|
68
|
+
*
|
|
69
|
+
* The `type` member is omitted in this phase: per RFC 9457 an absent `type` is
|
|
70
|
+
* treated as `"about:blank"` (no semantics beyond the HTTP status), so emitting
|
|
71
|
+
* it adds nothing. Per-route problem-type URIs arrive with the declared-errors
|
|
72
|
+
* map later. `code` is always present so consumers can branch on it
|
|
73
|
+
* (`"INTERNAL"` for non-RouterError failures).
|
|
15
74
|
*/
|
|
16
|
-
export function
|
|
75
|
+
export function createProblemDetails(
|
|
17
76
|
error: unknown,
|
|
77
|
+
status: number,
|
|
18
78
|
isDev: boolean,
|
|
19
|
-
):
|
|
79
|
+
): ProblemDetails {
|
|
20
80
|
if (error instanceof RouterError) {
|
|
21
81
|
return {
|
|
22
|
-
|
|
82
|
+
title: statusPhrase(status),
|
|
83
|
+
status,
|
|
84
|
+
detail: error.message,
|
|
23
85
|
code: error.code,
|
|
24
|
-
...(error.type ? { type: error.type } : {}),
|
|
25
86
|
...(isDev && error.stack ? { stack: error.stack } : {}),
|
|
26
87
|
};
|
|
27
88
|
}
|
|
28
89
|
if (error instanceof Error) {
|
|
29
90
|
return {
|
|
30
|
-
|
|
91
|
+
title: statusPhrase(status),
|
|
92
|
+
status,
|
|
93
|
+
detail: isDev ? error.message : "Internal Server Error",
|
|
94
|
+
code: "INTERNAL",
|
|
31
95
|
...(isDev && error.stack ? { stack: error.stack } : {}),
|
|
32
96
|
};
|
|
33
97
|
}
|
|
34
98
|
return {
|
|
35
|
-
|
|
99
|
+
title: statusPhrase(status),
|
|
100
|
+
status,
|
|
101
|
+
detail: isDev ? String(error) : "Internal Server Error",
|
|
102
|
+
code: "INTERNAL",
|
|
36
103
|
};
|
|
37
104
|
}
|
|
@@ -7,11 +7,12 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { RouterError } from "../errors.js";
|
|
10
|
-
import {
|
|
10
|
+
import { getRequestContext } from "../server/request-context.js";
|
|
11
11
|
import { contextGet } from "../context-var.js";
|
|
12
12
|
import { NOCACHE_SYMBOL } from "../cache/taint.js";
|
|
13
|
-
import {
|
|
14
|
-
import { createCacheScope } from "../cache/cache-scope.js";
|
|
13
|
+
import { RESPONSE_TYPE_MIME } from "../router/content-negotiation.js";
|
|
14
|
+
import { createCacheScope, resolveCacheTags } from "../cache/cache-scope.js";
|
|
15
|
+
import { serveResponseRouteWithCache } from "./response-cache-serve.js";
|
|
15
16
|
import { executeMiddleware } from "../router/middleware.js";
|
|
16
17
|
import {
|
|
17
18
|
createReverseFunction,
|
|
@@ -20,7 +21,7 @@ import {
|
|
|
20
21
|
import type { MiddlewareFn } from "../router/middleware.js";
|
|
21
22
|
import type { EntryData } from "../server/context.js";
|
|
22
23
|
import type { HandlerContext } from "./handler-context.js";
|
|
23
|
-
import {
|
|
24
|
+
import { createProblemDetails } from "./response-error.js";
|
|
24
25
|
import {
|
|
25
26
|
createResponseWithMergedHeaders,
|
|
26
27
|
finalizeResponse,
|
|
@@ -29,6 +30,12 @@ import {
|
|
|
29
30
|
mergeStubHeadersAndFinalize,
|
|
30
31
|
} from "./helpers.js";
|
|
31
32
|
import { isWebSocketUpgradeResponse } from "../response-utils.js";
|
|
33
|
+
import { stringifyJsonRouteResult } from "./json-route-result.js";
|
|
34
|
+
import {
|
|
35
|
+
EXTERNAL_REDIRECT_MARKER,
|
|
36
|
+
isExternalRedirect,
|
|
37
|
+
markExternalRedirect,
|
|
38
|
+
} from "../redirect-origin.js";
|
|
32
39
|
|
|
33
40
|
export interface ResponseRouteMatch {
|
|
34
41
|
responseType: string;
|
|
@@ -72,7 +79,7 @@ export async function handleResponseRoute<TEnv>(
|
|
|
72
79
|
}
|
|
73
80
|
|
|
74
81
|
// Build lightweight context for response handler
|
|
75
|
-
const reqCtx =
|
|
82
|
+
const reqCtx = getRequestContext();
|
|
76
83
|
const cleanUrl = stripInternalParams(url);
|
|
77
84
|
const responseHandlerCtx = {
|
|
78
85
|
request,
|
|
@@ -80,10 +87,13 @@ export async function handleResponseRoute<TEnv>(
|
|
|
80
87
|
env,
|
|
81
88
|
searchParams: cleanUrl.searchParams,
|
|
82
89
|
url: cleanUrl,
|
|
90
|
+
originalUrl: reqCtx.originalUrl,
|
|
83
91
|
pathname: url.pathname,
|
|
84
92
|
reverse: createReverseFunction(handlerCtx.getRequiredRouteMap()),
|
|
85
93
|
get: ((keyOrVar: any) => contextGet(variables, keyOrVar)) as any,
|
|
86
94
|
header: (name: string, value: string) => reqCtx.header(name, value),
|
|
95
|
+
waitUntil: reqCtx.waitUntil.bind(reqCtx),
|
|
96
|
+
executionContext: reqCtx.executionContext,
|
|
87
97
|
_responseType: preview.responseType,
|
|
88
98
|
};
|
|
89
99
|
// Brand with taint symbol so "use cache" detects it as request-scoped
|
|
@@ -106,49 +116,31 @@ export async function handleResponseRoute<TEnv>(
|
|
|
106
116
|
}
|
|
107
117
|
const headers = new Headers();
|
|
108
118
|
result.headers.forEach((value, key) => {
|
|
119
|
+
// Never copy the reserved external-redirect marker off a handler result.
|
|
120
|
+
// It is not a trust signal -- the opt-in is the out-of-band brand below
|
|
121
|
+
// -- and a proxy-style route returning an attacker-controlled upstream
|
|
122
|
+
// response must not let a forged value ride through to the browser.
|
|
123
|
+
if (key.toLowerCase() === EXTERNAL_REDIRECT_MARKER) return;
|
|
109
124
|
if (key.toLowerCase() === "set-cookie") {
|
|
110
125
|
headers.append(key, value);
|
|
111
126
|
} else {
|
|
112
127
|
headers.set(key, value);
|
|
113
128
|
}
|
|
114
129
|
});
|
|
115
|
-
|
|
130
|
+
const rewrapped = createResponseWithMergedHeaders(result.body, {
|
|
116
131
|
status: result.status,
|
|
117
132
|
headers,
|
|
118
133
|
});
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
if (result instanceof Response) {
|
|
126
|
-
return rewrapResponse(result);
|
|
127
|
-
}
|
|
128
|
-
return createResponseWithMergedHeaders(
|
|
129
|
-
JSON.stringify({ data: result }),
|
|
130
|
-
{
|
|
131
|
-
status: 200,
|
|
132
|
-
headers: { "content-type": "application/json;charset=utf-8" },
|
|
133
|
-
},
|
|
134
|
-
);
|
|
135
|
-
} catch (error) {
|
|
136
|
-
handlerCtx.callOnError(error, "handler", errorCtx);
|
|
137
|
-
const isDev = process.env.NODE_ENV !== "production";
|
|
138
|
-
const status = error instanceof RouterError ? error.status : 500;
|
|
139
|
-
return createResponseWithMergedHeaders(
|
|
140
|
-
JSON.stringify({
|
|
141
|
-
error: createResponseErrorPayload(error, isDev),
|
|
142
|
-
}),
|
|
143
|
-
{
|
|
144
|
-
status,
|
|
145
|
-
headers: { "content-type": "application/json;charset=utf-8" },
|
|
146
|
-
},
|
|
147
|
-
);
|
|
134
|
+
// Transfer the out-of-band external brand only when the handler result is
|
|
135
|
+
// genuinely branded (a real redirect(url, { external: true })). A proxied
|
|
136
|
+
// upstream Response is never branded, so an attacker cannot opt a response
|
|
137
|
+
// route's redirect out of the same-origin guard by injecting the header.
|
|
138
|
+
if (isExternalRedirect(result)) {
|
|
139
|
+
markExternalRedirect(rewrapped);
|
|
148
140
|
}
|
|
149
|
-
|
|
141
|
+
return rewrapped;
|
|
142
|
+
};
|
|
150
143
|
|
|
151
|
-
// Non-JSON response routes: catch errors and return plain Response
|
|
152
144
|
try {
|
|
153
145
|
const result = await (preview.handler as Function)(responseHandlerCtx);
|
|
154
146
|
|
|
@@ -156,38 +148,56 @@ export async function handleResponseRoute<TEnv>(
|
|
|
156
148
|
return rewrapResponse(result);
|
|
157
149
|
}
|
|
158
150
|
|
|
159
|
-
//
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
default:
|
|
182
|
-
// image, stream, any -- must return Response
|
|
183
|
-
throw new Error(
|
|
184
|
-
`Response route handler for "${preview.responseType}" must return a Response object, got ${typeof result}`,
|
|
185
|
-
);
|
|
151
|
+
// Handled before the MIME lookup (json is also a RESPONSE_TYPE_MIME key).
|
|
152
|
+
if (preview.responseType === "json") {
|
|
153
|
+
// Runtime guard: the json() return type rejects nested Promises at
|
|
154
|
+
// compile time, but an `as`-cast or untyped (JS) handler can still slip
|
|
155
|
+
// one through. stringifyJsonRouteResult throws a clear error instead of
|
|
156
|
+
// shipping empty data (shared with dispatch() so the two cannot drift).
|
|
157
|
+
const body = stringifyJsonRouteResult(result);
|
|
158
|
+
return createResponseWithMergedHeaders(body, {
|
|
159
|
+
status: 200,
|
|
160
|
+
headers: { "content-type": "application/json;charset=utf-8" },
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Object.hasOwn (not truthiness) so prototype names like "toString" are not
|
|
165
|
+
// matched; image/stream/any are absent and fall through to the throw.
|
|
166
|
+
if (Object.hasOwn(RESPONSE_TYPE_MIME, preview.responseType)) {
|
|
167
|
+
return createResponseWithMergedHeaders(String(result), {
|
|
168
|
+
status: 200,
|
|
169
|
+
headers: {
|
|
170
|
+
"content-type": `${RESPONSE_TYPE_MIME[preview.responseType]};charset=utf-8`,
|
|
171
|
+
},
|
|
172
|
+
});
|
|
186
173
|
}
|
|
174
|
+
|
|
175
|
+
throw new Error(
|
|
176
|
+
`Response route handler for "${preview.responseType}" must return a Response object, got ${typeof result}`,
|
|
177
|
+
);
|
|
187
178
|
} catch (error) {
|
|
188
179
|
handlerCtx.callOnError(error, "handler", errorCtx);
|
|
189
180
|
const isDev = process.env.NODE_ENV !== "production";
|
|
190
|
-
const
|
|
181
|
+
const derivedStatus = error instanceof RouterError ? error.status : 500;
|
|
182
|
+
// Resolve the effective status the same way createResponseWithMergedHeaders
|
|
183
|
+
// will (ctx.res.status override) so the problem body's status/title match
|
|
184
|
+
// the actual HTTP status — e.g. when a handler called ctx.setStatus()
|
|
185
|
+
// before throwing.
|
|
186
|
+
const status =
|
|
187
|
+
reqCtx.res.status !== 200 ? reqCtx.res.status : derivedStatus;
|
|
188
|
+
|
|
189
|
+
if (preview.responseType === "json") {
|
|
190
|
+
return createResponseWithMergedHeaders(
|
|
191
|
+
JSON.stringify(createProblemDetails(error, status, isDev)),
|
|
192
|
+
{
|
|
193
|
+
status,
|
|
194
|
+
headers: {
|
|
195
|
+
"content-type": "application/problem+json;charset=utf-8",
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
191
201
|
const message =
|
|
192
202
|
error instanceof RouterError
|
|
193
203
|
? error.message
|
|
@@ -227,130 +237,20 @@ export async function handleResponseRoute<TEnv>(
|
|
|
227
237
|
return callHandlerWithVary();
|
|
228
238
|
};
|
|
229
239
|
|
|
230
|
-
//
|
|
240
|
+
// Response-route cache: resolved through the shared serve leaf
|
|
241
|
+
// (rsc/response-cache-serve.ts) so production and the dispatch testing
|
|
242
|
+
// primitive share ONE owner of the cache contract. Returns undefined when no
|
|
243
|
+
// cache applies, so we fall through to a plain handler run.
|
|
231
244
|
if (preview.manifestEntry) {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
let conditionPassed = true;
|
|
242
|
-
if (cacheScope.config !== false && cacheScope.config.condition) {
|
|
243
|
-
try {
|
|
244
|
-
conditionPassed = !!cacheScope.config.condition(reqCtx);
|
|
245
|
-
} catch {
|
|
246
|
-
conditionPassed = false;
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
const store = cacheScope.getStore() ?? reqCtx._cacheStore;
|
|
251
|
-
if (conditionPassed && store?.getResponse && store?.putResponse) {
|
|
252
|
-
// Build cache key with response:{type}: prefix to avoid collision
|
|
253
|
-
// with segment keys and differentiate between response types.
|
|
254
|
-
// Include host and url.search so query-driven and multi-host
|
|
255
|
-
// responses cache separately.
|
|
256
|
-
let cacheKey = `response:${preview.responseType}:${url.host}${url.pathname}${url.search}`;
|
|
257
|
-
|
|
258
|
-
// Priority 1: Route-level key function (full override)
|
|
259
|
-
if (cacheScope.config !== false && cacheScope.config.key) {
|
|
260
|
-
try {
|
|
261
|
-
const customKey = await cacheScope.config.key(reqCtx);
|
|
262
|
-
cacheKey = `response:${customKey}`;
|
|
263
|
-
} catch {
|
|
264
|
-
// Fall back to default key on route-level key failure
|
|
265
|
-
}
|
|
266
|
-
} else if (store.keyGenerator) {
|
|
267
|
-
// Priority 2: Store-level keyGenerator (modifies default key)
|
|
268
|
-
try {
|
|
269
|
-
cacheKey = await store.keyGenerator(reqCtx, cacheKey);
|
|
270
|
-
} catch {
|
|
271
|
-
// Fall back to default key on keyGenerator failure
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
// Save pre-handler callbacks (registered by app-level middleware
|
|
276
|
-
// before we reach the cache block) and clear the live array.
|
|
277
|
-
// createResponseWithMergedHeaders (inside the handler) eagerly
|
|
278
|
-
// executes any callbacks present in _onResponseCallbacks, so
|
|
279
|
-
// handler-registered callbacks are baked into the handler's
|
|
280
|
-
// response and the cached artifact. Pre-handler callbacks are
|
|
281
|
-
// NOT in the live array during execution, so they are applied
|
|
282
|
-
// once per serve on every path (hit + miss) below.
|
|
283
|
-
const savedCallbacks = reqCtx._onResponseCallbacks;
|
|
284
|
-
reqCtx._onResponseCallbacks = [];
|
|
285
|
-
|
|
286
|
-
const applyPreHandlerCallbacks = (response: Response): Response => {
|
|
287
|
-
let result = response;
|
|
288
|
-
for (const callback of savedCallbacks) {
|
|
289
|
-
result = callback(result) ?? result;
|
|
290
|
-
}
|
|
291
|
-
return result;
|
|
292
|
-
};
|
|
293
|
-
|
|
294
|
-
try {
|
|
295
|
-
const cached = await store.getResponse(cacheKey);
|
|
296
|
-
|
|
297
|
-
if (cached && isCacheableStatus(cached.response.status)) {
|
|
298
|
-
if (!cached.shouldRevalidate) {
|
|
299
|
-
// Fresh hit
|
|
300
|
-
return applyPreHandlerCallbacks(cached.response);
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
// Stale hit (SWR) - return cached, revalidate in background
|
|
304
|
-
reqCtx.waitUntil(async () => {
|
|
305
|
-
try {
|
|
306
|
-
// finalizeResponse drains any onResponse callbacks registered
|
|
307
|
-
// during middleware execution (e.g. middleware short-circuit)
|
|
308
|
-
// that createResponseWithMergedHeaders didn't reach.
|
|
309
|
-
const fresh = finalizeResponse(await executeHandler());
|
|
310
|
-
if (isCacheableStatus(fresh.status)) {
|
|
311
|
-
await store.putResponse!(
|
|
312
|
-
cacheKey,
|
|
313
|
-
fresh.clone(),
|
|
314
|
-
cacheScope!.ttl,
|
|
315
|
-
cacheScope!.swr,
|
|
316
|
-
);
|
|
317
|
-
}
|
|
318
|
-
} catch (error) {
|
|
319
|
-
console.error(`[ResponseCache] Revalidation failed:`, error);
|
|
320
|
-
}
|
|
321
|
-
});
|
|
322
|
-
|
|
323
|
-
return applyPreHandlerCallbacks(cached.response);
|
|
324
|
-
}
|
|
325
|
-
} catch (error) {
|
|
326
|
-
console.error(`[ResponseCache] Cache lookup failed:`, error);
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
// Cache miss - execute handler and cache the result.
|
|
330
|
-
// createResponseWithMergedHeaders inside the handler drains callbacks
|
|
331
|
-
// registered during handler execution. finalizeResponse catches any
|
|
332
|
-
// remaining callbacks (e.g. from middleware short-circuit where the
|
|
333
|
-
// handler never ran) so the cached artifact includes all transforms.
|
|
334
|
-
const response = finalizeResponse(await executeHandler());
|
|
335
|
-
|
|
336
|
-
if (isCacheableStatus(response.status)) {
|
|
337
|
-
reqCtx.waitUntil(async () => {
|
|
338
|
-
try {
|
|
339
|
-
await store.putResponse!(
|
|
340
|
-
cacheKey,
|
|
341
|
-
response.clone(),
|
|
342
|
-
cacheScope!.ttl,
|
|
343
|
-
cacheScope!.swr,
|
|
344
|
-
);
|
|
345
|
-
} catch (error) {
|
|
346
|
-
console.error(`[ResponseCache] Cache write failed:`, error);
|
|
347
|
-
}
|
|
348
|
-
});
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
return applyPreHandlerCallbacks(response);
|
|
352
|
-
}
|
|
353
|
-
}
|
|
245
|
+
const cached = await serveResponseRouteWithCache({
|
|
246
|
+
reqCtx,
|
|
247
|
+
manifestEntry: preview.manifestEntry,
|
|
248
|
+
responseType: preview.responseType,
|
|
249
|
+
url,
|
|
250
|
+
executeHandler,
|
|
251
|
+
deps: { createCacheScope, resolveCacheTags },
|
|
252
|
+
});
|
|
253
|
+
if (cached !== undefined) return cached;
|
|
354
254
|
}
|
|
355
255
|
|
|
356
256
|
return executeHandler().then(finalizeResponse);
|