import { DurationObj } from "./time.mjs"; import * as evtmitter0 from "evtmitter"; import { Result, ResultValidErrors } from "t-result"; //#region src/asyncQueue.d.ts /** Configuration for rate limiting task execution */ type RateLimit = { /** Maximum number of tasks to execute within the interval */ maxTasks: number; /** Time interval in milliseconds or as a duration object */ interval: DurationObj | number; }; /** Configuration options for AsyncQueue initialization */ type AsyncQueueOptions = { /** Maximum number of tasks to run concurrently (default: 1) */ concurrency?: number; /** AbortSignal to cancel the entire queue */ signal?: AbortSignal; /** Default timeout for all tasks in milliseconds */ timeout?: number; /** Stop processing new tasks when any task fails (default: false) */ stopOnError?: boolean; /** Reject all pending tasks when stopping on error (default: false) */ rejectPendingOnError?: boolean; /** Start processing tasks immediately when added (default: true) */ autoStart?: boolean; /** Rate limit configuration to limit tasks per time interval */ rateLimit?: RateLimit; }; /** Options for adding individual tasks to the queue */ type AddOptions = { /** AbortSignal to cancel this specific task */ signal?: AbortSignal; /** Timeout for this specific task in milliseconds */ timeout?: number; /** Metadata to associate with this task */ meta?: I; /** Callback invoked when task completes successfully */ onComplete?: (value: T) => void; /** Callback invoked when task fails */ onError?: (error: E | Error) => void; }; /** Runtime context passed to task functions */ type RunCtx = { /** Combined AbortSignal from task, queue, and timeout signals */ signal?: AbortSignal; /** Metadata associated with this task */ meta?: I; }; /** * A powerful async task queue with advanced error handling and flow control * * @example * Basic Usage * ```typescript * const queue = createAsyncQueue({ concurrency: 2 }); * * const processedItems: string[] = []; * * queue.resultifyAdd(async () => { * await delay(100); * return 'task completed'; * }).then(result => { * if (result.ok) processedItems.push(result.value); * }); * * await queue.onIdle(); * console.log('Processed:', processedItems); * ``` * * @example * Error Recovery * ```typescript * const queue = createAsyncQueue({ * stopOnError: true, * rejectPendingOnError: false * }); * * // Add batch of tasks * const items = ['item1', 'item2', 'bad-item', 'item3']; * items.forEach(item => { * queue.resultifyAdd(async () => { * if (item === 'bad-item') throw new Error('Processing failed'); * return item.toUpperCase(); * }); * }); * * await queue.onIdle(); * * if (queue.isStopped) { * console.log(`Stopped at ${queue.failed} failures, ${queue.size} remaining`); * // Reset and continue with remaining tasks * queue.reset(); * await queue.onIdle(); * } * ``` * * @example * Lazy Start * ```typescript * const queue = createAsyncQueue({ autoStart: false }); * * // Prepare all tasks without starting * queue.resultifyAdd(() => processTask1()); * queue.resultifyAdd(() => processTask2()); * queue.resultifyAdd(() => processTask3()); * * // Start processing when ready * queue.start(); * await queue.onIdle(); * ``` * * @template T - The type of value returned by successful tasks * @template E - The type of errors that tasks can produce (defaults to Error) * @template I - The type of metadata associated with tasks (defaults to * unknown) */ declare class AsyncQueue { #private; /** * Event emitter for tracking task lifecycle * * @example * Listening to Events * ```typescript * const queue = createAsyncQueue(); * * queue.events.on('start', (event) => { * console.log('Task started:', event.payload.meta); * }); * * queue.events.on('complete', (event) => { * console.log('Task completed:', event.payload.value); * }); * * queue.events.on('error', (event) => { * console.error('Task failed:', event.payload.error); * }); * ``` */ events: evtmitter0.Emitter<{ /** Emitted when a task starts executing */ start: { meta: I; }; /** Emitted when a task completes successfully */ complete: { meta: I; value: T; }; /** Emitted when a task fails */ error: { meta: I; error: E | Error; }; }>; /** Array of all task failures with metadata for debugging and analysis */ failures: Array<{ meta: I; error: E | Error; }>; /** Array of all task completions with metadata for debugging and analysis */ completions: Array<{ meta: I; value: T; }>; constructor({ concurrency, signal, timeout: taskTimeout, stopOnError, rejectPendingOnError, autoStart, rateLimit }?: AsyncQueueOptions); /** * Add a task that returns a Result to the queue * * Use this method when your task function already returns a Result type. For * functions that throw errors or return plain values, use `resultifyAdd` * instead. * * @example * ```typescript * const queue = createAsyncQueue(); * * const result = await queue.add(async () => { * try { * const data = await fetchData(); * return Result.ok(data); * } catch (error) { * return Result.err(error); * } * }); * * if (result.ok) { * console.log('Success:', result.value); * } else { * console.log('Error:', result.error); * } * ```; * * @param fn - Task function that returns a Result * @param options - Optional configuration for this task * @returns Promise that resolves with the task result */ add(fn: (ctx: RunCtx) => Promise> | Result, options?: AddOptions): Promise>; /** * Add a task that returns a plain value or throws errors to the queue * * This is the most commonly used method. It automatically wraps your function * to handle errors and convert them to Result types. * * @example * Basic Usage * ```typescript * const queue = createAsyncQueue(); * * queue.resultifyAdd(async () => { * const response = await fetch('/api/data'); * return response.json(); * }).then(result => { * if (result.ok) { * console.log('Data:', result.value); * } else { * console.error('Failed:', result.error); * } * }); * ``` * * @example * With Callbacks * ```typescript * queue.resultifyAdd( * async () => processData(), * { * onComplete: (data) => console.log('Processed:', data), * onError: (error) => console.error('Failed:', error), * timeout: 5000 * } * ); * ``` * * @param fn - Task function that returns a value or throws * @param options - Optional configuration for this task * @returns Promise that resolves with the task result wrapped in Result */ resultifyAdd(fn: (ctx: RunCtx) => Promise | T, options?: AddOptions): Promise>; /** * Wait for the queue to become idle (no pending tasks, no queued tasks, and * no rate-limit timers) * * This method resolves when: * * - All tasks have completed (success or failure) * - The queue is stopped due to error (stopOnError), even with remaining tasks * - There are no queued tasks, no running tasks, and no pending rate-limit * timers * * @example * ```typescript * const queue = createAsyncQueue(); * * // Add multiple tasks * for (let i = 0; i < 10; i++) { * queue.resultifyAdd(async () => `task ${i}`); * } * * // Wait for all tasks to complete * await queue.onIdle(); * * console.log(`Completed: ${queue.completed}, Failed: ${queue.failed}`); * ``` * * @returns Promise that resolves when the queue is idle */ onIdle(): Promise; /** * Wait until the queued task count is below a limit * * Resolves immediately if `size < limit` at the moment of calling. This only * considers queued (not yet started) tasks; running tasks are tracked by * `pending`. * * @param limit Threshold that `size` must be below to resolve */ onSizeLessThan(limit: number): Promise; /** * Clear all queued tasks (does not affect currently running tasks) * * This removes all tasks waiting in the queue but allows currently executing * tasks to complete normally. * * @example * ```typescript * const queue = createAsyncQueue({ concurrency: 1 }); * * // Add multiple tasks * queue.resultifyAdd(async () => longRunningTask()); // Will start immediately * queue.resultifyAdd(async () => task2()); // Queued * queue.resultifyAdd(async () => task3()); // Queued * * // Clear remaining queued tasks * queue.clear(); * * // Only the first task will complete * await queue.onIdle(); * ```; */ clear(): void; /** Number of tasks that have completed successfully */ get completed(): number; /** Number of tasks that have failed */ get failed(): number; /** Number of tasks currently being processed */ get pending(): number; /** Number of tasks waiting in the queue to be processed */ get size(): number; /** * Manually start processing tasks (only needed if autoStart: false) * * @example * ```typescript * const queue = createAsyncQueue({ autoStart: false }); * * // Add tasks without starting processing * queue.resultifyAdd(async () => 'task1'); * queue.resultifyAdd(async () => 'task2'); * * // Start processing when ready * queue.start(); * await queue.onIdle(); * ```; */ start(): void; /** * Pause processing new tasks (currently running tasks continue) * * @example * ```typescript * const queue = createAsyncQueue(); * * // Start some tasks * queue.resultifyAdd(async () => longRunningTask1()); * queue.resultifyAdd(async () => longRunningTask2()); * * // Pause before more tasks are picked up * queue.pause(); * * // Later, resume processing * queue.resume(); * ```; */ pause(): void; /** Resume processing tasks after pause */ resume(): void; /** * Reset the queue after being stopped, allowing new tasks to be processed * * This clears the stopped state and error reason, and resumes processing any * remaining queued tasks if autoStart was enabled. * * @example * ```typescript * const queue = createAsyncQueue({ stopOnError: true }); * * // Add tasks that will cause the queue to stop * queue.resultifyAdd(async () => { throw new Error('fail'); }); * queue.resultifyAdd(async () => 'remaining task'); * * await queue.onIdle(); * * if (queue.isStopped) { * console.log(`Queue stopped, ${queue.size} tasks remaining`); * * // Reset and process remaining tasks * queue.reset(); * await queue.onIdle(); * } * ``` */ reset(): void; /** Whether the queue is stopped due to an error */ get isStopped(): boolean; /** Whether the queue is currently paused */ get isPaused(): boolean; /** Whether the queue has been started (relevant for autoStart: false) */ get isStarted(): boolean; /** The error that caused the queue to stop (if any) */ get stoppedReason(): Error | undefined; } /** AddOptions variant that requires metadata to be provided */ type AddOptionsWithId = Omit, 'meta'> & { meta: I; }; /** * AsyncQueue variant that requires metadata for all tasks * * This class enforces that every task must include metadata, which is useful * when you need to track or identify tasks consistently. * * @example * ```typescript * interface TaskMeta { * id: string; * priority: number; * } * * const queue = createAsyncQueueWithMeta({ concurrency: 2 }); * * queue.resultifyAdd( * async () => processImportantTask(), * { meta: { id: 'task-1', priority: 1 } } * ); * * // Listen to events with metadata * queue.events.on('complete', (event) => { * console.log(`Task ${event.payload.meta.id} completed`); * }); * ``` * * @template T - The type of value returned by successful tasks * @template I - The type of metadata (required for all tasks) * @template E - The type of errors that tasks can produce */ declare class AsyncQueueWithMeta extends AsyncQueue { constructor(options?: AsyncQueueOptions); add(fn: (ctx: RunCtx) => Promise> | Result, options: AddOptionsWithId): Promise>; resultifyAdd(fn: (ctx: RunCtx) => Promise | T, options: AddOptionsWithId): Promise>; } /** * Create a new AsyncQueue instance * * @example * Basic Queue * ```typescript * const queue = createAsyncQueue({ concurrency: 3 }); * ``` * * @example * Error Handling Queue * ```typescript * const queue = createAsyncQueue({ * concurrency: 2, * stopOnError: true, * rejectPendingOnError: true * }); * ``` * * @example * Lazy Start Queue * ```typescript * const queue = createAsyncQueue({ * autoStart: false, * concurrency: 1 * }); * ``` * * @template T - The type of value returned by successful tasks * @template E - The type of errors that tasks can produce (defaults to Error) * @param options - Configuration options for the queue * @returns A new AsyncQueue instance */ declare function createAsyncQueue(options?: AsyncQueueOptions): AsyncQueue; /** * Create a new AsyncQueueWithMeta instance that requires metadata for all tasks * * @example * ```typescript * interface TaskInfo { * taskId: string; * userId: string; * } * * const queue = createAsyncQueueWithMeta({ * concurrency: 5 * }); * * queue.resultifyAdd( * async (ctx) => { * console.log(`Processing task ${ctx.meta.taskId} for user ${ctx.meta.userId}`); * return await processUserTask(ctx.meta.userId); * }, * { meta: { taskId: '123', userId: 'user456' } } * ); * ``` * * @template T - The type of value returned by successful tasks * @template I - The type of metadata (required for all tasks) * @template E - The type of errors that tasks can produce (defaults to Error) * @param options - Configuration options for the queue * @returns A new AsyncQueueWithMeta instance */ declare function createAsyncQueueWithMeta(options?: AsyncQueueOptions): AsyncQueueWithMeta; //#endregion export { type AsyncQueue, type AsyncQueueWithMeta, createAsyncQueue, createAsyncQueueWithMeta };