@uxf/router 11.114.0 → 11.120.0

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/README.md CHANGED
@@ -6,61 +6,64 @@
6
6
  yarn add @uxf/router
7
7
  ```
8
8
 
9
- - Create `routes` directory
10
- - Create `routes.ts` and `index.ts` inside `routes` directory:
9
+ There are two factories:
11
10
 
12
- ```ts
13
- // routes/routes.ts
11
+ - `createRouter` from `@uxf/router` is the default. It is server-safe (no React hooks, no client-only imports) and can be used anywhere, including React Server Components. It returns `routeToUrl`, `route`, `routes`, `getRouteInfo`, `createRouteMatcher` and `createSitemapGenerator`.
12
+ - `createClientRouter` from `@uxf/router/client` is a superset that adds the React hooks (`useQueryParams`, `useQueryParamsStatic`, `usePageParams`, `useRouteInfo`). It imports `next/navigation` and `next/router`, so it must only be used in client components.
14
13
 
15
- import { createRouter } from "@uxf/router";
14
+ Define the routes once as plain data, then build each router from it:
16
15
 
17
- export default createRouter(
18
- {
19
- index: {
20
- path: "/"
21
- },
22
- "admin/index": {
23
- path: "/admin",
24
- schema: object({
25
- param1: optional(number())
26
- })
27
- },
28
- "blog/detail": {
29
- path: "/blog/[id]",
30
- schema: object({
31
- id: number()
32
- })
16
+ ```ts
17
+ // routes/routes.ts – plain route definitions, importable anywhere
18
+
19
+ import { number, object, optional, string } from "superstruct";
20
+
21
+ export const routes = {
22
+ index: {
23
+ path: "/",
24
+ },
25
+ "admin/index": {
26
+ path: "/admin",
27
+ schema: object({
28
+ param1: optional(number()),
29
+ }),
30
+ },
31
+ "blog/detail": {
32
+ path: "/blog/[id]",
33
+ schema: object({
34
+ id: number(),
35
+ }),
36
+ },
37
+ "localized-route": {
38
+ path: {
39
+ en: "/en/home",
40
+ cs: "/cs/domu",
33
41
  },
34
- "localized-route": {
35
- path: {
36
- en: "/en/home",
37
- cs: "/cs/domu"
38
- },
39
- schema: object({
40
- term: optional(string()),
41
- })
42
- }
43
- } as const,
44
- {
45
- locales: ["cs", "en"],
46
- baseUrl: "https://www.uxf.cz"
47
- } as const
48
- );
42
+ schema: object({
43
+ term: optional(string()),
44
+ }),
45
+ },
46
+ } as const;
47
+
48
+ export const routerOptions = {
49
+ locales: ["cs", "en"],
50
+ baseUrl: "https://www.uxf.cz",
51
+ } as const;
52
+
53
+ export type RouteList = typeof routes;
49
54
  ```
50
55
 
51
56
  ```ts
52
- // routes/index.ts
57
+ // routes/index.ts – server-safe entry (`@app-routes`), usable in RSC
53
58
 
54
- import router from "./routes";
55
- import { UxfGetServerSideProps, UxfGetStaticProps, ExtractSchema } from "@uxf/router";
59
+ import { createRouter, ExtractSchema, UxfGetServerSideProps, UxfGetStaticProps } from "@uxf/router";
56
60
  import { PreviewData as NextPreviewData } from "next/types";
61
+ import { RouteList, routerOptions, routes } from "./routes";
57
62
 
58
- export const {
59
- routeToUrl,
60
- sitemapGenerator,
61
- useQueryParams,
62
- useQueryParamsStatic
63
- } = router;
63
+ export const { route, routeToUrl, getRouteInfo, createRouteMatcher, createSitemapGenerator } = createRouter(
64
+ routes,
65
+ routerOptions,
66
+ );
64
67
 
65
68
  export type GetRouteSchema<K extends keyof RouteList> = ExtractSchema<RouteList[K]>;
66
69
 
@@ -68,13 +71,27 @@ export type GetStaticProps<
68
71
  Route extends keyof RouteList,
69
72
  Props extends { [key: string]: any } = { [key: string]: any },
70
73
  PreviewData extends NextPreviewData = NextPreviewData,
71
- > = UxfGetStaticProps<RouteList, Route, Props, PreviewData>;
74
+ > = UxfGetStaticProps<RouteList, Route, Props, PreviewData>;
72
75
 
73
76
  export type GetServerSideProps<
74
77
  Route extends keyof RouteList,
75
78
  Props extends { [key: string]: any } = { [key: string]: any },
76
79
  PreviewData extends NextPreviewData = NextPreviewData,
77
- > = UxfGetServerSideProps<RouteList, Route, Props, PreviewData>;
80
+ > = UxfGetServerSideProps<RouteList, Route, Props, PreviewData>;
81
+ ```
82
+
83
+ ```ts
84
+ // routes/client.ts – client entry (`@app-routes/client`), hooks for client components
85
+
86
+ "use client";
87
+
88
+ import { createClientRouter } from "@uxf/router/client";
89
+ import { routerOptions, routes } from "./routes";
90
+
91
+ export const { useQueryParams, useQueryParamsStatic, usePageParams, useRouteInfo } = createClientRouter(
92
+ routes,
93
+ routerOptions,
94
+ );
78
95
  ```
79
96
 
80
97
  Add configuration to `tsconfig.json`
@@ -86,6 +103,9 @@ Add configuration to `tsconfig.json`
86
103
  "paths": {
87
104
  "@app-routes": [
88
105
  "routes"
106
+ ],
107
+ "@app-routes/client": [
108
+ "routes/client"
89
109
  ]
90
110
  }
91
111
  }
@@ -94,9 +114,11 @@ Add configuration to `tsconfig.json`
94
114
 
95
115
  ## useQueryParams
96
116
 
117
+ Hooks live in the client entry (`@app-routes/client`):
118
+
97
119
  ```tsx
98
- import { useQueryParams } from "@app-routes";
99
- import { queryParamToNumber } from "./helper";
120
+ import { useQueryParams } from "@app-routes/client";
121
+ import { queryParamToNumber } from "@uxf/router";
100
122
 
101
123
  // can be used on SSR pages
102
124
  const [query, { push, replace }] = useQueryParams("route-name");
package/client.d.ts ADDED
@@ -0,0 +1 @@
1
+ export * from "./create-client-router";
package/client.js ADDED
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ "use client";
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
16
+ };
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ // Client entry point for `@uxf/router/client`.
19
+ //
20
+ // Exposes `createClientRouter` – a superset of `createRouter` (`@uxf/router`) that adds the React
21
+ // hooks. It imports client-only modules (`next/navigation`, `next/router`), so it must not be
22
+ // imported into React Server Components. For server-safe usage import from `@uxf/router` instead.
23
+ __exportStar(require("./create-client-router"), exports);
@@ -0,0 +1,44 @@
1
+ import { Params } from "next/dist/server/request/params";
2
+ import { Infer } from "superstruct";
3
+ import { RouteInfo, Router, RouterOptions } from "./create-router";
4
+ import { QueryParams, RoutesDefinition } from "./types";
5
+ export interface TransitionOptions {
6
+ shallow?: boolean;
7
+ locale?: string | false;
8
+ scroll?: boolean;
9
+ unstable_skipClientCache?: boolean;
10
+ }
11
+ type QueryParamsResult<Nullable extends boolean, T extends keyof RouteList, Locales extends string[], RouteList extends RoutesDefinition<Locales>> = [
12
+ Nullable extends true ? Infer<NonNullable<RouteList[T]["schema"]>> | null : Infer<NonNullable<RouteList[T]["schema"]>>,
13
+ {
14
+ push: (params: Infer<NonNullable<RouteList[T]["schema"]>>, options?: TransitionOptions) => Promise<boolean>;
15
+ replace: (params: Infer<NonNullable<RouteList[T]["schema"]>>, options?: TransitionOptions) => Promise<boolean>;
16
+ }
17
+ ];
18
+ /**
19
+ * The client router – a superset of `createRouter` (`@uxf/router`) that adds the React hooks.
20
+ * Lives in a separate client-only module (`@uxf/router/client`) because it imports `next/navigation`
21
+ * and `next/router`, which are not safe to pull into React Server Components.
22
+ */
23
+ export type ClientRouter<Locales extends string[], RouteList extends RoutesDefinition<Locales>> = Router<Locales, RouteList> & {
24
+ useRouteInfo: () => RouteInfo | null;
25
+ /**
26
+ * @deprecated use useQueryParamsStatic or useQueryParams instead
27
+ */
28
+ useQueryParamsDeprecated: <T extends keyof RouteList>() => QueryParams<RouteList, T>;
29
+ /**
30
+ * Returns path params merged with search params
31
+ */
32
+ usePageParams: () => Params | null;
33
+ useQueryParams: <T extends keyof RouteList>(routeName: T) => QueryParamsResult<false, T, Locales, RouteList>;
34
+ useQueryParamsStatic: <T extends keyof RouteList>(routeName: T) => QueryParamsResult<true, T, Locales, RouteList>;
35
+ };
36
+ /**
37
+ * Creates the client router – everything `createRouter` returns plus the React hooks
38
+ * (`useQueryParams`, `useQueryParamsStatic`, `usePageParams`, `useRouteInfo`).
39
+ *
40
+ * Use this in client components. For server-safe usage (including React Server Components) use
41
+ * `createRouter` from `@uxf/router` instead.
42
+ */
43
+ export declare function createClientRouter<Locales extends string[], RouteList extends RoutesDefinition<Locales>>(routes: RouteList, routerOptions: RouterOptions<Locales>): ClientRouter<Locales, RouteList>;
44
+ export {};
@@ -0,0 +1,100 @@
1
+ "use strict";
2
+ "use client";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.createClientRouter = createClientRouter;
5
+ const is_nil_1 = require("@uxf/core/utils/is-nil");
6
+ const is_not_nil_1 = require("@uxf/core/utils/is-not-nil");
7
+ const throw_error_1 = require("@uxf/core/utils/throw-error");
8
+ const navigation_1 = require("next/navigation");
9
+ const router_1 = require("next/router");
10
+ const superstruct_1 = require("superstruct");
11
+ const create_router_1 = require("./create-router");
12
+ /**
13
+ * Creates the client router – everything `createRouter` returns plus the React hooks
14
+ * (`useQueryParams`, `useQueryParamsStatic`, `usePageParams`, `useRouteInfo`).
15
+ *
16
+ * Use this in client components. For server-safe usage (including React Server Components) use
17
+ * `createRouter` from `@uxf/router` instead.
18
+ */
19
+ function createClientRouter(routes, routerOptions) {
20
+ const base = (0, create_router_1.createRouter)(routes, routerOptions);
21
+ const { routeToUrl, getRouteInfo } = base;
22
+ const useRouteInfo = () => {
23
+ const pathname = (0, navigation_1.usePathname)();
24
+ return (0, is_not_nil_1.isNotNil)(pathname) ? getRouteInfo(pathname) : null;
25
+ };
26
+ const usePageParams = () => {
27
+ const searchParams = (0, navigation_1.useSearchParams)();
28
+ const params = (0, navigation_1.useParams)();
29
+ if ((0, is_nil_1.isNil)(searchParams) || (0, is_nil_1.isNil)(params)) {
30
+ return null;
31
+ }
32
+ // Collect all query params, handling duplicate keys as arrays
33
+ const queryParams = Array.from(searchParams.entries()).reduce((acc, [key, value]) => {
34
+ const existing = acc[key];
35
+ if (existing === undefined) {
36
+ // First occurrence - set as single value
37
+ acc[key] = value;
38
+ }
39
+ else if (Array.isArray(existing)) {
40
+ // Already an array - append
41
+ existing.push(value);
42
+ }
43
+ else {
44
+ // Second occurrence - convert to array
45
+ acc[key] = [existing, value];
46
+ }
47
+ return acc;
48
+ }, {});
49
+ return { ...params, ...queryParams };
50
+ };
51
+ return {
52
+ ...base,
53
+ useRouteInfo,
54
+ usePageParams,
55
+ useQueryParamsDeprecated: () => (0, router_1.useRouter)().query,
56
+ useQueryParams(routeName) {
57
+ var _a;
58
+ const router = (0, navigation_1.useRouter)();
59
+ const pageParams = usePageParams();
60
+ if ((0, is_nil_1.isNil)(pageParams)) {
61
+ throw new Error("Router is not ready. Use useQueryParamsStatic instead of useQueryParams.");
62
+ }
63
+ const schema = (_a = routes[routeName].schema) !== null && _a !== void 0 ? _a : (0, throw_error_1.throwError)(`Route '${String(routeName)}' has no schema.`);
64
+ return [
65
+ (0, superstruct_1.mask)(pageParams, schema),
66
+ {
67
+ push: (params, options) => {
68
+ router.push(routeToUrl(routeName, params, {}), options);
69
+ return Promise.resolve(true); // remove me
70
+ },
71
+ replace: (params, options) => {
72
+ router.replace(routeToUrl(routeName, params, {}), options);
73
+ return Promise.resolve(true); // remove me
74
+ },
75
+ },
76
+ ];
77
+ },
78
+ useQueryParamsStatic(routeName) {
79
+ const router = (0, navigation_1.useRouter)();
80
+ const pageParams = usePageParams();
81
+ const schema = routes[routeName].schema;
82
+ if (!schema) {
83
+ throw new Error(`Route '${String(routeName)}' has no schema.`);
84
+ }
85
+ return [
86
+ (0, is_not_nil_1.isNotNil)(pageParams) ? (0, superstruct_1.mask)(pageParams, schema) : null,
87
+ {
88
+ push: (params) => {
89
+ router.push(routeToUrl(routeName, params, {}));
90
+ return Promise.resolve(true); // remove me
91
+ },
92
+ replace: (params) => {
93
+ router.replace(routeToUrl(routeName, params, {}));
94
+ return Promise.resolve(true); // remove me
95
+ },
96
+ },
97
+ ];
98
+ },
99
+ };
100
+ }
@@ -1,14 +1,7 @@
1
- import { Params } from "next/dist/server/request/params";
2
- import { LinkProps } from "next/link";
3
- import { Infer, Struct } from "superstruct";
1
+ import type { LinkProps } from "next/link";
2
+ import type { Struct } from "superstruct";
4
3
  import { SitemapGeneratorOptions, SitemapGeneratorType, SitemapRouteResolvers } from "./sitemap-generator";
5
- import { QueryParams, RouteDefinition, RoutesDefinition } from "./types";
6
- export interface TransitionOptions {
7
- shallow?: boolean;
8
- locale?: string | false;
9
- scroll?: boolean;
10
- unstable_skipClientCache?: boolean;
11
- }
4
+ import { RouteDefinition, RoutesDefinition } from "./types";
12
5
  export type ExtractSchema<T> = T extends {
13
6
  schema: Struct<infer U, any>;
14
7
  } ? U : null;
@@ -26,39 +19,28 @@ export type FunctionParametersGeneratorWithPartialParams<Locales extends string[
26
19
  }[keyof RouteList];
27
20
  type RouteFunction<Locales extends string[], RouteList extends RoutesDefinition<Locales>> = (...args: FunctionParametersGenerator<Locales, RouteList>) => LinkProps["href"];
28
21
  type RouteToUrlFunction<Locales extends string[], RouteList extends RoutesDefinition<Locales>> = (...args: FunctionParametersGenerator<Locales, RouteList>) => string;
29
- type QueryParamsResult<Nullable extends boolean, T extends keyof RouteList, Locales extends string[], RouteList extends RoutesDefinition<Locales>> = [
30
- Nullable extends true ? Infer<NonNullable<RouteList[T]["schema"]>> | null : Infer<NonNullable<RouteList[T]["schema"]>>,
31
- {
32
- push: (params: Infer<NonNullable<RouteList[T]["schema"]>>, options?: TransitionOptions) => Promise<boolean>;
33
- replace: (params: Infer<NonNullable<RouteList[T]["schema"]>>, options?: TransitionOptions) => Promise<boolean>;
34
- }
35
- ];
36
22
  export type RouteMatcher = (pathname: string, pathParams?: Record<string, string | string[]>) => boolean;
37
- type RouteInfo = {
23
+ export type RouteInfo = {
38
24
  pathname: string;
39
25
  routeName: string;
40
26
  routeDefinition: RouteDefinition<any>;
41
27
  };
42
- type Router<Locales extends string[], RouteList extends RoutesDefinition<Locales>> = {
28
+ /**
29
+ * The router. Contains only pure URL/route helpers – no React hooks and no client-only imports
30
+ * (`next/navigation`, `next/router`), so it is safe to import into React Server Components.
31
+ *
32
+ * For the React hooks (`useQueryParams`, `usePageParams`, …) use `createClientRouter`
33
+ * from `@uxf/router/client`, which is a superset of this router.
34
+ */
35
+ export type Router<Locales extends string[], RouteList extends RoutesDefinition<Locales>> = {
43
36
  route: RouteFunction<Locales, RouteList>;
44
37
  routeToUrl: RouteToUrlFunction<Locales, RouteList>;
45
38
  createSitemapGenerator: (resolvers: SitemapRouteResolvers<Locales, RouteList>, options?: SitemapGeneratorOptions) => SitemapGeneratorType;
46
39
  routes: RouteList;
47
40
  getRouteInfo: (pathname: string) => RouteInfo | null;
48
- useRouteInfo: () => RouteInfo | null;
49
- /**
50
- * @deprecated use useQueryParamsStatic or useQueryParams instead
51
- */
52
- useQueryParamsDeprecated: <T extends keyof RouteList>() => QueryParams<RouteList, T>;
53
- /**
54
- * Returns path params merged with search params
55
- */
56
- usePageParams: () => Params | null;
57
- useQueryParams: <T extends keyof RouteList>(routeName: T) => QueryParamsResult<false, T, Locales, RouteList>;
58
- useQueryParamsStatic: <T extends keyof RouteList>(routeName: T) => QueryParamsResult<true, T, Locales, RouteList>;
59
41
  createRouteMatcher: (...args: FunctionParametersGeneratorWithPartialParams<Locales, RouteList>) => RouteMatcher;
60
42
  };
61
- type RouterOptions<L extends string[]> = {
43
+ export type RouterOptions<L extends string[]> = {
62
44
  baseUrl?: string;
63
45
  locales?: L;
64
46
  };
@@ -66,5 +48,13 @@ export type ExtendedRouteDefinition<Locales extends string[]> = RouteDefinition<
66
48
  regex: RegExp[];
67
49
  };
68
50
  export type ExtendedRoutesDefinition<Locales extends string[]> = Record<string, ExtendedRouteDefinition<Locales>>;
51
+ /**
52
+ * Creates the router – URL building, route matching and sitemap generation.
53
+ *
54
+ * This is the server-safe default: it contains no React hooks and no client-only imports, so it can
55
+ * be used anywhere, including React Server Components. If you need the React hooks (`useQueryParams`,
56
+ * `usePageParams`, …) use `createClientRouter` from `@uxf/router/client` instead – it returns
57
+ * everything this router does plus the hooks.
58
+ */
69
59
  export declare function createRouter<Locales extends string[], RouteList extends RoutesDefinition<Locales>>(routes: RouteList, routerOptions: RouterOptions<Locales>): Router<Locales, RouteList>;
70
60
  export {};
@@ -2,13 +2,8 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createRouter = createRouter;
4
4
  const empty_object_1 = require("@uxf/core/constants/empty-object");
5
- const is_nil_1 = require("@uxf/core/utils/is-nil");
6
- const is_not_nil_1 = require("@uxf/core/utils/is-not-nil");
7
5
  const qs_1 = require("@uxf/core/utils/qs");
8
6
  const throw_error_1 = require("@uxf/core/utils/throw-error");
9
- const navigation_1 = require("next/navigation");
10
- const router_1 = require("next/router");
11
- const superstruct_1 = require("superstruct");
12
7
  const sitemap_generator_1 = require("./sitemap-generator");
13
8
  const path_to_regex_1 = require("./utils/path-to-regex");
14
9
  /**
@@ -19,6 +14,14 @@ const path_to_regex_1 = require("./utils/path-to-regex");
19
14
  function decodeArgs(args) {
20
15
  return { route: args[0], params: args[1], options: args[2] };
21
16
  }
17
+ /**
18
+ * Creates the router – URL building, route matching and sitemap generation.
19
+ *
20
+ * This is the server-safe default: it contains no React hooks and no client-only imports, so it can
21
+ * be used anywhere, including React Server Components. If you need the React hooks (`useQueryParams`,
22
+ * `usePageParams`, …) use `createClientRouter` from `@uxf/router/client` instead – it returns
23
+ * everything this router does plus the hooks.
24
+ */
22
25
  function createRouter(routes, routerOptions) {
23
26
  const routesWithRegex = Object.entries(routes).reduce((acc, [routeName, routeDefinition]) => ({
24
27
  ...acc,
@@ -94,35 +97,6 @@ function createRouter(routes, routerOptions) {
94
97
  routeDefinition: entry[1],
95
98
  };
96
99
  };
97
- const useRouteInfo = () => {
98
- const pathname = (0, navigation_1.usePathname)();
99
- return (0, is_not_nil_1.isNotNil)(pathname) ? getRouteInfo(pathname) : null;
100
- };
101
- const usePageParams = () => {
102
- const searchParams = (0, navigation_1.useSearchParams)();
103
- const params = (0, navigation_1.useParams)();
104
- if ((0, is_nil_1.isNil)(searchParams) || (0, is_nil_1.isNil)(params)) {
105
- return null;
106
- }
107
- // Collect all query params, handling duplicate keys as arrays
108
- const queryParams = Array.from(searchParams.entries()).reduce((acc, [key, value]) => {
109
- const existing = acc[key];
110
- if (existing === undefined) {
111
- // First occurrence - set as single value
112
- acc[key] = value;
113
- }
114
- else if (Array.isArray(existing)) {
115
- // Already an array - append
116
- existing.push(value);
117
- }
118
- else {
119
- // Second occurrence - convert to array
120
- acc[key] = [existing, value];
121
- }
122
- return acc;
123
- }, {});
124
- return { ...params, ...queryParams };
125
- };
126
100
  return {
127
101
  route(...args) {
128
102
  var _a;
@@ -140,52 +114,6 @@ function createRouter(routes, routerOptions) {
140
114
  },
141
115
  routes,
142
116
  getRouteInfo,
143
- useRouteInfo,
144
- usePageParams,
145
- useQueryParamsDeprecated: () => (0, router_1.useRouter)().query,
146
- useQueryParams(routeName) {
147
- var _a;
148
- const router = (0, navigation_1.useRouter)();
149
- const pageParams = usePageParams();
150
- if ((0, is_nil_1.isNil)(pageParams)) {
151
- throw new Error("Router is not ready. Use useQueryParamsStatic instead of useQueryParams.");
152
- }
153
- const schema = (_a = routes[routeName].schema) !== null && _a !== void 0 ? _a : (0, throw_error_1.throwError)(`Route '${String(routeName)}' has no schema.`);
154
- return [
155
- (0, superstruct_1.mask)(pageParams, schema),
156
- {
157
- push: (params, options) => {
158
- router.push(routeToUrl(routeName, params, {}), options);
159
- return Promise.resolve(true); // remove me
160
- },
161
- replace: (params, options) => {
162
- router.replace(routeToUrl(routeName, params, {}), options);
163
- return Promise.resolve(true); // remove me
164
- },
165
- },
166
- ];
167
- },
168
- useQueryParamsStatic(routeName) {
169
- const router = (0, navigation_1.useRouter)();
170
- const pageParams = usePageParams();
171
- const schema = routes[routeName].schema;
172
- if (!schema) {
173
- throw new Error(`Route '${String(routeName)}' has no schema.`);
174
- }
175
- return [
176
- (0, is_not_nil_1.isNotNil)(pageParams) ? (0, superstruct_1.mask)(pageParams, schema) : null,
177
- {
178
- push: (params) => {
179
- router.push(routeToUrl(routeName, params, {}));
180
- return Promise.resolve(true); // remove me
181
- },
182
- replace: (params) => {
183
- router.replace(routeToUrl(routeName, params, {}));
184
- return Promise.resolve(true); // remove me
185
- },
186
- },
187
- ];
188
- },
189
117
  createRouteMatcher(...args) {
190
118
  const [requiredRouteName, requiredParams] = args;
191
119
  return (pathname, pageParams) => {
@@ -1,10 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ const fs_1 = require("fs");
4
+ const path_1 = require("path");
3
5
  const superstruct_1 = require("superstruct");
6
+ const create_router_1 = require("./create-router");
4
7
  const merge_route_matchers_1 = require("./merge-route-matchers");
5
- const router_1 = require("./router");
6
8
  const superstruct_2 = require("./superstruct");
7
- const { routeToUrl, createRouteMatcher } = (0, router_1.createRouter)({
9
+ const { routeToUrl, createRouteMatcher } = (0, create_router_1.createRouter)({
8
10
  index: {
9
11
  path: "/",
10
12
  schema: (0, superstruct_1.object)({
@@ -168,3 +170,11 @@ test("merge route matchers", () => {
168
170
  param2: "any",
169
171
  })).toBe(false);
170
172
  });
173
+ // Guards the RSC-safety invariant: the router must not pull in any client-only modules.
174
+ test("create-router has no client-only imports", () => {
175
+ const source = (0, fs_1.readFileSync)((0, path_1.join)(__dirname, "create-router.ts"), "utf8");
176
+ expect(source).not.toMatch(/from "next\/navigation"/);
177
+ expect(source).not.toMatch(/from "next\/router"/);
178
+ expect(source).not.toMatch(/from "react"/);
179
+ expect(source).not.toMatch(/from "react-dom"/);
180
+ });
package/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
+ export * from "./create-router";
1
2
  export * from "./helper";
2
3
  export * from "./merge-route-matchers";
3
- export * from "./router";
4
4
  export * from "./types";
package/index.js CHANGED
@@ -1,4 +1,6 @@
1
1
  "use strict";
2
+ // Root entry point for `@uxf/router`. RSC-safe: contains no React hooks and no client-only imports.
3
+ // For the React hooks use `createClientRouter` from `@uxf/router/client`.
2
4
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
5
  if (k2 === undefined) k2 = k;
4
6
  var desc = Object.getOwnPropertyDescriptor(m, k);
@@ -14,7 +16,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
16
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
17
  };
16
18
  Object.defineProperty(exports, "__esModule", { value: true });
19
+ __exportStar(require("./create-router"), exports);
17
20
  __exportStar(require("./helper"), exports);
18
21
  __exportStar(require("./merge-route-matchers"), exports);
19
- __exportStar(require("./router"), exports);
20
22
  __exportStar(require("./types"), exports);
@@ -1,2 +1,2 @@
1
- import { RouteMatcher } from "./router";
1
+ import { RouteMatcher } from "./create-router";
2
2
  export declare function mergeRouteMatchers(routeMatchers: RouteMatcher[]): RouteMatcher;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uxf/router",
3
- "version": "11.114.0",
3
+ "version": "11.120.0",
4
4
  "description": "UXF Router",
5
5
  "author": "UXFans <dev@uxf.cz>",
6
6
  "homepage": "https://gitlab.com/uxf-npm/router#readme",
@@ -3,6 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.routesCheck = routesCheck;
5
5
  const throw_error_1 = require("@uxf/core/utils/throw-error");
6
+ const fs_1 = require("fs");
6
7
  const path_1 = require("path");
7
8
  const process_1 = require("process");
8
9
  const true_case_path_1 = require("true-case-path");
@@ -19,6 +20,33 @@ function fileExists(path) {
19
20
  }
20
21
  }
21
22
  }
23
+ function findAppRoute(dir, parts) {
24
+ if (parts.length === 0) {
25
+ if (fileExists((0, path_1.join)(dir, "page.ts")) || fileExists((0, path_1.join)(dir, "route.ts"))) {
26
+ return true;
27
+ }
28
+ }
29
+ else {
30
+ if (findAppRoute((0, path_1.join)(dir, parts[0]), parts.slice(1))) {
31
+ return true;
32
+ }
33
+ }
34
+ let entries;
35
+ try {
36
+ entries = (0, fs_1.readdirSync)(dir);
37
+ }
38
+ catch {
39
+ return false;
40
+ }
41
+ for (const entry of entries) {
42
+ if (entry.startsWith("(") && entry.endsWith(")")) {
43
+ if (findAppRoute((0, path_1.join)(dir, entry), parts)) {
44
+ return true;
45
+ }
46
+ }
47
+ }
48
+ return false;
49
+ }
22
50
  let hasError = false;
23
51
  function routesCheck(routes, options) {
24
52
  Object.entries(routes).forEach(([routeName, routeDefinition]) => {
@@ -40,10 +68,7 @@ function routesCheck(routes, options) {
40
68
  if (fileExists(pagePath)) {
41
69
  return;
42
70
  }
43
- if (fileExists((0, path_1.join)(options.appDir, ...pathParts, "page.ts"))) {
44
- return;
45
- }
46
- if (fileExists((0, path_1.join)(options.appDir, ...pathParts, "route.ts"))) {
71
+ if (findAppRoute(options.appDir, pathParts)) {
47
72
  return;
48
73
  }
49
74
  throw new Error("Invalid");
@@ -13,6 +13,8 @@ describe("routesCheck", () => {
13
13
  app1: { path: "/app-directory" },
14
14
  app2: { path: "/app-directory/[param]" },
15
15
  app3: { path: "/api" },
16
+ app4: { path: "/grouped-route" }, // page lives inside a (group) folder
17
+ app5: { path: "/nested/deep" }, // page lives inside nested (group) folders
16
18
  }, {
17
19
  shouldProcessExit: false,
18
20
  appDir: (0, path_1.join)(__dirname, "__test__", "app"),
@@ -1,8 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const superstruct_1 = require("superstruct");
4
- const router_1 = require("./router");
5
- const { createSitemapGenerator, routeToUrl } = (0, router_1.createRouter)({
4
+ const create_router_1 = require("./create-router");
5
+ const { createSitemapGenerator, routeToUrl } = (0, create_router_1.createRouter)({
6
6
  index: {
7
7
  path: "/",
8
8
  schema: (0, superstruct_1.object)({
File without changes