@rangojs/router 0.0.0-experimental.13 → 0.0.0-experimental.15

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.
package/src/index.rsc.ts CHANGED
@@ -67,13 +67,22 @@ export type {
67
67
  NotFoundInfo,
68
68
  NotFoundBoundaryFallbackProps,
69
69
  NotFoundBoundaryHandler,
70
+ // Error handling callback types
71
+ ErrorPhase,
72
+ OnErrorContext,
73
+ OnErrorCallback,
70
74
  } from "./types.js";
71
75
 
72
76
  // Router options type (server-only, so import directly)
73
77
  export type { RSCRouterOptions } from "./router.js";
74
78
 
75
79
  // Server-side createLoader and redirect
76
- export { createLoader, redirect } from "./route-definition.js";
80
+ export {
81
+ createLoader,
82
+ redirect,
83
+ type RouteHelpers,
84
+ type RouteHandlers,
85
+ } from "./route-definition.js";
77
86
 
78
87
  // Handle API
79
88
  export { createHandle, isHandle, type Handle } from "./handle.js";
@@ -167,3 +176,6 @@ export {
167
176
  type LocationStateDefinition,
168
177
  type LocationStateEntry,
169
178
  } from "./browser/react/location-state-shared.js";
179
+
180
+ // Path-based response type lookup from RegisteredRoutes
181
+ export type { PathResponse } from "./href-client.js";
package/src/index.ts CHANGED
@@ -68,6 +68,10 @@ export type {
68
68
  NotFoundInfo,
69
69
  NotFoundBoundaryFallbackProps,
70
70
  NotFoundBoundaryHandler,
71
+ // Error handling callback types
72
+ ErrorPhase,
73
+ OnErrorContext,
74
+ OnErrorCallback,
71
75
  } from "./types.js";
72
76
 
73
77
  // Search params schema types
