mcpField
V
valibot

valibot

github.com/open-circle开发162.1k 次安装今天更新
代码仓库

关于 valibot

Valibot 是用于验证和转换结构化数据的模块化、类型安全 Schema 库。此技能帮助开发者构建 Schema、解析输入,并区分其函数式管道 API 与 Zod 的链式 API。

主要功能

  1. 01为结构化数据构建 Schema
  2. 02通过可组合管道执行验证
  3. 03安全解析或在错误时抛出异常
  4. 04转换并规范化值
  5. 05推断输入和输出类型
  6. 06处理嵌套验证问题

适用场景

  1. 01验证表单或 API 输入
  2. 02转换并规范化解析后的值
  3. 03定义对象、数组或联合 Schema
  4. 04将验证代码从 Zod 迁移

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/open-circle/agent-skills --skill valibot

安装后在 Claude Code 中输入/valibot确认已加载。

附带文件.claude-plugin/plugin.json

详细介绍

Valibot

此技能可帮助你高效使用 Valibot,这是一个用于验证结构化数据的模块化、类型安全的模式库。

何时使用此技能

  • 用户询问如何使用 Valibot 验证模式时
  • 创建或修改 Valibot 模式时
  • 解析或验证用户输入时
  • 用户提到 Valibot、模式或验证时
  • 从 Zod 迁移到 Valibot 时

重要:Valibot 与 Zod 的区别——切勿混淆!

Valibot 和 Zod 的 API 不同,切勿混用!

主要区别
功能Zod ❌Valibot ✅
导入import { z } from 'zod'import * as v from 'valibot'
验证链式方法:.email().min(5)管道:v.pipe(v.string(), v.email(), v.minLength(5))
解析schema.parse(data)v.parse(schema, data)
安全解析schema.safeParse(data)v.safeParse(schema, data)
可选z.string().optional()v.optional(v.string())
可空z.string().nullable()v.nullable(v.string())
默认值z.string().default('x')v.optional(v.string(), 'x')
转换z.string().transform(fn)v.pipe(v.string(), v.transform(fn))
精化/检查z.string().refine(fn)v.pipe(v.string(), v.check(fn))
枚举z.enum(['a', 'b'])v.picklist(['a', 'b'])
原生枚举z.nativeEnum(MyEnum)v.enum(MyEnum)
联合z.union([a, b])v.union([a, b])
判别联合z.discriminatedUnion('type', [...])v.variant('type', [...])
交集z.intersection(a, b)v.intersect([a, b])
最小/最大长度.min(5).max(10)v.minLength(5), v.maxLength(10)
最小/最大值.gte(5).lte(10)v.minValue(5), v.maxValue(10)
推断类型z.infer<typeof Schema>v.InferOutput<typeof Schema>
推断输入z.input<typeof Schema>v.InferInput<typeof Schema>
应避免的常见错误
// ❌ WRONG - This is Zod syntax, NOT Valibot!
const Schema = v.string().email().min(5);
const result = Schema.parse(data);

// ✅ CORRECT - Valibot uses functions and pipelines
const Schema = v.pipe(v.string(), v.email(), v.minLength(5));
const result = v.parse(Schema, data);
// ❌ WRONG - Zod-style optional
const Schema = v.object({
  name: v.string().optional(),
});

// ✅ CORRECT - Valibot wraps with optional()
const Schema = v.object({
  name: v.optional(v.string()),
});
// ❌ WRONG - Zod-style default
const Schema = v.string().default("hello");

// ✅ CORRECT - Valibot uses second argument
const Schema = v.optional(v.string(), "hello");

安装

npm install valibot     # npm
yarn add valibot        # yarn
pnpm add valibot        # pnpm
bun add valibot         # bun

使用通配符导入(推荐):

import * as v from "valibot";

或单独导入:

import { object, string, pipe, email, parse } from "valibot";

心智模型

Valibot 的 API 分为三个主要概念:

1. 模式

模式定义预期的数据类型,是操作的起点。

import * as v from "valibot";

// Primitive schemas
const StringSchema = v.string();
const NumberSchema = v.number();
const BooleanSchema = v.boolean();
const DateSchema = v.date();

// Complex schemas
const ArraySchema = v.array(v.string());
const ObjectSchema = v.object({
  name: v.string(),
  age: v.number(),
});
2. 方法

方法用于使用或修改模式。模式始终是第一个参数。

// Parsing
const result = v.parse(StringSchema, "hello");
const safeResult = v.safeParse(StringSchema, "hello");

// Type guard
if (v.is(StringSchema, data)) {
  // data is typed as string
}
3. 操作

