@sv443-network/userutils 9.3.0 → 9.4.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/index.js CHANGED
@@ -1,6 +1,8 @@
1
1
  import { createNanoEvents } from 'nanoevents';
2
2
 
3
3
  var __defProp = Object.defineProperty;
4
+ var __defProps = Object.defineProperties;
5
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
4
6
  var __getOwnPropSymbols = Object.getOwnPropertySymbols;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __propIsEnum = Object.prototype.propertyIsEnumerable;
@@ -16,6 +18,7 @@ var __spreadValues = (a, b) => {
16
18
  }
17
19
  return a;
18
20
  };
21
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
19
22
  var __objRest = (source, exclude) => {
20
23
  var target = {};
21
24
  for (var prop in source)
@@ -248,16 +251,16 @@ function addGlobalStyle(style) {
248
251
  function preloadImages(srcUrls, rejects = false) {
249
252
  const promises = srcUrls.map((src) => new Promise((res, rej) => {
250
253
  const image = new Image();
251
- image.src = src;
252
254
  image.addEventListener("load", () => res(image));
253
255
  image.addEventListener("error", (evt) => rejects && rej(evt));
256
+ image.src = src;
254
257
  }));
255
258
  return Promise.allSettled(promises);
256
259
  }
257
260
  function openInNewTab(href, background, additionalProps) {
258
- var _a;
259
261
  try {
260
- (_a = GM.openInTab) == null ? void 0 : _a.call(GM, href, background);
262
+ if (typeof window.GM === "object")
263
+ GM.openInTab(href, background);
261
264
  } catch (e) {
262
265
  const openElem = document.createElement("a");
263
266
  Object.assign(openElem, __spreadValues({
@@ -274,12 +277,17 @@ function openInNewTab(href, background, additionalProps) {
274
277
  });
275
278
  document.body.appendChild(openElem);
276
279
  openElem.click();
277
- setTimeout(openElem.remove, 0);
280
+ setTimeout(() => {
281
+ try {
282
+ openElem.remove();
283
+ } catch (e2) {
284
+ }
285
+ }, 0);
278
286
  }
279
287
  }
280
288
  function interceptEvent(eventObject, eventName, predicate = () => true) {
281
289
  var _a;
282
- if (((_a = GM == null ? undefined : GM.info) == null ? undefined : _a.scriptHandler) && GM.info.scriptHandler === "FireMonkey" && (eventObject === window || eventObject === getUnsafeWindow()))
290
+ if (typeof window.GM === "object" && ((_a = GM == null ? undefined : GM.info) == null ? undefined : _a.scriptHandler) && GM.info.scriptHandler === "FireMonkey" && (eventObject === window || eventObject === getUnsafeWindow()))
283
291
  throw new PlatformError("Intercepting window events is not supported on FireMonkey due to the isolated context the userscript runs in.");
284
292
  Error.stackTraceLimit = Math.max(Error.stackTraceLimit, 100);
285
293
  if (isNaN(Error.stackTraceLimit))
@@ -439,6 +447,8 @@ function computeHash(input, algorithm = "SHA-256") {
439
447
  });
440
448
  }
441
449
  function randomId(length = 16, radix = 16, enhancedEntropy = false, randomCase = true) {
450
+ if (length < 1)
451
+ throw new RangeError("The length argument must be at least 1");
442
452
  if (radix < 2 || radix > 36)
443
453
  throw new RangeError("The radix argument must be between 2 and 36");
444
454
  let arr = [];
@@ -783,7 +793,7 @@ var DataStoreSerializer = class _DataStoreSerializer {
783
793
  deserializePartial(stores, data) {
784
794
  return __async(this, null, function* () {
785
795
  const deserStores = typeof data === "string" ? JSON.parse(data) : data;
786
- if (!Array.isArray(deserStores) || !deserStores.every(_DataStoreSerializer.isSerializedDataStore))
796
+ if (!Array.isArray(deserStores) || !deserStores.every(_DataStoreSerializer.isSerializedDataStoreObj))
787
797
  throw new TypeError("Invalid serialized data format! Expected an array of SerializedDataStore objects.");
788
798
  for (const storeData of deserStores.filter((s) => typeof stores === "function" ? stores(s.id) : stores.includes(s.id))) {
789
799
  const storeInst = this.stores.find((s) => s.id === storeData.id);
@@ -816,41 +826,59 @@ Has: ${checksum}`);
816
826
  /**
817
827
  * Loads the persistent data of the DataStore instances into the in-memory cache.
818
828
  * Also triggers the migration process if the data format has changed.
829
+ * @param stores An array of store IDs or a function that takes the store IDs and returns a boolean - if omitted, all stores will be loaded
819
830
  * @returns Returns a PromiseSettledResult array with the results of each DataStore instance in the format `{ id: string, data: object }`
820
831
  */
821
- loadStoresData() {
832
+ loadStoresData(stores) {
822
833
  return __async(this, null, function* () {
823
- return Promise.allSettled(this.stores.map(
824
- (store) => __async(this, null, function* () {
834
+ return Promise.allSettled(
835
+ this.getStoresFiltered(stores).map((store) => __async(this, null, function* () {
825
836
  return {
826
837
  id: store.id,
827
838
  data: yield store.loadData()
828
839
  };
829
- })
830
- ));
840
+ }))
841
+ );
831
842
  });
832
843
  }
833
- /** Resets the persistent data of the DataStore instances to their default values. */
834
- resetStoresData() {
844
+ /**
845
+ * Resets the persistent and in-memory data of the DataStore instances to their default values.
846
+ * @param stores An array of store IDs or a function that takes the store IDs and returns a boolean - if omitted, all stores will be affected
847
+ */
848
+ resetStoresData(stores) {
835
849
  return __async(this, null, function* () {
836
- return Promise.allSettled(this.stores.map((store) => store.saveDefaultData()));
850
+ return Promise.allSettled(
851
+ this.getStoresFiltered(stores).map((store) => store.saveDefaultData())
852
+ );
837
853
  });
838
854
  }
839
855
  /**
840
856
  * Deletes the persistent data of the DataStore instances.
841
- * Leaves the in-memory data untouched.
857
+ * Leaves the in-memory data untouched.
858
+ * @param stores An array of store IDs or a function that takes the store IDs and returns a boolean - if omitted, all stores will be affected
842
859
  */
843
- deleteStoresData() {
860
+ deleteStoresData(stores) {
844
861
  return __async(this, null, function* () {
845
- return Promise.allSettled(this.stores.map((store) => store.deleteData()));
862
+ return Promise.allSettled(
863
+ this.getStoresFiltered(stores).map((store) => store.deleteData())
864
+ );
846
865
  });
847
866
  }
867
+ /** Checks if a given value is an array of SerializedDataStore objects */
868
+ static isSerializedDataStoreObjArray(obj) {
869
+ return Array.isArray(obj) && obj.every((o) => typeof o === "object" && o !== null && "id" in o && "data" in o && "formatVersion" in o && "encoded" in o);
870
+ }
848
871
  /** Checks if a given value is a SerializedDataStore object */
849
- static isSerializedDataStore(obj) {
872
+ static isSerializedDataStoreObj(obj) {
850
873
  return typeof obj === "object" && obj !== null && "id" in obj && "data" in obj && "formatVersion" in obj && "encoded" in obj;
851
874
  }
875
+ /** Returns the DataStore instances whose IDs match the provided array or function */
876
+ getStoresFiltered(stores) {
877
+ return this.stores.filter((s) => typeof stores === "undefined" ? true : Array.isArray(stores) ? stores.includes(s.id) : stores(s.id));
878
+ }
852
879
  };
853
880
  var NanoEmitter = class {
881
+ /** Creates a new instance of NanoEmitter - a lightweight event emitter with helper methods and a strongly typed event map */
854
882
  constructor(options = {}) {
855
883
  __publicField(this, "events", createNanoEvents());
856
884
  __publicField(this, "eventUnsubscribes", []);
@@ -859,7 +887,27 @@ var NanoEmitter = class {
859
887
  publicEmit: false
860
888
  }, options);
861
889
  }
862
- /** Subscribes to an event - returns a function that unsubscribes the event listener */
890
+ /**
891
+ * Subscribes to an event and calls the callback when it's emitted.
892
+ * @param event The event to subscribe to. Use `as "_"` in case your event names aren't thoroughly typed (like when using a template literal, e.g. \`event-${val}\` as "_")
893
+ * @returns Returns a function that can be called to unsubscribe the event listener
894
+ * @example ```ts
895
+ * const emitter = new NanoEmitter<{
896
+ * foo: (bar: string) => void;
897
+ * }>({
898
+ * publicEmit: true,
899
+ * });
900
+ *
901
+ * let i = 0;
902
+ * const unsub = emitter.on("foo", (bar) => {
903
+ * // unsubscribe after 10 events:
904
+ * if(++i === 10) unsub();
905
+ * console.log(bar);
906
+ * });
907
+ *
908
+ * emitter.emit("foo", "bar");
909
+ * ```
910
+ */
863
911
  on(event, cb) {
864
912
  let unsub;
865
913
  const unsubProxy = () => {
@@ -872,19 +920,43 @@ var NanoEmitter = class {
872
920
  this.eventUnsubscribes.push(unsub);
873
921
  return unsubProxy;
874
922
  }
875
- /** Subscribes to an event and calls the callback or resolves the Promise only once */
923
+ /**
924
+ * Subscribes to an event and calls the callback or resolves the Promise only once when it's emitted.
925
+ * @param event The event to subscribe to. Use `as "_"` in case your event names aren't thoroughly typed (like when using a template literal, e.g. \`event-${val}\` as "_")
926
+ * @param cb The callback to call when the event is emitted - if provided or not, the returned Promise will resolve with the event arguments
927
+ * @returns Returns a Promise that resolves with the event arguments when the event is emitted
928
+ * @example ```ts
929
+ * const emitter = new NanoEmitter<{
930
+ * foo: (bar: string) => void;
931
+ * }>();
932
+ *
933
+ * // Promise syntax:
934
+ * const [bar] = await emitter.once("foo");
935
+ * console.log(bar);
936
+ *
937
+ * // Callback syntax:
938
+ * emitter.once("foo", (bar) => console.log(bar));
939
+ * ```
940
+ */
876
941
  once(event, cb) {
877
942
  return new Promise((resolve) => {
878
943
  let unsub;
879
944
  const onceProxy = (...args) => {
880
- unsub();
881
945
  cb == null ? undefined : cb(...args);
946
+ unsub == null ? undefined : unsub();
882
947
  resolve(args);
883
948
  };
884
- unsub = this.on(event, onceProxy);
949
+ unsub = this.events.on(event, onceProxy);
950
+ this.eventUnsubscribes.push(unsub);
885
951
  });
886
952
  }
887
- /** Emits an event on this instance - Needs `publicEmit` to be set to true in the constructor! */
953
+ /**
954
+ * Emits an event on this instance.
955
+ * ⚠️ Needs `publicEmit` to be set to true in the NanoEmitter constructor or super() call!
956
+ * @param event The event to emit
957
+ * @param args The arguments to pass to the event listeners
958
+ * @returns Returns true if `publicEmit` is true and the event was emitted successfully
959
+ */
888
960
  emit(event, ...args) {
889
961
  if (this.emitterOptions.publicEmit) {
890
962
  this.events.emit(event, ...args);
@@ -892,7 +964,7 @@ var NanoEmitter = class {
892
964
  }
893
965
  return false;
894
966
  }
895
- /** Unsubscribes all event listeners */
967
+ /** Unsubscribes all event listeners from this instance */
896
968
  unsubscribeAll() {
897
969
  for (const unsub of this.eventUnsubscribes)
898
970
  unsub();
@@ -932,6 +1004,10 @@ var Debouncer = class extends NanoEmitter {
932
1004
  removeAllListeners() {
933
1005
  this.listeners = [];
934
1006
  }
1007
+ /** Returns all registered listeners */
1008
+ getListeners() {
1009
+ return this.listeners;
1010
+ }
935
1011
  //#region timeout
936
1012
  /** Sets the timeout for the debouncer */
937
1013
  setTimeout(timeout) {
@@ -960,7 +1036,7 @@ var Debouncer = class extends NanoEmitter {
960
1036
  const cl = (...a) => {
961
1037
  this.queuedCall = undefined;
962
1038
  this.emit("call", ...a);
963
- this.listeners.forEach((l) => l.apply(this, a));
1039
+ this.listeners.forEach((l) => l.call(this, ...a));
964
1040
  };
965
1041
  const setRepeatTimeout = () => {
966
1042
  this.activeTimeout = setTimeout(() => {
@@ -1434,8 +1510,6 @@ function autoPlural(term, num, pluralType = "auto") {
1434
1510
  return `${term}${n === 1 ? "" : "s"}`;
1435
1511
  case "-ies":
1436
1512
  return `${String(term).slice(0, -1)}${n === 1 ? "y" : "ies"}`;
1437
- default:
1438
- return String(term);
1439
1513
  }
1440
1514
  }
1441
1515
  function insertValues(input, ...values) {
@@ -1494,6 +1568,112 @@ function purifyObj(obj) {
1494
1568
  return Object.assign(/* @__PURE__ */ Object.create(null), obj);
1495
1569
  }
1496
1570
 
1571
+ // lib/Mixins.ts
1572
+ var Mixins = class {
1573
+ /**
1574
+ * Creates a new Mixins instance.
1575
+ * @param config Configuration object to customize the behavior.
1576
+ */
1577
+ constructor(config = {}) {
1578
+ /** List of all registered mixins */
1579
+ __publicField(this, "mixins", []);
1580
+ /** Default configuration object for mixins */
1581
+ __publicField(this, "defaultMixinCfg");
1582
+ /** Whether the priorities should auto-increment if not specified */
1583
+ __publicField(this, "autoIncPrioEnabled");
1584
+ /** The current auto-increment priority counter */
1585
+ __publicField(this, "autoIncPrioCounter", /* @__PURE__ */ new Map());
1586
+ var _a, _b, _c;
1587
+ this.defaultMixinCfg = purifyObj({
1588
+ priority: (_a = config.defaultPriority) != null ? _a : 0,
1589
+ stopPropagation: (_b = config.defaultStopPropagation) != null ? _b : false,
1590
+ signal: config.defaultSignal
1591
+ });
1592
+ this.autoIncPrioEnabled = (_c = config.autoIncrementPriority) != null ? _c : false;
1593
+ }
1594
+ //#region public
1595
+ /**
1596
+ * Adds a mixin function to the given {@linkcode mixinKey}.
1597
+ * If no priority is specified, it will be calculated via the protected method {@linkcode calcPriority()} based on the constructor configuration, or fall back to the default priority.
1598
+ * @param mixinKey The key to identify the mixin function.
1599
+ * @param mixinFn The function to be called to apply the mixin. The first argument is the input value, the second argument is the context object (if any).
1600
+ * @param config Configuration object to customize the mixin behavior, or just the priority if a number is passed.
1601
+ * @returns Returns a cleanup function, to be called when this mixin is no longer needed.
1602
+ */
1603
+ add(mixinKey, mixinFn, config = purifyObj({})) {
1604
+ const calcPrio = typeof config === "number" ? config : this.calcPriority(mixinKey, config);
1605
+ const mixin = purifyObj(__spreadValues(__spreadValues(__spreadProps(__spreadValues({}, this.defaultMixinCfg), {
1606
+ key: mixinKey,
1607
+ fn: mixinFn
1608
+ }), typeof config === "object" ? config : {}), typeof calcPrio === "number" && !isNaN(calcPrio) ? { priority: calcPrio } : {}));
1609
+ this.mixins.push(mixin);
1610
+ const rem = () => {
1611
+ this.mixins = this.mixins.filter((m) => m !== mixin);
1612
+ };
1613
+ if (mixin.signal)
1614
+ mixin.signal.addEventListener("abort", rem, { once: true });
1615
+ return rem;
1616
+ }
1617
+ /** Returns a list of all added mixins with their keys and configuration objects, but not their functions */
1618
+ list() {
1619
+ return this.mixins.map((_a) => {
1620
+ var _b = _a, rest = __objRest(_b, ["fn"]);
1621
+ return rest;
1622
+ });
1623
+ }
1624
+ /**
1625
+ * Applies all mixins with the given key to the input value, respecting the priority and stopPropagation settings.
1626
+ * If additional context is set in the MixinMap, it will need to be passed as the third argument.
1627
+ * @returns The modified value after all mixins have been applied.
1628
+ */
1629
+ resolve(mixinKey, inputValue, ...inputCtx) {
1630
+ const mixins = this.mixins.filter((m) => m.key === mixinKey);
1631
+ const sortedMixins = [...mixins].sort((a, b) => b.priority - a.priority);
1632
+ let result = inputValue;
1633
+ for (let i = 0; i < sortedMixins.length; i++) {
1634
+ const mixin = sortedMixins[i];
1635
+ result = mixin.fn(result, ...inputCtx);
1636
+ if (result instanceof Promise) {
1637
+ return (() => __async(this, null, function* () {
1638
+ result = yield result;
1639
+ if (mixin.stopPropagation)
1640
+ return result;
1641
+ for (let j = i + 1; j < sortedMixins.length; j++) {
1642
+ const mixin2 = sortedMixins[j];
1643
+ result = yield mixin2.fn(result, ...inputCtx);
1644
+ if (mixin2.stopPropagation)
1645
+ break;
1646
+ }
1647
+ return result;
1648
+ }))();
1649
+ } else if (mixin.stopPropagation)
1650
+ break;
1651
+ }
1652
+ return result;
1653
+ }
1654
+ //#region protected
1655
+ /** Calculates the priority for a mixin based on the given configuration and the current auto-increment state of the instance */
1656
+ calcPriority(mixinKey, config) {
1657
+ var _a;
1658
+ if (config.priority !== undefined)
1659
+ return undefined;
1660
+ if (!this.autoIncPrioEnabled)
1661
+ return (_a = config.priority) != null ? _a : this.defaultMixinCfg.priority;
1662
+ if (!this.autoIncPrioCounter.has(mixinKey))
1663
+ this.autoIncPrioCounter.set(mixinKey, this.defaultMixinCfg.priority);
1664
+ let prio = this.autoIncPrioCounter.get(mixinKey);
1665
+ while (this.mixins.some((m) => m.key === mixinKey && m.priority === prio))
1666
+ prio++;
1667
+ this.autoIncPrioCounter.set(mixinKey, prio + 1);
1668
+ return prio;
1669
+ }
1670
+ /** Removes all mixins with the given key */
1671
+ removeAll(mixinKey) {
1672
+ this.mixins.filter((m) => m.key === mixinKey);
1673
+ this.mixins = this.mixins.filter((m) => m.key !== mixinKey);
1674
+ }
1675
+ };
1676
+
1497
1677
  // lib/SelectorObserver.ts
1498
1678
  var SelectorObserver = class {
1499
1679
  constructor(baseElement, options = {}) {
@@ -1751,15 +1931,15 @@ function getFallbackLanguage() {
1751
1931
  return fallbackLang;
1752
1932
  }
1753
1933
  function addTransform(transform) {
1754
- const [pattern, fn] = transform;
1934
+ const [regex, fn] = transform;
1755
1935
  valTransforms.push({
1756
1936
  fn,
1757
- regex: typeof pattern === "string" ? new RegExp(pattern, "gm") : pattern
1937
+ regex
1758
1938
  });
1759
1939
  }
1760
1940
  function deleteTransform(patternOrFn) {
1761
1941
  const idx = valTransforms.findIndex(
1762
- (t) => typeof patternOrFn === "function" ? t.fn === patternOrFn : typeof patternOrFn === "string" ? t.regex.source === patternOrFn : t.regex === patternOrFn
1942
+ (t) => typeof patternOrFn === "function" ? t.fn === patternOrFn : t.regex === patternOrFn
1763
1943
  );
1764
1944
  if (idx !== -1) {
1765
1945
  valTransforms.splice(idx, 1);
@@ -1830,4 +2010,4 @@ var tr = {
1830
2010
  }
1831
2011
  };
1832
2012
 
1833
- export { ChecksumMismatchError, DataStore, DataStoreSerializer, Debouncer, Dialog, MigrationError, NanoEmitter, PlatformError, SelectorObserver, UUError, addGlobalStyle, addParent, autoPlural, bitSetHas, clamp, compress, computeHash, consumeGen, consumeStringGen, currentDialogId, darkenColor, debounce, decompress, defaultDialogCss, defaultStrings, digitCount, fetchAdvanced, getListLength, getSiblingsFrame, getUnsafeWindow, hexToRgb, insertValues, interceptEvent, interceptWindowEvent, isDomLoaded, isScrollable, lightenColor, mapRange, observeElementProp, onDomLoad, openDialogs, openInNewTab, pauseFor, preloadImages, probeElementStyle, purifyObj, randRange, randomId, randomItem, randomItemIndex, randomizeArray, rgbToHex, roundFixed, setInnerHtmlUnsafe, takeRandomItem, tr };
2013
+ export { ChecksumMismatchError, DataStore, DataStoreSerializer, Debouncer, Dialog, MigrationError, Mixins, NanoEmitter, PlatformError, SelectorObserver, UUError, addGlobalStyle, addParent, autoPlural, bitSetHas, clamp, compress, computeHash, consumeGen, consumeStringGen, currentDialogId, darkenColor, debounce, decompress, defaultDialogCss, defaultStrings, digitCount, fetchAdvanced, getListLength, getSiblingsFrame, getUnsafeWindow, hexToRgb, insertValues, interceptEvent, interceptWindowEvent, isDomLoaded, isScrollable, lightenColor, mapRange, observeElementProp, onDomLoad, openDialogs, openInNewTab, pauseFor, preloadImages, probeElementStyle, purifyObj, randRange, randomId, randomItem, randomItemIndex, randomizeArray, rgbToHex, roundFixed, setInnerHtmlUnsafe, takeRandomItem, tr };
@@ -4,9 +4,9 @@
4
4
  */
5
5
  import type { DataStore } from "./DataStore.js";
6
6
  export type DataStoreSerializerOptions = {
7
- /** Whether to add a checksum to the exported data */
7
+ /** Whether to add a checksum to the exported data. Defaults to `true` */
8
8
  addChecksum?: boolean;
9
- /** Whether to ensure the integrity of the data when importing it by throwing an error (doesn't throw when the checksum property doesn't exist) */
9
+ /** Whether to ensure the integrity of the data when importing it by throwing an error (doesn't throw when the checksum property doesn't exist). Defaults to `true` */
10
10
  ensureIntegrity?: boolean;
11
11
  };
12
12
  /** Serialized data of a DataStore instance */
@@ -29,6 +29,8 @@ export type LoadStoresDataResult = {
29
29
  /** The in-memory data object */
30
30
  data: object;
31
31
  };
32
+ /** A filter for selecting data stores */
33
+ export type StoreFilter = string[] | ((id: string) => boolean);
32
34
  /**
33
35
  * Allows for easy serialization and deserialization of multiple DataStore instances.
34
36
  *
@@ -49,21 +51,21 @@ export declare class DataStoreSerializer {
49
51
  * @param useEncoding Whether to encode the data using each DataStore's `encodeData()` method
50
52
  * @param stringified Whether to return the result as a string or as an array of `SerializedDataStore` objects
51
53
  */
52
- serializePartial(stores: string[] | ((id: string) => boolean), useEncoding?: boolean, stringified?: true): Promise<string>;
54
+ serializePartial(stores: StoreFilter, useEncoding?: boolean, stringified?: true): Promise<string>;
53
55
  /**
54
56
  * Serializes only a subset of the data stores into a string.
55
57
  * @param stores An array of store IDs or functions that take a store ID and return a boolean
56
58
  * @param useEncoding Whether to encode the data using each DataStore's `encodeData()` method
57
59
  * @param stringified Whether to return the result as a string or as an array of `SerializedDataStore` objects
58
60
  */
59
- serializePartial(stores: string[] | ((id: string) => boolean), useEncoding?: boolean, stringified?: false): Promise<SerializedDataStore[]>;
61
+ serializePartial(stores: StoreFilter, useEncoding?: boolean, stringified?: false): Promise<SerializedDataStore[]>;
60
62
  /**
61
63
  * Serializes only a subset of the data stores into a string.
62
64
  * @param stores An array of store IDs or functions that take a store ID and return a boolean
63
65
  * @param useEncoding Whether to encode the data using each DataStore's `encodeData()` method
64
66
  * @param stringified Whether to return the result as a string or as an array of `SerializedDataStore` objects
65
67
  */
66
- serializePartial(stores: string[] | ((id: string) => boolean), useEncoding?: boolean, stringified?: boolean): Promise<string | SerializedDataStore[]>;
68
+ serializePartial(stores: StoreFilter, useEncoding?: boolean, stringified?: boolean): Promise<string | SerializedDataStore[]>;
67
69
  /**
68
70
  * Serializes the data stores into a string.
69
71
  * @param useEncoding Whether to encode the data using each DataStore's `encodeData()` method
@@ -80,7 +82,7 @@ export declare class DataStoreSerializer {
80
82
  * Deserializes the data exported via {@linkcode serialize()} and imports only a subset into the DataStore instances.
81
83
  * Also triggers the migration process if the data format has changed.
82
84
  */
83
- deserializePartial(stores: string[] | ((id: string) => boolean), data: string | SerializedDataStore[]): Promise<void>;
85
+ deserializePartial(stores: StoreFilter, data: string | SerializedDataStore[]): Promise<void>;
84
86
  /**
85
87
  * Deserializes the data exported via {@linkcode serialize()} and imports the data into all matching DataStore instances.
86
88
  * Also triggers the migration process if the data format has changed.
@@ -89,16 +91,25 @@ export declare class DataStoreSerializer {
89
91
  /**
90
92
  * Loads the persistent data of the DataStore instances into the in-memory cache.
91
93
  * Also triggers the migration process if the data format has changed.
94
+ * @param stores An array of store IDs or a function that takes the store IDs and returns a boolean - if omitted, all stores will be loaded
92
95
  * @returns Returns a PromiseSettledResult array with the results of each DataStore instance in the format `{ id: string, data: object }`
93
96
  */
94
- loadStoresData(): Promise<PromiseSettledResult<LoadStoresDataResult>[]>;
95
- /** Resets the persistent data of the DataStore instances to their default values. */
96
- resetStoresData(): Promise<PromiseSettledResult<void>[]>;
97
+ loadStoresData(stores?: StoreFilter): Promise<PromiseSettledResult<LoadStoresDataResult>[]>;
98
+ /**
99
+ * Resets the persistent and in-memory data of the DataStore instances to their default values.
100
+ * @param stores An array of store IDs or a function that takes the store IDs and returns a boolean - if omitted, all stores will be affected
101
+ */
102
+ resetStoresData(stores?: StoreFilter): Promise<PromiseSettledResult<void>[]>;
97
103
  /**
98
104
  * Deletes the persistent data of the DataStore instances.
99
105
  * Leaves the in-memory data untouched.
106
+ * @param stores An array of store IDs or a function that takes the store IDs and returns a boolean - if omitted, all stores will be affected
100
107
  */
101
- deleteStoresData(): Promise<PromiseSettledResult<void>[]>;
108
+ deleteStoresData(stores?: StoreFilter): Promise<PromiseSettledResult<void>[]>;
109
+ /** Checks if a given value is an array of SerializedDataStore objects */
110
+ static isSerializedDataStoreObjArray(obj: unknown): obj is SerializedDataStore[];
102
111
  /** Checks if a given value is a SerializedDataStore object */
103
- static isSerializedDataStore(obj: unknown): obj is SerializedDataStore;
112
+ static isSerializedDataStoreObj(obj: unknown): obj is SerializedDataStore;
113
+ /** Returns the DataStore instances whose IDs match the provided array or function */
114
+ protected getStoresFiltered(stores?: StoreFilter): DataStore[];
104
115
  }
@@ -60,6 +60,8 @@ export declare class Debouncer<TFunc extends AnyFunc> extends NanoEmitter<Deboun
60
60
  removeListener(fn: TFunc): void;
61
61
  /** Removes all listeners */
62
62
  removeAllListeners(): void;
63
+ /** Returns all registered listeners */
64
+ getListeners(): TFunc[];
63
65
  /** Sets the timeout for the debouncer */
64
66
  setTimeout(timeout: number): void;
65
67
  /** Returns the current timeout */
@@ -0,0 +1,127 @@
1
+ /**
2
+ * @module lib/Mixins
3
+ * Allows for defining and applying mixin functions to allow multiple sources to modify a value in a controlled way.
4
+ */
5
+ import type { Prettify } from "./types.js";
6
+ /** Full mixin object (either sync or async), as it is stored in the instance's mixin array. */
7
+ export type MixinObj<TArg, TCtx> = Prettify<MixinObjSync<TArg, TCtx> | MixinObjAsync<TArg, TCtx>>;
8
+ /** Asynchronous mixin object, as it is stored in the instance's mixin array. */
9
+ export type MixinObjSync<TArg, TCtx> = Prettify<{
10
+ /** The mixin function */
11
+ fn: (arg: TArg, ctx?: TCtx) => TArg;
12
+ } & MixinObjBase>;
13
+ /** Synchronous mixin object, as it is stored in the instance's mixin array. */
14
+ export type MixinObjAsync<TArg, TCtx> = Prettify<{
15
+ /** The mixin function */
16
+ fn: (arg: TArg, ctx?: TCtx) => TArg | Promise<TArg>;
17
+ } & MixinObjBase>;
18
+ /** Base type for mixin objects */
19
+ type MixinObjBase = Prettify<{
20
+ /** The public identifier key (purpose) of the mixin */
21
+ key: string;
22
+ } & MixinConfig>;
23
+ /** Configuration object for a mixin function */
24
+ export type MixinConfig = {
25
+ /** The higher, the earlier the mixin will be applied. Supports floating-point and negative numbers too. 0 by default. */
26
+ priority: number;
27
+ /** If true, no further mixins will be applied after this one. */
28
+ stopPropagation: boolean;
29
+ /** If set, the mixin will only be applied if the given signal is not aborted. */
30
+ signal?: AbortSignal;
31
+ };
32
+ /** Configuration object for the Mixins class */
33
+ export type MixinsConstructorConfig = {
34
+ /**
35
+ * If true, when no priority is specified, an auto-incrementing integer priority will be used, starting at `defaultPriority` or 0 (unique per mixin key). Defaults to false.
36
+ * If a priority value is already used, it will be incremented until a unique value is found.
37
+ * This is useful to ensure that mixins are applied in the order they were added, even if they don't specify a priority.
38
+ * It also allows for a finer level of interjection when the priority is a floating point number.
39
+ */
40
+ autoIncrementPriority: boolean;
41
+ /** The default priority for mixins that do not specify one. Defaults to 0. */
42
+ defaultPriority: number;
43
+ /** The default stopPropagation value for mixins that do not specify one. Defaults to false. */
44
+ defaultStopPropagation: boolean;
45
+ /** The default AbortSignal for mixins that do not specify one. Defaults to undefined. */
46
+ defaultSignal?: AbortSignal;
47
+ };
48
+ /**
49
+ * The mixin class allows for defining and applying mixin functions to allow multiple sources to modify values in a controlled way.
50
+ * Mixins are identified via their string key and can be added with {@linkcode add()}
51
+ * When calling {@linkcode resolve()}, all registered mixin functions with the same key will be applied to the input value in the order of their priority.
52
+ * If a mixin has the stopPropagation flag set to true, no further mixins will be applied after it.
53
+ * @template TMixinMap A map of mixin keys to their respective function signatures. The first argument of the function is the input value, the second argument is an optional context object. If it is defined here, it must be passed as the third argument in {@linkcode resolve()}.
54
+ * @example ```ts
55
+ * const ac = new AbortController();
56
+ * const { abort: removeAllMixins } = ac;
57
+ *
58
+ * const mathMixins = new Mixins<{
59
+ * // supports sync and async functions:
60
+ * foo: (val: number, ctx: { baz: string }) => Promise<number>;
61
+ * // first argument and return value have to be of the same type:
62
+ * bar: (val: bigint) => bigint;
63
+ * // ...
64
+ * }>({
65
+ * autoIncrementPriority: true,
66
+ * defaultPriority: 0,
67
+ * defaultSignal: ac.signal,
68
+ * });
69
+ *
70
+ * // will be applied last due to base priority of 0:
71
+ * mathMixins.add("foo", (val, ctx) => Promise.resolve(val * 2 + ctx.baz.length));
72
+ * // will be applied second due to manually set priority of 1:
73
+ * mathMixins.add("foo", (val) => val + 1, { priority: 1 });
74
+ * // will be applied first, even though the above ones were called first, because of the auto-incrementing priority of 2:
75
+ * mathMixins.add("foo", (val) => val / 2);
76
+ *
77
+ * const result = await mathMixins.resolve("foo", 10, { baz: "this has a length of 23" });
78
+ * // order of application:
79
+ * // input value: 10
80
+ * // 10 / 2 = 5
81
+ * // 5 + 1 = 6
82
+ * // 6 * 2 + 23 = 35
83
+ * // result = 35
84
+ *
85
+ * // removes all mixins added without a `signal` property:
86
+ * removeAllMixins();
87
+ * ```
88
+ */
89
+ export declare class Mixins<TMixinMap extends Record<string, (arg: any, ctx?: any) => any>, TMixinKey extends Extract<keyof TMixinMap, string> = Extract<keyof TMixinMap, string>> {
90
+ /** List of all registered mixins */
91
+ protected mixins: MixinObj<any, any>[];
92
+ /** Default configuration object for mixins */
93
+ protected readonly defaultMixinCfg: MixinConfig;
94
+ /** Whether the priorities should auto-increment if not specified */
95
+ protected readonly autoIncPrioEnabled: boolean;
96
+ /** The current auto-increment priority counter */
97
+ protected autoIncPrioCounter: Map<TMixinKey, number>;
98
+ /**
99
+ * Creates a new Mixins instance.
100
+ * @param config Configuration object to customize the behavior.
101
+ */
102
+ constructor(config?: Partial<MixinsConstructorConfig>);
103
+ /**
104
+ * Adds a mixin function to the given {@linkcode mixinKey}.
105
+ * If no priority is specified, it will be calculated via the protected method {@linkcode calcPriority()} based on the constructor configuration, or fall back to the default priority.
106
+ * @param mixinKey The key to identify the mixin function.
107
+ * @param mixinFn The function to be called to apply the mixin. The first argument is the input value, the second argument is the context object (if any).
108
+ * @param config Configuration object to customize the mixin behavior, or just the priority if a number is passed.
109
+ * @returns Returns a cleanup function, to be called when this mixin is no longer needed.
110
+ */
111
+ add<TKey extends TMixinKey, TArg extends Parameters<TMixinMap[TKey]>[0], TCtx extends Parameters<TMixinMap[TKey]>[1]>(mixinKey: TKey, mixinFn: (arg: TArg, ...ctx: TCtx extends undefined ? [void] : [TCtx]) => ReturnType<TMixinMap[TKey]> extends Promise<any> ? ReturnType<TMixinMap[TKey]> | Awaited<ReturnType<TMixinMap[TKey]>> : ReturnType<TMixinMap[TKey]>, config?: Partial<MixinConfig> | number): () => void;
112
+ /** Returns a list of all added mixins with their keys and configuration objects, but not their functions */
113
+ list(): ({
114
+ key: string;
115
+ } & MixinConfig)[];
116
+ /**
117
+ * Applies all mixins with the given key to the input value, respecting the priority and stopPropagation settings.
118
+ * If additional context is set in the MixinMap, it will need to be passed as the third argument.
119
+ * @returns The modified value after all mixins have been applied.
120
+ */
121
+ resolve<TKey extends TMixinKey, TArg extends Parameters<TMixinMap[TKey]>[0], TCtx extends Parameters<TMixinMap[TKey]>[1]>(mixinKey: TKey, inputValue: TArg, ...inputCtx: TCtx extends undefined ? [void] : [TCtx]): ReturnType<TMixinMap[TKey]> extends Promise<any> ? ReturnType<TMixinMap[TKey]> : ReturnType<TMixinMap[TKey]>;
122
+ /** Calculates the priority for a mixin based on the given configuration and the current auto-increment state of the instance */
123
+ protected calcPriority(mixinKey: TMixinKey, config: Partial<MixinConfig>): number | undefined;
124
+ /** Removes all mixins with the given key */
125
+ protected removeAll(mixinKey: TMixinKey): void;
126
+ }
127
+ export {};