UNPKG

39.9 kBMarkdownView Raw
1# Awilix
2
3[![Join the chat at https://gitter.im/awilix/Lobby](https://badges.gitter.im/awilix/Lobby.svg)](https://gitter.im/awilix/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
4[![npm](https://img.shields.io/npm/v/awilix.svg?maxAge=1000)](https://www.npmjs.com/package/awilix)
5[![dependency Status](https://img.shields.io/david/jeffijoe/awilix.svg?maxAge=1000)](https://david-dm.org/jeffijoe/awilix)
6[![devDependency Status](https://img.shields.io/david/dev/jeffijoe/awilix.svg?maxAge=1000)](https://david-dm.org/jeffijoe/awilix)
7[![Build Status](https://img.shields.io/travis/jeffijoe/awilix.svg?maxAge=1000)](https://travis-ci.org/jeffijoe/awilix)
8[![Coveralls](https://img.shields.io/coveralls/jeffijoe/awilix.svg?maxAge=1000)](https://coveralls.io/github/jeffijoe/awilix)
9[![npm](https://img.shields.io/npm/dt/awilix.svg?maxAge=1000)](https://www.npmjs.com/package/awilix)
10[![npm](https://img.shields.io/npm/l/awilix.svg?maxAge=1000)](https://github.com/jeffijoe/awilix/blob/master/LICENSE.md)
11[![node](https://img.shields.io/node/v/awilix.svg?maxAge=1000)](https://www.npmjs.com/package/awilix)
12[![JavaScript Style Guide](https://img.shields.io/badge/code%20style-standard-brightgreen.svg)](http://standardjs.com/)
13
14Extremely powerful **Dependency Injection** (DI) container for JavaScript/Node,
15written in [TypeScript](http://typescriptlang.org). **Make IoC great again!**
16
17> Check out this
18> [intro to Dependency Injection with Awilix](https://medium.com/@Jeffijoe/dependency-injection-in-node-js-2016-edition-f2a88efdd427)
19
20# Table of Contents
21
22- [Awilix](#awilix)
23- [Table of Contents](#table-of-contents)
24- [Installation](#installation)
25- [Usage](#usage)
26- [Lifetime management](#lifetime-management)
27 - [Scoped lifetime](#scoped-lifetime)
28- [Injection modes](#injection-modes)
29- [Auto-loading modules](#auto-loading-modules)
30- [Per-module local injections](#per-module-local-injections)
31- [Inlining resolver options](#inlining-resolver-options)
32- [Disposing](#disposing)
33- [API](#api)
34 - [The `awilix` object](#the-awilix-object)
35 - [Resolver options](#resolver-options)
36 - [`createContainer()`](#createcontainer)
37 - [`asFunction()`](#asfunction)
38 - [`asClass()`](#asclass)
39 - [`asValue()`](#asvalue)
40 - [`aliasTo()`](#aliasto)
41 - [`listModules()`](#listmodules)
42 - [`AwilixResolutionError`](#awilixresolutionerror)
43 - [The `AwilixContainer` object](#the-awilixcontainer-object)
44 - [`container.cradle`](#containercradle)
45 - [`container.registrations`](#containerregistrations)
46 - [`container.cache`](#containercache)
47 - [`container.options`](#containeroptions)
48 - [`container.resolve()`](#containerresolve)
49 - [`container.register()`](#containerregister)
50 - [`container.has()`](#containerhas)
51 - [`container.loadModules()`](#containerloadmodules)
52 - [`container.createScope()`](#containercreatescope)
53 - [`container.build()`](#containerbuild)
54 - [`container.dispose()`](#containerdispose)
55- [Universal Module (Browser Support)](#universal-module-browser-support)
56- [Contributing](#contributing)
57- [What's in a name?](#whats-in-a-name)
58- [Author](#author)
59
60# Installation
61
62Install with `npm`
63
64```
65npm install awilix
66```
67
68Or `yarn`
69
70```
71yarn add awilix
72```
73
74You can also use the [UMD](https://github.com/umdjs/umd) build from `unpkg`
75
76```html
77<script src="https://unpkg.com/awilix/lib/awilix.umd.js"/>
78<script>
79const container = Awilix.createContainer()
80</script>
81```
82
83# Usage
84
85Awilix has a pretty simple API (but with many possible ways to invoke it). At
86minimum, you need to do 3 things:
87
88- Create a container
89- Register some modules in it
90- Resolve and use!
91
92`index.js`
93
94```javascript
95const awilix = require('awilix')
96
97// Create the container and set the injectionMode to PROXY (which is also the default).
98const container = awilix.createContainer({
99 injectionMode: awilix.InjectionMode.PROXY
100})
101
102// This is our app code... We can use
103// factory functions, constructor functions
104// and classes freely.
105class UserController {
106 // We are using constructor injection.
107 constructor(opts) {
108 // Save a reference to our dependency.
109 this.userService = opts.userService
110 }
111
112 // imagine ctx is our HTTP request context...
113 getUser(ctx) {
114 return this.userService.getUser(ctx.params.id)
115 }
116}
117
118container.register({
119 // Here we are telling Awilix how to resolve a
120 // userController: by instantiating a class.
121 userController: awilix.asClass(UserController)
122})
123
124// Let's try with a factory function.
125const makeUserService = ({ db }) => {
126 // Notice how we can use destructuring
127 // to access dependencies
128 return {
129 getUser: id => {
130 return db.query(`select * from users where id=${id}`)
131 }
132 }
133}
134
135container.register({
136 // the `userService` is resolved by
137 // invoking the function.
138 userService: awilix.asFunction(makeUserService)
139})
140
141// Alright, now we need a database.
142// Let's make that a constructor function.
143// Notice how the dependency is referenced by name
144// directly instead of destructuring an object.
145// This is because we register it in "CLASSIC"
146// injection mode below.
147function Database(connectionString, timeout) {
148 // We can inject plain values as well!
149 this.conn = connectToYourDatabaseSomehow(connectionString, timeout)
150}
151
152Database.prototype.query = function(sql) {
153 // blah....
154 return this.conn.rawSql(sql)
155}
156
157// We use register coupled with asClass to tell Awilix to
158// use `new Database(...)` instead of just `Database(...)`.
159// We also want to use `CLASSIC` injection mode for this
160// registration. Read more about injection modes below.
161container.register({
162 db: awilix.asClass(Database).classic()
163})
164
165// Lastly we register the connection string and timeout values
166// as we need them in the Database constructor.
167container.register({
168 // We can register things as-is - this is not just
169 // limited to strings and numbers, it can be anything,
170 // really - they will be passed through directly.
171 connectionString: awilix.asValue(process.env.CONN_STR),
172 timeout: awilix.asValue(1000)
173})
174
175// We have now wired everything up!
176// Let's use it! (use your imagination with the router thing..)
177router.get('/api/users/:id', container.resolve('userController').getUser)
178
179// Alternatively, using the `cradle` proxy..
180router.get('/api/users/:id', container.cradle.userController.getUser)
181
182// Using `container.cradle.userController` is actually the same as calling
183// `container.resolve('userController')` - the cradle is our proxy!
184```
185
186That example is rather lengthy, but if you extract things to their proper files
187it becomes more manageable.
188
189[Check out a working Koa example!](/examples/koa)
190
191# Lifetime management
192
193Awilix supports managing the lifetime of instances. This means that you can
194control whether objects are resolved and used once, cached within a certain
195scope, or cached for the lifetime of the process.
196
197There are 3 lifetime types available.
198
199- `Lifetime.TRANSIENT`: This is the default. The registration is resolved every
200 time it is needed. This means if you resolve a class more than once, you will
201 get back a new instance every time.
202- `Lifetime.SCOPED`: The registration is scoped to the container - that means
203 that the resolved value will be reused when resolved from the same scope (or a
204 child scope).
205- `Lifetime.SINGLETON`: The registration is always reused no matter what - that
206 means that the resolved value is cached in the root container.
207
208They are exposed on the `awilix.Lifetime` object.
209
210```js
211const Lifetime = awilix.Lifetime
212```
213
214To register a module with a specific lifetime:
215
216```js
217const { asClass, asFunction, asValue } = awilix
218
219class MailService {}
220
221container.register({
222 mailService: asClass(MailService, { lifetime: Lifetime.SINGLETON })
223})
224
225// or using the chaining configuration API..
226container.register({
227 mailService: asClass(MailService).setLifetime(Lifetime.SINGLETON)
228})
229
230// or..
231container.register({
232 mailService: asClass(MailService).singleton()
233})
234
235// or.......
236container.register('mailService', asClass(MailService, { lifetime: SINGLETON }))
237```
238
239## Scoped lifetime
240
241In web applications, managing state without depending too much on the web
242framework can get difficult. Having to pass tons of information into every
243function just to make the right choices based on the authenticated user.
244
245Scoped lifetime in Awilix makes this simple - and fun!
246
247```js
248const { createContainer, asClass, asValue } = awilix
249const container = createContainer()
250
251class MessageService {
252 constructor({ currentUser }) {
253 this.user = currentUser
254 }
255
256 getMessages() {
257 const id = this.user.id
258 // wee!
259 }
260}
261
262container.register({
263 messageService: asClass(MessageService).scoped()
264})
265
266// imagine middleware in some web framework..
267app.use((req, res, next) => {
268 // create a scoped container
269 req.scope = container.createScope()
270
271 // register some request-specific data..
272 req.scope.register({
273 currentUser: asValue(req.user)
274 })
275
276 next()
277})
278
279app.get('/messages', (req, res) => {
280 // for each request we get a new message service!
281 const messageService = req.scope.resolve('messageService')
282 messageService.getMessages().then(messages => {
283 res.send(200, messages)
284 })
285})
286
287// The message service can now be tested
288// without depending on any request data!
289```
290
291**IMPORTANT!** If a singleton is resolved, and it depends on a scoped or
292transient registration, those will remain in the singleton for it's lifetime!
293
294```js
295const makePrintTime = ({ time }) => () => {
296 console.log('Time:', time)
297}
298
299const getTime = () => new Date().toString()
300
301container.register({
302 printTime: asFunction(makePrintTime).singleton(),
303 time: asFunction(getTime).transient()
304})
305
306// Resolving `time` 2 times will
307// invoke `getTime` 2 times.
308container.resolve('time')
309container.resolve('time')
310
311// These will print the same timestamp at all times,
312// because `printTime` is singleton and
313// `getTime` was invoked when making the singleton.
314container.resolve('printTime')()
315container.resolve('printTime')()
316```
317
318Read the documentation for [`container.createScope()`](#containercreatescope)
319for more examples.
320
321# Injection modes
322
323The injection mode determines how a function/constructor receives its
324dependencies. Pre-2.3.0, only one mode was supported - `PROXY` - which remains
325the default mode.
326
327Awilix v2.3.0 introduced an alternative injection mode: `CLASSIC`. The injection
328modes are available on `awilix.InjectionMode`
329
330- `InjectionMode.PROXY` (default): Injects a proxy to functions/constructors
331 which looks like a regular object.
332
333 ```js
334 class UserService {
335 constructor(opts) {
336 this.emailService = opts.emailService
337 this.logger = opts.logger
338 }
339 }
340 ```
341
342 or with destructuring:
343
344 ```js
345 class UserService {
346 constructor({ emailService, logger }) {
347 this.emailService = emailService
348 this.logger = logger
349 }
350 }
351 ```
352
353- `InjectionMode.CLASSIC`: Parses the function/constructor parameters, and
354 matches them with registrations in the container. _Don't use this if you
355 minify your code!_
356
357 ```js
358 class UserService {
359 constructor(emailService, logger) {
360 this.emailService = emailService
361 this.logger = logger
362 }
363 }
364 ```
365
366 Additionally, if the class has a base class but does not report any dependencies, the base class dependencies are passed in.
367
368 ```js
369 class Car {
370 constructor(engine) {
371 this.engine = engine
372 }
373 }
374
375 class Porsche extends Car {
376 constructor() {
377 super(...arguments)
378 console.log(arguments[0]) // whatever "engine" is
379 }
380 }
381 ```
382
383Injection modes can be set per-container and per-resolver. The most specific one
384wins.
385
386> Note: I personally don't see why you would want to have different injection
387> modes in a project, but if the need arises, Awilix supports it.
388
389**Container-wide**:
390
391```js
392const { createContainer, InjectionMode } = require('awilix')
393
394const container = createContainer({ injectionMode: InjectionMode.CLASSIC })
395```
396
397**Per resolver**:
398
399```js
400const container = createContainer()
401
402container.register({
403 logger: asClass(Logger).classic(),
404 // or..
405 emailService: asFunction(makeEmailService).proxy()
406 // or..
407 notificationService: asClass(NotificationService).setInjectionMode(InjectionMode.CLASSIC)
408})
409
410// or..
411container.register({
412 logger: asClass(Logger, { injectionMode: InjectionMode.CLASSIC })
413})
414```
415
416**For auto-loading modules**:
417
418```js
419const container = createContainer()
420container.loadModules(['services/**/*.js', 'repositories/**/*.js'], {
421 resolverOptions: {
422 injectionMode: InjectionMode.CLASSIC
423 }
424})
425```
426
427Choose whichever fits your style.
428
429- `PROXY` _technically_ allows you to defer pulling dependencies (for circular
430 dependency support), but **this isn't recommended**.
431- `CLASSIC` feels more like the DI you're used to in other languages.
432- `PROXY` is more descriptive, and makes for more readable tests; when unit
433 testing your classes/functions without using Awilix, you don't have to worry
434 about parameter ordering like you would with `CLASSIC`.
435- Performance-wise, `CLASSIC` is _slightly_ faster because it only reads the
436 dependencies from the constructor/function _once_ (when `asClass`/`asFunction`
437 is called), whereas accessing dependencies on the Proxy _may_ incur slight
438 overhead for each resolve.
439- **`CLASSIC` will not work when your code is minified!** It reads the function
440 signature to determine what dependencies to inject. Minifiers will usually
441 mangle these names.
442
443Here's an example outlining the testability points raised.
444
445```js
446// CLASSIC
447function database(connectionString, timeout, logger) {
448 // ...
449}
450
451// Shorter, but less readable, order-sensitive
452const db = database('localhost:1337;user=123...', 4000, new LoggerMock())
453
454// PROXY
455function database({ connectionString, timeout, logger }) {
456 // ...
457}
458
459// Longer, more readable, order does not matter
460const db = database({
461 logger: new LoggerMock(),
462 timeout: 4000,
463 connectionString: 'localhost:1337;user=123...'
464})
465```
466
467# Auto-loading modules
468
469When you have created your container, registering 100's of classes can get
470boring. You can automate this by using `loadModules`.
471
472> **Important**: auto-loading looks at a file's **default export**, which can be:
473>
474> - `module.exports = ...`
475> - `module.exports.default = ...`
476> - `export default ...`
477>
478> To load a non-default export, set the `[RESOLVER]` property on it:
479>
480> ```js
481> const { RESOLVER } = require('awilix')
482> export class ServiceClass {}
483> ServiceClass[RESOLVER] = {}
484> ```
485>
486> Or even more concise using TypeScript:
487>
488> ```typescript
489> // TypeScript
490> import { RESOLVER } from 'awilix'
491> export class ServiceClass {
492> static [RESOLVER] = {}
493> }
494> ```
495
496Note that **multiple** services can be registered per file, i.e. it is
497possible to have a file with a default export and named exports and for
498all of them to be loaded. The named exports do require the `RESOLVER`
499token to be recognized.
500
501Imagine this app structure:
502
503- `app`
504 - `services`
505 - `UserService.js` - exports an ES6 `class UserService {}`
506 - `emailService.js` - exports a factory function
507 `function makeEmailService() {}`
508 - `repositories`
509 - `UserRepository.js` - exports an ES6 `class UserRepository {}`
510 - `index.js` - our main script
511
512In our main script we would do the following:
513
514```js
515const awilix = require('awilix')
516
517const container = awilix.createContainer()
518
519// Load our modules!
520container.loadModules([
521 // Globs!
522 [
523 // To have different resolverOptions for specific modules.
524 'models/**/*.js',
525 {
526 register: awilix.asValue,
527 lifetime: Lifetime.SINGLETON
528 }
529 ],
530 'services/**/*.js',
531 'repositories/**/*.js'
532], {
533 // We want to register `UserService` as `userService` -
534 // by default loaded modules are registered with the
535 // name of the file (minus the extension)
536 formatName: 'camelCase',
537 // Apply resolver options to all modules.
538 resolverOptions: {
539 // We can give these auto-loaded modules
540 // the deal of a lifetime! (see what I did there?)
541 // By default it's `TRANSIENT`.
542 lifetime: Lifetime.SINGLETON,
543 // We can tell Awilix what to register everything as,
544 // instead of guessing. If omitted, will inspect the
545 // module to determinw what to register as.
546 register: awilix.asClass
547 }
548)
549
550// We are now ready! We now have a userService, userRepository and emailService!
551container.resolve('userService').getUser(1)
552```
553
554**Important**: Auto-loading relies on `glob` and therefore does not work with
555bundlers like Webpack, Rollup and Browserify.
556
557# Per-module local injections
558
559Some modules might need some additional configuration values than just
560dependencies.
561
562For example, our `userRepository` wants a `db` module which is registered with
563the container, but it also wants a `timeout` value. `timeout` is a very generic
564name and we don't want to register that as a value that can be accessed by all
565modules in the container (maybe other modules have a different timeout?)
566
567```js
568export default function userRepository({ db, timeout }) {
569 return {
570 find() {
571 return Promise.race([
572 db.query('select * from users'),
573 Promise.delay(timeout).then(() =>
574 Promise.reject(new Error('Timed out'))
575 )
576 ])
577 }
578 }
579}
580```
581
582Awilix 2.5 added per-module local injections. The following snippet contains
583_all_ the possible ways to set this up.
584
585```js
586import { createContainer, Lifetime, asFunction } from 'awilix'
587import createUserRepository from './repositories/userRepository'
588
589const container = createContainer()
590 // Using the fluid variant:
591 .register({
592 userRepository: asFunction(createUserRepository)
593 // Provide an injection function that returns an object with locals.
594 // The function is called once per resolve of the registration
595 // it is attached to.
596 .inject(() => ({ timeout: 2000 }))
597 })
598
599 // Shorthand variants
600 .register({
601 userRepository: asFunction(createUserRepository, {
602 injector: () => ({ timeout: 2000 })
603 })
604 })
605
606 // Stringly-typed shorthand
607 .register(
608 'userRepository',
609 asFunction(createUserRepository, {
610 injector: () => ({ timeout: 2000 })
611 })
612 )
613
614 // with `loadModules`
615 .loadModules([['repositories/*.js', { injector: () => ({ timeout: 2000 }) }]])
616```
617
618Now `timeout` is only available to the modules it was configured for.
619
620**IMPORTANT**: the way this works is by wrapping the `cradle` in another proxy
621that provides the returned values from the `inject` function. This means if you
622pass along the injected cradle object, anything with access to it can access the
623local injections.
624
625# Inlining resolver options
626
627Awilix 2.8 added support for inline resolver options. This is best explained
628with an example.
629
630**services/awesome-service.js**:
631
632```js
633import { RESOLVER, Lifetime, InjectionMode } from 'awilix'
634
635export default class AwesomeService {
636 constructor(awesomeRepository) {
637 this.awesomeRepository = awesomeRepository
638 }
639}
640
641// `RESOLVER` is a Symbol.
642AwesomeService[RESOLVER] = {
643 lifetime: Lifetime.SCOPED,
644 injectionMode: InjectionMode.CLASSIC
645}
646```
647
648**index.js**:
649
650```js
651import { createContainer, asClass } from 'awilix'
652import AwesomeService from './services/awesome-service.js'
653
654const container = createContainer().register({
655 awesomeService: asClass(AwesomeService)
656})
657
658console.log(container.registrations.awesomeService.lifetime) // 'SCOPED'
659console.log(container.registrations.awesomeService.injectionMode) // 'CLASSIC'
660```
661
662Additionally, if we add a `name` field and use `loadModules`, the `name` is used
663for registration.
664
665```diff
666// `RESOLVER` is a Symbol.
667AwesomeService[RESOLVER] = {
668+ name: 'superService',
669 lifetime: Lifetime.SCOPED,
670 injectionMode: InjectionMode.CLASSIC
671}
672```
673
674```js
675const container = createContainer().loadModules(['services/*.js'])
676console.log(container.registrations.superService.lifetime) // 'SCOPED'
677console.log(container.registrations.superService.injectionMode) // 'CLASSIC'
678```
679
680**Important**: the `name` field is only used by `loadModules`.
681
682# Disposing
683
684As of Awilix v3.0, you can call `container.dispose()` to clear the resolver
685cache and call any registered disposers. This is very useful to properly dispose
686resources like connection pools, and especially when using watch-mode in your
687integration tests.
688
689For example, database connection libraries usually have some sort of `destroy`
690or `end` function to close the connection. You can tell Awilix to call these for
691you when calling `container.dispose()`.
692
693**Important:** the container being disposed **will not dispose its' scopes**. It
694only disposes values **in it's own cache**.
695
696```js
697import { createContainer, asClass } from 'awilix'
698import pg from 'pg'
699
700class TodoStore {
701 constructor({ pool }) {
702 this.pool = pool
703 }
704
705 async getTodos() {
706 const result = await this.pool.query('SELECT * FROM todos')
707 return result.rows
708 }
709}
710
711function configureContainer() {
712 return container.register({
713 todoStore: asClass(TodoStore),
714 pool: asFunction(() => new pg.Pool())
715 // Disposables must be either `scoped` or `singleton`.
716 .singleton()
717 // This is called when the pool is going to be disposed.
718 // If it returns a Promise, it will be awaited by `dispose`.
719 .disposer(pool => pool.end())
720 })
721}
722
723const container = configureContainer()
724const todoStore = container.resolve('todoStore')
725
726// Later...
727container.dispose().then(() => {
728 console.log('Container has been disposed!')
729})
730```
731
732A perfect use case for this would be when using Awilix with an HTTP server.
733
734```js
735import express from 'express'
736import http from 'http'
737
738function createServer() {
739 const app = express()
740 const container = configureContainer()
741 app.get('/todos', async (req, res) => {
742 const store = container.resolve('todoStore')
743 const todos = await store.getTodos()
744 res.status(200).json(todos)
745 })
746
747 const server = http.createServer(app)
748 // Dispose container when the server closes.
749 server.on('close', () => container.dispose())
750 return server
751}
752
753test('server does server things', async () => {
754 const server = createServer()
755 server.listen(3000)
756
757 /// .. run your tests..
758
759 // Disposes everything, and your process no
760 // longer hangs on to zombie connections!
761 server.close()
762})
763```
764
765# API
766
767## The `awilix` object
768
769When importing `awilix`, you get the following top-level API:
770
771- `createContainer`
772- `listModules`
773- `AwilixResolutionError`
774- `asValue`
775- `asFunction`
776- `asClass`
777- `aliasTo`
778- `Lifetime` - documented above.
779- `InjectionMode` - documented above.
780
781These are documented below.
782
783## Resolver options
784
785Whenever you see a place where you can pass in **resolver options**, you can
786pass in an object with the following props:
787
788- `lifetime`: An `awilix.Lifetime.*` string, such as `awilix.Lifetime.SCOPED`
789- `injectionMode`: An `awilix.InjectionMode.*` string, such as
790 `awilix.InjectionMode.CLASSIC`
791- `injector`: An injector function - see
792 [Per-module local injections](#per-module-local-injections)
793- `register`: Only used in `loadModules`, determines how to register a loaded
794 module explicitly
795
796**Examples of usage:**
797
798```js
799container.register({
800 stuff: asClass(MyClass, { injectionMode: InjectionMode.CLASSIC })
801})
802
803container.loadModules([['some/path/to/*.js', { register: asClass }]], {
804 resolverOptions: {
805 lifetime: Lifetime.SCOPED
806 }
807})
808```
809
810## `createContainer()`
811
812Creates a new Awilix container. The container stuff is documented further down.
813
814Args:
815
816- `options`: Options object. Optional.
817 - `options.require`: The function to use when requiring modules. Defaults to
818 `require`. Useful when using something like
819 [`require-stack`](https://npmjs.org/package/require-stack). Optional.
820 - `options.injectionMode`: Determines the method for resolving dependencies.
821 Valid modes are:
822 - `PROXY`: Uses the `awilix` default dependency resolution mechanism (I.E.
823 injects the cradle into the function or class). This is the default
824 injection mode.
825 - `CLASSIC`: Uses the named dependency resolution mechanism. Dependencies
826 **_must_** be named exactly like they are in the registration. For
827 example, a dependency registered as `repository` cannot be referenced in a
828 class constructor as `repo`.
829
830## `asFunction()`
831
832Used with `container.register({ userService: asFunction(makeUserService) })`.
833Tells Awilix to invoke the function without any context.
834
835The returned resolver has the following chainable (fluid) API:
836
837- `asFunction(fn).setLifetime(lifetime: string)`: sets the lifetime of the
838 registration to the given value.
839- `asFunction(fn).transient()`: same as
840 `asFunction(fn).setLifetime(Lifetime.TRANSIENT)`.
841- `asFunction(fn).scoped()`: same as
842 `asFunction(fn).setLifetime(Lifetime.SCOPED)`.
843- `asFunction(fn).singleton()`: same as
844 `asFunction(fn).setLifetime(Lifetime.SINGLETON)`.
845- `asFunction(fn).inject(injector: Function)`: Let's you provide local
846 dependencies only available to this module. The `injector` gets the container
847 passed as the first and only argument and should return an object.
848
849## `asClass()`
850
851Used with `container.register({ userService: asClass(UserService) })`. Tells
852Awilix to instantiate the given function as a class using `new`.
853
854The returned resolver has the same chainable API as [`asFunction`](#asfunction).
855
856## `asValue()`
857
858Used with `container.register({ dbHost: asValue('localhost') })`. Tells Awilix
859to provide the given value as-is.
860
861## `aliasTo()`
862
863Resolves the dependency specified.
864
865```js
866container.register({
867 val: asValue(123),
868 aliasVal: aliasTo('val')
869})
870
871container.resolve('aliasVal') === container.resolve('val')
872```
873
874## `listModules()`
875
876Returns an array of `{name, path}` pairs, where the name is the module name, and
877path is the actual full path to the module.
878
879This is used internally, but is useful for other things as well, e.g.
880dynamically loading an `api` folder.
881
882Args:
883
884- `globPatterns`: a glob pattern string, or an array of them.
885- `opts.cwd`: The current working directory passed to `glob`. Defaults to
886 `process.cwd()`.
887- **returns**: an array of objects with:
888 - `name`: The module name - e.g. `db`
889 - `path`: The path to the module relative to `options.cwd` - e.g. `lib/db.js`
890
891Example:
892
893```js
894const listModules = require('awilix').listModules
895
896const result = listModules(['services/*.js'])
897
898console.log(result)
899// << [{ name: 'someService', path: 'path/to/services/someService.js' }]
900```
901
902**Important**: `listModules` relies on `glob` and therefore is not supported
903with bundlers like Webpack, Rollup and Browserify.
904
905## `AwilixResolutionError`
906
907This is a special error thrown when Awilix is unable to resolve all dependencies
908(due to missing or cyclic dependencies). You can catch this error and use
909`err instanceof AwilixResolutionError` if you wish. It will tell you what
910dependencies it could not find or which ones caused a cycle.
911
912## The `AwilixContainer` object
913
914The container returned from `createContainer` has some methods and properties.
915
916### `container.cradle`
917
918**Behold! This is where the magic happens!** The `cradle` is a proxy, and all
919getters will trigger a `container.resolve`. The `cradle` is actually being
920passed to the constructor/factory function, which is how everything gets wired
921up.
922
923### `container.registrations`
924
925A read-only getter that returns the internal registrations. When invoked on a
926_scope_, will show registrations for it's parent, and it's parent's parent, and
927so on.
928
929Not really useful for public use.
930
931### `container.cache`
932
933A `Map<string, CacheEntry>` used internally for caching resolutions. Not meant
934for public use but if you find it useful, go ahead but tread carefully.
935
936Each scope has it's own cache, and checks the cache of it's ancestors.
937
938```js
939let counter = 1
940container.register({
941 count: asFunction(() => counter++).singleton()
942})
943
944container.cradle.count === 1
945container.cradle.count === 1
946
947container.cache.delete('count')
948container.cradle.count === 2
949```
950
951### `container.options`
952
953Options passed to `createContainer` are stored here.
954
955```js
956const container = createContainer({
957 injectionMode: InjectionMode.CLASSIC
958})
959
960console.log(container.options.injectionMode) // 'CLASSIC'
961```
962
963### `container.resolve()`
964
965Resolves the registration with the given name. Used by the cradle.
966
967**Signature**
968
969- `resolve<T>(name: string, [resolveOpts: ResolveOptions]): T`
970
971```js
972container.register({
973 leet: asFunction(() => 1337)
974})
975
976container.resolve('leet') === 1337
977container.cradle.leet === 1337
978```
979
980The optional `resolveOpts` has the following fields:
981
982- `allowUnregistered`: if `true`, returns `undefined` when the dependency does
983 not exist, instead of throwing an error.
984
985### `container.register()`
986
987**Signatures**
988
989- `register(name: string, resolver: Resolver): AwilixContainer`
990- `register(nameAndResolverPair: NameAndResolverPair): AwilixContainer`
991
992Awilix needs to know how to resolve the modules, so let's pull out the resolver
993functions:
994
995```js
996const awilix = require('awilix')
997const { asValue, asFunction, asClass } = awilix
998```
999
1000- `asValue`: Resolves the given value as-is.
1001- `asFunction`: Resolve by invoking the function with the container cradle as
1002 the first and only argument.
1003- `asClass`: Like `asFunction` but uses `new`.
1004
1005Now we need to use them. There are multiple syntaxes for the `register`
1006function, pick the one you like the most - or use all of them, I don't really
1007care! :sunglasses:
1008
1009**Both styles supports chaining! `register` returns the container!**
1010
1011```js
1012// name-resolver
1013container.register('connectionString', asValue('localhost:1433;user=...'))
1014container.register('mailService', asFunction(makeMailService))
1015container.register('context', asClass(SessionContext))
1016
1017// object
1018container.register({
1019 connectionString: asValue('localhost:1433;user=...'),
1020 mailService: asFunction(makeMailService, { lifetime: Lifetime.SINGLETON }),
1021 context: asClass(SessionContext, { lifetime: Lifetime.SCOPED })
1022})
1023
1024// `asClass` and `asFunction` also supports a fluid syntax.
1025// This...
1026container.register(
1027 'mailService',
1028 asFunction(makeMailService).setLifetime(Lifetime.SINGLETON)
1029)
1030// .. is the same as this:
1031container.register('context', asClass(SessionContext).singleton())
1032
1033// .. and here are the other `Lifetime` variants as fluid functions.
1034container.register('context', asClass(SessionContext).transient())
1035container.register('context', asClass(SessionContext).scoped())
1036```
1037
1038**The object syntax, key-value syntax and chaining are valid for all `register`
1039calls!**
1040
1041### `container.has()`
1042
1043- `container.has(name: string | symbol): boolean`
1044
1045Determines if the container has a registration with the given name. Also checks ancestor containers.
1046
1047### `container.loadModules()`
1048
1049Given an array of globs, registers the modules and returns the container.
1050
1051Awilix will use `require` on the loaded modules, and register the
1052default-exported function or class as the name of the file.
1053
1054**This uses a heuristic to determine if it's a constructor function
1055(`function Database() {...}`); if the function name starts with a capital
1056letter, it will be `new`ed!**
1057
1058Args:
1059
1060- `globPatterns`: Array of glob patterns that match JS files to load.
1061- `opts.cwd`: The `cwd` being passed to `glob`. Defaults to `process.cwd()`.
1062- `opts.formatName`: Can be either `'camelCase'`, or a function that takes the
1063 current name as the first parameter and returns the new name. Default is to
1064 pass the name through as-is. The 2nd parameter is a full module descriptor.
1065- `opts.resolverOptions`: An `object` passed to the resolvers. Used to configure
1066 the lifetime, injection mode and more of the loaded modules.
1067- `opts.esModules`: Loads modules using Node's native ES modules. This is only supported on Node 14.0+ and should only be used if you're using the [Native Node ES modules](https://nodejs.org/api/esm.html)
1068
1069Example:
1070
1071```js
1072// index.js
1073container.loadModules(['services/*.js', 'repositories/*.js', 'db/db.js'])
1074
1075container.cradle.userService.getUser(123)
1076
1077// to configure lifetime for all modules loaded..
1078container.loadModules([
1079 'services/*.js',
1080 'repositories/*.js',
1081 'db/db.js'
1082], {
1083 resolverOptions: {
1084 lifetime: Lifetime.SINGLETON
1085 }
1086})
1087
1088container.cradle.userService.getUser(123)
1089
1090// to configure lifetime for specific globs..
1091container.loadModules([
1092 ['services/*.js', Lifetime.SCOPED], // all services will have scoped lifetime
1093 'repositories/*.js',
1094 'db/db.js'
1095], {
1096 resolverOptions: {
1097 lifetime: Lifetime.SINGLETON // db and repositories will be singleton
1098 }
1099)
1100
1101container.cradle.userService.getUser(123)
1102
1103// to use camelCase for modules where filenames are not camelCase
1104container.loadModules(['repositories/account-repository.js', 'db/db.js'], {
1105 formatName: 'camelCase'
1106})
1107
1108container.cradle.accountRepository.getUser(123)
1109
1110// to customize how modules are named in the container (and for injection)
1111container.loadModules(['repository/account.js', 'service/email.js'], {
1112 // This formats the module name so `repository/account.js` becomes `accountRepository`
1113 formatName: (name, descriptor) => {
1114 const splat = descriptor.path.split('/')
1115 const namespace = splat[splat.length - 2] // `repository` or `service`
1116 const upperNamespace =
1117 namespace.charAt(0).toUpperCase() + namespace.substring(1)
1118 return name + upperNamespace
1119 }
1120})
1121
1122container.cradle.accountRepository.getUser(123)
1123container.cradle.emailService.sendEmail('test@test.com', 'waddup')
1124```
1125
1126The `['glob', Lifetime.SCOPED]` syntax is a shorthand for passing in resolver
1127options like so: `['glob', { lifetime: Lifetime.SCOPED }]`
1128
1129**Important**: `loadModules` depends on `glob` and is therefore not supported in
1130module bundlers like Webpack, Rollup and Browserify.
1131
1132### `container.createScope()`
1133
1134Creates a new scope. All registrations with a `Lifetime.SCOPED` will be cached
1135inside a scope. A scope is basically a "child" container.
1136
1137- returns `AwilixContainer`
1138
1139```js
1140// Increments the counter every time it is resolved.
1141let counter = 1
1142container.register({
1143 counterValue: asFunction(() => counter++).scoped()
1144})
1145const scope1 = container.createScope()
1146const scope2 = container.createScope()
1147
1148const scope1Child = scope1.createScope()
1149
1150scope1.cradle.counterValue === 1
1151scope1.cradle.counterValue === 1
1152scope2.cradle.counterValue === 2
1153scope2.cradle.counterValue === 2
1154
1155scope1Child.cradle.counterValue === 3
1156```
1157
1158A _Scope_ maintains it's own cache of `Lifetime.SCOPED` registrations, meaning it **does not use the parent's cache** for scoped registrations.
1159
1160```js
1161let counter = 1
1162container.register({
1163 counterValue: asFunction(() => counter++).scoped()
1164})
1165const scope1 = container.createScope()
1166const scope2 = container.createScope()
1167
1168// The root container is also a scope.
1169container.cradle.counterValue === 1
1170container.cradle.counterValue === 1
1171
1172// This scope resolves and caches it's own.
1173scope1.cradle.counterValue === 2
1174scope1.cradle.counterValue === 2
1175
1176// This scope resolves and caches it's own.
1177scope2.cradle.counterValue === 3
1178scope2.cradle.counterValue === 3
1179```
1180
1181A scope may also register additional stuff - they will only be available within
1182that scope and it's children.
1183
1184```js
1185// Register a transient function
1186// that returns the value of the scope-provided dependency.
1187// For this example we could also use scoped lifetime.
1188container.register({
1189 scopedValue: asFunction(cradle => 'Hello ' + cradle.someValue)
1190})
1191
1192// Create a scope and register a value.
1193const scope = container.createScope()
1194scope.register({
1195 someValue: asValue('scope')
1196})
1197
1198scope.cradle.scopedValue === 'Hello scope'
1199container.cradle.someValue
1200// throws AwilixResolutionException
1201// because the root container does not know
1202// of the resolver.
1203```
1204
1205Things registered in the scope take precedence over it's parent.
1206
1207```js
1208// It does not matter when the scope is created,
1209// it will still have anything that is registered
1210// in it's parent.
1211const scope = container.createScope()
1212
1213container.register({
1214 value: asValue('root'),
1215 usedValue: asFunction(cradle => cradle.value)
1216})
1217
1218scope.register({
1219 value: asValue('scope')
1220})
1221
1222container.cradle.usedValue === 'root'
1223scope.cradle.usedValue === 'scope'
1224```
1225
1226### `container.build()`
1227
1228Builds an instance of a class (or a function) by injecting dependencies, but
1229without registering it in the container.
1230
1231It's basically a shortcut for `asClass(MyClass).resolve(container)`.
1232
1233Args:
1234
1235- `targetOrResolver`: A class, function or resolver (example: `asClass(..)`,
1236 `asFunction(..)`)
1237- `opts`: Resolver options.
1238
1239Returns an instance of whatever is passed in, or the result of calling the
1240resolver.
1241
1242**Important**: if you are doing this often for the same class/function, consider
1243using the explicit approach and save the resolver, **especially** if you are
1244using classic resolution because it scans the class constructor/function when
1245calling `asClass(Class)` / `asFunction(func)`.
1246
1247```js
1248// The following are equivelant..
1249class MyClass {
1250 constructor({ ping }) {
1251 this.ping = ping
1252 }
1253
1254 pong() {
1255 return this.ping
1256 }
1257}
1258
1259const createMyFunc = ({ ping }) => ({
1260 pong: () => ping
1261})
1262
1263container.register({
1264 ping: asValue('pong')
1265})
1266
1267// Shorthand
1268// This uses `utils.isClass()` to determine whether to
1269// use `asClass` or `asFunction`. This is fine for
1270// one-time resolutions.
1271const myClass = container.build(MyClass)
1272const myFunc = container.build(createMyFunc)
1273
1274// Explicit
1275// Save the resolver if you are planning on invoking often.
1276// **Especially** if you're using classic resolution.
1277const myClassResolver = asClass(MyClass)
1278const myFuncResolver = asFunction(MyFunc)
1279
1280const myClass = container.build(myClassResolver)
1281const myFunc = container.build(myFuncResolver)
1282```
1283
1284### `container.dispose()`
1285
1286Returns a `Promise` that resolves when all disposers of cached resolutions have
1287resolved. **Only cached values will be disposed, meaning they must have a
1288`Lifetime` of `SCOPED` or `SINGLETON`**, or else they are not cached by the
1289container and therefore can't be disposed by it.
1290
1291This also clears the container's cache.
1292
1293```js
1294const pg = require('pg')
1295
1296container.register({
1297 pool: asFunction(() => new pg.Pool())
1298 .disposer(pool => pool.end())
1299 // IMPORTANT! Must be either singleton or scoped!
1300 .singleton()
1301})
1302
1303const pool = container.resolve('pool')
1304pool.query('...')
1305
1306// Later..
1307container.dispose().then(() => {
1308 console.log('All dependencies disposed, you can exit now. :)')
1309})
1310```
1311
1312# Universal Module (Browser Support)
1313
1314**As of v3**, Awilix ships with official support for browser environments!
1315
1316The package includes 4 flavors.
1317
1318- CommonJS, the good ol' Node format - `lib/awilix.js`
1319- ES Modules, for use with module bundlers **in Node** - `lib/awilix.module.js`
1320- ES Modules, for use with module bundlers **in the browser** -
1321 `lib/awilix.browser.js`
1322- UMD, for dropping it into a script tag - `lib/awilix.umd.js`
1323
1324The `package.json` includes the proper fields for bundlers like Webpack, Rollup
1325and Browserify to pick the correct version, so you should not have to configure
1326anything. 😎
1327
1328**Important**: the browser builds do not support `loadModules` or `listModules`,
1329because they depend on Node-specific packages.
1330
1331**Also important**: due to using `Proxy` + various `Reflect` methods, Awilix is only _supposed_ to work in:
1332
1333- Chrome >= 49
1334- Firefox >= 18
1335- Edge >= 12
1336- Opera >= 36
1337- Safari >= 10
1338- Internet Explorer is not supported
1339
1340# Contributing
1341
1342Clone repo, run `npm install` to install all dependencies, run `npm run build` to create an initial build, and then
1343`npm run test -- --watchAll` to start writing code.
1344
1345For code coverage, run `npm run cover`.
1346
1347If you submit a PR, please aim for 100% code coverage and no linting errors.
1348Travis will fail if there are linting errors. Thank you for considering
1349contributing. :)
1350
1351# What's in a name?
1352
1353Awilix is the mayan goddess of the moon, and also my favorite character in the
1354game [SMITE](http://www.smitegame.com/play-for-free?ref=Jeffijoe).
1355
1356# Author
1357
1358Jeff Hansen - [@Jeffijoe](https://twitter.com/Jeffijoe)