Skip to content
gitstqPublic

About

🧩 Zero-dependency TypeScript-first schema validation toolkit with bilingual (en/zh-CN) errors, CN national-ID checksum formats, coercion, async refinements, JSON Schema export & CLI. 零依赖 TS 优先校验库,内建中英文错误信息与中文证件格式。

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

🧩 validkit

零依赖 · TypeScript 优先的代码级 Schema 声明与运行时校验工具库

语言 / Language:简体中文 · 繁體中文 · English

License: MIT Runtime Dependencies TypeScript Node Tests


🎉 项目介绍

validkit 是一个代码优先(code-first)的模式声明与运行时数据校验工具库:用一套链式 API 描述数据形状,它同时帮你完成运行时校验、类型推断、输入纠偏与错误信息国际化。

它解决什么痛点?

  • 🫠 类型只在编译期存在:TypeScript 的 interface 编译后消失,来自接口、表单、URL 参数、本地存储的数据依然可能是任意形状。
  • 🌍 错误信息要重复造轮子:主流校验库默认只有英文,面向中文用户的产品需要手写 errorMap,繁琐且容易遗漏。
  • 🧾 中文场景格式校验缺失:手机号之外,二代身份证、统一社会信用代码等带国家标准校验位的格式很少有库内建。
  • 🧳 依赖体积焦虑:表单、BFF、SDK 都要校验,没人想为它引入一棵庞大的依赖树。

自研差异化亮点 🌟

亮点 说明
🌐 内建双语错误信息 英文 / 简体中文开箱即用,支持全局切换、单次解析切换、按错误码自定义模板,无需手写 errorMap
🧾 中文格式全家桶 大陆手机号、二代身份证(GB 11643-1999 MOD11-2 校验位)、统一社会信用代码(GB 32100-2015 MOD31 校验位)、邮编、QQ 号
🪶 真·零运行时依赖 dependencies 为空,ESM + CJS 双产物 + .d.ts 类型声明,Node ≥16 与现代浏览器通用,可 Tree-shaking
🔌 四种消费姿势 parse(抛异常)/ safeParse(结果对象)/ isValid(类型守卫)/ assert(断言函数),另支持异步 refineAsync
📤 JSON Schema 导出 一行 v.toJsonSchema() 导出 Draft-07,便于对接 OpenAPI、表单渲染器等生态
🖥️ CI 友好 CLI validkit fmt email a@b.com 退出码即结果,Shell 脚本与流水线可直接使用

💡 灵感来源:本项目的产品逻辑参考了 GitHub Trending 上流行的 TypeScript 校验库 zod(MIT),但全部代码独立自研实现,并在错误信息国际化、中文格式校验、双包工程化等方向做了差异化设计,未复制其任何源码。


✨ 核心特性

  • ✅ 完整的标量类型:string / number / boolean / bigint / date / literal / enum / nativeEnum / null / undefined / any / unknown / never
  • 🧱 丰富的复合类型:object / array / tuple / union / intersection / record / map / set,对象支持 strict / passthrough / strip / partial / pick / omit / extend
  • ⛓️ 链式组合能力:optional / nullable / nullish / default / refine / refineAsync / transform / preprocess / custom / instanceOf
  • 🎯 精确的错误定位:嵌套对象、数组下标自动形成 user.addresses[0].city 形式的错误路径,默认收集全部错误,也可 abortEarly 快速失败
  • 🧮 Unicode 感知长度:字符串长度按码点(code point)计算,"你好".length === 2,不会把 Emoji/中文算错
  • 🔄 输入纠偏(coercion):表单字符串一键转 number / boolean / date / bigint
  • 📐 静态类型推断:Infer<typeof schema> 自动推导输出类型,default/transform 也能正确反映到类型上
  • 🧪 82 项单元测试全部通过,覆盖正常路径、边界条件、国家标准校验位与 CLI

🚀 快速开始

环境要求

  • Node.js ≥ 16.0.0(CLI 与 CJS/ESM 引入);浏览器端可通过任意打包器(Vite / webpack / esbuild)使用
  • TypeScript 项目建议 TS ≥ 5.0(纯 JS 项目无需 TypeScript)
  • 运行时第三方依赖:0 个

