@rangojs/router 0.0.0-experimental.d7eeaa75 → 0.0.0-experimental.dacec167
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 +120 -25
- package/dist/bin/rango.js +147 -57
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +2151 -846
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +57 -11
- package/skills/breadcrumbs/SKILL.md +3 -1
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +220 -30
- package/skills/caching/SKILL.md +116 -8
- package/skills/composability/SKILL.md +27 -2
- package/skills/document-cache/SKILL.md +78 -55
- package/skills/handler-use/SKILL.md +364 -0
- package/skills/hooks/SKILL.md +229 -20
- package/skills/host-router/SKILL.md +45 -20
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +46 -4
- package/skills/layout/SKILL.md +28 -7
- package/skills/links/SKILL.md +247 -17
- package/skills/loader/SKILL.md +219 -9
- package/skills/middleware/SKILL.md +47 -12
- package/skills/migrate-nextjs/SKILL.md +562 -0
- package/skills/migrate-react-router/SKILL.md +769 -0
- package/skills/mime-routes/SKILL.md +27 -0
- package/skills/observability/SKILL.md +137 -0
- package/skills/parallel/SKILL.md +71 -6
- package/skills/prerender/SKILL.md +14 -33
- package/skills/rango/SKILL.md +242 -22
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +66 -9
- package/skills/route/SKILL.md +57 -4
- package/skills/router-setup/SKILL.md +3 -3
- package/skills/server-actions/SKILL.md +751 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/testing/SKILL.md +778 -0
- package/skills/typesafety/SKILL.md +319 -27
- package/skills/use-cache/SKILL.md +34 -5
- package/skills/view-transitions/SKILL.md +294 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +117 -0
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/app-shell.ts +52 -0
- package/src/browser/event-controller.ts +86 -70
- package/src/browser/history-state.ts +21 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/navigation-bridge.ts +84 -11
- package/src/browser/navigation-client.ts +76 -28
- package/src/browser/navigation-store.ts +32 -9
- package/src/browser/navigation-transaction.ts +10 -28
- package/src/browser/partial-update.ts +64 -26
- package/src/browser/prefetch/cache.ts +129 -21
- package/src/browser/prefetch/fetch.ts +148 -16
- package/src/browser/prefetch/queue.ts +36 -5
- package/src/browser/rango-state.ts +53 -13
- package/src/browser/react/Link.tsx +30 -2
- package/src/browser/react/NavigationProvider.tsx +72 -31
- package/src/browser/react/filter-segment-order.ts +51 -7
- package/src/browser/react/index.ts +3 -0
- package/src/browser/react/location-state-shared.ts +175 -4
- package/src/browser/react/location-state.ts +39 -13
- package/src/browser/react/use-handle.ts +17 -9
- package/src/browser/react/use-navigation.ts +22 -2
- package/src/browser/react/use-params.ts +20 -8
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +22 -2
- package/src/browser/react/use-segments.ts +11 -8
- package/src/browser/response-adapter.ts +25 -0
- package/src/browser/rsc-router.tsx +64 -22
- package/src/browser/scroll-restoration.ts +22 -14
- package/src/browser/segment-reconciler.ts +36 -14
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +23 -30
- package/src/browser/types.ts +21 -0
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +60 -35
- package/src/build/generate-route-types.ts +2 -0
- package/src/build/index.ts +2 -0
- package/src/build/route-trie.ts +52 -25
- package/src/build/route-types/codegen.ts +4 -4
- package/src/build/route-types/include-resolution.ts +1 -1
- package/src/build/route-types/per-module-writer.ts +7 -4
- package/src/build/route-types/router-processing.ts +55 -14
- 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-scope.ts +28 -42
- package/src/cache/cf/cf-cache-store.ts +54 -13
- package/src/client.rsc.tsx +3 -0
- package/src/client.tsx +92 -182
- package/src/context-var.ts +5 -5
- package/src/decode-loader-results.ts +36 -0
- package/src/errors.ts +30 -1
- package/src/handle.ts +26 -13
- package/src/host/index.ts +2 -2
- package/src/host/router.ts +129 -57
- package/src/host/types.ts +31 -2
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +140 -20
- package/src/index.rsc.ts +9 -4
- package/src/index.ts +53 -15
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +21 -6
- package/src/loader.ts +3 -10
- package/src/missing-id-error.ts +68 -0
- package/src/outlet-context.ts +1 -1
- package/src/prerender.ts +4 -4
- package/src/response-utils.ts +37 -0
- package/src/reverse.ts +65 -36
- package/src/route-content-wrapper.tsx +6 -28
- package/src/route-definition/dsl-helpers.ts +384 -257
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +100 -28
- package/src/route-definition/resolve-handler-use.ts +6 -0
- package/src/route-definition/use-item-types.ts +32 -0
- package/src/route-types.ts +26 -41
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +15 -2
- package/src/router/error-handling.ts +1 -1
- package/src/router/handler-context.ts +21 -38
- package/src/router/intercept-resolution.ts +4 -18
- package/src/router/lazy-includes.ts +8 -8
- package/src/router/loader-resolution.ts +19 -2
- package/src/router/manifest.ts +22 -13
- package/src/router/match-api.ts +4 -3
- package/src/router/match-handlers.ts +63 -20
- package/src/router/match-middleware/cache-lookup.ts +44 -91
- package/src/router/match-middleware/cache-store.ts +3 -2
- package/src/router/match-result.ts +53 -32
- package/src/router/metrics.ts +1 -1
- package/src/router/middleware-types.ts +15 -26
- package/src/router/middleware.ts +99 -84
- package/src/router/pattern-matching.ts +101 -17
- package/src/router/prerender-match.ts +1 -1
- package/src/router/preview-match.ts +3 -1
- package/src/router/request-classification.ts +4 -28
- package/src/router/revalidation.ts +58 -2
- package/src/router/router-interfaces.ts +45 -28
- package/src/router/router-options.ts +40 -1
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +27 -6
- package/src/router/segment-resolution/revalidation.ts +147 -106
- package/src/router/segment-resolution/view-transition-default.ts +36 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry.ts +99 -0
- package/src/router/trie-matching.ts +18 -13
- package/src/router/types.ts +8 -0
- package/src/router/url-params.ts +49 -0
- package/src/router.ts +38 -23
- package/src/rsc/handler-context.ts +2 -2
- package/src/rsc/handler.ts +28 -69
- package/src/rsc/helpers.ts +91 -43
- package/src/rsc/index.ts +1 -1
- package/src/rsc/origin-guard.ts +28 -10
- package/src/rsc/progressive-enhancement.ts +4 -0
- package/src/rsc/response-route-handler.ts +46 -53
- package/src/rsc/rsc-rendering.ts +35 -51
- package/src/rsc/runtime-warnings.ts +9 -10
- package/src/rsc/server-action.ts +17 -37
- package/src/rsc/ssr-setup.ts +16 -0
- package/src/rsc/types.ts +8 -2
- package/src/search-params.ts +4 -4
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +122 -0
- package/src/segment-system.tsx +132 -116
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +143 -53
- package/src/server/cookie-store.ts +28 -4
- package/src/server/request-context.ts +20 -42
- package/src/ssr/index.tsx +5 -1
- package/src/static-handler.ts +1 -1
- package/src/testing/cache-status.ts +166 -0
- package/src/testing/collect-handle.ts +63 -0
- package/src/testing/dispatch.ts +440 -0
- package/src/testing/dom.entry.ts +22 -0
- package/src/testing/e2e/fixture.ts +154 -0
- package/src/testing/e2e/index.ts +149 -0
- package/src/testing/e2e/matchers.ts +51 -0
- package/src/testing/e2e/page-helpers.ts +272 -0
- package/src/testing/e2e/parity.ts +306 -0
- package/src/testing/e2e/server.ts +183 -0
- package/src/testing/flight-matchers.ts +104 -0
- package/src/testing/flight-runtime.d.ts +57 -0
- package/src/testing/flight-tree.ts +320 -0
- package/src/testing/flight.entry.ts +39 -0
- package/src/testing/flight.ts +197 -0
- package/src/testing/generated-routes.ts +223 -0
- package/src/testing/index.ts +106 -0
- package/src/testing/internal/context.ts +331 -0
- package/src/testing/internal/flight-client-globals.ts +30 -0
- package/src/testing/render-route.tsx +565 -0
- package/src/testing/run-loader.ts +341 -0
- package/src/testing/run-middleware.ts +188 -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 +270 -0
- package/src/types/global-namespace.ts +39 -26
- package/src/types/handler-context.ts +68 -50
- package/src/types/index.ts +1 -0
- package/src/types/loader-types.ts +5 -6
- package/src/types/request-scope.ts +126 -0
- package/src/types/route-entry.ts +11 -0
- package/src/types/segments.ts +35 -2
- package/src/urls/include-helper.ts +34 -67
- package/src/urls/index.ts +0 -3
- package/src/urls/path-helper-types.ts +41 -7
- package/src/urls/path-helper.ts +17 -52
- package/src/urls/pattern-types.ts +36 -19
- package/src/urls/response-types.ts +22 -29
- package/src/urls/type-extraction.ts +26 -116
- package/src/urls/urls-function.ts +1 -5
- package/src/use-loader.tsx +413 -42
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +6 -6
- package/src/vite/discovery/discover-routers.ts +101 -51
- package/src/vite/discovery/discovery-errors.ts +194 -0
- package/src/vite/discovery/gate-state.ts +171 -0
- package/src/vite/discovery/prerender-collection.ts +67 -26
- package/src/vite/discovery/route-types-writer.ts +40 -84
- package/src/vite/discovery/self-gen-tracking.ts +27 -1
- package/src/vite/discovery/state.ts +33 -0
- package/src/vite/discovery/virtual-module-codegen.ts +13 -23
- package/src/vite/index.ts +2 -0
- package/src/vite/plugin-types.ts +67 -0
- package/src/vite/plugins/cjs-to-esm.ts +8 -7
- package/src/vite/plugins/client-ref-dedup.ts +16 -0
- package/src/vite/plugins/client-ref-hashing.ts +28 -5
- package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
- package/src/vite/plugins/expose-action-id.ts +54 -30
- package/src/vite/plugins/expose-id-utils.ts +12 -8
- package/src/vite/plugins/expose-ids/export-analysis.ts +100 -20
- package/src/vite/plugins/expose-ids/handler-transform.ts +8 -61
- package/src/vite/plugins/expose-ids/loader-transform.ts +3 -5
- package/src/vite/plugins/expose-ids/router-transform.ts +20 -3
- package/src/vite/plugins/expose-internal-ids.ts +496 -486
- package/src/vite/plugins/performance-tracks.ts +29 -25
- package/src/vite/plugins/use-cache-transform.ts +65 -50
- package/src/vite/plugins/version-injector.ts +39 -23
- package/src/vite/plugins/version-plugin.ts +59 -2
- package/src/vite/plugins/virtual-entries.ts +2 -2
- package/src/vite/rango.ts +116 -29
- package/src/vite/router-discovery.ts +750 -100
- package/src/vite/utils/ast-handler-extract.ts +15 -15
- package/src/vite/utils/banner.ts +1 -1
- package/src/vite/utils/bundle-analysis.ts +4 -2
- package/src/vite/utils/client-chunks.ts +190 -0
- package/src/vite/utils/forward-user-plugins.ts +193 -0
- package/src/vite/utils/manifest-utils.ts +21 -5
- package/src/vite/utils/package-resolution.ts +41 -1
- package/src/vite/utils/prerender-utils.ts +21 -6
- package/src/vite/utils/shared-utils.ts +107 -26
- package/src/browser/action-response-classifier.ts +0 -99
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @rangojs/router/testing/vitest
|
|
3
|
+
*
|
|
4
|
+
* Vitest setup helper for the UNIT + INTEGRATION + DOM test project of a
|
|
5
|
+
* @rangojs/router consumer app. It returns the `resolve.alias` entries that make
|
|
6
|
+
* a real app's router / loaders / middleware importable in a bare Vitest process.
|
|
7
|
+
*
|
|
8
|
+
* Why this is needed (the documented "vi.mock(plugin-rsc) + import router"
|
|
9
|
+
* recipe is not sufficient for a real app):
|
|
10
|
+
*
|
|
11
|
+
* - `@rangojs/router` resolves to SERVER-ONLY STUBS outside the `react-server`
|
|
12
|
+
* condition — `urls()`, `createRouter()`, `cookies()`, `getRequestContext()`
|
|
13
|
+
* throw "only available in a react-server environment". Importing the app's own
|
|
14
|
+
* router/loaders/middleware then fails immediately. Vitest does NOT apply the
|
|
15
|
+
* `react-server` condition to bare-package exports resolution, and enabling it
|
|
16
|
+
* globally flips React to its server build (no `createContext`), crashing the
|
|
17
|
+
* router's client-boundary imports. The surgical fix is to alias ONLY the bare
|
|
18
|
+
* `@rangojs/router` specifier to its react-server entry (real impls) while
|
|
19
|
+
* leaving React as the client build — which is exactly what this helper does.
|
|
20
|
+
* - The build-only `@rangojs/router:version` virtual and `@vitejs/plugin-rsc/rsc`
|
|
21
|
+
* (whose real body imports unresolvable Vite virtuals) are stubbed.
|
|
22
|
+
* - Cloudflare apps additionally import the `cloudflare:workers` /
|
|
23
|
+
* `cloudflare:email` runtime virtuals; pass `{ preset: "cloudflare" }` to stub them.
|
|
24
|
+
*
|
|
25
|
+
* Usage (recommended one-call form — see {@link rangoTestConfig}):
|
|
26
|
+
*
|
|
27
|
+
* ```ts
|
|
28
|
+
* // vitest.config.ts
|
|
29
|
+
* import { defineConfig } from "vitest/config";
|
|
30
|
+
* import { rangoTestConfig } from "@rangojs/router/testing/vitest";
|
|
31
|
+
*
|
|
32
|
+
* export default defineConfig({
|
|
33
|
+
* test: {
|
|
34
|
+
* globals: true,
|
|
35
|
+
* include: ["test/**\/*.test.{ts,tsx}"],
|
|
36
|
+
* environment: "node",
|
|
37
|
+
* ...rangoTestConfig({ preset: "cloudflare" }),
|
|
38
|
+
* },
|
|
39
|
+
* });
|
|
40
|
+
* ```
|
|
41
|
+
*
|
|
42
|
+
* `rangoTestConfig` bundles the resolve aliases ({@link rangoTestAliases}) with
|
|
43
|
+
* the `server.deps.inline` contract ({@link rangoInlineDeps}) an installed
|
|
44
|
+
* consumer needs — @rangojs/router ships as TS source, and without `deps.inline`
|
|
45
|
+
* Vitest hands those `.ts` files to Node, which on Node >= 23 throws
|
|
46
|
+
* `ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING`. Use the lower-level
|
|
47
|
+
* `rangoTestAliases` directly only if you wire `deps.inline` yourself.
|
|
48
|
+
*
|
|
49
|
+
* Notes:
|
|
50
|
+
* - This is for the node/DOM project. The Flight project (real RSC rendering via
|
|
51
|
+
* `@rangojs/router/testing/flight`) uses the `react-server` condition and pure
|
|
52
|
+
* leaf server components — it does NOT use this alias (which would crash under
|
|
53
|
+
* the server React build). See the testing guide for the Flight config.
|
|
54
|
+
* - `renderRoute` (`@rangojs/router/testing/dom`) tests run in this same project
|
|
55
|
+
* under a DOM environment (`happy-dom`/`jsdom`); the alias does not affect them.
|
|
56
|
+
* - LIMITATION: the FULL app router still cannot be imported if it uses
|
|
57
|
+
* `Prerender()` / `createLoader()` (their build-time-injected `$$id` is absent
|
|
58
|
+
* in a bare test). Build a router from an importable, Prerender-free include for
|
|
59
|
+
* `dispatch`, or assert whole-router behavior with e2e.
|
|
60
|
+
*/
|
|
61
|
+
|
|
62
|
+
import { fileURLToPath } from "node:url";
|
|
63
|
+
|
|
64
|
+
/** A single Vite/Vitest resolve alias entry. Structurally a Vite `Alias`. */
|
|
65
|
+
export interface TestAlias {
|
|
66
|
+
find: string | RegExp;
|
|
67
|
+
replacement: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Options for {@link rangoTestAliases}. */
|
|
71
|
+
export interface RangoTestAliasOptions {
|
|
72
|
+
/**
|
|
73
|
+
* Deployment preset, matching `rango({ preset })` in the Vite plugin. With
|
|
74
|
+
* `"cloudflare"` the helper additionally stubs the Cloudflare Workers runtime
|
|
75
|
+
* virtuals (`cloudflare:workers` / `cloudflare:email`) a CF app's route tree
|
|
76
|
+
* imports. A string (not a boolean) so more presets can be added without an
|
|
77
|
+
* API change. Default: `"node"`.
|
|
78
|
+
*/
|
|
79
|
+
preset?: "node" | "cloudflare";
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Resolve a path relative to this module. Anchored at the PACKAGE ROOT
|
|
84
|
+
* (`../../` from both `src/testing/vitest.ts` and the shipped
|
|
85
|
+
* `dist/testing/vitest.js` — each is two levels below the root), so the alias
|
|
86
|
+
* targets always point at the `src/*.ts` files Vite transpiles at test time,
|
|
87
|
+
* regardless of whether this helper is loaded as source (in-repo) or as the
|
|
88
|
+
* compiled `dist` entry (an installed consumer).
|
|
89
|
+
*/
|
|
90
|
+
function here(relativeFromRoot: string): string {
|
|
91
|
+
return fileURLToPath(new URL(`../../${relativeFromRoot}`, import.meta.url));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Build the `resolve.alias` entries a consumer's node/DOM Vitest project needs to
|
|
96
|
+
* import a real @rangojs/router app's router/loaders/middleware. Spread into a
|
|
97
|
+
* Vitest config: `resolve: { alias: rangoTestAliases(...) }` (concat your own
|
|
98
|
+
* aliases as needed).
|
|
99
|
+
*/
|
|
100
|
+
export function rangoTestAliases(
|
|
101
|
+
opts: RangoTestAliasOptions = {},
|
|
102
|
+
): TestAlias[] {
|
|
103
|
+
const aliases: TestAlias[] = [
|
|
104
|
+
// Real impls (index.rsc.ts) for the bare specifier ONLY — exact regex so
|
|
105
|
+
// subpaths (/testing, /client, /cache, ...) are untouched. React stays the
|
|
106
|
+
// client build, so createContext and "use client" modules work.
|
|
107
|
+
{ find: /^@rangojs\/router$/, replacement: here("src/index.rsc.ts") },
|
|
108
|
+
{
|
|
109
|
+
find: "@rangojs/router:version",
|
|
110
|
+
replacement: here("src/testing/vitest-stubs/version.ts"),
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
find: /^@vitejs\/plugin-rsc\/rsc$/,
|
|
114
|
+
replacement: here("src/testing/vitest-stubs/plugin-rsc.ts"),
|
|
115
|
+
},
|
|
116
|
+
];
|
|
117
|
+
|
|
118
|
+
if (opts.preset === "cloudflare") {
|
|
119
|
+
aliases.push(
|
|
120
|
+
{
|
|
121
|
+
find: "cloudflare:workers",
|
|
122
|
+
replacement: here("src/testing/vitest-stubs/cloudflare-workers.ts"),
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
find: "cloudflare:email",
|
|
126
|
+
replacement: here("src/testing/vitest-stubs/cloudflare-email.ts"),
|
|
127
|
+
},
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return aliases;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Vitest `server.deps.inline` patterns that force Vite (not Node) to transpile
|
|
136
|
+
* @rangojs/router's TypeScript source under test.
|
|
137
|
+
*
|
|
138
|
+
* REQUIRED for an installed (node_modules) consumer: @rangojs/router ships as TS
|
|
139
|
+
* source, and Vitest externalizes node_modules by default — so without this Node
|
|
140
|
+
* loads the `.ts` files directly and, on Node >= 23, throws
|
|
141
|
+
* `ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING`. In this monorepo it is a no-op
|
|
142
|
+
* (the workspace symlink resolves to a realpath outside node_modules, which Vite
|
|
143
|
+
* already transpiles), which is precisely why an in-repo dogfood never surfaces
|
|
144
|
+
* the need and the contract has to be shipped explicitly.
|
|
145
|
+
*/
|
|
146
|
+
export const rangoInlineDeps: RegExp[] = [/@rangojs[/\\]router/];
|
|
147
|
+
|
|
148
|
+
/** The Vitest `test`-block fragment {@link rangoTestConfig} returns. */
|
|
149
|
+
export interface RangoTestConfig {
|
|
150
|
+
alias: TestAlias[];
|
|
151
|
+
server: { deps: { inline: RegExp[] } };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* The complete Vitest `test`-block fragment a consumer needs: the resolve
|
|
156
|
+
* aliases ({@link rangoTestAliases}) AND the `server.deps.inline` contract
|
|
157
|
+
* ({@link rangoInlineDeps}). Spread it into your `test` block so both land in
|
|
158
|
+
* one place and a consumer cannot forget the `deps.inline` half (omitting it
|
|
159
|
+
* loads rango's TS source through Node and breaks on Node >= 23):
|
|
160
|
+
*
|
|
161
|
+
* ```ts
|
|
162
|
+
* // vitest.config.ts
|
|
163
|
+
* import { defineConfig } from "vitest/config";
|
|
164
|
+
* import { rangoTestConfig } from "@rangojs/router/testing/vitest";
|
|
165
|
+
*
|
|
166
|
+
* export default defineConfig({
|
|
167
|
+
* test: {
|
|
168
|
+
* globals: true,
|
|
169
|
+
* include: ["test/**\/*.test.{ts,tsx}"],
|
|
170
|
+
* environment: "node",
|
|
171
|
+
* ...rangoTestConfig({ preset: "cloudflare" }),
|
|
172
|
+
* },
|
|
173
|
+
* });
|
|
174
|
+
* ```
|
|
175
|
+
*/
|
|
176
|
+
export function rangoTestConfig(
|
|
177
|
+
opts: RangoTestAliasOptions = {},
|
|
178
|
+
): RangoTestConfig {
|
|
179
|
+
return {
|
|
180
|
+
alias: rangoTestAliases(opts),
|
|
181
|
+
// fresh copy so the shared rangoInlineDeps const is never aliased into (or
|
|
182
|
+
// mutated through) a consumer's resolved config
|
|
183
|
+
server: { deps: { inline: [...rangoInlineDeps] } },
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** A minimal Vite plugin shape (avoids a hard dependency on Vite's types). */
|
|
188
|
+
interface FlightTransformPlugin {
|
|
189
|
+
name: string;
|
|
190
|
+
transform(
|
|
191
|
+
code: string,
|
|
192
|
+
id: string,
|
|
193
|
+
): Promise<{ code: string; map: unknown } | undefined>;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* A Vite plugin for the FLIGHT (react-server) Vitest project that applies the
|
|
198
|
+
* `"use client"` transform to a consumer's client modules — the same transform a
|
|
199
|
+
* real build applies. With it, `renderServerTree` (`@rangojs/router/testing/flight`)
|
|
200
|
+
* resolves client islands AUTOMATICALLY from the server tree's own imports: no
|
|
201
|
+
* `clientComponents` to pass, no filename convention. Without it, a `"use client"`
|
|
202
|
+
* module is imported as a plain (unmarked) function and would render server-side,
|
|
203
|
+
* so you must list islands via `renderServerTree(..., { clientComponents })`.
|
|
204
|
+
*
|
|
205
|
+
* Add it to your react-server Vitest project:
|
|
206
|
+
*
|
|
207
|
+
* ```ts
|
|
208
|
+
* // vitest.rsc.config.ts
|
|
209
|
+
* import { defineConfig } from "vitest/config";
|
|
210
|
+
* import { rangoUseClientTransform } from "@rangojs/router/testing/vitest";
|
|
211
|
+
*
|
|
212
|
+
* export default defineConfig({
|
|
213
|
+
* resolve: { conditions: ["react-server"] },
|
|
214
|
+
* plugins: [rangoUseClientTransform()],
|
|
215
|
+
* test: {
|
|
216
|
+
* include: ["test/**\/*.rsc-test.{ts,tsx}"],
|
|
217
|
+
* pool: "forks",
|
|
218
|
+
* execArgv: ["--conditions=react-server"],
|
|
219
|
+
* },
|
|
220
|
+
* });
|
|
221
|
+
* ```
|
|
222
|
+
*
|
|
223
|
+
* Each `"use client"` module's exports are replaced with client references keyed
|
|
224
|
+
* by the module's absolute path (the boundary id), the export name becoming the
|
|
225
|
+
* boundary name. Modules without the directive (server components) are untouched,
|
|
226
|
+
* so `renderToFlightString` of pure leaf trees is unaffected.
|
|
227
|
+
*/
|
|
228
|
+
export function rangoUseClientTransform(): FlightTransformPlugin {
|
|
229
|
+
return {
|
|
230
|
+
name: "rango:testing-use-client",
|
|
231
|
+
async transform(code, id) {
|
|
232
|
+
if (id.includes("/node_modules/")) return undefined;
|
|
233
|
+
// Fast path: only parse modules that mention the directive.
|
|
234
|
+
if (!code.includes("use client")) return undefined;
|
|
235
|
+
const { parseAstAsync } = await import("vite");
|
|
236
|
+
const { hasDirective, transformDirectiveProxyExport } =
|
|
237
|
+
await import("@vitejs/plugin-rsc/transforms");
|
|
238
|
+
// vite's parser and the transforms ship structurally-compatible but
|
|
239
|
+
// distinctly-typed ASTs (oxc vs estree); cast through the transform's own
|
|
240
|
+
// parameter type, exactly as plugin-rsc does at runtime.
|
|
241
|
+
type TransformAst = Parameters<typeof transformDirectiveProxyExport>[0];
|
|
242
|
+
let ast: TransformAst;
|
|
243
|
+
try {
|
|
244
|
+
ast = (await parseAstAsync(code)) as unknown as TransformAst;
|
|
245
|
+
} catch {
|
|
246
|
+
return undefined;
|
|
247
|
+
}
|
|
248
|
+
if (!hasDirective(ast.body, "use client")) return undefined;
|
|
249
|
+
const result = transformDirectiveProxyExport(ast, {
|
|
250
|
+
directive: "use client",
|
|
251
|
+
code,
|
|
252
|
+
runtime: (name: string) =>
|
|
253
|
+
`$$RangoRSD.registerClientReference(` +
|
|
254
|
+
`() => { throw new Error("client reference " + ${JSON.stringify(name)} + " is not callable on the server"); }, ` +
|
|
255
|
+
`${JSON.stringify(id)}, ${JSON.stringify(name)})`,
|
|
256
|
+
});
|
|
257
|
+
if (!result) return undefined;
|
|
258
|
+
const { output } = result;
|
|
259
|
+
// The vendored server serializer is the one renderToFlightString uses;
|
|
260
|
+
// resolvable here under the react-server condition.
|
|
261
|
+
output.prepend(
|
|
262
|
+
`import * as $$RangoRSD from "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge";\n`,
|
|
263
|
+
);
|
|
264
|
+
return {
|
|
265
|
+
code: output.toString(),
|
|
266
|
+
map: output.generateMap({ hires: true }),
|
|
267
|
+
};
|
|
268
|
+
},
|
|
269
|
+
};
|
|
270
|
+
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* ```typescript
|
|
8
8
|
* // In env.ts or env.d.ts
|
|
9
9
|
* declare global {
|
|
10
|
-
* namespace
|
|
10
|
+
* namespace Rango {
|
|
11
11
|
* interface Env extends AppBindings {}
|
|
12
12
|
* interface Vars extends AppVariables {}
|
|
13
13
|
* }
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* ```
|
|
19
19
|
*/
|
|
20
20
|
declare global {
|
|
21
|
-
namespace
|
|
21
|
+
namespace Rango {
|
|
22
22
|
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
|
23
23
|
interface Env {
|
|
24
24
|
// Empty by default - users augment with their bindings (e.g., { DB: D1Database })
|
|
@@ -44,13 +44,25 @@ declare global {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
/**
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
47
|
+
* Route map for path-validation surfaces (`href`, `ValidPaths`, `PathResponse`).
|
|
48
|
+
*
|
|
49
|
+
* Resolution order:
|
|
50
|
+
* 1. `RegisteredRoutes` — manual `extends typeof router.routeMap`; richest map,
|
|
51
|
+
* the only one carrying response-route payload metadata.
|
|
52
|
+
* 2. `GeneratedRouteMap` — auto-wired by `router.named-routes.gen.ts`; path +
|
|
53
|
+
* search only. Lets `rango generate` alone enable `href()`/`ValidPaths` path
|
|
54
|
+
* checking without a manual `RegisteredRoutes` declaration.
|
|
55
|
+
* 3. `Record<string, string>` — permissive fallback when nothing is registered.
|
|
56
|
+
*
|
|
57
|
+
* Referencing `GeneratedRouteMap` here is cycle-safe: it is declared in the
|
|
58
|
+
* standalone gen file with no imports, unlike `RegisteredRoutes extends typeof
|
|
59
|
+
* router.routeMap`.
|
|
50
60
|
*/
|
|
51
|
-
export type GetRegisteredRoutes = keyof
|
|
52
|
-
?
|
|
53
|
-
|
|
61
|
+
export type GetRegisteredRoutes = keyof Rango.RegisteredRoutes extends never
|
|
62
|
+
? keyof Rango.GeneratedRouteMap extends never
|
|
63
|
+
? Record<string, string>
|
|
64
|
+
: Rango.GeneratedRouteMap
|
|
65
|
+
: Rango.RegisteredRoutes;
|
|
54
66
|
|
|
55
67
|
/**
|
|
56
68
|
* Default route map for reverse() surfaces.
|
|
@@ -58,12 +70,11 @@ export type GetRegisteredRoutes = keyof RSCRouter.RegisteredRoutes extends never
|
|
|
58
70
|
* cycles, but falls back to RegisteredRoutes for manual augmentation and then to
|
|
59
71
|
* a permissive record when no route types are available.
|
|
60
72
|
*/
|
|
61
|
-
export type DefaultReverseRouteMap =
|
|
62
|
-
keyof
|
|
63
|
-
?
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
: RSCRouter.GeneratedRouteMap;
|
|
73
|
+
export type DefaultReverseRouteMap = keyof Rango.GeneratedRouteMap extends never
|
|
74
|
+
? keyof Rango.RegisteredRoutes extends never
|
|
75
|
+
? Record<string, string>
|
|
76
|
+
: Rango.RegisteredRoutes
|
|
77
|
+
: Rango.GeneratedRouteMap;
|
|
67
78
|
|
|
68
79
|
/**
|
|
69
80
|
* Default route map for Handler type.
|
|
@@ -71,30 +82,32 @@ export type DefaultReverseRouteMap =
|
|
|
71
82
|
* circular dependencies: router.tsx -> urls.tsx -> handler.tsx -> RegisteredRoutes -> router.tsx.
|
|
72
83
|
* GeneratedRouteMap is declared in a standalone gen file with no imports.
|
|
73
84
|
*/
|
|
74
|
-
export type DefaultHandlerRouteMap =
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
: RSCRouter.GeneratedRouteMap;
|
|
85
|
+
export type DefaultHandlerRouteMap = keyof Rango.GeneratedRouteMap extends never
|
|
86
|
+
? {}
|
|
87
|
+
: Rango.GeneratedRouteMap;
|
|
78
88
|
|
|
79
89
|
/**
|
|
80
|
-
* Default environment type - uses global augmentation if available
|
|
90
|
+
* Default environment type - uses global augmentation if available.
|
|
91
|
+
*
|
|
92
|
+
* Falls back to `unknown` (not `any`) when `Rango.Env` is not augmented, so
|
|
93
|
+
* an app that forgets to register its bindings gets a compile error on
|
|
94
|
+
* `ctx.env.SOMETHING` instead of silently losing all env type-checking. Augment
|
|
95
|
+
* `Rango.Env` to type `ctx.env`.
|
|
81
96
|
*/
|
|
82
|
-
export type DefaultEnv = keyof
|
|
83
|
-
? any
|
|
84
|
-
: RSCRouter.Env;
|
|
97
|
+
export type DefaultEnv = keyof Rango.Env extends never ? unknown : Rango.Env;
|
|
85
98
|
|
|
86
99
|
/**
|
|
87
100
|
* Default variables type - uses global augmentation if available, Record<string, any> otherwise
|
|
88
101
|
*/
|
|
89
|
-
export type DefaultVars = keyof
|
|
102
|
+
export type DefaultVars = keyof Rango.Vars extends never
|
|
90
103
|
? Record<string, any>
|
|
91
|
-
:
|
|
104
|
+
: Rango.Vars;
|
|
92
105
|
|
|
93
106
|
/**
|
|
94
107
|
* Default route name type for public `routeName` on contexts.
|
|
95
108
|
* When GeneratedRouteMap is augmented, narrows to the known route names.
|
|
96
109
|
* Otherwise falls back to `string` for untyped usage.
|
|
97
110
|
*/
|
|
98
|
-
export type DefaultRouteName = keyof
|
|
111
|
+
export type DefaultRouteName = keyof Rango.GeneratedRouteMap extends never
|
|
99
112
|
? string
|
|
100
|
-
: keyof
|
|
113
|
+
: keyof Rango.GeneratedRouteMap & string;
|
|
@@ -20,6 +20,7 @@ import type {
|
|
|
20
20
|
} from "./route-config.js";
|
|
21
21
|
import type { LoaderDefinition } from "./loader-types.js";
|
|
22
22
|
import type { UseItems, HandlerUseItem } from "../route-types.js";
|
|
23
|
+
import type { RequestScope } from "./request-scope.js";
|
|
23
24
|
|
|
24
25
|
// Re-export MiddlewareFn for internal/advanced use
|
|
25
26
|
export type { MiddlewareFn } from "../router/middleware.js";
|
|
@@ -42,7 +43,7 @@ export type { MiddlewareFn } from "../router/middleware.js";
|
|
|
42
43
|
*/
|
|
43
44
|
export type ScopedRouteMap<
|
|
44
45
|
TPrefix extends string,
|
|
45
|
-
TMap =
|
|
46
|
+
TMap = Rango.GeneratedRouteMap,
|
|
46
47
|
> = {
|
|
47
48
|
[K in keyof TMap as K extends `${TPrefix}.${infer Rest}`
|
|
48
49
|
? Rest
|
|
@@ -195,7 +196,7 @@ export type HandlerContext<
|
|
|
195
196
|
TEnv = DefaultEnv,
|
|
196
197
|
TSearch extends SearchSchema = {},
|
|
197
198
|
TRouteMap = never,
|
|
198
|
-
> = {
|
|
199
|
+
> = RequestScope<TEnv> & {
|
|
199
200
|
/**
|
|
200
201
|
* Route parameters extracted from the URL pattern.
|
|
201
202
|
* Type-safe when using Handler<"/path/:param"> or Handler<{ param: string }>.
|
|
@@ -215,44 +216,11 @@ export type HandlerContext<
|
|
|
215
216
|
* changing build semantics (e.g., skip expensive operations in dev).
|
|
216
217
|
*/
|
|
217
218
|
dev: boolean;
|
|
218
|
-
/**
|
|
219
|
-
* The original incoming Request object (transport URL intact).
|
|
220
|
-
* Use `ctx.url` / `ctx.searchParams` for application logic — those have
|
|
221
|
-
* internal `_rsc*` params stripped. `ctx.request` preserves the raw URL
|
|
222
|
-
* for cases where you need original headers, method, or body.
|
|
223
|
-
*/
|
|
224
|
-
request: Request;
|
|
225
|
-
/**
|
|
226
|
-
* Query parameters from the URL (system params like `_rsc*` are filtered).
|
|
227
|
-
* Always a standard URLSearchParams instance.
|
|
228
|
-
*/
|
|
229
|
-
searchParams: URLSearchParams;
|
|
230
219
|
/**
|
|
231
220
|
* Typed search parameters parsed from URL query string via the route's
|
|
232
221
|
* search schema. Empty object when no schema is defined.
|
|
233
222
|
*/
|
|
234
223
|
search: {} extends TSearch ? {} : ResolveSearchSchema<TSearch>;
|
|
235
|
-
/**
|
|
236
|
-
* The pathname portion of the request URL.
|
|
237
|
-
*/
|
|
238
|
-
pathname: string;
|
|
239
|
-
/**
|
|
240
|
-
* The full URL object (with internal `_rsc*` params stripped).
|
|
241
|
-
* Use this for application logic — routing, link generation, display.
|
|
242
|
-
*/
|
|
243
|
-
url: URL;
|
|
244
|
-
/**
|
|
245
|
-
* The original request URL with all parameters intact, including
|
|
246
|
-
* internal `_rsc*` transport params. Use `ctx.url` for application
|
|
247
|
-
* logic — this is only needed for advanced cases like debugging
|
|
248
|
-
* or custom cache keying.
|
|
249
|
-
*/
|
|
250
|
-
originalUrl: URL;
|
|
251
|
-
/**
|
|
252
|
-
* Platform bindings (DB, KV, secrets, etc.).
|
|
253
|
-
* Access resources like `ctx.env.DB`, `ctx.env.KV`.
|
|
254
|
-
*/
|
|
255
|
-
env: TEnv;
|
|
256
224
|
/**
|
|
257
225
|
* Type-safe getter for middleware variables.
|
|
258
226
|
* Preferred way to read middleware-injected variables.
|
|
@@ -503,13 +471,16 @@ export type RevalidateParams<TParams = GenericParams, TEnv = any> = Parameters<
|
|
|
503
471
|
* **Return Types:**
|
|
504
472
|
* - `boolean` - Hard decision: immediately returns this value (short-circuits)
|
|
505
473
|
* - `{ defaultShouldRevalidate: boolean }` - Soft decision: updates suggestion for next revalidator
|
|
474
|
+
* - `void` / `null` / `undefined` - Defer to the current suggestion (no opinion); the
|
|
475
|
+
* loop continues to the next revalidator without changing the running default
|
|
506
476
|
*
|
|
507
477
|
* **Execution Flow:**
|
|
508
478
|
* 1. Start with built-in `defaultShouldRevalidate` (true if params changed)
|
|
509
479
|
* 2. Execute global revalidators first, then route-specific
|
|
510
480
|
* 3. Hard decision (boolean): stop immediately and use that value
|
|
511
481
|
* 4. Soft decision (object): update suggestion and continue to next revalidator
|
|
512
|
-
* 5.
|
|
482
|
+
* 5. Defer (`void` / `null` / `undefined`): leave suggestion unchanged and continue
|
|
483
|
+
* 6. If no hard decision was returned: use the final running suggestion
|
|
513
484
|
*
|
|
514
485
|
* @param args.currentParams - Previous route params (generic by default, can be narrowed)
|
|
515
486
|
* @param args.currentUrl - Previous URL
|
|
@@ -521,7 +492,8 @@ export type RevalidateParams<TParams = GenericParams, TEnv = any> = Parameters<
|
|
|
521
492
|
* @param args.formData - Form data from action (future support)
|
|
522
493
|
* @param args.formMethod - HTTP method from action (future support)
|
|
523
494
|
*
|
|
524
|
-
* @returns Hard decision (boolean)
|
|
495
|
+
* @returns Hard decision (boolean), soft suggestion (object), or defer
|
|
496
|
+
* (`void` / `null` / `undefined`) to keep the running suggestion as-is.
|
|
525
497
|
*
|
|
526
498
|
* @example
|
|
527
499
|
* ```typescript
|
|
@@ -541,19 +513,34 @@ export type RevalidateParams<TParams = GenericParams, TEnv = any> = Parameters<
|
|
|
541
513
|
* })
|
|
542
514
|
* ```
|
|
543
515
|
*/
|
|
516
|
+
/**
|
|
517
|
+
* A reference to a server action, used by `isAction()` in a revalidate predicate.
|
|
518
|
+
*
|
|
519
|
+
* Either a directly imported action (`import { addToCart }`) or a namespace
|
|
520
|
+
* import of an action module (`import * as CartActions`). Matching resolves the
|
|
521
|
+
* action's build-injected id (`path#export`) — the same identity the router uses
|
|
522
|
+
* for `actionId` — so a renamed or moved action breaks at compile time instead
|
|
523
|
+
* of silently failing to match.
|
|
524
|
+
*/
|
|
525
|
+
export type ActionRef =
|
|
526
|
+
| ((...args: never[]) => unknown)
|
|
527
|
+
| Record<string, unknown>;
|
|
528
|
+
|
|
544
529
|
/**
|
|
545
530
|
* Revalidation function called during client-side navigation to decide whether
|
|
546
531
|
* a segment (layout, route, parallel slot, or loader) should be re-rendered.
|
|
547
532
|
*
|
|
548
533
|
* Return `true` to re-render, `false` to skip (keep client's current version),
|
|
549
|
-
*
|
|
550
|
-
* downstream
|
|
534
|
+
* `{ defaultShouldRevalidate: boolean }` to update the running suggestion for
|
|
535
|
+
* downstream revalidators, or nothing (`void` / `null` / `undefined`) to defer
|
|
536
|
+
* to the current suggestion without changing it.
|
|
551
537
|
*
|
|
552
538
|
* @example
|
|
553
539
|
* ```ts
|
|
554
|
-
* // Re-render
|
|
540
|
+
* // Re-render when a cart action happened or the browser signals staleness;
|
|
541
|
+
* // defer otherwise (|| undefined) so the segment default still applies
|
|
555
542
|
* revalidate(({ actionId, stale }) =>
|
|
556
|
-
* actionId?.includes("cart") || stale ||
|
|
543
|
+
* actionId?.includes("cart") || stale || undefined
|
|
557
544
|
* )
|
|
558
545
|
*
|
|
559
546
|
* // Always re-render when params change (default behavior made explicit)
|
|
@@ -580,8 +567,11 @@ export type ShouldRevalidateFn<TParams = GenericParams, TEnv = any> = (args: {
|
|
|
580
567
|
|
|
581
568
|
// ── Segment metadata (which segment is being evaluated) ──────────────
|
|
582
569
|
|
|
583
|
-
/**
|
|
584
|
-
|
|
570
|
+
/**
|
|
571
|
+
* The type of segment being revalidated. `"loader"` is passed to revalidate
|
|
572
|
+
* functions attached to a `loader(Fn, () => [revalidate(...)])` registration.
|
|
573
|
+
*/
|
|
574
|
+
segmentType: "layout" | "route" | "parallel" | "loader";
|
|
585
575
|
/** Layout name (e.g., `"root"`, `"shop"`, `"auth"`). Only set for layout segments. */
|
|
586
576
|
layoutName?: string;
|
|
587
577
|
/** Slot name (e.g., `"@sidebar"`, `"@modal"`). Only set for parallel segments. */
|
|
@@ -597,21 +587,49 @@ export type ShouldRevalidateFn<TParams = GenericParams, TEnv = any> = (args: {
|
|
|
597
587
|
* relative to the project root, followed by `#` and the exported function name.
|
|
598
588
|
*
|
|
599
589
|
* This is stable and can be used for path-based matching to revalidate
|
|
600
|
-
* when any action in a module or directory fires
|
|
590
|
+
* when any action in a module or directory fires. Prefer `|| undefined`
|
|
591
|
+
* (defer to the segment default / downstream revalidators) over `?? false`
|
|
592
|
+
* (hard short-circuit that suppresses the default and ends the chain):
|
|
601
593
|
*
|
|
602
594
|
* @example
|
|
603
595
|
* ```ts
|
|
604
596
|
* // Match a specific action
|
|
605
|
-
* revalidate(({ actionId }) => actionId === "src/actions/cart.ts#addToCart")
|
|
597
|
+
* revalidate(({ actionId }) => actionId === "src/actions/cart.ts#addToCart" || undefined)
|
|
606
598
|
*
|
|
607
599
|
* // Match any action in the cart module
|
|
608
|
-
* revalidate(({ actionId }) => actionId?.includes("cart")
|
|
600
|
+
* revalidate(({ actionId }) => actionId?.includes("cart") || undefined)
|
|
609
601
|
*
|
|
610
602
|
* // Match any action under src/apps/store/actions/
|
|
611
|
-
* revalidate(({ actionId }) => actionId?.startsWith("src/apps/store/actions/")
|
|
603
|
+
* revalidate(({ actionId }) => actionId?.startsWith("src/apps/store/actions/") || undefined)
|
|
612
604
|
* ```
|
|
613
605
|
*/
|
|
614
606
|
actionId?: string;
|
|
607
|
+
/**
|
|
608
|
+
* Typed, rename-safe action matching. Returns `true` when the action that
|
|
609
|
+
* triggered this revalidation is one of the given references — or, for a
|
|
610
|
+
* namespace import (`import * as CartActions`), any export of that module —
|
|
611
|
+
* and `false` otherwise (including plain navigation with no action).
|
|
612
|
+
*
|
|
613
|
+
* Prefer this over hand-written `actionId` substring matches: it resolves the
|
|
614
|
+
* action's stable `path#export` id from the imported reference, so a rename is
|
|
615
|
+
* a type error in one place instead of silent drift across consumers. It
|
|
616
|
+
* resolves the reference the same way the action boundary derives `actionId`
|
|
617
|
+
* (`$id ?? $$id`), so it matches in both dev and production.
|
|
618
|
+
*
|
|
619
|
+
* Returns a raw boolean, so for the common "revalidate on match, else defer"
|
|
620
|
+
* intent combine with `|| undefined`:
|
|
621
|
+
*
|
|
622
|
+
* @example
|
|
623
|
+
* ```ts
|
|
624
|
+
* import { addToCart, removeFromCart } from "./actions/cart";
|
|
625
|
+
* import * as CartActions from "./actions/cart";
|
|
626
|
+
*
|
|
627
|
+
* revalidate((ctx) => ctx.isAction(addToCart) || undefined); // one action
|
|
628
|
+
* revalidate((ctx) => ctx.isAction(addToCart, removeFromCart) || undefined); // several
|
|
629
|
+
* revalidate((ctx) => ctx.isAction(CartActions) || undefined); // any in the module
|
|
630
|
+
* ```
|
|
631
|
+
*/
|
|
632
|
+
isAction: (...actions: ActionRef[]) => boolean;
|
|
615
633
|
/** URL where the action was executed (the page the user was on when they triggered the action). */
|
|
616
634
|
actionUrl?: URL;
|
|
617
635
|
/** Return value from the action execution. Can be used to conditionally revalidate based on the action's outcome. */
|
|
@@ -647,7 +665,7 @@ export type ShouldRevalidateFn<TParams = GenericParams, TEnv = any> = (args: {
|
|
|
647
665
|
* action that may have mutated backend state.
|
|
648
666
|
*/
|
|
649
667
|
stale?: boolean;
|
|
650
|
-
}) => boolean | { defaultShouldRevalidate: boolean };
|
|
668
|
+
}) => boolean | { defaultShouldRevalidate: boolean } | null | void;
|
|
651
669
|
|
|
652
670
|
// MiddlewareFn is imported from "../router/middleware.js" and re-exported
|
|
653
671
|
|
|
@@ -752,7 +770,7 @@ export type Revalidate<
|
|
|
752
770
|
* Middleware function with typed params and environment
|
|
753
771
|
*
|
|
754
772
|
* @template TParams - Params object (defaults to generic)
|
|
755
|
-
* @template TEnv - Environment type (defaults to global
|
|
773
|
+
* @template TEnv - Environment type (defaults to global Rango.Env)
|
|
756
774
|
*
|
|
757
775
|
* Note: Middleware cannot directly use route names for params typing because
|
|
758
776
|
* middleware is defined during router setup, before RegisteredRoutes is populated.
|
|
@@ -760,7 +778,7 @@ export type Revalidate<
|
|
|
760
778
|
*
|
|
761
779
|
* @example
|
|
762
780
|
* ```typescript
|
|
763
|
-
* // Basic middleware (uses global
|
|
781
|
+
* // Basic middleware (uses global Rango.Env via module augmentation)
|
|
764
782
|
* const middleware: Middleware = async (ctx, next) => {
|
|
765
783
|
* ctx.set("user", { id: "123" }); // Type-safe!
|
|
766
784
|
* await next();
|
package/src/types/index.ts
CHANGED
|
@@ -3,11 +3,13 @@ import type { Handle } from "../handle.js";
|
|
|
3
3
|
import type { MiddlewareFn } from "../router/middleware.js";
|
|
4
4
|
import type { ScopedReverseFunction } from "../reverse.js";
|
|
5
5
|
import type { SearchSchema, ResolveSearchSchema } from "../search-params.js";
|
|
6
|
+
import type { UseItems, LoaderUseItem } from "../route-types.js";
|
|
6
7
|
import type {
|
|
7
8
|
DefaultEnv,
|
|
8
9
|
DefaultReverseRouteMap,
|
|
9
10
|
DefaultVars,
|
|
10
11
|
} from "./global-namespace.js";
|
|
12
|
+
import type { RequestScope } from "./request-scope.js";
|
|
11
13
|
|
|
12
14
|
/**
|
|
13
15
|
* Context passed to loader functions during execution
|
|
@@ -39,7 +41,7 @@ export type LoaderContext<
|
|
|
39
41
|
TEnv = DefaultEnv,
|
|
40
42
|
TBody = unknown,
|
|
41
43
|
TSearch extends SearchSchema = {},
|
|
42
|
-
> = {
|
|
44
|
+
> = RequestScope<TEnv> & {
|
|
43
45
|
params: TParams;
|
|
44
46
|
/**
|
|
45
47
|
* Route params extracted from the URL pattern match (server-side only).
|
|
@@ -48,12 +50,7 @@ export type LoaderContext<
|
|
|
48
50
|
* resource scoping.
|
|
49
51
|
*/
|
|
50
52
|
routeParams: Record<string, string>;
|
|
51
|
-
request: Request;
|
|
52
|
-
searchParams: URLSearchParams;
|
|
53
53
|
search: {} extends TSearch ? {} : ResolveSearchSchema<TSearch>;
|
|
54
|
-
pathname: string;
|
|
55
|
-
url: URL;
|
|
56
|
-
env: TEnv;
|
|
57
54
|
get: {
|
|
58
55
|
<T>(contextVar: ContextVar<T>): T | undefined;
|
|
59
56
|
} & (<K extends keyof DefaultVars>(key: K) => DefaultVars[K]);
|
|
@@ -207,4 +204,6 @@ export type LoaderDefinition<
|
|
|
207
204
|
__brand: "loader";
|
|
208
205
|
$$id: string; // Injected by Vite plugin (exposeInternalIds) - unique identifier
|
|
209
206
|
fn?: LoaderFn<T, TParams, any>; // Optional - server-side only, stored in registry for RSC
|
|
207
|
+
/** Composable default DSL items merged when the loader is mounted. */
|
|
208
|
+
use?: () => UseItems<LoaderUseItem>;
|
|
210
209
|
};
|