@gkucmierz/utils 4.0.2 → 4.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,9 +1,11 @@
1
1
  # @gkucmierz/utils
2
2
 
3
- [![Socket Badge](https://socket.dev/api/badge/npm/package/@gkucmierz/utils)](https://socket.dev/npm/package/@gkucmierz/utils)
4
- ![NPM Version](https://img.shields.io/npm/v/@gkucmierz/utils)
5
- ![License](https://img.shields.io/npm/l/@gkucmierz/utils)
6
- ![Downloads](https://img.shields.io/npm/dm/@gkucmierz/utils)
3
+ [![NPM Version](https://img.shields.io/npm/v/@gkucmierz/utils?color=3b82f6&logo=npm)](https://www.npmjs.com/package/@gkucmierz/utils)
4
+ [![Socket Security](https://img.shields.io/badge/Security-Socket.dev-7036fe?logo=socket&logoColor=white)](https://socket.dev/npm/package/@gkucmierz/utils)
5
+ [![Tests](https://img.shields.io/badge/Tests-153%20passed-22c55e?logo=jasmine&logoColor=white)](https://gitea.7u.pl/gkucmierz/utils)
6
+ [![Downloads](https://img.shields.io/npm/dm/@gkucmierz/utils?color=8b5cf6)](https://www.npmjs.com/package/@gkucmierz/utils)
7
+ [![License](https://img.shields.io/npm/l/@gkucmierz/utils?color=f59e0b)](https://gitea.7u.pl/gkucmierz/utils)
8
+ [![Node Version](https://img.shields.io/node/v/@gkucmierz/utils?color=43853d&logo=node.js&logoColor=white)](https://nodejs.org)
7
9
 
8
10
  A collection of useful utility functions and data structures for solving algorithmic tasks, competitive programming, and everyday development.
9
11
 
@@ -24,6 +26,7 @@ This library provides a wide range of mathematical functions and data structures
24
26
  - `bin2gray`, `gray2bin`: Pure array-cloning bit formatters for Gray code translations.
25
27
 
26
28
  - **Data Structures**:
29
+ - `ConflatingQueue`: Asynchronous coalescing work queue with key-based deduplication and controllable concurrency (`class`).
27
30
  - `SetCnt`: A set-like structure with element counting (`class`).
28
31
  - `Trie`: Efficient prefix tree implementation (`class`).
29
32
  - `Heap`: Min-heap priority queue (`class`).
@@ -78,8 +81,8 @@ This library provides a wide range of mathematical functions and data structures
78
81
 
79
82
  ## 📚 Documentation
80
83
 
81
- Full API documentation with examples is available at:
82
- 👉 **[https://gkucmierz.github.io/utils](https://gkucmierz.github.io/utils)**
84
+ - **Production Docs**: 👉 **[https://docs-utils.7u.pl](https://docs-utils.7u.pl)**
85
+ - **GitHub Pages Mirror**: [https://gkucmierz.github.io/utils](https://gkucmierz.github.io/utils)
83
86
 
84
87
  ## 🔗 Repository
85
88
  - **Internal Gitea**: [https://gitea.7u.pl/gkucmierz/utils](https://gitea.7u.pl/gkucmierz/utils)
package/main.mjs CHANGED
@@ -14,6 +14,9 @@ import {
14
14
  import {
15
15
  permutations, permutationsIterator
16
16
  } from './src/combinatorics/permutations.mjs'
17
+ import {
18
+ ConflatingQueue
19
+ } from './src/data-structures/ConflatingQueue.mjs'
17
20
  import {
18
21
  SetCnt
19
22
  } from './src/data-structures/SetCnt.mjs'
@@ -137,6 +140,7 @@ export * from './src/combinatorics/combinations.mjs';
137
140
  export * from './src/combinatorics/gray-code.mjs';
138
141
  export * from './src/combinatorics/n-choose-k.mjs';
139
142
  export * from './src/combinatorics/permutations.mjs';
143
+ export * from './src/data-structures/ConflatingQueue.mjs';
140
144
  export * from './src/data-structures/SetCnt.mjs';
141
145
  export * from './src/data-structures/Trie.mjs';
142
146
  export * from './src/data-structures/heap.mjs';
@@ -178,5 +182,5 @@ export * from './src/string-arrays/copy-case.mjs';
178
182
  export * from './src/string-arrays/format-big-number.mjs';
179
183
 
180
184
  export default [
181
- createLangtonsAnt, createUnlimitedGrid, combinations, combinationsIterator, bin2gray, gray2bin, nChooseK, permutations, permutationsIterator, SetCnt, Trie, Heap, ListNode, binarySearchArr, binarySearchGE, binarySearchLE, binarySearchRangeIncl, consumeIteratorNonBlocking, getType, measurePerformance, memoize, naturalSearch, randNormal, array2range, range2array, setSafeInterval, barycentricCoordinates, axisAngleToMatrix4, crossProduct, dotProduct, getRotationMatrixFromVectors, multiplyMatrix4, normalize, projectToTrackball, matrixAsArray, egcd, factors, factorsBI, gcd, gcdBI, lcm, lcmBI, lucasLehmerBI, mobius, mobiusBI, mod, modBI, phi, phiBI, powMod, powModBI, tonelliShanksBI, nelderMead, particleSwarmOptimization, simulatedAnnealing, goldenRatio, goldenRatioBI, goldenRatioStr, gpn, gpnBI, heronsFormula, heronsFormulaBI, squareRoot, squareRootBI, arrayHistogram, fromBase64, fromBase64Url, toBase64, toBase64Url, bijective2num, bijective2numBI, num2bijective, num2bijectiveBI, chunks, chunksAsyncIterator, chunksIterator, copyCase, formatBigNumber, formatBigNumberBI, wrapFn
185
+ createLangtonsAnt, createUnlimitedGrid, combinations, combinationsIterator, bin2gray, gray2bin, nChooseK, permutations, permutationsIterator, ConflatingQueue, SetCnt, Trie, Heap, ListNode, binarySearchArr, binarySearchGE, binarySearchLE, binarySearchRangeIncl, consumeIteratorNonBlocking, getType, measurePerformance, memoize, naturalSearch, randNormal, array2range, range2array, setSafeInterval, barycentricCoordinates, axisAngleToMatrix4, crossProduct, dotProduct, getRotationMatrixFromVectors, multiplyMatrix4, normalize, projectToTrackball, matrixAsArray, egcd, factors, factorsBI, gcd, gcdBI, lcm, lcmBI, lucasLehmerBI, mobius, mobiusBI, mod, modBI, phi, phiBI, powMod, powModBI, tonelliShanksBI, nelderMead, particleSwarmOptimization, simulatedAnnealing, goldenRatio, goldenRatioBI, goldenRatioStr, gpn, gpnBI, heronsFormula, heronsFormulaBI, squareRoot, squareRootBI, arrayHistogram, fromBase64, fromBase64Url, toBase64, toBase64Url, bijective2num, bijective2numBI, num2bijective, num2bijectiveBI, chunks, chunksAsyncIterator, chunksIterator, copyCase, formatBigNumber, formatBigNumberBI, wrapFn
182
186
  ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gkucmierz/utils",
3
- "version": "4.0.2",
3
+ "version": "4.1.1",
4
4
  "type": "module",
5
5
  "description": "Usefull functions for solving programming tasks",
6
6
  "keywords": [
@@ -12,6 +12,8 @@
12
12
  "benchmark",
13
13
  "combinations",
14
14
  "competitive-programming",
15
+ "conflating-queue",
16
+ "coalescing-queue",
15
17
  "data-structures",
16
18
  "factors",
17
19
  "gcd",
@@ -0,0 +1,162 @@
1
+ /**
2
+ * @module data-structures
3
+ */
4
+
5
+ /**
6
+ * ConflatingQueue (Coalescing Keyed Work Queue).
7
+ *
8
+ * A specialized asynchronous task queue that executes tasks with controllable concurrency
9
+ * (default 1 = sequential mutex) while automatically conflating (superseding/deduplicating)
10
+ * pending tasks with matching keys.
11
+ *
12
+ * When multiple tasks with the same key are enqueued before the previous task has started,
13
+ * the older pending task is superseded (resolved with `{ superseded: true }`), and only the
14
+ * freshest task is executed.
15
+ *
16
+ * Ideal for hardware control (DDC/CI, I2C, Serial), UI sliders, telemetry, and rate-limited APIs.
17
+ * @see {@link https://instacode.app/run/FASwtgDg9gTgLgAgN4IMJQHYDMA2BDOEDAcwEUBXAUyoQF8EsYowEAiAAWIGtyBjMEJRgAvAPTlCOAM6sA3MGDps+QiQrVK8oA|▶ Try it live in Instacode}
18
+ */
19
+ export class ConflatingQueue {
20
+ #concurrency;
21
+ #maxPending;
22
+ #pendingTasks = new Map();
23
+ #runningCount = 0;
24
+ #idleResolvers = [];
25
+
26
+ /**
27
+ * Initializes the ConflatingQueue.
28
+ *
29
+ * @param {object} [options={}]
30
+ * @param {number} [options.concurrency=1] - Maximum concurrent tasks executing simultaneously.
31
+ * @param {number} [options.maxPending=Infinity] - Maximum allowed distinct pending keys in queue.
32
+ */
33
+ constructor({ concurrency = 1, maxPending = Infinity } = {}) {
34
+ this.#concurrency = Math.max(1, concurrency || 1);
35
+ this.#maxPending = maxPending > 0 ? maxPending : Infinity;
36
+ }
37
+
38
+ /**
39
+ * Enqueues an asynchronous task associated with a specific key.
40
+ * If a pending task with the same key already exists, it is superseded by the new task.
41
+ *
42
+ * @param {string|number|symbol} key - Unique identifier for task coalescing.
43
+ * @param {Function} taskFn - The async or sync function to execute.
44
+ * @returns {Promise<*|{ superseded: boolean }>} Resolves with task result or `{ superseded: true }`.
45
+ */
46
+ enqueue(key, taskFn) {
47
+ if (typeof taskFn !== 'function') {
48
+ return Promise.reject(new TypeError('taskFn must be a function'));
49
+ }
50
+
51
+ if (this.#pendingTasks.has(key)) {
52
+ const prev = this.#pendingTasks.get(key);
53
+ prev.resolve({ superseded: true });
54
+ } else if (this.#pendingTasks.size >= this.#maxPending) {
55
+ return Promise.reject(new Error(`ConflatingQueue maxPending limit (${this.#maxPending}) exceeded`));
56
+ }
57
+
58
+ return new Promise((resolve, reject) => {
59
+ this.#pendingTasks.set(key, { taskFn, resolve, reject });
60
+ this.#drain();
61
+ });
62
+ }
63
+
64
+ /**
65
+ * Cancels a pending unstarted task by key.
66
+ *
67
+ * @param {string|number|symbol} key - Key of the pending task to cancel.
68
+ * @returns {boolean} True if task was cancelled, false if not found in pending queue.
69
+ */
70
+ cancel(key) {
71
+ if (!this.#pendingTasks.has(key)) return false;
72
+ const item = this.#pendingTasks.get(key);
73
+ this.#pendingTasks.delete(key);
74
+ item.resolve({ cancelled: true });
75
+ return true;
76
+ }
77
+
78
+ /**
79
+ * Clears and cancels all currently pending unstarted tasks.
80
+ */
81
+ clear() {
82
+ for (const item of this.#pendingTasks.values()) {
83
+ item.resolve({ cancelled: true });
84
+ }
85
+ this.#pendingTasks.clear();
86
+ }
87
+
88
+ /**
89
+ * Checks if the queue is completely idle (no running or pending tasks).
90
+ *
91
+ * @type {boolean}
92
+ */
93
+ get isIdle() {
94
+ return this.#runningCount === 0 && this.#pendingTasks.size === 0;
95
+ }
96
+
97
+ /**
98
+ * Returns current task count metrics.
99
+ *
100
+ * @type {{ running: number, pending: number, total: number }}
101
+ */
102
+ get size() {
103
+ return {
104
+ running: this.#runningCount,
105
+ pending: this.#pendingTasks.size,
106
+ total: this.#runningCount + this.#pendingTasks.size
107
+ };
108
+ }
109
+
110
+ /**
111
+ * Returns complete diagnostic statistics of the queue.
112
+ *
113
+ * @returns {{ running: number, pending: number, total: number, concurrency: number, maxPending: number, isIdle: boolean, pendingKeys: Array<string|number|symbol> }}
114
+ */
115
+ getStats() {
116
+ return {
117
+ running: this.#runningCount,
118
+ pending: this.#pendingTasks.size,
119
+ total: this.#runningCount + this.#pendingTasks.size,
120
+ concurrency: this.#concurrency,
121
+ maxPending: this.#maxPending,
122
+ isIdle: this.isIdle,
123
+ pendingKeys: Array.from(this.#pendingTasks.keys())
124
+ };
125
+ }
126
+
127
+ /**
128
+ * Returns a promise that resolves when the queue becomes completely idle.
129
+ *
130
+ * @returns {Promise<void>}
131
+ */
132
+ onIdle() {
133
+ if (this.isIdle) return Promise.resolve();
134
+ return new Promise(resolve => {
135
+ this.#idleResolvers.push(resolve);
136
+ });
137
+ }
138
+
139
+ async #drain() {
140
+ while (this.#runningCount < this.#concurrency && this.#pendingTasks.size > 0) {
141
+ const [key, item] = this.#pendingTasks.entries().next().value;
142
+ this.#pendingTasks.delete(key);
143
+ this.#runningCount++;
144
+
145
+ (async () => {
146
+ try {
147
+ const result = await item.taskFn();
148
+ item.resolve(result);
149
+ } catch (err) {
150
+ item.reject(err);
151
+ } finally {
152
+ this.#runningCount--;
153
+ if (this.isIdle) {
154
+ const resolvers = this.#idleResolvers.splice(0);
155
+ resolvers.forEach(r => r());
156
+ }
157
+ this.#drain();
158
+ }
159
+ })();
160
+ }
161
+ }
162
+ }