@reformer/core 1.0.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +53 -0
- package/dist/behaviors.d.ts +2 -0
- package/dist/behaviors.js +230 -0
- package/dist/core/behavior/behavior-applicator.d.ts +71 -0
- package/dist/core/behavior/behavior-applicator.js +92 -0
- package/dist/core/behavior/behavior-context.d.ts +29 -0
- package/dist/core/behavior/behavior-context.js +38 -0
- package/dist/core/behavior/behavior-registry.d.ts +97 -0
- package/dist/core/behavior/behavior-registry.js +198 -0
- package/dist/core/behavior/behaviors/compute-from.d.ts +41 -0
- package/dist/core/behavior/behaviors/compute-from.js +84 -0
- package/dist/core/behavior/behaviors/copy-from.d.ts +31 -0
- package/dist/core/behavior/behaviors/copy-from.js +64 -0
- package/dist/core/behavior/behaviors/enable-when.d.ts +49 -0
- package/dist/core/behavior/behaviors/enable-when.js +81 -0
- package/dist/core/behavior/behaviors/index.d.ts +11 -0
- package/dist/core/behavior/behaviors/index.js +11 -0
- package/dist/core/behavior/behaviors/reset-when.d.ts +51 -0
- package/dist/core/behavior/behaviors/reset-when.js +63 -0
- package/dist/core/behavior/behaviors/revalidate-when.d.ts +30 -0
- package/dist/core/behavior/behaviors/revalidate-when.js +51 -0
- package/dist/core/behavior/behaviors/sync-fields.d.ts +28 -0
- package/dist/core/behavior/behaviors/sync-fields.js +66 -0
- package/dist/core/behavior/behaviors/transform-value.d.ts +120 -0
- package/dist/core/behavior/behaviors/transform-value.js +110 -0
- package/dist/core/behavior/behaviors/watch-field.d.ts +35 -0
- package/dist/core/behavior/behaviors/watch-field.js +56 -0
- package/dist/core/behavior/compose-behavior.d.ts +106 -0
- package/dist/core/behavior/compose-behavior.js +166 -0
- package/dist/core/behavior/create-field-path.d.ts +20 -0
- package/dist/core/behavior/create-field-path.js +69 -0
- package/dist/core/behavior/index.d.ts +12 -0
- package/dist/core/behavior/index.js +17 -0
- package/dist/core/behavior/types.d.ts +152 -0
- package/dist/core/behavior/types.js +7 -0
- package/dist/core/context/form-context-impl.d.ts +29 -0
- package/dist/core/context/form-context-impl.js +37 -0
- package/dist/core/factories/index.d.ts +6 -0
- package/dist/core/factories/index.js +6 -0
- package/dist/core/factories/node-factory.d.ts +209 -0
- package/dist/core/factories/node-factory.js +281 -0
- package/dist/core/nodes/array-node.d.ts +308 -0
- package/dist/core/nodes/array-node.js +534 -0
- package/dist/core/nodes/field-node.d.ts +269 -0
- package/dist/core/nodes/field-node.js +510 -0
- package/dist/core/nodes/form-node.d.ts +342 -0
- package/dist/core/nodes/form-node.js +343 -0
- package/dist/core/nodes/group-node/field-registry.d.ts +191 -0
- package/dist/core/nodes/group-node/field-registry.js +215 -0
- package/dist/core/nodes/group-node/index.d.ts +11 -0
- package/dist/core/nodes/group-node/index.js +11 -0
- package/dist/core/nodes/group-node/proxy-builder.d.ts +71 -0
- package/dist/core/nodes/group-node/proxy-builder.js +161 -0
- package/dist/core/nodes/group-node/state-manager.d.ts +184 -0
- package/dist/core/nodes/group-node/state-manager.js +265 -0
- package/dist/core/nodes/group-node.d.ts +494 -0
- package/dist/core/nodes/group-node.js +770 -0
- package/dist/core/types/deep-schema.d.ts +78 -0
- package/dist/core/types/deep-schema.js +11 -0
- package/dist/core/types/field-path.d.ts +42 -0
- package/dist/core/types/field-path.js +4 -0
- package/dist/core/types/form-context.d.ts +83 -0
- package/dist/core/types/form-context.js +25 -0
- package/dist/core/types/group-node-proxy.d.ts +135 -0
- package/dist/core/types/group-node-proxy.js +31 -0
- package/dist/core/types/index.d.ts +163 -0
- package/dist/core/types/index.js +4 -0
- package/dist/core/types/validation-schema.d.ts +104 -0
- package/dist/core/types/validation-schema.js +10 -0
- package/dist/core/utils/create-form.d.ts +61 -0
- package/dist/core/utils/create-form.js +24 -0
- package/dist/core/utils/debounce.d.ts +160 -0
- package/dist/core/utils/debounce.js +197 -0
- package/dist/core/utils/error-handler.d.ts +180 -0
- package/dist/core/utils/error-handler.js +226 -0
- package/dist/core/utils/field-path-navigator.d.ts +240 -0
- package/dist/core/utils/field-path-navigator.js +374 -0
- package/dist/core/utils/index.d.ts +14 -0
- package/dist/core/utils/index.js +14 -0
- package/dist/core/utils/registry-helpers.d.ts +50 -0
- package/dist/core/utils/registry-helpers.js +79 -0
- package/dist/core/utils/registry-stack.d.ts +69 -0
- package/dist/core/utils/registry-stack.js +86 -0
- package/dist/core/utils/resources.d.ts +41 -0
- package/dist/core/utils/resources.js +69 -0
- package/dist/core/utils/subscription-manager.d.ts +180 -0
- package/dist/core/utils/subscription-manager.js +214 -0
- package/dist/core/utils/type-guards.d.ts +116 -0
- package/dist/core/utils/type-guards.js +169 -0
- package/dist/core/validation/core/apply-when.d.ts +28 -0
- package/dist/core/validation/core/apply-when.js +41 -0
- package/dist/core/validation/core/apply.d.ts +63 -0
- package/dist/core/validation/core/apply.js +38 -0
- package/dist/core/validation/core/index.d.ts +8 -0
- package/dist/core/validation/core/index.js +8 -0
- package/dist/core/validation/core/validate-async.d.ts +42 -0
- package/dist/core/validation/core/validate-async.js +45 -0
- package/dist/core/validation/core/validate-tree.d.ts +35 -0
- package/dist/core/validation/core/validate-tree.js +37 -0
- package/dist/core/validation/core/validate.d.ts +32 -0
- package/dist/core/validation/core/validate.js +38 -0
- package/dist/core/validation/field-path.d.ts +43 -0
- package/dist/core/validation/field-path.js +147 -0
- package/dist/core/validation/index.d.ts +21 -0
- package/dist/core/validation/index.js +33 -0
- package/dist/core/validation/validate-form.d.ts +85 -0
- package/dist/core/validation/validate-form.js +152 -0
- package/dist/core/validation/validation-applicator.d.ts +89 -0
- package/dist/core/validation/validation-applicator.js +217 -0
- package/dist/core/validation/validation-context.d.ts +47 -0
- package/dist/core/validation/validation-context.js +75 -0
- package/dist/core/validation/validation-registry.d.ts +156 -0
- package/dist/core/validation/validation-registry.js +298 -0
- package/dist/core/validation/validators/array-validators.d.ts +63 -0
- package/dist/core/validation/validators/array-validators.js +86 -0
- package/dist/core/validation/validators/date.d.ts +38 -0
- package/dist/core/validation/validators/date.js +117 -0
- package/dist/core/validation/validators/email.d.ts +44 -0
- package/dist/core/validation/validators/email.js +60 -0
- package/dist/core/validation/validators/index.d.ts +14 -0
- package/dist/core/validation/validators/index.js +14 -0
- package/dist/core/validation/validators/max-length.d.ts +45 -0
- package/dist/core/validation/validators/max-length.js +60 -0
- package/dist/core/validation/validators/max.d.ts +45 -0
- package/dist/core/validation/validators/max.js +60 -0
- package/dist/core/validation/validators/min-length.d.ts +45 -0
- package/dist/core/validation/validators/min-length.js +60 -0
- package/dist/core/validation/validators/min.d.ts +45 -0
- package/dist/core/validation/validators/min.js +60 -0
- package/dist/core/validation/validators/number.d.ts +38 -0
- package/dist/core/validation/validators/number.js +90 -0
- package/dist/core/validation/validators/pattern.d.ts +47 -0
- package/dist/core/validation/validators/pattern.js +62 -0
- package/dist/core/validation/validators/phone.d.ts +34 -0
- package/dist/core/validation/validators/phone.js +58 -0
- package/dist/core/validation/validators/required.d.ts +48 -0
- package/dist/core/validation/validators/required.js +69 -0
- package/dist/core/validation/validators/url.d.ts +29 -0
- package/dist/core/validation/validators/url.js +55 -0
- package/dist/create-field-path-CdPF3lIK.js +704 -0
- package/dist/hooks/useFormControl.d.ts +48 -0
- package/dist/hooks/useFormControl.js +298 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +8 -0
- package/dist/node-factory-D7DOnSSN.js +3200 -0
- package/dist/validators.d.ts +2 -0
- package/dist/validators.js +298 -0
- package/llms.txt +847 -0
- package/package.json +86 -0
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ValidationRegistry - система регистрации и применения валидаторов
|
|
3
|
+
*
|
|
4
|
+
* Работает как стек контекстов:
|
|
5
|
+
* 1. При вызове validation schema функции создается новый контекст
|
|
6
|
+
* 2. Все вызовы validate(), applyWhen() и т.д. регистрируют валидаторы в текущем контексте
|
|
7
|
+
* 3. После завершения схемы валидаторы применяются к GroupNode
|
|
8
|
+
*/
|
|
9
|
+
import { RegistryStack } from '../utils/registry-stack';
|
|
10
|
+
/**
|
|
11
|
+
* Контекст регистрации валидаторов
|
|
12
|
+
*/
|
|
13
|
+
class RegistrationContext {
|
|
14
|
+
validators = [];
|
|
15
|
+
conditionStack = [];
|
|
16
|
+
/**
|
|
17
|
+
* Добавить валидатор в контекст
|
|
18
|
+
*/
|
|
19
|
+
addValidator(registration) {
|
|
20
|
+
// Если есть активные условия, добавляем их к валидатору
|
|
21
|
+
if (this.conditionStack.length > 0) {
|
|
22
|
+
const condition = this.conditionStack[this.conditionStack.length - 1];
|
|
23
|
+
registration.condition = condition;
|
|
24
|
+
}
|
|
25
|
+
this.validators.push(registration);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Войти в условный блок
|
|
29
|
+
*/
|
|
30
|
+
enterCondition(fieldPath, conditionFn) {
|
|
31
|
+
this.conditionStack.push({ fieldPath, conditionFn });
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Выйти из условного блока
|
|
35
|
+
*/
|
|
36
|
+
exitCondition() {
|
|
37
|
+
this.conditionStack.pop();
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Получить все зарегистрированные валидаторы
|
|
41
|
+
*/
|
|
42
|
+
getValidators() {
|
|
43
|
+
return this.validators;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Реестр валидаторов для формы
|
|
48
|
+
*
|
|
49
|
+
* Каждый экземпляр GroupNode создает собственный реестр (композиция).
|
|
50
|
+
* Устраняет race conditions и изолирует формы друг от друга.
|
|
51
|
+
*
|
|
52
|
+
* Context stack используется для tracking текущего активного реестра:
|
|
53
|
+
* - beginRegistration() помещает this в global stack
|
|
54
|
+
* - endRegistration() извлекает из global stack
|
|
55
|
+
* - getCurrent() возвращает текущий активный реестр
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```typescript
|
|
59
|
+
* class GroupNode {
|
|
60
|
+
* private readonly validationRegistry = new ValidationRegistry();
|
|
61
|
+
*
|
|
62
|
+
* applyValidationSchema(schemaFn) {
|
|
63
|
+
* this.validationRegistry.beginRegistration(); // Pushes this to global stack
|
|
64
|
+
* schemaFn(createFieldPath(this)); // Uses getCurrent()
|
|
65
|
+
* this.validationRegistry.endRegistration(this); // Pops from global stack
|
|
66
|
+
* }
|
|
67
|
+
* }
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
export class ValidationRegistry {
|
|
71
|
+
/**
|
|
72
|
+
* Global stack активных реестров
|
|
73
|
+
* Используется для изоляции форм друг от друга
|
|
74
|
+
*/
|
|
75
|
+
static registryStack = new RegistryStack();
|
|
76
|
+
contextStack = [];
|
|
77
|
+
validators = [];
|
|
78
|
+
/**
|
|
79
|
+
* Получить текущий активный реестр из global stack
|
|
80
|
+
*
|
|
81
|
+
* @returns Текущий активный реестр или null
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```typescript
|
|
85
|
+
* // В schema-validators.ts
|
|
86
|
+
* export function required(...) {
|
|
87
|
+
* const registry = ValidationRegistry.getCurrent();
|
|
88
|
+
* if (registry) {
|
|
89
|
+
* registry.registerSync(...);
|
|
90
|
+
* }
|
|
91
|
+
* }
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
static getCurrent() {
|
|
95
|
+
return ValidationRegistry.registryStack.getCurrent();
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Начать регистрацию валидаторов для формы
|
|
99
|
+
*
|
|
100
|
+
* Помещает this в global stack для изоляции форм
|
|
101
|
+
*/
|
|
102
|
+
beginRegistration() {
|
|
103
|
+
const context = new RegistrationContext();
|
|
104
|
+
this.contextStack.push(context);
|
|
105
|
+
// Помещаем this в global stack для tracking текущего активного реестра
|
|
106
|
+
ValidationRegistry.registryStack.push(this);
|
|
107
|
+
return context;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Завершить регистрацию и применить валидаторы к GroupNode
|
|
111
|
+
*
|
|
112
|
+
* Извлекает this из global stack
|
|
113
|
+
*
|
|
114
|
+
* Сохраняет валидаторы в локальном состоянии (this.validators) вместо глобального WeakMap.
|
|
115
|
+
*/
|
|
116
|
+
endRegistration(form) {
|
|
117
|
+
const context = this.contextStack.pop();
|
|
118
|
+
if (!context) {
|
|
119
|
+
throw new Error('No active registration context');
|
|
120
|
+
}
|
|
121
|
+
// Извлекаем из global stack с проверкой
|
|
122
|
+
ValidationRegistry.registryStack.verify(this, 'ValidationRegistry');
|
|
123
|
+
// Сохраняем валидаторы в локальном состоянии
|
|
124
|
+
this.validators = context.getValidators();
|
|
125
|
+
// Применяем валидаторы к полям
|
|
126
|
+
this.applyValidators(form, this.validators);
|
|
127
|
+
// Применяем array-items validators к ArrayNode элементам
|
|
128
|
+
this.applyArrayItemValidators(form, this.validators);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Отменить регистрацию без применения валидаторов
|
|
132
|
+
* Используется для временной валидации (например, в validateForm)
|
|
133
|
+
*
|
|
134
|
+
* Извлекает this из global stack
|
|
135
|
+
*/
|
|
136
|
+
cancelRegistration() {
|
|
137
|
+
const context = this.contextStack.pop();
|
|
138
|
+
if (!context) {
|
|
139
|
+
throw new Error('No active registration context to cancel');
|
|
140
|
+
}
|
|
141
|
+
// Извлекаем из global stack с проверкой
|
|
142
|
+
ValidationRegistry.registryStack.verify(this, 'ValidationRegistry');
|
|
143
|
+
// Просто выбрасываем контекст без сохранения
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Получить текущий контекст регистрации
|
|
147
|
+
*/
|
|
148
|
+
getCurrentContext() {
|
|
149
|
+
return this.contextStack[this.contextStack.length - 1];
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Зарегистрировать синхронный валидатор
|
|
153
|
+
*/
|
|
154
|
+
registerSync(fieldPath, validator, options) {
|
|
155
|
+
const context = this.getCurrentContext();
|
|
156
|
+
if (!context) {
|
|
157
|
+
throw new Error('Validators can only be registered inside a validation schema function');
|
|
158
|
+
}
|
|
159
|
+
context.addValidator({
|
|
160
|
+
fieldPath,
|
|
161
|
+
type: 'sync',
|
|
162
|
+
validator: validator,
|
|
163
|
+
options,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Зарегистрировать асинхронный валидатор
|
|
168
|
+
*/
|
|
169
|
+
registerAsync(fieldPath, validator, options) {
|
|
170
|
+
const context = this.getCurrentContext();
|
|
171
|
+
if (!context) {
|
|
172
|
+
throw new Error('Validators can only be registered inside a validation schema function');
|
|
173
|
+
}
|
|
174
|
+
context.addValidator({
|
|
175
|
+
fieldPath,
|
|
176
|
+
type: 'async',
|
|
177
|
+
validator: validator,
|
|
178
|
+
options,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Зарегистрировать tree валидатор
|
|
183
|
+
*/
|
|
184
|
+
registerTree(validator, options) {
|
|
185
|
+
const context = this.getCurrentContext();
|
|
186
|
+
if (!context) {
|
|
187
|
+
throw new Error('Validators can only be registered inside a validation schema function');
|
|
188
|
+
}
|
|
189
|
+
context.addValidator({
|
|
190
|
+
fieldPath: options?.targetField || '__tree__',
|
|
191
|
+
type: 'tree',
|
|
192
|
+
validator: validator,
|
|
193
|
+
options,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Войти в условный блок
|
|
198
|
+
*/
|
|
199
|
+
enterCondition(fieldPath, conditionFn) {
|
|
200
|
+
const context = this.getCurrentContext();
|
|
201
|
+
if (!context) {
|
|
202
|
+
throw new Error('Conditions can only be used inside a validation schema function');
|
|
203
|
+
}
|
|
204
|
+
context.enterCondition(fieldPath, conditionFn);
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Выйти из условного блока
|
|
208
|
+
*/
|
|
209
|
+
exitCondition() {
|
|
210
|
+
const context = this.getCurrentContext();
|
|
211
|
+
if (!context) {
|
|
212
|
+
throw new Error('No active condition');
|
|
213
|
+
}
|
|
214
|
+
context.exitCondition();
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Зарегистрировать validation schema для элементов массива
|
|
218
|
+
*
|
|
219
|
+
* Используется функцией validateItems() для регистрации схемы валидации,
|
|
220
|
+
* которая будет применяться к каждому элементу ArrayNode.
|
|
221
|
+
*
|
|
222
|
+
* @param fieldPath - Путь к ArrayNode полю
|
|
223
|
+
* @param itemSchemaFn - Validation schema для элемента массива
|
|
224
|
+
*/
|
|
225
|
+
registerArrayItemValidation(fieldPath, itemSchemaFn // ValidationSchemaFn<TItem>
|
|
226
|
+
) {
|
|
227
|
+
const context = this.getCurrentContext();
|
|
228
|
+
if (!context) {
|
|
229
|
+
throw new Error('Array item validators can only be registered inside a validation schema function');
|
|
230
|
+
}
|
|
231
|
+
context.addValidator({
|
|
232
|
+
fieldPath,
|
|
233
|
+
type: 'array-items',
|
|
234
|
+
validator: itemSchemaFn,
|
|
235
|
+
options: {},
|
|
236
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Получить зарегистрированные валидаторы для этого реестра
|
|
241
|
+
*
|
|
242
|
+
* Возвращает локальный массив валидаторов (без аргумента form).
|
|
243
|
+
*/
|
|
244
|
+
getValidators() {
|
|
245
|
+
return this.validators;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Применить зарегистрированные валидаторы к GroupNode
|
|
249
|
+
* @private
|
|
250
|
+
*/
|
|
251
|
+
applyValidators(form, validators) {
|
|
252
|
+
// Группируем валидаторы по полям
|
|
253
|
+
const validatorsByField = new Map();
|
|
254
|
+
for (const registration of validators) {
|
|
255
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
256
|
+
if (registration.type === 'tree' || registration.type === 'array-items') {
|
|
257
|
+
// Tree и array-items валидаторы обрабатываются отдельно
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
const existing = validatorsByField.get(registration.fieldPath) || [];
|
|
261
|
+
existing.push(registration);
|
|
262
|
+
validatorsByField.set(registration.fieldPath, existing);
|
|
263
|
+
}
|
|
264
|
+
// Валидаторы сохранены в локальном массиве this.validators
|
|
265
|
+
// Они будут применяться при вызове GroupNode.validate()
|
|
266
|
+
if (import.meta.env.DEV) {
|
|
267
|
+
console.log(`Registered ${validators.length} validators for GroupNode`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Применить array-items validators к ArrayNode элементам
|
|
272
|
+
* @private
|
|
273
|
+
*/
|
|
274
|
+
applyArrayItemValidators(form, validators) {
|
|
275
|
+
// Фильтруем array-items validators
|
|
276
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
277
|
+
const arrayItemValidators = validators.filter((v) => v.type === 'array-items');
|
|
278
|
+
if (arrayItemValidators.length === 0) {
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
// Применяем validation schema к каждому ArrayNode
|
|
282
|
+
for (const registration of arrayItemValidators) {
|
|
283
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
284
|
+
const arrayNode = form[registration.fieldPath.split('.')[0]];
|
|
285
|
+
if (arrayNode && 'applyValidationSchema' in arrayNode) {
|
|
286
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
287
|
+
const itemSchemaFn = registration.validator;
|
|
288
|
+
arrayNode.applyValidationSchema(itemSchemaFn);
|
|
289
|
+
if (import.meta.env.DEV) {
|
|
290
|
+
console.log(`Applied validation schema to ArrayNode: ${registration.fieldPath}`);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
else if (import.meta.env.DEV) {
|
|
294
|
+
console.warn(`Field ${registration.fieldPath} is not an ArrayNode or doesn't exist`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Валидаторы для массивов
|
|
3
|
+
*
|
|
4
|
+
* Предоставляет специализированные функции для валидации ArrayNode:
|
|
5
|
+
* - notEmpty: проверка что массив не пустой
|
|
6
|
+
* - validateItems: применение validation schema к каждому элементу
|
|
7
|
+
*
|
|
8
|
+
* @group Validation
|
|
9
|
+
* @category Validators
|
|
10
|
+
*/
|
|
11
|
+
import type { ValidateOptions, ValidationSchemaFn } from '../../types/validation-schema';
|
|
12
|
+
import type { FieldPathNode } from '../../types';
|
|
13
|
+
/**
|
|
14
|
+
* Проверить что массив содержит хотя бы один элемент
|
|
15
|
+
*
|
|
16
|
+
* Это удобный алиас для `minLength(field, 1)`, оптимизированный для массивов.
|
|
17
|
+
*
|
|
18
|
+
* @group Validation
|
|
19
|
+
* @category Validators
|
|
20
|
+
*
|
|
21
|
+
* @param fieldPath - Поле-массив для валидации
|
|
22
|
+
* @param options - Опции валидации (message, params и т.д.)
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```typescript
|
|
26
|
+
* // Простая проверка
|
|
27
|
+
* notEmpty(path.properties, { message: 'Добавьте хотя бы один объект имущества' });
|
|
28
|
+
*
|
|
29
|
+
* // С дополнительными параметрами
|
|
30
|
+
* notEmpty(path.coBorrowers, {
|
|
31
|
+
* message: 'Требуется хотя бы один созаемщик',
|
|
32
|
+
* params: { minItems: 1 }
|
|
33
|
+
* });
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export declare function notEmpty<TForm, TItem>(fieldPath: FieldPathNode<TForm, TItem[] | undefined> | undefined, options?: ValidateOptions): void;
|
|
37
|
+
/**
|
|
38
|
+
* Применить validation schema к каждому элементу массива
|
|
39
|
+
*
|
|
40
|
+
* Регистрирует схему валидации, которая будет автоматически применяться
|
|
41
|
+
* к каждому элементу ArrayNode (как существующим, так и новым).
|
|
42
|
+
*
|
|
43
|
+
* @group Validation
|
|
44
|
+
* @category Validators
|
|
45
|
+
*
|
|
46
|
+
* @param fieldPath - Поле-массив для валидации элементов
|
|
47
|
+
* @param itemSchemaFn - Validation schema для одного элемента
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```typescript
|
|
51
|
+
* import { propertyValidation } from './property-validation';
|
|
52
|
+
*
|
|
53
|
+
* // В additionalValidation
|
|
54
|
+
* applyWhen(path.hasProperty, (value) => value === true, (path) => {
|
|
55
|
+
* // Проверка что массив не пустой
|
|
56
|
+
* notEmpty(path.properties, { message: 'Добавьте хотя бы один объект имущества' });
|
|
57
|
+
*
|
|
58
|
+
* // Валидация каждого элемента
|
|
59
|
+
* validateItems(path.properties, propertyValidation);
|
|
60
|
+
* });
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
export declare function validateItems<TForm, TItem>(fieldPath: FieldPathNode<TForm, TItem[] | undefined> | undefined, itemSchemaFn: ValidationSchemaFn<TItem>): void;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Валидаторы для массивов
|
|
3
|
+
*
|
|
4
|
+
* Предоставляет специализированные функции для валидации ArrayNode:
|
|
5
|
+
* - notEmpty: проверка что массив не пустой
|
|
6
|
+
* - validateItems: применение validation schema к каждому элементу
|
|
7
|
+
*
|
|
8
|
+
* @group Validation
|
|
9
|
+
* @category Validators
|
|
10
|
+
*/
|
|
11
|
+
import { getCurrentValidationRegistry } from '../../utils/registry-helpers';
|
|
12
|
+
import { extractPath } from '../field-path';
|
|
13
|
+
import { minLength } from './min-length';
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// notEmpty - Проверка что массив не пустой
|
|
16
|
+
// ============================================================================
|
|
17
|
+
/**
|
|
18
|
+
* Проверить что массив содержит хотя бы один элемент
|
|
19
|
+
*
|
|
20
|
+
* Это удобный алиас для `minLength(field, 1)`, оптимизированный для массивов.
|
|
21
|
+
*
|
|
22
|
+
* @group Validation
|
|
23
|
+
* @category Validators
|
|
24
|
+
*
|
|
25
|
+
* @param fieldPath - Поле-массив для валидации
|
|
26
|
+
* @param options - Опции валидации (message, params и т.д.)
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```typescript
|
|
30
|
+
* // Простая проверка
|
|
31
|
+
* notEmpty(path.properties, { message: 'Добавьте хотя бы один объект имущества' });
|
|
32
|
+
*
|
|
33
|
+
* // С дополнительными параметрами
|
|
34
|
+
* notEmpty(path.coBorrowers, {
|
|
35
|
+
* message: 'Требуется хотя бы один созаемщик',
|
|
36
|
+
* params: { minItems: 1 }
|
|
37
|
+
* });
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export function notEmpty(fieldPath, options) {
|
|
41
|
+
if (!fieldPath)
|
|
42
|
+
return;
|
|
43
|
+
// Используем minLength как базовую реализацию
|
|
44
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
45
|
+
minLength(fieldPath, 1, {
|
|
46
|
+
message: options?.message || 'Массив не должен быть пустым',
|
|
47
|
+
params: { minLength: 1, ...options?.params },
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
// ============================================================================
|
|
51
|
+
// validateItems - Валидация элементов массива
|
|
52
|
+
// ============================================================================
|
|
53
|
+
/**
|
|
54
|
+
* Применить validation schema к каждому элементу массива
|
|
55
|
+
*
|
|
56
|
+
* Регистрирует схему валидации, которая будет автоматически применяться
|
|
57
|
+
* к каждому элементу ArrayNode (как существующим, так и новым).
|
|
58
|
+
*
|
|
59
|
+
* @group Validation
|
|
60
|
+
* @category Validators
|
|
61
|
+
*
|
|
62
|
+
* @param fieldPath - Поле-массив для валидации элементов
|
|
63
|
+
* @param itemSchemaFn - Validation schema для одного элемента
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```typescript
|
|
67
|
+
* import { propertyValidation } from './property-validation';
|
|
68
|
+
*
|
|
69
|
+
* // В additionalValidation
|
|
70
|
+
* applyWhen(path.hasProperty, (value) => value === true, (path) => {
|
|
71
|
+
* // Проверка что массив не пустой
|
|
72
|
+
* notEmpty(path.properties, { message: 'Добавьте хотя бы один объект имущества' });
|
|
73
|
+
*
|
|
74
|
+
* // Валидация каждого элемента
|
|
75
|
+
* validateItems(path.properties, propertyValidation);
|
|
76
|
+
* });
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
export function validateItems(fieldPath, itemSchemaFn) {
|
|
80
|
+
if (!fieldPath)
|
|
81
|
+
return;
|
|
82
|
+
const path = extractPath(fieldPath);
|
|
83
|
+
// Регистрируем схему валидации для элементов массива
|
|
84
|
+
// Используем текущий активный реестр из context stack
|
|
85
|
+
getCurrentValidationRegistry().registerArrayItemValidation(path, itemSchemaFn);
|
|
86
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Валидатор даты
|
|
3
|
+
*
|
|
4
|
+
* @group Validation
|
|
5
|
+
* @category Validators
|
|
6
|
+
* @module validators/date
|
|
7
|
+
*/
|
|
8
|
+
import type { ValidateOptions } from '../../types/validation-schema';
|
|
9
|
+
import type { FieldPathNode } from '../../types';
|
|
10
|
+
/**
|
|
11
|
+
* Адаптер для date валидатора
|
|
12
|
+
* Проверяет, что значение является валидной датой и соответствует ограничениям
|
|
13
|
+
*
|
|
14
|
+
* @group Validation
|
|
15
|
+
* @category Validators
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* date(path.birthDate);
|
|
20
|
+
* date(path.birthDate, { maxDate: new Date() }); // Не позже сегодня
|
|
21
|
+
* date(path.eventDate, { minDate: new Date(), message: 'Дата не может быть в прошлом' });
|
|
22
|
+
* date(path.age, { minAge: 18, maxAge: 100 });
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export declare function date<TForm, TField extends string | Date | undefined = string | Date>(fieldPath: FieldPathNode<TForm, TField> | undefined, options?: ValidateOptions & {
|
|
26
|
+
/** Минимальная дата (включительно) */
|
|
27
|
+
minDate?: Date;
|
|
28
|
+
/** Максимальная дата (включительно) */
|
|
29
|
+
maxDate?: Date;
|
|
30
|
+
/** Минимальный возраст (для дат рождения) */
|
|
31
|
+
minAge?: number;
|
|
32
|
+
/** Максимальный возраст (для дат рождения) */
|
|
33
|
+
maxAge?: number;
|
|
34
|
+
/** Не разрешать будущие даты */
|
|
35
|
+
noFuture?: boolean;
|
|
36
|
+
/** Не разрешать прошлые даты */
|
|
37
|
+
noPast?: boolean;
|
|
38
|
+
}): void;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Валидатор даты
|
|
3
|
+
*
|
|
4
|
+
* @group Validation
|
|
5
|
+
* @category Validators
|
|
6
|
+
* @module validators/date
|
|
7
|
+
*/
|
|
8
|
+
import { validate } from '../core/validate';
|
|
9
|
+
/**
|
|
10
|
+
* Адаптер для date валидатора
|
|
11
|
+
* Проверяет, что значение является валидной датой и соответствует ограничениям
|
|
12
|
+
*
|
|
13
|
+
* @group Validation
|
|
14
|
+
* @category Validators
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* date(path.birthDate);
|
|
19
|
+
* date(path.birthDate, { maxDate: new Date() }); // Не позже сегодня
|
|
20
|
+
* date(path.eventDate, { minDate: new Date(), message: 'Дата не может быть в прошлом' });
|
|
21
|
+
* date(path.age, { minAge: 18, maxAge: 100 });
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export function date(fieldPath, options) {
|
|
25
|
+
if (!fieldPath)
|
|
26
|
+
return; // Защита от undefined fieldPath
|
|
27
|
+
validate(fieldPath, (value) => {
|
|
28
|
+
// Пропускаем null/undefined
|
|
29
|
+
if (!value) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
// Конвертируем в Date
|
|
33
|
+
let dateValue;
|
|
34
|
+
if (value instanceof Date) {
|
|
35
|
+
dateValue = value;
|
|
36
|
+
}
|
|
37
|
+
else if (typeof value === 'string') {
|
|
38
|
+
dateValue = new Date(value);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
return {
|
|
42
|
+
code: 'date_invalid',
|
|
43
|
+
message: options?.message || 'Неверный формат даты',
|
|
44
|
+
params: options?.params,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
// Проверка на валидность даты
|
|
48
|
+
if (isNaN(dateValue.getTime())) {
|
|
49
|
+
return {
|
|
50
|
+
code: 'date_invalid',
|
|
51
|
+
message: options?.message || 'Неверный формат даты',
|
|
52
|
+
params: options?.params,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
const now = new Date();
|
|
56
|
+
now.setHours(0, 0, 0, 0);
|
|
57
|
+
// Проверка минимальной даты
|
|
58
|
+
if (options?.minDate) {
|
|
59
|
+
const minDate = new Date(options.minDate);
|
|
60
|
+
minDate.setHours(0, 0, 0, 0);
|
|
61
|
+
if (dateValue < minDate) {
|
|
62
|
+
return {
|
|
63
|
+
code: 'date_min',
|
|
64
|
+
message: options?.message || `Дата должна быть не ранее ${minDate.toLocaleDateString()}`,
|
|
65
|
+
params: { minDate: options.minDate, ...options?.params },
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// Проверка максимальной даты
|
|
70
|
+
if (options?.maxDate) {
|
|
71
|
+
const maxDate = new Date(options.maxDate);
|
|
72
|
+
maxDate.setHours(0, 0, 0, 0);
|
|
73
|
+
if (dateValue > maxDate) {
|
|
74
|
+
return {
|
|
75
|
+
code: 'date_max',
|
|
76
|
+
message: options?.message || `Дата должна быть не позднее ${maxDate.toLocaleDateString()}`,
|
|
77
|
+
params: { maxDate: options.maxDate, ...options?.params },
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// Проверка на будущую дату
|
|
82
|
+
if (options?.noFuture && dateValue > now) {
|
|
83
|
+
return {
|
|
84
|
+
code: 'date_future',
|
|
85
|
+
message: options?.message || 'Дата не может быть в будущем',
|
|
86
|
+
params: options?.params,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
// Проверка на прошлую дату
|
|
90
|
+
if (options?.noPast && dateValue < now) {
|
|
91
|
+
return {
|
|
92
|
+
code: 'date_past',
|
|
93
|
+
message: options?.message || 'Дата не может быть в прошлом',
|
|
94
|
+
params: options?.params,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
// Проверка возраста
|
|
98
|
+
if (options?.minAge !== undefined || options?.maxAge !== undefined) {
|
|
99
|
+
const age = Math.floor((now.getTime() - dateValue.getTime()) / (365.25 * 24 * 60 * 60 * 1000));
|
|
100
|
+
if (options?.minAge !== undefined && age < options.minAge) {
|
|
101
|
+
return {
|
|
102
|
+
code: 'date_min_age',
|
|
103
|
+
message: options?.message || `Минимальный возраст: ${options.minAge} лет`,
|
|
104
|
+
params: { minAge: options.minAge, currentAge: age, ...options?.params },
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
if (options?.maxAge !== undefined && age > options.maxAge) {
|
|
108
|
+
return {
|
|
109
|
+
code: 'date_max_age',
|
|
110
|
+
message: options?.message || `Максимальный возраст: ${options.maxAge} лет`,
|
|
111
|
+
params: { maxAge: options.maxAge, currentAge: age, ...options?.params },
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return null;
|
|
116
|
+
});
|
|
117
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Валидатор формата email
|
|
3
|
+
*
|
|
4
|
+
* @group Validation
|
|
5
|
+
* @category Validators
|
|
6
|
+
* @module validators/email
|
|
7
|
+
*/
|
|
8
|
+
import type { ValidateOptions } from '../../types/validation-schema';
|
|
9
|
+
import type { FieldPathNode } from '../../types';
|
|
10
|
+
/**
|
|
11
|
+
* Валидатор формата email
|
|
12
|
+
*
|
|
13
|
+
* Проверяет, что значение соответствует формату email адреса.
|
|
14
|
+
* Пустые значения пропускаются (используйте `required` для обязательности).
|
|
15
|
+
*
|
|
16
|
+
* @group Validation
|
|
17
|
+
* @category Validators
|
|
18
|
+
*
|
|
19
|
+
* @param fieldPath - Путь к полю для валидации
|
|
20
|
+
* @param options - Опции валидации (message, params)
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```typescript
|
|
24
|
+
* // Базовое использование
|
|
25
|
+
* validationSchema: (path) => [
|
|
26
|
+
* required(path.email),
|
|
27
|
+
* email(path.email),
|
|
28
|
+
* ]
|
|
29
|
+
*
|
|
30
|
+
* // С кастомным сообщением
|
|
31
|
+
* email(path.email, { message: 'Введите корректный email адрес' })
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```typescript
|
|
36
|
+
* // Ошибка валидации
|
|
37
|
+
* {
|
|
38
|
+
* code: 'email',
|
|
39
|
+
* message: 'Неверный формат email',
|
|
40
|
+
* params: {}
|
|
41
|
+
* }
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export declare function email<TForm, TField extends string | undefined = string>(fieldPath: FieldPathNode<TForm, TField> | undefined, options?: ValidateOptions): void;
|