-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
140 lines (111 loc) · 3.33 KB
/
index.js
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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
var series = require('series');
var clone = require('clone');
var isEmpty = require('isempty');
var immodel = require('immodel');
module.exports = function() {
this.attrs = this.attrs || {};
this.attr = function(name, type) {
if(arguments.length === 1) {
// Support wildcard attributes
if(! this.hasAttr(name) && this.hasAttr('*'))
name = '*';
name = '$' + name;
if(! this.is(this.attrs[name])) {
this.attrs[name] = coerce(this, this.attrs[name]);
}
return this.attrs[name];
}
if('string' === typeof type)
type = {type: type};
return this.use(function() {
if(isEmpty(this.attrs)) {
this.on('init', function(evt) {
// Things that have attributes should have
// a default value of empty object if
// otherwise undefined
var doc = evt.doc;
if(doc.value === undefined)
doc.value = {};
});
}
this.attrs['$' + name] = type;
});
};
this.hasAttr = function(name) {
return !! this.attrs['$' + name];
};
this.prototype.set = function(path, leafValue) {
var idx = path.indexOf('.');
if(idx !== -1) {
var first = path.slice(0, idx);
var rest = path.slice(idx + 1);
return this.set(path.slice(0, idx), this.get(first).set(rest, leafValue));
}
var value = clone(this.value, true, 1);
value[path] = leafValue;
return (new this.model(value));
};
this.prototype.get = function(path) {
var idx = path.indexOf('.');
if(idx !== -1)
return this.get(path.slice(0, idx)).get(path.slice(idx + 1));
var type = this.model.attr(path);
// We should probably either throw an exception here or at least
// add a configuration option to do so. For now, we return
// an instance of the base immodel type with undefined value
if(! type) return (new immodel());
if(! type.isDocument(this.value[path])) {
this.value[path] = new type(this.value[path]);
}
return this.value[path];
};
this.isDocument = function(value) {
return value && value.__isDocument;
};
this.prototype.toJSON = function() {
var value = this.value;
var model = this.model;
var json = {};
if(isEmpty(this.attrs))
return value;
Object.keys(value).forEach(function(key) {
var val = value[key];
json[key] = model.isDocument(val)
? val.toJSON()
: val;
});
return json;
};
this.prototype.eachAttr = function(fn) {
var model = this.model;
Object.keys(model.attrs).forEach(function(name) {
name = name.slice(1);
if(name === '*') return;
fn(name, model.attr(name));
});
if(model.hasAttr('*')) {
Object.keys(this.value || {}).forEach(function(name) {
if(! model.hasAttr(name))
fn(name, model.attr(name));
});
}
};
this.prototype.eachAttrAsync = function(fn, cb) {
var attrs = [];
this.eachAttr(function(name, type) {
attrs.push([name, type]);
});
series(attrs, function(tuple, next) {
fn(tuple[0], tuple[1], next);
}, cb);
};
};
function coerce(model, opts) {
if(! opts) return;
type = model.is(opts.type)
? opts.type
: model.lookup(opts.type);
if(! type) throw new Error('type "' + opts.type + '" has not been registered');
delete opts.type;
return type.use(opts);
}