@rangojs/router 0.0.0-experimental.79 → 0.0.0-experimental.7d061845

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 (252) hide show
  1. package/README.md +120 -25
  2. package/dist/bin/rango.js +147 -57
  3. package/dist/testing/vitest.js +82 -0
  4. package/dist/vite/index.js +2138 -841
  5. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  6. package/package.json +68 -21
  7. package/skills/breadcrumbs/SKILL.md +3 -1
  8. package/skills/bundle-analysis/SKILL.md +159 -0
  9. package/skills/cache-guide/SKILL.md +220 -30
  10. package/skills/caching/SKILL.md +116 -8
  11. package/skills/composability/SKILL.md +27 -2
  12. package/skills/document-cache/SKILL.md +78 -55
  13. package/skills/handler-use/SKILL.md +3 -1
  14. package/skills/hooks/SKILL.md +229 -20
  15. package/skills/host-router/SKILL.md +45 -20
  16. package/skills/i18n/SKILL.md +276 -0
  17. package/skills/intercept/SKILL.md +26 -4
  18. package/skills/layout/SKILL.md +6 -7
  19. package/skills/links/SKILL.md +247 -17
  20. package/skills/loader/SKILL.md +219 -9
  21. package/skills/middleware/SKILL.md +15 -9
  22. package/skills/migrate-nextjs/SKILL.md +4 -2
  23. package/skills/migrate-react-router/SKILL.md +5 -0
  24. package/skills/mime-routes/SKILL.md +27 -0
  25. package/skills/observability/SKILL.md +137 -0
  26. package/skills/parallel/SKILL.md +12 -6
  27. package/skills/prerender/SKILL.md +14 -33
  28. package/skills/rango/SKILL.md +242 -24
  29. package/skills/react-compiler/SKILL.md +168 -0
  30. package/skills/response-routes/SKILL.md +66 -9
  31. package/skills/route/SKILL.md +33 -4
  32. package/skills/router-setup/SKILL.md +3 -3
  33. package/skills/server-actions/SKILL.md +751 -0
  34. package/skills/streams-and-websockets/SKILL.md +283 -0
  35. package/skills/testing/SKILL.md +816 -0
  36. package/skills/typesafety/SKILL.md +319 -27
  37. package/skills/use-cache/SKILL.md +34 -5
  38. package/skills/view-transitions/SKILL.md +294 -0
  39. package/src/__augment-tests__/augment.ts +81 -0
  40. package/src/__augment-tests__/augmented.check.ts +117 -0
  41. package/src/browser/action-coordinator.ts +53 -36
  42. package/src/browser/app-shell.ts +52 -0
  43. package/src/browser/event-controller.ts +86 -70
  44. package/src/browser/history-state.ts +21 -0
  45. package/src/browser/index.ts +3 -3
  46. package/src/browser/navigation-bridge.ts +65 -9
  47. package/src/browser/navigation-client.ts +45 -25
  48. package/src/browser/navigation-store.ts +32 -9
  49. package/src/browser/navigation-transaction.ts +10 -28
  50. package/src/browser/partial-update.ts +52 -26
  51. package/src/browser/prefetch/cache.ts +124 -26
  52. package/src/browser/prefetch/fetch.ts +114 -38
  53. package/src/browser/prefetch/queue.ts +36 -5
  54. package/src/browser/rango-state.ts +53 -13
  55. package/src/browser/react/Link.tsx +18 -13
  56. package/src/browser/react/NavigationProvider.tsx +72 -31
  57. package/src/browser/react/filter-segment-order.ts +51 -7
  58. package/src/browser/react/index.ts +3 -0
  59. package/src/browser/react/location-state-shared.ts +175 -4
  60. package/src/browser/react/location-state.ts +39 -13
  61. package/src/browser/react/use-handle.ts +17 -9
  62. package/src/browser/react/use-navigation.ts +22 -2
  63. package/src/browser/react/use-params.ts +20 -8
  64. package/src/browser/react/use-reverse.ts +106 -0
  65. package/src/browser/react/use-router.ts +22 -2
  66. package/src/browser/react/use-segments.ts +11 -8
  67. package/src/browser/response-adapter.ts +25 -0
  68. package/src/browser/rsc-router.tsx +64 -22
  69. package/src/browser/scroll-restoration.ts +22 -14
  70. package/src/browser/segment-structure-assert.ts +2 -2
  71. package/src/browser/server-action-bridge.ts +23 -30
  72. package/src/browser/types.ts +21 -0
  73. package/src/build/collect-fallback-refs.ts +107 -0
  74. package/src/build/generate-manifest.ts +60 -35
  75. package/src/build/generate-route-types.ts +2 -0
  76. package/src/build/index.ts +2 -0
  77. package/src/build/route-trie.ts +2 -1
  78. package/src/build/route-types/codegen.ts +4 -4
  79. package/src/build/route-types/include-resolution.ts +1 -1
  80. package/src/build/route-types/per-module-writer.ts +7 -4
  81. package/src/build/route-types/router-processing.ts +55 -14
  82. package/src/build/route-types/scan-filter.ts +1 -1
  83. package/src/build/route-types/source-scan.ts +118 -0
  84. package/src/build/runtime-discovery.ts +9 -20
  85. package/src/cache/cache-scope.ts +28 -42
  86. package/src/cache/cf/cf-cache-store.ts +54 -13
  87. package/src/client.rsc.tsx +3 -0
  88. package/src/client.tsx +10 -8
  89. package/src/context-var.ts +5 -5
  90. package/src/decode-loader-results.ts +36 -0
  91. package/src/errors.ts +30 -1
  92. package/src/handle.ts +26 -13
  93. package/src/host/index.ts +2 -2
  94. package/src/host/router.ts +129 -57
  95. package/src/host/types.ts +31 -2
  96. package/src/host/utils.ts +1 -1
  97. package/src/href-client.ts +140 -20
  98. package/src/index.rsc.ts +9 -4
  99. package/src/index.ts +16 -6
  100. package/src/loader-store.ts +500 -0
  101. package/src/loader.rsc.ts +21 -6
  102. package/src/loader.ts +3 -10
  103. package/src/missing-id-error.ts +68 -0
  104. package/src/outlet-context.ts +1 -1
  105. package/src/prerender.ts +4 -4
  106. package/src/response-utils.ts +37 -0
  107. package/src/reverse.ts +65 -39
  108. package/src/route-content-wrapper.tsx +6 -28
  109. package/src/route-definition/dsl-helpers.ts +253 -265
  110. package/src/route-definition/helper-factories.ts +29 -139
  111. package/src/route-definition/helpers-types.ts +43 -15
  112. package/src/route-definition/resolve-handler-use.ts +6 -0
  113. package/src/route-definition/use-item-types.ts +32 -0
  114. package/src/route-types.ts +19 -41
  115. package/src/router/basename.ts +14 -0
  116. package/src/router/content-negotiation.ts +15 -2
  117. package/src/router/error-handling.ts +1 -1
  118. package/src/router/handler-context.ts +21 -41
  119. package/src/router/intercept-resolution.ts +4 -18
  120. package/src/router/lazy-includes.ts +3 -3
  121. package/src/router/loader-resolution.ts +19 -2
  122. package/src/router/match-api.ts +4 -3
  123. package/src/router/match-handlers.ts +63 -20
  124. package/src/router/match-middleware/cache-lookup.ts +44 -91
  125. package/src/router/match-middleware/cache-store.ts +3 -2
  126. package/src/router/match-result.ts +53 -32
  127. package/src/router/metrics.ts +1 -1
  128. package/src/router/middleware-types.ts +15 -26
  129. package/src/router/middleware.ts +99 -84
  130. package/src/router/pattern-matching.ts +101 -17
  131. package/src/router/prerender-match.ts +1 -1
  132. package/src/router/preview-match.ts +3 -1
  133. package/src/router/request-classification.ts +4 -28
  134. package/src/router/revalidation.ts +58 -2
  135. package/src/router/router-interfaces.ts +45 -28
  136. package/src/router/router-options.ts +40 -1
  137. package/src/router/router-registry.ts +2 -5
  138. package/src/router/segment-resolution/fresh.ts +27 -6
  139. package/src/router/segment-resolution/revalidation.ts +147 -106
  140. package/src/router/segment-resolution/view-transition-default.ts +36 -0
  141. package/src/router/substitute-pattern-params.ts +56 -0
  142. package/src/router/telemetry.ts +99 -0
  143. package/src/router/trie-matching.ts +18 -13
  144. package/src/router/types.ts +8 -0
  145. package/src/router/url-params.ts +49 -0
  146. package/src/router.ts +38 -23
  147. package/src/rsc/handler-context.ts +2 -2
  148. package/src/rsc/handler.ts +28 -69
  149. package/src/rsc/helpers.ts +91 -43
  150. package/src/rsc/index.ts +1 -1
  151. package/src/rsc/origin-guard.ts +28 -10
  152. package/src/rsc/progressive-enhancement.ts +4 -0
  153. package/src/rsc/response-route-handler.ts +46 -53
  154. package/src/rsc/rsc-rendering.ts +35 -51
  155. package/src/rsc/runtime-warnings.ts +9 -10
  156. package/src/rsc/server-action.ts +17 -37
  157. package/src/rsc/ssr-setup.ts +16 -0
  158. package/src/rsc/types.ts +8 -2
  159. package/src/search-params.ts +4 -4
  160. package/src/segment-system.tsx +122 -56
  161. package/src/serialize.ts +243 -0
  162. package/src/server/context.ts +118 -51
  163. package/src/server/cookie-store.ts +28 -4
  164. package/src/server/request-context.ts +20 -42
  165. package/src/ssr/index.tsx +5 -1
  166. package/src/static-handler.ts +1 -1
  167. package/src/testing/cache-status.ts +166 -0
  168. package/src/testing/collect-handle.ts +63 -0
  169. package/src/testing/dispatch.ts +440 -0
  170. package/src/testing/dom.entry.ts +22 -0
  171. package/src/testing/e2e/fixture.ts +154 -0
  172. package/src/testing/e2e/index.ts +149 -0
  173. package/src/testing/e2e/matchers.ts +51 -0
  174. package/src/testing/e2e/page-helpers.ts +272 -0
  175. package/src/testing/e2e/parity.ts +306 -0
  176. package/src/testing/e2e/server.ts +183 -0
  177. package/src/testing/flight-matchers.ts +104 -0
  178. package/src/testing/flight-runtime.d.ts +57 -0
  179. package/src/testing/flight-tree.ts +332 -0
  180. package/src/testing/flight.entry.ts +46 -0
  181. package/src/testing/flight.ts +224 -0
  182. package/src/testing/generated-routes.ts +223 -0
  183. package/src/testing/index.ts +106 -0
  184. package/src/testing/internal/context.ts +304 -0
  185. package/src/testing/internal/flight-client-globals.ts +30 -0
  186. package/src/testing/internal/seed-vars.ts +42 -0
  187. package/src/testing/render-handler.ts +267 -0
  188. package/src/testing/render-route.tsx +565 -0
  189. package/src/testing/run-loader.ts +341 -0
  190. package/src/testing/run-middleware.ts +188 -0
  191. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  192. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  193. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  194. package/src/testing/vitest-stubs/version.ts +5 -0
  195. package/src/testing/vitest.ts +270 -0
  196. package/src/types/global-namespace.ts +39 -26
  197. package/src/types/handler-context.ts +68 -50
  198. package/src/types/index.ts +1 -0
  199. package/src/types/loader-types.ts +5 -6
  200. package/src/types/request-scope.ts +126 -0
  201. package/src/types/segments.ts +35 -1
  202. package/src/urls/include-helper.ts +10 -53
  203. package/src/urls/index.ts +0 -3
  204. package/src/urls/path-helper-types.ts +11 -3
  205. package/src/urls/path-helper.ts +17 -52
  206. package/src/urls/pattern-types.ts +36 -19
  207. package/src/urls/response-types.ts +22 -29
  208. package/src/urls/type-extraction.ts +26 -116
  209. package/src/urls/urls-function.ts +1 -5
  210. package/src/use-loader.tsx +413 -42
  211. package/src/vite/debug.ts +185 -0
  212. package/src/vite/discovery/bundle-postprocess.ts +6 -6
  213. package/src/vite/discovery/discover-routers.ts +101 -51
  214. package/src/vite/discovery/discovery-errors.ts +194 -0
  215. package/src/vite/discovery/gate-state.ts +171 -0
  216. package/src/vite/discovery/prerender-collection.ts +67 -26
  217. package/src/vite/discovery/route-types-writer.ts +40 -84
  218. package/src/vite/discovery/self-gen-tracking.ts +27 -1
  219. package/src/vite/discovery/state.ts +33 -0
  220. package/src/vite/discovery/virtual-module-codegen.ts +13 -23
  221. package/src/vite/index.ts +2 -0
  222. package/src/vite/plugin-types.ts +67 -0
  223. package/src/vite/plugins/cjs-to-esm.ts +8 -7
  224. package/src/vite/plugins/client-ref-dedup.ts +16 -0
  225. package/src/vite/plugins/client-ref-hashing.ts +28 -5
  226. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  227. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  228. package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
  229. package/src/vite/plugins/expose-action-id.ts +54 -30
  230. package/src/vite/plugins/expose-id-utils.ts +12 -8
  231. package/src/vite/plugins/expose-ids/export-analysis.ts +100 -20
  232. package/src/vite/plugins/expose-ids/handler-transform.ts +8 -61
  233. package/src/vite/plugins/expose-ids/loader-transform.ts +3 -5
  234. package/src/vite/plugins/expose-ids/router-transform.ts +20 -3
  235. package/src/vite/plugins/expose-internal-ids.ts +496 -486
  236. package/src/vite/plugins/performance-tracks.ts +29 -25
  237. package/src/vite/plugins/use-cache-transform.ts +65 -50
  238. package/src/vite/plugins/version-injector.ts +39 -23
  239. package/src/vite/plugins/version-plugin.ts +59 -2
  240. package/src/vite/plugins/virtual-entries.ts +2 -2
  241. package/src/vite/rango.ts +116 -29
  242. package/src/vite/router-discovery.ts +750 -100
  243. package/src/vite/utils/ast-handler-extract.ts +15 -15
  244. package/src/vite/utils/banner.ts +1 -1
  245. package/src/vite/utils/bundle-analysis.ts +4 -2
  246. package/src/vite/utils/client-chunks.ts +190 -0
  247. package/src/vite/utils/forward-user-plugins.ts +193 -0
  248. package/src/vite/utils/manifest-utils.ts +21 -5
  249. package/src/vite/utils/package-resolution.ts +41 -1
  250. package/src/vite/utils/prerender-utils.ts +5 -4
  251. package/src/vite/utils/shared-utils.ts +107 -26
  252. package/src/browser/action-response-classifier.ts +0 -99
