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

meta-mapper

v0.3.0

Published

Class based type(object) - mapper(shaper) via meta-data (reflect).

Downloads

69

Readme

meta-mapper

Class based type(object) - mapper(shaper) via meta-data (reflect).

Install

npm install --save meta-mapper

Use scenario

  • Map & sharp the data from request.

    Example: data { name: "shadow", val: "456", date: "2022-01-01", others: "..." } send from client, we'll need to map & sharp it to { name: string, val: number, date: Date }.

  • Db access need to convert your define class object to db object.

    Example: a defined object { myName: "shadow" } wanna to map to db object { my_name: "shadow" } & save in Db, or vice versa.

Feature

  • Support basic type Number | String | Boolean | Object.
  • Support instance type Enum | Any | Date | Array.
  • Support nest structure.
  • Support super class.
  • Support validation function on property.
  • Use stragety try map as much as possible, the errors indicates the runtime mapping issues.
  • Support generic type w/ Generic class/keyword since v0.2.0

    Use a name (the meta-name) to represent the type wannt, example paging.offset.date, for array structure try to use example paging.data.$ please.
    Check more details in test case in generic.test.ts please.

Limitation

  • Support class definition instead of object type constraint definition.
  • Now support enum int 2 ways, define an enum or via array type.

    A. enum XXX w/ @mp("", Enum, () => XXX)
    B. array w/ @mp("", Enum, () => [......])

  • Do not support cycle structure.

    If the structure can't use JSON.stringify, then don't use meta-mapper please.

  • Do not support custom map function yet.

Exception & Error

  • Most of the Exception was thrown for the definiation error, which means you might correct it on the program phase instead of runtime phase.
  • Error returned from $rtn.errors which represent the map runtime error. It's an array structure since we use try map as much as possible stragety.

    $rtn.errors[$idx].name is the meta name by default, example paging.offset. When there's array structure it could be like paging.detail.data[2].value etc.

Performance

Below performance test based on: 2.4 GHz 8-Core Intel Core i9 + 64G Memory.

  • A simple class structure - map action would cost 10 ~ 30(us).
  • A complex class structure - map action would cost about 100+(us) depends your data structure.

ref: check $/test/performance.test.ts for more details please.

Others

  • Null/Undefined is allowed & supported by default as "values" instead of "types".
  • Less dependency, only depends on "reflect-metadata"

API

  • @mc(name: string, types: ClassConstructor[])

    when define a class, attach @mc on the class as a meta class data.
    Example: @mc("my_class", ClassBase) for class class MyClass extends ClassBase.

    • name: meta-name (alias) of your class. By default null | "" indicates the same as the class-name.
    • type: super types of the class.
  • @mp(name: string, types: ClassConstructor[] | Function[])

    when define a property, attach @mp on the property as a meta property data.

    • name: meta-name (alias) of your property. By default null | "" indicates the same as thhe property-name.
    • types: internal nest types of the property.
      • Any: means should ignore the map action on this property, keep as it is.
      • Array: when it's an array, need to define as Array, and the internal element type of the Array.

        Example: @mp("custom_array", Array, MyCustomClass) for property custom_array: MyCustomClass[].

      • Enum: when it's an enum defined w/ enum XXX {...}, need to set as Enum and the real enum object type.

        Example: @mp("my_enum", Enum, () => MyEnum) for property my_enum: MyEnum.

  • @mv_???

    Validation function. Use mv_custom to define customized validation function please.

  • new(opt: MetaMapperOption)

    Define your mapper w/ custom mapper options.

    • opt: check the option below please.
  • map(type: ClassConstructorGeneric<T>, obj: any, genericTypes?: Array<GenericNameType>): MapperRtn<T>

    map from any object input to defined class data type.
    Example: @mp("my_enum", Enum, () => MyEnum) for property my_enum: MyEnum.

    • type: the class type you wanna to map to.
    • obj: the object you wanna to map from.
    • genericTypes: If any generic type, indicate it here please. for Array<T> indicate the name as $ (i.e. xx.$.xxx) please.

Mapper Option

Option | Default Value | Description --- | --- | --- from | PropertyKey | map from the property-key field, otherwise set as MetaName if from the meta-name please. to | PropertyKey | map to the property-key field, otherwise set as MetaName if to the meta-name please. validate | true | global swith: run validation check or not validateUndefined | false | global swith: when validate, should we validate undefined value or not. validateNull | true | global swith: when validate, should we validate null value or not. keepArrayLengthMatch | true | when map failed on an array item, should we still set as "undefined" to keep the array length or not. keepNullVal | false | if the value is null then keep the key or property conversion. keepOriginVal | false | no matter whate the value was just keep it the same, note when this flag set the keepNullVal will be ignored.

Code Example

ref: $/test/readme.test.ts as well pleae.



//1. define your class w/ meta
//@mc short for Meta-Class
//@mp short for Meta-Property
//@mv short for Meta-Validation
@mc
class PagingData {
    @mp
    code: string;

    //meta-alias is "value" instead of "val"
    //Note here mark the real data-type of the "val" | "value" as "Number" (instead of the define type "String")
    //In later map action, no matter what data-type of the "val" | "value" is, the mapper would try to conver it to "Number" type.
    @mp("value", Number)
    val: string;
}

@mc
class Paging {
    @mp
    @mv_number(0, 100, false, "test.paging.offset", "Must between [0 ~ 100]")
    offset: number;

    @mp
    @mv_number(1, 20, false, "test.paging.limit", "Must between [0 ~ 20]")
    limit: number;

    @mp
    date: Date;

    //first parameter is "" or null indicates the meta-name is same as the property name, here it's "data" as well.
    @mp("", Array, PagingData)
    data: PagingData[];
}
//2. create a meta-mapper (w/ default option)
//Which a meta-mapper instance can be shared among the application, but if you need different mapper option you might init multiple instance.
//Note by default the opt is from PropertyKey -> PropertyKey
//Here we wanna to test the map from propertyKey to MetaName, so we set "opt.to" to "Meta"
const mapper = new MetaMapper({ to: MetaMapOn.Meta });
//3. indicate the class-type to map the object via calling .map() method.
let obj = {
    offset: 0,
    limit: "20",
    date: "2022-01-01 09:10:00+08:00",
    value: 100,
    data: [{ code: "s-001", val: "001" },
        { code: "s-002", val: "002" }
    ]
};
let p = mapper.map(Paging, obj);
//4. verify the ouput, here the return "p.rtn" would be like:
let pAssert = {
    offset: 0,
    limit: 20,  //convert from String to Number
    date: new Date("2022-01-01T01:10:00.000Z"),     //convert from String to Date
    data: [
        //nest data convert from "val" to "value", and type from String to Number
        { code: "s-001", value: 1 },
        { code: "s-002", value: 2 }
    ]
};
assert.strictEqual(JSON.stringify(p.rtn), JSON.stringify(pAssert));
assert.strictEqual(p.rtn.date instanceof Date, true);

Design

  • MetaMapper-Flow: MetaMapper-Flow

  • MetaMapper-DataType: MetaMapper-DataType