@reformer/core 10.2.0 → 11.0.0-beta.10

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 (285) hide show
  1. package/README.md +90 -68
  2. package/dist/behaviors-value-D05z3my6.js +109 -0
  3. package/dist/behaviors.d.ts +1 -1
  4. package/dist/behaviors.js +376 -211
  5. package/dist/create-model-DPSG3I0O.js +30 -0
  6. package/dist/date-utils-90-qZbse.js +43 -0
  7. package/dist/en-HP8XEych.js +44 -0
  8. package/dist/form/aggregate-signals.d.ts +21 -63
  9. package/dist/form/behaviors/collections.d.ts +84 -0
  10. package/dist/form/behaviors/context.d.ts +56 -0
  11. package/dist/form/behaviors/index.d.ts +30 -0
  12. package/dist/form/behaviors/internals.d.ts +41 -0
  13. package/dist/form/{behaviors-node.d.ts → behaviors/node.d.ts} +28 -5
  14. package/dist/form/behaviors/operators.d.ts +59 -0
  15. package/dist/form/behaviors/schema-operators.d.ts +83 -0
  16. package/dist/form/behaviors/types.d.ts +37 -0
  17. package/dist/form/create-form.d.ts +59 -70
  18. package/dist/form/flow/flow.d.ts +87 -0
  19. package/dist/form/form-bundle.d.ts +153 -0
  20. package/dist/form/form-level.d.ts +121 -0
  21. package/dist/form/form-proxy-builder.d.ts +6 -22
  22. package/dist/form/index.d.ts +23 -27
  23. package/dist/form/nodes/form-array-node.d.ts +100 -0
  24. package/dist/form/nodes/form-control-node.d.ts +73 -0
  25. package/dist/form/nodes/form-group-node.d.ts +66 -0
  26. package/dist/form/nodes/node-kind.d.ts +29 -0
  27. package/dist/form/nodes/state/availability.d.ts +6 -0
  28. package/dist/form/nodes/state/component.d.ts +6 -0
  29. package/dist/form/nodes/state/index.d.ts +12 -0
  30. package/dist/form/nodes/state/interaction.d.ts +13 -0
  31. package/dist/form/nodes/state/issues.d.ts +9 -0
  32. package/dist/form/nodes/state/pending.d.ts +6 -0
  33. package/dist/form/schema/compile.d.ts +59 -0
  34. package/dist/form/schema/compiled.d.ts +143 -0
  35. package/dist/form/schema/row-key.d.ts +11 -0
  36. package/dist/form/schema-controller.d.ts +159 -0
  37. package/dist/form/schema-subtree.d.ts +28 -0
  38. package/dist/form/signal-node-registry.d.ts +27 -22
  39. package/dist/form/submission.d.ts +93 -0
  40. package/dist/form/type-guards.d.ts +14 -73
  41. package/dist/form/types/contracts.d.ts +21 -91
  42. package/dist/form/types/field-config.d.ts +27 -0
  43. package/dist/form/types/index.d.ts +5 -91
  44. package/dist/form/types/nodes.d.ts +159 -0
  45. package/dist/form/types/schema-node.d.ts +148 -50
  46. package/dist/form/types/validation-schema.d.ts +24 -41
  47. package/dist/form/validation/context.d.ts +67 -0
  48. package/dist/form/validation/index.d.ts +24 -28
  49. package/dist/form/validation/operators.d.ts +101 -0
  50. package/dist/form/validation/run.d.ts +120 -0
  51. package/dist/form/validation/strategy.d.ts +89 -0
  52. package/dist/form/validation/types.d.ts +83 -0
  53. package/dist/form/validators/date-utils.d.ts +42 -0
  54. package/dist/form/validators/email.d.ts +23 -0
  55. package/dist/form/{validation/validators → validators}/file-type.d.ts +10 -10
  56. package/dist/form/{validation/validators → validators}/file-utils.d.ts +3 -2
  57. package/dist/form/validators/future-date.d.ts +23 -0
  58. package/dist/form/{validation/validators → validators}/index.d.ts +7 -4
  59. package/dist/form/validators/integer.d.ts +22 -0
  60. package/dist/form/validators/is-date.d.ts +24 -0
  61. package/dist/form/validators/is-number.d.ts +25 -0
  62. package/dist/form/validators/max-age.d.ts +28 -0
  63. package/dist/form/validators/max-date.d.ts +25 -0
  64. package/dist/form/{validation/validators → validators}/max-file-size.d.ts +10 -10
  65. package/dist/form/validators/max-files.d.ts +26 -0
  66. package/dist/form/validators/max-length.d.ts +33 -0
  67. package/dist/form/validators/max-total-file-size.d.ts +26 -0
  68. package/dist/form/validators/max.d.ts +26 -0
  69. package/dist/form/validators/min-age.d.ts +27 -0
  70. package/dist/form/validators/min-date.d.ts +25 -0
  71. package/dist/form/{validation/validators → validators}/min-file-size.d.ts +10 -10
  72. package/dist/form/validators/min-files.d.ts +27 -0
  73. package/dist/form/validators/min-length.d.ts +33 -0
  74. package/dist/form/validators/min.d.ts +26 -0
  75. package/dist/form/validators/multiple-of.d.ts +25 -0
  76. package/dist/form/validators/non-negative.d.ts +22 -0
  77. package/dist/form/validators/non-zero.d.ts +22 -0
  78. package/dist/form/validators/past-date.d.ts +23 -0
  79. package/dist/form/validators/pattern.d.ts +26 -0
  80. package/dist/form/{validation/validators → validators}/phone.d.ts +11 -11
  81. package/dist/form/validators/required.d.ts +29 -0
  82. package/dist/form/validators/url.d.ts +45 -0
  83. package/dist/form/validators/validation-issue.d.ts +8 -0
  84. package/dist/i18n/descriptor.d.ts +54 -0
  85. package/dist/i18n/en.json.d.ts +49 -0
  86. package/dist/i18n/loader.d.ts +71 -0
  87. package/dist/i18n/locale-store.d.ts +46 -0
  88. package/dist/i18n/locale.d.ts +90 -0
  89. package/dist/i18n/message-format.d.ts +136 -0
  90. package/dist/i18n/resolve-localized.d.ts +17 -0
  91. package/dist/i18n/ru.json.d.ts +49 -0
  92. package/dist/i18n/translator.d.ts +61 -0
  93. package/dist/i18n/validation-message.d.ts +53 -0
  94. package/dist/i18n.d.ts +26 -0
  95. package/dist/i18n.js +86 -0
  96. package/dist/index.d.ts +18 -6
  97. package/dist/index.js +932 -2779
  98. package/dist/loader-BWkigpVe.js +52 -0
  99. package/dist/locale/en.d.ts +2 -0
  100. package/dist/locale/en.js +5 -0
  101. package/dist/locale/en.json +45 -0
  102. package/dist/locale/index.d.ts +18 -0
  103. package/dist/locale/ru.d.ts +2 -0
  104. package/dist/locale/ru.js +44 -0
  105. package/dist/locale/ru.json +45 -0
  106. package/dist/locale-CG54AU2Q.js +219 -0
  107. package/dist/locale.d.ts +2 -0
  108. package/dist/locale.js +9 -0
  109. package/dist/{state → model}/behaviors-value.d.ts +24 -6
  110. package/dist/{state/form-model.d.ts → model/create-model.d.ts} +18 -4
  111. package/dist/{state → model}/derived-registry.d.ts +1 -1
  112. package/dist/model/index.d.ts +42 -0
  113. package/dist/model/model-nodes.d.ts +190 -0
  114. package/dist/model/model-signals-proxy.d.ts +63 -0
  115. package/dist/model/model-value-proxy.d.ts +84 -0
  116. package/dist/{state → model}/safe-effect.d.ts +50 -2
  117. package/dist/model/types.d.ts +239 -0
  118. package/dist/model.d.ts +2 -0
  119. package/dist/model.js +27 -0
  120. package/dist/node-kind-C-r6ZMdo.js +15 -0
  121. package/dist/platforms/react/field-binding/field-adapter.d.ts +87 -0
  122. package/dist/platforms/react/field-binding/field-handle.d.ts +53 -0
  123. package/dist/platforms/react/form-bundle-context.d.ts +27 -0
  124. package/dist/{form → platforms/react}/hooks/types.d.ts +60 -38
  125. package/dist/platforms/react/hooks/use-form-bundle.d.ts +41 -0
  126. package/dist/platforms/react/hooks/use-form-flow.d.ts +40 -0
  127. package/dist/{form → platforms/react}/hooks/use-form-validation.d.ts +13 -7
  128. package/dist/{form → platforms/react}/hooks/useArrayLength.d.ts +6 -6
  129. package/dist/{form → platforms/react}/hooks/useFormControl.d.ts +7 -9
  130. package/dist/{form → platforms/react}/hooks/useFormControlValue.d.ts +18 -18
  131. package/dist/{form → platforms/react}/hooks/useSignalSubscription.d.ts +9 -1
  132. package/dist/platforms/react/i18n/context.d.ts +18 -0
  133. package/dist/platforms/react/i18n/hooks.d.ts +65 -0
  134. package/dist/platforms/react/i18n/provider.d.ts +59 -0
  135. package/dist/platforms/react/index.d.ts +30 -0
  136. package/dist/react.d.ts +22 -0
  137. package/dist/react.js +505 -0
  138. package/dist/runtime-token.d.ts +22 -0
  139. package/dist/safe-effect-BazLOv1g.js +669 -0
  140. package/dist/schema-controller-CpiO2JoK.js +143 -0
  141. package/dist/signals.d.ts +15 -9
  142. package/dist/signals.js +11 -6
  143. package/dist/strategy-DzvnsWLh.js +375 -0
  144. package/dist/type-guards-D-N_5Ddh.js +19 -0
  145. package/dist/validation-issue-B9mjI8jz.js +7 -0
  146. package/dist/validation-message-Ced9MGxs.js +194 -0
  147. package/dist/validation.d.ts +1 -1
  148. package/dist/validation.js +11 -165
  149. package/dist/validators/email.d.ts +1 -1
  150. package/dist/validators/email.js +5 -8
  151. package/dist/validators/future-date.d.ts +1 -1
  152. package/dist/validators/future-date.js +10 -13
  153. package/dist/validators/integer.d.ts +1 -1
  154. package/dist/validators/integer.js +4 -7
  155. package/dist/validators/is-date.d.ts +1 -1
  156. package/dist/validators/is-date.js +5 -8
  157. package/dist/validators/is-number.d.ts +1 -1
  158. package/dist/validators/is-number.js +4 -7
  159. package/dist/validators/max-age.d.ts +1 -1
  160. package/dist/validators/max-age.js +8 -12
  161. package/dist/validators/max-date.d.ts +1 -1
  162. package/dist/validators/max-date.js +10 -13
  163. package/dist/validators/max-length.d.ts +1 -1
  164. package/dist/validators/max-length.js +7 -10
  165. package/dist/validators/max.d.ts +1 -1
  166. package/dist/validators/max.js +4 -7
  167. package/dist/validators/min-age.d.ts +1 -1
  168. package/dist/validators/min-age.js +10 -11
  169. package/dist/validators/min-date.d.ts +1 -1
  170. package/dist/validators/min-date.js +8 -11
  171. package/dist/validators/min-length.d.ts +1 -1
  172. package/dist/validators/min-length.js +4 -7
  173. package/dist/validators/min.d.ts +1 -1
  174. package/dist/validators/min.js +4 -7
  175. package/dist/validators/multiple-of.d.ts +1 -1
  176. package/dist/validators/multiple-of.js +11 -10
  177. package/dist/validators/non-negative.d.ts +1 -1
  178. package/dist/validators/non-negative.js +2 -5
  179. package/dist/validators/non-zero.d.ts +1 -1
  180. package/dist/validators/non-zero.js +4 -7
  181. package/dist/validators/past-date.d.ts +1 -1
  182. package/dist/validators/past-date.js +10 -13
  183. package/dist/validators/pattern.d.ts +1 -1
  184. package/dist/validators/pattern.js +5 -8
  185. package/dist/validators/phone.d.ts +1 -1
  186. package/dist/validators/phone.js +10 -11
  187. package/dist/validators/required.d.ts +1 -1
  188. package/dist/validators/required.js +4 -7
  189. package/dist/validators/url.d.ts +1 -1
  190. package/dist/validators/url.js +48 -17
  191. package/dist/validators.d.ts +1 -5
  192. package/dist/validators.js +98 -154
  193. package/docs/llms/01-api-reference.md +89 -0
  194. package/docs/llms/02-quick-start.md +181 -0
  195. package/docs/llms/03-api-signatures.md +494 -0
  196. package/docs/llms/04-common-patterns.md +204 -0
  197. package/docs/llms/05-common-mistakes.md +192 -0
  198. package/docs/llms/06-troubleshooting.md +16 -0
  199. package/docs/llms/07-complete-import.md +130 -0
  200. package/docs/llms/08-form-types.md +54 -0
  201. package/docs/llms/09-formschema.md +191 -0
  202. package/docs/llms/10-arrays.md +241 -0
  203. package/docs/llms/11-async-watchfield.md +53 -0
  204. package/docs/llms/12-array-cleanup.md +31 -0
  205. package/docs/llms/13-multi-step.md +162 -0
  206. package/docs/llms/14-extended-mistakes.md +101 -0
  207. package/docs/llms/15-project-structure.md +279 -0
  208. package/docs/llms/16-ui-components.md +229 -0
  209. package/docs/llms/17-nonexistent-api.md +278 -0
  210. package/docs/llms/18-conditional-fields.md +222 -0
  211. package/docs/llms/19-reading-values.md +100 -0
  212. package/docs/llms/20-compute-vs-watch.md +153 -0
  213. package/docs/llms/21-array-operations.md +163 -0
  214. package/docs/llms/22-cycle-detection.md +86 -0
  215. package/docs/llms/23-copy-from.md +141 -0
  216. package/docs/llms/24-sync-fields.md +115 -0
  217. package/docs/llms/25-reset-when.md +122 -0
  218. package/docs/llms/26-transform-value.md +136 -0
  219. package/docs/llms/27-revalidate-when.md +133 -0
  220. package/docs/llms/28-submit-and-reset.md +409 -0
  221. package/docs/llms/29-async-preload.md +203 -0
  222. package/docs/llms/30-type-safety-recipes.md +213 -0
  223. package/docs/llms/31-async-validator-debounce.md +178 -0
  224. package/docs/llms/32-async-options-loading.md +108 -0
  225. package/docs/llms/33-validation-strategy.md +305 -0
  226. package/docs/llms/34-i18n.md +298 -0
  227. package/docs/llms/35-unified-contract.md +309 -0
  228. package/docs/llms/36-issue-messages.md +284 -0
  229. package/docs/llms/37-compiled-schema.md +332 -0
  230. package/docs/llms/38-form-flow.md +396 -0
  231. package/llms-index.json +1 -0
  232. package/llms.txt +10014 -3826
  233. package/package.json +77 -15
  234. package/dist/behaviors-node-DGEFMTQu.js +0 -18
  235. package/dist/behaviors-value-C7dSEhCc.js +0 -134
  236. package/dist/date-utils-dTT_x_Be.js +0 -31
  237. package/dist/derived-registry-CluZCQ7U.js +0 -16
  238. package/dist/form/behaviors.d.ts +0 -133
  239. package/dist/form/error-handler.d.ts +0 -166
  240. package/dist/form/factories/index.d.ts +0 -6
  241. package/dist/form/factories/node-factory.d.ts +0 -188
  242. package/dist/form/form-submitter.d.ts +0 -125
  243. package/dist/form/nodes/array-node.d.ts +0 -360
  244. package/dist/form/nodes/field-node.d.ts +0 -294
  245. package/dist/form/nodes/form-node.d.ts +0 -340
  246. package/dist/form/nodes/group-node.d.ts +0 -288
  247. package/dist/form/nodes/model-array-node.d.ts +0 -121
  248. package/dist/form/status-machine.d.ts +0 -153
  249. package/dist/form/types/deep-schema.d.ts +0 -116
  250. package/dist/form/types/form-proxy.d.ts +0 -141
  251. package/dist/form/unique-id.d.ts +0 -53
  252. package/dist/form/validation/validators/date-utils.d.ts +0 -30
  253. package/dist/form/validation/validators/email.d.ts +0 -23
  254. package/dist/form/validation/validators/future-date.d.ts +0 -23
  255. package/dist/form/validation/validators/integer.d.ts +0 -22
  256. package/dist/form/validation/validators/is-date.d.ts +0 -23
  257. package/dist/form/validation/validators/is-number.d.ts +0 -24
  258. package/dist/form/validation/validators/max-age.d.ts +0 -25
  259. package/dist/form/validation/validators/max-date.d.ts +0 -25
  260. package/dist/form/validation/validators/max-files.d.ts +0 -26
  261. package/dist/form/validation/validators/max-length.d.ts +0 -27
  262. package/dist/form/validation/validators/max-total-file-size.d.ts +0 -26
  263. package/dist/form/validation/validators/max.d.ts +0 -25
  264. package/dist/form/validation/validators/min-age.d.ts +0 -25
  265. package/dist/form/validation/validators/min-date.d.ts +0 -25
  266. package/dist/form/validation/validators/min-files.d.ts +0 -26
  267. package/dist/form/validation/validators/min-length.d.ts +0 -27
  268. package/dist/form/validation/validators/min.d.ts +0 -25
  269. package/dist/form/validation/validators/multiple-of.d.ts +0 -24
  270. package/dist/form/validation/validators/non-negative.d.ts +0 -22
  271. package/dist/form/validation/validators/non-zero.d.ts +0 -22
  272. package/dist/form/validation/validators/past-date.d.ts +0 -23
  273. package/dist/form/validation/validators/pattern.d.ts +0 -33
  274. package/dist/form/validation/validators/required.d.ts +0 -32
  275. package/dist/form/validation/validators/url.d.ts +0 -43
  276. package/dist/form/validation-schema.d.ts +0 -106
  277. package/dist/form/validation-strategy.d.ts +0 -55
  278. package/dist/form/validators.d.ts +0 -6
  279. package/dist/form-model-DYCzGGKF.js +0 -286
  280. package/dist/state/index.d.ts +0 -31
  281. package/dist/state/subscription-manager.d.ts +0 -180
  282. package/dist/state/types.d.ts +0 -144
  283. package/dist/state.d.ts +0 -2
  284. package/dist/state.js +0 -21
  285. package/dist/subscription-manager-705bls97.js +0 -171
@@ -0,0 +1,204 @@
1
+ ## 3. COMMON PATTERNS
2
+
3
+ Значения живут в модели (`model.$.field`), поведение (`defineFormBehavior`) работает на живых
4
+ сигналах, валидация — отдельная схема (`defineValidation`, прогон по требованию через
5
+ раннер). Слои раздельны: схема формы правил НЕ несёт.
6
+
7
+ ### Conditional Fields with Auto-Reset
8
+
9
+ `enableWhen` — оператор поведения: поле включается по условию, при выключении сбрасывается к
10
+ initial. Вызывается внутри `defineFormBehavior`:
11
+
12
+ ```typescript
13
+ import { defineFormBehavior, enableWhen } from '@reformer/core/behaviors';
14
+
15
+ const behavior = defineFormBehavior<MyForm>(({ model }) => {
16
+ enableWhen(
17
+ [model.$.propertyValue, model.$.initialPayment],
18
+ () => model.loanType === 'mortgage',
19
+ { resetOnDisable: true }
20
+ );
21
+ });
22
+ ```
23
+
24
+ ### Computed Field (same or cross level)
25
+
26
+ `compute`/`computeFrom` пишут в целевой сигнал при изменении источников. Цель не входит
27
+ в источники → цикла нет. Кросс-уровневые вычисления работают так же — источники берутся
28
+ по сигналам из любого места модели.
29
+
30
+ ```typescript
31
+ import { defineFormBehavior, compute, computeFrom } from '@reformer/core/behaviors';
32
+
33
+ const behavior = defineFormBehavior<MyForm>(({ model }) => {
34
+ // compute: auto-tracking — читаем что нужно прямо из value-модели
35
+ compute(model.$.total, () => (model.price ?? 0) * (model.quantity ?? 0));
36
+
37
+ // computeFrom: явный список источников
38
+ computeFrom([model.$.price, model.$.quantity], model.$.total, (price, qty) => price * qty);
39
+
40
+ // кросс-уровневое: fullName из вложенной группы
41
+ compute(model.$.fullName, () =>
42
+ [model.personalData.firstName, model.personalData.lastName].filter(Boolean).join(' ')
43
+ );
44
+ });
45
+ ```
46
+
47
+ ### Async reaction / dynamic options — `onChange`
48
+
49
+ Для async-реакции на изменение поля (загрузка справочников, зависимые селекты) используй
50
+ `onChange` из DSL. Колбэк выполняется вне effect-контекста (можно писать сигналы/ноды), а
51
+ 2-й аргумент — `{ signal }` (AbortSignal) для отмены устаревших запросов.
52
+
53
+ ```typescript
54
+ import { defineFormBehavior, onChange } from '@reformer/core/behaviors';
55
+
56
+ const behavior = defineFormBehavior<MyForm>(({ model, form }) => {
57
+ onChange(
58
+ model.$.region,
59
+ async (region, { signal }) => {
60
+ if (!region) {
61
+ form.city.updateComponentProps({ options: [] });
62
+ return;
63
+ }
64
+ const cities = await fetchCities(region, { signal });
65
+ form.city.updateComponentProps({ options: cities });
66
+ },
67
+ { debounce: 300 }
68
+ );
69
+ });
70
+ ```
71
+
72
+ > Низкоуровневый аналог — примитив `watchField(model.$.region, cb)` из `@reformer/core/model` (без debounce/AbortSignal;
73
+ > для сети предпочтителен `onChange`). См. `20-compute-vs-watch.md`, `32-async-options-loading.md`.
74
+
75
+ ### Reading values
76
+
77
+ ```typescript
78
+ // В React-компоненте — хуки (@reformer/core/react)
79
+ const { value, issues, disabled } = useFormControl(form.email);
80
+ const loanType = useFormControlValue(form.loanType); // значение напрямую
81
+
82
+ // Вне React — из модели
83
+ model.email; // value-доступ (реактивно внутри effect/computed)
84
+ model.$.email.value; // через сигнал
85
+ model.$.email.peek(); // нереактивный снимок
86
+ model.get(); // весь объект-снимок
87
+ ```
88
+
89
+ ### Submit + validation
90
+
91
+ Валидация — отдельная `ValidationDefinition<T>` (`defineValidation`), а не часть layout; в сборку
92
+ она уходит полем `validation`. `bundle.submission.submit(handler)` прогоняет все правила один раз,
93
+ разносит замечания по нодам и зовёт обработчик только при статусе `valid` (`severity: 'warning'` не
94
+ блокирует). Результат — `SubmitResult`.
95
+
96
+ ```typescript
97
+ async function handleSubmit(event: React.FormEvent) {
98
+ event.preventDefault();
99
+ const outcome = await bundle.submission.submit(async (values) => {
100
+ await api.send(values);
101
+ bundle.model.reset(); // к initial-снимку — только после успеха
102
+ });
103
+ if (outcome.status === 'invalid') showHint('Проверьте форму'); // поля уже подсвечены
104
+ }
105
+ ```
106
+
107
+ Без сборки — раннер `validateModel(model, schema)`: сам разносит замечания по нодам и отвечает
108
+ `Promise<boolean>`.
109
+
110
+ Multi-step: шаги и их правила — поток сборки `createForm({ flow: { steps: [{ id, rules }] } })`
111
+ (`id` — `selector` шага), правила вне шагов — `validation: { extras }`. Сборка отдаёт
112
+ `bundle.validateStep(id)`, `bundle.validate()` и поток `bundle.flow` (`next` / `previous` / `goTo`);
113
+ визард принимает поток пропом `flow`. См. `13-multi-step.md`, `28-submit-and-reset.md`,
114
+ `38-form-flow.md`.
115
+
116
+ ### Cross-field validation — через `cross`
117
+
118
+ Cross-field правило — обычная функция над **снимком** модели. Оператор `cross` берётся из
119
+ аргумента схемы и вешает правило на поле-носитель ошибки; тип снимка выведен из схемы:
120
+
121
+ ```typescript
122
+ import { defineValidation, validate } from '@reformer/core/validation';
123
+ import { required, min } from '@reformer/core/validators';
124
+ import type { ValidationIssue } from '@reformer/core';
125
+
126
+ // снимок формы читается напрямую — без каста, соседние поля доступны как поля объекта
127
+ const initialPaymentVsProperty = (form: MyForm): ValidationIssue | null =>
128
+ form.initialPayment && form.propertyValue && form.initialPayment > form.propertyValue
129
+ ? { code: 'initialPaymentTooHigh' } // текст кода — в словаре формы (defineIssueMessages)
130
+ : null;
131
+
132
+ const schema = defineValidation<MyForm>(({ model, cross }) => {
133
+ validate(model.$.initialPayment, [required(), min(0)]);
134
+ cross(model.$.initialPayment, initialPaymentVsProperty); // ошибка сядет на initialPayment
135
+ });
136
+ ```
137
+
138
+ > Правила строки массива и подформы — отдельная схема над их типом, подключённая через
139
+ > `applyEach(model.$.items, rowRules)` / `apply(model.$.group, groupRules)`. У такой схемы своя
140
+ > область: её `cross` получает снимок строки или группы.
141
+
142
+ ### Conditional validation — `validateWhen`
143
+
144
+ Условная валидация — `validateWhen(() => cond, () => { … })`: правила внутри активны, только пока
145
+ условие истинно; при `false` ранее тронутые поля гасятся. Это не `enable` (то — поведение), а
146
+ включение/выключение самих проверок:
147
+
148
+ ```typescript
149
+ import { defineValidation, validateWhen, validate } from '@reformer/core/validation';
150
+
151
+ const loanRules = defineValidation<MyForm>(({ model, cross }) => {
152
+ validateWhen(
153
+ () => model.loanType === 'mortgage',
154
+ () => {
155
+ validate(model.$.propertyValue, [required(), min(1000000)]);
156
+ cross(model.$.initialPayment, initialPaymentVsProperty);
157
+ }
158
+ );
159
+ });
160
+ ```
161
+
162
+ ### Перезапуск прогона — `revalidateWhen` (мост поведение → валидация)
163
+
164
+ Схема прогоняется по требованию (submit/шаг) либо живой стратегией сборки
165
+ (`validation: { strategy: 'change' }`, см. `33-validation-strategy.md`). Чтобы перезапустить
166
+ отдельную схему при изменении зависимости — `revalidateWhen` из **поведения** (мост
167
+ поведение → валидация):
168
+
169
+ ```typescript
170
+ import { defineFormBehavior, revalidateWhen } from '@reformer/core/behaviors';
171
+ import { validateModel } from '@reformer/core/validation';
172
+
173
+ const behavior = defineFormBehavior<MyForm>(({ model }) => {
174
+ revalidateWhen([model.$.propertyValue], () => void validateModel(model, schema));
175
+ });
176
+ ```
177
+
178
+ ### Extracting named rules
179
+
180
+ Когда тело правила растёт — выноси в именованную функцию. Field-правило типизируй `Rule<T>`
181
+ (`(value) => ValidationIssue | null`), cross-field — обычной `(f: Root) => ValidationIssue | null`.
182
+ Схема остаётся плоской и читается как оглавление:
183
+
184
+ ```typescript
185
+ import { defineValidation, validate } from '@reformer/core/validation';
186
+ import { required } from '@reformer/core/validators';
187
+ import type { Rule } from '@reformer/core/validation';
188
+
189
+ // field-правило: (value) => error
190
+ const validateAdultAge: Rule<string> = (value) => {
191
+ if (!value) return null;
192
+ const age = new Date().getFullYear() - new Date(value).getFullYear();
193
+ return age < 18 ? { code: 'tooYoung' } : null;
194
+ };
195
+
196
+ const schema = defineValidation<MyForm>(({ model }) => {
197
+ validate(model.$.birthDate, [required(), validateAdultAge]);
198
+ });
199
+ ```
200
+
201
+ **Naming convention** (camelCase, семантика, не эхо оператора):
202
+
203
+ - Cross-field правило → инвариант: `initialPaymentVsPropertyValue`, `paymentToIncomeUnderHalf`.
204
+ - Field-правило → проверка: `validateAdultAge`, `passwordsMatch`.
@@ -0,0 +1,192 @@
1
+ ## 4. COMMON MISTAKES
2
+
3
+ ### Imports rule (#1 cause of cascading errors — read first)
4
+
5
+ - Модель/форма/типы (`createForm`, `FormModel`, `FormSchemaNode`, `ValidationIssue`, `FormFlow`) — из `@reformer/core`.
6
+ - Хуки (`useFormBundle`, `useFormControl`, `useFormFlow`, `useIssueMessage`, …) и `I18nProvider` — из `@reformer/core/react`.
7
+ - Схема валидации (`defineValidation` + операторы `validate`/`validateAsync`/`validateWhen`/`apply`/`applyEach`) и раннер `validateModel` — из `@reformer/core/validation`. `cross` берётся из аргумента схемы.
8
+ - Чистые фабрики валидаторов (`required`/`min`/`email`/…) — из `@reformer/core/validators`.
9
+ - Словарь текстов замечаний (`defineIssueMessages`, `resolveIssueMessage`) — из `@reformer/core/i18n`.
10
+ - Декларативный DSL поведения (`defineFormBehavior` + операторы) — из `@reformer/core/behaviors`.
11
+
12
+ ```typescript
13
+ // ✅ CORRECT
14
+ import {
15
+ createModel,
16
+ createForm,
17
+ type FormGroup,
18
+ type FormSchemaNode,
19
+ type FormModel,
20
+ type ValidationIssue,
21
+ } from '@reformer/core';
22
+ import { useFormBundle, useFormControl, useFormControlValue } from '@reformer/core/react';
23
+ import { defineIssueMessages } from '@reformer/core/i18n';
24
+ // схема валидации + раннер — отдельный слой:
25
+ import {
26
+ defineValidation,
27
+ validate,
28
+ validateAsync,
29
+ validateWhen,
30
+ apply,
31
+ applyEach,
32
+ validateModel,
33
+ type Rule,
34
+ type AsyncRule,
35
+ type ValidationDefinition,
36
+ } from '@reformer/core/validation';
37
+ import { required, min, max, email } from '@reformer/core/validators';
38
+ // поведение формы — декларативный DSL:
39
+ import { defineFormBehavior, compute, enableWhen, copyFrom, onChange } from '@reformer/core/behaviors';
40
+ // примитивы над сигналами (вне формы, возвращают cleanup) — отдельный сабпат:
41
+ import { computeFrom, watchField } from '@reformer/core/model';
42
+ ```
43
+
44
+ > **В корне `@reformer/core` операторов поведения нет.** `computeFrom`, `copyFrom`, `watchField`,
45
+ > `transformValue`, `resetWhen`, `syncFields`, `revalidateWhen` — примитивы сабпата
46
+ > `@reformer/core/model`; `enableWhen` / `disableWhen` — только операторы `@reformer/core/behaviors`.
47
+ > В DSL для реакции на изменения используется `onChange`.
48
+
49
+ ### Значения — в модели, не в форме
50
+
51
+ Источник истины значений — модель. Форма (ноды) отражает их; `form.x.setValue(v)` пишет в ту же
52
+ модель.
53
+
54
+ ```typescript
55
+ // ✅ читаем/пишем значение
56
+ model.email = 'a@b.c'; // value-доступ
57
+ model.$.email.value; // сигнал
58
+ model.get(); // весь снимок для submit
59
+
60
+ // В компоненте — реактивно через хуки
61
+ const email = useFormControlValue(form.email);
62
+ ```
63
+
64
+ ### useFormControlValue (CRITICAL)
65
+
66
+ ```typescript
67
+ // WRONG - useFormControlValue returns T directly, NOT { value: T }
68
+ const { value: loanType } = useFormControlValue(control.loanType);
69
+ // Result: loanType is ALWAYS undefined!
70
+
71
+ // CORRECT
72
+ const loanType = useFormControlValue(control.loanType);
73
+
74
+ // ALSO CORRECT - useFormControl returns object
75
+ const { value, issues } = useFormControl(control.loanType);
76
+ ```
77
+
78
+ ### Behaviors принимают СИГНАЛЫ, а не пути/форму
79
+
80
+ ```typescript
81
+ // ❌ WRONG - строковые пути и (form) => ... это старый API, удалён
82
+ enableWhen(path.city, (form) => Boolean(form.country));
83
+ computeFrom(['price', 'quantity'], 'total', (values) => values.price * values.quantity);
84
+
85
+ // ✅ CORRECT - сигналы модели, условие читает model напрямую
86
+ enableWhen(model.$.city, () => Boolean(model.country), { resetOnDisable: true });
87
+ computeFrom([model.$.price, model.$.quantity], model.$.total, (price, qty) => price * qty);
88
+ ```
89
+
90
+ ### Валидаторы — оператор `validate`, а не узел схемы
91
+
92
+ Валидация — **отдельный слой** (`@reformer/core/validation`), а не ключ узла. Схема формы
93
+ (билдер `createForm` / JSON-DSL) правил **не несёт**: они живут в `ValidationDefinition` и уходят в
94
+ сборку полем `validation` (шаги — `flow`). Запуск — уровнем формы:
95
+ `bundle.submission.submit(handler)`, `bundle.validate()`, `bundle.validateStep(id)`; без сборки —
96
+ `validateModel(model, schema)`. У нод методов `validate()` / `submit()` нет.
97
+
98
+ ```typescript
99
+ // ❌ WRONG - ключей value и validators у узла нет, как и записи «имя → узел»
100
+ const schema = {
101
+ email: { value: model.$.email, component: Input, validators: [required(), email()] },
102
+ };
103
+ validate(path.email, required(), 'Email is required'); // старая сигнатура validate() — удалена
104
+ required({ message: 'Email is required' }); // опции message нет — текст живёт в словаре формы
105
+
106
+ // ✅ CORRECT - операторы validate/validateAsync внутри defineValidation; у правила — код
107
+ const emailSchema = defineValidation<MyForm>(({ model }) => {
108
+ validate(model.$.email, [required({ code: 'email.required' }), email()]);
109
+ validateAsync(model.$.email, [
110
+ async (value, { signal }) => {
111
+ const res = await fetch(`/api/free?email=${value}`, { signal });
112
+ return (await res.json()).free ? null : { code: 'email.taken' };
113
+ },
114
+ ]);
115
+ });
116
+ // тексты кодов — словарь формы
117
+ export const formMessages = defineIssueMessages({
118
+ 'email.required': 'Укажите email',
119
+ 'email.taken': 'Email уже занят',
120
+ });
121
+
122
+ // прогон — уровнем формы: замечания сами доезжают до нод, warnings не блокируют.
123
+ // Отклонённое async-правило — сбой: статус `error`, на поле замечание `ruleFailed`.
124
+ const outcome = await bundle.submission.submit(api.send); // createForm({ …, validation: emailSchema })
125
+ ```
126
+
127
+ ### Cross-field — оператор `cross`, а не `ctx.form`
128
+
129
+ ```typescript
130
+ // ❌ WRONG - ctx.form / ctx.setFieldValue / value.value — старый API; ModelValidator(value,scope,root) удалён
131
+ watchField(path.amount, (amount, ctx) => {
132
+ const rate = ctx.form.rate.value.value;
133
+ ctx.setFieldValue('total', amount * rate);
134
+ });
135
+
136
+ // ✅ CORRECT - compute (поведение) на сигналах; cross-field (валидация) через оператор cross
137
+ compute(model.$.total, () => (model.amount ?? 0) * (model.rate ?? 0));
138
+
139
+ // cross-field правило — обычная функция над снимком модели; `cross` берётся из аргумента схемы:
140
+ const amountVsMax = (form: MyForm): ValidationIssue | null =>
141
+ form.amount != null && form.maxAmount != null && form.amount > form.maxAmount
142
+ ? { code: 'amount.tooBig', params: { max: form.maxAmount } }
143
+ : null;
144
+
145
+ const amountSchema = defineValidation<MyForm>(({ model, cross }) => {
146
+ cross(model.$.amount, amountVsMax); // check получает снимок модели, ошибка — на model.$.amount
147
+ });
148
+ ```
149
+
150
+ ### Form-shape types — `interface` или `type`
151
+
152
+ Форму данных можно объявлять и `interface`, и `type` — оба работают с `createModel<T>`,
153
+ `createForm<T>`, `FormGroup<T>` и `FormArray<T>`. Группу от поля `FormGroup` отличает по виду
154
+ значения (обычный объект, а не массив, дата, файл или функция), а не по index signature. Эталонная
155
+ форма (`CreditApplication`) объявлена через `interface`.
156
+
157
+ ```typescript
158
+ // ✅ interface
159
+ export interface PropertyItem {
160
+ type: PropertyType;
161
+ description: string;
162
+ }
163
+
164
+ // ✅ type alias — то же самое
165
+ export type PropertyItem = {
166
+ type: PropertyType;
167
+ description: string;
168
+ };
169
+ ```
170
+
171
+ Единственная оговорка — свои обобщённые хелперы: `interface` не присваивается
172
+ `Record<string, unknown>` (у него нет неявной index signature). Ограничение пиши через
173
+ `T extends object`, а не `T extends Record<string, unknown>`.
174
+
175
+ `number | null` — конвенциональный тип для очищенного поля; встроенные валидаторы
176
+ (`min`, `max`, `minLength`, `maxLength`, `minDate`, `maxDate`, `minAge`, `maxAge`) пропускают
177
+ пустые значения внутри.
178
+
179
+ ### Вид ноды — гардами, а не instanceof
180
+
181
+ `FormControl`, `FormGroup`, `FormArray` — интерфейсы: классов нод в публичном API нет, проверка
182
+ `instanceof` не скомпилируется. Вид ноды различают гарды по метке вида:
183
+
184
+ ```typescript
185
+ // ❌ FormControl — тип, а не класс
186
+ if (node instanceof FormControl) { ... }
187
+
188
+ // ✅ type guards
189
+ import { isFormNode, isFormControl, isFormGroup, isFormArray } from '@reformer/core';
190
+ if (isFormControl(node)) node.updateComponentProps({ placeholder: 'Email' });
191
+ if (isFormArray(node)) node.push();
192
+ ```
@@ -0,0 +1,16 @@
1
+ ## 5. TROUBLESHOOTING
2
+
3
+ | Error | Cause | Solution |
4
+ | -------------------------------------------------------------- | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
5
+ | `'message' does not exist in type 'RuleOptions'` | Текст передан в правило — у правил текста нет | `required({ code: 'phone.required' })` + запись `'phone.required'` в `defineIssueMessages`; см. 36-issue-messages.md |
6
+ | `'string' is not assignable to 'RuleOptions'` | Валидатору передали строку вместо options | `required({ code: 'field.required' })` |
7
+ | `Module has no exported member` | Неверный источник импорта | хуки и `I18nProvider` — из `@reformer/core/react`; `watchField`/примитивы — из `@reformer/core/model`; DSL — из `@reformer/core/behaviors`; фабрики — из `@reformer/core/validators`; удалённое — 17-nonexistent-api.md |
8
+ | `undefined` из `useFormControlValue` | Деструктурировали хук | `const v = useFormControlValue(...)` — без деструктуризации |
9
+ | `enableWhen`/`disableWhen` не срабатывает | Поле не материализовано в форме (элемент массива) | Убедись, что поле есть в схеме `createForm`; для per-item — `applyEach` |
10
+ | `Cycle detected` | Взаимные `compute`/`computeFrom` без стабилизации | Разорви цикл через условие `when` или `peek`; см. 22-cycle-detection.md |
11
+ | Значение поля не пишется | Пишем в форму вместо модели | Значения принадлежат модели: `model.field = ...` / `model.$.field.value = ...` / `form.field.setValue(...)` |
12
+ | Замечания не появляются после submit | Отправка в обход проверки (`api.send(model.get())`) | `await bundle.submission.submit(handler)` — один прогон правил, при отказе вся форма помечается тронутой; без сборки — `await validateModel(model, schema)` |
13
+ | На поле текст вида `loanAmount.required` | Свой код правила без записи в словаре формы | Добавь код в `defineIssueMessages` и подключи `ValidationMessagesProvider messages` (cdk) |
14
+ | `compileSchema: в области «#» селектор «X» повторяется` | Два узла с одним `selector` в одной области схемы | Разные селекторы; см. 37-compiled-schema.md |
15
+ | `createForm: шага «X» нет в потоке` / `FormFlow: шага «X» нет` | `validateStep(id)` / `flow.goTo(id)` с неизвестным шагом | `id` — из `flow.steps`; см. 38-form-flow.md |
16
+ | `flow.goTo(id)` вернул `reason: 'locked'` | Прыжок вперёд через непройденный шаг | Вперёд — `flow.next()`; `goTo` — назад и на открытые шаги |
@@ -0,0 +1,130 @@
1
+ ## Import Patterns
2
+
3
+ ```typescript
4
+ // Модель, форма, уровень формы, типы — из @reformer/core. React здесь нет: корень работает и на сервере
5
+ import {
6
+ createModel,
7
+ createForm,
8
+ createFormFromModel,
9
+ disposeForm, // снять форму из createFormFromModel; сборку снимает bundle.dispose()
10
+ createSubmission, // отправка без сборки (визард в разметке без createForm)
11
+ arrayOf, // массив модели с шаблоном новой строки
12
+ compileSchema, // авторское дерево → CompiledSchema (обычно не нужен: createForm компилирует сам)
13
+ createFormFlow, // поток шагов без сборки
14
+ isFormControl,
15
+ isFormGroup,
16
+ isFormArray,
17
+ } from '@reformer/core';
18
+
19
+ // Привязка к React — хуки, контекст сборки, связка поля с контролом, провайдер локали — из /react
20
+ import {
21
+ useFormBundle,
22
+ useFormControl,
23
+ useFormControlValue,
24
+ useArrayLength,
25
+ useFormFlow, // снимок потока шагов для рендера
26
+ useFormValidation, // стратегия без сборки: submit() — попытка отправки, validate() — проверка
27
+ useIssueMessage, // текст замечания: словарь формы → локаль → английский → код
28
+ I18nProvider,
29
+ } from '@reformer/core/react';
30
+ import type { FieldControlState } from '@reformer/core/react';
31
+
32
+ // Типы
33
+ import type {
34
+ FormModel, // реактивная модель данных
35
+ FormBundle, // результат createForm: { model, form, render, flow, validate, validateStep, submission, dispose, … }
36
+ FormNode, // общий контракт ноды
37
+ FormControl, // нода одного поля
38
+ FormGroup, // нода группы — тип формы для props компонентов
39
+ FormArray, // нода массива под-форм
40
+ ModelArray, // реактивный массив модели (push/removeAt/at/map/length)
41
+ ModelSignals, // дерево ручек ($)
42
+ PathAwareSignal, // ручка, знающая свой путь
43
+ ValidationIssue, // { code, params?, severity? } — без текста
44
+ RuleOptions, // { code?, params? } — опции фабрик правил
45
+ FormSchemaNode, // узел схемы: поле | массив под-форм | подформа | контейнер
46
+ CompiledSchema, // скомпилированная схема: bundle.render.tree
47
+ FormValidation, // правила формы как данные: { rules?, extras?, strategy?, debounce? }
48
+ FormFlowConfig, // поток шагов в конфиге: { steps: [{ id, rules? }] }
49
+ FormFlow, // поток шагов сборки: current, next(), previous(), goTo(id)
50
+ FlowTransition, // итог перехода: moved | stayed
51
+ FormSubmission, // отправка сборки (bundle.submission): submit(handler), submitting
52
+ SubmitResult, // итог bundle.submission.submit: submitted | invalid | error | cancelled | failed
53
+ } from '@reformer/core';
54
+
55
+ // Схема валидации: операторы + раннер — из /validation
56
+ import {
57
+ defineValidation,
58
+ validate,
59
+ validateAsync,
60
+ validateWhen,
61
+ apply, // правила подформы: apply(model.$.group, rules); композиция: apply(rulesA, rulesB)
62
+ applyEach, // правила строк массива: applyEach(model.$.items, rules)
63
+ validateModel, // раннер без сборки: сбор + разнос, Promise<boolean>
64
+ runValidation, // только сбор: Promise<ValidationResult>
65
+ applyValidationResult, // разнос собранного результата по нодам
66
+ } from '@reformer/core/validation';
67
+ import type {
68
+ Rule, // (value) => ValidationIssue | null
69
+ AsyncRule, // (value, { signal }) => Promise<ValidationIssue | null>
70
+ ValidationDefinition, // ({ model, cross }) => void — cross берётся из аргумента схемы
71
+ ValidationResult, // { status, issues, failures }
72
+ } from '@reformer/core/validation';
73
+
74
+ // Валидаторы — чистые фабрики из /validators (кладутся в validate(sig, [...]))
75
+ import { required, min, max, email, minLength, pattern } from '@reformer/core/validators';
76
+
77
+ // Тексты замечаний и локаль без React — из /i18n
78
+ import { defineIssueMessages, resolveIssueMessage, createI18n } from '@reformer/core/i18n';
79
+
80
+ // Декларативный DSL behaviors — из /behaviors
81
+ import {
82
+ defineFormBehavior,
83
+ compute,
84
+ computeFrom,
85
+ copyFrom,
86
+ onChange,
87
+ enableWhen,
88
+ disableWhen,
89
+ transformValue,
90
+ resetWhen,
91
+ syncFields,
92
+ revalidateWhen,
93
+ apply,
94
+ applyEach,
95
+ aggregateInto,
96
+ exclusiveFlag,
97
+ hideWhen, // операторы узлов схемы — исполняет рендерер
98
+ renderEffect,
99
+ } from '@reformer/core/behaviors';
100
+
101
+ // Примитивы над сигналами (принимают сигналы, возвращают cleanup) — из /model, в корне их нет
102
+ import { computeFrom, copyFrom, watchField } from '@reformer/core/model';
103
+ // Утилиты значений модели — тоже из /model
104
+ import {
105
+ deepEqual, // сравнение значений модели (на нём isDirty)
106
+ deferredWritesSettled, // дождаться отложенных записей поведения тика; undefined — ждать нечего
107
+ } from '@reformer/core/model';
108
+ ```
109
+
110
+ ### Форма данных — `interface` или `type`
111
+
112
+ Оба варианта работают одинаково: `createModel<T>`, `createForm<T>`, `FormGroup<T>` и `FormArray<T>`
113
+ принимают и `interface`, и `type`-alias — для корня, вложенных групп и элементов массивов. Группу
114
+ от поля сборка отличает по виду значения (обычный объект — группа; массив объектов — массив
115
+ под-форм; примитив, массив примитивов, дата, файл — поле), а не по index signature.
116
+
117
+ ```typescript
118
+ export interface AddressForm {
119
+ street: string;
120
+ city: string;
121
+ }
122
+
123
+ export interface CoBorrower {
124
+ fullName: string;
125
+ phone: string;
126
+ }
127
+ ```
128
+
129
+ Оговорка для своих обобщённых хелперов: `interface` не присваивается `Record<string, unknown>`, поэтому
130
+ ограничение пиши `T extends object`. См. `30-type-safety-recipes.md`.
@@ -0,0 +1,54 @@
1
+ ## 7. FORM TYPE DEFINITION
2
+
3
+ Форму данных объявляй через `interface` или `type`-alias — оба варианта работают одинаково (см.
4
+ `30-type-safety-recipes.md`). Тип описывает форму ДАННЫХ (то, что кладётся в `createModel`), без
5
+ узлов/сигналов.
6
+
7
+ ```typescript
8
+ // CORRECT form type definition
9
+ interface MyForm {
10
+ // Required fields
11
+ name: string;
12
+ email: string;
13
+
14
+ // Optional fields — конвенция для форм: null («пользователь очистил»)
15
+ phone: string | null;
16
+ age: number | null;
17
+
18
+ // Enum/union types
19
+ status: 'active' | 'inactive';
20
+
21
+ // Nested objects
22
+ address: {
23
+ street: string;
24
+ city: string;
25
+ };
26
+
27
+ // Arrays of objects — model-owned (см. 10-arrays.md)
28
+ items: Array<{
29
+ id: string;
30
+ name: string;
31
+ }>;
32
+ }
33
+
34
+ // Модель создаётся из initial-значений этого типа
35
+ const model = createModel<MyForm>({
36
+ name: '',
37
+ email: '',
38
+ phone: null,
39
+ age: null,
40
+ status: 'active',
41
+ address: { street: '', city: '' },
42
+ items: [],
43
+ });
44
+ ```
45
+
46
+ `number | null` / `string | null` работают со встроенными валидаторами напрямую —
47
+ `min`/`max`/`minLength`/`minDate`/`minAge` пропускают пустые значения внутри, guard `if (v != null)`
48
+ не нужен.
49
+
50
+ Какой ноде формы соответствует поле, решает вид значения: объект — `FormGroup`
51
+ (`form.address.city`), массив объектов — `FormArray` (`form.items.at(0)`), примитив, массив
52
+ примитивов, `Date`, `File` — `FormControl`. Вычисляемые поля (цели `compute`) удобно держать
53
+ отдельным типом и объявлять форму пересечением: `type MyFormFull = MyForm & MyFormDerived` — тогда
54
+ на сервер уходит только ввод (`MyForm`), а вычисляемые ключи отрезаются перед отправкой.