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

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 (56) hide show
  1. package/README.md +7 -3
  2. package/dist/behaviors.js +211 -174
  3. package/dist/create-model-BBRHhHwK.js +30 -0
  4. package/dist/form/behaviors/collections.d.ts +14 -1
  5. package/dist/form/behaviors/context.d.ts +18 -2
  6. package/dist/form/behaviors/index.d.ts +3 -1
  7. package/dist/form/behaviors/internals.d.ts +0 -1
  8. package/dist/form/behaviors/schema-operators.d.ts +76 -0
  9. package/dist/form/behaviors/types.d.ts +15 -3
  10. package/dist/form/create-core-form.d.ts +5 -3
  11. package/dist/form/create-form.d.ts +28 -53
  12. package/dist/form/form-bundle.d.ts +136 -0
  13. package/dist/form/index.d.ts +6 -1
  14. package/dist/form/nodes/field-node.d.ts +2 -2
  15. package/dist/form/nodes/group-node.d.ts +1 -1
  16. package/dist/form/nodes/model-array-node.d.ts +10 -5
  17. package/dist/form/schema-controller.d.ts +137 -0
  18. package/dist/form/schema-subtree.d.ts +28 -0
  19. package/dist/form/signal-node-registry.d.ts +22 -16
  20. package/dist/form/types/deep-schema.d.ts +2 -2
  21. package/dist/form/types/form-proxy.d.ts +1 -1
  22. package/dist/form/types/schema-node.d.ts +37 -12
  23. package/dist/form/validation/context.d.ts +6 -1
  24. package/dist/form/validation/index.d.ts +1 -1
  25. package/dist/form/validation/operators.d.ts +70 -8
  26. package/dist/form/validation/types.d.ts +2 -1
  27. package/dist/index.js +345 -254
  28. package/dist/model/index.d.ts +5 -0
  29. package/dist/model/model-nodes.d.ts +67 -4
  30. package/dist/model/model-signals-proxy.d.ts +8 -1
  31. package/dist/model/model-value-proxy.d.ts +76 -0
  32. package/dist/model/types.d.ts +7 -4
  33. package/dist/model-value-proxy-caTmAiUd.js +419 -0
  34. package/dist/model.js +19 -17
  35. package/dist/node-BeV_lV5i.js +100 -0
  36. package/dist/platforms/react/form-bundle-context.d.ts +27 -0
  37. package/dist/platforms/react/index.d.ts +1 -0
  38. package/dist/strategy-DgzELWno.js +204 -0
  39. package/dist/validation.js +7 -6
  40. package/docs/llms/03-api-signatures.md +4 -4
  41. package/docs/llms/09-formschema.md +5 -5
  42. package/docs/llms/10-arrays.md +23 -4
  43. package/docs/llms/16-ui-components.md +2 -2
  44. package/docs/llms/18-conditional-fields.md +1 -1
  45. package/docs/llms/21-array-operations.md +26 -0
  46. package/docs/llms/28-submit-and-reset.md +3 -3
  47. package/docs/llms/29-async-preload.md +4 -4
  48. package/docs/llms/33-validation-strategy.md +2 -2
  49. package/docs/llms/35-unified-contract.md +246 -0
  50. package/llms-index.json +1 -1
  51. package/llms.txt +1148 -122
  52. package/package.json +1 -1
  53. package/dist/create-model-B8U-COv-.js +0 -103
  54. package/dist/model-signals-proxy-2vqS243J.js +0 -250
  55. package/dist/node-DGEFMTQu.js +0 -18
  56. package/dist/strategy-BXS_gBfz.js +0 -170
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Поддерево схемы для под-модели: содержимое строки массива (`item`) или подформы (`part`).
3
+ *
4
+ * Билдер поддерева зовут два места — сборка формы и рендерер. Оба обязаны получить ОДНО дерево:
5
+ * на объектах его узлов держится идентичность (ключи рендера, записи схемы-контроллера), а сам
6
+ * билдер не обязан быть чистым. Поэтому дерево строится один раз на пару «билдер + под-модель»
7
+ * и дальше отдаётся из памяти.
8
+ *
9
+ * Память привязана к под-модели: исчезла строка массива — исчезли и её поддеревья.
10
+ *
11
+ * @group Utilities
12
+ * @module form/schema-subtree
13
+ */
14
+ /**
15
+ * Поддерево схемы для под-модели — результат `builder(subModel)`, один на пару.
16
+ *
17
+ * @typeParam N - Тип узла, который возвращает билдер.
18
+ * @param builder - Билдер поддерева: `item` узла-массива или `part` узла-подформы.
19
+ * @param subModel - Под-модель строки массива или группы.
20
+ * @returns Узел поддерева; повторный вызов с той же парой отдаёт тот же объект.
21
+ *
22
+ * @example
23
+ * ```typescript
24
+ * const row = model.items.at(0);
25
+ * schemaSubtree(itemSchema, row) === schemaSubtree(itemSchema, row); // true
26
+ * ```
27
+ */
28
+ export declare function schemaSubtree<N>(builder: (subModel: any) => N, subModel: object): N;
@@ -1,13 +1,12 @@
1
- import { Signal } from '@preact/signals-core';
2
1
  import { FormNode } from './nodes/form-node';
