@fozy-labs/rx-toolkit 0.5.3-rc.1 → 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 (207) 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/DefaultOptions.d.ts +1 -1
  10. package/dist/common/options/SharedOptions.d.ts +3 -2
  11. package/dist/common/options/SharedOptions.js +6 -0
  12. package/dist/common/options/index.d.ts +1 -1
  13. package/dist/common/options/index.js +1 -1
  14. package/dist/common/react/index.d.ts +2 -2
  15. package/dist/common/react/index.js +2 -2
  16. package/dist/common/react/useConstant.js +1 -1
  17. package/dist/common/utils/deepEqual.js +1 -1
  18. package/dist/common/utils/index.d.ts +3 -3
  19. package/dist/common/utils/index.js +3 -3
  20. package/dist/common/utils/shallowEqual.js +1 -1
  21. package/dist/index.d.ts +8 -7
  22. package/dist/index.js +8 -7
  23. package/dist/query/SKIP_TOKEN.js +1 -1
  24. package/dist/query/api/createCommand.d.ts +21 -0
  25. package/dist/query/api/createCommand.js +20 -0
  26. package/dist/query/api/createOperation.d.ts +5 -3
  27. package/dist/query/api/createOperation.js +6 -2
  28. package/dist/query/api/createResource.d.ts +2 -2
  29. package/dist/query/api/createResourceDuplicator.d.ts +2 -2
  30. package/dist/query/core/Command/Command.d.ts +35 -0
  31. package/dist/query/core/{Opertation/Operation.js → Command/Command.js} +13 -14
  32. package/dist/query/core/Command/CommandAgent.d.ts +19 -0
  33. package/dist/query/core/{Opertation/OperationAgent.js → Command/CommandAgent.js} +13 -13
  34. package/dist/query/core/Command/index.d.ts +2 -0
  35. package/dist/query/core/Command/index.js +2 -0
  36. package/dist/query/core/Operation/Operation.d.ts +8 -0
  37. package/dist/query/core/Operation/Operation.js +4 -0
  38. package/dist/query/core/Operation/OperationAgent.d.ts +4 -0
  39. package/dist/query/core/Operation/OperationAgent.js +4 -0
  40. package/dist/query/core/QueriesCache.d.ts +2 -2
  41. package/dist/query/core/QueriesCache.js +1 -1
  42. package/dist/query/core/QueriesLifetimeHooks.d.ts +1 -1
  43. package/dist/query/core/QueriesLifetimeHooks.js +7 -7
  44. package/dist/query/core/Resource/Resource.d.ts +16 -16
  45. package/dist/query/core/Resource/Resource.js +7 -7
  46. package/dist/query/core/Resource/ResourceAgent.d.ts +2 -2
  47. package/dist/query/core/Resource/ResourceAgent.js +3 -3
  48. package/dist/query/core/Resource/ResourceDuplicator.d.ts +17 -17
  49. package/dist/query/core/Resource/ResourceDuplicator.js +18 -20
  50. package/dist/query/core/Resource/ResourceDuplicatorAgent.d.ts +6 -6
  51. package/dist/query/core/Resource/ResourceDuplicatorAgent.js +3 -3
  52. package/dist/query/core/Resource/ResourceRef.d.ts +2 -2
  53. package/dist/query/core/Resource/ResourceRef.js +12 -12
  54. package/dist/query/index.d.ts +11 -8
  55. package/dist/query/index.js +14 -8
  56. package/dist/query/lib/IndirectMap.js +4 -4
  57. package/dist/query/lib/ReactiveCache.d.ts +1 -1
  58. package/dist/query/react/useCommandAgent.d.ts +24 -0
  59. package/dist/query/react/useCommandAgent.js +39 -0
  60. package/dist/query/react/useOperationAgent.d.ts +6 -8
  61. package/dist/query/react/useOperationAgent.js +6 -23
  62. package/dist/query/react/useResourceAgent.d.ts +4 -4
  63. package/dist/query/react/useResourceAgent.js +1 -1
  64. package/dist/query/react/useResourceRef.d.ts +3 -3
  65. package/dist/query/react/useResourceRef.js +7 -2
  66. package/dist/query/types/Command.types.d.ts +154 -0
  67. package/dist/query/types/Command.types.js +1 -0
  68. package/dist/query/types/Operation.types.d.ts +13 -154
  69. package/dist/query/types/Resource.types.d.ts +7 -5
  70. package/dist/query/types/index.d.ts +4 -3
  71. package/dist/query/types/index.js +5 -3
  72. package/dist/query-v2/api/createApi.d.ts +10 -0
  73. package/dist/query-v2/api/createApi.js +83 -0
  74. package/dist/query-v2/core/common/CacheEntry.d.ts +29 -0
  75. package/dist/query-v2/core/common/CacheEntry.js +71 -0
  76. package/dist/query-v2/core/common/CacheMap.d.ts +38 -0
  77. package/dist/query-v2/core/common/CacheMap.js +127 -0
  78. package/dist/query-v2/core/common/LifecycleHooks.d.ts +22 -0
  79. package/dist/query-v2/core/common/LifecycleHooks.js +104 -0
  80. package/dist/query-v2/core/common/index.d.ts +3 -0
  81. package/dist/query-v2/core/common/index.js +3 -0
  82. package/dist/query-v2/core/index.d.ts +3 -0
  83. package/dist/query-v2/core/index.js +3 -0
  84. package/dist/query-v2/core/machines/Machine.d.ts +14 -0
  85. package/dist/query-v2/core/machines/Machine.js +33 -0
  86. package/dist/query-v2/core/machines/MachineError.d.ts +11 -0
  87. package/dist/query-v2/core/machines/MachineError.js +26 -0
  88. package/dist/query-v2/core/machines/MachineIdle.d.ts +8 -0
  89. package/dist/query-v2/core/machines/MachineIdle.js +19 -0
  90. package/dist/query-v2/core/machines/MachinePending.d.ts +12 -0
  91. package/dist/query-v2/core/machines/MachinePending.js +29 -0
  92. package/dist/query-v2/core/machines/MachineRefreshing.d.ts +14 -0
  93. package/dist/query-v2/core/machines/MachineRefreshing.js +46 -0
  94. package/dist/query-v2/core/machines/MachineSuccess.d.ts +16 -0
  95. package/dist/query-v2/core/machines/MachineSuccess.js +42 -0
  96. package/dist/query-v2/core/machines/MachineWithData.d.ts +18 -0
  97. package/dist/query-v2/core/machines/MachineWithData.js +40 -0
  98. package/dist/query-v2/core/machines/Patcher.d.ts +20 -0
  99. package/dist/query-v2/core/machines/Patcher.js +104 -0
  100. package/dist/query-v2/core/machines/index.d.ts +8 -0
  101. package/dist/query-v2/core/machines/index.js +8 -0
  102. package/dist/query-v2/core/resource/ResourceV2.d.ts +120 -0
  103. package/dist/query-v2/core/resource/ResourceV2.js +464 -0
  104. package/dist/query-v2/core/resource/ResourceV2Agent.d.ts +26 -0
  105. package/dist/query-v2/core/resource/ResourceV2Agent.js +132 -0
  106. package/dist/query-v2/core/resource/index.d.ts +2 -0
  107. package/dist/query-v2/core/resource/index.js +2 -0
  108. package/dist/query-v2/index.d.ts +11 -0
  109. package/dist/query-v2/index.js +17 -0
  110. package/dist/query-v2/lib/NO_VALUE.d.ts +2 -0
  111. package/dist/query-v2/lib/NO_VALUE.js +1 -0
  112. package/dist/query-v2/lib/SKIP_TOKEN.d.ts +2 -0
  113. package/dist/query-v2/lib/SKIP_TOKEN.js +1 -0
  114. package/dist/query-v2/lib/index.d.ts +4 -0
  115. package/dist/query-v2/lib/index.js +3 -0
  116. package/dist/query-v2/lib/stableStringify.d.ts +8 -0
  117. package/dist/query-v2/lib/stableStringify.js +23 -0
  118. package/dist/query-v2/plugins/ReactHooksPlugin.d.ts +25 -0
  119. package/dist/query-v2/plugins/ReactHooksPlugin.js +19 -0
  120. package/dist/query-v2/plugins/types.d.ts +1 -0
  121. package/dist/query-v2/plugins/types.js +1 -0
  122. package/dist/query-v2/react/__tests__/helpers.d.ts +12 -0
  123. package/dist/query-v2/react/__tests__/helpers.js +33 -0
  124. package/dist/query-v2/react/index.d.ts +2 -0
  125. package/dist/query-v2/react/index.js +2 -0
  126. package/dist/query-v2/react/useResourceV2Agent.d.ts +12 -0
  127. package/dist/query-v2/react/useResourceV2Agent.js +36 -0
  128. package/dist/query-v2/react/useResourceV2Ref.d.ts +12 -0
  129. package/dist/query-v2/react/useResourceV2Ref.js +57 -0
  130. package/dist/query-v2/snapshot/Snapshot.d.ts +13 -0
  131. package/dist/query-v2/snapshot/Snapshot.js +76 -0
  132. package/dist/query-v2/types/agent.types.d.ts +54 -0
  133. package/dist/query-v2/types/agent.types.js +1 -0
  134. package/dist/query-v2/types/api.types.d.ts +22 -0
  135. package/dist/query-v2/types/api.types.js +1 -0
  136. package/dist/query-v2/types/cache.types.d.ts +37 -0
  137. package/dist/query-v2/types/cache.types.js +1 -0
  138. package/dist/query-v2/types/index.d.ts +9 -0
  139. package/dist/query-v2/types/index.js +9 -0
  140. package/dist/query-v2/types/lifecycle.types.d.ts +25 -0
  141. package/dist/query-v2/types/lifecycle.types.js +1 -0
  142. package/dist/query-v2/types/machine.types.d.ts +67 -0
  143. package/dist/query-v2/types/machine.types.js +1 -0
  144. package/dist/query-v2/types/plugin.types.d.ts +38 -0
  145. package/dist/query-v2/types/plugin.types.js +1 -0
  146. package/dist/query-v2/types/resource.types.d.ts +35 -0
  147. package/dist/query-v2/types/resource.types.js +1 -0
  148. package/dist/query-v2/types/shared.types.d.ts +20 -0
  149. package/dist/query-v2/types/shared.types.js +1 -0
  150. package/dist/query-v2/types/snapshot.types.d.ts +21 -0
  151. package/dist/query-v2/types/snapshot.types.js +1 -0
  152. package/dist/signals/base/Batcher.js +9 -5
  153. package/dist/signals/base/ComputeCache.js +3 -3
  154. package/dist/signals/base/DependencyTracker.js +1 -1
  155. package/dist/signals/base/Devtools.d.ts +3 -2
  156. package/dist/signals/base/Devtools.js +54 -27
  157. package/dist/signals/base/Indexer.js +1 -1
  158. package/dist/signals/base/ReadonlySignal.js +1 -1
  159. package/dist/signals/base/SyncObservable.d.ts +1 -2
  160. package/dist/signals/base/SyncObservable.js +2 -5
  161. package/dist/signals/base/index.d.ts +6 -6
  162. package/dist/signals/base/index.js +6 -6
  163. package/dist/signals/index.d.ts +5 -4
  164. package/dist/signals/index.js +5 -4
  165. package/dist/signals/operators/index.d.ts +1 -1
  166. package/dist/signals/operators/index.js +1 -1
  167. package/dist/signals/operators/signalize.d.ts +1 -1
  168. package/dist/signals/react/index.d.ts +1 -1
  169. package/dist/signals/react/index.js +1 -1
  170. package/dist/signals/signals/Computed.d.ts +3 -4
  171. package/dist/signals/signals/Computed.js +18 -10
  172. package/dist/signals/signals/Effect.js +2 -1
  173. package/dist/signals/signals/LocalState.d.ts +44 -0
  174. package/dist/signals/signals/{LocalSignal.js → LocalState.js} +62 -28
  175. package/dist/signals/signals/Signal.d.ts +8 -7
  176. package/dist/signals/signals/Signal.js +4 -1
  177. package/dist/signals/signals/State.d.ts +4 -5
  178. package/dist/signals/signals/State.js +23 -9
  179. package/dist/signals/signals/index.d.ts +5 -5
  180. package/dist/signals/signals/index.js +5 -6
  181. package/dist/signals/types/SignalOptions.d.ts +16 -0
  182. package/dist/signals/types/SignalOptions.js +1 -0
  183. package/dist/signals/types/index.d.ts +3 -1
  184. package/dist/signals/types/index.js +3 -1
  185. package/dist/signals/types/normalizeSignalOptions.d.ts +2 -0
  186. package/dist/signals/types/normalizeSignalOptions.js +10 -0
  187. package/dist/signals/types/signals.types.d.ts +6 -2
  188. package/docs/CHANGELOG.md +111 -32
  189. package/docs/CONTRIBUTING.md +230 -0
  190. package/docs/contributing/ai-assisted-development.md +47 -0
  191. package/docs/contributing/query-v2/README.md +379 -0
  192. package/docs/{release → contributing/release}/README.md +59 -59
  193. package/docs/devtools/README.md +228 -228
  194. package/docs/migrations/0.5.0.md +58 -58
  195. package/docs/migrations/query-v2.md +171 -0
  196. package/docs/options/README.md +92 -90
  197. package/docs/query/README.md +575 -571
  198. package/docs/query-v2/README.md +280 -0
  199. package/docs/query-v2/api-reference.md +235 -0
  200. package/docs/query-v2/optimistic-updates.md +148 -0
  201. package/docs/query-v2/ssr.md +130 -0
  202. package/docs/signals/README.md +300 -295
  203. package/docs/usage/react/README.md +309 -307
  204. package/package.json +86 -63
  205. package/dist/query/core/Opertation/Operation.d.ts +0 -35
  206. package/dist/query/core/Opertation/OperationAgent.d.ts +0 -19
  207. package/dist/signals/signals/LocalSignal.d.ts +0 -32
