@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.
@@ -179,11 +179,12 @@ __export(lib_exports, {
179
179
  takeRandomItemIndex: () => takeRandomItemIndex,
180
180
  tr: () => tr,
181
181
  truncStr: () => truncStr,
182
- valsWithin: () => valsWithin
182
+ valsWithin: () => valsWithin,
183
+ versions: () => versions
183
184
  });
184
185
  module.exports = __toCommonJS(lib_exports);
185
186
 
186
- // node_modules/.pnpm/@sv443-network+coreutils@3.0.4/node_modules/@sv443-network/coreutils/dist/CoreUtils.mjs
187
+ // node_modules/.pnpm/@sv443-network+coreutils@3.3.0/node_modules/@sv443-network/coreutils/dist/CoreUtils.mjs
187
188
  function bitSetHas(bitSet, checkVal) {
188
189
  return (bitSet & checkVal) === checkVal;
189
190
  }
@@ -649,8 +650,202 @@ function truncStr(input, length, endStr = "...") {
649
650
  const finalStr = str.length > length ? str.substring(0, length - endStr.length) + endStr : str;
650
651
  return finalStr.length > length ? finalStr.substring(0, length) : finalStr;
651
652
  }
653
+ var createNanoEvents = () => ({
654
+ emit(event, ...args) {
655
+ for (let callbacks = this.events[event] || [], i = 0, length = callbacks.length; i < length; i++) {
656
+ callbacks[i](...args);
657
+ }
658
+ },
659
+ events: {},
660
+ on(event, cb) {
661
+ var _a;
662
+ ;
663
+ ((_a = this.events)[event] || (_a[event] = [])).push(cb);
664
+ return () => {
665
+ var _a2;
666
+ this.events[event] = (_a2 = this.events[event]) == null ? void 0 : _a2.filter((i) => cb !== i);
667
+ };
668
+ }
669
+ });
670
+ var NanoEmitter = class {
671
+ /** Creates a new instance of NanoEmitter - a lightweight event emitter with helper methods and a strongly typed event map */
672
+ constructor(options = {}) {
673
+ __publicField(this, "events", createNanoEvents());
674
+ __publicField(this, "eventUnsubscribes", []);
675
+ __publicField(this, "emitterOptions");
676
+ this.emitterOptions = __spreadValues({
677
+ publicEmit: false
678
+ }, options);
679
+ }
680
+ //#region on
681
+ /**
682
+ * Subscribes to an event and calls the callback when it's emitted.
683
+ * @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 "_")
684
+ * @returns Returns a function that can be called to unsubscribe the event listener
685
+ * @example ```ts
686
+ * const emitter = new NanoEmitter<{
687
+ * foo: (bar: string) => void;
688
+ * }>({
689
+ * publicEmit: true,
690
+ * });
691
+ *
692
+ * let i = 0;
693
+ * const unsub = emitter.on("foo", (bar) => {
694
+ * // unsubscribe after 10 events:
695
+ * if(++i === 10) unsub();
696
+ * console.log(bar);
697
+ * });
698
+ *
699
+ * emitter.emit("foo", "bar");
700
+ * ```
701
+ */
702
+ on(event, cb) {
703
+ let unsub;
704
+ const unsubProxy = () => {
705
+ if (!unsub)
706
+ return;
707
+ unsub();
708
+ this.eventUnsubscribes = this.eventUnsubscribes.filter((u) => u !== unsub);
709
+ };
710
+ unsub = this.events.on(event, cb);
711
+ this.eventUnsubscribes.push(unsub);
712
+ return unsubProxy;
713
+ }
714
+ //#region once
715
+ /**
716
+ * Subscribes to an event and calls the callback or resolves the Promise only once when it's emitted.
717
+ * @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 "_")
718
+ * @param cb The callback to call when the event is emitted - if provided or not, the returned Promise will resolve with the event arguments
719
+ * @returns Returns a Promise that resolves with the event arguments when the event is emitted
720
+ * @example ```ts
721
+ * const emitter = new NanoEmitter<{
722
+ * foo: (bar: string) => void;
723
+ * }>();
724
+ *
725
+ * // Promise syntax:
726
+ * const [bar] = await emitter.once("foo");
727
+ * console.log(bar);
728
+ *
729
+ * // Callback syntax:
730
+ * emitter.once("foo", (bar) => console.log(bar));
731
+ * ```
732
+ */
733
+ once(event, cb) {
734
+ return new Promise((resolve) => {
735
+ let unsub;
736
+ const onceProxy = ((...args) => {
737
+ cb == null ? void 0 : cb(...args);
738
+ unsub == null ? void 0 : unsub();
739
+ resolve(args);
740
+ });
741
+ unsub = this.events.on(event, onceProxy);
742
+ this.eventUnsubscribes.push(unsub);
743
+ });
744
+ }
745
+ //#region onMulti
746
+ /**
747
+ * 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.
748
+ * @param options An object or array of objects with the following properties:
749
+ * `callback` (required) is the function that will be called when the conditions are met.
750
+ *
751
+ * 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.
752
+ * If `signal` is provided, the subscription will be canceled when the given signal is aborted.
753
+ *
754
+ * If `oneOf` is used, the callback will be called when any of the matching events are emitted.
755
+ * 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.
756
+ * 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.
757
+ * At least one of `oneOf` or `allOf` must be provided.
758
+ *
759
+ * @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.
760
+ */
761
+ onMulti(options) {
762
+ const allUnsubs = [];
763
+ const unsubAll = () => {
764
+ for (const unsub of allUnsubs)
765
+ unsub();
766
+ allUnsubs.splice(0, allUnsubs.length);
767
+ this.eventUnsubscribes = this.eventUnsubscribes.filter((u) => !allUnsubs.includes(u));
768
+ };
769
+ for (const opts of Array.isArray(options) ? options : [options]) {
770
+ const optsWithDefaults = __spreadValues({
771
+ allOf: [],
772
+ oneOf: [],
773
+ once: false
774
+ }, opts);
775
+ const {
776
+ oneOf,
777
+ allOf,
778
+ once,
779
+ signal,
780
+ callback
781
+ } = optsWithDefaults;
782
+ if (signal == null ? void 0 : signal.aborted)
783
+ return unsubAll;
784
+ if (oneOf.length === 0 && allOf.length === 0)
785
+ throw new TypeError("NanoEmitter.onMulti(): Either `oneOf` or `allOf` or both must be provided in the options");
786
+ const curEvtUnsubs = [];
787
+ const checkUnsubAllEvt = (force = false) => {
788
+ if (!(signal == null ? void 0 : signal.aborted) && !force)
789
+ return;
790
+ for (const unsub of curEvtUnsubs)
791
+ unsub();
792
+ curEvtUnsubs.splice(0, curEvtUnsubs.length);
793
+ this.eventUnsubscribes = this.eventUnsubscribes.filter((u) => !curEvtUnsubs.includes(u));
794
+ };
795
+ const allOfEmitted = /* @__PURE__ */ new Set();
796
+ const allOfConditionMet = () => allOf.length === 0 || allOfEmitted.size === allOf.length;
797
+ for (const event of oneOf) {
798
+ const unsub = this.events.on(event, ((...args) => {
799
+ checkUnsubAllEvt();
800
+ if (allOfConditionMet()) {
801
+ callback(event, ...args);
802
+ if (once)
803
+ checkUnsubAllEvt(true);
804
+ }
805
+ }));
806
+ curEvtUnsubs.push(unsub);
807
+ }
808
+ for (const event of allOf) {
809
+ const unsub = this.events.on(event, ((...args) => {
810
+ checkUnsubAllEvt();
811
+ allOfEmitted.add(event);
812
+ if (allOfConditionMet() && (oneOf.length === 0 || oneOf.includes(event))) {
813
+ callback(event, ...args);
814
+ if (once)
815
+ checkUnsubAllEvt(true);
816
+ }
817
+ }));
818
+ curEvtUnsubs.push(unsub);
819
+ }
820
+ allUnsubs.push(() => checkUnsubAllEvt(true));
821
+ }
822
+ return unsubAll;
823
+ }
824
+ //#region emit
825
+ /**
826
+ * Emits an event on this instance.
827
+ * - ⚠️ Needs `publicEmit` to be set to true in the NanoEmitter constructor or super() call!
828
+ * @param event The event to emit
829
+ * @param args The arguments to pass to the event listeners
830
+ * @returns Returns true if `publicEmit` is true and the event was emitted successfully
831
+ */
832
+ emit(event, ...args) {
833
+ if (this.emitterOptions.publicEmit) {
834
+ this.events.emit(event, ...args);
835
+ return true;
836
+ }
837
+ return false;
838
+ }
839
+ //#region unsubscribeAll
840
+ /** Unsubscribes all event listeners from this instance */
841
+ unsubscribeAll() {
842
+ for (const unsub of this.eventUnsubscribes)
843
+ unsub();
844
+ this.eventUnsubscribes = [];
845
+ }
846
+ };
652
847
  var dsFmtVer = 1;
