Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

knifecycle

Package Overview
Dependencies
Maintainers
1
Versions
101
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

knifecycle - npm Package Compare versions

Comparing version 2.5.4 to 2.6.0

10

CHANGELOG.md

@@ -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)

90

dist/build.js

@@ -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",

@@ -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>

@@ -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';

`,
);
}));
);
});
});

@@ -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) {

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc