npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

app-container

v1.1.2

Published

asynchronous IoC container for node.js applications

Downloads

1,794

Readme

app-container

Yet another IoC container for node applications with a few specific goals:

  • support for asynchronous modules
  • flexible declaration & dependency syntax
  • support for initialization hooks

Installing

$ npm install --save app-container

Getting Started

Below is a super simple example. See /test/fixtures for some more examples.

Define your code as modules that accept dependencies.

using an asynchronous factory function...

// in config.js
import someAsyncModule from 'some-async-module'

export const inject = { }

export default async function() {
  const config = await someAsyncModule.load()
  return config
}

using a plain old javascript object...

// in greet.js
export const inject = {
  require: ['config']
}

export default {
  greet(greeting, name) {
    console.log(`${greeting}, ${name}${this.config.punctuation}`);
  },
};

using a constructor function...

// in hello.js
export const inject = {
  type: 'constructor',
  require: ['greet']
}

export default class Foo {
  constructor(greet) {
    this.greeter = greet;
  }

  sayHello(name) {
    this.greeter.greet('Hello', name);
  }
}

Create a new container.

// in container.js
import Container from 'app-container';

// create a new container
const container = new Container({
  namespace: 'inject', // this is the default namespace
  defaults: { singleton: true }, // these are defaults to apply to all module declarations
});

// register modules matching a given pattern. the directory will be scanned recursively.
container.glob('**/*.js', { cwd: __dirname });

export default container;

Load some dependencies...

// in index.js
import container from './container';

export default async function main() {
  const hello = await container.load('hello');
  hello.sayHello('World');
}

if (require.main === module) {
  main();
}

Modules

Module's need to declare themselves to the container by exposing an object at a given namespace. If no namespace is defined when creating a container, the default namespace of inject will be used. The simplest module declaration is shown below.

// in foo.js
export const inject = { };

export default function() {
  return {
    myMethod() { /* ... */ }
  };
};

Or, in commonjs format:

// in foo.js
module.exports = function() {
  return {
    myMethod() { /* ... */ }
  };
};

module.exports.inject = { };

If foo.js resides at the root of one of our registered directories, then the container would register a new component named foo with no dependencies and assume that foo's default export (or module.exports) is a factory function. We could instead declare foo as a constructor function like so:

// in foo.js
export const inject = { type: 'constructor' };

export default class Foo {
  myMethod() { /* ... */ }
};

Or even a plain old javascript object.

// in foo.js
export const inject = { };

export default {
  myMethod() { /* ... */ }
};

To declare a dependency, we can add a require property to our declaration. In the following example, we add a dependency on another component, bar. By the time our module's factory function is invoked, an instantiated bar instance will be passed in as an argument.

// in foo.js
export const inject = {
  require: 'bar'
};

export default function(bar) {
  return {
    myMethod() { /* ... */ }
  };
};

A component can have more than one dependency as well. By declaring require as an array, each dependency will be created, initialized, and passed in as arguments to our factory function.

// in foo.js
export const inject = {
  require: ['bar', 'baz', 'gar', 'gaz']
};

export default function(bar, baz, gar, gaz) {
  return {
    myMethod() { /* ... */ }
  };
};

We can also declare our dependencies as an object. This allows us to rename them, and/or group them in various ways.

// in foo.js
export const inject = {
  require: {
    some: 'bar',
    other: 'baz',
    mods: {
      gar: 'gar',
      gaz: 'gaz'
    }
  },
};

export default function({ some, other, mods }) {
  const { gar, gaz } = mods;
  return {
    myMethod() { /* ... */ }
  };
};

Plugins

We can use special syntax to use plugins that support additional functionality.

The all! plugin can be used to bulk load modules that match a pattern as an object.

// in foo.js
export const inject = {
  require: ['all!^b', 'all!^g']
};

export default function({ bar, baz }, { gar, gaz }) {
  return {
    myMethod() { /* ... */ }
  };
};

The any! plugin is similar to all, except that it loads resolved modules as an array.

// in foo.js
export const inject = {
  require: ['any!^b', 'any!^g']
};

export default function([bar, baz], [gar, gaz]) {
  return {
    myMethod() { /* ... */ }
  };
};

The container! plugin can be used to register/load dynamic components or change the behavior of the container at runtime.

// in repository.js
export const inject = {
  require: ['container!', 'config']
}

export default function(container, config) {
  if (config.backend === 'in-memory') {
    return container.load('inmem/repo');
  }
  return container.load('redis/repo');
}