@@ -0,0 +1,126 @@
1
+ /**
2
+ * RequestScope: the fields every user-facing context shares.
3
+ *
4
+ * A handler, middleware, loader, response handler, and the ALS-bound
5
+ * RequestContext are all different phases of the same request, and they
6
+ * all carry the same set of request-scoped capabilities: the raw Request,
7
+ * the parsed URL pair (`url` is cleaned of internal `_rsc*` params,
8
+ * `originalUrl` retains them), pathname/searchParams, platform bindings
9
+ * (`env`), and two escape hatches for work that outlives the response
10
+ * (`waitUntil`) or needs the raw Cloudflare runtime object
11
+ * (`executionContext`).
12
+ *
13
+ * Each public context type intersects `RequestScope<TEnv>` with its own
14
+ * phase-specific fields (e.g. `params`/`reverse` on HandlerContext,
15
+ * `headers`/`header()` on MiddlewareContext). That keeps platform surface
16
+ * in one place and lets the next runtime escape hatch we need land in
17
+ * one file instead of four.
18
+ */
19
+
20
+ import type { DefaultEnv } from "./global-namespace.js";
21
+
22
+ /**
23
+ * Minimal subset of Cloudflare Workers' ExecutionContext that the router
24
+ * uses. Defined locally so the package does not depend on
25
+ * `@cloudflare/workers-types`. Consumers that want the full type can cast.
26
+ *
27
+ * On non-Cloudflare runtimes (Node, dev server, tests), this is undefined
28
+ * — portable apps should prefer `ctx.waitUntil(...)`, which degrades
29
+ * gracefully. `ctx.executionContext` is the escape hatch for libraries
30
+ * (MCP, Durable Object routing, etc.) that type their arguments as the
31
+ * raw ExecutionContext.
32
+ */
33
+ export interface ExecutionContext {
34
+ waitUntil(promise: Promise<any>): void;
35
+ passThroughOnException(): void;
36
+ }
37
+
38
+ /**
39
+ * Fallback `waitUntil` body used when no Cloudflare `ExecutionContext`
40
+ * is available (Node, dev, tests). Runs the work fire-and-forget and
41
+ * logs errors so they don't silently swallow.
42
+ *
43
+ * Exported so every `waitUntil` call site degrades identically instead
44
+ * of inventing its own fallback policy.
45
+ */
46
+ export function fireAndForgetWaitUntil(fn: () => Promise<void>): void {
47
+ fn().catch((err) =>
48
+ console.error("[waitUntil] Background task failed:", err),
49
+ );
50
+ }
51
+
52
+ /**
53
+ * Fields present on every user-facing request context.
54
+ *
55
+ * @template TEnv - Platform bindings type (Cloudflare env, etc.).
56
+ */
57
+ export interface RequestScope<TEnv = DefaultEnv> {
58
+ /**
59
+ * The original incoming Request object (transport URL intact).
60
+ * Use `url` / `searchParams` for application logic — those have
61
+ * internal `_rsc*` params stripped. `request` preserves the raw URL
62
+ * when you need original headers, method, or body.
63
+ */
64
+ request: Request;
65
+
66
+ /**
67
+ * The request URL with internal `_rsc*` transport params stripped.
68
+ * Use this for routing, link generation, and display.
69
+ */
70
+ url: URL;
71
+
72
+ /**
73
+ * The original request URL with all parameters intact, including
74
+ * internal `_rsc*` transport params. Use `url` for application logic
75
+ * — this is only needed for advanced cases like debugging or custom
76
+ * cache keying.
77
+ */
78
+ originalUrl: URL;
79
+
80
+ /** URL pathname (same as `url.pathname`). */
81
+ pathname: string;
82
+
83
+ /**
84
+ * Query parameters from the URL (system params like `_rsc*` are
85
+ * filtered). Always a standard `URLSearchParams` instance.
86
+ */
87
+ searchParams: URLSearchParams;
88
+
89
+ /**
90
+ * Platform bindings (DB, KV, secrets, etc.). On Cloudflare Workers
91
+ * these are the `env` object passed to the Worker's `fetch()` handler.
92
+ */
93
+ env: TEnv;
94
+
95
+ /**
96
+ * Schedule work to run after the response is sent.
97
+ * On Cloudflare Workers, delegates to `executionContext.waitUntil()`.
98
+ * On Node / dev / tests, runs as fire-and-forget with error logging.
99
+ *
100
+ * @example
101
+ * ```typescript
102
+ * ctx.waitUntil(async () => {
103
+ * await cacheStore.set(key, data, ttl);
104
+ * });
105
+ * ```
106
+ */
107
+ waitUntil(fn: () => Promise<void>): void;
108
+
109
+ /**
110
+ * Raw Cloudflare Workers `ExecutionContext`, when running on a
111
+ * Cloudflare-compatible runtime. Undefined elsewhere.
112
+ *
113
+ * Escape hatch for libraries that type their arguments as
114
+ * `ExecutionContext` (MCP `fetch`, `routeAgentRequest`, etc.).
115
+ * For the common "do work after the response" case, prefer
116
+ * `ctx.waitUntil(...)` — it is platform-neutral.
117
+ *
118
+ * @example
119
+ * ```typescript
120
+ * path.any("/mcp", (ctx) =>
121
+ * emailMcp.fetch(ctx.request, ctx.env, ctx.executionContext!),
122
+ * );
123
+ * ```
124
+ */
125
+ executionContext?: ExecutionContext;
126
+ }
@@ -10,7 +10,10 @@ export type ViewTransitionClass = Record<string, string> | string;
10
10
 
