@reformer/core 8.0.0 → 9.0.0-beta.2

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 (52) hide show
  1. package/README.md +52 -57
  2. package/dist/behaviors-Br4Im38V.js +263 -0
  3. package/dist/behaviors.js +146 -146
  4. package/dist/core/factories/node-factory.d.ts +5 -4
  5. package/dist/core/model/behaviors.d.ts +15 -0
  6. package/dist/core/model/types.d.ts +41 -12
  7. package/dist/core/model/validate-model.d.ts +8 -4
  8. package/dist/core/nodes/field-node.d.ts +2 -2
  9. package/dist/core/nodes/group-node.d.ts +39 -3
  10. package/dist/core/nodes/model-array-node.d.ts +13 -1
  11. package/dist/core/types/deep-schema.d.ts +39 -5
  12. package/dist/core/types/form-proxy.d.ts +27 -2
  13. package/dist/core/types/index.d.ts +4 -10
  14. package/dist/core/types/schema-node.d.ts +79 -0
  15. package/dist/core/types/validation-schema.d.ts +37 -16
  16. package/dist/core/utils/create-form.d.ts +4 -3
  17. package/dist/core/utils/derived-registry.d.ts +29 -0
  18. package/dist/core/utils/error-handler.d.ts +4 -1
  19. package/dist/core/utils/form-submitter.d.ts +8 -4
  20. package/dist/core/utils/index.d.ts +1 -1
  21. package/dist/core/utils/safe-effect.d.ts +27 -7
  22. package/dist/core/utils/type-guards.d.ts +3 -3
  23. package/dist/core/validation/validators/date-utils.d.ts +5 -1
  24. package/dist/core/validation/validators/integer.d.ts +1 -1
  25. package/dist/core/validation/validators/is-number.d.ts +1 -1
  26. package/dist/core/validation/validators/max.d.ts +1 -1
  27. package/dist/core/validation/validators/min.d.ts +1 -1
  28. package/dist/core/validation/validators/multiple-of.d.ts +1 -1
  29. package/dist/core/validation/validators/non-negative.d.ts +1 -1
  30. package/dist/core/validation/validators/non-zero.d.ts +1 -1
  31. package/dist/core/validation/validators/required.d.ts +2 -1
  32. package/dist/{date-utils-xUWFslTj.js → date-utils-dTT_x_Be.js} +12 -10
  33. package/dist/hooks/types.d.ts +31 -1
  34. package/dist/hooks/useArrayLength.d.ts +1 -2
  35. package/dist/hooks/useFormControl.d.ts +2 -2
  36. package/dist/hooks/useSignalSubscription.d.ts +8 -1
  37. package/dist/index.js +625 -617
  38. package/dist/signals.d.ts +13 -0
  39. package/dist/signals.js +9 -0
  40. package/dist/validators/future-date.js +1 -1
  41. package/dist/validators/is-date.js +1 -1
  42. package/dist/validators/max-age.js +1 -1
  43. package/dist/validators/max-date.js +1 -1
  44. package/dist/validators/min-age.js +1 -1
  45. package/dist/validators/min-date.js +1 -1
  46. package/dist/validators/multiple-of.js +11 -7
  47. package/dist/validators/past-date.js +1 -1
  48. package/dist/validators/pattern.js +7 -6
  49. package/dist/validators/required.js +5 -9
  50. package/llms.txt +715 -175
  51. package/package.json +6 -2
  52. package/dist/behaviors-O9a1Djj9.js +0 -147
package/dist/behaviors.js CHANGED
@@ -1,27 +1,27 @@
1
- import { effect as j } from "@preact/signals-core";
2
- import { r as $, m as x, c as O, w as F, e as S, t as A, a as C, s as M, b as D } from "./behaviors-O9a1Djj9.js";
1
+ import { effect as O } from "@preact/signals-core";
2
+ import { r as $, m as k, u as x, c as S, w as F, e as A, t as C, a as D, s as M, b as G } from "./behaviors-Br4Im38V.js";
3
3
  let p = null;
