compile.js 6.0 KB

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