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