Socket
Socket
Sign inDemoInstall

knifecycle

Package Overview
Dependencies
Maintainers
1
Versions
100
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

knifecycle

Manage your NodeJS processes's lifecycle.


Version published
Weekly downloads
894
increased by49.75%
Maintainers
1
Weekly downloads
 
Created
Source

knifecycle

Manage your NodeJS processes's lifecycle.

NPM version Build Status Dependency Status devDependency Status Coverage Status Code Climate Dependency Status

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 show 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.

Features

  • services management: start services taking their dependencies in count and shut them down the same way to gracefully exit.
  • easy end to end testing: just replace your services per your own mocks and stubs.
  • 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 problem within purely functional libraries (require allows it), it may be harmful for your services, knifecycle impeach that.

Usage

First we create a Knifecycle instance:

// 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:

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

// 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';
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';
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';
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 './services/knifecycle';
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 finaly resolve the returned promise when 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);
  });

}

Debugging

Simply use the DEBUG env var by setting it to 'knifecycle':

DEBUG=knifecycle npm t

Plans

Use this lib for real world applications. I plan to use it with the Trip Story toy project first and use it at work then. Maybe for front-end stuffs 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

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.

Functions

getInstance()Knifecycle

Returns a Knifecycle instance (always the same)

constant(constantName, constantValue)function

Register a constant service

service(serviceName, service)function

Register a service

provider(serviceName, serviceProvider)Promise

Register a service provider

depends(dependenciesNames, serviceProvider)function

Decorator to claim that a service depends on others ones.

run(dependenciesNames)Promise

Creates a new execution silo

_getServiceDescriptor(siloContext, serviceName, serviceProvider)Promise

Initialize or return a service descriptor

_initializeServiceDescriptor(siloContext, serviceName, serviceProvider)Promise

Initialize a service

_initializeDependencies(siloContext, serviceName, servicesNames)Promise

Initialize a service dependencies

getInstance() ⇒ Knifecycle

Returns a Knifecycle instance (always the same)

Kind: global function
Returns: Knifecycle - The created/saved instance
Example

import Knifecycle from 'sf-knifecycle'

const $ = Knifecycle.getInstance();

constant(constantName, constantValue) ⇒ function

Register a constant service

Kind: global function
Returns: function - The created service provider

ParamTypeDescription
constantNameStringThe name of the service
constantValueanyThe constant value

Example

import Knifecycle from 'sf-knifecycle'

const $ = new Knifecycle();

$.constant('ENV', process.env); // Expose the process env
$.constant('time', Date.now.bind(Date)); // Expose a time() function

service(serviceName, service) ⇒ function

Register a service

Kind: global function
Returns: function - The created service provider

ParamTypeDescription
serviceNameStringService name
servicefunction | PromiseThe service promise or a function returning it

Example

import Knifecycle from 'sf-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,
    });
  });
});

provider(serviceName, serviceProvider) ⇒ Promise

Register a service provider

Kind: global function
Returns: Promise - The actual service descriptor promise

ParamTypeDescription
serviceNameStringService name
serviceProviderfunctionService provider or a service provider promise

Example

import Knifecycle from 'sf-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,
        });
      });
    });
  });
});

depends(dependenciesNames, serviceProvider) ⇒ function

Decorator to claim that a service depends on others ones.

Kind: global function
Returns: function - Returns the decorator function
$.depends(['env']): $.service('config', 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, }); }); }); });

ParamTypeDescription
dependenciesNamesArray.<String>Dependencies the decorated service provider depends on.
serviceProviderfunctionService provider or a service provider promise

Example

import Knifecycle from 'knifecycle'
import fs from 'fs';

const $ = new Knifecycle();

run(dependenciesNames) ⇒ Promise

Creates a new execution silo

Kind: global function
Returns: Promise - Service descriptor promise.

ParamTypeDescription
dependenciesNamesArray.<String>Service name.

_getServiceDescriptor(siloContext, serviceName, serviceProvider) ⇒ Promise

Initialize or return a service descriptor

Kind: global function
Returns: Promise - Service dependencies hash promise.

ParamTypeDescription
siloContextObjectCurrent execution silo context
serviceNameStringService name.
serviceProviderStringService provider.

_initializeServiceDescriptor(siloContext, serviceName, serviceProvider) ⇒ Promise

Initialize a service

Kind: global function
Returns: Promise - Service dependencies hash promise.

ParamTypeDescription
siloContextObjectCurrent execution silo context
serviceNameStringService name.
serviceProviderStringService provider.

_initializeDependencies(siloContext, serviceName, servicesNames) ⇒ Promise

Initialize a service dependencies

Kind: global function
Returns: Promise - Service dependencies hash promise.

ParamTypeDescription
siloContextObjectCurrent execution silo siloContext
serviceNameStringService name.
servicesNamesStringDependencies names.

Keywords

FAQs

Package last updated on 27 Aug 2016

Did you know?

Socket

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.

Install

Related posts

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