@rangojs/router 0.12.3 → 0.12.4

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.
@@ -54,6 +54,14 @@ export interface ClientUrlProjection {
54
54
  export declare function serializeClientUrlPatterns(patterns: ClientUrlPatterns): ClientUrlProjection;
55
55
  export declare function isClientUrlPatterns(value: unknown): value is ClientUrlPatterns;
56
56
  export declare function isClientUrlReference(value: unknown): value is ClientUrlReference;
57
+ /**
58
+ * A clientUrls() definition object or, on the server, its client reference.
59
+ * Run this BEFORE any duck-typing of the value (`typeof === "function"`,
60
+ * `.handler` reads): a client reference is a callable Proxy that throws on
61
+ * unknown property reads, so a later shape check would either invoke it as a
62
+ * thunk or surface React's "cannot dot into a client module" error.
63
+ */
64
+ export declare function isClientUrlSource(value: unknown): value is ClientUrlDefinitionSource;
57
65
  export declare function setClientUrlProjection(reference: string | ClientUrlReference, projection: ClientUrlProjection): void;
58
66
  export declare function getClientUrlProjection(reference: string | ClientUrlReference): ClientUrlProjection | undefined;
59
67
  export declare function clearClientUrlProjections(): void;
@@ -1,12 +1,16 @@
1
1
  import type { UrlPatterns } from "./pattern-types.js";
2
+ import type { ClientUrlPatterns } from "../client-urls/types.js";
2
3
  /**
3
4
  * What an async `include()` provider may resolve to: a `urls()` value directly,
4
5
  * or a module namespace whose `default` export is a `urls()` value (the shape
5
6
  * produced by `() => import("./routes")` when the route module does
6
- * `export default urls(...)`).
7
+ * `export default urls(...)`). A `clientUrls()` module resolves the same way
8
+ * (`() => import("./shop.client")`); on the server its `default` is the client
9
+ * reference, adapted exactly as the eager `include(prefix, clientUrlsDefault)`
10
+ * form.
7
11
  */
8
- export type IncludeModule<TEnv = any> = UrlPatterns<TEnv> | {
9
- default: UrlPatterns<TEnv>;
12
+ export type IncludeModule<TEnv = any> = UrlPatterns<TEnv> | ClientUrlPatterns | {
13
+ default: UrlPatterns<TEnv> | ClientUrlPatterns;
10
14
  };
11
15
  /**
12
16
  * An async/lazy include provider: a thunk returning a `urls()` value (or a
@@ -22,6 +26,7 @@ export type IncludeProvider<TEnv = any> = () => IncludeModule<TEnv> | Promise<In
22
26
  export declare function isIncludeProvider(value: unknown): value is IncludeProvider;
23
27
  /**
24
28
  * Normalize an async provider's resolved value to a `UrlPatterns`. Accepts a
25
- * `urls()` value directly or a module whose `default` export is one.
29
+ * `urls()`/`clientUrls()` value directly or a module whose `default` export is
30
+ * one.
26
31
  */
27
32
  export declare function resolveIncludeModule<TEnv = any>(mod: IncludeModule<TEnv>, id?: string): UrlPatterns<TEnv>;
@@ -40,10 +40,12 @@ export type TextResponsePathFn<TEnv> = <const TPattern extends string, const TNa
40
40
  /**
41
41
  * What an async include() provider resolves to. Route types (`TRoutes`) are
42
42
  * inferred from the resolved `urls()` value so `href()` and named routes stay
43
- * type-safe through a code-split module (`() => import("./routes")`).
43
+ * type-safe through a code-split module (`() => import("./routes")`). A
44
+ * clientUrls() module's default export types as ClientUrlPatterns, so
45
+ * `() => import("./shop.client")` infers the group's names the same way.
44
46
  */
45
- type IncludeResolved<TEnv, TRoutes extends Record<string, any>, TResponses extends Record<string, unknown>> = UrlPatterns<TEnv, TRoutes, TResponses> | {
46
- default: UrlPatterns<TEnv, TRoutes, TResponses>;
47
+ type IncludeResolved<TEnv, TRoutes extends Record<string, any>, TResponses extends Record<string, unknown>> = UrlPatterns<TEnv, TRoutes, TResponses> | ClientUrlPatterns<TRoutes> | {
48
+ default: UrlPatterns<TEnv, TRoutes, TResponses> | ClientUrlPatterns<TRoutes>;
47
49
  };
48
50
  /** include() argument: an eager `urls()` value or an async provider thunk. */
49
51
  export type IncludeArg<TEnv, TRoutes extends Record<string, any>, TResponses extends Record<string, unknown>> = UrlPatterns<TEnv, TRoutes, TResponses> | ClientUrlPatterns<TRoutes> | (() => IncludeResolved<TEnv, TRoutes, TResponses> | Promise<IncludeResolved<TEnv, TRoutes, TResponses>>);
@@ -3746,7 +3746,7 @@ import { resolve } from "node:path";
3746
3746
  // package.json
3747
3747
  var package_default = {
3748
3748
  name: "@rangojs/router",
3749
- version: "0.12.3",
3749
+ version: "0.12.4",
3750
3750
  description: "Django-inspired RSC router with composable URL patterns",
3751
3751
  keywords: [
3752
3752
  "react",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rangojs/router",
3
- "version": "0.12.3",
3
+ "version": "0.12.4",
4
4
  "description": "Django-inspired RSC router with composable URL patterns",
5
5
  "keywords": [
6
6
  "react",
@@ -201,8 +201,8 @@
201
201
  "@testing-library/dom": "^10.4.1",
202
202
  "@testing-library/react": "^16.3.2",
203
203
  "@types/node": "^24.10.1",
204
- "@types/react": "^19.2.7",
205
- "@types/react-dom": "^19.2.3",
204
+ "@types/react": "^19.3.0",
205
+ "@types/react-dom": "^19.3.0",
206
206
  "esbuild": "^0.28.1",
207
207
  "happy-dom": "^20.10.1",
208
208
  "jiti": "^2.7.0",
@@ -112,6 +112,10 @@ import shopUrls from "./shop.client.js";
112
112
  include("/shop", shopUrls, { name: "shop" });
113
113
  ```
114
114
 
115
+ The async form works without a server wrapper module too:
116
+ `include("/shop", () => import("./shop.client.js"), { name: "shop" })` — same
117
+ behavior, no startup win, uniform with code-split server groups.
118
+
115
119
  Route names compose through the include (`shop.index`, `shop.product`) and
116
120
  flow into the generated route map, so `href`/`reverse` and `Handler<"...">`
117
121
  typing work exactly as for server routes (`/typesafety`).
@@ -359,6 +359,19 @@ export function isClientUrlReference(
359
359
  }
360
360
  }
361
361
 
362
+ /**
363
+ * A clientUrls() definition object or, on the server, its client reference.
364
+ * Run this BEFORE any duck-typing of the value (`typeof === "function"`,
365
+ * `.handler` reads): a client reference is a callable Proxy that throws on
366
+ * unknown property reads, so a later shape check would either invoke it as a
367
+ * thunk or surface React's "cannot dot into a client module" error.
368
+ */
369
+ export function isClientUrlSource(
370
+ value: unknown,
371
+ ): value is ClientUrlDefinitionSource {
372
+ return isClientUrlPatterns(value) || isClientUrlReference(value);
373
+ }
374
+
362
375
  /**
363
376
  * Strip vite's HMR timestamp query from a module id. After an HMR update of
364
377
  * a clientUrls module, the RSC graph re-imports it under
package/src/router.ts CHANGED
@@ -3,10 +3,7 @@ import { createCacheScope } from "./cache/cache-scope.js";
3
3
  import { resolveCacheProfiles } from "./cache/profile-registry.js";
4
4
  import { isCachedFunction } from "./cache/taint.js";
5
5
  import { assertClientComponent } from "./component-utils.js";
6
- import {
7
- isClientUrlPatterns,
8
- isClientUrlReference,
9
- } from "./client-urls/server-projection.js";
6
+ import { isClientUrlSource } from "./client-urls/server-projection.js";
10
7
  import type { ClientUrlPatterns } from "./client-urls/types.js";
11
8
  import { DefaultDocument } from "./components/DefaultDocument.js";
12
9
  import type { SerializedManifest } from "./debug.js";
@@ -784,10 +781,7 @@ export function createRouter<TEnv = any>(
784
781
  // same lazy include materialization, so no ordering, one-definition, or
785
782
  // deferral rules exist. Prefixing, wrapping RSC layouts, and middleware
786
783
  // scope still come from mounting through include() in urls() yourself.
787
- if (
788
- isClientUrlPatterns(patternsOrBuilder) ||
789
- isClientUrlReference(patternsOrBuilder)
790
- ) {
784
+ if (isClientUrlSource(patternsOrBuilder)) {
791
785
  const clientSource = patternsOrBuilder as ClientUrlPatterns;
792
786
  patternsOrBuilder = urls(({ include }) => [
793
787
  include("/", clientSource, { name: "" }),
@@ -13,8 +13,7 @@ import type { IncludeProvider } from "./include-provider.js";
13
13
  import type { IncludeFn } from "./path-helper-types.js";
14
14
  import {
15
15
  clientUrlIncludePatterns,
16
- isClientUrlPatterns,
17
- isClientUrlReference,
16
+ isClientUrlSource,
18
17
  } from "../client-urls/server-projection.js";
19
18
  import type { ClientUrlPatterns } from "../client-urls/types.js";
20
19
 
@@ -77,14 +76,11 @@ export function createIncludeHelper<TEnv>(): IncludeFn<TEnv> {
77
76
  ): IncludeItem => {
78
77
  const { ctx } = requireDslContext("include() must be called inside urls()");
79
78
 
80
- // clientUrls() sources mount through include() like any urls() module.
81
- // Detect them FIRST: a client REFERENCE is a callable proxy, so the
82
- // downstream provider check (`typeof === "function"`) would otherwise
83
- // invoke it as an async include thunk. The substituted handler defers
84
- // materialization to evaluation time, when the discovery-installed
85
- // projection is available; the include machinery then applies URL and
86
- // route-name prefixes exactly as for server modules.
87
- if (isClientUrlPatterns(patterns) || isClientUrlReference(patterns)) {
79
+ // clientUrls() sources mount like any urls() module: the adapter defers
80
+ // materialization to evaluation time (projection installed by then) and
81
+ // the include machinery applies URL/name prefixes as for server modules.
82
+ // Ordering: see isClientUrlSource.
83
+ if (isClientUrlSource(patterns)) {
88
84
  patterns = clientUrlIncludePatterns(patterns) as UrlPatterns<TEnv>;
89
85
  }
90
86
 
@@ -1,14 +1,23 @@
1
1
  import type { UrlPatterns } from "./pattern-types.js";
2
+ import type { ClientUrlPatterns } from "../client-urls/types.js";
3
+ import {
4
+ clientUrlIncludePatterns,
5
+ isClientUrlSource,
6
+ } from "../client-urls/server-projection.js";
2
7
 
3
8
  /**
4
9
  * What an async `include()` provider may resolve to: a `urls()` value directly,
5
10
  * or a module namespace whose `default` export is a `urls()` value (the shape
6
11
  * produced by `() => import("./routes")` when the route module does
7
- * `export default urls(...)`).
12
+ * `export default urls(...)`). A `clientUrls()` module resolves the same way
13
+ * (`() => import("./shop.client")`); on the server its `default` is the client
14
+ * reference, adapted exactly as the eager `include(prefix, clientUrlsDefault)`
15
+ * form.
8
16
  */
9
17
  export type IncludeModule<TEnv = any> =
10
18
  | UrlPatterns<TEnv>
11
- | { default: UrlPatterns<TEnv> };
19
+ | ClientUrlPatterns
20
+ | { default: UrlPatterns<TEnv> | ClientUrlPatterns };
12
21
 
13
22
  /**
14
23
  * An async/lazy include provider: a thunk returning a `urls()` value (or a
@@ -36,9 +45,19 @@ function isUrlPatterns(value: unknown): value is UrlPatterns {
36
45
  );
37
46
  }
38
47
 
48
+ /**
49
+ * A `urls()` value as is; a `clientUrls()` source through the same adapter the
50
+ * eager include() path applies (ordering: see isClientUrlSource).
51
+ */
52
+ function toUrlPatterns(value: unknown): UrlPatterns | undefined {
53
+ if (isClientUrlSource(value)) return clientUrlIncludePatterns(value);
54
+ return isUrlPatterns(value) ? value : undefined;
55
+ }
56
+
39
57
  /**
40
58
  * Normalize an async provider's resolved value to a `UrlPatterns`. Accepts a
41
- * `urls()` value directly or a module whose `default` export is one.
59
+ * `urls()`/`clientUrls()` value directly or a module whose `default` export is
60
+ * one.
42
61
  */
43
62
  export function resolveIncludeModule<TEnv = any>(
44
63
  mod: IncludeModule<TEnv>,
@@ -53,8 +72,8 @@ export function resolveIncludeModule<TEnv = any>(
53
72
  // 404s with a misleading error). A bare `() => urls(...)` provider (no
54
73
  // module) has no `default`, so it still resolves via the mod-as-value branch.
55
74
  const def = (mod as { default?: unknown })?.default;
56
- if (isUrlPatterns(def)) return def as UrlPatterns<TEnv>;
57
- if (isUrlPatterns(mod)) return mod as UrlPatterns<TEnv>;
75
+ const resolved = toUrlPatterns(def) ?? toUrlPatterns(mod);
76
+ if (resolved) return resolved as UrlPatterns<TEnv>;
58
77
  // The common failure is a module namespace whose `default` is missing or not a
59
78
  // urls() value (e.g. only named exports); `typeof` alone says "object" and
60
79
  // hides that, so name the keys present. "provider" (not "async provider") —
@@ -65,7 +84,7 @@ export function resolveIncludeModule<TEnv = any>(
65
84
  : typeof mod;
66
85
  throw new Error(
67
86
  `[@rangojs/router] include() provider${id ? ` for "${id}"` : ""} must ` +
68
- `resolve to a urls() value — either returned directly or as the module's ` +
69
- `\`default\` export (e.g. \`export default urls(...)\`). Got ${got}.`,
87
+ `resolve to a urls() or clientUrls() value — either returned directly or ` +
88
+ `as the module's \`default\` export (e.g. \`export default urls(...)\`). Got ${got}.`,
70
89
  );
71
90
  }
@@ -160,7 +160,9 @@ export type TextResponsePathFn<TEnv> = <
160
160
  /**
161
161
  * What an async include() provider resolves to. Route types (`TRoutes`) are
162
162
  * inferred from the resolved `urls()` value so `href()` and named routes stay
163
- * type-safe through a code-split module (`() => import("./routes")`).
163
+ * type-safe through a code-split module (`() => import("./routes")`). A
164
+ * clientUrls() module's default export types as ClientUrlPatterns, so
165
+ * `() => import("./shop.client")` infers the group's names the same way.
164
166
  */
165
167
  type IncludeResolved<
166
168
  TEnv,
@@ -168,7 +170,12 @@ type IncludeResolved<
168
170
  TResponses extends Record<string, unknown>,
169
171
  > =
170
172
  | UrlPatterns<TEnv, TRoutes, TResponses>
171
- | { default: UrlPatterns<TEnv, TRoutes, TResponses> };
173
+ | ClientUrlPatterns<TRoutes>
174
+ | {
175
+ default:
176
+ | UrlPatterns<TEnv, TRoutes, TResponses>
177
+ | ClientUrlPatterns<TRoutes>;
178
+ };
172
179
 
173
180
  /** include() argument: an eager `urls()` value or an async provider thunk. */
174
181
  export type IncludeArg<