@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/README.md CHANGED
@@ -30,33 +30,35 @@ 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.
36
+
33
37
  ```tsx
34
38
  import { useMemo } from 'react';
35
39
  import {
40
+ createModel,
36
41
  createForm,
42
+ validateFormModel,
37
43
  useFormControl,
38
- required,
39
- email,
40
- validate,
41
- watchField,
42
- FieldNode,
43
- type ValidationSchemaFn,
44
- type BehaviorSchemaFn,
44
+ type FieldNode,
45
45
  } from '@reformer/core';
46
+ import { required, email, minLength } from '@reformer/core/validators';
47
+ import { defineFormBehavior, onChange } from '@reformer/core/behaviors';
46
48
 
47
49
  // 0. Simple FormField component
48
50
  function FormField({ label, control }: { label: string; control: FieldNode<string> }) {
49
- const { value, errors } = useFormControl(control);
51
+ const { value, errors, shouldShowError } = useFormControl(control);
50
52
 
51
53
  return (
52
54
  <div>
53
55
  <label>{label}</label>
54
56
  <input
55
- value={value}
57
+ value={value ?? ''}
56
58
  onChange={(e) => control.setValue(e.target.value)}
57
59
  onBlur={() => control.markAsTouched()}
58
60
  />
59
- {errors.length > 0 && <span className="error">{errors[0].message}</span>}
61
+ {shouldShowError && <span className="error">{errors[0].message}</span>}
60
62
  </div>
61
63
  );
62
64
  }
@@ -69,62 +71,55 @@ interface RegistrationForm {
69
71
  confirmPassword: string;
70
72
  }
71
73
 
72
- // 2. Form schema
73
- const formSchema = {
74
- username: { value: '' },
75
- email: { value: '' },
76
- password: { value: '' },
77
- confirmPassword: { value: '' },
74
+ // 2. Reactive model — the source of truth for values
75
+ const model = createModel<RegistrationForm>({
76
+ username: '',
77
+ email: '',
78
+ password: '',
79
+ confirmPassword: '',
80
+ });
81
+
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`.
85
+ const schema = {
86
+ 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
+ },
101
+ ],
78
102
  };
79
103
 
80
- // 3. Validation schema
81
- const validationSchema: ValidationSchemaFn<RegistrationForm> = (path) => {
82
- required(path.username);
83
-
84
- required(path.email);
85
- email(path.email);
86
-
87
- required(path.password);
88
- required(path.confirmPassword);
89
-
90
- // Cross-field validation: вешается на поле-носитель ошибки, соседнее поле читается через `root`
91
- validate(path.confirmPassword, (value, _control, root) => {
92
- const password = root.password.value.value;
93
- if (value && password && value !== password) {
94
- return { code: 'mismatch', message: 'Passwords do not match' };
95
- }
96
- return null;
104
+ // 4. Behavior — declarative reactive logic, run by `createForm({ behavior })`.
105
+ // Clear confirmPassword whenever password changes.
106
+ const behavior = defineFormBehavior<RegistrationForm>(({ model }) => {
107
+ onChange(model.$.password, () => {
108
+ if (model.confirmPassword) model.confirmPassword = '';
97
109
  });
98
- };
99
-
100
- // 4. Behavior schema
101
- const behaviorSchema: BehaviorSchemaFn<RegistrationForm> = (path) => {
102
- // Clear confirmPassword when password changes (if not empty)
103
- watchField(path.password, (_, ctx) => {
104
- const confirmValue = ctx.form.confirmPassword.value.value;
105
- if (confirmValue) {
106
- ctx.form.confirmPassword.setValue('', { emitEvent: false });
107
- }
108
- });
109
- };
110
+ });
110
111
 
111
112
  // 5. Registration form component
112
113
  function RegistrationFormExample() {
113
- const form = useMemo(
114
- () =>
115
- createForm<RegistrationForm>({
116
- form: formSchema,
117
- validation: validationSchema,
118
- behavior: behaviorSchema,
119
- }),
120
- []
121
- );
114
+ const form = useMemo(() => createForm<RegistrationForm>({ model, schema, behavior }), []);
122
115
 
123
116
  const handleSubmit = async (e: React.FormEvent) => {
124
117
  e.preventDefault();
125
- await form.validate();
126
- if (form.valid.value) {
127
- console.log('Form data:', form.value.value);
118
+ form.touchAll();
119
+ // Validate the whole model against the schema (sync + async); errors route into the form.
120
+ const { valid } = await validateFormModel(model, schema);
121
+ if (valid) {
122
+ console.log('Form data:', model.get());
128
123
  }
129
124
  };
130
125
 
@@ -0,0 +1,263 @@
1
+ import { effect as a } from "@preact/signals-core";
2
+ var l = /* @__PURE__ */ ((n) => (n.THROW = "throw", n.LOG = "log", n.CONVERT = "convert", n))(l || {});
3
+ class m {
4
+ static handle(e, t, r = "throw") {
5
+ const s = this.extractMessage(e);
6
+ switch (process.env.NODE_ENV !== "production" && console.error(`[${t}]`, e), r) {
7
+ case "throw":
8
+ throw e;
9
+ case "log":
10
+ return;
11
+ case "convert":
12
+ return {
13
+ code: "validator_error",
14
+ message: s,
15
+ params: { field: t }
16
+ };
17
+ }
18
+ }
19
+ /**
20
+ * Извлечь сообщение из ошибки
21
+ *
22
+ * Обрабатывает различные типы ошибок:
23
+ * - Error объекты → error.message
24
+ * - Строки → возвращает как есть
25
+ * - Объекты с message → извлекает message
26
+ * - Другое → String(error)
27
+ *
28
+ * @param error Ошибка для извлечения сообщения
29
+ * @returns Сообщение ошибки
30
+ * @private
31
+ *
32
+ * @example
33
+ * ```typescript
34
+ * FormErrorHandler.extractMessage(new Error('Test'));
35
+ * // 'Test'
36
+ *
37
+ * FormErrorHandler.extractMessage('String error');
38
+ * // 'String error'
39
+ *
40
+ * FormErrorHandler.extractMessage({ message: 'Object error' });
41
+ * // 'Object error'
42
+ *
43
+ * FormErrorHandler.extractMessage(null);
44
+ * // 'null'
45
+ * ```
46
+ */
47
+ static extractMessage(e) {
48
+ return e instanceof Error ? e.message : typeof e == "string" ? e : typeof e == "object" && e !== null && "message" in e ? String(e.message) : String(e);
49
+ }
50
+ /**
51
+ * Создать ValidationError с заданными параметрами
52
+ *
53
+ * Утилитная функция для создания ValidationError объектов
54
+ *
55
+ * @param code Код ошибки
56
+ * @param message Сообщение ошибки
57
+ * @param field Поле (опционально)
58
+ * @returns ValidationError объект
59
+ *
60
+ * @example
61
+ * ```typescript
62
+ * const error = FormErrorHandler.createValidationError(
63
+ * 'required',
64
+ * 'This field is required',
65
+ * 'email'
66
+ * );
67
+ * // { code: 'required', message: 'This field is required', field: 'email' }
68
+ * ```
69
+ */
70
+ static createValidationError(e, t, r) {
71
+ return {
72
+ code: e,
73
+ message: t,
74
+ params: r ? { field: r } : void 0
75
+ };
76
+ }
77
+ /**
78
+ * Проверить, является ли объект ValidationError
79
+ *
80
+ * Type guard для ValidationError
81
+ *
82
+ * @param value Значение для проверки
83
+ * @returns true если value является ValidationError
84
+ *
85
+ * @example
86
+ * ```typescript
87
+ * if (FormErrorHandler.isValidationError(result)) {
88
+ * console.log(result.code); // OK, типобезопасно
89
+ * }
90
+ * ```
91
+ */
92
+ static isValidationError(e) {
93
+ return typeof e == "object" && e !== null && "code" in e && "message" in e && typeof e.code == "string" && typeof e.message == "string";
94
+ }
95
+ }
96
+ const u = /* @__PURE__ */ new WeakMap();
97
+ function w(n) {
98
+ u.set(n, (u.get(n) ?? 0) + 1);
99
+ }
100
+ function E(n) {
101
+ const e = u.get(n);
102
+ e !== void 0 && (e <= 1 ? u.delete(n) : u.set(n, e - 1));
103
+ }
104
+ function O(n) {
105
+ return u.has(n);
106
+ }
107
+ const h = /* @__PURE__ */ new WeakMap();
108
+ function D(n, e) {
109
+ h.set(n, e);
110
+ }
111
+ function p(n) {
112
+ return h.get(n);
113
+ }
114
+ const y = "runOutsideEffect";
115
+ function g(n) {
116
+ return typeof n == "object" && n !== null && typeof n.then == "function";
117
+ }
118
+ function v(n, e) {
119
+ try {
120
+ const t = n();
121
+ g(t) && t.then(void 0, (r) => m.handle(r, e, l.LOG));
122
+ } catch (t) {
123
+ m.handle(t, e, l.LOG);
124
+ }
125
+ }
126
+ function F(n) {
127
+ return (...e) => {
128
+ i(() => n(...e));
129
+ };
130
+ }
131
+ function i(n) {
132
+ let e = !1;
133
+ return queueMicrotask(() => {
134
+ e || v(n, y);
135
+ }), () => {
136
+ e = !0;
137
+ };
138
+ }
139
+ function V(n, e) {
140
+ return () => {
141
+ e(() => v(n, "safeDebouncedCallback"));
142
+ };
143
+ }
144
+ function W(n, e, t, r) {
145
+ return a(() => {
146
+ const s = n.map((f) => f.value);
147
+ if (r?.when && !r.when(...s)) return;
148
+ const o = t(...s);
149
+ e.peek() !== o && (e.value = o);
150
+ });
151
+ }
152
+ function N(n, e, t) {
153
+ return a(() => {
154
+ const r = n.value;
155
+ if (t?.when && !t.when()) return;
156
+ const s = t?.transform ? t.transform(r) : r;
157
+ e.peek() !== s && (e.value = s);
158
+ });
159
+ }
160
+ function x(n, e, t) {
161
+ let r = !0;
162
+ return a(() => {
163
+ const s = n.value;
164
+ r && (r = !1, !t?.immediate) || e(s);
165
+ });
166
+ }
167
+ function k(n, e, t) {
168
+ return a(() => {
169
+ const r = e(), s = p(n);
170
+ s && i(() => {
171
+ r ? s.enable() : (s.disable(), t?.resetOnDisable && s.reset());
172
+ });
173
+ });
174
+ }
175
+ function C(n, e, t) {
176
+ return k(n, () => !e(), t);
177
+ }
178
+ function M(n, e) {
179
+ let t = !1;
180
+ return a(() => {
181
+ const r = n.value;
182
+ t || i(() => {
183
+ const s = e(r);
184
+ if (n.peek() !== s) {
185
+ t = !0;
186
+ try {
187
+ n.value = s;
188
+ } finally {
189
+ t = !1;
190
+ }
191
+ }
192
+ });
193
+ });
194
+ }
195
+ function j(n, e, t) {
196
+ const r = t?.resetValue ?? null;
197
+ return a(() => {
198
+ const s = e();
199
+ i(() => {
200
+ s && n.peek() !== r && (n.value = r);
201
+ });
202
+ });
203
+ }
204
+ function L(n, e, t) {
205
+ const r = t?.transform;
206
+ let s = !1;
207
+ const o = a(() => {
208
+ const c = n.value;
209
+ s || i(() => {
210
+ s = !0;
211
+ try {
212
+ const d = r ? r(c) : c;
213
+ e.peek() !== d && (e.value = d);
214
+ } finally {
215
+ s = !1;
216
+ }
217
+ });
218
+ }), f = a(() => {
219
+ const c = e.value;
220
+ s || i(() => {
221
+ s = !0;
222
+ try {
223
+ n.peek() !== c && (n.value = c);
224
+ } finally {
225
+ s = !1;
226
+ }
227
+ });
228
+ });
229
+ return () => {
230
+ o(), f();
231
+ };
232
+ }
233
+ function T(n, e) {
234
+ let t = !0;
235
+ return a(() => {
236
+ if (n.forEach((r) => r.value), t) {
237
+ t = !1;
238
+ return;
239
+ }
240
+ i(() => e());
241
+ });
242
+ }
243
+ export {
244
+ l as E,
245
+ m as F,
246
+ j as a,
247
+ T as b,
248
+ N as c,
249
+ D as d,
250
+ k as e,
251
+ F as f,
252
+ p as g,
253
+ V as h,
254
+ O as i,
255
+ W as j,
256
+ C as k,
257
+ w as m,
258
+ i as r,
259
+ L as s,
260
+ M as t,
261
+ E as u,
262
+ x as w
263
+ };