npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@seayoo-web/validator

v2.1.2

Published

javascript data validator

Readme

Validator 数据类型校验器

基本用法1:用已有类型约束验证器编写

// 导入定义工具
import { v } from "@seayoo-web/validator";

// 假设类型定义如下
const enum Gender {
    Male: "male"
    Female: "female"
}
interface User {
    name: string
    gender: Gender
}

// 定义类型校验器,传入泛型参数进行检查约束
const sch = v.object<User>({
    name: v.string().disallow(""),
    gender: v.string().enum(Gender.Male, Gender.Female)
})

// 检查数据是否符合要求
if (sch.validate("some unknown data")) {
    // ...
}

// 也可以将校验器转成类型守卫函数
const isUser = v.guard(sch);
if(isUser("some unknown data")) {
    // ...
}

// 如果为了获得类型守卫函数,可以使用内置的 objectGuard
import { v, objectGuard } from "@seayoo-web/validator"
const isUser = objectGuard<User>({
    name: v.string().disallow(""),
    gender: v.string().enum(Gender.Male, Gender.Female)
})

基本用法2:直接定义验证器并推导类型

import { v, type InferType } from "@seayoo-web/validator"

const sch = v.object({
    name: v.string().disallow(""),
    gender: v.string().enum("male", "female")
})

type User = InferType<typeof sch>
// type User = { name: string; gender: "male" | "female"; }

功能简介

  1. 通过 ts 类型定义约束 validator 的编写
  2. 通过无约束的 validator 自动推导 ts 类型
  3. 支持 Fastify 的 schema 验证(详见下方描述)

通过上述功能,使得

  1. 运行时的类型检查和 ts 定义尽可能保持严格一致,降低类型错误导致的潜在问题
  2. 减少中间临时类型定义和守卫函数编写,保持代码精简高效

基本概念

类型校验器 validator

校验器是一个 class,提供条件输入和 validate 功能来检查一个传入的数据是否符合特性类型定义要求。validate 函数返回了一个类型谓词

类型守卫 guard

类型守卫是一个 ts 概念,通过类型守卫检查的数据符合类型谓词约束,达到了类型收窄的目的。我们这里特指的是 类型守卫函数 其定义为 function<T>(data: unknown) => data is T

数据类型

除了基本数据类型外,以下几组数据类型需要注意区别:

数组 array 和 元组 tuple

这两个数据类型本质上都是数组,区别在于 array 长度不固定且所有元素类型都一致,tuple 的长度固定,元素类型没有约束。比如所有用户数据 User[] 就是数组,而坐标点 [number, number] 就是元组。

对象 object 和记录 record

原则上 object 囊括了大多数 js 复杂类型,在多数的场景下,我们其实默认的是 plain object 即一个没有原型链或原型链为 null纯对象,而其他 object 类型的数据类型都会称呼它们自己特有的名字,比如日期 Date 或者 函数 Function

record 是一个特殊的 plain object,其 ts 签名为 type Record<K extends keyof any, V> = {[P in K]: T},即一个拥有属于 string | number | symbol 索引的对象,其元素类型全部相同。在本工具中 record 定义跟 ts 略有区别,比如 Record<"a"|"b", number> 在 ts 中是合法的 Record,但在本工具范畴中,它属于 object,更确切的表述为:

  • record :含有 string number symbol 至少一个类型作为索引且元素类型一致的 plain object
  • object:不是 record 且具有有限数量索引的 object,其元素类型没有一致性约束;

联合对象 union 和枚举 enum

联合类型是特殊的类型组合,而枚举是一组特殊字面量的组合且已经不被推荐使用

在本工具中,没有对 enum 的直接支持,在 StringValidator NumberValidatorBigintValidator 中提供 enum 方法来检查数据的可选范围实现类似的约束。而 union 定义跟 ts 定义相同,只不过由于 nullundefined 过于常用,这两个类型与其他类型的联合由对应的校验器实现:即所有 validator 都支持 optionalmaybeNull 两个方法以支持和 undefined null 的联合。

具体来说:

  • 如果需要使用枚举,可以使用 const enumobject as const 来替代,点击这里查看。在本工具中按照实际枚举的类型来设定校验器;
  • union 类型校验由 UnionValidator 来检查,常用的 nullundefined 由各个校验器自行检查;

