@reformer/core 9.1.1 → 10.0.0-beta.1

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 (36) hide show
  1. package/README.md +39 -28
  2. package/dist/behaviors-node-DUrqAqp1.js +18 -0
  3. package/dist/behaviors.js +1 -1
  4. package/dist/form/create-form.d.ts +13 -17
  5. package/dist/form/index.d.ts +4 -6
  6. package/dist/form/nodes/group-node.d.ts +2 -15
  7. package/dist/form/types/schema-node.d.ts +13 -9
  8. package/dist/form/types/validation-schema.d.ts +7 -29
  9. package/dist/form/validation/index.d.ts +7 -0
  10. package/dist/form/validation/validators/email.d.ts +1 -1
  11. package/dist/form/validation/validators/file-type.d.ts +31 -0
  12. package/dist/form/validation/validators/file-utils.d.ts +51 -0
  13. package/dist/form/validation/validators/future-date.d.ts +1 -1
  14. package/dist/form/validation/validators/index.d.ts +7 -0
  15. package/dist/form/validation/validators/is-date.d.ts +1 -1
  16. package/dist/form/validation/validators/max-file-size.d.ts +28 -0
  17. package/dist/form/validation/validators/max-files.d.ts +26 -0
  18. package/dist/form/validation/validators/max-total-file-size.d.ts +26 -0
  19. package/dist/form/validation/validators/min-file-size.d.ts +28 -0
  20. package/dist/form/validation/validators/min-files.d.ts +26 -0
  21. package/dist/form/validation/validators/past-date.d.ts +1 -1
  22. package/dist/form/validation/validators/pattern.d.ts +1 -1
  23. package/dist/form/validation/validators/phone.d.ts +1 -1
  24. package/dist/form/validation/validators/url.d.ts +1 -1
  25. package/dist/form/validation-schema.d.ts +93 -0
  26. package/dist/index.js +352 -454
  27. package/dist/signal-node-registry-tiwel-TV.js +11 -0
  28. package/dist/state/behaviors-value.d.ts +3 -3
  29. package/dist/validation.d.ts +2 -0
  30. package/dist/validation.js +115 -0
  31. package/dist/validators.js +196 -65
  32. package/llms.txt +1198 -582
  33. package/package.json +5 -1
  34. package/dist/behaviors-node-BMOI2JJF.js +0 -26
  35. package/dist/form/validate-model-core.d.ts +0 -72
  36. package/dist/form/validate-model.d.ts +0 -27
package/README.md CHANGED
@@ -30,19 +30,21 @@ npm install @reformer/core@beta # Active development is underway, so you can try
30
30
 
31
31
  ## Quick Start
32
32
 
33
- ReFormer is built around the **M1 architecture**: a reactive `FormModel` owns the values, a single
34
- schema binds field config (component / validators) to the model's signals, and `createForm({ model, schema })`
35
- wires them into a typed form.
33
+ ReFormer is built around the **M1 architecture**: a reactive `FormModel` owns the values, a layout
34
+ schema binds field config (component / props) to the model's signals, and `createForm({ model, schema })`
35
+ wires them into a typed form. Validation is a **separate** ambient contract — `defineValidationSchema`,
36
+ run on demand by `validateModel(model, schema)` — so the layout schema carries no validators.
36
37
 
