@reformer/core 11.0.0-beta.8 → 11.0.0-beta.9

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 (224) hide show
  1. package/README.md +91 -73
  2. package/dist/behaviors-value-HA_Q03hc.js +108 -0
  3. package/dist/behaviors.js +370 -242
  4. package/dist/{create-model-BBRHhHwK.js → create-model-BvcbNPgL.js} +6 -6
  5. package/dist/date-utils-90-qZbse.js +43 -0
  6. package/dist/{en-CjAqn7h0.js → en-HP8XEych.js} +6 -3
  7. package/dist/form/aggregate-signals.d.ts +21 -63
  8. package/dist/form/behaviors/collections.d.ts +28 -7
  9. package/dist/form/behaviors/context.d.ts +19 -2
  10. package/dist/form/behaviors/index.d.ts +3 -3
  11. package/dist/form/behaviors/internals.d.ts +9 -2
  12. package/dist/form/behaviors/node.d.ts +25 -2
  13. package/dist/form/behaviors/operators.d.ts +17 -9
  14. package/dist/form/behaviors/schema-operators.d.ts +6 -4
  15. package/dist/form/behaviors/types.d.ts +12 -4
  16. package/dist/form/create-form.d.ts +47 -35
  17. package/dist/form/flow/flow.d.ts +87 -0
  18. package/dist/form/form-bundle.d.ts +45 -28
  19. package/dist/form/form-config-base.d.ts +46 -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 +16 -21
  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 +30 -14
  37. package/dist/form/signal-node-registry.d.ts +6 -7
  38. package/dist/form/submission.d.ts +93 -0
  39. package/dist/form/type-guards.d.ts +14 -73
  40. package/dist/form/types/contracts.d.ts +19 -92
  41. package/dist/form/types/field-config.d.ts +27 -0
  42. package/dist/form/types/index.d.ts +5 -57
  43. package/dist/form/types/nodes.d.ts +159 -0
  44. package/dist/form/types/schema-node.d.ts +139 -63
  45. package/dist/form/types/validation-schema.d.ts +23 -27
  46. package/dist/form/validation/context.d.ts +42 -7
  47. package/dist/form/validation/index.d.ts +9 -8
  48. package/dist/form/validation/operators.d.ts +22 -30
  49. package/dist/form/validation/run.d.ts +93 -18
  50. package/dist/form/validation/strategy.d.ts +41 -13
  51. package/dist/form/validation/types.d.ts +74 -20
  52. package/dist/form/validators/date-utils.d.ts +13 -1
  53. package/dist/form/validators/email.d.ts +8 -7
  54. package/dist/form/validators/file-type.d.ts +8 -7
  55. package/dist/form/validators/file-utils.d.ts +2 -1
  56. package/dist/form/validators/future-date.d.ts +8 -7
  57. package/dist/form/validators/index.d.ts +1 -1
  58. package/dist/form/validators/integer.d.ts +8 -7
  59. package/dist/form/validators/is-date.d.ts +11 -9
  60. package/dist/form/validators/is-number.d.ts +11 -9
  61. package/dist/form/validators/max-age.d.ts +13 -9
  62. package/dist/form/validators/max-date.d.ts +8 -7
  63. package/dist/form/validators/max-file-size.d.ts +8 -7
  64. package/dist/form/validators/max-files.d.ts +8 -7
  65. package/dist/form/validators/max-length.d.ts +14 -7
  66. package/dist/form/validators/max-total-file-size.d.ts +8 -7
  67. package/dist/form/validators/max.d.ts +10 -8
  68. package/dist/form/validators/min-age.d.ts +12 -9
  69. package/dist/form/validators/min-date.d.ts +8 -7
  70. package/dist/form/validators/min-file-size.d.ts +8 -7
  71. package/dist/form/validators/min-files.d.ts +11 -9
  72. package/dist/form/validators/min-length.d.ts +14 -7
  73. package/dist/form/validators/min.d.ts +10 -8
  74. package/dist/form/validators/multiple-of.d.ts +10 -8
  75. package/dist/form/validators/non-negative.d.ts +8 -7
  76. package/dist/form/validators/non-zero.d.ts +8 -7
  77. package/dist/form/validators/past-date.d.ts +8 -7
  78. package/dist/form/validators/pattern.d.ts +9 -8
  79. package/dist/form/validators/phone.d.ts +8 -7
  80. package/dist/form/validators/required.d.ts +11 -9
  81. package/dist/form/validators/url.d.ts +20 -13
  82. package/dist/form/validators/validation-issue.d.ts +8 -0
  83. package/dist/i18n/descriptor.d.ts +1 -1
  84. package/dist/i18n/en.json.d.ts +5 -1
  85. package/dist/i18n/locale-store.d.ts +7 -0
  86. package/dist/i18n/message-format.d.ts +24 -4
  87. package/dist/i18n/ru.json.d.ts +5 -1
  88. package/dist/i18n/translator.d.ts +11 -0
  89. package/dist/i18n/validation-message.d.ts +42 -7
  90. package/dist/i18n.d.ts +7 -12
  91. package/dist/i18n.js +66 -283
  92. package/dist/index.d.ts +16 -7
  93. package/dist/index.js +924 -2855
  94. package/dist/loader-BWkigpVe.js +52 -0
  95. package/dist/locale/en.js +1 -1
  96. package/dist/locale/en.json +4 -1
  97. package/dist/locale/ru.js +4 -1
  98. package/dist/locale/ru.json +4 -1
  99. package/dist/locale-CG54AU2Q.js +219 -0
  100. package/dist/locale.js +1 -1
  101. package/dist/model/behaviors-value.d.ts +24 -6
  102. package/dist/model/create-model.d.ts +2 -2
  103. package/dist/model/derived-registry.d.ts +1 -1
  104. package/dist/model/index.d.ts +11 -5
  105. package/dist/model/model-nodes.d.ts +79 -12
  106. package/dist/model/model-signals-proxy.d.ts +5 -6
  107. package/dist/model/model-value-proxy.d.ts +20 -15
  108. package/dist/model/safe-effect.d.ts +33 -1
  109. package/dist/model/types.d.ts +63 -13
  110. package/dist/model.js +20 -18
  111. package/dist/node-kind-C-r6ZMdo.js +15 -0
  112. package/dist/platforms/react/field-binding/field-adapter.d.ts +1 -1
  113. package/dist/platforms/react/field-binding/field-handle.d.ts +5 -2
  114. package/dist/platforms/react/form-bundle-context.d.ts +2 -2
  115. package/dist/platforms/react/hooks/types.d.ts +57 -38
  116. package/dist/platforms/react/hooks/use-form-bundle.d.ts +19 -4
  117. package/dist/platforms/react/hooks/use-form-flow.d.ts +40 -0
  118. package/dist/platforms/react/hooks/use-form-validation.d.ts +12 -6
  119. package/dist/platforms/react/hooks/useArrayLength.d.ts +6 -6
  120. package/dist/platforms/react/hooks/useFormControl.d.ts +7 -9
  121. package/dist/platforms/react/hooks/useFormControlValue.d.ts +18 -19
  122. package/dist/platforms/react/hooks/useSignalSubscription.d.ts +9 -1
  123. package/dist/platforms/react/i18n/context.d.ts +6 -1
  124. package/dist/platforms/react/i18n/hooks.d.ts +16 -10
  125. package/dist/platforms/react/i18n/provider.d.ts +9 -2
  126. package/dist/platforms/react/index.d.ts +6 -4
  127. package/dist/react.d.ts +22 -0
  128. package/dist/react.js +505 -0
  129. package/dist/runtime-token.d.ts +1 -2
  130. package/dist/safe-effect-BbtX3KuN.js +643 -0
  131. package/dist/schema-controller-CpiO2JoK.js +143 -0
  132. package/dist/signals.d.ts +15 -9
  133. package/dist/signals.js +11 -6
  134. package/dist/strategy-CoFSemuP.js +375 -0
  135. package/dist/type-guards-D-N_5Ddh.js +19 -0
  136. package/dist/validation-issue-B9mjI8jz.js +7 -0
  137. package/dist/validation-message-Ced9MGxs.js +194 -0
  138. package/dist/validation.js +9 -9
  139. package/dist/validators/email.js +5 -5
  140. package/dist/validators/future-date.js +8 -8
  141. package/dist/validators/integer.js +3 -3
  142. package/dist/validators/is-date.js +4 -4
  143. package/dist/validators/is-number.js +4 -4
  144. package/dist/validators/max-age.js +8 -9
  145. package/dist/validators/max-date.js +8 -8
  146. package/dist/validators/max-length.js +7 -7
  147. package/dist/validators/max.js +4 -4
  148. package/dist/validators/min-age.js +10 -8
  149. package/dist/validators/min-date.js +3 -3
  150. package/dist/validators/min-length.js +5 -5
  151. package/dist/validators/min.js +4 -4
  152. package/dist/validators/multiple-of.js +11 -7
  153. package/dist/validators/non-negative.js +3 -3
  154. package/dist/validators/non-zero.js +3 -3
  155. package/dist/validators/past-date.js +8 -8
  156. package/dist/validators/pattern.js +5 -5
  157. package/dist/validators/phone.js +11 -9
  158. package/dist/validators/required.js +2 -2
  159. package/dist/validators/url.js +48 -12
  160. package/dist/validators.js +17 -17
  161. package/docs/llms/01-api-reference.md +60 -34
  162. package/docs/llms/02-quick-start.md +111 -80
  163. package/docs/llms/03-api-signatures.md +368 -153
  164. package/docs/llms/04-common-patterns.md +58 -56
  165. package/docs/llms/05-common-mistakes.md +69 -44
  166. package/docs/llms/06-troubleshooting.md +14 -9
  167. package/docs/llms/07-complete-import.md +74 -43
  168. package/docs/llms/08-form-types.md +11 -4
  169. package/docs/llms/09-formschema.md +129 -81
  170. package/docs/llms/10-arrays.md +106 -75
  171. package/docs/llms/11-async-watchfield.md +2 -2
  172. package/docs/llms/13-multi-step.md +134 -63
  173. package/docs/llms/14-extended-mistakes.md +20 -17
  174. package/docs/llms/15-project-structure.md +221 -74
  175. package/docs/llms/16-ui-components.md +55 -42
  176. package/docs/llms/17-nonexistent-api.md +199 -32
  177. package/docs/llms/18-conditional-fields.md +66 -32
  178. package/docs/llms/19-reading-values.md +15 -3
  179. package/docs/llms/20-compute-vs-watch.md +16 -10
  180. package/docs/llms/21-array-operations.md +51 -27
  181. package/docs/llms/22-cycle-detection.md +1 -1
  182. package/docs/llms/23-copy-from.md +2 -2
  183. package/docs/llms/24-sync-fields.md +6 -5
  184. package/docs/llms/25-reset-when.md +14 -7
  185. package/docs/llms/26-transform-value.md +4 -4
  186. package/docs/llms/27-revalidate-when.md +16 -15
  187. package/docs/llms/28-submit-and-reset.md +276 -128
  188. package/docs/llms/29-async-preload.md +39 -29
  189. package/docs/llms/30-type-safety-recipes.md +105 -51
  190. package/docs/llms/31-async-validator-debounce.md +96 -53
  191. package/docs/llms/33-validation-strategy.md +177 -65
  192. package/docs/llms/34-i18n.md +69 -43
  193. package/docs/llms/35-unified-contract.md +100 -39
  194. package/docs/llms/36-issue-messages.md +284 -0
  195. package/docs/llms/37-compiled-schema.md +332 -0
  196. package/docs/llms/38-form-flow.md +397 -0
  197. package/llms-index.json +1 -1
  198. package/llms.txt +6084 -3420
  199. package/package.json +23 -4
  200. package/dist/behaviors-value-C7dSEhCc.js +0 -134
  201. package/dist/date-utils-dTT_x_Be.js +0 -31
  202. package/dist/form/create-core-form.d.ts +0 -72
  203. package/dist/form/factories/index.d.ts +0 -6
  204. package/dist/form/factories/node-factory.d.ts +0 -189
  205. package/dist/form/form-submitter.d.ts +0 -121
  206. package/dist/form/nodes/array-node.d.ts +0 -276
  207. package/dist/form/nodes/field-node.d.ts +0 -297
  208. package/dist/form/nodes/form-node.d.ts +0 -326
  209. package/dist/form/nodes/group-node.d.ts +0 -284
  210. package/dist/form/nodes/model-array-node.d.ts +0 -126
  211. package/dist/form/nodes/subscription-manager.d.ts +0 -180
  212. package/dist/form/status-machine.d.ts +0 -115
  213. package/dist/form/types/deep-schema.d.ts +0 -107
  214. package/dist/form/types/form-proxy.d.ts +0 -141
  215. package/dist/form/unique-id.d.ts +0 -45
  216. package/dist/form/validation/config.d.ts +0 -73
  217. package/dist/form/validation/error-handler.d.ts +0 -166
  218. package/dist/form/validators/validation-error.d.ts +0 -8
  219. package/dist/index-BrNkQj1y.js +0 -105
  220. package/dist/loader-Dv_Hl7JC.js +0 -216
  221. package/dist/model-value-proxy-caTmAiUd.js +0 -419
  222. package/dist/node-BeV_lV5i.js +0 -100
  223. package/dist/strategy-DgzELWno.js +0 -204
  224. package/dist/validation-error-B-U3RsV0.js +0 -11
