# `@lazarv/react-server/router`

Typed routing primitives for the file-system and programmatic routers. Includes route factories, schema-based validators, and the type-level helpers that extract params from route patterns.

## Functions

### `createRoute`

```ts
function createRoute<TPath extends string, TParams = ExtractParams<TPath>, TSearch = Record<string, string>>(path: TPath, element: React.ReactNode, options: RouteOptions<TPath, TParams, TSearch> & {
    parse: {
        search: {
            [K in keyof TSearch]: (value: string) => TSearch[K];
        };
    };
}): TypedRoute<TPath, TParams, TSearch, Partial<TSearch>>;
function createRoute<TPath extends string, TParams = ExtractParams<TPath>, TSearch = Record<string, string>>(path: TPath, element: React.ReactNode, options?: RouteOptions<TPath, TParams, TSearch>): TypedRoute<TPath, TParams, TSearch>;
function createRoute(path: "*", element: React.ReactNode, options?: Omit<RouteOptions<"*">, "exact">): TypedRoute<"*", {}, {}>;
function createRoute(path: `${string}/*`, element: React.ReactNode, options?: Omit<RouteOptions<string>, "exact">): TypedRoute<string, {}, {}>;
function createRoute(element: React.ReactNode, options?: Omit<RouteOptions<"*">, "exact">): TypedRoute<"*", {}, {}>;
function createRoute(path: "*", options?: Omit<RouteOptions<"*">, "exact">): RouteDescriptor<"*", {}, {}>;
function createRoute(path: `${string}/*`, options?: Omit<RouteOptions<string>, "exact">): RouteDescriptor<string, {}, {}>;
function createRoute<TPath extends string, TParams = ExtractParams<TPath>, TSearch = Record<string, string>>(path: TPath, options: RouteOptions<TPath, TParams, TSearch> & {
    parse: {
        search: {
            [K in keyof TSearch]: (value: string) => TSearch[K];
        };
    };
}): RouteDescriptor<TPath, TParams, TSearch, Partial<TSearch>>;
function createRoute<TPath extends string, TParams = ExtractParams<TPath>, TSearch = Record<string, string>>(path: TPath, options?: RouteOptions<TPath, TParams, TSearch>): RouteDescriptor<TPath, TParams, TSearch>;
function createRoute<TPath extends string, TParams = ExtractParams<TPath>, TSearch = Record<string, string>, TSearchInput = TSearch>(descriptor: RouteDescriptor<TPath, TParams, TSearch, TSearchInput>, element: React.ReactNode, options?: {
    loading?: React.ComponentType | React.ReactNode;
    resources?: (RouteResourceBinding | RouteResource | ClientRouteResources)[];
}): TypedRoute<TPath, TParams, TSearch, TSearchInput>;
```

Create a typed route with parse-based search — search params are optional on Link

### `createRouter`

```ts
function createRouter<T extends Record<string, TypedRoute<any, any, any, any>>>(routes: T): TypedRouter<T>;
```

Collect typed routes into a router.

```tsx
const router = createRouter({ home, about, user, notFound });

<router.Routes />
<router.user.Link params={{ id: "42" }}>User</router.user.Link>
```

### `useActionState`

```ts
function useActionState<T extends (...args: any[]) => T extends (...args: any[]) => infer R ? R : any, E = Error>(action: T): ActionState<ReturnType<T>, E>;
```

This hook returns the current state of the passed server action.
The state includes the form data, the data returned by the action, the error if the action failed and also the action's internal id.

**Parameters**

- `action` — Server action reference for which you want to get the action state

**Returns** — The current state of the referenced action

```tsx
import { useActionState } from '@lazarv/react-server';
import { addTodo } from './actions';

export default function AddTodo() {
 const { formData, error } = useActionState(addTodo);
 return (
  <form action={addTodo}>
   <input name="title" type="text" defaultValue={formData?.get?.("title") as string} />
   <button type="submit">Submit</button>
   {error?.map?.(({ message }, i) => (
    <p key={i}>{message}</p>
   )) ?? (error && (
    <p>{error}</p>
   ))}
  </form>
 );
}
```

### `useMatch`

```ts
function useMatch<T = RouteParams>(path: string, options?: MatchOptions): T | null;
```

This hook returns the route parameters for the given path.

**Parameters**

- `path` — The path to match
- `options` — Options for the match

**Returns** — The route parameters for the given path

```tsx
import { useMatch } from '@lazarv/react-server';

export default function Todo() {
 const { id } = useMatch('/todos/[id]');
 return <p>Todo id: {id}</p>;
}
```

## Constants

### `Route`

```ts
const Route: React.FC<React.PropsWithChildren<{
    path: string;
    exact?: boolean;
    matchers?: RouteMatchers;
    element?: React.ReactElement;
    render?: (props: React.PropsWithChildren<RouteParams>) => React.ReactElement;
    fallback?: boolean;
}>>;
```

Represents a route in the application.

```tsx
import { Route } from '@lazarv/react-server';

export default function App() {
 return (
  <Route path="/todos" exact>
   <Todos />
  </Route>
 );
}
```

### `SearchParams`

```ts
const SearchParams: React.FC<SearchParamsProps>;
```

Bidirectional search-param transform boundary.

Wrap your routes (or the entire app) to intercept how search params are
read from and written to the URL on the client.

```tsx
import { SearchParams } from "@lazarv/react-server/router";

<SearchParams
  decode={(sp) => { sp.delete("utm_source"); return sp; }}
>
  {children}
</SearchParams>
```

## Interfaces

### `AssertSchema`

```ts
interface AssertSchema<T = any> {
    assert(data: unknown): T;
}
```

Schema with ArkType-style `.assert()` (throws on failure, returns `T`).

### `ParseSchema`

```ts
interface ParseSchema<T = any> {
    parse(data: unknown): T;
}
```

Schema with a generic `.parse()` that throws on failure.

### `RouteDescriptor`

```ts
interface RouteDescriptor<TPath extends string = string, TParams = ExtractParams<TPath>, TSearch = Record<string, string>, TSearchInput = TSearch> {
    readonly path: TPath | undefined;
    readonly fallback: boolean;
    readonly exact: boolean;
    readonly validate: RouteValidate<TParams, TSearch> | null;
    readonly parse: RouteParse<TParams, TSearch> | null;
    Link: TPath extends "*" ? never : ExtractParams<TPath> extends Record<string, never> ? React.FC<Omit<import("../client/navigation").LinkProps<string>, "to" | "search"> & {
        to?: never;
        params?: never;
        search?: TSearchInput | ((prev: TSearch) => TSearchInput);
    }> : React.FC<Omit<import("../client/navigation").LinkProps<string>, "to" | "search"> & {
        to?: never;
        params: TParams;
        search?: TSearchInput | ((prev: TSearch) => TSearchInput);
    }>;
    href: TPath extends "*" ? never : ExtractParams<TPath> extends Record<string, never> ? (params?: never) => string : (params: TParams) => string;
    useParams(): TParams | null;
    useSearchParams(): TSearch;
    SearchParams: React.FC<SearchParamsProps>;
}
```

Route descriptor returned by the client-safe `createRoute` (from `navigation`).
Contains route metadata, `href()`, and a typed `.Link` component — but no `.Route`.
Both descriptors and full `TypedRoute` instances satisfy this.

### `RouteOptions`

```ts
interface RouteOptions<TPath extends string = string, TParams = ExtractParams<TPath>, TSearch = Record<string, string>> {
    exact?: boolean;
    loading?: React.ComponentType | React.ReactNode;
    matchers?: RouteMatchers;
    render?: (params: TParams & {
        children?: React.ReactNode;
    }) => React.ReactNode;
    children?: React.ReactNode;
    validate?: RouteValidate<TParams, TSearch>;
    parse?: RouteParse<TParams, TSearch>;
    resources?: (RouteResourceBinding | RouteResource | ClientRouteResources)[];
}
```

### `RouteParse`

```ts
interface RouteParse<TParams = any, TSearch = any> {
    params?: {
        [K in keyof TParams]?: (value: string) => TParams[K];
    };
    search?: {
        [K in keyof TSearch]?: (value: string) => TSearch[K];
    };
}
```

Lightweight parser map for route params and search params.
Each key maps to a function that converts the raw string to the desired type.
Built-in constructors like `Number`, `Boolean`, and `Date` work directly.

```ts
const user = createRoute("/user/[id]", {
  parse: { params: { id: Number } }
});
// user.useParams() → { id: 42 } instead of { id: "42" }
```

### `RouteResource`

```ts
interface RouteResource {
    query: (key?: any) => Promise<any>;
}
```

A resource descriptor (singleton) that can be used directly in
a route's `resources` array without `.from()`.

### `RouteResourceBinding`

```ts
interface RouteResourceBinding {
    resource: {
        query: (key?: any) => Promise<any>;
    };
    mapFn: (routeParams: Record<string, any>, searchParams: Record<string, any>) => any;
}
```

A route-resource binding — see `@lazarv/react-server/resources`.
Returned by `resource.from(mapFn)`.

### `RouteValidate`

```ts
interface RouteValidate<TParams = any, TSearch = any> {
    params?: ValidateSchema<TParams>;
    search?: ValidateSchema<TSearch>;
}
```

### `SafeParseSchema`

```ts
interface SafeParseSchema<T = any> {
    parse(data: unknown): T;
    safeParse(data: unknown): {
        success: true;
        data: T;
    } | {
        success: false;
        error: unknown;
    };
}
```

Schema with Zod / Valibot-style `.safeParse()` + `.parse()`.

### `SearchParamsProps`

```ts
interface SearchParamsProps {
    decode?: (searchParams: URLSearchParams) => URLSearchParams;
    encode?: (searchParams: URLSearchParams, current: URLSearchParams) => URLSearchParams;
    children?: React.ReactNode;
}
```

Props for the `<SearchParams>` transform boundary.

- `decode` — intercepts reading: receives raw `URLSearchParams` from the URL,
  returns a cleaned `URLSearchParams` that hooks (`useSearchParams`, etc.) see.
- `encode` — intercepts writing (typed Link merge mode): receives the merged
  `URLSearchParams` and the current URL params, returns the final
  `URLSearchParams` that goes into the URL.

Both are optional. Nesting is supported — decode chains outer→inner,
encode chains inner→outer.

### `TypedRoute`

```ts
interface TypedRoute<TPath extends string = string, TParams = ExtractParams<TPath>, TSearch = Record<string, string>, TSearchInput = TSearch> extends RouteDescriptor<TPath, TParams, TSearch, TSearchInput> {
    Route: React.FC<Partial<{
        element: React.ReactNode;
        loading: React.ComponentType | React.ReactNode;
        render: (params: TParams & {
            children?: React.ReactNode;
        }) => React.ReactNode;
        children: React.ReactNode;
        exact: boolean;
        fallback: boolean;
    }>>;
}
```

## Types

### `ActionState`

```ts
type ActionState<T, E> = {
    formData: FormData | null;
    data: T | null;
    error: E | null;
    actionId: string | null;
};
```

Represents the state of a server action.

### `ClientRouteResources`

```ts
type ClientRouteResources = RouteResourceBinding | (RouteResourceBinding | RouteResource)[];
```

Client resource binding(s) from a "use client" module.
Opaque on the server — passes through RSC serialization and resolves
on the client for navigation pre-loading. Can be a single binding or
an array of bindings exported from a "use client" module.

Place alongside server bindings in the `resources` array:
```ts
resources: [serverBinding, clientBinding]
```

### `ExtractParams`

```ts
type ExtractParams<T extends string> = T extends `${string}[...${infer P}]${infer R}` ? {
    [K in P]: string[];
} & ExtractParams<R> : T extends `${string}[${infer P}]${infer R}` ? {
    [K in P]: string;
} & ExtractParams<R> : {};
```

Extract typed params from a route path pattern.

```ts
ExtractParams<"/user/[id]">                  // { id: string }
ExtractParams<"/blog/[slug]/[commentId]">    // { slug: string; commentId: string }
ExtractParams<"/files/[...path]">            // { path: string[] }
ExtractParams<"/about">                      // {}
```

### `InferSchema`

```ts
type InferSchema<T> = T extends SafeParseSchema<infer U> ? U : T extends AssertSchema<infer U> ? U : T extends ParseSchema<infer U> ? U : never;
```

Infer the output type of a ValidateSchema

### `MatchOptions`

```ts
type MatchOptions = {
    exact?: boolean;
    fallback?: boolean;
    matchers?: RouteMatchers;
};
```

Options for the useMatch hook.

### `RouteMatchers`

```ts
type RouteMatchers = Record<string, (value: string) => boolean>;
```

### `RouteParams`

```ts
type RouteParams = Record<string, string>;
```

### `TypedRouter`

```ts
type TypedRouter<T extends Record<string, TypedRoute<any, any, any, any>>> = {
    Routes: React.FC;
    SearchParams: React.FC<SearchParamsProps>;
} & {
    [K in keyof T]: T[K];
};
```

### `ValidateSchema`

```ts
type ValidateSchema<T = any> = SafeParseSchema<T> | AssertSchema<T> | ParseSchema<T>;
```

Any schema that exposes at least one recognized validation method.

Supported patterns (tried in this order at runtime):
1. `.safeParse()` — Zod, Valibot
2. `.assert()` — ArkType
3. `.parse()` — generic fallback

You can use any library whose schema objects satisfy one of these shapes.