37
38
  ```tsx
38
39
  import { useMemo } from 'react';
39
40
  import {
40
41
  createModel,
41
42
  createForm,
42
- validateFormModel,
43
43
  useFormControl,
44
44
  type FieldNode,
45
+ type ValidationError,
45
46
  } from '@reformer/core';
47
+ import { validate, cross, defineValidationSchema, validateModel } from '@reformer/core/validation';
46
48
  import { required, email, minLength } from '@reformer/core/validators';
47
49
  import { defineFormBehavior, onChange } from '@reformer/core/behaviors';
48
50
 
@@ -79,45 +81,54 @@ const model = createModel<RegistrationForm>({
79
81
  confirmPassword: '',
80
82
  });
81
83
 
82
- // 3. Single schema — binds field config to model signals (`model.$.<field>`).
83
- // Validators are inline factories from `@reformer/core/validators`.
84
- // Cross-field validation is a ModelValidator `(value, model) => error | null`.
84
+ // 3. Layout schema — binds field config (component / props) to model signals
85
+ // (`model.$.<field>`). Layout carries NO validators; rules live in the
86
+ // validation schema below.
85
87
  const schema = {
86
88
  children: [
87
- { value: model.$.username, component: Input, validators: [required(), minLength(2)] },
88
- { value: model.$.email, component: Input, validators: [required(), email()] },
89
- { value: model.$.password, component: Input, validators: [required(), minLength(8)] },
90
- {
91
- value: model.$.confirmPassword,
92
- component: Input,
93
- validators: [
94
- required(),
95
- (value, m) =>
96
- value && m.password && value !== m.password
97
- ? { code: 'mismatch', message: 'Passwords do not match' }
98
- : null,
99
- ],
100
- },
89
+ { value: model.$.username, component: Input },
90
+ { value: model.$.email, component: Input },
91
+ { value: model.$.password, component: Input },
92
+ { value: model.$.confirmPassword, component: Input },
101
93
  ],
102
94
  };
103
95
 
104
- // 4. Behavior — declarative reactive logic, run by `createForm({ behavior })`.
105
- // Clear confirmPassword whenever password changes.
106
- const behavior = defineFormBehavior<RegistrationForm>(({ model }) => {
96
+ // 4. Validation schema — a separate ambient contract from `@reformer/core/validation`,
97
+ // run on demand (not reactive). Field rules go through `validate(sig, [rules])`;
98
+ // cross-field rules through `cross(sig, snapshot => error | null)` where the snapshot
99
+ // is `model.get()`. Rule factories (`required()`/…) come from `@reformer/core/validators`.
100
+ const passwordsMatch = (f: RegistrationForm): ValidationError | null =>
101
+ f.confirmPassword && f.password && f.confirmPassword !== f.password
102
+ ? { code: 'mismatch', message: 'Passwords do not match' }
103
+ : null;
104
+
105
+ const validationSchema = defineValidationSchema<RegistrationForm>(({ model }) => {
106
+ validate(model.$.username, [required(), minLength(2)]);
107
+ validate(model.$.email, [required(), email()]);
108
+ validate(model.$.password, [required(), minLength(8)]);
109
+ validate(model.$.confirmPassword, [required()]);
110
+ cross(model.$.confirmPassword, passwordsMatch);
111
+ });
112
+
113
+ // 5. Behavior — declarative reactive logic, run by `createForm({ behavior })`.
114
+ // Validation runs on demand, so the stale cross-field error on `confirmPassword`
115
+ // is cleared the moment `password` changes — a behavior→validation bridge.
116
+ const behavior = defineFormBehavior<RegistrationForm>(({ model, form }) => {
107
117
  onChange(model.$.password, () => {
108
- if (model.confirmPassword) model.confirmPassword = '';
118
+ form.confirmPassword.clearErrors();
109
119
  });
110
120
  });
111
121
 
112
- // 5. Registration form component
122
+ // 6. Registration form component
113
123
  function RegistrationFormExample() {
114
124
  const form = useMemo(() => createForm<RegistrationForm>({ model, schema, behavior }), []);
115
125
 
116
126
  const handleSubmit = async (e: React.FormEvent) => {
117
127
  e.preventDefault();
118
128
  form.touchAll();
119
- // Validate the whole model against the schema (sync + async); errors route into the form.
120
- const { valid } = await validateFormModel(model, schema);
129
+ // Validate the whole model against the validation schema (sync + async) on demand;
130
+ // errors route into the form nodes, so the UI highlights the offending fields.
131
+ const valid = await validateModel(model, validationSchema);
121
132
  if (valid) {
122
133
  console.log('Form data:', model.get());
123
134
  }
@@ -0,0 +1,18 @@
1
+ import { effect as i } from "@preact/signals-core";
2
+ import { g as s } from "./signal-node-registry-tiwel-TV.js";
3
+ import { d as f } from "./behaviors-value-lCpqicfa.js";
4
+ function a(n, r, t) {
5
+ return i(() => {
6
+ const o = r(), e = s(n);
7
+ e && f(() => {
8
+ o ? e.enable() : (e.disable(), t?.resetOnDisable && e.reset());
9
+ });
10
+ });
11
+ }
12
+ function b(n, r, t) {
13
+ return a(n, () => !r(), t);
14
+ }
15
+ export {
16
+ b as d,
17
+ a as e
18
+ };
package/dist/behaviors.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { effect as O } from "@preact/signals-core";
2
2
  import { d as $, m as k, u as x, a as S, w as F, t as A, r as C, s as D, b as M } from "./behaviors-value-lCpqicfa.js";
3
- import { e as G } from "./behaviors-node-BMOI2JJF.js";
3
+ import { e as G } from "./behaviors-node-DUrqAqp1.js";
4
4
  let p = null;
5
5
  function P(e) {
6
6
  if (!p)
@@ -73,12 +73,13 @@ export declare function createFormFromModel<T>(args: CreateFormFromModelArgs<T>)
73
73
  *
74
74
  * @group Utilities
75
75
  *
76
- * @param args - Модель данных, единая схема (component/componentProps/validators) и (опционально) поведение
76
+ * @param args - Модель данных, layout-схема (component/componentProps) и (опционально) поведение
77
77
  * @returns Типизированная форма с Proxy-доступом к полям
78
78
  *
79
- * @example Архитектура M1: `createModel` + единая схема + `createForm({ model, schema })`
79
+ * @example Архитектура M1: `createModel` + layout-схема + `createForm({ model, schema })`
80
80
  * ```typescript
