UNPKG

8.06 kBJavaScriptView Raw
1'use strict';
2
3const Promise = require('bluebird');
4const { parseArgs, shuffle } = require('./util');
5
6class Query {
7
8 /**
9 * Query constructor.
10 *
11 * @param {Array} data
12 */
13 constructor(data) {
14 this.data = data;
15 this.length = data.length;
16 }
17
18 /**
19 * Returns the number of elements.
20 *
21 * @return Number
22 */
23 count() {
24 return this.length;
25 }
26
27 /**
28 * Iterates over all documents.
29 *
30 * @param {Function} iterator
31 */
32 forEach(iterator) {
33 const { data, length } = this;
34
35 for (let i = 0; i < length; i++) {
36 iterator(data[i], i);
37 }
38 }
39
40 /**
41 * Returns an array containing all documents.
42 *
43 * @return {Array}
44 */
45 toArray() {
46 return this.data;
47 }
48
49 /**
50 * Returns the document at the specified index. `num` can be a positive or
51 * negative number.
52 *
53 * @param {Number} i
54 * @return {Document|Object}
55 */
56 eq(i) {
57 const index = i < 0 ? this.length + i : i;
58 return this.data[index];
59 }
60
61 /**
62 * Returns the first document.
63 *
64 * @return {Document|Object}
65 */
66 first() {
67 return this.eq(0);
68 }
69
70 /**
71 * Returns the last document.
72 *
73 * @return {Document|Object}
74 */
75 last() {
76 return this.eq(-1);
77 }
78
79 /**
80 * Returns the specified range of documents.
81 *
82 * @param {Number} start
83 * @param {Number} [end]
84 * @return {Query}
85 */
86 slice(start, end) {
87 return new this.constructor(this.data.slice(start, end));
88 }
89
90 /**
91 * Limits the number of documents returned.
92 *
93 * @param {Number} i
94 * @return {Query}
95 */
96 limit(i) {
97 return this.slice(0, i);
98 }
99
100 /**
101 * Specifies the number of items to skip.
102 *
103 * @param {Number} i
104 * @return {Query}
105 */
106 skip(i) {
107 return this.slice(i);
108 }
109
110 /**
111 * Returns documents in a reversed order.
112 *
113 * @return {Query}
114 */
115 reverse() {
116 return new this.constructor(this.data.slice().reverse());
117 }
118
119 /**
120 * Returns documents in random order.
121 *
122 * @return {Query}
123 */
124 shuffle() {
125 return new this.constructor(shuffle(this.data));
126 }
127
128 /**
129 * Finds matching documents.
130 *
131 * @param {Object} query
132 * @param {Object} [options]
133 * @param {Number} [options.limit=0] Limits the number of documents returned.
134 * @param {Number} [options.skip=0] Skips the first elements.
135 * @param {Boolean} [options.lean=false] Returns a plain JavaScript object.
136 * @return {Query|Array}
137 */
138 find(query, options = {}) {
139 const filter = this._schema._execQuery(query);
140 const { data, length } = this;
141 const { lean = false } = options;
142 let { limit = length, skip } = options;
143 const arr = [];
144
145 for (let i = 0; limit && i < length; i++) {
146 const item = data[i];
147
148 if (filter(item)) {
149 if (skip) {
150 skip--;
151 } else {
152 arr.push(lean ? item.toObject() : item);
153 limit--;
154 }
155 }
156 }
157
158 return lean ? arr : new this.constructor(arr);
159 }
160
161 /**
162 * Finds the first matching documents.
163 *
164 * @param {Object} query
165 * @param {Object} [options]
166 * @param {Number} [options.skip=0] Skips the first elements.
167 * @param {Boolean} [options.lean=false] Returns a plain JavaScript object.
168 * @return {Document|Object}
169 */
170 findOne(query, options = {}) {
171 options.limit = 1;
172
173 const result = this.find(query, options);
174 return options.lean ? result[0] : result.data[0];
175 }
176
177 /**
178 * Sorts documents.
179 *
180 * Example:
181 *
182 * ``` js
183 * query.sort('date', -1);
184 * query.sort({date: -1, title: 1});
185 * query.sort('-date title');
186 * ```
187 *
188 * If the `order` equals to `-1`, `desc` or `descending`, the data will be
189 * returned in reversed order.
190 *
191 * @param {String|Object} orderby
192 * @param {String|Number} [order]
193 * @return {Query}
194 */
195 sort(orderby, order) {
196 const sort = parseArgs(orderby, order);
197 const fn = this._schema._execSort(sort);
198
199 return new this.constructor(this.data.slice().sort(fn));
200 }
201
202 /**
203 * Creates an array of values by iterating each element in the collection.
204 *
205 * @param {Function} iterator
206 * @return {Array}
207 */
208 map(iterator) {
209 const { data, length } = this;
210 const result = new Array(length);
211
212 for (let i = 0; i < length; i++) {
213 result[i] = iterator(data[i], i);
214 }
215
216 return result;
217 }
218
219 /**
220 * Reduces a collection to a value which is the accumulated result of iterating
221 * each element in the collection.
222 *
223 * @param {Function} iterator
224 * @param {*} [initial] By default, the initial value is the first document.
225 * @return {*}
226 */
227 reduce(iterator, initial) {
228 const { data, length } = this;
229 let result, i;
230
231 if (initial === undefined) {
232 i = 1;
233 result = data[0];
234 } else {
235 i = 0;
236 result = initial;
237 }
238
239 for (; i < length; i++) {
240 result = iterator(result, data[i], i);
241 }
242
243 return result;
244 }
245
246 /**
247 * Reduces a collection to a value which is the accumulated result of iterating
248 * each element in the collection from right to left.
249 *
250 * @param {Function} iterator
251 * @param {*} [initial] By default, the initial value is the last document.
252 * @return {*}
253 */
254 reduceRight(iterator, initial) {
255 const { data, length } = this;
256 let result, i;
257
258 if (initial === undefined) {
259 i = length - 2;
260 result = data[length - 1];
261 } else {
262 i = length - 1;
263 result = initial;
264 }
265
266 for (; i >= 0; i--) {
267 result = iterator(result, data[i], i);
268 }
269
270 return result;
271 }
272
273 /**
274 * Creates a new array with all documents that pass the test implemented by the
275 * provided function.
276 *
277 * @param {Function} iterator
278 * @return {Query}
279 */
280 filter(iterator) {
281 const { data, length } = this;
282 const arr = [];
283
284 for (let i = 0; i < length; i++) {
285 const item = data[i];
286 if (iterator(item, i)) arr.push(item);
287 }
288
289 return new this.constructor(arr);
290 }
291
292 /**
293 * Tests whether all documents pass the test implemented by the provided
294 * function.
295 *
296 * @param {Function} iterator
297 * @return {Boolean}
298 */
299 every(iterator) {
300 const { data, length } = this;
301
302 for (let i = 0; i < length; i++) {
303 if (!iterator(data[i], i)) return false;
304 }
305
306 return true;
307 }
308
309 /**
310 * Tests whether some documents pass the test implemented by the provided
311 * function.
312 *
313 * @param {Function} iterator
314 * @return {Boolean}
315 */
316 some(iterator) {
317 const { data, length } = this;
318
319 for (let i = 0; i < length; i++) {
320 if (iterator(data[i], i)) return true;
321 }
322
323 return false;
324 }
325
326 /**
327 * Update all documents.
328 *
329 * @param {Object} data
330 * @param {Function} [callback]
331 * @return {Promise}
332 */
333 update(data, callback) {
334 const model = this._model;
335 const stack = this._schema._parseUpdate(data);
336
337 return Promise.mapSeries(this.data, item => model._updateWithStack(item._id, stack)).asCallback(callback);
338 }
339
340 /**
341 * Replace all documents.
342 *
343 * @param {Object} data
344 * @param {Function} [callback]
345 * @return {Promise}
346 */
347 replace(data, callback) {
348 const model = this._model;
349
350 return Promise.map(this.data, item => model.replaceById(item._id, data)).asCallback(callback);
351 }
352
353 /**
354 * Remove all documents.
355 *
356 * @param {Function} [callback]
357 * @return {Promise}
358 */
359 remove(callback) {
360 const model = this._model;
361
362 return Promise.mapSeries(this.data, item => model.removeById(item._id)).asCallback(callback);
363 }
364
365 /**
366 * Populates document references.
367 *
368 * @param {String|Object} expr
369 * @return {Query}
370 */
371 populate(expr) {
372 const stack = this._schema._parsePopulate(expr);
373 const { data, length } = this;
374 const model = this._model;
375
376 for (let i = 0; i < length; i++) {
377 data[i] = model._populate(data[i], stack);
378 }
379
380 return this;
381 }
382}
383
384Query.prototype.size = Query.prototype.count;
385
386Query.prototype.each = Query.prototype.forEach;
387
388Query.prototype.random = Query.prototype.shuffle;
389
390module.exports = Query;