@fozy-labs/rx-toolkit 0.5.3-rc.2 → 0.5.3

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 (196) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +143 -137
  3. package/dist/common/devtools/combineDevtools.js +3 -3
  4. package/dist/common/devtools/index.d.ts +3 -3
  5. package/dist/common/devtools/index.js +3 -3
  6. package/dist/common/devtools/reduxDevtools.d.ts +1 -1
  7. package/dist/common/devtools/reduxDevtools.js +17 -17
  8. package/dist/common/devtools/types.d.ts +0 -6
  9. package/dist/common/options/SharedOptions.d.ts +1 -0
  10. package/dist/common/options/SharedOptions.js +6 -0
  11. package/dist/common/options/index.d.ts +1 -1
  12. package/dist/common/options/index.js +1 -1
  13. package/dist/common/react/index.d.ts +2 -2
  14. package/dist/common/react/index.js +2 -2
  15. package/dist/common/react/useConstant.js +1 -1
  16. package/dist/common/utils/deepEqual.js +1 -1
  17. package/dist/common/utils/index.d.ts +3 -3
  18. package/dist/common/utils/index.js +3 -3
  19. package/dist/common/utils/shallowEqual.js +1 -1
  20. package/dist/index.d.ts +8 -7
  21. package/dist/index.js +8 -7
  22. package/dist/query/SKIP_TOKEN.js +1 -1
  23. package/dist/query/api/createCommand.d.ts +1 -1
  24. package/dist/query/api/createOperation.d.ts +1 -1
  25. package/dist/query/api/createOperation.js +1 -1
  26. package/dist/query/api/createResource.d.ts +1 -1
  27. package/dist/query/api/createResourceDuplicator.d.ts +1 -1
  28. package/dist/query/core/Command/Command.d.ts +7 -7
  29. package/dist/query/core/Command/Command.js +2 -2
  30. package/dist/query/core/Command/CommandAgent.d.ts +1 -1
  31. package/dist/query/core/Command/index.d.ts +2 -2
  32. package/dist/query/core/Command/index.js +2 -2
  33. package/dist/query/core/{Opertation → Operation}/Operation.d.ts +2 -2
  34. package/dist/query/core/{Opertation → Operation}/Operation.js +1 -1
  35. package/dist/query/core/{Opertation → Operation}/OperationAgent.d.ts +1 -1
  36. package/dist/query/core/{Opertation → Operation}/OperationAgent.js +1 -1
  37. package/dist/query/core/QueriesCache.d.ts +1 -1
  38. package/dist/query/core/QueriesCache.js +1 -1
  39. package/dist/query/core/QueriesLifetimeHooks.js +7 -7
  40. package/dist/query/core/Resource/Resource.d.ts +15 -15
  41. package/dist/query/core/Resource/Resource.js +7 -7
  42. package/dist/query/core/Resource/ResourceAgent.d.ts +1 -1
  43. package/dist/query/core/Resource/ResourceAgent.js +2 -2
  44. package/dist/query/core/Resource/ResourceDuplicator.d.ts +16 -16
  45. package/dist/query/core/Resource/ResourceDuplicator.js +18 -20
  46. package/dist/query/core/Resource/ResourceDuplicatorAgent.d.ts +5 -5
  47. package/dist/query/core/Resource/ResourceDuplicatorAgent.js +2 -2
  48. package/dist/query/core/Resource/ResourceRef.d.ts +2 -2
  49. package/dist/query/core/Resource/ResourceRef.js +12 -12
  50. package/dist/query/index.d.ts +11 -10
  51. package/dist/query/index.js +11 -10
  52. package/dist/query/lib/IndirectMap.js +4 -4
  53. package/dist/query/react/useCommandAgent.d.ts +2 -2
  54. package/dist/query/react/useOperationAgent.d.ts +1 -1
  55. package/dist/query/react/useOperationAgent.js +1 -1
  56. package/dist/query/react/useResourceAgent.d.ts +3 -3
  57. package/dist/query/react/useResourceAgent.js +1 -1
  58. package/dist/query/react/useResourceRef.d.ts +3 -3
  59. package/dist/query/react/useResourceRef.js +7 -2
  60. package/dist/query/types/Command.types.d.ts +1 -1
  61. package/dist/query/types/Operation.types.d.ts +1 -1
  62. package/dist/query/types/Resource.types.d.ts +7 -5
  63. package/dist/query/types/index.d.ts +4 -4
  64. package/dist/query/types/index.js +4 -4
  65. package/dist/query-v2/api/createApi.d.ts +10 -0
  66. package/dist/query-v2/api/createApi.js +83 -0
  67. package/dist/query-v2/core/common/CacheEntry.d.ts +29 -0
  68. package/dist/query-v2/core/common/CacheEntry.js +71 -0
  69. package/dist/query-v2/core/common/CacheMap.d.ts +38 -0
  70. package/dist/query-v2/core/common/CacheMap.js +127 -0
  71. package/dist/query-v2/core/common/LifecycleHooks.d.ts +22 -0
  72. package/dist/query-v2/core/common/LifecycleHooks.js +104 -0
  73. package/dist/query-v2/core/common/index.d.ts +3 -0
  74. package/dist/query-v2/core/common/index.js +3 -0
  75. package/dist/query-v2/core/index.d.ts +3 -0
  76. package/dist/query-v2/core/index.js +3 -0
  77. package/dist/query-v2/core/machines/Machine.d.ts +14 -0
  78. package/dist/query-v2/core/machines/Machine.js +33 -0
  79. package/dist/query-v2/core/machines/MachineError.d.ts +11 -0
  80. package/dist/query-v2/core/machines/MachineError.js +26 -0
  81. package/dist/query-v2/core/machines/MachineIdle.d.ts +8 -0
  82. package/dist/query-v2/core/machines/MachineIdle.js +19 -0
  83. package/dist/query-v2/core/machines/MachinePending.d.ts +12 -0
  84. package/dist/query-v2/core/machines/MachinePending.js +29 -0
  85. package/dist/query-v2/core/machines/MachineRefreshing.d.ts +14 -0
  86. package/dist/query-v2/core/machines/MachineRefreshing.js +46 -0
  87. package/dist/query-v2/core/machines/MachineSuccess.d.ts +16 -0
  88. package/dist/query-v2/core/machines/MachineSuccess.js +42 -0
  89. package/dist/query-v2/core/machines/MachineWithData.d.ts +18 -0
  90. package/dist/query-v2/core/machines/MachineWithData.js +40 -0
  91. package/dist/query-v2/core/machines/Patcher.d.ts +20 -0
  92. package/dist/query-v2/core/machines/Patcher.js +104 -0
  93. package/dist/query-v2/core/machines/index.d.ts +8 -0
  94. package/dist/query-v2/core/machines/index.js +8 -0
  95. package/dist/query-v2/core/resource/ResourceV2.d.ts +120 -0
  96. package/dist/query-v2/core/resource/ResourceV2.js +464 -0
  97. package/dist/query-v2/core/resource/ResourceV2Agent.d.ts +26 -0
  98. package/dist/query-v2/core/resource/ResourceV2Agent.js +132 -0
  99. package/dist/query-v2/core/resource/index.d.ts +2 -0
  100. package/dist/query-v2/core/resource/index.js +2 -0
  101. package/dist/query-v2/index.d.ts +11 -0
  102. package/dist/query-v2/index.js +17 -0
  103. package/dist/query-v2/lib/NO_VALUE.d.ts +2 -0
  104. package/dist/query-v2/lib/NO_VALUE.js +1 -0
  105. package/dist/query-v2/lib/SKIP_TOKEN.d.ts +2 -0
  106. package/dist/query-v2/lib/SKIP_TOKEN.js +1 -0
  107. package/dist/query-v2/lib/index.d.ts +4 -0
  108. package/dist/query-v2/lib/index.js +3 -0
  109. package/dist/query-v2/lib/stableStringify.d.ts +8 -0
  110. package/dist/query-v2/lib/stableStringify.js +23 -0
  111. package/dist/query-v2/plugins/ReactHooksPlugin.d.ts +25 -0
  112. package/dist/query-v2/plugins/ReactHooksPlugin.js +19 -0
  113. package/dist/query-v2/plugins/types.d.ts +1 -0
  114. package/dist/query-v2/plugins/types.js +1 -0
  115. package/dist/query-v2/react/__tests__/helpers.d.ts +12 -0
  116. package/dist/query-v2/react/__tests__/helpers.js +33 -0
  117. package/dist/query-v2/react/index.d.ts +2 -0
  118. package/dist/query-v2/react/index.js +2 -0
  119. package/dist/query-v2/react/useResourceV2Agent.d.ts +12 -0
  120. package/dist/query-v2/react/useResourceV2Agent.js +36 -0
  121. package/dist/query-v2/react/useResourceV2Ref.d.ts +12 -0
  122. package/dist/query-v2/react/useResourceV2Ref.js +57 -0
  123. package/dist/query-v2/snapshot/Snapshot.d.ts +13 -0
  124. package/dist/query-v2/snapshot/Snapshot.js +76 -0
  125. package/dist/query-v2/types/agent.types.d.ts +54 -0
  126. package/dist/query-v2/types/agent.types.js +1 -0
  127. package/dist/query-v2/types/api.types.d.ts +22 -0
  128. package/dist/query-v2/types/api.types.js +1 -0
  129. package/dist/query-v2/types/cache.types.d.ts +37 -0
  130. package/dist/query-v2/types/cache.types.js +1 -0
  131. package/dist/query-v2/types/index.d.ts +9 -0
  132. package/dist/query-v2/types/index.js +9 -0
  133. package/dist/query-v2/types/lifecycle.types.d.ts +25 -0
  134. package/dist/query-v2/types/lifecycle.types.js +1 -0
  135. package/dist/query-v2/types/machine.types.d.ts +67 -0
  136. package/dist/query-v2/types/machine.types.js +1 -0
  137. package/dist/query-v2/types/plugin.types.d.ts +38 -0
  138. package/dist/query-v2/types/plugin.types.js +1 -0
  139. package/dist/query-v2/types/resource.types.d.ts +35 -0
  140. package/dist/query-v2/types/resource.types.js +1 -0
  141. package/dist/query-v2/types/shared.types.d.ts +20 -0
  142. package/dist/query-v2/types/shared.types.js +1 -0
  143. package/dist/query-v2/types/snapshot.types.d.ts +21 -0
  144. package/dist/query-v2/types/snapshot.types.js +1 -0
  145. package/dist/signals/base/Batcher.js +9 -5
  146. package/dist/signals/base/ComputeCache.js +3 -3
  147. package/dist/signals/base/DependencyTracker.js +1 -1
  148. package/dist/signals/base/Devtools.d.ts +3 -2
  149. package/dist/signals/base/Devtools.js +54 -27
  150. package/dist/signals/base/Indexer.js +1 -1
  151. package/dist/signals/base/ReadonlySignal.js +1 -1
  152. package/dist/signals/base/SyncObservable.d.ts +1 -2
  153. package/dist/signals/base/SyncObservable.js +2 -5
  154. package/dist/signals/base/index.d.ts +6 -6
  155. package/dist/signals/base/index.js +6 -6
  156. package/dist/signals/index.d.ts +5 -4
  157. package/dist/signals/index.js +5 -4
  158. package/dist/signals/operators/index.d.ts +1 -1
  159. package/dist/signals/operators/index.js +1 -1
  160. package/dist/signals/react/index.d.ts +1 -1
  161. package/dist/signals/react/index.js +1 -1
  162. package/dist/signals/signals/Computed.d.ts +3 -4
  163. package/dist/signals/signals/Computed.js +18 -10
  164. package/dist/signals/signals/Effect.js +2 -1
  165. package/dist/signals/signals/LocalState.d.ts +3 -4
  166. package/dist/signals/signals/LocalState.js +8 -8
  167. package/dist/signals/signals/Signal.d.ts +7 -6
  168. package/dist/signals/signals/Signal.js +4 -1
  169. package/dist/signals/signals/State.d.ts +4 -5
  170. package/dist/signals/signals/State.js +23 -9
  171. package/dist/signals/signals/index.d.ts +5 -5
  172. package/dist/signals/signals/index.js +5 -6
  173. package/dist/signals/types/SignalOptions.d.ts +16 -0
  174. package/dist/signals/types/SignalOptions.js +1 -0
  175. package/dist/signals/types/index.d.ts +3 -1
  176. package/dist/signals/types/index.js +3 -1
  177. package/dist/signals/types/normalizeSignalOptions.d.ts +2 -0
  178. package/dist/signals/types/normalizeSignalOptions.js +10 -0
  179. package/dist/signals/types/signals.types.d.ts +2 -3
  180. package/docs/CHANGELOG.md +111 -90
  181. package/docs/CONTRIBUTING.md +230 -0
  182. package/docs/contributing/ai-assisted-development.md +47 -0
  183. package/docs/contributing/query-v2/README.md +379 -0
  184. package/docs/{release → contributing/release}/README.md +59 -59
  185. package/docs/devtools/README.md +228 -228
  186. package/docs/migrations/0.5.0.md +58 -58
  187. package/docs/migrations/query-v2.md +171 -0
  188. package/docs/options/README.md +92 -92
  189. package/docs/query/README.md +575 -573
  190. package/docs/query-v2/README.md +280 -0
  191. package/docs/query-v2/api-reference.md +235 -0
  192. package/docs/query-v2/optimistic-updates.md +148 -0
  193. package/docs/query-v2/ssr.md +130 -0
  194. package/docs/signals/README.md +300 -300
  195. package/docs/usage/react/README.md +309 -309
  196. package/package.json +86 -63
