@rangojs/router 0.0.0-experimental.19 → 0.0.0-experimental.1fa245e2

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.
Files changed (160) hide show
  1. package/{CLAUDE.md → AGENTS.md} +4 -0
  2. package/README.md +122 -30
  3. package/dist/bin/rango.js +245 -63
  4. package/dist/vite/index.js +859 -418
  5. package/package.json +3 -3
  6. package/skills/breadcrumbs/SKILL.md +250 -0
  7. package/skills/cache-guide/SKILL.md +32 -0
  8. package/skills/caching/SKILL.md +49 -8
  9. package/skills/document-cache/SKILL.md +2 -2
  10. package/skills/hooks/SKILL.md +33 -31
  11. package/skills/host-router/SKILL.md +218 -0
  12. package/skills/links/SKILL.md +3 -1
  13. package/skills/loader/SKILL.md +72 -22
  14. package/skills/middleware/SKILL.md +2 -0
  15. package/skills/parallel/SKILL.md +126 -0
  16. package/skills/prerender/SKILL.md +112 -70
  17. package/skills/rango/SKILL.md +0 -1
  18. package/skills/route/SKILL.md +34 -4
  19. package/skills/router-setup/SKILL.md +95 -5
  20. package/skills/typesafety/SKILL.md +35 -23
  21. package/src/__internal.ts +92 -0
  22. package/src/bin/rango.ts +18 -0
  23. package/src/browser/app-version.ts +14 -0
  24. package/src/browser/event-controller.ts +5 -0
  25. package/src/browser/link-interceptor.ts +4 -0
  26. package/src/browser/navigation-bridge.ts +114 -18
  27. package/src/browser/navigation-client.ts +126 -44
  28. package/src/browser/navigation-store.ts +43 -8
  29. package/src/browser/navigation-transaction.ts +11 -9
  30. package/src/browser/partial-update.ts +80 -15
  31. package/src/browser/prefetch/cache.ts +166 -27
  32. package/src/browser/prefetch/fetch.ts +52 -39
  33. package/src/browser/prefetch/policy.ts +6 -0
  34. package/src/browser/prefetch/queue.ts +92 -20
  35. package/src/browser/prefetch/resource-ready.ts +77 -0
  36. package/src/browser/react/Link.tsx +70 -14
  37. package/src/browser/react/NavigationProvider.tsx +40 -4
  38. package/src/browser/react/context.ts +7 -2
  39. package/src/browser/react/use-handle.ts +9 -58
  40. package/src/browser/react/use-router.ts +21 -8
  41. package/src/browser/rsc-router.tsx +143 -59
  42. package/src/browser/scroll-restoration.ts +41 -42
  43. package/src/browser/segment-reconciler.ts +6 -1
  44. package/src/browser/server-action-bridge.ts +454 -436
  45. package/src/browser/types.ts +60 -5
  46. package/src/build/generate-manifest.ts +6 -6
  47. package/src/build/generate-route-types.ts +5 -0
  48. package/src/build/route-trie.ts +19 -3
  49. package/src/build/route-types/include-resolution.ts +8 -1
  50. package/src/build/route-types/router-processing.ts +346 -87
  51. package/src/build/route-types/scan-filter.ts +8 -1
  52. package/src/cache/cache-runtime.ts +15 -11
  53. package/src/cache/cache-scope.ts +48 -7
  54. package/src/cache/cf/cf-cache-store.ts +453 -11
  55. package/src/cache/cf/index.ts +5 -1
  56. package/src/cache/document-cache.ts +17 -7
  57. package/src/cache/index.ts +1 -0
  58. package/src/cache/taint.ts +55 -0
  59. package/src/client.rsc.tsx +2 -1
  60. package/src/client.tsx +3 -102
  61. package/src/context-var.ts +72 -2
  62. package/src/debug.ts +2 -2
  63. package/src/handle.ts +40 -0
  64. package/src/handles/breadcrumbs.ts +66 -0
  65. package/src/handles/index.ts +1 -0
  66. package/src/host/index.ts +0 -3
  67. package/src/index.rsc.ts +8 -37
  68. package/src/index.ts +40 -66
  69. package/src/prerender/store.ts +57 -15
  70. package/src/prerender.ts +138 -77
  71. package/src/reverse.ts +22 -1
  72. package/src/route-definition/dsl-helpers.ts +73 -25
  73. package/src/route-definition/helpers-types.ts +10 -6
  74. package/src/route-definition/index.ts +3 -3
  75. package/src/route-definition/redirect.ts +11 -3
  76. package/src/route-definition/resolve-handler-use.ts +149 -0
  77. package/src/route-map-builder.ts +7 -1
  78. package/src/route-types.ts +11 -0
  79. package/src/router/content-negotiation.ts +100 -1
  80. package/src/router/find-match.ts +4 -2
  81. package/src/router/handler-context.ts +108 -25
  82. package/src/router/intercept-resolution.ts +11 -4
  83. package/src/router/lazy-includes.ts +4 -1
  84. package/src/router/loader-resolution.ts +123 -11
  85. package/src/router/logging.ts +5 -2
  86. package/src/router/manifest.ts +9 -3
  87. package/src/router/match-api.ts +125 -190
  88. package/src/router/match-middleware/background-revalidation.ts +30 -2
  89. package/src/router/match-middleware/cache-lookup.ts +88 -16
  90. package/src/router/match-middleware/cache-store.ts +53 -10
  91. package/src/router/match-middleware/intercept-resolution.ts +9 -7
  92. package/src/router/match-middleware/segment-resolution.ts +61 -5
  93. package/src/router/match-result.ts +22 -15
  94. package/src/router/metrics.ts +238 -13
  95. package/src/router/middleware-types.ts +53 -12
  96. package/src/router/middleware.ts +172 -85
  97. package/src/router/navigation-snapshot.ts +182 -0
  98. package/src/router/pattern-matching.ts +20 -5
  99. package/src/router/prerender-match.ts +114 -10
  100. package/src/router/preview-match.ts +30 -102
  101. package/src/router/request-classification.ts +310 -0
  102. package/src/router/revalidation.ts +27 -7
  103. package/src/router/route-snapshot.ts +245 -0
  104. package/src/router/router-context.ts +6 -1
  105. package/src/router/router-interfaces.ts +50 -5
  106. package/src/router/router-options.ts +50 -19
  107. package/src/router/segment-resolution/fresh.ts +200 -19
  108. package/src/router/segment-resolution/helpers.ts +30 -25
  109. package/src/router/segment-resolution/loader-cache.ts +1 -0
  110. package/src/router/segment-resolution/revalidation.ts +429 -301
  111. package/src/router/segment-wrappers.ts +2 -0
  112. package/src/router/trie-matching.ts +20 -2
  113. package/src/router/types.ts +1 -0
  114. package/src/router.ts +88 -15
  115. package/src/rsc/handler.ts +546 -359
  116. package/src/rsc/index.ts +0 -20
  117. package/src/rsc/manifest-init.ts +5 -1
  118. package/src/rsc/progressive-enhancement.ts +25 -8
  119. package/src/rsc/rsc-rendering.ts +35 -43
  120. package/src/rsc/server-action.ts +16 -10
  121. package/src/rsc/ssr-setup.ts +128 -0
  122. package/src/rsc/types.ts +10 -1
  123. package/src/search-params.ts +16 -13
  124. package/src/segment-system.tsx +140 -4
  125. package/src/server/context.ts +148 -16
  126. package/src/server/loader-registry.ts +9 -8
  127. package/src/server/request-context.ts +182 -34
  128. package/src/server.ts +6 -0
  129. package/src/ssr/index.tsx +4 -0
  130. package/src/static-handler.ts +18 -6
  131. package/src/theme/index.ts +4 -13
  132. package/src/types/cache-types.ts +4 -4
  133. package/src/types/handler-context.ts +149 -49
  134. package/src/types/loader-types.ts +36 -9
  135. package/src/types/route-config.ts +17 -8
  136. package/src/types/route-entry.ts +8 -1
  137. package/src/types/segments.ts +2 -5
  138. package/src/urls/path-helper-types.ts +9 -2
  139. package/src/urls/path-helper.ts +48 -13
  140. package/src/urls/pattern-types.ts +12 -0
  141. package/src/urls/response-types.ts +16 -6
  142. package/src/use-loader.tsx +73 -4
  143. package/src/vite/discovery/bundle-postprocess.ts +61 -89
  144. package/src/vite/discovery/discover-routers.ts +23 -5
  145. package/src/vite/discovery/prerender-collection.ts +48 -15
  146. package/src/vite/discovery/state.ts +17 -13
  147. package/src/vite/index.ts +8 -3
  148. package/src/vite/plugin-types.ts +51 -79
  149. package/src/vite/plugins/client-ref-dedup.ts +115 -0
  150. package/src/vite/plugins/expose-action-id.ts +1 -3
  151. package/src/vite/plugins/performance-tracks.ts +88 -0
  152. package/src/vite/plugins/refresh-cmd.ts +127 -0
  153. package/src/vite/plugins/version-plugin.ts +13 -1
  154. package/src/vite/rango.ts +174 -211
  155. package/src/vite/router-discovery.ts +169 -42
  156. package/src/vite/utils/banner.ts +3 -3
  157. package/src/vite/utils/prerender-utils.ts +78 -0
  158. package/src/vite/utils/shared-utils.ts +3 -2
  159. package/skills/testing/SKILL.md +0 -226
  160. package/src/route-definition/route-function.ts +0 -119
