You're Invited:Meet the Socket Team at BlackHat and DEF CON in Las Vegas, Aug 7-8.RSVP
Socket
Socket
Sign inDemoInstall

awilix

Package Overview
Dependencies
Maintainers
1
Versions
81
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

awilix

Simple dependency injection container + helper utilities to facilitate wiring of modules


Version published
Weekly downloads
160K
decreased by-0.74%
Maintainers
1
Created
Weekly downloads
 

Package description

What is awilix?

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.

What are awilix's main functionalities?

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'
  }
});

Other packages similar to awilix

Changelog

Source

2.2.6

  • Pass in the module descriptor to formatName - thanks @anasinnyk!
  • Fixed some issues with TypeScript definitions.

Readme

Source

Awilix

Join the chat at https://gitter.im/awilix/Lobby npm dependency Status devDependency Status Build Status Coveralls Code Climate npm npm node JavaScript Style Guide

Extremely powerful Inversion of Control (IoC) container for Node with dependency resolution support powered by ES6 Proxies. Make IoC great again!

Check out this intro to Dependency Injection with Awilix

Table of Contents

Installation

npm install awilix --save

Requires Node v6 or above

Usage

Awilix has a pretty simple API. At minimum, you need to do 3 things:

  • Create a container
  • Register some modules in it
  • Resolve and use!

index.js

const awilix = require('awilix');

// Create the container.
const container = awilix.createContainer();

// This is our app code... We can use
// factory functions, constructor functions
// and classes freely.
class UserController {
  // We are using constructor injection.
  constructor(opts) {
    // Save a reference to our dependency.
    this.userService = opts.userService;
  }

  // imagine ctx is our HTTP request context...
  getUser(ctx) {
    return this.userService.getUser(ctx.params.id);
  }
}

container.registerClass({
  // Here we are telling Awilix how to resolve a
  // userController: by instantiating a class.
  userController: UserController
});

// Let's try with a factory function.
const makeUserService = ({ db }) => {
  // Notice how we can use destructuring
  // to access dependencies
  return {
    getUser: (id) => {
      return db.query(`select * from users where id=${id}`);
    }
  };
};

container.registerFunction({
  // the `userService` is resolved by
  // invoking the function.
  userService: makeUserService
});

// Alright, now we need a database.
// Let's make that a constructor function.
function Database({ connectionString }) {
  // We can inject plain values as well!
  this.conn = connectToYourDatabaseSomehow(connectionString);
}

Database.prototype.query = function(sql) {
  // blah....
  return this.conn.rawSql(sql);
}

// We use registerClass because we want it to be called with `new`.
container.registerClass({
  db: Database
})

// Lastly we register the connection string,
// as we need it in the Database constructor.
container.registerValue({
  // We can register things as-is - this is not just
  // limited to strings and numbers, it can be anything,
  // really - they will be passed through directly.
  connectionString: process.env.CONN_STR
});


// We have now wired everything up!
// Let's use it! (use your imagination with the router thing..)
router.get('/api/users/:id', container.resolve('userController').getUser);

// Alternatively..
router.get('/api/users/:id', container.cradle.userController.getUser);

// Using  `container.cradle.userController` is actually the same as calling
// `container.resolve('userController')` - the cradle is our proxy!

That example looks big, but if you extract things to their proper files, it becomes rather elegant!

Check out a working Koa example!

Lifetime management

Awilix supports managing the lifetime of registrations. This means that you can control whether objects are resolved and used once, or cached for the lifetime of the process.

There are 3 lifetime types available.

  • TRANSIENT: This is the default. The registration is resolved every time it is needed. This means if you resolve a class more than once, you will get back a new instance every time.
  • SCOPED: The registration is scoped to the container - that means that the resolved value will be reused when resolved from the same scope.
  • SINGLETON: The registration is always reused no matter what.

They are exposed on the awilix.Lifetime object.

const Lifetime = awilix.Lifetime;

To register a module with a specific lifetime:

class MailService {}

container.registerClass({
  mailService: [MailService, { lifetime: Lifetime.SINGLETON }]
});

// or using the registration functions directly..
const { asClass, asFunction, asValue } = awilix;
container.register({
  mailService: asClass(MailService).lifetime(Lifetime.SINGLETON)
});

