@mrjacket/ahko 0.3.0 → 0.5.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,34 @@ 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.5.0] - 2026-09-22 — Throttle, Debounce & Rate Limiting
9
+
10
+ ### Added
11
+ - Debounce scheduling strategy (`EScheduleStrategy.DEBOUNCE`) with quiet window timer resets.
12
+ - Throttle scheduling strategy (`EScheduleStrategy.THROTTLE`) with immediate leading execution and coalesced trailing run.
13
+ - Promise coalescing by explicit identity key (`key: string | symbol`): all concurrent callers awaiting the same key receive the exact same Promise resolution without artificial cancellation rejections.
14
+ - Task start interval rate limiting via `minIntervalMs` on scheduler constructor (`IAhkoOptions`).
15
+ - Automatic key cleanup and timer detachment upon settlement guaranteeing zero memory leaks.
16
+ - Convenience API methods `ahko.debounce()` and `ahko.throttle()`.
17
+ - Validation for keys, quiet windows, throttle periods, and rate limit intervals.
18
+ - Comprehensive unit test suites for debounce coalescing, throttle leading/trailing runs, and interval rate limiting.
19
+
20
+ ---
21
+
22
+ ## [0.4.0] - 2026-09-22 — Timeout & Robust Cancellation
23
+
24
+ ### Added
25
+ - Execution timeout control via `timeoutMs` in `IScheduleOptions`.
26
+ - Rejection with `AhkoTimeoutError` containing exceeded `timeoutMs` threshold and descriptive message.
27
+ - Immediate rejection and slot recovery for hanging, uncooperative tasks via `Promise.race`.
28
+ - Active execution timeout isolation (queued waiting time and backoff delays do not consume execution timeout).
29
+ - Fresh `timeoutMs` window allocation on retry attempts.
30
+ - Unified signal coordination via `combineSignals` utility with deterministic listener detachment.
31
+ - Strict configuration validation for `timeoutMs` (positive finite numbers).
32
+ - Comprehensive unit test suite covering execution timeouts, cancellation precedence, uncooperative tasks, retries, and signal combination.
33
+
34
+ ---
35
+
8
36
  ## [0.3.0] - 2026-09-22 — Retry & Backoff
9
37
 
10
38
  ### Added
