171 lines
4.6 KiB
JavaScript
171 lines
4.6 KiB
JavaScript
'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;
|
|
}
|