MCPcopy
hub / github.com/jeffijoe/awilix

github.com/jeffijoe/awilix @v13.0.5 sqlite

repository ↗ · DeepWiki ↗ · release v13.0.5 ↗
279 symbols 625 edges 58 files 62 documented · 22%
README

Awilix

npm CI Coveralls npm npm node JavaScript Style Guide

Extremely powerful, performant, & battle-tested Dependency Injection (DI) container for JavaScript/Node, written in TypeScript.

Awilix enables you to write composable, testable software using dependency injection without special annotations, which in turn decouples your core application code from the intricacies of the DI mechanism.

💡 Check out this intro to Dependency Injection with Awilix

Table of Contents

Installation

Install with npm

npm install awilix

Or yarn

yarn add awilix

You can also use the UMD build from unpkg

<script src="https://unpkg.com/awilix/lib/awilix.umd.js" />
<script>
  const container = Awilix.createContainer()
</script>

Usage

Awilix has a pretty simple API (but with many possible ways to invoke it). 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 and set the injectionMode to PROXY (which is also the default).
// Enable strict mode for extra correctness checks (highly recommended).
const container = awilix.createContainer({
  injectionMode: awilix.InjectionMode.PROXY,
  strict: true,
})

// 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.register({
  // Here we are telling Awilix how to resolve a
  // userController: by instantiating a class.
  userController: awilix.asClass(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.register({
  // the `userService` is resolved by
  // invoking the function.
  userService: awilix.asFunction(makeUserService),
})

// Alright, now we need a database.
// Let's make that a constructor function.
// Notice how the dependency is referenced by name
// directly instead of destructuring an object.
// This is because we register it in "CLASSIC"
// injection mode below.
function Database(connectionString, timeout) {
  // We can inject plain values as well!
  this.conn = connectToYourDatabaseSomehow(connectionString, timeout)
}

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

// We use register coupled with asClass to tell Awilix to
// use `new Database(...)` instead of just `Database(...)`.
// We also want to use `CLASSIC` injection mode for this
// registration. Read more about injection modes below.
container.register({
  db: awilix.asClass(Database).classic(),
})

// Lastly we register the connection string and timeout values
// as we need them in the Database constructor.
container.register({
  // 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: awilix.asValue(process.env.CONN_STR),
  timeout: awilix.asValue(1000),
})

// 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, using the `cradle` proxy..
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 is rather lengthy, but if you extract things to their proper files it becomes more manageable.

Check out a working Koa example!

Lifetime management

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

There are 3 lifetime types available.

  • Lifetime.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.
  • Lifetime.SCOPED: The registration is scoped to the container - that means that the resolved value will be reused when resolved from the same scope (or a child scope).
  • Lifetime.SINGLETON: The registration is always reused no matter what - that means that the resolved value is cached in the root container.

They are exposed on the awilix.Lifetime object.

const Lifetime = awilix.Lifetime

To register a module with a specific lifetime:

const { asClass, asFunction, asValue } = awilix

class MailService {}

container.register({
  mailService: asClass(MailService, { lifetime: Lifetime.SINGLETON }),
})

// or using the chaining configuration API..
container.register({
  mailService: asClass(MailService).setLifetime(Lifetime.SINGLETON),
})

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

// or.......
container.register('mailService', asClass(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 its lifetime! Similarly, if a scoped module is resolved, and it depends on a transient registration, that remains in the scoped module for its lifetime. In the example above, if messageService was a singleton, it would be cached in the root container, and would always have the currentUser from the first request. Modules should generally not have a longer lifetime than their dependencies, as this can cause issues of stale data.

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')()

If you want a mismatched configuration like this to error, set strict in the container options. This will trigger the following error at runtime when the singleton printTime is resolved: AwilixResolutionError: Could not resolve 'time'. Dependency 'time' has a shorter lifetime than its ancestor: 'printTime'

In addition, registering a singleton on a scope other than the root container results in unpredictable behavior. In particular, if two different singletons are registered on two different scopes, they will share a cache entry and collide with each other. To throw a runtime error when a singleton is registered on a scope other than the root container, enable strict mode.

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

Strict mode

Strict mode is a new feature in Awilix 10. It enables additional correctness checks that can help you catch bugs early.

In particular, strict mode enables the following checks:

  • When a singleton or scoped registration depends on a transient non-value registration, an error is thrown. This detects and prevents the issue where a shorter lifetime dependency can leak outside its intended lifetime due to its preservation in a longer lifetime module.
  • Singleton registrations on any scopes are disabled. This prevents the issue where a singleton is registered on a scope other than the root container, which results in unpredictable behavior.
  • Singleton resolution is performed using registrations from the root container only, which prevents potential leaks in which scoped registrations are preserved in singletons.

Injection modes

The injection mode determines how a function/constructor receives its dependencies. Pre-2.3.0, only one mode was supported - PROXY - which remains the default mode.

Awilix v2.3.0 introduced an alternative injection mode: CLASSIC. The injection modes are available on awilix.InjectionMode

  • InjectionMode.PROXY (default): Injects a proxy to functions/constructors which looks like a regular object.

js class UserService { constructor(opts) { this.emailService = opts.emailService this.logger = opts.logger } }

or with destructuring:

js class UserService { constructor({ emailService, logger }) { this.emailService = emailService this.logger = logger } }

  • InjectionMode.CLASSIC: Parses the function/constructor parameters, and matches them with registrations in the container. CLASSIC mode has a slightly higher initialization cost as it has to parse the function/class to figure out the dependencies at the time of registration, however resolving them will be much faster than when using PROXY. Don't use CLASSIC if you minify your code! We recommend using CLASSIC in Node and PROXY in environments where minification is needed.

js class UserService { constructor(emailService, logger) { this.emailService = emailService this.logger = logger } }

Additionally, if the class has a base class but does not declare a constructor of its own, Awilix simply invokes the base constructor with whatever dependencies it requires.

```js class Car { constructor(engine) { this.engine = engine } }

class Porsche exten

Extension points exported contracts — how you extend this code

LoadedModuleDescriptor (Interface)
(no doc)
src/load-modules.ts
AwilixContainer (Interface)
(no doc)
src/container.ts
Token (Interface)
(no doc)
src/function-tokenizer.ts
Resolver (Interface)
(no doc)
src/resolvers.ts
Parameter (Interface)
(no doc)
src/param-parser.ts
ListModulesOptions (Interface)
(no doc)
src/list-modules.ts
AppConfig (Interface)
(no doc)
src/__tests__/type-inference.test.ts
ICradle (Interface)
(no doc)
examples/typescript/src/index.ts

Core symbols most depended-on inside this repo

asFunction
called by 149
src/resolvers.ts
resolve
called by 148
src/resolvers.ts
register
called by 133
src/container.ts
asValue
called by 85
src/resolvers.ts
asClass
called by 58
src/resolvers.ts
createScope
called by 49
src/container.ts
parseParameterList
called by 41
src/param-parser.ts
loadModules
called by 16
src/load-modules.ts

Shape

Function 114
Class 80
Method 62
Interface 22
Enum 1

Languages

TypeScript100%

Modules by API surface

src/resolvers.ts38 symbols
src/container.ts31 symbols
src/__tests__/container.test.ts27 symbols
src/__tests__/resolvers.test.ts18 symbols
src/function-tokenizer.ts15 symbols
src/errors.ts15 symbols
src/load-modules.ts11 symbols
src/__tests__/load-modules.test.ts11 symbols
src/__tests__/local-injections.test.ts10 symbols
src/__tests__/type-inference.test.ts8 symbols
src/__tests__/inheritance.test.js8 symbols
src/__tests__/container.disposing.test.ts8 symbols

Dependencies from manifests, versioned

@babel/core7.29.7 · 1×
@babel/plugin-transform-runtime7.29.7 · 1×
@babel/preset-env7.29.7 · 1×
@babel/runtime7.29.7 · 1×
@rollup/plugin-replace6.0.3 · 1×
@types/jest30.0.0 · 1×
@types/node25.9.3 · 1×
babel-cli6.24.1 · 1×
babel-jest30.4.1 · 1×
babel-preset-es20156.24.1 · 1×
eslint9.22.0 · 1×
fast-glob3.3.3 · 1×

Datastores touched

appDatabase · 1 repos

For agents

$ claude mcp add awilix \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact