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