11
11
  /**
12
12
  * Configuration for React's <ViewTransition> component.
13
- * Maps directly to ViewTransitionProps (minus children/ref/callbacks).
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
  /**
@@ -62,6 +79,14 @@ export interface ResolvedSegment {
62
79
  notFoundInfo?: NotFoundInfo; // For notFound segments: the not found information
63
80
  // Mount path from include() scope, used for MountContext.Provider wrapping
64
81
  mountPath?: string;
82
+ /**
83
+ * @internal Server-side marker: true when the segment's handler actually ran
84
+ * this request (not skipped via the revalidate cache path). Used by
85
+ * match-result.ts to populate `MatchResult.resolvedIds` for client-side
86
+ * handle-bucket cleanup. Stripped from the wire payload before serialization
87
+ * — never reaches the client.
88
+ */
89
+ _handlerRan?: boolean;
65
90
  }
66
91
 
67
92
  /**
@@ -116,6 +141,15 @@ export interface MatchResult {
116
141
  segments: ResolvedSegment[];
117
142
  matched: string[];
118
143
  diff: string[];
144
+ /**
145
+ * Every segment id whose handler actually ran on the server this request,
146
+ * including ones with `component === null` that get filtered out of
147
+ * `segments`/`diff` to avoid wasted bytes. Drives the client's handle-
148
+ * cleanup pass — a slot that re-resolves and pushes nothing must clear
149
+ * its previous handle bucket, but `diff` doesn't carry it because the
150
+ * segment payload doesn't either. A superset of `diff`.
151
+ */
152
+ resolvedIds: string[];
119
153
  /**
120
154
  * Merged route params from all matched segments
121
155
  * Available for use by the handler after route matching
@@ -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
- * Process an IncludeItem by executing its nested patterns with prefixes
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
- * Lazy includes are kept as-is (not expanded) for the router to handle later.
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
- const includeItem = item as IncludeItem & {
60
- _expanded?: AllUseItems[];
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
- * By default, include() IMMEDIATELY expands the nested patterns. This ensures
96
- * that routes from included patterns inherit the correct parent context
97
- * (the layout they're included in).
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 store = getContext();
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 RSCRouter.GeneratedRouteMap extends never
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 RSCRouter.GeneratedRouteMap & string) | `.${string}`,
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
- * Attach a ViewTransition boundary to the current segment or a group of routes
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;
@@ -1,16 +1,11 @@
1
1
  import type { ReactNode } from "react";
2
2
  import type { Handler } from "../types.js";
3
- import type {
4
- AllUseItems,
5
- RouteItem,
6
- RouteUseItem,
7
- UseItems,
8
- } from "../route-types.js";
3
+ import type { RouteItem, RouteUseItem, UseItems } from "../route-types.js";
9
4
  import {
10
- getContext,
11
5
  getUrlPrefix,
12
6
  getNamePrefix,
13
7
  getRootScoped,
8
+ requireDslContext,
14
9
  } from "../server/context";
15
10
  import { invariant, DataNotFoundError } from "../errors";
16
11
  import { validateUserRouteName } from "../route-name.js";
@@ -39,35 +34,10 @@ import {
39
34
  resolveHandlerUse,
40
35
  mergeHandlerUse,
41
36
  } from "../route-definition/resolve-handler-use.js";
42
-
43
- /**
44
- * Check if a value is a valid use item
45
- */
46
- const isValidUseItem = (item: any): item is AllUseItems | undefined | null => {
47
- return (
48
- typeof item === "undefined" ||
49
- item === null ||
50
- (item &&
51
- typeof item === "object" &&
52
- "type" in item &&
53
- [
54
- "layout",
55
- "route",
56
- "middleware",
57
- "revalidate",
58
- "parallel",
59
- "intercept",
60
- "loader",
61
- "loading",
62
- "errorBoundary",
63
- "notFoundBoundary",
64
- "when",
65
- "cache",
66
- "transition",
67
- "include",
68
- ].includes(item.type))
69
- );
70
- };
37
+ import {
38
+ emptySegmentBase,
39
+ runAndValidateUseItems,
40
+ } from "../route-definition/dsl-helpers.js";
71
41
 
