@ivogt/rsc-router 0.0.0-experimental.13 → 0.0.0-experimental.14

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.
@@ -675,7 +675,7 @@ import { resolve } from "node:path";
675
675
  // package.json
676
676
  var package_default = {
677
677
  name: "@ivogt/rsc-router",
678
- version: "0.0.0-experimental.13",
678
+ version: "0.0.0-experimental.14",
679
679
  type: "module",
680
680
  description: "Type-safe RSC router with partial rendering support",
681
681
  author: "Ivo Todorov",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ivogt/rsc-router",
3
- "version": "0.0.0-experimental.13",
3
+ "version": "0.0.0-experimental.14",
4
4
  "type": "module",
5
5
  "description": "Type-safe RSC router with partial rendering support",
6
6
  "author": "Ivo Todorov",
package/src/href.ts CHANGED
@@ -8,19 +8,22 @@ export type SanitizePrefix<T extends string> = T extends `/${infer P}` ? P : T;
8
8
 
9
9
  /**
10
10
  * Helper type to merge multiple route definitions into a single accumulated type.
11
- * Use this to define your app's complete route map for type-safe router.href().
11
+ * Note: When using createRSCRouter, types accumulate automatically through the
12
+ * builder chain, so this type is typically not needed.
12
13
  *
13
14
  * @example
14
15
  * ```typescript
15
- * import { homeRoutes, blogRoutes, shopRoutes } from "./routes";
16
- *
16
+ * // Manual type merging (rarely needed):
17
17
  * type AppRoutes = MergeRoutes<[
18
18
  * typeof homeRoutes,
19
- * PrefixedRoutes<typeof blogRoutes, "blog">,
20
- * PrefixedRoutes<typeof shopRoutes, "shop">,
19
+ * PrefixRoutePatterns<typeof blogRoutes, "/blog">,
21
20
  * ]>;
22
21
  *
23
- * export const router = createRSCRouter<AppEnv>() as RSCRouter<AppEnv, AppRoutes>;
22
+ * // Preferred: Let router accumulate types automatically
23
+ * const router = createRSCRouter<AppEnv>()
24
+ * .routes(homeRoutes).map(...)
25
+ * .routes("/blog", blogRoutes).map(...);
26
+ * type AppRoutes = typeof router.routeMap;
24
27
  * ```
25
28
  */
26
29
  export type MergeRoutes<T extends Record<string, string>[]> = T extends [
@@ -111,9 +114,10 @@ export type HrefFunction<TRoutes extends Record<string, string>> = {
111
114
  *
112
115
  * @example
113
116
  * ```typescript
114
- * const href = createHref(mergedRouteMap);
115
- * href("shop.cart"); // "/shop/cart"
116
- * href("shop.products.detail", { slug: "my-product" }); // "/shop/product/my-product"
117
+ * // Given routes: { cart: "/shop/cart", detail: "/shop/product/:slug" }
118
+ * const href = createHref(routeMap);
119
+ * href("cart"); // "/shop/cart"
120
+ * href("detail", { slug: "my-product" }); // "/shop/product/my-product"
117
121
  * ```
118
122
  */
119
123
  export function createHref<TRoutes extends Record<string, string>>(
@@ -1248,6 +1248,30 @@ export function map<const T extends RouteDefinition, TEnv = DefaultEnv>(
1248
1248
  };
1249
1249
  }
1250
1250
 
1251
+ /**
1252
+ * Create RouteHelpers for inline route definitions
1253
+ * Used internally by router.map() for inline handler syntax
1254
+ */
1255
+ export function createRouteHelpers<
1256
+ T extends RouteDefinition,
1257
+ TEnv,
1258
+ >(): RouteHelpers<T, TEnv> {
1259
+ return {
1260
+ route: createRouteHelper<T, TEnv>(),
1261
+ layout: createLayoutHelper<TEnv>(),
1262
+ parallel: createParallelHelper<TEnv>(),
1263
+ intercept: createInterceptHelper<T, TEnv>(),
1264
+ middleware: createMiddlewareHelper<TEnv>(),
1265
+ revalidate: createRevalidateHelper<TEnv>(),
1266
+ loader: createLoaderHelper<TEnv>(),
1267
+ loading: createLoadingHelper(),
1268
+ errorBoundary: createErrorBoundaryHelper<TEnv>(),
1269
+ notFoundBoundary: createNotFoundBoundaryHelper<TEnv>(),
1270
+ when: createWhenHelper(),
1271
+ cache: createCacheHelper(),
1272
+ };
1273
+ }
1274
+
1251
1275
  /**
1252
1276
  * Create a loader definition
1253
1277
  *
@@ -23,7 +23,7 @@
23
23
  * ```
24
24
  */
25
25
 
26
- import type { PrefixedRoutes } from "./href.js";
26
+ import type { PrefixRoutePatterns } from "./href.js";
27
27
 
28
28
  /**
29
29
  * Route map builder interface
@@ -37,12 +37,14 @@ export interface RouteMapBuilder<TRoutes extends Record<string, string> = {}> {
37
37
  add<T extends Record<string, string>>(routes: T): RouteMapBuilder<TRoutes & T>;
38
38
 
39
39
  /**
40
- * Add routes with prefix
40
+ * Add routes with prefix (only URL patterns are prefixed, keys stay unchanged)
41
+ * @param routes - Route definitions to add
42
+ * @param prefix - URL prefix WITHOUT leading slash (e.g., "blog" not "/blog")
41
43
  */
42
44
  add<T extends Record<string, string>, P extends string>(
43
45
  routes: T,
44
46
  prefix: P
45
- ): RouteMapBuilder<TRoutes & PrefixedRoutes<T, P>>;
47
+ ): RouteMapBuilder<TRoutes & PrefixRoutePatterns<T, `/${P}`>>;
46
48
 
47
49
  /**
48
50
  * The accumulated route map (for typeof extraction in module augmentation)
@@ -52,25 +54,29 @@ export interface RouteMapBuilder<TRoutes extends Record<string, string> = {}> {
52
54
 
53
55
  /**
54
56
  * Add routes to a map with optional prefix
57
+ * Keys stay unchanged for composability - only URL patterns get prefixed.
55
58
  *
56
59
  * @param routeMap - The map to add routes to
57
60
  * @param routes - Routes to add
58
- * @param prefix - Optional prefix for keys and paths
61
+ * @param prefix - Optional prefix for URL paths WITHOUT leading slash (keys stay unchanged)
59
62
  */
60
63
  function addRoutes(
61
64
  routeMap: Record<string, string>,
62
65
  routes: Record<string, string>,
63
66
  prefix: string = ""
64
67
  ): void {
68
+ // Normalize prefix: remove leading slash if accidentally provided
69
+ const normalizedPrefix = prefix.startsWith("/") ? prefix.slice(1) : prefix;
70
+
65
71
  for (const [key, pattern] of Object.entries(routes)) {
66
- const prefixedKey = prefix ? `${prefix}.${key}` : key;
67
72
  const prefixedPattern =
68
- prefix && pattern !== "/"
69
- ? `/${prefix}${pattern}`
70
- : prefix && pattern === "/"
71
- ? `/${prefix}`
73
+ normalizedPrefix && pattern !== "/"
74
+ ? `/${normalizedPrefix}${pattern}`
75
+ : normalizedPrefix && pattern === "/"
76
+ ? `/${normalizedPrefix}`
72
77
  : pattern;
73
- routeMap[prefixedKey] = prefixedPattern;
78
+ // Use original key - enables reusable route modules
79
+ routeMap[key] = prefixedPattern;
74
80
  }
75
81
  }
76
82
 
@@ -5,7 +5,9 @@
5
5
  */
6
6
 
7
7
  import { invariant, RouteNotFoundError } from "../errors";
8
+ import { createRouteHelpers } from "../route-definition";
8
9
  import { getContext, type EntryData, type MetricsStore } from "../server/context";
10
+ import MapRootLayout from "../server/root-layout";
9
11
  import type { RouteEntry } from "../types";
10
12
 
11
13
  /**
@@ -63,19 +65,39 @@ export async function loadManifest(
63
65
  Store.namespace || namespaceWithMount,
64
66
  Store.parent,
65
67
  async () => {
66
- const load = await entry.handler();
67
- if (
68
- load &&
69
- load !== null &&
70
- typeof load === "object" &&
71
- "default" in load
72
- ) {
73
- return load.default();
74
- }
75
- if (typeof load === "function") {
76
- return load();
68
+ // Create helpers - inline handlers use them, lazy handlers ignore them
69
+ const helpers = createRouteHelpers();
70
+
71
+ // Call handler with helpers - works for both inline and lazy
72
+ const result = entry.handler(helpers);
73
+
74
+ // Handle based on return type
75
+ if (result instanceof Promise) {
76
+ // Lazy: () => import(...) - returns Promise
77
+ const load = await result;
78
+ if (
79
+ load &&
80
+ load !== null &&
81
+ typeof load === "object" &&
82
+ "default" in load
83
+ ) {
84
+ // Promise<{ default: () => Array }> - e.g., dynamic import
85
+ // Pass helpers - functions that need them will use them,
86
+ // functions from route-definition's map() will ignore them
87
+ return load.default(helpers);
88
+ }
89
+ if (typeof load === "function") {
90
+ // Promise<() => Array>
91
+ return load(helpers);
92
+ }
93
+ // Promise<Array> - direct array from async handler
94
+ return load;
77
95
  }
78
- return load;
96
+
97
+ // Inline: ({ route }) => [...] - returns Array directly
98
+ // Wrap with layout (like map() from route-definition does)
99
+ // Flatten nested arrays from layout/route definitions
100
+ return [helpers.layout(MapRootLayout, () => result)].flat(3);
79
101
  }
80
102
  );
81
103
 
package/src/router.ts CHANGED
@@ -14,10 +14,14 @@ import {
14
14
  import {
15
15
  createHref,
16
16
  type HrefFunction,
17
- type PrefixedRoutes,
18
- type SanitizePrefix,
17
+ type PrefixRoutePatterns,
19
18
  } from "./href.js";
20
19
  import { registerRouteMap } from "./route-map-builder.js";
20
+ import {
21
+ createRouteHelpers,
22
+ type RouteHelpers,
23
+ } from "./route-definition.js";
24
+ import MapRootLayout from "./server/root-layout.js";
21
25
  import type { AllUseItems } from "./route-types.js";
22
26
  import {
23
27
  EntryData,
@@ -265,14 +269,131 @@ export interface RSCRouterOptions<TEnv = any> {
265
269
  | ((env: TEnv) => { store: SegmentCacheStore; enabled?: boolean });
266
270
  }
267
271
 
272
+ /**
273
+ * Type-level detection of conflicting route keys.
274
+ * Extracts keys that exist in both TExisting and TNew but with different URL patterns.
275
+ * Returns `never` if no conflicts exist.
276
+ *
277
+ * @example
278
+ * ```typescript
279
+ * ConflictingKeys<{ a: "/a" }, { a: "/b" }> // "a" (conflict - same key, different URLs)
280
+ * ConflictingKeys<{ a: "/a" }, { a: "/a" }> // never (no conflict - same key and URL)
281
+ * ConflictingKeys<{ a: "/a" }, { b: "/b" }> // never (no conflict - different keys)
282
+ * ```
283
+ */
284
+ type ConflictingKeys<
285
+ TExisting extends Record<string, string>,
286
+ TNew extends Record<string, string>
287
+ > = {
288
+ [K in keyof TExisting & keyof TNew]: TExisting[K] extends TNew[K]
289
+ ? TNew[K] extends TExisting[K]
290
+ ? never // Same value, no conflict
291
+ : K // Different values, conflict
292
+ : K; // Different values, conflict
293
+ }[keyof TExisting & keyof TNew];
294
+
295
+ /**
296
+ * Simplified route helpers for inline route definitions.
297
+ * Uses TRoutes (Record<string, string>) instead of RouteDefinition.
298
+ *
299
+ * Note: Some helpers use `any` for context types as a trade-off for simpler usage.
300
+ * The main type safety is in the `route` helper which enforces valid route names.
301
+ * For full type safety, use the standard map() API with separate handler files.
302
+ */
303
+ type InlineRouteHelpers<
304
+ TRoutes extends Record<string, string>,
305
+ TEnv,
306
+ > = {
307
+ /**
308
+ * Define a route handler for a specific route pattern
309
+ */
310
+ route: <K extends keyof TRoutes & string>(
311
+ name: K,
312
+ handler:
313
+ | ((ctx: HandlerContext<{}, TEnv>) => ReactNode | Promise<ReactNode>)
314
+ | ReactNode
315
+ ) => AllUseItems;
316
+
317
+ /**
318
+ * Define a layout that wraps child routes
319
+ */
320
+ layout: (
321
+ component: ReactNode | ((ctx: HandlerContext<any, TEnv>) => ReactNode | Promise<ReactNode>),
322
+ use?: () => AllUseItems[]
323
+ ) => AllUseItems;
324
+
325
+ /**
326
+ * Define parallel routes
327
+ */
328
+ parallel: (
329
+ slots: Record<`@${string}`, ReactNode | ((ctx: HandlerContext<any, TEnv>) => ReactNode | Promise<ReactNode>)>,
330
+ use?: () => AllUseItems[]
331
+ ) => AllUseItems;
332
+
333
+ /**
334
+ * Define route middleware
335
+ */
336
+ middleware: (fn: (ctx: any, next: () => Promise<void>) => Promise<void>) => AllUseItems;
337
+
338
+ /**
339
+ * Define revalidation handlers
340
+ */
341
+ revalidate: (fn: (ctx: any) => boolean | Promise<boolean>) => AllUseItems;
342
+
343
+ /**
344
+ * Define data loaders
345
+ */
346
+ loader: (loader: any, use?: () => AllUseItems[]) => AllUseItems;
347
+
348
+ /**
349
+ * Define loading states
350
+ */
351
+ loading: (component: ReactNode) => AllUseItems;
352
+
353
+ /**
354
+ * Define error boundaries
355
+ */
356
+ errorBoundary: (
357
+ handler: ReactNode | ((props: { error: Error }) => ReactNode)
358
+ ) => AllUseItems;
359
+
360
+ /**
361
+ * Define not found boundaries
362
+ */
363
+ notFoundBoundary: (
364
+ handler: ReactNode | ((props: { pathname: string }) => ReactNode)
365
+ ) => AllUseItems;
366
+
367
+ /**
368
+ * Define intercept routes
369
+ */
370
+ intercept: (
371
+ name: string,
372
+ handler: ReactNode | ((ctx: HandlerContext<any, TEnv>) => ReactNode | Promise<ReactNode>),
373
+ use?: () => AllUseItems[]
374
+ ) => AllUseItems;
375
+
376
+ /**
377
+ * Define when conditions for intercepts
378
+ */
379
+ when: (condition: (ctx: any) => boolean | Promise<boolean>) => AllUseItems;
380
+
381
+ /**
382
+ * Define cache configuration
383
+ */
384
+ cache: (config: { ttl?: number; swr?: number } | false, use?: () => AllUseItems[]) => AllUseItems;
385
+ };
386
+
268
387
  /**
269
388
  * Router builder for chaining .use() and .map()
270
389
  * TRoutes accumulates all registered route types through the chain
390
+ * TLocalRoutes contains the routes for the current .routes() call (for inline handler typing)
271
391
  */
272
392
  interface RouteBuilder<
273
393
  T extends RouteDefinition,
274
394
  TEnv,
275
395
  TRoutes extends Record<string, string>,
396
+ TLocalRoutes extends Record<string, string> = Record<string, string>,
276
397
  > {
277
398
  /**
278
399
  * Add middleware scoped to this mount
@@ -289,8 +410,34 @@ interface RouteBuilder<
289
410
  use(
290
411
  patternOrMiddleware: string | MiddlewareFn<TEnv>,
291
412
  middleware?: MiddlewareFn<TEnv>
292
- ): RouteBuilder<T, TEnv, TRoutes>;
413
+ ): RouteBuilder<T, TEnv, TRoutes, TLocalRoutes>;
293
414
 
415
+ /**
416
+ * Map routes to handlers
417
+ *
418
+ * Supports two patterns:
419
+ *
420
+ * 1. Lazy loading (code-split):
421
+ * ```typescript
422
+ * .routes(homeRoutes)
423
+ * .map(() => import("./handlers/home"))
424
+ * ```
425
+ *
426
+ * 2. Inline definition:
427
+ * ```typescript
428
+ * .routes({ index: "/", about: "/about" })
429
+ * .map(({ route }) => [
430
+ * route("index", () => <HomePage />),
431
+ * route("about", () => <AboutPage />),
432
+ * ])
433
+ * ```
434
+ */
435
+ // Inline definition overload - handler receives helpers (must be first for correct inference)
436
+ // Uses TLocalRoutes so route names don't need the prefix
437
+ map<H extends (helpers: InlineRouteHelpers<TLocalRoutes, TEnv>) => Array<AllUseItems>>(
438
+ handler: H
439
+ ): RSCRouter<TEnv, TRoutes>;
440
+ // Lazy loading overload - no parameters
294
441
  map(
295
442
  handler: () =>
296
443
  | Array<AllUseItems>
@@ -315,24 +462,40 @@ export interface RSCRouter<
315
462
  > {
316
463
  /**
317
464
  * Register routes with a prefix
318
- * Route types are accumulated through the chain
465
+ * Route keys stay unchanged, only URL patterns get the prefix applied.
466
+ * This enables composable route modules that work regardless of mount point.
467
+ *
468
+ * @throws Compile-time error if route keys conflict with previously registered routes
319
469
  */
320
470
  routes<TPrefix extends string, T extends ResolvedRouteMap<any>>(
321
471
  prefix: TPrefix,
322
472
  routes: T
323
- ): RouteBuilder<
324
- RouteDefinition,
325
- TEnv,
326
- TRoutes & PrefixedRoutes<T, SanitizePrefix<TPrefix>>
327
- >;
473
+ ): ConflictingKeys<TRoutes, T> extends never
474
+ ? RouteBuilder<
475
+ RouteDefinition,
476
+ TEnv,
477
+ TRoutes & PrefixRoutePatterns<T, TPrefix>,
478
+ T
479
+ >
480
+ : {
481
+ __error: `Route key conflict! Keys [${ConflictingKeys<TRoutes, T> & string}] already exist with different URL patterns.`;
482
+ hint: "Use unique key names for each route definition.";
483
+ };
328
484
 
329
485
  /**
330
486
  * Register routes without a prefix
331
487
  * Route types are accumulated through the chain
488
+ *
489
+ * @throws Compile-time error if route keys conflict with previously registered routes
332
490
  */
333
491
  routes<T extends ResolvedRouteMap<any>>(
334
492
  routes: T
335
- ): RouteBuilder<RouteDefinition, TEnv, TRoutes & T>;
493
+ ): ConflictingKeys<TRoutes, T> extends never
494
+ ? RouteBuilder<RouteDefinition, TEnv, TRoutes & T, T>
495
+ : {
496
+ __error: `Route key conflict! Keys [${ConflictingKeys<TRoutes, T> & string}] already exist with different URL patterns.`;
497
+ hint: "Use unique key names for each route definition.";
498
+ };
336
499
 
337
500
  /**
338
501
  * Add global middleware that runs on all routes
@@ -355,11 +518,13 @@ export interface RSCRouter<
355
518
  /**
356
519
  * Type-safe URL builder for registered routes
357
520
  * Types are inferred from the accumulated route registrations
521
+ * Route keys stay unchanged regardless of mount prefix.
358
522
  *
359
523
  * @example
360
524
  * ```typescript
361
- * router.href("shop.cart"); // "/shop/cart"
362
- * router.href("shop.products.detail", { slug: "widget" }); // "/shop/product/widget"
525
+ * // Given: .routes("/shop", { cart: "/cart", detail: "/product/:slug" })
526
+ * router.href("cart"); // "/shop/cart"
527
+ * router.href("detail", { slug: "widget" }); // "/shop/product/widget"
363
528
  * ```
364
529
  */
365
530
  href: HrefFunction<TRoutes>;
@@ -477,14 +642,16 @@ export interface RSCRouter<
477
642
  * });
478
643
  *
479
644
  * // Route types accumulate through the chain - no module augmentation needed!
645
+ * // Keys stay unchanged, only URL patterns get the prefix
480
646
  * router
481
647
  * .routes(homeRoutes) // accumulates homeRoutes
482
648
  * .map(() => import('./home'))
483
- * .routes('/shop', shopRoutes) // accumulates PrefixedRoutes<shopRoutes, "shop">
649
+ * .routes('/shop', shopRoutes) // accumulates shopRoutes with prefixed URLs
484
650
  * .map(() => import('./shop'));
485
651
  *
486
652
  * // router.href now has type-safe autocomplete for all registered routes
487
- * router.href("shop.cart");
653
+ * // Given shopRoutes = { cart: "/cart" }, href uses original key:
654
+ * router.href("cart"); // "/shop/cart"
488
655
  * ```
489
656
  */
490
657
  export function createRSCRouter<TEnv = any>(
@@ -3393,15 +3560,13 @@ export function createRSCRouter<TEnv = any>(
3393
3560
  function createRouteBuilder<TNewRoutes extends Record<string, string>>(
3394
3561
  prefix: string,
3395
3562
  routes: TNewRoutes
3396
- ): RouteBuilder<RouteDefinition, TEnv, TNewRoutes> {
3563
+ ): RouteBuilder<RouteDefinition, TEnv, any, TNewRoutes> {
3397
3564
  const currentMountIndex = mountIndex++;
3398
3565
 
3399
- // Merge routes into the href map with prefixes
3400
- // This enables type-safe router.href() calls
3566
+ // Merge routes into the href map
3567
+ // Keys stay unchanged for composability - only URL patterns get prefixed
3401
3568
  const routeEntries = routes as Record<string, string>;
3402
3569
  for (const [key, pattern] of Object.entries(routeEntries)) {
3403
- // Build prefixed key: "shop" + "cart" -> "shop.cart"
3404
- const prefixedKey = prefix ? `${prefix.slice(1)}.${key}` : key;
3405
3570
  // Build prefixed pattern: "/shop" + "/cart" -> "/shop/cart"
3406
3571
  const prefixedPattern =
3407
3572
  prefix && pattern !== "/"
@@ -3409,7 +3574,18 @@ export function createRSCRouter<TEnv = any>(
3409
3574
  : prefix && pattern === "/"
3410
3575
  ? prefix
3411
3576
  : pattern;
3412
- mergedRouteMap[prefixedKey] = prefixedPattern;
3577
+
3578
+ // Runtime validation: warn if key already exists with different pattern
3579
+ const existingPattern = mergedRouteMap[key];
3580
+ if (existingPattern !== undefined && existingPattern !== prefixedPattern) {
3581
+ console.warn(
3582
+ `[rsc-router] Route key conflict: "${key}" already maps to "${existingPattern}", ` +
3583
+ `overwriting with "${prefixedPattern}". Use unique key names to avoid this.`
3584
+ );
3585
+ }
3586
+
3587
+ // Use original key - enables reusable route modules
3588
+ mergedRouteMap[key] = prefixedPattern;
3413
3589
  }
3414
3590
 
3415
3591
  // Auto-register route map for runtime href() usage
@@ -3421,7 +3597,7 @@ export function createRSCRouter<TEnv = any>(
3421
3597
  | undefined;
3422
3598
 
3423
3599
  // Create builder object so .use() can return it
3424
- const builder: RouteBuilder<RouteDefinition, TEnv, TNewRoutes> = {
3600
+ const builder: RouteBuilder<RouteDefinition, TEnv, any, TNewRoutes> = {
3425
3601
  use(
3426
3602
  patternOrMiddleware: string | MiddlewareFn<TEnv>,
3427
3603
  middleware?: MiddlewareFn<TEnv>
@@ -3432,16 +3608,22 @@ export function createRSCRouter<TEnv = any>(
3432
3608
  },
3433
3609
 
3434
3610
  map(
3435
- handler: () =>
3436
- | Array<AllUseItems>
3437
- | Promise<{ default: () => Array<AllUseItems> }>
3438
- | Promise<() => Array<AllUseItems>>
3611
+ handler:
3612
+ | ((helpers: InlineRouteHelpers<TNewRoutes, TEnv>) => Array<AllUseItems>)
3613
+ | (() =>
3614
+ | Array<AllUseItems>
3615
+ | Promise<{ default: () => Array<AllUseItems> }>
3616
+ | Promise<() => Array<AllUseItems>>)
3439
3617
  ) {
3618
+ // Store handler as-is - detection happens at call time based on return type
3619
+ // Both patterns use the same signature:
3620
+ // - Inline: ({ route }) => [...] - receives helpers, returns Array
3621
+ // - Lazy: () => import(...) - ignores helpers, returns Promise
3440
3622
  routesEntries.push({
3441
3623
  prefix,
3442
3624
  routes: routes as ResolvedRouteMap<any>,
3443
3625
  trailingSlash: trailingSlashConfig,
3444
- handler,
3626
+ handler: handler as any,
3445
3627
  mountIndex: currentMountIndex,
3446
3628
  });
3447
3629
  // Return router with accumulated types
@@ -60,9 +60,10 @@ import { invokeOnError } from "../router/error-handling.js";
60
60
  * });
61
61
  * ```
62
62
  */
63
- export function createRSCHandler<TEnv = unknown>(
64
- options: CreateRSCHandlerOptions<TEnv>
65
- ) {
63
+ export function createRSCHandler<
64
+ TEnv = unknown,
65
+ TRoutes extends Record<string, string> = Record<string, string>,
66
+ >(options: CreateRSCHandlerOptions<TEnv, TRoutes>) {
66
67
  const { router, version = VERSION, nonce: nonceProvider } = options;
67
68
 
68
69
  // Use provided deps or default to @vitejs/plugin-rsc/rsc exports
package/src/rsc/types.ts CHANGED
@@ -140,11 +140,14 @@ export type NonceProvider<TEnv = unknown> = (
140
140
  /**
141
141
  * Options for creating an RSC handler
142
142
  */
143
- export interface CreateRSCHandlerOptions<TEnv = unknown> {
143
+ export interface CreateRSCHandlerOptions<
144
+ TEnv = unknown,
145
+ TRoutes extends Record<string, string> = Record<string, string>,
146
+ > {
144
147
  /**
145
148
  * The RSC router instance
146
149
  */
147
- router: RSCRouter<TEnv>;
150
+ router: RSCRouter<TEnv, TRoutes>;
148
151
 
149
152
  /**
150
153
  * RSC dependencies from @vitejs/plugin-rsc/rsc.
package/src/server.ts CHANGED
@@ -30,6 +30,7 @@ export {
30
30
  createHref,
31
31
  type HrefFunction,
32
32
  type PrefixedRoutes,
33
+ type PrefixRoutePatterns,
33
34
  type ParamsFor,
34
35
  type SanitizePrefix,
35
36
  type MergeRoutes,