@sv443-network/userutils 10.0.5 → 10.1.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 = {};
@@ -110,11 +112,12 @@ __export(lib_exports, {
110
112
  takeRandomItemIndex: () => takeRandomItemIndex,
111
113
  tr: () => tr,
112
114
  truncStr: () => truncStr,
113
- valsWithin: () => valsWithin
115
+ valsWithin: () => valsWithin,
116
+ versions: () => versions
114
117
  });
115
118
  module.exports = __toCommonJS(lib_exports);
116
119
 
117
- // node_modules/.pnpm/@sv443-network+coreutils@3.0.4/node_modules/@sv443-network/coreutils/dist/CoreUtils.mjs
120
+ // node_modules/.pnpm/@sv443-network+coreutils@3.3.0/node_modules/@sv443-network/coreutils/dist/CoreUtils.mjs
118
121
  function bitSetHas(bitSet, checkVal) {
119
122
  return (bitSet & checkVal) === checkVal;
120
123
  }
@@ -260,7 +263,7 @@ function darkenColor(color, percent, upperCase = false) {
260
263
  if (isHexCol)
261
264
  return rgbToHex(r, g, b, a, color.startsWith("#"), upperCase);
262
265
  else if (color.startsWith("rgba"))
263
- return `rgba(${r}, ${g}, ${b}, ${a ?? NaN})`;
266
+ return `rgba(${r}, ${g}, ${b}, ${a != null ? a : NaN})`;
264
267
  else
265
268
  return `rgb(${r}, ${g}, ${b})`;
266
269
  }
@@ -293,7 +296,8 @@ function atoab(str) {
293
296
  return Uint8Array.from(atob(str), (c) => c.charCodeAt(0));
294
297
  }
295
298
  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));
299
+ var _a;
300
+ const byteArray = input instanceof Uint8Array ? input : new TextEncoder().encode((_a = input == null ? void 0 : input.toString()) != null ? _a : String(input));
297
301
  const comp = new CompressionStream(compressionFormat);
298
302
  const writer = comp.writable.getWriter();
299
303
  writer.write(byteArray);
@@ -302,7 +306,8 @@ async function compress(input, compressionFormat, outputType = "string") {
302
306
  return outputType === "arrayBuffer" ? uintArr : abtoa(uintArr);
303
307
  }
304
308
  async function decompress(input, compressionFormat, outputType = "string") {
305
- const byteArray = input instanceof Uint8Array ? input : atoab((input == null ? void 0 : input.toString()) ?? String(input));
309
+ var _a;
310
+ const byteArray = input instanceof Uint8Array ? input : atoab((_a = input == null ? void 0 : input.toString()) != null ? _a : String(input));
306
311
  const decomp = new DecompressionStream(compressionFormat);
307
312
  const writer = decomp.writable.getWriter();
308
313
  writer.write(byteArray);
@@ -347,9 +352,9 @@ function randomId(length = 16, radix = 16, enhancedEntropy = false, randomCase =
347
352
  return arr.map((v) => caseArr[randRange(0, caseArr.length - 1, enhancedEntropy)] === 1 ? v.toUpperCase() : v).join("");
348
353
  }
349
354
  var DatedError = class extends Error {
350
- date;
351
355
  constructor(message, options) {
352
356
  super(message, options);
357
+ __publicField(this, "date");
353
358
  this.name = this.constructor.name;
354
359
  this.date = /* @__PURE__ */ new Date();
355
360
  }
@@ -432,7 +437,7 @@ function pauseFor(time, signal, rejectOnAbort = false) {
432
437
  });
433
438
  }
434
439
  function pureObj(obj) {
435
- return Object.assign(/* @__PURE__ */ Object.create(null), obj ?? {});
440
+ return Object.assign(/* @__PURE__ */ Object.create(null), obj != null ? obj : {});
436
441
  }
437
442
  function setImmediateInterval(callback, interval, signal) {
438
443
  let intervalId;
@@ -471,12 +476,13 @@ function scheduleExit(code = 0, timeout = 0) {
471
476
  setTimeout(exit, timeout);
472
477
  }
473
478
  function getCallStack(asArray, lines = Infinity) {
479
+ var _a;
474
480
  if (typeof lines !== "number" || isNaN(lines) || lines < 0)
475
481
  throw new TypeError("lines parameter must be a non-negative number");
476
482
  try {
477
483
  throw new Error("This is to capture a stack trace with CoreUtils.getCallStack(). (If you see this somewhere, you can safely ignore it.)");
478
484
  } catch (err) {
479
- const stack = (err.stack ?? "").split("\n").map((line) => line.trim()).slice(2, lines + 2);
485
+ const stack = ((_a = err.stack) != null ? _a : "").split("\n").map((line) => line.trim()).slice(2, lines + 2);
480
486
  return asArray !== false ? stack : stack.join("\n");
481
487
  }
482
488
  }
@@ -529,9 +535,10 @@ function createProgressBar(percentage, barLength, chars = defaultPbChars) {
529
535
  }
530
536
  function insertValues(input, ...values) {
531
537
  return input.replace(/%\d/gm, (match) => {
538
+ var _a2;
532
539
  var _a;
533
540
  const argIndex = Number(match.substring(1)) - 1;
534
- return (_a = values[argIndex] ?? match) == null ? void 0 : _a.toString();
541
+ return (_a = (_a2 = values[argIndex]) != null ? _a2 : match) == null ? void 0 : _a.toString();
535
542
  });
536
543
  }
537
544
  function joinArrayReadable(array, separators = ", ", lastSeparator = " and ") {
@@ -562,31 +569,209 @@ function secsToTimeStr(seconds) {
562
569
  ].join("");
563
570
  }
564
571
  function truncStr(input, length, endStr = "...") {
565
- const str = (input == null ? void 0 : input.toString()) ?? String(input);
572
+ var _a;
573
+ const str = (_a = input == null ? void 0 : input.toString()) != null ? _a : String(input);
566
574
  const finalStr = str.length > length ? str.substring(0, length - endStr.length) + endStr : str;
567
575
  return finalStr.length > length ? finalStr.substring(0, length) : finalStr;
568
576
  }
569
- var dsFmtVer = 1;
570
- var DataStore = class {
571
- id;
572
- formatVersion;
573
- defaultData;
574
- encodeData;
575
- decodeData;
576
- compressionFormat = "deflate-raw";
577
- memoryCache = true;
578
- engine;
579
- options;
577
+ var createNanoEvents = () => ({
578
+ emit(event, ...args) {
579
+ for (let callbacks = this.events[event] || [], i = 0, length = callbacks.length; i < length; i++) {
580
+ callbacks[i](...args);
581
+ }
582
+ },
583
+ events: {},
584
+ on(event, cb) {
585
+ var _a;
586
+ ;
587
+ ((_a = this.events)[event] || (_a[event] = [])).push(cb);
588
+ return () => {
589
+ var _a2;
590
+ this.events[event] = (_a2 = this.events[event]) == null ? void 0 : _a2.filter((i) => cb !== i);
591
+ };
592
+ }
593
+ });
594
+ var NanoEmitter = class {
595
+ /** Creates a new instance of NanoEmitter - a lightweight event emitter with helper methods and a strongly typed event map */
596
+ constructor(options = {}) {
597
+ __publicField(this, "events", createNanoEvents());
598
+ __publicField(this, "eventUnsubscribes", []);
599
+ __publicField(this, "emitterOptions");
600
+ this.emitterOptions = {
601
+ publicEmit: false,
602
+ ...options
603
+ };
604
+ }
605
+ //#region on
606
+ /**
607
+ * Subscribes to an event and calls the callback when it's emitted.
608
+ * @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 "_")
609
+ * @returns Returns a function that can be called to unsubscribe the event listener
610
+ * @example ```ts
611
+ * const emitter = new NanoEmitter<{
612
+ * foo: (bar: string) => void;
613
+ * }>({
614
+ * publicEmit: true,
615
+ * });
616
+ *
617
+ * let i = 0;
618
+ * const unsub = emitter.on("foo", (bar) => {
619
+ * // unsubscribe after 10 events:
620
+ * if(++i === 10) unsub();
621
+ * console.log(bar);
622
+ * });
623
+ *
624
+ * emitter.emit("foo", "bar");
625
+ * ```
626
+ */
627
+ on(event, cb) {
628
+ let unsub;
629
+ const unsubProxy = () => {
630
+ if (!unsub)
631
+ return;
632
+ unsub();
633
+ this.eventUnsubscribes = this.eventUnsubscribes.filter((u) => u !== unsub);
634
+ };
635
+ unsub = this.events.on(event, cb);
636
+ this.eventUnsubscribes.push(unsub);
637
+ return unsubProxy;
638
+ }
639
+ //#region once
640
+ /**
641
+ * Subscribes to an event and calls the callback or resolves the Promise only once when it's emitted.
642
+ * @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 "_")
643
+ * @param cb The callback to call when the event is emitted - if provided or not, the returned Promise will resolve with the event arguments
644
+ * @returns Returns a Promise that resolves with the event arguments when the event is emitted
645
+ * @example ```ts
646
+ * const emitter = new NanoEmitter<{
647
+ * foo: (bar: string) => void;
648
+ * }>();
649
+ *
650
+ * // Promise syntax:
651
+ * const [bar] = await emitter.once("foo");
652
+ * console.log(bar);
653
+ *
654
+ * // Callback syntax:
655
+ * emitter.once("foo", (bar) => console.log(bar));
656
+ * ```
657
+ */
658
+ once(event, cb) {
659
+ return new Promise((resolve) => {
660
+ let unsub;
661
+ const onceProxy = ((...args) => {
662
+ cb == null ? void 0 : cb(...args);
663
+ unsub == null ? void 0 : unsub();
664
+ resolve(args);
665
+ });
666
+ unsub = this.events.on(event, onceProxy);
667
+ this.eventUnsubscribes.push(unsub);
668
+ });
669
+ }
670
+ //#region onMulti
671
+ /**
672
+ * 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.
673
+ * @param options An object or array of objects with the following properties:
674
+ * `callback` (required) is the function that will be called when the conditions are met.
675
+ *
676
+ * 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.
677
+ * If `signal` is provided, the subscription will be canceled when the given signal is aborted.
678
+ *
679
+ * If `oneOf` is used, the callback will be called when any of the matching events are emitted.
680
+ * 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.
681
+ * 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.
682
+ * At least one of `oneOf` or `allOf` must be provided.
683
+ *
684
+ * @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.
685
+ */
686
+ onMulti(options) {
687
+ const allUnsubs = [];
688
+ const unsubAll = () => {
689
+ for (const unsub of allUnsubs)
690
+ unsub();
691
+ allUnsubs.splice(0, allUnsubs.length);
692
+ this.eventUnsubscribes = this.eventUnsubscribes.filter((u) => !allUnsubs.includes(u));
693
+ };
694
+ for (const opts of Array.isArray(options) ? options : [options]) {
695
+ const optsWithDefaults = {
696
+ allOf: [],
697
+ oneOf: [],
698
+ once: false,
699
+ ...opts
700
+ };
701
+ const {
702
+ oneOf,
703
+ allOf,
704
+ once,
705
+ signal,
706
+ callback
707
+ } = optsWithDefaults;
708
+ if (signal == null ? void 0 : signal.aborted)
709
+ return unsubAll;
710
+ if (oneOf.length === 0 && allOf.length === 0)
711
+ throw new TypeError("NanoEmitter.onMulti(): Either `oneOf` or `allOf` or both must be provided in the options");
712
+ const curEvtUnsubs = [];
713
+ const checkUnsubAllEvt = (force = false) => {
714
+ if (!(signal == null ? void 0 : signal.aborted) && !force)
715
+ return;
716
+ for (const unsub of curEvtUnsubs)
717
+ unsub();
718
+ curEvtUnsubs.splice(0, curEvtUnsubs.length);
719
+ this.eventUnsubscribes = this.eventUnsubscribes.filter((u) => !curEvtUnsubs.includes(u));
720
+ };
721
+ const allOfEmitted = /* @__PURE__ */ new Set();
722
+ const allOfConditionMet = () => allOf.length === 0 || allOfEmitted.size === allOf.length;
723
+ for (const event of oneOf) {
724
+ const unsub = this.events.on(event, ((...args) => {
725
+ checkUnsubAllEvt();
726
+ if (allOfConditionMet()) {
727
+ callback(event, ...args);
728
+ if (once)
729
+ checkUnsubAllEvt(true);
730
+ }
731
+ }));
732
+ curEvtUnsubs.push(unsub);
733
+ }
734
+ for (const event of allOf) {
735
+ const unsub = this.events.on(event, ((...args) => {
736
+ checkUnsubAllEvt();
737
+ allOfEmitted.add(event);
738
+ if (allOfConditionMet() && (oneOf.length === 0 || oneOf.includes(event))) {
739
+ callback(event, ...args);
740
+ if (once)
741
+ checkUnsubAllEvt(true);
742
+ }
743
+ }));
744
+ curEvtUnsubs.push(unsub);
745
+ }
746
+ allUnsubs.push(() => checkUnsubAllEvt(true));
747
+ }
748
+ return unsubAll;
749
+ }
750
+ //#region emit
580
751
  /**
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.
752
+ * Emits an event on this instance.
753
+ * - ⚠️ Needs `publicEmit` to be set to true in the NanoEmitter constructor or super() call!
754
+ * @param event The event to emit
755
+ * @param args The arguments to pass to the event listeners
756
+ * @returns Returns true if `publicEmit` is true and the event was emitted successfully
584
757
  */
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 = [];
758
+ emit(event, ...args) {
759
+ if (this.emitterOptions.publicEmit) {
760
+ this.events.emit(event, ...args);
761
+ return true;
762
+ }
763
+ return false;
764
+ }
765
+ //#region unsubscribeAll
766
+ /** Unsubscribes all event listeners from this instance */
767
+ unsubscribeAll() {
768
+ for (const unsub of this.eventUnsubscribes)
769
+ unsub();
770
+ this.eventUnsubscribes = [];
771
+ }
772
+ };
773
+ var dsFmtVer = 1;
774
+ var DataStore = class extends NanoEmitter {
590
775
  //#region constructor
591
776
  /**
592
777
  * Creates an instance of DataStore to manage a sync & async database that is cached in memory and persistently saved across sessions.
@@ -598,20 +783,43 @@ var DataStore = class {
598
783
  * @param opts The options for this DataStore instance
599
784
  */
600
785
  constructor(opts) {
786
+ var _a, _b, _c;
787
+ super(opts.nanoEmitterOptions);
788
+ __publicField(this, "id");
789
+ __publicField(this, "formatVersion");
790
+ __publicField(this, "defaultData");
791
+ __publicField(this, "encodeData");
792
+ __publicField(this, "decodeData");
793
+ __publicField(this, "compressionFormat", "deflate-raw");
794
+ __publicField(this, "memoryCache");
795
+ __publicField(this, "engine");
796
+ __publicField(this, "keyPrefix");
797
+ __publicField(this, "options");
798
+ /**
799
+ * Whether all first-init checks should be done.
800
+ * This includes migrating the internal DataStore format, migrating data from the UserUtils format, and anything similar.
801
+ * 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.
802
+ */
803
+ __publicField(this, "firstInit", true);
804
+ /** In-memory cached copy of the data that is saved in persistent storage used for synchronous read access. */
805
+ __publicField(this, "cachedData");
806
+ __publicField(this, "migrations");
807
+ __publicField(this, "migrateIds", []);
601
808
  this.id = opts.id;
602
809
  this.formatVersion = opts.formatVersion;
603
810
  this.defaultData = opts.defaultData;
604
- this.memoryCache = Boolean(opts.memoryCache ?? true);
811
+ this.memoryCache = (_a = opts.memoryCache) != null ? _a : true;
605
812
  this.cachedData = this.memoryCache ? opts.defaultData : {};
606
813
  this.migrations = opts.migrations;
607
814
  if (opts.migrateIds)
608
815
  this.migrateIds = Array.isArray(opts.migrateIds) ? opts.migrateIds : [opts.migrateIds];
609
816
  this.engine = typeof opts.engine === "function" ? opts.engine() : opts.engine;
817
+ this.keyPrefix = (_b = opts.keyPrefix) != null ? _b : "__ds-";
610
818
  this.options = opts;
611
819
  if ("encodeData" in opts && "decodeData" in opts && Array.isArray(opts.encodeData) && Array.isArray(opts.decodeData)) {
612
820
  this.encodeData = [opts.encodeData[0], opts.encodeData[1]];
613
821
  this.decodeData = [opts.decodeData[0], opts.decodeData[1]];
614
- this.compressionFormat = opts.encodeData[0] ?? null;
822
+ this.compressionFormat = (_c = opts.encodeData[0]) != null ? _c : null;
615
823
  } else if (opts.compressionFormat === null) {
616
824
  this.encodeData = void 0;
617
825
  this.decodeData = void 0;
@@ -635,6 +843,7 @@ var DataStore = class {
635
843
  * Also runs all necessary migration functions if the data format has changed since the last time the data was saved.
636
844
  */
637
845
  async loadData() {
846
+ var _a;
638
847
  try {
639
848
  if (this.firstInit) {
640
849
  this.firstInit = false;
@@ -648,13 +857,13 @@ var DataStore = class {
648
857
  promises.push(this.engine.setValue(newKey, value));
649
858
  promises.push(this.engine.deleteValue(oldKey));
650
859
  };
651
- migrateFmt(`_uucfg-${this.id}`, `__ds-${this.id}-dat`, oldData);
860
+ migrateFmt(`_uucfg-${this.id}`, `${this.keyPrefix}${this.id}-dat`, oldData);
652
861
  if (!isNaN(oldVer))
653
- migrateFmt(`_uucfgver-${this.id}`, `__ds-${this.id}-ver`, oldVer);
862
+ migrateFmt(`_uucfgver-${this.id}`, `${this.keyPrefix}${this.id}-ver`, oldVer);
654
863
  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);
864
+ migrateFmt(`_uucfgenc-${this.id}`, `${this.keyPrefix}${this.id}-enf`, [0, "0", true, "true"].includes(oldEnc) ? (_a = this.compressionFormat) != null ? _a : null : null);
656
865
  else {
657
- promises.push(this.engine.setValue(`__ds-${this.id}-enf`, this.compressionFormat));
866
+ promises.push(this.engine.setValue(`${this.keyPrefix}${this.id}-enf`, this.compressionFormat));
658
867
  promises.push(this.engine.deleteValue(`_uucfgenc-${this.id}`));
659
868
  }
660
869
  await Promise.allSettled(promises);
@@ -666,31 +875,27 @@ var DataStore = class {
666
875
  await this.migrateId(this.migrateIds);
667
876
  this.migrateIds = [];
668
877
  }
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);
878
+ const storedDataRaw = await this.engine.getValue(`${this.keyPrefix}${this.id}-dat`, null);
879
+ const storedFmtVer = Number(await this.engine.getValue(`${this.keyPrefix}${this.id}-ver`, NaN));
880
+ if (typeof storedDataRaw !== "string" && typeof storedDataRaw !== "object" || storedDataRaw === null || isNaN(storedFmtVer)) {
881
+ await this.saveDefaultData(false);
882
+ const data = this.engine.deepCopy(this.defaultData);
883
+ this.events.emit("loadData", data);
884
+ return data;
674
885
  }
675
- const storedData = storedDataRaw ?? JSON.stringify(this.defaultData);
676
- const encodingFmt = String(await this.engine.getValue(`__ds-${this.id}-enf`, null));
886
+ const storedData = storedDataRaw != null ? storedDataRaw : JSON.stringify(this.defaultData);
887
+ const encodingFmt = String(await this.engine.getValue(`${this.keyPrefix}${this.id}-enf`, null));
677
888
  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);
889
+ let parsed = typeof storedData === "string" ? await this.engine.deserializeData(storedData, isEncoded) : storedData;
684
890
  if (storedFmtVer < this.formatVersion && this.migrations)
685
891
  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);
892
+ const result = this.memoryCache ? this.cachedData = this.engine.deepCopy(parsed) : this.engine.deepCopy(parsed);
893
+ this.events.emit("loadData", result);
894
+ return result;
692
895
  } catch (err) {
896
+ const error = err instanceof Error ? err : new Error(String(err));
693
897
  console.warn("Error while parsing JSON data, resetting it to the default value.", err);
898
+ this.events.emit("error", error);
694
899
  await this.saveDefaultData();
695
900
  return this.defaultData;
696
901
  }
@@ -699,7 +904,7 @@ var DataStore = class {
699
904
  /**
700
905
  * Returns a copy of the data from the in-memory cache.
701
906
  * Use {@linkcode loadData()} to get fresh data from persistent storage (usually not necessary since the cache should always exactly reflect persistent storage).
702
- * ⚠️ If `memoryCache` was set to `false` in the constructor options, this method will throw an error.
907
+ * ⚠️ Only available when `memoryCache` is `true` (default). When set to `false`, this produces a type and runtime error - use {@linkcode loadData()} instead.
703
908
  */
704
909
  getData() {
705
910
  if (!this.memoryCache)
@@ -709,27 +914,47 @@ var DataStore = class {
709
914
  //#region setData
710
915
  /** Saves the data synchronously to the in-memory cache and asynchronously to the persistent storage */
711
916
  setData(data) {
712
- if (this.memoryCache)
917
+ const dataCopy = this.engine.deepCopy(data);
918
+ if (this.memoryCache) {
713
919
  this.cachedData = data;
920
+ this.events.emit("updateDataSync", dataCopy);
921
+ }
714
922
  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)
923
+ const results = await Promise.allSettled([
924
+ this.engine.setValue(`${this.keyPrefix}${this.id}-dat`, await this.engine.serializeData(data, this.encodingEnabled())),
925
+ this.engine.setValue(`${this.keyPrefix}${this.id}-ver`, this.formatVersion),
926
+ this.engine.setValue(`${this.keyPrefix}${this.id}-enf`, this.compressionFormat)
719
927
  ]);
928
+ if (results.every((r) => r.status === "fulfilled"))
929
+ this.events.emit("updateData", dataCopy);
930
+ else {
931
+ const error = new Error("Error while saving data to persistent storage: " + results.map((r) => r.status === "rejected" ? r.reason : null).filter(Boolean).join("; "));
932
+ console.error(error);
933
+ this.events.emit("error", error);
934
+ }
720
935
  resolve();
721
936
  });
722
937
  }
723
938
  //#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() {
939
+ /**
940
+ * Saves the default data passed in the constructor synchronously to the in-memory cache and asynchronously to persistent storage.
941
+ * @param emitEvent Whether to emit the `setDefaultData` event - set to `false` to prevent event emission (used internally during initial population in {@linkcode loadData()})
942
+ */
943
+ async saveDefaultData(emitEvent = true) {
726
944
  if (this.memoryCache)
727
945
  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)
946
+ const results = await Promise.allSettled([
947
+ this.engine.setValue(`${this.keyPrefix}${this.id}-dat`, await this.engine.serializeData(this.defaultData, this.encodingEnabled())),
948
+ this.engine.setValue(`${this.keyPrefix}${this.id}-ver`, this.formatVersion),
949
+ this.engine.setValue(`${this.keyPrefix}${this.id}-enf`, this.compressionFormat)
732
950
  ]);
951
+ if (results.every((r) => r.status === "fulfilled"))
952
+ emitEvent && this.events.emit("setDefaultData", this.defaultData);
953
+ else {
954
+ const error = new Error("Error while saving default data to persistent storage: " + results.map((r) => r.status === "rejected" ? r.reason : null).filter(Boolean).join("; "));
955
+ console.error(error);
956
+ this.events.emit("error", error);
957
+ }
733
958
  }
734
959
  //#region deleteData
735
960
  /**
@@ -740,11 +965,12 @@ var DataStore = class {
740
965
  async deleteData() {
741
966
  var _a, _b;
742
967
  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`)
968
+ this.engine.deleteValue(`${this.keyPrefix}${this.id}-dat`),
969
+ this.engine.deleteValue(`${this.keyPrefix}${this.id}-ver`),
970
+ this.engine.deleteValue(`${this.keyPrefix}${this.id}-enf`)
746
971
  ]);
747
972
  await ((_b = (_a = this.engine).deleteStorage) == null ? void 0 : _b.call(_a));
973
+ this.events.emit("deleteData");
748
974
  }
749
975
  //#region encodingEnabled
750
976
  /** Returns whether encoding and decoding are enabled for this DataStore instance */
@@ -765,30 +991,35 @@ var DataStore = class {
765
991
  let newData = oldData;
766
992
  const sortedMigrations = Object.entries(this.migrations).sort(([a], [b]) => Number(a) - Number(b));
767
993
  let lastFmtVer = oldFmtVer;
768
- for (const [fmtVer, migrationFunc] of sortedMigrations) {
994
+ for (let i = 0; i < sortedMigrations.length; i++) {
995
+ const [fmtVer, migrationFunc] = sortedMigrations[i];
769
996
  const ver = Number(fmtVer);
770
997
  if (oldFmtVer < this.formatVersion && oldFmtVer < ver) {
771
998
  try {
772
999
  const migRes = migrationFunc(newData);
773
1000
  newData = migRes instanceof Promise ? await migRes : migRes;
774
1001
  lastFmtVer = oldFmtVer = ver;
1002
+ const isFinal = ver >= this.formatVersion || i === sortedMigrations.length - 1;
1003
+ this.events.emit("migrateData", ver, newData, isFinal);
775
1004
  } catch (err) {
1005
+ const migError = new MigrationError(`Error while running migration function for format version '${fmtVer}'`, { cause: err });
1006
+ this.events.emit("migrationError", ver, migError);
1007
+ this.events.emit("error", migError);
776
1008
  if (!resetOnError)
777
- throw new MigrationError(`Error while running migration function for format version '${fmtVer}'`, { cause: err });
1009
+ throw migError;
778
1010
  await this.saveDefaultData();
779
- return this.getData();
1011
+ return this.engine.deepCopy(this.defaultData);
780
1012
  }
781
1013
  }
782
1014
  }
783
1015
  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)
1016
+ this.engine.setValue(`${this.keyPrefix}${this.id}-dat`, await this.engine.serializeData(newData, this.encodingEnabled())),
1017
+ this.engine.setValue(`${this.keyPrefix}${this.id}-ver`, lastFmtVer),
1018
+ this.engine.setValue(`${this.keyPrefix}${this.id}-enf`, this.compressionFormat)
787
1019
  ]);
788
- if (this.memoryCache)
789
- return this.cachedData = this.engine.deepCopy(newData);
790
- else
791
- return this.engine.deepCopy(newData);
1020
+ const result = this.memoryCache ? this.cachedData = this.engine.deepCopy(newData) : this.engine.deepCopy(newData);
1021
+ this.events.emit("updateData", result);
1022
+ return result;
792
1023
  }
793
1024
  //#region migrateId
794
1025
  /**
@@ -800,9 +1031,9 @@ var DataStore = class {
800
1031
  await Promise.all(ids.map(async (id) => {
801
1032
  const [data, fmtVer, isEncoded] = await (async () => {
802
1033
  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)
1034
+ this.engine.getValue(`${this.keyPrefix}${id}-dat`, JSON.stringify(this.defaultData)),
1035
+ this.engine.getValue(`${this.keyPrefix}${id}-ver`, NaN),
1036
+ this.engine.getValue(`${this.keyPrefix}${id}-enf`, null)
806
1037
  ]);
807
1038
  return [d, Number(f), Boolean(e) && String(e) !== "null"];
808
1039
  })();
@@ -810,20 +1041,21 @@ var DataStore = class {
810
1041
  return;
811
1042
  const parsed = await this.engine.deserializeData(data, isEncoded);
812
1043
  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`)
1044
+ this.engine.setValue(`${this.keyPrefix}${this.id}-dat`, await this.engine.serializeData(parsed, this.encodingEnabled())),
1045
+ this.engine.setValue(`${this.keyPrefix}${this.id}-ver`, fmtVer),
1046
+ this.engine.setValue(`${this.keyPrefix}${this.id}-enf`, this.compressionFormat),
1047
+ this.engine.deleteValue(`${this.keyPrefix}${id}-dat`),
1048
+ this.engine.deleteValue(`${this.keyPrefix}${id}-ver`),
1049
+ this.engine.deleteValue(`${this.keyPrefix}${id}-enf`)
819
1050
  ]);
1051
+ this.events.emit("migrateId", id, this.id);
820
1052
  }));
821
1053
  }
822
1054
  };
823
1055
  var DataStoreEngine = class {
824
- dataStoreOptions;
825
1056
  // setDataStoreOptions() is called from inside the DataStore constructor to set this value
826
1057
  constructor(options) {
1058
+ __publicField(this, "dataStoreOptions");
827
1059
  if (options)
828
1060
  this.dataStoreOptions = options;
829
1061
  }
@@ -851,7 +1083,7 @@ var DataStoreEngine = class {
851
1083
  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
1084
  if (decRes instanceof Promise)
853
1085
  decRes = await decRes;
854
- return JSON.parse(decRes ?? data);
1086
+ return JSON.parse(decRes != null ? decRes : data);
855
1087
  }
856
1088
  //#region misc api
857
1089
  /** Throws an error if the DataStoreOptions are not set or invalid */
@@ -869,13 +1101,12 @@ var DataStoreEngine = class {
869
1101
  try {
870
1102
  if ("structuredClone" in globalThis)
871
1103
  return structuredClone(obj);
872
- } catch {
1104
+ } catch (e) {
873
1105
  }
874
1106
  return JSON.parse(JSON.stringify(obj));
875
1107
  }
876
1108
  };
