compile.js 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. 'use strict';
  2. const get = require('../../helpers/get');
  3. const getSymbol = require('../../helpers/symbols').getSymbol;
  4. const utils = require('../../utils');
  5. let Document;
  6. /*!
  7. * exports
  8. */
  9. exports.compile = compile;
  10. exports.defineKey = defineKey;
  11. /*!
  12. * Compiles schemas.
  13. */
  14. function compile(tree, proto, prefix, options) {
  15. Document = Document || require('../../document');
  16. const keys = Object.keys(tree);
  17. const len = keys.length;
  18. let limb;
  19. let key;
  20. for (let i = 0; i < len; ++i) {
  21. key = keys[i];
  22. limb = tree[key];
  23. const hasSubprops = utils.isPOJO(limb) && Object.keys(limb).length &&
  24. (!limb[options.typeKey] || (options.typeKey === 'type' && limb.type.type));
  25. const subprops = hasSubprops ? limb : null;
  26. defineKey(key, subprops, proto, prefix, keys, options);
  27. }
  28. }
  29. /*!
  30. * Defines the accessor named prop on the incoming prototype.
  31. */
  32. function defineKey(prop, subprops, prototype, prefix, keys, options) {
  33. Document = Document || require('../../document');
  34. const path = (prefix ? prefix + '.' : '') + prop;
  35. prefix = prefix || '';
  36. if (subprops) {
  37. Object.defineProperty(prototype, prop, {
  38. enumerable: true,
  39. configurable: true,
  40. get: function() {
  41. const _this = this;
  42. if (!this.$__.getters) {
  43. this.$__.getters = {};
  44. }
  45. if (!this.$__.getters[path]) {
  46. const nested = Object.create(Document.prototype, getOwnPropertyDescriptors(this));
  47. // save scope for nested getters/setters
  48. if (!prefix) {
  49. nested.$__.scope = this;
  50. }
  51. nested.$__.nestedPath = path;
  52. Object.defineProperty(nested, 'schema', {
  53. enumerable: false,
  54. configurable: true,
  55. writable: false,
  56. value: prototype.schema
  57. });
  58. Object.defineProperty(nested, 'toObject', {
  59. enumerable: false,
  60. configurable: true,
  61. writable: false,
  62. value: function() {
  63. return utils.clone(_this.get(path, null, {
  64. virtuals: get(this, 'schema.options.toObject.virtuals', null)
  65. }));
  66. }
  67. });
  68. Object.defineProperty(nested, 'toJSON', {
  69. enumerable: false,
  70. configurable: true,
  71. writable: false,
  72. value: function() {
  73. return _this.get(path, null, {
  74. virtuals: get(_this, 'schema.options.toJSON.virtuals', null)
  75. });
  76. }
  77. });
  78. Object.defineProperty(nested, '$__isNested', {
  79. enumerable: false,
  80. configurable: true,
  81. writable: false,
  82. value: true
  83. });
  84. const _isEmptyOptions = Object.freeze({
  85. minimize: true,
  86. virtuals: false,
  87. getters: false,
  88. transform: false
  89. });
  90. Object.defineProperty(nested, '$isEmpty', {
  91. enumerable: false,
  92. configurable: true,
  93. writable: false,
  94. value: function() {
  95. return Object.keys(this.get(path, null, _isEmptyOptions) || {}).length === 0;
  96. }
  97. });
  98. compile(subprops, nested, path, options);
  99. this.$__.getters[path] = nested;
  100. }
  101. return this.$__.getters[path];
  102. },
  103. set: function(v) {
  104. if (v instanceof Document) {
  105. v = v.toObject({ transform: false });
  106. }
  107. const doc = this.$__.scope || this;
  108. return doc.$set(path, v);
  109. }
  110. });
  111. } else {
  112. Object.defineProperty(prototype, prop, {
  113. enumerable: true,
  114. configurable: true,
  115. get: function() {
  116. return this[getSymbol].call(this.$__.scope || this, path);
  117. },
  118. set: function(v) {
  119. return this.$set.call(this.$__.scope || this, path, v);
  120. }
  121. });
  122. }
  123. }
  124. // gets descriptors for all properties of `object`
  125. // makes all properties non-enumerable to match previous behavior to #2211
  126. function getOwnPropertyDescriptors(object) {
  127. const result = {};
  128. Object.getOwnPropertyNames(object).forEach(function(key) {
  129. result[key] = Object.getOwnPropertyDescriptor(object, key);
  130. // Assume these are schema paths, ignore them re: #5470
  131. if (result[key].get) {
  132. delete result[key];
  133. return;
  134. }
  135. result[key].enumerable = ['isNew', '$__', 'errors', '_doc', '$locals'].indexOf(key) === -1;
  136. });
  137. return result;
  138. }