|
@@ -3,7 +3,10 @@
|
|
|
* This module contains the DataStoreSerializer class, which allows you to import and export serialized DataStore data - [see the documentation for more info](https://github.com/Sv443-Network/UserUtils/blob/main/docs.md#datastoreserializer)
|
|
|
*/
|
|
|
|
|
|
-import { getUnsafeWindow, computeHash, type DataStore } from "./index.js";
|
|
|
+import { computeHash } from "./crypto.js";
|
|
|
+import { getUnsafeWindow } from "./dom.js";
|
|
|
+import { ChecksumMismatchError } from "./errors.js";
|
|
|
+import type { DataStore } from "./DataStore.js";
|
|
|
|
|
|
export type DataStoreSerializerOptions = {
|
|
|
/** Whether to add a checksum to the exported data */
|
|
@@ -63,40 +66,54 @@ export class DataStoreSerializer {
|
|
|
return computeHash(input, "SHA-256");
|
|
|
}
|
|
|
|
|
|
- /** Serializes a DataStore instance */
|
|
|
- protected async serializeStore(storeInst: DataStore): Promise<SerializedDataStore> {
|
|
|
- const data = storeInst.encodingEnabled()
|
|
|
- ? await storeInst.encodeData(JSON.stringify(storeInst.getData()))
|
|
|
- : JSON.stringify(storeInst.getData());
|
|
|
- const checksum = this.options.addChecksum
|
|
|
- ? await this.calcChecksum(data)
|
|
|
- : undefined;
|
|
|
-
|
|
|
- return {
|
|
|
- id: storeInst.id,
|
|
|
- data,
|
|
|
- formatVersion: storeInst.formatVersion,
|
|
|
- encoded: storeInst.encodingEnabled(),
|
|
|
- checksum,
|
|
|
- };
|
|
|
- }
|
|
|
-
|
|
|
- /** Serializes the data stores into a string */
|
|
|
- public async serialize(): Promise<string> {
|
|
|
+ /**
|
|
|
+ * Serializes the data stores into a string.
|
|
|
+ * @param useEncoding Whether to encode the data using each DataStore's `encodeData()` method
|
|
|
+ * @param stringified Whether to return the result as a string or as an array of `SerializedDataStore` objects
|
|
|
+ */
|
|
|
+ public async serialize(useEncoding: boolean, stringified: true): Promise<string>;
|
|
|
+ /**
|
|
|
+ * Serializes the data stores into a string.
|
|
|
+ * @param useEncoding Whether to encode the data using each DataStore's `encodeData()` method
|
|
|
+ * @param stringified Whether to return the result as a string or as an array of `SerializedDataStore` objects
|
|
|
+ */
|
|
|
+ public async serialize(useEncoding: boolean, stringified: false): Promise<SerializedDataStore[]>;
|
|
|
+ /**
|
|
|
+ * Serializes the data stores into a string.
|
|
|
+ * @param useEncoding Whether to encode the data using each DataStore's `encodeData()` method
|
|
|
+ * @param stringified Whether to return the result as a string or as an array of `SerializedDataStore` objects
|
|
|
+ */
|
|
|
+ public async serialize(useEncoding = true, stringified = true): Promise<string | SerializedDataStore[]> {
|
|
|
const serData: SerializedDataStore[] = [];
|
|
|
|
|
|
- for(const store of this.stores)
|
|
|
- serData.push(await this.serializeStore(store));
|
|
|
+ for(const storeInst of this.stores) {
|
|
|
+ const data = useEncoding && storeInst.encodingEnabled()
|
|
|
+ ? await storeInst.encodeData(JSON.stringify(storeInst.getData()))
|
|
|
+ : JSON.stringify(storeInst.getData());
|
|
|
+
|
|
|
+ serData.push({
|
|
|
+ id: storeInst.id,
|
|
|
+ data,
|
|
|
+ formatVersion: storeInst.formatVersion,
|
|
|
+ encoded: useEncoding && storeInst.encodingEnabled(),
|
|
|
+ checksum: this.options.addChecksum
|
|
|
+ ? await this.calcChecksum(data)
|
|
|
+ : undefined,
|
|
|
+ });
|
|
|
+ }
|
|
|
|
|
|
- return JSON.stringify(serData);
|
|
|
+ return stringified ? JSON.stringify(serData) : serData;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Deserializes the data exported via {@linkcode serialize()} and imports it into the DataStore instances.
|
|
|
* Also triggers the migration process if the data format has changed.
|
|
|
*/
|
|
|
- public async deserialize(serializedData: string): Promise<void> {
|
|
|
- const deserStores: SerializedDataStore[] = JSON.parse(serializedData);
|
|
|
+ public async deserialize(serializedData: string | SerializedDataStore[]): Promise<void> {
|
|
|
+ const deserStores: SerializedDataStore[] = typeof serializedData === "string" ? JSON.parse(serializedData) : serializedData;
|
|
|
+
|
|
|
+ if(!Array.isArray(deserStores) || !deserStores.every(DataStoreSerializer.isSerializedDataStore))
|
|
|
+ throw new TypeError("Invalid serialized data format! Expected an array of SerializedDataStore objects.");
|
|
|
|
|
|
for(const storeData of deserStores) {
|
|
|
const storeInst = this.stores.find(s => s.id === storeData.id);
|
|
@@ -106,7 +123,7 @@ export class DataStoreSerializer {
|
|
|
if(this.options.ensureIntegrity && typeof storeData.checksum === "string") {
|
|
|
const checksum = await this.calcChecksum(storeData.data);
|
|
|
if(checksum !== storeData.checksum)
|
|
|
- throw new Error(`Checksum mismatch for DataStore with ID "${storeData.id}"!\nExpected: ${storeData.checksum}\nHas: ${checksum}`);
|
|
|
+ throw new ChecksumMismatchError(`Checksum mismatch for DataStore with ID "${storeData.id}"!\nExpected: ${storeData.checksum}\nHas: ${checksum}`);
|
|
|
}
|
|
|
|
|
|
const decodedData = storeData.encoded && storeInst.encodingEnabled()
|
|
@@ -146,4 +163,9 @@ export class DataStoreSerializer {
|
|
|
public async deleteStoresData(): Promise<PromiseSettledResult<void>[]> {
|
|
|
return Promise.allSettled(this.stores.map(store => store.deleteData()));
|
|
|
}
|
|
|
+
|
|
|
+ /** Checks if a given value is a SerializedDataStore object */
|
|
|
+ public static isSerializedDataStore(obj: unknown): obj is SerializedDataStore {
|
|
|
+ return typeof obj === "object" && obj !== null && "id" in obj && "data" in obj && "formatVersion" in obj && "encoded" in obj;
|
|
|
+ }
|
|
|
}
|