// or..
container.register({
  mailService: asClass(MailService).singleton()
});

// all roads lead to rome
container.register({
  mailService: asClass(MailService, { lifetime: Lifetime.SINGLETON })
});
// seriously..
container.registerClass('mailService', MailService, { lifetime: SINGLETON });

Scoped lifetime

In web applications, managing state without depending too much on the web framework can get difficult. Having to pass tons of information into every function just to make the right choices based on the authenticated user.

Scoped lifetime in Awilix makes this simple - and fun!

const { createContainer, asClass, asValue } = awilix;
const container = createContainer();

class MessageService {
  constructor({ currentUser }) {
    this.user = currentUser;
  }

  getMessages() {
    const id = this.user.id;
    // wee!
  }
}

container.register({
  messageService: asClass(MessageService).scoped()
});

// imagine middleware in some web framework..
app.use((req, res, next) => {
  // create a scoped container
  req.scope = container.createScope();

  // register some request-specific data..
  req.scope.register({
    currentUser: asValue(req.user)
  });

  next();
});

app.get('/messages', (req,res) => {
  // for each request we get a new message service!
  const messageService = req.scope.resolve('messageService');
  messageService.getMessages().then(messages => {
    res.send(200, messages)
  });
});

// The message service can now be tested
// without depending on any request data!

IMPORTANT! If a singleton is resolved, and it depends on a scoped or transient registration, those will remain in the singleton for it's lifetime!

const makePrintTime = ({ time }) => () => {
  console.log('Time:', time);
};

const getTime = () => new Date().toString();

container.register({
  printTime: asFunction(makePrintTime).singleton(),
  time: asFunction(getTime).transient()
});

// Resolving `time` 2 times will
// invoke `getTime` 2 times.
container.resolve('time')
container.resolve('time')

// These will print the same timestamp at all times,
// because `printTime` is singleton and
// `getTime` was invoked when making the singleton.
container.resolve('printTime')();
container.resolve('printTime')();

Read the documentation for container.createScope() for more examples.

Auto-loading modules

When you have created your container, registering 100's of classes can get boring. You can automate this by using loadModules.

Imagine this app structure:

  • app
    • services
      • UserService.js - exports an ES6 class UserService {}
      • emailService.js - exports a factory function function makeEmailService() {}
    • repositories
      • UserRepository.js - exports an ES6 class UserRepository {}
    • index.js - our main script

In our main script we would do the following

const awilix = require('awilix');

const container = awilix.createContainer();

// Load our modules!
container.loadModules([
  // Globs!
  'services/**/*.js',
  'repositories/**/*.js'
], {
  // We want to register `UserService` as `userService` -
  // by default loaded modules are registered with the
  // name of the file (minus the extension)
  formatName: 'camelCase',
  // We can give these auto-loaded modules
  // the deal of a lifetime! (see what I did there?)
  // By default it's `TRANSIENT`.
  registrationOptions: {
    lifetime: Lifetime.SINGLETON
  }
});

// We are now ready! We now have a userService, userRepository and emailService!
container.resolve('userService').getUser(1);

API

The awilix object

When importing awilix, you get the following top-level API:

  • createContainer
  • listModules
  • AwilixResolutionError
  • asValue
  • asFunction
  • asClass
  • Lifetime - this one is documented above.

These are documented below.

createContainer()

Creates a new Awilix container. The container stuff is documented further down.

Args:

  • options: Options object. Optional.
    • options.require: The function to use when requiring modules. Defaults to require. Useful when using something like require-stack. Optional.

listModules()

Returns an array 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().
  • returns: 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;

const result = listModules([
  'services/*.js'
]);

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 missing or cyclic dependencies). You can catch this error and use err instanceof AwilixResolutionError if you wish. It will tell you what dependencies it could not find or which ones caused a cycle.

The AwilixContainer object

The container returned from createContainer has some methods and properties.

container.cradle

Behold! This is where the magic happens! The cradle is a proxy, and all getters will trigger a container.resolve. The cradle is actually being passed to the constructor/factory function, which is how everything gets wired up.

container.registrations

A read-only getter that returns the internal registrations. When invoked on a scope, will show registrations for it's parent, and it's parent's parent, and so on.

Not really useful for public use.

