@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
package/src/static-handler.ts
CHANGED
|
@@ -35,8 +35,7 @@ import type { Handler } from "./types.js";
|
|
|
35
35
|
import type { StaticBuildContext } from "./prerender.js";
|
|
36
36
|
import type { UseItems, HandlerUseItem } from "./route-types.js";
|
|
37
37
|
import { isCachedFunction } from "./cache/taint.js";
|
|
38
|
-
|
|
39
|
-
// -- Types ------------------------------------------------------------------
|
|
38
|
+
import { isUnderTestRunner } from "./runtime-env.js";
|
|
40
39
|
|
|
41
40
|
export interface StaticHandlerOptions {
|
|
42
41
|
/**
|
|
@@ -61,7 +60,9 @@ export interface StaticHandlerDefinition<
|
|
|
61
60
|
use?: () => UseItems<HandlerUseItem>;
|
|
62
61
|
}
|
|
63
62
|
|
|
64
|
-
//
|
|
63
|
+
// Process-stable fallback id counter (mirrors createHandle/createLoader/Prerender).
|
|
64
|
+
// Only assigned in bare unit tests where the Vite plugin did not inject an id.
|
|
65
|
+
let runtimeStaticIdCounter = 0;
|
|
65
66
|
|
|
66
67
|
export function Static<TParams extends Record<string, any> = {}>(
|
|
67
68
|
handler: (ctx: StaticBuildContext) => ReactNode | Promise<ReactNode>,
|
|
@@ -69,8 +70,6 @@ export function Static<TParams extends Record<string, any> = {}>(
|
|
|
69
70
|
__injectedId?: string,
|
|
70
71
|
): StaticHandlerDefinition<TParams>;
|
|
71
72
|
|
|
72
|
-
// -- Implementation ---------------------------------------------------------
|
|
73
|
-
|
|
74
73
|
export function Static<TParams extends Record<string, any>>(
|
|
75
74
|
handler: Function,
|
|
76
75
|
optionsOrId?: StaticHandlerOptions | string,
|
|
@@ -94,12 +93,15 @@ export function Static<TParams extends Record<string, any>>(
|
|
|
94
93
|
id = maybeId ?? "";
|
|
95
94
|
}
|
|
96
95
|
|
|
97
|
-
if (!id) {
|
|
96
|
+
if (!id && !isUnderTestRunner()) {
|
|
98
97
|
throw new Error(
|
|
99
|
-
"[
|
|
100
|
-
"
|
|
98
|
+
"[rango] Static: missing $$id. Use `export const X = Static(...)` and " +
|
|
99
|
+
"ensure the exposeInternalIds Vite plugin is configured.",
|
|
101
100
|
);
|
|
102
101
|
}
|
|
102
|
+
if (!id) {
|
|
103
|
+
id = `__rango_runtime_static_${runtimeStaticIdCounter++}`;
|
|
104
|
+
}
|
|
103
105
|
|
|
104
106
|
return {
|
|
105
107
|
__brand: "staticHandler" as const,
|
|
@@ -109,11 +111,6 @@ export function Static<TParams extends Record<string, any>>(
|
|
|
109
111
|
};
|
|
110
112
|
}
|
|
111
113
|
|
|
112
|
-
// -- Type guard -------------------------------------------------------------
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Type guard to check if a value is a StaticHandlerDefinition.
|
|
116
|
-
*/
|
|
117
114
|
export function isStaticHandler(
|
|
118
115
|
value: unknown,
|
|
119
116
|
): value is StaticHandlerDefinition {
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cache-status testing primitives for @rangojs/router consumers.
|
|
3
|
+
*
|
|
4
|
+
* Two complementary paths, both DEVELOPMENT/TEST ONLY:
|
|
5
|
+
*
|
|
6
|
+
* 1. Header path — `parseCacheHeader` / `assertCacheStatus` read the
|
|
7
|
+
* `X-Rango-Cache` response header. The header is emitted only when the
|
|
8
|
+
* router's debug cache signal gate is on (the `debugCacheSignal` option or
|
|
9
|
+
* `RANGO_TEST_SIGNALS=1`). With the gate off there is no header and these
|
|
10
|
+
* helpers throw a clear "header missing" error.
|
|
11
|
+
*
|
|
12
|
+
* 2. Telemetry path — `createCacheSink` returns a `{ sink, events }` pair the
|
|
13
|
+
* consumer wires via `createRouter({ telemetry: sink })`. This has ZERO
|
|
14
|
+
* production surface: no header, just structured `cache.decision` events
|
|
15
|
+
* (which carry the same coarse `segments` cache signal). Assert with
|
|
16
|
+
* `assertCacheDecision(events, routeKey, expected)` (the one-call counterpart
|
|
17
|
+
* of `assertCacheStatus`) or filter raw via `filterCacheDecisions`.
|
|
18
|
+
*
|
|
19
|
+
* Both paths report the SAME coarse route-level signal — pick by TRANSPORT, not
|
|
20
|
+
* by meaning: the header is the only signal a black-box Playwright `Response`
|
|
21
|
+
* carries (needs the debug gate ON); the sink is the only zero-production-surface
|
|
22
|
+
* option and the only one exposing per-segment `shouldRevalidate`.
|
|
23
|
+
*
|
|
24
|
+
* v1 cache status is COARSE (route-level): the router reports a single entry
|
|
25
|
+
* keyed by the route key (the route NAME), not per individual segment.
|
|
26
|
+
*
|
|
27
|
+
* Import path: from a Vitest unit/integration test use `@rangojs/router/testing`;
|
|
28
|
+
* from a Playwright e2e use `@rangojs/router/testing/e2e` (the barrel pulls a
|
|
29
|
+
* build-only virtual that does not resolve in a plain Playwright runner).
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import type {
|
|
33
|
+
CacheDecisionEvent,
|
|
34
|
+
CacheSegmentStatus,
|
|
35
|
+
TelemetryEvent,
|
|
36
|
+
TelemetrySink,
|
|
37
|
+
} from "../router/telemetry.js";
|
|
38
|
+
|
|
39
|
+
const CACHE_HEADER = "X-Rango-Cache";
|
|
40
|
+
|
|
41
|
+
/** Expected cache status passed to assertCacheStatus. */
|
|
42
|
+
export type ExpectedCacheStatus = CacheSegmentStatus;
|
|
43
|
+
|
|
44
|
+
/** A target carrying response headers (a Response or a `{ headers }` object). */
|
|
45
|
+
export type CacheStatusTarget = Response | { headers: Headers };
|
|
46
|
+
|
|
47
|
+
export function parseCacheHeader(
|
|
48
|
+
headerValue: string | null | undefined,
|
|
49
|
+
): Record<string, string> {
|
|
50
|
+
const result: Record<string, string> = {};
|
|
51
|
+
if (!headerValue) return result;
|
|
52
|
+
for (const rawEntry of headerValue.split(",")) {
|
|
53
|
+
const entry = rawEntry.trim();
|
|
54
|
+
if (entry.length === 0) continue;
|
|
55
|
+
const eq = entry.indexOf("=");
|
|
56
|
+
if (eq === -1) continue;
|
|
57
|
+
const id = entry.slice(0, eq).trim();
|
|
58
|
+
const status = entry.slice(eq + 1).trim();
|
|
59
|
+
if (id.length === 0 || status.length === 0) continue;
|
|
60
|
+
result[id] = status;
|
|
61
|
+
}
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function getHeaders(target: CacheStatusTarget): Headers {
|
|
66
|
+
return target.headers;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function assertCacheStatus(
|
|
70
|
+
target: CacheStatusTarget,
|
|
71
|
+
segment: string,
|
|
72
|
+
expected: ExpectedCacheStatus,
|
|
73
|
+
): void {
|
|
74
|
+
const headerValue = getHeaders(target).get(CACHE_HEADER);
|
|
75
|
+
if (headerValue === null) {
|
|
76
|
+
throw new Error(
|
|
77
|
+
`assertCacheStatus: response has no ${CACHE_HEADER} header. ` +
|
|
78
|
+
`Enable the debug cache signal via createRouter({ debugCacheSignal: true }) ` +
|
|
79
|
+
`or RANGO_TEST_SIGNALS=1.`,
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
const map = parseCacheHeader(headerValue);
|
|
83
|
+
const actual = map[segment];
|
|
84
|
+
if (actual === undefined) {
|
|
85
|
+
const known = Object.keys(map);
|
|
86
|
+
throw new Error(
|
|
87
|
+
`assertCacheStatus: segment "${segment}" not found in ${CACHE_HEADER} ` +
|
|
88
|
+
`("${headerValue}"). Known segments: ${
|
|
89
|
+
known.length > 0 ? known.join(", ") : "(none)"
|
|
90
|
+
}.`,
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
if (actual !== expected) {
|
|
94
|
+
throw new Error(
|
|
95
|
+
`assertCacheStatus: segment "${segment}" expected "${expected}" but got "${actual}".`,
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* A telemetry sink paired with the array it records events into.
|
|
102
|
+
*/
|
|
103
|
+
export interface CacheSink {
|
|
104
|
+
/** Wire into `createRouter({ telemetry: sink })`. */
|
|
105
|
+
sink: TelemetrySink;
|
|
106
|
+
/** All telemetry events captured so far, in emit order. */
|
|
107
|
+
events: TelemetryEvent[];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function createCacheSink(): CacheSink {
|
|
111
|
+
const events: TelemetryEvent[] = [];
|
|
112
|
+
const sink: TelemetrySink = {
|
|
113
|
+
emit(event: TelemetryEvent): void {
|
|
114
|
+
events.push(event);
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
return { sink, events };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function filterCacheDecisions(
|
|
121
|
+
events: readonly TelemetryEvent[],
|
|
122
|
+
): CacheDecisionEvent[] {
|
|
123
|
+
return events.filter(
|
|
124
|
+
(e): e is CacheDecisionEvent => e.type === "cache.decision",
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Telemetry-path counterpart of {@link assertCacheStatus}: assert a captured
|
|
130
|
+
* `cache.decision` event reported `expected` for the segment keyed by `routeKey`
|
|
131
|
+
* (the route NAME, the same coarse key the header path uses). Throws an
|
|
132
|
+
* actionable error when no matching segment was captured, or on a mismatch.
|
|
133
|
+
*
|
|
134
|
+
* Pairs with {@link createCacheSink}: wire `createRouter({ telemetry: sink })`,
|
|
135
|
+
* drive an RSC request, then assert against the recorded `events`. This is the
|
|
136
|
+
* zero-production-surface path (no header to enable). NOTE: `events` accumulates
|
|
137
|
+
* across requests, so the FIRST matching segment wins — slice or recreate the
|
|
138
|
+
* sink between requests for the same `routeKey`.
|
|
139
|
+
*/
|
|
140
|
+
export function assertCacheDecision(
|
|
141
|
+
events: readonly TelemetryEvent[],
|
|
142
|
+
routeKey: string,
|
|
143
|
+
expected: ExpectedCacheStatus,
|
|
144
|
+
): void {
|
|
145
|
+
const segments = filterCacheDecisions(events).flatMap(
|
|
146
|
+
(d) => d.segments ?? [],
|
|
147
|
+
);
|
|
148
|
+
const seg = segments.find((s) => s.id === routeKey);
|
|
149
|
+
if (seg === undefined) {
|
|
150
|
+
const known = segments.map((s) => s.id);
|
|
151
|
+
throw new Error(
|
|
152
|
+
`assertCacheDecision: no cache.decision segment for routeKey "${routeKey}". ` +
|
|
153
|
+
`Seen: ${known.length > 0 ? known.join(", ") : "(none)"}. Wire ` +
|
|
154
|
+
`createRouter({ telemetry: createCacheSink().sink }) and drive an RSC request.`,
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
if (seg.cacheStatus !== expected) {
|
|
158
|
+
throw new Error(
|
|
159
|
+
`assertCacheDecision: routeKey "${routeKey}" expected "${expected}" but got "${seg.cacheStatus}".`,
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* collectHandle — unit-test a handle's `collect`/accumulator function directly.
|
|
3
|
+
*
|
|
4
|
+
* A handle's collect function (the `createHandle(collect)` argument that maps the
|
|
5
|
+
* per-segment pushed values into the accumulated result) is otherwise not
|
|
6
|
+
* directly reachable: createHandle keeps it in a private registry keyed by the
|
|
7
|
+
* handle's `$$id` and returns only `{ __brand, $$id }`. This primitive runs that
|
|
8
|
+
* REAL registered collect on per-segment values you provide and returns the
|
|
9
|
+
* accumulated result — so the mapper/accumulator is unit-testable without a full
|
|
10
|
+
* route match.
|
|
11
|
+
*
|
|
12
|
+
* It relies on createHandle registering the collect even in a bare test (it
|
|
13
|
+
* assigns a runtime fallback id when the Vite plugin did not inject one). If a
|
|
14
|
+
* handle's module was never imported (so createHandle never ran), the collect is
|
|
15
|
+
* unregistered and this falls back to a flat array with a warning.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { getCollectFn, type Handle } from "../handle.js";
|
|
19
|
+
|
|
20
|
+
export function collectHandle<TData, TAccumulated>(
|
|
21
|
+
handle: Handle<TData, TAccumulated>,
|
|
22
|
+
segments: ReadonlyArray<ReadonlyArray<TData>>,
|
|
23
|
+
): TAccumulated {
|
|
24
|
+
const collectFn = getCollectFn(handle.$$id) as
|
|
25
|
+
| ((segments: TData[][]) => TAccumulated)
|
|
26
|
+
| undefined;
|
|
27
|
+
|
|
28
|
+
if (!collectFn) {
|
|
29
|
+
console.warn(
|
|
30
|
+
`[rango] collectHandle: handle "${handle.$$id}" has no registered collect ` +
|
|
31
|
+
`function. Import the handle's module so createHandle() runs. Falling ` +
|
|
32
|
+
`back to a flat array.`,
|
|
33
|
+
);
|
|
34
|
+
return segments.flat() as unknown as TAccumulated;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Drop empty arrays matching production behavior (segment count/indices).
|
|
38
|
+
const nonEmpty = segments.filter((seg) => seg.length > 0) as TData[][];
|
|
39
|
+
return collectFn(nonEmpty);
|
|
40
|
+
}
|