@sv443-network/coreutils 0.0.1 → 2.0.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.
@@ -65,6 +65,7 @@ __export(lib_exports, {
65
65
  joinArrayReadable: () => joinArrayReadable,
66
66
  lightenColor: () => lightenColor,
67
67
  mapRange: () => mapRange,
68
+ overflowVal: () => overflowVal,
68
69
  pauseFor: () => pauseFor,
69
70
  pureObj: () => pureObj,
70
71
  randRange: () => randRange,
@@ -74,6 +75,7 @@ __export(lib_exports, {
74
75
  randomizeArray: () => randomizeArray,
75
76
  rgbToHex: () => rgbToHex,
76
77
  roundFixed: () => roundFixed,
78
+ scheduleExit: () => scheduleExit,
77
79
  secsToTimeStr: () => secsToTimeStr,
78
80
  setImmediateInterval: () => setImmediateInterval,
79
81
  setImmediateTimeoutLoop: () => setImmediateTimeoutLoop,
@@ -127,6 +129,19 @@ function mapRange(value, range1min, range1max, range2min, range2max) {
127
129
  return value * (range2max / range1max);
128
130
  return (value - range1min) * ((range2max - range2min) / (range1max - range1min)) + range2min;
129
131
  }
132
+ function overflowVal(value, minOrMax, max) {
133
+ const min = typeof max === "number" ? minOrMax : 0;
134
+ max = typeof max === "number" ? max : minOrMax;
135
+ if (min > max)
136
+ throw new RangeError(`Parameter "min" can't be bigger than "max"`);
137
+ if (isNaN(value) || isNaN(min) || isNaN(max) || !isFinite(value) || !isFinite(min) || !isFinite(max))
138
+ return NaN;
139
+ if (value >= min && value <= max)
140
+ return value;
141
+ const range = max - min + 1;
142
+ const wrappedValue = ((value - min) % range + range) % range + min;
143
+ return wrappedValue;
144
+ }
130
145
  function randRange(...args) {
131
146
  let min, max, enhancedEntropy = false;
132
147
  if (typeof args[0] === "number" && typeof args[1] === "number")
@@ -222,10 +237,8 @@ function darkenColor(color, percent, upperCase = false) {
222
237
  return rgbToHex(r, g, b, a, color.startsWith("#"), upperCase);
223
238
  else if (color.startsWith("rgba"))
224
239
  return `rgba(${r}, ${g}, ${b}, ${a ?? NaN})`;
225
- else if (color.startsWith("rgb"))
226
- return `rgb(${r}, ${g}, ${b})`;
227
240
  else
228
- throw new TypeError("Unsupported color format");
241
+ return `rgb(${r}, ${g}, ${b})`;
229
242
  }
230
243
  function hexToRgb(hex) {
231
244
  hex = (hex.startsWith("#") ? hex.slice(1) : hex).trim();
@@ -258,22 +271,22 @@ function atoab(str) {
258
271
  return Uint8Array.from(atob(str), (c) => c.charCodeAt(0));
259
272
  }
260
273
  async function compress(input, compressionFormat, outputType = "string") {
261
- const byteArray = input instanceof ArrayBuffer ? input : new TextEncoder().encode((input == null ? void 0 : input.toString()) ?? String(input));
274
+ const byteArray = input instanceof Uint8Array ? input : new TextEncoder().encode((input == null ? void 0 : input.toString()) ?? String(input));
262
275
  const comp = new CompressionStream(compressionFormat);
263
276
  const writer = comp.writable.getWriter();
264
277
  writer.write(byteArray);
265
278
  writer.close();
266
- const buf = await new Response(comp.readable).arrayBuffer();
267
- return outputType === "arrayBuffer" ? buf : abtoa(buf);
279
+ const uintArr = new Uint8Array(await new Response(comp.readable).arrayBuffer());
280
+ return outputType === "arrayBuffer" ? uintArr : abtoa(uintArr);
268
281
  }
269
282
  async function decompress(input, compressionFormat, outputType = "string") {
270
- const byteArray = input instanceof ArrayBuffer ? input : atoab((input == null ? void 0 : input.toString()) ?? String(input));
283
+ const byteArray = input instanceof Uint8Array ? input : atoab((input == null ? void 0 : input.toString()) ?? String(input));
271
284
  const decomp = new DecompressionStream(compressionFormat);
272
285
  const writer = decomp.writable.getWriter();
273
286
  writer.write(byteArray);
274
287
  writer.close();
275
- const buf = await new Response(decomp.readable).arrayBuffer();
276
- return outputType === "arrayBuffer" ? buf : new TextDecoder().decode(buf);
288
+ const uintArr = new Uint8Array(await new Response(decomp.readable).arrayBuffer());
289
+ return outputType === "arrayBuffer" ? uintArr : new TextDecoder().decode(uintArr);
277
290
  }
278
291
  async function computeHash(input, algorithm = "SHA-256") {
279
292
  let data;
@@ -382,6 +395,18 @@ function setImmediateTimeoutLoop(callback, interval, signal) {
382
395
  signal == null ? void 0 : signal.addEventListener("abort", cleanup);
383
396
  loop();
384
397
  }
398
+ function scheduleExit(code = 0, timeout = 0) {
399
+ if (timeout < 0)
400
+ throw new TypeError("Timeout must be a non-negative number");
401
+ let exit;
402
+ if (typeof process !== "undefined" && "exit" in process)
403
+ exit = () => process.exit(code);
404
+ else if (typeof Deno !== "undefined" && "exit" in Deno)
405
+ exit = () => Deno.exit(code);
406
+ else
407
+ throw new Error("Cannot exit the process, no exit method available");
408
+ setTimeout(exit, timeout);
409
+ }
385
410
 
386
411
  // lib/text.ts
387
412
  function autoPlural(term, num, pluralType = "auto") {
@@ -507,7 +532,14 @@ var DataStore = class {
507
532
  decodeData;
508
533
  compressionFormat = "deflate-raw";
509
534
  engine;
535
+ options;
536
+ /**
537
+ * Whether all first-init checks should be done.
538
+ * This includes migrating the internal DataStore format, migrating data from the UserUtils format, and anything similar.
539
+ * 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.
540
+ */
510
541
  firstInit = true;
542
+ /** In-memory cached copy of the data that is saved in persistent storage used for synchronous read access. */
511
543
  cachedData;
512
544
  migrations;
513
545
  migrateIds = [];
@@ -515,13 +547,13 @@ var DataStore = class {
515
547
  * Creates an instance of DataStore to manage a sync & async database that is cached in memory and persistently saved across sessions.
516
548
  * Supports migrating data from older versions to newer ones and populating the cache with default data if no persistent data is found.
517
549
  *
518
- * - ⚠️ Requires the directives `@grant GM.getValue` and `@grant GM.setValue` if the storageMethod is left as the default of `"GM"`
519
550
  * - ⚠️ Make sure to call {@linkcode loadData()} at least once after creating an instance, or the returned data will be the same as `options.defaultData`
520
551
  *
521
552
  * @template TData The type of the data that is saved in persistent storage for the currently set format version (will be automatically inferred from `defaultData` if not provided) - **This has to be a JSON-compatible object!** (no undefined, circular references, etc.)
522
553
  * @param opts The options for this DataStore instance
523
554
  */
524
555
  constructor(opts) {
556
+ var _a;
525
557
  this.id = opts.id;
526
558
  this.formatVersion = opts.formatVersion;
527
559
  this.defaultData = opts.defaultData;
@@ -532,8 +564,9 @@ var DataStore = class {
532
564
  this.encodeData = opts.encodeData;
533
565
  this.decodeData = opts.decodeData;
534
566
  this.engine = typeof opts.engine === "function" ? opts.engine() : opts.engine;
567
+ this.options = opts;
535
568
  if (typeof opts.compressionFormat === "undefined")
536
- opts.compressionFormat = "deflate-raw";
569
+ opts.compressionFormat = ((_a = opts.encodeData) == null ? void 0 : _a[0]) ?? "deflate-raw";
537
570
  if (typeof opts.compressionFormat === "string") {
538
571
  this.encodeData = [opts.compressionFormat, async (data) => await compress(data, opts.compressionFormat, "string")];
539
572
  this.decodeData = [opts.compressionFormat, async (data) => await compress(data, opts.compressionFormat, "string")];
@@ -568,9 +601,14 @@ var DataStore = class {
568
601
  promises.push(this.engine.setValue(newKey, value));
569
602
  promises.push(this.engine.deleteValue(oldKey));
570
603
  };
571
- oldData && migrateFmt(`_uucfg-${this.id}`, `__ds-${this.id}-dat`, oldData);
572
- !isNaN(oldVer) && migrateFmt(`_uucfgver-${this.id}`, `__ds-${this.id}-ver`, oldVer);
573
- typeof oldEnc === "boolean" && migrateFmt(`_uucfgenc-${this.id}`, `__ds-${this.id}-enc`, oldEnc === true ? this.compressionFormat : null);
604
+ if (oldData)
605
+ migrateFmt(`_uucfg-${this.id}`, `__ds-${this.id}-dat`, oldData);
606
+ if (!isNaN(oldVer))
607
+ migrateFmt(`_uucfgver-${this.id}`, `__ds-${this.id}-ver`, oldVer);
608
+ if (typeof oldEnc === "boolean")
609
+ migrateFmt(`_uucfgenc-${this.id}`, `__ds-${this.id}-enf`, oldEnc === true ? Boolean(this.compressionFormat) || null : null);
610
+ else
611
+ promises.push(this.engine.setValue(`__ds-${this.id}-enf`, this.compressionFormat));
574
612
  await Promise.allSettled(promises);
575
613
  }
576
614
  await this.engine.setValue("__ds_fmt_ver", dsFmtVer);
@@ -580,21 +618,22 @@ var DataStore = class {
580
618
  await this.migrateId(this.migrateIds);
581
619
  this.migrateIds = [];
582
620
  }
583
- const gmData = await this.engine.getValue(`__ds-${this.id}-dat`, JSON.stringify(this.defaultData));
584
- let gmFmtVer = Number(await this.engine.getValue(`__ds-${this.id}-ver`, NaN));
585
- if (typeof gmData !== "string") {
621
+ const storedData = await this.engine.getValue(`__ds-${this.id}-dat`, JSON.stringify(this.defaultData));
622
+ let storedFmtVer = Number(await this.engine.getValue(`__ds-${this.id}-ver`, NaN));
623
+ if (typeof storedData !== "string") {
586
624
  await this.saveDefaultData();
587
625
  return { ...this.defaultData };
588
626
  }
589
- const isEncoded = Boolean(await this.engine.getValue(`__ds-${this.id}-enc`, false));
627
+ const encodingFmt = String(await this.engine.getValue(`__ds-${this.id}-enf`, null));
628
+ const isEncoded = encodingFmt !== "null" && encodingFmt !== "false";
590
629
  let saveData = false;
591
- if (isNaN(gmFmtVer)) {
592
- await this.engine.setValue(`__ds-${this.id}-ver`, gmFmtVer = this.formatVersion);
630
+ if (isNaN(storedFmtVer)) {
631
+ await this.engine.setValue(`__ds-${this.id}-ver`, storedFmtVer = this.formatVersion);
593
632
  saveData = true;
594
633
  }
595
- let parsed = await this.engine.deserializeData(gmData, isEncoded);
596
- if (gmFmtVer < this.formatVersion && this.migrations)
597
- parsed = await this.runMigrations(parsed, gmFmtVer);
634
+ let parsed = await this.engine.deserializeData(storedData, isEncoded);
635
+ if (storedFmtVer < this.formatVersion && this.migrations)
636
+ parsed = await this.runMigrations(parsed, storedFmtVer);
598
637
  if (saveData)
599
638
  await this.setData(parsed);
600
639
  return this.cachedData = this.engine.deepCopy(parsed);
@@ -614,12 +653,11 @@ var DataStore = class {
614
653
  /** Saves the data synchronously to the in-memory cache and asynchronously to the persistent storage */
615
654
  setData(data) {
616
655
  this.cachedData = data;
617
- const useEncoding = this.encodingEnabled();
618
656
  return new Promise(async (resolve) => {
619
657
  await Promise.allSettled([
620
- this.engine.setValue(`__ds-${this.id}-dat`, await this.engine.serializeData(data, useEncoding)),
658
+ this.engine.setValue(`__ds-${this.id}-dat`, await this.engine.serializeData(data, this.encodingEnabled())),
621
659
  this.engine.setValue(`__ds-${this.id}-ver`, this.formatVersion),
622
- this.engine.setValue(`__ds-${this.id}-enc`, useEncoding)
660
+ this.engine.setValue(`__ds-${this.id}-enf`, this.compressionFormat)
623
661
  ]);
624
662
  resolve();
625
663
  });
@@ -627,30 +665,29 @@ var DataStore = class {
627
665
  /** Saves the default data passed in the constructor synchronously to the in-memory cache and asynchronously to persistent storage */
628
666
  async saveDefaultData() {
629
667
  this.cachedData = this.defaultData;
630
- const useEncoding = this.encodingEnabled();
631
668
  await Promise.allSettled([
632
- this.engine.setValue(`__ds-${this.id}-dat`, await this.engine.serializeData(this.defaultData, useEncoding)),
669
+ this.engine.setValue(`__ds-${this.id}-dat`, await this.engine.serializeData(this.defaultData, this.encodingEnabled())),
633
670
  this.engine.setValue(`__ds-${this.id}-ver`, this.formatVersion),
634
- this.engine.setValue(`__ds-${this.id}-enc`, useEncoding)
671
+ this.engine.setValue(`__ds-${this.id}-enf`, this.compressionFormat)
635
672
  ]);
636
673
  }
637
674
  /**
638
- * Call this method to clear all persistently stored data associated with this DataStore instance.
675
+ * Call this method to clear all persistently stored data associated with this DataStore instance, including the storage container (if supported by the DataStoreEngine).
639
676
  * The in-memory cache will be left untouched, so you may still access the data with {@linkcode getData()}
640
- * Calling {@linkcode loadData()} or {@linkcode setData()} after this method was called will recreate persistent storage with the cached or default data.
641
- *
642
- * - ⚠️ This requires the additional directive `@grant GM.deleteValue` if the storageMethod is left as the default of `"GM"`
677
+ * Calling {@linkcode loadData()} or {@linkcode setData()} after this method was called will recreate persistent storage with the cached or default data.
643
678
  */
644
679
  async deleteData() {
680
+ var _a, _b;
645
681
  await Promise.allSettled([
646
682
  this.engine.deleteValue(`__ds-${this.id}-dat`),
647
683
  this.engine.deleteValue(`__ds-${this.id}-ver`),
648
- this.engine.deleteValue(`__ds-${this.id}-enc`)
684
+ this.engine.deleteValue(`__ds-${this.id}-enf`)
649
685
  ]);
686
+ await ((_b = (_a = this.engine).deleteStorage) == null ? void 0 : _b.call(_a));
650
687
  }
651
688
  /** Returns whether encoding and decoding are enabled for this DataStore instance */
652
689
  encodingEnabled() {
653
- return Boolean(this.encodeData && this.decodeData);
690
+ return Boolean(this.encodeData && this.decodeData) && this.compressionFormat !== null || Boolean(this.compressionFormat);
654
691
  }
655
692
  //#region migrations
656
693
  /**
@@ -684,7 +721,7 @@ var DataStore = class {
684
721
  await Promise.allSettled([
685
722
  this.engine.setValue(`__ds-${this.id}-dat`, await this.engine.serializeData(newData)),
686
723
  this.engine.setValue(`__ds-${this.id}-ver`, lastFmtVer),
687
- this.engine.setValue(`__ds-${this.id}-enc`, this.encodingEnabled())
724
+ this.engine.setValue(`__ds-${this.id}-enf`, this.compressionFormat)
688
725
  ]);
689
726
  return this.cachedData = { ...newData };
690
727
  }
@@ -695,19 +732,24 @@ var DataStore = class {
695
732
  async migrateId(oldIds) {
696
733
  const ids = Array.isArray(oldIds) ? oldIds : [oldIds];
697
734
  await Promise.all(ids.map(async (id) => {
698
- const data = await this.engine.getValue(`__ds-${id}-dat`, JSON.stringify(this.defaultData));
699
- const fmtVer = Number(await this.engine.getValue(`__ds-${id}-ver`, NaN));
700
- const isEncoded = Boolean(await this.engine.getValue(`__ds-${id}-enc`, false));
735
+ const [data, fmtVer, isEncoded] = await (async () => {
736
+ const [d, f, e] = await Promise.all([
737
+ this.engine.getValue(`__ds-${id}-dat`, JSON.stringify(this.defaultData)),
738
+ this.engine.getValue(`__ds-${id}-ver`, NaN),
739
+ this.engine.getValue(`__ds-${id}-enf`, null)
740
+ ]);
741
+ return [d, Number(f), Boolean(e) && String(e) !== "null"];
742
+ })();
701
743
  if (data === void 0 || isNaN(fmtVer))
702
744
  return;
703
745
  const parsed = await this.engine.deserializeData(data, isEncoded);
704
746
  await Promise.allSettled([
705
747
  this.engine.setValue(`__ds-${this.id}-dat`, await this.engine.serializeData(parsed)),
706
748
  this.engine.setValue(`__ds-${this.id}-ver`, fmtVer),
707
- this.engine.setValue(`__ds-${this.id}-enc`, isEncoded),
749
+ this.engine.setValue(`__ds-${this.id}-enf`, this.compressionFormat),
708
750
  this.engine.deleteValue(`__ds-${id}-dat`),
709
751
  this.engine.deleteValue(`__ds-${id}-ver`),
710
- this.engine.deleteValue(`__ds-${id}-enc`)
752
+ this.engine.deleteValue(`__ds-${id}-enf`)
711
753
  ]);
712
754
  }));
713
755
  }
@@ -717,7 +759,11 @@ var DataStore = class {
717
759
  var DataStoreEngine = class {
718
760
  dataStoreOptions;
719
761
  // setDataStoreOptions() is called from inside the DataStore constructor to set this value
720
- /** Called by DataStore on creation, to pass its options */
762
+ constructor(options) {
763
+ if (options)
764
+ this.dataStoreOptions = options;
765
+ }
766
+ /** Called by DataStore on creation, to pass its options. Only call this if you are using this instance standalone! */
721
767
  setDataStoreOptions(dataStoreOptions) {
722
768
  this.dataStoreOptions = dataStoreOptions;
723
769
  }
@@ -725,6 +771,7 @@ var DataStoreEngine = class {
725
771
  /** Serializes the given object to a string, optionally encoded with `options.encodeData` if {@linkcode useEncoding} is set to true */
726
772
  async serializeData(data, useEncoding) {
727
773
  var _a, _b, _c, _d, _e;
774
+ this.ensureDataStoreOptions();
728
775
  const stringData = JSON.stringify(data);
729
776
  if (!useEncoding || !((_a = this.dataStoreOptions) == null ? void 0 : _a.encodeData) || !((_b = this.dataStoreOptions) == null ? void 0 : _b.decodeData))
730
777
  return stringData;
@@ -736,12 +783,20 @@ var DataStoreEngine = class {
736
783
  /** Deserializes the given string to a JSON object, optionally decoded with `options.decodeData` if {@linkcode useEncoding} is set to true */
737
784
  async deserializeData(data, useEncoding) {
738
785
  var _a, _b, _c;
786
+ this.ensureDataStoreOptions();
739
787
  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;
740
788
  if (decRes instanceof Promise)
741
789
  decRes = await decRes;
742
790
  return JSON.parse(decRes ?? data);
743
791
  }
744
792
  //#region misc api
793
+ /** Throws an error if the DataStoreOptions are not set or invalid */
794
+ ensureDataStoreOptions() {
795
+ if (!this.dataStoreOptions)
796
+ throw new DatedError("DataStoreEngine must be initialized with DataStore options before use. If you are using this instance standalone, set them in the constructor or call `setDataStoreOptions()` with the DataStore options.");
797
+ if (!this.dataStoreOptions.id)
798
+ throw new DatedError("DataStoreEngine must be initialized with a valid DataStore ID");
799
+ }
745
800
  /**
746
801
  * Copies a JSON-compatible object and loses all its internal references in the process.
747
802
  * Uses [`structuredClone()`](https://developer.mozilla.org/en-US/docs/Web/API/structuredClone) if available, otherwise falls back to `JSON.parse(JSON.stringify(obj))`.
@@ -760,11 +815,11 @@ var BrowserStorageEngine = class extends DataStoreEngine {
760
815
  /**
761
816
  * Creates an instance of `BrowserStorageEngine`.
762
817
  *
763
- * ⚠️ Requires a DOM environment
764
- * ⚠️ Don't reuse this engine across multiple {@linkcode DataStore} instances
818
+ * - ⚠️ Requires a DOM environment
819
+ * - ⚠️ Don't reuse engines across multiple {@linkcode DataStore} instances
765
820
  */
766
821
  constructor(options) {
767
- super();
822
+ super(options == null ? void 0 : options.dataStoreOptions);
768
823
  this.options = {
769
824
  type: "localStorage",
770
825
  ...options
@@ -796,11 +851,11 @@ var FileStorageEngine = class extends DataStoreEngine {
796
851
  /**
797
852
  * Creates an instance of `FileStorageEngine`.
798
853
  *
799
- * ⚠️ Requires Node.js or Deno with Node compatibility
800
- * ⚠️ Don't reuse this engine across multiple {@linkcode DataStore} instances
854
+ * - ⚠️ Requires Node.js or Deno with Node compatibility (v1.31+)
855
+ * - ⚠️ Don't reuse engines across multiple {@linkcode DataStore} instances
801
856
  */
802
857
  constructor(options) {
803
- super();
858
+ super(options == null ? void 0 : options.dataStoreOptions);
804
859
  this.options = {
805
860
  filePath: (id) => `.ds-${id}`,
806
861
  ...options
@@ -809,28 +864,32 @@ var FileStorageEngine = class extends DataStoreEngine {
809
864
  //#region json file
810
865
  /** Reads the file contents */
811
866
  async readFile() {
812
- var _a, _b, _c;
867
+ var _a, _b, _c, _d;
868
+ this.ensureDataStoreOptions();
813
869
  try {
814
870
  if (!fs)
815
- fs = (await import("fs/promises")).default;
871
+ fs = (_a = await import("fs/promises")) == null ? void 0 : _a.default;
872
+ if (!fs)
873
+ throw new DatedError("FileStorageEngine requires Node.js or Deno with Node compatibility (v1.31+)", { cause: new Error("'node:fs/promises' module not available") });
816
874
  const path = typeof this.options.filePath === "string" ? this.options.filePath : this.options.filePath(this.dataStoreOptions.id);
817
875
  const data = await fs.readFile(path, "utf-8");
818
- if (!data)
819
- return void 0;
820
- return JSON.parse(await ((_c = (_b = (_a = this.dataStoreOptions) == null ? void 0 : _a.decodeData) == null ? void 0 : _b[1]) == null ? void 0 : _c.call(_b, data)) ?? data);
876
+ 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;
821
877
  } catch {
822
878
  return void 0;
823
879
  }
824
880
  }
825
881
  /** Overwrites the file contents */
826
882
  async writeFile(data) {
827
- var _a, _b, _c;
883
+ var _a, _b, _c, _d;
884
+ this.ensureDataStoreOptions();
828
885
  try {
829
886
  if (!fs)
830
- fs = (await import("fs/promises")).default;
887
+ fs = (_a = await import("fs/promises")) == null ? void 0 : _a.default;
888
+ if (!fs)
889
+ throw new DatedError("FileStorageEngine requires Node.js or Deno with Node compatibility (v1.31+)", { cause: new Error("'node:fs/promises' module not available") });
831
890
  const path = typeof this.options.filePath === "string" ? this.options.filePath : this.options.filePath(this.dataStoreOptions.id);
832
891
  await fs.mkdir(path.slice(0, path.lastIndexOf("/")), { recursive: true });
833
- await fs.writeFile(path, await ((_c = (_b = (_a = this.dataStoreOptions) == null ? void 0 : _a.encodeData) == null ? void 0 : _b[1]) == null ? void 0 : _c.call(_b, JSON.stringify(data))) ?? JSON.stringify(data, void 0, 2), "utf-8");
892
+ 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");
834
893
  } catch (err) {
835
894
  console.error("Error writing file:", err);
836
895
  }
@@ -864,6 +923,21 @@ var FileStorageEngine = class extends DataStoreEngine {
864
923
  delete data[name];
865
924
  await this.writeFile(data);
866
925
  }
926
+ /** Deletes the file that contains the data of this DataStore. */
927
+ async deleteStorage() {
928
+ var _a;
929
+ this.ensureDataStoreOptions();
930
+ try {
931
+ if (!fs)
932
+ fs = (_a = await import("fs/promises")) == null ? void 0 : _a.default;
933
+ if (!fs)
934
+ throw new DatedError("FileStorageEngine requires Node.js or Deno with Node compatibility (v1.31+)", { cause: new Error("'node:fs/promises' module not available") });
935
+ const path = typeof this.options.filePath === "string" ? this.options.filePath : this.options.filePath(this.dataStoreOptions.id);
936
+ await fs.unlink(path);
937
+ } catch (err) {
938
+ console.error("Error deleting file:", err);
939
+ }
940
+ }
867
941
  };
868
942
 
869
943
  // lib/DataStoreSerializer.ts
@@ -891,14 +965,16 @@ var DataStoreSerializer = class _DataStoreSerializer {
891
965
  * @param stringified Whether to return the result as a string or as an array of `SerializedDataStore` objects
892
966
  */
893
967
  async serializePartial(stores, useEncoding = true, stringified = true) {
968
+ var _a;
894
969
  const serData = [];
895
970
  for (const storeInst of this.stores.filter((s) => typeof stores === "function" ? stores(s.id) : stores.includes(s.id))) {
896
- const data = useEncoding && storeInst.encodingEnabled() ? await storeInst.encodeData[1](JSON.stringify(storeInst.getData())) : JSON.stringify(storeInst.getData());
971
+ const encoded = Boolean(useEncoding && storeInst.encodingEnabled() && ((_a = storeInst.encodeData) == null ? void 0 : _a[1]));
972
+ const data = encoded ? await storeInst.encodeData[1](JSON.stringify(storeInst.getData())) : JSON.stringify(storeInst.getData());
897
973
  serData.push({
898
974
  id: storeInst.id,
899
975
  data,
900
976
  formatVersion: storeInst.formatVersion,
901
- encoded: useEncoding && storeInst.encodingEnabled(),
977
+ encoded,
902
978
  checksum: this.options.addChecksum ? await this.calcChecksum(data) : void 0
903
979
  });
904
980
  }
@@ -1022,6 +1098,7 @@ var NanoEmitter = class {
1022
1098
  ...options
1023
1099
  };
1024
1100
  }
1101
+ //#region on
1025
1102
  /**
1026
1103
  * Subscribes to an event and calls the callback when it's emitted.
1027
1104
  * @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 "_")
@@ -1055,6 +1132,7 @@ var NanoEmitter = class {
1055
1132
  this.eventUnsubscribes.push(unsub);
1056
1133
  return unsubProxy;
1057
1134
  }
1135
+ //#region once
1058
1136
  /**
1059
1137
  * Subscribes to an event and calls the callback or resolves the Promise only once when it's emitted.
1060
1138
  * @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 "_")
@@ -1085,9 +1163,90 @@ var NanoEmitter = class {
1085
1163
  this.eventUnsubscribes.push(unsub);
1086
1164
  });
1087
1165
  }
1166
+ //#region onMulti
1167
+ /**
1168
+ * 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.
1169
+ * @param options An object or array of objects with the following properties:
1170
+ * `callback` (required) is the function that will be called when the conditions are met.
1171
+ *
1172
+ * 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.
1173
+ * If `signal` is provided, the subscription will be aborted when the given signal is aborted.
1174
+ *
1175
+ * If `oneOf` is used, the callback will be called when any of the matching events are emitted.
1176
+ * 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.
1177
+ * You may use a combination of the above two options, but at least one of them must be provided.
1178
+ *
1179
+ * @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.
1180
+ */
1181
+ onMulti(options) {
1182
+ const allUnsubs = [];
1183
+ const unsubAll = () => {
1184
+ for (const unsub of allUnsubs)
1185
+ unsub();
1186
+ allUnsubs.splice(0, allUnsubs.length);
1187
+ this.eventUnsubscribes = this.eventUnsubscribes.filter((u) => !allUnsubs.includes(u));
1188
+ };
1189
+ for (const opts of Array.isArray(options) ? options : [options]) {
1190
+ const optsWithDefaults = {
1191
+ allOf: [],
1192
+ oneOf: [],
1193
+ once: false,
1194
+ ...opts
1195
+ };
1196
+ const {
1197
+ oneOf,
1198
+ allOf,
1199
+ once,
1200
+ signal,
1201
+ callback
1202
+ } = optsWithDefaults;
1203
+ if (signal == null ? void 0 : signal.aborted)
1204
+ return unsubAll;
1205
+ const curEvtUnsubs = [];
1206
+ const checkUnsubAllEvt = (force = false) => {
1207
+ if (!(signal == null ? void 0 : signal.aborted) && !force)
1208
+ return;
1209
+ for (const unsub of curEvtUnsubs)
1210
+ unsub();
1211
+ curEvtUnsubs.splice(0, curEvtUnsubs.length);
1212
+ this.eventUnsubscribes = this.eventUnsubscribes.filter((u) => !curEvtUnsubs.includes(u));
1213
+ };
1214
+ for (const event of oneOf) {
1215
+ const unsub = this.events.on(event, (...args) => {
1216
+ checkUnsubAllEvt();
1217
+ callback(event, ...args);
1218
+ if (once)
1219
+ checkUnsubAllEvt(true);
1220
+ });
1221
+ curEvtUnsubs.push(unsub);
1222
+ }
1223
+ const allOfEmitted = /* @__PURE__ */ new Set();
1224
+ const checkAllOf = (event, ...args) => {
1225
+ checkUnsubAllEvt();
1226
+ allOfEmitted.add(event);
1227
+ if (allOfEmitted.size === allOf.length) {
1228
+ callback(event, ...args);
1229
+ if (once)
1230
+ checkUnsubAllEvt(true);
1231
+ }
1232
+ };
1233
+ for (const event of allOf) {
1234
+ const unsub = this.events.on(event, (...args) => {
1235
+ checkUnsubAllEvt();
1236
+ checkAllOf(event, ...args);
1237
+ });
1238
+ curEvtUnsubs.push(unsub);
1239
+ }
1240
+ if (oneOf.length === 0 && allOf.length === 0)
1241
+ throw new TypeError("NanoEmitter.onMulti(): Either `oneOf` or `allOf` or both must be provided in the options");
1242
+ allUnsubs.push(() => checkUnsubAllEvt(true));
1243
+ }
1244
+ return unsubAll;
1245
+ }
1246
+ //#region emit
1088
1247
  /**
1089
1248
  * Emits an event on this instance.
1090
- * ⚠️ Needs `publicEmit` to be set to true in the NanoEmitter constructor or super() call!
1249
+ * - ⚠️ Needs `publicEmit` to be set to true in the NanoEmitter constructor or super() call!
1091
1250
  * @param event The event to emit
1092
1251
  * @param args The arguments to pass to the event listeners
1093
1252
  * @returns Returns true if `publicEmit` is true and the event was emitted successfully
@@ -1099,6 +1258,7 @@ var NanoEmitter = class {
1099
1258
  }
1100
1259
  return false;
1101
1260
  }
1261
+ //#region unsubscribeAll
1102
1262
  /** Unsubscribes all event listeners from this instance */
1103
1263
  unsubscribeAll() {
1104
1264
  for (const unsub of this.eventUnsubscribes)