@sv443-network/coreutils 2.0.3 → 3.0.1

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.
@@ -3,11 +3,15 @@
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
5
  import type { DataStoreEngine } from "./DataStoreEngine.ts";
6
- import type { LooseUnion, Prettify, SerializableVal } from "./types.ts";
6
+ import type { LooseUnion, Prettify } from "./types.ts";
7
7
  /** Function that takes the data in the old format and returns the data in the new format. Also supports an asynchronous migration. */
8
8
  type MigrationFunc = (oldData: any) => any | Promise<any>;
9
9
  /** Dictionary of format version numbers and the function that migrates to them from the previous whole integer */
10
10
  export type DataMigrationsDict = Record<number, MigrationFunc>;
11
+ /** Tuple of a compression format identifier and a function to use to encode the data */
12
+ export type EncodeTuple = [format: LooseUnion<CompressionFormat> | null, encode: (data: string) => string | Promise<string>];
13
+ /** Tuple of a compression format identifier and a function to use to decode the data */
14
+ export type DecodeTuple = [format: LooseUnion<CompressionFormat> | null, decode: (data: string) => string | Promise<string>];
11
15
  /** Options for the DataStore instance */
12
16
  export type DataStoreOptions<TData extends DataStoreData> = Prettify<{
13
17
  /**
@@ -36,7 +40,7 @@ export type DataStoreOptions<TData extends DataStoreData> = Prettify<{
36
40
  *
37
41
  * - ⚠️ Don't reuse the same engine instance for multiple DataStores, unless it explicitly supports it!
38
42
  */
39
- engine: (() => DataStoreEngine<TData>) | DataStoreEngine<TData>;
43
+ engine: (() => DataStoreEngine) | DataStoreEngine;
40
44
  /**
41
45
  * A dictionary of functions that can be used to migrate data from older versions to newer ones.
42
46
  * The keys of the dictionary should be the format version that the functions can migrate to, from the previous whole integer value.
@@ -52,12 +56,19 @@ export type DataStoreOptions<TData extends DataStoreData> = Prettify<{
52
56
  * To migrate IDs manually, use the method {@linkcode DataStore.migrateId()} instead.
53
57
  */
54
58
  migrateIds?: string | string[];
59
+ /**
60
+ * Whether to keep a copy of the data in memory for synchronous read access. Defaults to `true`.
61
+ *
62
+ * - ⚠️ If turned off, {@linkcode DataStore.getData()} will throw an error and only {@linkcode DataStore.loadData()} can be used to access the data.
63
+ * This may be useful if multiple sources are modifying the data, or the data is very large and you want to save memory, but it will make accessing the data slower, especially when combined with compression.
64
+ */
65
+ memoryCache?: boolean;
55
66
  } & ({
56
67
  encodeData?: never;
57
68
  decodeData?: never;
58
69
  /**
59
70
  * The format to use for compressing the data. Defaults to `deflate-raw`. Explicitly set to `null` to store data uncompressed.
60
- * - ⚠️ Use either this property, or both `encodeData` and `decodeData`, but not all three!
71
+ * - ⚠️ Use either this property, or both `encodeData` and `decodeData`, but not a combination of the three!
61
72
  */
62
73
  compressionFormat?: CompressionFormat | null;
63
74
  } | {
@@ -70,7 +81,7 @@ export type DataStoreOptions<TData extends DataStoreData> = Prettify<{
70
81
  * 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.
71
82
  * @param data The input data as a serialized object (JSON string)
72
83
  */
73
- encodeData: [format: LooseUnion<CompressionFormat> | null, encode: (data: string) => string | Promise<string>];
84
+ encodeData: EncodeTuple;
74
85
  /**
75
86
  * Tuple of a compression format identifier and a function to use to decode the data after reading it from persistent storage.
76
87
  * Set the identifier to `null` or `"identity"` to indicate that no traditional compression is used.
@@ -80,11 +91,15 @@ export type DataStoreOptions<TData extends DataStoreData> = Prettify<{
80
91
  * 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.
81
92
  * @returns The resulting data as a valid serialized object (JSON string)
82
93
  */
83
- decodeData: [format: LooseUnion<CompressionFormat> | null, decode: (data: string) => string | Promise<string>];
94
+ decodeData: DecodeTuple;
84
95
  compressionFormat?: never;
85
96
  })>;
86
- /** Generic type that represents the serializable data structure saved in a {@linkcode DataStore} instance. */
87
- export type DataStoreData<TData extends SerializableVal = SerializableVal> = Record<string, SerializableVal | TData>;
97
+ /**
98
+ * Generic type that represents the serializable data structure saved in a {@linkcode DataStore} instance.
99
+ * - ⚠️ Uses `object` instead of an index signature so that interfaces without an explicit index signature can be used as `TData`.
100
+ * Make sure to only use JSON-serializable types here, otherwise unexpected behavior may occur!
101
+ */
102
+ export type DataStoreData = object;
88
103
  /**
89
104
  * 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.
90
105
  * Supports migrating data from older format versions to newer ones and populating the cache with default data if no persistent data is found.
@@ -96,7 +111,8 @@ export type DataStoreData<TData extends SerializableVal = SerializableVal> = Rec
96
111
  * - ⚠️ The data is stored as a JSON string, so only data compatible with [`JSON.stringify()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify) can be used. Circular structures and complex objects (containing functions, symbols, etc.) will either throw an error on load and save or cause otherwise unexpected behavior. Properties with a value of `undefined` will be removed from the data prior to saving it, so use `null` instead.
97
112
  * - ⚠️ Make sure to call {@linkcode loadData()} at least once after creating an instance, or the returned data will be the same as `options.defaultData`
98
113
  *
99
- * @template TData The type of the data that is saved in persistent storage for the currently set format version (FIXME:will be automatically inferred from `defaultData` if not provided)
114
+ * @template TData The type of the data that is saved in persistent storage for the currently set format version
115
+ * (TODO:FIXME: will be automatically inferred from `defaultData` if not provided)
100
116
  */
101
117
  export declare class DataStore<TData extends DataStoreData> {
102
118
  readonly id: string;
@@ -105,7 +121,8 @@ export declare class DataStore<TData extends DataStoreData> {
105
121
  readonly encodeData: DataStoreOptions<TData>["encodeData"];
106
122
  readonly decodeData: DataStoreOptions<TData>["decodeData"];
107
123
  readonly compressionFormat: Exclude<DataStoreOptions<TData>["compressionFormat"], undefined>;
108
- readonly engine: DataStoreEngine<TData>;
124
+ readonly memoryCache: boolean;
125
+ readonly engine: DataStoreEngine;
109
126
  options: DataStoreOptions<TData>;
110
127
  /**
111
128
  * Whether all first-init checks should be done.
@@ -114,9 +131,9 @@ export declare class DataStore<TData extends DataStoreData> {
114
131
  */
115
132
  protected firstInit: boolean;
116
133
  /** In-memory cached copy of the data that is saved in persistent storage used for synchronous read access. */
117
- private cachedData;
118
- private migrations?;
119
- private migrateIds;
134
+ protected cachedData: TData;
135
+ protected migrations?: DataMigrationsDict;
136
+ protected migrateIds: string[];
120
137
  /**
121
138
  * Creates an instance of DataStore to manage a sync & async database that is cached in memory and persistently saved across sessions.
122
139
  * Supports migrating data from older versions to newer ones and populating the cache with default data if no persistent data is found.
@@ -136,6 +153,7 @@ export declare class DataStore<TData extends DataStoreData> {
136
153
  /**
137
154
  * Returns a copy of the data from the in-memory cache.
138
155
  * Use {@linkcode loadData()} to get fresh data from persistent storage (usually not necessary since the cache should always exactly reflect persistent storage).
156
+ * ⚠️ If `memoryCache` was set to `false` in the constructor options, this method will throw an error.
139
157
  */
140
158
  getData(): TData;
141
159
  /** Saves the data synchronously to the in-memory cache and asynchronously to the persistent storage */
@@ -6,16 +6,16 @@
6
6
  import type { DataStoreData, DataStoreOptions } from "./DataStore.ts";
7
7
  import type { Prettify, SerializableVal } from "./types.ts";
8
8
  /** Contains the only properties of {@linkcode DataStoreOptions} that are relevant to the {@linkcode DataStoreEngine} class. */
9
- export type DataStoreEngineDSOptions<TData extends DataStoreData> = Prettify<Pick<DataStoreOptions<TData>, "decodeData" | "encodeData" | "id">>;
10
- export interface DataStoreEngine<TData extends DataStoreData> {
9
+ export type DataStoreEngineDSOptions<TData extends DataStoreData = DataStoreData> = Prettify<Pick<DataStoreOptions<TData>, "decodeData" | "encodeData" | "id">>;
10
+ export interface DataStoreEngine<TData extends DataStoreData = DataStoreData> {
11
11
  /** Deletes all data in persistent storage, including the data container itself (e.g. a file or a database) */
12
12
  deleteStorage?(): Promise<void>;
13
13
  }
14
14
  /**
15
15
  * Base class for creating {@linkcode DataStore} storage engines.
16
- * This acts as an interchangeable API for writing and reading persistent data in various environments.
16
+ * This acts as an interchangeable API for writing and reading persistent JSON-serializable data in various environments.
17
17
  */
18
- export declare abstract class DataStoreEngine<TData extends DataStoreData> {
18
+ export declare abstract class DataStoreEngine<TData extends DataStoreData = DataStoreData> {
19
19
  protected dataStoreOptions: DataStoreEngineDSOptions<TData>;
20
20
  constructor(options?: DataStoreEngineDSOptions<TData>);
21
21
  /** Called by DataStore on creation, to pass its options. Only call this if you are using this instance standalone! */
@@ -26,7 +26,7 @@ export declare abstract class DataStoreEngine<TData extends DataStoreData> {
26
26
  abstract setValue(name: string, value: SerializableVal): Promise<void>;
27
27
  /** Deletes a value from persistent storage */
28
28
  abstract deleteValue(name: string): Promise<void>;
29
- /** Serializes the given object to a string, optionally encoded with `options.encodeData` if {@linkcode useEncoding} is set to true */
29
+ /** Serializes the given object to a string, optionally encoded with `options.encodeData` if {@linkcode useEncoding} is not set to false and the `encodeData` and `decodeData` options are set */
30
30
  serializeData(data: TData, useEncoding?: boolean): Promise<string>;
31
31
  /** Deserializes the given string to a JSON object, optionally decoded with `options.decodeData` if {@linkcode useEncoding} is set to true */
32
32
  deserializeData(data: string, useEncoding?: boolean): Promise<TData>;
@@ -49,18 +49,18 @@ export type BrowserStorageEngineOptions = {
49
49
  dataStoreOptions?: DataStoreEngineDSOptions<DataStoreData>;
50
50
  };
51
51
  /**
52
- * Storage engine for the {@linkcode DataStore} class that uses the browser's LocalStorage or SessionStorage to store data.
52
+ * Storage engine for the {@linkcode DataStore} class that uses the browser's LocalStorage or SessionStorage to store JSON-serializable data.
53
53
  *
54
54
  * - ⚠️ Requires a DOM environment
55
- * - ⚠️ Don't reuse engines across multiple {@linkcode DataStore} instances
55
+ * - ⚠️ Don't reuse engine instances, always create a new one for each {@linkcode DataStore} instance
56
56
  */
57
- export declare class BrowserStorageEngine<TData extends DataStoreData> extends DataStoreEngine<TData> {
57
+ export declare class BrowserStorageEngine<TData extends DataStoreData = DataStoreData> extends DataStoreEngine<TData> {
58
58
  protected options: BrowserStorageEngineOptions & Required<Pick<BrowserStorageEngineOptions, "type">>;
59
59
  /**
60
60
  * Creates an instance of `BrowserStorageEngine`.
61
61
  *
62
62
  * - ⚠️ Requires a DOM environment
63
- * - ⚠️ Don't reuse engines across multiple {@linkcode DataStore} instances
63
+ * - ⚠️ Don't reuse engine instances, always create a new one for each {@linkcode DataStore} instance
64
64
  */
65
65
  constructor(options?: BrowserStorageEngineOptions);
66
66
  /** Fetches a value from persistent storage */
@@ -81,19 +81,19 @@ export type FileStorageEngineOptions = {
81
81
  dataStoreOptions?: DataStoreEngineDSOptions<DataStoreData>;
82
82
  };
83
83
  /**
84
- * Storage engine for the {@linkcode DataStore} class that uses a JSON file to store data.
84
+ * Storage engine for the {@linkcode DataStore} class that uses a JSON file to store JSON-serializable data.
85
85
  *
86
86
  * - ⚠️ Requires Node.js or Deno with Node compatibility (v1.31+)
87
- * - ⚠️ Don't reuse engines across multiple {@linkcode DataStore} instances
87
+ * - ⚠️ Don't reuse engine instances, always create a new one for each {@linkcode DataStore} instance
88
88
  */
89
- export declare class FileStorageEngine<TData extends DataStoreData> extends DataStoreEngine<TData> {
89
+ export declare class FileStorageEngine<TData extends DataStoreData = DataStoreData> extends DataStoreEngine<TData> {
90
90
  protected options: FileStorageEngineOptions & Required<Pick<FileStorageEngineOptions, "filePath">>;
91
91
  private fileAccessQueue;
92
92
  /**
93
93
  * Creates an instance of `FileStorageEngine`.
94
94
  *
95
95
  * - ⚠️ Requires Node.js or Deno with Node compatibility (v1.31+)
96
- * - ⚠️ Don't reuse engines across multiple {@linkcode DataStore} instances
96
+ * - ⚠️ Don't reuse engine instances, always create a new one for each {@linkcode DataStore} instance
97
97
  */
98
98
  constructor(options?: FileStorageEngineOptions);
99
99
  /** Reads the file contents */
@@ -9,6 +9,8 @@ export type DataStoreSerializerOptions = {
9
9
  addChecksum?: boolean;
10
10
  /** 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). Defaults to `true` */
11
11
  ensureIntegrity?: boolean;
12
+ /** If provided, all stores with an ID in the value's array will be remapped to the key's ID when deserialization is called. If they don't match a DataStore instance's ID, nothing will happen. */
13
+ remapIds?: Record<string, string[]>;
12
14
  };
13
15
  /** Meta object and serialized data of a DataStore instance */
14
16
  export type SerializedDataStore = {
@@ -7,15 +7,27 @@ export declare class DatedError extends Error {
7
7
  readonly date: Date;
8
8
  constructor(message: string, options?: ErrorOptions);
9
9
  }
10
- /** Error while validating checksum */
10
+ /** Error while validating checksum - extends {@linkcode DatedError} */
11
11
  export declare class ChecksumMismatchError extends DatedError {
12
12
  constructor(message: string, options?: ErrorOptions);
13
13
  }
14
- /** Error while migrating data */
14
+ /** Custom error class that can have a custom name - extends {@linkcode DatedError} */
15
+ export declare class CustomError extends DatedError {
16
+ constructor(name: string, message: string, options?: ErrorOptions);
17
+ }
18
+ /** Error while migrating data - extends {@linkcode DatedError} */
15
19
  export declare class MigrationError extends DatedError {
16
20
  constructor(message: string, options?: ErrorOptions);
17
21
  }
18
- /** Error while validating data */
22
+ /** Error while validating data - extends {@linkcode DatedError} */
19
23
  export declare class ValidationError extends DatedError {
20
24
  constructor(message: string, options?: ErrorOptions);
21
25
  }
26
+ /** Error related to script context (e.g. trying to access APIs that aren't supported by the executing JS engine) - extends {@linkcode DatedError} */
27
+ export declare class ScriptContextError extends DatedError {
28
+ constructor(message: string, options?: ErrorOptions);
29
+ }
30
+ /** Error related to networking - extends {@linkcode DatedError} */
31
+ export declare class NetworkError extends DatedError {
32
+ constructor(message: string, options?: ErrorOptions);
33
+ }
@@ -9,16 +9,16 @@ export interface NanoEmitterOptions {
9
9
  publicEmit: boolean;
10
10
  }
11
11
  type NanoEmitterOnMultiTriggerOptions<TEvtMap extends EventsMap, TKey extends keyof TEvtMap = keyof TEvtMap> = {
12
- /** Calls the callback when one of the given events is emitted */
12
+ /** Calls the callback when one of the given events is emitted. Either one of or both of `oneOf` and `allOf` need to be set. If both are set, they behave like an "AND" condition. */
13
13
  oneOf?: TKey[];
14
- /** Calls the callback when all of the given events are emitted */
14
+ /** Calls the callback when all of the given events are emitted. Either one of or both of `oneOf` and `allOf` need to be set. If both are set, they behave like an "AND" condition. */
15
15
  allOf?: TKey[];
16
16
  };
17
17
  /** Options for the {@linkcode NanoEmitter.onMulti()} method */
18
18
  export type NanoEmitterOnMultiOptions<TEvtMap extends EventsMap, TKey extends keyof TEvtMap = keyof TEvtMap> = Prettify<{
19
19
  /** If true, the callback will be called only once for the first event (or set of events) that match the criteria */
20
20
  once?: boolean;
21
- /** If provided, can be used to abort the subscription if the signal is aborted */
21
+ /** If provided, can be used to cancel the subscription if the signal is aborted */
22
22
  signal?: AbortSignal;
23
23
  /** The callback to call when the event with the given name is emitted */
24
24
  callback: (event: TKey, ...args: Parameters<TEvtMap[TKey]>) => void;
@@ -80,11 +80,12 @@ export declare class NanoEmitter<TEvtMap extends EventsMap = DefaultEvents> {
80
80
  * `callback` (required) is the function that will be called when the conditions are met.
81
81
  *
82
82
  * 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.
83
- * If `signal` is provided, the subscription will be aborted when the given signal is aborted.
83
+ * If `signal` is provided, the subscription will be canceled when the given signal is aborted.
84
84
  *
85
85
  * If `oneOf` is used, the callback will be called when any of the matching events are emitted.
86
86
  * 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.
87
- * You may use a combination of the above two options, but at least one of them must be provided.
87
+ * If both `oneOf` and `allOf` are used together, the callback will be called when any of the `oneOf` events are emitted AND all of the `allOf` events have been emitted at least once.
88
+ * At least one of `oneOf` or `allOf` must be provided.
88
89
  *
89
90
  * @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.
90
91
  */
@@ -61,5 +61,5 @@ export declare function randRange(max: number, enhancedEntropy?: boolean): numbe
61
61
  * ```
62
62
  */
63
63
  export declare function roundFixed(num: number, fractionDigits: number): number;
64
- /** Rounds the given values at the given decimal place (same as in {@linkcode roundFixed()}) and checks if they are within the given range (0.5 by default) */
64
+ /** Rounds the given values at the given decimal place (same as in {@linkcode roundFixed()}, 1 decimal place by default) and checks if they are within the given range (0.5 by default) */
65
65
  export declare function valsWithin(a: number, b: number, dec?: number, withinRange?: number): boolean;
@@ -71,3 +71,9 @@ export declare function setImmediateTimeoutLoop(callback: () => void | unknown |
71
71
  * @throws An error if no exit method is available (e.g. in browser environments)
72
72
  */
73
73
  export declare function scheduleExit(code?: number, timeout?: number): void;
74
+ /**
75
+ * Returns the current call stack, starting at the caller of this function.
76
+ * @param asArray Whether to return the stack as an array of strings or a single string. Defaults to true.
77
+ * @param lines The number of lines to return from the stack. Defaults to Infinity (all of them).
78
+ */
79
+ export declare function getCallStack<TAsArray extends boolean = true>(asArray?: TAsArray, lines?: number): TAsArray extends true ? string[] : string;
@@ -1,12 +1,13 @@
1
- import { DataStore, type DataStoreData } from "./DataStore.ts";
2
- import type { SerializableVal } from "./types.ts";
1
+ import { DataStore, type DataStoreData } from "../DataStore.ts";
2
+ import type { SerializableVal } from "../types.ts";
3
3
  /**
4
4
  * A DataStore wrapper subclass that exposes internal methods for testing via the `direct_` prefixed methods.
5
5
  */
6
- export declare class TestDataStore<TData extends DataStoreData> extends DataStore<TData> {
6
+ export declare class DirectAccessDataStore<TData extends DataStoreData> extends DataStore<TData> {
7
7
  direct_getValue<TValue extends SerializableVal = string>(name: string, defaultValue: TValue): Promise<string | TValue>;
8
8
  direct_setValue(name: string, value: SerializableVal): Promise<void>;
9
9
  direct_renameKey(oldName: string, newName: string): Promise<void>;
10
10
  direct_deleteValue(name: string): Promise<void>;
11
11
  direct_setFirstInit(value: boolean): void;
12
+ direct_getMemData(): TData;
12
13
  }
@@ -0,0 +1,11 @@
1
+ import { type Assertion } from "vitest";
2
+ /**
3
+ * Run an assertion in "soft" mode: catch assertion errors and log them instead of throwing.
4
+ *
5
+ * @example ```ts
6
+ * softExpect(() => expect(actual).toBe(42));
7
+ * softExpect(actual, e => e.toBe(42), "optional message");
8
+ * ```
9
+ */
10
+ export declare function softExpect(assertion: () => void | unknown): void;
11
+ export declare function softExpect<T>(actual: T, assertion: (expectation: Assertion<T>) => void | unknown, message?: string): void;
@@ -13,6 +13,13 @@ export type ListLike = unknown[] | {
13
13
  /**
14
14
  * A type that offers autocomplete for the passed union but also allows any arbitrary value of the same type to be passed.
15
15
  * Supports unions of strings, numbers and objects.
16
+ * @example ```ts
17
+ * type MyUnion = LooseUnion<"foo" | "bar" | "baz">;
18
+ *
19
+ * const a: MyUnion = "foo"; // Valid
20
+ * const b: MyUnion = "qux"; // Also valid, even though "qux" is not part of the union
21
+ * const c: MyUnion = 123; // Not valid, because it's not the same type
22
+ * ```
16
23
  */
17
24
  export type LooseUnion<TUnion extends string | number | object> = (TUnion) | (TUnion extends string ? (string & {}) : (TUnion extends number ? (number & {}) : (TUnion extends Record<keyof any, unknown> ? (object & {}) : never)));
18
25
  /** Any class reference that can be instantiated with `new` */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sv443-network/coreutils",
3
3
  "libName": "@sv443-network/coreutils",
4
- "version": "2.0.3",
4
+ "version": "3.0.1",
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",
@@ -76,6 +76,9 @@
76
76
  "/CHANGELOG.md",
77
77
  "/LICENSE.txt"
78
78
  ],
79
+ "publishConfig": {
80
+ "provenance": true
81
+ },
79
82
  "scripts": {
80
83
  "lint": "eslint . && tsc --noEmit",
81
84
  "format": "eslint . --fix",