@sv443-network/coreutils 3.2.0 → 3.4.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.
@@ -2,8 +2,10 @@
2
2
  * @module DataStore
3
3
  * This module contains the DataStore class, which is a general purpose, sync and async persistent database for JSON-serializable data - [see the documentation for more info](https://github.com/Sv443-Network/CoreUtils/blob/main/docs.md#class-datastore)
4
4
  */
5
+ import { MigrationError } from "./Errors.ts";
5
6
  import type { DataStoreEngine } from "./DataStoreEngine.ts";
6
7
  import type { LooseUnion, Prettify } from "./types.ts";
8
+ import { NanoEmitter, type NanoEmitterOptions } from "./NanoEmitter.ts";
7
9
  /** Function that takes the data in the old format and returns the data in the new format. Also supports an asynchronous migration. */
8
10
  type MigrationFunc = (oldData: any) => any | Promise<any>;
9
11
  /** Dictionary of format version numbers and the function that migrates to them from the previous whole integer */
@@ -45,7 +47,7 @@ export type DataStoreOptions<TData extends DataStoreData, TMemCache extends bool
45
47
  * A dictionary of functions that can be used to migrate data from older versions to newer ones.
46
48
  * The keys of the dictionary should be the format version that the functions can migrate to, from the previous whole integer value.
47
49
  * The values should be functions that take the data in the old format and return the data in the new format.
48
- * The functions will be run in order from the oldest to the newest version.
50
+ * The functions will be run in order from the oldest (smallest number) to the newest (biggest number) version.
49
51
  * If the current format version is not in the dictionary, no migrations will be run.
50
52
  */
51
53
  migrations?: DataMigrationsDict;
@@ -68,6 +70,8 @@ export type DataStoreOptions<TData extends DataStoreData, TMemCache extends bool
68
70
  * Note: this will break backwards compatibility with any previously saved data, so only change this if you know what you're doing and ideally before any non-volatile data is saved by the end user.
69
71
  */
70
72
  keyPrefix?: string;
73
+ /** Options for the internal NanoEmitter instance. */
74
+ nanoEmitterOptions?: NanoEmitterOptions;
71
75
  } & ({
72
76
  encodeData?: never;
73
77
  decodeData?: never;
@@ -102,9 +106,31 @@ export type DataStoreOptions<TData extends DataStoreData, TMemCache extends bool
102
106
  /**
103
107
  * Generic type that represents the serializable data structure saved in a {@linkcode DataStore} instance.
104
108
  * - ⚠️ Uses `object` instead of an index signature so that interfaces without an explicit index signature can be used as `TData`.
105
- * Make sure to only use JSON-serializable types here, otherwise unexpected behavior may occur!
109
+ * However, this also means that the type system won't prevent you from using non-serializable data structures like functions or symbols in the data, which will cause errors at runtime.
110
+ * Make sure to only use types that are compatible with `JSON.stringify()`, and use `null` instead of `undefined` when you need to preserve the key of an empty value.
106
111
  */
107
112
  export type DataStoreData = object;
113
+ /** Map of event names and their corresponding listener function signatures for the {@linkcode DataStore} class. */
114
+ export type DataStoreEventMap<TData> = {
115
+ /** Emitted whenever the data is loaded from persistent storage with {@linkcode DataStore.loadData()}. */
116
+ loadData: (data: TData) => void;
117
+ /** Emitted when the data is updated with {@linkcode DataStore.setData()} or {@linkcode DataStore.runMigrations()} */
118
+ updateData: (newData: TData) => void;
119
+ /** Emitted when the memory cache was updated with {@linkcode DataStore.setData()}, before the data is saved to persistent storage. Not emitted if `memoryCache` is set to `false`. */
120
+ updateDataSync: (newData: TData) => void;
121
+ /** Emitted for every called migration function with the resulting data. */
122
+ migrateData: (migratedTo: number, migratedData: unknown, isFinalMigration: boolean) => void;
123
+ /** Emitted for every successfully migrated old ID. Gets passed the old and new ID. */
124
+ migrateId: (oldId: string, newId: string) => void;
125
+ /** Emitted whenever the data is reset to the default value with {@linkcode DataStore.saveDefaultData()} (will not be called on the initial population of persistent storage with the default data in {@linkcode DataStore.loadData()}). */
126
+ setDefaultData: (defaultData: TData) => void;
127
+ /** Emitted after the data was deleted from persistent storage with {@linkcode DataStore.deleteData()}. */
128
+ deleteData: () => void;
129
+ /** Emitted when an error occurs at any point. */
130
+ error: (error: Error) => void;
131
+ /** Emitted only when an error occurs during a migration function. */
132
+ migrationError: (migratingTo: number, error: MigrationError) => void;
133
+ };
108
134
  /**
109
135
  * Manages a hybrid synchronous & asynchronous persistent JSON database that is cached in memory and persistently saved across sessions using one of the preset DataStoreEngines or your own one.
110
136
  * Supports migrating data from older format versions to newer ones and populating the cache with default data if no persistent data is found.
@@ -117,9 +143,8 @@ export type DataStoreData = object;
117
143
  * - ⚠️ Make sure to call {@linkcode loadData()} at least once after creating an instance, or the returned data will be the same as `options.defaultData`
118
144
  *
119
145
  * @template TData The type of the data that is saved in persistent storage for the currently set format version
120
- * (TODO:FIXME: will be automatically inferred from `defaultData` if not provided)
121
146
  */
122
- export declare class DataStore<TData extends DataStoreData, TMemCache extends boolean = true> {
147
+ export declare class DataStore<TData extends DataStoreData, TMemCache extends boolean = true> extends NanoEmitter<DataStoreEventMap<TData>> {
123
148
  readonly id: string;
124
149
  readonly formatVersion: number;
125
150
  readonly defaultData: TData;
@@ -164,8 +189,11 @@ export declare class DataStore<TData extends DataStoreData, TMemCache extends bo
164
189
  getData(this: DataStore<TData, true>): TData;
165
190
  /** Saves the data synchronously to the in-memory cache and asynchronously to the persistent storage */
166
191
  setData(data: TData): Promise<void>;
167
- /** Saves the default data passed in the constructor synchronously to the in-memory cache and asynchronously to persistent storage */
168
- saveDefaultData(): Promise<void>;
192
+ /**
193
+ * Saves the default data passed in the constructor synchronously to the in-memory cache and asynchronously to persistent storage.
194
+ * @param emitEvent Whether to emit the `setDefaultData` event - set to `false` to prevent event emission (used internally during initial population in {@linkcode loadData()})
195
+ */
196
+ saveDefaultData(emitEvent?: boolean): Promise<void>;
169
197
  /**
170
198
  * Call this method to clear all persistently stored data associated with this DataStore instance, including the storage container (if supported by the DataStoreEngine).
171
199
  * The in-memory cache will be left untouched, so you may still access the data with {@linkcode getData()}
@@ -35,7 +35,8 @@ export type LoadStoresDataResult = {
35
35
  /** Filter for selecting data stores */
36
36
  export type StoreFilter = string[] | ((id: string) => boolean);
37
37
  /**
38
- * Allows for easy serialization and deserialization of multiple DataStore instances.
38
+ * Allows for easy serialization and deserialization of multiple {@linkcode DataStore} instances.
39
+ * Offers methods to only serialize or deserialize a subset of the stores, and to ensure the integrity of the data by adding checksums.
39
40
  *
40
41
  * 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.
41
42
  * Remember that you can call `super.methodName()` in the subclass to access the original method.
@@ -46,7 +47,10 @@ export declare class DataStoreSerializer {
46
47
  protected stores: DataStore<DataStoreData, boolean>[];
47
48
  protected options: Required<DataStoreSerializerOptions>;
48
49
  constructor(stores: DataStore<DataStoreData, boolean>[], options?: DataStoreSerializerOptions);
49
- /** Calculates the checksum of a string */
50
+ /**
51
+ * Calculates the checksum of a string. Uses {@linkcode computeHash()} with SHA-256 and digests as a hex string by default.
52
+ * Override this in a subclass if a custom checksum method is needed.
53
+ */
50
54
  protected calcChecksum(input: string): Promise<string>;
51
55
  /**
52
56
  * Serializes only a subset of the data stores into a string.
@@ -2,7 +2,7 @@
2
2
  * @module Debouncer
3
3
  * This module contains the Debouncer class and debounce function that allow you to reduce the amount of calls in rapidly firing event listeners and such - [see the documentation for more info](https://github.com/Sv443-Network/UserUtils/blob/main/docs.md#debouncer)
4
4
  */
5
- import { NanoEmitter } from "./NanoEmitter.ts";
5
+ import { NanoEmitter, type NanoEmitterOptions } from "./NanoEmitter.ts";
6
6
  /**
7
7
  * The type of edge to use for the debouncer - [see the docs for a diagram and explanation.](https://github.com/Sv443-Network/UserUtils/blob/main/docs.md#debouncer)
8
8
  * - `immediate` - (default & recommended) - calls the listeners at the very first call ("rising" edge) and queues the latest call until the timeout expires
@@ -53,7 +53,7 @@ export declare class Debouncer<TFunc extends AnyFn> extends NanoEmitter<Debounce
53
53
  * @param timeout Timeout in milliseconds between letting through calls - defaults to 200
54
54
  * @param type The edge type to use for the debouncer - see {@linkcode DebouncerType} for details or [the documentation for an explanation and diagram](https://github.com/Sv443-Network/UserUtils/blob/main/docs.md#debouncer) - defaults to "immediate"
55
55
  */
56
- constructor(timeout?: number, type?: DebouncerType);
56
+ constructor(timeout?: number, type?: DebouncerType, nanoEmitterOptions?: NanoEmitterOptions);
57
57
  /** Adds a listener function that will be called on timeout */
58
58
  addListener(fn: TFunc): void;
59
59
  /** Removes the listener with the specified function reference */
@@ -82,5 +82,5 @@ export declare class Debouncer<TFunc extends AnyFn> extends NanoEmitter<Debounce
82
82
  *
83
83
  * Refer to the {@linkcode Debouncer} class definition or the [Debouncer documentation](https://github.com/Sv443-Network/UserUtils/blob/main/docs.md#debouncer) for more information.
84
84
  */
85
- export declare function debounce<TFunc extends (...args: any[]) => any>(fn: TFunc, timeout?: number, type?: DebouncerType): DebouncedFunction<TFunc>;
85
+ export declare function debounce<TFunc extends (...args: any[]) => any>(fn: TFunc, timeout?: number, type?: DebouncerType, nanoEmitterOptions?: NanoEmitterOptions): DebouncedFunction<TFunc>;
86
86
  export {};
@@ -77,3 +77,39 @@ export declare function scheduleExit(code?: number, timeout?: number): void;
77
77
  * @param lines The number of lines to return from the stack. Defaults to Infinity (all of them).
78
78
  */
79
79
  export declare function getCallStack<TAsArray extends boolean = true>(asArray?: TAsArray, lines?: number): TAsArray extends true ? string[] : string;
80
+ /** Options object for {@linkcode createRecurringTask()} */
81
+ export type RecurringTaskOptions<TVal extends void | unknown> = {
82
+ /** Timeout between running the task, in milliseconds. For async tasks and conditions, the timeout will be added onto the execution time of the Promises. */
83
+ timeout: number;
84
+ /**
85
+ * The task to run. Can return a value or a Promise that resolves to a value of any type, which will be passed to the optional callback once the task is finished.
86
+ * Gets passed the current iteration (starting at 0) as an argument. If no `condition` is given, the task will run indefinitely, or until aborted via the `signal`, `abortOnError` or `maxIterations` options.
87
+ */
88
+ task: (iteration: number) => TVal | Promise<TVal>;
89
+ /**
90
+ * Condition that needs to return true in order to run the task. If not given, the task will run indefinitely with the given timeout.
91
+ * Gets passed the current iteration (starting at 0) as an argument. A failing condition will still increment the iterations.
92
+ */
93
+ condition?: (iteration: number) => boolean | Promise<boolean>;
94
+ /** Gets called with the task's return value and iteration number every time it's finished. Can be an async function if asynchronous operations are needed in the callback. */
95
+ onSuccess?: (value: TVal, iteration: number) => void | Promise<void>;
96
+ /** Gets called with the error if the `task`, `condition` or `onSuccess` functions throw an error or return a rejected Promise. Can be an async function if asynchronous operations are needed in the callback. */
97
+ onError?: (error: unknown, iteration: number) => void | Promise<void>;
98
+ /**
99
+ * If true, the recurring task will stop if the condition or task functions throw an error or return a rejected Promise. Defaults to false.
100
+ * - ⚠️ If neither `onError` nor `abortOnError` are set, errors will be re-thrown, which could potentially crash the process if not handled by the caller.
101
+ */
102
+ abortOnError?: boolean;
103
+ /** Max number of times to run the task. If not given, will run indefinitely as long as the condition is true. */
104
+ maxIterations?: number;
105
+ /** Optional AbortSignal to cancel the task. */
106
+ signal?: AbortSignal;
107
+ /** Whether to run the task immediately on the first call or wait for the first timeout to pass. Defaults to true. */
108
+ immediate?: boolean;
109
+ };
110
+ /**
111
+ * Schedules a task to run immediately and repeatedly at the given timeout as long as the given condition returns true.
112
+ * Ensures no overlapping task executions and multiple ways to cleanly stop the repeated execution.
113
+ * @returns A promise that resolves once the task is stopped, either by the condition returning false, reaching the max iterations, or the signal being aborted.
114
+ */
115
+ export declare function createRecurringTask<TVal extends void | unknown>(options: RecurringTaskOptions<TVal>): Promise<void>;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sv443-network/coreutils",
3
3
  "libName": "@sv443-network/coreutils",
4
- "version": "3.2.0",
4
+ "version": "3.4.0",
5
5
  "description": "Cross-platform, general-purpose, JavaScript core library for Node, Deno and the browser. Intended to be used in conjunction with `@sv443-network/userutils` and `@sv443-network/djsutils`, but can be used independently as well.",
6
6
  "main": "dist/CoreUtils.cjs",
7
7
  "module": "dist/CoreUtils.mjs",
@@ -38,30 +38,30 @@
38
38
  },
39
39
  "homepage": "https://github.com/Sv443-Network/CoreUtils",
40
40
  "dependencies": {
41
- "nanoevents": "^9.1.0"
41
+ "nanoevents": "9.1.0"
42
42
  },
43
43
  "devDependencies": {
44
- "@changesets/cli": "^2.29.8",
45
- "@eslint/eslintrc": "^3.3.3",
46
- "@eslint/js": "^9.39.2",
47
- "@swc/core": "^1.15.11",
48
- "@testing-library/dom": "^10.4.1",
49
- "@types/deno": "^2.5.0",
50
- "@types/node": "^22.19.11",
51
- "@types/tx2": "^1.0.3",
52
- "@typescript-eslint/eslint-plugin": "^8.56.0",
53
- "@typescript-eslint/parser": "^8.56.0",
54
- "@typescript-eslint/utils": "^8.56.0",
55
- "@vitest/coverage-v8": "^3.2.4",
56
- "esbuild-plugin-umd-wrapper": "^3.0.0",
57
- "eslint": "^9.39.2",
58
- "globals": "^16.5.0",
59
- "jsdom": "^26.1.0",
60
- "tslib": "^2.8.1",
61
- "tsup": "^8.5.1",
62
- "tsx": "^4.21.0",
63
- "typescript": "^5.9.3",
64
- "vitest": "^3.2.4"
44
+ "@changesets/cli": "2.30.0",
45
+ "@eslint/eslintrc": "3.3.5",
46
+ "@eslint/js": "9.39.4",
47
+ "@swc/core": "1.15.18",
48
+ "@testing-library/dom": "10.4.1",
49
+ "@types/deno": "2.5.0",
50
+ "@types/node": "22.19.15",
51
+ "@types/tx2": "1.0.3",
52
+ "@typescript-eslint/eslint-plugin": "8.56.1",
53
+ "@typescript-eslint/parser": "8.56.1",
54
+ "@typescript-eslint/utils": "8.56.1",
55
+ "@vitest/coverage-v8": "3.2.4",
56
+ "esbuild-plugin-umd-wrapper": "3.0.0",
57
+ "eslint": "9.39.4",
58
+ "globals": "16.5.0",
59
+ "jsdom": "26.1.0",
60
+ "tslib": "2.8.1",
61
+ "tsup": "8.5.1",
62
+ "tsx": "4.21.0",
63
+ "typescript": "5.9.3",
64
+ "vitest": "3.2.4"
65
65
  },
66
66
  "files": [
67
67
  "/dist/CoreUtils.cjs",