@mrjacket/ahko 1.1.0 → 1.1.6

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,42 @@ 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
+ ## [1.1.6] - 2026-09-24
9
+
10
+ ### Documentation
11
+ - Add Coffee <3
12
+
13
+ ---
14
+
15
+ ## [1.1.5] - 2026-09-24 — Batch Collections, Dynamic & Adaptive Concurrency, Task Tags
16
+
17
+ ### Added
18
+ - **Batch Collections API (`ahko.map` & `ahko.each`)**:
19
+ - `ahko.map<TItem, TResult>(items, fn, options?)`: Concurrently transforms any iterable sequence while strictly preserving original element order.
20
+ - `ahko.each<TItem>(items, fn, options?)`: Concurrently iterates over any sequence returning `Promise<void>`.
21
+ - Supports localized concurrency caps per-batch (`options.concurrency`), falling back to scheduler concurrency when omitted.
22
+ - Fail-fast flow control via `stopOnError: true` (aborts remaining tasks immediately and rejects) or settled error propagation via `stopOnError: false` (default).
23
+ - Native integration with `signal`, `retry`, `tags`, and priority options.
24
+ - **Dynamic & Adaptive Concurrency (AIMD Auto-Chill Mode)**:
25
+ - Runtime dynamic concurrency reconfiguration via `ahko.setConcurrency(n)` and getter `ahko.concurrency`.
26
+ - Additive Increase / Multiplicative Decrease (AIMD) algorithm (`AdaptiveCoordinator`) adjusting scheduler capacity based on real-time task latency (`options.adaptive`).
27
+ - Seamlessly handles network latency spikes by scaling down concurrency on congestion and recovering when latency drops.
28
+ - Lifecycle event `"concurrency:change"` emitting `previousConcurrency`, `currentConcurrency`, and human-readable `reason`.
29
+ - Telemetry metrics in `ahko.stats().adaptive`: `currentConcurrency`, `averageLatencyMs`, `samplesRecorded`.
30
+ - **Task Tags & Selective Cancellation**:
31
+ - Categorize tasks via `tags: string[]` in `schedule()`, `map()`, or declarative profiles.
32
+ - Selectively cancel related tasks via `ahko.cancelByTag(tag, reason?)` without interrupting other pending or active workloads.
33
+ - Inspect workload density per category via `ahko.statsByTag(tag)` (`activeTasks` and `pendingTasks`).
34
+ - Memory-safe automatic tag indexing and instant cleanup upon task runner settlement.
35
+ - **Declarative Configuration Schema Expansion**:
36
+ - Added `adaptive` policy and `tags` classification to `schema.json` and `config.ahko.example.json`.
37
+ - **New Runnable Examples**:
38
+ - `examples/09-batch-collections.mjs`: Concurrent mapping over iterables with strict index ordering.
39
+ - `examples/10-adaptive-concurrency.mjs`: AIMD Auto-Chill mode reacting to upstream latency.
40
+ - `examples/11-tag-cancellation.mjs`: Tagged workload telemetry and selective cancellation.
41
+
42
+ ---
43
+
8
44
  ## [1.1.0] - 2026-09-23 — Declarative Config, Circuit Breaker & Priority Queue
9
45
 
10
46
  ### Added
package/README.md CHANGED
@@ -33,6 +33,9 @@
33
33
  <a href="https://github.com/x-name15/ahko/issues">
34
34
  <img src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg" alt="PRs Welcome">
35
35
  </a>
36
+ <a href="https://buymeacoffee.com/mrjacket">
37
+ <img src="https://img.shields.io/badge/Buy%20Me%20a%20Coffee-ffdd00?logo=buy-me-a-coffee&logoColor=black" alt="Buy Me A Coffee">
38
+ </a>
36
39
  </p>
37
40
 
38
41
  `ahko` is a low-energy task scheduler for JavaScript and TypeScript.
@@ -397,6 +400,69 @@ Ahko.loadConfig(config);
397
400
  const gateway = Ahko.fromProfile("critical-gateway");
