@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.
@@ -83,6 +83,7 @@ __export(lib_exports, {
83
83
  joinArrayReadable: () => joinArrayReadable,
84
84
  lightenColor: () => lightenColor,
85
85
  mapRange: () => mapRange,
86
+ overflowVal: () => overflowVal,
86
87
  pauseFor: () => pauseFor,
87
88
  pureObj: () => pureObj,
88
89
  randRange: () => randRange,
@@ -92,6 +93,7 @@ __export(lib_exports, {
92
93
  randomizeArray: () => randomizeArray,
93
94
  rgbToHex: () => rgbToHex,
94
95
  roundFixed: () => roundFixed,
96
+ scheduleExit: () => scheduleExit,
95
97
  secsToTimeStr: () => secsToTimeStr,
96
98
  setImmediateInterval: () => setImmediateInterval,
97
99
  setImmediateTimeoutLoop: () => setImmediateTimeoutLoop,
@@ -145,6 +147,19 @@ function mapRange(value, range1min, range1max, range2min, range2max) {
145
147
  return value * (range2max / range1max);
146
148
  return (value - range1min) * ((range2max - range2min) / (range1max - range1min)) + range2min;
147
149
  }
150
+ function overflowVal(value, minOrMax, max) {
151
+ const min = typeof max === "number" ? minOrMax : 0;
152
+ max = typeof max === "number" ? max : minOrMax;
153
+ if (min > max)
154
+ throw new RangeError(`Parameter "min" can't be bigger than "max"`);
155
+ if (isNaN(value) || isNaN(min) || isNaN(max) || !isFinite(value) || !isFinite(min) || !isFinite(max))
156
+ return NaN;
157
+ if (value >= min && value <= max)
158
+ return value;
159
+ const range = max - min + 1;
160
+ const wrappedValue = ((value - min) % range + range) % range + min;
161
+ return wrappedValue;
162
+ }
148
163
  function randRange(...args) {
149
164
  let min, max, enhancedEntropy = false;
150
165
  if (typeof args[0] === "number" && typeof args[1] === "number")
@@ -240,10 +255,8 @@ function darkenColor(color, percent, upperCase = false) {
240
255
  return rgbToHex(r, g, b, a, color.startsWith("#"), upperCase);
241
256
  else if (color.startsWith("rgba"))
242
257
  return `rgba(${r}, ${g}, ${b}, ${a ?? NaN})`;
243
- else if (color.startsWith("rgb"))
244
- return `rgb(${r}, ${g}, ${b})`;
245
258
  else
246
- throw new TypeError("Unsupported color format");
259
+ return `rgb(${r}, ${g}, ${b})`;
247
260
  }
248
261
  function hexToRgb(hex) {
249
262
  hex = (hex.startsWith("#") ? hex.slice(1) : hex).trim();
@@ -276,22 +289,22 @@ function atoab(str) {
276
289
  return Uint8Array.from(atob(str), (c) => c.charCodeAt(0));
277
290
  }
278
291
  async function compress(input, compressionFormat, outputType = "string") {
279
- const byteArray = input instanceof ArrayBuffer ? input : new TextEncoder().encode((input == null ? void 0 : input.toString()) ?? String(input));
292
+ const byteArray = input instanceof Uint8Array ? input : new TextEncoder().encode((input == null ? void 0 : input.toString()) ?? String(input));
280
293
  const comp = new CompressionStream(compressionFormat);
281
294
  const writer = comp.writable.getWriter();
282
295
  writer.write(byteArray);
283
296
  writer.close();
284
- const buf = await new Response(comp.readable).arrayBuffer();
285
- return outputType === "arrayBuffer" ? buf : abtoa(buf);
297
+ const uintArr = new Uint8Array(await new Response(comp.readable).arrayBuffer());
298
+ return outputType === "arrayBuffer" ? uintArr : abtoa(uintArr);
286
299
  }
287
300
  async function decompress(input, compressionFormat, outputType = "string") {
288
- const byteArray = input instanceof ArrayBuffer ? input : atoab((input == null ? void 0 : input.toString()) ?? String(input));
301
+ const byteArray = input instanceof Uint8Array ? input : atoab((input == null ? void 0 : input.toString()) ?? String(input));
289
302
  const decomp = new DecompressionStream(compressionFormat);
290
303
  const writer = decomp.writable.getWriter();
291
304
  writer.write(byteArray);
292
305
  writer.close();
293
- const buf = await new Response(decomp.readable).arrayBuffer();
294
- return outputType === "arrayBuffer" ? buf : new TextDecoder().decode(buf);
306
+ const uintArr = new Uint8Array(await new Response(decomp.readable).arrayBuffer());
307
+ return outputType === "arrayBuffer" ? uintArr : new TextDecoder().decode(uintArr);
295
308
  }
296
309
  async function computeHash(input, algorithm = "SHA-256") {
297
310
  let data;
@@ -400,6 +413,18 @@ function setImmediateTimeoutLoop(callback, interval, signal) {
400
413
  signal == null ? void 0 : signal.addEventListener("abort", cleanup);
401
414
  loop();
402
415
  }
416
+ function scheduleExit(code = 0, timeout = 0) {
417
+ if (timeout < 0)
418
+ throw new TypeError("Timeout must be a non-negative number");
419
+ let exit;
420
+ if (typeof process !== "undefined" && "exit" in process)
421
+ exit = () => process.exit(code);
422
+ else if (typeof Deno !== "undefined" && "exit" in Deno)
423
+ exit = () => Deno.exit(code);
424
+ else
425
+ throw new Error("Cannot exit the process, no exit method available");
426
+ setTimeout(exit, timeout);
427
+ }
403
428
 
404
429
  // lib/text.ts
405
430
  function autoPlural(term, num, pluralType = "auto") {
@@ -525,7 +550,14 @@ var DataStore = class {
525
550
  decodeData;
526
551
  compressionFormat = "deflate-raw";
527
552
  engine;
553
+ options;
554
+ /**
555
+ * Whether all first-init checks should be done.
556
+ * This includes migrating the internal DataStore format, migrating data from the UserUtils format, and anything similar.
557
+ * 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.
558
+ */
528
559
  firstInit = true;
560
+ /** In-memory cached copy of the data that is saved in persistent storage used for synchronous read access. */
529
561
  cachedData;
530
562
  migrations;
531
563
  migrateIds = [];
@@ -533,13 +565,13 @@ var DataStore = class {
533
565
  * Creates an instance of DataStore to manage a sync & async database that is cached in memory and persistently saved across sessions.
534
566
  * Supports migrating data from older versions to newer ones and populating the cache with default data if no persistent data is found.
535
567
  *
536
- * - ⚠️ Requires the directives `@grant GM.getValue` and `@grant GM.setValue` if the storageMethod is left as the default of `"GM"`
537
568
  * - ⚠️ Make sure to call {@linkcode loadData()} at least once after creating an instance, or the returned data will be the same as `options.defaultData`
538
569
  *
539
570
  * @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.)
540
571
  * @param opts The options for this DataStore instance
541
572
  */
542
573
  constructor(opts) {
574
+ var _a;
543
575
  this.id = opts.id;
544
576
  this.formatVersion = opts.formatVersion;
545
577
  this.defaultData = opts.defaultData;
@@ -550,8 +582,9 @@ var DataStore = class {
550
582
  this.encodeData = opts.encodeData;
551
583
  this.decodeData = opts.decodeData;
552
584
  this.engine = typeof opts.engine === "function" ? opts.engine() : opts.engine;
585
+ this.options = opts;
553
586
  if (typeof opts.compressionFormat === "undefined")
554
- opts.compressionFormat = "deflate-raw";
587
+ opts.compressionFormat = ((_a = opts.encodeData) == null ? void 0 : _a[0]) ?? "deflate-raw";
555
588
  if (typeof opts.compressionFormat === "string") {
556
589
  this.encodeData = [opts.compressionFormat, async (data) => await compress(data, opts.compressionFormat, "string")];
557
590
  this.decodeData = [opts.compressionFormat, async (data) => await compress(data, opts.compressionFormat, "string")];
@@ -586,9 +619,14 @@ var DataStore = class {
586
619
  promises.push(this.engine.setValue(newKey, value));
587
620
  promises.push(this.engine.deleteValue(oldKey));
588
621
  };
589
- oldData && migrateFmt(`_uucfg-${this.id}`, `__ds-${this.id}-dat`, oldData);
590
- !isNaN(oldVer) && migrateFmt(`_uucfgver-${this.id}`, `__ds-${this.id}-ver`, oldVer);
591
- typeof oldEnc === "boolean" && migrateFmt(`_uucfgenc-${this.id}`, `__ds-${this.id}-enc`, oldEnc === true ? this.compressionFormat : null);
622
+ if (oldData)
623
+ migrateFmt(`_uucfg-${this.id}`, `__ds-${this.id}-dat`, oldData);
624
+ if (!isNaN(oldVer))
625
+ migrateFmt(`_uucfgver-${this.id}`, `__ds-${this.id}-ver`, oldVer);
626
+ if (typeof oldEnc === "boolean")
627
+ migrateFmt(`_uucfgenc-${this.id}`, `__ds-${this.id}-enf`, oldEnc === true ? Boolean(this.compressionFormat) || null : null);
628
+ else
629
+ promises.push(this.engine.setValue(`__ds-${this.id}-enf`, this.compressionFormat));
592
630
  await Promise.allSettled(promises);
593
631
  }
594
632
  await this.engine.setValue("__ds_fmt_ver", dsFmtVer);
@@ -598,21 +636,22 @@ var DataStore = class {
598
636
  await this.migrateId(this.migrateIds);
599
637
  this.migrateIds = [];
600
638
  }
601
- const gmData = await this.engine.getValue(`__ds-${this.id}-dat`, JSON.stringify(this.defaultData));
602
- let gmFmtVer = Number(await this.engine.getValue(`__ds-${this.id}-ver`, NaN));
603
- if (typeof gmData !== "string") {
639
+ const storedData = await this.engine.getValue(`__ds-${this.id}-dat`, JSON.stringify(this.defaultData));
640
+ let storedFmtVer = Number(await this.engine.getValue(`__ds-${this.id}-ver`, NaN));
641
+ if (typeof storedData !== "string") {
604
642
  await this.saveDefaultData();
605
643
  return { ...this.defaultData };
606
644
  }
607
- const isEncoded = Boolean(await this.engine.getValue(`__ds-${this.id}-enc`, false));
645
+ const encodingFmt = String(await this.engine.getValue(`__ds-${this.id}-enf`, null));
646
+ const isEncoded = encodingFmt !== "null" && encodingFmt !== "false";
608
647
  let saveData = false;
609
- if (isNaN(gmFmtVer)) {
610
- await this.engine.setValue(`__ds-${this.id}-ver`, gmFmtVer = this.formatVersion);
648
+ if (isNaN(storedFmtVer)) {
649
+ await this.engine.setValue(`__ds-${this.id}-ver`, storedFmtVer = this.formatVersion);
611
650
  saveData = true;
612
651
  }
613
- let parsed = await this.engine.deserializeData(gmData, isEncoded);
614
- if (gmFmtVer < this.formatVersion && this.migrations)
615
- parsed = await this.runMigrations(parsed, gmFmtVer);
652
+ let parsed = await this.engine.deserializeData(storedData, isEncoded);
653
+ if (storedFmtVer < this.formatVersion && this.migrations)
654
+ parsed = await this.runMigrations(parsed, storedFmtVer);
616
655
  if (saveData)
617
656
  await this.setData(parsed);
618
657
  return this.cachedData = this.engine.deepCopy(parsed);
@@ -632,12 +671,11 @@ var DataStore = class {
632
671
  /** Saves the data synchronously to the in-memory cache and asynchronously to the persistent storage */
633
672
  setData(data) {
634
673
  this.cachedData = data;
635
- const useEncoding = this.encodingEnabled();
636
674
  return new Promise(async (resolve) => {
637
675
  await Promise.allSettled([
638
- this.engine.setValue(`__ds-${this.id}-dat`, await this.engine.serializeData(data, useEncoding)),
676
+ this.engine.setValue(`__ds-${this.id}-dat`, await this.engine.serializeData(data, this.encodingEnabled())),
639
677
  this.engine.setValue(`__ds-${this.id}-ver`, this.formatVersion),
640
- this.engine.setValue(`__ds-${this.id}-enc`, useEncoding)
678
+ this.engine.setValue(`__ds-${this.id}-enf`, this.compressionFormat)
641
679
  ]);
642
680
  resolve();
643
681
  });
@@ -645,30 +683,29 @@ var DataStore = class {
645
683
  /** Saves the default data passed in the constructor synchronously to the in-memory cache and asynchronously to persistent storage */
646
684
  async saveDefaultData() {
647
685
  this.cachedData = this.defaultData;
648
- const useEncoding = this.encodingEnabled();
649
686
  await Promise.allSettled([
650
- this.engine.setValue(`__ds-${this.id}-dat`, await this.engine.serializeData(this.defaultData, useEncoding)),
687
+ this.engine.setValue(`__ds-${this.id}-dat`, await this.engine.serializeData(this.defaultData, this.encodingEnabled())),
651
688
  this.engine.setValue(`__ds-${this.id}-ver`, this.formatVersion),
652
- this.engine.setValue(`__ds-${this.id}-enc`, useEncoding)
689
+ this.engine.setValue(`__ds-${this.id}-enf`, this.compressionFormat)
653
690
  ]);
654
691
  }
655
692
  /**
656
- * Call this method to clear all persistently stored data associated with this DataStore instance.
693
+ * Call this method to clear all persistently stored data associated with this DataStore instance, including the storage container (if supported by the DataStoreEngine).
657
694
  * The in-memory cache will be left untouched, so you may still access the data with {@linkcode getData()}
658
- * Calling {@linkcode loadData()} or {@linkcode setData()} after this method was called will recreate persistent storage with the cached or default data.
659
- *
660
- * - ⚠️ This requires the additional directive `@grant GM.deleteValue` if the storageMethod is left as the default of `"GM"`
695
+ * Calling {@linkcode loadData()} or {@linkcode setData()} after this method was called will recreate persistent storage with the cached or default data.
661
696
  */
662
697
  async deleteData() {
698
+ var _a, _b;
663
699
  await Promise.allSettled([
664
700
  this.engine.deleteValue(`__ds-${this.id}-dat`),
665
701
  this.engine.deleteValue(`__ds-${this.id}-ver`),
666
- this.engine.deleteValue(`__ds-${this.id}-enc`)
702
+ this.engine.deleteValue(`__ds-${this.id}-enf`)
667
703
  ]);
704
+ await ((_b = (_a = this.engine).deleteStorage) == null ? void 0 : _b.call(_a));
668
705
  }
669
706
  /** Returns whether encoding and decoding are enabled for this DataStore instance */
670
707
  encodingEnabled() {
671
- return Boolean(this.encodeData && this.decodeData);
708
+ return Boolean(this.encodeData && this.decodeData) && this.compressionFormat !== null || Boolean(this.compressionFormat);
672
709
  }
673
710
  //#region migrations
674
711
  /**
@@ -702,7 +739,7 @@ var DataStore = class {
702
739
  await Promise.allSettled([
703
740
  this.engine.setValue(`__ds-${this.id}-dat`, await this.engine.serializeData(newData)),
704
741
  this.engine.setValue(`__ds-${this.id}-ver`, lastFmtVer),
705
- this.engine.setValue(`__ds-${this.id}-enc`, this.encodingEnabled())
742
+ this.engine.setValue(`__ds-${this.id}-enf`, this.compressionFormat)
706
743
  ]);
707
744
  return this.cachedData = { ...newData };
708
745
  }
@@ -713,19 +750,24 @@ var DataStore = class {
713
750
  async migrateId(oldIds) {
714
751
  const ids = Array.isArray(oldIds) ? oldIds : [oldIds];
715
752
  await Promise.all(ids.map(async (id) => {
716
- const data = await this.engine.getValue(`__ds-${id}-dat`, JSON.stringify(this.defaultData));
717
- const fmtVer = Number(await this.engine.getValue(`__ds-${id}-ver`, NaN));
718
- const isEncoded = Boolean(await this.engine.getValue(`__ds-${id}-enc`, false));
753
+ const [data, fmtVer, isEncoded] = await (async () => {
754
+ const [d, f, e] = await Promise.all([
755
+ this.engine.getValue(`__ds-${id}-dat`, JSON.stringify(this.defaultData)),
756
+ this.engine.getValue(`__ds-${id}-ver`, NaN),
757
+ this.engine.getValue(`__ds-${id}-enf`, null)
758
+ ]);
759
+ return [d, Number(f), Boolean(e) && String(e) !== "null"];
760
+ })();
719
761
  if (data === void 0 || isNaN(fmtVer))
720
762
  return;
721
763
  const parsed = await this.engine.deserializeData(data, isEncoded);
722
764
  await Promise.allSettled([
723
765
  this.engine.setValue(`__ds-${this.id}-dat`, await this.engine.serializeData(parsed)),
724
766
  this.engine.setValue(`__ds-${this.id}-ver`, fmtVer),
725
- this.engine.setValue(`__ds-${this.id}-enc`, isEncoded),
767
+ this.engine.setValue(`__ds-${this.id}-enf`, this.compressionFormat),
726
768
  this.engine.deleteValue(`__ds-${id}-dat`),
727
769
  this.engine.deleteValue(`__ds-${id}-ver`),
728
- this.engine.deleteValue(`__ds-${id}-enc`)
770
+ this.engine.deleteValue(`__ds-${id}-enf`)
729
771
  ]);
730
772
  }));
731
773
  }
@@ -735,7 +777,11 @@ var DataStore = class {
735
777
  var DataStoreEngine = class {
736
778
  dataStoreOptions;
737
779
  // setDataStoreOptions() is called from inside the DataStore constructor to set this value
738
- /** Called by DataStore on creation, to pass its options */
780
+ constructor(options) {
781
+ if (options)
782
+ this.dataStoreOptions = options;
783
+ }
784
+ /** Called by DataStore on creation, to pass its options. Only call this if you are using this instance standalone! */
739
785
  setDataStoreOptions(dataStoreOptions) {
740
786
  this.dataStoreOptions = dataStoreOptions;
741
787
  }
@@ -743,6 +789,7 @@ var DataStoreEngine = class {
743
789
  /** Serializes the given object to a string, optionally encoded with `options.encodeData` if {@linkcode useEncoding} is set to true */
744
790
  async serializeData(data, useEncoding) {
745
791
  var _a, _b, _c, _d, _e;
792
+ this.ensureDataStoreOptions();
746
793
  const stringData = JSON.stringify(data);
747
794
  if (!useEncoding || !((_a = this.dataStoreOptions) == null ? void 0 : _a.encodeData) || !((_b = this.dataStoreOptions) == null ? void 0 : _b.decodeData))
748
795
  return stringData;
@@ -754,12 +801,20 @@ var DataStoreEngine = class {
754
801
  /** Deserializes the given string to a JSON object, optionally decoded with `options.decodeData` if {@linkcode useEncoding} is set to true */
755
802
  async deserializeData(data, useEncoding) {
756
803
  var _a, _b, _c;
804
+ this.ensureDataStoreOptions();
757
805
  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;
758
806
  if (decRes instanceof Promise)
759
807
  decRes = await decRes;
760
808
  return JSON.parse(decRes ?? data);
761
809
  }
762
810
  //#region misc api
811
+ /** Throws an error if the DataStoreOptions are not set or invalid */
812
+ ensureDataStoreOptions() {
813
+ if (!this.dataStoreOptions)
814
+ 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.");
815
+ if (!this.dataStoreOptions.id)
816
+ throw new DatedError("DataStoreEngine must be initialized with a valid DataStore ID");
817
+ }
763
818
  /**
764
819
  * Copies a JSON-compatible object and loses all its internal references in the process.
765
820
  * Uses [`structuredClone()`](https://developer.mozilla.org/en-US/docs/Web/API/structuredClone) if available, otherwise falls back to `JSON.parse(JSON.stringify(obj))`.
@@ -778,11 +833,11 @@ var BrowserStorageEngine = class extends DataStoreEngine {
778
833
  /**
779
834
  * Creates an instance of `BrowserStorageEngine`.
780
835
  *
781
- * ⚠️ Requires a DOM environment
782
- * ⚠️ Don't reuse this engine across multiple {@linkcode DataStore} instances
836
+ * - ⚠️ Requires a DOM environment
837
+ * - ⚠️ Don't reuse engines across multiple {@linkcode DataStore} instances
783
838
  */
784
839
  constructor(options) {
785
- super();
840
+ super(options == null ? void 0 : options.dataStoreOptions);
786
841
  this.options = {
787
842
  type: "localStorage",
788
843
  ...options
@@ -814,11 +869,11 @@ var FileStorageEngine = class extends DataStoreEngine {
814
869
  /**
815
870
  * Creates an instance of `FileStorageEngine`.
816
871
  *
817
- * ⚠️ Requires Node.js or Deno with Node compatibility
818
- * ⚠️ Don't reuse this engine across multiple {@linkcode DataStore} instances
872
+ * - ⚠️ Requires Node.js or Deno with Node compatibility (v1.31+)
873
+ * - ⚠️ Don't reuse engines across multiple {@linkcode DataStore} instances
819
874
  */
820
875
  constructor(options) {
821
- super();
876
+ super(options == null ? void 0 : options.dataStoreOptions);
822
877
  this.options = {
823
878
  filePath: (id) => `.ds-${id}`,
824
879
  ...options
@@ -827,28 +882,32 @@ var FileStorageEngine = class extends DataStoreEngine {
827
882
  //#region json file
828
883
  /** Reads the file contents */
829
884
  async readFile() {
830
- var _a, _b, _c;
885
+ var _a, _b, _c, _d;
886
+ this.ensureDataStoreOptions();
831
887
  try {
832
888
  if (!fs)
833
- fs = (await import("node:fs/promises")).default;
889
+ fs = (_a = await import("fs/promises")) == null ? void 0 : _a.default;
890
+ if (!fs)
891
+ throw new DatedError("FileStorageEngine requires Node.js or Deno with Node compatibility (v1.31+)", { cause: new Error("'node:fs/promises' module not available") });
834
892
  const path = typeof this.options.filePath === "string" ? this.options.filePath : this.options.filePath(this.dataStoreOptions.id);
835
893
  const data = await fs.readFile(path, "utf-8");
836
- if (!data)
837
- return void 0;
838
- 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);
894
+ 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;
839
895
  } catch {
840
896
  return void 0;
841
897
  }
842
898
  }
843
899
  /** Overwrites the file contents */
844
900
  async writeFile(data) {
845
- var _a, _b, _c;
901
+ var _a, _b, _c, _d;
902
+ this.ensureDataStoreOptions();
846
903
  try {
847
904
  if (!fs)
848
- fs = (await import("node:fs/promises")).default;
905
+ fs = (_a = await import("fs/promises")) == null ? void 0 : _a.default;
906
+ if (!fs)
907
+ throw new DatedError("FileStorageEngine requires Node.js or Deno with Node compatibility (v1.31+)", { cause: new Error("'node:fs/promises' module not available") });
849
908
  const path = typeof this.options.filePath === "string" ? this.options.filePath : this.options.filePath(this.dataStoreOptions.id);
850
909
  await fs.mkdir(path.slice(0, path.lastIndexOf("/")), { recursive: true });
851
- 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");
910
+ 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");
852
911
  } catch (err) {
853
912
  console.error("Error writing file:", err);
854
913
  }
@@ -882,6 +941,21 @@ var FileStorageEngine = class extends DataStoreEngine {
882
941
  delete data[name];
883
942
  await this.writeFile(data);
884
943
  }
944
+ /** Deletes the file that contains the data of this DataStore. */
945
+ async deleteStorage() {
946
+ var _a;
947
+ this.ensureDataStoreOptions();
948
+ try {
949
+ if (!fs)
950
+ fs = (_a = await import("fs/promises")) == null ? void 0 : _a.default;
951
+ if (!fs)
952
+ throw new DatedError("FileStorageEngine requires Node.js or Deno with Node compatibility (v1.31+)", { cause: new Error("'node:fs/promises' module not available") });
953
+ const path = typeof this.options.filePath === "string" ? this.options.filePath : this.options.filePath(this.dataStoreOptions.id);
954
+ await fs.unlink(path);
955
+ } catch (err) {
956
+ console.error("Error deleting file:", err);
957
+ }
958
+ }
885
959
  };
886
960
 
887
961
  // lib/DataStoreSerializer.ts
@@ -909,14 +983,16 @@ var DataStoreSerializer = class _DataStoreSerializer {
909
983
  * @param stringified Whether to return the result as a string or as an array of `SerializedDataStore` objects
910
984
  */
911
985
  async serializePartial(stores, useEncoding = true, stringified = true) {
986
+ var _a;
912
987
  const serData = [];
913
988
  for (const storeInst of this.stores.filter((s) => typeof stores === "function" ? stores(s.id) : stores.includes(s.id))) {
914
- const data = useEncoding && storeInst.encodingEnabled() ? await storeInst.encodeData[1](JSON.stringify(storeInst.getData())) : JSON.stringify(storeInst.getData());
989
+ const encoded = Boolean(useEncoding && storeInst.encodingEnabled() && ((_a = storeInst.encodeData) == null ? void 0 : _a[1]));
990
+ const data = encoded ? await storeInst.encodeData[1](JSON.stringify(storeInst.getData())) : JSON.stringify(storeInst.getData());
915
991
  serData.push({
916
992
  id: storeInst.id,
917
993
  data,
918
994
  formatVersion: storeInst.formatVersion,
919
- encoded: useEncoding && storeInst.encodingEnabled(),
995
+ encoded,
920
996
  checksum: this.options.addChecksum ? await this.calcChecksum(data) : void 0
921
997
  });
922
998
  }
@@ -1040,6 +1116,7 @@ var NanoEmitter = class {
1040
1116
  ...options
1041
1117
  };
1042
1118
  }
1119
+ //#region on
1043
1120
  /**
1044
1121
  * Subscribes to an event and calls the callback when it's emitted.
1045
1122
  * @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 "_")
@@ -1073,6 +1150,7 @@ var NanoEmitter = class {
1073
1150
  this.eventUnsubscribes.push(unsub);
1074
1151
  return unsubProxy;
1075
1152
  }
1153
+ //#region once
1076
1154
  /**
1077
1155
  * Subscribes to an event and calls the callback or resolves the Promise only once when it's emitted.
1078
1156
  * @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 "_")
@@ -1103,9 +1181,90 @@ var NanoEmitter = class {
1103
1181
  this.eventUnsubscribes.push(unsub);
1104
1182
  });
1105
1183
  }
1184
+ //#region onMulti
1185
+ /**
1186
+ * 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.
1187
+ * @param options An object or array of objects with the following properties:
1188
+ * `callback` (required) is the function that will be called when the conditions are met.
1189
+ *
1190
+ * 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.
1191
+ * If `signal` is provided, the subscription will be aborted when the given signal is aborted.
1192
+ *
1193
+ * If `oneOf` is used, the callback will be called when any of the matching events are emitted.
1194
+ * 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.
1195
+ * You may use a combination of the above two options, but at least one of them must be provided.
1196
+ *
1197
+ * @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.
1198
+ */
1199
+ onMulti(options) {
1200
+ const allUnsubs = [];
1201
+ const unsubAll = () => {
1202
+ for (const unsub of allUnsubs)
1203
+ unsub();
1204
+ allUnsubs.splice(0, allUnsubs.length);
1205
+ this.eventUnsubscribes = this.eventUnsubscribes.filter((u) => !allUnsubs.includes(u));
1206
+ };
1207
+ for (const opts of Array.isArray(options) ? options : [options]) {
1208
+ const optsWithDefaults = {
1209
+ allOf: [],
1210
+ oneOf: [],
1211
+ once: false,
1212
+ ...opts
1213
+ };
1214
+ const {
1215
+ oneOf,
1216
+ allOf,
1217
+ once,
1218
+ signal,
1219
+ callback
1220
+ } = optsWithDefaults;
1221
+ if (signal == null ? void 0 : signal.aborted)
1222
+ return unsubAll;
1223
+ const curEvtUnsubs = [];
1224
+ const checkUnsubAllEvt = (force = false) => {
1225
+ if (!(signal == null ? void 0 : signal.aborted) && !force)
1226
+ return;
1227
+ for (const unsub of curEvtUnsubs)
1228
+ unsub();
1229
+ curEvtUnsubs.splice(0, curEvtUnsubs.length);
1230
+ this.eventUnsubscribes = this.eventUnsubscribes.filter((u) => !curEvtUnsubs.includes(u));
1231
+ };
1232
+ for (const event of oneOf) {
1233
+ const unsub = this.events.on(event, (...args) => {
1234
+ checkUnsubAllEvt();
1235
+ callback(event, ...args);
1236
+ if (once)
1237
+ checkUnsubAllEvt(true);
1238
+ });
1239
+ curEvtUnsubs.push(unsub);
1240
+ }
1241
+ const allOfEmitted = /* @__PURE__ */ new Set();
1242
+ const checkAllOf = (event, ...args) => {
1243
+ checkUnsubAllEvt();
1244
+ allOfEmitted.add(event);
1245
+ if (allOfEmitted.size === allOf.length) {
1246
+ callback(event, ...args);
1247
+ if (once)
1248
+ checkUnsubAllEvt(true);
1249
+ }
1250
+ };
1251
+ for (const event of allOf) {
1252
+ const unsub = this.events.on(event, (...args) => {
1253
+ checkUnsubAllEvt();
1254
+ checkAllOf(event, ...args);
1255
+ });
1256
+ curEvtUnsubs.push(unsub);
1257
+ }
1258
+ if (oneOf.length === 0 && allOf.length === 0)
1259
+ throw new TypeError("NanoEmitter.onMulti(): Either `oneOf` or `allOf` or both must be provided in the options");
1260
+ allUnsubs.push(() => checkUnsubAllEvt(true));
1261
+ }
1262
+ return unsubAll;
1263
+ }
1264
+ //#region emit
1106
1265
  /**
1107
1266
  * Emits an event on this instance.
1108
- * ⚠️ Needs `publicEmit` to be set to true in the NanoEmitter constructor or super() call!
1267
+ * - ⚠️ Needs `publicEmit` to be set to true in the NanoEmitter constructor or super() call!
1109
1268
  * @param event The event to emit
1110
1269
  * @param args The arguments to pass to the event listeners
1111
1270
  * @returns Returns true if `publicEmit` is true and the event was emitted successfully
@@ -1117,6 +1276,7 @@ var NanoEmitter = class {
1117
1276
  }
1118
1277
  return false;
1119
1278
  }
1279
+ //#region unsubscribeAll
1120
1280
  /** Unsubscribes all event listeners from this instance */
1121
1281
  unsubscribeAll() {
1122
1282
  for (const unsub of this.eventUnsubscribes)
@@ -34,7 +34,7 @@ export type DataStoreOptions<TData extends DataStoreData> = Prettify<{
34
34
  * The engine middleware to use for persistent storage.
35
35
  * Create an instance of {@linkcode FileStorageEngine} (Node.js), {@linkcode BrowserStorageEngine} (DOM) or your own engine class that extends {@linkcode DataStoreEngine} and pass it here.
36
36
  *
37
- * ⚠️ Don't reuse the same engine instance for multiple DataStores, unless it explicitly supports it!
37
+ * - ⚠️ Don't reuse the same engine instance for multiple DataStores, unless it explicitly supports it!
38
38
  */
39
39
  engine: (() => DataStoreEngine<TData>) | DataStoreEngine<TData>;
40
40
  /**
@@ -57,26 +57,30 @@ export type DataStoreOptions<TData extends DataStoreData> = Prettify<{
57
57
  decodeData?: never;
58
58
  /**
59
59
  * The format to use for compressing the data. Defaults to `deflate-raw`. Explicitly set to `null` to store data uncompressed.
60
- * ⚠️ Use either this, or `encodeData` and `decodeData`, but not all three at a time!
60
+ * - ⚠️ Use either this property, or both `encodeData` and `decodeData`, but not all three!
61
61
  */
62
62
  compressionFormat?: CompressionFormat | null;
63
63
  } | {
64
64
  /**
65
65
  * Tuple of a compression format identifier and a function to use to encode the data prior to saving it in persistent storage.
66
- * If this is specified, `compressionFormat` can't be used. Also make sure to declare {@linkcode decodeData()} as well.
66
+ * Set the identifier to `null` or `"identity"` to indicate that no traditional compression is used.
67
+ *
68
+ * - ⚠️ If this is specified, `compressionFormat` can't be used. Also make sure to declare {@linkcode decodeData()} as well.
67
69
  *
68
70
  * You can make use of the [`compress()` function](https://github.com/Sv443-Network/CoreUtils/blob/main/docs.md#function-compress) here to make the data use up less space at the cost of a little bit of performance.
69
71
  * @param data The input data as a serialized object (JSON string)
70
72
  */
71
- encodeData: [format: LooseUnion<CompressionFormat>, encode: (data: string) => string | Promise<string>];
73
+ encodeData: [format: LooseUnion<CompressionFormat> | null, encode: (data: string) => string | Promise<string>];
72
74
  /**
73
75
  * Tuple of a compression format identifier and a function to use to decode the data after reading it from persistent storage.
74
- * If this is specified, `compressionFormat` can't be used. Also make sure to declare {@linkcode encodeData()} as well.
76
+ * Set the identifier to `null` or `"identity"` to indicate that no traditional compression is used.
77
+ *
78
+ * - ⚠️ If this is specified, `compressionFormat` can't be used. Also make sure to declare {@linkcode encodeData()} as well.
75
79
  *
76
80
  * You can make use of the [`decompress()` function](https://github.com/Sv443-Network/CoreUtils/blob/main/docs.md#function-decompress) here to make the data use up less space at the cost of a little bit of performance.
77
81
  * @returns The resulting data as a valid serialized object (JSON string)
78
82
  */
79
- decodeData: [format: LooseUnion<CompressionFormat>, decode: (data: string) => string | Promise<string>];
83
+ decodeData: [format: LooseUnion<CompressionFormat> | null, decode: (data: string) => string | Promise<string>];
80
84
  compressionFormat?: never;
81
85
  })>;
82
86
  /** Generic type that represents the serializable data structure saved in a {@linkcode DataStore} instance. */
@@ -100,9 +104,16 @@ export declare class DataStore<TData extends DataStoreData> {
100
104
  readonly defaultData: TData;
101
105
  readonly encodeData: DataStoreOptions<TData>["encodeData"];
102
106
  readonly decodeData: DataStoreOptions<TData>["decodeData"];
103
- readonly compressionFormat = "deflate-raw";
107
+ readonly compressionFormat: Exclude<DataStoreOptions<TData>["compressionFormat"], undefined>;
104
108
  readonly engine: DataStoreEngine<TData>;
109
+ options: DataStoreOptions<TData>;
110
+ /**
111
+ * Whether all first-init checks should be done.
112
+ * This includes migrating the internal DataStore format, migrating data from the UserUtils format, and anything similar.
113
+ * 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.
114
+ */
105
115
  protected firstInit: boolean;
116
+ /** In-memory cached copy of the data that is saved in persistent storage used for synchronous read access. */
106
117
  private cachedData;
107
118
  private migrations?;
108
119
  private migrateIds;
@@ -110,7 +121,6 @@ export declare class DataStore<TData extends DataStoreData> {
110
121
  * Creates an instance of DataStore to manage a sync & async database that is cached in memory and persistently saved across sessions.
111
122
  * Supports migrating data from older versions to newer ones and populating the cache with default data if no persistent data is found.
112
123
  *
113
- * - ⚠️ Requires the directives `@grant GM.getValue` and `@grant GM.setValue` if the storageMethod is left as the default of `"GM"`
114
124
  * - ⚠️ Make sure to call {@linkcode loadData()} at least once after creating an instance, or the returned data will be the same as `options.defaultData`
115
125
  *
116
126
  * @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.)
@@ -133,11 +143,9 @@ export declare class DataStore<TData extends DataStoreData> {
133
143
  /** Saves the default data passed in the constructor synchronously to the in-memory cache and asynchronously to persistent storage */
134
144
  saveDefaultData(): Promise<void>;
135
145
  /**
136
- * Call this method to clear all persistently stored data associated with this DataStore instance.
146
+ * Call this method to clear all persistently stored data associated with this DataStore instance, including the storage container (if supported by the DataStoreEngine).
137
147
  * The in-memory cache will be left untouched, so you may still access the data with {@linkcode getData()}
138
148
  * Calling {@linkcode loadData()} or {@linkcode setData()} after this method was called will recreate persistent storage with the cached or default data.
139
- *
140
- * - ⚠️ This requires the additional directive `@grant GM.deleteValue` if the storageMethod is left as the default of `"GM"`
141
149
  */
142
150
  deleteData(): Promise<void>;
143
151
  /** Returns whether encoding and decoding are enabled for this DataStore instance */