校验器通用方法

所有校验器支持以下方法:

check(value: unknown): true | TypeCheckError | TypeCheckError[]

检查一个数据,返回检查结果:ture表示检查通过,其他表示有一个或多个错误

convert(value: unknown): unknown

尝试将入参的值转化为目标类型,其中联合类型校验器和自定义校验器原封返回参数,如果参数为 null / undefined 也原封返回。

validate(value: unknown): data is T

检查一个数据,返回当前验证器对应的类型断言

其他通用方法

除了 v.unknownv.never 外的验证器都支持以下通用方法:

optional()

设置数据为可选,即数据支持 undefined

maybeNull()

设置数据可空,即数据支持 null

clone()

验证器是对象实例,属于引用型数据,任何调用都会修改原数据,为了获取一个配置相同的验证器,可以调用 clone 方法,其中 clone 方法不会复制 optional / maybeNull 的设置:

const s1 = v.object({ name: v.string() });
s1.validate(null); // false

const s2 = s1.maybeNull();
s2.validate(null); // true
s1.validate(null); // true ⚠️ 这可能不是预期的行为!!

// 可以使用 clone 方法来获取一个新校验器,并忽略 optional / maybeNull 的配置
// 原来的校验器过滤条件状态不受影响
const s3 = v1.clone();
s3.validate(null); // false
s3.validate(undefine); // false

lock()

同上原因,验证器是引用型数据,当不希望验证器被意外修改时,可以锁定验证器,锁定后的验证器将不再接受任何验证条件的变更:

const s1 = v.number().enum(1, 2).lock();
s1.validate(1); // true

type S1Type = InferType<typeof s1>; // 锁定后仅仅剩余部分方法可用 { clone, validate, check, convert }

StringValidator

字符串校验器,用法示例:

const s1 = v.string(); // string
const s2 = v.string().optional(); // string | undefined
const s3 = v.string().maybeNull(); // string | null

// 所有校验器都提供 validate 方法来验证传入的数据是否符合要求
s1.validate("aa"); // true
s1.validate(undefined); // false
s2.validate(undefined); // true
s3.validate(undefined); // false

// 后续校验器的 optional / maybeNull 都是相同的,且可以和过滤条件方法连写,比如
v.string()
  .url()
  .optional()
  .disallow("http://127.0.0.1", /^http:\/\/localhost(?:\d+)?/i);

// 字符串格式校验
v.string().url().dataUri(); // 允许 url 或 dataURI 格式的字符串
v.string().iosTime(); // 允许 ios 时间字符串,比如 2025-03-28T00:38:31.516Z
v.string().disallow("", /^\s+$/); // 不允许空字符串和空格字符串,支持正则表达式
v.string().pattern(/^[a-z][a-z\d]{3,}$/); // 自定义字符串验证正则
v.string().enum("A", "B", "C"); // 自定义可选字符串,设置后其他过滤条件将失效

// 如果需要校验模板字符串类型,可以这么做
const t1 = v.string().pattern<`${number}`>("NumberString", /^\d+$/);
const t2 = v.string().pattern<`${string}@seayoo.com`>("SeayooAccount", /.+@seayoo\.com$/);

NumberValidator

数字类型校验器,默认情况下内置了条件:在 [-2^53+1, 2^53-1] 且不允许 Nan 和 Infinity。示例如下:

v.number(); // 在安全整数内,且不允许 NaN 和 Infinity
v.number().optional().maybeNull(); // 可选且可为 null

v.number().min(1).max(10).disallow(1); // 在 (1, 10] 范围内的数字
v.number().min(0).integer(); // 大于等于 0 的安全整数
v.number().allowNaN().allowInfinity(); // 允许任意 number
v.number().unsafe(); // 允许超过安全整数范围外的数字,比如一个超大的浮点数,但不允许 Infinity
v.number().enum(1, 2, 3); // 仅仅允许 1 2 3,设置后其他过滤条件将失效

BigIntValidator

BigInt 类型校验器,示例如下:

v.bigint().optional().maybeNull(); // BigInt | undefined | null
v.bigint().min(1n).max(10n).disallow(2n); // [1, 2) (2, 10]
v.bigint().enum(2n, 4n, 6n); // 仅允许 2 4 6,设置后其他过滤条件将失效

BooleanValidator

布尔类型校验器,示例如下:

