knifecycle
Advanced tools
Comparing version 1.0.5 to 1.1.0
@@ -25,2 +25,3 @@ 'use strict'; | ||
var SHUTDOWN = '$shutdown'; | ||
var INJECT = '$inject'; | ||
var FATAL_ERROR = '$fatalError'; | ||
@@ -31,2 +32,3 @@ var E_UNMATCHED_DEPENDENCY = 'E_UNMATCHED_DEPENDENCY'; | ||
var E_BAD_SERVICE_PROMISE = 'E_BAD_SERVICE_PROMISE'; | ||
var E_BAD_INJECTION = 'E_BAD_INJECTION'; | ||
@@ -245,3 +247,6 @@ // Constants that should use Symbol whenever possible | ||
value: function run(dependenciesNames) { | ||
var _this2 = this; | ||
var siloContext = { | ||
name: 'silo-' + Date.now(), | ||
servicesDescriptors: new Map(), | ||
@@ -298,3 +303,10 @@ servicesSequence: [], | ||
return this._initializeDependencies(siloContext, 'silo', dependenciesNames).then(function (servicesHash) { | ||
// Create a provider for the special inject service | ||
siloContext.servicesDescriptors.set(INJECT, { | ||
servicePromise: Promise.resolve(function (dependenciesNames) { | ||
return _this2._initializeDependencies(siloContext, siloContext.name, dependenciesNames, true); | ||
}) | ||
}); | ||
return this._initializeDependencies(siloContext, siloContext.name, dependenciesNames).then(function (servicesHash) { | ||
debug('Handling fatal errors:', siloContext.errorsPromises); | ||
@@ -309,2 +321,3 @@ Promise.all(siloContext.errorsPromises).catch(siloContext.throwFatalError); | ||
* @param {Object} siloContext Current execution silo context | ||
* @param {Boolean} injectOnly Flag indicating if existing services only should be used | ||
* @param {String} serviceName Service name. | ||
@@ -317,3 +330,3 @@ * @param {String} serviceProvider Service provider. | ||
key: '_getServiceDescriptor', | ||
value: function _getServiceDescriptor(siloContext, serviceName) { | ||
value: function _getServiceDescriptor(siloContext, injectOnly, serviceName) { | ||
var serviceDescriptor = siloContext.servicesDescriptors.get(serviceName); | ||
@@ -325,2 +338,9 @@ | ||
// The inject service is intended to be used as a workaround for unavoidable | ||
// circular dependencies. It wouldn't make sense to instanciate new services | ||
// at this level so throwing an error | ||
if (injectOnly) { | ||
return Promise.reject(new _yerror2.default(E_BAD_INJECTION, serviceName)); | ||
} | ||
return this._initializeServiceDescriptor(siloContext, serviceName); | ||
@@ -385,2 +405,3 @@ } | ||
* @param {String} servicesNames Dependencies names. | ||
* @param {Boolean} injectOnly Flag indicating if existing services only should be used | ||
* @return {Promise} Service dependencies hash promise. | ||
@@ -392,7 +413,9 @@ */ | ||
value: function _initializeDependencies(siloContext, serviceName, servicesNames) { | ||
var _this2 = this; | ||
var _this3 = this; | ||
var injectOnly = arguments.length <= 3 || arguments[3] === undefined ? false : arguments[3]; | ||
debug('Initializing dependencies:', serviceName, servicesNames); | ||
return Promise.resolve().then(function () { | ||
return Promise.all(servicesNames.map(_this2._getServiceDescriptor.bind(_this2, siloContext))).then(function (servicesDescriptors) { | ||
return Promise.all(servicesNames.map(_this3._getServiceDescriptor.bind(_this3, siloContext, injectOnly))).then(function (servicesDescriptors) { | ||
debug('Initialized dependencies descriptors:', serviceName, servicesNames); | ||
@@ -399,0 +422,0 @@ siloContext.servicesSequence.push(servicesNames); |
@@ -294,2 +294,54 @@ 'use strict'; | ||
describe('inject', function () { | ||
it('should work with no dependencies', function (done) { | ||
$.constant('ENV', ENV); | ||
$.constant('time', time); | ||
$.provider('hash', $.depends(['ENV'], hashProvider)); | ||
$.run(['time', 'hash', '$inject']).then(function (dependencies) { | ||
_assert2.default.deepEqual(Object.keys(dependencies), ['time', 'hash', '$inject']); | ||
return dependencies.$inject([]).then(function (injectDependencies) { | ||
_assert2.default.deepEqual(Object.keys(injectDependencies), []); | ||
_assert2.default.deepEqual(injectDependencies, {}); | ||
done(); | ||
}); | ||
}).catch(done); | ||
}); | ||
it('should work with same dependencies then the running silo', function (done) { | ||
$.constant('ENV', ENV); | ||
$.constant('time', time); | ||
$.provider('hash', $.depends(['ENV'], hashProvider)); | ||
$.run(['time', 'hash', '$inject']).then(function (dependencies) { | ||
_assert2.default.deepEqual(Object.keys(dependencies), ['time', 'hash', '$inject']); | ||
return dependencies.$inject(['time', 'hash']).then(function (injectDependencies) { | ||
_assert2.default.deepEqual(Object.keys(injectDependencies), ['time', 'hash']); | ||
_assert2.default.deepEqual(injectDependencies, { | ||
hash: { ENV: ENV }, | ||
time: time | ||
}); | ||
done(); | ||
}); | ||
}).catch(done); | ||
}); | ||
it('should fail with non instanciated dependencies', function (done) { | ||
$.constant('ENV', ENV); | ||
$.constant('time', time); | ||
$.provider('hash', $.depends(['ENV'], hashProvider)); | ||
$.run(['time', '$inject']).then(function (dependencies) { | ||
_assert2.default.deepEqual(Object.keys(dependencies), ['time', '$inject']); | ||
return dependencies.$inject(['time', 'hash']).catch(function (err) { | ||
_assert2.default.equal(err.code, 'E_BAD_INJECTION'); | ||
done(); | ||
}); | ||
}).catch(done); | ||
}); | ||
}); | ||
describe('shutdown', function () { | ||
@@ -296,0 +348,0 @@ |
{ | ||
"name": "knifecycle", | ||
"version": "1.0.5", | ||
"version": "1.1.0", | ||
"description": "Manage your NodeJS processes's lifecycle.", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
@@ -20,45 +20,35 @@ # knifecycle | ||
Unfortunately, applications often rely on **global states** where the JavaScript | ||
module system show its limits. This is where `knifecycle` enters the game. | ||
module system shows its limits. This is where `knifecycle` enters the game. | ||
It is largely inspired from the Angular service system except is should not | ||
provide code but access to global stuffs (time, filesystem, dbs). It also | ||
have an important additional feature to shutdown processes which is less | ||
useful for front-end applications and doesn't exists in Angular. | ||
It is largely inspired by the Angular service system except it should not | ||
provide code but access to global states (time, filesystem, db). It also | ||
have an important additional feature to shutdown processes which is really | ||
useful for back-end servers and doesn't exists in Angular. | ||
## Features | ||
- services management: start services taking their dependencies in count and | ||
shut them down the same way to gracefully exit. | ||
shut them down the same way for graceful exits. | ||
- easy end to end testing: just replace your services per your own mocks and | ||
stubs. | ||
stubs while ensuring your application integrity between testing and production. | ||
- isolation: isolate processing in a clean manner, per concerns. | ||
- functional programming ready: encapsulate global states allowing the rest of | ||
your application to be purely functional. | ||
- no circular dependencies for services: while circular dependencies is not a | ||
- no circular dependencies for services: while circular dependencies are not a | ||
problem within purely functional libraries (require allows it), it may be | ||
harmful for your services, knifecycle impeach that. | ||
harmful for your services, knifecycle impeach that while providing an `$inject` | ||
service à la Angular to allow accessing existing services references. | ||
## Usage | ||
First we create a Knifecycle instance: | ||
Using Knifecycle is all about declaring the services our application need. Some | ||
of them are simple constants: | ||
```js | ||
// services/knifecycle.js | ||
// For this sample application, we know we won't need several lifecycle | ||
// instances so we will use the module singleton instead of injecting the | ||
// lifecycle instance everywhere. | ||
import Knifecycle from 'knifecycle'; | ||
const $ = Knifecycle.getInstance(); | ||
export default $; | ||
``` | ||
Then we create the services our application need. Some of them are simple | ||
constants: | ||
```js | ||
// services/core.js | ||
// Core services that are often needed. The constant decorator allows you to | ||
// declare values or simple functions managing global states | ||
import { constant } from './knifecycle'; | ||
import Winston from 'winston'; | ||
// Notice we are directly using the instance module that prepare the Knifecycle | ||
// instance for us | ||
import { constant } from 'knifecycle/instance'; | ||
// Add the process environment as a simple constant | ||
@@ -84,6 +74,7 @@ constant('ENV', process.env); | ||
logger. | ||
```js | ||
// services/logger.js | ||
// A log service that depends on the process environment | ||
import { depends, service } from './knifecycle'; | ||
import { depends, service } from 'knifecycle/instance'; | ||
import Logger from 'logger'; | ||
@@ -112,3 +103,3 @@ | ||
// services/db.js | ||
import { depends, provider } from './knifecycle'; | ||
import { depends, provider } from 'knifecycle/instance'; | ||
import MongoClient from 'mongodb'; | ||
@@ -143,6 +134,6 @@ | ||
Adding an express server | ||
Adding an Express server | ||
```js | ||
// services/server.js | ||
import { depends, constant, provider, service } from './knifecycle'; | ||
import { depends, constant, provider, service } from 'knifecycle/instance'; | ||
import express from 'express'; | ||
@@ -209,3 +200,3 @@ | ||
import { run } from './services/knifecycle'; | ||
import { run } from 'knifecycle/instance'; | ||
import * from './services/core'; | ||
@@ -227,3 +218,3 @@ import * from './services/log'; | ||
// The shutdown service will disable silos progressively and then the services | ||
// they rely on to finaly resolve the returned promise when done | ||
// they rely on to finally resolve the returned promise once done | ||
.then($shutdown) | ||
@@ -251,13 +242,14 @@ .then(() => { | ||
Use this lib for real world applications. I plan to use it with the | ||
[Trip Story](https://github.com/nfroidure/TripStory) toy project first and use | ||
it at work then. Maybe for front-end stuffs too. | ||
This library is already used by the microservices i am working on at 7Digital | ||
but I plan to use it with the | ||
[Trip Story](https://github.com/nfroidure/TripStory) toy project in order to | ||
illustrate its usage on an open-source project. I think i will also use it for | ||
front-end projects too. | ||
The scope of this library won't change. However the plan is: | ||
- improve performances | ||
- allow to declare singleton services | ||
- use next JavaScript feature that ships to Node if it make sense: | ||
depends, constant, service, provider may become decorators; | ||
WeakMap may be used to share singleton services between runs | ||
- track bugs | ||
- [allow to declare singleton services](https://github.com/nfroidure/knifecycle/issues/3) | ||
- evolve with Node. You will never have to transpile this library to use it with Node. | ||
- `depends`, `constant`, `service`, `provider` may become decorators; | ||
- track bugs ;) | ||
@@ -264,0 +256,0 @@ I'll also share most of my own services/providers and their stubs/mocks in order |
@@ -7,2 +7,3 @@ import YError from 'yerror'; | ||
const SHUTDOWN = '$shutdown'; | ||
const INJECT = '$inject'; | ||
const FATAL_ERROR = '$fatalError'; | ||
@@ -13,2 +14,3 @@ const E_UNMATCHED_DEPENDENCY = 'E_UNMATCHED_DEPENDENCY'; | ||
const E_BAD_SERVICE_PROMISE = 'E_BAD_SERVICE_PROMISE'; | ||
const E_BAD_INJECTION = 'E_BAD_INJECTION'; | ||
@@ -217,2 +219,3 @@ // Constants that should use Symbol whenever possible | ||
const siloContext = { | ||
name: 'silo-' + Date.now(), | ||
servicesDescriptors: new Map(), | ||
@@ -274,3 +277,10 @@ servicesSequence: [], | ||
return this._initializeDependencies(siloContext, 'silo', dependenciesNames) | ||
// Create a provider for the special inject service | ||
siloContext.servicesDescriptors.set(INJECT, { | ||
servicePromise: Promise.resolve(dependenciesNames => | ||
this._initializeDependencies(siloContext, siloContext.name, dependenciesNames, true) | ||
), | ||
}); | ||
return this._initializeDependencies(siloContext, siloContext.name, dependenciesNames) | ||
.then((servicesHash) => { | ||
@@ -286,2 +296,3 @@ debug('Handling fatal errors:', siloContext.errorsPromises); | ||
* @param {Object} siloContext Current execution silo context | ||
* @param {Boolean} injectOnly Flag indicating if existing services only should be used | ||
* @param {String} serviceName Service name. | ||
@@ -291,3 +302,3 @@ * @param {String} serviceProvider Service provider. | ||
*/ | ||
_getServiceDescriptor(siloContext, serviceName) { | ||
_getServiceDescriptor(siloContext, injectOnly, serviceName) { | ||
const serviceDescriptor = siloContext.servicesDescriptors.get(serviceName); | ||
@@ -299,2 +310,9 @@ | ||
// The inject service is intended to be used as a workaround for unavoidable | ||
// circular dependencies. It wouldn't make sense to instanciate new services | ||
// at this level so throwing an error | ||
if(injectOnly) { | ||
return Promise.reject(new YError(E_BAD_INJECTION, serviceName)); | ||
} | ||
return this._initializeServiceDescriptor(siloContext, serviceName); | ||
@@ -367,5 +385,6 @@ } | ||
* @param {String} servicesNames Dependencies names. | ||
* @param {Boolean} injectOnly Flag indicating if existing services only should be used | ||
* @return {Promise} Service dependencies hash promise. | ||
*/ | ||
_initializeDependencies(siloContext, serviceName, servicesNames) { | ||
_initializeDependencies(siloContext, serviceName, servicesNames, injectOnly = false) { | ||
debug('Initializing dependencies:', serviceName, servicesNames); | ||
@@ -375,3 +394,3 @@ return Promise.resolve() | ||
() => Promise.all( | ||
servicesNames.map(this._getServiceDescriptor.bind(this, siloContext)) | ||
servicesNames.map(this._getServiceDescriptor.bind(this, siloContext, injectOnly)) | ||
) | ||
@@ -378,0 +397,0 @@ .then((servicesDescriptors) => { |
@@ -309,2 +309,67 @@ import assert from 'assert'; | ||
describe('inject', () => { | ||
it('should work with no dependencies', (done) => { | ||
$.constant('ENV', ENV); | ||
$.constant('time', time); | ||
$.provider('hash', $.depends(['ENV'], hashProvider)); | ||
$.run(['time', 'hash', '$inject']) | ||
.then((dependencies) => { | ||
assert.deepEqual(Object.keys(dependencies), ['time', 'hash', '$inject']); | ||
return dependencies.$inject([]) | ||
.then((injectDependencies) => { | ||
assert.deepEqual(Object.keys(injectDependencies), []); | ||
assert.deepEqual(injectDependencies, {}); | ||
done(); | ||
}); | ||
}) | ||
.catch(done); | ||
}); | ||
it('should work with same dependencies then the running silo', (done) => { | ||
$.constant('ENV', ENV); | ||
$.constant('time', time); | ||
$.provider('hash', $.depends(['ENV'], hashProvider)); | ||
$.run(['time', 'hash', '$inject']) | ||
.then((dependencies) => { | ||
assert.deepEqual(Object.keys(dependencies), ['time', 'hash', '$inject']); | ||
return dependencies.$inject(['time', 'hash']) | ||
.then((injectDependencies) => { | ||
assert.deepEqual(Object.keys(injectDependencies), ['time', 'hash']); | ||
assert.deepEqual(injectDependencies, { | ||
hash: { ENV }, | ||
time, | ||
}); | ||
done(); | ||
}); | ||
}) | ||
.catch(done); | ||
}); | ||
it('should fail with non instanciated dependencies', (done) => { | ||
$.constant('ENV', ENV); | ||
$.constant('time', time); | ||
$.provider('hash', $.depends(['ENV'], hashProvider)); | ||
$.run(['time', '$inject']) | ||
.then((dependencies) => { | ||
assert.deepEqual(Object.keys(dependencies), ['time', '$inject']); | ||
return dependencies.$inject(['time', 'hash']) | ||
.catch((err) => { | ||
assert.equal(err.code, 'E_BAD_INJECTION'); | ||
done(); | ||
}); | ||
}) | ||
.catch(done); | ||
}); | ||
}); | ||
describe('shutdown', () => { | ||
@@ -311,0 +376,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
80596
11
1593
489