@reformer/core 2.0.0-beta.5 → 2.0.0-beta.7

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.
@@ -1,4 +1,4 @@
1
- import { b as w, c as E, d as I, B as L } from "./registry-helpers-BRxAr6nG.js";
1
+ import { b as w, c as E, d as I, B as L } from "./registry-helpers--8-OogF8.js";
2
2
  var $ = /* @__PURE__ */ Symbol.for("preact-signals");
3
3
  function B() {
4
4
  if (y > 1)
@@ -348,13 +348,22 @@ function Q(e, t, i) {
348
348
  const m = a.value.value;
349
349
  c || h(() => {
350
350
  c = !0;
351
- const U = s ? s(m) : m;
352
- r.setValue(U, { emitEvent: !1 }), c = !1;
351
+ try {
352
+ const U = s ? s(m) : m;
353
+ r.setValue(U, { emitEvent: !1 });
354
+ } finally {
355
+ c = !1;
356
+ }
353
357
  });
354
358
  }), b = g(() => {
355
359
  const m = r.value.value;
356
360
  c || h(() => {
357
- c = !0, a.setValue(m, { emitEvent: !1 }), c = !1;
361
+ c = !0;
362
+ try {
363
+ a.setValue(m, { emitEvent: !1 });
364
+ } finally {
365
+ c = !1;
366
+ }
358
367
  });
359
368
  });
