model.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966
  1. 'use strict';
  2. const { EventEmitter } = require('events');
  3. const cloneDeep = require('rfdc')();
  4. const Promise = require('bluebird');
  5. const { parseArgs, getProp, setGetter, shuffle } = require('./util');
  6. const Document = require('./document');
  7. const Query = require('./query');
  8. const Schema = require('./schema');
  9. const Types = require('./types');
  10. const WarehouseError = require('./error');
  11. const PopulationError = require('./error/population');
  12. const Mutex = require('./mutex');
  13. class Model extends EventEmitter {
  14. /**
  15. * Model constructor.
  16. *
  17. * @param {string} name Model name
  18. * @param {Schema|object} [schema] Schema
  19. */
  20. constructor(name, schema_) {
  21. super();
  22. let schema;
  23. // Define schema
  24. if (schema_ instanceof Schema) {
  25. schema = schema_;
  26. } else if (typeof schema_ === 'object') {
  27. schema = new Schema(schema_);
  28. } else {
  29. schema = new Schema();
  30. }
  31. // Set `_id` path for schema
  32. if (!schema.path('_id')) {
  33. schema.path('_id', {type: Types.CUID, required: true});
  34. }
  35. this.name = name;
  36. this.data = {};
  37. this._mutex = new Mutex();
  38. this.schema = schema;
  39. this.length = 0;
  40. class _Document extends Document {
  41. constructor(data) {
  42. super(data);
  43. // Apply getters
  44. schema._applyGetters(this);
  45. }
  46. }
  47. this.Document = _Document;
  48. _Document.prototype._model = this;
  49. _Document.prototype._schema = schema;
  50. class _Query extends Query {}
  51. this.Query = _Query;
  52. _Query.prototype._model = this;
  53. _Query.prototype._schema = schema;
  54. // Apply static methods
  55. Object.assign(this, schema.statics);
  56. // Apply instance methods
  57. Object.assign(_Document.prototype, schema.methods);
  58. }
  59. /**
  60. * Creates a new document.
  61. *
  62. * @param {object} data
  63. * @return {Document}
  64. */
  65. new(data) {
  66. return new this.Document(data);
  67. }
  68. /**
  69. * Finds a document by its identifier.
  70. *
  71. * @param {*} id
  72. * @param {object} options
  73. * @param {boolean} [options.lean=false] Returns a plain JavaScript object
  74. * @return {Document|object}
  75. */
  76. findById(id, options_) {
  77. const raw = this.data[id];
  78. if (!raw) return;
  79. const options = Object.assign({
  80. lean: false
  81. }, options_);
  82. const data = cloneDeep(raw);
  83. return options.lean ? data : this.new(data);
  84. }
  85. /**
  86. * Checks if the model contains a document with the specified id.
  87. *
  88. * @param {*} id
  89. * @return {boolean}
  90. */
  91. has(id) {
  92. return Boolean(this.data[id]);
  93. }
  94. /**
  95. * Acquires write lock.
  96. *
  97. * @param {*} id
  98. * @return {Promise}
  99. * @private
  100. */
  101. _acquireWriteLock(id) {
  102. const mutex = this._mutex;
  103. return new Promise((resolve, reject) => {
  104. mutex.lock(resolve);
  105. }).disposer(() => {
  106. mutex.unlock();
  107. });
  108. }
  109. /**
  110. * Inserts a document.
  111. *
  112. * @param {Document|object} data
  113. * @return {Promise}
  114. * @private
  115. */
  116. _insertOne(data_) {
  117. const schema = this.schema;
  118. // Apply getters
  119. const data = data_ instanceof this.Document ? data_ : this.new(data_);
  120. const id = data._id;
  121. // Check ID
  122. if (!id) {
  123. return Promise.reject(new WarehouseError('ID is not defined', WarehouseError.ID_UNDEFINED));
  124. }
  125. if (this.has(id)) {
  126. return Promise.reject(new WarehouseError('ID `' + id + '` has been used', WarehouseError.ID_EXIST));
  127. }
  128. // Apply setters
  129. const result = data.toObject();
  130. schema._applySetters(result);
  131. // Pre-hooks
  132. return execHooks(schema, 'pre', 'save', data).then(data => {
  133. // Insert data
  134. this.data[id] = result;
  135. this.length++;
  136. this.emit('insert', data);
  137. return execHooks(schema, 'post', 'save', data);
  138. });
  139. }
  140. /**
  141. * Inserts a document.
  142. *
  143. * @param {object} data
  144. * @param {function} [callback]
  145. * @return {Promise}
  146. */
  147. insertOne(data, callback) {
  148. return Promise.using(this._acquireWriteLock(), () => this._insertOne(data)).asCallback(callback);
  149. }
  150. /**
  151. * Inserts documents.
  152. *
  153. * @param {object|array} data
  154. * @param {function} [callback]
  155. * @return {Promise}
  156. */
  157. insert(data, callback) {
  158. if (Array.isArray(data)) {
  159. return Promise.mapSeries(data, item => this.insertOne(item)).asCallback(callback);
  160. }
  161. return this.insertOne(data, callback);
  162. }
  163. /**
  164. * Inserts the document if it does not exist; otherwise updates it.
  165. *
  166. * @param {object} data
  167. * @param {function} [callback]
  168. * @return {Promise}
  169. */
  170. save(data, callback) {
  171. const id = data._id;
  172. if (!id) return this.insertOne(data, callback);
  173. return Promise.using(this._acquireWriteLock(), () => {
  174. if (this.has(id)) {
  175. return this._replaceById(id, data);
  176. }
  177. return this._insertOne(data);
  178. }).asCallback(callback);
  179. }
  180. /**
  181. * Updates a document with a compiled stack.
  182. *
  183. * @param {*} id
  184. * @param {array} stack
  185. * @return {Promise}
  186. * @private
  187. */
  188. _updateWithStack(id, stack) {
  189. const schema = this.schema;
  190. const data = this.data[id];
  191. if (!data) {
  192. return Promise.reject(new WarehouseError('ID `' + id + '` does not exist', WarehouseError.ID_NOT_EXIST));
  193. }
  194. // Clone data
  195. let result = cloneDeep(data);
  196. // Update
  197. for (let i = 0, len = stack.length; i < len; i++) {
  198. stack[i](result);
  199. }
  200. // Apply getters
  201. const doc = this.new(result);
  202. // Apply setters
  203. result = doc.toObject();
  204. schema._applySetters(result);
  205. // Pre-hooks
  206. return execHooks(schema, 'pre', 'save', doc).then(data => {
  207. // Update data
  208. this.data[id] = result;
  209. this.emit('update', data);
  210. return execHooks(schema, 'post', 'save', data);
  211. });
  212. }
  213. /**
  214. * Finds a document by its identifier and update it.
  215. *
  216. * @param {*} id
  217. * @param {object} update
  218. * @param {function} [callback]
  219. * @return {Promise}
  220. */
  221. updateById(id, update, callback) {
  222. return Promise.using(this._acquireWriteLock(), () => {
  223. const stack = this.schema._parseUpdate(update);
  224. return this._updateWithStack(id, stack);
  225. }).asCallback(callback);
  226. }
  227. /**
  228. * Updates matching documents.
  229. *
  230. * @param {object} query
  231. * @param {object} data
  232. * @param {function} [callback]
  233. * @return {Promise}
  234. */
  235. update(query, data, callback) {
  236. return this.find(query).update(data, callback);
  237. }
  238. /**
  239. * Finds a document by its identifier and replace it.
  240. *
  241. * @param {*} id
  242. * @param {object} data
  243. * @return {Promise}
  244. * @private
  245. */
  246. _replaceById(id, data_) {
  247. const schema = this.schema;
  248. if (!this.has(id)) {
  249. return Promise.reject(new WarehouseError('ID `' + id + '` does not exist', WarehouseError.ID_NOT_EXIST));
  250. }
  251. data_._id = id;
  252. // Apply getters
  253. const data = data_ instanceof this.Document ? data_ : this.new(data_);
  254. // Apply setters
  255. const result = data.toObject();
  256. schema._applySetters(result);
  257. // Pre-hooks
  258. return execHooks(schema, 'pre', 'save', data).then(data => {
  259. // Replace data
  260. this.data[id] = result;
  261. this.emit('update', data);
  262. return execHooks(schema, 'post', 'save', data);
  263. });
  264. }
  265. /**
  266. * Finds a document by its identifier and replace it.
  267. *
  268. * @param {*} id
  269. * @param {object} data
  270. * @param {function} [callback]
  271. * @return {Promise}
  272. */
  273. replaceById(id, data, callback) {
  274. return Promise.using(this._acquireWriteLock(), () => this._replaceById(id, data)).asCallback(callback);
  275. }
  276. /**
  277. * Replaces matching documents.
  278. *
  279. * @param {object} query
  280. * @param {object} data
  281. * @param {function} [callback]
  282. * @return {Promise}
  283. */
  284. replace(query, data, callback) {
  285. return this.find(query).replace(data, callback);
  286. }
  287. /**
  288. * Finds a document by its identifier and remove it.
  289. *
  290. * @param {*} id
  291. * @param {function} [callback]
  292. * @return {Promise}
  293. * @private
  294. */
  295. _removeById(id) {
  296. const schema = this.schema;
  297. const data = this.data[id];
  298. if (!data) {
  299. return Promise.reject(new WarehouseError('ID `' + id + '` does not exist', WarehouseError.ID_NOT_EXIST));
  300. }
  301. // Pre-hooks
  302. return execHooks(schema, 'pre', 'remove', data).then(data => {
  303. // Remove data
  304. this.data[id] = null;
  305. this.length--;
  306. this.emit('remove', data);
  307. return execHooks(schema, 'post', 'remove', data);
  308. });
  309. }
  310. /**
  311. * Finds a document by its identifier and remove it.
  312. *
  313. * @param {*} id
  314. * @param {function} [callback]
  315. * @return {Promise}
  316. */
  317. removeById(id, callback) {
  318. return Promise.using(this._acquireWriteLock(), () => this._removeById(id)).asCallback(callback);
  319. }
  320. /**
  321. * Removes matching documents.
  322. *
  323. * @param {object} query
  324. * @param {object} [callback]
  325. * @return {Promise}
  326. */
  327. remove(query, callback) {
  328. return this.find(query).remove(callback);
  329. }
  330. /**
  331. * Deletes a model.
  332. */
  333. destroy() {
  334. this._database._models[this.name] = null;
  335. }
  336. /**
  337. * Returns the number of elements.
  338. *
  339. * @return {number}
  340. */
  341. count() {
  342. return this.length;
  343. }
  344. /**
  345. * Iterates over all documents.
  346. *
  347. * @param {function} iterator
  348. * @param {object} [options] See {@link Model#findById}.
  349. */
  350. forEach(iterator, options) {
  351. const keys = Object.keys(this.data);
  352. let num = 0;
  353. for (let i = 0, len = keys.length; i < len; i++) {
  354. const data = this.findById(keys[i], options);
  355. if (data) iterator(data, num++);
  356. }
  357. }
  358. /**
  359. * Returns an array containing all documents.
  360. *
  361. * @param {Object} [options] See {@link Model#findById}.
  362. * @return {Array}
  363. */
  364. toArray(options) {
  365. const result = new Array(this.length);
  366. this.forEach((item, i) => {
  367. result[i] = item;
  368. }, options);
  369. return result;
  370. }
  371. /**
  372. * Finds matching documents.
  373. *
  374. * @param {Object} query
  375. * @param {Object} [options]
  376. * @param {Number} [options.limit=0] Limits the number of documents returned.
  377. * @param {Number} [options.skip=0] Skips the first elements.
  378. * @param {Boolean} [options.lean=false] Returns a plain JavaScript object.
  379. * @return {Query|Array}
  380. */
  381. find(query, options_) {
  382. const options = options_ || {};
  383. const filter = this.schema._execQuery(query);
  384. const keys = Object.keys(this.data);
  385. const len = keys.length;
  386. let limit = options.limit || this.length;
  387. let skip = options.skip;
  388. const data = this.data;
  389. const arr = [];
  390. for (let i = 0; limit && i < len; i++) {
  391. const key = keys[i];
  392. const item = data[key];
  393. if (item && filter(item)) {
  394. if (skip) {
  395. skip--;
  396. } else {
  397. arr.push(this.findById(key, options));
  398. limit--;
  399. }
  400. }
  401. }
  402. return options.lean ? arr : new this.Query(arr);
  403. }
  404. /**
  405. * Finds the first matching documents.
  406. *
  407. * @param {Object} query
  408. * @param {Object} [options]
  409. * @param {Number} [options.skip=0] Skips the first elements.
  410. * @param {Boolean} [options.lean=false] Returns a plain JavaScript object.
  411. * @return {Document|Object}
  412. */
  413. findOne(query, options_) {
  414. const options = options_ || {};
  415. options.limit = 1;
  416. const result = this.find(query, options);
  417. return options.lean ? result[0] : result.data[0];
  418. }
  419. /**
  420. * Sorts documents. See {@link Query#sort}.
  421. *
  422. * @param {String|Object} orderby
  423. * @param {String|Number} [order]
  424. * @return {Query}
  425. */
  426. sort(orderby, order) {
  427. const sort = parseArgs(orderby, order);
  428. const fn = this.schema._execSort(sort);
  429. return new this.Query(this.toArray().sort(fn));
  430. }
  431. /**
  432. * Returns the document at the specified index. `num` can be a positive or
  433. * negative number.
  434. *
  435. * @param {Number} i
  436. * @param {Object} [options] See {@link Model#findById}.
  437. * @return {Document|Object}
  438. */
  439. eq(i_, options) {
  440. let index = i_ < 0 ? this.length + i_ : i_;
  441. const data = this.data;
  442. const keys = Object.keys(data);
  443. for (let i = 0, len = keys.length; i < len; i++) {
  444. const key = keys[i];
  445. const item = data[key];
  446. if (!item) continue;
  447. if (index) {
  448. index--;
  449. } else {
  450. return this.findById(key, options);
  451. }
  452. }
  453. }
  454. /**
  455. * Returns the first document.
  456. *
  457. * @param {Object} [options] See {@link Model#findById}.
  458. * @return {Document|Object}
  459. */
  460. first(options) {
  461. return this.eq(0, options);
  462. }
  463. /**
  464. * Returns the last document.
  465. *
  466. * @param {Object} [options] See {@link Model#findById}.
  467. * @return {Document|Object}
  468. */
  469. last(options) {
  470. return this.eq(-1, options);
  471. }
  472. /**
  473. * Returns the specified range of documents.
  474. *
  475. * @param {Number} start
  476. * @param {Number} [end]
  477. * @return {Query}
  478. */
  479. slice(start_, end_) {
  480. const total = this.length;
  481. let start = start_ | 0;
  482. if (start < 0) start += total;
  483. if (start > total - 1) return new this.Query([]);
  484. let end = end_ | 0 || total;
  485. if (end < 0) end += total;
  486. let len = start > end ? 0 : end - start;
  487. if (len > total) len = total - start;
  488. if (!len) return new this.Query([]);
  489. const arr = new Array(len);
  490. const keys = Object.keys(this.data);
  491. const keysLen = keys.length;
  492. let num = 0;
  493. for (let i = 0; num < len && i < keysLen; i++) {
  494. const data = this.findById(keys[i]);
  495. if (!data) continue;
  496. if (start) {
  497. start--;
  498. } else {
  499. arr[num++] = data;
  500. }
  501. }
  502. return new this.Query(arr);
  503. }
  504. /**
  505. * Limits the number of documents returned.
  506. *
  507. * @param {Number} i
  508. * @return {Query}
  509. */
  510. limit(i) {
  511. return this.slice(0, i);
  512. }
  513. /**
  514. * Specifies the number of items to skip.
  515. *
  516. * @param {Number} i
  517. * @return {Query}
  518. */
  519. skip(i) {
  520. return this.slice(i);
  521. }
  522. /**
  523. * Returns documents in a reversed order.
  524. *
  525. * @return {Query}
  526. */
  527. reverse() {
  528. return new this.Query(this.toArray().reverse());
  529. }
  530. /**
  531. * Returns documents in random order.
  532. *
  533. * @return {Query}
  534. */
  535. shuffle() {
  536. return new this.Query(shuffle(this.toArray()));
  537. }
  538. /**
  539. * Creates an array of values by iterating each element in the collection.
  540. *
  541. * @param {Function} iterator
  542. * @param {Object} [options]
  543. * @return {Array}
  544. */
  545. map(iterator, options) {
  546. const result = new Array(this.length);
  547. const keys = Object.keys(this.data);
  548. const len = keys.length;
  549. for (let i = 0, num = 0; i < len; i++) {
  550. const data = this.findById(keys[i], options);
  551. if (data) {
  552. result[num] = iterator(data, num);
  553. num++;
  554. }
  555. }
  556. return result;
  557. }
  558. /**
  559. * Reduces a collection to a value which is the accumulated result of iterating
  560. * each element in the collection.
  561. *
  562. * @param {Function} iterator
  563. * @param {*} [initial] By default, the initial value is the first document.
  564. * @return {*}
  565. */
  566. reduce(iterator, initial) {
  567. const arr = this.toArray();
  568. const len = this.length;
  569. let i, result;
  570. if (initial === undefined) {
  571. i = 1;
  572. result = arr[0];
  573. } else {
  574. i = 0;
  575. result = initial;
  576. }
  577. for (; i < len; i++) {
  578. result = iterator(result, arr[i], i);
  579. }
  580. return result;
  581. }
  582. /**
  583. * Reduces a collection to a value which is the accumulated result of iterating
  584. * each element in the collection from right to left.
  585. *
  586. * @param {Function} iterator
  587. * @param {*} [initial] By default, the initial value is the last document.
  588. * @return {*}
  589. */
  590. reduceRight(iterator, initial) {
  591. const arr = this.toArray();
  592. const len = this.length;
  593. let i, result;
  594. if (initial === undefined) {
  595. i = len - 2;
  596. result = arr[len - 1];
  597. } else {
  598. i = len - 1;
  599. result = initial;
  600. }
  601. for (; i >= 0; i--) {
  602. result = iterator(result, arr[i], i);
  603. }
  604. return result;
  605. }
  606. /**
  607. * Creates a new array with all documents that pass the test implemented by the
  608. * provided function.
  609. *
  610. * @param {Function} iterator
  611. * @param {Object} [options]
  612. * @return {Query}
  613. */
  614. filter(iterator, options) {
  615. const arr = [];
  616. this.forEach((item, i) => {
  617. if (iterator(item, i)) arr.push(item);
  618. }, options);
  619. return new this.Query(arr);
  620. }
  621. /**
  622. * Tests whether all documents pass the test implemented by the provided
  623. * function.
  624. *
  625. * @param {Function} iterator
  626. * @return {Boolean}
  627. */
  628. every(iterator) {
  629. const keys = Object.keys(this.data);
  630. const len = keys.length;
  631. let num = 0;
  632. if (!len) return true;
  633. for (let i = 0; i < len; i++) {
  634. const data = this.findById(keys[i]);
  635. if (data) {
  636. if (!iterator(data, num++)) return false;
  637. }
  638. }
  639. return true;
  640. }
  641. /**
  642. * Tests whether some documents pass the test implemented by the provided
  643. * function.
  644. *
  645. * @param {Function} iterator
  646. * @return {Boolean}
  647. */
  648. some(iterator) {
  649. const keys = Object.keys(this.data);
  650. const len = keys.length;
  651. let num = 0;
  652. if (!len) return false;
  653. for (let i = 0; i < len; i++) {
  654. const data = this.findById(keys[i]);
  655. if (data) {
  656. if (iterator(data, num++)) return true;
  657. }
  658. }
  659. return false;
  660. }
  661. /**
  662. * Returns a getter function for normal population.
  663. *
  664. * @param {Object} data
  665. * @param {Model} model
  666. * @param {Object} options
  667. * @return {Function}
  668. * @private
  669. */
  670. _populateGetter(data, model, options) {
  671. let hasCache = false;
  672. let cache;
  673. return () => {
  674. if (!hasCache) {
  675. cache = model.findById(data);
  676. hasCache = true;
  677. }
  678. return cache;
  679. };
  680. }
  681. /**
  682. * Returns a getter function for array population.
  683. *
  684. * @param {Object} data
  685. * @param {Model} model
  686. * @param {Object} options
  687. * @return {Function}
  688. * @private
  689. */
  690. _populateGetterArray(data, model, options) {
  691. const Query = model.Query;
  692. let hasCache = false;
  693. let cache;
  694. return () => {
  695. if (!hasCache) {
  696. let arr = [];
  697. for (let i = 0, len = data.length; i < len; i++) {
  698. arr.push(model.findById(data[i]));
  699. }
  700. if (options.match) {
  701. cache = new Query(arr).find(options.match, options);
  702. } else if (options.skip) {
  703. if (options.limit) {
  704. arr = arr.slice(options.skip, options.skip + options.limit);
  705. } else {
  706. arr = arr.slice(options.skip);
  707. }
  708. cache = new Query(arr);
  709. } else if (options.limit) {
  710. cache = new Query(arr.slice(0, options.limit));
  711. } else {
  712. cache = new Query(arr);
  713. }
  714. if (options.sort) {
  715. cache = cache.sort(options.sort);
  716. }
  717. hasCache = true;
  718. }
  719. return cache;
  720. };
  721. }
  722. /**
  723. * Populates document references with a compiled stack.
  724. *
  725. * @param {Object} data
  726. * @param {Array} stack
  727. * @return {Object}
  728. * @private
  729. */
  730. _populate(data, stack) {
  731. const models = this._database._models;
  732. for (let i = 0, len = stack.length; i < len; i++) {
  733. const item = stack[i];
  734. const model = models[item.model];
  735. if (!model) {
  736. throw new PopulationError('Model `' + item.model + '` does not exist');
  737. }
  738. const path = item.path;
  739. const prop = getProp(data, path);
  740. if (Array.isArray(prop)) {
  741. setGetter(data, path, this._populateGetterArray(prop, model, item));
  742. } else {
  743. setGetter(data, path, this._populateGetter(prop, model, item));
  744. }
  745. }
  746. return data;
  747. }
  748. /**
  749. * Populates document references.
  750. *
  751. * @param {String|Object} path
  752. * @return {Query}
  753. */
  754. populate(path) {
  755. if (!path) throw new TypeError('path is required');
  756. const stack = this.schema._parsePopulate(path);
  757. const arr = new Array(this.length);
  758. this.forEach((item, i) => {
  759. arr[i] = this._populate(item, stack);
  760. });
  761. return new Query(arr);
  762. }
  763. /**
  764. * Imports data.
  765. *
  766. * @param {Array} arr
  767. * @private
  768. */
  769. _import(arr) {
  770. const len = arr.length;
  771. const data = this.data;
  772. const schema = this.schema;
  773. for (let i = 0; i < len; i++) {
  774. const item = arr[i];
  775. data[item._id] = schema._parseDatabase(item);
  776. }
  777. this.length = len;
  778. }
  779. /**
  780. * Exports data.
  781. *
  782. * @return {String}
  783. * @private
  784. */
  785. _export() {
  786. return JSON.stringify(this.toJSON());
  787. }
  788. toJSON() {
  789. const result = new Array(this.length);
  790. const { data, schema } = this;
  791. const keys = Object.keys(data);
  792. const { length } = keys;
  793. for (let i = 0, num = 0; i < length; i++) {
  794. const raw = data[keys[i]];
  795. if (raw) {
  796. result[num++] = schema._exportDatabase(cloneDeep(raw));
  797. }
  798. }
  799. return result;
  800. }
  801. }
  802. Model.prototype.get = Model.prototype.findById;
  803. function execHooks(schema, type, event, data) {
  804. const hooks = schema.hooks[type][event];
  805. if (!hooks.length) return Promise.resolve(data);
  806. return Promise.each(hooks, hook => hook(data)).thenReturn(data);
  807. }
  808. Model.prototype.size = Model.prototype.count;
  809. Model.prototype.each = Model.prototype.forEach;
  810. Model.prototype.random = Model.prototype.shuffle;
  811. module.exports = Model;