@rangojs/router 0.16.0 → 0.18.0
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 +255 -89
- package/dist/testing/vitest.js +252 -0
- package/dist/types/browser/cookie-name.d.ts +6 -0
- package/dist/types/browser/history-state.d.ts +16 -0
- package/dist/types/browser/navigation-transaction.d.ts +6 -0
- package/dist/types/browser/partial-update.d.ts +11 -0
- package/dist/types/browser/react/ScrollRestoration.d.ts +6 -6
- package/dist/types/browser/rsc-router.d.ts +1 -1
- package/dist/types/browser/scroll-restoration.d.ts +7 -0
- package/dist/types/cache/cache-exec-scope.d.ts +18 -1
- package/dist/types/cache/cache-key-utils.d.ts +17 -2
- package/dist/types/cache/cache-policy.d.ts +17 -0
- package/dist/types/cache/cache-scope.d.ts +132 -19
- package/dist/types/cache/cache-tag.d.ts +92 -7
- package/dist/types/cache/cf/cf-base64.d.ts +6 -0
- package/dist/types/cache/cf/cf-cache-constants.d.ts +11 -0
- package/dist/types/cache/cf/cf-cache-store.d.ts +185 -44
- package/dist/types/cache/cf/cf-cache-types.d.ts +31 -4
- package/dist/types/cache/cf/cf-shell-frame.d.ts +95 -0
- package/dist/types/cache/cf/cf-tag-marker-memo.d.ts +34 -0
- package/dist/types/cache/handle-capture.d.ts +41 -3
- package/dist/types/cache/handle-snapshot.d.ts +54 -13
- package/dist/types/cache/index.d.ts +1 -0
- package/dist/types/cache/isolate-tag-memo.d.ts +149 -0
- package/dist/types/cache/segment-codec.d.ts +10 -3
- package/dist/types/cache/shell-memo.d.ts +178 -0
- package/dist/types/cache/shell-snapshot.d.ts +110 -21
- package/dist/types/cache/tag-invalidation.d.ts +30 -3
- package/dist/types/cache/taint.d.ts +1 -1
- package/dist/types/cache/types.d.ts +220 -44
- package/dist/types/cache/vercel/vercel-cache-store.d.ts +111 -13
- package/dist/types/client.d.ts +4 -4
- package/dist/types/client.rsc.d.ts +4 -5
- package/dist/types/components/DefaultDocument.d.ts +5 -2
- package/dist/types/context-var.d.ts +6 -5
- package/dist/types/escape-script.d.ts +1 -1
- package/dist/types/handles/MetaTags.d.ts +5 -4
- package/dist/types/handles/Scripts.d.ts +8 -7
- package/dist/types/handles/meta.d.ts +2 -2
- package/dist/types/handles/script.d.ts +17 -16
- package/dist/types/href-client.d.ts +1 -1
- package/dist/types/html.d.ts +12 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.rsc.d.ts +3 -2
- package/dist/types/loader-store.d.ts +7 -4
- package/dist/types/prerender/build-shell-capture.d.ts +11 -4
- package/dist/types/reverse.d.ts +36 -4
- package/dist/types/route-definition/dsl-helpers.d.ts +3 -3
- package/dist/types/route-definition/helpers-types.d.ts +33 -9
- package/dist/types/route-definition/resolve-handler-use.d.ts +23 -1
- package/dist/types/route-types.d.ts +1 -1
- package/dist/types/router/debug-manifest.d.ts +3 -2
- package/dist/types/router/error-handling.d.ts +20 -0
- package/dist/types/router/handler-context.d.ts +6 -0
- package/dist/types/router/instrument.d.ts +8 -0
- package/dist/types/router/match-middleware/background-revalidation.d.ts +46 -5
- package/dist/types/router/middleware-types.d.ts +9 -3
- package/dist/types/router/router-context.d.ts +3 -1
- package/dist/types/router/router-interfaces.d.ts +24 -2
- package/dist/types/router/router-options.d.ts +8 -5
- package/dist/types/router/segment-resolution/fresh.d.ts +10 -0
- package/dist/types/router/segment-resolution/loader-cache.d.ts +66 -1
- package/dist/types/router/segment-resolution/loader-mask.d.ts +9 -19
- package/dist/types/router/segment-resolution/loader-snapshot.d.ts +23 -12
- package/dist/types/router/segment-resolution/mask-nested.d.ts +67 -26
- package/dist/types/router/segment-resolution/revalidation.d.ts +6 -2
- package/dist/types/router/segment-wrappers.d.ts +1 -1
- package/dist/types/rsc/full-payload.d.ts +14 -0
- package/dist/types/rsc/handler.d.ts +1 -1
- package/dist/types/rsc/response-route-handler.d.ts +1 -1
- package/dist/types/rsc/rsc-rendering.d.ts +1 -2
- package/dist/types/rsc/shell-build-manifest.d.ts +8 -0
- package/dist/types/rsc/shell-capture-constants.d.ts +61 -6
- package/dist/types/rsc/shell-capture.d.ts +151 -19
- package/dist/types/rsc/shell-serve.d.ts +140 -17
- package/dist/types/rsc/ssr-setup.d.ts +6 -6
- package/dist/types/rsc/types.d.ts +17 -0
- package/dist/types/server/context.d.ts +139 -15
- package/dist/types/server/cookie-store.d.ts +21 -0
- package/dist/types/server/handle-store.d.ts +26 -2
- package/dist/types/server/request-context.d.ts +182 -53
- package/dist/types/ssr/index.d.ts +24 -0
- package/dist/types/ssr/ssr-root.d.ts +9 -8
- package/dist/types/static-handler.d.ts +4 -1
- package/dist/types/testing/flight.entry.d.ts +7 -0
- package/dist/types/testing/generated-routes.d.ts +2 -2
- package/dist/types/testing/internal/context.d.ts +1 -1
- package/dist/types/testing/render-route.d.ts +32 -6
- package/dist/types/testing/run-loader.d.ts +18 -3
- package/dist/types/testing/serve-shell-request.d.ts +132 -0
- package/dist/types/testing/shell-status.d.ts +23 -10
- package/dist/types/testing/vitest-stubs/plugin-rsc.d.ts +28 -5
- package/dist/types/testing/vitest.d.ts +35 -1
- package/dist/types/theme/ThemeScript.d.ts +3 -3
- package/dist/types/types/cache-types.d.ts +14 -10
- package/dist/types/types/handler-context.d.ts +28 -1
- package/dist/types/types/loader-types.d.ts +8 -6
- package/dist/types/urls/pattern-types.d.ts +22 -18
- package/dist/types/urls/response-types.d.ts +3 -12
- package/dist/types/use-loader.d.ts +12 -3
- package/dist/types/vite/plugins/use-cache-transform.d.ts +33 -2
- package/dist/types/vite/rango.d.ts +1 -1
- package/dist/types/vite/router-discovery.d.ts +29 -2
- package/dist/vite/index.js +1420 -1348
- package/package.json +2 -2
- package/skills/api-client/SKILL.md +22 -10
- package/skills/breadcrumbs/SKILL.md +75 -22
- package/skills/bundle-analysis/SKILL.md +22 -22
- package/skills/cache-guide/SKILL.md +306 -124
- package/skills/caching/SKILL.md +530 -98
- package/skills/catalog.json +16 -16
- package/skills/client-urls/SKILL.md +65 -38
- package/skills/cloudflare/SKILL.md +87 -20
- package/skills/cloudflare/references/d1-and-local-dev.md +15 -2
- package/skills/cloudflare/references/streaming-and-deploy.md +10 -3
- package/skills/cloudflare/references/webhooks-and-crypto.md +9 -2
- package/skills/comparison/SKILL.md +5 -0
- package/skills/comparison/references/framework-comparison.md +75 -57
- package/skills/composability/SKILL.md +38 -29
- package/skills/css/SKILL.md +10 -13
- package/skills/debug-manifest/SKILL.md +74 -31
- package/skills/defer-hydration/SKILL.md +52 -31
- package/skills/deployment-caching/SKILL.md +11 -3
- package/skills/document-cache/SKILL.md +98 -28
- package/skills/fonts/SKILL.md +16 -8
- package/skills/handler-use/SKILL.md +55 -17
- package/skills/hooks/SKILL.md +28 -20
- package/skills/hooks/data.md +23 -3
- package/skills/hooks/handle-and-actions.md +34 -17
- package/skills/hooks/navigation.md +39 -10
- package/skills/hooks/outlets.md +13 -6
- package/skills/hooks/state.md +39 -15
- package/skills/hooks/urls.md +7 -5
- package/skills/host-router/SKILL.md +64 -12
- package/skills/i18n/SKILL.md +60 -34
- package/skills/intercept/SKILL.md +161 -26
- package/skills/layout/SKILL.md +106 -50
- package/skills/links/SKILL.md +51 -19
- package/skills/loader/SKILL.md +386 -128
- package/skills/middleware/SKILL.md +129 -26
- package/skills/migrate-nextjs/SKILL.md +156 -70
- package/skills/migrate-nextjs/backend-host-swap.md +5 -2
- package/skills/migrate-react-router/SKILL.md +51 -39
- package/skills/migrate-react-router/component-migration.md +28 -7
- package/skills/migrate-react-router/data-and-actions.md +21 -6
- package/skills/migrate-react-router/route-mapping.md +19 -10
- package/skills/mime-routes/SKILL.md +12 -7
- package/skills/observability/SKILL.md +92 -14
- package/skills/parallel/SKILL.md +88 -47
- package/skills/ppr/SKILL.md +866 -379
- package/skills/prerender/SKILL.md +210 -108
- package/skills/rango/SKILL.md +115 -65
- package/skills/react-compiler/SKILL.md +6 -5
- package/skills/response-routes/SKILL.md +133 -25
- package/skills/route/SKILL.md +170 -60
- package/skills/router-setup/SKILL.md +420 -240
- package/skills/scripts/SKILL.md +39 -21
- package/skills/server-actions/SKILL.md +231 -59
- package/skills/shell-manifest/SKILL.md +28 -14
- package/skills/streams-and-websockets/SKILL.md +32 -14
- package/skills/tailwind/SKILL.md +37 -33
- package/skills/testing/SKILL.md +73 -33
- package/skills/testing/cache-prerender.md +60 -33
- package/skills/testing/client-components.md +59 -6
- package/skills/testing/e2e-parity.md +12 -2
- package/skills/testing/flight.md +19 -11
- package/skills/testing/handles.md +8 -5
- package/skills/testing/loader.md +26 -6
- package/skills/testing/middleware.md +8 -3
- package/skills/testing/predicates.md +120 -0
- package/skills/testing/render-handler.md +70 -22
- package/skills/testing/response-routes.md +11 -4
- package/skills/testing/reverse-and-types.md +20 -14
- package/skills/testing/serve-shell-request.md +188 -0
- package/skills/testing/server-actions.md +12 -3
- package/skills/testing/server-tree.md +9 -0
- package/skills/testing/setup.md +9 -5
- package/skills/theme/SKILL.md +155 -30
- package/skills/typesafety/SKILL.md +20 -9
- package/skills/typesafety/env-and-bindings.md +137 -50
- package/skills/typesafety/generated-files-and-cli.md +26 -14
- package/skills/typesafety/params-and-search.md +44 -19
- package/skills/typesafety/route-types.md +50 -14
- package/skills/use-cache/SKILL.md +224 -83
- package/skills/vercel/SKILL.md +35 -13
- package/skills/view-transitions/SKILL.md +42 -21
- package/src/__augment-tests__/augmented.check.ts +26 -1
- package/src/browser/cookie-name.ts +23 -0
- package/src/browser/history-state.ts +23 -0
- package/src/browser/index.ts +2 -2
- package/src/browser/navigation-bridge.ts +3 -4
- package/src/browser/navigation-transaction.ts +22 -2
- package/src/browser/partial-update.ts +35 -25
- package/src/browser/react/ScrollRestoration.tsx +6 -6
- package/src/browser/rsc-router.tsx +6 -1
- package/src/browser/scroll-restoration.ts +53 -6
- package/src/cache/cache-exec-scope.ts +37 -4
- package/src/cache/cache-key-utils.ts +23 -8
- package/src/cache/cache-policy.ts +22 -0
- package/src/cache/cache-runtime.ts +264 -68
- package/src/cache/cache-scope.ts +419 -43
- package/src/cache/cache-tag.ts +375 -11
- package/src/cache/cf/cf-base64.ts +36 -4
- package/src/cache/cf/cf-cache-constants.ts +12 -0
- package/src/cache/cf/cf-cache-store.ts +990 -348
- package/src/cache/cf/cf-cache-types.ts +32 -4
- package/src/cache/cf/cf-shell-frame.ts +258 -0
- package/src/cache/cf/cf-tag-marker-memo.ts +143 -39
- package/src/cache/document-cache.ts +15 -0
- package/src/cache/handle-capture.ts +90 -10
- package/src/cache/handle-snapshot.ts +133 -28
- package/src/cache/index.ts +2 -0
- package/src/cache/isolate-tag-memo.ts +308 -0
- package/src/cache/segment-codec.ts +26 -7
- package/src/cache/shell-memo.ts +341 -0
- package/src/cache/shell-snapshot.ts +217 -27
- package/src/cache/tag-invalidation.ts +108 -22
- package/src/cache/taint.ts +1 -1
- package/src/cache/types.ts +225 -44
- package/src/cache/vercel/vercel-cache-store.ts +545 -61
- package/src/client.rsc.tsx +10 -9
- package/src/client.tsx +5 -4
- package/src/components/DefaultDocument.tsx +7 -2
- package/src/context-var.ts +6 -5
- package/src/escape-script.ts +1 -1
- package/src/handles/MetaTags.tsx +13 -11
- package/src/handles/Scripts.tsx +11 -10
- package/src/handles/meta.ts +2 -2
- package/src/handles/script.ts +17 -16
- package/src/href-client.ts +1 -1
- package/src/html.ts +12 -0
- package/src/index.rsc.ts +5 -0
- package/src/index.ts +1 -0
- package/src/loader-store.ts +7 -4
- package/src/prerender/build-shell-capture.ts +73 -22
- package/src/reverse.ts +44 -4
- package/src/route-definition/dsl-helpers.ts +121 -33
- package/src/route-definition/helpers-types.ts +33 -9
- package/src/route-definition/resolve-handler-use.ts +121 -6
- package/src/route-types.ts +0 -3
- package/src/router/debug-manifest.ts +10 -2
- package/src/router/error-handling.ts +51 -15
- package/src/router/handler-context.ts +71 -28
- package/src/router/instrument.ts +54 -2
- package/src/router/intercept-resolution.ts +141 -43
- package/src/router/loader-resolution.ts +115 -39
- package/src/router/match-api.ts +40 -26
- package/src/router/match-handlers.ts +5 -0
- package/src/router/match-middleware/background-revalidation.ts +161 -96
- package/src/router/match-middleware/cache-lookup.ts +110 -10
- package/src/router/match-middleware/cache-store.ts +48 -101
- package/src/router/middleware-types.ts +9 -6
- package/src/router/middleware.ts +75 -13
- package/src/router/prerender-match.ts +59 -19
- package/src/router/route-snapshot.ts +11 -1
- package/src/router/router-context.ts +2 -0
- package/src/router/router-interfaces.ts +25 -2
- package/src/router/router-options.ts +8 -5
- package/src/router/segment-resolution/fresh.ts +62 -21
- package/src/router/segment-resolution/helpers.ts +4 -2
- package/src/router/segment-resolution/loader-cache.ts +407 -76
- package/src/router/segment-resolution/loader-mask.ts +12 -22
- package/src/router/segment-resolution/loader-snapshot.ts +208 -56
- package/src/router/segment-resolution/mask-nested.ts +138 -44
- package/src/router/segment-resolution/revalidation.ts +61 -16
- package/src/router/segment-wrappers.ts +4 -0
- package/src/rsc/full-payload.ts +25 -1
- package/src/rsc/handler.ts +14 -2
- package/src/rsc/progressive-enhancement.ts +3 -2
- package/src/rsc/render-pipeline.ts +3 -0
- package/src/rsc/response-cache-serve.ts +9 -6
- package/src/rsc/response-route-handler.ts +11 -3
- package/src/rsc/rsc-rendering.ts +608 -225
- package/src/rsc/shell-build-manifest.ts +27 -9
- package/src/rsc/shell-capture-constants.ts +73 -6
- package/src/rsc/shell-capture.ts +1068 -296
- package/src/rsc/shell-serve.ts +316 -34
- package/src/rsc/ssr-setup.ts +14 -8
- package/src/rsc/types.ts +18 -0
- package/src/server/context.ts +300 -32
- package/src/server/cookie-store.ts +87 -61
- package/src/server/handle-store.ts +232 -7
- package/src/server/request-context.ts +297 -67
- package/src/ssr/index.tsx +136 -44
- package/src/ssr/ssr-root.tsx +9 -8
- package/src/static-handler.ts +4 -1
- package/src/testing/flight-runtime.d.ts +21 -0
- package/src/testing/flight.entry.ts +14 -0
- package/src/testing/generated-routes.ts +2 -2
- package/src/testing/internal/context.ts +1 -1
- package/src/testing/internal/flight-client-globals.ts +7 -5
- package/src/testing/render-route.tsx +85 -24
- package/src/testing/run-loader.ts +68 -6
- package/src/testing/serve-shell-request.ts +378 -0
- package/src/testing/shell-status.ts +34 -12
- package/src/testing/vitest-stubs/plugin-rsc.ts +123 -20
- package/src/testing/vitest.ts +62 -1
- package/src/theme/ThemeProvider.tsx +7 -6
- package/src/theme/ThemeScript.tsx +3 -3
- package/src/types/cache-types.ts +14 -10
- package/src/types/handler-context.ts +28 -1
- package/src/types/loader-types.ts +8 -6
- package/src/urls/pattern-types.ts +22 -18
- package/src/urls/response-types.ts +3 -23
- package/src/use-loader.tsx +12 -3
- package/src/vite/discovery/shell-prerender-phase.ts +9 -0
- package/src/vite/plugins/use-cache-transform.ts +118 -74
- package/src/vite/rango.ts +7 -3
- package/src/vite/router-discovery.ts +14 -4
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
| `cacheStore` | `SegmentCacheStore` | Backing store for `use cache` functions (same shape as `createRouter({ cache })`). Without it, cached functions run uncached and their guards never fire. |
|
|
23
23
|
| `cacheProfiles` | `Record<string, CacheProfile>` | Profiles for `use cache: "name"`, same shape as `createRouter({ cacheProfiles })`. An unknown profile throws. |
|
|
24
24
|
| `theme` | `ThemeConfig \| true` | Theme config (same shape as `createRouter({ theme })`). Without it `ctx.theme` / `ctx.setTheme` are inert. |
|
|
25
|
+
| `build` | `boolean` | Seed `ctx.build` (default `false`) for code that branches on the build-time PPR shell-capture pass. With `true`, `ctx.waitUntil()` is inert, as at build time. |
|
|
25
26
|
| `stateCookie` | `StateCookieSeed` (`{ prefix?, routerId?, version? }`) | Customize the rango state cookie an action calling `invalidateClientCache()` rotates. The name is ALWAYS seeded (default `rango-state_router_0`) so the rotation `Set-Cookie` fires like production; override `prefix`/`routerId` to match `createRouter({ stateCookiePrefix, id })`, or `version` (value is `{version}:{timestamp}`, default `"0"`). |
|
|
26
27
|
|
|
27
28
|
### Context — `RequestContext<TEnv>` (what your code receives)
|
|
@@ -52,7 +53,12 @@
|
|
|
52
53
|
| `stateCookieName` | `string` | The resolved rango state cookie name this run seeded (default `rango-state_router_0`). Assert an `invalidateClientCache()` rotation against it without recomputing. |
|
|
53
54
|
| `locationState` | `Record<string, unknown>` | The flash set via `ctx.setLocationState()` / `redirect({ state })`, as the flat `{ key: value }` the client reads. |
|
|
54
55
|
|
|
55
|
-
Low-level
|
|
56
|
+
Low-level building blocks (all from `@rangojs/router/testing`):
|
|
57
|
+
|
|
58
|
+
- `createTestRequestContext(opts)` builds the context WITHOUT entering it and returns `{ ctx, request, url, variables, stateCookieName }`. Its `ctx.reverse` accepts any name from your `routeMap`.
|
|
59
|
+
- `runWithRequestContext(ctx, fn)` enters a context you already hold. `runInRequestContext` is the one-call convenience over these two, plus the result snapshot.
|
|
60
|
+
- `toRequest(request?, init?)` normalizes a `Request | string | undefined` into a `Request` (strings resolve against `http://localhost/`).
|
|
61
|
+
- `seedVariables(variables, vars?)` writes a `VarsInit` (object or `[key, value]` tuples) into a raw variables record, the same way the `vars` option does.
|
|
56
62
|
|
|
57
63
|
## Recipe
|
|
58
64
|
|
|
@@ -60,10 +66,13 @@ Low-level variant: when you already hold a context from `createTestRequestContex
|
|
|
60
66
|
import { it, expect } from "vitest";
|
|
61
67
|
import { runInRequestContext } from "@rangojs/router/testing";
|
|
62
68
|
import { loginAction } from "../src/actions/login"; // sets a session cookie + flash, then throw redirect("/app")
|
|
69
|
+
import { logoutAction, dismissBannerAction } from "../src/actions/session"; // invalidateClientCache() / keepClientCache()
|
|
70
|
+
|
|
71
|
+
const env = { DB: fakeDb }; // your binding double (see ./bindings.md)
|
|
63
72
|
|
|
64
73
|
it("sets the session cookie + flash and redirects", async () => {
|
|
65
74
|
const { thrown, cookies, locationState } = await runInRequestContext(
|
|
66
|
-
() => loginAction(
|
|
75
|
+
() => loginAction({ email: "ada@example.com", password: "pw" }),
|
|
67
76
|
{
|
|
68
77
|
env,
|
|
69
78
|
request: new Request("https://app.test/admin", {
|
|
@@ -97,7 +106,7 @@ it("asserts the client-cache directives an action issued", async () => {
|
|
|
97
106
|
|
|
98
107
|
- The snapshot fires whether `fn` RETURNS or THROWS. A `throw redirect("/app")` on the success path is captured on `thrown` (NOT re-thrown), so no try/catch is needed; assert on `thrown` for a throwing action.
|
|
99
108
|
- There is no cookies / headers option. Seed a request cookie by passing a full `Request` with the `Cookie` header (as in the recipe).
|
|
100
|
-
-
|
|
109
|
+
- An action has no loader context, so `runLoader` is the wrong shape for it even when the action reads cookies or vars; use `runInRequestContext`.
|
|
101
110
|
- Platform bindings are yours to double via `env` (see `./bindings.md`).
|
|
102
111
|
|
|
103
112
|
## See also
|
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
| `vars` | `VarsInit` | Context variables visible via `ctx.get(...)`, as a prior middleware would have set them. Object form (`{ user }`) or `[key, value]` tuples. |
|
|
19
19
|
| `clientComponents` | `Record<string, unknown>` | The `"use client"` components reachable from the tree, keyed by the boundary name to register each as a client reference (in place) so it serializes as an `I` row. Omit when `rangoUseClientTransform()` auto-discovers them, or for pure server-only trees. First-wins per worker; already-registered references are left untouched. |
|
|
20
20
|
|
|
21
|
+
It also accepts the rest of `RenderToFlightStringOptions` — `routeMap`, `theme`, `cacheStore`, `cacheProfiles` — with the same meaning as in [`./flight.md`](./flight.md).
|
|
22
|
+
|
|
21
23
|
### Context — what your code receives
|
|
22
24
|
|
|
23
25
|
A server component rendered here runs under a real request context: `getRequestContext()` resolves, `ctx.params`/`ctx.routeName`/`ctx.env` reflect the options, `ctx.get(MyVar)` reads a seeded `var`, and cookies come off the request. Same seeding as the handler-test primitives — you render an **element** you build (`<Page />`); to run a route **handler** `(ctx) => rsc` use `renderHandler` (see `./render-handler.md`).
|
|
@@ -64,6 +66,10 @@ Every SERVER/HOST element a server component produced (`<article>`, `<h2>`), in
|
|
|
64
66
|
|
|
65
67
|
Concatenates every string/number leaf of a node's subtree in document order — the clean way to assert rendered text, instead of `JSON.stringify(tree).toContain(...)`.
|
|
66
68
|
|
|
69
|
+
#### `assertFlightTreeRuntimeAvailable()`
|
|
70
|
+
|
|
71
|
+
Throws a descriptive error when a `@vitejs/plugin-rsc` subpath the round trip relies on (the vendored client deserializer or `registerClientReference`) no longer exports the expected function — the counterpart of `assertFlightRuntimeAvailable()` in `./flight.md`. Call it in a `beforeAll` to fail fast after a plugin-rsc upgrade.
|
|
72
|
+
|
|
67
73
|
## Recipe
|
|
68
74
|
|
|
69
75
|
```tsx
|
|
@@ -74,8 +80,11 @@ import {
|
|
|
74
80
|
findElements,
|
|
75
81
|
textContent,
|
|
76
82
|
} from "@rangojs/router/testing/flight";
|
|
83
|
+
import { flightMatchers } from "@rangojs/router/testing/flight-matchers";
|
|
77
84
|
import { PriceTag } from "./PriceTag.js"; // a "use client" component (any filename)
|
|
78
85
|
|
|
86
|
+
expect.extend(flightMatchers); // for toMatchFlight below
|
|
87
|
+
|
|
79
88
|
async function ProductPanel({ amount, asOf }: { amount: number; asOf: Date }) {
|
|
80
89
|
await Promise.resolve();
|
|
81
90
|
return (
|
package/skills/testing/setup.md
CHANGED
|
@@ -12,13 +12,15 @@ Real machinery: Vite transpiles `@rangojs/router`'s shipped TS source and resolv
|
|
|
12
12
|
| -------- | ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
13
13
|
| `preset` | `"node" \| "cloudflare"` | Deployment preset, matching `rango({ preset })` in the Vite plugin. `"cloudflare"` additionally stubs the `cloudflare:workers` / `cloudflare:email` runtime virtuals a CF route tree imports. A string (not a boolean) so more presets can be added without an API change. Default `"node"`. |
|
|
14
14
|
|
|
15
|
-
###
|
|
15
|
+
### Exports
|
|
16
16
|
|
|
17
|
-
|
|
|
17
|
+
| Export | Returns / value | Use |
|
|
18
18
|
| --------------------------- | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
19
19
|
| `rangoTestConfig(opts?)` | `{ alias, server: { deps: { inline } } }` | Recommended. Spread into the node/DOM project's `test` block. Bundles the resolve aliases AND `server.deps.inline`. |
|
|
20
20
|
| `rangoTestAliases(opts?)` | `TestAlias[]` (`{ find, replacement }[]`) | Lower-level. The bare `@rangojs/router` -> `index.rsc.ts` alias plus the `:version` / `@vitejs/plugin-rsc/rsc` (`/rsc/server`, `/rsc/client`) stubs (and CF stubs under `preset:"cloudflare"`). Used in the rsc project's `resolve.alias`. |
|
|
21
|
-
| `
|
|
21
|
+
| `rangoInlineDeps` | `RegExp[]` (`[/@rangojs[/\\]router/]`) | The `server.deps.inline` patterns on their own. Use in the rsc project (`test.server.deps.inline`), or wherever you call `rangoTestAliases` directly instead of `rangoTestConfig`. |
|
|
22
|
+
| `rangoUseClientTransform()` | a Vite plugin (`{ name, transform }`) | Add to the rsc project `plugins`. Applies the `"use client"` transform so `renderServerTree` / `renderHandler` auto-discover client islands from the server tree's imports. |
|
|
23
|
+
| `rangoUseCacheTransform()` | a Vite plugin (`enforce: "post"`) | Add to the rsc project `plugins`. Runs the rango plugin's `"use cache"` transform with dev ids (`<path>#<name>`, the path relative to the root its `configResolved` reads) so a directive function caches. |
|
|
22
24
|
|
|
23
25
|
### Returns — `RangoTestConfig` (from `rangoTestConfig`)
|
|
24
26
|
|
|
@@ -53,6 +55,7 @@ import { defineConfig } from "vitest/config";
|
|
|
53
55
|
import {
|
|
54
56
|
rangoTestAliases,
|
|
55
57
|
rangoUseClientTransform,
|
|
58
|
+
rangoUseCacheTransform,
|
|
56
59
|
rangoInlineDeps,
|
|
57
60
|
} from "@rangojs/router/testing/vitest";
|
|
58
61
|
|
|
@@ -60,7 +63,7 @@ import {
|
|
|
60
63
|
process.env.NODE_ENV = "production";
|
|
61
64
|
|
|
62
65
|
export default defineConfig({
|
|
63
|
-
plugins: [rangoUseClientTransform()],
|
|
66
|
+
plugins: [rangoUseClientTransform(), rangoUseCacheTransform()],
|
|
64
67
|
resolve: {
|
|
65
68
|
conditions: ["react-server"],
|
|
66
69
|
alias: rangoTestAliases({ preset: "cloudflare" }), // or { preset: "node" }
|
|
@@ -109,12 +112,13 @@ Scripts:
|
|
|
109
112
|
|
|
110
113
|
- Node >= 23 requires `rangoTestConfig()`, not bare `rangoTestAliases()`. `@rangojs/router` is consumed as SOURCE (exports -> `./src/*.ts`), and Node >= 23 refuses to type-strip `.ts` under `node_modules` (`ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING`). `rangoTestConfig` ships as compiled JS AND adds `server.deps.inline: [/@rangojs[/\\]router/]` so Vite (not Node) transpiles rango source. With bare `rangoTestAliases` you must wire `deps.inline` yourself.
|
|
111
114
|
- Two separate projects. The node/DOM project keeps React on its CLIENT build; the Flight project uses the `react-server` condition in a separate `vitest.rsc.config.ts`. The main project must NOT set `react-server` — it flips React to the no-hooks server build and breaks every `renderRoute` / client test.
|
|
112
|
-
- The rsc project needs BOTH `resolve.conditions: ["react-server"]` AND the bare `@rangojs/router` -> `index.rsc.ts` alias from `rangoTestAliases({ preset })`. `resolve.conditions` alone is not reliably applied to bare-package export resolution; without the alias a handler/component reading `getRequestContext()` / `cookies()` resolves the throwing out-of-react-server stub
|
|
115
|
+
- The rsc project needs BOTH `resolve.conditions: ["react-server"]` AND the bare `@rangojs/router` -> `index.rsc.ts` alias from `rangoTestAliases({ preset })`. `resolve.conditions` alone is not reliably applied to bare-package export resolution; without the alias a handler/component reading `getRequestContext()` / `cookies()` resolves the throwing out-of-react-server stub. `renderToFlightString`, `renderServerTree`, and `renderHandler` all detect this exact misconfiguration and reject with an actionable message naming `rangoTestAliases`, rather than surfacing the opaque stub error.
|
|
113
116
|
- `NODE_ENV` must be `"production"` in the rsc project. Dev `NODE_ENV` crashes the bare worker (jsxDEV owner-stack machinery uninitialized) and emits volatile debug rows that defeat stable Flight snapshots.
|
|
114
117
|
- The forked rsc worker (`pool: "forks"`) must force the condition via `execArgv: ["--conditions=react-server"]`, or React throws "the react-server condition must be enabled".
|
|
115
118
|
- The `@rangojs/router:version` and `@vitejs/plugin-rsc/rsc` (`/rsc/server`, `/rsc/client`) virtuals must be stubbed; the preset does it. A bare router import without stubbing throws.
|
|
116
119
|
- The rango fragment goes under `test` (`test.alias` + `test.server.deps.inline`, both returned by `rangoTestConfig`), NOT under top-level `resolve`.
|
|
117
120
|
- Wire `rangoUseClientTransform()` into the rsc project `plugins` so islands auto-discover from the server tree imports (see `./server-tree.md`); without it, register islands explicitly with `clientComponents`. The transform's `registerClientReference` import is resolved from `@rangojs/router`'s `@vitejs/plugin-rsc` — the consumer does not need a direct plugin-rsc dependency.
|
|
121
|
+
- Wire `rangoUseCacheTransform()` into the rsc project `plugins` so a function written with `"use cache"` is wrapped as in dev. `rango()` runs that transform only in its `rsc` environment and Vitest transforms in `ssr`, so without it the directive is inert and the function runs on every call. See [`./render-handler.md`](./render-handler.md#a-use-cache-hit).
|
|
118
122
|
|
|
119
123
|
## See also
|
|
120
124
|
|
package/skills/theme/SKILL.md
CHANGED
|
@@ -1,82 +1,207 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: theme
|
|
3
|
-
description: Opt-in theme system with FOUC prevention
|
|
3
|
+
description: Opt-in light/dark theme system with FOUC prevention, server-side theme access, and a useTheme() hook. Use when adding a light/dark mode toggle, reading or setting the theme in handlers or middleware, or when the page flashes the wrong theme on load (FOUC).
|
|
4
4
|
argument-hint: [setup]
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Theme Support
|
|
8
8
|
|
|
9
|
-
Opt-in theme system
|
|
9
|
+
Opt-in theme system. Enabling it gives you:
|
|
10
|
+
|
|
11
|
+
- an inline script (rendered by `<Html.Meta />`) that applies the stored theme
|
|
12
|
+
to `<html>` before first paint, so there is no flash of the wrong theme;
|
|
13
|
+
- `ctx.theme` / `ctx.setTheme()` in handlers and middleware;
|
|
14
|
+
- the `useTheme()` hook in client components (the provider is added for you).
|
|
15
|
+
|
|
16
|
+
The theme is stored in a cookie (so the server can read it) and in
|
|
17
|
+
localStorage.
|
|
10
18
|
|
|
11
19
|
## Enable
|
|
12
20
|
|
|
13
21
|
```typescript
|
|
14
22
|
import { createRouter } from "@rangojs/router";
|
|
15
23
|
|
|
16
|
-
//
|
|
17
|
-
const router = createRouter<
|
|
24
|
+
// All defaults
|
|
25
|
+
export const router = createRouter<AppBindings>({
|
|
18
26
|
document: Document,
|
|
19
27
|
urls: urlpatterns,
|
|
20
28
|
theme: true,
|
|
21
29
|
});
|
|
22
30
|
|
|
23
|
-
// Custom config
|
|
24
|
-
const router = createRouter<
|
|
31
|
+
// Custom config (values shown are the defaults unless noted)
|
|
32
|
+
export const router = createRouter<AppBindings>({
|
|
25
33
|
document: Document,
|
|
26
34
|
urls: urlpatterns,
|
|
27
35
|
theme: {
|
|
28
36
|
defaultTheme: "system", // "light" | "dark" | "system"
|
|
29
37
|
themes: ["light", "dark"],
|
|
30
|
-
attribute: "class", // or "data-theme"
|
|
31
|
-
storageKey: "theme",
|
|
38
|
+
attribute: "class", // or any "data-*" attribute, e.g. "data-theme"
|
|
39
|
+
storageKey: "theme", // cookie and localStorage key
|
|
40
|
+
enableSystem: true, // resolve "system" via prefers-color-scheme
|
|
41
|
+
enableColorScheme: true, // set style="color-scheme: ..." on <html>
|
|
42
|
+
value: { dark: "theme-dark" }, // optional: attribute value per theme (not a default)
|
|
32
43
|
},
|
|
33
44
|
});
|
|
34
45
|
```
|
|
35
46
|
|
|
36
|
-
|
|
47
|
+
With `attribute: "class"` the resolved theme is added as a class on `<html>`
|
|
48
|
+
(`<html class="dark">`); with a `data-*` attribute it is set as that
|
|
49
|
+
attribute's value (`<html data-theme="dark">`). `"system"` resolves to
|
|
50
|
+
`"light"` or `"dark"` before it is applied. With `enableSystem: false`,
|
|
51
|
+
`"system"` is not a valid theme and a `"system"` default falls back to the
|
|
52
|
+
first entry in `themes`.
|
|
53
|
+
|
|
54
|
+
## Document requirements
|
|
55
|
+
|
|
56
|
+
- Render `<Html.Meta />` in `<head>`: it emits the FOUC-prevention script
|
|
57
|
+
first, before the meta tags. The default Document already does this.
|
|
58
|
+
- Add `suppressHydrationWarning` to `<html>`: the script changes its
|
|
59
|
+
`class`/`style` before React hydrates.
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
"use client";
|
|
63
|
+
import type { ReactNode } from "react";
|
|
64
|
+
import { Html } from "@rangojs/router/client";
|
|
65
|
+
|
|
66
|
+
export function Document({ children }: { children: ReactNode }) {
|
|
67
|
+
return (
|
|
68
|
+
<html lang="en" suppressHydrationWarning>
|
|
69
|
+
<head>
|
|
70
|
+
<Html.Meta />
|
|
71
|
+
</head>
|
|
72
|
+
<body>{children}</body>
|
|
73
|
+
</html>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
A Document that does not render `<Html.Meta />` can place `<ThemeScript />`
|
|
79
|
+
from `@rangojs/router/theme` in `<head>` instead. It takes the resolved config
|
|
80
|
+
(`config: ResolvedThemeConfig`) and an optional `nonce`.
|
|
81
|
+
|
|
82
|
+
## Server (handlers and middleware)
|
|
83
|
+
|
|
84
|
+
`ctx.theme` is the stored theme, or `defaultTheme` when there is none.
|
|
85
|
+
`ctx.setTheme(theme)` sets the theme cookie on the response; invalid values
|
|
86
|
+
are rejected with a console warning. Both are typed optional because they only
|
|
87
|
+
exist when `theme` is configured.
|
|
88
|
+
|
|
89
|
+
`ctx.theme` is the visitor's theme cookie, so a handler reads it only on a
|
|
90
|
+
route whose output is rendered per request:
|
|
37
91
|
|
|
38
92
|
```typescript
|
|
39
93
|
import type { Middleware } from "@rangojs/router";
|
|
40
94
|
|
|
41
|
-
// In a handler
|
|
95
|
+
// In a handler on a route without ppr or cache()
|
|
42
96
|
path("/settings", (ctx) => {
|
|
43
|
-
const currentTheme = ctx.theme; //
|
|
97
|
+
const currentTheme = ctx.theme; // "light" | "dark" | "system" | undefined
|
|
44
98
|
return <SettingsPage theme={currentTheme} />;
|
|
45
99
|
});
|
|
46
100
|
|
|
47
|
-
// In middleware
|
|
48
|
-
export const
|
|
49
|
-
|
|
50
|
-
|
|
101
|
+
// In middleware: apply a theme chosen through a query param
|
|
102
|
+
export const themeFromQuery: Middleware = async (ctx, next) => {
|
|
103
|
+
const requested = ctx.url.searchParams.get("theme");
|
|
104
|
+
if (requested === "light" || requested === "dark") {
|
|
105
|
+
ctx.setTheme?.(requested);
|
|
106
|
+
}
|
|
51
107
|
await next();
|
|
52
108
|
};
|
|
53
109
|
```
|
|
54
110
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
111
|
+
### On `ppr` and `cache()` routes
|
|
112
|
+
|
|
113
|
+
A `ppr` shell and a `cache()` entry are shared by every visitor, so a handler
|
|
114
|
+
there must not read the visitor's theme. `ctx.theme` (handler and middleware)
|
|
115
|
+
and `getRequestContext().theme` are guarded like `cookies()`:
|
|
116
|
+
|
|
117
|
+
| Where the theme is read | What happens |
|
|
118
|
+
| ---------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
|
|
119
|
+
| a handler on a `ppr` route | The shell capture is refused (warned once per URL); every request stays a MISS |
|
|
120
|
+
| inside a `cache()` boundary | Throws on a cache miss, like `cookies()` |
|
|
121
|
+
| inside a `"use cache"` function (handler or middleware) | Throws; read it outside and pass it in as an argument |
|
|
122
|
+
| after `ctx.dynamic()` on `ppr` | Allowed: that render is never captured |
|
|
123
|
+
| middleware `ctx.set()` of the theme, read by a handler | Not guarded: the first visitor's theme bakes into the shell or `cache()` entry, like a session object |
|
|
124
|
+
| a route with no `ppr` or `cache()` above it, or middleware | Allowed: rendered per request |
|
|
125
|
+
| a loader with its own `cache()` and no `key()` | The miss fails and stores nothing (#972), like a `cookies()` read there; key the theme |
|
|
126
|
+
|
|
127
|
+
The theme getters are read-only and non-enumerable: `{ ...ctx }` and
|
|
128
|
+
`Object.assign({}, ctx)` do not carry `theme`, and assigning it throws.
|
|
129
|
+
|
|
130
|
+
`ctx.setTheme()` throws in a handler on these routes too, like any response
|
|
131
|
+
write there; call it from middleware.
|
|
132
|
+
|
|
133
|
+
Read the theme where it is per request instead:
|
|
134
|
+
|
|
135
|
+
- `useTheme()` in a client component. The `<html>` class is right before
|
|
136
|
+
paint (the theme script sets it). On a `ppr` HIT the shell carries the
|
|
137
|
+
no-cookie default (`defaultTheme`), whoever captured it: a visitor with no
|
|
138
|
+
stored theme sees the default, and one with a stored theme sees it after
|
|
139
|
+
hydration.
|
|
140
|
+
- A live loader (no `ssr: false`). Loaders do not get `ctx.theme` or
|
|
141
|
+
`ctx.setTheme`; read the cookie directly. A live loader runs on every
|
|
142
|
+
request, HITs included, and on a `ppr` route needs `loading()` or an inline
|
|
143
|
+
`<Suspense>` above its reader. A bake-lane loader (`ssr: false`) runs at
|
|
144
|
+
capture, where this `cookies()` read refuses the capture, and so does a
|
|
145
|
+
loader a handler awaits with `ctx.use()`.
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
import { cookies, createLoader } from "@rangojs/router";
|
|
149
|
+
|
|
150
|
+
export const ThemedLoader = createLoader(async () => {
|
|
151
|
+
const theme = cookies().get("theme")?.value ?? "system"; // storageKey
|
|
152
|
+
return loadThemedAssets(theme);
|
|
153
|
+
});
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
If you bind it with its own `cache()`, the entry is shared across users, so the
|
|
157
|
+
miss fails unless it has a `key()`. Any `key()` switches that check off, so
|
|
158
|
+
make it include the theme:
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
loader(ThemedLoader, () => [
|
|
162
|
+
cache({
|
|
163
|
+
ttl: 300,
|
|
164
|
+
key: () => `themed:${cookies().get("theme")?.value ?? "system"}`,
|
|
165
|
+
}),
|
|
166
|
+
]);
|
|
167
|
+
```
|
|
58
168
|
|
|
59
169
|
## Client
|
|
60
170
|
|
|
61
171
|
```tsx
|
|
62
172
|
"use client";
|
|
63
|
-
import { useTheme } from "@rangojs/router/theme";
|
|
173
|
+
import { useTheme, type Theme } from "@rangojs/router/theme";
|
|
64
174
|
|
|
65
|
-
function ThemeToggle() {
|
|
66
|
-
const { theme, setTheme, resolvedTheme,
|
|
175
|
+
export function ThemeToggle() {
|
|
176
|
+
const { theme, setTheme, resolvedTheme, themes } = useTheme();
|
|
67
177
|
|
|
68
178
|
return (
|
|
69
|
-
<
|
|
70
|
-
|
|
71
|
-
<
|
|
72
|
-
|
|
73
|
-
|
|
179
|
+
<label>
|
|
180
|
+
Theme ({resolvedTheme})
|
|
181
|
+
<select value={theme} onChange={(e) => setTheme(e.target.value as Theme)}>
|
|
182
|
+
{themes.map((t) => (
|
|
183
|
+
<option key={t} value={t}>
|
|
184
|
+
{t}
|
|
185
|
+
</option>
|
|
186
|
+
))}
|
|
187
|
+
</select>
|
|
188
|
+
</label>
|
|
74
189
|
);
|
|
75
190
|
}
|
|
76
191
|
```
|
|
77
192
|
|
|
78
|
-
|
|
193
|
+
`useTheme()` returns:
|
|
194
|
+
|
|
195
|
+
- `theme`: the stored setting (`"light" | "dark" | "system"`)
|
|
196
|
+
- `setTheme(theme)`: update the theme; writes the cookie and localStorage
|
|
197
|
+
- `resolvedTheme`: what is actually shown (`"system"` resolved)
|
|
198
|
+
- `systemTheme`: the current OS preference (`"light" | "dark"`)
|
|
199
|
+
- `themes`: the configured themes, with `"system"` first when `enableSystem` is on
|
|
200
|
+
|
|
201
|
+
`useTheme()` throws when `theme` is not enabled in `createRouter()`.
|
|
202
|
+
|
|
203
|
+
## Related
|
|
79
204
|
|
|
80
|
-
-
|
|
81
|
-
-
|
|
82
|
-
-
|
|
205
|
+
- `/tailwind`: pairing `attribute: "class"` with Tailwind's `dark:` variant
|
|
206
|
+
- `/css`: where document stylesheets go
|
|
207
|
+
- `/router-setup`: the Document component
|
|
@@ -1,17 +1,27 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: typesafety
|
|
3
|
-
description: Set up type-safe
|
|
3
|
+
description: Set up type-safe route names, params, search params, response payloads, and environment types in @rangojs/router. Use when route or search params aren't typed, ctx.env or ctx.get() is untyped, TypeScript can't infer a loader's return type, or wiring RegisteredRoutes / the generated route map.
|
|
4
4
|
argument-hint: [setup]
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Type Safety Setup
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
environment
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
This skill covers how route names, params, search params, response payloads,
|
|
10
|
+
environment bindings, and context variables get their types. Use it when
|
|
11
|
+
setting up a new app's type surfaces or when one of them resolves to
|
|
12
|
+
`any`/`unknown`/`never` unexpectedly.
|
|
13
|
+
|
|
14
|
+
What you lose without the setup:
|
|
15
|
+
|
|
16
|
+
- `ctx.reverse()` accepts any name and `href()` any `/${string}` path, so a
|
|
17
|
+
typo 404s at runtime instead of failing to compile.
|
|
18
|
+
- `Handler<"name">` is rejected (there is no generated map to look the name
|
|
19
|
+
up in), so `ctx.params`/`ctx.search` can't be inferred from the route.
|
|
20
|
+
- `ctx.env` is `unknown` until `Rango.Env` is registered (every binding access
|
|
21
|
+
is a compile error), and string-key `ctx.get("key")` returns `any` until
|
|
22
|
+
`Rango.Vars` is registered (a typo'd key is silently accepted).
|
|
23
|
+
- `Rango.PathResponse` resolves to `never` until `Rango.RegisteredRoutes` is
|
|
24
|
+
registered.
|
|
15
25
|
|
|
16
26
|
Each topic's full setup, code, and caveats live in a companion file linked
|
|
17
27
|
below. Read the one for your case.
|
|
@@ -48,5 +58,6 @@ below. Read the one for your case.
|
|
|
48
58
|
scoped context tokens, handle typing, passing loaders/handles as typed
|
|
49
59
|
props, and location state typing.
|
|
50
60
|
|
|
51
|
-
|
|
52
|
-
`useReverse`)
|
|
61
|
+
Related skills: `/links` (URL generation: `ctx.reverse`, `href`,
|
|
62
|
+
`useReverse`, per-module `*.gen.ts`), `/response-routes` (response payload
|
|
63
|
+
typing), `/api-client` (typed fetch client over `RouteResponse`).
|