UNPKG

alepha

Version:

Easy-to-use modern TypeScript framework for building many kind of applications.

647 lines 21.6 kB
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