-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathindex.js
204 lines (159 loc) · 4.24 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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
var util = require('util'),
mongoose = require('mongoose'),
clone = require('clone'),
_toJSON = mongoose.Document.prototype.toJSON;
// convert string formatted fields to object formatted ones
function normalizeFields(fields) {
if (!fields) return;
if (fields.constructor.name === 'Object') {
return fields;
} else if ('string' === typeof fields) {
var _fields = {};
fields.split(/\s+/).forEach(function(field) {
if (!field) return;
var include = +(field[0] !== '-');
field = include ? field : field.substring(1);
_fields[field] = include;
});
return _fields;
}
throw new TypeError('Invalid select fields. Must be a string or object.');
}
// create an empty object or array as a destination to copy
function emptyObject(obj) {
if (obj && obj.constructor.name === 'Object') {
return {};
} else if (util.isArray(obj)) {
return [];
}
}
// copy a value recursively
function pick(src, dst, field) {
if (!src || !dst) return;
if (util.isArray(src)) {
pickArray(src, dst, field);
return;
}
var _field = field[0],
_src, _dst;
if (!(_field in src)) return;
_src = src[_field];
if (field.length > 1) {
if (_field in dst) {
// get a reference when a value already exists
_dst = dst[_field];
} else {
_dst = emptyObject(_src);
if (_dst) {
dst[_field] = _dst;
}
}
// continue to search nested objects
pick(_src, _dst, field.slice(1));
return;
}
dst[_field] = clone(_src);
}
// pick only objects and arrays from a array
function pickArray(src, dst, field) {
var i = 0;
src.forEach(function(_src) {
var _dst;
if (dst.length > i) {
_dst = dst[i];
i++;
} else {
_dst = emptyObject(_src);
if (_dst) {
dst.push(_dst);
i++;
}
}
pick(_src, _dst, field);
});
}
function only(data, fields) {
if (!fields.length) return data;
var _data = {};
fields.forEach(function(field) {
pick(data, _data, field.split('.'));
});
return _data;
}
// delete a value recursively
function omit(data, field) {
if (!data) return;
if (util.isArray(data)) {
data.forEach(function(_data) {
omit(_data, field);
});
return;
}
var _field = field[0];
if (field.length > 1) {
omit(data[_field], field.slice(1));
return;
}
if (data.constructor.name === 'Object') {
delete data[_field];
}
}
function except(data, fields) {
var _data = clone(data);
fields.forEach(function(field) {
omit(_data, field.split('.'));
});
return _data;
}
function select(data, fields) {
if (!fields) return data;
var inclusive = [],
exclusive = [];
fields = normalizeFields(fields);
Object.keys(fields).forEach(function(field) {
(fields[field] ? inclusive : exclusive).push(field);
});
data = inclusive.length ? only(data, inclusive) : data;
return exclusive.length ? except(data, exclusive) : data;
}
// include "_id" by default
function setDefault(fields) {
if ('_id' in fields) return;
var hasInclusion = Object.keys(fields).some(function(f) {
return fields[f];
});
if (hasInclusion) {
fields._id = 1;
}
}
exports = module.exports = function(schema, fields) {
var methods = schema.methods,
toJSON = methods.toJSON || _toJSON;
// NOTE: toJSON calls toJSON with a same option recursively for all subdocuments.
methods.toJSON = function(options) {
var schemaOptions = this.schema.options.toJSON,
_options = options || schemaOptions || {},
_fields = (options || {}).select || (schemaOptions || {}).select || fields,
obj;
_options = clone(_options);
if (!options) {
// use default fields in all subdocuments.
delete _options.select;
} else if ('undefined' !== typeof options.select) {
// fields are specified directly, then don't limit fields in all subdocuments.
if (options.select) {
// the route for an original document
_options.select = null;
} else {
// the route for all subdocuments
_fields = null;
}
}
obj = toJSON.call(this, _options);
if (!_fields) return obj;
_fields = normalizeFields(_fields);
setDefault(_fields);
return select(obj, _fields);
};
};
exports.select = select;