@mrjacket/ahko 0.2.0 → 0.3.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 CHANGED
@@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.3.0] - 2026-09-22 — Retry & Backoff
9
+
10
+ ### Added
11
+ - Automatic retry engine supporting `attempts`, exponential/linear backoff, and full jitter.
12
+ - Retry filtering via `shouldRetry` predicate `(error, attempt) => boolean | Promise<boolean>`.
13
+ - Concurrency slot release during backoff delay to prevent capacity starvation.
14
+ - Cancellation safety during backoff delay (clears timers immediately, rejects with `AhkoCancellationError`, and halts remaining retries).
15
+ - Public retry models (`IRetryOptions`, `TRetryBackoff`, `TRetryPredicate`) and backoff calculation utilities.
16
+ - Comprehensive unit test suite covering backoff calculations, jitter, slot release, predicates, and cancellations.
17
+
18
+ ---
19
+
8
20
  ## [0.2.0] - 2026-09-22 — Idle Scheduling
9
21
 
10
22
  ### Added
package/README.md CHANGED
@@ -97,7 +97,51 @@ await ahko.schedule(
97
97
  );
98
98
  ```
99
99
 
100
- ### 3. First-Class Cancellation (`AbortSignal`)
100
+ ### 3. Opportunistic Idle Execution
101
+
102
+ Schedule work to run when the runtime is idle (using browser `requestIdleCallback`, Node.js `setImmediate`, or universal fallback):
103
+
104
+ ```typescript
105
+ // Dedicated convenience method
106
+ await ahko.idle(async ({ signal }) => {
107
+ await computeBackgroundAnalytics({ signal });
108
+ });
109
+
110
+ // Or via schedule options with maximum wait timeout
111
+ await ahko.schedule(
112
+ async ({ signal }) => {
113
+ await performLowPriorityWork({ signal });
114
+ },
115
+ {
116
+ strategy: "idle",
117
+ idleTimeout: 5000, // Forces execution if idle window doesn't appear in 5s
118
+ }
119
+ );
120
+ ```
121
+
122
+ ### 4. Resilient Retries & Backoff
123
+
124
+ Automatically retry failed tasks with configurable exponential or linear backoff and full jitter:
125
+
126
+ ```typescript
127
+ const result = await ahko.schedule(
128
+ async ({ signal }) => {
129
+ return callExternalService({ signal });
130
+ },
131
+ {
132
+ retry: {
133
+ attempts: 3, // 1 initial run + up to 2 retries
134
+ backoff: "exponential", // "exponential" | "linear" | "none"
135
+ baseDelay: 250, // starting delay in ms
136
+ maxDelay: 5000, // maximum delay cap in ms
137
+ jitter: true, // randomize backoff to prevent thundering herds
138
+ shouldRetry: (error) => isNetworkError(error),
139
+ },
140
+ }
141
+ );
142
+ ```
143
+
144
+ ### 5. First-Class Cancellation (`AbortSignal`)
101
145
 
102
146
  AHKO provides native, cooperative cancellation:
103
147
 
@@ -116,7 +160,7 @@ const taskPromise = ahko.schedule(
116
160
  controller.abort();
117
161
  ```
118
162
 
119
- ### 4. Telemetry (`stats`)
163
+ ### 6. Telemetry (`stats`)
120
164
 
121
165
  Inspect real-time scheduler state without synthetic metrics:
122
166
 
@@ -137,6 +181,19 @@ console.log(stats);
137
181
 
138
182
  ---
139
183
 
184
+ ## Documentation
185
+
186
+ Comprehensive guides and technical documentation are available in the [`docs/`](./docs) directory:
187
+
188
+ | Document | Description |
189
+ |---|---|
190
+ | [**Getting Started**](./docs/getting-started.md) | Quickstart guide, installation, and fundamental usage patterns. |
191
+ | [**Library API**](./docs/library.md) | Complete programmatic API reference, TypeScript interfaces, and options. |
192
+ | [**Architecture**](./docs/architecture.md) | Architectural specifications, lifecycle state machine, and design decisions. |
193
+ | [**Roadmap**](./docs/roadmap.md) | Milestone progression from 0.1.0 through 1.0.0. |
194
+
195
+ ---
196
+
140
197
  ## API Reference
141
198
 
142
199
  ### `new Ahko(options?: IAhkoOptions)`
@@ -151,19 +208,28 @@ Creates an AHKO scheduler instance.
151
208
 
152
209
  Schedules an asynchronous task with full return type inference.
153
210
 
154
- - `task`: `(context: ITaskContext) => Promise<T> | T`
155
- - `options.strategy`: `"immediate"` (default) or `"delay"`.
156
- - `options.delay`: Delay in milliseconds when strategy is `"delay"`.
157
- - `options.signal`: Optional `AbortSignal` for cancellation.
211
+ | Option | Type | Default | Description |
212
+ |---|---|---|---|
213
+ | `strategy` | `"immediate" \| "delay" \| "idle"` | `"immediate"` | Scheduling execution strategy. |
214
+ | `delay` | `number` | `0` | Delay in milliseconds when strategy is `"delay"`. |
215
+ | `idleTimeout` | `number` | `undefined` | Maximum time to wait for idle window before forcing queue entry. |
216
+ | `retry` | `IRetryOptions` | `undefined` | Automatic retry policy (attempts, backoff, jitter, predicate). |
217
+ | `signal` | `AbortSignal` | `undefined` | Optional external `AbortSignal` for cooperative cancellation. |
218
+
219
+ ### `ahko.idle<T>(task: ITask<T>, options?: Omit<IScheduleOptions, "strategy">): Promise<T>`
220
+
221
+ Convenience method scheduling a task under `strategy: "idle"`.
158
222
 
