@fozy-labs/rx-toolkit 0.4.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 +21 -0
- package/README.md +131 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/query/SKIP_TOKEN.d.ts +1 -0
- package/dist/query/SKIP_TOKEN.js +1 -0
- package/dist/query/api/DefaultOptions.d.ts +9 -0
- package/dist/query/api/DefaultOptions.js +9 -0
- package/dist/query/api/createDevtools.d.ts +1 -0
- package/dist/query/api/createDevtools.js +6 -0
- package/dist/query/api/createOperation.d.ts +7 -0
- package/dist/query/api/createOperation.js +6 -0
- package/dist/query/api/createResource.d.ts +3 -0
- package/dist/query/api/createResource.js +2 -0
- package/dist/query/api/createSubResource.d.ts +0 -0
- package/dist/query/api/createSubResource.js +1 -0
- package/dist/query/core/Opertation/Operation.d.ts +34 -0
- package/dist/query/core/Opertation/Operation.js +176 -0
- package/dist/query/core/Opertation/OperationAgent.d.ts +20 -0
- package/dist/query/core/Opertation/OperationAgent.js +54 -0
- package/dist/query/core/QueriesCache.d.ts +8 -0
- package/dist/query/core/QueriesCache.js +31 -0
- package/dist/query/core/Resource/Resource.d.ts +40 -0
- package/dist/query/core/Resource/Resource.js +154 -0
- package/dist/query/core/Resource/ResourceAgent.d.ts +34 -0
- package/dist/query/core/Resource/ResourceAgent.js +87 -0
- package/dist/query/core/Resource/ResourceRef.d.ts +18 -0
- package/dist/query/core/Resource/ResourceRef.js +52 -0
- package/dist/query/core/SharedOptions.d.ts +5 -0
- package/dist/query/core/SharedOptions.js +4 -0
- package/dist/query/index.d.ts +7 -0
- package/dist/query/index.js +7 -0
- package/dist/query/lib/IndirectMap.d.ts +18 -0
- package/dist/query/lib/IndirectMap.js +85 -0
- package/dist/query/lib/ReactiveCache.d.ts +77 -0
- package/dist/query/lib/ReactiveCache.js +96 -0
- package/dist/query/lib/shallowEqual.d.ts +1 -0
- package/dist/query/lib/shallowEqual.js +21 -0
- package/dist/query/react/useOperationAgent.d.ts +9 -0
- package/dist/query/react/useOperationAgent.js +22 -0
- package/dist/query/react/useResourceAgent.d.ts +9 -0
- package/dist/query/react/useResourceAgent.js +25 -0
- package/dist/query/types/Operation.types.d.ts +91 -0
- package/dist/query/types/Operation.types.js +1 -0
- package/dist/query/types/Resource.types.d.ts +90 -0
- package/dist/query/types/Resource.types.js +1 -0
- package/dist/query/types/shared.types.d.ts +4 -0
- package/dist/query/types/shared.types.js +1 -0
- package/dist/react-hooks/index.d.ts +5 -0
- package/dist/react-hooks/index.js +5 -0
- package/dist/react-hooks/useConstant.d.ts +4 -0
- package/dist/react-hooks/useConstant.js +19 -0
- package/dist/react-hooks/useEventHandler.d.ts +1 -0
- package/dist/react-hooks/useEventHandler.js +6 -0
- package/dist/react-hooks/useObservable.d.ts +12 -0
- package/dist/react-hooks/useObservable.js +48 -0
- package/dist/react-hooks/useSignal.d.ts +2 -0
- package/dist/react-hooks/useSignal.js +12 -0
- package/dist/react-hooks/useSyncObservable.d.ts +16 -0
- package/dist/react-hooks/useSyncObservable.js +52 -0
- package/dist/signal/base/Batcher.d.ts +7 -0
- package/dist/signal/base/Batcher.js +21 -0
- package/dist/signal/base/Computed.d.ts +10 -0
- package/dist/signal/base/Computed.js +26 -0
- package/dist/signal/base/Effect.d.ts +12 -0
- package/dist/signal/base/Effect.js +69 -0
- package/dist/signal/base/ReadonlySignal.d.ts +12 -0
- package/dist/signal/base/ReadonlySignal.js +20 -0
- package/dist/signal/base/Signal.d.ts +30 -0
- package/dist/signal/base/Signal.js +44 -0
- package/dist/signal/base/SyncObservable.d.ts +5 -0
- package/dist/signal/base/SyncObservable.js +18 -0
- package/dist/signal/base/Tracker.d.ts +5 -0
- package/dist/signal/base/Tracker.js +7 -0
- package/dist/signal/base/index.d.ts +8 -0
- package/dist/signal/base/index.js +8 -0
- package/dist/signal/base/types.d.ts +15 -0
- package/dist/signal/base/types.js +1 -0
- package/dist/signal/extends/LocalSignal.d.ts +24 -0
- package/dist/signal/extends/LocalSignal.js +66 -0
- package/dist/signal/extends/index.d.ts +1 -0
- package/dist/signal/extends/index.js +1 -0
- package/dist/signal/index.d.ts +3 -0
- package/dist/signal/index.js +3 -0
- package/dist/signal/operators/batch.d.ts +2 -0
- package/dist/signal/operators/batch.js +27 -0
- package/dist/signal/operators/filterUpdates.d.ts +5 -0
- package/dist/signal/operators/filterUpdates.js +18 -0
- package/dist/signal/operators/index.d.ts +4 -0
- package/dist/signal/operators/index.js +4 -0
- package/dist/signal/operators/mapSignals.d.ts +3 -0
- package/dist/signal/operators/mapSignals.js +10 -0
- package/dist/signal/operators/signalize.d.ts +3 -0
- package/dist/signal/operators/signalize.js +6 -0
- package/docs/query/README.md +224 -0
- package/docs/signals/README.md +119 -0
- package/docs/usage/react/README.md +144 -0
- package/package.json +57 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { QueriesCache } from "../QueriesCache";
|
|
2
|
+
import { SharedOptions } from "../SharedOptions";
|
|
3
|
+
import { ResourceAgent } from "./ResourceAgent";
|
|
4
|
+
import { ResourceRef } from "./ResourceRef";
|
|
5
|
+
class ResourceQueryState {
|
|
6
|
+
static create() {
|
|
7
|
+
return {
|
|
8
|
+
abortController: null,
|
|
9
|
+
args: null,
|
|
10
|
+
data: null,
|
|
11
|
+
error: null,
|
|
12
|
+
isError: false,
|
|
13
|
+
isReloading: false,
|
|
14
|
+
isDone: false,
|
|
15
|
+
isSuccess: false,
|
|
16
|
+
isLocked: false,
|
|
17
|
+
isLoading: false,
|
|
18
|
+
isInitiated: false,
|
|
19
|
+
lockCount: 0
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
static load(state = ResourceQueryState.create(), args) {
|
|
23
|
+
return {
|
|
24
|
+
...state,
|
|
25
|
+
args: args,
|
|
26
|
+
isLoading: !state.isDone,
|
|
27
|
+
isReloading: state.isDone,
|
|
28
|
+
isInitiated: true,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
static success(state, data) {
|
|
32
|
+
return {
|
|
33
|
+
...state,
|
|
34
|
+
data,
|
|
35
|
+
isLoading: false,
|
|
36
|
+
isReloading: false,
|
|
37
|
+
isDone: true,
|
|
38
|
+
isSuccess: true,
|
|
39
|
+
isError: false,
|
|
40
|
+
error: null,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
static error(state, error) {
|
|
44
|
+
return {
|
|
45
|
+
...state,
|
|
46
|
+
isLoading: false,
|
|
47
|
+
isReloading: false,
|
|
48
|
+
isDone: true,
|
|
49
|
+
isSuccess: false,
|
|
50
|
+
isError: true,
|
|
51
|
+
error,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
static incrementLock(state) {
|
|
55
|
+
const lockCount = state.lockCount + 1;
|
|
56
|
+
return {
|
|
57
|
+
...state,
|
|
58
|
+
isLocked: lockCount > 0,
|
|
59
|
+
lockCount
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
static decrementLock(state) {
|
|
63
|
+
const lockCount = Math.max(0, state.lockCount - 1);
|
|
64
|
+
return {
|
|
65
|
+
...state,
|
|
66
|
+
isLocked: lockCount > 0,
|
|
67
|
+
lockCount
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
static setData(state, data) {
|
|
71
|
+
return {
|
|
72
|
+
...state,
|
|
73
|
+
data
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
export class Resource {
|
|
78
|
+
_options;
|
|
79
|
+
_queriesCache = new QueriesCache('res');
|
|
80
|
+
constructor(_options) {
|
|
81
|
+
this._options = _options;
|
|
82
|
+
}
|
|
83
|
+
createAgent = () => {
|
|
84
|
+
return new ResourceAgent(this);
|
|
85
|
+
};
|
|
86
|
+
createRef = (args) => {
|
|
87
|
+
return new ResourceRef(this, args);
|
|
88
|
+
};
|
|
89
|
+
getQueryCache(args) {
|
|
90
|
+
return this._queriesCache.getQueryCache(args);
|
|
91
|
+
}
|
|
92
|
+
createQueryCache(args) {
|
|
93
|
+
return this._queriesCache.createQueryCache(args, ResourceQueryState.create());
|
|
94
|
+
}
|
|
95
|
+
incrementLock(args, options) {
|
|
96
|
+
let cache = options?.cache ?? this.getQueryCache(args);
|
|
97
|
+
if (!cache) {
|
|
98
|
+
cache = this.createQueryCache(args);
|
|
99
|
+
}
|
|
100
|
+
cache.next(ResourceQueryState.incrementLock(cache.value));
|
|
101
|
+
return cache;
|
|
102
|
+
}
|
|
103
|
+
decrementLock(args, options) {
|
|
104
|
+
let cache = options?.cache ?? this.getQueryCache(args);
|
|
105
|
+
if (!cache) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
cache.next(ResourceQueryState.decrementLock(cache.value));
|
|
109
|
+
return cache;
|
|
110
|
+
}
|
|
111
|
+
updateData(args, updateFn, options) {
|
|
112
|
+
let cache = options?.cache ?? this.getQueryCache(args);
|
|
113
|
+
if (!cache) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
const cacheValue = cache.value;
|
|
117
|
+
if (!cacheValue.isDone) {
|
|
118
|
+
return cache;
|
|
119
|
+
}
|
|
120
|
+
const newData = updateFn(cacheValue.data);
|
|
121
|
+
cache.next(ResourceQueryState.setData(cache.value, newData));
|
|
122
|
+
return cache;
|
|
123
|
+
}
|
|
124
|
+
initiate(args, options) {
|
|
125
|
+
let cache = options?.cache ?? this._queriesCache.getQueryCache(args);
|
|
126
|
+
const state = ResourceQueryState.load(cache?.value, args);
|
|
127
|
+
if (!cache) {
|
|
128
|
+
cache = this._queriesCache.createQueryCache(args, state);
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
cache.next(state);
|
|
132
|
+
}
|
|
133
|
+
let abortController = state.abortController;
|
|
134
|
+
abortController?.abort();
|
|
135
|
+
abortController = new AbortController();
|
|
136
|
+
const query = this._options.queryFn(args, { abortSignal: abortController.signal });
|
|
137
|
+
query
|
|
138
|
+
.then((data) => {
|
|
139
|
+
if (abortController.signal.aborted) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const selectedData = this._options.select ? this._options.select(data) : data;
|
|
143
|
+
cache.next(ResourceQueryState.success(state, selectedData));
|
|
144
|
+
})
|
|
145
|
+
.catch((error) => {
|
|
146
|
+
if (abortController.signal.aborted) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
SharedOptions.onError?.(error);
|
|
150
|
+
cache.next(ResourceQueryState.error(state, error));
|
|
151
|
+
});
|
|
152
|
+
return cache;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Computed } from "../../../signal";
|
|
2
|
+
import { ResourceAgentInstance, ResourceDefinition } from "../../../query/types/Resource.types";
|
|
3
|
+
import type { Resource } from "./Resource";
|
|
4
|
+
export declare class ResourceAgent<D extends ResourceDefinition> implements ResourceAgentInstance<D> {
|
|
5
|
+
private _resource;
|
|
6
|
+
private _resources$;
|
|
7
|
+
state$: Computed<{
|
|
8
|
+
isInitiated: boolean;
|
|
9
|
+
isLoading: boolean;
|
|
10
|
+
isDone: boolean;
|
|
11
|
+
isSuccess: boolean;
|
|
12
|
+
isError: boolean;
|
|
13
|
+
isLocked: boolean;
|
|
14
|
+
isReloading: boolean;
|
|
15
|
+
error: undefined;
|
|
16
|
+
data: undefined;
|
|
17
|
+
args: D["Args"];
|
|
18
|
+
} | {
|
|
19
|
+
isInitiated: boolean;
|
|
20
|
+
isLoading: boolean;
|
|
21
|
+
isDone: boolean;
|
|
22
|
+
isSuccess: boolean;
|
|
23
|
+
isError: boolean;
|
|
24
|
+
isLocked: boolean;
|
|
25
|
+
isReloading: boolean;
|
|
26
|
+
error: {} | undefined;
|
|
27
|
+
data: NonNullable<D["Data"]> | undefined;
|
|
28
|
+
args: NonNullable<D["Args"]> | undefined;
|
|
29
|
+
}>;
|
|
30
|
+
constructor(_resource: Resource<D>);
|
|
31
|
+
private _next;
|
|
32
|
+
initiate(args: D["Args"], force?: boolean): void;
|
|
33
|
+
createAgent: () => ResourceAgentInstance<D>;
|
|
34
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { Computed, Signal } from "../../../signal";
|
|
2
|
+
export class ResourceAgent {
|
|
3
|
+
_resource;
|
|
4
|
+
_resources$ = new Signal({
|
|
5
|
+
previous$: null,
|
|
6
|
+
current$: null,
|
|
7
|
+
});
|
|
8
|
+
state$ = new Computed(() => {
|
|
9
|
+
const resources = this._resources$.value;
|
|
10
|
+
let prevState;
|
|
11
|
+
const currState = resources.current$?.value;
|
|
12
|
+
if (!currState?.isDone) {
|
|
13
|
+
prevState = resources.previous$?.value;
|
|
14
|
+
}
|
|
15
|
+
// Нет текущего состояния — дефолт
|
|
16
|
+
if (!currState) {
|
|
17
|
+
return {
|
|
18
|
+
isInitiated: false,
|
|
19
|
+
isLoading: false,
|
|
20
|
+
isDone: false,
|
|
21
|
+
isSuccess: false,
|
|
22
|
+
isError: false,
|
|
23
|
+
isLocked: false,
|
|
24
|
+
isReloading: false,
|
|
25
|
+
error: undefined,
|
|
26
|
+
data: undefined,
|
|
27
|
+
args: undefined,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
// Если идёт загрузка, но есть успешные данные из прошлого запроса — показываем их
|
|
31
|
+
const isShowPrev = currState.isLoading && prevState && prevState.isSuccess;
|
|
32
|
+
return {
|
|
33
|
+
isInitiated: currState.isInitiated || !!prevState,
|
|
34
|
+
isLoading: currState.isLoading,
|
|
35
|
+
isDone: currState.isDone,
|
|
36
|
+
isSuccess: currState.isSuccess,
|
|
37
|
+
isError: currState.isError,
|
|
38
|
+
isLocked: currState.isLocked,
|
|
39
|
+
isReloading: currState.isReloading,
|
|
40
|
+
error: isShowPrev ? prevState.error ?? undefined : currState.error ?? undefined,
|
|
41
|
+
data: isShowPrev ? prevState.data ?? undefined : currState.data ?? undefined,
|
|
42
|
+
args: currState.args ?? undefined,
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
constructor(_resource) {
|
|
46
|
+
this._resource = _resource;
|
|
47
|
+
}
|
|
48
|
+
_next(newCache) {
|
|
49
|
+
const { previous$, current$ } = this._resources$.value;
|
|
50
|
+
if (!current$) {
|
|
51
|
+
this._resources$.next({
|
|
52
|
+
previous$: null,
|
|
53
|
+
current$: newCache,
|
|
54
|
+
});
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (!current$.value.isDone && previous$?.value.isDone) {
|
|
58
|
+
this._resources$.next({
|
|
59
|
+
previous$: previous$,
|
|
60
|
+
current$: newCache,
|
|
61
|
+
});
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
this._resources$.next({
|
|
65
|
+
previous$: current$,
|
|
66
|
+
current$: newCache,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
initiate(args, force = false) {
|
|
70
|
+
const current = this._resources$.value.current$;
|
|
71
|
+
const cache = this._resource.getQueryCache(args);
|
|
72
|
+
if (!cache) {
|
|
73
|
+
const newCache = this._resource.initiate(args);
|
|
74
|
+
this._next(newCache);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (force || !(cache.value.isDone || cache.value.isLoading)) {
|
|
78
|
+
this._resource.initiate(args, { cache });
|
|
79
|
+
}
|
|
80
|
+
if (current !== cache) {
|
|
81
|
+
this._next(cache);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
createAgent = () => {
|
|
85
|
+
return this;
|
|
86
|
+
};
|
|
87
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Resource } from "./Resource";
|
|
2
|
+
import { ResourceDefinition, ResourceRefInstanse } from "../../types/Resource.types";
|
|
3
|
+
export declare class ResourceRef<D extends ResourceDefinition> implements ResourceRefInstanse<D> {
|
|
4
|
+
private readonly _resource;
|
|
5
|
+
private readonly _args;
|
|
6
|
+
private _cacheItem;
|
|
7
|
+
constructor(_resource: Resource<D>, _args: D["Args"]);
|
|
8
|
+
get has(): boolean;
|
|
9
|
+
lock(): {
|
|
10
|
+
unlock: () => void;
|
|
11
|
+
};
|
|
12
|
+
unlockOne(): void;
|
|
13
|
+
update(updateFn: (data: D["Data"]) => D["Data"]): {
|
|
14
|
+
rollback: () => void;
|
|
15
|
+
};
|
|
16
|
+
create(data: D["Data"]): void;
|
|
17
|
+
invalidate(): void;
|
|
18
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export class ResourceRef {
|
|
2
|
+
_resource;
|
|
3
|
+
_args;
|
|
4
|
+
_cacheItem = null;
|
|
5
|
+
constructor(_resource, _args) {
|
|
6
|
+
this._resource = _resource;
|
|
7
|
+
this._args = _args;
|
|
8
|
+
}
|
|
9
|
+
get has() {
|
|
10
|
+
if (this._cacheItem)
|
|
11
|
+
return true;
|
|
12
|
+
this._cacheItem = this._resource.getQueryCache(this._args) ?? null;
|
|
13
|
+
return !!this._cacheItem;
|
|
14
|
+
}
|
|
15
|
+
lock() {
|
|
16
|
+
this._cacheItem = this._resource.incrementLock(this._args, { cache: this._cacheItem ?? undefined });
|
|
17
|
+
let isLocked = true;
|
|
18
|
+
return {
|
|
19
|
+
unlock: () => {
|
|
20
|
+
if (!isLocked)
|
|
21
|
+
return;
|
|
22
|
+
isLocked = false;
|
|
23
|
+
this._resource.decrementLock(this._args, { cache: this._cacheItem });
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
unlockOne() {
|
|
28
|
+
this._cacheItem = this._resource.decrementLock(this._args, { cache: this._cacheItem });
|
|
29
|
+
}
|
|
30
|
+
update(updateFn) {
|
|
31
|
+
const cacheItem = this._cacheItem ?? this._resource.getQueryCache(this._args);
|
|
32
|
+
if (!cacheItem) {
|
|
33
|
+
console.warn('Trying to update non-existing cache item');
|
|
34
|
+
return {
|
|
35
|
+
rollback: () => { }
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
const value = cacheItem.value;
|
|
39
|
+
this._resource.updateData(this._args, updateFn, { cache: cacheItem });
|
|
40
|
+
return {
|
|
41
|
+
rollback: () => {
|
|
42
|
+
this._resource.updateData(this._args, () => value.data, { cache: cacheItem });
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
create(data) {
|
|
47
|
+
throw new Error("Method not implemented.");
|
|
48
|
+
}
|
|
49
|
+
invalidate() {
|
|
50
|
+
throw new Error("Method not implemented.");
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export * from './api/createResource';
|
|
2
|
+
export * from './api/createOperation';
|
|
3
|
+
export * from './api/DefaultOptions';
|
|
4
|
+
export * from './api/createDevtools';
|
|
5
|
+
export * from './SKIP_TOKEN';
|
|
6
|
+
export * from './react/useResourceAgent';
|
|
7
|
+
export * from './react/useOperationAgent';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export * from './api/createResource';
|
|
2
|
+
export * from './api/createOperation';
|
|
3
|
+
export * from './api/DefaultOptions';
|
|
4
|
+
export * from './api/createDevtools';
|
|
5
|
+
export * from './SKIP_TOKEN';
|
|
6
|
+
export * from './react/useResourceAgent';
|
|
7
|
+
export * from './react/useOperationAgent';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
type CompareFn<T> = (a: T, b: T) => boolean;
|
|
2
|
+
export declare class IndirectMap<KEY, VALUE> {
|
|
3
|
+
private _compareObjectsFn;
|
|
4
|
+
private _compareCache;
|
|
5
|
+
private _map;
|
|
6
|
+
constructor(_compareObjectsFn?: CompareFn<KEY>);
|
|
7
|
+
private _getCachedKey;
|
|
8
|
+
get(key: KEY): VALUE | undefined;
|
|
9
|
+
set(key: KEY, value: VALUE): void;
|
|
10
|
+
/**
|
|
11
|
+
* Удаляет элемент из кеша, не зависимо от того,
|
|
12
|
+
* ссылается ли на него другой объект или нет
|
|
13
|
+
* @param key
|
|
14
|
+
*/
|
|
15
|
+
delete(key: KEY): void;
|
|
16
|
+
has(key: KEY): boolean;
|
|
17
|
+
}
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { shallowEqual } from "../../query/lib/shallowEqual";
|
|
2
|
+
export class IndirectMap {
|
|
3
|
+
_compareObjectsFn;
|
|
4
|
+
_compareCache = new WeakMap();
|
|
5
|
+
_map = new Map();
|
|
6
|
+
constructor(_compareObjectsFn = shallowEqual) {
|
|
7
|
+
this._compareObjectsFn = _compareObjectsFn;
|
|
8
|
+
}
|
|
9
|
+
_getCachedKey(key) {
|
|
10
|
+
const cachedKey = this._compareCache.get(key);
|
|
11
|
+
if (cachedKey) {
|
|
12
|
+
return cachedKey;
|
|
13
|
+
}
|
|
14
|
+
for (const cachedKey of this._map.keys()) {
|
|
15
|
+
if (this._compareObjectsFn(key, cachedKey)) {
|
|
16
|
+
this._compareCache.set(key, cachedKey);
|
|
17
|
+
return cachedKey;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
get(key) {
|
|
23
|
+
const item = this._map.get(key);
|
|
24
|
+
if (!item) {
|
|
25
|
+
const isObject = typeof key === 'object' && key !== null;
|
|
26
|
+
if (!isObject) {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
const cachedKey = this._getCachedKey(key);
|
|
30
|
+
if (!cachedKey) {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
return this._map.get(cachedKey);
|
|
34
|
+
}
|
|
35
|
+
return item;
|
|
36
|
+
}
|
|
37
|
+
set(key, value) {
|
|
38
|
+
const has = this._map.has(key);
|
|
39
|
+
if (has) {
|
|
40
|
+
this._map.set(key, value);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
const isObject = typeof key === 'object' && key !== null;
|
|
44
|
+
if (!isObject) {
|
|
45
|
+
this._map.set(key, value);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const cachedKey = this._getCachedKey(key);
|
|
49
|
+
if (cachedKey) {
|
|
50
|
+
this._map.set(cachedKey, value);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
this._map.set(key, value);
|
|
54
|
+
this._compareCache.set(key, key);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Удаляет элемент из кеша, не зависимо от того,
|
|
60
|
+
* ссылается ли на него другой объект или нет
|
|
61
|
+
* @param key
|
|
62
|
+
*/
|
|
63
|
+
delete(key) {
|
|
64
|
+
const isObject = typeof key === 'object' && key !== null;
|
|
65
|
+
if (isObject) {
|
|
66
|
+
const cachedKey = this._getCachedKey(key);
|
|
67
|
+
if (cachedKey) {
|
|
68
|
+
this._map.delete(cachedKey);
|
|
69
|
+
this._compareCache.delete(key);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
this._map.delete(key);
|
|
73
|
+
}
|
|
74
|
+
has(key) {
|
|
75
|
+
const has = this._map.has(key);
|
|
76
|
+
if (!has && typeof key === 'object' && key !== null) {
|
|
77
|
+
const cachedKey = this._getCachedKey(key);
|
|
78
|
+
if (!cachedKey) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
return this._map.has(cachedKey);
|
|
82
|
+
}
|
|
83
|
+
return has;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Observable, Subject } from "rxjs";
|
|
2
|
+
type Options<VALUE> = {
|
|
3
|
+
/**
|
|
4
|
+
* Начальное состояние кэша
|
|
5
|
+
*/
|
|
6
|
+
initialState: VALUE;
|
|
7
|
+
/**
|
|
8
|
+
* Время жизни кэша в миллисекундах (пока нет подписок на кеш)
|
|
9
|
+
* @default 60_000 (1 минута)
|
|
10
|
+
*/
|
|
11
|
+
cacheLifeTime?: number;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Класс `ReactiveCache` представляет собой реактивный кэш,
|
|
15
|
+
* который позволяет управлять состоянием и временем жизни кэшированных данных.
|
|
16
|
+
*
|
|
17
|
+
* @template VALUE Тип значения, хранимого в кэше.
|
|
18
|
+
*/
|
|
19
|
+
export declare class ReactiveCache<VALUE> {
|
|
20
|
+
/**
|
|
21
|
+
* Время жизни кэша в миллисекундах.
|
|
22
|
+
* Если значение больше 0, то кэш очищается через указанное время после отписки.
|
|
23
|
+
* @private
|
|
24
|
+
*/
|
|
25
|
+
private readonly _cacheLifeTime;
|
|
26
|
+
/**
|
|
27
|
+
* Внутренний `BehaviorSubject`, хранящий текущее состояние кэша.
|
|
28
|
+
* @private
|
|
29
|
+
*/
|
|
30
|
+
private _state$;
|
|
31
|
+
/**
|
|
32
|
+
* Текущее значение.
|
|
33
|
+
* @private
|
|
34
|
+
*/
|
|
35
|
+
private _value;
|
|
36
|
+
/**
|
|
37
|
+
* Реактивное значене (Observable)
|
|
38
|
+
*/
|
|
39
|
+
value$: Observable<VALUE>;
|
|
40
|
+
/**
|
|
41
|
+
* Значение без сайд-эффектов (для использования в DevTools)
|
|
42
|
+
*/
|
|
43
|
+
spy$: Observable<VALUE>;
|
|
44
|
+
/**
|
|
45
|
+
* Subject, уведомляющий об очистке кэша.
|
|
46
|
+
*/
|
|
47
|
+
onClean$: Subject<VALUE>;
|
|
48
|
+
/**
|
|
49
|
+
* Создает новый экземпляр `ReactiveCacheItem`.
|
|
50
|
+
*
|
|
51
|
+
* @param options Параметры для настройки элемента кэша.
|
|
52
|
+
* @param options.initialState Начальное состояние кэша.
|
|
53
|
+
* @param options.cacheLifeTime Время жизни кэша в миллисекундах (по умолчанию 60_000).
|
|
54
|
+
*/
|
|
55
|
+
constructor(options: Options<VALUE>);
|
|
56
|
+
/**
|
|
57
|
+
* Возвращает текущее значение кэша.
|
|
58
|
+
* @returns {VALUE} Текущее значение кэша.
|
|
59
|
+
*/
|
|
60
|
+
get value(): VALUE;
|
|
61
|
+
/**
|
|
62
|
+
* Возвращает текущее значение кэша.
|
|
63
|
+
* @returns {VALUE} Текущее значение кэша.
|
|
64
|
+
*/
|
|
65
|
+
peek(): VALUE;
|
|
66
|
+
/**
|
|
67
|
+
* Устанавливает новое значение в кэш и обновляет поток состояния.
|
|
68
|
+
*
|
|
69
|
+
* @param value Новое значение для кэша.
|
|
70
|
+
*/
|
|
71
|
+
next(value: VALUE): void;
|
|
72
|
+
/**
|
|
73
|
+
* Завершает работу кэша, закрывая все потоки и уведомляя об очистке.
|
|
74
|
+
*/
|
|
75
|
+
complete(): void;
|
|
76
|
+
}
|
|
77
|
+
export {};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { finalize, ReplaySubject, share, Subject, takeUntil, timer } from "rxjs";
|
|
2
|
+
import { Signal } from "../../signal";
|
|
3
|
+
/**
|
|
4
|
+
* Класс `ReactiveCache` представляет собой реактивный кэш,
|
|
5
|
+
* который позволяет управлять состоянием и временем жизни кэшированных данных.
|
|
6
|
+
*
|
|
7
|
+
* @template VALUE Тип значения, хранимого в кэше.
|
|
8
|
+
*/
|
|
9
|
+
export class ReactiveCache {
|
|
10
|
+
/**
|
|
11
|
+
* Время жизни кэша в миллисекундах.
|
|
12
|
+
* Если значение больше 0, то кэш очищается через указанное время после отписки.
|
|
13
|
+
* @private
|
|
14
|
+
*/
|
|
15
|
+
_cacheLifeTime;
|
|
16
|
+
/**
|
|
17
|
+
* Внутренний `BehaviorSubject`, хранящий текущее состояние кэша.
|
|
18
|
+
* @private
|
|
19
|
+
*/
|
|
20
|
+
_state$;
|
|
21
|
+
/**
|
|
22
|
+
* Текущее значение.
|
|
23
|
+
* @private
|
|
24
|
+
*/
|
|
25
|
+
_value;
|
|
26
|
+
/**
|
|
27
|
+
* Реактивное значене (Observable)
|
|
28
|
+
*/
|
|
29
|
+
value$;
|
|
30
|
+
/**
|
|
31
|
+
* Значение без сайд-эффектов (для использования в DevTools)
|
|
32
|
+
*/
|
|
33
|
+
spy$;
|
|
34
|
+
/**
|
|
35
|
+
* Subject, уведомляющий об очистке кэша.
|
|
36
|
+
*/
|
|
37
|
+
onClean$ = new Subject();
|
|
38
|
+
/**
|
|
39
|
+
* Создает новый экземпляр `ReactiveCacheItem`.
|
|
40
|
+
*
|
|
41
|
+
* @param options Параметры для настройки элемента кэша.
|
|
42
|
+
* @param options.initialState Начальное состояние кэша.
|
|
43
|
+
* @param options.cacheLifeTime Время жизни кэша в миллисекундах (по умолчанию 60_000).
|
|
44
|
+
*/
|
|
45
|
+
constructor(options) {
|
|
46
|
+
this._cacheLifeTime = options.cacheLifeTime || 60_000;
|
|
47
|
+
this._state$ = new Signal(options.initialState);
|
|
48
|
+
this._value = options.initialState;
|
|
49
|
+
this.spy$ = this._state$.pipe(takeUntil(this.onClean$));
|
|
50
|
+
this.value$ = this._state$.pipe(finalize(() => {
|
|
51
|
+
this.complete();
|
|
52
|
+
}), share({
|
|
53
|
+
connector: () => new ReplaySubject(1),
|
|
54
|
+
/**
|
|
55
|
+
* Если lifetime больше 0,
|
|
56
|
+
* то очистим кэш значения по истечении этого времени,
|
|
57
|
+
* иначе очищаем сразу после отписки.
|
|
58
|
+
*/
|
|
59
|
+
resetOnRefCountZero: this._cacheLifeTime > 0
|
|
60
|
+
? () => timer(this._cacheLifeTime)
|
|
61
|
+
: true,
|
|
62
|
+
resetOnComplete: true,
|
|
63
|
+
}));
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Возвращает текущее значение кэша.
|
|
67
|
+
* @returns {VALUE} Текущее значение кэша.
|
|
68
|
+
*/
|
|
69
|
+
get value() {
|
|
70
|
+
return this._state$.value;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Возвращает текущее значение кэша.
|
|
74
|
+
* @returns {VALUE} Текущее значение кэша.
|
|
75
|
+
*/
|
|
76
|
+
peek() {
|
|
77
|
+
return this._value;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Устанавливает новое значение в кэш и обновляет поток состояния.
|
|
81
|
+
*
|
|
82
|
+
* @param value Новое значение для кэша.
|
|
83
|
+
*/
|
|
84
|
+
next(value) {
|
|
85
|
+
this._value = value;
|
|
86
|
+
this._state$.next(value);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Завершает работу кэша, закрывая все потоки и уведомляя об очистке.
|
|
90
|
+
*/
|
|
91
|
+
complete() {
|
|
92
|
+
this._state$.complete();
|
|
93
|
+
this.onClean$.next(this._value);
|
|
94
|
+
this.onClean$.complete();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function shallowEqual(a: unknown, b: unknown): boolean;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export function shallowEqual(a, b) {
|
|
2
|
+
if (a === b) {
|
|
3
|
+
return true;
|
|
4
|
+
}
|
|
5
|
+
if (typeof a !== "object" || a === null || typeof b !== "object" || b === null) {
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
8
|
+
const keysA = Object.keys(a);
|
|
9
|
+
const keysB = Object.keys(b);
|
|
10
|
+
if (keysA.length !== keysB.length) {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
for (let i = 0; i < keysA.length; i++) {
|
|
14
|
+
const key = keysA[i];
|
|
15
|
+
// @ts-ignore
|
|
16
|
+
if (!Object.prototype.hasOwnProperty.call(b, key) || a[key] !== b[key]) {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return true;
|
|
21
|
+
}
|