@sv443-network/userutils 8.0.2 → 8.1.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 CHANGED
@@ -1,5 +1,12 @@
1
1
  # @sv443-network/userutils
2
2
 
3
+ ## 8.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 6296529: Added new DataStoreSerializer methods `loadStoresData()`, `resetStoresData()` and `deleteStoresData()` for parallelized bulk operations on DataStore instances
8
+ - b0bce9c: Added DataStore method `migrateId()` to be able to migrate to a new ID
9
+
3
10
  ## 8.0.2
4
11
 
5
12
  ### Patch Changes
package/README.md CHANGED
@@ -186,8 +186,9 @@ Additionally, there are the following extra options:
186
186
 
187
187
  <br>
188
188
 
189
- #### Methods:
190
- `addListener<TElement = HTMLElement>(selector: string, options: SelectorListenerOptions): void`
189
+ ### Methods:
190
+ #### `SelectorObserver.addListener()`
191
+ Usage: `SelectorObserver.addListener<TElement = HTMLElement>(selector: string, options: SelectorListenerOptions): void`
191
192
  Adds a listener (specified in `options.listener`) for the given selector that will be called once the selector exists in the DOM. It will be passed the element(s) that match the selector as the only argument.
192
193
  The listener will be called immediately if the selector already exists in the DOM.
193
194
 
@@ -216,45 +217,53 @@ The listener will be called immediately if the selector already exists in the DO
216
217
 
217
218
  <br>
218
219
 
219
- `enable(immediatelyCheckSelectors?: boolean): boolean`
220
+ #### `SelectorObserver.enable()`
221
+ Usage: `SelectorObserver.enable(immediatelyCheckSelectors?: boolean): boolean`
220
222
  Enables the observation of the child elements for the first time or if it was disabled before.
221
223
  `immediatelyCheckSelectors` is set to true by default, which means all previously registered selectors will be checked. Set to false to only check them on the first detected mutation.
222
224
  Returns true if the observation was enabled, false if it was already enabled or the passed `baseElementSelector` couldn't be found.
223
225
 
224
226
  <br>
225
227
 
226
- `disable(): void`
228
+ #### `SelectorObserver.disable()`
229
+ Usage: `SelectorObserver.disable(): void`
227
230
  Disables the observation of the child elements.
228
231
  If selectors are currently being checked, the current selector will be finished before disabling.
229
232
 
230
233
  <br>
231
234
 
232
- `isEnabled(): boolean`
235
+ #### `SelectorObserver.isEnabled()`
236
+ Usage: `SelectorObserver.isEnabled(): boolean`
233
237
  Returns whether the observation of the child elements is currently enabled.
234
238
 
235
239
  <br>
236
240
 
237
- `clearListeners(): void`
241
+ #### `SelectorObserver.clearListeners()`
242
+ Usage: `SelectorObserver.clearListeners(): void`
238
243
  Removes all listeners for all selectors.
239
244
 
240
245
  <br>
241
246
 
242
- `removeAllListeners(selector: string): boolean`
247
+ #### `SelectorObserver.removeAllListeners()`
248
+ Usage: `SelectorObserver.removeAllListeners(selector: string): boolean`
243
249
  Removes all listeners for the given selector.
244
250
 
245
251
  <br>
246
252
 
247
- `removeListener(selector: string, options: SelectorListenerOptions): boolean`
253
+ #### `SelectorObserver.removeListener()`
254
+ Usage: `SelectorObserver.removeListener(selector: string, options: SelectorListenerOptions): boolean`
248
255
  Removes a specific listener for the given selector and options.
249
256
 
250
257
  <br>
251
258
 
252
- `getAllListeners(): Map<string, SelectorListenerOptions[]>`
259
+ #### `SelectorObserver.getAllListeners()`
260
+ Usage: `SelectorObserver.getAllListeners(): Map<string, SelectorListenerOptions[]>`
253
261
  Returns a Map of all selectors and their listeners.
254
262
 
255
263
  <br>
256
264
 
257
- `getListeners(selector: string): SelectorListenerOptions[] | undefined`
265
+ #### `SelectorObserver.getListeners()`
266
+ Usage: `SelectorObserver.getListeners(selector: string): SelectorListenerOptions[] | undefined`
258
267
  Returns all listeners for the given selector or undefined if there are none.
259
268
 
260
269
  <br>
@@ -995,7 +1004,7 @@ It combines the data of multiple DataStore instances into a single object that c
995
1004
  The options object has the following properties:
996
1005
  | Property | Description |
997
1006
  | :-- | :-- |
