@oscarpalmer/atoms 0.131.0 → 0.133.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/atoms.full.js +234 -1
- package/dist/index.js +3 -1
- package/dist/promise.js +66 -0
- package/dist/queue.js +169 -0
- package/package.json +52 -37
- package/src/index.ts +2 -0
- package/src/models.ts +5 -0
- package/src/promise.ts +175 -0
- package/src/queue.ts +308 -0
- package/types/index.d.ts +2 -0
- package/types/models.d.ts +4 -0
- package/types/promise.d.ts +40 -0
- package/types/queue.d.ts +87 -0
package/dist/atoms.full.js
CHANGED
|
@@ -1971,6 +1971,239 @@ function round(value, decimals) {
|
|
|
1971
1971
|
function sum(array, key) {
|
|
1972
1972
|
return getAggregated("sum", array, key);
|
|
1973
1973
|
}
|
|
1974
|
+
var PromiseTimeoutError = class extends Error {
|
|
1975
|
+
constructor() {
|
|
1976
|
+
super(MESSAGE_TIMEOUT);
|
|
1977
|
+
this.name = NAME;
|
|
1978
|
+
}
|
|
1979
|
+
};
|
|
1980
|
+
/**
|
|
1981
|
+
* Create a delayed promise that resolves after a certain amount of time
|
|
1982
|
+
* @param time How long to wait for _(in milliseconds; defaults to screen refresh rate)_
|
|
1983
|
+
* @returns A promise that resolves after the delay
|
|
1984
|
+
*/
|
|
1985
|
+
function delay(time) {
|
|
1986
|
+
return new Promise((resolve) => setTimeout(resolve, getNumberOrDefault$1(time)));
|
|
1987
|
+
}
|
|
1988
|
+
function getBooleanOrDefault$1(value, defaultValue) {
|
|
1989
|
+
return typeof value === "boolean" ? value : defaultValue;
|
|
1990
|
+
}
|
|
1991
|
+
function getNumberOrDefault$1(value) {
|
|
1992
|
+
return typeof value === "number" ? value : 0;
|
|
1993
|
+
}
|
|
1994
|
+
async function promises(items, eager) {
|
|
1995
|
+
const actual = items.filter((item) => item instanceof Promise);
|
|
1996
|
+
if (actual.length === 0) return actual;
|
|
1997
|
+
const isEager = getBooleanOrDefault$1(eager, false);
|
|
1998
|
+
const { length } = actual;
|
|
1999
|
+
const data = {
|
|
2000
|
+
last: length - 1,
|
|
2001
|
+
result: []
|
|
2002
|
+
};
|
|
2003
|
+
let handlers;
|
|
2004
|
+
return new Promise((resolve, reject) => {
|
|
2005
|
+
handlers = {
|
|
2006
|
+
reject,
|
|
2007
|
+
resolve
|
|
2008
|
+
};
|
|
2009
|
+
for (let index = 0; index < length; index += 1) actual[index].then((value) => resolveResult(index, data, handlers, value, isEager)).catch((reason) => rejectResult(index, data, handlers, reason, isEager));
|
|
2010
|
+
});
|
|
2011
|
+
}
|
|
2012
|
+
function rejectResult(index, data, handlers, reason, eager) {
|
|
2013
|
+
if (eager) handlers.reject(reason);
|
|
2014
|
+
else {
|
|
2015
|
+
data.result[index] = {
|
|
2016
|
+
status: TYPE_REJECTED,
|
|
2017
|
+
reason
|
|
2018
|
+
};
|
|
2019
|
+
if (index === data.last) handlers.resolve(data.result);
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
function resolveResult(index, data, handlers, value, eager) {
|
|
2023
|
+
data.result[index] = eager ? value : {
|
|
2024
|
+
status: TYPE_FULFILLED,
|
|
2025
|
+
value
|
|
2026
|
+
};
|
|
2027
|
+
if (index === data.last) handlers.resolve(data.result);
|
|
2028
|
+
}
|
|
2029
|
+
function timed(promise, timeout) {
|
|
2030
|
+
if (!(promise instanceof Promise)) throw new TypeError(MESSAGE_EXPECTATION);
|
|
2031
|
+
if (getNumberOrDefault$1(timeout) <= 0) return promise;
|
|
2032
|
+
return Promise.race([promise, new Promise((_, reject) => setTimeout(() => reject(new PromiseTimeoutError()), timeout))]);
|
|
2033
|
+
}
|
|
2034
|
+
const MESSAGE_EXPECTATION = "Timed function expected a Promise";
|
|
2035
|
+
const MESSAGE_TIMEOUT = "Promise timed out";
|
|
2036
|
+
const NAME = "PromiseTimeoutError";
|
|
2037
|
+
const TYPE_FULFILLED = "fulfilled";
|
|
2038
|
+
const TYPE_REJECTED = "rejected";
|
|
2039
|
+
var Queue = class {
|
|
2040
|
+
#callback;
|
|
2041
|
+
#handled = [];
|
|
2042
|
+
#id = 0;
|
|
2043
|
+
#items = [];
|
|
2044
|
+
#options;
|
|
2045
|
+
#paused;
|
|
2046
|
+
#runners = 0;
|
|
2047
|
+
/**
|
|
2048
|
+
* Is the queue active?
|
|
2049
|
+
*/
|
|
2050
|
+
get active() {
|
|
2051
|
+
return this.#runners > 0;
|
|
2052
|
+
}
|
|
2053
|
+
/**
|
|
2054
|
+
* Is the queue empty?
|
|
2055
|
+
*/
|
|
2056
|
+
get empty() {
|
|
2057
|
+
return this.#items.length === 0;
|
|
2058
|
+
}
|
|
2059
|
+
/**
|
|
2060
|
+
* Is the queue full?
|
|
2061
|
+
*/
|
|
2062
|
+
get full() {
|
|
2063
|
+
return this.#options.maximum > 0 && this.#items.length >= this.#options.maximum;
|
|
2064
|
+
}
|
|
2065
|
+
/**
|
|
2066
|
+
* Is the queue paused?
|
|
2067
|
+
*/
|
|
2068
|
+
get paused() {
|
|
2069
|
+
return this.#paused;
|
|
2070
|
+
}
|
|
2071
|
+
/**
|
|
2072
|
+
* Number of items in the queue
|
|
2073
|
+
*/
|
|
2074
|
+
get size() {
|
|
2075
|
+
return this.#items.length;
|
|
2076
|
+
}
|
|
2077
|
+
constructor(callback, options) {
|
|
2078
|
+
this.#callback = callback;
|
|
2079
|
+
this.#options = options;
|
|
2080
|
+
this.#paused = !options.autostart;
|
|
2081
|
+
}
|
|
2082
|
+
/**
|
|
2083
|
+
* Add an item to the queue
|
|
2084
|
+
* @param parameters Parameters to use when item runs
|
|
2085
|
+
* @returns Queued item
|
|
2086
|
+
*/
|
|
2087
|
+
add(...parameters) {
|
|
2088
|
+
if (this.full) throw new QueueError(MESSAGE_MAXIMUM);
|
|
2089
|
+
let rejector;
|
|
2090
|
+
let resolver;
|
|
2091
|
+
const id = this.#identify();
|
|
2092
|
+
const promise = new Promise((resolve, reject) => {
|
|
2093
|
+
rejector = reject;
|
|
2094
|
+
resolver = resolve;
|
|
2095
|
+
});
|
|
2096
|
+
this.#items.push({
|
|
2097
|
+
id,
|
|
2098
|
+
parameters,
|
|
2099
|
+
promise,
|
|
2100
|
+
reject: rejector,
|
|
2101
|
+
resolve: resolver
|
|
2102
|
+
});
|
|
2103
|
+
if (this.#options.autostart) this.#run();
|
|
2104
|
+
return {
|
|
2105
|
+
id,
|
|
2106
|
+
promise
|
|
2107
|
+
};
|
|
2108
|
+
}
|
|
2109
|
+
/**
|
|
2110
|
+
* Remove and reject all items in the queue
|
|
2111
|
+
*/
|
|
2112
|
+
clear() {
|
|
2113
|
+
const items = this.#items.splice(0);
|
|
2114
|
+
const { length } = items;
|
|
2115
|
+
for (let index = 0; index < length; index += 1) items[index].reject(new QueueError(MESSAGE_CLEAR));
|
|
2116
|
+
}
|
|
2117
|
+
/**
|
|
2118
|
+
* Pause the queue
|
|
2119
|
+
*
|
|
2120
|
+
* - Currently running items will not be stopped
|
|
2121
|
+
* - New added items will not run until the queue is resumed
|
|
2122
|
+
*/
|
|
2123
|
+
pause() {
|
|
2124
|
+
this.#paused = true;
|
|
2125
|
+
}
|
|
2126
|
+
/**
|
|
2127
|
+
* Remove and reject a specific item in the queue
|
|
2128
|
+
* @param id ID of queued item
|
|
2129
|
+
*/
|
|
2130
|
+
remove(id) {
|
|
2131
|
+
const index = this.#items.findIndex((item) => item.id === id);
|
|
2132
|
+
if (index > -1) {
|
|
2133
|
+
const [item] = this.#items.splice(index, 1);
|
|
2134
|
+
item.reject(new QueueError(MESSAGE_REMOVE));
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2137
|
+
/**
|
|
2138
|
+
* Resume the queue
|
|
2139
|
+
*/
|
|
2140
|
+
resume() {
|
|
2141
|
+
if (this.#paused) {
|
|
2142
|
+
const handled = this.#handled.splice(0);
|
|
2143
|
+
const { length } = handled;
|
|
2144
|
+
for (let index = 0; index < length; index += 1) handled[index]();
|
|
2145
|
+
}
|
|
2146
|
+
this.#paused = false;
|
|
2147
|
+
const length = Math.min(this.#options.concurrency, this.#items.length);
|
|
2148
|
+
for (let index = 0; index < length; index += 1) this.#run();
|
|
2149
|
+
}
|
|
2150
|
+
#identify() {
|
|
2151
|
+
this.#id += 1;
|
|
2152
|
+
return this.#id;
|
|
2153
|
+
}
|
|
2154
|
+
async #run() {
|
|
2155
|
+
if (this.#paused || this.#runners >= this.#options.concurrency) return;
|
|
2156
|
+
this.#runners += 1;
|
|
2157
|
+
let item = this.#items.shift();
|
|
2158
|
+
while (item != null) {
|
|
2159
|
+
let handler;
|
|
2160
|
+
let result;
|
|
2161
|
+
try {
|
|
2162
|
+
result = await this.#callback(...item.parameters);
|
|
2163
|
+
handler = item.resolve;
|
|
2164
|
+
} catch (thrown) {
|
|
2165
|
+
result = thrown;
|
|
2166
|
+
handler = item.reject;
|
|
2167
|
+
}
|
|
2168
|
+
if (this.#paused) {
|
|
2169
|
+
this.#handled.push(() => handler(result));
|
|
2170
|
+
break;
|
|
2171
|
+
}
|
|
2172
|
+
handler(result);
|
|
2173
|
+
item = this.#items.shift();
|
|
2174
|
+
}
|
|
2175
|
+
this.#runners -= 1;
|
|
2176
|
+
}
|
|
2177
|
+
};
|
|
2178
|
+
var QueueError = class extends Error {
|
|
2179
|
+
constructor(message) {
|
|
2180
|
+
super(message);
|
|
2181
|
+
this.name = ERROR_NAME;
|
|
2182
|
+
}
|
|
2183
|
+
};
|
|
2184
|
+
function getBooleanOrDefault(value, defaultValue) {
|
|
2185
|
+
return typeof value === "boolean" ? value : defaultValue;
|
|
2186
|
+
}
|
|
2187
|
+
function getNumberOrDefault(value, defaultValue) {
|
|
2188
|
+
return typeof value === "number" && value > 0 ? Math.floor(value) : defaultValue;
|
|
2189
|
+
}
|
|
2190
|
+
function getOptions(input) {
|
|
2191
|
+
const options = typeof input === "object" && input != null ? input : {};
|
|
2192
|
+
return {
|
|
2193
|
+
autostart: getBooleanOrDefault(options.autostart, true),
|
|
2194
|
+
concurrency: getNumberOrDefault(options.concurrency, 1),
|
|
2195
|
+
maximum: getNumberOrDefault(options.maximum, 0)
|
|
2196
|
+
};
|
|
2197
|
+
}
|
|
2198
|
+
function queue(callback, options) {
|
|
2199
|
+
if (typeof callback !== "function") throw new TypeError(MESSAGE_CALLBACK);
|
|
2200
|
+
return new Queue(callback, getOptions(options));
|
|
2201
|
+
}
|
|
2202
|
+
const ERROR_NAME = "QueueError";
|
|
2203
|
+
const MESSAGE_CALLBACK = "A Queue requires a callback function";
|
|
2204
|
+
const MESSAGE_CLEAR = "Queue was cleared";
|
|
2205
|
+
const MESSAGE_MAXIMUM = "Queue has reached its maximum size";
|
|
2206
|
+
const MESSAGE_REMOVE = "Item removed from queue";
|
|
1974
2207
|
function findKey(needle, haystack) {
|
|
1975
2208
|
const keys = Object.keys(haystack);
|
|
1976
2209
|
const index = keys.map((key) => key.toLowerCase()).indexOf(needle.toLowerCase());
|
|
@@ -2980,4 +3213,4 @@ function unsmush(value) {
|
|
|
2980
3213
|
}
|
|
2981
3214
|
return unsmushed;
|
|
2982
3215
|
}
|
|
2983
|
-
export { frame_rate_default as FRAME_RATE_MS, SizedMap, SizedSet, average, beacon, between, camelCase, capitalize, chunk, clamp, clone, compact, compare, count, createUuid, debounce, diff, endsWith, equal, error, exists, filter, find, flatten, fromQuery, getArray, getColor, getForegroundColor, getHexColor, getHexaColor, getHslColor, getHslaColor, getNormalizedHex, getNumber, getRandomBoolean, getRandomCharacters, getRandomColor, getRandomFloat, getRandomHex, getRandomInteger, getRandomItem, getRandomItems, getRgbColor, getRgbaColor, getString, getUuid, getValue, groupBy, hexToHsl, hexToHsla, hexToRgb, hexToRgba, hslToHex, hslToRgb, hslToRgba, includes, indexOf, insert, isArrayOrPlainObject, isColor, isEmpty, isError, isHexColor, isHslColor, isHslLike, isHslaColor, isKey, isNullable, isNullableOrEmpty, isNullableOrWhitespace, isNumber, isNumerical, isObject, isOk, isPlainObject, isPrimitive, isResult, isRgbColor, isRgbLike, isRgbaColor, isTypedArray, join, kebabCase, logger, lowerCase, max, memoize, merge, min, noop, ok, parse, partial, pascalCase, push, result, rgbToHex, rgbToHsl, rgbToHsla, round, setValue, shuffle, smush, snakeCase, sort, splice, startsWith, sum, template, throttle, titleCase, toMap, toQuery, toRecord, toSet, trim, truncate, unique, unsmush, unwrap, upperCase, words };
|
|
3216
|
+
export { frame_rate_default as FRAME_RATE_MS, PromiseTimeoutError, QueueError, SizedMap, SizedSet, average, beacon, between, camelCase, capitalize, chunk, clamp, clone, compact, compare, count, createUuid, debounce, delay, diff, endsWith, equal, error, exists, filter, find, flatten, fromQuery, getArray, getColor, getForegroundColor, getHexColor, getHexaColor, getHslColor, getHslaColor, getNormalizedHex, getNumber, getRandomBoolean, getRandomCharacters, getRandomColor, getRandomFloat, getRandomHex, getRandomInteger, getRandomItem, getRandomItems, getRgbColor, getRgbaColor, getString, getUuid, getValue, groupBy, hexToHsl, hexToHsla, hexToRgb, hexToRgba, hslToHex, hslToRgb, hslToRgba, includes, indexOf, insert, isArrayOrPlainObject, isColor, isEmpty, isError, isHexColor, isHslColor, isHslLike, isHslaColor, isKey, isNullable, isNullableOrEmpty, isNullableOrWhitespace, isNumber, isNumerical, isObject, isOk, isPlainObject, isPrimitive, isResult, isRgbColor, isRgbLike, isRgbaColor, isTypedArray, join, kebabCase, logger, lowerCase, max, memoize, merge, min, noop, ok, parse, partial, pascalCase, promises, push, queue, result, rgbToHex, rgbToHsl, rgbToHsla, round, setValue, shuffle, smush, snakeCase, sort, splice, startsWith, sum, template, throttle, timed, titleCase, toMap, toQuery, toRecord, toSet, trim, truncate, unique, unsmush, unwrap, upperCase, words };
|
package/dist/index.js
CHANGED
|
@@ -37,6 +37,8 @@ import { SizedMap, SizedSet } from "./sized.js";
|
|
|
37
37
|
import { debounce, memoize, throttle } from "./function.js";
|
|
38
38
|
import { logger } from "./logger.js";
|
|
39
39
|
import { average, count, min, round, sum } from "./math.js";
|
|
40
|
+
import { PromiseTimeoutError, delay, promises, timed } from "./promise.js";
|
|
41
|
+
import { QueueError, queue } from "./queue.js";
|
|
40
42
|
import { setValue } from "./internal/value/set.js";
|
|
41
43
|
import { fromQuery, toQuery } from "./query.js";
|
|
42
44
|
import { getRandomBoolean, getRandomCharacters, getRandomColor, getRandomHex, getRandomItem, getRandomItems } from "./random.js";
|
|
@@ -54,4 +56,4 @@ import { partial } from "./value/partial.js";
|
|
|
54
56
|
import { smush } from "./value/smush.js";
|
|
55
57
|
import { unsmush } from "./value/unsmush.js";
|
|
56
58
|
import "./value/index.js";
|
|
57
|
-
export { frame_rate_default as FRAME_RATE_MS, SizedMap, SizedSet, average, beacon, between, camelCase, capitalize, chunk, clamp, clone, compact, compare, count, createUuid, debounce, diff, endsWith, equal, error, exists, filter, find, flatten, fromQuery, getArray, getColor, getForegroundColor, getHexColor, getHexaColor, getHslColor, getHslaColor, getNormalizedHex, getNumber, getRandomBoolean, getRandomCharacters, getRandomColor, getRandomFloat, getRandomHex, getRandomInteger, getRandomItem, getRandomItems, getRgbColor, getRgbaColor, getString, getUuid, getValue, groupBy, hexToHsl, hexToHsla, hexToRgb, hexToRgba, hslToHex, hslToRgb, hslToRgba, includes, indexOf, insert, isArrayOrPlainObject, isColor, isEmpty, isError, isHexColor, isHslColor, isHslLike, isHslaColor, isKey, isNullable, isNullableOrEmpty, isNullableOrWhitespace, isNumber, isNumerical, isObject, isOk, isPlainObject, isPrimitive, isResult, isRgbColor, isRgbLike, isRgbaColor, isTypedArray, join, kebabCase, logger, lowerCase, max, memoize, merge, min, noop, ok, parse, partial, pascalCase, push, result, rgbToHex, rgbToHsl, rgbToHsla, round, setValue, shuffle, smush, snakeCase, sort, splice, startsWith, sum, template, throttle, titleCase, toMap, toQuery, toRecord, toSet, trim, truncate, unique, unsmush, unwrap, upperCase, words };
|
|
59
|
+
export { frame_rate_default as FRAME_RATE_MS, PromiseTimeoutError, QueueError, SizedMap, SizedSet, average, beacon, between, camelCase, capitalize, chunk, clamp, clone, compact, compare, count, createUuid, debounce, delay, diff, endsWith, equal, error, exists, filter, find, flatten, fromQuery, getArray, getColor, getForegroundColor, getHexColor, getHexaColor, getHslColor, getHslaColor, getNormalizedHex, getNumber, getRandomBoolean, getRandomCharacters, getRandomColor, getRandomFloat, getRandomHex, getRandomInteger, getRandomItem, getRandomItems, getRgbColor, getRgbaColor, getString, getUuid, getValue, groupBy, hexToHsl, hexToHsla, hexToRgb, hexToRgba, hslToHex, hslToRgb, hslToRgba, includes, indexOf, insert, isArrayOrPlainObject, isColor, isEmpty, isError, isHexColor, isHslColor, isHslLike, isHslaColor, isKey, isNullable, isNullableOrEmpty, isNullableOrWhitespace, isNumber, isNumerical, isObject, isOk, isPlainObject, isPrimitive, isResult, isRgbColor, isRgbLike, isRgbaColor, isTypedArray, join, kebabCase, logger, lowerCase, max, memoize, merge, min, noop, ok, parse, partial, pascalCase, promises, push, queue, result, rgbToHex, rgbToHsl, rgbToHsla, round, setValue, shuffle, smush, snakeCase, sort, splice, startsWith, sum, template, throttle, timed, titleCase, toMap, toQuery, toRecord, toSet, trim, truncate, unique, unsmush, unwrap, upperCase, words };
|
package/dist/promise.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
var PromiseTimeoutError = class extends Error {
|
|
2
|
+
constructor() {
|
|
3
|
+
super(MESSAGE_TIMEOUT);
|
|
4
|
+
this.name = NAME;
|
|
5
|
+
}
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Create a delayed promise that resolves after a certain amount of time
|
|
9
|
+
* @param time How long to wait for _(in milliseconds; defaults to screen refresh rate)_
|
|
10
|
+
* @returns A promise that resolves after the delay
|
|
11
|
+
*/
|
|
12
|
+
function delay(time) {
|
|
13
|
+
return new Promise((resolve) => setTimeout(resolve, getNumberOrDefault(time)));
|
|
14
|
+
}
|
|
15
|
+
function getBooleanOrDefault(value, defaultValue) {
|
|
16
|
+
return typeof value === "boolean" ? value : defaultValue;
|
|
17
|
+
}
|
|
18
|
+
function getNumberOrDefault(value) {
|
|
19
|
+
return typeof value === "number" ? value : 0;
|
|
20
|
+
}
|
|
21
|
+
async function promises(items, eager) {
|
|
22
|
+
const actual = items.filter((item) => item instanceof Promise);
|
|
23
|
+
if (actual.length === 0) return actual;
|
|
24
|
+
const isEager = getBooleanOrDefault(eager, false);
|
|
25
|
+
const { length } = actual;
|
|
26
|
+
const data = {
|
|
27
|
+
last: length - 1,
|
|
28
|
+
result: []
|
|
29
|
+
};
|
|
30
|
+
let handlers;
|
|
31
|
+
return new Promise((resolve, reject) => {
|
|
32
|
+
handlers = {
|
|
33
|
+
reject,
|
|
34
|
+
resolve
|
|
35
|
+
};
|
|
36
|
+
for (let index = 0; index < length; index += 1) actual[index].then((value) => resolveResult(index, data, handlers, value, isEager)).catch((reason) => rejectResult(index, data, handlers, reason, isEager));
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
function rejectResult(index, data, handlers, reason, eager) {
|
|
40
|
+
if (eager) handlers.reject(reason);
|
|
41
|
+
else {
|
|
42
|
+
data.result[index] = {
|
|
43
|
+
status: TYPE_REJECTED,
|
|
44
|
+
reason
|
|
45
|
+
};
|
|
46
|
+
if (index === data.last) handlers.resolve(data.result);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function resolveResult(index, data, handlers, value, eager) {
|
|
50
|
+
data.result[index] = eager ? value : {
|
|
51
|
+
status: TYPE_FULFILLED,
|
|
52
|
+
value
|
|
53
|
+
};
|
|
54
|
+
if (index === data.last) handlers.resolve(data.result);
|
|
55
|
+
}
|
|
56
|
+
function timed(promise, timeout) {
|
|
57
|
+
if (!(promise instanceof Promise)) throw new TypeError(MESSAGE_EXPECTATION);
|
|
58
|
+
if (getNumberOrDefault(timeout) <= 0) return promise;
|
|
59
|
+
return Promise.race([promise, new Promise((_, reject) => setTimeout(() => reject(new PromiseTimeoutError()), timeout))]);
|
|
60
|
+
}
|
|
61
|
+
var MESSAGE_EXPECTATION = "Timed function expected a Promise";
|
|
62
|
+
var MESSAGE_TIMEOUT = "Promise timed out";
|
|
63
|
+
var NAME = "PromiseTimeoutError";
|
|
64
|
+
var TYPE_FULFILLED = "fulfilled";
|
|
65
|
+
var TYPE_REJECTED = "rejected";
|
|
66
|
+
export { PromiseTimeoutError, delay, promises, timed };
|
package/dist/queue.js
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
var Queue = class {
|
|
2
|
+
#callback;
|
|
3
|
+
#handled = [];
|
|
4
|
+
#id = 0;
|
|
5
|
+
#items = [];
|
|
6
|
+
#options;
|
|
7
|
+
#paused;
|
|
8
|
+
#runners = 0;
|
|
9
|
+
/**
|
|
10
|
+
* Is the queue active?
|
|
11
|
+
*/
|
|
12
|
+
get active() {
|
|
13
|
+
return this.#runners > 0;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Is the queue empty?
|
|
17
|
+
*/
|
|
18
|
+
get empty() {
|
|
19
|
+
return this.#items.length === 0;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Is the queue full?
|
|
23
|
+
*/
|
|
24
|
+
get full() {
|
|
25
|
+
return this.#options.maximum > 0 && this.#items.length >= this.#options.maximum;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Is the queue paused?
|
|
29
|
+
*/
|
|
30
|
+
get paused() {
|
|
31
|
+
return this.#paused;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Number of items in the queue
|
|
35
|
+
*/
|
|
36
|
+
get size() {
|
|
37
|
+
return this.#items.length;
|
|
38
|
+
}
|
|
39
|
+
constructor(callback, options) {
|
|
40
|
+
this.#callback = callback;
|
|
41
|
+
this.#options = options;
|
|
42
|
+
this.#paused = !options.autostart;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Add an item to the queue
|
|
46
|
+
* @param parameters Parameters to use when item runs
|
|
47
|
+
* @returns Queued item
|
|
48
|
+
*/
|
|
49
|
+
add(...parameters) {
|
|
50
|
+
if (this.full) throw new QueueError(MESSAGE_MAXIMUM);
|
|
51
|
+
let rejector;
|
|
52
|
+
let resolver;
|
|
53
|
+
const id = this.#identify();
|
|
54
|
+
const promise = new Promise((resolve, reject) => {
|
|
55
|
+
rejector = reject;
|
|
56
|
+
resolver = resolve;
|
|
57
|
+
});
|
|
58
|
+
this.#items.push({
|
|
59
|
+
id,
|
|
60
|
+
parameters,
|
|
61
|
+
promise,
|
|
62
|
+
reject: rejector,
|
|
63
|
+
resolve: resolver
|
|
64
|
+
});
|
|
65
|
+
if (this.#options.autostart) this.#run();
|
|
66
|
+
return {
|
|
67
|
+
id,
|
|
68
|
+
promise
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Remove and reject all items in the queue
|
|
73
|
+
*/
|
|
74
|
+
clear() {
|
|
75
|
+
const items = this.#items.splice(0);
|
|
76
|
+
const { length } = items;
|
|
77
|
+
for (let index = 0; index < length; index += 1) items[index].reject(new QueueError(MESSAGE_CLEAR));
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Pause the queue
|
|
81
|
+
*
|
|
82
|
+
* - Currently running items will not be stopped
|
|
83
|
+
* - New added items will not run until the queue is resumed
|
|
84
|
+
*/
|
|
85
|
+
pause() {
|
|
86
|
+
this.#paused = true;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Remove and reject a specific item in the queue
|
|
90
|
+
* @param id ID of queued item
|
|
91
|
+
*/
|
|
92
|
+
remove(id) {
|
|
93
|
+
const index = this.#items.findIndex((item) => item.id === id);
|
|
94
|
+
if (index > -1) {
|
|
95
|
+
const [item] = this.#items.splice(index, 1);
|
|
96
|
+
item.reject(new QueueError(MESSAGE_REMOVE));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Resume the queue
|
|
101
|
+
*/
|
|
102
|
+
resume() {
|
|
103
|
+
if (this.#paused) {
|
|
104
|
+
const handled = this.#handled.splice(0);
|
|
105
|
+
const { length } = handled;
|
|
106
|
+
for (let index = 0; index < length; index += 1) handled[index]();
|
|
107
|
+
}
|
|
108
|
+
this.#paused = false;
|
|
109
|
+
const length = Math.min(this.#options.concurrency, this.#items.length);
|
|
110
|
+
for (let index = 0; index < length; index += 1) this.#run();
|
|
111
|
+
}
|
|
112
|
+
#identify() {
|
|
113
|
+
this.#id += 1;
|
|
114
|
+
return this.#id;
|
|
115
|
+
}
|
|
116
|
+
async #run() {
|
|
117
|
+
if (this.#paused || this.#runners >= this.#options.concurrency) return;
|
|
118
|
+
this.#runners += 1;
|
|
119
|
+
let item = this.#items.shift();
|
|
120
|
+
while (item != null) {
|
|
121
|
+
let handler;
|
|
122
|
+
let result;
|
|
123
|
+
try {
|
|
124
|
+
result = await this.#callback(...item.parameters);
|
|
125
|
+
handler = item.resolve;
|
|
126
|
+
} catch (thrown) {
|
|
127
|
+
result = thrown;
|
|
128
|
+
handler = item.reject;
|
|
129
|
+
}
|
|
130
|
+
if (this.#paused) {
|
|
131
|
+
this.#handled.push(() => handler(result));
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
handler(result);
|
|
135
|
+
item = this.#items.shift();
|
|
136
|
+
}
|
|
137
|
+
this.#runners -= 1;
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
var QueueError = class extends Error {
|
|
141
|
+
constructor(message) {
|
|
142
|
+
super(message);
|
|
143
|
+
this.name = ERROR_NAME;
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
function getBooleanOrDefault(value, defaultValue) {
|
|
147
|
+
return typeof value === "boolean" ? value : defaultValue;
|
|
148
|
+
}
|
|
149
|
+
function getNumberOrDefault(value, defaultValue) {
|
|
150
|
+
return typeof value === "number" && value > 0 ? Math.floor(value) : defaultValue;
|
|
151
|
+
}
|
|
152
|
+
function getOptions(input) {
|
|
153
|
+
const options = typeof input === "object" && input != null ? input : {};
|
|
154
|
+
return {
|
|
155
|
+
autostart: getBooleanOrDefault(options.autostart, true),
|
|
156
|
+
concurrency: getNumberOrDefault(options.concurrency, 1),
|
|
157
|
+
maximum: getNumberOrDefault(options.maximum, 0)
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
function queue(callback, options) {
|
|
161
|
+
if (typeof callback !== "function") throw new TypeError(MESSAGE_CALLBACK);
|
|
162
|
+
return new Queue(callback, getOptions(options));
|
|
163
|
+
}
|
|
164
|
+
var ERROR_NAME = "QueueError";
|
|
165
|
+
var MESSAGE_CALLBACK = "A Queue requires a callback function";
|
|
166
|
+
var MESSAGE_CLEAR = "Queue was cleared";
|
|
167
|
+
var MESSAGE_MAXIMUM = "Queue has reached its maximum size";
|
|
168
|
+
var MESSAGE_REMOVE = "Item removed from queue";
|
|
169
|
+
export { QueueError, queue };
|
package/package.json
CHANGED
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
"devDependencies": {
|
|
8
8
|
"@types/node": "^25.2",
|
|
9
9
|
"@vitest/coverage-istanbul": "^4",
|
|
10
|
-
"jsdom": "^28",
|
|
11
|
-
"oxfmt": "^0.
|
|
12
|
-
"oxlint": "^1.
|
|
10
|
+
"jsdom": "^28.1",
|
|
11
|
+
"oxfmt": "^0.33",
|
|
12
|
+
"oxlint": "^1.48",
|
|
13
13
|
"rolldown": "1.0.0-rc.4",
|
|
14
14
|
"tslib": "^2.8",
|
|
15
15
|
"typescript": "^5.9",
|
|
@@ -19,75 +19,90 @@
|
|
|
19
19
|
"exports": {
|
|
20
20
|
"./package.json": "./package.json",
|
|
21
21
|
".": {
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
"types": "./types/index.d.ts",
|
|
23
|
+
"default": "./dist/index.js"
|
|
24
24
|
},
|
|
25
25
|
"./array": {
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
"types": "./types/array/index.d.ts",
|
|
27
|
+
"default": "./dist/array/index.js"
|
|
28
28
|
},
|
|
29
29
|
"./beacon": {
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
"types": "./types/beacon.d.ts",
|
|
31
|
+
"default": "./dist/beacon.js"
|
|
32
32
|
},
|
|
33
33
|
"./color": {
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
"types": "./types/color/index.d.ts",
|
|
35
|
+
"default": "./dist/color/index.js"
|
|
36
36
|
},
|
|
37
37
|
"./frame-rate": {
|
|
38
38
|
"types": "./types/internal/frame-rate.d.ts",
|
|
39
39
|
"default": "./dist/internal/frame-rate.js"
|
|
40
40
|
},
|
|
41
41
|
"./function": {
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
"types": "./types/function.d.ts",
|
|
43
|
+
"default": "./dist/function.js"
|
|
44
44
|
},
|
|
45
45
|
"./is": {
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
"types": "./types/is.d.ts",
|
|
47
|
+
"default": "./dist/is.js"
|
|
48
48
|
},
|
|
49
49
|
"./logger": {
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
"types": "./types/logger.d.ts",
|
|
51
|
+
"default": "./dist/logger.js"
|
|
52
52
|
},
|
|
53
53
|
"./math": {
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
"types": "./types/math.d.ts",
|
|
55
|
+
"default": "./dist/math.js"
|
|
56
56
|
},
|
|
57
57
|
"./models": {
|
|
58
|
-
|
|
58
|
+
"types": "./types/models.d.ts"
|
|
59
59
|
},
|
|
60
60
|
"./number": {
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
"types": "./types/number.d.ts",
|
|
62
|
+
"default": "./dist/number.js"
|
|
63
|
+
},
|
|
64
|
+
"./promise": {
|
|
65
|
+
"types": "./types/promise.d.ts",
|
|
66
|
+
"default": "./dist/promise.js"
|
|
67
|
+
},
|
|
68
|
+
"./queue": {
|
|
69
|
+
"types": "./types/queue.d.ts",
|
|
70
|
+
"default": "./dist/queue.js"
|
|
63
71
|
},
|
|
64
72
|
"./query": {
|
|
65
|
-
|
|
66
|
-
|
|
73
|
+
"types": "./types/query.d.ts",
|
|
74
|
+
"default": "./dist/query.js"
|
|
67
75
|
},
|
|
68
76
|
"./random": {
|
|
69
|
-
|
|
70
|
-
|
|
77
|
+
"types": "./types/random.d.ts",
|
|
78
|
+
"default": "./dist/random.js"
|
|
71
79
|
},
|
|
72
80
|
"./result": {
|
|
73
|
-
|
|
74
|
-
|
|
81
|
+
"types": "./types/result.d.ts",
|
|
82
|
+
"default": "./dist/result.js"
|
|
75
83
|
},
|
|
76
84
|
"./sized": {
|
|
77
|
-
|
|
78
|
-
|
|
85
|
+
"types": "./types/sized.d.ts",
|
|
86
|
+
"default": "./dist/sized.js"
|
|
79
87
|
},
|
|
80
88
|
"./string": {
|
|
81
|
-
|
|
82
|
-
|
|
89
|
+
"types": "./types/string/index.d.ts",
|
|
90
|
+
"default": "./dist/string/index.js"
|
|
83
91
|
},
|
|
84
92
|
"./value": {
|
|
85
|
-
|
|
86
|
-
|
|
93
|
+
"types": "./types/value/index.d.ts",
|
|
94
|
+
"default": "./dist/value/index.js"
|
|
87
95
|
}
|
|
88
96
|
},
|
|
89
|
-
"files": [
|
|
90
|
-
|
|
97
|
+
"files": [
|
|
98
|
+
"dist",
|
|
99
|
+
"src",
|
|
100
|
+
"types"
|
|
101
|
+
],
|
|
102
|
+
"keywords": [
|
|
103
|
+
"helper",
|
|
104
|
+
"utility"
|
|
105
|
+
],
|
|
91
106
|
"license": "MIT",
|
|
92
107
|
"module": "./dist/index.js",
|
|
93
108
|
"name": "@oscarpalmer/atoms",
|
|
@@ -105,5 +120,5 @@
|
|
|
105
120
|
},
|
|
106
121
|
"type": "module",
|
|
107
122
|
"types": "./types/index.d.ts",
|
|
108
|
-
"version": "0.
|
|
123
|
+
"version": "0.133.0"
|
|
109
124
|
}
|
package/src/index.ts
CHANGED
package/src/models.ts
CHANGED
package/src/promise.ts
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
// #region Types
|
|
2
|
+
|
|
3
|
+
type Data<Items extends unknown[]> = {
|
|
4
|
+
last: number;
|
|
5
|
+
result: Items | PromisesResult<Items>;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
type Handlers<Items extends unknown[]> = {
|
|
9
|
+
resolve: (value: Items | PromisesResult<Items>) => void;
|
|
10
|
+
reject: (reason: unknown) => void;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export class PromiseTimeoutError extends Error {
|
|
14
|
+
constructor() {
|
|
15
|
+
super(MESSAGE_TIMEOUT);
|
|
16
|
+
|
|
17
|
+
this.name = NAME;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
type Promises<Items extends unknown[]> = {
|
|
22
|
+
[K in keyof Items]: Promise<Items[K]>;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
type PromisesResult<Items extends unknown[]> = {
|
|
26
|
+
[K in keyof Items]: ResolvedResult<Items[K]> | RejectedResult;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
type RejectedResult = {
|
|
30
|
+
status: typeof TYPE_REJECTED;
|
|
31
|
+
reason: unknown;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
type ResolvedResult<Value> = {
|
|
35
|
+
status: typeof TYPE_FULFILLED;
|
|
36
|
+
value: Value;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// #endregion
|
|
40
|
+
|
|
41
|
+
// #region Functions
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Create a delayed promise that resolves after a certain amount of time
|
|
45
|
+
* @param time How long to wait for _(in milliseconds; defaults to screen refresh rate)_
|
|
46
|
+
* @returns A promise that resolves after the delay
|
|
47
|
+
*/
|
|
48
|
+
export function delay(time?: number): Promise<void> {
|
|
49
|
+
return new Promise(resolve => setTimeout(resolve, getNumberOrDefault(time)));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function getBooleanOrDefault(value: unknown, defaultValue: boolean): boolean {
|
|
53
|
+
return typeof value === 'boolean' ? value : defaultValue;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function getNumberOrDefault(value: unknown): number {
|
|
57
|
+
return typeof value === 'number' ? value : 0;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Handle a list of promises, returning their results in an ordered array. If any promise in the list is rejected, the whole function will reject
|
|
62
|
+
* @param items List of promises
|
|
63
|
+
* @param eager Reject immediately if any promise is rejected
|
|
64
|
+
* @return List of results
|
|
65
|
+
*/
|
|
66
|
+
export async function promises<Items extends unknown[]>(
|
|
67
|
+
items: Promises<Items>,
|
|
68
|
+
eager: true,
|
|
69
|
+
): Promise<Items>;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Handle a list of promises, returning their results in an ordered array of rejected and resolved results
|
|
73
|
+
* @param items List of promises
|
|
74
|
+
* @return List of results
|
|
75
|
+
*/
|
|
76
|
+
export async function promises<Items extends unknown[]>(
|
|
77
|
+
items: Promises<Items>,
|
|
78
|
+
): Promise<PromisesResult<Items>>;
|
|
79
|
+
|
|
80
|
+
export async function promises<Items extends unknown[]>(
|
|
81
|
+
items: Promises<Items>,
|
|
82
|
+
eager?: unknown,
|
|
83
|
+
): Promise<Items | PromisesResult<Items>> {
|
|
84
|
+
const actual = items.filter(item => item instanceof Promise);
|
|
85
|
+
|
|
86
|
+
if (actual.length === 0) {
|
|
87
|
+
return actual as unknown as Items | PromisesResult<Items>;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const isEager = getBooleanOrDefault(eager, false);
|
|
91
|
+
|
|
92
|
+
const {length} = actual;
|
|
93
|
+
|
|
94
|
+
const data: Data<Items> = {
|
|
95
|
+
last: length - 1,
|
|
96
|
+
result: [] as unknown as Items | PromisesResult<Items>,
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
let handlers: Handlers<Items>;
|
|
100
|
+
|
|
101
|
+
return new Promise((resolve, reject) => {
|
|
102
|
+
handlers = {reject, resolve};
|
|
103
|
+
|
|
104
|
+
for (let index = 0; index < length; index += 1) {
|
|
105
|
+
void actual[index]
|
|
106
|
+
.then(value => resolveResult(index, data, handlers, value, isEager))
|
|
107
|
+
.catch(reason => rejectResult(index, data, handlers, reason, isEager));
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function rejectResult<Items extends unknown[]>(
|
|
113
|
+
index: number,
|
|
114
|
+
data: Data<Items>,
|
|
115
|
+
handlers: Handlers<Items>,
|
|
116
|
+
reason: unknown,
|
|
117
|
+
eager: boolean,
|
|
118
|
+
) {
|
|
119
|
+
if (eager) {
|
|
120
|
+
handlers.reject(reason);
|
|
121
|
+
} else {
|
|
122
|
+
data.result[index] = {status: TYPE_REJECTED, reason};
|
|
123
|
+
|
|
124
|
+
if (index === data.last) {
|
|
125
|
+
handlers.resolve(data.result as Items | PromisesResult<Items>);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function resolveResult<Items extends unknown[]>(
|
|
131
|
+
index: number,
|
|
132
|
+
data: Data<Items>,
|
|
133
|
+
handlers: Handlers<Items>,
|
|
134
|
+
value: unknown,
|
|
135
|
+
eager: boolean,
|
|
136
|
+
) {
|
|
137
|
+
(data.result as unknown[])[index] = eager ? value : {status: TYPE_FULFILLED, value};
|
|
138
|
+
|
|
139
|
+
if (index === data.last) {
|
|
140
|
+
handlers.resolve(data.result as Items | PromisesResult<Items>);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function timed(promise: Promise<unknown>, timeout: number): Promise<unknown> {
|
|
145
|
+
if (!(promise instanceof Promise)) {
|
|
146
|
+
throw new TypeError(MESSAGE_EXPECTATION);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const time = getNumberOrDefault(timeout);
|
|
150
|
+
|
|
151
|
+
if (time <= 0) {
|
|
152
|
+
return promise;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return Promise.race([
|
|
156
|
+
promise,
|
|
157
|
+
new Promise((_, reject) => setTimeout(() => reject(new PromiseTimeoutError()), timeout)),
|
|
158
|
+
]);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// #endregion
|
|
162
|
+
|
|
163
|
+
// #region Variables
|
|
164
|
+
|
|
165
|
+
const MESSAGE_EXPECTATION = 'Timed function expected a Promise';
|
|
166
|
+
|
|
167
|
+
const MESSAGE_TIMEOUT = 'Promise timed out';
|
|
168
|
+
|
|
169
|
+
const NAME = 'PromiseTimeoutError';
|
|
170
|
+
|
|
171
|
+
const TYPE_FULFILLED = 'fulfilled';
|
|
172
|
+
|
|
173
|
+
const TYPE_REJECTED = 'rejected';
|
|
174
|
+
|
|
175
|
+
// #endregion
|
package/src/queue.ts
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import type {GenericAsyncCallback, GenericCallback} from './models';
|
|
2
|
+
|
|
3
|
+
// #region Types and classes
|
|
4
|
+
|
|
5
|
+
class Queue<CallbackParameters extends Parameters<GenericAsyncCallback>, CallbackResult> {
|
|
6
|
+
readonly #callback: GenericAsyncCallback;
|
|
7
|
+
|
|
8
|
+
readonly #handled: Array<GenericCallback> = [];
|
|
9
|
+
|
|
10
|
+
#id = 0;
|
|
11
|
+
|
|
12
|
+
readonly #items: Array<QueuedItem<CallbackParameters, CallbackResult>> = [];
|
|
13
|
+
|
|
14
|
+
readonly #options: Required<QueueOptions>;
|
|
15
|
+
|
|
16
|
+
#paused: boolean;
|
|
17
|
+
|
|
18
|
+
#runners = 0;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Is the queue active?
|
|
22
|
+
*/
|
|
23
|
+
get active(): boolean {
|
|
24
|
+
return this.#runners > 0;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Is the queue empty?
|
|
29
|
+
*/
|
|
30
|
+
get empty(): boolean {
|
|
31
|
+
return this.#items.length === 0;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Is the queue full?
|
|
36
|
+
*/
|
|
37
|
+
get full(): boolean {
|
|
38
|
+
return this.#options.maximum > 0 && this.#items.length >= this.#options.maximum;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Is the queue paused?
|
|
43
|
+
*/
|
|
44
|
+
get paused(): boolean {
|
|
45
|
+
return this.#paused;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Number of items in the queue
|
|
50
|
+
*/
|
|
51
|
+
get size(): number {
|
|
52
|
+
return this.#items.length;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
constructor(callback: GenericAsyncCallback, options: Required<QueueOptions>) {
|
|
56
|
+
this.#callback = callback;
|
|
57
|
+
this.#options = options;
|
|
58
|
+
|
|
59
|
+
this.#paused = !options.autostart;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Add an item to the queue
|
|
64
|
+
* @param parameters Parameters to use when item runs
|
|
65
|
+
* @returns Queued item
|
|
66
|
+
*/
|
|
67
|
+
add(...parameters: CallbackParameters): Queued<CallbackResult> {
|
|
68
|
+
if (this.full) {
|
|
69
|
+
throw new QueueError(MESSAGE_MAXIMUM);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
let rejector: (reason?: unknown) => void;
|
|
73
|
+
let resolver: (value: CallbackResult) => void;
|
|
74
|
+
|
|
75
|
+
const id = this.#identify();
|
|
76
|
+
|
|
77
|
+
const promise = new Promise<CallbackResult>((resolve, reject) => {
|
|
78
|
+
rejector = reject;
|
|
79
|
+
resolver = resolve;
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
this.#items.push({
|
|
83
|
+
id,
|
|
84
|
+
parameters,
|
|
85
|
+
promise,
|
|
86
|
+
reject: rejector!,
|
|
87
|
+
resolve: resolver!,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
if (this.#options.autostart) {
|
|
91
|
+
void this.#run();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return {id, promise};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Remove and reject all items in the queue
|
|
99
|
+
*/
|
|
100
|
+
clear(): void {
|
|
101
|
+
const items = this.#items.splice(0);
|
|
102
|
+
const {length} = items;
|
|
103
|
+
|
|
104
|
+
for (let index = 0; index < length; index += 1) {
|
|
105
|
+
items[index].reject(new QueueError(MESSAGE_CLEAR));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Pause the queue
|
|
111
|
+
*
|
|
112
|
+
* - Currently running items will not be stopped
|
|
113
|
+
* - New added items will not run until the queue is resumed
|
|
114
|
+
*/
|
|
115
|
+
pause(): void {
|
|
116
|
+
this.#paused = true;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Remove and reject a specific item in the queue
|
|
121
|
+
* @param id ID of queued item
|
|
122
|
+
*/
|
|
123
|
+
remove(id: number): void {
|
|
124
|
+
const index = this.#items.findIndex(item => item.id === id);
|
|
125
|
+
|
|
126
|
+
if (index > -1) {
|
|
127
|
+
const [item] = this.#items.splice(index, 1);
|
|
128
|
+
|
|
129
|
+
item.reject(new QueueError(MESSAGE_REMOVE));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Resume the queue
|
|
135
|
+
*/
|
|
136
|
+
resume(): void {
|
|
137
|
+
if (this.#paused) {
|
|
138
|
+
const handled = this.#handled.splice(0);
|
|
139
|
+
const {length} = handled;
|
|
140
|
+
|
|
141
|
+
for (let index = 0; index < length; index += 1) {
|
|
142
|
+
handled[index]();
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
this.#paused = false;
|
|
147
|
+
|
|
148
|
+
const length = Math.min(this.#options.concurrency, this.#items.length);
|
|
149
|
+
|
|
150
|
+
for (let index = 0; index < length; index += 1) {
|
|
151
|
+
void this.#run();
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
#identify(): number {
|
|
156
|
+
this.#id += 1;
|
|
157
|
+
|
|
158
|
+
return this.#id;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async #run(): Promise<void> {
|
|
162
|
+
if (this.#paused || this.#runners >= this.#options.concurrency) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
this.#runners += 1;
|
|
167
|
+
|
|
168
|
+
let item = this.#items.shift();
|
|
169
|
+
|
|
170
|
+
while (item != null) {
|
|
171
|
+
let handler: GenericCallback;
|
|
172
|
+
let result: unknown | CallbackResult;
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
result = await this.#callback(...item.parameters);
|
|
176
|
+
handler = item.resolve;
|
|
177
|
+
} catch (thrown) {
|
|
178
|
+
result = thrown;
|
|
179
|
+
handler = item.reject;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (this.#paused) {
|
|
183
|
+
this.#handled.push(() => handler(result));
|
|
184
|
+
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
handler(result);
|
|
189
|
+
|
|
190
|
+
item = this.#items.shift();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
this.#runners -= 1;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
class QueueError extends Error {
|
|
198
|
+
constructor(message: string) {
|
|
199
|
+
super(message);
|
|
200
|
+
|
|
201
|
+
this.name = ERROR_NAME;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
type QueueOptions = {
|
|
206
|
+
/**
|
|
207
|
+
* Automatically start processing the queue when the first item is added _(defaults to `true`)_
|
|
208
|
+
*/
|
|
209
|
+
autostart?: boolean;
|
|
210
|
+
/**
|
|
211
|
+
* Number of runners to process the queue concurrently _(defaults to `1`)_
|
|
212
|
+
*/
|
|
213
|
+
concurrency?: number;
|
|
214
|
+
/**
|
|
215
|
+
* Maximum number of items allowed in the queue _(defaults to `0`, which means no limit)_
|
|
216
|
+
*/
|
|
217
|
+
maximum?: number;
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
type Queued<Value> = {
|
|
221
|
+
readonly id: number;
|
|
222
|
+
readonly promise: Promise<Value>;
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
type QueuedItem<CallbackParameters extends Parameters<GenericAsyncCallback>, CallbackResult> = {
|
|
226
|
+
id: number;
|
|
227
|
+
parameters: CallbackParameters;
|
|
228
|
+
promise: Promise<CallbackResult>;
|
|
229
|
+
reject: (reason?: unknown) => void;
|
|
230
|
+
resolve: (value: CallbackResult) => void;
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
// #endregion
|
|
234
|
+
|
|
235
|
+
// #region Functions
|
|
236
|
+
|
|
237
|
+
function getBooleanOrDefault(value: unknown, defaultValue: boolean): boolean {
|
|
238
|
+
return typeof value === 'boolean' ? value : defaultValue;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function getNumberOrDefault(value: unknown, defaultValue: number): number {
|
|
242
|
+
return typeof value === 'number' && value > 0 ? Math.floor(value) : defaultValue;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function getOptions(input?: QueueOptions): Required<QueueOptions> {
|
|
246
|
+
const options = typeof input === 'object' && input != null ? input : {};
|
|
247
|
+
|
|
248
|
+
return {
|
|
249
|
+
autostart: getBooleanOrDefault(options.autostart, true),
|
|
250
|
+
concurrency: getNumberOrDefault(options.concurrency, 1),
|
|
251
|
+
maximum: getNumberOrDefault(options.maximum, 0),
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Create a queue for an asynchronous callback function
|
|
257
|
+
* @param callback Callback function for queued items
|
|
258
|
+
* @param options Queue options
|
|
259
|
+
* @returns Queue instance
|
|
260
|
+
*/
|
|
261
|
+
function queue<Callback extends GenericAsyncCallback>(
|
|
262
|
+
callback: Callback,
|
|
263
|
+
options?: QueueOptions,
|
|
264
|
+
): Queue<Parameters<Callback>, Awaited<ReturnType<Callback>>>;
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Create a queue for an asynchronous callback function
|
|
268
|
+
* @param callback Callback function for queued items
|
|
269
|
+
* @param options Queue options
|
|
270
|
+
* @returns Queue instance
|
|
271
|
+
*/
|
|
272
|
+
function queue<Callback extends GenericCallback>(
|
|
273
|
+
callback: Callback,
|
|
274
|
+
options?: QueueOptions,
|
|
275
|
+
): Queue<Parameters<Callback>, ReturnType<Callback>>;
|
|
276
|
+
|
|
277
|
+
function queue(
|
|
278
|
+
callback: GenericCallback,
|
|
279
|
+
options?: QueueOptions,
|
|
280
|
+
): Queue<Parameters<GenericCallback>, ReturnType<GenericCallback>> {
|
|
281
|
+
if (typeof callback !== 'function') {
|
|
282
|
+
throw new TypeError(MESSAGE_CALLBACK);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return new Queue(callback, getOptions(options));
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// #endregion
|
|
289
|
+
|
|
290
|
+
// #region Variables
|
|
291
|
+
|
|
292
|
+
const ERROR_NAME = 'QueueError';
|
|
293
|
+
|
|
294
|
+
const MESSAGE_CALLBACK = 'A Queue requires a callback function';
|
|
295
|
+
|
|
296
|
+
const MESSAGE_CLEAR = 'Queue was cleared';
|
|
297
|
+
|
|
298
|
+
const MESSAGE_MAXIMUM = 'Queue has reached its maximum size';
|
|
299
|
+
|
|
300
|
+
const MESSAGE_REMOVE = 'Item removed from queue';
|
|
301
|
+
|
|
302
|
+
// #endregion
|
|
303
|
+
|
|
304
|
+
// #region Exports
|
|
305
|
+
|
|
306
|
+
export {queue, QueueError, type Queue, type Queued, type QueueOptions};
|
|
307
|
+
|
|
308
|
+
// #endregion
|
package/types/index.d.ts
CHANGED
package/types/models.d.ts
CHANGED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export declare class PromiseTimeoutError extends Error {
|
|
2
|
+
constructor();
|
|
3
|
+
}
|
|
4
|
+
type Promises<Items extends unknown[]> = {
|
|
5
|
+
[K in keyof Items]: Promise<Items[K]>;
|
|
6
|
+
};
|
|
7
|
+
type PromisesResult<Items extends unknown[]> = {
|
|
8
|
+
[K in keyof Items]: ResolvedResult<Items[K]> | RejectedResult;
|
|
9
|
+
};
|
|
10
|
+
type RejectedResult = {
|
|
11
|
+
status: typeof TYPE_REJECTED;
|
|
12
|
+
reason: unknown;
|
|
13
|
+
};
|
|
14
|
+
type ResolvedResult<Value> = {
|
|
15
|
+
status: typeof TYPE_FULFILLED;
|
|
16
|
+
value: Value;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Create a delayed promise that resolves after a certain amount of time
|
|
20
|
+
* @param time How long to wait for _(in milliseconds; defaults to screen refresh rate)_
|
|
21
|
+
* @returns A promise that resolves after the delay
|
|
22
|
+
*/
|
|
23
|
+
export declare function delay(time?: number): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Handle a list of promises, returning their results in an ordered array. If any promise in the list is rejected, the whole function will reject
|
|
26
|
+
* @param items List of promises
|
|
27
|
+
* @param eager Reject immediately if any promise is rejected
|
|
28
|
+
* @return List of results
|
|
29
|
+
*/
|
|
30
|
+
export declare function promises<Items extends unknown[]>(items: Promises<Items>, eager: true): Promise<Items>;
|
|
31
|
+
/**
|
|
32
|
+
* Handle a list of promises, returning their results in an ordered array of rejected and resolved results
|
|
33
|
+
* @param items List of promises
|
|
34
|
+
* @return List of results
|
|
35
|
+
*/
|
|
36
|
+
export declare function promises<Items extends unknown[]>(items: Promises<Items>): Promise<PromisesResult<Items>>;
|
|
37
|
+
export declare function timed(promise: Promise<unknown>, timeout: number): Promise<unknown>;
|
|
38
|
+
declare const TYPE_FULFILLED = "fulfilled";
|
|
39
|
+
declare const TYPE_REJECTED = "rejected";
|
|
40
|
+
export {};
|
package/types/queue.d.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { GenericAsyncCallback, GenericCallback } from './models';
|
|
2
|
+
declare class Queue<CallbackParameters extends Parameters<GenericAsyncCallback>, CallbackResult> {
|
|
3
|
+
#private;
|
|
4
|
+
/**
|
|
5
|
+
* Is the queue active?
|
|
6
|
+
*/
|
|
7
|
+
get active(): boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Is the queue empty?
|
|
10
|
+
*/
|
|
11
|
+
get empty(): boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Is the queue full?
|
|
14
|
+
*/
|
|
15
|
+
get full(): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Is the queue paused?
|
|
18
|
+
*/
|
|
19
|
+
get paused(): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Number of items in the queue
|
|
22
|
+
*/
|
|
23
|
+
get size(): number;
|
|
24
|
+
constructor(callback: GenericAsyncCallback, options: Required<QueueOptions>);
|
|
25
|
+
/**
|
|
26
|
+
* Add an item to the queue
|
|
27
|
+
* @param parameters Parameters to use when item runs
|
|
28
|
+
* @returns Queued item
|
|
29
|
+
*/
|
|
30
|
+
add(...parameters: CallbackParameters): Queued<CallbackResult>;
|
|
31
|
+
/**
|
|
32
|
+
* Remove and reject all items in the queue
|
|
33
|
+
*/
|
|
34
|
+
clear(): void;
|
|
35
|
+
/**
|
|
36
|
+
* Pause the queue
|
|
37
|
+
*
|
|
38
|
+
* - Currently running items will not be stopped
|
|
39
|
+
* - New added items will not run until the queue is resumed
|
|
40
|
+
*/
|
|
41
|
+
pause(): void;
|
|
42
|
+
/**
|
|
43
|
+
* Remove and reject a specific item in the queue
|
|
44
|
+
* @param id ID of queued item
|
|
45
|
+
*/
|
|
46
|
+
remove(id: number): void;
|
|
47
|
+
/**
|
|
48
|
+
* Resume the queue
|
|
49
|
+
*/
|
|
50
|
+
resume(): void;
|
|
51
|
+
}
|
|
52
|
+
declare class QueueError extends Error {
|
|
53
|
+
constructor(message: string);
|
|
54
|
+
}
|
|
55
|
+
type QueueOptions = {
|
|
56
|
+
/**
|
|
57
|
+
* Automatically start processing the queue when the first item is added _(defaults to `true`)_
|
|
58
|
+
*/
|
|
59
|
+
autostart?: boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Number of runners to process the queue concurrently _(defaults to `1`)_
|
|
62
|
+
*/
|
|
63
|
+
concurrency?: number;
|
|
64
|
+
/**
|
|
65
|
+
* Maximum number of items allowed in the queue _(defaults to `0`, which means no limit)_
|
|
66
|
+
*/
|
|
67
|
+
maximum?: number;
|
|
68
|
+
};
|
|
69
|
+
type Queued<Value> = {
|
|
70
|
+
readonly id: number;
|
|
71
|
+
readonly promise: Promise<Value>;
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Create a queue for an asynchronous callback function
|
|
75
|
+
* @param callback Callback function for queued items
|
|
76
|
+
* @param options Queue options
|
|
77
|
+
* @returns Queue instance
|
|
78
|
+
*/
|
|
79
|
+
declare function queue<Callback extends GenericAsyncCallback>(callback: Callback, options?: QueueOptions): Queue<Parameters<Callback>, Awaited<ReturnType<Callback>>>;
|
|
80
|
+
/**
|
|
81
|
+
* Create a queue for an asynchronous callback function
|
|
82
|
+
* @param callback Callback function for queued items
|
|
83
|
+
* @param options Queue options
|
|
84
|
+
* @returns Queue instance
|
|
85
|
+
*/
|
|
86
|
+
declare function queue<Callback extends GenericCallback>(callback: Callback, options?: QueueOptions): Queue<Parameters<Callback>, ReturnType<Callback>>;
|
|
87
|
+
export { queue, QueueError, type Queue, type Queued, type QueueOptions };
|