# awilix-manager

[![NPM Version](https://img.shields.io/npm/v/awilix-manager.svg)](https://npmjs.org/package/awilix-manager)
[![Build Status](https://github.com/kibertoad/awilix-manager/workflows/ci/badge.svg)](https://github.com/kibertoad/awilix-manager/actions)
[![Coverage Status](https://coveralls.io/repos/kibertoad/awilix-manager/badge.svg?branch=main)](https://coveralls.io/r/kibertoad/awilix-manager?branch=main)

Wrapper over awilix to support more complex use-cases

## Getting started

First install the package:

```bash
npm i awilix-manager
```

Next, set up your DI configuration:

```js
import { AwilixManager } from 'awilix-manager'
import { asClass, createContainer } from 'awilix'

class AsyncClass {
  async init() {
    // init logic
  }

  async dispose() {
    // dispose logic
  }
}

const diContainer = createContainer({
  injectionMode: 'PROXY',
})

diContainer.register(
  'dependency1',
  asClass(AsyncClass, {
    lifetime: 'SINGLETON',
    asyncInitPriority: 10, // lower value means its initted earlier
    asyncDisposePriority: 10, // lower value means its disposed earlier
    asyncInit: 'init',
    asyncDispose: 'dispose',
    eagerInject: true, // this will be constructed and cached immediately. Redundant for resolves with `asyncInit` parameter set, as that is always resolved eagerly. If a string is passed, then additional synchronous method will be invoked in addition to constructor on injection.
  }),
)

const awilixManager = new AwilixManager({
  diContainer,
  asyncInit: true,
  asyncDispose: true,
  strictBooleanEnforced: true,  
})
await awilixManager.executeInit() // this will execute eagerInject and asyncInit
await awilixManager.executeDispose() // this will execute asyncDispose
```

## Disabling eager injection conditionally

In some cases you may want to prevent eager injection and async disposal of some of your dependencies - e. g. when you want to disable all of your background jobs or message consumers in some of your integration tests.
You can use `enabled` resolver parameter for that:

```js
import { AwilixManager } from 'awilix-manager'
import { asClass, createContainer } from 'awilix'

class QueueConsumerClass {
  async consume() {
    // consumer registration logic
  }

  async destroy() {
    // dispose logic
  }
}

const diContainer = createContainer({
  injectionMode: 'PROXY',
})

const isAMQPEnabled = false // disable consumers, e. g. for tests

diContainer.register(
  'dependency1',
  asClass(QueueConsumerClass, {
    lifetime: 'SINGLETON',
    asyncInitPriority: 10, // lower value means its initted earlier
    asyncDisposePriority: 10, // lower value means its disposed earlier
    asyncInit: 'consume',
    asyncDispose: 'destroy',
    enabled: isAMQPEnabled, // default is true
  }),
)

const awilixManager = new AwilixManager({
  diContainer,
  asyncInit: true,
  asyncDispose: true,
  strictBooleanEnforced: true,    
})
await awilixManager.executeInit() // this will not execute asyncInit, because consumer is disabled
await awilixManager.executeDispose() // this will not execute asyncDispose, because consumer is disabled
```

Note that passing `undefined` or `null` as a value for the `enabled` parameter counts as a default, which is `true`. That may lead to hard-to-debug errors, as it may be erroneously assumed that passing falsy value should equal to passing `false`. In order to prevent this, it is recommended to set `strictBooleanEnforced` flag to `true`, which would throw an error if a non-boolean value is explicitly set to the `enabled` field. In future semver major release this will become a default behaviour.

## Fetching dependencies based on tags

In some cases you may want to get dependencies based on a supplied list of tags. 
You can use `tags` parameter in conjunction with the `getWithTags` method for that:

```js
import { AwilixManager } from 'awilix-manager'
import { asClass, createContainer } from 'awilix'

const diContainer = createContainer({
  injectionMode: 'PROXY',
})

class QueueConsumerHighPriorityClass {
}

class QueueConsumerLowPriorityClass {
}

diContainer.register(
  'dependency1',
  asClass(QueueConsumerHighPriorityClass, {
    lifetime: 'SINGLETON',
    asyncInit: true,
    tags: ['queue', 'high-priority'],
  }),
)
diContainer.register(
  'dependency2',
  asClass(QueueConsumerLowPriorityClass, {
    lifetime: 'SINGLETON',
    asyncInit: true,
    tags: ['queue', 'low-priority'],
  }),
)

const awilixManager = new AwilixManager({
  diContainer,
  asyncInit: true,
  asyncDispose: true,
})

// This will return a record with dependency1 and dependency2
const result1 = awilixManager.getWithTags(['queue'])
// This will return a record with only dependency2
const result2 = awilixManager.getWithTags(['queue', 'low-priority'])
```

## Fetching dependencies based on a predicate

In some cases you may want to get dependencies based on whether they satisfy some condition.
You can use `getByPredicate` method for that:

```js
import { AwilixManager } from 'awilix-manager'
import { asClass, createContainer } from 'awilix'

const diContainer = createContainer({
  injectionMode: 'PROXY',
})

class QueueConsumerHighPriorityClass {
}

class QueueConsumerLowPriorityClass {
}

diContainer.register(
  'dependency1',
  asClass(QueueConsumerHighPriorityClass, {
    lifetime: 'SINGLETON',
    asyncInit: true,
  }),
)
diContainer.register(
  'dependency2',
  asClass(QueueConsumerLowPriorityClass, {
    lifetime: 'SINGLETON',
    asyncInit: true,
  }),
)

const awilixManager = new AwilixManager({
  diContainer,
  asyncInit: true,
  asyncDispose: true,
})

// This will return a record with dependency1
const result1 = awilixManager.getByPredicate((entry) => entry instanceof QueueConsumerHighPriorityClass)
// This will return a record with dependency2
const result2 = awilixManager.getByPredicate((entry) => entry instanceof QueueConsumerLowPriorityClass))
```

Note that this will resolve all non-disabled dependencies within the container, even the ones without eager injection enabled.

## Mocking dependencies

Sometimes you may want to intentionally inject objects that do not fully conform to the type definition of an original class. For that you can use `asMockClass`, `asMockFunction` or `asMockValue` resolvers:

```ts
type DiContainerType = {
    realClass: RealClass
    realClass2: RealClass
}
const diConfiguration: NameAndRegistrationPair<DiContainerType> = {
    realClass: asClass(RealClass),
    realClass2: asMockClass(FakeClass),
}

const diContainer = createContainer<DiContainerType>({
    injectionMode: 'PROXY',
})

for (const [dependencyKey, dependencyValue] of Object.entries(diConfiguration)) {
    diContainer.register(dependencyKey, dependencyValue as Resolver<unknown>)
}

const { realClass, realClass2 } = diContainer.cradle
expect(realClass).toBeInstanceOf(RealClass)
expect(realClass2).toBeInstanceOf(FakeClass)
```
