hinoki
Version:
sane, simple dependency injection and more
447 lines (446 loc) • 16.5 kB
JavaScript
// 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;
});