@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
@@ -0,0 +1,230 @@
1
+ # Contributing
2
+
3
+ Руководство для контрибьюторов проекта **@fozy-labs/rx-toolkit**.
4
+
5
+ ## Содержание
6
+
7
+ - [Contributing](#contributing)
8
+ - [Содержание](#содержание)
9
+ - [Быстрый старт](#быстрый-старт)
10
+ - [Структура проекта](#структура-проекта)
11
+ - [Разработка](#разработка)
12
+ - [Исходный код (`src/`)](#исходный-код-src)
13
+ - [Интерактивные примеры (`apps/demos/`)](#интерактивные-примеры-appsdemos)
14
+ - [Документация (`docs/`)](#документация-docs)
15
+ - [Тесты](#тесты)
16
+ - [Инструменты разработки](#инструменты-разработки)
17
+ - [Соглашения](#соглашения)
18
+ - [Именование файлов](#именование-файлов)
19
+ - [Протокол сигналов](#протокол-сигналов)
20
+ - [Код и документация](#код-и-документация)
21
+ - [Коммиты](#коммиты)
22
+ - [CHANGELOG](#changelog)
23
+ - [index.ts](#indexts)
24
+ - [AI-assisted разработка](#ai-assisted-разработка)
25
+ - [Релиз](#релиз)
26
+
27
+
28
+ ## Быстрый старт
29
+
30
+ ```bash
31
+ # Клонирование
32
+ git clone https://github.com/fozy-labs/rx-toolkit.git
33
+ cd rx-toolkit
34
+
35
+ # Установка зависимостей
36
+ npm install
37
+
38
+ # Проверка типов
39
+ npm run ts-check
40
+
41
+ # Запуск тестов
42
+ npm run test
43
+
44
+ # Сборка
45
+ npm run build
46
+ ```
47
+
48
+
49
+ ## Структура проекта
50
+
51
+ ```
52
+ rx-toolkit/
53
+ ├── .github/ # AI-промпты, инструкции, скиллы
54
+ ├── src/ # Исходный код библиотеки
55
+ │ ├── signals/ # Реактивные примитивы (Signal, Computed, Effect)
56
+ │ ├── query/ # Кеш-менеджер (Resource, Command)
57
+ │ └── common/ # Утилиты, devtools, React-хуки
58
+ ├── apps/
59
+ │ └── demos/ # Интерактивные примеры (React + Vite + MDX)
60
+ ├── docs/ # Документация
61
+ └── dist/ # Результат сборки (не коммитится)
62
+ ```
63
+
64
+
65
+ ## Разработка
66
+
67
+ ### Исходный код (`src/`)
68
+
69
+ Библиотека состоит из трёх "модулей":
70
+
71
+ | Модуль | Путь | Описание |
72
+ |--------|------|---------------------------------------------------------------------|
73
+ | **Signals** | `src/signals/` | Реактивные примитивы: `State`, `Computed`, `Effect`, операторы и тд |
74
+ | **Query** | `src/query/` | Кеш-менеджер: `Resource`, `Command`, агенты, `SKIP_TOKEN` и тд |
75
+ | **Common** | `src/common/` | Общие утилиты, интеграция с DevTools, React-хуки и тд |
76
+
77
+ > **Алиас путей:** `@/` → `src/`.
78
+
79
+
80
+ ### Интерактивные примеры (`apps/demos/`)
81
+
82
+ Демо-приложение на **React 19 + Vite + MDX + Tailwind CSS + HeroUI**.
83
+ Примеры можно запускать и редактировать прямо в браузере благодаря `react-live`.
84
+
85
+ ```bash
86
+ cd apps/demos
87
+ npm install
88
+ npm run dev # http://localhost:3000
89
+ ```
90
+
91
+
92
+ **Структура примеров:**
93
+
94
+ ```
95
+ apps/demos/src/
96
+ ├── pages/ # MDX-страницы (SignalsPage, QueriesPage, HomePage)
97
+ ├── examples/
98
+ │ ├── signals/ # Примеры для сигналов
99
+ │ └── query/ # Примеры для query
100
+ ├── components/ # LiveExample, QueryTabs и другие компоненты
101
+ └── utils/ # Утилиты для fetch-запросов
102
+ ```
103
+
104
+ > При изменении кода в `src/` рассмотрите необходимость добавления интерактивного примера в `apps/demos/`.
105
+
106
+
107
+ ### Документация (`docs/`)
108
+
109
+ Документация на **русском языке**:
110
+
111
+ | Файл | Содержание |
112
+ |------------------------|--------------------------------|
113
+ | `docs/signals/` | Реактивные примитивы |
114
+ | `docs/query/` | Query кеш-менеджер |
115
+ | `docs/usage/react/` | React-хуки |
116
+ | `docs/devtools/` | Интеграция с Redux DevTools |
117
+ | `docs/options/` | Глобальные настройки |
118
+ | `docs/migrations/` | Гайды миграции между версиями |
119
+ | `docs/contributing/` | Руководства для контрибьюторов |
120
+ | `docs/CHANGELOG.md` | История изменений |
121
+ | `docs/CONTRIBUTING.md` | Руководство для контрибьюторов |
122
+
123
+ > При изменении кода в `src/` рассмотрите необходимость обновления соответствующей документации в `docs/`.
124
+
125
+
126
+ ## Тесты
127
+
128
+ Используется **Vitest** с окружением `jsdom`.
129
+
130
+ ```bash
131
+ npm run test # Однократный запуск
132
+ npm run test:watch # Watch-режим
133
+ npm run test:coverage # Отчёт о покрытии
134
+ npm run test:ui # Vitest UI в браузере
135
+ ```
136
+
137
+ - Тесты размещаются рядом с кодом: `MyModule.test.ts`
138
+ - Интеграционные — в `src/__tests__/integration/`
139
+
140
+
141
+ ## Инструменты разработки
142
+
143
+ ### Команды
144
+
145
+ ```bash
146
+ npm run lint # Проверка линтером (ESLint)
147
+ npm run lint:fix # Автоисправление ошибок линтера
148
+ npm run format # Форматирование кода (Prettier)
149
+ npm run format:check # Проверка форматирования без изменений
150
+ ```
151
+
152
+ > `apps/demos/` имеет отдельную конфигурацию ESLint: `cd apps/demos && npx eslint src/`
153
+
154
+ ### Настройка редактора
155
+
156
+ Рекомендуемые расширения VS Code:
157
+ - **Prettier** (`esbenp.prettier-vscode`)
158
+ - **ESLint** (`dbaeumer.vscode-eslint`)
159
+
160
+ Включите `editor.formatOnSave: true` для автоматического форматирования при сохранении.
161
+
162
+ ### Git blame
163
+
164
+ Файл `.git-blame-ignore-revs` исключает коммиты массового форматирования из `git blame`. GitHub учитывает его автоматически. Для локальной настройки:
165
+
166
+ ```bash
167
+ git config blame.ignoreRevsFile .git-blame-ignore-revs
168
+ ```
169
+
170
+
171
+ ## Соглашения
172
+
173
+ ### Именование файлов
174
+
175
+ - Классы/типы — **PascalCase**: `Signal.ts`, `ReadonlySignal.ts`
176
+ - Фабрики/утилиты — **camelCase**: `createResource.ts`, `deepEqual.ts`
177
+ - Типы — суффиксы: `XDefinition`, `XInstance` и тд
178
+
179
+
180
+ ### Протокол сигналов
181
+
182
+ ```typescript
183
+ signal() // или signal.get()
184
+ signal.peek() // получить без подписки
185
+ signal.set(v) // установить значение
186
+ signal.obs // RxJS Observable
187
+ ```
188
+
189
+
190
+ ### Код и документация
191
+
192
+ - Код и комментарии в коде — **на английском**
193
+ - Документация (`docs/`) — **на русском**
194
+ - AI кастомизация (`.github/`) — **на английском**
195
+
196
+
197
+ ### Коммиты
198
+
199
+ Используются [Conventional Commits](https://www.conventionalcommits.org/), но со следующими адоптациями:
200
+ - `chore(..)` (вместо `docs(..)`) - при настройке AI окружения (промпты, инструкции, скиллы и тд)
201
+ - `thoughts(..)` (вместо `docs(..)`) - коммиты сгенерированные AI при работе над `.thoughts`
202
+
203
+
204
+ ### CHANGELOG
205
+
206
+ Используется формат [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
207
+
208
+ ### index.ts
209
+
210
+ - `src/index.ts` — единственная точка экспорта публичного API.
211
+ - `<module>/index.ts` — точка экспорта для конкретного "модуля".
212
+
213
+
214
+ ## AI-assisted разработка
215
+
216
+ Проект настроен для работы с **GitHub Copilot**.
217
+
218
+ [//]: # (For humans only guide:)
219
+ Описание подхода: [docs/contributing/ai-assisted-development.md](contributing/ai-assisted-development.md).
220
+
221
+
222
+ ## Релиз
223
+
224
+ Релизы делятся на:
225
+ - **RC** — не стабильные релизы
226
+ - **Stable** — стабильные релизы
227
+
228
+ [//]: # (For humans only guide:)
229
+ Инструкция по выпуску описана тут [docs/contributing/release/README.md](contributing/release/README.md).
230
+
@@ -0,0 +1,47 @@
1
+
2
+ ## AI-assisted разработка
3
+
4
+ Проект настроен для работы с **GitHub Copilot**.
5
+
6
+ Ниже описаны файлы, которые автоматически подключаются к AI-ассистенту.
7
+
8
+ ### Файлы конфигурации
9
+
10
+ | Файл | Назначение |
11
+ |-----------------------------------|------------------------------------------------|
12
+ | `.github/skills/` | Скиллы |
13
+ | `.github/instructions/` | Правила для работы с определенными каталогами. |
14
+ | `.github/prompts/` | Готовые промпты (комманды) |
15
+ | `.github/copilot-instructions.md` | Основные правила проекта. Загружается всегда. |
16
+
17
+ ### Workflow разработки фичей
18
+
19
+ Для фич используется поэтапный AI-workflow:
20
+
21
+ ```
22
+ Research → [review] → Design → [review] → Plan → [review] → Implement → [review]
23
+ ```
24
+
25
+ Артефакты каждой фазы сохраняются в:
26
+
27
+ ```
28
+ .thoughts/<date>_<feature-name>/
29
+ ├── 01-research/
30
+ ├── 02-design/
31
+ ├── 03-plan/
32
+ └── 04-implement/
33
+ ```
34
+
35
+
36
+ ### Как начать
37
+
38
+ 1. Для запуска полного workflow фичи — используйте промпт `00-compined.prompt.md`
39
+ 2. Для отдельных фаз — соответствующий промпт (`01-research`, `02-design`, и т.д.)
40
+
41
+ #### 00-compined.prompt.md
42
+
43
+ - Агент выполнит все этапы в одной петле, время на ревью составит - до 25 минут.
44
+ - При старте или по завершению ревью - измените статус в `README.md` этавпа на один из:
45
+ - Review (в процессе)
46
+ - Approved (принято)
47
+ - Redraft (есть замечания, нужно доработать) - создайте и опишите замечания в файле `REVEIW.md`
@@ -0,0 +1,379 @@
1
+
2
+ # Query v2 RFC
3
+
4
+ Цель Query v2 — создать более чистую, предсказуемую и расширяемую архитектуру для загрузки данных и кеширования.
5
+
6
+ Для этого мы в экспериментальном виде реализуем "Api" и "ResourceV2".
7
+
8
+ ## Motivation
9
+
10
+ Есть проблемы, нужно их решить:
11
+
12
+ 1. Сложности работы с внутренним API пакета, и его расширением.
13
+ 2. Отсутствие надежной системы очистки кеша - текущая не сбрасывает состояние полностью.
14
+ 3. Отсутствие возможности группировки разных API (ресурсов и комманд), для передачи им общих настроек, изоляции и одновременной очистки.
15
+ 4. Нет поддержки SSR
16
+
17
+ ## New package API
18
+
19
+ ### createApi
20
+
21
+ `createApi` - это новая, никак не зависящая от старых реализаций, функция для создания группы ресурсов (в дальнейшем и операций).
22
+
23
+ | Опция | Тип | По умолчанию | Режим | Описание |
24
+ |----------------------|------------------------------------------|-------------------|---------------|------------------------------------------------------------------------------------------------------------------------------------------------------|
25
+ | `keyPrefix` | `string` \| `null` \| `undefined` | null | | Префикс для ключей ресурсов |
26
+ | `keyStrategy` | `'serialize'` \| `'compare` | `'serialize'` | | Режим хранения и сравнения кеша. `serialize` хранит ключ кеша в виде строки. `compare` это старый способо, когда ключем являлось значние аргументов |
27
+ | `serializeArgs` | `TSerializeArgsFn` | `stableStringify` | `'serialize'` | Функция для сериализации аргументов в строку. |
28
+ | `compareArg` | `TCompareArgsFn` | `shallowEqual` | `compare` | Функция для сравнения аргументов. |
29
+ | `initialSnapshot` | `TApiSnapshot` \| `null` \| `undefined` | null | `'serialize'` | Снимок для инициализации API с набором данных. |
30
+ | `cacheLifetime` | `number` | 120_000 | | Время жизни кеша в миллисекундах. После истечения этого времени, кеш будет считаться устаревшим. |
31
+ | `plugins` | `TApiPlugin[]` | [] | | Массив плагинов для расширения функциональности API. |
32
+ | `maxSnapshotDataAge` | `number` | 5_000 | `'serialize'` | От какого возраста данных в спашноте необходима инвалидация. |
33
+ | `doCacheArgs` | `boolean` | false | `serialize` | Кешировать ли результат сериализации аргументов. |
34
+
35
+ | Свойство | Тип | Описание |
36
+ |-----------------------|----------------------|------------------------------------------------------------------------------------------------|
37
+ | `resetAll()` | `() => void` | Метод для сброса всего состояния API. |
38
+ | `createResource(...)` | `TCreateResourceFn` | Метод для создания ресурса. Принимает определение ресурса и возвращает экземпляр `ResourceV2`. |
39
+ | `getSnapshot()` | `() => TApiSnapshot` | Метод для получения снимка текущего состояния API. |
40
+
41
+ ### api.createResource
42
+
43
+ `api.createResource` - это метод для создания ресурса (`ResourceV2`).
44
+
45
+ | Опция | Тип | По умолчанию | Описание |
46
+ |----------------------|--------------------------------|----------------------------------------|------------------------------------------------------------------------------------------------------------------------------|
47
+ | `key` | `string` | Обязятелен для `'serialize'` стратегии | Уникальный ключ ресурса. Использует для логирования в девтулс. Если указан, то будет проверятся на уникальнось в рамках api. |
48
+ | `queryFn` | `TQueryFn` | Обязателен | Функция для выполнения запроса. Должна возвращать промис. |
49
+ | `onCacheEntryAdded` | `TResourceOnCacheEntryAddedFn` | | Хук, который вызывается при добавлении новой записи в кеш. |
50
+ | `onQueryStarted` | `TResourceOnQueryStartedFn` | | Хук, который вызывается при запуске запроса. |
51
+ | `serializeArgs` | `TSerializeArgsFn` | createApi опция (stableStringify) | Функция для сериализации аргументов в строку. |
52
+ | `compareArg` | `TCompareArgsFn` | createApi опция (shallowEqual) | Функция для сравнения аргументов. |
53
+ | `cacheLifetime` | `number` | createApi опция (60_000) | Время жизни кеша для этого ресурса в миллисекундах. |
54
+ | `beforeDevtoolsPush` | `TBeforeDevtoolsPushFn` | | Хук, который вызывается перед отправкой данных в Devtools. Позволяет модифицировать данные. |
55
+ | `maxSnapshotDataAge` | `number` | createApi опция (5_000) | От какого возраста данных в спашноте необходима инвалидация. |
56
+ | `doCacheArgs` | `boolean` | createApi опция (false) | Кешировать ли результат сериализации аргументов. |
57
+
58
+ | Свойство | Тип | Описание |
59
+ |----------------------------|-------------------------------|------------------------------------------------------------------------------------|
60
+ | `createAgent()` | `CreateResourceV2AgentFn` | Метод для создания агента ресурса, который позволяет управлять состоянием ресурса. |
61
+ | `query(args, doForse)` | `TResourveV2QueryFn` | Метод для выполнения запроса с заданными аргументами. Возвращает промис. |
62
+ | `query$(args, doForse)` | `TResourveV2QuerySignalFn` | Метод для получения сигнала состояния запроса с заданными аргументами. |
63
+ | `entry(args, doInitiate)` | `TGetResourceV2EntryFn` | Метод для получения объекта кеша. |
64
+ | `entry$(args, doInitiate)` | `TGetResourceV2EntrySignalFn` | Метод для получения сигнала объекта кеша. |
65
+
66
+
67
+ С react плагином:
68
+
69
+ | Свойство | Тип | Описание |
70
+ |---------------------|------------------|---------------------------------------------------------------------------------------------------------------------------|
71
+ | `useResource(args)` | `TUseResourceV2` | Хук для выполнения запроса с заданными аргументами. Возвращает массив с состоянием запроса и функцией для его обновления. |
72
+
73
+
74
+ ### TApiSnapshot
75
+
76
+ `TApiSnapshot` - это тип, представляет сериализуемый снимок состояния API,
77
+ который может быть использован для инициализации состояния с предопределенным набором данных (например для дегидрации/гидрации).
78
+ Для гарантии совместимости содержит в себе текущую версию формата, `keyPrefix` и тип.
79
+
80
+ ### TResourveV2QueryFn
81
+
82
+ ```ts
83
+ type TResourveV2QueryFn = <D extends TResourceV2Definition>(
84
+ args: D['queryFnArgs'],
85
+ force: boolean
86
+ ) => TResourceV2Cache
87
+ ```
88
+
89
+ ### ICacheEntry
90
+ `ICacheEntry` - представляет собой реактивную единицу кеша, хранит Machine.
91
+
92
+
93
+ ## Махиники
94
+
95
+ ### Machine
96
+ `Machine` - это класс, который хранит state и инкапсулирует логику управление state. `Machine` содержит методы для перехода к другим `Machine`.
97
+
98
+ ### Patch'es
99
+
100
+ `Patch` - это абстрактное представление изменений, которые могут быть применены к данным ресурса.
101
+ Патчи позволяют реализовать оптимистичные обновления.
102
+ В отличие от некоторых других реализаций, патчи в ResourceV2 (как и в V1), защищены от ряда race conditions, благодаря `originalData`.
103
+
104
+ ```
105
+ // Все валидные committed - пропускаем и убираем из очереди
106
+ // Все aborted - применяем и убираем из очереди
107
+ // Все pending - применяем и оставляем в очереди
108
+ // Все commited (которые после pending) - применяем, но оставляем в очереди
109
+ // Все aborted (которые после pending) - откатываем, но оставляем в очереди
110
+ // Если после aborted нет pending - пропускаем и убираем из очереди
111
+ // Те после применения всех транзакций, очередь должна начинаться с первой pending транзакции (если есть), включая все, что после неё.
112
+ // (Подробнее в коде V1) (у V1 проблема: если race conditions все же происходит, то patch "зависает")
113
+ ```
114
+
115
+ ## Naming Convention
116
+
117
+ - Все interface и type начинаются с `I` или `T`
118
+ - Все что непосредственно относится к ResourceV2, должно содежраь в названии `ResourceV2`, чтобы не путать с текущей реализацией.
119
+
120
+ ## Примеры
121
+
122
+ ### Пример создания API используя публичное API пакета:
123
+
124
+ ```ts
125
+ const mainApi = createApi({
126
+ keyPrefix: 'main',
127
+ onQueryError(ctx) {
128
+ // ...
129
+ }
130
+ });
131
+
132
+ // ===
133
+
134
+ import { mainContracts } from '@/my-shared/contracts';
135
+
136
+ const getUserById = mainApi.createResource({
137
+ queryFn: mainContracts.fetchUserById,
138
+ key: 'getUserById',
139
+ });
140
+
141
+ ```
142
+
143
+
144
+ ### Strong typing
145
+
146
+ Важно, сохрнить сильные стороны текущей реализации, в том числе сильную типизацию:
147
+
148
+ ```ts
149
+ const mainApi = createApi({
150
+ plugins: [new ReactHooksPlugin()] // Новое: Модифицируем типы, добавлям хуки в русурс
151
+ });
152
+
153
+ const getUserById = mainApi.createResource({
154
+ queryFn: mainContracts.fetchUserById, // Тип getUserById определится автоматически
155
+ });
156
+
157
+
158
+ // ===
159
+
160
+ const userQuery = getUserById.useResource(userId ?? SKIP) // Типипизция допускает передачу только userId и SKIP
161
+ ```
162
+
163
+ ### Пример вне фреймворка:
164
+
165
+
166
+ ```ts
167
+
168
+ // Используем агента
169
+ class UserStore {
170
+ private getUserByIdAgent = getUserById.createAgent();
171
+ selectedUserId$ = Signal.compute(() => this.getUserByIdAgent.state$().args);
172
+
173
+ selectedUser$ = Signal.compute(() => this.getUserByIdAgent.state$().data);
174
+
175
+ selectUserId(userId: number) {
176
+ this.getUserByIdAgent.start(userId);
177
+ }
178
+ }
179
+
180
+
181
+ // Или используем "query select"
182
+
183
+ class UserStore {
184
+ private getUserByIdAgent = getUserById.createAgent();
185
+ selectedUserId$ = Signal.state<number | null>(null);
186
+
187
+ selectedUser$ = Signal.compute<User | null>(() => {
188
+ if (!this.selectedUserId$()) return null;
189
+ return this.getUserByIdAgent.query$(this.selectedUserId$()).data;
190
+ });
191
+
192
+ selectUserId(userId: number) {
193
+ this.selectedUserId$.set(userId);
194
+ }
195
+ }
196
+ ```
197
+
198
+ Разница между `start` и `query$`:
199
+ 1) `start` устанавливает fresh аргументы, напрямую и возвращает промис
200
+ 2) `query$` тоже устанавливает fresh аргументы, но `query$` - это сигнал, он возращает текущее состояние запроса.
201
+
202
+ Прямой доступ к ResourceV2 vs agent:
203
+ 1) Agent может хранить fresh и stale аргументы.
204
+ 2) Agent реализует Stale-While-Revalidate стратегию, позволяя получать не актуальные данные, пока новые загружаются.
205
+ 3) Agent предоставляет более удобное API.
206
+
207
+
208
+ ### Пример опций createApi
209
+
210
+ ```ts
211
+ const mainApi = createApi({
212
+ keyPrefix: 'main', // Префикс ключа
213
+ mode: 'serialize', // Режим хранения хранения и сравнения кеша
214
+ serializeFn: (args) => fastJson(args), // Функция сериализации аргументов
215
+ onQueryError({ type, payload }) { /*...*/ }, // Хук ошибки
216
+ snapshot: JSON.parse(hydrateData.mainApiSnapshot), // Снимок для инициализации API с набором данных
217
+ });
218
+
219
+ mainApi.resetAll(); // Сбросить все состояние
220
+ mainApi.createResource(/*...*/) // Содать ресурс
221
+ mainApi.getSnapshot(); // Получить снимок текущего состояния API
222
+ ```
223
+
224
+
225
+ ## Внутрянка
226
+
227
+ ### Как кеш (state machine) типизируется:
228
+
229
+ ```ts
230
+ type TMachine<ARGS, DATA> =
231
+ MachinePanding<ARGS, DATA>
232
+ | MachineIdle<ARGS, DATA> //...
233
+
234
+ class MachineIdle<ARGS, DATA> {
235
+ state: TResourceV2IdleState;
236
+
237
+ constructor(state: TResourceV2IdleState) {
238
+ this.state = state;
239
+ }
240
+
241
+ start(args: D['queryFnArgs']) {
242
+ return new MachinePending({
243
+ status: 'pending',
244
+ args,
245
+ error: null,
246
+ data: null,
247
+ updatedAt: null,
248
+ });
249
+ }
250
+
251
+ /* другие методы, которые могут понадобиться для idle состояния */
252
+
253
+ static create() {
254
+ return new MachineIdle({
255
+ status: 'idle',
256
+ args: null,
257
+ error: null,
258
+ data: null,
259
+ updatedAt: null,
260
+ });
261
+ }
262
+ }
263
+
264
+ class MachinePending<ARGS, DATA> {
265
+ state: TResourceV2PendingState;
266
+
267
+ constructor(state: TResourceV2PendingState) {
268
+ this.state = state;
269
+ }
270
+
271
+ errorHappened(error: Error) {
272
+ return new MachineError({
273
+ status: 'error',
274
+ args: this.state.args,
275
+ error,
276
+ data: null,
277
+ updatedAt: null,
278
+ });
279
+ }
280
+
281
+ successHappened(data: DATA) {
282
+ return new MachineSuccess({
283
+ status: 'success',
284
+ args: this.state.args,
285
+ error: null,
286
+ data,
287
+ updatedAt: Date.now(),
288
+ });
289
+ }
290
+
291
+ /* другие методы, которые могут понадобиться для pending состояния */
292
+
293
+ static create(args: ARGS) {
294
+ return new MachinePending({
295
+ status: 'pending',
296
+ args,
297
+ originalData: NO_VALUE,
298
+ });
299
+ }
300
+ }
301
+
302
+ // Лучше, чтобы MachineSuccess и MachineRefreshing наследовались от общего класса (чтобы не дублировать код связанный с патчами)
303
+ class MachineSuccess<ARGS, DATA> {
304
+ state: TResourceV2SuccessState;
305
+
306
+ constructor(state: TResourceV2SuccessState) {
307
+ this.state = state;
308
+ }
309
+
310
+ invalidate() {
311
+ return new MachineRefreshing({
312
+ status: 'refreshing',
313
+ args: this.state.args,
314
+ data: this.state.data,
315
+ updatedAt: this.state.updatedAt,
316
+ });
317
+ }
318
+
319
+ addPatch(patch: TResourceV2Patch) {
320
+ const originalData = this.state.originalData === NO_VALUE
321
+ ? this.state.data
322
+ : this.state.originalData;
323
+
324
+ const patches = (this.state.patches ?? []).concat(tr);
325
+
326
+ return new MachineSuccess({
327
+ status: 'success',
328
+ args: this.state.args,
329
+ data: Patcher.resolvePatches(originalData, patches),
330
+ updatedAt: Date.now(),
331
+ originalData,
332
+ patches,
333
+ });
334
+ }
335
+
336
+ finishPatch(type: 'commit' | 'abort', patch: TResourceV2Patch) {
337
+ const { originalData, patches } = Patcher.finishPatch(
338
+ this.state.originalData,
339
+ this.state.patches,
340
+ type,
341
+ patch
342
+ );
343
+
344
+ const machine = new MachineSuccess({
345
+ status: 'success',
346
+ args: this.state.args,
347
+ data: !!originalData?.length
348
+ ? Patcher.resolvePatches(originalData, patches)
349
+ : originalData,
350
+ updatedAt: Date.now(),
351
+ originalData,
352
+ patches,
353
+ });
354
+ }
355
+
356
+ createPatch(patchFn: TPatchFn<D['queryFnReturn']>) {
357
+ const patch = Patcher.createPatch(patchFn, this.state.data);
358
+
359
+ const state = this.addPatch(patch);
360
+
361
+ return { state, patch };
362
+ }
363
+
364
+ /* другие методы, которые могут понадобиться для pending состояния */
365
+
366
+ static deploy(snapshotSlice: TResourceV2SnapshotSlice<D>) {
367
+ return new MachineSuccess({
368
+ status: 'success',
369
+ args: snapshotSlice.args,
370
+ data: snapshotSlice.data,
371
+ updatedAt: snapshotSlice.updatedAt,
372
+ });
373
+ }
374
+
375
+ }
376
+
377
+
378
+ // ...
379
+ ```