mcpField
V
valibot

valibot

github.com/open-circle開発162.1k 回インストール今日更新
リポジトリ

valibot について

Valibotは、構造化データの検証と変換に使う、モジュール型で型安全なスキーマライブラリです。このスキルはスキーマ作成や入力解析を支援し、関数ベースのパイプラインAPIとZodのチェーンAPIの混同を防ぎます。

主な機能

  1. 01構造化データのスキーマ作成
  2. 02組み合わせ可能なパイプライン検証
  3. 03安全な解析とエラー時の例外処理
  4. 04値の変換と正規化
  5. 05入力型と出力型の推論
  6. 06ネストした検証問題の処理

活用シーン

  1. 01フォームやAPI入力の検証
  2. 02解析値の変換と正規化
  3. 03オブジェクト、配列、ユニオンのスキーマ定義
  4. 04Zodから検証コードを移行

インストール

コマンドをコピーしてターミナルで実行します。

$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())
Nullablez.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))
Refine/Checkz.string().refine(fn)v.pipe(v.string(), v.check(fn))
Enumz.enum(['a', 'b'])v.picklist(['a', 'b'])
ネイティブ Enumz.nativeEnum(MyEnum)v.enum(MyEnum)
Unionz.union([a, b])v.union([a, b])
判別可能な Unionz.discriminatedUnion('type', [...])v.variant('type', [...])
Intersectionz.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 は、次の 3 つの主な概念に分かれています。

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() — 1つ以上の要素がある
  • 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
);
オプションフィールドとNull許容フィールド
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: ['...'] } }
}
Issueの構造

各Issueには次の項目が含まれます。

  • kind:'schema' | 'validation' | 'transformation'
  • type:関数名(例:'string'、'email'、'min_length')
  • input:問題のある入力
  • expected:期待された値
  • received:受け取った値
  • message:人が読めるメッセージ
  • path:ネストされたIssueのパス項目の配列

フォールバック値

// 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"),
);

追加リソース

コメント