159
223
  ### `ahko.stats(): IAhkoStats`
160
224
 
161
- Returns a snapshot of current task counters and capacity.
225
+ Returns a snapshot of current task counters and queue capacity.
162
226
 
163
227
  ---
164
228
 
165
229
  ## Errors
166
230
 
231
+ All scheduler errors inherit from `AhkoError`:
232
+
167
233
  - `AhkoError`: Base class for all scheduler errors.
168
234
  - `AhkoCancellationError`: Thrown when a task is aborted.
169
235
  - `AhkoConfigurationError`: Thrown when invalid options are provided.
package/dist/index.cjs CHANGED
@@ -26,9 +26,12 @@ __export(src_exports, {
26
26
  AhkoError: () => AhkoError,
27
27
  AhkoQueueError: () => AhkoQueueError,
28
28
  AhkoTimeoutError: () => AhkoTimeoutError,
29
+ DEFAULT_BASE_DELAY: () => DEFAULT_BASE_DELAY,
30
+ DEFAULT_MAX_DELAY: () => DEFAULT_MAX_DELAY,
29
31
  EScheduleStrategy: () => EScheduleStrategy,
30
32
  ETaskState: () => ETaskState,
31
- VERSION: () => VERSION
33
+ VERSION: () => VERSION,
34
+ calculateBackoff: () => calculateBackoff
32
35
  });
33
36
  module.exports = __toCommonJS(src_exports);
34
37
 
@@ -81,6 +84,31 @@ var ETaskState = /* @__PURE__ */ ((ETaskState2) => {
81
84
  return ETaskState2;
82
85
  })(ETaskState || {});
83
86
 
87
+ // src/retry/backoff.ts
88
+ var DEFAULT_BASE_DELAY = 250;
89
+ var DEFAULT_MAX_DELAY = 1e4;
90
+ function calculateBackoff(attempt, options, randomFn = Math.random) {
91
+ const backoff = options?.backoff ?? "exponential";
92
+ if (backoff === "none") {
93
+ return 0;
94
+ }
95
+ const baseDelay = typeof options?.baseDelay === "number" && !Number.isNaN(options.baseDelay) && options.baseDelay >= 0 ? options.baseDelay : DEFAULT_BASE_DELAY;
96
+ const maxDelay = typeof options?.maxDelay === "number" && !Number.isNaN(options.maxDelay) && options.maxDelay >= baseDelay ? options.maxDelay : Math.max(DEFAULT_MAX_DELAY, baseDelay);
97
+ let calculatedDelay;
98
+ if (backoff === "linear") {
99
+ calculatedDelay = baseDelay * Math.max(1, attempt);
100
+ } else {
101
+ const exponent = Math.max(0, attempt - 1);
102
+ const factor = exponent > 30 ? 2 ** 30 : 2 ** exponent;
103
+ calculatedDelay = baseDelay * factor;
104
+ }
105
+ const cappedDelay = Math.min(calculatedDelay, maxDelay);
106
+ if (options?.jitter) {
107
+ return Math.floor(randomFn() * (cappedDelay + 1));
108
+ }
109
+ return Math.floor(cappedDelay);
110
+ }
111
+
84
112
  // src/scheduler/idle-scheduler.ts
85
113
  var IdleScheduler = class {
86
114
  /**
@@ -132,6 +160,10 @@ var TaskQueue = class {
132
160
  delayedEntries = /* @__PURE__ */ new Set();
133
161
  /** Set of tasks currently awaiting an idle opportunity */
134
162
  idleEntries = /* @__PURE__ */ new Set();
163
+ /** Set of tasks currently awaiting a retry backoff timer */
164
+ retryEntries = /* @__PURE__ */ new Set();
165
+ /** WeakMap associating task runners with their scheduling options */
166
+ runnerOptions = /* @__PURE__ */ new WeakMap();
135
167
  /** Cumulative completed tasks counter */
136
168
  completedTasks = 0;
137
169
  /** Cumulative failed tasks counter */
@@ -170,6 +202,26 @@ var TaskQueue = class {
170
202
  `Unsupported schedule strategy "${String(strategy)}". Supported strategies: "immediate", "delay", "idle".`
171
203
  );
172
204
  }
