UNPKG

5.26 kBJavaScriptView Raw
1"use strict";
2
3Object.defineProperty(exports, "__esModule", {
4 value: true
5});
6exports.factory = factory;
7exports.sortFactories = sortFactories;
8exports.create = create;
9exports.isFactory = isFactory;
10exports.assertDependencies = assertDependencies;
11exports.isOptionalDependency = isOptionalDependency;
12exports.stripOptionalNotation = stripOptionalNotation;
13
14var _array = require("./array.js");
15
16var _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 */
42function 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
70function 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
115function 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
130function 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
144function 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
163function isOptionalDependency(dependency) {
164 return dependency && dependency[0] === '?';
165}
166
167function stripOptionalNotation(dependency) {
168 return dependency && dependency[0] === '?' ? dependency.slice(1) : dependency;
169}
\No newline at end of file