@oscarpalmer/atoms 0.133.0 → 0.134.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.
@@ -1974,28 +1974,98 @@ function sum(array, key) {
1974
1974
  var PromiseTimeoutError = class extends Error {
1975
1975
  constructor() {
1976
1976
  super(MESSAGE_TIMEOUT);
1977
- this.name = NAME;
1977
+ this.name = ERROR_NAME$1;
1978
1978
  }
1979
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)));
1980
+ function delay(options) {
1981
+ const { signal, time } = getPromiseOptions(options);
1982
+ if (signal?.aborted ?? false) return Promise.reject(signal.reason);
1983
+ function abort() {
1984
+ clearTimeout(timeout);
1985
+ rejector(signal.reason);
1986
+ }
1987
+ signal?.addEventListener("abort", abort, abortOptions$1);
1988
+ let rejector;
1989
+ let timeout;
1990
+ return new Promise((resolve, reject) => {
1991
+ rejector = reject;
1992
+ timeout = setTimeout(() => {
1993
+ signal?.removeEventListener("abort", abort);
1994
+ resolve();
1995
+ }, time);
1996
+ });
1987
1997
  }
1988
1998
  function getBooleanOrDefault$1(value, defaultValue) {
1989
1999
  return typeof value === "boolean" ? value : defaultValue;
1990
2000
  }
1991
2001
  function getNumberOrDefault$1(value) {
1992
- return typeof value === "number" ? value : 0;
2002
+ return typeof value === "number" && value > 0 ? value : 0;
2003
+ }
2004
+ function getPromiseOptions(input) {
2005
+ if (typeof input === "number") return { time: getNumberOrDefault$1(input) };
2006
+ const options = typeof input === "object" && input !== null ? input : {};
2007
+ return {
2008
+ signal: options.signal instanceof AbortSignal ? options.signal : void 0,
2009
+ time: getNumberOrDefault$1(options.time)
2010
+ };
2011
+ }
2012
+ function getPromisesOptions(input) {
2013
+ if (typeof input === "boolean") return { eager: input };
2014
+ if (input instanceof AbortSignal) return {
2015
+ eager: false,
2016
+ signal: input
2017
+ };
2018
+ const options = typeof input === "object" && input !== null ? input : {};
2019
+ return {
2020
+ eager: getBooleanOrDefault$1(options.eager, false),
2021
+ signal: options.signal instanceof AbortSignal ? options.signal : void 0
2022
+ };
2023
+ }
2024
+ function handleResult(status, parameters) {
2025
+ const { data, eager, handlers, index, signal, value } = parameters;
2026
+ if (signal?.aborted ?? false) return;
2027
+ if (eager && status === TYPE_REJECTED) {
2028
+ handlers.reject(value);
2029
+ return;
2030
+ }
2031
+ data.result[index] = eager ? value : status === TYPE_FULFILLED ? {
2032
+ status,
2033
+ value
2034
+ } : {
2035
+ status,
2036
+ reason: value
2037
+ };
2038
+ if (index === data.last) handlers.resolve(data.result);
2039
+ }
2040
+ /**
2041
+ * Is the value a fulfilled promise result?
2042
+ * @param value Value to check
2043
+ * @returns `true` if the value is a fulfilled promise result, `false` otherwise
2044
+ */
2045
+ function isFulfilled(value) {
2046
+ return isType(value, TYPE_FULFILLED);
2047
+ }
2048
+ /**
2049
+ * Is the value a rejected promise result?
2050
+ * @param value Value to check
2051
+ * @returns `true` if the value is a rejected promise result, `false` otherwise
2052
+ */
2053
+ function isRejected(value) {
2054
+ return isType(value, TYPE_REJECTED);
1993
2055
  }
1994
- async function promises(items, eager) {
2056
+ function isType(value, type) {
2057
+ return typeof value === "object" && value !== null && value.status === type;
2058
+ }
2059
+ async function promises(items, options) {
1995
2060
  const actual = items.filter((item) => item instanceof Promise);
1996
- if (actual.length === 0) return actual;
1997
- const isEager = getBooleanOrDefault$1(eager, false);
1998
2061
  const { length } = actual;
2062
+ const { eager, signal } = getPromisesOptions(options);
2063
+ if (signal?.aborted ?? false) return Promise.reject(signal.reason);
2064
+ if (length === 0) return actual;
2065
+ function abort() {
2066
+ handlers.reject(signal.reason);
2067
+ }
2068
+ signal?.addEventListener("abort", abort, abortOptions$1);
1999
2069
  const data = {
2000
2070
  last: length - 1,
2001
2071
  result: []
@@ -2006,34 +2076,48 @@ async function promises(items, eager) {
2006
2076
  reject,
2007
2077
  resolve
2008
2078
  };
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));
2079
+ for (let index = 0; index < length; index += 1) actual[index].then((value) => handleResult(TYPE_FULFILLED, {
2080
+ data,
2081
+ eager,
2082
+ handlers,
2083
+ index,
2084
+ signal,
2085
+ value
2086
+ })).catch((reason) => handleResult(TYPE_REJECTED, {
2087
+ data,
2088
+ eager,
2089
+ handlers,
2090
+ index,
2091
+ signal,
2092
+ value: reason
2093
+ }));
2010
2094
  });
2011
2095
  }
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) {
2096
+ function timed(promise, options) {
2030
2097
  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
- }
2098
+ const { signal, time } = getPromiseOptions(options);
2099
+ if (signal?.aborted ?? false) return Promise.reject(signal.reason);
2100
+ if (time <= 0) return promise;
2101
+ function abort() {
2102
+ clearTimeout(timeout);
2103
+ rejector(signal.reason);
2104
+ }
2105
+ signal?.addEventListener(EVENT_NAME$1, abort, abortOptions$1);
2106
+ let rejector;
2107
+ let timeout;
2108
+ return Promise.race([promise, new Promise((_, reject) => {
2109
+ rejector = reject;
2110
+ timeout = setTimeout(() => {
2111
+ signal?.removeEventListener(EVENT_NAME$1, abort);
2112
+ reject(new PromiseTimeoutError());
2113
+ }, time);
2114
+ })]);
2115
+ }
2116
+ const abortOptions$1 = { once: true };
2117
+ const ERROR_NAME$1 = "PromiseTimeoutError";
2118
+ const EVENT_NAME$1 = "abort";
2034
2119
  const MESSAGE_EXPECTATION = "Timed function expected a Promise";
2035
2120
  const MESSAGE_TIMEOUT = "Promise timed out";
2036
- const NAME = "PromiseTimeoutError";
2037
2121
  const TYPE_FULFILLED = "fulfilled";
2038
2122
  const TYPE_REJECTED = "rejected";
