@rangojs/router 0.0.0-experimental.fb4fdc18 → 0.0.0-experimental.fce7fbd1
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 +9 -9
- package/dist/bin/rango.js +147 -57
- package/dist/testing/vitest.js +48 -0
- package/dist/vite/index.js +914 -485
- package/package.json +55 -11
- 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 +3 -1
- package/skills/hooks/SKILL.md +214 -18
- package/skills/host-router/SKILL.md +45 -20
- package/skills/intercept/SKILL.md +26 -4
- package/skills/layout/SKILL.md +6 -7
- package/skills/links/SKILL.md +173 -17
- package/skills/loader/SKILL.md +149 -6
- package/skills/middleware/SKILL.md +13 -9
- package/skills/migrate-nextjs/SKILL.md +1 -1
- package/skills/mime-routes/SKILL.md +27 -0
- package/skills/observability/SKILL.md +137 -0
- package/skills/parallel/SKILL.md +5 -6
- package/skills/prerender/SKILL.md +14 -33
- package/skills/rango/SKILL.md +242 -26
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +58 -9
- package/skills/route/SKILL.md +13 -4
- package/skills/router-setup/SKILL.md +3 -3
- package/skills/server-actions/SKILL.md +53 -41
- package/skills/testing/SKILL.md +599 -0
- package/skills/typesafety/SKILL.md +310 -26
- 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/event-controller.ts +42 -66
- package/src/browser/history-state.ts +21 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/navigation-bridge.ts +6 -6
- package/src/browser/navigation-client.ts +12 -15
- package/src/browser/navigation-store.ts +7 -8
- package/src/browser/navigation-transaction.ts +10 -28
- package/src/browser/partial-update.ts +9 -19
- package/src/browser/react/NavigationProvider.tsx +29 -40
- 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-params.ts +3 -4
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +14 -1
- package/src/browser/response-adapter.ts +25 -0
- package/src/browser/rsc-router.tsx +30 -16
- package/src/browser/scroll-restoration.ts +22 -14
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +23 -30
- package/src/browser/types.ts +2 -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-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 +49 -6
- package/src/client.rsc.tsx +3 -0
- package/src/client.tsx +10 -8
- 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 +6 -4
- package/src/index.ts +13 -6
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +2 -5
- package/src/loader.ts +3 -10
- package/src/missing-id-error.ts +68 -0
- package/src/prerender.ts +4 -4
- package/src/response-utils.ts +9 -0
- package/src/reverse.ts +65 -41
- package/src/route-content-wrapper.tsx +6 -28
- package/src/route-definition/dsl-helpers.ts +238 -263
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +37 -14
- package/src/route-definition/use-item-types.ts +32 -0
- package/src/route-types.ts +19 -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 +4 -42
- package/src/router/intercept-resolution.ts +4 -18
- package/src/router/lazy-includes.ts +2 -2
- package/src/router/loader-resolution.ts +16 -2
- package/src/router/match-handlers.ts +62 -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 +32 -30
- package/src/router/metrics.ts +1 -1
- package/src/router/middleware-types.ts +1 -1
- package/src/router/middleware.ts +46 -78
- 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 +43 -1
- 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 +19 -6
- package/src/router/segment-resolution/revalidation.ts +19 -6
- 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/types.ts +8 -0
- package/src/router.ts +37 -21
- package/src/rsc/handler-context.ts +2 -2
- package/src/rsc/handler.ts +20 -65
- package/src/rsc/helpers.ts +22 -2
- package/src/rsc/index.ts +1 -1
- package/src/rsc/origin-guard.ts +28 -10
- package/src/rsc/response-route-handler.ts +32 -52
- package/src/rsc/rsc-rendering.ts +27 -53
- package/src/rsc/runtime-warnings.ts +9 -10
- package/src/rsc/server-action.ts +13 -37
- package/src/rsc/ssr-setup.ts +16 -0
- package/src/rsc/types.ts +2 -2
- package/src/search-params.ts +4 -4
- package/src/segment-system.tsx +121 -65
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +118 -51
- package/src/server/cookie-store.ts +28 -4
- package/src/server/request-context.ts +10 -0
- 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 +21 -0
- package/src/testing/flight.entry.ts +22 -0
- package/src/testing/flight.ts +182 -0
- package/src/testing/generated-routes.ts +223 -0
- package/src/testing/index.ts +105 -0
- package/src/testing/internal/context.ts +193 -0
- package/src/testing/render-route.tsx +536 -0
- package/src/testing/run-loader.ts +296 -0
- package/src/testing/run-middleware.ts +170 -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 +183 -0
- package/src/types/global-namespace.ts +39 -26
- package/src/types/handler-context.ts +56 -11
- package/src/types/index.ts +1 -0
- package/src/types/segments.ts +18 -1
- package/src/urls/include-helper.ts +10 -53
- package/src/urls/index.ts +0 -3
- package/src/urls/path-helper-types.ts +11 -3
- package/src/urls/path-helper.ts +17 -52
- package/src/urls/pattern-types.ts +36 -19
- package/src/urls/response-types.ts +20 -19
- 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 +1 -0
- package/src/vite/discovery/bundle-postprocess.ts +6 -6
- package/src/vite/discovery/discover-routers.ts +70 -48
- package/src/vite/discovery/discovery-errors.ts +194 -0
- package/src/vite/discovery/prerender-collection.ts +19 -25
- package/src/vite/discovery/route-types-writer.ts +40 -84
- 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 +3 -7
- package/src/vite/plugins/client-ref-hashing.ts +12 -1
- package/src/vite/plugins/cloudflare-protocol-stub.ts +1 -1
- package/src/vite/plugins/expose-action-id.ts +2 -2
- 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-internal-ids.ts +47 -67
- package/src/vite/plugins/performance-tracks.ts +12 -16
- package/src/vite/plugins/use-cache-transform.ts +13 -11
- package/src/vite/plugins/version-injector.ts +2 -12
- package/src/vite/plugins/version-plugin.ts +59 -2
- package/src/vite/plugins/virtual-entries.ts +2 -2
- package/src/vite/rango.ts +67 -15
- package/src/vite/router-discovery.ts +208 -63
- package/src/vite/utils/ast-handler-extract.ts +15 -15
- 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/shared-utils.ts +107 -26
- package/src/browser/action-response-classifier.ts +0 -99
|
@@ -0,0 +1,183 @@
|
|
|
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 `{ cloudflare: true }` 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({ cloudflare: true }),
|
|
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
|
+
* Stub the Cloudflare Workers runtime virtuals (`cloudflare:workers` /
|
|
74
|
+
* `cloudflare:email`). Enable for a Cloudflare app whose route tree imports
|
|
75
|
+
* them. Default: false.
|
|
76
|
+
*/
|
|
77
|
+
cloudflare?: boolean;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Resolve a path relative to this module. Anchored at the PACKAGE ROOT
|
|
82
|
+
* (`../../` from both `src/testing/vitest.ts` and the shipped
|
|
83
|
+
* `dist/testing/vitest.js` — each is two levels below the root), so the alias
|
|
84
|
+
* targets always point at the `src/*.ts` files Vite transpiles at test time,
|
|
85
|
+
* regardless of whether this helper is loaded as source (in-repo) or as the
|
|
86
|
+
* compiled `dist` entry (an installed consumer).
|
|
87
|
+
*/
|
|
88
|
+
function here(relativeFromRoot: string): string {
|
|
89
|
+
return fileURLToPath(new URL(`../../${relativeFromRoot}`, import.meta.url));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Build the `resolve.alias` entries a consumer's node/DOM Vitest project needs to
|
|
94
|
+
* import a real @rangojs/router app's router/loaders/middleware. Spread into a
|
|
95
|
+
* Vitest config: `resolve: { alias: rangoTestAliases(...) }` (concat your own
|
|
96
|
+
* aliases as needed).
|
|
97
|
+
*/
|
|
98
|
+
export function rangoTestAliases(
|
|
99
|
+
opts: RangoTestAliasOptions = {},
|
|
100
|
+
): TestAlias[] {
|
|
101
|
+
const aliases: TestAlias[] = [
|
|
102
|
+
// Real impls (index.rsc.ts) for the bare specifier ONLY — exact regex so
|
|
103
|
+
// subpaths (/testing, /client, /cache, ...) are untouched. React stays the
|
|
104
|
+
// client build, so createContext and "use client" modules work.
|
|
105
|
+
{ find: /^@rangojs\/router$/, replacement: here("src/index.rsc.ts") },
|
|
106
|
+
{
|
|
107
|
+
find: "@rangojs/router:version",
|
|
108
|
+
replacement: here("src/testing/vitest-stubs/version.ts"),
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
find: /^@vitejs\/plugin-rsc\/rsc$/,
|
|
112
|
+
replacement: here("src/testing/vitest-stubs/plugin-rsc.ts"),
|
|
113
|
+
},
|
|
114
|
+
];
|
|
115
|
+
|
|
116
|
+
if (opts.cloudflare) {
|
|
117
|
+
aliases.push(
|
|
118
|
+
{
|
|
119
|
+
find: "cloudflare:workers",
|
|
120
|
+
replacement: here("src/testing/vitest-stubs/cloudflare-workers.ts"),
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
find: "cloudflare:email",
|
|
124
|
+
replacement: here("src/testing/vitest-stubs/cloudflare-email.ts"),
|
|
125
|
+
},
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return aliases;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Vitest `server.deps.inline` patterns that force Vite (not Node) to transpile
|
|
134
|
+
* @rangojs/router's TypeScript source under test.
|
|
135
|
+
*
|
|
136
|
+
* REQUIRED for an installed (node_modules) consumer: @rangojs/router ships as TS
|
|
137
|
+
* source, and Vitest externalizes node_modules by default — so without this Node
|
|
138
|
+
* loads the `.ts` files directly and, on Node >= 23, throws
|
|
139
|
+
* `ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING`. In this monorepo it is a no-op
|
|
140
|
+
* (the workspace symlink resolves to a realpath outside node_modules, which Vite
|
|
141
|
+
* already transpiles), which is precisely why an in-repo dogfood never surfaces
|
|
142
|
+
* the need and the contract has to be shipped explicitly.
|
|
143
|
+
*/
|
|
144
|
+
export const rangoInlineDeps: RegExp[] = [/@rangojs[/\\]router/];
|
|
145
|
+
|
|
146
|
+
/** The Vitest `test`-block fragment {@link rangoTestConfig} returns. */
|
|
147
|
+
export interface RangoTestConfig {
|
|
148
|
+
alias: TestAlias[];
|
|
149
|
+
server: { deps: { inline: RegExp[] } };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* The complete Vitest `test`-block fragment a consumer needs: the resolve
|
|
154
|
+
* aliases ({@link rangoTestAliases}) AND the `server.deps.inline` contract
|
|
155
|
+
* ({@link rangoInlineDeps}). Spread it into your `test` block so both land in
|
|
156
|
+
* one place and a consumer cannot forget the `deps.inline` half (omitting it
|
|
157
|
+
* loads rango's TS source through Node and breaks on Node >= 23):
|
|
158
|
+
*
|
|
159
|
+
* ```ts
|
|
160
|
+
* // vitest.config.ts
|
|
161
|
+
* import { defineConfig } from "vitest/config";
|
|
162
|
+
* import { rangoTestConfig } from "@rangojs/router/testing/vitest";
|
|
163
|
+
*
|
|
164
|
+
* export default defineConfig({
|
|
165
|
+
* test: {
|
|
166
|
+
* globals: true,
|
|
167
|
+
* include: ["test/**\/*.test.{ts,tsx}"],
|
|
168
|
+
* environment: "node",
|
|
169
|
+
* ...rangoTestConfig({ cloudflare: true }),
|
|
170
|
+
* },
|
|
171
|
+
* });
|
|
172
|
+
* ```
|
|
173
|
+
*/
|
|
174
|
+
export function rangoTestConfig(
|
|
175
|
+
opts: RangoTestAliasOptions = {},
|
|
176
|
+
): RangoTestConfig {
|
|
177
|
+
return {
|
|
178
|
+
alias: rangoTestAliases(opts),
|
|
179
|
+
// fresh copy so the shared rangoInlineDeps const is never aliased into (or
|
|
180
|
+
// mutated through) a consumer's resolved config
|
|
181
|
+
server: { deps: { inline: [...rangoInlineDeps] } },
|
|
182
|
+
};
|
|
183
|
+
}
|
|
@@ -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;
|
|
@@ -43,7 +43,7 @@ export type { MiddlewareFn } from "../router/middleware.js";
|
|
|
43
43
|
*/
|
|
44
44
|
export type ScopedRouteMap<
|
|
45
45
|
TPrefix extends string,
|
|
46
|
-
TMap =
|
|
46
|
+
TMap = Rango.GeneratedRouteMap,
|
|
47
47
|
> = {
|
|
48
48
|
[K in keyof TMap as K extends `${TPrefix}.${infer Rest}`
|
|
49
49
|
? Rest
|
|
@@ -513,6 +513,19 @@ export type RevalidateParams<TParams = GenericParams, TEnv = any> = Parameters<
|
|
|
513
513
|
* })
|
|
514
514
|
* ```
|
|
515
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
|
+
|
|
516
529
|
/**
|
|
517
530
|
* Revalidation function called during client-side navigation to decide whether
|
|
518
531
|
* a segment (layout, route, parallel slot, or loader) should be re-rendered.
|
|
@@ -524,9 +537,10 @@ export type RevalidateParams<TParams = GenericParams, TEnv = any> = Parameters<
|
|
|
524
537
|
*
|
|
525
538
|
* @example
|
|
526
539
|
* ```ts
|
|
527
|
-
* // 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
|
|
528
542
|
* revalidate(({ actionId, stale }) =>
|
|
529
|
-
* actionId?.includes("cart") || stale ||
|
|
543
|
+
* actionId?.includes("cart") || stale || undefined
|
|
530
544
|
* )
|
|
531
545
|
*
|
|
532
546
|
* // Always re-render when params change (default behavior made explicit)
|
|
@@ -553,8 +567,11 @@ export type ShouldRevalidateFn<TParams = GenericParams, TEnv = any> = (args: {
|
|
|
553
567
|
|
|
554
568
|
// ── Segment metadata (which segment is being evaluated) ──────────────
|
|
555
569
|
|
|
556
|
-
/**
|
|
557
|
-
|
|
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";
|
|
558
575
|
/** Layout name (e.g., `"root"`, `"shop"`, `"auth"`). Only set for layout segments. */
|
|
559
576
|
layoutName?: string;
|
|
560
577
|
/** Slot name (e.g., `"@sidebar"`, `"@modal"`). Only set for parallel segments. */
|
|
@@ -570,21 +587,49 @@ export type ShouldRevalidateFn<TParams = GenericParams, TEnv = any> = (args: {
|
|
|
570
587
|
* relative to the project root, followed by `#` and the exported function name.
|
|
571
588
|
*
|
|
572
589
|
* This is stable and can be used for path-based matching to revalidate
|
|
573
|
-
* 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):
|
|
574
593
|
*
|
|
575
594
|
* @example
|
|
576
595
|
* ```ts
|
|
577
596
|
* // Match a specific action
|
|
578
|
-
* revalidate(({ actionId }) => actionId === "src/actions/cart.ts#addToCart")
|
|
597
|
+
* revalidate(({ actionId }) => actionId === "src/actions/cart.ts#addToCart" || undefined)
|
|
579
598
|
*
|
|
580
599
|
* // Match any action in the cart module
|
|
581
|
-
* revalidate(({ actionId }) => actionId?.includes("cart")
|
|
600
|
+
* revalidate(({ actionId }) => actionId?.includes("cart") || undefined)
|
|
582
601
|
*
|
|
583
602
|
* // Match any action under src/apps/store/actions/
|
|
584
|
-
* revalidate(({ actionId }) => actionId?.startsWith("src/apps/store/actions/")
|
|
603
|
+
* revalidate(({ actionId }) => actionId?.startsWith("src/apps/store/actions/") || undefined)
|
|
585
604
|
* ```
|
|
586
605
|
*/
|
|
587
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;
|
|
588
633
|
/** URL where the action was executed (the page the user was on when they triggered the action). */
|
|
589
634
|
actionUrl?: URL;
|
|
590
635
|
/** Return value from the action execution. Can be used to conditionally revalidate based on the action's outcome. */
|
|
@@ -725,7 +770,7 @@ export type Revalidate<
|
|
|
725
770
|
* Middleware function with typed params and environment
|
|
726
771
|
*
|
|
727
772
|
* @template TParams - Params object (defaults to generic)
|
|
728
|
-
* @template TEnv - Environment type (defaults to global
|
|
773
|
+
* @template TEnv - Environment type (defaults to global Rango.Env)
|
|
729
774
|
*
|
|
730
775
|
* Note: Middleware cannot directly use route names for params typing because
|
|
731
776
|
* middleware is defined during router setup, before RegisteredRoutes is populated.
|
|
@@ -733,7 +778,7 @@ export type Revalidate<
|
|
|
733
778
|
*
|
|
734
779
|
* @example
|
|
735
780
|
* ```typescript
|
|
736
|
-
* // Basic middleware (uses global
|
|
781
|
+
* // Basic middleware (uses global Rango.Env via module augmentation)
|
|
737
782
|
* const middleware: Middleware = async (ctx, next) => {
|
|
738
783
|
* ctx.set("user", { id: "123" }); // Type-safe!
|
|
739
784
|
* await next();
|
package/src/types/index.ts
CHANGED
package/src/types/segments.ts
CHANGED
|
@@ -10,7 +10,10 @@ export type ViewTransitionClass = Record<string, string> | string;
|
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Configuration for React's <ViewTransition> component.
|
|
13
|
-
*
|
|
13
|
+
*
|
|
14
|
+
* The phase fields (enter/exit/update/share/default/name) map directly to
|
|
15
|
+
* ViewTransitionProps (minus children/ref/callbacks). The `viewTransition`
|
|
16
|
+
* field is router-specific and is stripped before the config reaches React.
|
|
14
17
|
*/
|
|
15
18
|
export interface TransitionConfig {
|
|
16
19
|
enter?: ViewTransitionClass;
|
|
@@ -19,6 +22,20 @@ export interface TransitionConfig {
|
|
|
19
22
|
share?: ViewTransitionClass;
|
|
20
23
|
default?: ViewTransitionClass;
|
|
21
24
|
name?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Whether the router wraps this segment's content in its own
|
|
27
|
+
* <ViewTransition> boundary.
|
|
28
|
+
*
|
|
29
|
+
* - "auto" (default): the router places the boundary, producing the
|
|
30
|
+
* router-owned cross-fade described by the phase fields above.
|
|
31
|
+
* - false: the router places no boundary. The navigation commit is still
|
|
32
|
+
* driven through startTransition (so loaders hold instead of flashing a
|
|
33
|
+
* skeleton, and consumer-placed <ViewTransition> elements still animate),
|
|
34
|
+
* but the router contributes no cross-fade of its own.
|
|
35
|
+
*
|
|
36
|
+
* When unset, inherits the createRouter({ viewTransition }) default.
|
|
37
|
+
*/
|
|
38
|
+
viewTransition?: "auto" | false;
|
|
22
39
|
}
|
|
23
40
|
|
|
24
41
|
/**
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import type { AllUseItems, IncludeItem } from "../route-types.js";
|
|
2
2
|
import {
|
|
3
|
-
getContext,
|
|
4
|
-
runWithPrefixes,
|
|
5
3
|
getUrlPrefix,
|
|
6
4
|
getNamePrefix,
|
|
5
|
+
requireDslContext,
|
|
7
6
|
} from "../server/context";
|
|
8
7
|
import {
|
|
9
8
|
INTERNAL_INCLUDE_SCOPE_PREFIX,
|
|
@@ -26,28 +25,10 @@ function allocateInternalIncludeScopeId(
|
|
|
26
25
|
}
|
|
27
26
|
|
|
28
27
|
/**
|
|
29
|
-
*
|
|
30
|
-
* This expands the include into actual route registrations
|
|
31
|
-
*/
|
|
32
|
-
function processIncludeItem(item: IncludeItem): AllUseItems[] {
|
|
33
|
-
const { prefix, patterns } = item;
|
|
34
|
-
const namePrefix =
|
|
35
|
-
(item as IncludeItem & { _lazyContext?: { namePrefix?: string } })
|
|
36
|
-
._lazyContext?.namePrefix ?? item.options?.name;
|
|
37
|
-
|
|
38
|
-
// Execute the nested patterns' handler with URL and name prefixes
|
|
39
|
-
// The urlPrefix being set tells nested urls() to skip RootLayout wrapping
|
|
40
|
-
return runWithPrefixes(prefix, namePrefix, () => {
|
|
41
|
-
// Call the nested patterns' handler - this registers routes with prefixed patterns/names
|
|
42
|
-
return (patterns as UrlPatterns).handler();
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Recursively process items, expanding any IncludeItems
|
|
48
|
-
* Returns items with IncludeItems expanded into actual route items
|
|
28
|
+
* Recursively walk items, recursing into layout children.
|
|
49
29
|
*
|
|
50
|
-
*
|
|
30
|
+
* All includes are lazy and kept as-is; the router expands them on the first
|
|
31
|
+
* matching request.
|
|
51
32
|
*/
|
|
52
33
|
export function processItems(items: readonly AllUseItems[]): AllUseItems[] {
|
|
53
34
|
const result: AllUseItems[] = [];
|
|
@@ -56,26 +37,8 @@ export function processItems(items: readonly AllUseItems[]): AllUseItems[] {
|
|
|
56
37
|
if (!item) continue;
|
|
57
38
|
|
|
58
39
|
if (item.type === "include") {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
lazy?: boolean;
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
// Lazy includes are NOT expanded here - kept for router to handle
|
|
65
|
-
if (includeItem.lazy) {
|
|
66
|
-
result.push(item);
|
|
67
|
-
continue;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Eager includes are already expanded during include() call
|
|
71
|
-
if (includeItem._expanded) {
|
|
72
|
-
// Items were expanded immediately - just process them recursively
|
|
73
|
-
result.push(...processItems(includeItem._expanded));
|
|
74
|
-
} else {
|
|
75
|
-
// Fallback for legacy include items without _expanded
|
|
76
|
-
const expanded = processIncludeItem(item as IncludeItem);
|
|
77
|
-
result.push(...processItems(expanded));
|
|
78
|
-
}
|
|
40
|
+
// All includes are lazy; the router expands them on first matching request.
|
|
41
|
+
result.push(item);
|
|
79
42
|
} else if (item.type === "layout" && (item as any).uses) {
|
|
80
43
|
// Process nested items in layout
|
|
81
44
|
const layoutItem = item as any;
|
|
@@ -92,13 +55,9 @@ export function processItems(items: readonly AllUseItems[]): AllUseItems[] {
|
|
|
92
55
|
/**
|
|
93
56
|
* Create include() helper for composing URL patterns
|
|
94
57
|
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
* With `lazy: true`, patterns are NOT expanded at definition time. Instead,
|
|
100
|
-
* they're evaluated on first request that matches the prefix. This improves
|
|
101
|
-
* cold start time for apps with many routes.
|
|
58
|
+
* All includes are lazy: the nested patterns are NOT expanded at definition
|
|
59
|
+
* time. Instead they are evaluated on the first request that matches the
|
|
60
|
+
* prefix, which improves cold start time for apps with many routes.
|
|
102
61
|
*/
|
|
103
62
|
export function createIncludeHelper<TEnv>(): IncludeFn<TEnv> {
|
|
104
63
|
return (
|
|
@@ -106,9 +65,7 @@ export function createIncludeHelper<TEnv>(): IncludeFn<TEnv> {
|
|
|
106
65
|
patterns: UrlPatterns<TEnv>,
|
|
107
66
|
options?: IncludeOptions,
|
|
108
67
|
): IncludeItem => {
|
|
109
|
-
const
|
|
110
|
-
const ctx = store.getStore();
|
|
111
|
-
if (!ctx) throw new Error("include() must be called inside urls()");
|
|
68
|
+
const { ctx } = requireDslContext("include() must be called inside urls()");
|
|
112
69
|
|
|
113
70
|
const explicitName = options?.name;
|
|
114
71
|
const hasExplicitName = hasExplicitNameOption(options);
|
package/src/urls/index.ts
CHANGED
|
@@ -13,7 +13,6 @@ export type {
|
|
|
13
13
|
UnnamedRoute,
|
|
14
14
|
LocalOnlyInclude,
|
|
15
15
|
PathOptions,
|
|
16
|
-
PathDefinition,
|
|
17
16
|
UrlPatterns,
|
|
18
17
|
IncludeOptions,
|
|
19
18
|
} from "./pattern-types.js";
|
|
@@ -22,8 +21,6 @@ export type {
|
|
|
22
21
|
export type {
|
|
23
22
|
ExtractRoutes,
|
|
24
23
|
ExtractResponses,
|
|
25
|
-
ExtractRouteNames,
|
|
26
|
-
ExtractPathParams,
|
|
27
24
|
ResponseError,
|
|
28
25
|
ResponseEnvelope,
|
|
29
26
|
RouteResponse,
|
|
@@ -264,7 +264,7 @@ export type PathHelpers<TEnv> = {
|
|
|
264
264
|
* Define an intercepting route for soft navigation
|
|
265
265
|
* Note: routeName must match a named path() in this urlpatterns
|
|
266
266
|
*/
|
|
267
|
-
intercept: keyof
|
|
267
|
+
intercept: keyof Rango.GeneratedRouteMap extends never
|
|
268
268
|
? (
|
|
269
269
|
slotName: `@${string}`,
|
|
270
270
|
routeName: string,
|
|
@@ -273,7 +273,7 @@ export type PathHelpers<TEnv> = {
|
|
|
273
273
|
) => InterceptItem
|
|
274
274
|
: (
|
|
275
275
|
slotName: `@${string}`,
|
|
276
|
-
routeName: (keyof
|
|
276
|
+
routeName: (keyof Rango.GeneratedRouteMap & string) | `.${string}`,
|
|
277
277
|
handler: ReactNode | Handler<any, any, TEnv>,
|
|
278
278
|
use?: () => InterceptUseItem[],
|
|
279
279
|
) => InterceptItem;
|
|
@@ -350,7 +350,15 @@ export type PathHelpers<TEnv> = {
|
|
|
350
350
|
};
|
|
351
351
|
|
|
352
352
|
/**
|
|
353
|
-
*
|
|
353
|
+
* Opt a route (or group of routes) into transition-driven navigation.
|
|
354
|
+
*
|
|
355
|
+
* Two independent layers: (1) startTransition, on all React versions, holds
|
|
356
|
+
* the previous content across a same-route nav (no skeleton flash) and is the
|
|
357
|
+
* precondition for any view transition; (2) on experimental React, an
|
|
358
|
+
* additional `<ViewTransition>` boundary cross-fades/morphs the swap. Pass
|
|
359
|
+
* `{ viewTransition: false }` to keep #1 without the router boundary. A view
|
|
360
|
+
* transition cannot fire without a startTransition. See
|
|
361
|
+
* skills/view-transitions for the startTransition x ViewTransition matrix.
|
|
354
362
|
*/
|
|
355
363
|
transition: {
|
|
356
364
|
(): TransitionItem;
|