@@ -77,6 +81,9 @@ export type { SearchSchema, SearchSchemaValue, ResolveSearchSchema, RouteSearchP
77
81
  // Use this when defining loaders that will be imported by client components
78
82
  export { createLoader } from "./loader.js";
79
83
 
84
+ // Route definition types (safe to import anywhere)
85
+ export type { RouteHelpers, RouteHandlers } from "./route-definition.js";
86
+
80
87
  // Response route types (usable in both server and client contexts)
81
88
  export type {
82
89
  ResponseHandler,
@@ -517,6 +517,10 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
517
517
  */
518
518
  const hasRoutesInItem = (item: AllUseItems): boolean => {
519
519
  if (item.type === "route") return true;
520
+ // Lazy includes contain deferred routes — treat them as having routes
521
+ // to prevent the parent layout from being misclassified as orphan,
522
+ // which would clear its parent pointer and break the middleware chain.
523
+ if (item.type === "include") return true;
520
524
  if (item.type === "cache" && item.uses) {
521
525
  return item.uses.some((child) => hasRoutesInItem(child));
522
526
  }
@@ -823,6 +827,11 @@ const parallel: RouteHelpers<any, any>["parallel"] = (slots, use) => {
823
827
  invariant(false, "No parent entry available for parallel()");
824
828
  }
825
829
 
830
+ invariant(
831
+ ctx.parent.type !== "parallel",
832
+ "parallel() cannot be nested inside another parallel()"
833
+ );
834
+
826
835
  const namespace = `${ctx.namespace}.$${store.getNextIndex("parallel")}`;
827
836
 
828
837
  // Unwrap any static handler definitions in parallel slots
@@ -888,6 +897,11 @@ const intercept: RouteHelpers<any, any>["intercept"] = (
888
897
  invariant(false, "No parent entry available for intercept()");
889
898
  }
890
899
 
900
+ invariant(
901
+ ctx.parent.type !== "parallel",
902
+ "intercept() cannot be used inside parallel()"
903
+ );
904
+
891
905
  const namespace = `${ctx.namespace}.$${store.getNextIndex("intercept")}.${slotName}`;
892
906
 
893
907
  // Apply name prefix to routeName (from include())
@@ -1080,6 +1094,12 @@ const layout: RouteHelpers<any, any>["layout"] = (handler, use) => {
1080
1094
  const store = getContext();
1081
1095
  const ctx = store.getStore();
1082
1096
  if (!ctx) throw new Error("layout() must be called inside map()");
1097
+
1098
+ invariant(
1099
+ !ctx.parent || ctx.parent.type !== "parallel",
1100
+ "layout() cannot be used inside parallel()"
1101
+ );
1102
+
1083
1103
  const isRoot = !ctx.parent || ctx.parent === null;
1084
1104
  const nextIndex = isRoot ? "$root" : store.getNextIndex("layout");
1085
1105
  const namespace = `${ctx.namespace}.${nextIndex}`;
@@ -1127,6 +1147,17 @@ const layout: RouteHelpers<any, any>["layout"] = (handler, use) => {
1127
1147
  result.some((item) => hasRoutesInItem(item));
1128
1148
 
1129
1149
  if (!hasRoutes) {
1150
+ // Orphan layouts must not contain other layouts as children.
1151
+ // If we're here, all child layouts are also orphan (if any had routes,
1152
+ // hasRoutesInItem would have returned true). Nested orphan chains are
1153
+ // confusing — use sibling orphan layouts instead.
1154
+ if (result) {
1155
+ invariant(
1156
+ !result.some((item) => item?.type === "layout"),
1157
+ `orphan layout cannot contain other layouts as children [${namespace}]`
1158
+ );
1159
+ }
1160
+
1130
1161
  const parent = ctx.parent;
1131
1162
 
1132
1163
  // Allow orphan layouts at root level if they're part of map() builder result
@@ -104,8 +104,8 @@ import type { ResolvedSegment } from "../../types.js";
104
104
  import { getRequestContext } from "../../server/request-context.js";
105
105
  import type { MatchContext, MatchPipelineState } from "../match-context.js";
106
106
  import { getRouterContext } from "../router-context.js";
107
- import type { GeneratorMiddleware } from "./cache-lookup.js";
108
107
  import { debugLog, debugWarn } from "../logging.js";
108
+ import type { GeneratorMiddleware } from "./cache-lookup.js";
109
109
 
110
110
  /**
111
111
  * Creates cache store middleware
package/src/router.ts CHANGED
@@ -98,7 +98,7 @@ import {
98
98
  } from "./router/match-api.js";
99
99
 
100
100
  import type { SegmentResolutionDeps, MatchApiDeps } from "./router/types.js";
101
- import { createHandlerContext, createBuildContext } from "./router/handler-context.js";
101
+ import { createHandlerContext } from "./router/handler-context.js";
102
102
  import {
103
103
  setupLoaderAccess,
104
104
  setupLoaderAccessSilent,
@@ -1655,7 +1655,7 @@ export function createRouter<TEnv = any>(
1655
1655
  routes: {} as ResolvedRouteMap<any>, // Empty until first match
1656
1656
  trailingSlash: entry.trailingSlash,
1657
1657
  handler: (lazyInclude.patterns as UrlPatterns<TEnv>).handler,
1658
- mountIndex: entry.mountIndex,
1658
+ mountIndex: mountIndex++,
1659
1659
  // Lazy evaluation fields
1660
1660
  lazy: true,
1661
1661
  lazyPatterns: lazyInclude.patterns,
@@ -1911,11 +1911,18 @@ export function createRouter<TEnv = any>(
1911
1911
  };
1912
1912
 
1913
1913
  return runWithRequestContext(minimalRequestContext, async () => {
1914
- // 6. Create BuildContext (no request/env/headers/cookies)
1915
- const buildCtx = createBuildContext<TEnv>(
1914
+ // 6. Create handler context with synthetic request for pre-rendering.
1915
+ // The synthetic request and route map are available at build time,
1916
+ // so reverse() and other context properties work normally.
1917
+ const buildCtx = createHandlerContext<TEnv>(
1916
1918
  matchedParams,
1919
+ minimalRequestContext.request,
1920
+ minimalRequestContext.url.searchParams,
1917
1921
  pathname,
1918
- handleStore,
1922
+ minimalRequestContext.url,
1923
+ {},
1924
+ mergedRouteMap,
1925
+ matched.routeKey,
1919
1926
  );
1920
1927
 
1921
1928
  // 7. Wire use() for handles only (loaders throw)
@@ -2527,7 +2534,7 @@ export function createRouter<TEnv = any>(
2527
2534
  routes: {} as ResolvedRouteMap<any>, // Empty until first match
2528
2535
  trailingSlash: trailingSlashConfig,
2529
2536
  handler: urlPatterns.handler,
2530
- mountIndex: currentMountIndex,
2537
+ mountIndex: mountIndex++,
2531
2538
  // Lazy evaluation fields
2532
2539
  lazy: true,
2533
2540
  lazyPatterns: lazyInclude.patterns,
package/src/server.ts CHANGED
@@ -1,95 +1,20 @@
1
1
  /**
2
- * rsc-router/server
2
+ * @rangojs/router/server — Internal subpath
3
3
  *
4
- * Server-only exports for route definition and building
5
- * These should only be imported in server-side handler files
4
+ * This module is NOT user-facing. Import from "@rangojs/router" instead.
5
+ *
6
+ * Exports here are consumed by the Vite plugin (discovery, manifest injection,
7
+ * virtual modules) and the RSC handler internals. They are not part of the
8
+ * public API and may change without notice.
6
9
  */
7
10
 
8
- // Route definition helpers (server-only)
9
- export {
10
- createLoader,
11
- redirect,
12
- type RouteHelpers,
13
- type RouteHandlers,
14
- } from "./route-definition.js";
15
-
16
- // Django-style URL patterns (server-only)
11
+ // Router registry (used by Vite plugin for build-time discovery)
17
12
  export {
18
- urls,
19
- RESPONSE_TYPE,
20
- type PathHelpers,
21
- type PathOptions,
22
- type UrlPatterns,
23
- type IncludeOptions,
24
- type ResponseHandler,
25
- type ResponseHandlerContext,
26
- type JsonResponseHandler,
27
- type TextResponseHandler,
28
- type JsonValue,
29
- type ResponsePathFn,
30
- type JsonResponsePathFn,
31
- type TextResponsePathFn,
32
- type RouteResponse,
33
- type ResponseError,
34
- type ResponseEnvelope,
35
- } from "./urls.js";
36
-
37
- // Re-export IncludeItem from route-types
38
- export type { IncludeItem } from "./route-types.js";
39
-
40
- // Core router (server-only)
41
- export {
42
- createRouter,
43
13
  RSC_ROUTER_BRAND,
44
14
  RouterRegistry,
45
- type RSCRouter,
46
- type RSCRouterOptions,
47
- type RootLayoutProps,
48
15
  } from "./router.js";
49
16
 
50
- // Type-safe reverse utilities (Django-style URL reversal)
51
- export {
52
- createReverse,
53
- type ReverseFunction,
54
- type PrefixedRoutes,
55
- type PrefixRoutePatterns,
56
- type ParamsFor,
57
- type SanitizePrefix,
58
- type MergeRoutes,
59
- } from "./reverse.js";
60
-
61
- // Segment system (server-only)
62
- export { renderSegments } from "./segment-system.js";
63
-
64
- // Performance tracking (server-only)
65
- export { track } from "./server/context.js";
66
-
67
- // Handle API (works in both server and client contexts)
68
- export { createHandle, isHandle, type Handle } from "./handle.js";
69
-
70
- // Pre-render handler API
71
- export {
72
- Prerender,
73
- isPrerenderHandler,
74
- type PrerenderHandlerDefinition,
75
- type PrerenderOptions,
76
- type BuildContext,
77
- } from "./prerender.js";
78
-
79
- // Static handler API
80
- export {
81
- Static,
82
- isStaticHandler,
83
- type StaticHandlerDefinition,
84
- } from "./static-handler.js";
85
-
86
- // Built-in handles
87
- export { Meta } from "./handles/meta.js";
88
-
89
- // Loader registry (for GET-based loader fetching)
90
- export { registerLoaderById, setLoaderImports } from "./server/loader-registry.js";
91
-
92
- // Route map builder (for build-time manifest registration)
17
+ // Route map builder (Vite plugin injects these via virtual modules)
93
18
  export {
94
19
  registerRouteMap,
95
20
  setCachedManifest,
@@ -103,87 +28,17 @@ export {
103
28
  ensureRouterManifest,
104
29
  } from "./route-map-builder.js";
105
30
 
106
- // Request context (for accessing request data in server components/actions)
31
+ // Loader registry (Vite plugin registers lazy loader imports)
32
+ export { registerLoaderById, setLoaderImports } from "./server/loader-registry.js";
33
+
34
+ // Request context creation (used by RSC handler, not user-facing)
107
35
  export {
108
- getRequestContext,
109
- requireRequestContext,
110
36
  createRequestContext,
111
- type RequestContext,
112
37
  type CreateRequestContextOptions,
113
38
  } from "./server/request-context.js";
114
39
 
115
- // Meta types
116
- export type { MetaDescriptor, MetaDescriptorBase } from "./router/types.js";
117
-
118
- // Middleware context types (Middleware type is exported from types.ts)
119
- export type {
120
- MiddlewareContext,
121
- CookieOptions,
122
- } from "./router/middleware.js";
123
-
124
- // Error classes and utilities
125
- export {
126
- RouteNotFoundError,
127
- DataNotFoundError,
128
- notFound,
129
- MiddlewareError,
130
- HandlerError,
131
- BuildError,
132
- InvalidHandlerError,
133
- sanitizeError,
134
- RouterError,
135
- } from "./errors.js";
136
-
137
- // Component utilities
40
+ // Component utilities (used internally for server/client boundary checks)
138
41
  export {
139
42
  isClientComponent,
140
43
  assertClientComponent,
141
44
  } from "./component-utils.js";
142
-
143
- // Debug utilities for route matching (development only)
144
- export {
145
- enableMatchDebug,
146
- getMatchDebugStats,
147
- } from "./router/pattern-matching.js";
148
-
149
- // Types (re-exported for convenience - user-facing only)
150
- export type {
151
- // Configuration types
152
- RouterEnv,
153
- DefaultEnv,
154
- RouteDefinition,
155
- RouteConfig,
156
- RouteDefinitionOptions,
157
- TrailingSlashMode,
158
- // Handler types
159
- Handler, // Supports params object, path pattern, or route name
160
- HandlerContext,
161
- ExtractParams,
162
- GenericParams,
163
- // Middleware types (also exported from router/middleware.js above)
164
- Middleware, // Supports env type and optional route name for params
165
- // Revalidation types
166
- RevalidateParams,
167
- Revalidate,
168
- RouteKeys,
169
- // Loader types
170
- LoaderDefinition,
171
- LoaderFn,
172
- LoaderContext,
173
- // Error boundary types
174
- ErrorInfo,
175
- ErrorBoundaryFallbackProps,
176
- ErrorBoundaryHandler,
177
- ClientErrorBoundaryFallbackProps,
178
- // NotFound boundary types
179
- NotFoundInfo,
180
- NotFoundBoundaryFallbackProps,
181
- NotFoundBoundaryHandler,
182
- // Error handling callback types
183
- ErrorPhase,
184
- OnErrorContext,
185
- OnErrorCallback,
186
- } from "./types.js";
187
-
188
- // Path-based response type lookup from RegisteredRoutes
189
- export type { PathResponse } from "./href-client.js";
package/src/urls.ts CHANGED
@@ -813,6 +813,24 @@ function createPathHelper<TEnv>(): PathFn<TEnv> {
813
813
  const ctx = store.getStore();
814
814
  if (!ctx) throw new Error("path() must be called inside urls()");
815
815
 
816
+ invariant(
817
+ !ctx.parent || ctx.parent.type !== "parallel",
818
+ "path() cannot be used inside parallel()"
819
+ );
820
+
821
+ // Walk the parent chain to prevent path() nested under another path(),
822
+ // even when separated by intermediate layouts (e.g. path(layout(path())))
823
+ {
824
+ let ancestor = ctx.parent;
825
+ while (ancestor) {
826
+ invariant(
827
+ ancestor.type !== "route",
828
+ "path() cannot be nested inside another path()"
829
+ );
830
+ ancestor = ancestor.parent;
831
+ }
832
+ }
833
+
816
834
  // Determine options and use based on argument types
817
835
  let options: PathOptions | undefined;
818
836
  let use: (() => RouteUseItem[]) | undefined;
@@ -1113,6 +1131,17 @@ function createIncludeHelper<TEnv>(): IncludeFn<TEnv> {
1113
1131
  // sibling entries (e.g., BlogLayout and ArticlesLayout under NavLayout).
1114
1132
  const capturedCounters = { ...ctx.counters };
1115
1133
 
1134
+ // Reserve a layout slot in the parent's counter so sibling lazy includes
1135
+ // produce different shortCode indices for their root layout.
1136
+ // Without this, consecutive include() calls capture identical counters
1137
+ // and their first child layouts get the same shortCode (e.g., both M0L0L0),
1138
+ // causing the client partial-update diff to see no changes on navigation.
1139
+ if (capturedParent?.shortCode) {
1140
+ const layoutCounterKey = `${capturedParent.shortCode}_layout`;
1141
+ ctx.counters[layoutCounterKey] ??= 0;
1142
+ ctx.counters[layoutCounterKey]++;
1143
+ }
1144
+
1116
1145
  // All includes are lazy - patterns are evaluated on first matching request
1117
1146
  // This improves cold start time significantly for large route sets
1118
1147
  return {
package/src/vite/index.ts CHANGED
@@ -7,8 +7,6 @@ import { createRequire } from "node:module";
7
7
  import { mkdirSync, writeFileSync, readFileSync, existsSync, unlinkSync } from "node:fs";
8
8
  import {
9
9
  generateRouteTypesSource,
10
- writePerModuleRouteTypes,
11
- writePerModuleRouteTypesForFile,
12
10
  writeCombinedRouteTypes,
13
11
  findRouterFiles,
14
12
  createScanFilter,
@@ -111,9 +109,8 @@ interface RangoBaseOptions {
111
109
  banner?: boolean;
112
110
 
113
111
  /**
114
- * Generate static route type files (.gen.ts) by parsing url modules at startup.
115
- * Creates per-module route maps and per-router named-routes.gen.ts for type-safe
116
- * Handler<"name", routes> and href() without executing router code.
112
+ * Generate named-routes.gen.ts by parsing url modules at startup.
113
+ * Provides type-safe Handler<"name"> and href() without executing router code.
117
114
  * Set to `false` to disable (run `npx rango extract-names` manually instead).
118
115
  * @default true
119
116
  */
@@ -964,15 +961,13 @@ function createRouterDiscoveryPlugin(
964
961
  exclude: opts.exclude,
965
962
  });
966
963
  }
967
- // Generate per-module route types from static source parsing.
968
- // Runs before the dev server starts so .gen.ts files exist immediately for IDE.
964
+ // Generate combined named-routes.gen.ts from static source parsing.
965
+ // Runs before the dev server starts so the gen file exists immediately for IDE.
969
966
  // In build mode, the runtime discovery in buildStart produces the definitive
970
- // named-routes.gen.ts (including dynamically generated routes). However, we
971
- // still need to write initial gen files here so discovery can import router
972
- // modules that reference them. preserveIfLarger prevents overwriting a
973
- // previously generated complete file with a partial one.
967
+ // named-routes.gen.ts (including dynamically generated routes).
968
+ // preserveIfLarger prevents overwriting a previously generated complete
969
+ // file with a partial one.
974
970
  if (opts?.staticRouteTypesGeneration !== false) {
975
- writePerModuleRouteTypes(projectRoot, scanFilter);
976
971
  cachedRouterFiles = findRouterFiles(projectRoot, scanFilter);
977
972
  writeCombinedRouteTypes(projectRoot, cachedRouterFiles, { preserveIfLarger: true });
978
973
  }
@@ -1173,8 +1168,8 @@ function createRouterDiscoveryPlugin(
1173
1168
  res.end("No prerender match");
1174
1169
  });
1175
1170
 
1176
- // Watch url module and router files for changes and regenerate route types.
1177
- // Process files containing urls( (per-module types) or createRouter( (per-router types).
1171
+ // Watch url module and router files for changes and regenerate named-routes.gen.ts.
1172
+ // Process files containing urls( or createRouter( to update the combined route map.
1178
1173
  if (opts?.staticRouteTypesGeneration !== false) {
1179
1174
  server.watcher.on("change", (filePath) => {
1180
1175
  if (filePath.endsWith(".gen.ts")) return;
@@ -1188,9 +1183,6 @@ function createRouterDiscoveryPlugin(
1188
1183
  const hasUrls = source.includes("urls(");
1189
1184
  const hasCreateRouter = /\bcreateRouter\s*[<(]/.test(source);
1190
1185
  if (!hasUrls && !hasCreateRouter) return;
1191
- if (hasUrls) {
1192
- writePerModuleRouteTypesForFile(filePath);
1193
- }
1194
1186
  // Invalidate cache when a router file changes (new router added/removed)
1195
1187
  if (hasCreateRouter) {
1196
1188
  cachedRouterFiles = undefined;