@@ -1,571 +1,575 @@
1
- # RxQuery
2
-
3
- RxQuery система для управления асинхронными запросами и кэшированием данных в RxToolkit. Она состоит из двух основных компонентов: **Resources** и **Operations**.
4
-
5
- ## Основные концепции
6
-
7
- ### Resources (Ресурсы)
8
-
9
- Resources предназначены для реактивного кэширования повторяемых запросов. Они автоматически управляют состоянием загрузки, кэшируют результаты и обеспечивают эффективную инвалидацию данных.
10
-
11
- **Ключевые особенности:**
12
- - Автоматическое кэширование по аргументам запроса
13
- - Поддержка AbortController для отмены запросов
14
- - Реактивные обновления состояния
15
- - Оптимистичные обновления
16
- - Гибкое управление временем жизни кэша
17
-
18
- ### Operations (Операции)
19
-
20
- Operations представляют одноразовые операции или мутации. Они не кэшируются, но предоставляют состояние выполнения и могут связываться с ресурсами для их обновления.
21
-
22
- **Ключевые особенности:**
23
- - Отслеживание состояния выполнения
24
- - Связывание с ресурсами для их обновления
25
- - Поддержка оптимистичных обновлений
26
- - Возможность блокировки связанных ресурсов
27
- - Автоматический откат при ошибках
28
-
29
- ### Agents (Агенты)
30
-
31
- Agents представляют собой интеллектуальные обертки над ресурсами (или операциями), которые обеспечивают более удобную работу с состояниями запросов для потребителей.
32
-
33
- **Основная проблема, которую решают агенты:**
34
-
35
- Кэш ресурсов содержит "сырые" состояния отдельных запросов, но потребителям нужна более высокоуровневая логика:
36
- - `isInitialLoading` должно быть true только при первой загрузке ресурса
37
- - При смене аргументов запроса нужно показывать данные предыдущего запроса, пока загружается новый
38
- - Состояние загрузки должно отражать контекст использования, а не просто состояние кэша
39
-
40
- ### ResourceRef (Ссылка на ресурс)
41
-
42
- Ref это абстракция для взаимодействия с элементом кэша ресурса напрямую.
43
-
44
- **Особенности:**
45
- - Операции используют ref под капотом для управления связанным ресурсом
46
- - Ref может ссылаться на отсутствующий элемент кэша
47
- - Позволяет выполнять patch-транзакции для оптимистичных обновлений
48
-
49
- ---
50
-
51
- ## API
52
-
53
- ### createResource
54
-
55
- Создает новый ресурс для кэширования данных.
56
-
57
- ```typescript
58
- import { createResource } from '@fozy-labs/rx-toolkit';
59
-
60
- interface User {
61
- id: string;
62
- name: string;
63
- email: string;
64
- }
65
-
66
- const userResource = createResource<{ id: string }, User>({
67
- async queryFn(args, tools) {
68
- const response = await fetch(`/api/users/${args.id}`, {
69
- signal: tools.abortSignal // Поддержка отмены запроса
70
- });
71
- return response.json();
72
- },
73
-
74
- // Опционально: трансформация данных
75
- select: (data) => ({
76
- id: data.id,
77
- name: data.name,
78
- email: data.email
79
- }),
80
-
81
- // Опционально: время жизни кэша (по умолчанию 60 секунд)
82
- cacheLifetime: 30000, // 30 секунд
83
-
84
- // Опционально: имя для devtools
85
- devtoolsName: 'user-resource',
86
-
87
- // Опционально: кастомное сравнение аргументов
88
- compareArgsFn: (args1, args2) => args1.id === args2.id,
89
- });
90
- ```
91
-
92
- **Параметры createResource:**
93
-
94
- | Параметр | Тип | Описание |
95
- |----------|-----|----------|
96
- | `queryFn` | `(args, tools) => Promise<Result>` | Функция выполнения запроса |
97
- | `select` | `(data) => Selected` | Опциональная функция трансформации данных |
98
- | `cacheLifetime` | `number \| false` | Время жизни кэша в мс (default: 60000). `false` кэш не удаляется |
99
- | `compareArgsFn` | `(args1, args2) => boolean` | Кастомная функция сравнения аргументов |
100
- | `onCacheEntryAdded` | `(args, tools) => void` | Хук при добавлении элемента в кэш |
101
- | `onQueryStarted` | `(args, tools) => void` | Хук при старте запроса |
102
- | `devtoolsName` | `string \| false` | Имя для devtools (`false` отключить) |
103
-
104
- **Tools в queryFn:**
105
- - `abortSignal` — AbortSignal для отмены запроса
106
-
107
- ### createOperation
108
-
109
- Создает новую операцию для выполнения мутаций.
110
-
111
- ```typescript
112
- import { createOperation } from '@fozy-labs/rx-toolkit';
113
-
114
- const updateUser = createOperation<
115
- { id: string; data: Partial<User> },
116
- User
117
- >({
118
- async queryFn(args) {
119
- const response = await fetch(`/api/users/${args.id}`, {
120
- method: 'PATCH',
121
- headers: { 'Content-Type': 'application/json' },
122
- body: JSON.stringify(args.data)
123
- });
124
- return response.json();
125
- },
126
-
127
- // Связывание с ресурсами
128
- link(add) {
129
- add({
130
- resource: userResource,
131
- forwardArgs: (args) => ({ id: args.id }),
132
- // Обновление кэша после успешного запроса
133
- update({ draft, args, data }) {
134
- Object.assign(draft, args.data);
135
- },
136
- });
137
- },
138
-
139
- devtoolsName: 'update-user',
140
- });
141
- ```
142
-
143
- **Параметры createOperation:**
144
-
145
- | Параметр | Тип | Описание |
146
- |---------------------|-----------------------------|-----------------------------------------------|
147
- | `queryFn` | `(args) => Promise<Result>` | Функция выполнения операции |
148
- | `select` | `(data) => Selected` | Опциональная функция трансформации результата |
149
- | `link` | `(add) => void` | Функция связывания с ресурсами |
150
- | `cacheLifetime` | `number \| false` | Время жизни кэша операции (default: 1000) |
151
- | `onCacheEntryAdded` | `(args, tools) => void` | Хук при добавлении в кэш |
152
- | `onQueryStarted` | `(args, tools) => void` | Хук при старте операции |
153
- | `devtoolsName` | `string \| false` | Имя для devtools |
154
-
155
- ---
156
-
157
- ## Свойства Link
158
-
159
- Link позволяет связывать операции с ресурсами для автоматического обновления кэша:
160
-
161
- ```typescript
162
- type LinkOptions<D, RD> = {
163
- /**
164
- * Целевой ресурс, с которым связывается операция
165
- * @required
166
- */
167
- resource: ResourceInstance<RD>;
168
-
169
- /**
170
- * Функция для получения аргументов ресурса из аргументов операции.
171
- * Используется для определения какой элемент в кэше нужно обновить
172
- * @required
173
- */
174
- forwardArgs: (args: D["Args"]) => RD["Args"];
175
-
176
- /**
177
- * Инвалидация кэша после выполнения операции.
178
- * При true кэш будет очищен и ресурс перезагрузится
179
- * @default false
180
- */
181
- invalidate?: boolean;
182
-
183
- /**
184
- * Блокировка ресурса во время выполнения операции.
185
- * При true — ресурс не сможет выполнять новые запросы
186
- * @default false
187
- */
188
- lock?: boolean;
189
-
190
- /**
191
- * Обновление кэша ПОСЛЕ успешного выполнения операции.
192
- * Использует Immer для иммутабельных обновлений
193
- */
194
- update?: (tools: {
195
- draft: RD["Data"]; // Immer draft для мутации
196
- args: D["Args"]; // Аргументы операции
197
- data: D["Data"]; // Результат операции
198
- }) => void | RD["Data"];
199
-
200
- /**
201
- * Оптимистичное обновление ДО выполнения операции.
202
- * Позволяет обновить UI немедленно
203
- */
204
- optimisticUpdate?: (tools: {
205
- draft: RD["Data"]; // Immer draft для мутации
206
- args: D["Args"]; // Аргументы операции
207
- }) => void | RD["Data"];
208
-
209
- /**
210
- * Создание нового элемента в кэше.
211
- * Используется когда операция создает новую сущность
212
- */
213
- create?: (tools: {
214
- args: D["Args"];
215
- data: D["Data"];
216
- }) => RD["Data"] | Promise<RD["Data"]>;
217
- };
218
- ```
219
-
220
- ### Пример: Оптимистичные обновления
221
-
222
- ```typescript
223
- const toggleCartItem = createOperation({
224
- queryFn: async (args: { id: string; enabled: boolean }) => {
225
- return fetch(`/api/cart/toggle`, {
226
- method: 'POST',
227
- body: JSON.stringify(args)
228
- }).then(r => r.json());
229
- },
230
- link(add) {
231
- add({
232
- resource: cartResource,
233
- forwardArgs: () => undefined, // Корзина без параметров
234
-
235
- // Оптимистичное обновление — UI обновится мгновенно
236
- optimisticUpdate: ({ draft, args }) => {
237
- const item = draft.items.find(i => i.id === args.id);
238
- if (item) {
239
- item.enabled = args.enabled;
240
- }
241
- }
242
- // При ошибке изменения автоматически откатятся
243
- });
244
- }
245
- });
246
- ```
247
-
248
- ---
249
-
250
- ## Состояния запросов
251
-
252
- ### ResourceQueryState
253
-
254
- Состояние запроса ресурса через агента:
255
-
256
- ```typescript
257
- type ResourceQueryState<D> = {
258
- /** Инициализирован ли хотя бы один запрос */
259
- isInitiated: boolean;
260
-
261
- /** Любая загрузка (первая или повторная) */
262
- isLoading: boolean;
263
-
264
- /** Первая загрузка (данных еще не было) */
265
- isInitialLoading: boolean;
266
-
267
- /** Перезагрузка (данные уже есть) */
268
- isReloading: boolean;
269
-
270
- /** Завершен ли запрос */
271
- isDone: boolean;
272
-
273
- /** Успешно ли завершен последний запрос */
274
- isSuccess: boolean;
275
-
276
- /** Произошла ли ошибка последнего запроса */
277
- isError: boolean;
278
-
279
- /** Заблокирован ли ресурс операцией */
280
- isLocked: boolean;
281
-
282
- /** Оригинал ошибки, если есть */
283
- error: unknown | undefined;
284
-
285
- /** Данные (или select данных) */
286
- data: D["Data"] | undefined;
287
-
288
- /** Аргументы последнего запроса */
289
- args: D["Args"] | undefined;
290
- }
291
- ```
292
-
293
- ### OperationQueryState
294
-
295
- Состояние выполнения операции:
296
-
297
- ```typescript
298
- type OperationQueryState<D> = {
299
- isInitiated: boolean;
300
- isLoading: boolean;
301
- isDone: boolean;
302
- isSuccess: boolean;
303
- isError: boolean;
304
- error: unknown | undefined;
305
- data: D["Data"] | undefined;
306
- }
307
- ```
308
-
309
- ---
310
-
311
- ## ResourceRef API
312
-
313
- ResourceRef предоставляет низкоуровневый доступ к элементу кэша:
314
-
315
- ```typescript
316
- type ResourceRefInstanse<D> = {
317
- /** Проверка наличия элемента в кэше */
318
- get has(): boolean;
319
-
320
- /** Блокировка ресурса (возвращает функцию разблокировки) */
321
- lock(): { unlock: () => void };
322
-
323
- /** Снятие одной блокировки */
324
- unlockOne(): void;
325
-
326
- /** Patch-транзакция для изменения данных */
327
- patch(patchFn: (data: D['Data']) => void): ResourceTransaction | null;
328
-
329
- /** Инвалидация (очистка) кэша */
330
- invalidate(): void;
331
-
332
- /** Создание элемента в кэше с данными */
333
- create(data: D['Data']): void;
334
- }
335
- ```
336
-
337
- ### Patch-транзакции
338
-
339
- Транзакции позволяют делать изменения с возможностью отката:
340
-
341
- ```typescript
342
- type ResourceTransaction = {
343
- patches: ImmerPatch[] // Патчи изменений
344
- inversePatches: ImmerPatch[] // Патчи для отката
345
- status: 'pending' | 'committed' | 'aborted'
346
- abort(): void // Откатить изменения
347
- commit(): void // Подтвердить изменения
348
- }
349
- ```
350
-
351
- **Пример использования транзакций:**
352
-
353
- ```typescript
354
- import { useResourceRef } from '@fozy-labs/rx-toolkit';
355
-
356
- function TodoList() {
357
- const todoRef = useResourceRef(todoResource, undefined);
358
- const [pendingChanges, setPendingChanges] = useState([]);
359
-
360
- const handleToggle = (itemId: number) => {
361
- const transaction = todoRef.patch((draft) => {
362
- const item = draft.items.find(i => i.id === itemId);
363
- if (item) item.completed = !item.completed;
364
- });
365
-
366
- if (transaction) {
367
- setPendingChanges(prev => [...prev, {
368
- id: itemId,
369
- transaction
370
- }]);
371
- }
372
- };
373
-
374
- const commitChange = (id: number) => {
375
- const change = pendingChanges.find(c => c.id === id);
376
- change?.transaction.commit();
377
- setPendingChanges(prev => prev.filter(c => c.id !== id));
378
- };
379
-
380
- const abortChange = (id: number) => {
381
- const change = pendingChanges.find(c => c.id === id);
382
- change?.transaction.abort(); // Данные вернутся к исходным
383
- setPendingChanges(prev => prev.filter(c => c.id !== id));
384
- };
385
- }
386
- ```
387
-
388
- ---
389
-
390
- ## Lifecycle хуки
391
-
392
- ### onCacheEntryAdded
393
-
394
- Вызывается при добавлении нового элемента в кэш:
395
-
396
- ```typescript
397
- const userResource = createResource({
398
- queryFn: fetchUser,
399
-
400
- onCacheEntryAdded(args, { $cacheDataLoaded, $cacheEntryRemoved, dataChanged$ }) {
401
- // args аргументы запроса
402
-
403
- // Ожидание первой загрузки данных
404
- $cacheDataLoaded.then(() => {
405
- console.log('Данные загружены в кэш');
406
- });
407
-
408
- // Ожидание удаления из кэша
409
- $cacheEntryRemoved.then(() => {
410
- console.log('Элемент удален из кэша');
411
- });
412
-
413
- // Подписка на изменения данных
414
- const sub = dataChanged$.subscribe(data => {
415
- console.log('Данные изменились:', data);
416
- });
417
- }
418
- });
419
- ```
420
-
421
- ### onQueryStarted
422
-
423
- Вызывается при старте каждого запроса:
424
-
425
- ```typescript
426
- const userResource = createResource({
427
- queryFn: fetchUser,
428
-
429
- async onQueryStarted(args, { $queryFulfilled }) {
430
- console.log('Запрос начат с аргументами:', args);
431
-
432
- const result = await $queryFulfilled;
433
-
434
- if (result.isError) {
435
- console.error('Ошибка запроса:', result.error);
436
- } else {
437
- console.log('Запрос успешен:', result.data);
438
- }
439
- }
440
- });
441
- ```
442
-
443
- ---
444
-
445
- ## Утилиты
446
-
447
- ### resetAllQueriesCache
448
-
449
- Сбрасывает кэш всех ресурсов в приложении:
450
-
451
- ```typescript
452
- import { resetAllQueriesCache } from '@fozy-labs/rx-toolkit';
453
-
454
- function LogoutButton() {
455
- const handleLogout = () => {
456
- // Очистить все кэшированные данные при выходе
457
- resetAllQueriesCache();
458
- navigate('/login');
459
- };
460
-
461
- return <button onClick={handleLogout}>Выйти</button>;
462
- }
463
- ```
464
-
465
- ### SKIP токен
466
-
467
- Используется для условного пропуска запроса:
468
-
469
- ```typescript
470
- import { useResourceAgent, SKIP } from '@fozy-labs/rx-toolkit';
471
-
472
- function UserProfile({ userId }: { userId: string | null }) {
473
- // Запрос будет выполнен только если userId не null
474
- const userQuery = useResourceAgent(
475
- userResource,
476
- userId ? { id: userId } : SKIP
477
- );
478
-
479
- if (!userId) return <div>Выберите пользователя</div>;
480
- if (userQuery.isLoading) return <div>Загрузка...</div>;
481
-
482
- return <div>{userQuery.data?.name}</div>;
483
- }
484
- ```
485
-
486
- ---
487
-
488
- ## Примеры
489
-
490
- ### Корзина покупок с оптимистичными обновлениями
491
-
492
- ```typescript
493
- import { createResource, createOperation, useResourceAgent, useOperationAgent } from '@fozy-labs/rx-toolkit';
494
-
495
- const cartResource = createResource({
496
- queryFn: () => fetch('/api/cart').then(r => r.json()),
497
- devtoolsName: 'cart'
498
- });
499
-
500
- const toggleCartItem = createOperation({
501
- queryFn: (args: { id: string; enabled: boolean }) =>
502
- fetch('/api/cart/toggle', {
503
- method: 'POST',
504
- body: JSON.stringify(args)
505
- }).then(r => r.json()),
506
-
507
- link(add) {
508
- add({
509
- resource: cartResource,
510
- forwardArgs: () => undefined,
511
- optimisticUpdate: ({ draft, args }) => {
512
- const item = draft.items.find(i => i.id === args.id);
513
- if (item) item.enabled = args.enabled;
514
- }
515
- });
516
- }
517
- });
518
-
519
- function ShoppingCart() {
520
- const cartQuery = useResourceAgent(cartResource, undefined);
521
- const [toggleItem, toggleState] = useOperationAgent(toggleCartItem);
522
-
523
- return (
524
- <div>
525
- {cartQuery.data?.items.map(item => (
526
- <div key={item.id}>
527
- <span>{item.name}</span>
528
- <button onClick={() => toggleItem({
529
- id: item.id,
530
- enabled: !item.enabled
531
- })}>
532
- {item.enabled ? 'Убрать' : 'Добавить'}
533
- </button>
534
- </div>
535
- ))}
536
- </div>
537
- );
538
- }
539
- ```
540
-
541
- ### Зависимые запросы
542
-
543
- ```typescript
544
- const userResource = createResource({
545
- queryFn: (args: { id: number }) => fetch(`/api/users/${args.id}`).then(r => r.json()),
546
- });
547
-
548
- const userStatsResource = createResource({
549
- queryFn: (args: { userId: number; period: string }) =>
550
- fetch(`/api/users/${args.userId}/stats?period=${args.period}`).then(r => r.json()),
551
- });
552
-
553
- function UserDashboard({ userId }: { userId: number }) {
554
- const [period, setPeriod] = useState('daily');
555
-
556
- const userQuery = useResourceAgent(userResource, { id: userId });
557
-
558
- // Запрос статистики выполняется только после загрузки пользователя
559
- const statsQuery = useResourceAgent(
560
- userStatsResource,
561
- userQuery.isSuccess ? { userId, period } : SKIP
562
- );
563
-
564
- // ...
565
- }
566
- ```
567
-
568
- ## React интеграция
569
-
570
- См. [React интеграция](../usage/react/README.md) для подробной информации о React хуках.
571
-
1
+ # RxQuery
2
+
3
+ > **Note:** Экспериментальная версия Query v2 доступна см. [Query v2](../query-v2/README.md).
4
+
5
+ RxQuery система для управления асинхронными запросами и кэшированием данных в RxToolkit. Она состоит из двух основных компонентов: **Resources** и **Commands**.
6
+
7
+ ## Основные концепции
8
+
9
+ ### Resources (Ресурсы)
10
+
11
+ Resources предназначены для реактивного кэширования повторяемых запросов. Они автоматически управляют состоянием загрузки, кэшируют результаты и обеспечивают эффективную инвалидацию данных.
12
+
13
+ **Ключевые особенности:**
14
+ - Автоматическое кэширование по аргументам запроса
15
+ - Поддержка AbortController для отмены запросов
16
+ - Реактивные обновления состояния
17
+ - Оптимистичные обновления
18
+ - Гибкое управление временем жизни кэша
19
+
20
+ ### Commands (Команды)
21
+
22
+ Commands представляют одноразовые команды или мутации. Они не кэшируются, но предоставляют состояние выполнения и могут связываться с ресурсами для их обновления.
23
+
24
+ **Ключевые особенности:**
25
+ - Отслеживание состояния выполнения
26
+ - Связывание с ресурсами для их обновления
27
+ - Поддержка оптимистичных обновлений
28
+ - Возможность блокировки связанных ресурсов
29
+ - Автоматический откат при ошибках
30
+
31
+ ### Agents (Агенты)
32
+
33
+ Agents представляют собой интеллектуальные обертки над ресурсами (или командами), которые обеспечивают более удобную работу с состояниями запросов для потребителей.
34
+
35
+ **Основная проблема, которую решают агенты:**
36
+
37
+ Кэш ресурсов содержит "сырые" состояния отдельных запросов, но потребителям нужна более высокоуровневая логика:
38
+ - `isInitialLoading` должно быть true только при первой загрузке ресурса
39
+ - При смене аргументов запроса нужно показывать данные предыдущего запроса, пока загружается новый
40
+ - Состояние загрузки должно отражать контекст использования, а не просто состояние кэша
41
+
42
+ ### ResourceRef (Ссылка на ресурс)
43
+
44
+ Ref — это абстракция для взаимодействия с элементом кэша ресурса напрямую.
45
+
46
+ **Особенности:**
47
+ - Команды используют ref под капотом для управления связанным ресурсом
48
+ - Ref может ссылаться на отсутствующий элемент кэша
49
+ - Позволяет выполнять patch-транзакции для оптимистичных обновлений
50
+
51
+ ---
52
+
53
+ ## API
54
+
55
+ ### createResource
56
+
57
+ Создает новый ресурс для кэширования данных.
58
+
59
+ ```typescript
60
+ import { createResource } from '@fozy-labs/rx-toolkit';
61
+
62
+ interface User {
63
+ id: string;
64
+ name: string;
65
+ email: string;
66
+ }
67
+
68
+ const userResource = createResource<{ id: string }, User>({
69
+ async queryFn(args, tools) {
70
+ const response = await fetch(`/api/users/${args.id}`, {
71
+ signal: tools.abortSignal // Поддержка отмены запроса
72
+ });
73
+ return response.json();
74
+ },
75
+
76
+ // Опционально: трансформация данных
77
+ select: (data) => ({
78
+ id: data.id,
79
+ name: data.name,
80
+ email: data.email
81
+ }),
82
+
83
+ // Опционально: время жизни кэша (по умолчанию 60 секунд)
84
+ cacheLifetime: 30000, // 30 секунд
85
+
86
+ // Опционально: имя для devtools
87
+ devtoolsName: 'user-resource',
88
+
89
+ // Опционально: кастомное сравнение аргументов
90
+ compareArgsFn: (args1, args2) => args1.id === args2.id,
91
+ });
92
+ ```
93
+
94
+ **Параметры createResource:**
95
+
96
+ | Параметр | Тип | Описание |
97
+ |----------|-----|----------|
98
+ | `queryFn` | `(args, tools) => Promise<Result>` | Функция выполнения запроса |
99
+ | `select` | `(data) => Selected` | Опциональная функция трансформации данных |
100
+ | `cacheLifetime` | `number \| false` | Время жизни кэша в мс (default: 60000). `false` — кэш не удаляется |
101
+ | `compareArgsFn` | `(args1, args2) => boolean` | Кастомная функция сравнения аргументов |
102
+ | `onCacheEntryAdded` | `(args, tools) => void` | Хук при добавлении элемента в кэш |
103
+ | `onQueryStarted` | `(args, tools) => void` | Хук при старте запроса |
104
+ | `devtoolsName` | `string \| false` | Имя для devtools (`false` — отключить) |
105
+
106
+ **Tools в queryFn:**
107
+ - `abortSignal` — AbortSignal для отмены запроса
108
+
109
+ ### createCommand
110
+
111
+ Создает новую команду для выполнения мутаций.
112
+
113
+ ```typescript
114
+ import { createCommand } from '@fozy-labs/rx-toolkit';
115
+
116
+ const updateUser = createCommand<
117
+ { id: string; data: Partial<User> },
118
+ User
119
+ >({
120
+ async queryFn(args) {
121
+ const response = await fetch(`/api/users/${args.id}`, {
122
+ method: 'PATCH',
123
+ headers: { 'Content-Type': 'application/json' },
124
+ body: JSON.stringify(args.data)
125
+ });
126
+ return response.json();
127
+ },
128
+
129
+ // Связывание с ресурсами
130
+ link(add) {
131
+ add({
132
+ resource: userResource,
133
+ forwardArgs: (args) => ({ id: args.id }),
134
+ // Обновление кэша после успешного запроса
135
+ update({ draft, args, data }) {
136
+ Object.assign(draft, args.data);
137
+ },
138
+ });
139
+ },
140
+
141
+ devtoolsName: 'update-user',
142
+ });
143
+ ```
144
+
145
+ **Параметры createCommand:**
146
+
147
+ | Параметр | Тип | Описание |
148
+ |---------------------|-----------------------------|-----------------------------------------------|
149
+ | `queryFn` | `(args) => Promise<Result>` | Функция выполнения команды |
150
+ | `select` | `(data) => Selected` | Опциональная функция трансформации результата |
151
+ | `link` | `(add) => void` | Функция связывания с ресурсами |
152
+ | `cacheLifetime` | `number \| false` | Время жизни кэша команды (default: 1000) |
153
+ | `onCacheEntryAdded` | `(args, tools) => void` | Хук при добавлении в кэш |
154
+ | `onQueryStarted` | `(args, tools) => void` | Хук при старте команды |
155
+ | `devtoolsName` | `string \| false` | Имя для devtools |
156
+
157
+ > **Note:** `createOperation` and `useOperationAgent` are deprecated aliases for `createCommand` and `useCommandAgent` respectively. They will be removed in v0.6.0.
158
+
159
+ ---
160
+
161
+ ## Свойства Link
162
+
163
+ Link позволяет связывать команды с ресурсами для автоматического обновления кэша:
164
+
165
+ ```typescript
166
+ type LinkOptions<D, RD> = {
167
+ /**
168
+ * Целевой ресурс, с которым связывается команда
169
+ * @required
170
+ */
171
+ resource: ResourceInstance<RD>;
172
+
173
+ /**
174
+ * Функция для получения аргументов ресурса из аргументов команды.
175
+ * Используется для определения какой элемент в кэше нужно обновить
176
+ * @required
177
+ */
178
+ forwardArgs: (args: D["Args"]) => RD["Args"];
179
+
180
+ /**
181
+ * Инвалидация кэша после выполнения команды.
182
+ * При true — кэш будет очищен и ресурс перезагрузится
183
+ * @default false
184
+ */
185
+ invalidate?: boolean;
186
+
187
+ /**
188
+ * Блокировка ресурса во время выполнения команды.
189
+ * При true — ресурс не сможет выполнять новые запросы
190
+ * @default false
191
+ */
192
+ lock?: boolean;
193
+
194
+ /**
195
+ * Обновление кэша ПОСЛЕ успешного выполнения команды.
196
+ * Использует Immer для иммутабельных обновлений
197
+ */
198
+ update?: (tools: {
199
+ draft: RD["Data"]; // Immer draft для мутации
200
+ args: D["Args"]; // Аргументы команды
201
+ data: D["Data"]; // Результат команды
202
+ }) => void | RD["Data"];
203
+
204
+ /**
205
+ * Оптимистичное обновление ДО выполнения команды.
206
+ * Позволяет обновить UI немедленно
207
+ */
208
+ optimisticUpdate?: (tools: {
209
+ draft: RD["Data"]; // Immer draft для мутации
210
+ args: D["Args"]; // Аргументы команды
211
+ }) => void | RD["Data"];
212
+
213
+ /**
214
+ * Создание нового элемента в кэше.
215
+ * Используется когда команда создает новую сущность
216
+ */
217
+ create?: (tools: {
218
+ args: D["Args"];
219
+ data: D["Data"];
220
+ }) => RD["Data"] | Promise<RD["Data"]>;
221
+ };
222
+ ```
223
+
224
+ ### Пример: Оптимистичные обновления
225
+
226
+ ```typescript
227
+ const toggleCartItem = createCommand({
228
+ queryFn: async (args: { id: string; enabled: boolean }) => {
229
+ return fetch(`/api/cart/toggle`, {
230
+ method: 'POST',
231
+ body: JSON.stringify(args)
232
+ }).then(r => r.json());
233
+ },
234
+ link(add) {
235
+ add({
236
+ resource: cartResource,
237
+ forwardArgs: () => undefined, // Корзина без параметров
238
+
239
+ // Оптимистичное обновление — UI обновится мгновенно
240
+ optimisticUpdate: ({ draft, args }) => {
241
+ const item = draft.items.find(i => i.id === args.id);
242
+ if (item) {
243
+ item.enabled = args.enabled;
244
+ }
245
+ }
246
+ // При ошибке изменения автоматически откатятся
247
+ });
248
+ }
249
+ });
250
+ ```
251
+
252
+ ---
253
+
254
+ ## Состояния запросов
255
+
256
+ ### ResourceQueryState
257
+
258
+ Состояние запроса ресурса через агента:
259
+
260
+ ```typescript
261
+ type ResourceQueryState<D> = {
262
+ /** Инициализирован ли хотя бы один запрос */
263
+ isInitiated: boolean;
264
+
265
+ /** Любая загрузка (первая или повторная) */
266
+ isLoading: boolean;
267
+
268
+ /** Первая загрузка (данных еще не было) */
269
+ isInitialLoading: boolean;
270
+
271
+ /** Перезагрузка (данные уже есть) */
272
+ isReloading: boolean;
273
+
274
+ /** Завершен ли запрос */
275
+ isDone: boolean;
276
+
277
+ /** Успешно ли завершен последний запрос */
278
+ isSuccess: boolean;
279
+
280
+ /** Произошла ли ошибка последнего запроса */
281
+ isError: boolean;
282
+
283
+ /** Заблокирован ли ресурс командой */
284
+ isLocked: boolean;
285
+
286
+ /** Оригинал ошибки, если есть */
287
+ error: unknown | undefined;
288
+
289
+ /** Данные (или select данных) */
290
+ data: D["Data"] | undefined;
291
+
292
+ /** Аргументы последнего запроса */
293
+ args: D["Args"] | undefined;
294
+ }
295
+ ```
296
+
297
+ ### CommandQueryState
298
+
299
+ Состояние выполнения команды:
300
+
301
+ ```typescript
302
+ type CommandQueryState<D> = {
303
+ isInitiated: boolean;
304
+ isLoading: boolean;
305
+ isDone: boolean;
306
+ isSuccess: boolean;
307
+ isError: boolean;
308
+ error: unknown | undefined;
309
+ data: D["Data"] | undefined;
310
+ }
311
+ ```
312
+
313
+ ---
314
+
315
+ ## ResourceRef API
316
+
317
+ ResourceRef предоставляет низкоуровневый доступ к элементу кэша:
318
+
319
+ ```typescript
320
+ type ResourceRefInstance<D> = {
321
+ /** Проверка наличия элемента в кэше */
322
+ get has(): boolean;
323
+
324
+ /** Блокировка ресурса (возвращает функцию разблокировки) */
325
+ lock(): { unlock: () => void };
326
+
327
+ /** Снятие одной блокировки */
328
+ unlockOne(): void;
329
+
330
+ /** Patch-транзакция для изменения данных */
331
+ patch(patchFn: (data: D['Data']) => void): ResourceTransaction | null;
332
+
333
+ /** Инвалидация (очистка) кэша */
334
+ invalidate(): void;
335
+
336
+ /** Создание элемента в кэше с данными */
337
+ create(data: D['Data']): void;
338
+ }
339
+ ```
340
+
341
+ ### Patch-транзакции
342
+
343
+ Транзакции позволяют делать изменения с возможностью отката:
344
+
345
+ ```typescript
346
+ type ResourceTransaction = {
347
+ patches: ImmerPatch[] // Патчи изменений
348
+ inversePatches: ImmerPatch[] // Патчи для отката
349
+ status: 'pending' | 'committed' | 'aborted'
350
+ abort(): void // Откатить изменения
351
+ commit(): void // Подтвердить изменения
352
+ }
353
+ ```
354
+
355
+ **Пример использования транзакций:**
356
+
357
+ ```typescript
358
+ import { useResourceRef } from '@fozy-labs/rx-toolkit';
359
+
360
+ function TodoList() {
361
+ const todoRef = useResourceRef(todoResource, undefined);
362
+ const [pendingChanges, setPendingChanges] = useState([]);
363
+
364
+ const handleToggle = (itemId: number) => {
365
+ const transaction = todoRef.patch((draft) => {
366
+ const item = draft.items.find(i => i.id === itemId);
367
+ if (item) item.completed = !item.completed;
368
+ });
369
+
370
+ if (transaction) {
371
+ setPendingChanges(prev => [...prev, {
372
+ id: itemId,
373
+ transaction
374
+ }]);
375
+ }
376
+ };
377
+
378
+ const commitChange = (id: number) => {
379
+ const change = pendingChanges.find(c => c.id === id);
380
+ change?.transaction.commit();
381
+ setPendingChanges(prev => prev.filter(c => c.id !== id));
382
+ };
383
+
384
+ const abortChange = (id: number) => {
385
+ const change = pendingChanges.find(c => c.id === id);
386
+ change?.transaction.abort(); // Данные вернутся к исходным
387
+ setPendingChanges(prev => prev.filter(c => c.id !== id));
388
+ };
389
+ }
390
+ ```
391
+
392
+ ---
393
+
394
+ ## Lifecycle хуки
395
+
396
+ ### onCacheEntryAdded
397
+
398
+ Вызывается при добавлении нового элемента в кэш:
399
+
400
+ ```typescript
401
+ const userResource = createResource({
402
+ queryFn: fetchUser,
403
+
404
+ onCacheEntryAdded(args, { $cacheDataLoaded, $cacheEntryRemoved, dataChanged$ }) {
405
+ // args аргументы запроса
406
+
407
+ // Ожидание первой загрузки данных
408
+ $cacheDataLoaded.then(() => {
409
+ console.log('Данные загружены в кэш');
410
+ });
411
+
412
+ // Ожидание удаления из кэша
413
+ $cacheEntryRemoved.then(() => {
414
+ console.log('Элемент удален из кэша');
415
+ });
416
+
417
+ // Подписка на изменения данных
418
+ const sub = dataChanged$.subscribe(data => {
419
+ console.log('Данные изменились:', data);
420
+ });
421
+ }
422
+ });
423
+ ```
424
+
425
+ ### onQueryStarted
426
+
427
+ Вызывается при старте каждого запроса:
428
+
429
+ ```typescript
430
+ const userResource = createResource({
431
+ queryFn: fetchUser,
432
+
433
+ async onQueryStarted(args, { $queryFulfilled }) {
434
+ console.log('Запрос начат с аргументами:', args);
435
+
436
+ const result = await $queryFulfilled;
437
+
438
+ if (result.isError) {
439
+ console.error('Ошибка запроса:', result.error);
440
+ } else {
441
+ console.log('Запрос успешен:', result.data);
442
+ }
443
+ }
444
+ });
445
+ ```
446
+
447
+ ---
448
+
449
+ ## Утилиты
450
+
451
+ ### resetAllQueriesCache
452
+
453
+ Сбрасывает кэш всех ресурсов в приложении:
454
+
455
+ ```typescript
456
+ import { resetAllQueriesCache } from '@fozy-labs/rx-toolkit';
457
+
458
+ function LogoutButton() {
459
+ const handleLogout = () => {
460
+ // Очистить все кэшированные данные при выходе
461
+ resetAllQueriesCache();
462
+ navigate('/login');
463
+ };
464
+
465
+ return <button onClick={handleLogout}>Выйти</button>;
466
+ }
467
+ ```
468
+
469
+ ### SKIP токен
470
+
471
+ Используется для условного пропуска запроса:
472
+
473
+ ```typescript
474
+ import { useResourceAgent, SKIP } from '@fozy-labs/rx-toolkit';
475
+
476
+ function UserProfile({ userId }: { userId: string | null }) {
477
+ // Запрос будет выполнен только если userId не null
478
+ const userQuery = useResourceAgent(
479
+ userResource,
480
+ userId ? { id: userId } : SKIP
481
+ );
482
+
483
+ if (!userId) return <div>Выберите пользователя</div>;
484
+ if (userQuery.isLoading) return <div>Загрузка...</div>;
485
+
486
+ return <div>{userQuery.data?.name}</div>;
487
+ }
488
+ ```
489
+
490
+ ---
491
+
492
+ ## Примеры
493
+
494
+ ### Корзина покупок с оптимистичными обновлениями
495
+
496
+ ```typescript
497
+ import { createResource, createCommand, useResourceAgent, useCommandAgent } from '@fozy-labs/rx-toolkit';
498
+
499
+ const cartResource = createResource({
500
+ queryFn: () => fetch('/api/cart').then(r => r.json()),
501
+ devtoolsName: 'cart'
502
+ });
503
+
504
+ const toggleCartItem = createCommand({
505
+ queryFn: (args: { id: string; enabled: boolean }) =>
506
+ fetch('/api/cart/toggle', {
507
+ method: 'POST',
508
+ body: JSON.stringify(args)
509
+ }).then(r => r.json()),
510
+
511
+ link(add) {
512
+ add({
513
+ resource: cartResource,
514
+ forwardArgs: () => undefined,
515
+ optimisticUpdate: ({ draft, args }) => {
516
+ const item = draft.items.find(i => i.id === args.id);
517
+ if (item) item.enabled = args.enabled;
518
+ }
519
+ });
520
+ }
521
+ });
522
+
523
+ function ShoppingCart() {
524
+ const cartQuery = useResourceAgent(cartResource, undefined);
525
+ const [toggleItem, toggleState] = useCommandAgent(toggleCartItem);
526
+
527
+ return (
528
+ <div>
529
+ {cartQuery.data?.items.map(item => (
530
+ <div key={item.id}>
531
+ <span>{item.name}</span>
532
+ <button onClick={() => toggleItem({
533
+ id: item.id,
534
+ enabled: !item.enabled
535
+ })}>
536
+ {item.enabled ? 'Убрать' : 'Добавить'}
537
+ </button>
538
+ </div>
539
+ ))}
540
+ </div>
541
+ );
542
+ }
543
+ ```
544
+
545
+ ### Зависимые запросы
546
+
547
+ ```typescript
548
+ const userResource = createResource({
549
+ queryFn: (args: { id: number }) => fetch(`/api/users/${args.id}`).then(r => r.json()),
550
+ });
551
+
552
+ const userStatsResource = createResource({
553
+ queryFn: (args: { userId: number; period: string }) =>
554
+ fetch(`/api/users/${args.userId}/stats?period=${args.period}`).then(r => r.json()),
555
+ });
556
+
557
+ function UserDashboard({ userId }: { userId: number }) {
558
+ const [period, setPeriod] = useState('daily');
559
+
560
+ const userQuery = useResourceAgent(userResource, { id: userId });
561
+
562
+ // Запрос статистики выполняется только после загрузки пользователя
563
+ const statsQuery = useResourceAgent(
564
+ userStatsResource,
565
+ userQuery.isSuccess ? { userId, period } : SKIP
566
+ );
567
+
568
+ // ...
569
+ }
570
+ ```
571
+
572
+ ## React интеграция
573
+
574
+ См. [React интеграция](../usage/react/README.md) для подробной информации о React хуках.
575
+