@zajno/common 1.4.2 → 1.4.6
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/.nvmrc +1 -1
- package/README.md +5 -5
- package/coverage/clover.xml +394 -107
- package/coverage/coverage-final.json +18 -13
- package/coverage/lcov-report/index.html +38 -38
- package/coverage/lcov-report/src/__tests__/helpers/index.html +1 -1
- package/coverage/lcov-report/src/__tests__/helpers/main.ts.html +1 -1
- package/coverage/lcov-report/src/async/arrays.ts.html +1 -1
- package/coverage/lcov-report/src/async/index.html +24 -9
- package/coverage/lcov-report/src/async/timeout.ts.html +230 -0
- package/coverage/lcov-report/src/cache.ts.html +623 -0
- package/coverage/lcov-report/src/dates/calc.ts.html +1 -1
- package/coverage/lcov-report/src/dates/convert.ts.html +1 -1
- package/coverage/lcov-report/src/dates/datex.ts.html +1 -1
- package/coverage/lcov-report/src/dates/format.ts.html +1 -1
- package/coverage/lcov-report/src/dates/index.html +1 -1
- package/coverage/lcov-report/src/dates/index.ts.html +1 -1
- package/coverage/lcov-report/src/dates/parse.ts.html +1 -1
- package/coverage/lcov-report/src/dates/period.ts.html +1 -1
- package/coverage/lcov-report/src/dates/shift.ts.html +1 -1
- package/coverage/lcov-report/src/dates/types.ts.html +1 -1
- package/coverage/lcov-report/src/dates/yearDate.ts.html +1 -1
- package/coverage/lcov-report/src/disposer.ts.html +332 -0
- package/coverage/lcov-report/src/enumHelper.ts.html +1 -1
- package/coverage/lcov-report/src/event.ts.html +1 -1
- package/coverage/lcov-report/src/fields/index.html +1 -1
- package/coverage/lcov-report/src/fields/update.ts.html +1 -1
- package/coverage/lcov-report/src/functions.ts.html +1 -1
- package/coverage/lcov-report/src/index.html +78 -18
- package/coverage/lcov-report/src/lazy.light.ts.html +1 -1
- package/coverage/lcov-report/src/logger/batch.ts.html +2 -2
- package/coverage/lcov-report/src/logger/console.ts.html +5 -5
- package/coverage/lcov-report/src/logger/index.html +1 -1
- package/coverage/lcov-report/src/logger/index.ts.html +22 -22
- package/coverage/lcov-report/src/logger/named.ts.html +13 -13
- package/coverage/lcov-report/src/logger/proxy.ts.html +7 -7
- package/coverage/lcov-report/src/math/arrays.ts.html +23 -23
- package/coverage/lcov-report/src/math/calc.ts.html +9 -9
- package/coverage/lcov-report/src/math/distribution.ts.html +2 -2
- package/coverage/lcov-report/src/math/index.html +1 -1
- package/coverage/lcov-report/src/math/index.ts.html +6 -6
- package/coverage/lcov-report/src/observers.ts.html +368 -0
- package/coverage/lcov-report/src/observersMap.ts.html +395 -0
- package/coverage/lcov-report/src/observingCache.ts.html +530 -0
- package/coverage/lcov-report/src/transitionObserver.ts.html +29 -8
- package/coverage/lcov-report/src/types.ts.html +5 -5
- package/coverage/lcov-report/src/validation/ValidationErrors.ts.html +1 -1
- package/coverage/lcov-report/src/validation/creditCard.ts.html +1 -1
- package/coverage/lcov-report/src/validation/helpers.ts.html +1 -1
- package/coverage/lcov-report/src/validation/index.html +1 -1
- package/coverage/lcov-report/src/validation/index.ts.html +1 -1
- package/coverage/lcov-report/src/validation/types.ts.html +1 -1
- package/coverage/lcov-report/src/validation/validators.ts.html +2 -2
- package/coverage/lcov-report/src/validation/wrappers.ts.html +1 -1
- package/coverage/lcov-report/src/viewModels/FlagModel.ts.html +1 -1
- package/coverage/lcov-report/src/viewModels/MultiSelectModel.ts.html +1 -1
- package/coverage/lcov-report/src/viewModels/NumberModel.ts.html +24 -24
- package/coverage/lcov-report/src/viewModels/SelectModel.ts.html +1 -1
- package/coverage/lcov-report/src/viewModels/Validatable.ts.html +1 -1
- package/coverage/lcov-report/src/viewModels/index.html +15 -15
- package/coverage/lcov-report/src/viewModels/wrappers.ts.html +1 -1
- package/coverage/lcov.info +741 -133
- package/lib/cache.d.ts +6 -2
- package/lib/cache.d.ts.map +1 -1
- package/lib/cache.js +20 -4
- package/lib/cache.js.map +1 -1
- package/lib/observersMap.d.ts +22 -0
- package/lib/observersMap.d.ts.map +1 -0
- package/lib/observersMap.js +93 -0
- package/lib/observersMap.js.map +1 -0
- package/lib/observingCache.d.ts +31 -0
- package/lib/observingCache.d.ts.map +1 -0
- package/lib/observingCache.js +118 -0
- package/lib/observingCache.js.map +1 -0
- package/lib/transitionObserver.d.ts +1 -0
- package/lib/transitionObserver.d.ts.map +1 -1
- package/lib/transitionObserver.js +8 -1
- package/lib/transitionObserver.js.map +1 -1
- package/lib/validation/validators.js +1 -1
- package/lib/viewModels/InitializableModel.d.ts +6 -0
- package/lib/viewModels/InitializableModel.d.ts.map +1 -0
- package/lib/viewModels/InitializableModel.js +23 -0
- package/lib/viewModels/InitializableModel.js.map +1 -0
- package/package.json +1 -1
- package/src/__tests__/observingCache.test.ts +127 -0
- package/src/cache.ts +25 -4
- package/src/observersMap.ts +105 -0
- package/src/observingCache.ts +150 -0
- package/src/transitionObserver.ts +7 -0
- package/src/validation/validators.ts +1 -1
- package/src/viewModels/InitializableModel.ts +17 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { observable, reaction, runInAction, toJS } from 'mobx';
|
|
2
|
+
import { ObservingCache } from '../observingCache';
|
|
3
|
+
import { setTimeoutAsync } from '../async/timeout';
|
|
4
|
+
|
|
5
|
+
type TestItem = {
|
|
6
|
+
hello: string,
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
function createData() {
|
|
10
|
+
const Database: Record<string, TestItem> = observable({
|
|
11
|
+
'123': { hello: 'Hi, 123' },
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const Repository = {
|
|
15
|
+
fetch: (key: string, cb: (value: TestItem) => void) => {
|
|
16
|
+
return reaction(() => Database[key], async item => {
|
|
17
|
+
await setTimeoutAsync(100);
|
|
18
|
+
cb(item);
|
|
19
|
+
}, { fireImmediately: true, delay: 100 });
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const subscribeFn = jest.fn().mockImplementation(null);
|
|
24
|
+
const unsubFn = jest.fn().mockImplementation(null);
|
|
25
|
+
|
|
26
|
+
const Cache = new ObservingCache((key, cb) => {
|
|
27
|
+
subscribeFn(key);
|
|
28
|
+
const unsub = Repository.fetch(key, cb);
|
|
29
|
+
return () => {
|
|
30
|
+
unsubFn();
|
|
31
|
+
unsub();
|
|
32
|
+
};
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
Database,
|
|
37
|
+
Repository,
|
|
38
|
+
Cache,
|
|
39
|
+
subscribeFn,
|
|
40
|
+
unsubFn,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
describe('ObservingCache works with', () => {
|
|
46
|
+
it('no observing', async () => {
|
|
47
|
+
const { Database, Cache, subscribeFn, unsubFn } = createData();
|
|
48
|
+
try {
|
|
49
|
+
expect(Cache).not.toBeNull();
|
|
50
|
+
expect(subscribeFn).not.toHaveBeenCalled();
|
|
51
|
+
expect(unsubFn).not.toHaveBeenCalled();
|
|
52
|
+
|
|
53
|
+
const KEY = '123';
|
|
54
|
+
|
|
55
|
+
const deferred = Cache.get(KEY);
|
|
56
|
+
|
|
57
|
+
expect(deferred.busy).toBeUndefined();
|
|
58
|
+
expect(deferred.current).toBeUndefined();
|
|
59
|
+
expect(deferred.busy).toBeTruthy();
|
|
60
|
+
|
|
61
|
+
const expectedItem = toJS(Database[KEY]);
|
|
62
|
+
|
|
63
|
+
await expect(deferred.promise).resolves.toStrictEqual(expectedItem);
|
|
64
|
+
|
|
65
|
+
expect(deferred.current).toStrictEqual(expectedItem);
|
|
66
|
+
expect(deferred.busy).toBeFalsy();
|
|
67
|
+
|
|
68
|
+
expect(unsubFn).toHaveBeenCalledTimes(1);
|
|
69
|
+
|
|
70
|
+
expect(subscribeFn).toHaveBeenCalledTimes(1);
|
|
71
|
+
expect(subscribeFn).toHaveBeenCalledWith(KEY);
|
|
72
|
+
|
|
73
|
+
subscribeFn.mockClear();
|
|
74
|
+
unsubFn.mockClear();
|
|
75
|
+
|
|
76
|
+
Database[KEY] = { hello: 'bye' };
|
|
77
|
+
|
|
78
|
+
expect(subscribeFn).not.toHaveBeenCalled();
|
|
79
|
+
} finally {
|
|
80
|
+
Cache.dispose();
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('infinite observing', async () => {
|
|
85
|
+
const { Database, Cache, subscribeFn, unsubFn } = createData();
|
|
86
|
+
try {
|
|
87
|
+
Cache.useObservingStrategy(true);
|
|
88
|
+
|
|
89
|
+
const KEY = '123';
|
|
90
|
+
|
|
91
|
+
const deferred = Cache.get(KEY);
|
|
92
|
+
|
|
93
|
+
expect(deferred.busy).toBeUndefined();
|
|
94
|
+
expect(deferred.current).toBeUndefined();
|
|
95
|
+
expect(deferred.busy).toBeTruthy();
|
|
96
|
+
|
|
97
|
+
const expectedItem = toJS(Database[KEY]);
|
|
98
|
+
|
|
99
|
+
await expect(deferred.promise).resolves.toStrictEqual(expectedItem);
|
|
100
|
+
|
|
101
|
+
expect(deferred.current).toStrictEqual(expectedItem);
|
|
102
|
+
expect(deferred.busy).toBeFalsy();
|
|
103
|
+
|
|
104
|
+
expect(unsubFn).not.toHaveBeenCalled();
|
|
105
|
+
|
|
106
|
+
expect(subscribeFn).toHaveBeenCalledTimes(1);
|
|
107
|
+
expect(subscribeFn).toHaveBeenCalledWith(KEY);
|
|
108
|
+
|
|
109
|
+
subscribeFn.mockClear();
|
|
110
|
+
unsubFn.mockClear();
|
|
111
|
+
|
|
112
|
+
const replaceItem: TestItem = { hello: 'bye' };
|
|
113
|
+
runInAction(() => {
|
|
114
|
+
Database[KEY] = replaceItem;
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
expect(subscribeFn).not.toHaveBeenCalled();
|
|
118
|
+
|
|
119
|
+
await setTimeoutAsync(300);
|
|
120
|
+
|
|
121
|
+
expect(deferred.current).toStrictEqual(replaceItem);
|
|
122
|
+
} finally {
|
|
123
|
+
Cache.dispose();
|
|
124
|
+
expect(unsubFn).toHaveBeenCalledTimes(1);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
});
|
package/src/cache.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { observable, makeObservable, runInAction, action } from 'mobx';
|
|
2
|
+
import { NumberModel } from './viewModels/NumberModel';
|
|
2
3
|
import { createLogger, ILogger } from './logger';
|
|
3
4
|
|
|
4
5
|
export type DeferredGetter<T> = {
|
|
@@ -28,14 +29,25 @@ export class PromiseCache<T, K = string> {
|
|
|
28
29
|
private readonly _fetchCache: Record<string, Promise<T>> = { };
|
|
29
30
|
|
|
30
31
|
private _logger: ILogger = null;
|
|
32
|
+
private _observeItems = false;
|
|
33
|
+
|
|
34
|
+
private readonly _busyCount = new NumberModel(0);
|
|
31
35
|
|
|
32
36
|
constructor(
|
|
33
37
|
readonly fetcher: (id: K) => Promise<T>,
|
|
34
38
|
readonly keyAdapter?: K extends string ? null : (k: K) => string,
|
|
35
39
|
readonly keyParser?: K extends string ? null : (id: string) => K,
|
|
36
|
-
|
|
40
|
+
observeItems = false,
|
|
37
41
|
) {
|
|
38
42
|
makeObservable(this);
|
|
43
|
+
this._observeItems = observeItems;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
public get busyCount() { return this._busyCount.value; }
|
|
47
|
+
|
|
48
|
+
useObserveItems(observeItems: boolean) {
|
|
49
|
+
this._observeItems = observeItems;
|
|
50
|
+
return this;
|
|
39
51
|
}
|
|
40
52
|
|
|
41
53
|
private _pk(k: K): string {
|
|
@@ -73,10 +85,12 @@ export class PromiseCache<T, K = string> {
|
|
|
73
85
|
return this._itemsStatus[key];
|
|
74
86
|
}
|
|
75
87
|
|
|
76
|
-
getCurrent(id: K): T {
|
|
88
|
+
getCurrent(id: K, initiateFetch = true): T {
|
|
77
89
|
const key = this._pk(id);
|
|
78
90
|
const item = this._itemsCache[key];
|
|
79
|
-
|
|
91
|
+
if (initiateFetch) {
|
|
92
|
+
this.get(id);
|
|
93
|
+
}
|
|
80
94
|
this._logger?.log(key, 'getCurrent returns', item);
|
|
81
95
|
return item;
|
|
82
96
|
}
|
|
@@ -104,17 +118,19 @@ export class PromiseCache<T, K = string> {
|
|
|
104
118
|
|
|
105
119
|
private _doFetchAsync = async (id: K, key: string) => {
|
|
106
120
|
try {
|
|
121
|
+
this._busyCount.increment();
|
|
107
122
|
const res = await this.fetcher(id);
|
|
108
123
|
if (this._fetchCache[key]) {
|
|
109
124
|
this._logger?.log(key, 'item\'s <promise> resolved to', res);
|
|
110
125
|
const result = res
|
|
111
|
-
? (this.
|
|
126
|
+
? (this._observeItems ? observable.object(res) : res)
|
|
112
127
|
: null;
|
|
113
128
|
runInAction(() => this._itemsCache[key] = result);
|
|
114
129
|
}
|
|
115
130
|
return res;
|
|
116
131
|
} finally {
|
|
117
132
|
runInAction(() => {
|
|
133
|
+
this._busyCount.decrement();
|
|
118
134
|
delete this._fetchCache[key];
|
|
119
135
|
this._itemsStatus[key] = false;
|
|
120
136
|
});
|
|
@@ -131,6 +147,11 @@ export class PromiseCache<T, K = string> {
|
|
|
131
147
|
this._set(key, value, undefined, undefined);
|
|
132
148
|
}
|
|
133
149
|
|
|
150
|
+
hasKey(id: K) {
|
|
151
|
+
const key = this._pk(id);
|
|
152
|
+
return this._itemsCache[key] !== undefined || this._itemsStatus[key] !== undefined;
|
|
153
|
+
}
|
|
154
|
+
|
|
134
155
|
keys() {
|
|
135
156
|
return Object.keys(this._itemsCache);
|
|
136
157
|
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { createLogger, ILogger } from './logger';
|
|
2
|
+
import { combineDisposers, IDisposable } from './disposer';
|
|
3
|
+
import { NumberModel } from './viewModels/NumberModel';
|
|
4
|
+
|
|
5
|
+
type Unsub = () => void;
|
|
6
|
+
|
|
7
|
+
export class ObserversMap implements IDisposable {
|
|
8
|
+
/** Unsusbcrobers map: key => unsub fn */
|
|
9
|
+
private readonly _map = new Map<string, () => void>();
|
|
10
|
+
/** Timeouts map: key => timeout handle */
|
|
11
|
+
private readonly _timeouts = new Map<string, any>();
|
|
12
|
+
|
|
13
|
+
private readonly _logger: ILogger = null;
|
|
14
|
+
private readonly _count = new NumberModel();
|
|
15
|
+
|
|
16
|
+
constructor(readonly subscribe: null | ((key: string) => Promise<Unsub[]>), readonly name?: string) {
|
|
17
|
+
this._logger = createLogger(`[Observers:${this.name || '?'}]`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public get count() { return this._count.value; }
|
|
21
|
+
|
|
22
|
+
public getIsObserving(key: string) {
|
|
23
|
+
return this._map.has(key);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public getHasObserveTimeout(key: string) {
|
|
27
|
+
return this.getIsObserving(key) && this._timeouts.has(key);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public async enable(key: string, enable: boolean, clearAfter: number = null, existingUnsubs: Unsub[] = null) {
|
|
31
|
+
if (enable === this.getIsObserving(key)) {
|
|
32
|
+
this.refreshTimeout(key, enable, clearAfter, true);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (enable) {
|
|
37
|
+
this._logger.log('Adding observer for key =', key, clearAfter ? `, clearAfter = ${clearAfter}` : '');
|
|
38
|
+
|
|
39
|
+
// this marker will help to determine whether unsubscribe was requested while we were in process of subscribing
|
|
40
|
+
let disabed = false;
|
|
41
|
+
const marker = () => { disabed = true; };
|
|
42
|
+
|
|
43
|
+
this._map.set(key, marker);
|
|
44
|
+
|
|
45
|
+
if (!this.subscribe && !existingUnsubs) {
|
|
46
|
+
throw new Error('Neither subscribe function nor existingUnsubs has been configured');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const unsubs = existingUnsubs || await this.subscribe(key);
|
|
50
|
+
const result = combineDisposers(...unsubs);
|
|
51
|
+
|
|
52
|
+
if (disabed) { // unsubscribe was requested
|
|
53
|
+
result();
|
|
54
|
+
} else {
|
|
55
|
+
this._map.set(key, result);
|
|
56
|
+
this._count.increment();
|
|
57
|
+
this.refreshTimeout(key, true, clearAfter);
|
|
58
|
+
}
|
|
59
|
+
} else {
|
|
60
|
+
this._logger.log('Removing observer for key =', key);
|
|
61
|
+
this.refreshTimeout(key, false);
|
|
62
|
+
const unsub = this._map.get(key);
|
|
63
|
+
this._map.delete(key);
|
|
64
|
+
unsub();
|
|
65
|
+
this._count.decrement();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
private refreshTimeout(key: string, enable: boolean, timeout?: number, refresh = false) {
|
|
70
|
+
const current = this._timeouts.get(key);
|
|
71
|
+
if (current) {
|
|
72
|
+
clearTimeout(current);
|
|
73
|
+
this._timeouts.delete(key);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (enable && refresh && current == null) {
|
|
77
|
+
// DO NOT setup new timeout because it's not intended to clear subscribtion if it was previously enabled for long term
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (enable && timeout) {
|
|
82
|
+
const t = setTimeout(() => this.enable(key, false), timeout);
|
|
83
|
+
this._timeouts.set(key, t);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
public clear() {
|
|
88
|
+
// Clear timeouts
|
|
89
|
+
for (const t of this._timeouts.values()) {
|
|
90
|
+
clearTimeout(t);
|
|
91
|
+
}
|
|
92
|
+
this._timeouts.clear();
|
|
93
|
+
|
|
94
|
+
// Invoke unsubscribers
|
|
95
|
+
for (const u of this._map.values()) {
|
|
96
|
+
u();
|
|
97
|
+
}
|
|
98
|
+
this._map.clear();
|
|
99
|
+
this._count.setValue(0);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
public dispose() {
|
|
103
|
+
this.clear();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { action } from 'mobx';
|
|
2
|
+
import { Disposable } from './disposer';
|
|
3
|
+
import { PromiseCache, DeferredGetter } from './cache';
|
|
4
|
+
import { ObserversMap } from './observersMap';
|
|
5
|
+
import { Fields } from './fields';
|
|
6
|
+
|
|
7
|
+
export type Unsub = () => void;
|
|
8
|
+
export type Fetcher<T> = (key: string, cb: (val: T) => Promise<void> | void) => Unsub | Promise<Unsub>;
|
|
9
|
+
|
|
10
|
+
type ObserveStrategy = boolean | 'short' | number;
|
|
11
|
+
|
|
12
|
+
export interface IObservingCache<T> {
|
|
13
|
+
get(key: string): DeferredGetter<T>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class ObservingCache<T> extends Disposable implements IObservingCache<T> {
|
|
17
|
+
|
|
18
|
+
private readonly _cache: PromiseCache<T>;
|
|
19
|
+
private readonly _observers: ObserversMap;
|
|
20
|
+
|
|
21
|
+
private _observeStrategy: ObserveStrategy = null;
|
|
22
|
+
private readonly _observeStrategyOverrides: Record<string, ObserveStrategy> = { };
|
|
23
|
+
|
|
24
|
+
private _updater: Fields.Updater<T> = null;
|
|
25
|
+
|
|
26
|
+
constructor(readonly fetcher: Fetcher<T>) {
|
|
27
|
+
super();
|
|
28
|
+
|
|
29
|
+
this._cache = new PromiseCache(this._fetch);
|
|
30
|
+
this._observers = new ObserversMap(this._subscribe);
|
|
31
|
+
|
|
32
|
+
this.disposer.add(this._observers);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public get loadingCount() { return this._cache.busyCount; }
|
|
36
|
+
public get observersCount() { return this._observers.count; }
|
|
37
|
+
|
|
38
|
+
useObservingStrategy(observe: ObserveStrategy) {
|
|
39
|
+
this._observeStrategy = observe;
|
|
40
|
+
if (!this._observeStrategy) {
|
|
41
|
+
this._observers.clear();
|
|
42
|
+
} else {
|
|
43
|
+
const currentKeys = this._cache.keys();
|
|
44
|
+
const timeout = getObserveTimeout(this._observeStrategy);
|
|
45
|
+
currentKeys.forEach(key => this._observers.enable(key, true, timeout));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return this;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
useUpdater(updater: Fields.Updater<T>) {
|
|
52
|
+
this._updater = updater;
|
|
53
|
+
this._cache.useObserveItems(updater != null);
|
|
54
|
+
return this;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
getIsCached(key: string) {
|
|
58
|
+
return this._cache.hasKey(key);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
getCurent(key: string) {
|
|
62
|
+
return this._cache.getCurrent(key, false);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
get(key: string, overrideStrategy?: ObserveStrategy): DeferredGetter<T> {
|
|
66
|
+
if (overrideStrategy !== undefined) {
|
|
67
|
+
this._observeStrategyOverrides[key] = overrideStrategy;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const strategy = firstDefined(this._observeStrategyOverrides[key], this._observeStrategy);
|
|
71
|
+
|
|
72
|
+
if (strategy && !this._observers.getIsObserving(key)) {
|
|
73
|
+
// ensure observe
|
|
74
|
+
if (this._cache.hasKey(key)) {
|
|
75
|
+
// the request has been initiated already
|
|
76
|
+
const timeout = getObserveTimeout(strategy);
|
|
77
|
+
this._observers.enable(key, true, timeout);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return this._cache.getDeferred(key);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
populate = (key: string, item: T) => {
|
|
85
|
+
this._updateItem(key, item);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
private _fetch = async (key: string): Promise<T> => {
|
|
89
|
+
let firstLoad = true;
|
|
90
|
+
|
|
91
|
+
return new Promise<T>((resolve) => {
|
|
92
|
+
Promise.resolve<Unsub>(
|
|
93
|
+
this.fetcher(key, item => {
|
|
94
|
+
if (firstLoad) {
|
|
95
|
+
resolve(item);
|
|
96
|
+
firstLoad = false;
|
|
97
|
+
} else {
|
|
98
|
+
this._updateItem(key, item);
|
|
99
|
+
}
|
|
100
|
+
})
|
|
101
|
+
).then(unsub => {
|
|
102
|
+
const strategy = firstDefined(this._observeStrategyOverrides[key], this._observeStrategy);
|
|
103
|
+
if (!strategy) {
|
|
104
|
+
// immediate unsub in case no observing strategy has been set
|
|
105
|
+
unsub();
|
|
106
|
+
} else {
|
|
107
|
+
const timeout = getObserveTimeout(strategy);
|
|
108
|
+
this._observers.enable(key, true, timeout, [unsub]);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
private _subscribe = (key: string) => {
|
|
115
|
+
return Promise.all([
|
|
116
|
+
this.fetcher(key, item => this._updateItem(key, item)),
|
|
117
|
+
]);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
private _updateItem = action((key: string, item: T) => {
|
|
121
|
+
if (this._updater != null) {
|
|
122
|
+
const current = this._cache.getCurrent(key, false);
|
|
123
|
+
if (current != null) {
|
|
124
|
+
const result = this._updater(current, item);
|
|
125
|
+
// re-set existing item but with updated contents
|
|
126
|
+
this._cache.updateValueDirectly(key, result);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
this._cache.updateValueDirectly(key, item);
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function getObserveTimeout(s: ObserveStrategy) {
|
|
136
|
+
if (!s) {
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return typeof s === 'number'
|
|
141
|
+
? s
|
|
142
|
+
: (s === 'short'
|
|
143
|
+
? 5 * 60 * 1000
|
|
144
|
+
: null
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function firstDefined<T>(...values: (T | undefined)[]) {
|
|
149
|
+
return values.find(v => v !== undefined);
|
|
150
|
+
}
|
|
@@ -22,7 +22,7 @@ export const ValidatorsRegExps = {
|
|
|
22
22
|
expiryDateCard: /^\d+$/,
|
|
23
23
|
cvv: /^\d+$/,
|
|
24
24
|
// eslint-disable-next-line no-useless-escape
|
|
25
|
-
website: /^(https?:\/\/)?([a-z0-9]+(
|
|
25
|
+
website: /^(https?:\/\/)?([a-z0-9]+([a-z0-9\-]+\.))+[a-z]{2,}(:[0-9]{1,})?(\/.*)?$/i,
|
|
26
26
|
linkedin: /^(https?:\/\/)?([a-z]+\d*[a-z]*\.)?linkedin\.com\/.*$/i,
|
|
27
27
|
};
|
|
28
28
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import NumberModel from './NumberModel';
|
|
2
|
+
|
|
3
|
+
export class InitializableModel {
|
|
4
|
+
private readonly _counter = new NumberModel(0);
|
|
5
|
+
|
|
6
|
+
public get initializing() { return this._counter.value > 0; }
|
|
7
|
+
|
|
8
|
+
protected async runOperation<T>(operation: () => Promise<T> | T): Promise<T> {
|
|
9
|
+
try {
|
|
10
|
+
this._counter.increment();
|
|
11
|
+
const res = await operation();
|
|
12
|
+
return res;
|
|
13
|
+
} finally {
|
|
14
|
+
this._counter.decrement();
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|