205
+ if (options?.retry) {
206
+ if (typeof options.retry.attempts !== "number" || Number.isNaN(options.retry.attempts) || options.retry.attempts < 1 || !Number.isInteger(options.retry.attempts)) {
207
+ throw new AhkoConfigurationError(
208
+ `Invalid retry attempts "${options.retry.attempts}". attempts must be an integer greater than or equal to 1.`
209
+ );
210
+ }
211
+ if (options.retry.baseDelay !== void 0 && (typeof options.retry.baseDelay !== "number" || Number.isNaN(options.retry.baseDelay) || options.retry.baseDelay < 0)) {
212
+ throw new AhkoConfigurationError(
213
+ `Invalid retry baseDelay "${options.retry.baseDelay}". baseDelay must be a non-negative number in milliseconds.`
214
+ );
215
+ }
216
+ if (options.retry.maxDelay !== void 0 && (typeof options.retry.maxDelay !== "number" || Number.isNaN(options.retry.maxDelay) || options.retry.maxDelay < 0)) {
217
+ throw new AhkoConfigurationError(
218
+ `Invalid retry maxDelay "${options.retry.maxDelay}". maxDelay must be a non-negative number in milliseconds.`
219
+ );
220
+ }
221
+ }
222
+ if (options) {
223
+ this.runnerOptions.set(runner, options);
224
+ }
173
225
  if (runner.state === "cancelled" /* CANCELLED */) {
174
226
  this.cancelledTasks++;
175
227
  return runner.promise;
@@ -286,28 +338,86 @@ var TaskQueue = class {
286
338
  }
287
339
  /**
288
340
  * Internal execution of an active task runner.
289
- * Settle caller promise strictly after stats and active status are updated.
290
341
  */
291
342
  async executeRunner(runner) {
343
+ const options = this.runnerOptions.get(runner);
292
344
  try {
293
345
  const result = await runner.run();
294
346
  this.completedTasks++;
295
347
  this.activeRunners.delete(runner);
348
+ this.runnerOptions.delete(runner);
296
349
  runner.resolve(result);
297
350
  } catch (error) {
298
351
  if (runner.state === "cancelled" /* CANCELLED */) {
299
352
  this.cancelledTasks++;
300
- } else if (runner.state === "timed_out" /* TIMED_OUT */) {
353
+ this.activeRunners.delete(runner);
354
+ this.runnerOptions.delete(runner);
355
+ runner.reject(error);
356
+ return;
357
+ }
358
+ const shouldRetry = await runner.canRetry(error, options?.retry);
359
+ if (shouldRetry) {
360
+ this.activeRunners.delete(runner);
361
+ this.scheduleRetry(runner, options);
362
+ return;
363
+ }
364
+ if (runner.state === "timed_out" /* TIMED_OUT */) {
301
365
  this.timedOutTasks++;
302
366
  } else {
303
367
  this.failedTasks++;
304
368
  }
305
369
  this.activeRunners.delete(runner);
370
+ this.runnerOptions.delete(runner);
306
371
  runner.reject(error);
307
372
  } finally {
308
373
  this.pump();
309
374
  }
310
375
  }
376
+ /**
377
+ * Schedules a retry attempt following backoff delay,
378
+ * without holding a concurrency slot.
379
+ */
380
+ scheduleRetry(runner, options) {
381
+ const backoffDelay = calculateBackoff(runner.attempt - 1, options?.retry);
382
+ if (backoffDelay === 0) {
383
+ runner.onCancel = () => {
384
+ const index = this.queue.indexOf(runner);
385
+ if (index !== -1) {
386
+ this.queue.splice(index, 1);
387
+ this.cancelledTasks++;
388
+ }
389
+ };
390
+ this.queue.push(runner);
391
+ this.pump();
392
+ return;
393
+ }
394
+ const retryEntry = {
395
+ runner,
396
+ timerId: setTimeout(() => {
397
+ this.retryEntries.delete(retryEntry);
398
+ if (runner.state === "cancelled" /* CANCELLED */) {
399
+ return;
400
+ }
401
+ runner.onCancel = () => {
402
+ const index = this.queue.indexOf(runner);
403
+ if (index !== -1) {
404
+ this.queue.splice(index, 1);
405
+ this.cancelledTasks++;
406
+ }
407
+ };
408
+ this.queue.push(runner);
409
+ this.pump();
410
+ }, backoffDelay)
411
+ };
412
+ this.retryEntries.add(retryEntry);
413
+ runner.onCancel = () => {
414
+ if (this.retryEntries.has(retryEntry)) {
415
+ clearTimeout(retryEntry.timerId);
416
+ this.retryEntries.delete(retryEntry);
417
+ this.cancelledTasks++;
418
+ }
419
+ };
420
+ }
311
421
  /**
312
422
  * Returns telemetry snapshot for the scheduler.
313
423
  *
@@ -316,7 +426,7 @@ var TaskQueue = class {
316
426
  getStats() {
317
427
  return Object.freeze({
318
428
  activeTasks: this.activeRunners.size,
319
- pendingTasks: this.queue.length + this.delayedEntries.size + this.idleEntries.size,
429
+ pendingTasks: this.queue.length + this.delayedEntries.size + this.idleEntries.size + this.retryEntries.size,
320
430
  completedTasks: this.completedTasks,
321
431
  failedTasks: this.failedTasks,
322
432
  cancelledTasks: this.cancelledTasks,
@@ -402,12 +512,15 @@ var TaskRunner = class {
402
512
  get state() {
403
513
  return this._state;
404
514
  }
515
+ /** Current execution attempt count (1-indexed) */
516
+ attempt = 1;
405
517
  /**
406
518
  * Resolves the deferred promise.
407
519
  *
408
520
  * @param value - Value to resolve with.
409
521
  */
410
522
  resolve(value) {
523
+ this.cleanup();
411
524
  this.resolvePromise(value);
412
525
  }
413
526
  /**
@@ -416,8 +529,40 @@ var TaskRunner = class {
416
529
  * @param reason - Reason to reject with.
417
530
  */
418
531
  reject(reason) {
532
+ this.cleanup();
419
533
  this.rejectPromise(reason);
420
534
  }
535
+ /**
536
+ * Evaluates if the task should be retried following an execution failure.
537
+ *
538
+ * @param error - The error encountered during the attempt.
539
+ * @param retryOptions - Configured retry policy.
540
+ * @returns A promise resolving to true if retry should proceed, false otherwise.
541
+ */
542
+ async canRetry(error, retryOptions) {
543
+ if (this._state === "cancelled" /* CANCELLED */ || this.abortController.signal.aborted) {
544
+ return false;
545
+ }
546
+ if (!retryOptions || typeof retryOptions.attempts !== "number") {
547
+ return false;
548
+ }
549
+ if (this.attempt >= retryOptions.attempts) {
550
+ return false;
551
+ }
552
+ if (typeof retryOptions.shouldRetry === "function") {
553
+ try {
554
+ const allowed = await retryOptions.shouldRetry(error, this.attempt);
555
+ if (!allowed) {
556
+ return false;
557
+ }
558
+ } catch {
559
+ return false;
560
+ }
561
+ }
562
+ this.attempt++;
563
+ this._state = "pending" /* PENDING */;
564
+ return true;
565
+ }
421
566
  /**
422
567
  * Executes the task within an allocated concurrency slot.
423
568
  *
@@ -435,10 +580,8 @@ var TaskRunner = class {
435
580
  try {
436
581
  const result = await this.task(context);
437
582
  this._state = "completed" /* COMPLETED */;
438
- this.cleanup();
439
583
  return result;
440
584
  } catch (error) {
441
- this.cleanup();
442
585
  const isCancelled = this._state === "cancelled" /* CANCELLED */ || this.abortController.signal.aborted;
443
586
  if (isCancelled) {
444
587
  this._state = "cancelled" /* CANCELLED */;
@@ -582,7 +725,7 @@ var Ahko = class {
582
725
  };
583
726
 
584
727
  // src/version.ts
585
- var VERSION = "0.2.0";
728
+ var VERSION = "0.3.0";
586
729
 
587
730
  // src/errors/queue.error.ts
588
731
  var AhkoQueueError = class extends AhkoError {
@@ -621,8 +764,11 @@ var AhkoTimeoutError = class extends AhkoError {
621
764
  AhkoError,
622
765
  AhkoQueueError,
623
766
  AhkoTimeoutError,
767
+ DEFAULT_BASE_DELAY,
768
+ DEFAULT_MAX_DELAY,
624
769
  EScheduleStrategy,
625
770
  ETaskState,
626
- VERSION
771
+ VERSION,
772
+ calculateBackoff
627
773
  });
628
774
  //# sourceMappingURL=index.cjs.map
@@ -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/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} from \"./models/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","/**\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 { 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 * 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 /** 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 (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 * Settle caller promise strictly after stats and active status are updated.\n */\n private async executeRunner(runner: TaskRunner<unknown>): Promise<void> {\n try {\n const result = await runner.run();\n this.completedTasks++;\n this.activeRunners.delete(runner);\n runner.resolve(result);\n } catch (error) {\n if (runner.state === ETaskState.CANCELLED) {\n this.cancelledTasks++;\n } else if (runner.state === ETaskState.TIMED_OUT) {\n this.timedOutTasks++;\n } else {\n this.failedTasks++;\n }\n this.activeRunners.delete(runner);\n runner.reject(error);\n } finally {\n this.pump();\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: this.queue.length + this.delayedEntries.size + this.idleEntries.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 { 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 /**\n * Resolves the deferred promise.\n *\n * @param value - Value to resolve with.\n */\n public resolve(value: T): void {\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.rejectPromise(reason);\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 this.cleanup();\n return result;\n } catch (error) {\n this.cleanup();\n\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.2.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/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"]}
package/dist/index.d.ts CHANGED
@@ -2,4 +2,5 @@ export { Ahko } from "./ahko.js";
2
2
  export { VERSION } from "./version.js";
