@rangojs/router 0.0.0-experimental.14 → 0.0.0-experimental.141
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 +17 -0
- package/README.md +432 -7
- package/dist/bin/rango.js +2073 -213
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +7258 -2714
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +140 -67
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +329 -0
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +487 -0
- package/skills/caching/SKILL.md +357 -25
- 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 +246 -0
- package/skills/css/SKILL.md +76 -0
- package/skills/debug-manifest/SKILL.md +16 -10
- package/skills/document-cache/SKILL.md +87 -62
- package/skills/fonts/SKILL.md +6 -4
- package/skills/handler-use/SKILL.md +364 -0
- package/skills/hooks/SKILL.md +557 -79
- package/skills/host-router/SKILL.md +320 -0
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +207 -15
- package/skills/layout/SKILL.md +146 -6
- package/skills/links/SKILL.md +304 -25
- package/skills/loader/SKILL.md +616 -54
- package/skills/middleware/SKILL.md +217 -37
- package/skills/migrate-nextjs/SKILL.md +611 -0
- package/skills/migrate-react-router/SKILL.md +927 -0
- package/skills/mime-routes/SKILL.md +42 -11
- package/skills/observability/SKILL.md +194 -0
- package/skills/parallel/SKILL.md +284 -3
- package/skills/ppr/SKILL.md +293 -0
- package/skills/prerender/SKILL.md +437 -52
- package/skills/rango/SKILL.md +369 -22
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +263 -121
- package/skills/route/SKILL.md +350 -21
- package/skills/router-setup/SKILL.md +246 -33
- package/skills/scripts/SKILL.md +179 -0
- package/skills/server-actions/SKILL.md +775 -0
- package/skills/shell-manifest/SKILL.md +185 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/tailwind/SKILL.md +27 -3
- package/skills/testing/SKILL.md +126 -222
- 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 +9 -8
- package/skills/typesafety/SKILL.md +532 -103
- package/skills/use-cache/SKILL.md +367 -0
- 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 +77 -44
- package/src/bin/rango.ts +312 -15
- package/src/browser/action-coordinator.ts +114 -0
- 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/connection-warmup.ts +134 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +293 -202
- package/src/browser/history-state.ts +101 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/intercept-utils.ts +52 -0
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/link-interceptor.ts +24 -4
- package/src/browser/logging.ts +11 -0
- package/src/browser/merge-segment-loaders.ts +20 -12
- package/src/browser/navigation-bridge.ts +385 -576
- package/src/browser/navigation-client.ts +245 -75
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +184 -118
- package/src/browser/navigation-transaction.ts +247 -0
- package/src/browser/network-error-handler.ts +88 -0
- package/src/browser/partial-update.ts +412 -364
- package/src/browser/prefetch/cache.ts +359 -0
- package/src/browser/prefetch/fetch.ts +452 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +48 -0
- package/src/browser/prefetch/queue.ts +209 -0
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +194 -0
- package/src/browser/react/Link.tsx +275 -68
- package/src/browser/react/NavigationProvider.tsx +265 -109
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/context.ts +11 -0
- package/src/browser/react/filter-segment-order.ts +70 -0
- package/src/browser/react/index.ts +0 -48
- package/src/browser/react/location-state-shared.ts +272 -60
- package/src/browser/react/location-state.ts +90 -20
- package/src/browser/react/mount-context.ts +6 -1
- package/src/browser/react/nonce-context.ts +23 -0
- package/src/browser/react/shallow-equal.ts +27 -0
- package/src/browser/react/use-action.ts +35 -66
- package/src/browser/react/use-handle.ts +39 -126
- package/src/browser/react/use-href.tsx +8 -1
- package/src/browser/react/use-link-status.ts +39 -13
- package/src/browser/react/use-navigation.ts +53 -69
- package/src/browser/react/use-params.ts +75 -0
- package/src/browser/react/use-pathname.ts +47 -0
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +98 -0
- package/src/browser/react/use-search-params.ts +51 -0
- package/src/browser/react/use-segments.ts +72 -99
- package/src/browser/response-adapter.ts +164 -0
- package/src/browser/rsc-router.tsx +300 -72
- package/src/browser/scroll-restoration.ts +138 -50
- package/src/browser/segment-reconciler.ts +243 -0
- package/src/browser/segment-structure-assert.ts +17 -1
- package/src/browser/server-action-bridge.ts +668 -613
- package/src/browser/types.ts +223 -51
- package/src/browser/validate-redirect-origin.ts +56 -0
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +252 -161
- package/src/build/generate-route-types.ts +41 -1038
- package/src/build/index.ts +12 -7
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +225 -42
- package/src/build/route-types/ast-helpers.ts +25 -0
- package/src/build/route-types/ast-route-extraction.ts +105 -0
- package/src/build/route-types/codegen.ts +113 -0
- package/src/build/route-types/include-resolution.ts +812 -0
- package/src/build/route-types/param-extraction.ts +51 -0
- package/src/build/route-types/per-module-writer.ts +144 -0
- package/src/build/route-types/router-processing.ts +695 -0
- package/src/build/route-types/scan-filter.ts +85 -0
- package/src/build/route-types/source-scan.ts +216 -0
- package/src/build/runtime-discovery.ts +223 -0
- package/src/cache/background-task.ts +34 -0
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-key-utils.ts +60 -0
- package/src/cache/cache-policy.ts +199 -0
- package/src/cache/cache-runtime.ts +525 -0
- package/src/cache/cache-scope.ts +298 -332
- 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 +2508 -158
- 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 +17 -17
- package/src/cache/document-cache.ts +199 -92
- package/src/cache/handle-capture.ts +81 -0
- package/src/cache/handle-snapshot.ts +111 -0
- package/src/cache/index.ts +24 -35
- package/src/cache/memory-segment-store.ts +363 -30
- package/src/cache/profile-registry.ts +88 -0
- package/src/cache/read-through-swr.ts +178 -0
- package/src/cache/segment-codec.ts +248 -0
- package/src/cache/shell-snapshot.ts +368 -0
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/taint.ts +153 -0
- package/src/cache/types.ts +222 -211
- package/src/cache/vercel/index.ts +11 -0
- package/src/cache/vercel/vercel-cache-store.ts +1113 -0
- package/src/client.rsc.tsx +43 -21
- package/src/client.tsx +131 -347
- package/src/cloudflare/index.ts +11 -0
- package/src/cloudflare/tracing.ts +109 -0
- package/src/component-utils.ts +23 -4
- package/src/components/DefaultDocument.tsx +13 -3
- package/src/context-var.ts +168 -0
- package/src/debug.ts +19 -9
- 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 +106 -10
- package/src/escape-script.ts +52 -0
- package/src/handle.ts +110 -35
- package/src/handles/MetaTags.tsx +83 -59
- package/src/handles/Scripts.tsx +183 -0
- package/src/handles/breadcrumbs.ts +93 -0
- package/src/handles/deferred-resolution.ts +127 -0
- package/src/handles/is-thenable.ts +18 -0
- package/src/handles/meta.ts +44 -53
- package/src/handles/script.ts +244 -0
- package/src/host/cookie-handler.ts +20 -65
- package/src/host/errors.ts +21 -30
- package/src/host/index.ts +13 -9
- package/src/host/pattern-matcher.ts +50 -79
- package/src/host/router.ts +151 -121
- package/src/host/testing.ts +45 -32
- package/src/host/types.ts +52 -11
- package/src/host/utils.ts +2 -2
- package/src/href-client.ts +192 -57
- package/src/index.rsc.ts +173 -35
- package/src/index.ts +241 -73
- package/src/internal-debug.ts +9 -2
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +31 -99
- package/src/loader.ts +30 -12
- package/src/missing-id-error.ts +68 -0
- package/src/outlet-context.ts +1 -1
- package/src/outlet-provider.tsx +41 -0
- package/src/prerender/param-hash.ts +16 -14
- package/src/prerender/store.ts +121 -21
- package/src/prerender.ts +460 -26
- 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 +62 -0
- package/src/reverse.ts +198 -128
- package/src/root-error-boundary.tsx +42 -48
- package/src/route-content-wrapper.tsx +22 -77
- package/src/route-definition/dsl-helpers.ts +1116 -0
- package/src/route-definition/helper-factories.ts +88 -0
- package/src/route-definition/helpers-types.ts +505 -0
- package/src/route-definition/index.ts +54 -0
- package/src/route-definition/redirect.ts +134 -0
- package/src/route-definition/resolve-handler-use.ts +160 -0
- package/src/route-definition/use-item-types.ts +29 -0
- package/src/route-definition.ts +1 -1481
- package/src/route-map-builder.ts +82 -144
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +71 -45
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +263 -0
- package/src/router/debug-manifest.ts +72 -0
- package/src/router/error-handling.ts +54 -27
- package/src/router/find-match.ts +245 -0
- package/src/router/handler-context.ts +377 -125
- package/src/router/instrument.ts +350 -0
- package/src/router/intercept-resolution.ts +59 -28
- package/src/router/lazy-includes.ts +254 -0
- package/src/router/loader-resolution.ts +421 -157
- package/src/router/logging.ts +106 -6
- package/src/router/manifest.ts +131 -57
- package/src/router/match-api.ts +167 -246
- package/src/router/match-context.ts +4 -24
- package/src/router/match-handlers.ts +440 -0
- package/src/router/match-middleware/background-revalidation.ts +117 -93
- package/src/router/match-middleware/cache-lookup.ts +297 -150
- package/src/router/match-middleware/cache-store.ts +123 -51
- package/src/router/match-middleware/intercept-resolution.ts +44 -43
- package/src/router/match-middleware/segment-resolution.ts +64 -22
- package/src/router/match-pipelines.ts +11 -87
- package/src/router/match-result.ts +121 -50
- package/src/router/metrics.ts +219 -28
- package/src/router/middleware-types.ts +93 -0
- package/src/router/middleware.ts +505 -441
- package/src/router/navigation-snapshot.ts +133 -0
- package/src/router/params-util.ts +23 -0
- package/src/router/parse-pattern.ts +115 -0
- package/src/router/pattern-matching.ts +311 -142
- package/src/router/prefetch-cache-ttl.ts +51 -0
- package/src/router/prefetch-limits.ts +37 -0
- package/src/router/prerender-match.ts +547 -0
- package/src/router/preview-match.ts +102 -0
- package/src/router/request-classification.ts +278 -0
- package/src/router/revalidation.ts +203 -62
- package/src/router/route-snapshot.ts +246 -0
- package/src/router/router-context.ts +45 -48
- package/src/router/router-interfaces.ts +554 -0
- package/src/router/router-options.ts +779 -0
- package/src/router/router-registry.ts +21 -0
- package/src/router/segment-resolution/fresh.ts +772 -0
- package/src/router/segment-resolution/helpers.ts +348 -0
- package/src/router/segment-resolution/loader-cache.ts +250 -0
- package/src/router/segment-resolution/loader-mask.ts +44 -0
- package/src/router/segment-resolution/revalidation.ts +1331 -0
- package/src/router/segment-resolution/static-store.ts +81 -0
- 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 +25 -1354
- package/src/router/segment-wrappers.ts +292 -0
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +75 -0
- package/src/router/telemetry-otel.ts +261 -0
- package/src/router/telemetry.ts +377 -0
- package/src/router/timeout.ts +128 -0
- package/src/router/tracing.ts +206 -0
- package/src/router/trie-matching.ts +240 -61
- package/src/router/types.ts +23 -70
- package/src/router/url-params.ts +57 -0
- package/src/router.ts +781 -2378
- package/src/rsc/full-payload.ts +70 -0
- package/src/rsc/handler-context.ts +46 -0
- package/src/rsc/handler.ts +905 -1142
- package/src/rsc/helpers.ts +275 -19
- package/src/rsc/index.ts +2 -25
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +305 -0
- package/src/rsc/manifest-init.ts +77 -0
- package/src/rsc/nonce.ts +14 -0
- package/src/rsc/origin-guard.ts +155 -0
- package/src/rsc/progressive-enhancement.ts +502 -0
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-cache-serve.ts +238 -0
- package/src/rsc/response-error.ts +104 -0
- package/src/rsc/response-route-handler.ts +257 -0
- package/src/rsc/rsc-rendering.ts +527 -0
- package/src/rsc/runtime-warnings.ts +55 -0
- package/src/rsc/server-action.ts +522 -0
- package/src/rsc/shell-capture.ts +897 -0
- package/src/rsc/shell-serve.ts +124 -0
- package/src/rsc/ssr-setup.ts +144 -0
- package/src/rsc/transition-gate.ts +89 -0
- package/src/rsc/types.ts +95 -12
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +99 -82
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +149 -0
- package/src/segment-system.tsx +349 -134
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +459 -85
- package/src/server/cookie-parse.ts +32 -0
- package/src/server/cookie-store.ts +310 -0
- package/src/server/fetchable-loader-store.ts +11 -6
- package/src/server/handle-store.ts +123 -42
- package/src/server/loader-registry.ts +51 -100
- package/src/server/request-context.ts +848 -157
- package/src/server.ts +15 -8
- package/src/ssr/index.tsx +443 -135
- package/src/ssr/ssr-root.tsx +228 -0
- package/src/static-handler.ts +45 -18
- package/src/testing/cache-status.ts +162 -0
- package/src/testing/collect-handle.ts +46 -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 +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 +76 -98
- package/src/theme/ThemeScript.tsx +12 -14
- package/src/theme/constants.ts +57 -15
- package/src/theme/index.ts +3 -20
- package/src/theme/theme-context.ts +5 -35
- package/src/theme/theme-script.ts +43 -39
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +123 -0
- package/src/types/cache-types.ts +207 -0
- package/src/types/error-types.ts +132 -0
- package/src/types/global-namespace.ts +113 -0
- package/src/types/handler-context.ts +839 -0
- package/src/types/index.ts +81 -0
- package/src/types/loader-types.ts +212 -0
- package/src/types/request-scope.ts +112 -0
- package/src/types/route-config.ts +138 -0
- package/src/types/route-entry.ts +114 -0
- package/src/types/segments.ts +271 -0
- package/src/types.ts +1 -1795
- package/src/urls/include-helper.ts +162 -0
- package/src/urls/include-provider.ts +71 -0
- package/src/urls/index.ts +44 -0
- package/src/urls/path-helper-types.ts +413 -0
- package/src/urls/path-helper.ts +280 -0
- package/src/urls/pattern-types.ts +160 -0
- package/src/urls/response-types.ts +109 -0
- package/src/urls/type-extraction.ts +316 -0
- package/src/urls/urls-function.ts +80 -0
- package/src/urls.ts +1 -1341
- package/src/use-loader.tsx +406 -141
- 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 +182 -0
- package/src/vite/discovery/discover-routers.ts +389 -0
- 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 +467 -0
- package/src/vite/discovery/route-types-writer.ts +214 -0
- package/src/vite/discovery/self-gen-tracking.ts +73 -0
- package/src/vite/discovery/state.ts +161 -0
- package/src/vite/discovery/virtual-module-codegen.ts +183 -0
- package/src/vite/index.ts +23 -2255
- package/src/vite/inject-client-debug.ts +36 -0
- package/src/vite/plugin-types.ts +303 -0
- package/src/vite/plugins/cjs-to-esm.ts +90 -0
- package/src/vite/plugins/client-ref-dedup.ts +120 -0
- package/src/vite/plugins/client-ref-hashing.ts +118 -0
- 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/{expose-action-id.ts → plugins/expose-action-id.ts} +88 -110
- package/src/vite/{expose-id-utils.ts → plugins/expose-id-utils.ts} +89 -79
- package/src/vite/plugins/expose-ids/export-analysis.ts +363 -0
- package/src/vite/plugins/expose-ids/handler-transform.ts +130 -0
- package/src/vite/plugins/expose-ids/loader-transform.ts +64 -0
- package/src/vite/plugins/expose-ids/router-transform.ts +199 -0
- package/src/vite/plugins/expose-ids/types.ts +45 -0
- package/src/vite/plugins/expose-internal-ids.ts +805 -0
- package/src/vite/plugins/performance-tracks.ts +89 -0
- package/src/vite/plugins/refresh-cmd.ts +127 -0
- package/src/vite/plugins/use-cache-transform.ts +313 -0
- package/src/vite/plugins/vercel-output.ts +384 -0
- package/src/vite/plugins/version-injector.ts +94 -0
- package/src/vite/plugins/version-plugin.ts +263 -0
- package/src/vite/plugins/virtual-entries.ts +234 -0
- package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
- package/src/vite/rango.ts +560 -0
- package/src/vite/router-discovery.ts +1638 -0
- package/src/vite/{ast-handler-extract.ts → utils/ast-handler-extract.ts} +200 -37
- package/src/vite/utils/banner.ts +36 -0
- package/src/vite/utils/bundle-analysis.ts +132 -0
- 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 +15 -0
- package/src/vite/utils/package-resolution.ts +89 -0
- package/src/vite/utils/prerender-utils.ts +249 -0
- package/src/vite/utils/shared-utils.ts +269 -0
- package/CLAUDE.md +0 -43
- package/dist/vite/index.named-routes.gen.ts +0 -103
- package/src/browser/lru-cache.ts +0 -69
- package/src/browser/react/use-client-cache.ts +0 -56
- package/src/browser/request-controller.ts +0 -164
- package/src/browser/shallow.ts +0 -35
- package/src/cache/memory-store.ts +0 -253
- package/src/handles/index.ts +0 -6
- package/src/href-context.ts +0 -33
- package/src/network-error-thrower.tsx +0 -21
- package/src/router.gen.ts +0 -6
- package/src/static-handler.gen.ts +0 -5
- package/src/urls.gen.ts +0 -8
- package/src/vite/expose-internal-ids.ts +0 -1167
- package/src/vite/package-resolution.ts +0 -125
- package/src/vite/virtual-entries.ts +0 -114
- /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
package/src/defer.ts
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deferred handle values — "decide synchronously, resolve late".
|
|
3
|
+
*
|
|
4
|
+
* A handle is pushed from code that holds `ctx` (a route/layout handler), so the
|
|
5
|
+
* decision to push lands before the handles stream seals. But the value often
|
|
6
|
+
* isn't known there — it may come from a deep async component far from the
|
|
7
|
+
* handler. `ctx.use(Handle).defer()` reserves the handle's slot now (synchronous,
|
|
8
|
+
* so ordering and the pre-seal timing hold) and returns a resolver with the SAME
|
|
9
|
+
* signature as the push: you call it later, anywhere in the render, with the same
|
|
10
|
+
* value you would have passed to the push.
|
|
11
|
+
*
|
|
12
|
+
* const breadcrumb = ctx.use(Breadcrumbs); // (item) => void & .defer()
|
|
13
|
+
* const resolve = breadcrumb.defer({ timeoutMs: 5000, else: null });
|
|
14
|
+
* // deep async component, far from ctx:
|
|
15
|
+
* resolve({ label, href, content }); // identical call, just deferred
|
|
16
|
+
*
|
|
17
|
+
* Under the hood the reserved slot is a Promise. Handle values are resolved
|
|
18
|
+
* before any consumer sees them (resolve-by-default: the full render resolves
|
|
19
|
+
* server-side, navigation resolves client-side before apply), so `useHandle`
|
|
20
|
+
* receives the resolved value, never the Promise. The hazard that guards against
|
|
21
|
+
* bugs: a deferred slot whose resolver is never called would keep the render —
|
|
22
|
+
* and the HTTP response — waiting forever. So a deferred auto-resolves to `else`
|
|
23
|
+
* after `timeoutMs` (default {@link DEFAULT_DEFER_TIMEOUT_MS}) if the resolver is
|
|
24
|
+
* never called, degrading gracefully (and warning in dev) instead of hanging.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/** Default auto-resolve window. Long enough for genuine deep async work, short
|
|
28
|
+
* enough that a forgotten resolve does not hang the response indefinitely. */
|
|
29
|
+
export const DEFAULT_DEFER_TIMEOUT_MS = 10_000;
|
|
30
|
+
|
|
31
|
+
/** Options for `ctx.use(Handle).defer()`. */
|
|
32
|
+
export interface DeferOptions<TData> {
|
|
33
|
+
/**
|
|
34
|
+
* Auto-resolve to `else` after this many ms if the resolver is never called,
|
|
35
|
+
* so a forgotten resolve cannot hold the Flight stream — and thus the HTTP
|
|
36
|
+
* response — open. Defaults to {@link DEFAULT_DEFER_TIMEOUT_MS}. `0` or
|
|
37
|
+
* `Infinity` disable the timeout intentionally (not recommended on a request
|
|
38
|
+
* path). Any other non-finite or negative value is treated as a mistake and
|
|
39
|
+
* falls back to the default rather than silently disabling the safety net.
|
|
40
|
+
* Named `timeoutMs` to match the router's `*Ms` duration convention.
|
|
41
|
+
*/
|
|
42
|
+
timeoutMs?: number;
|
|
43
|
+
/**
|
|
44
|
+
* Value the slot resolves to if the timeout fires before the resolver is
|
|
45
|
+
* called. Defaults to `undefined` (the deferred item is skipped/empty). For
|
|
46
|
+
* renderable handle content, `null` is the usual graceful fallback, so the
|
|
47
|
+
* type admits `null` even when `TData` does not.
|
|
48
|
+
*/
|
|
49
|
+
else?: TData | null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The call signature shared by a handle push and the resolver returned by
|
|
54
|
+
* `.defer()`: a concrete value, a `Promise` of the value (Flight streams it as a
|
|
55
|
+
* late row), or a thunk returning a `Promise` (called immediately).
|
|
56
|
+
*/
|
|
57
|
+
export type HandlePushFn<TData> = (
|
|
58
|
+
data: TData | Promise<TData> | (() => Promise<TData>),
|
|
59
|
+
) => void;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The push function returned by `ctx.use(Handle)`. Call it to push a value now,
|
|
63
|
+
* or call `.defer()` to reserve the slot now and resolve the value later (e.g.
|
|
64
|
+
* from a deep async component) with a timeout safety net.
|
|
65
|
+
*/
|
|
66
|
+
export type HandlePush<TData> = HandlePushFn<TData> & {
|
|
67
|
+
/**
|
|
68
|
+
* Reserve this handle's slot synchronously and return a resolver that is
|
|
69
|
+
* push-equal: it takes the same argument shapes as the push (value, Promise, or
|
|
70
|
+
* thunk) and behaves identically. Two things the resolver adds over a direct
|
|
71
|
+
* push: a timeout (if the resolver is never called, the slot auto-resolves to
|
|
72
|
+
* `options.else` after `options.timeoutMs`; calling the resolver cancels it),
|
|
73
|
+
* and — on the action/revalidation path only — a thunk it runs does NOT
|
|
74
|
+
* re-enter the deadlock-guard push-callback scope a direct push thunk gets,
|
|
75
|
+
* because a deferred resolver fires after the handler phase has closed.
|
|
76
|
+
*
|
|
77
|
+
* The reserved slot is resolved before any consumer reads it
|
|
78
|
+
* (resolve-by-default), so `useHandle` receives the resolved value (or the
|
|
79
|
+
* `else` fallback on timeout), never a Promise.
|
|
80
|
+
*/
|
|
81
|
+
defer(options?: DeferOptions<TData>): HandlePushFn<TData>;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// Internal: a timeout-bounded { promise, resolve }. Not part of the public API
|
|
85
|
+
// (the public surface is `ctx.use(Handle).defer()`); exported for `withDefer`
|
|
86
|
+
// and unit tests only. Resolves to `T`, the `else` fallback, or `undefined`.
|
|
87
|
+
export function createDeferred<T>(options?: {
|
|
88
|
+
timeoutMs?: number;
|
|
89
|
+
fallback?: T | null;
|
|
90
|
+
}): {
|
|
91
|
+
promise: Promise<T | null | undefined>;
|
|
92
|
+
resolve: (value: T | null | undefined) => void;
|
|
93
|
+
} {
|
|
94
|
+
let resolveInner!: (value: T | null | undefined) => void;
|
|
95
|
+
let settled = false;
|
|
96
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
97
|
+
|
|
98
|
+
const promise = new Promise<T | null | undefined>((resolve) => {
|
|
99
|
+
resolveInner = resolve;
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const finish = (value: T | null | undefined): void => {
|
|
103
|
+
if (settled) return;
|
|
104
|
+
settled = true;
|
|
105
|
+
if (timer !== undefined) {
|
|
106
|
+
clearTimeout(timer);
|
|
107
|
+
timer = undefined;
|
|
108
|
+
}
|
|
109
|
+
resolveInner(value);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// 0 and Infinity are documented intentional disables. Any other non-finite or
|
|
113
|
+
// negative value (NaN, -1, a bad parsed config/env) is a mistake — fall back to
|
|
114
|
+
// the default rather than SILENTLY disabling the safety net, which would let a
|
|
115
|
+
// forgotten resolve hang the Flight stream and the response forever.
|
|
116
|
+
const requested = options?.timeoutMs ?? DEFAULT_DEFER_TIMEOUT_MS;
|
|
117
|
+
let ms: number;
|
|
118
|
+
if (requested === 0 || requested === Infinity) {
|
|
119
|
+
ms = requested;
|
|
120
|
+
} else if (Number.isFinite(requested) && requested > 0) {
|
|
121
|
+
ms = requested;
|
|
122
|
+
} else {
|
|
123
|
+
if (process.env.NODE_ENV !== "production") {
|
|
124
|
+
console.warn(
|
|
125
|
+
`[rango] defer(): invalid timeout ${String(requested)}; using the ` +
|
|
126
|
+
`${DEFAULT_DEFER_TIMEOUT_MS}ms default so the safety net stays on. ` +
|
|
127
|
+
`Use 0 or Infinity to disable the timeout intentionally.`,
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
ms = DEFAULT_DEFER_TIMEOUT_MS;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (ms > 0 && ms !== Infinity) {
|
|
134
|
+
timer = setTimeout(() => {
|
|
135
|
+
if (process.env.NODE_ENV !== "production") {
|
|
136
|
+
console.warn(
|
|
137
|
+
`[rango] A deferred handle value was not resolved within ${ms}ms; ` +
|
|
138
|
+
`resolving to the fallback so the response can flush. Call the ` +
|
|
139
|
+
`resolver from the component that produces the value, or raise timeoutMs.`,
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
finish(options?.fallback);
|
|
143
|
+
}, ms);
|
|
144
|
+
// Don't let a pending timer alone keep a Node process alive (no-op on workerd).
|
|
145
|
+
(timer as { unref?: () => void }).unref?.();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return { promise, resolve: finish };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Attach `.defer()` to a handle push function. The deferred slot is reserved by
|
|
153
|
+
* pushing the deferred promise through the same push (so ordering, sealing, and
|
|
154
|
+
* Flight streaming all reuse the existing path); the returned resolver settles it.
|
|
155
|
+
*/
|
|
156
|
+
export function withDefer<TData>(push: HandlePushFn<TData>): HandlePush<TData> {
|
|
157
|
+
const handlePush = push as HandlePush<TData>;
|
|
158
|
+
// Safe to mutate push in place: each ctx.use(Handle) call (request-context.ts,
|
|
159
|
+
// loader-resolution.ts) builds a fresh closure, so .defer never leaks across
|
|
160
|
+
// handles or requests.
|
|
161
|
+
handlePush.defer = (options) => {
|
|
162
|
+
const deferred = createDeferred<TData>({
|
|
163
|
+
timeoutMs: options?.timeoutMs,
|
|
164
|
+
fallback: options?.else,
|
|
165
|
+
});
|
|
166
|
+
// Reserve the slot now by pushing the pending promise (the renderer use()s it).
|
|
167
|
+
push(deferred.promise as Promise<TData>);
|
|
168
|
+
// The resolver is push-equal: a thunk is invoked immediately (as push does)
|
|
169
|
+
// and a Promise is adopted by the reserved slot. Calling it settles the slot
|
|
170
|
+
// and cancels the timeout — the timeout only fires if it is never called.
|
|
171
|
+
const resolveSlot = deferred.resolve as (
|
|
172
|
+
value: TData | Promise<TData>,
|
|
173
|
+
) => void;
|
|
174
|
+
return (data) => {
|
|
175
|
+
// The thunk runs without re-entering the push-callback scope a direct push
|
|
176
|
+
// thunk gets on the action/revalidation path (loader-resolution.ts): a
|
|
177
|
+
// deferred resolver fires from a deep component after the handler phase has
|
|
178
|
+
// closed, so there is no live deadlock-guard window to exempt.
|
|
179
|
+
resolveSlot(
|
|
180
|
+
typeof data === "function" ? (data as () => Promise<TData>)() : data,
|
|
181
|
+
);
|
|
182
|
+
};
|
|
183
|
+
};
|
|
184
|
+
return handlePush;
|
|
185
|
+
}
|
package/src/deps/ssr.ts
CHANGED
package/src/encode-kv.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared key=value serialization core.
|
|
3
|
+
*
|
|
4
|
+
* One encode+join rule shared by every deterministic query-string serializer
|
|
5
|
+
* in the codebase (cache keys, route-param strings, search-param strings,
|
|
6
|
+
* prerender param hashes). Each call site differs only in how it pre-filters
|
|
7
|
+
* and orders the pairs it hands in, plus whether it sorts; the encoding itself
|
|
8
|
+
* (`encodeURIComponent(k)=encodeURIComponent(v)` joined by `&`) is identical.
|
|
9
|
+
*
|
|
10
|
+
* Dependency-free leaf module so any layer (build-time prerender hashing,
|
|
11
|
+
* runtime cache keys, client search-param serialization) can import it without
|
|
12
|
+
* pulling in cache/router internals.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export interface EncodeKVOptions {
|
|
16
|
+
/**
|
|
17
|
+
* Sort pairs by key in codepoint (byte) order before joining. Byte order via
|
|
18
|
+
* the `<` operator, NOT localeCompare, so the result is identical across
|
|
19
|
+
* Node, Workers, and browsers regardless of runtime locale.
|
|
20
|
+
*
|
|
21
|
+
* Defaults to false (insertion order preserved).
|
|
22
|
+
*/
|
|
23
|
+
sort?: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Serialize an array of [key, value] pairs to a deterministic query string
|
|
28
|
+
* (no leading `?`). Both key and value are passed through encodeURIComponent
|
|
29
|
+
* so a key/value containing `&` or `=` cannot collide with a structurally
|
|
30
|
+
* different pair set.
|
|
31
|
+
*
|
|
32
|
+
* Callers are responsible for any filtering (e.g. dropping reserved params) and
|
|
33
|
+
* any value coercion (e.g. String(v), skipping null/undefined) BEFORE calling.
|
|
34
|
+
* This function never inspects or skips values; it encodes exactly what it is
|
|
35
|
+
* given so each call site keeps its own existing output.
|
|
36
|
+
*/
|
|
37
|
+
export function encodeKV(
|
|
38
|
+
pairs: Iterable<readonly [string, string]>,
|
|
39
|
+
options: EncodeKVOptions = {},
|
|
40
|
+
): string {
|
|
41
|
+
const list = [...pairs];
|
|
42
|
+
if (list.length === 0) return "";
|
|
43
|
+
if (options.sort) {
|
|
44
|
+
list.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
|
|
45
|
+
}
|
|
46
|
+
return list
|
|
47
|
+
.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
|
|
48
|
+
.join("&");
|
|
49
|
+
}
|
package/src/errors.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Custom error classes for
|
|
2
|
+
* Custom error classes for Rango
|
|
3
3
|
*
|
|
4
4
|
* All errors include:
|
|
5
5
|
* - Descriptive names for easy identification
|
|
@@ -22,10 +22,22 @@ export class RouteNotFoundError extends Error {
|
|
|
22
22
|
|
|
23
23
|
constructor(message: string, options?: ErrorOptions) {
|
|
24
24
|
super(message);
|
|
25
|
+
Object.setPrototypeOf(this, RouteNotFoundError.prototype);
|
|
25
26
|
this.cause = options?.cause;
|
|
26
27
|
}
|
|
27
28
|
}
|
|
28
29
|
|
|
30
|
+
// name fallback covers cross-realm errors (Vite dev dupes, RSC serialization)
|
|
31
|
+
// where instanceof fails.
|
|
32
|
+
export function isRouteNotFoundError(
|
|
33
|
+
error: unknown,
|
|
34
|
+
): error is RouteNotFoundError {
|
|
35
|
+
return (
|
|
36
|
+
error instanceof RouteNotFoundError ||
|
|
37
|
+
(error instanceof Error && error.name === "RouteNotFoundError")
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
29
41
|
/**
|
|
30
42
|
* Thrown when data is not found (e.g., product with ID doesn't exist)
|
|
31
43
|
* Use this in handlers/loaders to trigger the nearest notFoundBoundary
|
|
@@ -45,6 +57,7 @@ export class DataNotFoundError extends Error {
|
|
|
45
57
|
|
|
46
58
|
constructor(message: string = "Not found", options?: ErrorOptions) {
|
|
47
59
|
super(message);
|
|
60
|
+
Object.setPrototypeOf(this, DataNotFoundError.prototype);
|
|
48
61
|
this.cause = options?.cause;
|
|
49
62
|
}
|
|
50
63
|
}
|
|
@@ -74,6 +87,7 @@ export class MiddlewareError extends Error {
|
|
|
74
87
|
|
|
75
88
|
constructor(message: string, options?: ErrorOptions) {
|
|
76
89
|
super(message);
|
|
90
|
+
Object.setPrototypeOf(this, MiddlewareError.prototype);
|
|
77
91
|
this.cause = options?.cause;
|
|
78
92
|
}
|
|
79
93
|
}
|
|
@@ -87,6 +101,7 @@ export class HandlerError extends Error {
|
|
|
87
101
|
|
|
88
102
|
constructor(message: string, options?: ErrorOptions) {
|
|
89
103
|
super(message);
|
|
104
|
+
Object.setPrototypeOf(this, HandlerError.prototype);
|
|
90
105
|
this.cause = options?.cause;
|
|
91
106
|
}
|
|
92
107
|
}
|
|
@@ -100,6 +115,25 @@ export class BuildError extends Error {
|
|
|
100
115
|
|
|
101
116
|
constructor(message: string, options?: ErrorOptions) {
|
|
102
117
|
super(message);
|
|
118
|
+
Object.setPrototypeOf(this, BuildError.prototype);
|
|
119
|
+
this.cause = options?.cause;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Thrown when a route-definition DSL helper (route/layout/loader/cache/…) is
|
|
125
|
+
* called outside an active urls()/map() builder, so there is no
|
|
126
|
+
* AsyncLocalStorage build context to attach to. The message names the specific
|
|
127
|
+
* helper and how to fix it; the `cause` records the mechanical reason so the
|
|
128
|
+
* failure mode is identifiable (not conflated with an unrelated throw).
|
|
129
|
+
*/
|
|
130
|
+
export class DslContextError extends Error {
|
|
131
|
+
name = "DslContextError" as const;
|
|
132
|
+
cause?: unknown;
|
|
133
|
+
|
|
134
|
+
constructor(message: string, options?: ErrorOptions) {
|
|
135
|
+
super(message);
|
|
136
|
+
Object.setPrototypeOf(this, DslContextError.prototype);
|
|
103
137
|
this.cause = options?.cause;
|
|
104
138
|
}
|
|
105
139
|
}
|
|
@@ -133,9 +167,10 @@ export class NetworkError extends Error {
|
|
|
133
167
|
options?: ErrorOptions & {
|
|
134
168
|
url?: string;
|
|
135
169
|
operation?: "action" | "navigation" | "revalidation";
|
|
136
|
-
}
|
|
170
|
+
},
|
|
137
171
|
) {
|
|
138
172
|
super(message);
|
|
173
|
+
Object.setPrototypeOf(this, NetworkError.prototype);
|
|
139
174
|
this.cause = options?.cause;
|
|
140
175
|
this.url = options?.url;
|
|
141
176
|
this.operation = options?.operation;
|
|
@@ -190,23 +225,78 @@ export function isNetworkError(error: unknown): boolean {
|
|
|
190
225
|
export class RouterError extends Error {
|
|
191
226
|
name = "RouterError" as const;
|
|
192
227
|
code: string;
|
|
193
|
-
type?: string;
|
|
194
228
|
status: number;
|
|
195
229
|
cause?: unknown;
|
|
196
230
|
|
|
197
|
-
constructor(
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
231
|
+
constructor(
|
|
232
|
+
code: string,
|
|
233
|
+
message: string,
|
|
234
|
+
options?: {
|
|
235
|
+
status?: number;
|
|
236
|
+
cause?: unknown;
|
|
237
|
+
},
|
|
238
|
+
) {
|
|
202
239
|
super(message);
|
|
240
|
+
Object.setPrototypeOf(this, RouterError.prototype);
|
|
203
241
|
this.code = code;
|
|
204
242
|
this.status = options?.status ?? 500;
|
|
205
|
-
this.type = options?.type;
|
|
206
243
|
this.cause = options?.cause;
|
|
207
244
|
}
|
|
208
245
|
}
|
|
209
246
|
|
|
247
|
+
/**
|
|
248
|
+
* Thrown inside a Prerender or Static handler at build time to signal
|
|
249
|
+
* "skip this entry, log it, and continue with the rest."
|
|
250
|
+
*
|
|
251
|
+
* When thrown, the entry is excluded from the pre-render manifest
|
|
252
|
+
* but the build continues normally. Regular throws (non-Skip)
|
|
253
|
+
* stop all further pre-rendering and fail the build.
|
|
254
|
+
*
|
|
255
|
+
* @example
|
|
256
|
+
* ```typescript
|
|
257
|
+
* export const BlogPost = Prerender(
|
|
258
|
+
* async () => allPosts.map(p => ({ slug: p.slug })),
|
|
259
|
+
* async (ctx) => {
|
|
260
|
+
* const post = await getPost(ctx.params.slug);
|
|
261
|
+
* if (post.draft) throw new Skip(`"${ctx.params.slug}" is a draft`);
|
|
262
|
+
* return <PostPage post={post} />;
|
|
263
|
+
* },
|
|
264
|
+
* );
|
|
265
|
+
* ```
|
|
266
|
+
*/
|
|
267
|
+
export class Skip extends Error {
|
|
268
|
+
name = "Skip" as const;
|
|
269
|
+
cause?: unknown;
|
|
270
|
+
|
|
271
|
+
constructor(message: string = "Entry skipped", options?: ErrorOptions) {
|
|
272
|
+
super(message);
|
|
273
|
+
Object.setPrototypeOf(this, Skip.prototype);
|
|
274
|
+
this.cause = options?.cause;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Type guard to check if a thrown value is a Skip signal.
|
|
280
|
+
*/
|
|
281
|
+
export function isSkip(value: unknown): value is Skip {
|
|
282
|
+
return value instanceof Skip;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Thrown by the partial updater when the server responds with a redirect payload
|
|
287
|
+
* that carries location state. Caught by navigate() to re-navigate to the
|
|
288
|
+
* redirect target with the server-set state merged into history.pushState.
|
|
289
|
+
*
|
|
290
|
+
* Not an Error subclass -- this is a control flow signal, not a failure.
|
|
291
|
+
*/
|
|
292
|
+
export class ServerRedirect {
|
|
293
|
+
readonly name = "ServerRedirect";
|
|
294
|
+
constructor(
|
|
295
|
+
public readonly url: string,
|
|
296
|
+
public readonly state: Record<string, unknown> | undefined,
|
|
297
|
+
) {}
|
|
298
|
+
}
|
|
299
|
+
|
|
210
300
|
/**
|
|
211
301
|
* Thrown when route handler returns invalid type
|
|
212
302
|
*/
|
|
@@ -216,6 +306,7 @@ export class InvalidHandlerError extends Error {
|
|
|
216
306
|
|
|
217
307
|
constructor(message: string, options?: ErrorOptions) {
|
|
218
308
|
super(message);
|
|
309
|
+
Object.setPrototypeOf(this, InvalidHandlerError.prototype);
|
|
219
310
|
this.cause = options?.cause;
|
|
220
311
|
}
|
|
221
312
|
}
|
|
@@ -262,7 +353,12 @@ export function sanitizeError(error: unknown): Response {
|
|
|
262
353
|
return error;
|
|
263
354
|
}
|
|
264
355
|
|
|
265
|
-
|
|
356
|
+
// Vite replaces import.meta.env.DEV at compile time. The fallback covers
|
|
357
|
+
// non-Vite environments (plain Node, test runners without Vite transforms).
|
|
358
|
+
// SECURITY: fail closed — default to production when the environment is ambiguous.
|
|
359
|
+
const isDev =
|
|
360
|
+
(import.meta as any).env?.DEV ??
|
|
361
|
+
globalThis.process?.env?.NODE_ENV === "development";
|
|
266
362
|
|
|
267
363
|
if (isDev) {
|
|
268
364
|
// Development: Send full error details for debugging
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Escape a JSON (or JSON-derived) string for safe embedding inside an HTML
|
|
3
|
+
* <script> element via dangerouslySetInnerHTML. Without this a value containing
|
|
4
|
+
* "</script>" closes the tag early — the rest of the page leaks as raw HTML, and
|
|
5
|
+
* in an executable script the trailing content runs. Escaping "<" defeats the
|
|
6
|
+
* early close; ">" and "&" are escaped for completeness so the serialized payload
|
|
7
|
+
* can never form HTML syntax. The result is still valid JSON and a valid JS
|
|
8
|
+
* string literal (\uXXXX escapes are legal in both) and re-parses identically.
|
|
9
|
+
*
|
|
10
|
+
* Used by every site that interpolates JSON.stringify(...) into inline <script>
|
|
11
|
+
* content: the JSON-LD meta descriptors (handles/MetaTags) and the FOUC theme
|
|
12
|
+
* init script (theme/theme-script).
|
|
13
|
+
*/
|
|
14
|
+
export function escapeJsonForScript(json: string): string {
|
|
15
|
+
return json
|
|
16
|
+
.replace(/</g, "\\u003c")
|
|
17
|
+
.replace(/>/g, "\\u003e")
|
|
18
|
+
.replace(/&/g, "\\u0026");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Escape an inline <script> body so it cannot terminate or corrupt the document.
|
|
23
|
+
* Two sequences are rewritten, each via a JS escape that is valid in string,
|
|
24
|
+
* template, regex (including the `u`/`v` flags), and JSON contexts — so the body
|
|
25
|
+
* still parses identically as code AND as JSON (application/json, ld+json):
|
|
26
|
+
* - "</script" -> "<\/script": stops a literal close tag inside the body from
|
|
27
|
+
* ending the element early. `\/` is a valid JSON escape and a valid regex escape.
|
|
28
|
+
* - "<!--": the "!" (U+0021) is emitted as a unicode escape (see the replacement
|
|
29
|
+
* string below), so the literal "<!--" token never reaches the HTML parser. A
|
|
30
|
+
* literal "<!--" puts the parser into the "script data escaped" state and a
|
|
31
|
+
* following "<script" into "script data DOUBLE escaped", where the real
|
|
32
|
+
* "</script>" no longer closes the element — `var x = "<!--<script>"` would
|
|
33
|
+
* swallow the rest of the document. The unicode-escape form decodes back to "!"
|
|
34
|
+
* in string/template/JSON/regex contexts, unlike "\!" (invalid JSON, invalid
|
|
35
|
+
* /u-regex escape).
|
|
36
|
+
* Real operators such as `a < b` and `a && b` are untouched (unlike
|
|
37
|
+
* escapeJsonForScript, which \u-escapes every "<", "&", ">").
|
|
38
|
+
*
|
|
39
|
+
* GUARANTEE / LIMITATION: value-preserving for the contexts where these sequences
|
|
40
|
+
* legitimately appear — string/template literals, regexes (incl. `u`/`v`), and
|
|
41
|
+
* JSON. It is NOT source-text-preserving (e.g. String.raw`</script>` sees the
|
|
42
|
+
* extra backslash), and it cannot rewrite "</script"/"<!--" that appear as bare
|
|
43
|
+
* code (a legacy `<!--` line comment, or `</script` outside any literal) — neither
|
|
44
|
+
* occurs in valid script payloads. Not a general sanitizer for arbitrary UNTRUSTED
|
|
45
|
+
* source; for untrusted dynamic data, JSON-encode it and read it back, rather than
|
|
46
|
+
* inlining it as code.
|
|
47
|
+
*
|
|
48
|
+
* Used by the Script handle's <Scripts> renderer for inline `children`.
|
|
49
|
+
*/
|
|
50
|
+
export function escapeScriptBody(js: string): string {
|
|
51
|
+
return js.replace(/<!--/g, "<\\u0021--").replace(/<\/(script)/gi, "<\\/$1");
|
|
52
|
+
}
|