@mrjacket/ahko 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/README.md +22 -2
- package/dist/errors/timeout.error.d.ts +15 -2
- package/dist/index.cjs +200 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +198 -25
- package/dist/index.js.map +1 -1
- package/dist/models/options.model.d.ts +7 -0
- package/dist/scheduler/signal.d.ts +20 -0
- package/dist/scheduler/task-runner.d.ts +17 -8
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/errors/ahko.error.ts","../src/errors/configuration.error.ts","../src/models/strategy.model.ts","../src/models/state.model.ts","../src/retry/backoff.ts","../src/scheduler/idle-scheduler.ts","../src/scheduler/task-queue.ts","../src/errors/cancellation.error.ts","../src/scheduler/task-runner.ts","../src/ahko.ts","../src/version.ts","../src/errors/queue.error.ts","../src/errors/timeout.error.ts"],"sourcesContent":["export { Ahko } from \"./ahko.js\";\nexport { VERSION } from \"./version.js\";\n\n// Errors\nexport {\n AhkoError,\n AhkoCancellationError,\n AhkoConfigurationError,\n AhkoQueueError,\n AhkoTimeoutError,\n} from \"./errors/index.js\";\n\n// Models and interfaces\nexport {\n ETaskState,\n EScheduleStrategy,\n} from \"./models/index.js\";\n\nexport type {\n ITask,\n ITaskContext,\n IScheduleOptions,\n IAhkoOptions,\n IAhkoStats,\n TScheduleStrategy,\n IRetryOptions,\n TRetryBackoff,\n TRetryPredicate,\n} from \"./models/index.js\";\n\n// Retry utilities\nexport {\n calculateBackoff,\n DEFAULT_BASE_DELAY,\n DEFAULT_MAX_DELAY,\n} from \"./retry/index.js\";\n","/**\n * Base error class for all errors originating from the Ahko scheduler.\n */\nexport class AhkoError extends Error {\n /**\n * Creates a new AhkoError instance.\n *\n * @param message - Descriptive error message.\n * @param options - Standard Error options including cause.\n */\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = \"AhkoError\";\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n","import { AhkoError } from \"./ahko.error.js\";\n\n/**\n * Thrown when invalid configuration or scheduling options are provided.\n */\nexport class AhkoConfigurationError extends AhkoError {\n /**\n * Creates a new AhkoConfigurationError.\n *\n * @param message - Explanation of the invalid configuration parameter.\n * @param options - Standard Error options including cause.\n */\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = \"AhkoConfigurationError\";\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n","/**\n * Fundamental scheduling strategies supported by the Ahko scheduler.\n */\nexport enum EScheduleStrategy {\n /** Execute as soon as a concurrency slot is available */\n IMMEDIATE = \"immediate\",\n /** Delay execution for a designated duration before queuing */\n DELAY = \"delay\",\n /** Execute during platform idle opportunities (requestIdleCallback in browser, setImmediate in Node.js) */\n IDLE = \"idle\",\n}\n\n/**\n * Union type representing valid scheduling strategy identifiers.\n */\nexport type TScheduleStrategy = EScheduleStrategy | \"immediate\" | \"delay\" | \"idle\";\n","/**\n * Represents the discrete lifecycle states of an Ahko task.\n */\nexport enum ETaskState {\n /** Task has been scheduled and is awaiting execution in queue or timer */\n PENDING = \"pending\",\n /** Task is currently executing within an allocated concurrency slot */\n RUNNING = \"running\",\n /** Task successfully finished execution */\n COMPLETED = \"completed\",\n /** Task execution threw an error or rejected */\n FAILED = \"failed\",\n /** Task was cancelled via AbortSignal before or during execution */\n CANCELLED = \"cancelled\",\n /** Task was terminated because its execution exceeded the timeout */\n TIMED_OUT = \"timed_out\",\n}\n","import type { IRetryOptions } from \"../models/retry.model.js\";\n\n/**\n * Default base delay for backoff calculations in milliseconds.\n */\nexport const DEFAULT_BASE_DELAY = 250;\n\n/**\n * Default maximum delay ceiling for backoff calculations in milliseconds.\n */\nexport const DEFAULT_MAX_DELAY = 10_000;\n\n/**\n * Computes backoff delay in milliseconds for a retry attempt based on configured policy.\n *\n * @param attempt - 1-based index of the attempt that failed (1 for first failure, 2 for second, etc.).\n * @param options - Retry configuration options.\n * @param randomFn - Injectable random generator function (defaults to Math.random) for deterministic testing.\n * @returns Delay duration in milliseconds before next attempt.\n */\nexport function calculateBackoff(\n attempt: number,\n options?: IRetryOptions,\n randomFn: () => number = Math.random\n): number {\n const backoff = options?.backoff ?? \"exponential\";\n\n if (backoff === \"none\") {\n return 0;\n }\n\n const baseDelay =\n typeof options?.baseDelay === \"number\" && !Number.isNaN(options.baseDelay) && options.baseDelay >= 0\n ? options.baseDelay\n : DEFAULT_BASE_DELAY;\n\n const maxDelay =\n typeof options?.maxDelay === \"number\" && !Number.isNaN(options.maxDelay) && options.maxDelay >= baseDelay\n ? options.maxDelay\n : Math.max(DEFAULT_MAX_DELAY, baseDelay);\n\n let calculatedDelay: number;\n\n if (backoff === \"linear\") {\n calculatedDelay = baseDelay * Math.max(1, attempt);\n } else {\n // exponential: baseDelay * 2^(attempt - 1)\n const exponent = Math.max(0, attempt - 1);\n // Prevent 2^exponent overflow\n const factor = exponent > 30 ? 2 ** 30 : 2 ** exponent;\n calculatedDelay = baseDelay * factor;\n }\n\n const cappedDelay = Math.min(calculatedDelay, maxDelay);\n\n if (options?.jitter) {\n // Full jitter: uniformly random between 0 and cappedDelay\n return Math.floor(randomFn() * (cappedDelay + 1));\n }\n\n return Math.floor(cappedDelay);\n}\n","/**\n * Handle returned by the IdleScheduler allowing cancellation of an idle request.\n */\nexport interface IIdleHandle {\n /**\n * Cancels the scheduled idle callback and cleans up platform resources.\n */\n cancel(): void;\n}\n\n/**\n * Platform-agnostic scheduler for opportunistic idle task execution.\n *\n * Automatically detects and selects platform capabilities:\n * 1. Browser: `requestIdleCallback` / `cancelIdleCallback` (with optional timeout)\n * 2. Node.js: `setImmediate` / `clearImmediate` as low-priority primitive\n * 3. Fallback: `setTimeout(..., 0)` / `clearTimeout`\n */\nexport class IdleScheduler {\n /**\n * Schedules a callback to execute during the next idle opportunity.\n *\n * @param callback - Function to invoke when idle opportunity arises.\n * @param timeout - Optional max deadline in milliseconds to wait before invoking (browser only).\n * @param runtime - Target runtime scope providing scheduling primitives (defaults to globalThis).\n * @returns An {@link IIdleHandle} with a `cancel()` method for cleanup.\n */\n public static schedule(\n callback: () => void,\n timeout?: number,\n runtime: typeof globalThis = globalThis\n ): IIdleHandle {\n // 1. Browser requestIdleCallback\n if (\n typeof (runtime as Record<string, unknown>).requestIdleCallback === \"function\" &&\n typeof (runtime as Record<string, unknown>).cancelIdleCallback === \"function\"\n ) {\n const requestFn = (runtime as Record<string, unknown>).requestIdleCallback as (\n cb: (deadline?: unknown) => void,\n opts?: { timeout?: number }\n ) => number;\n\n const cancelFn = (runtime as Record<string, unknown>).cancelIdleCallback as (\n handle: number\n ) => void;\n\n const id = requestFn(\n () => callback(),\n typeof timeout === \"number\" && !Number.isNaN(timeout) && timeout >= 0\n ? { timeout }\n : undefined\n );\n\n return {\n cancel: () => cancelFn(id),\n };\n }\n\n // 2. Node.js setImmediate\n if (\n typeof (runtime as Record<string, unknown>).setImmediate === \"function\" &&\n typeof (runtime as Record<string, unknown>).clearImmediate === \"function\"\n ) {\n const setImmFn = (runtime as Record<string, unknown>).setImmediate as (\n cb: () => void\n ) => ReturnType<typeof setImmediate>;\n\n const clearImmFn = (runtime as Record<string, unknown>).clearImmediate as (\n handle: ReturnType<typeof setImmediate>\n ) => void;\n\n const handle = setImmFn(() => callback());\n\n return {\n cancel: () => clearImmFn(handle),\n };\n }\n\n // 3. Universal fallback setTimeout(0)\n const setTimerFn = runtime.setTimeout.bind(runtime);\n const clearTimerFn = runtime.clearTimeout.bind(runtime);\n\n const timerId = setTimerFn(() => callback(), 0);\n\n return {\n cancel: () => clearTimerFn(timerId),\n };\n }\n}\n","import { AhkoConfigurationError } from \"../errors/configuration.error.js\";\nimport type { IScheduleOptions } from \"../models/options.model.js\";\nimport type { IAhkoStats } from \"../models/stats.model.js\";\nimport { ETaskState } from \"../models/state.model.js\";\nimport { EScheduleStrategy } from \"../models/strategy.model.js\";\nimport { calculateBackoff } from \"../retry/backoff.js\";\nimport { IdleScheduler, type IIdleHandle } from \"./idle-scheduler.js\";\nimport { TaskRunner } from \"./task-runner.js\";\n\n/**\n * Entry tracking delayed task timers for deterministic cancellation and memory cleanup.\n */\ninterface IDelayedEntry {\n runner: TaskRunner<unknown>;\n timerId: ReturnType<typeof setTimeout>;\n}\n\n/**\n * Entry tracking idle callback handles for deterministic cancellation and cleanup.\n */\ninterface IIdleEntry {\n runner: TaskRunner<unknown>;\n handle: IIdleHandle;\n}\n\n/**\n * Entry tracking backoff delay timers for retry attempts.\n */\ninterface IRetryEntry {\n runner: TaskRunner<unknown>;\n timerId: ReturnType<typeof setTimeout>;\n}\n\n/**\n * Memory-safe FIFO task queue managing concurrency allocation,\n * delayed scheduling, and task lifecycle counters.\n */\nexport class TaskQueue {\n /** Maximum concurrent active tasks */\n public readonly concurrency: number;\n\n /** Queue of pending task runners waiting for a concurrency slot */\n private readonly queue: TaskRunner<unknown>[] = [];\n\n /** Set of task runners currently executing */\n private readonly activeRunners = new Set<TaskRunner<unknown>>();\n\n /** Set of tasks currently in delay phase */\n private readonly delayedEntries = new Set<IDelayedEntry>();\n\n /** Set of tasks currently awaiting an idle opportunity */\n private readonly idleEntries = new Set<IIdleEntry>();\n\n /** Set of tasks currently awaiting a retry backoff timer */\n private readonly retryEntries = new Set<IRetryEntry>();\n\n /** WeakMap associating task runners with their scheduling options */\n private readonly runnerOptions = new WeakMap<TaskRunner<unknown>, IScheduleOptions>();\n\n /** Cumulative completed tasks counter */\n private completedTasks = 0;\n\n /** Cumulative failed tasks counter */\n private failedTasks = 0;\n\n /** Cumulative cancelled tasks counter */\n private cancelledTasks = 0;\n\n /** Cumulative timed out tasks counter */\n private timedOutTasks = 0;\n\n /**\n * Creates a new TaskQueue.\n *\n * @param concurrency - Maximum concurrent tasks (defaults to Infinity).\n * @throws {AhkoConfigurationError} If concurrency is less than 1 or not a valid number.\n */\n constructor(concurrency = Infinity) {\n if (Number.isNaN(concurrency) || concurrency < 1) {\n throw new AhkoConfigurationError(\n `Invalid concurrency \"${concurrency}\". Must be a number greater than or equal to 1.`\n );\n }\n this.concurrency = concurrency;\n }\n\n /**\n * Enqueues a task runner according to the specified schedule options.\n *\n * @template T - The return type produced by the task.\n * @param runner - The task runner instance.\n * @param options - Scheduling options.\n * @returns The deferred promise associated with the task runner.\n * @throws {AhkoConfigurationError} If scheduling options are invalid.\n */\n public enqueue<T>(runner: TaskRunner<T>, options?: IScheduleOptions): Promise<T> {\n const strategy = options?.strategy ?? EScheduleStrategy.IMMEDIATE;\n\n if (\n strategy !== EScheduleStrategy.IMMEDIATE &&\n strategy !== EScheduleStrategy.DELAY &&\n strategy !== EScheduleStrategy.IDLE\n ) {\n throw new AhkoConfigurationError(\n `Unsupported schedule strategy \"${String(strategy)}\". Supported strategies: \"immediate\", \"delay\", \"idle\".`\n );\n }\n\n if (options?.retry) {\n if (\n typeof options.retry.attempts !== \"number\" ||\n Number.isNaN(options.retry.attempts) ||\n options.retry.attempts < 1 ||\n !Number.isInteger(options.retry.attempts)\n ) {\n throw new AhkoConfigurationError(\n `Invalid retry attempts \"${options.retry.attempts}\". attempts must be an integer greater than or equal to 1.`\n );\n }\n\n if (\n options.retry.baseDelay !== undefined &&\n (typeof options.retry.baseDelay !== \"number\" ||\n Number.isNaN(options.retry.baseDelay) ||\n options.retry.baseDelay < 0)\n ) {\n throw new AhkoConfigurationError(\n `Invalid retry baseDelay \"${options.retry.baseDelay}\". baseDelay must be a non-negative number in milliseconds.`\n );\n }\n\n if (\n options.retry.maxDelay !== undefined &&\n (typeof options.retry.maxDelay !== \"number\" ||\n Number.isNaN(options.retry.maxDelay) ||\n options.retry.maxDelay < 0)\n ) {\n throw new AhkoConfigurationError(\n `Invalid retry maxDelay \"${options.retry.maxDelay}\". maxDelay must be a non-negative number in milliseconds.`\n );\n }\n }\n\n if (options) {\n this.runnerOptions.set(runner as TaskRunner<unknown>, options);\n }\n\n if (runner.state === ETaskState.CANCELLED) {\n this.cancelledTasks++;\n return runner.promise;\n }\n\n if (strategy === EScheduleStrategy.DELAY) {\n const delayMs = options?.delay ?? 0;\n if (typeof delayMs !== \"number\" || Number.isNaN(delayMs) || delayMs < 0) {\n throw new AhkoConfigurationError(\n `Invalid delay \"${delayMs}\". Delay must be a non-negative number in milliseconds.`\n );\n }\n\n this.scheduleDelayed(runner as TaskRunner<unknown>, delayMs);\n return runner.promise;\n }\n\n if (strategy === EScheduleStrategy.IDLE) {\n if (\n options?.idleTimeout !== undefined &&\n (typeof options.idleTimeout !== \"number\" ||\n Number.isNaN(options.idleTimeout) ||\n options.idleTimeout < 0)\n ) {\n throw new AhkoConfigurationError(\n `Invalid idleTimeout \"${options.idleTimeout}\". idleTimeout must be a non-negative number in milliseconds.`\n );\n }\n\n this.scheduleIdle(runner as TaskRunner<unknown>, options?.idleTimeout);\n return runner.promise;\n }\n\n // Attach immediate onCancel handler to dequeue without consuming concurrency\n runner.onCancel = () => {\n const index = this.queue.indexOf(runner as TaskRunner<unknown>);\n if (index !== -1) {\n this.queue.splice(index, 1);\n this.cancelledTasks++;\n }\n };\n\n // Immediate strategy: add to pending queue and pump\n this.queue.push(runner as TaskRunner<unknown>);\n this.pump();\n\n return runner.promise;\n }\n\n /**\n * Schedules a task to be placed into the queue after a delay,\n * handling early cancellation safely.\n */\n private scheduleDelayed(runner: TaskRunner<unknown>, delayMs: number): void {\n const delayedEntry: IDelayedEntry = {\n runner,\n timerId: setTimeout(() => {\n this.delayedEntries.delete(delayedEntry);\n if (runner.state === ETaskState.CANCELLED) {\n return;\n }\n\n runner.onCancel = () => {\n const index = this.queue.indexOf(runner);\n if (index !== -1) {\n this.queue.splice(index, 1);\n this.cancelledTasks++;\n }\n };\n\n this.queue.push(runner);\n this.pump();\n }, delayMs),\n };\n\n this.delayedEntries.add(delayedEntry);\n\n runner.onCancel = () => {\n if (this.delayedEntries.has(delayedEntry)) {\n clearTimeout(delayedEntry.timerId);\n this.delayedEntries.delete(delayedEntry);\n this.cancelledTasks++;\n }\n };\n }\n\n /**\n * Schedules a task to be placed into the queue during an idle opportunity,\n * handling early cancellation safely.\n */\n private scheduleIdle(runner: TaskRunner<unknown>, idleTimeout?: number): void {\n let idleEntry!: IIdleEntry;\n\n const handle = IdleScheduler.schedule(() => {\n this.idleEntries.delete(idleEntry);\n if (runner.state === ETaskState.CANCELLED) {\n return;\n }\n\n runner.onCancel = () => {\n const index = this.queue.indexOf(runner);\n if (index !== -1) {\n this.queue.splice(index, 1);\n this.cancelledTasks++;\n }\n };\n\n this.queue.push(runner);\n this.pump();\n }, idleTimeout);\n\n idleEntry = { runner, handle };\n this.idleEntries.add(idleEntry);\n\n runner.onCancel = () => {\n if (this.idleEntries.has(idleEntry)) {\n handle.cancel();\n this.idleEntries.delete(idleEntry);\n this.cancelledTasks++;\n }\n };\n }\n\n /**\n * Pumps the queue by picking pending tasks and executing them\n * as long as concurrency capacity is available.\n */\n private pump(): void {\n while (this.activeRunners.size < this.concurrency && this.queue.length > 0) {\n const runner = this.queue.shift();\n if (!runner) {\n break;\n }\n\n if (runner.state === ETaskState.CANCELLED) {\n continue;\n }\n\n this.activeRunners.add(runner);\n\n // Execute runner without unhandled rejection risk\n void this.executeRunner(runner);\n }\n }\n\n /**\n * Internal execution of an active task runner.\n */\n private async executeRunner(runner: TaskRunner<unknown>): Promise<void> {\n const options = this.runnerOptions.get(runner);\n\n try {\n const result = await runner.run();\n this.completedTasks++;\n this.activeRunners.delete(runner);\n this.runnerOptions.delete(runner);\n runner.resolve(result);\n } catch (error) {\n if (runner.state === ETaskState.CANCELLED) {\n this.cancelledTasks++;\n this.activeRunners.delete(runner);\n this.runnerOptions.delete(runner);\n runner.reject(error);\n return;\n }\n\n const shouldRetry = await runner.canRetry(error, options?.retry);\n if (shouldRetry) {\n // Free concurrency slot immediately during backoff\n this.activeRunners.delete(runner);\n this.scheduleRetry(runner, options);\n return;\n }\n\n if (runner.state === ETaskState.TIMED_OUT) {\n this.timedOutTasks++;\n } else {\n this.failedTasks++;\n }\n this.activeRunners.delete(runner);\n this.runnerOptions.delete(runner);\n runner.reject(error);\n } finally {\n this.pump();\n }\n }\n\n /**\n * Schedules a retry attempt following backoff delay,\n * without holding a concurrency slot.\n */\n private scheduleRetry(runner: TaskRunner<unknown>, options?: IScheduleOptions): void {\n const backoffDelay = calculateBackoff(runner.attempt - 1, options?.retry);\n\n if (backoffDelay === 0) {\n runner.onCancel = () => {\n const index = this.queue.indexOf(runner);\n if (index !== -1) {\n this.queue.splice(index, 1);\n this.cancelledTasks++;\n }\n };\n this.queue.push(runner);\n this.pump();\n return;\n }\n\n const retryEntry: IRetryEntry = {\n runner,\n timerId: setTimeout(() => {\n this.retryEntries.delete(retryEntry);\n if (runner.state === ETaskState.CANCELLED) {\n return;\n }\n\n runner.onCancel = () => {\n const index = this.queue.indexOf(runner);\n if (index !== -1) {\n this.queue.splice(index, 1);\n this.cancelledTasks++;\n }\n };\n\n this.queue.push(runner);\n this.pump();\n }, backoffDelay),\n };\n\n this.retryEntries.add(retryEntry);\n\n runner.onCancel = () => {\n if (this.retryEntries.has(retryEntry)) {\n clearTimeout(retryEntry.timerId);\n this.retryEntries.delete(retryEntry);\n this.cancelledTasks++;\n }\n };\n }\n\n /**\n * Returns telemetry snapshot for the scheduler.\n *\n * @returns Frozen snapshot of current task metrics.\n */\n public getStats(): IAhkoStats {\n return Object.freeze({\n activeTasks: this.activeRunners.size,\n pendingTasks:\n this.queue.length +\n this.delayedEntries.size +\n this.idleEntries.size +\n this.retryEntries.size,\n completedTasks: this.completedTasks,\n failedTasks: this.failedTasks,\n cancelledTasks: this.cancelledTasks,\n timedOutTasks: this.timedOutTasks,\n capacity: this.concurrency,\n });\n }\n}\n","import { AhkoError } from \"./ahko.error.js\";\n\n/**\n * Thrown when a task is cancelled before or during execution.\n */\nexport class AhkoCancellationError extends AhkoError {\n /**\n * Creates a new AhkoCancellationError.\n *\n * @param message - Reason for cancellation.\n * @param options - Standard Error options including cause.\n */\n constructor(message = \"Task was cancelled\", options?: ErrorOptions) {\n super(message, options);\n this.name = \"AhkoCancellationError\";\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n","import { AhkoCancellationError } from \"../errors/cancellation.error.js\";\nimport type { ITaskContext } from \"../models/context.model.js\";\nimport type { IRetryOptions } from \"../models/retry.model.js\";\nimport { ETaskState } from \"../models/state.model.js\";\nimport type { ITask } from \"../models/task.model.js\";\n\nlet taskIdCounter = 0;\n\n/**\n * Internal task lifecycle manager responsible for execution, state transitions,\n * AbortSignal coordination, and deterministic resource cleanup.\n *\n * @template T - The return type produced by the underlying task.\n */\nexport class TaskRunner<T> {\n /** Unique task identifier */\n public readonly taskId: string;\n\n /** Current lifecycle state */\n private _state: ETaskState = ETaskState.PENDING;\n\n /** Internal AbortController whose signal is passed to the task context */\n private readonly abortController: AbortController;\n\n /** The user task function to execute */\n private readonly task: ITask<T>;\n\n /** User-supplied AbortSignal for external cancellation */\n private readonly externalSignal?: AbortSignal;\n\n /** Abort event listener reference for clean detachment */\n private readonly abortListener?: () => void;\n\n /** Promise resolve handler */\n private resolvePromise!: (value: T | PromiseLike<T>) => void;\n\n /** Promise reject handler */\n private rejectPromise!: (reason?: unknown) => void;\n\n /** Deferred promise exposed to the caller */\n public readonly promise: Promise<T>;\n\n /** Callback invoked when runner is cancelled while pending */\n public onCancel?: (runner: TaskRunner<T>) => void;\n\n /**\n * Creates a new TaskRunner instance.\n *\n * @param task - The asynchronous work unit to run.\n * @param externalSignal - Optional external AbortSignal to propagate.\n */\n constructor(task: ITask<T>, externalSignal?: AbortSignal) {\n this.taskId = `task_${Date.now().toString(36)}_${(++taskIdCounter).toString(36)}_${Math.random().toString(36).slice(2, 6)}`;\n this.task = task;\n this.externalSignal = externalSignal;\n this.abortController = new AbortController();\n\n this.promise = new Promise<T>((resolve, reject) => {\n this.resolvePromise = resolve;\n this.rejectPromise = reject;\n });\n\n if (this.externalSignal) {\n if (this.externalSignal.aborted) {\n this._state = ETaskState.CANCELLED;\n const reason = this.externalSignal.reason;\n const cancelError = new AhkoCancellationError(\n typeof reason === \"string\" ? reason : \"Task was cancelled prior to execution\",\n { cause: reason instanceof Error ? reason : undefined }\n );\n this.rejectPromise(cancelError);\n } else {\n this.abortListener = () => {\n this.handleExternalAbort();\n };\n this.externalSignal.addEventListener(\"abort\", this.abortListener, { once: true });\n }\n }\n }\n\n /**\n * Gets the current lifecycle state of the task.\n */\n public get state(): ETaskState {\n return this._state;\n }\n\n /** Current execution attempt count (1-indexed) */\n public attempt = 1;\n\n /**\n * Resolves the deferred promise.\n *\n * @param value - Value to resolve with.\n */\n public resolve(value: T): void {\n this.cleanup();\n this.resolvePromise(value);\n }\n\n /**\n * Rejects the deferred promise.\n *\n * @param reason - Reason to reject with.\n */\n public reject(reason: unknown): void {\n this.cleanup();\n this.rejectPromise(reason);\n }\n\n /**\n * Evaluates if the task should be retried following an execution failure.\n *\n * @param error - The error encountered during the attempt.\n * @param retryOptions - Configured retry policy.\n * @returns A promise resolving to true if retry should proceed, false otherwise.\n */\n public async canRetry(error: unknown, retryOptions?: IRetryOptions): Promise<boolean> {\n if (this._state === ETaskState.CANCELLED || this.abortController.signal.aborted) {\n return false;\n }\n\n if (!retryOptions || typeof retryOptions.attempts !== \"number\") {\n return false;\n }\n\n if (this.attempt >= retryOptions.attempts) {\n return false;\n }\n\n if (typeof retryOptions.shouldRetry === \"function\") {\n try {\n const allowed = await retryOptions.shouldRetry(error, this.attempt);\n if (!allowed) {\n return false;\n }\n } catch {\n return false;\n }\n }\n\n this.attempt++;\n this._state = ETaskState.PENDING;\n return true;\n }\n\n /**\n * Executes the task within an allocated concurrency slot.\n *\n * @returns A promise resolving to the task result or rejecting on failure/cancellation.\n */\n public async run(): Promise<T> {\n if (this._state === ETaskState.CANCELLED) {\n throw new AhkoCancellationError(\"Task was cancelled prior to execution\");\n }\n\n this._state = ETaskState.RUNNING;\n\n const context: ITaskContext = {\n signal: this.abortController.signal,\n taskId: this.taskId,\n };\n\n try {\n const result = await this.task(context);\n this._state = ETaskState.COMPLETED;\n return result;\n } catch (error) {\n const isCancelled =\n (this._state as ETaskState) === ETaskState.CANCELLED ||\n this.abortController.signal.aborted;\n\n if (isCancelled) {\n this._state = ETaskState.CANCELLED;\n throw new AhkoCancellationError(\"Task was cancelled during execution\", {\n cause: error instanceof Error ? error : undefined,\n });\n }\n\n this._state = ETaskState.FAILED;\n throw error;\n }\n }\n\n /**\n * Cancels the task, aborting pending or running execution.\n *\n * @param reason - Optional cancellation reason.\n */\n public cancel(reason?: unknown): void {\n if (\n this._state === ETaskState.COMPLETED ||\n this._state === ETaskState.FAILED ||\n this._state === ETaskState.CANCELLED ||\n this._state === ETaskState.TIMED_OUT\n ) {\n return;\n }\n\n const wasPending = this._state === ETaskState.PENDING;\n this._state = ETaskState.CANCELLED;\n this.abortController.abort(reason);\n this.cleanup();\n\n if (wasPending) {\n const cancellationError = new AhkoCancellationError(\n typeof reason === \"string\" ? reason : \"Task was cancelled prior to execution\",\n { cause: reason instanceof Error ? reason : undefined }\n );\n this.rejectPromise(cancellationError);\n this.onCancel?.(this);\n }\n }\n\n /**\n * Handles external AbortSignal trigger.\n */\n private handleExternalAbort(): void {\n this.cancel(this.externalSignal?.reason);\n }\n\n /**\n * Detaches event listeners from external signal to guarantee memory safety.\n */\n public cleanup(): void {\n if (this.externalSignal && this.abortListener) {\n this.externalSignal.removeEventListener(\"abort\", this.abortListener);\n }\n }\n}\n","import { AhkoConfigurationError } from \"./errors/configuration.error.js\";\nimport type { IAhkoOptions, IScheduleOptions } from \"./models/options.model.js\";\nimport type { IAhkoStats } from \"./models/stats.model.js\";\nimport { EScheduleStrategy } from \"./models/strategy.model.js\";\nimport type { ITask } from \"./models/task.model.js\";\nimport { TaskQueue } from \"./scheduler/task-queue.js\";\nimport { TaskRunner } from \"./scheduler/task-runner.js\";\n\n/**\n * Ahko — Low-energy asynchronous task scheduler.\n *\n * Coordinates execution timing, enforces concurrency limits, and cooperates\n * natively with AbortSignal cancellation.\n *\n * @example\n * ```typescript\n * import { Ahko } from \"@mrjacket/ahko\";\n *\n * const ahko = new Ahko({ concurrency: 2 });\n *\n * const result = await ahko.schedule(async ({ signal, taskId }) => {\n * const res = await fetch(\"https://api.example.com\", { signal });\n * return res.json();\n * });\n * ```\n */\nexport class Ahko {\n /** Internal queue and concurrency manager */\n private readonly queue: TaskQueue;\n\n /**\n * Initializes a new Ahko scheduler instance.\n *\n * @param options - Optional scheduler configuration.\n * @throws {AhkoConfigurationError} If concurrency is invalid (less than 1 or NaN).\n *\n * @example\n * ```typescript\n * const ahko = new Ahko({ concurrency: 4 });\n * ```\n */\n constructor(options?: IAhkoOptions) {\n this.queue = new TaskQueue(options?.concurrency);\n }\n\n /**\n * Schedules a task for execution with full return type inference.\n *\n * @template T - Inferred return type of the task.\n * @param task - Asynchronous or synchronous task function accepting an {@link ITaskContext}.\n * @param options - Task-specific scheduling options such as strategy, delay, and cancellation signal.\n * @returns A promise that resolves with the task's return value.\n *\n * @throws {AhkoConfigurationError} If the task is not a function or options are invalid.\n * @throws {AhkoCancellationError} If the task is cancelled prior to or during execution.\n *\n * @example\n * ```typescript\n * // Immediate execution (subject to concurrency)\n * const count = await ahko.schedule(async () => 42);\n *\n * // Delayed execution\n * await ahko.schedule(\n * async ({ signal }) => doWork({ signal }),\n * { strategy: \"delay\", delay: 1000 }\n * );\n * ```\n */\n public schedule<T>(task: ITask<T>, options?: IScheduleOptions): Promise<T> {\n if (typeof task !== \"function\") {\n throw new AhkoConfigurationError(\"Task must be a valid function.\");\n }\n\n const runner = new TaskRunner<T>(task, options?.signal);\n return this.queue.enqueue(runner, options);\n }\n\n /**\n * Convenience method to schedule a task during platform idle opportunities.\n *\n * Equivalent to calling `schedule(task, { ...options, strategy: \"idle\" })`.\n * In browsers, uses `requestIdleCallback` when available.\n * In Node.js, uses `setImmediate`.\n * Falls back to `setTimeout(..., 0)` if neither is available.\n *\n * @template T - Inferred return type of the task.\n * @param task - Task function to run when idle.\n * @param options - Scheduling options (excluding strategy).\n * @returns A promise resolving to the task's return value.\n *\n * @throws {AhkoConfigurationError} If the task is not a function or options are invalid.\n * @throws {AhkoCancellationError} If the task is cancelled prior to or during execution.\n *\n * @example\n * ```typescript\n * const result = await ahko.idle(async ({ signal }) => {\n * return computeAnalytics();\n * });\n * ```\n */\n public idle<T>(\n task: ITask<T>,\n options?: Omit<IScheduleOptions, \"strategy\">\n ): Promise<T> {\n return this.schedule(task, {\n ...options,\n strategy: EScheduleStrategy.IDLE,\n });\n }\n\n /**\n * Retrieves real-time telemetry metrics from the scheduler.\n *\n * @returns An {@link IAhkoStats} snapshot of active, pending, completed, failed, cancelled, and timed out tasks.\n *\n * @example\n * ```typescript\n * const stats = ahko.stats();\n * console.log(`Active: ${stats.activeTasks}, Pending: ${stats.pendingTasks}`);\n * ```\n */\n public stats(): IAhkoStats {\n return this.queue.getStats();\n }\n}\n","/**\n * Current version of @mrjacket/ahko package.\n */\nexport const VERSION = \"0.3.0\";\n","import { AhkoError } from \"./ahko.error.js\";\n\n/**\n * Thrown when an internal queue invariant is violated or queue limits are breached.\n */\nexport class AhkoQueueError extends AhkoError {\n /**\n * Creates a new AhkoQueueError.\n *\n * @param message - Explanation of the queue failure.\n * @param options - Standard Error options including cause.\n */\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = \"AhkoQueueError\";\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n","import { AhkoError } from \"./ahko.error.js\";\n\n/**\n * Thrown when a task exceeds its allotted timeout duration.\n */\nexport class AhkoTimeoutError extends AhkoError {\n /**\n * Creates a new AhkoTimeoutError.\n *\n * @param message - Explanation of timeout expiry.\n * @param options - Standard Error options including cause.\n */\n constructor(message = \"Task execution timed out\", options?: ErrorOptions) {\n super(message, options);\n this.name = \"AhkoTimeoutError\";\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n"],"mappings":"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","names":["EScheduleStrategy","ETaskState"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/errors/ahko.error.ts","../src/errors/configuration.error.ts","../src/models/strategy.model.ts","../src/errors/timeout.error.ts","../src/models/state.model.ts","../src/retry/backoff.ts","../src/scheduler/idle-scheduler.ts","../src/scheduler/task-queue.ts","../src/errors/cancellation.error.ts","../src/scheduler/task-runner.ts","../src/ahko.ts","../src/version.ts","../src/errors/queue.error.ts","../src/scheduler/signal.ts"],"sourcesContent":["export { Ahko } from \"./ahko.js\";\nexport { VERSION } from \"./version.js\";\n\n// Errors\nexport {\n AhkoError,\n AhkoCancellationError,\n AhkoConfigurationError,\n AhkoQueueError,\n AhkoTimeoutError,\n type IAhkoTimeoutErrorOptions,\n} from \"./errors/index.js\";\n\n// Models and interfaces\nexport {\n ETaskState,\n EScheduleStrategy,\n} from \"./models/index.js\";\n\nexport type {\n ITask,\n ITaskContext,\n IScheduleOptions,\n IAhkoOptions,\n IAhkoStats,\n TScheduleStrategy,\n IRetryOptions,\n TRetryBackoff,\n TRetryPredicate,\n} from \"./models/index.js\";\n\n// Retry utilities\nexport {\n calculateBackoff,\n DEFAULT_BASE_DELAY,\n DEFAULT_MAX_DELAY,\n} from \"./retry/index.js\";\n\n// Signal utilities\nexport {\n combineSignals,\n type ICombinedSignal,\n} from \"./scheduler/signal.js\";\n\n","/**\n * Base error class for all errors originating from the Ahko scheduler.\n */\nexport class AhkoError extends Error {\n /**\n * Creates a new AhkoError instance.\n *\n * @param message - Descriptive error message.\n * @param options - Standard Error options including cause.\n */\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = \"AhkoError\";\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n","import { AhkoError } from \"./ahko.error.js\";\n\n/**\n * Thrown when invalid configuration or scheduling options are provided.\n */\nexport class AhkoConfigurationError extends AhkoError {\n /**\n * Creates a new AhkoConfigurationError.\n *\n * @param message - Explanation of the invalid configuration parameter.\n * @param options - Standard Error options including cause.\n */\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = \"AhkoConfigurationError\";\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n","/**\n * Fundamental scheduling strategies supported by the Ahko scheduler.\n */\nexport enum EScheduleStrategy {\n /** Execute as soon as a concurrency slot is available */\n IMMEDIATE = \"immediate\",\n /** Delay execution for a designated duration before queuing */\n DELAY = \"delay\",\n /** Execute during platform idle opportunities (requestIdleCallback in browser, setImmediate in Node.js) */\n IDLE = \"idle\",\n}\n\n/**\n * Union type representing valid scheduling strategy identifiers.\n */\nexport type TScheduleStrategy = EScheduleStrategy | \"immediate\" | \"delay\" | \"idle\";\n","import { AhkoError } from \"./ahko.error.js\";\n\n/**\n * Options for constructing an AhkoTimeoutError.\n */\nexport interface IAhkoTimeoutErrorOptions extends ErrorOptions {\n /**\n * The timeout threshold in milliseconds that was exceeded.\n */\n timeoutMs?: number;\n}\n\n/**\n * Thrown when a task exceeds its allotted timeout duration.\n */\nexport class AhkoTimeoutError extends AhkoError {\n /**\n * The timeout threshold in milliseconds that was exceeded, if configured.\n */\n public readonly timeoutMs?: number;\n\n /**\n * Creates a new AhkoTimeoutError.\n *\n * @param message - Explanation of timeout expiry.\n * @param options - Standard Error options including optional timeoutMs and cause.\n */\n constructor(\n message = \"Task execution timed out\",\n options?: IAhkoTimeoutErrorOptions\n ) {\n super(message, options);\n this.name = \"AhkoTimeoutError\";\n this.timeoutMs = options?.timeoutMs;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n","/**\n * Represents the discrete lifecycle states of an Ahko task.\n */\nexport enum ETaskState {\n /** Task has been scheduled and is awaiting execution in queue or timer */\n PENDING = \"pending\",\n /** Task is currently executing within an allocated concurrency slot */\n RUNNING = \"running\",\n /** Task successfully finished execution */\n COMPLETED = \"completed\",\n /** Task execution threw an error or rejected */\n FAILED = \"failed\",\n /** Task was cancelled via AbortSignal before or during execution */\n CANCELLED = \"cancelled\",\n /** Task was terminated because its execution exceeded the timeout */\n TIMED_OUT = \"timed_out\",\n}\n","import type { IRetryOptions } from \"../models/retry.model.js\";\n\n/**\n * Default base delay for backoff calculations in milliseconds.\n */\nexport const DEFAULT_BASE_DELAY = 250;\n\n/**\n * Default maximum delay ceiling for backoff calculations in milliseconds.\n */\nexport const DEFAULT_MAX_DELAY = 10_000;\n\n/**\n * Computes backoff delay in milliseconds for a retry attempt based on configured policy.\n *\n * @param attempt - 1-based index of the attempt that failed (1 for first failure, 2 for second, etc.).\n * @param options - Retry configuration options.\n * @param randomFn - Injectable random generator function (defaults to Math.random) for deterministic testing.\n * @returns Delay duration in milliseconds before next attempt.\n */\nexport function calculateBackoff(\n attempt: number,\n options?: IRetryOptions,\n randomFn: () => number = Math.random\n): number {\n const backoff = options?.backoff ?? \"exponential\";\n\n if (backoff === \"none\") {\n return 0;\n }\n\n const baseDelay =\n typeof options?.baseDelay === \"number\" && !Number.isNaN(options.baseDelay) && options.baseDelay >= 0\n ? options.baseDelay\n : DEFAULT_BASE_DELAY;\n\n const maxDelay =\n typeof options?.maxDelay === \"number\" && !Number.isNaN(options.maxDelay) && options.maxDelay >= baseDelay\n ? options.maxDelay\n : Math.max(DEFAULT_MAX_DELAY, baseDelay);\n\n let calculatedDelay: number;\n\n if (backoff === \"linear\") {\n calculatedDelay = baseDelay * Math.max(1, attempt);\n } else {\n // exponential: baseDelay * 2^(attempt - 1)\n const exponent = Math.max(0, attempt - 1);\n // Prevent 2^exponent overflow\n const factor = exponent > 30 ? 2 ** 30 : 2 ** exponent;\n calculatedDelay = baseDelay * factor;\n }\n\n const cappedDelay = Math.min(calculatedDelay, maxDelay);\n\n if (options?.jitter) {\n // Full jitter: uniformly random between 0 and cappedDelay\n return Math.floor(randomFn() * (cappedDelay + 1));\n }\n\n return Math.floor(cappedDelay);\n}\n","/**\n * Handle returned by the IdleScheduler allowing cancellation of an idle request.\n */\nexport interface IIdleHandle {\n /**\n * Cancels the scheduled idle callback and cleans up platform resources.\n */\n cancel(): void;\n}\n\n/**\n * Platform-agnostic scheduler for opportunistic idle task execution.\n *\n * Automatically detects and selects platform capabilities:\n * 1. Browser: `requestIdleCallback` / `cancelIdleCallback` (with optional timeout)\n * 2. Node.js: `setImmediate` / `clearImmediate` as low-priority primitive\n * 3. Fallback: `setTimeout(..., 0)` / `clearTimeout`\n */\nexport class IdleScheduler {\n /**\n * Schedules a callback to execute during the next idle opportunity.\n *\n * @param callback - Function to invoke when idle opportunity arises.\n * @param timeout - Optional max deadline in milliseconds to wait before invoking (browser only).\n * @param runtime - Target runtime scope providing scheduling primitives (defaults to globalThis).\n * @returns An {@link IIdleHandle} with a `cancel()` method for cleanup.\n */\n public static schedule(\n callback: () => void,\n timeout?: number,\n runtime: typeof globalThis = globalThis\n ): IIdleHandle {\n // 1. Browser requestIdleCallback\n if (\n typeof (runtime as Record<string, unknown>).requestIdleCallback === \"function\" &&\n typeof (runtime as Record<string, unknown>).cancelIdleCallback === \"function\"\n ) {\n const requestFn = (runtime as Record<string, unknown>).requestIdleCallback as (\n cb: (deadline?: unknown) => void,\n opts?: { timeout?: number }\n ) => number;\n\n const cancelFn = (runtime as Record<string, unknown>).cancelIdleCallback as (\n handle: number\n ) => void;\n\n const id = requestFn(\n () => callback(),\n typeof timeout === \"number\" && !Number.isNaN(timeout) && timeout >= 0\n ? { timeout }\n : undefined\n );\n\n return {\n cancel: () => cancelFn(id),\n };\n }\n\n // 2. Node.js setImmediate\n if (\n typeof (runtime as Record<string, unknown>).setImmediate === \"function\" &&\n typeof (runtime as Record<string, unknown>).clearImmediate === \"function\"\n ) {\n const setImmFn = (runtime as Record<string, unknown>).setImmediate as (\n cb: () => void\n ) => ReturnType<typeof setImmediate>;\n\n const clearImmFn = (runtime as Record<string, unknown>).clearImmediate as (\n handle: ReturnType<typeof setImmediate>\n ) => void;\n\n const handle = setImmFn(() => callback());\n\n return {\n cancel: () => clearImmFn(handle),\n };\n }\n\n // 3. Universal fallback setTimeout(0)\n const setTimerFn = runtime.setTimeout.bind(runtime);\n const clearTimerFn = runtime.clearTimeout.bind(runtime);\n\n const timerId = setTimerFn(() => callback(), 0);\n\n return {\n cancel: () => clearTimerFn(timerId),\n };\n }\n}\n","import { AhkoConfigurationError } from \"../errors/configuration.error.js\";\nimport { AhkoTimeoutError } from \"../errors/timeout.error.js\";\nimport type { IScheduleOptions } from \"../models/options.model.js\";\nimport type { IAhkoStats } from \"../models/stats.model.js\";\nimport { ETaskState } from \"../models/state.model.js\";\nimport { EScheduleStrategy } from \"../models/strategy.model.js\";\nimport { calculateBackoff } from \"../retry/backoff.js\";\nimport { IdleScheduler, type IIdleHandle } from \"./idle-scheduler.js\";\nimport { TaskRunner } from \"./task-runner.js\";\n\n/**\n * Entry tracking delayed task timers for deterministic cancellation and memory cleanup.\n */\ninterface IDelayedEntry {\n runner: TaskRunner<unknown>;\n timerId: ReturnType<typeof setTimeout>;\n}\n\n/**\n * Entry tracking idle callback handles for deterministic cancellation and cleanup.\n */\ninterface IIdleEntry {\n runner: TaskRunner<unknown>;\n handle: IIdleHandle;\n}\n\n/**\n * Entry tracking backoff delay timers for retry attempts.\n */\ninterface IRetryEntry {\n runner: TaskRunner<unknown>;\n timerId: ReturnType<typeof setTimeout>;\n}\n\n/**\n * Memory-safe FIFO task queue managing concurrency allocation,\n * delayed scheduling, and task lifecycle counters.\n */\nexport class TaskQueue {\n /** Maximum concurrent active tasks */\n public readonly concurrency: number;\n\n /** Queue of pending task runners waiting for a concurrency slot */\n private readonly queue: TaskRunner<unknown>[] = [];\n\n /** Set of task runners currently executing */\n private readonly activeRunners = new Set<TaskRunner<unknown>>();\n\n /** Set of tasks currently in delay phase */\n private readonly delayedEntries = new Set<IDelayedEntry>();\n\n /** Set of tasks currently awaiting an idle opportunity */\n private readonly idleEntries = new Set<IIdleEntry>();\n\n /** Set of tasks currently awaiting a retry backoff timer */\n private readonly retryEntries = new Set<IRetryEntry>();\n\n /** WeakMap associating task runners with their scheduling options */\n private readonly runnerOptions = new WeakMap<TaskRunner<unknown>, IScheduleOptions>();\n\n /** Cumulative completed tasks counter */\n private completedTasks = 0;\n\n /** Cumulative failed tasks counter */\n private failedTasks = 0;\n\n /** Cumulative cancelled tasks counter */\n private cancelledTasks = 0;\n\n /** Cumulative timed out tasks counter */\n private timedOutTasks = 0;\n\n /**\n * Creates a new TaskQueue.\n *\n * @param concurrency - Maximum concurrent tasks (defaults to Infinity).\n * @throws {AhkoConfigurationError} If concurrency is less than 1 or not a valid number.\n */\n constructor(concurrency = Infinity) {\n if (Number.isNaN(concurrency) || concurrency < 1) {\n throw new AhkoConfigurationError(\n `Invalid concurrency \"${concurrency}\". Must be a number greater than or equal to 1.`\n );\n }\n this.concurrency = concurrency;\n }\n\n /**\n * Enqueues a task runner according to the specified schedule options.\n *\n * @template T - The return type produced by the task.\n * @param runner - The task runner instance.\n * @param options - Scheduling options.\n * @returns The deferred promise associated with the task runner.\n * @throws {AhkoConfigurationError} If scheduling options are invalid.\n */\n public enqueue<T>(runner: TaskRunner<T>, options?: IScheduleOptions): Promise<T> {\n const strategy = options?.strategy ?? EScheduleStrategy.IMMEDIATE;\n\n if (\n strategy !== EScheduleStrategy.IMMEDIATE &&\n strategy !== EScheduleStrategy.DELAY &&\n strategy !== EScheduleStrategy.IDLE\n ) {\n throw new AhkoConfigurationError(\n `Unsupported schedule strategy \"${String(strategy)}\". Supported strategies: \"immediate\", \"delay\", \"idle\".`\n );\n }\n\n if (options?.retry) {\n if (\n typeof options.retry.attempts !== \"number\" ||\n Number.isNaN(options.retry.attempts) ||\n options.retry.attempts < 1 ||\n !Number.isInteger(options.retry.attempts)\n ) {\n throw new AhkoConfigurationError(\n `Invalid retry attempts \"${options.retry.attempts}\". attempts must be an integer greater than or equal to 1.`\n );\n }\n\n if (\n options.retry.baseDelay !== undefined &&\n (typeof options.retry.baseDelay !== \"number\" ||\n Number.isNaN(options.retry.baseDelay) ||\n options.retry.baseDelay < 0)\n ) {\n throw new AhkoConfigurationError(\n `Invalid retry baseDelay \"${options.retry.baseDelay}\". baseDelay must be a non-negative number in milliseconds.`\n );\n }\n\n if (\n options.retry.maxDelay !== undefined &&\n (typeof options.retry.maxDelay !== \"number\" ||\n Number.isNaN(options.retry.maxDelay) ||\n options.retry.maxDelay < 0)\n ) {\n throw new AhkoConfigurationError(\n `Invalid retry maxDelay \"${options.retry.maxDelay}\". maxDelay must be a non-negative number in milliseconds.`\n );\n }\n }\n\n if (options?.timeoutMs !== undefined) {\n if (\n typeof options.timeoutMs !== \"number\" ||\n Number.isNaN(options.timeoutMs) ||\n !Number.isFinite(options.timeoutMs) ||\n options.timeoutMs <= 0\n ) {\n throw new AhkoConfigurationError(\n `Invalid timeoutMs \"${options.timeoutMs}\". timeoutMs must be a positive finite number greater than 0.`\n );\n }\n }\n\n if (options) {\n this.runnerOptions.set(runner as TaskRunner<unknown>, options);\n }\n\n if (runner.state === ETaskState.CANCELLED) {\n this.cancelledTasks++;\n return runner.promise;\n }\n\n if (strategy === EScheduleStrategy.DELAY) {\n const delayMs = options?.delay ?? 0;\n if (typeof delayMs !== \"number\" || Number.isNaN(delayMs) || delayMs < 0) {\n throw new AhkoConfigurationError(\n `Invalid delay \"${delayMs}\". Delay must be a non-negative number in milliseconds.`\n );\n }\n\n this.scheduleDelayed(runner as TaskRunner<unknown>, delayMs);\n return runner.promise;\n }\n\n if (strategy === EScheduleStrategy.IDLE) {\n if (\n options?.idleTimeout !== undefined &&\n (typeof options.idleTimeout !== \"number\" ||\n Number.isNaN(options.idleTimeout) ||\n options.idleTimeout < 0)\n ) {\n throw new AhkoConfigurationError(\n `Invalid idleTimeout \"${options.idleTimeout}\". idleTimeout must be a non-negative number in milliseconds.`\n );\n }\n\n this.scheduleIdle(runner as TaskRunner<unknown>, options?.idleTimeout);\n return runner.promise;\n }\n\n // Attach immediate onCancel handler to dequeue without consuming concurrency\n runner.onCancel = () => {\n const index = this.queue.indexOf(runner as TaskRunner<unknown>);\n if (index !== -1) {\n this.queue.splice(index, 1);\n this.cancelledTasks++;\n }\n };\n\n // Immediate strategy: add to pending queue and pump\n this.queue.push(runner as TaskRunner<unknown>);\n this.pump();\n\n return runner.promise;\n }\n\n /**\n * Schedules a task to be placed into the queue after a delay,\n * handling early cancellation safely.\n */\n private scheduleDelayed(runner: TaskRunner<unknown>, delayMs: number): void {\n const delayedEntry: IDelayedEntry = {\n runner,\n timerId: setTimeout(() => {\n this.delayedEntries.delete(delayedEntry);\n if (runner.state === ETaskState.CANCELLED) {\n return;\n }\n\n runner.onCancel = () => {\n const index = this.queue.indexOf(runner);\n if (index !== -1) {\n this.queue.splice(index, 1);\n this.cancelledTasks++;\n }\n };\n\n this.queue.push(runner);\n this.pump();\n }, delayMs),\n };\n\n this.delayedEntries.add(delayedEntry);\n\n runner.onCancel = () => {\n if (this.delayedEntries.has(delayedEntry)) {\n clearTimeout(delayedEntry.timerId);\n this.delayedEntries.delete(delayedEntry);\n this.cancelledTasks++;\n }\n };\n }\n\n /**\n * Schedules a task to be placed into the queue during an idle opportunity,\n * handling early cancellation safely.\n */\n private scheduleIdle(runner: TaskRunner<unknown>, idleTimeout?: number): void {\n let idleEntry!: IIdleEntry;\n\n const handle = IdleScheduler.schedule(() => {\n this.idleEntries.delete(idleEntry);\n if (runner.state === ETaskState.CANCELLED) {\n return;\n }\n\n runner.onCancel = () => {\n const index = this.queue.indexOf(runner);\n if (index !== -1) {\n this.queue.splice(index, 1);\n this.cancelledTasks++;\n }\n };\n\n this.queue.push(runner);\n this.pump();\n }, idleTimeout);\n\n idleEntry = { runner, handle };\n this.idleEntries.add(idleEntry);\n\n runner.onCancel = () => {\n if (this.idleEntries.has(idleEntry)) {\n handle.cancel();\n this.idleEntries.delete(idleEntry);\n this.cancelledTasks++;\n }\n };\n }\n\n /**\n * Pumps the queue by picking pending tasks and executing them\n * as long as concurrency capacity is available.\n */\n private pump(): void {\n while (this.activeRunners.size < this.concurrency && this.queue.length > 0) {\n const runner = this.queue.shift();\n if (!runner) {\n break;\n }\n\n if (runner.state === ETaskState.CANCELLED) {\n continue;\n }\n\n this.activeRunners.add(runner);\n\n // Execute runner without unhandled rejection risk\n void this.executeRunner(runner);\n }\n }\n\n /**\n * Internal execution of an active task runner.\n */\n private async executeRunner(runner: TaskRunner<unknown>): Promise<void> {\n const options = this.runnerOptions.get(runner);\n\n try {\n const result = await runner.run();\n this.completedTasks++;\n this.activeRunners.delete(runner);\n this.runnerOptions.delete(runner);\n runner.resolve(result);\n } catch (error) {\n if (runner.state === ETaskState.CANCELLED) {\n this.cancelledTasks++;\n this.activeRunners.delete(runner);\n this.runnerOptions.delete(runner);\n runner.reject(error);\n return;\n }\n\n const shouldRetry = await runner.canRetry(error, options?.retry);\n if (shouldRetry) {\n // Free concurrency slot immediately during backoff\n this.activeRunners.delete(runner);\n this.scheduleRetry(runner, options);\n return;\n }\n\n if (runner.state === ETaskState.TIMED_OUT || error instanceof AhkoTimeoutError) {\n this.timedOutTasks++;\n } else {\n this.failedTasks++;\n }\n this.activeRunners.delete(runner);\n this.runnerOptions.delete(runner);\n runner.reject(error);\n } finally {\n this.pump();\n }\n }\n\n /**\n * Schedules a retry attempt following backoff delay,\n * without holding a concurrency slot.\n */\n private scheduleRetry(runner: TaskRunner<unknown>, options?: IScheduleOptions): void {\n const backoffDelay = calculateBackoff(runner.attempt - 1, options?.retry);\n\n if (backoffDelay === 0) {\n runner.onCancel = () => {\n const index = this.queue.indexOf(runner);\n if (index !== -1) {\n this.queue.splice(index, 1);\n this.cancelledTasks++;\n }\n };\n this.queue.push(runner);\n this.pump();\n return;\n }\n\n const retryEntry: IRetryEntry = {\n runner,\n timerId: setTimeout(() => {\n this.retryEntries.delete(retryEntry);\n if (runner.state === ETaskState.CANCELLED) {\n return;\n }\n\n runner.onCancel = () => {\n const index = this.queue.indexOf(runner);\n if (index !== -1) {\n this.queue.splice(index, 1);\n this.cancelledTasks++;\n }\n };\n\n this.queue.push(runner);\n this.pump();\n }, backoffDelay),\n };\n\n this.retryEntries.add(retryEntry);\n\n runner.onCancel = () => {\n if (this.retryEntries.has(retryEntry)) {\n clearTimeout(retryEntry.timerId);\n this.retryEntries.delete(retryEntry);\n this.cancelledTasks++;\n }\n };\n }\n\n /**\n * Returns telemetry snapshot for the scheduler.\n *\n * @returns Frozen snapshot of current task metrics.\n */\n public getStats(): IAhkoStats {\n return Object.freeze({\n activeTasks: this.activeRunners.size,\n pendingTasks:\n this.queue.length +\n this.delayedEntries.size +\n this.idleEntries.size +\n this.retryEntries.size,\n completedTasks: this.completedTasks,\n failedTasks: this.failedTasks,\n cancelledTasks: this.cancelledTasks,\n timedOutTasks: this.timedOutTasks,\n capacity: this.concurrency,\n });\n }\n}\n","import { AhkoError } from \"./ahko.error.js\";\n\n/**\n * Thrown when a task is cancelled before or during execution.\n */\nexport class AhkoCancellationError extends AhkoError {\n /**\n * Creates a new AhkoCancellationError.\n *\n * @param message - Reason for cancellation.\n * @param options - Standard Error options including cause.\n */\n constructor(message = \"Task was cancelled\", options?: ErrorOptions) {\n super(message, options);\n this.name = \"AhkoCancellationError\";\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n","import { AhkoCancellationError } from \"../errors/cancellation.error.js\";\nimport { AhkoTimeoutError } from \"../errors/timeout.error.js\";\nimport type { ITaskContext } from \"../models/context.model.js\";\nimport type { IRetryOptions } from \"../models/retry.model.js\";\nimport { ETaskState } from \"../models/state.model.js\";\nimport type { ITask } from \"../models/task.model.js\";\n\nlet taskIdCounter = 0;\n\n/**\n * Internal task lifecycle manager responsible for execution, state transitions,\n * AbortSignal coordination, timeout enforcement, and deterministic resource cleanup.\n *\n * @template T - The return type produced by the underlying task.\n */\nexport class TaskRunner<T> {\n /** Unique task identifier */\n public readonly taskId: string;\n\n /** Current lifecycle state */\n private _state: ETaskState = ETaskState.PENDING;\n\n /** Internal AbortController whose signal is passed to the task context */\n private abortController: AbortController;\n\n /** The user task function to execute */\n private readonly task: ITask<T>;\n\n /** User-supplied AbortSignal for external cancellation */\n public readonly externalSignal?: AbortSignal;\n\n /** Maximum execution duration allowed in milliseconds */\n public readonly timeoutMs?: number;\n\n /** Active timeout timer identifier */\n private timeoutTimerId?: ReturnType<typeof setTimeout>;\n\n /** Abort event listener reference for clean detachment */\n private readonly abortListener?: () => void;\n\n /** Promise resolve handler */\n private resolvePromise!: (value: T | PromiseLike<T>) => void;\n\n /** Promise reject handler */\n private rejectPromise!: (reason?: unknown) => void;\n\n /** Deferred promise exposed to the caller */\n public readonly promise: Promise<T>;\n\n /** Callback invoked when runner is cancelled while pending */\n public onCancel?: (runner: TaskRunner<T>) => void;\n\n /** Current execution attempt count (1-indexed) */\n public attempt = 1;\n\n /**\n * Creates a new TaskRunner instance.\n *\n * @param task - The asynchronous work unit to run.\n * @param externalSignal - Optional external AbortSignal to propagate.\n * @param timeoutMs - Optional maximum execution time in milliseconds.\n */\n constructor(task: ITask<T>, externalSignal?: AbortSignal, timeoutMs?: number) {\n this.taskId = `task_${Date.now().toString(36)}_${(++taskIdCounter).toString(36)}_${Math.random().toString(36).slice(2, 6)}`;\n this.task = task;\n this.externalSignal = externalSignal;\n this.timeoutMs = timeoutMs;\n this.abortController = new AbortController();\n\n this.promise = new Promise<T>((resolve, reject) => {\n this.resolvePromise = resolve;\n this.rejectPromise = reject;\n });\n\n if (this.externalSignal) {\n if (this.externalSignal.aborted) {\n this._state = ETaskState.CANCELLED;\n const reason = this.externalSignal.reason;\n const cancelError = new AhkoCancellationError(\n typeof reason === \"string\" ? reason : \"Task was cancelled prior to execution\",\n { cause: reason instanceof Error ? reason : undefined }\n );\n this.abortController.abort(cancelError);\n this.rejectPromise(cancelError);\n } else {\n this.abortListener = () => {\n this.handleExternalAbort();\n };\n this.externalSignal.addEventListener(\"abort\", this.abortListener, { once: true });\n }\n }\n }\n\n /**\n * Gets the current lifecycle state of the task.\n */\n public get state(): ETaskState {\n return this._state;\n }\n\n /**\n * Resolves the deferred promise.\n *\n * @param value - Value to resolve with.\n */\n public resolve(value: T): void {\n this.cleanup();\n this.resolvePromise(value);\n }\n\n /**\n * Rejects the deferred promise.\n *\n * @param reason - Reason to reject with.\n */\n public reject(reason: unknown): void {\n this.cleanup();\n this.rejectPromise(reason);\n }\n\n /**\n * Evaluates if the task should be retried following an execution failure or timeout.\n *\n * @param error - The error encountered during the attempt.\n * @param retryOptions - Configured retry policy.\n * @returns A promise resolving to true if retry should proceed, false otherwise.\n */\n public async canRetry(error: unknown, retryOptions?: IRetryOptions): Promise<boolean> {\n if (this._state === ETaskState.CANCELLED || (this.externalSignal?.aborted ?? false)) {\n return false;\n }\n\n if (!retryOptions || typeof retryOptions.attempts !== \"number\") {\n return false;\n }\n\n if (this.attempt >= retryOptions.attempts) {\n return false;\n }\n\n if (typeof retryOptions.shouldRetry === \"function\") {\n try {\n const allowed = await retryOptions.shouldRetry(error, this.attempt);\n if (!allowed) {\n return false;\n }\n } catch {\n return false;\n }\n }\n\n this.attempt++;\n this._state = ETaskState.PENDING;\n this.abortController = new AbortController();\n return true;\n }\n\n /**\n * Executes the task within an allocated concurrency slot.\n *\n * @returns A promise resolving to the task result or rejecting on failure/cancellation/timeout.\n */\n public async run(): Promise<T> {\n if (this._state === ETaskState.CANCELLED) {\n throw new AhkoCancellationError(\"Task was cancelled prior to execution\");\n }\n\n this._state = ETaskState.RUNNING;\n\n const context: ITaskContext = {\n signal: this.abortController.signal,\n taskId: this.taskId,\n };\n\n let abortListener: (() => void) | undefined;\n\n const abortPromise = new Promise<never>((_, reject) => {\n abortListener = () => {\n if (this._state === ETaskState.TIMED_OUT) {\n reject(\n new AhkoTimeoutError(\n `Task execution timed out after ${this.timeoutMs}ms`,\n { timeoutMs: this.timeoutMs }\n )\n );\n } else {\n const reason = this.abortController.signal.reason;\n reject(\n new AhkoCancellationError(\"Task was cancelled during execution\", {\n cause: reason instanceof Error ? reason : undefined,\n })\n );\n }\n };\n this.abortController.signal.addEventListener(\"abort\", abortListener, { once: true });\n });\n\n let timeoutPromise: Promise<never> | undefined;\n if (this.timeoutMs !== undefined) {\n timeoutPromise = new Promise<never>((_, reject) => {\n this.timeoutTimerId = setTimeout(() => {\n if (this._state !== ETaskState.RUNNING) {\n return;\n }\n this._state = ETaskState.TIMED_OUT;\n const timeoutError = new AhkoTimeoutError(\n `Task execution timed out after ${this.timeoutMs}ms`,\n { timeoutMs: this.timeoutMs }\n );\n this.abortController.abort(timeoutError);\n reject(timeoutError);\n }, this.timeoutMs);\n });\n }\n\n let taskExecutionPromise: Promise<T>;\n try {\n taskExecutionPromise = Promise.resolve(this.task(context));\n } catch (syncError) {\n taskExecutionPromise = Promise.reject(syncError);\n }\n\n // Suppress unhandled rejection in background if task finishes or fails after timeout/cancellation\n taskExecutionPromise.catch(() => {});\n\n const racePromises: Array<Promise<T | never>> = [\n taskExecutionPromise,\n abortPromise,\n ];\n if (timeoutPromise) {\n racePromises.push(timeoutPromise);\n }\n\n try {\n const result = await Promise.race(racePromises);\n this.clearTimeoutTimer();\n if (abortListener) {\n this.abortController.signal.removeEventListener(\"abort\", abortListener);\n }\n\n if ((this._state as ETaskState) === ETaskState.TIMED_OUT) {\n throw new AhkoTimeoutError(\n `Task execution timed out after ${this.timeoutMs}ms`,\n { timeoutMs: this.timeoutMs }\n );\n }\n\n if ((this._state as ETaskState) === ETaskState.CANCELLED) {\n throw new AhkoCancellationError(\"Task was cancelled during execution\");\n }\n\n this._state = ETaskState.COMPLETED;\n return result;\n } catch (error) {\n this.clearTimeoutTimer();\n if (abortListener) {\n this.abortController.signal.removeEventListener(\"abort\", abortListener);\n }\n\n if ((this._state as ETaskState) === ETaskState.TIMED_OUT || error instanceof AhkoTimeoutError) {\n this._state = ETaskState.TIMED_OUT;\n if (error instanceof AhkoTimeoutError) {\n throw error;\n }\n throw new AhkoTimeoutError(\n `Task execution timed out after ${this.timeoutMs}ms`,\n {\n timeoutMs: this.timeoutMs,\n cause: error instanceof Error ? error : undefined,\n }\n );\n }\n\n const isCancelled =\n (this._state as ETaskState) === ETaskState.CANCELLED ||\n this.abortController.signal.aborted ||\n (this.externalSignal?.aborted ?? false);\n\n if (isCancelled) {\n this._state = ETaskState.CANCELLED;\n if (error instanceof AhkoCancellationError) {\n throw error;\n }\n throw new AhkoCancellationError(\"Task was cancelled during execution\", {\n cause: error instanceof Error ? error : undefined,\n });\n }\n\n this._state = ETaskState.FAILED;\n throw error;\n }\n }\n\n /**\n * Clears the active timeout timer.\n */\n private clearTimeoutTimer(): void {\n if (this.timeoutTimerId !== undefined) {\n clearTimeout(this.timeoutTimerId);\n this.timeoutTimerId = undefined;\n }\n }\n\n /**\n * Cancels the task, aborting pending or running execution.\n *\n * @param reason - Optional cancellation reason.\n */\n public cancel(reason?: unknown): void {\n if (\n this._state === ETaskState.COMPLETED ||\n this._state === ETaskState.FAILED ||\n this._state === ETaskState.CANCELLED ||\n this._state === ETaskState.TIMED_OUT\n ) {\n return;\n }\n\n const wasPending = this._state === ETaskState.PENDING;\n this._state = ETaskState.CANCELLED;\n this.clearTimeoutTimer();\n this.abortController.abort(reason);\n this.cleanup();\n\n if (wasPending) {\n const cancellationError = new AhkoCancellationError(\n typeof reason === \"string\" ? reason : \"Task was cancelled prior to execution\",\n { cause: reason instanceof Error ? reason : undefined }\n );\n this.rejectPromise(cancellationError);\n this.onCancel?.(this);\n }\n }\n\n /**\n * Handles external AbortSignal trigger.\n */\n private handleExternalAbort(): void {\n this.cancel(this.externalSignal?.reason);\n }\n\n /**\n * Detaches event listeners from external signal to guarantee memory safety.\n */\n public cleanup(): void {\n this.clearTimeoutTimer();\n if (this.externalSignal && this.abortListener) {\n this.externalSignal.removeEventListener(\"abort\", this.abortListener);\n }\n }\n}\n","import { AhkoConfigurationError } from \"./errors/configuration.error.js\";\nimport type { IAhkoOptions, IScheduleOptions } from \"./models/options.model.js\";\nimport type { IAhkoStats } from \"./models/stats.model.js\";\nimport { EScheduleStrategy } from \"./models/strategy.model.js\";\nimport type { ITask } from \"./models/task.model.js\";\nimport { TaskQueue } from \"./scheduler/task-queue.js\";\nimport { TaskRunner } from \"./scheduler/task-runner.js\";\n\n/**\n * Ahko — Low-energy asynchronous task scheduler.\n *\n * Coordinates execution timing, enforces concurrency limits, and cooperates\n * natively with AbortSignal cancellation.\n *\n * @example\n * ```typescript\n * import { Ahko } from \"@mrjacket/ahko\";\n *\n * const ahko = new Ahko({ concurrency: 2 });\n *\n * const result = await ahko.schedule(async ({ signal, taskId }) => {\n * const res = await fetch(\"https://api.example.com\", { signal });\n * return res.json();\n * });\n * ```\n */\nexport class Ahko {\n /** Internal queue and concurrency manager */\n private readonly queue: TaskQueue;\n\n /**\n * Initializes a new Ahko scheduler instance.\n *\n * @param options - Optional scheduler configuration.\n * @throws {AhkoConfigurationError} If concurrency is invalid (less than 1 or NaN).\n *\n * @example\n * ```typescript\n * const ahko = new Ahko({ concurrency: 4 });\n * ```\n */\n constructor(options?: IAhkoOptions) {\n this.queue = new TaskQueue(options?.concurrency);\n }\n\n /**\n * Schedules a task for execution with full return type inference.\n *\n * @template T - Inferred return type of the task.\n * @param task - Asynchronous or synchronous task function accepting an {@link ITaskContext}.\n * @param options - Task-specific scheduling options such as strategy, delay, and cancellation signal.\n * @returns A promise that resolves with the task's return value.\n *\n * @throws {AhkoConfigurationError} If the task is not a function or options are invalid.\n * @throws {AhkoCancellationError} If the task is cancelled prior to or during execution.\n *\n * @example\n * ```typescript\n * // Immediate execution (subject to concurrency)\n * const count = await ahko.schedule(async () => 42);\n *\n * // Delayed execution\n * await ahko.schedule(\n * async ({ signal }) => doWork({ signal }),\n * { strategy: \"delay\", delay: 1000 }\n * );\n * ```\n */\n public schedule<T>(task: ITask<T>, options?: IScheduleOptions): Promise<T> {\n if (typeof task !== \"function\") {\n throw new AhkoConfigurationError(\"Task must be a valid function.\");\n }\n\n const runner = new TaskRunner<T>(task, options?.signal, options?.timeoutMs);\n return this.queue.enqueue(runner, options);\n }\n\n /**\n * Convenience method to schedule a task during platform idle opportunities.\n *\n * Equivalent to calling `schedule(task, { ...options, strategy: \"idle\" })`.\n * In browsers, uses `requestIdleCallback` when available.\n * In Node.js, uses `setImmediate`.\n * Falls back to `setTimeout(..., 0)` if neither is available.\n *\n * @template T - Inferred return type of the task.\n * @param task - Task function to run when idle.\n * @param options - Scheduling options (excluding strategy).\n * @returns A promise resolving to the task's return value.\n *\n * @throws {AhkoConfigurationError} If the task is not a function or options are invalid.\n * @throws {AhkoCancellationError} If the task is cancelled prior to or during execution.\n *\n * @example\n * ```typescript\n * const result = await ahko.idle(async ({ signal }) => {\n * return computeAnalytics();\n * });\n * ```\n */\n public idle<T>(\n task: ITask<T>,\n options?: Omit<IScheduleOptions, \"strategy\">\n ): Promise<T> {\n return this.schedule(task, {\n ...options,\n strategy: EScheduleStrategy.IDLE,\n });\n }\n\n /**\n * Retrieves real-time telemetry metrics from the scheduler.\n *\n * @returns An {@link IAhkoStats} snapshot of active, pending, completed, failed, cancelled, and timed out tasks.\n *\n * @example\n * ```typescript\n * const stats = ahko.stats();\n * console.log(`Active: ${stats.activeTasks}, Pending: ${stats.pendingTasks}`);\n * ```\n */\n public stats(): IAhkoStats {\n return this.queue.getStats();\n }\n}\n","/**\n * Current version of @mrjacket/ahko package.\n */\nexport const VERSION = \"0.4.0\";\n","import { AhkoError } from \"./ahko.error.js\";\n\n/**\n * Thrown when an internal queue invariant is violated or queue limits are breached.\n */\nexport class AhkoQueueError extends AhkoError {\n /**\n * Creates a new AhkoQueueError.\n *\n * @param message - Explanation of the queue failure.\n * @param options - Standard Error options including cause.\n */\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = \"AhkoQueueError\";\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n","/**\n * Result of combining multiple AbortSignals.\n */\nexport interface ICombinedSignal {\n /**\n * The unified AbortSignal that aborts when any source signal aborts.\n */\n readonly signal: AbortSignal;\n\n /**\n * Detaches all registered event listeners from source signals to prevent memory leaks.\n */\n cleanup: () => void;\n}\n\n/**\n * Combines multiple AbortSignals into a single coordinated AbortSignal with deterministic cleanup.\n *\n * @param signals - Array of source AbortSignals (undefined entries are ignored).\n * @returns A unified signal interface with explicit cleanup callback.\n */\nexport function combineSignals(\n signals: ReadonlyArray<AbortSignal | undefined>\n): ICombinedSignal {\n const activeSignals = signals.filter(\n (signal): signal is AbortSignal => signal !== undefined\n );\n\n if (activeSignals.length === 0) {\n const controller = new AbortController();\n return {\n signal: controller.signal,\n cleanup: () => {},\n };\n }\n\n // Check if any source signal is already aborted\n const alreadyAborted = activeSignals.find((s) => s.aborted);\n if (alreadyAborted) {\n const controller = new AbortController();\n controller.abort(alreadyAborted.reason);\n return {\n signal: controller.signal,\n cleanup: () => {},\n };\n }\n\n if (activeSignals.length === 1) {\n return {\n signal: activeSignals[0],\n cleanup: () => {},\n };\n }\n\n const controller = new AbortController();\n const cleanupFns: Array<() => void> = [];\n\n const onAbort = (event: Event): void => {\n const target = event.target as AbortSignal;\n cleanup();\n controller.abort(target.reason);\n };\n\n for (const sig of activeSignals) {\n sig.addEventListener(\"abort\", onAbort, { once: true });\n cleanupFns.push(() => {\n sig.removeEventListener(\"abort\", onAbort);\n });\n }\n\n const cleanup = (): void => {\n for (const fn of cleanupFns) {\n fn();\n }\n cleanupFns.length = 0;\n };\n\n return {\n signal: controller.signal,\n cleanup,\n };\n}\n"],"mappings":"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","names":["EScheduleStrategy","ETaskState","controller"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { Ahko } from "./ahko.js";
|
|
2
2
|
export { VERSION } from "./version.js";
|
|
3
|
-
export { AhkoError, AhkoCancellationError, AhkoConfigurationError, AhkoQueueError, AhkoTimeoutError, } from "./errors/index.js";
|
|
3
|
+
export { AhkoError, AhkoCancellationError, AhkoConfigurationError, AhkoQueueError, AhkoTimeoutError, type IAhkoTimeoutErrorOptions, } from "./errors/index.js";
|
|
4
4
|
export { ETaskState, EScheduleStrategy, } from "./models/index.js";
|
|
5
5
|
export type { ITask, ITaskContext, IScheduleOptions, IAhkoOptions, IAhkoStats, TScheduleStrategy, IRetryOptions, TRetryBackoff, TRetryPredicate, } from "./models/index.js";
|
|
6
6
|
export { calculateBackoff, DEFAULT_BASE_DELAY, DEFAULT_MAX_DELAY, } from "./retry/index.js";
|
|
7
|
+
export { combineSignals, type ICombinedSignal, } from "./scheduler/signal.js";
|
package/dist/index.js
CHANGED
|
@@ -36,6 +36,26 @@ var EScheduleStrategy = /* @__PURE__ */ ((EScheduleStrategy2) => {
|
|
|
36
36
|
return EScheduleStrategy2;
|
|
37
37
|
})(EScheduleStrategy || {});
|
|
38
38
|
|
|
39
|
+
// src/errors/timeout.error.ts
|
|
40
|
+
var AhkoTimeoutError = class extends AhkoError {
|
|
41
|
+
/**
|
|
42
|
+
* The timeout threshold in milliseconds that was exceeded, if configured.
|
|
43
|
+
*/
|
|
44
|
+
timeoutMs;
|
|
45
|
+
/**
|
|
46
|
+
* Creates a new AhkoTimeoutError.
|
|
47
|
+
*
|
|
48
|
+
* @param message - Explanation of timeout expiry.
|
|
49
|
+
* @param options - Standard Error options including optional timeoutMs and cause.
|
|
50
|
+
*/
|
|
51
|
+
constructor(message = "Task execution timed out", options) {
|
|
52
|
+
super(message, options);
|
|
53
|
+
this.name = "AhkoTimeoutError";
|
|
54
|
+
this.timeoutMs = options?.timeoutMs;
|
|
55
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
39
59
|
// src/models/state.model.ts
|
|
40
60
|
var ETaskState = /* @__PURE__ */ ((ETaskState2) => {
|
|
41
61
|
ETaskState2["PENDING"] = "pending";
|
|
@@ -182,6 +202,13 @@ var TaskQueue = class {
|
|
|
182
202
|
);
|
|
183
203
|
}
|
|
184
204
|
}
|
|
205
|
+
if (options?.timeoutMs !== void 0) {
|
|
206
|
+
if (typeof options.timeoutMs !== "number" || Number.isNaN(options.timeoutMs) || !Number.isFinite(options.timeoutMs) || options.timeoutMs <= 0) {
|
|
207
|
+
throw new AhkoConfigurationError(
|
|
208
|
+
`Invalid timeoutMs "${options.timeoutMs}". timeoutMs must be a positive finite number greater than 0.`
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
185
212
|
if (options) {
|
|
186
213
|
this.runnerOptions.set(runner, options);
|
|
187
214
|
}
|
|
@@ -324,7 +351,7 @@ var TaskQueue = class {
|
|
|
324
351
|
this.scheduleRetry(runner, options);
|
|
325
352
|
return;
|
|
326
353
|
}
|
|
327
|
-
if (runner.state === "timed_out" /* TIMED_OUT */) {
|
|
354
|
+
if (runner.state === "timed_out" /* TIMED_OUT */ || error instanceof AhkoTimeoutError) {
|
|
328
355
|
this.timedOutTasks++;
|
|
329
356
|
} else {
|
|
330
357
|
this.failedTasks++;
|
|
@@ -427,6 +454,10 @@ var TaskRunner = class {
|
|
|
427
454
|
task;
|
|
428
455
|
/** User-supplied AbortSignal for external cancellation */
|
|
429
456
|
externalSignal;
|
|
457
|
+
/** Maximum execution duration allowed in milliseconds */
|
|
458
|
+
timeoutMs;
|
|
459
|
+
/** Active timeout timer identifier */
|
|
460
|
+
timeoutTimerId;
|
|
430
461
|
/** Abort event listener reference for clean detachment */
|
|
431
462
|
abortListener;
|
|
432
463
|
/** Promise resolve handler */
|
|
@@ -437,16 +468,20 @@ var TaskRunner = class {
|
|
|
437
468
|
promise;
|
|
438
469
|
/** Callback invoked when runner is cancelled while pending */
|
|
439
470
|
onCancel;
|
|
471
|
+
/** Current execution attempt count (1-indexed) */
|
|
472
|
+
attempt = 1;
|
|
440
473
|
/**
|
|
441
474
|
* Creates a new TaskRunner instance.
|
|
442
475
|
*
|
|
443
476
|
* @param task - The asynchronous work unit to run.
|
|
444
477
|
* @param externalSignal - Optional external AbortSignal to propagate.
|
|
478
|
+
* @param timeoutMs - Optional maximum execution time in milliseconds.
|
|
445
479
|
*/
|
|
446
|
-
constructor(task, externalSignal) {
|
|
480
|
+
constructor(task, externalSignal, timeoutMs) {
|
|
447
481
|
this.taskId = `task_${Date.now().toString(36)}_${(++taskIdCounter).toString(36)}_${Math.random().toString(36).slice(2, 6)}`;
|
|
448
482
|
this.task = task;
|
|
449
483
|
this.externalSignal = externalSignal;
|
|
484
|
+
this.timeoutMs = timeoutMs;
|
|
450
485
|
this.abortController = new AbortController();
|
|
451
486
|
this.promise = new Promise((resolve, reject) => {
|
|
452
487
|
this.resolvePromise = resolve;
|
|
@@ -460,6 +495,7 @@ var TaskRunner = class {
|
|
|
460
495
|
typeof reason === "string" ? reason : "Task was cancelled prior to execution",
|
|
461
496
|
{ cause: reason instanceof Error ? reason : void 0 }
|
|
462
497
|
);
|
|
498
|
+
this.abortController.abort(cancelError);
|
|
463
499
|
this.rejectPromise(cancelError);
|
|
464
500
|
} else {
|
|
465
501
|
this.abortListener = () => {
|
|
@@ -475,8 +511,6 @@ var TaskRunner = class {
|
|
|
475
511
|
get state() {
|
|
476
512
|
return this._state;
|
|
477
513
|
}
|
|
478
|
-
/** Current execution attempt count (1-indexed) */
|
|
479
|
-
attempt = 1;
|
|
480
514
|
/**
|
|
481
515
|
* Resolves the deferred promise.
|
|
482
516
|
*
|
|
@@ -496,14 +530,14 @@ var TaskRunner = class {
|
|
|
496
530
|
this.rejectPromise(reason);
|
|
497
531
|
}
|
|
498
532
|
/**
|
|
499
|
-
* Evaluates if the task should be retried following an execution failure.
|
|
533
|
+
* Evaluates if the task should be retried following an execution failure or timeout.
|
|
500
534
|
*
|
|
501
535
|
* @param error - The error encountered during the attempt.
|
|
502
536
|
* @param retryOptions - Configured retry policy.
|
|
503
537
|
* @returns A promise resolving to true if retry should proceed, false otherwise.
|
|
504
538
|
*/
|
|
505
539
|
async canRetry(error, retryOptions) {
|
|
506
|
-
if (this._state === "cancelled" /* CANCELLED */ || this.
|
|
540
|
+
if (this._state === "cancelled" /* CANCELLED */ || (this.externalSignal?.aborted ?? false)) {
|
|
507
541
|
return false;
|
|
508
542
|
}
|
|
509
543
|
if (!retryOptions || typeof retryOptions.attempts !== "number") {
|
|
@@ -524,12 +558,13 @@ var TaskRunner = class {
|
|
|
524
558
|
}
|
|
525
559
|
this.attempt++;
|
|
526
560
|
this._state = "pending" /* PENDING */;
|
|
561
|
+
this.abortController = new AbortController();
|
|
527
562
|
return true;
|
|
528
563
|
}
|
|
529
564
|
/**
|
|
530
565
|
* Executes the task within an allocated concurrency slot.
|
|
531
566
|
*
|
|
532
|
-
* @returns A promise resolving to the task result or rejecting on failure/cancellation.
|
|
567
|
+
* @returns A promise resolving to the task result or rejecting on failure/cancellation/timeout.
|
|
533
568
|
*/
|
|
534
569
|
async run() {
|
|
535
570
|
if (this._state === "cancelled" /* CANCELLED */) {
|
|
@@ -540,14 +575,100 @@ var TaskRunner = class {
|
|
|
540
575
|
signal: this.abortController.signal,
|
|
541
576
|
taskId: this.taskId
|
|
542
577
|
};
|
|
578
|
+
let abortListener;
|
|
579
|
+
const abortPromise = new Promise((_, reject) => {
|
|
580
|
+
abortListener = () => {
|
|
581
|
+
if (this._state === "timed_out" /* TIMED_OUT */) {
|
|
582
|
+
reject(
|
|
583
|
+
new AhkoTimeoutError(
|
|
584
|
+
`Task execution timed out after ${this.timeoutMs}ms`,
|
|
585
|
+
{ timeoutMs: this.timeoutMs }
|
|
586
|
+
)
|
|
587
|
+
);
|
|
588
|
+
} else {
|
|
589
|
+
const reason = this.abortController.signal.reason;
|
|
590
|
+
reject(
|
|
591
|
+
new AhkoCancellationError("Task was cancelled during execution", {
|
|
592
|
+
cause: reason instanceof Error ? reason : void 0
|
|
593
|
+
})
|
|
594
|
+
);
|
|
595
|
+
}
|
|
596
|
+
};
|
|
597
|
+
this.abortController.signal.addEventListener("abort", abortListener, { once: true });
|
|
598
|
+
});
|
|
599
|
+
let timeoutPromise;
|
|
600
|
+
if (this.timeoutMs !== void 0) {
|
|
601
|
+
timeoutPromise = new Promise((_, reject) => {
|
|
602
|
+
this.timeoutTimerId = setTimeout(() => {
|
|
603
|
+
if (this._state !== "running" /* RUNNING */) {
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
this._state = "timed_out" /* TIMED_OUT */;
|
|
607
|
+
const timeoutError = new AhkoTimeoutError(
|
|
608
|
+
`Task execution timed out after ${this.timeoutMs}ms`,
|
|
609
|
+
{ timeoutMs: this.timeoutMs }
|
|
610
|
+
);
|
|
611
|
+
this.abortController.abort(timeoutError);
|
|
612
|
+
reject(timeoutError);
|
|
613
|
+
}, this.timeoutMs);
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
let taskExecutionPromise;
|
|
617
|
+
try {
|
|
618
|
+
taskExecutionPromise = Promise.resolve(this.task(context));
|
|
619
|
+
} catch (syncError) {
|
|
620
|
+
taskExecutionPromise = Promise.reject(syncError);
|
|
621
|
+
}
|
|
622
|
+
taskExecutionPromise.catch(() => {
|
|
623
|
+
});
|
|
624
|
+
const racePromises = [
|
|
625
|
+
taskExecutionPromise,
|
|
626
|
+
abortPromise
|
|
627
|
+
];
|
|
628
|
+
if (timeoutPromise) {
|
|
629
|
+
racePromises.push(timeoutPromise);
|
|
630
|
+
}
|
|
543
631
|
try {
|
|
544
|
-
const result = await
|
|
632
|
+
const result = await Promise.race(racePromises);
|
|
633
|
+
this.clearTimeoutTimer();
|
|
634
|
+
if (abortListener) {
|
|
635
|
+
this.abortController.signal.removeEventListener("abort", abortListener);
|
|
636
|
+
}
|
|
637
|
+
if (this._state === "timed_out" /* TIMED_OUT */) {
|
|
638
|
+
throw new AhkoTimeoutError(
|
|
639
|
+
`Task execution timed out after ${this.timeoutMs}ms`,
|
|
640
|
+
{ timeoutMs: this.timeoutMs }
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
if (this._state === "cancelled" /* CANCELLED */) {
|
|
644
|
+
throw new AhkoCancellationError("Task was cancelled during execution");
|
|
645
|
+
}
|
|
545
646
|
this._state = "completed" /* COMPLETED */;
|
|
546
647
|
return result;
|
|
547
648
|
} catch (error) {
|
|
548
|
-
|
|
649
|
+
this.clearTimeoutTimer();
|
|
650
|
+
if (abortListener) {
|
|
651
|
+
this.abortController.signal.removeEventListener("abort", abortListener);
|
|
652
|
+
}
|
|
653
|
+
if (this._state === "timed_out" /* TIMED_OUT */ || error instanceof AhkoTimeoutError) {
|
|
654
|
+
this._state = "timed_out" /* TIMED_OUT */;
|
|
655
|
+
if (error instanceof AhkoTimeoutError) {
|
|
656
|
+
throw error;
|
|
657
|
+
}
|
|
658
|
+
throw new AhkoTimeoutError(
|
|
659
|
+
`Task execution timed out after ${this.timeoutMs}ms`,
|
|
660
|
+
{
|
|
661
|
+
timeoutMs: this.timeoutMs,
|
|
662
|
+
cause: error instanceof Error ? error : void 0
|
|
663
|
+
}
|
|
664
|
+
);
|
|
665
|
+
}
|
|
666
|
+
const isCancelled = this._state === "cancelled" /* CANCELLED */ || this.abortController.signal.aborted || (this.externalSignal?.aborted ?? false);
|
|
549
667
|
if (isCancelled) {
|
|
550
668
|
this._state = "cancelled" /* CANCELLED */;
|
|
669
|
+
if (error instanceof AhkoCancellationError) {
|
|
670
|
+
throw error;
|
|
671
|
+
}
|
|
551
672
|
throw new AhkoCancellationError("Task was cancelled during execution", {
|
|
552
673
|
cause: error instanceof Error ? error : void 0
|
|
553
674
|
});
|
|
@@ -556,6 +677,15 @@ var TaskRunner = class {
|
|
|
556
677
|
throw error;
|
|
557
678
|
}
|
|
558
679
|
}
|
|
680
|
+
/**
|
|
681
|
+
* Clears the active timeout timer.
|
|
682
|
+
*/
|
|
683
|
+
clearTimeoutTimer() {
|
|
684
|
+
if (this.timeoutTimerId !== void 0) {
|
|
685
|
+
clearTimeout(this.timeoutTimerId);
|
|
686
|
+
this.timeoutTimerId = void 0;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
559
689
|
/**
|
|
560
690
|
* Cancels the task, aborting pending or running execution.
|
|
561
691
|
*
|
|
@@ -567,6 +697,7 @@ var TaskRunner = class {
|
|
|
567
697
|
}
|
|
568
698
|
const wasPending = this._state === "pending" /* PENDING */;
|
|
569
699
|
this._state = "cancelled" /* CANCELLED */;
|
|
700
|
+
this.clearTimeoutTimer();
|
|
570
701
|
this.abortController.abort(reason);
|
|
571
702
|
this.cleanup();
|
|
572
703
|
if (wasPending) {
|
|
@@ -588,6 +719,7 @@ var TaskRunner = class {
|
|
|
588
719
|
* Detaches event listeners from external signal to guarantee memory safety.
|
|
589
720
|
*/
|
|
590
721
|
cleanup() {
|
|
722
|
+
this.clearTimeoutTimer();
|
|
591
723
|
if (this.externalSignal && this.abortListener) {
|
|
592
724
|
this.externalSignal.removeEventListener("abort", this.abortListener);
|
|
593
725
|
}
|
|
@@ -639,7 +771,7 @@ var Ahko = class {
|
|
|
639
771
|
if (typeof task !== "function") {
|
|
640
772
|
throw new AhkoConfigurationError("Task must be a valid function.");
|
|
641
773
|
}
|
|
642
|
-
const runner = new TaskRunner(task, options?.signal);
|
|
774
|
+
const runner = new TaskRunner(task, options?.signal, options?.timeoutMs);
|
|
643
775
|
return this.queue.enqueue(runner, options);
|
|
644
776
|
}
|
|
645
777
|
/**
|
|
@@ -688,7 +820,7 @@ var Ahko = class {
|
|
|
688
820
|
};
|
|
689
821
|
|
|
690
822
|
// src/version.ts
|
|
691
|
-
var VERSION = "0.
|
|
823
|
+
var VERSION = "0.4.0";
|
|
692
824
|
|
|
693
825
|
// src/errors/queue.error.ts
|
|
694
826
|
var AhkoQueueError = class extends AhkoError {
|
|
@@ -705,20 +837,60 @@ var AhkoQueueError = class extends AhkoError {
|
|
|
705
837
|
}
|
|
706
838
|
};
|
|
707
839
|
|
|
708
|
-
// src/
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
840
|
+
// src/scheduler/signal.ts
|
|
841
|
+
function combineSignals(signals) {
|
|
842
|
+
const activeSignals = signals.filter(
|
|
843
|
+
(signal) => signal !== void 0
|
|
844
|
+
);
|
|
845
|
+
if (activeSignals.length === 0) {
|
|
846
|
+
const controller2 = new AbortController();
|
|
847
|
+
return {
|
|
848
|
+
signal: controller2.signal,
|
|
849
|
+
cleanup: () => {
|
|
850
|
+
}
|
|
851
|
+
};
|
|
720
852
|
}
|
|
721
|
-
|
|
853
|
+
const alreadyAborted = activeSignals.find((s) => s.aborted);
|
|
854
|
+
if (alreadyAborted) {
|
|
855
|
+
const controller2 = new AbortController();
|
|
856
|
+
controller2.abort(alreadyAborted.reason);
|
|
857
|
+
return {
|
|
858
|
+
signal: controller2.signal,
|
|
859
|
+
cleanup: () => {
|
|
860
|
+
}
|
|
861
|
+
};
|
|
862
|
+
}
|
|
863
|
+
if (activeSignals.length === 1) {
|
|
864
|
+
return {
|
|
865
|
+
signal: activeSignals[0],
|
|
866
|
+
cleanup: () => {
|
|
867
|
+
}
|
|
868
|
+
};
|
|
869
|
+
}
|
|
870
|
+
const controller = new AbortController();
|
|
871
|
+
const cleanupFns = [];
|
|
872
|
+
const onAbort = (event) => {
|
|
873
|
+
const target = event.target;
|
|
874
|
+
cleanup();
|
|
875
|
+
controller.abort(target.reason);
|
|
876
|
+
};
|
|
877
|
+
for (const sig of activeSignals) {
|
|
878
|
+
sig.addEventListener("abort", onAbort, { once: true });
|
|
879
|
+
cleanupFns.push(() => {
|
|
880
|
+
sig.removeEventListener("abort", onAbort);
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
const cleanup = () => {
|
|
884
|
+
for (const fn of cleanupFns) {
|
|
885
|
+
fn();
|
|
886
|
+
}
|
|
887
|
+
cleanupFns.length = 0;
|
|
888
|
+
};
|
|
889
|
+
return {
|
|
890
|
+
signal: controller.signal,
|
|
891
|
+
cleanup
|
|
892
|
+
};
|
|
893
|
+
}
|
|
722
894
|
export {
|
|
723
895
|
Ahko,
|
|
724
896
|
AhkoCancellationError,
|
|
@@ -731,6 +903,7 @@ export {
|
|
|
731
903
|
EScheduleStrategy,
|
|
732
904
|
ETaskState,
|
|
733
905
|
VERSION,
|
|
734
|
-
calculateBackoff
|
|
906
|
+
calculateBackoff,
|
|
907
|
+
combineSignals
|
|
735
908
|
};
|
|
736
909
|
//# sourceMappingURL=index.js.map
|