安装

npm install validkit
# 或
pnpm add validkit
yarn add validkit

30 秒示例

import { v, type Infer } from 'validkit';

// 1️⃣ 声明模式
const User = v.object({
  name: v.string().min(2, '姓名至少 2 个字符'),
  age: v.number().int().min(0).max(150),
  email: v.string().email(),
  phone: v.string().cnMobile().optional(),
  role: v.enum(['admin', 'member'] as const).default('member'),
});

// 2️⃣ 推导 TS 类型,无需重复维护 interface
type User = Infer<typeof User>;

// 3️⃣ 校验
const result = User.safeParse({ name: '小琳', age: 28, email: 'lin@example.com' });
if (result.success) {
  console.log(result.data); // { name: '小琳', age: 28, email: ..., role: 'member' } ← default 自动补全
} else {
  console.log(result.issues); // 结构化错误数组
}

在命令行中快速校验

npx validkit fmt email ada@example.com        # PASS,退出码 0
npx validkit fmt cnMobile 13800138000          # PASS
npx validkit fmt ipv4 127.0.0.1 10.0.0.1 bad   # 2 PASS / 1 FAIL,退出码 1
npx validkit formats                           # 列出全部内建格式

📖 详细使用指南

1. 四种校验姿势

const Age = v.number().int().min(0);

Age.parse(20);                 // ✅ 返回 20
Age.parse('x');                // ❌ 抛出 ValidationError

Age.safeParse('x');           // { success: false, issues: [...], error }
Age.isValid(20);              // true(类型守卫:number)
Age.assert(someUnknown);      // asserts 类型守卫,失败抛错

2. 字符串:规则、归一化与内建格式

v.string()
  .trim()                       // 先去空白
  .toLowerCase()                // 再转小写
  .min(3).max(20)               // 长度(按 Unicode 码点)
  .pattern(/^[a-z0-9_]+$/)      // 正则
  .startsWith('id_');

// 内建格式(部分)
v.string().email();
v.string().url();
v.string().uuid();
v.string().ipv4();
v.string().ipv6();
v.string().datetime();          // ISO 8601
v.string().creditCard();        // Luhn 校验,自动忽略空格/连字符
v.string().cnMobile();          // 中国大陆手机号
v.string().cnIdCard();          // 二代身份证 + GB11643 校验位 + 出生日期合法性
v.string().cnCreditCode();      // 统一社会信用代码 + GB32100 校验位
v.string().cnPostal();          // 邮政编码
v.string().qq();
v.string().hexColor();
v.string().macAddress();
v.string().numeric();
v.string().alphanum();

每条规则都支持传入自定义消息作为最后一个参数,例如 v.string().min(6, '密码至少 6 位')。

3. 数字 / 布尔 / 日期 / 大整数

v.number().int().positive().multipleOf(5);
v.number().coerce();            // 允许把 "42" 转成 42(表单场景)
v.boolean().coerce();           // 'true'/'1'/1 → true,'false'/'0'/0 → false
v.bigint().min(0n).max(10n);
v.date().min(new Date('2026-01-01'));
v.date().coerce();              // ISO 字符串 / 时间戳 → Date

4. 对象与数组

const Order = v.object({
  id: v.string().uuid(),
  items: v.array(
    v.object({ sku: v.string(), qty: v.number().int().min(1) }),
  ).min(1),
  coupon: v.string().optional(),
});

// 未知键策略
v.object({ a: v.string() });                 // 默认 strip:剔除多余键
v.object({ a: v.string() }).strict();        // strict:出现多余键直接报错
v.object({ a: v.string() }).passthrough();   // passthrough:原样保留

// 对象派生
Order.partial();      // 全部字段可选(PATCH 场景)
Order.pick(['id']);   // 挑选字段
Order.omit(['coupon']);
Order.extend({ memo: v.string() });

5. 联合、交叉、元组、记录

