@entityjs/entityjs
Advanced tools
Comparing version 0.0.5 to 0.0.6
{ | ||
"name": "@entityjs/entityjs", | ||
"version": "0.0.5", | ||
"version": "0.0.6", | ||
"description": "entity framework for pure javascript", | ||
@@ -5,0 +5,0 @@ "type": "module", |
193
README.md
@@ -21,7 +21,4 @@ # entityJS | ||
class Member extends Entity{ | ||
constructor(){ | ||
super(); | ||
stringValue(this, "name"); | ||
numberValue(this, "age"); | ||
} | ||
name = stringValue(); | ||
age = numberValue(); | ||
} | ||
@@ -54,7 +51,4 @@ ``` | ||
class Remember extends Entity{ | ||
constructor(){ | ||
super(); | ||
stringList(this, "friends"); | ||
numberMap(this, "lotto"); | ||
} | ||
friends = stringList(); | ||
lotto = numberMap(); | ||
} | ||
@@ -72,14 +66,8 @@ | ||
class Partner extends Entity{ | ||
constructor() { | ||
super(); | ||
stringValue(this, "name"); | ||
} | ||
name = stringValue(); | ||
} | ||
class Member extends Entity{ | ||
constructor() { | ||
super(); | ||
stringValue(this, "name"); | ||
numberValue(this, "age"); | ||
entityList(this, "partners", Partner); | ||
} | ||
name = stringValue(); | ||
age = numberValue(); | ||
partners = entityList(Partner); | ||
} | ||
@@ -96,2 +84,167 @@ const member = (new Member).parse({ | ||
### 4. default value | ||
If there is a default value, the parsing will pass even if there is no corresponding property in the json. | ||
Therefore, you can think of the default value as a kind of optional field declaration for parsing. | ||
```js | ||
class Member extends Entity{ | ||
name = stringValue().default("hika"); | ||
age = numberValue(); | ||
} | ||
const member = (new Member).parse({ //There is no name field in json | ||
age:18 | ||
}); // but parsing ok! | ||
member.name == "hika" | ||
member.age == 18 | ||
``` | ||
### 5. validator | ||
Validation can be set precisely for each field, and it is applied when directly entering a value in a property or parsing from json. | ||
```js | ||
class Member extends Entity{ | ||
name = stringValue() | ||
.default("hika") | ||
.validation(name=> 3 < name.length && name.length < 20); //4 ~ 19 characters | ||
age = numberValue().validation(age=> 9 < age && age < 20); //teenager | ||
} | ||
const member = (new Member).parse({ | ||
name:"ted", //invalid | ||
age:9 //invalid | ||
}); // throw exception | ||
const member = (new Member).parse({ | ||
name:"teds", //valid | ||
age:12 //valid | ||
}); //parsing ok | ||
``` | ||
### 6. decorator | ||
Without changing the value of the actual field, you can do extra work when you get it with get. | ||
```js | ||
class Member extends Entity{ | ||
name = stringValue() | ||
.default("hika") | ||
.validation(name=> 3 < name.length && name.length < 20) | ||
.decorator(name=>"**" + name + "**"); //set decorator | ||
age = numberValue().validation(age=> 9 < age && age < 20); | ||
} | ||
const member = (new Member).parse({ | ||
name:"hika", | ||
age:12 | ||
}); | ||
member.name == "**hika**" //decorated! | ||
``` | ||
### 7. union type | ||
In practice, json is often in the form of a or b. In particular, it is common for a and b to have shared fields. To handle this, define the abstract type of a and b to define a common field, and define a and b by inheriting it. This is called a union type. | ||
```json | ||
//backend developers | ||
{ | ||
"name":"hika", | ||
"age":18, | ||
"part":"backend", | ||
"languages":["java", "kotlin"], | ||
"framework":"spring", | ||
"database":"mysql" | ||
} | ||
//frontend developers | ||
{ | ||
"name":"hika", | ||
"age":18, | ||
"part":"frontend", | ||
"languages":["js", "ts"], | ||
"view":"react", | ||
"state":"redux" | ||
} | ||
``` | ||
In the example above, the developers has 'name', 'age', and 'languages' as shared fields. | ||
However, backend developers have 'framework' and 'database', and front-end developers have 'view' and 'state'. | ||
This can be effectively modeled as follows. | ||
#### 1 'part' is determined as an enum. | ||
```js | ||
const PART = new Enum("backend", "frontend"); | ||
``` | ||
#### 2 Collect the shared fields to define the abstract type 'Developer'. | ||
```js | ||
class Developer extends Entity{ | ||
name = stringValue(); | ||
age = numberValue(); | ||
part = enumValue(PART); | ||
languages = stringList(); | ||
} | ||
``` | ||
#### 3 Define 'Backend' and 'Frontend' by extending 'Developer'. | ||
```js | ||
class Backend extends Developer{ | ||
framework = stringValue(); | ||
database = stringValue(); | ||
} | ||
class Frontend extends Developer{ | ||
view = stringValue(); | ||
state = stringValue(); | ||
} | ||
``` | ||
#### 4 Declare Union. | ||
```js | ||
Entity.union(Developer, Backend, Frontend) | ||
``` | ||
* The first parameter becomes an abstract type, followed by concrete classes. According to the order of concrete classes, it is judged first when parsing. | ||
#### 5 Parsing through static method of abstract class. | ||
```js | ||
const dev1 = Developer.parse({ | ||
"name":"hika", | ||
"age":18, | ||
"part":"backend", | ||
"languages":["java", "kotlin"], | ||
"framework":"spring", | ||
"database":"mysql" | ||
}); | ||
dev1 instanceof Backend == true | ||
const dev2 = Developer.parse({ | ||
"name":"hika", | ||
"age":18, | ||
"part":"frontend", | ||
"languages":["js", "ts"], | ||
"view":"react", | ||
"state":"redux" | ||
}); | ||
dev2 instanceof Frontend == true | ||
``` | ||
A cleaner implementation is possible because it can branch through exact type matching without understanding the shape of json based on the value or existence of a specific field. | ||
```js | ||
//other system - field level branch | ||
if(entity.field === undefined) | ||
if(entity.field === specialValue) | ||
//entityJS - type level branch | ||
switch(true){ | ||
case entity instanceof Frontend:{ | ||
... | ||
} | ||
case entity instanceof Backend:{ | ||
... | ||
} | ||
} | ||
``` | ||
## addtional content | ||
@@ -98,0 +251,0 @@ * NPM <a href="https://www.npmjs.com/package/@entityjs/entityjs" target="_blank">https://www.npmjs.com/package/@entityjs/entityjs</a> |
@@ -7,14 +7,8 @@ import {Entity} from "../../src/entity/Entity.js"; | ||
class Partner extends Entity{ | ||
constructor() { | ||
super(); | ||
stringValue(this, "name"); | ||
} | ||
name = stringValue(); | ||
} | ||
class Member extends Entity{ | ||
constructor() { | ||
super(); | ||
stringValue(this, "name"); | ||
numberValue(this, "age"); | ||
entityList(this, "partners", Partner); | ||
} | ||
name = stringValue(); | ||
age = numberValue(); | ||
partners = entityList(Partner); | ||
} | ||
@@ -33,14 +27,8 @@ | ||
class Partner extends Entity{ | ||
constructor() { | ||
super(); | ||
stringValue(this, "name"); | ||
} | ||
name = stringValue(); | ||
} | ||
class Member extends Entity{ | ||
constructor() { | ||
super(); | ||
stringValue(this, "name"); | ||
numberValue(this, "age"); | ||
entityList(this, "partners", Partner); | ||
} | ||
name = stringValue(); | ||
age = numberValue(); | ||
partners = entityList(); | ||
} | ||
@@ -47,0 +35,0 @@ |
import {Entity, stringValue, numberValue, dateValue, entityList, enumValue, Enum} from "../../src/index.js"; | ||
class Member extends Entity{ | ||
#name = stringValue(this, "name").validation(v=>2<v.length && v.length<=10); | ||
#point = numberValue(this, "point").default(0); | ||
name = stringValue().validation(v=>2<v.length && v.length<=10); | ||
point = numberValue().default(0); | ||
} | ||
class VIP extends Member{ | ||
#freeParking = numberValue(this, "freeParking").validation(v=>v>=0); | ||
#vipCoupons = entityList(this, "vipCoupons", Coupon); | ||
#until = dateValue(this, "until"); | ||
freeParking = numberValue().validation(v=>v>=0); | ||
vipCoupons = entityList(Coupon); | ||
until = dateValue(); | ||
} | ||
@@ -18,5 +18,5 @@ class Normal extends Member{} | ||
class Coupon extends Entity{ | ||
#title = stringValue(this, "title"); | ||
#type = enumValue(this, "type", CouponType); | ||
#value = numberValue(this, "value"); | ||
title = stringValue(); | ||
type = enumValue(CouponType); | ||
value = numberValue(); | ||
} | ||
@@ -38,12 +38,11 @@ | ||
}); | ||
const sourceCode = ` | ||
class Member extends Entity{ | ||
#name = stringValue(this, "name").validation(v => 2 < v.length && v.length<=10); | ||
#point = numberValue(this, "point").default(0); | ||
name = stringValue().validation(v=>2<v.length && v.length<=10); | ||
point = numberValue().default(0); | ||
} | ||
class VIP extends Member{ | ||
#freeParking = numberValue(this, "freeParking").validation(v=>v>=0); | ||
#vipCoupons = entityList(this, "vipCoupons", Coupon); | ||
#until = dateValue(this, "until"); | ||
freeParking = numberValue().validation(v=>v>=0); | ||
vipCoupons = entityList(Coupon); | ||
until = dateValue(); | ||
} | ||
@@ -59,5 +58,5 @@ class Normal extends Member{} | ||
class Coupon extends Entity{ | ||
#title = stringValue(this, "title"); | ||
#type = enumValue(this, "type", CouponType); | ||
#value = numberValue(this, "value"); | ||
title = stringValue(); | ||
type = enumValue(CouponType); | ||
value = numberValue(); | ||
} | ||
@@ -64,0 +63,0 @@ |
import {setProp} from "../util/util.js"; | ||
import {Field} from "../field/Field.js"; | ||
const handler = { | ||
get(target, prop){ | ||
if(prop[0] === "#") return target[prop.substr(1)]; | ||
const field = target[prop]; | ||
return field instanceof Field ? field.get() : field; | ||
}, | ||
set(target, prop, value){ | ||
const field = target[prop]; | ||
field instanceof Field ? field.set(value) : (target[prop] = value); | ||
return true; | ||
} | ||
}; | ||
class Entity{ | ||
@@ -9,3 +21,4 @@ static union(base, ...sub){ | ||
let target; | ||
if(!sub.some(cls=>Object.entries((target = new cls)._fields).every(([key, field])=>{ | ||
if(!sub.some(cls=>Object.keys(target = new cls).every(key=>{ | ||
const field = target["#" + key]; | ||
const jsonValue = json[key]; | ||
@@ -24,17 +37,17 @@ if(jsonValue === undefined && field.get() === undefined) return false; | ||
constructor() { | ||
setProp(this, '_fields', {}); | ||
return new Proxy(this, handler); | ||
} | ||
parse(json){ | ||
Object.entries(this._fields).forEach(([key, field])=>{ | ||
Object.keys(this).forEach(key=>{ | ||
const jsonValue = json[key]; | ||
const field = this["#" + key]; | ||
if(jsonValue === undefined && field.get() === undefined) throw 'no key in json:' + key; | ||
this[key] = field.fromJSON(jsonValue); | ||
field.set(field.fromJSON(jsonValue)); | ||
}); | ||
return this; | ||
} | ||
toJSON(){return this._fields;} | ||
define(field, descriptor){ | ||
if(!(descriptor instanceof Field)) throw 'invalid descriptor(no Field type):' + descriptor | ||
Object.defineProperty(this, field, this._fields[field] = descriptor); | ||
return descriptor; | ||
toJSON(){ | ||
const result = {}; | ||
Object.keys(this).forEach(key=>result[key] = this["#" + key]); | ||
return result; | ||
} | ||
@@ -41,0 +54,0 @@ } |
import {Field} from "../Field.js"; | ||
class BooleanField extends Field{ | ||
typeValidation(v){ return typeof newValue != "boolean";} | ||
typeValidation(v){ return typeof v == "boolean";} | ||
} | ||
const booleanValue = (entity, key)=>entity.define(key, new BooleanField); | ||
const booleanValue = _=>new BooleanField; | ||
export {booleanValue}; |
@@ -7,3 +7,3 @@ import {Field} from "../Field.js"; | ||
} | ||
const booleanList = (entity, key)=>entity.define(key, new BooleanListField); | ||
const booleanList = _=>new BooleanListField; | ||
export {booleanList}; |
@@ -7,3 +7,3 @@ import {Field} from "../Field.js"; | ||
} | ||
const booleanMap = (entity, key)=>entity.define(key, new BooleanMapField); | ||
const booleanMap = _=>new BooleanMapField; | ||
export {booleanMap}; |
@@ -9,4 +9,4 @@ import {Field} from "../Field.js"; | ||
const dateValue = (entity, key)=>entity.define(key, new DateField); | ||
const dateValue = _=>new DateField; | ||
export {dateValue}; |
@@ -9,4 +9,4 @@ import {Field} from "../Field.js"; | ||
} | ||
const dateList = (entity, key)=>entity.define(key, new DateListField); | ||
const dateList = _=>new DateListField; | ||
export {dateList}; |
@@ -9,3 +9,3 @@ import {Field} from "../Field.js"; | ||
} | ||
const dateMap = (entity, key)=>entity.define(key, new DateMapField); | ||
const dateMap = _=>new DateMapField; | ||
export {dateMap}; |
@@ -11,4 +11,4 @@ import {Field} from "../Field.js"; | ||
} | ||
const entityValue = (entity, key, cls)=>entity.define(key, new EntityField(cls)); | ||
const entityValue = cls=>new EntityField(cls); | ||
export {entityValue}; |
@@ -12,4 +12,4 @@ import {Field} from "../Field.js"; | ||
} | ||
const entityList = (entity, key, cls)=>entity.define(key, new EntityListField(cls)); | ||
const entityList = cls=>new EntityListField(cls); | ||
export {entityList}; |
@@ -13,4 +13,4 @@ import {Field} from "../Field.js"; | ||
const entityMap = (entity, key, cls)=>entity.define(key, new EntityMapField(cls)); | ||
const entityMap = cls=>new EntityMapField(cls); | ||
export {entityMap}; |
@@ -11,4 +11,4 @@ import {Field} from "../Field.js"; | ||
const enumValue = (entity, key, targetEnum)=>entity.define(key, new EnumField(targetEnum)); | ||
const enumValue = targetEnum=>new EnumField(targetEnum); | ||
export {enumValue}; |
@@ -10,4 +10,4 @@ import {Field} from "../Field.js"; | ||
} | ||
const enumList = (entity, key, targetEnum)=>entity.define(key, new EnumListField(targetEnum)); | ||
const enumList = targetEnum=>new EnumListField(targetEnum); | ||
export {enumList}; |
@@ -12,4 +12,4 @@ import {Field} from "../Field.js"; | ||
const enumMap = (entity, key, targetEnum)=>entity.define(key, new EnumMapField(targetEnum)); | ||
const enumMap = targetEnum=>new EnumMapField(targetEnum); | ||
export {enumMap}; |
@@ -16,4 +16,10 @@ class Field{ | ||
} | ||
default(v){this.set(v);} | ||
decorator(v){this._decorator = v;} | ||
default(v){ | ||
this.set(v); | ||
return this; | ||
} | ||
decorator(v){ | ||
this._decorator = v; | ||
return this; | ||
} | ||
toJSON(){return this.v;} | ||
@@ -20,0 +26,0 @@ fromJSON(v){return v;} |
import {Field} from "../Field.js"; | ||
class NumberField extends Field{ | ||
typeValidation(v){ return typeof newValue != "number";} | ||
typeValidation(v){ return typeof v == "number";} | ||
} | ||
const numberValue = (entity, key)=>entity.define(key, new NumberField); | ||
const numberValue = _=>new NumberField(); | ||
export {numberValue}; |
@@ -7,4 +7,4 @@ import {Field} from "../Field.js"; | ||
} | ||
const numberList = (entity, key)=>entity.define(key, new NumberListField); | ||
const numberList = _=>new NumberListField; | ||
export {numberList}; |
@@ -8,4 +8,4 @@ import {Field} from "../Field.js"; | ||
const numberMap = (entity, key)=>entity.define(key, new NumberMapField); | ||
const numberMap = _=>new NumberMapField; | ||
export {numberMap}; |
import {Field} from "../Field.js"; | ||
class StringField extends Field{ | ||
typeValidation(v){ return typeof newValue != "string";} | ||
typeValidation(v){ return typeof v == "string";} | ||
} | ||
const stringValue = (entity, key)=>entity.define(key, new StringField); | ||
const stringValue = ()=>new StringField; | ||
export {stringValue}; |
@@ -7,4 +7,4 @@ import {Field} from "../Field.js"; | ||
} | ||
const stringList = (entity, key)=>entity.define(key, new StringListField); | ||
const stringList = _=>new StringListField; | ||
export {stringList}; |
@@ -7,4 +7,4 @@ import {Field} from "../Field.js"; | ||
} | ||
const stringMap = (entity, key)=>entity.define(key, new StringMapField); | ||
const stringMap = _=>new StringMapField; | ||
export {stringMap}; |
79939
605
256