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 |
|
14 | Extremely powerful **Dependency Injection** (DI) container for JavaScript/Node,
|
15 | written 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 |
|
62 | Install with `npm`
|
63 |
|
64 | ```
|
65 | npm install awilix
|
66 | ```
|
67 |
|
68 | Or `yarn`
|
69 |
|
70 | ```
|
71 | yarn add awilix
|
72 | ```
|
73 |
|
74 | You 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>
|
79 | const container = Awilix.createContainer()
|
80 | </script>
|
81 | ```
|
82 |
|
83 | # Usage
|
84 |
|
85 | Awilix has a pretty simple API (but with many possible ways to invoke it). At
|
86 | minimum, 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
|
95 | const awilix = require('awilix')
|
96 |
|
97 | // Create the container and set the injectionMode to PROXY (which is also the default).
|
98 | const 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.
|
105 | class 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 |
|
118 | container.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.
|
125 | const 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 |
|
135 | container.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.
|
147 | function Database(connectionString, timeout) {
|
148 | // We can inject plain values as well!
|
149 | this.conn = connectToYourDatabaseSomehow(connectionString, timeout)
|
150 | }
|
151 |
|
152 | Database.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.
|
161 | container.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.
|
167 | container.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..)
|
177 | router.get('/api/users/:id', container.resolve('userController').getUser)
|
178 |
|
179 | // Alternatively, using the `cradle` proxy..
|
180 | router.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 |
|
186 | That example is rather lengthy, but if you extract things to their proper files
|
187 | it becomes more manageable.
|
188 |
|
189 | [Check out a working Koa example!](/examples/koa)
|
190 |
|
191 | # Lifetime management
|
192 |
|
193 | Awilix supports managing the lifetime of instances. This means that you can
|
194 | control whether objects are resolved and used once, cached within a certain
|
195 | scope, or cached for the lifetime of the process.
|
196 |
|
197 | There 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 |
|
208 | They are exposed on the `awilix.Lifetime` object.
|
209 |
|
210 | ```js
|
211 | const Lifetime = awilix.Lifetime
|
212 | ```
|
213 |
|
214 | To register a module with a specific lifetime:
|
215 |
|
216 | ```js
|
217 | const { asClass, asFunction, asValue } = awilix
|
218 |
|
219 | class MailService {}
|
220 |
|
221 | container.register({
|
222 | mailService: asClass(MailService, { lifetime: Lifetime.SINGLETON })
|
223 | })
|
224 |
|
225 | // or using the chaining configuration API..
|
226 | container.register({
|
227 | mailService: asClass(MailService).setLifetime(Lifetime.SINGLETON)
|
228 | })
|
229 |
|
230 | // or..
|
231 | container.register({
|
232 | mailService: asClass(MailService).singleton()
|
233 | })
|
234 |
|
235 | // or.......
|
236 | container.register('mailService', asClass(MailService, { lifetime: SINGLETON }))
|
237 | ```
|
238 |
|
239 | ## Scoped lifetime
|
240 |
|
241 | In web applications, managing state without depending too much on the web
|
242 | framework can get difficult. Having to pass tons of information into every
|
243 | function just to make the right choices based on the authenticated user.
|
244 |
|
245 | Scoped lifetime in Awilix makes this simple - and fun!
|
246 |
|
247 | ```js
|
248 | const { createContainer, asClass, asValue } = awilix
|
249 | const container = createContainer()
|
250 |
|
251 | class MessageService {
|
252 | constructor({ currentUser }) {
|
253 | this.user = currentUser
|
254 | }
|
255 |
|
256 | getMessages() {
|
257 | const id = this.user.id
|
258 | // wee!
|
259 | }
|
260 | }
|
261 |
|
262 | container.register({
|
263 | messageService: asClass(MessageService).scoped()
|
264 | })
|
265 |
|
266 | // imagine middleware in some web framework..
|
267 | app.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 |
|
279 | app.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
|
292 | transient registration, those will remain in the singleton for it's lifetime!
|
293 |
|
294 | ```js
|
295 | const makePrintTime = ({ time }) => () => {
|
296 | console.log('Time:', time)
|
297 | }
|
298 |
|
299 | const getTime = () => new Date().toString()
|
300 |
|
301 | container.register({
|
302 | printTime: asFunction(makePrintTime).singleton(),
|
303 | time: asFunction(getTime).transient()
|
304 | })
|
305 |
|
306 | // Resolving `time` 2 times will
|
307 | // invoke `getTime` 2 times.
|
308 | container.resolve('time')
|
309 | container.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.
|
314 | container.resolve('printTime')()
|
315 | container.resolve('printTime')()
|
316 | ```
|
317 |
|
318 | Read the documentation for [`container.createScope()`](#containercreatescope)
|
319 | for more examples.
|
320 |
|
321 | # Injection modes
|
322 |
|
323 | The injection mode determines how a function/constructor receives its
|
324 | dependencies. Pre-2.3.0, only one mode was supported - `PROXY` - which remains
|
325 | the default mode.
|
326 |
|
327 | Awilix v2.3.0 introduced an alternative injection mode: `CLASSIC`. The injection
|
328 | modes 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 |
|
383 | Injection modes can be set per-container and per-resolver. The most specific one
|
384 | wins.
|
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
|
392 | const { createContainer, InjectionMode } = require('awilix')
|
393 |
|
394 | const container = createContainer({ injectionMode: InjectionMode.CLASSIC })
|
395 | ```
|
396 |
|
397 | **Per resolver**:
|
398 |
|
399 | ```js
|
400 | const container = createContainer()
|
401 |
|
402 | container.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..
|
411 | container.register({
|
412 | logger: asClass(Logger, { injectionMode: InjectionMode.CLASSIC })
|
413 | })
|
414 | ```
|
415 |
|
416 | **For auto-loading modules**:
|
417 |
|
418 | ```js
|
419 | const container = createContainer()
|
420 | container.loadModules(['services/**/*.js', 'repositories/**/*.js'], {
|
421 | resolverOptions: {
|
422 | injectionMode: InjectionMode.CLASSIC
|
423 | }
|
424 | })
|
425 | ```
|
426 |
|
427 | Choose 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 |
|
443 | Here's an example outlining the testability points raised.
|
444 |
|
445 | ```js
|
446 | // CLASSIC
|
447 | function database(connectionString, timeout, logger) {
|
448 | // ...
|
449 | }
|
450 |
|
451 | // Shorter, but less readable, order-sensitive
|
452 | const db = database('localhost:1337;user=123...', 4000, new LoggerMock())
|
453 |
|
454 | // PROXY
|
455 | function database({ connectionString, timeout, logger }) {
|
456 | // ...
|
457 | }
|
458 |
|
459 | // Longer, more readable, order does not matter
|
460 | const db = database({
|
461 | logger: new LoggerMock(),
|
462 | timeout: 4000,
|
463 | connectionString: 'localhost:1337;user=123...'
|
464 | })
|
465 | ```
|
466 |
|
467 | # Auto-loading modules
|
468 |
|
469 | When you have created your container, registering 100's of classes can get
|
470 | boring. 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 |
|
496 | Note that **multiple** services can be registered per file, i.e. it is
|
497 | possible to have a file with a default export and named exports and for
|
498 | all of them to be loaded. The named exports do require the `RESOLVER`
|
499 | token to be recognized.
|
500 |
|
501 | Imagine 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 |
|
512 | In our main script we would do the following:
|
513 |
|
514 | ```js
|
515 | const awilix = require('awilix')
|
516 |
|
517 | const container = awilix.createContainer()
|
518 |
|
519 | // Load our modules!
|
520 | container.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!
|
551 | container.resolve('userService').getUser(1)
|
552 | ```
|
553 |
|
554 | **Important**: Auto-loading relies on `glob` and therefore does not work with
|
555 | bundlers like Webpack, Rollup and Browserify.
|
556 |
|
557 | # Per-module local injections
|
558 |
|
559 | Some modules might need some additional configuration values than just
|
560 | dependencies.
|
561 |
|
562 | For example, our `userRepository` wants a `db` module which is registered with
|
563 | the container, but it also wants a `timeout` value. `timeout` is a very generic
|
564 | name and we don't want to register that as a value that can be accessed by all
|
565 | modules in the container (maybe other modules have a different timeout?)
|
566 |
|
567 | ```js
|
568 | export 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 |
|
582 | Awilix 2.5 added per-module local injections. The following snippet contains
|
583 | _all_ the possible ways to set this up.
|
584 |
|
585 | ```js
|
586 | import { createContainer, Lifetime, asFunction } from 'awilix'
|
587 | import createUserRepository from './repositories/userRepository'
|
588 |
|
589 | const 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 |
|
618 | Now `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
|
621 | that provides the returned values from the `inject` function. This means if you
|
622 | pass along the injected cradle object, anything with access to it can access the
|
623 | local injections.
|
624 |
|
625 | # Inlining resolver options
|
626 |
|
627 | Awilix 2.8 added support for inline resolver options. This is best explained
|
628 | with an example.
|
629 |
|
630 | **services/awesome-service.js**:
|
631 |
|
632 | ```js
|
633 | import { RESOLVER, Lifetime, InjectionMode } from 'awilix'
|
634 |
|
635 | export default class AwesomeService {
|
636 | constructor(awesomeRepository) {
|
637 | this.awesomeRepository = awesomeRepository
|
638 | }
|
639 | }
|
640 |
|
641 | // `RESOLVER` is a Symbol.
|
642 | AwesomeService[RESOLVER] = {
|
643 | lifetime: Lifetime.SCOPED,
|
644 | injectionMode: InjectionMode.CLASSIC
|
645 | }
|
646 | ```
|
647 |
|
648 | **index.js**:
|
649 |
|
650 | ```js
|
651 | import { createContainer, asClass } from 'awilix'
|
652 | import AwesomeService from './services/awesome-service.js'
|
653 |
|
654 | const container = createContainer().register({
|
655 | awesomeService: asClass(AwesomeService)
|
656 | })
|
657 |
|
658 | console.log(container.registrations.awesomeService.lifetime) // 'SCOPED'
|
659 | console.log(container.registrations.awesomeService.injectionMode) // 'CLASSIC'
|
660 | ```
|
661 |
|
662 | Additionally, if we add a `name` field and use `loadModules`, the `name` is used
|
663 | for registration.
|
664 |
|
665 | ```diff
|
666 | // `RESOLVER` is a Symbol.
|
667 | AwesomeService[RESOLVER] = {
|
668 | + name: 'superService',
|
669 | lifetime: Lifetime.SCOPED,
|
670 | injectionMode: InjectionMode.CLASSIC
|
671 | }
|
672 | ```
|
673 |
|
674 | ```js
|
675 | const container = createContainer().loadModules(['services/*.js'])
|
676 | console.log(container.registrations.superService.lifetime) // 'SCOPED'
|
677 | console.log(container.registrations.superService.injectionMode) // 'CLASSIC'
|
678 | ```
|
679 |
|
680 | **Important**: the `name` field is only used by `loadModules`.
|
681 |
|
682 | # Disposing
|
683 |
|
684 | As of Awilix v3.0, you can call `container.dispose()` to clear the resolver
|
685 | cache and call any registered disposers. This is very useful to properly dispose
|
686 | resources like connection pools, and especially when using watch-mode in your
|
687 | integration tests.
|
688 |
|
689 | For example, database connection libraries usually have some sort of `destroy`
|
690 | or `end` function to close the connection. You can tell Awilix to call these for
|
691 | you when calling `container.dispose()`.
|
692 |
|
693 | **Important:** the container being disposed **will not dispose its' scopes**. It
|
694 | only disposes values **in it's own cache**.
|
695 |
|
696 | ```js
|
697 | import { createContainer, asClass } from 'awilix'
|
698 | import pg from 'pg'
|
699 |
|
700 | class 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 |
|
711 | function 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 |
|
723 | const container = configureContainer()
|
724 | const todoStore = container.resolve('todoStore')
|
725 |
|
726 | // Later...
|
727 | container.dispose().then(() => {
|
728 | console.log('Container has been disposed!')
|
729 | })
|
730 | ```
|
731 |
|
732 | A perfect use case for this would be when using Awilix with an HTTP server.
|
733 |
|
734 | ```js
|
735 | import express from 'express'
|
736 | import http from 'http'
|
737 |
|
738 | function 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 |
|
753 | test('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 |
|
769 | When 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 |
|
781 | These are documented below.
|
782 |
|
783 | ## Resolver options
|
784 |
|
785 | Whenever you see a place where you can pass in **resolver options**, you can
|
786 | pass 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
|
799 | container.register({
|
800 | stuff: asClass(MyClass, { injectionMode: InjectionMode.CLASSIC })
|
801 | })
|
802 |
|
803 | container.loadModules([['some/path/to/*.js', { register: asClass }]], {
|
804 | resolverOptions: {
|
805 | lifetime: Lifetime.SCOPED
|
806 | }
|
807 | })
|
808 | ```
|
809 |
|
810 | ## `createContainer()`
|
811 |
|
812 | Creates a new Awilix container. The container stuff is documented further down.
|
813 |
|
814 | Args:
|
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 |
|
832 | Used with `container.register({ userService: asFunction(makeUserService) })`.
|
833 | Tells Awilix to invoke the function without any context.
|
834 |
|
835 | The 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 |
|
851 | Used with `container.register({ userService: asClass(UserService) })`. Tells
|
852 | Awilix to instantiate the given function as a class using `new`.
|
853 |
|
854 | The returned resolver has the same chainable API as [`asFunction`](#asfunction).
|
855 |
|
856 | ## `asValue()`
|
857 |
|
858 | Used with `container.register({ dbHost: asValue('localhost') })`. Tells Awilix
|
859 | to provide the given value as-is.
|
860 |
|
861 | ## `aliasTo()`
|
862 |
|
863 | Resolves the dependency specified.
|
864 |
|
865 | ```js
|
866 | container.register({
|
867 | val: asValue(123),
|
868 | aliasVal: aliasTo('val')
|
869 | })
|
870 |
|
871 | container.resolve('aliasVal') === container.resolve('val')
|
872 | ```
|
873 |
|
874 | ## `listModules()`
|
875 |
|
876 | Returns an array of `{name, path}` pairs, where the name is the module name, and
|
877 | path is the actual full path to the module.
|
878 |
|
879 | This is used internally, but is useful for other things as well, e.g.
|
880 | dynamically loading an `api` folder.
|
881 |
|
882 | Args:
|
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 |
|
891 | Example:
|
892 |
|
893 | ```js
|
894 | const listModules = require('awilix').listModules
|
895 |
|
896 | const result = listModules(['services/*.js'])
|
897 |
|
898 | console.log(result)
|
899 | // << [{ name: 'someService', path: 'path/to/services/someService.js' }]
|
900 | ```
|
901 |
|
902 | **Important**: `listModules` relies on `glob` and therefore is not supported
|
903 | with bundlers like Webpack, Rollup and Browserify.
|
904 |
|
905 | ## `AwilixResolutionError`
|
906 |
|
907 | This 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
|
910 | dependencies it could not find or which ones caused a cycle.
|
911 |
|
912 | ## The `AwilixContainer` object
|
913 |
|
914 | The 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
|
919 | getters will trigger a `container.resolve`. The `cradle` is actually being
|
920 | passed to the constructor/factory function, which is how everything gets wired
|
921 | up.
|
922 |
|
923 | ### `container.registrations`
|
924 |
|
925 | A 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
|
927 | so on.
|
928 |
|
929 | Not really useful for public use.
|
930 |
|
931 | ### `container.cache`
|
932 |
|
933 | A `Map<string, CacheEntry>` used internally for caching resolutions. Not meant
|
934 | for public use but if you find it useful, go ahead but tread carefully.
|
935 |
|
936 | Each scope has it's own cache, and checks the cache of it's ancestors.
|
937 |
|
938 | ```js
|
939 | let counter = 1
|
940 | container.register({
|
941 | count: asFunction(() => counter++).singleton()
|
942 | })
|
943 |
|
944 | container.cradle.count === 1
|
945 | container.cradle.count === 1
|
946 |
|
947 | container.cache.delete('count')
|
948 | container.cradle.count === 2
|
949 | ```
|
950 |
|
951 | ### `container.options`
|
952 |
|
953 | Options passed to `createContainer` are stored here.
|
954 |
|
955 | ```js
|
956 | const container = createContainer({
|
957 | injectionMode: InjectionMode.CLASSIC
|
958 | })
|
959 |
|
960 | console.log(container.options.injectionMode) // 'CLASSIC'
|
961 | ```
|
962 |
|
963 | ### `container.resolve()`
|
964 |
|
965 | Resolves 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
|
972 | container.register({
|
973 | leet: asFunction(() => 1337)
|
974 | })
|
975 |
|
976 | container.resolve('leet') === 1337
|
977 | container.cradle.leet === 1337
|
978 | ```
|
979 |
|
980 | The 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 |
|
992 | Awilix needs to know how to resolve the modules, so let's pull out the resolver
|
993 | functions:
|
994 |
|
995 | ```js
|
996 | const awilix = require('awilix')
|
997 | const { 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 |
|
1005 | Now we need to use them. There are multiple syntaxes for the `register`
|
1006 | function, pick the one you like the most - or use all of them, I don't really
|
1007 | care! :sunglasses:
|
1008 |
|
1009 | **Both styles supports chaining! `register` returns the container!**
|
1010 |
|
1011 | ```js
|
1012 | // name-resolver
|
1013 | container.register('connectionString', asValue('localhost:1433;user=...'))
|
1014 | container.register('mailService', asFunction(makeMailService))
|
1015 | container.register('context', asClass(SessionContext))
|
1016 |
|
1017 | // object
|
1018 | container.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...
|
1026 | container.register(
|
1027 | 'mailService',
|
1028 | asFunction(makeMailService).setLifetime(Lifetime.SINGLETON)
|
1029 | )
|
1030 | // .. is the same as this:
|
1031 | container.register('context', asClass(SessionContext).singleton())
|
1032 |
|
1033 | // .. and here are the other `Lifetime` variants as fluid functions.
|
1034 | container.register('context', asClass(SessionContext).transient())
|
1035 | container.register('context', asClass(SessionContext).scoped())
|
1036 | ```
|
1037 |
|
1038 | **The object syntax, key-value syntax and chaining are valid for all `register`
|
1039 | calls!**
|
1040 |
|
1041 | ### `container.has()`
|
1042 |
|
1043 | - `container.has(name: string | symbol): boolean`
|
1044 |
|
1045 | Determines if the container has a registration with the given name. Also checks ancestor containers.
|
1046 |
|
1047 | ### `container.loadModules()`
|
1048 |
|
1049 | Given an array of globs, registers the modules and returns the container.
|
1050 |
|
1051 | Awilix will use `require` on the loaded modules, and register the
|
1052 | default-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
|
1056 | letter, it will be `new`ed!**
|
1057 |
|
1058 | Args:
|
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 |
|
1069 | Example:
|
1070 |
|
1071 | ```js
|
1072 | // index.js
|
1073 | container.loadModules(['services/*.js', 'repositories/*.js', 'db/db.js'])
|
1074 |
|
1075 | container.cradle.userService.getUser(123)
|
1076 |
|
1077 | // to configure lifetime for all modules loaded..
|
1078 | container.loadModules([
|
1079 | 'services/*.js',
|
1080 | 'repositories/*.js',
|
1081 | 'db/db.js'
|
1082 | ], {
|
1083 | resolverOptions: {
|
1084 | lifetime: Lifetime.SINGLETON
|
1085 | }
|
1086 | })
|
1087 |
|
1088 | container.cradle.userService.getUser(123)
|
1089 |
|
1090 | // to configure lifetime for specific globs..
|
1091 | container.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 |
|
1101 | container.cradle.userService.getUser(123)
|
1102 |
|
1103 | // to use camelCase for modules where filenames are not camelCase
|
1104 | container.loadModules(['repositories/account-repository.js', 'db/db.js'], {
|
1105 | formatName: 'camelCase'
|
1106 | })
|
1107 |
|
1108 | container.cradle.accountRepository.getUser(123)
|
1109 |
|
1110 | // to customize how modules are named in the container (and for injection)
|
1111 | container.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 |
|
1122 | container.cradle.accountRepository.getUser(123)
|
1123 | container.cradle.emailService.sendEmail('test@test.com', 'waddup')
|
1124 | ```
|
1125 |
|
1126 | The `['glob', Lifetime.SCOPED]` syntax is a shorthand for passing in resolver
|
1127 | options like so: `['glob', { lifetime: Lifetime.SCOPED }]`
|
1128 |
|
1129 | **Important**: `loadModules` depends on `glob` and is therefore not supported in
|
1130 | module bundlers like Webpack, Rollup and Browserify.
|
1131 |
|
1132 | ### `container.createScope()`
|
1133 |
|
1134 | Creates a new scope. All registrations with a `Lifetime.SCOPED` will be cached
|
1135 | inside 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.
|
1141 | let counter = 1
|
1142 | container.register({
|
1143 | counterValue: asFunction(() => counter++).scoped()
|
1144 | })
|
1145 | const scope1 = container.createScope()
|
1146 | const scope2 = container.createScope()
|
1147 |
|
1148 | const scope1Child = scope1.createScope()
|
1149 |
|
1150 | scope1.cradle.counterValue === 1
|
1151 | scope1.cradle.counterValue === 1
|
1152 | scope2.cradle.counterValue === 2
|
1153 | scope2.cradle.counterValue === 2
|
1154 |
|
1155 | scope1Child.cradle.counterValue === 3
|
1156 | ```
|
1157 |
|
1158 | A _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
|
1161 | let counter = 1
|
1162 | container.register({
|
1163 | counterValue: asFunction(() => counter++).scoped()
|
1164 | })
|
1165 | const scope1 = container.createScope()
|
1166 | const scope2 = container.createScope()
|
1167 |
|
1168 | // The root container is also a scope.
|
1169 | container.cradle.counterValue === 1
|
1170 | container.cradle.counterValue === 1
|
1171 |
|
1172 | // This scope resolves and caches it's own.
|
1173 | scope1.cradle.counterValue === 2
|
1174 | scope1.cradle.counterValue === 2
|
1175 |
|
1176 | // This scope resolves and caches it's own.
|
1177 | scope2.cradle.counterValue === 3
|
1178 | scope2.cradle.counterValue === 3
|
1179 | ```
|
1180 |
|
1181 | A scope may also register additional stuff - they will only be available within
|
1182 | that 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.
|
1188 | container.register({
|
1189 | scopedValue: asFunction(cradle => 'Hello ' + cradle.someValue)
|
1190 | })
|
1191 |
|
1192 | // Create a scope and register a value.
|
1193 | const scope = container.createScope()
|
1194 | scope.register({
|
1195 | someValue: asValue('scope')
|
1196 | })
|
1197 |
|
1198 | scope.cradle.scopedValue === 'Hello scope'
|
1199 | container.cradle.someValue
|
1200 | // throws AwilixResolutionException
|
1201 | // because the root container does not know
|
1202 | // of the resolver.
|
1203 | ```
|
1204 |
|
1205 | Things 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.
|
1211 | const scope = container.createScope()
|
1212 |
|
1213 | container.register({
|
1214 | value: asValue('root'),
|
1215 | usedValue: asFunction(cradle => cradle.value)
|
1216 | })
|
1217 |
|
1218 | scope.register({
|
1219 | value: asValue('scope')
|
1220 | })
|
1221 |
|
1222 | container.cradle.usedValue === 'root'
|
1223 | scope.cradle.usedValue === 'scope'
|
1224 | ```
|
1225 |
|
1226 | ### `container.build()`
|
1227 |
|
1228 | Builds an instance of a class (or a function) by injecting dependencies, but
|
1229 | without registering it in the container.
|
1230 |
|
1231 | It's basically a shortcut for `asClass(MyClass).resolve(container)`.
|
1232 |
|
1233 | Args:
|
1234 |
|
1235 | - `targetOrResolver`: A class, function or resolver (example: `asClass(..)`,
|
1236 | `asFunction(..)`)
|
1237 | - `opts`: Resolver options.
|
1238 |
|
1239 | Returns an instance of whatever is passed in, or the result of calling the
|
1240 | resolver.
|
1241 |
|
1242 | **Important**: if you are doing this often for the same class/function, consider
|
1243 | using the explicit approach and save the resolver, **especially** if you are
|
1244 | using classic resolution because it scans the class constructor/function when
|
1245 | calling `asClass(Class)` / `asFunction(func)`.
|
1246 |
|
1247 | ```js
|
1248 | // The following are equivelant..
|
1249 | class MyClass {
|
1250 | constructor({ ping }) {
|
1251 | this.ping = ping
|
1252 | }
|
1253 |
|
1254 | pong() {
|
1255 | return this.ping
|
1256 | }
|
1257 | }
|
1258 |
|
1259 | const createMyFunc = ({ ping }) => ({
|
1260 | pong: () => ping
|
1261 | })
|
1262 |
|
1263 | container.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.
|
1271 | const myClass = container.build(MyClass)
|
1272 | const 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.
|
1277 | const myClassResolver = asClass(MyClass)
|
1278 | const myFuncResolver = asFunction(MyFunc)
|
1279 |
|
1280 | const myClass = container.build(myClassResolver)
|
1281 | const myFunc = container.build(myFuncResolver)
|
1282 | ```
|
1283 |
|
1284 | ### `container.dispose()`
|
1285 |
|
1286 | Returns a `Promise` that resolves when all disposers of cached resolutions have
|
1287 | resolved. **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
|
1289 | container and therefore can't be disposed by it.
|
1290 |
|
1291 | This also clears the container's cache.
|
1292 |
|
1293 | ```js
|
1294 | const pg = require('pg')
|
1295 |
|
1296 | container.register({
|
1297 | pool: asFunction(() => new pg.Pool())
|
1298 | .disposer(pool => pool.end())
|
1299 | // IMPORTANT! Must be either singleton or scoped!
|
1300 | .singleton()
|
1301 | })
|
1302 |
|
1303 | const pool = container.resolve('pool')
|
1304 | pool.query('...')
|
1305 |
|
1306 | // Later..
|
1307 | container.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 |
|
1316 | The 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 |
|
1324 | The `package.json` includes the proper fields for bundlers like Webpack, Rollup
|
1325 | and Browserify to pick the correct version, so you should not have to configure
|
1326 | anything. 😎
|
1327 |
|
1328 | **Important**: the browser builds do not support `loadModules` or `listModules`,
|
1329 | because 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 |
|
1342 | Clone 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 |
|
1345 | For code coverage, run `npm run cover`.
|
1346 |
|
1347 | If you submit a PR, please aim for 100% code coverage and no linting errors.
|
1348 | Travis will fail if there are linting errors. Thank you for considering
|
1349 | contributing. :)
|
1350 |
|
1351 | # What's in a name?
|
1352 |
|
1353 | Awilix is the mayan goddess of the moon, and also my favorite character in the
|
1354 | game [SMITE](http://www.smitegame.com/play-for-free?ref=Jeffijoe).
|
1355 |
|
1356 | # Author
|
1357 |
|
1358 | Jeff Hansen - [@Jeffijoe](https://twitter.com/Jeffijoe)
|