typomap v1.0.0
a tiny and simple entity mapper for frontend. inspired by typeorm
extendable,simple config,support typescript, zero dependency
install
npm i --save typomapdefine entity
import { Entity, Model, type } from 'typomap'
@Entity()
export default class Named extends Model {
constructor (source) {
super()
this.merge(source)
}
@type(String)
name = ''
@from('image')
@type(String)
@nullable()
avatar = ''
}example 1: parse data from backend api
new Named().parse({ name: 'Typox', date: new Date(), image: 'x' })get result
Named {name: "Typox", avatar: "x"}example 2: new object for frontend data
new Named({ name: 'Typox', avatar: 'x' })
or
new Named().merge({ name: 'Typox', avatar: 'x' })get result
Named {name: "Typox", avatar: "x"}example 3: attr not match
new Named().parse({ date: new Date() })get result
Error: Named.name defined as String, got:undefinedcustomize error message
import { setMessageFormat } from 'typox'
setMessageFormat('{entity}.{attr}: {value} is not "{type}"')get result
Error: Named.name: undefined is not "String"not match handler.
by default, it will throw an error if not match.
import { setLogger } from 'typox'
setLogger(console)or
setLogger({
error: (v: string) => { alert(v) }
})extend others and more anotations
import { Entity, Model, type, from, nullable, parse, validator } from 'typox'
@Entity()
export default class Staff extends Named {
constructor (source) {
super()
this.merge(source)
}
@from('author.name')
@type(String)
author = ''
@from('author.email')
@nullable()
email = ''
@from('birthday')
@type(Number)
@parse(v => v.getFullYear())
@validator([
(v) => v.getFullYear() > 2020
])
year = ''
}
new Staff().parse({
name: 'Typox Mapper',
author: { name: 'tan' },
birthday: new Date()
})Staff { name: 'Typox Mapper', author: 'tan', email: '', year: 2021 }as props type of vue component
<template>
<div>
{{ staff }}
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import Staff from '@/entity/Staff'
export default defineComponent({
props: {
staff: {
type: Staff
}
}
})
</script>mult type and enumeration
import { Entity, Model, type, enumeration } from 'typox'
@Entity()
export default class Named extends Model {
constructor (source) {
super()
this.merge(source)
}
@type(String)
name = ''
@type([String, Number])
key = ''
@enumeration([10, 20])
age = 0
}good
new Named().parse({ name: 'Typox', key: 1, age: 10})
get
Named { name: "Typox", key: 1, age: 10 }
new Named().parse({ name: 'Typox', key: 'private key', age: 10})
get
Named { name: "Typox", key: "private key", age: 10 }error
new Named().parse({ name: 'Typox', key: 1, age: 30})
get
Error: Named.age defined as [10, 20], 30reverse entity to json object for api request
import { Entity, Model, type, to, reverse } from 'typox'
@Entity()
export default class Query extends Model {
constructor (source) {
super()
this.merge(source)
}
@from('nickname')
@type(String)
@to('userName')
name = ''
@type(String)
@to("privatekey")
@reverse((v) => 'base64://' + v)
key = ''
@to()
addr = ''
}const entity = new Query().parse({ nickname: 'typox', key: '123' })
get
Query { name: "typox", key: "123", addr: '' }
entity.reverse()
get
{ "userName": "typox", "privatekey": "base64://123" }entity.reverse() will ingore null, '', undefined property by default, you can use entity.reverse({ lightly: false }) to get { "userName": "typox", "privatekey": "base64://123", "addr": "" }
interface ReverseOption {
// lightly mode will ignore attribute if the value is blank string
lightly?: boolean | undefined
// ignore attribute
exclusion?: string[]
}set lightly for global
import { defaults } from 'typox'
defaults.lightly = falsedecorators
- @
Enitityor @entity=> anotation for entity class - @
from=> define from original prop. eg:@from('company.name'). or@from(['name', 'member.name']) - @
type=> define data type. support mult type. eg:@type([Number, String]) - @
nullable=> allow data can benullorundefined - @
parse=> define how to parse the original data. eg:@parse((v, me) => (v * me.unit) + 'miniute').thiskeyword in parse function is supported from V2.8.1 - @
enumeration=> enumeration - @
validator=> support mult validator - @
omit=> omit this attribute when call functioin parse, merge, reverse - @
to=> define how to format entity to a json object, generally for the backend api. eg:tranfer entity attrnametouserName,@to('userName') - @
reverse=> define how to format the entity attr to json attr. eg:@reverse((v, me) => me.status === 1 ? moment(v).format('YYYYMMDD') : moment(v).format('YY-MM-DD'))or :@reverse(function(v) { return this.status === 1 ? v : '' }) - @
recover=> define how to convert string vlaue to the attritube value from query @formatalias to@parse@decorators=> define custom decorators.@dep=> define attribute dependencies, typox will parse dependencies before this attribute- @
merge=> define how to copy data. such as array to object. eg:@merge((v, me) => new Staff(v).
custom decorators
import { Entity, Model, type, decorators } from 'typox'
@Entity()
export class Tm extends Model {
constructor (source?: Record<string, unknown>) {
super()
this.merge(source)
}
@decorators({
max: () => 0
})
@type(Number)
id = 0
@decorators({
max: function (this: Tm) {
return this.id + 5
}
})
@type(String)
name = ''
}run:
new Tm().runDecorators('max')get result
{name: 5, id: 0}private function of entity from model
entity.parse(source, option?: ParseOption), the parametersourcecan be a json object or a entity, normally use this function to merge some values from other objectentity.merge(source), the parametersourcecan be a json object or a entity, this will set the attribute value of entity by source attr value (if the source has the same attribute)entity.recover(source), the parametersourcecan be a json object or a entity, normally use this function to recover entity from the url. this function will auto tranform prop data to the type of defined. eg: http://localhost/logs?name=typox&page=1&size=10. create entity bynew LogQuery().recover({ name: 'typox', page: '1', size: '10' })get resultLogQuery { name: 'typox', page: 1, size: 10 }entity.reverse()transform entity to a json objectentity.runDecorators(attr: string)run custom decorator
interface ParseOption {
// nullable for all attribute
nullable?: boolean
// ignore validator
validate?: boolean
}more
you can define some prop without anotation. it will ignore when create a entity or reverse to a json object. eg:
@Entity()
export default class Named extends Model {
constructor (source) {
super()
this.merge(source)
}
loading = false
isme () {
return this.name === 'typox'
}
@type(String)
name = ''
}new Named().parse({ loading: true, name: 'typox' })get
Named { loading: false, name: "typox" }named.isme() will get true
named.reverse() will get json object { name: 'typox' }
especially useful for data lock in table
use this in parse function
by default, typox will parse attributes one by on as you defined.
@Entity()
export class Staff extends Model {
constructor (source?: Record<string, unknown>) {
super()
this.merge(source)
}
@parse(function (this: Staff, v) {
console.log(v, this.id)
return v
})
@nullable()
@type(String)
@to()
key = ''
@parse((v) => {
console.log(v)
return v
})
@type(Number)
@to()
id = 0
}
new Staff().parse({ id: 1, key: '2' })
output:
1
2 1you can define @parse by normal function as @parse(function () { return this.min + this.max }), but in TypeScript you will get an eslint error 'this' implicitly has type 'any' because it does not have a type annotation., you can resovle this by @parse(function (this: Staff, v) { return this.min + v })
method parameter check in runtime
if your project do not support typescript, but you want to check the parameter in runtime
import { param } from 'typox'
import { Log } from '../entity/log'
export class LogRepository extends Repository {
@param(Log)
static async create (row) {
return axios.post('/system/log', row)
}
}LogRepository.create(new Member())get
Error: LogRepository.create(Member <> Log)2 years ago