compile.js 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. 'use strict';
  2. const documentSchemaSymbol = require('../../helpers/symbols').documentSchemaSymbol;
  3. const get = require('../../helpers/get');
  4. const internalToObjectOptions = require('../../options').internalToObjectOptions;
  5. const utils = require('../../utils');
  6. let Document;
  7. const getSymbol = require('../../helpers/symbols').getSymbol;
  8. const scopeSymbol = require('../../helpers/symbols').scopeSymbol;
  9. /*!
  10. * exports
  11. */
  12. exports.compile = compile;
  13. exports.defineKey = defineKey;
  14. /*!
  15. * Compiles schemas.
  16. */
  17. function compile(tree, proto, prefix, options) {
  18. Document = Document || require('../../document');
  19. const keys = Object.keys(tree);
  20. const len = keys.length;
  21. let limb;
  22. let key;
  23. for (let i = 0; i < len; ++i) {
  24. key = keys[i];
  25. limb = tree[key];
  26. const hasSubprops = utils.isPOJO(limb) && Object.keys(limb).length &&
  27. (!limb[options.typeKey] || (options.typeKey === 'type' && limb.type.type));
  28. const subprops = hasSubprops ? limb : null;
  29. defineKey(key, subprops, proto, prefix, keys, options);
  30. }
  31. }
  32. /*!
  33. * Defines the accessor named prop on the incoming prototype.
  34. */
  35. function defineKey(prop, subprops, prototype, prefix, keys, options) {
  36. Document = Document || require('../../document');
  37. const path = (prefix ? prefix + '.' : '') + prop;
  38. prefix = prefix || '';
  39. if (subprops) {
  40. Object.defineProperty(prototype, prop, {
  41. enumerable: true,
  42. configurable: true,
  43. get: function() {
  44. const _this = this;
  45. if (!this.$__.getters) {
  46. this.$__.getters = {};
  47. }
  48. if (!this.$__.getters[path]) {
  49. const nested = Object.create(Document.prototype, getOwnPropertyDescriptors(this));
  50. // save scope for nested getters/setters
  51. if (!prefix) {
  52. nested.$__[scopeSymbol] = this;
  53. }
  54. nested.$__.nestedPath = path;
  55. Object.defineProperty(nested, 'schema', {
  56. enumerable: false,
  57. configurable: true,
  58. writable: false,
  59. value: prototype.schema
  60. });
  61. Object.defineProperty(nested, '$__schema', {
  62. enumerable: false,
  63. configurable: true,
  64. writable: false,
  65. value: prototype.schema
  66. });
  67. Object.defineProperty(nested, documentSchemaSymbol, {
  68. enumerable: false,
  69. configurable: true,
  70. writable: false,
  71. value: prototype.schema
  72. });
  73. Object.defineProperty(nested, 'toObject', {
  74. enumerable: false,
  75. configurable: true,
  76. writable: false,
  77. value: function() {
  78. return utils.clone(_this.get(path, null, {
  79. virtuals: get(this, 'schema.options.toObject.virtuals', null)
  80. }));
  81. }
  82. });
  83. Object.defineProperty(nested, '$__get', {
  84. enumerable: false,
  85. configurable: true,
  86. writable: false,
  87. value: function() {
  88. return _this.get(path, null, {
  89. virtuals: get(this, 'schema.options.toObject.virtuals', null)
  90. });
  91. }
  92. });
  93. Object.defineProperty(nested, 'toJSON', {
  94. enumerable: false,
  95. configurable: true,
  96. writable: false,
  97. value: function() {
  98. return _this.get(path, null, {
  99. virtuals: get(_this, 'schema.options.toJSON.virtuals', null)
  100. });
  101. }
  102. });
  103. Object.defineProperty(nested, '$__isNested', {
  104. enumerable: false,
  105. configurable: true,
  106. writable: false,
  107. value: true
  108. });
  109. const _isEmptyOptions = Object.freeze({
  110. minimize: true,
  111. virtuals: false,
  112. getters: false,
  113. transform: false
  114. });
  115. Object.defineProperty(nested, '$isEmpty', {
  116. enumerable: false,
  117. configurable: true,
  118. writable: false,
  119. value: function() {
  120. return Object.keys(this.get(path, null, _isEmptyOptions) || {}).length === 0;
  121. }
  122. });
  123. Object.defineProperty(nested, '$__parent', {
  124. enumerable: false,
  125. configurable: true,
  126. writable: false,
  127. value: this
  128. });
  129. compile(subprops, nested, path, options);
  130. this.$__.getters[path] = nested;
  131. }
  132. return this.$__.getters[path];
  133. },
  134. set: function(v) {
  135. if (v != null && v.$__isNested) {
  136. // Convert top-level to POJO, but leave subdocs hydrated so `$set`
  137. // can handle them. See gh-9293.
  138. v = v.$__get();
  139. } else if (v instanceof Document && !v.$__isNested) {
  140. v = v.toObject(internalToObjectOptions);
  141. }
  142. const doc = this.$__[scopeSymbol] || this;
  143. doc.$set(path, v);
  144. }
  145. });
  146. } else {
  147. Object.defineProperty(prototype, prop, {
  148. enumerable: true,
  149. configurable: true,
  150. get: function() {
  151. return this[getSymbol].call(this.$__[scopeSymbol] || this, path);
  152. },
  153. set: function(v) {
  154. this.$set.call(this.$__[scopeSymbol] || this, path, v);
  155. }
  156. });
  157. }
  158. }
  159. // gets descriptors for all properties of `object`
  160. // makes all properties non-enumerable to match previous behavior to #2211
  161. function getOwnPropertyDescriptors(object) {
  162. const result = {};
  163. Object.getOwnPropertyNames(object).forEach(function(key) {
  164. result[key] = Object.getOwnPropertyDescriptor(object, key);
  165. // Assume these are schema paths, ignore them re: #5470
  166. if (result[key].get) {
  167. delete result[key];
  168. return;
  169. }
  170. result[key].enumerable = [
  171. 'isNew',
  172. '$__',
  173. 'errors',
  174. '_doc',
  175. '$locals',
  176. '$op',
  177. '__parentArray',
  178. '__index',
  179. '$isDocumentArrayElement'
  180. ].indexOf(key) === -1;
  181. });
  182. return result;
  183. }