998
- | `id` | A unique internal identification string for this instance. If two DataStores share the same ID, they will overwrite each other's data. Choose wisely because if it is changed, the previously saved data will not be able to be loaded anymore. |
1007
+ | `id` | A unique internal identification string for this instance. If two DataStores share the same ID, they will overwrite each other's data, so it is recommended that you use a prefix that is unique to your project. |
999
1008
  | `defaultData` | The default data to use if no data is saved in persistent storage yet. Until the data is loaded from persistent storage, this will be the data returned by `getData()`. For TypeScript, the type of the data passed here is what will be used for all other methods of the instance. |
1000
1009
  | `formatVersion` | An incremental version of the data format. If the format of the data is changed in any way, this number should be incremented, in which case all necessary functions of the migrations dictionary will be run consecutively. Never decrement this number or skip numbers. |
1001
1010
  | `migrations?` | (Optional) A dictionary of functions that can be used to migrate data from older versions of the data to newer ones. The keys of the dictionary should be the format version that the functions can migrate to, from the previous whole integer value. The values should be functions that take the data in the old format and return the data in the new format. The functions will be run in order from the oldest to the newest version. If the current format version is not in the dictionary, no migrations will be run. |
@@ -1005,34 +1014,60 @@ The options object has the following properties:
1005
1014
 
1006
1015
  <br>
1007
1016
 
1008
- #### Methods:
1009
- `loadData(): Promise<TData>`
1017
+ ### Methods:
1018
+ #### `DataStore.loadData()`
1019
+ Usage: `loadData(): Promise<TData>`
1010
1020
  Asynchronously loads the data from persistent storage and returns it.
1011
1021
  If no data was saved in persistent storage before, the value of `options.defaultData` will be returned and written to persistent storage.
1012
- If the formatVersion of the saved data is lower than the current one and the `options.migrations` property is present, the data will be migrated to the latest format before the Promise resolves.
1013
-
1014
- `getData(): TData`
1022
+ If the formatVersion of the saved data is lower than the current one and the `options.migrations` property is present, the data will be migrated to the latest format before the Promise resolves.
1023
+
1024
+ <br>
1025
+
1026
+ #### `DataStore.getData()`
1027
+ Usage: `getData(): TData`
1015
1028
  Synchronously returns the current data that is stored in the internal cache.
1016
- If no data was loaded from persistent storage yet using `loadData()`, the value of `options.defaultData` will be returned.
1017
-
1018
- `setData(data: TData): Promise<void>`
1019
- Writes the given data synchronously to the internal cache and asynchronously to persistent storage.
1020
-
1021
- `saveDefaultData(): Promise<void>`
1022
- Writes the default data given in `options.defaultData` synchronously to the internal cache and asynchronously to persistent storage.
1023
-
1024
- `deleteData(): Promise<void>`
1029
+ If no data was loaded from persistent storage yet using `loadData()`, the value of `options.defaultData` will be returned.
1030
+
1031
+ <br>
1032
+
1033
+ #### `DataStore.setData()`
1034
+ Usage: `setData(data: TData): Promise<void>`
1035
+ Writes the given data synchronously to the internal cache and asynchronously to persistent storage.
1036
+
1037
+ <br>
1038
+
1039
+ #### `DataStore.saveDefaultData()`
1040
+ Usage: `saveDefaultData(): Promise<void>`
1041
+ Writes the default data given in `options.defaultData` synchronously to the internal cache and asynchronously to persistent storage.
1042
+
1043
+ <br>
1044
+
1045
+ #### `DataStore.deleteData()`
1046
+ Usage: `deleteData(): Promise<void>`
1025
1047
  Fully deletes the data from persistent storage only.
1026
1048
  The internal cache will be left untouched, so any subsequent calls to `getData()` will return the data that was last loaded.
1027
1049
  If `loadData()` or `setData()` are called after this, the persistent storage will be populated with the value of `options.defaultData` again.
1028
1050
  This is why you should either immediately repopulate the cache and persistent storage or the page should probably be reloaded or closed after this method is called.
1029
- ⚠️ If you want to use this method, the additional directive `@grant GM.deleteValue` is required.
1030
-
1031
- `runMigrations(oldData: any, oldFmtVer: number, resetOnError?: boolean): Promise<TData>`
1051
+ ⚠️ If you want to use this method, the additional directive `@grant GM.deleteValue` is required.
1052
+
1053
+ <br>
1054
+
1055
+ #### `DataStore.runMigrations()`
1056
+ Usage: `runMigrations(oldData: any, oldFmtVer: number, resetOnError?: boolean): Promise<TData>`
1032
1057
  Runs all necessary migration functions to migrate the given `oldData` to the latest format.
