tiny-essentials 1.5.1 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -45,6 +45,7 @@ class TinyPromiseQueue {
45
45
  * @property {(value: any) => any} resolve - The resolve function from the Promise.
46
46
  * @property {(reason?: any) => any} reject - The reject function from the Promise.
47
47
  * @property {string|undefined} [id] - Optional identifier for the task.
48
+ * @property {string|null|undefined} [marker] - Optional marker for the task.
48
49
  * @property {number|null|undefined} [delay] - Optional delay (in ms) before the task is executed.
49
50
  */
50
51
 
@@ -66,15 +67,13 @@ class TinyPromiseQueue {
66
67
  }
67
68
 
68
69
  /**
69
- * Processes the next task in the queue if not already running.
70
- * Ensures tasks are executed in order, one at a time.
70
+ * Processes the a normal task.
71
+ *
72
+ * @param {QueuedTask} data
71
73
  *
72
74
  * @returns {Promise<void>}
73
75
  */
74
- async #processQueue() {
75
- if (this.#running || this.#queue.length === 0) return;
76
- this.#running = true;
77
- const data = this.#queue.shift();
76
+ async #normalProcessQueue(data) {
78
77
  if (
79
78
  data &&
80
79
  typeof data.task === 'function' &&
@@ -83,6 +82,14 @@ class TinyPromiseQueue {
83
82
  ) {
84
83
  const { task, resolve, reject, delay, id } = data;
85
84
  try {
85
+ if (id && this.#blacklist.has(id)) {
86
+ reject(new Error('The function was canceled on TinyPromiseQueue.'));
87
+ this.#blacklist.delete(id);
88
+ this.#running = false;
89
+ this.#processQueue();
90
+ return;
91
+ }
92
+
86
93
  if (delay && id) {
87
94
  await new Promise((resolveDelay) => {
88
95
  const timeoutId = setTimeout(() => {
@@ -93,14 +100,6 @@ class TinyPromiseQueue {
93
100
  });
94
101
  }
95
102
 
96
- if (id && this.#blacklist.has(id)) {
97
- reject(new Error('The function was canceled on TinyPromiseQueue.'));
98
- this.#blacklist.delete(id);
99
- this.#running = false;
100
- this.#processQueue();
101
- return;
102
- }
103
-
104
103
  const result = await task();
105
104
  resolve(result);
106
105
  } catch (error) {
@@ -112,6 +111,61 @@ class TinyPromiseQueue {
112
111
  }
113
112
  }
114
113
 
114
+ /**
115
+ * Processes a group task.
116
+ *
117
+ * @returns {Promise<void>}
118
+ */
119
+ async #groupProcessQueue() {
120
+ /** @type {Array<QueuedTask>} */
121
+ const grouped = [];
122
+ while (this.#queue.length && this.#queue[0]?.marker === 'POINT_MARKER') {
123
+ // @ts-ignore
124
+ grouped.push(this.#queue.shift());
125
+ }
126
+
127
+ if (grouped.length === 0) {
128
+ this.#running = false;
129
+ this.#processQueue();
130
+ return;
131
+ }
132
+
133
+ await Promise.all(
134
+ grouped.map(
135
+ ({ task, resolve, reject, id }) =>
136
+ new Promise(async (pResolve) => {
137
+ if (id && this.#blacklist.has(id)) {
138
+ this.#blacklist.delete(id);
139
+ reject(new Error('The function was canceled on TinyPromiseQueue.'));
140
+ pResolve(true);
141
+ return;
142
+ }
143
+ await task().then(resolve).catch(reject);
144
+ pResolve(true);
145
+ }),
146
+ ),
147
+ );
148
+
149
+ this.#running = false;
150
+ this.#processQueue();
151
+ }
152
+
153
+ /**
154
+ * Processes the next task in the queue if not already running.
155
+ * Ensures tasks are executed in order, one at a time.
156
+ *
157
+ * @returns {Promise<void>}
158
+ */
159
+ async #processQueue() {
160
+ if (this.#running || this.#queue.length === 0) return;
161
+ this.#running = true;
162
+ if (typeof this.#queue[0]?.marker !== 'string' || this.#queue[0]?.marker !== 'POINT_MARKER') {
163
+ const data = this.#queue.shift();
164
+ // @ts-ignore
165
+ this.#normalProcessQueue(data);
166
+ } else this.#groupProcessQueue();
167
+ }
168
+
115
169
  /**
116
170
  * Returns the index of a task by its ID.
117
171
  *
@@ -154,6 +208,22 @@ class TinyPromiseQueue {
154
208
  this.#queue.splice(toIndex, 0, item);
155
209
  }
156
210
 
211
+ /**
212
+ * Inserts a point in the queue where subsequent tasks will be grouped and executed together in a Promise.all.
213
+ * If the queue is currently empty, behaves like a regular promise.
214
+ *
215
+ * @param {(...args: any[]) => Promise<any>|Promise<any>} task A function that returns a Promise.
216
+ * @param {string} [id] Optional ID to identify the task in the queue.
217
+ * @returns {Promise<any>} A Promise that resolves or rejects with the result of the task once it's processed.
218
+ */
219
+ async enqueuePoint(task, id) {
220
+ if (!this.#running) return task();
221
+ return new Promise((resolve, reject) => {
222
+ this.#queue.push({ marker: 'POINT_MARKER', task, resolve, reject, id });
223
+ this.#processQueue();
224
+ });
225
+ }
226
+
157
227
  /**
158
228
  * Adds a new async task to the queue and ensures it runs in order after previous tasks.
159
229
  * Optionally, a delay can be added before the task is executed.
@@ -192,7 +262,8 @@ class TinyPromiseQueue {
192
262
 
193
263
  const index = this.getIndexById(id);
194
264
  if (index !== -1) {
195
- this.#queue.splice(index, 1);
265
+ const [removed] = this.#queue.splice(index, 1);
266
+ removed?.reject?.(new Error('The function was canceled on TinyPromiseQueue.'));
196
267
  cancelled = true;
197
268
  }
198
269
 
@@ -1 +1 @@
1
- (()=>{"use strict";var e={d:(t,i)=>{for(var s in i)e.o(i,s)&&!e.o(t,s)&&Object.defineProperty(t,s,{enumerable:!0,get:i[s]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)},t={};e.d(t,{TinyPromiseQueue:()=>i});const i=class{#e=[];#t=!1;#i={};#s=new Set;isRunning(){return this.#t}async#u(){if(this.#t||0===this.#e.length)return;this.#t=!0;const e=this.#e.shift();if(e&&"function"==typeof e.task&&"function"==typeof e.resolve&&"function"==typeof e.reject){const{task:t,resolve:i,reject:s,delay:u,id:n}=e;try{if(u&&n&&await new Promise((e=>{const t=setTimeout((()=>{delete this.#i[n],e(null)}),u);this.#i[n]=t})),n&&this.#s.has(n))return s(new Error("The function was canceled on TinyPromiseQueue.")),this.#s.delete(n),this.#t=!1,void this.#u();i(await t())}catch(e){s(e)}finally{this.#t=!1,this.#u()}}}getIndexById(e){return this.#e.findIndex((t=>t.id===e))}getQueuedIds(){return this.#e.map(((e,t)=>({index:t,id:e.id}))).filter((e=>"string"==typeof e.id))}reorderQueue(e,t){if("number"!=typeof e||"number"!=typeof t||e<0||t<0||e>=this.#e.length||t>=this.#e.length)return;const[i]=this.#e.splice(e,1);this.#e.splice(t,0,i)}enqueue(e,t,i){return new Promise(((s,u)=>{this.#e.push({task:e,resolve:s,reject:u,id:i,delay:t}),this.#u()}))}cancelTask(e){if(!e)return!1;let t=!1;e in this.#i&&(clearTimeout(this.#i[e]),delete this.#i[e],t=!0);const i=this.getIndexById(e);return-1!==i&&(this.#e.splice(i,1),t=!0),t&&this.#s.add(e),t}};window.TinyPromiseQueue=t.TinyPromiseQueue})();
1
+ (()=>{"use strict";var e={d:(t,s)=>{for(var i in s)e.o(s,i)&&!e.o(t,i)&&Object.defineProperty(t,i,{enumerable:!0,get:s[i]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)},t={};e.d(t,{TinyPromiseQueue:()=>s});const s=class{#e=[];#t=!1;#s={};#i=new Set;isRunning(){return this.#t}async#u(e){if(e&&"function"==typeof e.task&&"function"==typeof e.resolve&&"function"==typeof e.reject){const{task:t,resolve:s,reject:i,delay:u,id:n}=e;try{if(n&&this.#i.has(n))return i(new Error("The function was canceled on TinyPromiseQueue.")),this.#i.delete(n),this.#t=!1,void this.#n();u&&n&&await new Promise((e=>{const t=setTimeout((()=>{delete this.#s[n],e(null)}),u);this.#s[n]=t})),s(await t())}catch(e){i(e)}finally{this.#t=!1,this.#n()}}}async#r(){const e=[];for(;this.#e.length&&"POINT_MARKER"===this.#e[0]?.marker;)e.push(this.#e.shift());if(0===e.length)return this.#t=!1,void this.#n();await Promise.all(e.map((({task:e,resolve:t,reject:s,id:i})=>new Promise((async u=>{if(i&&this.#i.has(i))return this.#i.delete(i),s(new Error("The function was canceled on TinyPromiseQueue.")),void u(!0);await e().then(t).catch(s),u(!0)}))))),this.#t=!1,this.#n()}async#n(){if(!this.#t&&0!==this.#e.length)if(this.#t=!0,"string"!=typeof this.#e[0]?.marker||"POINT_MARKER"!==this.#e[0]?.marker){const e=this.#e.shift();this.#u(e)}else this.#r()}getIndexById(e){return this.#e.findIndex((t=>t.id===e))}getQueuedIds(){return this.#e.map(((e,t)=>({index:t,id:e.id}))).filter((e=>"string"==typeof e.id))}reorderQueue(e,t){if("number"!=typeof e||"number"!=typeof t||e<0||t<0||e>=this.#e.length||t>=this.#e.length)return;const[s]=this.#e.splice(e,1);this.#e.splice(t,0,s)}async enqueuePoint(e,t){return this.#t?new Promise(((s,i)=>{this.#e.push({marker:"POINT_MARKER",task:e,resolve:s,reject:i,id:t}),this.#n()})):e()}enqueue(e,t,s){return new Promise(((i,u)=>{this.#e.push({task:e,resolve:i,reject:u,id:s,delay:t}),this.#n()}))}cancelTask(e){if(!e)return!1;let t=!1;e in this.#s&&(clearTimeout(this.#s[e]),delete this.#s[e],t=!0);const s=this.getIndexById(e);if(-1!==s){const[e]=this.#e.splice(s,1);e?.reject?.(new Error("The function was canceled on TinyPromiseQueue.")),t=!0}return t&&this.#i.add(e),t}};window.TinyPromiseQueue=t.TinyPromiseQueue})();
@@ -0,0 +1,4 @@
1
+ body .detect-made-by-ai .made-by-ai {
2
+ color: yellow;
3
+ text-shadow: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000;
4
+ }
@@ -0,0 +1 @@
1
+ body .detect-made-by-ai .made-by-ai{color:#ff0;text-shadow:-1px -1px 0 #000,1px -1px 0 #000,-1px 1px 0 #000,1px 1px 0 #000}
@@ -23,5 +23,6 @@ exports.reorderObjTypeOrder = objFilter.reorderObjTypeOrder;
23
23
  exports.getAge = simpleMath.getAge;
24
24
  exports.getSimplePerc = simpleMath.getSimplePerc;
25
25
  exports.ruleOfThree = simpleMath.ruleOfThree;
26
+ exports.addAiMarkerShortcut = text.addAiMarkerShortcut;
26
27
  exports.toTitleCase = text.toTitleCase;
27
28
  exports.toTitleCaseLowerFirst = text.toTitleCaseLowerFirst;
@@ -1,3 +1,4 @@
1
+ import { addAiMarkerShortcut } from './text.mjs';
1
2
  import { extendObjType } from './objFilter.mjs';
2
3
  import { reorderObjTypeOrder } from './objFilter.mjs';
3
4
  import { cloneObjTypeOrder } from './objFilter.mjs';
@@ -14,5 +15,5 @@ import { getTimeDuration } from './clock.mjs';
14
15
  import { shuffleArray } from './array.mjs';
15
16
  import { toTitleCase } from './text.mjs';
16
17
  import { toTitleCaseLowerFirst } from './text.mjs';
17
- export { extendObjType, reorderObjTypeOrder, cloneObjTypeOrder, countObj, objType, ruleOfThree, getSimplePerc, asyncReplace, getAge, formatCustomTimer, formatDayTimer, formatTimer, getTimeDuration, shuffleArray, toTitleCase, toTitleCaseLowerFirst };
18
+ export { addAiMarkerShortcut, extendObjType, reorderObjTypeOrder, cloneObjTypeOrder, countObj, objType, ruleOfThree, getSimplePerc, asyncReplace, getAge, formatCustomTimer, formatDayTimer, formatTimer, getTimeDuration, shuffleArray, toTitleCase, toTitleCaseLowerFirst };
18
19
  //# sourceMappingURL=index.d.mts.map
@@ -3,5 +3,5 @@ import { shuffleArray } from './array.mjs';
3
3
  import { formatCustomTimer, formatDayTimer, formatTimer, getTimeDuration } from './clock.mjs';
4
4
  import { countObj, extendObjType, reorderObjTypeOrder, cloneObjTypeOrder, objType, } from './objFilter.mjs';
5
5
  import { getAge, getSimplePerc, ruleOfThree } from './simpleMath.mjs';
6
- import { toTitleCase, toTitleCaseLowerFirst } from './text.mjs';
7
- export { extendObjType, reorderObjTypeOrder, cloneObjTypeOrder, countObj, objType, ruleOfThree, getSimplePerc, asyncReplace, getAge, formatCustomTimer, formatDayTimer, formatTimer, getTimeDuration, shuffleArray, toTitleCase, toTitleCaseLowerFirst, };
6
+ import { addAiMarkerShortcut, toTitleCase, toTitleCaseLowerFirst } from './text.mjs';
7
+ export { addAiMarkerShortcut, extendObjType, reorderObjTypeOrder, cloneObjTypeOrder, countObj, objType, ruleOfThree, getSimplePerc, asyncReplace, getAge, formatCustomTimer, formatDayTimer, formatTimer, getTimeDuration, shuffleArray, toTitleCase, toTitleCaseLowerFirst, };
@@ -30,5 +30,64 @@ function toTitleCaseLowerFirst(str) {
30
30
  return titleCased.charAt(0).toLowerCase() + titleCased.slice(1);
31
31
  }
32
32
 
33
+ /**
34
+ * Enables a keyboard shortcut to toggle a CSS class on the document body.
35
+ *
36
+ * This function listens for a specific key combination: `Ctrl + Alt + [key]`.
37
+ * When triggered, it prevents the default behavior and toggles the
38
+ * `detect-made-by-ai` class on the `<body>`, which can be used to apply visual
39
+ * indicators or filters on AI-generated content.
40
+ *
41
+ * If executed outside of a browser environment (e.g., in Node.js), the function logs an error and exits.
42
+ * If the `<body>` is not available at the moment the shortcut is triggered, a warning is logged.
43
+ *
44
+ * @param {string} [key='a'] - The lowercase character key to be used in combination with Ctrl and Alt.
45
+ */
46
+ function addAiMarkerShortcut(key = 'a') {
47
+ if (typeof HTMLElement === 'undefined') {
48
+ console.error(
49
+ '[AiMarkerShortcut] Environment does not support the DOM. This function must be run in a browser.',
50
+ );
51
+ return;
52
+ }
53
+ document.addEventListener('keydown', function (event) {
54
+ if (event.ctrlKey && event.altKey && event.key.toLowerCase() === key) {
55
+ event.preventDefault(); // Prevent any default behavior
56
+ if (!document.body) {
57
+ console.warn(
58
+ '[AiMarkerShortcut] <body> element not found. Cannot toggle class. Ensure the DOM is fully loaded when using the shortcut.',
59
+ );
60
+ return;
61
+ }
62
+ document.body.classList.toggle('detect-made-by-ai');
63
+ }
64
+ });
65
+ }
66
+
67
+ /*
68
+ import { useEffect } from "react";
69
+
70
+ function KeyPressHandler() {
71
+ useEffect(() => {
72
+ const handleKeyDown = (event) => {
73
+ if (event.ctrlKey && event.altKey && event.key.toLowerCase() === "a") {
74
+ event.preventDefault();
75
+ document.body.classList.toggle("detect-made-by-ai");
76
+ }
77
+ };
78
+
79
+ document.addEventListener("keydown", handleKeyDown);
80
+ return () => {
81
+ document.removeEventListener("keydown", handleKeyDown);
82
+ };
83
+ }, []);
84
+
85
+ return null;
86
+ }
87
+
88
+ export default KeyPressHandler;
89
+ */
90
+
91
+ exports.addAiMarkerShortcut = addAiMarkerShortcut;
33
92
  exports.toTitleCase = toTitleCase;
34
93
  exports.toTitleCaseLowerFirst = toTitleCaseLowerFirst;
@@ -18,4 +18,18 @@ export function toTitleCase(str: string): string;
18
18
  * @returns {string} The string converted to title case with the first letter in lowercase.
19
19
  */
20
20
  export function toTitleCaseLowerFirst(str: string): string;
21
+ /**
22
+ * Enables a keyboard shortcut to toggle a CSS class on the document body.
23
+ *
24
+ * This function listens for a specific key combination: `Ctrl + Alt + [key]`.
25
+ * When triggered, it prevents the default behavior and toggles the
26
+ * `detect-made-by-ai` class on the `<body>`, which can be used to apply visual
27
+ * indicators or filters on AI-generated content.
28
+ *
29
+ * If executed outside of a browser environment (e.g., in Node.js), the function logs an error and exits.
30
+ * If the `<body>` is not available at the moment the shortcut is triggered, a warning is logged.
31
+ *
32
+ * @param {string} [key='a'] - The lowercase character key to be used in combination with Ctrl and Alt.
33
+ */
34
+ export function addAiMarkerShortcut(key?: string): void;
21
35
  //# sourceMappingURL=text.d.mts.map
@@ -23,3 +23,55 @@ export function toTitleCaseLowerFirst(str) {
23
23
  const titleCased = str.replace(/\w\S*/g, (txt) => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase());
24
24
  return titleCased.charAt(0).toLowerCase() + titleCased.slice(1);
25
25
  }
26
+ /**
27
+ * Enables a keyboard shortcut to toggle a CSS class on the document body.
28
+ *
29
+ * This function listens for a specific key combination: `Ctrl + Alt + [key]`.
30
+ * When triggered, it prevents the default behavior and toggles the
31
+ * `detect-made-by-ai` class on the `<body>`, which can be used to apply visual
32
+ * indicators or filters on AI-generated content.
33
+ *
34
+ * If executed outside of a browser environment (e.g., in Node.js), the function logs an error and exits.
35
+ * If the `<body>` is not available at the moment the shortcut is triggered, a warning is logged.
36
+ *
37
+ * @param {string} [key='a'] - The lowercase character key to be used in combination with Ctrl and Alt.
38
+ */
39
+ export function addAiMarkerShortcut(key = 'a') {
40
+ if (typeof HTMLElement === 'undefined') {
41
+ console.error('[AiMarkerShortcut] Environment does not support the DOM. This function must be run in a browser.');
42
+ return;
43
+ }
44
+ document.addEventListener('keydown', function (event) {
45
+ if (event.ctrlKey && event.altKey && event.key.toLowerCase() === key) {
46
+ event.preventDefault(); // Prevent any default behavior
47
+ if (!document.body) {
48
+ console.warn('[AiMarkerShortcut] <body> element not found. Cannot toggle class. Ensure the DOM is fully loaded when using the shortcut.');
49
+ return;
50
+ }
51
+ document.body.classList.toggle('detect-made-by-ai');
52
+ }
53
+ });
54
+ }
55
+ /*
56
+ import { useEffect } from "react";
57
+
58
+ function KeyPressHandler() {
59
+ useEffect(() => {
60
+ const handleKeyDown = (event) => {
61
+ if (event.ctrlKey && event.altKey && event.key.toLowerCase() === "a") {
62
+ event.preventDefault();
63
+ document.body.classList.toggle("detect-made-by-ai");
64
+ }
65
+ };
66
+
67
+ document.addEventListener("keydown", handleKeyDown);
68
+ return () => {
69
+ document.removeEventListener("keydown", handleKeyDown);
70
+ };
71
+ }, []);
72
+
73
+ return null;
74
+ }
75
+
76
+ export default KeyPressHandler;
77
+ */
package/dist/v1/index.cjs CHANGED
@@ -27,6 +27,7 @@ exports.reorderObjTypeOrder = objFilter.reorderObjTypeOrder;
27
27
  exports.getAge = simpleMath.getAge;
28
28
  exports.getSimplePerc = simpleMath.getSimplePerc;
29
29
  exports.ruleOfThree = simpleMath.ruleOfThree;
30
+ exports.addAiMarkerShortcut = text.addAiMarkerShortcut;
30
31
  exports.toTitleCase = text.toTitleCase;
31
32
  exports.toTitleCaseLowerFirst = text.toTitleCaseLowerFirst;
32
33
  exports.TinyPromiseQueue = TinyPromiseQueue;
@@ -1,5 +1,6 @@
1
1
  import TinyPromiseQueue from './libs/TinyPromiseQueue.mjs';
2
2
  import TinyLevelUp from '../legacy/libs/userLevel.mjs';
3
+ import { addAiMarkerShortcut } from './basics/text.mjs';
3
4
  import { extendObjType } from './basics/objFilter.mjs';
4
5
  import { reorderObjTypeOrder } from './basics/objFilter.mjs';
5
6
  import { cloneObjTypeOrder } from './basics/objFilter.mjs';
@@ -17,5 +18,5 @@ import { getTimeDuration } from './basics/clock.mjs';
17
18
  import { shuffleArray } from './basics/array.mjs';
18
19
  import { toTitleCase } from './basics/text.mjs';
19
20
  import { toTitleCaseLowerFirst } from './basics/text.mjs';
20
- export { TinyPromiseQueue, TinyLevelUp, extendObjType, reorderObjTypeOrder, cloneObjTypeOrder, countObj, checkObj, objType, ruleOfThree, getSimplePerc, asyncReplace, getAge, formatCustomTimer, formatDayTimer, formatTimer, getTimeDuration, shuffleArray, toTitleCase, toTitleCaseLowerFirst };
21
+ export { TinyPromiseQueue, TinyLevelUp, addAiMarkerShortcut, extendObjType, reorderObjTypeOrder, cloneObjTypeOrder, countObj, checkObj, objType, ruleOfThree, getSimplePerc, asyncReplace, getAge, formatCustomTimer, formatDayTimer, formatTimer, getTimeDuration, shuffleArray, toTitleCase, toTitleCaseLowerFirst };
21
22
  //# sourceMappingURL=index.d.mts.map
package/dist/v1/index.mjs CHANGED
@@ -4,6 +4,6 @@ import { shuffleArray } from './basics/array.mjs';
4
4
  import { formatCustomTimer, formatDayTimer, formatTimer, getTimeDuration, } from './basics/clock.mjs';
5
5
  import { countObj, extendObjType, reorderObjTypeOrder, cloneObjTypeOrder, objType, checkObj, } from './basics/objFilter.mjs';
6
6
  import { getAge, getSimplePerc, ruleOfThree } from './basics/simpleMath.mjs';
7
- import { toTitleCase, toTitleCaseLowerFirst } from './basics/text.mjs';
7
+ import { addAiMarkerShortcut, toTitleCase, toTitleCaseLowerFirst } from './basics/text.mjs';
8
8
  import TinyPromiseQueue from './libs/TinyPromiseQueue.mjs';
9
- export { TinyPromiseQueue, TinyLevelUp, extendObjType, reorderObjTypeOrder, cloneObjTypeOrder, countObj, checkObj, objType, ruleOfThree, getSimplePerc, asyncReplace, getAge, formatCustomTimer, formatDayTimer, formatTimer, getTimeDuration, shuffleArray, toTitleCase, toTitleCaseLowerFirst, };
9
+ export { TinyPromiseQueue, TinyLevelUp, addAiMarkerShortcut, extendObjType, reorderObjTypeOrder, cloneObjTypeOrder, countObj, checkObj, objType, ruleOfThree, getSimplePerc, asyncReplace, getAge, formatCustomTimer, formatDayTimer, formatTimer, getTimeDuration, shuffleArray, toTitleCase, toTitleCaseLowerFirst, };
@@ -15,6 +15,7 @@ class TinyPromiseQueue {
15
15
  * @property {(value: any) => any} resolve - The resolve function from the Promise.
16
16
  * @property {(reason?: any) => any} reject - The reject function from the Promise.
17
17
  * @property {string|undefined} [id] - Optional identifier for the task.
18
+ * @property {string|null|undefined} [marker] - Optional marker for the task.
18
19
  * @property {number|null|undefined} [delay] - Optional delay (in ms) before the task is executed.
19
20
  */
20
21
 
@@ -36,15 +37,13 @@ class TinyPromiseQueue {
36
37
  }
37
38
 
38
39
  /**
39
- * Processes the next task in the queue if not already running.
40
- * Ensures tasks are executed in order, one at a time.
40
+ * Processes the a normal task.
41
+ *
42
+ * @param {QueuedTask} data
41
43
  *
42
44
  * @returns {Promise<void>}
43
45
  */
44
- async #processQueue() {
45
- if (this.#running || this.#queue.length === 0) return;
46
- this.#running = true;
47
- const data = this.#queue.shift();
46
+ async #normalProcessQueue(data) {
48
47
  if (
49
48
  data &&
50
49
  typeof data.task === 'function' &&
@@ -53,6 +52,14 @@ class TinyPromiseQueue {
53
52
  ) {
54
53
  const { task, resolve, reject, delay, id } = data;
55
54
  try {
55
+ if (id && this.#blacklist.has(id)) {
56
+ reject(new Error('The function was canceled on TinyPromiseQueue.'));
57
+ this.#blacklist.delete(id);
58
+ this.#running = false;
59
+ this.#processQueue();
60
+ return;
61
+ }
62
+
56
63
  if (delay && id) {
57
64
  await new Promise((resolveDelay) => {
58
65
  const timeoutId = setTimeout(() => {
@@ -63,14 +70,6 @@ class TinyPromiseQueue {
63
70
  });
64
71
  }
65
72
 
66
- if (id && this.#blacklist.has(id)) {
67
- reject(new Error('The function was canceled on TinyPromiseQueue.'));
68
- this.#blacklist.delete(id);
69
- this.#running = false;
70
- this.#processQueue();
71
- return;
72
- }
73
-
74
73
  const result = await task();
75
74
  resolve(result);
76
75
  } catch (error) {
@@ -82,6 +81,61 @@ class TinyPromiseQueue {
82
81
  }
83
82
  }
84
83
 
84
+ /**
85
+ * Processes a group task.
86
+ *
87
+ * @returns {Promise<void>}
88
+ */
89
+ async #groupProcessQueue() {
90
+ /** @type {Array<QueuedTask>} */
91
+ const grouped = [];
92
+ while (this.#queue.length && this.#queue[0]?.marker === 'POINT_MARKER') {
93
+ // @ts-ignore
94
+ grouped.push(this.#queue.shift());
95
+ }
96
+
97
+ if (grouped.length === 0) {
98
+ this.#running = false;
99
+ this.#processQueue();
100
+ return;
101
+ }
102
+
103
+ await Promise.all(
104
+ grouped.map(
105
+ ({ task, resolve, reject, id }) =>
106
+ new Promise(async (pResolve) => {
107
+ if (id && this.#blacklist.has(id)) {
108
+ this.#blacklist.delete(id);
109
+ reject(new Error('The function was canceled on TinyPromiseQueue.'));
110
+ pResolve(true);
111
+ return;
112
+ }
113
+ await task().then(resolve).catch(reject);
114
+ pResolve(true);
115
+ }),
116
+ ),
117
+ );
118
+
119
+ this.#running = false;
120
+ this.#processQueue();
121
+ }
122
+
123
+ /**
124
+ * Processes the next task in the queue if not already running.
125
+ * Ensures tasks are executed in order, one at a time.
126
+ *
127
+ * @returns {Promise<void>}
128
+ */
129
+ async #processQueue() {
130
+ if (this.#running || this.#queue.length === 0) return;
131
+ this.#running = true;
132
+ if (typeof this.#queue[0]?.marker !== 'string' || this.#queue[0]?.marker !== 'POINT_MARKER') {
133
+ const data = this.#queue.shift();
134
+ // @ts-ignore
135
+ this.#normalProcessQueue(data);
136
+ } else this.#groupProcessQueue();
137
+ }
138
+
85
139
  /**
86
140
  * Returns the index of a task by its ID.
87
141
  *
@@ -124,6 +178,22 @@ class TinyPromiseQueue {
124
178
  this.#queue.splice(toIndex, 0, item);
125
179
  }
126
180
 
181
+ /**
182
+ * Inserts a point in the queue where subsequent tasks will be grouped and executed together in a Promise.all.
183
+ * If the queue is currently empty, behaves like a regular promise.
184
+ *
185
+ * @param {(...args: any[]) => Promise<any>|Promise<any>} task A function that returns a Promise.
186
+ * @param {string} [id] Optional ID to identify the task in the queue.
187
+ * @returns {Promise<any>} A Promise that resolves or rejects with the result of the task once it's processed.
188
+ */
189
+ async enqueuePoint(task, id) {
190
+ if (!this.#running) return task();
191
+ return new Promise((resolve, reject) => {
192
+ this.#queue.push({ marker: 'POINT_MARKER', task, resolve, reject, id });
193
+ this.#processQueue();
194
+ });
195
+ }
196
+
127
197
  /**
128
198
  * Adds a new async task to the queue and ensures it runs in order after previous tasks.
129
199
  * Optionally, a delay can be added before the task is executed.
@@ -162,7 +232,8 @@ class TinyPromiseQueue {
162
232
 
163
233
  const index = this.getIndexById(id);
164
234
  if (index !== -1) {
165
- this.#queue.splice(index, 1);
235
+ const [removed] = this.#queue.splice(index, 1);
236
+ removed?.reject?.(new Error('The function was canceled on TinyPromiseQueue.'));
166
237
  cancelled = true;
167
238
  }
168
239
 
@@ -37,6 +37,15 @@ declare class TinyPromiseQueue {
37
37
  * @param {number} toIndex The index where the task should be placed.
38
38
  */
39
39
  reorderQueue(fromIndex: number, toIndex: number): void;
40
+ /**
41
+ * Inserts a point in the queue where subsequent tasks will be grouped and executed together in a Promise.all.
42
+ * If the queue is currently empty, behaves like a regular promise.
43
+ *
44
+ * @param {(...args: any[]) => Promise<any>|Promise<any>} task A function that returns a Promise.
45
+ * @param {string} [id] Optional ID to identify the task in the queue.
46
+ * @returns {Promise<any>} A Promise that resolves or rejects with the result of the task once it's processed.
47
+ */
48
+ enqueuePoint(task: (...args: any[]) => Promise<any> | Promise<any>, id?: string): Promise<any>;
40
49
  /**
41
50
  * Adds a new async task to the queue and ensures it runs in order after previous tasks.
42
51
  * Optionally, a delay can be added before the task is executed.