操作会在 pipe() 中验证或转换数据。必须在管道中使用。

// Actions are used in pipe()
const EmailSchema = v.pipe(
  v.string(),
  v.trim(),
  v.email(),
  v.endsWith("@example.com"),
);

管道

管道通过验证和转换操作扩展模式。管道始终以模式开头,后接操作。

import * as v from "valibot";

const UsernameSchema = v.pipe(
  v.string(),
  v.trim(),
  v.minLength(3, "Username must be at least 3 characters"),
  v.maxLength(20, "Username must be at most 20 characters"),
  v.regex(
    /^[a-z0-9_]+$/i,
    "Username can only contain letters, numbers, and underscores",
  ),
);

const AgeSchema = v.pipe(
  v.number(),
  v.integer("Age must be a whole number"),
  v.minValue(0, "Age cannot be negative"),
  v.maxValue(150, "Age cannot exceed 150"),
);
常见验证操作

字符串验证:

  • v.email() — 邮箱格式有效
  • v.url() — URL 格式有效
  • v.uuid() — UUID 格式有效
  • v.regex(pattern) — 匹配正则表达式模式
  • v.minLength(n) — 最小长度
  • v.maxLength(n) — 最大长度
  • v.length(n) — 指定长度
  • v.nonEmpty() — 字符串不能为空
  • v.startsWith(str) — 以指定字符串开头
  • v.endsWith(str) — 以指定字符串结尾
  • v.includes(str) — 包含指定字符串

数字验证:

  • v.minValue(n) — 最小值(>=)
  • v.maxValue(n) — 最大值(<=)
  • v.gtValue(n) — 大于(>)
  • v.ltValue(n) — 小于(<)
  • v.integer() — 必须为整数
  • v.finite() — 必须为有限数
  • v.safeInteger() — 安全整数范围
  • v.multipleOf(n) — 必须是 n 的倍数

数组验证:

  • v.minLength(n) — 最少项数
  • v.maxLength(n) — 最多项数
  • v.length(n) — 指定项数
  • v.nonEmpty() — 至少包含一项
  • v.includes(item) — 包含指定项
  • v.excludes(item) — 不包含指定项
使用 check() 自定义验证
const PasswordSchema = v.pipe(
  v.string(),
  v.minLength(8),
  v.check(
    (input) => /[A-Z]/.test(input),
    "Password must contain an uppercase letter",
  ),
  v.check((input) => /[0-9]/.test(input), "Password must contain a number"),
);
值转换

这些操作会修改值,但不会改变其类型:

字符串转换:

  • v.trim() — 移除首尾空白字符
  • v.trimStart() — 移除开头的空白字符
  • v.trimEnd() — 移除结尾的空白字符
  • v.toLowerCase() — 转换为小写
  • v.toUpperCase() — 转换为大写

数字转换:

  • v.toMinValue(n) — 限定最小值(如果小于 n,则设为 n)
  • v.toMaxValue(n) — 限定最大值(如果大于 n,则设为 n)
const NormalizedEmailSchema = v.pipe(
  v.string(),
  v.trim(),
  v.toLowerCase(),
  v.email(),
);

// Clamp number to range 0-100
const PercentageSchema = v.pipe(v.number(), v.toMinValue(0), v.toMaxValue(100));
类型转换

要在数据类型之间转换,请使用以下内置转换操作:

  • v.toNumber() — 转换为数字
  • v.toString() — 转换为字符串
  • v.toBoolean() — 转换为布尔值
  • v.toBigint() — 转换为 bigint
  • v.toDate() — 转换为 Date
// Convert string to number
const PortSchema = v.pipe(v.string(), v.toNumber(), v.integer(), v.minValue(1));

// Convert ISO string to Date
const TimestampSchema = v.pipe(v.string(), v.isoDateTime(), v.toDate());

// Convert to boolean
const FlagSchema = v.pipe(v.string(), v.toBoolean());
自定义转换

要进行自定义转换,请使用 v.transform():

const DateStringSchema = v.pipe(
  v.string(),
  v.isoDate(),
  v.transform((input) => new Date(input)),
);

// Custom object transformation
const UserSchema = v.pipe(
  v.object({
    firstName: v.string(),
    lastName: v.string(),
  }),
  v.transform((input) => ({
    ...input,
    fullName: `${input.firstName} ${input.lastName}`,
  })),
);

对象架构

基本对象
const UserSchema = v.object({
  id: v.number(),
  name: v.string(),
  email: v.pipe(v.string(), v.email()),
  age: v.optional(v.number()),
});

