Skip to content

lixiangio/typea

Repository files navigation

typea

功能强大的 JS 运行时数据验证与转换器,使用全镜像的对称数据结构模型,轻量级、简单、直观、易于读写。

Typea 中的很多类型概念引用自 TypeScript,相关概念请参考 TypeScript 文档

特性

  • 支持 string、number、boolean、object、array、function、symbol、null、undefined、any 等常见基础类型;

  • 支持 Tuple Types 元组类型,为数组内的每个子元素提供精确的差异化类型匹配;

  • 支持在 Array / Tuple 结构体中使用 [ ...type ] 扩展运算符语法定义类型,匹配零个或多个连续的同类型元素;

  • 支持在 Object 结构体中使用 { ...type } 扩展运算符语法定义类型,匹配零个或多个同类型的可选属性;

  • 支持 Union Types 联合类型,匹配多个类型声明中的一个;

  • 支持 partial(type)required(type)pick(type, key)omit(type, key) 类型转换函数;

  • 支持 Literal Types 字面量赋值匹配,可满足模糊匹配与精准匹配的双重需求;

  • 支持 Optional Properties 可选属性,使用 optional( type ) 函数代替 TS 的 name? 属性修饰符;

  • 支持 Index Signatures 索引签名,使用 [ $index ] 为动态属性添加类型约束;

  • 支持对象、数组递归验证,只需要按数据结构建模即可,不必担心数据层级深度问题;

  • 支持数据就近、集中处理,减少碎片化代码,通过分布在节点上的 set 方法可合成新的数据结构;

  • 对象属性命名安全、无冲突,模型中的所有类型声明均使用唯一的 symbol 类型标识,没有类似 type 的特殊保留关键字;

  • 拥有足够的容错能力,在验证期间通常不需要使用 try/catch 来捕获异常,返回的 path 路径信息可快速定位错误节点;

  • 轻量级、支持按需扩展自定义数据类型,实现最小化集成。

Example

import { Schema, createType, object, number, string, boolean } from "typea";
import { union, partial } from "typea/utility";

// 按需添加扩展类型
import Email from "typea/email.js";
import MobilePhone from "typea/mobilePhone.js";

const email = createType("email", Email);
const mobilePhone = createType("mobilePhone", MobilePhone);

// 创建镜像数据模型

const category = object({
  id: number,
  name: string
});

const categorys = [...category]; // 包含多个 category 的数组

category.childs = categorys; // 循环引用,递归验证 (注意!如果验证数据中也同样存在循环引用,会导致无限循环)

const schema = new Schema({
  id: number,
  name: string,
  email,
  mobilePhone,
  categorys,
  union: union(number, "hello", null, [...number], undefined), // Union 联合类型
  url: [string], // 单次匹配
  link: [...string], // 连续的零次或多次匹配,类似于 TS 中的 string[]
  list: [string, ...string], // 一次或多次 string 子匹配
  array: [...number, boolean], // 多类型扩展匹配
  tuple: [string, Number, { name: string }, function () { }, () => { }], // 多类型固定匹配
  user: {
    username: "莉莉", // Literal 字面量
    age: number({ max: 200 }),
    address: optional([{ city: String }, { city: "母鸡" }]),
  },
  map: { ...number },
  methods: {
    open() { }, // func 类型
  },
  description: string({ optional: true }), // 可选属性
  ...string, // 索引签名,扩展后赋值为 { [indexKey]: string },作用等同于 TS 类型声明 [name: string]: string
});

// 使用数据模型校验数据

const { error, value } = schema.verify({
  id: 123,
  name: "test",
  email: "gmail@gmail.com",
  mobilePhone: "18666666666",
  union: 100,
  url: ["https://github.com/"],
  list: ["a", "b", "c"],
  link: ["https://github.com/", "https://www.google.com/"],
  array: [1, 6, 8, 12, true],
  categorys: [
    {
      id: 1,
      name: "dog",
      childs: [
        {
          id: 13,
          name: "d2",
          childs: [
            {
              id: 12,
              name: "lili",
              childs: [],
            },
          ],
        },
      ],
    },
    {
      id: 2,
      name: "cat",
      childs: [],
    },
  ],
  tuple: [
    "hello word",
    123,
    { name: "lili" },
    function (v) {
      return v++;
    },
    () => { },
  ],
  title: "hello",
  user: {
    username: "莉莉",
    age: 99,
    address: [{ city: "黑猫" }, { city: "母鸡" }],
  },
  map: {
    a: 123,
    b: 456,
    c: 1000,
  },
  methods: {
    open(v) {
      return v + 1;
    },
  },
  string1: "s1",
  string2: "s2",
  string3: "s3",
});

if (error) {
  console.error(error);
} else {
  console.log(value);
}

Install

npm install typea

类型

基础类型大小写兼容(推荐使用小写类型),如类型声明 string、string() 、String 等效,扩展类型不支持大小写混用。