Asynchronous Components

The container supports asynchronous modules in two ways. 1) By returning a promise from a factory function:

// in foo.js
export const inject = {
  name: 'foo',
  require: ['bar', 'baz']
};

export default function(bar, baz) {
  return Promise.resolve({
    myMethod() { /* ... */ }
  });
};

// in bar.js
export const inject = {
  name: 'bar',
  require: ['bar', 'baz']
};

export default async function(bar, baz) {
  await new Promise(resolve => setTimeout(resolve, 1000))
  return {
    myMethod() { /* ... */ }
  }
};

or, 2) by exposing an initialization method/function on the module instance that returns a promise.

// in foo.js
export const inject = {
  name: 'foo',
  init: 'connect',
  require: ['bar', 'baz']
};

export default function(bar, baz) {
  return {
    myMethod() { /* ... */ },

    connect() {
      return Promise.resolve();
    },
  };
};

Module Properties

A module declaration can declare any combination of the following properties.

| Property | Type | Description | | --- | --- | --- | | init | String | The name of a method/function to call to initialize the module instance after it's been created. | | name | String | A custom name to use to register with the container. If not provided, the relative path to the file (minus the extension) will be used instead when registering modules using glob | | require | Object String String[] | Module dependencies declarations | | singleton | Boolean | Whether or not the module should be treated as a singleton, meaning that if the module is required by two or more other modules, only one instance will ever be created, and all downstream modules will share the same instance. | | type | String | The default export (or module.exports) should either be a factory function, constructor function or something like a plain old javascript object or function that doesn't need instantiation/initialization. If no type is declared, the container will inspect it and assume that it is a factory function (if a function is exported), or an object, if something else is exported. You can override the default behavior by declaring a type property with a value of constructor or object. |

API

Container([options]) => container

Constructor function for creating container instances.

Parameters

| Name | Type | Description | | --- | --- | --- | | options | Object | | | options.defaults | Object | a map of default module options to apply to each module declaration | | options.namespace | String | override the default namespace inject |

Example
// instantiate a new container using the default namespace and setting the default
// singleton flag to true
const container = new Container({
  namespace: 'inject',
  defaults: {
    singleton: true,
  },
})

glob(pattern, options)

The glob method allows for automagically registering multiple modules with the container instance using glob to match files in a given directory.

Parameters

| Name | Type | Description | | --- | --- | --- | | pattern | String | a glob pattern for matching modules to register with the container. Only modules that match this pattern and declare a matching namespace will be registered. | | options | Object | an options object to pass to the underlying glob.sync call. |

Example
// recursively register all .js files in the same directory as this file
// excluding index.js. see glob.js for more options
const container = new Container()
container.glob('**/*.js', { cwd: __dirname, ignore: ['index.js'] })

load(...components) => Bluebird

Load one or more components

Parameters

| Name | Type | Description | | --- | --- | --- | | component | Object|String | one or more components to load |

Example
// load a single module
const foo = await container.load('foo');

// load multiple modules
const [foo, bar] = await container.load('foo', 'bar');
// or
const [foo, bar] = await container.load(['foo', 'bar']);

// the container returns a bluebird promise, so any bluebird function can be used.
container.load('foo', 'bar')
.spread((foo, bar) => {
  // ..
});

// use a component map
const { foo, bar} = await container.load({ foo: 'services/foo', bar: 'services/bar' });

// use all or any
const { 'services/foo': foo, 'services/bar': bar } = await container.load('all!^services');

const services = await container.load('any!^services');

register(mod, name, [options]) => Bluebird

Load one or more components

Parameters

| Name | Type | Description | | --- | --- | --- | | mod | Function|Object | module definition | | name | Object|String | component name or valid compoment options object | | [options] | *Object | component options object (see module properties above) |

Example
import Container from 'app-container';

import * as bar from './bar';
import * as foo from './foo';

const container = new Container({
  namespace: 'inject',
  defaults: { singleton: true },
});

container.register(bar, 'bar', bar.inject);
container.register(foo, foo.inject);

export default container;

Debugging

To enable debugging, you can use the DEBUG environment variable.

$ export DEBUG=app-container*

Testing

run the test suite with code coverage

$ docker-compose run app-container

Contributing

  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes using conventional changelog standards (git commit -am 'feat: adds my new feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Ensure linting/security/tests are all passing
  6. Create new Pull Request

LICENSE

Copyright (c) 2017 Chris Ludden.

Licensed under the MIT License