type User = v.InferOutput<typeof UserSchema>;
对象变体
// Regular object - strips unknown keys (default)
const ObjectSchema = v.object({ key: v.string() });

// Loose object - allows and preserves unknown keys
const LooseObjectSchema = v.looseObject({ key: v.string() });

// Strict object - throws on unknown keys
const StrictObjectSchema = v.strictObject({ key: v.string() });

// Object with rest - validates unknown keys against a schema
const ObjectWithRestSchema = v.objectWithRest(
  { key: v.string() },
  v.number(), // unknown keys must be numbers
);
可选和可空字段
const ProfileSchema = v.object({
  // Required
  name: v.string(),

  // Optional (can be undefined or missing)
  nickname: v.optional(v.string()),

  // Optional with default
  role: v.optional(v.string(), "user"),

  // Nullable (can be null)
  avatar: v.nullable(v.string()),

  // Nullish (can be null or undefined)
  bio: v.nullish(v.string()),

  // Nullish with default
  theme: v.nullish(v.string(), "light"),
});
对象方法
const BaseSchema = v.object({
  id: v.number(),
  name: v.string(),
  email: v.string(),
  password: v.string(),
});

// Pick specific keys
const PublicUserSchema = v.pick(BaseSchema, ["id", "name"]);

// Omit specific keys
const UserWithoutPasswordSchema = v.omit(BaseSchema, ["password"]);

// Make all optional
const PartialUserSchema = v.partial(BaseSchema);

// Make all required
const RequiredUserSchema = v.required(PartialUserSchema);

// Merge objects
const ExtendedUserSchema = v.object({
  ...BaseSchema.entries,
  createdAt: v.date(),
});
跨字段验证
const RegistrationSchema = v.pipe(
  v.object({
    password: v.pipe(v.string(), v.minLength(8)),
    confirmPassword: v.string(),
  }),
  v.forward(
    v.partialCheck(
      [["password"], ["confirmPassword"]],
      (input) => input.password === input.confirmPassword,
      "Passwords do not match",
    ),
    ["confirmPassword"],
  ),
);

数组和元组

数组
const TagsSchema = v.pipe(
  v.array(v.string()),
  v.minLength(1, "At least one tag required"),
  v.maxLength(10, "Maximum 10 tags allowed"),
);

// Array of objects
const UsersSchema = v.array(
  v.object({
    id: v.number(),
    name: v.string(),
  }),
);
元组
// Fixed-length array with specific types
const CoordinatesSchema = v.tuple([v.number(), v.number()]);
// Type: [number, number]

// Tuple with rest
const ArgsSchema = v.tupleWithRest(
  [v.string()], // first arg is string
  v.number(), // rest are numbers
);
// Type: [string, ...number[]]

联合类型和变体

联合类型
const StringOrNumberSchema = v.union([v.string(), v.number()]);

const StatusSchema = v.union([
  v.literal("pending"),
  v.literal("active"),
  v.literal("inactive"),
]);
选项列表(用于字符串/数字字面量)
// Simpler than union of literals
const StatusSchema = v.picklist(["pending", "active", "inactive"]);

const PrioritySchema = v.picklist([1, 2, 3]);
变体(可辨识联合类型)

对于可辨识联合类型,请使用 variant 以获得更好的性能:

const EventSchema = v.variant("type", [
  v.object({
    type: v.literal("click"),
    x: v.number(),
    y: v.number(),
  }),
  v.object({
    type: v.literal("keypress"),
    key: v.string(),
  }),
  v.object({
    type: v.literal("scroll"),
    direction: v.picklist(["up", "down"]),
  }),
]);

解析数据

parse() — 出错时抛出异常
import * as v from "valibot";

const EmailSchema = v.pipe(v.string(), v.email());

try {
  const email = v.parse(EmailSchema, "[email protected]");
  console.log(email); // '[email protected]'
} catch (error) {
  console.error(error); // ValiError
}
safeParse() — 返回结果对象
const result = v.safeParse(EmailSchema, input);

if (result.success) {
  console.log(result.output); // Valid data
} else {
  console.log(result.issues); // Array of issues
}
is() — 类型守卫
if (v.is(EmailSchema, input)) {
  // input is typed as string
}
配置选项
// Abort early - stop at first error
v.parse(Schema, data, { abortEarly: true });

// Abort pipe early - stop pipeline at first error
v.parse(Schema, data, { abortPipeEarly: true });

类型推断

import * as v from "valibot";

const UserSchema = v.object({
  name: v.string(),
  age: v.pipe(v.string(), v.transform(Number)),
  role: v.optional(v.string(), "user"),
});

