@sv443-network/userutils 10.0.6 → 10.2.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.
@@ -5,6 +5,7 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
8
9
  var __export = (target, all) => {
9
10
  for (var name in all)
10
11
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -26,6 +27,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
26
27
  mod
27
28
  ));
28
29
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
29
31
 
30
32
  // lib/index.ts
31
33
  var lib_exports = {};
@@ -62,6 +64,7 @@ __export(lib_exports, {
62
64
  consumeGen: () => consumeGen,
63
65
  consumeStringGen: () => consumeStringGen,
64
66
  createProgressBar: () => createProgressBar,
67
+ createRecurringTask: () => createRecurringTask,
65
68
  currentDialogId: () => currentDialogId,
66
69
  darkenColor: () => darkenColor,
67
70
  debounce: () => debounce,
@@ -110,11 +113,12 @@ __export(lib_exports, {
110
113
  takeRandomItemIndex: () => takeRandomItemIndex,
111
114
  tr: () => tr,
112
115
  truncStr: () => truncStr,
113
- valsWithin: () => valsWithin
116
+ valsWithin: () => valsWithin,
117
+ versions: () => versions
114
118
  });
115
119
  module.exports = __toCommonJS(lib_exports);
116
120
 
117
- // node_modules/.pnpm/@sv443-network+coreutils@3.0.5/node_modules/@sv443-network/coreutils/dist/CoreUtils.mjs
121
+ // node_modules/.pnpm/@sv443-network+coreutils@3.4.0/node_modules/@sv443-network/coreutils/dist/CoreUtils.mjs
118
122
  function bitSetHas(bitSet, checkVal) {
119
123
  return (bitSet & checkVal) === checkVal;
120
124
  }
@@ -260,7 +264,7 @@ function darkenColor(color, percent, upperCase = false) {
260
264
  if (isHexCol)
261
265
  return rgbToHex(r, g, b, a, color.startsWith("#"), upperCase);
262
266
  else if (color.startsWith("rgba"))
263
- return `rgba(${r}, ${g}, ${b}, ${a ?? NaN})`;
267
+ return `rgba(${r}, ${g}, ${b}, ${a != null ? a : NaN})`;
264
268
  else
265
269
  return `rgb(${r}, ${g}, ${b})`;
266
270
  }
@@ -293,7 +297,8 @@ function atoab(str) {
293
297
  return Uint8Array.from(atob(str), (c) => c.charCodeAt(0));
294
298
  }
295
299
  async function compress(input, compressionFormat, outputType = "string") {
296
- const byteArray = input instanceof Uint8Array ? input : new TextEncoder().encode((input == null ? void 0 : input.toString()) ?? String(input));
300
+ var _a;
301
+ const byteArray = input instanceof Uint8Array ? input : new TextEncoder().encode((_a = input == null ? void 0 : input.toString()) != null ? _a : String(input));
297
302
  const comp = new CompressionStream(compressionFormat);
298
303
  const writer = comp.writable.getWriter();
299
304
  writer.write(byteArray);
@@ -302,7 +307,8 @@ async function compress(input, compressionFormat, outputType = "string") {
302
307
  return outputType === "arrayBuffer" ? uintArr : abtoa(uintArr);
303
308
  }
304
309
  async function decompress(input, compressionFormat, outputType = "string") {
305
- const byteArray = input instanceof Uint8Array ? input : atoab((input == null ? void 0 : input.toString()) ?? String(input));
310
+ var _a;
311
+ const byteArray = input instanceof Uint8Array ? input : atoab((_a = input == null ? void 0 : input.toString()) != null ? _a : String(input));
306
312
  const decomp = new DecompressionStream(compressionFormat);
307
313
  const writer = decomp.writable.getWriter();
308
314
  writer.write(byteArray);
@@ -347,9 +353,9 @@ function randomId(length = 16, radix = 16, enhancedEntropy = false, randomCase =
347
353
  return arr.map((v) => caseArr[randRange(0, caseArr.length - 1, enhancedEntropy)] === 1 ? v.toUpperCase() : v).join("");
348
354
  }
349
355
  var DatedError = class extends Error {
350
- date;
351
356
  constructor(message, options) {
352
357
  super(message, options);
358
+ __publicField(this, "date");
353
359
  this.name = this.constructor.name;
354
360
  this.date = /* @__PURE__ */ new Date();
355
361
  }
@@ -432,7 +438,7 @@ function pauseFor(time, signal, rejectOnAbort = false) {
432
438
  });
433
439
  }
434
440
  function pureObj(obj) {
435
- return Object.assign(/* @__PURE__ */ Object.create(null), obj ?? {});
441
+ return Object.assign(/* @__PURE__ */ Object.create(null), obj != null ? obj : {});
436
442
  }
437
443
  function setImmediateInterval(callback, interval, signal) {
438
444
  let intervalId;
@@ -471,15 +477,50 @@ function scheduleExit(code = 0, timeout = 0) {
471
477
  setTimeout(exit, timeout);
472
478
  }
473
479
  function getCallStack(asArray, lines = Infinity) {
480
+ var _a;
474
481
  if (typeof lines !== "number" || isNaN(lines) || lines < 0)
475
482
  throw new TypeError("lines parameter must be a non-negative number");
476
483
  try {
477
- throw new Error("This is to capture a stack trace with CoreUtils.getCallStack(). (If you see this somewhere, you can safely ignore it.)");
484
+ throw new CustomError("GetCallStack", "Capturing a stack trace with CoreUtils.getCallStack(). If you see this anywhere, you can safely ignore it.");
478
485
  } catch (err) {
479
- const stack = (err.stack ?? "").split("\n").map((line) => line.trim()).slice(2, lines + 2);
486
+ const stack = ((_a = err.stack) != null ? _a : "").split("\n").map((line) => line.trim()).slice(2, lines + 2);
480
487
  return asArray !== false ? stack : stack.join("\n");
481
488
  }
482
489
  }
490
+ function createRecurringTask(options) {
491
+ var _a;
492
+ let iterations = 0;
493
+ let aborted = false;
494
+ (_a = options.signal) == null ? void 0 : _a.addEventListener("abort", () => {
495
+ aborted = true;
496
+ }, { once: true });
497
+ const runRecurringTask = async (initial = false) => {
498
+ var _a3, _b;
499
+ var _a2;
500
+ if (aborted)
501
+ return;
502
+ try {
503
+ if (((_a3 = options.immediate) != null ? _a3 : true) || !initial) {
504
+ iterations++;
505
+ if ((_b = await ((_a2 = options.condition) == null ? void 0 : _a2.call(options, iterations - 1))) != null ? _b : true) {
506
+ const val = await options.task(iterations - 1);
507
+ if (options.onSuccess)
508
+ await options.onSuccess(val, iterations - 1);
509
+ }
510
+ }
511
+ } catch (err) {
512
+ if (options.onError)
513
+ await options.onError(err, iterations - 1);
514
+ if (options.abortOnError)
515
+ aborted = true;
516
+ if (!options.onError && !options.abortOnError)
517
+ throw err;
518
+ }
519
+ if (!aborted && (typeof options.maxIterations !== "number" || iterations < options.maxIterations))
520
+ setTimeout(runRecurringTask, options.timeout);
521
+ };
522
+ return runRecurringTask(true);
523
+ }
483
524
  function autoPlural(term, num, pluralType = "auto") {
484
525
  if (typeof num !== "number") {
485
526
  if ("length" in num)
@@ -529,9 +570,10 @@ function createProgressBar(percentage, barLength, chars = defaultPbChars) {
529
570
  }
530
571
  function insertValues(input, ...values) {
531
572
  return input.replace(/%\d/gm, (match) => {
573
+ var _a2;
532
574
  var _a;
533
575
  const argIndex = Number(match.substring(1)) - 1;
534
- return (_a = values[argIndex] ?? match) == null ? void 0 : _a.toString();
576
+ return (_a = (_a2 = values[argIndex]) != null ? _a2 : match) == null ? void 0 : _a.toString();
535
577
  });
536
578
  }
537
579
  function joinArrayReadable(array, separators = ", ", lastSeparator = " and ") {
@@ -562,31 +604,209 @@ function secsToTimeStr(seconds) {
562
604
  ].join("");
563
605
  }
564
606
  function truncStr(input, length, endStr = "...") {
565
- const str = (input == null ? void 0 : input.toString()) ?? String(input);
607
+ var _a;
608
+ const str = (_a = input == null ? void 0 : input.toString()) != null ? _a : String(input);
566
609
  const finalStr = str.length > length ? str.substring(0, length - endStr.length) + endStr : str;
567
610
  return finalStr.length > length ? finalStr.substring(0, length) : finalStr;
568
611
  }
569
- var dsFmtVer = 1;
570
- var DataStore = class {
571
- id;
572
- formatVersion;
573
- defaultData;
574
- encodeData;
575
- decodeData;
576
- compressionFormat = "deflate-raw";
577
- memoryCache;
578
- engine;
579
- options;
612
+ var createNanoEvents = () => ({
613
+ emit(event, ...args) {
614
+ for (let callbacks = this.events[event] || [], i = 0, length = callbacks.length; i < length; i++) {
615
+ callbacks[i](...args);
616
+ }
617
+ },
618
+ events: {},
619
+ on(event, cb) {
620
+ var _a;
621
+ ;
622
+ ((_a = this.events)[event] || (_a[event] = [])).push(cb);
623
+ return () => {
624
+ var _a2;
625
+ this.events[event] = (_a2 = this.events[event]) == null ? void 0 : _a2.filter((i) => cb !== i);
626
+ };
627
+ }
628
+ });
629
+ var NanoEmitter = class {
630
+ /** Creates a new instance of NanoEmitter - a lightweight event emitter with helper methods and a strongly typed event map */
631
+ constructor(options = {}) {
632
+ __publicField(this, "events", createNanoEvents());
633
+ __publicField(this, "eventUnsubscribes", []);
634
+ __publicField(this, "emitterOptions");
635
+ this.emitterOptions = {
636
+ publicEmit: false,
637
+ ...options
638
+ };
639
+ }
640
+ //#region on
641
+ /**
642
+ * Subscribes to an event and calls the callback when it's emitted.
643
+ * @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 "_")
644
+ * @returns Returns a function that can be called to unsubscribe the event listener
645
+ * @example ```ts
646
+ * const emitter = new NanoEmitter<{
647
+ * foo: (bar: string) => void;
648
+ * }>({
649
+ * publicEmit: true,
650
+ * });
651
+ *
652
+ * let i = 0;
653
+ * const unsub = emitter.on("foo", (bar) => {
654
+ * // unsubscribe after 10 events:
655
+ * if(++i === 10) unsub();
656
+ * console.log(bar);
657
+ * });
658
+ *
659
+ * emitter.emit("foo", "bar");
660
+ * ```
661
+ */
662
+ on(event, cb) {
663
+ let unsub;
664
+ const unsubProxy = () => {
665
+ if (!unsub)
666
+ return;
667
+ unsub();
668
+ this.eventUnsubscribes = this.eventUnsubscribes.filter((u) => u !== unsub);
669
+ };
670
+ unsub = this.events.on(event, cb);
671
+ this.eventUnsubscribes.push(unsub);
672
+ return unsubProxy;
673
+ }
674
+ //#region once
675
+ /**
676
+ * Subscribes to an event and calls the callback or resolves the Promise only once when it's emitted.
677
+ * @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 "_")
678
+ * @param cb The callback to call when the event is emitted - if provided or not, the returned Promise will resolve with the event arguments
679
+ * @returns Returns a Promise that resolves with the event arguments when the event is emitted
680
+ * @example ```ts
681
+ * const emitter = new NanoEmitter<{
682
+ * foo: (bar: string) => void;
683
+ * }>();
684
+ *
685
+ * // Promise syntax:
686
+ * const [bar] = await emitter.once("foo");
687
+ * console.log(bar);
688
+ *
689
+ * // Callback syntax:
690
+ * emitter.once("foo", (bar) => console.log(bar));
691
+ * ```
692
+ */
693
+ once(event, cb) {
694
+ return new Promise((resolve) => {
695
+ let unsub;
696
+ const onceProxy = ((...args) => {
697
+ cb == null ? void 0 : cb(...args);
698
+ unsub == null ? void 0 : unsub();
699
+ resolve(args);
700
+ });
701
+ unsub = this.events.on(event, onceProxy);
702
+ this.eventUnsubscribes.push(unsub);
703
+ });
704
+ }
705
+ //#region onMulti
706
+ /**
707
+ * Allows subscribing to multiple events and calling the callback only when one of, all of, or a subset of the events are emitted, either continuously or only once.
708
+ * @param options An object or array of objects with the following properties:
709
+ * `callback` (required) is the function that will be called when the conditions are met.
710
+ *
711
+ * Set `once` to true to call the callback only once for the first event (or set of events) that match the criteria, then stop listening.
712
+ * If `signal` is provided, the subscription will be canceled when the given signal is aborted.
713
+ *
714
+ * If `oneOf` is used, the callback will be called when any of the matching events are emitted.
715
+ * If `allOf` is used, the callback will be called after all of the matching events are emitted at least once, then any time any of them are emitted.
716
+ * If both `oneOf` and `allOf` are used together, the callback will be called when any of the `oneOf` events are emitted AND all of the `allOf` events have been emitted at least once.
717
+ * At least one of `oneOf` or `allOf` must be provided.
718
+ *
719
+ * @returns Returns a function that can be called to unsubscribe all listeners created by this call. Alternatively, pass an `AbortSignal` to all options objects to achieve the same effect or for finer control.
720
+ */
721
+ onMulti(options) {
722
+ const allUnsubs = [];
723
+ const unsubAll = () => {
724
+ for (const unsub of allUnsubs)
725
+ unsub();
726
+ allUnsubs.splice(0, allUnsubs.length);
727
+ this.eventUnsubscribes = this.eventUnsubscribes.filter((u) => !allUnsubs.includes(u));
728
+ };
729
+ for (const opts of Array.isArray(options) ? options : [options]) {
730
+ const optsWithDefaults = {
731
+ allOf: [],
732
+ oneOf: [],
733
+ once: false,
734
+ ...opts
735
+ };
736
+ const {
737
+ oneOf,
738
+ allOf,
739
+ once,
740
+ signal,
741
+ callback
742
+ } = optsWithDefaults;
743
+ if (signal == null ? void 0 : signal.aborted)
744
+ return unsubAll;
745
+ if (oneOf.length === 0 && allOf.length === 0)
746
+ throw new TypeError("NanoEmitter.onMulti(): Either `oneOf` or `allOf` or both must be provided in the options");
747
+ const curEvtUnsubs = [];
748
+ const checkUnsubAllEvt = (force = false) => {
749
+ if (!(signal == null ? void 0 : signal.aborted) && !force)
750
+ return;
751
+ for (const unsub of curEvtUnsubs)
752
+ unsub();
753
+ curEvtUnsubs.splice(0, curEvtUnsubs.length);
754
+ this.eventUnsubscribes = this.eventUnsubscribes.filter((u) => !curEvtUnsubs.includes(u));
755
+ };
756
+ const allOfEmitted = /* @__PURE__ */ new Set();
757
+ const allOfConditionMet = () => allOf.length === 0 || allOfEmitted.size === allOf.length;
758
+ for (const event of oneOf) {
759
+ const unsub = this.events.on(event, ((...args) => {
760
+ checkUnsubAllEvt();
761
+ if (allOfConditionMet()) {
762
+ callback(event, ...args);
763
+ if (once)
764
+ checkUnsubAllEvt(true);
765
+ }
766
+ }));
767
+ curEvtUnsubs.push(unsub);
768
+ }
769
+ for (const event of allOf) {
770
+ const unsub = this.events.on(event, ((...args) => {
771
+ checkUnsubAllEvt();
772
+ allOfEmitted.add(event);
773
+ if (allOfConditionMet() && (oneOf.length === 0 || oneOf.includes(event))) {
774
+ callback(event, ...args);
775
+ if (once)
776
+ checkUnsubAllEvt(true);
777
+ }
778
+ }));
779
+ curEvtUnsubs.push(unsub);
780
+ }
781
+ allUnsubs.push(() => checkUnsubAllEvt(true));
782
+ }
783
+ return unsubAll;
784
+ }
785
+ //#region emit
580
786
  /**
581
- * Whether all first-init checks should be done.
582
- * This includes migrating the internal DataStore format, migrating data from the UserUtils format, and anything similar.
583
- * This is set to `true` by default. Create a subclass and set it to `false` before calling {@linkcode loadData()} if you want to explicitly skip these checks.
787
+ * Emits an event on this instance.
788
+ * - ⚠️ Needs `publicEmit` to be set to true in the NanoEmitter constructor or super() call!
789
+ * @param event The event to emit
790
+ * @param args The arguments to pass to the event listeners
791
+ * @returns Returns true if `publicEmit` is true and the event was emitted successfully
584
792
  */
585
- firstInit = true;
586
- /** In-memory cached copy of the data that is saved in persistent storage used for synchronous read access. */
587
- cachedData;
588
- migrations;
589
- migrateIds = [];
793
+ emit(event, ...args) {
794
+ if (this.emitterOptions.publicEmit) {
795
+ this.events.emit(event, ...args);
796
+ return true;
797
+ }
798
+ return false;
799
+ }
800
+ //#region unsubscribeAll
801
+ /** Unsubscribes all event listeners from this instance */
802
+ unsubscribeAll() {
803
+ for (const unsub of this.eventUnsubscribes)
804
+ unsub();
805
+ this.eventUnsubscribes = [];
806
+ }
807
+ };
808
+ var dsFmtVer = 1;
809
+ var DataStore = class extends NanoEmitter {
590
810
  //#region constructor
591
811
  /**
592
812
  * Creates an instance of DataStore to manage a sync & async database that is cached in memory and persistently saved across sessions.
@@ -598,20 +818,43 @@ var DataStore = class {
598
818
  * @param opts The options for this DataStore instance
599
819
  */
600
820
  constructor(opts) {
821
+ var _a, _b, _c;
822
+ super(opts.nanoEmitterOptions);
823
+ __publicField(this, "id");
824
+ __publicField(this, "formatVersion");
825
+ __publicField(this, "defaultData");
826
+ __publicField(this, "encodeData");
827
+ __publicField(this, "decodeData");
828
+ __publicField(this, "compressionFormat", "deflate-raw");
829
+ __publicField(this, "memoryCache");
830
+ __publicField(this, "engine");
831
+ __publicField(this, "keyPrefix");
832
+ __publicField(this, "options");
833
+ /**
834
+ * Whether all first-init checks should be done.
835
+ * This includes migrating the internal DataStore format, migrating data from the UserUtils format, and anything similar.
836
+ * This is set to `true` by default. Create a subclass and set it to `false` before calling {@linkcode loadData()} if you want to explicitly skip these checks.
837
+ */
838
+ __publicField(this, "firstInit", true);
839
+ /** In-memory cached copy of the data that is saved in persistent storage used for synchronous read access. */
840
+ __publicField(this, "cachedData");
841
+ __publicField(this, "migrations");
842
+ __publicField(this, "migrateIds", []);
601
843
  this.id = opts.id;
602
844
  this.formatVersion = opts.formatVersion;
603
845
  this.defaultData = opts.defaultData;
604
- this.memoryCache = opts.memoryCache ?? true;
846
+ this.memoryCache = (_a = opts.memoryCache) != null ? _a : true;
605
847
  this.cachedData = this.memoryCache ? opts.defaultData : {};
606
848
  this.migrations = opts.migrations;
607
849
  if (opts.migrateIds)
608
850
  this.migrateIds = Array.isArray(opts.migrateIds) ? opts.migrateIds : [opts.migrateIds];
609
851
  this.engine = typeof opts.engine === "function" ? opts.engine() : opts.engine;
852
+ this.keyPrefix = (_b = opts.keyPrefix) != null ? _b : "__ds-";
610
853
  this.options = opts;
611
854
  if ("encodeData" in opts && "decodeData" in opts && Array.isArray(opts.encodeData) && Array.isArray(opts.decodeData)) {
612
855
  this.encodeData = [opts.encodeData[0], opts.encodeData[1]];
613
856
  this.decodeData = [opts.decodeData[0], opts.decodeData[1]];
614
- this.compressionFormat = opts.encodeData[0] ?? null;
857
+ this.compressionFormat = (_c = opts.encodeData[0]) != null ? _c : null;
615
858
  } else if (opts.compressionFormat === null) {
616
859
  this.encodeData = void 0;
617
860
  this.decodeData = void 0;
@@ -635,6 +878,7 @@ var DataStore = class {
635
878
  * Also runs all necessary migration functions if the data format has changed since the last time the data was saved.
636
879
  */
637
880
  async loadData() {
881
+ var _a;
638
882
  try {
639
883
  if (this.firstInit) {
640
884
  this.firstInit = false;
@@ -648,13 +892,13 @@ var DataStore = class {
648
892
  promises.push(this.engine.setValue(newKey, value));
649
893
  promises.push(this.engine.deleteValue(oldKey));
650
894
  };
651
- migrateFmt(`_uucfg-${this.id}`, `__ds-${this.id}-dat`, oldData);
895
+ migrateFmt(`_uucfg-${this.id}`, `${this.keyPrefix}${this.id}-dat`, oldData);
652
896
  if (!isNaN(oldVer))
653
- migrateFmt(`_uucfgver-${this.id}`, `__ds-${this.id}-ver`, oldVer);
897
+ migrateFmt(`_uucfgver-${this.id}`, `${this.keyPrefix}${this.id}-ver`, oldVer);
654
898
  if (typeof oldEnc === "boolean" || oldEnc === "true" || oldEnc === "false" || typeof oldEnc === "number" || oldEnc === "0" || oldEnc === "1")
655
- migrateFmt(`_uucfgenc-${this.id}`, `__ds-${this.id}-enf`, [0, "0", true, "true"].includes(oldEnc) ? this.compressionFormat ?? null : null);
899
+ migrateFmt(`_uucfgenc-${this.id}`, `${this.keyPrefix}${this.id}-enf`, [0, "0", true, "true"].includes(oldEnc) ? (_a = this.compressionFormat) != null ? _a : null : null);
656
900
  else {
657
- promises.push(this.engine.setValue(`__ds-${this.id}-enf`, this.compressionFormat));
901
+ promises.push(this.engine.setValue(`${this.keyPrefix}${this.id}-enf`, this.compressionFormat));
658
902
  promises.push(this.engine.deleteValue(`_uucfgenc-${this.id}`));
659
903
  }
660
904
  await Promise.allSettled(promises);
@@ -666,31 +910,27 @@ var DataStore = class {
666
910
  await this.migrateId(this.migrateIds);
667
911
  this.migrateIds = [];
668
912
  }
669
- const storedDataRaw = await this.engine.getValue(`__ds-${this.id}-dat`, null);
670
- let storedFmtVer = Number(await this.engine.getValue(`__ds-${this.id}-ver`, NaN));
671
- if (typeof storedDataRaw !== "string") {
672
- await this.saveDefaultData();
673
- return this.engine.deepCopy(this.defaultData);
913
+ const storedDataRaw = await this.engine.getValue(`${this.keyPrefix}${this.id}-dat`, null);
914
+ const storedFmtVer = Number(await this.engine.getValue(`${this.keyPrefix}${this.id}-ver`, NaN));
915
+ if (typeof storedDataRaw !== "string" && typeof storedDataRaw !== "object" || storedDataRaw === null || isNaN(storedFmtVer)) {
916
+ await this.saveDefaultData(false);
917
+ const data = this.engine.deepCopy(this.defaultData);
918
+ this.events.emit("loadData", data);
919
+ return data;
674
920
  }
675
- const storedData = storedDataRaw ?? JSON.stringify(this.defaultData);
676
- const encodingFmt = String(await this.engine.getValue(`__ds-${this.id}-enf`, null));
921
+ const storedData = storedDataRaw != null ? storedDataRaw : JSON.stringify(this.defaultData);
922
+ const encodingFmt = String(await this.engine.getValue(`${this.keyPrefix}${this.id}-enf`, null));
677
923
  const isEncoded = encodingFmt !== "null" && encodingFmt !== "false" && encodingFmt !== "0" && encodingFmt !== "" && encodingFmt !== null;
678
- let saveData = false;
679
- if (isNaN(storedFmtVer)) {
680
- await this.engine.setValue(`__ds-${this.id}-ver`, storedFmtVer = this.formatVersion);
681
- saveData = true;
682
- }
683
- let parsed = await this.engine.deserializeData(storedData, isEncoded);
924
+ let parsed = typeof storedData === "string" ? await this.engine.deserializeData(storedData, isEncoded) : storedData;
684
925
  if (storedFmtVer < this.formatVersion && this.migrations)
685
926
  parsed = await this.runMigrations(parsed, storedFmtVer);
686
- if (saveData)
687
- await this.setData(parsed);
688
- if (this.memoryCache)
689
- return this.cachedData = this.engine.deepCopy(parsed);
690
- else
691
- return this.engine.deepCopy(parsed);
927
+ const result = this.memoryCache ? this.cachedData = this.engine.deepCopy(parsed) : this.engine.deepCopy(parsed);
928
+ this.events.emit("loadData", result);
929
+ return result;
692
930
  } catch (err) {
931
+ const error = err instanceof Error ? err : new Error(String(err));
693
932
  console.warn("Error while parsing JSON data, resetting it to the default value.", err);
933
+ this.events.emit("error", error);
694
934
  await this.saveDefaultData();
695
935
  return this.defaultData;
696
936
  }
@@ -709,27 +949,47 @@ var DataStore = class {
709
949
  //#region setData
710
950
  /** Saves the data synchronously to the in-memory cache and asynchronously to the persistent storage */
711
951
  setData(data) {
712
- if (this.memoryCache)
952
+ const dataCopy = this.engine.deepCopy(data);
953
+ if (this.memoryCache) {
713
954
  this.cachedData = data;
955
+ this.events.emit("updateDataSync", dataCopy);
956
+ }
714
957
  return new Promise(async (resolve) => {
715
- await Promise.allSettled([
716
- this.engine.setValue(`__ds-${this.id}-dat`, await this.engine.serializeData(data, this.encodingEnabled())),
717
- this.engine.setValue(`__ds-${this.id}-ver`, this.formatVersion),
718
- this.engine.setValue(`__ds-${this.id}-enf`, this.compressionFormat)
958
+ const results = await Promise.allSettled([
959
+ this.engine.setValue(`${this.keyPrefix}${this.id}-dat`, await this.engine.serializeData(data, this.encodingEnabled())),
960
+ this.engine.setValue(`${this.keyPrefix}${this.id}-ver`, this.formatVersion),
961
+ this.engine.setValue(`${this.keyPrefix}${this.id}-enf`, this.compressionFormat)
719
962
  ]);
963
+ if (results.every((r) => r.status === "fulfilled"))
964
+ this.events.emit("updateData", dataCopy);
965
+ else {
966
+ const error = new Error("Error while saving data to persistent storage: " + results.map((r) => r.status === "rejected" ? r.reason : null).filter(Boolean).join("; "));
967
+ console.error(error);
968
+ this.events.emit("error", error);
969
+ }
720
970
  resolve();
721
971
  });
722
972
  }
723
973
  //#region saveDefaultData
724
- /** Saves the default data passed in the constructor synchronously to the in-memory cache and asynchronously to persistent storage */
725
- async saveDefaultData() {
974
+ /**
975
+ * Saves the default data passed in the constructor synchronously to the in-memory cache and asynchronously to persistent storage.
976
+ * @param emitEvent Whether to emit the `setDefaultData` event - set to `false` to prevent event emission (used internally during initial population in {@linkcode loadData()})
977
+ */
978
+ async saveDefaultData(emitEvent = true) {
726
979
  if (this.memoryCache)
727
980
  this.cachedData = this.defaultData;
728
- await Promise.allSettled([
729
- this.engine.setValue(`__ds-${this.id}-dat`, await this.engine.serializeData(this.defaultData, this.encodingEnabled())),
730
- this.engine.setValue(`__ds-${this.id}-ver`, this.formatVersion),
731
- this.engine.setValue(`__ds-${this.id}-enf`, this.compressionFormat)
981
+ const results = await Promise.allSettled([
982
+ this.engine.setValue(`${this.keyPrefix}${this.id}-dat`, await this.engine.serializeData(this.defaultData, this.encodingEnabled())),
983
+ this.engine.setValue(`${this.keyPrefix}${this.id}-ver`, this.formatVersion),
984
+ this.engine.setValue(`${this.keyPrefix}${this.id}-enf`, this.compressionFormat)
732
985
  ]);
986
+ if (results.every((r) => r.status === "fulfilled"))
987
+ emitEvent && this.events.emit("setDefaultData", this.defaultData);
988
+ else {
989
+ const error = new Error("Error while saving default data to persistent storage: " + results.map((r) => r.status === "rejected" ? r.reason : null).filter(Boolean).join("; "));
990
+ console.error(error);
991
+ this.events.emit("error", error);
992
+ }
733
993
  }
734
994
  //#region deleteData
735
995
  /**
@@ -740,11 +1000,12 @@ var DataStore = class {
740
1000
  async deleteData() {
741
1001
  var _a, _b;
742
1002
  await Promise.allSettled([
743
- this.engine.deleteValue(`__ds-${this.id}-dat`),
744
- this.engine.deleteValue(`__ds-${this.id}-ver`),
745
- this.engine.deleteValue(`__ds-${this.id}-enf`)
1003
+ this.engine.deleteValue(`${this.keyPrefix}${this.id}-dat`),
1004
+ this.engine.deleteValue(`${this.keyPrefix}${this.id}-ver`),
1005
+ this.engine.deleteValue(`${this.keyPrefix}${this.id}-enf`)
746
1006
  ]);
747
1007
  await ((_b = (_a = this.engine).deleteStorage) == null ? void 0 : _b.call(_a));
1008
+ this.events.emit("deleteData");
748
1009
  }
749
1010
  //#region encodingEnabled
750
1011
  /** Returns whether encoding and decoding are enabled for this DataStore instance */
@@ -765,30 +1026,35 @@ var DataStore = class {
765
1026
  let newData = oldData;
766
1027
  const sortedMigrations = Object.entries(this.migrations).sort(([a], [b]) => Number(a) - Number(b));
767
1028
  let lastFmtVer = oldFmtVer;
768
- for (const [fmtVer, migrationFunc] of sortedMigrations) {
1029
+ for (let i = 0; i < sortedMigrations.length; i++) {
1030
+ const [fmtVer, migrationFunc] = sortedMigrations[i];
769
1031
  const ver = Number(fmtVer);
770
1032
  if (oldFmtVer < this.formatVersion && oldFmtVer < ver) {
771
1033
  try {
772
1034
  const migRes = migrationFunc(newData);
773
1035
  newData = migRes instanceof Promise ? await migRes : migRes;
774
1036
  lastFmtVer = oldFmtVer = ver;
1037
+ const isFinal = ver >= this.formatVersion || i === sortedMigrations.length - 1;
1038
+ this.events.emit("migrateData", ver, newData, isFinal);
775
1039
  } catch (err) {
1040
+ const migError = new MigrationError(`Error while running migration function for format version '${fmtVer}'`, { cause: err });
1041
+ this.events.emit("migrationError", ver, migError);
1042
+ this.events.emit("error", migError);
776
1043
  if (!resetOnError)
777
- throw new MigrationError(`Error while running migration function for format version '${fmtVer}'`, { cause: err });
1044
+ throw migError;
778
1045
  await this.saveDefaultData();
779
1046
  return this.engine.deepCopy(this.defaultData);
780
1047
  }
781
1048
  }
782
1049
  }
783
1050
  await Promise.allSettled([
784
- this.engine.setValue(`__ds-${this.id}-dat`, await this.engine.serializeData(newData, this.encodingEnabled())),
785
- this.engine.setValue(`__ds-${this.id}-ver`, lastFmtVer),
786
- this.engine.setValue(`__ds-${this.id}-enf`, this.compressionFormat)
1051
+ this.engine.setValue(`${this.keyPrefix}${this.id}-dat`, await this.engine.serializeData(newData, this.encodingEnabled())),
1052
+ this.engine.setValue(`${this.keyPrefix}${this.id}-ver`, lastFmtVer),
1053
+ this.engine.setValue(`${this.keyPrefix}${this.id}-enf`, this.compressionFormat)
787
1054
  ]);
788
- if (this.memoryCache)
789
- return this.cachedData = this.engine.deepCopy(newData);
790
- else
791
- return this.engine.deepCopy(newData);
1055
+ const result = this.memoryCache ? this.cachedData = this.engine.deepCopy(newData) : this.engine.deepCopy(newData);
1056
+ this.events.emit("updateData", result);
1057
+ return result;
792
1058
  }
793
1059
  //#region migrateId
794
1060
  /**
@@ -800,9 +1066,9 @@ var DataStore = class {
800
1066
  await Promise.all(ids.map(async (id) => {
801
1067
  const [data, fmtVer, isEncoded] = await (async () => {
802
1068
  const [d, f, e] = await Promise.all([
803
- this.engine.getValue(`__ds-${id}-dat`, JSON.stringify(this.defaultData)),
804
- this.engine.getValue(`__ds-${id}-ver`, NaN),
805
- this.engine.getValue(`__ds-${id}-enf`, null)
1069
+ this.engine.getValue(`${this.keyPrefix}${id}-dat`, JSON.stringify(this.defaultData)),
1070
+ this.engine.getValue(`${this.keyPrefix}${id}-ver`, NaN),
1071
+ this.engine.getValue(`${this.keyPrefix}${id}-enf`, null)
806
1072
  ]);
807
1073
  return [d, Number(f), Boolean(e) && String(e) !== "null"];
808
1074
  })();
@@ -810,20 +1076,21 @@ var DataStore = class {
810
1076
  return;
811
1077
  const parsed = await this.engine.deserializeData(data, isEncoded);
812
1078
  await Promise.allSettled([
813
- this.engine.setValue(`__ds-${this.id}-dat`, await this.engine.serializeData(parsed, this.encodingEnabled())),
814
- this.engine.setValue(`__ds-${this.id}-ver`, fmtVer),
815
- this.engine.setValue(`__ds-${this.id}-enf`, this.compressionFormat),
816
- this.engine.deleteValue(`__ds-${id}-dat`),
817
- this.engine.deleteValue(`__ds-${id}-ver`),
818
- this.engine.deleteValue(`__ds-${id}-enf`)
1079
+ this.engine.setValue(`${this.keyPrefix}${this.id}-dat`, await this.engine.serializeData(parsed, this.encodingEnabled())),
1080
+ this.engine.setValue(`${this.keyPrefix}${this.id}-ver`, fmtVer),
1081
+ this.engine.setValue(`${this.keyPrefix}${this.id}-enf`, this.compressionFormat),
1082
+ this.engine.deleteValue(`${this.keyPrefix}${id}-dat`),
1083
+ this.engine.deleteValue(`${this.keyPrefix}${id}-ver`),
1084
+ this.engine.deleteValue(`${this.keyPrefix}${id}-enf`)
819
1085
  ]);
1086
+ this.events.emit("migrateId", id, this.id);
820
1087
  }));
821
1088
  }
822
1089
  };
823
1090
  var DataStoreEngine = class {
824
- dataStoreOptions;
825
1091
  // setDataStoreOptions() is called from inside the DataStore constructor to set this value
826
1092
  constructor(options) {
1093
+ __publicField(this, "dataStoreOptions");
827
1094
  if (options)
828
1095
  this.dataStoreOptions = options;
829
1096
  }
@@ -851,7 +1118,7 @@ var DataStoreEngine = class {
851
1118
  let decRes = ((_a = this.dataStoreOptions) == null ? void 0 : _a.decodeData) && useEncoding ? (_c = (_b = this.dataStoreOptions.decodeData) == null ? void 0 : _b[1]) == null ? void 0 : _c.call(_b, data) : void 0;
852
1119
  if (decRes instanceof Promise)
853
1120
  decRes = await decRes;
854
- return JSON.parse(decRes ?? data);
1121
+ return JSON.parse(decRes != null ? decRes : data);
855
1122
  }
856
1123
  //#region misc api
857
1124
  /** Throws an error if the DataStoreOptions are not set or invalid */
@@ -869,13 +1136,12 @@ var DataStoreEngine = class {
869
1136
  try {
870
1137
  if ("structuredClone" in globalThis)
871
1138
  return structuredClone(obj);
872
- } catch {
1139
+ } catch (e) {
873
1140
  }
874
1141
  return JSON.parse(JSON.stringify(obj));
875
1142
  }
876
1143
  };
877
1144
  var BrowserStorageEngine = class extends DataStoreEngine {
878
- options;
879
1145
  /**
880
1146
  * Creates an instance of `BrowserStorageEngine`.
881
1147
  *
@@ -884,6 +1150,7 @@ var BrowserStorageEngine = class extends DataStoreEngine {
884
1150
  */
885
1151
  constructor(options) {
886
1152
  super(options == null ? void 0 : options.dataStoreOptions);
1153
+ __publicField(this, "options");
887
1154
  this.options = {
888
1155
  type: "localStorage",
889
1156
  ...options
@@ -912,8 +1179,6 @@ var BrowserStorageEngine = class extends DataStoreEngine {
912
1179
  };
913
1180
  var fs;
914
1181
  var FileStorageEngine = class extends DataStoreEngine {
915
- options;
916
- fileAccessQueue = Promise.resolve();
917
1182
  /**
918
1183
  * Creates an instance of `FileStorageEngine`.
919
1184
  *
@@ -922,6 +1187,8 @@ var FileStorageEngine = class extends DataStoreEngine {
922
1187
  */
923
1188
  constructor(options) {
924
1189
  super(options == null ? void 0 : options.dataStoreOptions);
1190
+ __publicField(this, "options");
1191
+ __publicField(this, "fileAccessQueue", Promise.resolve());
925
1192
  this.options = {
926
1193
  filePath: (id) => `.ds-${id}`,
927
1194
  ...options
@@ -930,6 +1197,7 @@ var FileStorageEngine = class extends DataStoreEngine {
930
1197
  //#region json file
931
1198
  /** Reads the file contents */
932
1199
  async readFile() {
1200
+ var _a2;
933
1201
  var _a, _b, _c, _d;
934
1202
  this.ensureDataStoreOptions();
935
1203
  try {
@@ -937,15 +1205,16 @@ var FileStorageEngine = class extends DataStoreEngine {
937
1205
  fs = (_a = await import("fs/promises")) == null ? void 0 : _a.default;
938
1206
  if (!fs)
939
1207
  throw new ScriptContextError("FileStorageEngine requires Node.js or Deno with Node compatibility (v1.31+)", { cause: new DatedError("'node:fs/promises' module not available") });
940
- const path = typeof this.options.filePath === "string" ? this.options.filePath : this.options.filePath(this.dataStoreOptions.id);
1208
+ const path = typeof this.options.filePath === "string" ? this.options.filePath : this.options.filePath(this.dataStoreOptions.id, this.dataStoreOptions);
941
1209
  const data = await fs.readFile(path, "utf-8");
942
- return data ? JSON.parse(await ((_d = (_c = (_b = this.dataStoreOptions) == null ? void 0 : _b.decodeData) == null ? void 0 : _c[1]) == null ? void 0 : _d.call(_c, data)) ?? data) : void 0;
943
- } catch {
1210
+ return data ? JSON.parse((_a2 = await ((_d = (_c = (_b = this.dataStoreOptions) == null ? void 0 : _b.decodeData) == null ? void 0 : _c[1]) == null ? void 0 : _d.call(_c, data))) != null ? _a2 : data) : void 0;
1211
+ } catch (e) {
944
1212
  return void 0;
945
1213
  }
946
1214
  }
947
1215
  /** Overwrites the file contents */
948
1216
  async writeFile(data) {
1217
+ var _a2;
949
1218
  var _a, _b, _c, _d;
950
1219
  this.ensureDataStoreOptions();
951
1220
  try {
@@ -953,9 +1222,9 @@ var FileStorageEngine = class extends DataStoreEngine {
953
1222
  fs = (_a = await import("fs/promises")) == null ? void 0 : _a.default;
954
1223
  if (!fs)
955
1224
  throw new ScriptContextError("FileStorageEngine requires Node.js or Deno with Node compatibility (v1.31+)", { cause: new DatedError("'node:fs/promises' module not available") });
956
- const path = typeof this.options.filePath === "string" ? this.options.filePath : this.options.filePath(this.dataStoreOptions.id);
1225
+ const path = typeof this.options.filePath === "string" ? this.options.filePath : this.options.filePath(this.dataStoreOptions.id, this.dataStoreOptions);
957
1226
  await fs.mkdir(path.slice(0, path.lastIndexOf(path.includes("/") ? "/" : "\\")), { recursive: true });
958
- await fs.writeFile(path, await ((_d = (_c = (_b = this.dataStoreOptions) == null ? void 0 : _b.encodeData) == null ? void 0 : _c[1]) == null ? void 0 : _d.call(_c, JSON.stringify(data))) ?? JSON.stringify(data, void 0, 2), "utf-8");
1227
+ await fs.writeFile(path, (_a2 = await ((_d = (_c = (_b = this.dataStoreOptions) == null ? void 0 : _b.encodeData) == null ? void 0 : _c[1]) == null ? void 0 : _d.call(_c, JSON.stringify(data)))) != null ? _a2 : JSON.stringify(data, void 0, 2), "utf-8");
959
1228
  } catch (err) {
960
1229
  console.error("Error writing file:", err);
961
1230
  }
@@ -969,8 +1238,21 @@ var FileStorageEngine = class extends DataStoreEngine {
969
1238
  const value = data == null ? void 0 : data[name];
970
1239
  if (typeof value === "undefined")
971
1240
  return defaultValue;
972
- if (typeof value === "string")
973
- return value;
1241
+ if (typeof defaultValue === "string") {
1242
+ if (typeof value === "object" && value !== null)
1243
+ return JSON.stringify(value);
1244
+ if (typeof value === "string")
1245
+ return value;
1246
+ return String(value);
1247
+ }
1248
+ if (typeof value === "string") {
1249
+ try {
1250
+ const parsed = JSON.parse(value);
1251
+ return parsed;
1252
+ } catch (e) {
1253
+ return defaultValue;
1254
+ }
1255
+ }
974
1256
  return value;
975
1257
  }
976
1258
  /** Sets a value in persistent storage */
@@ -979,7 +1261,18 @@ var FileStorageEngine = class extends DataStoreEngine {
979
1261
  let data = await this.readFile();
980
1262
  if (!data)
981
1263
  data = {};
982
- data[name] = value;
1264
+ let storeVal = value;
1265
+ if (typeof value === "string") {
1266
+ try {
1267
+ if (value.startsWith("{") || value.startsWith("[")) {
1268
+ const parsed = JSON.parse(value);
1269
+ if (typeof parsed === "object" && parsed !== null)
1270
+ storeVal = parsed;
1271
+ }
1272
+ } catch (e) {
1273
+ }
1274
+ }
1275
+ data[name] = storeVal;
983
1276
  await this.writeFile(data);
984
1277
  }).catch((err) => {
985
1278
  console.error("Error in setValue:", err);
@@ -1012,7 +1305,7 @@ var FileStorageEngine = class extends DataStoreEngine {
1012
1305
  fs = (_a = await import("fs/promises")) == null ? void 0 : _a.default;
1013
1306
  if (!fs)
1014
1307
  throw new ScriptContextError("FileStorageEngine requires Node.js or Deno with Node compatibility (v1.31+)", { cause: new DatedError("'node:fs/promises' module not available") });
1015
- const path = typeof this.options.filePath === "string" ? this.options.filePath : this.options.filePath(this.dataStoreOptions.id);
1308
+ const path = typeof this.options.filePath === "string" ? this.options.filePath : this.options.filePath(this.dataStoreOptions.id, this.dataStoreOptions);
1016
1309
  return await fs.unlink(path);
1017
1310
  } catch (err) {
1018
1311
  console.error("Error deleting file:", err);
@@ -1020,9 +1313,9 @@ var FileStorageEngine = class extends DataStoreEngine {
1020
1313
  }
1021
1314
  };
1022
1315
  var DataStoreSerializer = class _DataStoreSerializer {
1023
- stores;
1024
- options;
1025
1316
  constructor(stores, options = {}) {
1317
+ __publicField(this, "stores");
1318
+ __publicField(this, "options");
1026
1319
  if (!crypto || !crypto.subtle)
1027
1320
  throw new ScriptContextError("DataStoreSerializer has to run in a secure context (HTTPS) or in another environment that implements the subtleCrypto API!");
1028
1321
  this.stores = stores;
@@ -1033,7 +1326,10 @@ var DataStoreSerializer = class _DataStoreSerializer {
1033
1326
  ...options
1034
1327
  };
1035
1328
  }
1036
- /** Calculates the checksum of a string */
1329
+ /**
1330
+ * Calculates the checksum of a string. Uses {@linkcode computeHash()} with SHA-256 and digests as a hex string by default.
1331
+ * Override this in a subclass if a custom checksum method is needed.
1332
+ */
1037
1333
  async calcChecksum(input) {
1038
1334
  return computeHash(input, "SHA-256");
1039
1335
  }
@@ -1078,8 +1374,9 @@ var DataStoreSerializer = class _DataStoreSerializer {
1078
1374
  if (!Array.isArray(deserStores) || !deserStores.every(_DataStoreSerializer.isSerializedDataStoreObj))
1079
1375
  throw new TypeError("Invalid serialized data format! Expected an array of SerializedDataStore objects.");
1080
1376
  const resolveStoreId = (id) => {
1377
+ var _a2;
1081
1378
  var _a;
1082
- return ((_a = Object.entries(this.options.remapIds).find(([, v]) => v.includes(id))) == null ? void 0 : _a[0]) ?? id;
1379
+ return (_a2 = (_a = Object.entries(this.options.remapIds).find(([, v]) => v.includes(id))) == null ? void 0 : _a[0]) != null ? _a2 : id;
1083
1380
  };
1084
1381
  const matchesFilter = (id) => typeof stores === "function" ? stores(id) : stores.includes(id);
1085
1382
  for (const storeData of deserStores) {
@@ -1156,218 +1453,23 @@ Has: ${checksum}`);
1156
1453
  return this.stores.filter((s) => typeof stores === "undefined" ? true : Array.isArray(stores) ? stores.includes(s.id) : stores(s.id));
1157
1454
  }
1158
1455
  };
1159
- var createNanoEvents = () => ({
1160
- emit(event, ...args) {
1161
- for (let callbacks = this.events[event] || [], i = 0, length = callbacks.length; i < length; i++) {
1162
- callbacks[i](...args);
1163
- }
1164
- },
1165
- events: {},
1166
- on(event, cb) {
1167
- ;
1168
- (this.events[event] ||= []).push(cb);
1169
- return () => {
1170
- var _a;
1171
- this.events[event] = (_a = this.events[event]) == null ? void 0 : _a.filter((i) => cb !== i);
1172
- };
1173
- }
1174
- });
1175
- var NanoEmitter = class {
1176
- events = createNanoEvents();
1177
- eventUnsubscribes = [];
1178
- emitterOptions;
1179
- /** Creates a new instance of NanoEmitter - a lightweight event emitter with helper methods and a strongly typed event map */
1180
- constructor(options = {}) {
1181
- this.emitterOptions = {
1182
- publicEmit: false,
1183
- ...options
1184
- };
1185
- }
1186
- //#region on
1187
- /**
1188
- * Subscribes to an event and calls the callback when it's emitted.
1189
- * @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 "_")
1190
- * @returns Returns a function that can be called to unsubscribe the event listener
1191
- * @example ```ts
1192
- * const emitter = new NanoEmitter<{
1193
- * foo: (bar: string) => void;
1194
- * }>({
1195
- * publicEmit: true,
1196
- * });
1197
- *
1198
- * let i = 0;
1199
- * const unsub = emitter.on("foo", (bar) => {
1200
- * // unsubscribe after 10 events:
1201
- * if(++i === 10) unsub();
1202
- * console.log(bar);
1203
- * });
1204
- *
1205
- * emitter.emit("foo", "bar");
1206
- * ```
1207
- */
1208
- on(event, cb) {
1209
- let unsub;
1210
- const unsubProxy = () => {
1211
- if (!unsub)
1212
- return;
1213
- unsub();
1214
- this.eventUnsubscribes = this.eventUnsubscribes.filter((u) => u !== unsub);
1215
- };
1216
- unsub = this.events.on(event, cb);
1217
- this.eventUnsubscribes.push(unsub);
1218
- return unsubProxy;
1219
- }
1220
- //#region once
1221
- /**
1222
- * Subscribes to an event and calls the callback or resolves the Promise only once when it's emitted.
1223
- * @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 "_")
1224
- * @param cb The callback to call when the event is emitted - if provided or not, the returned Promise will resolve with the event arguments
1225
- * @returns Returns a Promise that resolves with the event arguments when the event is emitted
1226
- * @example ```ts
1227
- * const emitter = new NanoEmitter<{
1228
- * foo: (bar: string) => void;
1229
- * }>();
1230
- *
1231
- * // Promise syntax:
1232
- * const [bar] = await emitter.once("foo");
1233
- * console.log(bar);
1234
- *
1235
- * // Callback syntax:
1236
- * emitter.once("foo", (bar) => console.log(bar));
1237
- * ```
1238
- */
1239
- once(event, cb) {
1240
- return new Promise((resolve) => {
1241
- let unsub;
1242
- const onceProxy = ((...args) => {
1243
- cb == null ? void 0 : cb(...args);
1244
- unsub == null ? void 0 : unsub();
1245
- resolve(args);
1246
- });
1247
- unsub = this.events.on(event, onceProxy);
1248
- this.eventUnsubscribes.push(unsub);
1249
- });
1250
- }
1251
- //#region onMulti
1252
- /**
1253
- * Allows subscribing to multiple events and calling the callback only when one of, all of, or a subset of the events are emitted, either continuously or only once.
1254
- * @param options An object or array of objects with the following properties:
1255
- * `callback` (required) is the function that will be called when the conditions are met.
1256
- *
1257
- * Set `once` to true to call the callback only once for the first event (or set of events) that match the criteria, then stop listening.
1258
- * If `signal` is provided, the subscription will be canceled when the given signal is aborted.
1259
- *
1260
- * If `oneOf` is used, the callback will be called when any of the matching events are emitted.
1261
- * If `allOf` is used, the callback will be called after all of the matching events are emitted at least once, then any time any of them are emitted.
1262
- * If both `oneOf` and `allOf` are used together, the callback will be called when any of the `oneOf` events are emitted AND all of the `allOf` events have been emitted at least once.
1263
- * At least one of `oneOf` or `allOf` must be provided.
1264
- *
1265
- * @returns Returns a function that can be called to unsubscribe all listeners created by this call. Alternatively, pass an `AbortSignal` to all options objects to achieve the same effect or for finer control.
1266
- */
1267
- onMulti(options) {
1268
- const allUnsubs = [];
1269
- const unsubAll = () => {
1270
- for (const unsub of allUnsubs)
1271
- unsub();
1272
- allUnsubs.splice(0, allUnsubs.length);
1273
- this.eventUnsubscribes = this.eventUnsubscribes.filter((u) => !allUnsubs.includes(u));
1274
- };
1275
- for (const opts of Array.isArray(options) ? options : [options]) {
1276
- const optsWithDefaults = {
1277
- allOf: [],
1278
- oneOf: [],
1279
- once: false,
1280
- ...opts
1281
- };
1282
- const {
1283
- oneOf,
1284
- allOf,
1285
- once,
1286
- signal,
1287
- callback
1288
- } = optsWithDefaults;
1289
- if (signal == null ? void 0 : signal.aborted)
1290
- return unsubAll;
1291
- if (oneOf.length === 0 && allOf.length === 0)
1292
- throw new TypeError("NanoEmitter.onMulti(): Either `oneOf` or `allOf` or both must be provided in the options");
1293
- const curEvtUnsubs = [];
1294
- const checkUnsubAllEvt = (force = false) => {
1295
- if (!(signal == null ? void 0 : signal.aborted) && !force)
1296
- return;
1297
- for (const unsub of curEvtUnsubs)
1298
- unsub();
1299
- curEvtUnsubs.splice(0, curEvtUnsubs.length);
1300
- this.eventUnsubscribes = this.eventUnsubscribes.filter((u) => !curEvtUnsubs.includes(u));
1301
- };
1302
- const allOfEmitted = /* @__PURE__ */ new Set();
1303
- const allOfConditionMet = () => allOf.length === 0 || allOfEmitted.size === allOf.length;
1304
- for (const event of oneOf) {
1305
- const unsub = this.events.on(event, ((...args) => {
1306
- checkUnsubAllEvt();
1307
- if (allOfConditionMet()) {
1308
- callback(event, ...args);
1309
- if (once)
1310
- checkUnsubAllEvt(true);
1311
- }
1312
- }));
1313
- curEvtUnsubs.push(unsub);
1314
- }
1315
- for (const event of allOf) {
1316
- const unsub = this.events.on(event, ((...args) => {
1317
- checkUnsubAllEvt();
1318
- allOfEmitted.add(event);
1319
- if (allOfConditionMet() && (oneOf.length === 0 || oneOf.includes(event))) {
1320
- callback(event, ...args);
1321
- if (once)
1322
- checkUnsubAllEvt(true);
1323
- }
1324
- }));
1325
- curEvtUnsubs.push(unsub);
1326
- }
1327
- allUnsubs.push(() => checkUnsubAllEvt(true));
1328
- }
1329
- return unsubAll;
1330
- }
1331
- //#region emit
1332
- /**
1333
- * Emits an event on this instance.
1334
- * - ⚠️ Needs `publicEmit` to be set to true in the NanoEmitter constructor or super() call!
1335
- * @param event The event to emit
1336
- * @param args The arguments to pass to the event listeners
1337
- * @returns Returns true if `publicEmit` is true and the event was emitted successfully
1338
- */
1339
- emit(event, ...args) {
1340
- if (this.emitterOptions.publicEmit) {
1341
- this.events.emit(event, ...args);
1342
- return true;
1343
- }
1344
- return false;
1345
- }
1346
- //#region unsubscribeAll
1347
- /** Unsubscribes all event listeners from this instance */
1348
- unsubscribeAll() {
1349
- for (const unsub of this.eventUnsubscribes)
1350
- unsub();
1351
- this.eventUnsubscribes = [];
1352
- }
1353
- };
1354
1456
  var Debouncer = class extends NanoEmitter {
1355
1457
  /**
1356
1458
  * Creates a new debouncer with the specified timeout and edge type.
1357
1459
  * @param timeout Timeout in milliseconds between letting through calls - defaults to 200
1358
1460
  * @param type The edge type to use for the debouncer - see {@linkcode DebouncerType} for details or [the documentation for an explanation and diagram](https://github.com/Sv443-Network/UserUtils/blob/main/docs.md#debouncer) - defaults to "immediate"
1359
1461
  */
1360
- constructor(timeout = 200, type = "immediate") {
1361
- super();
1462
+ constructor(timeout = 200, type = "immediate", nanoEmitterOptions) {
1463
+ super(nanoEmitterOptions);
1464
+ /** All registered listener functions and the time they were attached */
1465
+ __publicField(this, "listeners", []);
1466
+ /** The currently active timeout */
1467
+ __publicField(this, "activeTimeout");
1468
+ /** The latest queued call */
1469
+ __publicField(this, "queuedCall");
1362
1470
  this.timeout = timeout;
1363
1471
  this.type = type;
1364
1472
  }
1365
- /** All registered listener functions and the time they were attached */
1366
- listeners = [];
1367
- /** The currently active timeout */
1368
- activeTimeout;
1369
- /** The latest queued call */
1370
- queuedCall;
1371
1473
  //#region listeners
1372
1474
  /** Adds a listener function that will be called on timeout */
1373
1475
  addListener(fn) {
@@ -1389,7 +1491,7 @@ var Debouncer = class extends NanoEmitter {
1389
1491
  //#region timeout
1390
1492
  /** Sets the timeout for the debouncer */
1391
1493
  setTimeout(timeout) {
1392
- this.emit("change", this.timeout = timeout, this.type);
1494
+ this.events.emit("change", this.timeout = timeout, this.type);
1393
1495
  }
1394
1496
  /** Returns the current timeout */
1395
1497
  getTimeout() {
@@ -1402,7 +1504,7 @@ var Debouncer = class extends NanoEmitter {
1402
1504
  //#region type
1403
1505
  /** Sets the edge type for the debouncer */
1404
1506
  setType(type) {
1405
- this.emit("change", this.timeout, this.type = type);
1507
+ this.events.emit("change", this.timeout, this.type = type);
1406
1508
  }
1407
1509
  /** Returns the current edge type */
1408
1510
  getType() {
@@ -1413,7 +1515,7 @@ var Debouncer = class extends NanoEmitter {
1413
1515
  call(...args) {
1414
1516
  const cl = (...a) => {
1415
1517
  this.queuedCall = void 0;
1416
- this.emit("call", ...a);
1518
+ this.events.emit("call", ...a);
1417
1519
  this.listeners.forEach((l) => l.call(this, ...a));
1418
1520
  };
1419
1521
  const setRepeatTimeout = () => {
@@ -1446,14 +1548,30 @@ var Debouncer = class extends NanoEmitter {
1446
1548
  }
1447
1549
  }
1448
1550
  };
1449
- function debounce(fn, timeout = 200, type = "immediate") {
1450
- const debouncer = new Debouncer(timeout, type);
1551
+ function debounce(fn, timeout = 200, type = "immediate", nanoEmitterOptions) {
1552
+ const debouncer = new Debouncer(timeout, type, nanoEmitterOptions);
1451
1553
  debouncer.addListener(fn);
1452
1554
  const func = ((...args) => debouncer.call(...args));
1453
1555
  func.debouncer = debouncer;
1454
1556
  return func;
1455
1557
  }
1456
1558
 
1559
+ // lib/consts.ts
1560
+ var rawConsts = {
1561
+ coreUtilsVersion: "3.4.0",
1562
+ userUtilsVersion: "10.2.0"
1563
+ };
1564
+ function getConst(constKey, defaultVal) {
1565
+ const val = rawConsts[constKey];
1566
+ return val.match(/^#\{\{.+\}\}$/) ? defaultVal : val;
1567
+ }
1568
+ var versions = {
1569
+ /** Semver version string of the bundled library CoreUtils. */
1570
+ CoreUtils: getConst("coreUtilsVersion", "ERR:unknown"),
1571
+ /** Semver version string of UserUtils. */
1572
+ UserUtils: getConst("userUtilsVersion", "ERR:unknown")
1573
+ };
1574
+
1457
1575
  // lib/Errors.ts
1458
1576
  var PlatformError = class extends DatedError {
1459
1577
  constructor(message, options) {
@@ -1466,7 +1584,7 @@ var PlatformError = class extends DatedError {
1466
1584
  function getUnsafeWindow() {
1467
1585
  try {
1468
1586
  return unsafeWindow;
1469
- } catch {
1587
+ } catch (e) {
1470
1588
  return window;
1471
1589
  }
1472
1590
  }
@@ -1497,7 +1615,7 @@ function openInNewTab(href, background, additionalProps) {
1497
1615
  try {
1498
1616
  if (typeof window.GM === "object")
1499
1617
  GM.openInTab(href, background);
1500
- } catch {
1618
+ } catch (e) {
1501
1619
  const openElem = document.createElement("a");
1502
1620
  Object.assign(openElem, {
1503
1621
  className: "userutils-open-in-new-tab",
@@ -1517,7 +1635,7 @@ function openInNewTab(href, background, additionalProps) {
1517
1635
  setTimeout(() => {
1518
1636
  try {
1519
1637
  openElem.remove();
1520
- } catch {
1638
+ } catch (e2) {
1521
1639
  }
1522
1640
  }, 0);
1523
1641
  }
@@ -1533,8 +1651,8 @@ function interceptEvent(eventObject, eventName, predicate = () => true) {
1533
1651
  }
1534
1652
  (function(original) {
1535
1653
  eventObject.__proto__.addEventListener = function(...args) {
1536
- var _a2;
1537
- const origListener = typeof args[1] === "function" ? args[1] : ((_a2 = args[1]) == null ? void 0 : _a2.handleEvent) ?? (() => void 0);
1654
+ var _a2, _b;
1655
+ const origListener = typeof args[1] === "function" ? args[1] : (_b = (_a2 = args[1]) == null ? void 0 : _a2.handleEvent) != null ? _b : (() => void 0);
1538
1656
  args[1] = function(...a) {
1539
1657
  if (args[0] === eventName && predicate(Array.isArray(a) ? a[0] : a))
1540
1658
  return;
@@ -1577,8 +1695,8 @@ function observeElementProp(element, property, callback) {
1577
1695
  }
1578
1696
  }
1579
1697
  function getSiblingsFrame(refElement, siblingAmount, refElementAlignment = "center-top", includeRef = true) {
1580
- var _a;
1581
- const siblings = [...((_a = refElement.parentNode) == null ? void 0 : _a.childNodes) ?? []];
1698
+ var _a, _b;
1699
+ const siblings = [...(_b = (_a = refElement.parentNode) == null ? void 0 : _a.childNodes) != null ? _b : []];
1582
1700
  const elemSiblIdx = siblings.indexOf(refElement);
1583
1701
  if (elemSiblIdx === -1)
1584
1702
  throw new Error("Element doesn't have a parent node");
@@ -1599,13 +1717,13 @@ function getSiblingsFrame(refElement, siblingAmount, refElementAlignment = "cent
1599
1717
  }
1600
1718
  var ttPolicy;
1601
1719
  function setInnerHtmlUnsafe(element, html) {
1602
- var _a, _b;
1720
+ var _a, _b, _c;
1603
1721
  if (!ttPolicy && typeof ((_a = window == null ? void 0 : window.trustedTypes) == null ? void 0 : _a.createPolicy) === "function") {
1604
1722
  ttPolicy = window.trustedTypes.createPolicy("_uu_set_innerhtml_unsafe", {
1605
1723
  createHTML: (unsafeHtml) => unsafeHtml
1606
1724
  });
1607
1725
  }
1608
- element.innerHTML = ((_b = ttPolicy == null ? void 0 : ttPolicy.createHTML) == null ? void 0 : _b.call(ttPolicy, html)) ?? html;
1726
+ element.innerHTML = (_c = (_b = ttPolicy == null ? void 0 : ttPolicy.createHTML) == null ? void 0 : _b.call(ttPolicy, html)) != null ? _c : html;
1609
1727
  return element;
1610
1728
  }
1611
1729
  function probeElementStyle(probeStyle, element, hideOffscreen = true, parentElement = document.body) {
@@ -1798,20 +1916,20 @@ var defaultStrings = {
1798
1916
  closeDialogTooltip: "Click to close the dialog"
1799
1917
  };
1800
1918
  var Dialog = class _Dialog extends NanoEmitter {
1801
- /** Options passed to the dialog in the constructor */
1802
- options;
1803
- /** ID that gets added to child element IDs - has to be unique and conform to HTML ID naming rules! */
1804
- id;
1805
- /** Strings used in the dialog (used for translations) */
1806
- strings;
1807
- dialogOpen = false;
1808
- dialogMounted = false;
1809
1919
  constructor(options) {
1810
1920
  super();
1921
+ /** Options passed to the dialog in the constructor */
1922
+ __publicField(this, "options");
1923
+ /** ID that gets added to child element IDs - has to be unique and conform to HTML ID naming rules! */
1924
+ __publicField(this, "id");
1925
+ /** Strings used in the dialog (used for translations) */
1926
+ __publicField(this, "strings");
1927
+ __publicField(this, "dialogOpen", false);
1928
+ __publicField(this, "dialogMounted", false);
1811
1929
  const { strings, ...opts } = options;
1812
1930
  this.strings = {
1813
1931
  ...defaultStrings,
1814
- ...strings ?? {}
1932
+ ...strings != null ? strings : {}
1815
1933
  };
1816
1934
  this.options = {
1817
1935
  closeOnBgClick: true,
@@ -1828,11 +1946,12 @@ var Dialog = class _Dialog extends NanoEmitter {
1828
1946
  //#region public
1829
1947
  /** Call after DOMContentLoaded to pre-render the dialog and invisibly mount it in the DOM */
1830
1948
  async mount() {
1949
+ var _a;
1831
1950
  if (this.dialogMounted)
1832
1951
  return;
1833
1952
  this.dialogMounted = true;
1834
1953
  if (!document.querySelector("style.uu-dialog-css"))
1835
- addGlobalStyle(this.options.dialogCss ?? defaultDialogCss).classList.add("uu-dialog-css");
1954
+ addGlobalStyle((_a = this.options.dialogCss) != null ? _a : defaultDialogCss).classList.add("uu-dialog-css");
1836
1955
  const bgElem = document.createElement("div");
1837
1956
  bgElem.id = `uu-${this.id}-dialog-bg`;
1838
1957
  bgElem.classList.add("uu-dialog-bg");
@@ -1900,7 +2019,7 @@ var Dialog = class _Dialog extends NanoEmitter {
1900
2019
  }
1901
2020
  /** Closes the dialog - prevents default action and immediate propagation of the passed event */
1902
2021
  close(e) {
1903
- var _a;
2022
+ var _a, _b;
1904
2023
  e == null ? void 0 : e.preventDefault();
1905
2024
  e == null ? void 0 : e.stopImmediatePropagation();
1906
2025
  if (!this.isOpen())
@@ -1913,9 +2032,9 @@ var Dialog = class _Dialog extends NanoEmitter {
1913
2032
  dialogBg.style.display = "none";
1914
2033
  dialogBg.inert = true;
1915
2034
  openDialogs.splice(openDialogs.indexOf(this.id), 1);
1916
- currentDialogId = openDialogs[0] ?? null;
2035
+ currentDialogId = (_a = openDialogs[0]) != null ? _a : null;
1917
2036
  if (currentDialogId)
1918
- (_a = document.querySelector(`#uu-${currentDialogId}-dialog-bg`)) == null ? void 0 : _a.removeAttribute("inert");
2037
+ (_b = document.querySelector(`#uu-${currentDialogId}-dialog-bg`)) == null ? void 0 : _b.removeAttribute("inert");
1919
2038
  if (openDialogs.length === 0) {
1920
2039
  document.body.classList.add("uu-no-select");
1921
2040
  document.body.removeAttribute("inert");
@@ -1951,7 +2070,8 @@ var Dialog = class _Dialog extends NanoEmitter {
1951
2070
  }
1952
2071
  //#region protected
1953
2072
  getString(key) {
1954
- return this.strings[key] ?? defaultStrings[key];
2073
+ var _a;
2074
+ return (_a = this.strings[key]) != null ? _a : defaultStrings[key];
1955
2075
  }
1956
2076
  /** Called once to attach all generic event listeners */
1957
2077
  attachListeners(bgElem) {
@@ -1976,7 +2096,7 @@ var Dialog = class _Dialog extends NanoEmitter {
1976
2096
  * @param listenerOptions Provide a {@linkcode listenerOptions} object to configure the listeners
1977
2097
  */
1978
2098
  onInteraction(elem, listener, listenerOptions) {
1979
- const { preventDefault = true, stopPropagation = true, ...listenerOpts } = listenerOptions ?? {};
2099
+ const { preventDefault = true, stopPropagation = true, ...listenerOpts } = listenerOptions != null ? listenerOptions : {};
1980
2100
  const interactionKeys = ["Enter", " ", "Space"];
1981
2101
  const proxListener = (e) => {
1982
2102
  if (e instanceof KeyboardEvent) {
@@ -2056,7 +2176,6 @@ var Dialog = class _Dialog extends NanoEmitter {
2056
2176
 
2057
2177
  // lib/GMStorageEngine.ts
2058
2178
  var GMStorageEngine = class extends DataStoreEngine {
2059
- options;
2060
2179
  /**
2061
2180
  * Creates an instance of `GMStorageEngine`.
2062
2181
  *
@@ -2065,6 +2184,7 @@ var GMStorageEngine = class extends DataStoreEngine {
2065
2184
  */
2066
2185
  constructor(options) {
2067
2186
  super(options == null ? void 0 : options.dataStoreOptions);
2187
+ __publicField(this, "options");
2068
2188
  this.options = {
2069
2189
  ...options
2070
2190
  };
@@ -2120,25 +2240,26 @@ var GMStorageEngine = class extends DataStoreEngine {
2120
2240
 
2121
2241
  // lib/Mixins.ts
2122
2242
  var Mixins = class {
2123
- /** List of all registered mixins */
2124
- mixins = [];
2125
- /** Default configuration object for mixins */
2126
- defaultMixinCfg;
2127
- /** Whether the priorities should auto-increment if not specified */
2128
- autoIncPrioEnabled;
2129
- /** The current auto-increment priority counter */
2130
- autoIncPrioCounter = /* @__PURE__ */ new Map();
2131
2243
  /**
2132
2244
  * Creates a new Mixins instance.
2133
2245
  * @param config Configuration object to customize the behavior.
2134
2246
  */
2135
2247
  constructor(config = {}) {
2248
+ /** List of all registered mixins */
2249
+ __publicField(this, "mixins", []);
2250
+ /** Default configuration object for mixins */
2251
+ __publicField(this, "defaultMixinCfg");
2252
+ /** Whether the priorities should auto-increment if not specified */
2253
+ __publicField(this, "autoIncPrioEnabled");
2254
+ /** The current auto-increment priority counter */
2255
+ __publicField(this, "autoIncPrioCounter", /* @__PURE__ */ new Map());
2256
+ var _a, _b, _c;
2136
2257
  this.defaultMixinCfg = pureObj({
2137
- priority: config.defaultPriority ?? 0,
2138
- stopPropagation: config.defaultStopPropagation ?? false,
2258
+ priority: (_a = config.defaultPriority) != null ? _a : 0,
2259
+ stopPropagation: (_b = config.defaultStopPropagation) != null ? _b : false,
2139
2260
  signal: config.defaultSignal
2140
2261
  });
2141
- this.autoIncPrioEnabled = config.autoIncrementPriority ?? false;
2262
+ this.autoIncPrioEnabled = (_c = config.autoIncrementPriority) != null ? _c : false;
2142
2263
  }
2143
2264
  //#region public
2144
2265
  /**
@@ -2203,10 +2324,11 @@ var Mixins = class {
2203
2324
  //#region protected
2204
2325
  /** Calculates the priority for a mixin based on the given configuration and the current auto-increment state of the instance */
2205
2326
  calcPriority(mixinKey, config) {
2327
+ var _a;
2206
2328
  if (config.priority !== void 0)
2207
2329
  return void 0;
2208
2330
  if (!this.autoIncPrioEnabled)
2209
- return config.priority ?? this.defaultMixinCfg.priority;
2331
+ return (_a = config.priority) != null ? _a : this.defaultMixinCfg.priority;
2210
2332
  if (!this.autoIncPrioCounter.has(mixinKey))
2211
2333
  this.autoIncPrioCounter.set(mixinKey, this.defaultMixinCfg.priority);
2212
2334
  let prio = this.autoIncPrioCounter.get(mixinKey);
@@ -2227,13 +2349,13 @@ var Mixins = class {
2227
2349
 
2228
2350
  // lib/SelectorObserver.ts
2229
2351
  var SelectorObserver = class {
2230
- enabled = false;
2231
- baseElement;
2232
- observer;
2233
- observerOptions;
2234
- customOptions;
2235
- listenerMap;
2236
2352
  constructor(baseElement, options = {}) {
2353
+ __publicField(this, "enabled", false);
2354
+ __publicField(this, "baseElement");
2355
+ __publicField(this, "observer");
2356
+ __publicField(this, "observerOptions");
2357
+ __publicField(this, "customOptions");
2358
+ __publicField(this, "listenerMap");
2237
2359
  this.baseElement = baseElement;
2238
2360
  this.listenerMap = /* @__PURE__ */ new Map();
2239
2361
  const {
@@ -2249,10 +2371,10 @@ var SelectorObserver = class {
2249
2371
  ...observerOptions
2250
2372
  };
2251
2373
  this.customOptions = {
2252
- defaultDebounce: defaultDebounce ?? 0,
2253
- defaultDebounceType: defaultDebounceType ?? "immediate",
2254
- disableOnNoListeners: disableOnNoListeners ?? false,
2255
- enableOnAddListener: enableOnAddListener ?? true
2374
+ defaultDebounce: defaultDebounce != null ? defaultDebounce : 0,
2375
+ defaultDebounceType: defaultDebounceType != null ? defaultDebounceType : "immediate",
2376
+ disableOnNoListeners: disableOnNoListeners != null ? disableOnNoListeners : false,
2377
+ enableOnAddListener: enableOnAddListener != null ? enableOnAddListener : true
2256
2378
  };
2257
2379
  if (typeof this.customOptions.checkInterval !== "number") {
2258
2380
  this.observer = new MutationObserver(() => this.checkAllSelectors());
@@ -2403,9 +2525,9 @@ var valTransforms = [];
2403
2525
  var fallbackLang;
2404
2526
  function translate(language, key, ...trArgs) {
2405
2527
  if (typeof language !== "string")
2406
- language = fallbackLang ?? "";
2528
+ language = fallbackLang != null ? fallbackLang : "";
2407
2529
  const trObj = trans[language];
2408
- if (typeof language !== "string" || language.length === 0 || typeof trObj !== "object" || trObj === null)
2530
+ if (typeof language !== "string" || typeof trObj !== "object" || trObj === null)
2409
2531
  return fallbackLang && language !== fallbackLang ? translate(fallbackLang, key, ...trArgs) : key;
2410
2532
  const transformTrVal = (trKey, trValue) => {
2411
2533
  const tfs = valTransforms.filter(({ regex }) => new RegExp(regex).test(String(trValue)));
@@ -2457,15 +2579,18 @@ function trFor(language, key, ...args) {
2457
2579
  function useTr(language) {
2458
2580
  return (key, ...args) => translate(language, key, ...args);
2459
2581
  }
2460
- function hasKey(language = fallbackLang ?? "", key) {
2582
+ function hasKey(language = fallbackLang != null ? fallbackLang : "", key) {
2461
2583
  return tr.for(language, key) !== key;
2462
2584
  }
2463
2585
  function addTranslations(language, translations) {
2464
2586
  trans[language] = JSON.parse(JSON.stringify(translations));
2465
2587
  }
2466
- function getTranslations(language = fallbackLang ?? "") {
2588
+ function getTranslations(language = fallbackLang != null ? fallbackLang : "") {
2467
2589
  return trans[language];
2468
2590
  }
2591
+ function getAllTranslations(asCopy = true) {
2592
+ return asCopy ? JSON.parse(JSON.stringify(trans)) : trans;
2593
+ }
2469
2594
  var deleteTranslations = (language) => {
2470
2595
  if (language in trans) {
2471
2596
  delete trans[language];
@@ -2496,51 +2621,55 @@ function deleteTransform(patternOrFn) {
2496
2621
  }
2497
2622
  return false;
2498
2623
  }
2499
- var templateLiteralTransform = [
2500
- /\$\{([a-zA-Z0-9$_-]+)\}/gm,
2501
- ({ matches, trArgs, trValue }) => {
2502
- const patternStart = "${", patternEnd = "}", patternRegex = /\$\{.+\}/m;
2503
- let str = String(trValue);
2504
- const eachKeyInTrString = (keys) => keys.every((key) => trValue.includes(`${patternStart}${key}${patternEnd}`));
2505
- const namedMapping = () => {
2506
- if (!str.includes(patternStart) || typeof trArgs[0] === "undefined" || typeof trArgs[0] !== "object" || !eachKeyInTrString(Object.keys(trArgs[0] ?? {})))
2507
- return;
2508
- for (const match of matches) {
2509
- const repl = match[1] !== void 0 ? trArgs[0][match[1]] : void 0;
2510
- if (typeof repl !== "undefined")
2511
- str = str.replace(match[0], String(repl));
2512
- }
2513
- };
2514
- const positionalMapping = () => {
2515
- if (!patternRegex.test(str) || !trArgs[0])
2516
- return;
2517
- let matchNum = -1;
2518
- for (const match of matches) {
2519
- matchNum++;
2520
- if (typeof trArgs[matchNum] !== "undefined")
2521
- str = str.replace(match[0], String(trArgs[matchNum]));
2522
- }
2523
- };
2524
- let notStringifiable = false;
2525
- try {
2526
- String(trArgs[0]);
2527
- } catch {
2528
- notStringifiable = true;
2624
+ var commonKeyedTransform = ({ matches, trArgs, trValue }, patternRegex, quickMatchPattern) => {
2625
+ let str = String(trValue);
2626
+ const someMatchKeyInArgs = (obj) => matches.some((match) => match[1] !== void 0 && match[1] in obj);
2627
+ const namedMapping = () => {
2628
+ if (!str.includes(quickMatchPattern) || typeof trArgs[0] === "undefined" || typeof trArgs[0] !== "object" || !someMatchKeyInArgs(trArgs[0]))
2629
+ return;
2630
+ for (const match of matches) {
2631
+ const repl = match[1] !== void 0 ? trArgs[0][match[1]] : void 0;
2632
+ if (typeof repl !== "undefined")
2633
+ str = str.replace(match[0], String(repl));
2529
2634
  }
2530
- const isArgsObject = trArgs[0] && typeof trArgs[0] === "object" && trArgs[0] !== null && (notStringifiable || String(trArgs[0]).startsWith("[object"));
2531
- if (isArgsObject && eachKeyInTrString(Object.keys(trArgs[0])))
2532
- namedMapping();
2533
- else
2534
- positionalMapping();
2535
- return str;
2635
+ };
2636
+ const positionalMapping = () => {
2637
+ if (!patternRegex.test(str) || typeof trArgs[0] === "undefined")
2638
+ return;
2639
+ let matchNum = -1;
2640
+ for (const match of matches) {
2641
+ matchNum++;
2642
+ if (typeof trArgs[matchNum] !== "undefined")
2643
+ str = str.replace(match[0], String(trArgs[matchNum]));
2644
+ }
2645
+ };
2646
+ let notStringifiable = false;
2647
+ try {
2648
+ void `${trArgs[0]}`;
2649
+ } catch (e) {
2650
+ notStringifiable = true;
2536
2651
  }
2652
+ const isArgsObject = trArgs[0] && typeof trArgs[0] === "object" && trArgs[0] !== null && (notStringifiable || String(trArgs[0]).startsWith("[object"));
2653
+ if (isArgsObject && someMatchKeyInArgs(trArgs[0]))
2654
+ namedMapping();
2655
+ else
2656
+ positionalMapping();
2657
+ return str;
2658
+ };
2659
+ var templateLiteralTransform = [
2660
+ /\$\{([a-zA-Z0-9$_-]+)\}/gm,
2661
+ (tfProps) => commonKeyedTransform(tfProps, /\$\{.+\}/m, "${")
2662
+ ];
2663
+ var i18nTransform = [
2664
+ /\{\{([a-zA-Z0-9$_-]+)\}\}/gm,
2665
+ (tfProps) => commonKeyedTransform(tfProps, /\{\{.+\}\}/m, "{{")
2537
2666
  ];
2538
2667
  var percentTransform = [
2539
2668
  /%(\d+)/gm,
2540
2669
  ({ matches, trArgs, trValue }) => {
2541
2670
  let str = String(trValue);
2542
2671
  for (const match of matches) {
2543
- const repl = match[1] !== void 0 ? trArgs == null ? void 0 : trArgs[Number(match[1]) - 1] : void 0;
2672
+ const repl = trArgs == null ? void 0 : trArgs[Number(match[1]) - 1];
2544
2673
  if (typeof repl !== "undefined")
2545
2674
  str = str.replace(match[0], String(repl));
2546
2675
  }
@@ -2550,17 +2679,84 @@ var percentTransform = [
2550
2679
  var tr = {
2551
2680
  for: (...params) => trFor(...params),
2552
2681
  use: (...params) => useTr(...params),
2553
- hasKey: (language = fallbackLang ?? "", key) => hasKey(language, key),
2682
+ hasKey: (language = fallbackLang != null ? fallbackLang : "", key) => hasKey(language, key),
2554
2683
  addTranslations,
2555
2684
  getTranslations,
2685
+ getAllTranslations,
2556
2686
  deleteTranslations,
2557
2687
  setFallbackLanguage,
2558
2688
  getFallbackLanguage,
2559
2689
  addTransform,
2560
2690
  deleteTransform,
2691
+ /** Collection of predefined transform functions that can be added via {@linkcode tr.addTransform()} */
2561
2692
  transforms: {
2693
+ /**
2694
+ * This transform will replace placeholders matching `${key}` with the value of the passed argument(s).
2695
+ * The arguments can be passed in keyed object form or positionally via the spread operator:
2696
+ * - Keyed: If the first argument is an object and `key` is found in it, the value will be used for the replacement.
2697
+ * - Positional: If the first argument is not an object or has a `toString()` method that returns something that doesn't start with `[object`, the values will be positionally inserted in the order they were passed.
2698
+ *
2699
+ * @example ```ts
2700
+ * tr.addTranslations("en", {
2701
+ * "greeting": "Hello, ${user}!\nYou have ${notifs} notifications.",
2702
+ * });
2703
+ * tr.addTransform(tr.transforms.templateLiteral);
2704
+ *
2705
+ * const t = tr.use("en");
2706
+ *
2707
+ * // both calls return the same result:
2708
+ * t("greeting", { user: "John", notifs: 5 }); // "Hello, John!\nYou have 5 notifications."
2709
+ * t("greeting", "John", 5); // "Hello, John!\nYou have 5 notifications."
2710
+ *
2711
+ * // when a key isn't found in the object, it will be left as-is:
2712
+ * t("greeting", { user: "John" }); // "Hello, John!\nYou have ${notifs} notifications."
2713
+ * ```
2714
+ */
2562
2715
  templateLiteral: templateLiteralTransform,
2716
+ /**
2717
+ * This transform will replace placeholders matching `{{key}}` with the value of the passed argument(s).
2718
+ * This format is commonly used in i18n libraries. Note that advanced syntax is not supported, only simple key replacement.
2719
+ * The arguments can be passed in keyed object form or positionally via the spread operator:
2720
+ * - Keyed: If the first argument is an object and `key` is found in it, the value will be used for the replacement.
2721
+ * - Positional: If the first argument is not an object or has a `toString()` method that returns something that doesn't start with `[object`, the values will be positionally inserted in the order they were passed.
2722
+ *
2723
+ * @example ```ts
2724
+ * tr.addTranslations("en", {
2725
+ * "greeting": "Hello, {{user}}!\nYou have {{notifs}} notifications.",
2726
+ * });
2727
+ * tr.addTransform(tr.transforms.i18n);
2728
+ *
2729
+ * const t = tr.use("en");
2730
+ *
2731
+ * // both calls return the same result:
2732
+ * t("greeting", { user: "Alice", notifs: 5 }); // "Hello, Alice!\nYou have 5 notifications."
2733
+ * t("greeting", "Alice", 5); // "Hello, Alice!\nYou have 5 notifications."
2734
+ *
2735
+ * // when a key isn't found in the object, it will be left as-is:
2736
+ * t("greeting", { user: "Alice" }); // "Hello, Alice!\nYou have {{notifs}} notifications."
2737
+ * ```
2738
+ */
2739
+ i18n: i18nTransform,
2740
+ /**
2741
+ * This transform will replace `%n` placeholders with the value of the passed arguments.
2742
+ * The `%n` placeholders are 1-indexed, meaning `%1` will be replaced by the first argument (index 0), `%2` by the second (index 1), and so on.
2743
+ * Objects will be stringified via `String()` before being inserted.
2744
+ *
2745
+ * @example ```ts
2746
+ * tr.addTranslations("en", {
2747
+ * "greeting": "Hello, %1!\nYou have %2 notifications.",
2748
+ * });
2749
+ * tr.addTransform(tr.transforms.percent);
2750
+ *
2751
+ * const t = tr.use("en");
2752
+ *
2753
+ * // arguments are inserted in the order they're passed:
2754
+ * t("greeting", "Bob", 5); // "Hello, Bob!\nYou have 5 notifications."
2755
+ *
2756
+ * // when a value isn't found, the placeholder will be left as-is:
2757
+ * t("greeting", "Bob"); // "Hello, Bob!\nYou have %2 notifications."
2758
+ * ```
2759
+ */
2563
2760
  percent: percentTransform
2564
2761
  }
2565
2762
  };
2566
- //# sourceMappingURL=UserUtils.cjs.map