@superutils/rx 0.1.12 → 0.1.14

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/dist/index.d.ts CHANGED
@@ -1,36 +1,50 @@
1
1
  import { TimeoutOptions, IPromisE_Timeout } from '@superutils/promise';
2
- import { Subscribable, BehaviorSubject, Subject, Subscription } from 'rxjs';
3
- export { BehaviorSubject, Subject, Subscribable, Subscription, Unsubscribable, isObservable, skip } from 'rxjs';
4
- import { DeferredOptions, ThrottleOptions, DebounceOptions, ValueOrPromise, DropFirst, filter, FindOptions, find, search, sort, SortOptions, TypedMap } from '@superutils/core';
5
- export { TypedMap, objToMap } from '@superutils/core';
2
+ import { SubscriptionLike, Observable, BehaviorSubject, Subject, Subscription } from 'rxjs';
3
+ import { DeferredOptions, ValueOrPromise } from '@superutils/core';
6
4
 
7
- interface SubjectLike<T = unknown> {
8
- next: (value: T) => void;
9
- subscribe: (next: (value: T) => void, ...args: unknown[]) => SubscriptionLike;
10
- unsubscribe?: Unsubscribe;
11
- closed?: boolean;
12
- value?: T;
13
- }
14
- interface SubscriptionLike {
15
- closed?: boolean;
16
- unsubscribe: Unsubscribe;
17
- }
18
- /** Wrap a value from a signle subject or an array of subjects and values */
19
- type UnwrapSubjectValue<T> = T extends SubjectLike<infer V> ? V : T extends readonly unknown[] ? {
20
- -readonly [K in keyof T]: UnwrapSubjectValue<T[K]>;
5
+ type AsPromise_Defaults = Required<Pick<AsPromise_Options, 'invalidInputMsg' | 'timeoutMsg'>>;
6
+ type AsPromise_Options = Omit<TimeoutOptions, 'batchFunc'> & {
7
+ /** Error message used when input is not a valid observable */
8
+ invalidInputMsg?: string;
9
+ /** Number of emit values to skip/ignore. Default: `0` */
10
+ skip?: number;
11
+ /** Error message to use when times out. */
12
+ timeoutMsg?: string | Error;
13
+ };
14
+ type Unsubscribe = SubscriptionLike['unsubscribe'];
15
+ type UnsubscribeCandidate = Unsubscribe | SubscriptionLike | SubscriptionLike[] | unknown[] | object | undefined | null | boolean;
16
+ /**
17
+ * Recursively extracts the emitted value type from an Observable, an array of Observables,
18
+ * or returns the type itself if it's a static value.
19
+ *
20
+ * @template T - The input type to unwrap.
21
+ */
22
+ type UnwrapSourceValue<T> = T extends Observable<infer V> ? V : T extends readonly unknown[] ? {
23
+ -readonly [K in keyof T]: UnwrapSourceValue<T[K]>;
24
+ } : T;
25
+ /**
26
+ * Recursively extracts the emitted value type from an Observable or an array of Observables,
27
+ * similar to {@link UnwrapSourceValue}.
28
+ *
29
+ * However, if the Observable does not have a `.value` property (i.e., it is not a `BehaviorSubject`),
30
+ * the resulting type is unioned with `undefined` to reflect that an initial value may not be
31
+ * immediately available.
32
+ *
33
+ * @template T - The input type to unwrap.
34
+ */
35
+ type UnwrapSourceValueStrict<T> = T extends Observable<infer V> ? T extends {
36
+ value: infer V;
37
+ } ? V : V | undefined : T extends readonly unknown[] ? {
38
+ -readonly [K in keyof T]: UnwrapSourceValueStrict<T[K]>;
21
39
  } : T;
22
- type Unsubscribe = () => void;
23
- type UnsubscribeCandidates = Unsubscribe | SubscriptionLike | SubscriptionLike[] | unknown[] | Record<PropertyKey, unknown> | undefined | null | boolean;
24
40
 
25
41
  /**
26
42
  * @summary Create a promise using RxJS subject and wait until an expected value is received
27
43
  *
28
- * @param subject RxJS subject or observable
44
+ * @param input$ RxJS subject or observable
29
45
  * @param expectedValue (optional) if undefined, will resolve as soon as any value is received.
30
46
  * If function, it should return true or false to indicate whether the value should be resolved.
31
- * @param timeoutOrOptions (optional)
32
- * @param timeoutOrOptions.timeout (optional) timeout duration in milliseconds if no value received within given time
33
- * @param timeoutOrOptions.timeoutMsg (optional) error message to use when times out.
47
+ * @param timeoutOrOptions (optional) timeout duration or options
34
48
  *
35
49
  * @returns timeout promise
36
50
  *
@@ -50,79 +64,108 @@ type UnsubscribeCandidates = Unsubscribe | SubscriptionLike | SubscriptionLike[]
50
64
  * subjectAsPromise(subject, 5).then(value => console.log('Expected 5, received ', value))
51
65
  * ```
52
66
  */
53
- declare const asPromise: <T = unknown>(subject: Subscribable<T> | SubjectLike<T>, expectedValue?: T | ((value: T) => boolean), timeoutOrOptions?: number | (Omit<TimeoutOptions, "batchFunc"> & {
54
- timeoutMsg?: string | Error;
55
- })) => IPromisE_Timeout<T>;
67
+ declare const asPromise: {
68
+ <T = unknown>(input$: Observable<T>, expectedValue?: T | ((value: T) => boolean), timeoutOrOptions?: number | AsPromise_Options): IPromisE_Timeout<T>;
69
+ defaults: Required<Pick<AsPromise_Options, "invalidInputMsg" | "timeoutMsg">>;
70
+ };
56
71
 
57
- /** Symbol used to signal to ignore an update when using a `valueModifier()` callback with {@link copyRxSubject} */
72
+ /** Symbol used to signal to ignore an update when using a `transform()` callback with {@link copyRx} */
58
73
  declare const IGNORE_UPDATE_SYMBOL: unique symbol;
59
- type CopyRxSubjectOptions<ThisArg> = {
74
+ type CopyRx_Options<TOut, ThisArg> = {
75
+ /**
76
+ * Debounce or throttle delay in milliseconds.
77
+ * If provided, updates to the output subject will be delayed accordingly.
78
+ *
79
+ * Default: `undefined`
80
+ */
60
81
  delay?: number;
82
+ /**
83
+ * The initial value for the output subject.
84
+ * This is particularly useful when creating a new `BehaviorSubject` internally.
85
+ */
86
+ initialValue?: TOut;
87
+ /**
88
+ * An optional destination subject to which values will be copied.
89
+ * If provided, this subject will be returned by the function.
90
+ * If not provided, a new `BehaviorSubject` is created.
91
+ */
92
+ output?: BehaviorSubject<TOut> | Subject<TOut>;
93
+ /**
94
+ * Number of initial emissions to skip from the input observable(s).
95
+ *
96
+ * If an array is provided, each element corresponds to the observable at the same index.
97
+ *
98
+ * Default: `1` for `BehaviorSubject` (to avoid redundant updates of the initial value), otherwise `0`.
99
+ */
100
+ skipEmits?: number | (number | undefined | null)[];
101
+ /** Use this only if */
102
+ transformSequentially?: boolean;
61
103
  } & DeferredOptions<ThisArg>;
62
104
  /**
63
- * Value modifier function definition for {@link copyRxSubject}
105
+ * Value modifier function definition for {@link copyRx}.
106
+ *
107
+ * This function is executed for each new value emitted by the input observable(s).
108
+ *
109
+ * - **`this` context**: The `this` context within the `transform` function can be set using the `options.thisArg`
110
+ * - **Ignoring updates**: Returning {@link copyRx.IGNORE} will prevent the output observable from emitting the current update.
111
+ * - **Asynchronous transformations**: If a `Promise` is returned, the output observable will only be updated once the promise resolves.
112
+ * However, this can introduce race conditions if new input values arrive before the previous promise resolves.
113
+ * To prevent such race conditions, enable the `transformSequentially` flag in `CopyRx_Options`.
114
+ * - **Error handling**: If the `transform` function throws an error, the update is gracefully ignored, and the `onError` callback (if provided in `CopyRx_Options`) will be invoked.
115
+ */
116
+ type CopyRx_Transform<TIn = unknown, TOut = TIn, ThisArg = unknown> = (this: ThisArg,
117
+ /**
118
+ * The current value from the input observable (or an array of values if multiple sources are provided).
119
+ *
120
+ * - If `input` to `copyRx` is a single observable, `newValue` will be the value emitted by that observable.
121
+ * - If `input` is an array of observables/values, `newValue` will be an array containing the latest values from all sources.
64
122
  *
65
- * Returning {@link IGNORE_UPDATE_SYMBOL} will ignore the update.
123
+ * **Note:** If the transformation is asynchronous, a `BehaviorSubject` output will emit `undefined` (or the
124
+ * `initialValue`) immediately upon subscription until the first `transform` promise resolves.
66
125
  */
67
- type ValueModifier<T = unknown, TCopy = T> = (newValue: T, previousValue: TCopy | undefined, copy$: SubjectLike<TCopy>) => TCopy | typeof IGNORE_UPDATE_SYMBOL;
126
+ newValue: TIn,
68
127
  /**
69
- * @function copyRxSubject
70
- * @summary returns a subject that automatically copies the value(s) of the source subject(s).
71
- *
72
- * The the changes are applied unidirectionally from the source subject to the destination subject.
73
- * Changes on the destination subject is NOT applied back into the source subject.
74
- *
75
- * @param source$ RxJS source subject(s). If Array provied, value of `copy$` will also be an Array by default,
76
- * unless a different type is provided by `copy$` or `valueModifier`.
77
- *
78
- * @param copy$ (optional) RxJS copy/destination subject.
79
- * If `undefined`, a new subject will be created.
80
- * Value type will be inferred automatically based on `copy$`, `valueModifier` and `source$`.
81
- * Default: `new BehaviorSubject()`
82
- * @param valueModifier (optional) callback to modify the value (an thus type) before copying from `source$`.
83
- * Accepts async functions. Function invocation errors will be gracefully ignored.
84
- * PS: If the very first invokation returns `IGNORE_UPDATE_SYMBOL`, the value of `copy$.value` will be undefined.
85
- * Args: `newValue, previousValue, copy$`
86
- * @param {CopyRxSubjectOptions} options (optional) options to enable debouce/throttling `copy$` value changes.
87
- * @param options.delay (optional) delay in milliseconds. Default: `0`
88
- * @param options.onError (optional) callback invoked whenever `valueModifier` execution fails.
89
- * @param options.throttle (optional) `true`: throttle, `false`: debounce. Default: `false`
90
- *
91
- * @returns `copy$` if provided, otherwise, a new `BehaviorSubject` instance
128
+ * The destination observable/subject where the results are being copied.
129
+ */
130
+ output: Observable<TOut>) => ValueOrPromise<TOut> | typeof IGNORE_UPDATE_SYMBOL;
131
+ /**
132
+ * Returns a subject that automatically copies the value(s) of the source subject(s).
133
+ *
134
+ * Established a unidirectional data flow from source(s) to a destination subject.
135
+ * Changes to the destination subject are NOT applied back to the source.
136
+ *
137
+ * @param source$ RxJS input observable(s) or static value(s). If an array is provided,
138
+ * the output subject will emit an array of values by default.
139
+ * @param transform (optional) A function to map or filter values before they are emitted by the output subject.
140
+ * Supports async functions. If it throws, the update is ignored.
141
+ * @param options (optional) Configuration for timing (delay), initial state, and error handling.
142
+ *
143
+ * @returns The destination subject (either the one provided in `options.output` or a new `BehaviorSubject`).
92
144
  *
93
145
  * @example
94
146
  * #### Auto-copy values from a single subject
95
147
  * ```typescript
96
- * import { BehaviorSubject, copyRxSubject } from '@superutils/rx'
148
+ * import { BehaviorSubject, copyRx } from '@superutils/rx'
97
149
  *
98
150
  * const number$ = new BehaviorSubject(1)
99
- * const even$ = copyRxSubject(
100
- * // source subject
101
- * number$,
102
- * // create and return a new BehaviorSubject. An existing RxJS subject can also be provided here.
103
- * null,
104
- * // copy and transform the value from number$
105
- * newValue => newValue % 2 === 0,
106
- * // debounce/throttle value changes to even$
107
- * // {
108
- * // delay: 300 //
109
- * // throttle: false,
110
- * // }
151
+ * const even$ = copyRx(
152
+ * number$, // input observable
153
+ * newValue => newValue % 2 === 0 ? newValue : copyRx.IGNORE,
111
154
  * )
112
155
  * // subscribe to even$ changes
113
- * even$.subscribe(console.log)
114
- * number$.next(2) // prints: true
115
- * number$.next(3) // print: false
156
+ * even$.subscribe(console.log) // prints: 2
157
+ * number$.next(2)
158
+ * number$.next(3) // (ignored)
116
159
  * ```
117
160
  *
118
161
  * @example
119
162
  * #### Auto-copy from an array of subjects & values
120
163
  * ```javascript
121
- * import { BehaviorSubject, copyRxSubject } from '@superutils/rx'
164
+ * import { BehaviorSubject, copyRx } from '@superutils/rx'
122
165
  *
123
166
  * const theme$ = new BehaviorSubject('dark')
124
167
  * const userId$ = new BehaviorSubject('username')
125
- * const settings$ = copyRxSubject(
168
+ * const settings$ = copyRx(
126
169
  * [
127
170
  * theme$,
128
171
  * userId$,
@@ -135,649 +178,13 @@ type ValueModifier<T = unknown, TCopy = T> = (newValue: T, previousValue: TCopy
135
178
  * )
136
179
  * ```
137
180
  */
138
- declare function copyRxSubject<TCopy extends T, TSource$, T = UnwrapSubjectValue<TSource$>, TCopy$ extends SubjectLike<TCopy> = BehaviorSubject<TCopy>, ThisArg = unknown>(source$: TSource$, copy$?: TCopy$ | null, valueModifier?: ValueModifier<T, TCopy> | null, options?: CopyRxSubjectOptions<ThisArg>): TCopy$;
139
- declare function copyRxSubject<TCopy, TSource$, T = UnwrapSubjectValue<TSource$>, TCopy$ extends SubjectLike<TCopy> = BehaviorSubject<TCopy>, ThisArg = unknown>(source$: TSource$, copy$: TCopy$ | undefined | null, valueModifier: ValueModifier<T, TCopy> | null, options?: CopyRxSubjectOptions<ThisArg>): TCopy$;
140
- declare namespace copyRxSubject {
141
- var defaults: {
181
+ declare function copyRx<TOut, Source$ extends Observable<any> | unknown[] = Observable<any> | unknown[], TIn = UnwrapSourceValueStrict<Source$>, Copy$ extends BehaviorSubject<TOut> | Subject<TOut> = BehaviorSubject<TOut>, ThisArg = unknown>(source$: Source$, transform?: CopyRx_Transform<TIn, TOut, ThisArg> | null, options?: CopyRx_Options<TOut, ThisArg>): Copy$;
182
+ declare function copyRx<TOut, Source$ extends Observable<any> | unknown[] = Observable<any> | unknown[], TIn = UnwrapSourceValueStrict<Source$>, Copy$ extends BehaviorSubject<TOut> | Subject<TOut> = BehaviorSubject<TOut>, ThisArg = unknown>(source$: Source$, transform: CopyRx_Transform<TIn, TOut, ThisArg>, options?: CopyRx_Options<TOut, ThisArg>): Copy$;
183
+ declare namespace copyRx {
184
+ var defaults: Required<Omit<DeferredOptions<unknown>, "thisArg"> & {
142
185
  delay: number;
143
- } & DeferredOptions<unknown>;
144
- var IGNORE_UPDATE_SYMBOL: typeof IGNORE_UPDATE_SYMBOL;
145
- }
146
-
147
- /** Throttle & debounce related options */
148
- type DelayOptions = ({
149
- throttle: true;
150
- } & Omit<ThrottleOptions, 'onError' | 'thisArg'>) | ({
151
- throttle?: false;
152
- } & Omit<DebounceOptions, 'onError' | 'thisArg'>);
153
- /**
154
- * Categorizes errors encountered during DataStorage operations.
155
- *
156
- * These types are passed to the `onError` callback to help identify which phase of the
157
- * data lifecycle (reading, writing, or processing) failed.
158
- */
159
- declare enum OnErrorType {
160
- /** Occurs when the user-provided `onChange` callback throws an exception. */
161
- onChange = "onChange",
162
- /** Occurs when the user-provided `parse` function fails to process the raw storage string. */
163
- parse = "parse",
164
- /**
165
- * Occurs when the default `JSON.parse` fallback fails.
166
- * This usually happens if the data in the underlying storage is corrupted or not valid JSON.
167
- */
168
- parse_json = "parse-json",
169
- /** Occurs when the user-provided `stringify` function fails to serialize the data Map. */
170
- stringify = "stringify",
171
- /**
172
- * Occurs when the default `JSON.stringify` fallback fails.
173
- * This may happen if the Map contains circular references or other non-serializable values.
174
- */
175
- stringify_json = "stringify-json",
176
- /** Occurs when the attempt to save data to the underlying storage (e.g., `localStorage.setItem`) fails. */
177
- write = "write"
178
- }
179
- /** Storage type with only properties that are used by `DataStorage` */
180
- type StorageCompact = Pick<Storage, 'getItem' | 'setItem'>;
181
- /** Initial options provided through the constructor */
182
- type StorageOptions<Key, Value, CacheDisabled extends boolean = false> = {
183
- /**
184
- * An optional `Map` used to seed the storage if no persistent data is found for the instance.
185
- *
186
- * **Data Precedence:**
187
- * Persistent data associated with the instance's specific `name` takes priority. This value is
188
- * only utilized if the storage entry for that `name` does not exist (e.g., first-time use).
189
- *
190
- * **Initialization Behavior:**
191
- * - If provided and non-empty, the instance initializes immediately during construction.
192
- * - Otherwise, initialization is lazy, occurring upon an explicit `init()` call or the first read/write operation.
193
- *
194
- * **Type Inference:**
195
- * When provided, it enables automatic inference of the `Key` and `Value` generic types.
196
- * If omitted, these default to `unknown` and `object` respectively, unless explicitly defined.
197
- *
198
- * @default undefined
199
- */
200
- initialValue?: Map<Key, Value>;
201
- } & Pick<Partial<IDataStorage<Key, Value, CacheDisabled>>, 'cacheDisabled' | 'onChange' | 'onError' | 'parse' | 'spaces' | 'storage' | 'stringify'> & (CacheDisabled extends false ? Pick<Partial<IDataStorage<Key, Value, CacheDisabled>>, 'delay' | 'delayOptions'> : {
202
- delay?: never;
203
- delayOptions?: never;
204
- });
205
- type StorageParseFn<ResultMap, ThisArg> = (this: ThisArg, text: string | null | undefined) => ResultMap | void;
206
- type StorageSearch<K, V, MatchExact extends boolean = false, AsMap extends boolean = true> = (...args: DropFirst<Parameters<typeof search<K, V, MatchExact, AsMap>>>) => ReturnType<typeof search<K, V, MatchExact, AsMap>>;
207
- type StorageSort<K, V> = (...args: StorageSortByComparator<K, V> | StorageSortByPropertyName<V> | StorageSortByKey) => Map<K, V>;
208
- type StorageSortByComparator<K, V> = [
209
- comparator: Parameters<typeof sort<K, V>>[1],
210
- options?: StorageSortOptions
211
- ];
212
- type StorageSortByKey = [byKey: true, options?: StorageSortOptions];
213
- type StorageSortByPropertyName<V> = [
214
- propertyName: keyof V & string,
215
- options?: StorageSortOptions
216
- ];
217
- type StorageSortOptions = SortOptions & {
218
- save?: boolean;
219
- };
220
- type StorageStringify<Data, ThisArg> = (this: ThisArg, data: Data) => string | undefined | void;
221
- type StorageToJSON<K, V> = (replacer?: null | ((key: K, value: V) => unknown), spacing?: string | number, data?: Map<K, V>) => string;
222
- interface IDataStorage<Key, Value, CacheDisabled extends boolean = false> {
223
- /** Disable in-memory cache and only directly read/write from storage (local storage or JSON fle) */
224
- readonly cacheDisabled: CacheDisabled;
225
- /**
226
- * Debounce/throttle delay duration in milliseconds for writing to storage when caching is enabled.
227
- *
228
- * Increasing this value can improve performance when dealing with large datasets
229
- * or frequent updates by reducing the number of write operations.
230
- *
231
- * Default: `300`
232
- */
233
- readonly delay: number;
234
- readonly delayOptions?: DelayOptions;
235
- /**
236
- * Indicates wherether storage has been initialized (`init()` function invoked).
237
- */
238
- readonly initialized: boolean;
239
- /**
240
- * Storage name. Filename (NodeJS) or property name (browser LocalStorage).
241
- * If empty string or undefined, data will not be saved to storage and will only work in-memory.
242
- *
243
- * Default: `''`
244
- */
245
- readonly name?: string | null;
246
- /**
247
- * A callback function executed whenever a data change occurs within the storage.
248
- *
249
- * This hook allows for reactive side-effects. If the callback throws an error or returns a
250
- * rejected Promise, the exception is caught gracefully and redirected to the {@link onError}
251
- * callback with the type {@link OnErrorType.onChange}.
252
- *
253
- * Note: Execution of this callback is managed by internal subscriptions and will stop
254
- * firing once {@link unsubscribe} is called.
255
- */
256
- onChange?: (this: IDataStorage<Key, Value, CacheDisabled>, data: Map<Key, Value>) => ValueOrPromise<void | Map<Key, Value>>;
257
- /**
258
- * A global error handler invoked whenever an internal operation fails.
259
- *
260
- * It captures failures in the following areas:
261
- * - Data parsing and serialization (JSON or custom logic).
262
- * - Storage access (e.g., `localStorage` quota or permission errors).
263
- * - Execution of user-provided callbacks like {@link onChange}.
264
- *
265
- * **Note:** If this handler itself throws an error, the exception is
266
- * ignored gracefully to prevent application crashes during storage cycles.
267
- */
268
- onError?: (this: IDataStorage<Key, Value, CacheDisabled>, err: unknown, type: OnErrorType) => ValueOrPromise<void>;
269
- /**
270
- * A callback to customize the deserialization of data read from storage.
271
- *
272
- * This allows you to transform the raw string from the underlying storage back into a
273
- * `Map<Key, Value>`. It serves as the functional inverse of {@link stringify}.
274
- *
275
- * **Fallback Behavior:** The system falls back to internal `JSON.parse` logic if `parse`:
276
- * - is not a non-function
277
- * - throws an error
278
- * - returns a non-Map value,
279
- *
280
- * **Error Triggers:**
281
- * - If this custom `parse` function fails: {@link onError} is triggered with {@link OnErrorType.parse}.
282
- * - If the default `JSON.parse` fallback fails: {@link onError} is triggered with {@link OnErrorType.parse_json}.
283
- */
284
- parse?: StorageParseFn<Map<Key, Value>, IDataStorage<Key, Value, CacheDisabled>>;
285
- /** Get the number of items */
286
- readonly size: number;
287
- /** Number of spaces to use when stringifying. Default: `undefined` */
288
- spaces?: number;
289
- /**
290
- * `LocalStorage` or equivalent storage instance to be used as the underlying storage and to read & write from.
291
- *
292
- * Notes:
293
- * - Ignored when `name` is falsy (in-memory only mode)
294
- * - For NodeJS or equivalent, an instance of `LocalStorage` from "node-localstoarge" NPM module can be used.
295
- * - If `undefined`, will not attempt to use `globalThis.localStorage`, if available
296
- * - If `null`, will not attempt to use `globalThis.localStorage`
297
- *
298
- * Default:
299
- * - browser: `localStorage`
300
- * - node: `undefined` (in-memory mode)
301
- */
302
- readonly storage?: StorageCompact | null;
303
- /**
304
- * A callback function to customize the serialization of data before it is written to storage.
305
- *
306
- * This allows you to transform the data `Map<Key, Value>` into a string format suitable
307
- * for the underlying storage (e.g., JSON). It serves as the functional inverse of {@link parse}.
308
- *
309
- * Use this to sanitize data, remove circular references, or optimize the storage size by
310
- * only persisting necessary fields.
311
- *
312
- * **Fallback Behavior:**
313
- * If this function is not defined, throws an error, or returns `undefined` or a non-string value,
314
- * the system falls back to internal `JSON.stringify` logic.
315
- *
316
- * **Error Triggers:**
317
- * - If this custom `stringify` function fails: {@link onError} is triggered with {@link OnErrorType.stringify}.
318
- * - If the default `JSON.stringify` fallback fails: {@link onError} is triggered with
319
- * {@link OnErrorType.stringify_json}.
320
- *
321
- * @param data a map of all values stored in this storage
322
- *
323
- * @returns string or undefined
324
- *
325
- * @example
326
- * #### Sanitize data before saving
327
- * ```javascript
328
- * import { DataStorage } from '@superutils/rx'
329
- *
330
- * const stringify = data => {
331
- * // Convert Map to an array of entries, removing sensitive fields
332
- * const entries = Array.from(data).map(([id, user]) => {
333
- * const { password, ...publicData } = user
334
- * return [id, publicData]
335
- * })
336
- * return JSON.stringify(entries)
337
- * }
338
- * const storage = new DataStorage('users', { stringify })
339
- * ```
340
- */
341
- stringify?: StorageStringify<Map<Key, Value>, IDataStorage<Key, Value, CacheDisabled>>;
342
- /**
343
- * The underlying RxJS Subject that serves as the primary reactive interface for observing data modifications.
344
- *
345
- * Its implementation type is determined by the caching strategy:
346
- * - **BehaviorSubject**: Used when caching is enabled.
347
- * It maintains the current state and emits it immediately to new subscribers.
348
- * - **Subject**: Used when caching is disabled.
349
- * It acts as a pure event pipe, emitting updates only at the moment they occur without retaining an in-memory copy.
350
- */
351
- readonly subject: CacheDisabled extends true ? Subject<Map<Key, Value>> : BehaviorSubject<Map<Key, Value>>;
352
- /** Clear all items */
353
- readonly clear: () => IDataStorage<Key, Value, CacheDisabled>;
354
- /** Delete one or more items by their respective keys */
355
- readonly delete: (key: Key | Key[]) => IDataStorage<Key, Value, CacheDisabled>;
356
- /** Filter items by predicate */
357
- readonly filter: <AsArray extends boolean = false>(...args: DropFirst<Parameters<typeof filter<Key, Value, AsArray>>>) => ReturnType<typeof filter<Key, Value, AsArray>>;
358
- /** Find an item by predicate or search criteria */
359
- readonly find: <IncludeKey extends boolean = false>(predicateOrOptions: FindOptions<Key, Value, IncludeKey> | Parameters<IDataStorage<Key, Value, CacheDisabled>['filter']>[0]) => ReturnType<typeof find<Key, Value, IncludeKey>>;
360
- /** Get item by key */
361
- readonly get: (key: Key) => Value | undefined;
362
- /**
363
- * Get all items
364
- *
365
- * @param forceUpdate (optional) if `true` and cache is enabled, reads & updates data directly from storage
366
- * Default: `false`
367
- */
368
- readonly getAll: (forceUpdate?: boolean) => Map<Key, Value>;
369
- /** Check if key exists */
370
- readonly has: (key: Key) => boolean;
371
- /**
372
- * Initializes storage and sets up internal subscriptions.
373
- *
374
- * Manual invocation is not typically necessary, as initialization occurs automatically
375
- * in one of the following scenarios:
376
- * - During construction, if an `initialValue` with at least one entry is provided.
377
- * - On the first attempt to read or write data.
378
- *
379
- * @param initialValue An optional map to initialize the storage with if it's currently empty.
380
- * @returns `true` if initialization was successful, or `false` if the storage was already initialized.
381
- */
382
- readonly init: (initialValue?: Map<Key, Value>) => boolean;
383
- /** Get all keys */
384
- readonly keys: () => Key[];
385
- /** Map each item on the data to an Array */
386
- readonly map: <T = unknown>(callback: (value: Value, key: Key, entries: [Key, Value][], index: number) => T) => T[];
387
- /**
388
- * Reads and parses data directly from the persistent storage medium.
389
- *
390
- * This operation is synchronous and does not trigger reactive updates via `subject`.
391
- * It is useful for debugging custom `parse` logic or manual data retrieval.
392
- *
393
- * @param dataStr (optional) A raw string to parse. If omitted, the method fetches
394
- * the current value associated with the instance `name` from the underlying `storage`.
395
- */
396
- readonly read: (dataStr?: string | null) => Map<Key, Value>;
397
- /**
398
- * Search through the stored data (`Map<Key, Value>`).
399
- * It supports both a global search (using a string or RegExp) across all properties
400
- * of an item, and a detailed, field-specific search using a query object.
401
- *
402
- * @param options The search criteria. See {@link SearchOptions} for available properties.
403
- *
404
- * @returns A `Map` or an `Array` containing the matched items, based on the `asMap` option.
405
- *
406
- * @example
407
- * #### Search for users in a specific city
408
- * ```javascript
409
- * import { DataStorage } from '@superutils/rx'
410
- *
411
- * const storage = new DataStorage('users', {
412
- * initialValue: new Map([
413
- * [1, { name: 'John Doe', city: 'New York' }],
414
- * [2, { name: 'Jane Doe', city: 'London' }],
415
- * [3, { name: 'Peter Jones', city: 'New York' }],
416
- * ])
417
- * })
418
- *
419
- * const nyUsers = storage.search({ query: { city: 'New York' } })
420
- * console.log(nyUsers.size) // 2
421
- * ```
422
- */
423
- readonly search: <MatchExact extends boolean = false, AsMap extends boolean = true>(...args: DropFirst<Parameters<typeof search<Key, Value, MatchExact, AsMap>>>) => ReturnType<typeof search<Key, Value, MatchExact, AsMap>>;
424
- /** Set item by key */
425
- readonly set: <K extends Key, V extends Value>(key: K, value: V) => IDataStorage<Key, Value, CacheDisabled>;
426
- /**
427
- * Set multiple entries at once and/or replace the storage entries
428
- *
429
- * @param data (optional) Data to add. Default: `new Map()`
430
- * @param replace (optional) Whether to merge with or replace current data.
431
- * - `true`: replace all entries with `data`
432
- * - `false`: merge with current data (existing entries with matching keys will be overwritten)
433
- *
434
- * Default: `false`
435
- */
436
- readonly setAll: (data?: Map<Key, Value>, replace?: boolean) => IDataStorage<Key, Value, CacheDisabled>;
437
- /**
438
- * Sort items in the storage.
439
- *
440
- * @param nameOrComparator Criteria to sort by. Accepts one of the following:
441
- * - `function`: A comparator function to sort the data.
442
- * - `string`: A property name of the value object to sort by.
443
- * - `true`: Sorts the map by its keys.
444
- * @param options (optional) Sorting options.
445
- * @param options.save (optional) Whether to save the sorted data back to storage (localStorage/file).
446
- *
447
- * @returns The sorted Map.
448
- */
449
- readonly sort: StorageSort<Key, Value>;
450
- /** Convert list of items (Map) to 2D Array */
451
- readonly toArray: () => [Key, Value][];
452
- /** Convert list of items (Map) to JSON string of 2D Array */
453
- readonly toJSON: StorageToJSON<Key, Value>;
454
- /** Convert list of items into an object */
455
- readonly toObject: <T extends object = object>(data?: Map<Key, Value>) => T;
456
- /** Convert list of items (Map) to JSON string of 2D Array */
457
- readonly toString: (data?: Map<Key, Value>) => string;
458
- /**
459
- * Unsubscribe from all internal subscriptions.
460
- *
461
- * This will result in:
462
- * - Automatic writing to storage being disabled (manual writes via `instance.write()` will still work).
463
- * - The `onChange` callback no longer being triggered.
464
- * - The instance stopping listening to force update cache triggers.
465
- */
466
- readonly unsubscribe: () => void;
467
- /** Get all values as an array */
468
- readonly values: () => Value[];
469
- /**
470
- * Write data to the underlying storage (localStorage or file).
471
- *
472
- * @param data (optional) Data to write.
473
- * - If provided, it overwrites the storage.
474
- * - If not provided, the current in-memory data is used (if cache is enabled).
475
- * @returns `true` if the write was successful, `false` otherwise.
476
- */
477
- readonly write: (data?: Map<Key, Value>) => void;
478
- }
479
- interface IObjectStorage<T extends object, CacheDisabled extends boolean = false, ObjectMap extends TypedMap<T> = TypedMap<T>> extends IDataStorage<keyof T, T[keyof T], CacheDisabled> {
480
- get<Key extends keyof T>(key: Key): T[Key] | undefined;
481
- getAll(forceRead?: boolean): TypedMap<T>;
482
- parse?: StorageParseFn<ObjectMap, IObjectStorage<T, CacheDisabled>>;
483
- set<Key extends keyof T, Value extends T[Key]>(key: Key, value: Value): IObjectStorage<T, CacheDisabled>;
484
- setAll(data?: ObjectMap, replace?: boolean): IObjectStorage<T, CacheDisabled>;
485
- stringify?: StorageStringify<ObjectMap, IObjectStorage<T, CacheDisabled>>;
486
- toObject<O extends object = T>(data?: Map<keyof T, T[keyof T]>): O;
487
- }
488
-
489
- /**
490
- * RxJS Subject to trigger forced update of cached data from underlying storage of {@link DataStorage} instances.
491
- *
492
- * `value`: determines which cache-enabled storage instances to be updated
493
- * - name (`string` | `string[]`): update all instances with a specific name(s)
494
- * - global (`true`): update all instances globally
495
- *
496
- * @example
497
- * ```javascript
498
- * import { DataStorage, forceUpdateCache$ } from '@superutils/rx'
499
- *
500
- * const names = ['products', 'users']
501
- *
502
- * // Update all DataStorage instances by a list of their names
503
- * forceUpdateCache$.next(names)
504
- * // alternatively: DataStorage.forceUpdateCache(names)
505
- *
506
- * // Update all DataStorage instances with a specific name
507
- * forceUpdateCache$.next(names[0])
508
- * // alternatively: DataStorage.forceUpdateCache(names[0])
509
- *
510
- * // Update every single instance of DataStorage that uses storage (has a "name" property)
511
- * forceUpdateCache$(true)
512
- * // alternatively: DataStorage.forceUpdateCache(true)
513
- * ```
514
- *
515
- * @example
516
- *
517
- * #### Practical example
518
- * ```typescript
519
- * import { DataStorage } from '@superutils/rx'
520
- *
521
- * const name = 'user-profile'
522
- * type User = {
523
- * age: number
524
- * name: string
525
- * roles?: string[]
526
- * }
527
- * const userStore = DataStorage.(name, { delay: 0 }) // delay is set to zero to simplify the example
528
- * userStore.set('name', 'John Doe')
529
- * userStore.set('name', 'John Doe')
530
- * ```
531
- */
532
- declare const forceUpdateCache$: Subject<string | boolean | string[]>;
533
- /**
534
- *
535
- *
536
- * @remarks
537
- * **On the `This` template parameter:**
538
- * Using `This` as a self-referential template is a **good practice** in this context because:
539
- * - It provides accurate **type inference** for method return types that depend on the generic parameters.
540
- * - It enables **type-safe property access** through `This['methodName']`, allowing the implementation
541
- * to reference interface contracts without circular dependencies or casting issues.
542
- * - It allows **fluent API chains** (returning `this`) while maintaining proper generic type information.
543
- * - It prevents **type widening** that would occur if methods returned the concrete class type instead
544
- * of the interface type, which is important for generic constraints and polymorphism.
545
- *
546
- * However, it increases **cognitive complexity** and is only warranted when:
547
- * - The class implements a complex generic interface with interdependent type parameters.
548
- * - Type-safe property references are essential to avoid runtime errors or casting.
549
- * - Fluent interfaces or chaining is a core API feature.
550
- *
551
- *
552
- */
553
- /**
554
- * A generic, reactive data storage class that provides a Map-like interface with advanced features
555
- * such as search, filtering, and sorting. Supports both in-memory caching and persistent storage
556
- * (LocalStorage in browsers, JSON files in NodeJS via `node-localstorage` NPM module).
557
- *
558
- * #### Notes:
559
- * - **Performance**: `DataStorage` is optimized for small to medium datasets.
560
- * - For datasets > 1MB, consider increasing the `delay` option to reduce write frequency.
561
- * - It is **NOT** recommended for datasets larger than 3MB due to synchronous serialization costs.
562
- * - **RxJS Integration**: Built on RxJS for reactive data handling, though no prior RxJS knowledge is required.
563
- * - **Storage Behavior**:
564
- * - If `name` is omitted, the instance operates in-memory only and data is not persisted to storage.
565
- * - If `cacheDisabled` is `true`, data is not kept in memory; every read/write operation accesses the underlying
566
- * storage directly.
567
- *
568
- * @template Key The type of keys stored in the map.
569
- * @template Value The type of values stored in the map.
570
- * @template CacheDisabled A literal boolean type indicating whether in-memory caching is disabled.
571
- * @template This A self-referential interface type extending {@link IDataStorage} used for accurate
572
- * method signature inference and type-safe property access. This allows method implementations to
573
- * reference their return types and other method signatures through the interface definition.
574
- *
575
- * @see {@link forceUpdateCache$} for cache invalidation across instances.
576
- * @see {@link DataStorage.fromObject} for object-oriented storage initialization.
577
- *
578
- * @example
579
- * #### Browser Usage 1: use like a map
580
- * ```javascript
581
- * import { DataStorage } from '@superutils/rx'
582
- *
583
- * const userStorage = new DataStorage('users')
584
- * userStorage.set(1, { name: 'Alice', age: 30 })
585
- * const user = userStorage.get(1)
586
- * console.log(user) // prints: {name: 'Alice', age: 30}
587
- * ```
588
- *
589
- * @example
590
- * #### Browser Usage 2:
591
- * ```javascript
592
- * import { DataStorage } from '@superutils/rx'
593
- * import fetch from '@superutils/fetch'
594
- *
595
- * const { products } = await fetch.get('[DUMMYJSON-DOT-COM]/products')
596
- * const storage = new DataStorage('products', {
597
- * initialValue: new Map(products.map(p => [p.id, p])) // convert to Map
598
- * })
599
- *
600
- * // print product with id `1`
601
- * console.log(storage.get(1))
602
- *
603
- * // search for items
604
- * const searchResult = storage.search({
605
- * query: { availabilityStatus: 'low' }
606
- * })
607
- * console.log(searchResult)
608
- * ```
609
- * @example
610
- * #### NodeJS Usage
611
- * ```javascript
612
- * import { DataStorage } from '@superutils/rx'
613
- * import fetch from '@superutils/fetch'
614
- * import { LocalStorage } from 'node-localstorage'
615
- *
616
- * // Add localStorage alternative for NodeJS that reads and writes to JSON files.
617
- * // This is not necessary for browsers.
618
- * globalThis.localStorage = new LocalStorage('./data', 1e7)
619
- *
620
- * const storage = new DataStorage('products')
621
- * const { products } = await fetch.get('[DUMMYJSON-DOT-COM]/products')
622
- * // save all items to storage
623
- * storage.setAll(
624
- * new Map(products.map(p => [p.id, p])), // convert to Map
625
- * )
626
- *
627
- * // print product with id `1`
628
- * console.log(storage.get(1))
629
- *
630
- * // search for items
631
- * const searchResult = storage.search({
632
- * query: { availabilityStatus: 'low' }
633
- * })
634
- * console.log(searchResult)
635
- * ```
636
- *
637
- * @example
638
- * #### Advanced: `onChange` and RxJS subject
639
- *
640
- * Internally, `DataStorage` uses RxJS subject which is exposed as `subject` property.
641
- * You can use this to subscribe to changes and do additional operations such as logging or sanitization etc.
642
- *
643
- * Alternatively, you can also set the `onChange` callback which is triggered whenever the subject changes and
644
- * does not require maintaining a subscription or knowledge of RxJS subject.
645
- *
646
- * ```javascript
647
- * import { DataStorage } from '@superutils/rx'
648
- *
649
- * const storage = new DataStorage('my-data')
650
- * const sub = storage.subject.subscribe(data => {
651
- * // Write to the database whenever data changes
652
- * console.log('Saving to database...', data)
653
- * })
654
- * // unsubscribe from subject
655
- * setTimeout(()=> sub.unsubscribe(), 1000)
656
- *
657
- * // add an entry to storage
658
- * storage.set('bob', { age: 99, id: 'bob', name: 'Bob' })
659
- * ```
660
- */
661
- declare class DataStorage<Key, Value, CacheDisabled extends boolean = false,
662
- /**
663
- * @remarks
664
- * **On the `This` template parameter:**
665
- * Using `This` as a self-referential template is a **good practice** in this context because:
666
- * - It provides accurate **type inference** for method return types that depend on the generic parameters.
667
- * - It enables **type-safe property access** through `This['methodName']`, allowing the implementation
668
- * to reference interface contracts without circular dependencies or casting issues.
669
- * - It allows **fluent API chains** (returning `this`) while maintaining proper generic type information.
670
- * - It prevents **type widening** that would occur if methods returned the concrete class type instead
671
- * of the interface type, which is important for generic constraints and polymorphism.
672
- *
673
- * However, it increases **cognitive complexity** and is only warranted when:
674
- * - The class implements a complex generic interface with interdependent type parameters.
675
- * - Type-safe property references are essential to avoid runtime errors or casting.
676
- * - Fluent interfaces or chaining is a core API feature.
677
- */
678
- This extends IDataStorage<Key, Value, CacheDisabled> = IDataStorage<Key, Value, CacheDisabled>> implements IDataStorage<Key, Value, CacheDisabled> {
679
- readonly cacheDisabled: This['cacheDisabled'];
680
- readonly delay: This['delay'];
681
- /** Debounce and throttle related options */
682
- readonly delayOptions?: This['delayOptions'];
683
- readonly initialized: This['initialized'];
684
- readonly name: This['name'];
685
- onChange?: This['onChange'];
686
- onError?: This['onError'];
687
- parse?: This['parse'];
688
- get size(): number;
689
- spaces?: This['spaces'];
690
- readonly storage?: This['storage'];
691
- stringify?: This['stringify'];
692
- readonly subject: This['subject'];
693
- private subscriptions;
694
- constructor(name?: This['name'], options?: StorageOptions<Key, Value, CacheDisabled>);
695
- clear: This['clear'];
696
- delete: This['delete'];
697
- filter: This['filter'];
698
- find: This['find'];
699
- /**
700
- * Creates a {@link DataStorage} instance initialized from a plain object.
701
- *
702
- * This factory method automatically configures `parse` and `stringify` logic to
703
- * treat the underlying storage as a serialized object, while providing a
704
- * type-safe Map-like interface for individual properties.
705
- *
706
- * This default behavior can be overridden by providing custom `parse` and `stringify` implementations in `options`.
707
- *
708
- * @param name (optional) The name for the storage (e.g., localStorage key or filename).
709
- * @param options (optional) Configuration options for the storage instance.
710
- * @param options.initialValue (optional) An optional object to populate the storage if it's currently empty.
711
- *
712
- * @template T (optional) The structure of the object being stored. Can auto-infer from `options.initialValue`.
713
- * @template CacheDisabled (optional) Literal type determining whether to disable in-memory caching.
714
- *
715
- * @returns A new DataStorage instance mapped to the object's keys and values.
716
- *
717
- * @example
718
- * #### Store and access a User object
719
- * ```typescript
720
- * import { DataStorage } from '@superutils/rx'
721
- *
722
- * interface User {
723
- * age: number;
724
- * name: string;
725
- * }
726
- *
727
- * const initialValue: User = {
728
- * age: 99,
729
- * name: 'Ninety Nine'
730
- * }
731
- *
732
- * // Initialize storage from the object
733
- * const storage = DataStorage.fromObject<User>('user-profile', { initialValue })
734
- *
735
- * // Keys are inferred from the User interface
736
- * const name = storage.get('name') // Inferred as: string | undefined
737
- * console.log(name) // Prints: 'Ninety Nine'
738
- *
739
- * // Update properties safely
740
- * storage.set('age', 100)
741
- *
742
- * // Reconstruct the updated object
743
- * const userObj = storage.toObject<User>()
744
- * console.log(userObj) // { age: 100, name: 'Ninety Nine' }
745
- * ```
746
- */
747
- static fromObject: <T extends object, Key_1 extends keyof T = keyof T, Value_1 extends T[Key_1] = T[Key_1], CacheDisabled_1 extends boolean = false>(name?: string | null, options?: Omit<StorageOptions<Key_1, Value_1, CacheDisabled_1>, "initialValue"> & {
748
- initialValue?: T;
749
- }) => IObjectStorage<T, CacheDisabled_1>;
750
- /**
751
- * Trigger forced update of cached data from storage.
752
- *
753
- * @param name determines which cache-enabled storage instances to be updated.
754
- * - name (`string` | `string[]`): update all instances with a specific name(s)
755
- * - global (`true`): update all instances globally
756
- *
757
- * See {@link forceUpdateCache$} for more details.
758
- */
759
- static forceUpdateCache: (name: string | string[] | true) => void;
760
- get: This['get'];
761
- getAll: This['getAll'];
762
- private handleForceUpdateCacheChange;
763
- private handleSubjectChange;
764
- has: This['has'];
765
- init: This['init'];
766
- keys: This['keys'];
767
- map: This['map'];
768
- read: This['read'];
769
- search: This['search'];
770
- set: This['set'];
771
- setAll: This['setAll'];
772
- sort: This['sort'];
773
- toArray: This['toArray'];
774
- toJSON: This['toJSON'];
775
- toObject: This['toObject'];
776
- toString: This['toString'];
777
- private triggerOnError;
778
- unsubscribe: This['unsubscribe'];
779
- values: This['values'];
780
- write: This['write'];
186
+ }>;
187
+ var IGNORE: typeof IGNORE_UPDATE_SYMBOL;
781
188
  }
782
189
 
783
190
  /**
@@ -942,16 +349,6 @@ declare class IntervalRunner<TResult = unknown, TArgs extends unknown[] = unknow
942
349
  stop: (resetRunCount?: boolean) => this;
943
350
  }
944
351
 
945
- /**
946
- * Check if value is similar to a RxJS subject with .subscribe & .next functions
947
- *
948
- * @param x The value to check
949
- * @param withValue When `true`, also checks if `value` property exists in `x`
950
- *
951
- * @returns `true` if the value is subject-like, `false` otherwise.
952
- */
953
- declare const isSubjectLike: <T>(x: unknown, withValue?: boolean) => x is SubjectLike<T>;
954
-
955
352
  /**
956
353
  * Check if value is an instance of RxJS `Subscription` or subscription-like object
957
354
  *
@@ -961,10 +358,10 @@ declare const isSubjectLike: <T>(x: unknown, withValue?: boolean) => x is Subjec
961
358
  declare const isSubscriptionLike: (value: unknown, strict?: boolean) => value is Subscription;
962
359
 
963
360
  /**
964
- * @function unsubscribeAll
965
- * @summary unsubscribe to multiple RxJS subscriptions
966
- * @param {Function|Unsubscribable|Array} unsub
361
+ * Unsubscribe from one or mroe RxJS subscriptions
362
+ *
363
+ * @param candidate RxJS subscription, unsubscribe function or mix of both in array/object
967
364
  */
968
- declare const unsubscribeAll: (unsub?: UnsubscribeCandidates, onError?: (err: unknown) => void) => void;
365
+ declare const unsubscribeAll: (candidate?: UnsubscribeCandidate, onError?: (err: unknown) => void) => void;
969
366
 
970
- export { type CopyRxSubjectOptions, DataStorage, type DelayOptions, type IDataStorage, IGNORE_UPDATE_SYMBOL, type IObjectStorage, IntervalRunner, IntervalSubject, OnErrorType, type OnResultType, type StorageCompact, type StorageOptions, type StorageParseFn, type StorageSearch, type StorageSort, type StorageSortByComparator, type StorageSortByKey, type StorageSortByPropertyName, type StorageSortOptions, type StorageStringify, type StorageToJSON, type SubjectLike, type SubscriptionLike, type Unsubscribe, type UnsubscribeCandidates, type UnwrapSubjectValue, type ValueModifier, asPromise, copyRxSubject, forceUpdateCache$, isSubjectLike, isSubscriptionLike, type onBeforeExecType, unsubscribeAll };
367
+ export { type AsPromise_Defaults, type AsPromise_Options, type CopyRx_Options, type CopyRx_Transform, IGNORE_UPDATE_SYMBOL, IntervalRunner, IntervalSubject, type OnResultType, type Unsubscribe, type UnsubscribeCandidate, type UnwrapSourceValue, type UnwrapSourceValueStrict, asPromise, copyRx, isSubscriptionLike, type onBeforeExecType, unsubscribeAll };