Security News
GitHub Removes Malicious Pull Requests Targeting Open Source Repositories
GitHub removed 27 malicious pull requests attempting to inject harmful code across multiple open source repositories, in another round of low-effort attacks.
knifecycle
Advanced tools
Manage your NodeJS processes's lifecycle.
Most (maybe all) applications rely on two kinds of dependencies.
The code dependencies are fully covered by require/system modules in a
testable manner (with mockery
or System
directly). There is no need for
another dependency management system if those libraries are pure functions
(involve no global states at all).
Unfortunately, applications often rely on global states where the JavaScript
module system shows its limits. This is where knifecycle
enters the game.
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.
$inject
service à la Angular to allow accessing existing services references.Using Knifecycle is all about declaring the services our application need. Some of them are simple constants:
// services/core.js
// Core services that are often needed. The constant decorator allows you to
// declare values or simple functions managing global states
// 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
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));
While others are services that may depend on higher level ones. By example a logger.
// services/logger.js
// A log service that depends on the process environment
import { depends, service } from 'knifecycle/instance';
import Logger from 'logger';
// Register a service with the service method.
// A service function returns a service promise
service('logger',
// Declare the service dependencies with the depends decorator
depends(['ENV'],
function logService({ ENV }) {
let logger = new Logger({
logFile: ENV.LOGFILE,
});
logger.log('info', 'Log service initialized!');
return Promise.resolve(logger);
}
)
);
Let's add a db service too:
// services/db.js
import { depends, provider } from 'knifecycle/instance';
import MongoClient from 'mongodb';
// Register a service with the provider method.
// A service provider returns a service descriptor promise exposing:
// - a mandatory service property containing the actual service
// - an optional shutdown function allowing to gracefully close the service
// - an optional error promise to handle the service failure
provider('db',
// Declare the service dependencies with the depends decorator
depends(['ENV', 'logger'],
function dbProvider({ ENV, logger }) {
return MongoClient.connect(ENV.DB_URI)
.then(function(db) {
let fatalErrorPromise = new Promise((resolve, reject) {
db.once('error', reject);
});
logger.log('info', 'db service initialized!');
return {
servicePromise: db,
shutdownProvider: db.close.bind(db, true),
errorPromise: fatalErrorPromise,
};
});
})
);
Adding an Express server
// services/server.js
import { depends, constant, provider, service } from 'knifecycle/instance';
import express from 'express';
// Create an express app
constant('app', express());
// Setting a route to serve the current timestamp.
service('routes/time',
depends('app', 'now', 'logger',
function timeRoutesProvider() {
return Promise.resolve()
.then(() => {
app.get('/time', (req, res, next) => {
const curTime = now();
logger.log('info', 'Sending the current time:', curTime);
res.status(200).send(curTime);
});
});
})
);
// Add an HTTP server service
provider('server',
depends(['app', 'routes/time', 'logger', 'ENV'],
function serverProvider({ app, logger, ENV }) {
return new Promise((resolve, reject) => {
app.listen(ENV.PORT, (server) => {
logger.log('info', 'server listening on port ' + ENV.PORT + '!');
resolve(server);
});
}).then(function(server) {
let fatalErrorPromise = new Promise((resolve, reject) {
db.once('error', reject);
});
function shutdownServer() {
return new Promise((resolve, reject) => {
server.close((err) => {
if(err) {
reject(err);
return;
}
resolve();
})
});
}
return {
servicePromise: Promise.resolve(server),
shutdownProvider: shutdownServer,
errorPromise: fatalErrorPromise,
};
});
})
);
Let's wire it altogether to bootstrap an express application:
// app.js
import { run } from 'knifecycle/instance';
import * from './services/core';
import * from './services/log';
import * from './services/db';
import * from './services/server';
// At this point, nothing is running. To instanciate services, we have to create
// an execution silo using them
// Note that we required the $shutdown service implicitly created by knifecycle
run(['server', 'waitSignal', 'exit', '$shutdown'])
function main({ waitSignal, exit, $shutdown }) {
// We want to exit gracefully when a SIG_TERM/INT signal is received
Promise.any([
waitSignal('SIGINT'),
waitSignal('SIGTERM'),
])
// The shutdown service will disable silos progressively and then the services
// they rely on to finally resolve the returned promise once done
.then($shutdown)
.then(() => {
// graceful shutdown was successful let's exit in peace
process.exit(0);
})
.catch((err) => {
console.error('Could not exit gracefully:', err);
process.exit(1);
});
}
Simply use the DEBUG env var by setting it to 'knifecycle':
DEBUG=knifecycle npm t
This library is already used by the microservices i am working on at 7Digital but I plan to use it with the Trip Story 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:
depends
, constant
, service
, provider
may become decorators;I'll also share most of my own services/providers and their stubs/mocks in order to let you reuse it through your projects easily.
Knifecycle
Returns a Knifecycle instance (always the same)
function
Register a constant service
function
Register a service
Promise
Register a service provider
function
Decorator to claim that a service depends on others ones.
Promise
Creates a new execution silo
Promise
Initialize or return a service descriptor
Promise
Initialize a service
Promise
Initialize a service dependencies
Knifecycle
Returns a Knifecycle instance (always the same)
Kind: global function
Returns: Knifecycle
- The created/saved instance
Example
import Knifecycle from 'knifecycle'
const $ = Knifecycle.getInstance();
function
Register a constant service
Kind: global function
Returns: function
- The created service provider
Param | Type | Description |
---|---|---|
constantName | String | The name of the service |
constantValue | any | The constant value |
Example
import Knifecycle from 'knifecycle'
const $ = new Knifecycle();
$.constant('ENV', process.env); // Expose the process env
$.constant('time', Date.now.bind(Date)); // Expose a time() function
function
Register a service
Kind: global function
Returns: function
- The created service provider
Param | Type | Description |
---|---|---|
serviceName | String | Service name |
service | function | Promise | The service promise or a function returning it |
Example
import Knifecycle from 'knifecycle'
import fs from 'fs';
const $ = new Knifecycle();
$.service('config', function config() {
return new Promise((resolve, reject) {
fs.readFile('config.js', function(err, data) {
let config;
if(err) {
return reject(err);
}
try {
config = JSON.parse(data.toString);
} catch (err) {
return reject(err);
}
resolve({
service: config,
});
});
});
Promise
Register a service provider
Kind: global function
Returns: Promise
- The actual service descriptor promise
Param | Type | Description |
---|---|---|
serviceName | String | Service name |
serviceProvider | function | Service provider or a service provider promise |
Example
import Knifecycle from 'knifecycle'
import fs from 'fs';
const $ = new Knifecycle();
$.provider('config', function configProvider() {
return Promise.resolve({
servicePromise: new Promise((resolve, reject) {
fs.readFile('config.js', function(err, data) {
let config;
if(err) {
return reject(err);
}
try {
config = JSON.parse(data.toString);
} catch (err) {
return reject(err);
}
resolve({
service: config,
});
});
});
});
});
function
Decorator to claim that a service depends on others ones.
Kind: global function
Returns: function
- Returns the decorator function
Param | Type | Description |
---|---|---|
dependenciesNames | Array.<String> | Dependencies the decorated service provider depends on. |
serviceProvider | function | Service provider or a service provider promise |
Example
import Knifecycle from 'knifecycle'
import fs from 'fs';
const $ = new Knifecycle();
$.service('config', $.depends(['ENV'], function configProvider({ ENV }) {
return new Promise((resolve, reject) {
fs.readFile(ENV.CONFIG_FILE, function(err, data) {
let config;
if(err) {
return reject(err);
}
try {
config = JSON.parse(data.toString);
} catch (err) {
return reject(err);
}
resolve({
service: config,
});
});
});
}));
Promise
Creates a new execution silo
Kind: global function
Returns: Promise
- Service descriptor promise Returns the decorator function
Param | Type | Description |
---|---|---|
dependenciesNames | Array.<String> | Service name. |
Example
import Knifecycle from 'knifecycle'
const $ = new Knifecycle();
$.constant('ENV', process.env);
$.run(['ENV'])
.then(({ ENV }) => {
// Here goes your code
})
Promise
Initialize or return a service descriptor
Kind: global function
Returns: Promise
- Service dependencies hash promise.
Param | Type | Description |
---|---|---|
siloContext | Object | Current execution silo context |
injectOnly | Boolean | Flag indicating if existing services only should be used |
serviceName | String | Service name. |
serviceProvider | String | Service provider. |
Promise
Initialize a service
Kind: global function
Returns: Promise
- Service dependencies hash promise.
Param | Type | Description |
---|---|---|
siloContext | Object | Current execution silo context |
serviceName | String | Service name. |
serviceProvider | String | Service provider. |
Promise
Initialize a service dependencies
Kind: global function
Returns: Promise
- Service dependencies hash promise.
Param | Type | Default | Description |
---|---|---|---|
siloContext | Object | Current execution silo siloContext | |
serviceName | String | Service name. | |
servicesNames | String | Dependencies names. | |
injectOnly | Boolean | false | Flag indicating if existing services only should be used |
FAQs
Manage your NodeJS processes's lifecycle automatically with an unobtrusive dependency injection implementation.
The npm package knifecycle receives a total of 483 weekly downloads. As such, knifecycle popularity was classified as not popular.
We found that knifecycle demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
GitHub removed 27 malicious pull requests attempting to inject harmful code across multiple open source repositories, in another round of low-effort attacks.
Security News
RubyGems.org has added a new "maintainer" role that allows for publishing new versions of gems. This new permission type is aimed at improving security for gem owners and the service overall.
Security News
Node.js will be enforcing stricter semver-major PR policies a month before major releases to enhance stability and ensure reliable release candidates.