package/README.md CHANGED
@@ -160,7 +160,51 @@ const taskPromise = ahko.schedule(
160
160
  controller.abort();
161
161
  ```
162
162
 
163
- ### 6. Telemetry (`stats`)
163
+ ### 6. Execution Deadlines & Timeouts (`timeoutMs`)
164
+
165
+ Enforce deadlines per attempt. If a task exceeds `timeoutMs`, its context signal is aborted and the task rejects with `AhkoTimeoutError`:
166
+
167
+ ```typescript
168
+ try {
169
+ await ahko.schedule(
170
+ async ({ signal }) => {
171
+ return callLongRunningApi({ signal });
172
+ },
173
+ { timeoutMs: 3000 } // aborts and rejects if running longer than 3 seconds
174
+ );
175
+ } catch (error) {
176
+ if (error instanceof AhkoTimeoutError) {
177
+ console.error(`Timed out after ${error.timeoutMs}ms`);
178
+ }
179
+ }
180
+ ```
181
+
182
+ ### 7. Debounce & Throttle with Promise Coalescing
183
+
184
+ Coalesce repeated invocations into shared executions by explicit `key`. Callers share the exact same returned Promise:
185
+
186
+ ```typescript
187
+ // Debounce: waits for 300ms of quiet before running
188
+ const results = await ahko.debounce("search_box", async () => {
189
+ return queryApi(text);
190
+ }, 300);
191
+
192
+ // Throttle: runs leading edge immediately, coalesces trailing calls
193
+ await ahko.throttle("window_resize", async () => {
194
+ recalculateLayout();
195
+ }, 100);
196
+ ```
197
+
198
+ ### 8. Paced Execution (`minIntervalMs`)
199
+
200
+ Prevent burst spikes by ensuring a minimum interval elapses between consecutive task starts:
201
+
202
+ ```typescript
203
+ // At most 2 concurrent tasks, paced at least 50ms apart
204
+ const ahko = new Ahko({ concurrency: 2, minIntervalMs: 50 });
205
+ ```
206
+
207
+ ### 9. Telemetry (`stats`)
164
208
 
165
209
  Inspect real-time scheduler state without synthetic metrics:
166
210
 
@@ -174,7 +218,7 @@ console.log(stats);
174
218
  // completedTasks: 42,
175
219
  // failedTasks: 1,
176
220
  // cancelledTasks: 2,
177
- // timedOutTasks: 0,
221
+ // timedOutTasks: 1,
178
222
  // capacity: 3
179
223
  // }
180
224
  ```
@@ -203,6 +247,7 @@ Creates an AHKO scheduler instance.
203
247
  | Option | Type | Default | Description |
204
248
  |---|---|---|---|
205
249
  | `concurrency` | `number` | `Infinity` | Maximum concurrent tasks allowed to run simultaneously. Must be $\ge 1$. |
250
+ | `minIntervalMs` | `number` | `0` | Minimum interval in milliseconds between consecutive task starts. Must be $\ge 0$. |
206
251
 
207
252
  ### `ahko.schedule<T>(task: ITask<T>, options?: IScheduleOptions): Promise<T>`
208
253
 
@@ -210,12 +255,23 @@ Schedules an asynchronous task with full return type inference.
210
255
 
211
256
  | Option | Type | Default | Description |
212
257
  |---|---|---|---|
213
- | `strategy` | `"immediate" \| "delay" \| "idle"` | `"immediate"` | Scheduling execution strategy. |
258
+ | `strategy` | `"immediate" \| "delay" \| "idle" \| "throttle" \| "debounce"` | `"immediate"` | Scheduling execution strategy. |
214
259
  | `delay` | `number` | `0` | Delay in milliseconds when strategy is `"delay"`. |
260
+ | `key` | `string \| symbol` | `undefined` | Explicit identity key for `"debounce"` and `"throttle"`. |
261
+ | `waitMs` | `number` | `undefined` | Window duration in ms for debounce quiet period or throttle interval. |
215
262
  | `idleTimeout` | `number` | `undefined` | Maximum time to wait for idle window before forcing queue entry. |
216
263
  | `retry` | `IRetryOptions` | `undefined` | Automatic retry policy (attempts, backoff, jitter, predicate). |
264
+ | `timeoutMs` | `number` | `undefined` | Maximum execution duration in milliseconds per attempt before aborting with `AhkoTimeoutError`. |
217
265
  | `signal` | `AbortSignal` | `undefined` | Optional external `AbortSignal` for cooperative cancellation. |
218
266
 
267
+ ### `ahko.debounce<T>(key: string | symbol, task: ITask<T>, waitMs: number, options?: IScheduleOptions): Promise<T>`
268
+
269
+ Convenience method scheduling a debounced task with key-based Promise coalescing.
270
+
271
+ ### `ahko.throttle<T>(key: string | symbol, task: ITask<T>, waitMs: number, options?: IScheduleOptions): Promise<T>`
272
+
273
+ Convenience method scheduling a throttled task with leading execution and coalesced trailing run.
274
+
219
275
  ### `ahko.idle<T>(task: ITask<T>, options?: Omit<IScheduleOptions, "strategy">): Promise<T>`
220
276
 
221
277
  Convenience method scheduling a task under `strategy: "idle"`.
package/dist/ahko.d.ts CHANGED
@@ -44,6 +44,7 @@ export declare class Ahko {
44
44
  *
45
45
  * @throws {AhkoConfigurationError} If the task is not a function or options are invalid.
46
46
  * @throws {AhkoCancellationError} If the task is cancelled prior to or during execution.
47
+ * @throws {AhkoTimeoutError} If task execution exceeds timeoutMs.
47
48
  *
48
49
  * @example
49
50
  * ```typescript
@@ -73,15 +74,30 @@ export declare class Ahko {
73
74
  *
74
75
  * @throws {AhkoConfigurationError} If the task is not a function or options are invalid.
75
76
  * @throws {AhkoCancellationError} If the task is cancelled prior to or during execution.
76
- *
77
- * @example
78
- * ```typescript
79
- * const result = await ahko.idle(async ({ signal }) => {
80
- * return computeAnalytics();
81
- * });
82
- * ```
83
77
  */
84
78
  idle<T>(task: ITask<T>, options?: Omit<IScheduleOptions, "strategy">): Promise<T>;
79
+ /**
80
+ * Convenience method to schedule a debounced task with key-based Promise coalescing.
81
+ *
82
+ * @template T - Inferred return type of the task.
83
+ * @param key - Explicit identity key.
84
+ * @param task - Work to execute once calls stop arriving.
85
+ * @param waitMs - Quiet window duration in milliseconds.
86
+ * @param options - Additional schedule options.
87
+ * @returns Shared promise resolving with the final execution outcome.
88
+ */
89
+ debounce<T>(key: string | symbol, task: ITask<T>, waitMs: number, options?: Omit<IScheduleOptions, "strategy" | "key" | "waitMs">): Promise<T>;
90
+ /**
91
+ * Convenience method to schedule a throttled task with leading execution and coalesced trailing run.
92
+ *
93
+ * @template T - Inferred return type of the task.
94
+ * @param key - Explicit identity key.
95
+ * @param task - Work to execute.
96
+ * @param waitMs - Throttle interval duration in milliseconds.
97
+ * @param options - Additional schedule options.
98
+ * @returns Promise resolving with the leading or coalesced trailing result.
99
+ */
100
+ throttle<T>(key: string | symbol, task: ITask<T>, waitMs: number, options?: Omit<IScheduleOptions, "strategy" | "key" | "waitMs">): Promise<T>;
85
101
  /**
86
102
  * Retrieves real-time telemetry metrics from the scheduler.
87
103
  *
@@ -1,13 +1,26 @@
1
1
  import { AhkoError } from "./ahko.error.js";
2
+ /**
3
+ * Options for constructing an AhkoTimeoutError.
4
+ */
5
+ export interface IAhkoTimeoutErrorOptions extends ErrorOptions {
6
+ /**
7
+ * The timeout threshold in milliseconds that was exceeded.
8
+ */
9
+ timeoutMs?: number;
10
+ }
2
11
  /**
3
12
  * Thrown when a task exceeds its allotted timeout duration.
4
13
  */
5
14
  export declare class AhkoTimeoutError extends AhkoError {
15
+ /**
16
+ * The timeout threshold in milliseconds that was exceeded, if configured.
17
+ */
18
+ readonly timeoutMs?: number;
6
19
  /**
7
20
  * Creates a new AhkoTimeoutError.
8
21
  *
9
22
  * @param message - Explanation of timeout expiry.
10
- * @param options - Standard Error options including cause.
23
+ * @param options - Standard Error options including optional timeoutMs and cause.
11
24
  */
12
- constructor(message?: string, options?: ErrorOptions);
25
+ constructor(message?: string, options?: IAhkoTimeoutErrorOptions);
13
26
  }