3
2
  /**
4
- * Связать сигнал модели с его нодой формы.
3
+ * Связать ручку модели с её нодой формы.
5
4
  *
6
- * Вызывается движком при сборке формы (`createForm`/{@link createFormFromModel}) для каждого
7
- * листового поля. Прикладной код обычно этот реестр не заполняет напрямую.
5
+ * Вызывается движком при сборке формы (`createForm`/{@link createFormFromModel}) для каждого поля,
6
+ * группы и массива под-форм. Прикладной код обычно этот реестр не заполняет напрямую.
8
7
  *
9
- * @param signal - Сигнал значения из {@link FormModel} (ручка поля, `model.$.path`)
10
- * @param node - Нода формы, отвечающая за это поле
8
+ * @param signal - Ручка из {@link FormModel}: сигнал листа, узел-массив или узел-группа дерева `model.$`
9
+ * @param node - Нода формы, отвечающая за эту часть модели
11
10
  *
12
11
  * @example Привязка листовых полей при построении формы
13
12
  * ```typescript
@@ -18,19 +17,20 @@ import { FormNode } from './nodes/form-node';
18
17
  * if (sig && node) registerSignalNode(sig, node);
19
18
  * ```
20
19
  *
21
- * @see {@link getNodeForSignal} - обратный поиск ноды по сигналу
20
+ * @see {@link getNodeForSignal} - обратный поиск ноды по ручке
22
21
  * @group Utilities
23
22
  */
24
- export declare function registerSignalNode(signal: Signal<any>, node: FormNode<any>): void;
23
+ export declare function registerSignalNode(signal: object, node: FormNode<any>): void;
25
24
  /**
26
- * Найти ноду формы по сигналу модели.
25
+ * Найти ноду формы по ручке модели.
27
26
  *
28
- * Используется state-операциями behavior (`enableWhen`/`disableWhen`) и роутингом ошибок
29
- * валидации в ноды. Возвращает `undefined`, если форма ещё не построена или поле не
30
- * материализовано в этой форме (например, элемент массива, который строится per-item).
27
+ * Используется state-операциями behavior (`enableWhen`/`disableWhen`), подключением под-схем
28
+ * (`apply`/`applyEach`) и роутингом ошибок валидации в ноды. Возвращает `undefined`, если форма
29
+ * ещё не построена или эта часть модели в форме не материализована (например, массив без
30
+ * item-схемы).
31
31
  *
32
- * @param signal - Сигнал значения из {@link FormModel}
33
- * @returns Нода формы для этого поля или `undefined`
32
+ * @param signal - Ручка из {@link FormModel}: `model.$.<поле>`, `model.$.<группа>`, `model.$.<массив>`
33
+ * @returns Нода формы — поля, группы или массива — либо `undefined`
34
34
  *
35
35
  * @example Роутинг ошибок валидации в ноду поля
36
36
  * ```typescript
@@ -40,7 +40,13 @@ export declare function registerSignalNode(signal: Signal<any>, node: FormNode<a
40
40
  * getNodeForSignal(sig)?.setErrors([{ code: 'required', message: 'Обязательно' }]);
41
41
  * ```
42
42
  *
43
- * @see {@link registerSignalNode} - регистрация связи сигнал→нода
43
+ * @example Нода группы и строки массива
44
+ * ```typescript
45
+ * getNodeForSignal(model.$.address)?.disable(); // группа целиком
46
+ * getNodeForSignal(model.$.phones[0])?.markAsTouched(); // форма строки массива
47
+ * ```
48
+ *
49
+ * @see {@link registerSignalNode} - регистрация связи ручка→нода
44
50
  * @group Utilities
45
51
  */