container.cache

An object used internally for caching resolutions. It's a plain object. Not meant for public use but if you find it useful, go ahead but tread carefully.

Each scope has it's own cache, and checks the cache of it's parents.

let counter = 1;
container.register({
  count: asFunction(() => counter++).singleton()
});

container.cradle.count === 1
container.cradle.count === 1

delete container.cache.count;
container.cradle.count === 2

container.resolve()

Resolves the registration with the given name. Used by the cradle.

container.registerFunction({
  leet: () => 1337
});

container.resolve('leet') === 1337
container.cradle.leet === 1337

container.register()

Registers modules with the container. This function is used by the registerValue, registerFunction and registerClass functions.

Awilix needs to know how to resolve the modules, so let's pull out the registration functions:

const awilix = require('awilix');
const { asValue, asFunction, asClass } = awilix;
  • asValue: Resolves the given value as-is.
  • asFunction: Resolve by invoking the function with the container cradle as the first and only argument.
  • asClass: Like asFunction but uses new.

Now we need to use them. There are multiple syntaxes for the register function, pick the one you like the most - or use all of them, I don't really care! :sunglasses:

Both styles supports chaining! register returns the container!

// name-value-options
container.register('connectionString', asValue('localhost:1433;user=...'));
container.register('mailService', asFunction(makeMailService), { lifetime: Lifetime.SINGLETON });
container.register('context', asClass(SessionContext), { lifetime: Lifetime.SCOPED });

// object
container.register({
  connectionString: asValue('localhost:1433;user=...'),
  mailService: asFunction(makeMailService, { lifetime: Lifetime.SINGLETON }),
  context: asClass(SessionContext, { lifetime: Lifetime.SCOPED })
})

// `registerFunction` and `registerClass` also supports a fluid syntax.
container.register('mailService', asFunction(makeMailService).setLifetime(Lifetime.SINGLETON))
container.register('context', asClass(SessionContext).singleton())
container.register('context', asClass(SessionContext).transient())
container.register('context', asClass(SessionContext).scoped())

The object syntax, key-value syntax and chaining are valid for all register calls!

container.registerValue()

Registers a constant value in the container. Can be anything.

container.registerValue({
  someName: 'some value',
  db: myDatabaseObject
});

// Alternative syntax:
container.registerValue('someName', 'some value');
container.registerValue('db', myDatabaseObject);

// Chaining
container
  .registerValue('someName', 'some value')
  .registerValue('db', myDatabaseObject);

container.registerFunction()

Registers a standard function to be called whenever being resolved. The factory function can return anything it wants, and whatever it returns is what is passed to dependents.

By default all registrations are TRANSIENT, meaning resolutions will not be cached. This is configurable on a per-registration level.

The array syntax for values means [value, options]. This is also valid for registerClass.

const myFactoryFunction = ({ someName }) => (
  `${new Date().toISOString()}: Hello, this is ${someName}`
);

container.registerFunction({ fullString: myFactoryFunction });
console.log(container.cradle.fullString);
// << 2016-06-24T16:00:00.00Z: Hello, this is some value

// Wait 2 seconds, try again
setTimeout(() => {
  console.log(container.cradle.fullString);
  // << 2016-06-24T16:00:02.00Z: Hello, this is some value

  // The timestamp is different because the
  // factory function was called again!
}, 2000);

// Let's try this again, but we want it to be
// cached!
const Lifetime = awilix.Lifetime;
container.registerFunction({
  fullString: [myFactoryFunction, { lifetime: Lifetime.SINGLETON }]
});

console.log(container.cradle.fullString);
// << 2016-06-24T16:00:02.00Z: Hello, this is some value

// Wait 2 seconds, try again
setTimeout(() => {
  console.log(container.cradle.fullString);
  // << 2016-06-24T16:00:02.00Z: Hello, this is some value

  // The timestamp is the same, because
  // the factory function's result was cached.
}, 2000);

container.registerClass()

Same as registerFunction, except it will use new.

By default all registrations are TRANSIENT, meaning resolutions will not be cached. This is configurable on a per-registration level.

class Exclaimer {
  constructor({ fullString }) {
    this.fullString = fullString;
  }

  exclaim() {
    return this.fullString + '!!!!!';
  }
}

