/**
* @file Higher-Level Abstractions for @doeixd/machine
* @description
* This module provides a collection of powerful, pre-built patterns and primitives
* on top of the core `@doeixd/machine` library. These utilities are designed to
* solve common, recurring problems in state management, such as data fetching,
* hierarchical state, and toggling boolean context properties.
*
* Think of this as the "standard library" of common machine patterns.
*/
import { MachineBase } from './base';
import { Machine, Transitions, Context } from './index';
/**
* A type utility to infer the child machine type from a parent.
*/
type ChildMachine
= P extends MachineBase<{
child: infer C;
}> ? C : never;
type BooleanKey = {
[K in keyof T]-?: T[K] extends boolean ? K : never;
}[keyof T];
/**
* Creates a transition method that delegates a call to a child machine.
*
* This is a higher-order function that reduces boilerplate when implementing
* hierarchical state machines. It generates a method for the parent machine that:
* 1. Checks if the specified action exists on the current child state.
* 2. If it exists, calls the action on the child.
* 3. Reconstructs the parent machine with the new child state returned by the action.
* 4. If the action doesn't exist on the child, it returns the parent machine unchanged.
*
* @template P - The parent machine type, which must have a `child` property in its context.
* @template K - The name of the action on the child machine to delegate to.
* @param actionName - The string name of the child's transition method.
* @param ...args - Any arguments to pass to the child's transition method.
* @returns The parent machine instance, with its `child` state potentially updated.
*
* @example
* ```typescript
* class Parent extends MachineBase<{ child: ChildMachine }> {
* // Instead of writing a manual delegation method...
* // save = () => {
* // if ('save' in this.context.child) {
* // const newChild = this.context.child.save();
* // return setContext(this, { child: newChild });
* // }
* // return this;
* // }
*
* // ...you can just use the primitive.
* save = delegateToChild('save');
* edit = delegateToChild('edit');
* }
* ```
*/
export declare function delegateToChild;
}>, K extends keyof ChildMachine
& string>(actionName: K): (...args: ChildMachine
[K] extends (...a: infer A) => any ? A : never) => P;
/**
* Creates a transition method that toggles a boolean property within the machine's context.
*
* This is a simple utility to reduce boilerplate for managing boolean flags.
*
* @template M - The machine type.
* @template K - The key of the boolean property in the machine's context.
* @param prop - The string name of the context property to toggle.
* @returns A new machine instance with the toggled property.
*
* @example
* ```typescript
* class SettingsMachine extends MachineBase<{ notifications: boolean; darkMode: boolean }> {
* toggleNotifications = toggle('notifications');
* toggleDarkMode = toggle('darkMode');
* }
* ```
*/
export declare function toggle, K extends BooleanKey>>(prop: K): (this: M) => M;
/**
* A fully-featured, pre-built state machine for data fetching.
* It handles loading, success, error states, cancellation, and retry logic out of the box.
*
* This machine is highly customizable through its configuration options.
*/
/**
* Abort-aware request function consumed by {@link createFetchMachine}.
*
* @typeParam T - Successful data type.
* @typeParam P - Request parameter type.
*/
export type Fetcher = (params: P, options: {
signal: AbortSignal;
}) => Promise;
/**
* Success callback invoked once when a fetch attempt resolves.
* @typeParam T - Successful data type.
*/
export type OnSuccess = (data: T) => void;
/**
* Final-failure callback invoked after the retry budget is exhausted.
* @typeParam E - Normalized error type.
*/
export type OnError = (error: E) => void;
/**
* Configuration for {@link createFetchMachine}.
*
* @typeParam T - Successful data type.
* @typeParam E - Normalized error type exposed by error typestates.
* @typeParam P - Parameters accepted by `fetch`, `retry`, and `refetch`.
*/
export interface FetchMachineConfig {
/** Performs one request and should honor the supplied abort signal. */
fetcher: Fetcher;
/** Used when a transition does not supply explicit parameters. */
initialParams?: P;
/** Retries available after the first failed attempt. Defaults to `3`. */
maxRetries?: number;
/** Observes successful data before the success snapshot is returned. */
onSuccess?: OnSuccess;
/** Observes the normalized error after no retries remain. */
onError?: OnError;
/** Converts unknown thrown values into the declared error type. */
mapError?: (error: unknown) => E;
}
type IdleContext = {
status: 'idle';
};
type LoadingContext = {
status: 'loading';
abortController: AbortController;
attempts: number;
};
type RetryingContext = {
status: 'retrying';
error: E;
attempts: number;
};
type SuccessContext = {
status: 'success';
data: T;
};
type ErrorContext = {
status: 'error';
error: E;
};
type CanceledContext = {
status: 'canceled';
};
declare class IdleMachine extends MachineBase {
private config;
constructor(config: FetchMachineConfig);
fetch: (params?: P) => LoadingMachine;
}
type LoadingResult = SuccessMachine | RetryingMachine | ErrorMachine | CanceledMachine;
declare class LoadingMachine extends MachineBase {
private config;
private params;
private readonly completion;
constructor(config: FetchMachineConfig, params: P, attempts: number);
/** Resolves to the typestate produced by the configured fetch operation. */
done: () => Promise>;
private execute;
succeed: (data: T) => SuccessMachine;
fail: (error: E) => RetryingMachine | ErrorMachine;
cancel: () => CanceledMachine;
}
declare class RetryingMachine extends MachineBase> {
private config;
private params;
constructor(config: FetchMachineConfig, params: P, error: E, attempts: number);
retry: (params?: P) => LoadingMachine;
}
declare class SuccessMachine extends MachineBase> {
private config;
constructor(config: FetchMachineConfig, context: SuccessContext);
refetch: (params?: P) => LoadingMachine;
}
declare class ErrorMachine extends MachineBase> {
private config;
constructor(config: FetchMachineConfig, context: ErrorContext);
retry: (params?: P) => LoadingMachine;
}
declare class CanceledMachine extends MachineBase {
private config;
constructor(config: FetchMachineConfig);
refetch: (params?: P) => LoadingMachine;
}
/**
* Complete typestate union returned by {@link createFetchMachine} transitions.
*
* Narrow `context.status` before calling state-specific operations such as
* `done`, `retry`, `cancel`, or `refetch`.
*
* @typeParam T - Successful data type.
* @typeParam E - Normalized error type.
* @typeParam P - Request parameter type.
*/
export type FetchMachine = IdleMachine | LoadingMachine | RetryingMachine | SuccessMachine | ErrorMachine | CanceledMachine;
/**
* Creates a pre-built, highly configurable async data-fetching machine.
*
* This factory function returns a state machine that handles the entire lifecycle
* of a data request, including loading, success, error, cancellation, and retries.
*
* @template T - The type of the data to be fetched.
* @template E - The type of the error.
* @template P - The type of parameters accepted by fetch operations.
* @param config - Configuration object.
* @param config.fetcher - An async function that takes params and returns the data.
* @param [config.maxRetries=3] - The number of times to retry on failure.
* @param [config.onSuccess] - Optional callback fired with the data on success.
* @param [config.onError] - Optional callback fired with the error on final failure.
* @param [config.mapError] - Converts an unknown thrown value to `E`.
* @returns An `IdleMachine` instance, ready to start fetching.
* @throws {TypeError} If `config.fetcher` is not a function.
* @throws {RangeError} If `maxRetries` is negative or not an integer.
*
* @example
* ```typescript
* // 1. Define your data fetching logic
* async function fetchUser(id: number): Promise<{ id: number; name: string }> {
* const res = await fetch(`/api/users/${id}`);
* if (!res.ok) throw new Error('User not found');
* return res.json();
* }
*
* // 2. Create the machine
* const userMachine = createFetchMachine({
* fetcher: fetchUser,
* onSuccess: (user) => console.log(`Fetched: ${user.name}`),
* });
*
* // 3. Use it (e.g., in a React hook)
* if (userMachine.context.status === 'idle') {
* const loading = userMachine.fetch(123);
* const result = await loading.done();
* }
* ```
*
* @note This is a simplified example. For a real-world implementation, you would
* typically use this machine with a runner (like `runMachine` or `useMachine`) to
* manage the async transitions and state updates automatically.
*/
export declare function createFetchMachine(config: FetchMachineConfig): FetchMachine;
/**
* The core type for a Parallel Machine.
* It combines two machines, M1 and M2, into a single, unified type.
* @template M1 - The first machine in the parallel composition.
* @template M2 - The second machine in the parallel composition.
*/
export type ParallelMachine, M2 extends Machine> = Machine & Context> & {
[K in keyof Transitions]: Transitions[K] extends (...args: infer A) => infer R ? R extends Machine ? (...args: A) => ParallelMachine : never : never;
} & {
[K in keyof Transitions]: Transitions[K] extends (...args: infer A) => infer R ? R extends Machine ? (...args: A) => ParallelMachine : never : never;
};
/**
* Creates a parallel machine by composing two independent machines.
*
* This function takes two machines and merges them into a single machine entity.
* Transitions from either machine can be called, and they will only affect
* their respective part of the combined state.
*
* Transition names must be unique across the two machines. A collision throws
* instead of silently choosing one implementation.
*
* @param m1 The first machine instance.
* @param m2 The second machine instance.
* @returns A new ParallelMachine instance.
* @throws {Error} If the inputs share a context key or transition name.
* @typeParam M1 - First machine type.
* @typeParam M2 - Second machine type.
*
* @example
* ```ts
* const combined = createParallelMachine(counter, panel);
* const updated = combined.increment().toggle();
* ```
*/
export declare function createParallelMachine, M2 extends Machine>(m1: M1, m2: M2): ParallelMachine;
/**
* Rewrites every transition return type while retaining its name and parameters.
*
* @typeParam M - Machine whose transitions are inspected.
* @typeParam T - Replacement return type for every transition.
* @example
* ```ts
* type Chained = RemapTransitions;
* ```
*/
export type RemapTransitions, T> = {
[K in keyof Transitions]: Transitions[K] extends (...args: infer A) => any ? (...args: A) => T : never;
};
export {};
//# sourceMappingURL=higher-order.d.ts.map