398
401
  ```
399
402
 
403
+ ### 18. Batch Collections API (`ahko.map` & `ahko.each`)
404
+
405
+ Transform collections concurrently with strict index order preservation and localized concurrency limits:
406
+
407
+ ```typescript
408
+ const urls = ["/api/users", "/api/posts", "/api/comments"];
409
+
410
+ // Processes concurrently (max 2 at a time), guaranteed return in original order
411
+ const responses = await ahko.map(
412
+ urls,
413
+ async (url, index, { signal }) => {
414
+ const res = await fetch(url, { signal });
415
+ return res.json();
416
+ },
417
+ { concurrency: 2, stopOnError: true }
418
+ );
419
+
420
+ // Or iterate over items returning void:
421
+ await ahko.each(userIds, async (id) => syncUser(id), { concurrency: 5 });
422
+ ```
423
+
424
+ ### 19. Dynamic & Adaptive Concurrency (AIMD Auto-Chill)
425
+
426
+ Adjust concurrency dynamically on the fly or let Ahko adapt automatically to network latency:
427
+
428
+ ```typescript
429
+ // Manually update concurrency at runtime:
430
+ ahko.setConcurrency(5);
431
+ console.log(ahko.concurrency); // 5
432
+
433
+ // Or enable AIMD (Additive Increase / Multiplicative Decrease) Auto-Chill mode:
434
+ const adaptiveAhko = new Ahko({
435
+ concurrency: 4,
436
+ adaptive: {
437
+ targetLatencyMs: 150, // if tasks take >150ms, cut concurrency in half
438
+ sampleWindowSize: 5, // adjust after every 5 completed tasks
439
+ minConcurrency: 1,
440
+ maxConcurrency: 10,
441
+ backoffFactor: 0.5,
442
+ },
443
+ });
444
+
445
+ adaptiveAhko.on("concurrency:change", ({ previousConcurrency, currentConcurrency, reason }) => {
446
+ console.log(`Capacity adapted: ${previousConcurrency} -> ${currentConcurrency} (${reason})`);
447
+ });
448
+ ```
449
+
450
+ ### 20. Task Tags & Selective Cancellation
451
+
452
+ Classify tasks by tags, query categorical telemetry, and selectively cancel specific operations:
453
+
454
+ ```typescript
455
+ // Tag tasks during scheduling:
456
+ ahko.schedule(generateReport, { tags: ["reports", "finance"] });
457
+ ahko.schedule(syncDatabase, { tags: ["sync"] });
458
+
459
+ // Check active and pending tasks by tag:
460
+ console.log(ahko.statsByTag("reports")); // { activeTasks: 1, pendingTasks: 0 }
461
+
462
+ // Cancel all tasks associated with a tag without affecting other tasks:
463
+ ahko.cancelByTag("reports", "User navigated away");
464
+ ```
465
+
400
466
  ---
401
467
 
402
468
  ## Documentation
@@ -429,8 +495,33 @@ Creates an ahko scheduler instance.
429
495
  | `concurrency` | `number` | `Infinity` | Maximum concurrent tasks allowed to run simultaneously. Must be $\ge 1$. |
430
496
  | `minIntervalMs` | `number` | `0` | Minimum interval in milliseconds between consecutive task starts. Must be $\ge 0$. |
431
497
  | `circuitBreaker` | `ICircuitBreakerOptions` | `undefined` | Optional failure threshold and cooldown reset configuration. |
498
+ | `adaptive` | `IAdaptiveConcurrencyOptions` | `undefined` | Optional AIMD adaptive concurrency options based on task execution latency. |
432
499
  | `profile` | `string` | `undefined` | Name of declarative profile to inherit settings from. |
433
500
 
501
+ ### `ahko.concurrency`
502
+
503
+ Getter returning the current concurrency capacity limit.
504
+
505
+ ### `ahko.setConcurrency(newConcurrency: number): void`
506
+
507
+ Dynamically updates the concurrency limit of the scheduler at runtime.
508
+
509
+ ### `ahko.map<TItem, TResult>(items, fn, options?): Promise<TResult[]>`
510
+
511
+ Concurrently transforms an iterable sequence into an array with strict index ordering. Supports localized `concurrency` limits and `stopOnError`.
512
+
513
+ ### `ahko.each<TItem>(items, fn, options?): Promise<void>`
514
+
515
+ Iterates over an iterable sequence concurrently, executing the callback for each element.
516
+
517
+ ### `ahko.cancelByTag(tag: string, reason?: unknown): number`
518
+
519
+ Cancels all pending, delayed, and active tasks marked with the specified tag. Returns the number of cancelled tasks.
520
+
521
+ ### `ahko.statsByTag(tag: string): { activeTasks: number; pendingTasks: number }`
522
+
523
+ Returns active and pending task counts for a specific classification tag.
524
+
434
525
  ### `ahko.schedule<T>(task: ITask<T>, options?: IScheduleOptions): Promise<T>`
435
526
 
436
527
  Schedules an asynchronous task with full return type inference.
@@ -447,6 +538,7 @@ Schedules an asynchronous task with full return type inference.
447
538
  | `timeoutMs` | `number` | `undefined` | Maximum execution duration in milliseconds per attempt before aborting with `AhkoTimeoutError`. |
448
539
  | `totalTimeoutMs` | `number` | `undefined` | Total execution budget across wait time, retries, and execution. |
449
540
  | `signal` | `AbortSignal` | `undefined` | Optional external `AbortSignal` for cooperative cancellation. |
541
+ | `tags` | `string[]` | `undefined` | Classification tags for selective cancellation and metric grouping. |
450
542
 
451
543
  ### `ahko.wrap(fn, options?)`
452
544
 
@@ -474,7 +566,7 @@ Convenience method scheduling a task under `strategy: "idle"`.
474
566
 
475
567
  ### `ahko.on(event, handler)`
476
568
 
477
- Subscribes to scheduler lifecycle events (`task:start`, `task:complete`, `task:fail`, `task:cancel`, `task:timeout`, `idle`). Returns an unsubscribe function.
569
+ Subscribes to scheduler lifecycle events (`task:start`, `task:complete`, `task:fail`, `task:cancel`, `task:timeout`, `idle`, `concurrency:change`). Returns an unsubscribe function.
478
570
 
479
571
  ### `ahko.off(event, handler)`
480
572
 
@@ -519,6 +611,16 @@ All scheduler errors inherit from `AhkoError`:
519
611
 
520
612
  ---
521
613
 
614
+ ## Support & Sponsoring
615
+
616
+ If you liked this library or want to support my work, I'd be eternally grateful for a warm coffee! ☕ <3
617
+
618
+ <a href="https://buymeacoffee.com/mrjacket" target="_blank">
619
+ <img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" width="180">
620
+ </a>
621
+
622
+ ---
623
+
522
624
  ## License
523
625
  [GNU General Public License v3.0 (GPL-3.0-only)](LICENSE)
524
626
 
package/dist/ahko.d.ts CHANGED
@@ -1,5 +1,7 @@
1
+ import type { IBatchOptions, IBatchMapOptions } from "./models/batch.model.js";
1
2
  import type { ECircuitState } from "./models/circuit-breaker.model.js";
2
3
  import type { IAhkoFileConfig } from "./models/config.model.js";
4
+ import type { ITaskContext } from "./models/context.model.js";
3
5
  import type { TAhkoEventName, TAhkoEventHandler, TAhkoUnsubscribe } from "./models/events.model.js";
4
6
  import type { IAhkoOptions, IScheduleOptions } from "./models/options.model.js";
5
7
  import type { IAhkoStats } from "./models/stats.model.js";
@@ -70,6 +72,17 @@ export declare class Ahko {
70
72
  * ```
