{"id":12353,"library":"vest","title":"Vest Form Validation Framework","description":"Vest is a declarative and framework-agnostic JavaScript validation framework that enables developers to write validation logic using a syntax familiar to unit testing frameworks like Jest or Mocha. It aims to separate validation concerns from UI logic, promoting cleaner components and easier testing. Currently stable at version 6.3.2, Vest offers robust features including asynchronous validation support, strong TypeScript type safety, server-side rendering (SSR) compatibility with state hydration, and extensibility for custom rules. It distinguishes itself by managing validation state intelligently, handling dependent fields, and providing a powerful assertion library (`enforce`). The project maintains a consistent release cadence with regular patch and minor updates, and significant architectural shifts between major versions. Vest is designed for complex validation scenarios across various JavaScript environments (React, Vue, Svelte, Angular, Node.js, vanilla JS) and implements the Standard Schema specification.","status":"active","version":"6.3.2","language":"javascript","source_language":"en","source_url":"https://github.com/ealush/vest","tags":["javascript","Form validation","Unit testing","Framework-agnostic","Validation","Declarative","Asynchronous validations","Async validations","Testing","typescript"],"install":[{"cmd":"npm install vest","lang":"bash","label":"npm"},{"cmd":"yarn add vest","lang":"bash","label":"yarn"},{"cmd":"pnpm add vest","lang":"bash","label":"pnpm"}],"dependencies":[],"imports":[{"note":"Vest is primarily designed for ESM. CommonJS `require` can be problematic since v4 deprecated default exports and v6 returns a Suite Object, not a function.","wrong":"const create = require('vest');","symbol":"create","correct":"import { create } from 'vest';"},{"note":"Since Vest v4, default imports were removed for better tree-shaking. Always use named imports for core utilities like `test`.","wrong":"import vest, { test } from 'vest';","symbol":"test","correct":"import { test } from 'vest';"},{"note":"The `enforce` assertion library is a named export. Ensure your environment supports ESM imports.","wrong":"const { enforce } = require('vest');","symbol":"enforce","correct":"import { enforce } from 'vest';"},{"note":"The `memo` utility, for memoizing validation logic, was moved to its own module in v6.","symbol":"memo","correct":"import { memo } from 'vest/memo';"},{"note":"For SSR and hydration, `SuiteSerializer` is imported from a specific subpath.","symbol":"SuiteSerializer","correct":"import { SuiteSerializer } from 'vest/exports/SuiteSerializer';"}],"quickstart":{"code":"import { create, test, enforce } from 'vest';\n\n// Simulate an async API call, e.g., checking if a username exists\nconst doesUsernameExist = async (username: string): Promise<boolean> => {\n  return new Promise(resolve => {\n    setTimeout(() => {\n      resolve(username === 'admin'); // 'admin' is taken\n    }, 500);\n  });\n};\n\ninterface FormData {\n  username?: string;\n  email?: string;\n  password?: string;\n  confirmPassword?: string;\n}\n\nconst signupSuite = create((data: FormData = {}) => {\n  test('username', 'Username is required', () => {\n    enforce(data.username).isNotBlank();\n  });\n\n  test('username', 'Username must be at least 3 characters long', () => {\n    enforce(data.username).longerThanOrEquals(3);\n  });\n\n  // Async validation\n  test('username', 'Username is already taken', async () => {\n    if (data.username) {\n      await enforce(await doesUsernameExist(data.username)).isFalsy();\n    }\n  });\n\n  test('email', 'Email is required', () => {\n    enforce(data.email).isNotBlank();\n  });\n\n  test('email', 'Email must be valid', () => {\n    enforce(data.email).isEmail();\n  });\n\n  test('password', 'Password is required', () => {\n    enforce(data.password).isNotBlank();\n  });\n\n  test('password', 'Password must be at least 8 characters long', () => {\n    enforce(data.password).longerThanOrEquals(8);\n  });\n\n  // Conditional validation: only run if password is valid and present\n  if (data.password && signupSuite.get().hasErrors('password') === false) {\n    test('confirmPassword', 'Passwords do not match', () => {\n      enforce(data.confirmPassword).equals(data.password);\n    });\n  }\n});\n\nasync function validateForm(formData: FormData) {\n  console.log('Validating with data:', formData);\n  const result = await signupSuite.run(formData);\n\n  console.log('Validation Results:');\n  console.log('  isValid:', result.isValid());\n  console.log('  hasErrors:', result.hasErrors());\n  console.log('  getErrors:', result.getErrors());\n  console.log('  getWarnings:', result.getWarnings());\n\n  if (!result.isValid()) {\n    console.log('Validation failed!');\n  } else {\n    console.log('Validation passed!');\n  }\n}\n\n// Example usage\nvalidateForm({ username: '', email: 'test@example.com', password: 'password123', confirmPassword: 'password123' });\nvalidateForm({ username: 'john', email: 'john@example.com', password: 'short', confirmPassword: 'short' });\nvalidateForm({ username: 'admin', email: 'admin@example.com', password: 'password123', confirmPassword: 'password123' });\nvalidateForm({ username: 'newUser', email: 'new@example.com', password: 'supersecurepassword', confirmPassword: 'supersecurepassword' });","lang":"typescript","description":"This quickstart demonstrates how to define a Vest validation suite with synchronous and asynchronous tests, including conditional logic, and how to execute the suite and interpret its results for a typical signup form."},"warnings":[{"fix":"Refactor `const result = suite(data);` to `const result = suite.run(data);`. Similarly, `suite.reset()` and `suite.get()` are now methods of the suite object.","message":"In Vest v6, `create` now returns a `Suite Object` with methods like `.run()`, `.reset()`, and `.get()`, instead of directly returning a callable function. Direct calls to the suite function (e.g., `suite(data)`) will result in a TypeError.","severity":"breaking","affected_versions":">=6.0.0"},{"fix":"Replace `promisify(suite)` and `suite(data).done(callback)` with `await suite.run(data)` for async operations.","message":"The `promisify` utility and the `.done()` callback on the result object were removed in Vest v6. The `suite.run()` method now returns a Promise-like object, simplifying asynchronous handling.","severity":"breaking","affected_versions":">=6.0.0"},{"fix":"Change `import { test } from 'vest'; test.memo(...)` to `import { test } from 'vest'; import { memo } from 'vest/memo'; memo(() => { test(...) }, [dependencies]);`.","message":"The `test.memo` API was promoted to a top-level `memo` export in Vest v6 and must be imported from `vest/memo`. It now wraps a block of logic, not just individual tests.","severity":"breaking","affected_versions":">=6.0.0"},{"fix":"Always use named imports: `import { create, test, enforce } from 'vest';`.","message":"Vest v4 removed default import support (e.g., `import vest from 'vest'`) for better tree-shaking. All core Vest utilities like `create`, `test`, and `enforce` must be explicitly named imports.","severity":"breaking","affected_versions":">=4.0.0"},{"fix":"Use Vest's `skipWhen` utility for conditional test execution to maintain correct state tracking: `skipWhen(condition, () => { test(...) });`.","message":"Using standard `if/else` statements to conditionally run tests within a Vest suite can lead to unpredictable behavior and out-of-sync test results, as Vest relies on the consistent order of execution for test state management.","severity":"gotcha","affected_versions":">=4.0.0"},{"fix":"Refactor internal `only(fieldName)` calls within the suite definition to external `suite.only('fieldName').run(formData)` or `suite.focus({ only: 'fieldName' }).run(formData)`.","message":"In Vest v6, focusing or skipping specific fields or groups (e.g., `only`, `skip`) is handled by methods on the `Suite Object` (e.g., `suite.only('fieldName').run(data)`) rather than by passing arguments to `only()` or `skip()` inside the suite callback.","severity":"gotcha","affected_versions":">=6.0.0"}],"env_vars":null,"last_verified":"2026-04-19T00:00:00.000Z","next_check":"2026-07-18T00:00:00.000Z","problems":[{"fix":"Call the `.run()` method on the suite object: `const result = mySuite.run(data);`.","cause":"Attempting to call the result of `create` directly as a function, which was the behavior in Vest v5, but `create` returns a Suite Object in v6.","error":"TypeError: suite is not a function"},{"fix":"Ensure `test` is imported as a named export: `import { create, test, enforce } from 'vest';`.","cause":"`test` was not correctly imported as a named export. This often happens if attempting a default import (e.g., `import vest from 'vest'`) or if `test` is simply missing from the named import.","error":"ReferenceError: test is not defined"},{"fix":"Ensure the field exists or add conditional checks before enforcing: `if (data.username) { enforce(data.username).isNotBlank(); }` or handle `undefined` within your schema definition.","cause":"The `enforce` assertion was called with an `undefined` value, usually when `data.fieldName` is missing or `null` in the input, and Vest attempts to apply an assertion that expects a defined value.","error":"Cannot read properties of undefined (reading 'isNotBlank')"},{"fix":"Always `await` the result when working with async validations: `const result = await mySuite.run(data);`.","cause":"Forgetting to `await` the result of `suite.run()` when the suite contains asynchronous tests, or not handling the Promise-like nature of the v6 `SuiteResult` object.","error":"Asynchronous validation is not waiting for completion or result is premature."}],"ecosystem":"npm"}