3
3
  export { AhkoError, AhkoCancellationError, AhkoConfigurationError, AhkoQueueError, AhkoTimeoutError, } from "./errors/index.js";
4
4
  export { ETaskState, EScheduleStrategy, } from "./models/index.js";
5
- export type { ITask, ITaskContext, IScheduleOptions, IAhkoOptions, IAhkoStats, TScheduleStrategy, } from "./models/index.js";
5
+ export type { ITask, ITaskContext, IScheduleOptions, IAhkoOptions, IAhkoStats, TScheduleStrategy, IRetryOptions, TRetryBackoff, TRetryPredicate, } from "./models/index.js";
6
+ export { calculateBackoff, DEFAULT_BASE_DELAY, DEFAULT_MAX_DELAY, } from "./retry/index.js";
package/dist/index.js CHANGED
@@ -47,6 +47,31 @@ var ETaskState = /* @__PURE__ */ ((ETaskState2) => {
47
47
  return ETaskState2;
48
48
  })(ETaskState || {});
49
49
 
50
+ // src/retry/backoff.ts
51
+ var DEFAULT_BASE_DELAY = 250;
52
+ var DEFAULT_MAX_DELAY = 1e4;
53
+ function calculateBackoff(attempt, options, randomFn = Math.random) {
54
+ const backoff = options?.backoff ?? "exponential";
55
+ if (backoff === "none") {
56
+ return 0;
57
+ }
58
+ const baseDelay = typeof options?.baseDelay === "number" && !Number.isNaN(options.baseDelay) && options.baseDelay >= 0 ? options.baseDelay : DEFAULT_BASE_DELAY;
59
+ const maxDelay = typeof options?.maxDelay === "number" && !Number.isNaN(options.maxDelay) && options.maxDelay >= baseDelay ? options.maxDelay : Math.max(DEFAULT_MAX_DELAY, baseDelay);
60
+ let calculatedDelay;
61
+ if (backoff === "linear") {
62
+ calculatedDelay = baseDelay * Math.max(1, attempt);
63
+ } else {
64
+ const exponent = Math.max(0, attempt - 1);
65
+ const factor = exponent > 30 ? 2 ** 30 : 2 ** exponent;
66
+ calculatedDelay = baseDelay * factor;
67
+ }
68
+ const cappedDelay = Math.min(calculatedDelay, maxDelay);
69
+ if (options?.jitter) {
70
+ return Math.floor(randomFn() * (cappedDelay + 1));
71
+ }
72
+ return Math.floor(cappedDelay);
73
+ }
74
+
50
75
  // src/scheduler/idle-scheduler.ts
