knifecycle
Advanced tools
Comparing version 2.5.4 to 2.6.0
@@ -0,1 +1,11 @@ | ||
<a name="2.6.0"></a> | ||
# [2.6.0](https://github.com/nfroidure/knifecycle/compare/v2.5.4...v2.6.0) (2018-09-30) | ||
### Features | ||
* **$autoload:** Allows to autoload missing dependencies ([5b1cdb1](https://github.com/nfroidure/knifecycle/commit/5b1cdb1)) | ||
<a name="2.5.4"></a> | ||
@@ -2,0 +12,0 @@ ## [2.5.4](https://github.com/nfroidure/knifecycle/compare/v2.5.3...v2.5.4) (2018-09-22) |
@@ -7,2 +7,6 @@ "use strict"; | ||
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } } | ||
function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; } | ||
/* Architecture Note #2: Build | ||
@@ -43,4 +47,9 @@ | ||
function buildInitializer(constants, loader, dependencies) { | ||
return Promise.all(dependencies.map(dependency => buildDependencyTree(constants, loader, dependency))).then(dependencyTrees => { | ||
function buildInitializer(_x, _x2, _x3) { | ||
return _buildInitializer.apply(this, arguments); | ||
} | ||
function _buildInitializer() { | ||
_buildInitializer = _asyncToGenerator(function* (constants, loader, dependencies) { | ||
const dependencyTrees = yield Promise.all(dependencies.map(dependency => buildDependencyTree(constants, loader, dependency))); | ||
const dependenciesHash = buildDependenciesHash(dependencyTrees.filter(identity)); | ||
@@ -99,41 +108,54 @@ const batches = (0, _sequence.buildInitializationSequence)({ | ||
}); | ||
return _buildInitializer.apply(this, arguments); | ||
} | ||
function buildDependencyTree(constants, loader, dependencyDeclaration) { | ||
const { | ||
mappedName, | ||
optional | ||
} = (0, _util.parseDependencyDeclaration)(dependencyDeclaration); | ||
function buildDependencyTree(_x4, _x5, _x6) { | ||
return _buildDependencyTree.apply(this, arguments); | ||
} | ||
if (constants[mappedName]) { | ||
return Promise.resolve({ | ||
__name: mappedName, | ||
__constant: constants[mappedName] | ||
}); | ||
} | ||
function _buildDependencyTree() { | ||
_buildDependencyTree = _asyncToGenerator(function* (constants, loader, dependencyDeclaration) { | ||
const { | ||
mappedName, | ||
optional | ||
} = (0, _util.parseDependencyDeclaration)(dependencyDeclaration); | ||
return loader(mappedName).then(({ | ||
path, | ||
initializer | ||
}) => { | ||
const node = { | ||
__name: mappedName, | ||
__initializer: initializer, | ||
__inject: initializer && initializer[_util.SPECIAL_PROPS.INJECT] ? initializer[_util.SPECIAL_PROPS.INJECT] : [], | ||
__type: initializer && initializer[_util.SPECIAL_PROPS.TYPE] ? initializer[_util.SPECIAL_PROPS.TYPE] : 'provider', | ||
__initializerName: 'init' + upperCaseFirst(mappedName), | ||
__path: path, | ||
__childNodes: [] | ||
}; | ||
return initializer[_util.SPECIAL_PROPS.INJECT] && initializer[_util.SPECIAL_PROPS.INJECT].length ? Promise.all(initializer[_util.SPECIAL_PROPS.INJECT].map(childDependencyDeclaration => buildDependencyTree(constants, loader, childDependencyDeclaration))).then(childNodes => { | ||
node.__childNodes = childNodes.filter(identity); | ||
return node; | ||
}) : node; | ||
}).catch(err => { | ||
if (optional) { | ||
return null; | ||
if (constants[mappedName]) { | ||
return Promise.resolve({ | ||
__name: mappedName, | ||
__constant: constants[mappedName] | ||
}); | ||
} | ||
throw err; | ||
try { | ||
const { | ||
path, | ||
initializer | ||
} = yield loader(mappedName); | ||
const node = { | ||
__name: mappedName, | ||
__initializer: initializer, | ||
__inject: initializer && initializer[_util.SPECIAL_PROPS.INJECT] ? initializer[_util.SPECIAL_PROPS.INJECT] : [], | ||
__type: initializer && initializer[_util.SPECIAL_PROPS.TYPE] ? initializer[_util.SPECIAL_PROPS.TYPE] : 'provider', | ||
__initializerName: 'init' + upperCaseFirst(mappedName), | ||
__path: path, | ||
__childNodes: [] | ||
}; | ||
if (initializer[_util.SPECIAL_PROPS.INJECT] && initializer[_util.SPECIAL_PROPS.INJECT].length) { | ||
const childNodes = yield Promise.all(initializer[_util.SPECIAL_PROPS.INJECT].map(childDependencyDeclaration => buildDependencyTree(constants, loader, childDependencyDeclaration))); | ||
node.__childNodes = childNodes.filter(identity); | ||
return node; | ||
} else { | ||
return node; | ||
} | ||
} catch (err) { | ||
if (optional) { | ||
return null; | ||
} | ||
throw err; | ||
} | ||
}); | ||
return _buildDependencyTree.apply(this, arguments); | ||
} | ||
@@ -140,0 +162,0 @@ |
@@ -13,2 +13,6 @@ "use strict"; | ||
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } } | ||
function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; } | ||
describe('buildInitializer', () => { | ||
@@ -48,3 +52,7 @@ function aProvider() {} | ||
it('should build an initialization module', () => (0, _build.default)(mockedConstants, mockedLoader, ['dep1', 'finalMappedDep>dep3']).then(content => { | ||
it('should build an initialization module', | ||
/*#__PURE__*/ | ||
_asyncToGenerator(function* () { | ||
const content = yield (0, _build.default)(mockedConstants, mockedLoader, ['dep1', 'finalMappedDep>dep3']); | ||
_assert.default.equal(content, ` | ||
@@ -51,0 +59,0 @@ // Definition batch #0 |
@@ -46,6 +46,10 @@ "use strict"; | ||
/* eslint max-len: ["warn", { "ignoreComments": true }] */ | ||
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } } | ||
function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; } | ||
const debug = (0, _debug.default)('knifecycle'); | ||
const DISPOSE = '$dispose'; | ||
const DESTROY = '$destroy'; | ||
const AUTOLOAD = '$autoload'; | ||
const INJECTOR = '$injector'; | ||
@@ -55,5 +59,8 @@ const SILO_CONTEXT = '$siloContext'; | ||
const ALLOWED_INITIALIZER_TYPES = ['provider', 'service']; | ||
const E_BAD_ANALYZER_TYPE = 'E_BAD_ANALYZER_TYPE'; | ||
const E_BAD_INITIALIZER_TYPE = 'E_BAD_INITIALIZER_TYPE'; | ||
const E_BAD_AUTOLOADED_INITIALIZER = 'E_BAD_AUTOLOADED_INITIALIZER'; | ||
const E_AUTOLOADED_INITIALIZER_MISMATCH = 'E_AUTOLOADED_INITIALIZER_MISMATCH'; | ||
const E_UNMATCHED_DEPENDENCY = 'E_UNMATCHED_DEPENDENCY'; | ||
const E_CIRCULAR_DEPENDENCY = 'E_CIRCULAR_DEPENDENCY'; | ||
const E_BAD_INITIALIZER = 'E_BAD_INITIALIZER'; | ||
const E_ANONYMOUS_ANALYZER = 'E_ANONYMOUS_ANALYZER'; | ||
@@ -63,3 +70,6 @@ const E_BAD_SERVICE_PROVIDER = 'E_BAD_SERVICE_PROVIDER'; | ||
const E_BAD_INJECTION = 'E_BAD_INJECTION'; | ||
const E_CONSTANT_INJECTION = 'E_CONSTANT_INJECTION'; // Constants that should use Symbol whenever possible | ||
const E_CONSTANT_INJECTION = 'E_CONSTANT_INJECTION'; | ||
const E_INSTANCE_DESTROYED = 'E_INSTANCE_DESTROYED'; | ||
const E_AUTOLOADER_DYNAMIC_DEPENDENCY = 'E_AUTOLOADER_DYNAMIC_DEPENDENCY'; | ||
const E_BAD_CLASS = 'E_BAD_CLASS'; // Constants that should use Symbol whenever possible | ||
@@ -114,3 +124,5 @@ const INSTANCE = '__instance'; | ||
}) => Promise.resolve({ | ||
service: dependenciesDeclarations => this._initializeDependencies($siloContext, $siloContext.name, dependenciesDeclarations, true) | ||
service: dependenciesDeclarations => this._initializeDependencies($siloContext, $siloContext.name, dependenciesDeclarations, { | ||
injectOnly: true | ||
}) | ||
}))); | ||
@@ -305,2 +317,6 @@ this.provider(DESTROY, () => Promise.resolve({ | ||
register(initializer) { | ||
if (typeof initializer !== 'function') { | ||
throw new _yerror.default(E_BAD_INITIALIZER, initializer); | ||
} | ||
initializer[_util.SPECIAL_PROPS.INJECT] = initializer[_util.SPECIAL_PROPS.INJECT] || []; | ||
@@ -315,3 +331,3 @@ initializer[_util.SPECIAL_PROPS.OPTIONS] = initializer[_util.SPECIAL_PROPS.OPTIONS] || {}; | ||
if (!ALLOWED_INITIALIZER_TYPES.includes(initializer[_util.SPECIAL_PROPS.TYPE])) { | ||
throw new _yerror.default(E_BAD_ANALYZER_TYPE, initializer[_util.SPECIAL_PROPS.TYPE], ALLOWED_INITIALIZER_TYPES); | ||
throw new _yerror.default(E_BAD_INITIALIZER_TYPE, initializer[_util.SPECIAL_PROPS.TYPE], ALLOWED_INITIALIZER_TYPES); | ||
} | ||
@@ -452,93 +468,113 @@ | ||
run(dependenciesDeclarations) { | ||
const _this = this; | ||
var _this2 = this; | ||
const internalDependencies = [...new Set(dependenciesDeclarations.concat(DISPOSE))]; | ||
const siloContext = { | ||
name: `silo-${this._silosCounter++}`, | ||
servicesDescriptors: new Map(), | ||
servicesSequence: [], | ||
servicesShutdownsPromises: new Map(), | ||
errorsPromises: [] | ||
}; | ||
return _asyncToGenerator(function* () { | ||
const _this = _this2; | ||
const internalDependencies = [...new Set(dependenciesDeclarations.concat(DISPOSE))]; | ||
const siloContext = { | ||
name: `silo-${_this2._silosCounter++}`, | ||
servicesDescriptors: new Map(), | ||
servicesSequence: [], | ||
servicesShutdownsPromises: new Map(), | ||
errorsPromises: [] | ||
}; | ||
if (this.shutdownPromise) { | ||
throw new _yerror.default('E_INSTANCE_DESTROYED'); | ||
} // Create a provider for the special fatal error service | ||
if (_this2.shutdownPromise) { | ||
throw new _yerror.default(E_INSTANCE_DESTROYED); | ||
} // Create a provider for the special fatal error service | ||
siloContext.servicesDescriptors.set(FATAL_ERROR, { | ||
service: { | ||
promise: new Promise((resolve, reject) => { | ||
siloContext.throwFatalError = err => { | ||
debug('Handled a fatal error', err); | ||
reject(err); | ||
}; | ||
}) | ||
} | ||
}); // Make the siloContext available for internal injections | ||
siloContext.servicesDescriptors.set(FATAL_ERROR, { | ||
service: { | ||
promise: new Promise((resolve, reject) => { | ||
siloContext.throwFatalError = err => { | ||
debug('Handled a fatal error', err); | ||
reject(err); | ||
}; | ||
}) | ||
} | ||
}); // Make the siloContext available for internal injections | ||
siloContext.servicesDescriptors.set(SILO_CONTEXT, { | ||
service: siloContext | ||
}); // Create a provider for the shutdown special dependency | ||
siloContext.servicesDescriptors.set(SILO_CONTEXT, { | ||
service: siloContext | ||
}); // Create a provider for the shutdown special dependency | ||
siloContext.servicesDescriptors.set(DISPOSE, { | ||
service: () => { | ||
siloContext.shutdownPromise = siloContext.shutdownPromise || _shutdownNextServices(siloContext.servicesSequence); | ||
debug('Shutting down services'); | ||
return siloContext.shutdownPromise.then(() => { | ||
this._silosContexts.delete(siloContext); | ||
}); // Shutdown services in their instanciation order | ||
siloContext.servicesDescriptors.set(DISPOSE, { | ||
service: function () { | ||
var _ref = _asyncToGenerator(function* () { | ||
siloContext.shutdownPromise = siloContext.shutdownPromise || _shutdownNextServices(siloContext.servicesSequence); | ||
debug('Shutting down services'); | ||
yield siloContext.shutdownPromise; | ||
function _shutdownNextServices(reversedServiceSequence) { | ||
if (0 === reversedServiceSequence.length) { | ||
return Promise.resolve(); | ||
} | ||
_this2._silosContexts.delete(siloContext); // Shutdown services in their instanciation order | ||
return Promise.all(reversedServiceSequence.pop().map(serviceName => { | ||
const singletonServiceDescriptor = _this._singletonsServicesDescriptors.get(serviceName); | ||
const serviceDescriptor = singletonServiceDescriptor || siloContext.servicesDescriptors.get(serviceName); | ||
let serviceShutdownPromise = _this._singletonsServicesShutdownsPromises.get(serviceName) || siloContext.servicesShutdownsPromises.get(serviceName); | ||
if (serviceShutdownPromise) { | ||
debug('Reusing a service shutdown promise:', serviceName); | ||
return serviceShutdownPromise; | ||
function _shutdownNextServices(_x) { | ||
return _shutdownNextServices2.apply(this, arguments); | ||
} | ||
if (reversedServiceSequence.some(servicesDeclarations => servicesDeclarations.includes(serviceName))) { | ||
debug('Delaying service shutdown:', serviceName); | ||
return Promise.resolve(); | ||
} | ||
function _shutdownNextServices2() { | ||
_shutdownNextServices2 = _asyncToGenerator(function* (reversedServiceSequence) { | ||
if (0 === reversedServiceSequence.length) { | ||
return; | ||
} | ||
if (singletonServiceDescriptor) { | ||
const handleSet = _this._singletonsServicesHandles.get(serviceName); | ||
yield Promise.all(reversedServiceSequence.pop().map(serviceName => { | ||
const singletonServiceDescriptor = _this._singletonsServicesDescriptors.get(serviceName); | ||
handleSet.delete(siloContext.name); | ||
const serviceDescriptor = singletonServiceDescriptor || siloContext.servicesDescriptors.get(serviceName); | ||
let serviceShutdownPromise = _this._singletonsServicesShutdownsPromises.get(serviceName) || siloContext.servicesShutdownsPromises.get(serviceName); | ||
if (handleSet.size) { | ||
debug('Singleton is used elsewhere:', serviceName, handleSet); | ||
return Promise.resolve(); | ||
} | ||
if (serviceShutdownPromise) { | ||
debug('Reusing a service shutdown promise:', serviceName); | ||
return serviceShutdownPromise; | ||
} | ||
_this._singletonsServicesDescriptors.delete(serviceName); | ||
} | ||
if (reversedServiceSequence.some(servicesDeclarations => servicesDeclarations.includes(serviceName))) { | ||
debug('Delaying service shutdown:', serviceName); | ||
return Promise.resolve(); | ||
} | ||
debug('Shutting down a service:', serviceName); | ||
serviceShutdownPromise = serviceDescriptor.dispose ? serviceDescriptor.dispose() : Promise.resolve(); | ||
if (singletonServiceDescriptor) { | ||
const handleSet = _this._singletonsServicesHandles.get(serviceName); | ||
if (singletonServiceDescriptor) { | ||
_this._singletonsServicesShutdownsPromises.set(serviceName, serviceShutdownPromise); | ||
handleSet.delete(siloContext.name); | ||
if (handleSet.size) { | ||
debug('Singleton is used elsewhere:', serviceName, handleSet); | ||
return Promise.resolve(); | ||
} | ||
_this._singletonsServicesDescriptors.delete(serviceName); | ||
} | ||
debug('Shutting down a service:', serviceName); | ||
serviceShutdownPromise = serviceDescriptor.dispose ? serviceDescriptor.dispose() : Promise.resolve(); | ||
if (singletonServiceDescriptor) { | ||
_this._singletonsServicesShutdownsPromises.set(serviceName, serviceShutdownPromise); | ||
} | ||
siloContext.servicesShutdownsPromises.set(serviceName, serviceShutdownPromise); | ||
return serviceShutdownPromise; | ||
})); | ||
yield _shutdownNextServices(reversedServiceSequence); | ||
}); | ||
return _shutdownNextServices2.apply(this, arguments); | ||
} | ||
}); | ||
siloContext.servicesShutdownsPromises.set(serviceName, serviceShutdownPromise); | ||
return serviceShutdownPromise; | ||
})).then(_shutdownNextServices.bind(null, reversedServiceSequence)); | ||
} | ||
}, | ||
dispose: Promise.resolve.bind(Promise) | ||
}); | ||
return function service() { | ||
return _ref.apply(this, arguments); | ||
}; | ||
}(), | ||
dispose: Promise.resolve.bind(Promise) | ||
}); | ||
this._silosContexts.add(siloContext); | ||
_this2._silosContexts.add(siloContext); | ||
return this._initializeDependencies(siloContext, siloContext.name, internalDependencies).then(servicesHash => { | ||
const servicesHash = yield _this2._initializeDependencies(siloContext, siloContext.name, internalDependencies, { | ||
injectOnly: false, | ||
autoloading: false | ||
}); | ||
debug('Handling fatal errors:', siloContext.errorsPromises); | ||
@@ -554,9 +590,16 @@ Promise.all(siloContext.errorsPromises).catch(siloContext.throwFatalError); | ||
}, {}); | ||
}); | ||
})(); | ||
} | ||
/** | ||
* Initialize or return a service descriptor | ||
* @param {Object} siloContext Current execution silo context | ||
* @param {Boolean} injectOnly Flag indicating if existing services only should be used | ||
* @param {String} serviceName Service name. | ||
* @param {Object} siloContext | ||
* Current execution silo context | ||
* @param {String} serviceName | ||
* Service name. | ||
* @param {Object} options | ||
* Options for service retrieval | ||
* @param {Boolean} options.injectOnly | ||
* Flag indicating if existing services only should be used | ||
* @param {Boolean} options.autoloading | ||
* Flag to indicating $autoload dependencies on the fly loading | ||
* @param {String} serviceProvider Service provider. | ||
@@ -567,6 +610,10 @@ * @return {Promise} Service dependencies hash promise. | ||
_getServiceDescriptor(siloContext, injectOnly, serviceName) { | ||
_getServiceDescriptor(siloContext, serviceName, { | ||
injectOnly, | ||
autoloading | ||
}) { | ||
let serviceDescriptor = this._singletonsServicesDescriptors.get(serviceName); | ||
if (serviceDescriptor) { | ||
// The auto loader must only have static dependencies | ||
this._singletonsServicesHandles.get(serviceName).add(siloContext.name); | ||
@@ -588,9 +635,17 @@ } else { | ||
return this._initializeServiceDescriptor(siloContext, serviceName); | ||
return this._initializeServiceDescriptor(siloContext, serviceName, { | ||
autoloading: autoloading || AUTOLOAD === serviceName, | ||
injectOnly | ||
}); | ||
} | ||
/** | ||
* Initialize a service | ||
* Initialize a service descriptor | ||
* @param {Object} siloContext Current execution silo context | ||
* @param {String} serviceName Service name. | ||
* @param {String} serviceProvider Service provider. | ||
* @param {Object} options | ||
* Options for service retrieval | ||
* @param {Boolean} options.injectOnly | ||
* Flag indicating if existing services only should be used | ||
* @param {Boolean} options.autoloading | ||
* Flag to indicating $autoload dependendencies on the fly loading. | ||
* @return {Promise} Service dependencies hash promise. | ||
@@ -600,73 +655,129 @@ */ | ||
_initializeServiceDescriptor(siloContext, serviceName) { | ||
const serviceProvider = this._servicesProviders.get(serviceName); | ||
_initializeServiceDescriptor(siloContext, serviceName, { | ||
autoloading, | ||
injectOnly | ||
}) { | ||
var _this3 = this; | ||
let serviceDescriptorPromise; | ||
debug('Initializing a service descriptor:', serviceName); | ||
return _asyncToGenerator(function* () { | ||
let serviceProvider = _this3._servicesProviders.get(serviceName); | ||
if (!serviceProvider) { | ||
debug('No service provider:', serviceName); | ||
serviceDescriptorPromise = Promise.reject(new _yerror.default(E_UNMATCHED_DEPENDENCY, serviceName)); | ||
siloContext.servicesDescriptors.set(serviceName, serviceDescriptorPromise); | ||
return serviceDescriptorPromise; | ||
} // A singleton service may use a reserved resource | ||
// like a TCP socket. This is why we have to be aware | ||
// of singleton services full shutdown before creating | ||
// a new one | ||
debug('Initializing a service descriptor:', serviceName); | ||
if (!serviceProvider) { | ||
debug('No service provider:', serviceName); | ||
serviceDescriptorPromise = (this._singletonsServicesShutdownsPromises.get(serviceName) || Promise.resolve()). // Anyway delete any shutdown promise before instanciating | ||
// a new service | ||
then(() => { | ||
this._singletonsServicesShutdownsPromises.delete(serviceName); | ||
try { | ||
if (autoloading) { | ||
// The auto loader must only have static dependencies | ||
throw new _yerror.default(E_AUTOLOADER_DYNAMIC_DEPENDENCY, serviceName); | ||
} | ||
siloContext.servicesShutdownsPromises.delete(serviceName); | ||
}).then(this._initializeDependencies.bind(this, siloContext, serviceName, serviceProvider[_util.SPECIAL_PROPS.INJECT])); | ||
serviceDescriptorPromise = serviceDescriptorPromise.then(servicesHash => { | ||
debug('Successfully gathered service dependencies:', serviceName); | ||
return serviceProvider[_util.SPECIAL_PROPS.INJECT].reduce((finalHash, dependencyDeclaration) => { | ||
const { | ||
serviceName, | ||
mappedName | ||
} = (0, _util.parseDependencyDeclaration)(dependencyDeclaration); | ||
finalHash[serviceName] = servicesHash[mappedName]; | ||
return finalHash; | ||
}, {}); | ||
}).then(serviceProvider).then(serviceDescriptor => { | ||
if (!serviceDescriptor) { | ||
debug('Provider did not return a descriptor:', serviceName); | ||
return Promise.reject(new _yerror.default(E_BAD_SERVICE_PROVIDER, serviceName)); | ||
} | ||
if (!_this3._servicesProviders.get(AUTOLOAD)) { | ||
throw new _yerror.default(E_UNMATCHED_DEPENDENCY, serviceName); | ||
} | ||
debug('Successfully initialized a service descriptor:', serviceName); | ||
debug(`Loading the $autoload to lookup for: ${serviceName}.`); | ||
if (serviceDescriptor.fatalErrorPromise) { | ||
debug('Registering service descriptor error promise:', serviceName); | ||
siloContext.errorsPromises.push(serviceDescriptor.fatalErrorPromise); | ||
} | ||
try { | ||
const autoloadingDescriptor = yield _this3._getServiceDescriptor(siloContext, AUTOLOAD, { | ||
injectOnly, | ||
autoloading: true | ||
}); | ||
const { | ||
initializer, | ||
path | ||
} = yield autoloadingDescriptor.service(serviceName); | ||
siloContext.servicesDescriptors.set(serviceName, serviceDescriptor); | ||
return serviceDescriptor; | ||
}).catch(err => { | ||
debug('Error initializing a service descriptor:', serviceName, err.stack); | ||
if (typeof initializer !== 'function') { | ||
throw new _yerror.default(E_BAD_AUTOLOADED_INITIALIZER, serviceName, initializer); | ||
} | ||
if (E_UNMATCHED_DEPENDENCY === err.code) { | ||
throw _yerror.default.wrap(...[err, E_UNMATCHED_DEPENDENCY, serviceName].concat(err.params)); | ||
if (initializer[_util.SPECIAL_PROPS.NAME] !== serviceName) { | ||
throw new _yerror.default(E_AUTOLOADED_INITIALIZER_MISMATCH, serviceName, initializer[_util.SPECIAL_PROPS.NAME]); | ||
} | ||
debug(`Loaded the auto loader at path ${path}.`); | ||
_this3.register(initializer); | ||
serviceProvider = _this3._servicesProviders.get(serviceName); | ||
} catch (err) { | ||
debug(`Could not load ${serviceName} via the auto loader.`); | ||
throw err; | ||
} | ||
} catch (err) { | ||
const serviceDescriptorPromise = Promise.reject(err); | ||
siloContext.servicesDescriptors.set(serviceName, serviceDescriptorPromise); | ||
yield serviceDescriptorPromise; | ||
} | ||
} | ||
throw err; | ||
}); | ||
const serviceDescriptorPromise = _asyncToGenerator(function* () { | ||
let serviceDescriptor; | ||
if (serviceProvider[_util.SPECIAL_PROPS.OPTIONS].singleton) { | ||
const handlesSet = new Set(); | ||
handlesSet.add(siloContext.name); | ||
try { | ||
// A singleton service may use a reserved resource | ||
// like a TCP socket. This is why we have to be aware | ||
// of singleton services full shutdown before creating | ||
// a new one | ||
yield _this3._singletonsServicesShutdownsPromises.get(serviceName) || Promise.resolve(); // Anyway delete any shutdown promise before instanciating | ||
// a new service | ||
this._singletonsServicesHandles.set(serviceName, handlesSet); | ||
_this3._singletonsServicesShutdownsPromises.delete(serviceName); | ||
this._singletonsServicesDescriptors.set(serviceName, serviceDescriptorPromise); | ||
} else { | ||
siloContext.servicesDescriptors.set(serviceName, serviceDescriptorPromise); | ||
} | ||
siloContext.servicesShutdownsPromises.delete(serviceName); | ||
const servicesHash = yield _this3._initializeDependencies(siloContext, serviceName, serviceProvider[_util.SPECIAL_PROPS.INJECT], { | ||
injectOnly, | ||
autoloading | ||
}); | ||
debug('Successfully gathered service dependencies:', serviceName); | ||
serviceDescriptor = yield serviceProvider(serviceProvider[_util.SPECIAL_PROPS.INJECT].reduce((finalHash, dependencyDeclaration) => { | ||
const { | ||
serviceName, | ||
mappedName | ||
} = (0, _util.parseDependencyDeclaration)(dependencyDeclaration); | ||
finalHash[serviceName] = servicesHash[mappedName]; | ||
return finalHash; | ||
}, {})); | ||
return serviceDescriptorPromise; | ||
if (!serviceDescriptor) { | ||
debug('Provider did not return a descriptor:', serviceName); | ||
return Promise.reject(new _yerror.default(E_BAD_SERVICE_PROVIDER, serviceName)); | ||
} | ||
debug('Successfully initialized a service descriptor:', serviceName); | ||
if (serviceDescriptor.fatalErrorPromise) { | ||
debug('Registering service descriptor error promise:', serviceName); | ||
siloContext.errorsPromises.push(serviceDescriptor.fatalErrorPromise); | ||
} | ||
siloContext.servicesDescriptors.set(serviceName, serviceDescriptor); | ||
} catch (err) { | ||
debug('Error initializing a service descriptor:', serviceName, err.stack); | ||
if (E_UNMATCHED_DEPENDENCY === err.code) { | ||
throw _yerror.default.wrap(...[err, E_UNMATCHED_DEPENDENCY, serviceName].concat(err.params)); | ||
} | ||
throw err; | ||
} | ||
return serviceDescriptor; | ||
})(); | ||
if (serviceProvider[_util.SPECIAL_PROPS.OPTIONS].singleton) { | ||
const handlesSet = new Set(); | ||
handlesSet.add(siloContext.name); | ||
_this3._singletonsServicesHandles.set(serviceName, handlesSet); | ||
_this3._singletonsServicesDescriptors.set(serviceName, serviceDescriptorPromise); | ||
} else { | ||
siloContext.servicesDescriptors.set(serviceName, serviceDescriptorPromise); | ||
} | ||
return serviceDescriptorPromise; | ||
})(); | ||
} | ||
@@ -678,3 +789,8 @@ /** | ||
* @param {String} servicesDeclarations Dependencies declarations. | ||
* @param {Boolean} injectOnly Flag indicating if existing services only should be used | ||
* @param {Object} options | ||
* Options for service retrieval | ||
* @param {Boolean} options.injectOnly | ||
* Flag indicating if existing services only should be used | ||
* @param {Boolean} options.autoloading | ||
* Flag to indicating $autoload dependendencies on the fly loading. | ||
* @return {Promise} Service dependencies hash promise. | ||
@@ -684,20 +800,29 @@ */ | ||
_initializeDependencies(siloContext, serviceName, servicesDeclarations, injectOnly = false) { | ||
debug('Initializing dependencies:', serviceName, servicesDeclarations); | ||
return Promise.resolve().then(() => Promise.all(servicesDeclarations.map(serviceDeclaration => { | ||
const { | ||
mappedName, | ||
optional | ||
} = (0, _util.parseDependencyDeclaration)(serviceDeclaration); | ||
return this._getServiceDescriptor(siloContext, injectOnly, mappedName).catch(err => { | ||
if (optional) { | ||
return Promise.resolve(); | ||
} | ||
_initializeDependencies(siloContext, serviceName, servicesDeclarations, { | ||
injectOnly = false, | ||
autoloading = false | ||
}) { | ||
var _this4 = this; | ||
throw err; | ||
}); | ||
})).then(servicesDescriptors => { | ||
return _asyncToGenerator(function* () { | ||
debug('Initializing dependencies:', serviceName, servicesDeclarations); | ||
const servicesDescriptors = yield Promise.all(servicesDeclarations.map(serviceDeclaration => { | ||
const { | ||
mappedName, | ||
optional | ||
} = (0, _util.parseDependencyDeclaration)(serviceDeclaration); | ||
return _this4._getServiceDescriptor(siloContext, mappedName, { | ||
injectOnly, | ||
autoloading | ||
}).catch(err => { | ||
if (optional) { | ||
return Promise.resolve(); | ||
} | ||
throw err; | ||
}); | ||
})); | ||
debug('Initialized dependencies descriptors:', serviceName, servicesDeclarations); | ||
siloContext.servicesSequence.push(servicesDeclarations.map(_pickMappedNameFromDeclaration)); | ||
return Promise.all(servicesDescriptors.map(serviceDescriptor => { | ||
const services = yield Promise.all(servicesDescriptors.map(serviceDescriptor => { | ||
if (!serviceDescriptor) { | ||
@@ -709,8 +834,9 @@ return {}.undef; | ||
})); | ||
}).then(services => services.reduce((hash, service, index) => { | ||
const mappedName = _pickMappedNameFromDeclaration(servicesDeclarations[index]); | ||
return services.reduce((hash, service, index) => { | ||
const mappedName = _pickMappedNameFromDeclaration(servicesDeclarations[index]); | ||
hash[mappedName] = service; | ||
return hash; | ||
}, {}))); | ||
hash[mappedName] = service; | ||
return hash; | ||
}, {}); | ||
})(); | ||
} | ||
@@ -735,6 +861,5 @@ | ||
const { | ||
serviceName, | ||
mappedName | ||
} = (0, _util.parseDependencyDeclaration)(dependencyDeclaration); | ||
return mappedName || serviceName; | ||
return mappedName; | ||
} | ||
@@ -771,3 +896,3 @@ | ||
if (!classes[style.className]) { | ||
throw new _yerror.default('E_BAD_CLASS', style.className, serviceName); | ||
throw new _yerror.default(E_BAD_CLASS, style.className, serviceName); | ||
} | ||
@@ -780,3 +905,3 @@ | ||
if (!classes[style.className]) { | ||
throw new _yerror.default('E_BAD_CLASS', style.className, dependedServiceName); | ||
throw new _yerror.default(E_BAD_CLASS, style.className, dependedServiceName); | ||
} | ||
@@ -783,0 +908,0 @@ |
@@ -7,7 +7,14 @@ "use strict"; | ||
var _yerror = _interopRequireDefault(require("yerror")); | ||
var _index = require("./index"); | ||
var _util = require("./util"); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
/* eslint max-nested-callbacks:0 */ | ||
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } } | ||
function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; } | ||
describe('Knifecycle', () => { | ||
@@ -59,2 +66,41 @@ let $; | ||
}); | ||
describe('register', () => { | ||
it('should fail when intitializer is no a function', () => { | ||
_assert.default.throws(() => { | ||
$.register('not_a_function'); | ||
}, err => { | ||
_assert.default.deepEqual(err.code, 'E_BAD_INITIALIZER'); | ||
_assert.default.deepEqual(err.params, ['not_a_function']); | ||
return true; | ||
}); | ||
}); | ||
it('should fail with no service name', () => { | ||
_assert.default.throws(() => { | ||
$.register(() => {}); | ||
}, err => { | ||
_assert.default.deepEqual(err.code, 'E_ANONYMOUS_ANALYZER'); | ||
_assert.default.deepEqual(err.params, []); | ||
return true; | ||
}); | ||
}); | ||
it('should fail with a bad service type', () => { | ||
_assert.default.throws(() => { | ||
const fn = () => {}; | ||
fn[_util.SPECIAL_PROPS.NAME] = 'test'; | ||
fn[_util.SPECIAL_PROPS.TYPE] = 'not_allowed_type'; | ||
$.register(fn); | ||
}, err => { | ||
_assert.default.deepEqual(err.code, 'E_BAD_INITIALIZER_TYPE'); | ||
_assert.default.deepEqual(err.params, ['not_allowed_type', ['provider', 'service']]); | ||
return true; | ||
}); | ||
}); | ||
}); | ||
describe('provider', () => { | ||
@@ -130,20 +176,26 @@ it('should register provider', () => { | ||
describe('run', () => { | ||
it('should work with no dependencies', done => { | ||
$.run([]).then(dependencies => { | ||
_assert.default.deepEqual(dependencies, {}); | ||
}).then(() => done()).catch(done); | ||
}); | ||
it('should work with constant dependencies', done => { | ||
it('should work with no dependencies', | ||
/*#__PURE__*/ | ||
_asyncToGenerator(function* () { | ||
const dependencies = yield $.run([]); | ||
_assert.default.deepEqual(dependencies, {}); | ||
})); | ||
it('should work with constant dependencies', | ||
/*#__PURE__*/ | ||
_asyncToGenerator(function* () { | ||
$.constant('ENV', ENV); | ||
$.constant('time', time); | ||
$.run(['time', 'ENV']).then(dependencies => { | ||
_assert.default.deepEqual(Object.keys(dependencies), ['time', 'ENV']); | ||
const dependencies = yield $.run(['time', 'ENV']); | ||
_assert.default.deepEqual(dependencies, { | ||
ENV, | ||
time | ||
}); | ||
}).then(() => done()).catch(done); | ||
}); | ||
it('should work with service dependencies', done => { | ||
_assert.default.deepEqual(Object.keys(dependencies), ['time', 'ENV']); | ||
_assert.default.deepEqual(dependencies, { | ||
ENV, | ||
time | ||
}); | ||
})); | ||
it('should work with service dependencies', | ||
/*#__PURE__*/ | ||
_asyncToGenerator(function* () { | ||
$.service('sample', (0, _index.inject)(['time'], function sampleService({ | ||
@@ -155,26 +207,30 @@ time | ||
$.constant('time', time); | ||
$.run(['sample']).then(dependencies => { | ||
_assert.default.deepEqual(Object.keys(dependencies), ['sample']); | ||
const dependencies = yield $.run(['sample']); | ||
_assert.default.deepEqual(dependencies, { | ||
sample: 'function' | ||
}); | ||
}).then(() => done()).catch(done); | ||
}); | ||
it('should work with simple dependencies', done => { | ||
_assert.default.deepEqual(Object.keys(dependencies), ['sample']); | ||
_assert.default.deepEqual(dependencies, { | ||
sample: 'function' | ||
}); | ||
})); | ||
it('should work with simple dependencies', | ||
/*#__PURE__*/ | ||
_asyncToGenerator(function* () { | ||
$.constant('ENV', ENV); | ||
$.constant('time', time); | ||
$.provider('hash', (0, _index.inject)(['ENV'], hashProvider)); | ||
$.run(['time', 'hash']).then(dependencies => { | ||
_assert.default.deepEqual(Object.keys(dependencies), ['time', 'hash']); | ||
const dependencies = yield $.run(['time', 'hash']); | ||
_assert.default.deepEqual(dependencies, { | ||
hash: { | ||
ENV | ||
}, | ||
time | ||
}); | ||
}).then(() => done()).catch(done); | ||
}); | ||
it('should work with given optional dependencies', done => { | ||
_assert.default.deepEqual(Object.keys(dependencies), ['time', 'hash']); | ||
_assert.default.deepEqual(dependencies, { | ||
hash: { | ||
ENV | ||
}, | ||
time | ||
}); | ||
})); | ||
it('should work with given optional dependencies', | ||
/*#__PURE__*/ | ||
_asyncToGenerator(function* () { | ||
$.constant('ENV', ENV); | ||
@@ -184,31 +240,35 @@ $.constant('DEBUG', {}); | ||
$.provider('hash', (0, _index.inject)(['ENV', '?DEBUG'], hashProvider)); | ||
$.run(['time', 'hash']).then(dependencies => { | ||
_assert.default.deepEqual(Object.keys(dependencies), ['time', 'hash']); | ||
const dependencies = yield $.run(['time', 'hash']); | ||
_assert.default.deepEqual(dependencies, { | ||
hash: { | ||
ENV, | ||
DEBUG: {} | ||
}, | ||
time | ||
}); | ||
}).then(() => done()).catch(done); | ||
}); | ||
it('should work with lacking optional dependencies', done => { | ||
_assert.default.deepEqual(Object.keys(dependencies), ['time', 'hash']); | ||
_assert.default.deepEqual(dependencies, { | ||
hash: { | ||
ENV, | ||
DEBUG: {} | ||
}, | ||
time | ||
}); | ||
})); | ||
it('should work with lacking optional dependencies', | ||
/*#__PURE__*/ | ||
_asyncToGenerator(function* () { | ||
$.constant('ENV', ENV); | ||
$.constant('time', time); | ||
$.provider('hash', (0, _index.inject)(['ENV', '?DEBUG'], hashProvider)); | ||
$.run(['time', 'hash']).then(dependencies => { | ||
_assert.default.deepEqual(Object.keys(dependencies), ['time', 'hash']); | ||
const dependencies = yield $.run(['time', 'hash']); | ||
_assert.default.deepEqual(dependencies, { | ||
hash: { | ||
ENV, | ||
DEBUG: {}.undef | ||
}, | ||
time | ||
}); | ||
}).then(() => done()).catch(done); | ||
}); | ||
it('should work with deeper dependencies', done => { | ||
_assert.default.deepEqual(Object.keys(dependencies), ['time', 'hash']); | ||
_assert.default.deepEqual(dependencies, { | ||
hash: { | ||
ENV, | ||
DEBUG: {}.undef | ||
}, | ||
time | ||
}); | ||
})); | ||
it('should work with deeper dependencies', | ||
/*#__PURE__*/ | ||
_asyncToGenerator(function* () { | ||
$.constant('ENV', ENV); | ||
@@ -222,7 +282,9 @@ $.constant('time', time); | ||
$.provider('hash5', (0, _index.inject)(['hash4'], hashProvider)); | ||
$.run(['hash5', 'time']).then(dependencies => { | ||
_assert.default.deepEqual(Object.keys(dependencies), ['hash5', 'time']); | ||
}).then(() => done()).catch(done); | ||
}); | ||
it('should instanciate services once', done => { | ||
const dependencies = yield $.run(['hash5', 'time']); | ||
_assert.default.deepEqual(Object.keys(dependencies), ['hash5', 'time']); | ||
})); | ||
it('should instanciate services once', | ||
/*#__PURE__*/ | ||
_asyncToGenerator(function* () { | ||
const timeServiceStub = _sinon.default.spy(timeService); | ||
@@ -235,9 +297,11 @@ | ||
$.provider('hash3', (0, _index.inject)(['ENV', 'time'], hashProvider)); | ||
$.run(['hash', 'hash2', 'hash3', 'time']).then(dependencies => { | ||
_assert.default.deepEqual(Object.keys(dependencies), ['hash', 'hash2', 'hash3', 'time']); | ||
const dependencies = yield $.run(['hash', 'hash2', 'hash3', 'time']); | ||
_assert.default.deepEqual(timeServiceStub.args, [[{}]]); | ||
}).then(() => done()).catch(done); | ||
}); | ||
it('should instanciate a single mapped service', done => { | ||
_assert.default.deepEqual(Object.keys(dependencies), ['hash', 'hash2', 'hash3', 'time']); | ||
_assert.default.deepEqual(timeServiceStub.args, [[{}]]); | ||
})); | ||
it('should instanciate a single mapped service', | ||
/*#__PURE__*/ | ||
_asyncToGenerator(function* () { | ||
const providerStub = _sinon.default.stub().returns(Promise.resolve({ | ||
@@ -253,13 +317,15 @@ service: 'stub' | ||
$.provider('mappedStub2', providerStub2); | ||
$.run(['stub>mappedStub']).then(dependencies => { | ||
_assert.default.deepEqual(dependencies, { | ||
stub: 'stub' | ||
}); | ||
const dependencies = yield $.run(['stub>mappedStub']); | ||
_assert.default.deepEqual(providerStub.args, [[{ | ||
stub2: 'stub2' | ||
}]]); | ||
}).then(() => done()).catch(done); | ||
}); | ||
it('should instanciate several services with mappings', done => { | ||
_assert.default.deepEqual(dependencies, { | ||
stub: 'stub' | ||
}); | ||
_assert.default.deepEqual(providerStub.args, [[{ | ||
stub2: 'stub2' | ||
}]]); | ||
})); | ||
it('should instanciate several services with mappings', | ||
/*#__PURE__*/ | ||
_asyncToGenerator(function* () { | ||
const timeServiceStub = _sinon.default.spy(timeService); | ||
@@ -272,48 +338,65 @@ | ||
$.provider('aHash3', (0, _index.inject)(['ENV', 'hash>aHash'], hashProvider)); | ||
$.run(['hash2>aHash2', 'hash3>aHash3', 'time>aTime']).then(dependencies => { | ||
_assert.default.deepEqual(Object.keys(dependencies), ['hash2', 'hash3', 'time']); | ||
const dependencies = yield $.run(['hash2>aHash2', 'hash3>aHash3', 'time>aTime']); | ||
_assert.default.deepEqual(timeServiceStub.args, [[{}]]); | ||
}).then(() => done()).catch(done); | ||
}); | ||
it('should fail with bad service', done => { | ||
_assert.default.deepEqual(Object.keys(dependencies), ['hash2', 'hash3', 'time']); | ||
_assert.default.deepEqual(timeServiceStub.args, [[{}]]); | ||
})); | ||
it('should fail with bad service', | ||
/*#__PURE__*/ | ||
_asyncToGenerator(function* () { | ||
$.service('lol', () => {}); | ||
$.run(['lol']).then(() => { | ||
try { | ||
yield $.run(['lol']); | ||
throw new Error('E_UNEXPECTED_SUCCESS'); | ||
}).catch(err => { | ||
} catch (err) { | ||
_assert.default.deepEqual(err.code, 'E_BAD_SERVICE_PROMISE'); | ||
_assert.default.deepEqual(err.params, ['lol']); | ||
}).then(() => done()).catch(done); | ||
}); | ||
it('should fail with bad provider', done => { | ||
} | ||
})); | ||
it('should fail with bad provider', | ||
/*#__PURE__*/ | ||
_asyncToGenerator(function* () { | ||
$.provider('lol', () => {}); | ||
$.run(['lol']).then(() => { | ||
try { | ||
yield $.run(['lol']); | ||
throw new Error('E_UNEXPECTED_SUCCESS'); | ||
}).catch(err => { | ||
} catch (err) { | ||
_assert.default.deepEqual(err.code, 'E_BAD_SERVICE_PROVIDER'); | ||
_assert.default.deepEqual(err.params, ['lol']); | ||
}).then(() => done()).catch(done); | ||
}); | ||
it('should fail with bad service in a provider', done => { | ||
} | ||
})); | ||
it('should fail with bad service in a provider', | ||
/*#__PURE__*/ | ||
_asyncToGenerator(function* () { | ||
$.provider('lol', () => Promise.resolve()); | ||
$.run(['lol']).then(() => { | ||
try { | ||
yield $.run(['lol']); | ||
throw new Error('E_UNEXPECTED_SUCCESS'); | ||
}).catch(err => { | ||
} catch (err) { | ||
_assert.default.deepEqual(err.code, 'E_BAD_SERVICE_PROVIDER'); | ||
_assert.default.deepEqual(err.params, ['lol']); | ||
}).then(() => done()).catch(done); | ||
}); | ||
it('should fail with undeclared dependencies', done => { | ||
$.run(['lol']).then(() => { | ||
} | ||
})); | ||
it('should fail with undeclared dependencies', | ||
/*#__PURE__*/ | ||
_asyncToGenerator(function* () { | ||
try { | ||
yield $.run(['lol']); | ||
throw new Error('E_UNEXPECTED_SUCCESS'); | ||
}).catch(err => { | ||
} catch (err) { | ||
_assert.default.deepEqual(err.code, 'E_UNMATCHED_DEPENDENCY'); | ||
_assert.default.deepEqual(err.params, ['lol']); | ||
}).then(() => done()).catch(done); | ||
}); | ||
it('should fail with undeclared dependencies upstream', done => { | ||
} | ||
})); | ||
it('should fail with undeclared dependencies upstream', | ||
/*#__PURE__*/ | ||
_asyncToGenerator(function* () { | ||
$.constant('ENV', ENV); | ||
@@ -323,11 +406,15 @@ $.constant('time', time); | ||
$.provider('hash2', (0, _index.inject)(['ENV', 'lol'], hashProvider)); | ||
$.run(['time', 'hash']).then(() => { | ||
try { | ||
yield $.run(['time', 'hash']); | ||
throw new Error('E_UNEXPECTED_SUCCESS'); | ||
}).catch(err => { | ||
} catch (err) { | ||
_assert.default.deepEqual(err.code, 'E_UNMATCHED_DEPENDENCY'); | ||
_assert.default.deepEqual(err.params, ['hash', 'hash2', 'lol']); | ||
}).then(() => done()).catch(done); | ||
}); | ||
it('should provide a fatal error handler', done => { | ||
} | ||
})); | ||
it('should provide a fatal error handler', | ||
/*#__PURE__*/ | ||
_asyncToGenerator(function* () { | ||
$.constant('ENV', ENV); | ||
@@ -349,6 +436,10 @@ $.constant('time', time); | ||
function dbProvider({ | ||
ENV | ||
}) { | ||
return Promise.resolve().then(() => { | ||
function dbProvider(_x) { | ||
return _dbProvider.apply(this, arguments); | ||
} | ||
function _dbProvider() { | ||
_dbProvider = _asyncToGenerator(function* ({ | ||
ENV | ||
}) { | ||
let service; | ||
@@ -367,81 +458,385 @@ const fatalErrorPromise = new Promise((resolve, reject) => { | ||
}); | ||
return _dbProvider.apply(this, arguments); | ||
} | ||
$.run(['time', 'hash', 'db', 'process']).then(({ | ||
const { | ||
process, | ||
db | ||
}) => { | ||
process.fatalErrorPromise.then(() => { | ||
done(new Error('E_UNEXPECTED_SUCCESS')); | ||
}).catch(err => { | ||
_assert.default.deepEqual(err.message, 'E_DB_ERROR'); | ||
}).then(() => done()).catch(done); | ||
} = yield $.run(['time', 'hash', 'db', 'process']); | ||
try { | ||
db.reject(new Error('E_DB_ERROR')); | ||
}).catch(done); | ||
}); | ||
yield process.fatalErrorPromise; | ||
throw new Error('E_UNEXPECTED_SUCCESS'); | ||
} catch (err) { | ||
_assert.default.deepEqual(err.message, 'E_DB_ERROR'); | ||
} | ||
})); | ||
}); | ||
describe('autoload', () => { | ||
it('should work with lacking autoloaded dependencies', | ||
/*#__PURE__*/ | ||
_asyncToGenerator(function* () { | ||
$.register((0, _util.initializer)({ | ||
type: 'service', | ||
name: '$autoload', | ||
inject: [] | ||
}, | ||
/*#__PURE__*/ | ||
_asyncToGenerator(function* () { | ||
return ( | ||
/*#__PURE__*/ | ||
function () { | ||
var _ref19 = _asyncToGenerator(function* (serviceName) { | ||
return { | ||
path: '/path/of/debug', | ||
initializer: (0, _util.initializer)({ | ||
type: 'service', | ||
name: 'DEBUG', | ||
inject: [] | ||
}, | ||
/*#__PURE__*/ | ||
_asyncToGenerator(function* () { | ||
return 'THE_DEBUG:' + serviceName; | ||
})) | ||
}; | ||
}); | ||
return function (_x2) { | ||
return _ref19.apply(this, arguments); | ||
}; | ||
}() | ||
); | ||
}))); | ||
$.constant('ENV', ENV); | ||
$.constant('time', time); | ||
$.provider('hash', (0, _index.inject)(['ENV', '?DEBUG'], hashProvider)); | ||
const dependencies = yield $.run(['time', 'hash']); | ||
_assert.default.deepEqual(Object.keys(dependencies), ['time', 'hash']); | ||
_assert.default.deepEqual(dependencies, { | ||
hash: { | ||
ENV, | ||
DEBUG: 'THE_DEBUG:DEBUG' | ||
}, | ||
time | ||
}); | ||
})); | ||
it('should work with deeper several lacking dependencies', | ||
/*#__PURE__*/ | ||
_asyncToGenerator(function* () { | ||
$.register((0, _util.initializer)({ | ||
name: '$autoload', | ||
type: 'service' | ||
}, | ||
/*#__PURE__*/ | ||
_asyncToGenerator(function* () { | ||
return ( | ||
/*#__PURE__*/ | ||
function () { | ||
var _ref23 = _asyncToGenerator(function* (serviceName) { | ||
return { | ||
path: `/path/to/${serviceName}`, | ||
initializer: (0, _util.initializer)({ | ||
type: 'provider', | ||
name: serviceName, | ||
inject: 'hash2' === serviceName ? ['hash1'] : 'hash4' === serviceName ? ['hash3'] : [] | ||
}, hashProvider) | ||
}; | ||
}); | ||
return function (_x3) { | ||
return _ref23.apply(this, arguments); | ||
}; | ||
}() | ||
); | ||
}))); | ||
$.constant('ENV', ENV); | ||
$.constant('time', time); | ||
$.provider('hash', (0, _index.inject)(['ENV'], hashProvider)); | ||
$.provider('hash1', (0, _index.inject)(['hash'], hashProvider)); | ||
$.provider('hash3', (0, _index.inject)(['hash2'], hashProvider)); | ||
$.provider('hash5', (0, _index.inject)(['hash4'], hashProvider)); | ||
const dependencies = yield $.run(['hash5', 'time']); | ||
_assert.default.deepEqual(Object.keys(dependencies), ['hash5', 'time']); | ||
})); | ||
it('should work with various dependencies', | ||
/*#__PURE__*/ | ||
_asyncToGenerator(function* () { | ||
$.provider('hash', (0, _index.inject)(['hash2'], hashProvider)); | ||
$.provider('hash3', (0, _index.inject)(['?ENV'], hashProvider)); | ||
$.constant('DEBUG', 1); | ||
$.register((0, _util.initializer)({ | ||
type: 'service', | ||
name: '$autoload', | ||
inject: ['?ENV', 'DEBUG'] | ||
}, | ||
/*#__PURE__*/ | ||
_asyncToGenerator(function* () { | ||
return ( | ||
/*#__PURE__*/ | ||
function () { | ||
var _ref26 = _asyncToGenerator(function* (serviceName) { | ||
return { | ||
path: '/path/of/debug', | ||
initializer: (0, _util.initializer)({ | ||
type: 'service', | ||
name: 'hash2', | ||
inject: ['hash3'] | ||
}, | ||
/*#__PURE__*/ | ||
_asyncToGenerator(function* () { | ||
return 'THE_HASH:' + serviceName; | ||
})) | ||
}; | ||
}); | ||
return function (_x4) { | ||
return _ref26.apply(this, arguments); | ||
}; | ||
}() | ||
); | ||
}))); | ||
const dependencies = yield $.run(['hash', '?ENV']); | ||
_assert.default.deepEqual(Object.keys(dependencies), ['hash', 'ENV']); | ||
})); | ||
it('should fail when autoload does not exists', | ||
/*#__PURE__*/ | ||
_asyncToGenerator(function* () { | ||
try { | ||
yield $.run(['test']); | ||
throw new _yerror.default('E_UNEXPECTED_SUCCESS'); | ||
} catch (err) { | ||
_assert.default.equal(err.code, 'E_UNMATCHED_DEPENDENCY'); | ||
} | ||
})); | ||
it('should fail when autoloaded dependencies are not found', | ||
/*#__PURE__*/ | ||
_asyncToGenerator(function* () { | ||
$.register((0, _util.initializer)({ | ||
type: 'service', | ||
name: '$autoload', | ||
inject: [] | ||
}, | ||
/*#__PURE__*/ | ||
_asyncToGenerator(function* () { | ||
return ( | ||
/*#__PURE__*/ | ||
function () { | ||
var _ref31 = _asyncToGenerator(function* (serviceName) { | ||
throw new _yerror.default('E_CANNOT_AUTOLOAD', serviceName); | ||
}); | ||
return function (_x5) { | ||
return _ref31.apply(this, arguments); | ||
}; | ||
}() | ||
); | ||
}))); | ||
try { | ||
yield $.run(['test']); | ||
throw new _yerror.default('E_UNEXPECTED_SUCCESS'); | ||
} catch (err) { | ||
_assert.default.equal(err.code, 'E_CANNOT_AUTOLOAD'); | ||
_assert.default.deepEqual(err.params, ['test']); | ||
} | ||
})); | ||
it('should fail when autoloaded dependencies are not initializers', | ||
/*#__PURE__*/ | ||
_asyncToGenerator(function* () { | ||
$.register((0, _util.initializer)({ | ||
type: 'service', | ||
name: '$autoload', | ||
inject: [] | ||
}, | ||
/*#__PURE__*/ | ||
_asyncToGenerator(function* () { | ||
return ( | ||
/*#__PURE__*/ | ||
_asyncToGenerator(function* () { | ||
return 'not_an_initializer'; | ||
}) | ||
); | ||
}))); | ||
try { | ||
yield $.run(['test']); | ||
throw new _yerror.default('E_UNEXPECTED_SUCCESS'); | ||
} catch (err) { | ||
_assert.default.equal(err.code, 'E_BAD_AUTOLOADED_INITIALIZER'); | ||
_assert.default.deepEqual(err.params, ['test', {}.undef]); | ||
} | ||
})); | ||
it('should fail when autoloaded dependencies are not right initializers', | ||
/*#__PURE__*/ | ||
_asyncToGenerator(function* () { | ||
$.register((0, _util.initializer)({ | ||
type: 'service', | ||
name: '$autoload', | ||
inject: [] | ||
}, | ||
/*#__PURE__*/ | ||
_asyncToGenerator(function* () { | ||
return ( | ||
/*#__PURE__*/ | ||
function () { | ||
var _ref37 = _asyncToGenerator(function* (serviceName) { | ||
return { | ||
path: '/path/of/debug', | ||
initializer: (0, _util.initializer)({ | ||
type: 'service', | ||
name: 'not-' + serviceName, | ||
inject: [] | ||
}, | ||
/*#__PURE__*/ | ||
_asyncToGenerator(function* () { | ||
return 'THE_TEST:' + serviceName; | ||
})) | ||
}; | ||
}); | ||
return function (_x6) { | ||
return _ref37.apply(this, arguments); | ||
}; | ||
}() | ||
); | ||
}))); | ||
try { | ||
yield $.run(['test']); | ||
throw new _yerror.default('E_UNEXPECTED_SUCCESS'); | ||
} catch (err) { | ||
_assert.default.equal(err.code, 'E_AUTOLOADED_INITIALIZER_MISMATCH'); | ||
_assert.default.deepEqual(err.params, ['test', 'not-test']); | ||
} | ||
})); | ||
it('should fail when autoload depends on autoloaded/unexisting dependencies', | ||
/*#__PURE__*/ | ||
_asyncToGenerator(function* () { | ||
$.register((0, _util.initializer)({ | ||
type: 'service', | ||
name: '$autoload', | ||
inject: ['ENV'] | ||
}, | ||
/*#__PURE__*/ | ||
_asyncToGenerator(function* () { | ||
return ( | ||
/*#__PURE__*/ | ||
function () { | ||
var _ref41 = _asyncToGenerator(function* (serviceName) { | ||
return { | ||
path: '/path/of/debug', | ||
initializer: (0, _util.initializer)({ | ||
type: 'service', | ||
name: 'DEBUG', | ||
inject: [] | ||
}, | ||
/*#__PURE__*/ | ||
_asyncToGenerator(function* () { | ||
return 'THE_DEBUG:' + serviceName; | ||
})) | ||
}; | ||
}); | ||
return function (_x7) { | ||
return _ref41.apply(this, arguments); | ||
}; | ||
}() | ||
); | ||
}))); | ||
try { | ||
yield $.run(['test']); | ||
throw new _yerror.default('E_UNEXPECTED_SUCCESS'); | ||
} catch (err) { | ||
_assert.default.equal(err.code, 'E_AUTOLOADER_DYNAMIC_DEPENDENCY'); | ||
_assert.default.deepEqual(err.params, ['ENV']); | ||
} | ||
})); | ||
}); | ||
describe('inject', () => { | ||
it('should work with no dependencies', done => { | ||
it('should work with no dependencies', | ||
/*#__PURE__*/ | ||
_asyncToGenerator(function* () { | ||
$.constant('ENV', ENV); | ||
$.constant('time', time); | ||
$.provider('hash', (0, _index.inject)(['ENV'], hashProvider)); | ||
$.run(['time', 'hash', '$injector']).then(dependencies => { | ||
_assert.default.deepEqual(Object.keys(dependencies), ['time', 'hash', '$injector']); | ||
const dependencies = yield $.run(['time', 'hash', '$injector']); | ||
return dependencies.$injector([]).then(injectDependencies => { | ||
_assert.default.deepEqual(Object.keys(injectDependencies), []); | ||
_assert.default.deepEqual(Object.keys(dependencies), ['time', 'hash', '$injector']); | ||
_assert.default.deepEqual(injectDependencies, {}); | ||
}); | ||
}).then(() => done()).catch(done); | ||
}); | ||
it('should work with same dependencies then the running silo', done => { | ||
const injectDependencies = yield dependencies.$injector([]); | ||
_assert.default.deepEqual(Object.keys(injectDependencies), []); | ||
_assert.default.deepEqual(injectDependencies, {}); | ||
})); | ||
it('should work with same dependencies then the running silo', | ||
/*#__PURE__*/ | ||
_asyncToGenerator(function* () { | ||
$.constant('ENV', ENV); | ||
$.constant('time', time); | ||
$.provider('hash', (0, _index.inject)(['ENV'], hashProvider)); | ||
$.run(['time', 'hash', '$injector']).then(dependencies => { | ||
_assert.default.deepEqual(Object.keys(dependencies), ['time', 'hash', '$injector']); | ||
const dependencies = yield $.run(['time', 'hash', '$injector']); | ||
return dependencies.$injector(['time', 'hash']).then(injectDependencies => { | ||
_assert.default.deepEqual(Object.keys(injectDependencies), ['time', 'hash']); | ||
_assert.default.deepEqual(Object.keys(dependencies), ['time', 'hash', '$injector']); | ||
_assert.default.deepEqual(injectDependencies, { | ||
hash: { | ||
ENV | ||
}, | ||
time | ||
}); | ||
}); | ||
}).then(() => done()).catch(done); | ||
}); | ||
it('should fail with non instanciated dependencies', done => { | ||
const injectDependencies = yield dependencies.$injector(['time', 'hash']); | ||
_assert.default.deepEqual(Object.keys(injectDependencies), ['time', 'hash']); | ||
_assert.default.deepEqual(injectDependencies, { | ||
hash: { | ||
ENV | ||
}, | ||
time | ||
}); | ||
})); | ||
it('should fail with non instanciated dependencies', | ||
/*#__PURE__*/ | ||
_asyncToGenerator(function* () { | ||
$.constant('ENV', ENV); | ||
$.constant('time', time); | ||
$.provider('hash', (0, _index.inject)(['ENV'], hashProvider)); | ||
$.run(['time', '$injector']).then(dependencies => { | ||
_assert.default.deepEqual(Object.keys(dependencies), ['time', '$injector']); | ||
const dependencies = yield $.run(['time', '$injector']); | ||
return dependencies.$injector(['time', 'hash']).catch(err => { | ||
_assert.default.equal(err.code, 'E_BAD_INJECTION'); | ||
}); | ||
}).then(() => done()).catch(done); | ||
}); | ||
it('should create dependencies when not declared as singletons', done => { | ||
_assert.default.deepEqual(Object.keys(dependencies), ['time', '$injector']); | ||
try { | ||
yield dependencies.$injector(['time', 'hash']); | ||
throw new _yerror.default('E_UNEXPECTED_SUCCESS'); | ||
} catch (err) { | ||
_assert.default.equal(err.code, 'E_BAD_INJECTION'); | ||
} | ||
})); | ||
it('should create dependencies when not declared as singletons', | ||
/*#__PURE__*/ | ||
_asyncToGenerator(function* () { | ||
$.constant('ENV', ENV); | ||
$.provider('hash', (0, _index.inject)(['ENV'], hashProvider)); | ||
Promise.all([$.run(['hash']), $.run(['hash'])]).then(([{ | ||
const [{ | ||
hash | ||
}, { | ||
hash: sameHash | ||
}]) => { | ||
_assert.default.notEqual(hash, sameHash); | ||
}] = yield Promise.all([$.run(['hash']), $.run(['hash'])]); | ||
return $.run(['hash']).then(({ | ||
hash: yaSameHash | ||
}) => { | ||
_assert.default.notEqual(hash, yaSameHash); | ||
}); | ||
}).then(() => done()).catch(done); | ||
}); | ||
it('should reuse dependencies when declared as singletons', done => { | ||
_assert.default.notEqual(hash, sameHash); | ||
const { | ||
hash: yaSameHash | ||
} = yield $.run(['hash']); | ||
_assert.default.notEqual(hash, yaSameHash); | ||
})); | ||
it('should reuse dependencies when declared as singletons', | ||
/*#__PURE__*/ | ||
_asyncToGenerator(function* () { | ||
$.constant('ENV', ENV); | ||
@@ -454,3 +849,3 @@ $.provider('hash', (0, _index.inject)(['ENV'], hashProvider), { | ||
}, hashProvider))); | ||
Promise.all([$.run(['hash']), $.run(['hash']), $.run(['hash2']), $.run(['hash2'])]).then(([{ | ||
const [{ | ||
hash, | ||
@@ -461,24 +856,28 @@ hash2 | ||
hash2: sameHash2 | ||
}]) => { | ||
_assert.default.equal(hash, sameHash); | ||
}] = yield Promise.all([$.run(['hash']), $.run(['hash']), $.run(['hash2']), $.run(['hash2'])]); | ||
_assert.default.equal(hash2, sameHash2); | ||
_assert.default.equal(hash, sameHash); | ||
return $.run(['hash']).then(({ | ||
hash: yaSameHash | ||
}) => { | ||
_assert.default.equal(hash, yaSameHash); | ||
}); | ||
}).then(() => done()).catch(done); | ||
}); | ||
_assert.default.equal(hash2, sameHash2); | ||
const { | ||
hash: yaSameHash | ||
} = yield $.run(['hash']); | ||
_assert.default.equal(hash, yaSameHash); | ||
})); | ||
}); | ||
describe('$destroy', () => { | ||
it('should work even with one silo and no dependencies', done => { | ||
$.run(['$destroy']).then(dependencies => { | ||
_assert.default.equal(typeof dependencies.$destroy, 'function'); | ||
it('should work even with one silo and no dependencies', | ||
/*#__PURE__*/ | ||
_asyncToGenerator(function* () { | ||
const dependencies = yield $.run(['$destroy']); | ||
return dependencies.$destroy(); | ||
}).then(() => done()).catch(done); | ||
}); | ||
it('should work with several silos and dependencies', done => { | ||
_assert.default.equal(typeof dependencies.$destroy, 'function'); | ||
yield dependencies.$destroy(); | ||
})); | ||
it('should work with several silos and dependencies', | ||
/*#__PURE__*/ | ||
_asyncToGenerator(function* () { | ||
$.constant('ENV', ENV); | ||
@@ -491,9 +890,11 @@ $.constant('time', time); | ||
$.provider('hash2', (0, _index.inject)(['ENV'], hashProvider)); | ||
Promise.all([$.run(['$destroy']), $.run(['ENV', 'hash', 'hash1', 'time']), $.run(['ENV', 'hash', 'hash2'])]).then(([dependencies]) => { | ||
_assert.default.equal(typeof dependencies.$destroy, 'function'); | ||
const [dependencies] = yield Promise.all([$.run(['$destroy']), $.run(['ENV', 'hash', 'hash1', 'time']), $.run(['ENV', 'hash', 'hash2'])]); | ||
return dependencies.$destroy(); | ||
}).then(() => done()).catch(done); | ||
}); | ||
it('should work when trigered from several silos simultaneously', done => { | ||
_assert.default.equal(typeof dependencies.$destroy, 'function'); | ||
yield dependencies.$destroy(); | ||
})); | ||
it('should work when trigered from several silos simultaneously', | ||
/*#__PURE__*/ | ||
_asyncToGenerator(function* () { | ||
$.constant('ENV', ENV); | ||
@@ -504,5 +905,8 @@ $.constant('time', time); | ||
$.provider('hash2', (0, _index.inject)(['ENV'], hashProvider)); | ||
Promise.all([$.run(['$destroy']), $.run(['$destroy', 'ENV', 'hash', 'hash1', 'time']), $.run(['$destroy', 'ENV', 'hash', 'hash2'])]).then(dependenciesBuckets => Promise.all(dependenciesBuckets.map(dependencies => dependencies.$destroy()))).then(() => done()).catch(done); | ||
}); | ||
it('should work when a silo shutdown is in progress', done => { | ||
const dependenciesBuckets = yield Promise.all([$.run(['$destroy']), $.run(['$destroy', 'ENV', 'hash', 'hash1', 'time']), $.run(['$destroy', 'ENV', 'hash', 'hash2'])]); | ||
yield Promise.all(dependenciesBuckets.map(dependencies => dependencies.$destroy())); | ||
})); | ||
it('should work when a silo shutdown is in progress', | ||
/*#__PURE__*/ | ||
_asyncToGenerator(function* () { | ||
$.constant('ENV', ENV); | ||
@@ -513,5 +917,8 @@ $.constant('time', time); | ||
$.provider('hash2', (0, _index.inject)(['ENV'], hashProvider)); | ||
Promise.all([$.run(['$destroy']), $.run(['$dispose', 'ENV', 'hash', 'hash1', 'time']), $.run(['ENV', 'hash', 'hash2'])]).then(([dependencies1, dependencies2]) => Promise.all([dependencies2.$dispose(), dependencies1.$destroy()])).then(() => done()).catch(done); | ||
}); | ||
it('should disallow new runs', done => { | ||
const [dependencies1, dependencies2] = yield Promise.all([$.run(['$destroy']), $.run(['$dispose', 'ENV', 'hash', 'hash1', 'time']), $.run(['ENV', 'hash', 'hash2'])]); | ||
yield Promise.all([dependencies2.$dispose(), dependencies1.$destroy()]); | ||
})); | ||
it('should disallow new runs', | ||
/*#__PURE__*/ | ||
_asyncToGenerator(function* () { | ||
$.constant('ENV', ENV); | ||
@@ -521,43 +928,52 @@ $.constant('time', time); | ||
$.provider('hash1', (0, _index.inject)(['ENV'], hashProvider)); | ||
$.run(['$destroy']).then(dependencies => { | ||
_assert.default.equal(typeof dependencies.$destroy, 'function'); | ||
const dependencies = yield $.run(['$destroy']); | ||
return dependencies.$destroy(); | ||
}).then(() => { | ||
_assert.default.throws(() => $.run(['ENV', 'hash', 'hash1']), err => { | ||
_assert.default.equal(err.code, 'E_INSTANCE_DESTROYED'); | ||
_assert.default.equal(typeof dependencies.$destroy, 'function'); | ||
return true; | ||
}); | ||
}).then(() => done()).catch(done); | ||
}); | ||
yield dependencies.$destroy(); | ||
try { | ||
yield $.run(['ENV', 'hash', 'hash1']); | ||
throw new _yerror.default('E_UNEXPECTED_SUCCES'); | ||
} catch (err) { | ||
_assert.default.equal(err.code, 'E_INSTANCE_DESTROYED'); | ||
} | ||
})); | ||
}); | ||
describe('$dispose', () => { | ||
it('should work with no dependencies', done => { | ||
$.run(['$dispose']).then(dependencies => { | ||
_assert.default.equal(typeof dependencies.$dispose, 'function'); | ||
it('should work with no dependencies', | ||
/*#__PURE__*/ | ||
_asyncToGenerator(function* () { | ||
const dependencies = yield $.run(['$dispose']); | ||
return dependencies.$dispose(); | ||
}).then(() => done()).catch(done); | ||
}); | ||
it('should work with constant dependencies', done => { | ||
_assert.default.equal(typeof dependencies.$dispose, 'function'); | ||
return dependencies.$dispose(); | ||
})); | ||
it('should work with constant dependencies', | ||
/*#__PURE__*/ | ||
_asyncToGenerator(function* () { | ||
$.constant('ENV', ENV); | ||
$.constant('time', time); | ||
$.run(['time', 'ENV', '$dispose']).then(dependencies => { | ||
_assert.default.deepEqual(Object.keys(dependencies), ['time', 'ENV', '$dispose']); | ||
const dependencies = yield $.run(['time', 'ENV', '$dispose']); | ||
return dependencies.$dispose(); | ||
}).then(() => done()).catch(done); | ||
}); | ||
it('should work with simple dependencies', done => { | ||
_assert.default.deepEqual(Object.keys(dependencies), ['time', 'ENV', '$dispose']); | ||
yield dependencies.$dispose(); | ||
})); | ||
it('should work with simple dependencies', | ||
/*#__PURE__*/ | ||
_asyncToGenerator(function* () { | ||
$.constant('ENV', ENV); | ||
$.constant('time', time); | ||
$.provider('hash', (0, _index.inject)(['ENV'], hashProvider)); | ||
$.run(['time', 'hash', '$dispose']).then(dependencies => { | ||
_assert.default.deepEqual(Object.keys(dependencies), ['time', 'hash', '$dispose']); | ||
const dependencies = yield $.run(['time', 'hash', '$dispose']); | ||
return dependencies.$dispose(); | ||
}).then(() => done()).catch(done); | ||
}); | ||
it('should work with deeper dependencies', done => { | ||
_assert.default.deepEqual(Object.keys(dependencies), ['time', 'hash', '$dispose']); | ||
yield dependencies.$dispose(); | ||
})); | ||
it('should work with deeper dependencies', | ||
/*#__PURE__*/ | ||
_asyncToGenerator(function* () { | ||
let shutdownCallResolve; | ||
@@ -591,14 +1007,17 @@ let shutdownResolve; | ||
}))); | ||
$.run(['hash5', 'time', '$dispose', 'shutdownChecker']).then(dependencies => { | ||
_assert.default.deepEqual(Object.keys(dependencies), ['hash5', 'time', '$dispose', 'shutdownChecker']); | ||
const dependencies = yield $.run(['hash5', 'time', '$dispose', 'shutdownChecker']); | ||
shutdownCallPromise.then(() => { | ||
_assert.default.deepEqual(shutdownStub.args, [[]]); | ||
_assert.default.deepEqual(Object.keys(dependencies), ['hash5', 'time', '$dispose', 'shutdownChecker']); | ||
shutdownResolve(); | ||
}).catch(done); | ||
return dependencies.$dispose(); | ||
}).then(done).catch(done); | ||
}); | ||
it('should work with deeper multi used dependencies', done => { | ||
const finalPromise = shutdownCallPromise.then(() => { | ||
_assert.default.deepEqual(shutdownStub.args, [[]]); | ||
shutdownResolve(); | ||
}); | ||
yield dependencies.$dispose(); | ||
yield finalPromise; | ||
})); | ||
it('should work with deeper multi used dependencies', | ||
/*#__PURE__*/ | ||
_asyncToGenerator(function* () { | ||
let shutdownCallResolve; | ||
@@ -628,14 +1047,17 @@ let shutdownResolve; | ||
$.provider('hash2', (0, _index.inject)(['shutdownChecker'], hashProvider)); | ||
$.run(['hash1', 'hash2', '$dispose', 'shutdownChecker']).then(dependencies => { | ||
_assert.default.deepEqual(Object.keys(dependencies), ['hash1', 'hash2', '$dispose', 'shutdownChecker']); | ||
const dependencies = yield $.run(['hash1', 'hash2', '$dispose', 'shutdownChecker']); | ||
shutdownCallPromise.then(() => { | ||
_assert.default.deepEqual(shutdownStub.args, [[]]); | ||
_assert.default.deepEqual(Object.keys(dependencies), ['hash1', 'hash2', '$dispose', 'shutdownChecker']); | ||
shutdownResolve(); | ||
}).catch(done); | ||
return dependencies.$dispose(); | ||
}).then(() => done()).catch(done); | ||
}); | ||
it('should delay service shutdown to their deeper dependencies', done => { | ||
const finalPromise = shutdownCallPromise.then(() => { | ||
_assert.default.deepEqual(shutdownStub.args, [[]]); | ||
shutdownResolve(); | ||
}); | ||
yield dependencies.$dispose(); | ||
yield finalPromise; | ||
})); | ||
it('should delay service shutdown to their deeper dependencies', | ||
/*#__PURE__*/ | ||
_asyncToGenerator(function* () { | ||
const servicesShutdownCalls = _sinon.default.spy(() => Promise.resolve()); | ||
@@ -655,11 +1077,13 @@ | ||
}))); | ||
$.run(['hash2', '$dispose']).then(dependencies => { | ||
_assert.default.deepEqual(Object.keys(dependencies), ['hash2', '$dispose']); | ||
const dependencies = yield $.run(['hash2', '$dispose']); | ||
return dependencies.$dispose(); | ||
}).then(() => { | ||
_assert.default.deepEqual(servicesShutdownCalls.args, [['hash2'], ['hash1'], ['hash']]); | ||
}).then(() => done()).catch(done); | ||
}); | ||
it('should not shutdown singleton dependencies if used elsewhere', done => { | ||
_assert.default.deepEqual(Object.keys(dependencies), ['hash2', '$dispose']); | ||
yield dependencies.$dispose(); | ||
_assert.default.deepEqual(servicesShutdownCalls.args, [['hash2'], ['hash1'], ['hash']]); | ||
})); | ||
it('should not shutdown singleton dependencies if used elsewhere', | ||
/*#__PURE__*/ | ||
_asyncToGenerator(function* () { | ||
$.constant('ENV', ENV); | ||
@@ -670,16 +1094,17 @@ $.constant('time', time); | ||
}); | ||
$.run(['time', 'hash']).then(dependencies => { | ||
const { | ||
hash | ||
} = dependencies; | ||
return $.run(['time', 'hash', '$dispose']).then(dependencies => { | ||
_assert.default.equal(dependencies.hash, hash); | ||
const { | ||
hash | ||
} = yield $.run(['time', 'hash']); | ||
const dependencies = yield $.run(['time', 'hash', '$dispose']); | ||
return dependencies.$dispose().then(() => $.run(['time', 'hash']).then(dependencies => { | ||
_assert.default.equal(dependencies.hash, hash); | ||
})); | ||
}); | ||
}).then(() => done()).catch(done); | ||
}); | ||
it('should shutdown singleton dependencies if not used elsewhere', done => { | ||
_assert.default.equal(dependencies.hash, hash); | ||
yield dependencies.$dispose(); | ||
const newDependencies = yield $.run(['time', 'hash']); | ||
_assert.default.equal(newDependencies.hash, hash); | ||
})); | ||
it('should shutdown singleton dependencies if not used elsewhere', | ||
/*#__PURE__*/ | ||
_asyncToGenerator(function* () { | ||
$.constant('ENV', ENV); | ||
@@ -690,11 +1115,11 @@ $.constant('time', time); | ||
}); | ||
$.run(['time', 'hash', '$dispose']).then(dependencies => { | ||
const { | ||
hash | ||
} = dependencies; | ||
return dependencies.$dispose().then(() => $.run(['time', 'hash']).then(dependencies => { | ||
_assert.default.notEqual(dependencies.hash, hash); | ||
})); | ||
}).then(() => done()).catch(done); | ||
}); | ||
const { | ||
hash, | ||
$dispose | ||
} = yield $.run(['time', 'hash', '$dispose']); | ||
yield $dispose(); | ||
const dependencies = yield $.run(['time', 'hash']); | ||
_assert.default.notEqual(dependencies.hash, hash); | ||
})); | ||
}); | ||
@@ -701,0 +1126,0 @@ describe('toMermaidGraph', () => { |
@@ -82,3 +82,14 @@ "use strict"; | ||
function wrapInitializer(wrapper, baseInitializer) { | ||
return reuseSpecialProps(baseInitializer, services => baseInitializer(services).then(wrapper.bind(null, services))); | ||
return reuseSpecialProps(baseInitializer, | ||
/*#__PURE__*/ | ||
function () { | ||
var _ref = _asyncToGenerator(function* (services) { | ||
const baseInstance = yield baseInitializer(services); | ||
return wrapper(services, baseInstance); | ||
}); | ||
return function (_x) { | ||
return _ref.apply(this, arguments); | ||
}; | ||
}()); | ||
} | ||
@@ -317,3 +328,3 @@ /** | ||
function () { | ||
var _ref = _asyncToGenerator(function* (...args) { | ||
var _ref2 = _asyncToGenerator(function* (...args) { | ||
return handlerFunction.bind(null, ...args); | ||
@@ -323,3 +334,3 @@ }); | ||
return function () { | ||
return _ref.apply(this, arguments); | ||
return _ref2.apply(this, arguments); | ||
}; | ||
@@ -326,0 +337,0 @@ }()); |
@@ -11,2 +11,6 @@ "use strict"; | ||
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } } | ||
function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; } | ||
function aProvider() {} | ||
@@ -82,4 +86,6 @@ | ||
}); | ||
describe('wrapInitializer', done => { | ||
it('should work', () => { | ||
describe('wrapInitializer', () => { | ||
it('should work', | ||
/*#__PURE__*/ | ||
_asyncToGenerator(function* () { | ||
function baseInitializer() { | ||
@@ -107,10 +113,10 @@ return Promise.resolve(() => 'test'); | ||
}, baseInitializer); | ||
newInitializer({ | ||
const service = yield newInitializer({ | ||
log | ||
}).then(service => { | ||
_assert.default.equal(service(), 'test-wrapped'); | ||
}); | ||
_assert.default.deepEqual(log.args, [['Wrapping...']]); | ||
}).then(done).catch(done); | ||
}); | ||
_assert.default.equal(service(), 'test-wrapped'); | ||
_assert.default.deepEqual(log.args, [['Wrapping...']]); | ||
})); | ||
}); | ||
@@ -250,3 +256,5 @@ describe('inject', () => { | ||
describe('handler', () => { | ||
it('should work', () => { | ||
it('should work', | ||
/*#__PURE__*/ | ||
_asyncToGenerator(function* () { | ||
const injectedServices = ['kikooo', 'lol']; | ||
@@ -263,6 +271,9 @@ const services = { | ||
return theInitializer(services).then(theHandler => theHandler('test')).then(result => _assert.default.deepEqual(result, { | ||
const theHandler = yield theInitializer(services); | ||
const result = yield theHandler('test'); | ||
_assert.default.deepEqual(result, { | ||
deps: services, | ||
args: ['test'] | ||
})); | ||
}); | ||
@@ -275,3 +286,3 @@ function sampleHandler(deps, ...args) { | ||
} | ||
}); | ||
})); | ||
it('should fail for anonymous functions', () => { | ||
@@ -278,0 +289,0 @@ _assert.default.throws(() => { |
{ | ||
"name": "knifecycle", | ||
"version": "2.5.4", | ||
"version": "2.6.0", | ||
"description": "Manage your NodeJS processes's lifecycle.", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
438
README.md
@@ -73,2 +73,4 @@ [//]: # ( ) | ||
embedding Knifecycle in your builds. | ||
- optionally autoload services dependencies with custom | ||
logic | ||
@@ -80,38 +82,38 @@ ## Usage | ||
Let's say we are building a web service. First, we need to | ||
Let's say we are building a CLI script. He is how we would | ||
proceed with Knifecycle: | ||
First, we need to | ||
handle a configuration file so we are creating an | ||
initializer to instanciate our `CONFIG` service: | ||
```js | ||
// services/config.js | ||
// bin.js | ||
import fs from 'fs'; | ||
import { initializer } from 'knifecycle'; | ||
import Knifecycle, { initializer, inject, name } from 'knifecycle'; | ||
// We are using the `initializer` decorator to | ||
// declare our service initializer specificities | ||
// Note that the initializer` decorator is pure | ||
// so it just adds static informations and do not | ||
// register the initializer to the provider yet. | ||
export const initConfig = initializer({ | ||
// we have to give our final service a name | ||
// for further use in other services injections | ||
name: 'CONFIG', | ||
// we will need an `ENV` variable in the initializer | ||
// so adding it in the injected dependencies. | ||
inject: ['ENV'], | ||
// our initializer is simple so we use the `service` | ||
// type for the initializer which just indicate that | ||
// the initializer will return a promise of the actual | ||
// service | ||
type: 'service', | ||
// We don't want to read the config file everytime we | ||
// inject it so declaring it as a singleton | ||
options: { singleton: true }, | ||
// Here is the actual initializer implementation, you | ||
// can notice that it expect the `ENV` dependency to | ||
// be set as a property of an object in first argument. | ||
}, async ({ ENV }) => { | ||
return new Promise((resolve, reject) { | ||
fs.readFile(ENV.CONFIG_PATH, function(err, data) { | ||
if(err) { | ||
return reject(err); | ||
// First of all we create a new Knifecycle instance | ||
const $ = new Knifecycle(); | ||
// Some of our code with rely on the process environment | ||
// let's inject it as a constant instead of directly | ||
// pickking en vars in `process.env` to make our code | ||
// easily testable | ||
$.constant('ENV', process.env); | ||
// Let's do so for CLI args with another constant | ||
// in real world apps we would have create a service | ||
// that would parse args in a complexer way | ||
$.constant('ARGS', process.argv); | ||
// We want our CLI tool to rely on some configuration | ||
// Let's build an injectable service initializer that | ||
// reads environment variables via an injected but | ||
// optional `ENV` object | ||
async function initConfig({ ENV = { CONFIG_PATH: '.' } }) { | ||
return new Promise((resolve, reject) => { | ||
fs.readFile(ENV.CONFIG_PATH, 'utf-8', (err, data) => { | ||
if (err) { | ||
reject(err); | ||
return; | ||
} | ||
@@ -123,35 +125,63 @@ try { | ||
} | ||
}, 'utf-8'); | ||
}); | ||
``` | ||
}); | ||
}); | ||
} | ||
Our service also uses a database so let's write an | ||
initializer for it: | ||
```js | ||
// services/db.js | ||
import { initializer } from 'knifecycle'; | ||
// We are using the `initializer` decorator to | ||
// declare our service initializer specificities | ||
// and register it with our Knifecycle instance | ||
$.register( | ||
initializer( | ||
{ | ||
// we have to give our final service a name | ||
// for further use in other services injections | ||
name: 'CONFIG', | ||
// we will need an `ENV` variable in the initializer | ||
// so adding it in the injected dependencies. The `?` | ||
// sign tells Knifecycle that the ENV dependency | ||
// is optional | ||
inject: ['?ENV'], | ||
// our initializer is simple so we use the `service` | ||
// type for the initializer which just indicate that | ||
// the initializer will return a promise of the actual | ||
// service | ||
type: 'service', | ||
// We don't want to read the config file everytime we | ||
// inject it so declaring it as a singleton | ||
options: { singleton: true }, | ||
}, | ||
initConfig, | ||
), | ||
); | ||
const initDB = initializer({ | ||
name: 'db', | ||
// Here we are injecting the previous `CONFIG` service | ||
// plus an optional one. If it does not exist then it | ||
// will silently fail and the service will be undefined. | ||
inject: ['CONFIG', '?log'], | ||
// The initializer type is slightly different. Indeed, | ||
// we need to manage the database connection errors | ||
// and wait for it to flush before shutting down the | ||
// process. | ||
// A service provider returns a promise of a provider | ||
// descriptor exposing: | ||
// - a mandatory `service` property containing the | ||
// actual service; | ||
// - an optional `dispose` function allowing to | ||
// gracefully close the service; | ||
// - an optional `fatalErrorPromise` property to | ||
// handle the service unrecoverable failure. | ||
type: 'provider', | ||
options: { singleton: true }, | ||
}, async ({ CONFIG, log }) { | ||
const db = await MongoClient.connect(CONFIG.DB_URI); | ||
let fatalErrorPromise = new Promise((resolve, reject) { | ||
// Our CLI also uses a database so let's write an | ||
// initializer for it: | ||
const initDB = initializer( | ||
{ | ||
name: 'db', | ||
// Here we are injecting the previous `CONFIG` service | ||
// as required so that our DB cannot be connected without | ||
// having a proper config. | ||
inject: ['CONFIG', 'DB_URI', '?log'], | ||
// The initializer type is slightly different. Indeed, | ||
// we need to manage the database connection errors | ||
// and wait for it to flush before shutting down the | ||
// process. | ||
// A service provider returns a promise of a provider | ||
// descriptor exposing: | ||
// - a mandatory `service` property containing the | ||
// actual service; | ||
// - an optional `dispose` function allowing to | ||
// gracefully close the service; | ||
// - an optional `fatalErrorPromise` property to | ||
// handle the service unrecoverable failure. | ||
type: 'provider', | ||
options: { singleton: true }, | ||
}, | ||
async ({ CONFIG, DB_URI, log }) => { | ||
const db = await MongoClient.connect( | ||
DB_URI, | ||
CONFIG.databaseOptions, | ||
); | ||
let fatalErrorPromise = new Promise((resolve, reject) => { | ||
db.once('error', reject); | ||
@@ -168,151 +198,127 @@ }); | ||
}; | ||
}); | ||
``` | ||
}, | ||
); | ||
We need a last initializer for the HTTP server itself: | ||
```js | ||
// services/server.js | ||
import { initializer } from 'knifecycle'; | ||
import express from 'express'; | ||
// Here we are registering our initializer apart to | ||
// be able to reuse it, we also declare the required | ||
// DB_URI constant it needs | ||
$.constant('DB_URI', 'posgresql://xxxx').register(initDB); | ||
const initDB = initializer({ | ||
name: 'server', | ||
inject: ['ENV', 'CONFIG', '?log'], | ||
options: { singleton: true }, | ||
}, async ({ ENV, CONFIG, log }) => { | ||
const app = express(); | ||
const server = new Promise((resolve, reject) => { | ||
const port = ENV.PORT || CONFIG.PORT; | ||
const server = app.listen(port, () => { | ||
log && log('info', `server listening on port ${port}!`); | ||
resolve(server); | ||
}); | ||
}); | ||
let fatalErrorPromise = new Promise((resolve, reject) { | ||
app.once('error', reject); | ||
server.once('error', reject); | ||
}); | ||
function dispose() { | ||
return new Promise((resolve, reject) => { | ||
server.close((err) => { | ||
if(err) { | ||
reject(err); | ||
return; | ||
} | ||
resolve(); | ||
}) | ||
}); | ||
} | ||
return { | ||
service: app, | ||
dispose, | ||
fatalErrorPromise, | ||
}; | ||
}); | ||
``` | ||
Great! We are ready to make it work altogether: | ||
```js | ||
import { getInstance } from 'knifecycle'; | ||
import initConfig from 'services/config'; | ||
import initDB from 'services/db'; | ||
import initServer from 'services/server'; | ||
// We need only one Knifecycle instance so using | ||
// a the singleton API | ||
getInstance() | ||
// Registering our initializers | ||
.register(initConfig) | ||
.register(initServer) | ||
.register(initDB) | ||
// Let's say we need to have another `db` | ||
// service pointing to another db server. | ||
.register( | ||
// Say we need to use two different DB server | ||
// We can reuse our initializer by tweaking | ||
// some of its properties | ||
$.constant('DB_URI2', 'posgresql://yyyy'); | ||
$.register( | ||
// First we remap the injected dependencies. It will | ||
// take the `DB2_CONFIG` service and inject it as | ||
// `CONFIG` | ||
inject(['DB2_CONFIG>CONFIG', '?log'], | ||
// Then we override its name | ||
name('db2', initDB) | ||
) | ||
) | ||
// Finally, we have to create the `DB2_CONFIG` service | ||
// on which the `db2` service now depends on | ||
.register(name('DB2_CONFIG', inject(['CONFIG'], async ({ CONFIG }) => { | ||
// Let's just pick up the `db2` uri in the `CONFIG` | ||
// service | ||
return { | ||
DB_URI: CONFIG.DB2_URI, | ||
}; | ||
}))) | ||
// Add the process environment as a simple constant | ||
.constant('ENV', process.env) | ||
// Add a function providing the current timestamp | ||
.constant('now', Date.now.bind(Date)) | ||
// Add a delay function | ||
.constant('delay', Promise.delay.bind(Promise)) | ||
// Add process lifetime utils | ||
.constant('waitSignal', function waitSignal(signal) { | ||
return new Promise((resolve, reject) => { | ||
process.once(signal, resolve.bind(null, signal)); | ||
}); | ||
}) | ||
.constant('exit', process.exit.bind(exit)) | ||
// Setting a route to serve the current timestamp. | ||
.register(name('timeRoute', | ||
// take the `DB_URI2` constant and inject it as | ||
// `DB_URI` | ||
inject( | ||
['server', 'now', '?log'], | ||
async ({ server: app, now, log }) => { | ||
app.get('/time', (req, res, next) => { | ||
const curTime = now(); | ||
['CONFIG', 'DB_URI2>DB_URI', '?log'], | ||
// Then we override its name to make it | ||
// available as a different service | ||
name('db2', initDB), | ||
), | ||
); | ||
log && log('info', 'Sending the current time:', curTime); | ||
res.status(200).send(curTime); | ||
}); | ||
} | ||
) | ||
)) | ||
// A lot of NodeJS functions have some side effects | ||
// declaring them as constants allows you to easily | ||
// mock/monitor/patch it. The `common-services` NPM | ||
// module contains a few useful ones | ||
$.constant('now', Date.now.bind(Date)) | ||
.constant('log', console.log.bind(console)) | ||
.constant('exit', process.exit.bind(process)); | ||
// At this point, nothing is running. To instanciate | ||
// Finally, let's declare an `$autoload` service | ||
// to allow us to load only the initializers needed | ||
// to run the given commands | ||
$.register( | ||
initializer( | ||
{ | ||
name: '$autoload', | ||
type: 'service', | ||
inject: ['CONFIG', 'ARGS'], | ||
}, | ||
async ({ CONFIG, ARGS }) => async serviceName => { | ||
if ('command' !== serviceName) { | ||
throw new Error(`${serviceName} not supported!`); | ||
} | ||
try { | ||
const path = CONFIG.commands + '/' + ARGS[2]; | ||
return { | ||
path, | ||
initializer: require(path).default, | ||
}; | ||
} catch (err) { | ||
throw new Error(`Cannot load ${serviceName}: ${ARGS[2]}!`); | ||
} | ||
}, | ||
), | ||
); | ||
// At this point, nothing is running. To instanciate the | ||
// services, we have to create an execution silo using | ||
// them. Note that we required the `$destroy` service | ||
// implicitly created by `knifecycle` | ||
.run(['server', 'timeRoute', 'waitSignal', 'exit', '$destroy']) | ||
// Note that despite we injected them, we do not take | ||
// back the `server` and `timeRoute` services. We only | ||
// need them to get up and running but do not need to | ||
// operate on them | ||
.then(({ waitSignal, exit, $destroy }) { | ||
// We want to exit gracefully when a SIG_TERM/INT | ||
// signal is received | ||
Promise.any([ | ||
waitSignal('SIGINT'), | ||
waitSignal('SIGTERM'), | ||
]) | ||
// The `$destroy` service will disable all silos | ||
// progressively and then the services they rely | ||
// on to finally resolve the returned promise | ||
// once done | ||
.then($destroy) | ||
.then(() => { | ||
// graceful shutdown was successful let's exit | ||
// in peace | ||
exit(0); | ||
$.run(['command', '$destroy', 'exit', 'log']) | ||
// Here, command contains the initializer eventually | ||
// found by automatically loading a NodeJS module | ||
// in the above `$autoload service`. The db connections | ||
// we only be instanciated if that command needs it | ||
.then(async ({ command, $destroy, exit, log }) => { | ||
try { | ||
command(); | ||
log('It worked!'); | ||
} catch (err) { | ||
log('It failed!', err); | ||
} finally { | ||
// Here we ensure every db connections are closed | ||
// properly | ||
await $destroy().catch(err => { | ||
console.error('Could not exit gracefully:', err); | ||
exit(1); | ||
}); | ||
} | ||
}) | ||
.catch((err) => { | ||
console.error('Could not exit gracefully:', err); | ||
exit(1); | ||
.catch(err => { | ||
console.error('Could not launch the app:', err); | ||
process.exit(1); | ||
}); | ||
``` | ||
Running the following should make the magic happen: | ||
```sh | ||
cat "{ commands: './commands'}" > config.json | ||
DEBUG=knifecycle CONFIG_PATH=./config.json node -r @babel/register bin.js mycommand test | ||
// Prints: Could not launch the app: Error: Cannot load command: mycommand! | ||
// (...stack trace) | ||
``` | ||
Or at least, we still have to create commands, let's create the `mycommand` one: | ||
```js | ||
// commands/mycommand.js | ||
import { initializer } from './dist'; | ||
}) | ||
.catch((err) => { | ||
console.error('Could not launch the app:', err); | ||
process.exit(1); | ||
}); | ||
// A simple command that prints the given args | ||
export default initializer( | ||
{ | ||
name: 'command', | ||
type: 'service', | ||
// Here we could have injected whatever we declared | ||
// in the previous file: db, now, exit... | ||
inject: ['ARGS', 'log'], | ||
}, | ||
async ({ ARGS, log }) => { | ||
return () => log('Command args:', ARGS.slice(2)); | ||
}, | ||
); | ||
``` | ||
So now, it works: | ||
```sh | ||
DEBUG=knifecycle CONFIG_PATH=./config.json node -r @babel/register bin.js mycommand test | ||
// Prints: Command args: [ 'mycommand', 'test' ] | ||
// It worked! | ||
``` | ||
This is a very simple example but you can find a complexer CLI usage | ||
with (`metapak`)[https://github.com/nfroidure/metapak/blob/master/bin/metapak.js]. | ||
## Debugging | ||
@@ -325,2 +331,4 @@ | ||
``` | ||
The output is very verbose but lead to a deep understanding of | ||
mechanisms that take place under the hood. | ||
@@ -415,5 +423,5 @@ ## Plans | ||
* [.run(dependenciesDeclarations)](#Knifecycle+run) ⇒ <code>Promise</code> | ||
* [._getServiceDescriptor(siloContext, injectOnly, serviceName, serviceProvider)](#Knifecycle+_getServiceDescriptor) ⇒ <code>Promise</code> | ||
* [._initializeServiceDescriptor(siloContext, serviceName, serviceProvider)](#Knifecycle+_initializeServiceDescriptor) ⇒ <code>Promise</code> | ||
* [._initializeDependencies(siloContext, serviceName, servicesDeclarations, injectOnly)](#Knifecycle+_initializeDependencies) ⇒ <code>Promise</code> | ||
* [._getServiceDescriptor(siloContext, serviceName, options, serviceProvider)](#Knifecycle+_getServiceDescriptor) ⇒ <code>Promise</code> | ||
* [._initializeServiceDescriptor(siloContext, serviceName, options)](#Knifecycle+_initializeServiceDescriptor) ⇒ <code>Promise</code> | ||
* [._initializeDependencies(siloContext, serviceName, servicesDeclarations, options)](#Knifecycle+_initializeDependencies) ⇒ <code>Promise</code> | ||
* _static_ | ||
@@ -596,3 +604,3 @@ * [.getInstance()](#Knifecycle.getInstance) ⇒ [<code>Knifecycle</code>](#Knifecycle) | ||
### knifecycle._getServiceDescriptor(siloContext, injectOnly, serviceName, serviceProvider) ⇒ <code>Promise</code> | ||
### knifecycle._getServiceDescriptor(siloContext, serviceName, options, serviceProvider) ⇒ <code>Promise</code> | ||
Initialize or return a service descriptor | ||
@@ -606,4 +614,6 @@ | ||
| siloContext | <code>Object</code> | Current execution silo context | | ||
| injectOnly | <code>Boolean</code> | Flag indicating if existing services only should be used | | ||
| serviceName | <code>String</code> | Service name. | | ||
| options | <code>Object</code> | Options for service retrieval | | ||
| options.injectOnly | <code>Boolean</code> | Flag indicating if existing services only should be used | | ||
| options.autoloading | <code>Boolean</code> | Flag to indicating $autoload dependencies on the fly loading | | ||
| serviceProvider | <code>String</code> | Service provider. | | ||
@@ -613,4 +623,4 @@ | ||
### knifecycle._initializeServiceDescriptor(siloContext, serviceName, serviceProvider) ⇒ <code>Promise</code> | ||
Initialize a service | ||
### knifecycle._initializeServiceDescriptor(siloContext, serviceName, options) ⇒ <code>Promise</code> | ||
Initialize a service descriptor | ||
@@ -624,7 +634,9 @@ **Kind**: instance method of [<code>Knifecycle</code>](#Knifecycle) | ||
| serviceName | <code>String</code> | Service name. | | ||
| serviceProvider | <code>String</code> | Service provider. | | ||
| options | <code>Object</code> | Options for service retrieval | | ||
| options.injectOnly | <code>Boolean</code> | Flag indicating if existing services only should be used | | ||
| options.autoloading | <code>Boolean</code> | Flag to indicating $autoload dependendencies on the fly loading. | | ||
<a name="Knifecycle+_initializeDependencies"></a> | ||
### knifecycle._initializeDependencies(siloContext, serviceName, servicesDeclarations, injectOnly) ⇒ <code>Promise</code> | ||
### knifecycle._initializeDependencies(siloContext, serviceName, servicesDeclarations, options) ⇒ <code>Promise</code> | ||
Initialize a service dependencies | ||
@@ -635,8 +647,10 @@ | ||
| Param | Type | Default | Description | | ||
| --- | --- | --- | --- | | ||
| siloContext | <code>Object</code> | | Current execution silo siloContext | | ||
| serviceName | <code>String</code> | | Service name. | | ||
| servicesDeclarations | <code>String</code> | | Dependencies declarations. | | ||
| injectOnly | <code>Boolean</code> | <code>false</code> | Flag indicating if existing services only should be used | | ||
| Param | Type | Description | | ||
| --- | --- | --- | | ||
| siloContext | <code>Object</code> | Current execution silo siloContext | | ||
| serviceName | <code>String</code> | Service name. | | ||
| servicesDeclarations | <code>String</code> | Dependencies declarations. | | ||
| options | <code>Object</code> | Options for service retrieval | | ||
| options.injectOnly | <code>Boolean</code> | Flag indicating if existing services only should be used | | ||
| options.autoloading | <code>Boolean</code> | Flag to indicating $autoload dependendencies on the fly loading. | | ||
@@ -643,0 +657,0 @@ <a name="Knifecycle.getInstance"></a> |
179
src/build.js
@@ -40,59 +40,59 @@ import { SPECIAL_PROPS, parseDependencyDeclaration } from './util'; | ||
*/ | ||
function buildInitializer(constants, loader, dependencies) { | ||
return Promise.all( | ||
async function buildInitializer(constants, loader, dependencies) { | ||
const dependencyTrees = await Promise.all( | ||
dependencies.map(dependency => | ||
buildDependencyTree(constants, loader, dependency), | ||
), | ||
).then(dependencyTrees => { | ||
const dependenciesHash = buildDependenciesHash( | ||
dependencyTrees.filter(identity), | ||
); | ||
const batches = buildInitializationSequence({ | ||
__name: 'main', | ||
__childNodes: dependencyTrees.filter(identity), | ||
}); | ||
batches.pop(); | ||
); | ||
const dependenciesHash = buildDependenciesHash( | ||
dependencyTrees.filter(identity), | ||
); | ||
const batches = buildInitializationSequence({ | ||
__name: 'main', | ||
__childNodes: dependencyTrees.filter(identity), | ||
}); | ||
batches.pop(); | ||
return `${batches | ||
.map( | ||
(batch, index) => ` | ||
return `${batches | ||
.map( | ||
(batch, index) => ` | ||
// Definition batch #${index}${batch | ||
.map(name => { | ||
if (!dependenciesHash[name].__initializer) { | ||
return ` | ||
.map(name => { | ||
if (!dependenciesHash[name].__initializer) { | ||
return ` | ||
const ${name} = ${JSON.stringify(constants[name], null, 2)};`; | ||
} | ||
} | ||
return ` | ||
return ` | ||
import ${dependenciesHash[name].__initializerName} from '${ | ||
dependenciesHash[name].__path | ||
}';`; | ||
}) | ||
.join('')}`, | ||
) | ||
.join('\n')} | ||
dependenciesHash[name].__path | ||
}';`; | ||
}) | ||
.join('')}`, | ||
) | ||
.join('\n')} | ||
export async function initialize(services = {}) {${batches | ||
.map( | ||
(batch, index) => ` | ||
.map( | ||
(batch, index) => ` | ||
// Initialization batch #${index} | ||
const batch${index} = {${batch | ||
.map(name => { | ||
if (!dependenciesHash[name].__initializer) { | ||
return ` | ||
.map(name => { | ||
if (!dependenciesHash[name].__initializer) { | ||
return ` | ||
${name}: Promise.resolve(${name}),`; | ||
} | ||
return ` | ||
} | ||
return ` | ||
${name}: ${dependenciesHash[name].__initializerName}({${ | ||
dependenciesHash[name].__inject | ||
? `${dependenciesHash[name].__inject | ||
.map(parseDependencyDeclaration) | ||
.map( | ||
({ serviceName, mappedName }) => | ||
` | ||
dependenciesHash[name].__inject | ||
? `${dependenciesHash[name].__inject | ||
.map(parseDependencyDeclaration) | ||
.map( | ||
({ serviceName, mappedName }) => | ||
` | ||
${serviceName}: services['${mappedName}'],`, | ||
) | ||
.join('')}` | ||
: '' | ||
} | ||
) | ||
.join('')}` | ||
: '' | ||
} | ||
})${ | ||
@@ -103,4 +103,4 @@ 'provider' === dependenciesHash[name].__type | ||
},`; | ||
}) | ||
.join('')} | ||
}) | ||
.join('')} | ||
}; | ||
@@ -113,10 +113,10 @@ | ||
${batch | ||
.map(name => { | ||
return ` | ||
.map(name => { | ||
return ` | ||
services['${name}'] = await batch${index}['${name}'];`; | ||
}) | ||
.join('')} | ||
}) | ||
.join('')} | ||
`, | ||
) | ||
.join('')} | ||
) | ||
.join('')} | ||
return {${dependencies | ||
@@ -133,6 +133,5 @@ .map(parseDependencyDeclaration) | ||
`; | ||
}); | ||
} | ||
function buildDependencyTree(constants, loader, dependencyDeclaration) { | ||
async function buildDependencyTree(constants, loader, dependencyDeclaration) { | ||
const { mappedName, optional } = parseDependencyDeclaration( | ||
@@ -149,42 +148,40 @@ dependencyDeclaration, | ||
return loader(mappedName) | ||
.then(({ path, initializer }) => { | ||
const node = { | ||
__name: mappedName, | ||
__initializer: initializer, | ||
__inject: | ||
initializer && initializer[SPECIAL_PROPS.INJECT] | ||
? initializer[SPECIAL_PROPS.INJECT] | ||
: [], | ||
__type: | ||
initializer && initializer[SPECIAL_PROPS.TYPE] | ||
? initializer[SPECIAL_PROPS.TYPE] | ||
: 'provider', | ||
__initializerName: 'init' + upperCaseFirst(mappedName), | ||
__path: path, | ||
__childNodes: [], | ||
}; | ||
try { | ||
const { path, initializer } = await loader(mappedName); | ||
const node = { | ||
__name: mappedName, | ||
__initializer: initializer, | ||
__inject: | ||
initializer && initializer[SPECIAL_PROPS.INJECT] | ||
? initializer[SPECIAL_PROPS.INJECT] | ||
: [], | ||
__type: | ||
initializer && initializer[SPECIAL_PROPS.TYPE] | ||
? initializer[SPECIAL_PROPS.TYPE] | ||
: 'provider', | ||
__initializerName: 'init' + upperCaseFirst(mappedName), | ||
__path: path, | ||
__childNodes: [], | ||
}; | ||
return initializer[SPECIAL_PROPS.INJECT] && | ||
initializer[SPECIAL_PROPS.INJECT].length | ||
? Promise.all( | ||
initializer[SPECIAL_PROPS.INJECT].map(childDependencyDeclaration => | ||
buildDependencyTree( | ||
constants, | ||
loader, | ||
childDependencyDeclaration, | ||
), | ||
), | ||
).then(childNodes => { | ||
node.__childNodes = childNodes.filter(identity); | ||
return node; | ||
}) | ||
: node; | ||
}) | ||
.catch(err => { | ||
if (optional) { | ||
return null; | ||
} | ||
throw err; | ||
}); | ||
if ( | ||
initializer[SPECIAL_PROPS.INJECT] && | ||
initializer[SPECIAL_PROPS.INJECT].length | ||
) { | ||
const childNodes = await Promise.all( | ||
initializer[SPECIAL_PROPS.INJECT].map(childDependencyDeclaration => | ||
buildDependencyTree(constants, loader, childDependencyDeclaration), | ||
), | ||
); | ||
node.__childNodes = childNodes.filter(identity); | ||
return node; | ||
} else { | ||
return node; | ||
} | ||
} catch (err) { | ||
if (optional) { | ||
return null; | ||
} | ||
throw err; | ||
} | ||
} | ||
@@ -191,0 +188,0 @@ |
@@ -49,10 +49,10 @@ import assert from 'assert'; | ||
it('should build an initialization module', () => | ||
buildInitializer(mockedConstants, mockedLoader, [ | ||
it('should build an initialization module', async () => { | ||
const content = await buildInitializer(mockedConstants, mockedLoader, [ | ||
'dep1', | ||
'finalMappedDep>dep3', | ||
]).then(content => { | ||
assert.equal( | ||
content, | ||
` | ||
]); | ||
assert.equal( | ||
content, | ||
` | ||
// Definition batch #0 | ||
@@ -121,4 +121,4 @@ import initDep1 from './services/dep1'; | ||
`, | ||
); | ||
})); | ||
); | ||
}); | ||
}); |
347
src/index.js
@@ -19,2 +19,3 @@ /* eslint max-len: ["warn", { "ignoreComments": true }] */ | ||
const DESTROY = '$destroy'; | ||
const AUTOLOAD = '$autoload'; | ||
const INJECTOR = '$injector'; | ||
@@ -26,5 +27,8 @@ const SILO_CONTEXT = '$siloContext'; | ||
const E_BAD_ANALYZER_TYPE = 'E_BAD_ANALYZER_TYPE'; | ||
const E_BAD_INITIALIZER_TYPE = 'E_BAD_INITIALIZER_TYPE'; | ||
const E_BAD_AUTOLOADED_INITIALIZER = 'E_BAD_AUTOLOADED_INITIALIZER'; | ||
const E_AUTOLOADED_INITIALIZER_MISMATCH = 'E_AUTOLOADED_INITIALIZER_MISMATCH'; | ||
const E_UNMATCHED_DEPENDENCY = 'E_UNMATCHED_DEPENDENCY'; | ||
const E_CIRCULAR_DEPENDENCY = 'E_CIRCULAR_DEPENDENCY'; | ||
const E_BAD_INITIALIZER = 'E_BAD_INITIALIZER'; | ||
const E_ANONYMOUS_ANALYZER = 'E_ANONYMOUS_ANALYZER'; | ||
@@ -35,2 +39,5 @@ const E_BAD_SERVICE_PROVIDER = 'E_BAD_SERVICE_PROVIDER'; | ||
const E_CONSTANT_INJECTION = 'E_CONSTANT_INJECTION'; | ||
const E_INSTANCE_DESTROYED = 'E_INSTANCE_DESTROYED'; | ||
const E_AUTOLOADER_DYNAMIC_DEPENDENCY = 'E_AUTOLOADER_DYNAMIC_DEPENDENCY'; | ||
const E_BAD_CLASS = 'E_BAD_CLASS'; | ||
@@ -92,3 +99,3 @@ // Constants that should use Symbol whenever possible | ||
dependenciesDeclarations, | ||
true, | ||
{ injectOnly: true }, | ||
), | ||
@@ -312,2 +319,5 @@ }), | ||
register(initializer) { | ||
if (typeof initializer !== 'function') { | ||
throw new YError(E_BAD_INITIALIZER, initializer); | ||
} | ||
initializer[SPECIAL_PROPS.INJECT] = initializer[SPECIAL_PROPS.INJECT] || []; | ||
@@ -323,3 +333,3 @@ initializer[SPECIAL_PROPS.OPTIONS] = | ||
throw new YError( | ||
E_BAD_ANALYZER_TYPE, | ||
E_BAD_INITIALIZER_TYPE, | ||
initializer[SPECIAL_PROPS.TYPE], | ||
@@ -495,3 +505,3 @@ ALLOWED_INITIALIZER_TYPES, | ||
*/ | ||
run(dependenciesDeclarations) { | ||
async run(dependenciesDeclarations) { | ||
const _this = this; | ||
@@ -510,3 +520,3 @@ const internalDependencies = [ | ||
if (this.shutdownPromise) { | ||
throw new YError('E_INSTANCE_DESTROYED'); | ||
throw new YError(E_INSTANCE_DESTROYED); | ||
} | ||
@@ -532,3 +542,3 @@ | ||
siloContext.servicesDescriptors.set(DISPOSE, { | ||
service: () => { | ||
service: async () => { | ||
siloContext.shutdownPromise = | ||
@@ -540,12 +550,13 @@ siloContext.shutdownPromise || | ||
return siloContext.shutdownPromise.then(() => { | ||
this._silosContexts.delete(siloContext); | ||
}); | ||
await siloContext.shutdownPromise; | ||
this._silosContexts.delete(siloContext); | ||
// Shutdown services in their instanciation order | ||
function _shutdownNextServices(reversedServiceSequence) { | ||
async function _shutdownNextServices(reversedServiceSequence) { | ||
if (0 === reversedServiceSequence.length) { | ||
return Promise.resolve(); | ||
return; | ||
} | ||
return Promise.all( | ||
await Promise.all( | ||
reversedServiceSequence.pop().map(serviceName => { | ||
@@ -603,3 +614,5 @@ const singletonServiceDescriptor = _this._singletonsServicesDescriptors.get( | ||
}), | ||
).then(_shutdownNextServices.bind(null, reversedServiceSequence)); | ||
); | ||
await _shutdownNextServices(reversedServiceSequence); | ||
} | ||
@@ -612,23 +625,23 @@ }, | ||
return this._initializeDependencies( | ||
const servicesHash = await this._initializeDependencies( | ||
siloContext, | ||
siloContext.name, | ||
internalDependencies, | ||
).then(servicesHash => { | ||
debug('Handling fatal errors:', siloContext.errorsPromises); | ||
Promise.all(siloContext.errorsPromises).catch( | ||
siloContext.throwFatalError, | ||
); | ||
return dependenciesDeclarations.reduce( | ||
(finalHash, dependencyDeclaration) => { | ||
const { serviceName, mappedName } = parseDependencyDeclaration( | ||
dependencyDeclaration, | ||
); | ||
{ injectOnly: false, autoloading: false }, | ||
); | ||
finalHash[serviceName] = servicesHash[mappedName]; | ||
return finalHash; | ||
}, | ||
{}, | ||
); | ||
}); | ||
debug('Handling fatal errors:', siloContext.errorsPromises); | ||
Promise.all(siloContext.errorsPromises).catch(siloContext.throwFatalError); | ||
return dependenciesDeclarations.reduce( | ||
(finalHash, dependencyDeclaration) => { | ||
const { serviceName, mappedName } = parseDependencyDeclaration( | ||
dependencyDeclaration, | ||
); | ||
finalHash[serviceName] = servicesHash[mappedName]; | ||
return finalHash; | ||
}, | ||
{}, | ||
); | ||
} | ||
@@ -638,9 +651,16 @@ | ||
* Initialize or return a service descriptor | ||
* @param {Object} siloContext Current execution silo context | ||
* @param {Boolean} injectOnly Flag indicating if existing services only should be used | ||
* @param {String} serviceName Service name. | ||
* @param {Object} siloContext | ||
* Current execution silo context | ||
* @param {String} serviceName | ||
* Service name. | ||
* @param {Object} options | ||
* Options for service retrieval | ||
* @param {Boolean} options.injectOnly | ||
* Flag indicating if existing services only should be used | ||
* @param {Boolean} options.autoloading | ||
* Flag to indicating $autoload dependencies on the fly loading | ||
* @param {String} serviceProvider Service provider. | ||
* @return {Promise} Service dependencies hash promise. | ||
*/ | ||
_getServiceDescriptor(siloContext, injectOnly, serviceName) { | ||
_getServiceDescriptor(siloContext, serviceName, { injectOnly, autoloading }) { | ||
let serviceDescriptor = this._singletonsServicesDescriptors.get( | ||
@@ -651,2 +671,3 @@ serviceName, | ||
if (serviceDescriptor) { | ||
// The auto loader must only have static dependencies | ||
this._singletonsServicesHandles.get(serviceName).add(siloContext.name); | ||
@@ -668,15 +689,26 @@ } else { | ||
return this._initializeServiceDescriptor(siloContext, serviceName); | ||
return this._initializeServiceDescriptor(siloContext, serviceName, { | ||
autoloading: autoloading || AUTOLOAD === serviceName, | ||
injectOnly, | ||
}); | ||
} | ||
/** | ||
* Initialize a service | ||
* Initialize a service descriptor | ||
* @param {Object} siloContext Current execution silo context | ||
* @param {String} serviceName Service name. | ||
* @param {String} serviceProvider Service provider. | ||
* @param {Object} options | ||
* Options for service retrieval | ||
* @param {Boolean} options.injectOnly | ||
* Flag indicating if existing services only should be used | ||
* @param {Boolean} options.autoloading | ||
* Flag to indicating $autoload dependendencies on the fly loading. | ||
* @return {Promise} Service dependencies hash promise. | ||
*/ | ||
_initializeServiceDescriptor(siloContext, serviceName) { | ||
const serviceProvider = this._servicesProviders.get(serviceName); | ||
let serviceDescriptorPromise; | ||
async _initializeServiceDescriptor( | ||
siloContext, | ||
serviceName, | ||
{ autoloading, injectOnly }, | ||
) { | ||
let serviceProvider = this._servicesProviders.get(serviceName); | ||
@@ -687,52 +719,94 @@ debug('Initializing a service descriptor:', serviceName); | ||
debug('No service provider:', serviceName); | ||
serviceDescriptorPromise = Promise.reject( | ||
new YError(E_UNMATCHED_DEPENDENCY, serviceName), | ||
); | ||
siloContext.servicesDescriptors.set( | ||
serviceName, | ||
serviceDescriptorPromise, | ||
); | ||
return serviceDescriptorPromise; | ||
try { | ||
if (autoloading) { | ||
// The auto loader must only have static dependencies | ||
throw new YError(E_AUTOLOADER_DYNAMIC_DEPENDENCY, serviceName); | ||
} | ||
if (!this._servicesProviders.get(AUTOLOAD)) { | ||
throw new YError(E_UNMATCHED_DEPENDENCY, serviceName); | ||
} | ||
debug(`Loading the $autoload to lookup for: ${serviceName}.`); | ||
try { | ||
const autoloadingDescriptor = await this._getServiceDescriptor( | ||
siloContext, | ||
AUTOLOAD, | ||
{ injectOnly, autoloading: true }, | ||
); | ||
const { initializer, path } = await autoloadingDescriptor.service( | ||
serviceName, | ||
); | ||
if (typeof initializer !== 'function') { | ||
throw new YError( | ||
E_BAD_AUTOLOADED_INITIALIZER, | ||
serviceName, | ||
initializer, | ||
); | ||
} | ||
if (initializer[SPECIAL_PROPS.NAME] !== serviceName) { | ||
throw new YError( | ||
E_AUTOLOADED_INITIALIZER_MISMATCH, | ||
serviceName, | ||
initializer[SPECIAL_PROPS.NAME], | ||
); | ||
} | ||
debug(`Loaded the auto loader at path ${path}.`); | ||
this.register(initializer); | ||
serviceProvider = this._servicesProviders.get(serviceName); | ||
} catch (err) { | ||
debug(`Could not load ${serviceName} via the auto loader.`); | ||
throw err; | ||
} | ||
} catch (err) { | ||
const serviceDescriptorPromise = Promise.reject(err); | ||
siloContext.servicesDescriptors.set( | ||
serviceName, | ||
serviceDescriptorPromise, | ||
); | ||
await serviceDescriptorPromise; | ||
} | ||
} | ||
// A singleton service may use a reserved resource | ||
// like a TCP socket. This is why we have to be aware | ||
// of singleton services full shutdown before creating | ||
// a new one | ||
serviceDescriptorPromise = ( | ||
this._singletonsServicesShutdownsPromises.get(serviceName) || | ||
Promise.resolve() | ||
) | ||
// Anyway delete any shutdown promise before instanciating | ||
// a new service | ||
.then(() => { | ||
const serviceDescriptorPromise = (async () => { | ||
let serviceDescriptor; | ||
try { | ||
// A singleton service may use a reserved resource | ||
// like a TCP socket. This is why we have to be aware | ||
// of singleton services full shutdown before creating | ||
// a new one | ||
await (this._singletonsServicesShutdownsPromises.get(serviceName) || | ||
Promise.resolve()); | ||
// Anyway delete any shutdown promise before instanciating | ||
// a new service | ||
this._singletonsServicesShutdownsPromises.delete(serviceName); | ||
siloContext.servicesShutdownsPromises.delete(serviceName); | ||
}) | ||
.then( | ||
this._initializeDependencies.bind( | ||
this, | ||
const servicesHash = await this._initializeDependencies( | ||
siloContext, | ||
serviceName, | ||
serviceProvider[SPECIAL_PROPS.INJECT], | ||
), | ||
); | ||
{ injectOnly, autoloading }, | ||
); | ||
serviceDescriptorPromise = serviceDescriptorPromise | ||
.then(servicesHash => { | ||
debug('Successfully gathered service dependencies:', serviceName); | ||
return serviceProvider[SPECIAL_PROPS.INJECT].reduce( | ||
(finalHash, dependencyDeclaration) => { | ||
const { serviceName, mappedName } = parseDependencyDeclaration( | ||
dependencyDeclaration, | ||
); | ||
finalHash[serviceName] = servicesHash[mappedName]; | ||
return finalHash; | ||
}, | ||
{}, | ||
serviceDescriptor = await serviceProvider( | ||
serviceProvider[SPECIAL_PROPS.INJECT].reduce( | ||
(finalHash, dependencyDeclaration) => { | ||
const { serviceName, mappedName } = parseDependencyDeclaration( | ||
dependencyDeclaration, | ||
); | ||
finalHash[serviceName] = servicesHash[mappedName]; | ||
return finalHash; | ||
}, | ||
{}, | ||
), | ||
); | ||
}) | ||
.then(serviceProvider) | ||
.then(serviceDescriptor => { | ||
if (!serviceDescriptor) { | ||
@@ -750,5 +824,3 @@ debug('Provider did not return a descriptor:', serviceName); | ||
siloContext.servicesDescriptors.set(serviceName, serviceDescriptor); | ||
return serviceDescriptor; | ||
}) | ||
.catch(err => { | ||
} catch (err) { | ||
debug( | ||
@@ -765,3 +837,6 @@ 'Error initializing a service descriptor:', | ||
throw err; | ||
}); | ||
} | ||
return serviceDescriptor; | ||
})(); | ||
if (serviceProvider[SPECIAL_PROPS.OPTIONS].singleton) { | ||
@@ -790,60 +865,60 @@ const handlesSet = new Set(); | ||
* @param {String} servicesDeclarations Dependencies declarations. | ||
* @param {Boolean} injectOnly Flag indicating if existing services only should be used | ||
* @param {Object} options | ||
* Options for service retrieval | ||
* @param {Boolean} options.injectOnly | ||
* Flag indicating if existing services only should be used | ||
* @param {Boolean} options.autoloading | ||
* Flag to indicating $autoload dependendencies on the fly loading. | ||
* @return {Promise} Service dependencies hash promise. | ||
*/ | ||
_initializeDependencies( | ||
async _initializeDependencies( | ||
siloContext, | ||
serviceName, | ||
servicesDeclarations, | ||
injectOnly = false, | ||
{ injectOnly = false, autoloading = false }, | ||
) { | ||
debug('Initializing dependencies:', serviceName, servicesDeclarations); | ||
return Promise.resolve().then(() => | ||
Promise.all( | ||
servicesDeclarations.map(serviceDeclaration => { | ||
const { mappedName, optional } = parseDependencyDeclaration( | ||
serviceDeclaration, | ||
); | ||
const servicesDescriptors = await Promise.all( | ||
servicesDeclarations.map(serviceDeclaration => { | ||
const { mappedName, optional } = parseDependencyDeclaration( | ||
serviceDeclaration, | ||
); | ||
return this._getServiceDescriptor( | ||
siloContext, | ||
injectOnly, | ||
mappedName, | ||
).catch(err => { | ||
if (optional) { | ||
return Promise.resolve(); | ||
} | ||
throw err; | ||
}); | ||
}), | ||
) | ||
.then(servicesDescriptors => { | ||
debug( | ||
'Initialized dependencies descriptors:', | ||
serviceName, | ||
servicesDeclarations, | ||
); | ||
siloContext.servicesSequence.push( | ||
servicesDeclarations.map(_pickMappedNameFromDeclaration), | ||
); | ||
return Promise.all( | ||
servicesDescriptors.map(serviceDescriptor => { | ||
if (!serviceDescriptor) { | ||
return {}.undef; | ||
} | ||
return serviceDescriptor.service; | ||
}), | ||
); | ||
}) | ||
.then(services => | ||
services.reduce((hash, service, index) => { | ||
const mappedName = _pickMappedNameFromDeclaration( | ||
servicesDeclarations[index], | ||
); | ||
return this._getServiceDescriptor(siloContext, mappedName, { | ||
injectOnly, | ||
autoloading, | ||
}).catch(err => { | ||
if (optional) { | ||
return Promise.resolve(); | ||
} | ||
throw err; | ||
}); | ||
}), | ||
); | ||
debug( | ||
'Initialized dependencies descriptors:', | ||
serviceName, | ||
servicesDeclarations, | ||
); | ||
siloContext.servicesSequence.push( | ||
servicesDeclarations.map(_pickMappedNameFromDeclaration), | ||
); | ||
hash[mappedName] = service; | ||
return hash; | ||
}, {}), | ||
), | ||
const services = await Promise.all( | ||
servicesDescriptors.map(serviceDescriptor => { | ||
if (!serviceDescriptor) { | ||
return {}.undef; | ||
} | ||
return serviceDescriptor.service; | ||
}), | ||
); | ||
return services.reduce((hash, service, index) => { | ||
const mappedName = _pickMappedNameFromDeclaration( | ||
servicesDeclarations[index], | ||
); | ||
hash[mappedName] = service; | ||
return hash; | ||
}, {}); | ||
} | ||
@@ -863,7 +938,5 @@ } | ||
function _pickMappedNameFromDeclaration(dependencyDeclaration) { | ||
const { serviceName, mappedName } = parseDependencyDeclaration( | ||
dependencyDeclaration, | ||
); | ||
const { mappedName } = parseDependencyDeclaration(dependencyDeclaration); | ||
return mappedName || serviceName; | ||
return mappedName; | ||
} | ||
@@ -901,3 +974,3 @@ | ||
if (!classes[style.className]) { | ||
throw new YError('E_BAD_CLASS', style.className, serviceName); | ||
throw new YError(E_BAD_CLASS, style.className, serviceName); | ||
} | ||
@@ -911,3 +984,3 @@ classesApplications[serviceName] = style.className; | ||
if (!classes[style.className]) { | ||
throw new YError('E_BAD_CLASS', style.className, dependedServiceName); | ||
throw new YError(E_BAD_CLASS, style.className, dependedServiceName); | ||
} | ||
@@ -914,0 +987,0 @@ classesApplications[dependedServiceName] = style.className; |
@@ -5,4 +5,6 @@ /* eslint max-nested-callbacks:0 */ | ||
import sinon from 'sinon'; | ||
import YError from 'yerror'; | ||
import { Knifecycle, inject, options } from './index'; | ||
import { initializer, SPECIAL_PROPS } from './util'; | ||
@@ -62,2 +64,48 @@ describe('Knifecycle', () => { | ||
describe('register', () => { | ||
it('should fail when intitializer is no a function', () => { | ||
assert.throws( | ||
() => { | ||
$.register('not_a_function'); | ||
}, | ||
err => { | ||
assert.deepEqual(err.code, 'E_BAD_INITIALIZER'); | ||
assert.deepEqual(err.params, ['not_a_function']); | ||
return true; | ||
}, | ||
); | ||
}); | ||
it('should fail with no service name', () => { | ||
assert.throws( | ||
() => { | ||
$.register(() => {}); | ||
}, | ||
err => { | ||
assert.deepEqual(err.code, 'E_ANONYMOUS_ANALYZER'); | ||
assert.deepEqual(err.params, []); | ||
return true; | ||
}, | ||
); | ||
}); | ||
it('should fail with a bad service type', () => { | ||
assert.throws( | ||
() => { | ||
const fn = () => {}; | ||
fn[SPECIAL_PROPS.NAME] = 'test'; | ||
fn[SPECIAL_PROPS.TYPE] = 'not_allowed_type'; | ||
$.register(fn); | ||
}, | ||
err => { | ||
assert.deepEqual(err.code, 'E_BAD_INITIALIZER_TYPE'); | ||
assert.deepEqual(err.params, [ | ||
'not_allowed_type', | ||
['provider', 'service'], | ||
]); | ||
return true; | ||
}, | ||
); | ||
}); | ||
}); | ||
describe('provider', () => { | ||
@@ -150,28 +198,22 @@ it('should register provider', () => { | ||
describe('run', () => { | ||
it('should work with no dependencies', done => { | ||
$.run([]) | ||
.then(dependencies => { | ||
assert.deepEqual(dependencies, {}); | ||
}) | ||
.then(() => done()) | ||
.catch(done); | ||
it('should work with no dependencies', async () => { | ||
const dependencies = await $.run([]); | ||
assert.deepEqual(dependencies, {}); | ||
}); | ||
it('should work with constant dependencies', done => { | ||
it('should work with constant dependencies', async () => { | ||
$.constant('ENV', ENV); | ||
$.constant('time', time); | ||
$.run(['time', 'ENV']) | ||
.then(dependencies => { | ||
assert.deepEqual(Object.keys(dependencies), ['time', 'ENV']); | ||
assert.deepEqual(dependencies, { | ||
ENV, | ||
time, | ||
}); | ||
}) | ||
.then(() => done()) | ||
.catch(done); | ||
const dependencies = await $.run(['time', 'ENV']); | ||
assert.deepEqual(Object.keys(dependencies), ['time', 'ENV']); | ||
assert.deepEqual(dependencies, { | ||
ENV, | ||
time, | ||
}); | ||
}); | ||
it('should work with service dependencies', done => { | ||
it('should work with service dependencies', async () => { | ||
$.service( | ||
@@ -185,14 +227,11 @@ 'sample', | ||
$.run(['sample']) | ||
.then(dependencies => { | ||
assert.deepEqual(Object.keys(dependencies), ['sample']); | ||
assert.deepEqual(dependencies, { | ||
sample: 'function', | ||
}); | ||
}) | ||
.then(() => done()) | ||
.catch(done); | ||
const dependencies = await $.run(['sample']); | ||
assert.deepEqual(Object.keys(dependencies), ['sample']); | ||
assert.deepEqual(dependencies, { | ||
sample: 'function', | ||
}); | ||
}); | ||
it('should work with simple dependencies', done => { | ||
it('should work with simple dependencies', async () => { | ||
$.constant('ENV', ENV); | ||
@@ -202,15 +241,12 @@ $.constant('time', time); | ||
$.run(['time', 'hash']) | ||
.then(dependencies => { | ||
assert.deepEqual(Object.keys(dependencies), ['time', 'hash']); | ||
assert.deepEqual(dependencies, { | ||
hash: { ENV }, | ||
time, | ||
}); | ||
}) | ||
.then(() => done()) | ||
.catch(done); | ||
const dependencies = await $.run(['time', 'hash']); | ||
assert.deepEqual(Object.keys(dependencies), ['time', 'hash']); | ||
assert.deepEqual(dependencies, { | ||
hash: { ENV }, | ||
time, | ||
}); | ||
}); | ||
it('should work with given optional dependencies', done => { | ||
it('should work with given optional dependencies', async () => { | ||
$.constant('ENV', ENV); | ||
@@ -221,15 +257,12 @@ $.constant('DEBUG', {}); | ||
$.run(['time', 'hash']) | ||
.then(dependencies => { | ||
assert.deepEqual(Object.keys(dependencies), ['time', 'hash']); | ||
assert.deepEqual(dependencies, { | ||
hash: { ENV, DEBUG: {} }, | ||
time, | ||
}); | ||
}) | ||
.then(() => done()) | ||
.catch(done); | ||
const dependencies = await $.run(['time', 'hash']); | ||
assert.deepEqual(Object.keys(dependencies), ['time', 'hash']); | ||
assert.deepEqual(dependencies, { | ||
hash: { ENV, DEBUG: {} }, | ||
time, | ||
}); | ||
}); | ||
it('should work with lacking optional dependencies', done => { | ||
it('should work with lacking optional dependencies', async () => { | ||
$.constant('ENV', ENV); | ||
@@ -239,15 +272,12 @@ $.constant('time', time); | ||
$.run(['time', 'hash']) | ||
.then(dependencies => { | ||
assert.deepEqual(Object.keys(dependencies), ['time', 'hash']); | ||
assert.deepEqual(dependencies, { | ||
hash: { ENV, DEBUG: {}.undef }, | ||
time, | ||
}); | ||
}) | ||
.then(() => done()) | ||
.catch(done); | ||
const dependencies = await $.run(['time', 'hash']); | ||
assert.deepEqual(Object.keys(dependencies), ['time', 'hash']); | ||
assert.deepEqual(dependencies, { | ||
hash: { ENV, DEBUG: {}.undef }, | ||
time, | ||
}); | ||
}); | ||
it('should work with deeper dependencies', done => { | ||
it('should work with deeper dependencies', async () => { | ||
$.constant('ENV', ENV); | ||
@@ -262,11 +292,8 @@ $.constant('time', time); | ||
$.run(['hash5', 'time']) | ||
.then(dependencies => { | ||
assert.deepEqual(Object.keys(dependencies), ['hash5', 'time']); | ||
}) | ||
.then(() => done()) | ||
.catch(done); | ||
const dependencies = await $.run(['hash5', 'time']); | ||
assert.deepEqual(Object.keys(dependencies), ['hash5', 'time']); | ||
}); | ||
it('should instanciate services once', done => { | ||
it('should instanciate services once', async () => { | ||
const timeServiceStub = sinon.spy(timeService); | ||
@@ -280,17 +307,14 @@ | ||
$.run(['hash', 'hash2', 'hash3', 'time']) | ||
.then(dependencies => { | ||
assert.deepEqual(Object.keys(dependencies), [ | ||
'hash', | ||
'hash2', | ||
'hash3', | ||
'time', | ||
]); | ||
assert.deepEqual(timeServiceStub.args, [[{}]]); | ||
}) | ||
.then(() => done()) | ||
.catch(done); | ||
const dependencies = await $.run(['hash', 'hash2', 'hash3', 'time']); | ||
assert.deepEqual(Object.keys(dependencies), [ | ||
'hash', | ||
'hash2', | ||
'hash3', | ||
'time', | ||
]); | ||
assert.deepEqual(timeServiceStub.args, [[{}]]); | ||
}); | ||
it('should instanciate a single mapped service', done => { | ||
it('should instanciate a single mapped service', async () => { | ||
const providerStub = sinon.stub().returns( | ||
@@ -309,20 +333,18 @@ Promise.resolve({ | ||
$.provider('mappedStub2', providerStub2); | ||
$.run(['stub>mappedStub']) | ||
.then(dependencies => { | ||
assert.deepEqual(dependencies, { | ||
stub: 'stub', | ||
}); | ||
assert.deepEqual(providerStub.args, [ | ||
[ | ||
{ | ||
stub2: 'stub2', | ||
}, | ||
], | ||
]); | ||
}) | ||
.then(() => done()) | ||
.catch(done); | ||
const dependencies = await $.run(['stub>mappedStub']); | ||
assert.deepEqual(dependencies, { | ||
stub: 'stub', | ||
}); | ||
assert.deepEqual(providerStub.args, [ | ||
[ | ||
{ | ||
stub2: 'stub2', | ||
}, | ||
], | ||
]); | ||
}); | ||
it('should instanciate several services with mappings', done => { | ||
it('should instanciate several services with mappings', async () => { | ||
const timeServiceStub = sinon.spy(timeService); | ||
@@ -336,71 +358,57 @@ | ||
$.run(['hash2>aHash2', 'hash3>aHash3', 'time>aTime']) | ||
.then(dependencies => { | ||
assert.deepEqual(Object.keys(dependencies), [ | ||
'hash2', | ||
'hash3', | ||
'time', | ||
]); | ||
assert.deepEqual(timeServiceStub.args, [[{}]]); | ||
}) | ||
.then(() => done()) | ||
.catch(done); | ||
const dependencies = await $.run([ | ||
'hash2>aHash2', | ||
'hash3>aHash3', | ||
'time>aTime', | ||
]); | ||
assert.deepEqual(Object.keys(dependencies), ['hash2', 'hash3', 'time']); | ||
assert.deepEqual(timeServiceStub.args, [[{}]]); | ||
}); | ||
it('should fail with bad service', done => { | ||
it('should fail with bad service', async () => { | ||
$.service('lol', () => {}); | ||
$.run(['lol']) | ||
.then(() => { | ||
throw new Error('E_UNEXPECTED_SUCCESS'); | ||
}) | ||
.catch(err => { | ||
assert.deepEqual(err.code, 'E_BAD_SERVICE_PROMISE'); | ||
assert.deepEqual(err.params, ['lol']); | ||
}) | ||
.then(() => done()) | ||
.catch(done); | ||
try { | ||
await $.run(['lol']); | ||
throw new Error('E_UNEXPECTED_SUCCESS'); | ||
} catch (err) { | ||
assert.deepEqual(err.code, 'E_BAD_SERVICE_PROMISE'); | ||
assert.deepEqual(err.params, ['lol']); | ||
} | ||
}); | ||
it('should fail with bad provider', done => { | ||
it('should fail with bad provider', async () => { | ||
$.provider('lol', () => {}); | ||
$.run(['lol']) | ||
.then(() => { | ||
throw new Error('E_UNEXPECTED_SUCCESS'); | ||
}) | ||
.catch(err => { | ||
assert.deepEqual(err.code, 'E_BAD_SERVICE_PROVIDER'); | ||
assert.deepEqual(err.params, ['lol']); | ||
}) | ||
.then(() => done()) | ||
.catch(done); | ||
try { | ||
await $.run(['lol']); | ||
throw new Error('E_UNEXPECTED_SUCCESS'); | ||
} catch (err) { | ||
assert.deepEqual(err.code, 'E_BAD_SERVICE_PROVIDER'); | ||
assert.deepEqual(err.params, ['lol']); | ||
} | ||
}); | ||
it('should fail with bad service in a provider', done => { | ||
it('should fail with bad service in a provider', async () => { | ||
$.provider('lol', () => Promise.resolve()); | ||
$.run(['lol']) | ||
.then(() => { | ||
throw new Error('E_UNEXPECTED_SUCCESS'); | ||
}) | ||
.catch(err => { | ||
assert.deepEqual(err.code, 'E_BAD_SERVICE_PROVIDER'); | ||
assert.deepEqual(err.params, ['lol']); | ||
}) | ||
.then(() => done()) | ||
.catch(done); | ||
try { | ||
await $.run(['lol']); | ||
throw new Error('E_UNEXPECTED_SUCCESS'); | ||
} catch (err) { | ||
assert.deepEqual(err.code, 'E_BAD_SERVICE_PROVIDER'); | ||
assert.deepEqual(err.params, ['lol']); | ||
} | ||
}); | ||
it('should fail with undeclared dependencies', done => { | ||
$.run(['lol']) | ||
.then(() => { | ||
throw new Error('E_UNEXPECTED_SUCCESS'); | ||
}) | ||
.catch(err => { | ||
assert.deepEqual(err.code, 'E_UNMATCHED_DEPENDENCY'); | ||
assert.deepEqual(err.params, ['lol']); | ||
}) | ||
.then(() => done()) | ||
.catch(done); | ||
it('should fail with undeclared dependencies', async () => { | ||
try { | ||
await $.run(['lol']); | ||
throw new Error('E_UNEXPECTED_SUCCESS'); | ||
} catch (err) { | ||
assert.deepEqual(err.code, 'E_UNMATCHED_DEPENDENCY'); | ||
assert.deepEqual(err.params, ['lol']); | ||
} | ||
}); | ||
it('should fail with undeclared dependencies upstream', done => { | ||
it('should fail with undeclared dependencies upstream', async () => { | ||
$.constant('ENV', ENV); | ||
@@ -411,15 +419,12 @@ $.constant('time', time); | ||
$.run(['time', 'hash']) | ||
.then(() => { | ||
throw new Error('E_UNEXPECTED_SUCCESS'); | ||
}) | ||
.catch(err => { | ||
assert.deepEqual(err.code, 'E_UNMATCHED_DEPENDENCY'); | ||
assert.deepEqual(err.params, ['hash', 'hash2', 'lol']); | ||
}) | ||
.then(() => done()) | ||
.catch(done); | ||
try { | ||
await $.run(['time', 'hash']); | ||
throw new Error('E_UNEXPECTED_SUCCESS'); | ||
} catch (err) { | ||
assert.deepEqual(err.code, 'E_UNMATCHED_DEPENDENCY'); | ||
assert.deepEqual(err.params, ['hash', 'hash2', 'lol']); | ||
} | ||
}); | ||
it('should provide a fatal error handler', done => { | ||
it('should provide a fatal error handler', async () => { | ||
$.constant('ENV', ENV); | ||
@@ -439,39 +444,250 @@ $.constant('time', time); | ||
function dbProvider({ ENV }) { | ||
return Promise.resolve().then(() => { | ||
let service; | ||
const fatalErrorPromise = new Promise((resolve, reject) => { | ||
service = Promise.resolve({ | ||
resolve, | ||
reject, | ||
ENV, | ||
}); | ||
async function dbProvider({ ENV }) { | ||
let service; | ||
const fatalErrorPromise = new Promise((resolve, reject) => { | ||
service = Promise.resolve({ | ||
resolve, | ||
reject, | ||
ENV, | ||
}); | ||
}); | ||
return { | ||
service, | ||
fatalErrorPromise, | ||
}; | ||
}); | ||
return { | ||
service, | ||
fatalErrorPromise, | ||
}; | ||
} | ||
$.run(['time', 'hash', 'db', 'process']) | ||
.then(({ process, db }) => { | ||
process.fatalErrorPromise | ||
.then(() => { | ||
done(new Error('E_UNEXPECTED_SUCCESS')); | ||
}) | ||
.catch(err => { | ||
assert.deepEqual(err.message, 'E_DB_ERROR'); | ||
}) | ||
.then(() => done()) | ||
.catch(done); | ||
db.reject(new Error('E_DB_ERROR')); | ||
}) | ||
.catch(done); | ||
const { process, db } = await $.run(['time', 'hash', 'db', 'process']); | ||
try { | ||
db.reject(new Error('E_DB_ERROR')); | ||
await process.fatalErrorPromise; | ||
throw new Error('E_UNEXPECTED_SUCCESS'); | ||
} catch (err) { | ||
assert.deepEqual(err.message, 'E_DB_ERROR'); | ||
} | ||
}); | ||
}); | ||
describe('autoload', () => { | ||
it('should work with lacking autoloaded dependencies', async () => { | ||
$.register( | ||
initializer( | ||
{ | ||
type: 'service', | ||
name: '$autoload', | ||
inject: [], | ||
}, | ||
async () => async serviceName => ({ | ||
path: '/path/of/debug', | ||
initializer: initializer( | ||
{ | ||
type: 'service', | ||
name: 'DEBUG', | ||
inject: [], | ||
}, | ||
async () => 'THE_DEBUG:' + serviceName, | ||
), | ||
}), | ||
), | ||
); | ||
$.constant('ENV', ENV); | ||
$.constant('time', time); | ||
$.provider('hash', inject(['ENV', '?DEBUG'], hashProvider)); | ||
const dependencies = await $.run(['time', 'hash']); | ||
assert.deepEqual(Object.keys(dependencies), ['time', 'hash']); | ||
assert.deepEqual(dependencies, { | ||
hash: { ENV, DEBUG: 'THE_DEBUG:DEBUG' }, | ||
time, | ||
}); | ||
}); | ||
it('should work with deeper several lacking dependencies', async () => { | ||
$.register( | ||
initializer( | ||
{ | ||
name: '$autoload', | ||
type: 'service', | ||
}, | ||
async () => async serviceName => ({ | ||
path: `/path/to/${serviceName}`, | ||
initializer: initializer( | ||
{ | ||
type: 'provider', | ||
name: serviceName, | ||
inject: | ||
'hash2' === serviceName | ||
? ['hash1'] | ||
: 'hash4' === serviceName | ||
? ['hash3'] | ||
: [], | ||
}, | ||
hashProvider, | ||
), | ||
}), | ||
), | ||
); | ||
$.constant('ENV', ENV); | ||
$.constant('time', time); | ||
$.provider('hash', inject(['ENV'], hashProvider)); | ||
$.provider('hash1', inject(['hash'], hashProvider)); | ||
$.provider('hash3', inject(['hash2'], hashProvider)); | ||
$.provider('hash5', inject(['hash4'], hashProvider)); | ||
const dependencies = await $.run(['hash5', 'time']); | ||
assert.deepEqual(Object.keys(dependencies), ['hash5', 'time']); | ||
}); | ||
it('should work with various dependencies', async () => { | ||
$.provider('hash', inject(['hash2'], hashProvider)); | ||
$.provider('hash3', inject(['?ENV'], hashProvider)); | ||
$.constant('DEBUG', 1); | ||
$.register( | ||
initializer( | ||
{ | ||
type: 'service', | ||
name: '$autoload', | ||
inject: ['?ENV', 'DEBUG'], | ||
}, | ||
async () => async serviceName => ({ | ||
path: '/path/of/debug', | ||
initializer: initializer( | ||
{ | ||
type: 'service', | ||
name: 'hash2', | ||
inject: ['hash3'], | ||
}, | ||
async () => 'THE_HASH:' + serviceName, | ||
), | ||
}), | ||
), | ||
); | ||
const dependencies = await $.run(['hash', '?ENV']); | ||
assert.deepEqual(Object.keys(dependencies), ['hash', 'ENV']); | ||
}); | ||
it('should fail when autoload does not exists', async () => { | ||
try { | ||
await $.run(['test']); | ||
throw new YError('E_UNEXPECTED_SUCCESS'); | ||
} catch (err) { | ||
assert.equal(err.code, 'E_UNMATCHED_DEPENDENCY'); | ||
} | ||
}); | ||
it('should fail when autoloaded dependencies are not found', async () => { | ||
$.register( | ||
initializer( | ||
{ | ||
type: 'service', | ||
name: '$autoload', | ||
inject: [], | ||
}, | ||
async () => async serviceName => { | ||
throw new YError('E_CANNOT_AUTOLOAD', serviceName); | ||
}, | ||
), | ||
); | ||
try { | ||
await $.run(['test']); | ||
throw new YError('E_UNEXPECTED_SUCCESS'); | ||
} catch (err) { | ||
assert.equal(err.code, 'E_CANNOT_AUTOLOAD'); | ||
assert.deepEqual(err.params, ['test']); | ||
} | ||
}); | ||
it('should fail when autoloaded dependencies are not initializers', async () => { | ||
$.register( | ||
initializer( | ||
{ | ||
type: 'service', | ||
name: '$autoload', | ||
inject: [], | ||
}, | ||
async () => async () => 'not_an_initializer', | ||
), | ||
); | ||
try { | ||
await $.run(['test']); | ||
throw new YError('E_UNEXPECTED_SUCCESS'); | ||
} catch (err) { | ||
assert.equal(err.code, 'E_BAD_AUTOLOADED_INITIALIZER'); | ||
assert.deepEqual(err.params, ['test', {}.undef]); | ||
} | ||
}); | ||
it('should fail when autoloaded dependencies are not right initializers', async () => { | ||
$.register( | ||
initializer( | ||
{ | ||
type: 'service', | ||
name: '$autoload', | ||
inject: [], | ||
}, | ||
async () => async serviceName => ({ | ||
path: '/path/of/debug', | ||
initializer: initializer( | ||
{ | ||
type: 'service', | ||
name: 'not-' + serviceName, | ||
inject: [], | ||
}, | ||
async () => 'THE_TEST:' + serviceName, | ||
), | ||
}), | ||
), | ||
); | ||
try { | ||
await $.run(['test']); | ||
throw new YError('E_UNEXPECTED_SUCCESS'); | ||
} catch (err) { | ||
assert.equal(err.code, 'E_AUTOLOADED_INITIALIZER_MISMATCH'); | ||
assert.deepEqual(err.params, ['test', 'not-test']); | ||
} | ||
}); | ||
it('should fail when autoload depends on autoloaded/unexisting dependencies', async () => { | ||
$.register( | ||
initializer( | ||
{ | ||
type: 'service', | ||
name: '$autoload', | ||
inject: ['ENV'], | ||
}, | ||
async () => async serviceName => ({ | ||
path: '/path/of/debug', | ||
initializer: initializer( | ||
{ | ||
type: 'service', | ||
name: 'DEBUG', | ||
inject: [], | ||
}, | ||
async () => 'THE_DEBUG:' + serviceName, | ||
), | ||
}), | ||
), | ||
); | ||
try { | ||
await $.run(['test']); | ||
throw new YError('E_UNEXPECTED_SUCCESS'); | ||
} catch (err) { | ||
assert.equal(err.code, 'E_AUTOLOADER_DYNAMIC_DEPENDENCY'); | ||
assert.deepEqual(err.params, ['ENV']); | ||
} | ||
}); | ||
}); | ||
describe('inject', () => { | ||
it('should work with no dependencies', done => { | ||
it('should work with no dependencies', async () => { | ||
$.constant('ENV', ENV); | ||
@@ -481,19 +697,15 @@ $.constant('time', time); | ||
$.run(['time', 'hash', '$injector']) | ||
.then(dependencies => { | ||
assert.deepEqual(Object.keys(dependencies), [ | ||
'time', | ||
'hash', | ||
'$injector', | ||
]); | ||
return dependencies.$injector([]).then(injectDependencies => { | ||
assert.deepEqual(Object.keys(injectDependencies), []); | ||
assert.deepEqual(injectDependencies, {}); | ||
}); | ||
}) | ||
.then(() => done()) | ||
.catch(done); | ||
const dependencies = await $.run(['time', 'hash', '$injector']); | ||
assert.deepEqual(Object.keys(dependencies), [ | ||
'time', | ||
'hash', | ||
'$injector', | ||
]); | ||
const injectDependencies = await dependencies.$injector([]); | ||
assert.deepEqual(Object.keys(injectDependencies), []); | ||
assert.deepEqual(injectDependencies, {}); | ||
}); | ||
it('should work with same dependencies then the running silo', done => { | ||
it('should work with same dependencies then the running silo', async () => { | ||
$.constant('ENV', ENV); | ||
@@ -503,27 +715,18 @@ $.constant('time', time); | ||
$.run(['time', 'hash', '$injector']) | ||
.then(dependencies => { | ||
assert.deepEqual(Object.keys(dependencies), [ | ||
'time', | ||
'hash', | ||
'$injector', | ||
]); | ||
return dependencies | ||
.$injector(['time', 'hash']) | ||
.then(injectDependencies => { | ||
assert.deepEqual(Object.keys(injectDependencies), [ | ||
'time', | ||
'hash', | ||
]); | ||
assert.deepEqual(injectDependencies, { | ||
hash: { ENV }, | ||
time, | ||
}); | ||
}); | ||
}) | ||
.then(() => done()) | ||
.catch(done); | ||
const dependencies = await $.run(['time', 'hash', '$injector']); | ||
assert.deepEqual(Object.keys(dependencies), [ | ||
'time', | ||
'hash', | ||
'$injector', | ||
]); | ||
const injectDependencies = await dependencies.$injector(['time', 'hash']); | ||
assert.deepEqual(Object.keys(injectDependencies), ['time', 'hash']); | ||
assert.deepEqual(injectDependencies, { | ||
hash: { ENV }, | ||
time, | ||
}); | ||
}); | ||
it('should fail with non instanciated dependencies', done => { | ||
it('should fail with non instanciated dependencies', async () => { | ||
$.constant('ENV', ENV); | ||
@@ -533,29 +736,30 @@ $.constant('time', time); | ||
$.run(['time', '$injector']) | ||
.then(dependencies => { | ||
assert.deepEqual(Object.keys(dependencies), ['time', '$injector']); | ||
return dependencies.$injector(['time', 'hash']).catch(err => { | ||
assert.equal(err.code, 'E_BAD_INJECTION'); | ||
}); | ||
}) | ||
.then(() => done()) | ||
.catch(done); | ||
const dependencies = await $.run(['time', '$injector']); | ||
assert.deepEqual(Object.keys(dependencies), ['time', '$injector']); | ||
try { | ||
await dependencies.$injector(['time', 'hash']); | ||
throw new YError('E_UNEXPECTED_SUCCESS'); | ||
} catch (err) { | ||
assert.equal(err.code, 'E_BAD_INJECTION'); | ||
} | ||
}); | ||
it('should create dependencies when not declared as singletons', done => { | ||
it('should create dependencies when not declared as singletons', async () => { | ||
$.constant('ENV', ENV); | ||
$.provider('hash', inject(['ENV'], hashProvider)); | ||
Promise.all([$.run(['hash']), $.run(['hash'])]) | ||
.then(([{ hash }, { hash: sameHash }]) => { | ||
assert.notEqual(hash, sameHash); | ||
return $.run(['hash']).then(({ hash: yaSameHash }) => { | ||
assert.notEqual(hash, yaSameHash); | ||
}); | ||
}) | ||
.then(() => done()) | ||
.catch(done); | ||
const [{ hash }, { hash: sameHash }] = await Promise.all([ | ||
$.run(['hash']), | ||
$.run(['hash']), | ||
]); | ||
assert.notEqual(hash, sameHash); | ||
const { hash: yaSameHash } = await $.run(['hash']); | ||
assert.notEqual(hash, yaSameHash); | ||
}); | ||
it('should reuse dependencies when declared as singletons', done => { | ||
it('should reuse dependencies when declared as singletons', async () => { | ||
$.constant('ENV', ENV); | ||
@@ -578,3 +782,6 @@ $.provider('hash', inject(['ENV'], hashProvider), { | ||
Promise.all([ | ||
const [ | ||
{ hash, hash2 }, | ||
{ hash: sameHash, hash2: sameHash2 }, | ||
] = await Promise.all([ | ||
$.run(['hash']), | ||
@@ -584,12 +791,9 @@ $.run(['hash']), | ||
$.run(['hash2']), | ||
]) | ||
.then(([{ hash, hash2 }, { hash: sameHash, hash2: sameHash2 }]) => { | ||
assert.equal(hash, sameHash); | ||
assert.equal(hash2, sameHash2); | ||
return $.run(['hash']).then(({ hash: yaSameHash }) => { | ||
assert.equal(hash, yaSameHash); | ||
}); | ||
}) | ||
.then(() => done()) | ||
.catch(done); | ||
]); | ||
assert.equal(hash, sameHash); | ||
assert.equal(hash2, sameHash2); | ||
const { hash: yaSameHash } = await $.run(['hash']); | ||
assert.equal(hash, yaSameHash); | ||
}); | ||
@@ -599,14 +803,10 @@ }); | ||
describe('$destroy', () => { | ||
it('should work even with one silo and no dependencies', done => { | ||
$.run(['$destroy']) | ||
.then(dependencies => { | ||
assert.equal(typeof dependencies.$destroy, 'function'); | ||
it('should work even with one silo and no dependencies', async () => { | ||
const dependencies = await $.run(['$destroy']); | ||
assert.equal(typeof dependencies.$destroy, 'function'); | ||
return dependencies.$destroy(); | ||
}) | ||
.then(() => done()) | ||
.catch(done); | ||
await dependencies.$destroy(); | ||
}); | ||
it('should work with several silos and dependencies', done => { | ||
it('should work with several silos and dependencies', async () => { | ||
$.constant('ENV', ENV); | ||
@@ -618,17 +818,14 @@ $.constant('time', time); | ||
Promise.all([ | ||
const [dependencies] = await Promise.all([ | ||
$.run(['$destroy']), | ||
$.run(['ENV', 'hash', 'hash1', 'time']), | ||
$.run(['ENV', 'hash', 'hash2']), | ||
]) | ||
.then(([dependencies]) => { | ||
assert.equal(typeof dependencies.$destroy, 'function'); | ||
]); | ||
return dependencies.$destroy(); | ||
}) | ||
.then(() => done()) | ||
.catch(done); | ||
assert.equal(typeof dependencies.$destroy, 'function'); | ||
await dependencies.$destroy(); | ||
}); | ||
it('should work when trigered from several silos simultaneously', done => { | ||
it('should work when trigered from several silos simultaneously', async () => { | ||
$.constant('ENV', ENV); | ||
@@ -640,17 +837,14 @@ $.constant('time', time); | ||
Promise.all([ | ||
const dependenciesBuckets = await Promise.all([ | ||
$.run(['$destroy']), | ||
$.run(['$destroy', 'ENV', 'hash', 'hash1', 'time']), | ||
$.run(['$destroy', 'ENV', 'hash', 'hash2']), | ||
]) | ||
.then(dependenciesBuckets => | ||
Promise.all( | ||
dependenciesBuckets.map(dependencies => dependencies.$destroy()), | ||
), | ||
) | ||
.then(() => done()) | ||
.catch(done); | ||
]); | ||
await Promise.all( | ||
dependenciesBuckets.map(dependencies => dependencies.$destroy()), | ||
); | ||
}); | ||
it('should work when a silo shutdown is in progress', done => { | ||
it('should work when a silo shutdown is in progress', async () => { | ||
$.constant('ENV', ENV); | ||
@@ -662,15 +856,11 @@ $.constant('time', time); | ||
Promise.all([ | ||
const [dependencies1, dependencies2] = await Promise.all([ | ||
$.run(['$destroy']), | ||
$.run(['$dispose', 'ENV', 'hash', 'hash1', 'time']), | ||
$.run(['ENV', 'hash', 'hash2']), | ||
]) | ||
.then(([dependencies1, dependencies2]) => | ||
Promise.all([dependencies2.$dispose(), dependencies1.$destroy()]), | ||
) | ||
.then(() => done()) | ||
.catch(done); | ||
]); | ||
await Promise.all([dependencies2.$dispose(), dependencies1.$destroy()]); | ||
}); | ||
it('should disallow new runs', done => { | ||
it('should disallow new runs', async () => { | ||
$.constant('ENV', ENV); | ||
@@ -681,19 +871,14 @@ $.constant('time', time); | ||
$.run(['$destroy']) | ||
.then(dependencies => { | ||
assert.equal(typeof dependencies.$destroy, 'function'); | ||
const dependencies = await $.run(['$destroy']); | ||
return dependencies.$destroy(); | ||
}) | ||
.then(() => { | ||
assert.throws( | ||
() => $.run(['ENV', 'hash', 'hash1']), | ||
err => { | ||
assert.equal(err.code, 'E_INSTANCE_DESTROYED'); | ||
return true; | ||
}, | ||
); | ||
}) | ||
.then(() => done()) | ||
.catch(done); | ||
assert.equal(typeof dependencies.$destroy, 'function'); | ||
await dependencies.$destroy(); | ||
try { | ||
await $.run(['ENV', 'hash', 'hash1']); | ||
throw new YError('E_UNEXPECTED_SUCCES'); | ||
} catch (err) { | ||
assert.equal(err.code, 'E_INSTANCE_DESTROYED'); | ||
} | ||
}); | ||
@@ -703,32 +888,20 @@ }); | ||
describe('$dispose', () => { | ||
it('should work with no dependencies', done => { | ||
$.run(['$dispose']) | ||
.then(dependencies => { | ||
assert.equal(typeof dependencies.$dispose, 'function'); | ||
it('should work with no dependencies', async () => { | ||
const dependencies = await $.run(['$dispose']); | ||
assert.equal(typeof dependencies.$dispose, 'function'); | ||
return dependencies.$dispose(); | ||
}) | ||
.then(() => done()) | ||
.catch(done); | ||
return dependencies.$dispose(); | ||
}); | ||
it('should work with constant dependencies', done => { | ||
it('should work with constant dependencies', async () => { | ||
$.constant('ENV', ENV); | ||
$.constant('time', time); | ||
$.run(['time', 'ENV', '$dispose']) | ||
.then(dependencies => { | ||
assert.deepEqual(Object.keys(dependencies), [ | ||
'time', | ||
'ENV', | ||
'$dispose', | ||
]); | ||
const dependencies = await $.run(['time', 'ENV', '$dispose']); | ||
assert.deepEqual(Object.keys(dependencies), ['time', 'ENV', '$dispose']); | ||
return dependencies.$dispose(); | ||
}) | ||
.then(() => done()) | ||
.catch(done); | ||
await dependencies.$dispose(); | ||
}); | ||
it('should work with simple dependencies', done => { | ||
it('should work with simple dependencies', async () => { | ||
$.constant('ENV', ENV); | ||
@@ -738,17 +911,9 @@ $.constant('time', time); | ||
$.run(['time', 'hash', '$dispose']) | ||
.then(dependencies => { | ||
assert.deepEqual(Object.keys(dependencies), [ | ||
'time', | ||
'hash', | ||
'$dispose', | ||
]); | ||
const dependencies = await $.run(['time', 'hash', '$dispose']); | ||
assert.deepEqual(Object.keys(dependencies), ['time', 'hash', '$dispose']); | ||
return dependencies.$dispose(); | ||
}) | ||
.then(() => done()) | ||
.catch(done); | ||
await dependencies.$dispose(); | ||
}); | ||
it('should work with deeper dependencies', done => { | ||
it('should work with deeper dependencies', async () => { | ||
let shutdownCallResolve; | ||
@@ -787,25 +952,25 @@ let shutdownResolve; | ||
$.run(['hash5', 'time', '$dispose', 'shutdownChecker']) | ||
.then(dependencies => { | ||
assert.deepEqual(Object.keys(dependencies), [ | ||
'hash5', | ||
'time', | ||
'$dispose', | ||
'shutdownChecker', | ||
]); | ||
const dependencies = await $.run([ | ||
'hash5', | ||
'time', | ||
'$dispose', | ||
'shutdownChecker', | ||
]); | ||
assert.deepEqual(Object.keys(dependencies), [ | ||
'hash5', | ||
'time', | ||
'$dispose', | ||
'shutdownChecker', | ||
]); | ||
shutdownCallPromise | ||
.then(() => { | ||
assert.deepEqual(shutdownStub.args, [[]]); | ||
shutdownResolve(); | ||
}) | ||
.catch(done); | ||
const finalPromise = shutdownCallPromise.then(() => { | ||
assert.deepEqual(shutdownStub.args, [[]]); | ||
shutdownResolve(); | ||
}); | ||
return dependencies.$dispose(); | ||
}) | ||
.then(done) | ||
.catch(done); | ||
await dependencies.$dispose(); | ||
await finalPromise; | ||
}); | ||
it('should work with deeper multi used dependencies', done => { | ||
it('should work with deeper multi used dependencies', async () => { | ||
let shutdownCallResolve; | ||
@@ -840,25 +1005,25 @@ let shutdownResolve; | ||
$.run(['hash1', 'hash2', '$dispose', 'shutdownChecker']) | ||
.then(dependencies => { | ||
assert.deepEqual(Object.keys(dependencies), [ | ||
'hash1', | ||
'hash2', | ||
'$dispose', | ||
'shutdownChecker', | ||
]); | ||
const dependencies = await $.run([ | ||
'hash1', | ||
'hash2', | ||
'$dispose', | ||
'shutdownChecker', | ||
]); | ||
assert.deepEqual(Object.keys(dependencies), [ | ||
'hash1', | ||
'hash2', | ||
'$dispose', | ||
'shutdownChecker', | ||
]); | ||
shutdownCallPromise | ||
.then(() => { | ||
assert.deepEqual(shutdownStub.args, [[]]); | ||
shutdownResolve(); | ||
}) | ||
.catch(done); | ||
const finalPromise = shutdownCallPromise.then(() => { | ||
assert.deepEqual(shutdownStub.args, [[]]); | ||
shutdownResolve(); | ||
}); | ||
return dependencies.$dispose(); | ||
}) | ||
.then(() => done()) | ||
.catch(done); | ||
await dependencies.$dispose(); | ||
await finalPromise; | ||
}); | ||
it('should delay service shutdown to their deeper dependencies', done => { | ||
it('should delay service shutdown to their deeper dependencies', async () => { | ||
const servicesShutdownCalls = sinon.spy(() => Promise.resolve()); | ||
@@ -891,19 +1056,14 @@ | ||
$.run(['hash2', '$dispose']) | ||
.then(dependencies => { | ||
assert.deepEqual(Object.keys(dependencies), ['hash2', '$dispose']); | ||
return dependencies.$dispose(); | ||
}) | ||
.then(() => { | ||
assert.deepEqual(servicesShutdownCalls.args, [ | ||
['hash2'], | ||
['hash1'], | ||
['hash'], | ||
]); | ||
}) | ||
.then(() => done()) | ||
.catch(done); | ||
const dependencies = await $.run(['hash2', '$dispose']); | ||
assert.deepEqual(Object.keys(dependencies), ['hash2', '$dispose']); | ||
await dependencies.$dispose(); | ||
assert.deepEqual(servicesShutdownCalls.args, [ | ||
['hash2'], | ||
['hash1'], | ||
['hash'], | ||
]); | ||
}); | ||
it('should not shutdown singleton dependencies if used elsewhere', done => { | ||
it('should not shutdown singleton dependencies if used elsewhere', async () => { | ||
$.constant('ENV', ENV); | ||
@@ -915,20 +1075,14 @@ $.constant('time', time); | ||
$.run(['time', 'hash']) | ||
.then(dependencies => { | ||
const { hash } = dependencies; | ||
const { hash } = await $.run(['time', 'hash']); | ||
const dependencies = await $.run(['time', 'hash', '$dispose']); | ||
return $.run(['time', 'hash', '$dispose']).then(dependencies => { | ||
assert.equal(dependencies.hash, hash); | ||
return dependencies.$dispose().then(() => | ||
$.run(['time', 'hash']).then(dependencies => { | ||
assert.equal(dependencies.hash, hash); | ||
}), | ||
); | ||
}); | ||
}) | ||
.then(() => done()) | ||
.catch(done); | ||
assert.equal(dependencies.hash, hash); | ||
await dependencies.$dispose(); | ||
const newDependencies = await $.run(['time', 'hash']); | ||
assert.equal(newDependencies.hash, hash); | ||
}); | ||
it('should shutdown singleton dependencies if not used elsewhere', done => { | ||
it('should shutdown singleton dependencies if not used elsewhere', async () => { | ||
$.constant('ENV', ENV); | ||
@@ -940,14 +1094,8 @@ $.constant('time', time); | ||
$.run(['time', 'hash', '$dispose']) | ||
.then(dependencies => { | ||
const { hash } = dependencies; | ||
const { hash, $dispose } = await $.run(['time', 'hash', '$dispose']); | ||
return dependencies.$dispose().then(() => | ||
$.run(['time', 'hash']).then(dependencies => { | ||
assert.notEqual(dependencies.hash, hash); | ||
}), | ||
); | ||
}) | ||
.then(() => done()) | ||
.catch(done); | ||
await $dispose(); | ||
const dependencies = await $.run(['time', 'hash']); | ||
assert.notEqual(dependencies.hash, hash); | ||
}); | ||
@@ -954,0 +1102,0 @@ }); |
@@ -55,5 +55,7 @@ import YError from 'yerror'; | ||
export function wrapInitializer(wrapper, baseInitializer) { | ||
return reuseSpecialProps(baseInitializer, services => | ||
baseInitializer(services).then(wrapper.bind(null, services)), | ||
); | ||
return reuseSpecialProps(baseInitializer, async services => { | ||
const baseInstance = await baseInitializer(services); | ||
return wrapper(services, baseInstance); | ||
}); | ||
} | ||
@@ -60,0 +62,0 @@ |
@@ -66,4 +66,4 @@ import assert from 'assert'; | ||
describe('wrapInitializer', done => { | ||
it('should work', () => { | ||
describe('wrapInitializer', () => { | ||
it('should work', async () => { | ||
function baseInitializer() { | ||
@@ -85,9 +85,5 @@ return Promise.resolve(() => 'test'); | ||
newInitializer({ log }) | ||
.then(service => { | ||
assert.equal(service(), 'test-wrapped'); | ||
assert.deepEqual(log.args, [['Wrapping...']]); | ||
}) | ||
.then(done) | ||
.catch(done); | ||
const service = await newInitializer({ log }); | ||
assert.equal(service(), 'test-wrapped'); | ||
assert.deepEqual(log.args, [['Wrapping...']]); | ||
}); | ||
@@ -211,3 +207,3 @@ }); | ||
describe('handler', () => { | ||
it('should work', () => { | ||
it('should work', async () => { | ||
const injectedServices = ['kikooo', 'lol']; | ||
@@ -223,10 +219,8 @@ const services = { | ||
return theInitializer(services) | ||
.then(theHandler => theHandler('test')) | ||
.then(result => | ||
assert.deepEqual(result, { | ||
deps: services, | ||
args: ['test'], | ||
}), | ||
); | ||
const theHandler = await theInitializer(services); | ||
const result = await theHandler('test'); | ||
assert.deepEqual(result, { | ||
deps: services, | ||
args: ['test'], | ||
}); | ||
@@ -233,0 +227,0 @@ function sampleHandler(deps, ...args) { |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
255281
5755
918