877
1109
  var BrowserStorageEngine = class extends DataStoreEngine {
878
- options;
879
1110
  /**
880
1111
  * Creates an instance of `BrowserStorageEngine`.
881
1112
  *
@@ -884,6 +1115,7 @@ var BrowserStorageEngine = class extends DataStoreEngine {
884
1115
  */
885
1116
  constructor(options) {
886
1117
  super(options == null ? void 0 : options.dataStoreOptions);
1118
+ __publicField(this, "options");
887
1119
  this.options = {
888
1120
  type: "localStorage",
889
1121
  ...options
@@ -912,8 +1144,6 @@ var BrowserStorageEngine = class extends DataStoreEngine {
912
1144
  };
913
1145
  var fs;
914
1146
  var FileStorageEngine = class extends DataStoreEngine {
915
- options;
916
- fileAccessQueue = Promise.resolve();
917
1147
  /**
918
1148
  * Creates an instance of `FileStorageEngine`.
919
1149
  *
@@ -922,6 +1152,8 @@ var FileStorageEngine = class extends DataStoreEngine {
922
1152
  */
923
1153
  constructor(options) {
924
1154
  super(options == null ? void 0 : options.dataStoreOptions);
1155
+ __publicField(this, "options");
1156
+ __publicField(this, "fileAccessQueue", Promise.resolve());
925
1157
  this.options = {
926
1158
  filePath: (id) => `.ds-${id}`,
927
1159
  ...options
@@ -930,6 +1162,7 @@ var FileStorageEngine = class extends DataStoreEngine {
930
1162
  //#region json file
931
1163
  /** Reads the file contents */
932
1164
  async readFile() {
1165
+ var _a2;
933
1166
  var _a, _b, _c, _d;
934
1167
  this.ensureDataStoreOptions();
935
1168
  try {
@@ -937,15 +1170,16 @@ var FileStorageEngine = class extends DataStoreEngine {
937
1170
  fs = (_a = await import("fs/promises")) == null ? void 0 : _a.default;
938
1171
  if (!fs)
939
1172
  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);
1173
+ const path = typeof this.options.filePath === "string" ? this.options.filePath : this.options.filePath(this.dataStoreOptions.id, this.dataStoreOptions);
941
1174
  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 {
1175
+ 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;
1176
+ } catch (e) {
944
1177
  return void 0;
945
1178
  }
946
1179
  }
947
1180
  /** Overwrites the file contents */
948
1181
  async writeFile(data) {
1182
+ var _a2;
949
1183
  var _a, _b, _c, _d;
950
1184
  this.ensureDataStoreOptions();
951
1185
  try {
@@ -953,9 +1187,9 @@ var FileStorageEngine = class extends DataStoreEngine {
953
1187
  fs = (_a = await import("fs/promises")) == null ? void 0 : _a.default;
954
1188
  if (!fs)
955
1189
  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);
1190
+ const path = typeof this.options.filePath === "string" ? this.options.filePath : this.options.filePath(this.dataStoreOptions.id, this.dataStoreOptions);
957
1191
  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");
1192
+ 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
1193
  } catch (err) {
960
1194
  console.error("Error writing file:", err);
961
1195
  }
@@ -969,8 +1203,21 @@ var FileStorageEngine = class extends DataStoreEngine {
969
1203
  const value = data == null ? void 0 : data[name];
970
1204
  if (typeof value === "undefined")
971
1205
  return defaultValue;
972
- if (typeof value === "string")
973
- return value;
1206
+ if (typeof defaultValue === "string") {
1207
+ if (typeof value === "object" && value !== null)
1208
+ return JSON.stringify(value);
1209
+ if (typeof value === "string")
1210
+ return value;
1211
+ return String(value);
1212
+ }
1213
+ if (typeof value === "string") {
1214
+ try {
1215
+ const parsed = JSON.parse(value);
1216
+ return parsed;
1217
+ } catch (e) {
1218
+ return defaultValue;
1219
+ }
1220
+ }
974
1221
  return value;
975
1222
  }
976
1223
  /** Sets a value in persistent storage */
@@ -979,7 +1226,18 @@ var FileStorageEngine = class extends DataStoreEngine {
979
1226
  let data = await this.readFile();
980
1227
  if (!data)
981
1228
  data = {};
982
- data[name] = value;
1229
+ let storeVal = value;
1230
+ if (typeof value === "string") {
1231
+ try {
1232
+ if (value.startsWith("{") || value.startsWith("[")) {
1233
+ const parsed = JSON.parse(value);
1234
+ if (typeof parsed === "object" && parsed !== null)
1235
+ storeVal = parsed;
1236
+ }
1237
+ } catch (e) {
1238
+ }
1239
+ }
1240
+ data[name] = storeVal;
983
1241
  await this.writeFile(data);
984
1242
  }).catch((err) => {
985
1243
  console.error("Error in setValue:", err);
@@ -1012,7 +1270,7 @@ var FileStorageEngine = class extends DataStoreEngine {
1012
1270
  fs = (_a = await import("fs/promises")) == null ? void 0 : _a.default;
1013
1271
  if (!fs)
1014
1272
  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);
1273
+ const path = typeof this.options.filePath === "string" ? this.options.filePath : this.options.filePath(this.dataStoreOptions.id, this.dataStoreOptions);
1016
1274
  return await fs.unlink(path);
1017
1275
  } catch (err) {
1018
1276
  console.error("Error deleting file:", err);
@@ -1020,9 +1278,9 @@ var FileStorageEngine = class extends DataStoreEngine {
1020
1278
  }
1021
1279
  };
1022
1280
  var DataStoreSerializer = class _DataStoreSerializer {
1023
- stores;
1024
- options;
1025
1281
  constructor(stores, options = {}) {
1282
+ __publicField(this, "stores");
1283
+ __publicField(this, "options");
1026
1284
  if (!crypto || !crypto.subtle)
1027
1285
  throw new ScriptContextError("DataStoreSerializer has to run in a secure context (HTTPS) or in another environment that implements the subtleCrypto API!");
1028
1286
  this.stores = stores;
@@ -1078,8 +1336,9 @@ var DataStoreSerializer = class _DataStoreSerializer {
1078
1336
  if (!Array.isArray(deserStores) || !deserStores.every(_DataStoreSerializer.isSerializedDataStoreObj))
1079
1337
  throw new TypeError("Invalid serialized data format! Expected an array of SerializedDataStore objects.");
1080
1338
  const resolveStoreId = (id) => {
1339
+ var _a2;
1081
1340
  var _a;
1082
- return ((_a = Object.entries(this.options.remapIds).find(([, v]) => v.includes(id))) == null ? void 0 : _a[0]) ?? id;
1341
+ return (_a2 = (_a = Object.entries(this.options.remapIds).find(([, v]) => v.includes(id))) == null ? void 0 : _a[0]) != null ? _a2 : id;
1083
1342
  };
1084
1343
  const matchesFilter = (id) => typeof stores === "function" ? stores(id) : stores.includes(id);
1085
1344
  for (const storeData of deserStores) {
@@ -1156,201 +1415,6 @@ Has: ${checksum}`);
1156
1415
  return this.stores.filter((s) => typeof stores === "undefined" ? true : Array.isArray(stores) ? stores.includes(s.id) : stores(s.id));
1157
1416
  }
1158
1417
  };
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
1418
  var Debouncer = class extends NanoEmitter {
1355
1419
  /**
1356
1420
  * Creates a new debouncer with the specified timeout and edge type.
@@ -1359,15 +1423,15 @@ var Debouncer = class extends NanoEmitter {
1359
1423
  */
1360
1424
  constructor(timeout = 200, type = "immediate") {
1361
1425
  super();
1426
+ /** All registered listener functions and the time they were attached */
1427
+ __publicField(this, "listeners", []);
1428
+ /** The currently active timeout */
1429
+ __publicField(this, "activeTimeout");
1430
+ /** The latest queued call */
1431
+ __publicField(this, "queuedCall");
1362
1432
  this.timeout = timeout;
1363
1433
  this.type = type;
1364
1434
  }
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
1435
  //#region listeners
1372
1436
  /** Adds a listener function that will be called on timeout */
1373
1437
  addListener(fn) {
@@ -1454,6 +1518,22 @@ function debounce(fn, timeout = 200, type = "immediate") {
1454
1518
  return func;
1455
1519
  }
1456
1520
 
1521
+ // lib/consts.ts
1522
+ var rawConsts = {
1523
+ coreUtilsVersion: "3.3.0",
1524
+ userUtilsVersion: "10.1.0"
1525
+ };
1526
+ function getConst(constKey, defaultVal) {
1527
+ const val = rawConsts[constKey];
1528
+ return val.match(/^#\{\{.+\}\}$/) ? defaultVal : val;
1529
+ }
1530
+ var versions = {
1531
+ /** Semver version string of the bundled library CoreUtils. */
1532
+ CoreUtils: getConst("coreUtilsVersion", "ERR:unknown"),
1533
+ /** Semver version string of UserUtils. */
1534
+ UserUtils: getConst("userUtilsVersion", "ERR:unknown")
1535
+ };
1536
+
1457
1537
  // lib/Errors.ts
1458
1538
  var PlatformError = class extends DatedError {
1459
1539
  constructor(message, options) {
@@ -1466,7 +1546,7 @@ var PlatformError = class extends DatedError {
1466
1546
  function getUnsafeWindow() {
1467
1547
  try {
1468
1548
  return unsafeWindow;
1469
- } catch {
1549
+ } catch (e) {
1470
1550
  return window;
1471
1551
  }
1472
1552
  }
@@ -1497,7 +1577,7 @@ function openInNewTab(href, background, additionalProps) {
1497
1577
  try {
1498
1578
  if (typeof window.GM === "object")
1499
1579
  GM.openInTab(href, background);
1500
- } catch {
1580
+ } catch (e) {
1501
1581
  const openElem = document.createElement("a");
1502
1582
  Object.assign(openElem, {
1503
1583
  className: "userutils-open-in-new-tab",
@@ -1517,7 +1597,7 @@ function openInNewTab(href, background, additionalProps) {
1517
1597
  setTimeout(() => {
1518
1598
  try {
1519
1599
  openElem.remove();
1520
- } catch {
1600
+ } catch (e2) {
1521
1601
  }
1522
1602
  }, 0);
1523
1603
  }
@@ -1533,8 +1613,8 @@ function interceptEvent(eventObject, eventName, predicate = () => true) {
1533
1613
  }
1534
1614
  (function(original) {
1535
1615
  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);
1616
+ var _a2, _b;
1617
+ const origListener = typeof args[1] === "function" ? args[1] : (_b = (_a2 = args[1]) == null ? void 0 : _a2.handleEvent) != null ? _b : (() => void 0);
1538
1618
  args[1] = function(...a) {
1539
1619
  if (args[0] === eventName && predicate(Array.isArray(a) ? a[0] : a))
1540
1620
  return;
@@ -1577,8 +1657,8 @@ function observeElementProp(element, property, callback) {
1577
1657
  }
1578
1658
  }
1579
1659
  function getSiblingsFrame(refElement, siblingAmount, refElementAlignment = "center-top", includeRef = true) {
1580
- var _a;
1581
- const siblings = [...((_a = refElement.parentNode) == null ? void 0 : _a.childNodes) ?? []];
1660
+ var _a, _b;
1661
+ const siblings = [...(_b = (_a = refElement.parentNode) == null ? void 0 : _a.childNodes) != null ? _b : []];
1582
1662
  const elemSiblIdx = siblings.indexOf(refElement);
1583
1663
  if (elemSiblIdx === -1)
1584
1664
  throw new Error("Element doesn't have a parent node");
@@ -1599,13 +1679,13 @@ function getSiblingsFrame(refElement, siblingAmount, refElementAlignment = "cent
1599
1679
  }
1600
1680
  var ttPolicy;
1601
1681
  function setInnerHtmlUnsafe(element, html) {
1602
- var _a, _b;
1682
+ var _a, _b, _c;
1603
1683
  if (!ttPolicy && typeof ((_a = window == null ? void 0 : window.trustedTypes) == null ? void 0 : _a.createPolicy) === "function") {
1604
1684
  ttPolicy = window.trustedTypes.createPolicy("_uu_set_innerhtml_unsafe", {
1605
1685
  createHTML: (unsafeHtml) => unsafeHtml
1606
1686
  });
1607
1687
  }
1608
- element.innerHTML = ((_b = ttPolicy == null ? void 0 : ttPolicy.createHTML) == null ? void 0 : _b.call(ttPolicy, html)) ?? html;
1688
+ element.innerHTML = (_c = (_b = ttPolicy == null ? void 0 : ttPolicy.createHTML) == null ? void 0 : _b.call(ttPolicy, html)) != null ? _c : html;
1609
1689
  return element;
1610
1690
  }
1611
1691
  function probeElementStyle(probeStyle, element, hideOffscreen = true, parentElement = document.body) {
@@ -1798,20 +1878,20 @@ var defaultStrings = {
1798
1878
  closeDialogTooltip: "Click to close the dialog"
1799
1879
  };
1800
1880
  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
1881
  constructor(options) {
1810
1882
  super();
1883
+ /** Options passed to the dialog in the constructor */
1884
+ __publicField(this, "options");
1885
+ /** ID that gets added to child element IDs - has to be unique and conform to HTML ID naming rules! */
1886
+ __publicField(this, "id");
1887
+ /** Strings used in the dialog (used for translations) */
1888
+ __publicField(this, "strings");
1889
+ __publicField(this, "dialogOpen", false);
1890
+ __publicField(this, "dialogMounted", false);
1811
1891
  const { strings, ...opts } = options;
1812
1892
  this.strings = {
1813
1893
  ...defaultStrings,
1814
- ...strings ?? {}
1894
+ ...strings != null ? strings : {}
1815
1895
  };
1816
1896
  this.options = {
1817
1897
  closeOnBgClick: true,
@@ -1828,11 +1908,12 @@ var Dialog = class _Dialog extends NanoEmitter {
1828
1908
  //#region public
1829
1909
  /** Call after DOMContentLoaded to pre-render the dialog and invisibly mount it in the DOM */
1830
1910
  async mount() {
1911
+ var _a;
1831
1912
  if (this.dialogMounted)
1832
1913
  return;
1833
1914
  this.dialogMounted = true;
1834
1915
  if (!document.querySelector("style.uu-dialog-css"))
1835
- addGlobalStyle(this.options.dialogCss ?? defaultDialogCss).classList.add("uu-dialog-css");
1916
+ addGlobalStyle((_a = this.options.dialogCss) != null ? _a : defaultDialogCss).classList.add("uu-dialog-css");
1836
1917
  const bgElem = document.createElement("div");
1837
1918
  bgElem.id = `uu-${this.id}-dialog-bg`;
1838
1919
  bgElem.classList.add("uu-dialog-bg");
@@ -1900,7 +1981,7 @@ var Dialog = class _Dialog extends NanoEmitter {
1900
1981
  }
1901
1982
  /** Closes the dialog - prevents default action and immediate propagation of the passed event */
1902
1983
  close(e) {
1903
- var _a;
1984
+ var _a, _b;
1904
1985
  e == null ? void 0 : e.preventDefault();
1905
1986
  e == null ? void 0 : e.stopImmediatePropagation();
1906
1987
  if (!this.isOpen())
@@ -1913,9 +1994,9 @@ var Dialog = class _Dialog extends NanoEmitter {
1913
1994
  dialogBg.style.display = "none";
1914
1995
  dialogBg.inert = true;
1915
1996
  openDialogs.splice(openDialogs.indexOf(this.id), 1);
1916
- currentDialogId = openDialogs[0] ?? null;
1997
+ currentDialogId = (_a = openDialogs[0]) != null ? _a : null;
1917
1998
  if (currentDialogId)
1918
- (_a = document.querySelector(`#uu-${currentDialogId}-dialog-bg`)) == null ? void 0 : _a.removeAttribute("inert");
1999
+ (_b = document.querySelector(`#uu-${currentDialogId}-dialog-bg`)) == null ? void 0 : _b.removeAttribute("inert");
1919
2000
  if (openDialogs.length === 0) {
1920
2001
  document.body.classList.add("uu-no-select");
1921
2002
  document.body.removeAttribute("inert");
@@ -1951,7 +2032,8 @@ var Dialog = class _Dialog extends NanoEmitter {
1951
2032
  }
1952
2033
  //#region protected
1953
2034
  getString(key) {
1954
- return this.strings[key] ?? defaultStrings[key];
2035
+ var _a;
2036
+ return (_a = this.strings[key]) != null ? _a : defaultStrings[key];
1955
2037
  }
1956
2038
  /** Called once to attach all generic event listeners */
1957
2039
  attachListeners(bgElem) {
@@ -1976,7 +2058,7 @@ var Dialog = class _Dialog extends NanoEmitter {
1976
2058
  * @param listenerOptions Provide a {@linkcode listenerOptions} object to configure the listeners
1977
2059
  */
1978
2060
  onInteraction(elem, listener, listenerOptions) {
1979
- const { preventDefault = true, stopPropagation = true, ...listenerOpts } = listenerOptions ?? {};
2061
+ const { preventDefault = true, stopPropagation = true, ...listenerOpts } = listenerOptions != null ? listenerOptions : {};
1980
2062
  const interactionKeys = ["Enter", " ", "Space"];
1981
2063
  const proxListener = (e) => {
1982
2064
  if (e instanceof KeyboardEvent) {
@@ -2056,7 +2138,6 @@ var Dialog = class _Dialog extends NanoEmitter {
2056
2138
 
2057
2139
  // lib/GMStorageEngine.ts
2058
2140
  var GMStorageEngine = class extends DataStoreEngine {
2059
- options;
2060
2141
  /**
2061
2142
  * Creates an instance of `GMStorageEngine`.
2062
2143
  *
@@ -2065,6 +2146,7 @@ var GMStorageEngine = class extends DataStoreEngine {
2065
2146
  */
2066
2147
  constructor(options) {
2067
2148
  super(options == null ? void 0 : options.dataStoreOptions);
2149
+ __publicField(this, "options");
2068
2150
  this.options = {
2069
2151
  ...options
2070
2152
  };
@@ -2120,25 +2202,26 @@ var GMStorageEngine = class extends DataStoreEngine {
2120
2202
 
2121
2203
  // lib/Mixins.ts
2122
2204
  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
2205
  /**
2132
2206
  * Creates a new Mixins instance.
2133
2207
  * @param config Configuration object to customize the behavior.
2134
2208
  */
2135
2209
  constructor(config = {}) {
2210
+ /** List of all registered mixins */
2211
+ __publicField(this, "mixins", []);
2212
+ /** Default configuration object for mixins */
2213
+ __publicField(this, "defaultMixinCfg");
2214
+ /** Whether the priorities should auto-increment if not specified */
2215
+ __publicField(this, "autoIncPrioEnabled");
2216
+ /** The current auto-increment priority counter */
2217
+ __publicField(this, "autoIncPrioCounter", /* @__PURE__ */ new Map());
2218
+ var _a, _b, _c;
2136
2219
  this.defaultMixinCfg = pureObj({
2137
- priority: config.defaultPriority ?? 0,
2138
- stopPropagation: config.defaultStopPropagation ?? false,
2220
+ priority: (_a = config.defaultPriority) != null ? _a : 0,
2221
+ stopPropagation: (_b = config.defaultStopPropagation) != null ? _b : false,
2139
2222
  signal: config.defaultSignal
2140
2223
  });
2141
- this.autoIncPrioEnabled = config.autoIncrementPriority ?? false;
2224
+ this.autoIncPrioEnabled = (_c = config.autoIncrementPriority) != null ? _c : false;
2142
2225
  }
2143
2226
  //#region public
2144
2227
  /**
@@ -2203,10 +2286,11 @@ var Mixins = class {
2203
2286
  //#region protected
2204
2287
  /** Calculates the priority for a mixin based on the given configuration and the current auto-increment state of the instance */
2205
2288
  calcPriority(mixinKey, config) {
2289
+ var _a;
2206
2290
  if (config.priority !== void 0)
2207
2291
  return void 0;
2208
2292
  if (!this.autoIncPrioEnabled)
2209
- return config.priority ?? this.defaultMixinCfg.priority;
2293
+ return (_a = config.priority) != null ? _a : this.defaultMixinCfg.priority;
2210
2294
  if (!this.autoIncPrioCounter.has(mixinKey))
2211
2295
  this.autoIncPrioCounter.set(mixinKey, this.defaultMixinCfg.priority);
2212
2296
  let prio = this.autoIncPrioCounter.get(mixinKey);
@@ -2227,13 +2311,13 @@ var Mixins = class {
2227
2311
 
2228
2312
  // lib/SelectorObserver.ts
2229
2313
  var SelectorObserver = class {
2230
- enabled = false;
2231
- baseElement;
2232
- observer;
2233
- observerOptions;
2234
- customOptions;
2235
- listenerMap;
2236
2314
  constructor(baseElement, options = {}) {
2315
+ __publicField(this, "enabled", false);
2316
+ __publicField(this, "baseElement");
2317
+ __publicField(this, "observer");
2318
+ __publicField(this, "observerOptions");
2319
+ __publicField(this, "customOptions");
2320
+ __publicField(this, "listenerMap");
2237
2321
  this.baseElement = baseElement;
2238
2322
  this.listenerMap = /* @__PURE__ */ new Map();
2239
2323
  const {
@@ -2249,10 +2333,10 @@ var SelectorObserver = class {
2249
2333
  ...observerOptions
2250
2334
  };
2251
2335
  this.customOptions = {
2252
- defaultDebounce: defaultDebounce ?? 0,
2253
- defaultDebounceType: defaultDebounceType ?? "immediate",
2254
- disableOnNoListeners: disableOnNoListeners ?? false,
2255
- enableOnAddListener: enableOnAddListener ?? true
2336
+ defaultDebounce: defaultDebounce != null ? defaultDebounce : 0,
2337
+ defaultDebounceType: defaultDebounceType != null ? defaultDebounceType : "immediate",
2338
+ disableOnNoListeners: disableOnNoListeners != null ? disableOnNoListeners : false,
2339
+ enableOnAddListener: enableOnAddListener != null ? enableOnAddListener : true
2256
2340
  };
2257
2341
  if (typeof this.customOptions.checkInterval !== "number") {
2258
2342
  this.observer = new MutationObserver(() => this.checkAllSelectors());
@@ -2403,9 +2487,9 @@ var valTransforms = [];
2403
2487
  var fallbackLang;
2404
2488
  function translate(language, key, ...trArgs) {
2405
2489
  if (typeof language !== "string")
2406
- language = fallbackLang ?? "";
2490
+ language = fallbackLang != null ? fallbackLang : "";
2407
2491
  const trObj = trans[language];
2408
- if (typeof language !== "string" || language.length === 0 || typeof trObj !== "object" || trObj === null)
2492
+ if (typeof language !== "string" || typeof trObj !== "object" || trObj === null)
2409
2493
  return fallbackLang && language !== fallbackLang ? translate(fallbackLang, key, ...trArgs) : key;
2410
2494
  const transformTrVal = (trKey, trValue) => {
2411
2495
  const tfs = valTransforms.filter(({ regex }) => new RegExp(regex).test(String(trValue)));
@@ -2457,15 +2541,18 @@ function trFor(language, key, ...args) {
2457
2541
  function useTr(language) {
2458
2542
  return (key, ...args) => translate(language, key, ...args);
2459
2543
  }
2460
- function hasKey(language = fallbackLang ?? "", key) {
2544
+ function hasKey(language = fallbackLang != null ? fallbackLang : "", key) {
2461
2545
  return tr.for(language, key) !== key;
2462
2546
  }
2463
2547
  function addTranslations(language, translations) {
2464
2548
  trans[language] = JSON.parse(JSON.stringify(translations));
2465
2549
  }
2466
- function getTranslations(language = fallbackLang ?? "") {
2550
+ function getTranslations(language = fallbackLang != null ? fallbackLang : "") {
2467
2551
  return trans[language];
2468
2552
  }
2553
+ function getAllTranslations(asCopy = true) {
2554
+ return asCopy ? JSON.parse(JSON.stringify(trans)) : trans;
2555
+ }
2469
2556
  var deleteTranslations = (language) => {
2470
2557
  if (language in trans) {
2471
2558
  delete trans[language];
@@ -2496,51 +2583,55 @@ function deleteTransform(patternOrFn) {
2496
2583
  }
2497
2584
  return false;
2498
2585
  }
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;
2586
+ var commonKeyedTransform = ({ matches, trArgs, trValue }, patternRegex, quickMatchPattern) => {
2587
+ let str = String(trValue);
2588
+ const someMatchKeyInArgs = (obj) => matches.some((match) => match[1] !== void 0 && match[1] in obj);
2589
+ const namedMapping = () => {
2590
+ if (!str.includes(quickMatchPattern) || typeof trArgs[0] === "undefined" || typeof trArgs[0] !== "object" || !someMatchKeyInArgs(trArgs[0]))
2591
+ return;
2592
+ for (const match of matches) {
2593
+ const repl = match[1] !== void 0 ? trArgs[0][match[1]] : void 0;
2594
+ if (typeof repl !== "undefined")
2595
+ str = str.replace(match[0], String(repl));
2529
2596
  }
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;
2597
+ };
2598
+ const positionalMapping = () => {
2599
+ if (!patternRegex.test(str) || typeof trArgs[0] === "undefined")
2600
+ return;
2601
+ let matchNum = -1;
2602
+ for (const match of matches) {
2603
+ matchNum++;
2604
+ if (typeof trArgs[matchNum] !== "undefined")
2605
+ str = str.replace(match[0], String(trArgs[matchNum]));
2606
+ }
2607
+ };
2608
+ let notStringifiable = false;
2609
+ try {
2610
+ void `${trArgs[0]}`;
2611
+ } catch (e) {
2612
+ notStringifiable = true;
2536
2613
  }
2614
+ const isArgsObject = trArgs[0] && typeof trArgs[0] === "object" && trArgs[0] !== null && (notStringifiable || String(trArgs[0]).startsWith("[object"));
2615
+ if (isArgsObject && someMatchKeyInArgs(trArgs[0]))
2616
+ namedMapping();
2617
+ else
2618
+ positionalMapping();
2619
+ return str;
2620
+ };
2621
+ var templateLiteralTransform = [
2622
+ /\$\{([a-zA-Z0-9$_-]+)\}/gm,
2623
+ (tfProps) => commonKeyedTransform(tfProps, /\$\{.+\}/m, "${")
2624
+ ];
2625
+ var i18nTransform = [
2626
+ /\{\{([a-zA-Z0-9$_-]+)\}\}/gm,
2627
+ (tfProps) => commonKeyedTransform(tfProps, /\{\{.+\}\}/m, "{{")
2537
2628
  ];
2538
2629
  var percentTransform = [
2539
2630
  /%(\d+)/gm,
2540
2631
  ({ matches, trArgs, trValue }) => {
2541
2632
  let str = String(trValue);
2542
2633
  for (const match of matches) {
2543
- const repl = match[1] !== void 0 ? trArgs == null ? void 0 : trArgs[Number(match[1]) - 1] : void 0;
2634
+ const repl = trArgs == null ? void 0 : trArgs[Number(match[1]) - 1];
2544
2635
  if (typeof repl !== "undefined")
2545
2636
  str = str.replace(match[0], String(repl));
2546
2637
  }
@@ -2550,17 +2641,84 @@ var percentTransform = [
2550
2641
  var tr = {
2551
2642
  for: (...params) => trFor(...params),
2552
2643
  use: (...params) => useTr(...params),
2553
- hasKey: (language = fallbackLang ?? "", key) => hasKey(language, key),
2644
+ hasKey: (language = fallbackLang != null ? fallbackLang : "", key) => hasKey(language, key),
2554
2645
  addTranslations,
2555
2646
  getTranslations,
2647
+ getAllTranslations,
2556
2648
  deleteTranslations,
2557
2649
  setFallbackLanguage,
2558
2650
  getFallbackLanguage,
2559
2651
  addTransform,
2560
2652
  deleteTransform,
2653
+ /** Collection of predefined transform functions that can be added via {@linkcode tr.addTransform()} */
2561
2654
  transforms: {
2655
+ /**
2656
+ * This transform will replace placeholders matching `${key}` with the value of the passed argument(s).
2657
+ * The arguments can be passed in keyed object form or positionally via the spread operator:
2658
+ * - Keyed: If the first argument is an object and `key` is found in it, the value will be used for the replacement.
2659
+ * - 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.
2660
+ *
2661
+ * @example ```ts
2662
+ * tr.addTranslations("en", {
2663
+ * "greeting": "Hello, ${user}!\nYou have ${notifs} notifications.",
2664
+ * });
2665
+ * tr.addTransform(tr.transforms.templateLiteral);
2666
+ *
2667
+ * const t = tr.use("en");
2668
+ *
2669
+ * // both calls return the same result:
2670
+ * t("greeting", { user: "John", notifs: 5 }); // "Hello, John!\nYou have 5 notifications."
2671
+ * t("greeting", "John", 5); // "Hello, John!\nYou have 5 notifications."
2672
+ *
2673
+ * // when a key isn't found in the object, it will be left as-is:
2674
+ * t("greeting", { user: "John" }); // "Hello, John!\nYou have ${notifs} notifications."
2675
+ * ```
2676
+ */
2562
2677
  templateLiteral: templateLiteralTransform,
2678
+ /**
2679
+ * This transform will replace placeholders matching `{{key}}` with the value of the passed argument(s).
2680
+ * This format is commonly used in i18n libraries. Note that advanced syntax is not supported, only simple key replacement.
2681
+ * The arguments can be passed in keyed object form or positionally via the spread operator:
2682
+ * - Keyed: If the first argument is an object and `key` is found in it, the value will be used for the replacement.
2683
+ * - 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.
2684
+ *
2685
+ * @example ```ts
2686
+ * tr.addTranslations("en", {
2687
+ * "greeting": "Hello, {{user}}!\nYou have {{notifs}} notifications.",
2688
+ * });
2689
+ * tr.addTransform(tr.transforms.i18n);
2690
+ *
2691
+ * const t = tr.use("en");
2692
+ *
2693
+ * // both calls return the same result:
2694
+ * t("greeting", { user: "Alice", notifs: 5 }); // "Hello, Alice!\nYou have 5 notifications."
2695
+ * t("greeting", "Alice", 5); // "Hello, Alice!\nYou have 5 notifications."
2696
+ *
2697
+ * // when a key isn't found in the object, it will be left as-is:
2698
+ * t("greeting", { user: "Alice" }); // "Hello, Alice!\nYou have {{notifs}} notifications."
2699
+ * ```
2700
+ */
2701
+ i18n: i18nTransform,
2702
+ /**
2703
+ * This transform will replace `%n` placeholders with the value of the passed arguments.
2704
+ * 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.
2705
+ * Objects will be stringified via `String()` before being inserted.
2706
+ *
2707
+ * @example ```ts
2708
+ * tr.addTranslations("en", {
2709
+ * "greeting": "Hello, %1!\nYou have %2 notifications.",
2710
+ * });
2711
+ * tr.addTransform(tr.transforms.percent);
2712
+ *
2713
+ * const t = tr.use("en");
2714
+ *
2715
+ * // arguments are inserted in the order they're passed:
2716
+ * t("greeting", "Bob", 5); // "Hello, Bob!\nYou have 5 notifications."
2717
+ *
2718
+ * // when a value isn't found, the placeholder will be left as-is:
2719
+ * t("greeting", "Bob"); // "Hello, Bob!\nYou have %2 notifications."
2720
+ * ```
2721
+ */
2563
2722
  percent: percentTransform
2564
2723
  }
2565
2724
  };
2566
- //# sourceMappingURL=UserUtils.cjs.map