@fozy-labs/rx-toolkit 0.5.3 → 0.6.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 (302) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +22 -13
  3. package/dist/common/devtools/reduxDevtools.js +17 -2
  4. package/dist/common/react/index.d.ts +1 -0
  5. package/dist/common/react/index.js +1 -0
  6. package/dist/common/react/useIsomorphicLayoutEffect.d.ts +17 -0
  7. package/dist/common/react/useIsomorphicLayoutEffect.js +17 -0
  8. package/dist/index.d.ts +1 -2
  9. package/dist/index.js +1 -2
  10. package/dist/query/api/createApi.d.ts +4 -0
  11. package/dist/query/api/createApi.js +9 -0
  12. package/dist/query/api/index.d.ts +1 -0
  13. package/dist/query/api/index.js +1 -0
  14. package/dist/query/constants.d.ts +12 -0
  15. package/dist/query/constants.js +15 -0
  16. package/dist/query/core/api/Api.d.ts +20 -0
  17. package/dist/query/core/api/Api.js +129 -0
  18. package/dist/query/core/api/constants.d.ts +2 -0
  19. package/dist/query/core/api/constants.js +3 -0
  20. package/dist/query/core/api/index.d.ts +4 -0
  21. package/dist/query/core/api/index.js +4 -0
  22. package/dist/query/core/api/mergeHooks.d.ts +2 -0
  23. package/dist/query/core/api/mergeHooks.js +26 -0
  24. package/dist/query/core/api/normalizeLinks.d.ts +2 -0
  25. package/dist/query/core/api/normalizeLinks.js +11 -0
  26. package/dist/query/core/cache/CacheEntry.d.ts +21 -0
  27. package/dist/query/core/cache/CacheEntry.js +54 -0
  28. package/dist/query/core/cache/CacheMap.d.ts +19 -0
  29. package/dist/query/core/cache/CacheMap.js +32 -0
  30. package/dist/query/core/cache/QueryCacheEntry.d.ts +21 -0
  31. package/dist/query/core/cache/QueryCacheEntry.js +136 -0
  32. package/dist/query/core/cache/index.d.ts +3 -0
  33. package/dist/query/core/cache/index.js +3 -0
  34. package/dist/query/core/command/Command.d.ts +67 -0
  35. package/dist/query/core/command/Command.js +253 -0
  36. package/dist/query/core/command/CommandAgent.d.ts +17 -0
  37. package/dist/query/core/command/CommandAgent.js +67 -0
  38. package/dist/query/core/command/LinkManager.d.ts +24 -0
  39. package/dist/query/core/command/LinkManager.js +71 -0
  40. package/dist/query/core/command/index.d.ts +2 -0
  41. package/dist/query/core/command/index.js +2 -0
  42. package/dist/query/core/errors/CacheEntryRemovedError.d.ts +7 -0
  43. package/dist/query/core/errors/CacheEntryRemovedError.js +9 -0
  44. package/dist/query/core/errors/MachineStateError.d.ts +8 -0
  45. package/dist/query/core/errors/MachineStateError.js +10 -0
  46. package/dist/query/core/errors/MachineTransitionError.d.ts +7 -0
  47. package/dist/query/core/errors/MachineTransitionError.js +9 -0
  48. package/dist/query/core/errors/index.d.ts +3 -0
  49. package/dist/query/core/errors/index.js +3 -0
  50. package/dist/query/core/index.d.ts +8 -0
  51. package/dist/query/core/index.js +8 -0
  52. package/dist/query/core/machine/Machine.d.ts +21 -0
  53. package/dist/query/core/machine/Machine.js +42 -0
  54. package/dist/query/core/machine/MachineBase.d.ts +31 -0
  55. package/dist/query/core/machine/MachineBase.js +176 -0
  56. package/dist/query/core/machine/MachineError.d.ts +10 -0
  57. package/dist/query/core/machine/MachineError.js +19 -0
  58. package/dist/query/core/machine/MachinePending.d.ts +13 -0
  59. package/dist/query/core/machine/MachinePending.js +32 -0
  60. package/dist/query/core/machine/MachineRefreshError.d.ts +12 -0
  61. package/dist/query/core/machine/MachineRefreshError.js +23 -0
  62. package/dist/query/core/machine/MachineRefreshing.d.ts +15 -0
  63. package/dist/query/core/machine/MachineRefreshing.js +43 -0
  64. package/dist/query/core/machine/MachineSuccess.d.ts +12 -0
  65. package/dist/query/core/machine/MachineSuccess.js +23 -0
  66. package/dist/query/core/machine/MachineWithData.d.ts +25 -0
  67. package/dist/query/core/machine/MachineWithData.js +73 -0
  68. package/dist/query/core/machine/index.d.ts +9 -0
  69. package/dist/query/core/machine/index.js +9 -0
  70. package/dist/query/core/machine/machine-helpers.d.ts +9 -0
  71. package/dist/query/core/machine/machine-helpers.js +80 -0
  72. package/dist/query/core/patcher/Patcher.d.ts +30 -0
  73. package/dist/query/core/patcher/Patcher.js +122 -0
  74. package/dist/query/core/patcher/index.d.ts +1 -0
  75. package/dist/query/core/patcher/index.js +1 -0
  76. package/dist/query/core/resource/Resource.d.ts +105 -0
  77. package/dist/query/core/resource/Resource.js +340 -0
  78. package/dist/query/core/resource/ResourceAgent.d.ts +37 -0
  79. package/dist/query/core/resource/ResourceAgent.js +179 -0
  80. package/dist/query/core/resource/index.d.ts +2 -0
  81. package/dist/query/core/resource/index.js +2 -0
  82. package/dist/query/core/snapshoter/Snapshoter.d.ts +22 -0
  83. package/dist/query/core/snapshoter/Snapshoter.js +78 -0
  84. package/dist/query/core/snapshoter/index.d.ts +2 -0
  85. package/dist/query/core/snapshoter/index.js +1 -0
  86. package/dist/query/core/syncer/Syncer.d.ts +32 -0
  87. package/dist/query/core/syncer/Syncer.js +85 -0
  88. package/dist/query/core/syncer/index.d.ts +2 -0
  89. package/dist/query/core/syncer/index.js +1 -0
  90. package/dist/query/index.d.ts +5 -10
  91. package/dist/query/index.js +5 -13
  92. package/dist/query/lib/broadcastSyncDriver.d.ts +5 -0
  93. package/dist/query/lib/broadcastSyncDriver.js +46 -0
  94. package/dist/query/lib/index.d.ts +3 -0
  95. package/dist/query/lib/index.js +3 -0
  96. package/dist/{query-v2 → query}/lib/stableStringify.js +5 -3
  97. package/dist/query/lib/toKeyed.d.ts +11 -0
  98. package/dist/query/lib/toKeyed.js +18 -0
  99. package/dist/query/react/ReactHooksPlugin.d.ts +31 -0
  100. package/dist/query/react/ReactHooksPlugin.js +21 -0
  101. package/dist/query/react/index.d.ts +3 -0
  102. package/dist/query/react/index.js +3 -0
  103. package/dist/query/react/useCommand.d.ts +2 -0
  104. package/dist/query/react/useCommand.js +14 -0
  105. package/dist/query/react/useResource.d.ts +2 -0
  106. package/dist/query/react/useResource.js +16 -0
  107. package/dist/query/types/api.d.ts +39 -0
  108. package/dist/query/types/cache.d.ts +51 -0
  109. package/dist/query/types/command.d.ts +52 -0
  110. package/dist/query/types/common.d.ts +65 -0
  111. package/dist/query/types/index.d.ts +8 -4
  112. package/dist/query/types/index.js +8 -5
  113. package/dist/query/types/plugin-hkt.d.ts +64 -0
  114. package/dist/query/types/resource.d.ts +49 -0
  115. package/dist/query/types/snapshot.d.ts +27 -0
  116. package/dist/query/types/snapshot.js +2 -0
  117. package/dist/query/types/state.d.ts +24 -0
  118. package/dist/signals/base/ComputeCache.js +1 -1
  119. package/dist/signals/base/Devtools.js +2 -6
  120. package/dist/signals/signals/Computed.d.ts +1 -0
  121. package/dist/signals/signals/Computed.js +5 -1
  122. package/dist/signals/signals/Effect.d.ts +0 -4
  123. package/dist/signals/signals/Effect.js +0 -6
  124. package/dist/signals/signals/LocalState.d.ts +1 -10
  125. package/dist/signals/signals/LocalState.js +0 -12
  126. package/dist/signals/signals/Signal.d.ts +2 -8
  127. package/dist/signals/signals/Signal.js +1 -10
  128. package/dist/signals/signals/State.d.ts +2 -1
  129. package/dist/signals/signals/State.js +9 -0
  130. package/dist/signals/types/SignalOptions.d.ts +0 -2
  131. package/dist/signals/types/normalizeSignalOptions.js +0 -3
  132. package/dist/signals/types/signals.types.d.ts +3 -1
  133. package/docs/CHANGELOG.md +61 -31
  134. package/docs/migrations/0.6.0.md +224 -0
  135. package/docs/query/README.md +52 -562
  136. package/docs/query/api/README.md +59 -0
  137. package/docs/query/api/_CacheEntry.md +39 -0
  138. package/docs/query/api/_CacheMap.md +30 -0
  139. package/docs/query/api/_QueryCacheEntry.md +81 -0
  140. package/docs/query/api/command-agent.md +60 -0
  141. package/docs/query/api/command.md +76 -0
  142. package/docs/query/api/resource-agent.md +68 -0
  143. package/docs/query/api/resource.md +77 -0
  144. package/docs/query/concepts/agent.md +70 -0
  145. package/docs/query/concepts/architecture.md +139 -0
  146. package/docs/query/concepts/cache.md +81 -0
  147. package/docs/query/concepts/dataflows.md +473 -0
  148. package/docs/query/concepts/keyed.md +42 -0
  149. package/docs/query/concepts/machine.md +106 -0
  150. package/docs/query/concepts/patching.md +85 -0
  151. package/docs/query/usage/broadcast.md +188 -0
  152. package/docs/query/usage/command.md +203 -0
  153. package/docs/query/usage/lifecycle.md +114 -0
  154. package/docs/query/usage/links.md +125 -0
  155. package/docs/query/usage/plugins.md +96 -0
  156. package/docs/query/usage/resource.md +206 -0
  157. package/docs/query/usage/snapshot.md +80 -0
  158. package/docs/usage/react/README.md +45 -91
  159. package/package.json +6 -9
  160. package/dist/query/SKIP_TOKEN.d.ts +0 -1
  161. package/dist/query/SKIP_TOKEN.js +0 -1
  162. package/dist/query/api/createCommand.d.ts +0 -21
  163. package/dist/query/api/createCommand.js +0 -20
  164. package/dist/query/api/createOperation.d.ts +0 -5
  165. package/dist/query/api/createOperation.js +0 -6
  166. package/dist/query/api/createResource.d.ts +0 -3
  167. package/dist/query/api/createResource.js +0 -2
  168. package/dist/query/api/createResourceDuplicator.d.ts +0 -4
  169. package/dist/query/api/createResourceDuplicator.js +0 -2
  170. package/dist/query/api/resetAllQueriesCache.d.ts +0 -1
  171. package/dist/query/api/resetAllQueriesCache.js +0 -4
  172. package/dist/query/core/Command/Command.d.ts +0 -35
  173. package/dist/query/core/Command/Command.js +0 -210
  174. package/dist/query/core/Command/CommandAgent.d.ts +0 -19
  175. package/dist/query/core/Command/CommandAgent.js +0 -54
  176. package/dist/query/core/Command/index.d.ts +0 -2
  177. package/dist/query/core/Command/index.js +0 -2
  178. package/dist/query/core/Operation/Operation.d.ts +0 -8
  179. package/dist/query/core/Operation/Operation.js +0 -4
  180. package/dist/query/core/Operation/OperationAgent.d.ts +0 -4
  181. package/dist/query/core/Operation/OperationAgent.js +0 -4
  182. package/dist/query/core/QueriesCache.d.ts +0 -9
  183. package/dist/query/core/QueriesCache.js +0 -28
  184. package/dist/query/core/QueriesLifetimeHooks.d.ts +0 -22
  185. package/dist/query/core/QueriesLifetimeHooks.js +0 -86
  186. package/dist/query/core/ResetAllQueriesSignal.d.ts +0 -6
  187. package/dist/query/core/ResetAllQueriesSignal.js +0 -11
  188. package/dist/query/core/Resource/Resource.d.ts +0 -51
  189. package/dist/query/core/Resource/Resource.js +0 -232
  190. package/dist/query/core/Resource/ResourceAgent.d.ts +0 -35
  191. package/dist/query/core/Resource/ResourceAgent.js +0 -110
  192. package/dist/query/core/Resource/ResourceDuplicator.d.ts +0 -73
  193. package/dist/query/core/Resource/ResourceDuplicator.js +0 -227
  194. package/dist/query/core/Resource/ResourceDuplicatorAgent.d.ts +0 -35
  195. package/dist/query/core/Resource/ResourceDuplicatorAgent.js +0 -110
  196. package/dist/query/core/Resource/ResourceRef.d.ts +0 -16
  197. package/dist/query/core/Resource/ResourceRef.js +0 -136
  198. package/dist/query/lib/IndirectMap.d.ts +0 -19
  199. package/dist/query/lib/IndirectMap.js +0 -88
  200. package/dist/query/lib/ReactiveCache.d.ts +0 -62
  201. package/dist/query/lib/ReactiveCache.js +0 -80
  202. package/dist/query/react/useCommandAgent.d.ts +0 -24
  203. package/dist/query/react/useCommandAgent.js +0 -39
  204. package/dist/query/react/useOperationAgent.d.ts +0 -6
  205. package/dist/query/react/useOperationAgent.js +0 -6
  206. package/dist/query/react/useResourceAgent.d.ts +0 -6
  207. package/dist/query/react/useResourceAgent.js +0 -31
  208. package/dist/query/react/useResourceRef.d.ts +0 -5
  209. package/dist/query/react/useResourceRef.js +0 -13
  210. package/dist/query/types/Command.types.d.ts +0 -154
  211. package/dist/query/types/Command.types.js +0 -1
  212. package/dist/query/types/Operation.types.d.ts +0 -13
  213. package/dist/query/types/Operation.types.js +0 -1
  214. package/dist/query/types/Resource.types.d.ts +0 -129
  215. package/dist/query/types/Resource.types.js +0 -1
  216. package/dist/query/types/shared.types.d.ts +0 -26
  217. package/dist/query/types/shared.types.js +0 -1
  218. package/dist/query-v2/api/createApi.d.ts +0 -10
  219. package/dist/query-v2/api/createApi.js +0 -83
  220. package/dist/query-v2/core/common/CacheEntry.d.ts +0 -29
  221. package/dist/query-v2/core/common/CacheEntry.js +0 -71
  222. package/dist/query-v2/core/common/CacheMap.d.ts +0 -38
  223. package/dist/query-v2/core/common/CacheMap.js +0 -127
  224. package/dist/query-v2/core/common/LifecycleHooks.d.ts +0 -22
  225. package/dist/query-v2/core/common/LifecycleHooks.js +0 -104
  226. package/dist/query-v2/core/common/index.d.ts +0 -3
  227. package/dist/query-v2/core/common/index.js +0 -3
  228. package/dist/query-v2/core/index.d.ts +0 -3
  229. package/dist/query-v2/core/index.js +0 -3
  230. package/dist/query-v2/core/machines/Machine.d.ts +0 -14
  231. package/dist/query-v2/core/machines/Machine.js +0 -33
  232. package/dist/query-v2/core/machines/MachineError.d.ts +0 -11
  233. package/dist/query-v2/core/machines/MachineError.js +0 -26
  234. package/dist/query-v2/core/machines/MachineIdle.d.ts +0 -8
  235. package/dist/query-v2/core/machines/MachineIdle.js +0 -19
  236. package/dist/query-v2/core/machines/MachinePending.d.ts +0 -12
  237. package/dist/query-v2/core/machines/MachinePending.js +0 -29
  238. package/dist/query-v2/core/machines/MachineRefreshing.d.ts +0 -14
  239. package/dist/query-v2/core/machines/MachineRefreshing.js +0 -46
  240. package/dist/query-v2/core/machines/MachineSuccess.d.ts +0 -16
  241. package/dist/query-v2/core/machines/MachineSuccess.js +0 -42
  242. package/dist/query-v2/core/machines/MachineWithData.d.ts +0 -18
  243. package/dist/query-v2/core/machines/MachineWithData.js +0 -40
  244. package/dist/query-v2/core/machines/Patcher.d.ts +0 -20
  245. package/dist/query-v2/core/machines/Patcher.js +0 -104
  246. package/dist/query-v2/core/machines/index.d.ts +0 -8
  247. package/dist/query-v2/core/machines/index.js +0 -8
  248. package/dist/query-v2/core/resource/ResourceV2.d.ts +0 -120
  249. package/dist/query-v2/core/resource/ResourceV2.js +0 -464
  250. package/dist/query-v2/core/resource/ResourceV2Agent.d.ts +0 -26
  251. package/dist/query-v2/core/resource/ResourceV2Agent.js +0 -132
  252. package/dist/query-v2/core/resource/index.d.ts +0 -2
  253. package/dist/query-v2/core/resource/index.js +0 -2
  254. package/dist/query-v2/index.d.ts +0 -11
  255. package/dist/query-v2/index.js +0 -17
  256. package/dist/query-v2/lib/NO_VALUE.d.ts +0 -2
  257. package/dist/query-v2/lib/NO_VALUE.js +0 -1
  258. package/dist/query-v2/lib/SKIP_TOKEN.d.ts +0 -2
  259. package/dist/query-v2/lib/SKIP_TOKEN.js +0 -1
  260. package/dist/query-v2/lib/index.d.ts +0 -4
  261. package/dist/query-v2/lib/index.js +0 -3
  262. package/dist/query-v2/plugins/ReactHooksPlugin.d.ts +0 -25
  263. package/dist/query-v2/plugins/ReactHooksPlugin.js +0 -19
  264. package/dist/query-v2/plugins/types.d.ts +0 -1
  265. package/dist/query-v2/react/__tests__/helpers.d.ts +0 -12
  266. package/dist/query-v2/react/__tests__/helpers.js +0 -33
  267. package/dist/query-v2/react/index.d.ts +0 -2
  268. package/dist/query-v2/react/index.js +0 -2
  269. package/dist/query-v2/react/useResourceV2Agent.d.ts +0 -12
  270. package/dist/query-v2/react/useResourceV2Agent.js +0 -36
  271. package/dist/query-v2/react/useResourceV2Ref.d.ts +0 -12
  272. package/dist/query-v2/react/useResourceV2Ref.js +0 -57
  273. package/dist/query-v2/snapshot/Snapshot.d.ts +0 -13
  274. package/dist/query-v2/snapshot/Snapshot.js +0 -76
  275. package/dist/query-v2/types/agent.types.d.ts +0 -54
  276. package/dist/query-v2/types/api.types.d.ts +0 -22
  277. package/dist/query-v2/types/cache.types.d.ts +0 -37
  278. package/dist/query-v2/types/index.d.ts +0 -9
  279. package/dist/query-v2/types/index.js +0 -9
  280. package/dist/query-v2/types/lifecycle.types.d.ts +0 -25
  281. package/dist/query-v2/types/machine.types.d.ts +0 -67
  282. package/dist/query-v2/types/plugin.types.d.ts +0 -38
  283. package/dist/query-v2/types/resource.types.d.ts +0 -35
  284. package/dist/query-v2/types/resource.types.js +0 -1
  285. package/dist/query-v2/types/shared.types.d.ts +0 -20
  286. package/dist/query-v2/types/shared.types.js +0 -1
  287. package/dist/query-v2/types/snapshot.types.d.ts +0 -21
  288. package/dist/query-v2/types/snapshot.types.js +0 -1
  289. package/docs/contributing/query-v2/README.md +0 -379
  290. package/docs/migrations/query-v2.md +0 -171
  291. package/docs/query-v2/README.md +0 -280
  292. package/docs/query-v2/api-reference.md +0 -235
  293. package/docs/query-v2/optimistic-updates.md +0 -148
  294. package/docs/query-v2/ssr.md +0 -130
  295. /package/dist/{query-v2 → query}/lib/stableStringify.d.ts +0 -0
  296. /package/dist/{query-v2/plugins/types.js → query/types/api.js} +0 -0
  297. /package/dist/{query-v2/types/agent.types.js → query/types/cache.js} +0 -0
  298. /package/dist/{query-v2/types/api.types.js → query/types/command.js} +0 -0
  299. /package/dist/{query-v2/types/cache.types.js → query/types/common.js} +0 -0
  300. /package/dist/{query-v2/types/lifecycle.types.js → query/types/plugin-hkt.js} +0 -0
  301. /package/dist/{query-v2/types/machine.types.js → query/types/resource.js} +0 -0
  302. /package/dist/{query-v2/types/plugin.types.js → query/types/state.js} +0 -0
