diff --git a/ata-validator/package.json b/ata-validator/package.json new file mode 100644 index 00000000..f5d1a052 --- /dev/null +++ b/ata-validator/package.json @@ -0,0 +1,18 @@ +{ + "name": "@hookform/resolvers/ata-validator", + "amdName": "hookformResolversAtaValidator", + "version": "1.0.0", + "private": true, + "description": "React Hook Form validation resolver: ata-validator", + "main": "dist/ata-validator.js", + "module": "dist/ata-validator.module.js", + "umd:main": "dist/ata-validator.umd.js", + "source": "src/index.ts", + "types": "dist/index.d.ts", + "license": "MIT", + "peerDependencies": { + "react-hook-form": "^7.55.0", + "@hookform/resolvers": "^2.0.0", + "ata-validator": "^0.7.0" + } +} diff --git a/ata-validator/src/__tests__/Form-native-validation.tsx b/ata-validator/src/__tests__/Form-native-validation.tsx new file mode 100644 index 00000000..f29f6ae0 --- /dev/null +++ b/ata-validator/src/__tests__/Form-native-validation.tsx @@ -0,0 +1,80 @@ +import { render, screen } from '@testing-library/react'; +import user from '@testing-library/user-event'; +import React from 'react'; +import { useForm } from 'react-hook-form'; +import { ataResolver } from '..'; + +type FormData = { username: string; password: string }; + +const schema = { + type: 'object', + properties: { + username: { + type: 'string', + minLength: 1, + }, + password: { + type: 'string', + minLength: 1, + }, + }, + required: ['username', 'password'], + additionalProperties: false, +}; + +interface Props { + onSubmit: (data: FormData) => void; +} + +function TestComponent({ onSubmit }: Props) { + const { register, handleSubmit } = useForm({ + resolver: ataResolver(schema), + shouldUseNativeValidation: true, + }); + + return ( +
+ + + + + +
+ ); +} + +test("form's native validation with ata-validator", async () => { + const handleSubmit = vi.fn(); + render(); + + let usernameField = screen.getByPlaceholderText( + /username/i, + ) as HTMLInputElement; + expect(usernameField.validity.valid).toBe(true); + expect(usernameField.validationMessage).toBe(''); + + let passwordField = screen.getByPlaceholderText( + /password/i, + ) as HTMLInputElement; + expect(passwordField.validity.valid).toBe(true); + expect(passwordField.validationMessage).toBe(''); + + await user.click(screen.getByText(/submit/i)); + + usernameField = screen.getByPlaceholderText(/username/i) as HTMLInputElement; + expect(usernameField.validity.valid).toBe(false); + + passwordField = screen.getByPlaceholderText(/password/i) as HTMLInputElement; + expect(passwordField.validity.valid).toBe(false); + + await user.type(screen.getByPlaceholderText(/username/i), 'joe'); + await user.type(screen.getByPlaceholderText(/password/i), 'password'); + + usernameField = screen.getByPlaceholderText(/username/i) as HTMLInputElement; + expect(usernameField.validity.valid).toBe(true); + expect(usernameField.validationMessage).toBe(''); + + passwordField = screen.getByPlaceholderText(/password/i) as HTMLInputElement; + expect(passwordField.validity.valid).toBe(true); + expect(passwordField.validationMessage).toBe(''); +}); diff --git a/ata-validator/src/__tests__/Form.tsx b/ata-validator/src/__tests__/Form.tsx new file mode 100644 index 00000000..3da951b3 --- /dev/null +++ b/ata-validator/src/__tests__/Form.tsx @@ -0,0 +1,61 @@ +import { render, screen } from '@testing-library/react'; +import user from '@testing-library/user-event'; +import React from 'react'; +import { useForm } from 'react-hook-form'; +import { ataResolver } from '..'; + +type FormData = { username: string; password: string }; + +const schema = { + type: 'object', + properties: { + username: { + type: 'string', + minLength: 1, + }, + password: { + type: 'string', + minLength: 1, + }, + }, + required: ['username', 'password'], + additionalProperties: false, +}; + +interface Props { + onSubmit: (data: FormData) => void; +} + +function TestComponent({ onSubmit }: Props) { + const { + register, + formState: { errors }, + handleSubmit, + } = useForm({ + resolver: ataResolver(schema), + }); + + return ( +
+ + {errors.username && {errors.username.message}} + + + {errors.password && {errors.password.message}} + + +
+ ); +} + +test("form's validation with ata-validator and TypeScript's integration", async () => { + const handleSubmit = vi.fn(); + render(); + + expect(screen.queryAllByRole('alert')).toHaveLength(0); + + await user.click(screen.getByText(/submit/i)); + + expect(screen.queryAllByRole('alert').length).toBeGreaterThan(0); + expect(handleSubmit).not.toHaveBeenCalled(); +}); diff --git a/ata-validator/src/__tests__/__fixtures__/data.ts b/ata-validator/src/__tests__/__fixtures__/data.ts new file mode 100644 index 00000000..e9ec5333 --- /dev/null +++ b/ata-validator/src/__tests__/__fixtures__/data.ts @@ -0,0 +1,134 @@ +import { Field, InternalFieldName } from 'react-hook-form'; + +interface Data { + username: string; + password: string; + email?: string; + birthday?: number; + tags: string[]; + enabled: boolean; + url: string; + like?: { id: number; name: string }[]; + deepObject: { data: string; twoLayersDeep: { name: string } }; +} + +export const schema = { + type: 'object', + properties: { + username: { + type: 'string', + minLength: 3, + maxLength: 30, + pattern: '^\\w+$', + }, + password: { + type: 'string', + minLength: 8, + pattern: '.*[A-Z].*', + }, + email: { + type: 'string', + format: 'email', + }, + birthday: { + type: 'integer', + minimum: 1900, + maximum: 2013, + }, + tags: { + type: 'array', + items: { type: 'string' }, + }, + enabled: { + type: 'boolean', + }, + url: { + type: 'string', + format: 'uri', + }, + like: { + type: 'array', + items: { + type: 'object', + properties: { + id: { type: 'number' }, + name: { type: 'string', minLength: 4, maxLength: 4 }, + }, + required: ['id', 'name'], + }, + }, + deepObject: { + type: 'object', + properties: { + data: { type: 'string' }, + twoLayersDeep: { + type: 'object', + properties: { name: { type: 'string' } }, + additionalProperties: false, + required: ['name'], + }, + }, + required: ['data', 'twoLayersDeep'], + }, + }, + required: ['username', 'password', 'tags', 'enabled', 'deepObject'], + additionalProperties: false, +}; + +export const validData: Data = { + username: 'Doe', + password: 'Password123_', + email: 'john@doe.com', + birthday: 2000, + tags: ['tag1', 'tag2'], + enabled: true, + url: 'https://react-hook-form.com/', + like: [{ id: 1, name: 'name' }], + deepObject: { + data: 'data', + twoLayersDeep: { name: 'deeper' }, + }, +}; + +export const invalidData = { + username: '__', + password: 'invalid', + email: '', + birthday: 'birthYear', + like: [{ id: 'z' }], + url: 'abc', + deepObject: { + data: 233, + twoLayersDeep: { name: 123 }, + }, +}; + +export const invalidDataWithUndefined = { + username: 'jsun969', + password: undefined, + deepObject: { + twoLayersDeep: { + name: 'deeper', + }, + data: undefined, + }, +}; + +export const fields: Record = { + username: { + ref: { name: 'username' }, + name: 'username', + }, + password: { + ref: { name: 'password' }, + name: 'password', + }, + email: { + ref: { name: 'email' }, + name: 'email', + }, + birthday: { + ref: { name: 'birthday' }, + name: 'birthday', + }, +}; diff --git a/ata-validator/src/__tests__/__snapshots__/ata-validator.ts.snap b/ata-validator/src/__tests__/__snapshots__/ata-validator.ts.snap new file mode 100644 index 00000000..9026582f --- /dev/null +++ b/ata-validator/src/__tests__/__snapshots__/ata-validator.ts.snap @@ -0,0 +1,262 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`ataResolver > should return all the error messages from ataResolver when requirement fails 1`] = ` +{ + "errors": { + "deepObject": { + "message": "must have required property 'deepObject'", + "ref": undefined, + "type": "required", + }, + "enabled": { + "message": "must have required property 'enabled'", + "ref": undefined, + "type": "required", + }, + "password": { + "message": "must have required property 'password'", + "ref": { + "name": "password", + }, + "type": "required", + }, + "tags": { + "message": "must have required property 'tags'", + "ref": undefined, + "type": "required", + }, + "username": { + "message": "must have required property 'username'", + "ref": { + "name": "username", + }, + "type": "required", + }, + }, + "values": {}, +} +`; + +exports[`ataResolver > should return all the error messages from ataResolver when some property is undefined 1`] = ` +{ + "errors": { + "deepObject": { + "data": { + "message": "must have required property 'data'", + "ref": undefined, + "type": "required", + }, + }, + "enabled": { + "message": "must have required property 'enabled'", + "ref": undefined, + "type": "required", + }, + "password": { + "message": "must have required property 'password'", + "ref": { + "name": "password", + }, + "type": "required", + }, + "tags": { + "message": "must have required property 'tags'", + "ref": undefined, + "type": "required", + }, + }, + "values": {}, +} +`; + +exports[`ataResolver > should return all the error messages from ataResolver when validation fails and validateAllFieldCriteria set to true 1`] = ` +{ + "errors": { + "birthday": { + "message": "must be integer", + "ref": { + "name": "birthday", + }, + "type": "type", + "types": { + "type": "must be integer", + }, + }, + "deepObject": { + "data": { + "message": "must be string", + "ref": undefined, + "type": "type", + "types": { + "type": "must be string", + }, + }, + "twoLayersDeep": { + "name": { + "message": "must be string", + "ref": undefined, + "type": "type", + "types": { + "type": "must be string", + }, + }, + }, + }, + "email": { + "message": "must match format "email"", + "ref": { + "name": "email", + }, + "type": "format", + "types": { + "format": "must match format "email"", + }, + }, + "enabled": { + "message": "must have required property 'enabled'", + "ref": undefined, + "type": "required", + "types": { + "required": "must have required property 'enabled'", + }, + }, + "like": [ + { + "id": { + "message": "must be number", + "ref": undefined, + "type": "type", + "types": { + "type": "must be number", + }, + }, + "name": { + "message": "must have required property 'name'", + "ref": undefined, + "type": "required", + "types": { + "required": "must have required property 'name'", + }, + }, + }, + ], + "password": { + "message": "must NOT have fewer than 8 characters", + "ref": { + "name": "password", + }, + "type": "minLength", + "types": { + "minLength": "must NOT have fewer than 8 characters", + "pattern": "must match pattern ".*[A-Z].*"", + }, + }, + "tags": { + "message": "must have required property 'tags'", + "ref": undefined, + "type": "required", + "types": { + "required": "must have required property 'tags'", + }, + }, + "url": { + "message": "must match format "uri"", + "ref": undefined, + "type": "format", + "types": { + "format": "must match format "uri"", + }, + }, + "username": { + "message": "must NOT have fewer than 3 characters", + "ref": { + "name": "username", + }, + "type": "minLength", + "types": { + "minLength": "must NOT have fewer than 3 characters", + }, + }, + }, + "values": {}, +} +`; + +exports[`ataResolver > should return single error message from ataResolver when validation fails and validateAllFieldCriteria set to false 1`] = ` +{ + "errors": { + "birthday": { + "message": "must be integer", + "ref": { + "name": "birthday", + }, + "type": "type", + }, + "deepObject": { + "data": { + "message": "must be string", + "ref": undefined, + "type": "type", + }, + "twoLayersDeep": { + "name": { + "message": "must be string", + "ref": undefined, + "type": "type", + }, + }, + }, + "email": { + "message": "must match format "email"", + "ref": { + "name": "email", + }, + "type": "format", + }, + "enabled": { + "message": "must have required property 'enabled'", + "ref": undefined, + "type": "required", + }, + "like": [ + { + "id": { + "message": "must be number", + "ref": undefined, + "type": "type", + }, + "name": { + "message": "must have required property 'name'", + "ref": undefined, + "type": "required", + }, + }, + ], + "password": { + "message": "must NOT have fewer than 8 characters", + "ref": { + "name": "password", + }, + "type": "minLength", + }, + "tags": { + "message": "must have required property 'tags'", + "ref": undefined, + "type": "required", + }, + "url": { + "message": "must match format "uri"", + "ref": undefined, + "type": "format", + }, + "username": { + "message": "must NOT have fewer than 3 characters", + "ref": { + "name": "username", + }, + "type": "minLength", + }, + }, + "values": {}, +} +`; diff --git a/ata-validator/src/__tests__/ata-validator.ts b/ata-validator/src/__tests__/ata-validator.ts new file mode 100644 index 00000000..01eff7f7 --- /dev/null +++ b/ata-validator/src/__tests__/ata-validator.ts @@ -0,0 +1,113 @@ +import { ataResolver } from '..'; +import { + fields, + invalidData, + invalidDataWithUndefined, + schema, + validData, +} from './__fixtures__/data'; + +const shouldUseNativeValidation = false; + +describe('ataResolver', () => { + it('should return values from ataResolver when validation pass', async () => { + expect( + await ataResolver(schema)(validData, undefined, { + fields, + shouldUseNativeValidation, + }), + ).toEqual({ + values: validData, + errors: {}, + }); + }); + + it('should return values from ataResolver when validation pass & raw=true', async () => { + const result = await ataResolver(schema, undefined, { raw: true })( + validData, + undefined, + { + fields, + shouldUseNativeValidation, + }, + ); + + expect(result).toEqual({ errors: {}, values: validData }); + }); + + it('should return single error message from ataResolver when validation fails and validateAllFieldCriteria set to false', async () => { + expect( + await ataResolver(schema)(invalidData, undefined, { + fields, + shouldUseNativeValidation, + }), + ).toMatchSnapshot(); + }); + + it('should return all the error messages from ataResolver when validation fails and validateAllFieldCriteria set to true', async () => { + expect( + await ataResolver(schema)( + invalidData, + {}, + { fields, criteriaMode: 'all', shouldUseNativeValidation }, + ), + ).toMatchSnapshot(); + }); + + it('should return all the error messages from ataResolver when requirement fails', async () => { + expect( + await ataResolver(schema)({}, undefined, { + fields, + shouldUseNativeValidation, + }), + ).toMatchSnapshot(); + }); + + it('should return all the error messages from ataResolver when some property is undefined', async () => { + expect( + await ataResolver(schema)(invalidDataWithUndefined, undefined, { + fields, + shouldUseNativeValidation, + }), + ).toMatchSnapshot(); + }); + + it('should support coerceTypes option', async () => { + const coerceSchema = { + type: 'object', + properties: { + age: { type: 'integer' }, + active: { type: 'boolean' }, + }, + required: ['age', 'active'], + }; + + const result = await ataResolver(coerceSchema, { coerceTypes: true })( + { age: '25', active: 'true' }, + undefined, + { fields, shouldUseNativeValidation }, + ); + + expect(result.errors).toEqual({}); + }); + + it('should support removeAdditional option', async () => { + const strictSchema = { + type: 'object', + properties: { + name: { type: 'string' }, + }, + required: ['name'], + additionalProperties: false, + }; + + const result = await ataResolver(strictSchema, { + removeAdditional: true, + })({ name: 'test', extra: 'field' }, undefined, { + fields, + shouldUseNativeValidation, + }); + + expect(result.errors).toEqual({}); + }); +}); diff --git a/ata-validator/src/ata-validator.ts b/ata-validator/src/ata-validator.ts new file mode 100644 index 00000000..a1af6a87 --- /dev/null +++ b/ata-validator/src/ata-validator.ts @@ -0,0 +1,76 @@ +import { toNestErrors, validateFieldsNatively } from '@hookform/resolvers'; +import { ValidationError, Validator } from 'ata-validator'; +import { FieldError, appendErrors } from 'react-hook-form'; +import { Resolver } from './types'; + +const parseErrorSchema = ( + ataErrors: ValidationError[], + validateAllFieldCriteria: boolean, +) => { + const parsedErrors: Record = {}; + + for (let index = 0; index < ataErrors.length; index += 1) { + const error = ataErrors[index]; + + const instancePath = + error.keyword === 'required' + ? `${error.instancePath}/${error.params.missingProperty}` + : error.instancePath; + + const path = instancePath.substring(1).replace(/\//g, '.'); + + if (!parsedErrors[path]) { + parsedErrors[path] = { + message: error.message, + type: error.keyword, + }; + } + + if (validateAllFieldCriteria) { + const types = parsedErrors[path].types; + const messages = types && types[error.keyword]; + + parsedErrors[path] = appendErrors( + path, + validateAllFieldCriteria, + parsedErrors, + error.keyword, + messages + ? ([] as string[]).concat(messages as string[], error.message || '') + : error.message, + ) as FieldError; + } + } + + return parsedErrors; +}; + +export const ataResolver: Resolver = ( + schema, + schemaOptions, + resolverOptions = {}, +) => { + const validator = new Validator(schema, schemaOptions); + return async (values, _, options) => { + const result = validator.validate(values); + + options.shouldUseNativeValidation && validateFieldsNatively({}, options); + + return result.valid + ? { + values: resolverOptions.raw ? Object.assign({}, values) : values, + errors: {}, + } + : { + values: {}, + errors: toNestErrors( + parseErrorSchema( + result.errors, + !options.shouldUseNativeValidation && + options.criteriaMode === 'all', + ), + options, + ), + }; + }; +}; diff --git a/ata-validator/src/index.ts b/ata-validator/src/index.ts new file mode 100644 index 00000000..b86f12d1 --- /dev/null +++ b/ata-validator/src/index.ts @@ -0,0 +1,2 @@ +export * from './ata-validator'; +export * from './types'; diff --git a/ata-validator/src/types.ts b/ata-validator/src/types.ts new file mode 100644 index 00000000..182d4178 --- /dev/null +++ b/ata-validator/src/types.ts @@ -0,0 +1,16 @@ +import type { ValidationError, ValidatorOptions } from 'ata-validator'; +import { FieldValues, ResolverOptions, ResolverResult } from 'react-hook-form'; + +export type Resolver = ( + schema: object, + schemaOptions?: ValidatorOptions, + resolverOptions?: { + raw?: boolean; + }, +) => ( + values: TFieldValues, + context: TContext | undefined, + options: ResolverOptions, +) => Promise>; + +export type AtaValidationError = ValidationError; diff --git a/bun.lock b/bun.lock index 3c54f718..54f4d134 100644 --- a/bun.lock +++ b/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "@hookform/resolvers", @@ -25,6 +26,7 @@ "ajv-errors": "^3.0.0", "ajv-formats": "^2.1.1", "arktype": "2.0.4", + "ata-validator": "^0.7.3", "check-export-map": "^1.3.1", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", @@ -528,6 +530,8 @@ "asyncro": ["asyncro@3.0.0", "", {}, "sha512-nEnWYfrBmA3taTiuiOoZYmgJ/CNrSoQLeLs29SeLcPu60yaw/mHDBHV0iOZ051fTvsTHxpCY+gXibqT9wbQYfg=="], + "ata-validator": ["ata-validator@0.7.3", "", { "dependencies": { "node-addon-api": "^8.7.0", "node-api-headers": "^1.8.0", "pkg-prebuilds": "^1.0.0" } }, "sha512-v0fxdhKdfFk+klksvlzjja3g+lXjCgXstw4s4niAHry0VfN/XUCUKWMD37BDSuP26v8o9UFpMzR0OZQZzz79dg=="], + "autoprefixer": ["autoprefixer@10.4.21", "", { "dependencies": { "browserslist": "^4.24.4", "caniuse-lite": "^1.0.30001702", "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": { "autoprefixer": "bin/autoprefixer" } }, "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ=="], "available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="], @@ -1016,6 +1020,10 @@ "nice-try": ["nice-try@1.0.5", "", {}, "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ=="], + "node-addon-api": ["node-addon-api@8.7.0", "", {}, "sha512-9MdFxmkKaOYVTV+XVRG8ArDwwQ77XIgIPyKASB1k3JPq3M8fGQQQE3YpMOrKm6g//Ktx8ivZr8xo1Qmtqub+GA=="], + + "node-api-headers": ["node-api-headers@1.8.0", "", {}, "sha512-jfnmiKWjRAGbdD1yQS28bknFM1tbHC1oucyuMPjmkEs+kpiu76aRs40WlTmBmyEgzDM76ge1DQ7XJ3R5deiVjQ=="], + "node-releases": ["node-releases@2.0.19", "", {}, "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw=="], "nope-validator": ["nope-validator@1.0.4", "", {}, "sha512-+ooCIlPpDJbD8H9oz+izVqe28ZO+Km6WCGYJnkm+DOLkAWunhDwxV9PyZ5J2gZ/URFeX3u1Mk+yz7kIra5wayg=="], @@ -1090,6 +1098,8 @@ "pkg-dir": ["pkg-dir@4.2.0", "", { "dependencies": { "find-up": "^4.0.0" } }, "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ=="], + "pkg-prebuilds": ["pkg-prebuilds@1.0.0", "", { "dependencies": { "yargs": "^17.7.2" }, "bin": { "pkg-prebuilds-copy": "bin/copy.mjs", "pkg-prebuilds-verify": "bin/verify.mjs" } }, "sha512-D9wlkXZCmjxj2kBHTw3fGSyjoahr33breGBoJcoezpi7ouYS59DJVOHMZ+dgqacSrZiJo4qtkXxLQTE+BqXJmQ=="], + "possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="], "postcss": ["postcss@8.5.3", "", { "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A=="], diff --git a/config/node-13-exports.js b/config/node-13-exports.js index efab7b13..23257fea 100644 --- a/config/node-13-exports.js +++ b/config/node-13-exports.js @@ -21,6 +21,7 @@ const subRepositories = [ 'vine', 'fluentvalidation-ts', 'standard-schema', + 'ata-validator', ]; const copySrc = () => { diff --git a/package.json b/package.json index 9b66a9ff..448a782f 100644 --- a/package.json +++ b/package.json @@ -129,6 +129,12 @@ "import": "./standard-schema/dist/standard-schema.mjs", "require": "./standard-schema/dist/standard-schema.js" }, + "./ata-validator": { + "types": "./ata-validator/dist/index.d.ts", + "umd": "./ata-validator/dist/ata-validator.umd.js", + "import": "./ata-validator/dist/ata-validator.mjs", + "require": "./ata-validator/dist/ata-validator.js" + }, "./package.json": "./package.json", "./*": "./*" }, @@ -193,7 +199,10 @@ "fluentvalidation-ts/dist", "standard-schema/package.json", "standard-schema/src", - "standard-schema/dist" + "standard-schema/dist", + "ata-validator/package.json", + "ata-validator/src", + "ata-validator/dist" ], "publishConfig": { "access": "public" @@ -221,6 +230,7 @@ "build:vine": "microbundle --cwd vine --globals @hookform/resolvers=hookformResolvers,react-hook-form=ReactHookForm,@vinejs/vine=vine", "build:fluentvalidation-ts": "microbundle --cwd fluentvalidation-ts --globals @hookform/resolvers=hookformResolvers,react-hook-form=ReactHookForm", "build:standard-schema": "microbundle --cwd standard-schema --globals @hookform/resolvers=hookformResolvers,react-hook-form=ReactHookForm,@standard-schema/spec=standardSchema", + "build:ata-validator": "microbundle --cwd ata-validator --globals @hookform/resolvers=hookformResolvers,react-hook-form=ReactHookForm,ata-validator=ataValidator", "postbuild": "node ./config/node-13-exports.js && check-export-map", "lint": "biome check --write --vcs-use-ignore-file=true .", "lint:types": "tsc", @@ -254,7 +264,8 @@ "typeschema", "vine", "fluentvalidation-ts", - "standard-schema" + "standard-schema", + "ata-validator" ], "repository": { "type": "git", @@ -282,6 +293,7 @@ "@vinejs/vine": "^3.0.1", "@vitejs/plugin-react": "^4.3.4", "ajv": "^8.17.1", + "ata-validator": "^0.7.3", "ajv-errors": "^3.0.0", "ajv-formats": "^2.1.1", "arktype": "2.0.4",