'use strict'; const documentSchemaSymbol = require('../../helpers/symbols').documentSchemaSymbol; const get = require('../../helpers/get'); const getSymbol = require('../../helpers/symbols').getSymbol; const utils = require('../../utils'); let Document; /*! * exports */ exports.compile = compile; exports.defineKey = defineKey; /*! * Compiles schemas. */ function compile(tree, proto, prefix, options) { Document = Document || require('../../document'); const keys = Object.keys(tree); const len = keys.length; let limb; let key; for (let i = 0; i < len; ++i) { key = keys[i]; limb = tree[key]; const hasSubprops = utils.isPOJO(limb) && Object.keys(limb).length && (!limb[options.typeKey] || (options.typeKey === 'type' && limb.type.type)); const subprops = hasSubprops ? limb : null; defineKey(key, subprops, proto, prefix, keys, options); } } /*! * Defines the accessor named prop on the incoming prototype. */ function defineKey(prop, subprops, prototype, prefix, keys, options) { Document = Document || require('../../document'); const path = (prefix ? prefix + '.' : '') + prop; prefix = prefix || ''; if (subprops) { Object.defineProperty(prototype, prop, { enumerable: true, configurable: true, get: function() { const _this = this; if (!this.$__.getters) { this.$__.getters = {}; } if (!this.$__.getters[path]) { const nested = Object.create(Document.prototype, getOwnPropertyDescriptors(this)); // save scope for nested getters/setters if (!prefix) { nested.$__.scope = this; } nested.$__.nestedPath = path; Object.defineProperty(nested, 'schema', { enumerable: false, configurable: true, writable: false, value: prototype.schema }); Object.defineProperty(nested, documentSchemaSymbol, { enumerable: false, configurable: true, writable: false, value: prototype.schema }); Object.defineProperty(nested, 'toObject', { enumerable: false, configurable: true, writable: false, value: function() { return utils.clone(_this.get(path, null, { virtuals: get(this, 'schema.options.toObject.virtuals', null) })); } }); Object.defineProperty(nested, 'toJSON', { enumerable: false, configurable: true, writable: false, value: function() { return _this.get(path, null, { virtuals: get(_this, 'schema.options.toJSON.virtuals', null) }); } }); Object.defineProperty(nested, '$__isNested', { enumerable: false, configurable: true, writable: false, value: true }); const _isEmptyOptions = Object.freeze({ minimize: true, virtuals: false, getters: false, transform: false }); Object.defineProperty(nested, '$isEmpty', { enumerable: false, configurable: true, writable: false, value: function() { return Object.keys(this.get(path, null, _isEmptyOptions) || {}).length === 0; } }); compile(subprops, nested, path, options); this.$__.getters[path] = nested; } return this.$__.getters[path]; }, set: function(v) { if (v instanceof Document) { v = v.toObject({ transform: false }); } const doc = this.$__.scope || this; return doc.$set(path, v); } }); } else { Object.defineProperty(prototype, prop, { enumerable: true, configurable: true, get: function() { return this[getSymbol].call(this.$__.scope || this, path); }, set: function(v) { return this.$set.call(this.$__.scope || this, path, v); } }); } } // gets descriptors for all properties of `object` // makes all properties non-enumerable to match previous behavior to #2211 function getOwnPropertyDescriptors(object) { const result = {}; Object.getOwnPropertyNames(object).forEach(function(key) { result[key] = Object.getOwnPropertyDescriptor(object, key); // Assume these are schema paths, ignore them re: #5470 if (result[key].get) { delete result[key]; return; } result[key].enumerable = ['isNew', '$__', 'errors', '_doc', '$locals'].indexOf(key) === -1; }); return result; }