@reformer/core 11.0.0-beta.3 → 11.0.0-beta.5

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 (130) hide show
  1. package/dist/behaviors.d.ts +1 -1
  2. package/dist/behaviors.js +37 -37
  3. package/dist/create-model-Dxh9cfLp.js +97 -0
  4. package/dist/form/behaviors/collections.d.ts +50 -0
  5. package/dist/form/behaviors/context.d.ts +23 -0
  6. package/dist/form/behaviors/index.d.ts +28 -0
  7. package/dist/form/behaviors/internals.d.ts +26 -0
  8. package/dist/form/{behaviors-node.d.ts → behaviors/node.d.ts} +3 -3
  9. package/dist/form/behaviors/operators.d.ts +51 -0
  10. package/dist/form/behaviors/types.d.ts +17 -0
  11. package/dist/form/create-core-form.d.ts +3 -3
  12. package/dist/form/create-form.d.ts +12 -10
  13. package/dist/form/factories/node-factory.d.ts +17 -16
  14. package/dist/form/index.d.ts +10 -16
  15. package/dist/form/nodes/array-node.d.ts +3 -3
  16. package/dist/form/nodes/field-node.d.ts +6 -3
  17. package/dist/form/nodes/group-node.d.ts +7 -7
  18. package/dist/form/nodes/model-array-node.d.ts +3 -3
  19. package/dist/form/types/contracts.d.ts +9 -6
  20. package/dist/form/types/deep-schema.d.ts +5 -5
  21. package/dist/form/types/validation-schema.d.ts +1 -1
  22. package/dist/form/{validation-config.d.ts → validation/config.d.ts} +3 -3
  23. package/dist/form/validation/context.d.ts +27 -0
  24. package/dist/form/{error-handler.d.ts → validation/error-handler.d.ts} +1 -1
  25. package/dist/form/validation/index.d.ts +23 -28
  26. package/dist/form/validation/operators.d.ts +42 -0
  27. package/dist/form/validation/run.d.ts +45 -0
  28. package/dist/form/{validation-strategy.d.ts → validation/strategy.d.ts} +2 -2
  29. package/dist/form/validation/types.d.ts +28 -0
  30. package/dist/form/{validation/validators → validators}/date-utils.d.ts +1 -1
  31. package/dist/form/{validation/validators → validators}/email.d.ts +6 -7
  32. package/dist/form/{validation/validators → validators}/file-type.d.ts +6 -7
  33. package/dist/form/{validation/validators → validators}/file-utils.d.ts +1 -1
  34. package/dist/form/{validation/validators → validators}/future-date.d.ts +6 -7
  35. package/dist/form/{validation/validators → validators}/index.d.ts +7 -4
  36. package/dist/form/{validation/validators → validators}/integer.d.ts +6 -7
  37. package/dist/form/{validation/validators → validators}/is-date.d.ts +6 -7
  38. package/dist/form/{validation/validators → validators}/is-number.d.ts +6 -7
  39. package/dist/form/{validation/validators → validators}/max-age.d.ts +6 -7
  40. package/dist/form/{validation/validators → validators}/max-date.d.ts +6 -7
  41. package/dist/form/{validation/validators → validators}/max-file-size.d.ts +6 -7
  42. package/dist/form/{validation/validators → validators}/max-files.d.ts +6 -7
  43. package/dist/form/{validation/validators → validators}/max-length.d.ts +7 -8
  44. package/dist/form/{validation/validators → validators}/max-total-file-size.d.ts +6 -7
  45. package/dist/form/{validation/validators → validators}/max.d.ts +7 -8
  46. package/dist/form/{validation/validators → validators}/min-age.d.ts +6 -7
  47. package/dist/form/{validation/validators → validators}/min-date.d.ts +6 -7
  48. package/dist/form/{validation/validators → validators}/min-file-size.d.ts +6 -7
  49. package/dist/form/{validation/validators → validators}/min-files.d.ts +6 -7
  50. package/dist/form/{validation/validators → validators}/min-length.d.ts +7 -8
  51. package/dist/form/{validation/validators → validators}/min.d.ts +7 -8
  52. package/dist/form/{validation/validators → validators}/multiple-of.d.ts +6 -7
  53. package/dist/form/{validation/validators → validators}/non-negative.d.ts +6 -7
  54. package/dist/form/{validation/validators → validators}/non-zero.d.ts +6 -7
  55. package/dist/form/{validation/validators → validators}/past-date.d.ts +6 -7
  56. package/dist/form/{validation/validators → validators}/pattern.d.ts +7 -15
  57. package/dist/form/{validation/validators → validators}/phone.d.ts +6 -7
  58. package/dist/form/{validation/validators → validators}/required.d.ts +8 -13
  59. package/dist/form/{validation/validators → validators}/url.d.ts +8 -13
  60. package/dist/index.d.ts +7 -4
  61. package/dist/index.js +272 -94
  62. package/dist/{state/form-model.d.ts → model/create-model.d.ts} +0 -14
  63. package/dist/{state → model}/index.d.ts +10 -11
  64. package/dist/model/model-nodes.d.ts +60 -0
  65. package/dist/model/model-signals-proxy.d.ts +32 -0
  66. package/dist/model/model-value-proxy.d.ts +3 -0
  67. package/dist/{state → model}/safe-effect.d.ts +1 -1
  68. package/dist/model-signals-proxy-mV3WGdlO.js +233 -0
  69. package/dist/model.d.ts +2 -0
  70. package/dist/model.js +21 -0
  71. package/dist/{form → platforms/react}/hooks/types.d.ts +1 -1
  72. package/dist/{form → platforms/react}/hooks/use-form-bundle.d.ts +1 -1
  73. package/dist/{form → platforms/react}/hooks/use-form-validation.d.ts +3 -3
  74. package/dist/{form → platforms/react}/hooks/useArrayLength.d.ts +1 -1
  75. package/dist/{form → platforms/react}/hooks/useFormControl.d.ts +3 -3
  76. package/dist/{form → platforms/react}/hooks/useFormControlValue.d.ts +2 -2
  77. package/dist/platforms/react/index.d.ts +23 -0
  78. package/dist/signals.d.ts +1 -1
  79. package/dist/strategy-DA3JwxsR.js +170 -0
  80. package/dist/validation.d.ts +1 -1
  81. package/dist/validation.js +10 -165
  82. package/dist/validators/email.d.ts +1 -1
  83. package/dist/validators/future-date.d.ts +1 -1
  84. package/dist/validators/integer.d.ts +1 -1
  85. package/dist/validators/is-date.d.ts +1 -1
  86. package/dist/validators/is-number.d.ts +1 -1
  87. package/dist/validators/max-age.d.ts +1 -1
  88. package/dist/validators/max-date.d.ts +1 -1
  89. package/dist/validators/max-length.d.ts +1 -1
  90. package/dist/validators/max.d.ts +1 -1
  91. package/dist/validators/min-age.d.ts +1 -1
  92. package/dist/validators/min-date.d.ts +1 -1
  93. package/dist/validators/min-length.d.ts +1 -1
  94. package/dist/validators/min.d.ts +1 -1
  95. package/dist/validators/multiple-of.d.ts +1 -1
  96. package/dist/validators/non-negative.d.ts +1 -1
  97. package/dist/validators/non-zero.d.ts +1 -1
  98. package/dist/validators/past-date.d.ts +1 -1
  99. package/dist/validators/pattern.d.ts +1 -1
  100. package/dist/validators/phone.d.ts +1 -1
  101. package/dist/validators/required.d.ts +1 -1
  102. package/dist/validators/url.d.ts +1 -1
  103. package/dist/validators.d.ts +1 -1
  104. package/docs/llms/02-quick-start.md +6 -6
  105. package/docs/llms/03-api-signatures.md +3 -3
  106. package/docs/llms/05-common-mistakes.md +1 -1
  107. package/docs/llms/09-formschema.md +11 -11
  108. package/docs/llms/10-arrays.md +4 -4
  109. package/docs/llms/14-extended-mistakes.md +1 -1
  110. package/docs/llms/15-project-structure.md +8 -7
  111. package/docs/llms/16-ui-components.md +6 -6
  112. package/docs/llms/17-nonexistent-api.md +2 -2
  113. package/docs/llms/29-async-preload.md +4 -4
  114. package/docs/llms/30-type-safety-recipes.md +1 -1
  115. package/llms-index.json +1 -0
  116. package/llms.txt +2102 -262
  117. package/package.json +5 -4
  118. package/dist/form/behaviors.d.ts +0 -133
  119. package/dist/form/validation-schema.d.ts +0 -106
  120. package/dist/form/validators.d.ts +0 -11
  121. package/dist/form-model-DhW2HZOB.js +0 -323
  122. package/dist/state.d.ts +0 -2
  123. package/dist/state.js +0 -21
  124. package/dist/subscription-manager-705bls97.js +0 -171
  125. /package/dist/{state → form/nodes}/subscription-manager.d.ts +0 -0
  126. /package/dist/{state → model}/behaviors-value.d.ts +0 -0
  127. /package/dist/{state → model}/derived-registry.d.ts +0 -0
  128. /package/dist/{state → model}/types.d.ts +0 -0
  129. /package/dist/{behaviors-node-DGEFMTQu.js → node-DGEFMTQu.js} +0 -0
  130. /package/dist/{form → platforms/react}/hooks/useSignalSubscription.d.ts +0 -0