@@ -1,8 +1,22 @@
1
- import { ElementType } from 'react';
2
1
  import { Signal } from '../../signals';
3
2
  /**
4
- * Минимальный контракт реактивного массива модели ({@link FormSchemaNode.array}).
5
- * Совпадает по форме с рантайм-фасадом `model.<array>` (см. `ModelArray`); рендерерский
3
+ * Ссылка на компонент узла схемы: то, что слой представления умеет нарисовать. Для React это
4
+ * компонент (функция либо объект `forwardRef`/`memo`) или имя HTML-тега (`'div'`, `'h3'`).
5
+ *
6
+ * Ядро ссылку не интерпретирует и о слое представления не знает: она доезжает до рендерера как
7
+ * есть, а к типу своего UI-слоя её приводит уже он.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * const byComponent: ComponentRef = Input; // React-компонент
12
+ * const byTag: ComponentRef = 'section'; // HTML-тег
13
+ * ```
14
+ *
15
+ * @group Types
16
+ */
17
+ export type ComponentRef = string | object;
18
+ /**
19
+ * Контракт реактивного массива модели — фасада `model.<массив>`. Рендерерский
6
20
  * `RenderModelArrayControl` — его расширение (добавляет `move`).
7
21
  *
8
22
  * @group Types
@@ -17,80 +31,95 @@ export interface SchemaArrayControl {
17
31
  removeAt(index: number): void;
18
32
  }
19
33
  /**
20
- * Узел единой схемы M1 — layout-дерево, обходимое `createFormFromModel({ model, schema })`
21
- * и рендерерами (schema-валидация живёт отдельно — `@reformer/core/validation`).
34
+ * Ручка значения из дерева `model.$`: лист (`model.$.email`) или массив целиком
35
+ * (`model.$.tags`). Группа ручкой значения не является — у неё нет пути.
22
36
  *
23
- * Узел совмещает несколько ролей (различаются рантаймом по форме):
24
- * - **поле** — `{ model: model.$.x, component, componentProps }`;
25
- * - **массив под-форм** — `{ model: model.$.items, item: (model) => узел }`;
26
- * - **подформа** — `{ model: model.$.group, part: (model) => узел }`;
27
- * - **контейнер/ветка** — вложенные узлы (`children`), опц. условие `when`;
28
- * - **record-of-fields** — под-узлы под произвольными именованными ключами (индексная сигнатура).
37
+ * @group Types
38
+ */
39
+ export type SchemaValueHandle = Signal<any> & {
40
+ readonly __path: string;
41
+ };
42
+ /**
43
+ * Ручка массива из дерева `model.$` (`model.$.phones`) — ручка значения с длиной.
29
44
  *
30
- * Индексная сигнатура (`[key: string]: unknown`) отражает свободный рекурсивный обход: под-узлы
31
- * допустимы под любым ключом. Известные поля типизированы (даёт автокомплит и проверку их типов).
45
+ * @group Types
46
+ */
47
+ export type SchemaArrayHandle = SchemaValueHandle & {
48
+ readonly length: number;
49
+ };
50
+ /**
51
+ * Ручка группы из дерева `model.$` (`model.$.address`).
32
52
  *
33
53
  * @group Types
34
54
  */