81
- * import { createModel, createForm, validateFormModel } from '@reformer/core';
81
+ * import { createModel, createForm } from '@reformer/core';
82
+ * import { defineValidationSchema, validate, validateModel } from '@reformer/core/validation';
82
83
  * import { required, email, minLength } from '@reformer/core/validators';
83
84
  *
84
85
  * interface UserForm {
@@ -88,28 +89,23 @@ export declare function createFormFromModel<T>(args: CreateFormFromModelArgs<T>)
88
89
  *
89
90
  * const model = createModel<UserForm>({ email: '', password: '' });
90
91
  *
92
+ * // Layout-схема НЕ несёт validators — только component/componentProps.
91
93
  * const schema = {
92
94
  * children: [
93
- * {
94
- * value: model.$.email,
95
- * component: Input,
96
- * validators: [required(), email()],
97
- * },
98
- * {
99
- * value: model.$.password,
100
- * component: Input,
101
- * validators: [required(), minLength(8)],
102
- * },
95
+ * { value: model.$.email, component: Input },
96
+ * { value: model.$.password, component: Input },
103
97
  * ],
104
98
  * };
105
99
  *
106
100
  * const form = createForm<UserForm>({ model, schema });
107
- *
108
- * // TypeScript знает о полях (нода привязана к сигналу модели):
109
101
  * form.email.setValue('test@mail.com');
110
102
  *
111
- * // Валидация всей модели по схеме (sync + async):
112
- * const { valid } = await validateFormModel(model, schema);
103
+ * // Валидация — ОТДЕЛЬНАЯ схема, прогон внешним раннером (ошибки сами доезжают до нод):
104
+ * const validation = defineValidationSchema<UserForm>(({ model }) => {
105
+ * validate(model.$.email, [required(), email()]);
106
+ * validate(model.$.password, [required(), minLength(8)]);
107
+ * });
108
+ * const ok = await validateModel(model, validation);
113
109
  * ```
114
110
  */
115
111
  export declare function createForm<T>(args: CreateFormFromModelArgs<T>): FormProxy<T>;
@@ -2,9 +2,10 @@
2
2
  * Модуль `form` — доменный слой формы поверх state-субстрата (`model`).
3
3
  *
4
4
  * Ноды (value/touched/dirty/status/errors/componentProps), сборка формы из модели по идентичности
5
- * сигнала (`createForm`), state-операции над нодами (`enableWhen`/`disableWhen`), in-form роутинг
6
- * валидации (`validateFormModel`), submit/статус, React-хуки. Зависит от `model` (form→state
7
- * разрешено); обратной зависимости нет.
5
+ * сигнала (`createForm`), state-операции над нодами (`enableWhen`/`disableWhen`), submit/статус,
6
+ * React-хуки. Schema-валидация — внешний контракт `@reformer/core/validation` (`validateModel`),
7
+ * роутит ошибки в ноды через реестр сигнал→нода. Зависит от `model` (form→state разрешено);
8
+ * обратной зависимости нет.
8
9
  *
9
10
  * @group Form
10
11
  * @module form
@@ -18,9 +19,6 @@ export { ModelArrayNode } from './nodes/model-array-node';
18
19
  export type { ModelArrayControl } from './nodes/model-array-node';
19
20
  export { createForm, createFormFromModel } from './create-form';
20
21
  export type { CreateFormFromModelArgs } from './create-form';
21
- export { validateModel, validateModelSync } from './validate-model-core';
22
- export type { ModelValidator, ModelValidationResult } from './validate-model-core';
23
- export { validateFormModel } from './validate-model';
24
22
  export { enableWhen, disableWhen } from './behaviors-node';
25
23
  export { FormErrorHandler, ErrorStrategy } from './error-handler';
26
24
  export { registerSignalNode, getNodeForSignal } from './signal-node-registry';
@@ -7,8 +7,8 @@ import { SubmitOptions, SubmitResult } from '../form-submitter';
7
7
  * GroupNode - узел для группы полей
8
8
  *
9
9
  * Создаётся из {@link FormSchema} (дерево field-конфигов). Обычно строится через `createForm`
10
- * (M1: `createForm({ model, schema })`); валидация/behavior живут на слое модели
11
- * (`validateFormModel`, `computeFrom`/`enableWhen`/…), а не на ноде.
10
+ * (M1: `createForm({ model, schema })`); schema-валидация/behavior живут на слое модели
11
+ * (`validateModel` из `@reformer/core/validation`, `computeFrom`/`enableWhen`/…), а не на ноде.
12
12
  *
13
13
  * @group Nodes
14
14
  *
@@ -54,12 +54,6 @@ export declare class GroupNode<T> extends FormNode<T> {
54
54
  private readonly _disabled;
55
55
  /** Form-level validation errors */
56
56
  private readonly _formErrors;
57
- /**
58
- * M1: валидация уровня модели/схемы (`validateFormModel(model, schema)`). Привязывается
59
- * `createForm({ model, schema })`, т.к. под M1 валидаторы срезаны с FieldNode и живут на слое
60
- * модели. Без неё `validate()`/`submit()` пропускали бы все schema-валидаторы. @internal
61
- */
62
- private _modelValidate?;
63
57
  /**
64
58
  * M1: связь листовой ноды-ребёнка → её сигнал модели (тот, что помечает `markDerived`).
65
59
  * Нужна bulk-сеттерам (`setValue`/`patchValue`), чтобы корректно определять derived-поля:
@@ -281,13 +275,6 @@ export declare class GroupNode<T> extends FormNode<T> {
281
275
  * @internal
282
276
  */
283
277
  attachBehaviorCleanup(cleanup: () => void): void;
284
- /**
285
- * Прикрепить валидацию уровня модели/схемы (M1). Вызывается `createForm({ model, schema })`,
286
- * чтобы `validate()`/`submit()` прогоняли schema-валидаторы (`validateFormModel`), а не только
287
- * пустые FieldNode. Возвращаемый флаг — валидна ли модель по схеме.
288
- * @internal
289
- */
290
- attachModelValidator(validate: () => Promise<boolean>): void;
291
278
  /**
292
279
  * Связать листовую ноду-ребёнка с её сигналом модели (M1). Вызывается `createForm` для каждого
293
280
  * листового поля на его владеющей группе — используется bulk-сеттерами для derived-guard (F9).
@@ -1,10 +1,10 @@
1
- import { ComponentType } from 'react';
1
+ import { ElementType } from 'react';
2
2
  import { ValidationError } from './contracts';
3
3
  /**
4
- * Валидатор поля схемы. Узел хранит validators гетерогенно: движок `validate*` вызывает их как
5
- * `(value, scope, root)`, но сюда кладут любой контракт — `ValidatorFn` (value),
6
- * `Validator` (value, control, root) или `ModelValidator` (value, model, root). Поэтому тип
7
- * намеренно широкий по параметрам.
4
+ * Валидатор поля схемы (legacy). Дерево-движок, вызывавший `validators` узла как
5
+ * `(value, scope, root)`, удалён — живые правила это `Rule<T>` в `validate(sig, [...])`
6
+ * (`@reformer/core/validation`). Тип остаётся широким по параметрам для совместимости
7
+ * узлов, всё ещё несущих `validators` (гетерогенные контракты `ValidatorFn`/`Validator`).
8
8
  *
9
9
  * @group Types
10
10
  */
@@ -26,8 +26,8 @@ export interface SchemaArrayControl {
26
26
  removeAt(index: number): void;
27
27
  }
28
28
  /**
29
- * Узел единой схемы M1 — дерево, обходимое `createForm({ model, schema })`,
30
- * `validateModel`/`validateFormModel` и рендерерами.
29
+ * Узел единой схемы M1 — layout-дерево, обходимое `createForm({ model, schema })`
30
+ * и рендерерами (schema-валидация живёт отдельно — `@reformer/core/validation`).
31
31
  *
32
32
  * Узел совмещает несколько ролей (различаются рантаймом по форме):
33
33
  * - **поле** — несёт `value: Signal` (сигнал модели `model.$.x`) + `component`/`validators`;
@@ -47,8 +47,12 @@ export interface FormSchemaNode {
47
47
  * `Signal`). Движок разбирает узел как поле рантаймом по `value instanceof Signal`.
48
48
  */
49
49
  value?: unknown;
50
- /** UI-компонент. Опционален: core-часть работает без UI (значение/валидация). */
51
- component?: ComponentType<any>;
50
+ /**
51
+ * UI-компонент либо нативный HTML-тег (`'div'`, `'p'`, `'h3'`) для презентационной вёрстки
52
+ * прямо в схеме. Опционален: core-часть работает без UI (значение/валидация) и `component`
53
+ * не интерпретирует — он доезжает до рендерера как есть.
54
+ */
55
+ component?: ElementType;
52
56
  /** Props компонента. Также «клапан» для вложенности под-узлов (напр. steps визарда). */
53
57
  componentProps?: Record<string, unknown>;
54
58
  validators?: SchemaValidator[];
@@ -1,36 +1,14 @@
1
1
  import { FormValue, ValidationError } from './index';
2
2
  import { FormModel } from '../../state/types';
3
3
  /**
4
- * Чистый синхронный валидатор поля.
4
+ * Чистый синхронный валидатор поля (legacy-сигнатура `(value, scope, root)`).
5
5
  *
6
- * Сигнатура зеркалит то, что реально вызывает движок M1
7
- * (`validateModel`/`validateFormModel`/`validateModelSync`): `validator(value, scope, root)`.
8
- * - `value` — значение поля (`TField`);
9
- * - `scope` — ближайшая scope-**модель** (под-модель элемента массива или корень). Из `TForm`/`TField`
10
- * её тип не выводится, поэтому `unknown` — потребитель сужает сам (для типизированного scope
11
- * используйте {@link ModelValidator}`<TField, TScope, TForm>`);
12
- * - `root` — корневая модель формы `FormModel<TForm>` (реактивный value-proxy: `root.field` читает
13
- * значение). Возвращает `ValidationError` либо `null`. Не знает про реестр валидации.
14
- *
15
- * Совместим с полем `validators` узла схемы (см. `SchemaValidator`) — там же лежат `ModelValidator`
16
- * и `ValidatorFn`. Встроенные фабрики (`required()`/`email()`/…) возвращают `(value) => …` и
17
- * дополнительные аргументы игнорируют.
18
- *
19
- * @example Кастомный валидатор в массиве `validators` поля схемы
20
- * ```typescript
21
- * const isAdult: Validator<MyForm, number> = (value, _scope, root) => {
22
- * if (value < 18) return { code: 'tooYoung', message: '18+' };
23
- * // root — FormModel<MyForm>: root.someOtherField читается как значение
24
- * return null;
25
- * };
26
- *
27
- * // Фабрики и кастомные валидаторы кладутся в `validators: [...]` поля:
28
- * const schema = {
29
- * children: [
30
- * { value: model.$.age, component: Input, validators: [required(), isAdult] },
31
- * ],
32
- * };
33
- * ```
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).
34
12
  */
35
13
  export type Validator<TForm, TField> = (value: TField, scope: unknown, root: FormModel<TForm>) => ValidationError | null;
36
14
  /**
@@ -19,3 +19,10 @@ export { pastDate } from './validators/past-date';
19
19
  export { futureDate } from './validators/future-date';
20
20
  export { minAge } from './validators/min-age';
21
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';
@@ -20,4 +20,4 @@ import { Validator, ValidateOptions } from '../../types/validation-schema';
20
20
  * },
21
21
  * ```
22
22
  */
23
- export declare function email<TForm = unknown, TField extends string | undefined = string>(options?: ValidateOptions): Validator<TForm, TField>;
23
+ export declare function email<TForm = unknown, TField extends string | null | undefined = string>(options?: ValidateOptions): Validator<TForm, TField>;
@@ -0,0 +1,31 @@
1
+ import { Validator, ValidateOptions } from '../../types/validation-schema';
2
+ /**
3
+ * Фабрика валидатора типа файла.
4
+ *
5
+ * Проверяет каждый файл против accept-строки в синтаксисе нативного атрибута
6
+ * `<input type="file" accept>`: расширения (`.pdf`), точные MIME (`image/png`),
7
+ * wildcard-категории (`image/*`), список через запятую. Матчинг регистронезависимый,
8
+ * по `type` и/или расширению из `name` ({@link matchesFileAccept}).
9
+ *
10
+ * Нужен потому, что нативный `accept` — только подсказка пикеру: на drag-and-drop
11
+ * и «All files» он не действует. Пустые значения (`null`/`undefined`/`''`/`[]`)
12
+ * пропускаются (используйте {@link required} для обязательности).
13
+ *
14
+ * @param accept - Accept-строка допустимых типов, например `'image/*,.pdf'`
15
+ * @param options - Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически
16
+ * попадают `accept` и `fileName` (первый нарушивший файл).
17
+ * @returns Чистый валидатор {@link Validator} для файла или массива файлов
18
+ *
19
+ * @example Только изображения и PDF
20
+ * ```typescript
21
+ * import { fileType } from '@reformer/core/validators';
22
+ *
23
+ * // Внутри FieldConfig схемы формы:
24
+ * documents: {
25
+ * value: model.$.documents,
26
+ * component: FileUploadField,
27
+ * validators: [fileType('image/*,.pdf', { message: 'Только изображения или PDF' })],
28
+ * },
29
+ * ```
30
+ */
31
+ export declare function fileType<TForm = unknown, TField = unknown>(accept: string, options?: ValidateOptions): Validator<TForm, TField>;
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Утилиты для работы с файлами в валидаторах.
3
+ *
4
+ * Duck-typing вместо `instanceof File` — SSR-безопасность (в Node может не быть глобального
5
+ * `File`) и поддержка сериализуемых дескрипторов загруженных файлов (`RemoteFileRef` из
6
+ * `@reformer/cdk/file-upload`) наравне с нативными `File`.
7
+ *
8
+ * @group Validation
9
+ * @category Validators
10
+ * @module validators/file-utils
11
+ */
12
+ /**
13
+ * Минимальный файлоподобный контракт, с которым работают file-валидаторы:
14
+ * нативный `File`, сериализуемый дескриптор загруженного файла и т.п.
15
+ */
16
+ export interface FileLike {
17
+ name: string;
18
+ size?: number;
19
+ type?: string;
20
+ }
21
+ /**
22
+ * Проверяет, что значение файлоподобно: объект со строковым `name` и опциональными
23
+ * числовым `size` / строковым `type`.
24
+ *
25
+ * @param value - Проверяемое значение
26
+ * @returns `true`, если значение соответствует {@link FileLike}
27
+ */
28
+ export declare function isFileLike(value: unknown): value is FileLike;
29
+ /**
30
+ * Нормализует значение file-поля в массив файлоподобных элементов.
31
+ *
32
+ * Одиночный {@link FileLike} оборачивается в массив; из массива отбираются только
33
+ * файлоподобные элементы. Для прочих значений — `null` (валидатор пропускает).
34
+ *
35
+ * @param value - Значение поля (`FileLike`, `FileLike[]` или что-то ещё)
36
+ * @returns Массив файлоподобных элементов или `null`
37
+ */
38
+ export declare function toFileArray(value: unknown): FileLike[] | null;
39
+ /**
40
+ * Проверяет файл против accept-паттерна в синтаксисе нативного атрибута
41
+ * `<input type="file" accept>`: список через запятую из расширений (`.pdf`),
42
+ * точных MIME (`image/png`) и wildcard-категорий (`image/*`). Регистронезависимо.
43
+ *
44
+ * Нужен собственный матчер, потому что `accept` у нативного input — только подсказка
45
+ * пикеру: на drag-and-drop и на программное значение он не действует.
46
+ *
47
+ * @param file - Файлоподобное значение (`name` + опциональный `type`)
48
+ * @param accept - Accept-строка; пустая/пробельная строка матчит всё
49
+ * @returns `true`, если файл подходит хотя бы под один паттерн
50
+ */
51
+ export declare function matchesFileAccept(file: Pick<FileLike, 'name' | 'type'>, accept: string): boolean;
@@ -20,4 +20,4 @@ import { Validator, ValidateOptions } from '../../types/validation-schema';
20
20
  * },