@@ -11,7 +11,7 @@ import { ConfigWithSchema, ConfigWithValue, UnknownRecord } from '../types/index
11
11
  * import { NodeFactory } from '@reformer/core';
12
12
  *
13
13
  * const factory = new NodeFactory();
14
- * const node = factory.createNode({ value: '', component: Input }); // → FieldNode<string>
14
+ * const node = factory.createNode({ value: '', component: InputField }); // → FieldNode<string>
15
15
  * ```
16
16
  */
17
17
  export declare class NodeFactory {
@@ -33,28 +33,29 @@ export declare class NodeFactory {
33
33
  * ```typescript
34
34
  * const factory = new NodeFactory();
35
35
  *
36
+ * Конфиг node-level (не layout-схема M1, где `validators` уже нет).
36
37
  * // FieldNode
37
38
  * const field = factory.createNode({
38
39
  * value: 'test@mail.com',
39
- * component: Input,
40
+ * component: InputField,
40
41
  * validators: [required, email]
41
42
  * });
42
43
  *
43
44
  * // GroupNode
44
45
  * const group = factory.createNode({
45
- * email: { value: '', component: Input },
46
- * password: { value: '', component: Input }
46
+ * email: { value: '', component: InputField },
47
+ * password: { value: '', component: InputField }
47
48
  * });