51
76
  var IdleScheduler = class {
52
77
  /**
@@ -98,6 +123,10 @@ var TaskQueue = class {
98
123
  delayedEntries = /* @__PURE__ */ new Set();
99
124
  /** Set of tasks currently awaiting an idle opportunity */
100
125
  idleEntries = /* @__PURE__ */ new Set();
126
+ /** Set of tasks currently awaiting a retry backoff timer */
127
+ retryEntries = /* @__PURE__ */ new Set();
128
+ /** WeakMap associating task runners with their scheduling options */
129
+ runnerOptions = /* @__PURE__ */ new WeakMap();
101
130
  /** Cumulative completed tasks counter */
102
131
  completedTasks = 0;
103
132
  /** Cumulative failed tasks counter */
@@ -136,6 +165,26 @@ var TaskQueue = class {
136
165
  `Unsupported schedule strategy "${String(strategy)}". Supported strategies: "immediate", "delay", "idle".`
137
166
  );
138
167
  }
168
+ if (options?.retry) {
169
+ if (typeof options.retry.attempts !== "number" || Number.isNaN(options.retry.attempts) || options.retry.attempts < 1 || !Number.isInteger(options.retry.attempts)) {
170
+ throw new AhkoConfigurationError(
171
+ `Invalid retry attempts "${options.retry.attempts}". attempts must be an integer greater than or equal to 1.`
172
+ );
173
+ }
174
+ if (options.retry.baseDelay !== void 0 && (typeof options.retry.baseDelay !== "number" || Number.isNaN(options.retry.baseDelay) || options.retry.baseDelay < 0)) {
175
+ throw new AhkoConfigurationError(
176
+ `Invalid retry baseDelay "${options.retry.baseDelay}". baseDelay must be a non-negative number in milliseconds.`
177
+ );
178
+ }
179
+ if (options.retry.maxDelay !== void 0 && (typeof options.retry.maxDelay !== "number" || Number.isNaN(options.retry.maxDelay) || options.retry.maxDelay < 0)) {
180
+ throw new AhkoConfigurationError(
181
+ `Invalid retry maxDelay "${options.retry.maxDelay}". maxDelay must be a non-negative number in milliseconds.`
182
+ );
183
+ }
184
+ }
185
+ if (options) {
186
+ this.runnerOptions.set(runner, options);
187
+ }
139
188
  if (runner.state === "cancelled" /* CANCELLED */) {
140
189
  this.cancelledTasks++;
141
190
  return runner.promise;
@@ -252,28 +301,86 @@ var TaskQueue = class {
252
301
  }
253
302
  /**
254
303
  * Internal execution of an active task runner.
255
- * Settle caller promise strictly after stats and active status are updated.
256
304
  */
257
305
  async executeRunner(runner) {
306
+ const options = this.runnerOptions.get(runner);
258
307
  try {
259
308
  const result = await runner.run();
260
309
  this.completedTasks++;
261
310
  this.activeRunners.delete(runner);
311
+ this.runnerOptions.delete(runner);
262
312
  runner.resolve(result);
263
313
  } catch (error) {
264
314
  if (runner.state === "cancelled" /* CANCELLED */) {
265
315
  this.cancelledTasks++;
266
- } else if (runner.state === "timed_out" /* TIMED_OUT */) {
316
+ this.activeRunners.delete(runner);
317
+ this.runnerOptions.delete(runner);
318
+ runner.reject(error);
319
+ return;
320
+ }
321
+ const shouldRetry = await runner.canRetry(error, options?.retry);
322
+ if (shouldRetry) {
323
+ this.activeRunners.delete(runner);
324
+ this.scheduleRetry(runner, options);
325
+ return;
326
+ }
327
+ if (runner.state === "timed_out" /* TIMED_OUT */) {
267
328
  this.timedOutTasks++;
268
329
  } else {
269
330
  this.failedTasks++;
270
331
  }
271
332
  this.activeRunners.delete(runner);
333
+ this.runnerOptions.delete(runner);
272
334
  runner.reject(error);
273
335
  } finally {
274
336
  this.pump();
275
337
  }
276
338
  }
339
+ /**
340
+ * Schedules a retry attempt following backoff delay,
341
+ * without holding a concurrency slot.
342
+ */
343
+ scheduleRetry(runner, options) {
344
+ const backoffDelay = calculateBackoff(runner.attempt - 1, options?.retry);
345
+ if (backoffDelay === 0) {
346
+ runner.onCancel = () => {
347
+ const index = this.queue.indexOf(runner);
348
+ if (index !== -1) {
349
+ this.queue.splice(index, 1);
350
+ this.cancelledTasks++;
351
+ }
352
+ };
353
+ this.queue.push(runner);
354
+ this.pump();
355
+ return;
356
+ }
357
+ const retryEntry = {
358
+ runner,
359
+ timerId: setTimeout(() => {
360
+ this.retryEntries.delete(retryEntry);
361
+ if (runner.state === "cancelled" /* CANCELLED */) {
362
+ return;
363
+ }
364
+ runner.onCancel = () => {
365
+ const index = this.queue.indexOf(runner);
366
+ if (index !== -1) {
367
+ this.queue.splice(index, 1);
368
+ this.cancelledTasks++;
369
+ }
370
+ };
371
+ this.queue.push(runner);
372
+ this.pump();
373
+ }, backoffDelay)
374
+ };
375
+ this.retryEntries.add(retryEntry);
376
+ runner.onCancel = () => {
377
+ if (this.retryEntries.has(retryEntry)) {
378
+ clearTimeout(retryEntry.timerId);
379
+ this.retryEntries.delete(retryEntry);
380
+ this.cancelledTasks++;
381
+ }
382
+ };
383
+ }
277
384
  /**
278
385
  * Returns telemetry snapshot for the scheduler.
279
386
  *
@@ -282,7 +389,7 @@ var TaskQueue = class {
282
389
  getStats() {
283
390
  return Object.freeze({
284
391
  activeTasks: this.activeRunners.size,
285
- pendingTasks: this.queue.length + this.delayedEntries.size + this.idleEntries.size,
392
+ pendingTasks: this.queue.length + this.delayedEntries.size + this.idleEntries.size + this.retryEntries.size,
286
393
  completedTasks: this.completedTasks,
287
394
  failedTasks: this.failedTasks,
288
395
  cancelledTasks: this.cancelledTasks,
@@ -368,12 +475,15 @@ var TaskRunner = class {
368
475
  get state() {
369
476
  return this._state;
370
477
  }
478
+ /** Current execution attempt count (1-indexed) */
479
+ attempt = 1;
371
480
  /**
372
481
  * Resolves the deferred promise.
373
482
  *
374
483
  * @param value - Value to resolve with.
375
484
  */
376
485
  resolve(value) {
486
+ this.cleanup();
377
487
  this.resolvePromise(value);
378
488
  }
379
489
  /**
@@ -382,8 +492,40 @@ var TaskRunner = class {
382
492
  * @param reason - Reason to reject with.
383
493
  */
384
494
  reject(reason) {
495
+ this.cleanup();
385
496
  this.rejectPromise(reason);
386
497
  }
498
+ /**
499
+ * Evaluates if the task should be retried following an execution failure.
500
+ *
501
+ * @param error - The error encountered during the attempt.
502
+ * @param retryOptions - Configured retry policy.
503
+ * @returns A promise resolving to true if retry should proceed, false otherwise.
504
+ */
505
+ async canRetry(error, retryOptions) {
506
+ if (this._state === "cancelled" /* CANCELLED */ || this.abortController.signal.aborted) {
507
+ return false;
508
+ }
509
+ if (!retryOptions || typeof retryOptions.attempts !== "number") {
510
+ return false;
511
+ }
512
+ if (this.attempt >= retryOptions.attempts) {
513
+ return false;
514
+ }
515
+ if (typeof retryOptions.shouldRetry === "function") {
516
+ try {
517
+ const allowed = await retryOptions.shouldRetry(error, this.attempt);
518
+ if (!allowed) {
519
+ return false;
520
+ }
521
+ } catch {
522
+ return false;
523
+ }
524
+ }
525
+ this.attempt++;
526
+ this._state = "pending" /* PENDING */;
527
+ return true;
528
+ }
387
529
  /**
388
530
  * Executes the task within an allocated concurrency slot.
389
531
  *
@@ -401,10 +543,8 @@ var TaskRunner = class {
401
543
  try {
402
544
  const result = await this.task(context);
403
545
  this._state = "completed" /* COMPLETED */;
404
- this.cleanup();
405
546
  return result;
406
547
  } catch (error) {
407
- this.cleanup();
408
548
  const isCancelled = this._state === "cancelled" /* CANCELLED */ || this.abortController.signal.aborted;
409
549
  if (isCancelled) {
410
550
  this._state = "cancelled" /* CANCELLED */;
@@ -548,7 +688,7 @@ var Ahko = class {
548
688
  };
549
689
 
550
690
  // src/version.ts
551
- var VERSION = "0.2.0";
691
+ var VERSION = "0.3.0";
552
692
 
553
693
  // src/errors/queue.error.ts
554
694
  var AhkoQueueError = class extends AhkoError {
@@ -586,8 +726,11 @@ export {
586
726
  AhkoError,
587
727
  AhkoQueueError,
588
728
  AhkoTimeoutError,
729
+ DEFAULT_BASE_DELAY,
730
+ DEFAULT_MAX_DELAY,
589
731
  EScheduleStrategy,
590
732
  ETaskState,
591
- VERSION
733
+ VERSION,
734
+ calculateBackoff
592
735
  };
593
736
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors/ahko.error.ts","../src/errors/configuration.error.ts","../src/models/strategy.model.ts","../src/models/state.model.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":["/**\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","/**\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 { 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 * 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 /** 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 (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 * Settle caller promise strictly after stats and active status are updated.\n */\n private async executeRunner(runner: TaskRunner<unknown>): Promise<void> {\n try {\n const result = await runner.run();\n this.completedTasks++;\n this.activeRunners.delete(runner);\n runner.resolve(result);\n } catch (error) {\n if (runner.state === ETaskState.CANCELLED) {\n this.cancelledTasks++;\n } else if (runner.state === ETaskState.TIMED_OUT) {\n this.timedOutTasks++;\n } else {\n this.failedTasks++;\n }\n this.activeRunners.delete(runner);\n runner.reject(error);\n } finally {\n this.pump();\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: this.queue.length + this.delayedEntries.size + this.idleEntries.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 { 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 /**\n * Resolves the deferred promise.\n *\n * @param value - Value to resolve with.\n */\n public resolve(value: T): void {\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.rejectPromise(reason);\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 this.cleanup();\n return result;\n } catch (error) {\n this.cleanup();\n\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.2.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/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":["/**\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,5 +1,6 @@
1
1
  export * from "./context.model.js";
2
2
  export * from "./options.model.js";
3
+ export * from "./retry.model.js";
3
4
  export * from "./state.model.js";
4
5
  export * from "./stats.model.js";
5
6
  export * from "./strategy.model.js";
@@ -1,3 +1,4 @@
1
+ import type { IRetryOptions } from "./retry.model.js";
1
2
  import type { TScheduleStrategy } from "./strategy.model.js";
2
3
  /**
3
4
  * Options to configure a specific scheduled task.
@@ -19,6 +20,10 @@ export interface IScheduleOptions {
19
20
  * Forwards to requestIdleCallback({ timeout }) when running in browser runtimes.
20
21
  */
21
22
  idleTimeout?: number;
23
+ /**
24
+ * Automatic retry options for transient failure handling.
25
+ */
26
+ retry?: IRetryOptions;
22
27
  /**
23
28
  * External cancellation signal.
24
29
  * If aborted before start, the task is removed from the queue without execution.
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Supported backoff algorithms for retry attempts.
3
+ */
4
+ export type TRetryBackoff = "exponential" | "linear" | "none";
5
+ /**
6
+ * Predicate function determining whether a specific error warrants a retry attempt.
7
+ *
8
+ * @param error - The error thrown by the failed attempt.
9
+ * @param attempt - The 1-based index of the attempt that just failed.
10
+ * @returns Boolean or Promise<boolean> indicating whether to retry.
11
+ */
12
+ export type TRetryPredicate = (error: unknown, attempt: number) => boolean | Promise<boolean>;
13
+ /**
14
+ * Options configuring automatic retry behavior for a scheduled task.
15
+ */
16
+ export interface IRetryOptions {
17
+ /**
18
+ * Total number of execution attempts allowed (initial attempt + retries).
19
+ * For example, `attempts: 3` means 1 initial execution plus up to 2 retries.
20
+ * Must be an integer greater than or equal to 1.
21
+ * @default 1
22
+ */
23
+ attempts: number;
24
+ /**
25
+ * Backoff algorithm to apply between failed attempts.
26
+ * @default "exponential"
27
+ */
28
+ backoff?: TRetryBackoff;
29
+ /**
30
+ * Base delay in milliseconds used as the starting multiplier for backoff.
31
+ * Must be a non-negative number.
32
+ * @default 250
33
+ */
34
+ baseDelay?: number;
35
+ /**
36
+ * Maximum backoff delay cap in milliseconds to prevent unbounded growth.
37
+ * Must be a non-negative number greater than or equal to baseDelay.
38
+ * @default 10000
39
+ */
40
+ maxDelay?: number;
41
+ /**
42
+ * Whether to apply full jitter randomization to the calculated delay
43
+ * to distribute retry waves across concurrent tasks.
44
+ * @default false
45
+ */
46
+ jitter?: boolean;
47
+ /**
48
+ * Optional predicate filter to evaluate whether an error is retryable.
49
+ * If not provided, all errors (except cancellations) trigger a retry.
50
+ */
51
+ shouldRetry?: TRetryPredicate;
52
+ }
@@ -0,0 +1,18 @@
1
+ import type { IRetryOptions } from "../models/retry.model.js";
2
+ /**
3
+ * Default base delay for backoff calculations in milliseconds.
4
+ */
5
+ export declare const DEFAULT_BASE_DELAY = 250;
6
+ /**
7
+ * Default maximum delay ceiling for backoff calculations in milliseconds.
8
+ */
9
+ export declare const DEFAULT_MAX_DELAY = 10000;
10
+ /**
11
+ * Computes backoff delay in milliseconds for a retry attempt based on configured policy.
12
+ *
13
+ * @param attempt - 1-based index of the attempt that failed (1 for first failure, 2 for second, etc.).
14
+ * @param options - Retry configuration options.
15
+ * @param randomFn - Injectable random generator function (defaults to Math.random) for deterministic testing.
16
+ * @returns Delay duration in milliseconds before next attempt.
17
+ */
18
+ export declare function calculateBackoff(attempt: number, options?: IRetryOptions, randomFn?: () => number): number;
@@ -0,0 +1 @@
1
+ export * from "./backoff.js";
@@ -16,6 +16,10 @@ export declare class TaskQueue {
16
16
  private readonly delayedEntries;
17
17
  /** Set of tasks currently awaiting an idle opportunity */
18
18
  private readonly idleEntries;
19
+ /** Set of tasks currently awaiting a retry backoff timer */
20
+ private readonly retryEntries;
21
+ /** WeakMap associating task runners with their scheduling options */
22
+ private readonly runnerOptions;
19
23
  /** Cumulative completed tasks counter */
20
24
  private completedTasks;
21
25
  /** Cumulative failed tasks counter */
@@ -58,9 +62,13 @@ export declare class TaskQueue {
58
62
  private pump;
59
63
  /**
60
64
  * Internal execution of an active task runner.
61
- * Settle caller promise strictly after stats and active status are updated.
62
65
  */
63
66
  private executeRunner;
67
+ /**
68
+ * Schedules a retry attempt following backoff delay,
69
+ * without holding a concurrency slot.
70
+ */
71
+ private scheduleRetry;
64
72
  /**
65
73
  * Returns telemetry snapshot for the scheduler.
66
74
  *
@@ -1,3 +1,4 @@
1
+ import type { IRetryOptions } from "../models/retry.model.js";
1
2
  import { ETaskState } from "../models/state.model.js";
2
3
  import type { ITask } from "../models/task.model.js";
3
4
  /**
@@ -38,6 +39,8 @@ export declare class TaskRunner<T> {
38
39
  * Gets the current lifecycle state of the task.
39
40
  */
40
41
  get state(): ETaskState;
42
+ /** Current execution attempt count (1-indexed) */
43
+ attempt: number;
41
44
  /**
42
45
  * Resolves the deferred promise.
43
46
  *
@@ -50,6 +53,14 @@ export declare class TaskRunner<T> {
50
53
  * @param reason - Reason to reject with.
51
54
  */
52
55
  reject(reason: unknown): void;
56
+ /**
57
+ * Evaluates if the task should be retried following an execution failure.
58
+ *
59
+ * @param error - The error encountered during the attempt.
60
+ * @param retryOptions - Configured retry policy.
61
+ * @returns A promise resolving to true if retry should proceed, false otherwise.
62
+ */
63
+ canRetry(error: unknown, retryOptions?: IRetryOptions): Promise<boolean>;
53
64
  /**
54
65
  * Executes the task within an allocated concurrency slot.
55
66
  *
package/dist/version.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  /**
2
2
  * Current version of @mrjacket/ahko package.
3
3
  */
4
- export declare const VERSION = "0.2.0";
4
+ export declare const VERSION = "0.3.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrjacket/ahko",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "A low-energy task scheduler for JavaScript and TypeScript. Let your code chill.",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -47,11 +47,30 @@
47
47
  },
48
48
  "keywords": [
49
49
  "scheduler",
50
+ "task-scheduler",
50
51
  "queue",
51
52
  "concurrency",
53
+ "concurrency-control",
52
54
  "delay",
55
+ "idle",
56
+ "requestidlecallback",
57
+ "retry",
58
+ "backoff",
59
+ "exponential-backoff",
60
+ "jitter",
61
+ "throttle",
62
+ "debounce",
63
+ "rate-limit",
53
64
  "abortsignal",
54
- "async"
65
+ "abortcontroller",
66
+ "async",
67
+ "flow-control",
68
+ "task-runner",
69
+ "zero-dependencies",
70
+ "cooperative-scheduling",
71
+ "low-energy",
72
+ "typescript",
73
+ "100 kanojo"
55
74
  ],
56
75
  "devDependencies": {
57
76
  "@types/node": "^26.6.2",