@rangojs/router 0.0.0-experimental.97 → 0.0.0-experimental.98914650
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/README.md +24 -9
- package/dist/bin/rango.js +157 -63
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +1584 -639
- package/package.json +71 -21
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +60 -0
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +222 -30
- package/skills/caching/SKILL.md +263 -8
- 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 +3 -1
- package/skills/hooks/SKILL.md +235 -28
- package/skills/host-router/SKILL.md +122 -22
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +29 -5
- package/skills/layout/SKILL.md +13 -9
- package/skills/links/SKILL.md +173 -17
- package/skills/loader/SKILL.md +170 -23
- package/skills/middleware/SKILL.md +16 -10
- package/skills/migrate-nextjs/SKILL.md +38 -16
- package/skills/mime-routes/SKILL.md +27 -0
- package/skills/observability/SKILL.md +137 -0
- package/skills/parallel/SKILL.md +11 -7
- package/skills/prerender/SKILL.md +14 -33
- package/skills/rango/SKILL.md +250 -25
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +114 -47
- package/skills/route/SKILL.md +42 -5
- package/skills/router-setup/SKILL.md +3 -3
- package/skills/server-actions/SKILL.md +78 -42
- 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 +316 -26
- package/skills/use-cache/SKILL.md +36 -5
- package/skills/vercel/SKILL.md +107 -0
- 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 +0 -65
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +14 -27
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +37 -143
- 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 +30 -59
- package/src/browser/navigation-client.ts +96 -84
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +32 -82
- package/src/browser/navigation-transaction.ts +9 -59
- package/src/browser/partial-update.ts +60 -127
- package/src/browser/prefetch/cache.ts +82 -72
- package/src/browser/prefetch/fetch.ts +108 -33
- package/src/browser/prefetch/queue.ts +6 -3
- package/src/browser/rango-state.ts +157 -115
- package/src/browser/react/Link.tsx +0 -2
- package/src/browser/react/NavigationProvider.tsx +41 -48
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/filter-segment-order.ts +0 -2
- 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 +17 -14
- package/src/browser/react/use-link-status.ts +0 -4
- package/src/browser/react/use-navigation.ts +0 -3
- 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 +20 -5
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +0 -13
- package/src/browser/response-adapter.ts +52 -1
- package/src/browser/rsc-router.tsx +70 -34
- package/src/browser/scroll-restoration.ts +22 -14
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +168 -44
- package/src/browser/types.ts +36 -21
- 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 +3 -0
- package/src/build/index.ts +8 -2
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +89 -10
- package/src/build/route-types/codegen.ts +4 -4
- package/src/build/route-types/include-resolution.ts +1 -1
- 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 +122 -22
- package/src/build/route-types/scan-filter.ts +1 -1
- 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 +134 -32
- package/src/cache/cache-scope.ts +100 -74
- package/src/cache/cache-tag.ts +98 -0
- package/src/cache/cf/cf-cache-store.ts +2255 -238
- package/src/cache/cf/index.ts +6 -16
- package/src/cache/document-cache.ts +61 -20
- package/src/cache/handle-snapshot.ts +63 -0
- package/src/cache/index.ts +22 -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/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 +25 -61
- package/src/component-utils.ts +19 -0
- package/src/context-var.ts +17 -5
- 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 +31 -23
- 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 +63 -9
- package/src/index.ts +64 -9
- 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-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +10 -11
- package/src/prerender/store.ts +32 -37
- package/src/prerender.ts +61 -6
- package/src/redirect-origin.ts +100 -0
- package/src/response-utils.ts +9 -0
- package/src/reverse.ts +65 -40
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +7 -72
- package/src/route-definition/dsl-helpers.ts +244 -281
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +40 -17
- package/src/route-definition/redirect.ts +43 -9
- package/src/route-definition/resolve-handler-use.ts +6 -0
- package/src/route-definition/use-item-types.ts +32 -0
- package/src/route-map-builder.ts +0 -16
- package/src/route-types.ts +19 -41
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +15 -15
- package/src/router/error-handling.ts +13 -17
- package/src/router/find-match.ts +44 -23
- package/src/router/handler-context.ts +4 -41
- package/src/router/intercept-resolution.ts +14 -19
- package/src/router/lazy-includes.ts +9 -46
- package/src/router/loader-resolution.ts +91 -46
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +18 -29
- package/src/router/match-api.ts +0 -20
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +57 -58
- package/src/router/match-middleware/background-revalidation.ts +0 -7
- package/src/router/match-middleware/cache-lookup.ts +150 -271
- package/src/router/match-middleware/cache-store.ts +3 -33
- 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 +31 -80
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-types.ts +5 -112
- package/src/router/middleware.ts +118 -133
- package/src/router/navigation-snapshot.ts +0 -51
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +62 -67
- package/src/router/prerender-match.ts +99 -63
- package/src/router/preview-match.ts +3 -1
- package/src/router/request-classification.ts +28 -62
- package/src/router/revalidation.ts +50 -56
- package/src/router/route-snapshot.ts +0 -1
- package/src/router/router-context.ts +0 -27
- package/src/router/router-interfaces.ts +68 -35
- package/src/router/router-options.ts +55 -1
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +44 -63
- package/src/router/segment-resolution/helpers.ts +34 -0
- package/src/router/segment-resolution/loader-cache.ts +40 -37
- package/src/router/segment-resolution/revalidation.ts +203 -285
- 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 +0 -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 +87 -48
- package/src/router/types.ts +9 -63
- package/src/router/url-params.ts +0 -5
- package/src/router.ts +80 -41
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +83 -78
- package/src/rsc/helpers.ts +93 -5
- package/src/rsc/index.ts +1 -1
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/manifest-init.ts +28 -41
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +12 -1
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +76 -62
- package/src/rsc/rsc-rendering.ts +41 -60
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +62 -67
- package/src/rsc/ssr-setup.ts +16 -0
- package/src/rsc/types.ts +10 -5
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +4 -20
- package/src/segment-loader-promise.ts +14 -2
- package/src/segment-system.tsx +199 -142
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +150 -51
- package/src/server/cookie-store.ts +80 -5
- package/src/server/handle-store.ts +7 -24
- package/src/server/loader-registry.ts +5 -24
- package/src/server/request-context.ts +165 -87
- package/src/ssr/index.tsx +14 -14
- 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 +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 +13 -4
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +54 -41
- package/src/types/handler-context.ts +97 -22
- package/src/types/index.ts +1 -10
- package/src/types/loader-types.ts +6 -3
- package/src/types/request-scope.ts +0 -19
- package/src/types/route-config.ts +6 -50
- package/src/types/route-entry.ts +0 -6
- package/src/types/segments.ts +18 -14
- package/src/urls/include-helper.ts +9 -56
- package/src/urls/index.ts +1 -11
- package/src/urls/path-helper-types.ts +19 -5
- package/src/urls/path-helper.ts +17 -106
- package/src/urls/pattern-types.ts +36 -19
- package/src/urls/response-types.ts +20 -19
- package/src/urls/type-extraction.ts +58 -139
- package/src/urls/urls-function.ts +1 -18
- package/src/use-loader.tsx +292 -107
- package/src/vite/debug.ts +1 -0
- package/src/vite/discovery/bundle-postprocess.ts +8 -7
- package/src/vite/discovery/discover-routers.ts +95 -82
- package/src/vite/discovery/discovery-errors.ts +194 -0
- package/src/vite/discovery/prerender-collection.ts +26 -34
- package/src/vite/discovery/route-types-writer.ts +40 -84
- package/src/vite/discovery/state.ts +39 -1
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +4 -0
- package/src/vite/plugin-types.ts +185 -10
- package/src/vite/plugins/cjs-to-esm.ts +3 -18
- package/src/vite/plugins/client-ref-dedup.ts +0 -11
- package/src/vite/plugins/client-ref-hashing.ts +12 -11
- package/src/vite/plugins/cloudflare-protocol-stub.ts +1 -21
- package/src/vite/plugins/expose-action-id.ts +4 -75
- package/src/vite/plugins/expose-id-utils.ts +3 -54
- package/src/vite/plugins/expose-ids/export-analysis.ts +76 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +6 -74
- package/src/vite/plugins/expose-ids/loader-transform.ts +3 -20
- package/src/vite/plugins/expose-ids/router-transform.ts +0 -13
- package/src/vite/plugins/expose-internal-ids.ts +57 -67
- package/src/vite/plugins/performance-tracks.ts +9 -16
- package/src/vite/plugins/refresh-cmd.ts +1 -1
- package/src/vite/plugins/use-cache-transform.ts +26 -49
- package/src/vite/plugins/vercel-output.ts +258 -0
- package/src/vite/plugins/version-injector.ts +2 -32
- package/src/vite/plugins/version-plugin.ts +32 -23
- package/src/vite/plugins/virtual-entries.ts +35 -17
- package/src/vite/rango.ts +148 -115
- package/src/vite/router-discovery.ts +220 -68
- package/src/vite/utils/ast-handler-extract.ts +15 -31
- 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 +1 -73
- package/src/vite/utils/prerender-utils.ts +0 -34
- package/src/vite/utils/shared-utils.ts +95 -43
- package/src/browser/action-response-classifier.ts +0 -99
- package/src/browser/react/use-client-cache.ts +0 -58
- package/src/browser/shallow.ts +0 -40
- package/src/handles/index.ts +0 -7
- package/src/router/middleware-cookies.ts +0 -55
|
@@ -73,7 +73,7 @@ export interface RootLayoutProps {
|
|
|
73
73
|
/**
|
|
74
74
|
* Router configuration options
|
|
75
75
|
*/
|
|
76
|
-
export interface
|
|
76
|
+
export interface RangoOptions<TEnv = any> {
|
|
77
77
|
/**
|
|
78
78
|
* Unique identifier for this router instance.
|
|
79
79
|
* Used to namespace static output files and route maps.
|
|
@@ -132,6 +132,21 @@ export interface RSCRouterOptions<TEnv = any> {
|
|
|
132
132
|
*/
|
|
133
133
|
allowDebugManifest?: boolean;
|
|
134
134
|
|
|
135
|
+
/**
|
|
136
|
+
* DEVELOPMENT/TEST ONLY. Emit an `X-Rango-Cache` response header describing
|
|
137
|
+
* the cache status of the matched route, for use by testing primitives such
|
|
138
|
+
* as `assertCacheStatus`.
|
|
139
|
+
*
|
|
140
|
+
* Defaults to `false`. When neither this option nor the
|
|
141
|
+
* `RANGO_TEST_SIGNALS=1` environment flag is set, NO header is emitted and
|
|
142
|
+
* router output is byte-identical to the default.
|
|
143
|
+
*
|
|
144
|
+
* The header encodes per-segment (v1: coarse route-level) status keyed by the
|
|
145
|
+
* route NAME, e.g. `X-Rango-Cache: product.detail=hit`. Do NOT enable in
|
|
146
|
+
* production — it exposes internal cache decisions.
|
|
147
|
+
*/
|
|
148
|
+
debugCacheSignal?: boolean;
|
|
149
|
+
|
|
135
150
|
/**
|
|
136
151
|
* Document component that wraps the entire application.
|
|
137
152
|
*
|
|
@@ -357,6 +372,30 @@ export interface RSCRouterOptions<TEnv = any> {
|
|
|
357
372
|
*/
|
|
358
373
|
theme?: import("../theme/types.js").ThemeConfig | true;
|
|
359
374
|
|
|
375
|
+
/**
|
|
376
|
+
* Default for whether the router wraps `transition()` segments in its own
|
|
377
|
+
* React `<ViewTransition>` boundary (experimental React only).
|
|
378
|
+
*
|
|
379
|
+
* - "auto" (default): every route/layout that opts in via `transition()`
|
|
380
|
+
* gets a router-owned cross-fade.
|
|
381
|
+
* - false: the router never places its own boundary. Routes that use
|
|
382
|
+
* `transition()` still drive navigation through startTransition (so loaders
|
|
383
|
+
* hold instead of flashing a skeleton) and still let consumer-placed
|
|
384
|
+
* `<ViewTransition>` elements animate — the router just contributes no
|
|
385
|
+
* cross-fade of its own. This is the "router triggers, you place the
|
|
386
|
+
* transitions" model.
|
|
387
|
+
*
|
|
388
|
+
* A per-segment `transition({ viewTransition })` overrides this default.
|
|
389
|
+
*
|
|
390
|
+
* @example
|
|
391
|
+
* ```typescript
|
|
392
|
+
* // App-wide: drive + hold, but never auto-wrap. Place <ViewTransition>
|
|
393
|
+
* // yourself in components where you want a morph.
|
|
394
|
+
* const router = createRouter<AppEnv>({ viewTransition: false });
|
|
395
|
+
* ```
|
|
396
|
+
*/
|
|
397
|
+
viewTransition?: "auto" | false;
|
|
398
|
+
|
|
360
399
|
/**
|
|
361
400
|
* URL patterns to register with the router.
|
|
362
401
|
*
|
|
@@ -457,6 +496,21 @@ export interface RSCRouterOptions<TEnv = any> {
|
|
|
457
496
|
*/
|
|
458
497
|
prefetchCacheTTL?: number | false;
|
|
459
498
|
|
|
499
|
+
/**
|
|
500
|
+
* Prefix for the rango state cookie name. The resolved name is
|
|
501
|
+
* `{prefix}_{routerId}`; the prefix is sanitized to cookie-name-safe
|
|
502
|
+
* characters (`[A-Za-z0-9-]`) and an empty result falls back to the default.
|
|
503
|
+
*
|
|
504
|
+
* The rango state cookie keys the client's prefetch / HTTP caches. Overriding
|
|
505
|
+
* the prefix lets you align it with cookie-naming policies or consent-manager
|
|
506
|
+
* classification lists, or avoid colliding with an existing `rango-state`
|
|
507
|
+
* cookie. It is not a full-name override: the `_{routerId}` suffix is what
|
|
508
|
+
* keeps sibling apps on one origin from clobbering each other's state.
|
|
509
|
+
*
|
|
510
|
+
* @default "rango-state"
|
|
511
|
+
*/
|
|
512
|
+
stateCookiePrefix?: string;
|
|
513
|
+
|
|
460
514
|
/**
|
|
461
515
|
* Enable connection warmup to keep TCP+TLS alive after idle periods.
|
|
462
516
|
*
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { RangoInternal } from "./router-interfaces.js";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Brand marker for identifying router instances at build time.
|
|
@@ -12,10 +12,7 @@ export const RSC_ROUTER_BRAND = "__rsc_router__" as const;
|
|
|
12
12
|
* Used by the Vite plugin at build time to discover routers and extract
|
|
13
13
|
* manifests, prefix trees, and pre-render candidates.
|
|
14
14
|
*/
|
|
15
|
-
export const RouterRegistry: Map<
|
|
16
|
-
string,
|
|
17
|
-
RSCRouterInternal<any, any>
|
|
18
|
-
> = new Map();
|
|
15
|
+
export const RouterRegistry: Map<string, RangoInternal<any, any>> = new Map();
|
|
19
16
|
|
|
20
17
|
export let routerAutoId = 0;
|
|
21
18
|
|
|
@@ -27,58 +27,17 @@ import {
|
|
|
27
27
|
tryStaticSlot,
|
|
28
28
|
resolveLayoutComponent,
|
|
29
29
|
resolveWithErrorBoundary,
|
|
30
|
+
warnOnStreamedResponse,
|
|
30
31
|
} from "./helpers.js";
|
|
32
|
+
import { applyViewTransitionDefault } from "./view-transition-default.js";
|
|
31
33
|
import { getRouterContext } from "../router-context.js";
|
|
32
|
-
import {
|
|
34
|
+
import { observeStreamedHandler } from "./streamed-handler-telemetry.js";
|
|
33
35
|
import {
|
|
34
36
|
track,
|
|
35
|
-
|
|
37
|
+
RangoContext,
|
|
36
38
|
runInsideLoaderScope,
|
|
37
39
|
} from "../../server/context.js";
|
|
38
40
|
|
|
39
|
-
// ---------------------------------------------------------------------------
|
|
40
|
-
// Streamed handler telemetry
|
|
41
|
-
// ---------------------------------------------------------------------------
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Attach a fire-and-forget rejection observer to a streamed handler promise.
|
|
45
|
-
* React catches the actual error via its error boundary; this only emits
|
|
46
|
-
* the handler.error telemetry event.
|
|
47
|
-
*/
|
|
48
|
-
function observeStreamedHandler(
|
|
49
|
-
promise: Promise<ReactNode>,
|
|
50
|
-
segmentId: string,
|
|
51
|
-
segmentType: string,
|
|
52
|
-
pathname?: string,
|
|
53
|
-
routeKey?: string,
|
|
54
|
-
params?: Record<string, string>,
|
|
55
|
-
): void {
|
|
56
|
-
let routerCtx;
|
|
57
|
-
try {
|
|
58
|
-
routerCtx = getRouterContext();
|
|
59
|
-
} catch {
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
if (!routerCtx?.telemetry) return;
|
|
63
|
-
const sink = resolveSink(routerCtx.telemetry);
|
|
64
|
-
const reqId = routerCtx.requestId;
|
|
65
|
-
promise.catch((err: unknown) => {
|
|
66
|
-
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
67
|
-
safeEmit(sink, {
|
|
68
|
-
type: "handler.error",
|
|
69
|
-
timestamp: performance.now(),
|
|
70
|
-
requestId: reqId,
|
|
71
|
-
segmentId,
|
|
72
|
-
segmentType,
|
|
73
|
-
error: errorObj,
|
|
74
|
-
handledByBoundary: true,
|
|
75
|
-
pathname,
|
|
76
|
-
routeKey,
|
|
77
|
-
params,
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
|
|
82
41
|
// ---------------------------------------------------------------------------
|
|
83
42
|
// Fresh path (full match, no revalidation)
|
|
84
43
|
// ---------------------------------------------------------------------------
|
|
@@ -132,18 +91,32 @@ export async function resolveLoaders<TEnv>(
|
|
|
132
91
|
|
|
133
92
|
// Loading disabled: still start all loaders in parallel, but only emit
|
|
134
93
|
// settled promises so handlers don't stream loading placeholders.
|
|
135
|
-
|
|
94
|
+
//
|
|
95
|
+
// Wrap each loader promise with wrapLoaderPromise BEFORE awaiting. The wrapped
|
|
96
|
+
// promise resolves to a LoaderDataResult and never rejects, routing a failed
|
|
97
|
+
// loader to its own per-loader error boundary. Awaiting the RAW promises here
|
|
98
|
+
// instead would (1) propagate a rejection to the segment-level boundary,
|
|
99
|
+
// collapsing the whole entry and discarding successful sibling data, and
|
|
100
|
+
// (2) leave the other in-flight raw promises without a .catch, producing
|
|
101
|
+
// unhandled rejections. Mirrors the loading path and intercept-resolution.
|
|
102
|
+
const pendingLoaderData = loaderEntries.map((loaderEntry, i) => {
|
|
103
|
+
const { loader } = loaderEntry;
|
|
104
|
+
const segmentId = `${shortCode}D${i}.${loader.$$id}`;
|
|
136
105
|
const start = performance.now();
|
|
137
|
-
const
|
|
138
|
-
|
|
106
|
+
const wrapped = deps.wrapLoaderPromise(
|
|
107
|
+
runInsideLoaderScope(() =>
|
|
108
|
+
resolveLoaderData(loaderEntry, ctx, ctx.pathname),
|
|
109
|
+
),
|
|
110
|
+
entry,
|
|
111
|
+
segmentId,
|
|
112
|
+
ctx.pathname,
|
|
139
113
|
);
|
|
140
|
-
return {
|
|
114
|
+
return { wrapped, start, segmentId, loaderId: loader.$$id };
|
|
141
115
|
});
|
|
142
|
-
await Promise.all(pendingLoaderData.map((p) => p.
|
|
116
|
+
await Promise.all(pendingLoaderData.map((p) => p.wrapped));
|
|
143
117
|
|
|
144
118
|
return loaderEntries.map((loaderEntry, i) => {
|
|
145
119
|
const { loader } = loaderEntry;
|
|
146
|
-
const segmentId = `${shortCode}D${i}.${loader.$$id}`;
|
|
147
120
|
const pending = pendingLoaderData[i]!;
|
|
148
121
|
if (ms && !ms.metrics.some((m) => m.label === `loader:${loader.$$id}`)) {
|
|
149
122
|
// All loaders ran in parallel via Promise.all — each span covers
|
|
@@ -159,19 +132,14 @@ export async function resolveLoaders<TEnv>(
|
|
|
159
132
|
);
|
|
160
133
|
}
|
|
161
134
|
return {
|
|
162
|
-
id: segmentId,
|
|
135
|
+
id: pending.segmentId,
|
|
163
136
|
namespace: entry.id,
|
|
164
137
|
type: "loader" as const,
|
|
165
138
|
index: i,
|
|
166
139
|
component: null,
|
|
167
140
|
params: ctx.params,
|
|
168
141
|
loaderId: loader.$$id,
|
|
169
|
-
loaderData:
|
|
170
|
-
pending.promise,
|
|
171
|
-
entry,
|
|
172
|
-
segmentId,
|
|
173
|
-
ctx.pathname,
|
|
174
|
-
),
|
|
142
|
+
loaderData: pending.wrapped,
|
|
175
143
|
belongsToRoute,
|
|
176
144
|
};
|
|
177
145
|
});
|
|
@@ -224,7 +192,10 @@ export async function resolveSegment<TEnv>(
|
|
|
224
192
|
index: 0,
|
|
225
193
|
component,
|
|
226
194
|
loading: entry.loading === false ? null : entry.loading,
|
|
227
|
-
transition:
|
|
195
|
+
transition: applyViewTransitionDefault(
|
|
196
|
+
entry.transition,
|
|
197
|
+
deps.viewTransitionDefault,
|
|
198
|
+
),
|
|
228
199
|
params,
|
|
229
200
|
belongsToRoute: false,
|
|
230
201
|
layoutName: entry.id,
|
|
@@ -293,6 +264,7 @@ export async function resolveSegment<TEnv>(
|
|
|
293
264
|
if (entry.loading) {
|
|
294
265
|
const result = handleHandlerResult(handler(context));
|
|
295
266
|
if (result instanceof Promise) {
|
|
267
|
+
warnOnStreamedResponse(result, entry.id);
|
|
296
268
|
result.finally(doneRouteHandler).catch(() => {});
|
|
297
269
|
const tracked = deps.trackHandler(result, {
|
|
298
270
|
segmentId: entry.shortCode,
|
|
@@ -359,7 +331,10 @@ export async function resolveSegment<TEnv>(
|
|
|
359
331
|
index: 0,
|
|
360
332
|
component: component ?? null,
|
|
361
333
|
loading: entry.loading === false ? null : entry.loading,
|
|
362
|
-
transition:
|
|
334
|
+
transition: applyViewTransitionDefault(
|
|
335
|
+
entry.transition,
|
|
336
|
+
deps.viewTransitionDefault,
|
|
337
|
+
),
|
|
363
338
|
params,
|
|
364
339
|
belongsToRoute: true,
|
|
365
340
|
...(entry.mountPath ? { mountPath: entry.mountPath } : {}),
|
|
@@ -443,7 +418,10 @@ export async function resolveOrphanLayout<TEnv>(
|
|
|
443
418
|
belongsToRoute,
|
|
444
419
|
layoutName: orphan.id,
|
|
445
420
|
loading: orphan.loading === false ? null : orphan.loading,
|
|
446
|
-
transition:
|
|
421
|
+
transition: applyViewTransitionDefault(
|
|
422
|
+
orphan.transition,
|
|
423
|
+
deps.viewTransitionDefault,
|
|
424
|
+
),
|
|
447
425
|
...(orphan.mountPath ? { mountPath: orphan.mountPath } : {}),
|
|
448
426
|
});
|
|
449
427
|
|
|
@@ -565,7 +543,10 @@ export async function resolveParallelEntry<TEnv>(
|
|
|
565
543
|
index: 0,
|
|
566
544
|
component,
|
|
567
545
|
loading: parallelEntry.loading === false ? null : parallelEntry.loading,
|
|
568
|
-
transition:
|
|
546
|
+
transition: applyViewTransitionDefault(
|
|
547
|
+
parallelEntry.transition,
|
|
548
|
+
deps.viewTransitionDefault,
|
|
549
|
+
),
|
|
569
550
|
params,
|
|
570
551
|
slot,
|
|
571
552
|
belongsToRoute,
|
|
@@ -632,7 +613,7 @@ export async function resolveAllSegments<TEnv>(
|
|
|
632
613
|
// can guard non-cacheable variable reads. Also guards response-level
|
|
633
614
|
// side effects (headers.set). Persists for all descendant entries.
|
|
634
615
|
if (entry.type === "cache") {
|
|
635
|
-
const store =
|
|
616
|
+
const store = RangoContext.getStore();
|
|
636
617
|
if (store) store.insideCacheScope = true;
|
|
637
618
|
}
|
|
638
619
|
const doneEntry = track(`segment:${entry.id}`, 1);
|
|
@@ -52,6 +52,40 @@ export function handleHandlerResult(
|
|
|
52
52
|
return result;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Dev-only: warn when a handler on a route that declares loading() resolves or
|
|
57
|
+
* rejects with a Response (e.g. redirect()).
|
|
58
|
+
*
|
|
59
|
+
* On a non-loading route a returned/thrown Response short-circuits to an HTTP
|
|
60
|
+
* redirect. But when the route declares loading(), the handler result is
|
|
61
|
+
* streamed (not awaited at the resolution boundary), so the Response surfaces
|
|
62
|
+
* only during RSC serialization and is rendered into the stream instead of
|
|
63
|
+
* becoming a 302/308 — a silent failure mode. Issue redirects from middleware,
|
|
64
|
+
* a loader, or a synchronous handler return instead. Compiled out in production.
|
|
65
|
+
*/
|
|
66
|
+
export function warnOnStreamedResponse(
|
|
67
|
+
result: Promise<unknown>,
|
|
68
|
+
entryId: string,
|
|
69
|
+
): void {
|
|
70
|
+
if (process.env.NODE_ENV === "production") return;
|
|
71
|
+
// A Response can surface either as a rejection (handleHandlerResult rethrows a
|
|
72
|
+
// resolved Response) or as a resolved value (the raw parallel-slot handler is
|
|
73
|
+
// not run through handleHandlerResult). Check both so every streamed path is
|
|
74
|
+
// covered. Each handler is an independent observer; it does not consume the
|
|
75
|
+
// rejection for the trackHandler/observeStreamedHandler chains.
|
|
76
|
+
const check = (value: unknown) => {
|
|
77
|
+
if (value instanceof Response) {
|
|
78
|
+
console.warn(
|
|
79
|
+
`[rango] Handler for "${entryId}" returned a Response (e.g. ` +
|
|
80
|
+
`redirect()), but it declares loading(): the Response is rendered ` +
|
|
81
|
+
`into the RSC stream, NOT sent as an HTTP redirect. Issue redirects ` +
|
|
82
|
+
`from middleware, a loader, or a synchronous handler return.`,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
result.then(check, check);
|
|
87
|
+
}
|
|
88
|
+
|
|
55
89
|
// ---------------------------------------------------------------------------
|
|
56
90
|
// Static handler interception
|
|
57
91
|
// ---------------------------------------------------------------------------
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* Cache key resolution (3-tier, matching CacheScope.resolveKey):
|
|
9
9
|
* 1. options.key(requestCtx) — full override
|
|
10
10
|
* 2. store.keyGenerator(requestCtx, defaultKey) — store-level modification
|
|
11
|
-
* 3. loader:{loaderId}:{pathname}:{sortedParams} — default
|
|
11
|
+
* 3. loader:{loaderId}:{host}{pathname}:{sortedParams} — default
|
|
12
12
|
*
|
|
13
13
|
* Values are serialized via RSC Flight (serializeResult/deserializeResult),
|
|
14
14
|
* supporting ReactNode, Promises, null, and all RSC-serializable types.
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
import type { LoaderEntry } from "../../server/context.js";
|
|
22
|
-
import type { HandlerContext } from "../../types.js";
|
|
22
|
+
import type { HandlerContext, InternalHandlerContext } from "../../types.js";
|
|
23
23
|
import { INTERNAL_RANGO_DEBUG } from "../../internal-debug.js";
|
|
24
24
|
import { getRequestContext } from "../../server/request-context.js";
|
|
25
25
|
import { sortedRouteParams } from "../../cache/cache-key-utils.js";
|
|
@@ -28,9 +28,11 @@ import {
|
|
|
28
28
|
resolveSwrWindow,
|
|
29
29
|
resolveCacheKey,
|
|
30
30
|
resolveCacheStore,
|
|
31
|
+
resolveTagsOption,
|
|
31
32
|
DEFAULT_ROUTE_TTL,
|
|
32
33
|
} from "../../cache/cache-policy.js";
|
|
33
34
|
import { readThroughItem } from "../../cache/read-through-swr.js";
|
|
35
|
+
import { recordRequestTags } from "../../cache/cache-tag.js";
|
|
34
36
|
// Lazy-loaded to avoid pulling @vitejs/plugin-rsc/rsc into modules that
|
|
35
37
|
// import segment-resolution but never use loader caching.
|
|
36
38
|
let _serializeResult: typeof import("../../cache/segment-codec.js").serializeResult;
|
|
@@ -55,12 +57,13 @@ function debugLoaderCacheLog(message: string): void {
|
|
|
55
57
|
|
|
56
58
|
function getDefaultLoaderCacheKey(
|
|
57
59
|
loaderId: string,
|
|
60
|
+
host: string,
|
|
58
61
|
pathname: string,
|
|
59
62
|
params: Record<string, string>,
|
|
60
63
|
): string {
|
|
61
64
|
const paramStr = sortedRouteParams(params);
|
|
62
65
|
const base = paramStr ? `${pathname}:${paramStr}` : pathname;
|
|
63
|
-
return `loader:${loaderId}:${base}`;
|
|
66
|
+
return `loader:${loaderId}:${host}${base}`;
|
|
64
67
|
}
|
|
65
68
|
|
|
66
69
|
/**
|
|
@@ -74,7 +77,13 @@ async function resolveLoaderKey(
|
|
|
74
77
|
params: Record<string, string>,
|
|
75
78
|
): Promise<string> {
|
|
76
79
|
const options = loaderEntry.cache!.options;
|
|
77
|
-
|
|
80
|
+
// The host is part of the loader cache identity, matching the route-level
|
|
81
|
+
// cache (cache-scope getCacheKeyBase: `${host}${pathname}`) and "use cache"
|
|
82
|
+
// (cache-runtime pushes ctx.url.host). Without it, a multi-tenant host router
|
|
83
|
+
// serving the same pathname for different hosts would leak one host's cached
|
|
84
|
+
// loader data to another.
|
|
85
|
+
const host = getRequestContext()?.url?.host ?? "localhost";
|
|
86
|
+
const defaultKey = getDefaultLoaderCacheKey(loaderId, host, pathname, params);
|
|
78
87
|
if (options === false) return defaultKey;
|
|
79
88
|
return resolveCacheKey(options.key, store, defaultKey, "LoaderCache");
|
|
80
89
|
}
|
|
@@ -87,23 +96,8 @@ async function resolveLoaderKey(
|
|
|
87
96
|
*/
|
|
88
97
|
function resolveTags(loaderEntry: LoaderEntry): string[] | undefined {
|
|
89
98
|
const options = loaderEntry.cache?.options;
|
|
90
|
-
if (!options
|
|
91
|
-
|
|
92
|
-
if (typeof options.tags === "function") {
|
|
93
|
-
const requestCtx = getRequestContext();
|
|
94
|
-
if (!requestCtx) return undefined;
|
|
95
|
-
try {
|
|
96
|
-
return options.tags(requestCtx);
|
|
97
|
-
} catch (error) {
|
|
98
|
-
console.error(
|
|
99
|
-
`[LoaderCache] Tags function failed, caching without tags:`,
|
|
100
|
-
error,
|
|
101
|
-
);
|
|
102
|
-
return undefined;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return options.tags;
|
|
99
|
+
if (!options) return undefined;
|
|
100
|
+
return resolveTagsOption(options.tags, getRequestContext(), "LoaderCache");
|
|
107
101
|
}
|
|
108
102
|
|
|
109
103
|
function getLoaderStore(
|
|
@@ -152,11 +146,30 @@ export function resolveLoaderData<TEnv>(
|
|
|
152
146
|
const swrWindow = resolveSwrWindow(options.swr, store.defaults);
|
|
153
147
|
const swr = swrWindow || undefined;
|
|
154
148
|
const tags = resolveTags(loaderEntry);
|
|
149
|
+
recordRequestTags(tags);
|
|
150
|
+
|
|
151
|
+
// A handler that later awaits this same loader via ctx.use(loader) must get
|
|
152
|
+
// THIS memoized promise, not a fresh execution. Rather than rebind ctx.use
|
|
153
|
+
// once per cached loader (O(N) chained wrappers + a synchronous
|
|
154
|
+
// capture-before-overwrite invariant), install a single stable interceptor on
|
|
155
|
+
// the first cached loader that consults a per-ctx override table, then just
|
|
156
|
+
// prime the table for each subsequent cached loader. The captured pre-
|
|
157
|
+
// interceptor `originalUse` (whatever setup mode installed it) runs the
|
|
158
|
+
// cache-miss execute, so a loader never awaits its own in-flight promise.
|
|
159
|
+
const internal = ctx as InternalHandlerContext<any, TEnv>;
|
|
160
|
+
let overrides = internal._loaderCacheOverrides;
|
|
161
|
+
if (!overrides) {
|
|
162
|
+
overrides = internal._loaderCacheOverrides = new Map();
|
|
163
|
+
const originalUse = ctx.use;
|
|
164
|
+
internal._loaderCacheOriginalUse = originalUse;
|
|
165
|
+
ctx.use = ((item: any) => {
|
|
166
|
+
const cached = overrides!.get(item?.$$id);
|
|
167
|
+
if (cached) return cached;
|
|
168
|
+
return originalUse(item);
|
|
169
|
+
}) as typeof ctx.use;
|
|
170
|
+
}
|
|
171
|
+
const runMiss = internal._loaderCacheOriginalUse!;
|
|
155
172
|
|
|
156
|
-
// Wrap ctx.use() so cache HIT primes the handler's memoization map.
|
|
157
|
-
// ctx.use() closes over the match context's loaderPromises (not request context's).
|
|
158
|
-
// By intercepting ctx.use(), we inject cached data into the correct map.
|
|
159
|
-
const originalUse = ctx.use;
|
|
160
173
|
const dataPromise = (async () => {
|
|
161
174
|
const codec = await getCodec();
|
|
162
175
|
const key = await resolveLoaderKey(
|
|
@@ -171,7 +184,7 @@ export function resolveLoaderData<TEnv>(
|
|
|
171
184
|
getItem: (k) => store.getItem!(k),
|
|
172
185
|
setItem: (k, v, o) => store.setItem!(k, v, o),
|
|
173
186
|
key,
|
|
174
|
-
execute: () =>
|
|
187
|
+
execute: () => runMiss(loaderEntry.loader),
|
|
175
188
|
serialize: (d) => codec.serializeResult(d),
|
|
176
189
|
deserialize: (v) => codec.deserializeResult(v),
|
|
177
190
|
storeOptions: { ttl, swr, tags },
|
|
@@ -183,17 +196,7 @@ export function resolveLoaderData<TEnv>(
|
|
|
183
196
|
});
|
|
184
197
|
})();
|
|
185
198
|
|
|
186
|
-
|
|
187
|
-
// This is needed because ctx.use() closes over the match context's loaderPromises
|
|
188
|
-
// map which is separate from the request context. By wrapping use(), we intercept
|
|
189
|
-
// the handler's call and return the shared dataPromise.
|
|
190
|
-
const wrappedUse = ((item: any) => {
|
|
191
|
-
if (item === loaderEntry.loader || item?.$$id === loaderId) {
|
|
192
|
-
return dataPromise;
|
|
193
|
-
}
|
|
194
|
-
return originalUse(item);
|
|
195
|
-
}) as typeof ctx.use;
|
|
196
|
-
ctx.use = wrappedUse;
|
|
199
|
+
overrides.set(loaderId, dataPromise);
|
|
197
200
|
|
|
198
201
|
return dataPromise;
|
|
199
202
|
}
|