
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
A fast and customizable Dependency Injection Container for Node.js / io.js
var Cation = require('cation')
var container = new Cation()
var Atom = function() { /* Atom service! */ }
container.register('Atom', Atom)
container.get('Atom').then(function(atom) {
/* new atom object! */
})
Cation is a powerful Dependency Injection Container (DIC). The first version was released in 2011-2012 as an unstable/experimental library and was inspired by the Symfony 2 container. It only allowed a JSON schema to register a service (yeah, it was an ugly experiment). There were no Factories, no Decorators. Just "Services".
The version 2 is a MUCH, MUCH BETTER EVOLUTION, heavily inspired by these projects:
Cool things you'll enjoy for sure:
Service, Factory and Static resource providers, right out of the box.Decorator support.$ npm install cation
This is probably one of those things you are doing a lot, creating new objects. I'm going to reuse the Atom example from the top of this file and explain with simple words what happens when you use Cation.
This is the constructor for a service object:
var Atom = function() { /* Atom constructor */ }
And this is how you register the Atom constructor in the container:
var Cation = require('cation')
var container = new Cation()
container.register('Atom', Atom)
Boom. It's done. If you need a new object:
container.get('Atom').then(function(atom) {
// a new Atom instance over here!
})
// or...
var getAtomPromise = container.get('Atom')
getAtomPromise.then(function(atom) {
// a new Atom instance over here!
})
So, what happened here?
Cation#register.ServiceProvider object with the arguments you provided.Cation#get.Promise object.Promise#then and pass a callback function that will be executed when the promise is resolved.ServiceProvider internally creates a new object from the constructor function you provided in the first place and returns it.The main purpose of a Dependency Injection Container. Imagine you need to create a chemical element object, with a few arguments:
var Element = function(name, symbol, atomicNumber, atomicWeight) {
this.name = name
this.symbol = symbol
this.atomicNumber = atomicNumber
this.atomicWeight = atomicWeight
}
container.register('Hydrogen', Element, {
args: ['Hydrogen', 'H', 1, 1.008]
})
container.get('Hydrogen').then(function(element) {
console.log(element.name) // Hydrogen
})
What if one or more arguments are dependencies to other services? Let's take a look at a little more complex example:
// HEADS UP!
//
// For the sake of "simplicity", I'm going to use a few ES6 features in this example.
//
// The third argument here, ...elements, is known as a "rest parameter" and is part of the ES6 specification.
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters
var Compound = function(name, formula, ...elements) {
this.name = name
this.formula = formula
this.elements = elements
this.getMolarMass = function() {
// HEADS UP!
// I'm using "arrow functions" here. It's also part of ES6.
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions
return this.elements
.map(element => element.atomicWeight)
.reduce((accumulator, weight) => accumulator + weight)
}
}
container.register('Hydrogen', Element, {
args: ['Hydrogen', 'H', 1, 1.008]
})
container.register('Oxygen', Element, {
args: ['Oxygen', 'O', 8, 15.999]
})
// This. Read it carefully...
container.register('Water', Compound, {
args: ['Water', 'H2O', '@Hydrogen', '@Oxygen', '@Hydrogen'] // notice the "@"?
})
container.get('Water').then(function(molecule) {
console.log(molecule.formula) // H2O
console.log(molecule.getMolarMass()) // 18.015
})
Sweet, isn't it? Just remember: you can use whatever you want as an argument. But if you need an argument that is a registered resource in Cation, you need to reference it by using a string like "@MyRegisteredResource".
Q: What if I need, in fact, an argument that is a string and starts with the @ character but it's not a resource reference?
A: You can escape it with double backslashes.
container.register('Something', Something, {
args: ['@ServiceReference', '\\@NormalStringArgument']
})
By doing this, your service will receive a new ServiceReference object as a first argument and "@NormalStringArgument" as a second argument.
Psst! Click here to see this example using ES6 syntax.
Enabling this pattern is as easy as setting an option named singleton to true.
container.register('SingletonService', Service, {
singleton: true
})
register optionsAs you can see, the register method can take up to three arguments:
When you are registering a service, the available options are:
type: this option is always provided by default as service. You can replace it with type: 'factory' and you'll be registering a factory, instead of a service. You'll learn about this in the next topic.singleton: boolean option and false by default. If set to true, Cation will treat the resource as a singleton.args: array option, [] by default. These are the arguments to apply to the service constructor. It only works if the type is service.decorators: array option, [] by default. These are the names of the Decorators to be applied to the returned objects. You'll learn about this in the Decorators topic.tags: array option, [] by default. You can use this feature to tag your services. You'll learn about this in Working with tagged resources section.Having known that, these options are the same:
{
type : 'service',
singleton : true,
args : ['a', 2, '@B'],
decorators : ['decorator1', 'decorator2']
}
{
singleton : true,
args : ['a', 2, '@B'],
decorators : ['decorator1', 'decorator2']
}
{
args : []
singleton : true
}
{
singleton: true
}
If you need a more granular control over your created objects, you can opt for the Factory Pattern. In Cation, a factory must follow these simple rules:
Cation#register method must receive an options object with, at least, a property type equals to factory.// if you know how promises work, you'll notice that this code block can be
// simpliflied by just returning the execution of Promise.all().then().
// I'll keep this example to explicitly show the returned promise.
container.register('ServiceC', function(container) {
var factoryPromise = new Promise(function(resolveFactoryPromise, rejectFactoryPromise) {
var depencencyAPromise = container.get('ServiceA')
var dependencyBPromise = container.get('ServiceB')
// main promise will resolve when all dependency promises are resolved
Promise.all([dependencyAPromise, dependencyBPromise]).then(function(services) {
// our resolved services
var dependencyA = service[0]
var dependencyB = service[1]
dependencyA.doSomething()
dependencyB.property = 'Something Else'
resolveFactoryPromise(new ServiceC(dependencyA, dependencyB))
}).catch(function(error) {
rejectFactoryPromise(error)
})
})
return factoryPromise
}, {
type: 'factory'
})
container.get('ServiceC').then(function(serviceObject) {
// do something with serviceObject
})
// and this is the simplified version of the above implementation
container.register('ServiceC', function(container) {
var depencencyAPromise = container.get('ServiceA')
var dependencyBPromise = container.get('ServiceB')
// we return the promise chain.
// remember, if you return something inside a promise, you'll get a promise whose
// resolved value is whatever you returned in the latter.
// `promise chaining` if you want to call it by a name.
return Promise.all([dependencyAPromise, dependencyBPromise]).then(function(services) {
// our resolved services
var dependencyA = services[0]
var dependencyB = services[1]
dependencyA.doSomething()
dependencyB.property = 'Something Else'
// return something to create another promise that will be handled by you
// on `Cation#get` call.
return new ServiceC(dependencyA, dependencyB)
})
}, {
type: 'factory'
})
container.get('ServiceC').then(function(serviceObject) {
// still works!
})
The Cation#register method, when registering a factory resource, can take these options:
type: you MUST set this option to 'factory'.singleton: boolean option, false by default. After the first factory execution, the instance will be stored as a singleton.decorators: array option, [] by default. These are the names of the Decorators to be applied to the returned objects. You'll learn about this in the Decorators topic.tags: array option, [] by default. You can use this feature to tag your factories. You'll learn about this in Working with tagged resources section.Psst! Click here to see this example using ES6 syntax.
These kind of resources are treated as constants by Cation. You can freely register a number, string, array, object or functions. Every time you call Cation#get you'll receive the same value.
// register a static resource
container.register('hydrogen-atomic-weight', 1.008, {
type: 'static'
})
// use the resource as a dependency in a service
container.register('Hydrogen', Element, {
args: ['Hydrogen', 'H', 1, '@hydrogen-atomic-weight']
})
type: you MUST set this option to 'static'.tags: array option, [] by default. You can use this feature to tag your resources. You'll learn about this in Working with tagged resources section.The Cation decorators are simple functions called just after the service creation, but before it's returned to you. Its main purpose is to decorate a given object. Every decorator must follow these rules:
Cation#register method must receive an options object with, at least, a property decorators equals to an array with the decorator names to be applied.// Example inspired by "Learning JavaScript Design Patterns" book by Addy Osmani
function BasicMacBookProRetina() {
this.resolution = function() { return 13 }
this.storage = function() { return 128 }
this.cpu = function() { return '2.6GHz dual-core Intel Core i5' }
this.price = function() { return 1299 }
this.toString = function() {
return '%in%-inch MacBook Pro with Retina Display. Storage: %storage%GB. CPU: %cpu%. Price: $%price%.'
.replace(/%in%/, this.resolution())
.replace(/%storage%/, this.storage())
.replace(/%cpu%/, this.cpu())
.replace(/%price%/, this.price())
}
}
container.addDecorator('storage', function(macbook) {
var originalStorage = macbook.storage()
var originalCost = macbook.cost()
macbook.storage = function() { return originalStorage * 2 }
macbook.cost = function() { return cost + 200 }
return macbook
})
container.addDecorator('cpu', function(macbook) {
var originalCost = macbook.cost()
macbook.cost = function() { return originalCost + 100 }
macbook.cpu = function() { return '2.8GHz dual-core Intel Core i5' }
return macbook
})
// 128GB storage version
container.register('13in-MBP-Retina-128', BasicMacBookProRetina)
// 256GB storage version
container.register('13in-MBP-Retina-256', BasicMacBookProRetina, {
decorators: ['storage']
})
// 512GB storage + higher freq CPU
container.register('13in-MBP-Retina-512', BasicMacBookProRetina, {
decorators: ['storage', 'storage', 'cpu']
})
// ----
container.get('13in-MBP-Retina-128').then(function(macbook) {
console.log(macbook)
// 13-inch MacBook Pro with Retina Display. Storage: 128GB. CPU: 2.6GHz dual-core Intel Core i5. Price: $1299.
})
container.get('13in-MBP-Retina-256').then(function(macbook) {
console.log(macbook)
// 13-inch MacBook Pro with Retina Display. Storage: 256GB. CPU: 2.6GHz dual-core Intel Core i5. Price: $1499.
})
container.get('13in-MBP-Retina-512').then(function(macbook) {
console.log(macbook)
// 13-inch MacBook Pro with Retina Display. Storage: 512GB. CPU: 2.8GHz dual-core Intel Core i5. Price: $1799.
})
Every time you call Cation#register and, in the options object, you specify a type, what you are really doing is telling Cation to use an specific Resource Provider.
As you can clearly see now, Cation comes with three Providers by default:
ServiceProvider. { type: 'service' }FactoryProvider. { type: 'factory' }StaticProvider. { type: 'static' }What if, for some reason, you need a custom provider?
/**
* The GhostProvider will register a new static resource every time you
* register something using this type.
* Also, it provides a "Boo! 👻" string when trying to retrieve the registered resource.
*
* @param {Cation} container A Cation instance
* @param {String} id The Resource ID
* @param {mixed} resource The Resource Object
* @param {Object} options The register options
*/
function GhostProvider(container, id, resource, options) {
// All ResourceProviders are created with these args, always.
this.container = container
this.id = id
this.resource = resource
this.options = options
this.container.register(
'%id%-gravestone'.replace(/%id%/, this.id),
'Here lies %id%. %date% - %date%. RIP'
.replace(/%id%/, this.id)
.replace(/%date%/, new Date().toDateString())
,
{ type: 'static' }
)
// This is where magic happens...
// You must ALWAYS implement a `get` method.
this.get = function() {
// And this method should ALWAYS return a new Promise.
return new Promise(function(resolve) {
// NEVER forget to resolve the promise with whatever you want to return on `Cation#get`. Never.
resolve('Boo! 👻')
})
}
}
container.addProvider('ghost', GhostProvider)
container.register('DeadResource', 'resource value', {
type: 'ghost'
})
// and here... we... GO.
container.get('DeadResource').then(function(resource) {
console.log(resource) // Boo! 👻
return container.get('DeadResource-gravestone')
}).then(function(gravestone) {
console.log(gravestone) // Here lies DeadResource. Wed Jan 28 2015 - Wed Jan 28 2015. RIP
})
Psst! Click here to see this example using ES6 syntax.
The Cation constructor can take an options object as an argument. Currently, the only supported option is id. With this ID you can keep track of your containers, in case you are creating more than one.
var container1 = new Cation({ id: 'c-1' })
var container2 = new Cation({ id: 'c-2' })
console.log(container1.getId()) // c-1
console.log(container2.getId()) // c-2
Tags are strings that can be applied to any resource. By themselves, tags don't actually alter the functionality of your resources in any way, but can be really useful if you need to group them to easily retrieve and manipulate them in some specific way.
To enable this feature, just register a resource with an option tags equals to an array of strings.
// https://github.com/wycats/handlebars.js
container.register('Handlebars', Handlebars, {
type: 'static'
})
function HandlebarsCompiler(Handlebars) {
this.compile = function(source, data) {
var template = Handlebars.compile(source)
return template(data)
}
}
container.register('HandlebarsCompiler', HandlebarsCompiler, {
args: ['@Handlebars'],
tags: ['framework.view.compiler']
})
// somewhere,
// in your awesome new JS framework...
var compilerId = container.findTaggedResourceIds('framework.view.compiler').shift()
container.get(compilerId).then(function(compiler) {
var source = loadHtmlSource() // load template files
var data = loadTemplateData() // data to be injected inside of templates
var result = compiler.compile(source, data)
})
// now, if you want another compiler
// https://github.com/paularmstrong/swig
container.register('Swig', swig, {
type: 'static'
})
function SwigCompiler(Swig) {
this.compile = function(source, data) {
return Swig.render(source, { locals: data })
}
}
container.register('SwigCompiler', SwigCompiler, {
args: ['@Swig'],
tags: ['framework.view.compiler']
})
// you can do really cool things with this new feature. A little more complex example
// could have been loading all tagged compilers and make them compile .hbs or
// .swig templates, with just one function:
// container.get(compilerId).then(function(compiler) { ... })
// just go and make awesome things :)
Please, check the Contributing.md document for detailed info.
Check the LICENSE file.
Cation constructor
| Parameters | Type | Description |
|---|---|---|
| options (optional) | object | An object containing options. |
Options
| Name | Type | Description |
|---|---|---|
| id (optional) | string | An ID for the current Cation instance. |
Return
| Type | Description |
|---|---|
| string | The container ID. |
Registers a service constructor, a factory function or a static value.
| Parameters | Type | Description |
|---|---|---|
| id | string | The ID of the resource to register. Must be unique. |
| resource | mixed | The resource to be registered. |
| options (optional) | object | An object containing options. |
Options
| Name | Type | Description |
|---|---|---|
| type (optional) | string | Specify the type of resource to be registered. It can be service (default), factory or static. |
| args (optional) | array | Arguments to apply if the registered resource is a service. |
| singleton (optional) | boolean | Singleton behaviour. |
| decorators (optional) | array | Decorators to be applied to the returned instances. |
Retrieves a resource from the container.
| Parameters | Type | Description |
|---|---|---|
| id | string | The ID of a previously registered resource. |
Return
| Type | Description |
|---|---|
| Promise | Promise whose resolved value is the requested service instance / resource value. |
Checks if a resource is registered.
| Parameters | Type | Description |
|---|---|---|
| id | string | The ID of a resource. |
Return
| Type | Description |
|---|---|
| Boolean | true if the container has the resource, false otherwise. |
Removes a resource from the container.
| Parameters | Type | Description |
|---|---|---|
| id | string | The ID of a resource. |
Registers a resource provider.
| Parameters | Type | Description |
|---|---|---|
| name | string | Provider name. Must be unique. |
| providerFunction | function | Provider function. |
providerFunction
| Parameters | Type | Description |
|---|---|---|
| container | Cation | A Cation instance. |
| id | string | The ID of the resource being registered. |
| resource | mixed | The resource being registered. |
| options | object | An object containing options. |
Checks if a given provider is registered.
| Parameters | Type | Description |
|---|---|---|
| name | string | The name of a provider. |
Return
| Type | Description |
|---|---|
| Boolean | true if the container has the provider, false otherwise. |
Removes a given provider.
| Parameters | Type | Description |
|---|---|---|
| name | string | The name of a provider. |
Registers a resource decorator.
| Parameters | Type | Description |
|---|---|---|
| name | string | Decorator name. Must be unique. |
| decoratorFunction | function | Decorator function. |
decoratorFunction
| Parameters | Type | Description |
|---|---|---|
| resource | mixed | The resource to be decorated. |
Checks if a given decorator is registered.
| Parameters | Type | Description |
|---|---|---|
| name | string | The name of a decorator. |
Return
| Type | Description |
|---|---|
| Boolean | true if the container has the decorator, false otherwise. |
Removes a given decorator.
| Parameters | Type | Description |
|---|---|---|
| name | string | The name of a decorator. |
Checks if a resource is cached. Only instances from services declared as singleton will be stored in cache.
| Parameters | Type | Description |
|---|---|---|
| id | string | The ID of a resource. |
Return
| Type | Description |
|---|---|
| Boolean | true if the container has the resource in the singleton cache, false otherwise. |
Removes all singleton instances from cache.
Returns an array of resource IDs for a given tag.
| Parameters | Type | Description |
|---|---|---|
| tagName | string | The tag name. |
Return
| Type | Description |
|---|---|
| Array | An array of resource IDs. |
FAQs
Node.js Dependency Container
We found that cation demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer 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
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.