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 🙏

© 2024 – Pkg Stats / Ryan Hefner

nstarter-entity

v0.3.0

Published

nstarter 标准传输对象实体化插件

Downloads

30

Readme

nstarter-entity

nstarter-entity 面向 nstarter Typescript 工程中,标准传输对象实体化的场景,提供了标准的结构管理方式,支持对实体对象进行结构定义,生成并实现结构校验。

安装

npm install -S nstarter-entity

实体定义需要使用 typescript-json-schema 作为生成标准 JSON Schema 的工具。需要将其作为开发依赖 (devDependencies) 安装到最终使用的工程中,用于在开发及构建过程中,基于 Typescript 定义生成。

npm install -D typescript-json-schema

https://github.com/YousefED/typescript-json-schema

使用方式

实体结构定义

对于实体结构的定义过程,仅需基于 AbstractEntity 基类定义新的自定义实体对象,即可实现新的实体对象定义。属性定义过程中,可以使用 typescript-json-schema 支持的 jsDoc 注释关键字,进行 Typescript 类型定义以外的 JSON Schema 规则的指定。Typescript 默认常规语法行为,如可选属性、联合类型等,能够被默认自动识别。

  • 普通实体结构

    import { AbstractEntity } from 'nstarter-entity';
      
    export class DemoEntity extends AbstractEntity {
        /**
         * @minimum 0
         * @type integer
         */
        width: number;
      
        /**
         * @minimum 0
         * @type integer
         * @multipleOf 2
         */
        height: number;
      
        /**
         * 信息
         * @default {}
         */
        meta: {
            description?: string
        }
    }

除了常规的实体结构定义之外,实体结构也支持相互之间的嵌套封装,允许使用实体类本身,作为属性类型。

  • 嵌套实体结构

    嵌套场景下,支持子属性实例类型,数组 / 对象子属性的实例类型嵌套。

    export class WrapperEntity extends AbstractEntity {
        /**
         * @minItems 1
         */
        @entityAttr(DemoEntity)
        items: DemoEntity[];
      
        @entityAttr()
        item: DemoEntity;
      
        @entityAttr(DemoEntity)
        itemMap?: {
            [key: string]: DemoEntity
        };
    }

    另外,对于子属性的类型定义,也支持使用 any 类型,并且能够自动在结构校验过程保留相关属性。

    export class AnyItemEntity extends AbstractEntity {
        anyItem: any;
      
        anyMap?: {
            [key: string]: any
        };
    }

💡 小技巧:

除了常规 JSON Schema 注释关键字以外,对于需要隐藏的属性,或者 getter 方法,可以通过 @ignore 注释来标注被 JSON Schema 排除。特别是扩展类属性的 getter 方法,默认也会被包含在 JSON Schema 的结构内,但是在实体对象参数的场景下,往往需要排除相关方法生成的对象属性。

实体对象实例化

针对不同的场景,实体对象提供了不同的方式供对象实例化过程使用。

  • 安全实例化

    实体对象本身默认提供了安全实例化的方式,在创建对象时,直接传入参数即可完成对象初始化过程。

    此种方式在实例化对象创建过程中,会自动校验输入参数的结构是否符合 JSON Schema 的设计规定。对于不满足结构规约的参数,会抛出对应的 ValidationError 对象返回详细的错误校验信息。

    // 普通
    const foo = new DemoEntity({
        width: 1,
        height: 2
    });
      
    // 嵌套实例化
    const bar = new WrapperEntity({
        items: [{
           width: 1,
           height: 2
        }],
        item: {
           width: 3,
           height: 4
        }
    });

    此外,也可以使用实体对象的 fromJSON 方法,对空对象实例初始化。

    // 普通
    const foo = new DemoEntity().fromJSON({
        width: 1,
        height: 2
    });
      
    // 嵌套实例化
    const bar = new WrapperEntity().fromJSON({
        items: [{
           width: 1,
           height: 2
        }],
        item: {
           width: 3,
           height: 4
        }
    });
  • 非安全实例化

    对于入参为可信环境,例如业务逻辑代码内部,或者从安全数据库环境加载数据的过程,在入参本身安全可信的场景下,也可以使用非安全实例化的方式,初始化实体对象。

    实体对象提供了 assign 方法,允许将对象属性参数,在不经过结构检查的情况下,直接初始化到空对象实例上,或者也可以用于部分属性的覆盖。

    // 普通
    const foo = new DemoEntity().assign({
        width: 1,
        height: 2
    });
    
    // 嵌套实例化
    const bar = new WrapperEntity().assign([{
        items: [{
           width: 1,
           height: 2
        }],
        item: {
           width: 3,
           height: 4
        }
    }]);