@@ -0,0 +1,85 @@
1
+ # Оптимистичные обновления (Patching)
2
+
3
+ Патч мгновенно применяет изменения к данным в [кэше][cache], не дожидаясь ответа сервера.
4
+ Пользователь видит результат сразу; при ошибке — данные откатываются.
5
+ Механизм основан на Immer.
6
+
7
+
8
+ ## Жизненный цикл патча
9
+
10
+ ```mermaid
11
+ stateDiagram-v2
12
+ [*] --> pending : queryCacheEntry.createPatch(patchFn)
13
+ pending --> committed : handle.commit()
14
+ pending --> aborted : handle.abort()
15
+ ```
16
+
17
+ - **pending** — патч создан, изменения отражены в `data`, но сервер ещё не подтвердил операцию.
18
+ - **committed** — сервер подтвердил; патч вливается в базовые данные при следующем ребейсе.
19
+ - **aborted** — операция отменена; `inversePatches` откатывают изменения.
20
+
21
+
22
+ ## patchState
23
+
24
+ Когда хотя бы один патч активен, в состоянии [машины][machine] появляется поле `patchState`:
25
+
26
+ | Поле | Описание |
27
+ |------|-----------------------------------------------------------------|
28
+ | `originalData` | Данные до применения первого патча |
29
+ | `patches[]` | Стек патчей |
30
+ | `isConsistencyViolation` | Флаг нарушения консистентности (см. ниже) |
31
+
32
+ Пока `patchState` присутствует: `data` = результат применения всех патчей, `originalData` = нетронутые серверные данные. Когда все патчи завершены — `patchState` сбрасывается в `null`.
33
+
34
+
35
+ ## Стек патчей
36
+
37
+ Каждый вызов `createPatch` добавляет новый патч в стек. `originalData` фиксируется при создании первого патча, но может изменяться, см "Поведение при аборте".
38
+ Immer-рецепты накладываются поверх текущего `data` (уже содержащего предыдущие патчи), а не поверх `originalData`.
39
+
40
+
41
+ ## Ребейс при обновлении
42
+
43
+ Когда сервер возвращает свежие данные (переход `refreshing → success`), патчи переигрываются на новой базе.
44
+ Если после ребейса pending-патчей не осталось — `patchState` очищается.
45
+
46
+
47
+ ## Поведение при аборте
48
+
49
+ Аборт требует применения обратного immer-патча к `originalData`.
50
+ Но при наличии множественных патчей, происходит переигрывание всех патчей поверх `originalData`.
51
+
52
+ **Для упрощения расчетов (при переигрывании)**:
53
+ - все независимые завершенные патчи (те все `committed` и `aborted`, которые были созданы до первого `pending`) - вливаются в `originalData` и удаляются из стека;
54
+ - все `pending` патчи и зависимые от них `committed` и `aborted` - переигрываются поверх обновленного `originalData`, результат сохраняется в `data`, эти патчи остаются в стеке;
55
+ - если не было `pending` патчей, то `patchState` очищается.
56
+
57
+ В результате переигрывания, может возникнуть нарушение консистентности (см. ниже).
58
+
59
+ ## Нарушение консистентности
60
+
61
+ Если при ребейсе или аборте нарушится порядок зависимых патчей (immer не может разрешить изменения) — выставляется `isConsistencyViolation = true`.
62
+ В этом случае стек патчей очищается (`data` остается пропатченой) и запускается автоматическая инвалидация.
63
+
64
+
65
+ ## Декларативный API
66
+
67
+ С типичным сценарием «мутация + оптимистичное обновление» описано в руководстве по [связям][links].
68
+
69
+
70
+ ## См. также
71
+
72
+ - [Машина состояний][machine] — патчи применяются к состояниям `success`, `refreshing`, `refresh-error`.
73
+ - [Кэш][cache] — запись кэша хранит `patchState` и предоставляет `createPatch`.
74
+ - [Broadcast][broadcast] — кросс-табовая синхронизация, включая передачу патчей.
75
+ - [Использование ресурсов][usage-res] — оптимистичные обновления в контексте ресурса.
76
+ - [Использование команд][usage-cmd] — оптимистичные обновления в контексте команды.
77
+ - [Связи (links)][links] — декларативный `optimisticUpdate` через `link()`.
78
+
79
+
80
+ [machine]: machine.md
81
+ [cache]: cache.md
82
+ [broadcast]: ../usage/broadcast.md
83
+ [usage-res]: ../usage/resource.md
84
+ [usage-cmd]: ../usage/command.md
85
+ [links]: ../usage/links.md
@@ -0,0 +1,188 @@
1
+ # Кросс-табовая синхронизация (Broadcast)
2
+
3
+ Кросс-табовая синхронизация позволяет нескольким вкладкам браузера разделять состояние кэша.
4
+ Когда вкладке нужны данные, она перед выполнением `queryFn` отправляет запрос (REQ) через `syncDriver`;
5
+ если другая вкладка уже располагает данными, она отвечает (RES) — и сетевой запрос не выполняется.
6
+ Внутри это реализовано через хук `beforeQuery`, который `createApi` автоматически внедряет в каждый ресурс с включённой синхронизацией:
7
+ непосредственно перед вызовом `queryFn` хук отправляет REQ через `syncDriver` и, получив RES, возвращает данные из другой вкладки, минуя сеть.
8
+ Синхронизация управляется через `syncDriver` — опцию `createApi`,
9
+ принимающую реализацию интерфейса `ISyncDriver`.
10
+
11
+
12
+ ## Подключение syncDriver
13
+
14
+ Опция `syncDriver` задаётся при вызове `createApi`:
15
+
16
+ ```typescript
17
+ import { createApi, broadcastSyncDriver } from '@fozy-labs/rx-toolkit';
18
+
19
+ const api = createApi({
20
+ keyPrefix: 'my-api',
21
+ defaultSync: 'resources',
22
+ syncDriver: broadcastSyncDriver(),
23
+ });
24
+ ```
25
+
26
+
27
+ ## broadcastSyncDriver
28
+
29
+ Встроенная реализация `ISyncDriver` на базе [BroadcastChannel API][broadcast-channel].
30
+
31
+ | Параметр | Тип | По умолчанию | Описание |
32
+ |-----------|----------|-----------------------------|--------------------------------------------------------------------|
33
+ | `channel` | `string` | `"rx-toolkit:{keyPrefix}"` | Имя `BroadcastChannel`. Если не указано, генерируется из `keyPrefix` API. |
34
+
35
+ ```typescript
36
+ // Канал по умолчанию — "rx-toolkit:my-api"
37
+ const api = createApi({
38
+ keyPrefix: 'my-api',
39
+ syncDriver: broadcastSyncDriver(),
40
+ });
41
+
42
+ // Явное имя канала
43
+ const api = createApi({
44
+ keyPrefix: 'my-api',
45
+ syncDriver: broadcastSyncDriver({ channel: 'shared-state' }),
46
+ });
47
+ ```
48
+
49
+
50
+ ## Управление синхронизацией
51
+
52
+ Опции определяющие, участвует ли ресурс или команда в синхронизации.
53
+
54
+ | Сущность | Опция | Принимаемое значение | По умолчанию |
55
+ |-------------|---------------|--------------------------------|---------------------|
56
+ | **api** | `defaultSync` | `resources` \| `all` \| `none` | `none` |
57
+ | **Ресурс** | `sync` | `boolean` | **api defaultSync** |
58
+ | **Команда** | `sync` | `boolean` | **api defaultSync** |
59
+
60
+ > Если `syncDriver` не задан в `createApi`, то синхронизация работать не будет.
61
+
62
+ ```typescript
63
+ const getCatalog = api.createResource({
64
+ key: 'catalog',
65
+ queryFn: fetchCatalog,
66
+ sync: true,
67
+ });
68
+
69
+ // Приватные данные пользователя — отключаем sync
70
+ const getProfile = api.createResource({
71
+ key: 'profile',
72
+ queryFn: fetchProfile,
73
+ sync: false,
74
+ });
75
+
76
+ // Мутация, результат которой нужен всем вкладкам
77
+ const markRead = api.createCommand({
78
+ key: 'markRead',
79
+ queryFn: markNotificationRead,
80
+ sync: true,
81
+ });
82
+ ```
83
+
84
+
85
+ ## Что синхронизируется
86
+
87
+ Когда вкладка получает REQ и запись находится в одном из состояний ниже,
88
+ она отвечает RES с соответствующими данными:
89
+
90
+ | Состояние [машины][machine] | Данные в RES |
91
+ |-----------------------------|----------------|
92
+ | `pending` | — |
93
+ | `success` | `data` |
94
+ | `success` (с патчами) | `originalData` |
95
+ | `error` | — |
96
+ | `refreshing` | — |
97
+ | `refresh-error` | — |
98
+
99
+
100
+ ## Кастомный syncDriver
101
+
102
+ `ISyncDriver` — транспортно-агностичный контракт.
103
+ Встроенная реализация — `broadcastSyncDriver`, но можно создать свою (WebSocket, SharedWorker и т. д.).
104
+
105
+
106
+ ### ISyncDriver
107
+
108
+ | Метод | Сигнатура | Описание |
109
+ |--------------|--------------------------------------------------|-------------------------------------------------------------------------|
110
+ | `connect` | `(onMessage: (msg: ISyncMessage) => void) => void` | Подключиться к каналу. `onMessage` вызывается при получении внешних сообщений |
111
+ | `disconnect` | `() => void` | Отключиться от канала и освободить ресурсы |
112
+ | `send` | `(message: ISyncMessage) => void` | Отправить сообщение |
113
+
114
+
115
+ ### ISyncMessage
116
+
117
+ | Поле | Тип | Описание |
118
+ |-------------|----------------------------------|-------------------------------------------|
119
+ | `type` | `REQ` \| `RES` | Тип сообщения: запрос, ответ или ошибка |
120
+ | `reqId` | `string` | Идентификатор запроса (для связи REQ-RES) |
121
+ | `keys` | `[<prefix>, <key>, <entry_key>]` | Ключи |
122
+ | `data` | `any` | Данные (для RES) |
123
+
124
+
125
+ ## Полный пример
126
+
127
+ Пример демонстрирует `createApi` с `broadcastSyncDriver`,
128
+ ресурс и команду со связями — и поведение на двух вкладках.
129
+
130
+ ```typescript
131
+ import { createApi, broadcastSyncDriver, reactHooksPlugin } from '@fozy-labs/rx-toolkit';
132
+
133
+ const api = createApi({
134
+ keyPrefix: 'main-api',
135
+ syncDriver: broadcastSyncDriver(),
136
+ plugins: [reactHooksPlugin()],
137
+ defaultSync: 'resources', // по умолчанию синхронизировать только ресурсы
138
+ });
139
+
140
+ // Ресурс — sync: true (наследуется от defaultSync: 'resources')
141
+ const todosResource = api.createResource({
142
+ key: 'todos',
143
+ queryFn: async () => {
144
+ const res = await fetch('/api/todos');
145
+ return res.json();
146
+ },
147
+ });
148
+ ```
149
+
150
+ ```tsx
151
+ function TodoApp() {
152
+ const { data: todos, isLoading } = todosResource.useResource();
153
+
154
+ if (isLoading) return <p>Загрузка...</p>;
155
+
156
+ return (
157
+ <div>
158
+ <ul>
159
+ {todos.map((t: any) => <li key={t.id}>{t.text}</li>)}
160
+ </ul>
161
+ <button disabled={isSaving} onClick={() => trigger({ text: 'Новая задача' })}>
162
+ Добавить
163
+ </button>
164
+ </div>
165
+ );
166
+ }
167
+ ```
168
+
169
+
170
+ ## См. также
171
+
172
+ - [API-справочник][api-readme] — полная таблица опций `createApi`
173
+ - [Связи (Links)][links] — декларативное соединение команд и ресурсов
174
+ - [Патчинг][patching] — механизм оптимистичных обновлений и отката
175
+ - [Ресурс (API)][api-resource] — опции ресурса, включая `sync`
176
+ - [Кэш][cache] — управление кэш-записями и их жизненным циклом
177
+ - [Потоки данных][dataflows] — диаграммы кросс-табовой синхронизации
178
+
179
+
180
+
181
+ [links]: ./links.md
182
+ [api-readme]: ../api/README.md
183
+ [api-resource]: ../api/resource.md
184
+ [cache]: ../concepts/cache.md
185
+ [dataflows]: ../concepts/dataflows.md
186
+ [machine]: ../concepts/machine.md
187
+ [patching]: ../concepts/patching.md
188
+ [broadcast-channel]: https://developer.mozilla.org/en-US/docs/Web/API/BroadcastChannel
@@ -0,0 +1,203 @@
1
+ # Команда (Command)
2
+
3
+ Команда — абстракция для **операций записи** (мутаций): создание, обновление, удаление данных. Для чтения данных используйте [ресурс][resource].
4
+
5
+ Аналог: `useMutation` в TanStack Query, `mutation endpoint` в RTK Query.
6
+
7
+
8
+ ## Создание команды
9
+
10
+ ```typescript
11
+ const addTodoCommand = api.createCommand({
12
+ queryFn: async (args: { text: string }) => {
13
+ const res = await fetch('/api/todos', {
14
+ method: 'POST',
15
+ headers: { 'Content-Type': 'application/json' },
16
+ body: JSON.stringify(args),
17
+ });
18
+ return res.json();
19
+ },
20
+ links: (link) => link({
21
+ resource: todosResource,
22
+ forwardArgs: () => undefined,
23
+ invalidate: true,
24
+ }),
25
+ });
26
+ ```
27
+
28
+ `queryFn` — единственная обязательная опция. Принимает аргументы мутации, возвращает промис с данными.
29
+ `links` — колбэк, описывающий связи с ресурсами, которые нужно обновить после выполнения команды.
30
+
31
+
32
+ ## Опции
33
+
34
+ Полный список опций — см. [API-справочник команды][api-command].
35
+
36
+
37
+ ## API команды
38
+
39
+ Полный список методов — см. [API-справочник команды][api-command].
40
+
41
+
42
+ ## React: useCommand
43
+
44
+ Для работы в React подключите `reactHooksPlugin()` при создании API:
45
+
46
+ ```typescript
47
+ import { createApi, reactHooksPlugin } from '@fozy-labs/rx-toolkit';
48
+
49
+ const api = createApi({
50
+ plugins: [reactHooksPlugin()],
51
+ });
52
+ ```
53
+
54
+ `useCommand` — метод на экземпляре команды, доступный после подключения плагина:
55
+
56
+ ```tsx
57
+ function AddTodoForm() {
58
+ const [trigger, { data, error, isLoading }] = addTodoCommand.useCommand();
59
+ const [text, setText] = React.useState('');
60
+
61
+ const handleSubmit = async (e: React.FormEvent) => {
62
+ e.preventDefault();
63
+ if (!text.trim()) return;
64
+ await trigger({ text });
65
+ setText('');
66
+ };
67
+
68
+ return (
69
+ <form onSubmit={handleSubmit}>
70
+ <input value={text} onChange={e => setText(e.target.value)} disabled={isLoading} />
71
+ <button disabled={isLoading}>Добавить</button>
72
+ {error && <p>Ошибка: {String(error)}</p>}
73
+ </form>
74
+ );
75
+ }
76
+ ```
77
+
78
+ Поведение хука:
79
+
80
+ 1. Хук не запускает запрос при монтировании — мутация выполняется только при вызове `trigger`.
81
+ 2. `trigger(args)` запускает `queryFn` и возвращает `Promise<TData>`.
82
+ 3. Состояние (`isLoading`, `isSuccess`, `isError`) обновляется реактивно.
83
+
84
+ ## Состояния команды
85
+
86
+ `useCommand` возвращает `[trigger, state]`, где `state` содержит:
87
+
88
+ | Поле | Тип | Описание |
89
+ |---|---|---|
90
+ | `status` | `string` | `'idle'` · `'pending'` · `'success'` · `'error'` |
91
+ | `data` | `TData \| null` | Данные последнего успешного ответа. |
92
+ | `error` | `unknown` | Ошибка последнего запроса. |
93
+ | `isLoading` | `boolean` | `true` при выполнении мутации. |
94
+ | `isSuccess` | `boolean` | `true` когда мутация завершилась успешно. |
95
+ | `isError` | `boolean` | `true` при ошибке мутации. |
96
+
97
+
98
+ ## Императивный API
99
+
100
+ ### trigger
101
+
102
+ ```typescript
103
+ // Без ключа — создаётся автоматическая кэш-запись
104
+ const data = await addTodoCommand.trigger({ text: 'Новая задача' });
105
+
106
+ // С явным ключом — привязывает результат к кэш-записи 'my-mutation-1'
107
+ const data = await addTodoCommand.trigger({ text: 'Новая задача' }, 'my-mutation-1');
108
+ ```
109
+
110
+ Запускает `queryFn` и возвращает промис с результатом. Необязательный второй аргумент `key` идентифицирует кэш-запись.
111
+
112
+ ### getEntry
113
+
114
+ Синхронно возвращает кэш-запись для указанного ключа, или `null` если записи нет.
115
+
116
+ ```typescript
117
+ const entry = addTodoCommand.getEntry('my-mutation-1');
118
+ if (entry) {
119
+ console.log(entry.machine$().data);
120
+ }
121
+ ```
122
+
123
+ ### getEntry$
124
+
125
+ Реактивный аналог `getEntry`. Вызывает сигнал внутри, поэтому должен использоваться в реактивном контексте (`Signal.compute`, `Signal.effect` и т. д.). Возвращает кэш-запись или `null`.
126
+
127
+ ```ts
128
+ const entry$ = Signal.compute(() => addTodoCommand.getEntry$('my-mutation-1'));
129
+ ```
130
+
131
+ ### createAgent
132
+
133
+ Создаёт агент — реактивный наблюдатель за командой. Принимает опциональный `key` для привязки к конкретной кэш-записи.
134
+ Полная таблица методов и статусов — в [API агента команды][api-cmd-agent].
135
+
136
+ ```typescript
137
+ const agent = addTodoCommand.createAgent('my-mutation-1');
138
+
139
+ // trigger через агент
140
+ agent.trigger({ text: 'New todo' });
141
+ // agent.state$() → { status: "pending", data: null, isLoading: true, ... }
142
+ ```
143
+
144
+
145
+ ## Кэш-ключ команды
146
+
147
+ Кэш-ключ команды — это строка.
148
+ По умолчанию ключ генерируется автоматически (таймстамп + индекс при нескольких вызовах в одном таймстампе),
149
+ поэтому каждый вызов создаёт отдельную кэш-запись.
150
+
151
+ Способ указания ключа зависит от API:
152
+
153
+ - **Императивно** — ключ передаётся вторым аргументом в метод `trigger`:
154
+
155
+ ```typescript
156
+ const data = await addTodoCommand.trigger({ text: 'Задача' }, 'my-mutation-1');
157
+ ```
158
+
159
+ - **React-хук** — ключ задаётся на уровне `useCommand`, а функция `trigger` вызывается только с `args`:
160
+
161
+ ```tsx
162
+ const [trigger, state] = addTodoCommand.useCommand('my-mutation-1');
163
+ await trigger({ text: 'Задача' });
164
+ ```
165
+
166
+ - **Агент** — ключ передаётся в `createAgent` и может меняться с помощью методов `trigger` или `setKey`:
167
+
168
+ ```typescript
169
+ const agent = addTodoCommand.createAgent('my-mutation-1');
170
+ agent.trigger({ text: 'Задача' }, 'my-mutation-2');
171
+ agent.setKey('my-mutation-3');
172
+ ```
173
+
174
+ Разные потребители могут синхронизировать состояние, используя один и тот же ключ.
175
+
176
+
177
+ ## Связи (Links)
178
+
179
+ Связи позволяют декларативно связать команду с ресурсами — подробнее в [руководстве по связям][links].
180
+
181
+
182
+ ## Хуки жизненного цикла
183
+
184
+ Хуки позволяют реагировать на события кэша — подробнее в [руководстве по жизненному циклу][lifecycle].
185
+
186
+
187
+ ## См. также
188
+
189
+ - [Ресурс][resource] — чтение данных с кэшированием и SWR
190
+ - [Машина состояний][machine] — детали переходов между статусами
191
+ - [Система кэширования][cache] — жизненный цикл записей кэша
192
+ - [Агент][agent] — реактивный наблюдатель, транслирующий состояние в UI
193
+ - [Broadcast][broadcast] — синхронизация между вкладками; команды поддерживают опцию `sync: true`
194
+
195
+ [resource]: ./resource.md
196
+ [machine]: ../concepts/machine.md
197
+ [cache]: ../concepts/cache.md
198
+ [agent]: ../concepts/agent.md
199
+ [broadcast]: ./broadcast.md
200
+ [api-command]: ../api/command.md
201
+ [api-cmd-agent]: ../api/command-agent.md
202
+ [lifecycle]: ./lifecycle.md
203
+ [links]: ./links.md
@@ -0,0 +1,114 @@
1
+ # Хуки жизненного цикла (Lifecycle Hooks)
2
+
3
+ Хуки жизненного цикла позволяют реагировать на события кэш-записей.
4
+ Доступны как для [ресурсов][resource], так и для [команд][command].
5
+ Полный список параметров — в API-справочнике ([ресурс][api-resource],
6
+ [команда][api-command]).
7
+
8
+
9
+ ## onCacheEntryAdded
10
+
11
+ Вызывается **один раз** при создании кэш-записи для конкретных аргументов.
12
+ Колбэк получает `args` и объект контекста `ctx`:
13
+
14
+ | Свойство ctx | Тип | Описание |
15
+ |---|-------------------------------------|---------------------------------------------------------------------------------------------------------|
16
+ | `entry` | `CacheEntry` (ресурса или команды) | Текущая кэш-запись. |
17
+ | `$cacheDataLoaded` | `Promise<TData>` | Разрешается при первом успешном ответе. Отклоняется, если запись удалена до успешного получения данных. |
18
+ | `$cacheEntryRemoved` | `Promise<void>` | Разрешается при удалении записи из кэша. |
19
+
20
+
21
+ ```typescript
22
+ const messagesResource = api.createResource({
23
+ queryFn: (chatId: string) => fetch(`/api/chats/${chatId}/messages`).then(r => r.json()),
24
+ onCacheEntryAdded: async (chatId, { $cacheDataLoaded, $cacheEntryRemoved }) => {
25
+ console.log('Cache entry added for chatId:', chatId);
26
+
27
+ await $cacheDataLoaded;
28
+
29
+ console.log('Initial data loaded for chatId:', chatId);
30
+
31
+ await $cacheEntryRemoved;
32
+
33
+ console.log('Cache entry removed for chatId:', chatId);
34
+ }
35
+ });
36
+ ```
37
+
38
+ Хук работает аналогично и для [команд][command].
39
+
40
+
41
+ ## onQueryStarted
42
+
43
+ Функция, которая вызывается при запуске каждого отдельного запроса.
44
+ Позволяет выполнять код на протяжении всего жизненного цикла отдельного запроса.
45
+
46
+ Колбэк получает `args` и объект контекста `ctx`:
47
+
48
+ | Свойство ctx | Тип | Описание |
49
+ |---|-------------------------------------|----------------------------------------------------------------------|
50
+ | `entry` | `CacheEntry` (ресурса или команды) | Текущая кэш-запись. |
51
+ | `$queryFulfilled` | `Promise<{ data: TData }>` | Разрешается с данными при успехе. Отклоняется при ошибке или аборте. |
52
+
53
+ ```typescript
54
+ const userResource = api.createResource({
55
+ queryFn: (id: number) => fetch(`/api/users/${id}`).then(r => r.json()),
56
+ onQueryStarted: async (args, { $queryFulfilled }) => {
57
+ console.log('Query started with args:', args);
58
+
59
+ const { data } = await $queryFulfilled;
60
+
61
+ console.log('Query succeeded with data:', data);
62
+ },
63
+ });
64
+ ```
65
+
66
+ Хук работает аналогично и для [команд][command].
67
+
68
+
69
+ ## Обработка ошибок
70
+
71
+ Ошибки внутри колбэков **подавляются автоматически** — не выбрасываются наружу и не влияют на основной поток запроса.
72
+ При необходимости (например, для предотвращения утечек памяти) оборачивайте `$queryFulfilled` и `$cacheDataLoaded` в `try/catch`:
73
+
74
+ ```typescript
75
+ onCacheEntryAdded: async (id, { $cacheDataLoaded, entry }) => {
76
+ try {
77
+ const connection = createUserConnection(id);
78
+ const { data } = await $cacheDataLoaded;
79
+ } catch {
80
+ connection.close('unused');
81
+ return;
82
+ }
83
+
84
+ connection.onUserUpdated((partialUser) => {
85
+ const patch = entry.patch((draft) => {
86
+ Object.assign(draft, partialUser);
87
+ });
88
+
89
+ path.commit();
90
+ });
91
+
92
+ await $cacheEntryRemoved;
93
+
94
+ connection.close('disposed');
95
+ },
96
+ ```
97
+
98
+
99
+ ## См. также
100
+
101
+ - [Кэш][cache] — управление кэш-записями и их жизненным циклом
102
+ - [Ресурс][resource] — чтение данных и кэширование
103
+ - [Команда][command] — мутации и побочные эффекты
104
+ - [Потоки данных][dataflows] — диаграммы жизненного цикла запросов
105
+ - [API: ресурс][api-resource] — полная таблица параметров ресурса
106
+ - [API: команда][api-command] — полная таблица параметров команды
107
+
108
+
109
+ [cache]: ../concepts/cache.md
110
+ [resource]: ./resource.md
111
+ [command]: ./command.md
112
+ [dataflows]: ../concepts/dataflows.md
113
+ [api-resource]: ../api/resource.md
114
+ [api-command]: ../api/command.md