container.registerClass({
  exclaimer: Exclaimer
});

// or..
container.registerClass({
  exclaimer: [Exclaimer, { lifetime: Lifetime.SINGLETON }]
});

container.cradle.exclaimer.exclaim();
// << 2016-06-24T17:00:00.00Z: Hello, this is some value!!!!!

container.createScope()

Creates a new scope. All registrations with a Lifetime.SCOPED will be cached inside a scope. A scope is basically a "child" container.

  • returns AwilixContainer
// Increments the counter every time it is resolved.
let counter = 1;
container.register({
  counterValue: asFunction(() => counter++).scoped()
});
const scope1 = container.createScope();
const scope2 = container.createScope();

const scope1Child = scope1.createScope();

scope1.cradle.counterValue === 1
scope1.cradle.counterValue === 1
scope2.cradle.counterValue === 2
scope2.cradle.counterValue === 2

scope1Child.cradle.counterValue === 1

Be careful! If a scope's parent has already resolved a scoped value, that value will be returned.

let counter = 1;
container.register({
  counterValue: asFunction(() => counter++).scoped()
});
const scope1 = container.createScope();
const scope2 = container.createScope();

container.cradle.counterValue === 1

// These are resolved to the parent's cached value.
scope1.cradle.counterValue === 1
scope1.cradle.counterValue === 1
scope2.cradle.counterValue === 1
scope2.cradle.counterValue === 1

A scope may also register additional stuff - they will only be available within that scope and it's children.

// Register a transient function
// that returns the value of the scope-provided dependency.
// For this example we could also use scoped lifetime.
container.register({
  scopedValue: asFunction((cradle) => 'Hello ' + cradle.someValue)
});

// Create a scope and register a value.
const scope = container.createScope();
scope.register({
  someValue: asValue('scope')
});

scope.cradle.scopedValue === 'Hello scope';
container.cradle.someValue
// throws AwilixResolutionException
// because the root container does not know
// of the registration.

Things registered in the scope take precedence over it's parent.

// It does not matter when the scope is created,
// it will still have anything that is registered
// in it's parent.
const scope = container.createScope();

container.register({
  value: asValue('root'),
  usedValue: asFunction((cradle) => cradle.value)
});

scope.register({
  value: asValue('scope')
});

container.cradle.usedValue === 'root'
scope.cradle.usedValue === 'scope'

container.loadModules()

Given an array of globs, registers the modules and returns the container.

Awilix will use require on the loaded modules, and register the default-exported function or class as the name of the file.

This will not work for constructor functions (function Database{} ...), because there is no way to determine when to use new. Internally, Awilix uses is-class which only works for ES6 classes.

Args:

  • globPatterns: Array of glob patterns that match JS files to load.
  • opts.cwd: The cwd being passed to glob. Defaults to process.cwd().
  • opts.formatName: Can be either 'camelCase', or a function that takes the current name as the first parameter and returns the new name. Default is to pass the name through as-is.
  • registrationOptions: An object passed to the registrations. Used to configure the lifetime of the loaded modules.

Example:

// index.js
container.loadModules([
  'services/*.js',
  'repositories/*.js',
  'db/db.js'
])

container.cradle.userService.getUser(123);

// to configure lifetime for all modules loaded..
container.loadModules([
  'services/*.js',
  'repositories/*.js',
  'db/db.js'
], {
  registrationOptions: {
    lifetime: Lifetime.SINGLETON
  }
});

container.cradle.userService.getUser(123);

// to configure lifetime for specific globs..
container.loadModules([
  ['services/*.js', Lifetime.SCOPED], // all services will have scoped lifetime
  'repositories/*.js',
  'db/db.js'
], {
  registrationOptions: {
    lifetime: Lifetime.SINGLETON // db and repositories will be singleton
  }
});

container.cradle.userService.getUser(123);

The ['glob', Lifetime.SCOPED] syntax is a shorthand for passing in registration options like so: ['glob', { lifetime: Lifetime.SCOPED }]

Contributing

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 considering contributing. :)

What's in a name?

Awilix is the mayan goddess of the moon, and also my favorite character in the game SMITE.

Author

Jeff Hansen - @Jeffijoe

Keywords

FAQs

Package last updated on 27 Apr 2017

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc