@fozy-labs/rx-toolkit 0.4.17 → 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.
Files changed (77) hide show
  1. package/README.md +20 -19
  2. package/dist/common/devtools/reduxDevtools.d.ts +17 -0
  3. package/dist/common/devtools/reduxDevtools.js +98 -17
  4. package/dist/common/devtools/types.d.ts +1 -0
  5. package/dist/common/options/DefaultOptions.d.ts +0 -2
  6. package/dist/common/options/DefaultOptions.js +0 -2
  7. package/dist/common/options/SharedOptions.d.ts +0 -2
  8. package/dist/common/options/SharedOptions.js +0 -1
  9. package/dist/query/api/resetAllQueriesCache.d.ts +1 -0
  10. package/dist/query/api/resetAllQueriesCache.js +4 -0
  11. package/dist/query/core/Opertation/Operation.js +10 -7
  12. package/dist/query/core/Opertation/OperationAgent.d.ts +0 -2
  13. package/dist/query/core/Opertation/OperationAgent.js +4 -21
  14. package/dist/query/core/QueriesCache.d.ts +1 -1
  15. package/dist/query/core/QueriesCache.js +2 -6
  16. package/dist/query/core/{CleanAllQueriesSignal.d.ts → ResetAllQueriesSignal.d.ts} +1 -1
  17. package/dist/query/core/ResetAllQueriesSignal.js +11 -0
  18. package/dist/query/core/Resource/Resource.js +7 -3
  19. package/dist/query/core/Resource/ResourceAgent.d.ts +2 -3
  20. package/dist/query/core/Resource/ResourceAgent.js +62 -29
  21. package/dist/query/index.d.ts +1 -1
  22. package/dist/query/index.js +1 -1
  23. package/dist/query/lib/IndirectMap.d.ts +1 -1
  24. package/dist/query/lib/IndirectMap.js +1 -1
  25. package/dist/query/lib/ReactiveCache.d.ts +1 -1
  26. package/dist/query/lib/ReactiveCache.js +2 -2
  27. package/dist/query/react/useOperationAgent.js +2 -5
  28. package/dist/query/react/useResourceAgent.js +1 -4
  29. package/dist/query/types/Operation.types.d.ts +1 -3
  30. package/dist/query/types/Resource.types.d.ts +1 -3
  31. package/dist/signals/base/Batcher.d.ts +1 -1
  32. package/dist/signals/base/Batcher.js +1 -1
  33. package/dist/signals/base/DependencyTracker.d.ts +18 -0
  34. package/dist/signals/base/DependencyTracker.js +13 -0
  35. package/dist/signals/base/Devtools.d.ts +1 -1
  36. package/dist/signals/base/Devtools.js +13 -1
  37. package/dist/signals/base/ReadonlySignal.d.ts +5 -7
  38. package/dist/signals/base/ReadonlySignal.js +20 -12
  39. package/dist/signals/base/index.d.ts +1 -2
  40. package/dist/signals/base/index.js +1 -2
  41. package/dist/signals/operators/index.d.ts +0 -2
  42. package/dist/signals/operators/index.js +0 -2
  43. package/dist/signals/operators/signalize.d.ts +2 -2
  44. package/dist/signals/operators/signalize.js +1 -1
  45. package/dist/signals/react/useSignal.d.ts +6 -2
  46. package/dist/signals/react/useSignal.js +2 -21
  47. package/dist/signals/signals/Computed.d.ts +13 -11
  48. package/dist/signals/signals/Computed.js +79 -26
  49. package/dist/signals/signals/Effect.d.ts +11 -7
  50. package/dist/signals/signals/Effect.js +60 -58
  51. package/dist/signals/signals/LocalSignal.d.ts +14 -7
  52. package/dist/signals/signals/LocalSignal.js +52 -33
  53. package/dist/signals/signals/Signal.d.ts +13 -37
  54. package/dist/signals/signals/Signal.js +44 -58
  55. package/dist/signals/types/index.d.ts +1 -0
  56. package/dist/signals/types/index.js +1 -0
  57. package/dist/signals/types/signals.types.d.ts +16 -0
  58. package/docs/CHANGELOG.md +32 -0
  59. package/docs/devtools/README.md +162 -29
  60. package/docs/migrations/0.5.0.md +73 -0
  61. package/docs/options/README.md +89 -0
  62. package/docs/query/README.md +425 -89
  63. package/docs/release/README.md +58 -6
  64. package/docs/signals/README.md +207 -34
  65. package/docs/usage/react/README.md +261 -49
  66. package/package.json +5 -5
  67. package/dist/query/api/cleanAllQueriesCache.d.ts +0 -1
  68. package/dist/query/api/cleanAllQueriesCache.js +0 -4
  69. package/dist/query/core/CleanAllQueriesSignal.js +0 -11
  70. package/dist/signals/base/Tracker.d.ts +0 -10
  71. package/dist/signals/base/Tracker.js +0 -7
  72. package/dist/signals/base/types.d.ts +0 -23
  73. package/dist/signals/operators/filterUpdates.d.ts +0 -5
  74. package/dist/signals/operators/filterUpdates.js +0 -18
  75. package/dist/signals/operators/mapSignals.d.ts +0 -3
  76. package/dist/signals/operators/mapSignals.js +0 -10
  77. /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
- Поставляется с React-хуками из коробки.
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 store = {
46
- count$: new Signal(0),
47
- doubled$: new Computed(() => store.count$.value * 2),
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(store.count$);
55
+ const count = useSignal(count$);
56
56
 
57
57
  // Angular signal
58
- count$ = toSignal(store.count$);
58
+ public readonly count = toSignal(count$.obs);
59
59
 
60
60
  // Angular pipe
61
- {{ store.count$ | async }}
61
+ {{ count$.obs | async }}
62
62
 
63
63
  // SolidJS
64
- const count$ = from(store.count$)
64
+ const count = from(count$.obs)
65
65
 
66
66
  // Svelte
67
- $: count = store.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$ = new Computed(() => clickCount$.value * 2);
82
+ const doubled$ = Signal.compute(() => clickCount$() * 2);
83
83
 
84
- console.log(doubled$.value); // Всегда актуальное значение
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, data, args }) => {
111
- const item = draft.items.find(i => i.id === data.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 reduxDevtools = devtools.connect({ name: options.name ?? 'RxToolkit' });
9
- reduxDevtools.init(state);
10
- const scheduler = Batcher.scheduler(Infinity);
11
- let isCreated = false;
12
- const updateFn = () => {
13
- reduxDevtools.send({ type: isCreated ? 'create' : 'update' }, state);
14
- isCreated = false;
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
- scheduler.schedule(createFn());
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
- clearFn();
107
+ pendingActionType = 'clear';
108
+ scheduler.schedule(flushToDevtools);
32
109
  return;
33
110
  }
34
111
  state = applyState(keys, newState, state);
35
- scheduler.schedule(updateFn);
112
+ // Не перезаписываем 'create' на 'update' если create еще не отправлен
113
+ if (pendingActionType !== 'create') {
114
+ pendingActionType = 'update';
115
+ }
116
+ scheduler.schedule(flushToDevtools);
36
117
  };
37
118
  }
38
119
  };
@@ -8,4 +8,5 @@ export type StateDevtoolsOptions = {
8
8
  isDisabled?: boolean;
9
9
  name?: string;
10
10
  base?: string;
11
+ _skipValues?: any[];
11
12
  } | string;
@@ -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
  }
@@ -3,6 +3,5 @@ export class SharedOptions {
3
3
  static DEVTOOLS = null;
4
4
  static onQueryError = null;
5
5
  static getScopeName = null;
6
- static getScopeDestroyed$ = null;
7
6
  static defaultCompareArgs = shallowEqual;
8
7
  }
@@ -0,0 +1 @@
1
+ export declare function resetAllQueriesCache(): void;
@@ -0,0 +1,4 @@
1
+ import { ResetAllQueriesSignal } from "../core/ResetAllQueriesSignal";
2
+ export function resetAllQueriesCache() {
3
+ ResetAllQueriesSignal.clean();
4
+ }
@@ -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 { CleanAllQueriesSignal } from "../../../query/core/CleanAllQueriesSignal";
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
- CleanAllQueriesSignal.clean$.subscribe(() => {
70
- this._queriesCache.clear();
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.batch(() => this._initiate(args, options));
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.batch(() => {
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.batch(() => {
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, Effect, Signal } from "../../../signals";
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$.value;
21
- const currState = operations.current$?.value$.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$.next({
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
- clear(): void;
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
- clear() {
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
  }
@@ -1,5 +1,5 @@
1
1
  import { Subject } from "rxjs";
2
- export declare class CleanAllQueriesSignal {
2
+ export declare class ResetAllQueriesSignal {
3
3
  private static subject$;
4
4
  static clean$: Subject<void>;
5
5
  static clean(): void;
@@ -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 { CleanAllQueriesSignal } from "../CleanAllQueriesSignal";
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
- CleanAllQueriesSignal.clean$.subscribe(() => {
121
- this._queriesCache.clear();
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, Effect, Signal } from "../../../signals";
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$.value;
9
+ const resources = this._resources$.get();
25
10
  let prevState;
26
- const currState = resources.current$?.value$.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$.value.current$;
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$.value;
122
+ const { previous$, current$ } = this._resources$.peek();
90
123
  if (!current$) {
91
- this._resources$.next({
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$.next({
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$.next({
137
+ this._resources$.set({
105
138
  previous$: current$,
106
139
  current$: newCache,
107
140
  });