46
- export declare function getNodeForSignal(signal: Signal<any>): FormNode<any> | undefined;
52
+ export declare function getNodeForSignal(signal: object): FormNode<any> | undefined;
@@ -6,7 +6,7 @@ import { ValidatorFn, AsyncValidatorFn, AnyFunction } from './index';
6
6
  * @group Types
7
7
  * @category Configuration Types
8
8
  * @remarks
9
- * В схеме `createForm({ model, schema })` лист связывают через **`value: model.$.field`**: harvest
9
+ * В схеме `createFormFromModel({ model, schema })` лист связывают через **`value: model.$.field`**: harvest
10
10
  * ловит узел по идентичности `Signal` (`node.value instanceof Signal` — см. `create-form.ts`) и
11
11
  * нормализует его в `valueSignal`. Писать `valueSignal:` **в самой схеме `createForm` нельзя** —
12
12
  * harvest его не подхватит (проверка идёт по `node.value`), и `component`/`componentProps` этого
@@ -68,7 +68,7 @@ type NonEmptyFieldConfig<T> = RequireAtLeastOne<FieldConfig<T>>;
68
68
  * Использует NonNullable для корректной обработки опциональных полей.
69
69
  *
70
70
  * ⚠️ Не путать с {@link FormSchemaNode} — тот описывает **узел дерева** M1-схемы (лист/массив/
71
- * контейнер), передаваемой в `createForm({ model, schema })`. `FormSchema` — это data-shaped конфиг
71
+ * контейнер), передаваемой в `createFormFromModel({ model, schema })`. `FormSchema` — это data-shaped конфиг
72
72
  * (ключи = поля данных), а не узел дерева.
73
73
  *
74
74
  * @group Types
@@ -53,7 +53,7 @@ export type FormControlsProxy<T> = {
53
53
  * };
54
54
  * }
55
55
  *
56
- * const form = createForm<UserForm>(schema);
56
+ * const form = createLegacyForm<UserForm>(schema);
57
57
  *
58
58
  * // Доступ к методам GroupNode
59
59
  * await form.validate();
@@ -13,16 +13,17 @@ export interface SchemaArrayControl {
13
13
  /** Реактивная длина. */
14
14
  readonly length: number;
15
15
  at(index: number): unknown;
16
- push(item: unknown): void;
16
+ push(item?: unknown): void;
17
17
  removeAt(index: number): void;
18
18
  }
