![Introducing Enhanced Alert Actions and Triage Functionality](https://cdn.sanity.io/images/cgdhsj6q/production/fe71306d515f85de6139b46745ea7180362324f0-2530x946.png?w=800&fit=max&auto=format)
Product
Introducing Enhanced Alert Actions and Triage Functionality
Socket now supports four distinct alert actions instead of the previous two, and alert triaging allows users to override the actions taken for all individual alerts.
awilix
Advanced tools
Package description
Awilix is a powerful and flexible dependency injection (DI) container for JavaScript and Node.js applications. It helps manage dependencies in a clean and modular way, making it easier to write maintainable and testable code.
Container Creation
This feature allows you to create a DI container where you can register your dependencies. The `createContainer` function initializes a new container instance.
const { createContainer, asClass, asFunction, asValue } = require('awilix');
const container = createContainer();
Registering Dependencies
You can register different types of dependencies in the container using `asClass`, `asFunction`, and `asValue`. This example shows how to register a class, a function, and a value.
class MyService {}
function myFunction() {}
const myValue = 'some value';
container.register({
myService: asClass(MyService),
myFunction: asFunction(myFunction),
myValue: asValue(myValue)
});
Resolving Dependencies
Once dependencies are registered, you can resolve them from the container. This example demonstrates how to resolve a class instance, a function, and a value.
const myServiceInstance = container.resolve('myService');
const myFunctionInstance = container.resolve('myFunction');
const myValueInstance = container.resolve('myValue');
Scoped and Singleton Registrations
Awilix supports different lifetimes for dependencies. You can register dependencies as scoped or singleton. Scoped dependencies are created per resolution scope, while singletons are created once and shared.
container.register({
myScopedService: asClass(MyService).scoped(),
mySingletonService: asClass(MyService).singleton()
});
Auto-Loading Modules
Awilix can automatically load and register modules from specified directories. This feature is useful for large applications where manual registration would be cumbersome.
const path = require('path');
container.loadModules([path.join(__dirname, 'services/*.js')], {
formatName: 'camelCase',
resolverOptions: {
lifetime: 'SINGLETON'
}
});
Inversify is a powerful and flexible inversion of control (IoC) container for TypeScript and JavaScript apps. It uses decorators and is designed to work with TypeScript, making it a good choice for TypeScript projects. Compared to Awilix, Inversify has a more opinionated API and relies heavily on TypeScript features.
BottleJS is a simple and lightweight dependency injection micro-container. It is less feature-rich compared to Awilix but is easier to set up and use for smaller projects. BottleJS focuses on simplicity and minimalism.
TypeDI is a dependency injection tool for TypeScript and JavaScript. It uses decorators and is designed to work seamlessly with TypeScript. TypeDI is similar to Inversify but offers a different API and set of features. It is a good alternative for TypeScript projects that need DI.
Readme
Simple Inversion of Control (IoC) container for Node with dependency resolution support. Make IoC great again!
npm install awilix --save
Requires Node v4 or above, and possibly npm v3.
Awilix has a pretty simple API. At minimum, you need to do 2 things:
index.js
const awilix = require('awilix');
// Some module you want to access everywhere.
const database = require('./db');
// Create the container.
const container = awilix.createContainer();
// Register the database module as "db".
container.register({
db: database
});
// Use it.
container.db.query('SELECT ...');
So the above example is not very interesting though. A reasonably sized project has a few directories with modules that may (or may not) depend on one another.
You could just require
them all in the correct order, but where's the fun in that? Nah, let's have Awilix auto-load our modules and hook them up to the container.
For this mini-tutorial, we will use a todos API as an example.
services/todosService.js
// A nice function that takes the container
// as the first argument, and any number of
// additional arguments.
function getTodos(container, searchText) {
const todos = container.todos;
return todos.getTodos({ searchText: searchText }).then(result => {
// Do something useful with the result here.. Or not.
return result;
});
}
// Export it so we can test it.
module.exports.getTodos = getTodos;
// To make this module Awilix-compatible, we
// have to use a default export that looks like this:
module.exports.default = (container) => {
// Expose this module as "todosService"
container.register({
todosService: container.bindAll({
// The todosService has a getTodos function,
// which needs the container to grab
// it's dependencies when called.
getTodos: getTodos
})
});
};
The first bit is pretty standard, apart from the container
argument. We basically just treat it as an object that has all the stuff we need - in this
case, a todos repository (exposed as todos
). How this came to be, we will find out... right now!
repositories/todosRepository.js
// This example is written with ES6 import-export syntax,
// as well as ES6 classes just to demonstrate how that works.
export class TodosRepository {
// We need a database object to query against
// - we declare this in the constructor.
// This is called "constructor injection".
constructor(db) {
this.db = db;
}
// Queries the DB for todos..
getTodos(query) {
return this.db.sql(`select * from todos where text like '%${query.searchText}%'`).then(result => {
// Transform DB result to todos..
return result.map(todo => {...});
});
}
}
// Since we are using constructor injection,
// we can't be sure if "container.db" is available yet,
// so we use the dependsOn API.
export default function (container) {
// We depend on the module exposed as "db".
container.dependsOn(['db'], () => {
// When we get here, we know "db" is ready!
// Register the repository as "todos".
container.register({
todos: new TodosRepository(container.db)
})
});
}
This time we used a class (instead of functions that take the container as the first argument), and so we used container.dependsOn
to defer registration
until the db
module was ready.
For good measure, let's cover the database module as well.
db/db.js
// We require the provider here as registering
// it with the container won't be needed.
const someDbProvider = require('some-db-provider');
export function connect() {
// Returns a promise...
return someDbProvider.connect({
host: 'localhost'
});
}
export default function(container) {
// We are returning a promise. Yes, this is supported.
return connect().then(database => {
container.register({
db: database
})
});
}
Okay! We have written our modules, now we need to connect them.
In our app's main script (where we imported Awilix), we are going to load our modules. For clarity, I've included everything here.
index.js
const awilix = require('awilix');
// Create the container.
const container = awilix.createContainer();
// Load our modules.
container.loadModules([
// Awilix uses `glob` to resolve modules.
// If you've used `dependsOn` correctly, or just using
// the functional aproach (e.g. `todosService.js`), the
// ordering won't matter.
'services/*.js',
'repositories/*.js',
'db/db.js'
]).then(() => {
// Our container is ready! Start your server or whatever..
// Most importantly, we can use our todos service!
const todosService = container.todosService;
todosService.getTodos('use awilix').then(todos => {
// Success! To the pub!
console.log(todos);
});
});
Note how in getTodos
, we did not specify the container as the first argument! The observant reader may have remembered that we used a little
function called bindAll
in todosService.js
.
That's it for the mini-guide. Be sure to read the short API docs below so you know what's possible.
So in the above example, you might have noticed a pattern:
module.exports = function(container) {
container.register({
someStuff: 'yay',
someFunction: container.bind(someFunction),
someObject: container.bindAll({
moreFunctions: moreFunctions
})
})
}
This is what I refer to as the Awilix Container Pattern (ACP), and is what the loadModules
API uses to let you register your stuff with the container in a "discovery-based" manner.
To make a module eligible for loading through loadModules
, it needs a default exported function that takes the container as the first parameter. The function is reponsible for registering things with the container using container.register
.
An ACP function MAY:
Promise
container.dependsOn
to declare dependencies up-front (see corresponding section)Example in ES5:
// When the module does not export anything else..
module.exports = function(container) {
}
// When the module exports more than default.
module.exports.default = function(container) {
}
Example in ES6:
export default function(container) {
}
// With an arrow function
export default container => {
};
awilix
objectWhen importing awilix
, you get the following stuff:
createContainer
listModules
AwilixResolutionError
These are documented below.
createContainer()
Creates a new Awilix container. The container stuff is documented further down.
listModules()
Returns a promise for a list of {name, path}
pairs,
where the name is the module name, and path is the actual
full path to the module.
This is used internally, but is useful for other things as well, e.g.
dynamically loading an api
folder.
Args:
globPatterns
: a glob pattern string, or an array of them.opts.cwd
: The current working directory passed to glob
. Defaults to process.cwd()
.Promise
for an array of objects with:
name
: The module name - e.g. db
path
: The path to the module relative to options.cwd
- e.g. lib/db.js
Example:
const listModules = require('awilix').listModules;
listModules([
'services/*.js'
]).then(result => {
console.log(result);
// << [{ name: 'someService', path: 'path/to/services/someService.js' }]
})
AwilixResolutionError
This is a special error thrown when Awilix is unable to resolve all dependencies (due to dependOn
). You can catch this error and use err instanceof AwilixResolutionError
if you wish. It will tell you what
dependencies it could not find.
AwilixContainer
objectThe container returned from createContainer
has some methods and properties.
container.registeredModules
A hash that contains all registered modules. Anything in there will also be present on the container
itself.
container.bind()
Creates a new function where the first parameter of the given function will always be the container it was bound to.
Args:
fn
: The function to bind.ctx
: The this
-context for the function.Example:
container.one = 1;
const myFunction = (container, arg1, arg2) => arg1 + arg2 + container.one;
const bound = container.bind(myFunction);
bound(2, 3);
// << 6
container.bindAll()
Given an object, binds all it's functions to the container and assigns it to the given object. The object is returned.
Args:
obj
: Object with functions to bind.obj
Example:
const obj = {
method1: (container, arg1, arg2) => arg1 + arg2,
method2: (container, arg1, arg2) => arg1 - arg2
};
const boundObj = container.bindAll(obj);
boundObj === obj;
// << true
obj.method1(1, 2);
// << 3
container.register()
Given an object, registers one or more things with the container. The values can be anything, and therefore are not bound automatically.
Args:
obj
: Object with things to register. Key is the what others will address the module as, value is the module itself.container
.Example:
const addTodo = (container, text) => { /* ...*/ };
const connect = container => awesomeDb.connect(container.DB_HOST);
container.register({
// Any value goes.
DB_HOST: 'localhost',
// using bindAll
todoService: container.bindAll({
addTodo: addTodo
}),
// Not bound.
log: function(text) {
console.log('AWILIX DEMO:', text);
},
connect: container.bind(connect)
});
// Exposed on the container as well as `registeredModules`.
container.todoService === container.registeredModules.todoService;
// << true
container.todoService.addTodo('follow and start awilix repo');
console.log(container.DB_HOST);
// << localhost
container.log('Hello!');
// << AWILIX DEMO: Hello!
connect();
container.loadModules()
Given an array of globs, returns a Promise
when loading is done.
Awilix will use require
on the loaded modules, and call their default exported function (if it is a function, that is..) with the container as the first parameter (this is the Awilix Container Pattern (ACP)). This function then gets to do the registration of one or more modules.
Args:
globPatterns
: Array of glob patterns that match JS files to load.opts.cwd
: The cwd
being passed to glob
. Defaults to process.cwd()
.Promise
for when we're done. This won't be resolved until all modules are ready.Example:
// index.js
container.loadModules([
'services/*.js',
'repositories/*.js',
'db/db.js'
]).then(() => {
console.log('We are ready!');
container.todoService.addTodo('go to the pub');
});
container.dependsOn()
Used in conjunction with loadModules
, makes it easy to state up-front what
your module needs, and then get notified once it's ready. This is useful for doing constructor injection where you grab the dependencies off the container
at construction time rather than at function-call-time.
I recommend using the functional approach as it's less complex, but if you must, this method works perfectly fine as well. It's just a bit more verbose.
Args:
dependencies
: Array of strings that map to the modules being grabbed off the container - e.g. 'db'
when using container.db
.Example:
// repositories/todosRepository.js
class TodosRepository {
constructor(dependencies) {
// We save the reference here, so it *has* to exist at construction-time!
this.db = dependencies.db;
}
}
// We are not using any named exports, so we
// don't have to use module.exports.default.
module.exports = container => {
container.dependsOn(['db'], () => {
container.register({
todos: new TodosRepository(container)
})
});
}
Clone repo, run npm i
to install all dependencies, and then npm run test-watch
+ npm run lint-watch
to start writing code.
For code coverage, run npm run coverage
.
If you submit a PR, please aim for 100% code coverage and no linting errors. Travis will fail if there are linting errors. Thank you for your considering contributing. :)
Awilix is the mayan goddess of the moon, and also my favorite character in the game SMITE.
Jeff Hansen - @Jeffijoe
FAQs
Extremely powerful dependency injection container.
The npm package awilix receives a total of 156,678 weekly downloads. As such, awilix popularity was classified as popular.
We found that awilix demonstrated a healthy version release cadence and project activity because the last version was released less than 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.
Product
Socket now supports four distinct alert actions instead of the previous two, and alert triaging allows users to override the actions taken for all individual alerts.
Security News
Polyfill.io has been serving malware for months via its CDN, after the project's open source maintainer sold the service to a company based in China.
Security News
OpenSSF is warning open source maintainers to stay vigilant against reputation farming on GitHub, where users artificially inflate their status by manipulating interactions on closed issues and PRs.