@rangojs/router 0.0.0-experimental.8 → 0.0.0-experimental.80

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 (312) hide show
  1. package/AGENTS.md +9 -0
  2. package/README.md +942 -4
  3. package/dist/bin/rango.js +1689 -0
  4. package/dist/vite/index.js +4960 -935
  5. package/package.json +70 -60
  6. package/skills/breadcrumbs/SKILL.md +250 -0
  7. package/skills/cache-guide/SKILL.md +294 -0
  8. package/skills/caching/SKILL.md +93 -23
  9. package/skills/composability/SKILL.md +172 -0
  10. package/skills/debug-manifest/SKILL.md +12 -8
  11. package/skills/document-cache/SKILL.md +18 -16
  12. package/skills/fonts/SKILL.md +167 -0
  13. package/skills/handler-use/SKILL.md +362 -0
  14. package/skills/hooks/SKILL.md +334 -72
  15. package/skills/host-router/SKILL.md +218 -0
  16. package/skills/intercept/SKILL.md +151 -8
  17. package/skills/layout/SKILL.md +122 -3
  18. package/skills/links/SKILL.md +92 -31
  19. package/skills/loader/SKILL.md +404 -44
  20. package/skills/middleware/SKILL.md +205 -37
  21. package/skills/migrate-nextjs/SKILL.md +560 -0
  22. package/skills/migrate-react-router/SKILL.md +764 -0
  23. package/skills/mime-routes/SKILL.md +128 -0
  24. package/skills/parallel/SKILL.md +263 -1
  25. package/skills/prerender/SKILL.md +685 -0
  26. package/skills/rango/SKILL.md +87 -16
  27. package/skills/response-routes/SKILL.md +411 -0
  28. package/skills/route/SKILL.md +281 -14
  29. package/skills/router-setup/SKILL.md +210 -32
  30. package/skills/tailwind/SKILL.md +129 -0
  31. package/skills/theme/SKILL.md +9 -8
  32. package/skills/typesafety/SKILL.md +328 -89
  33. package/skills/use-cache/SKILL.md +324 -0
  34. package/src/__internal.ts +102 -4
  35. package/src/bin/rango.ts +321 -0
  36. package/src/browser/action-coordinator.ts +97 -0
  37. package/src/browser/action-response-classifier.ts +99 -0
  38. package/src/browser/app-version.ts +14 -0
  39. package/src/browser/event-controller.ts +92 -64
  40. package/src/browser/history-state.ts +80 -0
  41. package/src/browser/intercept-utils.ts +52 -0
  42. package/src/browser/link-interceptor.ts +24 -4
  43. package/src/browser/logging.ts +55 -0
  44. package/src/browser/merge-segment-loaders.ts +20 -12
  45. package/src/browser/navigation-bridge.ts +317 -560
  46. package/src/browser/navigation-client.ts +206 -68
  47. package/src/browser/navigation-store.ts +73 -55
  48. package/src/browser/navigation-transaction.ts +297 -0
  49. package/src/browser/network-error-handler.ts +61 -0
  50. package/src/browser/partial-update.ts +343 -316
  51. package/src/browser/prefetch/cache.ts +216 -0
  52. package/src/browser/prefetch/fetch.ts +206 -0
  53. package/src/browser/prefetch/observer.ts +65 -0
  54. package/src/browser/prefetch/policy.ts +48 -0
  55. package/src/browser/prefetch/queue.ts +160 -0
  56. package/src/browser/prefetch/resource-ready.ts +77 -0
  57. package/src/browser/rango-state.ts +112 -0
  58. package/src/browser/react/Link.tsx +253 -74
  59. package/src/browser/react/NavigationProvider.tsx +87 -11
  60. package/src/browser/react/context.ts +11 -0
  61. package/src/browser/react/filter-segment-order.ts +11 -0
  62. package/src/browser/react/index.ts +12 -12
  63. package/src/browser/react/location-state-shared.ts +95 -53
  64. package/src/browser/react/location-state.ts +60 -15
  65. package/src/browser/react/mount-context.ts +6 -1
  66. package/src/browser/react/nonce-context.ts +23 -0
  67. package/src/browser/react/shallow-equal.ts +27 -0
  68. package/src/browser/react/use-action.ts +29 -51
  69. package/src/browser/react/use-client-cache.ts +5 -3
  70. package/src/browser/react/use-handle.ts +30 -126
  71. package/src/browser/react/use-href.tsx +2 -2
  72. package/src/browser/react/use-link-status.ts +6 -5
  73. package/src/browser/react/use-navigation.ts +44 -65
  74. package/src/browser/react/use-params.ts +65 -0
  75. package/src/browser/react/use-pathname.ts +47 -0
  76. package/src/browser/react/use-router.ts +76 -0
  77. package/src/browser/react/use-search-params.ts +56 -0
  78. package/src/browser/react/use-segments.ts +80 -97
  79. package/src/browser/response-adapter.ts +73 -0
  80. package/src/browser/rsc-router.tsx +214 -58
  81. package/src/browser/scroll-restoration.ts +127 -52
  82. package/src/browser/segment-reconciler.ts +243 -0
  83. package/src/browser/segment-structure-assert.ts +16 -0
  84. package/src/browser/server-action-bridge.ts +510 -603
  85. package/src/browser/shallow.ts +6 -1
  86. package/src/browser/types.ts +141 -48
  87. package/src/browser/validate-redirect-origin.ts +29 -0
  88. package/src/build/generate-manifest.ts +235 -24
  89. package/src/build/generate-route-types.ts +39 -0
  90. package/src/build/index.ts +13 -0
  91. package/src/build/route-trie.ts +291 -0
  92. package/src/build/route-types/ast-helpers.ts +25 -0
  93. package/src/build/route-types/ast-route-extraction.ts +98 -0
  94. package/src/build/route-types/codegen.ts +102 -0
  95. package/src/build/route-types/include-resolution.ts +418 -0
  96. package/src/build/route-types/param-extraction.ts +48 -0
  97. package/src/build/route-types/per-module-writer.ts +128 -0
  98. package/src/build/route-types/router-processing.ts +618 -0
  99. package/src/build/route-types/scan-filter.ts +85 -0
  100. package/src/build/runtime-discovery.ts +231 -0
  101. package/src/cache/background-task.ts +34 -0
  102. package/src/cache/cache-key-utils.ts +44 -0
  103. package/src/cache/cache-policy.ts +125 -0
  104. package/src/cache/cache-runtime.ts +342 -0
  105. package/src/cache/cache-scope.ts +167 -309
  106. package/src/cache/cf/cf-cache-store.ts +571 -17
  107. package/src/cache/cf/index.ts +13 -3
  108. package/src/cache/document-cache.ts +116 -77
  109. package/src/cache/handle-capture.ts +81 -0
  110. package/src/cache/handle-snapshot.ts +41 -0
  111. package/src/cache/index.ts +1 -15
  112. package/src/cache/memory-segment-store.ts +191 -13
  113. package/src/cache/profile-registry.ts +73 -0
  114. package/src/cache/read-through-swr.ts +134 -0
  115. package/src/cache/segment-codec.ts +256 -0
  116. package/src/cache/taint.ts +153 -0
  117. package/src/cache/types.ts +72 -122
  118. package/src/client.rsc.tsx +3 -1
  119. package/src/client.tsx +135 -301
  120. package/src/component-utils.ts +4 -4
  121. package/src/components/DefaultDocument.tsx +5 -1
  122. package/src/context-var.ts +156 -0
  123. package/src/debug.ts +19 -9
  124. package/src/errors.ts +108 -2
  125. package/src/handle.ts +55 -29
  126. package/src/handles/MetaTags.tsx +73 -20
  127. package/src/handles/breadcrumbs.ts +66 -0
  128. package/src/handles/index.ts +1 -0
  129. package/src/handles/meta.ts +30 -13
  130. package/src/host/cookie-handler.ts +21 -15
  131. package/src/host/errors.ts +8 -8
  132. package/src/host/index.ts +4 -7
  133. package/src/host/pattern-matcher.ts +27 -27
  134. package/src/host/router.ts +61 -39
  135. package/src/host/testing.ts +8 -8
  136. package/src/host/types.ts +15 -7
  137. package/src/host/utils.ts +1 -1
  138. package/src/href-client.ts +119 -29
  139. package/src/index.rsc.ts +155 -19
  140. package/src/index.ts +251 -30
  141. package/src/internal-debug.ts +11 -0
  142. package/src/loader.rsc.ts +26 -157
  143. package/src/loader.ts +27 -10
  144. package/src/network-error-thrower.tsx +3 -1
  145. package/src/outlet-provider.tsx +45 -0
  146. package/src/prerender/param-hash.ts +37 -0
  147. package/src/prerender/store.ts +186 -0
  148. package/src/prerender.ts +524 -0
  149. package/src/reverse.ts +354 -0
  150. package/src/root-error-boundary.tsx +41 -29
  151. package/src/route-content-wrapper.tsx +7 -4
  152. package/src/route-definition/dsl-helpers.ts +1121 -0
  153. package/src/route-definition/helper-factories.ts +200 -0
  154. package/src/route-definition/helpers-types.ts +478 -0
  155. package/src/route-definition/index.ts +55 -0
  156. package/src/route-definition/redirect.ts +101 -0
  157. package/src/route-definition/resolve-handler-use.ts +149 -0
  158. package/src/route-definition.ts +1 -1428
  159. package/src/route-map-builder.ts +217 -123
  160. package/src/route-name.ts +53 -0
  161. package/src/route-types.ts +77 -8
  162. package/src/router/content-negotiation.ts +215 -0
  163. package/src/router/debug-manifest.ts +72 -0
  164. package/src/router/error-handling.ts +9 -9
  165. package/src/router/find-match.ts +160 -0
  166. package/src/router/handler-context.ts +438 -86
  167. package/src/router/intercept-resolution.ts +402 -0
  168. package/src/router/lazy-includes.ts +237 -0
  169. package/src/router/loader-resolution.ts +356 -128
  170. package/src/router/logging.ts +251 -0
  171. package/src/router/manifest.ts +163 -35
  172. package/src/router/match-api.ts +555 -0
  173. package/src/router/match-context.ts +5 -3
  174. package/src/router/match-handlers.ts +440 -0
  175. package/src/router/match-middleware/background-revalidation.ts +108 -93
  176. package/src/router/match-middleware/cache-lookup.ts +460 -10
  177. package/src/router/match-middleware/cache-store.ts +98 -26
  178. package/src/router/match-middleware/intercept-resolution.ts +57 -17
  179. package/src/router/match-middleware/segment-resolution.ts +80 -6
  180. package/src/router/match-pipelines.ts +10 -45
  181. package/src/router/match-result.ts +135 -35
  182. package/src/router/metrics.ts +240 -15
  183. package/src/router/middleware-cookies.ts +55 -0
  184. package/src/router/middleware-types.ts +220 -0
  185. package/src/router/middleware.ts +324 -369
  186. package/src/router/navigation-snapshot.ts +182 -0
  187. package/src/router/pattern-matching.ts +211 -43
  188. package/src/router/prerender-match.ts +502 -0
  189. package/src/router/preview-match.ts +98 -0
  190. package/src/router/request-classification.ts +310 -0
  191. package/src/router/revalidation.ts +137 -38
  192. package/src/router/route-snapshot.ts +245 -0
  193. package/src/router/router-context.ts +41 -21
  194. package/src/router/router-interfaces.ts +484 -0
  195. package/src/router/router-options.ts +618 -0
  196. package/src/router/router-registry.ts +24 -0
  197. package/src/router/segment-resolution/fresh.ts +748 -0
  198. package/src/router/segment-resolution/helpers.ts +268 -0
  199. package/src/router/segment-resolution/loader-cache.ts +199 -0
  200. package/src/router/segment-resolution/revalidation.ts +1379 -0
  201. package/src/router/segment-resolution/static-store.ts +67 -0
  202. package/src/router/segment-resolution.ts +21 -0
  203. package/src/router/segment-wrappers.ts +291 -0
  204. package/src/router/telemetry-otel.ts +299 -0
  205. package/src/router/telemetry.ts +300 -0
  206. package/src/router/timeout.ts +148 -0
  207. package/src/router/trie-matching.ts +239 -0
  208. package/src/router/types.ts +78 -3
  209. package/src/router.ts +740 -4252
  210. package/src/rsc/handler-context.ts +45 -0
  211. package/src/rsc/handler.ts +907 -797
  212. package/src/rsc/helpers.ts +140 -6
  213. package/src/rsc/index.ts +0 -20
  214. package/src/rsc/loader-fetch.ts +229 -0
  215. package/src/rsc/manifest-init.ts +90 -0
  216. package/src/rsc/nonce.ts +14 -0
  217. package/src/rsc/origin-guard.ts +141 -0
  218. package/src/rsc/progressive-enhancement.ts +391 -0
  219. package/src/rsc/response-error.ts +37 -0
  220. package/src/rsc/response-route-handler.ts +347 -0
  221. package/src/rsc/rsc-rendering.ts +246 -0
  222. package/src/rsc/runtime-warnings.ts +42 -0
  223. package/src/rsc/server-action.ts +356 -0
  224. package/src/rsc/ssr-setup.ts +128 -0
  225. package/src/rsc/types.ts +46 -11
  226. package/src/search-params.ts +230 -0
  227. package/src/segment-content-promise.ts +67 -0
  228. package/src/segment-loader-promise.ts +122 -0
  229. package/src/segment-system.tsx +134 -36
  230. package/src/server/context.ts +341 -61
  231. package/src/server/cookie-store.ts +190 -0
  232. package/src/server/fetchable-loader-store.ts +37 -0
  233. package/src/server/handle-store.ts +113 -15
  234. package/src/server/loader-registry.ts +24 -64
  235. package/src/server/request-context.ts +607 -81
  236. package/src/server.ts +35 -130
  237. package/src/ssr/index.tsx +103 -30
  238. package/src/static-handler.ts +126 -0
  239. package/src/theme/ThemeProvider.tsx +21 -15
  240. package/src/theme/ThemeScript.tsx +5 -5
  241. package/src/theme/constants.ts +5 -2
  242. package/src/theme/index.ts +4 -14
  243. package/src/theme/theme-context.ts +4 -30
  244. package/src/theme/theme-script.ts +21 -18
  245. package/src/types/boundaries.ts +158 -0
  246. package/src/types/cache-types.ts +198 -0
  247. package/src/types/error-types.ts +192 -0
  248. package/src/types/global-namespace.ts +100 -0
  249. package/src/types/handler-context.ts +791 -0
  250. package/src/types/index.ts +88 -0
  251. package/src/types/loader-types.ts +210 -0
  252. package/src/types/route-config.ts +170 -0
  253. package/src/types/route-entry.ts +120 -0
  254. package/src/types/segments.ts +150 -0
  255. package/src/types.ts +1 -1623
  256. package/src/urls/include-helper.ts +207 -0
  257. package/src/urls/index.ts +53 -0
  258. package/src/urls/path-helper-types.ts +372 -0
  259. package/src/urls/path-helper.ts +364 -0
  260. package/src/urls/pattern-types.ts +107 -0
  261. package/src/urls/response-types.ts +116 -0
  262. package/src/urls/type-extraction.ts +372 -0
  263. package/src/urls/urls-function.ts +98 -0
  264. package/src/urls.ts +1 -802
  265. package/src/use-loader.tsx +161 -81
  266. package/src/vite/discovery/bundle-postprocess.ts +181 -0
  267. package/src/vite/discovery/discover-routers.ts +348 -0
  268. package/src/vite/discovery/prerender-collection.ts +439 -0
  269. package/src/vite/discovery/route-types-writer.ts +258 -0
  270. package/src/vite/discovery/self-gen-tracking.ts +47 -0
  271. package/src/vite/discovery/state.ts +117 -0
  272. package/src/vite/discovery/virtual-module-codegen.ts +203 -0
  273. package/src/vite/index.ts +15 -1133
  274. package/src/vite/plugin-types.ts +103 -0
  275. package/src/vite/plugins/cjs-to-esm.ts +93 -0
  276. package/src/vite/plugins/client-ref-dedup.ts +115 -0
  277. package/src/vite/plugins/client-ref-hashing.ts +105 -0
  278. package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +72 -53
  279. package/src/vite/plugins/expose-id-utils.ts +299 -0
  280. package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
  281. package/src/vite/plugins/expose-ids/handler-transform.ts +209 -0
  282. package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
  283. package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
  284. package/src/vite/plugins/expose-ids/types.ts +45 -0
  285. package/src/vite/plugins/expose-internal-ids.ts +786 -0
  286. package/src/vite/plugins/performance-tracks.ts +88 -0
  287. package/src/vite/plugins/refresh-cmd.ts +127 -0
  288. package/src/vite/plugins/use-cache-transform.ts +323 -0
  289. package/src/vite/plugins/version-injector.ts +83 -0
  290. package/src/vite/plugins/version-plugin.ts +266 -0
  291. package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +23 -14
  292. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  293. package/src/vite/rango.ts +462 -0
  294. package/src/vite/router-discovery.ts +918 -0
  295. package/src/vite/utils/ast-handler-extract.ts +517 -0
  296. package/src/vite/utils/banner.ts +36 -0
  297. package/src/vite/utils/bundle-analysis.ts +137 -0
  298. package/src/vite/utils/manifest-utils.ts +70 -0
  299. package/src/vite/{package-resolution.ts → utils/package-resolution.ts} +25 -29
  300. package/src/vite/utils/prerender-utils.ts +221 -0
  301. package/src/vite/utils/shared-utils.ts +170 -0
  302. package/CLAUDE.md +0 -43
  303. package/src/browser/lru-cache.ts +0 -69
  304. package/src/browser/request-controller.ts +0 -164
  305. package/src/cache/memory-store.ts +0 -253
  306. package/src/href-context.ts +0 -33
  307. package/src/href.ts +0 -255
  308. package/src/server/route-manifest-cache.ts +0 -173
  309. package/src/vite/expose-handle-id.ts +0 -209
  310. package/src/vite/expose-loader-id.ts +0 -426
  311. package/src/vite/expose-location-state-id.ts +0 -177
  312. /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