35
- export interface FormSchemaNode {
36
- /**
37
- * Привязка узла к части модели — ручка из дерева `model.$`. Чем узел является, решают ручка и
38
- * соседние ключи:
39
- * - лист или массив (`model.$.email`, `model.$.tags`) — **поле**;
40
- * - массив вместе с {@link FormSchemaNode.item} — **массив под-форм**;
41
- * - группа вместе с {@link FormSchemaNode.part} — **подформа**.
42
- *
43
- * Узел распознаётся по значению, а не по имени ключа: в записи «имя поля → узел» поле данных
44
- * может называться `model`, и тогда под этим ключом лежит обычный вложенный узел.
45
- */
46
- model?: unknown;
55
+ export interface SchemaGroupHandle {
56
+ peek(): object | null | undefined;
57
+ /** Признак «не ручка значения»: у листа и массива путь есть, у группы — нет. */
58
+ readonly __path?: never;
59
+ }
60
+ /** Ключи, общие для узла любого вида. */
61
+ interface SchemaNodeBase {
47
62
  /**
48
- * Прежняя запись привязки поля — то же, что {@link FormSchemaNode.model}.
49
- *
50
- * @deprecated Пишите `model: model.$.<path>`.
63
+ * UI-компонент либо нативный HTML-тег (`'div'`, `'p'`, `'h3'`) для презентационной вёрстки
64
+ * прямо в схеме. Опционален: ядро работает без UI и `component` не интерпретирует — он доезжает
65
+ * до рендерера как есть.
51
66
  */
52
- value?: unknown;
67
+ component?: ComponentRef;
53
68
  /**
54
- * UI-компонент либо нативный HTML-тег (`'div'`, `'p'`, `'h3'`) для презентационной вёрстки
55
- * прямо в схеме. Опционален: core-часть работает без UI (значение/валидация) и `component`
56
- * не интерпретирует — он доезжает до рендерера как есть.
69
+ * Пропсы компонента. Вложенных узлов здесь нет: обход схемы в пропсы не заглядывает, дети узла
70
+ * пишутся в `children`.
57
71
  */
58
- component?: ElementType;
59
- /** Props компонента. Также «клапан» для вложенности под-узлов (напр. steps визарда). */
60
72
  componentProps?: Record<string, unknown>;
61
- updateOn?: 'change' | 'blur' | 'submit';
62
- disabled?: boolean;
63
- /** Задержка (мс) перед запуском асинхронной валидации. */
64
- debounce?: number;
65
- /** Идентификатор узла (для wizard/tabs/renderBehavior). */
73
+ /** Идентификатор узла: по нему узел адресуют поведение (`schema.node(...)`) и шаги визарда. */
66
74
  selector?: string;
67
75
  /**
68
- * ⚠️ Рантайм этого поля НЕ ЧИТАЕТ — `renderer-react` берёт testId из `componentProps.testId`
69
- * (иначе выводит из пути сигнала). Поле оставлено только потому, что `RenderSchemaNode`
70
- * рендерера объявляет свой одноимённый; пишите `componentProps: { testId: '…' }`.
71
- */
72
- testId?: string;
73
- /**
74
- * Содержимое узла: под-узлы (даёт контекстную типизацию вложенным литералам — value/validators/when)
75
- * и текстовые части. Текст (литерал, число, сигнал модели) — такой же ребёнок, как узел: core его
76
- * не интерпретирует (обход пропускает примитивы и не спускается внутрь сигнала), а рендерер
77
- * выводит на своём месте в порядке следования.
78
- */
79
- children?: readonly (FormSchemaNode | string | number | Signal<any>)[];
80
- /**
81
- * Прежняя запись привязки массива под-форм — фасад `model.<path>` (вместе с `item`).
76
+ * Прежний ключ привязки поля — его нет: пишите `model: model.$.<поле>`.
82
77
  *
83
- * @deprecated Пишите `model: model.$.<path>`.
78
+ * Запрет объявлен явно: в `children` узел стоит в объединении с реактивным текстом
79
+ * (`Signal`, у которого есть `value`), и без него прежний ключ проходил проверку лишних ключей.
84
80
  */
85
- array?: SchemaArrayControl;
86
- /** Схема элемента массива: под-модель элемента → узел поддерева. */
87
- item?: (model: any) => FormSchemaNode;
81
+ value?: never;
82
+ /** Прежний ключ привязки массива — его нет: пишите `model: model.$.<массив>` вместе с `item`. */
83
+ array?: never;
84
+ }
85
+ /**
86
+ * Поле: узел, привязанный к ручке значения — листу или массиву целиком (мультивыбор, теги, файлы).
87
+ *
88
+ * @example
89
+ * ```ts
90
+ * { model: model.$.email, component: Input, componentProps: { label: 'Email' } }
91
+ * ```
92
+ *
93
+ * @group Types
94
+ */
95
+ export interface SchemaFieldNode extends SchemaNodeBase {
96
+ /** Привязка поля — ручка значения `model.$.<лист | массив>`. */
97
+ model: SchemaValueHandle;
88
98
  /**
89
- * Подформа: под-модель группы → узел поддерева. Часть объявляется один раз и подключается к
90
- * любой группе той же формы данных: `{ model: model.$.registrationAddress, part: address }`.
91
- * Привязки внутри части идут через `$` полученной под-модели.
99
+ * Поле создаётся отключённым — и остаётся: это постоянная блокировка (вычисляемые `total`,
100
+ * `fullName`). `enable()` группы, массива или формы её не снимает; снимает только явный
101
+ * `enable()` самого поля либо оператор `enableWhen` на нём.
92
102
  */
93
- part?: (model: any) => FormSchemaNode;
103
+ disabled?: boolean;
104
+ item?: never;
105
+ part?: never;
106
+ children?: never;
107
+ }
108
+ /**
109
+ * Массив под-форм: у каждой строки своя форма, разметку строки строит `item`.
110
+ *
111
+ * @example
112
+ * ```ts
113
+ * { model: model.$.phones, component: FieldArray, item: (row) => ({ children: [...] }) }
114
+ * ```
115
+ *
116
+ * @group Types
117
+ */
118
+ export interface SchemaArrayNode extends SchemaNodeBase {
119
+ /** Привязка — ручка массива `model.$.<массив>`. */
120
+ model: SchemaArrayHandle;
121
+ /** Схема строки: под-модель строки → узел поддерева. */
122
+ item: (model: any) => FormSchemaNode;
94
123
  /**
95
124
  * Шаблон нового элемента массива для кнопки «Добавить»: либо готовое значение, либо фабрика
96
125
  * `() => value`. Запасной путь — для форм, чья модель создаётся из данных без кода: шаблон,
@@ -100,6 +129,53 @@ export interface FormSchemaNode {
100
129
  * рантайм различает по `typeof initialValue === 'function'`.
101
130
  */
102
131
  initialValue?: unknown;
103
- /** Свободная вложенность: record-of-fields и произвольные под-узлы. */
104
- [key: string]: unknown;
132
+ part?: never;
133
+ children?: never;
134
+ }
135
+ /**
136
+ * Подформа: часть схемы, подключённая к группе модели. Часть объявляется один раз и подключается
137
+ * к любой группе той же формы данных; привязки внутри части идут через `$` полученной под-модели.
138
+ *
139
+ * @example
140
+ * ```ts
141
+ * { model: model.$.registrationAddress, part: address }
142
+ * ```
143
+ *
144
+ * @group Types
145
+ */
146
+ export interface SchemaPartNode extends SchemaNodeBase {
147
+ /** Привязка — ручка группы `model.$.<группа>`. */
148
+ model: SchemaGroupHandle;
149
+ /** Часть схемы: под-модель группы → узел поддерева. */
150
+ part: (model: any) => FormSchemaNode;
151
+ item?: never;
152
+ children?: never;
153
+ }
154
+ /** Ребёнок контейнера: вложенный узел либо текстовая часть — литерал, число, сигнал модели. */
155
+ export type SchemaChild = FormSchemaNode | string | number | Signal<any>;
156
+ /**
157
+ * Контейнер: узел с детьми (секция, шаг визарда, html-тег). Текст — такой же ребёнок, как узел:
158
+ * ядро его не интерпретирует, а рендерер выводит на своём месте в порядке следования.
159
+ *
160
+ * @example
161
+ * ```ts
162
+ * { component: Section, componentProps: { title: 'Контакты' }, children: [phoneField, emailField] }
163
+ * ```
164
+ *
165
+ * @group Types
166
+ */
167
+ export interface SchemaContainerNode extends SchemaNodeBase {
168
+ children?: readonly SchemaChild[];
169
+ model?: never;
170
+ item?: never;
171
+ part?: never;
105
172
  }
173
+ /**
174
+ * Узел схемы формы: поле, массив под-форм, подформа или контейнер — см. описание модуля.
175
+ *
176
+ * Тип закрыт: опечатка в ключе, группа без `part` и `item` не на массиве — ошибки компиляции.
177
+ *
178
+ * @group Types
179
+ */
180
+ export type FormSchemaNode = SchemaFieldNode | SchemaArrayNode | SchemaPartNode | SchemaContainerNode;
181
+ export {};
@@ -1,36 +1,32 @@
1
- import { FormValue, ValidationError } from './index';
2
- import { FormModel } from '../../model/types';
3
1
  /**
4
- * Чистый синхронный валидатор поля (legacy-сигнатура `(value, scope, root)`).
2
+ * Типы фабрик валидаторов (`@reformer/core/validators`).
5
3
  *
6
- * @deprecated Осиротевший остаток удалённого дерево-движка (`validateFormModel`). Живой контракт —
7
- * `Rule<T> = (value) => ValidationError | null` из `@reformer/core/validation`: правила
8
- * передаются в `validate(sig, [rules])` внутри `defineValidationSchema` и запускаются
9
- * `validateModel(model, schema)`. Cross-field — оператор `cross(sig, (f) => …)` над снапшотом
10
- * `model.get()` (третий аргумент `root` больше не нужен). Тип экспортируется только ради
11
- * обратной совместимости (`SchemaValidator`-union).
4
+ * Контракт валидации — `@reformer/core/validation`: `defineValidation` + операторы
5
+ * (`validate`/`validateAsync`/`validateWhen`/`cross`/`apply`/`applyEach`) и раннер
6
+ * `validateModel(model, definition)`; правила там — `Rule<T> = (value) => issue | null`.
12
7
  */
13
- export type Validator<TForm, TField> = (value: TField, scope: unknown, root: FormModel<TForm>) => ValidationError | null;
14
8
  /**
15
9
  * Опции валидатора-фабрики (`required()`/`pattern()`/…). Передаются вторым (или последним)
16
- * аргументом в фабрику и попадают в возвращаемую {@link ValidationError}.
10
+ * аргументом в фабрику и попадают в возвращаемое {@link ValidationIssue}.
11
+ *
12
+ * Текста среди опций нет: правило сообщает только код и параметры, а строку по коду даёт слой
13
+ * представления (`resolveIssueMessage` из `@reformer/core/i18n`). Нужен свой текст для
14
+ * конкретного поля — дайте правилу свой код и запись в словаре формы.
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * // правило
19
+ * required({ code: 'loanAmount.required' });
20
+ * // словарь формы (`defineIssueMessages`)
21
+ * const messages = defineIssueMessages({ 'loanAmount.required': 'Укажите сумму кредита' });
22
+ * ```
17
23
  */
18
- export interface ValidateOptions {
19
- /** Готовое сообщение об ошибке. Если не задано, валидаторы кладут `''`, и отображаемый текст
20
- * берётся из словаря локали по `code` (`validation.<code>`; см. `resolveValidationError` в
21
- * `@reformer/core/i18n`). Явное сообщение важнее словаря, но не переключается вместе с языком —
22
- * для локализуемого текста используйте {@link ValidateOptions.messageKey}. */
23
- message?: string;
24
+ export interface RuleOptions {
24
25
  /**
25
- * Ключ сообщения в словаре приложения — локализуемый текст автора правила. Если ключ есть в
26
- * активной локали, он важнее и `message`, и текста по `code`; если нет — в ход идёт `message`.
27
- *
28
- * @example
29
- * ```ts
30
- * required({ messageKey: 'profile.name.required', message: 'Name is required' });
31
- * ```
26
+ * Свой код вместо встроенного. По нему текст ищется сначала в словаре формы, затем в локали
27
+ * (`validation.<code>`); принято именовать `<поле>.<правило>`.
32
28
  */
33
- messageKey?: string;
34
- /** Параметры ошибки (подстановка в шаблон сообщения / i18n). */
35
- params?: Record<string, FormValue>;
29
+ code?: string;
30
+ /** Дополнительные параметры замечания: подставляются в текст поверх параметров правила. */
31
+ params?: Readonly<Record<string, unknown>>;
36
32
  }
@@ -1,4 +1,6 @@
1
- import { FormModel, PathAwareSignal, ValidationError } from '../../index';
1
+ import { FormModel, PathAwareSignal } from '../../model/types';
2
+ import { ValidationIssue } from '../types/contracts';
3
+ import { ValidationFailure } from './types';
2
4
  /** Сток одного прогона: модель scope, накопленные ошибки, незавершённые async-правила, гейты. */
3
5
  export interface VContext {
4
6
  /**
@@ -6,16 +8,39 @@ export interface VContext {
6
8
  * это вся модель; внутри `apply(ручка, схема)` / `applyEach` — под-модель группы или строки.
7
9
  */
8
10
  model: FormModel<unknown>;
9
- /** Корень прогона — модель, с которой вызван раннер. Правило получает его третьим аргументом. */
10
- root: FormModel<unknown>;
11
- /** Накопленные ошибки по сигналу (ключ — идентичность `PathAwareSignal`). */
12
- errors: Map<PathAwareSignal<unknown>, ValidationError[]>;
11
+ /**
12
+ * Накопленные ошибки по сигналу (ключ — идентичность `PathAwareSignal`). Здесь и поля выключенной
13
+ * ветки `validateWhen` — с пустым списком: при разносе их прежние ошибки гасятся.
14
+ */
15
+ issues: Map<PathAwareSignal<unknown>, ValidationIssue[]>;
16
+ /**
17
+ * Поля, правила которых в этом прогоне действительно выполнялись — ветка активна. Только их разнос
18
+ * с `touch` помечает тронутыми: поле выключенной ветки пользователь не видел.
19
+ */
20
+ checked: Set<PathAwareSignal<unknown>>;
13
21
  /** Незавершённые async-правила (раннер их дожидается). */
14
22
  pending: Promise<void>[];
23
+ /** Поля с async-правилами: на время ожидания раннер держит их ноды в `pending`. */
24
+ asyncFields: Set<PathAwareSignal<unknown>>;
25
+ /**
26
+ * Правила, которые не вернули результат. Список — один на весь прогон: области `apply` /
27
+ * `applyEach` получают поверхностную копию контекста и пишут в ту же ссылку.
28
+ */
29
+ failures: ValidationFailure[];
15
30
  /** Стек активных условий `validateWhen` (все должны быть истинны, чтобы правило сработало). */
16
31
  whenStack: Array<() => boolean>;
17
32
  /** Отмена устаревшего прогона: прокидывается в async-правила, гасит роутинг после await. */
18
33
  signal: AbortSignal;
34
+ /**
35
+ * Определение, правила которого исполняются сейчас: корень прогона либо схема, подключённая
36
+ * `apply` / `applyEach`. Замечание помечается им: так разнос отличает замечания одного
37
+ * определения, полученные разными прогонами (шаг и вся форма), от замечаний чужого.
38
+ */
39
+ origin: object;
40
+ /** Замечания по полю и определению-источнику. Одна ссылка на весь прогон, как `failures`. */
41
+ origins: Map<PathAwareSignal<unknown>, Map<object, ValidationIssue[]>>;
42
+ /** Определения, исполненные в прогоне: за их замечания на проверенных полях прогон отвечает. */
43
+ executed: Set<object>;
19
44
  }
20
45
  /**
21
46
  * Открыть ambient-окно на время `fn` и закрыть его в `finally`.
@@ -26,7 +51,17 @@ export interface VContext {
26
51
  export declare function runWithContext<R>(ctx: VContext, fn: () => R): R;
27
52
  /** Контекст активного прогона либо понятная ошибка, если оператор вызван вне схемы. */
28
53
  export declare function requireCtx(op: string): VContext;
29
- /** «Тронуть» сигнал: гарантирует его наличие в `errors` (даже пустым) → он попадёт в очистку. */
30
- export declare function touch(ctx: VContext, sig: PathAwareSignal<unknown>): ValidationError[];
54
+ /**
55
+ * «Тронуть» сигнал: гарантирует его наличие в замечаниях прогона (даже пустым) → он попадёт в
56
+ * очистку. Возвращает список текущего определения ({@link VContext.origin}): в него и пишут правила.
57
+ */
58
+ export declare function touch(ctx: VContext, sig: PathAwareSignal<unknown>): ValidationIssue[];
59
+ /** Исполнить определение: его замечания помечаются им же. */
60
+ export declare function withOrigin(ctx: VContext, origin: object, fn: () => void): void;
31
61
  /** Активна ли текущая ветка (все `validateWhen`-условия истинны). */
32
62
  export declare const gated: (ctx: VContext) => boolean;
63
+ /**
64
+ * Правила поля выполняются: ветка активна. Поле запоминается проверенным ({@link VContext.checked}).
65
+ * Без активной ветки — `false`, и поле остаётся только в `issues` для гашения.
66
+ */
67
+ export declare function checking(ctx: VContext, sig: PathAwareSignal<unknown>): boolean;
@@ -1,11 +1,12 @@
1
1
  /**
2
2
  * Декларативный контракт СХЕМЫ ВАЛИДАЦИИ — `@reformer/core/validation`.
3
3
  *
4
- * Схема валидации — обычная функция `(ctx: { model }) => void`. Внутри вызываются свободные операторы
5
- * (`validate`/`validateAsync`/`validateWhen`/`cross`/`each`/`apply`), которые САМИ пишут ошибки в ambient-сток
6
- * текущего прогона — автор не видит ни коллектора, ни `getNodeForSignal`, ни `.push`. Внешний раннер
7
- * {@link validateModel} открывает ambient-окно на время СИНХРОННОГО прогона схемы, дожидается async-правил
8
- * и разносит ошибки по нодам формы (`getNodeForSignal(sig).setErrors(...)`), гася поля, ставшие валидными.
4
+ * Схема валидации — обычная функция `({ model, cross }) => void`. Внутри вызываются свободные операторы
5
+ * (`validate`/`validateAsync`/`validateWhen`/`apply`/`applyEach`) и `cross` области, которые САМИ пишут ошибки в
6
+ * ambient-сток текущего прогона — автор не видит ни коллектора, ни `getNodeForSignal`, ни `.push`.
7
+ * Раннер открывает ambient-окно на время СИНХРОННОГО прогона схемы и дожидается async-правил
8
+ * ({@link runValidation} — сбор результата), затем разносит ошибки по нодам формы, гася поля, ставшие
9
+ * валидными ({@link applyValidationResult}); {@link validateModel} делает оба шага и отвечает `boolean`.
9
10
  *
10
11
  * Зеркалит контракт поведения (`@reformer/core/behaviors`): тот же ambient-стиль, но отдельный слой —
11
12
  * валидация НЕ реактивна (прогон по требованию: submit/шаг), поведение — реактивно (живые подписки).
@@ -17,7 +18,7 @@
17
18
  * @group Validation
18
19
  * @module form/validation
19
20
  */
20
- export type { Rule, AsyncRule, ValidationSchema } from './types';
21
- export { validate, validateAsync, validateWhen, cross, each, apply, applyEach } from './operators';
22
- export { defineValidationSchema, validateModel } from './run';
21
+ export type { Rule, AsyncRule, ValidationDefinition, ValidationScope, ValidationStatus, ValidationFailure, ValidationResult, } from './types';
22
+ export { validate, validateAsync, validateWhen, apply, applyEach } from './operators';
23
+ export { defineValidation, runValidation, applyValidationResult, validateModel } from './run';
23
24
  export * from './strategy';
@@ -1,5 +1,5 @@
1
- import { FormModel, ModelArray, PathAwareSignal, ValidationError } from '../../index';
2
- import { AsyncRule, Rule, ValidationSchema } from './types';
1
+ import { FormModel, ModelArray, PathAwareSignal } from '../../model/types';
2
+ import { AsyncRule, Rule, ValidationDefinition, ValidationScope } from './types';
3
3
  /**
4
4
  * Синхронные правила поля.
5
5
  *
@@ -8,38 +8,39 @@ import { AsyncRule, Rule, ValidationSchema } from './types';
8
8
  *
9
9
  * @example
10
10
  * ```ts
11
- * validate(model.$.loanAmount, [required({ message: 'Сумма' }), min(50000)]);
11
+ * validate(model.$.loanAmount, [required({ code: 'loanAmount.required' }), min(50000)]);
12
12
  * validate(model.$.tags, [required(), maxLength(3)]); // tags: string[] — массив как значение
13
13
  * ```
14
14
  */
15
15
  export declare function validate<TField>(sig: PathAwareSignal<TField>, rules: Rule<TField>[]): void;
16
16
  /**
17
- * Асинхронные правила поля (зеркалит движковое разделение `validators` / `asyncValidators`).
18
- * Раннер дожидается их и прокидывает `AbortSignal` для отмены устаревших ответов.
17
+ * Асинхронные правила поля. Раннер дожидается их и прокидывает `AbortSignal` для отмены
18
+ * устаревших ответов.
19
+ *
20
+ * Отклонённое правило (сеть, исключение) — сбой проверки: он попадает в `failures` результата
21
+ * прогона, статус прогона становится `error`, а на поле ложится блокирующая ошибка `ruleFailed`
22
+ * (текст — из словаря локали). Отмена устаревшего прогона сбоем не считается.
19
23
  *
20
24
  * @example
21
25
  * ```ts
22
26
  * validateAsync(model.$.username, [async (v, { signal }) => {
23
27
  * const r = await fetch(`/api/free?u=${v}`, { signal });
24
- * return (await r.json()).free ? null : { code: 'taken', message: 'Занято' };
28
+ * return (await r.json()).free ? null : { code: 'taken' };
25
29
  * }]);
26
30
  * ```
27
31
  */
28
32
  export declare function validateAsync<TField>(sig: PathAwareSignal<TField>, rules: AsyncRule<TField>[]): void;
29
33
  /**
30
34
  * Условная валидация: правила внутри `cb` активны, только пока `cond()` истинно; иначе их поля
31
- * ГАСЯТСЯ (пустой bucket → `setErrors([])`). Не трогает включение/сброс поля — это дело поведения (`enableWhen`).
35
+ * ГАСЯТСЯ (пустой bucket → `setIssues([])`). Не трогает включение/сброс поля — это дело поведения (`enableWhen`).
32
36
  */
33
37
  export declare function validateWhen(cond: () => boolean, cb: () => void): void;
34
38
  /**
35
- * Cross-field правило: `fn` получает СНАПШОТ модели текущей области (`model.get()`) и вешает
36
- * ошибку на `sig`.
39
+ * Область схемы над моделью: сама модель и `cross`, снимок которого — эта модель.
37
40
  *
38
- * Область — та модель, с которой запущена схема: в корне прогона это вся форма, а в схеме,
39
- * подключённой через {@link apply} / {@link applyEach}, — под-модель группы или строки массива.
40
- * Поэтому правила подформы пишутся один раз и не знают, где она стоит.
41
+ * @internal
41
42
  */
42
- export declare function cross<TSnapshot>(sig: PathAwareSignal<unknown>, fn: (form: TSnapshot) => ValidationError | null): void;
43
+ export declare function scopeOf<T>(model: FormModel<T>): ValidationScope<T>;
43
44
  /** Привязка к группе: ручка `model.$.<группа>` (тип значения — из её `peek()`). */
44
45
  interface GroupBinding<V> {
45
46
  peek(): V;
@@ -50,16 +51,16 @@ interface ArrayBinding<U> {
50
51
  }
51
52
  /**
52
53
  * Применить схему к КАЖДОМУ элементу массива под-форм. Схема элемента — обычная
53
- * `ValidationSchema` над типом элемента: тот же набор правил подключается и к группе через
54
+ * `ValidationDefinition` над типом элемента: тот же набор правил подключается и к группе через
54
55
  * {@link apply}.
55
56
  *
56
57
  * У каждого элемента своя область: `cross` внутри схемы получает снапшот элемента.
57
58
  *
58
59
  * @example
59
60
  * ```ts
60
- * const propertyRules = defineValidationSchema<Property>(({ model }) => {
61
+ * const propertyRules = defineValidation<Property>(({ model, cross }) => {
61
62
  * validate(model.$.type, [required()]);
62
- * cross<Property>(model.$.estimatedValue, (property) =>
63
+ * cross(model.$.estimatedValue, (property) =>
63
64
  * property.hasEncumbrance && property.estimatedValue < 100_000 ? tooCheap : null
64
65
  * );
65
66
  * });
@@ -67,16 +68,7 @@ interface ArrayBinding<U> {
67
68
  * applyEach(model.$.properties, propertyRules);
68
69
  * ```
69
70
  */
70
- export declare function applyEach<U extends object>(array: ArrayBinding<U> | ModelArray<U>, schema: ValidationSchema<U>): void;
71
- /**
72
- * Применить под-правила к КАЖДОМУ элементу массива — прежняя запись {@link applyEach}.
73
- *
74
- * Колбэк получает под-модель элемента, но области не создаёт: `cross` внутри него видит снапшот
75
- * корня прогона.
76
- *
77
- * @deprecated Пишите `applyEach(model.$.<массив>, schema)`.
78
- */
79
- export declare function each<U extends object>(arr: ArrayBinding<U> | ModelArray<U>, itemFn: (item: FormModel<U>) => void): void;
71
+ export declare function applyEach<U extends object>(array: ArrayBinding<U> | ModelArray<U>, schema: ValidationDefinition<U>): void;
80
72
  /**
81
73
  * Подключить схему подформы к группе модели — одной или нескольким.
82
74
  *
@@ -85,7 +77,7 @@ export declare function each<U extends object>(arr: ArrayBinding<U> | ModelArray
85
77
  *
86
78
  * @example
87
79
  * ```ts
88
- * const addressRules = defineValidationSchema<Address>(({ model }) => {
80
+ * const addressRules = defineValidation<Address>(({ model }) => {
89
81
  * validate(model.$.city, [required()]);
90
82
  * });
91
83
  *
@@ -96,14 +88,14 @@ export declare function each<U extends object>(arr: ArrayBinding<U> | ModelArray
96
88
  * );
97
89
  * ```
98
90
  */
99
- export declare function apply<V extends object | null | undefined>(group: GroupBinding<V> | GroupBinding<V>[], schema: ValidationSchema<NonNullable<V>>): void;
91
+ export declare function apply<V extends object | null | undefined>(group: GroupBinding<V> | GroupBinding<V>[], schema: ValidationDefinition<NonNullable<V>>): void;
100
92
  /**
101
93
  * Композиция схем над ТОЙ ЖЕ моделью области — так форма собирается из схем шагов.
102
94
  *
103
95
  * @example
104
96
  * ```ts
105
- * const formRules = defineValidationSchema<Form>(() => apply(loanRules, contactsRules));
97
+ * const formRules = defineValidation<Form>(() => apply(loanRules, contactsRules));
106
98
  * ```
107
99
  */
108
- export declare function apply<T>(...schemas: ValidationSchema<T>[]): void;
100
+ export declare function apply<T>(...schemas: ValidationDefinition<T>[]): void;
109
101
  export {};