@rangojs/router 0.0.0-experimental.84 → 0.0.0-experimental.86

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 (43) hide show
  1. package/README.md +50 -20
  2. package/dist/vite/index.js +19 -9
  3. package/package.json +14 -15
  4. package/skills/breadcrumbs/SKILL.md +3 -1
  5. package/skills/hooks/SKILL.md +4 -2
  6. package/skills/links/SKILL.md +88 -16
  7. package/skills/loader/SKILL.md +35 -2
  8. package/skills/typesafety/SKILL.md +3 -1
  9. package/src/browser/app-shell.ts +52 -0
  10. package/src/browser/navigation-bridge.ts +51 -2
  11. package/src/browser/navigation-store.ts +25 -1
  12. package/src/browser/partial-update.ts +20 -1
  13. package/src/browser/prefetch/cache.ts +16 -0
  14. package/src/browser/rango-state.ts +53 -13
  15. package/src/browser/react/NavigationProvider.tsx +44 -9
  16. package/src/browser/react/use-router.ts +8 -1
  17. package/src/browser/rsc-router.tsx +34 -6
  18. package/src/browser/types.ts +13 -0
  19. package/src/cache/cf/cf-cache-store.ts +5 -7
  20. package/src/index.rsc.ts +3 -0
  21. package/src/index.ts +3 -0
  22. package/src/outlet-context.ts +1 -1
  23. package/src/reverse.ts +3 -2
  24. package/src/router/handler-context.ts +20 -3
  25. package/src/router/lazy-includes.ts +1 -1
  26. package/src/router/loader-resolution.ts +3 -0
  27. package/src/router/match-api.ts +3 -3
  28. package/src/router/middleware-types.ts +2 -22
  29. package/src/router/middleware.ts +18 -3
  30. package/src/router/pattern-matching.ts +60 -9
  31. package/src/router/trie-matching.ts +10 -4
  32. package/src/router/url-params.ts +49 -0
  33. package/src/router.ts +1 -2
  34. package/src/rsc/handler.ts +2 -1
  35. package/src/rsc/response-route-handler.ts +3 -0
  36. package/src/server/request-context.ts +10 -42
  37. package/src/types/handler-context.ts +2 -34
  38. package/src/types/loader-types.ts +2 -6
  39. package/src/types/request-scope.ts +126 -0
  40. package/src/urls/response-types.ts +2 -10
  41. package/src/vite/rango.ts +23 -7
  42. package/src/vite/utils/banner.ts +1 -1
  43. package/src/vite/utils/package-resolution.ts +1 -1
@@ -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
+ }
@@ -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.
@@ -93,19 +94,10 @@ export type TextResponseHandler<
93
94
  export interface ResponseHandlerContext<
94
95
  TParams = Record<string, string>,
95
96
  TEnv = any,
96
- > {
97
- request: Request;
97
+ > extends RequestScope<TEnv> {
98
98
  params: TParams;
99
99
  /** @internal Phantom property for params type invariance. Prevents mounting handlers on wrong routes. */
100
100
  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
101
  reverse: ResponseReverseFunction;
110
102
  /** Read a variable set by middleware via ctx.set(key, value) or ctx.set(ContextVar, value). */
111
103
  get: {
package/src/vite/rango.ts CHANGED
@@ -12,6 +12,7 @@ import { VIRTUAL_IDS } from "./plugins/virtual-entries.js";
12
12
  import {
13
13
  getExcludeDeps,
14
14
  getPackageAliases,
15
+ getPublishedPackageName,
15
16
  } from "./utils/package-resolution.js";
16
17
  import { findRouterFiles } from "../build/generate-route-types.js";
17
18
  import { createVersionPlugin } from "./plugins/version-plugin.js";
@@ -72,6 +73,13 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
72
73
  "@vitejs/plugin-rsc/vendor/react-server-dom/client.browser",
73
74
  ];
74
75
 
76
+ // Vite supports a nested `A > B` syntax in optimizeDeps.include that resolves
77
+ // B from A's location. We anchor transitive deps (rsc-html-stream,
78
+ // @vitejs/plugin-rsc/vendor/*) to @rangojs/router so pnpm consumers — where
79
+ // these aren't visible at the app root — can still pre-bundle them.
80
+ const pkg = getPublishedPackageName();
81
+ const nested = (spec: string) => `${pkg} > ${spec}`;
82
+
75
83
  // Mutable ref for router path (node preset only).
76
84
  // Set immediately when user-specified, or populated by the auto-discover
77
85
  // config() hook using Vite's resolved root.
@@ -126,7 +134,7 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
126
134
  // Pre-bundle rsc-html-stream to prevent discovery during first request
127
135
  // Exclude rsc-router modules to ensure same Context instance
128
136
  optimizeDeps: {
129
- include: ["rsc-html-stream/client"],
137
+ include: [nested("rsc-html-stream/client")],
130
138
  exclude: excludeDeps,
131
139
  esbuildOptions: sharedEsbuildOptions,
132
140
  },
@@ -151,8 +159,10 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
151
159
  "react-dom/static.edge",
152
160
  "react/jsx-runtime",
153
161
  "react/jsx-dev-runtime",
154
- "rsc-html-stream/server",
155
- "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge",
162
+ nested("rsc-html-stream/server"),
163
+ nested(
164
+ "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge",
165
+ ),
156
166
  ],
157
167
  exclude: excludeDeps,
158
168
  esbuildOptions: sharedEsbuildOptions,
@@ -167,7 +177,9 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
167
177
  "react",
168
178
  "react/jsx-runtime",
169
179
  "react/jsx-dev-runtime",
170
- "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge",
180
+ nested(
181
+ "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge",
182
+ ),
171
183
  ],
172
184
  exclude: excludeDeps,
173
185
  esbuildOptions: sharedEsbuildOptions,
@@ -280,7 +292,7 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
280
292
  "react-dom",
281
293
  "react/jsx-runtime",
282
294
  "react/jsx-dev-runtime",
283
- "rsc-html-stream/client",
295
+ nested("rsc-html-stream/client"),
284
296
  ],
285
297
  exclude: excludeDeps,
286
298
  esbuildOptions: sharedEsbuildOptions,
@@ -297,7 +309,9 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
297
309
  "react-dom/static.edge",
298
310
  "react/jsx-runtime",
299
311
  "react/jsx-dev-runtime",
300
- "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge",
312
+ nested(
313
+ "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge",
314
+ ),
301
315
  ],
302
316
  exclude: excludeDeps,
303
317
  esbuildOptions: sharedEsbuildOptions,
@@ -310,7 +324,9 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
310
324
  "react",
311
325
  "react/jsx-runtime",
312
326
  "react/jsx-dev-runtime",
313
- "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge",
327
+ nested(
328
+ "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge",
329
+ ),
314
330
  ],
315
331
  esbuildOptions: sharedEsbuildOptions,
316
332
  },
@@ -1,4 +1,4 @@
1
- import packageJson from "../../../package.json" with { type: "json" };
1
+ import packageJson from "../../../package.json";
2
2
 
3
3
  export const rangoVersion: string = packageJson.version;
4
4
 
@@ -7,7 +7,7 @@
7
7
 
8
8
  import { existsSync } from "node:fs";
9
9
  import { resolve } from "node:path";
10
- import packageJson from "../../../package.json" with { type: "json" };
10
+ import packageJson from "../../../package.json";
11
11
 
12
12
  /**
13
13
  * The canonical name used in virtual entries (without scope)