72
42
  /**
73
43
  * Apply URL prefix to a pattern
@@ -112,9 +82,9 @@ export function createPathHelper<TEnv>(): PathFn<TEnv> {
112
82
  optionsOrUse?: PathOptions | (() => UseItems<RouteUseItem>),
113
83
  maybeUse?: () => UseItems<RouteUseItem>,
114
84
  ): RouteItem => {
115
- const store = getContext();
116
- const ctx = store.getStore();
117
- if (!ctx) throw new Error("path() must be called inside urls()");
85
+ const { store, ctx } = requireDslContext(
86
+ "path() must be called inside urls()",
87
+ );
118
88
 
119
89
  invariant(
120
90
  !ctx.parent || ctx.parent.type !== "parallel",
@@ -214,6 +184,7 @@ export function createPathHelper<TEnv>(): PathFn<TEnv> {
214
184
  : () => handler;
215
185
 
216
186
  const entry = {
187
+ ...emptySegmentBase(),
217
188
  id: namespace,
218
189
  shortCode: store.getShortCode("route"),
219
190
  type: "route" as const,
@@ -221,15 +192,6 @@ export function createPathHelper<TEnv>(): PathFn<TEnv> {
221
192
  handler: wrappedHandler,
222
193
  // Store the PREFIXED pattern for route matching
223
194
  pattern: prefixedPattern,
224
- loading: undefined,
225
- middleware: [],
226
- revalidate: [],
227
- errorBoundary: [],
228
- notFoundBoundary: [],
229
- layout: [],
230
- parallel: {},
231
- intercept: [],
232
- loader: [],
233
195
  ...(urlPrefix ? { mountPath: urlPrefix } : {}),
234
196
  ...(isPassthroughHandler(handler)
235
197
  ? {
@@ -301,10 +263,13 @@ export function createPathHelper<TEnv>(): PathFn<TEnv> {
301
263
 
302
264
  // Run merged use callback (handler.use defaults + explicit use) if present
303
265
  if (mergedUse) {
304
- const result = store.run(namespace, entry, mergedUse)?.flat(3);
305
- invariant(
306
- Array.isArray(result) && result.every((item) => isValidUseItem(item)),
307
- `path() use() callback must return an array of use items [${namespace}]`,
266
+ const result = runAndValidateUseItems(
267
+ store,
268
+ namespace,
269
+ entry,
270
+ mergedUse,
271
+ "path",
272
+ "use",
308
273
  );
309
274
  return { name: namespace, type: "route", uses: result } as RouteItem;
310
275
  }
@@ -1,10 +1,5 @@
1
- import type { ReactNode } from "react";
2
- import type { Handler, TrailingSlashMode } from "../types.js";
3
- import type {
4
- AllUseItems,
5
- RouteUseItem,
6
- UrlPatternsBrand,
7
- } from "../route-types.js";
1
+ import type { TrailingSlashMode } from "../types.js";
2
+ import type { AllUseItems, UrlPatternsBrand } from "../route-types.js";
8
3
  import type { SearchSchema } from "../search-params.js";
9
4
  import { RESPONSE_TYPE } from "./response-types.js";
10
5
  import type { DefaultEnv } from "../types.js";
@@ -54,16 +49,6 @@ export interface PathOptions<
54
49
  [RESPONSE_TYPE]?: string;
55
50
  }
56
51
 
57
- /**
58
- * Internal representation of a URL pattern definition
59
- */
60
- export interface PathDefinition {
61
- pattern: string;
62
- name?: string;
63
- handler: ReactNode | Handler<any, any, any>;
64
- use?: RouteUseItem[];
65
- }
66
-
67
52
  /**
68
53
  * Result of urls() - contains the route definitions
69
54
  */
