compile.js 4.8 KB

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