import { SafeFnWithUtils } from '../async/safe'; import { RetryStrategyName } from '../async/types'; /** * Retry delay: milliseconds, strategy name, or custom function. */ export type EffectRetryDelay = number | RetryStrategyName | ((attempt: number) => number); /** * Retry configuration for effects. * Unified with async module retry options. */ export interface EffectRetryConfig { /** Number of retry attempts */ retries: number; /** Delay between retries: ms, strategy name, or custom function (default: "backoff") */ delay?: EffectRetryDelay; } /** * Context passed to custom error handlers. */ export interface EffectErrorContext { /** The error that was thrown */ error: unknown; /** Call to retry the effect immediately */ retry: () => void; /** Number of times this error has occurred consecutively */ retryCount: number; } /** * Error handling strategy for effects. */ export type EffectErrorStrategy = "failFast" | "keepAlive" | EffectRetryConfig | ((context: EffectErrorContext) => void); /** * Options for effect(). */ export interface EffectOptions { /** Error handling strategy */ onError?: EffectErrorStrategy; } /** * Options passed from store to effect via scheduleEffect hook. */ export interface RunEffectOptions { /** Store-level error callback - always called when effect errors */ onError?: (error: unknown) => void; } /** * Context passed to effect functions. * Provides utilities for safe cleanup and async handling. */ export interface EffectContext { /** * The run number (1-indexed). Increments each time the effect runs. * Can be used as a token to detect stale async operations. * * @example * effect((ctx) => { * const runToken = ctx.nth; * fetchData().then(data => { * if (ctx.nth === runToken) { * state.data = data; // Only if still same run * } * }); * }); */ readonly nth: number; /** * AbortSignal that is aborted when effect is cleaned up. * Created lazily on first access. * * @example * effect((ctx) => { * fetch('/api/data', { signal: ctx.signal }); * }); */ readonly signal: AbortSignal; /** * Register a cleanup function. * Cleanup runs when effect re-runs or is disposed. * Can be called multiple times - all cleanups run in LIFO order. * * @returns Unregister function to remove this cleanup * * @example * effect((ctx) => { * const sub = subscribe(); * ctx.onCleanup(() => sub.unsubscribe()); // Registered before risky code * doSomethingRisky(); // Even if this throws, cleanup runs * }); */ onCleanup(listener: VoidFunction): VoidFunction; /** * Safely execute operations that should be cancelled if effect becomes stale. * * Overloads: * 1. `safe(promise)` - Wrap promise, never resolve/reject if stale * 2. `safe(fn, ...args)` - Call function, wrap result if promise * 3. `safe(Abortable, ...args)` - Call with signal, wrap result if promise * * Utilities: * - `safe.all([...])` - Like Promise.all, cancellation-aware * - `safe.race([...])` - Like Promise.race, cancellation-aware * - `safe.any([...])` - Like Promise.any, cancellation-aware * - `safe.settled([...])` - Like Promise.allSettled, cancellation-aware * - `safe.callback(fn)` - Wrap callback to only execute if not stale * * @example * ```ts * effect((ctx) => { * // Wrap a promise * ctx.safe(fetchData()).then(data => { * state.data = data; * }); * * // Concurrent operations * ctx.safe.all([fetchA(), fetchB()]).then(([a, b]) => { * state.data = { a, b }; * }); * * // Wrap event callback * const onClick = ctx.safe.callback(() => state.clicked = true); * }); * ``` */ safe: SafeFnWithUtils; /** * Manually trigger a re-run of the effect. * Useful for async.derive pattern where thrown promises should trigger re-computation. * * Note: If called while the effect is currently running, the refresh will be * scheduled for after the current run completes. * * @example * effect((ctx) => { * try { * const data = async.wait(state.asyncData); // throws if pending * state.computed = transform(data); * } catch (ex) { * if (isPromise(ex)) { * ctx.safe(ex).then(ctx.refresh, ctx.refresh); * } * } * }); */ refresh(): void; } /** * Effect function type. * Receives EffectContext for cleanup registration, safe async, and abort signal. */ export type EffectFn = (ctx: EffectContext) => void; /** * Create a reactive effect. * * The effect runs immediately and re-runs when any tracked dependency changes. * Dependencies are automatically tracked when state properties are read. * * Effects can span multiple stores - use `resolver` from ReadEvent to subscribe. * * ## Suspense-like Behavior * * Effects automatically catch thrown promises (e.g., from `async.wait()`) and * re-run when they resolve. Uses `ctx.nth` for staleness detection - if dependencies * change before the promise resolves, the refresh is skipped. * * @param fn - Effect function that receives EffectContext * @param options - Effect options (error handling, etc.) * @returns Dispose function to stop the effect * * @example * // Basic effect with cleanup * effect((ctx) => { * const sub = subscribe(); * ctx.onCleanup(() => sub.unsubscribe()); * }); * * @example * // Suspense-like: auto-catches thrown promises * effect(() => { * const s = state.otherProp; * const user = async.wait(state.userAsync); // throws if pending * // Effect auto-re-runs when promise resolves * // If otherProp changes first, promise refresh is skipped * state.computed = transform(user); * }); * * @example * // Safe async - never resolves if effect re-runs * effect((ctx) => { * ctx.safe(fetchData()).then(data => { * state.data = data; * }); * }); * * @example * // Abort signal for fetch * effect((ctx) => { * fetch('/api/data', { signal: ctx.signal }); * }); * * @example * // With retry on error * effect((ctx) => { * fetchData(); * }, { onError: { retries: 3, delay: 1000 } }); * * @example * // Custom error handler * effect((ctx) => { * doSomething(); * }, { onError: ({ error, retry, retryCount }) => { * if (retryCount < 3) retry(); * else console.error(error); * }}); */ export declare function effect(fn: EffectFn, options?: EffectOptions): VoidFunction; //# sourceMappingURL=effect.d.ts.map