factory.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.assertDependencies = assertDependencies;
  6. exports.create = create;
  7. exports.factory = factory;
  8. exports.isFactory = isFactory;
  9. exports.isOptionalDependency = isOptionalDependency;
  10. exports.sortFactories = sortFactories;
  11. exports.stripOptionalNotation = stripOptionalNotation;
  12. var _array = require("./array.js");
  13. var _object = require("./object.js");
  14. /**
  15. * Create a factory function, which can be used to inject dependencies.
  16. *
  17. * The created functions are memoized, a consecutive call of the factory
  18. * with the exact same inputs will return the same function instance.
  19. * The memoized cache is exposed on `factory.cache` and can be cleared
  20. * if needed.
  21. *
  22. * Example:
  23. *
  24. * const name = 'log'
  25. * const dependencies = ['config', 'typed', 'divideScalar', 'Complex']
  26. *
  27. * export const createLog = factory(name, dependencies, ({ typed, config, divideScalar, Complex }) => {
  28. * // ... create the function log here and return it
  29. * }
  30. *
  31. * @param {string} name Name of the function to be created
  32. * @param {string[]} dependencies The names of all required dependencies
  33. * @param {function} create Callback function called with an object with all dependencies
  34. * @param {Object} [meta] Optional object with meta information that will be attached
  35. * to the created factory function as property `meta`.
  36. * @returns {function}
  37. */
  38. function factory(name, dependencies, create, meta) {
  39. function assertAndCreate(scope) {
  40. // we only pass the requested dependencies to the factory function
  41. // to prevent functions to rely on dependencies that are not explicitly
  42. // requested.
  43. var deps = (0, _object.pickShallow)(scope, dependencies.map(stripOptionalNotation));
  44. assertDependencies(name, dependencies, scope);
  45. return create(deps);
  46. }
  47. assertAndCreate.isFactory = true;
  48. assertAndCreate.fn = name;
  49. assertAndCreate.dependencies = dependencies.slice().sort();
  50. if (meta) {
  51. assertAndCreate.meta = meta;
  52. }
  53. return assertAndCreate;
  54. }
  55. /**
  56. * Sort all factories such that when loading in order, the dependencies are resolved.
  57. *
  58. * @param {Array} factories
  59. * @returns {Array} Returns a new array with the sorted factories.
  60. */
  61. function sortFactories(factories) {
  62. var factoriesByName = {};
  63. factories.forEach(function (factory) {
  64. factoriesByName[factory.fn] = factory;
  65. });
  66. function containsDependency(factory, dependency) {
  67. // TODO: detect circular references
  68. if (isFactory(factory)) {
  69. if ((0, _array.contains)(factory.dependencies, dependency.fn || dependency.name)) {
  70. return true;
  71. }
  72. if (factory.dependencies.some(function (d) {
  73. return containsDependency(factoriesByName[d], dependency);
  74. })) {
  75. return true;
  76. }
  77. }
  78. return false;
  79. }
  80. var sorted = [];
  81. function addFactory(factory) {
  82. var index = 0;
  83. while (index < sorted.length && !containsDependency(sorted[index], factory)) {
  84. index++;
  85. }
  86. sorted.splice(index, 0, factory);
  87. }
  88. // sort regular factory functions
  89. factories.filter(isFactory).forEach(addFactory);
  90. // sort legacy factory functions AFTER the regular factory functions
  91. factories.filter(function (factory) {
  92. return !isFactory(factory);
  93. }).forEach(addFactory);
  94. return sorted;
  95. }
  96. // TODO: comment or cleanup if unused in the end
  97. function create(factories) {
  98. var scope = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  99. sortFactories(factories).forEach(function (factory) {
  100. return factory(scope);
  101. });
  102. return scope;
  103. }
  104. /**
  105. * Test whether an object is a factory. This is the case when it has
  106. * properties name, dependencies, and a function create.
  107. * @param {*} obj
  108. * @returns {boolean}
  109. */
  110. function isFactory(obj) {
  111. return typeof obj === 'function' && typeof obj.fn === 'string' && Array.isArray(obj.dependencies);
  112. }
  113. /**
  114. * Assert that all dependencies of a list with dependencies are available in the provided scope.
  115. *
  116. * Will throw an exception when there are dependencies missing.
  117. *
  118. * @param {string} name Name for the function to be created. Used to generate a useful error message
  119. * @param {string[]} dependencies
  120. * @param {Object} scope
  121. */
  122. function assertDependencies(name, dependencies, scope) {
  123. var allDefined = dependencies.filter(function (dependency) {
  124. return !isOptionalDependency(dependency);
  125. }) // filter optionals
  126. .every(function (dependency) {
  127. return scope[dependency] !== undefined;
  128. });
  129. if (!allDefined) {
  130. var missingDependencies = dependencies.filter(function (dependency) {
  131. return scope[dependency] === undefined;
  132. });
  133. // TODO: create a custom error class for this, a MathjsError or something like that
  134. throw new Error("Cannot create function \"".concat(name, "\", ") + "some dependencies are missing: ".concat(missingDependencies.map(function (d) {
  135. return "\"".concat(d, "\"");
  136. }).join(', '), "."));
  137. }
  138. }
  139. function isOptionalDependency(dependency) {
  140. return dependency && dependency[0] === '?';
  141. }
  142. function stripOptionalNotation(dependency) {
  143. return dependency && dependency[0] === '?' ? dependency.slice(1) : dependency;
  144. }