Skip to content

Async Validation Examples

Problem

Implement async validation examples in a production-friendly way with @vielzeug/validit while keeping setup and cleanup explicit.

Runnable Example

The snippet below is copy-paste runnable in a TypeScript project with @vielzeug/validit installed.

Username Availability

ts
import { v } from '@vielzeug/validit';

const UsernameSchema = v
  .string()
  .min(3)
  .refineAsync(async (value) => {
    const exists = await db.users.exists({ username: value });
    return !exists;
  }, 'Username already taken');

const result = await UsernameSchema.safeParseAsync(input.username);

Domain Validation

ts
const CompanyEmailSchema = v
  .string()
  .email()
  .refineAsync(
    async (value) => {
      const domain = value.split('@')[1] ?? '';
      return allowedDomains.has(domain.toLowerCase());
    },
    ({ value }) => `${value} is not an allowed company email`,
  );

Async Object Refinement

ts
const InviteSchema = v
  .object({
    workspaceId: v.string().uuid(),
    email: v.string().email(),
  })
  .refineAsync(async ({ workspaceId, email }) => {
    return !(await db.invites.exists({ workspaceId, email }));
  }, 'Invite already exists for this user and workspace');

await InviteSchema.parseAsync(payload);

Important

Use parseAsync() or safeParseAsync() when a schema contains async refinements.

Expected Output

  • The example runs without type errors in a standard TypeScript setup.
  • The main flow produces the behavior described in the recipe title.

Common Pitfalls

  • Forgetting cleanup/dispose calls can leak listeners or stale state.
  • Skipping explicit typing can hide integration issues until runtime.
  • Not handling error branches makes examples harder to adapt safely.