2039
2123
  var Queue = class {
@@ -2082,23 +2166,30 @@ var Queue = class {
2082
2166
  /**
2083
2167
  * Add an item to the queue
2084
2168
  * @param parameters Parameters to use when item runs
2169
+ * @param signal Optional signal to abort the item
2085
2170
  * @returns Queued item
2086
2171
  */
2087
- add(...parameters) {
2172
+ add(parameters, signal) {
2088
2173
  if (this.full) throw new QueueError(MESSAGE_MAXIMUM);
2174
+ const abortSignal = signal instanceof AbortSignal ? signal : void 0;
2175
+ if (abortSignal?.aborted ?? false) throw new Error(abortSignal.reason);
2176
+ const id = this.#identify();
2089
2177
  let rejector;
2090
2178
  let resolver;
2091
- const id = this.#identify();
2092
2179
  const promise = new Promise((resolve, reject) => {
2093
2180
  rejector = reject;
2094
2181
  resolver = resolve;
2095
2182
  });
2183
+ const aborter = abortSignal == null ? void 0 : () => rejector(abortSignal.reason);
2184
+ signal?.addEventListener(EVENT_NAME, aborter, abortOptions);
2096
2185
  this.#items.push({
2097
2186
  id,
2098
2187
  parameters,
2099
2188
  promise,
2189
+ abort: aborter,
2100
2190
  reject: rejector,
2101
- resolve: resolver
2191
+ resolve: resolver,
2192
+ signal: abortSignal
2102
2193
  });
2103
2194
  if (this.#options.autostart) this.#run();
2104
2195
  return {
@@ -2112,7 +2203,11 @@ var Queue = class {
2112
2203
  clear() {
2113
2204
  const items = this.#items.splice(0);
2114
2205
  const { length } = items;
2115
- for (let index = 0; index < length; index += 1) items[index].reject(new QueueError(MESSAGE_CLEAR));
2206
+ for (let index = 0; index < length; index += 1) {
2207
+ const { abort, reject, signal } = items[index];
2208
+ reject(new QueueError(MESSAGE_CLEAR));
2209
+ signal?.removeEventListener(EVENT_NAME, abort);
2210
+ }
2116
2211
  }
2117
2212
  /**
2118
2213
  * Pause the queue
@@ -2130,8 +2225,9 @@ var Queue = class {
2130
2225
  remove(id) {
2131
2226
  const index = this.#items.findIndex((item) => item.id === id);
2132
2227
  if (index > -1) {
2133
- const [item] = this.#items.splice(index, 1);
2134
- item.reject(new QueueError(MESSAGE_REMOVE));
2228
+ const { abort, reject, signal } = this.#items.splice(index, 1)[0];
2229
+ reject(new QueueError(MESSAGE_REMOVE));
2230
+ signal?.removeEventListener(EVENT_NAME, abort);
2135
2231
  }
2136
2232
  }
2137
2233
  /**
@@ -2159,17 +2255,24 @@ var Queue = class {
2159
2255
  let handler;
2160
2256
  let result;
2161
2257
  try {
2162
- result = await this.#callback(...item.parameters);
2163
- handler = item.resolve;
2258
+ if (!(item.signal?.aborted ?? false)) {
2259
+ result = await this.#callback(...item.parameters);
2260
+ handler = item.resolve;
2261
+ }
2164
2262
  } catch (thrown) {
2165
2263
  result = thrown;
2166
2264
  handler = item.reject;
2167
2265
  }
2168
2266
  if (this.#paused) {
2169
- this.#handled.push(() => handler(result));
2267
+ const paused = item;
2268
+ this.#handled.push(() => {
2269
+ paused.signal?.removeEventListener(EVENT_NAME, paused.abort);
2270
+ if (!(paused.signal?.aborted ?? false)) handler(result);
2271
+ });
2170
2272
  break;
2171
2273
  }
2172
- handler(result);
2274
+ item.signal?.removeEventListener(EVENT_NAME, item.abort);
2275
+ if (!(item.signal?.aborted ?? false)) handler(result);
2173
2276
  item = this.#items.shift();
2174
2277
  }
2175
2278
  this.#runners -= 1;
@@ -2199,7 +2302,9 @@ function queue(callback, options) {
2199
2302
  if (typeof callback !== "function") throw new TypeError(MESSAGE_CALLBACK);
2200
2303
  return new Queue(callback, getOptions(options));
2201
2304
  }
2305
+ const abortOptions = { once: true };
2202
2306
  const ERROR_NAME = "QueueError";
2307
+ const EVENT_NAME = "abort";
2203
2308
  const MESSAGE_CALLBACK = "A Queue requires a callback function";
2204
2309
  const MESSAGE_CLEAR = "Queue was cleared";
2205
2310
  const MESSAGE_MAXIMUM = "Queue has reached its maximum size";
@@ -3213,4 +3318,4 @@ function unsmush(value) {
3213
3318
  }
3214
3319
  return unsmushed;
3215
3320
  }
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 };
3321
+ 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, isFulfilled, isHexColor, isHslColor, isHslLike, isHslaColor, isKey, isNullable, isNullableOrEmpty, isNullableOrWhitespace, isNumber, isNumerical, isObject, isOk, isPlainObject, isPrimitive, isRejected, 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 };
@@ -1,4 +1,4 @@
1
- import { ALPHA_FULL_HEX_LONG, ALPHA_FULL_VALUE, ALPHA_NONE_HEX, ALPHA_NONE_VALUE, DEFAULT_ALPHA, MAX_HEX, MAX_PERCENT } from "../constants.js";
1
+ import { ALPHA_FULL_HEX_LONG, DEFAULT_ALPHA } from "../constants.js";
2
2
  function getAlpha(value) {
3
3
  if (typeof value === "number") return getAlphaFromValue(value);
4
4
  if (typeof value === "string" && value !== ALPHA_FULL_HEX_LONG) return {
@@ -1,4 +1,4 @@
1
- import { HEX_BLACK, HEX_WHITE, MAX_DEGREE, MAX_HEX, MAX_PERCENT, SRGB_LUMINANCE_BLUE, SRGB_LUMINANCE_EXPONENT, SRGB_LUMINANCE_GREEN, SRGB_LUMINANCE_MINIMUM, SRGB_LUMINANCE_MODIFIER, SRGB_LUMINANCE_MULTIPLIER, SRGB_LUMINANCE_OFFSET, SRGB_LUMINANCE_RED, SRGB_LUMINANCE_THRESHOLD } from "../constants.js";
1
+ import { HEX_BLACK, HEX_WHITE, SRGB_LUMINANCE_EXPONENT, SRGB_LUMINANCE_MODIFIER, SRGB_LUMINANCE_MULTIPLIER, SRGB_LUMINANCE_OFFSET } from "../constants.js";
2
2
  import { clamp } from "../../internal/number.js";
3
3
  import { getState } from "./state.js";
4
4
  import { Color } from "../instance.js";
@@ -1,4 +1,4 @@
1
- import { ALPHA_FULL_VALUE, ALPHA_NONE_VALUE, EXPRESSION_HEX_LONG, EXPRESSION_HEX_SHORT, KEYS_HSL, KEYS_HSLA, KEYS_RGB, KEYS_RGBA, LENGTH_LONG, LENGTH_SHORT, MAX_DEGREE, MAX_HEX, MAX_PERCENT } from "../constants.js";
1
+ import { EXPRESSION_HEX_LONG, EXPRESSION_HEX_SHORT, KEYS_HSL, KEYS_HSLA, KEYS_RGB, KEYS_RGBA } from "../constants.js";
2
2
  import { between } from "../../internal/number.js";
3
3
  function hasKeys(value, keys) {
4
4
  return typeof value === "object" && value !== null && keys.every((key) => key in value);
@@ -1,4 +1,4 @@
1
- import { ALPHA_FULL_VALUE, DEFAULT_HSL, DEFAULT_RGB, HEX_BLACK, KEYS_HSL, KEYS_RGB, LENGTH_LONG } from "../constants.js";
1
+ import { DEFAULT_HSL, DEFAULT_RGB, HEX_BLACK, KEYS_HSL, KEYS_RGB } from "../constants.js";
2
2
  import { getAlpha } from "./alpha.js";
3
3
  import { isColor, isHexColor, isHslLike, isRgbLike } from "./is.js";
4
4
  import { getDegrees, getHexValue, getPercentage } from "./get.js";
@@ -1,5 +1,5 @@
1
1
  import { join } from "../../internal/string.js";
2
- import { ALPHA_FULL_HEX_LONG, ALPHA_FULL_HEX_SHORT, EXPRESSION_HEX_LONG, EXPRESSION_PREFIX, HEX_BLACK, LENGTH_LONG, LENGTH_SHORT, MAX_HEX } from "../constants.js";
2
+ import { ALPHA_FULL_HEX_LONG, EXPRESSION_HEX_LONG, EXPRESSION_PREFIX, HEX_BLACK } from "../constants.js";
3
3
  import { isHexColor } from "../misc/is.js";
4
4
  import { convertRgbToHsla } from "./rgb.js";
5
5
  function convertHexToRgba(value) {
@@ -1,4 +1,4 @@
1
- import { ALPHA_FULL_VALUE, DEFAULT_HSL, MAX_DEGREE, MAX_HEX, MAX_PERCENT } from "../constants.js";
1
+ import { DEFAULT_HSL } from "../constants.js";
2
2
  import { getAlphaValue } from "../misc/alpha.js";
3
3
  import { isHslLike } from "../misc/is.js";
4
4
  import { getDegrees, getHexValue, getPercentage } from "../misc/get.js";
@@ -1,5 +1,5 @@
1
1
  import { join } from "../../internal/string.js";
2
- import { ALPHA_FULL_VALUE, DEFAULT_RGB, MAX_HEX, MAX_PERCENT } from "../constants.js";
2
+ import { DEFAULT_RGB } from "../constants.js";
3
3
  import { getAlpha, getAlphaValue } from "../misc/alpha.js";
4
4
  import { isRgbLike } from "../misc/is.js";
5
5
  import { getHexValue } from "../misc/get.js";
package/dist/index.js CHANGED
@@ -37,7 +37,7 @@ 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";
40
+ import { PromiseTimeoutError, delay, isFulfilled, isRejected, promises, timed } from "./promise.js";
41
41
  import { QueueError, queue } from "./queue.js";
42
42
  import { setValue } from "./internal/value/set.js";
43
43
  import { fromQuery, toQuery } from "./query.js";
@@ -56,4 +56,4 @@ import { partial } from "./value/partial.js";
56
56
  import { smush } from "./value/smush.js";
57
57
  import { unsmush } from "./value/unsmush.js";
58
58
  import "./value/index.js";
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 };
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, isFulfilled, isHexColor, isHslColor, isHslLike, isHslaColor, isKey, isNullable, isNullableOrEmpty, isNullableOrWhitespace, isNumber, isNumerical, isObject, isOk, isPlainObject, isPrimitive, isRejected, 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 CHANGED
@@ -1,28 +1,98 @@
1
1
  var PromiseTimeoutError = class extends Error {
2
2
  constructor() {
3
3
  super(MESSAGE_TIMEOUT);
4
- this.name = NAME;
4
+ this.name = ERROR_NAME;
5
5
  }
6
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)));
7
+ function delay(options) {
8
+ const { signal, time } = getPromiseOptions(options);
9
+ if (signal?.aborted ?? false) return Promise.reject(signal.reason);
10
+ function abort() {
11
+ clearTimeout(timeout);
12
+ rejector(signal.reason);
13
+ }
14
+ signal?.addEventListener("abort", abort, abortOptions);
15
+ let rejector;
16
+ let timeout;
17
+ return new Promise((resolve, reject) => {
18
+ rejector = reject;
19
+ timeout = setTimeout(() => {
20
+ signal?.removeEventListener("abort", abort);
21
+ resolve();
22
+ }, time);
23
+ });
14
24
  }
15
25
  function getBooleanOrDefault(value, defaultValue) {
16
26
  return typeof value === "boolean" ? value : defaultValue;
17
27
  }
18
28
  function getNumberOrDefault(value) {
19
- return typeof value === "number" ? value : 0;
29
+ return typeof value === "number" && value > 0 ? value : 0;
30
+ }
31
+ function getPromiseOptions(input) {
32
+ if (typeof input === "number") return { time: getNumberOrDefault(input) };
33
+ const options = typeof input === "object" && input !== null ? input : {};
34
+ return {
35
+ signal: options.signal instanceof AbortSignal ? options.signal : void 0,
36
+ time: getNumberOrDefault(options.time)
37
+ };
38
+ }
39
+ function getPromisesOptions(input) {
40
+ if (typeof input === "boolean") return { eager: input };
41
+ if (input instanceof AbortSignal) return {
42
+ eager: false,
43
+ signal: input
44
+ };
45
+ const options = typeof input === "object" && input !== null ? input : {};
46
+ return {
47
+ eager: getBooleanOrDefault(options.eager, false),
48
+ signal: options.signal instanceof AbortSignal ? options.signal : void 0
49
+ };
50
+ }
51
+ function handleResult(status, parameters) {
52
+ const { data, eager, handlers, index, signal, value } = parameters;
53
+ if (signal?.aborted ?? false) return;
54
+ if (eager && status === TYPE_REJECTED) {
55
+ handlers.reject(value);
56
+ return;
57
+ }
58
+ data.result[index] = eager ? value : status === TYPE_FULFILLED ? {
59
+ status,
60
+ value
61
+ } : {
62
+ status,
63
+ reason: value
64
+ };
65
+ if (index === data.last) handlers.resolve(data.result);
20
66
  }
21
- async function promises(items, eager) {
67
+ /**
68
+ * Is the value a fulfilled promise result?
69
+ * @param value Value to check
70
+ * @returns `true` if the value is a fulfilled promise result, `false` otherwise
71
+ */
72
+ function isFulfilled(value) {
73
+ return isType(value, TYPE_FULFILLED);
74
+ }
75
+ /**
76
+ * Is the value a rejected promise result?
77
+ * @param value Value to check
78
+ * @returns `true` if the value is a rejected promise result, `false` otherwise
79
+ */
80
+ function isRejected(value) {
81
+ return isType(value, TYPE_REJECTED);
82
+ }
83
+ function isType(value, type) {
84
+ return typeof value === "object" && value !== null && value.status === type;
85
+ }
86
+ async function promises(items, options) {
22
87
  const actual = items.filter((item) => item instanceof Promise);
23
- if (actual.length === 0) return actual;
24
- const isEager = getBooleanOrDefault(eager, false);
25
88
  const { length } = actual;
89
+ const { eager, signal } = getPromisesOptions(options);
90
+ if (signal?.aborted ?? false) return Promise.reject(signal.reason);
91
+ if (length === 0) return actual;
92
+ function abort() {
93
+ handlers.reject(signal.reason);
94
+ }
95
+ signal?.addEventListener("abort", abort, abortOptions);
26
96
  const data = {
27
97
  last: length - 1,
28
98
  result: []
@@ -33,34 +103,48 @@ async function promises(items, eager) {
33
103
  reject,
34
104
  resolve
35
105
  };
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));
106
+ for (let index = 0; index < length; index += 1) actual[index].then((value) => handleResult(TYPE_FULFILLED, {
107
+ data,
108
+ eager,
109
+ handlers,
110
+ index,
111
+ signal,
112
+ value
113
+ })).catch((reason) => handleResult(TYPE_REJECTED, {
114
+ data,
115
+ eager,
116
+ handlers,
117
+ index,
118
+ signal,
119
+ value: reason
120
+ }));
37
121
  });
38
122
  }
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) {
123
+ function timed(promise, options) {
57
124
  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))]);
