@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.
- package/LICENSE +0 -0
- package/README.md +3 -0
- package/dist/browser/index.min.js +4 -0
- package/dist/browser/index.min.js.map +1 -0
- package/dist/index.cjs +651 -0
- package/dist/index.d.cts +730 -0
- package/dist/index.d.ts +730 -0
- package/dist/index.js +636 -0
- package/package.json +53 -0
package/dist/index.d.ts
ADDED
|
@@ -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 };
|