@@ -1,300 +1,300 @@
1
- # RxSignals
2
-
3
- RxSignals — это реактивная система управления состоянием, вдохновленная современными фреймворками типа SolidJS и Angular Signals. Она предоставляет эффективные инструменты для создания реактивных приложений.
4
-
5
- ## Основные концепции
6
-
7
- ### Реактивность на основе значений
8
-
9
- Сигналы (`State`) хранят текущее состояние, а производные сущности (`Computed`, `Effect`) автоматически отслеживают зависимости,
10
- применяя кеширование на основе *значений*. Это приводит к тому, что в отличие от классического RxJS-подхода,
11
- где каждое `next()` — это событие, в RxSignals важен именно факт *изменения значения*.
12
-
13
- ### State
14
-
15
- База для создания реактивных сигналов с изменяемым состоянием.
16
-
17
- **Пример использования:**
18
-
19
- ```typescript
20
- import { Signal } from '@fozy-labs/rx-toolkit';
21
-
22
- const name = Signal.state('John');
23
- const age = Signal.state(25);
24
-
25
- // Чтение значения (с отслеживанием зависимостей)
26
- console.log(name()); // "John"
27
-
28
- // Чтение значения без отслеживания
29
- console.log(name.peek()); // "John"
30
-
31
- // Запись нового значения
32
- name.set('Jane');
33
-
34
- // Подписка на изменения через RxJS Observable
35
- const subscription = name.obs.subscribe(newName => {
36
- console.log(`Name changed to: ${newName}`);
37
- });
38
-
39
- // Отписка
40
- subscription.unsubscribe();
41
- ```
42
-
43
- **API Signal:**
44
- - `()`|`get()` — получить значение и зарегистрировать зависимость (для использования внутри Computed/Effect)
45
- - `peek()` — получить значение без регистрации зависимости
46
- - `set(value)` — установить новое значение
47
- - `obs` — RxJS Observable для подписки на изменения
48
-
49
- ### Computed
50
-
51
- Создает вычисляемое значение, которое автоматически обновляется при изменении зависимостей.
52
-
53
- ```typescript
54
- import { Signal } from '@fozy-labs/rx-toolkit';
55
-
56
- const firstName = Signal.state('John');
57
- const lastName = Signal.state('Doe');
58
-
59
- const fullName = Signal.compute(() => `${firstName()} ${lastName()}`);
60
-
61
- console.log(fullName()); // "John Doe"
62
-
63
- firstName.set('Jane');
64
- console.log(fullName()); // "Jane Doe"
65
-
66
- // Подписка на изменения
67
- fullName.obs.subscribe(name => console.log(name));
68
- ```
69
-
70
- **API Computed:**
71
- - `()`|`get()` — получить вычисленное значение с регистрацией зависимости
72
- - `peek()` — получить значение без регистрации зависимости
73
- - `obs` — RxJS Observable для подписки на изменения
74
-
75
- Также на данный момент Computed
76
-
77
- ### Effect
78
-
79
- Создает побочный эффект, который автоматически выполняется при изменении используемых сигналов.
80
-
81
- ```typescript
82
- import { Signal } from '@fozy-labs/rx-toolkit';
83
-
84
- const count = Signal.state(0);
85
- const message = Signal.state('Hello');
86
-
87
- const effect = Signal.effect(() => {
88
- // Выведет: "Hello: 0" при инициализации
89
- console.log(`${message()}: ${count()}`);
90
- });
91
-
92
- count.set(1); // Выведет: "Hello: 1"
93
- message.set('Hi'); // Выведет: "Hi: 1"
94
-
95
- // Остановка эффекта
96
- effect.unsubscribe();
97
- ```
98
-
99
- **Cleanup функция (teardown):**
100
-
101
- Effect поддерживает возврат функции очистки, которая вызывается перед следующим выполнением или при отписке:
102
-
103
- ```typescript
104
- const effect = Signal.effect(() => {
105
- count(); // Создаем подписку на count (тк не работает при асинхронных операциях)
106
- const timer = setInterval(() => count(), 1000);
107
-
108
- // Cleanup - вызывается перед повторным выполнением эффекта
109
- return () => {
110
- clearInterval(timer);
111
- };
112
- });
113
- ```
114
-
115
- ## Функциональный vs классовый стиль
116
-
117
- RxSignals поддерживает как функциональный, так и классовый стили создания сигналов, позволяя выбрать подход в зависимости от предпочтений и архитектуры приложения.
118
- #### Функциональный стиль (рекомендуемый)
119
-
120
- Используйте статические методы `Signal.state`,`Signal.compute` и `Signal.effect` для создания сигналов.
121
- Этот стиль лаконичен, похож на SolidJS и подходит для большинства случаев:
122
-
123
- ```tszz
124
- import { Signal } from '@fozy-labs/rx-toolkit';
125
-
126
- const count = Signal.state(0);
127
- const doubled = Signal.compute(() => count() * 2);
128
- const logEffect = Signal.effect(() => console.log(doubled()));
129
- ```
130
-
131
- #### Классовый стиль
132
-
133
- Создавайте экземпляры классов Signal, Computed и Effect напрямую.
134
- Этот стиль более явный, похож на RxJs и полезен для наследования или сложной логики,
135
- учтите, что вызов `()` недоступен и нужно использовать `get()`:
136
-
137
- ```ts
138
- import { State, Computed, Effect } from '@fozy-labs/rx-toolkit';
139
-
140
- const count = new State(0);
141
- const doubled = new Computed(() => count.get() * 2);
142
- const logEffect = new Effect(() => console.log(doubled.get()));
143
- ```
144
-
145
- ### ReadonlySignal
146
-
147
- Базовый класс для сигналов только для чтения. Используется внутри `signalize` и для создания кастомных сигналов.
148
-
149
- ```typescript
150
- import { ReadonlySignal } from '@fozy-labs/rx-toolkit';
151
-
152
- const customSignal = new ReadonlySignal((subscriber) => {
153
- // Логика подписки
154
- subscriber.next(initialValue);
155
- return () => {
156
- // Cleanup
157
- };
158
- });
159
- ```
160
-
161
- ### LocalState
162
-
163
- Сигнал, который автоматически синхронизируется с `localStorage`.
164
-
165
- ```typescript
166
- import { z } from 'zod/v4';
167
- import { LocalState } from '@fozy-labs/rx-toolkit';
168
-
169
- enum FILTER {
170
- ALL = 'all',
171
- CHANNELS = 'channels',
172
- CHATS = 'chats',
173
- MEETINGS = 'meetings',
174
- }
175
-
176
- const selectedFilter$ = LocalState.create({
177
- key: 'memberships-list-selected-filter',
178
- defaultValue: FILTER.ALL,
179
- zodSchema: z.nativeEnum(FILTER), // Опционально: валидация через Zod
180
- });
181
-
182
- // Использование
183
- console.log(selectedFilter$()); // Значение из localStorage или FILTER.ALL
184
- selectedFilter$.set(FILTER.CHANNELS); // Сохраняется в localStorage
185
-
186
- function logout() {
187
- selectedFilter$.clear(); // Удаляет значение из localStorage (сбрасывает на defaultValue)
188
- }
189
- ```
190
-
191
- **Опции LocalState:**
192
- - `key` — ключ для localStorage
193
- - `defaultValue` — значение по умолчанию
194
- - `zodSchema` — опциональная Zod-схема для валидации
195
- - `userId` — опциональный идентификатор пользователя для изоляции данных
196
- - `checkEffect` — функция валидации значения
197
- - `devtoolsOptions` — настройки для devtools
198
- - `driver` — драйвер для хранения (по умолчанию localStorage, можно заменить на кастомный драйвер)
199
-
200
- ## Операторы
201
-
202
- ### signalize
203
-
204
- Преобразует RxJS Observable в Signal. Позволяет использовать любой Observable как реактивный сигнал.
205
-
206
- ```typescript
207
- import { interval, startWith } from 'rxjs';
208
- import { signalize, Effect } from '@fozy-labs/rx-toolkit';
209
-
210
- // Создаем Observable, который эмитит значение каждую секунду
211
- const timer$ = interval(1000).pipe(
212
- startWith(0),
213
- );
214
-
215
- // Преобразуем Observable в Signal
216
- const tick$ = signalize(timer$);
217
-
218
- // Теперь можно использовать tick$ как обычный Signal
219
- new Effect(() => {
220
- console.log(`Timer: ${tick$.get()}`);
221
- });
222
-
223
- // Доступ к значению без подписки
224
- console.log(tick$.peek());
225
- ```
226
-
227
- ## Батчинг обновлений (Batcher)
228
-
229
- RxSignals автоматически группирует множественные обновления сигналов в один цикл обновления. Это обеспечивает:
230
- - Консистентность состояния
231
- - Оптимальную производительность
232
- - Предсказуемый порядок выполнения эффектов
233
-
234
- ```typescript
235
- const a = Signal.state(1);
236
- const b = Signal.state(2);
237
- const sum = Signal.compute(() => a() + b());
238
-
239
- new Effect(() => {
240
- console.log(`Sum: ${sum()}`);
241
- });
242
-
243
- // Оба изменения обрабатываются в одном батче
244
- Batcher.run(() => {
245
- a.set(10);
246
- b.set(20);
247
- });
248
- // Effect выведет: "Sum: 30" (один раз, а не два)
249
- ```
250
-
251
- ## Интеграция с RxJS
252
-
253
- Сигналы полностью совместимы с RxJS. Каждый сигнал предоставляет `obs` — стандартный RxJS Observable:
254
-
255
- ```typescript
256
- import { filter, take, debounceTime } from 'rxjs';
257
- import { Signal, Computed, signalize } from '@fozy-labs/rx-toolkit';
258
-
259
- const clicks = Signal.state(0);
260
-
261
- // Используем RxJS операторы
262
- const tenClicks$ = clicks.obs.pipe(
263
- filter(value => value === 10),
264
- take(1)
265
- );
266
-
267
- tenClicks$.subscribe(() => {
268
- console.log('Reached 10 clicks!');
269
- });
270
-
271
- // Или наоборот - превращаем Observable в Signal
272
- const debouncedClicks$ = signalize(
273
- clicks.obs.pipe(
274
- debounceTime(300)
275
- )
276
- );
277
-
278
- // Теперь debouncedClicks$ можно использовать в Computed/Effect
279
- const doubled = Signal.compute(() => debouncedClicks$() * 2);
280
- ```
281
-
282
- ## Devtools
283
-
284
- Сигналы поддерживают интеграцию с Redux DevTools для отладки:
285
-
286
- ```typescript
287
- import { Signal } from '@fozy-labs/rx-toolkit';
288
-
289
- // С именем для devtools
290
- const count$ = Signal.state(0, 'counter');
291
-
292
- // Или с расширенными опциями
293
- const user$ = Signal.state(null, {
294
- isDisabled: false, // Отключить отслеживание в devtools
295
- });
296
- ```
297
-
298
- ## React интеграция
299
-
300
- См. [React интеграция](../usage/react/README.md) для подробной информации о том, как использовать RxSignals в React приложениях.
1
+ # RxSignals
2
+
3
+ RxSignals — это реактивная система управления состоянием, вдохновленная современными фреймворками типа SolidJS и Angular Signals. Она предоставляет эффективные инструменты для создания реактивных приложений.
4
+
5
+ ## Основные концепции
6
+
7
+ ### Реактивность на основе значений
8
+
9
+ Сигналы (`State`) хранят текущее состояние, а производные сущности (`Computed`, `Effect`) автоматически отслеживают зависимости,
10
+ применяя кеширование на основе *значений*. Это приводит к тому, что в отличие от классического RxJS-подхода,
11
+ где каждое `next()` — это событие, в RxSignals важен именно факт *изменения значения*.
12
+
13
+ ### State
14
+
15
+ База для создания реактивных сигналов с изменяемым состоянием.
16
+
17
+ **Пример использования:**
18
+
19
+ ```typescript
20
+ import { Signal } from '@fozy-labs/rx-toolkit';
21
+
22
+ const name = Signal.state('John');
23
+ const age = Signal.state(25);
24
+
25
+ // Чтение значения (с отслеживанием зависимостей)
26
+ console.log(name()); // "John"
27
+
28
+ // Чтение значения без отслеживания
29
+ console.log(name.peek()); // "John"
30
+
31
+ // Запись нового значения
32
+ name.set('Jane');
33
+
34
+ // Подписка на изменения через RxJS Observable
35
+ const subscription = name.obs.subscribe(newName => {
36
+ console.log(`Name changed to: ${newName}`);
37
+ });
38
+
39
+ // Отписка
40
+ subscription.unsubscribe();
41
+ ```
42
+
43
+ **API Signal:**
44
+ - `()`|`get()` — получить значение и зарегистрировать зависимость (для использования внутри Computed/Effect)
45
+ - `peek()` — получить значение без регистрации зависимости
46
+ - `set(value)` — установить новое значение
47
+ - `obs` — RxJS Observable для подписки на изменения
48
+
49
+ ### Computed
50
+
51
+ Создает вычисляемое значение, которое автоматически обновляется при изменении зависимостей.
52
+
53
+ ```typescript
54
+ import { Signal } from '@fozy-labs/rx-toolkit';
55
+
56
+ const firstName = Signal.state('John');
57
+ const lastName = Signal.state('Doe');
58
+
59
+ const fullName = Signal.compute(() => `${firstName()} ${lastName()}`);
60
+
61
+ console.log(fullName()); // "John Doe"
62
+
63
+ firstName.set('Jane');
64
+ console.log(fullName()); // "Jane Doe"
65
+
66
+ // Подписка на изменения
67
+ fullName.obs.subscribe(name => console.log(name));
68
+ ```
69
+
70
+ **API Computed:**
71
+ - `()`|`get()` — получить вычисленное значение с регистрацией зависимости
72
+ - `peek()` — получить значение без регистрации зависимости
73
+ - `obs` — RxJS Observable для подписки на изменения
74
+
75
+ Также на данный момент Computed
76
+
77
+ ### Effect
78
+
79
+ Создает побочный эффект, который автоматически выполняется при изменении используемых сигналов.
80
+
81
+ ```typescript
82
+ import { Signal } from '@fozy-labs/rx-toolkit';
83
+
84
+ const count = Signal.state(0);
85
+ const message = Signal.state('Hello');
86
+
87
+ const effect = Signal.effect(() => {
88
+ // Выведет: "Hello: 0" при инициализации
89
+ console.log(`${message()}: ${count()}`);
90
+ });
91
+
92
+ count.set(1); // Выведет: "Hello: 1"
93
+ message.set('Hi'); // Выведет: "Hi: 1"
94
+
95
+ // Остановка эффекта
96
+ effect.unsubscribe();
97
+ ```
98
+
99
+ **Cleanup функция (teardown):**
100
+
101
+ Effect поддерживает возврат функции очистки, которая вызывается перед следующим выполнением или при отписке:
102
+
103
+ ```typescript
104
+ const effect = Signal.effect(() => {
105
+ count(); // Создаем подписку на count (тк не работает при асинхронных операциях)
106
+ const timer = setInterval(() => count(), 1000);
107
+
108
+ // Cleanup - вызывается перед повторным выполнением эффекта
109
+ return () => {
110
+ clearInterval(timer);
111
+ };
112
+ });
113
+ ```
114
+
115
+ ## Функциональный vs классовый стиль
116
+
117
+ RxSignals поддерживает как функциональный, так и классовый стили создания сигналов, позволяя выбрать подход в зависимости от предпочтений и архитектуры приложения.
118
+ #### Функциональный стиль (рекомендуемый)
119
+
120
+ Используйте статические методы `Signal.state`,`Signal.compute` и `Signal.effect` для создания сигналов.
121
+ Этот стиль лаконичен, похож на SolidJS и подходит для большинства случаев:
122
+
123
+ ```tszz
124
+ import { Signal } from '@fozy-labs/rx-toolkit';
125
+
126
+ const count = Signal.state(0);
127
+ const doubled = Signal.compute(() => count() * 2);
128
+ const logEffect = Signal.effect(() => console.log(doubled()));
129
+ ```
130
+
131
+ #### Классовый стиль
132
+
133
+ Создавайте экземпляры классов Signal, Computed и Effect напрямую.
134
+ Этот стиль более явный, похож на RxJs и полезен для наследования или сложной логики,
135
+ учтите, что вызов `()` недоступен и нужно использовать `get()`:
136
+
137
+ ```ts
138
+ import { State, Computed, Effect } from '@fozy-labs/rx-toolkit';
139
+
140
+ const count = new State(0);
141
+ const doubled = new Computed(() => count.get() * 2);
142
+ const logEffect = new Effect(() => console.log(doubled.get()));
143
+ ```
144
+
145
+ ### ReadonlySignal
146
+
147
+ Базовый класс для сигналов только для чтения. Используется внутри `signalize` и для создания кастомных сигналов.
148
+
149
+ ```typescript
150
+ import { ReadonlySignal } from '@fozy-labs/rx-toolkit';
151
+
152
+ const customSignal = new ReadonlySignal((subscriber) => {
153
+ // Логика подписки
154
+ subscriber.next(initialValue);
155
+ return () => {
156
+ // Cleanup
157
+ };
158
+ });
159
+ ```
160
+
161
+ ### LocalState
162
+
163
+ Сигнал, который автоматически синхронизируется с `localStorage`.
164
+
165
+ ```typescript
166
+ import { z } from 'zod/v4';
167
+ import { LocalState } from '@fozy-labs/rx-toolkit';
168
+
169
+ enum FILTER {
170
+ ALL = 'all',
171
+ CHANNELS = 'channels',
172
+ CHATS = 'chats',
173
+ MEETINGS = 'meetings',
174
+ }
175
+
176
+ const selectedFilter$ = LocalState.create({
177
+ key: 'memberships-list-selected-filter',
178
+ defaultValue: FILTER.ALL,
179
+ zodSchema: z.nativeEnum(FILTER), // Опционально: валидация через Zod
180
+ });
181
+
182
+ // Использование
183
+ console.log(selectedFilter$()); // Значение из localStorage или FILTER.ALL
184
+ selectedFilter$.set(FILTER.CHANNELS); // Сохраняется в localStorage
185
+
186
+ function logout() {
187
+ selectedFilter$.clear(); // Удаляет значение из localStorage (сбрасывает на defaultValue)
188
+ }
189
+ ```
190
+
191
+ **Опции LocalState:**
192
+ - `key` — ключ для localStorage
193
+ - `defaultValue` — значение по умолчанию
194
+ - `zodSchema` — опциональная Zod-схема для валидации
195
+ - `userId` — опциональный идентификатор пользователя для изоляции данных
196
+ - `checkEffect` — функция валидации значения
197
+ - `devtoolsOptions` — настройки для devtools
198
+ - `driver` — драйвер для хранения (по умолчанию localStorage, можно заменить на кастомный драйвер)
199
+
200
+ ## Операторы
201
+
202
+ ### signalize
203
+
204
+ Преобразует RxJS Observable в Signal. Позволяет использовать любой Observable как реактивный сигнал.
205
+
206
+ ```typescript
207
+ import { interval, startWith } from 'rxjs';
208
+ import { signalize, Effect } from '@fozy-labs/rx-toolkit';
209
+
210
+ // Создаем Observable, который эмитит значение каждую секунду
211
+ const timer$ = interval(1000).pipe(
212
+ startWith(0),
213
+ );
214
+
215
+ // Преобразуем Observable в Signal
216
+ const tick$ = signalize(timer$);
217
+
218
+ // Теперь можно использовать tick$ как обычный Signal
219
+ new Effect(() => {
220
+ console.log(`Timer: ${tick$.get()}`);
221
+ });
222
+
223
+ // Доступ к значению без подписки
224
+ console.log(tick$.peek());
225
+ ```
226
+
227
+ ## Батчинг обновлений (Batcher)
228
+
229
+ RxSignals автоматически группирует множественные обновления сигналов в один цикл обновления. Это обеспечивает:
230
+ - Консистентность состояния
231
+ - Оптимальную производительность
232
+ - Предсказуемый порядок выполнения эффектов
233
+
234
+ ```typescript
235
+ const a = Signal.state(1);
236
+ const b = Signal.state(2);
237
+ const sum = Signal.compute(() => a() + b());
238
+
239
+ new Effect(() => {
240
+ console.log(`Sum: ${sum()}`);
241
+ });
242
+
243
+ // Оба изменения обрабатываются в одном батче
244
+ Batcher.run(() => {
245
+ a.set(10);
246
+ b.set(20);
247
+ });
248
+ // Effect выведет: "Sum: 30" (один раз, а не два)
249
+ ```
250
+
251
+ ## Интеграция с RxJS
252
+
253
+ Сигналы полностью совместимы с RxJS. Каждый сигнал предоставляет `obs` — стандартный RxJS Observable:
254
+
255
+ ```typescript
256
+ import { filter, take, debounceTime } from 'rxjs';
257
+ import { Signal, Computed, signalize } from '@fozy-labs/rx-toolkit';
258
+
259
+ const clicks = Signal.state(0);
260
+
261
+ // Используем RxJS операторы
262
+ const tenClicks$ = clicks.obs.pipe(
263
+ filter(value => value === 10),
264
+ take(1)
265
+ );
266
+
267
+ tenClicks$.subscribe(() => {
268
+ console.log('Reached 10 clicks!');
269
+ });
270
+
271
+ // Или наоборот - превращаем Observable в Signal
272
+ const debouncedClicks$ = signalize(
273
+ clicks.obs.pipe(
274
+ debounceTime(300)
275
+ )
276
+ );
277
+
278
+ // Теперь debouncedClicks$ можно использовать в Computed/Effect
279
+ const doubled = Signal.compute(() => debouncedClicks$() * 2);
280
+ ```
281
+
282
+ ## Devtools
283
+
284
+ Сигналы поддерживают интеграцию с Redux DevTools для отладки:
285
+
286
+ ```typescript
287
+ import { Signal } from '@fozy-labs/rx-toolkit';
288
+
289
+ // С именем для devtools
290
+ const count$ = Signal.state(0, 'counter');
291
+
292
+ // Или с расширенными опциями
293
+ const user$ = Signal.state(null, {
294
+ isDisabled: false, // Отключить отслеживание в devtools
295
+ });
296
+ ```
297
+
298
+ ## React интеграция
299
+
300
+ См. [React интеграция](../usage/react/README.md) для подробной информации о том, как использовать RxSignals в React приложениях.