v.union([v.literal('text'), v.object({ kind: v.literal('img'), url: v.string().url() })]);
v.intersection(
  v.object({ a: v.string() }),
  v.object({ b: v.number() }),
);
v.tuple([v.string(), v.number()]);           // 定长、位置类型固定
v.record(v.number());                         // Record<string, number>
v.record(v.number(), v.string().uuid());      // 自定义键模式

6. 自定义校验、异步校验与转换

// 同步 refine
const Even = v.number().refine((n) => n % 2 === 0, '必须是偶数');

// 异步 refineAsync(需要 parseAsync / safeParseAsync)
const AvailableName = v.string().refineAsync(async (name) => {
  const res = await fetch(`/api/check?name=${name}`);
  return (await res.json()).available;
}, '用户名已被占用');

await AvailableName.safeParseAsync('lobster');

// transform:改变输出类型
v.string().transform((s) => s.trim().length);

// preprocess:解析前先处理输入
v.preprocess((i) => (typeof i === 'string' ? i.trim() : i), v.string().min(2));

7. 表单纠偏(Coercion)

const Form = v.object({
  keyword: v.coerce.string(),
  page: v.coerce.number().int().min(1),
  agree: v.coerce.boolean(),
  at: v.coerce.date(),
  amount: v.coerce.bigint(),
});
// URLSearchParams / FormData 取出的全是字符串,可直接交给 Form.parse

8. 🌐 错误信息国际化

// 方式一:单次解析指定
v.string().email().safeParse('bad', { locale: 'zh-CN' });
// → "value 必须是合法的电子邮箱"

// 方式二:全局切换
v.setLocale('zh-CN');

// 方式三:覆盖某个错误码的模板(字段名通过 {field} 注入)
v.object({ email: v.string().email() }).safeParse(raw, {
  messages: {
    required: (m) => `【${m.field}】不能为空`,
    invalid_type: (m) => `【${m.field}】类型错误,应为 ${m.expected}`,
  },
});

错误对象结构(可直接渲染到表单字段旁):

interface Issue {
  code: 'required' | 'invalid_type' | 'too_small' | 'invalid_format' | ...;
  path: (string | number)[];  // 例如 ['items', 0, 'qty']
  message: string;
  meta?: Record<string, string | number | boolean>;
}

9. 📤 导出 JSON Schema(Draft-07)

v.toJsonSchema(v.object({
  id: v.string().uuid(),
  age: v.number().int().min(0).optional(),
  email: v.string().email().nullable(),
}));
// {
//   type: 'object',
//   required: ['id', 'email'],
//   properties: {
//     id: { type: 'string', format: 'uuid' },
//     age: { type: 'integer', minimum: 0 },
//     email: { type: ['string', 'null'], format: 'email' }
//   }
// }

10. 典型场景:React 表单校验

const LoginForm = v.object({
  account: v.string().min(1, '请输入账号'),
  password: v.string().min(8, '密码至少 8 位'),
});

function onSubmit(e) {
  const r = LoginForm.safeParse(values, { locale: 'zh-CN', abortEarly: true });
  if (!r.success) setError(r.issues[0].message);
}

🖼️ 演示动图/截图占位:docs/demo.gif(终端与表单场景的实际运行效果将在此展示)。


💡 设计思路与迭代规划

设计理念

  1. 代码即单一事实来源(Single Source of Truth):一份链式声明同时产出运行时校验逻辑与 TS 静态类型,杜绝“接口定义写两遍、两边对不上”。
  2. 默认友好,而非默认裸奔:错误信息、中文格式、类型收窄这些 90% 项目都需要的能力直接内建。
  3. 零依赖即安全与稳定:没有供应链风险、没有版本冲突、没有安装陷阱;库本身可被浏览器、Node、BFF、CLI 任意场景嵌入。
  4. 错误结构化优先:错误不是拼好的字符串而已,code + path + meta 结构化输出,UI 渲染、日志分析、国际化二次加工都方便。

