{"_id":"@4riders/reform","_rev":"3-23e601ad190dd3f50dc5e48f21a8fdfe","name":"@4riders/reform","dist-tags":{"latest":"3.0.26"},"versions":{"3.0.24":{"name":"@4riders/reform","version":"3.0.24","keywords":["react","form","typescript"],"author":{"name":"Franck Wolff","email":"franck.wolff@4riders.net"},"license":"MIT","_id":"@4riders/reform@3.0.24","maintainers":[{"name":"franckwolff","email":"frawolff@gmail.com"}],"homepage":"https://github.com/4riders/reform#readme","bugs":{"url":"https://github.com/4riders/reform/issues"},"dist":{"shasum":"7ce6c356eb0a902a4d02322a9b9e42fde325abcd","tarball":"https://registry.npmjs.org/@4riders/reform/-/reform-3.0.24.tgz","fileCount":48,"integrity":"sha512-4y76Yor4ildqzlTcGZPpC1Ht9I0YaKNX5BRe6ns46cNQte8hRlBKHR9WnQRj75VTleXKyhEi6I2rrcIfhTsL9g==","signatures":[{"sig":"MEYCIQDa0M4TR6F54CKnpf+I2RHkkZzFt4EdQ/ewzRovzSOGQgIhAPaJABQpvS0la7Da2S1FhhCJSdzUXqXuwa5k4BucKqXA","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":739899},"type":"module","types":"./dist/index.d.ts","module":"./dist/index.es.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.es.js"}},"gitHead":"1033e0baacd7635690accc8327db6dccda0c6749","scripts":{"test":"vitest --run","build":"tsc && vite build","typedoc":"typedoc --options typedoc.json","prepublishOnly":"NODE_ENV=release tsc && vite build"},"_npmUser":{"name":"franckwolff","email":"frawolff@gmail.com"},"repository":{"url":"git+https://github.com/4riders/reform.git","type":"git"},"_npmVersion":"10.9.0","description":"Reform is a powerful, type-safe, and extensible validation and form management library for TypeScript and React","directories":{},"sideEffects":false,"_nodeVersion":"22.12.0","publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"packageManager":"yarn@4.13.0","devDependencies":{"vite":"^8.0.3","jsdom":"^29.0.1","react":"^18.3.1","vitest":"^4.1.2","typedoc":"^0.28.18","react-dom":"^18.3.1","typescript":"^6.0.2","@babel/core":"^7.29.0","@types/node":"^25.5.0","@types/react":"^18.3.28","vite-plugin-dts":"^4.5.4","@types/react-dom":"^18.3.7","@types/babel__core":"^7.20.5","@testing-library/dom":"^10.4.1","@vitejs/plugin-react":"^6.0.1","@rolldown/plugin-babel":"^0.2.2","@testing-library/react":"^16.3.2","babel-plugin-react-compiler":"^1.0.0","@babel/plugin-proposal-decorators":"^7.29.0"},"peerDependencies":{"react":">=18","react-dom":">=18"},"_npmOperationalInternal":{"tmp":"tmp/reform_3.0.24_1774636974415_0.5700115495331597","host":"s3://npm-registry-packages-npm-production"}},"3.0.25":{"name":"@4riders/reform","version":"3.0.25","keywords":["react","form","typescript"],"author":{"name":"Franck Wolff","email":"franck.wolff@4riders.net"},"license":"MIT","_id":"@4riders/reform@3.0.25","maintainers":[{"name":"franckwolff","email":"frawolff@gmail.com"}],"homepage":"https://github.com/4riders/reform#readme","bugs":{"url":"https://github.com/4riders/reform/issues"},"dist":{"shasum":"33f5706c9a38ecbb3eec11727d8da2c6a4257cb7","tarball":"https://registry.npmjs.org/@4riders/reform/-/reform-3.0.25.tgz","fileCount":48,"integrity":"sha512-hZVyujVNHo+LzCYZk59+cVGLFk15B/3R/ZpGAUGnNgngSua/mq6xtV2yjSX0zsPzcnSOJeiZEmZDeqWTbAAhpw==","signatures":[{"sig":"MEUCIQCVthfBX/zVN61yub059Lp11XhipYkn8wfanpBmo/fEQQIgQC9lg8flnvafwvtURalu2Sqp0pUhYsgGKyCuu/qQ6tM=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":739899},"type":"module","types":"./dist/index.d.ts","module":"./dist/index.es.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.es.js"}},"gitHead":"1033e0baacd7635690accc8327db6dccda0c6749","scripts":{"test":"vitest --run","build":"tsc && vite build","typedoc":"typedoc --options typedoc.json","prepublishOnly":"NODE_ENV=release tsc && vite build"},"_npmUser":{"name":"franckwolff","email":"frawolff@gmail.com"},"repository":{"url":"git+https://github.com/4riders/reform.git","type":"git"},"_npmVersion":"10.9.0","description":"Reform is a powerful, type-safe, and extensible validation and form management library for TypeScript and React","directories":{},"sideEffects":false,"_nodeVersion":"22.12.0","publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"packageManager":"yarn@4.13.0","devDependencies":{"vite":"^8.0.3","jsdom":"^29.0.1","react":"^18.3.1","vitest":"^4.1.2","typedoc":"^0.28.18","react-dom":"^18.3.1","typescript":"^6.0.2","@babel/core":"^7.29.0","@types/node":"^25.5.0","@types/react":"^18.3.28","vite-plugin-dts":"^4.5.4","@types/react-dom":"^18.3.7","@types/babel__core":"^7.20.5","@testing-library/dom":"^10.4.1","@vitejs/plugin-react":"^6.0.1","@rolldown/plugin-babel":"^0.2.2","@testing-library/react":"^16.3.2","babel-plugin-react-compiler":"^1.0.0","@babel/plugin-proposal-decorators":"^7.29.0"},"peerDependencies":{"react":">=18","react-dom":">=18"},"_npmOperationalInternal":{"tmp":"tmp/reform_3.0.25_1774637095417_0.9658084792541937","host":"s3://npm-registry-packages-npm-production"}},"3.0.26":{"name":"@4riders/reform","version":"3.0.26","description":"Reform is a powerful, type-safe, and extensible validation and form management library for TypeScript and React","author":{"name":"Franck Wolff","email":"franck.wolff@4riders.net"},"type":"module","main":"./dist/index.umd.cjs","module":"./dist/index.js","exports":{".":{"import":"./dist/index.js","require":"./dist/index.umd.cjs"}},"types":"./dist/index.d.ts","sideEffects":false,"license":"MIT","publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"homepage":"https://github.com/4riders/reform#readme","repository":{"type":"git","url":"git+https://github.com/4riders/reform.git"},"bugs":{"url":"https://github.com/4riders/reform/issues"},"keywords":["react","form","validation","decorators","typescript"],"scripts":{"build":"tsc && vite build","test":"vitest --run","prepublishOnly":"NODE_ENV=release tsc && vite build","typedoc":"typedoc --options typedoc.json"},"peerDependencies":{"react":">=19","react-dom":">=19"},"devDependencies":{"@babel/core":"^7.29.0","@babel/plugin-proposal-decorators":"^7.29.0","@rolldown/plugin-babel":"^0.2.2","@testing-library/dom":"^10.4.1","@testing-library/react":"^16.3.2","@types/babel__core":"^7.20.5","@types/node":"^25.5.0","@types/react":"^19.2.14","@types/react-dom":"^19.2.3","@vitejs/plugin-react":"^6.0.1","babel-plugin-react-compiler":"^1.0.0","jsdom":"^29.0.1","react":"^19.2.4","react-dom":"^19.2.4","rolldown":"^1.0.0-rc.12","typedoc":"^0.28.18","typescript":"^6.0.2","vite":"^8.0.3","vite-plugin-dts":"^4.5.4","vitest":"^4.1.2"},"packageManager":"yarn@4.13.0","gitHead":"641ec6dbcd7193505d7705ade61fd5b5138b0993","_id":"@4riders/reform@3.0.26","_nodeVersion":"25.8.2","_npmVersion":"11.11.1","dist":{"integrity":"sha512-7SAlYT6FwYqYOufXjenhyV/LmBZG9lpSwJD3OqS1u9+Q7f9JchWcFZ+0B3SsyQTyvN3/bLL2ljgMx+xxKOFpCA==","shasum":"b042695699f6b0c24c0ec6ecd33e7560a04bf36d","tarball":"https://registry.npmjs.org/@4riders/reform/-/reform-3.0.26.tgz","fileCount":50,"unpackedSize":1098705,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIDrtjtyvAzf77xjARMde7IzSG2oSqsa8Kw7Ii0X/emyIAiBc4KhB+ocCszY91dCmY9+ZlgpEFyv4/xGVmKGFxwFy2w=="}]},"_npmUser":{"name":"franckwolff","email":"frawolff@gmail.com"},"directories":{},"maintainers":[{"name":"franckwolff","email":"frawolff@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/reform_3.0.26_1774715124153_0.4723005534185085"},"_hasShrinkwrap":false}},"time":{"created":"2026-03-27T18:42:54.332Z","modified":"2026-03-28T16:25:24.499Z","3.0.24":"2026-03-27T18:42:54.638Z","3.0.25":"2026-03-27T18:44:55.599Z","3.0.26":"2026-03-28T16:25:24.364Z"},"bugs":{"url":"https://github.com/4riders/reform/issues"},"author":{"name":"Franck Wolff","email":"franck.wolff@4riders.net"},"license":"MIT","homepage":"https://github.com/4riders/reform#readme","keywords":["react","form","validation","decorators","typescript"],"repository":{"type":"git","url":"git+https://github.com/4riders/reform.git"},"description":"Reform is a powerful, type-safe, and extensible validation and form management library for TypeScript and React","maintainers":[{"name":"franckwolff","email":"frawolff@gmail.com"}],"readme":"# Reform\n\nReform 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.\n\n## Features\n\n- **Type-safe validation schemas** using decorators and constraints\n- **Composable constraints** for fields, arrays, objects, and custom types\n- **Localized validation messages** with pluggable message providers\n- **Advanced form state management** (dirty, touched, errors, async validation)\n- **Observer pattern** for reacting to changes in form fields\n- **Deep object path utilities** for accessing and updating nested data\n- **Extensible metadata system** for field and form configuration\n\n## Installation\n\nUsing npm:\n```bash\n$ npm install @4riders/reform\n```\n\nUsing yarn:\n```bash\n$ yarn add @4riders/reform\n```\n\n## Quick Start\n\n### Defining a Model with Decorators and Running Validations\n\n```tsx\nimport { string } from '@4riders/reform'\n\nclass Person {\n\t\n    @string({ required: true, min: 1 })\n    name: string | null = null\n}\n```\n\nThe `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.\n\nTo 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):\n\n```tsx\nimport { Yop, instance } from '@4riders/reform'\n\nconst statuses = Yop.validate(\n    {}, // (1)\n    instance({ of: Person }) // (2)\n)\nconsole.log(statuses)\n```\n\n1. 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()`.\n2. 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.\n\nRunning 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:\n\n```json\n[{\n    \"level\": \"error\",\n    \"path\": \"name\",\n    \"value\": undefined,\n    \"kind\": \"string\",\n    \"code\": \"required\",\n    \"constraint\": true,\n    \"message\": \"Required field\"\n}]\n```\n\nSee [ValidationStatus](types/ValidationStatus.html) for more details on the validation status object.\n\n### Custom Validation Messages and Dynamic Constraints\n\nYou 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`):\n\n```tsx\nimport { string } from '@4riders/reform'\n\nclass Person {\n\t\n\t@string({ required: [true, \"Please enter your name!\"] })\n\tname: string | null = null\n}\nconst statuses = Yop.validate(new Person(), instance({ of: Person }))\nconsole.log(statuses)\n```\n\nRunning this code will print the following validation status with the custom message:\n\n```json\n[{\n    \"level\": \"error\",\n    \"path\": \"name\",\n    \"value\": null,\n    \"kind\": \"string\",\n    \"code\": \"required\",\n    \"constraint\": true,\n    \"message\": \"Please enter your name!\"\n}]\n```\n\nConstraints 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:\n\n```tsx\nimport { string } from '@4riders/reform'\n\nclass Person {\n\n\tminNameLength = 4\n\t\n\t@string({ min: ctx => [ctx.parent.minNameLength, `Name must be at least ${ctx.parent.minNameLength} characters long, but got ${ctx.value.length}!`] })\n\tname: string | null = \"Bob\"\n}\n\nconst statuses = Yop.validate(new Person(), instance({ of: Person }))\nconsole.log(statuses)\n```\n\nRunning this code will print the following validation status with the parameterized custom message:\n\n```json\n[{\n    \"level\": \"error\",\n    \"path\": \"name\",\n    \"value\": \"Bob\",\n    \"kind\": \"string\",\n    \"code\": \"min\",\n    \"constraint\": 4,\n    \"message\": \"Name must be at least 4 characters long, but got 3!\"\n}]\n```\n\n### Form Management with React\n\nAfter 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.\n\n```tsx\nimport { useForm, Form } from '@4riders/reform'\n\nfunction UserForm() {\n    \n    const form = useForm(Person, form => {\n        // This function is called when the form is submitted and valid\n        console.log('Form submitted with values:', form.values)\n        form.setSubmitting(false)\n    })\n\n    return (\n        <Form form={ form } autoComplete=\"off\" noValidate disabled={ form.submitting }>\n            {/* Inputs here */}\n            <button type=\"submit\">Submit</button>\n        </Form>\n    )\n}\n```\n\nThe [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`.\n\n### Form Inputs Components\n\nYou 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:\n\n```tsx\nimport { ComponentType } from 'react'\nimport { BaseTextField, Form, string, StringConstraints, StringValue, useForm, useFormField } from '@4riders/reform'\n\nfunction TextField(props: { label: string, path: string }) { // (1)\n    const { constraints, status, render } = useFormField<StringValue, number>(props.path!)\n\n    return (\n        <div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>\n            <div>{ props.label + (constraints?.required ? \" *\" : \"\") }</div>\n            <BaseTextField name={ props.path! } render={ render } />\n            { status?.message && <div style={{ color: 'red' }}>{ status.message }</div> }\n        </div>\n    )\n}\n\ntype TextFieldProps<Parent> = StringConstraints<StringValue, Parent> & { // (2)\n    input?: ComponentType<any>\n    label?: string\n    path?: string\n}\n\nfunction textField<Parent>(props?: TextFieldProps<Parent>) { // (3)\n    return string<StringValue, Parent>({ input: TextField, ...props })\n}\n\nclass Person {\n\n    @textField({ label: \"Name\", required: true })  // (4)\n    name: string | null = null\n}\n\nfunction PersonForm() { // (5)\n    \n    const form = useForm(Person, form => {\n        console.log('Form submitted with values:', form.values)\n        form.setSubmitting(false)\n    })\n\n    return (\n        <Form form={ form } autoComplete=\"off\" noValidate disabled={ form.submitting }>\n            <TextField path=\"name\" label=\"Name\" />\n            <button type=\"submit\">Submit</button>\n        </Form>\n    )\n}\n```\n\n1. 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.\n2. 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.\n3. 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.\n4. 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.\n5. 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.\n\n### Observers\n\nYou 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:\n\n```tsx\nimport { observer, useForm } from '@4riders/reform'\n\nclass Person {\n\n    age: number | null = null\n \n    ＠observer(\"age\", (context) => context.setValue(\n          context.observedValue != null ? (context.observedValue as number) >= 18 : null\n    ))\n    adult: boolean | null = null\n}\n \nconst form = useForm(MyFormModel, () => {})\n```\n\nIn 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.\n\nSee the [observer](functions/observer.html) decorator for more details and options.\n\nNote 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:\n\n```tsx\nconst form = useForm(MyFormModel, () => {})\nuseObservers(MyFormModel, form)\n```\n\n## API Reference\n\nSee [full API reference](modules.html), including all decorators, utilities, and form management hooks.\n\n## Building, Testing, and Publishing\n\nUse the following commands to build, test, and publish the package:\n\n```bash\n$ yarn build # build the library\n$ yarn test # run tests\n$ npm publish # publish to npm\n```\n\n## License\n\nMIT","readmeFilename":"README.md"}