-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathmodel.js
More file actions
114 lines (97 loc) · 3.1 KB
/
model.js
File metadata and controls
114 lines (97 loc) · 3.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
export const is = (type, value) => {
if(type && type.isValid instanceof Function){
return type.isValid(value)
} else if((type === String && ((value instanceof String) || typeof value === 'string'))
|| (type === Number && ((value instanceof Number) || typeof value === 'number'))
|| (type === Boolean && ((value instanceof Boolean) || typeof value === 'boolean'))
|| (type === Function && ((value instanceof Function) || typeof value === 'function'))
|| (type === Object && ((value instanceof Object) || typeof value === 'object'))
|| (type === undefined)
){
return true
}
return false
}
const check = (types, required, data) => {
Object.keys(types).forEach(key => {
let t = types[key],
value = data[key]
if(required[key] || value !== undefined){
if(!(t instanceof Array)) t = [t]
let i = t.reduce((a,_type) => a || is(_type, value), false)
if(!i) {
throw `{${key}: ${JSON.stringify(value)}} is not one of ${t.map(x => `\n - ${x}`)}`
}
}
})
return true
}
export const Model = (...args) => {
let types, required, logic
args.map(x => {
if(x instanceof Function && !logic){ logic = x }
else if(typeof x === 'object') {
if(!types){ types = x }
else if(!required){ required = x }
}
})
const isValid = (data) => {
const pipe = logic ? [check, logic] : [check]
return pipe.reduce((a,v) => a && v(types||{},required||{},data), true)
}
const whenValid = (data) => new Promise((res,rej) => isValid(data) && res(data))
return {isValid, whenValid}
}
export const ArrayOf = (M) => {
return Model((t,r,data) => {
if(!(data instanceof Array)) throw `${data} not an Array`
data.map(x => {
if(!is(M, x))
throw `${x} is not a model instance`
})
return true
})
}
/**
Use it
// create a Name model with required first/last,
// but optional middle
let Name = Model({
first: String,
middle: String,
last: String
}, {first:true, last:true})
// create a Tags model with extra checks
let Tags = Model((types,required,data) => {
if(!(data instanceof Array)) throw `${data} not an Array`
data.map(x => {
if(!is(String, x))
throw `[${data}] contains non-String`
})
return true
})
// create a Price model that just has a business logic fn
let Price = Model((t,r,d) => {
return (d instanceof Number || typeof d === 'number') && d !== 0
})
// create an Activity model with a required type and name,
// all others optional
let Activity = Model({
type: [String, Function, Number],
latlng: Array,//LatLng,
title: String,
tags: Tags,
name: Name,
price: Price
}, {name:true, price: true})
// create a new Activity instance, throwing errors if there are
// any invalid fields.
let a = {
tags: ['1','2'],
type: 1,
name: {first:'matt',last:'keas'},
price: 100.43,
url: 'http://www.google.com'
}
Activity.whenValid(a).then(log).catch(e => log(e+''))
**/