فهرست منبع

feat: DataStoreSerializer methods for partial de-/serialization

Sv443 1 ماه پیش
والد
کامیت
89d7970
3فایلهای تغییر یافته به همراه112 افزوده شده و 18 حذف شده
  1. 5 0
      .changeset/chilly-needles-trade.md
  2. 55 5
      docs.md
  3. 52 13
      lib/DataStoreSerializer.ts

+ 5 - 0
.changeset/chilly-needles-trade.md

@@ -0,0 +1,5 @@
+---
+"@sv443-network/userutils": minor
+---
+
+Added `DataStoreSerializer` methods `serializePartial()` and `deserializePartial()` for partial data imports and exports

+ 55 - 5
docs.md

@@ -1481,8 +1481,13 @@ The options object has the following properties:
 
 ### Methods:
 #### `DataStoreSerializer.serialize()`
-Signature: `serialize(): Promise<string>`  
-Serializes all DataStore instances passed in the constructor and returns the serialized data as a JSON string.  
+Signature: `serialize(useEncoding?: boolean, stringified?: boolean): Promise<string | SerializedDataStore[]>`  
+Serializes all DataStore instances passed in the constructor and returns the serialized data as a JSON string by deafault.  
+If `useEncoding` is set to `true` (default), the data will be encoded using the `encodeData` function set on the DataStore instance.  
+If `stringified` is set to `true` (default), the serialized data will be returned as a stringified JSON array, otherwise the unencoded objects will be returned in an array.  
+  
+If you need a partial export, use the method [`DataStoreSerializer.serializePartial()`](#datastoreserializerserializepartial) instead.  
+  
 <details><summary>Click to view the structure of the returned data.</summary>  
 
 ```jsonc
@@ -1495,7 +1500,11 @@ Serializes all DataStore instances passed in the constructor and returns the ser
     "checksum": "420deadbeef69",                    // property will be missing if addChecksum is set to false
   },
   {
-    // ...
+    "id": "bar-data",
+    "data": "{\"foo\":\"hello\",\"bar\":\"world\"}", // for unencoded stores, the data will be a stringified JSON object
+    "formatVersion": 1,
+    "encoded": false,
+    "checksum": "69beefdead420"
   }
 ]
 ```
@@ -1503,16 +1512,49 @@ Serializes all DataStore instances passed in the constructor and returns the ser
 
 <br>
 
+#### `DataStoreSerializer.serializePartial()`
+Signature: `serializePartial(stores: string[] | ((id: string) => boolean), useEncoding?: boolean, stringified?: boolean): Promise<string | SerializedDataStore[]>`  
+Serializes only the DataStore instances that have an ID that is included in the `stores` array.  
+  
+The `stores` argument can be an array containing the IDs of the DataStore instances, or a function that takes each ID as an argument and returns a boolean, indicating whether the store should be serialized.  
+If `useEncoding` is set to `true` (default), the data will be encoded using the `encodeData` function set on the DataStore instance.  
+If `stringified` is set to `true` (default), the serialized data will be returned as a stringified JSON array, otherwise the unencoded objects will be returned in an array.  
+  
+For more information or to export all DataStore instances, refer to the method [`DataStoreSerializer.serialize()`](#datastoreserializerserialize)
+
+<br>
+
 #### `DataStoreSerializer.deserialize()`
-Signature: `deserialize(data: string): Promise<void>`  
-Deserializes the given string that was created with `serialize()` and imports the contained data each DataStore instance.  
+Signature: `deserialize(data: string | SerializedDataStore[]): Promise<void>`  
+Deserializes the given string or array of serialized DataStores that was created with `serialize()` or `serializePartial()` and imports the contained data into each matching DataStore instance.  
+In the process of importing the data, the migrations will be run, if the `formatVersion` property is lower than the one set on the DataStore instance.  
+  
+The `data` parameter can be the data as a string or an array of serialized DataStores, as returned by the `serialize()` or `serializePartial()` methods.  
+  
+If `ensureIntegrity` is set to `true` and the checksum doesn't match, a [`ChecksumMismatchError`](#checksummismatcherror) will be thrown.  
+If `ensureIntegrity` is set to `false`, the checksum check will be skipped entirely.  
+If the `checksum` property is missing on the imported data, the checksum check will also be skipped.  
+If `encoded` is set to `true`, the data will be decoded using the `decodeData` function set on the DataStore instance.  
+  
+For only importing a subset of the serialized data, use the method [`DataStoreSerializer.deserializePartial()`](#datastoreserializerdeserializepartial) instead.
+
+<br>
+
+#### `DataStoreSerializer.deserializePartial()`
+Signature: `deserializePartial(stores: string[] | ((id: string) => boolean), data: string | SerializedDataStore[]): Promise<void>`  
+Deserializes only the DataStore instances that have an ID that is included in the `stores` array.  
 In the process of importing the data, the migrations will be run, if the `formatVersion` property is lower than the one set on the DataStore instance.  
   
+The `stores` parameter can be an array containing the IDs of the DataStore instances, or a function that takes each ID as an argument and returns a boolean, indicating whether the store should be deserialized.  
+The `data` parameter can be the data as a string or an array of serialized DataStores, as returned by the `serialize()` or `serializePartial()` methods.  
+  
 If `ensureIntegrity` is set to `true` and the checksum doesn't match, a [`ChecksumMismatchError`](#checksummismatcherror) will be thrown.  
 If `ensureIntegrity` is set to `false`, the checksum check will be skipped entirely.  
 If the `checksum` property is missing on the imported data, the checksum check will also be skipped.  
 If `encoded` is set to `true`, the data will be decoded using the `decodeData` function set on the DataStore instance.  
   
+If you want to import all serialized data, refer to the method [`DataStoreSerializer.deserialize()`](#datastoreserializerdeserialize)
+
 <br>
 
 #### `DataStoreSerializer.loadStoresData()`
@@ -1650,6 +1692,14 @@ async function resetMyDataPls() {
   // reset the data of all stores in both the cache and the persistent storage
   await serializer.resetStoresData();
 }
+
+async function exportOnlyFoo() {
+  // with the `serializePartial()` method, you can export only the data of specific stores:
+  const serializedExample1 = await serializer.serializePartial(["foo-data"]);
+
+  // or using a matcher function:
+  const serializedExample2 = await serializer.serializePartial((id) => id.startsWith("foo"));
+}
 ```
 </details>
 

+ 52 - 13
lib/DataStoreSerializer.ts

@@ -11,7 +11,7 @@ import type { DataStore } from "./DataStore.js";
 export type DataStoreSerializerOptions = {
   /** Whether to add a checksum to the exported data */
   addChecksum?: boolean;
-  /** Whether to ensure the integrity of the data when importing it (unless the checksum property doesn't exist) */
+  /** Whether to ensure the integrity of the data when importing it by throwing an error (doesn't throw when the checksum property doesn't exist) */
   ensureIntegrity?: boolean;
 };
 
@@ -25,7 +25,7 @@ export type SerializedDataStore = {
   formatVersion: number;
   /** Whether the data is encoded */
   encoded: boolean;
-  /** The checksum of the data - key is not present for data without a checksum */
+  /** The checksum of the data - key is not present when `addChecksum` is `false` */
   checksum?: string;
 };
 
@@ -67,26 +67,36 @@ export class DataStoreSerializer {
   }
 
   /**
-   * Serializes the data stores into a string.  
+   * Serializes only a subset of the data stores into a string.  
+   * @param stores An array of store IDs or functions that take a store ID and return a boolean
    * @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>;
+  public async serializePartial(stores: string[] | ((id: string) => boolean), useEncoding?: boolean, stringified?: true): Promise<string>;
   /**
-   * Serializes the data stores into a string.  
+   * Serializes only a subset of the data stores into a string.  
+   * @param stores An array of store IDs or functions that take a store ID and return a boolean
    * @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[]>;
+  public async serializePartial(stores: string[] | ((id: string) => boolean), useEncoding?: boolean, stringified?: false): Promise<SerializedDataStore[]>;
   /**
-   * Serializes the data stores into a string.  
+   * Serializes only a subset of the data stores into a string.  
+   * @param stores An array of store IDs or functions that take a store ID and return a boolean
    * @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[]> {
+  public async serializePartial(stores: string[] | ((id: string) => boolean), useEncoding?: boolean, stringified?: boolean): Promise<string | SerializedDataStore[]>;
+  /**
+   * Serializes only a subset of the data stores into a string.  
+   * @param stores An array of store IDs or functions that take a store ID and return a boolean
+   * @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 serializePartial(stores: string[] | ((id: string) => boolean), useEncoding = true, stringified = true): Promise<string | SerializedDataStore[]> {
     const serData: SerializedDataStore[] = [];
 
-    for(const storeInst of this.stores) {
+    for(const storeInst of this.stores.filter(s => typeof stores === "function" ? stores(s.id) : stores.includes(s.id))) {
       const data = useEncoding && storeInst.encodingEnabled()
         ? await storeInst.encodeData(JSON.stringify(storeInst.getData()))
         : JSON.stringify(storeInst.getData());
@@ -106,16 +116,37 @@ export class DataStoreSerializer {
   }
 
   /**
-   * Deserializes the data exported via {@linkcode serialize()} and imports it into the DataStore instances.  
+   * 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[]> {
+    return this.serializePartial(this.stores.map(s => s.id), useEncoding, stringified);
+  }
+
+  /**
+   * Deserializes the data exported via {@linkcode serialize()} and imports only a subset into the DataStore instances.  
    * Also triggers the migration process if the data format has changed.
    */
-  public async deserialize(serializedData: string | SerializedDataStore[]): Promise<void> {
-    const deserStores: SerializedDataStore[] = typeof serializedData === "string" ? JSON.parse(serializedData) : serializedData;
+  public async deserializePartial(stores: string[] | ((id: string) => boolean), data: string | SerializedDataStore[]): Promise<void> {
+    const deserStores: SerializedDataStore[] = typeof data === "string" ? JSON.parse(data) : data;
 
     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) {
+    for(const storeData of deserStores.filter(s => typeof stores === "function" ? stores(s.id) : stores.includes(s.id))) {
       const storeInst = this.stores.find(s => s.id === storeData.id);
       if(!storeInst)
         throw new Error(`DataStore instance with ID "${storeData.id}" not found! Make sure to provide it in the DataStoreSerializer constructor.`);
@@ -137,6 +168,14 @@ export class DataStoreSerializer {
     }
   }
 
+  /**
+   * Deserializes the data exported via {@linkcode serialize()} and imports the data into all matching DataStore instances.  
+   * Also triggers the migration process if the data format has changed.
+   */
+  public async deserialize(data: string | SerializedDataStore[]): Promise<void> {
+    return this.deserializePartial(this.stores.map(s => s.id), data);
+  }
+
   /**
    * Loads the persistent data of the DataStore instances into the in-memory cache.  
    * Also triggers the migration process if the data format has changed.