v.bool();
v.bool().optional().maybeNull();

ObjectValidator

对象类型校验器,示例如下:

import { type InferType, v } from "@seayoo-web/validator"

const o1 = v.object({
    name: v.string(),
    id: v.number().min(1).integer(),
    address: v.object({
        city: v.string(),
        code: v.string(),
    }).optional()
}).maybeNull()

// 上述校验器对应的类型为
// null | { name: string, id: number, address?: { city: string, code: string } }
// InferType 工具用于读取验证器守卫数据的类型
type UserInfoWithNull = InferType<typeof o1>
// 可以手工剔净
type UserInfo = NonNullable<UserInfoWithNull>

o1.validate({ name: "Jack", id: 1 }) // true
o1.validate({ name: "Mark", id: 0 }) // false

// 如果对已有类型关联校验器,可以传入泛型参数
// 假设类型定义如下
const enum Gender {
    Male: "male"
    Female: "female"
}
interface User {
    name: string
    gender: Gender
}
// 设定泛型参数后可以自动检查验证器编写是否正确
const o2 = v.object<User>({
    name: v.string().disallow(""),
    gender: v.string().enum(Gender.Male, Gender.Female)
})

o2.validate({ name: "", gender: Gender.Male }) // false
o2.validate({ name: "Jack", gender: "unknown" })  // false

// 如果需要约束数据是 plain object
v.object({ ... }).plain()

// 如果需要检查某一个字段是否匹配
const m = v.object({ type: v.string().enum("A", "B") })
m.match("type", "A") // true
m.match("type", "C") // false

// 当需要复用验证器定义,有两个方法
// 方法1,单独保存 objectShape, v.shape 是用于定义 ObjectValidator 参数的辅助工具
const objShape = v.shape<DataType>({ ... })
const o3 = v.object(objShape).optional()
const o4 = v.object(objShape).maybeNull()
// 方法2,使用 clone 方法,💡clone 方法不会复制 optional / maybeNull 的配置
const o5 = v.object<DataType>({ ... }).optional()
const o6 = o5.clone().maybeNull()

对象验证器是使用频次较高的一个类型,为了获取对应的类型守卫函数,有四个方法,这些方法都支持泛型参数以指定要校验的类型,然后用来约束验证器的编写:

// 方法一
const o1 = v.object({ ... }) // or: v.object<T>({ ... })
const guard1 = o1.validate.bind(o1)

// 方法二,推荐
const guard2 = v.guard(v.object({ ... })) // or: v.guard<T>(v.object({ ... }))

// 方法三,推荐,此方法会强制约束为 plain object
const guard3 = typedObjectGuard({ ... }) // or: typedObjectGuard<T>({ ... })

// 方法四
const o4 = v.object({ ... })
const guard4 = function(data: unknown): data is InferType<typeof o4>{
   return o4.validate(data)
}

在定义多个相似的对象时,可以通过 v.shape 来定义公共对象的校验器:

const userBaseInfoShape = v.shape<UserBaseInfo>({
  name: v.string(),
  gender: v.number().enum(1, 2),
});
const availableUser = v.object<UserBaseInfo & { status: "available"; score: number }>({
  ...userBaseInfoShape,
  status: v.string().enum("available"),
  score: v.number(),
});
const removedUser = v.object<UserBaseInfo & { status: "removed"; removeAt: string }>({
  ...userBaseInfoShape,
  status: v.string().enum("removed"),
  removeAt: v.string().iosTime(),
});

// 也可以通过 clone 方法来复用定义配置
const userMaybeNull = availableUser.clone().maybeNull();

// 也可以从定义好的验证器中提取 shape(浅层副本)
const userShape = availableUser.shape;

空对象检测

// 导入内置的 EmptyObject / EmptyObjectValidator 工具

import { type EmptyObject, EmptyObjectValidator } from "@seayoo-web/validator";

// 另有空对象检测方法,见 RecordValidator

RecordValidator

Record 类型校验器,默认输出类型为 string Record,即 Record<string, T>

const r1 = v.record(v.number()); // 对应数据类型为 Record<string, number>
r1.validate({ a: 1 }); // true
r1.validate({ a: 1, b: "2" }); // false

// 支持 optional / maybeNull
const r2 = v.record(v.number()).maybeNull();
r2.validate(null); // true

