simple-squiggle

A restricted subset of Squiggle
Log | Files | Refs | README

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 }