@4riders/reform 3.0.24

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 (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +266 -0
  3. package/dist/index.d.ts +2715 -0
  4. package/dist/index.es.js +1715 -0
  5. package/dist/index.es.js.map +1 -0
  6. package/package.json +70 -0
  7. package/src/index.ts +90 -0
  8. package/src/reform/ArrayHelper.ts +164 -0
  9. package/src/reform/Form.tsx +81 -0
  10. package/src/reform/FormManager.ts +494 -0
  11. package/src/reform/Reform.ts +15 -0
  12. package/src/reform/components/BaseCheckboxField.tsx +72 -0
  13. package/src/reform/components/BaseDateField.tsx +84 -0
  14. package/src/reform/components/BaseRadioField.tsx +72 -0
  15. package/src/reform/components/BaseSelectField.tsx +103 -0
  16. package/src/reform/components/BaseTextAreaField.tsx +87 -0
  17. package/src/reform/components/BaseTextField.tsx +135 -0
  18. package/src/reform/components/InputHTMLProps.tsx +89 -0
  19. package/src/reform/observers/observer.ts +131 -0
  20. package/src/reform/observers/observerPath.ts +327 -0
  21. package/src/reform/observers/useObservers.ts +232 -0
  22. package/src/reform/useForm.ts +246 -0
  23. package/src/reform/useFormContext.tsx +37 -0
  24. package/src/reform/useFormField.ts +75 -0
  25. package/src/reform/useRender.ts +12 -0
  26. package/src/yop/MessageProvider.ts +204 -0
  27. package/src/yop/Metadata.ts +304 -0
  28. package/src/yop/ObjectsUtil.ts +811 -0
  29. package/src/yop/TypesUtil.ts +148 -0
  30. package/src/yop/ValidationContext.ts +207 -0
  31. package/src/yop/Yop.ts +430 -0
  32. package/src/yop/constraints/CommonConstraints.ts +124 -0
  33. package/src/yop/constraints/Constraint.ts +135 -0
  34. package/src/yop/constraints/MinMaxConstraints.ts +53 -0
  35. package/src/yop/constraints/OneOfConstraint.ts +40 -0
  36. package/src/yop/constraints/TestConstraint.ts +176 -0
  37. package/src/yop/decorators/array.ts +157 -0
  38. package/src/yop/decorators/boolean.ts +69 -0
  39. package/src/yop/decorators/date.ts +73 -0
  40. package/src/yop/decorators/email.ts +66 -0
  41. package/src/yop/decorators/file.ts +69 -0
  42. package/src/yop/decorators/id.ts +35 -0
  43. package/src/yop/decorators/ignored.ts +40 -0
  44. package/src/yop/decorators/instance.ts +110 -0
  45. package/src/yop/decorators/number.ts +73 -0
  46. package/src/yop/decorators/string.ts +90 -0
  47. package/src/yop/decorators/test.ts +41 -0
  48. package/src/yop/decorators/time.ts +112 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 4riders SAS
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,266 @@
1
+ # Reform
2
+
3
+ Reform is a powerful, type-safe, and extensible validation and form management library for TypeScript and React. A unique feature of this framework is its use of modern TypeScript class decorators to define validation schemas and constraints directly on your model classes, enabling highly expressive, maintainable, and type-safe form logic. It provides advanced features for building complex forms, handling validation, and managing form state with a focus on flexibility and developer experience.
4
+
5
+ ## Features
6
+
7
+ - **Type-safe validation schemas** using decorators and constraints
8
+ - **Composable constraints** for fields, arrays, objects, and custom types
9
+ - **Localized validation messages** with pluggable message providers
10
+ - **Advanced form state management** (dirty, touched, errors, async validation)
11
+ - **Observer pattern** for reacting to changes in form fields
12
+ - **Deep object path utilities** for accessing and updating nested data
13
+ - **Extensible metadata system** for field and form configuration
14
+
15
+ ## Installation
16
+
17
+ Using npm:
18
+ ```bash
19
+ $ npm install @4riders/reform
20
+ ```
21
+
22
+ Using yarn:
23
+ ```bash
24
+ $ yarn add @4riders/reform
25
+ ```
26
+
27
+ ## Quick Start
28
+
29
+ ### Defining a Model with Decorators and Running Validations
30
+
31
+ ```tsx
32
+ import { string } from '@4riders/reform'
33
+
34
+ class Person {
35
+
36
+ @string({ required: true, min: 1 })
37
+ name: string | null = null
38
+ }
39
+ ```
40
+
41
+ The `name` property above can neither be `null` nor `undefined` because of the `required: true` constraint but could be an empty string without the `min: 1` constraint. See the [string](functions/string.html) decorator for more options.
42
+
43
+ To validate a value based on this model, you can use the [validate](classes/Yop.html#validate-2) function (we will later use the [useForm](functions/useForm.html) hook to manage form state and validation in React, but this is how you can validate any value against a model):
44
+
45
+ ```tsx
46
+ import { Yop, instance } from '@4riders/reform'
47
+
48
+ const statuses = Yop.validate(
49
+ {}, // (1)
50
+ instance({ of: Person }) // (2)
51
+ )
52
+ console.log(statuses)
53
+ ```
54
+
55
+ 1. The value we want to validate, in this case an empty object `{}`. This could be any value, even `null` or `undefined`, and of course a `new Person()`.
56
+ 2. A validation schema defined as an instance of the `Person` class, which tells the validator to use the constraints defined by the decorators used in the `Person` class.
57
+
58
+ Running the code above will print in the console an array of one validation status, because the `name` property is required but is `undefined` in the `{}` value:
59
+
60
+ ```json
61
+ [{
62
+ "level": "error",
63
+ "path": "name",
64
+ "value": undefined,
65
+ "kind": "string",
66
+ "code": "required",
67
+ "constraint": true,
68
+ "message": "Required field"
69
+ }]
70
+ ```
71
+
72
+ See [ValidationStatus](types/ValidationStatus.html) for more details on the validation status object.
73
+
74
+ ### Custom Validation Messages and Dynamic Constraints
75
+
76
+ You can provide custom validation messages directly in the constraints as a tuple where the first element is the constraint value and the second element is the custom message (which can be a `string` or a `JSX.Element`):
77
+
78
+ ```tsx
79
+ import { string } from '@4riders/reform'
80
+
81
+ class Person {
82
+
83
+ @string({ required: [true, "Please enter your name!"] })
84
+ name: string | null = null
85
+ }
86
+ const statuses = Yop.validate(new Person(), instance({ of: Person }))
87
+ console.log(statuses)
88
+ ```
89
+
90
+ Running this code will print the following validation status with the custom message:
91
+
92
+ ```json
93
+ [{
94
+ "level": "error",
95
+ "path": "name",
96
+ "value": null,
97
+ "kind": "string",
98
+ "code": "required",
99
+ "constraint": true,
100
+ "message": "Please enter your name!"
101
+ }]
102
+ ```
103
+
104
+ Constraints can also be defined as a function that returns a tuple of the constraint value and the message, which allows for dynamic messages based on the value or other factors:
105
+
106
+ ```tsx
107
+ import { string } from '@4riders/reform'
108
+
109
+ class Person {
110
+
111
+ minNameLength = 4
112
+
113
+ @string({ min: ctx => [ctx.parent.minNameLength, `Name must be at least ${ctx.parent.minNameLength} characters long, but got ${ctx.value.length}!`] })
114
+ name: string | null = "Bob"
115
+ }
116
+
117
+ const statuses = Yop.validate(new Person(), instance({ of: Person }))
118
+ console.log(statuses)
119
+ ```
120
+
121
+ Running this code will print the following validation status with the parameterized custom message:
122
+
123
+ ```json
124
+ [{
125
+ "level": "error",
126
+ "path": "name",
127
+ "value": "Bob",
128
+ "kind": "string",
129
+ "code": "min",
130
+ "constraint": 4,
131
+ "message": "Name must be at least 4 characters long, but got 3!"
132
+ }]
133
+ ```
134
+
135
+ ### Form Management with React
136
+
137
+ After defining your model with decorators, you can use the [useForm](functions/useForm.html) hook to manage form state and validation in React. The `useForm` hook has two overloads, the simplest one takes the model and a submit function, and returns a [FormManager](interfaces/FormManager.html) instance.
138
+
139
+ ```tsx
140
+ import { useForm, Form } from '@4riders/reform'
141
+
142
+ function UserForm() {
143
+
144
+ const form = useForm(Person, form => {
145
+ // This function is called when the form is submitted and valid
146
+ console.log('Form submitted with values:', form.values)
147
+ form.setSubmitting(false)
148
+ })
149
+
150
+ return (
151
+ <Form form={ form } autoComplete="off" noValidate disabled={ form.submitting }>
152
+ {/* Inputs here */}
153
+ <button type="submit">Submit</button>
154
+ </Form>
155
+ )
156
+ }
157
+ ```
158
+
159
+ The [Form](functions/Form.html) component is a wrapper around the standard HTML `<form>` element that handles the submit event and calls the provided submit function with the form manager instance. It also sets a React `Context` that allows child components to access the form manager and its state through the [useFormContext](functions/useFormContext.html) hook. All children of the `Form` component are enclosed within an HTML `<fieldset>` element, which is disabled when the `disabled` property is set to `true`.
160
+
161
+ ### Form Inputs Components
162
+
163
+ You can create your own form input components that are connected to the form state and validation by using the [useFormField](functions/useFormField.html) hook, which takes a field path and returns the field's constraints, validation status, and a render function. For example, here is a simple `TextField` component that uses the [BaseTextField](functions/BaseTextField.html) component and connects it to the form state:
164
+
165
+ ```tsx
166
+ import { ComponentType } from 'react'
167
+ import { BaseTextField, Form, string, StringConstraints, StringValue, useForm, useFormField } from '@4riders/reform'
168
+
169
+ function TextField(props: { label: string, path: string }) { // (1)
170
+ const { constraints, status, render } = useFormField<StringValue, number>(props.path!)
171
+
172
+ return (
173
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
174
+ <div>{ props.label + (constraints?.required ? " *" : "") }</div>
175
+ <BaseTextField name={ props.path! } render={ render } />
176
+ { status?.message && <div style={{ color: 'red' }}>{ status.message }</div> }
177
+ </div>
178
+ )
179
+ }
180
+
181
+ type TextFieldProps<Parent> = StringConstraints<StringValue, Parent> & { // (2)
182
+ input?: ComponentType<any>
183
+ label?: string
184
+ path?: string
185
+ }
186
+
187
+ function textField<Parent>(props?: TextFieldProps<Parent>) { // (3)
188
+ return string<StringValue, Parent>({ input: TextField, ...props })
189
+ }
190
+
191
+ class Person {
192
+
193
+ @textField({ label: "Name", required: true }) // (4)
194
+ name: string | null = null
195
+ }
196
+
197
+ function PersonForm() { // (5)
198
+
199
+ const form = useForm(Person, form => {
200
+ console.log('Form submitted with values:', form.values)
201
+ form.setSubmitting(false)
202
+ })
203
+
204
+ return (
205
+ <Form form={ form } autoComplete="off" noValidate disabled={ form.submitting }>
206
+ <TextField path="name" label="Name" />
207
+ <button type="submit">Submit</button>
208
+ </Form>
209
+ )
210
+ }
211
+ ```
212
+
213
+ 1. The `TextField` component uses the [useFormField](functions/useFormField.html) hook to get the constraints and validation status for the field based on the provided path, and renders a label, the input component, and any validation message. It uses the `BaseTextField` component as the input, which is a simple wrapper around an HTML `<input type="text">` element that handles change and blur events and calls the provided render function to update the form state.
214
+ 2. The `TextFieldProps` type defines the props for the `TextField` component and the `textField` decorator. It extends [StringConstraints](interfaces/StringConstraints.html) and adds a `path` property (the path to the field in the model), a `label` property, and an `input` component to render.
215
+ 3. The `textField` function is a decorator that creates a string constraint with the `TextField` component as the default input, allowing us to use it directly in the model definition.
216
+ 4. The `name` property in the `Person` class is decorated with the `@textField` decorator, which defines it as a required string field with the `TextField` component as its input and a label of "Name". Note that the `BaseTextField` component converts automatically an empty string to `null` so there is no need to add a `min: 1` constraint to disallow empty values.
217
+ 5. The `PersonForm` component uses the [useForm](functions/useForm.html) hook to create a form manager for the `Person` model, and renders a [Form](functions/Form.html) component with a `TextField` for the `name` property and a submit button.
218
+
219
+ ### Observers
220
+
221
+ You can also create observers that react to changes in form fields using the [observer](functions/observer.html) decorator, which takes a field path and a callback function that is called whenever the field value changes. For example, you can create an observer that logs the current value and validation status of the `name` field whenever it changes:
222
+
223
+ ```tsx
224
+ import { observer, useForm } from '@4riders/reform'
225
+
226
+ class Person {
227
+
228
+ age: number | null = null
229
+
230
+ @observer("age", (context) => context.setValue(
231
+ context.observedValue != null ? (context.observedValue as number) >= 18 : null
232
+ ))
233
+ adult: boolean | null = null
234
+ }
235
+
236
+ const form = useForm(MyFormModel, () => {})
237
+ ```
238
+
239
+ In this example, the `adult` field is automatically updated to `true` or `false` based on the value of the `age` field, and it is also marked as untouched to avoid triggering validation messages when it changes.
240
+
241
+ See the [observer](functions/observer.html) decorator for more details and options.
242
+
243
+ Note that observers are automatically set up when using the simpler overload of the `useForm` hook, so you don't need to do anything special to enable them. However, if you are using the more advanced overload of the `useForm` hook, you need to call the [useObservers](functions/useObservers.html) hook after initializing the form:
244
+
245
+ ```tsx
246
+ const form = useForm(MyFormModel, () => {})
247
+ useObservers(MyFormModel, form)
248
+ ```
249
+
250
+ ## API Reference
251
+
252
+ See [full API reference](modules.html), including all decorators, utilities, and form management hooks.
253
+
254
+ ## Building, Testing, and Publishing
255
+
256
+ Use the following commands to build, test, and publish the package:
257
+
258
+ ```bash
259
+ $ yarn build # build the library
260
+ $ yarn test # run tests
261
+ $ npm publish # publish to npm
262
+ ```
263
+
264
+ ## License
265
+
266
+ MIT