71
73
  */
72
74
  constructor(options?: IAhkoOptions);
75
+ /**
76
+ * Current concurrency limit.
77
+ */
78
+ get concurrency(): number;
79
+ /**
80
+ * Dynamically updates the concurrency limit of the scheduler.
81
+ *
82
+ * @param concurrency - New maximum concurrency (must be >= 1).
83
+ * @throws {AhkoConfigurationError} If concurrency is invalid.
84
+ */
85
+ setConcurrency(concurrency: number): void;
73
86
  /**
74
87
  * Pauses scheduler dispatch. In-flight tasks run to completion, but pending tasks remain queued.
75
88
  */
@@ -168,6 +181,68 @@ export declare class Ahko {
168
181
  * @returns Promise resolving with the leading or coalesced trailing result.
169
182
  */
170
183
  throttle<T>(key: string | symbol, task: ITask<T>, waitMs: number, options?: Omit<IScheduleOptions, "strategy" | "key" | "waitMs">): Promise<T>;
184
+ /**
185
+ * Transforms an iterable of items concurrently using an asynchronous mapping function.
186
+ *
187
+ * Results are guaranteed to be returned in the original index order.
188
+ * Concurrency can be capped per-batch or fall back to the scheduler's global limit.
189
+ *
190
+ * @template TItem - Type of input elements.
191
+ * @template TResult - Type of mapped elements.
192
+ * @param items - Iterable sequence of items to process.
193
+ * @param fn - Mapper callback receiving item, index, and task context.
194
+ * @param options - Batch execution options (concurrency, stopOnError, retry, signal, tags, etc.).
195
+ * @returns Array of transformed results in index order.
196
+ *
197
+ * @throws {AhkoConfigurationError} If fn is not a function or concurrency is invalid.
198
+ * @throws {AhkoCancellationError} If batch or item is cancelled.
199
+ *
200
+ * @example
201
+ * ```typescript
202
+ * const urls = ["/api/1", "/api/2", "/api/3"];
203
+ * const data = await ahko.map(urls, async (url, i, { signal }) => {
204
+ * const res = await fetch(url, { signal });
205
+ * return res.json();
206
+ * }, { concurrency: 2 });
207
+ * ```
208
+ */
209
+ map<TItem, TResult>(items: Iterable<TItem>, fn: (item: TItem, index: number, context: ITaskContext) => Promise<TResult> | TResult, options?: IBatchMapOptions<TItem, TResult>): Promise<TResult[]>;
210
+ /**
211
+ * Iterates sequentially or concurrently over an iterable sequence of items,
212
+ * executing the callback function for each element.
213
+ *
214
+ * @template TItem - Type of input elements.
215
+ * @param items - Iterable sequence of items to process.
216
+ * @param fn - Callback receiving item, index, and task context.
217
+ * @param options - Batch execution options.
218
+ * @returns Promise resolving once all items have finished executing.
219
+ *
220
+ * @example
221
+ * ```typescript
222
+ * await ahko.each(userQueue, async (user, index, { signal }) => {
223
+ * await sendWelcomeEmail(user, { signal });
224
+ * }, { concurrency: 5 });
225
+ * ```
226
+ */
227
+ each<TItem>(items: Iterable<TItem>, fn: (item: TItem, index: number, context: ITaskContext) => Promise<void> | void, options?: IBatchOptions): Promise<void>;
228
+ /**
229
+ * Cancels all pending, delayed, and active tasks tagged with the given tag.
230
+ *
231
+ * @param tag - Tag identifier.
232
+ * @param reason - Optional cancellation reason.
233
+ * @returns Total number of tasks cancelled.
234
+ */
235
+ cancelByTag(tag: string, reason?: unknown): number;
236
+ /**
237
+ * Retrieves active and pending task counts for a given tag.
238
+ *
239
+ * @param tag - Tag identifier.
240
+ * @returns Object with activeTasks and pendingTasks counts.
241
+ */
242
+ statsByTag(tag: string): {
243
+ activeTasks: number;
244
+ pendingTasks: number;
245
+ };
171
246
  /**
172
247
  * Retrieves real-time telemetry metrics from the scheduler.
173
248
  *