@@ -72,8 +57,6 @@ export interface UrlPatterns<
72
57
  TRoutes extends Record<string, any> = Record<string, string>,
73
58
  TResponses extends Record<string, unknown> = Record<string, unknown>,
74
59
  > {
75
- /** Internal: route definitions */
76
- readonly definitions: PathDefinition[];
77
60
  /** Internal: compiled handler function */
78
61
  readonly handler: () => AllUseItems[];
79
62
  /** Internal: trailing slash config per route name */
@@ -88,6 +71,40 @@ export interface UrlPatterns<
88
71
  readonly _responses?: TResponses;
89
72
  }
90
73
 
74
+ /**
75
+ * Extract the phantom env type carried by a UrlPatterns value.
76
+ */
77
+ export type UrlPatternsEnv<T> =
78
+ T extends UrlPatterns<infer TEnv, any, any> ? TEnv : never;
79
+
80
+ /**
81
+ * Guards `routes()` env compatibility without over-constraining.
82
+ *
83
+ * - An env-agnostic block (its env is `unknown` — e.g. a shared urls() module,
84
+ * or an app that does not augment `Rango.Env`) attaches to any router.
85
+ * - A block carrying a concrete env is accepted only when the router env
86
+ * (`TRouterEnv`) satisfies it; resolves to `never` otherwise, so a
87
+ * `urls<{ DB: D1Database }>()` cannot be mounted on a `createRouter<{}>()`.
88
+ *
89
+ * Use as `patterns: T & EnvCompatible<T, TEnv>` so `T` still infers from the
90
+ * argument — a bare `EnvCompatible<T, TEnv>` parameter sits in a non-inferrable
91
+ * conditional position and would collapse `T` to its constraint.
92
+ *
93
+ * Known limitation: `TRouterEnv extends ...` distributes over a union router env,
94
+ * so a `urls<A>()` block is accepted on `createRouter<A | B>()` even though the
95
+ * `B` arm cannot supply `A`'s env. Suppressing distribution with
96
+ * `[TRouterEnv] extends [...]` would close that edge but breaks the common
97
+ * generic-`TEnv` call sites (a deferred type parameter can't resolve the tuple
98
+ * conditional, so the intersection stops reducing to `T`). A router has one env,
99
+ * so a union env is not a supported pattern; the distributive form is kept.
100
+ */
101
+ export type EnvCompatible<TPatterns, TRouterEnv> =
102
+ unknown extends UrlPatternsEnv<TPatterns>
103
+ ? TPatterns
104
+ : TRouterEnv extends UrlPatternsEnv<TPatterns>
105
+ ? TPatterns
106
+ : never;
107
+
91
108
  /**
92
109
  * Options for include()
93
110
  */