1033
- If `resetOnError` is set to `false`, the migration will be aborted if an error is thrown and no data will be committed. If it is set to `true` (default) and an error is encountered, it will be suppressed and the `defaultData` will be saved to persistent storage and returned.
1034
-
1035
- `encodingEnabled(): boolean`
1058
+ If `resetOnError` is set to `false`, the migration will be aborted if an error is thrown and no data will be committed. If it is set to `true` (default) and an error is encountered, it will be suppressed and the `defaultData` will be saved to persistent storage and returned.
1059
+
1060
+ <br>
1061
+
1062
+ #### `DataStore.migrateId()`
1063
+ Usage: `migrateId(oldIds: string | string[]): Promise<void>`
1064
+ Tries to migrate the currently saved persistent data from one or more old IDs to the ID set in the constructor.
1065
+ If no data exist for the old ID(s), nothing will be done, but some time may still pass trying to fetch the non-existent data.
1066
+
1067
+ <br>
1068
+
1069
+ #### `DataStore.encodingEnabled()`
1070
+ Usage: `encodingEnabled(): boolean`
1036
1071
  Returns `true` if both `options.encodeData` and `options.decodeData` are set, else `false`.
1037
1072
  Uses TypeScript's type guard notation for easier use in conditional statements.
1038
1073
 