125
+ const { signal, time } = getPromiseOptions(options);
126
+ if (signal?.aborted ?? false) return Promise.reject(signal.reason);
127
+ if (time <= 0) return promise;
128
+ function abort() {
129
+ clearTimeout(timeout);
130
+ rejector(signal.reason);
131
+ }
132
+ signal?.addEventListener(EVENT_NAME, abort, abortOptions);
133
+ let rejector;
134
+ let timeout;
135
+ return Promise.race([promise, new Promise((_, reject) => {
136
+ rejector = reject;
137
+ timeout = setTimeout(() => {
138
+ signal?.removeEventListener(EVENT_NAME, abort);
139
+ reject(new PromiseTimeoutError());
140
+ }, time);
141
+ })]);
60
142
  }
143
+ var abortOptions = { once: true };
144
+ var ERROR_NAME = "PromiseTimeoutError";
145
+ var EVENT_NAME = "abort";
61
146
  var MESSAGE_EXPECTATION = "Timed function expected a Promise";
62
147
  var MESSAGE_TIMEOUT = "Promise timed out";
63
- var NAME = "PromiseTimeoutError";
64
148
  var TYPE_FULFILLED = "fulfilled";
65
149
  var TYPE_REJECTED = "rejected";
66
- export { PromiseTimeoutError, delay, promises, timed };
150
+ export { PromiseTimeoutError, delay, isFulfilled, isRejected, promises, timed };
package/dist/queue.js CHANGED
@@ -44,23 +44,30 @@ var Queue = class {
44
44
  /**
45
45
  * Add an item to the queue
46
46
  * @param parameters Parameters to use when item runs
47
+ * @param signal Optional signal to abort the item
47
48
  * @returns Queued item
48
49
  */
49
- add(...parameters) {
50
+ add(parameters, signal) {
50
51
  if (this.full) throw new QueueError(MESSAGE_MAXIMUM);
52
+ const abortSignal = signal instanceof AbortSignal ? signal : void 0;
53
+ if (abortSignal?.aborted ?? false) throw new Error(abortSignal.reason);
54
+ const id = this.#identify();
51
55
  let rejector;
52
56
  let resolver;
53
- const id = this.#identify();
54
57
  const promise = new Promise((resolve, reject) => {
55
58
  rejector = reject;
56
59
  resolver = resolve;
57
60
  });
61
+ const aborter = abortSignal == null ? void 0 : () => rejector(abortSignal.reason);
62
+ signal?.addEventListener(EVENT_NAME, aborter, abortOptions);
58
63
  this.#items.push({
59
64
  id,
60
65
  parameters,
61
66
  promise,
67
+ abort: aborter,
62
68
  reject: rejector,
63
- resolve: resolver
69
+ resolve: resolver,
70
+ signal: abortSignal
64
71
  });
65
72
  if (this.#options.autostart) this.#run();
66
73
  return {
@@ -74,7 +81,11 @@ var Queue = class {
74
81
  clear() {
75
82
  const items = this.#items.splice(0);
76
83
  const { length } = items;
77
- for (let index = 0; index < length; index += 1) items[index].reject(new QueueError(MESSAGE_CLEAR));
84
+ for (let index = 0; index < length; index += 1) {
85
+ const { abort, reject, signal } = items[index];
86
+ reject(new QueueError(MESSAGE_CLEAR));
87
+ signal?.removeEventListener(EVENT_NAME, abort);
88
+ }
78
89
  }
79
90
  /**
80
91
  * Pause the queue
@@ -92,8 +103,9 @@ var Queue = class {
92
103
  remove(id) {
93
104
  const index = this.#items.findIndex((item) => item.id === id);
94
105
  if (index > -1) {
95
- const [item] = this.#items.splice(index, 1);
96
- item.reject(new QueueError(MESSAGE_REMOVE));
106
+ const { abort, reject, signal } = this.#items.splice(index, 1)[0];
107
+ reject(new QueueError(MESSAGE_REMOVE));
108
+ signal?.removeEventListener(EVENT_NAME, abort);
97
109
  }
98
110
  }
99
111
  /**
@@ -121,17 +133,24 @@ var Queue = class {
121
133
  let handler;
122
134
  let result;
123
135
  try {
124
- result = await this.#callback(...item.parameters);
125
- handler = item.resolve;
136
+ if (!(item.signal?.aborted ?? false)) {
137
+ result = await this.#callback(...item.parameters);
138
+ handler = item.resolve;
139
+ }
126
140
  } catch (thrown) {
127
141
  result = thrown;
128
142
  handler = item.reject;
129
143
  }
130
144
  if (this.#paused) {
131
- this.#handled.push(() => handler(result));
145
+ const paused = item;
146
+ this.#handled.push(() => {
147
+ paused.signal?.removeEventListener(EVENT_NAME, paused.abort);
148
+ if (!(paused.signal?.aborted ?? false)) handler(result);
149
+ });
132
150
  break;
133
151
  }
134
- handler(result);
152
+ item.signal?.removeEventListener(EVENT_NAME, item.abort);
153
+ if (!(item.signal?.aborted ?? false)) handler(result);
135
154
  item = this.#items.shift();
136
155
  }
137
156
  this.#runners -= 1;
@@ -161,7 +180,9 @@ function queue(callback, options) {
161
180
  if (typeof callback !== "function") throw new TypeError(MESSAGE_CALLBACK);
162
181
  return new Queue(callback, getOptions(options));
163
182
  }
183
+ var abortOptions = { once: true };
164
184
  var ERROR_NAME = "QueueError";
185
+ var EVENT_NAME = "abort";
165
186
  var MESSAGE_CALLBACK = "A Queue requires a callback function";
166
187
  var MESSAGE_CLEAR = "Queue was cleared";
167
188
  var MESSAGE_MAXIMUM = "Queue has reached its maximum size";
package/package.json CHANGED
@@ -5,15 +5,15 @@
5
5
  },
6
6
  "description": "Atomic utilities for making your JavaScript better.",
7
7
  "devDependencies": {
8
- "@types/node": "^25.2",
8
+ "@types/node": "^25.3",
9
9
  "@vitest/coverage-istanbul": "^4",
10
10
  "jsdom": "^28.1",
11
11
  "oxfmt": "^0.33",
12
12
  "oxlint": "^1.48",
13
- "rolldown": "1.0.0-rc.4",
13
+ "rolldown": "1.0.0-rc.5",
14
14
  "tslib": "^2.8",
15
15
  "typescript": "^5.9",
16
- "vite": "8.0.0-beta.14",
16
+ "vite": "8.0.0-beta.15",
17
17
  "vitest": "^4"
18
18
  },
19
19
  "exports": {
@@ -120,5 +120,5 @@
120
120
  },
121
121
  "type": "module",
122
122
  "types": "./types/index.d.ts",
123
- "version": "0.133.0"
123
+ "version": "0.134.0"
124
124
  }
package/src/models.ts CHANGED
@@ -135,6 +135,14 @@ export type PlainObject = Record<PropertyKey, unknown>;
135
135
  */
136
136
  export type Primitive = null | undefined | string | number | boolean | symbol | bigint;
137
137
 
138
+ /**
139
+ * Set required keys for a type
140
+ */
141
+ export type RequiredKeys<Model extends object, Keys extends keyof Model> = Required<
142
+ Pick<Model, Keys>
143
+ > &
144
+ Omit<Model, Keys>;
145
+
138
146
  /**
139
147
  * Flattens the type to improve type hints in IDEs
140
148
  *
package/src/promise.ts CHANGED
@@ -1,20 +1,47 @@
1
1
  // #region Types
2
2
 
3
+ import type {RequiredKeys} from './models';
4
+
3
5
  type Data<Items extends unknown[]> = {
4
6
  last: number;
5
7
  result: Items | PromisesResult<Items>;
6
8
  };
7
9
 
10
+ type FulfilledPromiseResult<Value> = {
11
+ status: typeof TYPE_FULFILLED;
12
+ value: Value;
13
+ };
14
+
8
15
  type Handlers<Items extends unknown[]> = {
9
16
  resolve: (value: Items | PromisesResult<Items>) => void;
10
17
  reject: (reason: unknown) => void;
11
18
  };
12
19
 
20
+ type Parameters<Items extends unknown[]> = {
21
+ data: Data<Items>;
22
+ eager: boolean;
23
+ handlers: Handlers<Items>;
24
+ index: number;
25
+ signal?: AbortSignal;
26
+ value?: unknown;
27
+ };
28
+
29
+ type PromiseOptions = {
30
+ /**
31
+ * AbortSignal for aborting the promise; when aborted, the promise will reject with the reason of the signal
32
+ */
33
+ signal?: AbortSignal;
34
+ /**
35
+ * How long to wait for (in milliseconds; defaults to `0`)
36
+ */
37
+ time?: number;
38
+ };
39
+
13
40
  export class PromiseTimeoutError extends Error {
14
41
  constructor() {
15
42
  super(MESSAGE_TIMEOUT);
16
43
 
17
- this.name = NAME;
44
+ this.name = ERROR_NAME;
18
45
  }
19
46
  }
20
47
 
@@ -22,31 +49,67 @@ type Promises<Items extends unknown[]> = {
22
49
  [K in keyof Items]: Promise<Items[K]>;
23
50
  };
24
51
 
52
+ type PromisesOptions = {
53
+ eager?: boolean;
54
+ signal?: AbortSignal;
55
+ };
56
+
25
57
  type PromisesResult<Items extends unknown[]> = {
26
- [K in keyof Items]: ResolvedResult<Items[K]> | RejectedResult;
58
+ [K in keyof Items]: PromisesResultItem<Items[K]>;
27
59
  };
28
60
 
29
- type RejectedResult = {
61
+ type PromisesResultItem<Value> = FulfilledPromiseResult<Value> | RejectedPromiseResult;
62
+
63
+ type RejectedPromiseResult = {
30
64
  status: typeof TYPE_REJECTED;
31
65
  reason: unknown;
32
66
  };
33
67
 
34
- type ResolvedResult<Value> = {
35
- status: typeof TYPE_FULFILLED;
36
- value: Value;
37
- };
38
-
39
68
  // #endregion
40
69
 
41
70
  // #region Functions
42
71
 
72
+ /**
73
+ * Create a delayed promise that resolves after a certain amount of time, or rejects if aborted
74
+ * @param options Options for the delay
75
+ * @returns A delayed promise
76
+ */
77
+ export function delay(options?: PromiseOptions): Promise<void>;
78
+
43
79
  /**
44
80
  * 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
81
+ * @param time How long to wait for _(in milliseconds; defaults to `0`)_
82
+ * @returns A delayed promise
47
83
  */
48
- export function delay(time?: number): Promise<void> {
49
- return new Promise(resolve => setTimeout(resolve, getNumberOrDefault(time)));
84
+ export function delay(time?: number): Promise<void>;
85
+
86
+ export function delay(options?: unknown): Promise<void> {
87
+ const {signal, time} = getPromiseOptions(options);
88
+
89
+ if (signal?.aborted ?? false) {
90
+ return Promise.reject(signal!.reason);
91
+ }
92
+
93
+ function abort(): void {
94
+ clearTimeout(timeout);
95
+
96
+ rejector(signal!.reason);
97
+ }
98
+
99
+ signal?.addEventListener('abort', abort, abortOptions);
100
+
101
+ let rejector: (reason: unknown) => void;
102
+ let timeout: ReturnType<typeof setTimeout>;
103
+
104
+ return new Promise((resolve, reject) => {
105
+ rejector = reject;
106
+
107
+ timeout = setTimeout(() => {
108
+ signal?.removeEventListener('abort', abort);
109
+
110
+ resolve();
111
+ }, time);
112
+ });
50
113
  }
51
114
 
52
115
  function getBooleanOrDefault(value: unknown, defaultValue: boolean): boolean {
@@ -54,9 +117,105 @@ function getBooleanOrDefault(value: unknown, defaultValue: boolean): boolean {
54
117
  }
55
118
 
56
119
  function getNumberOrDefault(value: unknown): number {
57
- return typeof value === 'number' ? value : 0;
120
+ return typeof value === 'number' && value > 0 ? value : 0;
121
+ }
122
+
123
+ function getPromiseOptions(input: unknown): RequiredKeys<PromiseOptions, 'time'> {
124
+ if (typeof input === 'number') {
125
+ return {
126
+ time: getNumberOrDefault(input),
127
+ };
128
+ }
129
+
130
+ const options = typeof input === 'object' && input !== null ? (input as PromiseOptions) : {};
131
+
132
+ return {
133
+ signal: options.signal instanceof AbortSignal ? options.signal : undefined,
134
+ time: getNumberOrDefault(options.time),
135
+ };
136
+ }
137
+
138
+ function getPromisesOptions(input: unknown): RequiredKeys<PromisesOptions, 'eager'> {
139
+ if (typeof input === 'boolean') {
140
+ return {eager: input};
141
+ }
142
+
143
+ if (input instanceof AbortSignal) {
144
+ return {eager: false, signal: input};
145
+ }
146
+
147
+ const options = typeof input === 'object' && input !== null ? (input as PromisesOptions) : {};
148
+
149
+ return {
150
+ eager: getBooleanOrDefault(options.eager, false),
151
+ signal: options.signal instanceof AbortSignal ? options.signal : undefined,
152
+ };
153
+ }
154
+
155
+ function handleResult<Items extends unknown[]>(
156
+ status: string,
157
+ parameters: Parameters<Items>,
158
+ ): void {
159
+ const {data, eager, handlers, index, signal, value} = parameters;
160
+
161
+ if (signal?.aborted ?? false) {
162
+ return;
163
+ }
164
+
165
+ if (eager && status === TYPE_REJECTED) {
166
+ handlers.reject(value);
167
+
168
+ return;
169
+ }
170
+
171
+ (data.result as unknown[])[index] = eager
172
+ ? value
173
+ : status === TYPE_FULFILLED
174
+ ? {status, value}
175
+ : {status, reason: value};
176
+
177
+ if (index === data.last) {
178
+ handlers.resolve(data.result as Items | PromisesResult<Items>);
179
+ }
58
180
  }
59
181
 
182
+ /**
183
+ * Is the value a fulfilled promise result?
184
+ * @param value Value to check
185
+ * @returns `true` if the value is a fulfilled promise result, `false` otherwise
186
+ */
187
+ export function isFulfilled<Value>(value: unknown): value is FulfilledPromiseResult<Value> {
188
+ return isType(value, TYPE_FULFILLED);
189
+ }
190
+
191
+ /**
192
+ * Is the value a rejected promise result?
193
+ * @param value Value to check
194
+ * @returns `true` if the value is a rejected promise result, `false` otherwise
195
+ */
196
+ export function isRejected(value: unknown): value is RejectedPromiseResult {
197
+ return isType(value, TYPE_REJECTED);
198
+ }
199
+
200
+ function isType(value: unknown, type: string): boolean {
201
+ return (
202
+ typeof value === 'object' &&
203
+ value !== null &&
204
+ (value as PromisesResultItem<unknown>).status === type
205
+ );
206
+ }
207
+
208
+ /**
209
+ * ---
210
+ * @param items List of promises
211
+ * @param options Options for handling the promises
212
+ * @return List of results
213
+ */
214
+ export async function promises<Items extends unknown[], Options extends PromisesOptions>(
215
+ items: Promises<Items>,
216
+ options?: Options,
217
+ ): Promise<Options['eager'] extends true ? Items : PromisesResult<Items>>;
218
+
60
219
  /**
61
220
  * 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
221
  * @param items List of promises
@@ -71,25 +230,36 @@ export async function promises<Items extends unknown[]>(
71
230
  /**
72
231
  * Handle a list of promises, returning their results in an ordered array of rejected and resolved results
73
232
  * @param items List of promises
233
+ * @param signal AbortSignal for aborting the operation _(when aborted, the promise will reject with the reason of the signal)_
74
234
  * @return List of results
75
235
  */
76
236
  export async function promises<Items extends unknown[]>(
77
237
  items: Promises<Items>,
238
+ signal?: AbortSignal,
78
239
  ): Promise<PromisesResult<Items>>;
79
240
 
80
241
  export async function promises<Items extends unknown[]>(
81
242
  items: Promises<Items>,
82
- eager?: unknown,
243
+ options?: unknown,
83
244
  ): Promise<Items | PromisesResult<Items>> {
84
245
  const actual = items.filter(item => item instanceof Promise);
246
+ const {length} = actual;
247
+
248
+ const {eager, signal} = getPromisesOptions(options);
85
249
 
86
- if (actual.length === 0) {
250
+ if (signal?.aborted ?? false) {
251
+ return Promise.reject(signal!.reason);
252
+ }
253
+
254
+ if (length === 0) {
87
255
  return actual as unknown as Items | PromisesResult<Items>;
88
256
  }
89
257
 
90
- const isEager = getBooleanOrDefault(eager, false);
258
+ function abort(): void {
259
+ handlers.reject(signal!.reason);
260
+ }
91
261
 
92
- const {length} = actual;
262
+ signal?.addEventListener('abort', abort, abortOptions);
93
263
 
94
264
  const data: Data<Items> = {
95
265
  last: length - 1,
@@ -103,58 +273,55 @@ export async function promises<Items extends unknown[]>(
103
273
 
104
274
  for (let index = 0; index < length; index += 1) {
105
275
  void actual[index]
106
- .then(value => resolveResult(index, data, handlers, value, isEager))
107
- .catch(reason => rejectResult(index, data, handlers, reason, isEager));
276
+ .then(value => handleResult(TYPE_FULFILLED, {data, eager, handlers, index, signal, value}))
277
+ .catch(reason =>
278
+ handleResult(TYPE_REJECTED, {data, eager, handlers, index, signal, value: reason}),
279
+ );
108
280
  }
109
281
  });
110
282
  }
111
283
 
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
- }
284
+ export function timed(promise: Promise<unknown>, options: PromiseOptions): Promise<unknown>;
129
285
 
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};
286
+ export function timed(promise: Promise<unknown>, time: number): Promise<unknown>;
138
287
 
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> {
288
+ export function timed(promise: Promise<unknown>, options: unknown): Promise<unknown> {
145
289
  if (!(promise instanceof Promise)) {
146
290
  throw new TypeError(MESSAGE_EXPECTATION);
147
291
  }
148
292
 
149
- const time = getNumberOrDefault(timeout);
293
+ const {signal, time} = getPromiseOptions(options);
294
+
295
+ if (signal?.aborted ?? false) {
296
+ return Promise.reject(signal!.reason);
297
+ }
150
298
 
151
299
  if (time <= 0) {
152
300
  return promise;
153
301
  }
154
302
 
303
+ function abort(): void {
304
+ clearTimeout(timeout);
305
+
306
+ rejector(signal!.reason);
307
+ }
308
+
309
+ signal?.addEventListener(EVENT_NAME, abort, abortOptions);
310
+
311
+ let rejector: (reason: unknown) => void;
312
+ let timeout: ReturnType<typeof setTimeout>;
313
+
155
314
  return Promise.race([
156
315
  promise,
157
- new Promise((_, reject) => setTimeout(() => reject(new PromiseTimeoutError()), timeout)),
316
+ new Promise((_, reject) => {
317
+ rejector = reject;
318
+
319
+ timeout = setTimeout(() => {
320
+ signal?.removeEventListener(EVENT_NAME, abort);
321
+
322
+ reject(new PromiseTimeoutError());
323
+ }, time);
324
+ }),
158
325
  ]);
159
326
  }
160
327
 
@@ -162,12 +329,16 @@ export function timed(promise: Promise<unknown>, timeout: number): Promise<unkno
162
329
 
163
330
  // #region Variables
164
331
 
332
+ const abortOptions = {once: true};
333
+
334
+ const ERROR_NAME = 'PromiseTimeoutError';
335
+
336
+ const EVENT_NAME = 'abort';
337
+
165
338
  const MESSAGE_EXPECTATION = 'Timed function expected a Promise';
166
339
 
167
340
  const MESSAGE_TIMEOUT = 'Promise timed out';
168
341
 
169
- const NAME = 'PromiseTimeoutError';
170
-
171
342
  const TYPE_FULFILLED = 'fulfilled';
172
343
 
173
344
  const TYPE_REJECTED = 'rejected';
package/src/queue.ts CHANGED
@@ -62,29 +62,42 @@ class Queue<CallbackParameters extends Parameters<GenericAsyncCallback>, Callbac
62
62
  /**
63
63
  * Add an item to the queue
64
64
  * @param parameters Parameters to use when item runs
65
+ * @param signal Optional signal to abort the item
65
66
  * @returns Queued item
66
67
  */
67
- add(...parameters: CallbackParameters): Queued<CallbackResult> {
68
+ add(parameters: CallbackParameters, signal?: AbortSignal): Queued<CallbackResult> {
68
69
  if (this.full) {
69
70
  throw new QueueError(MESSAGE_MAXIMUM);
70
71
  }
71
72
 
72
- let rejector: (reason?: unknown) => void;
73
- let resolver: (value: CallbackResult) => void;
73
+ const abortSignal = signal instanceof AbortSignal ? signal : undefined;
74
+
75
+ if (abortSignal?.aborted ?? false) {
76
+ throw new Error(abortSignal!.reason);
77
+ }
74
78
 
75
79
  const id = this.#identify();
76
80
 
81
+ let rejector: (reason?: unknown) => void;
82
+ let resolver: (value: CallbackResult) => void;
83
+
77
84
  const promise = new Promise<CallbackResult>((resolve, reject) => {
78
85
  rejector = reject;
79
86
  resolver = resolve;
80
87
  });
81
88
 
89
+ const aborter = abortSignal == null ? undefined : () => rejector(abortSignal.reason);
90
+
91
+ signal?.addEventListener(EVENT_NAME, aborter!, abortOptions);
92
+
82
93
  this.#items.push({
83
94
  id,
84
95
  parameters,
85
96
  promise,
97
+ abort: aborter,
86
98
  reject: rejector!,
87
99
  resolve: resolver!,
100
+ signal: abortSignal,
88
101
  });
89
102
 
90
103
  if (this.#options.autostart) {
@@ -102,7 +115,11 @@ class Queue<CallbackParameters extends Parameters<GenericAsyncCallback>, Callbac
102
115
  const {length} = items;
103
116
 
104
117
  for (let index = 0; index < length; index += 1) {
105
- items[index].reject(new QueueError(MESSAGE_CLEAR));
118
+ const {abort, reject, signal} = items[index];
119
+
120
+ reject(new QueueError(MESSAGE_CLEAR));
121
+
122
+ signal?.removeEventListener(EVENT_NAME, abort!);
106
123
  }
107
124
  }
108
125
 
@@ -124,9 +141,11 @@ class Queue<CallbackParameters extends Parameters<GenericAsyncCallback>, Callbac
124
141
  const index = this.#items.findIndex(item => item.id === id);
125
142
 
126
143
  if (index > -1) {
127
- const [item] = this.#items.splice(index, 1);
144
+ const {abort, reject, signal} = this.#items.splice(index, 1)[0];
145
+
146
+ reject(new QueueError(MESSAGE_REMOVE));
128
147
 
129
- item.reject(new QueueError(MESSAGE_REMOVE));
148
+ signal?.removeEventListener(EVENT_NAME, abort!);
130
149
  }
131
150
  }
132
151
 
@@ -172,20 +191,34 @@ class Queue<CallbackParameters extends Parameters<GenericAsyncCallback>, Callbac
172
191
  let result: unknown | CallbackResult;
173
192
 
174
193
  try {
175
- result = await this.#callback(...item.parameters);
176
- handler = item.resolve;
194
+ if (!(item.signal?.aborted ?? false)) {
195
+ result = await this.#callback(...item.parameters);
196
+ handler = item.resolve;
197
+ }
177
198
  } catch (thrown) {
178
199
  result = thrown;
179
200
  handler = item.reject;
180
201
  }
181
202
 
182
203
  if (this.#paused) {
183
- this.#handled.push(() => handler(result));
204
+ const paused = item;
205
+
206
+ this.#handled.push(() => {
207
+ paused.signal?.removeEventListener(EVENT_NAME, paused.abort!);
208
+
209
+ if (!(paused.signal?.aborted ?? false)) {
210
+ handler(result);
211
+ }
212
+ });
184
213
 
185
214
  break;
186
215
  }
187
216
 
188
- handler(result);
217
+ item.signal?.removeEventListener(EVENT_NAME, item.abort!);
218
+
219
+ if (!(item.signal?.aborted ?? false)) {
220
+ handler!(result as CallbackResult);
221
+ }
189
222
 
190
223
  item = this.#items.shift();
191
224
  }
@@ -223,11 +256,13 @@ type Queued<Value> = {
223
256
  };
224
257
 
225
258
  type QueuedItem<CallbackParameters extends Parameters<GenericAsyncCallback>, CallbackResult> = {
259
+ abort?: () => void;
226
260
  id: number;
227
261
  parameters: CallbackParameters;
228
262
  promise: Promise<CallbackResult>;
229
263
  reject: (reason?: unknown) => void;
230
264
  resolve: (value: CallbackResult) => void;
265
+ signal?: AbortSignal;
231
266
  };
232
267
 
233
268
  // #endregion
@@ -289,8 +324,12 @@ function queue(
289
324
 
290
325
  // #region Variables
291
326
 
327
+ const abortOptions = {once: true};
328
+
292
329
  const ERROR_NAME = 'QueueError';
293
330
 
331
+ const EVENT_NAME = 'abort';
332
+
294
333
  const MESSAGE_CALLBACK = 'A Queue requires a callback function';
295
334
 
296
335
  const MESSAGE_CLEAR = 'Queue was cleared';
package/types/models.d.ts CHANGED
@@ -78,6 +78,10 @@ export type PlainObject = Record<PropertyKey, unknown>;
78
78
  * _(Thanks, type-fest!)_
79
79
  */
80
80
  export type Primitive = null | undefined | string | number | boolean | symbol | bigint;
81
+ /**
82
+ * Set required keys for a type
83
+ */
84
+ export type RequiredKeys<Model extends object, Keys extends keyof Model> = Required<Pick<Model, Keys>> & Omit<Model, Keys>;
81
85
  /**
82
86
  * Flattens the type to improve type hints in IDEs
83
87
  *
@@ -1,26 +1,66 @@
1
+ type FulfilledPromiseResult<Value> = {
2
+ status: typeof TYPE_FULFILLED;
3
+ value: Value;
4
+ };
5
+ type PromiseOptions = {
6
+ /**
7
+ * AbortSignal for aborting the promise; when aborted, the promise will reject with the reason of the signal
8
+ */
9
+ signal?: AbortSignal;
10
+ /**
11
+ * How long to wait for (in milliseconds; defaults to `0`)
12
+ */
13
+ time?: number;
14
+ };
1
15
  export declare class PromiseTimeoutError extends Error {
2
16
  constructor();
3
17
  }
4
18
  type Promises<Items extends unknown[]> = {
5
19
  [K in keyof Items]: Promise<Items[K]>;
6
20
  };
21
+ type PromisesOptions = {
22
+ eager?: boolean;
23
+ signal?: AbortSignal;
24
+ };
7
25
  type PromisesResult<Items extends unknown[]> = {
8
- [K in keyof Items]: ResolvedResult<Items[K]> | RejectedResult;
26
+ [K in keyof Items]: PromisesResultItem<Items[K]>;
9
27
  };
10
- type RejectedResult = {
28
+ type PromisesResultItem<Value> = FulfilledPromiseResult<Value> | RejectedPromiseResult;
29
+ type RejectedPromiseResult = {
11
30
  status: typeof TYPE_REJECTED;
12
31
  reason: unknown;
13
32
  };
14
- type ResolvedResult<Value> = {
15
- status: typeof TYPE_FULFILLED;
16
- value: Value;
17
- };
33
+ /**
34
+ * Create a delayed promise that resolves after a certain amount of time, or rejects if aborted
35
+ * @param options Options for the delay
36
+ * @returns A delayed promise
37
+ */
38
+ export declare function delay(options?: PromiseOptions): Promise<void>;
18
39
  /**
19
40
  * 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
41
+ * @param time How long to wait for _(in milliseconds; defaults to `0`)_
42
+ * @returns A delayed promise
22
43
  */
23
44
  export declare function delay(time?: number): Promise<void>;
45
+ /**
46
+ * Is the value a fulfilled promise result?
47
+ * @param value Value to check
48
+ * @returns `true` if the value is a fulfilled promise result, `false` otherwise
49
+ */
50
+ export declare function isFulfilled<Value>(value: unknown): value is FulfilledPromiseResult<Value>;
51
+ /**
52
+ * Is the value a rejected promise result?
53
+ * @param value Value to check
54
+ * @returns `true` if the value is a rejected promise result, `false` otherwise
55
+ */
56
+ export declare function isRejected(value: unknown): value is RejectedPromiseResult;
57
+ /**
58
+ * ---
59
+ * @param items List of promises
60
+ * @param options Options for handling the promises
61
+ * @return List of results
62
+ */
63
+ export declare function promises<Items extends unknown[], Options extends PromisesOptions>(items: Promises<Items>, options?: Options): Promise<Options['eager'] extends true ? Items : PromisesResult<Items>>;
24
64
  /**
25
65
  * 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
66
  * @param items List of promises
@@ -31,10 +71,12 @@ export declare function promises<Items extends unknown[]>(items: Promises<Items>
31
71
  /**
32
72
  * Handle a list of promises, returning their results in an ordered array of rejected and resolved results
33
73
  * @param items List of promises
74
+ * @param signal AbortSignal for aborting the operation _(when aborted, the promise will reject with the reason of the signal)_
34
75
  * @return List of results
35
76
  */
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>;
77
+ export declare function promises<Items extends unknown[]>(items: Promises<Items>, signal?: AbortSignal): Promise<PromisesResult<Items>>;
78
+ export declare function timed(promise: Promise<unknown>, options: PromiseOptions): Promise<unknown>;
79
+ export declare function timed(promise: Promise<unknown>, time: number): Promise<unknown>;
38
80
  declare const TYPE_FULFILLED = "fulfilled";
39
81
  declare const TYPE_REJECTED = "rejected";
40
82
  export {};
package/types/queue.d.ts CHANGED
@@ -25,9 +25,10 @@ declare class Queue<CallbackParameters extends Parameters<GenericAsyncCallback>,
25
25
  /**
26
26
  * Add an item to the queue
27
27
  * @param parameters Parameters to use when item runs
28
+ * @param signal Optional signal to abort the item
28
29
  * @returns Queued item
29
30
  */
30
- add(...parameters: CallbackParameters): Queued<CallbackResult>;
31
+ add(parameters: CallbackParameters, signal?: AbortSignal): Queued<CallbackResult>;
31
32
  /**
32
33
  * Remove and reject all items in the queue
33
34
  */