@fozy-labs/rx-toolkit 0.4.18 → 0.5.0-rc.0
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/README.md +20 -19
- package/dist/common/devtools/reduxDevtools.d.ts +17 -0
- package/dist/common/devtools/reduxDevtools.js +98 -17
- package/dist/common/devtools/types.d.ts +1 -0
- package/dist/common/options/DefaultOptions.d.ts +0 -2
- package/dist/common/options/DefaultOptions.js +0 -2
- package/dist/common/options/SharedOptions.d.ts +0 -2
- package/dist/common/options/SharedOptions.js +0 -1
- package/dist/query/api/resetAllQueriesCache.d.ts +1 -0
- package/dist/query/api/resetAllQueriesCache.js +4 -0
- package/dist/query/core/Opertation/Operation.js +10 -7
- package/dist/query/core/Opertation/OperationAgent.d.ts +0 -2
- package/dist/query/core/Opertation/OperationAgent.js +4 -21
- package/dist/query/core/QueriesCache.d.ts +1 -1
- package/dist/query/core/QueriesCache.js +2 -6
- package/dist/query/core/{CleanAllQueriesSignal.d.ts → ResetAllQueriesSignal.d.ts} +1 -1
- package/dist/query/core/ResetAllQueriesSignal.js +11 -0
- package/dist/query/core/Resource/Resource.js +7 -3
- package/dist/query/core/Resource/ResourceAgent.d.ts +2 -3
- package/dist/query/core/Resource/ResourceAgent.js +62 -29
- package/dist/query/index.d.ts +1 -1
- package/dist/query/index.js +1 -1
- package/dist/query/lib/IndirectMap.d.ts +1 -1
- package/dist/query/lib/IndirectMap.js +1 -1
- package/dist/query/lib/ReactiveCache.d.ts +1 -1
- package/dist/query/lib/ReactiveCache.js +2 -2
- package/dist/query/react/useOperationAgent.js +2 -5
- package/dist/query/react/useResourceAgent.js +1 -4
- package/dist/query/types/Operation.types.d.ts +1 -3
- package/dist/query/types/Resource.types.d.ts +1 -3
- package/dist/signals/base/Batcher.d.ts +1 -1
- package/dist/signals/base/Batcher.js +1 -1
- package/dist/signals/base/DependencyTracker.d.ts +18 -0
- package/dist/signals/base/DependencyTracker.js +13 -0
- package/dist/signals/base/Devtools.d.ts +1 -1
- package/dist/signals/base/Devtools.js +13 -1
- package/dist/signals/base/ReadonlySignal.d.ts +5 -7
- package/dist/signals/base/ReadonlySignal.js +20 -12
- package/dist/signals/base/index.d.ts +1 -2
- package/dist/signals/base/index.js +1 -2
- package/dist/signals/operators/index.d.ts +0 -2
- package/dist/signals/operators/index.js +0 -2
- package/dist/signals/operators/signalize.d.ts +2 -2
- package/dist/signals/operators/signalize.js +1 -1
- package/dist/signals/react/useSignal.d.ts +6 -2
- package/dist/signals/react/useSignal.js +2 -21
- package/dist/signals/signals/Computed.d.ts +13 -11
- package/dist/signals/signals/Computed.js +79 -26
- package/dist/signals/signals/Effect.d.ts +11 -7
- package/dist/signals/signals/Effect.js +60 -58
- package/dist/signals/signals/LocalSignal.d.ts +14 -7
- package/dist/signals/signals/LocalSignal.js +52 -33
- package/dist/signals/signals/Signal.d.ts +13 -37
- package/dist/signals/signals/Signal.js +44 -58
- package/dist/signals/types/index.d.ts +1 -0
- package/dist/signals/types/index.js +1 -0
- package/dist/signals/types/signals.types.d.ts +16 -0
- package/docs/CHANGELOG.md +32 -0
- package/docs/devtools/README.md +162 -29
- package/docs/migrations/0.5.0.md +73 -0
- package/docs/options/README.md +89 -0
- package/docs/query/README.md +425 -89
- package/docs/release/README.md +58 -6
- package/docs/signals/README.md +207 -34
- package/docs/usage/react/README.md +261 -49
- package/package.json +1 -1
- package/dist/query/api/cleanAllQueriesCache.d.ts +0 -1
- package/dist/query/api/cleanAllQueriesCache.js +0 -4
- package/dist/query/core/CleanAllQueriesSignal.js +0 -11
- package/dist/signals/base/Tracker.d.ts +0 -10
- package/dist/signals/base/Tracker.js +0 -7
- package/dist/signals/base/types.d.ts +0 -23
- package/dist/signals/operators/filterUpdates.d.ts +0 -5
- package/dist/signals/operators/filterUpdates.js +0 -18
- package/dist/signals/operators/mapSignals.d.ts +0 -3
- package/dist/signals/operators/mapSignals.js +0 -10
- /package/dist/signals/{base/types.js → types/signals.types.js} +0 -0
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ npm install @fozy-labs/rx-toolkit rxjs
|
|
|
13
13
|
|
|
14
14
|
## 🎯 Цель
|
|
15
15
|
|
|
16
|
-
RxJS действительно мощный инструмент реактивного программирования,
|
|
16
|
+
RxJS действительно мощный инструмент реактивного программирования,
|
|
17
17
|
он удобен когда мы работаем с потоком событий, но когда речь заходит о состоянии приложения,
|
|
18
18
|
из-за асинхронной природы rx'а, его использование становится сложным и громоздким, не говоря уже о кешировании данных
|
|
19
19
|
(хотя некоторые разработчики "продают" rxjs, как альтернативу Query библиотекам,
|
|
@@ -29,42 +29,42 @@ RxToolkit решает эти проблемы, предоставляя сво
|
|
|
29
29
|
- 💾 **Кеш-менеджер** — Предоставляет Query реализацию для работы с данными.
|
|
30
30
|
- 🔷 **TypeScript-first** — Полная типизация.
|
|
31
31
|
- 🔗 **Интеграция с фреймворками** — Как и RxJS напрямую работает в Angular, Svelte и SolidJS.
|
|
32
|
-
|
|
32
|
+
Поставляется с React-хуками из коробки.
|
|
33
33
|
|
|
34
34
|
## 📚 Документация
|
|
35
35
|
- [**RxSignals**](./docs/signals/README.md) - реактивные примитивы
|
|
36
36
|
- [**RxQuery**](./docs/query/README.md) - кеш-менеджер для работы с данными
|
|
37
37
|
- [**React**](./docs/usage/react/README.md) - интеграция с React
|
|
38
38
|
- [**Devtools**](./docs/devtools/README.md) - инструменты разработчика
|
|
39
|
+
- [**Changelog**](./docs/CHANGELOG.md) - история изменений
|
|
40
|
+
- [**DefaultOptions**](./docs/options/README.md) - глобальные настройки
|
|
39
41
|
|
|
40
42
|
## 🌟 Примеры
|
|
41
43
|
|
|
42
44
|
###### Создаем сигнал
|
|
43
45
|
```typescript
|
|
44
46
|
// Описываем логику в обычном JavaScript
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
increment: () => store.count$.value++,
|
|
49
|
-
};
|
|
47
|
+
const count$ = Signal.create(0);
|
|
48
|
+
const doubled$ = Signal.compute(() => count$() * 2);
|
|
49
|
+
const increment = () => count$.set(count$.peek() + 1);
|
|
50
50
|
```
|
|
51
51
|
|
|
52
52
|
###### Подключаем к фреймворку
|
|
53
53
|
```typescript
|
|
54
54
|
// React
|
|
55
|
-
const count = useSignal(
|
|
55
|
+
const count = useSignal(count$);
|
|
56
56
|
|
|
57
57
|
// Angular signal
|
|
58
|
-
count
|
|
58
|
+
public readonly count = toSignal(count$.obs);
|
|
59
59
|
|
|
60
60
|
// Angular pipe
|
|
61
|
-
{{
|
|
61
|
+
{{ count$.obs | async }}
|
|
62
62
|
|
|
63
63
|
// SolidJS
|
|
64
|
-
const count
|
|
64
|
+
const count = from(count$.obs)
|
|
65
65
|
|
|
66
66
|
// Svelte
|
|
67
|
-
$: count =
|
|
67
|
+
$: count = count$.obs;
|
|
68
68
|
```
|
|
69
69
|
|
|
70
70
|
###### Работаем с RxJS
|
|
@@ -79,20 +79,21 @@ const clicker$ = fromEvent(document, 'click').pipe(
|
|
|
79
79
|
|
|
80
80
|
// Получаем сигнал из Observable
|
|
81
81
|
const clickCount$ = signalize(clicker$);
|
|
82
|
-
const doubled$ =
|
|
82
|
+
const doubled$ = Signal.compute(() => clickCount$() * 2);
|
|
83
83
|
|
|
84
|
-
console.log(doubled$.
|
|
84
|
+
console.log(doubled$.peek()); // Всегда актуальное значение
|
|
85
85
|
|
|
86
86
|
// Или наоборот, получаем событие из сигнала
|
|
87
|
-
const on10click$ = doubled$.pipe(
|
|
87
|
+
const on10click$ = doubled$.obs.pipe(
|
|
88
88
|
filter(value => value === 10),
|
|
89
89
|
take(1)
|
|
90
90
|
);
|
|
91
91
|
|
|
92
|
-
on10click$.subscribe(() => {
|
|
92
|
+
const sub = on10click$.subscribe(() => {
|
|
93
93
|
console.log('Great! That you first reached 10 clicks!');
|
|
94
94
|
});
|
|
95
|
-
|
|
95
|
+
// Не забываем отписаться
|
|
96
|
+
sub.unsubscribe();
|
|
96
97
|
```
|
|
97
98
|
|
|
98
99
|
###### RxQuery (Корзина покупок)
|
|
@@ -107,8 +108,8 @@ const toggleCardItem = createOperation({
|
|
|
107
108
|
add({
|
|
108
109
|
resource: getCart,
|
|
109
110
|
forwardArgs: () => undefined,
|
|
110
|
-
optimisticUpdate: ({ draft,
|
|
111
|
-
const item = draft.items.find(i => i.id ===
|
|
111
|
+
optimisticUpdate: ({ draft, args }) => {
|
|
112
|
+
const item = draft.items.find(i => i.id === args.id);
|
|
112
113
|
if (!item) return;
|
|
113
114
|
item.enabled = args.enabled;
|
|
114
115
|
}
|
|
@@ -8,9 +8,26 @@ interface ReduxDevtoolsConnection {
|
|
|
8
8
|
init(state: any): void;
|
|
9
9
|
send(action: any, state: any): void;
|
|
10
10
|
}
|
|
11
|
+
/**
|
|
12
|
+
* Стратегия батчинга обновлений:
|
|
13
|
+
* - 'sync' - синхронное выполнение без батчинга (каждое обновление отправляется немедленно)
|
|
14
|
+
* - 'microtask' - пакование в микротаске (queueMicrotask), все обновления в текущем синхронном потоке объединяются
|
|
15
|
+
* - 'task' - пакование в макротаске (setTimeout), с настраиваемой задержкой
|
|
16
|
+
*/
|
|
17
|
+
export type BatchStrategy = 'sync' | 'microtask' | 'task';
|
|
11
18
|
type Options = {
|
|
12
19
|
name?: string;
|
|
13
20
|
driver?: ReduxDevtoolsExtension;
|
|
21
|
+
/**
|
|
22
|
+
* Стратегия батчинга обновлений
|
|
23
|
+
* @default 'microtask'
|
|
24
|
+
*/
|
|
25
|
+
batchStrategy?: BatchStrategy;
|
|
26
|
+
/**
|
|
27
|
+
* Задержка для стратегии 'task' (в миллисекундах)
|
|
28
|
+
* @default 0
|
|
29
|
+
*/
|
|
30
|
+
taskDelay?: number;
|
|
14
31
|
};
|
|
15
32
|
export declare function reduxDevtools(options?: Options): DevtoolsLike;
|
|
16
33
|
export {};
|
|
@@ -1,38 +1,119 @@
|
|
|
1
1
|
import { Batcher } from "../../signals";
|
|
2
|
+
/**
|
|
3
|
+
* Создает планировщик обновлений с указанной стратегией батчинга.
|
|
4
|
+
*
|
|
5
|
+
* Планировщик гарантирует:
|
|
6
|
+
* - Объединение множественных обновлений в один вызов flush
|
|
7
|
+
* - Порядок: сначала все pending обновления, затем flush
|
|
8
|
+
* - Отмену запланированного flush при новых обновлениях (для task стратегии)
|
|
9
|
+
*/
|
|
10
|
+
function createBatchScheduler(strategy, taskDelay) {
|
|
11
|
+
// Для sync режима используем Batcher.scheduler(Infinity),
|
|
12
|
+
// чтобы обновления devtools происходили в конце батча сигналов
|
|
13
|
+
const batcherScheduler = Batcher.scheduler(Infinity);
|
|
14
|
+
let isPending = false;
|
|
15
|
+
let timeoutId = null;
|
|
16
|
+
let pendingFlush = null;
|
|
17
|
+
const executePending = () => {
|
|
18
|
+
isPending = false;
|
|
19
|
+
timeoutId = null;
|
|
20
|
+
if (pendingFlush) {
|
|
21
|
+
const fn = pendingFlush;
|
|
22
|
+
pendingFlush = null;
|
|
23
|
+
fn();
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
const scheduleExecution = () => {
|
|
27
|
+
if (isPending)
|
|
28
|
+
return; // Уже запланировано
|
|
29
|
+
isPending = true;
|
|
30
|
+
switch (strategy) {
|
|
31
|
+
case 'sync':
|
|
32
|
+
// Используем Batcher — выполнится в конце текущего батча сигналов
|
|
33
|
+
// или сразу, если батч не активен
|
|
34
|
+
batcherScheduler.schedule(executePending);
|
|
35
|
+
break;
|
|
36
|
+
case 'microtask':
|
|
37
|
+
queueMicrotask(executePending);
|
|
38
|
+
break;
|
|
39
|
+
case 'task':
|
|
40
|
+
timeoutId = setTimeout(executePending, taskDelay);
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
return {
|
|
45
|
+
/**
|
|
46
|
+
* Планирует выполнение flush функции.
|
|
47
|
+
* Множественные вызовы schedule до выполнения батча объединяются в один flush.
|
|
48
|
+
*/
|
|
49
|
+
schedule(flushFn) {
|
|
50
|
+
pendingFlush = flushFn;
|
|
51
|
+
scheduleExecution();
|
|
52
|
+
},
|
|
53
|
+
/**
|
|
54
|
+
* Отменяет запланированный flush (полезно при cleanup)
|
|
55
|
+
*/
|
|
56
|
+
cancel() {
|
|
57
|
+
if (timeoutId !== null) {
|
|
58
|
+
clearTimeout(timeoutId);
|
|
59
|
+
timeoutId = null;
|
|
60
|
+
}
|
|
61
|
+
isPending = false;
|
|
62
|
+
pendingFlush = null;
|
|
63
|
+
},
|
|
64
|
+
/**
|
|
65
|
+
* Принудительно выполняет pending flush синхронно
|
|
66
|
+
*/
|
|
67
|
+
flush() {
|
|
68
|
+
if (timeoutId !== null) {
|
|
69
|
+
clearTimeout(timeoutId);
|
|
70
|
+
timeoutId = null;
|
|
71
|
+
}
|
|
72
|
+
if (pendingFlush) {
|
|
73
|
+
isPending = false;
|
|
74
|
+
const fn = pendingFlush;
|
|
75
|
+
pendingFlush = null;
|
|
76
|
+
fn();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
}
|
|
2
81
|
export function reduxDevtools(options = {}) {
|
|
3
82
|
const devtools = options.driver ?? window.__REDUX_DEVTOOLS_EXTENSION__;
|
|
4
83
|
if (!devtools) {
|
|
5
84
|
throw new Error('Redux Devtools extension is not installed');
|
|
6
85
|
}
|
|
86
|
+
const batchStrategy = options.batchStrategy ?? 'microtask';
|
|
87
|
+
const taskDelay = options.taskDelay ?? 0;
|
|
7
88
|
let state = {};
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
const scheduler =
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const clearFn = () => {
|
|
17
|
-
reduxDevtools.send({ type: 'clear' }, state);
|
|
18
|
-
};
|
|
19
|
-
const createFn = () => {
|
|
20
|
-
isCreated = true;
|
|
21
|
-
return updateFn;
|
|
89
|
+
const connection = devtools.connect({ name: options.name ?? 'RxToolkit' });
|
|
90
|
+
connection.init(state);
|
|
91
|
+
const scheduler = createBatchScheduler(batchStrategy, taskDelay);
|
|
92
|
+
// Отслеживаем тип последнего действия для правильного action type в devtools
|
|
93
|
+
let pendingActionType = 'update';
|
|
94
|
+
const flushToDevtools = () => {
|
|
95
|
+
connection.send({ type: pendingActionType }, state);
|
|
96
|
+
pendingActionType = 'update'; // Сбрасываем на дефолт после отправки
|
|
22
97
|
};
|
|
23
98
|
return {
|
|
24
99
|
state(name, initState) {
|
|
25
100
|
const keys = name.split('/');
|
|
26
101
|
state = applyState(keys, initState, state);
|
|
27
|
-
|
|
102
|
+
pendingActionType = 'create';
|
|
103
|
+
scheduler.schedule(flushToDevtools);
|
|
28
104
|
return (newState) => {
|
|
29
105
|
if (newState === '$COMPLETED' || newState === '$CLEANED') {
|
|
30
106
|
state = deleteState(keys, state);
|
|
31
|
-
|
|
107
|
+
pendingActionType = 'clear';
|
|
108
|
+
scheduler.schedule(flushToDevtools);
|
|
32
109
|
return;
|
|
33
110
|
}
|
|
34
111
|
state = applyState(keys, newState, state);
|
|
35
|
-
|
|
112
|
+
// Не перезаписываем 'create' на 'update' если create еще не отправлен
|
|
113
|
+
if (pendingActionType !== 'create') {
|
|
114
|
+
pendingActionType = 'update';
|
|
115
|
+
}
|
|
116
|
+
scheduler.schedule(flushToDevtools);
|
|
36
117
|
};
|
|
37
118
|
}
|
|
38
119
|
};
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import type { DevtoolsLike } from "../../common/devtools";
|
|
2
|
-
import { Observable } from "rxjs";
|
|
3
2
|
type Update = Partial<{
|
|
4
3
|
DEVTOOLS: DevtoolsLike | null;
|
|
5
4
|
onQueryError: (error: unknown) => void;
|
|
6
5
|
getScopeName: () => string | null;
|
|
7
|
-
getScopeDestroyed$: () => Observable<void> | null;
|
|
8
6
|
}>;
|
|
9
7
|
export declare class DefaultOptions {
|
|
10
8
|
static update(part: Update): void;
|
|
@@ -7,7 +7,5 @@ export class DefaultOptions {
|
|
|
7
7
|
SharedOptions.onQueryError = part.onQueryError;
|
|
8
8
|
if (part.getScopeName !== undefined)
|
|
9
9
|
SharedOptions.getScopeName = part.getScopeName;
|
|
10
|
-
if (part.getScopeDestroyed$ !== undefined)
|
|
11
|
-
SharedOptions.getScopeDestroyed$ = part.getScopeDestroyed$;
|
|
12
10
|
}
|
|
13
11
|
}
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import { Observable } from "rxjs";
|
|
2
1
|
import { DevtoolsLike } from "../../common/devtools";
|
|
3
2
|
import { shallowEqual } from "../../common/utils";
|
|
4
3
|
export declare class SharedOptions {
|
|
5
4
|
static DEVTOOLS: DevtoolsLike | null;
|
|
6
5
|
static onQueryError: ((error: unknown) => void) | null;
|
|
7
6
|
static getScopeName: (() => string | null) | null;
|
|
8
|
-
static getScopeDestroyed$: (() => Observable<void> | null) | null;
|
|
9
7
|
static defaultCompareArgs: typeof shallowEqual;
|
|
10
8
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function resetAllQueriesCache(): void;
|
|
@@ -3,7 +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 {
|
|
6
|
+
import { ResetAllQueriesSignal } from "../../../query/core/ResetAllQueriesSignal";
|
|
7
7
|
class OperationQueryState {
|
|
8
8
|
static create() {
|
|
9
9
|
return {
|
|
@@ -66,8 +66,11 @@ export class Operation {
|
|
|
66
66
|
devtoolsName: _options.devtoolsName,
|
|
67
67
|
});
|
|
68
68
|
this._createLinks();
|
|
69
|
-
|
|
70
|
-
this._queriesCache.
|
|
69
|
+
ResetAllQueriesSignal.clean$.subscribe(() => {
|
|
70
|
+
const caches = Array.from(this._queriesCache.values());
|
|
71
|
+
caches.forEach((cache) => {
|
|
72
|
+
cache.next(OperationQueryState.create());
|
|
73
|
+
});
|
|
71
74
|
});
|
|
72
75
|
}
|
|
73
76
|
_createLinks() {
|
|
@@ -99,7 +102,7 @@ export class Operation {
|
|
|
99
102
|
return cache;
|
|
100
103
|
}
|
|
101
104
|
initiate(args, options) {
|
|
102
|
-
return Batcher.
|
|
105
|
+
return Batcher.run(() => this._initiate(args, options));
|
|
103
106
|
}
|
|
104
107
|
_initiate(args, options) {
|
|
105
108
|
let cache = options?.cache ?? this.getQueryCache(args);
|
|
@@ -129,7 +132,7 @@ export class Operation {
|
|
|
129
132
|
});
|
|
130
133
|
query
|
|
131
134
|
.then((result) => {
|
|
132
|
-
Batcher.
|
|
135
|
+
Batcher.run(() => {
|
|
133
136
|
const data = this._options.select ? this._options.select(result) : result;
|
|
134
137
|
cache.next(OperationQueryState.success(state, data));
|
|
135
138
|
/**
|
|
@@ -160,7 +163,7 @@ export class Operation {
|
|
|
160
163
|
});
|
|
161
164
|
})
|
|
162
165
|
.catch((error) => {
|
|
163
|
-
Batcher.
|
|
166
|
+
Batcher.run(() => {
|
|
164
167
|
cache.next(OperationQueryState.error(state, error));
|
|
165
168
|
/**
|
|
166
169
|
* Обновляем связанные ресурсы
|
|
@@ -186,7 +189,7 @@ export class Operation {
|
|
|
186
189
|
mutate(args) {
|
|
187
190
|
const cache = this.initiate(args);
|
|
188
191
|
const resolver = new PromiseResolver();
|
|
189
|
-
const subscription = cache.value$.subscribe((state) => {
|
|
192
|
+
const subscription = cache.value$.obs.subscribe((state) => {
|
|
190
193
|
if (!state.isInitiated || state.isLoading || state.isRepeating)
|
|
191
194
|
return;
|
|
192
195
|
if (state.isError) {
|
|
@@ -4,7 +4,6 @@ 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;
|
|
8
7
|
state$: Computed<{
|
|
9
8
|
isLoading: boolean;
|
|
10
9
|
isDone: boolean;
|
|
@@ -18,5 +17,4 @@ export declare class OperationAgent<D extends OperationDefinition> implements Op
|
|
|
18
17
|
private _next;
|
|
19
18
|
initiate(args: D["Args"]): void;
|
|
20
19
|
createAgent(): OperationAgentInstanse<D>;
|
|
21
|
-
complete(): void;
|
|
22
20
|
}
|
|
@@ -1,24 +1,12 @@
|
|
|
1
|
-
import { Computed,
|
|
1
|
+
import { Computed, 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
|
-
});
|
|
19
7
|
state$ = new Computed(() => {
|
|
20
|
-
const operations = this._operations$.
|
|
21
|
-
const currState = operations.current$?.value$.
|
|
8
|
+
const operations = this._operations$.get();
|
|
9
|
+
const currState = operations.current$?.value$.get();
|
|
22
10
|
// Нет текущего состояния — дефолт
|
|
23
11
|
if (!currState) {
|
|
24
12
|
return {
|
|
@@ -45,7 +33,7 @@ export class OperationAgent {
|
|
|
45
33
|
this._operation = _operation;
|
|
46
34
|
}
|
|
47
35
|
_next(newCache) {
|
|
48
|
-
this._operations$.
|
|
36
|
+
this._operations$.set({
|
|
49
37
|
current$: newCache,
|
|
50
38
|
});
|
|
51
39
|
}
|
|
@@ -63,9 +51,4 @@ export class OperationAgent {
|
|
|
63
51
|
createAgent() {
|
|
64
52
|
return new OperationAgent(this._operation);
|
|
65
53
|
}
|
|
66
|
-
complete() {
|
|
67
|
-
this._effect.complete();
|
|
68
|
-
this._operations$.complete();
|
|
69
|
-
this.state$.complete();
|
|
70
|
-
}
|
|
71
54
|
}
|
|
@@ -6,5 +6,5 @@ export declare class QueriesCache<KEY, VALUE> {
|
|
|
6
6
|
constructor(_cacheLifeTime?: number | false, compareArgsFn?: typeof shallowEqual);
|
|
7
7
|
getQueryCache(args: KEY): ReactiveCache<VALUE> | undefined;
|
|
8
8
|
createQueryCache(args: KEY, initialState: VALUE): ReactiveCache<VALUE>;
|
|
9
|
-
|
|
9
|
+
values(): MapIterator<ReactiveCache<VALUE>>;
|
|
10
10
|
}
|
|
@@ -22,11 +22,7 @@ export class QueriesCache {
|
|
|
22
22
|
this._cache.set(args, cache);
|
|
23
23
|
return cache;
|
|
24
24
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const values = Array.from(this._cache.values);
|
|
28
|
-
values.forEach(c => {
|
|
29
|
-
c.complete();
|
|
30
|
-
});
|
|
25
|
+
values() {
|
|
26
|
+
return this._cache.values();
|
|
31
27
|
}
|
|
32
28
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Subject } from "rxjs";
|
|
2
|
+
import { Batcher } from "../../signals";
|
|
3
|
+
export class ResetAllQueriesSignal {
|
|
4
|
+
static subject$ = new Subject();
|
|
5
|
+
static clean$ = ResetAllQueriesSignal.subject$;
|
|
6
|
+
static clean() {
|
|
7
|
+
Batcher.run(() => {
|
|
8
|
+
ResetAllQueriesSignal.subject$.next();
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { SharedOptions } from "../../../common/options/SharedOptions";
|
|
2
2
|
import { QueriesCache } from "../QueriesCache";
|
|
3
3
|
import { QueriesLifetimeHooks } from "../QueriesLifetimeHooks";
|
|
4
|
-
import {
|
|
4
|
+
import { ResetAllQueriesSignal } from "../ResetAllQueriesSignal";
|
|
5
5
|
import { ResourceAgent } from "./ResourceAgent";
|
|
6
6
|
import { ResourceRef } from "./ResourceRef";
|
|
7
7
|
class ResourceQueryState {
|
|
@@ -117,8 +117,12 @@ export class Resource {
|
|
|
117
117
|
devtoolsName: _options.devtoolsName,
|
|
118
118
|
});
|
|
119
119
|
this._queriesCache = new QueriesCache(_options.cacheLifetime ?? this._DEFAULT_CACHE_LIFETIME);
|
|
120
|
-
|
|
121
|
-
this._queriesCache.
|
|
120
|
+
ResetAllQueriesSignal.clean$.subscribe(() => {
|
|
121
|
+
const caches = Array.from(this._queriesCache.values());
|
|
122
|
+
caches.forEach((cache) => {
|
|
123
|
+
cache.value.abortController?.abort();
|
|
124
|
+
cache.next(ResourceQueryState.create(cache.value.args));
|
|
125
|
+
});
|
|
122
126
|
});
|
|
123
127
|
}
|
|
124
128
|
createAgent = () => {
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { Computed } from "../../../signals";
|
|
2
|
-
import { ResourceAgentInstance, ResourceDefinition } from "../../../query/types";
|
|
2
|
+
import { ResourceAgentInstance, ResourceDefinition, ResourceQueryState } from "../../../query/types";
|
|
3
3
|
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;
|
|
8
7
|
state$: Computed<{
|
|
9
8
|
isInitiated: boolean;
|
|
10
9
|
isLoading: boolean;
|
|
@@ -31,8 +30,8 @@ export declare class ResourceAgent<D extends ResourceDefinition> implements Reso
|
|
|
31
30
|
args: NonNullable<D["Args"]> | undefined;
|
|
32
31
|
}>;
|
|
33
32
|
constructor(_resource: Resource<D>);
|
|
33
|
+
getState(values: D["Args"]): ResourceQueryState<D>;
|
|
34
34
|
initiate(args: D["Args"], force?: boolean): void;
|
|
35
35
|
compareArgs(args: D["Args"], otherArgs: D["Args"]): boolean;
|
|
36
|
-
complete(): void;
|
|
37
36
|
private _next;
|
|
38
37
|
}
|
|
@@ -1,29 +1,35 @@
|
|
|
1
|
-
import { Computed,
|
|
1
|
+
import { Computed, 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
|
-
});
|
|
23
8
|
state$ = new Computed(() => {
|
|
24
|
-
const resources = this._resources$.
|
|
9
|
+
const resources = this._resources$.get();
|
|
25
10
|
let prevState;
|
|
26
|
-
const currState = resources.current$?.value$.
|
|
11
|
+
const currState = resources.current$?.value$.get();
|
|
12
|
+
// Отлавливаем кейс, когда ресурс был спрошен.
|
|
13
|
+
// На данные момент единсвенная причина сброса - resetAllQueriesCache(),
|
|
14
|
+
// но в будущем могут быть и другие причины, что потребует доработку.
|
|
15
|
+
if (currState && !currState.isInitiated) {
|
|
16
|
+
this._resource.initiate(currState.args, { cache: resources.current$ });
|
|
17
|
+
return {
|
|
18
|
+
isInitiated: true,
|
|
19
|
+
isLoading: true,
|
|
20
|
+
isInitialLoading: true,
|
|
21
|
+
isDone: false,
|
|
22
|
+
isSuccess: false,
|
|
23
|
+
isError: false,
|
|
24
|
+
isReloading: false,
|
|
25
|
+
error: undefined,
|
|
26
|
+
data: undefined,
|
|
27
|
+
// TODO вообще нет точного представлния, как блокировака доложна работать.
|
|
28
|
+
// Мб тут стоит брать currState.isLocked.
|
|
29
|
+
isLocked: false,
|
|
30
|
+
args: currState.args,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
27
33
|
if (!currState?.isDone) {
|
|
28
34
|
prevState = resources.previous$?.value;
|
|
29
35
|
}
|
|
@@ -62,8 +68,40 @@ export class ResourceAgent {
|
|
|
62
68
|
constructor(_resource) {
|
|
63
69
|
this._resource = _resource;
|
|
64
70
|
}
|
|
71
|
+
getState(values) {
|
|
72
|
+
const cache = this._resource.getQueryCache(values);
|
|
73
|
+
if (!cache) {
|
|
74
|
+
return {
|
|
75
|
+
isInitiated: false,
|
|
76
|
+
isLoading: false,
|
|
77
|
+
isInitialLoading: false,
|
|
78
|
+
isDone: false,
|
|
79
|
+
isSuccess: false,
|
|
80
|
+
isError: false,
|
|
81
|
+
isLocked: false,
|
|
82
|
+
isReloading: false,
|
|
83
|
+
error: undefined,
|
|
84
|
+
data: undefined,
|
|
85
|
+
args: undefined,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
const state = cache.value;
|
|
89
|
+
return {
|
|
90
|
+
isInitiated: state.isInitiated,
|
|
91
|
+
isLoading: state.isLoading,
|
|
92
|
+
isInitialLoading: state.isLoading && !state.isDone,
|
|
93
|
+
isDone: state.isDone,
|
|
94
|
+
isSuccess: state.isSuccess,
|
|
95
|
+
isError: state.isError,
|
|
96
|
+
isLocked: state.isLocked,
|
|
97
|
+
isReloading: state.isReloading,
|
|
98
|
+
error: state.error ?? undefined,
|
|
99
|
+
data: state.data ?? undefined,
|
|
100
|
+
args: state.args ?? undefined,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
65
103
|
initiate(args, force = false) {
|
|
66
|
-
const current = this._resources$.
|
|
104
|
+
const current = this._resources$.peek().current$;
|
|
67
105
|
const cache = this._resource.getQueryCache(args);
|
|
68
106
|
if (!cache) {
|
|
69
107
|
const newCache = this._resource.initiate(args);
|
|
@@ -80,28 +118,23 @@ export class ResourceAgent {
|
|
|
80
118
|
compareArgs(args, otherArgs) {
|
|
81
119
|
return this._resource.compareArgs(args, otherArgs);
|
|
82
120
|
}
|
|
83
|
-
complete() {
|
|
84
|
-
this._effect.complete();
|
|
85
|
-
this.state$.complete();
|
|
86
|
-
this._resources$.complete();
|
|
87
|
-
}
|
|
88
121
|
_next(newCache) {
|
|
89
|
-
const { previous$, current$ } = this._resources$.
|
|
122
|
+
const { previous$, current$ } = this._resources$.peek();
|
|
90
123
|
if (!current$) {
|
|
91
|
-
this._resources$.
|
|
124
|
+
this._resources$.set({
|
|
92
125
|
previous$: null,
|
|
93
126
|
current$: newCache,
|
|
94
127
|
});
|
|
95
128
|
return;
|
|
96
129
|
}
|
|
97
|
-
if (!current$.value.isDone && previous$?.value.isDone) {
|
|
98
|
-
this._resources$.
|
|
130
|
+
if (!current$.value$.peek().isDone && previous$?.value$.peek().isDone) {
|
|
131
|
+
this._resources$.set({
|
|
99
132
|
previous$: previous$,
|
|
100
133
|
current$: newCache,
|
|
101
134
|
});
|
|
102
135
|
return;
|
|
103
136
|
}
|
|
104
|
-
this._resources$.
|
|
137
|
+
this._resources$.set({
|
|
105
138
|
previous$: current$,
|
|
106
139
|
current$: newCache,
|
|
107
140
|
});
|