Git Product home page Git Product logo

nfroidure / knifecycle Goto Github PK

View Code? Open in Web Editor NEW
30.0 3.0 2.0 2.93 MB

Manage your NodeJS processes's lifecycle automatically with an unobtrusive dependency injection implementation.

Home Page: https://insertafter.com/en/blog/unobstrusive_dependency_injection_with_knifecycle.html

License: MIT License

TypeScript 100.00%
javascript dependency-injection-container inversion-of-control nodejs function-decorator handler-functions hacktoberfest

knifecycle's Introduction

knifecycle

Manage your NodeJS processes's lifecycle automatically with an unobtrusive dependency injection implementation.

GitHub license Coverage Status

The Knifecycle logo

Most (maybe all) applications rely on two kinds of dependencies.

The lifecycle of most (all?) applications

The code dependencies are fully covered by JavaScript 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.

The app lifecycle sequence graph

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.

Last but not least, you can build your code with Knifecycle so that once in production, it do not have to resolve the dependency tree leading to better performances and reduce the bundle size (especially for tools like AWS Lambda / GCP Functions where each endpoint has its own zip).

You may want to look at the architecture notes to better handle the reasonning behind knifecycle and its implementation.

At this point you may think that a DI system is useless. My advice is that it depends. But at least, you should not make a definitive choice and allow both approaches, Knifecycle permits this, most modules made usable by Knifecycle can in fact be used without it (this is also why static build works). See this blog post for more context about this statement.

Features

  • services management: start services taking their dependencies in count and shut them down the same way for graceful exits (namely dependency injection with inverted control),
  • singleton: maintain singleton services across several running execution silos,
  • easy end to end testing: just replace your services per your own mocks and 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 are not a problem within purely functional libraries (require allows it), it may be harmful for your services, knifecycle impeach that while providing an $injector service à la Angular to allow accessing existing services references if you really need to,
  • generate Mermaid graphs of the dependency tree,
  • auto-detect injected services names,
  • build raw initialization modules to avoid embedding Knifecycle in your builds,
  • optionally autoload services dependencies with custom logic.

You can find all Knifecycle comptabile modules on NPM with the knifecycle keyword.

Usage

Using knifecycle is all about declaring the services our application needs and running your application over it.

Let's say we are building a CLI script. Here is how we would proceed with Knifecycle:

// bin.js
import fs from 'fs';
import { YError } from 'YError';
import {
  Knifecycle,
  initializer,
  constant,
  inject,
  name
} from 'knifecycle';

// 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 env vars in `process.env` to make our code
// easily testable
$.register(constant('ENV', process.env));

// Let's do so for CLI args with another constant
// in real world apps we would have created a service
// that would parse args in a complexer way
$.register(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
// In a real world app, you may use the
// `application-services` module services instead.
async function initConfig({ ENV = { CONFIG_PATH: '.' } }) {
  await fs.promises.readFile(
    ENV.CONFIG_PATH,
    'utf-8',
    (err, data) => {
      if (err) {
        reject(err);
        return;
      }
      try {
        resolve(JSON.parse(data));
      } catch (err) {
        reject(err);
      }
    },
  );
}

// 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
      singleton: true,
    },
    initConfig,
  ),
);

// Our CLI also uses a database so let's write an
// initializer for it (in a real world app you
// can use `postgresql-service` instead):
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',
    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);
    });

    // Logging only if the `log` service is defined
    log && log('info', 'db service initialized!');

    return {
      service: db,
      dispose: db.close.bind(db, true),
      fatalErrorPromise,
    };
  },
);

// Here we are registering our initializer apart to
// be able to reuse it, we also declare the required
// DB_URI constant it needs
$.register(constant('DB_URI', 'posgresql://xxxx'));
$.register(initDB);

// Say we need to use two different DB server
// We can reuse our initializer by tweaking
// some of its properties
$.register(constant('DB_URI2', 'posgresql://yyyy'));
$.register(
  // First we remap the injected dependencies. It will
  // take the `DB_URI2` constant and inject it as
  // `DB_URI`
  inject(
    ['CONFIG', 'DB_URI2>DB_URI', '?log'],
    // Then we override its name to make it
    // available as a different service
    name('db2', initDB),
  ),
);

// 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
$.register(constant('now', Date.now.bind(Date)))
  .register(constant('log', console.log.bind(console)))
  .register(constant('exit', process.exit.bind(process)));

// 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'],
      // Note that the auto loader must be a singleton
      singleton: true,
    },
    async ({ CONFIG, ARGS }) =>
      async (serviceName) => {
        if ('command' !== serviceName) {
          // Allows to signal that the dependency is not found
          // so that optional dependencies doesn't impeach the
          // injector to resolve the dependency tree
          throw new YError('E_UNMATCHED_DEPENDENCY', serviceName);
        }
        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 `$instance` service
// implicitly created by `knifecycle`
$.run(['command', '$instance', 'exit', 'log'])
  // Here, command contains the initializer eventually
  // found by automatically loading a NodeJS module
  // in the above `$autoload` service. The db connection
  // will only be instanciated if that command needs it
  .then(async ({ command, $instance, exit, log }) => {
    try {
      command();

      log('It worked!');
    } catch (err) {
      log('It failed!', err);
    } finally {
      // Here we ensure every db connections are closed
      // properly. We could have use `$.destroy()` the same
      // way but this is to illustrate that the Knifecycle
      // instance can be injected in services contexts
      // (rarely done but good to know it exists)
      await $instance.destroy().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:

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:

// commands/mycommand.js
import { initializer } from './dist';

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

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

Auto detection

Knifecycle also provide some utility function to automatically assign the initializer property declarations, the following 3 ways to declare the getUser service are equivalent:

import noop from 'noop';
import { autoInject, inject, initializer, autoService } from 'knifecycle';

initializer({
  name: 'getUser',
  inject: ['db', '?log'],
  type: 'service',
}, getUser);

service('getUser', autoInject(getUser)));

autoService(getUser);

async function getUser({ db, log = noop}) {}

That said, if you need to build your code with webpack/babel you may have to convert auto-detections to raw declarations with the babel-plugin-knifecycle plugin. You can also do this only for the performance improvements it brings.

Also, keep in mind that the auto-detection is based on a simple regular expression so you should care to keep initializer signatures simple to avoid having a E_AUTO_INJECTION_FAILURE error. As a rule of thumb, avoid setting complex default values.

// Won't work
autoInject(async ({ log = () => {} }) => {});

// Will work
function noop() {}
autoInject(async ({ log = noop }) => {});

Debugging

Simply use the DEBUG environment variable by setting it to 'knifecycle':

DEBUG=knifecycle npm t

The output is very verbose but lead to a deep understanding of mechanisms that take place under the hood.

Plans

The scope of this library won't change. However the plan is:

  • improve performances;
  • track bugs ;).

I'll also share most of my own initializers and their stubs/mocks in order to let you reuse it through your projects easily. Here are the current projects that use this DI lib:

Notice that those modules remains usable without using Knifecycle at all which is maybe the best feature of this library 😉.

API

Classes

Knifecycle

Functions

initInitializerBuilder(services)Promise.<function()>

Instantiate the initializer builder service

constant(name, value)function

Decorator that creates an initializer for a constant value

service(serviceBuilder, [name], [dependencies], [singleton], [extra])function

Decorator that creates an initializer from a service builder

autoService(serviceBuilder)function

Decorator that creates an initializer from a service builder by automatically detecting its name and dependencies

provider(providerBuilder, [name], [dependencies], [singleton], [extra])function

Decorator that creates an initializer for a provider builder

autoProvider(providerBuilder)function

Decorator that creates an initializer from a provider builder by automatically detecting its name and dependencies

handler(handlerFunction, [name], [dependencies], [options])function

Shortcut to create an initializer with a simple handler

autoHandler(handlerFunction)function

Allows to create an initializer with a simple handler automagically

parseDependencyDeclaration(dependencyDeclaration)Object

Explode a dependency declaration an returns its parts.

stringifyDependencyDeclaration(dependencyDeclarationParts)String

Stringify a dependency declaration from its parts.

Knifecycle

Kind: global class

new Knifecycle(options)

Create a new Knifecycle instance

Returns: Knifecycle - The Knifecycle instance

Param Type Description
options Object An object with options
options.sequential boolean Allows to load dependencies sequentially (usefull for debugging)

Example

import Knifecycle from 'knifecycle'

const $ = new Knifecycle();

knifecycle.register(initializer) ⇒ Knifecycle

Register an initializer

Kind: instance method of Knifecycle
Returns: Knifecycle - The Knifecycle instance (for chaining)

Param Type Description
initializer function An initializer

knifecycle.toMermaidGraph(options) ⇒ String

Outputs a Mermaid compatible dependency graph of the declared services. See Mermaid docs

Kind: instance method of Knifecycle
Returns: String - Returns a string containing the Mermaid dependency graph

Param Type Description
options Object Options for generating the graph (destructured)
options.shapes Array.<Object> Various shapes to apply
options.styles Array.<Object> Various styles to apply
options.classes Object A hash of various classes contents

Example

import Knifecycle, { inject, constant, service } from 'knifecycle';
import appInitializer from './app';

const $ = new Knifecycle();

$.register(constant('ENV', process.env));
$.register(constant('OS', require('os')));
$.register(service('app', inject(['ENV', 'OS'], appInitializer)));
$.toMermaidGraph();

// returns
graph TD
  app-->ENV
  app-->OS

knifecycle.run(dependenciesDeclarations) ⇒ Promise

Creates a new execution silo

Kind: instance method of Knifecycle
Returns: Promise - Service descriptor promise

Param Type Description
dependenciesDeclarations Array.<String> Service name.

Example

import Knifecycle, { constant } from 'knifecycle'

const $ = new Knifecycle();

$.register(constant('ENV', process.env));
$.run(['ENV'])
.then(({ ENV }) => {
 // Here goes your code
})

knifecycle.destroy() ⇒ Promise

Destroy the Knifecycle instance

Kind: instance method of Knifecycle
Returns: Promise - Full destruction promise
Example

import Knifecycle, { constant } from 'knifecycle'

const $ = new Knifecycle();

$.register(constant('ENV', process.env));
$.run(['ENV'])
.then(({ ENV }) => {
   // Here goes your code

   // Finally destroy the instance
   $.destroy()
})

initInitializerBuilder(services) ⇒ Promise.<function()>

Instantiate the initializer builder service

Kind: global function
Returns: Promise.<function()> - A promise of the buildInitializer function

Param Type Description
services Object The services to inject
services.$autoload Object The dependencies autoloader

Example

import initInitializerBuilder from 'knifecycle/dist/build';

const buildInitializer = await initInitializerBuilder({
  $autoload: async () => {},
});

initInitializerBuilder~buildInitializer(dependencies) ⇒ Promise.<String>

Create a JavaScript module that initialize a set of dependencies with hardcoded import/awaits.

Kind: inner method of initInitializerBuilder
Returns: Promise.<String> - The JavaScript module content

Param Type Description
dependencies Array.<String> The main dependencies

Example

import initInitializerBuilder from 'knifecycle/dist/build';

const buildInitializer = await initInitializerBuilder({
  $autoload: async () => {},
});

const content = await buildInitializer(['entryPoint']);

constant(name, value) ⇒ function

Decorator that creates an initializer for a constant value

Kind: global function
Returns: function - Returns a new constant initializer

Param Type Description
name String The constant's name.
value any The constant's value

Example

import Knifecycle, { constant, service } from 'knifecycle';

const { printAnswer } = new Knifecycle()
  .register(constant('THE_NUMBER', value))
  .register(constant('log', console.log.bind(console)))
  .register(service(
    async ({ THE_NUMBER, log }) => () => log(THE_NUMBER),
    'printAnswer',
    ['THE_NUMBER', 'log'],
  ))
  .run(['printAnswer']);

printAnswer(); // 42

service(serviceBuilder, [name], [dependencies], [singleton], [extra]) ⇒ function

Decorator that creates an initializer from a service builder

Kind: global function
Returns: function - Returns a new initializer

Param Type Description
serviceBuilder function An async function to build the service
[name] String The service's name
[dependencies] Array.<String> The service's injected dependencies
[singleton] Boolean Whether the service is a singleton or not
[extra] any Eventual extra informations

Example

import Knifecycle, { constant, service } from 'knifecycle';

const { printAnswer } = new Knifecycle()
  .register(constant('THE_NUMBER', value))
  .register(constant('log', console.log.bind(console)))
  .register(service(
    async ({ THE_NUMBER, log }) => () => log(THE_NUMBER),
    'printAnswer',
    ['THE_NUMBER', 'log'],
    true
  ))
  .run(['printAnswer']);

printAnswer(); // 42

autoService(serviceBuilder) ⇒ function

Decorator that creates an initializer from a service builder by automatically detecting its name and dependencies

Kind: global function
Returns: function - Returns a new initializer

Param Type Description
serviceBuilder function An async function to build the service

provider(providerBuilder, [name], [dependencies], [singleton], [extra]) ⇒ function

Decorator that creates an initializer for a provider builder

Kind: global function
Returns: function - Returns a new provider initializer

Param Type Description
providerBuilder function An async function to build the service provider
[name] String The service's name
[dependencies] Array.<String> The service's dependencies
[singleton] Boolean Whether the service is a singleton or not
[extra] any Eventual extra informations

Example

import Knifecycle, { provider } from 'knifecycle'
import fs from 'fs';

const $ = new Knifecycle();

$.register(provider(configProvider, 'config'));

async function configProvider() {
  return new Promise((resolve, reject) {
    fs.readFile('config.js', function(err, data) {
      let config;

      if(err) {
        reject(err);
        return;
      }

      try {
        config = JSON.parse(data.toString);
      } catch (err) {
        reject(err);
        return;
      }

      resolve({
        service: config,
      });
    });
  });
}

autoProvider(providerBuilder) ⇒ function

Decorator that creates an initializer from a provider builder by automatically detecting its name and dependencies

Kind: global function
Returns: function - Returns a new provider initializer

Param Type Description
providerBuilder function An async function to build the service provider

handler(handlerFunction, [name], [dependencies], [options]) ⇒ function

Shortcut to create an initializer with a simple handler

Kind: global function
Returns: function - Returns a new initializer

Param Type Default Description
handlerFunction function The handler function
[name] String The name of the handler. Default to the DI prop if exists
[dependencies] Array.<String> [] The dependencies to inject in it
[options] Object Options attached to the built initializer

Example

import Knifecycle, { handler } from 'knifecycle';

new Knifecycle()
.register(handler(getUser, 'getUser', ['db', '?log']));

const QUERY = `SELECT * FROM users WHERE id=$1`
async function getUser({ db }, userId) {
  const [row] = await db.query(QUERY, userId);

  return row;
}

autoHandler(handlerFunction) ⇒ function

Allows to create an initializer with a simple handler automagically

Kind: global function
Returns: function - Returns a new initializer

Param Type Description
handlerFunction function The handler function

Example

import Knifecycle, { autoHandler } from 'knifecycle';

new Knifecycle()
.register(autoHandler(getUser));

const QUERY = `SELECT * FROM users WHERE id=$1`
async function getUser({ db }, userId) {
  const [row] = await db.query(QUERY, userId);

  return row;
}

parseDependencyDeclaration(dependencyDeclaration) ⇒ Object

Explode a dependency declaration an returns its parts.

Kind: global function
Returns: Object - The various parts of it

Param Type Description
dependencyDeclaration String A dependency declaration string

Example

parseDependencyDeclaration('pgsql>db');
// Returns
{
  serviceName: 'pgsql',
  mappedName: 'db',
  optional: false,
}

stringifyDependencyDeclaration(dependencyDeclarationParts) ⇒ String

Stringify a dependency declaration from its parts.

Kind: global function
Returns: String - The various parts of it

Param Type Description
dependencyDeclarationParts Object A dependency declaration string

Example

stringifyDependencyDeclaration({
  serviceName: 'pgsql',
  mappedName: 'db',
  optional: false,
});

// Returns
'pgsql>db'

Authors

License

MIT

knifecycle's People

Contributors

greenkeeper[bot] avatar jbpionnier avatar nfroidure avatar oupsla avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

Forkers

oupsla

knifecycle's Issues

Export constants that may be useful as `Knifecycle` static properties

Feature description

To be DRY, some knifecycle friendly libs will benefit from those exports while expressing the dependency to Knifecycle in a declarative way.

Use cases

$inject and other $ prefixed props, the optional/mapping symbols, the dependency declaration parser and available errors.

An in-range update of metapak is breaking the build 🚨

The devDependency metapak was updated from 3.1.1 to 3.1.2.

🚨 View failing branch.

This version is covered by your current version range and after updating it in your project the build failed.

metapak is a devDependency of this project. It might not break your production code or affect downstream projects, but probably breaks your build or test tools, which may prevent deploying or publishing.

Status Details
  • continuous-integration/travis-ci/push: The Travis CI build failed (Details).

Commits

The new version differs by 2 commits.

See the full diff

FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

Do not catch common errors for optional module

This commit do not solves every possible error:
8cfcabd

TypeError for example but maybe more errors ain't throwed back and lead to unexpected behaviors.

Maybe check for a single error code instead of white listing a set of common errors... Options to specify the error codes that should be ignored may be the best solution.

An in-range update of jsdoc-to-markdown is breaking the build 🚨

Version 3.0.1 of jsdoc-to-markdown was just published.

Branch Build failing 🚨
Dependency jsdoc-to-markdown
Current Version 3.0.0
Type devDependency

This version is covered by your current version range and after updating it in your project the build failed.

jsdoc-to-markdown is a devDependency of this project. It might not break your production code or affect downstream projects, but probably breaks your build or test tools, which may prevent deploying or publishing.

Status Details
  • dependency-ci Dependencies checked Details
  • continuous-integration/travis-ci/push The Travis CI build failed Details

Commits

The new version differs by 5 commits.

See the full diff

FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

Optional dependencies

Feature request

Feature description

Allow to flag dependencies as optionnal: ?myOptionnalService

Use cases

Optionnal debugging service, optionnal ENV/CONSTANTS.

[x] I will do it

Better debug

Just spend time on debugging the following case:

$.constant('x',  $.depends(['y'], () -> { }));

A simple check for function given to constant with dependencies and a nice error would have saved me some time.

Also, find some time to check bad definitions like this one:

$.service('x',  $.depends(['x'], () -> { }));

Allow to create singleton services

A singleton service instance is available across multiple running silos. It is maintained in a WeakMap in order to avoid spawning multiple instances of a service while ensuring unused services can still be garbage collected.

import { depends, service } from './knifecycle';
import LoggerService from 'logger';

service('logger', depends('stdout', LoggerService), { singleton: true });

Consider checking singleton dependencies are also singletons, not sure it is that important, but with no use case for singleton depending on non-singleton, it may help prevent programming errors.

An in-range update of sinon is breaking the build 🚨

The devDependency sinon was updated from 7.2.5 to 7.2.6.

🚨 View failing branch.

This version is covered by your current version range and after updating it in your project the build failed.

sinon is a devDependency of this project. It might not break your production code or affect downstream projects, but probably breaks your build or test tools, which may prevent deploying or publishing.

Status Details
  • continuous-integration/travis-ci/push: The Travis CI build failed (Details).

Commits

The new version differs by 6 commits.

  • 0468cd4 Update docs/changelog.md and set new release id in docs/_config.yml
  • 7f2c8c2 Add release documentation for v7.2.6
  • 36b99b3 7.2.6
  • 1fc586e Update CHANGELOG.md and AUTHORS for new release
  • c8758fd Upgrade @sinonjs/formatio
  • e24daed Set fake.lastArg to last argument regardless of type

See the full diff

FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

Add a special property for extra infos

Feature description

Currently adding extra infos can be done by adding properties to functions
but it won't be replicated by the various helpers Knifecyle provides.

For that matter, creating an _extra special props could lead to more flexibility.

Use cases

For example, in my Swagger HTTP Router
project, I could add extra informations like OAuth roles, CORS etc... to handlers so that
the router initialisation could reuse it to add some additionnel logic on their basis.

  • I will/have implement the feature

Add chaining?

I am not a big fan of chaining features but since Knifecycle is an OOP thing it may make sense to make it methods chainable.

Create an autoloader?

In order to dynamically load dependencies code, allow to provide an $autoload service to automatically load initializers code could be nice and fit well to embrace SystemJS modules.

An in-range update of metapak-nfroidure is breaking the build 🚨

The devDependency metapak-nfroidure was updated from 7.1.2 to 7.2.0.

🚨 View failing branch.

This version is covered by your current version range and after updating it in your project the build failed.

metapak-nfroidure is a devDependency of this project. It might not break your production code or affect downstream projects, but probably breaks your build or test tools, which may prevent deploying or publishing.

Status Details
  • continuous-integration/travis-ci/push: The Travis CI build could not complete due to an error (Details).

Commits

The new version differs by 3 commits.

  • f58a03d 7.2.0
  • c0a2e8e feat(Jest config): Allow to add project roots
  • 82b25b0 chore(Dependencies): Update dependencies

See the full diff

FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

An in-range update of cz-conventional-changelog is breaking the build 🚨

Version 2.1.0 of cz-conventional-changelog was just published.

Branch Build failing 🚨
Dependency cz-conventional-changelog
Current Version 2.0.0
Type devDependency

This version is covered by your current version range and after updating it in your project the build failed.

cz-conventional-changelog is a devDependency of this project. It might not break your production code or affect downstream projects, but probably breaks your build or test tools, which may prevent deploying or publishing.

Status Details
  • dependency-ci Dependencies checked Details
  • continuous-integration/travis-ci/push The Travis CI build failed Details

Release Notes v2.1.0

<a name"2.1.0">

2.1.0 (2017-10-27)

Features

  • prompt: add confirmation fields + edit for clarity (#58) (d40ac2c5)
Commits

The new version differs by 3 commits.

  • d40ac2c feat(prompt): add confirmation fields + edit for clarity (#58)
  • f7a770e Merge pull request #57 from Epitrochoid/remove-unused-dep
  • 2d1095b Removed unused pad-right dependency

See the full diff

FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

An in-range update of eslint-plugin-prettier is breaking the build 🚨

The devDependency eslint-plugin-prettier was updated from 3.0.1 to 3.1.0.

🚨 View failing branch.

This version is covered by your current version range and after updating it in your project the build failed.

eslint-plugin-prettier is a devDependency of this project. It might not break your production code or affect downstream projects, but probably breaks your build or test tools, which may prevent deploying or publishing.

Status Details
  • continuous-integration/travis-ci/push: The Travis CI build could not complete due to an error (Details).

Commits

The new version differs by 38 commits.

  • bb521d0 Build: update package.json and changelog for v3.1.0
  • 21fa69a New: Allow options to be passed to prettier.getFileInfo (#187)
  • bb597e1 build(deps-dev): bump eslint-plugin-eslint-plugin from 2.0.1 to 2.1.0
  • 0bb7c1d build(deps-dev): bump eslint-config-prettier from 4.1.0 to 4.2.0
  • 2f77df4 build(deps-dev): bump vue-eslint-parser from 6.0.3 to 6.0.4
  • 222b87a build(deps-dev): bump mocha from 6.1.3 to 6.1.4
  • 58d8ff8 build(deps-dev): bump prettier from 1.16.4 to 1.17.0
  • e94e56c build(deps-dev): bump mocha from 6.1.2 to 6.1.3
  • c02244b build(deps-dev): bump mocha from 6.1.1 to 6.1.2
  • a9a2e4e build(deps-dev): bump mocha from 6.0.2 to 6.1.1
  • 073c14c build(deps-dev): bump eslint from 5.15.3 to 5.16.0
  • bda931f build(deps-dev): bump eslint from 5.15.2 to 5.15.3
  • 19f53d6 build(deps-dev): bump eslint from 5.15.1 to 5.15.2
  • 34b39de build(deps-dev): bump eslint from 5.15.0 to 5.15.1
  • 13bcc66 build(deps-dev): bump eslint from 5.14.1 to 5.15.0

There are 38 commits in total.

See the full diff

FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

An in-range update of babel7 is breaking the build 🚨

There have been updates to the babel7 monorepo:

    • The devDependency @babel/cli was updated from 7.1.2 to 7.1.5.

🚨 View failing branch.

This version is covered by your current version range and after updating it in your project the build failed.

This monorepo update includes releases of one or more dependencies which all belong to the babel7 group definition.

babel7 is a devDependency of this project. It might not break your production code or affect downstream projects, but probably breaks your build or test tools, which may prevent deploying or publishing.

Status Details
  • continuous-integration/travis-ci/push: The Travis CI build could not complete due to an error (Details).

FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

An in-range update of browserify is breaking the build 🚨

The devDependency browserify was updated from 16.2.3 to 16.3.0.

🚨 View failing branch.

This version is covered by your current version range and after updating it in your project the build failed.

browserify is a devDependency of this project. It might not break your production code or affect downstream projects, but probably breaks your build or test tools, which may prevent deploying or publishing.

Status Details
  • continuous-integration/travis-ci/push: The Travis CI build could not complete due to an error (Details).

Commits

The new version differs by 8 commits.

  • 9824fae 16.3.0
  • 9e3397b Add http2 to builtins (#1913)
  • d2ade25 Add http2 to builtins
  • 876182d Tweak license text so Github detects it correctly
  • 16f82a7 Update license (#1906)
  • 7ad39ce Merge pull request #1139 from insidewarehouse/resolve-exposed-folders
  • f13b713 when a module is exposed, it should still resolve the way it would normally do, i.e. with/without extension and directories should fall back to index, and index from a directory should be accepted with/without extension too
  • 8f80729 Update license

See the full diff

FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

Rethink handler auto detection

Currently the handler util uses autoInject and autoName under the hood but it may be a mistake. Indeed, if we use Babel to replace auto-detections by their raw usage for perf reasons, we could use autoHandler and replace those calls with handler ones.

Such Babel transformation would change this:

import { autoHandler } from 'knifecycle';

export default autoHandler(async getUser({ db }, { userId})) {});

into

import { handler } from 'knifecycle';

export default handler('getUser', ['user'], async getUser({ db }, { userId})) {})

leading to better performances since handler would not attempt any auto-detection then.

Version 10 of node.js has been released

Version 10 of Node.js (code name Dubnium) has been released! 🎊

To see what happens to your code in Node.js 10, Greenkeeper has created a branch with the following changes:

  • Added the new Node.js version to your .travis.yml
  • The new Node.js version is in-range for the engines in 1 of your package.json files, so that was left alone

If you’re interested in upgrading this repo to Node.js 10, you can open a PR with these changes. Please note that this issue is just intended as a friendly reminder and the PR as a possible starting point for getting your code running on Node.js 10.

More information on this issue

Greenkeeper has checked the engines key in any package.json file, the .nvmrc file, and the .travis.yml file, if present.

  • engines was only updated if it defined a single version, not a range.
  • .nvmrc was updated to Node.js 10
  • .travis.yml was only changed if there was a root-level node_js that didn’t already include Node.js 10, such as node or lts/*. In this case, the new version was appended to the list. We didn’t touch job or matrix configurations because these tend to be quite specific and complex, and it’s difficult to infer what the intentions were.

For many simpler .travis.yml configurations, this PR should suffice as-is, but depending on what you’re doing it may require additional work or may not be applicable at all. We’re also aware that you may have good reasons to not update to Node.js 10, which is why this was sent as an issue and not a pull request. Feel free to delete it without comment, I’m a humble robot and won’t feel rejected 🤖


FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

Add an $injector service

$.run(['$inject']).then(({ $inject }) => {
  $inject(['aService']).then(({ aService }) => {
    // Yay!  
  });
});

Create an `handler` decorator

Often having that kind of boilerplate code:

module.exports = initializer({
  name: getMovingDevicesMails.name,
  type: 'service',
  inject: [
    'getMovingDevices',
    'elasticSearch', '?log',
  ],
}, (...args) => Promise.resolve(
  getMovingDevicesMails.bind(null, ...args)
));

That could be simplified like so:

module.exports = handler(getMovingDevicesMails, [
  'getMovingDevices',
  'elasticSearch', '?log',
]);

An in-range update of babel7 is breaking the build 🚨

There have been updates to the babel7 monorepo:

    • The devDependency @babel/cli was updated from 7.1.1 to 7.1.2.

🚨 View failing branch.

This version is covered by your current version range and after updating it in your project the build failed.

This monorepo update includes releases of one or more dependencies which all belong to the babel7 group definition.

babel7 is a devDependency of this project. It might not break your production code or affect downstream projects, but probably breaks your build or test tools, which may prevent deploying or publishing.

Status Details
  • continuous-integration/travis-ci/push: The Travis CI build could not complete due to an error (Details).

FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

An in-range update of sinon is breaking the build 🚨

The devDependency sinon was updated from 7.3.0 to 7.3.1.

🚨 View failing branch.

This version is covered by your current version range and after updating it in your project the build failed.

sinon is a devDependency of this project. It might not break your production code or affect downstream projects, but probably breaks your build or test tools, which may prevent deploying or publishing.

Status Details
  • continuous-integration/travis-ci/push: The Travis CI build could not complete due to an error (Details).

Commits

The new version differs by 6 commits.

  • 3812a7d Update docs/changelog.md and set new release id in docs/_config.yml
  • 93bef55 Add release documentation for v7.3.1
  • e02c192 7.3.1
  • 8ee1d35 Update CHANGELOG.md and AUTHORS for new release
  • bc53d82 Fix security issues
  • 1a09166 Update @sinonjs/samsam to v3.3.1

See the full diff

FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

An in-range update of babel7 is breaking the build 🚨

There have been updates to the babel7 monorepo:

    • The devDependency @babel/cli was updated from 7.1.0 to 7.1.1.

🚨 View failing branch.

This version is covered by your current version range and after updating it in your project the build failed.

This monorepo update includes releases of one or more dependencies which all belong to the babel7 group definition.

babel7 is a devDependency of this project. It might not break your production code or affect downstream projects, but probably breaks your build or test tools, which may prevent deploying or publishing.

Status Details
  • continuous-integration/travis-ci/push: The Travis CI build could not complete due to an error (Details).

FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

Use `$inject` as a prop on providers

Feature description

Instead of using $.depends which is a function, it is maybe better to use $inject since it is more known and may be more understood.

It will introduce breaking changes.

Use cases

I often end up creating files that exports a function like this which introduce a lot of boilerplate only to allow customizing services/deps name:

module.exports = initDBService;

function initDBService($, name='db', dependencies=['ENV']) {
  return $.service(name, $depends(dbService));
}

function dbService({ ENV }) {
/// actual service code
}

Which is later used like this:

import initDBService from 'db';
import $ from 'knifecycle/instance';

initDBService($);

It would be more concise to do this:

module.exports = dbService;

dbService.$inject = ['ENV'];

function dbService({ ENV }) {
/// actual service code
}

And use it that way:

import dbService from 'db';
import $ from 'knifecycle/instance';

$.service('db', dbService);

// Optionnally change deps
const customDBService = dbService.bind();
customDBService.$inject = ['CONFIG:ENV'];
$.service('customDB', customDBService);

The only con is that some people could forget the bind and this would create some weird behaviors.

  • I will do it

Document `$*` internal services types

The $inject, $shutdown, $dispose, $autoload and $instance internal services should be documented and their interface should be specified in the types definitions.

Better service mapping symbol?

Currently the mapping symbol is :. It unfortunately do not tells the direction in which the map takes places leading to constant lookup of the doc.

Maybe that > would be a better fit ?

This would be a breaking change so will wait the 2.0.0 version to do so.

`$injector` should accept flags

Would be nice to allow to specify that the injector is used in singleton context to avoid loading non-singletons into singletons.

Better naming

Some bad naming choices were made, the 2.0.0 breaking changes is a good occasion to bring better names:

  • $shutdown would be better named $dispose
  • $shutdownAll would be better named $destroy
  • $fatalError should be renamed $fatalErrorPromise

An in-range update of karma-browserify is breaking the build 🚨

The devDependency karma-browserify was updated from 6.0.0 to 6.1.0.

🚨 View failing branch.

This version is covered by your current version range and after updating it in your project the build failed.

karma-browserify is a devDependency of this project. It might not break your production code or affect downstream projects, but probably breaks your build or test tools, which may prevent deploying or publishing.

Status Details
  • continuous-integration/travis-ci/push: The Travis CI build could not complete due to an error (Details).

Commits

The new version differs by 4 commits.

  • ee355f5 6.1.0
  • a3ebf22 chore(project): bump dependencies
  • 58f9788 chore(project): replace phantomjs with chrome for testing
  • b2709c3 chore(travis): drop sudo requirement

See the full diff

FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

Better signature for provider/service

Check for the use case of needing a servicePromise property on service initializers, looks like it doesn't make sense.

Also check for the necessity of having two imbrications of promises for the providers.

rootContext?

Follow up to https://twitter.com/nfroidure/status/865478307826946048

This project creates a tight link with the library instead of using the module system and functions while making testing harder. Why not just pass an rootContext down through the functions?

App.js

/**
 * @flow
 */
const AppLogger = require('./AppLogger')
import type { appLogger } from './AppLogger'
const Server = require('./Server')

type rootContext = {
  env: {
    LOGFILE: string,
    PORT: string,
  },
  now: () => number,
  logger: appLogger,
}

const main = async () => {
  if (
    typeof process.env.LOGFILE !== 'string' ||
    typeof process.env.PORT !== 'string'
  ) {
    return
  }
  const rootContext: rootContext = {
    env: process.env,
    now: () => Date.now(),
    logger: AppLogger.make(process.env.LOGFILE),
  }
  const server = await Server.make(rootContext)
}

main()

AppLogger.js

/**
 * @flow
 */
const Logger = require('logger')

export type appLogger = {
  +log: (info: 'info', string: string, ...args: Array<number | string>) => void,
}

exports.make = (outPath: string) => Logger.createLogger(outPath)

GetTimeRoute.js

/**
 * @flow
 */
import type { appLogger } from './AppLogger'

type rootContext = {
  +now: () => number,
  +logger: appLogger,
}

exports.make = (rootContext: rootContext) => (
  req: express$Request,
  res: express$Response,
  next: express$NextFunction
) => {
  const currentTime = rootContext.now()
  rootContext.logger.log('info', 'Sending the current time:', currentTime)
  res.status(200).end(String(currentTime))
}

Server.js

/**
 * @flow
 */
const express = require('express')
const GetTimeRoute = require('./GetTimeRoute')
import type { appLogger } from './AppLogger'

type rootContext = {
  +env: {
    +PORT: string,
  },
  +now: () => number,
  +logger: appLogger,
}

exports.make = async (rootContext: rootContext) => {
  const app = express()
  app.get('/time', GetTimeRoute.make(rootContext))
  const server = await new Promise(resolve =>
    app.listen(rootContext.env.PORT, resolve)
  )
  rootContext.logger.log(
    'info',
    'server listening on port ' + rootContext.env.PORT + '!'
  )
  return server
}

Remove methods and export decorators

The $.constant,$.service,$.initializer methods can be replaced by coupling $.register and initializer, service, constant decorators that would make it more composable and flexible.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.