// Output type (after transformations and defaults)
type User = v.InferOutput<typeof UserSchema>;
// { name: string; age: number; role: string }

// Input type (before transformations)
type UserInput = v.InferInput<typeof UserSchema>;
// { name: string; age: string; role?: string | undefined }

// Issue type
type UserIssue = v.InferIssue<typeof UserSchema>;

错误处理

自定义错误消息
const LoginSchema = v.object({
  email: v.pipe(
    v.string("Email must be a string"),
    v.nonEmpty("Please enter your email"),
    v.email("Invalid email format"),
  ),
  password: v.pipe(
    v.string("Password must be a string"),
    v.nonEmpty("Please enter your password"),
    v.minLength(8, "Password must be at least 8 characters"),
  ),
});
展平错误
const result = v.safeParse(LoginSchema, data);

if (!result.success) {
  const flat = v.flatten(result.issues);
  // { nested: { email: ['Invalid email format'], password: ['...'] } }
}
问题结构

每个问题都包含:

  • kind:'schema' | 'validation' | 'transformation'
  • type:函数名称(例如,'string'、'email'、'min_length')
  • input:有问题的输入
  • expected:预期内容
  • received:实际收到的内容
  • message:易于理解的消息
  • path:嵌套问题的路径项数组

回退值

// Static fallback
const NumberSchema = v.fallback(v.number(), 0);
v.parse(NumberSchema, "invalid"); // Returns 0

// Dynamic fallback
const DateSchema = v.fallback(v.date(), () => new Date());

递归架构

import * as v from "valibot";

type TreeNode = {
  value: string;
  children: TreeNode[];
};

const TreeNodeSchema: v.GenericSchema<TreeNode> = v.object({
  value: v.string(),
  children: v.lazy(() => v.array(TreeNodeSchema)),
});

异步验证

对于异步操作(例如,数据库检查),请使用异步变体:

import * as v from "valibot";

const isUsernameAvailable = async (username: string) => {
  // Check database
  return true;
};

const UsernameSchema = v.pipeAsync(
  v.string(),
  v.minLength(3),
  v.checkAsync(isUsernameAvailable, "Username is already taken"),
);

// Must use parseAsync
const username = await v.parseAsync(UsernameSchema, "john");

JSON Schema 转换

import { toJsonSchema } from "@valibot/to-json-schema";
import * as v from "valibot";

const EmailSchema = v.pipe(v.string(), v.email());
const jsonSchema = toJsonSchema(EmailSchema);
// { type: 'string', format: 'email' }

命名约定

约定 1:名称相同(简洁性优先时推荐)
export const User = v.object({
  name: v.string(),
  email: v.pipe(v.string(), v.email()),
});

export type User = v.InferOutput<typeof User>;

// Usage
const users: User[] = [];
users.push(v.parse(User, data));
约定 2:使用后缀(输入/输出不同时推荐)
export const UserSchema = v.object({
  name: v.string(),
  age: v.pipe(v.string(), v.transform(Number)),
});

export type UserInput = v.InferInput<typeof UserSchema>;
export type UserOutput = v.InferOutput<typeof UserSchema>;

常见模式

登录表单
const LoginSchema = v.object({
  email: v.pipe(
    v.string(),
    v.nonEmpty("Please enter your email"),
    v.email("Invalid email address"),
  ),
  password: v.pipe(
    v.string(),
    v.nonEmpty("Please enter your password"),
    v.minLength(8, "Password must be at least 8 characters"),
  ),
});
API 响应
const ApiResponseSchema = v.variant("status", [
  v.object({
    status: v.literal("success"),
    data: v.unknown(),
  }),
  v.object({
    status: v.literal("error"),
    error: v.object({
      code: v.string(),
      message: v.string(),
    }),
  }),
]);
环境变量
const EnvSchema = v.object({
  NODE_ENV: v.picklist(["development", "production", "test"]),
  PORT: v.pipe(v.string(), v.transform(Number), v.integer(), v.minValue(1)),
  DATABASE_URL: v.pipe(v.string(), v.url()),
  API_KEY: v.pipe(v.string(), v.minLength(32)),
});

const env = v.parse(EnvSchema, process.env);
日期处理
// String to Date
const DateFromStringSchema = v.pipe(
  v.string(),
  v.isoDate(),
  v.transform((input) => new Date(input)),
);

// Date validation
const FutureDateSchema = v.pipe(
  v.date(),
  v.minValue(new Date(), "Date must be in the future"),
);

其他资源

评论

支持 Markdown