653
- var DataStore = class {
848
+ var DataStore = class extends NanoEmitter {
654
849
  //#region constructor
655
850
  /**
656
851
  * Creates an instance of DataStore to manage a sync & async database that is cached in memory and persistently saved across sessions.
@@ -662,14 +857,17 @@ var DataStore = class {
662
857
  * @param opts The options for this DataStore instance
663
858
  */
664
859
  constructor(opts) {
860
+ var _a, _b, _c;
861
+ super(opts.nanoEmitterOptions);
665
862
  __publicField(this, "id");
666
863
  __publicField(this, "formatVersion");
667
864
  __publicField(this, "defaultData");
668
865
  __publicField(this, "encodeData");
669
866
  __publicField(this, "decodeData");
670
867
  __publicField(this, "compressionFormat", "deflate-raw");
671
- __publicField(this, "memoryCache", true);
868
+ __publicField(this, "memoryCache");
672
869
  __publicField(this, "engine");
870
+ __publicField(this, "keyPrefix");
673
871
  __publicField(this, "options");
674
872
  /**
675
873
  * Whether all first-init checks should be done.
@@ -681,21 +879,21 @@ var DataStore = class {
681
879
  __publicField(this, "cachedData");
682
880
  __publicField(this, "migrations");
683
881
  __publicField(this, "migrateIds", []);
684
- var _a, _b;
685
882
  this.id = opts.id;
686
883
  this.formatVersion = opts.formatVersion;
687
884
  this.defaultData = opts.defaultData;
688
- this.memoryCache = Boolean((_a = opts.memoryCache) != null ? _a : true);
885
+ this.memoryCache = (_a = opts.memoryCache) != null ? _a : true;
689
886
  this.cachedData = this.memoryCache ? opts.defaultData : {};
690
887
  this.migrations = opts.migrations;
691
888
  if (opts.migrateIds)
692
889
  this.migrateIds = Array.isArray(opts.migrateIds) ? opts.migrateIds : [opts.migrateIds];
693
890
  this.engine = typeof opts.engine === "function" ? opts.engine() : opts.engine;
891
+ this.keyPrefix = (_b = opts.keyPrefix) != null ? _b : "__ds-";
694
892
  this.options = opts;
695
893
  if ("encodeData" in opts && "decodeData" in opts && Array.isArray(opts.encodeData) && Array.isArray(opts.decodeData)) {
696
894
  this.encodeData = [opts.encodeData[0], opts.encodeData[1]];
697
895
  this.decodeData = [opts.decodeData[0], opts.decodeData[1]];
698
- this.compressionFormat = (_b = opts.encodeData[0]) != null ? _b : null;
896
+ this.compressionFormat = (_c = opts.encodeData[0]) != null ? _c : null;
699
897
  } else if (opts.compressionFormat === null) {
700
898
  this.encodeData = void 0;
701
899
  this.decodeData = void 0;
@@ -738,13 +936,13 @@ var DataStore = class {
738
936
  promises.push(this.engine.setValue(newKey, value));
739
937
  promises.push(this.engine.deleteValue(oldKey));
740
938
  };
741
- migrateFmt(`_uucfg-${this.id}`, `__ds-${this.id}-dat`, oldData);
939
+ migrateFmt(`_uucfg-${this.id}`, `${this.keyPrefix}${this.id}-dat`, oldData);
742
940
  if (!isNaN(oldVer))
743
- migrateFmt(`_uucfgver-${this.id}`, `__ds-${this.id}-ver`, oldVer);
941
+ migrateFmt(`_uucfgver-${this.id}`, `${this.keyPrefix}${this.id}-ver`, oldVer);
744
942
  if (typeof oldEnc === "boolean" || oldEnc === "true" || oldEnc === "false" || typeof oldEnc === "number" || oldEnc === "0" || oldEnc === "1")
745
- migrateFmt(`_uucfgenc-${this.id}`, `__ds-${this.id}-enf`, [0, "0", true, "true"].includes(oldEnc) ? (_a = this.compressionFormat) != null ? _a : null : null);
943
+ migrateFmt(`_uucfgenc-${this.id}`, `${this.keyPrefix}${this.id}-enf`, [0, "0", true, "true"].includes(oldEnc) ? (_a = this.compressionFormat) != null ? _a : null : null);
746
944
  else {
747
- promises.push(this.engine.setValue(`__ds-${this.id}-enf`, this.compressionFormat));
945
+ promises.push(this.engine.setValue(`${this.keyPrefix}${this.id}-enf`, this.compressionFormat));
748
946
  promises.push(this.engine.deleteValue(`_uucfgenc-${this.id}`));
749
947
  }
750
948
  yield Promise.allSettled(promises);
@@ -756,31 +954,27 @@ var DataStore = class {
756
954
  yield this.migrateId(this.migrateIds);
757
955
  this.migrateIds = [];
758
956
  }
759
- const storedDataRaw = yield this.engine.getValue(`__ds-${this.id}-dat`, null);
760
- let storedFmtVer = Number(yield this.engine.getValue(`__ds-${this.id}-ver`, NaN));
761
- if (typeof storedDataRaw !== "string") {
762
- yield this.saveDefaultData();
763
- return this.engine.deepCopy(this.defaultData);
957
+ const storedDataRaw = yield this.engine.getValue(`${this.keyPrefix}${this.id}-dat`, null);
958
+ const storedFmtVer = Number(yield this.engine.getValue(`${this.keyPrefix}${this.id}-ver`, NaN));
959
+ if (typeof storedDataRaw !== "string" && typeof storedDataRaw !== "object" || storedDataRaw === null || isNaN(storedFmtVer)) {
960
+ yield this.saveDefaultData(false);
961
+ const data = this.engine.deepCopy(this.defaultData);
962
+ this.events.emit("loadData", data);
963
+ return data;
764
964
  }
765
965
  const storedData = storedDataRaw != null ? storedDataRaw : JSON.stringify(this.defaultData);
766
- const encodingFmt = String(yield this.engine.getValue(`__ds-${this.id}-enf`, null));
966
+ const encodingFmt = String(yield this.engine.getValue(`${this.keyPrefix}${this.id}-enf`, null));
767
967
  const isEncoded = encodingFmt !== "null" && encodingFmt !== "false" && encodingFmt !== "0" && encodingFmt !== "" && encodingFmt !== null;
768
- let saveData = false;
769
- if (isNaN(storedFmtVer)) {
770
- yield this.engine.setValue(`__ds-${this.id}-ver`, storedFmtVer = this.formatVersion);
771
- saveData = true;
772
- }
773
- let parsed = yield this.engine.deserializeData(storedData, isEncoded);
968
+ let parsed = typeof storedData === "string" ? yield this.engine.deserializeData(storedData, isEncoded) : storedData;
774
969
  if (storedFmtVer < this.formatVersion && this.migrations)
775
970
  parsed = yield this.runMigrations(parsed, storedFmtVer);
776
- if (saveData)
777
- yield this.setData(parsed);
778
- if (this.memoryCache)
779
- return this.cachedData = this.engine.deepCopy(parsed);
780
- else
781
- return this.engine.deepCopy(parsed);
971
+ const result = this.memoryCache ? this.cachedData = this.engine.deepCopy(parsed) : this.engine.deepCopy(parsed);
972
+ this.events.emit("loadData", result);
973
+ return result;
782
974
  } catch (err) {
975
+ const error = err instanceof Error ? err : new Error(String(err));
783
976
  console.warn("Error while parsing JSON data, resetting it to the default value.", err);
977
+ this.events.emit("error", error);
784
978
  yield this.saveDefaultData();
785
979
  return this.defaultData;
786
980
  }
@@ -790,7 +984,7 @@ var DataStore = class {
790
984
  /**
791
985
  * Returns a copy of the data from the in-memory cache.
792
986
  * Use {@linkcode loadData()} to get fresh data from persistent storage (usually not necessary since the cache should always exactly reflect persistent storage).
793
- * ⚠️ If `memoryCache` was set to `false` in the constructor options, this method will throw an error.
987
+ * ⚠️ Only available when `memoryCache` is `true` (default). When set to `false`, this produces a type and runtime error - use {@linkcode loadData()} instead.
794
988
  */
795
989
  getData() {
796
990
  if (!this.memoryCache)
@@ -800,28 +994,48 @@ var DataStore = class {
800
994
  //#region setData
801
995
  /** Saves the data synchronously to the in-memory cache and asynchronously to the persistent storage */
802
996
  setData(data) {
803
- if (this.memoryCache)
997
+ const dataCopy = this.engine.deepCopy(data);
998
+ if (this.memoryCache) {
804
999
  this.cachedData = data;
1000
+ this.events.emit("updateDataSync", dataCopy);
1001
+ }
805
1002
  return new Promise((resolve) => __async(this, null, function* () {
806
- yield Promise.allSettled([
807
- this.engine.setValue(`__ds-${this.id}-dat`, yield this.engine.serializeData(data, this.encodingEnabled())),
808
- this.engine.setValue(`__ds-${this.id}-ver`, this.formatVersion),
809
- this.engine.setValue(`__ds-${this.id}-enf`, this.compressionFormat)
1003
+ const results = yield Promise.allSettled([
1004
+ this.engine.setValue(`${this.keyPrefix}${this.id}-dat`, yield this.engine.serializeData(data, this.encodingEnabled())),
1005
+ this.engine.setValue(`${this.keyPrefix}${this.id}-ver`, this.formatVersion),
1006
+ this.engine.setValue(`${this.keyPrefix}${this.id}-enf`, this.compressionFormat)
810
1007
  ]);
1008
+ if (results.every((r) => r.status === "fulfilled"))
1009
+ this.events.emit("updateData", dataCopy);
1010
+ else {
1011
+ const error = new Error("Error while saving data to persistent storage: " + results.map((r) => r.status === "rejected" ? r.reason : null).filter(Boolean).join("; "));
1012
+ console.error(error);
1013
+ this.events.emit("error", error);
1014
+ }
811
1015
  resolve();
812
1016
  }));
813
1017
  }
814
1018
  //#region saveDefaultData
815
- /** Saves the default data passed in the constructor synchronously to the in-memory cache and asynchronously to persistent storage */
816
- saveDefaultData() {
1019
+ /**
1020
+ * Saves the default data passed in the constructor synchronously to the in-memory cache and asynchronously to persistent storage.
1021
+ * @param emitEvent Whether to emit the `setDefaultData` event - set to `false` to prevent event emission (used internally during initial population in {@linkcode loadData()})
1022
+ */
1023
+ saveDefaultData(emitEvent = true) {
817
1024
  return __async(this, null, function* () {
818
1025
  if (this.memoryCache)
819
1026
  this.cachedData = this.defaultData;
820
- yield Promise.allSettled([
821
- this.engine.setValue(`__ds-${this.id}-dat`, yield this.engine.serializeData(this.defaultData, this.encodingEnabled())),
822
- this.engine.setValue(`__ds-${this.id}-ver`, this.formatVersion),
823
- this.engine.setValue(`__ds-${this.id}-enf`, this.compressionFormat)
1027
+ const results = yield Promise.allSettled([
1028
+ this.engine.setValue(`${this.keyPrefix}${this.id}-dat`, yield this.engine.serializeData(this.defaultData, this.encodingEnabled())),
1029
+ this.engine.setValue(`${this.keyPrefix}${this.id}-ver`, this.formatVersion),
1030
+ this.engine.setValue(`${this.keyPrefix}${this.id}-enf`, this.compressionFormat)
824
1031
  ]);
1032
+ if (results.every((r) => r.status === "fulfilled"))
1033
+ emitEvent && this.events.emit("setDefaultData", this.defaultData);
1034
+ else {
1035
+ const error = new Error("Error while saving default data to persistent storage: " + results.map((r) => r.status === "rejected" ? r.reason : null).filter(Boolean).join("; "));
1036
+ console.error(error);
1037
+ this.events.emit("error", error);
1038
+ }
825
1039
  });
826
1040
  }
827
1041
  //#region deleteData
@@ -834,11 +1048,12 @@ var DataStore = class {
834
1048
  return __async(this, null, function* () {
835
1049
  var _a, _b;
836
1050
  yield Promise.allSettled([
837
- this.engine.deleteValue(`__ds-${this.id}-dat`),
838
- this.engine.deleteValue(`__ds-${this.id}-ver`),
839
- this.engine.deleteValue(`__ds-${this.id}-enf`)
1051
+ this.engine.deleteValue(`${this.keyPrefix}${this.id}-dat`),
1052
+ this.engine.deleteValue(`${this.keyPrefix}${this.id}-ver`),
1053
+ this.engine.deleteValue(`${this.keyPrefix}${this.id}-enf`)
840
1054
  ]);
841
1055
  yield (_b = (_a = this.engine).deleteStorage) == null ? void 0 : _b.call(_a);
1056
+ this.events.emit("deleteData");
842
1057
  });
843
1058
  }
844
1059
  //#region encodingEnabled
@@ -861,30 +1076,35 @@ var DataStore = class {
861
1076
  let newData = oldData;
862
1077
  const sortedMigrations = Object.entries(this.migrations).sort(([a], [b]) => Number(a) - Number(b));
863
1078
  let lastFmtVer = oldFmtVer;
864
- for (const [fmtVer, migrationFunc] of sortedMigrations) {
1079
+ for (let i = 0; i < sortedMigrations.length; i++) {
1080
+ const [fmtVer, migrationFunc] = sortedMigrations[i];
865
1081
  const ver = Number(fmtVer);
866
1082
  if (oldFmtVer < this.formatVersion && oldFmtVer < ver) {
867
1083
  try {
868
1084
  const migRes = migrationFunc(newData);
869
1085
  newData = migRes instanceof Promise ? yield migRes : migRes;
870
1086
  lastFmtVer = oldFmtVer = ver;
1087
+ const isFinal = ver >= this.formatVersion || i === sortedMigrations.length - 1;
1088
+ this.events.emit("migrateData", ver, newData, isFinal);
871
1089
  } catch (err) {
1090
+ const migError = new MigrationError(`Error while running migration function for format version '${fmtVer}'`, { cause: err });
1091
+ this.events.emit("migrationError", ver, migError);
1092
+ this.events.emit("error", migError);
872
1093
  if (!resetOnError)
873
- throw new MigrationError(`Error while running migration function for format version '${fmtVer}'`, { cause: err });
1094
+ throw migError;
874
1095
  yield this.saveDefaultData();
875
- return this.getData();
1096
+ return this.engine.deepCopy(this.defaultData);
876
1097
  }
877
1098
  }
878
1099
  }
879
1100
  yield Promise.allSettled([
880
- this.engine.setValue(`__ds-${this.id}-dat`, yield this.engine.serializeData(newData, this.encodingEnabled())),
881
- this.engine.setValue(`__ds-${this.id}-ver`, lastFmtVer),
882
- this.engine.setValue(`__ds-${this.id}-enf`, this.compressionFormat)
1101
+ this.engine.setValue(`${this.keyPrefix}${this.id}-dat`, yield this.engine.serializeData(newData, this.encodingEnabled())),
1102
+ this.engine.setValue(`${this.keyPrefix}${this.id}-ver`, lastFmtVer),
1103
+ this.engine.setValue(`${this.keyPrefix}${this.id}-enf`, this.compressionFormat)
883
1104
  ]);
884
- if (this.memoryCache)
885
- return this.cachedData = this.engine.deepCopy(newData);
886
- else
887
- return this.engine.deepCopy(newData);
1105
+ const result = this.memoryCache ? this.cachedData = this.engine.deepCopy(newData) : this.engine.deepCopy(newData);
1106
+ this.events.emit("updateData", result);
1107
+ return result;
888
1108
  });
889
1109
  }
890
1110
  //#region migrateId
@@ -898,9 +1118,9 @@ var DataStore = class {
898
1118
  yield Promise.all(ids.map((id) => __async(this, null, function* () {
899
1119
  const [data, fmtVer, isEncoded] = yield (() => __async(this, null, function* () {
900
1120
  const [d, f, e] = yield Promise.all([
901
- this.engine.getValue(`__ds-${id}-dat`, JSON.stringify(this.defaultData)),
902
- this.engine.getValue(`__ds-${id}-ver`, NaN),
903
- this.engine.getValue(`__ds-${id}-enf`, null)
1121
+ this.engine.getValue(`${this.keyPrefix}${id}-dat`, JSON.stringify(this.defaultData)),
1122
+ this.engine.getValue(`${this.keyPrefix}${id}-ver`, NaN),
1123
+ this.engine.getValue(`${this.keyPrefix}${id}-enf`, null)
904
1124
  ]);
905
1125
  return [d, Number(f), Boolean(e) && String(e) !== "null"];
906
1126
  }))();
@@ -908,13 +1128,14 @@ var DataStore = class {
908
1128
  return;
909
1129
  const parsed = yield this.engine.deserializeData(data, isEncoded);
910
1130
  yield Promise.allSettled([
911
- this.engine.setValue(`__ds-${this.id}-dat`, yield this.engine.serializeData(parsed, this.encodingEnabled())),
912
- this.engine.setValue(`__ds-${this.id}-ver`, fmtVer),
913
- this.engine.setValue(`__ds-${this.id}-enf`, this.compressionFormat),
914
- this.engine.deleteValue(`__ds-${id}-dat`),
915
- this.engine.deleteValue(`__ds-${id}-ver`),
916
- this.engine.deleteValue(`__ds-${id}-enf`)
1131
+ this.engine.setValue(`${this.keyPrefix}${this.id}-dat`, yield this.engine.serializeData(parsed, this.encodingEnabled())),
1132
+ this.engine.setValue(`${this.keyPrefix}${this.id}-ver`, fmtVer),
1133
+ this.engine.setValue(`${this.keyPrefix}${this.id}-enf`, this.compressionFormat),
1134
+ this.engine.deleteValue(`${this.keyPrefix}${id}-dat`),
1135
+ this.engine.deleteValue(`${this.keyPrefix}${id}-ver`),
1136
+ this.engine.deleteValue(`${this.keyPrefix}${id}-enf`)
917
1137
  ]);
1138
+ this.events.emit("migrateId", id, this.id);
918
1139
  })));
919
1140
  });
920
1141
  }
@@ -1046,7 +1267,7 @@ var FileStorageEngine = class extends DataStoreEngine {
1046
1267
  fs = (_a = yield import("fs/promises")) == null ? void 0 : _a.default;
1047
1268
  if (!fs)
1048
1269
  throw new ScriptContextError("FileStorageEngine requires Node.js or Deno with Node compatibility (v1.31+)", { cause: new DatedError("'node:fs/promises' module not available") });
1049
- const path = typeof this.options.filePath === "string" ? this.options.filePath : this.options.filePath(this.dataStoreOptions.id);
1270
+ const path = typeof this.options.filePath === "string" ? this.options.filePath : this.options.filePath(this.dataStoreOptions.id, this.dataStoreOptions);
1050
1271
  const data = yield fs.readFile(path, "utf-8");
1051
1272
  return data ? JSON.parse((_a2 = yield (_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;
1052
1273
  } catch (e) {
@@ -1065,7 +1286,7 @@ var FileStorageEngine = class extends DataStoreEngine {
1065
1286
  fs = (_a = yield import("fs/promises")) == null ? void 0 : _a.default;
1066
1287
  if (!fs)
1067
1288
  throw new ScriptContextError("FileStorageEngine requires Node.js or Deno with Node compatibility (v1.31+)", { cause: new DatedError("'node:fs/promises' module not available") });
1068
- const path = typeof this.options.filePath === "string" ? this.options.filePath : this.options.filePath(this.dataStoreOptions.id);
1289
+ const path = typeof this.options.filePath === "string" ? this.options.filePath : this.options.filePath(this.dataStoreOptions.id, this.dataStoreOptions);
1069
1290
  yield fs.mkdir(path.slice(0, path.lastIndexOf(path.includes("/") ? "/" : "\\")), { recursive: true });
1070
1291
  yield fs.writeFile(path, (_a2 = yield (_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");
1071
1292
  } catch (err) {
@@ -1083,8 +1304,21 @@ var FileStorageEngine = class extends DataStoreEngine {
1083
1304
  const value = data == null ? void 0 : data[name];
1084
1305
  if (typeof value === "undefined")
1085
1306
  return defaultValue;
1086
- if (typeof value === "string")
1087
- return value;
1307
+ if (typeof defaultValue === "string") {
1308
+ if (typeof value === "object" && value !== null)
1309
+ return JSON.stringify(value);
1310
+ if (typeof value === "string")
1311
+ return value;
1312
+ return String(value);
1313
+ }
1314
+ if (typeof value === "string") {
1315
+ try {
1316
+ const parsed = JSON.parse(value);
1317
+ return parsed;
1318
+ } catch (e) {
1319
+ return defaultValue;
1320
+ }
1321
+ }
1088
1322
  return value;
1089
1323
  });
1090
1324
  }
@@ -1095,7 +1329,18 @@ var FileStorageEngine = class extends DataStoreEngine {
1095
1329
  let data = yield this.readFile();
1096
1330
  if (!data)
1097
1331
  data = {};
1098
- data[name] = value;
1332
+ let storeVal = value;
1333
+ if (typeof value === "string") {
1334
+ try {
1335
+ if (value.startsWith("{") || value.startsWith("[")) {
1336
+ const parsed = JSON.parse(value);
1337
+ if (typeof parsed === "object" && parsed !== null)
1338
+ storeVal = parsed;
1339
+ }
1340
+ } catch (e) {
1341
+ }
1342
+ }
1343
+ data[name] = storeVal;
1099
1344
  yield this.writeFile(data);
1100
1345
  })).catch((err) => {
1101
1346
  console.error("Error in setValue:", err);
@@ -1132,7 +1377,7 @@ var FileStorageEngine = class extends DataStoreEngine {
1132
1377
  fs = (_a = yield import("fs/promises")) == null ? void 0 : _a.default;
1133
1378
  if (!fs)
1134
1379
  throw new ScriptContextError("FileStorageEngine requires Node.js or Deno with Node compatibility (v1.31+)", { cause: new DatedError("'node:fs/promises' module not available") });
1135
- const path = typeof this.options.filePath === "string" ? this.options.filePath : this.options.filePath(this.dataStoreOptions.id);
1380
+ const path = typeof this.options.filePath === "string" ? this.options.filePath : this.options.filePath(this.dataStoreOptions.id, this.dataStoreOptions);
1136
1381
  return yield fs.unlink(path);
1137
1382
  } catch (err) {
1138
1383
  console.error("Error deleting file:", err);
@@ -1295,200 +1540,6 @@ Has: ${checksum}`);
1295
1540
  return this.stores.filter((s) => typeof stores === "undefined" ? true : Array.isArray(stores) ? stores.includes(s.id) : stores(s.id));
1296
1541
  }
1297
1542
  };
1298
- var createNanoEvents = () => ({
1299
- emit(event, ...args) {
1300
- for (let callbacks = this.events[event] || [], i = 0, length = callbacks.length; i < length; i++) {
1301
- callbacks[i](...args);
1302
- }
1303
- },
1304
- events: {},
1305
- on(event, cb) {
1306
- var _a;
1307
- ;
1308
- ((_a = this.events)[event] || (_a[event] = [])).push(cb);
1309
- return () => {
1310
- var _a2;
1311
- this.events[event] = (_a2 = this.events[event]) == null ? void 0 : _a2.filter((i) => cb !== i);
1312
- };
1313
- }
1314
- });
1315
- var NanoEmitter = class {
1316
- /** Creates a new instance of NanoEmitter - a lightweight event emitter with helper methods and a strongly typed event map */
1317
- constructor(options = {}) {
1318
- __publicField(this, "events", createNanoEvents());
1319
- __publicField(this, "eventUnsubscribes", []);
1320
- __publicField(this, "emitterOptions");
1321
- this.emitterOptions = __spreadValues({
1322
- publicEmit: false
1323
- }, options);
1324
- }
1325
- //#region on
1326
- /**
1327
- * Subscribes to an event and calls the callback when it's emitted.
1328
- * @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 "_")
1329
- * @returns Returns a function that can be called to unsubscribe the event listener
1330
- * @example ```ts
1331
- * const emitter = new NanoEmitter<{
1332
- * foo: (bar: string) => void;
1333
- * }>({
1334
- * publicEmit: true,
1335
- * });
1336
- *
1337
- * let i = 0;
1338
- * const unsub = emitter.on("foo", (bar) => {
1339
- * // unsubscribe after 10 events:
1340
- * if(++i === 10) unsub();
1341
- * console.log(bar);
1342
- * });
1343
- *
1344
- * emitter.emit("foo", "bar");
1345
- * ```
1346
- */
1347
- on(event, cb) {
1348
- let unsub;
1349
- const unsubProxy = () => {
1350
- if (!unsub)
1351
- return;
1352
- unsub();
1353
- this.eventUnsubscribes = this.eventUnsubscribes.filter((u) => u !== unsub);
1354
- };
1355
- unsub = this.events.on(event, cb);
1356
- this.eventUnsubscribes.push(unsub);
1357
- return unsubProxy;
1358
- }
1359
- //#region once
1360
- /**
1361
- * Subscribes to an event and calls the callback or resolves the Promise only once when it's emitted.
1362
- * @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 "_")
1363
- * @param cb The callback to call when the event is emitted - if provided or not, the returned Promise will resolve with the event arguments
1364
- * @returns Returns a Promise that resolves with the event arguments when the event is emitted
1365
- * @example ```ts
1366
- * const emitter = new NanoEmitter<{
1367
- * foo: (bar: string) => void;
1368
- * }>();
1369
- *
1370
- * // Promise syntax:
1371
- * const [bar] = await emitter.once("foo");
1372
- * console.log(bar);
1373
- *
1374
- * // Callback syntax:
1375
- * emitter.once("foo", (bar) => console.log(bar));
1376
- * ```
1377
- */
1378
- once(event, cb) {
1379
- return new Promise((resolve) => {
1380
- let unsub;
1381
- const onceProxy = ((...args) => {
1382
- cb == null ? void 0 : cb(...args);
1383
- unsub == null ? void 0 : unsub();
1384
- resolve(args);
1385
- });
1386
- unsub = this.events.on(event, onceProxy);
1387
- this.eventUnsubscribes.push(unsub);
1388
- });
1389
- }
1390
- //#region onMulti
1391
- /**
1392
- * 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.
1393
- * @param options An object or array of objects with the following properties:
1394
- * `callback` (required) is the function that will be called when the conditions are met.
1395
- *
1396
- * 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.
1397
- * If `signal` is provided, the subscription will be canceled when the given signal is aborted.
1398
- *
1399
- * If `oneOf` is used, the callback will be called when any of the matching events are emitted.
1400
- * 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.
1401
- * 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.
1402
- * At least one of `oneOf` or `allOf` must be provided.
1403
- *
1404
- * @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.
1405
- */
1406
- onMulti(options) {
1407
- const allUnsubs = [];
1408
- const unsubAll = () => {
1409
- for (const unsub of allUnsubs)
1410
- unsub();
1411
- allUnsubs.splice(0, allUnsubs.length);
1412
- this.eventUnsubscribes = this.eventUnsubscribes.filter((u) => !allUnsubs.includes(u));
1413
- };
1414
- for (const opts of Array.isArray(options) ? options : [options]) {
1415
- const optsWithDefaults = __spreadValues({
1416
- allOf: [],
1417
- oneOf: [],
1418
- once: false
1419
- }, opts);
1420
- const {
1421
- oneOf,
1422
- allOf,
1423
- once,
1424
- signal,
1425
- callback
1426
- } = optsWithDefaults;
1427
- if (signal == null ? void 0 : signal.aborted)
1428
- return unsubAll;
1429
- if (oneOf.length === 0 && allOf.length === 0)
1430
- throw new TypeError("NanoEmitter.onMulti(): Either `oneOf` or `allOf` or both must be provided in the options");
1431
- const curEvtUnsubs = [];
1432
- const checkUnsubAllEvt = (force = false) => {
1433
- if (!(signal == null ? void 0 : signal.aborted) && !force)
1434
- return;
1435
- for (const unsub of curEvtUnsubs)
1436
- unsub();
1437
- curEvtUnsubs.splice(0, curEvtUnsubs.length);
1438
- this.eventUnsubscribes = this.eventUnsubscribes.filter((u) => !curEvtUnsubs.includes(u));
1439
- };
1440
- const allOfEmitted = /* @__PURE__ */ new Set();
1441
- const allOfConditionMet = () => allOf.length === 0 || allOfEmitted.size === allOf.length;
1442
- for (const event of oneOf) {
1443
- const unsub = this.events.on(event, ((...args) => {
1444
- checkUnsubAllEvt();
1445
- if (allOfConditionMet()) {
1446
- callback(event, ...args);
1447
- if (once)
1448
- checkUnsubAllEvt(true);
1449
- }
1450
- }));
1451
- curEvtUnsubs.push(unsub);
1452
- }
1453
- for (const event of allOf) {
1454
- const unsub = this.events.on(event, ((...args) => {
1455
- checkUnsubAllEvt();
1456
- allOfEmitted.add(event);
1457
- if (allOfConditionMet() && (oneOf.length === 0 || oneOf.includes(event))) {
1458
- callback(event, ...args);
1459
- if (once)
1460
- checkUnsubAllEvt(true);
1461
- }
1462
- }));
1463
- curEvtUnsubs.push(unsub);
1464
- }
1465
- allUnsubs.push(() => checkUnsubAllEvt(true));
1466
- }
1467
- return unsubAll;
1468
- }
1469
- //#region emit
1470
- /**
1471
- * Emits an event on this instance.
1472
- * - ⚠️ Needs `publicEmit` to be set to true in the NanoEmitter constructor or super() call!
1473
- * @param event The event to emit
1474
- * @param args The arguments to pass to the event listeners
1475
- * @returns Returns true if `publicEmit` is true and the event was emitted successfully
1476
- */
1477
- emit(event, ...args) {
1478
- if (this.emitterOptions.publicEmit) {
1479
- this.events.emit(event, ...args);
1480
- return true;
1481
- }
1482
- return false;
1483
- }
1484
- //#region unsubscribeAll
1485
- /** Unsubscribes all event listeners from this instance */
1486
- unsubscribeAll() {
1487
- for (const unsub of this.eventUnsubscribes)
1488
- unsub();
1489
- this.eventUnsubscribes = [];
1490
- }
1491
- };
1492
1543
  var Debouncer = class extends NanoEmitter {
1493
1544
  /**
1494
1545
  * Creates a new debouncer with the specified timeout and edge type.
@@ -1592,6 +1643,22 @@ function debounce(fn, timeout = 200, type = "immediate") {
1592
1643
  return func;
1593
1644
  }
1594
1645
 
1646
+ // lib/consts.ts
1647
+ var rawConsts = {
1648
+ coreUtilsVersion: "3.3.0",
1649
+ userUtilsVersion: "10.1.0"
1650
+ };
1651
+ function getConst(constKey, defaultVal) {
1652
+ const val = rawConsts[constKey];
1653
+ return val.match(/^#\{\{.+\}\}$/) ? defaultVal : val;
1654
+ }
1655
+ var versions = {
1656
+ /** Semver version string of the bundled library CoreUtils. */
1657
+ CoreUtils: getConst("coreUtilsVersion", "ERR:unknown"),
1658
+ /** Semver version string of UserUtils. */
1659
+ UserUtils: getConst("userUtilsVersion", "ERR:unknown")
1660
+ };
1661
+
1595
1662
  // lib/Errors.ts
1596
1663
  var PlatformError = class extends DatedError {
1597
1664
  constructor(message, options) {
@@ -2558,7 +2625,7 @@ function translate(language, key, ...trArgs) {
2558
2625
  if (typeof language !== "string")
2559
2626
  language = fallbackLang != null ? fallbackLang : "";
2560
2627
  const trObj = trans[language];
2561
- if (typeof language !== "string" || language.length === 0 || typeof trObj !== "object" || trObj === null)
2628
+ if (typeof language !== "string" || typeof trObj !== "object" || trObj === null)
2562
2629
  return fallbackLang && language !== fallbackLang ? translate(fallbackLang, key, ...trArgs) : key;
2563
2630
  const transformTrVal = (trKey, trValue) => {
2564
2631
  const tfs = valTransforms.filter(({ regex }) => new RegExp(regex).test(String(trValue)));
@@ -2619,6 +2686,9 @@ function addTranslations(language, translations) {
2619
2686
  function getTranslations(language = fallbackLang != null ? fallbackLang : "") {
2620
2687
  return trans[language];
2621
2688
  }
2689
+ function getAllTranslations(asCopy = true) {
2690
+ return asCopy ? JSON.parse(JSON.stringify(trans)) : trans;
2691
+ }
2622
2692
  var deleteTranslations = (language) => {
2623
2693
  if (language in trans) {
2624
2694
  delete trans[language];
@@ -2649,52 +2719,55 @@ function deleteTransform(patternOrFn) {
2649
2719
  }
2650
2720
  return false;
2651
2721
  }
2652
- var templateLiteralTransform = [
2653
- /\$\{([a-zA-Z0-9$_-]+)\}/gm,
2654
- ({ matches, trArgs, trValue }) => {
2655
- const patternStart = "${", patternEnd = "}", patternRegex = /\$\{.+\}/m;
2656
- let str = String(trValue);
2657
- const eachKeyInTrString = (keys) => keys.every((key) => trValue.includes(`${patternStart}${key}${patternEnd}`));
2658
- const namedMapping = () => {
2659
- var _a;
2660
- if (!str.includes(patternStart) || typeof trArgs[0] === "undefined" || typeof trArgs[0] !== "object" || !eachKeyInTrString(Object.keys((_a = trArgs[0]) != null ? _a : {})))
2661
- return;
2662
- for (const match of matches) {
2663
- const repl = match[1] !== void 0 ? trArgs[0][match[1]] : void 0;
2664
- if (typeof repl !== "undefined")
2665
- str = str.replace(match[0], String(repl));
2666
- }
2667
- };
2668
- const positionalMapping = () => {
2669
- if (!patternRegex.test(str) || !trArgs[0])
2670
- return;
2671
- let matchNum = -1;
2672
- for (const match of matches) {
2673
- matchNum++;
2674
- if (typeof trArgs[matchNum] !== "undefined")
2675
- str = str.replace(match[0], String(trArgs[matchNum]));
2676
- }
2677
- };
2678
- let notStringifiable = false;
2679
- try {
2680
- String(trArgs[0]);
2681
- } catch (e) {
2682
- notStringifiable = true;
2722
+ var commonKeyedTransform = ({ matches, trArgs, trValue }, patternRegex, quickMatchPattern) => {
2723
+ let str = String(trValue);
2724
+ const someMatchKeyInArgs = (obj) => matches.some((match) => match[1] !== void 0 && match[1] in obj);
2725
+ const namedMapping = () => {
2726
+ if (!str.includes(quickMatchPattern) || typeof trArgs[0] === "undefined" || typeof trArgs[0] !== "object" || !someMatchKeyInArgs(trArgs[0]))
2727
+ return;
2728
+ for (const match of matches) {
2729
+ const repl = match[1] !== void 0 ? trArgs[0][match[1]] : void 0;
2730
+ if (typeof repl !== "undefined")
2731
+ str = str.replace(match[0], String(repl));
2683
2732
  }
2684
- const isArgsObject = trArgs[0] && typeof trArgs[0] === "object" && trArgs[0] !== null && (notStringifiable || String(trArgs[0]).startsWith("[object"));
2685
- if (isArgsObject && eachKeyInTrString(Object.keys(trArgs[0])))
2686
- namedMapping();
2687
- else
2688
- positionalMapping();
2689
- return str;
2733
+ };
2734
+ const positionalMapping = () => {
2735
+ if (!patternRegex.test(str) || typeof trArgs[0] === "undefined")
2736
+ return;
2737
+ let matchNum = -1;
2738
+ for (const match of matches) {
2739
+ matchNum++;
2740
+ if (typeof trArgs[matchNum] !== "undefined")
2741
+ str = str.replace(match[0], String(trArgs[matchNum]));
2742
+ }
2743
+ };
2744
+ let notStringifiable = false;
2745
+ try {
2746
+ void `${trArgs[0]}`;
2747
+ } catch (e) {
2748
+ notStringifiable = true;
2690
2749
  }
2750
+ const isArgsObject = trArgs[0] && typeof trArgs[0] === "object" && trArgs[0] !== null && (notStringifiable || String(trArgs[0]).startsWith("[object"));
2751
+ if (isArgsObject && someMatchKeyInArgs(trArgs[0]))
2752
+ namedMapping();
2753
+ else
2754
+ positionalMapping();
2755
+ return str;
2756
+ };
2757
+ var templateLiteralTransform = [
2758
+ /\$\{([a-zA-Z0-9$_-]+)\}/gm,
2759
+ (tfProps) => commonKeyedTransform(tfProps, /\$\{.+\}/m, "${")
2760
+ ];
2761
+ var i18nTransform = [
2762
+ /\{\{([a-zA-Z0-9$_-]+)\}\}/gm,
2763
+ (tfProps) => commonKeyedTransform(tfProps, /\{\{.+\}\}/m, "{{")
2691
2764
  ];
2692
2765
  var percentTransform = [
2693
2766
  /%(\d+)/gm,
2694
2767
  ({ matches, trArgs, trValue }) => {
2695
2768
  let str = String(trValue);
2696
2769
  for (const match of matches) {
2697
- const repl = match[1] !== void 0 ? trArgs == null ? void 0 : trArgs[Number(match[1]) - 1] : void 0;
2770
+ const repl = trArgs == null ? void 0 : trArgs[Number(match[1]) - 1];
2698
2771
  if (typeof repl !== "undefined")
2699
2772
  str = str.replace(match[0], String(repl));
2700
2773
  }
@@ -2707,13 +2780,81 @@ var tr = {
2707
2780
  hasKey: (language = fallbackLang != null ? fallbackLang : "", key) => hasKey(language, key),
2708
2781
  addTranslations,
2709
2782
  getTranslations,
2783
+ getAllTranslations,
2710
2784
  deleteTranslations,
2711
2785
  setFallbackLanguage,
2712
2786
  getFallbackLanguage,
2713
2787
  addTransform,
2714
2788
  deleteTransform,
2789
+ /** Collection of predefined transform functions that can be added via {@linkcode tr.addTransform()} */
2715
2790
  transforms: {
2791
+ /**
2792
+ * This transform will replace placeholders matching `${key}` with the value of the passed argument(s).
2793
+ * The arguments can be passed in keyed object form or positionally via the spread operator:
2794
+ * - Keyed: If the first argument is an object and `key` is found in it, the value will be used for the replacement.
2795
+ * - 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.
2796
+ *
2797
+ * @example ```ts
2798
+ * tr.addTranslations("en", {
2799
+ * "greeting": "Hello, ${user}!\nYou have ${notifs} notifications.",
2800
+ * });
2801
+ * tr.addTransform(tr.transforms.templateLiteral);
2802
+ *
2803
+ * const t = tr.use("en");
2804
+ *
2805
+ * // both calls return the same result:
2806
+ * t("greeting", { user: "John", notifs: 5 }); // "Hello, John!\nYou have 5 notifications."
2807
+ * t("greeting", "John", 5); // "Hello, John!\nYou have 5 notifications."
2808
+ *
2809
+ * // when a key isn't found in the object, it will be left as-is:
2810
+ * t("greeting", { user: "John" }); // "Hello, John!\nYou have ${notifs} notifications."
2811
+ * ```
2812
+ */
2716
2813
  templateLiteral: templateLiteralTransform,
2814
+ /**
2815
+ * This transform will replace placeholders matching `{{key}}` with the value of the passed argument(s).
2816
+ * This format is commonly used in i18n libraries. Note that advanced syntax is not supported, only simple key replacement.
2817
+ * The arguments can be passed in keyed object form or positionally via the spread operator:
2818
+ * - Keyed: If the first argument is an object and `key` is found in it, the value will be used for the replacement.
2819
+ * - 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.
2820
+ *
2821
+ * @example ```ts
2822
+ * tr.addTranslations("en", {
2823
+ * "greeting": "Hello, {{user}}!\nYou have {{notifs}} notifications.",
2824
+ * });
2825
+ * tr.addTransform(tr.transforms.i18n);
2826
+ *
2827
+ * const t = tr.use("en");
2828
+ *
2829
+ * // both calls return the same result:
2830
+ * t("greeting", { user: "Alice", notifs: 5 }); // "Hello, Alice!\nYou have 5 notifications."
2831
+ * t("greeting", "Alice", 5); // "Hello, Alice!\nYou have 5 notifications."
2832
+ *
2833
+ * // when a key isn't found in the object, it will be left as-is:
2834
+ * t("greeting", { user: "Alice" }); // "Hello, Alice!\nYou have {{notifs}} notifications."
2835
+ * ```
2836
+ */
2837
+ i18n: i18nTransform,
2838
+ /**
2839
+ * This transform will replace `%n` placeholders with the value of the passed arguments.
2840
+ * 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.
2841
+ * Objects will be stringified via `String()` before being inserted.
2842
+ *
2843
+ * @example ```ts
2844
+ * tr.addTranslations("en", {
2845
+ * "greeting": "Hello, %1!\nYou have %2 notifications.",
2846
+ * });
2847
+ * tr.addTransform(tr.transforms.percent);
2848
+ *
2849
+ * const t = tr.use("en");
2850
+ *
2851
+ * // arguments are inserted in the order they're passed:
2852
+ * t("greeting", "Bob", 5); // "Hello, Bob!\nYou have 5 notifications."
2853
+ *
2854
+ * // when a value isn't found, the placeholder will be left as-is:
2855
+ * t("greeting", "Bob"); // "Hello, Bob!\nYou have %2 notifications."
2856
+ * ```
2857
+ */
2717
2858
  percent: percentTransform
2718
2859
  }
2719
2860
  };
@@ -2740,5 +2881,3 @@ if (typeof module.exports == "object" && typeof exports == "object") {
2740
2881
 
2741
2882
 
2742
2883
 
2743
-
2744
- //# sourceMappingURL=UserUtils.umd.js.map