@@ -5,6 +5,7 @@ import type {
5
5
  DefaultVars,
6
6
  } from "../types/global-namespace.js";
7
7
  import type { UseItems, ResponseRouteUseItem } from "../route-types.js";
8
+ import type { RequestScope } from "../types/request-scope.js";
8
9
 
9
10
  /**
10
11
  * Reverse function for response handler contexts.
@@ -31,21 +32,32 @@ type ResponseReverseFunction = [DefaultReverseRouteMap] extends [
31
32
  * Symbol marking a route as a response route (non-RSC).
32
33
  * Stored on PathOptions and UrlPatterns to signal the trie to short-circuit.
33
34
  */
34
- export const RESPONSE_TYPE: unique symbol = Symbol.for(
35
- "rangojs.responseType",
36
- ) as any;
35
+ export const RESPONSE_TYPE: unique symbol = Symbol.for("rangojs.responseType");
37
36
 
38
37
  /**
39
- * Handler that must return Response (not ReactNode).
40
- * Used by path.image(), path.stream(), path.any() (binary/streaming data).
38
+ * Shared shape of a response-route handler: a function returning TReturn (or a
39
+ * promise of it), plus an optional composable `use` thunk merged at mount time.
41
40
  */
42
- export type ResponseHandler<TParams = Record<string, string>, TEnv = any> = ((
41
+ type ResponseHandlerOf<
42
+ TReturn,
43
+ TParams = Record<string, string>,
44
+ TEnv = any,
45
+ > = ((
43
46
  ctx: ResponseHandlerContext<TParams, TEnv>,
44
- ) => Response | Promise<Response>) & {
47
+ ) => TReturn | Promise<TReturn>) & {
45
48
  /** Composable default DSL items merged when the handler is mounted. */
46
49
  use?: () => UseItems<ResponseRouteUseItem>;
47
50
  };
48
51
 
52
+ /**
53
+ * Handler that must return Response (not ReactNode).
54
+ * Used by path.image(), path.stream(), path.any() (binary/streaming data).
55
+ */
56
+ export type ResponseHandler<
57
+ TParams = Record<string, string>,
58
+ TEnv = any,
59
+ > = ResponseHandlerOf<Response, TParams, TEnv>;
60
+
49
61
  /**
50
62
  * JSON-serializable value type for auto-wrap support.
51
63
  */
@@ -64,12 +76,7 @@ export type JsonValue =
64
76
  export type JsonResponseHandler<
65
77
  TParams = Record<string, string>,
66
78
  TEnv = any,
67
- > = ((
68
- ctx: ResponseHandlerContext<TParams, TEnv>,
69
- ) => JsonValue | Response | Promise<JsonValue | Response>) & {
70
- /** Composable default DSL items merged when the handler is mounted. */
71
- use?: () => UseItems<ResponseRouteUseItem>;
72
- };
79
+ > = ResponseHandlerOf<JsonValue | Response, TParams, TEnv>;
73
80
 
74
81
  /**
75
82
  * Handler for text-based response routes (text, html, xml).
@@ -78,12 +85,7 @@ export type JsonResponseHandler<
78
85
  export type TextResponseHandler<
79
86
  TParams = Record<string, string>,
80
87
  TEnv = any,
81
- > = ((
82
- ctx: ResponseHandlerContext<TParams, TEnv>,
83
- ) => string | Response | Promise<string | Response>) & {
84
- /** Composable default DSL items merged when the handler is mounted. */
85
- use?: () => UseItems<ResponseRouteUseItem>;
86
- };
88
+ > = ResponseHandlerOf<string | Response, TParams, TEnv>;
87
89
 
88
90
  /**
89
91
  * Lighter handler context for response routes.
@@ -93,19 +95,10 @@ export type TextResponseHandler<
93
95
  export interface ResponseHandlerContext<
94
96
  TParams = Record<string, string>,
95
97
  TEnv = any,
96
- > {
97
- request: Request;
98
+ > extends RequestScope<TEnv> {
98
99
  params: TParams;
99
100
  /** @internal Phantom property for params type invariance. Prevents mounting handlers on wrong routes. */
100
101
  readonly _paramCheck?: (params: TParams) => TParams;
101
- /** Platform bindings (DB, KV, secrets, etc.). */
102
- env: TEnv;
103
- /** Query parameters from the URL (system params like `_rsc*` are filtered). */
104
- searchParams: URLSearchParams;
105
- /** The full URL object (with system params filtered). */
106
- url: URL;
107
- /** The pathname portion of the request URL. */
108
- pathname: string;
109
102
  reverse: ResponseReverseFunction;
110
103
  /** Read a variable set by middleware via ctx.set(key, value) or ctx.set(ContextVar, value). */
111
104
  get: {