package/src/urls.ts CHANGED
@@ -1,802 +1 @@
1
- /**
2
- * Django-inspired URL patterns for @rangojs/router
3
- *
4
- * This module provides `urls()` and `path()` for defining routes with
5
- * URL patterns visible at the definition site.
6
- *
7
- * @example
8
- * ```typescript
9
- * // urls/blog.ts
10
- * export const blogPatterns = urls(({ path, layout, loader }) => [
11
- * layout(BlogLayout, () => [
12
- * path("/", BlogIndex, { name: "index" }),
13
- * path("/:slug", BlogPost, { name: "post" }, () => [
14
- * loader(PostLoader),
15
- * ]),
16
- * ]),
17
- * ]);
18
- *
19
- * // urls/index.ts
20
- * export const urlpatterns = urls(({ path, layout, include }) => [
21
- * layout(RootLayout, () => [
22
- * path("/", HomePage, { name: "home" }),
23
- * include("/blog", blogPatterns, { name: "blog" }),
24
- * ]),
25
- * ]);
26
- * ```
27
- */
28
- import type { ReactNode } from "react";
29
- import type {
30
- DefaultEnv,
31
- ErrorBoundaryHandler,
32
- ExtractParams,
33
- Handler,
34
- LoaderDefinition,
35
- MiddlewareFn,
36
- NotFoundBoundaryHandler,
37
- PartialCacheOptions,
38
- ShouldRevalidateFn,
39
- TrailingSlashMode,
40
- } from "./types.js";
41
- import type {
42
- AllUseItems,
43
- LayoutItem,
44
- TypedLayoutItem,
45
- RouteItem,
46
- TypedRouteItem,
47
- ParallelItem,
48
- InterceptItem,
49
- MiddlewareItem,
50
- RevalidateItem,
51
- LoaderItem,
52
- LoadingItem,
53
- ErrorBoundaryItem,
54
- NotFoundBoundaryItem,
55
- LayoutUseItem,
56
- RouteUseItem,
57
- ParallelUseItem,
58
- InterceptUseItem,
59
- LoaderUseItem,
60
- WhenItem,
61
- CacheItem,
62
- TypedCacheItem,
63
- IncludeItem,
64
- TypedIncludeItem,
65
- IncludeBrand,
66
- UrlPatternsBrand,
67
- } from "./route-types.js";
68
- import {
69
- getContext,
70
- runWithPrefixes,
71
- getUrlPrefix,
72
- getNamePrefix,
73
- type EntryData,
74
- type InterceptEntry,
75
- type InterceptWhenFn,
76
- } from "./server/context";
77
- import { invariant } from "./errors";
78
-
79
- // ============================================================================
80
- // Types
81
- // ============================================================================
82
-
83
- /**
84
- * Sentinel type for unnamed routes.
85
- * Using a branded string instead of `never` prevents TypeScript from
86
- * widening array type inference when mixing named and unnamed routes.
87
- */
88
- export type UnnamedRoute = "$unnamed";
89
-
90
- /**
91
- * Options for path() function
92
- */
93
- export interface PathOptions<TName extends string = string> {
94
- /** Route name for href() lookups */
95
- name?: TName;
96
- /** Trailing slash behavior: "never" (redirect /path/ to /path), "always" (redirect /path to /path/), "ignore" (match both) */
97
- trailingSlash?: TrailingSlashMode;
98
- }
99
-
100
- /**
101
- * Internal representation of a URL pattern definition
102
- */
103
- export interface PathDefinition {
104
- pattern: string;
105
- name?: string;
106
- handler: ReactNode | Handler<any, any>;
107
- use?: RouteUseItem[];
108
- }
109
-
110
- /**
111
- * Result of urls() - contains the route definitions
112
- */
113
- export interface UrlPatterns<
114
- TEnv = any,
115
- TRoutes extends Record<string, string> = Record<string, string>
116
- > {
117
- /** Internal: route definitions */
118
- readonly definitions: PathDefinition[];
119
- /** Internal: compiled handler function */
120
- readonly handler: () => AllUseItems[];
121
- /** Internal: trailing slash config per route name */
122
- readonly trailingSlash: Record<string, TrailingSlashMode>;
123
- /** Brand for type checking */
124
- readonly [UrlPatternsBrand]: void;
125
- /** Environment type brand (phantom) */
126
- readonly _env?: TEnv;
127
- /** Routes type brand (phantom) - carries route name -> pattern mapping */
128
- readonly _routes?: TRoutes;
129
- }
130
-
131
- /**
132
- * Options for include()
133
- */
134
- export interface IncludeOptions<TNamePrefix extends string = string> {
135
- /** Name prefix for all routes in this pattern set */
136
- name?: TNamePrefix;
137
- }
138
-
139
- // ============================================================================
140
- // Route Type Extraction Utilities
141
- // ============================================================================
142
-
143
- /**
144
- * Prefix route names with a given prefix (e.g., "blog" + "post" = "blog.post")
145
- */
146
- type PrefixRoutes<
147
- TRoutes extends Record<string, string>,
148
- TPrefix extends string
149
- > = TPrefix extends ""
150
- ? TRoutes
151
- : {
152
- [K in keyof TRoutes as K extends string ? `${TPrefix}.${K}` : never]: TRoutes[K];
153
- };
154
-
155
- /**
156
- * Prefix route patterns with a URL prefix (e.g., "/blog" + "/:slug" = "/blog/:slug")
157
- */
158
- type PrefixPatterns<
159
- TRoutes extends Record<string, string>,
160
- TUrlPrefix extends string
161
- > = {
162
- [K in keyof TRoutes]: TRoutes[K] extends string
163
- ? `${TUrlPrefix}${TRoutes[K]}`
164
- : TRoutes[K];
165
- };
166
-
167
- /**
168
- * Depth counter for limiting recursion (max 40 levels)
169
- * Supports up to 40 sibling items at any level of a urls() call
170
- * Note: Higher values hit TypeScript's internal recursion limits
171
- */
172
- type Depth = [
173
- never,
174
- 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
175
- 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39
176
- ];
177
-
178
- /**
179
- * Force TypeScript to eagerly evaluate a type.
180
- * This helps with interface extension by creating a "concrete" object type.
181
- */
182
- type Simplify<T> = T extends Record<string, string>
183
- ? { [K in keyof T]: T[K] }
184
- : T;
185
-
186
- /**
187
- * Convert a union type to an intersection type.
188
- * Used to combine route maps from multiple siblings without recursive tuple processing.
189
- */
190
- type UnionToIntersection<U> =
191
- (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
192
-
193
- /**
194
- * Extract routes from a single item (path, include, layout, cache with children)
195
- * D is the current depth level for nested layouts/caches
196
- */
197
- type ExtractRoutesFromItem<T, D extends number = 40> = [D] extends [never]
198
- ? {} // Max depth reached, stop recursion
199
- : // TypedRouteItem: extract name -> pattern (exclude unnamed routes)
200
- T extends TypedRouteItem<infer TName, infer TPattern>
201
- ? TName extends string
202
- ? TName extends UnnamedRoute
203
- ? {} // Exclude unnamed routes from type map
204
- : { [K in TName]: TPattern }
205
- : {}
206
- // TypedIncludeItem: extract prefixed routes (both name and URL prefix)
207
- : T extends TypedIncludeItem<infer TRoutes, infer TNamePrefix, infer TUrlPrefix>
208
- ? TNamePrefix extends string
209
- ? TUrlPrefix extends string
210
- ? PrefixRoutes<PrefixPatterns<TRoutes, TUrlPrefix>, TNamePrefix>
211
- : PrefixRoutes<TRoutes, TNamePrefix>
212
- : TUrlPrefix extends string
213
- ? PrefixPatterns<TRoutes, TUrlPrefix>
214
- : TRoutes
215
- // TypedLayoutItem: extract child routes from phantom type
216
- : T extends TypedLayoutItem<infer TChildRoutes>
217
- ? TChildRoutes
218
- // TypedCacheItem: extract child routes from phantom type
219
- : T extends TypedCacheItem<infer TChildRoutes>
220
- ? TChildRoutes
221
- // Fallback (won't extract routes)
222
- : {};
223
-
224
- /**
225
- * Extract routes from an array of items using mapped types.
226
- * Uses UnionToIntersection to combine routes without recursive tuple processing,
227
- * removing the sibling limit that was caused by TypeScript recursion limits.
228
- * D is passed to ExtractRoutesFromItem for nested depth tracking.
229
- */
230
- type ExtractRoutesFromItems<
231
- T extends readonly any[],
232
- D extends number = 40
233
- > = T extends readonly any[]
234
- ? UnionToIntersection<
235
- { [K in keyof T]: ExtractRoutesFromItem<T[K], D> }[number]
236
- > extends infer R
237
- ? R extends Record<string, string>
238
- ? R
239
- : {}
240
- : {}
241
- : {};
242
-
243
- /**
244
- * Main utility: extract route map from urls() callback return type
245
- * Uses mapped types for sibling processing (no sibling limit).
246
- * Uses Simplify to force eager evaluation for interface extension compatibility.
247
- */
248
- export type ExtractRoutes<T extends readonly any[]> = Simplify<ExtractRoutesFromItems<T, 40>>;
249
-
250
- // ============================================================================
251
- // Path Helpers Type
252
- // ============================================================================
253
-
254
- /**
255
- * Helpers provided by urls()
256
- */
257
- export type PathHelpers<TEnv> = {
258
- /**
259
- * Define a route with URL pattern at definition site
260
- *
261
- * @example
262
- * ```typescript
263
- * // Pattern and component only
264
- * path("/about", AboutPage)
265
- *
266
- * // With options
267
- * path("/:slug", PostPage, { name: "post" })
268
- *
269
- * // With children (loaders, middleware, etc.)
270
- * path("/:slug", PostPage, { name: "post" }, () => [
271
- * loader(PostLoader),
272
- * ])
273
- * ```
274
- */
275
- path: <const TPattern extends string, const TName extends string = UnnamedRoute>(
276
- pattern: TPattern,
277
- handler: ReactNode | Handler<ExtractParams<TPattern>, TEnv>,
278
- optionsOrUse?: PathOptions<TName> | (() => RouteUseItem[]),
279
- use?: () => RouteUseItem[]
280
- ) => TypedRouteItem<TName, TPattern>;
281
-
282
- /**
283
- * Define a layout that wraps child routes
284
- */
285
- layout: <const TChildren extends readonly LayoutUseItem[] = readonly LayoutUseItem[]>(
286
- component: ReactNode | Handler<any, TEnv>,
287
- use?: () => TChildren
288
- ) => TypedLayoutItem<ExtractRoutes<TChildren>>;
289
-
290
- /**
291
- * Include nested URL patterns with optional name prefix
292
- *
293
- * ```typescript
294
- * // Without name - routes keep local names
295
- * include("/blog", blogPatterns)
296
- *
297
- * // With name - routes are prefixed (e.g., "index" → "blog.index")
298
- * include("/blog", blogPatterns, { name: "blog" })
299
- * ```
300
- */
301
- include: <
302
- TRoutes extends Record<string, string>,
303
- const TUrlPrefix extends string,
304
- const TNamePrefix extends string = never
305
- >(
306
- prefix: TUrlPrefix,
307
- patterns: UrlPatterns<TEnv, TRoutes>,
308
- options?: IncludeOptions<TNamePrefix>
309
- ) => TypedIncludeItem<TRoutes, TNamePrefix, TUrlPrefix>;
310
-
311
- /**
312
- * Define parallel routes that render simultaneously in named slots
313
- */
314
- parallel: <TSlots extends Record<`@${string}`, Handler<any, TEnv> | ReactNode>>(
315
- slots: TSlots,
316
- use?: () => ParallelUseItem[]
317
- ) => ParallelItem;
318
-
319
- /**
320
- * Define an intercepting route for soft navigation
321
- * Note: routeName must match a named path() in this urlpatterns
322
- */
323
- intercept: (
324
- slotName: `@${string}`,
325
- routeName: string,
326
- handler: ReactNode | Handler<any, TEnv>,
327
- use?: () => InterceptUseItem[]
328
- ) => InterceptItem;
329
-
330
- /**
331
- * Attach middleware to the current route/layout
332
- */
333
- middleware: (...fns: MiddlewareFn<TEnv>[]) => MiddlewareItem;
334
-
335
- /**
336
- * Control when a segment should revalidate during navigation
337
- */
338
- revalidate: (fn: ShouldRevalidateFn<any, TEnv>) => RevalidateItem;
339
-
340
- /**
341
- * Attach a data loader to the current route/layout
342
- */
343
- loader: <TData>(
344
- loaderDef: LoaderDefinition<TData>,
345
- use?: () => LoaderUseItem[]
346
- ) => LoaderItem;
347
-
348
- /**
349
- * Attach a loading component to the current route/layout
350
- */
351
- loading: (component: ReactNode, options?: { ssr?: boolean }) => LoadingItem;
352
-
353
- /**
354
- * Attach an error boundary to catch errors in this segment
355
- */
356
- errorBoundary: (
357
- fallback: ReactNode | ErrorBoundaryHandler
358
- ) => ErrorBoundaryItem;
359
-
360
- /**
361
- * Attach a not-found boundary to handle notFound() calls
362
- */
363
- notFoundBoundary: (
364
- fallback: ReactNode | NotFoundBoundaryHandler
365
- ) => NotFoundBoundaryItem;
366
-
367
- /**
368
- * Define a condition for when an intercept should activate
369
- */
370
- when: (fn: InterceptWhenFn) => WhenItem;
371
-
372
- /**
373
- * Define cache configuration for segments
374
- */
375
- cache: {
376
- (): CacheItem;
377
- <const TChildren extends readonly AllUseItems[] = readonly AllUseItems[]>(
378
- children: () => TChildren
379
- ): TypedCacheItem<ExtractRoutes<TChildren>>;
380
- <const TChildren extends readonly AllUseItems[] = readonly AllUseItems[]>(
381
- options: PartialCacheOptions | false,
382
- use?: () => TChildren
383
- ): TypedCacheItem<ExtractRoutes<TChildren>>;
384
- };
385
- };
386
-
387
- // ============================================================================
388
- // Helper Implementations
389
- // ============================================================================
390
-
391
- /**
392
- * Check if a value is a valid use item
393
- */
394
- const isValidUseItem = (item: any): item is AllUseItems | undefined | null => {
395
- return (
396
- typeof item === "undefined" ||
397
- item === null ||
398
- (item &&
399
- typeof item === "object" &&
400
- "type" in item &&
401
- [
402
- "layout",
403
- "route",
404
- "middleware",
405
- "revalidate",
406
- "parallel",
407
- "intercept",
408
- "loader",
409
- "loading",
410
- "errorBoundary",
411
- "notFoundBoundary",
412
- "when",
413
- "cache",
414
- "include",
415
- ].includes(item.type))
416
- );
417
- };
418
-
419
- /**
420
- * Apply URL prefix to a pattern
421
- * Handles edge cases like "/" patterns and double slashes
422
- */
423
- function applyUrlPrefix(prefix: string, pattern: string): string {
424
- if (!prefix) return pattern;
425
- if (pattern === "/") return prefix;
426
- if (prefix.endsWith("/") && pattern.startsWith("/")) {
427
- return prefix + pattern.slice(1);
428
- }
429
- return prefix + pattern;
430
- }
431
-
432
- /**
433
- * Apply name prefix to a route name
434
- */
435
- function applyNamePrefix(prefix: string | undefined, name: string): string {
436
- if (!prefix) return name;
437
- return `${prefix}.${name}`;
438
- }
439
-
440
- /**
441
- * Create path() helper
442
- *
443
- * The path() function is the key new feature - it combines URL pattern
444
- * with handler at the definition site.
445
- */
446
- function createPathHelper<TEnv>(): PathHelpers<TEnv>["path"] {
447
- return ((
448
- pattern: string,
449
- handler: ReactNode | Handler<any, TEnv>,
450
- optionsOrUse?: PathOptions | (() => RouteUseItem[]),
451
- maybeUse?: () => RouteUseItem[]
452
- ): RouteItem => {
453
- const store = getContext();
454
- const ctx = store.getStore();
455
- if (!ctx) throw new Error("path() must be called inside urls()");
456
-
457
- // Determine options and use based on argument types
458
- let options: PathOptions | undefined;
459
- let use: (() => RouteUseItem[]) | undefined;
460
-
461
- if (typeof optionsOrUse === "function") {
462
- // path(pattern, handler, use)
463
- use = optionsOrUse as () => RouteUseItem[];
464
- } else if (typeof optionsOrUse === "object") {
465
- // path(pattern, handler, options) or path(pattern, handler, options, use)
466
- options = optionsOrUse as PathOptions;
467
- use = maybeUse;
468
- }
469
-
470
- // Get prefixes from context (set by include())
471
- const urlPrefix = getUrlPrefix();
472
- const namePrefix = getNamePrefix();
473
-
474
- // Apply URL prefix to pattern
475
- const prefixedPattern = applyUrlPrefix(urlPrefix, pattern);
476
-
477
- // Generate route name - use provided name or generate from pattern
478
- const localName = options?.name || `$path_${pattern.replace(/[/:*?]/g, "_")}`;
479
- // Apply name prefix if set (from include())
480
- const routeName = applyNamePrefix(namePrefix, localName);
481
-
482
- const namespace = `${ctx.namespace}.${store.getNextIndex("route")}.${routeName}`;
483
-
484
- // Ensure handler is always a function (wrap ReactNode if needed)
485
- const wrappedHandler: Handler<any, TEnv> =
486
- typeof handler === "function"
487
- ? (handler as Handler<any, TEnv>)
488
- : () => handler;
489
-
490
- const entry = {
491
- id: namespace,
492
- shortCode: store.getShortCode("route"),
493
- type: "route" as const,
494
- parent: ctx.parent,
495
- handler: wrappedHandler,
496
- // Store the PREFIXED pattern for route matching
497
- pattern: prefixedPattern,
498
- loading: undefined,
499
- middleware: [],
500
- revalidate: [],
501
- errorBoundary: [],
502
- notFoundBoundary: [],
503
- layout: [],
504
- parallel: [],
505
- intercept: [],
506
- loader: [],
507
- ...(urlPrefix ? { mountPath: urlPrefix } : {}),
508
- };
509
-
510
- // Check for duplicate route names (TypeScript should catch this, but runtime check too)
511
- invariant(
512
- ctx.manifest.get(routeName) === undefined,
513
- `Duplicate route name: ${routeName} at ${namespace}`
514
- );
515
-
516
- // Register route entry with prefixed name
517
- ctx.manifest.set(routeName, entry);
518
-
519
- // Also store pattern in a separate map for URL generation
520
- if (ctx.patterns) {
521
- ctx.patterns.set(routeName, prefixedPattern);
522
- }
523
-
524
- // Store pattern grouped by URL prefix for separate entry creation
525
- if (ctx.patternsByPrefix) {
526
- const urlPrefix = getUrlPrefix() || "";
527
- if (!ctx.patternsByPrefix.has(urlPrefix)) {
528
- ctx.patternsByPrefix.set(urlPrefix, new Map());
529
- }
530
- ctx.patternsByPrefix.get(urlPrefix)!.set(routeName, prefixedPattern);
531
- }
532
-
533
- // Store trailing slash config if specified
534
- if (options?.trailingSlash && ctx.trailingSlash) {
535
- ctx.trailingSlash.set(routeName, options.trailingSlash);
536
- }
537
-
538
- // Run use callback if provided
539
- if (use && typeof use === "function") {
540
- const result = store.run(namespace, entry, use);
541
- invariant(
542
- Array.isArray(result) && result.every((item) => isValidUseItem(item)),
543
- `path() use() callback must return an array of use items [${namespace}]`
544
- );
545
- return { name: namespace, type: "route", uses: result } as RouteItem;
546
- }
547
-
548
- return { name: namespace, type: "route" } as RouteItem;
549
- }) as PathHelpers<TEnv>["path"];
550
- }
551
-
552
- /**
553
- * Process an IncludeItem by executing its nested patterns with prefixes
554
- * This expands the include into actual route registrations
555
- */
556
- function processIncludeItem(item: IncludeItem): AllUseItems[] {
557
- const { prefix, patterns, options } = item;
558
- const namePrefix = options?.name;
559
-
560
- // Execute the nested patterns' handler with URL and name prefixes
561
- // The urlPrefix being set tells nested urls() to skip RootLayout wrapping
562
- return runWithPrefixes(prefix, namePrefix, () => {
563
- // Call the nested patterns' handler - this registers routes with prefixed patterns/names
564
- return (patterns as UrlPatterns).handler();
565
- });
566
- }
567
-
568
- /**
569
- * Recursively process items, expanding any IncludeItems
570
- * Returns items with IncludeItems expanded into actual route items
571
- *
572
- * Lazy includes are kept as-is (not expanded) for the router to handle later.
573
- */
574
- function processItems(items: readonly AllUseItems[]): AllUseItems[] {
575
- const result: AllUseItems[] = [];
576
-
577
- for (const item of items) {
578
- if (!item) continue;
579
-
580
- if (item.type === "include") {
581
- const includeItem = item as IncludeItem & {
582
- _expanded?: AllUseItems[];
583
- lazy?: boolean;
584
- };
585
-
586
- // Lazy includes are NOT expanded here - kept for router to handle
587
- if (includeItem.lazy) {
588
- result.push(item);
589
- continue;
590
- }
591
-
592
- // Eager includes are already expanded during include() call
593
- if (includeItem._expanded) {
594
- // Items were expanded immediately - just process them recursively
595
- result.push(...processItems(includeItem._expanded));
596
- } else {
597
- // Fallback for legacy include items without _expanded
598
- const expanded = processIncludeItem(item as IncludeItem);
599
- result.push(...processItems(expanded));
600
- }
601
- } else if (item.type === "layout" && (item as any).uses) {
602
- // Process nested items in layout
603
- const layoutItem = item as any;
604
- layoutItem.uses = processItems(layoutItem.uses);
605
- result.push(layoutItem);
606
- } else {
607
- result.push(item);
608
- }
609
- }
610
-
611
- return result;
612
- }
613
-
614
- /**
615
- * Create include() helper for composing URL patterns
616
- *
617
- * By default, include() IMMEDIATELY expands the nested patterns. This ensures
618
- * that routes from included patterns inherit the correct parent context
619
- * (the layout they're included in).
620
- *
621
- * With `lazy: true`, patterns are NOT expanded at definition time. Instead,
622
- * they're evaluated on first request that matches the prefix. This improves
623
- * cold start time for apps with many routes.
624
- */
625
- function createIncludeHelper<TEnv>(): PathHelpers<TEnv>["include"] {
626
- return (
627
- prefix: string,
628
- patterns: UrlPatterns<TEnv>,
629
- options?: IncludeOptions
630
- ): IncludeItem => {
631
- const store = getContext();
632
- const ctx = store.getStore();
633
- if (!ctx) throw new Error("include() must be called inside urls()");
634
-
635
- const namePrefix = options?.name;
636
- const name = `$include_${prefix.replace(/[/:*?]/g, "_")}`;
637
-
638
- // Capture context for deferred evaluation
639
- const capturedUrlPrefix = getUrlPrefix();
640
- const capturedNamePrefix = getNamePrefix();
641
- const capturedParent = ctx.parent;
642
- const fullPrefix = capturedUrlPrefix ? capturedUrlPrefix + prefix : prefix;
643
- const fullNamePrefix = namePrefix
644
- ? capturedNamePrefix
645
- ? `${capturedNamePrefix}.${namePrefix}`
646
- : namePrefix
647
- : capturedNamePrefix;
648
-
649
- // Track this include for build-time manifest generation
650
- if (ctx.trackedIncludes) {
651
- ctx.trackedIncludes.push({
652
- prefix,
653
- fullPrefix,
654
- namePrefix: fullNamePrefix,
655
- patterns,
656
- lazy: true,
657
- });
658
- }
659
-
660
- // All includes are lazy - patterns are evaluated on first matching request
661
- // This improves cold start time significantly for large route sets
662
- return {
663
- type: "include",
664
- name,
665
- prefix,
666
- patterns,
667
- options,
668
- lazy: true,
669
- _lazyContext: {
670
- urlPrefix: capturedUrlPrefix,
671
- namePrefix: fullNamePrefix,
672
- parent: capturedParent,
673
- },
674
- } as IncludeItem;
675
- };
676
- }
677
-
678
- // ============================================================================
679
- // Re-use existing helpers from route-definition.ts
680
- // ============================================================================
681
-
682
- // Import the helper creation functions from route-definition
683
- import {
684
- createRouteHelpers,
685
- } from "./route-definition.js";
686
-
687
- // ============================================================================
688
- // urls() Main Entry Point
689
- // ============================================================================
690
-
691
- /**
692
- * Define URL patterns with Django-inspired syntax
693
- *
694
- * Replaces map() as the entry point for route definitions.
695
- * URL patterns are now visible at the definition site via path().
696
- *
697
- * @example
698
- * ```typescript
699
- * export const blogPatterns = urls(({ path, layout, loader }) => [
700
- * layout(BlogLayout, () => [
701
- * path("/", BlogIndex, { name: "index" }),
702
- * path("/:slug", BlogPost, { name: "post" }, () => [
703
- * loader(PostLoader),
704
- * ]),
705
- * ]),
706
- * ]);
707
- * ```
708
- */
709
- export function urls<
710
- TEnv = DefaultEnv,
711
- const TItems extends readonly AllUseItems[] = readonly AllUseItems[]
712
- >(
713
- builder: (helpers: PathHelpers<TEnv>) => TItems
714
- ): UrlPatterns<TEnv, ExtractRoutes<TItems>> {
715
- // Collect path definitions during build
716
- const definitions: PathDefinition[] = [];
717
-
718
- // Create the handler function that will be called by the router
719
- const handler = () => {
720
- invariant(
721
- typeof builder === "function",
722
- "urls() expects a builder function as its argument"
723
- );
724
-
725
- // Get base helpers from the existing route-definition module
726
- const baseHelpers = createRouteHelpers<any, TEnv>();
727
-
728
- // Create the path helper
729
- const pathHelper = createPathHelper<TEnv>();
730
-
731
- // Create the include helper
732
- const includeHelper = createIncludeHelper<TEnv>();
733
-
734
- // Combine all helpers
735
- // Note: layout and cache are cast to their typed versions - phantom types don't affect runtime
736
- const helpers: PathHelpers<TEnv> = {
737
- path: pathHelper,
738
- include: includeHelper,
739
- layout: baseHelpers.layout as PathHelpers<TEnv>["layout"],
740
- parallel: baseHelpers.parallel,
741
- intercept: baseHelpers.intercept as PathHelpers<TEnv>["intercept"],
742
- middleware: baseHelpers.middleware,
743
- revalidate: baseHelpers.revalidate,
744
- loader: baseHelpers.loader,
745
- loading: baseHelpers.loading,
746
- errorBoundary: baseHelpers.errorBoundary,
747
- notFoundBoundary: baseHelpers.notFoundBoundary,
748
- when: baseHelpers.when,
749
- cache: baseHelpers.cache as PathHelpers<TEnv>["cache"],
750
- };
751
-
752
- // Execute builder directly - manifest.ts handles RootLayout wrapping
753
- // for inline handlers (non-Promise results).
754
- // For nested include() calls, routes inherit the outer RootLayout.
755
- const builderResult = builder(helpers);
756
- return processItems(builderResult);
757
- };
758
-
759
- // trailingSlash config is populated when handler() runs
760
- // We expose it via a getter that reads from the context after handler execution
761
- return {
762
- definitions,
763
- handler,
764
- get trailingSlash() {
765
- // Get the trailingSlash map from the current context
766
- // This will be populated after handler() is called
767
- const store = getContext();
768
- const ctx = store.context.getStore();
769
- if (!ctx?.trailingSlash) {
770
- return {};
771
- }
772
- return Object.fromEntries(ctx.trailingSlash);
773
- },
774
- } as UrlPatterns<TEnv, ExtractRoutes<TItems>>;
775
- }
776
-
777
- // ============================================================================
778
- // Type Utilities for path()
779
- // ============================================================================
780
-
781
- /**
782
- * Extract route names from a UrlPatterns result
783
- * Used for type-safe href() generation
784
- */
785
- export type ExtractRouteNames<T extends UrlPatterns<any>> =
786
- T extends UrlPatterns<infer _TEnv>
787
- ? string // For now, will be refined with full implementation
788
- : never;
789
-
790
- /**
791
- * Extract params for a specific route name
792
- */
793
- export type ExtractPathParams<
794
- T extends UrlPatterns<any>,
795
- K extends string
796
- > = ExtractParams<string>; // Will be refined with pattern tracking
797
-
798
- // ============================================================================
799
- // Exports
800
- // ============================================================================
801
-
802
- export type { AllUseItems, IncludeItem, TypedRouteItem, TypedIncludeItem, TypedLayoutItem, TypedCacheItem } from "./route-types.js";
1
+ export * from "./urls/index.js";