21
21
  * ```
22
22
  */
23
- export declare function futureDate<TForm = unknown, TField extends string | Date | undefined = string | Date>(options?: ValidateOptions): Validator<TForm, TField>;
23
+ export declare function futureDate<TForm = unknown, TField extends string | Date | null | undefined = string | Date>(options?: ValidateOptions): Validator<TForm, TField>;
@@ -26,3 +26,10 @@ export { pastDate } from './past-date';
26
26
  export { futureDate } from './future-date';
27
27
  export { minAge } from './min-age';
28
28
  export { maxAge } from './max-age';
29
+ export { maxFileSize } from './max-file-size';
30
+ export { minFileSize } from './min-file-size';
31
+ export { fileType } from './file-type';
32
+ export { maxFiles } from './max-files';
33
+ export { minFiles } from './min-files';
34
+ export { maxTotalFileSize } from './max-total-file-size';
35
+ export { isFileLike, matchesFileAccept, toFileArray, type FileLike } from './file-utils';
@@ -20,4 +20,4 @@ import { Validator, ValidateOptions } from '../../types/validation-schema';
20
20
  * },
21
21
  * ```
22
22
  */
23
- export declare function isDate<TForm = unknown, TField extends string | Date | undefined = string | Date>(options?: ValidateOptions): Validator<TForm, TField>;
23
+ export declare function isDate<TForm = unknown, TField extends string | Date | null | undefined = string | Date>(options?: ValidateOptions): Validator<TForm, TField>;
@@ -0,0 +1,28 @@
1
+ import { Validator, ValidateOptions } from '../../types/validation-schema';
2
+ /**
3
+ * Фабрика валидатора максимального размера файла.
4
+ *
5
+ * Работает с одиночным файлом или массивом (`File` либо файлоподобный дескриптор с
6
+ * `name`/`size`). Проверяется каждый файл; в ошибку попадает первый нарушивший.
7
+ * Пустые значения (`null`/`undefined`/`''`/`[]`) и элементы без числового `size`
8
+ * (например, дескриптор загруженного файла без размера) пропускаются
9
+ * (используйте {@link required} для обязательности).
10
+ *
11
+ * @param maxSize - Максимально допустимый размер файла в байтах (включительно)
12
+ * @param options - Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически
13
+ * попадают `maxFileSize`, `fileName` и `actualSize`.
14
+ * @returns Чистый валидатор {@link Validator} для файла или массива файлов
15
+ *
16
+ * @example Ограничение размера вложений
17
+ * ```typescript
18
+ * import { maxFileSize } from '@reformer/core/validators';
19
+ *
20
+ * // Внутри FieldConfig схемы формы:
21
+ * documents: {
22
+ * value: model.$.documents,
23
+ * component: FileUploadField,
24
+ * validators: [maxFileSize(5 * 1024 * 1024, { message: 'Файл больше 5 МБ' })],
25
+ * },
26
+ * ```
27
+ */
28
+ export declare function maxFileSize<TForm = unknown, TField = unknown>(maxSize: number, options?: ValidateOptions): Validator<TForm, TField>;
@@ -0,0 +1,26 @@
1
+ import { Validator, ValidateOptions } from '../../types/validation-schema';
2
+ /**
3
+ * Фабрика валидатора максимального количества файлов.
4
+ *
5
+ * Работает с массивом файлов (одиночный файл считается как 1). Пустые значения
6
+ * (`null`/`undefined`/`''`/`[]`) пропускаются (используйте {@link required}
7
+ * для обязательности).
8
+ *
9
+ * @param max - Максимально допустимое количество файлов (включительно)
10
+ * @param options - Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически
11
+ * попадают `maxFiles` и `actualCount`.
12
+ * @returns Чистый валидатор {@link Validator} для файла или массива файлов
13
+ *
14
+ * @example Не более трёх вложений
15
+ * ```typescript
16
+ * import { maxFiles } from '@reformer/core/validators';
17
+ *
18
+ * // Внутри FieldConfig схемы формы:
19
+ * documents: {
20
+ * value: model.$.documents,
21
+ * component: FileUploadField,
22
+ * validators: [maxFiles(3, { message: 'Максимум 3 файла' })],
23
+ * },
24
+ * ```
25
+ */
26
+ export declare function maxFiles<TForm = unknown, TField = unknown>(max: number, options?: ValidateOptions): Validator<TForm, TField>;
@@ -0,0 +1,26 @@
1
+ import { Validator, ValidateOptions } from '../../types/validation-schema';
2
+ /**
3
+ * Фабрика валидатора суммарного размера всех файлов.
4
+ *
5
+ * Складывает `size` всех файлов значения (элементы без числового `size` в сумму
6
+ * не входят). Пустые значения (`null`/`undefined`/`''`/`[]`) пропускаются
7
+ * (используйте {@link required} для обязательности).
8
+ *
9
+ * @param maxTotal - Максимально допустимый суммарный размер в байтах (включительно)
10
+ * @param options - Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически
11
+ * попадают `maxTotalFileSize` и `actualTotal`.
12
+ * @returns Чистый валидатор {@link Validator} для файла или массива файлов
13
+ *
14
+ * @example Суммарно не более 20 МБ
15
+ * ```typescript
16
+ * import { maxTotalFileSize } from '@reformer/core/validators';
17
+ *
18
+ * // Внутри FieldConfig схемы формы:
19
+ * documents: {
20
+ * value: model.$.documents,
21
+ * component: FileUploadField,
22
+ * validators: [maxTotalFileSize(20 * 1024 * 1024, { message: 'Суммарно не более 20 МБ' })],
23
+ * },
24
+ * ```
25
+ */
26
+ export declare function maxTotalFileSize<TForm = unknown, TField = unknown>(maxTotal: number, options?: ValidateOptions): Validator<TForm, TField>;
@@ -0,0 +1,28 @@
1
+ import { Validator, ValidateOptions } from '../../types/validation-schema';
2
+ /**
3
+ * Фабрика валидатора минимального размера файла.
4
+ *
5
+ * Работает с одиночным файлом или массивом (`File` либо файлоподобный дескриптор с
6
+ * `name`/`size`). Проверяется каждый файл; в ошибку попадает первый нарушивший.
7
+ * Типовой сценарий — отсев пустых (0-байтовых) файлов: `minFileSize(1)`.
8
+ * Пустые значения (`null`/`undefined`/`''`/`[]`) и элементы без числового `size`
9
+ * пропускаются (используйте {@link required} для обязательности).
10
+ *
11
+ * @param minSize - Минимально допустимый размер файла в байтах (включительно)
12
+ * @param options - Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически
13
+ * попадают `minFileSize`, `fileName` и `actualSize`.
14
+ * @returns Чистый валидатор {@link Validator} для файла или массива файлов
15
+ *
16
+ * @example Отсев пустых файлов
17
+ * ```typescript
18
+ * import { minFileSize } from '@reformer/core/validators';
19
+ *
20
+ * // Внутри FieldConfig схемы формы:
21
+ * documents: {
22
+ * value: model.$.documents,
23
+ * component: FileUploadField,
24
+ * validators: [minFileSize(1, { message: 'Файл пустой' })],
25
+ * },
26
+ * ```
27
+ */
28
+ export declare function minFileSize<TForm = unknown, TField = unknown>(minSize: number, options?: ValidateOptions): Validator<TForm, TField>;
@@ -0,0 +1,26 @@
1
+ import { Validator, ValidateOptions } from '../../types/validation-schema';
2
+ /**
3
+ * Фабрика валидатора минимального количества файлов.
4
+ *
5
+ * Работает с массивом файлов (одиночный файл считается как 1). Пустые значения
6
+ * (`null`/`undefined`/`''`/`[]`) пропускаются — «нужен хотя бы один файл» выражается
7
+ * связкой `required()` + `minFiles(n)`, как и у {@link minLength}.
8
+ *
9
+ * @param min - Минимально допустимое количество файлов (включительно)
10
+ * @param options - Опции валидатора ({@link ValidateOptions}). В `params` ошибки автоматически
11
+ * попадают `minFiles` и `actualCount`.
12
+ * @returns Чистый валидатор {@link Validator} для файла или массива файлов
13
+ *
14
+ * @example Не менее двух документов
15
+ * ```typescript
16
+ * import { required, minFiles } from '@reformer/core/validators';
17
+ *
18
+ * // Внутри FieldConfig схемы формы:
19
+ * documents: {
20
+ * value: model.$.documents,
21
+ * component: FileUploadField,
22
+ * validators: [required(), minFiles(2, { message: 'Приложите минимум 2 документа' })],
23
+ * },
24
+ * ```
25
+ */
26
+ export declare function minFiles<TForm = unknown, TField = unknown>(min: number, options?: ValidateOptions): Validator<TForm, TField>;
@@ -20,4 +20,4 @@ import { Validator, ValidateOptions } from '../../types/validation-schema';
20
20
  * },
21
21
  * ```
22
22
  */
23
- export declare function pastDate<TForm = unknown, TField extends string | Date | undefined = string | Date>(options?: ValidateOptions): Validator<TForm, TField>;
23
+ export declare function pastDate<TForm = unknown, TField extends string | Date | null | undefined = string | Date>(options?: ValidateOptions): Validator<TForm, TField>;
@@ -30,4 +30,4 @@ import { Validator, ValidateOptions } from '../../types/validation-schema';
30
30
  * },
31
31
  * ```
32
32
  */
33
- export declare function pattern<TForm = unknown, TField extends string | undefined = string>(regex: RegExp, options?: ValidateOptions): Validator<TForm, TField>;
33
+ export declare function pattern<TForm = unknown, TField extends string | null | undefined = string>(regex: RegExp, options?: ValidateOptions): Validator<TForm, TField>;