@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.
- package/CHANGELOG.md +39 -10
- package/README.md +52 -9
- package/dist/CoreUtils.cjs +219 -59
- package/dist/CoreUtils.min.cjs +2 -2
- package/dist/CoreUtils.min.mjs +2 -2
- package/dist/CoreUtils.min.umd.js +2 -2
- package/dist/CoreUtils.mjs +219 -59
- package/dist/CoreUtils.umd.js +219 -59
- package/dist/lib/DataStore.d.ts +19 -11
- package/dist/lib/DataStoreEngine.d.ts +35 -14
- package/dist/lib/NanoEmitter.d.ts +33 -1
- package/dist/lib/TieredCache.d.ts +1 -1
- package/dist/lib/crypto.d.ts +15 -15
- package/dist/lib/math.d.ts +4 -0
- package/dist/lib/misc.d.ts +8 -0
- package/package.json +35 -28
package/dist/CoreUtils.umd.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
|
285
|
-
return outputType === "arrayBuffer" ?
|
|
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
|
|
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
|
|
294
|
-
return outputType === "arrayBuffer" ?
|
|
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
|
-
|
|
590
|
-
|
|
591
|
-
|
|
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
|
|
602
|
-
let
|
|
603
|
-
if (typeof
|
|
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
|
|
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(
|
|
610
|
-
await this.engine.setValue(`__ds-${this.id}-ver`,
|
|
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(
|
|
614
|
-
if (
|
|
615
|
-
parsed = await this.runMigrations(parsed,
|
|
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,
|
|
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}-
|
|
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,
|
|
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}-
|
|
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}-
|
|
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}-
|
|
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
|
|
717
|
-
|
|
718
|
-
|
|
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}-
|
|
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}-
|
|
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
|
-
|
|
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
|
|
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
|
|
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("
|
|
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
|
-
|
|
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("
|
|
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 ((
|
|
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
|
|
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
|
|
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)
|
package/dist/lib/DataStore.d.ts
CHANGED
|
@@ -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
|
|
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
|
-
*
|
|
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
|
|
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
|
-
*
|
|
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
|
|
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
|
|
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 */
|