@rangojs/router 0.0.0-experimental.110 → 0.0.0-experimental.111

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.
@@ -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,
@@ -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 */
@@ -32,21 +32,32 @@ type ResponseReverseFunction = [DefaultReverseRouteMap] extends [
32
32
  * Symbol marking a route as a response route (non-RSC).
33
33
  * Stored on PathOptions and UrlPatterns to signal the trie to short-circuit.
34
34
  */
35
- export const RESPONSE_TYPE: unique symbol = Symbol.for(
36
- "rangojs.responseType",
37
- ) as any;
35
+ export const RESPONSE_TYPE: unique symbol = Symbol.for("rangojs.responseType");
38
36
 
39
37
  /**
40
- * Handler that must return Response (not ReactNode).
41
- * 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.
42
40
  */
43
- export type ResponseHandler<TParams = Record<string, string>, TEnv = any> = ((
41
+ type ResponseHandlerOf<
42
+ TReturn,
43
+ TParams = Record<string, string>,
44
+ TEnv = any,
45
+ > = ((
44
46
  ctx: ResponseHandlerContext<TParams, TEnv>,
45
- ) => Response | Promise<Response>) & {
47
+ ) => TReturn | Promise<TReturn>) & {
46
48
  /** Composable default DSL items merged when the handler is mounted. */
47
49
  use?: () => UseItems<ResponseRouteUseItem>;
48
50
  };
49
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
+
50
61
  /**
51
62
  * JSON-serializable value type for auto-wrap support.
52
63
  */
@@ -65,12 +76,7 @@ export type JsonValue =
65
76
  export type JsonResponseHandler<
66
77
  TParams = Record<string, string>,
67
78
  TEnv = any,
68
- > = ((
69
- ctx: ResponseHandlerContext<TParams, TEnv>,
70
- ) => JsonValue | Response | Promise<JsonValue | Response>) & {
71
- /** Composable default DSL items merged when the handler is mounted. */
72
- use?: () => UseItems<ResponseRouteUseItem>;
73
- };
79
+ > = ResponseHandlerOf<JsonValue | Response, TParams, TEnv>;
74
80
 
75
81
  /**
76
82
  * Handler for text-based response routes (text, html, xml).
@@ -79,12 +85,7 @@ export type JsonResponseHandler<
79
85
  export type TextResponseHandler<
80
86
  TParams = Record<string, string>,
81
87
  TEnv = any,
82
- > = ((
83
- ctx: ResponseHandlerContext<TParams, TEnv>,
84
- ) => string | Response | Promise<string | Response>) & {
85
- /** Composable default DSL items merged when the handler is mounted. */
86
- use?: () => UseItems<ResponseRouteUseItem>;
87
- };
88
+ > = ResponseHandlerOf<string | Response, TParams, TEnv>;
88
89
 
89
90
  /**
90
91
  * Lighter handler context for response routes.
@@ -1,4 +1,3 @@
1
- import type { ExtractParams } from "../types.js";
2
1
  import type { JsonSerialize } from "../serialize.js";
3
2
  import type {
4
3
  TypedRouteItem,
@@ -7,11 +6,7 @@ import type {
7
6
  TypedCacheItem,
8
7
  TypedTransitionItem,
9
8
  } from "../route-types.js";
10
- import type {
11
- LocalOnlyInclude,
12
- UnnamedRoute,
13
- UrlPatterns,
14
- } from "./pattern-types.js";
9
+ import type { LocalOnlyInclude, UnnamedRoute } from "./pattern-types.js";
15
10
 
16
11
  // ============================================================================
17
12
  // Route Type Extraction Utilities
@@ -68,62 +63,6 @@ type PrefixPatterns<
68
63
  : TRoutes[K];
69
64
  };
70
65
 
71
- /**
72
- * Depth counter for limiting recursion (max 40 levels)
73
- * Supports up to 40 sibling items at any level of a urls() call
74
- * Note: Higher values hit TypeScript's internal recursion limits
75
- */
76
- type Depth = [
77
- never,
78
- 0,
79
- 1,
80
- 2,
81
- 3,
82
- 4,
83
- 5,
84
- 6,
85
- 7,
86
- 8,
87
- 9,
88
- 10,
89
- 11,
90
- 12,
91
- 13,
92
- 14,
93
- 15,
94
- 16,
95
- 17,
96
- 18,
97
- 19,
98
- 20,
99
- 21,
100
- 22,
101
- 23,
102
- 24,
103
- 25,
104
- 26,
105
- 27,
106
- 28,
107
- 29,
108
- 30,
109
- 31,
110
- 32,
111
- 33,
112
- 34,
113
- 35,
114
- 36,
115
- 37,
116
- 38,
117
- 39,
118
- ];
119
-
120
- /**
121
- * Force TypeScript to eagerly evaluate a type.
122
- * This helps with interface extension by creating a "concrete" object type.
123
- */
124
- type Simplify<T> =
125
- T extends Record<string, string> ? { [K in keyof T]: T[K] } : T;
126
-
127
66
  /**
128
67
  * Convert a union type to an intersection type.
129
68
  * Used to combine route maps from multiple siblings without recursive tuple processing.
@@ -136,13 +75,11 @@ type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
136
75
 
137
76
  /**
138
77
  * Extract routes from a single item (path, include, layout, cache with children)
139
- * D is the current depth level for nested layouts/caches
140
78
  */
141
- type ExtractRoutesFromItem<T, D extends number = 40> = [D] extends [never]
142
- ? {} // Max depth reached, stop recursion
143
- : // TypedRouteItem: extract name -> pattern (exclude unnamed routes)
144
- // When search schema is non-empty, value becomes { path, search } object
145
- T extends TypedRouteItem<infer TName, infer TPattern, any, infer TSearch>
79
+ type ExtractRoutesFromItem<T> =
80
+ // TypedRouteItem: extract name -> pattern (exclude unnamed routes)
81
+ // When search schema is non-empty, value becomes { path, search } object
82
+ T extends TypedRouteItem<infer TName, infer TPattern, any, infer TSearch>
146
83
  ? TName extends string
147
84
  ? TName extends UnnamedRoute
148
85
  ? {} // Exclude unnamed routes from type map
@@ -186,14 +123,10 @@ type ExtractRoutesFromItem<T, D extends number = 40> = [D] extends [never]
186
123
  * Extract routes from an array of items using mapped types.
187
124
  * Uses UnionToIntersection to combine routes without recursive tuple processing,
188
125
  * removing the sibling limit that was caused by TypeScript recursion limits.
189
- * D is passed to ExtractRoutesFromItem for nested depth tracking.
190
126
  */
191
- type ExtractRoutesFromItems<
192
- T extends readonly any[],
193
- D extends number = 40,
194
- > = T extends readonly any[]
127
+ type ExtractRoutesFromItems<T extends readonly any[]> = T extends readonly any[]
195
128
  ? UnionToIntersection<
196
- { [K in keyof T]: ExtractRoutesFromItem<T[K], D> }[number]
129
+ { [K in keyof T]: ExtractRoutesFromItem<T[K]> }[number]
197
130
  > extends infer R
198
131
  ? R extends Record<string, any>
199
132
  ? R
@@ -204,12 +137,8 @@ type ExtractRoutesFromItems<
204
137
  /**
205
138
  * Main utility: extract route map from urls() callback return type
206
139
  * Uses mapped types for sibling processing (no sibling limit).
207
- * Uses Simplify to force eager evaluation for interface extension compatibility.
208
140
  */
209
- export type ExtractRoutes<T extends readonly any[]> = ExtractRoutesFromItems<
210
- T,
211
- 40
212
- >;
141
+ export type ExtractRoutes<T extends readonly any[]> = ExtractRoutesFromItems<T>;
213
142
 
214
143
  // ============================================================================
215
144
  // Response Type Extraction Utilities
@@ -237,9 +166,8 @@ type PrefixKeys<
237
166
  * Extract response data types from a single item.
238
167
  * Parallel to ExtractRoutesFromItem but extracts name -> TData mapping.
239
168
  */
240
- type ExtractResponsesFromItem<T, D extends number = 40> = [D] extends [never]
241
- ? {}
242
- : T extends TypedRouteItem<infer TName, any, infer TData>
169
+ type ExtractResponsesFromItem<T> =
170
+ T extends TypedRouteItem<infer TName, any, infer TData>
243
171
  ? TName extends string
244
172
  ? TName extends UnnamedRoute
245
173
  ? {}
@@ -273,46 +201,23 @@ type ExtractResponsesFromItem<T, D extends number = 40> = [D] extends [never]
273
201
  * Extract responses from an array of items using mapped types.
274
202
  * Parallel to ExtractRoutesFromItems.
275
203
  */
276
- type ExtractResponsesFromItems<
277
- T extends readonly any[],
278
- D extends number = 40,
279
- > = T extends readonly any[]
280
- ? UnionToIntersection<
281
- { [K in keyof T]: ExtractResponsesFromItem<T[K], D> }[number]
282
- > extends infer R
283
- ? R extends Record<string, unknown>
284
- ? R
204
+ type ExtractResponsesFromItems<T extends readonly any[]> =
205
+ T extends readonly any[]
206
+ ? UnionToIntersection<
207
+ { [K in keyof T]: ExtractResponsesFromItem<T[K]> }[number]
208
+ > extends infer R
209
+ ? R extends Record<string, unknown>
210
+ ? R
211
+ : {}
285
212
  : {}
286
- : {}
287
- : {};
213
+ : {};
288
214
 
289
215
  /**
290
216
  * Main utility: extract response data type map from urls() callback return type.
291
217
  * Parallel to ExtractRoutes.
292
218
  */
293
219
  export type ExtractResponses<T extends readonly any[]> =
294
- ExtractResponsesFromItems<T, 40>;
295
-
296
- // ============================================================================
297
- // Type Utilities for path()
298
- // ============================================================================
299
-
300
- /**
301
- * Extract route names from a UrlPatterns result
302
- * Used for type-safe href() generation
303
- */
304
- export type ExtractRouteNames<T extends UrlPatterns<any>> =
305
- T extends UrlPatterns<infer _TEnv>
306
- ? string // For now, will be refined with full implementation
307
- : never;
308
-
309
- /**
310
- * Extract params for a specific route name
311
- */
312
- export type ExtractPathParams<
313
- T extends UrlPatterns<any>,
314
- K extends string,
315
- > = ExtractParams<string>; // Will be refined with pattern tracking
220
+ ExtractResponsesFromItems<T>;
316
221
 
317
222
  // ============================================================================
318
223
  // Response Envelope Types
@@ -3,7 +3,7 @@ import type { AllUseItems } from "../route-types.js";
3
3
  import { getContext } from "../server/context";
4
4
  import { invariant } from "../errors";
5
5
  import { createRouteHelpers } from "../route-definition.js";
6
- import type { PathDefinition, UrlPatterns } from "./pattern-types.js";
6
+ import type { UrlPatterns } from "./pattern-types.js";
7
7
  import type { PathHelpers } from "./path-helper-types.js";
8
8
  import type { ExtractRoutes, ExtractResponses } from "./type-extraction.js";
9
9
  import { createPathHelper, attachPathResponseTags } from "./path-helper.js";
@@ -34,9 +34,6 @@ export function urls<
34
34
  >(
35
35
  builder: (helpers: PathHelpers<TEnv>) => TItems,
36
36
  ): UrlPatterns<TEnv, ExtractRoutes<TItems>, ExtractResponses<TItems>> {
37
- // Collect path definitions during build
38
- const definitions: PathDefinition[] = [];
39
-
40
37
  // Create the handler function that will be called by the router
41
38
  const handler = () => {
42
39
  invariant(
@@ -82,7 +79,6 @@ export function urls<
82
79
  // trailingSlash config is populated when handler() runs
83
80
  // We expose it via a getter that reads from the context after handler execution
84
81
  return {
85
- definitions,
86
82
  handler,
87
83
  get trailingSlash() {
88
84
  // Get the trailingSlash map from the current context