48
49
  *
49
50
  * // ArrayNode (объект)
50
51
  * const array = factory.createNode({
51
- * schema: { title: { value: '', component: Input } },
52
+ * schema: { title: { value: '', component: InputField } },
52
53
  * initialItems: [{ title: 'Item 1' }]
53
54
  * });
54
55
  *
55
56
  * // ArrayNode (массив) - новый формат
56
57
  * const array2 = factory.createNode([
57
- * { title: { value: '', component: Input } }, // schema
58
+ * { title: { value: '', component: InputField } }, // schema
58
59
  * { title: 'Item 1' }, // initial item 1
59
60
  * { title: 'Item 2' } // initial item 2
60
61
  * ]);
@@ -79,7 +80,7 @@ export declare class NodeFactory {
79
80
  *
80
81
  * // Массив с начальными элементами
81
82
  * const array = factory.createArrayNodeFromArray([
82
- * { title: { value: '', component: Input } }, // schema
83
+ * { title: { value: '', component: InputField } }, // schema
83
84
  * { title: 'Item 1' }, // initial value
84
85
  * { title: 'Item 2' } // initial value
85
86
  * ]);
@@ -93,7 +94,7 @@ export declare class NodeFactory {
93
94
  * ✅ НОВОЕ: Извлечено из GroupNode для централизации логики
94
95
  *
95
96
  * Преобразует схему формы в объект со значениями:
96
- * - `{ name: { value: 'John', component: Input } } → { name: 'John' }`
97
+ * - `{ name: { value: 'John', component: InputField } } → { name: 'John' }`
97
98
  * - Поддерживает вложенные группы
98
99
  * - Поддерживает массивы
99
100
  *
@@ -105,10 +106,10 @@ export declare class NodeFactory {
105
106
  * const factory = new NodeFactory();
106
107
  *
107
108
  * const schema = {
108
- * name: { value: 'John', component: Input },
109
- * age: { value: 30, component: Input },
109
+ * name: { value: 'John', component: InputField },
110
+ * age: { value: 30, component: InputField },
110
111
  * address: {
111
- * city: { value: 'Moscow', component: Input }
112
+ * city: { value: 'Moscow', component: InputField }
112
113
  * }
113
114
  * };
114
115
  *
@@ -131,7 +132,7 @@ export declare class NodeFactory {
131
132
  * ```typescript
132
133
  * const factory = new NodeFactory();
133
134
  *
134
- * factory.isFieldConfig({ value: '', component: Input }); // true
135
+ * factory.isFieldConfig({ value: '', component: InputField }); // true
135
136
  * factory.isFieldConfig({ email: { value: '' } }); // false
136
137
  * factory.isFieldConfig(null); // false
137
138
  * ```
@@ -154,7 +155,7 @@ export declare class NodeFactory {
154
155
  * const factory = new NodeFactory();
155
156
  *
156
157
  * factory.isArrayConfig({ schema: {}, initialItems: [] }); // true
157
- * factory.isArrayConfig({ value: '', component: Input }); // false
158
+ * factory.isArrayConfig({ value: '', component: InputField }); // false
158
159
  * factory.isArrayConfig({ email: { value: '' } }); // false
159
160
  * ```
160
161
  */
@@ -175,11 +176,11 @@ export declare class NodeFactory {
175
176
  * const factory = new NodeFactory();
176
177
  *
177
178
  * factory.isGroupConfig({
178
- * email: { value: '', component: Input },
179
- * password: { value: '', component: Input }
179
+ * email: { value: '', component: InputField },
180
+ * password: { value: '', component: InputField }
180
181
  * }); // true
181
182
  *
182
- * factory.isGroupConfig({ value: '', component: Input }); // false
183
+ * factory.isGroupConfig({ value: '', component: InputField }); // false
183
184
  * factory.isGroupConfig({ schema: {} }); // false
184
185
  * factory.isGroupConfig(null); // false
185
186
  * ```
@@ -1,10 +1,11 @@
1
1
  /**
2
- * Модуль `form` — доменный слой формы поверх state-субстрата (`model`).
2
+ * Модуль `form` — доменный слой формы поверх реактивной модели (`model`).
3
3
  *
4
4
  * Ноды (value/touched/dirty/status/errors/componentProps), сборка формы из модели по идентичности
5
- * сигнала (`createForm`), state-операции над нодами (`enableWhen`/`disableWhen`), submit/статус,
6
- * React-хуки. Schema-валидация — внешний контракт `@reformer/core/validation` (`validateModel`),
7
- * роутит ошибки в ноды через реестр сигнал→нода. Зависит от `model` (form→state разрешено);
5
+ * сигнала (`createForm`), state-операции над нодами (`enableWhen`/`disableWhen`), submit/статус.
6
+ * React-биндинги вынесены в `platforms/react` — этот слой от React не зависит.
7
+ * Schema-валидация — внешний контракт `@reformer/core/validation` (`validateModel`),
8
+ * роутит ошибки в ноды через реестр сигнал→нода. Зависит от `model` (form→model разрешено);
8
9
  * обратной зависимости нет.
9
10
  *
10
11
  * @group Form
@@ -21,19 +22,12 @@ export { createForm, createFormFromModel } from './create-form';
21
22
  export type { CreateFormFromModelArgs } from './create-form';
22
23
  export { createCoreForm } from './create-core-form';
23
24
  export type { CoreForm, CreateCoreFormConfig, CreateFormConfigBase } from './create-core-form';
24
- export { buildValidation } from './validation-config';
25
- export type { FormValidation, FormValidationBundle } from './validation-config';
26
- export { useFormBundle } from './hooks/use-form-bundle';
27
- export type { FormBundleLike } from './hooks/use-form-bundle';
28
- export { enableWhen, disableWhen } from './behaviors-node';
29
- export { FormErrorHandler, ErrorStrategy } from './error-handler';
25
+ export { buildValidation } from './validation/config';
26
+ export type { FormValidation, FormValidationBundle } from './validation/config';
27
+ export { enableWhen, disableWhen } from './behaviors/node';
28
+ export { FormErrorHandler, ErrorStrategy } from './validation/error-handler';
30
29
  export { registerSignalNode, getNodeForSignal } from './signal-node-registry';
31
30
  export { FormSubmitter } from './form-submitter';
31
+ export { SubscriptionManager } from './nodes/subscription-manager';
32
32
  export type { SubmittableForm, SubmitOptions, SubmitResult } from './form-submitter';
33
33
  export { isFormNode, isFieldNode, isGroupNode, isArrayNode, getNodeType } from './type-guards';
34
- export { useFormControl } from './hooks/useFormControl';
35
- export { useFormControlValue } from './hooks/useFormControlValue';
36
- export { useArrayLength } from './hooks/useArrayLength';
37
- export { useFormValidation } from './hooks/use-form-validation';
38
- export type { UseFormValidationArgs, UseFormValidationResult } from './hooks/use-form-validation';
39
- export type { FieldControlState, ArrayControlState } from './hooks/types';
@@ -11,8 +11,8 @@ import { FormProxy } from '../types/form-proxy';
11
11
  * @example
12
12
  * ```typescript
13
13
  * const array = new ArrayNode({
14
- * title: { value: '', component: Input },
15
- * price: { value: 0, component: Input },
14
+ * title: { value: '', component: InputField },
15
+ * price: { value: 0, component: InputField },
16
16
  * });
17
17
  *
18
18
  * array.push({ title: 'Item 1', price: 100 });
@@ -141,7 +141,7 @@ export declare class ArrayNode<T extends object> extends FormNode<T[]> {
141
141
  * @example
142
142
  * ```typescript
143
143
  * const arrayNode = new ArrayNode(
144
- * { name: { value: '', component: Input } },
144
+ * { name: { value: '', component: InputField } },
145
145
  * [{ name: 'Initial 1' }, { name: 'Initial 2' }]
146
146
  * );
147
147
  *
@@ -7,10 +7,11 @@ import { FieldConfig, FieldStatus, ValidationError } from '../types/index';
7
7
  * @group Nodes
8
8
  *
9
9
  * @example
10
+ * Пример node-level: конфиг УЗЛА, а не layout-схема M1 (в ней поля `validators` нет).
10
11
  * ```typescript
11
12
  * const field = new FieldNode({
12
13
  * value: '',
13
- * component: Input,
14
+ * component: InputField,
14
15
  * validators: [required, email],
15
16
  * });
16
17
  *
@@ -102,7 +103,7 @@ export declare class FieldNode<T> extends FormNode<T> {
102
103
  *
103
104
  * @example
104
105
  * ```typescript
105
- * const field = new FieldNode({ value: 'initial', component: Input });
106
+ * const field = new FieldNode({ value: 'initial', component: InputField });
106
107
  *
107
108
  * field.setValue('changed');
108
109
  * field.reset('temp value');
@@ -194,12 +195,14 @@ export declare class FieldNode<T> extends FormNode<T> {
194
195
  * @param updateOn - новый триггер валидации: 'change' | 'blur' | 'submit'
195
196
  *
196
197
  * @example
198
+ * Пример node-level: плоская схема с инлайн-значениями (back-compat путь до M1). В layout-схеме
199
+ * `createForm({ model, schema })` поля `validators` нет — правила живут в `defineValidationSchema`.
197
200
  * ```typescript
198
201
  * // Сценарий 1: Instant feedback после submit
199
202
  * const form = createForm({
200
203
  * email: {
201
204
  * value: '',
202
- * component: Input,
205
+ * component: InputField,
203
206
  * updateOn: 'submit', // Изначально валидация только при submit
204
207
  * validators: [required, email],
205
208
  * },
@@ -15,8 +15,8 @@ import { SubmitOptions, SubmitResult } from '../form-submitter';
15
15
  * @example
16
16
  * ```typescript
17
17
  * const form = new GroupNode({
18
- * email: { valueSignal: model.$.email, component: Input },
19
- * password: { valueSignal: model.$.password, component: Input },
18
+ * email: { valueSignal: model.$.email, component: InputField },
19
+ * password: { valueSignal: model.$.password, component: InputField },
20
20
  * });
21
21
  *
22
22
  * // Прямой доступ к полям через Proxy
@@ -136,8 +136,8 @@ export declare class GroupNode<T> extends FormNode<T> {
136
136
  * @example
137
137
  * ```typescript
138
138
  * const form = new GroupNode({
139
- * email: { value: '', component: Input },
140
- * name: { value: '', component: Input },
139
+ * email: { value: '', component: InputField },
140
+ * name: { value: '', component: InputField },
141
141
  * });
142
142
  *
143
143
  * const proxy = form.getProxy();
@@ -189,11 +189,11 @@ export declare class GroupNode<T> extends FormNode<T> {
189
189
  * @example
190
190
  * ```typescript
191
191
  * const form = new GroupNode({
192
- * email: { value: '', component: Input },
192
+ * email: { value: '', component: InputField },
193
193
  * address: {
194
- * city: { value: '', component: Input }
194
+ * city: { value: '', component: InputField }
195
195
  * },
196
- * items: [{ name: { value: '', component: Input } }]
196
+ * items: [{ name: { value: '', component: InputField } }]
197
197
  * });
198
198
  *
199
199
  * form.getFieldByPath('email'); // FieldNode
@@ -1,7 +1,7 @@
1
1
  import { Signal, ReadonlySignal } from '@preact/signals-core';
2
2
  import { FormNode } from './form-node';
3
3
  import { FieldStatus, ValidationError } from '../types/index';
4
- import { FormModel } from '../../state/types';
4
+ import { FormModel } from '../../model/types';
5
5
  import { FormProxy } from '../types/form-proxy';
6
6
  /** Минимальный контракт реактивного массива модели, используемый узлом. */
7
7
  export interface ModelArrayControl<TItem extends object> {
@@ -36,8 +36,8 @@ export interface ModelArrayControl<TItem extends object> {
36
36
  * const model = createModel<{ rows: { name: string; qty: number }[] }>({ rows: [] });
37
37
  *
38
38
  * const rowItem = (item: FormModel<{ name: string; qty: number }>) => ({
39
- * name: { value: item.$.name, component: Input },
40
- * qty: { value: item.$.qty, component: Input },
39
+ * name: { value: item.$.name, component: InputField },
40
+ * qty: { value: item.$.qty, component: InputField },
41
41
  * });
42
42
  *
43
43
  * const form = createForm({
@@ -1,13 +1,16 @@
1
1
  /**
2
- * Общие контракт-типы значения и валидации (state-слой).
2
+ * Общие контракт-типы значения и валидации.
3
3
  *
4
- * Эти типы нужны и headless-движку валидации данных (`validate-model-core`, `schema-node`,
5
- * `error-handler` — state), и form-слою. Живут в `model/`, поэтому state их импортирует напрямую,
6
- * а form-слой реэкспортирует из `form/types` (form→state разрешено). Так граница state⇏form не
7
- * нарушается: общие контракты принадлежат нижнему слою.
4
+ * Базовый словарь «значение + ошибка + статус», на который опираются узлы формы
5
+ * (`FormNode.getErrors`, `FieldNode`, `FormStatusMachine`, `aggregate-signals`), слой валидации
6
+ * и платформенные биндинги. Файл не импортирует ничего — это лист графа зависимостей.
7
+ *
8
+ * Живёт в `form/types/`, а не в слое модели: `FieldStatus`/`ErrorFilterOptions` — понятия формы,
9
+ * а модель этих типов не использует (и импортировать их оттуда запрещено границей model⇏form).
10
+ * Наружу отдаётся через `form/types/index`.
8
11
  *
9
12
  * @group Types
10
- * @module model/contracts
13
+ * @module form/types/contracts
11
14
  */
12
15
  /**
13
16
  * Represents any valid form value type
@@ -89,14 +89,14 @@ type NonEmptyFieldConfig<T> = RequireAtLeastOne<FieldConfig<T>>;
89
89
  * }
90
90
  *
91
91
  * const schema: FormSchema<Form> = {
92
- * name: { value: '', component: Input },
92
+ * name: { value: '', component: InputField },
93
93
  * address: {
94
- * city: { value: '', component: Input },
95
- * street: { value: '', component: Input },
94
+ * city: { value: '', component: InputField },
95
+ * street: { value: '', component: InputField },
96
96
  * },
97
97
  * items: [{
98
- * title: { value: '', component: Input },
99
- * price: { value: 0, component: Input },
98
+ * title: { value: '', component: InputField },
99
+ * price: { value: 0, component: InputField },
100
100
  * }],
101
101
  * };
102
102
  * ```
@@ -1,5 +1,5 @@
1
1
  import { FormValue, ValidationError } from './index';
2
- import { FormModel } from '../../state/types';
2
+ import { FormModel } from '../../model/types';
3
3
  /**
4
4
  * Чистый синхронный валидатор поля (legacy-сигнатура `(value, scope, root)`).
5
5
  *
@@ -1,7 +1,7 @@
1
1
  import { ReadonlySignal } from '@preact/signals-core';
2
- import { FormModel } from '../state/types';
3
- import { ValidationSchema } from './validation-schema';
4
- import { FormValidationController, ValidationStrategyKind } from './validation-strategy';
2
+ import { FormModel } from '../../model/types';
3
+ import { ValidationSchema } from './types';
4
+ import { FormValidationController, ValidationStrategyKind } from './strategy';
5
5
  /**
6
6
  * Правила валидации формы — ДАННЫЕ (без привязки к рендеру).
7
7
  *
@@ -0,0 +1,27 @@
1
+ import { FormModel, PathAwareSignal, ValidationError } from '../../index';
2
+ /** Сток одного прогона: модель scope, накопленные ошибки, незавершённые async-правила, гейты. */
3
+ export interface VContext {
4
+ /** Модель текущего scope — источник снапшота для `cross` и модель для `apply`. */
5
+ model: FormModel<unknown>;
6
+ /** Накопленные ошибки по сигналу (ключ — идентичность `PathAwareSignal`). */
7
+ errors: Map<PathAwareSignal<unknown>, ValidationError[]>;
8
+ /** Незавершённые async-правила (раннер их дожидается). */
9
+ pending: Promise<void>[];
10
+ /** Стек активных условий `validateWhen` (все должны быть истинны, чтобы правило сработало). */
11
+ whenStack: Array<() => boolean>;
12
+ /** Отмена устаревшего прогона: прокидывается в async-правила, гасит роутинг после await. */
13
+ signal: AbortSignal;
14
+ }
15
+ /**
16
+ * Открыть ambient-окно на время `fn` и закрыть его в `finally`.
17
+ *
18
+ * Единственный способ установить контекст: прямое присваивание `current` из другого модуля
19
+ * невозможно. Сохранение/восстановление `prev` делает вложенный прогон безопасным.
20
+ */
21
+ export declare function runWithContext<R>(ctx: VContext, fn: () => R): R;
22
+ /** Контекст активного прогона либо понятная ошибка, если оператор вызван вне схемы. */
23
+ export declare function requireCtx(op: string): VContext;
24
+ /** «Тронуть» сигнал: гарантирует его наличие в `errors` (даже пустым) → он попадёт в очистку. */
25
+ export declare function touch(ctx: VContext, sig: PathAwareSignal<unknown>): ValidationError[];
26
+ /** Активна ли текущая ветка (все `validateWhen`-условия истинны). */
27
+ export declare const gated: (ctx: VContext) => boolean;
@@ -1,4 +1,4 @@
1
- import { ValidationError } from './types/contracts';
1
+ import { ValidationError } from '../types/contracts';
2
2
  /**
3
3
  * Стратегия обработки ошибок
4
4
  *
@@ -1,28 +1,23 @@
1
- export { required } from './validators/required';
2
- export { min } from './validators/min';
3
- export { max } from './validators/max';
4
- export { minLength } from './validators/min-length';
5
- export { maxLength } from './validators/max-length';
6
- export { email } from './validators/email';
7
- export { pattern } from './validators/pattern';
8
- export { url } from './validators/url';
9
- export { phone, type PhoneFormat } from './validators/phone';
10
- export { isNumber } from './validators/is-number';
11
- export { integer } from './validators/integer';
12
- export { multipleOf } from './validators/multiple-of';
13
- export { nonNegative } from './validators/non-negative';
14
- export { nonZero } from './validators/non-zero';
15
- export { isDate } from './validators/is-date';
16
- export { minDate } from './validators/min-date';
17
- export { maxDate } from './validators/max-date';
18
- export { pastDate } from './validators/past-date';
19
- export { futureDate } from './validators/future-date';
20
- export { minAge } from './validators/min-age';
21
- export { maxAge } from './validators/max-age';
22
- export { maxFileSize } from './validators/max-file-size';
23
- export { minFileSize } from './validators/min-file-size';
24
- export { fileType } from './validators/file-type';
25
- export { maxFiles } from './validators/max-files';
26
- export { minFiles } from './validators/min-files';
27
- export { maxTotalFileSize } from './validators/max-total-file-size';
28
- export { isFileLike, matchesFileAccept, toFileArray, type FileLike } from './validators/file-utils';
1
+ /**
2
+ * Декларативный контракт СХЕМЫ ВАЛИДАЦИИ — `@reformer/core/validation`.
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(...)`), гася поля, ставшие валидными.
9
+ *
10
+ * Зеркалит контракт поведения (`@reformer/core/behaviors`): тот же ambient-стиль, но отдельный слой —
11
+ * валидация НЕ реактивна (прогон по требованию: submit/шаг), поведение — реактивно (живые подписки).
12
+ *
13
+ * Модуль разложен по зонам ответственности: `./types` — контракт правил и схемы, `./context` —
14
+ * ambient-сток прогона, `./operators` — операторы схемы, `./run` — раннер, `./strategy` — КОГДА
15
+ * запускать (реэкспортируется отсюда, тот же сабпат).
16
+ *
17
+ * @group Validation
18
+ * @module form/validation
19
+ */
20
+ export type { Rule, AsyncRule, ValidationSchema } from './types';
21
+ export { validate, validateAsync, validateWhen, cross, each, apply } from './operators';
22
+ export { defineValidationSchema, validateModel } from './run';
23
+ export * from './strategy';
@@ -0,0 +1,42 @@
1
+ import { FormModel, ModelArray, PathAwareSignal, ValidationError } from '../../index';
2
+ import { AsyncRule, Rule, ValidationSchema } from './types';
3
+ /**
4
+ * Синхронные правила поля.
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * validate(model.$.loanAmount, [required({ message: 'Сумма' }), min(50000)]);
9
+ * ```
10
+ */
11
+ export declare function validate<TField>(sig: PathAwareSignal<TField>, rules: Rule<TField>[]): void;
12
+ /**
13
+ * Асинхронные правила поля (зеркалит движковое разделение `validators` / `asyncValidators`).
14
+ * Раннер дожидается их и прокидывает `AbortSignal` для отмены устаревших ответов.
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * validateAsync(model.$.username, [async (v, { signal }) => {
19
+ * const r = await fetch(`/api/free?u=${v}`, { signal });
20
+ * return (await r.json()).free ? null : { code: 'taken', message: 'Занято' };
21
+ * }]);
22
+ * ```
23
+ */
24
+ export declare function validateAsync<TField>(sig: PathAwareSignal<TField>, rules: AsyncRule<TField>[]): void;
25
+ /**
26
+ * Условная валидация: правила внутри `cb` активны, только пока `cond()` истинно; иначе их поля
27
+ * ГАСЯТСЯ (пустой bucket → `setErrors([])`). Не трогает включение/сброс поля — это дело поведения (`enableWhen`).
28
+ */
29
+ export declare function validateWhen(cond: () => boolean, cb: () => void): void;
30
+ /**
31
+ * Cross-field правило: `fn` получает СНАПШОТ модели текущего scope (`model.get()`) и вешает ошибку на `sig`.
32
+ * Для элементов массива / под-моделей захватывайте нужный снапшот в замыкание (`const item = im.get()`),
33
+ * т.к. `fn` всегда получает модель ТЕКУЩЕГО scope (корень прогона), а не под-модель.
34
+ */
35
+ export declare function cross<TSnapshot>(sig: PathAwareSignal<unknown>, fn: (form: TSnapshot) => ValidationError | null): void;
36
+ /**
37
+ * Применить под-правила к КАЖДОМУ элементу текущего массива модели.
38
+ * `U extends object` — элементы должны быть под-моделями (объектами); для массива примитивов валидируйте лист напрямую.
39
+ */
40
+ export declare function each<U extends object>(arr: ModelArray<U>, itemFn: (item: FormModel<U>) => void): void;
41
+ /** Композиция под-схем в текущую (над той же моделью scope). Заменяет пошаговую группировку. */
42
+ export declare function apply<T>(...schemas: ValidationSchema<T>[]): void;
@@ -0,0 +1,45 @@
1
+ import { FormModel } from '../../index';
2
+ import { ValidationSchema } from './types';
3
+ /**
4
+ * Тонкая identity-обёртка для типизации/discoverability (как `defineFormBehavior`). Возвращает схему как есть.
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * export const step1 = defineValidationSchema<LoanForm>(({ model }) => {
9
+ * validate(model.$.loanAmount, [required(), min(50000)]);
10
+ * });
11
+ * ```
12
+ */
13
+ export declare function defineValidationSchema<T>(schema: ValidationSchema<T>): ValidationSchema<T>;
14
+ /**
15
+ * Провалидировать модель ЛЮБОЙ схемой (шаг или вся форма). Открывает ambient-окно на время
16
+ * синхронного прогона `schema`, дожидается async-правил, разносит ошибки по нодам формы и гасит
17
+ * поля, ставшие валидными. Возвращает `true`, если нет блокирующих ошибок (`severity:'warning'` не блокирует).
18
+ *
19
+ * Гашение — на пару (model, schema): `validateModel(model, step2)` и `validateModel(model, form)` не мешают
20
+ * друг другу. Устаревший прогон (быстрый повторный вызов той же (model, schema)) отменяется через `AbortSignal`
21
+ * и возвращает `false` (**fail-closed** — отменённому результату нельзя доверять для submit).
22
+ *
23
+ * ⚠️ `schema` должна быть СТАБИЛЬНОЙ ссылкой: отмена устаревших прогонов ключится по идентичности `schema`.
24
+ * Инлайн-стрелка (`validateModel(model, ({ model }) => …)`) каждый раз создаёт НОВЫЙ прогон без дедупликации —
25
+ * держите схемы в `const` / `defineValidationSchema`.
26
+ *
27
+ * `options.touch` (§6): по завершении помечает `touched` ИМЕННО поля, которые схема проверяла
28
+ * (не всё поддерево). Ошибки становятся видимыми (`shouldShowError = invalid && (touched||dirty)`)
29
+ * без ручного `form.markAsTouched()`; при пошаговой валидации следующий шаг не показывается
30
+ * «тронутым» до ввода. Валидные проверенные поля тоже метятся — это безвредно (ошибка не покажется).
31
+ *
32
+ * @param model - Модель данных формы.
33
+ * @param schema - Схема валидации (стабильная ссылка).
34
+ * @param options - `{ touch }` — пометить провалидированные поля `touched` для показа ошибок.
35
+ *
36
+ * @example
37
+ * ```ts
38
+ * const ok = await validateModel(model, step2Validation); // один шаг
39
+ * const all = await validateModel(model, formValidation); // вся форма
40
+ * const stepOk = await validateModel(model, step2Validation, { touch: true }); // + показать ошибки шага
41
+ * ```
42
+ */
43
+ export declare function validateModel<T>(model: FormModel<T>, schema: ValidationSchema<T>, options?: {
44
+ touch?: boolean;
45
+ }): Promise<boolean>;
@@ -1,6 +1,6 @@
1
1
  import { ReadonlySignal } from '@preact/signals-core';
2
- import { FormModel } from '../index';
3
- import { ValidationSchema } from './validation-schema';
2
+ import { FormModel } from '../../index';
3
+ import { ValidationSchema } from './types';
4
4
  /**
5
5
  * Когда запускается schema-валидация:
6
6
  * - `submit` (по умолчанию) — только явным вызовом `validate()` (на отправке);
@@ -0,0 +1,28 @@
1
+ import { FormModel, ValidationError } from '../../index';
2
+ /**
3
+ * Внутренний callable-вид правила: value-only фабрики игнорируют 2-й/3-й аргумент.
4
+ *
5
+ * @internal
6
+ */
7
+ export type CallableRule = (value: unknown, model: unknown, root: unknown) => ValidationError | null;
8
+ /**
9
+ * Синхронное правило поля типа `TField`. Проверяется ТОЛЬКО значение (`value`).
10
+ *
11
+ * Позиционные `scope`/`root` помечены `never` намеренно: так встроенные value-only фабрики
12
+ * (`required()`/`min()`/… — они `(value, model, root) => …`) и inline-правила `(value) => …`
13
+ * ОБА присваиваются в `Rule<TField>[]` без `any`, при этом сохраняется проверка типа поля
14
+ * (`validate(model.$.age, [email()])` подсветится — `email` ждёт `string`, поле `number`).
15
+ * На вызове раннер приводит правило к callable и передаёт `(value, model, model)`.
16
+ */
17
+ export type Rule<TField> = (value: TField, scope: never, root: never) => ValidationError | null;
18
+ /**
19
+ * Асинхронное правило поля. Получает `AbortSignal` для отмены устаревших ответов (быстрый повторный
20
+ * прогон той же схемы отменяет предыдущий). Сетевой сбой не должен блокировать — ловите и возвращайте `null`.
21
+ */
22
+ export type AsyncRule<TField> = (value: TField, ctx: {
23
+ signal: AbortSignal;
24
+ }) => Promise<ValidationError | null>;
25
+ /** Схема валидации — обычная функция над (под)моделью. First-class значение (можно `apply`/тестировать/переиспользовать). */
26
+ export type ValidationSchema<T> = (ctx: {
27
+ model: FormModel<T>;
28
+ }) => void;
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * @group Validation
5
5
  * @category Validators
6
- * @module validators/date-utils
6
+ * @module form/validators/date-utils
7
7
  */
8
8
  /**
9
9
  * Парсит значение в объект Date
@@ -1,4 +1,4 @@
1
- import { Validator, ValidateOptions } from '../../types/validation-schema';
1
+ import { Validator, ValidateOptions } from '../types/validation-schema';
2
2
  /**
3
3
  * Фабрика валидатора формата email.
4
4
  *
@@ -10,14 +10,13 @@ import { Validator, ValidateOptions } from '../../types/validation-schema';
10
10
  *
11
11
  * @example Проверка формата email
12
12
  * ```typescript
13
+ * import { defineValidationSchema, validate } from '@reformer/core/validation';
13
14
  * import { required, email } from '@reformer/core/validators';
14
15
  *
15
- * // Внутри FieldConfig схемы формы:
16
- * email: {
17
- * value: model.$.email,
18
- * component: Input,
19
- * validators: [required(), email({ message: 'Введите корректный email' })],
20
- * },
16
+ * // Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
17
+ * const validation = defineValidationSchema<MyForm>(({ model }) => {
18
+ * validate(model.$.email, [required(), email({ message: 'Введите корректный email' })]);
19
+ * });
21
20
  * ```
22
21
  */
23
22
  export declare function email<TForm = unknown, TField extends string | null | undefined = string>(options?: ValidateOptions): Validator<TForm, TField>;
@@ -1,4 +1,4 @@
1
- import { Validator, ValidateOptions } from '../../types/validation-schema';
1
+ import { Validator, ValidateOptions } from '../types/validation-schema';
2
2
  /**
3
3
  * Фабрика валидатора типа файла.
4
4
  *
@@ -18,14 +18,13 @@ import { Validator, ValidateOptions } from '../../types/validation-schema';
18
18
  *
19
19
  * @example Только изображения и PDF
20
20
  * ```typescript
21
+ * import { defineValidationSchema, validate } from '@reformer/core/validation';
21
22
  * import { fileType } from '@reformer/core/validators';
22
23
  *
23
- * // Внутри FieldConfig схемы формы:
24
- * documents: {
25
- * value: model.$.documents,
26
- * component: FileUploadField,
27
- * validators: [fileType('image/*,.pdf', { message: 'Только изображения или PDF' })],
28
- * },
24
+ * // Правила живут в отдельной схеме над МОДЕЛЬЮ — layout-схема валидаторов не несёт.
25
+ * const validation = defineValidationSchema<MyForm>(({ model }) => {
26
+ * validate(model.$.documents, [fileType('image/*,.pdf', { message: 'Только изображения или PDF' })]);
27
+ * });
29
28
  * ```
30
29
  */
31
30
  export declare function fileType<TForm = unknown, TField = unknown>(accept: string, options?: ValidateOptions): Validator<TForm, TField>;
@@ -7,7 +7,7 @@
7
7
  *
8
8
  * @group Validation
9
9
  * @category Validators
10
- * @module validators/file-utils
10
+ * @module form/validators/file-utils
11
11
  */
12
12
  /**
13
13
  * Минимальный файлоподобный контракт, с которым работают file-валидаторы: