/** * Effect system for Composable Svelte. * * Effects are declarative, type-safe descriptions of side effects. * They are VALUES, not executions - the Store executes them. * * Key principle: Effects describe WHAT to do, not HOW or WHEN. */ import type { Effect as EffectType, EffectExecutor, Dispatch } from './types.js'; /** * Effect namespace containing all effect constructors. */ export declare const Effect: { /** * No side effects. * * @example * ```typescript * case 'reset': * return [initialState, Effect.none()]; * ``` */ none(): EffectType; /** * Execute async work and dispatch actions. * * Use this for API calls, timers, or any async operation that needs * to dispatch actions back to the store. * * @param execute - Function that performs async work and dispatches actions * * @example * ```typescript * Effect.run(async (dispatch) => { * const data = await api.fetch(); * dispatch({ type: 'dataLoaded', data }); * }) * ``` */ run(execute: EffectExecutor): EffectType; /** * Execute effect without waiting for completion. * No actions can be dispatched. * * Use this for fire-and-forget operations like analytics tracking * or logging where you don't care about the result. * * @param execute - Function that performs work without dispatching * * @example * ```typescript * Effect.fireAndForget(() => { * analytics.track('button_clicked'); * }) * ``` */ fireAndForget(execute: () => void | Promise): EffectType; /** * Execute multiple effects in parallel. * * All effects in the batch are started simultaneously. * Use this when you have multiple independent effects. * * Optimizations: * - Returns Effect.none() for empty batches * - Returns single effect directly if batch has only one effect * - Filters out Effect.none() from batch to reduce overhead * * @param effects - Effects to execute in parallel * * @example * ```typescript * Effect.batch( * Effect.run(async (d) => { ... }), * Effect.run(async (d) => { ... }), * Effect.fireAndForget(() => { ... }) * ) * ``` */ batch(...effects: EffectType[]): EffectType; /** * Execute effect that can be cancelled by ID. * Cancels any in-flight effect with the same ID. * * Use this for operations that should be cancelled when superseded, * like API requests that may become stale. * * @param id - Unique identifier for cancellation * @param execute - Function that performs async work * * @example * ```typescript * Effect.cancellable('fetch-user', async (dispatch) => { * const user = await api.fetchUser(); * dispatch({ type: 'userLoaded', user }); * }) * ``` */ cancellable(id: string, execute: EffectExecutor): EffectType; /** * Execute effect after debounce delay. * Resets timer if called again with same ID. * * Use this for search-as-you-type, autosave, or other operations * where you want to wait until the user stops performing an action. * * @param id - Unique identifier for debouncing * @param ms - Delay in milliseconds (must be non-negative) * @param execute - Function that performs async work * @throws {TypeError} If ms is negative * * @example * ```typescript * Effect.debounced('search', 300, async (dispatch) => { * const results = await api.search(query); * dispatch({ type: 'resultsLoaded', results }); * }) * ``` */ debounced(id: string, ms: number, execute: EffectExecutor): EffectType; /** * Execute effect at most once per time period. * * Use this for scroll handlers, resize handlers, or other high-frequency * events where you want to limit execution rate. * * @param id - Unique identifier for throttling * @param ms - Minimum interval between executions in milliseconds (must be non-negative) * @param execute - Function that performs async work * @throws {TypeError} If ms is negative * * @example * ```typescript * Effect.throttled('scroll', 100, async (dispatch) => { * dispatch({ type: 'scrolled', y: window.scrollY }); * }) * ``` */ throttled(id: string, ms: number, execute: EffectExecutor): EffectType; /** * Execute effect after a delay. * Useful for animations and timed transitions. * * @param ms - Delay in milliseconds (must be non-negative) * @param create - Function that creates the dispatch call after delay * @throws {TypeError} If ms is negative * * @example * ```typescript * Effect.afterDelay(300, (dispatch) => { * dispatch({ type: 'animationCompleted' }); * }) * ``` */ afterDelay(ms: number, create: EffectExecutor): EffectType; /** * Create a long-running subscription with automatic cleanup. * * Use this for subscriptions that need to be maintained over time and properly * cleaned up when cancelled or when the store is destroyed. This is essential * for WebSocket connections, event listeners, and other persistent resources. * * The subscription is identified by ID and can be cancelled with Effect.cancel(). * When cancelled, the cleanup function returned by setup() is called automatically. * * @param id - Unique identifier for subscription (used for cancellation) * @param setup - Function that sets up the subscription and returns cleanup * * @example * ```typescript * // WebSocket subscription * Effect.subscription('websocket-connection', (dispatch) => { * const socket = new WebSocket('wss://example.com'); * * socket.onmessage = (event) => { * dispatch({ type: 'messageReceived', data: event.data }); * }; * * socket.onerror = (error) => { * dispatch({ type: 'connectionError', error }); * }; * * // Return cleanup function * return () => { * socket.close(); * }; * }) * ``` * * @example * ```typescript * // Event listener subscription * Effect.subscription('window-resize', (dispatch) => { * const handler = () => { * dispatch({ type: 'windowResized', width: window.innerWidth }); * }; * * window.addEventListener('resize', handler); * * return () => { * window.removeEventListener('resize', handler); * }; * }) * ``` * * @example * ```typescript * // Cancelling a subscription * case 'disconnect': * return [ * state, * Effect.cancel('websocket-connection') * ]; * ``` */ subscription(id: string, setup: (dispatch: Dispatch) => (() => void | Promise)): EffectType; /** * Cancel all in-flight effects with the given ID. * * This cancels effects created with: * - Effect.cancellable() * - Effect.subscription() * - Effect.debounced() * - Effect.throttled() * * For subscriptions, this triggers the cleanup function returned by setup(). * * @param id - The ID of the effect(s) to cancel * * @example * ```typescript * case 'disconnect': * return [ * state, * Effect.batch( * Effect.cancel('websocket-connection'), * Effect.cancel('websocket-messages') * ) * ]; * ``` */ cancel(id: string): EffectType; /** * Create an effect that coordinates with animation lifecycle. * Automatically dispatches completion events after the specified duration. * * This is a convenience wrapper around `Effect.afterDelay()` specifically * for animation use cases. It provides a cleaner API for common patterns * where you need to dispatch an action after an animation completes. * * **Use this when:** * - You have a fixed-duration animation (e.g., CSS transition, Svelte transition) * - You need to transition presentation state after animation completes * - You want a concise, declarative way to express "do X after Y milliseconds" * * **Don't use this when:** * - Animation duration is variable (use callback from animation library) * - You need to cancel the animation mid-flight (use cancellable effects) * * @param config - Animation configuration * @param config.duration - Animation duration in milliseconds (must be non-negative) * @param config.onComplete - Action to dispatch when animation completes * @throws {TypeError} If duration is negative * * @example * ```typescript * // Simple presentation completion * case 'addButtonTapped': { * return [ * { * ...state, * presentation: { status: 'presenting', content: destination } * }, * Effect.animated({ * duration: 300, * onComplete: { * type: 'presentation', * event: { type: 'presentationCompleted' } * } * }) * ]; * } * ``` * * @example * ```typescript * // With timeout fallback * case 'addButtonTapped': { * return [ * { * ...state, * presentation: { status: 'presenting', content: destination } * }, * Effect.batch( * Effect.animated({ * duration: 300, * onComplete: { * type: 'presentation', * event: { type: 'presentationCompleted' } * } * }), * Effect.animated({ * duration: 600, // 2x expected duration * onComplete: { * type: 'presentation', * event: { type: 'presentationTimeout' } * } * }) * ) * ]; * } * ``` */ animated(config: { duration: number; onComplete: A; }): EffectType; /** * Create a presentation effect with automatic lifecycle management. * * This helper generates both `present` and `dismiss` effects for a complete * animation lifecycle. It's designed for the common pattern where you have: * - A presentation animation (animate IN) * - A dismissal animation (animate OUT) * - Both dispatch events to transition presentation state * * **Benefits:** * - DRY: Define animation durations once, reuse for present/dismiss * - Type-safe: Action creator ensures correct event structure * - Consistent: Both animations use same pattern * * **Use this when:** * - You have symmetric present/dismiss animations (e.g., modal, sheet, drawer) * - Both animations dispatch to the same action type * - You want to avoid repeating duration/event configuration * * **Don't use this when:** * - Animations are asymmetric (different action types, complex coordination) * - You need fine-grained control over timing * - You're not using the presentation lifecycle pattern * * @param config - Transition configuration * @param config.presentDuration - Duration for presentation animation (default: 300ms) * @param config.dismissDuration - Duration for dismissal animation (default: 200ms) * @param config.createPresentationEvent - Function to create action from event * @throws {TypeError} If duration is negative * * @example * ```typescript * // Define transition once * const transition = Effect.transition({ * presentDuration: 300, * dismissDuration: 200, * createPresentationEvent: (event) => ({ * type: 'presentation', * event * }) * }); * * // Use in reducer * case 'addButtonTapped': * return [ * { ...state, presentation: { status: 'presenting', content: destination } }, * transition.present * ]; * * case 'closeButtonTapped': * return [ * { ...state, presentation: { status: 'dismissing', content: state.presentation.content } }, * transition.dismiss * ]; * ``` * * @example * ```typescript * // Nested presentation (child within parent) * const parentTransition = Effect.transition({ * createPresentationEvent: (event) => ({ * type: 'parentPresentation', * event * }) * }); * * const childTransition = Effect.transition({ * createPresentationEvent: (event) => ({ * type: 'childPresentation', * event * }) * }); * ``` */ transition(config: { presentDuration?: number; dismissDuration?: number; createPresentationEvent: (event: { type: "presentationCompleted" | "dismissalCompleted"; }) => A; }): { present: EffectType; dismiss: EffectType; }; /** * Map effect actions to parent actions (for composition). * * This is the key function that enables reducer composition. * It transforms all actions dispatched by a child effect into * parent actions. * * @param effect - The child effect * @param f - Function to transform child action to parent action * * @example * ```typescript * const childEffect: Effect = ...; * const parentEffect: Effect = Effect.map( * childEffect, * (childAction) => ({ type: 'child', action: childAction }) * ); * ``` */ map(effect: EffectType, f: (a: A) => B): EffectType; }; //# sourceMappingURL=effect.d.ts.map