// 可以设置元素类型来约束验证器
const r3 = v.record<number>(v.number());
// 也可以使用辅助工具 v.define 来定义,注意泛型参数不一样
// 此时可以指定 Record 的 key 类型 💡运行时不检查任何 key 的类型
const r4 = v.define<Record<string, number>>(v.record(v.number));

// 支持 clone 方法复制配置 💡clone 方法不会复制 optional / maybeNull 的配置
const r5 = r2.clone();
r5.validate(null); // false

同 ObjectValidator 相似,可以有多种方法来获取 record 的类型守卫,以下是推荐用法:

import { typedRecordGuard } from "@seayoo-web/validator";

const guard1 = v.guard<Record<string, number>>(v.record(v.number()));
const guard2 = typedRecordGuard<number>(v.number());

:::alert

注意,RecordValidator 运行时并不对 record key 做任何检查。record key 的类型仅仅在 ts 状态中生效,可以通过 v.define v.guard 来设定 record key 的类型。

:::

空对象检测

const emptyRecordValidator = v.record(v.never()); // 对应的数据类型为 Record<string, never>

// 另有内置的 EmptyObject 和 EmptyObjectValidator 工具

ArrayValidator

数组类型校验器,示例如下

const ar1 = v.array(v.number()) // 对应数据类型为 number[]
ar1.validate([]) // true
ar1.validate([1, 2, "3"]) // false

// 支持 optional / maybeNull
const ar2 = v.array(v.number()).maybeNull()
ar2.validate(null) // true

// 可以限制数组的长度
v.array(...).min(1).max(100) // 最少有一个元素,最多不超过100个元素
// 也可以 min == max
v.array(...).min(2).max(2) // 即约束数组的长度必须是 2

// 可以通过设置元素类型来约束验证器编写
const ar3 = v.array<number>(v.number())
// 也可以通过 v.define 来约束,注意泛型类型
const ar4 = v.define<number[]>(v.array(v.number()))

// 支持 clone 方法复制配置 💡clone 方法不会复制 optional / maybeNull 的配置
const ar5 = ar2.clone()
ar2.validate(null) // false

同 ObjectValidator 相似,可以有多种方法来获取 array 的类型守卫,以下是推荐用法

import { typedArrayGuard } from "@seayoo-web/utils";

const guard1 = v.guard<number[]>(v.array(v.number()));
const guard2 = typedArrayGuard<number>(v.number());

TupleValidator

元组类型校验器,示例如下

const t1 = v.tuple(v.number(), v.string()); // 对应数据类型为 [number, string]
t1.validate([1, "2"]); // true
t1.validate([1, "2", 3]); // false
t1.validate(["1", "2"]); // false

// 支持 optional / maybeNull
const t2 = v.tuple(v.number(), v.string()).maybeNull();
t2.validate(null); // true

// 💡与 object 不同,tuple 类型的泛型参数不是数据类型本身,而是校验器 validator
import { type InferValidator } from "@seayoo-web/utils";
const t3 = v.tuple<[InferValidator<number>, InferValidator<string>]>(v.number(), v.string());

// 可以通过 v.define 来简化定义
const t4 = v.define<[number, string]>(v.tuple(v.number(), v.string()));

// 可以通过 v.guard 获取类型守卫
const guard = v.guard<[number, string]>(v.tuple(v.number(), v.string()));

// 支持 clone 方法复制配置 💡clone 方法不会复制 optional / maybeNull 的配置
const t5 = t2.clone();
t5.validate(null); // false

UnionValidator

联合类型校验器,其中 null undefined 的处理由各个验证器独立实现,UnionValidator 主要负责对其他类型联合的处理。示例如下

const student = v.object({ name: v.string(), classRoom: v.number() });
const teacher = v.object({ name: v.string(), subject: v.string() });

// 对应数据类型为 {name: string, classRoom: number} | {name: string, subject: string}
const schoolMember = v.union(student, teacher);

// 支持 optional / maybeNull
const u1 = v.union(v.number(), v.string()).maybeNull();
u1.validate(null); // true

//💡与 object 不同,union 类型的泛型参数不是数据类型本身,而是校验器 validator
// 可以通过 v.define 进行类型约束
const u2 = v.define<number | string>(v.union(v.number(), v.string()));