技术选型理由

  • TypeScript 7 strict:从实现侧保证 API 类型严谨;const 类型参数保证字面量不被宽化。
  • ESM + CJS 双产物:exports 条件导出同时服务现代打包器与老项目 CommonJS;类型声明单独输出到 dist/types。
  • Node 内置 node:test:测试框架也保持零依赖,npm run verify 一条命令完成构建、类型自检、单测与双模块冒烟。

🗺️ 迭代路线图

  • v1.1:v.errorMap() 支持日语 / 韩语等更多 locale;错误信息支持模板插值扩展
  • v1.2:v.email() 国际化域名(Punycode)、v.string().byteLength() 字节长度
  • v1.3:JSON Schema 导入(Draft-07 → validkit 模式),与导出形成闭环
  • v1.4:Deno / Bun / Edge Runtime 官方兼容测试矩阵
  • v2.0:插件化自定义类型注册表与性能基准(benchmark)报告

社区贡献方向

🐛 边界 Bug 报告 · 🌐 新 locale 翻译 · 🧾 更多国家/地区的证件与号码格式 · 📚 框架集成示例(Vue / Solid / Fastify / Hono)。


📦 打包与部署指南

validkit 属于工具库(library),无需下载可执行文件,安装后直接引入即可。

从源码构建

git clone https://github.com/gitstq/validkit.git
cd validkit
npm install        # 仅安装开发依赖(typescript / @types/node)
npm run build      # 产出 dist/esm、dist/cjs、dist/types
npm run verify     # 构建 + 类型自检 + 82 项单测 + ESM/CJS/CLI 冒烟

构建产物说明:

路径 内容 用途
dist/esm/ ESM 模块(含 package.json: {"type":"module"}) import / 现代打包器
dist/cjs/ CommonJS 模块 + 可执行 CLI require / npx validkit
dist/types/ .d.ts 类型声明 TypeScript 智能提示与类型推断

在各类环境集成

// ESM / TypeScript(推荐)
import { v } from 'validkit';
// CommonJS
const { v } = require('validkit');
<!-- 浏览器:经 Vite/esbuild/webpack 打包后使用,无任何运行时外部依赖 -->

兼容环境

环境 支持情况
Node.js ≥ 16.0.0(CJS / ESM 均测试通过)
现代浏览器 Chrome / Edge / Firefox / Safari 最近两个大版本(经打包器)
TypeScript ≥ 5.0(类型推断体验最佳)
Deno / Bun 可通过 npm 兼容层引入(官方矩阵在路线图中)

常见问题(FAQ)

Q:和 zod 是什么关系? A:产品形态受 zod 启发,但代码 100% 独立自研;差异点是内建中英双语、中文证件格式、零依赖双包与内置 CLI。

Q:为什么字符串长度对中文是正确的? A:长度按 Unicode 码点(Array.from(str).length)计算,而不是 UTF-16 code unit,Emoji 也不会被算成 2。

Q:异步校验为什么没生效? A:refineAsync 返回 Promise,需要使用 parseAsync / safeParseAsync 等待结果。

Q:default(() => value) 里的函数会被当成默认值本身吗? A:不会。函数会被当作工厂调用(惰性求值),与 zod 行为一致;若确实需要默认值是一个函数,请再包一层 () => () => fn。


🤝 贡献指南

欢迎 PR!流程与规范详见 CONTRIBUTING.md:

  1. Fork 并新建 feat/xxx 分支;
  2. npm install 后开发,新增能力必须配套单元测试;
  3. 提交信息遵循 Angular 规范:feat: / fix: / docs: / refactor: / test: / chore:;
  4. 提交前执行 npm run verify,确保构建、类型、测试、冒烟全绿。

Issue 反馈请提供最小复现;安全问题请勿公开 Issue。


📄 开源协议

本项目基于 MIT License 开源,可自由用于个人与商业项目,保留版权声明即可。

⭐ 如果 validkit 帮你少写了一个校验工具函数,欢迎点个 Star 支持!

About

🧩 Zero-dependency TypeScript-first schema validation toolkit with bilingual (en/zh-CN) errors, CN national-ID checksum formats, coercion, async refinements, JSON Schema export & CLI. 零依赖 TS 优先校验库,内建中英文错误信息与中文证件格式。

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages