From 5fc890a7989ddb11c99520cc86f5f188ad30003c Mon Sep 17 00:00:00 2001 From: Shinrai Date: Sat, 3 Oct 2026 17:19:38 -0700 Subject: [PATCH 01/16] fix(enqueue): accept task-level postDelay/startDelay, keep delay/start as deprecated aliases enqueue() only read the task-level `delay` and `start` options, although the JSDoc already pointed at `postDelay`. Passing the documented name gave no delay at all. - enqueue() now normalizes task options: `postDelay` / `startDelay` are the current names, `delay` / `start` map onto them and emit a `warning` event in the same deprecation shape the priority and coalescing levels use (once per name per queue instance, so a hot enqueue path isn't flooded). The current name wins when both are given; caller options are never mutated. `postDelay: -1` bypasses an active delay like `delay: -1` did. - getPriorityConfig() / getCoalescingConfig() accept both names in taskOptions. - The scheduler's delay gate now uses nextAvailableTime alone. It used to require the completed task's *priority* postDelay to be positive, so a task-level completion delay on a priority without a postDelay was set but never enforced (true for the old `delay` name as well). - JSDoc, generated types and the README task-option list use the new names. Fixes #51 --- README.md | 4 +- src/hold-my-task.mjs | 106 ++++++++++--- tests/TaskLevelDelayNames.test.vitest.mjs | 177 ++++++++++++++++++++++ types/src/hold-my-task.d.mts | 19 ++- types/src/hold-my-task.d.mts.map | 2 +- 5 files changed, 277 insertions(+), 31 deletions(-) create mode 100644 tests/TaskLevelDelayNames.test.vitest.mjs diff --git a/README.md b/README.md index 6797636..e60feab 100644 --- a/README.md +++ b/README.md @@ -314,12 +314,12 @@ Adds a task to the queue. **Task Options:** - `priority` (number) - Task priority (higher = more important) -- `delay` (number) - Override completion delay for this task (use -1 to bypass delays) +- `postDelay` (number) - Override completion delay for this task (use -1 to bypass delays). `delay` is a deprecated alias that emits a `warning` event - `bypassDelay` (boolean) - If true, skip any active delay period and start immediately - `timeout` (number) - Timeout in milliseconds - `signal` (AbortSignal) - External abort signal - `timestamp` (number) - Absolute execution timestamp -- `start` (number) - Milliseconds from now when the task should be ready to run (convenience for timestamp calculation) +- `startDelay` (number) - Milliseconds from now when the task should be ready to run (convenience for timestamp calculation). `start` is a deprecated alias that emits a `warning` event - `coalescingKey` (string) - Tasks with the same coalescing key can be merged for efficiency - `mustRunBy` (number) - Absolute timestamp by which the task must execute (overrides coalescing delays) - `metadata` (any) - Custom metadata attached to the task. Individual metadata is always directly accessible via the returned task handle diff --git a/src/hold-my-task.mjs b/src/hold-my-task.mjs index d786fa2..a1dd455 100644 --- a/src/hold-my-task.mjs +++ b/src/hold-my-task.mjs @@ -140,6 +140,61 @@ export class HoldMyTask extends EventEmitter { return transformed; } + /** + * Normalizes task-level delay option names passed to enqueue(). + * `postDelay` / `startDelay` are the current names; `delay` / `start` are deprecated aliases. + * When both forms are given, the current name wins. Each deprecated alias emits one + * deprecation `warning` event per queue instance (not one per task), so a hot enqueue + * path doesn't flood listeners. + * @param {Object} options - Task options (never mutated) + * @returns {Object} A copy of the options using the current names + * @private + * @internal + */ + _normalizeTaskDelayOptions(options) { + if (!("delay" in options) && !("start" in options)) { + return options; + } + + const normalized = { ...options }; + const aliases = [ + { deprecated: "start", replacement: "startDelay" }, + { deprecated: "delay", replacement: "postDelay" } + ]; + + for (const { deprecated, replacement } of aliases) { + if (!(deprecated in options)) continue; + if (!(replacement in options)) { + normalized[replacement] = options[deprecated]; + this._warnTaskOptionDeprecated(deprecated, replacement); + } + delete normalized[deprecated]; + } + + return normalized; + } + + /** + * Emits a deprecation warning for a task-level option, once per option name per instance. + * @param {string} deprecated - Deprecated option name + * @param {string} replacement - Current option name + * @returns {void} + * @private + * @internal + */ + _warnTaskOptionDeprecated(deprecated, replacement) { + if (this._warnedTaskOptions.has(deprecated)) return; + this._warnedTaskOptions.add(deprecated); + setImmediate(() => + this.emit("warning", { + type: "deprecation", + message: `Task option '${deprecated}' is deprecated. Use '${replacement}' instead.`, + deprecated, + replacement + }) + ); + } + constructor(options = {}) { super(); @@ -350,6 +405,9 @@ export class HoldMyTask extends EventEmitter { this.coalescingRepresentatives = new Map(); // representativeTaskId -> { coalescingKey, groupId } this.nextGroupId = 1; + // Deprecated task-level option names already warned about (one warning per name per instance) + this._warnedTaskOptions = new Set(); + if (this.options.autoStart) { this.resume(); } @@ -378,11 +436,13 @@ export class HoldMyTask extends EventEmitter { * @param {string|number} [options.id] - Custom task ID for identification and later reference (must be unique) * @param {number} [options.priority] - Task priority (higher numbers run first) * @param {number} [options.timestamp] - When the task should be ready to run (milliseconds since epoch) - * @param {number} [options.start] - Milliseconds from now when the task should be ready to run (convenience for timestamp calculation) + * @param {number} [options.startDelay] - Milliseconds from now when the task should be ready to run (convenience for timestamp calculation). Overrides the priority's startDelay + * @param {number} [options.start] - DEPRECATED: Use startDelay instead * @param {AbortSignal} [options.signal] - AbortSignal to cancel the task * @param {number} [options.timeout] - Task timeout in milliseconds (for execution time limit) * @param {number} [options.expire] - Task expiration timestamp or milliseconds from now (for queue waiting time limit) - * @param {number} [options.delay] - DEPRECATED: Use postDelay instead. Delay after task completion before next task of same priority + * @param {number} [options.postDelay] - Delay after this task completes before the next task can start. Overrides the priority's postDelay; use -1 to bypass the current delay period + * @param {number} [options.delay] - DEPRECATED: Use postDelay instead * @param {boolean} [options.bypassDelay] - If true, skip any active delay period and start immediately * @param {string} [options.coalescingKey] - Key for task coalescing - tasks with same key will be coalesced within windows * @param {number} [options.coalescingWindowDuration] - Override coalescing window duration (task-level override of key-level and defaults) @@ -408,8 +468,8 @@ export class HoldMyTask extends EventEmitter { * // Bypass current delay for urgent task * const urgent = queue.enqueue(urgentTask, { priority: 10, bypassDelay: true }); * - * // Alternative: use delay: -1 to bypass - * const urgent2 = queue.enqueue(urgentTask, { priority: 10, delay: -1 }); + * // Alternative: use postDelay: -1 to bypass + * const urgent2 = queue.enqueue(urgentTask, { priority: 10, postDelay: -1 }); * * // Coalescing tasks - multiple device status checks become one * queue.enqueue(checkDeviceStatus, callback1, { coalescingKey: "device-123", coalescingWindowDuration: 1000 }); @@ -438,6 +498,9 @@ export class HoldMyTask extends EventEmitter { finalOptions = { ...optionsOrCallback, ...options }; } + // Task-level delay names: postDelay/startDelay are current, delay/start are deprecated aliases + finalOptions = this._normalizeTaskDelayOptions(finalOptions); + // Generate ID - use custom ID if provided, otherwise auto-generate const id = finalOptions.id ? String(finalOptions.id) : String(this.nextId++); @@ -457,7 +520,7 @@ export class HoldMyTask extends EventEmitter { // Regular task handling - apply priority defaults for start delay const priority = finalOptions.priority ?? this.options.defaultPriority; const priorityConfig = this.getPriorityConfig(priority, finalOptions); - const effectiveStart = finalOptions.start ?? priorityConfig.startDelay ?? 0; + const effectiveStart = priorityConfig.startDelay ?? 0; const readyAt = finalOptions.timestamp ?? (effectiveStart ? now + effectiveStart : now); // Calculate expiration timestamp @@ -484,8 +547,8 @@ export class HoldMyTask extends EventEmitter { status: "pending", signal: finalOptions.signal, timeout: finalOptions.timeout, - delay: finalOptions.delay, // completion delay - bypassDelay: finalOptions.bypassDelay || finalOptions.delay === -1, // bypass current delay period + delay: finalOptions.postDelay, // task-level completion delay override (undefined = use the priority's postDelay) + bypassDelay: finalOptions.bypassDelay || finalOptions.postDelay === -1, // bypass current delay period metadata: finalOptions.metadata }; @@ -620,14 +683,14 @@ export class HoldMyTask extends EventEmitter { const priorityConfig = this.getPriorityConfig(priority, options); // Apply configuration priority: task options > coalescing key config > priority defaults > coalescing defaults - const effectiveStart = options.start ?? coalescingConfig.startDelay ?? priorityConfig.startDelay ?? 0; + const effectiveStart = options.startDelay ?? coalescingConfig.startDelay ?? priorityConfig.startDelay ?? 0; const readyAt = options.timestamp ?? (effectiveStart ? now + effectiveStart : now); const windowEnd = now + coalescingConfig.windowDuration; const mustRunBy = now + coalescingConfig.maxDelay; // Apply effective delay configuration - const effectiveDelay = options.delay ?? coalescingConfig.postDelay ?? priorityConfig.postDelay; + const effectiveDelay = options.postDelay ?? coalescingConfig.postDelay ?? priorityConfig.postDelay; // Create task item (not added to main queue directly) const taskItem = { @@ -640,7 +703,7 @@ export class HoldMyTask extends EventEmitter { signal: options.signal, timeout: options.timeout, delay: effectiveDelay, - bypassDelay: options.bypassDelay || options.delay === -1, + bypassDelay: options.bypassDelay || options.postDelay === -1, metadata: options.metadata, coalescingKey, enqueueSeq: this.enqueueSeq++ @@ -1671,8 +1734,9 @@ export class HoldMyTask extends EventEmitter { // Start tasks up to concurrency limits (both global and per-priority) let hasWaitingTasks = false; const currentTime = this.now(); - const delay = this.lastCompletedPriority !== null ? (this.options.priorities[this.lastCompletedPriority]?.postDelay ?? 0) : 0; - const delayActive = delay > 0 && currentTime < this.nextAvailableTime; + // nextAvailableTime is only set (non-zero) when the completed task's effective delay - its own + // postDelay override or its priority's postDelay - was positive, so it alone decides the gate. + const delayActive = currentTime < this.nextAvailableTime; while (this.readyHeap.size() > 0) { const task = this.readyHeap.peek(); @@ -2050,8 +2114,7 @@ export class HoldMyTask extends EventEmitter { // Check if we have ready tasks that can run immediately if (this.readyHeap.size() > 0 && this.running.size < this.options.concurrency) { - const delay = this.lastCompletedPriority !== null ? (this.options.priorities[this.lastCompletedPriority]?.postDelay ?? 0) : 0; - const delayActive = delay > 0 && now < this.nextAvailableTime; + const delayActive = now < this.nextAvailableTime; if (!delayActive) { shouldRunNow = true; @@ -2246,7 +2309,7 @@ export class HoldMyTask extends EventEmitter { * // Check with task-level overrides * const effectiveConfig = queue.getCoalescingConfig('ui.update', { * coalescingWindowDuration: 50, - * delay: 30 // Still accepts old property names for backwards compatibility + * postDelay: 30 // Deprecated task-level name delay is still accepted * }); */ getCoalescingConfig(coalescingKey, taskOptions = {}) { @@ -2254,8 +2317,9 @@ export class HoldMyTask extends EventEmitter { const defaults = this.options.coalescing.defaults; const priorityConfig = this.getPriorityConfig(taskOptions.priority ?? this.options.defaultPriority); - const postDelay = taskOptions.delay ?? keyConfig.postDelay ?? priorityConfig.postDelay ?? defaults.postDelay; - const startDelay = taskOptions.start ?? keyConfig.startDelay ?? priorityConfig.startDelay ?? defaults.startDelay; + const postDelay = taskOptions.postDelay ?? taskOptions.delay ?? keyConfig.postDelay ?? priorityConfig.postDelay ?? defaults.postDelay; + const startDelay = + taskOptions.startDelay ?? taskOptions.start ?? keyConfig.startDelay ?? priorityConfig.startDelay ?? defaults.startDelay; return { windowDuration: taskOptions.coalescingWindowDuration ?? keyConfig.windowDuration ?? defaults.windowDuration, @@ -2356,15 +2420,15 @@ export class HoldMyTask extends EventEmitter { * * // Check with task-level overrides * const effectiveConfig = queue.getPriorityConfig(1, { - * delay: 50, - * start: 10 + * postDelay: 50, + * startDelay: 10 // Deprecated task-level names delay/start are still accepted * }); */ getPriorityConfig(priority, taskOptions = {}) { const priorityConfig = this.options.priorities[priority] || {}; - const postDelay = taskOptions.delay ?? priorityConfig.postDelay; - const startDelay = taskOptions.start ?? priorityConfig.startDelay; + const postDelay = taskOptions.postDelay ?? taskOptions.delay ?? priorityConfig.postDelay; + const startDelay = taskOptions.startDelay ?? taskOptions.start ?? priorityConfig.startDelay; return { // New clear property names diff --git a/tests/TaskLevelDelayNames.test.vitest.mjs b/tests/TaskLevelDelayNames.test.vitest.mjs new file mode 100644 index 0000000..87194b7 --- /dev/null +++ b/tests/TaskLevelDelayNames.test.vitest.mjs @@ -0,0 +1,177 @@ +/** + * + * @Project: @cldmv/holdmytask + * @Filename: /tests/TaskLevelDelayNames.test.vitest.mjs + * @Date: 2026-10-03T17:15:23-07:00 (1791072923) + * @Author: Nate Corcoran + * @Email: + * ----- + * @Last modified by: Nate Corcoran (Shinrai@users.noreply.github.com) + * @Last modified time: 2026-10-03T17:19:29-07:00 (1791073169) + * ----- + * @Copyright: Copyright (c) 2013-2026 Catalyzed Motivation Inc. All rights reserved. + * + */ + +import { test, expect, describe, vi, beforeEach, afterEach } from "vitest"; +import { HoldMyTask } from "../src/hold-my-task.mjs"; + +// #51: task-level `postDelay` / `startDelay` are the current names; `delay` / `start` +// remain as deprecated aliases that emit a deprecation warning. +const fakeTimers = () => + vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout", "setInterval", "clearInterval", "setImmediate", "clearImmediate", "Date"] }); + +describe.each([ + { smartScheduling: true, mode: "Smart Scheduling" }, + { smartScheduling: false, mode: "Traditional Polling" } +])("task-level delay names with $mode", ({ smartScheduling }) => { + beforeEach(fakeTimers); + afterEach(() => vi.useRealTimers()); + + test("task-level postDelay delays the next task without any priority config", async () => { + const q = new HoldMyTask({ concurrency: 1, smartScheduling }); + try { + const a = q.enqueue(() => "a", { postDelay: 200 }); + const b = q.enqueue(() => "b"); + await vi.advanceTimersByTimeAsync(150); + expect(a.status()).toBe("completed"); + expect(b.status()).not.toBe("completed"); + await vi.advanceTimersByTimeAsync(150); + await Promise.all([a, b]); + expect(b.startedAt - a.finishedAt).toBeGreaterThanOrEqual(200); + } finally { + q.destroy(); + } + }); + + test("deprecated task-level delay still delays the next task and warns once", async () => { + const q = new HoldMyTask({ concurrency: 1, smartScheduling }); + const warnings = []; + q.on("warning", (w) => warnings.push(w)); + try { + const a = q.enqueue(() => "a", { delay: 200 }); + const b = q.enqueue(() => "b", { delay: 0 }); + await vi.advanceTimersByTimeAsync(400); + await Promise.all([a, b]); + expect(b.startedAt - a.finishedAt).toBeGreaterThanOrEqual(200); + const delayWarnings = warnings.filter((w) => w.deprecated === "delay"); + expect(delayWarnings).toHaveLength(1); + expect(delayWarnings[0]).toMatchObject({ type: "deprecation", deprecated: "delay", replacement: "postDelay" }); + expect(delayWarnings[0].message).toContain("deprecated"); + } finally { + q.destroy(); + } + }); + + test("task-level startDelay holds the task back; deprecated start does the same and warns", async () => { + const q = new HoldMyTask({ concurrency: 2, smartScheduling }); + const warnings = []; + q.on("warning", (w) => warnings.push(w)); + try { + const enqueuedAt = Date.now(); + const a = q.enqueue(() => "a", { startDelay: 100 }); + const b = q.enqueue(() => "b", { start: 100 }); + await vi.advanceTimersByTimeAsync(50); + expect(a.status()).toBe("pending"); + expect(b.status()).toBe("pending"); + await vi.advanceTimersByTimeAsync(100); + await Promise.all([a, b]); + expect(a.startedAt - enqueuedAt).toBeGreaterThanOrEqual(100); + expect(b.startedAt - enqueuedAt).toBeGreaterThanOrEqual(100); + expect(warnings.filter((w) => w.deprecated === "start")).toEqual([ + expect.objectContaining({ type: "deprecation", deprecated: "start", replacement: "startDelay" }) + ]); + expect(warnings.filter((w) => w.deprecated === "startDelay" || w.deprecated === "postDelay")).toHaveLength(0); + } finally { + q.destroy(); + } + }); + + test("postDelay: -1 bypasses an active delay period", async () => { + const q = new HoldMyTask({ concurrency: 1, smartScheduling, priorities: { 1: { postDelay: 500 } } }); + try { + const a = q.enqueue(() => "a", { priority: 1 }); + await vi.advanceTimersByTimeAsync(30); + await a; + const b = q.enqueue(() => "b", { priority: 1, postDelay: -1 }); + await vi.advanceTimersByTimeAsync(50); + expect(b.status()).toBe("completed"); + await b; + } finally { + q.destroy(); + } + }); +}); + +describe("task-level delay name resolution", () => { + test("new names win over deprecated aliases without a warning, and options are not mutated", async () => { + const q = new HoldMyTask({ concurrency: 1, autoStart: false }); + const warnings = []; + q.on("warning", (w) => warnings.push(w)); + const options = { postDelay: 10, delay: 999, startDelay: 5, start: 999 }; + const p = q.enqueue(() => "x", options); + expect(options).toEqual({ postDelay: 10, delay: 999, startDelay: 5, start: 999 }); + await new Promise((resolve) => setImmediate(resolve)); + expect(warnings).toHaveLength(0); + q.resume(); + await p; + q.destroy(); + }); + + test("callback-form options are normalized too", async () => { + const q = new HoldMyTask({ concurrency: 1 }); + const warnings = []; + q.on("warning", (w) => warnings.push(w)); + const result = await new Promise((resolve) => + q.enqueue( + () => "cb", + (err, value) => resolve(value), + { delay: 0 } + ) + ); + expect(result).toBe("cb"); + expect(warnings.some((w) => w.deprecated === "delay")).toBe(true); + q.destroy(); + }); + + test("getPriorityConfig and getCoalescingConfig honour task-level postDelay/startDelay and the aliases", () => { + const q = new HoldMyTask({ autoStart: false, priorities: { 1: { postDelay: 100, startDelay: 20 } } }); + expect(q.getPriorityConfig(1, { postDelay: 5, startDelay: 6 })).toMatchObject({ postDelay: 5, startDelay: 6, delay: 5, start: 6 }); + expect(q.getPriorityConfig(1, { delay: 7, start: 8 })).toMatchObject({ postDelay: 7, startDelay: 8 }); + expect(q.getPriorityConfig(1, { postDelay: 1, delay: 2 }).postDelay).toBe(1); + expect(q.getPriorityConfig(1)).toMatchObject({ postDelay: 100, startDelay: 20 }); + expect(q.getCoalescingConfig("k", { postDelay: 9, startDelay: 3 })).toMatchObject({ postDelay: 9, startDelay: 3 }); + expect(q.getCoalescingConfig("k", { delay: 4, start: 2 })).toMatchObject({ postDelay: 4, startDelay: 2 }); + q.destroy(); + }); + + test("coalescing tasks honour task-level postDelay/startDelay", async () => { + vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout", "setInterval", "clearInterval", "setImmediate", "clearImmediate", "Date"] }); + const q = new HoldMyTask({ concurrency: 1, coalescing: { defaults: { windowDuration: 10, maxDelay: 50 } } }); + try { + const enqueuedAt = Date.now(); + let aRanAt; + let bRanAt; + const a = q.enqueue( + () => { + aRanAt = Date.now(); + return "a"; + }, + { coalescingKey: "k", startDelay: 100, postDelay: 200 } + ); + await vi.advanceTimersByTimeAsync(150); + await a; + const b = q.enqueue(() => { + bRanAt = Date.now(); + return "b"; + }); + await vi.advanceTimersByTimeAsync(1000); + await b; + expect(aRanAt - enqueuedAt).toBeGreaterThanOrEqual(100); + expect(bRanAt - aRanAt).toBeGreaterThanOrEqual(200); + } finally { + q.destroy(); + vi.useRealTimers(); + } + }); +}); diff --git a/types/src/hold-my-task.d.mts b/types/src/hold-my-task.d.mts index e7ff006..59ba973 100644 --- a/types/src/hold-my-task.d.mts +++ b/types/src/hold-my-task.d.mts @@ -67,6 +67,7 @@ export declare class HoldMyTask extends EventEmitter { coalescingGroups: Map | undefined; coalescingRepresentatives: Map | undefined; nextGroupId: number | undefined; + _warnedTaskOptions: Set | undefined; timeoutId: number | undefined; constructor(options?: {}); /** @@ -107,11 +108,13 @@ export declare class HoldMyTask extends EventEmitter { * @param {string|number} [options.id] - Custom task ID for identification and later reference (must be unique) * @param {number} [options.priority] - Task priority (higher numbers run first) * @param {number} [options.timestamp] - When the task should be ready to run (milliseconds since epoch) - * @param {number} [options.start] - Milliseconds from now when the task should be ready to run (convenience for timestamp calculation) + * @param {number} [options.startDelay] - Milliseconds from now when the task should be ready to run (convenience for timestamp calculation). Overrides the priority's startDelay + * @param {number} [options.start] - DEPRECATED: Use startDelay instead * @param {AbortSignal} [options.signal] - AbortSignal to cancel the task * @param {number} [options.timeout] - Task timeout in milliseconds (for execution time limit) * @param {number} [options.expire] - Task expiration timestamp or milliseconds from now (for queue waiting time limit) - * @param {number} [options.delay] - DEPRECATED: Use postDelay instead. Delay after task completion before next task of same priority + * @param {number} [options.postDelay] - Delay after this task completes before the next task can start. Overrides the priority's postDelay; use -1 to bypass the current delay period + * @param {number} [options.delay] - DEPRECATED: Use postDelay instead * @param {boolean} [options.bypassDelay] - If true, skip any active delay period and start immediately * @param {string} [options.coalescingKey] - Key for task coalescing - tasks with same key will be coalesced within windows * @param {number} [options.coalescingWindowDuration] - Override coalescing window duration (task-level override of key-level and defaults) @@ -137,8 +140,8 @@ export declare class HoldMyTask extends EventEmitter { * // Bypass current delay for urgent task * const urgent = queue.enqueue(urgentTask, { priority: 10, bypassDelay: true }); * - * // Alternative: use delay: -1 to bypass - * const urgent2 = queue.enqueue(urgentTask, { priority: 10, delay: -1 }); + * // Alternative: use postDelay: -1 to bypass + * const urgent2 = queue.enqueue(urgentTask, { priority: 10, postDelay: -1 }); * * // Coalescing tasks - multiple device status checks become one * queue.enqueue(checkDeviceStatus, callback1, { coalescingKey: "device-123", coalescingWindowDuration: 1000 }); @@ -151,10 +154,12 @@ export declare class HoldMyTask extends EventEmitter { id?: string | number; priority?: number; timestamp?: number; + startDelay?: number; start?: number; signal?: AbortSignal; timeout?: number; expire?: number; + postDelay?: number; delay?: number; bypassDelay?: boolean; coalescingKey?: string; @@ -428,7 +433,7 @@ export declare class HoldMyTask extends EventEmitter { * // Check with task-level overrides * const effectiveConfig = queue.getCoalescingConfig('ui.update', { * coalescingWindowDuration: 50, - * delay: 30 // Still accepts old property names for backwards compatibility + * postDelay: 30 // Deprecated task-level name delay is still accepted * }); */ getCoalescingConfig(coalescingKey: string, taskOptions?: Object): Object; @@ -488,8 +493,8 @@ export declare class HoldMyTask extends EventEmitter { * * // Check with task-level overrides * const effectiveConfig = queue.getPriorityConfig(1, { - * delay: 50, - * start: 10 + * postDelay: 50, + * startDelay: 10 // Deprecated task-level names delay/start are still accepted * }); */ getPriorityConfig(priority: number, taskOptions?: Object): Object; diff --git a/types/src/hold-my-task.d.mts.map b/types/src/hold-my-task.d.mts.map index a191e06..4611d0a 100644 --- a/types/src/hold-my-task.d.mts.map +++ b/types/src/hold-my-task.d.mts.map @@ -1 +1 @@ -{"version":3,"file":"hold-my-task.d.mts","sourceRoot":"","sources":["../../src/hold-my-task.mjs"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"hold-my-task.d.mts","sourceRoot":"","sources":["../../src/hold-my-task.mjs"],"names":[],"mappings":"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"} \ No newline at end of file From 6ee16030fc1352727fe32567c52beb31a8f36a44 Mon Sep 17 00:00:00 2001 From: "cldmv-bot[bot]" <230771808+cldmv-bot[bot]@users.noreply.github.com> Date: Sun, 4 Oct 2026 00:29:29 +0000 Subject: [PATCH 02/16] chore: bump version to 2.0.5 --- package-lock.json | 4 +- package.json | 2 +- types/build.d.mts | 2 +- types/devcheck.d.mts | 2 +- types/examples/enhanced-coalescing-test.d.mts | 2 +- .../old/breaking-point-analysis.d.mts | 140 +-- types/examples/old/coalescing-analysis.d.mts | 2 +- .../examples/old/device-control-pattern.d.mts | 76 +- types/examples/old/device-simulation.d.mts | 120 +- types/examples/old/proper-delay-test.d.mts | 100 +- types/examples/old/run-volume-test.d.mts | 2 +- types/examples/old/test-analysis.d.mts | 2 +- types/examples/old/test-breaking-point.d.mts | 2 +- types/examples/old/test-device-control.d.mts | 2 +- .../examples/old/test-device-simulation.d.mts | 2 +- types/examples/old/test-proper-delays.d.mts | 2 +- types/examples/old/test-timing-analysis.d.mts | 2 +- types/examples/old/timing-analysis.d.mts | 58 +- .../examples/old/volume-coalescing-test.d.mts | 138 +- types/examples/priority-stress-test.d.mts | 109 +- types/examples/run-priority-stress-test.d.mts | 2 +- types/examples/unified-priority-test.d.mts | 2 +- types/index.d.mts | 2 +- types/src/hold-my-task.d.mts | 1108 ++++++++--------- types/src/utils.d.mts | 108 +- 25 files changed, 982 insertions(+), 1009 deletions(-) diff --git a/package-lock.json b/package-lock.json index e9fb258..b3b78bb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@cldmv/holdmytask", - "version": "2.0.4", + "version": "2.0.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@cldmv/holdmytask", - "version": "2.0.4", + "version": "2.0.5", "license": "Apache-2.0", "devDependencies": { "@cldmv/configs": "^1.2.0", diff --git a/package.json b/package.json index 1a82277..e6d0f07 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@cldmv/holdmytask", - "version": "2.0.4", + "version": "2.0.5", "description": "A tiny task queue that waits until your task is ready", "main": "./index.cjs", "module": "./index.mjs", diff --git a/types/build.d.mts b/types/build.d.mts index dbc4fc5..01edb98 100644 --- a/types/build.d.mts +++ b/types/build.d.mts @@ -13,4 +13,4 @@ * */ export {}; -//# sourceMappingURL=build.d.mts.map +//# sourceMappingURL=build.d.mts.map \ No newline at end of file diff --git a/types/devcheck.d.mts b/types/devcheck.d.mts index bc770aa..8c79ebe 100644 --- a/types/devcheck.d.mts +++ b/types/devcheck.d.mts @@ -13,4 +13,4 @@ * */ export {}; -//# sourceMappingURL=devcheck.d.mts.map +//# sourceMappingURL=devcheck.d.mts.map \ No newline at end of file diff --git a/types/examples/enhanced-coalescing-test.d.mts b/types/examples/enhanced-coalescing-test.d.mts index 4db2c23..7a81ab8 100644 --- a/types/examples/enhanced-coalescing-test.d.mts +++ b/types/examples/enhanced-coalescing-test.d.mts @@ -14,4 +14,4 @@ * */ export {}; -//# sourceMappingURL=enhanced-coalescing-test.d.mts.map +//# sourceMappingURL=enhanced-coalescing-test.d.mts.map \ No newline at end of file diff --git a/types/examples/old/breaking-point-analysis.d.mts b/types/examples/old/breaking-point-analysis.d.mts index 06809b9..d5cc602 100644 --- a/types/examples/old/breaking-point-analysis.d.mts +++ b/types/examples/old/breaking-point-analysis.d.mts @@ -16,84 +16,82 @@ * Device with configurable delays */ declare class ConfigurableDelayDevice { - volume: number; - commandCount: number; - infoRequestCount: number; - commandDelay: number; - infoDelay: number; - constructor(initialVolume?: number, commandDelay?: number, infoDelay?: number); - volumeCommand(change: any): Promise<{ - commandId: number; - oldVolume: number; - newVolume: number; - change: number; - processingTime: number; - }>; - getInfo(): Promise<{ - requestId: number; - volume: number; - timestamp: number; - totalCommands: number; - totalInfoRequests: number; - processingTime: number; - }>; - getStats(): { - currentVolume: number; - totalCommands: number; - totalInfoRequests: number; - }; + volume: number; + commandCount: number; + infoRequestCount: number; + commandDelay: number; + infoDelay: number; + constructor(initialVolume?: number, commandDelay?: number, infoDelay?: number); + volumeCommand(change: any): Promise<{ + commandId: number; + oldVolume: number; + newVolume: number; + change: number; + processingTime: number; + }>; + getInfo(): Promise<{ + requestId: number; + volume: number; + timestamp: number; + totalCommands: number; + totalInfoRequests: number; + processingTime: number; + }>; + getStats(): { + currentVolume: number; + totalCommands: number; + totalInfoRequests: number; + }; } /** * Controller for timing tests */ declare class TimingTestController { - device: any; - coalescingWindowDuration: number; - queue: any; - commandCounter: number; - results: any[]; - constructor(device: any, coalescingWindowDuration?: number); - volumeUp(amount?: number): Promise<{ - commandId: number; - userActionTime: number; - completionTime: number; - totalDuration: number; - volumeResult: any; - infoResult: any; - deviceVolumeAtCompletion: any; - infoReportsVolume: any; - isAccurate: boolean; - timingData: { - volumeQueueDelay: any; - volumeProcessingTime: any; - infoQueueDelay: any; - infoProcessingTime: any; - }; - }>; - getResults(): { - results: any[]; - deviceStats: any; - coalescingWindowDuration: number; - }; - destroy(): void; + device: any; + coalescingWindowDuration: number; + queue: any; + commandCounter: number; + results: any[]; + constructor(device: any, coalescingWindowDuration?: number); + volumeUp(amount?: number): Promise<{ + commandId: number; + userActionTime: number; + completionTime: number; + totalDuration: number; + volumeResult: any; + infoResult: any; + deviceVolumeAtCompletion: any; + infoReportsVolume: any; + isAccurate: boolean; + timingData: { + volumeQueueDelay: any; + volumeProcessingTime: any; + infoQueueDelay: any; + infoProcessingTime: any; + }; + }>; + getResults(): { + results: any[]; + deviceStats: any; + coalescingWindowDuration: number; + }; + destroy(): void; } /** * Test different device delays to find the breaking point */ -declare function findBreakingPoint(): Promise< - { - description: string | number; - commandDelay: string | number; - coalescingWindow: string | number; - accurateCommands: number; - inaccurateCommands: number; - accuracyRate: number; - maxDuration: number; - avgDuration: number; - deviceCommands: any; - deviceInfoRequests: any; - coalescingEfficiency: number; - }[] ->; +declare function findBreakingPoint(): Promise<{ + description: string | number; + commandDelay: string | number; + coalescingWindow: string | number; + accurateCommands: number; + inaccurateCommands: number; + accuracyRate: number; + maxDuration: number; + avgDuration: number; + deviceCommands: any; + deviceInfoRequests: any; + coalescingEfficiency: number; +}[]>; export { ConfigurableDelayDevice, TimingTestController, findBreakingPoint }; -//# sourceMappingURL=breaking-point-analysis.d.mts.map +//# sourceMappingURL=breaking-point-analysis.d.mts.map \ No newline at end of file diff --git a/types/examples/old/coalescing-analysis.d.mts b/types/examples/old/coalescing-analysis.d.mts index e816fbe..9b7add2 100644 --- a/types/examples/old/coalescing-analysis.d.mts +++ b/types/examples/old/coalescing-analysis.d.mts @@ -14,4 +14,4 @@ */ declare function analyzeApproaches(): Promise; export { analyzeApproaches }; -//# sourceMappingURL=coalescing-analysis.d.mts.map +//# sourceMappingURL=coalescing-analysis.d.mts.map \ No newline at end of file diff --git a/types/examples/old/device-control-pattern.d.mts b/types/examples/old/device-control-pattern.d.mts index 1e2c097..b4b5fa2 100644 --- a/types/examples/old/device-control-pattern.d.mts +++ b/types/examples/old/device-control-pattern.d.mts @@ -14,46 +14,46 @@ */ import { EventEmitter } from "events"; declare class DeviceController extends EventEmitter { - queue: any; - deviceState: { - volume: number; - lastUpdated: number; - }; - pendingVolumeChanges: Map; - constructor(); - /** - * User command: Volume Up - * This accumulates changes and triggers coalesced update - */ - volumeUp(amount?: number): Promise; - /** - * The actual device update task that gets executed (coalesced) - * This applies ALL accumulated changes at once - */ - updateDeviceInfo(coalescingKey: any): Promise<{ - volume: number; - lastUpdated: number; - }>; - /** - * Update system state after device change - */ - updateSystemState(): void; - /** - * Emit events for state changes - */ - emitStateChange(oldVolume: any, newVolume: any): void; - /** - * Get current device state - */ - getState(): { - volume: number; - lastUpdated: number; - }; - destroy(): void; + queue: any; + deviceState: { + volume: number; + lastUpdated: number; + }; + pendingVolumeChanges: Map; + constructor(); + /** + * User command: Volume Up + * This accumulates changes and triggers coalesced update + */ + volumeUp(amount?: number): Promise; + /** + * The actual device update task that gets executed (coalesced) + * This applies ALL accumulated changes at once + */ + updateDeviceInfo(coalescingKey: any): Promise<{ + volume: number; + lastUpdated: number; + }>; + /** + * Update system state after device change + */ + updateSystemState(): void; + /** + * Emit events for state changes + */ + emitStateChange(oldVolume: any, newVolume: any): void; + /** + * Get current device state + */ + getState(): { + volume: number; + lastUpdated: number; + }; + destroy(): void; } declare function demonstrateDeviceControl(): Promise; declare class AdvancedDeviceController extends DeviceController { - volumeUp(amount?: number): Promise; + volumeUp(amount?: number): Promise; } export { DeviceController, AdvancedDeviceController, demonstrateDeviceControl }; -//# sourceMappingURL=device-control-pattern.d.mts.map +//# sourceMappingURL=device-control-pattern.d.mts.map \ No newline at end of file diff --git a/types/examples/old/device-simulation.d.mts b/types/examples/old/device-simulation.d.mts index 05f551f..fb8be9b 100644 --- a/types/examples/old/device-simulation.d.mts +++ b/types/examples/old/device-simulation.d.mts @@ -17,79 +17,79 @@ import { EventEmitter } from "events"; * Simulated device that tracks its own state */ declare class PseudoDevice { - volume: number; - commandCount: number; - infoRequestCount: number; - constructor(initialVolume?: number); - /** - * Device receives a volume change command - */ - volumeCommand(change: any): Promise<{ - commandId: number; - oldVolume: number; - newVolume: number; - change: number; - }>; - /** - * Device responds to info request - */ - getInfo(): Promise<{ - requestId: number; - volume: number; - timestamp: number; - totalCommands: number; - totalInfoRequests: number; - }>; - /** - * Get device stats - */ - getStats(): { - currentVolume: number; - totalCommands: number; - totalInfoRequests: number; - }; + volume: number; + commandCount: number; + infoRequestCount: number; + constructor(initialVolume?: number); + /** + * Device receives a volume change command + */ + volumeCommand(change: any): Promise<{ + commandId: number; + oldVolume: number; + newVolume: number; + change: number; + }>; + /** + * Device responds to info request + */ + getInfo(): Promise<{ + requestId: number; + volume: number; + timestamp: number; + totalCommands: number; + totalInfoRequests: number; + }>; + /** + * Get device stats + */ + getStats(): { + currentVolume: number; + totalCommands: number; + totalInfoRequests: number; + }; } /** * Controller that uses the queue system to communicate with the device */ declare class DeviceController extends EventEmitter { - device: any; - queue: any; - optimisticVolume: any; - pendingChanges: number; - constructor(device: any, queueOptions?: {}); - /** - * User calls volumeUp - this should update device and then get fresh info - */ - volumeUp(amount?: number): Promise<{ - volumeCommand: any; - deviceInfo: any; - optimisticVolume: any; - pendingChanges: number; - }>; - /** - * Get current state - */ - getState(): { - optimisticVolume: any; - pendingChanges: number; - deviceStats: any; - }; - destroy(): void; + device: any; + queue: any; + optimisticVolume: any; + pendingChanges: number; + constructor(device: any, queueOptions?: {}); + /** + * User calls volumeUp - this should update device and then get fresh info + */ + volumeUp(amount?: number): Promise<{ + volumeCommand: any; + deviceInfo: any; + optimisticVolume: any; + pendingChanges: number; + }>; + /** + * Get current state + */ + getState(): { + optimisticVolume: any; + pendingChanges: number; + deviceStats: any; + }; + destroy(): void; } /** * Test scenario: Rapid volume commands */ declare function testRapidVolumeCommands(): Promise<{ - expectedVolume: number; - actualVolume: number; - totalCommands: number; - totalInfoRequests: number; - success: boolean; + expectedVolume: number; + actualVolume: number; + totalCommands: number; + totalInfoRequests: number; + success: boolean; }>; /** * Test different coalescing configurations */ declare function testCoalescingConfigurations(): Promise; export { PseudoDevice, DeviceController, testRapidVolumeCommands, testCoalescingConfigurations }; -//# sourceMappingURL=device-simulation.d.mts.map +//# sourceMappingURL=device-simulation.d.mts.map \ No newline at end of file diff --git a/types/examples/old/proper-delay-test.d.mts b/types/examples/old/proper-delay-test.d.mts index 082baa5..6f70d3e 100644 --- a/types/examples/old/proper-delay-test.d.mts +++ b/types/examples/old/proper-delay-test.d.mts @@ -13,65 +13,65 @@ * */ declare class SimpleDevice { - volume: number; - commandCount: number; - infoRequestCount: number; - constructor(initialVolume?: number); - volumeCommand(change: any): Promise<{ - commandId: number; - oldVolume: number; - newVolume: number; - change: number; - }>; - getInfo(): Promise<{ - requestId: number; - volume: number; - timestamp: number; - totalCommands: number; - totalInfoRequests: number; - }>; - getStats(): { - currentVolume: number; - totalCommands: number; - totalInfoRequests: number; - }; + volume: number; + commandCount: number; + infoRequestCount: number; + constructor(initialVolume?: number); + volumeCommand(change: any): Promise<{ + commandId: number; + oldVolume: number; + newVolume: number; + change: number; + }>; + getInfo(): Promise<{ + requestId: number; + volume: number; + timestamp: number; + totalCommands: number; + totalInfoRequests: number; + }>; + getStats(): { + currentVolume: number; + totalCommands: number; + totalInfoRequests: number; + }; } /** * Controller using proper queue delays */ declare class ProperDelayController { - device: any; - queue: any; - commandCounter: number; - constructor(device: any); - volumeUp(amount?: number): Promise<{ - commandId: number; - startTime: number; - endTime: number; - duration: number; - volumeResult: any; - infoResult: any; - deviceVolumeAtEnd: any; - infoReportsVolume: any; - isAccurate: boolean; - }>; - getQueueInfo(): { - pendingCount: any; - runningCount: any; - completedCount: any; - }; - destroy(): void; + device: any; + queue: any; + commandCounter: number; + constructor(device: any); + volumeUp(amount?: number): Promise<{ + commandId: number; + startTime: number; + endTime: number; + duration: number; + volumeResult: any; + infoResult: any; + deviceVolumeAtEnd: any; + infoReportsVolume: any; + isAccurate: boolean; + }>; + getQueueInfo(): { + pendingCount: any; + runningCount: any; + completedCount: any; + }; + destroy(): void; } /** * Test the proper delay scenario */ declare function testProperDelays(): Promise<{ - totalAccurate: number; - totalTests: number; - accuracyRate: number; - deviceCommands: number; - deviceInfoRequests: number; - finalVolume: number; + totalAccurate: number; + totalTests: number; + accuracyRate: number; + deviceCommands: number; + deviceInfoRequests: number; + finalVolume: number; }>; export { SimpleDevice, ProperDelayController, testProperDelays }; -//# sourceMappingURL=proper-delay-test.d.mts.map +//# sourceMappingURL=proper-delay-test.d.mts.map \ No newline at end of file diff --git a/types/examples/old/run-volume-test.d.mts b/types/examples/old/run-volume-test.d.mts index deef1ba..e3a9d42 100644 --- a/types/examples/old/run-volume-test.d.mts +++ b/types/examples/old/run-volume-test.d.mts @@ -13,4 +13,4 @@ * */ export {}; -//# sourceMappingURL=run-volume-test.d.mts.map +//# sourceMappingURL=run-volume-test.d.mts.map \ No newline at end of file diff --git a/types/examples/old/test-analysis.d.mts b/types/examples/old/test-analysis.d.mts index d5fbc33..e4c25b7 100644 --- a/types/examples/old/test-analysis.d.mts +++ b/types/examples/old/test-analysis.d.mts @@ -13,4 +13,4 @@ * */ export {}; -//# sourceMappingURL=test-analysis.d.mts.map +//# sourceMappingURL=test-analysis.d.mts.map \ No newline at end of file diff --git a/types/examples/old/test-breaking-point.d.mts b/types/examples/old/test-breaking-point.d.mts index b099948..58afce8 100644 --- a/types/examples/old/test-breaking-point.d.mts +++ b/types/examples/old/test-breaking-point.d.mts @@ -13,4 +13,4 @@ * */ export {}; -//# sourceMappingURL=test-breaking-point.d.mts.map +//# sourceMappingURL=test-breaking-point.d.mts.map \ No newline at end of file diff --git a/types/examples/old/test-device-control.d.mts b/types/examples/old/test-device-control.d.mts index ded4c37..29c1485 100644 --- a/types/examples/old/test-device-control.d.mts +++ b/types/examples/old/test-device-control.d.mts @@ -13,4 +13,4 @@ * */ export {}; -//# sourceMappingURL=test-device-control.d.mts.map +//# sourceMappingURL=test-device-control.d.mts.map \ No newline at end of file diff --git a/types/examples/old/test-device-simulation.d.mts b/types/examples/old/test-device-simulation.d.mts index 1987396..c1a716f 100644 --- a/types/examples/old/test-device-simulation.d.mts +++ b/types/examples/old/test-device-simulation.d.mts @@ -13,4 +13,4 @@ * */ export {}; -//# sourceMappingURL=test-device-simulation.d.mts.map +//# sourceMappingURL=test-device-simulation.d.mts.map \ No newline at end of file diff --git a/types/examples/old/test-proper-delays.d.mts b/types/examples/old/test-proper-delays.d.mts index a86d081..7e2bc9e 100644 --- a/types/examples/old/test-proper-delays.d.mts +++ b/types/examples/old/test-proper-delays.d.mts @@ -13,4 +13,4 @@ * */ export {}; -//# sourceMappingURL=test-proper-delays.d.mts.map +//# sourceMappingURL=test-proper-delays.d.mts.map \ No newline at end of file diff --git a/types/examples/old/test-timing-analysis.d.mts b/types/examples/old/test-timing-analysis.d.mts index 0315938..9df23a4 100644 --- a/types/examples/old/test-timing-analysis.d.mts +++ b/types/examples/old/test-timing-analysis.d.mts @@ -13,4 +13,4 @@ * */ export {}; -//# sourceMappingURL=test-timing-analysis.d.mts.map +//# sourceMappingURL=test-timing-analysis.d.mts.map \ No newline at end of file diff --git a/types/examples/old/timing-analysis.d.mts b/types/examples/old/timing-analysis.d.mts index b634aaa..05b93cc 100644 --- a/types/examples/old/timing-analysis.d.mts +++ b/types/examples/old/timing-analysis.d.mts @@ -23,40 +23,40 @@ import { DeviceController } from "./device-simulation.mjs"; * Enhanced controller that logs detailed timing information */ declare class TimingAnalysisController extends DeviceController { - commandCounter: number; - timingLog: any[]; - constructor(device: any, queueOptions?: {}); - volumeUp(amount?: number): Promise<{ - commandId: number; - startTime: number; - endTime: number; - totalDuration: number; - volumeCommand: any; - infoResult: any; - expectedVolume: any; - reportedVolume: any; - }>; - getTimingAnalysis(): { - timingLog: any[]; - deviceStats: any; - }; + commandCounter: number; + timingLog: any[]; + constructor(device: any, queueOptions?: {}); + volumeUp(amount?: number): Promise<{ + commandId: number; + startTime: number; + endTime: number; + totalDuration: number; + volumeCommand: any; + infoResult: any; + expectedVolume: any; + reportedVolume: any; + }>; + getTimingAnalysis(): { + timingLog: any[]; + deviceStats: any; + }; } /** * Test with reference counting approach */ declare class ReferenceCountingController extends DeviceController { - pendingVolumeCommands: Map; - commandCounter: number; - constructor(device: any, queueOptions?: {}); - volumeUp(amount?: number): Promise<{ - commandId: number; - volumeResult: any; - infoResult: any; - expectedVolume: any; - reportedVolume: any; - accurate: boolean; - }>; + pendingVolumeCommands: Map; + commandCounter: number; + constructor(device: any, queueOptions?: {}); + volumeUp(amount?: number): Promise<{ + commandId: number; + volumeResult: any; + infoResult: any; + expectedVolume: any; + reportedVolume: any; + accurate: boolean; + }>; } declare function analyzeTimingIssues(): Promise; export { TimingAnalysisController, ReferenceCountingController, analyzeTimingIssues }; -//# sourceMappingURL=timing-analysis.d.mts.map +//# sourceMappingURL=timing-analysis.d.mts.map \ No newline at end of file diff --git a/types/examples/old/volume-coalescing-test.d.mts b/types/examples/old/volume-coalescing-test.d.mts index 32c09fc..0bc063e 100644 --- a/types/examples/old/volume-coalescing-test.d.mts +++ b/types/examples/old/volume-coalescing-test.d.mts @@ -16,86 +16,78 @@ * Simple volume system that tracks state */ declare class VolumeSystem { - volume: number; - commandCount: number; - updateCount: number; - log: any[]; - constructor(initialVolume?: number); - /** - * Execute a volume command (changes the actual volume) - */ - executeVolumeCommand( - change: any, - commandId: any - ): Promise<{ - commandId: any; - oldVolume: number; - newVolume: number; - change: number; - executionTime: number; - }>; - /** - * Execute an update command (reports current state) - */ - executeUpdateCommand(updateId: any): Promise<{ - updateId: any; - volume: number; - timestamp: number; - totalCommands: number; - totalUpdates: number; - }>; - getState(): { - currentVolume: number; - totalCommands: number; - totalUpdates: number; - }; - getLog(): any[]; - clearLog(): void; + volume: number; + commandCount: number; + updateCount: number; + log: any[]; + constructor(initialVolume?: number); + /** + * Execute a volume command (changes the actual volume) + */ + executeVolumeCommand(change: any, commandId: any): Promise<{ + commandId: any; + oldVolume: number; + newVolume: number; + change: number; + executionTime: number; + }>; + /** + * Execute an update command (reports current state) + */ + executeUpdateCommand(updateId: any): Promise<{ + updateId: any; + volume: number; + timestamp: number; + totalCommands: number; + totalUpdates: number; + }>; + getState(): { + currentVolume: number; + totalCommands: number; + totalUpdates: number; + }; + getLog(): any[]; + clearLog(): void; } /** * Controller that implements volume commands with coalesced updates */ declare class VolumeController { - volumeSystem: any; - queue: any; - commandCounter: number; - constructor(volumeSystem: any, queueOptions?: {}); - /** - * Volume up command with coalesced update - */ - volumeUp( - amount?: number, - options?: {} - ): Promise<{ - commandId: number; - userActionTime: number; - endTime: number; - totalDuration: number; - volumeResult: any; - updateResult: any; - systemVolumeAtEnd: any; - updateReportsVolume: any; - isAccurate: boolean; - options: {}; - }>; - destroy(): void; + volumeSystem: any; + queue: any; + commandCounter: number; + constructor(volumeSystem: any, queueOptions?: {}); + /** + * Volume up command with coalesced update + */ + volumeUp(amount?: number, options?: {}): Promise<{ + commandId: number; + userActionTime: number; + endTime: number; + totalDuration: number; + volumeResult: any; + updateResult: any; + systemVolumeAtEnd: any; + updateReportsVolume: any; + isAccurate: boolean; + options: {}; + }>; + destroy(): void; } /** * Test different timing scenarios */ -declare function testVolumeCoalescing(): Promise< - { - scenario: string; - totalDuration: number; - accurateCommands: number; - totalCommands: number; - accuracyRate: number; - finalVolume: number; - expectedVolume: number; - volumeCommandsExecuted: number; - updateCommandsExecuted: number; - coalescingEfficiency: number; - }[] ->; +declare function testVolumeCoalescing(): Promise<{ + scenario: string; + totalDuration: number; + accurateCommands: number; + totalCommands: number; + accuracyRate: number; + finalVolume: number; + expectedVolume: number; + volumeCommandsExecuted: number; + updateCommandsExecuted: number; + coalescingEfficiency: number; +}[]>; export { VolumeSystem, VolumeController, testVolumeCoalescing }; -//# sourceMappingURL=volume-coalescing-test.d.mts.map +//# sourceMappingURL=volume-coalescing-test.d.mts.map \ No newline at end of file diff --git a/types/examples/priority-stress-test.d.mts b/types/examples/priority-stress-test.d.mts index ffd2ebd..adb58a1 100644 --- a/types/examples/priority-stress-test.d.mts +++ b/types/examples/priority-stress-test.d.mts @@ -16,70 +16,65 @@ * Volume system with realistic timing */ declare class RealisticVolumeSystem { - volume: number; - commandCount: number; - updateCount: number; - log: any[]; - constructor(initialVolume?: number); - executeVolumeCommand( - change: any, - commandId: any - ): Promise<{ - commandId: any; - oldVolume: number; - newVolume: number; - change: number; - executionTime: number; - processingTime: number; - }>; - executeUpdateCommand(updateId: any): Promise<{ - updateId: any; - volume: number; - timestamp: number; - totalCommands: number; - totalUpdates: number; - processingTime: number; - }>; - getState(): { - currentVolume: number; - totalCommands: number; - totalUpdates: number; - }; - getLog(): any[]; - clearLog(): void; + volume: number; + commandCount: number; + updateCount: number; + log: any[]; + constructor(initialVolume?: number); + executeVolumeCommand(change: any, commandId: any): Promise<{ + commandId: any; + oldVolume: number; + newVolume: number; + change: number; + executionTime: number; + processingTime: number; + }>; + executeUpdateCommand(updateId: any): Promise<{ + updateId: any; + volume: number; + timestamp: number; + totalCommands: number; + totalUpdates: number; + processingTime: number; + }>; + getState(): { + currentVolume: number; + totalCommands: number; + totalUpdates: number; + }; + getLog(): any[]; + clearLog(): void; } /** * Realistic volume controller with proper priorities and delays */ declare class PriorityVolumeController { - volumeSystem: any; - queue: any; - commandCounter: number; - constructor(volumeSystem: any, queueOptions?: {}); - /** - * Volume up with realistic "fire and forget" pattern - * REAL-WORLD PATTERN: Volume task enqueues update task AFTER completing volume change - */ - volumeUp(amount?: number, options?: {}): any; - destroy(): void; + volumeSystem: any; + queue: any; + commandCounter: number; + constructor(volumeSystem: any, queueOptions?: {}); + /** + * Volume up with realistic "fire and forget" pattern + * REAL-WORLD PATTERN: Volume task enqueues update task AFTER completing volume change + */ + volumeUp(amount?: number, options?: {}): any; + destroy(): void; } /** * Stress test scenarios */ -declare function runPriorityStressTests(): Promise< - { - scenario: string; - totalDuration: number; - accurateCommands: any; - totalCommands: number; - accuracyRate: number; - finalVolume: number; - expectedVolume: number; - volumeCommandsExecuted: number; - updateCommandsExecuted: number; - coalescingEfficiency: number; - averageCommandDuration: number; - }[] ->; +declare function runPriorityStressTests(): Promise<{ + scenario: string; + totalDuration: number; + accurateCommands: any; + totalCommands: number; + accuracyRate: number; + finalVolume: number; + expectedVolume: number; + volumeCommandsExecuted: number; + updateCommandsExecuted: number; + coalescingEfficiency: number; + averageCommandDuration: number; +}[]>; export { RealisticVolumeSystem, PriorityVolumeController, runPriorityStressTests }; -//# sourceMappingURL=priority-stress-test.d.mts.map +//# sourceMappingURL=priority-stress-test.d.mts.map \ No newline at end of file diff --git a/types/examples/run-priority-stress-test.d.mts b/types/examples/run-priority-stress-test.d.mts index 72a5d46..d6896e5 100644 --- a/types/examples/run-priority-stress-test.d.mts +++ b/types/examples/run-priority-stress-test.d.mts @@ -13,4 +13,4 @@ * */ export {}; -//# sourceMappingURL=run-priority-stress-test.d.mts.map +//# sourceMappingURL=run-priority-stress-test.d.mts.map \ No newline at end of file diff --git a/types/examples/unified-priority-test.d.mts b/types/examples/unified-priority-test.d.mts index 9f45f7c..857a1e0 100644 --- a/types/examples/unified-priority-test.d.mts +++ b/types/examples/unified-priority-test.d.mts @@ -14,4 +14,4 @@ * */ export {}; -//# sourceMappingURL=unified-priority-test.d.mts.map +//# sourceMappingURL=unified-priority-test.d.mts.map \ No newline at end of file diff --git a/types/index.d.mts b/types/index.d.mts index b15d2ee..f5345bc 100644 --- a/types/index.d.mts +++ b/types/index.d.mts @@ -45,4 +45,4 @@ export { HoldMyTask as TaskManager }; export { HoldMyTask as TaskQueue }; export { HoldMyTask as QueueManager }; export { HoldMyTask as TaskProcessor }; -//# sourceMappingURL=index.d.mts.map +//# sourceMappingURL=index.d.mts.map \ No newline at end of file diff --git a/types/src/hold-my-task.d.mts b/types/src/hold-my-task.d.mts index 59ba973..e5a470c 100644 --- a/types/src/hold-my-task.d.mts +++ b/types/src/hold-my-task.d.mts @@ -21,564 +21,552 @@ import { MinHeap } from "./utils.mjs"; * @extends EventEmitter */ export declare class HoldMyTask extends EventEmitter { - _syncMode: boolean | undefined; - options: - | { - constructor: Function; - toString(): string; - toLocaleString(): string; - valueOf(): Object; - hasOwnProperty(v: PropertyKey): boolean; - isPrototypeOf(v: Object): boolean; - propertyIsEnumerable(v: PropertyKey): boolean; - concurrency: number; - tick: number; - autoStart: boolean; - defaultPriority: number; - maxQueue: any; - priorities: {}; - smartScheduling: boolean; - healingInterval: number; - coalescing: { - defaults: Object; - keys: {}; - }; - coalescingWindowDuration: any; - coalescingMaxDelay: any; - coalescingMultipleCallbacks: any; - coalescingResolveAllPromises: any; - } - | undefined; - pendingHeap: MinHeap | undefined; - readyHeap: MinHeap | undefined; - running: Set | undefined; - runningByPriority: Map | undefined; - tasks: Map | undefined; - nextId: number | undefined; - enqueueSeq: number | undefined; - isActive: boolean | undefined; - destroyed: boolean | undefined; - lastCompletedPriority: any; - nextAvailableTime: any; - schedulerTimeout: number | null | undefined; - healingInterval: number | null | undefined; - lastSchedulerRun: number | undefined; - intervalId: number | null | undefined; - coalescingGroups: Map | undefined; - coalescingRepresentatives: Map | undefined; - nextGroupId: number | undefined; - _warnedTaskOptions: Set | undefined; - timeoutId: number | undefined; - constructor(options?: {}); - /** - * Synchronous initialization for backwards compatibility - * @private - * @param {Object} options - Configuration options - */ - private _initializeSync; - /** - * Asynchronous initialization for modern usage - * @private - * @param {Object} options - Configuration options - * @returns {Promise} Promise that resolves to this instance - */ - private _initializeAsync; - /** - * Common initialization logic used by both sync and async modes - * @private - * @param {Object} options - Configuration options - */ - private _initializeCommon; - /** - * Internal convenience method to create a new HoldMyTask instance with async initialization. - * This enables event listeners to be attached before validation errors can occur. - * @param {Object} [options={}] - Configuration options - * @returns {Promise} Promise that resolves to the initialized instance - * @private - * @example - * // Internal usage - prefer new HoldMyTask({ sync: false }) for public API - * const queue = await HoldMyTask._create({ maxQueue: 100 }); - */ - private static _create; - /** - * Adds a task to the queue for execution. Supports both callback and promise-based APIs. - * @param {Function} task - The task function to execute. Can be sync or async. - * @param {Function|Object} [optionsOrCallback] - Either a callback function or options object - * @param {Object} [options={}] - Additional options (if callback was provided as second parameter) - * @param {string|number} [options.id] - Custom task ID for identification and later reference (must be unique) - * @param {number} [options.priority] - Task priority (higher numbers run first) - * @param {number} [options.timestamp] - When the task should be ready to run (milliseconds since epoch) - * @param {number} [options.startDelay] - Milliseconds from now when the task should be ready to run (convenience for timestamp calculation). Overrides the priority's startDelay - * @param {number} [options.start] - DEPRECATED: Use startDelay instead - * @param {AbortSignal} [options.signal] - AbortSignal to cancel the task - * @param {number} [options.timeout] - Task timeout in milliseconds (for execution time limit) - * @param {number} [options.expire] - Task expiration timestamp or milliseconds from now (for queue waiting time limit) - * @param {number} [options.postDelay] - Delay after this task completes before the next task can start. Overrides the priority's postDelay; use -1 to bypass the current delay period - * @param {number} [options.delay] - DEPRECATED: Use postDelay instead - * @param {boolean} [options.bypassDelay] - If true, skip any active delay period and start immediately - * @param {string} [options.coalescingKey] - Key for task coalescing - tasks with same key will be coalesced within windows - * @param {number} [options.coalescingWindowDuration] - Override coalescing window duration (task-level override of key-level and defaults) - * @param {number} [options.coalescingMaxDelay] - Override coalescing max delay (task-level override of key-level and defaults) - * @param {boolean} [options.coalescingMultipleCallbacks] - Override callback behavior (task-level override of key-level and defaults) - * @param {boolean} [options.coalescingResolveAllPromises] - Override promise resolution behavior (task-level override of key-level and defaults) - * @param {*} [options.metadata] - Arbitrary metadata to attach to the task - * @returns {Promise|Object} Promise (if no callback) or task control object with id, cancel, status methods - * @throws {Error} If queue is destroyed or full - * @example - * // Promise API - * const result = await queue.enqueue(async () => fetchData()); - * - * // Callback API - * queue.enqueue(() => processData(), (err, result) => { - * if (err) console.error(err); - * else console.log(result); - * }); - * - * // With options - * const task = queue.enqueue(myTask, { priority: 5, timeout: 30000, expire: 10000 }); - * - * // Bypass current delay for urgent task - * const urgent = queue.enqueue(urgentTask, { priority: 10, bypassDelay: true }); - * - * // Alternative: use postDelay: -1 to bypass - * const urgent2 = queue.enqueue(urgentTask, { priority: 10, postDelay: -1 }); - * - * // Coalescing tasks - multiple device status checks become one - * queue.enqueue(checkDeviceStatus, callback1, { coalescingKey: "device-123", coalescingWindowDuration: 1000 }); - * queue.enqueue(checkDeviceStatus, callback2, { coalescingKey: "device-123" }); // Gets coalesced with first - */ - enqueue( - task: Function, - optionsOrCallback?: Function | Object, - options?: { - id?: string | number; - priority?: number; - timestamp?: number; - startDelay?: number; - start?: number; - signal?: AbortSignal; - timeout?: number; - expire?: number; - postDelay?: number; - delay?: number; - bypassDelay?: boolean; - coalescingKey?: string; - coalescingWindowDuration?: number; - coalescingMaxDelay?: number; - coalescingMultipleCallbacks?: boolean; - coalescingResolveAllPromises?: boolean; - metadata?: any; - } - ): Promise | Object; - /** - * Cancels a pending task by ID. - * @param {string} id - The task ID to cancel - * @param {string} [reason="Task canceled"] - Reason for cancellation - * @returns {boolean} True if task was found and cancelled, false otherwise - * @example - * const task = queue.enqueue(() => longRunningTask()); - * const cancelled = queue.cancel(task.id, "User requested cancellation"); - */ - cancel(id: string, reason?: string): boolean; - /** - * Alias for cancel() method for backward compatibility. - * @param {string|number} id - The task ID to cancel - * @param {string} [reason="Task canceled"] - Reason for cancellation - * @returns {boolean} True if task was found and cancelled, false otherwise - */ - cancelTask(id: string | number, reason?: string): boolean; - /** - * Pauses the task queue, stopping execution of new tasks. - * Currently running tasks will continue to completion. - * @returns {void} - * @example - * queue.pause(); - * // Queue stops processing new tasks - */ - pause(): void; - /** - * Resumes the task queue after being paused. - * @returns {void} - * @example - * queue.resume(); - * // Queue resumes processing tasks - */ - resume(): void; - /** - * Clears all pending and ready tasks from the queue. - * Currently running tasks will continue to completion. - * @returns {void} - * @example - * queue.clear(); - * // All queued tasks are removed - */ - clear(): void; - /** - * Returns the number of tasks in the queue. - * @returns {number} Number of tasks in the queue - * @example - * const totalTasks = queue.size(); // 5 - */ - size(): number; - /** - * Returns the number of tasks in the queue (alias for size()). - * @returns {number} Number of tasks in the queue - * @example - * const totalTasks = queue.length(); // 5 - */ - length(): number; - /** - * Returns the number of currently running tasks. - * @returns {number} Number of running tasks - * @example - * const runningTasks = queue.inflight(); // 2 - */ - inflight(): number; - /** - * Gets information about a coalescing group by key and group ID. - * @param {string} coalescingKey - The coalescing key - * @param {string} [groupId] - Optional group ID. If omitted, returns all groups for the key - * @returns {Object|Array|null} Group info object, array of groups, or null if not found - * @example - * // Get all groups for a coalescing key - * const groups = queue.getCoalescingGroup('ui.update'); - * - * // Get specific group by ID - * const group = queue.getCoalescingGroup('ui.update', '1'); - * console.log(group.tasks.size); // Number of tasks in group - * - * // Access individual task metadata - * for (const [taskId, task] of group.tasks) { - * console.log(`Task ${taskId}:`, task.metadata); - * } - */ - getCoalescingGroup(coalescingKey: string, groupId?: string): Object | any[] | null; - /** - * Gets metadata for all tasks in a coalescing group. - * @param {string} coalescingKey - The coalescing key - * @param {string} [groupId] - Optional group ID. If omitted, returns metadata from all groups for the key - * @returns {Array} Array of metadata objects with task IDs - * @example - * // Get metadata from all groups for a key - * const allMetadata = queue.getCoalescingGroupMetadata('ui.update'); - * - * // Get metadata from specific group - * const groupMetadata = queue.getCoalescingGroupMetadata('ui.update', '1'); - * - * // Example output: - * // [ - * // { taskId: '123', metadata: { userId: 100, action: 'save' } }, - * // { taskId: '124', metadata: { userId: 200, action: 'delete' } } - * // ] - */ - getCoalescingGroupMetadata(coalescingKey: string, groupId?: string): any[]; - /** - * Gets a summary of all active coalescing groups. - * @returns {Object} Summary object with coalescing key stats - * @example - * const summary = queue.getCoalescingGroupsSummary(); - * console.log(summary); - * // { - * // 'ui.update': { groupCount: 2, totalTasks: 5 }, - * // 'api.batch': { groupCount: 1, totalTasks: 3 } - * // } - */ - getCoalescingGroupsSummary(): Object; - /** - * Finds the coalescing group that contains a specific task ID. - * @param {string|number} taskId - The task ID to search for - * @returns {Object|null} Group information including the task's metadata, or null if not found - * @example - * const groupInfo = queue.findCoalescingGroupByTaskId('123'); - * if (groupInfo) { - * console.log('Task is in group:', groupInfo.groupId); - * console.log('Task metadata:', groupInfo.task.metadata); - * console.log('Other tasks in group:', groupInfo.groupTasks.length); - * } - */ - findCoalescingGroupByTaskId(taskId: string | number): Object | null; - /** - * Destroys the queue, canceling all tasks and stopping the scheduler. - * Once destroyed, the queue cannot be reused. - * @returns {void} - * @example - * queue.destroy(); - * // Queue is permanently shut down - */ - destroy(): void; - /** - * Returns the current timestamp in milliseconds. - * @returns {number} Current timestamp - * @example - * const timestamp = queue.now(); // 1699564800000 - */ - now(): number; - /** - * Main scheduler tick that moves ready tasks and starts execution. - * @returns {void} - * @private - */ - private schedulerTick; - /** - * Checks if a task can start based on both global and per-priority concurrency limits. - * @param {Object} task - The task to check - * @returns {boolean} True if the task can start, false if concurrency limits prevent it - * @private - */ - private _canStartTask; - /** - * Clears all active timers (intervals and timeouts). - * @returns {void} - * @private - */ - private clearTimers; - /** - * Calculates when the next scheduler run should happen and sets appropriate timeout. - * @private - * @returns {void} - * - * @description - * Smart scheduling that calculates the optimal time for the next scheduler run based on: - * - When the next pending task becomes ready - * - When delay periods end - * - Whether there are tasks that can run immediately - * - * Uses setTimeout for precise timing instead of constant polling intervals. - */ - private scheduleSmartTimeout; - /** - * Runs the main scheduler logic and reschedules if needed. - * @private - * @returns {void} - * - * @description - * Executes the scheduler tick logic and then determines if more scheduling is needed. - * Tracks when scheduler last ran for healing mechanism. - */ - private runScheduler; - /** - * Starts the self-healing interval that ensures scheduler continues working. - * @private - * @returns {void} - * - * @description - * Healing mechanism that periodically checks if the scheduler should be running - * but isn't due to timeout failures or other issues. Runs every healingInterval milliseconds. - */ - private startHealingInterval; - /** - * Clears all scheduler-related timers. - * @private - * @returns {void} - * - * @description - * Cleans up both the main scheduler timeout and the healing interval timer. - */ - private clearSchedulerTimers; - /** - * Configure coalescing settings for specific keys dynamically. - * @param {string} coalescingKey - The coalescing key to configure - * @param {Object} config - Configuration for this key - * @param {number} [config.windowDuration] - Window duration in milliseconds for this key - * @param {number} [config.maxDelay] - Maximum delay in milliseconds for this key - * @param {number} [config.postDelay] - Post-completion delay in milliseconds for this key - * @param {number} [config.startDelay] - Pre-execution delay in milliseconds for this key - * @param {number} [config.delay] - DEPRECATED: Use postDelay instead - * @param {number} [config.start] - DEPRECATED: Use startDelay instead - * @param {boolean} [config.multipleCallbacks] - Whether to call multiple callbacks for this key - * @param {boolean} [config.resolveAllPromises] - Whether to resolve all promises for this key - * @returns {void} - * - * @example - * // Configure specific keys after queue creation - * queue.configureCoalescingKey('ui.update', { - * windowDuration: 100, - * maxDelay: 500, - * postDelay: 25, - * startDelay: 0 - * }); - * - * queue.configureCoalescingKey('api.batch', { - * windowDuration: 1000, - * maxDelay: 5000, - * postDelay: 100, - * startDelay: 200, - * resolveAllPromises: false - * }); - */ - configureCoalescingKey( - coalescingKey: string, - config: { - windowDuration?: number; - maxDelay?: number; - postDelay?: number; - startDelay?: number; - delay?: number; - start?: number; - multipleCallbacks?: boolean; - resolveAllPromises?: boolean; - } - ): void; - /** - * Get the effective coalescing configuration for a specific key. - * @param {string} coalescingKey - The coalescing key to get configuration for - * @param {Object} [taskOptions={}] - Task-level options that may override key configuration - * @returns {Object} The effective configuration for this key - * - * @example - * // Get effective configuration for a key - * const config = queue.getCoalescingConfig('ui.update'); - * console.log(`UI updates coalesce within ${config.windowDuration}ms with ${config.postDelay}ms post-completion delay`); - * - * // Check with task-level overrides - * const effectiveConfig = queue.getCoalescingConfig('ui.update', { - * coalescingWindowDuration: 50, - * postDelay: 30 // Deprecated task-level name delay is still accepted - * }); - */ - getCoalescingConfig(coalescingKey: string, taskOptions?: Object): Object; - /** - * Get all configured coalescing keys and their configurations. - * @returns {Object} Map of coalescingKey to configuration - * - * @example - * // See all configured coalescing keys - * const allConfigs = queue.getCoalescingConfigurations(); - * Object.entries(allConfigs).forEach(([key, config]) => { - * console.log(`${key}: ${config.windowDuration}ms window, ${config.maxDelay}ms max delay, ${config.postDelay}ms post-completion delay, ${config.startDelay}ms pre-execution delay`); - * }); - */ - getCoalescingConfigurations(): Object; - /** - * Configure default settings for specific priorities dynamically. - * @param {number} priority - The priority level to configure - * @param {Object} config - Configuration for this priority - * @param {number} [config.postDelay] - Default post-completion delay in milliseconds for this priority - * @param {number} [config.startDelay] - Default pre-execution delay in milliseconds for this priority - * @param {number} [config.delay] - DEPRECATED: Use postDelay instead - * @param {number} [config.start] - DEPRECATED: Use startDelay instead - * @returns {void} - * - * @example - * // Configure priority defaults after queue creation - * queue.configurePriority(1, { - * postDelay: 100, // High priority tasks have 100ms delay after completion - * startDelay: 0 // High priority tasks start immediately - * }); - * - * queue.configurePriority(3, { - * postDelay: 0, // Low priority tasks have no delay after completion - * startDelay: 200 // Low priority tasks wait 200ms before starting - * }); - */ - configurePriority( - priority: number, - config: { - postDelay?: number; - startDelay?: number; - delay?: number; - start?: number; - } - ): void; - /** - * Get the effective configuration for a specific priority. - * @param {number} priority - The priority level to get configuration for - * @param {Object} [taskOptions={}] - Task-level options that may override priority configuration - * @returns {Object} The effective configuration for this priority - * - * @example - * // Get effective configuration for a priority - * const config = queue.getPriorityConfig(1); - * console.log(`Priority 1 tasks: ${config.delay}ms delay, ${config.start}ms start delay`); - * - * // Check with task-level overrides - * const effectiveConfig = queue.getPriorityConfig(1, { - * postDelay: 50, - * startDelay: 10 // Deprecated task-level names delay/start are still accepted - * }); - */ - getPriorityConfig(priority: number, taskOptions?: Object): Object; - /** - * Get all configured priorities and their configurations. - * @returns {Object} Map of priority to configuration - * - * @example - * // See all configured priorities - * const allConfigs = queue.getPriorityConfigurations(); - * Object.entries(allConfigs).forEach(([priority, config]) => { - * console.log(`Priority ${priority}: ${config.delay}ms delay, ${config.start}ms start delay`); - * }); - */ - getPriorityConfigurations(): Object; - /** - * Alias for destroy() method for common queue system naming. - * @returns {void} - */ - shutdown(): void; - /** - * Alias for enqueue() method for common queue system naming. - * @param {Function} task - The task function to execute - * @param {Function|Object} optionsOrCallback - Callback function or options object - * @param {Object} options - Task options (if callback provided as second parameter) - * @returns {Promise|TaskHandle} Promise if no callback provided, TaskHandle otherwise - */ - schedule(task: Function, optionsOrCallback: Function | Object, options?: Object): Promise | TaskHandle; - /** - * Alias for enqueue() method for common queue system naming. - * @param {Function} task - The task function to execute - * @param {Function|Object} optionsOrCallback - Callback function or options object - * @param {Object} options - Task options (if callback provided as second parameter) - * @returns {Promise|TaskHandle} Promise if no callback provided, TaskHandle otherwise - */ - add(task: Function, optionsOrCallback: Function | Object, options?: Object): Promise | TaskHandle; - /** - * Find a task by its ID. - * @param {string|number} id - The task ID to find - * @returns {Object|null} Task object if found, null otherwise - */ - get(id: string | number): Object | null; - /** - * Check if a task with the given ID exists. - * @param {string|number} id - The task ID to check - * @returns {boolean} True if task exists, false otherwise - */ - has(id: string | number): boolean; - /** - * Alias for get() method for backward compatibility. - * @param {string|number} id - The task ID to find - * @returns {Object|null} Task object if found, null otherwise - */ - getTask(id: string | number): Object | null; - /** - * Alias for has() method for backward compatibility. - * @param {string|number} id - The task ID to check - * @returns {boolean} True if task exists, false otherwise - */ - hasTask(id: string | number): boolean; - /** - * Get detailed information about the current queue state for debugging. - * @returns {Object} Comprehensive queue state information - */ - inspect(): Object; - /** - * Get information about active timers and scheduler state. - * @returns {Object} Timer and scheduler information - */ - inspectTimers(): Object; - /** - * Get a summary of all queued tasks by status. - * @returns {Object} Task summary by status - */ - inspectTasks(): Object; - /** - * Get detailed information about the scheduler state and timing. - * @returns {Object} Scheduler state information - */ - inspectScheduler(): Object; - /** - * Log comprehensive queue state to console for debugging. - * @param {boolean} [detailed=false] - Whether to include detailed task information - */ - debugLog(detailed?: boolean): void; + _syncMode: boolean | undefined; + options: { + constructor: Function; + toString(): string; + toLocaleString(): string; + valueOf(): Object; + hasOwnProperty(v: PropertyKey): boolean; + isPrototypeOf(v: Object): boolean; + propertyIsEnumerable(v: PropertyKey): boolean; + concurrency: number; + tick: number; + autoStart: boolean; + defaultPriority: number; + maxQueue: any; + priorities: {}; + smartScheduling: boolean; + healingInterval: number; + coalescing: { + defaults: Object; + keys: {}; + }; + coalescingWindowDuration: any; + coalescingMaxDelay: any; + coalescingMultipleCallbacks: any; + coalescingResolveAllPromises: any; + } | undefined; + pendingHeap: MinHeap | undefined; + readyHeap: MinHeap | undefined; + running: Set | undefined; + runningByPriority: Map | undefined; + tasks: Map | undefined; + nextId: number | undefined; + enqueueSeq: number | undefined; + isActive: boolean | undefined; + destroyed: boolean | undefined; + lastCompletedPriority: any; + nextAvailableTime: any; + schedulerTimeout: number | null | undefined; + healingInterval: number | null | undefined; + lastSchedulerRun: number | undefined; + intervalId: number | null | undefined; + coalescingGroups: Map | undefined; + coalescingRepresentatives: Map | undefined; + nextGroupId: number | undefined; + _warnedTaskOptions: Set | undefined; + timeoutId: number | undefined; + constructor(options?: {}); + /** + * Synchronous initialization for backwards compatibility + * @private + * @param {Object} options - Configuration options + */ + private _initializeSync; + /** + * Asynchronous initialization for modern usage + * @private + * @param {Object} options - Configuration options + * @returns {Promise} Promise that resolves to this instance + */ + private _initializeAsync; + /** + * Common initialization logic used by both sync and async modes + * @private + * @param {Object} options - Configuration options + */ + private _initializeCommon; + /** + * Internal convenience method to create a new HoldMyTask instance with async initialization. + * This enables event listeners to be attached before validation errors can occur. + * @param {Object} [options={}] - Configuration options + * @returns {Promise} Promise that resolves to the initialized instance + * @private + * @example + * // Internal usage - prefer new HoldMyTask({ sync: false }) for public API + * const queue = await HoldMyTask._create({ maxQueue: 100 }); + */ + private static _create; + /** + * Adds a task to the queue for execution. Supports both callback and promise-based APIs. + * @param {Function} task - The task function to execute. Can be sync or async. + * @param {Function|Object} [optionsOrCallback] - Either a callback function or options object + * @param {Object} [options={}] - Additional options (if callback was provided as second parameter) + * @param {string|number} [options.id] - Custom task ID for identification and later reference (must be unique) + * @param {number} [options.priority] - Task priority (higher numbers run first) + * @param {number} [options.timestamp] - When the task should be ready to run (milliseconds since epoch) + * @param {number} [options.startDelay] - Milliseconds from now when the task should be ready to run (convenience for timestamp calculation). Overrides the priority's startDelay + * @param {number} [options.start] - DEPRECATED: Use startDelay instead + * @param {AbortSignal} [options.signal] - AbortSignal to cancel the task + * @param {number} [options.timeout] - Task timeout in milliseconds (for execution time limit) + * @param {number} [options.expire] - Task expiration timestamp or milliseconds from now (for queue waiting time limit) + * @param {number} [options.postDelay] - Delay after this task completes before the next task can start. Overrides the priority's postDelay; use -1 to bypass the current delay period + * @param {number} [options.delay] - DEPRECATED: Use postDelay instead + * @param {boolean} [options.bypassDelay] - If true, skip any active delay period and start immediately + * @param {string} [options.coalescingKey] - Key for task coalescing - tasks with same key will be coalesced within windows + * @param {number} [options.coalescingWindowDuration] - Override coalescing window duration (task-level override of key-level and defaults) + * @param {number} [options.coalescingMaxDelay] - Override coalescing max delay (task-level override of key-level and defaults) + * @param {boolean} [options.coalescingMultipleCallbacks] - Override callback behavior (task-level override of key-level and defaults) + * @param {boolean} [options.coalescingResolveAllPromises] - Override promise resolution behavior (task-level override of key-level and defaults) + * @param {*} [options.metadata] - Arbitrary metadata to attach to the task + * @returns {Promise|Object} Promise (if no callback) or task control object with id, cancel, status methods + * @throws {Error} If queue is destroyed or full + * @example + * // Promise API + * const result = await queue.enqueue(async () => fetchData()); + * + * // Callback API + * queue.enqueue(() => processData(), (err, result) => { + * if (err) console.error(err); + * else console.log(result); + * }); + * + * // With options + * const task = queue.enqueue(myTask, { priority: 5, timeout: 30000, expire: 10000 }); + * + * // Bypass current delay for urgent task + * const urgent = queue.enqueue(urgentTask, { priority: 10, bypassDelay: true }); + * + * // Alternative: use postDelay: -1 to bypass + * const urgent2 = queue.enqueue(urgentTask, { priority: 10, postDelay: -1 }); + * + * // Coalescing tasks - multiple device status checks become one + * queue.enqueue(checkDeviceStatus, callback1, { coalescingKey: "device-123", coalescingWindowDuration: 1000 }); + * queue.enqueue(checkDeviceStatus, callback2, { coalescingKey: "device-123" }); // Gets coalesced with first + */ + enqueue(task: Function, optionsOrCallback?: Function | Object, options?: { + id?: string | number; + priority?: number; + timestamp?: number; + startDelay?: number; + start?: number; + signal?: AbortSignal; + timeout?: number; + expire?: number; + postDelay?: number; + delay?: number; + bypassDelay?: boolean; + coalescingKey?: string; + coalescingWindowDuration?: number; + coalescingMaxDelay?: number; + coalescingMultipleCallbacks?: boolean; + coalescingResolveAllPromises?: boolean; + metadata?: any; + }): Promise | Object; + /** + * Cancels a pending task by ID. + * @param {string} id - The task ID to cancel + * @param {string} [reason="Task canceled"] - Reason for cancellation + * @returns {boolean} True if task was found and cancelled, false otherwise + * @example + * const task = queue.enqueue(() => longRunningTask()); + * const cancelled = queue.cancel(task.id, "User requested cancellation"); + */ + cancel(id: string, reason?: string): boolean; + /** + * Alias for cancel() method for backward compatibility. + * @param {string|number} id - The task ID to cancel + * @param {string} [reason="Task canceled"] - Reason for cancellation + * @returns {boolean} True if task was found and cancelled, false otherwise + */ + cancelTask(id: string | number, reason?: string): boolean; + /** + * Pauses the task queue, stopping execution of new tasks. + * Currently running tasks will continue to completion. + * @returns {void} + * @example + * queue.pause(); + * // Queue stops processing new tasks + */ + pause(): void; + /** + * Resumes the task queue after being paused. + * @returns {void} + * @example + * queue.resume(); + * // Queue resumes processing tasks + */ + resume(): void; + /** + * Clears all pending and ready tasks from the queue. + * Currently running tasks will continue to completion. + * @returns {void} + * @example + * queue.clear(); + * // All queued tasks are removed + */ + clear(): void; + /** + * Returns the number of tasks in the queue. + * @returns {number} Number of tasks in the queue + * @example + * const totalTasks = queue.size(); // 5 + */ + size(): number; + /** + * Returns the number of tasks in the queue (alias for size()). + * @returns {number} Number of tasks in the queue + * @example + * const totalTasks = queue.length(); // 5 + */ + length(): number; + /** + * Returns the number of currently running tasks. + * @returns {number} Number of running tasks + * @example + * const runningTasks = queue.inflight(); // 2 + */ + inflight(): number; + /** + * Gets information about a coalescing group by key and group ID. + * @param {string} coalescingKey - The coalescing key + * @param {string} [groupId] - Optional group ID. If omitted, returns all groups for the key + * @returns {Object|Array|null} Group info object, array of groups, or null if not found + * @example + * // Get all groups for a coalescing key + * const groups = queue.getCoalescingGroup('ui.update'); + * + * // Get specific group by ID + * const group = queue.getCoalescingGroup('ui.update', '1'); + * console.log(group.tasks.size); // Number of tasks in group + * + * // Access individual task metadata + * for (const [taskId, task] of group.tasks) { + * console.log(`Task ${taskId}:`, task.metadata); + * } + */ + getCoalescingGroup(coalescingKey: string, groupId?: string): Object | any[] | null; + /** + * Gets metadata for all tasks in a coalescing group. + * @param {string} coalescingKey - The coalescing key + * @param {string} [groupId] - Optional group ID. If omitted, returns metadata from all groups for the key + * @returns {Array} Array of metadata objects with task IDs + * @example + * // Get metadata from all groups for a key + * const allMetadata = queue.getCoalescingGroupMetadata('ui.update'); + * + * // Get metadata from specific group + * const groupMetadata = queue.getCoalescingGroupMetadata('ui.update', '1'); + * + * // Example output: + * // [ + * // { taskId: '123', metadata: { userId: 100, action: 'save' } }, + * // { taskId: '124', metadata: { userId: 200, action: 'delete' } } + * // ] + */ + getCoalescingGroupMetadata(coalescingKey: string, groupId?: string): any[]; + /** + * Gets a summary of all active coalescing groups. + * @returns {Object} Summary object with coalescing key stats + * @example + * const summary = queue.getCoalescingGroupsSummary(); + * console.log(summary); + * // { + * // 'ui.update': { groupCount: 2, totalTasks: 5 }, + * // 'api.batch': { groupCount: 1, totalTasks: 3 } + * // } + */ + getCoalescingGroupsSummary(): Object; + /** + * Finds the coalescing group that contains a specific task ID. + * @param {string|number} taskId - The task ID to search for + * @returns {Object|null} Group information including the task's metadata, or null if not found + * @example + * const groupInfo = queue.findCoalescingGroupByTaskId('123'); + * if (groupInfo) { + * console.log('Task is in group:', groupInfo.groupId); + * console.log('Task metadata:', groupInfo.task.metadata); + * console.log('Other tasks in group:', groupInfo.groupTasks.length); + * } + */ + findCoalescingGroupByTaskId(taskId: string | number): Object | null; + /** + * Destroys the queue, canceling all tasks and stopping the scheduler. + * Once destroyed, the queue cannot be reused. + * @returns {void} + * @example + * queue.destroy(); + * // Queue is permanently shut down + */ + destroy(): void; + /** + * Returns the current timestamp in milliseconds. + * @returns {number} Current timestamp + * @example + * const timestamp = queue.now(); // 1699564800000 + */ + now(): number; + /** + * Main scheduler tick that moves ready tasks and starts execution. + * @returns {void} + * @private + */ + private schedulerTick; + /** + * Checks if a task can start based on both global and per-priority concurrency limits. + * @param {Object} task - The task to check + * @returns {boolean} True if the task can start, false if concurrency limits prevent it + * @private + */ + private _canStartTask; + /** + * Clears all active timers (intervals and timeouts). + * @returns {void} + * @private + */ + private clearTimers; + /** + * Calculates when the next scheduler run should happen and sets appropriate timeout. + * @private + * @returns {void} + * + * @description + * Smart scheduling that calculates the optimal time for the next scheduler run based on: + * - When the next pending task becomes ready + * - When delay periods end + * - Whether there are tasks that can run immediately + * + * Uses setTimeout for precise timing instead of constant polling intervals. + */ + private scheduleSmartTimeout; + /** + * Runs the main scheduler logic and reschedules if needed. + * @private + * @returns {void} + * + * @description + * Executes the scheduler tick logic and then determines if more scheduling is needed. + * Tracks when scheduler last ran for healing mechanism. + */ + private runScheduler; + /** + * Starts the self-healing interval that ensures scheduler continues working. + * @private + * @returns {void} + * + * @description + * Healing mechanism that periodically checks if the scheduler should be running + * but isn't due to timeout failures or other issues. Runs every healingInterval milliseconds. + */ + private startHealingInterval; + /** + * Clears all scheduler-related timers. + * @private + * @returns {void} + * + * @description + * Cleans up both the main scheduler timeout and the healing interval timer. + */ + private clearSchedulerTimers; + /** + * Configure coalescing settings for specific keys dynamically. + * @param {string} coalescingKey - The coalescing key to configure + * @param {Object} config - Configuration for this key + * @param {number} [config.windowDuration] - Window duration in milliseconds for this key + * @param {number} [config.maxDelay] - Maximum delay in milliseconds for this key + * @param {number} [config.postDelay] - Post-completion delay in milliseconds for this key + * @param {number} [config.startDelay] - Pre-execution delay in milliseconds for this key + * @param {number} [config.delay] - DEPRECATED: Use postDelay instead + * @param {number} [config.start] - DEPRECATED: Use startDelay instead + * @param {boolean} [config.multipleCallbacks] - Whether to call multiple callbacks for this key + * @param {boolean} [config.resolveAllPromises] - Whether to resolve all promises for this key + * @returns {void} + * + * @example + * // Configure specific keys after queue creation + * queue.configureCoalescingKey('ui.update', { + * windowDuration: 100, + * maxDelay: 500, + * postDelay: 25, + * startDelay: 0 + * }); + * + * queue.configureCoalescingKey('api.batch', { + * windowDuration: 1000, + * maxDelay: 5000, + * postDelay: 100, + * startDelay: 200, + * resolveAllPromises: false + * }); + */ + configureCoalescingKey(coalescingKey: string, config: { + windowDuration?: number; + maxDelay?: number; + postDelay?: number; + startDelay?: number; + delay?: number; + start?: number; + multipleCallbacks?: boolean; + resolveAllPromises?: boolean; + }): void; + /** + * Get the effective coalescing configuration for a specific key. + * @param {string} coalescingKey - The coalescing key to get configuration for + * @param {Object} [taskOptions={}] - Task-level options that may override key configuration + * @returns {Object} The effective configuration for this key + * + * @example + * // Get effective configuration for a key + * const config = queue.getCoalescingConfig('ui.update'); + * console.log(`UI updates coalesce within ${config.windowDuration}ms with ${config.postDelay}ms post-completion delay`); + * + * // Check with task-level overrides + * const effectiveConfig = queue.getCoalescingConfig('ui.update', { + * coalescingWindowDuration: 50, + * postDelay: 30 // Deprecated task-level name delay is still accepted + * }); + */ + getCoalescingConfig(coalescingKey: string, taskOptions?: Object): Object; + /** + * Get all configured coalescing keys and their configurations. + * @returns {Object} Map of coalescingKey to configuration + * + * @example + * // See all configured coalescing keys + * const allConfigs = queue.getCoalescingConfigurations(); + * Object.entries(allConfigs).forEach(([key, config]) => { + * console.log(`${key}: ${config.windowDuration}ms window, ${config.maxDelay}ms max delay, ${config.postDelay}ms post-completion delay, ${config.startDelay}ms pre-execution delay`); + * }); + */ + getCoalescingConfigurations(): Object; + /** + * Configure default settings for specific priorities dynamically. + * @param {number} priority - The priority level to configure + * @param {Object} config - Configuration for this priority + * @param {number} [config.postDelay] - Default post-completion delay in milliseconds for this priority + * @param {number} [config.startDelay] - Default pre-execution delay in milliseconds for this priority + * @param {number} [config.delay] - DEPRECATED: Use postDelay instead + * @param {number} [config.start] - DEPRECATED: Use startDelay instead + * @returns {void} + * + * @example + * // Configure priority defaults after queue creation + * queue.configurePriority(1, { + * postDelay: 100, // High priority tasks have 100ms delay after completion + * startDelay: 0 // High priority tasks start immediately + * }); + * + * queue.configurePriority(3, { + * postDelay: 0, // Low priority tasks have no delay after completion + * startDelay: 200 // Low priority tasks wait 200ms before starting + * }); + */ + configurePriority(priority: number, config: { + postDelay?: number; + startDelay?: number; + delay?: number; + start?: number; + }): void; + /** + * Get the effective configuration for a specific priority. + * @param {number} priority - The priority level to get configuration for + * @param {Object} [taskOptions={}] - Task-level options that may override priority configuration + * @returns {Object} The effective configuration for this priority + * + * @example + * // Get effective configuration for a priority + * const config = queue.getPriorityConfig(1); + * console.log(`Priority 1 tasks: ${config.delay}ms delay, ${config.start}ms start delay`); + * + * // Check with task-level overrides + * const effectiveConfig = queue.getPriorityConfig(1, { + * postDelay: 50, + * startDelay: 10 // Deprecated task-level names delay/start are still accepted + * }); + */ + getPriorityConfig(priority: number, taskOptions?: Object): Object; + /** + * Get all configured priorities and their configurations. + * @returns {Object} Map of priority to configuration + * + * @example + * // See all configured priorities + * const allConfigs = queue.getPriorityConfigurations(); + * Object.entries(allConfigs).forEach(([priority, config]) => { + * console.log(`Priority ${priority}: ${config.delay}ms delay, ${config.start}ms start delay`); + * }); + */ + getPriorityConfigurations(): Object; + /** + * Alias for destroy() method for common queue system naming. + * @returns {void} + */ + shutdown(): void; + /** + * Alias for enqueue() method for common queue system naming. + * @param {Function} task - The task function to execute + * @param {Function|Object} optionsOrCallback - Callback function or options object + * @param {Object} options - Task options (if callback provided as second parameter) + * @returns {Promise|TaskHandle} Promise if no callback provided, TaskHandle otherwise + */ + schedule(task: Function, optionsOrCallback: Function | Object, options?: Object): Promise | TaskHandle; + /** + * Alias for enqueue() method for common queue system naming. + * @param {Function} task - The task function to execute + * @param {Function|Object} optionsOrCallback - Callback function or options object + * @param {Object} options - Task options (if callback provided as second parameter) + * @returns {Promise|TaskHandle} Promise if no callback provided, TaskHandle otherwise + */ + add(task: Function, optionsOrCallback: Function | Object, options?: Object): Promise | TaskHandle; + /** + * Find a task by its ID. + * @param {string|number} id - The task ID to find + * @returns {Object|null} Task object if found, null otherwise + */ + get(id: string | number): Object | null; + /** + * Check if a task with the given ID exists. + * @param {string|number} id - The task ID to check + * @returns {boolean} True if task exists, false otherwise + */ + has(id: string | number): boolean; + /** + * Alias for get() method for backward compatibility. + * @param {string|number} id - The task ID to find + * @returns {Object|null} Task object if found, null otherwise + */ + getTask(id: string | number): Object | null; + /** + * Alias for has() method for backward compatibility. + * @param {string|number} id - The task ID to check + * @returns {boolean} True if task exists, false otherwise + */ + hasTask(id: string | number): boolean; + /** + * Get detailed information about the current queue state for debugging. + * @returns {Object} Comprehensive queue state information + */ + inspect(): Object; + /** + * Get information about active timers and scheduler state. + * @returns {Object} Timer and scheduler information + */ + inspectTimers(): Object; + /** + * Get a summary of all queued tasks by status. + * @returns {Object} Task summary by status + */ + inspectTasks(): Object; + /** + * Get detailed information about the scheduler state and timing. + * @returns {Object} Scheduler state information + */ + inspectScheduler(): Object; + /** + * Log comprehensive queue state to console for debugging. + * @param {boolean} [detailed=false] - Whether to include detailed task information + */ + debugLog(detailed?: boolean): void; } -//# sourceMappingURL=hold-my-task.d.mts.map +//# sourceMappingURL=hold-my-task.d.mts.map \ No newline at end of file diff --git a/types/src/utils.d.mts b/types/src/utils.d.mts index 729fa64..a19ba63 100644 --- a/types/src/utils.d.mts +++ b/types/src/utils.d.mts @@ -17,58 +17,58 @@ * Maintains the heap property where parent nodes are smaller than their children. */ export declare class MinHeap { - heap: any[]; - compare: Function; - /** - * Creates a new MinHeap with a custom comparison function. - * @param {Function} compare - Comparison function that returns negative if a < b, positive if a > b, 0 if equal - * @example - * // Priority queue (higher priority = smaller value) - * const heap = new MinHeap((a, b) => a.priority - b.priority); - */ - constructor(compare: Function); - /** - * Adds an item to the heap, maintaining heap property. - * @param {*} item - The item to add to the heap - * @returns {void} - * @example - * heap.push({ value: 5, priority: 1 }); - */ - push(item: any): void; - /** - * Removes and returns the minimum item from the heap. - * @returns {*|undefined} The minimum item, or undefined if heap is empty - * @example - * const min = heap.pop(); // Returns item with smallest comparison value - */ - pop(): any | undefined; - /** - * Returns the minimum item without removing it from the heap. - * @returns {*|undefined} The minimum item, or undefined if heap is empty - * @example - * const min = heap.peek(); // Look at minimum without removing - */ - peek(): any | undefined; - /** - * Returns the number of items in the heap. - * @returns {number} The size of the heap - * @example - * const count = heap.size(); // 5 - */ - size(): number; - /** - * Moves an item up the heap to maintain heap property after insertion. - * @param {number} index - Index of the item to bubble up - * @returns {void} - * @private - */ - private bubbleUp; - /** - * Moves an item down the heap to maintain heap property after removal. - * @param {number} index - Index of the item to sink down - * @returns {void} - * @private - */ - private sinkDown; + heap: any[]; + compare: Function; + /** + * Creates a new MinHeap with a custom comparison function. + * @param {Function} compare - Comparison function that returns negative if a < b, positive if a > b, 0 if equal + * @example + * // Priority queue (higher priority = smaller value) + * const heap = new MinHeap((a, b) => a.priority - b.priority); + */ + constructor(compare: Function); + /** + * Adds an item to the heap, maintaining heap property. + * @param {*} item - The item to add to the heap + * @returns {void} + * @example + * heap.push({ value: 5, priority: 1 }); + */ + push(item: any): void; + /** + * Removes and returns the minimum item from the heap. + * @returns {*|undefined} The minimum item, or undefined if heap is empty + * @example + * const min = heap.pop(); // Returns item with smallest comparison value + */ + pop(): any | undefined; + /** + * Returns the minimum item without removing it from the heap. + * @returns {*|undefined} The minimum item, or undefined if heap is empty + * @example + * const min = heap.peek(); // Look at minimum without removing + */ + peek(): any | undefined; + /** + * Returns the number of items in the heap. + * @returns {number} The size of the heap + * @example + * const count = heap.size(); // 5 + */ + size(): number; + /** + * Moves an item up the heap to maintain heap property after insertion. + * @param {number} index - Index of the item to bubble up + * @returns {void} + * @private + */ + private bubbleUp; + /** + * Moves an item down the heap to maintain heap property after removal. + * @param {number} index - Index of the item to sink down + * @returns {void} + * @private + */ + private sinkDown; } -//# sourceMappingURL=utils.d.mts.map +//# sourceMappingURL=utils.d.mts.map \ No newline at end of file From 6ece31051f68d87e241f52bc4d4e6d0e2538f69d Mon Sep 17 00:00:00 2001 From: Shinrai Date: Sat, 3 Oct 2026 17:24:32 -0700 Subject: [PATCH 03/16] fix(callback): deliver task failures to the callback without requiring an error listener When a callback-style task failed, timed out, was aborted or expired, the queue emitted `error` (before the callback for failures, after it for expiry). HoldMyTask is an EventEmitter, so with no `error` listener the emit threw: the process crashed (an unhandled rejection from _startTask, an uncaught exception from the scheduler for expiry) and, for failures, the callback never ran. Task-failure `error` events are now emitted only when a listener is attached (listenerCount("error") > 0). The failure always reaches the task's callback with the documented payload ({ type: "error", error }, { type: "timeout", message }, { type: "canceled", message: "Task was aborted" }, or the expire Error). Promise-API behaviour is unchanged. Errors thrown by the user's own callback are still emitted unconditionally, so a bug in a callback is not silently swallowed. Fixes #53 --- src/hold-my-task.mjs | 28 ++++- ...llbackErrorWithoutListener.test.vitest.mjs | 103 ++++++++++++++++++ types/src/hold-my-task.d.mts.map | 2 +- 3 files changed, 127 insertions(+), 6 deletions(-) create mode 100644 tests/CallbackErrorWithoutListener.test.vitest.mjs diff --git a/src/hold-my-task.mjs b/src/hold-my-task.mjs index a1dd455..c335c26 100644 --- a/src/hold-my-task.mjs +++ b/src/hold-my-task.mjs @@ -1899,20 +1899,37 @@ export class HoldMyTask extends EventEmitter { error.taskId = item.id; if (item.callback) { - // Callback API - emit error event + // Callback API - deliver the failure to the callback try { item.callback(error, null); } catch (err) { this.emit("error", { error: err, task: item }); } - // Emit error event for callback API - this.emit("error", { error, task: item }); + // Also report it as a queue "error" event, but only to listeners: the callback already + // has the failure, so an unhandled-"error" throw would only crash the caller's process. + this._emitTaskError({ error, task: item }); } else if (item.reject) { // Promise API - just reject, don't emit error event (handled by promise) item.reject(error); } } + /** + * Emits a task failure as an `error` event when, and only when, a listener is attached. + * Used for callback-API task failures, which are always delivered to the task's callback + * as well; with no listener, EventEmitter would otherwise throw the event and crash the + * process before (or after) the callback could handle it. + * @param {Object} payload - Event payload + * @returns {void} + * @private + * @internal + */ + _emitTaskError(payload) { + if (this.listenerCount("error") > 0) { + this.emit("error", payload); + } + } + /** * Starts execution of a ready task. * @param {Object} item - The task item to execute @@ -1998,9 +2015,10 @@ export class HoldMyTask extends EventEmitter { : "error"; item.finishedAt = this.now(); - // Emit error event only for callback API (promise API conveys error via rejection) + // Emit error event only for callback API (promise API conveys error via rejection), + // and only to listeners - the callback below always receives the failure. if (item.callback) { - this.emit("error", item); + this._emitTaskError(item); } // Store final values on promise handle before rejecting (for promise API) diff --git a/tests/CallbackErrorWithoutListener.test.vitest.mjs b/tests/CallbackErrorWithoutListener.test.vitest.mjs new file mode 100644 index 0000000..3db9dba --- /dev/null +++ b/tests/CallbackErrorWithoutListener.test.vitest.mjs @@ -0,0 +1,103 @@ +/** + * + * @Project: @cldmv/holdmytask + * @Filename: /tests/CallbackErrorWithoutListener.test.vitest.mjs + * @Date: 2026-10-03T17:20:10-07:00 (1791073210) + * @Author: Nate Corcoran + * @Email: + * ----- + * @Last modified by: Nate Corcoran (Shinrai@users.noreply.github.com) + * @Last modified time: 2026-10-03T17:23:55-07:00 (1791073435) + * ----- + * @Copyright: Copyright (c) 2013-2026 Catalyzed Motivation Inc. All rights reserved. + * + */ + +import { test, expect, describe } from "vitest"; +import { HoldMyTask } from "../src/hold-my-task.mjs"; + +// #53: a callback-style task failure must reach the callback even when nothing listens +// for the queue's `error` event; the event is only emitted when a listener exists. +const runCallbackTask = (q, task, options = {}) => + new Promise((resolve) => { + q.enqueue(task, (err, result) => resolve({ err, result }), options); + }); + +describe.each([ + { smartScheduling: true, mode: "Smart Scheduling" }, + { smartScheduling: false, mode: "Traditional Polling" } +])("callback task failures without an error listener ($mode)", ({ smartScheduling }) => { + test("a failing task delivers { type: 'error', error } to the callback without throwing", async () => { + const q = new HoldMyTask({ smartScheduling }); + const boom = new Error("boom"); + expect(q.listenerCount("error")).toBe(0); + const { err, result } = await runCallbackTask(q, () => { + throw boom; + }); + expect(err).toEqual({ type: "error", error: boom }); + expect(result).toBeNull(); + q.destroy(); + }); + + test("a timed-out task delivers { type: 'timeout', message } to the callback without throwing", async () => { + const q = new HoldMyTask({ smartScheduling }); + const { err, result } = await runCallbackTask(q, () => new Promise((resolve) => setTimeout(resolve, 500)), { timeout: 20 }); + expect(err).toEqual({ type: "timeout", message: "Task timed out after 20ms" }); + expect(result).toBeNull(); + q.destroy(); + }); + + test("an aborted task delivers { type: 'canceled', message: 'Task was aborted' } to the callback without throwing", async () => { + const q = new HoldMyTask({ smartScheduling }); + const { err, result } = await runCallbackTask(q, () => { + const abort = new Error("The operation was aborted"); + abort.name = "AbortError"; + throw abort; + }); + expect(err).toEqual({ type: "canceled", message: "Task was aborted" }); + expect(result).toBeNull(); + q.destroy(); + }); + + test("an expired task delivers its expire error to the callback without throwing", async () => { + const q = new HoldMyTask({ smartScheduling, concurrency: 1 }); + q.enqueue( + () => new Promise((resolve) => setTimeout(resolve, 60)), + () => {} + ); + const { err, result } = await runCallbackTask(q, () => "never", { expire: 10 }); + expect(err).toBeInstanceOf(Error); + expect(err.type).toBe("expire"); + expect(result).toBeNull(); + q.destroy(); + }); + + test("with an error listener attached, the event is still emitted and the callback still runs", async () => { + const q = new HoldMyTask({ smartScheduling }); + const events = []; + q.on("error", (payload) => events.push(payload)); + const boom = new Error("boom"); + const { err } = await runCallbackTask(q, () => { + throw boom; + }); + expect(err).toEqual({ type: "error", error: boom }); + expect(events).toHaveLength(1); + expect(events[0].error).toBe(boom); + expect(events[0].status).toBe("error"); + q.destroy(); + }); + + test("with an error listener attached, an expired callback task is still reported", async () => { + const q = new HoldMyTask({ smartScheduling, concurrency: 1 }); + const events = []; + q.on("error", (payload) => events.push(payload)); + q.enqueue( + () => new Promise((resolve) => setTimeout(resolve, 60)), + () => {} + ); + const { err } = await runCallbackTask(q, () => "never", { expire: 10 }); + expect(err.type).toBe("expire"); + expect(events.some((e) => e.error === err)).toBe(true); + q.destroy(); + }); +}); diff --git a/types/src/hold-my-task.d.mts.map b/types/src/hold-my-task.d.mts.map index 4611d0a..86dfc37 100644 --- a/types/src/hold-my-task.d.mts.map +++ b/types/src/hold-my-task.d.mts.map @@ -1 +1 @@ -{"version":3,"file":"hold-my-task.d.mts","sourceRoot":"","sources":["../../src/hold-my-task.mjs"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"hold-my-task.d.mts","sourceRoot":"","sources":["../../src/hold-my-task.mjs"],"names":[],"mappings":"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"} \ No newline at end of file From 5279ee6101dadd603ce85b581cda5873f9756ddb Mon Sep 17 00:00:00 2001 From: cldmv-bot <230939188+cldmv-bot@users.noreply.github.com> Date: Sun, 4 Oct 2026 00:34:10 +0000 Subject: [PATCH 04/16] style: apply automated lint/format fixes --- types/build.d.mts | 2 +- types/devcheck.d.mts | 2 +- types/examples/enhanced-coalescing-test.d.mts | 2 +- .../old/breaking-point-analysis.d.mts | 140 ++- types/examples/old/coalescing-analysis.d.mts | 2 +- .../examples/old/device-control-pattern.d.mts | 76 +- types/examples/old/device-simulation.d.mts | 120 +- types/examples/old/proper-delay-test.d.mts | 100 +- types/examples/old/run-volume-test.d.mts | 2 +- types/examples/old/test-analysis.d.mts | 2 +- types/examples/old/test-breaking-point.d.mts | 2 +- types/examples/old/test-device-control.d.mts | 2 +- .../examples/old/test-device-simulation.d.mts | 2 +- types/examples/old/test-proper-delays.d.mts | 2 +- types/examples/old/test-timing-analysis.d.mts | 2 +- types/examples/old/timing-analysis.d.mts | 58 +- .../examples/old/volume-coalescing-test.d.mts | 138 +- types/examples/priority-stress-test.d.mts | 109 +- types/examples/run-priority-stress-test.d.mts | 2 +- types/examples/unified-priority-test.d.mts | 2 +- types/index.d.mts | 2 +- types/src/hold-my-task.d.mts | 1108 +++++++++-------- types/src/utils.d.mts | 108 +- 23 files changed, 1006 insertions(+), 979 deletions(-) diff --git a/types/build.d.mts b/types/build.d.mts index 01edb98..dbc4fc5 100644 --- a/types/build.d.mts +++ b/types/build.d.mts @@ -13,4 +13,4 @@ * */ export {}; -//# sourceMappingURL=build.d.mts.map \ No newline at end of file +//# sourceMappingURL=build.d.mts.map diff --git a/types/devcheck.d.mts b/types/devcheck.d.mts index 8c79ebe..bc770aa 100644 --- a/types/devcheck.d.mts +++ b/types/devcheck.d.mts @@ -13,4 +13,4 @@ * */ export {}; -//# sourceMappingURL=devcheck.d.mts.map \ No newline at end of file +//# sourceMappingURL=devcheck.d.mts.map diff --git a/types/examples/enhanced-coalescing-test.d.mts b/types/examples/enhanced-coalescing-test.d.mts index 7a81ab8..4db2c23 100644 --- a/types/examples/enhanced-coalescing-test.d.mts +++ b/types/examples/enhanced-coalescing-test.d.mts @@ -14,4 +14,4 @@ * */ export {}; -//# sourceMappingURL=enhanced-coalescing-test.d.mts.map \ No newline at end of file +//# sourceMappingURL=enhanced-coalescing-test.d.mts.map diff --git a/types/examples/old/breaking-point-analysis.d.mts b/types/examples/old/breaking-point-analysis.d.mts index d5cc602..06809b9 100644 --- a/types/examples/old/breaking-point-analysis.d.mts +++ b/types/examples/old/breaking-point-analysis.d.mts @@ -16,82 +16,84 @@ * Device with configurable delays */ declare class ConfigurableDelayDevice { - volume: number; - commandCount: number; - infoRequestCount: number; - commandDelay: number; - infoDelay: number; - constructor(initialVolume?: number, commandDelay?: number, infoDelay?: number); - volumeCommand(change: any): Promise<{ - commandId: number; - oldVolume: number; - newVolume: number; - change: number; - processingTime: number; - }>; - getInfo(): Promise<{ - requestId: number; - volume: number; - timestamp: number; - totalCommands: number; - totalInfoRequests: number; - processingTime: number; - }>; - getStats(): { - currentVolume: number; - totalCommands: number; - totalInfoRequests: number; - }; + volume: number; + commandCount: number; + infoRequestCount: number; + commandDelay: number; + infoDelay: number; + constructor(initialVolume?: number, commandDelay?: number, infoDelay?: number); + volumeCommand(change: any): Promise<{ + commandId: number; + oldVolume: number; + newVolume: number; + change: number; + processingTime: number; + }>; + getInfo(): Promise<{ + requestId: number; + volume: number; + timestamp: number; + totalCommands: number; + totalInfoRequests: number; + processingTime: number; + }>; + getStats(): { + currentVolume: number; + totalCommands: number; + totalInfoRequests: number; + }; } /** * Controller for timing tests */ declare class TimingTestController { - device: any; - coalescingWindowDuration: number; - queue: any; - commandCounter: number; - results: any[]; - constructor(device: any, coalescingWindowDuration?: number); - volumeUp(amount?: number): Promise<{ - commandId: number; - userActionTime: number; - completionTime: number; - totalDuration: number; - volumeResult: any; - infoResult: any; - deviceVolumeAtCompletion: any; - infoReportsVolume: any; - isAccurate: boolean; - timingData: { - volumeQueueDelay: any; - volumeProcessingTime: any; - infoQueueDelay: any; - infoProcessingTime: any; - }; - }>; - getResults(): { - results: any[]; - deviceStats: any; - coalescingWindowDuration: number; - }; - destroy(): void; + device: any; + coalescingWindowDuration: number; + queue: any; + commandCounter: number; + results: any[]; + constructor(device: any, coalescingWindowDuration?: number); + volumeUp(amount?: number): Promise<{ + commandId: number; + userActionTime: number; + completionTime: number; + totalDuration: number; + volumeResult: any; + infoResult: any; + deviceVolumeAtCompletion: any; + infoReportsVolume: any; + isAccurate: boolean; + timingData: { + volumeQueueDelay: any; + volumeProcessingTime: any; + infoQueueDelay: any; + infoProcessingTime: any; + }; + }>; + getResults(): { + results: any[]; + deviceStats: any; + coalescingWindowDuration: number; + }; + destroy(): void; } /** * Test different device delays to find the breaking point */ -declare function findBreakingPoint(): Promise<{ - description: string | number; - commandDelay: string | number; - coalescingWindow: string | number; - accurateCommands: number; - inaccurateCommands: number; - accuracyRate: number; - maxDuration: number; - avgDuration: number; - deviceCommands: any; - deviceInfoRequests: any; - coalescingEfficiency: number; -}[]>; +declare function findBreakingPoint(): Promise< + { + description: string | number; + commandDelay: string | number; + coalescingWindow: string | number; + accurateCommands: number; + inaccurateCommands: number; + accuracyRate: number; + maxDuration: number; + avgDuration: number; + deviceCommands: any; + deviceInfoRequests: any; + coalescingEfficiency: number; + }[] +>; export { ConfigurableDelayDevice, TimingTestController, findBreakingPoint }; -//# sourceMappingURL=breaking-point-analysis.d.mts.map \ No newline at end of file +//# sourceMappingURL=breaking-point-analysis.d.mts.map diff --git a/types/examples/old/coalescing-analysis.d.mts b/types/examples/old/coalescing-analysis.d.mts index 9b7add2..e816fbe 100644 --- a/types/examples/old/coalescing-analysis.d.mts +++ b/types/examples/old/coalescing-analysis.d.mts @@ -14,4 +14,4 @@ */ declare function analyzeApproaches(): Promise; export { analyzeApproaches }; -//# sourceMappingURL=coalescing-analysis.d.mts.map \ No newline at end of file +//# sourceMappingURL=coalescing-analysis.d.mts.map diff --git a/types/examples/old/device-control-pattern.d.mts b/types/examples/old/device-control-pattern.d.mts index b4b5fa2..1e2c097 100644 --- a/types/examples/old/device-control-pattern.d.mts +++ b/types/examples/old/device-control-pattern.d.mts @@ -14,46 +14,46 @@ */ import { EventEmitter } from "events"; declare class DeviceController extends EventEmitter { - queue: any; - deviceState: { - volume: number; - lastUpdated: number; - }; - pendingVolumeChanges: Map; - constructor(); - /** - * User command: Volume Up - * This accumulates changes and triggers coalesced update - */ - volumeUp(amount?: number): Promise; - /** - * The actual device update task that gets executed (coalesced) - * This applies ALL accumulated changes at once - */ - updateDeviceInfo(coalescingKey: any): Promise<{ - volume: number; - lastUpdated: number; - }>; - /** - * Update system state after device change - */ - updateSystemState(): void; - /** - * Emit events for state changes - */ - emitStateChange(oldVolume: any, newVolume: any): void; - /** - * Get current device state - */ - getState(): { - volume: number; - lastUpdated: number; - }; - destroy(): void; + queue: any; + deviceState: { + volume: number; + lastUpdated: number; + }; + pendingVolumeChanges: Map; + constructor(); + /** + * User command: Volume Up + * This accumulates changes and triggers coalesced update + */ + volumeUp(amount?: number): Promise; + /** + * The actual device update task that gets executed (coalesced) + * This applies ALL accumulated changes at once + */ + updateDeviceInfo(coalescingKey: any): Promise<{ + volume: number; + lastUpdated: number; + }>; + /** + * Update system state after device change + */ + updateSystemState(): void; + /** + * Emit events for state changes + */ + emitStateChange(oldVolume: any, newVolume: any): void; + /** + * Get current device state + */ + getState(): { + volume: number; + lastUpdated: number; + }; + destroy(): void; } declare function demonstrateDeviceControl(): Promise; declare class AdvancedDeviceController extends DeviceController { - volumeUp(amount?: number): Promise; + volumeUp(amount?: number): Promise; } export { DeviceController, AdvancedDeviceController, demonstrateDeviceControl }; -//# sourceMappingURL=device-control-pattern.d.mts.map \ No newline at end of file +//# sourceMappingURL=device-control-pattern.d.mts.map diff --git a/types/examples/old/device-simulation.d.mts b/types/examples/old/device-simulation.d.mts index fb8be9b..05f551f 100644 --- a/types/examples/old/device-simulation.d.mts +++ b/types/examples/old/device-simulation.d.mts @@ -17,79 +17,79 @@ import { EventEmitter } from "events"; * Simulated device that tracks its own state */ declare class PseudoDevice { - volume: number; - commandCount: number; - infoRequestCount: number; - constructor(initialVolume?: number); - /** - * Device receives a volume change command - */ - volumeCommand(change: any): Promise<{ - commandId: number; - oldVolume: number; - newVolume: number; - change: number; - }>; - /** - * Device responds to info request - */ - getInfo(): Promise<{ - requestId: number; - volume: number; - timestamp: number; - totalCommands: number; - totalInfoRequests: number; - }>; - /** - * Get device stats - */ - getStats(): { - currentVolume: number; - totalCommands: number; - totalInfoRequests: number; - }; + volume: number; + commandCount: number; + infoRequestCount: number; + constructor(initialVolume?: number); + /** + * Device receives a volume change command + */ + volumeCommand(change: any): Promise<{ + commandId: number; + oldVolume: number; + newVolume: number; + change: number; + }>; + /** + * Device responds to info request + */ + getInfo(): Promise<{ + requestId: number; + volume: number; + timestamp: number; + totalCommands: number; + totalInfoRequests: number; + }>; + /** + * Get device stats + */ + getStats(): { + currentVolume: number; + totalCommands: number; + totalInfoRequests: number; + }; } /** * Controller that uses the queue system to communicate with the device */ declare class DeviceController extends EventEmitter { - device: any; - queue: any; - optimisticVolume: any; - pendingChanges: number; - constructor(device: any, queueOptions?: {}); - /** - * User calls volumeUp - this should update device and then get fresh info - */ - volumeUp(amount?: number): Promise<{ - volumeCommand: any; - deviceInfo: any; - optimisticVolume: any; - pendingChanges: number; - }>; - /** - * Get current state - */ - getState(): { - optimisticVolume: any; - pendingChanges: number; - deviceStats: any; - }; - destroy(): void; + device: any; + queue: any; + optimisticVolume: any; + pendingChanges: number; + constructor(device: any, queueOptions?: {}); + /** + * User calls volumeUp - this should update device and then get fresh info + */ + volumeUp(amount?: number): Promise<{ + volumeCommand: any; + deviceInfo: any; + optimisticVolume: any; + pendingChanges: number; + }>; + /** + * Get current state + */ + getState(): { + optimisticVolume: any; + pendingChanges: number; + deviceStats: any; + }; + destroy(): void; } /** * Test scenario: Rapid volume commands */ declare function testRapidVolumeCommands(): Promise<{ - expectedVolume: number; - actualVolume: number; - totalCommands: number; - totalInfoRequests: number; - success: boolean; + expectedVolume: number; + actualVolume: number; + totalCommands: number; + totalInfoRequests: number; + success: boolean; }>; /** * Test different coalescing configurations */ declare function testCoalescingConfigurations(): Promise; export { PseudoDevice, DeviceController, testRapidVolumeCommands, testCoalescingConfigurations }; -//# sourceMappingURL=device-simulation.d.mts.map \ No newline at end of file +//# sourceMappingURL=device-simulation.d.mts.map diff --git a/types/examples/old/proper-delay-test.d.mts b/types/examples/old/proper-delay-test.d.mts index 6f70d3e..082baa5 100644 --- a/types/examples/old/proper-delay-test.d.mts +++ b/types/examples/old/proper-delay-test.d.mts @@ -13,65 +13,65 @@ * */ declare class SimpleDevice { - volume: number; - commandCount: number; - infoRequestCount: number; - constructor(initialVolume?: number); - volumeCommand(change: any): Promise<{ - commandId: number; - oldVolume: number; - newVolume: number; - change: number; - }>; - getInfo(): Promise<{ - requestId: number; - volume: number; - timestamp: number; - totalCommands: number; - totalInfoRequests: number; - }>; - getStats(): { - currentVolume: number; - totalCommands: number; - totalInfoRequests: number; - }; + volume: number; + commandCount: number; + infoRequestCount: number; + constructor(initialVolume?: number); + volumeCommand(change: any): Promise<{ + commandId: number; + oldVolume: number; + newVolume: number; + change: number; + }>; + getInfo(): Promise<{ + requestId: number; + volume: number; + timestamp: number; + totalCommands: number; + totalInfoRequests: number; + }>; + getStats(): { + currentVolume: number; + totalCommands: number; + totalInfoRequests: number; + }; } /** * Controller using proper queue delays */ declare class ProperDelayController { - device: any; - queue: any; - commandCounter: number; - constructor(device: any); - volumeUp(amount?: number): Promise<{ - commandId: number; - startTime: number; - endTime: number; - duration: number; - volumeResult: any; - infoResult: any; - deviceVolumeAtEnd: any; - infoReportsVolume: any; - isAccurate: boolean; - }>; - getQueueInfo(): { - pendingCount: any; - runningCount: any; - completedCount: any; - }; - destroy(): void; + device: any; + queue: any; + commandCounter: number; + constructor(device: any); + volumeUp(amount?: number): Promise<{ + commandId: number; + startTime: number; + endTime: number; + duration: number; + volumeResult: any; + infoResult: any; + deviceVolumeAtEnd: any; + infoReportsVolume: any; + isAccurate: boolean; + }>; + getQueueInfo(): { + pendingCount: any; + runningCount: any; + completedCount: any; + }; + destroy(): void; } /** * Test the proper delay scenario */ declare function testProperDelays(): Promise<{ - totalAccurate: number; - totalTests: number; - accuracyRate: number; - deviceCommands: number; - deviceInfoRequests: number; - finalVolume: number; + totalAccurate: number; + totalTests: number; + accuracyRate: number; + deviceCommands: number; + deviceInfoRequests: number; + finalVolume: number; }>; export { SimpleDevice, ProperDelayController, testProperDelays }; -//# sourceMappingURL=proper-delay-test.d.mts.map \ No newline at end of file +//# sourceMappingURL=proper-delay-test.d.mts.map diff --git a/types/examples/old/run-volume-test.d.mts b/types/examples/old/run-volume-test.d.mts index e3a9d42..deef1ba 100644 --- a/types/examples/old/run-volume-test.d.mts +++ b/types/examples/old/run-volume-test.d.mts @@ -13,4 +13,4 @@ * */ export {}; -//# sourceMappingURL=run-volume-test.d.mts.map \ No newline at end of file +//# sourceMappingURL=run-volume-test.d.mts.map diff --git a/types/examples/old/test-analysis.d.mts b/types/examples/old/test-analysis.d.mts index e4c25b7..d5fbc33 100644 --- a/types/examples/old/test-analysis.d.mts +++ b/types/examples/old/test-analysis.d.mts @@ -13,4 +13,4 @@ * */ export {}; -//# sourceMappingURL=test-analysis.d.mts.map \ No newline at end of file +//# sourceMappingURL=test-analysis.d.mts.map diff --git a/types/examples/old/test-breaking-point.d.mts b/types/examples/old/test-breaking-point.d.mts index 58afce8..b099948 100644 --- a/types/examples/old/test-breaking-point.d.mts +++ b/types/examples/old/test-breaking-point.d.mts @@ -13,4 +13,4 @@ * */ export {}; -//# sourceMappingURL=test-breaking-point.d.mts.map \ No newline at end of file +//# sourceMappingURL=test-breaking-point.d.mts.map diff --git a/types/examples/old/test-device-control.d.mts b/types/examples/old/test-device-control.d.mts index 29c1485..ded4c37 100644 --- a/types/examples/old/test-device-control.d.mts +++ b/types/examples/old/test-device-control.d.mts @@ -13,4 +13,4 @@ * */ export {}; -//# sourceMappingURL=test-device-control.d.mts.map \ No newline at end of file +//# sourceMappingURL=test-device-control.d.mts.map diff --git a/types/examples/old/test-device-simulation.d.mts b/types/examples/old/test-device-simulation.d.mts index c1a716f..1987396 100644 --- a/types/examples/old/test-device-simulation.d.mts +++ b/types/examples/old/test-device-simulation.d.mts @@ -13,4 +13,4 @@ * */ export {}; -//# sourceMappingURL=test-device-simulation.d.mts.map \ No newline at end of file +//# sourceMappingURL=test-device-simulation.d.mts.map diff --git a/types/examples/old/test-proper-delays.d.mts b/types/examples/old/test-proper-delays.d.mts index 7e2bc9e..a86d081 100644 --- a/types/examples/old/test-proper-delays.d.mts +++ b/types/examples/old/test-proper-delays.d.mts @@ -13,4 +13,4 @@ * */ export {}; -//# sourceMappingURL=test-proper-delays.d.mts.map \ No newline at end of file +//# sourceMappingURL=test-proper-delays.d.mts.map diff --git a/types/examples/old/test-timing-analysis.d.mts b/types/examples/old/test-timing-analysis.d.mts index 9df23a4..0315938 100644 --- a/types/examples/old/test-timing-analysis.d.mts +++ b/types/examples/old/test-timing-analysis.d.mts @@ -13,4 +13,4 @@ * */ export {}; -//# sourceMappingURL=test-timing-analysis.d.mts.map \ No newline at end of file +//# sourceMappingURL=test-timing-analysis.d.mts.map diff --git a/types/examples/old/timing-analysis.d.mts b/types/examples/old/timing-analysis.d.mts index 05b93cc..b634aaa 100644 --- a/types/examples/old/timing-analysis.d.mts +++ b/types/examples/old/timing-analysis.d.mts @@ -23,40 +23,40 @@ import { DeviceController } from "./device-simulation.mjs"; * Enhanced controller that logs detailed timing information */ declare class TimingAnalysisController extends DeviceController { - commandCounter: number; - timingLog: any[]; - constructor(device: any, queueOptions?: {}); - volumeUp(amount?: number): Promise<{ - commandId: number; - startTime: number; - endTime: number; - totalDuration: number; - volumeCommand: any; - infoResult: any; - expectedVolume: any; - reportedVolume: any; - }>; - getTimingAnalysis(): { - timingLog: any[]; - deviceStats: any; - }; + commandCounter: number; + timingLog: any[]; + constructor(device: any, queueOptions?: {}); + volumeUp(amount?: number): Promise<{ + commandId: number; + startTime: number; + endTime: number; + totalDuration: number; + volumeCommand: any; + infoResult: any; + expectedVolume: any; + reportedVolume: any; + }>; + getTimingAnalysis(): { + timingLog: any[]; + deviceStats: any; + }; } /** * Test with reference counting approach */ declare class ReferenceCountingController extends DeviceController { - pendingVolumeCommands: Map; - commandCounter: number; - constructor(device: any, queueOptions?: {}); - volumeUp(amount?: number): Promise<{ - commandId: number; - volumeResult: any; - infoResult: any; - expectedVolume: any; - reportedVolume: any; - accurate: boolean; - }>; + pendingVolumeCommands: Map; + commandCounter: number; + constructor(device: any, queueOptions?: {}); + volumeUp(amount?: number): Promise<{ + commandId: number; + volumeResult: any; + infoResult: any; + expectedVolume: any; + reportedVolume: any; + accurate: boolean; + }>; } declare function analyzeTimingIssues(): Promise; export { TimingAnalysisController, ReferenceCountingController, analyzeTimingIssues }; -//# sourceMappingURL=timing-analysis.d.mts.map \ No newline at end of file +//# sourceMappingURL=timing-analysis.d.mts.map diff --git a/types/examples/old/volume-coalescing-test.d.mts b/types/examples/old/volume-coalescing-test.d.mts index 0bc063e..32c09fc 100644 --- a/types/examples/old/volume-coalescing-test.d.mts +++ b/types/examples/old/volume-coalescing-test.d.mts @@ -16,78 +16,86 @@ * Simple volume system that tracks state */ declare class VolumeSystem { - volume: number; - commandCount: number; - updateCount: number; - log: any[]; - constructor(initialVolume?: number); - /** - * Execute a volume command (changes the actual volume) - */ - executeVolumeCommand(change: any, commandId: any): Promise<{ - commandId: any; - oldVolume: number; - newVolume: number; - change: number; - executionTime: number; - }>; - /** - * Execute an update command (reports current state) - */ - executeUpdateCommand(updateId: any): Promise<{ - updateId: any; - volume: number; - timestamp: number; - totalCommands: number; - totalUpdates: number; - }>; - getState(): { - currentVolume: number; - totalCommands: number; - totalUpdates: number; - }; - getLog(): any[]; - clearLog(): void; + volume: number; + commandCount: number; + updateCount: number; + log: any[]; + constructor(initialVolume?: number); + /** + * Execute a volume command (changes the actual volume) + */ + executeVolumeCommand( + change: any, + commandId: any + ): Promise<{ + commandId: any; + oldVolume: number; + newVolume: number; + change: number; + executionTime: number; + }>; + /** + * Execute an update command (reports current state) + */ + executeUpdateCommand(updateId: any): Promise<{ + updateId: any; + volume: number; + timestamp: number; + totalCommands: number; + totalUpdates: number; + }>; + getState(): { + currentVolume: number; + totalCommands: number; + totalUpdates: number; + }; + getLog(): any[]; + clearLog(): void; } /** * Controller that implements volume commands with coalesced updates */ declare class VolumeController { - volumeSystem: any; - queue: any; - commandCounter: number; - constructor(volumeSystem: any, queueOptions?: {}); - /** - * Volume up command with coalesced update - */ - volumeUp(amount?: number, options?: {}): Promise<{ - commandId: number; - userActionTime: number; - endTime: number; - totalDuration: number; - volumeResult: any; - updateResult: any; - systemVolumeAtEnd: any; - updateReportsVolume: any; - isAccurate: boolean; - options: {}; - }>; - destroy(): void; + volumeSystem: any; + queue: any; + commandCounter: number; + constructor(volumeSystem: any, queueOptions?: {}); + /** + * Volume up command with coalesced update + */ + volumeUp( + amount?: number, + options?: {} + ): Promise<{ + commandId: number; + userActionTime: number; + endTime: number; + totalDuration: number; + volumeResult: any; + updateResult: any; + systemVolumeAtEnd: any; + updateReportsVolume: any; + isAccurate: boolean; + options: {}; + }>; + destroy(): void; } /** * Test different timing scenarios */ -declare function testVolumeCoalescing(): Promise<{ - scenario: string; - totalDuration: number; - accurateCommands: number; - totalCommands: number; - accuracyRate: number; - finalVolume: number; - expectedVolume: number; - volumeCommandsExecuted: number; - updateCommandsExecuted: number; - coalescingEfficiency: number; -}[]>; +declare function testVolumeCoalescing(): Promise< + { + scenario: string; + totalDuration: number; + accurateCommands: number; + totalCommands: number; + accuracyRate: number; + finalVolume: number; + expectedVolume: number; + volumeCommandsExecuted: number; + updateCommandsExecuted: number; + coalescingEfficiency: number; + }[] +>; export { VolumeSystem, VolumeController, testVolumeCoalescing }; -//# sourceMappingURL=volume-coalescing-test.d.mts.map \ No newline at end of file +//# sourceMappingURL=volume-coalescing-test.d.mts.map diff --git a/types/examples/priority-stress-test.d.mts b/types/examples/priority-stress-test.d.mts index adb58a1..ffd2ebd 100644 --- a/types/examples/priority-stress-test.d.mts +++ b/types/examples/priority-stress-test.d.mts @@ -16,65 +16,70 @@ * Volume system with realistic timing */ declare class RealisticVolumeSystem { - volume: number; - commandCount: number; - updateCount: number; - log: any[]; - constructor(initialVolume?: number); - executeVolumeCommand(change: any, commandId: any): Promise<{ - commandId: any; - oldVolume: number; - newVolume: number; - change: number; - executionTime: number; - processingTime: number; - }>; - executeUpdateCommand(updateId: any): Promise<{ - updateId: any; - volume: number; - timestamp: number; - totalCommands: number; - totalUpdates: number; - processingTime: number; - }>; - getState(): { - currentVolume: number; - totalCommands: number; - totalUpdates: number; - }; - getLog(): any[]; - clearLog(): void; + volume: number; + commandCount: number; + updateCount: number; + log: any[]; + constructor(initialVolume?: number); + executeVolumeCommand( + change: any, + commandId: any + ): Promise<{ + commandId: any; + oldVolume: number; + newVolume: number; + change: number; + executionTime: number; + processingTime: number; + }>; + executeUpdateCommand(updateId: any): Promise<{ + updateId: any; + volume: number; + timestamp: number; + totalCommands: number; + totalUpdates: number; + processingTime: number; + }>; + getState(): { + currentVolume: number; + totalCommands: number; + totalUpdates: number; + }; + getLog(): any[]; + clearLog(): void; } /** * Realistic volume controller with proper priorities and delays */ declare class PriorityVolumeController { - volumeSystem: any; - queue: any; - commandCounter: number; - constructor(volumeSystem: any, queueOptions?: {}); - /** - * Volume up with realistic "fire and forget" pattern - * REAL-WORLD PATTERN: Volume task enqueues update task AFTER completing volume change - */ - volumeUp(amount?: number, options?: {}): any; - destroy(): void; + volumeSystem: any; + queue: any; + commandCounter: number; + constructor(volumeSystem: any, queueOptions?: {}); + /** + * Volume up with realistic "fire and forget" pattern + * REAL-WORLD PATTERN: Volume task enqueues update task AFTER completing volume change + */ + volumeUp(amount?: number, options?: {}): any; + destroy(): void; } /** * Stress test scenarios */ -declare function runPriorityStressTests(): Promise<{ - scenario: string; - totalDuration: number; - accurateCommands: any; - totalCommands: number; - accuracyRate: number; - finalVolume: number; - expectedVolume: number; - volumeCommandsExecuted: number; - updateCommandsExecuted: number; - coalescingEfficiency: number; - averageCommandDuration: number; -}[]>; +declare function runPriorityStressTests(): Promise< + { + scenario: string; + totalDuration: number; + accurateCommands: any; + totalCommands: number; + accuracyRate: number; + finalVolume: number; + expectedVolume: number; + volumeCommandsExecuted: number; + updateCommandsExecuted: number; + coalescingEfficiency: number; + averageCommandDuration: number; + }[] +>; export { RealisticVolumeSystem, PriorityVolumeController, runPriorityStressTests }; -//# sourceMappingURL=priority-stress-test.d.mts.map \ No newline at end of file +//# sourceMappingURL=priority-stress-test.d.mts.map diff --git a/types/examples/run-priority-stress-test.d.mts b/types/examples/run-priority-stress-test.d.mts index d6896e5..72a5d46 100644 --- a/types/examples/run-priority-stress-test.d.mts +++ b/types/examples/run-priority-stress-test.d.mts @@ -13,4 +13,4 @@ * */ export {}; -//# sourceMappingURL=run-priority-stress-test.d.mts.map \ No newline at end of file +//# sourceMappingURL=run-priority-stress-test.d.mts.map diff --git a/types/examples/unified-priority-test.d.mts b/types/examples/unified-priority-test.d.mts index 857a1e0..9f45f7c 100644 --- a/types/examples/unified-priority-test.d.mts +++ b/types/examples/unified-priority-test.d.mts @@ -14,4 +14,4 @@ * */ export {}; -//# sourceMappingURL=unified-priority-test.d.mts.map \ No newline at end of file +//# sourceMappingURL=unified-priority-test.d.mts.map diff --git a/types/index.d.mts b/types/index.d.mts index f5345bc..b15d2ee 100644 --- a/types/index.d.mts +++ b/types/index.d.mts @@ -45,4 +45,4 @@ export { HoldMyTask as TaskManager }; export { HoldMyTask as TaskQueue }; export { HoldMyTask as QueueManager }; export { HoldMyTask as TaskProcessor }; -//# sourceMappingURL=index.d.mts.map \ No newline at end of file +//# sourceMappingURL=index.d.mts.map diff --git a/types/src/hold-my-task.d.mts b/types/src/hold-my-task.d.mts index e5a470c..59ba973 100644 --- a/types/src/hold-my-task.d.mts +++ b/types/src/hold-my-task.d.mts @@ -21,552 +21,564 @@ import { MinHeap } from "./utils.mjs"; * @extends EventEmitter */ export declare class HoldMyTask extends EventEmitter { - _syncMode: boolean | undefined; - options: { - constructor: Function; - toString(): string; - toLocaleString(): string; - valueOf(): Object; - hasOwnProperty(v: PropertyKey): boolean; - isPrototypeOf(v: Object): boolean; - propertyIsEnumerable(v: PropertyKey): boolean; - concurrency: number; - tick: number; - autoStart: boolean; - defaultPriority: number; - maxQueue: any; - priorities: {}; - smartScheduling: boolean; - healingInterval: number; - coalescing: { - defaults: Object; - keys: {}; - }; - coalescingWindowDuration: any; - coalescingMaxDelay: any; - coalescingMultipleCallbacks: any; - coalescingResolveAllPromises: any; - } | undefined; - pendingHeap: MinHeap | undefined; - readyHeap: MinHeap | undefined; - running: Set | undefined; - runningByPriority: Map | undefined; - tasks: Map | undefined; - nextId: number | undefined; - enqueueSeq: number | undefined; - isActive: boolean | undefined; - destroyed: boolean | undefined; - lastCompletedPriority: any; - nextAvailableTime: any; - schedulerTimeout: number | null | undefined; - healingInterval: number | null | undefined; - lastSchedulerRun: number | undefined; - intervalId: number | null | undefined; - coalescingGroups: Map | undefined; - coalescingRepresentatives: Map | undefined; - nextGroupId: number | undefined; - _warnedTaskOptions: Set | undefined; - timeoutId: number | undefined; - constructor(options?: {}); - /** - * Synchronous initialization for backwards compatibility - * @private - * @param {Object} options - Configuration options - */ - private _initializeSync; - /** - * Asynchronous initialization for modern usage - * @private - * @param {Object} options - Configuration options - * @returns {Promise} Promise that resolves to this instance - */ - private _initializeAsync; - /** - * Common initialization logic used by both sync and async modes - * @private - * @param {Object} options - Configuration options - */ - private _initializeCommon; - /** - * Internal convenience method to create a new HoldMyTask instance with async initialization. - * This enables event listeners to be attached before validation errors can occur. - * @param {Object} [options={}] - Configuration options - * @returns {Promise} Promise that resolves to the initialized instance - * @private - * @example - * // Internal usage - prefer new HoldMyTask({ sync: false }) for public API - * const queue = await HoldMyTask._create({ maxQueue: 100 }); - */ - private static _create; - /** - * Adds a task to the queue for execution. Supports both callback and promise-based APIs. - * @param {Function} task - The task function to execute. Can be sync or async. - * @param {Function|Object} [optionsOrCallback] - Either a callback function or options object - * @param {Object} [options={}] - Additional options (if callback was provided as second parameter) - * @param {string|number} [options.id] - Custom task ID for identification and later reference (must be unique) - * @param {number} [options.priority] - Task priority (higher numbers run first) - * @param {number} [options.timestamp] - When the task should be ready to run (milliseconds since epoch) - * @param {number} [options.startDelay] - Milliseconds from now when the task should be ready to run (convenience for timestamp calculation). Overrides the priority's startDelay - * @param {number} [options.start] - DEPRECATED: Use startDelay instead - * @param {AbortSignal} [options.signal] - AbortSignal to cancel the task - * @param {number} [options.timeout] - Task timeout in milliseconds (for execution time limit) - * @param {number} [options.expire] - Task expiration timestamp or milliseconds from now (for queue waiting time limit) - * @param {number} [options.postDelay] - Delay after this task completes before the next task can start. Overrides the priority's postDelay; use -1 to bypass the current delay period - * @param {number} [options.delay] - DEPRECATED: Use postDelay instead - * @param {boolean} [options.bypassDelay] - If true, skip any active delay period and start immediately - * @param {string} [options.coalescingKey] - Key for task coalescing - tasks with same key will be coalesced within windows - * @param {number} [options.coalescingWindowDuration] - Override coalescing window duration (task-level override of key-level and defaults) - * @param {number} [options.coalescingMaxDelay] - Override coalescing max delay (task-level override of key-level and defaults) - * @param {boolean} [options.coalescingMultipleCallbacks] - Override callback behavior (task-level override of key-level and defaults) - * @param {boolean} [options.coalescingResolveAllPromises] - Override promise resolution behavior (task-level override of key-level and defaults) - * @param {*} [options.metadata] - Arbitrary metadata to attach to the task - * @returns {Promise|Object} Promise (if no callback) or task control object with id, cancel, status methods - * @throws {Error} If queue is destroyed or full - * @example - * // Promise API - * const result = await queue.enqueue(async () => fetchData()); - * - * // Callback API - * queue.enqueue(() => processData(), (err, result) => { - * if (err) console.error(err); - * else console.log(result); - * }); - * - * // With options - * const task = queue.enqueue(myTask, { priority: 5, timeout: 30000, expire: 10000 }); - * - * // Bypass current delay for urgent task - * const urgent = queue.enqueue(urgentTask, { priority: 10, bypassDelay: true }); - * - * // Alternative: use postDelay: -1 to bypass - * const urgent2 = queue.enqueue(urgentTask, { priority: 10, postDelay: -1 }); - * - * // Coalescing tasks - multiple device status checks become one - * queue.enqueue(checkDeviceStatus, callback1, { coalescingKey: "device-123", coalescingWindowDuration: 1000 }); - * queue.enqueue(checkDeviceStatus, callback2, { coalescingKey: "device-123" }); // Gets coalesced with first - */ - enqueue(task: Function, optionsOrCallback?: Function | Object, options?: { - id?: string | number; - priority?: number; - timestamp?: number; - startDelay?: number; - start?: number; - signal?: AbortSignal; - timeout?: number; - expire?: number; - postDelay?: number; - delay?: number; - bypassDelay?: boolean; - coalescingKey?: string; - coalescingWindowDuration?: number; - coalescingMaxDelay?: number; - coalescingMultipleCallbacks?: boolean; - coalescingResolveAllPromises?: boolean; - metadata?: any; - }): Promise | Object; - /** - * Cancels a pending task by ID. - * @param {string} id - The task ID to cancel - * @param {string} [reason="Task canceled"] - Reason for cancellation - * @returns {boolean} True if task was found and cancelled, false otherwise - * @example - * const task = queue.enqueue(() => longRunningTask()); - * const cancelled = queue.cancel(task.id, "User requested cancellation"); - */ - cancel(id: string, reason?: string): boolean; - /** - * Alias for cancel() method for backward compatibility. - * @param {string|number} id - The task ID to cancel - * @param {string} [reason="Task canceled"] - Reason for cancellation - * @returns {boolean} True if task was found and cancelled, false otherwise - */ - cancelTask(id: string | number, reason?: string): boolean; - /** - * Pauses the task queue, stopping execution of new tasks. - * Currently running tasks will continue to completion. - * @returns {void} - * @example - * queue.pause(); - * // Queue stops processing new tasks - */ - pause(): void; - /** - * Resumes the task queue after being paused. - * @returns {void} - * @example - * queue.resume(); - * // Queue resumes processing tasks - */ - resume(): void; - /** - * Clears all pending and ready tasks from the queue. - * Currently running tasks will continue to completion. - * @returns {void} - * @example - * queue.clear(); - * // All queued tasks are removed - */ - clear(): void; - /** - * Returns the number of tasks in the queue. - * @returns {number} Number of tasks in the queue - * @example - * const totalTasks = queue.size(); // 5 - */ - size(): number; - /** - * Returns the number of tasks in the queue (alias for size()). - * @returns {number} Number of tasks in the queue - * @example - * const totalTasks = queue.length(); // 5 - */ - length(): number; - /** - * Returns the number of currently running tasks. - * @returns {number} Number of running tasks - * @example - * const runningTasks = queue.inflight(); // 2 - */ - inflight(): number; - /** - * Gets information about a coalescing group by key and group ID. - * @param {string} coalescingKey - The coalescing key - * @param {string} [groupId] - Optional group ID. If omitted, returns all groups for the key - * @returns {Object|Array|null} Group info object, array of groups, or null if not found - * @example - * // Get all groups for a coalescing key - * const groups = queue.getCoalescingGroup('ui.update'); - * - * // Get specific group by ID - * const group = queue.getCoalescingGroup('ui.update', '1'); - * console.log(group.tasks.size); // Number of tasks in group - * - * // Access individual task metadata - * for (const [taskId, task] of group.tasks) { - * console.log(`Task ${taskId}:`, task.metadata); - * } - */ - getCoalescingGroup(coalescingKey: string, groupId?: string): Object | any[] | null; - /** - * Gets metadata for all tasks in a coalescing group. - * @param {string} coalescingKey - The coalescing key - * @param {string} [groupId] - Optional group ID. If omitted, returns metadata from all groups for the key - * @returns {Array} Array of metadata objects with task IDs - * @example - * // Get metadata from all groups for a key - * const allMetadata = queue.getCoalescingGroupMetadata('ui.update'); - * - * // Get metadata from specific group - * const groupMetadata = queue.getCoalescingGroupMetadata('ui.update', '1'); - * - * // Example output: - * // [ - * // { taskId: '123', metadata: { userId: 100, action: 'save' } }, - * // { taskId: '124', metadata: { userId: 200, action: 'delete' } } - * // ] - */ - getCoalescingGroupMetadata(coalescingKey: string, groupId?: string): any[]; - /** - * Gets a summary of all active coalescing groups. - * @returns {Object} Summary object with coalescing key stats - * @example - * const summary = queue.getCoalescingGroupsSummary(); - * console.log(summary); - * // { - * // 'ui.update': { groupCount: 2, totalTasks: 5 }, - * // 'api.batch': { groupCount: 1, totalTasks: 3 } - * // } - */ - getCoalescingGroupsSummary(): Object; - /** - * Finds the coalescing group that contains a specific task ID. - * @param {string|number} taskId - The task ID to search for - * @returns {Object|null} Group information including the task's metadata, or null if not found - * @example - * const groupInfo = queue.findCoalescingGroupByTaskId('123'); - * if (groupInfo) { - * console.log('Task is in group:', groupInfo.groupId); - * console.log('Task metadata:', groupInfo.task.metadata); - * console.log('Other tasks in group:', groupInfo.groupTasks.length); - * } - */ - findCoalescingGroupByTaskId(taskId: string | number): Object | null; - /** - * Destroys the queue, canceling all tasks and stopping the scheduler. - * Once destroyed, the queue cannot be reused. - * @returns {void} - * @example - * queue.destroy(); - * // Queue is permanently shut down - */ - destroy(): void; - /** - * Returns the current timestamp in milliseconds. - * @returns {number} Current timestamp - * @example - * const timestamp = queue.now(); // 1699564800000 - */ - now(): number; - /** - * Main scheduler tick that moves ready tasks and starts execution. - * @returns {void} - * @private - */ - private schedulerTick; - /** - * Checks if a task can start based on both global and per-priority concurrency limits. - * @param {Object} task - The task to check - * @returns {boolean} True if the task can start, false if concurrency limits prevent it - * @private - */ - private _canStartTask; - /** - * Clears all active timers (intervals and timeouts). - * @returns {void} - * @private - */ - private clearTimers; - /** - * Calculates when the next scheduler run should happen and sets appropriate timeout. - * @private - * @returns {void} - * - * @description - * Smart scheduling that calculates the optimal time for the next scheduler run based on: - * - When the next pending task becomes ready - * - When delay periods end - * - Whether there are tasks that can run immediately - * - * Uses setTimeout for precise timing instead of constant polling intervals. - */ - private scheduleSmartTimeout; - /** - * Runs the main scheduler logic and reschedules if needed. - * @private - * @returns {void} - * - * @description - * Executes the scheduler tick logic and then determines if more scheduling is needed. - * Tracks when scheduler last ran for healing mechanism. - */ - private runScheduler; - /** - * Starts the self-healing interval that ensures scheduler continues working. - * @private - * @returns {void} - * - * @description - * Healing mechanism that periodically checks if the scheduler should be running - * but isn't due to timeout failures or other issues. Runs every healingInterval milliseconds. - */ - private startHealingInterval; - /** - * Clears all scheduler-related timers. - * @private - * @returns {void} - * - * @description - * Cleans up both the main scheduler timeout and the healing interval timer. - */ - private clearSchedulerTimers; - /** - * Configure coalescing settings for specific keys dynamically. - * @param {string} coalescingKey - The coalescing key to configure - * @param {Object} config - Configuration for this key - * @param {number} [config.windowDuration] - Window duration in milliseconds for this key - * @param {number} [config.maxDelay] - Maximum delay in milliseconds for this key - * @param {number} [config.postDelay] - Post-completion delay in milliseconds for this key - * @param {number} [config.startDelay] - Pre-execution delay in milliseconds for this key - * @param {number} [config.delay] - DEPRECATED: Use postDelay instead - * @param {number} [config.start] - DEPRECATED: Use startDelay instead - * @param {boolean} [config.multipleCallbacks] - Whether to call multiple callbacks for this key - * @param {boolean} [config.resolveAllPromises] - Whether to resolve all promises for this key - * @returns {void} - * - * @example - * // Configure specific keys after queue creation - * queue.configureCoalescingKey('ui.update', { - * windowDuration: 100, - * maxDelay: 500, - * postDelay: 25, - * startDelay: 0 - * }); - * - * queue.configureCoalescingKey('api.batch', { - * windowDuration: 1000, - * maxDelay: 5000, - * postDelay: 100, - * startDelay: 200, - * resolveAllPromises: false - * }); - */ - configureCoalescingKey(coalescingKey: string, config: { - windowDuration?: number; - maxDelay?: number; - postDelay?: number; - startDelay?: number; - delay?: number; - start?: number; - multipleCallbacks?: boolean; - resolveAllPromises?: boolean; - }): void; - /** - * Get the effective coalescing configuration for a specific key. - * @param {string} coalescingKey - The coalescing key to get configuration for - * @param {Object} [taskOptions={}] - Task-level options that may override key configuration - * @returns {Object} The effective configuration for this key - * - * @example - * // Get effective configuration for a key - * const config = queue.getCoalescingConfig('ui.update'); - * console.log(`UI updates coalesce within ${config.windowDuration}ms with ${config.postDelay}ms post-completion delay`); - * - * // Check with task-level overrides - * const effectiveConfig = queue.getCoalescingConfig('ui.update', { - * coalescingWindowDuration: 50, - * postDelay: 30 // Deprecated task-level name delay is still accepted - * }); - */ - getCoalescingConfig(coalescingKey: string, taskOptions?: Object): Object; - /** - * Get all configured coalescing keys and their configurations. - * @returns {Object} Map of coalescingKey to configuration - * - * @example - * // See all configured coalescing keys - * const allConfigs = queue.getCoalescingConfigurations(); - * Object.entries(allConfigs).forEach(([key, config]) => { - * console.log(`${key}: ${config.windowDuration}ms window, ${config.maxDelay}ms max delay, ${config.postDelay}ms post-completion delay, ${config.startDelay}ms pre-execution delay`); - * }); - */ - getCoalescingConfigurations(): Object; - /** - * Configure default settings for specific priorities dynamically. - * @param {number} priority - The priority level to configure - * @param {Object} config - Configuration for this priority - * @param {number} [config.postDelay] - Default post-completion delay in milliseconds for this priority - * @param {number} [config.startDelay] - Default pre-execution delay in milliseconds for this priority - * @param {number} [config.delay] - DEPRECATED: Use postDelay instead - * @param {number} [config.start] - DEPRECATED: Use startDelay instead - * @returns {void} - * - * @example - * // Configure priority defaults after queue creation - * queue.configurePriority(1, { - * postDelay: 100, // High priority tasks have 100ms delay after completion - * startDelay: 0 // High priority tasks start immediately - * }); - * - * queue.configurePriority(3, { - * postDelay: 0, // Low priority tasks have no delay after completion - * startDelay: 200 // Low priority tasks wait 200ms before starting - * }); - */ - configurePriority(priority: number, config: { - postDelay?: number; - startDelay?: number; - delay?: number; - start?: number; - }): void; - /** - * Get the effective configuration for a specific priority. - * @param {number} priority - The priority level to get configuration for - * @param {Object} [taskOptions={}] - Task-level options that may override priority configuration - * @returns {Object} The effective configuration for this priority - * - * @example - * // Get effective configuration for a priority - * const config = queue.getPriorityConfig(1); - * console.log(`Priority 1 tasks: ${config.delay}ms delay, ${config.start}ms start delay`); - * - * // Check with task-level overrides - * const effectiveConfig = queue.getPriorityConfig(1, { - * postDelay: 50, - * startDelay: 10 // Deprecated task-level names delay/start are still accepted - * }); - */ - getPriorityConfig(priority: number, taskOptions?: Object): Object; - /** - * Get all configured priorities and their configurations. - * @returns {Object} Map of priority to configuration - * - * @example - * // See all configured priorities - * const allConfigs = queue.getPriorityConfigurations(); - * Object.entries(allConfigs).forEach(([priority, config]) => { - * console.log(`Priority ${priority}: ${config.delay}ms delay, ${config.start}ms start delay`); - * }); - */ - getPriorityConfigurations(): Object; - /** - * Alias for destroy() method for common queue system naming. - * @returns {void} - */ - shutdown(): void; - /** - * Alias for enqueue() method for common queue system naming. - * @param {Function} task - The task function to execute - * @param {Function|Object} optionsOrCallback - Callback function or options object - * @param {Object} options - Task options (if callback provided as second parameter) - * @returns {Promise|TaskHandle} Promise if no callback provided, TaskHandle otherwise - */ - schedule(task: Function, optionsOrCallback: Function | Object, options?: Object): Promise | TaskHandle; - /** - * Alias for enqueue() method for common queue system naming. - * @param {Function} task - The task function to execute - * @param {Function|Object} optionsOrCallback - Callback function or options object - * @param {Object} options - Task options (if callback provided as second parameter) - * @returns {Promise|TaskHandle} Promise if no callback provided, TaskHandle otherwise - */ - add(task: Function, optionsOrCallback: Function | Object, options?: Object): Promise | TaskHandle; - /** - * Find a task by its ID. - * @param {string|number} id - The task ID to find - * @returns {Object|null} Task object if found, null otherwise - */ - get(id: string | number): Object | null; - /** - * Check if a task with the given ID exists. - * @param {string|number} id - The task ID to check - * @returns {boolean} True if task exists, false otherwise - */ - has(id: string | number): boolean; - /** - * Alias for get() method for backward compatibility. - * @param {string|number} id - The task ID to find - * @returns {Object|null} Task object if found, null otherwise - */ - getTask(id: string | number): Object | null; - /** - * Alias for has() method for backward compatibility. - * @param {string|number} id - The task ID to check - * @returns {boolean} True if task exists, false otherwise - */ - hasTask(id: string | number): boolean; - /** - * Get detailed information about the current queue state for debugging. - * @returns {Object} Comprehensive queue state information - */ - inspect(): Object; - /** - * Get information about active timers and scheduler state. - * @returns {Object} Timer and scheduler information - */ - inspectTimers(): Object; - /** - * Get a summary of all queued tasks by status. - * @returns {Object} Task summary by status - */ - inspectTasks(): Object; - /** - * Get detailed information about the scheduler state and timing. - * @returns {Object} Scheduler state information - */ - inspectScheduler(): Object; - /** - * Log comprehensive queue state to console for debugging. - * @param {boolean} [detailed=false] - Whether to include detailed task information - */ - debugLog(detailed?: boolean): void; + _syncMode: boolean | undefined; + options: + | { + constructor: Function; + toString(): string; + toLocaleString(): string; + valueOf(): Object; + hasOwnProperty(v: PropertyKey): boolean; + isPrototypeOf(v: Object): boolean; + propertyIsEnumerable(v: PropertyKey): boolean; + concurrency: number; + tick: number; + autoStart: boolean; + defaultPriority: number; + maxQueue: any; + priorities: {}; + smartScheduling: boolean; + healingInterval: number; + coalescing: { + defaults: Object; + keys: {}; + }; + coalescingWindowDuration: any; + coalescingMaxDelay: any; + coalescingMultipleCallbacks: any; + coalescingResolveAllPromises: any; + } + | undefined; + pendingHeap: MinHeap | undefined; + readyHeap: MinHeap | undefined; + running: Set | undefined; + runningByPriority: Map | undefined; + tasks: Map | undefined; + nextId: number | undefined; + enqueueSeq: number | undefined; + isActive: boolean | undefined; + destroyed: boolean | undefined; + lastCompletedPriority: any; + nextAvailableTime: any; + schedulerTimeout: number | null | undefined; + healingInterval: number | null | undefined; + lastSchedulerRun: number | undefined; + intervalId: number | null | undefined; + coalescingGroups: Map | undefined; + coalescingRepresentatives: Map | undefined; + nextGroupId: number | undefined; + _warnedTaskOptions: Set | undefined; + timeoutId: number | undefined; + constructor(options?: {}); + /** + * Synchronous initialization for backwards compatibility + * @private + * @param {Object} options - Configuration options + */ + private _initializeSync; + /** + * Asynchronous initialization for modern usage + * @private + * @param {Object} options - Configuration options + * @returns {Promise} Promise that resolves to this instance + */ + private _initializeAsync; + /** + * Common initialization logic used by both sync and async modes + * @private + * @param {Object} options - Configuration options + */ + private _initializeCommon; + /** + * Internal convenience method to create a new HoldMyTask instance with async initialization. + * This enables event listeners to be attached before validation errors can occur. + * @param {Object} [options={}] - Configuration options + * @returns {Promise} Promise that resolves to the initialized instance + * @private + * @example + * // Internal usage - prefer new HoldMyTask({ sync: false }) for public API + * const queue = await HoldMyTask._create({ maxQueue: 100 }); + */ + private static _create; + /** + * Adds a task to the queue for execution. Supports both callback and promise-based APIs. + * @param {Function} task - The task function to execute. Can be sync or async. + * @param {Function|Object} [optionsOrCallback] - Either a callback function or options object + * @param {Object} [options={}] - Additional options (if callback was provided as second parameter) + * @param {string|number} [options.id] - Custom task ID for identification and later reference (must be unique) + * @param {number} [options.priority] - Task priority (higher numbers run first) + * @param {number} [options.timestamp] - When the task should be ready to run (milliseconds since epoch) + * @param {number} [options.startDelay] - Milliseconds from now when the task should be ready to run (convenience for timestamp calculation). Overrides the priority's startDelay + * @param {number} [options.start] - DEPRECATED: Use startDelay instead + * @param {AbortSignal} [options.signal] - AbortSignal to cancel the task + * @param {number} [options.timeout] - Task timeout in milliseconds (for execution time limit) + * @param {number} [options.expire] - Task expiration timestamp or milliseconds from now (for queue waiting time limit) + * @param {number} [options.postDelay] - Delay after this task completes before the next task can start. Overrides the priority's postDelay; use -1 to bypass the current delay period + * @param {number} [options.delay] - DEPRECATED: Use postDelay instead + * @param {boolean} [options.bypassDelay] - If true, skip any active delay period and start immediately + * @param {string} [options.coalescingKey] - Key for task coalescing - tasks with same key will be coalesced within windows + * @param {number} [options.coalescingWindowDuration] - Override coalescing window duration (task-level override of key-level and defaults) + * @param {number} [options.coalescingMaxDelay] - Override coalescing max delay (task-level override of key-level and defaults) + * @param {boolean} [options.coalescingMultipleCallbacks] - Override callback behavior (task-level override of key-level and defaults) + * @param {boolean} [options.coalescingResolveAllPromises] - Override promise resolution behavior (task-level override of key-level and defaults) + * @param {*} [options.metadata] - Arbitrary metadata to attach to the task + * @returns {Promise|Object} Promise (if no callback) or task control object with id, cancel, status methods + * @throws {Error} If queue is destroyed or full + * @example + * // Promise API + * const result = await queue.enqueue(async () => fetchData()); + * + * // Callback API + * queue.enqueue(() => processData(), (err, result) => { + * if (err) console.error(err); + * else console.log(result); + * }); + * + * // With options + * const task = queue.enqueue(myTask, { priority: 5, timeout: 30000, expire: 10000 }); + * + * // Bypass current delay for urgent task + * const urgent = queue.enqueue(urgentTask, { priority: 10, bypassDelay: true }); + * + * // Alternative: use postDelay: -1 to bypass + * const urgent2 = queue.enqueue(urgentTask, { priority: 10, postDelay: -1 }); + * + * // Coalescing tasks - multiple device status checks become one + * queue.enqueue(checkDeviceStatus, callback1, { coalescingKey: "device-123", coalescingWindowDuration: 1000 }); + * queue.enqueue(checkDeviceStatus, callback2, { coalescingKey: "device-123" }); // Gets coalesced with first + */ + enqueue( + task: Function, + optionsOrCallback?: Function | Object, + options?: { + id?: string | number; + priority?: number; + timestamp?: number; + startDelay?: number; + start?: number; + signal?: AbortSignal; + timeout?: number; + expire?: number; + postDelay?: number; + delay?: number; + bypassDelay?: boolean; + coalescingKey?: string; + coalescingWindowDuration?: number; + coalescingMaxDelay?: number; + coalescingMultipleCallbacks?: boolean; + coalescingResolveAllPromises?: boolean; + metadata?: any; + } + ): Promise | Object; + /** + * Cancels a pending task by ID. + * @param {string} id - The task ID to cancel + * @param {string} [reason="Task canceled"] - Reason for cancellation + * @returns {boolean} True if task was found and cancelled, false otherwise + * @example + * const task = queue.enqueue(() => longRunningTask()); + * const cancelled = queue.cancel(task.id, "User requested cancellation"); + */ + cancel(id: string, reason?: string): boolean; + /** + * Alias for cancel() method for backward compatibility. + * @param {string|number} id - The task ID to cancel + * @param {string} [reason="Task canceled"] - Reason for cancellation + * @returns {boolean} True if task was found and cancelled, false otherwise + */ + cancelTask(id: string | number, reason?: string): boolean; + /** + * Pauses the task queue, stopping execution of new tasks. + * Currently running tasks will continue to completion. + * @returns {void} + * @example + * queue.pause(); + * // Queue stops processing new tasks + */ + pause(): void; + /** + * Resumes the task queue after being paused. + * @returns {void} + * @example + * queue.resume(); + * // Queue resumes processing tasks + */ + resume(): void; + /** + * Clears all pending and ready tasks from the queue. + * Currently running tasks will continue to completion. + * @returns {void} + * @example + * queue.clear(); + * // All queued tasks are removed + */ + clear(): void; + /** + * Returns the number of tasks in the queue. + * @returns {number} Number of tasks in the queue + * @example + * const totalTasks = queue.size(); // 5 + */ + size(): number; + /** + * Returns the number of tasks in the queue (alias for size()). + * @returns {number} Number of tasks in the queue + * @example + * const totalTasks = queue.length(); // 5 + */ + length(): number; + /** + * Returns the number of currently running tasks. + * @returns {number} Number of running tasks + * @example + * const runningTasks = queue.inflight(); // 2 + */ + inflight(): number; + /** + * Gets information about a coalescing group by key and group ID. + * @param {string} coalescingKey - The coalescing key + * @param {string} [groupId] - Optional group ID. If omitted, returns all groups for the key + * @returns {Object|Array|null} Group info object, array of groups, or null if not found + * @example + * // Get all groups for a coalescing key + * const groups = queue.getCoalescingGroup('ui.update'); + * + * // Get specific group by ID + * const group = queue.getCoalescingGroup('ui.update', '1'); + * console.log(group.tasks.size); // Number of tasks in group + * + * // Access individual task metadata + * for (const [taskId, task] of group.tasks) { + * console.log(`Task ${taskId}:`, task.metadata); + * } + */ + getCoalescingGroup(coalescingKey: string, groupId?: string): Object | any[] | null; + /** + * Gets metadata for all tasks in a coalescing group. + * @param {string} coalescingKey - The coalescing key + * @param {string} [groupId] - Optional group ID. If omitted, returns metadata from all groups for the key + * @returns {Array} Array of metadata objects with task IDs + * @example + * // Get metadata from all groups for a key + * const allMetadata = queue.getCoalescingGroupMetadata('ui.update'); + * + * // Get metadata from specific group + * const groupMetadata = queue.getCoalescingGroupMetadata('ui.update', '1'); + * + * // Example output: + * // [ + * // { taskId: '123', metadata: { userId: 100, action: 'save' } }, + * // { taskId: '124', metadata: { userId: 200, action: 'delete' } } + * // ] + */ + getCoalescingGroupMetadata(coalescingKey: string, groupId?: string): any[]; + /** + * Gets a summary of all active coalescing groups. + * @returns {Object} Summary object with coalescing key stats + * @example + * const summary = queue.getCoalescingGroupsSummary(); + * console.log(summary); + * // { + * // 'ui.update': { groupCount: 2, totalTasks: 5 }, + * // 'api.batch': { groupCount: 1, totalTasks: 3 } + * // } + */ + getCoalescingGroupsSummary(): Object; + /** + * Finds the coalescing group that contains a specific task ID. + * @param {string|number} taskId - The task ID to search for + * @returns {Object|null} Group information including the task's metadata, or null if not found + * @example + * const groupInfo = queue.findCoalescingGroupByTaskId('123'); + * if (groupInfo) { + * console.log('Task is in group:', groupInfo.groupId); + * console.log('Task metadata:', groupInfo.task.metadata); + * console.log('Other tasks in group:', groupInfo.groupTasks.length); + * } + */ + findCoalescingGroupByTaskId(taskId: string | number): Object | null; + /** + * Destroys the queue, canceling all tasks and stopping the scheduler. + * Once destroyed, the queue cannot be reused. + * @returns {void} + * @example + * queue.destroy(); + * // Queue is permanently shut down + */ + destroy(): void; + /** + * Returns the current timestamp in milliseconds. + * @returns {number} Current timestamp + * @example + * const timestamp = queue.now(); // 1699564800000 + */ + now(): number; + /** + * Main scheduler tick that moves ready tasks and starts execution. + * @returns {void} + * @private + */ + private schedulerTick; + /** + * Checks if a task can start based on both global and per-priority concurrency limits. + * @param {Object} task - The task to check + * @returns {boolean} True if the task can start, false if concurrency limits prevent it + * @private + */ + private _canStartTask; + /** + * Clears all active timers (intervals and timeouts). + * @returns {void} + * @private + */ + private clearTimers; + /** + * Calculates when the next scheduler run should happen and sets appropriate timeout. + * @private + * @returns {void} + * + * @description + * Smart scheduling that calculates the optimal time for the next scheduler run based on: + * - When the next pending task becomes ready + * - When delay periods end + * - Whether there are tasks that can run immediately + * + * Uses setTimeout for precise timing instead of constant polling intervals. + */ + private scheduleSmartTimeout; + /** + * Runs the main scheduler logic and reschedules if needed. + * @private + * @returns {void} + * + * @description + * Executes the scheduler tick logic and then determines if more scheduling is needed. + * Tracks when scheduler last ran for healing mechanism. + */ + private runScheduler; + /** + * Starts the self-healing interval that ensures scheduler continues working. + * @private + * @returns {void} + * + * @description + * Healing mechanism that periodically checks if the scheduler should be running + * but isn't due to timeout failures or other issues. Runs every healingInterval milliseconds. + */ + private startHealingInterval; + /** + * Clears all scheduler-related timers. + * @private + * @returns {void} + * + * @description + * Cleans up both the main scheduler timeout and the healing interval timer. + */ + private clearSchedulerTimers; + /** + * Configure coalescing settings for specific keys dynamically. + * @param {string} coalescingKey - The coalescing key to configure + * @param {Object} config - Configuration for this key + * @param {number} [config.windowDuration] - Window duration in milliseconds for this key + * @param {number} [config.maxDelay] - Maximum delay in milliseconds for this key + * @param {number} [config.postDelay] - Post-completion delay in milliseconds for this key + * @param {number} [config.startDelay] - Pre-execution delay in milliseconds for this key + * @param {number} [config.delay] - DEPRECATED: Use postDelay instead + * @param {number} [config.start] - DEPRECATED: Use startDelay instead + * @param {boolean} [config.multipleCallbacks] - Whether to call multiple callbacks for this key + * @param {boolean} [config.resolveAllPromises] - Whether to resolve all promises for this key + * @returns {void} + * + * @example + * // Configure specific keys after queue creation + * queue.configureCoalescingKey('ui.update', { + * windowDuration: 100, + * maxDelay: 500, + * postDelay: 25, + * startDelay: 0 + * }); + * + * queue.configureCoalescingKey('api.batch', { + * windowDuration: 1000, + * maxDelay: 5000, + * postDelay: 100, + * startDelay: 200, + * resolveAllPromises: false + * }); + */ + configureCoalescingKey( + coalescingKey: string, + config: { + windowDuration?: number; + maxDelay?: number; + postDelay?: number; + startDelay?: number; + delay?: number; + start?: number; + multipleCallbacks?: boolean; + resolveAllPromises?: boolean; + } + ): void; + /** + * Get the effective coalescing configuration for a specific key. + * @param {string} coalescingKey - The coalescing key to get configuration for + * @param {Object} [taskOptions={}] - Task-level options that may override key configuration + * @returns {Object} The effective configuration for this key + * + * @example + * // Get effective configuration for a key + * const config = queue.getCoalescingConfig('ui.update'); + * console.log(`UI updates coalesce within ${config.windowDuration}ms with ${config.postDelay}ms post-completion delay`); + * + * // Check with task-level overrides + * const effectiveConfig = queue.getCoalescingConfig('ui.update', { + * coalescingWindowDuration: 50, + * postDelay: 30 // Deprecated task-level name delay is still accepted + * }); + */ + getCoalescingConfig(coalescingKey: string, taskOptions?: Object): Object; + /** + * Get all configured coalescing keys and their configurations. + * @returns {Object} Map of coalescingKey to configuration + * + * @example + * // See all configured coalescing keys + * const allConfigs = queue.getCoalescingConfigurations(); + * Object.entries(allConfigs).forEach(([key, config]) => { + * console.log(`${key}: ${config.windowDuration}ms window, ${config.maxDelay}ms max delay, ${config.postDelay}ms post-completion delay, ${config.startDelay}ms pre-execution delay`); + * }); + */ + getCoalescingConfigurations(): Object; + /** + * Configure default settings for specific priorities dynamically. + * @param {number} priority - The priority level to configure + * @param {Object} config - Configuration for this priority + * @param {number} [config.postDelay] - Default post-completion delay in milliseconds for this priority + * @param {number} [config.startDelay] - Default pre-execution delay in milliseconds for this priority + * @param {number} [config.delay] - DEPRECATED: Use postDelay instead + * @param {number} [config.start] - DEPRECATED: Use startDelay instead + * @returns {void} + * + * @example + * // Configure priority defaults after queue creation + * queue.configurePriority(1, { + * postDelay: 100, // High priority tasks have 100ms delay after completion + * startDelay: 0 // High priority tasks start immediately + * }); + * + * queue.configurePriority(3, { + * postDelay: 0, // Low priority tasks have no delay after completion + * startDelay: 200 // Low priority tasks wait 200ms before starting + * }); + */ + configurePriority( + priority: number, + config: { + postDelay?: number; + startDelay?: number; + delay?: number; + start?: number; + } + ): void; + /** + * Get the effective configuration for a specific priority. + * @param {number} priority - The priority level to get configuration for + * @param {Object} [taskOptions={}] - Task-level options that may override priority configuration + * @returns {Object} The effective configuration for this priority + * + * @example + * // Get effective configuration for a priority + * const config = queue.getPriorityConfig(1); + * console.log(`Priority 1 tasks: ${config.delay}ms delay, ${config.start}ms start delay`); + * + * // Check with task-level overrides + * const effectiveConfig = queue.getPriorityConfig(1, { + * postDelay: 50, + * startDelay: 10 // Deprecated task-level names delay/start are still accepted + * }); + */ + getPriorityConfig(priority: number, taskOptions?: Object): Object; + /** + * Get all configured priorities and their configurations. + * @returns {Object} Map of priority to configuration + * + * @example + * // See all configured priorities + * const allConfigs = queue.getPriorityConfigurations(); + * Object.entries(allConfigs).forEach(([priority, config]) => { + * console.log(`Priority ${priority}: ${config.delay}ms delay, ${config.start}ms start delay`); + * }); + */ + getPriorityConfigurations(): Object; + /** + * Alias for destroy() method for common queue system naming. + * @returns {void} + */ + shutdown(): void; + /** + * Alias for enqueue() method for common queue system naming. + * @param {Function} task - The task function to execute + * @param {Function|Object} optionsOrCallback - Callback function or options object + * @param {Object} options - Task options (if callback provided as second parameter) + * @returns {Promise|TaskHandle} Promise if no callback provided, TaskHandle otherwise + */ + schedule(task: Function, optionsOrCallback: Function | Object, options?: Object): Promise | TaskHandle; + /** + * Alias for enqueue() method for common queue system naming. + * @param {Function} task - The task function to execute + * @param {Function|Object} optionsOrCallback - Callback function or options object + * @param {Object} options - Task options (if callback provided as second parameter) + * @returns {Promise|TaskHandle} Promise if no callback provided, TaskHandle otherwise + */ + add(task: Function, optionsOrCallback: Function | Object, options?: Object): Promise | TaskHandle; + /** + * Find a task by its ID. + * @param {string|number} id - The task ID to find + * @returns {Object|null} Task object if found, null otherwise + */ + get(id: string | number): Object | null; + /** + * Check if a task with the given ID exists. + * @param {string|number} id - The task ID to check + * @returns {boolean} True if task exists, false otherwise + */ + has(id: string | number): boolean; + /** + * Alias for get() method for backward compatibility. + * @param {string|number} id - The task ID to find + * @returns {Object|null} Task object if found, null otherwise + */ + getTask(id: string | number): Object | null; + /** + * Alias for has() method for backward compatibility. + * @param {string|number} id - The task ID to check + * @returns {boolean} True if task exists, false otherwise + */ + hasTask(id: string | number): boolean; + /** + * Get detailed information about the current queue state for debugging. + * @returns {Object} Comprehensive queue state information + */ + inspect(): Object; + /** + * Get information about active timers and scheduler state. + * @returns {Object} Timer and scheduler information + */ + inspectTimers(): Object; + /** + * Get a summary of all queued tasks by status. + * @returns {Object} Task summary by status + */ + inspectTasks(): Object; + /** + * Get detailed information about the scheduler state and timing. + * @returns {Object} Scheduler state information + */ + inspectScheduler(): Object; + /** + * Log comprehensive queue state to console for debugging. + * @param {boolean} [detailed=false] - Whether to include detailed task information + */ + debugLog(detailed?: boolean): void; } -//# sourceMappingURL=hold-my-task.d.mts.map \ No newline at end of file +//# sourceMappingURL=hold-my-task.d.mts.map diff --git a/types/src/utils.d.mts b/types/src/utils.d.mts index a19ba63..729fa64 100644 --- a/types/src/utils.d.mts +++ b/types/src/utils.d.mts @@ -17,58 +17,58 @@ * Maintains the heap property where parent nodes are smaller than their children. */ export declare class MinHeap { - heap: any[]; - compare: Function; - /** - * Creates a new MinHeap with a custom comparison function. - * @param {Function} compare - Comparison function that returns negative if a < b, positive if a > b, 0 if equal - * @example - * // Priority queue (higher priority = smaller value) - * const heap = new MinHeap((a, b) => a.priority - b.priority); - */ - constructor(compare: Function); - /** - * Adds an item to the heap, maintaining heap property. - * @param {*} item - The item to add to the heap - * @returns {void} - * @example - * heap.push({ value: 5, priority: 1 }); - */ - push(item: any): void; - /** - * Removes and returns the minimum item from the heap. - * @returns {*|undefined} The minimum item, or undefined if heap is empty - * @example - * const min = heap.pop(); // Returns item with smallest comparison value - */ - pop(): any | undefined; - /** - * Returns the minimum item without removing it from the heap. - * @returns {*|undefined} The minimum item, or undefined if heap is empty - * @example - * const min = heap.peek(); // Look at minimum without removing - */ - peek(): any | undefined; - /** - * Returns the number of items in the heap. - * @returns {number} The size of the heap - * @example - * const count = heap.size(); // 5 - */ - size(): number; - /** - * Moves an item up the heap to maintain heap property after insertion. - * @param {number} index - Index of the item to bubble up - * @returns {void} - * @private - */ - private bubbleUp; - /** - * Moves an item down the heap to maintain heap property after removal. - * @param {number} index - Index of the item to sink down - * @returns {void} - * @private - */ - private sinkDown; + heap: any[]; + compare: Function; + /** + * Creates a new MinHeap with a custom comparison function. + * @param {Function} compare - Comparison function that returns negative if a < b, positive if a > b, 0 if equal + * @example + * // Priority queue (higher priority = smaller value) + * const heap = new MinHeap((a, b) => a.priority - b.priority); + */ + constructor(compare: Function); + /** + * Adds an item to the heap, maintaining heap property. + * @param {*} item - The item to add to the heap + * @returns {void} + * @example + * heap.push({ value: 5, priority: 1 }); + */ + push(item: any): void; + /** + * Removes and returns the minimum item from the heap. + * @returns {*|undefined} The minimum item, or undefined if heap is empty + * @example + * const min = heap.pop(); // Returns item with smallest comparison value + */ + pop(): any | undefined; + /** + * Returns the minimum item without removing it from the heap. + * @returns {*|undefined} The minimum item, or undefined if heap is empty + * @example + * const min = heap.peek(); // Look at minimum without removing + */ + peek(): any | undefined; + /** + * Returns the number of items in the heap. + * @returns {number} The size of the heap + * @example + * const count = heap.size(); // 5 + */ + size(): number; + /** + * Moves an item up the heap to maintain heap property after insertion. + * @param {number} index - Index of the item to bubble up + * @returns {void} + * @private + */ + private bubbleUp; + /** + * Moves an item down the heap to maintain heap property after removal. + * @param {number} index - Index of the item to sink down + * @returns {void} + * @private + */ + private sinkDown; } -//# sourceMappingURL=utils.d.mts.map \ No newline at end of file +//# sourceMappingURL=utils.d.mts.map From 90400186ca3533bd589f4cd3b6cd0e3ce191a19d Mon Sep 17 00:00:00 2001 From: Shinrai Date: Sat, 3 Oct 2026 17:26:32 -0700 Subject: [PATCH 05/16] fix(config): warn on non-integer priority keys instead of dropping them silently The constructor ran `priorities` (and the deprecated `delays`) keys through parseInt and silently discarded anything that came back NaN, so a config such as `priorities: { high: { postDelay: 100 } }` simply did nothing. Keys parseInt could only partly read ("2.5") were silently truncated. Non-integer keys now emit a `warning` event: { type: "invalid-priority", message, option, key, priority } A key parseInt can't read is still ignored (priority: null); a truncated key keeps its historical meaning (the truncated priority) but is reported. Integer keys, including negative ones, are unaffected. A warning rather than a thrown TypeError, for consistency with how the library treats other questionable configuration: the constructor never throws for config, deprecated names are reported through `warning` events, and invalid runtime config (configurePriority / configureCoalescingKey) is reported through events rather than thrown. Throwing would also turn configs that currently construct fine into a crash on a patch upgrade. Fixes #54 --- README.md | 2 +- src/hold-my-task.mjs | 47 +++++++++++-- tests/PriorityKeyValidation.test.vitest.mjs | 73 +++++++++++++++++++++ types/src/hold-my-task.d.mts.map | 2 +- 4 files changed, 117 insertions(+), 7 deletions(-) create mode 100644 tests/PriorityKeyValidation.test.vitest.mjs diff --git a/README.md b/README.md index e60feab..cfc2853 100644 --- a/README.md +++ b/README.md @@ -280,7 +280,7 @@ This pattern is particularly useful when you need to handle initialization error - `defaultPriority` (number, default: 0) - Default task priority - `maxQueue` (number, default: Infinity) - Maximum queued tasks. Use `-1` for unlimited queue capacity (equivalent to `Infinity`) - `delays` (object, default: {}) - **DEPRECATED:** Priority-to-delay mapping for completion delays (use `priorities` instead) -- `priorities` (object, default: {}) - Priority-specific configuration: `{ [priority]: { concurrency, postDelay, startDelay } }` +- `priorities` (object, default: {}) - Priority-specific configuration: `{ [priority]: { concurrency, postDelay, startDelay } }`. Keys must be integers; any other key emits a `warning` event with `type: "invalid-priority"` (and is ignored when it isn't numeric at all) - `concurrency` (number) - Maximum concurrent tasks for this priority (defaults to global concurrency limit) - `postDelay` (number) - Delay after task completion before next task of same priority - `startDelay` (number) - Delay before task execution (pre-execution delay) diff --git a/src/hold-my-task.mjs b/src/hold-my-task.mjs index c335c26..c10af99 100644 --- a/src/hold-my-task.mjs +++ b/src/hold-my-task.mjs @@ -35,7 +35,7 @@ export class HoldMyTask extends EventEmitter { * @param {boolean} [options.smartScheduling=true] - Use dynamic timeouts instead of constant polling for better performance * @param {number} [options.tick=25] - Polling interval in milliseconds when smartScheduling is disabled * @param {number} [options.healingInterval=5000] - Self-healing check interval in milliseconds (smart scheduling only) - * @param {Object} [options.priorities={}] - Priority-specific default configurations + * @param {Object} [options.priorities={}] - Priority-specific default configurations, keyed by integer priority. A non-integer key emits an "invalid-priority" warning event * @param {number} [options.priorities[priority].concurrency] - Maximum concurrent tasks for this priority (defaults to global concurrency limit) * @param {number} [options.priorities[priority].postDelay] - Delay after task completion before next task of same priority * @param {number} [options.priorities[priority].startDelay] - Delay before task execution (pre-execution delay) @@ -210,6 +210,41 @@ export class HoldMyTask extends EventEmitter { } } + /** + * Resolves a `priorities` / `delays` object key to a numeric priority. + * Integer keys ("0", "10", "-1") resolve silently. Any other key emits a `warning` event of + * type "invalid-priority" so a typo can't silently disable a priority config: a key + * parseInt can't read (e.g. "high") is ignored, and a key it truncates (e.g. "2.5") keeps + * its historical meaning - the truncated priority - but is reported. + * @param {string} key - The object key as written in the config + * @param {string} option - The option the key came from ("priorities" or "delays") + * @returns {number|null} The numeric priority, or null when the key is ignored + * @private + * @internal + */ + _resolvePriorityKey(key, option) { + if (/^-?\d+$/.test(key)) { + return Number(key); + } + + const parsed = parseInt(key); + const priority = isNaN(parsed) ? null : parsed; + const message = + priority === null + ? `Priority key '${key}' in '${option}' is not an integer and was ignored. Priority keys must be integers.` + : `Priority key '${key}' in '${option}' is not an integer; it was applied to priority ${priority}. Priority keys must be integers.`; + setImmediate(() => + this.emit("warning", { + type: "invalid-priority", + message, + option, + key, + priority + }) + ); + return priority; + } + /** * Synchronous initialization for backwards compatibility * @private @@ -304,8 +339,9 @@ export class HoldMyTask extends EventEmitter { // First, transform any existing priority configurations to use new property names if (cleanOptions.priorities && typeof cleanOptions.priorities === "object") { for (const [priority, config] of Object.entries(cleanOptions.priorities)) { - const priorityNum = parseInt(priority); - if (!isNaN(priorityNum) && config != null) { + if (config == null) continue; + const priorityNum = this._resolvePriorityKey(priority, "priorities"); + if (priorityNum !== null) { transformedPriorities[priorityNum] = HoldMyTask._transformDelayProperties(config, this); } } @@ -325,8 +361,9 @@ export class HoldMyTask extends EventEmitter { ); for (const [priority, delay] of Object.entries(cleanOptions.delays)) { - const priorityNum = parseInt(priority); - if (!isNaN(priorityNum) && delay != null) { + if (delay == null) continue; + const priorityNum = this._resolvePriorityKey(priority, "delays"); + if (priorityNum !== null) { transformedPriorities[priorityNum] = { postDelay: delay, // Use new property name startDelay: 0, // Use new property name diff --git a/tests/PriorityKeyValidation.test.vitest.mjs b/tests/PriorityKeyValidation.test.vitest.mjs new file mode 100644 index 0000000..d54aa21 --- /dev/null +++ b/tests/PriorityKeyValidation.test.vitest.mjs @@ -0,0 +1,73 @@ +/** + * + * @Project: @cldmv/holdmytask + * @Filename: /tests/PriorityKeyValidation.test.vitest.mjs + * @Date: 2026-10-03T17:24:45-07:00 (1791073485) + * @Author: Nate Corcoran + * @Email: + * ----- + * @Last modified by: Nate Corcoran (Shinrai@users.noreply.github.com) + * @Last modified time: 2026-10-03T17:25:44-07:00 (1791073544) + * ----- + * @Copyright: Copyright (c) 2013-2026 Catalyzed Motivation Inc. All rights reserved. + * + */ + +import { test, expect, describe } from "vitest"; +import { HoldMyTask } from "../src/hold-my-task.mjs"; + +// #54: priority keys that aren't integers used to be dropped (or truncated) silently. +const collectWarnings = async (options) => { + const queue = new HoldMyTask({ autoStart: false, ...options }); + const warnings = []; + queue.on("warning", (w) => warnings.push(w)); + await new Promise((resolve) => setImmediate(resolve)); + return { queue, warnings: warnings.filter((w) => w.type === "invalid-priority") }; +}; + +describe("priority key validation", () => { + test("a non-numeric priorities key emits an invalid-priority warning and is ignored", async () => { + const { queue, warnings } = await collectWarnings({ priorities: { high: { postDelay: 100 }, 1: { postDelay: 50 } } }); + expect(warnings).toEqual([ + { + type: "invalid-priority", + message: expect.stringContaining("'high'"), + option: "priorities", + key: "high", + priority: null + } + ]); + expect(queue.getPriorityConfigurations()).toEqual({ 1: expect.objectContaining({ postDelay: 50 }) }); + queue.destroy(); + }); + + test("a non-integer key that parseInt truncates warns and keeps the truncated priority", async () => { + const { queue, warnings } = await collectWarnings({ priorities: { "2.5": { postDelay: 25 } } }); + expect(warnings).toEqual([expect.objectContaining({ type: "invalid-priority", option: "priorities", key: "2.5", priority: 2 })]); + expect(warnings[0].message).toContain("priority 2"); + expect(queue.getPriorityConfig(2).postDelay).toBe(25); + queue.destroy(); + }); + + test("the deprecated delays option is validated the same way", async () => { + const { queue, warnings } = await collectWarnings({ delays: { low: 10, 3: 30 } }); + expect(warnings).toEqual([expect.objectContaining({ type: "invalid-priority", option: "delays", key: "low", priority: null })]); + expect(queue.getPriorityConfig(3).postDelay).toBe(30); + queue.destroy(); + }); + + test("integer keys, including negative ones, produce no invalid-priority warning", async () => { + const { queue, warnings } = await collectWarnings({ + priorities: { 0: { postDelay: 1 }, 10: { postDelay: 2 }, "-1": { postDelay: 3 } } + }); + expect(warnings).toEqual([]); + expect(queue.getPriorityConfig(-1).postDelay).toBe(3); + queue.destroy(); + }); + + test("a key with a null config is skipped without a warning", async () => { + const { queue, warnings } = await collectWarnings({ priorities: { 1: null } }); + expect(warnings).toEqual([]); + queue.destroy(); + }); +}); diff --git a/types/src/hold-my-task.d.mts.map b/types/src/hold-my-task.d.mts.map index 86dfc37..53ead91 100644 --- a/types/src/hold-my-task.d.mts.map +++ b/types/src/hold-my-task.d.mts.map @@ -1 +1 @@ -{"version":3,"file":"hold-my-task.d.mts","sourceRoot":"","sources":["../../src/hold-my-task.mjs"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"hold-my-task.d.mts","sourceRoot":"","sources":["../../src/hold-my-task.mjs"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAEtC;;;;;GAKG;AACH,qBAAa,UAAW,SAAQ,YAAY;IAqOrC,SAAS;IAuIT,OAAO;;;;;;;;;;;;;;;;;YAYV,QAAQ;YACR,IAAI;;;;;;;IAaD,WAAW;IACX,SAAS;IAMT,OAAO;IACP,iBAAiB;IACjB,KAAK;IACL,MAAM;IACN,UAAU;IACV,QAAQ;IACR,SAAS;IACT,qBAAqB;IACrB,iBAAiB;IAGjB,gBAAgB;IAChB,eAAe;IACf,gBAAgB;IAGhB,UAAU;IAGV,gBAAgB;IAChB,yBAAyB;IACzB,WAAW;IAGX,kBAAkB;IAixCjB,SAAS;IAzgDhB,YAAY,OAAO,KAAK,EAavB;IAqCD;;;;OAIG;YACH,eAAe;IAKf;;;;;OAKG;YACG,gBAAgB;IAYtB;;;;OAIG;YACH,iBAAiB;IA4KjB;;;;;;;;;OASG;mBACU,OAAO;IAKpB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA8CG;IACH,OAAO,CAAC,IAAI,UAAA,EAAE,iBAAiB,AA5C5B,CACA,EADQ,WAAS,MA4CW,EAAE,OAAO,AA3CrC,CAkBA,EAjBA;QAAgC,EAAE,AAAlC,CACA,EADQ,MAAM,GAAC,MAAM,CACrB;QAAyB,QAAQ,AAAjC,CACA,EADQ,MAAM,CACd;QAAyB,SAAS,AAAlC,CACA,EADQ,MAAM,CACd;QAAyB,UAAU,AAAnC,CACA,EADQ,MAAM,CACd;QAAyB,KAAK,AAA9B,CACA,EADQ,MAAM,CACd;QAA8B,MAAM,AAApC,CACA,EADQ,WAAW,CACnB;QAAyB,OAAO,AAAhC,CACA,EADQ,MAAM,CACd;QAAyB,MAAM,AAA/B,CACA,EADQ,MAAM,CACd;QAAyB,SAAS,AAAlC,CACA,EADQ,MAAM,CACd;QAAyB,KAAK,AAA9B,CACA,EADQ,MAAM,CACd;QAA0B,WAAW,AAArC,CACA,EADQ,OAAO,CACf;QAAyB,aAAa,AAAtC,CACA,EADQ,MAAM,CACd;QAAyB,wBAAwB,AAAjD,CACA,EADQ,MAAM,CACd;QAAyB,kBAAkB,AAA3C,CACA,EADQ,MAAM,CACd;QAA0B,2BAA2B,AAArD,CACA,EADQ,OAAO,CACf;QAA0B,4BAA4B,AAAtD,CACA,EADQ,OAAO,CACf;QAAoB,QAAQ,AAA5B,CACA,EADQ,GAAC,CACT;KAyB0C,GAzBhC,eAAQ,MAAM,CAmN1B;IAmnBD;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAE,EAPE,MAOF,EAAE,MAAM,AANd,CACA,EADQ,MAMM,GALJ,OAAO,CAoBnB;IAED;;;;;OAKG;IACH,UAAU,CAAC,EAAE,EAJF,MAAM,GAAC,MAIL,EAAE,MAAM,AAHlB,CACA,EADQ,MAGU,GAFR,OAAO,CAInB;IAED;;;;;;;OAOG;IACH,KAAK,IALQ,IAAI,CAYhB;IAED;;;;;;OAMG;IACH,MAAM,IALO,IAAI,CA2BhB;IAED;;;;;;;OAOG;IACH,KAAK,IALQ,IAAI,CAuDhB;IAED;;;;;OAKG;IACH,IAAI,IAJS,MAAM,CAMlB;IAED;;;;;OAKG;IACH,MAAM,IAJO,MAAM,CAMlB;IAED;;;;;OAKG;IACH,QAAQ,IAJK,MAAM,CAMlB;IAED;;;;;;;;;;;;;;;;;OAiBG;IACH,kBAAkB,CAAC,aAAa,EAhBrB,MAgBqB,EAAE,OAAO,AAftC,CACA,EADQ,MAeqC,GAdnC,MAAM,WAAO,IAAI,CA8D7B;IAED;;;;;;;;;;;;;;;;;OAiBG;IACH,0BAA0B,CAAC,aAAa,EAhB7B,MAgB6B,EAAE,OAAO,AAf9C,CACA,EADQ,MAe6C,SAoBvD;IAED;;;;;;;;;;OAUG;IACH,0BAA0B,IATb,MAAM,CAqBlB;IAED;;;;;;;;;;;OAWG;IACH,2BAA2B,CAAC,MAAM,EAVvB,MAAM,GAAC,MAUgB,GATrB,MAAM,GAAC,IAAI,CAqCvB;IAED;;;;;;;OAOG;IACH,OAAO,IALM,IAAI,CAUhB;IAED;;;;;OAKG;IACH,GAAG,IAJU,MAAM,CAMlB;IA+CD;;;;OAIG;YACH,aAAa;IA8Ib;;;;;OAKG;YACH,aAAa;IAwOb;;;;OAIG;YACH,WAAW;IAWX;;;;;;;;;;;;OAYG;YACH,oBAAoB;IAqDpB;;;;;;;;OAQG;YACH,YAAY;IAUZ;;;;;;;;OAQG;YACH,oBAAoB;IAqBpB;;;;;;;OAOG;YACH,oBAAoB;IAWpB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA8BG;IACH,sBAAsB,CAAC,aAAa,EA7BzB,MA6ByB,EAAE,MAAM,EA3BzC;QAAwB,cAAc,AAAtC,CACA,EADQ,MAAM,CACd;QAAwB,QAAQ,AAAhC,CACA,EADQ,MAAM,CACd;QAAwB,SAAS,AAAjC,CACA,EADQ,MAAM,CACd;QAAwB,UAAU,AAAlC,CACA,EADQ,MAAM,CACd;QAAwB,KAAK,AAA7B,CACA,EADQ,MAAM,CACd;QAAwB,KAAK,AAA7B,CACA,EADQ,MAAM,CACd;QAAyB,iBAAiB,AAA1C,CACA,EADQ,OAAO,CACf;QAAyB,kBAAkB,AAA3C,CACA,EADQ,OAAO,CACf;KAmByC,GAnB/B,IAAI,CAyDhB;IAED;;;;;;;;;;;;;;;;OAgBG;IACH,mBAAmB,CAAC,aAAa,EAftB,MAesB,EAAE,WAAW,AAd3C,CACA,EADQ,MAcwC,GAbtC,MAAM,CAkClB;IAED;;;;;;;;;;OAUG;IACH,2BAA2B,IATd,MAAM,CAelB;IAED;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,iBAAiB,CAAC,QAAQ,EApBf,MAoBe,EAAE,MAAM,EAlB/B;QAAwB,SAAS,AAAjC,CACA,EADQ,MAAM,CACd;QAAwB,UAAU,AAAlC,CACA,EADQ,MAAM,CACd;QAAwB,KAAK,AAA7B,CACA,EADQ,MAAM,CACd;QAAwB,KAAK,AAA7B,CACA,EADQ,MAAM,CACd;KAc+B,GAdrB,IAAI,CA2ChB;IAED;;;;;;;;;;;;;;;;OAgBG;IACH,iBAAiB,CAAC,QAAQ,EAff,MAee,EAAE,WAAW,AAdpC,CACA,EADQ,MAciC,GAb/B,MAAM,CA2BlB;IAED;;;;;;;;;;OAUG;IACH,yBAAyB,IATZ,MAAM,CAelB;IAED;;;OAGG;IACH,QAAQ,IAFK,IAAI,CAIhB;IAED;;;;;;OAMG;IACH,QAAQ,CAAC,IAAI,UAAA,EAAE,iBAAiB,EAJrB,WAAS,MAIY,EAAE,OAAO,GAH9B,MAGmC,GAFjC,eAAQ,UAAU,CAI9B;IAED;;;;;;OAMG;IACH,GAAG,CAAC,IAAI,UAAA,EAAE,iBAAiB,EAJhB,WAAS,MAIO,EAAE,OAAO,GAHzB,MAG8B,GAF5B,eAAQ,UAAU,CAI9B;IAED;;;;OAIG;IACH,GAAG,CAAC,EAAE,EAHK,MAAM,GAAC,MAGZ,GAFO,MAAM,GAAC,IAAI,CAIvB;IAED;;;;OAIG;IACH,GAAG,CAAC,EAAE,EAHK,MAAM,GAAC,MAGZ,GAFO,OAAO,CAInB;IAED;;;;OAIG;IACH,OAAO,CAAC,EAAE,EAHC,MAAM,GAAC,MAGR,GAFG,MAAM,GAAC,IAAI,CAIvB;IAED;;;;OAIG;IACH,OAAO,CAAC,EAAE,EAHC,MAAM,GAAC,MAGR,GAFG,OAAO,CAInB;IAMD;;;OAGG;IACH,OAAO,IAFM,MAAM,CAiIlB;IAED;;;OAGG;IACH,aAAa,IAFA,MAAM,CAqBlB;IAED;;;OAGG;IACH,YAAY,IAFC,MAAM,CAiGlB;IAED;;;OAGG;IACH,gBAAgB,IAFH,MAAM,CA2GlB;IAED;;;OAGG;IACH,QAAQ,CAAC,QAAQ,AAFd,CACF,EADU,OAEc,QAqExB;CACD"} \ No newline at end of file From 6ae9caf50f385c65405c6bab24f6b3f67643de31 Mon Sep 17 00:00:00 2001 From: Shinrai Date: Sat, 3 Oct 2026 17:26:49 -0700 Subject: [PATCH 06/16] test: regression coverage for postDelay after awaiting the previous task Deterministic fake-timer tests (promise API after await, promise API in the resolving microtask, callback API enqueuing from the completion callback) in both scheduling modes. All pass on the current code; #52 could not be reproduced. Closes #52 --- tests/PostDelayAfterAwait.test.vitest.mjs | 101 ++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 tests/PostDelayAfterAwait.test.vitest.mjs diff --git a/tests/PostDelayAfterAwait.test.vitest.mjs b/tests/PostDelayAfterAwait.test.vitest.mjs new file mode 100644 index 0000000..7bc858d --- /dev/null +++ b/tests/PostDelayAfterAwait.test.vitest.mjs @@ -0,0 +1,101 @@ +/** + * + * @Project: @cldmv/holdmytask + * @Filename: /tests/PostDelayAfterAwait.test.vitest.mjs + * @Date: 2026-10-03T17:26:43-07:00 (1791073603) + * @Author: Nate Corcoran + * @Email: + * ----- + * @Last modified by: Nate Corcoran (Shinrai@users.noreply.github.com) + * @Last modified time: 2026-10-03T17:26:45-07:00 (1791073605) + * ----- + * @Copyright: Copyright (c) 2013-2026 Catalyzed Motivation Inc. All rights reserved. + * + */ + +import { test, expect, describe, vi, beforeEach, afterEach } from "vitest"; +import { HoldMyTask } from "../src/hold-my-task.mjs"; + +// Regression coverage for #52: a task enqueued immediately after awaiting the previous +// task (same priority) must still wait out the priority postDelay. +describe.each([ + { smartScheduling: true, mode: "Smart Scheduling" }, + { smartScheduling: false, mode: "Traditional Polling" } +])("priority postDelay after await with $mode", ({ smartScheduling }) => { + beforeEach(() => { + vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout", "setInterval", "clearInterval", "setImmediate", "clearImmediate", "Date"] }); + }); + afterEach(() => { + vi.useRealTimers(); + }); + + test("promise API: enqueue right after await honours postDelay", async () => { + const q = new HoldMyTask({ concurrency: 1, smartScheduling, priorities: { 1: { postDelay: 300 } } }); + try { + const first = q.enqueue(() => "a", { priority: 1 }); + await vi.advanceTimersByTimeAsync(50); + await first; + const finishedAt = first.finishedAt; + + // 50ms of the 300ms postDelay has already elapsed; the next 200ms stay inside it. + const second = q.enqueue(() => "b", { priority: 1 }); + await vi.advanceTimersByTimeAsync(200); + expect(second.status()).not.toBe("completed"); + + await vi.advanceTimersByTimeAsync(200); + await second; + expect(second.startedAt - finishedAt).toBeGreaterThanOrEqual(300); + } finally { + q.destroy(); + } + }); + + test("promise API: enqueue in the same microtask as resolution honours postDelay", async () => { + const q = new HoldMyTask({ concurrency: 1, smartScheduling, priorities: { 1: { postDelay: 300 } } }); + try { + let second; + let finishedAt; + const first = q.enqueue(() => "a", { priority: 1 }); + first.then(() => { + finishedAt = first.finishedAt; + second = q.enqueue(() => "b", { priority: 1 }); + }); + await vi.advanceTimersByTimeAsync(50); + expect(second).toBeDefined(); + await vi.advanceTimersByTimeAsync(400); + await second; + expect(second.startedAt - finishedAt).toBeGreaterThanOrEqual(300); + } finally { + q.destroy(); + } + }); + + test("callback API: enqueue from inside the completion callback honours postDelay", async () => { + const q = new HoldMyTask({ concurrency: 1, smartScheduling, priorities: { 1: { postDelay: 300 } } }); + try { + let finishedAt; + let secondStartedAt; + q.enqueue( + () => "a", + (err) => { + expect(err).toBeNull(); + finishedAt = Date.now(); + q.enqueue( + () => { + secondStartedAt = Date.now(); + return "b"; + }, + () => {}, + { priority: 1 } + ); + }, + { priority: 1 } + ); + await vi.advanceTimersByTimeAsync(500); + expect(secondStartedAt).toBeDefined(); + expect(secondStartedAt - finishedAt).toBeGreaterThanOrEqual(300); + } finally { + q.destroy(); + } + }); +}); From a43b00e3ca980048bed024813243f2d3b39ccb30 Mon Sep 17 00:00:00 2001 From: Shinrai Date: Sat, 3 Oct 2026 16:54:21 -0700 Subject: [PATCH 07/16] docs(readme): restructure README to the CLDMV slothlet layout Reorganize the README into the standard section order: intro and tagline, reference-style badge row with definitions at the bottom (adds the coverage badge from the badges branch), What's New, Key Features, Installation with Node.js requirements (including the require(esm) floor), Quick Start, then the existing usage and API sections, Documentation, quality badges, Contributing, Links and License. Also fixes content that disagreed with the code: - the mangled "Import Options" heading emoji - the "@cldmv/holdmytask/src" import, which is not an exported subpath; replaced with the holdmytask-dev export condition - configurePriority/configureCoalescingKey and coalescing defaults now list postDelay/startDelay (delay/start are deprecated aliases; configurePriority never accepted maxDelay) - Quick Start uses priorities/coalescing instead of the deprecated delays/coalescingWindowDuration options - Testing section names the actual npm scripts (coverage, test:watch, build:ci) --- README.md | 255 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 180 insertions(+), 75 deletions(-) diff --git a/README.md b/README.md index cfc2853..96185ef 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,18 @@ # @cldmv/holdmytask -[![npm version](https://img.shields.io/npm/v/%40cldmv%2Fholdmytask.svg?style=for-the-badge&logo=npm&logoColor=white&labelColor=CB3837)](https://www.npmjs.com/package/@cldmv/holdmytask) -[![npm downloads](https://img.shields.io/npm/dm/%40cldmv%2Fholdmytask.svg?style=for-the-badge&logo=npm&logoColor=white&labelColor=CB3837)](https://www.npmjs.com/package/@cldmv/holdmytask) -[![GitHub downloads](https://img.shields.io/github/downloads/CLDMV/holdmytask/total?style=for-the-badge&logo=github&logoColor=white&labelColor=181717)](https://github.com/CLDMV/holdmytask/releases) -[![Last commit](https://img.shields.io/github/last-commit/CLDMV/holdmytask?style=for-the-badge&logo=github&logoColor=white&labelColor=181717)](https://github.com/CLDMV/holdmytask/commits) -[![npm last update](https://img.shields.io/npm/last-update/%40cldmv%2Fholdmytask?style=for-the-badge&logo=npm&logoColor=white&labelColor=CB3837)](https://www.npmjs.com/package/@cldmv/holdmytask) +**@cldmv/holdmytask** is a tiny, dependency-free task queue for Node.js that executes tasks with priority ordering, concurrency control, and completion delays. It is built for asynchronous workflows with real timing requirements: rate-limited APIs, device control, UI refreshes, and anything else where _when_ a task runs matters as much as _whether_ it runs. -[![Contributors](https://img.shields.io/github/contributors/CLDMV/holdmytask.svg?style=for-the-badge&logo=github&logoColor=white&labelColor=181717)](https://github.com/CLDMV/holdmytask/graphs/contributors) [![Sponsor shinrai](https://img.shields.io/github/sponsors/shinrai?style=for-the-badge&logo=githubsponsors&logoColor=white&labelColor=EA4AAA&label=Sponsor)](https://github.com/sponsors/shinrai) +Every task can carry a priority, a scheduled start time, a timeout, an `AbortSignal`, and metadata. Per-priority concurrency limits and pre/post delays shape throughput, urgent work can bypass an active delay, and tasks that share a coalescing key collapse into a single execution whose result resolves every caller. Smart scheduling sets precise timers for the next ready task instead of polling. -A tiny, dependency-free task queue for Node.js that executes tasks with priority ordering, concurrency control, and completion delays. Perfect for managing asynchronous workflows with sophisticated timing requirements. +Use it with callbacks or promises, from ESM or CommonJS, with full TypeScript definitions included. + +> _A tiny task queue that waits until your task is ready._ + +[![npm version]][npm_version_url] [![npm downloads]][npm_downloads_url] [![GitHub downloads]][github_downloads_url] [![Last commit]][last_commit_url] [![npm last update]][npm_last_update_url] [![coverage]][coverage_url] + +[![Contributors]][contributors_url] [![Sponsor shinrai]][sponsor_url] + +--- ## โœจ What's New @@ -25,7 +29,11 @@ A tiny, dependency-free task queue for Node.js that executes tasks with priority - **v2.0.1** (September 2026) โ€” Fix: a task's timeout timer is now cleared when the task fails or is cancelled ([Changelog](https://github.com/CLDMV/holdmytask/blob/master/docs/changelog/v2/v2.0.1.md)) - **v2.0.0** (August 2026) โ€” `HoldMyTask` and its aliases are the real class again (breaking for code adapted to v1.6.1's factories); scheduler deadlock fix; `holdmytask-dev` export condition ([Changelog](https://github.com/CLDMV/holdmytask/blob/master/docs/changelog/v2/v2.0.0.md)) -## โœจ Features +๐Ÿ“š **For complete version history and detailed release notes, see the [docs/changelog/](https://github.com/CLDMV/holdmytask/tree/master/docs/changelog/) folder.** + +--- + +## ๐Ÿš€ Key Features - **Smart scheduling** - Dynamic timeout-based scheduling for optimal performance - **Task coalescing** - Intelligent merging of similar tasks for efficiency @@ -40,13 +48,72 @@ A tiny, dependency-free task queue for Node.js that executes tasks with priority - **TypeScript ready** - Full type definitions included - **Zero dependencies** - Lightweight and fast +--- + ## ๐Ÿ“ฆ Installation +### Requirements + +- **Node.js v18.12.0 or higher** for ESM (`import`), as declared in the package's `engines` field +- **CommonJS `require()`** needs Node.js `^20.19.0` or `>=22.12.0`. The CommonJS entry is a thin wrapper that loads the ESM build through Node's synchronous `require(esm)`. On older Node.js versions `require("@cldmv/holdmytask")` throws an `ERR_REQUIRE_ESM` error that names the supported versions; load the package with `import()` instead. + +### Install + ```bash npm install @cldmv/holdmytask ``` -## ๏ฟฝ Import Options +--- + +## ๐Ÿš€ Quick Start + +```javascript +import { HoldMyTask } from "@cldmv/holdmytask"; + +const queue = new HoldMyTask({ + concurrency: 2, + priorities: { + 1: { postDelay: 100 }, // 100ms delay after priority 1 tasks + 2: { postDelay: 200 } // 200ms delay after priority 2 tasks + }, + coalescing: { + defaults: { + windowDuration: 200, // Group similar tasks within 200ms + maxDelay: 1000 // Force execution after 1000ms max + } + } +}); + +// Enqueue a task +queue.enqueue( + async (signal) => { + // Your task logic here + return "task result"; + }, + (error, result) => { + if (error) { + console.error("Task failed:", error); + } else { + console.log("Task completed:", result); + } + }, + { priority: 1, timeout: 5000 } +); + +// Coalescing example - multiple similar tasks become one +const results = await Promise.all([ + queue.enqueue(async () => updateUI(), { coalescingKey: "ui.update" }), + queue.enqueue(async () => updateUI(), { coalescingKey: "ui.update" }), + queue.enqueue(async () => updateUI(), { coalescingKey: "ui.update" }) +]); +// Only one updateUI() call executes, all three promises resolve with the same result +``` + +CommonJS works the same way: `const { HoldMyTask } = require("@cldmv/holdmytask")` (see [Requirements](#requirements) for the Node.js versions that support `require()`). + +--- + +## ๐Ÿ“ฅ Import Options The library provides flexible import options for different use cases: @@ -62,13 +129,10 @@ import HoldMyTask from "@cldmv/holdmytask"; // Import everything import * as HoldMyTaskLib from "@cldmv/holdmytask"; // Uses optimized distribution files -``` - -### Development Source Access -```javascript -import { HoldMyTask } from "@cldmv/holdmytask/main"; -// Conditional: uses source files in development, dist files in production +// CommonJS (Node.js ^20.19.0 or >=22.12.0) +const HoldMyTask = require("@cldmv/holdmytask"); // the class itself +const { HoldMyTask: Named } = require("@cldmv/holdmytask"); // or by name ``` ### Common Queue System Aliases @@ -89,54 +153,27 @@ import { // All aliases are functionally identical const myQueue = new queue({ concurrency: 5 }); const stdQueue = new Queue({ concurrency: 5 }); -const manager = new TaskManager({ priorities: { high: { delay: 100 } } }); +const manager = new TaskManager({ priorities: { high: { postDelay: 100 } } }); ``` -### Direct Source Import (Development Only) +The ESM entry also exports async factory helpers that resolve to a new instance: `createHoldMyTask(options)`, `createQueue(options)`, `createTaskManager(options)`, and `createTaskProcessor(options)`. + +### Development Source Access ```javascript -import { HoldMyTask } from "@cldmv/holdmytask/src"; -// Always uses source files - bypasses devcheck +import { HoldMyTask } from "@cldmv/holdmytask/main"; +// Resolves to dist files by default; resolves to src/ when Node.js runs with the holdmytask-dev condition ``` -**Note:** The main import automatically runs environment checks in development mode to ensure proper configuration. +To run against the unbuilt source (from a checkout of this repository), start Node.js with the `holdmytask-dev` export condition: -## ๐Ÿš€ Quick Start - -```javascript -import { HoldMyTask } from "@cldmv/holdmytask"; +```bash +node --conditions=holdmytask-dev your-script.mjs +``` -const queue = new HoldMyTask({ - concurrency: 2, - delays: { 1: 100, 2: 200 }, // 100ms delay after priority 1 tasks, 200ms after priority 2 - coalescingWindowDuration: 200, // Group similar tasks within 200ms - coalescingMaxDelay: 1000 // Force execution after 1000ms max -}); +**Note:** In a checkout of this repository, the root import runs a best-effort development environment check (`devcheck.mjs`) that exits with setup instructions when `src/` is present but the `holdmytask-dev` condition is not set. The check is skipped when the package is installed as a dependency and in CI. -// Enqueue a task -queue.enqueue( - async (signal) => { - // Your task logic here - return "task result"; - }, - (error, result) => { - if (error) { - console.error("Task failed:", error); - } else { - console.log("Task completed:", result); - } - }, - { priority: 1, timeout: 5000 } -); - -// Coalescing example - multiple similar tasks become one -const results = await Promise.all([ - queue.enqueue(async () => updateUI(), { coalescingKey: "ui.update" }), - queue.enqueue(async () => updateUI(), { coalescingKey: "ui.update" }), - queue.enqueue(async () => updateUI(), { coalescingKey: "ui.update" }) -]); -// Only one updateUI() call executes, all three promises resolve with the same result -``` +--- ## ๐Ÿ”„ Promise API @@ -180,6 +217,8 @@ queue.enqueue(task1, callback, options); const result = await queue.enqueue(task2, options); ``` +--- + ## ๐Ÿ—๏ธ Async Constructor Pattern HoldMyTask works excellently with async constructor patterns for classes that need initialization: @@ -241,6 +280,8 @@ This pattern is particularly useful for: - **Resource acquisition**: Set up required resources in a controlled manner - **Dependency injection**: Initialize dependencies in the correct order +--- + ## ๐Ÿ“š API Reference ### Constructor @@ -288,11 +329,11 @@ This pattern is particularly useful when you need to handle initialization error - `defaults` (object) - Default settings for all coalescing keys - `windowDuration` (number, default: 200) - Window duration in milliseconds - `maxDelay` (number, default: 1000) - Maximum delay before forcing execution - - `delay` (number) - Default completion delay for coalescing tasks - - `start` (number) - Default start delay for coalescing tasks + - `postDelay` (number) - Default completion delay for coalescing tasks (`delay` is the deprecated name) + - `startDelay` (number) - Default start delay for coalescing tasks (`start` is the deprecated name) - `resolveAllPromises` (boolean, default: true) - Whether all promises resolve with result - `multipleCallbacks` (boolean, default: false) - Whether to call multiple callbacks - - `keys` (object) - Per-key configuration overrides: `{ [key]: { windowDuration, maxDelay, delay, start, ... } }` + - `keys` (object) - Per-key configuration overrides: `{ [key]: { windowDuration, maxDelay, postDelay, startDelay, ... } }` - `coalescingWindowDuration` (number, default: 200) - **DEPRECATED:** Use `coalescing.defaults.windowDuration` - `coalescingMaxDelay` (number, default: 1000) - **DEPRECATED:** Use `coalescing.defaults.maxDelay` - `coalescingResolveAllPromises` (boolean, default: true) - **DEPRECATED:** Use `coalescing.defaults.resolveAllPromises` @@ -439,10 +480,10 @@ queue.enqueue(task2, { id: "duplicate" }); // throws: Task ID "duplicate" alread - `configurePriority(priority, config)` - Configure or update priority-specific settings - `priority` (string|number) - Priority level to configure - - `config` (object) - Configuration: `{ delay?, maxDelay?, start? }` + - `config` (object) - Configuration: `{ postDelay?, startDelay? }` (the deprecated `delay` / `start` names are still accepted) - `configureCoalescingKey(key, config)` - Configure or update coalescing key settings - `key` (string) - Coalescing key to configure - - `config` (object) - Configuration: `{ windowDuration?, maxDelay?, delay?, start?, multipleCallbacks?, resolveAllPromises? }` + - `config` (object) - Configuration: `{ windowDuration?, maxDelay?, postDelay?, startDelay?, multipleCallbacks?, resolveAllPromises? }` (the deprecated `delay` / `start` names are still accepted) - `getPriorityConfig(priority, taskOptions?)` - Get effective configuration for a specific priority - `getPriorityConfigurations()` - Get all configured priorities and their settings - `getCoalescingConfig(coalescingKey, taskOptions?)` - Get effective configuration for a specific coalescing key @@ -639,6 +680,8 @@ queue.on("warning", (warning) => { // In: constructor options ``` +--- + ## โš™๏ธ Concurrency & Delays Interaction Understanding how concurrency and delays work together is crucial for optimal queue behavior: @@ -709,10 +752,6 @@ HoldMyTask uses a sophisticated dual-heap scheduling system for optimal performa This architecture enables handling thousands of tasks with precise timing control while maintaining excellent performance. -[![CodeFactor](https://img.shields.io/codefactor/grade/github/CLDMV/holdmytask?style=for-the-badge&logo=codefactor&logoColor=white&labelColor=F44A6A)](https://www.codefactor.io/repository/github/cldmv/holdmytask) [![npms.io score](https://img.shields.io/npms-io/final-score/%40cldmv%2Fholdmytask?style=for-the-badge&logo=npms&logoColor=white&labelColor=0B5D57)](https://npms.io/search?q=%40cldmv%2Fholdmytask) - -[![npm unpacked size](https://img.shields.io/npm/unpacked-size/%40cldmv%2Fholdmytask.svg?style=for-the-badge&logo=npm&logoColor=white&labelColor=CB3837)](https://www.npmjs.com/package/@cldmv/holdmytask) [![Repo size](https://img.shields.io/github/repo-size/CLDMV/holdmytask?style=for-the-badge&logo=github&logoColor=white&labelColor=181717)](https://github.com/CLDMV/holdmytask) - ### Unlimited Queue Capacity For scenarios requiring unlimited task queuing, you can set `maxQueue` to `-1`: @@ -740,6 +779,8 @@ for (let i = 0; i < 100000; i++) { While `-1` allows unlimited queuing, be mindful of memory usage with very large task sets. Each queued task consumes memory until executed. +--- + ## ๐ŸŽฏ Advanced Features ### Priority System @@ -1059,6 +1100,8 @@ queue.enqueue(taskD, callback, { priority: 1 }); // Waits for taskC's completion - โœ… **Concurrency aware** - works correctly with multiple concurrent execution slots - โš ๏ธ **Use sparingly** - frequent bypassing defeats the purpose of delay-based rate limiting +--- + ## ๐Ÿ”„ Task Coalescing System The coalescing system allows multiple similar tasks to be intelligently merged, reducing redundant operations while ensuring all promises resolve with accurate results. This is perfect for scenarios like UI updates, API calls, or device commands where only the final result matters. @@ -1476,7 +1519,9 @@ function processVolumeCommands() { // - Total time: ~25-30 seconds (vs 50+ seconds without coalescing) ``` -### Timeouts +--- + +## โฑ๏ธ Timeouts Tasks automatically timeout and either call the callback with an error or reject the promise: @@ -1518,6 +1563,8 @@ try { } ``` +--- + ## ๐Ÿ›‘ AbortController Support The library uses `AbortController` for cooperative task cancellation. This allows tasks to be cancelled gracefully without forcing termination. @@ -1679,6 +1726,8 @@ try { - **Cooperative**: Tasks must actively check and respond to the abort signal - **Async operations**: Only cancellable if the underlying operation supports AbortSignal +--- + ## ๐Ÿ“ Examples ### Basic Usage @@ -1813,23 +1862,26 @@ setInterval(() => { }, 1000); ``` +--- + ## ๐Ÿงช Testing & Development ```bash -# Run tests +# Run tests (vitest suite + CommonJS entry tests) npm test # Run tests with coverage -npm run test:coverage +npm run coverage # Run tests in watch mode -npm run test:run +npm run test:watch -# Lint code +# Lint and format check npm run lint +npm run format:check -# Build for publishing -npm run build +# Build types, type-check, and build for publishing +npm run build:ci ``` ### Testing with Injectable Clock @@ -1853,21 +1905,74 @@ console.log(Date.now()); // Returns actual system time queue.enqueue(task, { timestamp: queue.now() + 5000 }); // 5 seconds from mock time ``` -## ๐Ÿ“„ License +--- + +## ๐Ÿ“š Documentation + +- **[Changelog](https://github.com/CLDMV/holdmytask/tree/master/docs/changelog/)** โ€” release notes for every version (v1 + v2) +- **[Examples](https://github.com/CLDMV/holdmytask/tree/master/examples/)** โ€” runnable scripts exercising priorities, coalescing, and stress scenarios +- **[Type definitions](https://github.com/CLDMV/holdmytask/tree/master/types/)** โ€” the TypeScript declarations shipped with the package -[![GitHub license](https://img.shields.io/github/license/CLDMV/holdmytask.svg?style=for-the-badge&logo=github&logoColor=white&labelColor=181717)](https://github.com/CLDMV/holdmytask/blob/HEAD/LICENSE) [![npm license](https://img.shields.io/npm/l/%40cldmv%2Fholdmytask.svg?style=for-the-badge&logo=npm&logoColor=white&labelColor=CB3837)](https://www.npmjs.com/package/@cldmv/holdmytask) +[![CodeFactor]][codefactor_url] [![OpenSSF Scorecard]][ossf_scorecard_url] [![npms.io score]][npms_url] [![npm unpacked size]][npm_size_url] [![Repo size]][repo_size_url] -Apache License 2.0 - see [LICENSE](LICENSE) file for details. +--- ## ๐Ÿค Contributing -Contributions welcome! Please ensure: +Contributions are welcome! Please ensure: - All tests pass - Code follows existing style - New features include tests - Documentation is updated +[![Contributors]][contributors_url] [![Sponsor shinrai]][sponsor_url] + +--- + +## ๐Ÿ”— Links + +- **npm**: [@cldmv/holdmytask](https://www.npmjs.com/package/@cldmv/holdmytask) +- **GitHub**: [CLDMV/holdmytask](https://github.com/CLDMV/holdmytask) +- **Issues**: [GitHub Issues](https://github.com/CLDMV/holdmytask/issues) +- **Changelog**: [docs/changelog/](https://github.com/CLDMV/holdmytask/tree/master/docs/changelog/) +- **Releases**: [GitHub Releases](https://github.com/CLDMV/holdmytask/releases) + --- -**Made with โค๏ธ for robust task management** +## ๐Ÿ“„ License + +[![GitHub license]][github_license_url] [![npm license]][npm_license_url] + +Apache-2.0 ยฉ Shinrai / CLDMV โ€” see [LICENSE](https://github.com/CLDMV/holdmytask/blob/HEAD/LICENSE) for details. + +[npm version]: https://img.shields.io/npm/v/%40cldmv%2Fholdmytask.svg?style=for-the-badge&logo=npm&logoColor=white&labelColor=CB3837 +[npm_version_url]: https://www.npmjs.com/package/@cldmv/holdmytask +[last commit]: https://img.shields.io/github/last-commit/CLDMV/holdmytask?style=for-the-badge&logo=github&logoColor=white&labelColor=181717 +[last_commit_url]: https://github.com/CLDMV/holdmytask/commits +[npm last update]: https://img.shields.io/npm/last-update/%40cldmv%2Fholdmytask?style=for-the-badge&logo=npm&logoColor=white&labelColor=CB3837 +[npm_last_update_url]: https://www.npmjs.com/package/@cldmv/holdmytask +[codefactor]: https://img.shields.io/codefactor/grade/github/CLDMV/holdmytask?style=for-the-badge&logo=codefactor&logoColor=white&labelColor=F44A6A +[codefactor_url]: https://www.codefactor.io/repository/github/cldmv/holdmytask +[openssf scorecard]: https://img.shields.io/ossf-scorecard/github.com/CLDMV/holdmytask?style=for-the-badge&label=OpenSSF%20Scorecard +[ossf_scorecard_url]: https://scorecard.dev/viewer/?uri=github.com/CLDMV/holdmytask +[npms.io score]: https://img.shields.io/npms-io/final-score/%40cldmv%2Fholdmytask?style=for-the-badge&logo=npms&logoColor=white&labelColor=0B5D57 +[npms_url]: https://npms.io/search?q=%40cldmv%2Fholdmytask +[npm downloads]: https://img.shields.io/npm/dm/%40cldmv%2Fholdmytask.svg?style=for-the-badge&logo=npm&logoColor=white&labelColor=CB3837 +[npm_downloads_url]: https://www.npmjs.com/package/@cldmv/holdmytask +[github downloads]: https://img.shields.io/github/downloads/CLDMV/holdmytask/total?style=for-the-badge&logo=github&logoColor=white&labelColor=181717 +[github_downloads_url]: https://github.com/CLDMV/holdmytask/releases +[npm unpacked size]: https://img.shields.io/npm/unpacked-size/%40cldmv%2Fholdmytask.svg?style=for-the-badge&logo=npm&logoColor=white&labelColor=CB3837 +[npm_size_url]: https://www.npmjs.com/package/@cldmv/holdmytask +[repo size]: https://img.shields.io/github/repo-size/CLDMV/holdmytask?style=for-the-badge&logo=github&logoColor=white&labelColor=181717 +[repo_size_url]: https://github.com/CLDMV/holdmytask +[github license]: https://img.shields.io/github/license/CLDMV/holdmytask.svg?style=for-the-badge&logo=github&logoColor=white&labelColor=181717 +[github_license_url]: https://github.com/CLDMV/holdmytask/blob/HEAD/LICENSE +[npm license]: https://img.shields.io/npm/l/%40cldmv%2Fholdmytask.svg?style=for-the-badge&logo=npm&logoColor=white&labelColor=CB3837 +[npm_license_url]: https://www.npmjs.com/package/@cldmv/holdmytask +[coverage]: https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2FCLDMV%2Fholdmytask%2Fbadges%2Fcoverage.json&style=for-the-badge&logo=vitest&logoColor=white +[coverage_url]: https://github.com/CLDMV/holdmytask/blob/badges/coverage.json +[contributors]: https://img.shields.io/github/contributors/CLDMV/holdmytask.svg?style=for-the-badge&logo=github&logoColor=white&labelColor=181717 +[contributors_url]: https://github.com/CLDMV/holdmytask/graphs/contributors +[sponsor shinrai]: https://img.shields.io/github/sponsors/shinrai?style=for-the-badge&logo=githubsponsors&logoColor=white&labelColor=EA4AAA&label=Sponsor +[sponsor_url]: https://github.com/sponsors/shinrai From 480924568b70c31fbad238487bc4961fb5b13bbf Mon Sep 17 00:00:00 2001 From: Shinrai Date: Sat, 3 Oct 2026 17:02:06 -0700 Subject: [PATCH 08/16] docs(readme): use the current API in every example Rewrite all README examples and option descriptions that used deprecated options: - delays -> priorities: { [priority]: { postDelay } } - delay/start in priority, coalescing defaults/keys and the configure* methods -> postDelay/startDelay - coalescingWindowDuration/MaxDelay/ResolveAllPromises constructor options -> coalescing.defaults.* - the alias example used a non-numeric priority key, which the constructor silently drops Add a Deprecated Options table (old -> new) and make clear that the task-level delay/start and coalescing* overrides are current names. Fix content that disagreed with the code: - Deprecation Warning Events: warnings carry type/message/deprecated/ replacement (there is no `source`); output now matches the real messages and emission order - Async Initialization: sync:false returns a plain Promise with no .on(); attach listeners to the awaited instance - resolveAllPromises:false rejects the non-representative tasks with "Task was coalesced with a newer task" instead of resolving them with undefined, and the newest task is the representative - callback-style failures emit an "error" event that throws when no listener is attached; document it, add a listener to Quick Start, and describe the callback error payload shape - options list: drop onError (not implemented), add healingInterval and sync --- README.md | 239 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 151 insertions(+), 88 deletions(-) diff --git a/README.md b/README.md index 96185ef..d144831 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,9 @@ const queue = new HoldMyTask({ } }); +// Callback-style tasks also report failures as queue "error" events, so listen for them +queue.on("error", (task) => console.error(`Task ${task.id} ${task.status}:`, task.error)); + // Enqueue a task queue.enqueue( async (signal) => { @@ -153,7 +156,7 @@ import { // All aliases are functionally identical const myQueue = new queue({ concurrency: 5 }); const stdQueue = new Queue({ concurrency: 5 }); -const manager = new TaskManager({ priorities: { high: { postDelay: 100 } } }); +const manager = new TaskManager({ priorities: { 1: { postDelay: 100 } } }); ``` The ESM entry also exports async factory helpers that resolve to a new instance: `createHoldMyTask(options)`, `createQueue(options)`, `createTaskManager(options)`, and `createTaskProcessor(options)`. @@ -292,25 +295,22 @@ const queue = new HoldMyTask(options?) ### Async Initialization Pattern -For async initialization that allows event listeners to be attached before any initialization events can fire, use `sync: false`: +With `sync: false`, the constructor returns a Promise that resolves to the instance once initialization has run on the next turn of the event loop. The returned value is a plain Promise, so attach listeners to the resolved instance: ```javascript // Create instance with async initialization -const queuePromise = new HoldMyTask({ +const queue = await new HoldMyTask({ concurrency: 5, maxQueue: 100, sync: false // Enable async initialization }); -// Attach event listeners before initialization completes -queuePromise.on("error", (err) => console.error("Queue error:", err)); -queuePromise.on("warning", (warning) => console.warn("Warning:", warning.message)); - -// Wait for initialization to complete -const queue = await queuePromise; +// Initialization warnings are emitted asynchronously, so listeners attached here still receive them +queue.on("error", (err) => console.error("Queue error:", err)); +queue.on("warning", (warning) => console.warn("Warning:", warning.message)); ``` -This pattern is particularly useful when you need to handle initialization errors or warnings through event listeners rather than try/catch blocks. +Initialization warnings (such as [deprecation warnings](#deprecation-warning-events)) are emitted on `setImmediate` in both modes, so a listener attached right after a synchronous `new HoldMyTask(...)` receives them as well. **Options:** @@ -320,26 +320,25 @@ This pattern is particularly useful when you need to handle initialization error - `autoStart` (boolean, default: true) - Whether to start processing immediately - `defaultPriority` (number, default: 0) - Default task priority - `maxQueue` (number, default: Infinity) - Maximum queued tasks. Use `-1` for unlimited queue capacity (equivalent to `Infinity`) -- `delays` (object, default: {}) - **DEPRECATED:** Priority-to-delay mapping for completion delays (use `priorities` instead) - `priorities` (object, default: {}) - Priority-specific configuration: `{ [priority]: { concurrency, postDelay, startDelay } }`. Keys must be integers; any other key emits a `warning` event with `type: "invalid-priority"` (and is ignored when it isn't numeric at all) - `concurrency` (number) - Maximum concurrent tasks for this priority (defaults to global concurrency limit) - - `postDelay` (number) - Delay after task completion before next task of same priority + - `postDelay` (number) - Delay after a task of this priority completes before the next task can start (the delay applies queue-wide; see [Priority Delays](#priority-delays---advanced-timing-control)) - `startDelay` (number) - Delay before task execution (pre-execution delay) - `coalescing` (object) - Enhanced coalescing configuration - `defaults` (object) - Default settings for all coalescing keys - `windowDuration` (number, default: 200) - Window duration in milliseconds - `maxDelay` (number, default: 1000) - Maximum delay before forcing execution - - `postDelay` (number) - Default completion delay for coalescing tasks (`delay` is the deprecated name) - - `startDelay` (number) - Default start delay for coalescing tasks (`start` is the deprecated name) - - `resolveAllPromises` (boolean, default: true) - Whether all promises resolve with result + - `postDelay` (number) - Default completion delay for coalescing tasks + - `startDelay` (number) - Default start delay for coalescing tasks + - `resolveAllPromises` (boolean, default: true) - Whether every task in a coalescing group resolves with the result; when `false`, only the newest task resolves and the others are rejected with `Task was coalesced with a newer task` - `multipleCallbacks` (boolean, default: false) - Whether to call multiple callbacks - `keys` (object) - Per-key configuration overrides: `{ [key]: { windowDuration, maxDelay, postDelay, startDelay, ... } }` -- `coalescingWindowDuration` (number, default: 200) - **DEPRECATED:** Use `coalescing.defaults.windowDuration` -- `coalescingMaxDelay` (number, default: 1000) - **DEPRECATED:** Use `coalescing.defaults.maxDelay` -- `coalescingResolveAllPromises` (boolean, default: true) - **DEPRECATED:** Use `coalescing.defaults.resolveAllPromises` -- `onError` (function) - Global error handler +- `healingInterval` (number, default: 5000) - Self-healing check interval in milliseconds (smart scheduling only) +- `sync` (boolean, default: true) - Set to `false` for [async initialization](#async-initialization-pattern); the constructor then returns a Promise that resolves to the instance - `now` (function) - Injectable clock for testing +Deprecated option names are still accepted and converted for you, but each one emits a `warning` event; see [Deprecated Options](#deprecated-options) for the mapping. + ### Methods #### `enqueue(task, callback?, options?)` @@ -480,10 +479,10 @@ queue.enqueue(task2, { id: "duplicate" }); // throws: Task ID "duplicate" alread - `configurePriority(priority, config)` - Configure or update priority-specific settings - `priority` (string|number) - Priority level to configure - - `config` (object) - Configuration: `{ postDelay?, startDelay? }` (the deprecated `delay` / `start` names are still accepted) + - `config` (object) - Configuration: `{ postDelay?, startDelay? }` - `configureCoalescingKey(key, config)` - Configure or update coalescing key settings - `key` (string) - Coalescing key to configure - - `config` (object) - Configuration: `{ windowDuration?, maxDelay?, postDelay?, startDelay?, multipleCallbacks?, resolveAllPromises? }` (the deprecated `delay` / `start` names are still accepted) + - `config` (object) - Configuration: `{ windowDuration?, maxDelay?, postDelay?, startDelay?, multipleCallbacks?, resolveAllPromises? }` - `getPriorityConfig(priority, taskOptions?)` - Get effective configuration for a specific priority - `getPriorityConfigurations()` - Get all configured priorities and their settings - `getCoalescingConfig(coalescingKey, taskOptions?)` - Get effective configuration for a specific coalescing key @@ -658,28 +657,55 @@ queue.on("warning", (warning) => { }); ``` +> [!IMPORTANT] +> When a callback-style task fails, times out, or is aborted, the queue emits an `error` event with the task (its `error` and `status` properties describe the failure) before calling the task's callback. HoldMyTask is an `EventEmitter`, so if no `error` listener is attached, Node.js throws the event as an unhandled error. Attach a `queue.on("error", ...)` listener whenever you use the callback API. Promise-style tasks report task failures only through the rejected promise. + +The callback's first argument is an error payload rather than the raw error: `{ type: "timeout", message }`, `{ type: "canceled", message: "Task was aborted" }`, or `{ type: "error", error }`. + ### Deprecation Warning Events -When deprecated configuration options are used, HoldMyTask emits `warning` events to help with migration: +When deprecated configuration options are used, HoldMyTask converts them to the current names and emits one `warning` event per deprecated option. The events are emitted asynchronously (on `setImmediate`), so a listener attached right after the constructor returns still receives them. + +Each warning has this shape: + +- `type` - always `"deprecation"` +- `message` - a human-readable description +- `deprecated` - the deprecated option or property name +- `replacement` - the name to use instead ```javascript const queue = new HoldMyTask({ - delays: { 1: 100 }, // Deprecated option - coalescingWindowDuration: 200 // Deprecated option + delays: { 1: 100 }, // Deprecated: use priorities + coalescingWindowDuration: 200 // Deprecated: use coalescing.defaults.windowDuration }); queue.on("warning", (warning) => { - console.warn(`Deprecation Warning: ${warning.message}`); - console.warn(`Use: ${warning.replacement}`); - console.warn(`In: ${warning.source}`); + if (warning.type === "deprecation") { + console.warn(`${warning.message} (${warning.deprecated} -> ${warning.replacement})`); + } }); // Output: -// Deprecation Warning: Option 'delays' is deprecated -// Use: priorities: { 1: { delay: 100 } } -// In: constructor options +// Option 'coalescingWindowDuration' is deprecated. Use 'coalescing.defaults.windowDuration' instead. (coalescingWindowDuration -> coalescing.defaults.windowDuration) +// Option 'delays' is deprecated. Use 'priorities' instead with { [priority]: { postDelay: value, startDelay: 0 } } format. (delays -> priorities) ``` +### Deprecated Options + +These names still work, but each emits a deprecation warning. Use the current names in new code: + +| Deprecated | Use instead | +| ------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------- | +| `delays: { [priority]: ms }` | `priorities: { [priority]: { postDelay: ms } }` | +| `delay` in a `priorities` entry, `coalescing.defaults`, `coalescing.keys` entry, `configurePriority()` or `configureCoalescingKey()` | `postDelay` | +| `start` in the same places | `startDelay` | +| `coalescingWindowDuration` (constructor) | `coalescing.defaults.windowDuration` | +| `coalescingMaxDelay` (constructor) | `coalescing.defaults.maxDelay` | +| `coalescingMultipleCallbacks` (constructor) | `coalescing.defaults.multipleCallbacks` | +| `coalescingResolveAllPromises` (constructor) | `coalescing.defaults.resolveAllPromises` | + +Task-level options passed to `enqueue()` are not deprecated: `delay`, `start`, and the per-task `coalescingWindowDuration` / `coalescingMaxDelay` / `coalescingMultipleCallbacks` / `coalescingResolveAllPromises` overrides are the current names there. + --- ## โš™๏ธ Concurrency & Delays Interaction @@ -691,7 +717,7 @@ Understanding how concurrency and delays work together is crucial for optimal qu ```javascript const queue = new HoldMyTask({ concurrency: 3, // Up to 3 tasks can run simultaneously - delays: { 1: 500, 2: 1000 } + priorities: { 1: { postDelay: 500 }, 2: { postDelay: 1000 } } }); ``` @@ -704,7 +730,7 @@ const queue = new HoldMyTask({ ### Timing Examples ```javascript -// Timeline with concurrency: 2, delays: { 1: 1000 } +// Timeline with concurrency: 2, priorities: { 1: { postDelay: 1000 } } // 10:00:00 - Start: TaskA (pri 1), TaskB (pri 1) - both running // 10:00:02 - TaskA completes โ†’ 1000ms delay starts, TaskB still running @@ -810,50 +836,50 @@ HoldMyTask supports comprehensive priority and coalescing configuration for soph ```javascript const queue = new HoldMyTask({ concurrency: 3, - // Priority-specific defaults (replaces legacy delays) + // Priority-specific defaults priorities: { - 1: { delay: 200, start: 0 }, // High priority: 200ms delay, immediate start - 2: { delay: 100, start: 25 }, // Medium priority: 100ms delay, 25ms start delay - 3: { delay: 50, start: 50 } // Low priority: 50ms delay, 50ms start delay + 1: { postDelay: 200, startDelay: 0 }, // High priority: 200ms delay, immediate start + 2: { postDelay: 100, startDelay: 25 }, // Medium priority: 100ms delay, 25ms start delay + 3: { postDelay: 50, startDelay: 50 } // Low priority: 50ms delay, 50ms start delay }, // Enhanced coalescing with per-key settings coalescing: { defaults: { windowDuration: 200, maxDelay: 1000, - delay: 75, // Default completion delay for coalescing tasks - start: 25, // Default start delay for coalescing tasks + postDelay: 75, // Default completion delay for coalescing tasks + startDelay: 25, // Default start delay for coalescing tasks resolveAllPromises: true }, keys: { "ui.update": { windowDuration: 100, maxDelay: 500, - delay: 25, // Fast UI updates - start: 0 + postDelay: 25, // Fast UI updates + startDelay: 0 }, "api.batch": { windowDuration: 1000, maxDelay: 5000, - delay: 200, // Slower API operations - start: 100 + postDelay: 200, // Slower API operations + startDelay: 100 } } } }); // Dynamic configuration -queue.configurePriority(4, { delay: 300, start: 75 }); +queue.configurePriority(4, { postDelay: 300, startDelay: 75 }); queue.configureCoalescingKey("data.sync", { windowDuration: 800, maxDelay: 3000, - delay: 150, - start: 50 + postDelay: 150, + startDelay: 50 }); // Get configuration information const priority4Config = queue.getPriorityConfig(4); -console.log(`Priority 4: ${priority4Config.delay}ms delay, ${priority4Config.start}ms start delay`); +console.log(`Priority 4: ${priority4Config.postDelay}ms delay, ${priority4Config.startDelay}ms start delay`); const dataSyncConfig = queue.getCoalescingConfig("data.sync"); console.log(`Data sync: ${dataSyncConfig.windowDuration}ms window, ${dataSyncConfig.maxDelay}ms max delay`); @@ -873,7 +899,7 @@ console.log("All coalescing configs:", allCoalescingKeys); 4. Coalescing defaults 5. System defaults (lowest priority) -**Backward Compatibility:** Legacy `delays` options are automatically converted to the new `priorities` format. +**Backward Compatibility:** Deprecated option names are converted automatically; see [Deprecated Options](#deprecated-options). ### Smart Scheduling @@ -1024,10 +1050,10 @@ Priority delays create "cool-down" periods after task completion based on the co ```javascript const queue = new HoldMyTask({ concurrency: 1, - delays: { - 1: 1000, // 1 second delay after priority 1 tasks complete - 2: 500, // 500ms delay after priority 2 tasks complete - 3: 0 // No delay after priority 3 tasks (explicit) + priorities: { + 1: { postDelay: 1000 }, // 1 second delay after priority 1 tasks complete + 2: { postDelay: 500 }, // 500ms delay after priority 2 tasks complete + 3: { postDelay: 0 } // No delay after priority 3 tasks (explicit) } }); @@ -1053,7 +1079,7 @@ When urgent tasks need to execute immediately, bypassing active delay periods, u ```javascript const queue = new HoldMyTask({ concurrency: 1, - delays: { 1: 1000 } // 1 second delay after priority 1 tasks + priorities: { 1: { postDelay: 1000 } } // 1 second delay after priority 1 tasks }); // Timeline example: @@ -1080,7 +1106,7 @@ queue.enqueue(emergencyTaskD, callback, { ```javascript const queue = new HoldMyTask({ concurrency: 2, - delays: { 1: 800, 2: 400 } + priorities: { 1: { postDelay: 800 }, 2: { postDelay: 400 } } }); // Multiple tasks with different bypass behavior @@ -1164,9 +1190,13 @@ function volumeUp() { ```javascript const queue = new HoldMyTask({ concurrency: 1, - coalescingWindowDuration: 200, // 200ms window for grouping tasks - coalescingMaxDelay: 1000, // Maximum 1000ms delay before forcing execution - coalescingResolveAllPromises: true // All promises get the result (default: true) + coalescing: { + defaults: { + windowDuration: 200, // 200ms window for grouping tasks + maxDelay: 1000, // Maximum 1000ms delay before forcing execution + resolveAllPromises: true // All promises get the result (default: true) + } + } }); ``` @@ -1212,8 +1242,12 @@ The correct pattern for coalescing with updates is to enqueue update tasks **fro ```javascript const queue = new HoldMyTask({ concurrency: 2, // Allow volume and update tasks to run concurrently - coalescingWindowDuration: 200, - coalescingMaxDelay: 1000 + coalescing: { + defaults: { + windowDuration: 200, + maxDelay: 1000 + } + } }); // Volume commands using fire-and-forget pattern @@ -1302,8 +1336,12 @@ For precise scheduling, use `timestamp` instead of `start`: ```javascript const queue = new HoldMyTask({ - coalescingWindowDuration: 300, - coalescingMaxDelay: 2000 + coalescing: { + defaults: { + windowDuration: 300, + maxDelay: 2000 + } + } }); // Schedule all updates for the same exact time @@ -1337,8 +1375,12 @@ Control maximum delay with `mustRunBy` to ensure tasks don't wait too long: ```javascript const queue = new HoldMyTask({ - coalescingWindowDuration: 500, // Try to group for 500ms - coalescingMaxDelay: 2000 // But never wait more than 2 seconds + coalescing: { + defaults: { + windowDuration: 500, // Try to group for 500ms + maxDelay: 2000 // But never wait more than 2 seconds + } + } }); // Critical system updates @@ -1367,8 +1409,12 @@ Control how promises resolve within coalescing groups: ```javascript const queue = new HoldMyTask({ - coalescingResolveAllPromises: true, // Default: all promises get the result - coalescingWindowDuration: 200 + coalescing: { + defaults: { + resolveAllPromises: true, // Default: all promises get the result + windowDuration: 200 + } + } }); // Mode 1: All promises resolve (default behavior) @@ -1379,18 +1425,23 @@ const results1 = await Promise.all([ ]); // All three promises resolve with the same result -// Mode 2: Only representative promise resolves +// Mode 2: Only the representative (newest) task's promise resolves const queue2 = new HoldMyTask({ - coalescingResolveAllPromises: false, - coalescingWindowDuration: 200 + coalescing: { + defaults: { + resolveAllPromises: false, + windowDuration: 200 + } + } }); -const [result1, result2, result3] = await Promise.all([ - queue2.enqueue(task, { coalescingKey: "test" }), // Resolves with result - queue2.enqueue(task, { coalescingKey: "test" }), // Resolves with undefined - queue2.enqueue(task, { coalescingKey: "test" }) // Resolves with undefined +const settled = await Promise.allSettled([ + queue2.enqueue(task, { coalescingKey: "test" }), // Rejects: "Task was coalesced with a newer task" + queue2.enqueue(task, { coalescingKey: "test" }), // Rejects: "Task was coalesced with a newer task" + queue2.enqueue(task, { coalescingKey: "test" }) // Resolves with the result ]); -// Only the first (representative) promise gets the actual result +// Only the newest task in the group gets the result; the others are rejected +// (callback-style tasks receive the same error as their first argument) ``` ### Real-World Coalescing Patterns @@ -1402,8 +1453,12 @@ class VolumeController { constructor() { this.queue = new HoldMyTask({ concurrency: 2, // Allow volume and update tasks concurrently - coalescingWindowDuration: 200, - coalescingMaxDelay: 1000 + coalescing: { + defaults: { + windowDuration: 200, + maxDelay: 1000 + } + } }); } @@ -1448,8 +1503,12 @@ class APIBatcher { constructor() { this.queue = new HoldMyTask({ concurrency: 2, - coalescingWindowDuration: 300, - coalescingMaxDelay: 1500 + coalescing: { + defaults: { + windowDuration: 300, + maxDelay: 1500 + } + } }); } @@ -1479,8 +1538,12 @@ Real-world performance improvements with the embedded update pattern: // Fire-and-forget pattern with embedded coalescing updates const queue = new HoldMyTask({ concurrency: 2, - coalescingWindowDuration: 200, - coalescingMaxDelay: 1000 + coalescing: { + defaults: { + windowDuration: 200, + maxDelay: 1000 + } + } }); // Volume control example - realistic embedded pattern @@ -1523,7 +1586,7 @@ function processVolumeCommands() { ## โฑ๏ธ Timeouts -Tasks automatically timeout and either call the callback with an error or reject the promise: +Tasks automatically time out and either call the callback with an error payload or reject the promise. Callback-style timeouts also emit an `error` event on the queue (see [Events](#events)): **Callback API:** @@ -1761,10 +1824,10 @@ function processUser(userId) { ```javascript const queue = new HoldMyTask({ concurrency: 1, - delays: { - 1: 1000, // 1 second between high-priority tasks - 2: 100, // 100ms between medium-priority tasks - 3: 0 // No delay for low-priority tasks + priorities: { + 1: { postDelay: 1000 }, // 1 second between high-priority tasks + 2: { postDelay: 100 }, // 100ms between medium-priority tasks + 3: { postDelay: 0 } // No delay for low-priority tasks } }); @@ -1781,7 +1844,7 @@ queue.enqueue(mediumTask2, callback, { priority: 2 }); ```javascript const queue = new HoldMyTask({ concurrency: 5, - delays: { 1: 50 } // Small delay between batches + priorities: { 1: { postDelay: 50 } } // Small delay between batches }); async function processBatch(items) { @@ -1816,10 +1879,10 @@ Real-world scenario: API rate limiting with emergency override capability. ```javascript const apiQueue = new HoldMyTask({ concurrency: 2, - delays: { - 1: 2000, // 2 second delay between API calls (rate limiting) - 2: 5000, // 5 second delay for heavy operations - 9: 0 // No delay for monitoring tasks + priorities: { + 1: { postDelay: 2000 }, // 2 second delay between API calls (rate limiting) + 2: { postDelay: 5000 }, // 5 second delay for heavy operations + 9: { postDelay: 0 } // No delay for monitoring tasks } }); From e49fbcf5717927aa0bd14c5e0911215c11ce977d Mon Sep 17 00:00:00 2001 From: Shinrai Date: Sat, 3 Oct 2026 18:04:59 -0700 Subject: [PATCH 09/16] docs(readme): document postDelay/startDelay task options and optional error listener --- README.md | 42 ++++++++++++++++++++---------------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index d144831..a0515c2 100644 --- a/README.md +++ b/README.md @@ -84,9 +84,6 @@ const queue = new HoldMyTask({ } }); -// Callback-style tasks also report failures as queue "error" events, so listen for them -queue.on("error", (task) => console.error(`Task ${task.id} ${task.status}:`, task.error)); - // Enqueue a task queue.enqueue( async (signal) => { @@ -354,12 +351,12 @@ Adds a task to the queue. **Task Options:** - `priority` (number) - Task priority (higher = more important) -- `postDelay` (number) - Override completion delay for this task (use -1 to bypass delays). `delay` is a deprecated alias that emits a `warning` event +- `postDelay` (number) - Override completion delay for this task (use -1 to bypass delays). `delay` is a deprecated alias that emits a `warning` event once per queue - `bypassDelay` (boolean) - If true, skip any active delay period and start immediately - `timeout` (number) - Timeout in milliseconds - `signal` (AbortSignal) - External abort signal - `timestamp` (number) - Absolute execution timestamp -- `startDelay` (number) - Milliseconds from now when the task should be ready to run (convenience for timestamp calculation). `start` is a deprecated alias that emits a `warning` event +- `startDelay` (number) - Milliseconds from now when the task should be ready to run (convenience for timestamp calculation). `start` is a deprecated alias that emits a `warning` event once per queue - `coalescingKey` (string) - Tasks with the same coalescing key can be merged for efficiency - `mustRunBy` (number) - Absolute timestamp by which the task must execute (overrides coalescing delays) - `metadata` (any) - Custom metadata attached to the task. Individual metadata is always directly accessible via the returned task handle @@ -615,7 +612,7 @@ const queue = new HoldMyTask({ concurrency: 2 }); // Add some tasks queue.enqueue(async () => "task1"); -queue.enqueue(async () => "task2", { priority: 2, start: 5000 }); +queue.enqueue(async () => "task2", { priority: 2, startDelay: 5000 }); // Check initial state console.log("Initial state:"); @@ -657,8 +654,7 @@ queue.on("warning", (warning) => { }); ``` -> [!IMPORTANT] -> When a callback-style task fails, times out, or is aborted, the queue emits an `error` event with the task (its `error` and `status` properties describe the failure) before calling the task's callback. HoldMyTask is an `EventEmitter`, so if no `error` listener is attached, Node.js throws the event as an unhandled error. Attach a `queue.on("error", ...)` listener whenever you use the callback API. Promise-style tasks report task failures only through the rejected promise. +When a callback-style task fails, times out, or is aborted, the task's callback receives the failure. The queue also emits an `error` event with the task (its `error` and `status` properties describe the failure), but only when an `error` listener is attached, so a listener is optional. Promise-style tasks report task failures only through the rejected promise. The callback's first argument is an error payload rather than the raw error: `{ type: "timeout", message }`, `{ type: "canceled", message: "Task was aborted" }`, or `{ type: "error", error }`. @@ -699,12 +695,14 @@ These names still work, but each emits a deprecation warning. Use the current na | `delays: { [priority]: ms }` | `priorities: { [priority]: { postDelay: ms } }` | | `delay` in a `priorities` entry, `coalescing.defaults`, `coalescing.keys` entry, `configurePriority()` or `configureCoalescingKey()` | `postDelay` | | `start` in the same places | `startDelay` | +| `delay` as a task option passed to `enqueue()` | `postDelay` | +| `start` as a task option passed to `enqueue()` | `startDelay` | | `coalescingWindowDuration` (constructor) | `coalescing.defaults.windowDuration` | | `coalescingMaxDelay` (constructor) | `coalescing.defaults.maxDelay` | | `coalescingMultipleCallbacks` (constructor) | `coalescing.defaults.multipleCallbacks` | | `coalescingResolveAllPromises` (constructor) | `coalescing.defaults.resolveAllPromises` | -Task-level options passed to `enqueue()` are not deprecated: `delay`, `start`, and the per-task `coalescingWindowDuration` / `coalescingMaxDelay` / `coalescingMultipleCallbacks` / `coalescingResolveAllPromises` overrides are the current names there. +Each task-level alias (`delay`, `start`) warns once per queue instance, not once per task. If both a deprecated alias and its replacement are given, the replacement wins. The per-task `coalescingWindowDuration` / `coalescingMaxDelay` / `coalescingMultipleCallbacks` / `coalescingResolveAllPromises` overrides are not deprecated and remain the current names for task options. --- @@ -1064,15 +1062,15 @@ const queue = new HoldMyTask({ // โ†’ Next task can't start until 10:00:07.5 (500ms delay) // Override delay for specific task -queue.enqueue(task, callback, { priority: 1, delay: 200 }); // Uses 200ms instead of 1000ms +queue.enqueue(task, callback, { priority: 1, postDelay: 200 }); // Uses 200ms instead of 1000ms // Set zero delay for specific task -queue.enqueue(task, callback, { priority: 1, delay: 0 }); // No delay after this task +queue.enqueue(task, callback, { priority: 1, postDelay: 0 }); // No delay after this task ``` ### Delay Bypass - Emergency Task Injection -When urgent tasks need to execute immediately, bypassing active delay periods, use the `bypassDelay` option or `delay: -1` syntax. This is perfect for emergency situations, high-priority interrupts, or critical system tasks. +When urgent tasks need to execute immediately, bypassing active delay periods, use the `bypassDelay` option or `postDelay: -1` syntax. This is perfect for emergency situations, high-priority interrupts, or critical system tasks. #### Bypass Behavior @@ -1097,7 +1095,7 @@ queue.enqueue(urgentTaskC, callback, { // Alternative bypass syntax queue.enqueue(emergencyTaskD, callback, { priority: 1, - delay: -1 // Same as bypassDelay: true + postDelay: -1 // Same as bypassDelay: true }); ``` @@ -1141,7 +1139,7 @@ When tasks with the same `coalescingKey` are enqueued within a time window, they 3. **One representative task** executes for the entire group 4. **All promises** in the group resolve with the same result -โš ๏ธ **Critical Timing Consideration**: Real-world tasks take time to execute (100ms-2000ms+). If your coalescing tasks need to see the final state from other operations, ensure proper timing with `start` delays or `timestamp` scheduling. Tasks that start too early may see intermediate states rather than final results. +โš ๏ธ **Critical Timing Consideration**: Real-world tasks take time to execute (100ms-2000ms+). If your coalescing tasks need to see the final state from other operations, ensure proper timing with `startDelay` or `timestamp` scheduling. Tasks that start too early may see intermediate states rather than final results. ### ๐ŸŽฏ Correct Coalescing Pattern: Fire-and-Forget with Embedded Updates @@ -1216,7 +1214,7 @@ async function updateVolume(change) { { coalescingKey: "volume.update", // Tasks with same key get grouped priority: 1, - delay: 100 // 100ms delay after completion + postDelay: 100 // 100ms delay after completion } ); } @@ -1274,7 +1272,7 @@ function volumeUp(amount = 1) { { coalescingKey: "volume.ui.update", // UI updates get coalesced priority: 5, // Lower priority than volume commands - delay: 100 // Brief delay after UI updates + postDelay: 100 // Brief delay after UI updates } ); @@ -1282,7 +1280,7 @@ function volumeUp(amount = 1) { }, { priority: 1, // High priority for user actions - delay: 100 // Brief delay after volume operations + postDelay: 100 // Brief delay after volume operations } ); } @@ -1315,7 +1313,7 @@ async function processDataBatch(data) { { coalescingKey: "api.batch.process", priority: 2, - start: 100 // 100ms delay allows grouping + startDelay: 100 // 100ms delay allows grouping } ); } @@ -1332,7 +1330,7 @@ async function processDataBatch(data) { ### Coalescing with Explicit Timestamps -For precise scheduling, use `timestamp` instead of `start`: +For precise scheduling, use `timestamp` instead of `startDelay`: ```javascript const queue = new HoldMyTask({ @@ -1481,7 +1479,7 @@ class VolumeController { { coalescingKey: "volume.ui.update", // Updates coalesce together priority: 3, // Lower priority than volume changes - delay: 100 // Brief delay after UI updates + postDelay: 100 // Brief delay after UI updates } ); @@ -1489,7 +1487,7 @@ class VolumeController { }, { priority: 1, // High priority for user actions - delay: 50 // Brief delay between volume operations + postDelay: 50 // Brief delay between volume operations } ); } @@ -1905,7 +1903,7 @@ apiQueue.enqueue(processSecurityAlert, handleEmergency, { // Alternative syntax for bypass apiQueue.enqueue(emergencyShutdown, handleEmergency, { priority: 1, - delay: -1, // Same as bypassDelay: true + postDelay: -1, // Same as bypassDelay: true metadata: { action: "shutdown" } }); From fcb7dda9ba5aa41a70b8f43ab95b6f79c2965c69 Mon Sep 17 00:00:00 2001 From: Shinrai Date: Sat, 3 Oct 2026 18:33:57 -0700 Subject: [PATCH 10/16] docs(readme): document the invalid-priority warning from #57 Warnings are no longer only deprecations: #57 reports non-integer priority keys as type "invalid-priority". Describe both warning shapes. --- README.md | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index a0515c2..5e2f089 100644 --- a/README.md +++ b/README.md @@ -307,7 +307,7 @@ queue.on("error", (err) => console.error("Queue error:", err)); queue.on("warning", (warning) => console.warn("Warning:", warning.message)); ``` -Initialization warnings (such as [deprecation warnings](#deprecation-warning-events)) are emitted on `setImmediate` in both modes, so a listener attached right after a synchronous `new HoldMyTask(...)` receives them as well. +Initialization warnings (such as [deprecation warnings](#warning-events)) are emitted on `setImmediate` in both modes, so a listener attached right after a synchronous `new HoldMyTask(...)` receives them as well. **Options:** @@ -658,17 +658,25 @@ When a callback-style task fails, times out, or is aborted, the task's callback The callback's first argument is an error payload rather than the raw error: `{ type: "timeout", message }`, `{ type: "canceled", message: "Task was aborted" }`, or `{ type: "error", error }`. -### Deprecation Warning Events +### Warning Events -When deprecated configuration options are used, HoldMyTask converts them to the current names and emits one `warning` event per deprecated option. The events are emitted asynchronously (on `setImmediate`), so a listener attached right after the constructor returns still receives them. +HoldMyTask reports configuration problems through `warning` events instead of throwing. The events are emitted asynchronously (on `setImmediate`), so a listener attached right after the constructor returns still receives them. There are two types. -Each warning has this shape: +**Deprecations** (`type: "deprecation"`): when deprecated options are used, HoldMyTask converts them to the current names and emits one warning per deprecated option: -- `type` - always `"deprecation"` +- `type` - `"deprecation"` - `message` - a human-readable description - `deprecated` - the deprecated option or property name - `replacement` - the name to use instead +**Invalid priority keys** (`type: "invalid-priority"`): `priorities` (and the deprecated `delays`) must be keyed by integers. A key that isn't one is reported instead of being dropped silently: a key that isn't numeric at all (`"high"`) is ignored, and a key with a fractional part (`"2.5"`) is applied to the truncated priority (`2`), as before: + +- `type` - `"invalid-priority"` +- `message` - a human-readable description +- `option` - the option the key came from (`"priorities"` or `"delays"`) +- `key` - the key as written +- `priority` - the priority it was applied to, or `null` when it was ignored + ```javascript const queue = new HoldMyTask({ delays: { 1: 100 }, // Deprecated: use priorities From b0ef387f624b96fb9471918b5be4a2e3a4e4321a Mon Sep 17 00:00:00 2001 From: Shinrai Date: Sat, 3 Oct 2026 20:09:51 -0700 Subject: [PATCH 11/16] deps: bump @cldmv/fix-headers to 2.1.4 and restamp file headers Bumps @cldmv/fix-headers to 2.1.4 and re-runs npm run fix:headers. 0 files' headers were restamped. --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index b3b78bb..a813899 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "Apache-2.0", "devDependencies": { "@cldmv/configs": "^1.2.0", - "@cldmv/fix-headers": "^2.1.1", + "@cldmv/fix-headers": "^2.1.4", "@cldmv/vitest-runner": "^1.2.0", "@eslint/css": "^2.0.0", "@eslint/js": "^10.0.1", @@ -131,9 +131,9 @@ } }, "node_modules/@cldmv/fix-headers": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@cldmv/fix-headers/-/fix-headers-2.1.1.tgz", - "integrity": "sha512-08xW44RvtrKUCTOjUFKHyrDvx6rT70zGqgRR+tdW0R9ElZrHwSg25xCRrZD+U7Dmj5fK9KmtuOWPjZtJYSfzYA==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@cldmv/fix-headers/-/fix-headers-2.1.4.tgz", + "integrity": "sha512-PvCKMztN9k+jOjEpMCANMaDIkNW7cs2qp5P73JVzResk1+DfqhoZVqT9jpTT8cUu+6OGszsNqj8/X0Q9IhfNtg==", "dev": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index e6d0f07..83d3465 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,7 @@ "type": "module", "devDependencies": { "@cldmv/configs": "^1.2.0", - "@cldmv/fix-headers": "^2.1.1", + "@cldmv/fix-headers": "^2.1.4", "@cldmv/vitest-runner": "^1.2.0", "@eslint/css": "^2.0.0", "@eslint/js": "^10.0.1", From 6527fb6729405a13ec6c43511048e6cd6e1f694a Mon Sep 17 00:00:00 2001 From: Shinrai Date: Sat, 3 Oct 2026 19:28:00 -0700 Subject: [PATCH 12/16] docs: add v2.0.5 release changelog and promote it in What's New Covers #55 (task-level postDelay/startDelay, deprecated delay/start aliases, delay-gate fix), #56 (callback failures without an error listener), #57 (invalid-priority warning), #59 (regression tests for #52) and #50 (README restructure). --- README.md | 10 +++--- docs/changelog/v2/v2.0.5.md | 63 +++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 5 deletions(-) create mode 100644 docs/changelog/v2/v2.0.5.md diff --git a/README.md b/README.md index 5e2f089..958b9cc 100644 --- a/README.md +++ b/README.md @@ -16,18 +16,18 @@ Use it with callbacks or promises, from ESM or CommonJS, with full TypeScript de ## โœจ What's New -### Latest: v2.0.4 (October 2026) +### Latest: v2.0.5 (October 2026) -- **Clearer `require()` failure on older Node.js** โ€” The CommonJS entry now throws an `ERR_REQUIRE_ESM` error that names the supported Node.js versions (`^20.19.0` or `>=22.12.0`) and points to `import()`, instead of a bare error on versions without `require(esm)`. Behavior on supported versions is unchanged. -- **Security patch for development tooling** โ€” Updates `brace-expansion` to 5.0.12, fixing [GHSA-q2hr-2g5m-vwhr](https://github.com/advisories/GHSA-q2hr-2g5m-vwhr), a quadratic-time denial of service in a development-only dependency pulled in by `minimatch`. `@eslint/css` moves to 2.0.0 and `prettier` to 3.9.9. -- [View full v2.0.4 Changelog](https://github.com/CLDMV/holdmytask/blob/master/docs/changelog/v2/v2.0.4.md) +- **Task-level `postDelay` / `startDelay` work as documented** โ€” `enqueue()` now honors the documented `postDelay` and `startDelay` task options, and a task-level completion delay is enforced even when the task's priority has no `postDelay` of its own. The old `delay` / `start` names still work as deprecated aliases and emit one `deprecation` warning per queue instance. +- **Safer failure reporting** โ€” A failing callback-style task no longer crashes the process when no `error` listener is attached; the failure always reaches the callback, and the `error` event is emitted only when someone listens. Non-integer `priorities` keys now produce an `invalid-priority` warning instead of being dropped silently. The README was rewritten to the CLDMV layout with every example on the current API. +- [View full v2.0.5 Changelog](https://github.com/CLDMV/holdmytask/blob/master/docs/changelog/v2/v2.0.5.md) ### Recent Releases +- **v2.0.4** (October 2026) โ€” Clearer `require()` error on Node.js without `require(esm)`; `brace-expansion` security patch in development tooling ([Changelog](https://github.com/CLDMV/holdmytask/blob/master/docs/changelog/v2/v2.0.4.md)) - **v2.0.3** (October 2026) โ€” Development toolchain moves to vitest 5; bundle-size and v4 workflow updates; verbatim Apache-2.0 license text ([Changelog](https://github.com/CLDMV/holdmytask/blob/master/docs/changelog/v2/v2.0.3.md)) - **v2.0.2** (September 2026) โ€” Development dependency update (`@humanfs/node`); no other changes ([Changelog](https://github.com/CLDMV/holdmytask/blob/master/docs/changelog/v2/v2.0.2.md)) - **v2.0.1** (September 2026) โ€” Fix: a task's timeout timer is now cleared when the task fails or is cancelled ([Changelog](https://github.com/CLDMV/holdmytask/blob/master/docs/changelog/v2/v2.0.1.md)) -- **v2.0.0** (August 2026) โ€” `HoldMyTask` and its aliases are the real class again (breaking for code adapted to v1.6.1's factories); scheduler deadlock fix; `holdmytask-dev` export condition ([Changelog](https://github.com/CLDMV/holdmytask/blob/master/docs/changelog/v2/v2.0.0.md)) ๐Ÿ“š **For complete version history and detailed release notes, see the [docs/changelog/](https://github.com/CLDMV/holdmytask/tree/master/docs/changelog/) folder.** diff --git a/docs/changelog/v2/v2.0.5.md b/docs/changelog/v2/v2.0.5.md new file mode 100644 index 0000000..71d5066 --- /dev/null +++ b/docs/changelog/v2/v2.0.5.md @@ -0,0 +1,63 @@ +# HoldMyTask v2.0.5 Changelog + +**Release Date**: October 2026 +**Release Type**: Patch +**Branch**: `release/2.0.5` + +--- + +## Overview + +v2.0.5 fixes three long-standing problems in how the queue reads its options and reports failures. The documented task-level `postDelay` / `startDelay` options now actually work (the old `delay` / `start` names become deprecated aliases), a callback-style task failure no longer crashes the process when no `error` listener is attached, and a `priorities` key that is not an integer is now reported instead of being dropped silently. The README was rewritten to the CLDMV layout and every example now uses the current API. + +Runtime behavior changes in a few observable ways, all described below: code that still passes `delay` or `start` to `enqueue()` now receives a deprecation `warning` event, a task-level completion delay is now enforced even when the task's priority has no `postDelay` of its own, a callback-style task failure is no longer thrown as an unhandled `error` event, and a non-integer priority key now produces an `invalid-priority` warning. No option was removed and no exported signature changed. + +--- + +## ๐Ÿ› Bug Fixes + +### Task-level `postDelay` / `startDelay` are honored; `delay` / `start` are deprecated aliases ([#55](https://github.com/CLDMV/holdmytask/pull/55)) + +`enqueue()` only read the task-level `delay` and `start` options, although its JSDoc already pointed at `postDelay`. Passing the documented name gave no delay at all. `enqueue()` now treats `postDelay` and `startDelay` as the current task-level names, matching the names already used in `priorities`, `coalescing.defaults` and `coalescing.keys`. `delay` and `start` still work and map onto the new names; when both forms are given, the current name wins. `postDelay: -1` bypasses an active delay period exactly as `delay: -1` did, and the caller's options object is never mutated. `getPriorityConfig()` and `getCoalescingConfig()` accept both names in their `taskOptions` argument. + +**New warning:** each deprecated task-level alias now emits a `warning` event of type `"deprecation"` (with `deprecated` and `replacement` fields), in the same shape the priority and coalescing levels already used. The warning fires once per alias per queue instance, not once per task, so a hot enqueue path does not flood listeners. No warning is emitted when the replacement name is also present. + +**Scheduler fix:** the delay gate between tasks used to require the completed task's _priority_ to have a positive `postDelay`. A task-level completion delay on a priority without one was recorded but never enforced, which was true of the old `delay` name as well. The gate now relies on the computed next-available time alone, so a task-level `postDelay` (or `delay`) takes effect regardless of the priority's configuration. Code that relied on a task-level delay being ignored in that situation will now see the delay applied. Closes [#51](https://github.com/CLDMV/holdmytask/issues/51). + +### Callback-style task failures no longer require an `error` listener ([#56](https://github.com/CLDMV/holdmytask/pull/56)) + +When a callback-style task failed, timed out, was aborted or expired, the queue emitted `error`. `HoldMyTask` is an `EventEmitter`, so with no `error` listener that emit threw: the process crashed (an unhandled rejection for failures, an uncaught exception from the scheduler for expiry), and for failures the task's callback never ran. Task-failure `error` events are now emitted only when at least one `error` listener is attached. The failure always reaches the task's callback with the documented payload (`{ type: "error", error }`, `{ type: "timeout", message }`, `{ type: "canceled", message: "Task was aborted" }`, or the expiry error). + +Listeners that are attached see the same events as before. The difference is for queues without one: a callback task failure is no longer thrown. An error thrown by the callback itself is still emitted unconditionally, so a bug inside a callback is not silently swallowed, and the `error` events for invalid `enqueue()` arguments and rejected `configurePriority()` / `configureCoalescingKey()` input are unchanged. Promise-style tasks are unchanged and report failures only through the rejected promise. Closes [#53](https://github.com/CLDMV/holdmytask/issues/53). + +### Non-integer priority keys are reported instead of dropped silently ([#57](https://github.com/CLDMV/holdmytask/pull/57)) + +The constructor ran `priorities` (and the deprecated `delays`) keys through `parseInt` and discarded anything that came back `NaN`, so a config such as `priorities: { high: { postDelay: 100 } }` did nothing, and a key such as `"2.5"` was truncated without notice. Any non-integer key now emits a `warning` event of type `"invalid-priority"` with `message`, `option` (`"priorities"` or `"delays"`), `key` and `priority` fields. A key that cannot be read as a number is still ignored (`priority: null`); a fractional key keeps its historical meaning, the truncated priority, but is reported. Integer keys, including negative ones, are unaffected. The constructor still never throws for configuration, so a config that constructed fine on v2.0.4 still constructs fine. Closes [#54](https://github.com/CLDMV/holdmytask/issues/54). + +--- + +## ๐Ÿงช Tests + +- New suites: `TaskLevelDelayNames` ([#55](https://github.com/CLDMV/holdmytask/pull/55)), `CallbackErrorWithoutListener` ([#56](https://github.com/CLDMV/holdmytask/pull/56)) and `PriorityKeyValidation` ([#57](https://github.com/CLDMV/holdmytask/pull/57)). +- `PostDelayAfterAwait` ([#59](https://github.com/CLDMV/holdmytask/pull/59)) adds deterministic fake-timer coverage for `postDelay` when the next task is enqueued after awaiting the previous one: promise API after `await`, promise API in the resolving microtask, and callback API enqueuing from the completion callback, in both smart-scheduling and polling modes. All cases pass on the current code; the report in [#52](https://github.com/CLDMV/holdmytask/issues/52) could not be reproduced, and the issue is closed with these tests as regression coverage. + +## ๐Ÿ“š Documentation + +- The README was restructured to the CLDMV layout and every example was updated from deprecated option names to the current API ([#50](https://github.com/CLDMV/holdmytask/pull/50)). It now documents the task-level `postDelay` / `startDelay` options, the optional `error` listener for callback-style tasks, both `warning` event types (`deprecation` and `invalid-priority`), and a table of every deprecated option with its replacement. +- The JSDoc and generated `types/` declarations for `enqueue()` list `postDelay` and `startDelay`, with `delay` and `start` marked deprecated. +- **NEW:** [docs/changelog/v2/v2.0.5.md](./v2.0.5.md) โ€” this changelog. + +## ๐Ÿ”ง Dependencies + +_No dependency updates_ + +--- + +## Upgrade notes + +No breaking API changes โ€” drop-in for [v2.0.4](./v2.0.4.md), with these behavior differences to check: + +- Replace task-level `delay` with `postDelay` and `start` with `startDelay` in `enqueue()` calls. The old names keep working, but each emits one `deprecation` warning per queue instance. +- A task-level `postDelay` (or `delay`) is now enforced even when the task's priority has no `postDelay`. If a queue depended on that delay being ignored, remove it from the task options. +- A callback-style task failure is no longer thrown when no `error` listener is attached; handle failures in the task callback, or attach an `error` listener if the queue-level event is wanted. +- Check `priorities` / `delays` configs for non-integer keys. They behave as before, but now emit an `invalid-priority` warning. From 8342f42a2eeafd09dfb6b1c57a7f59e3b1c3490f Mon Sep 17 00:00:00 2001 From: Shinrai Date: Sat, 3 Oct 2026 20:35:22 -0700 Subject: [PATCH 13/16] docs: list the fix-headers 2.1.4 bump (#61) in the v2.0.5 notes --- docs/changelog/v2/v2.0.5.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changelog/v2/v2.0.5.md b/docs/changelog/v2/v2.0.5.md index 71d5066..9617832 100644 --- a/docs/changelog/v2/v2.0.5.md +++ b/docs/changelog/v2/v2.0.5.md @@ -49,7 +49,7 @@ The constructor ran `priorities` (and the deprecated `delays`) keys through `par ## ๐Ÿ”ง Dependencies -_No dependency updates_ +- `@cldmv/fix-headers` (dev dependency) `^2.1.1` โ†’ `^2.1.4` ([#61](https://github.com/CLDMV/holdmytask/pull/61)). Version 2.1.4 no longer writes a JavaScript comment into JSON or Markdown files, processes every repeated `--input`, and never walks dependency folders such as `node_modules`. Only `package.json` and the lockfile changed; no file headers were restamped and the published package is unaffected. --- From b9a39a795bca9eeb7e1df3ffafb7f61d1d82ff83 Mon Sep 17 00:00:00 2001 From: Shinrai Date: Sun, 4 Oct 2026 16:40:27 -0700 Subject: [PATCH 14/16] deps: bump @cldmv/fix-headers to 2.2.0 Bumps @cldmv/fix-headers from ^2.1.4 to 2.2.0. No file headers changed. --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index a813899..09137bd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "Apache-2.0", "devDependencies": { "@cldmv/configs": "^1.2.0", - "@cldmv/fix-headers": "^2.1.4", + "@cldmv/fix-headers": "^2.2.0", "@cldmv/vitest-runner": "^1.2.0", "@eslint/css": "^2.0.0", "@eslint/js": "^10.0.1", @@ -131,9 +131,9 @@ } }, "node_modules/@cldmv/fix-headers": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@cldmv/fix-headers/-/fix-headers-2.1.4.tgz", - "integrity": "sha512-PvCKMztN9k+jOjEpMCANMaDIkNW7cs2qp5P73JVzResk1+DfqhoZVqT9jpTT8cUu+6OGszsNqj8/X0Q9IhfNtg==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cldmv/fix-headers/-/fix-headers-2.2.0.tgz", + "integrity": "sha512-EQTAKCo0B639q2bde+vO5ciYbtvNgAJFm0DBthIJQnmTFJmQDUlObp5EsTRgg5CUlFoUnGMX+q35LC02QvYU4w==", "dev": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index 83d3465..a058351 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,7 @@ "type": "module", "devDependencies": { "@cldmv/configs": "^1.2.0", - "@cldmv/fix-headers": "^2.1.4", + "@cldmv/fix-headers": "^2.2.0", "@cldmv/vitest-runner": "^1.2.0", "@eslint/css": "^2.0.0", "@eslint/js": "^10.0.1", From 472b73886e04763e1ba0511fe273687d484af173 Mon Sep 17 00:00:00 2001 From: Shinrai Date: Sun, 4 Oct 2026 19:04:53 -0700 Subject: [PATCH 15/16] deps: bump @cldmv/configs to 1.2.4 Bump @cldmv/configs ^1.2.0 (locked 1.2.0-1.2.2) to ^1.2.4 (1.2.4). The older shared fix-headers.json forced forceAuthorUpdate and forceLastModifiedAuthorUpdate on; 1.2.4 sets both false, so with fix-headers 2.2.0 @Author is never rewritten and @Last modified by changes only on real content edits. Restamped files: 0. --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 09137bd..7f5d0ab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "2.0.5", "license": "Apache-2.0", "devDependencies": { - "@cldmv/configs": "^1.2.0", + "@cldmv/configs": "^1.2.4", "@cldmv/fix-headers": "^2.2.0", "@cldmv/vitest-runner": "^1.2.0", "@eslint/css": "^2.0.0", @@ -120,9 +120,9 @@ } }, "node_modules/@cldmv/configs": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@cldmv/configs/-/configs-1.2.0.tgz", - "integrity": "sha512-FDmlxOx6ceKuD5zTamUy9XOfAC4opeOaLxWqZz9okKxj8TsTZP6dN7W0VccRYRdw4606NvXjhdZqOPibcNpXmQ==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@cldmv/configs/-/configs-1.2.4.tgz", + "integrity": "sha512-7HPqAgCKqol3fHpawEsXJ5ZqHxlDPZk1puoFu43f/gaNfhWwufDTzjkvLk90yVEumyz4N3pUwIxfbHUkUTpDEg==", "dev": true, "license": "Apache-2.0", "funding": { diff --git a/package.json b/package.json index a058351..915d04d 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,7 @@ }, "type": "module", "devDependencies": { - "@cldmv/configs": "^1.2.0", + "@cldmv/configs": "^1.2.4", "@cldmv/fix-headers": "^2.2.0", "@cldmv/vitest-runner": "^1.2.0", "@eslint/css": "^2.0.0", From a594d504b7aac0421f0429d0afa1baf8632aaf12 Mon Sep 17 00:00:00 2001 From: Shinrai Date: Sun, 4 Oct 2026 20:12:24 -0700 Subject: [PATCH 16/16] docs: update the v2.0.5 release notes --- README.md | 1 + docs/changelog/v2/v2.0.5.md | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 958b9cc..529dddc 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ Use it with callbacks or promises, from ESM or CommonJS, with full TypeScript de - **Task-level `postDelay` / `startDelay` work as documented** โ€” `enqueue()` now honors the documented `postDelay` and `startDelay` task options, and a task-level completion delay is enforced even when the task's priority has no `postDelay` of its own. The old `delay` / `start` names still work as deprecated aliases and emit one `deprecation` warning per queue instance. - **Safer failure reporting** โ€” A failing callback-style task no longer crashes the process when no `error` listener is attached; the failure always reaches the callback, and the `error` event is emitted only when someone listens. Non-integer `priorities` keys now produce an `invalid-priority` warning instead of being dropped silently. The README was rewritten to the CLDMV layout with every example on the current API. +- **Header tooling on fix-headers 2.2.0** โ€” the `@cldmv/fix-headers` and `@cldmv/configs` dev dependencies move to 2.2.0 and 1.2.4, so `@Last modified by` now follows content edits only; no file was restamped, and the published package is unaffected (#61, #62). - [View full v2.0.5 Changelog](https://github.com/CLDMV/holdmytask/blob/master/docs/changelog/v2/v2.0.5.md) ### Recent Releases diff --git a/docs/changelog/v2/v2.0.5.md b/docs/changelog/v2/v2.0.5.md index 9617832..e118831 100644 --- a/docs/changelog/v2/v2.0.5.md +++ b/docs/changelog/v2/v2.0.5.md @@ -49,7 +49,10 @@ The constructor ran `priorities` (and the deprecated `delays`) keys through `par ## ๐Ÿ”ง Dependencies -- `@cldmv/fix-headers` (dev dependency) `^2.1.1` โ†’ `^2.1.4` ([#61](https://github.com/CLDMV/holdmytask/pull/61)). Version 2.1.4 no longer writes a JavaScript comment into JSON or Markdown files, processes every repeated `--input`, and never walks dependency folders such as `node_modules`. Only `package.json` and the lockfile changed; no file headers were restamped and the published package is unaffected. +Both are dev dependencies and affect only the `npm run fix:headers` script, which extends `@cldmv/configs/fix-headers.json`. Only `package.json` and the lockfile changed; no file headers were restamped and the published package is unaffected. + +- `@cldmv/fix-headers` `^2.1.1` โ†’ `^2.2.0`, in two steps. 2.1.4 ([#61](https://github.com/CLDMV/holdmytask/pull/61)) no longer writes a JavaScript comment into JSON or Markdown files, processes every repeated `--input`, and never walks dependency folders such as `node_modules`. 2.2.0 ([#62](https://github.com/CLDMV/holdmytask/pull/62)) makes `@Last modified by` follow edits to a file's content only, so a run that merely rewrites a header keeps the recorded editor. +- `@cldmv/configs` `^1.2.0` โ†’ `^1.2.4` ([#62](https://github.com/CLDMV/holdmytask/pull/62)). The shared fix-headers config now sets `forceAuthorUpdate` and `forceLastModifiedAuthorUpdate` to `false`, so `@Author` keeps the file's creator and `@Last modified by` changes only with real content edits. The 1.2.1 and 1.2.2 releases of that package only changed its own CI. ---