alepha
Version:
Easy-to-use modern TypeScript framework for building many kind of applications.
647 lines • 21.6 kB
TypeScript
import { Alepha, Async, Atom, Computed, Hook, Hooks, Service, State, Static, TAtomObject } from "alepha";
import React, { DependencyList, ErrorInfo, PropsWithChildren, ReactNode } from "react";
import { DurationLike } from "alepha/datetime";
import { ClientScope, HttpVirtualClient } from "alepha/server/links";
//#region ../../src/react/core/components/ClientOnly.d.ts
interface ClientOnlyProps {
fallback?: ReactNode;
disabled?: boolean;
}
/**
* A small utility component that renders its children only on the client side.
*
* Optionally, you can provide a fallback React node that will be rendered.
*
* You should use this component when
* - you have code that relies on browser-specific APIs
* - you want to avoid server-side rendering for a specific part of your application
* - you want to prevent pre-rendering of a component
*
* @example
* ```tsx
* import { ClientOnly } from "alepha/react";
*
* const MyComponent = () => {
* // Avoids SSR issues with Date API
* return (
* <ClientOnly>
* {new Date().toLocaleTimeString()}
* </ClientOnly>
* );
* }
* ```
*/
declare const ClientOnly: (props: PropsWithChildren<ClientOnlyProps>) => ReactNode;
//#endregion
//#region ../../src/react/core/components/ErrorBoundary.d.ts
/**
* Props for the ErrorBoundary component.
*/
interface ErrorBoundaryProps {
/**
* Fallback React node to render when an error is caught.
* If not provided, a default error message will be shown.
*/
fallback: (error: Error, reset: () => void) => ReactNode;
/**
* Optional callback that receives the error and error info.
* Use this to log errors to a monitoring service.
*/
onError?: (error: Error, info: ErrorInfo) => void;
/**
* Clear a caught error whenever any of these values changes (compared with
* `Object.is`, positionally).
*
* A boundary that catches has to be told when the reason to fail is gone —
* otherwise the fallback latches and every later render keeps showing the
* stale error. The router passes the current path, so navigating away from a
* page that threw recovers on its own.
*
* Unlike remounting via `key`, this clears the error WITHOUT tearing down the
* children, so unrelated component state survives an ordinary navigation.
*/
resetKeys?: readonly unknown[];
}
/**
* State of the ErrorBoundary component.
*/
interface ErrorBoundaryState {
error?: Error;
}
/**
* A reusable error boundary for catching rendering errors in any part of the React component tree.
*
* It's already included in the Alepha React framework when using page or layout components.
*/
declare class ErrorBoundary extends React.Component<PropsWithChildren<ErrorBoundaryProps>, ErrorBoundaryState> {
constructor(props: ErrorBoundaryProps);
/**
* Update state so the next render shows the fallback UI.
*/
static getDerivedStateFromError(error: Error): ErrorBoundaryState;
/**
* Lifecycle method called when an error is caught.
* You can log the error or perform side effects here.
*/
componentDidCatch(error: Error, info: ErrorInfo): void;
/**
* Recover once the caller signals the failing input is gone.
*/
componentDidUpdate(prevProps: PropsWithChildren<ErrorBoundaryProps>): void;
protected hasResetKeyChanged(previous: readonly unknown[] | undefined, next: readonly unknown[] | undefined): boolean;
render(): ReactNode;
}
//#endregion
//#region ../../src/react/core/contexts/AlephaContext.d.ts
/**
* React context to provide the Alepha instance throughout the component tree.
*/
declare const AlephaContext: import("react").Context<Alepha | undefined>;
//#endregion
//#region ../../src/react/core/contexts/AlephaProvider.d.ts
interface AlephaProviderProps {
children: ReactNode;
onError: (error: Error) => ReactNode;
onLoading: () => ReactNode;
}
/**
* AlephaProvider component to initialize and provide Alepha instance to the app.
*
* This isn't recommended for apps using `alepha/react/router`, as Router will handle this for you.
*/
declare const AlephaProvider: (props: AlephaProviderProps) => string | number | bigint | boolean | import("react").JSX.Element | Iterable<ReactNode> | Promise<string | number | bigint | boolean | Iterable<ReactNode> | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | import("react").ReactPortal | null | undefined> | null | undefined;
//#endregion
//#region ../../src/react/core/hooks/useAction.d.ts
/**
* Hook for handling async actions with automatic error handling and event emission.
*
* By default, prevents concurrent executions - if an action is running and you call it again,
* the second call will be ignored. Use `debounce` option to delay execution instead.
*
* Emits lifecycle events:
* - `react:action:begin` - When action starts
* - `react:action:success` - When action completes successfully
* - `react:action:error` - When action throws an error
* - `react:action:end` - Always emitted at the end
*
* @example Basic usage
* ```tsx
* const action = useAction({
* handler: async (data) => {
* await api.save(data);
* }
* }, []);
*
* <button onClick={() => action.run(data)} disabled={action.loading}>
* Save
* </button>
* ```
*
* @example With debounce (search input)
* ```tsx
* const search = useAction({
* handler: async (query: string) => {
* await api.search(query);
* },
* debounce: 300 // Wait 300ms after last call
* }, []);
*
* <input onChange={(e) => search.run(e.target.value)} />
* ```
*
* @example Run on component mount
* ```tsx
* const fetchData = useAction({
* handler: async () => {
* const data = await api.getData();
* return data;
* },
* runOnInit: true // Runs once when component mounts
* }, []);
* ```
*
* @example Run periodically (polling)
* ```tsx
* const pollStatus = useAction({
* handler: async () => {
* const status = await api.getStatus();
* return status;
* },
* runEvery: 5000 // Run every 5 seconds
* }, []);
*
* // Or with duration tuple
* const pollStatus = useAction({
* handler: async () => {
* const status = await api.getStatus();
* return status;
* },
* runEvery: [30, 'seconds'] // Run every 30 seconds
* }, []);
* ```
*
* @example With AbortController
* ```tsx
* const fetch = useAction({
* handler: async (url, { signal }) => {
* const response = await fetch(url, { signal });
* return response.json();
* }
* }, []);
* // Automatically cancelled on unmount or when new request starts
* ```
*
* @example With error handling
* ```tsx
* const deleteAction = useAction({
* handler: async (id: string) => {
* await api.delete(id);
* },
* onError: (error) => {
* if (error.code === 'NOT_FOUND') {
* // Custom error handling
* }
* }
* }, []);
*
* {deleteAction.error && <div>Error: {deleteAction.error.message}</div>}
* ```
*
* @example Global error handling
* ```tsx
* // In your root app setup
* alepha.events.on("react:action:error", ({ error }) => {
* toast.danger(error.message);
* Sentry.captureException(error);
* });
* ```
*/
declare function useAction<Args extends any[], Result = void>(options: UseActionOptions<Args, Result>, deps: DependencyList): UseActionReturn<Args, Result>;
/**
* Context object passed as the last argument to action handlers.
* Contains an AbortSignal that can be used to cancel the request.
*/
interface ActionContext {
/**
* AbortSignal that can be passed to fetch or other async operations.
* The signal will be aborted when:
* - The component unmounts
* - A new action is triggered (cancels previous)
* - The cancel() method is called
*
* @example
* ```tsx
* const action = useAction({
* handler: async (url, { signal }) => {
* const response = await fetch(url, { signal });
* return response.json();
* }
* }, []);
* ```
*/
signal: AbortSignal;
}
interface UseActionOptions<Args extends any[] = any[], Result = any> {
/**
* The async action handler function.
* Receives the action arguments plus an ActionContext as the last parameter.
*/
handler: (...args: [...Args, ActionContext]) => Async<Result>;
/**
* Custom error handler. If provided, prevents default error re-throw.
*/
onError?: (error: Error) => void | Promise<void>;
/**
* Custom success handler.
*/
onSuccess?: (result: Result) => void | Promise<void>;
/**
* Optional identifier for this action (useful for debugging/analytics)
*/
id?: string;
name?: string;
/**
* Debounce delay in milliseconds. If specified, the action will only execute
* after the specified delay has passed since the last call. Useful for search inputs
* or other high-frequency events.
*
* @example
* ```tsx
* // Execute search 300ms after user stops typing
* const search = useAction({ handler: search, debounce: 300 }, [])
* ```
*/
debounce?: number;
/**
* If true, the action will be executed once when the component mounts.
*
* @example
* ```tsx
* const fetchData = useAction({
* handler: async () => await api.getData(),
* runOnInit: true
* }, []);
* ```
*/
runOnInit?: boolean;
/**
* If specified, the action will be executed periodically at the given interval.
* The interval is specified as a DurationLike value (number in ms, Duration object, or [number, unit] tuple).
*
* @example
* ```tsx
* // Run every 5 seconds
* const poll = useAction({
* handler: async () => await api.poll(),
* runEvery: 5000
* }, []);
* ```
*
* @example
* ```tsx
* // Run every 1 minute
* const poll = useAction({
* handler: async () => await api.poll(),
* runEvery: [1, 'minute']
* }, []);
* ```
*/
runEvery?: DurationLike;
}
interface UseActionReturn<Args extends any[], Result> {
/**
* Execute the action with the provided arguments.
*
* @example
* ```tsx
* const action = useAction({ handler: async (data) => { ... } }, []);
* action.run(data);
* ```
*/
run: (...args: Args) => Promise<Result | undefined>;
/**
* Loading state - true when action is executing.
*/
loading: boolean;
/**
* Error state - contains error if action failed, undefined otherwise.
*/
error?: Error;
/**
* Cancel any pending debounced action or abort the current in-flight request.
*
* @example
* ```tsx
* const action = useAction({ ... }, []);
*
* <button onClick={action.cancel} disabled={!action.loading}>
* Cancel
* </button>
* ```
*/
cancel: () => void;
/**
* The result data from the last successful action execution.
*/
result?: Result;
}
//#endregion
//#region ../../src/react/core/hooks/useAlepha.d.ts
/**
* Main Alepha hook.
*
* It provides access to the Alepha instance within a React component.
*
* With Alepha, you can access the core functionalities of the framework:
*
* - alepha.state() for state management
* - alepha.inject() for dependency injection
* - alepha.events.emit() for event handling
* etc...
*/
declare const useAlepha: () => Alepha;
//#endregion
//#region ../../src/react/core/hooks/useClient.d.ts
/**
* Hook to get a virtual client for the specified scope.
*
* It's the React-hook version of `$client()`, from `AlephaServerLinks` module.
*/
declare const useClient: <T extends object>(scope?: ClientScope) => HttpVirtualClient<T>;
//#endregion
//#region ../../src/react/core/hooks/useComputed.d.ts
/**
* Subscribes to a `$computed` value.
*
* `getSnapshot` always reads the dependency atoms **live** off the store —
* never through a subscription-set dirty flag. A dirty flag is only ever
* flipped by the `state:mutate` listener registered in `subscribe`, which
* React wires up in a passive effect (after commit); `EventManager.emit` is
* async on top of that. A mutation dispatched between the initial render and
* that effect committing would be invisible to the flag, so `getSnapshot`
* (including React's own post-subscribe consistency re-check) would keep
* returning the stale pre-mutation value. See `useStore.ts` and
* `useSelector.ts` for the same rationale.
*
* Referential stability for `useSyncExternalStore` comes from comparing the
* *inputs* (the resolved values of the computed's transitive atom
* dependencies) by identity, not from caching on a mount-time ref: when none
* of the inputs changed since the last snapshot, the previous output
* reference is returned as-is; when any input changed (or `target` itself
* was swapped for a different `Computed`), the value is recomputed and a new
* reference is cached.
*
* A `Computed` passed here should be declared at module scope, like an
* atom — constructing one inline in a component body creates a new
* instance every render, which (because `subscribe`/`getSnapshot` are keyed
* on `target.key`, matching `useStore`/`useSelector`) is tolerated for
* resubscription purposes, but any closures captured by that instance's
* `get()` will be locked to whichever render first created the subscription
* until `target.key` itself changes.
*
* ```tsx
* const total = useComputed(cartTotal);
* ```
*/
declare function useComputed<R>(target: Computed<R>): R;
//#endregion
//#region ../../src/react/core/hooks/useEvents.d.ts
/**
* Allow subscribing to multiple Alepha events. See {@link Hooks} for available events.
*
* useEvents is fully typed to ensure correct event callback signatures.
*
* @example
* ```tsx
* useEvents(
* {
* "react:transition:begin": (ev) => {
* console.log("Transition began to:", ev.to);
* },
* "react:transition:error": {
* priority: "first",
* callback: (ev) => {
* console.error("Transition error:", ev.error);
* },
* },
* },
* [],
* );
* ```
*/
declare const useEvents: (opts: UseEvents, deps: DependencyList) => void;
type UseEvents = { [T in keyof Hooks]?: Hook<T> | ((payload: Hooks[T]) => Async<void>); };
//#endregion
//#region ../../src/react/core/hooks/useInject.d.ts
/**
* Hook to inject a service instance.
* It's a wrapper of `useAlepha().inject(service)` with a memoization.
*/
declare const useInject: <T extends object>(service: Service<T>) => T;
//#endregion
//#region ../../src/react/core/hooks/useQuery.d.ts
/**
* Hook for declarative data fetching with automatic execution and refetch.
*
* Thin wrapper over {@link useAction}: it pre-applies `runOnInit: true`,
* exposes the last result as `data`, and provides a stable `refetch()` to
* re-run the query on demand. For optimistic mutations and side-effects,
* use {@link useAction} directly — `useQuery` is for the read path.
*
* Caching, request deduplication, and AbortSignal cancellation come from
* `useAction` + `HttpClient`. There is no separate cache layer — pass
* `localCache` to your `HttpClient.fetch()`/`fetchAction()` call inside
* the query handler if you want per-call caching.
*
* @example Basic
* ```tsx
* const client = useInject(HttpClient);
* const { data, loading, error, refetch } = useQuery({
* handler: async ({ signal }) => {
* const res = await client.fetch("/api/users", { request: { signal } });
* return res.data;
* },
* }, []);
* ```
*
* @example Re-fetch when a dep changes
* ```tsx
* const { data } = useQuery({
* handler: async () => api.getUser(userId),
* }, [userId]);
* ```
*
* @example Polling
* ```tsx
* const { data } = useQuery({
* handler: async () => api.getStatus(),
* runEvery: [5, "seconds"],
* }, []);
* ```
*/
declare function useQuery<Result>(options: UseQueryOptions<Result>, deps: DependencyList): UseQueryReturn<Result>;
interface UseQueryOptions<Result> {
/**
* Async query handler. Receives an {@link ActionContext} with an
* AbortSignal that fires on unmount, dependency change, or `cancel()`.
*/
handler: (context: ActionContext) => Async<Result>;
/**
* Optional identifier (used in lifecycle events for debugging/analytics).
*/
id?: string;
/**
* If `false`, skip automatic execution on mount and dep change. Use
* `refetch()` to trigger manually. Defaults to `true`.
*/
enabled?: boolean;
/**
* Initial value for `data` before the first successful fetch.
*/
initialData?: Result;
/**
* Re-run periodically. See {@link useAction} `runEvery`.
*/
runEvery?: DurationLike;
/**
* Debounce delay in milliseconds. See {@link useAction} `debounce`.
*/
debounce?: number;
/**
* Called on success with the resolved value.
*/
onSuccess?: (result: Result) => void | Promise<void>;
/**
* Custom error handler. If provided, prevents default error re-throw.
*/
onError?: (error: Error) => void | Promise<void>;
}
interface UseQueryReturn<Result> {
/**
* The last successful result. `undefined` until the first fetch resolves
* (or the value of `initialData` if provided).
*/
data: Result | undefined;
/**
* Loading state — `true` while a fetch is in flight, and also from the
* first render until the initial auto-run settles (for enabled queries
* with no `initialData`), so a skeleton can render without a flash.
*/
loading: boolean;
/**
* Error from the last failed fetch, if any.
*/
error?: Error;
/**
* Re-run the query. The previous in-flight request, if any, is aborted.
*/
refetch: () => Promise<Result | undefined>;
/**
* Abort the in-flight request without scheduling another.
*/
cancel: () => void;
}
//#endregion
//#region ../../src/react/core/hooks/useSelector.d.ts
/**
* Subscribes to a slice of an atom's state.
*
* Unlike {@link useStore}, which re-renders on every mutation of the atom,
* `useSelector` re-renders only when the selected slice actually changes
* according to `equality` (default: `Object.is`). Pass {@link shallowEqual}
* when the selector builds a fresh object on each call.
*
* ```tsx
* const theme = useSelector(uiAtom, (s) => s.theme);
* ```
*/
declare function useSelector<T extends TAtomObject, R>(target: Atom<T>, select: (state: Static<T>) => R, equality?: (a: R, b: R) => boolean): R;
//#endregion
//#region ../../src/react/core/hooks/useStore.d.ts
/**
* Hook to access and mutate the Alepha state.
*
* Reads through `useSyncExternalStore`, so the store — not a `useState` copy of
* it — is the single source of truth. That is what keeps the value correct when
* a mutation lands between render and effect-commit, when the component swaps
* which `target` it watches, and when React renders concurrently (no tearing).
*/
declare function useStore<T extends TAtomObject>(target: Atom<T>, defaultValue?: Static<T>): UseStoreReturn<Static<T>>;
declare function useStore<Key extends keyof State>(target: Key, defaultValue?: State[Key]): UseStoreReturn<State[Key]>;
type UseStoreReturn<T> = [T, (value: T) => void];
//#endregion
//#region ../../src/react/core/utils/shallowEqual.d.ts
/**
* Shallow structural equality over own enumerable keys.
*
* Intended as the `equality` argument of `useSelector` when the selector
* builds a fresh object/array on every call (e.g. `(s) => ({ a: s.a })`).
*/
declare const shallowEqual: (a: unknown, b: unknown) => boolean;
//#endregion
//#region ../../src/react/core/index.d.ts
declare module "alepha" {
interface Hooks {
/**
* Fires when a user action is starting.
* Action can be a form submission, a route transition, or a custom action.
*/
"react:action:begin": {
type: string;
id?: string;
};
/**
* Fires when a user action has succeeded.
* Action can be a form submission, a route transition, or a custom action.
*/
"react:action:success": {
type: string;
id?: string;
};
/**
* Fires when a user action has failed.
* Action can be a form submission, a route transition, or a custom action.
*/
"react:action:error": {
type: string;
id?: string;
error: Error;
};
/**
* Fires when a user action has completed, regardless of success or failure.
* Action can be a form submission, a route transition, or a custom action.
*/
"react:action:end": {
type: string;
id?: string;
};
}
}
/**
* Full-stack React framework with server-side rendering.
*
* **Features:**
* - React page routes with type-safe params
* - Async action handler with loading/error/cancel states
* - Type-safe HTTP client access
* - Dependency injection in components
* - Global state management
* - Router navigation methods
* - Current route state access
* - Check if path is active
* - URL query parameters
* - Access route schema
* - Subscribe to Alepha events
* - Type-safe form handling with validation
* - Error handling wrapper component
* - Client-side only rendering component
* - Server-side rendering with hydration
* - Automatic code splitting
* - Event system for action tracking
*
* @module alepha.react
*/
declare const AlephaReact: import("alepha").Service<import("alepha").Module>;
//#endregion
export { ActionContext, AlephaContext, AlephaProvider, AlephaProviderProps, AlephaReact, ClientOnly, type ClientOnlyProps, ErrorBoundary, type ErrorBoundaryProps, UseActionOptions, UseActionReturn, UseQueryOptions, UseQueryReturn, UseStoreReturn, shallowEqual, useAction, useAlepha, useClient, useComputed, useEvents, useInject, useQuery, useSelector, useStore };
//# sourceMappingURL=index.d.ts.map