@@ -1085,7 +1120,7 @@ const migrations = {
1085
1120
 
1086
1121
  // You probably want to export this instance (or helper functions) so you can use it anywhere in your script:
1087
1122
  export const manager = new DataStore({
1088
- /** A unique ID for this instance - choose wisely as changing it is not supported and will result in data loss! */
1123
+ /** A unique ID for this instance */
1089
1124
  id: "my-userscript-config",
1090
1125
  /** Default, initial and fallback data */
1091
1126
  defaultData,
@@ -1161,12 +1196,9 @@ The options object has the following properties:
1161
1196
 
1162
1197
  <br>
1163
1198
 
1164
- #### Methods:
1165
- `constructor(stores: DataStore[], options?: DataStoreSerializerOptions)`
1166
- Creates a new DataStoreSerializer instance with the given DataStore instances and options.
1167
- If no options are passed, the defaults will be used.
1168
-
1169
- `serialize(): Promise<string>`
1199
+ ### Methods:
1200
+ #### `DataStoreSerializer.serialize()`
1201
+ Usage: `serialize(): Promise<string>`
1170
1202
  Serializes all DataStore instances passed in the constructor and returns the serialized data as a JSON string.
1171
1203
  <details><summary>Click to view the structure of the returned data.</summary>
1172
1204
 
@@ -1185,15 +1217,65 @@ Serializes all DataStore instances passed in the constructor and returns the ser
1185
1217
  ]
1186
1218
  ```
1187
1219
  </details>
1188
-
1189
- `deserialize(data: string): Promise<void>`
1190
- Deserializes the given JSON string and imports the data into the DataStore instances.
1220
+
1221
+ <br>
1222
+
1223
+ #### `DataStoreSerializer.deserialize()`
1224
+ Usage: `deserialize(data: string): Promise<void>`
1225
+ Deserializes the given string that was created with `serialize()` and imports the contained data each DataStore instance.
1191
1226
  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.
1192
1227
 
1193
1228
  If `ensureIntegrity` is set to `true` and the checksum doesn't match, an error will be thrown.
1194
1229
  If `ensureIntegrity` is set to `false`, the checksum check will be skipped entirely.
1195
1230
  If the `checksum` property is missing on the imported data, the checksum check will also be skipped.
1196
1231
  If `encoded` is set to `true`, the data will be decoded using the `decodeData` function set on the DataStore instance.
1232
+
1233
+ <br>
1234
+
1235
+ #### `DataStoreSerializer.loadStoresData()`
1236
+ Usage: `loadStoresData(): PromiseSettledResult<{ id: string, data: object }>[];`
1237
+ Loads the persistent data of the DataStore instances into the in-memory cache of each DataStore instance.
1238
+ Also triggers the migration process if the data format has changed.
1239
+ See the [`DataStore.loadData()`](#datastoreloaddata) method for more information.
1240
+
1241
+ <details><summary>Click to view the structure of the returned data.</summary>
1242
+
1243
+ ```jsonc
1244
+ [
1245
+ {
1246
+ "status": "fulfilled",
1247
+ "value": {
1248
+ "id": "foo-data",
1249
+ "data": {
1250
+ "foo": "hello",
1251
+ "bar": "world"
1252
+ }
1253
+ }
1254
+ },
1255
+ {
1256
+ "status": "rejected",
1257
+ "reason": "Checksum mismatch for DataStore with ID \"bar-data\"!\nExpected: 69beefdead420\nHas: 420abcdef69"
1258
+ }
1259
+ ]
1260
+ ```
1261
+
1262
+ </details>
1263
+
1264
+ <br>
1265
+
1266
+ #### `DataStoreSerializer.resetStoresData()`
1267
+ Usage: `resetStoresData(): PromiseSettledResult[];`
1268
+ Resets the persistent data of the DataStore instances to their default values.
1269
+ This affects both the in-memory cache and the persistent storage.
1270
+ Any call to `serialize()` will then use the value of `options.defaultData` of the respective DataStore instance.
1271
+
1272
+ <br>
1273
+
1274
+ #### `DataStoreSerializer.deleteStoresData()`
1275
+ Usage: `deleteStoresData(): PromiseSettledResult[];`
1276
+ Deletes the persistent data of the DataStore instances from the set storage method.
1277
+ Leaves the in-memory cache of the DataStore instances untouched.
1278
+ Any call to `setData()` on the instances will recreate their own persistent storage data.
1197
1279
 
1198
1280
  <br>
1199
1281
 
@@ -1234,9 +1316,8 @@ const serializer = new DataStoreSerializer([fooStore, barStore], {
1234
1316
  });
1235
1317
 
1236
1318
  async function exportMyDataPls() {
1237
- // first, make sure the persistent data of the stores is loaded into their caches:
1238
- await fooStore.loadData();
1239
- await barStore.loadData();
1319
+ // first, make sure the persistent data of all stores is loaded into their caches:
1320
+ await serializer.loadStoresData();
1240
1321
 
1241
1322
  // now serialize the data:
1242
1323
  const serializedData = await serializer.serialize();
@@ -1269,11 +1350,11 @@ async function exportMyDataPls() {
1269
1350
  }
1270
1351
 
1271
1352
  async function importMyDataPls() {
1272
- // grab the data from the file by using the system file picker or any other method
1353
+ // grab the data from the file by using the system file picker or a text field or something similar
1273
1354
  const data = await getDataFromSomewhere();
1274
1355
 
1275
1356
  try {
1276
- // import the data
1357
+ // import the data and run migrations if necessary
1277
1358
  await serializer.deserialize(data);
1278
1359
  }
1279
1360
  catch(err) {
@@ -1281,6 +1362,11 @@ async function importMyDataPls() {
1281
1362
  alert(`Data import failed: ${err}`);
1282
1363
  }
1283
1364
  }
1365
+
1366
+ async function resetMyDataPls() {
1367
+ // reset the data of all stores in both the cache and the persistent storage
1368
+ await serializer.resetStoresData();
1369
+ }
1284
1370
  ```
1285
1371
  </details>
1286
1372
 
@@ -1313,41 +1399,71 @@ The options object has the following properties:
1313
1399
  | `verticalAlign?: "top" \| "center" \| "bottom"` | (Optional) Where to align or anchor the dialog vertically. Defaults to `"center"`. |
1314
1400
  | `strings?: Partial<typeof defaultStrings>` | (Optional) Strings used in the dialog (used for translations). Defaults to the default English strings (importable with the name `defaultStrings`). |
1315
1401
  | `dialogCss?: string` | (Optional) CSS to apply to the dialog. Defaults to the default (importable with the name `defaultDialogCss`). |
1316
-
1317
- Methods:
1318
- `open(): Promise<void>`
1402
+
1403
+ <br>
1404
+
1405
+ ### Methods:
1406
+ #### `Dialog.open()`
1407
+ Usage: `open(): Promise<void>`
1319
1408
  Opens the dialog.
1320
-
1321
- `close(): void`
1409
+
1410
+ <br>
1411
+
1412
+ #### `Dialog.close()`
1413
+ Usage: `close(): void`
1322
1414
  Closes the dialog.
1323
-
1324
- `mount(): Promise<void>`
1415
+
1416
+ <br>
1417
+
1418
+ #### `Dialog.mount()`
1419
+ Usage: `mount(): Promise<void>`
1325
1420
  Mounts the dialog to the DOM by calling the render functions provided in the options object.
1326
1421
  Can be done before opening the dialog to avoid a delay.
1327
-
1328
- `unmount(): void`
1422
+
1423
+ <br>
1424
+
1425
+ #### `Dialog.unmount()`
1426
+ Usage: `unmount(): void`
1329
1427
  Unmounts the dialog from the DOM.
1330
-
1331
- `remount(): Promise<void>`
1428
+
1429
+ <br>
1430
+
1431
+ #### `Dialog.remount()`
1432
+ Usage: `remount(): Promise<void>`
1332
1433
  Unmounts and mounts the dialog again.
1333
1434
  The render functions in the options object will be called again.
1334
1435
  May cause a flickering effect due to the rendering delay.
1335
-
1336
- `isOpen(): boolean`
1436
+
1437
+ <br>
1438
+
1439
+ #### `Dialog.isOpen()`
1440
+ Usage: `isOpen(): boolean`
1337
1441
  Returns `true` if the dialog is open, else `false`.
1338
-
1339
- `isMounted(): boolean`
1442
+
1443
+ <br>
1444
+
1445
+ #### `Dialog.isMounted()`
1446
+ Usage: `isMounted(): boolean`
1340
1447
  Returns `true` if the dialog is mounted, else `false`.
1341
-
1342
- `destroy(): void`
1448
+
1449
+ <br>
1450
+
1451
+ #### `Dialog.destroy()`
1452
+ Usage: `destroy(): void`
1343
1453
  Destroys the dialog.
1344
1454
  Removes all listeners and unmounts the dialog by default.
1345
-
1346
- `static getCurrentDialogId(): string`
1455
+
1456
+ <br>
1457
+
1458
+ #### `Dialog.getCurrentDialogId()`
1459
+ Usage: `static getCurrentDialogId(): string`
1347
1460
  Static method that returns the ID of the currently open dialog.
1348
1461
  Needs to be called without creating an instance of the class.
1349
-
1350
- `static getOpenDialogs(): string[]`
1462
+
1463
+ <br>
1464
+
1465
+ #### `Dialog.getOpenDialogs()`
1466
+ Usage: `static getOpenDialogs(): string[]`
1351
1467
  Static method that returns an array of the IDs of all open dialogs.
1352
1468
  Needs to be called without creating an instance of the class.
1353
1469
 
@@ -8,7 +8,7 @@
8
8
  // ==UserLibrary==
9
9
  // @name UserUtils
10
10
  // @description Library with various utilities for userscripts - register listeners for when CSS selectors exist, intercept events, create persistent & synchronous data stores, modify the DOM more easily and more
11
- // @version 8.0.2
11
+ // @version 8.1.0
12
12
  // @license MIT
13
13
  // @copyright Sv443 (https://github.com/Sv443)
14
14
 
@@ -478,7 +478,7 @@ var UserUtils = (function (exports) {
478
478
  * The in-memory cache will be left untouched, so you may still access the data with {@linkcode getData()}
479
479
  * Calling {@linkcode loadData()} or {@linkcode setData()} after this method was called will recreate persistent storage with the cached or default data.
480
480
  *
481
- * ⚠️ This requires the additional directive `@grant GM.deleteValue`
481
+ * ⚠️ This requires the additional directive `@grant GM.deleteValue` if the storageMethod is left as the default of `"GM"`
482
482
  */
483
483
  deleteData() {
484
484
  return __async(this, null, function* () {
@@ -532,6 +532,31 @@ var UserUtils = (function (exports) {
532
532
  return this.cachedData = __spreadValues({}, newData);
533
533
  });
534
534
  }
535
+ /**
536
+ * Tries to migrate the currently saved persistent data from one or more old IDs to the ID set in the constructor.
537
+ * If no data exist for the old ID(s), nothing will be done, but some time may still pass trying to fetch the non-existent data.
538
+ */
539
+ migrateId(oldIds) {
540
+ return __async(this, null, function* () {
541
+ const ids = Array.isArray(oldIds) ? oldIds : [oldIds];
542
+ yield Promise.all(ids.map((id) => __async(this, null, function* () {
543
+ const data = yield this.getValue(`_uucfg-${id}`, JSON.stringify(this.defaultData));
544
+ const fmtVer = Number(yield this.getValue(`_uucfgver-${id}`, NaN));
545
+ const isEncoded = Boolean(yield this.getValue(`_uucfgenc-${id}`, false));
546
+ if (data === void 0 || isNaN(fmtVer))
547
+ return;
548
+ const parsed = yield this.deserializeData(data, isEncoded);
549
+ yield Promise.allSettled([
550
+ this.setValue(`_uucfg-${this.id}`, yield this.serializeData(parsed)),
551
+ this.setValue(`_uucfgver-${this.id}`, fmtVer),
552
+ this.setValue(`_uucfgenc-${this.id}`, isEncoded),
553
+ this.deleteValue(`_uucfg-${id}`),
554
+ this.deleteValue(`_uucfgver-${id}`),
555
+ this.deleteValue(`_uucfgenc-${id}`)
556
+ ]);
557
+ })));
558
+ });
559
+ }
535
560
  //#region serialization
536
561
  /** Serializes the data using the optional this.encodeData() and returns it as a string */
537
562
  serializeData(data, useEncoding = true) {
@@ -673,6 +698,38 @@ Has: ${checksum}`);
673
698
  }
674
699
  });
675
700
  }
701
+ /**
702
+ * Loads the persistent data of the DataStore instances into the in-memory cache.
703
+ * Also triggers the migration process if the data format has changed.
704
+ * @returns Returns a PromiseSettledResult array with the results of each DataStore instance in the format `{ id: string, data: object }`
705
+ */
706
+ loadStoresData() {
707
+ return __async(this, null, function* () {
708
+ return Promise.allSettled(this.stores.map(
709
+ (store) => __async(this, null, function* () {
710
+ return {
711
+ id: store.id,
712
+ data: yield store.loadData()
713
+ };
714
+ })
715
+ ));
716
+ });
717
+ }
718
+ /** Resets the persistent data of the DataStore instances to their default values. */
719
+ resetStoresData() {
720
+ return __async(this, null, function* () {
721
+ return Promise.allSettled(this.stores.map((store) => store.saveDefaultData()));
722
+ });
723
+ }
724
+ /**
725
+ * Deletes the persistent data of the DataStore instances.
726
+ * Leaves the in-memory data untouched.
727
+ */
728
+ deleteStoresData() {
729
+ return __async(this, null, function* () {
730
+ return Promise.allSettled(this.stores.map((store) => store.deleteData()));
731
+ });
732
+ }
676
733
  };
677
734
 
678
735
  // node_modules/nanoevents/index.js
package/dist/index.js CHANGED
@@ -458,7 +458,7 @@ var DataStore = class {
458
458
  * The in-memory cache will be left untouched, so you may still access the data with {@linkcode getData()}
459
459
  * Calling {@linkcode loadData()} or {@linkcode setData()} after this method was called will recreate persistent storage with the cached or default data.
460
460
  *
461
- * ⚠️ This requires the additional directive `@grant GM.deleteValue`
461
+ * ⚠️ This requires the additional directive `@grant GM.deleteValue` if the storageMethod is left as the default of `"GM"`
462
462
  */
463
463
  deleteData() {
464
464
  return __async(this, null, function* () {
@@ -512,6 +512,31 @@ var DataStore = class {
512
512
  return this.cachedData = __spreadValues({}, newData);
513
513
  });
514
514
  }
515
+ /**
516
+ * Tries to migrate the currently saved persistent data from one or more old IDs to the ID set in the constructor.
517
+ * If no data exist for the old ID(s), nothing will be done, but some time may still pass trying to fetch the non-existent data.
518
+ */
519
+ migrateId(oldIds) {
520
+ return __async(this, null, function* () {
521
+ const ids = Array.isArray(oldIds) ? oldIds : [oldIds];
522
+ yield Promise.all(ids.map((id) => __async(this, null, function* () {
523
+ const data = yield this.getValue(`_uucfg-${id}`, JSON.stringify(this.defaultData));
524
+ const fmtVer = Number(yield this.getValue(`_uucfgver-${id}`, NaN));
525
+ const isEncoded = Boolean(yield this.getValue(`_uucfgenc-${id}`, false));
526
+ if (data === void 0 || isNaN(fmtVer))
527
+ return;
528
+ const parsed = yield this.deserializeData(data, isEncoded);
529
+ yield Promise.allSettled([
530
+ this.setValue(`_uucfg-${this.id}`, yield this.serializeData(parsed)),
531
+ this.setValue(`_uucfgver-${this.id}`, fmtVer),
532
+ this.setValue(`_uucfgenc-${this.id}`, isEncoded),
533
+ this.deleteValue(`_uucfg-${id}`),
534
+ this.deleteValue(`_uucfgver-${id}`),
535
+ this.deleteValue(`_uucfgenc-${id}`)
536
+ ]);
537
+ })));
538
+ });
539
+ }
515
540
  //#region serialization
516
541
  /** Serializes the data using the optional this.encodeData() and returns it as a string */
517
542
  serializeData(data, useEncoding = true) {
@@ -653,6 +678,38 @@ Has: ${checksum}`);
653
678
  }
654
679
  });
655
680
  }
681
+ /**
682
+ * Loads the persistent data of the DataStore instances into the in-memory cache.
683
+ * Also triggers the migration process if the data format has changed.
684
+ * @returns Returns a PromiseSettledResult array with the results of each DataStore instance in the format `{ id: string, data: object }`
685
+ */
686
+ loadStoresData() {
687
+ return __async(this, null, function* () {
688
+ return Promise.allSettled(this.stores.map(
689
+ (store) => __async(this, null, function* () {
690
+ return {
691
+ id: store.id,
692
+ data: yield store.loadData()
693
+ };
694
+ })
695
+ ));
696
+ });
697
+ }
698
+ /** Resets the persistent data of the DataStore instances to their default values. */
699
+ resetStoresData() {
700
+ return __async(this, null, function* () {
701
+ return Promise.allSettled(this.stores.map((store) => store.saveDefaultData()));
702
+ });
703
+ }
704
+ /**
705
+ * Deletes the persistent data of the DataStore instances.
706
+ * Leaves the in-memory data untouched.
707
+ */
708
+ deleteStoresData() {
709
+ return __async(this, null, function* () {
710
+ return Promise.allSettled(this.stores.map((store) => store.deleteData()));
711
+ });
712
+ }
656
713
  };
657
714
  var NanoEmitter = class {
658
715
  constructor(options = {}) {
@@ -5,11 +5,15 @@ type MigrationFunc = (oldData: any) => any | Promise<any>;
5
5
  export type DataMigrationsDict = Record<number, MigrationFunc>;
6
6
  /** Options for the DataStore instance */
7
7
  export type DataStoreOptions<TData> = {
8
- /** A unique internal ID for this data store - choose wisely as changing it is not supported yet. */
8
+ /**
9
+ * A unique internal ID for this data store.
10
+ * To avoid conflicts with other scripts, it is recommended to use a prefix that is unique to your script.
11
+ * If you want to change the ID, you should make use of the {@linkcode DataStore.migrateId()} method.
12
+ */
9
13
  id: string;
10
14
  /**
11
15
  * The default data object to use if no data is saved in persistent storage yet.
12
- * Until the data is loaded from persistent storage with `loadData()`, this will be the data returned by `getData()`
16
+ * Until the data is loaded from persistent storage with {@linkcode DataStore.loadData()}, this will be the data returned by {@linkcode DataStore.getData()}.
13
17
  *
14
18
  * ⚠️ This has to be an object that can be serialized to JSON using `JSON.stringify()`, so no functions or circular references are allowed, they will cause unexpected behavior.
15
19
  */
@@ -31,8 +35,8 @@ export type DataStoreOptions<TData> = {
31
35
  migrations?: DataMigrationsDict;
32
36
  /**
33
37
  * Where the data should be saved (`"GM"` by default).
34
- * The protected methods `getValue` , `setValue` and `deleteValue` are used to interact with the storage.
35
- * If you want to use a different storage method, you can extend the class and overwrite these methods.
38
+ * The protected methods {@linkcode DataStore.getValue()}, {@linkcode DataStore.setValue()} and {@linkcode DataStore.deleteValue()} are used to interact with the storage.
39
+ * `"GM"` storage, `"localStorage"` and `"sessionStorage"` are supported out of the box, but in an extended class you can overwrite those methods to implement any other storage method.
36
40
  */
37
41
  storageMethod?: "GM" | "localStorage" | "sessionStorage";
38
42
  } & ({
@@ -57,16 +61,18 @@ export type DataStoreOptions<TData> = {
57
61
  decodeData?: never;
58
62
  });
59
63
  /**
60
- * Manages a hybrid synchronous & asynchronous persistent JSON database that is cached in memory and persistently saved across sessions using [GM storage.](https://wiki.greasespot.net/GM.setValue)
64
+ * Manages a hybrid synchronous & asynchronous persistent JSON database that is cached in memory and persistently saved across sessions using [GM storage](https://wiki.greasespot.net/GM.setValue) (default), [localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) or [sessionStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage).
61
65
  * Supports migrating data from older format versions to newer ones and populating the cache with default data if no persistent data is found.
66
+ * Can be overridden to implement any other storage method.
62
67
  *
63
68
  * All methods are at least `protected`, so you can easily extend this class and overwrite them to use a different storage method or to add additional functionality.
64
69
  * Remember that you can call `super.methodName()` in the subclass to access the original method.
65
70
  *
66
- * ⚠️ Requires the directives `@grant GM.getValue` and `@grant GM.setValue` if the storageMethod is left as the default of `"GM"`
71
+ * ⚠️ The stored data has to be **serializable using `JSON.stringify()`**, meaning no undefined, circular references, etc. are allowed.
72
+ * ⚠️ If the storageMethod is left as the default of `"GM"` the directives `@grant GM.getValue` and `@grant GM.setValue` are required. If you then also use the method {@linkcode DataStore.deleteData()}, the extra directive `@grant GM.deleteValue` is needed too.
67
73
  * ⚠️ Make sure to call {@linkcode loadData()} at least once after creating an instance, or the returned data will be the same as `options.defaultData`
68
74
  *
69
- * @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.)
75
+ * @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)
70
76
  */
71
77
  export declare class DataStore<TData extends object = object> {
72
78
  readonly id: string;
@@ -109,7 +115,7 @@ export declare class DataStore<TData extends object = object> {
109
115
  * The in-memory cache will be left untouched, so you may still access the data with {@linkcode getData()}
110
116
  * Calling {@linkcode loadData()} or {@linkcode setData()} after this method was called will recreate persistent storage with the cached or default data.
111
117
  *
112
- * ⚠️ This requires the additional directive `@grant GM.deleteValue`
118
+ * ⚠️ This requires the additional directive `@grant GM.deleteValue` if the storageMethod is left as the default of `"GM"`
113
119
  */
114
120
  deleteData(): Promise<void>;
115
121
  /** Returns whether encoding and decoding are enabled for this DataStore instance */
@@ -122,6 +128,11 @@ export declare class DataStore<TData extends object = object> {
122
128
  * If one of the migrations fails, the data will be reset to the default value if `resetOnError` is set to `true` (default). Otherwise, an error will be thrown and no data will be saved.
123
129
  */
124
130
  runMigrations(oldData: any, oldFmtVer: number, resetOnError?: boolean): Promise<TData>;
131
+ /**
132
+ * Tries to migrate the currently saved persistent data from one or more old IDs to the ID set in the constructor.
133
+ * If no data exist for the old ID(s), nothing will be done, but some time may still pass trying to fetch the non-existent data.
134
+ */
135
+ migrateId(oldIds: string | string[]): Promise<void>;
125
136
  /** Serializes the data using the optional this.encodeData() and returns it as a string */
126
137
  protected serializeData(data: TData, useEncoding?: boolean): Promise<string>;
127
138
  /** Deserializes the data using the optional this.decodeData() and returns it as a JSON object */
@@ -18,6 +18,13 @@ export type SerializedDataStore = {
18
18
  /** The checksum of the data - key is not present for data without a checksum */
19
19
  checksum?: string;
20
20
  };
21
+ /** Result of {@linkcode DataStoreSerializer.loadStoresData()} */
22
+ export type LoadStoresDataResult = {
23
+ /** The ID of the DataStore instance */
24
+ id: string;
25
+ /** The in-memory data object */
26
+ data: object;
27
+ };
21
28
  /**
22
29
  * Allows for easy serialization and deserialization of multiple DataStore instances.
23
30
  *
@@ -41,4 +48,17 @@ export declare class DataStoreSerializer {
41
48
  * Also triggers the migration process if the data format has changed.
42
49
  */
43
50
  deserialize(serializedData: string): Promise<void>;
51
+ /**
52
+ * Loads the persistent data of the DataStore instances into the in-memory cache.
53
+ * Also triggers the migration process if the data format has changed.
54
+ * @returns Returns a PromiseSettledResult array with the results of each DataStore instance in the format `{ id: string, data: object }`
55
+ */
56
+ loadStoresData(): Promise<PromiseSettledResult<LoadStoresDataResult>[]>;
57
+ /** Resets the persistent data of the DataStore instances to their default values. */
58
+ resetStoresData(): Promise<PromiseSettledResult<void>[]>;
59
+ /**
60
+ * Deletes the persistent data of the DataStore instances.
61
+ * Leaves the in-memory data untouched.
62
+ */
63
+ deleteStoresData(): Promise<PromiseSettledResult<void>[]>;
44
64
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sv443-network/userutils",
3
3
  "libName": "UserUtils",
4
- "version": "8.0.2",
4
+ "version": "8.1.0",
5
5
  "description": "Library with various utilities for userscripts - register listeners for when CSS selectors exist, intercept events, create persistent & synchronous data stores, modify the DOM more easily and more",
6
6
  "main": "dist/index.js",
7
7
  "module": "dist/index.js",