alepha
Version:
Easy-to-use modern TypeScript framework for building many kind of applications.
1,223 lines • 83.3 kB
TypeScript
import { Alepha, AlephaError, Async, KIND, Middleware, Primitive, SchemaValidator, Static, TObject, TSchema } from "alepha";
import { DateTimeProvider } from "alepha/datetime";
import { BrowserHeadProvider, Head, ServerHeadProvider, SimpleHead } from "alepha/react/head";
import { ServerHandler, ServerRequest, ServerRouterProvider } from "alepha/server";
import { LinkProvider } from "alepha/server/links";
import { AnchorHTMLAttributes, CSSProperties, FC, ReactNode } from "react";
import { ServerStaticProvider } from "alepha/server/static";
import { FileSystemProvider } from "alepha/system";
import { Route, RouterProvider } from "alepha/router";
//#region ../../src/react/router/constants/PAGE_PRELOAD_KEY.d.ts
/**
* Symbol key for SSR module preloading path.
* Using Symbol.for() allows the Vite plugin to inject this at build time.
* @internal
*/
declare const PAGE_PRELOAD_KEY: unique symbol;
//#endregion
//#region ../../src/react/router/errors/Redirection.d.ts
/**
* Used for Redirection during the page loading.
*
* Depends on the context, it can be thrown or just returned.
*
* @example
* ```ts
* import { Redirection } from "alepha/react";
*
* const MyPage = $page({
* loader: async () => {
* if (needRedirect) {
* throw new Redirection("/new-path");
* }
* },
* });
* ```
*/
declare class Redirection extends AlephaError {
readonly redirect: string;
constructor(redirect: string);
}
//#endregion
//#region ../../src/react/router/providers/RootComponentsProvider.d.ts
/**
* Extension point letting any module contribute root-level React nodes that
* render on every page (siblings of the page view, inside AlephaContext).
*
* A module pushes into `rootComponents` from its `register` hook; the array
* is rendered by `ReactPageProvider.root()`. SSR-safe (same element feeds
* server render + client hydrate).
*/
declare class RootComponentsProvider {
rootComponents: ReactNode[];
}
//#endregion
//#region ../../src/react/router/providers/RouterLocaleProvider.d.ts
/**
* Generic locale path-prefix mechanism for the router.
*
* This provider knows nothing about i18n — it only deals with URL path
* segments. It is configured by the i18n module (`I18nProvider`) when
* `routing: "prefix"` is enabled, which keeps the dependency one-directional
* (`i18n → router`) and avoids a module cycle.
*
* The default locale is served WITHOUT a prefix (`/about` = default,
* `/fr/about` = French). The active locale is derived from the current
* request/navigation and stored under `alepha.react.router.locale`, so every
* URL the router builds (`pathname()`) automatically carries the right prefix.
*/
declare class RouterLocaleProvider {
protected readonly alepha: Alepha;
/**
* Whether locale path-prefixing is active. Off by default — opt-in via the
* i18n module.
*/
enabled: boolean;
/**
* The default locale, served without a path prefix (e.g. `"en"` → `/about`).
*/
defaultLocale: string;
/**
* All known locales, including the default one.
*/
locales: string[];
/**
* Configure the provider. Called by the i18n module before the SSR routes
* are registered.
*/
configure(options: {
enabled?: boolean;
defaultLocale?: string;
locales?: string[];
}): void;
/**
* Locales that carry a URL prefix — every known locale except the default.
*/
get prefixedLocales(): string[];
/**
* Splits a leading locale segment off a pathname.
*
* - `/fr/about` → `{ locale: "fr", pathname: "/about" }` when `fr` is a
* prefixed locale.
* - `/about` → `{ locale: defaultLocale, pathname: "/about" }`.
*
* When prefixing is disabled the pathname is returned untouched.
*/
detect(pathname: string): {
locale: string;
pathname: string;
};
/**
* Prepends the locale prefix to a pathname when needed. The default locale
* (and any unknown/disabled case) returns the pathname unchanged.
*/
withPrefix(pathname: string, locale?: string): string;
/**
* The active locale, derived from the current request/navigation. Falls back
* to the default locale when nothing has been detected.
*/
get current(): string;
set current(locale: string);
/**
* Normalizes a stripped pathname so it always starts with a single slash and
* carries no trailing slash (except the root `/`).
*/
protected normalize(pathname: string): string;
}
//#endregion
//#region ../../src/react/router/providers/ReactPageProvider.d.ts
declare const reactPageOptions: import("alepha").Atom<import("zod").ZodObject<{
strictMode: import("zod").ZodDefault<import("zod").ZodBoolean>;
staticFilePattern: import("zod").ZodString;
}, import("zod/v4/core").$strip>, "alepha.react.page.options">;
/**
* Handle page routes for React applications. (Browser and Server)
*/
declare class ReactPageProvider {
protected readonly dateTimeProvider: DateTimeProvider;
protected readonly log: import("alepha/logger").Logger;
protected readonly options: Readonly<{
strictMode: boolean;
staticFilePattern: string;
}>;
protected readonly alepha: Alepha;
protected readonly rootComponentsProvider: RootComponentsProvider;
protected readonly localeProvider: RouterLocaleProvider;
protected readonly pages: PageRoute[];
protected nextIdCursor: number;
protected readonly configure: import("alepha").HookPrimitive<"configure">;
getPages(): PageRoute[];
getConcretePages(): ConcretePageRoute[];
page(name: string): PageRoute;
/**
* Find a route by name anywhere in the tree (including nested children).
* Returns undefined if no page with that name exists.
*/
protected findRoute(name: string, routes?: PageRouteEntry[]): PageRoute | undefined;
pathname(name: string, options?: {
params?: Record<string, string>;
query?: Record<string, string>;
}): string;
url(name: string, options?: {
params?: Record<string, string>;
host?: string;
}): URL;
root(state: ReactRouterState): ReactNode;
protected convertStringObjectToObject: (schema?: TSchema, value?: any) => any;
/**
* Create a new RouterState based on a given route and request.
* This method resolves the layers for the route, applying any query and params schemas defined in the route.
* It also handles errors and redirects.
*/
createLayers(route: PageRoute, state: ReactRouterState, previous?: PreviousLayerData[]): Promise<CreateLayersResult>;
protected getErrorHandler(route: PageRoute): ErrorHandler | undefined;
protected createElement(page: PageRoute, props: Record<string, any>, targetUrl?: URL): Promise<ReactNode>;
/**
* Detect chunk load errors caused by stale dynamic imports after a deployment.
* When new assets are deployed with different hashes, old chunk URLs return 404.
*/
protected isChunkLoadError(error: unknown): boolean;
/**
* Navigate to the target URL to fetch updated assets after a chunk load failure.
* Uses sessionStorage to prevent infinite reload loops.
* Returns true if navigation was initiated.
*/
protected reloadAfterChunkError(url?: URL): boolean;
renderError(error: Error): ReactNode;
renderEmptyView(): ReactNode;
href(page: {
options: {
name?: string;
};
}, params?: Record<string, any>): string;
compile(path: string, params?: Record<string, string>): string;
protected renderView(index: number, path: string, view: ReactNode | undefined, page: PageRoute): ReactNode;
/**
* Resolve the effective `ssr` value for a route by walking up the parent
* chain. Returns the nearest explicit `ssr` value, defaulting to `true`.
*
* The decision is made at the leaf: a parent's `ssr` only acts as a default
* for descendants that did not set their own value.
*/
isSSR(route: PageRoute): boolean;
protected map(pages: Array<PagePrimitive>, target: PagePrimitive): PageRouteEntry;
add(entry: PageRouteEntry): void;
protected createMatch(page: PageRoute): string;
protected nextId(): string;
}
declare const isPageRoute: (it: any) => it is PageRoute;
interface PageRouteEntry extends Omit<PagePrimitiveOptions, "children" | "parent"> {
children?: PageRouteEntry[];
}
interface ConcretePageRoute extends PageRoute {
/**
* When exported, static routes can be split into multiple pages with different params.
* We replace 'name' by the new name for each static entry, and old 'name' becomes 'staticName'.
*/
staticName?: string;
params?: Record<string, string>;
}
interface PageRoute extends PageRouteEntry {
type: "page";
name: string;
parent?: PageRoute;
match: string;
/**
* Optional meta information associated with the page route, can be used for any purpose (e.g. menu label, icon, etc.).
*/
label?: string;
}
interface Layer {
config?: {
query?: Record<string, any>;
params?: Record<string, any>;
context?: Record<string, any>;
};
name: string;
props?: Record<string, any>;
error?: Error;
part?: string;
element: ReactNode;
index: number;
path: string;
route?: PageRoute;
cache?: boolean;
}
type PreviousLayerData = Omit<Layer, "element" | "index" | "path">;
interface AnchorProps {
href: string;
onClick: (ev?: any) => any;
}
interface ReactRouterState {
/**
* Stack of layers for the current page.
*/
layers: Array<Layer>;
/**
* URL of the current page.
*/
url: URL;
/**
* Error handler for the current page.
*/
onError: ErrorHandler;
/**
* Params extracted from the URL for the current page.
*/
params: Record<string, any>;
/**
* Query parameters extracted from the URL for the current page.
*/
query: Record<string, string>;
/**
* Optional meta information associated with the current page.
*/
meta: Record<string, any>;
/**
* Head configuration for the current page (title, meta tags, etc.).
* Populated by HeadProvider during SSR.
*/
head: Head;
/**
* Optional name of the current page route
*/
name?: string;
}
interface RouterStackItem {
route: PageRoute;
config?: Record<string, any>;
props?: Record<string, any>;
error?: Error;
cache?: boolean;
}
interface CreateLayersResult {
redirect?: string;
state?: ReactRouterState;
}
//#endregion
//#region ../../src/react/router/services/ReactPageService.d.ts
/**
* $page methods interface.
*/
declare abstract class ReactPageService {
fetch(pathname: string, options?: PagePrimitiveRenderOptions): Promise<{
html: string;
response: Response;
}>;
render(name: string, options?: PagePrimitiveRenderOptions): Promise<PagePrimitiveRenderResult>;
}
//#endregion
//#region ../../src/react/router/primitives/$page.d.ts
/**
* Main primitive for defining a React route in the application.
*
* The $page primitive is the core building block for creating type-safe, SSR-enabled React routes.
* It provides a declarative way to define pages with powerful features:
*
* **Routing & Navigation**
* - URL pattern matching with parameters (e.g., `/users/:id`)
* - Nested routing with parent-child relationships
* - Type-safe URL parameter and query string validation
*
* **Data Loading**
* - Server-side data fetching with the `loader` function
* - Automatic serialization and hydration for SSR
* - Access to request context, URL params, and parent data
*
* **Component Loading**
* - Direct component rendering or lazy loading for code splitting
* - Client-only rendering when browser APIs are needed
* - Automatic fallback handling during hydration
*
* **Performance Optimization**
* - Static generation for pre-rendered pages at build time
* - Server-side caching with configurable TTL and providers
* - Code splitting through lazy component loading
*
* **Error Handling**
* - Custom error handlers with support for redirects
* - Hierarchical error handling (child → parent)
* - HTTP status code handling (404, 401, etc.)
*
* @example Simple page with data fetching
* ```typescript
* const userProfile = $page({
* path: "/users/:id",
* schema: {
* params: z.object({ id: z.integer() }),
* query: z.object({ tab: z.text().optional() })
* },
* loader: async ({ params }) => {
* const user = await userApi.getUser(params.id);
* return { user };
* },
* lazy: () => import("./UserProfile.tsx")
* });
* ```
*
* @example Nested routing with error handling
* ```typescript
* const projectSection = $page({
* path: "/projects/:id",
* children: () => [projectBoard, projectSettings],
* loader: async ({ params }) => {
* const project = await projectApi.get(params.id);
* return { project };
* },
* errorHandler: (error) => {
* if (HttpError.is(error, 404)) {
* return <ProjectNotFound />;
* }
* }
* });
* ```
*
* @example Static generation with caching
* ```typescript
* const blogPost = $page({
* path: "/blog/:slug",
* static: {
* entries: posts.map(p => ({ params: { slug: p.slug } }))
* },
* loader: async ({ params }) => {
* const post = await loadPost(params.slug);
* return { post };
* }
* });
* ```
*/
declare const $page: {
<TConfig extends PageConfigSchema = PageConfigSchema, TProps extends object = any, TPropsParent extends object = TPropsParentDefault>(options: PagePrimitiveOptions<TConfig, TProps, TPropsParent>): PagePrimitive<TConfig, TProps, TPropsParent>;
[KIND]: typeof PagePrimitive;
};
interface PagePrimitiveOptions<TConfig extends PageConfigSchema = PageConfigSchema, TProps extends object = TPropsDefault, TPropsParent extends object = TPropsParentDefault> {
/**
* Identifier name for the page. Must be unique.
*
* @default Primitive key
*/
name?: string;
/**
* Add a pathname to the page.
*
* Pathname can contain parameters, like `/post/:slug`.
*
* @default ""
*/
path?: string;
/**
* Add an input schema to define:
* - `params`: parameters from the pathname.
* - `query`: query parameters from the URL.
*/
schema?: TConfig;
/**
* Middleware to apply to the loader function.
* Works the same as `use` on `$action` and `$job`.
*
* @example
* ```ts
* dashboard = $page({
* use: [$cache({ ttl: [5, "minutes"] })],
* loader: async ({ params }) => this.dashboardService.getData(),
* lazy: () => import("./Dashboard.tsx"),
* });
* ```
*/
use?: Middleware[];
/**
* Load data before rendering the page.
*
* This function receives
* - the request context (params, query, etc.)
* - the parent props (if page has a parent)
*
* > In SSR, the returned data will be serialized and sent to the client, then reused during the client-side hydration.
*
* Loader can be stopped by throwing an error, which will be handled by the `errorHandler` function.
* It's common to throw a `NotFoundError` to display a 404 page.
*
* RedirectError can be thrown to redirect the user to another page.
*/
loader?: (context: PageLoader<TConfig, TPropsParent>) => Async<TProps>;
/**
* Default props to pass to the component when rendering the page.
*
* Resolved props from the `resolve` function will override these default props.
*/
props?: () => Partial<TProps>;
/**
* The component to render when the page is loaded.
*
* If `lazy` is defined, this will be ignored.
* Prefer using `lazy` to improve the initial loading time.
*/
component?: FC<TProps & TPropsParent>;
/**
* Lazy load the component when the page is loaded.
*
* It's recommended to use this for components to improve the initial loading time
* and enable code-splitting.
*/
lazy?: () => Promise<{
default: FC<TProps & TPropsParent>;
}>;
/**
* Attach child pages to create nested routes, adopting them as children of
* this page.
*
* Use this when you want a parent to own children it cannot modify — most
* notably pages that come from an injected router in another package, whose
* `$page` definitions are frozen and cannot declare `parent` themselves.
*
* ```ts
* layout = $page({
* path: "/app",
* children: () => [
* this.productRouter.catalogPage, // from $inject(ProductRouter)
* this.productRouter.checkoutPage,
* ],
* });
* ```
*
* Use a thunk (`() => [...]`) when the children are defined later in the
* same class.
*
* **Declare each edge from one side only.** If a child already sets
* `parent: thisPage`, do NOT also add it to `children` — the link is
* already established, and declaring it on both sides creates a TypeScript
* circular dependency between the two class fields (each references the
* other before it is initialised).
*/
children?: Array<PagePrimitive> | (() => Array<PagePrimitive>);
/**
* Define a parent page for nested routing.
*
* Use this when you own the child page and can edit its definition — it is
* the simplest way to nest routes and reads top-down. For pages you do NOT
* own (e.g. pages exposed by an injected router from another package), let
* the parent adopt them via its `children` option instead.
*
* **Declare each edge from one side only.** If you set `parent` here, do
* NOT also add this page to the parent's `children` array — the link is
* already established, and declaring it on both sides creates a TypeScript
* circular dependency between the two class fields.
*/
parent?: PagePrimitive<PageConfigSchema, TPropsParent, any>;
/**
* UI-affordance predicate for this page's navigation entry — **NOT security**.
* For real access control, gate the route with `use: [$secure({ permissions })]`
* (server-enforced); `can` is only consulted by navigation surfaces (sidebar,
* breadcrumbs, command palette), never by the router.
*
* Receives a `{ has }` context (a permission probe equivalent to
* `useAuth().has`) so it can express arbitrary access logic — including
* OR-of-permissions, which `nav.permission` (AND) cannot.
*
* - `true` (or omitted) → nav entry visible and enabled.
* - `"disabled"` → visible but disabled (greyed — e.g. insufficient role / paywalled).
* - `false` → hidden.
*
* For the common "needs these permissions" case, prefer the declarative
* {@link PageNav.permission} instead.
*/
can?: (ctx: PageCanContext) => boolean | "disabled";
/**
* Navigation metadata — declares this page's presence in navigation
* surfaces: the sidebar, the breadcrumb trail, and a command palette
* (Spotlight). A page WITHOUT `nav` is route-only (reachable by URL but not
* listed).
*
* `$page` is pure React, so `label` / `icon` / `description` / `badge` accept
* `ReactNode` (pass `<Users />` directly).
*
* Visibility is UI-only (NOT security — gate the route with `use: [$secure(...)]`):
* an entry is hidden when `nav.hidden`, when `nav.permission` is set and not
* fully granted, or when `can()` returns `false`; it renders disabled when
* `can()` returns `"disabled"`.
*/
nav?: PageNav;
/**
* Catch any error from the `loader` function or during `rendering`.
*
* Expected to return one of the following:
* - a ReactNode to render an error page
* - a Redirection to redirect the user
* - undefined to let the error propagate
*
* If not defined, the error will be thrown and handled by the server or client error handler.
* If a leaf $page does not define an error handler, the error can be caught by parent pages.
*
* @example Catch a 404 from API and render a custom not found component:
* ```ts
* loader: async ({ params, query }) => {
* api.fetch("/api/resource", { params, query });
* },
* errorHandler: (error, context) => {
* if (HttpError.is(error, 404)) {
* return <ResourceNotFound />;
* }
* }
* ```
*
* @example Catch an 401 error and redirect the user to the login page:
* ```ts
* loader: async ({ params, query }) => {
* // but the user is not authenticated
* api.fetch("/api/resource", { params, query });
* },
* errorHandler: (error, context) => {
* if (HttpError.is(error, 401)) {
* // throwing a Redirection is also valid!
* return new Redirection("/login");
* }
* }
* ```
*/
errorHandler?: ErrorHandler;
/**
* If true, the page will be considered as a static page, immutable and cacheable.
* Replace boolean by an object to define static entries. (e.g. list of params/query)
*
* Browser-side: it only works with the build pipeline, which can pre-render the page at build time.
*
* Server-side: It will act as timeless cached page. You can use `cache` to configure the cache behavior.
*/
static?: boolean | {
entries?: Array<Partial<PageRequestConfig<TConfig>>>;
};
/**
* Enable or disable server-side rendering for this page.
*
* - `true` (default): the page component is rendered on the server and
* hydrated on the client.
* - `false`: the loader still runs on the server (so data is preloaded and
* serialized for hydration), but the component is rendered only on the
* client. The server emits no HTML for this page.
*
* **Decided at the leaf, inherited as default by descendants.**
*
* The effective value is determined by the matched leaf page: walk up the
* parent chain and use the nearest explicit `ssr` value. Setting
* `ssr: false` on a parent therefore acts as the default for its children;
* a child can override with `ssr: true`.
*
* Skipping rendering while keeping the loader is the recommended strategy
* for CPU-constrained server environments (e.g. Cloudflare Workers) and
* heavy admin/dashboard views where SSR provides little SEO value.
*
* @example
* ```ts
* root = $page({ ssr: false }); // default for children
* home = $page({ parent: root, ssr: true }); // overrides → SSR
* about = $page({ parent: root }); // inherits → no SSR
* ```
*
* @default true
*/
ssr?: boolean;
/**
* Called before the server response is sent to the client. (server only)
*/
onServerResponse?: (request: ServerRequest) => unknown;
/**
* Called when user enters the page. (browser only)
*
* Useful for browser-only side effects like analytics, scroll management,
* or focus handling that don't need to return data to the component.
*
* @example
* ```ts
* onEnter: () => {
* analytics.trackPageView("/dashboard");
* window.scrollTo(0, 0);
* }
* ```
*/
onEnter?: () => void;
/**
* Called when user leaves the page. (browser only)
*/
onLeave?: () => void;
/**
* @experimental
*
* Add a css animation when the page is loaded or unloaded.
* It uses CSS animations, so you need to define the keyframes in your CSS.
*
* @example Simple animation name
* ```ts
* animation: "fadeIn"
* ```
*
* CSS example:
* ```css
* @keyframes fadeIn {
* from { opacity: 0; }
* to { opacity: 1; }
* }
* ```
*
* @example Detailed animation
* ```ts
* animation: {
* enter: { name: "fadeIn", duration: 300 },
* exit: { name: "fadeOut", duration: 200, timing: "ease-in-out" },
* }
* ```
*
* @example Only exit animation
* ```ts
* animation: {
* exit: "fadeOut"
* }
* ```
*
* @example With custom timing function
* ```ts
* animation: {
* enter: { name: "fadeIn", duration: 300, timing: "cubic-bezier(0.4, 0, 0.2, 1)" },
* exit: { name: "fadeOut", duration: 200, timing: "ease-in-out" },
* }
* ```
*/
animation?: PageAnimation;
/**
* Head configuration for the page (title, meta tags, etc.).
*
* Can be a static object or a function that receives resolved props.
*
* @example Static head
* ```ts
* head: {
* title: "My Page",
* description: "Page description",
* }
* ```
*
* @example Dynamic head based on props
* ```ts
* head: (props) => ({
* title: props.user.name,
* description: `Profile of ${props.user.name}`,
* })
* ```
*/
head?: Head | ((props: TProps, previous?: Head) => Head);
/**
* Redirect to another path when this page is matched.
*
* This is a shorthand for throwing a `Redirection` in the loader.
* The redirect is performed before any loader or component rendering.
*
* @example
* ```ts
* home = $page({
* path: "/",
* redirect: "/dashboard",
* });
* ```
*/
redirect?: string;
/**
* Label for the page, used for navigation menus or breadcrumbs.
*
* This is optional and can be used by the application to display a user-friendly name for the page.
* It has no functional impact on routing or rendering.
*/
label?: string;
/**
* Source path for SSR module preloading.
*
* This is automatically injected by the viteAlephaPreload plugin.
* It maps to the source file path used in Vite's SSR manifest.
*
* @internal
*/
[PAGE_PRELOAD_KEY]?: string;
}
declare class PagePrimitive<TConfig extends PageConfigSchema = PageConfigSchema, TProps extends object = TPropsDefault, TPropsParent extends object = TPropsParentDefault> extends Primitive<PagePrimitiveOptions<TConfig, TProps, TPropsParent>> {
protected readonly reactPageService: ReactPageService;
protected onInit(): void;
get name(): string;
/**
* For testing or build purposes.
*
* This will render the page (HTML layout included or not) and return the HTML + context.
* Only valid for server-side rendering, it will throw an error if called on the client-side.
*/
render(options?: PagePrimitiveRenderOptions): Promise<PagePrimitiveRenderResult>;
fetch(options?: PagePrimitiveRenderOptions): Promise<{
html: string;
response: Response;
}>;
}
/**
* Context passed to {@link PagePrimitiveOptions.can} — a permission probe
* (equivalent to `useAuth().has`) supplied by the navigation builder.
*/
interface PageCanContext {
has: (permission: string) => boolean;
}
/**
* Navigation metadata for a page — see {@link PagePrimitiveOptions.nav}.
* Consumed by the sidebar, breadcrumbs and the command palette, all derived
* from the route tree (no separate nav list).
*/
interface PageNav {
/**
* Label for nav surfaces. Falls back to the page's `label`, then `head.title`.
*/
label?: ReactNode;
/**
* Leading icon — a React element, e.g. `icon: <Users />`.
*/
icon?: ReactNode;
/**
* Section the entry belongs to: sidebar group heading + command-palette section.
*/
group?: string;
/**
* Sort order within the group (ascending).
*/
order?: number;
/**
* Permission(s) required to SEE the entry — a single string or an array
* (ALL required, matching `$secure({ permissions })`). UI gate only; the
* real route gate is `use: [$secure(...)]`. For OR / custom logic use `can`.
*/
permission?: string | string[];
/**
* Extra search terms for the command palette (synonyms / aliases beyond the label).
*/
keywords?: string[];
/**
* Short description — command-palette subtitle / sidebar tooltip.
*/
description?: ReactNode;
/**
* Trailing badge (unread count, "New", …) for the sidebar entry.
*/
badge?: ReactNode;
/**
* Keep the route but exclude it from nav surfaces (e.g. a `/users/:id` detail page).
*/
hidden?: boolean;
}
type ErrorHandler = (error: Error, state: ReactRouterState) => ReactNode | Redirection | undefined;
interface PageConfigSchema {
query?: TSchema;
params?: TSchema;
}
type TPropsDefault = any;
type TPropsParentDefault = {};
interface PagePrimitiveRenderOptions {
params?: Record<string, string>;
query?: Record<string, string>;
/**
* If true, the HTML layout will be included in the response.
* If false, only the page content will be returned.
*
* @default true
*/
html?: boolean;
hydration?: boolean;
}
interface PagePrimitiveRenderResult {
html: string;
state: ReactRouterState;
redirect?: string;
}
interface PageRequestConfig<TConfig extends PageConfigSchema = PageConfigSchema> {
params: TConfig["params"] extends TSchema ? Static<TConfig["params"]> : Record<string, string>;
query: TConfig["query"] extends TSchema ? Static<TConfig["query"]> : Record<string, string>;
}
type PageLoader<TConfig extends PageConfigSchema = PageConfigSchema, TPropsParent extends object = TPropsParentDefault> = PageRequestConfig<TConfig> & TPropsParent & Omit<ReactRouterState, "layers" | "onError">;
type PageAnimation = PageAnimationObject | ((state: ReactRouterState) => PageAnimationObject | undefined);
type PageAnimationObject = CssAnimationName | {
enter?: CssAnimation | CssAnimationName;
exit?: CssAnimation | CssAnimationName;
};
type CssAnimationName = string;
type CssAnimation = {
name: string;
duration?: number;
timing?: string;
};
//#endregion
//#region ../../src/react/router/services/ReactRouter.d.ts
interface RouterPushOptions {
replace?: boolean;
params?: Record<string, string>;
query?: Record<string, string>;
meta?: Record<string, any>;
/**
* Recreate the whole page, ignoring the current state.
*/
force?: boolean;
}
/**
* Friendly browser router API.
*
* Can be safely used server-side, but most methods will be no-op.
*/
declare class ReactRouter<T extends object> {
protected readonly alepha: Alepha;
protected readonly pageApi: ReactPageProvider;
get state(): ReactRouterState;
get pages(): import("alepha/react/router").PageRoute[];
get concretePages(): import("alepha/react/router").ConcretePageRoute[];
get browser(): ReactBrowserProvider | undefined;
isActive(href: string, options?: {
startWith?: boolean;
}): boolean;
node(name: keyof VirtualRouter<T> | string, config?: {
params?: Record<string, any>;
query?: Record<string, any>;
}): any;
path(name: keyof VirtualRouter<T> | string, config?: {
params?: Record<string, any>;
query?: Record<string, any>;
}): string;
/**
* Reload the current page.
* This is equivalent to calling `go()` with the current pathname and search.
*/
reload(): Promise<void>;
getURL(): URL;
get location(): Location;
get current(): ReactRouterState;
get pathname(): string;
get query(): Record<string, string>;
back(): Promise<void>;
forward(): Promise<void>;
invalidate(props?: Record<string, any>): Promise<void>;
push(path: string, options?: RouterPushOptions): Promise<void>;
push(path: keyof VirtualRouter<T>, options?: RouterPushOptions): Promise<void>;
anchor(path: string, options?: RouterPushOptions): AnchorProps;
anchor(path: keyof VirtualRouter<T>, options?: RouterPushOptions): AnchorProps;
/**
* Prepend the base URL to the given path.
*/
base(path: string): string;
/**
* Set query params.
*
* @param record
* @param options
*/
setQueryParams(record: Record<string, any> | ((queryParams: Record<string, any>) => Record<string, any>), options?: {
/**
* If true, this will add a new entry to the history stack.
*/
push?: boolean;
}): void;
}
type VirtualRouter<T> = { [K in keyof T as T[K] extends PagePrimitive ? K : never]: T[K]; };
//#endregion
//#region ../../src/react/router/providers/ReactBrowserRouterProvider.d.ts
interface BrowserRoute extends Route {
page: PageRoute;
}
/**
* Implementation of AlephaRouter for React in browser environment.
*/
declare class ReactBrowserRouterProvider extends RouterProvider<BrowserRoute> {
protected readonly log: import("alepha/logger").Logger;
protected readonly alepha: Alepha;
protected readonly pageApi: ReactPageProvider;
protected readonly browserHeadProvider: BrowserHeadProvider;
protected readonly localeProvider: RouterLocaleProvider;
add(entry: PageRouteEntry): void;
protected readonly configure: import("alepha").HookPrimitive<"configure">;
transition(url: URL, previous?: PreviousLayerData[], meta?: {}, isStale?: () => boolean): Promise<string | undefined>;
root(state: ReactRouterState): ReactNode;
}
//#endregion
//#region ../../src/react/router/providers/ReactBrowserProvider.d.ts
/**
* React browser renderer configuration atom
*/
declare const reactBrowserOptions: import("alepha").Atom<import("zod").ZodObject<{
scrollRestoration: import("zod").ZodEnum<{
manual: "manual";
top: "top";
}>;
interceptAnchorClicks: import("zod").ZodDefault<import("zod").ZodBoolean>;
}, import("zod/v4/core").$strip>, "alepha.react.browser.options">;
type ReactBrowserRendererOptions = Static<typeof reactBrowserOptions.schema>;
declare module "alepha" {
interface State {
[reactBrowserOptions.key]: ReactBrowserRendererOptions;
}
}
declare class ReactBrowserProvider {
protected readonly log: import("alepha/logger").Logger;
protected readonly client: LinkProvider;
protected readonly alepha: Alepha;
protected readonly router: ReactBrowserRouterProvider;
protected readonly dateTimeProvider: DateTimeProvider;
protected readonly browserHeadProvider: BrowserHeadProvider;
protected readonly validator: SchemaValidator;
protected readonly options: Readonly<{
scrollRestoration: "manual" | "top";
interceptAnchorClicks: boolean;
}>;
get rootId(): string;
protected getRootElement(): HTMLElement;
transitioning?: {
to: string;
from?: string;
};
/**
* Monotonic counter used to detect stale (superseded) transitions.
*
* Each call to `render()` captures `++this.transitionId` and any
* subsequent `render()` invalidates older in-flight transitions.
* This prevents a slow page from racing past a newer navigation
* (e.g. user clicks /pageA which has a 2s loader, then clicks /pageB
* — pageB must remain the committed page).
*/
protected transitionId: number;
get state(): ReactRouterState;
/**
* Accessor for Document DOM API.
*/
get document(): Document;
/**
* Accessor for History DOM API.
*/
get history(): History;
/**
* Accessor for Location DOM API.
*/
get location(): Location;
get base(): string;
get url(): string;
pushState(path: string, replace?: boolean): void;
invalidate(props?: Record<string, any>): Promise<void>;
push(url: string, options?: RouterPushOptions): Promise<void>;
protected render(options?: RouterRenderOptions): Promise<void>;
/**
* Get embedded layers from the server.
*/
protected getHydrationState(): ReactHydrationState | undefined;
/**
* Apply the SSR hydration payload (the `#__ssr` script tag) to the atom
* store.
*
* Every key except `alepha.react.router.layers` is treated as an atom
* value. A registered atom's value is explicitly schema-validated: an
* invalid value is dropped (warn + keep the atom's default) instead of
* being trusted, so a tampered payload can't smuggle bad data into a
* validated atom. Atoms not registered yet fall through to
* `Alepha.set()`, which lets `StateManager.register()` decode them
* against the schema the moment they first get used.
*
* `alepha.react.router.layers` is deliberately skipped by this loop: it
* carries render instructions (`part`, `name`, `config`, `props`,
* `error`), not atom values. Those are NOT hardened here and are trusted
* as-is from the SSR payload — a tampered payload can still influence
* rendering through this key. Validating router layers is separate,
* future work; this method only guarantees atom values.
*/
protected applyHydration(hydration: ReactHydrationState): void;
protected readonly onTransitionEnd: import("alepha").HookPrimitive<"react:transition:end">;
readonly ready: import("alepha").HookPrimitive<"ready">;
/**
* Attach a delegated click listener that routes plain `<a href="/...">`
* clicks through the SPA router. Returns a detach function (used in tests).
*
* Bails out on modifier keys, non-primary mouse buttons, `target`, `download`,
* `data-no-router`, hash-only/external/non-http hrefs, and already-prevented
* events. Honors the runtime `interceptAnchorClicks` flag.
*/
protected attachAnchorInterceptor(): () => void;
protected stripBase(path: string): string;
}
type ReactHydrationState = {
"alepha.react.router.layers"?: Array<PreviousLayerData>;
} & {
[key: string]: any;
};
interface RouterRenderOptions {
url?: string;
previous?: PreviousLayerData[];
meta?: Record<string, any>;
/**
* Transition id used to detect supersession by a newer navigation.
* When omitted, render() allocates a fresh id internally.
*/
transitionId?: number;
}
//#endregion
//#region ../../src/react/router/components/ErrorViewer.d.ts
interface ErrorViewerProps {
error: Error;
alepha: Alepha;
onRetry?: () => void;
}
declare const ErrorViewer: (props: ErrorViewerProps) => import("react").JSX.Element;
//#endregion
//#region ../../src/react/router/components/Link.d.ts
interface LinkProps extends AnchorHTMLAttributes<HTMLAnchorElement> {
href: string;
}
/**
* Link component for client-side navigation.
*
* It's a simple wrapper around an anchor (`<a>`) element using the `useRouter` hook.
*/
declare const Link: (props: LinkProps) => import("react").DetailedReactHTMLElement<{
children?: import("react").ReactNode | undefined;
dangerouslySetInnerHTML?: {
__html: string | TrustedHTML;
} | undefined;
onCopy?: import("react").ClipboardEventHandler<HTMLAnchorElement> | undefined;
onCopyCapture?: import("react").ClipboardEventHandler<HTMLAnchorElement> | undefined;
onCut?: import("react").ClipboardEventHandler<HTMLAnchorElement> | undefined;
onCutCapture?: import("react").ClipboardEventHandler<HTMLAnchorElement> | undefined;
onPaste?: import("react").ClipboardEventHandler<HTMLAnchorElement> | undefined;
onPasteCapture?: import("react").ClipboardEventHandler<HTMLAnchorElement> | undefined;
onCompositionEnd?: import("react").CompositionEventHandler<HTMLAnchorElement> | undefined;
onCompositionEndCapture?: import("react").CompositionEventHandler<HTMLAnchorElement> | undefined;
onCompositionStart?: import("react").CompositionEventHandler<HTMLAnchorElement> | undefined;
onCompositionStartCapture?: import("react").CompositionEventHandler<HTMLAnchorElement> | undefined;
onCompositionUpdate?: import("react").CompositionEventHandler<HTMLAnchorElement> | undefined;
onCompositionUpdateCapture?: import("react").CompositionEventHandler<HTMLAnchorElement> | undefined;
onFocus?: import("react").FocusEventHandler<HTMLAnchorElement> | undefined;
onFocusCapture?: import("react").FocusEventHandler<HTMLAnchorElement> | undefined;
onBlur?: import("react").FocusEventHandler<HTMLAnchorElement> | undefined;
onBlurCapture?: import("react").FocusEventHandler<HTMLAnchorElement> | undefined;
onChange?: import("react").ChangeEventHandler<HTMLAnchorElement, Element> | undefined;
onChangeCapture?: import("react").ChangeEventHandler<HTMLAnchorElement, Element> | undefined;
onBeforeInput?: import("react").InputEventHandler<HTMLAnchorElement> | undefined;
onBeforeInputCapture?: import("react").InputEventHandler<HTMLAnchorElement> | undefined;
onInput?: import("react").InputEventHandler<HTMLAnchorElement> | undefined;
onInputCapture?: import("react").InputEventHandler<HTMLAnchorElement> | undefined;
onReset?: import("react").ReactEventHandler<HTMLAnchorElement> | undefined;
onResetCapture?: import("react").ReactEventHandler<HTMLAnchorElement> | undefined;
onSubmit?: import("react").SubmitEventHandler<HTMLAnchorElement> | undefined;
onSubmitCapture?: import("react").SubmitEventHandler<HTMLAnchorElement> | undefined;
onInvalid?: import("react").ReactEventHandler<HTMLAnchorElement> | undefined;
onInvalidCapture?: import("react").ReactEventHandler<HTMLAnchorElement> | undefined;
onLoad?: import("react").ReactEventHandler<HTMLAnchorElement> | undefined;
onLoadCapture?: import("react").ReactEventHandler<HTMLAnchorElement> | undefined;
onError?: import("react").ReactEventHandler<HTMLAnchorElement> | undefined;
onErrorCapture?: import("react").ReactEventHandler<HTMLAnchorElement> | undefined;
onKeyDown?: import("react").KeyboardEventHandler<HTMLAnchorElement> | undefined;
onKeyDownCapture?: import("react").KeyboardEventHandler<HTMLAnchorElement> | undefined;
onKeyPress?: import("react").KeyboardEventHandler<HTMLAnchorElement> | undefined;
onKeyPressCapture?: import("react").KeyboardEventHandler<HTMLAnchorElement> | undefined;
onKeyUp?: import("react").KeyboardEventHandler<HTMLAnchorElement> | undefined;
onKeyUpCapture?: import("react").KeyboardEventHandler<HTMLAnchorElement> | undefined;
onAbort?: import("react").ReactEventHandler<HTMLAnchorElement> | undefined;
onAbortCapture?: import("react").ReactEventHandler<HTMLAnchorElement> | undefined;
onCanPlay?: import("react").ReactEventHandler<HTMLAnchorElement> | undefined;
onCanPlayCapture?: import("react").ReactEventHandler<HTMLAnchorElement> | undefined;
onCanPlayThrough?: import("react").ReactEventHandler<HTMLAnchorElement> | undefined;
onCanPlayThroughCapture?: import("react").ReactEventHandler<HTMLAnchorElement> | undefined;
onDurationChange?: import("react").ReactEventHandler<HTMLAnchorElement> | undefined;
onDurationChangeCapture?: import("react").ReactEventHandler<HTMLAnchorElement> | undefined;
onEmptied?: import("react").ReactEventHandler<HTMLAnchorElement> | undefined;
onEmptiedCapture?: import("react").ReactEventHandler<HTMLAnchorElement> | undefined;
onEncrypted?: import("react").ReactEventHandler<HTMLAnchorElement> | undefined;
onEncryptedCapture?: import("react").ReactEventHandler<HTMLAnchorElement> | undefined;
onEnded?: import("react").ReactEventHandler<HTMLAnchorElement> | undefined;
onEndedCapture?: import("react").ReactEventHandler<HTMLAnchorElement> | undefined;
onLoadedData?: import("react").ReactEventHandler<HTMLAnchorElement> | undefined;
onLoadedDataCapture?: import("react").ReactEventHandler<HTMLAnchorElement> | undefined;
onLoadedMetadata?: import("react").ReactEventHandler<HTMLAnchorElement> | undefined;
onLoadedMetadataCapture?: import("react").ReactEventHandler<HTMLAnchorElement> | undefined;
onLoadStart?: import("react").ReactEventHandler<HTMLAnchorElement> | undefined;
onLoadStartCapture?: import("react").ReactEventHandler<HTMLAnchorElement> | undefined;
onPause?: import("react").ReactEventHandler<HTMLAnchorElement> | undefined;
onPauseCapture?: import("react").ReactEventHandler<HTMLAnchorElement> | undefined;
onPlay?: import("react").ReactEventHandler<HTMLAnchorElement> | undefined;
onPlayCapture?: import("react").ReactEventHandler<HTMLAnchorElement> | undefined;
onPlaying?: import("react").ReactEventHandler<HTMLAnchorElement> | undefined;
onPlayingCapture?: import("react").ReactEventHandler<HTMLAnchorElement> | undefined;
onProgress?: import("react").ReactEventHandler<HTMLAnchorElement> | undefined;
onProgressCapture?: import("react").ReactEventHandler<HTMLAnchorElement> | undefined;
onRateChange?: import("react").ReactEventHandler<HTMLAnchorElement> | undefined;
onRateChangeCapture?: import("react").ReactEventHandler<HTMLAnchorElement> | undefined;
onSeeked?: import("react").ReactEventHandler<HTMLAnchorElement> | undefined;
onSeekedCapture?: import("react").ReactEventHandler<HTMLAnchorElement> | undefined;
onSeeking?: import("react").ReactEventHandler<HTMLAnchorElement> | undefined;
onSeekingCapture?: import("react").ReactEventHandler<HTMLAnchorElement> | undefined;
onStalled?: import("react").ReactEventHandler<HTMLAnchorElement> | undefined;
onStalledCapture?: import("react").ReactEventHandler<HTMLAnchorElement> | undefined;
onSuspend?: import("react").ReactEventHandler<HTMLAnchorElement> | undefined;
onSuspendCapture?: import("react").ReactEventHandler<HTMLAnchorElement> | undefined;
onTimeUpdate?: import("react").ReactEventHandler<HTMLAnchorElement> | undefined;
onTimeUpdateCapture?: import("react").ReactEventHandler<HTMLAnchorElement> | undefined;
onVolumeChange?: import("react").ReactEventHandler<HTMLAnchorElement> | undefined;
onVolumeChangeCapture?: import("react").ReactEventHandler<HTMLAnchorElement> | undefined;
onWaiting?: import("react").ReactEventHandler<HTMLAnchorElement> | undefined;
onWaitingCapture?: import("react").ReactEventHandler<HTMLAnchorElement> | undefined;
onAuxClick?: import("react").MouseEventHandler<HTMLAnchorElement> | undefined;
onAuxClickCapture?: import("react").MouseEventHandler<HTMLAnchorElement> | undefined;
onClickCapture?: import("react").MouseEventHandler<HTMLAnchorElement> | undefined;
onContextMenu?: import("react").MouseEventHandler<HTMLAnchorElement> | undefined;
onContextMenuCapture?: import("react").MouseEventHandler<HTMLAnchorElement> | undefined;
onDoubleClick?: import("react").MouseEventHandler<HTMLAnchorElement> | undefined;
onDoubleClickCapture?: import("react").MouseEventHandler<HTMLAnchorElement> | undefined;
onDrag?: import("react").DragEventHandler<HTMLAnchorElement> | undefined;
onDragCapture?: import("react").DragEventHandler<HTMLAnchorElement> | undefined;
onDragEnd?: import("react").DragEventHandler<HTMLAnchorElement> | undefined;
onDragEndCapture?: import("react").DragEventHandler<HTMLAnchorElement> | undefined;
onDragEnter?: import("react").DragEventHandler<HTMLAnchorElement> | undefined;
onDragEnterCapture?: import("react").DragEventHandler<HTMLAnchorElement> | undefined;
onDragExit?: import("react").DragEventHandler<HTMLAnchorElement> | undefined;
onDragExitCapture?: import("react").DragEventHandler<HTMLAnchorElement> | undefined;
onDragLeave?: import("react").DragEventHandler<HTMLAnchorElement> | undefined;
onDragLeaveCapture?: import("react").DragEventHandler<HTMLAnchorElement> | undefined;
onDragOver?: import("react").DragEventHandler<HTMLAnchorElement> | undefined;
onDragOverCapture?: import("react").DragEventHandler<HTMLAnchorElement> | undefined;
onDragStart?: import("react").DragEventHandler<HTMLAnchorElement> | undefined;
onDragStartCapture?: import("react").DragEventHandler<HTMLAnchorElement> | undefined;
onDrop?: import("react").DragEventHandler<HTMLAnchorElement> | undefined;
onDropCapture?: import("react").DragEventHandler<HTMLAnchorElement> | undefined;
onMouseDown?: import("react").MouseEventHandler<HTMLAnchorElement> | undefined;
onMouseDownCapture?: import("react").MouseEventHandler<HTMLAnchorElement> | undefined;
onMouseEnter?: import("react").MouseEventHandler<HTMLAnchorElement> | undefined;
onMouseLeave?: import("react").MouseEventHandler<HTMLAnchorElement> | undefined;
onMouseMove?: import("react").MouseEventHandler<HTMLAnchorElement> | undefined;
onMouseMoveCapture?: import("react").MouseEventHandler<HTMLAnchorElement> | undefined;
onMouseOut?: import("react").MouseEventHandler<HTMLAnchorElement> | undefined;
onMouseOutCapture?: import("react").MouseEventHandler<HTMLAnchorElement> | undefined;
onMouseOver?: import("react").MouseEventHandler<HTMLAnchorElement> | undefined;
onMouseOverCapture?: import("react").MouseEventHandler<HTMLAnchorElement> | undefined;
onMouseUp?: import("react").MouseEventHandler<HTMLAnchorElement> | undefined;
onMouseUpCapture?: import("react").MouseEventHandler<HTMLAnchorElement> | undefined;
onSelect?: import("react").ReactEventHandler<HTMLAnchorElement> | undefined;
onSelectCapture?: import("react").ReactEventHandler<HTMLAnchorElement> | undefined;
onTouchCancel?: import("react").TouchEventHandler<HTMLAnchorElement> | undefined;
onTouchCancelCapture?: import("react").TouchEventHandler<HTMLAnchorElement> | undefined;
onTouchEnd?: import("react").TouchEventHandler<HTMLAnchorElement> | undefined;
onTouchEndCapture?: import("react").TouchEventHandler<HTMLAnchorElement> | undefined;
onTouchMove?: import("react").TouchEventHandler<HTMLAnchorElement> | undefined;
onTouchMoveCapture?: import("react").TouchEventHandler<HTMLAnchorElement> | undefined;
onTouchStart?: import("react").TouchEventHandler<HTMLAnchorElement> | undefined;
onTouchStartCapture?: import("react").TouchEventHandler<HTMLAnchorElement> | undefined;
onPointerDown?: import("react").PointerEventHandler<HTMLAnchorElement> | undefined;
onPointerDownCapture?: import("react").PointerEventHandler<HTMLAnchorElement> | undefined;
onPointerMove?: import("react").PointerEventHandler<HTMLAnchorElement> | undefined;
onPointerMoveCapture?: import("react").PointerEventHandler<HTMLAnchorElement> | undefined;
onPointerUp?: import("react").PointerEventHandler<HTMLAnchorElement> | undefined;
onPointerUpCapture?: import("react").PointerEventHandler<HTMLAnchorElement> | undefined;
onPointerCancel?: import("react").PointerEventHandler<HTMLAnchorElement> | undefined;
onPointerCancelCapture?: import("react").PointerEventHandler<HTMLAnchorElement> | undefined;
onPointerEnter?: import("react").PointerEventHandler<HTMLAnchorElement> | undefined;
onPointerLeave?: import("react").PointerEventHandler<HTMLAnchorElement> | undefined;
onPointerOver?: import("react").PointerEventHandler<HTMLAnchorElement> | undefined;
onPointerOverCapture?: import("react").PointerEventHandler<HTMLAnchorElement> | undefined;
onPointerOut?: import("react").PointerEventHandler<HTMLAnchorElement> | undefined;
onPointerOutCapture?: import("react").PointerEventHandler<HTMLAnchorElement> | undefined;
onGotPointerCapture?: import("react").PointerEventHandler<HTMLAnchorElement> | undefined;
onGotPointerCaptureCapture?: import("react").PointerEventHandler<HTMLAnchorElement> | undefined;
onLostPointerCapture?: import("react").PointerEventHandler<HTMLAnchorElement> | undefined;
onLostPointerCaptureCapture?: import("react").PointerEventHandler<HTMLAnchorElement> | undefined;
onScroll?: import("react").UIEventHandler<HTMLAnchorElement> | undefined;
onScrollCapture?: import("react").UIEventHandler<HTMLAnchorElement> | undefined;
onScrollEnd?: import("react").UIEventHandler