大写不需要通过声明就可以直接使用,好处是使用方便,缺点是不支持传参,仅适用于简单的基础类型声明。

小写的好处是可以通过函数传参的方式,添加更丰富的类型扩展选项,实现更高级的数据校验功能。

模型

模型是可复用的静态类型结构体,通常只需要创建一次即可,作用与 TS 中的 interface、 type 相似。

输入参数

  • node any - 数据结构镜像表达式;

  • dataany - 待验证的数据,支持任意数据类型;

返回值

返回值是基于约定的对象结构,error 和 data 属性不会同时存在,验证成功返回 data,验证失败返回 error 和 msg

  • dataany - 经过验证、处理后导出数据,仅保留 options 中定义的数据结构,未定义的部分会被忽略。内置空值过滤,自动剔除对象、数组中的空字符串、undefind 值。

  • error string - 验证失败时返回的错误信息,包含错误的具体位置信息,仅供开发者调试使用

类型函数的通用选项

  • optional boolean - 可选属性,当值为 true 时允许存在未定义属性。

  • defaultany - 属性不存在时填充默认值,在使用 default 时,optional 会自动设为 true。

  • set function - 赋值函数,用于对输入值处理后再输出赋值,函数中 this 指向原始数据 data。使用 set 时, optional 会自动设为 true。

专用选项

针对不同的数据类型,会有不同的可选参数,选项如下

string
  • min number - 限制字符串最小长度

  • max number - 限制字符串最大长度

  • reg RegExp - 正则表达式

  • in string[] - 匹配多个可选值中的一个

number

内置类型转换,允许字符串类型的纯数字

  • min number - 限制最小值

  • max number - 限制最大值

  • in number[] - 匹配多个可选值中的一个

array
  • min number - 限制数组最小长度

  • max number - 限制数组最大长度

附加常见数据类型

typea 库中包含了以下常见类型,默认不引用,推荐按需扩展。

import { createType } from "typea";
import date from "typea/date.js";
import email from "typea/email.js";
import mobilePhone from "typea/mobilePhone.js";
import mongoId from "typea/mongoId.js";

createType(date.name, date);
createType(email.name, email);
createType(mobilePhone.name, mobilePhone);
createType(mongoId.name, mongoId);
email

验证 email

mobilePhone

验证手机号

mongoId

验证 mongodb 中的 ObjectId

自定义数据类型

typea 中仅内置了少量常见的数据类型,如果不能满足需求,可以通过 createType 方法搭配 validator 等第三方库自行扩展。

当定义的数据类型已存在时则合并,新的验证函数会覆盖内置的同名验证函数。

createType(name, options)

  • name function, symbol, string - 类型名称(必选)

  • options object - 类型选项(必填)

    • type(data, options) function - 数据类型验证函数(必选)

      • data any - 待验证数据

      • options any - 验证表达式或数据类型

    • [$name](data, options) function - 自定义验证函数(可选)

createType("int", {
  type(data) {
    if (Number.isInteger(data)) {
      return { data };
    } else {
      return { error: "必须为 int 类型" };
    }
  },
  min(data, min) {
    if (data < min) {
      return { error: `不能小于${min}` };
    } else {
      return { data };
    }
  },
});

参考示例

// 数组验证

import { Schema, string, number  } from "typea";

const numberAllowNull = number({ optional: true });

const schema = new Schema({
  a: [string],
  b: [numberAllowNull],
  c: [{ a: Number, b: Number }],
  d: [
    {
      d1: 666,
      d2: string,
    },
    Number,
    [
      {
        xa: Number,
        xb: [numberAllowNull],
      },
    ],
    String,
  ],
  e: Array,
});

const { error, value } = schema.verify({
  a: ["dog", "cat"],
  b: [123, 456, 789],
  c: [{ a: 1 }, { a: 2 }, { b: "3" }],
  d: [
    {
      d1: 666,
      d2: "888",
    },
    999,
    [
      {
        xa: 1,
        xb: [1, 2, 3],
      },
      {
        xa: 9,
        xb: [2, 4, 3],
      },
    ],
    "hello",
  ],
  e: [1, 2, 3],
});
// 对象验证

const sample = {
  a: {
    a1: 1,
    a2: 12,
  },
  b: 99,
  f(a, b) {
    return a + b;
  },
};

import { Schema, number } from "typea";

const { error, value } = new Schema({
  a: {
    a1: number({ optional: true }),
    a2: 12,
  },
  b: 99,
  f(func, set) {
    set(func(1, 1));
  },
}).verify(sample);
// 扩展数据类型

import { Schema, createType } from "typea";

const int = createType("int", {
  type(data) {
    if (Number.isInteger(data)) {
      return { data };
    } else {
      return { error: "必须为 int 类型" };
    }
  },
});

const { error, value } = new Schema({ age: int }).verify({ age: 20 });

About

Powerful JS Runtime Data Validation and Converters

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published