Source: instantiators/PrototypeWrapper.js

  1. /**
  2. * @typedef Object PrototypeWrap
  3. * @property {Object} targetPrototype The proxied and wired up prototype to use
  4. * @property {Array} originalChain The original prototype chain to use later to restore the prototype
  5. */
  6. /**
  7. * The Prototype Wrapper will resolve dependencies up in the prototype tree.
  8. * If you require a module which inherits other prototypes, this wrapper will insert proxies in
  9. * between the inheritance steps which then will handle the dependency injections in the parent
  10. * prototypes. You can still call super() with arguments in the constructor, the supplied arguments
  11. * will then be appended to the injected properties in the constructor of the parent prototype.
  12. *
  13. * @author Wolfgang Felbermeier (@f3lang)
  14. */
  15. class PrototypeWrapper {
  16. constructor(objectManager) {
  17. this.objectManager = objectManager;
  18. }
  19. /**
  20. * Will wrap the node require and inject prototype proxies for the dependency injection.
  21. * @param {String} module The path of the module to require
  22. * @param {String} root The identifier of the tree to work in
  23. * @param {String} requestId The id of the current injection request
  24. * @return {PrototypeWrap} An Object containing the resolved prototype and the original chain
  25. */
  26. require(module, root, requestId) {
  27. let mod = require(module);
  28. let prototypeChain = this.buildPrototypeChain(mod);
  29. if (prototypeChain.length === 1) {
  30. // no chain detected so we can continue with a regular require()
  31. return {targetPrototype: mod, originalChain: null};
  32. } else if (prototypeChain.length === 2) {
  33. // chain with one level of inheritance detected
  34. let targetPrototype = prototypeChain[0];
  35. let wrappedParentPrototype = this.wrapPrototype(prototypeChain[1], root, requestId + prototypeChain[1].name);
  36. Object.setPrototypeOf(targetPrototype, wrappedParentPrototype);
  37. return {targetPrototype, originalChain: prototypeChain};
  38. }
  39. // more complex chain detected with multiple levels of inheritance
  40. let originalChain = prototypeChain.slice();
  41. let i = prototypeChain.length;
  42. let targetPrototype = prototypeChain[i - 2];
  43. while (i-- && i > 1) {
  44. let upperPrototype = prototypeChain[i];
  45. let proxiedUpperPrototype = this.wrapPrototype(upperPrototype, root, requestId + upperPrototype.name);
  46. Object.setPrototypeOf(targetPrototype, proxiedUpperPrototype);
  47. prototypeChain[i - 1] = targetPrototype;
  48. targetPrototype = prototypeChain[i - 2];
  49. }
  50. Object.setPrototypeOf(targetPrototype, prototypeChain[1]);
  51. return {targetPrototype, originalChain};
  52. }
  53. /**
  54. * Will traverse the prototype chain recursively up to the top and convert
  55. * it to an array.
  56. * @param {Object} prototype The prototype to build a chain for
  57. * @return {Array} An array with all prototypes of the chain ordered from bottom to top.
  58. */
  59. buildPrototypeChain(prototype) {
  60. let chain = [];
  61. chain.push(prototype);
  62. if (Object.getPrototypeOf(prototype) !== Object.getPrototypeOf(class {
  63. })) {
  64. chain.push(...this.buildPrototypeChain(Object.getPrototypeOf(prototype)));
  65. }
  66. return chain;
  67. }
  68. /**
  69. * Will wrap a prototype in a proxy, that will inject the dependencies into the
  70. * original prototype constructor.
  71. * @param {Object} prototype The prototype to wrap
  72. * @param {String} root The identifier of the tree to work in
  73. * @param {String} requestId The id of the current injection request
  74. * @return {Object} The wrapped prototype
  75. */
  76. wrapPrototype(prototype, root, requestId) {
  77. let injectConfiguration = this.objectManager.getInjectionConfiguration(prototype.name);
  78. prototype.tree = root;
  79. if (!injectConfiguration || injectConfiguration.config.injectMap.length === 0) {
  80. return prototype;
  81. }
  82. let params = this.objectManager.getModuleParams(injectConfiguration.config.injectMap, root, requestId);
  83. return class extends prototype {
  84. constructor(...args) {
  85. module.exports.tree = root;
  86. super(...params, ...args);
  87. }
  88. };
  89. }
  90. /**
  91. * After instantiation of the module it is important to restore the original
  92. * prototype tree, since node will somehow cache it, which will lead to problems
  93. * when traversing the tree again.
  94. * @param {Array} originalChain The original prototype chain as an Array
  95. */
  96. restoreOriginalPrototypeChain(originalChain) {
  97. if (originalChain.length === 2) {
  98. Object.setPrototypeOf(originalChain[0], originalChain[1]);
  99. return;
  100. }
  101. let i = originalChain.length - 1;
  102. while (i--) {
  103. Object.setPrototypeOf(originalChain[i], originalChain[i + 1]);
  104. }
  105. }
  106. }
  107. module.exports = PrototypeWrapper;
  108. module.exports.inject = ['ObjectManager'];