4
- function k(e) {
4
+ function P(e) {
5
5
  if (!p)
6
6
  throw new Error(
7
7
  `[@reformer/core/behaviors] "${e}" вызван вне defineFormBehavior(...) — операторы поведения можно вызывать только внутри схемы.`
8
8
  );
9
9
  return p;
10
10
  }
11
- function a(e) {
12
- k("onDispose").cleanups.push(e);
11
+ function s(e) {
12
+ P("onDispose").cleanups.push(e);
13
13
  }
14
14
  function y() {
15
- const e = k("getScope");
15
+ const e = P("getScope");
16
16
  return { model: e.model, form: e.form };
17
17
  }
18
- function G(e) {
18
+ function T(e) {
19
19
  return {
20
- __run(n, t) {
21
- const o = { cleanups: [], model: n, form: t }, r = p;
20
+ __run(t, n) {
21
+ const o = { cleanups: [], model: t, form: n }, r = p;
22
22
  p = o;
23
23
  try {
24
- e({ model: n, form: t });
24
+ e({ model: t, form: n });
25
25
  } finally {
26
26
  p = r;
27
27
  }
@@ -32,211 +32,211 @@ function G(e) {
32
32
  };
33
33
  }
34
34
  function m(e) {
35
- a(j(e));
35
+ s(O(e));
36
36
  }
37
- function g(e) {
37
+ function _(e) {
38
38
  $(e);
39
39
  }
40
- const h = (e) => typeof e == "object" && e !== null && typeof e.peek == "function", P = (e) => Array.isArray(e) ? e : [e];
41
- function B(e) {
42
- const n = {};
43
- for (const t of Object.keys(e)) {
44
- const o = e[t];
45
- n[t] = h(o) ? o.value : B(o);
40
+ const h = (e) => typeof e == "object" && e !== null && typeof e.peek == "function", B = (e) => Array.isArray(e) ? e : [e];
41
+ function E(e) {
42
+ const t = {};
43
+ for (const n of Object.keys(e)) {
44
+ const o = e[n];
45
+ t[n] = h(o) ? o.value : E(o);
46
46
  }
47
- return n;
47
+ return t;
48
48
  }
49
- function E(e, n) {
50
- for (const t of Object.keys(e)) {
51
- const o = e[t];
52
- h(o) ? o.value = n?.[t] : E(o, n?.[t] ?? {});
49
+ function W(e, t) {
50
+ for (const n of Object.keys(e)) {
51
+ const o = e[n];
52
+ h(o) ? o.value = t?.[n] : W(o, t?.[n] ?? {});
53
53
  }
54
54
  }
55
- function T(e) {
55
+ function I(e) {
56
56
  if (!e) return;
57
- const { form: n } = y();
58
- return n.getFieldByPath(e);
57
+ const { form: t } = y();
58
+ return t.getFieldByPath(e);
59
59
  }
60
- function W(e) {
60
+ function V(e) {
61
61
  return new Proxy(
62
62
  {},
63
63
  {
64
- get: (n, t) => {
65
- if (t === "$") return e;
66
- if (typeof t != "string") return;
67
- const o = e[t];
64
+ get: (t, n) => {
65
+ if (n === "$") return e;
66
+ if (typeof n != "string") return;
67
+ const o = e[n];
68
68
  if (o != null)
69
- return h(o) ? o.value : W(o);
69
+ return h(o) ? o.value : V(o);
70
70
  }
71
71
  }
72
72
  );
73
73
  }
74
- function V(e) {
75
- return (n) => {
74
+ function j(e) {
75
+ return (t) => {
76
76
  try {
77
- n();
78
- } catch (t) {
79
- if (t instanceof Error && /cycle detected/i.test(t.message)) {
77
+ t();
78
+ } catch (n) {
79
+ if (n instanceof Error && /cycle detected/i.test(n.message)) {
80
80
  const o = e.__path ?? "?";
81
81
  throw new Error(
82
82
  `[@reformer/core/behaviors] compute("${o}"): расходящийся цикл пересчёта — взаимные compute/computeFrom без стабилизации. Проверьте зависимости или добавьте стабилизирующее условие (when) / разорвите цикл через peek.`
83
83
  );
84
84
  }
85
- throw t;
85
+ throw n;
86
86
  }
87
87
  };
88
88
  }
89
- function K(e, n, t) {
90
- x(e);
91
- const o = V(e);
89
+ function L(e, t, n) {
90
+ k(e), s(() => x(e));
91
+ const o = j(e);
92
92
  m(() => {
93
- if (t?.when && !t.when()) return;
94
- const r = n();
93
+ if (n?.when && !n.when()) return;
94
+ const r = t();
95
95
  e.peek() !== r && o(() => {
96
96
  e.value = r;
97
97
  });
98
98
  });
99
99
  }
100
- function L(e, n, t, o) {
101
- x(n);
102
- const r = V(n);
100
+ function Q(e, t, n, o) {
101
+ k(t), s(() => x(t));
102
+ const r = j(t);
103
103
  m(() => {
104
104
  const c = e.map((f) => f.value);
105
105
  if (o?.when && !o.when(...c)) return;
106
- const i = t(...c);
107
- n.peek() !== i && r(() => {
108
- n.value = i;
106
+ const i = n(...c);
107
+ t.peek() !== i && r(() => {
108
+ t.value = i;
109
109
  });
110
110
  });
111
111
  }
112
- function Q(e, n, t) {
113
- if (h(e) && h(n)) {
114
- a(O(e, n, t));
112
+ function U(e, t, n) {
113
+ if (h(e) && h(t)) {
114
+ s(S(e, t, n));
115
115
  return;
116
116
  }
117
- const o = e, r = n;
117
+ const o = e, r = t;
118
118
  m(() => {
119
- const c = B(o);
120
- t?.when && !t.when() || g(() => E(r, c));
119
+ const c = E(o);
120
+ n?.when && !n.when() || _(() => W(r, c));
121
121
  });
122
122
  }
123
- function I(e, n, t) {
123
+ function R(e, t, n) {
124
124
  let o;
125
125
  const r = (c) => {
126
126
  o?.abort(), o = new AbortController();
127
127
  const { signal: i } = o;
128
- $(() => n(c, { signal: i }));
128
+ $(() => t(c, { signal: i }));
129
129
  };
130
- if (!t?.debounce)
131
- a(F(e, r, { immediate: t?.immediate }));
130
+ if (!n?.debounce)
131
+ s(F(e, r, { immediate: n?.immediate }));
132
132
  else {
133
133
  let c;
134
134
  const i = F(
135
135
  e,
136
136
  (f) => {
137
- c && clearTimeout(c), c = setTimeout(() => r(f), t.debounce);
137
+ c && clearTimeout(c), c = setTimeout(() => r(f), n.debounce);
138
138
  },
139
- { immediate: t?.immediate }
139
+ { immediate: n?.immediate }
140
140
  );
141
- a(() => {
141
+ s(() => {
142
142
  c && clearTimeout(c), i();
143
143
  });
144
144
  }
145
- a(() => o?.abort());
145
+ s(() => o?.abort());
146
146
  }
147
- function R(e, n, t) {
148
- for (const o of P(e))
149
- h(o) ? a(S(o, n, t)) : q(o, n, t);
147
+ function q(e, t, n) {
148
+ for (const o of B(e))
149
+ h(o) ? s(A(o, t, n)) : z(o, t, n);
150
150
  }
151
- function U(e, n, t) {
152
- R(e, () => !n(), t);
151
+ function X(e, t, n) {
152
+ q(e, () => !t(), n);
153
153
  }
154
- function q(e, n, t) {
155
- const o = T(e.__path);
154
+ function z(e, t, n) {
155
+ const o = I(e.__path);
156
156
  o && m(() => {
157
- const r = n();
158
- g(() => {
159
- r ? o.enable() : (o.disable(), t?.resetOnDisable && o.reset());
157
+ const r = t();
158
+ _(() => {
159
+ r ? o.enable() : (o.disable(), n?.resetOnDisable && o.reset());
160
160
  });
161
161
  });
162
162
  }
163
- function X(e, n) {
164
- a(A(e, n));
163
+ function Y(e, t) {
164
+ s(C(e, t));
165
165
  }
166
- function Y(e, n, t) {
167
- a(C(e, n, t));
166
+ function Z(e, t, n) {
167
+ s(D(e, t, n));
168
168
  }
169
- function Z(e, n, t) {
170
- a(M(e, n, t));
169
+ function ee(e, t, n) {
170
+ s(M(e, t, n));
171
171
  }
172
- function ee(e, n) {
173
- a(D(e, n));
172
+ function ne(e, t) {
173
+ s(G(e, t));
174
174
  }
175
- function _(e, n) {
176
- return n.split(".").reduce((t, o) => t?.[o], e);
175
+ function g(e, t) {
176
+ return t.split(".").reduce((n, o) => n?.[o], e);
177
177
  }
178
- function z(e) {
178
+ function N(e) {
179
179
  return new Proxy(
180
180
  {},
181
181
  {
182
- get(n, t) {
183
- if (!(typeof t == "symbol" || t === "then"))
182
+ get(t, n) {
183
+ if (!(typeof n == "symbol" || n === "then"))
184
184
  throw new Error(
185
185
  `[@reformer/core/behaviors] applyEach: форма строки массива "${e}" недоступна (form.${String(
186
- t
186
+ n
187
187
  )}) — массив не материализован в схеме формы. Per-row node-операции (enableWhen/updateComponentProps/reset) требуют узла { array, item } в схеме; value-операции (row.$.*: compute/copyFrom/transformValue) работают и без материализации.`
188
188
  );
189
189
  }
190
190
  }
191
191
  );
192
192
  }
193
- function N(e, n) {
194
- const { model: t, form: o } = y(), r = e.__path;
193
+ function H(e, t) {
194
+ const { model: n, form: o } = y(), r = e.__path;
195
195
  if (!r) return;
196
- const c = _(t, r);
196
+ const c = g(n, r);
197
197
  if (!c) return;
198
198
  const i = o.getFieldByPath(
199
199
  r
200
- ), f = i?.length, s = /* @__PURE__ */ new Map();
200
+ ), f = i?.length, u = /* @__PURE__ */ new Map();
201
201
  m(() => {
202
202
  const d = f ? f.value : c.length, w = /* @__PURE__ */ new Set();
203
- for (let u = 0; u < d; u++) {
204
- const l = c.at(u);
205
- if (l != null && (w.add(l), !s.has(l))) {
206
- const b = i?.at?.(u) ?? z(r);
207
- s.set(l, n.__run(l, b));
203
+ for (let a = 0; a < d; a++) {
204
+ const l = c.at(a);
205
+ if (l != null && (w.add(l), !u.has(l))) {
206
+ const b = i?.at?.(a) ?? N(r);
207
+ u.set(l, t.__run(l, b));
208
208
  }
209
209
  }
210
- for (const [u, l] of s)
211
- w.has(u) || (l(), s.delete(u));
212
- }), a(() => {
213
- for (const d of s.values()) d();
214
- s.clear();
210
+ for (const [a, l] of u)
211
+ w.has(a) || (l(), u.delete(a));
212
+ }), s(() => {
213
+ for (const d of u.values()) d();
214
+ u.clear();
215
215
  });
216
216
  }
217
217
  function v(e) {
218
218
  if (e == null || typeof e != "object") return;
219
- const n = e;
220
- if (typeof n.at == "function" && typeof n.length == "number") {
221
- const t = n.length;
222
- for (let o = 0; o < t; o++) v(n.at(o));
219
+ const t = e;
220
+ if (typeof t.at == "function" && typeof t.length == "number") {
221
+ const n = t.length;
222
+ for (let o = 0; o < n; o++) v(t.at(o));
223
223
  return;
224
224
  }
225
- for (const t of Object.keys(e)) v(e[t]);
225
+ for (const n of Object.keys(e)) v(e[n]);
226
226
  }
227
- function te(e, n) {
228
- const { model: t } = y(), o = e.__path;
227
+ function te(e, t) {
228
+ const { model: n } = y(), o = e.__path;
229
229
  if (!o) return;
230
- const r = _(t, o);
231
- r && N(
230
+ const r = g(n, o);
231
+ r && H(
232
232
  e,
233
- G(({ model: c }) => {
234
- I(n(c), (i) => {
233
+ T(({ model: c }) => {
234
+ R(t(c), (i) => {
235
235
  if (i)
236
236
  for (let f = 0; f < r.length; f++) {
237
- const s = r.at(f);
238
- if (s && s !== c) {
239
- const d = n(s);
237
+ const u = r.at(f);
238
+ if (u && u !== c) {
239
+ const d = t(u);
240
240
  d.peek() && (d.value = !1);
241
241
  }
242
242
  }
@@ -244,57 +244,57 @@ function te(e, n) {
244
244
  })
245
245
  );
246
246
  }
247
- function ne(e, n) {
248
- const { model: t } = y(), o = e.__path;
247
+ function oe(e, t) {
248
+ const { model: n } = y(), o = e.__path;
249
249
  if (!o) return;
250
- const r = _(t, o);
250
+ const r = g(n, o);
251
251
  if (!r) return;
252
252
  let c = !1, i = 0;
253
253
  m(() => {
254
- v(r), !c && (c = !0, g(() => {
254
+ v(r), !c && (c = !0, _(() => {
255
255
  c = !1;
256
- const f = n(r.toArray());
257
- let s = !1;
256
+ const f = t(r.toArray());
257
+ let u = !1;
258
258
  for (const { index: d, patch: w } of f) {
259
- const u = r.at(d);
260
- if (u)
259
+ const a = r.at(d);
260
+ if (a)
261
261
  for (const [l, b] of Object.entries(w))
262
- u[l] !== b && (u[l] = b, s = !0);
262
+ a[l] !== b && (a[l] = b, u = !0);
263
263
  }
264
- if (i = s ? i + 1 : 0, i > 50)
264
+ if (i = u ? i + 1 : 0, i > 50)
265
265
  throw i = 0, new Error(
266
266
  `[@reformer/core/behaviors] aggregateInto("${o}"): запись не сходится (>50 проходов) — derive должна быть идемпотентной на фикспоинте.`
267
267
  );
268
268
  }));
269
269
  });
270
270
  }
271
- function oe(e, n) {
272
- const { form: t } = y();
273
- for (const o of P(e)) {
271
+ function re(e, t) {
272
+ const { form: n } = y();
273
+ for (const o of B(e)) {
274
274
  const r = o, c = r.__path;
275
275
  if (!c) continue;
276
- const i = t ? t.getFieldByPath(c) : void 0, f = i ? i.getProxy?.() ?? i : void 0;
277
- a(n.__run(W(r), f));
276
+ const i = n ? n.getFieldByPath(c) : void 0, f = i ? i.getProxy?.() ?? i : void 0;
277
+ s(t.__run(V(r), f));
278
278
  }
279
279
  }
280
280
  export {
281
- ne as aggregateInto,
282
- oe as apply,
283
- N as applyEach,
284
- K as compute,
285
- L as computeFrom,
286
- Q as copyFrom,
287
- g as defer,
288
- G as defineFormBehavior,
289
- U as disableWhen,
281
+ oe as aggregateInto,
282
+ re as apply,
283
+ H as applyEach,
284
+ L as compute,
285
+ Q as computeFrom,
286
+ U as copyFrom,
287
+ _ as defer,
288
+ T as defineFormBehavior,
289
+ X as disableWhen,
290
290
  m as effect,
291
- R as enableWhen,
291
+ q as enableWhen,
292
292
  te as exclusiveFlag,
293
293
  y as getScope,
294
- I as onChange,
295
- a as onDispose,
296
- Y as resetWhen,
297
- ee as revalidateWhen,
298
- Z as syncFields,
299
- X as transformValue
294
+ R as onChange,
295
+ s as onDispose,
296
+ Z as resetWhen,
297
+ ne as revalidateWhen,
298
+ ee as syncFields,
299
+ Y as transformValue
300
300
  };
@@ -1,4 +1,5 @@
1
1
  import { FormNode } from '../nodes/form-node';
2
+ import { ConfigWithSchema, ConfigWithValue, UnknownRecord } from '../types';
2
3
  /**
3
4
  * Фабрика для создания узлов формы.
4
5
  *
@@ -59,7 +60,7 @@ export declare class NodeFactory {
59
60
  * ]);
60
61
  * ```
61
62
  */
62
- createNode<T>(config: unknown): FormNode<T>;
63
+ createNode(config: unknown): FormNode<unknown>;
63
64
  /**
64
65
  * Создать ArrayNode из массива [schema, ...initialItems]
65
66
  *
@@ -135,7 +136,7 @@ export declare class NodeFactory {
135
136
  * factory.isFieldConfig(null); // false
136
137
  * ```
137
138
  */
138
- isFieldConfig(config: unknown): boolean;
139
+ isFieldConfig(config: unknown): config is ConfigWithValue;
139
140
  /**
140
141
  * Проверяет, является ли конфиг конфигурацией массива (ArrayConfig)
141
142
  *
@@ -157,7 +158,7 @@ export declare class NodeFactory {
157
158
  * factory.isArrayConfig({ email: { value: '' } }); // false
158
159
  * ```
159
160
  */
160
- isArrayConfig(config: unknown): boolean;
161
+ isArrayConfig(config: unknown): config is ConfigWithSchema;
161
162
  /**
162
163
  * Проверяет, является ли конфиг конфигурацией группы (GroupConfig)
163
164
  *
@@ -183,5 +184,5 @@ export declare class NodeFactory {
183
184
  * factory.isGroupConfig(null); // false
184
185
  * ```
185
186
  */
186
- isGroupConfig(config: unknown): boolean;
187
+ isGroupConfig(config: unknown): config is UnknownRecord;
187
188
  }
@@ -57,10 +57,25 @@ export declare function watchField<T>(source: ReadonlySignal<T>, cb: (value: T)
57
57
  * ⚠️ Поле должно быть материализовано в форме (`createForm`) — иначе ноды в реестре нет (например,
58
58
  * элемент массива, который строится per-item).
59
59
  *
60
+ * ⚠️ Если то же поле одновременно является целью `compute`/`computeFrom`, обязательно ограничь
61
+ * compute тем же условием (`when`), что и это `enableWhen`. Механизмы не согласованы: `enableWhen`
62
+ * работает со статус-машиной ноды (enable/disable/reset), а `compute` пишет `target.value` напрямую
63
+ * в сигнал модели и проверяет только собственный `options.when` — состояние `disabled` он НЕ смотрит.
64
+ * Поэтому при выключенном поле любое изменение зависимости compute молча заново заполнит его (а с
65
+ * `resetOnDisable` — перезапишет только что сброшенное значение), и так как `getValue()`/`submit`
66
+ * включают disabled-поля, мусор утечёт в payload. Это аналог правила #13 гайда add-behavior,
67
+ * которое сегодня сформулировано только для `copyFrom`; для `compute` то же ограничение обязательно.
68
+ *
60
69
  * @group Model
61
70
  * @example
62
71
  * ```typescript
63
72
  * enableWhen(model.$.propertyValue, () => model.loanType === 'mortgage', { resetOnDisable: true });
73
+ *
74
+ * // Поле-цель compute, которое ТАКЖЕ в resetOnDisable enableWhen: compute обязан нести тот же when,
75
+ * // иначе initialPayment заново заполнится в выключенном состоянии и утечёт в submit.
76
+ * compute(model.$.initialPayment, () => model.propertyValue * 0.2, {
77
+ * when: () => model.loanType === 'mortgage',
78
+ * });
64
79
  * ```
65
80
  */
66
81
  export declare function enableWhen(target: ReadonlySignal<unknown>, condition: () => boolean, options?: {
@@ -14,6 +14,14 @@ export type PathAwareSignal<T> = Signal<T> & {
14
14
  * @internal
15
15
  */
16
16
  type Opaque = Date | File | Blob;
17
+ /**
18
+ * Фасад элемента массива при обходе (`at`/`map`/`forEach`): массив → {@link ModelArray},
19
+ * Opaque (Date/File/Blob) и примитив → значение, объект → под-модель {@link FormModel}.
20
+ * Зеркалит листовой/объектный сплит {@link ModelValue} (но объект → под-модель с API),
21
+ * чтобы `files: File[]`/`dates: Date[]`/вложенные массивы не мис-типизировались как `FormModel`.
22
+ * @internal
23
+ */
24
+ type ModelArrayItem<U> = NonNullable<U> extends ReadonlyArray<infer E> ? ModelArray<E> : NonNullable<U> extends Opaque ? U : NonNullable<U> extends object ? FormModel<NonNullable<U>> : U;
17
25
  /**
18
26
  * Значение поля в value-доступе модели:
19
27
  * - массив → {@link ModelArray}
@@ -40,6 +48,11 @@ export type ModelObject<T> = {
40
48
  * @group Model
41
49
  */
42
50
  export interface ModelArray<U> {
51
+ /**
52
+ * Путь массива в модели (dot-нотация). Предоставляется рантаймом (value-прокси) и требуется
53
+ * рендер-слою для резолва узла массива (напр. `ArrayRenderNode` в `@reformer/renderer-react`).
54
+ */
55
+ readonly __path: string;
43
56
  /** Реактивная длина. */
44
57
  readonly length: number;
45
58
  /** Добавить элемент в конец (значение элемента целиком). */
@@ -54,12 +67,12 @@ export interface ModelArray<U> {
54
67
  swap(a: number, b: number): void;
55
68
  /** Очистить массив. */
56
69
  clear(): void;
57
- /** Под-модель элемента по индексу (для объектных элементов) или значение (для примитивных). */
58
- at(index: number): U extends object ? FormModel<U> : U | undefined;
59
- /** Map по элементам: объектные → {@link FormModel}, примитивныезначение. */
60
- map<R>(fn: (item: U extends object ? FormModel<U> : U, index: number) => R): R[];
61
- /** Итерация по элементам. */
62
- forEach(fn: (item: U extends object ? FormModel<U> : U, index: number) => void): void;
70
+ /** Элемент по индексу: объект под-модель, массив {@link ModelArray}, лист → значение. */
71
+ at(index: number): NonNullable<U> extends object ? ModelArrayItem<U> : ModelArrayItem<U> | undefined;
72
+ /** Map по элементам (объект → {@link FormModel}, Opaque/примитивзначение, массив → {@link ModelArray}). */
73
+ map<R>(fn: (item: ModelArrayItem<U>, index: number) => R): R[];
74
+ /** Итерация по элементам (см. {@link ModelArrayItem}). */
75
+ forEach(fn: (item: ModelArrayItem<U>, index: number) => void): void;
63
76
  /** Снимок массива значений (без подписки). */
64
77
  toArray(): U[];
65
78
  /** Индексный value-доступ. */
@@ -72,11 +85,19 @@ export interface ModelArray<U> {
72
85
  * @group Model
73
86
  */
74
87
  export type ModelSignals<T> = {
75
- [K in keyof T]: NonNullable<T[K]> extends ReadonlyArray<infer U> ? {
76
- readonly length: number;
77
- readonly [index: number]: ModelSignals<U & object>;
78
- } : NonNullable<T[K]> extends Opaque ? PathAwareSignal<T[K]> : NonNullable<T[K]> extends object ? ModelSignals<NonNullable<T[K]>> : PathAwareSignal<T[K]>;
88
+ [K in keyof T]: ModelSignalNode<T[K]>;
79
89
  };
90
+ /**
91
+ * Узел дерева сигналов `model.$`: массив → индексируемый узел под-сигналов, Opaque (Date/File/Blob)
92
+ * и примитив → {@link PathAwareSignal}, объект → вложенное дерево {@link ModelSignals}. Для примитивных
93
+ * и Opaque массивов элемент — сам сигнал листа (а не под-дерево), поэтому `model.$.tags[0]` — это
94
+ * `PathAwareSignal<string>`, а не `ModelSignals<never>`.
95
+ * @internal
96
+ */
97
+ type ModelSignalNode<V> = NonNullable<V> extends ReadonlyArray<infer U> ? {
98
+ readonly length: number;
99
+ readonly [index: number]: ModelSignalNode<U>;
100
+ } : NonNullable<V> extends Opaque ? PathAwareSignal<V> : NonNullable<V> extends object ? ModelSignals<NonNullable<V>> : PathAwareSignal<V>;
80
101
  /**
81
102
  * API уровня модели (доступно на корне и под-моделях элементов массива).
82
103
  *
@@ -90,9 +111,17 @@ export interface ModelApi<T> {
90
111
  readonly $: ModelSignals<T>;
91
112
  /** Снимок значений (без подписки) — для submit. */
92
113
  get(): T;
93
- /** Массовая замена значений (load с сервера). Не меняет initial-снимок. */
114
+ /**
115
+ * @deprecated Алиас {@link ModelApi.patch}: `set` выполняет то же частичное **слияние** —
116
+ * обновляет только переданные ключи, отсутствующие НЕ трогает (это НЕ полная замена, вопреки
117
+ * старому названию). Используйте `patch`; для замены с обнулением лишних полей делайте
118
+ * `patch({ ...defaults, ...dto })` явно. Не меняет initial-снимок.
119
+ */
94
120
  set(value: Partial<T>): void;
95
- /** Частичное слияние значений. */
121
+ /**
122
+ * Частичное слияние значений (load/patch с сервера): обновляет только переданные ключи,
123
+ * отсутствующие ключи НЕ трогаются. Не меняет initial-снимок.
124
+ */
96
125
  patch(value: Partial<T>): void;
97
126
  /** Отличаются ли текущие значения от initial-снимка (value-diff). */
98
127
  isDirty(): boolean;
@@ -1,9 +1,13 @@
1
1
  import { ValidationError } from '../types';
2
+ import { FormSchemaNode } from '../types/schema-node';
2
3
  import { FormModel } from './types';
3
4
  /**
4
- * Валидатор слоя данных. `value` — значение поля; `model` — ближайший scope (под-модель элемента
5
+ * Валидатор слоя **данных**. `value` — значение поля; `model` — ближайший scope (под-модель элемента
5
6
  * массива или корень); `root` — корневая модель.
6
7
  *
8
+ * Отличие от {@link Validator}: 2-й/3-й аргументы — сами данные ({@link FormModel}/scope), а не
9
+ * `FormProxy`-узлы формы. Оба совместимы с полем `validators` узла схемы (см. {@link SchemaValidator}).
10
+ *
7
11
  * @group Model
8
12
  */
9
13
  export type ModelValidator<TValue = unknown, TModel = unknown, TRoot = unknown> = (value: TValue, model: TModel, root: TRoot) => ValidationError | null | Promise<ValidationError | null>;
@@ -32,7 +36,7 @@ export interface ModelValidationResult {
32
36
  * if (!res.valid) console.log(res.errors); // { 'email': [{ code, message }], ... }
33
37
  * ```
34
38
  */
35
- export declare function validateModelSync<T>(model: FormModel<T>, schema: unknown): ModelValidationResult;
39
+ export declare function validateModelSync<T>(model: FormModel<T>, schema: FormSchemaNode): ModelValidationResult;
36
40
  /**
37
41
  * Headless-валидация данных (sync + async). Работает без UI/нод.
38
42
  *
@@ -43,7 +47,7 @@ export declare function validateModelSync<T>(model: FormModel<T>, schema: unknow
43
47
  * if (!res.valid) console.log(res.errors); // { 'email': [{ code, message }], ... }
44
48
  * ```
45
49
  */
46
- export declare function validateModel<T>(model: FormModel<T>, schema: unknown): Promise<ModelValidationResult>;
50
+ export declare function validateModel<T>(model: FormModel<T>, schema: FormSchemaNode): Promise<ModelValidationResult>;
47
51
  /**
48
52
  * In-form валидация: прогоняет {@link validateModel} и роутит ошибки в ноды формы
49
53
  * (через реестр сигнал→нода): `node.setErrors(errors[path] ?? [])` — заодно очищает прошедшие поля
@@ -65,4 +69,4 @@ export declare function validateModel<T>(model: FormModel<T>, schema: unknown):
65
69
  * if (res.valid) await api.save(model.get());
66
70
  * ```
67
71
  */
68
- export declare function validateFormModel<T>(model: FormModel<T>, schema: unknown): Promise<ModelValidationResult>;
72
+ export declare function validateFormModel<T>(model: FormModel<T>, schema: FormSchemaNode): Promise<ModelValidationResult>;
@@ -35,7 +35,7 @@ export declare class FieldNode<T> extends FormNode<T> {
35
35
  readonly status: ReadonlySignal<FieldStatus>;
36
36
  readonly disabled: ReadonlySignal<boolean>;
37
37
  readonly errors: ReadonlySignal<ValidationError[]>;
38
- readonly componentProps: ReadonlySignal<Record<string, any>>;
38
+ readonly componentProps: ReadonlySignal<Record<string, unknown>>;
39
39
  /**
40
40
  * Вычисляемое свойство: нужно ли показывать ошибку
41
41
  * Ошибка показывается если поле невалидно И (touched ИЛИ dirty)
@@ -186,7 +186,7 @@ export declare class FieldNode<T> extends FormNode<T> {
186
186
  * });
187
187
  * ```
188
188
  */
189
- updateComponentProps(props: Partial<Record<string, any>>): void;
189
+ updateComponentProps(props: Partial<Record<string, unknown>>): void;
190
190
  /**
191
191
  * Динамически изменяет триггер валидации (updateOn)
192
192
  * Полезно для адаптивной валидации - например, переключиться на instant feedback после первого submit