生成 JSON Schema

因为实体对象生成过程引入了 JSON Schema 校验机制,项目运行前,需要在编译 typescript 的同时,生成对应数据模型的 Schema 定义资源。可以在 npm script 中添加如下的 typescript-json-schema 构建脚本,来生成需要使用的结构定义资源。

typescript-json-schema \
   ./src/entities/*.ts * \
   --out ./resources/schema.entities.json \
   --required \
   --excludePrivate \
   --ignoreErrors \
   --noExtraProps

可以根据实际工程目录情况,自行指定需要包含的实体对象 Typescript 定义文件或者目录,并指定输出目录。schema 生成过程的参数也可以按照实际使用情况。

另外,允许采用多种不同的方式生成多个不同的 JSON Schema 结构定义文件,以用于不同类型的数据结构校验。

更多有关 typescript-json-schema 的具体使用方法与参数说明,可以直接参考工具的使用说明文档:

https://github.com/YousefED/typescript-json-schema#command-line

常用的参数说明:

  • --required - 根据 ts 类型定义生成必填属性声明
  • --excludePrivate - 排除类型定义中的私有属性
  • --ignoreErrors - 忽略生成过程错误 (一般对于存在第三方 ts 包引用的场景,需要开启)
  • --noExtraProps - 禁止传入额外属性

对于前面定义的 DemoEntity,在执行 JSON Schema 生成过程后,可以得到如下的结构定义文件:

{
    "$schema": "http://json-schema.org/draft-07/schema#",
    "definitions": {
        "DemoEntity": {
            "additionalProperties": false,
            "properties": {
                "height": {
                    "minimum": 0,
                    "multipleOf": 2,
                    "type": "integer"
                },
                "meta": {
                    "additionalProperties": false,
                    "default": {
                    },
                    "properties": {
                        "description": {
                            "type": "string"
                        }
                    },
                    "type": "object"
                },
                "width": {
                    "minimum": 0,
                    "type": "integer"
                }
            },
            "required": [
                "height",
                "meta",
                "width"
            ],
            "type": "object"
        },
        "WrapperEntity": {
            "additionalProperties": false,
            "properties": {
                "item": {
                    "$ref": "#/definitions/DemoEntity"
                },
                "itemMap": {
                    "additionalProperties": {
                        "$ref": "#/definitions/DemoEntity"
                    },
                    "type": "object"
                },
                "items": {
                    "items": {
                        "$ref": "#/definitions/DemoEntity"
                    },
                    "minItems": 1,
                    "type": "array"
                }
            },
            "required": [
                "item",
                "items"
            ],
            "type": "object"
        }
    }
}

工程启动资源加载

最后,还有非常重要且必要的一点,nstarter-entity 本身对 schema 文件名没有详细的位置以及命名规定,所以提供了 SchemaManager 对象,用于管理结构定义资源文件的加载与初始化。

// 初始化
const schemaManager = SchemaManager.Initialize('./resources/schema.invalid.json');

// 允许加载多个结构定义文件
schemaManager.loadSchemaDefinition('./resources/schema.invalid.json');

// 配置自定义扩展格式
schemaManager.setSchemaFormats({
      'oid': /^[0-9a-f]{24}$/
});

为了防止资源加载依赖,产生潜在加载顺序导致先初始化对象实例的情况。建议可以将 SchemaManager 初始配置过程编写到独立文件中,在主启动代码的早期加载。

⚠ 注意

如果结构定义文件存在问题,会存在导致加载过程出现抛错的可能性,需要根据实际情况处理对应的错误信息。

实体对象扩展

在实体对象定义基于 AbstractEntity 实现了基本初始化、校验、序列化功能的基础上。在实际使用场景中,可以进一步对实体对象进行扩展,额外提供扩展的对象操作方法,或者覆盖实现默认的对象行为等。

API 说明

  • AbstractEntity - 实体基类

    • AbstractEntity(obj?: any) - 实体对象的安全初始化构造方法,初始对象参数可选。

    • AbstractEntity.prototype.fromJSON(obj: any) - 对象实例的安全初始化,基于 JSON 数据生成对象实例。

    • AbstractEntity.prototype.assign(obj: any) - 对象实例的非安全初始化,或者属性覆盖。

      一般场景使用默认预定义行为即可满足需要,对于存在特殊属性处理逻辑的情况,可自行在实体对象类定义中 override 相关行为。

    • AbstractEntity.prototype.toJSON(obj: any) - 用于 JSON 结构序列化的标准接口。

    一般使用默认行为,同样支持在实体类定义中进行扩展。

  • entityAttr - 嵌套实体属性装饰器

    • @entityAttr(itemCtor?: Constructor) - 标识可以被递归初始化的子属性,并对于复杂嵌套结构提供必要的子属性实体构造函数。
  • SchemaManager - Schema 管理器

    • SchemaManager.Initialize(definintion?: string) - 初始化,并获取 Schema 管理器对象的单例实体。

    • SchemaManager.prototype.loadSchemaDefinition(definition: string) - 加载 JSON Schema 结构定义文件,允许多次加载不同文件。

    • SchemaManager.prototype.setSchemaFormats(format: ISchemaFormats) - 配置 AJV 的自定义类型校验格式。

限制场景

受限于静态 schema 生成原理,以及 typescript 本身的限制,目前的实体对象使用上对于部分复杂场景存在一定的局限性,存在部分需要在使用过程中避开的情况。

  1. 实体对象联合类型 / 交叉类型

    局限性:

    • 结构校验: ✓
    • 自动实例化: ✗

    在自动生成的 JSON Schema 中,虽然静态生成的结果能够满足联合类型要求,但是在 Typescript 编译过程中中,无法自动生成对应联合类型的有效构造函数,因而无法实现联合类型属性的的递归初始化。相关基于已有对象实体本身定义的类型,需要在子属性类型定义中,避免作为联合类型的子类型使用。特殊场景下如需使用,需要自行实现联合类型具体采用何种类型初始化。

    例如,需要尽量避免以下结构定义的出现。如果存在相关定义必要性,需要自行实现相关子属性对象实例初始化的逻辑。

    export class UnionTypeEntity extends AbstractEntity {
        item: FooEntity | BarEntity
    }
  2. 泛型实体对象定义

    局限性:

    • 结构校验: ✗
    • 自动实例化: ✗

    泛型对象类型,在静态代码分析阶段,实际类型并不明确,目前的自动生成机制,会将泛型类型直接生成为 object 类型,而无法进行内部结构校验,也无法用于嵌套的初始化。对于实体对象的定义,现阶段需要严格避免。

    例如,以下出现的泛型对象属性类型声明方式应当避免:

    export class GenericEntity<T> extends AbstractEntity {
        item: T
    }
  3. 嵌套接口定义内部实例层级限制

    局限性:

    • 结构校验: ✓
    • 自动实例化: ✗

    受限于 JavaScript 对象构造方法传递的层级限制,以及装饰器的使用层级限制,目前的装饰 器实现下,对超过一层中间层的属性,不支持自动传递构造函数定义与创建的方法,相关属性 无法被正确自动初始化,需要手动干预。

    对于如下的实体对象数组映射表形式,无法提供完整自动实例化的支持。

    export class WrapperArrayMapEntity extends AbstractEntity {
        @entityAttr(TestEntity)
        itemArrayMap?: {
            [key: string]: DemoEntity[]
        };
    }

    同样,以下复杂结构对象,可以实现接口的安全结构校验初始化,但是无法自动完成实例化。

    export class ComplexItemEntity extends AbstractEntity {
        item: {
            foo: DemoEntity,
            bar: {
                baz: string,
                qux: DemoEntity
            }
        }
    }

License

MIT