19
19
  /**
20
- * Узел единой схемы M1 — layout-дерево, обходимое `createForm({ model, schema })`
20
+ * Узел единой схемы M1 — layout-дерево, обходимое `createFormFromModel({ model, schema })`
21
21
  * и рендерерами (schema-валидация живёт отдельно — `@reformer/core/validation`).
22
22
  *
23
23
  * Узел совмещает несколько ролей (различаются рантаймом по форме):
24
- * - **поле** — несёт `value: Signal` (сигнал модели `model.$.x`) + `component`/`validators`;
25
- * - **массив** — `{ array: model.<path>, item(itemModel) }`;
24
+ * - **поле** — `{ model: model.$.x, component, componentProps }`;
25
+ * - **массив под-форм** — `{ model: model.$.items, item: (model) => узел }`;
26
+ * - **подформа** — `{ model: model.$.group, part: (model) => узел }`;
26
27
  * - **контейнер/ветка** — вложенные узлы (`children`), опц. условие `when`;
27
28
  * - **record-of-fields** — под-узлы под произвольными именованными ключами (индексная сигнатура).
28
29
  *
@@ -33,9 +34,20 @@ export interface SchemaArrayControl {
33
34
  */
34
35
  export interface FormSchemaNode {
35
36
  /**
36
- * «Ручка» значения поля — маркер узла-поля. Обычно сигнал модели (`model.$.<path>`), но форма
37
- * зависит от таргета (для массива `model.$.x` — дерево сигналов; в renderer-типах сужается до
38
- * `Signal`). Движок разбирает узел как поле рантаймом по `value instanceof Signal`.
37
+ * Привязка узла к части модели — ручка из дерева `model.$`. Чем узел является, решают ручка и
38
+ * соседние ключи:
39
+ * - лист или массив (`model.$.email`, `model.$.tags`) — **поле**;
40
+ * - массив вместе с {@link FormSchemaNode.item} — **массив под-форм**;
41
+ * - группа вместе с {@link FormSchemaNode.part} — **подформа**.
42
+ *
43
+ * Узел распознаётся по значению, а не по имени ключа: в записи «имя поля → узел» поле данных
44
+ * может называться `model`, и тогда под этим ключом лежит обычный вложенный узел.
45
+ */
46
+ model?: unknown;
47
+ /**
48
+ * Прежняя запись привязки поля — то же, что {@link FormSchemaNode.model}.
49
+ *
50
+ * @deprecated Пишите `model: model.$.<path>`.
39
51
  */
40
52
  value?: unknown;
41
53
  /**
@@ -65,14 +77,27 @@ export interface FormSchemaNode {
65
77
  * выводит на своём месте в порядке следования.
66
78
  */
67
79
  children?: readonly (FormSchemaNode | string | number | Signal<any>)[];
68
- /** Реактивный массив модели (`model.<path>`) — маркер узла-массива (вместе с `item`). */
80
+ /**
81
+ * Прежняя запись привязки массива под-форм — фасад `model.<path>` (вместе с `item`).
82
+ *
83
+ * @deprecated Пишите `model: model.$.<path>`.
84
+ */
69
85
  array?: SchemaArrayControl;
70
86
  /** Схема элемента массива: под-модель элемента → узел поддерева. */
71
- item?: (itemModel: any) => FormSchemaNode;
87
+ item?: (model: any) => FormSchemaNode;
88
+ /**
89
+ * Подформа: под-модель группы → узел поддерева. Часть объявляется один раз и подключается к
90
+ * любой группе той же формы данных: `{ model: model.$.registrationAddress, part: address }`.
91
+ * Привязки внутри части идут через `$` полученной под-модели.
92
+ */
93
+ part?: (model: any) => FormSchemaNode;
72
94
  /**
73
- * Значение нового элемента массива для кнопки «Добавить»: либо готовое значение,
74
- * либо фабрика `() => value`. Тип не различает варианты (union `unknown | (() => unknown)`
75
- * схлопывается в `unknown`) — рантайм различает по `typeof initialValue === 'function'`.
95
+ * Шаблон нового элемента массива для кнопки «Добавить»: либо готовое значение, либо фабрика
96
+ * `() => value`. Запасной путь — для форм, чья модель создаётся из данных без кода: шаблон,
97
+ * объявленный в модели (`arrayOf(blank)`), главнее.
98
+ *
99
+ * Тип не различает варианты (union `unknown | (() => unknown)` схлопывается в `unknown`) —
100
+ * рантайм различает по `typeof initialValue === 'function'`.
76
101
  */
77
102
  initialValue?: unknown;
78
103
  /** Свободная вложенность: record-of-fields и произвольные под-узлы. */
@@ -1,8 +1,13 @@
1
1
  import { FormModel, PathAwareSignal, ValidationError } from '../../index';
2
2
  /** Сток одного прогона: модель scope, накопленные ошибки, незавершённые async-правила, гейты. */
3
3
  export interface VContext {
4
- /** Модель текущего scope — источник снапшота для `cross` и модель для `apply`. */
4
+ /**
5
+ * Модель текущей области — источник снапшота для `cross` и модель для `apply`. В корне прогона
6
+ * это вся модель; внутри `apply(ручка, схема)` / `applyEach` — под-модель группы или строки.
7
+ */
5
8
  model: FormModel<unknown>;
9
+ /** Корень прогона — модель, с которой вызван раннер. Правило получает его третьим аргументом. */
10
+ root: FormModel<unknown>;
6
11
  /** Накопленные ошибки по сигналу (ключ — идентичность `PathAwareSignal`). */
7
12
  errors: Map<PathAwareSignal<unknown>, ValidationError[]>;
8
13
  /** Незавершённые async-правила (раннер их дожидается). */
@@ -18,6 +18,6 @@
18
18
  * @module form/validation
19
19
  */
20
20
  export type { Rule, AsyncRule, ValidationSchema } from './types';
21
- export { validate, validateAsync, validateWhen, cross, each, apply } from './operators';
21
+ export { validate, validateAsync, validateWhen, cross, each, apply, applyEach } from './operators';
22
22
  export { defineValidationSchema, validateModel } from './run';
23
23
  export * from './strategy';
@@ -32,16 +32,78 @@ export declare function validateAsync<TField>(sig: PathAwareSignal<TField>, rule
32
32
  */
33
33
  export declare function validateWhen(cond: () => boolean, cb: () => void): void;
34
34
  /**
35
- * Cross-field правило: `fn` получает СНАПШОТ модели текущего scope (`model.get()`) и вешает ошибку на `sig`.
36
- * Для элементов массива / под-моделей захватывайте нужный снапшот в замыкание (`const item = im.get()`),
37
- * т.к. `fn` всегда получает модель ТЕКУЩЕГО scope (корень прогона), а не под-модель.
35
+ * Cross-field правило: `fn` получает СНАПШОТ модели текущей области (`model.get()`) и вешает
36
+ * ошибку на `sig`.
37
+ *
38
+ * Область — та модель, с которой запущена схема: в корне прогона это вся форма, а в схеме,
39
+ * подключённой через {@link apply} / {@link applyEach}, — под-модель группы или строки массива.
40
+ * Поэтому правила подформы пишутся один раз и не знают, где она стоит.
38
41
  */
39
42
  export declare function cross<TSnapshot>(sig: PathAwareSignal<unknown>, fn: (form: TSnapshot) => ValidationError | null): void;
43
+ /** Привязка к группе: ручка `model.$.<группа>` (тип значения — из её `peek()`). */
44
+ interface GroupBinding<V> {
45
+ peek(): V;
46
+ }
47
+ /** Привязка к массиву под-форм: ручка `model.$.<массив>`. */
48
+ interface ArrayBinding<U> {
49
+ peek(): readonly U[] | null | undefined;
50
+ }
51
+ /**
52
+ * Применить схему к КАЖДОМУ элементу массива под-форм. Схема элемента — обычная
53
+ * `ValidationSchema` над типом элемента: тот же набор правил подключается и к группе через
54
+ * {@link apply}.
55
+ *
56
+ * У каждого элемента своя область: `cross` внутри схемы получает снапшот элемента.
57
+ *
58
+ * @example
59
+ * ```ts
60
+ * const propertyRules = defineValidationSchema<Property>(({ model }) => {
61
+ * validate(model.$.type, [required()]);
62
+ * cross<Property>(model.$.estimatedValue, (property) =>
63
+ * property.hasEncumbrance && property.estimatedValue < 100_000 ? tooCheap : null
64
+ * );
65
+ * });
66
+ *
67
+ * applyEach(model.$.properties, propertyRules);
68
+ * ```
69
+ */
70
+ export declare function applyEach<U extends object>(array: ArrayBinding<U> | ModelArray<U>, schema: ValidationSchema<U>): void;
71
+ /**
72
+ * Применить под-правила к КАЖДОМУ элементу массива — прежняя запись {@link applyEach}.
73
+ *
74
+ * Колбэк получает под-модель элемента, но области не создаёт: `cross` внутри него видит снапшот
75
+ * корня прогона.
76
+ *
77
+ * @deprecated Пишите `applyEach(model.$.<массив>, schema)`.
78
+ */
79
+ export declare function each<U extends object>(arr: ArrayBinding<U> | ModelArray<U>, itemFn: (item: FormModel<U>) => void): void;
40
80
  /**
41
- * Применить под-правила к КАЖДОМУ элементу текущего массива модели.
42
- * `U extends object` — элементы должны быть под-моделями (объектами); массив примитивов — одно
43
- * значение поля, его проверяют целиком: `validate(model.$.<массив>, [...])`.
81
+ * Подключить схему подформы к группе модели — одной или нескольким.
82
+ *
83
+ * Схема запускается в области группы: `model` в ней — под-модель, `cross` получает снапшот
84
+ * группы. Привязка — ручка `model.$.<группа>`, как у узла-подформы схемы и у `apply` поведения.
85
+ *
86
+ * @example
87
+ * ```ts
88
+ * const addressRules = defineValidationSchema<Address>(({ model }) => {
89
+ * validate(model.$.city, [required()]);
90
+ * });
91
+ *
92
+ * apply(model.$.registrationAddress, addressRules);
93
+ * validateWhen(
94
+ * () => !model.sameAsRegistration,
95
+ * () => apply(model.$.residenceAddress, addressRules)
96
+ * );
97
+ * ```
98
+ */
99
+ export declare function apply<V extends object | null | undefined>(group: GroupBinding<V> | GroupBinding<V>[], schema: ValidationSchema<NonNullable<V>>): void;
100
+ /**
101
+ * Композиция схем над ТОЙ ЖЕ моделью области — так форма собирается из схем шагов.
102
+ *
103
+ * @example
104
+ * ```ts
105
+ * const formRules = defineValidationSchema<Form>(() => apply(loanRules, contactsRules));
106
+ * ```
44
107
  */
45
- export declare function each<U extends object>(arr: ModelArray<U>, itemFn: (item: FormModel<U>) => void): void;
46
- /** Композиция под-схем в текущую (над той же моделью scope). Заменяет пошаговую группировку. */
47
108
  export declare function apply<T>(...schemas: ValidationSchema<T>[]): void;
109
+ export {};
@@ -12,7 +12,8 @@ export type CallableRule = (value: unknown, model: unknown, root: unknown) => Va
12
12
  * (`required()`/`min()`/… — они `(value, model, root) => …`) и inline-правила `(value) => …`
13
13
  * ОБА присваиваются в `Rule<TField>[]` без `any`, при этом сохраняется проверка типа поля
14
14
  * (`validate(model.$.age, [email()])` подсветится — `email` ждёт `string`, поле `number`).
15
- * На вызове раннер приводит правило к callable и передаёт `(value, model, model)`.
15
+ * На вызове раннер приводит правило к callable и передаёт `(value, scope, root)`: модель текущей
16
+ * области (в корне прогона — вся модель) и корень прогона.
16
17
  */
17
18
  export type Rule<TField> = (value: TField, scope: never, root: never) => ValidationError | null;
18
19
  /**