360
369
  return () => {
package/dist/behaviors.js CHANGED
@@ -1,5 +1,5 @@
1
- import { a as s, b as r, g as t, c as o, m as l, i as h, f as n, e as i, k as m, j as p, s as c, t as d, l as f, n as F, h as y } from "./behaviors-DzYL8kY_.js";
2
- import { d as v, B, c as b } from "./registry-helpers-BRxAr6nG.js";
1
+ import { a as s, b as r, g as t, c as o, m as l, i as h, f as n, e as i, k as m, j as p, s as c, t as d, l as f, n as F, h as y } from "./behaviors-DyPzh2-X.js";
2
+ import { d as v, B, c as b } from "./registry-helpers--8-OogF8.js";
3
3
  export {
4
4
  v as BehaviorContextImpl,
5
5
  B as BehaviorRegistry,
@@ -39,6 +39,8 @@ export declare class ArrayNode<T extends FormFields> extends FormNode<T[]> {
39
39
  * Использует SubscriptionManager вместо массива для управления подписками
40
40
  */
41
41
  private disposers;
42
+ /** Array-level validation errors (e.g., "минимум 1 элемент") */
43
+ private readonly _arrayErrors;
42
44
  private validationSchemaFn?;
43
45
  private behaviorSchemaFn?;
44
46
  readonly value: ReadonlySignal<T[]>;
@@ -74,7 +76,7 @@ export declare class ArrayNode<T extends FormFields> extends FormNode<T[]> {
74
76
  /**
75
77
  * Получить элемент по индексу
76
78
  * @param index - Индекс элемента
77
- * @returns Типизированный GroupNode или undefined если индекс вне границ
79
+ * @returns Типизированный GroupNode proxy или undefined если индекс вне границ
78
80
  */
79
81
  at(index: number): FormProxy<T> | undefined;
80
82
  getValue(): T[];
@@ -128,7 +130,29 @@ export declare class ArrayNode<T extends FormFields> extends FormNode<T[]> {
128
130
  */
129
131
  resetToInitial(): void;
130
132
  validate(): Promise<boolean>;
131
- setErrors(_errors: ValidationError[]): void;
133
+ /**
134
+ * Установить array-level validation errors
135
+ *
136
+ * @param errors - Массив ошибок валидации уровня массива
137
+ *
138
+ * @example
139
+ * ```typescript
140
+ * arrayNode.setErrors([{
141
+ * code: 'minItems',
142
+ * message: 'Минимум 1 элемент обязателен',
143
+ * }]);
144
+ * ```
145
+ */
146
+ setErrors(errors: ValidationError[]): void;
147
+ /**
148
+ * Очистить все errors (array-level + item-level)
149
+ *
150
+ * @example
151
+ * ```typescript
152
+ * arrayNode.clearErrors();
153
+ * console.log(arrayNode.errors.value); // []
154
+ * ```
155
+ */
132
156
  clearErrors(): void;
133
157
  /**
134
158
  * Hook: вызывается после markAsTouched()
@@ -156,12 +180,12 @@ export declare class ArrayNode<T extends FormFields> extends FormNode<T[]> {
156
180
  protected onMarkAsPristine(): void;
157
181
  /**
158
182
  * Итерировать по элементам массива
159
- * @param callback - Функция, вызываемая для каждого элемента с типизированным GroupNode
183
+ * @param callback - Функция, вызываемая для каждого элемента с типизированным GroupNode proxy
160
184
  */
161
185
  forEach(callback: (item: FormProxy<T>, index: number) => void): void;
162
186
  /**
163
187
  * Маппинг элементов массива
164
- * @param callback - Функция преобразования с типизированным GroupNode
188
+ * @param callback - Функция преобразования с типизированным GroupNode proxy
165
189
  * @returns Новый массив результатов
166
190
  */
167
191
  map<R>(callback: (item: FormProxy<T>, index: number) => R): R[];
@@ -49,6 +49,7 @@ export declare class FieldNode<T> extends FormNode<T> {
49
49
  private updateOn;
50
50
  private initialValue;
51
51
  private currentValidationId;
52
+ private currentAbortController?;
52
53
  private debounceMs;
53
54
  private validateDebounceTimer?;
54
55
  private validateDebounceResolve?;
@@ -137,12 +138,10 @@ export declare class FieldNode<T> extends FormNode<T> {
137
138
  * Немедленная валидация без debounce
138
139
  * @private
139
140
  * @remarks
140
- * Защищена от race conditions:
141
- * - Проверка validationId после синхронной валидации
142
- * - Проверка перед установкой pending
143
- * - Проверка после Promise.all
144
- * - Проверка перед обработкой async результатов
145
- * - Проверка перед очисткой errors
141
+ * Защищена от race conditions через AbortController:
142
+ * - Отменяет предыдущую валидацию при запуске новой
143
+ * - Передаёт AbortSignal в async валидаторы для отмены операций (например, fetch)
144
+ * - Проверяет signal.aborted в ключевых точках
146
145
  */
147
146
  private validateImmediate;
148
147
  setErrors(errors: ValidationError[]): void;
@@ -256,6 +255,13 @@ export declare class FieldNode<T> extends FormNode<T> {
256
255
  * Очистить все ресурсы и таймеры
257
256
  * Должен вызываться при unmount компонента
258
257
  *
258
+ * @remarks
259
+ * Освобождает все ресурсы:
260
+ * - Отписывает все subscriptions через SubscriptionManager
261
+ * - Очищает debounce таймер
262
+ * - Resolve'ит висячий debounce промис (предотвращает утечку памяти)
263
+ * - Отменяет текущую async валидацию через AbortController
264
+ *
259
265
  * @example
260
266
  * ```typescript
261
267
  * useEffect(() => {
@@ -16,7 +16,7 @@
16
16
  * items: Array<{ title: string }>;
17
17
  * }
18
18
  *
19
- * const form: FormProxy<MyForm> = new GroupNode(schema);
19
+ * const form = createForm<MyForm>(schema);
20
20
  *
21
21
  * // TypeScript знает, что это FieldNode<string>
22
22
  * form.name.setValue('John');
@@ -72,7 +72,7 @@ export type FormControlsProxy<T> = {
72
72
  * };
73
73
  * }
74
74
  *
75
- * const form: FormProxy<UserForm> = new GroupNode(schema);
75
+ * const form = createForm<UserForm>(schema);
76
76
  *
77
77
  * // Доступ к методам GroupNode
78
78
  * await form.validate();
@@ -20,12 +20,46 @@ export type UnknownFormValue = unknown;
20
20
  * @category Validation Types
21
21
  */
22
22
  export type ValidatorFn<T = FormValue> = (value: T) => ValidationError | null;
23
+ /**
24
+ * Опции для асинхронного валидатора
25
+ * @group Types
26
+ * @category Validation Types
27
+ */
28
+ export interface AsyncValidatorOptions {
29
+ /**
30
+ * AbortSignal для отмены валидации
31
+ * Позволяет отменить асинхронную операцию при новой валидации
32
+ */
33
+ signal?: AbortSignal;
34
+ }
23
35
  /**
24
36
  * Асинхронная функция валидации
37
+ *
38
+ * @param value - Значение для валидации
39
+ * @param options - Опции валидации (опционально)
40
+ * @returns Promise с ошибкой валидации или null если значение валидно
41
+ *
42
+ * @example
43
+ * ```typescript
44
+ * // Простой валидатор (без поддержки отмены)
45
+ * const emailExists: AsyncValidatorFn<string> = async (value) => {
46
+ * const exists = await checkEmail(value);
47
+ * return exists ? { code: 'exists', message: 'Email already exists' } : null;
48
+ * };
49
+ *
50
+ * // Валидатор с поддержкой отмены
51
+ * const emailExistsAbortable: AsyncValidatorFn<string> = async (value, options) => {
52
+ * const exists = await fetch(`/api/check-email?email=${value}`, {
53
+ * signal: options?.signal // Передаём signal в fetch для отмены запроса
54
+ * });
55
+ * return exists ? { code: 'exists', message: 'Email already exists' } : null;
56
+ * };
57
+ * ```
58
+ *
25
59
  * @group Types
26
60
  * @category Validation Types
27
61
  */
28
- export type AsyncValidatorFn<T = FormValue> = (value: T) => Promise<ValidationError | null>;
62
+ export type AsyncValidatorFn<T = FormValue> = (value: T, options?: AsyncValidatorOptions) => Promise<ValidationError | null>;
29
63
  /**
30
64
  * Ошибка валидации
31
65
  * @group Types
@@ -1,18 +1,20 @@
1
1
  /**
2
2
  * Фабричная функция для создания формы с правильной типизацией
3
3
  *
4
- * Решает проблему с типизацией конструктора GroupNode, который возвращает
5
- * Proxy (FormProxy), но TypeScript не может это вывести автоматически.
4
+ * Это рекомендуемый способ создания форм в ReFormer v2.0+.
5
+ * Создаёт GroupNode и возвращает его Proxy для типобезопасного доступа к полям.
6
6
  *
7
7
  * @group Utilities
8
8
  *
9
9
  * @example
10
10
  * ```typescript
11
- * // Вместо:
12
- * const form: FormProxy<MyForm> = new GroupNode<MyForm>(config);
13
- *
14
- * // Используйте:
11
+ * // Рекомендуемый способ:
15
12
  * const form = createForm<MyForm>(config);
13
+ * form.email.setValue('test@mail.com'); // Типобезопасный доступ к полям
14
+ *
15
+ * // Если нужен именно GroupNode instance:
16
+ * const groupNode = new GroupNode<MyForm>(config);
17
+ * const proxy = groupNode.getProxy(); // Явно получаем Proxy
16
18
  * ```
17
19
  */
18
20
  import type { FormProxy, GroupNodeConfig, FormSchema } from '../types';
@@ -13,3 +13,4 @@ export { Debouncer } from './debounce';
13
13
  export { FormErrorHandler, ErrorStrategy } from './error-handler';
14
14
  export { createForm } from './create-form';
15
15
  export * from './resources';
16
+ export { uniqueId } from './unique-id';
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Генератор уникальных идентификаторов
3
+ *
4
+ * Использует атомный счётчик для генерации гарантированно уникальных ключей.
5
+ * Решает проблему возможного дублирования ключей при использовании
6
+ * Date.now() + Math.random() в быстрых последовательных вызовах.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * const key1 = uniqueId('watch'); // "watch-1"
11
+ * const key2 = uniqueId('watch'); // "watch-2"
12
+ * const key3 = uniqueId('effect'); // "effect-3"
13
+ * ```
14
+ */
15
+ /**
16
+ * Генерирует уникальный идентификатор с указанным префиксом
17
+ *
18
+ * @param prefix - Префикс для идентификатора
19
+ * @returns Уникальный идентификатор в формате `${prefix}-${counter}`
20
+ */
21
+ export declare function uniqueId(prefix: string): string;
22
+ /**
23
+ * Сбросить счётчик (только для тестов)
24
+ * @internal
25
+ */
26
+ export declare function resetUniqueIdCounter(): void;
@@ -1,27 +1,25 @@
1
1
  /**
2
2
  * Реализация контекста валидации
3
+ *
4
+ * Использует паттерн наследования для устранения дублирования кода
5
+ * между различными типами контекстов валидации.
3
6
  */
4
7
  import type { GroupNode } from '../nodes/group-node';
5
8
  import type { FieldNode } from '../nodes/field-node';
6
9
  import type { FormProxy } from '../types/form-proxy';
7
10
  import type { FormContext } from '../types/form-context';
8
11
  /**
9
- * Реализация контекста валидации для отдельного поля
10
- * Реализует FormContext
12
+ * Базовый класс контекста валидации
13
+ * Содержит общую логику для всех типов контекстов
14
+ * @internal
11
15
  */
12
- export declare class ValidationContextImpl<TForm, TField> implements FormContext<TForm> {
13
- private _form;
14
- private control;
16
+ declare abstract class BaseValidationContext<TForm> implements FormContext<TForm> {
17
+ protected readonly _form: GroupNode<TForm>;
15
18
  /**
16
19
  * Форма с типизированным Proxy-доступом к полям
17
20
  */
18
21
  readonly form: FormProxy<TForm>;
19
- constructor(form: GroupNode<TForm>, _fieldKey: keyof TForm, control: FieldNode<TField>);
20
- /**
21
- * Получить текущее значение поля (внутренний метод для validation-applicator)
22
- * @internal
23
- */
24
- value(): TField;
22
+ constructor(form: GroupNode<TForm>);
25
23
  /**
26
24
  * Безопасно установить значение поля по строковому пути
27
25
  * Автоматически использует emitEvent: false для предотвращения циклов
@@ -29,42 +27,36 @@ export declare class ValidationContextImpl<TForm, TField> implements FormContext
29
27
  setFieldValue(path: string, value: unknown): void;
30
28
  }
31
29
  /**
32
- * Реализация контекста для cross-field валидации
33
- * Реализует FormContext
30
+ * Контекст валидации для отдельного поля
31
+ * Предоставляет доступ к значению конкретного поля
34
32
  */
35
- export declare class TreeValidationContextImpl<TForm> implements FormContext<TForm> {
36
- private _form;
33
+ export declare class ValidationContextImpl<TForm, TField> extends BaseValidationContext<TForm> {
34
+ private control;
35
+ constructor(form: GroupNode<TForm>, _fieldKey: keyof TForm, control: FieldNode<TField>);
37
36
  /**
38
- * Форма с типизированным Proxy-доступом к полям
37
+ * Получить текущее значение поля (внутренний метод для validation-applicator)
38
+ * @internal
39
39
  */
40
- readonly form: FormProxy<TForm>;
40
+ value(): TField;
41
+ }
42
+ /**
43
+ * Контекст для cross-field валидации
44
+ * Не предоставляет доступ к конкретному полю, только к форме целиком
45
+ */
46
+ export declare class TreeValidationContextImpl<TForm> extends BaseValidationContext<TForm> {
41
47
  constructor(form: GroupNode<TForm>);
42
- /**
43
- * Безопасно установить значение поля по строковому пути
44
- * Автоматически использует emitEvent: false для предотвращения циклов
45
- */
46
- setFieldValue(path: string, value: unknown): void;
47
48
  }
48
49
  /**
49
- * Реализация контекста валидации для ArrayNode
50
- * Позволяет валидаторам типа notEmpty работать с массивами
50
+ * Контекст валидации для ArrayNode
51
+ * Предоставляет доступ к значению массива
51
52
  */
52
- export declare class ArrayValidationContextImpl<TForm, TItem> implements FormContext<TForm> {
53
- private _form;
53
+ export declare class ArrayValidationContextImpl<TForm, TItem> extends BaseValidationContext<TForm> {
54
54
  private arrayValue;
55
- /**
56
- * Форма с типизированным Proxy-доступом к полям
57
- */
58
- readonly form: FormProxy<TForm>;
59
55
  constructor(form: GroupNode<TForm>, _fieldKey: keyof TForm, arrayValue: TItem[]);
60
56
  /**
61
57
  * Получить текущее значение массива (для валидатора)
62
58
  * @internal
63
59
  */
64
60
  value(): TItem[];
65
- /**
66
- * Безопасно установить значение поля по строковому пути
67
- * Автоматически использует emitEvent: false для предотвращения циклов
68
- */
69
- setFieldValue(path: string, value: unknown): void;
70
61
  }
62
+ export {};
@@ -142,11 +142,6 @@ export declare class ValidationRegistry {
142
142
  * Возвращает локальный массив валидаторов (без аргумента form).
143
143
  */
144
144
  getValidators(): ValidatorRegistration[];
145
- /**
146
- * Применить зарегистрированные валидаторы к GroupNode
147
- * @private
148
- */
149
- private applyValidators;
150
145
  /**
151
146
  * Применить array-items validators к ArrayNode элементам
152
147
  * @private