Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

davanchen/easytype

Open more actions menu

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

欢迎使用EasyType:一个基于TypeScript的动态类型反射系统

众所周知JavaScript因为语言的特性,无法与JAVA一样提供一种动态类型反射机制,而市面上又缺乏完善的解决方案,EasyType的出现是为了从根本上解决这个问题, 赋予开发者尤其是后端开发者更多的能力。

警告:单元测试未完全覆盖,切勿用于商业项目。
开源只是为了交流技术、不想把一个好的理念埋没在个人手里,由于个人时间关系,本项目可能不会得到良好的维护,期望有成熟的公司或者团队能够改进或者重构它,让他成为node后端必备框架之一。

项目起源

从18年开始,我就决定让团队使用node+typescript来开发后端服务,经过一年多的实践,发现各种库都有一套自己的“建模语言”来申明类型,比如mongoose、数据验证器、GraphQL、GRPC、swagger等等。这不禁让我迷惑不解,为什么用了typescript以后还要重复写这么多的类型申明?于是我从19年初开始开发了这个框架陆续来实现。刚开始是通过AST来分析mongoose的schema生成模型接口和定义,但是没有从根本上解决问题,所以接下来是通过直接分析typescript的类申明来实现,后续又引入了json-schema标准,最后对枚举、方法、联合类型、泛型都提供了支持。

设计目标

  • 能够覆盖typescript绝大多数的类型,尤其是对泛型能提供完善的支持。
  • 尽可能的减少侵入,无需改动任何的代码
  • 支持Transpile模式,无需构建即可直接运行,而且编译速度非常快
  • 能够通过cli运行与构建项目,也能够脱离cli运行或者构建项目

方案对比

项目 对比
io-ts 定义了一套类型声明,设计目标可能主要是解决IO传输中的编码与解码
class-transformer 定义了一套修饰器,只支持部分的TS类型
typescript-json-schema 需要调用CLI生成JSON格式的类型声明,非动态
tsruntime 是和typescript-json-schema一样在TypeCheck阶段实现,因此不支持Transpile模式
type-reflect 类似,但功能不够完善

使用场景

引入EasyType将为你的后端开发带来更大的想象空间,其中我们团队用到的部分就包括:

  • 不用添加任何代码,将TS类申明直接转换成mongoose schema
  • 不用添加任何代码,可以直接使用各种json-schema数据验证器
  • 不用添加任何代码,动态生成API文档,稍微改动就能生成OpenAPI规范的swagger文档,你的接口改动团队其他成员可以随时看到。
  • 不用添加任何代码,动态生成RPC声明,比如一键生成GRPC proto申明文件,把微服务开发变成一件很轻松的事情。
  • 由于后端能够输出完整的类型申明,因此前端(尤其是后台开发)能够快速的构建出数据显示、操作界面,会使开发变得更快速高效。

原理

通过typescript的自定义transform,在编译阶段把类型写入类描述中,最后在运行时生成json-schema标准的类型说明。

@Reflectable()
export class User extends Document {
    /** 用户ID */
    uid: number;

    /** 用户名 */
    username: string;
}

比如以上代码,通过编译器将变为:

export class User extends Document {
    $easy.IsObject({
        $type: 1,
        $properties: {
            uid: {
                $type: 2,
                $description: "\u7528\u6237ID",
                $ref: Number
            },
            username: {
                $type: 2,
                $description: "\u7528\u6237\u540D",
                $ref: String
            },
        },
        $target: User,
        $id: "User",
        $extends: mongoose_1.Document
    })
    private $metadata: any;
}

通过在运行时调用 Schema.getMetadata(User), 即可得到User的类型声明(json-schema):

{
    "type": "object",
    "properties": {
        "_id": {
            "type": "string",
            "format": "OBJECT_ID",
            "description": "ID"
        },
        "uid": {
            "type": "number",
            "description": "用户ID",
        },
        "username": {
            "type": "string",
            "description": "用户名",
        }
    },
    "required": [
        "_id",
        "uid",
        "username"
    ],
    "$id": "User",
    "$x": "Document"
}

更强大的ENUM

在typescript中enum的存在更像是为了描述类型(你在运行时很难分清哪个是key,哪个是value),这也就不能与JAVA一样提供一些操作, 因此EasyType在编译阶段增加一些方法,使之变得更加灵活和强大,借助这些特性我们就能够实现无缝输出ENUM信息到API文档,而无需再书写任何的注释或者代码。

export interface EnumInterface<T> {
    readonly keys: string[];

    readonly values: number[] | string[];

    getValue(key: string): Undefinable<T>;

    hasValue(value: any): boolean;

    getKeys(value: any): string[];

    getKey(value: any): Undefinable<string>;

    hasKey(key: string): boolean;

    getDescription(key: string): Undefinable<string>;
}

export type Enum<T = any, V = number | string> =
    { readonly [P in keyof T]: T[P]; }
    & Readonly<EnumInfo>
    & EnumInterface<V>
    ;

现在你可以通过 Enum<Foo>.Keys 和 Enum<Bar>.values 获得键值,也可以拿到对应的像这样的类型申明:

    {
        "name": "AssetType",
        "description": "用户资产类型",
        "fields": [
            {
                "key": "BALANCE",
                "value": 1,
                "description": "账户余额"
            },
            {
                "key": "POINTS",
                "value": 3,
                "description": "账户积分"
            }
        ]
    },

部分继承

使用关键词extends会继承基类所有的属性,有时候如果你想部分继承基类属性,可以使用Inherits语法糖:

@Reflectable()
export class UserLoginDto implements Inherits<User> {
    username: string;
    password: string;
}

方法反射

方法的注释、修饰符、参数、返回值等信息会被标注到Metadata中,可以通过 Reflect.getMetadata('easy:metadata', target, propertyKey) 获取。

还有哪些问题?

  • 还没来得及做完整的单元测试,所以暂时不能用于商业项目
  • 泛型目前编译器这块已完成,但是运行时由于时间关系还未能提供支持
  • 由于设计原因,只能支持类的输出,不支持interface和type的输出,因为前者在js运行时以function存在,后者不存在于运行时,或许后面会想办法支持。
  • 低版本的TypeScript(3.7以下)会有一些问题,所以用最新的TSC编译吧。

插播一条广告

即将推出基于EasyType+nest.js的全家桶开发包,尽情期待。 项目地址: https://github.com/davanchen/easynest

演示:借助vscode插件(即将开源)一键生成proto

演示:EasyNest API文档模块自动生成API描述(枚举、控制器、模型)

About

Runtime type system based on typescript.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published
Morty Proxy This is a proxified and sanitized view of the page, visit original site.