You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1726 lines
43 KiB

  1. /*!
  2. * Module dependencies.
  3. */
  4. var EventEmitter = require('events').EventEmitter
  5. , setMaxListeners = EventEmitter.prototype.setMaxListeners
  6. , MongooseError = require('./error')
  7. , MixedSchema = require('./schema/mixed')
  8. , Schema = require('./schema')
  9. , ValidatorError = require('./schematype').ValidatorError
  10. , utils = require('./utils')
  11. , clone = utils.clone
  12. , isMongooseObject = utils.isMongooseObject
  13. , inspect = require('util').inspect
  14. , ElemMatchError = MongooseError.ElemMatchError
  15. , ValidationError = MongooseError.ValidationError
  16. , DocumentError = MongooseError.DocumentError
  17. , InternalCache = require('./internal')
  18. , deepEqual = utils.deepEqual
  19. , hooks = require('hooks')
  20. , DocumentArray
  21. , MongooseArray
  22. , Embedded
  23. /**
  24. * Document constructor.
  25. *
  26. * @param {Object} obj the values to set
  27. * @param {Object} [opts] optional object containing the fields which were selected in the query returning this document and any populated paths data
  28. * @param {Boolean} [skipId] bool, should we auto create an ObjectId _id
  29. * @inherits NodeJS EventEmitter http://nodejs.org/api/events.html#events_class_events_eventemitter
  30. * @event `init`: Emitted on a document after it has was retreived from the db and fully hydrated by Mongoose.
  31. * @event `save`: Emitted when the document is successfully saved
  32. * @api private
  33. */
  34. function Document (obj, fields, skipId) {
  35. this.$__ = new InternalCache;
  36. this.isNew = true;
  37. this.errors = undefined;
  38. var schema = this.schema;
  39. if ('boolean' === typeof fields) {
  40. this.$__.strictMode = fields;
  41. fields = undefined;
  42. } else {
  43. this.$__.strictMode = schema.options && schema.options.strict;
  44. this.$__.selected = fields;
  45. }
  46. var required = schema.requiredPaths();
  47. for (var i = 0; i < required.length; ++i) {
  48. this.$__.activePaths.require(required[i]);
  49. }
  50. setMaxListeners.call(this, 0);
  51. this._doc = this.$__buildDoc(obj, fields, skipId);
  52. if (obj) {
  53. this.set(obj, undefined, true);
  54. }
  55. this.$__registerHooks();
  56. }
  57. /*!
  58. * Inherit from EventEmitter.
  59. */
  60. Document.prototype.__proto__ = EventEmitter.prototype;
  61. /**
  62. * The documents schema.
  63. *
  64. * @api public
  65. * @property schema
  66. */
  67. Document.prototype.schema;
  68. /**
  69. * Boolean flag specifying if the document is new.
  70. *
  71. * @api public
  72. * @property isNew
  73. */
  74. Document.prototype.isNew;
  75. /**
  76. * The string version of this documents _id.
  77. *
  78. * ####Note:
  79. *
  80. * This getter exists on all documents by default. The getter can be disabled by setting the `id` [option](/docs/guide.html#id) of its `Schema` to false at construction time.
  81. *
  82. * new Schema({ name: String }, { id: false });
  83. *
  84. * @api public
  85. * @see Schema options /docs/guide.html#options
  86. * @property id
  87. */
  88. Document.prototype.id;
  89. /**
  90. * Hash containing current validation errors.
  91. *
  92. * @api public
  93. * @property errors
  94. */
  95. Document.prototype.errors;
  96. /**
  97. * Builds the default doc structure
  98. *
  99. * @param {Object} obj
  100. * @param {Object} [fields]
  101. * @param {Boolean} [skipId]
  102. * @return {Object}
  103. * @api private
  104. * @method $__buildDoc
  105. * @memberOf Document
  106. */
  107. Document.prototype.$__buildDoc = function (obj, fields, skipId) {
  108. var doc = {}
  109. , self = this
  110. , exclude
  111. , keys
  112. , key
  113. , ki
  114. // determine if this doc is a result of a query with
  115. // excluded fields
  116. if (fields && 'Object' === fields.constructor.name) {
  117. keys = Object.keys(fields);
  118. ki = keys.length;
  119. while (ki--) {
  120. if ('_id' !== keys[ki]) {
  121. exclude = 0 === fields[keys[ki]];
  122. break;
  123. }
  124. }
  125. }
  126. var paths = Object.keys(this.schema.paths)
  127. , plen = paths.length
  128. , ii = 0
  129. for (; ii < plen; ++ii) {
  130. var p = paths[ii];
  131. if ('_id' == p) {
  132. if (skipId) continue;
  133. if (obj && '_id' in obj) continue;
  134. }
  135. var type = this.schema.paths[p]
  136. , path = p.split('.')
  137. , len = path.length
  138. , last = len-1
  139. , curPath = ''
  140. , doc_ = doc
  141. , i = 0
  142. for (; i < len; ++i) {
  143. var piece = path[i]
  144. , def
  145. // support excluding intermediary levels
  146. if (exclude) {
  147. curPath += piece;
  148. if (curPath in fields) break;
  149. curPath += '.';
  150. }
  151. if (i === last) {
  152. if (fields) {
  153. if (exclude) {
  154. // apply defaults to all non-excluded fields
  155. if (p in fields) continue;
  156. def = type.getDefault(self, true);
  157. if ('undefined' !== typeof def) {
  158. doc_[piece] = def;
  159. self.$__.activePaths.default(p);
  160. }
  161. } else if (p in fields) {
  162. // selected field
  163. def = type.getDefault(self, true);
  164. if ('undefined' !== typeof def) {
  165. doc_[piece] = def;
  166. self.$__.activePaths.default(p);
  167. }
  168. }
  169. } else {
  170. def = type.getDefault(self, true);
  171. if ('undefined' !== typeof def) {
  172. doc_[piece] = def;
  173. self.$__.activePaths.default(p);
  174. }
  175. }
  176. } else {
  177. doc_ = doc_[piece] || (doc_[piece] = {});
  178. }
  179. }
  180. };
  181. return doc;
  182. };
  183. /**
  184. * Initializes the document without setters or marking anything modified.
  185. *
  186. * Called internally after a document is returned from mongodb.
  187. *
  188. * @param {Object} doc document returned by mongo
  189. * @param {Function} fn callback
  190. * @api private
  191. */
  192. Document.prototype.init = function (doc, opts, fn) {
  193. // do not prefix this method with $__ since its
  194. // used by public hooks
  195. if ('function' == typeof opts) {
  196. fn = opts;
  197. opts = null;
  198. }
  199. this.isNew = false;
  200. // handle docs with populated paths
  201. if (doc._id && opts && opts.populated && opts.populated.length) {
  202. var id = String(doc._id);
  203. for (var i = 0; i < opts.populated.length; ++i) {
  204. var item = opts.populated[i];
  205. this.populated(item.path, item._docs[id], item);
  206. }
  207. }
  208. init(this, doc, this._doc);
  209. this.$__storeShard();
  210. this.emit('init', this);
  211. if (fn) fn(null);
  212. return this;
  213. };
  214. /*!
  215. * Init helper.
  216. *
  217. * @param {Object} self document instance
  218. * @param {Object} obj raw mongodb doc
  219. * @param {Object} doc object we are initializing
  220. * @api private
  221. */
  222. function init (self, obj, doc, prefix) {
  223. prefix = prefix || '';
  224. var keys = Object.keys(obj)
  225. , len = keys.length
  226. , schema
  227. , path
  228. , i;
  229. while (len--) {
  230. i = keys[len];
  231. path = prefix + i;
  232. schema = self.schema.path(path);
  233. if (!schema && utils.isObject(obj[i]) &&
  234. (!obj[i].constructor || 'Object' == obj[i].constructor.name)) {
  235. // assume nested object
  236. if (!doc[i]) doc[i] = {};
  237. init(self, obj[i], doc[i], path + '.');
  238. } else {
  239. if (obj[i] === null) {
  240. doc[i] = null;
  241. } else if (obj[i] !== undefined) {
  242. if (schema) {
  243. self.$__try(function(){
  244. doc[i] = schema.cast(obj[i], self, true);
  245. });
  246. } else {
  247. doc[i] = obj[i];
  248. }
  249. }
  250. // mark as hydrated
  251. self.$__.activePaths.init(path);
  252. }
  253. }
  254. };
  255. /**
  256. * Stores the current values of the shard keys.
  257. *
  258. * ####Note:
  259. *
  260. * _Shard key values do not / are not allowed to change._
  261. *
  262. * @api private
  263. * @method $__storeShard
  264. * @memberOf Document
  265. */
  266. Document.prototype.$__storeShard = function () {
  267. // backwards compat
  268. var key = this.schema.options.shardKey || this.schema.options.shardkey;
  269. if (!(key && 'Object' == key.constructor.name)) return;
  270. var orig = this.$__.shardval = {}
  271. , paths = Object.keys(key)
  272. , len = paths.length
  273. , val
  274. for (var i = 0; i < len; ++i) {
  275. val = this.getValue(paths[i]);
  276. if (isMongooseObject(val)) {
  277. orig[paths[i]] = val.toObject({ depopulate: true })
  278. } else if (null != val && val.valueOf) {
  279. orig[paths[i]] = val.valueOf();
  280. } else {
  281. orig[paths[i]] = val;
  282. }
  283. }
  284. }
  285. /*!
  286. * Set up middleware support
  287. */
  288. for (var k in hooks) {
  289. Document.prototype[k] = Document[k] = hooks[k];
  290. }
  291. /**
  292. * Sends an update command with this document `_id` as the query selector.
  293. *
  294. * ####Example:
  295. *
  296. * weirdCar.update({$inc: {wheels:1}}, { w: 1 }, callback);
  297. *
  298. * ####Valid options:
  299. *
  300. * - same as in [Model.update](#model_Model.update)
  301. *
  302. * @see Model.update #model_Model.update
  303. * @param {Object} doc
  304. * @param {Object} options
  305. * @param {Function} callback
  306. * @return {Query}
  307. * @api public
  308. */
  309. Document.prototype.update = function update () {
  310. var args = utils.args(arguments);
  311. args.unshift({_id: this._id});
  312. return this.constructor.update.apply(this.constructor, args);
  313. }
  314. /**
  315. * Sets the value of a path, or many paths.
  316. *
  317. * ####Example:
  318. *
  319. * // path, value
  320. * doc.set(path, value)
  321. *
  322. * // object
  323. * doc.set({
  324. * path : value
  325. * , path2 : {
  326. * path : value
  327. * }
  328. * })
  329. *
  330. * // only-the-fly cast to number
  331. * doc.set(path, value, Number)
  332. *
  333. * // only-the-fly cast to string
  334. * doc.set(path, value, String)
  335. *
  336. * // changing strict mode behavior
  337. * doc.set(path, value, { strict: false });
  338. *
  339. * @param {String|Object} path path or object of key/vals to set
  340. * @param {Any} val the value to set
  341. * @param {Schema|String|Number|Buffer|etc..} [type] optionally specify a type for "on-the-fly" attributes
  342. * @param {Object} [options] optionally specify options that modify the behavior of the set
  343. * @api public
  344. */
  345. Document.prototype.set = function (path, val, type, options) {
  346. if (type && 'Object' == type.constructor.name) {
  347. options = type;
  348. type = undefined;
  349. }
  350. var merge = options && options.merge
  351. , adhoc = type && true !== type
  352. , constructing = true === type
  353. , adhocs
  354. var strict = options && 'strict' in options
  355. ? options.strict
  356. : this.$__.strictMode;
  357. if (adhoc) {
  358. adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {});
  359. adhocs[path] = Schema.interpretAsType(path, type);
  360. }
  361. if ('string' !== typeof path) {
  362. // new Document({ key: val })
  363. if (null === path || undefined === path) {
  364. var _ = path;
  365. path = val;
  366. val = _;
  367. } else {
  368. var prefix = val
  369. ? val + '.'
  370. : '';
  371. if (path instanceof Document) path = path._doc;
  372. var keys = Object.keys(path)
  373. , i = keys.length
  374. , pathtype
  375. , key
  376. while (i--) {
  377. key = keys[i];
  378. pathtype = this.schema.pathType(prefix + key);
  379. if (null != path[key]
  380. // need to know if plain object - no Buffer, ObjectId, ref, etc
  381. && utils.isObject(path[key])
  382. && (!path[key].constructor || 'Object' == path[key].constructor.name)
  383. && 'virtual' != pathtype
  384. && !(this.$__path(prefix + key) instanceof MixedSchema)
  385. && !(this.schema.paths[key] && this.schema.paths[key].options.ref)
  386. ) {
  387. this.set(path[key], prefix + key, constructing);
  388. } else if (strict) {
  389. if ('real' === pathtype || 'virtual' === pathtype) {
  390. this.set(prefix + key, path[key], constructing);
  391. } else if ('throw' == strict) {
  392. throw new Error("Field `" + key + "` is not in schema.");
  393. }
  394. } else if (undefined !== path[key]) {
  395. this.set(prefix + key, path[key], constructing);
  396. }
  397. }
  398. return this;
  399. }
  400. }
  401. // ensure _strict is honored for obj props
  402. // docschema = new Schema({ path: { nest: 'string' }})
  403. // doc.set('path', obj);
  404. var pathType = this.schema.pathType(path);
  405. if ('nested' == pathType && val && utils.isObject(val) &&
  406. (!val.constructor || 'Object' == val.constructor.name)) {
  407. if (!merge) this.setValue(path, null);
  408. this.set(val, path, constructing);
  409. return this;
  410. }
  411. var schema;
  412. var parts = path.split('.');
  413. if ('adhocOrUndefined' == pathType && strict) {
  414. // check for roots that are Mixed types
  415. var mixed;
  416. for (var i = 0; i < parts.length; ++i) {
  417. var subpath = parts.slice(0, i+1).join('.');
  418. schema = this.schema.path(subpath);
  419. if (schema instanceof MixedSchema) {
  420. // allow changes to sub paths of mixed types
  421. mixed = true;
  422. break;
  423. }
  424. }
  425. if (!mixed) {
  426. if ('throw' == strict) {
  427. throw new Error("Field `" + path + "` is not in schema.");
  428. }
  429. return this;
  430. }
  431. } else if ('virtual' == pathType) {
  432. schema = this.schema.virtualpath(path);
  433. schema.applySetters(val, this);
  434. return this;
  435. } else {
  436. schema = this.$__path(path);
  437. }
  438. var pathToMark;
  439. // When using the $set operator the path to the field must already exist.
  440. // Else mongodb throws: "LEFT_SUBFIELD only supports Object"
  441. if (parts.length <= 1) {
  442. pathToMark = path;
  443. } else {
  444. for (var i = 0; i < parts.length; ++i) {
  445. var subpath = parts.slice(0, i+1).join('.');
  446. if (this.isDirectModified(subpath) // earlier prefixes that are already
  447. // marked as dirty have precedence
  448. || this.get(subpath) === null) {
  449. pathToMark = subpath;
  450. break;
  451. }
  452. }
  453. if (!pathToMark) pathToMark = path;
  454. }
  455. // if this doc is being constructed we should not trigger getters
  456. var priorVal = constructing
  457. ? undefined
  458. : this.get(path);
  459. if (!schema || undefined === val) {
  460. this.$__set(pathToMark, path, constructing, parts, schema, val, priorVal);
  461. return this;
  462. }
  463. var self = this;
  464. var shouldSet = this.$__try(function(){
  465. val = schema.applySetters(val, self, false, priorVal);
  466. });
  467. if (shouldSet) {
  468. this.$__set(pathToMark, path, constructing, parts, schema, val, priorVal);
  469. }
  470. return this;
  471. }
  472. /**
  473. * Determine if we should mark this change as modified.
  474. *
  475. * @return {Boolean}
  476. * @api private
  477. * @method $__shouldModify
  478. * @memberOf Document
  479. */
  480. Document.prototype.$__shouldModify = function (
  481. pathToMark, path, constructing, parts, schema, val, priorVal) {
  482. if (this.isNew) return true;
  483. if (this.isDirectModified(pathToMark)) return false;
  484. if (undefined === val && !this.isSelected(path)) {
  485. // when a path is not selected in a query, its initial
  486. // value will be undefined.
  487. return true;
  488. }
  489. if (undefined === val && path in this.$__.activePaths.states.default) {
  490. // we're just unsetting the default value which was never saved
  491. return false;
  492. }
  493. if (!deepEqual(val, priorVal || this.get(path))) {
  494. return true;
  495. }
  496. if (!constructing &&
  497. null != val &&
  498. path in this.$__.activePaths.states.default &&
  499. deepEqual(val, schema.getDefault(this, constructing))) {
  500. // a path with a default was $unset on the server
  501. // and the user is setting it to the same value again
  502. return true;
  503. }
  504. return false;
  505. }
  506. /**
  507. * Handles the actual setting of the value and marking the path modified if appropriate.
  508. *
  509. * @api private
  510. * @method $__set
  511. * @memberOf Document
  512. */
  513. Document.prototype.$__set = function (
  514. pathToMark, path, constructing, parts, schema, val, priorVal) {
  515. var shouldModify = this.$__shouldModify.apply(this, arguments);
  516. if (shouldModify) {
  517. this.markModified(pathToMark, val);
  518. // handle directly setting arrays (gh-1126)
  519. MongooseArray || (MongooseArray = require('./types/array'));
  520. if (val instanceof MongooseArray) {
  521. val._registerAtomic('$set', val);
  522. }
  523. }
  524. var obj = this._doc
  525. , i = 0
  526. , l = parts.length
  527. for (; i < l; i++) {
  528. var next = i + 1
  529. , last = next === l;
  530. if (last) {
  531. obj[parts[i]] = val;
  532. } else {
  533. if (obj[parts[i]] && 'Object' === obj[parts[i]].constructor.name) {
  534. obj = obj[parts[i]];
  535. } else if (obj[parts[i]] && Array.isArray(obj[parts[i]])) {
  536. obj = obj[parts[i]];
  537. } else {
  538. obj = obj[parts[i]] = {};
  539. }
  540. }
  541. }
  542. }
  543. /**
  544. * Gets a raw value from a path (no getters)
  545. *
  546. * @param {String} path
  547. * @api private
  548. */
  549. Document.prototype.getValue = function (path) {
  550. return utils.getValue(path, this._doc);
  551. }
  552. /**
  553. * Sets a raw value for a path (no casting, setters, transformations)
  554. *
  555. * @param {String} path
  556. * @param {Object} value
  557. * @api private
  558. */
  559. Document.prototype.setValue = function (path, val) {
  560. utils.setValue(path, val, this._doc);
  561. return this;
  562. }
  563. /**
  564. * Returns the value of a path.
  565. *
  566. * ####Example
  567. *
  568. * // path
  569. * doc.get('age') // 47
  570. *
  571. * // dynamic casting to a string
  572. * doc.get('age', String) // "47"
  573. *
  574. * @param {String} path
  575. * @param {Schema|String|Number|Buffer|etc..} [type] optionally specify a type for on-the-fly attributes
  576. * @api public
  577. */
  578. Document.prototype.get = function (path, type) {
  579. var adhocs;
  580. if (type) {
  581. adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {});
  582. adhocs[path] = Schema.interpretAsType(path, type);
  583. }
  584. var schema = this.$__path(path) || this.schema.virtualpath(path)
  585. , pieces = path.split('.')
  586. , obj = this._doc;
  587. for (var i = 0, l = pieces.length; i < l; i++) {
  588. obj = undefined === obj || null === obj
  589. ? undefined
  590. : obj[pieces[i]];
  591. }
  592. if (schema) {
  593. obj = schema.applyGetters(obj, this);
  594. }
  595. return obj;
  596. };
  597. /**
  598. * Returns the schematype for the given `path`.
  599. *
  600. * @param {String} path
  601. * @api private
  602. * @method $__path
  603. * @memberOf Document
  604. */
  605. Document.prototype.$__path = function (path) {
  606. var adhocs = this.$__.adhocPaths
  607. , adhocType = adhocs && adhocs[path];
  608. if (adhocType) {
  609. return adhocType;
  610. } else {
  611. return this.schema.path(path);
  612. }
  613. };
  614. /**
  615. * Marks the path as having pending changes to write to the db.
  616. *
  617. * _Very helpful when using [Mixed](./schematypes.html#mixed) types._
  618. *
  619. * ####Example:
  620. *
  621. * doc.mixed.type = 'changed';
  622. * doc.markModified('mixed.type');
  623. * doc.save() // changes to mixed.type are now persisted
  624. *
  625. * @param {String} path the path to mark modified
  626. * @api public
  627. */
  628. Document.prototype.markModified = function (path) {
  629. this.$__.activePaths.modify(path);
  630. }
  631. /**
  632. * Catches errors that occur during execution of `fn` and stores them to later be passed when `save()` is executed.
  633. *
  634. * @param {Function} fn function to execute
  635. * @param {Object} scope the scope with which to call fn
  636. * @api private
  637. * @method $__try
  638. * @memberOf Document
  639. */
  640. Document.prototype.$__try = function (fn, scope) {
  641. var res;
  642. try {
  643. fn.call(scope);
  644. res = true;
  645. } catch (e) {
  646. this.$__error(e);
  647. res = false;
  648. }
  649. return res;
  650. };
  651. /**
  652. * Returns the list of paths that have been modified.
  653. *
  654. * @return {Array}
  655. * @api public
  656. */
  657. Document.prototype.modifiedPaths = function () {
  658. var directModifiedPaths = Object.keys(this.$__.activePaths.states.modify);
  659. return directModifiedPaths.reduce(function (list, path) {
  660. var parts = path.split('.');
  661. return list.concat(parts.reduce(function (chains, part, i) {
  662. return chains.concat(parts.slice(0, i).concat(part).join('.'));
  663. }, []));
  664. }, []);
  665. };
  666. /**
  667. * Returns true if this document was modified, else false.
  668. *
  669. * If `path` is given, checks if a path or any full path containing `path` as part of its path chain has been modified.
  670. *
  671. * ####Example
  672. *
  673. * doc.set('documents.0.title', 'changed');
  674. * doc.isModified() // true
  675. * doc.isModified('documents') // true
  676. * doc.isModified('documents.0.title') // true
  677. * doc.isDirectModified('documents') // false
  678. *
  679. * @param {String} [path] optional
  680. * @return {Boolean}
  681. * @api public
  682. */
  683. Document.prototype.isModified = function (path) {
  684. return path
  685. ? !!~this.modifiedPaths().indexOf(path)
  686. : this.$__.activePaths.some('modify');
  687. };
  688. /**
  689. * Returns true if `path` was directly set and modified, else false.
  690. *
  691. * ####Example
  692. *
  693. * doc.set('documents.0.title', 'changed');
  694. * doc.isDirectModified('documents.0.title') // true
  695. * doc.isDirectModified('documents') // false
  696. *
  697. * @param {String} path
  698. * @return {Boolean}
  699. * @api public
  700. */
  701. Document.prototype.isDirectModified = function (path) {
  702. return (path in this.$__.activePaths.states.modify);
  703. };
  704. /**
  705. * Checks if `path` was initialized.
  706. *
  707. * @param {String} path
  708. * @return {Boolean}
  709. * @api public
  710. */
  711. Document.prototype.isInit = function (path) {
  712. return (path in this.$__.activePaths.states.init);
  713. };
  714. /**
  715. * Checks if `path` was selected in the source query which initialized this document.
  716. *
  717. * ####Example
  718. *
  719. * Thing.findOne().select('name').exec(function (err, doc) {
  720. * doc.isSelected('name') // true
  721. * doc.isSelected('age') // false
  722. * })
  723. *
  724. * @param {String} path
  725. * @return {Boolean}
  726. * @api public
  727. */
  728. Document.prototype.isSelected = function isSelected (path) {
  729. if (this.$__.selected) {
  730. if ('_id' === path) {
  731. return 0 !== this.$__.selected._id;
  732. }
  733. var paths = Object.keys(this.$__.selected)
  734. , i = paths.length
  735. , inclusive = false
  736. , cur
  737. if (1 === i && '_id' === paths[0]) {
  738. // only _id was selected.
  739. return 0 === this.$__.selected._id;
  740. }
  741. while (i--) {
  742. cur = paths[i];
  743. if ('_id' == cur) continue;
  744. inclusive = !! this.$__.selected[cur];
  745. break;
  746. }
  747. if (path in this.$__.selected) {
  748. return inclusive;
  749. }
  750. i = paths.length;
  751. var pathDot = path + '.';
  752. while (i--) {
  753. cur = paths[i];
  754. if ('_id' == cur) continue;
  755. if (0 === cur.indexOf(pathDot)) {
  756. return inclusive;
  757. }
  758. if (0 === pathDot.indexOf(cur + '.')) {
  759. return inclusive;
  760. }
  761. }
  762. return ! inclusive;
  763. }
  764. return true;
  765. }
  766. /**
  767. * Executes registered validation rules for this document.
  768. *
  769. * ####Note:
  770. *
  771. * This method is called `pre` save and if a validation rule is violated, [save](#model_Model-save) is aborted and the error is returned to your `callback`.
  772. *
  773. * ####Example:
  774. *
  775. * doc.validate(function (err) {
  776. * if (err) handleError(err);
  777. * else // validation passed
  778. * });
  779. *
  780. * @param {Function} cb called after validation completes, passing an error if one occurred
  781. * @api public
  782. */
  783. Document.prototype.validate = function (cb) {
  784. var self = this
  785. // only validate required fields when necessary
  786. var paths = Object.keys(this.$__.activePaths.states.require).filter(function (path) {
  787. if (!self.isSelected(path) && !self.isModified(path)) return false;
  788. return true;
  789. });
  790. paths = paths.concat(Object.keys(this.$__.activePaths.states.init));
  791. paths = paths.concat(Object.keys(this.$__.activePaths.states.modify));
  792. paths = paths.concat(Object.keys(this.$__.activePaths.states.default));
  793. if (0 === paths.length) {
  794. complete();
  795. return this;
  796. }
  797. var validating = {}
  798. , total = 0;
  799. paths.forEach(validatePath);
  800. return this;
  801. function validatePath (path) {
  802. if (validating[path]) return;
  803. validating[path] = true;
  804. total++;
  805. process.nextTick(function(){
  806. var p = self.schema.path(path);
  807. if (!p) return --total || complete();
  808. var val = self.getValue(path);
  809. p.doValidate(val, function (err) {
  810. if (err) {
  811. self.invalidate(
  812. path
  813. , err
  814. , undefined
  815. , true // embedded docs
  816. );
  817. }
  818. --total || complete();
  819. }, self);
  820. });
  821. }
  822. function complete () {
  823. var err = self.$__.validationError;
  824. self.$__.validationError = undefined;
  825. self.emit('validate', self);
  826. cb(err);
  827. }
  828. };
  829. /**
  830. * Marks a path as invalid, causing validation to fail.
  831. *
  832. * @param {String} path the field to invalidate
  833. * @param {String|Error} err the error which states the reason `path` was invalid
  834. * @param {Object|String|Number|any} value optional invalid value
  835. * @api public
  836. */
  837. Document.prototype.invalidate = function (path, err, val) {
  838. if (!this.$__.validationError) {
  839. this.$__.validationError = new ValidationError(this);
  840. }
  841. if (!err || 'string' === typeof err) {
  842. // sniffing arguments:
  843. // need to handle case where user does not pass value
  844. // so our error message is cleaner
  845. err = 2 < arguments.length
  846. ? new ValidatorError(path, err, val)
  847. : new ValidatorError(path, err)
  848. }
  849. this.$__.validationError.errors[path] = err;
  850. }
  851. /**
  852. * Resets the internal modified state of this document.
  853. *
  854. * @api private
  855. * @return {Document}
  856. * @method $__reset
  857. * @memberOf Document
  858. */
  859. Document.prototype.$__reset = function reset () {
  860. var self = this;
  861. DocumentArray || (DocumentArray = require('./types/documentarray'));
  862. this.$__.activePaths
  863. .map('init', 'modify', function (i) {
  864. return self.getValue(i);
  865. })
  866. .filter(function (val) {
  867. return val && val instanceof DocumentArray && val.length;
  868. })
  869. .forEach(function (array) {
  870. var i = array.length;
  871. while (i--) {
  872. var doc = array[i];
  873. if (!doc) continue;
  874. doc.$__reset();
  875. }
  876. });
  877. // clear atomics
  878. this.$__dirty().forEach(function (dirt) {
  879. var type = dirt.value;
  880. if (type && type._atomics) {
  881. type._atomics = {};
  882. }
  883. });
  884. // Clear 'modify'('dirty') cache
  885. this.$__.activePaths.clear('modify');
  886. this.$__.validationError = undefined;
  887. this.errors = undefined;
  888. var self = this;
  889. this.schema.requiredPaths().forEach(function (path) {
  890. self.$__.activePaths.require(path);
  891. });
  892. return this;
  893. }
  894. /**
  895. * Returns this documents dirty paths / vals.
  896. *
  897. * @api private
  898. * @method $__dirty
  899. * @memberOf Document
  900. */
  901. Document.prototype.$__dirty = function () {
  902. var self = this;
  903. var all = this.$__.activePaths.map('modify', function (path) {
  904. return { path: path
  905. , value: self.getValue(path)
  906. , schema: self.$__path(path) };
  907. });
  908. // Sort dirty paths in a flat hierarchy.
  909. all.sort(function (a, b) {
  910. return (a.path < b.path ? -1 : (a.path > b.path ? 1 : 0));
  911. });
  912. // Ignore "foo.a" if "foo" is dirty already.
  913. var minimal = []
  914. , lastPath
  915. , top;
  916. all.forEach(function (item, i) {
  917. if (item.path.indexOf(lastPath) !== 0) {
  918. lastPath = item.path + '.';
  919. minimal.push(item);
  920. top = item;
  921. } else {
  922. // special case for top level MongooseArrays
  923. if (top.value && top.value._atomics && top.value.hasAtomics()) {
  924. // the `top` array itself and a sub path of `top` are being modified.
  925. // the only way to honor all of both modifications is through a $set
  926. // of entire array.
  927. top.value._atomics = {};
  928. top.value._atomics.$set = top.value;
  929. }
  930. }
  931. });
  932. top = lastPath = null;
  933. return minimal;
  934. }
  935. /*!
  936. * Compiles schemas.
  937. */
  938. function compile (tree, proto, prefix) {
  939. var keys = Object.keys(tree)
  940. , i = keys.length
  941. , limb
  942. , key;
  943. while (i--) {
  944. key = keys[i];
  945. limb = tree[key];
  946. define(key
  947. , (('Object' === limb.constructor.name
  948. && Object.keys(limb).length)
  949. && (!limb.type || limb.type.type)
  950. ? limb
  951. : null)
  952. , proto
  953. , prefix
  954. , keys);
  955. }
  956. };
  957. /*!
  958. * Defines the accessor named prop on the incoming prototype.
  959. */
  960. function define (prop, subprops, prototype, prefix, keys) {
  961. var prefix = prefix || ''
  962. , path = (prefix ? prefix + '.' : '') + prop;
  963. if (subprops) {
  964. Object.defineProperty(prototype, prop, {
  965. enumerable: true
  966. , get: function () {
  967. if (!this.$__.getters)
  968. this.$__.getters = {};
  969. if (!this.$__.getters[path]) {
  970. var nested = Object.create(this);
  971. // save scope for nested getters/setters
  972. if (!prefix) nested.$__.scope = this;
  973. // shadow inherited getters from sub-objects so
  974. // thing.nested.nested.nested... doesn't occur (gh-366)
  975. var i = 0
  976. , len = keys.length;
  977. for (; i < len; ++i) {
  978. // over-write the parents getter without triggering it
  979. Object.defineProperty(nested, keys[i], {
  980. enumerable: false // It doesn't show up.
  981. , writable: true // We can set it later.
  982. , configurable: true // We can Object.defineProperty again.
  983. , value: undefined // It shadows its parent.
  984. });
  985. }
  986. nested.toObject = function () {
  987. return this.get(path);
  988. };
  989. compile(subprops, nested, path);
  990. this.$__.getters[path] = nested;
  991. }
  992. return this.$__.getters[path];
  993. }
  994. , set: function (v) {
  995. if (v instanceof Document) v = v.toObject();
  996. return (this.$__.scope || this).set(path, v);
  997. }
  998. });
  999. } else {
  1000. Object.defineProperty(prototype, prop, {
  1001. enumerable: true
  1002. , get: function ( ) { return this.get.call(this.$__.scope || this, path); }
  1003. , set: function (v) { return this.set.call(this.$__.scope || this, path, v); }
  1004. });
  1005. }
  1006. };
  1007. /**
  1008. * Assigns/compiles `schema` into this documents prototype.
  1009. *
  1010. * @param {Schema} schema
  1011. * @api private
  1012. * @method $__setSchema
  1013. * @memberOf Document
  1014. */
  1015. Document.prototype.$__setSchema = function (schema) {
  1016. compile(schema.tree, this);
  1017. this.schema = schema;
  1018. }
  1019. /**
  1020. * Register default hooks
  1021. *
  1022. * @api private
  1023. * @method $__registerHooks
  1024. * @memberOf Document
  1025. */
  1026. Document.prototype.$__registerHooks = function () {
  1027. if (!this.save) return;
  1028. DocumentArray || (DocumentArray = require('./types/documentarray'));
  1029. this.pre('save', function (next) {
  1030. // validate all document arrays.
  1031. // we keep the error semaphore to make sure we don't
  1032. // call `save` unnecessarily (we only need 1 error)
  1033. var subdocs = 0
  1034. , error = false
  1035. , self = this;
  1036. // check for DocumentArrays
  1037. var arrays = this.$__.activePaths
  1038. .map('init', 'modify', function (i) {
  1039. return self.getValue(i);
  1040. })
  1041. .filter(function (val) {
  1042. return val && val instanceof DocumentArray && val.length;
  1043. });
  1044. if (!arrays.length)
  1045. return next();
  1046. arrays.forEach(function (array) {
  1047. if (error) return;
  1048. // handle sparse arrays by using for loop vs array.forEach
  1049. // which skips the sparse elements
  1050. var len = array.length
  1051. subdocs += len;
  1052. for (var i = 0; i < len; ++i) {
  1053. if (error) break;
  1054. var doc = array[i];
  1055. if (!doc) {
  1056. --subdocs || next();
  1057. continue;
  1058. }
  1059. doc.save(handleSave);
  1060. }
  1061. });
  1062. function handleSave (err) {
  1063. if (error) return;
  1064. if (err) {
  1065. self.$__.validationError = undefined;
  1066. return next(error = err);
  1067. }
  1068. --subdocs || next();
  1069. }
  1070. }, function (err) {
  1071. // emit on the Model if listening
  1072. if (this.constructor.listeners('error').length) {
  1073. this.constructor.emit('error', err);
  1074. } else {
  1075. // emit on the connection
  1076. if (!this.db.listeners('error').length) {
  1077. err.stack = 'No listeners detected, throwing. '
  1078. + 'Consider adding an error listener to your connection.\n'
  1079. + err.stack
  1080. }
  1081. this.db.emit('error', err);
  1082. }
  1083. }).pre('save', function checkForExistingErrors (next) {
  1084. // if any doc.set() calls failed
  1085. var err = this.$__.saveError;
  1086. if (err) {
  1087. this.$__.saveError = null;
  1088. next(err);
  1089. } else {
  1090. next();
  1091. }
  1092. }).pre('save', function validation (next) {
  1093. return this.validate(next);
  1094. });
  1095. // add user defined queues
  1096. this.$__doQueue();
  1097. };
  1098. /**
  1099. * Registers an error
  1100. *
  1101. * @param {Error} err
  1102. * @api private
  1103. * @method $__error
  1104. * @memberOf Document
  1105. */
  1106. Document.prototype.$__error = function (err) {
  1107. this.$__.saveError = err;
  1108. return this;
  1109. };
  1110. /**
  1111. * Executes methods queued from the Schema definition
  1112. *
  1113. * @api private
  1114. * @method $__doQueue
  1115. * @memberOf Document
  1116. */
  1117. Document.prototype.$__doQueue = function () {
  1118. var q = this.schema && this.schema.callQueue;
  1119. if (q) {
  1120. for (var i = 0, l = q.length; i < l; i++) {
  1121. this[q[i][0]].apply(this, q[i][1]);
  1122. }
  1123. }
  1124. return this;
  1125. };
  1126. /**
  1127. * Converts this document into a plain javascript object, ready for storage in MongoDB.
  1128. *
  1129. * Buffers are converted to instances of [mongodb.Binary](http://mongodb.github.com/node-mongodb-native/api-bson-generated/binary.html) for proper storage.
  1130. *
  1131. * ####Options:
  1132. *
  1133. * - `getters` apply all getters (path and virtual getters)
  1134. * - `virtuals` apply virtual getters (can override `getters` option)
  1135. * - `minimize` remove empty objects (defaults to true)
  1136. * - `transform` a transform function to apply to the resulting document before returning
  1137. *
  1138. * ####Getters/Virtuals
  1139. *
  1140. * Example of only applying path getters
  1141. *
  1142. * doc.toObject({ getters: true, virtuals: false })
  1143. *
  1144. * Example of only applying virtual getters
  1145. *
  1146. * doc.toObject({ virtuals: true })
  1147. *
  1148. * Example of applying both path and virtual getters
  1149. *
  1150. * doc.toObject({ getters: true })
  1151. *
  1152. * To apply these options to every document of your schema by default, set your [schemas](#schema_Schema) `toObject` option to the same argument.
  1153. *
  1154. * schema.set('toObject', { virtuals: true })
  1155. *
  1156. * ####Transform
  1157. *
  1158. * We may need to perform a transformation of the resulting object based on some criteria, say to remove some sensitive information or return a custom object. In this case we set the optional `transform` function.
  1159. *
  1160. * Transform functions receive three arguments
  1161. *
  1162. * function (doc, ret, options) {}
  1163. *
  1164. * - `doc` The mongoose document which is being converted
  1165. * - `ret` The plain object representation which has been converted
  1166. * - `options` The options in use (either schema options or the options passed inline)
  1167. *
  1168. * ####Example
  1169. *
  1170. * // specify the transform schema option
  1171. * if (!schema.options.toObject) schema.options.toObject = {};
  1172. * schema.options.toObject.transform = function (doc, ret, options) {
  1173. * // remove the _id of every document before returning the result
  1174. * delete ret._id;
  1175. * }
  1176. *
  1177. * // without the transformation in the schema
  1178. * doc.toObject(); // { _id: 'anId', name: 'Wreck-it Ralph' }
  1179. *
  1180. * // with the transformation
  1181. * doc.toObject(); // { name: 'Wreck-it Ralph' }
  1182. *
  1183. * With transformations we can do a lot more than remove properties. We can even return completely new customized objects:
  1184. *
  1185. * if (!schema.options.toObject) schema.options.toObject = {};
  1186. * schema.options.toObject.transform = function (doc, ret, options) {
  1187. * return { movie: ret.name }
  1188. * }
  1189. *
  1190. * // without the transformation in the schema
  1191. * doc.toObject(); // { _id: 'anId', name: 'Wreck-it Ralph' }
  1192. *
  1193. * // with the transformation
  1194. * doc.toObject(); // { movie: 'Wreck-it Ralph' }
  1195. *
  1196. * _Note: if a transform function returns `undefined`, the return value will be ignored._
  1197. *
  1198. * Transformations may also be applied inline, overridding any transform set in the options:
  1199. *
  1200. * function xform (doc, ret, options) {
  1201. * return { inline: ret.name, custom: true }
  1202. * }
  1203. *
  1204. * // pass the transform as an inline option
  1205. * doc.toObject({ transform: xform }); // { inline: 'Wreck-it Ralph', custom: true }
  1206. *
  1207. * _Note: if you call `toObject` and pass any options, the transform declared in your schema options will __not__ be applied. To force its application pass `transform: true`_
  1208. *
  1209. * if (!schema.options.toObject) schema.options.toObject = {};
  1210. * schema.options.toObject.hide = '_id';
  1211. * schema.options.toObject.transform = function (doc, ret, options) {
  1212. * if (options.hide) {
  1213. * options.hide.split(' ').forEach(function (prop) {
  1214. * delete ret[prop];
  1215. * });
  1216. * }
  1217. * }
  1218. *
  1219. * var doc = new Doc({ _id: 'anId', secret: 47, name: 'Wreck-it Ralph' });
  1220. * doc.toObject(); // { secret: 47, name: 'Wreck-it Ralph' }
  1221. * doc.toObject({ hide: 'secret _id' }); // { _id: 'anId', secret: 47, name: 'Wreck-it Ralph' }
  1222. * doc.toObject({ hide: 'secret _id', transform: true }); // { name: 'Wreck-it Ralph' }
  1223. *
  1224. * Transforms are applied to the document _and each of its sub-documents_. To determine whether or not you are currently operating on a sub-document you might use the following guard:
  1225. *
  1226. * if ('function' == typeof doc.ownerDocument) {
  1227. * // working with a sub doc
  1228. * }
  1229. *
  1230. * Transforms, like all of these options, are also available for `toJSON`.
  1231. *
  1232. * See [schema options](/docs/guide.html#toObject) for some more details.
  1233. *
  1234. * _During save, no custom options are applied to the document before being sent to the database._
  1235. *
  1236. * @param {Object} [options]
  1237. * @return {Object} js object
  1238. * @see mongodb.Binary http://mongodb.github.com/node-mongodb-native/api-bson-generated/binary.html
  1239. * @api public
  1240. */
  1241. Document.prototype.toObject = function (options) {
  1242. if (options && options.depopulate && this.$__.wasPopulated) {
  1243. // populated paths that we set to a document
  1244. return clone(this._id, options);
  1245. }
  1246. // When internally saving this document we always pass options,
  1247. // bypassing the custom schema options.
  1248. if (!(options && 'Object' == options.constructor.name)) {
  1249. options = this.schema.options.toObject
  1250. ? clone(this.schema.options.toObject)
  1251. : {};
  1252. }
  1253. ;('minimize' in options) || (options.minimize = this.schema.options.minimize);
  1254. var ret = clone(this._doc, options);
  1255. if (options.virtuals || options.getters && false !== options.virtuals) {
  1256. applyGetters(this, ret, 'virtuals', options);
  1257. }
  1258. if (options.getters) {
  1259. applyGetters(this, ret, 'paths', options);
  1260. }
  1261. if (true === options.transform) {
  1262. var opts = options.json
  1263. ? this.schema.options.toJSON
  1264. : this.schema.options.toObject;
  1265. if (opts) {
  1266. options.transform = opts.transform;
  1267. }
  1268. }
  1269. if ('function' == typeof options.transform) {
  1270. var xformed = options.transform(this, ret, options);
  1271. if ('undefined' != typeof xformed) ret = xformed;
  1272. }
  1273. return ret;
  1274. };
  1275. /*!
  1276. * Applies virtuals properties to `json`.
  1277. *
  1278. * @param {Document} self
  1279. * @param {Object} json
  1280. * @param {String} type either `virtuals` or `paths`
  1281. * @return {Object} `json`
  1282. */
  1283. function applyGetters (self, json, type, options) {
  1284. var schema = self.schema
  1285. , paths = Object.keys(schema[type])
  1286. , i = paths.length
  1287. , path
  1288. while (i--) {
  1289. path = paths[i];
  1290. var parts = path.split('.')
  1291. , plen = parts.length
  1292. , last = plen - 1
  1293. , branch = json
  1294. , part
  1295. for (var ii = 0; ii < plen; ++ii) {
  1296. part = parts[ii];
  1297. if (ii === last) {
  1298. branch[part] = clone(self.get(path), options);
  1299. } else {
  1300. branch = branch[part] || (branch[part] = {});
  1301. }
  1302. }
  1303. }
  1304. return json;
  1305. }
  1306. /**
  1307. * The return value of this method is used in calls to JSON.stringify(doc).
  1308. *
  1309. * This method accepts the same options as [Document#toObject](#document_Document-toObject). To apply the options to every document of your schema by default, set your [schemas](#schema_Schema) `toJSON` option to the same argument.
  1310. *
  1311. * schema.set('toJSON', { virtuals: true })
  1312. *
  1313. * See [schema options](/docs/guide.html#toJSON) for details.
  1314. *
  1315. * @param {Object} options same options as [Document#toObject](#document_Document-toObject)
  1316. * @return {Object}
  1317. * @see Document#toObject #document_Document-toObject
  1318. * @api public
  1319. */
  1320. Document.prototype.toJSON = function (options) {
  1321. // check for object type since an array of documents
  1322. // being stringified passes array indexes instead
  1323. // of options objects. JSON.stringify([doc, doc])
  1324. if (!(options && 'Object' == options.constructor.name)) {
  1325. options = this.schema.options.toJSON
  1326. ? clone(this.schema.options.toJSON)
  1327. : {};
  1328. }
  1329. options.json = true;
  1330. return this.toObject(options);
  1331. };
  1332. /**
  1333. * Helper for console.log
  1334. *
  1335. * @api public
  1336. */
  1337. Document.prototype.inspect = function (options) {
  1338. var opts = options && 'Object' == options.constructor.name ? options :
  1339. this.schema.options.toObject ? clone(this.schema.options.toObject) :
  1340. {};
  1341. opts.minimize = false;
  1342. return inspect(this.toObject(opts));
  1343. };
  1344. /**
  1345. * Helper for console.log
  1346. *
  1347. * @api public
  1348. * @method toString
  1349. */
  1350. Document.prototype.toString = Document.prototype.inspect;
  1351. /**
  1352. * Returns true if the Document stores the same data as doc.
  1353. *
  1354. * Documents are considered equal when they have matching `_id`s.
  1355. *
  1356. * @param {Document} doc a document to compare
  1357. * @return {Boolean}
  1358. * @api public
  1359. */
  1360. Document.prototype.equals = function (doc) {
  1361. var tid = this.get('_id');
  1362. var docid = doc.get('_id');
  1363. return tid && tid.equals
  1364. ? tid.equals(docid)
  1365. : tid === docid;
  1366. }
  1367. /**
  1368. * Populates document references, executing the `callback` when complete.
  1369. *
  1370. * ####Example:
  1371. *
  1372. * doc
  1373. * .populate('company')
  1374. * .populate({
  1375. * path: 'notes',
  1376. * match: /airline/,
  1377. * select: 'text',
  1378. * model: 'modelName'
  1379. * options: opts
  1380. * }, function (err, user) {
  1381. * assert(doc._id == user._id) // the document itself is passed
  1382. * })
  1383. *
  1384. * // summary
  1385. * doc.populate(path) // not executed
  1386. * doc.populate(options); // not executed
  1387. * doc.populate(path, callback) // executed
  1388. * doc.populate(options, callback); // executed
  1389. * doc.populate(callback); // executed
  1390. *
  1391. *
  1392. * ####NOTE:
  1393. *
  1394. * Population does not occur unless a `callback` is passed.
  1395. * Passing the same path a second time will overwrite the previous path options.
  1396. * See [Model.populate()](#model_Model.populate) for explaination of options.
  1397. *
  1398. * @see Model.populate #model_Model.populate
  1399. * @param {String|Object} [path] The path to populate or an options object
  1400. * @param {Function} [callback] When passed, population is invoked
  1401. * @api public
  1402. * @return {Document} this
  1403. */
  1404. Document.prototype.populate = function populate () {
  1405. if (0 === arguments.length) return this;
  1406. var pop = this.$__.populate || (this.$__.populate = {});
  1407. var args = utils.args(arguments);
  1408. var fn;
  1409. if ('function' == typeof args[args.length-1]) {
  1410. fn = args.pop();
  1411. }
  1412. // allow `doc.populate(callback)`
  1413. if (args.length) {
  1414. // use hash to remove duplicate paths
  1415. var res = utils.populate.apply(null, args);
  1416. for (var i = 0; i < res.length; ++i) {
  1417. pop[res[i].path] = res[i];
  1418. }
  1419. }
  1420. if (fn) {
  1421. var paths = utils.object.vals(pop);
  1422. this.$__.populate = undefined;
  1423. this.constructor.populate(this, paths, fn);
  1424. }
  1425. return this;
  1426. }
  1427. /**
  1428. * Gets _id(s) used during population of the given `path`.
  1429. *
  1430. * ####Example:
  1431. *
  1432. * Model.findOne().populate('author').exec(function (err, doc) {
  1433. * console.log(doc.author.name) // Dr.Seuss
  1434. * console.log(doc.populated('author')) // '5144cf8050f071d979c118a7'
  1435. * })
  1436. *
  1437. * If the path was not populated, undefined is returned.
  1438. *
  1439. * @param {String} path
  1440. * @return {Array|ObjectId|Number|Buffer|String|undefined}
  1441. * @api public
  1442. */
  1443. Document.prototype.populated = function (path, val, options) {
  1444. // val and options are internal
  1445. if (null == val) {
  1446. if (!this.$__.populated) return undefined;
  1447. var v = this.$__.populated[path];
  1448. if (v) return v.value;
  1449. return undefined;
  1450. }
  1451. // internal
  1452. if (true === val) {
  1453. if (!this.$__.populated) return undefined;
  1454. return this.$__.populated[path];
  1455. }
  1456. this.$__.populated || (this.$__.populated = {});
  1457. this.$__.populated[path] = { value: val, options: options };
  1458. return val;
  1459. }
  1460. /**
  1461. * Returns the full path to this document.
  1462. *
  1463. * @param {String} [path]
  1464. * @return {String}
  1465. * @api private
  1466. * @method $__fullPath
  1467. * @memberOf Document
  1468. */
  1469. Document.prototype.$__fullPath = function (path) {
  1470. // overridden in SubDocuments
  1471. return path || '';
  1472. }
  1473. /*!
  1474. * Module exports.
  1475. */
  1476. Document.ValidationError = ValidationError;
  1477. module.exports = exports = Document;
  1478. exports.Error = DocumentError;