@fozy-labs/rx-toolkit 0.4.15 → 0.4.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/common/devtools/reduxDevtools.js +0 -1
- package/dist/common/options/SharedOptions.d.ts +3 -1
- package/dist/common/options/SharedOptions.js +2 -0
- package/dist/common/react/index.d.ts +1 -0
- package/dist/common/react/index.js +1 -0
- package/dist/common/react/useSyncObservable.js +4 -5
- package/dist/common/react/useUnmount.d.ts +1 -0
- package/dist/common/react/useUnmount.js +24 -0
- package/dist/common/utils/deepEqual.d.ts +1 -0
- package/dist/common/utils/deepEqual.js +21 -0
- package/dist/common/utils/index.d.ts +2 -0
- package/dist/common/utils/index.js +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/query/api/cleanAllQueriesCache.d.ts +1 -0
- package/dist/query/api/cleanAllQueriesCache.js +4 -0
- package/dist/query/core/CleanAllQueriesSignal.d.ts +6 -0
- package/dist/query/core/CleanAllQueriesSignal.js +11 -0
- package/dist/query/core/Opertation/Operation.js +4 -0
- package/dist/query/core/Opertation/OperationAgent.d.ts +2 -0
- package/dist/query/core/Opertation/OperationAgent.js +18 -1
- package/dist/query/core/QueriesCache.d.ts +3 -1
- package/dist/query/core/QueriesCache.js +11 -17
- package/dist/query/core/Resource/Resource.d.ts +2 -1
- package/dist/query/core/Resource/Resource.js +14 -4
- package/dist/query/core/Resource/ResourceAgent.d.ts +3 -1
- package/dist/query/core/Resource/ResourceAgent.js +39 -20
- package/dist/query/index.d.ts +1 -0
- package/dist/query/index.js +1 -0
- package/dist/query/lib/IndirectMap.d.ts +1 -0
- package/dist/query/lib/IndirectMap.js +4 -1
- package/dist/query/lib/ReactiveCache.d.ts +1 -0
- package/dist/query/lib/ReactiveCache.js +4 -0
- package/dist/query/react/useOperationAgent.js +4 -1
- package/dist/query/react/useResourceAgent.js +5 -6
- package/dist/query/types/Operation.types.d.ts +2 -0
- package/dist/query/types/Resource.types.d.ts +6 -0
- package/dist/signals/signals/Effect.d.ts +2 -1
- package/dist/signals/signals/Effect.js +12 -1
- package/package.json +1 -1
- /package/dist/{query/lib → common/utils}/shallowEqual.d.ts +0 -0
- /package/dist/{query/lib → common/utils}/shallowEqual.js +0 -0
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import { DevtoolsLike } from "../../common/devtools";
|
|
2
1
|
import { Observable } from "rxjs";
|
|
2
|
+
import { DevtoolsLike } from "../../common/devtools";
|
|
3
|
+
import { shallowEqual } from "../../common/utils";
|
|
3
4
|
export declare class SharedOptions {
|
|
4
5
|
static DEVTOOLS: DevtoolsLike | null;
|
|
5
6
|
static onQueryError: ((error: unknown) => void) | null;
|
|
6
7
|
static getScopeName: (() => string | null) | null;
|
|
7
8
|
static getScopeDestroyed$: (() => Observable<void> | null) | null;
|
|
9
|
+
static defaultCompareArgs: typeof shallowEqual;
|
|
8
10
|
}
|
|
@@ -2,6 +2,7 @@ import React from 'react';
|
|
|
2
2
|
import { BehaviorSubject } from 'rxjs';
|
|
3
3
|
import { useConstant } from "./useConstant";
|
|
4
4
|
import { useEventHandler } from "./useEventHandler";
|
|
5
|
+
import { useUnmount } from "../../common/react/useUnmount";
|
|
5
6
|
const NONE = Symbol('NONE');
|
|
6
7
|
/**
|
|
7
8
|
* Hook for automatically subscribing and unsubscribing from an Observable.
|
|
@@ -29,11 +30,9 @@ export function useSyncObservable(input$, initialValue = NONE) {
|
|
|
29
30
|
subscription,
|
|
30
31
|
};
|
|
31
32
|
}, [input$]);
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
subject$.complete();
|
|
36
|
-
};
|
|
33
|
+
useUnmount(() => {
|
|
34
|
+
subscription.unsubscribe();
|
|
35
|
+
subject$.complete();
|
|
37
36
|
}, [subject$]);
|
|
38
37
|
const subscribe = React.useCallback((updateStore) => {
|
|
39
38
|
const subjectSubscription = subject$.subscribe(updateStore);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function useUnmount(fn: () => void, deps?: any[]): void;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
export function useUnmount(fn, deps = []) {
|
|
3
|
+
const preventUnmountRef = React.useRef(null);
|
|
4
|
+
React.useEffect(() => {
|
|
5
|
+
return () => {
|
|
6
|
+
let isPrevented = false;
|
|
7
|
+
preventUnmountRef.current = () => {
|
|
8
|
+
isPrevented = true;
|
|
9
|
+
};
|
|
10
|
+
new Promise((resolve) => {
|
|
11
|
+
resolve();
|
|
12
|
+
}).then(() => {
|
|
13
|
+
if (isPrevented)
|
|
14
|
+
return;
|
|
15
|
+
preventUnmountRef.current = null;
|
|
16
|
+
fn();
|
|
17
|
+
});
|
|
18
|
+
};
|
|
19
|
+
}, deps);
|
|
20
|
+
React.useEffect(() => {
|
|
21
|
+
preventUnmountRef.current?.();
|
|
22
|
+
preventUnmountRef.current = null;
|
|
23
|
+
}, deps);
|
|
24
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function deepEqual(a: unknown, b: unknown): boolean;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export function deepEqual(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) || !deepEqual(a[key], b[key])) {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return true;
|
|
21
|
+
}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function cleanAllQueriesCache(): void;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Subject } from "rxjs";
|
|
2
|
+
import { Batcher } from "../../signals";
|
|
3
|
+
export class CleanAllQueriesSignal {
|
|
4
|
+
static subject$ = new Subject();
|
|
5
|
+
static clean$ = CleanAllQueriesSignal.subject$;
|
|
6
|
+
static clean() {
|
|
7
|
+
Batcher.batch(() => {
|
|
8
|
+
CleanAllQueriesSignal.subject$.next();
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -3,6 +3,7 @@ import { Batcher } from "../../../signals";
|
|
|
3
3
|
import { QueriesCache } from "../QueriesCache";
|
|
4
4
|
import { QueriesLifetimeHooks } from "../QueriesLifetimeHooks";
|
|
5
5
|
import { OperationAgent } from "./OperationAgent";
|
|
6
|
+
import { CleanAllQueriesSignal } from "../../../query/core/CleanAllQueriesSignal";
|
|
6
7
|
class OperationQueryState {
|
|
7
8
|
static create() {
|
|
8
9
|
return {
|
|
@@ -65,6 +66,9 @@ export class Operation {
|
|
|
65
66
|
devtoolsName: _options.devtoolsName,
|
|
66
67
|
});
|
|
67
68
|
this._createLinks();
|
|
69
|
+
CleanAllQueriesSignal.clean$.subscribe(() => {
|
|
70
|
+
this._queriesCache.clear();
|
|
71
|
+
});
|
|
68
72
|
}
|
|
69
73
|
_createLinks() {
|
|
70
74
|
this._options.link?.((linkOptions) => {
|
|
@@ -4,6 +4,7 @@ import type { Operation } from "./Operation";
|
|
|
4
4
|
export declare class OperationAgent<D extends OperationDefinition> implements OperationAgentInstanse<D> {
|
|
5
5
|
private _operation;
|
|
6
6
|
private _operations$;
|
|
7
|
+
private _effect;
|
|
7
8
|
state$: Computed<{
|
|
8
9
|
isLoading: boolean;
|
|
9
10
|
isDone: boolean;
|
|
@@ -17,4 +18,5 @@ export declare class OperationAgent<D extends OperationDefinition> implements Op
|
|
|
17
18
|
private _next;
|
|
18
19
|
initiate(args: D["Args"]): void;
|
|
19
20
|
createAgent(): OperationAgentInstanse<D>;
|
|
21
|
+
complete(): void;
|
|
20
22
|
}
|
|
@@ -1,9 +1,21 @@
|
|
|
1
|
-
import { Computed, Signal } from "../../../signals";
|
|
1
|
+
import { Computed, Effect, Signal } from "../../../signals";
|
|
2
2
|
export class OperationAgent {
|
|
3
3
|
_operation;
|
|
4
4
|
_operations$ = new Signal({
|
|
5
5
|
current$: null,
|
|
6
6
|
}, { isDisabled: true });
|
|
7
|
+
_effect = new Effect(() => {
|
|
8
|
+
const current$ = this._operations$.value.current$;
|
|
9
|
+
// Если ресурс который мы слушаем очистился, то инициируем его заново с теми же аргументами
|
|
10
|
+
const sub = current$?.onClean$.subscribe(() => {
|
|
11
|
+
this._operations$.next({
|
|
12
|
+
current$: null,
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
return () => {
|
|
16
|
+
sub?.unsubscribe();
|
|
17
|
+
};
|
|
18
|
+
});
|
|
7
19
|
state$ = new Computed(() => {
|
|
8
20
|
const operations = this._operations$.value;
|
|
9
21
|
const currState = operations.current$?.value$.value;
|
|
@@ -51,4 +63,9 @@ export class OperationAgent {
|
|
|
51
63
|
createAgent() {
|
|
52
64
|
return new OperationAgent(this._operation);
|
|
53
65
|
}
|
|
66
|
+
complete() {
|
|
67
|
+
this._effect.complete();
|
|
68
|
+
this._operations$.complete();
|
|
69
|
+
this.state$.complete();
|
|
70
|
+
}
|
|
54
71
|
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { ReactiveCache } from "../../query/lib/ReactiveCache";
|
|
2
|
+
import { shallowEqual } from "../../common/utils";
|
|
2
3
|
export declare class QueriesCache<KEY, VALUE> {
|
|
3
4
|
private _cacheLifeTime;
|
|
4
5
|
private readonly _cache;
|
|
5
|
-
constructor(_cacheLifeTime?: number | false);
|
|
6
|
+
constructor(_cacheLifeTime?: number | false, compareArgsFn?: typeof shallowEqual);
|
|
6
7
|
getQueryCache(args: KEY): ReactiveCache<VALUE> | undefined;
|
|
7
8
|
createQueryCache(args: KEY, initialState: VALUE): ReactiveCache<VALUE>;
|
|
9
|
+
clear(): void;
|
|
8
10
|
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { IndirectMap } from "../../query/lib/IndirectMap";
|
|
2
2
|
import { ReactiveCache } from "../../query/lib/ReactiveCache";
|
|
3
|
+
import { shallowEqual } from "../../common/utils";
|
|
3
4
|
export class QueriesCache {
|
|
4
5
|
_cacheLifeTime;
|
|
5
|
-
_cache
|
|
6
|
-
constructor(_cacheLifeTime = 60_000) {
|
|
6
|
+
_cache;
|
|
7
|
+
constructor(_cacheLifeTime = 60_000, compareArgsFn = shallowEqual) {
|
|
7
8
|
this._cacheLifeTime = _cacheLifeTime;
|
|
9
|
+
this._cache = new IndirectMap(compareArgsFn);
|
|
8
10
|
}
|
|
9
11
|
getQueryCache(args) {
|
|
10
12
|
return this._cache.get(args);
|
|
@@ -14,25 +16,17 @@ export class QueriesCache {
|
|
|
14
16
|
initialState,
|
|
15
17
|
cacheLifeTime: this._cacheLifeTime,
|
|
16
18
|
});
|
|
17
|
-
// const stateDevtools = SharedOptions.DEVTOOLS?.state;
|
|
18
|
-
//
|
|
19
|
-
// if (stateDevtools) {
|
|
20
|
-
// const key = `${this._logname}:${JSON.stringify(args)}:i=${Indexer.getIndex()}`;
|
|
21
|
-
// let devtools = stateDevtools(key, initialState);
|
|
22
|
-
//
|
|
23
|
-
// cache.spy$.subscribe((state) => {
|
|
24
|
-
// if (state === initialState) return;
|
|
25
|
-
// devtools(state);
|
|
26
|
-
// });
|
|
27
|
-
//
|
|
28
|
-
// cache.onClean$.subscribe(() => {
|
|
29
|
-
// devtools('$CLEANED' as any);
|
|
30
|
-
// });
|
|
31
|
-
// }
|
|
32
19
|
cache.onClean$.subscribe(() => {
|
|
33
20
|
this._cache.delete(args);
|
|
34
21
|
});
|
|
35
22
|
this._cache.set(args, cache);
|
|
36
23
|
return cache;
|
|
37
24
|
}
|
|
25
|
+
clear() {
|
|
26
|
+
// Делаем именно так, тк при очистке могут синхронно добавляться новые кеши
|
|
27
|
+
const values = Array.from(this._cache.values);
|
|
28
|
+
values.forEach(c => {
|
|
29
|
+
c.complete();
|
|
30
|
+
});
|
|
31
|
+
}
|
|
38
32
|
}
|
|
@@ -4,7 +4,7 @@ import { ResourceAgent } from "./ResourceAgent";
|
|
|
4
4
|
export type CoreResourceQueryState<D extends ResourceDefinition> = {
|
|
5
5
|
transactions: ResourceTransaction[] | null;
|
|
6
6
|
abortController: AbortController | null;
|
|
7
|
-
args: D['Args']
|
|
7
|
+
args: D['Args'];
|
|
8
8
|
savedData: D['Data'] | null;
|
|
9
9
|
data: D['Data'] | null;
|
|
10
10
|
error: unknown | null;
|
|
@@ -47,4 +47,5 @@ export declare class Resource<D extends ResourceDefinition> implements ResourceI
|
|
|
47
47
|
initiate(args: D['Args'], options?: {
|
|
48
48
|
cache?: CoreResourceQueryCache<D>;
|
|
49
49
|
}): CoreResourceQueryCache<D>;
|
|
50
|
+
compareArgs(args1: D['Args'], args2: D['Args']): boolean;
|
|
50
51
|
}
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
+
import { SharedOptions } from "../../../common/options/SharedOptions";
|
|
1
2
|
import { QueriesCache } from "../QueriesCache";
|
|
2
3
|
import { QueriesLifetimeHooks } from "../QueriesLifetimeHooks";
|
|
4
|
+
import { CleanAllQueriesSignal } from "../CleanAllQueriesSignal";
|
|
3
5
|
import { ResourceAgent } from "./ResourceAgent";
|
|
4
6
|
import { ResourceRef } from "./ResourceRef";
|
|
5
7
|
class ResourceQueryState {
|
|
6
|
-
static create() {
|
|
8
|
+
static create(args) {
|
|
7
9
|
return {
|
|
8
10
|
transactions: null,
|
|
9
11
|
savedData: null,
|
|
10
12
|
abortController: null,
|
|
11
|
-
args
|
|
13
|
+
args,
|
|
12
14
|
data: null,
|
|
13
15
|
error: null,
|
|
14
16
|
isError: false,
|
|
@@ -21,7 +23,8 @@ class ResourceQueryState {
|
|
|
21
23
|
lockCount: 0
|
|
22
24
|
};
|
|
23
25
|
}
|
|
24
|
-
static load(state
|
|
26
|
+
static load(state, args) {
|
|
27
|
+
state = state ?? ResourceQueryState.create(args);
|
|
25
28
|
return {
|
|
26
29
|
...state,
|
|
27
30
|
abortController: new AbortController(),
|
|
@@ -114,6 +117,9 @@ export class Resource {
|
|
|
114
117
|
devtoolsName: _options.devtoolsName,
|
|
115
118
|
});
|
|
116
119
|
this._queriesCache = new QueriesCache(_options.cacheLifetime ?? this._DEFAULT_CACHE_LIFETIME);
|
|
120
|
+
CleanAllQueriesSignal.clean$.subscribe(() => {
|
|
121
|
+
this._queriesCache.clear();
|
|
122
|
+
});
|
|
117
123
|
}
|
|
118
124
|
createAgent = () => {
|
|
119
125
|
return new ResourceAgent(this);
|
|
@@ -124,7 +130,7 @@ export class Resource {
|
|
|
124
130
|
getQueryCache(args) {
|
|
125
131
|
return this._queriesCache.getQueryCache(args);
|
|
126
132
|
}
|
|
127
|
-
createQueryCache(args, state = ResourceQueryState.create()) {
|
|
133
|
+
createQueryCache(args, state = ResourceQueryState.create(args)) {
|
|
128
134
|
const cache = this._queriesCache.createQueryCache(args, state);
|
|
129
135
|
const hookResolvers = this._hooks.onCacheEntryAdded(args);
|
|
130
136
|
const spySub = cache.spy$.subscribe((state) => {
|
|
@@ -215,4 +221,8 @@ export class Resource {
|
|
|
215
221
|
});
|
|
216
222
|
return cache;
|
|
217
223
|
}
|
|
224
|
+
compareArgs(args1, args2) {
|
|
225
|
+
const compareFn = this._options.compareArgsFn ?? SharedOptions.defaultCompareArgs;
|
|
226
|
+
return compareFn(args1, args2);
|
|
227
|
+
}
|
|
218
228
|
}
|
|
@@ -4,6 +4,7 @@ import type { Resource } from "./Resource";
|
|
|
4
4
|
export declare class ResourceAgent<D extends ResourceDefinition> implements ResourceAgentInstance<D> {
|
|
5
5
|
private _resource;
|
|
6
6
|
private _resources$;
|
|
7
|
+
private _effect;
|
|
7
8
|
state$: Computed<{
|
|
8
9
|
isInitiated: boolean;
|
|
9
10
|
isLoading: boolean;
|
|
@@ -30,7 +31,8 @@ export declare class ResourceAgent<D extends ResourceDefinition> implements Reso
|
|
|
30
31
|
args: NonNullable<D["Args"]> | undefined;
|
|
31
32
|
}>;
|
|
32
33
|
constructor(_resource: Resource<D>);
|
|
33
|
-
private _next;
|
|
34
34
|
initiate(args: D["Args"], force?: boolean): void;
|
|
35
|
+
compareArgs(args: D["Args"], otherArgs: D["Args"]): boolean;
|
|
35
36
|
complete(): void;
|
|
37
|
+
private _next;
|
|
36
38
|
}
|
|
@@ -1,10 +1,25 @@
|
|
|
1
|
-
import { Computed, Signal } from "../../../signals";
|
|
1
|
+
import { Computed, Effect, Signal } from "../../../signals";
|
|
2
2
|
export class ResourceAgent {
|
|
3
3
|
_resource;
|
|
4
4
|
_resources$ = new Signal({
|
|
5
5
|
previous$: null,
|
|
6
6
|
current$: null,
|
|
7
7
|
}, { isDisabled: true });
|
|
8
|
+
_effect = new Effect(() => {
|
|
9
|
+
const current$ = this._resources$.value.current$;
|
|
10
|
+
const args = current$?.value.args;
|
|
11
|
+
// Если ресурс который мы слушаем очистился, то инициируем его заново с теми же аргументами
|
|
12
|
+
const sub = current$?.onClean$.subscribe(() => {
|
|
13
|
+
this._resources$.next({
|
|
14
|
+
previous$: null,
|
|
15
|
+
current$: null,
|
|
16
|
+
});
|
|
17
|
+
this.initiate(args);
|
|
18
|
+
});
|
|
19
|
+
return () => {
|
|
20
|
+
sub?.unsubscribe();
|
|
21
|
+
};
|
|
22
|
+
});
|
|
8
23
|
state$ = new Computed(() => {
|
|
9
24
|
const resources = this._resources$.value;
|
|
10
25
|
let prevState;
|
|
@@ -47,6 +62,29 @@ export class ResourceAgent {
|
|
|
47
62
|
constructor(_resource) {
|
|
48
63
|
this._resource = _resource;
|
|
49
64
|
}
|
|
65
|
+
initiate(args, force = false) {
|
|
66
|
+
const current = this._resources$.value.current$;
|
|
67
|
+
const cache = this._resource.getQueryCache(args);
|
|
68
|
+
if (!cache) {
|
|
69
|
+
const newCache = this._resource.initiate(args);
|
|
70
|
+
this._next(newCache);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (force || !(cache.value.isDone || cache.value.isLoading)) {
|
|
74
|
+
this._resource.initiate(args, { cache });
|
|
75
|
+
}
|
|
76
|
+
if (current !== cache) {
|
|
77
|
+
this._next(cache);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
compareArgs(args, otherArgs) {
|
|
81
|
+
return this._resource.compareArgs(args, otherArgs);
|
|
82
|
+
}
|
|
83
|
+
complete() {
|
|
84
|
+
this._effect.complete();
|
|
85
|
+
this.state$.complete();
|
|
86
|
+
this._resources$.complete();
|
|
87
|
+
}
|
|
50
88
|
_next(newCache) {
|
|
51
89
|
const { previous$, current$ } = this._resources$.value;
|
|
52
90
|
if (!current$) {
|
|
@@ -68,23 +106,4 @@ export class ResourceAgent {
|
|
|
68
106
|
current$: newCache,
|
|
69
107
|
});
|
|
70
108
|
}
|
|
71
|
-
initiate(args, force = false) {
|
|
72
|
-
const current = this._resources$.value.current$;
|
|
73
|
-
const cache = this._resource.getQueryCache(args);
|
|
74
|
-
if (!cache) {
|
|
75
|
-
const newCache = this._resource.initiate(args);
|
|
76
|
-
this._next(newCache);
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
if (force || !(cache.value.isDone || cache.value.isLoading)) {
|
|
80
|
-
this._resource.initiate(args, { cache });
|
|
81
|
-
}
|
|
82
|
-
if (current !== cache) {
|
|
83
|
-
this._next(cache);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
complete() {
|
|
87
|
-
this.state$.complete();
|
|
88
|
-
this._resources$.complete();
|
|
89
|
-
}
|
|
90
109
|
}
|
package/dist/query/index.d.ts
CHANGED
package/dist/query/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { shallowEqual } from "
|
|
1
|
+
import { shallowEqual } from "../../common/utils";
|
|
2
2
|
export class IndirectMap {
|
|
3
3
|
_compareObjectsFn;
|
|
4
4
|
_compareCache = new WeakMap();
|
|
@@ -82,4 +82,7 @@ export class IndirectMap {
|
|
|
82
82
|
}
|
|
83
83
|
return has;
|
|
84
84
|
}
|
|
85
|
+
get values() {
|
|
86
|
+
return this._map.values();
|
|
87
|
+
}
|
|
85
88
|
}
|
|
@@ -24,6 +24,7 @@ export class ReactiveCache {
|
|
|
24
24
|
* Subject, уведомляющий об очистке кэша.
|
|
25
25
|
*/
|
|
26
26
|
onClean$ = new Subject();
|
|
27
|
+
closed = false;
|
|
27
28
|
/**
|
|
28
29
|
* Создает новый экземпляр `ReactiveCacheItem`.
|
|
29
30
|
*
|
|
@@ -69,6 +70,9 @@ export class ReactiveCache {
|
|
|
69
70
|
* Завершает работу кэша, закрывая все потоки и уведомляя об очистке.
|
|
70
71
|
*/
|
|
71
72
|
complete() {
|
|
73
|
+
if (this.closed)
|
|
74
|
+
return;
|
|
75
|
+
this.closed = true;
|
|
72
76
|
this._state$.complete();
|
|
73
77
|
this.onClean$.next(this._state$.value);
|
|
74
78
|
this.onClean$.complete();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useConstant, useEventHandler } from "../../common/react";
|
|
1
|
+
import { useConstant, useEventHandler, useUnmount } from "../../common/react";
|
|
2
2
|
import { useSignal } from "../../signals/react";
|
|
3
3
|
export function useOperationAgent(op) {
|
|
4
4
|
const agent = useConstant(() => op.createAgent());
|
|
@@ -19,5 +19,8 @@ export function useOperationAgent(op) {
|
|
|
19
19
|
});
|
|
20
20
|
});
|
|
21
21
|
});
|
|
22
|
+
useUnmount(() => {
|
|
23
|
+
agent.complete();
|
|
24
|
+
}, [agent]);
|
|
22
25
|
return [trigger, state];
|
|
23
26
|
}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { useConstant } from "../../common/react";
|
|
2
|
+
import { useConstant, useUnmount } from "../../common/react";
|
|
3
3
|
import { useSignal } from "../../signals/react";
|
|
4
|
-
import { shallowEqual } from "../../query/lib/shallowEqual";
|
|
5
4
|
import { SKIP } from "../../query/SKIP_TOKEN";
|
|
6
5
|
export function useResourceAgent(res, ...argss) {
|
|
7
6
|
const args = (argss[0] === SKIP ? SKIP : argss[0]);
|
|
8
|
-
const prevArgsRef = React.useRef(
|
|
7
|
+
const prevArgsRef = React.useRef(SKIP);
|
|
9
8
|
const agent = useConstant(() => {
|
|
10
9
|
const agent = res.createAgent();
|
|
11
10
|
if (args !== SKIP) {
|
|
@@ -13,12 +12,12 @@ export function useResourceAgent(res, ...argss) {
|
|
|
13
12
|
}
|
|
14
13
|
return agent;
|
|
15
14
|
});
|
|
16
|
-
if (args !== SKIP && !
|
|
15
|
+
if (args !== SKIP && !agent.compareArgs(args, prevArgsRef.current)) {
|
|
17
16
|
prevArgsRef.current = args;
|
|
18
17
|
agent.initiate(args);
|
|
19
18
|
}
|
|
20
|
-
|
|
19
|
+
useUnmount(() => {
|
|
21
20
|
agent.complete();
|
|
22
|
-
}
|
|
21
|
+
});
|
|
23
22
|
return useSignal(agent.state$);
|
|
24
23
|
}
|
|
@@ -132,6 +132,8 @@ export type OperationAgentInstanse<D extends OperationDefinition> = {
|
|
|
132
132
|
initiate(args: D["Args"]): void;
|
|
133
133
|
/** Создает новый агент операции */
|
|
134
134
|
createAgent(): OperationAgentInstanse<D>;
|
|
135
|
+
/** Завершает все текущие выполнения операции и очищает ресурсы */
|
|
136
|
+
complete(): void;
|
|
135
137
|
};
|
|
136
138
|
/**
|
|
137
139
|
* Состояние выполнения операции
|
|
@@ -35,6 +35,10 @@ export type ResourceCreateOptions<D extends ResourceDefinition> = {
|
|
|
35
35
|
* Настройка отображения в devtools
|
|
36
36
|
*/
|
|
37
37
|
devtoolsName?: string | false;
|
|
38
|
+
/**
|
|
39
|
+
* Сравнение аргументов между собой
|
|
40
|
+
*/
|
|
41
|
+
compareArgsFn?: (args1: D["Args"], args2: D["Args"]) => boolean;
|
|
38
42
|
};
|
|
39
43
|
/**
|
|
40
44
|
* Определение типов ресурса
|
|
@@ -69,6 +73,8 @@ export type ResourceAgentInstance<D extends ResourceDefinition> = {
|
|
|
69
73
|
initiate(args: D["Args"], force?: boolean): void;
|
|
70
74
|
/** Завершает работу агента, позволяя освободить ресурсы */
|
|
71
75
|
complete(): void;
|
|
76
|
+
/** Сравнивает аргументы между собой */
|
|
77
|
+
compareArgs(args1: D["Args"], args2: D["Args"]): unknown;
|
|
72
78
|
};
|
|
73
79
|
/**
|
|
74
80
|
* Состояние запроса ресурса
|
|
@@ -2,10 +2,11 @@ import { SubscriptionLike } from "rxjs";
|
|
|
2
2
|
export declare class Effect implements SubscriptionLike {
|
|
3
3
|
private _onComplete?;
|
|
4
4
|
private _subscriptions;
|
|
5
|
+
private _teardown?;
|
|
5
6
|
protected readonly _scopeDestroyedSub: import("rxjs").Subscription | undefined;
|
|
6
7
|
closed: boolean;
|
|
7
8
|
_rang: number;
|
|
8
|
-
constructor(effectFn: (ctx: (fn: () => void) => void) => void, _onComplete?: (() => void) | undefined);
|
|
9
|
+
constructor(effectFn: (ctx: (fn: () => void) => void) => void | (() => void), _onComplete?: (() => void) | undefined);
|
|
9
10
|
/**
|
|
10
11
|
* Выполняет функцию в tracked-контексте, подписываясь на Tracker.
|
|
11
12
|
*/
|
|
@@ -3,6 +3,7 @@ import { SharedOptions } from "../../common/options/SharedOptions";
|
|
|
3
3
|
export class Effect {
|
|
4
4
|
_onComplete;
|
|
5
5
|
_subscriptions = [];
|
|
6
|
+
_teardown;
|
|
6
7
|
_scopeDestroyedSub = SharedOptions.getScopeDestroyed$?.()?.subscribe(() => {
|
|
7
8
|
this.complete();
|
|
8
9
|
});
|
|
@@ -18,6 +19,9 @@ export class Effect {
|
|
|
18
19
|
_runInTrackedContext(effectFn, isAsyncRun = false) {
|
|
19
20
|
let prevSubscriptions;
|
|
20
21
|
if (!isAsyncRun) {
|
|
22
|
+
// Вызываем teardown перед перезапуском эффекта
|
|
23
|
+
this._teardown?.();
|
|
24
|
+
this._teardown = undefined;
|
|
21
25
|
this._rang = 0;
|
|
22
26
|
prevSubscriptions = this._subscriptions;
|
|
23
27
|
this._subscriptions = [];
|
|
@@ -51,9 +55,13 @@ export class Effect {
|
|
|
51
55
|
this.complete();
|
|
52
56
|
},
|
|
53
57
|
});
|
|
54
|
-
effectFn((fn) => {
|
|
58
|
+
const teardown = effectFn((fn) => {
|
|
55
59
|
this._runInTrackedContext(fn, true);
|
|
56
60
|
});
|
|
61
|
+
// Сохраняем teardown функцию, если она была возвращена
|
|
62
|
+
if (typeof teardown === 'function') {
|
|
63
|
+
this._teardown = teardown;
|
|
64
|
+
}
|
|
57
65
|
trackerSub.unsubscribe();
|
|
58
66
|
isTrackedContext = false;
|
|
59
67
|
scheduler = Batcher.scheduler(this._rang);
|
|
@@ -63,6 +71,9 @@ export class Effect {
|
|
|
63
71
|
if (this.closed)
|
|
64
72
|
return;
|
|
65
73
|
this.closed = true;
|
|
74
|
+
// Вызываем teardown перед завершением эффекта
|
|
75
|
+
this._teardown?.();
|
|
76
|
+
this._teardown = undefined;
|
|
66
77
|
this._subscriptions.forEach((sub) => sub.unsubscribe());
|
|
67
78
|
this._scopeDestroyedSub?.unsubscribe();
|
|
68
79
|
this._onComplete?.();
|
package/package.json
CHANGED
|
File without changes
|
|
File without changes
|