UNPKG

hinoki

Version:

sane, simple dependency injection and more

447 lines (446 loc) 16.5 kB
// Generated by CoffeeScript 1.10.0 (function(root, factory) { if (('function' === typeof define) && (define.amd != null)) { return define(['bluebird', 'lodash', 'helfer'], factory); } else if (typeof exports !== "undefined" && exports !== null) { return module.exports = factory(require('bluebird'), require('lodash'), require('helfer'), require('fs'), require('path')); } else { if (root.Promise == null) { throw new Error('missing global variable `Promise`'); } if (root._ == null) { throw new Error('missing global variable `_`'); } if (root.helfer == null) { throw new Error('missing global variable `helfer`'); } return root.hinoki = factory(root.Promise, root._, root.helfer); } })(this, function(Promise, _, helfer, fsModule, pathModule) { var hinoki; hinoki = function(arg1, arg2, arg3) { var cacheTarget, keyOrKeysOrFunction, keys, lifetimes, path, paths, source; source = hinoki.source(arg1); if (arg3 != null) { lifetimes = helfer.coerceToArray(arg2); keyOrKeysOrFunction = arg3; } else { lifetimes = [{}]; keyOrKeysOrFunction = arg2; } cacheTarget = 0; if ('function' === typeof keyOrKeysOrFunction) { keys = hinoki.getKeysToInject(keyOrKeysOrFunction); paths = _.map(keys, helfer.coerceToArray); return hinoki.getValuesAndCacheTarget(source, lifetimes, paths, cacheTarget).promise.spread(keyOrKeysOrFunction); } if (Array.isArray(keyOrKeysOrFunction)) { keys = helfer.coerceToArray(keyOrKeysOrFunction); paths = _.map(keys, helfer.coerceToArray); return hinoki.getValuesAndCacheTarget(source, lifetimes, paths, cacheTarget).promise; } path = helfer.coerceToArray(keyOrKeysOrFunction); return hinoki.getValueAndCacheTarget(source, lifetimes, path, cacheTarget).promise; }; hinoki.isNodejs = ((fsModule != null ? fsModule.statSync : void 0) != null) && ((fsModule != null ? fsModule.readdirSync : void 0) != null); hinoki.PromiseAndCacheTarget = function(promise, cacheTarget) { this.promise = promise; this.cacheTarget = cacheTarget; return this; }; hinoki.getValuesAndCacheTarget = function(source, lifetimes, paths, cacheTarget) { var nextCacheTarget, promise; nextCacheTarget = cacheTarget; promise = Promise.all(_.map(paths, function(path) { var result; result = hinoki.getValueAndCacheTarget(source, lifetimes, path, cacheTarget); nextCacheTarget = Math.max(nextCacheTarget, result.cacheTarget); return result.promise; })); return new hinoki.PromiseAndCacheTarget(promise, nextCacheTarget); }; hinoki.getValueAndCacheTarget = function(source, lifetimes, path, cacheTarget) { var dependenciesPromise, dependencyKey, dependencyKeys, dependencyKeysIndex, dependencyKeysLength, dependencyPaths, factory, factoryCallResultPromise, key, lifetimeIndex, newPath, nextCacheTarget, promise, result, returnPromise, valueOrPromise; key = path[0]; lifetimeIndex = helfer.findIndexWhereProperty(lifetimes, key); if (lifetimeIndex !== -1) { valueOrPromise = lifetimes[lifetimeIndex][key]; promise = helfer.isThenable(valueOrPromise) ? (typeof hinoki.debug === "function" ? hinoki.debug({ event: 'lifetimeHasPromise', path: path, promise: valueOrPromise, lifetime: lifetimes[lifetimeIndex], lifetimeIndex: lifetimeIndex }) : void 0, valueOrPromise) : (typeof hinoki.debug === "function" ? hinoki.debug({ event: 'lifetimeHasValue', path: path, value: valueOrPromise, lifetime: lifetimes[lifetimeIndex], lifetimeIndex: lifetimeIndex }) : void 0, Promise.resolve(valueOrPromise)); return new hinoki.PromiseAndCacheTarget(promise, lifetimeIndex); } factory = source(key); if (factory == null) { return new hinoki.PromiseAndCacheTarget(Promise.reject(new hinoki.NotFoundError(path)), cacheTarget); } if (!hinoki.isFactory(factory)) { return new hinoki.PromiseAndCacheTarget(Promise.reject(new hinoki.BadFactoryError(path, factory)), cacheTarget); } if (typeof hinoki.debug === "function") { hinoki.debug({ event: 'sourceReturnedFactory', path: path, factory: factory }); } dependencyKeys = hinoki.baseGetKeysToInject(factory, true); dependencyKeysIndex = -1; dependencyKeysLength = dependencyKeys.length; dependencyPaths = []; while (++dependencyKeysIndex < dependencyKeysLength) { dependencyKey = dependencyKeys[dependencyKeysIndex]; newPath = path.slice(); newPath.unshift(dependencyKey); if (-1 !== path.indexOf(dependencyKey)) { return new hinoki.PromiseAndCacheTarget(Promise.reject(new hinoki.CircularDependencyError(newPath)), cacheTarget); } dependencyPaths.push(newPath); } if (dependencyPaths.length !== 0) { result = hinoki.getValuesAndCacheTarget(source, lifetimes, dependencyPaths, cacheTarget); dependenciesPromise = result.promise; nextCacheTarget = result.cacheTarget; } else { dependenciesPromise = Promise.resolve([]); nextCacheTarget = cacheTarget; } factoryCallResultPromise = dependenciesPromise.then(function(dependencyValues) { return hinoki.callFactory(path, factory, dependencyValues); }); if (!factory.__nocache) { lifetimes[nextCacheTarget][key] = factoryCallResultPromise; } returnPromise = factoryCallResultPromise.then(function(value) { if (!factory.__nocache) { lifetimes[nextCacheTarget][key] = value; } return value; })["catch"](function(error) { if (!factory.__nocache) { delete lifetimes[nextCacheTarget][key]; } return Promise.reject(error); }); return new hinoki.PromiseAndCacheTarget(returnPromise, nextCacheTarget); }; hinoki.tryCatch = function(fun, args) { var error, error1; try { return fun.apply(null, args); } catch (error1) { error = error1; if (helfer.isError(error)) { return error; } else { return new Error(error.toString()); } } }; hinoki.callFactoryFunction = function(path, factoryFunction, args) { var result; result = hinoki.tryCatch(factoryFunction, args); if (helfer.isUndefined(result)) { return Promise.reject(new hinoki.FactoryReturnedUndefinedError(path, factoryFunction)); } if (helfer.isError(result)) { return Promise.reject(new hinoki.ErrorInFactory(path, factoryFunction, result)); } if (helfer.isThenable(result)) { if (typeof hinoki.debug === "function") { hinoki.debug({ event: 'factoryReturnedPromise', path: path, promise: result, factory: factoryFunction }); } return result.then(function(value) { if (typeof hinoki.debug === "function") { hinoki.debug({ event: 'promiseResolved', path: path, value: value, factory: factoryFunction }); } return value; })["catch"](function(rejection) { return Promise.reject(new hinoki.PromiseRejectedError(path, factoryFunction, rejection)); }); } if (typeof hinoki.debug === "function") { hinoki.debug({ event: 'factoryReturnedValue', path: path, value: result, factory: factoryFunction }); } return Promise.resolve(result); }; hinoki.callFactoryObjectArray = function(path, factoryObject, dependenciesObject) { var i, iterator, key, keys, length, result; iterator = function(f, key) { var dependencies, dependencyKeys, newPath; newPath = path.slice(); newPath[0] += '[' + key + ']'; if (!hinoki.isFactory(f)) { return Promise.reject(new hinoki.BadFactoryError(newPath, f)); } if ('function' === typeof f) { dependencyKeys = hinoki.getKeysToInject(f); dependencies = _.map(dependencyKeys, function(dependencyKey) { return dependenciesObject[dependencyKey]; }); return hinoki.callFactoryFunction(newPath, f, dependencies); } else if ('object' === typeof f) { return hinoki.callFactoryObjectArray(newPath, f, dependenciesObject); } }; if (Array.isArray(factoryObject)) { return Promise.all(factoryObject).map(iterator); } else if ('object' === typeof factoryObject) { keys = Object.keys(factoryObject); length = keys.length; i = -1; result = {}; while (++i < length) { key = keys[i]; if (0 !== key.indexOf('__')) { result[key] = iterator(factoryObject[key], key); } } return Promise.props(result); } }; hinoki.callFactory = function(path, factory, dependencyValues) { var dependenciesObject, dependencyKeys; if ('function' === typeof factory) { return hinoki.callFactoryFunction(path, factory, dependencyValues); } else { dependencyKeys = hinoki.getKeysToInject(factory); dependenciesObject = _.zipObject(dependencyKeys, dependencyValues); return hinoki.callFactoryObjectArray(path, factory, dependenciesObject); } }; hinoki.BaseError = function() {}; helfer.inherits(hinoki.BaseError, Error); hinoki.NotFoundError = function(path) { this.name = 'NotFoundError'; this.message = "neither value nor factory found for `" + path[0] + "` in path `" + (hinoki.pathToString(path)) + "`"; if (Error.captureStackTrace != null) { Error.captureStackTrace(this, this.constructor); } this.path = path; }; helfer.inherits(hinoki.NotFoundError, hinoki.BaseError); hinoki.CircularDependencyError = function(path) { this.name = 'CircularDependencyError'; this.message = "circular dependency `" + (hinoki.pathToString(path)) + "`"; if (Error.captureStackTrace != null) { Error.captureStackTrace(this, this.constructor); } this.path = path; }; helfer.inherits(hinoki.CircularDependencyError, hinoki.BaseError); hinoki.ErrorInFactory = function(path, factory, error) { this.name = 'ErrorInFactory'; this.message = "error in factory for `" + path[0] + "`. original error `" + (error.toString()) + "`"; if (Error.captureStackTrace != null) { Error.captureStackTrace(this, this.constructor); } this.path = path; this.factory = factory; this.error = error; }; helfer.inherits(hinoki.ErrorInFactory, hinoki.BaseError); hinoki.FactoryReturnedUndefinedError = function(path, factory) { this.name = 'FactoryReturnedUndefinedError'; this.message = "factory for `" + path[0] + "` returned undefined"; if (Error.captureStackTrace != null) { Error.captureStackTrace(this, this.constructor); } this.path = path; this.factory = factory; }; helfer.inherits(hinoki.FactoryReturnedUndefinedError, hinoki.BaseError); hinoki.PromiseRejectedError = function(path, factory, error) { this.name = 'PromiseRejectedError'; this.message = "promise returned from factory for `" + path[0] + "` was rejected. original error `" + (error.toString()) + "`"; if (Error.captureStackTrace != null) { Error.captureStackTrace(this, this.constructor); } this.path = path; this.factory = factory; this.error = error; }; helfer.inherits(hinoki.PromiseRejectedError, hinoki.BaseError); hinoki.BadFactoryError = function(path, factory) { this.name = 'BadFactoryError'; this.message = "factory for `" + path[0] + "` has to be a function, object of factories or array of factories but is `" + (typeof factory) + "`"; if (Error.captureStackTrace != null) { Error.captureStackTrace(this, this.constructor); } this.path = path; this.factory = factory; }; helfer.inherits(hinoki.BadFactoryError, hinoki.BaseError); hinoki.pathToString = function(path) { return path.join(' <- '); }; hinoki.getKeysToInject = function(factory) { return hinoki.baseGetKeysToInject(factory, false); }; hinoki.baseGetKeysToInject = function(factory, cache) { var keys, keysSet, type; if (factory.__inject != null) { return factory.__inject; } type = typeof factory; if (('object' === type) || ('function' === type)) { if ('function' === type) { keys = helfer.parseFunctionArguments(factory); } else { keysSet = {}; _.forEach(factory, function(subFactory) { var subKeys; subKeys = hinoki.baseGetKeysToInject(subFactory, cache); return _.forEach(subKeys, function(subKey) { return keysSet[subKey] = true; }); }); keys = Object.keys(keysSet); } if (cache) { factory.__inject = keys; } return keys; } return []; }; hinoki.isFactory = function(value) { var type; type = typeof value; return (type === 'function') || Array.isArray(value) || (type === 'object'); }; if (hinoki.isNodejs) { hinoki.requireSource = function(filepath) { if ('string' !== typeof filepath) { throw new Error('argument must be a string'); } return hinoki.baseRequireSource(filepath, {}); }; hinoki.baseRequireSource = function(filepath, object) { var exports, extension, filenames, stat; stat = fsModule.statSync(filepath); if (stat.isFile()) { extension = pathModule.extname(filepath); if (extension !== '.js' && extension !== '.coffee') { return; } if (extension === '.coffee') { require('coffee-script/register'); } exports = require(filepath); Object.keys(exports).map(function(key) { if (!hinoki.isFactory(exports[key])) { throw new Error('export is not a factory: ' + key + ' in :' + filepath); } if (object[key] != null) { throw new Error('duplicate export: ' + key + ' in: ' + filepath + '. first was in: ' + object[key].__file); } object[key] = exports[key]; return object[key].__file = filepath; }); } else if (stat.isDirectory()) { filenames = fsModule.readdirSync(filepath); filenames.forEach(function(filename) { return hinoki.baseRequireSource(pathModule.join(filepath, filename), object); }); } return object; }; } hinoki.source = function(arg) { var coercedSources, source; if ('function' === typeof arg) { return arg; } if (Array.isArray(arg)) { coercedSources = _.map(arg, hinoki.source); source = function(key) { var index, length, result; index = -1; length = arg.length; while (++index < length) { result = coercedSources[index](key); if (result != null) { return result; } } return null; }; source.keys = function() { var keys; keys = []; _.each(coercedSources, function(source) { if (source.keys != null) { return keys = keys.concat(source.keys()); } }); return keys; }; return source; } if ('string' === typeof arg) { if (!hinoki.isNodejs) { throw new Error('string sources only work on Node.js because they need the filesystem module to be present'); } return hinoki.source(hinoki.requireSource(arg)); } if ('object' === typeof arg) { source = function(key) { return arg[key]; }; source.keys = function() { return Object.keys(arg); }; return source; } throw new Error('argument must be a function, string, object or array of these'); }; hinoki.decorateSourceToAlsoLookupWithPrefix = function(innerSource, prefix) { var source; source = function(key) { var result, wrapperFactory; result = innerSource(key); if (result != null) { return result; } if (0 === key.indexOf(prefix)) { return null; } wrapperFactory = function(wrapped) { return wrapped; }; wrapperFactory.__inject = [prefix + key]; return wrapperFactory; }; if (innerSource.keys != null) { source.keys = innerSource.keys; } return source; }; return hinoki; });