UNPKG

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