@@ -1,226 +0,0 @@
1
- ---
2
- name: testing
3
- description: Unit test route trees with buildRouteTree()
4
- argument-hint:
5
- ---
6
-
7
- # Route Tree Unit Testing
8
-
9
- Unit test route definitions by inspecting the route tree, segment IDs, middleware, intercepts, loaders, and pattern matching without running a dev server.
10
-
11
- ## Setup
12
-
13
- The `buildRouteTree` helper lives in `src/__tests__/helpers/route-tree.ts` (not shipped with npm). Import it in your test files:
14
-
15
- ```typescript
16
- import { buildRouteTree } from "./helpers/route-tree.js";
17
- ```
18
-
19
- ## buildRouteTree(urlPatterns)
20
-
21
- Takes a `urls()` result and returns a `RouteTree` with inspection methods:
22
-
23
- ```typescript
24
- import { urls } from "@rangojs/router";
25
- import { buildRouteTree } from "./helpers/route-tree.js";
26
-
27
- const tree = buildRouteTree(
28
- urls(({ path, layout, middleware, loader, intercept, when }) => [
29
- layout(RootLayout, () => [
30
- middleware(authMiddleware),
31
- path("/", HomePage, { name: "home" }),
32
- path("/blog/:slug", BlogPost, { name: "blog.post" }, () => [
33
- loader(PostLoader),
34
- ]),
35
- ]),
36
- ]),
37
- );
38
- ```
39
-
40
- ## RouteTree API
41
-
42
- ### Route Patterns
43
-
44
- ```typescript
45
- tree.routes(); // { home: "/", "blog.post": "/blog/:slug" }
46
- tree.routeNames(); // ["home", "blog.post"]
47
- ```
48
-
49
- ### URL Matching
50
-
51
- ```typescript
52
- const m = tree.match("/blog/hello");
53
- m.routeKey; // "blog.post"
54
- m.params; // { slug: "hello" }
55
-
56
- tree.match("/nonexistent"); // null
57
- ```
58
-
59
- ### Segment IDs
60
-
61
- ```typescript
62
- tree.segmentId("home"); // "M0L0L0R0"
63
- tree.segmentIds(); // { home: "M0L0L0R0", "blog.post": "M0L0L0R1" }
64
- tree.segmentPath("blog.post");
65
- // [
66
- // { id: "M0L0", type: "layout" }, // synthetic root
67
- // { id: "M0L0L0", type: "layout" }, // RootLayout
68
- // { id: "M0L0L0R1", type: "route", pattern: "/blog/:slug" },
69
- // ]
70
- ```
71
-
72
- ### Entry Access
73
-
74
- ```typescript
75
- tree.entry("blog.post"); // EntryData
76
- tree.entry("blog.post")!.parent!.type; // "layout"
77
- tree.entryByPattern("/blog/:slug"); // EntryData (lookup by URL pattern)
78
- ```
79
-
80
- ### Middleware
81
-
82
- ```typescript
83
- tree.hasMiddleware("home"); // true (inherited from layout)
84
- tree.middleware("home"); // [authMiddleware] (direct only)
85
- tree.middlewareChain("home");
86
- // [{ segmentId: "M0L0L0", count: 1 }] // all middleware root-to-route
87
- ```
88
-
89
- ### Loaders
90
-
91
- ```typescript
92
- tree.hasLoaders("blog.post"); // true
93
- tree.loaders("blog.post"); // [LoaderEntry { loader, revalidate, cache? }]
94
- ```
95
-
96
- ### Intercepts
97
-
98
- ```typescript
99
- tree.intercepts("home");
100
- // [{ slotName: "@modal", routeName: "card", hasWhen: true, whenCount: 1, hasLoader: false, hasMiddleware: false }]
101
- tree.interceptEntries("home"); // raw InterceptEntry[]
102
- ```
103
-
104
- ### Parallel Slots
105
-
106
- ```typescript
107
- tree.parallelSlots("home"); // EntryData[] of type="parallel"
108
- tree.parallelSlotNames("home"); // ["@sidebar", "@main"]
109
- ```
110
-
111
- ### Boundaries
112
-
113
- ```typescript
114
- tree.hasErrorBoundary("home"); // boolean
115
- tree.hasNotFoundBoundary("home"); // boolean
116
- ```
117
-
118
- ### Cache & Loading
119
-
120
- ```typescript
121
- tree.hasCache("home"); // boolean
122
- tree.hasLoading("home"); // boolean
123
- ```
124
-
125
- ### Debug
126
-
127
- ```typescript
128
- console.log(tree.debug());
129
- // Route Tree:
130
- // home: / [M0L0L0R0] (M0L0 > M0L0L0 > M0L0L0R0) {mw:1}
131
- // blog.post: /blog/:slug [M0L0L0R1] (M0L0 > M0L0L0 > M0L0L0R1) {mw:1, ld:1}
132
- ```
133
-
134
- ## Segment ID Format
135
-
136
- | Prefix | Meaning |
137
- | ------ | ----------------------------- |
138
- | `M0` | Mount index (router instance) |
139
- | `L` | Layout |
140
- | `R` | Route |
141
- | `P` | Parallel slot |
142
- | `D` | Loader (data) |
143
- | `C` | Cache boundary |
144
-
145
- Example: `M0L0L0R1` = mount 0, synthetic root layout, user layout, second route.
146
-
147
- ## Examples
148
-
149
- ### include() with prefix
150
-
151
- ```typescript
152
- const blogPatterns = urls(({ path }) => [
153
- path("/", BlogIndex, { name: "index" }),
154
- path("/:slug", BlogPost, { name: "post" }),
155
- ]);
156
-
157
- const tree = buildRouteTree(
158
- urls(({ path, include }) => [
159
- path("/", HomePage, { name: "home" }),
160
- include("/blog", blogPatterns, { name: "blog" }),
161
- ]),
162
- );
163
-
164
- expect(tree.routes()).toEqual({
165
- home: "/",
166
- "blog.index": "/blog",
167
- "blog.post": "/blog/:slug",
168
- });
169
- ```
170
-
171
- ### Middleware chain
172
-
173
- ```typescript
174
- const authMw = async (ctx, next) => next();
175
- const logMw = async (ctx, next) => next();
176
-
177
- const tree = buildRouteTree(
178
- urls(({ path, layout, middleware }) => [
179
- layout(RootLayout, () => [
180
- middleware(logMw),
181
- layout(AuthLayout, () => [
182
- middleware(authMw),
183
- path("/dashboard", Dashboard, { name: "dashboard" }),
184
- ]),
185
- ]),
186
- ]),
187
- );
188
-
189
- expect(tree.middlewareChain("dashboard")).toEqual([
190
- { segmentId: "M0L0L0", count: 1 }, // logMw on RootLayout
191
- { segmentId: "M0L0L0L0", count: 1 }, // authMw on AuthLayout
192
- ]);
193
- ```
194
-
195
- ### Intercepts with when()
196
-
197
- ```typescript
198
- const tree = buildRouteTree(
199
- urls(({ path, layout, intercept, when }) => [
200
- layout(ShopLayout, () => [
201
- path("/products", ProductList, { name: "products" }),
202
- path("/products/:id", ProductDetail, { name: "product.detail" }),
203
- intercept("@modal", "product.detail", ProductModal, () => [
204
- when((ctx) => ctx.from.pathname.startsWith("/products")),
205
- ]),
206
- ]),
207
- ]),
208
- );
209
-
210
- const intercepts = tree.intercepts("products");
211
- // Note: intercepts are on the parent where intercept() is called
212
- ```
213
-
214
- ### Constrained parameters
215
-
216
- ```typescript
217
- const tree = buildRouteTree(
218
- urls(({ path }) => [
219
- path("/:locale(en|fr)?/about", AboutPage, { name: "about" }),
220
- ]),
221
- );
222
-
223
- expect(tree.match("/about")).not.toBeNull();
224
- expect(tree.match("/fr/about")!.params).toEqual({ locale: "fr" });
225
- expect(tree.match("/de/about")).toBeNull();
226
- ```
@@ -1,119 +0,0 @@
1
- import type {
2
- ResolvedRouteMap,
3
- RouteConfig,
4
- RouteDefinition,
5
- RouteDefinitionOptions,
6
- TrailingSlashMode,
7
- } from "../types.js";
8
-
9
- /**
10
- * Result of route() function with paths and trailing slash config
11
- */
12
- export interface RouteDefinitionResult<T extends RouteDefinition> {
13
- routes: ResolvedRouteMap<T>;
14
- trailingSlash: Record<string, TrailingSlashMode>;
15
- }
16
-
17
- /**
18
- * Check if a value is a RouteConfig object
19
- */
20
- function isRouteConfig(value: unknown): value is RouteConfig {
21
- return (
22
- typeof value === "object" &&
23
- value !== null &&
24
- "path" in value &&
25
- typeof (value as RouteConfig).path === "string"
26
- );
27
- }
28
-
29
- /**
30
- * Define routes with optional trailing slash configuration
31
- *
32
- * @example
33
- * ```typescript
34
- * // Simple string paths
35
- * const routes = route({
36
- * blog: "/blog",
37
- * post: "/blog/:id",
38
- * });
39
- *
40
- * // With trailing slash config
41
- * const routes = route({
42
- * blog: "/blog",
43
- * api: { path: "/api", trailingSlash: "ignore" },
44
- * }, { trailingSlash: "never" }); // global default
45
- * ```
46
- */
47
- export function route<const T extends RouteDefinition>(
48
- input: T,
49
- options?: RouteDefinitionOptions,
50
- ): ResolvedRouteMap<T> & {
51
- __trailingSlash?: Record<string, TrailingSlashMode>;
52
- } {
53
- const trailingSlash: Record<string, TrailingSlashMode> = {};
54
- const routes = flattenRoutes(
55
- input as RouteDefinition,
56
- "",
57
- trailingSlash,
58
- options?.trailingSlash,
59
- );
60
-
61
- // Attach trailing slash config as a non-enumerable property
62
- // This keeps backwards compatibility while passing the config through
63
- const result = routes as ResolvedRouteMap<T> & {
64
- __trailingSlash?: Record<string, TrailingSlashMode>;
65
- };
66
- if (Object.keys(trailingSlash).length > 0) {
67
- Object.defineProperty(result, "__trailingSlash", {
68
- value: trailingSlash,
69
- enumerable: false,
70
- writable: false,
71
- });
72
- }
73
-
74
- return result;
75
- }
76
-
77
- /**
78
- * Flatten nested route definitions
79
- */
80
- function flattenRoutes(
81
- routes: RouteDefinition,
82
- prefix: string,
83
- trailingSlashConfig: Record<string, TrailingSlashMode>,
84
- defaultTrailingSlash?: TrailingSlashMode,
85
- ): Record<string, string> {
86
- const flattened: Record<string, string> = {};
87
-
88
- for (const [key, value] of Object.entries(routes)) {
89
- const fullKey = prefix + key;
90
-
91
- if (typeof value === "string") {
92
- // Direct route pattern - include prefix
93
- flattened[fullKey] = value;
94
- // Apply default trailing slash if set
95
- if (defaultTrailingSlash) {
96
- trailingSlashConfig[fullKey] = defaultTrailingSlash;
97
- }
98
- } else if (isRouteConfig(value)) {
99
- // Route config object with path and optional trailingSlash
100
- flattened[fullKey] = value.path;
101
- // Use route-specific config or fall back to default
102
- const mode = value.trailingSlash ?? defaultTrailingSlash;
103
- if (mode) {
104
- trailingSlashConfig[fullKey] = mode;
105
- }
106
- } else {
107
- // Nested routes - flatten recursively
108
- const nested = flattenRoutes(
109
- value,
110
- `${fullKey}.`,
111
- trailingSlashConfig,
112
- defaultTrailingSlash,
113
- );
114
- Object.assign(flattened, nested);
115
- }
116
- }
117
-
118
- return flattened;
119
- }