@superutils/rx 0.1.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.
@@ -0,0 +1,730 @@
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, DropFirst, filter, search, ValueOrPromise, sort, SortOptions } from '@superutils/core';
5
+
6
+ interface SubjectLike<T = unknown> {
7
+ next: (value: T) => void;
8
+ subscribe: (next: (value: T) => void, ...args: unknown[]) => SubscriptionLike;
9
+ unsubscribe?: Unsubscribe;
10
+ closed?: boolean;
11
+ value?: T;
12
+ }
13
+ interface SubscriptionLike {
14
+ closed?: boolean;
15
+ unsubscribe: Unsubscribe;
16
+ }
17
+ /** Wrap a value from a signle subject or an array of subjects and values */
18
+ type UnwrapSubjectValue<T> = T extends SubjectLike<infer V> ? V : T extends readonly unknown[] ? {
19
+ -readonly [K in keyof T]: UnwrapSubjectValue<T[K]>;
20
+ } : T;
21
+ type Unsubscribe = () => void;
22
+ type UnsubscribeCandidates = Unsubscribe | SubscriptionLike | SubscriptionLike[] | unknown[] | Record<PropertyKey, unknown> | undefined | null | boolean;
23
+
24
+ /**
25
+ * @summary Create a promise using RxJS subject and wait until an expected value is received
26
+ *
27
+ * @param subject RxJS subject or observable
28
+ * @param expectedValue (optional) if undefined, will resolve as soon as any value is received.
29
+ * If function, it should return true or false to indicate whether the value should be resolved.
30
+ * @param timeoutOrOptions (optional)
31
+ * @param timeoutOrOptions.timeout (optional) timeout duration in milliseconds if no value received within given time
32
+ * @param timeoutOrOptions.timeoutMsg (optional) error message to use when times out.
33
+ *
34
+ * @returns timeout promise
35
+ *
36
+ * @example
37
+ * #### Create a promise using RxJS subject
38
+ * ```typescript
39
+ * import { BehaviorSubject, subjectAsPromise } from '@superutils/rx'
40
+ *
41
+ * const subject = new BehaviorSubject(0)
42
+ * setInterval(() => subject.next(subject.value + 1), 1000)
43
+ *
44
+ * // resolve conditionally based on value received
45
+ * subjectAsPromise(subject, value => value >= 3)
46
+ * .then(value => console.log('Expected >= 3, received ', value))
47
+ *
48
+ * // resolve when only specific value is received
49
+ * subjectAsPromise(subject, 5).then(value => console.log('Expected 5, received ', value))
50
+ * ```
51
+ */
52
+ declare const asPromise: <T = unknown>(subject: Subscribable<T> | SubjectLike<T>, expectedValue?: T | ((value: T) => boolean), timeoutOrOptions?: number | (Omit<TimeoutOptions, "batchFunc"> & {
53
+ timeoutMsg?: string | Error;
54
+ })) => IPromisE_Timeout<T>;
55
+
56
+ /** Symbol used to signal to ignore an update when using a `valueModifier()` callback with {@link copyRxSubject} */
57
+ declare const IGNORE_UPDATE_SYMBOL: unique symbol;
58
+ type CopyRxSubjectOptions<ThisArg> = {
59
+ delay?: number;
60
+ } & DeferredOptions<ThisArg>;
61
+ /**
62
+ * Value modifier function definition for {@link copyRxSubject}
63
+ *
64
+ * Returning {@link IGNORE_UPDATE_SYMBOL} will ignore the update.
65
+ */
66
+ type ValueModifier<T = unknown, TCopy = T> = (newValue: T, previousValue: TCopy | undefined, copy$: SubjectLike<TCopy>) => TCopy | typeof IGNORE_UPDATE_SYMBOL;
67
+ /**
68
+ * @function copyRxSubject
69
+ * @summary returns a subject that automatically copies the value(s) of the source subject(s).
70
+ *
71
+ * The the changes are applied unidirectionally from the source subject to the destination subject.
72
+ * Changes on the destination subject is NOT applied back into the source subject.
73
+ *
74
+ * @param source$ RxJS source subject(s). If Array provied, value of `copy$` will also be an Array by default,
75
+ * unless a different type is provided by `copy$` or `valueModifier`.
76
+ *
77
+ * @param copy$ (optional) RxJS copy/destination subject.
78
+ * If `undefined`, a new subject will be created.
79
+ * Value type will be inferred automatically based on `copy$`, `valueModifier` and `source$`.
80
+ * Default: `new BehaviorSubject()`
81
+ * @param valueModifier (optional) callback to modify the value (an thus type) before copying from `source$`.
82
+ * Accepts async functions. Function invocation errors will be gracefully ignored.
83
+ * PS: If the very first invokation returns `IGNORE_UPDATE_SYMBOL`, the value of `copy$.value` will be undefined.
84
+ * Args: `newValue, previousValue, copy$`
85
+ * @param {CopyRxSubjectOptions} options (optional) options to enable debouce/throttling `copy$` value changes.
86
+ * @param options.delay (optional) delay in milliseconds. Default: `0`
87
+ * @param options.onError (optional) callback invoked whenever `valueModifier` execution fails.
88
+ * @param options.throttle (optional) `true`: throttle, `false`: debounce. Default: `false`
89
+ *
90
+ * @returns `copy$` if provided, otherwise, a new `BehaviorSubject` instance
91
+ *
92
+ * @example
93
+ * #### Auto-copy values from a single subject
94
+ * ```typescript
95
+ * import { BehaviorSubject, copyRxSubject } from '@superutils/rx'
96
+ *
97
+ * const number$ = new BehaviorSubject(1)
98
+ * const even$ = copyRxSubject(
99
+ * // source subject
100
+ * number$,
101
+ * // create and return a new BehaviorSubject. An existing RxJS subject can also be provided here.
102
+ * null,
103
+ * // copy and transform the value from number$
104
+ * newValue => newValue % 2 === 0,
105
+ * // debounce/throttle value changes to even$t
106
+ * // {
107
+ * // delay: 300 //
108
+ * // throttle: false,
109
+ * // }
110
+ * )
111
+ * // subscribe to even$ changes
112
+ * even$.subscribe(console.log)
113
+ * number$.next(2) // prints: true
114
+ * number$.next(3) // print: false
115
+ * ```
116
+ *
117
+ * @example
118
+ * #### Auto-copy from an array of subjects & values
119
+ * ```javascript
120
+ * import { BehaviorSubject, copyRxSubject } from '@superutils/rx'
121
+ *
122
+ * const theme$ = new BehaviorSubject('dark')
123
+ * const userId$ = new BehaviorSubject('username')
124
+ * const settings$ = copyRxSubject(
125
+ * [
126
+ * theme$,
127
+ * userId$,
128
+ * 'my-fancy-app' // fixed/unobserved value
129
+ * ]
130
+ * )
131
+ * // subscribe to the subject with reduced array values
132
+ * settings$.subscribe(([theme, user, appName]) =>
133
+ * console.log({ theme, user, appName })
134
+ * )
135
+ * ```
136
+ */
137
+ 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$;
138
+ 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$;
139
+ declare namespace copyRxSubject {
140
+ var defaults: {
141
+ delay: number;
142
+ } & DeferredOptions<unknown>;
143
+ var IGNORE_UPDATE_SYMBOL: typeof IGNORE_UPDATE_SYMBOL;
144
+ }
145
+
146
+ type DelayOptions = ({
147
+ throttle: true;
148
+ } & Omit<ThrottleOptions, 'onError' | 'thisArg'>) | ({
149
+ throttle?: false;
150
+ } & Omit<DebounceOptions, 'onError' | 'thisArg'>);
151
+ type OnErrorType = 'onChange' | 'parse' | 'parse-json' | 'stringify' | 'stringify-json' | 'write';
152
+ /** Storage type with only properties that are used by `DataStorage` */
153
+ type StorageCompact = Pick<Storage, 'getItem' | 'setItem'>;
154
+ type StorageFilter<K, V extends StorageValue> = <IncludeKey extends boolean = false>(...args: DropFirst<Parameters<typeof filter<K, V, IncludeKey>>>) => ReturnType<typeof filter<K, V, IncludeKey>>;
155
+ type StorageFind<K, V extends StorageValue> = (predicateOrOptions: Parameters<StorageFilter<K, V>>[0] | Parameters<StorageSearch<K, V>>[0]) => V | undefined;
156
+ type StorageMap<K, V extends StorageValue> = <T>(callback: (value: V, key: K, data: [K, V][], index: number) => T) => T[];
157
+ /**
158
+ * Callback triggered when value changes and/or a force read is triggered
159
+ */
160
+ type StorageOnChangeFn<K, V extends StorageValue> = (data: Map<K, V>) => ValueOrPromise<void | Map<K, V>>;
161
+ type StorageOnErrorFn = (err: unknown, type: OnErrorType) => ValueOrPromise<void>;
162
+ /** Initial options provided through the constructor */
163
+ type StorageOptions<Key, Value extends StorageValue, CacheDisabled extends boolean = false> = {
164
+ /** value to set, only if storage is empty. Default: `new Map()` */
165
+ initialValue?: Map<Key, Value>;
166
+ } & Pick<Partial<IDataStorage<Key, Value, CacheDisabled>>, 'cacheDisabled' | 'onChange' | 'onError' | 'parse' | 'spaces' | 'storage' | 'stringify'> & (CacheDisabled extends false ? Pick<Partial<IDataStorage<Key, Value, CacheDisabled>>, 'delay' | 'delayOptions'> : {
167
+ delay?: never;
168
+ delayOptions?: never;
169
+ });
170
+ type StorageParseFn<K, V extends StorageValue> = (data: string) => Map<K, V>;
171
+ type StorageSearch<K, V extends StorageValue> = <MatchExact extends boolean = false, AsMap extends boolean = true>(...args: DropFirst<Parameters<typeof search<K, V, MatchExact, AsMap>>>) => ReturnType<typeof search<K, V, MatchExact, AsMap>>;
172
+ type StorageSort<K, V extends StorageValue> = (...args: StorageSortByComparator<K, V> | StorageSortByPropertyName<V> | StorageSortByKey) => Map<K, V>;
173
+ type StorageSortByComparator<K, V extends StorageValue> = [
174
+ comparator: Parameters<typeof sort<K, V>>[1],
175
+ options?: StorageSortOptions
176
+ ];
177
+ type StorageSortByKey = [byKey: true, options?: StorageSortOptions];
178
+ type StorageSortByPropertyName<V extends StorageValue> = [
179
+ propertyName: keyof V & string,
180
+ options?: StorageSortOptions
181
+ ];
182
+ type StorageSortOptions = SortOptions & {
183
+ save?: boolean;
184
+ };
185
+ type StorageStringifyFn<K, V extends StorageValue> = (data: Map<K, V>) => string;
186
+ type StorageToJSON<K, V> = (replacer?: null | ((key: K, value: V) => unknown), spacing?: string | number, data?: Map<K, V>) => string;
187
+ type StorageValue = Record<PropertyKey, unknown>;
188
+ interface IDataStorage<Key, Value extends StorageValue, CacheDisabled extends boolean = false> {
189
+ /** Disable in-memory cache and only directly read/write from storage (local storage or JSON fle) */
190
+ readonly cacheDisabled: CacheDisabled;
191
+ /**
192
+ * Debounce/throttle delay duration in milliseconds for writing to storage when caching is enabled.
193
+ *
194
+ * Increasing this value can improve performance when dealing with large datasets
195
+ * or frequent updates by reducing the number of write operations.
196
+ *
197
+ * Default: `300`
198
+ */
199
+ readonly delay: number;
200
+ readonly delayOptions?: DelayOptions;
201
+ /**
202
+ * Indicates wherether storage has been initialized (`init()` function invoked).
203
+ */
204
+ readonly initialized: boolean;
205
+ /**
206
+ * Storage name. Filename (NodeJS) or property name (browser LocalStorage).
207
+ * If empty string or undefined, data will not be saved to storage and will only work in-memory.
208
+ *
209
+ * Default: `''`
210
+ */
211
+ readonly name?: string;
212
+ /** Get the number of items */
213
+ readonly size: number;
214
+ /** Number of spaces to use when stringifying. Default: `undefined` */
215
+ spaces?: number;
216
+ /**
217
+ * `LocalStorage` or equivalent storage instance to be used as the underlying storage and to read & write from.
218
+ *
219
+ * Notes:
220
+ * - Ignored when `name` is falsy (in-memory only mode)
221
+ * - For NodeJS or equivalent, an instance of `LocalStorage` from "node-localstoarge" NPM module can be used.
222
+ * - If `undefined`, will not attempt to use `globalThis.localStorage`, if available
223
+ * - If `null`, will not attempt to use `globalThis.localStorage`
224
+ *
225
+ * Default:
226
+ * - browser: `localStorage`
227
+ * - node: `undefined` (in-memory mode)
228
+ */
229
+ readonly storage?: StorageCompact | null;
230
+ readonly subject: CacheDisabled extends true ? Subject<Map<Key, Value>> : BehaviorSubject<Map<Key, Value>>;
231
+ /** Clear all items */
232
+ readonly clear: () => IDataStorage<Key, Value, CacheDisabled>;
233
+ /** Delete one or more items by their respective keys */
234
+ readonly delete: (key: Key | Key[]) => IDataStorage<Key, Value, CacheDisabled>;
235
+ /** Find an item by predicate or search criteria */
236
+ readonly find: StorageFind<Key, Value>;
237
+ /** Filter items by predicate */
238
+ readonly filter: StorageFilter<Key, Value>;
239
+ /** Get item by key */
240
+ readonly get: (key: Key) => Value | undefined;
241
+ /**
242
+ * Get all items
243
+ *
244
+ * @param forceUpdate (optional) if `true` and cache is enabled, reads & updates data directly from storage
245
+ */
246
+ readonly getAll: (forceUpdate: boolean) => Map<Key, Value>;
247
+ /** Check if key exists */
248
+ readonly has: (key: Key) => boolean;
249
+ /**
250
+ * Initializes storage and sets up internal subscriptions.
251
+ *
252
+ * Manual invocation is not typically necessary, as initialization occurs automatically
253
+ * in one of the following scenarios:
254
+ * - During construction, if an `initialValue` with at least one entry is provided.
255
+ * - On the first attempt to read or write data.
256
+ *
257
+ * @param initialValue An optional map to initialize the storage with if it's currently empty.
258
+ * @returns `true` if initialization was successful, or `false` if the storage was already initialized.
259
+ */
260
+ readonly init: (initialValue?: Map<Key, Value>) => boolean;
261
+ /** Get all keys */
262
+ readonly keys: () => Key[];
263
+ /** Map each item on the data to an Array */
264
+ readonly map: StorageMap<Key, Value>;
265
+ /**
266
+ * Callback to be invoked whenever value change is triggered.
267
+ *
268
+ * If `onChange` invocation fails, it will be ignored gracefully.
269
+ */
270
+ onChange?: StorageOnChangeFn<Key, Value>;
271
+ /**
272
+ * Callback to be invoked whenever read/write operation fails.
273
+ *
274
+ * If `onError` invocation failure will be ignored gracefully.
275
+ */
276
+ onError?: StorageOnErrorFn;
277
+ /**
278
+ * A callback to customize the deserialization of data read from storage.
279
+ *
280
+ * This can be used to override the default `JSON.parse` behavior and serves as the counterpart to `stringify`.
281
+ * If this function is not provided, throws an error, or returns `undefined`, the default
282
+ * `JSON.parse` will be used as a fallback.
283
+ */
284
+ readonly parse?: StorageParseFn<Key, Value>;
285
+ /** Read directly from the localStorage (browser) or file (NodeJS) without triggering the `this.subject`. */
286
+ readonly read: () => Map<Key, Value>;
287
+ /** Search items */
288
+ readonly search: StorageSearch<Key, Value>;
289
+ /** Set item by key */
290
+ readonly set: (key: Key, value: Value) => IDataStorage<Key, Value, CacheDisabled>;
291
+ /**
292
+ * Set multiple entries at once and/or replace the storage entries
293
+ *
294
+ * @param data (optional) Data to add. Default: `new Map()`
295
+ * @param replace (optional) Whether to merge with or replace current data.
296
+ * - `true`: replace all entries with `data`
297
+ * - `false`: merge with current data (existing entries with matching keys will be overwritten)
298
+ *
299
+ * Default: `false`
300
+ */
301
+ readonly setAll: (data?: Map<Key, Value>, replace?: boolean) => IDataStorage<Key, Value, CacheDisabled>;
302
+ /**
303
+ * Sort items in the storage.
304
+ *
305
+ * @param nameOrComparator Criteria to sort by. Accepts one of the following:
306
+ * - `function`: A comparator function to sort the data.
307
+ * - `string`: A property name of the value object to sort by.
308
+ * - `true`: Sorts the map by its keys.
309
+ * @param options (optional) Sorting options.
310
+ * @param options.save (optional) Whether to save the sorted data back to storage (localStorage/file).
311
+ *
312
+ * @returns The sorted Map.
313
+ */
314
+ readonly sort: StorageSort<Key, Value>;
315
+ /**
316
+ * Callback function to customize the serialization of data to be stored to storage.
317
+ *
318
+ * Useful when data needs to be sanitised before storing and/or remove circlar references.
319
+ *
320
+ * @example
321
+ * ```javascript
322
+ * import fetch from '@superutils/fetch'
323
+ * import { DataStorage } from '@superutils/rx'
324
+ * import { LocalStorage } from 'node-localstorage'
325
+ *
326
+ * // Create a localStorage alternative for NodeJS that reads and writes to JSON files.
327
+ * // This is not necessary for browsers
328
+ * globalThis.localStorage = new LocalStorage('./data', 1e7)
329
+ *
330
+ * const storage = new DataStorage('products.json')
331
+ * storage.stringify = data => Array
332
+ * .from(data)
333
+ * .map(([key, product]) => [
334
+ * key,
335
+ * { id: product.id, title: product.title } // only store what's needed
336
+ * ])
337
+ *
338
+ * const { products } = await fetch('[DUMMYJSON-DOT-COM]/products)
339
+ * const productsMap = result.products.map(p => [p.id, p])
340
+ * storage.setAll(productsMap, true)
341
+ * console.log(storage.getAll())
342
+ * ```
343
+ */
344
+ readonly stringify?: StorageStringifyFn<Key, Value>;
345
+ /** Convert list of items (Map) to 2D Array */
346
+ readonly toArray: () => [Key, Value][];
347
+ /** Convert list of items (Map) to JSON string of 2D Array */
348
+ readonly toJSON: StorageToJSON<Key, Value>;
349
+ /** Convert list of items (Map) to JSON string of 2D Array */
350
+ readonly toString: () => string;
351
+ /**
352
+ * Unsubscribe from all internal subscriptions.
353
+ *
354
+ * This will result in:
355
+ * - Automatic writing to storage being disabled (manual writes via `instance.write()` will still work).
356
+ * - The `onChange` callback no longer being triggered.
357
+ * - The instance stopping listening to force update cache triggers.
358
+ */
359
+ readonly unsubscribe: () => void;
360
+ /** Get all values */
361
+ readonly values: () => Value[];
362
+ /**
363
+ * Write data to the underlying storage (localStorage or file).
364
+ *
365
+ * @param data (optional) Data to write.
366
+ * - If provided, it overwrites the storage.
367
+ * - If not provided, the current in-memory data is used (if cache is enabled).
368
+ * @param silent (optional) Whether to suppress errors if the write operation fails.
369
+ * - `true`: Returns `false` on failure without throwing.
370
+ * - `false`: Throws an error on failure.
371
+ *
372
+ * Default: `this.silent`
373
+ * @returns `true` if the write was successful, `false` otherwise.
374
+ */
375
+ readonly write: (data?: Map<Key, Value>, silent?: boolean) => void;
376
+ }
377
+
378
+ /**
379
+ * Force all or specific instances of DataStorage to reload data from storage
380
+ *
381
+ * @example
382
+ * ```javascript
383
+ * import { DataStorage } from '@superutils/rx'
384
+ *
385
+ * // Update all DataStorage instances with specific name(s)
386
+ * const name = 'products'
387
+ * forceUpdateCache$.next([name])
388
+ *
389
+ * // Update every single instance of DataStorage that uses storage (has a "name")
390
+ * forceUpdateCache$.next(true)
391
+ * ```
392
+ */
393
+ declare const forceUpdateCache$: Subject<string | boolean | string[]>;
394
+ declare class DataStorage<Key, Value extends StorageValue, CacheDisabled extends boolean = false> implements IDataStorage<Key, Value, CacheDisabled> {
395
+ readonly cacheDisabled: CacheDisabled;
396
+ readonly delay: number;
397
+ readonly delayOptions?: DelayOptions;
398
+ /** Debounce and throttle related options */
399
+ readonly initialized: boolean;
400
+ readonly name: string;
401
+ get size(): number;
402
+ spaces?: number;
403
+ readonly storage?: StorageCompact | null;
404
+ readonly subject: CacheDisabled extends true ? Subject<Map<Key, Value>> : BehaviorSubject<Map<Key, Value>>;
405
+ private subscriptions;
406
+ /**
407
+ * A wrapper for reading and writing to LocalStorage (browser) or JSON files (NodeJS),
408
+ * providing a Map-like interface with advanced features like search, filtering, and sorting.
409
+ *
410
+ * #### Notes:
411
+ * - **Performance**: `DataStorage` is optimized for small to medium datasets.
412
+ * - For datasets > 1MB, consider increasing the `delay` option to reduce write frequency.
413
+ * - It is **NOT** recommended for datasets larger than 3MB due to synchronous serialization costs.
414
+ * - **RxJS Integration**: Built on RxJS for reactive data handling, though no prior RxJS knowledge is required.
415
+ * - **Storage Behavior**:
416
+ * - If `name` is omitted, the instance operates in-memory only and data is not persisted to storage.
417
+ * - If `cacheDisabled` is `true`, data is not kept in memory; every read/write operation accesses the underlying
418
+ * storage directly.
419
+ *
420
+ * @example
421
+ * #### Browser Usage
422
+ * ```javascript
423
+ * import { DataStorage } from '@superutils/rx'
424
+ * import fetch from '@superutils/fetch'
425
+ *
426
+ * const storage = new DataStorage('products')
427
+ * const { products } = await fetch('[DUMMYJSON-DOT-COM]/products')
428
+ * // save all items to storage
429
+ * storage.setAll(
430
+ * new Map(products.map(p => [p.id, p])), // convert to Map
431
+ * )
432
+ *
433
+ * // print product with id `1`
434
+ * console.log(storage.get(1))
435
+ *
436
+ * // search for items
437
+ * const searchResult = storage.search({
438
+ * query: { availabilityStatus: 'low' }
439
+ * })
440
+ * console.log(searchResult)
441
+ * ```
442
+ * @example
443
+ * #### NodeJS Usage
444
+ * ```javascript
445
+ * import { DataStorage } from '@superutils/rx'
446
+ * import fetch from '@superutils/fetch'
447
+ * import { LocalStorage } from 'node-localstorage'
448
+ *
449
+ * // Add localStorage alternative for NodeJS that reads and writes to JSON files.
450
+ * // This is not necessary for browsers.
451
+ * globalThis.localStorage = new LocalStorage('./data', 1e7)
452
+ *
453
+ * const storage = new DataStorage('products')
454
+ * const { products } = await fetch('[DUMMYJSON-DOT-COM]/products')
455
+ * // save all items to storage
456
+ * storage.setAll(
457
+ * new Map(products.map(p => [p.id, p])), // convert to Map
458
+ * )
459
+ *
460
+ * // print product with id `1`
461
+ * console.log(storage.get(1))
462
+ *
463
+ * // search for items
464
+ * const searchResult = storage.search({
465
+ * query: { availabilityStatus: 'low' }
466
+ * })
467
+ * console.log(searchResult)
468
+ * ```
469
+ *
470
+ * @example
471
+ * #### Advanced: `onChange` and RxJS subject
472
+ *
473
+ * Internally, `DataStorage` uses RxJS subject which is exposed as `subject` property.
474
+ * You can use this to subscribe to changes and do additional operations such as logging or sanitization etc.
475
+ *
476
+ * Alternatively, you can also set the `onChange` callback which is triggered whenever the subject changes and
477
+ * does not require maintaining a subscription or knowledge of RxJS subject.
478
+ *
479
+ * ```javascript
480
+ * import { DataStorage } from '@superutils/rx'
481
+ *
482
+ * const storage = new DataStorage('my-data')
483
+ * const sub = storage.subject.subscribe(data => {
484
+ * // Write to the database whenever data changes
485
+ * console.log('Saving to database...', data)
486
+ * })
487
+ * // unsubscribe from subject
488
+ * setTimeout(()=> sub.unsbuscribe(), 1000)
489
+ *
490
+ * // add an entry to storage
491
+ * storage.set('bob', { age: 99, id: 'bob', name: 'Bob' })
492
+ * ```
493
+ */
494
+ constructor(name?: string | null, options?: StorageOptions<Key, Value, CacheDisabled>);
495
+ readonly clear: () => this;
496
+ readonly delete: (keys: Key | Key[]) => this;
497
+ readonly find: StorageFind<Key, Value>;
498
+ readonly filter: StorageFilter<Key, Value>;
499
+ /**
500
+ * Trigger forced update of cached data from storage.
501
+ *
502
+ * @param name determines which storage instances to be updated.
503
+ * - name (`string` | `string[]`): update all instances with a specific name(s)
504
+ * - global (`true`): update all instances globally
505
+ *
506
+ * @example
507
+ * ```javascript
508
+ * import { DataStorage } from '@superutils/rx'
509
+ *
510
+ * // Update all DataStorage instances with specific name(s)
511
+ * const name = 'products'
512
+ * DataStorage.forceUpdateCache([name])
513
+ *
514
+ * // Update every single instance of DataStorage that uses storage (has a "name")
515
+ * DataStorage.forceUpdateCache(true)
516
+ * ```
517
+ */
518
+ static forceUpdateCache: (name: string | string[] | true) => void;
519
+ readonly get: (key: Key) => Value | undefined;
520
+ readonly getAll: (forceRead?: boolean) => Map<Key, Value>;
521
+ readonly has: (key: Key) => boolean;
522
+ readonly init: (initialValue?: Map<Key, Value>) => boolean;
523
+ readonly keys: () => Key[];
524
+ readonly map: StorageMap<Key, Value>;
525
+ onChange?: StorageOnChangeFn<Key, Value>;
526
+ onError?: StorageOnErrorFn;
527
+ readonly parse?: StorageParseFn<Key, Value>;
528
+ readonly read: () => Map<Key, Value>;
529
+ readonly search: StorageSearch<Key, Value>;
530
+ readonly set: (key: Key, value: Value) => this;
531
+ readonly setAll: (data?: Map<Key, Value>, replace?: boolean) => this;
532
+ readonly sort: StorageSort<Key, Value>;
533
+ readonly stringify?: StorageStringifyFn<Key, Value>;
534
+ readonly toArray: () => [Key, Value][];
535
+ readonly toJSON: StorageToJSON<Key, Value>;
536
+ readonly toString: (data?: Map<Key, Value>) => string;
537
+ private triggerOnError;
538
+ readonly unsubscribe: () => void;
539
+ readonly values: () => Value[];
540
+ readonly write: (data?: Map<Key, Value>) => boolean;
541
+ }
542
+
543
+ /**
544
+ * @summary Extention of a BehaviorSubject with interval function
545
+ *
546
+ * -----------------------------------------------
547
+ *
548
+ * @example
549
+ * #### Fetch data from API server every minute
550
+ * ```typescript
551
+ * import fetch from '@superutils/fetch'
552
+ * import { IntervalSubject } from '@superutils/rx'
553
+ *
554
+ * const initialValue = 0
555
+ * const interval$ = new IntervalSubject(
556
+ * true, // auto-start
557
+ * 60 * 1000, // interval delay. increment counter every "x" milliseconds
558
+ * initialValue, // initial counter value
559
+ * 1, // increment by 1 at each interval
560
+ * )
561
+ *
562
+ * const onChange = (counter: number) => {
563
+ * counter === initialValue && console.log('Counter started')
564
+ * fetch.get('[DUMMYJSON-DOT-COM]/http/200').then(
565
+ * () => console.log(new Date().toISOString(), 'Successful ping'),
566
+ * (err: Error) => console.log('Ping failed.', err)
567
+ * )
568
+ * }
569
+ *
570
+ * // BehaviorSubject automatically resolves with the initial value if subscribed immediately.
571
+ * // subscribe to the subject and execute `onChange`: first time immediately and then every 60 seconds
572
+ * interval$.subscribe(onChange)
573
+ * ```
574
+ */
575
+ declare class IntervalSubject extends BehaviorSubject<number> {
576
+ autoStart: boolean;
577
+ private _delay;
578
+ readonly initialValue: number;
579
+ incrementBy: number;
580
+ private _intervalId;
581
+ private _running;
582
+ constructor(autoStart: boolean, _delay?: number, initialValue?: number, incrementBy?: number);
583
+ get delay(): number;
584
+ set delay(newDelay: number);
585
+ get running(): boolean;
586
+ pause: () => this;
587
+ resume: () => this;
588
+ start: () => this;
589
+ stop: () => this;
590
+ }
591
+
592
+ type OnResultType<TResult = unknown> = (error: Error | null, result: TResult | undefined, runCount: number, once: boolean) => void | Promise<void>;
593
+ type onBeforeExecType = (runCount: number, once: boolean) => void | Promise<unknown>;
594
+ /**
595
+ * @summary a simple runner to execute a task periodically.
596
+ *
597
+ * When to use `IntervalRunner` instead of `IntervalSubject`?
598
+ *
599
+ * `IntervalRunner` is useful when the execution of the `onResult` time must be on the clock and/or must be excluded
600
+ * from the interval delay duration.
601
+ *
602
+ * Example use case:
603
+ * When an API call needs to be made periodically and there's a possibility of delayed response (due to network issues
604
+ * or longer backend execution time). In this case, using IntervalRunner with `sequential = true` will ensure the
605
+ * delay is consistent between completion of current and start of the next API call.
606
+ *
607
+ * @param taskFn task function to be executed periodically
608
+ * @param taskArgs arguments to be supplied to the task function.
609
+ * @param intervalMs timer delay in milliseconds.
610
+ * @param sequential true (default): will use setTimeout and will delay until execution is completed.
611
+ * This will ensure, in case the current execution takes longer, the following execution will not occur until current one is done and the interval delay is passed.
612
+ *
613
+ * false: will use setInterval and the delay time to execute task will not affected. This may cause unwanted issues if the execution takes longer than the interval delay time. Use with caution.
614
+ *
615
+ * Default: `true`
616
+ *
617
+ * @param preExecute (optional) if true, will pre-execute task before starting the timer.
618
+ *
619
+ * Default: `true`
620
+ *
621
+ * @example
622
+ * #### Execute a function sequentially
623
+ * Counting time will not start until function execution ends, maintaining the delay betweeen
624
+ * end of execution consistent.
625
+ * ```typescript
626
+ * import fetch from '@superutils/fetch'
627
+ * import { IntervalRunner } fro '@superutils/rx'
628
+ *
629
+ * const runner = new IntervalRunner(
630
+ * fetch.get,
631
+ * ['[DUMMYJSON-DOT-COM]/products'],
632
+ * 2000,
633
+ * )
634
+ * runner.start(result => console.log({ result }))
635
+ * ```
636
+ *
637
+ * @example
638
+ * #### Execute a function at without enforcing sequential execution.
639
+ * Will start counting time even if function execution is unfinied, maintaining the delay betweeen
640
+ * start of execution consistent.
641
+ * ```typescript
642
+ * import fetch from '@superutils/fetch'
643
+ * import { IntervalRunner } fro '@superutils/rx'
644
+ *
645
+ * const runner = new IntervalRunner(
646
+ * fetch.get,
647
+ * ['[DUMMYJSON-DOT-COM]/products'],
648
+ * 2000,
649
+ * false,
650
+ * )
651
+ * runner.start(result => console.log({ result }))
652
+ * ```
653
+ */
654
+ declare class IntervalRunner<TResult = unknown, TArgs extends unknown[] = unknown[]> {
655
+ readonly taskFn: (...args: TArgs) => TResult | Promise<TResult>;
656
+ readonly taskArgs: TArgs;
657
+ readonly sequential: boolean;
658
+ readonly preExecute: boolean;
659
+ private idInterval;
660
+ lastResult: TResult | undefined;
661
+ minIntervalMs: number;
662
+ private onBeforeExec?;
663
+ private onResult;
664
+ /**
665
+ * @summary RxJS BehaviorSubject to change timer delay and restart the timer
666
+ */
667
+ readonly intervalMs$: BehaviorSubject<number>;
668
+ private runCount;
669
+ private started;
670
+ private subscription;
671
+ constructor(taskFn: (...args: TArgs) => TResult | Promise<TResult>, taskArgs: TArgs, intervalMs: BehaviorSubject<number> | number, sequential?: boolean, // if true, timer will start start only after execution is finished
672
+ preExecute?: boolean);
673
+ private clearInterval;
674
+ private executeTask;
675
+ /** Execute the task function regardless of the interval runner state */
676
+ executeOnce: () => Promise<TResult | undefined>;
677
+ /** Check if interval is running*/
678
+ isStarted: () => boolean;
679
+ /**
680
+ * Restart interval
681
+ *
682
+ * @param resetRunCount (optional) whether to reset run count
683
+ *
684
+ * @returns {Boolean} indicates whether restart was successful
685
+ */
686
+ restart: (resetRunCount?: boolean) => boolean;
687
+ /**
688
+ * @summary set `onResult` & `onBeforeExec` callbacks and start execution.
689
+ *
690
+ * If it's already running, the callbacks will be used on the next execution.
691
+ *
692
+ * In order to start using callbacks immediately, invoke the `intervalRunner.stop()` function first.
693
+ *
694
+ * @returns {Boolean} indicates whether starting interveral waa successful
695
+ */
696
+ start: (onResult: OnResultType<TResult>, onBeforeExec?: onBeforeExecType) => boolean;
697
+ /**
698
+ * Stop interval runner
699
+ *
700
+ * @param resetRunCount (optional) whether to reset the run counter
701
+ */
702
+ stop: (resetRunCount?: boolean) => this;
703
+ }
704
+
705
+ /**
706
+ * Check if value is similar to a RxJS subject with .subscribe & .next functions
707
+ *
708
+ * @param x The value to check
709
+ * @param withValue When `true`, also checks if `value` property exists in `x`
710
+ *
711
+ * @returns `true` if the value is subject-like, `false` otherwise.
712
+ */
713
+ declare const isSubjectLike: <T>(x: unknown, withValue?: boolean) => x is SubjectLike<T>;
714
+
715
+ /**
716
+ * Check if value is an instance of RxJS `Subscription` or subscription-like object
717
+ *
718
+ * @param value
719
+ * @param strict (optional) if true, will only check if value is instance of Subscription. Default: `false`
720
+ */
721
+ declare const isSubscriptionLike: (value: unknown, strict?: boolean) => value is Subscription;
722
+
723
+ /**
724
+ * @function unsubscribeAll
725
+ * @summary unsubscribe to multiple RxJS subscriptions
726
+ * @param {Function|Unsubscribable|Array} unsub
727
+ */
728
+ declare const unsubscribeAll: (unsub?: UnsubscribeCandidates, onError?: (err: unknown) => void) => void;
729
+
730
+ export { type CopyRxSubjectOptions, DataStorage, type DelayOptions, type IDataStorage, IGNORE_UPDATE_SYMBOL, IntervalRunner, IntervalSubject, type OnErrorType, type OnResultType, type StorageCompact, type StorageFilter, type StorageFind, type StorageMap, type StorageOnChangeFn, type StorageOnErrorFn, type StorageOptions, type StorageParseFn, type StorageSearch, type StorageSort, type StorageSortByComparator, type StorageSortByKey, type StorageSortByPropertyName, type StorageSortOptions, type StorageStringifyFn, type StorageToJSON, type StorageValue, type SubjectLike, type SubscriptionLike, type Unsubscribe, type UnsubscribeCandidates, type UnwrapSubjectValue, type ValueModifier, asPromise, copyRxSubject, forceUpdateCache$, isSubjectLike, isSubscriptionLike, type onBeforeExecType, unsubscribeAll };