// 可以通过 v.guard 获取类型守卫
const guard = v.guard<string | number>(v.union(v.number(), v.string()));

// 支持 clone 方法复制配置 💡clone 方法不会复制 optional / maybeNull 的配置
const u3 = u1.clone();
u3.validate(null); // false

由于 ts 的推导限制,从 uniontuple 的推导顺序是不固定的,就导致无法直接从 union 类型来约束 validator 的编写:

const s = v.define<string | number | bigint>(v.union(v.string(), v.number(), v.bigint()));
// 如果从 string | number | bigint 推导出 [StringValidator, NumberValidator, BigIntValidator] 的话上述代码就是类型检测通过的,因为 v.union 方法的参数顺序刚好跟推导出的类型一致
// 不幸的是,从 string | number | bigint 可能推导出 [BigIntValidator, StringValidator, NumberValidator] 这时 v.union 的参数顺序就跟推导出的类型不一致了,哪怕运行时逻辑是正确的

为了解决这一问题,UnionValidator 不再尝试从 union 推导 tuple,而是将 union 作为整体返回。这又引发另外一个问题:ts 允许比定义类型更窄的值进行赋值:

const s = v.object<{ name: string | number | bigint }>({
  name: v.union(v.string(), v.number()),
});
// 上述代码中 name 被推导为 UnionValidator<string | number | bigint>,其赋值的类型为 UnionValidator<string | number>
// 上述代码在 ts 的赋值逻辑中完全是正确的,这显然不符合我们对验证器的定义:不能多,不能少也不能错

为了解决上述问题,UnionValidator 引入 satisfies 方法,通过泛型参数进一步反向(假设从类型推导验证器为正向)进行类型约束:

type NameType = string | number | bigint;
const s = v.object<{ name: NameType }>({
  name: v.union(v.string(), v.number()).satisfies<NameType>(), // 设置后将检测 union 类型,不符合则进行错误提示
});

添加 satisfies 并提供设置泛型后,上述代码将被提示错误,在补足 v.bigint 后错误消失。

CustomValidator

自定义类型校验器,用于上述校验器无法覆盖的场景,通过自定义类型守卫来实现验证,示例如下:

function isSeayooHost(data: unknown): data is `${string}.seayoo.com` {
  return typeof data === "string" && data.endsWith(".seayoo.com");
}
const c = v.custom("SeayooHost", isSeayooHost);
c.validate("www.seayoo.com"); // true

自定义类型可以嵌入到 ObjectValidator / RecordValidator / ArrayValidator / TupleValidator / UnionValidator 中用以实现各种复杂类型的处理和校验。这是一个兜底的方案,大多数场景应该用不到它。

UnknownValidator

校验一个 unknown 类型数据,实际上 validate 没有任何逻辑,直接返回 true

const s = v.unknown();
s.validate(""); // validate 验证任何数据结果都是 true

NeverValidator

校验一个 never 类型数据,实际上 validate 没有任何逻辑,直接返回 false

const s = v.never();
s.validate(""); // validate 验证任何数据结果都是 false

Fastify 集成

可以使用本工具把 IDataValidator 与 Fastify 的类型系统进行集成,达到在编译期和运行时都能受益的效果。

主要导出(可直接从包中导入):

  • FastifyValidatorCompiler:用于 app.setValidatorCompiler(...),将 IDataValidator 转为 Fastify 可识别的校验函数。
  • TypeProviderOfFastify:TypeScript 类型提供器,配合 app.withTypeProvider<TypeProviderOfFastify>() 使用以获得类型推导。
  • FastifyPluginCallback / FastifyPluginAsync:按本库类型推导的插件签名。
  • FastifyInstance:带本类型校验器的 FastifyInstance 类型别名。

简化示例:

import Fastify from "fastify";
import { v, FastifyValidatorCompiler } from "@seayoo-web/validator";
import type { TypeProviderOfFastify } from "@seayoo-web/validator";

const app = Fastify().setValidatorCompiler(FastifyValidatorCompiler).withTypeProvider<TypeProviderOfFastify>();

app.get(
  "/test",
  {
    schema: {
      querystring: v.object({
        name: v.string().optional(),
      }),
    },
  },
  function (req, reply) {
    // 此处 req.query 的类型会由 TypeProviderOfFastify 推断
    return { name: req.query.name };
  },
);