jeffijoe / awilix Goto Github PK
View Code? Open in Web Editor NEWExtremely powerful Inversion of Control (IoC) container for Node.JS
License: MIT License
Extremely powerful Inversion of Control (IoC) container for Node.JS
License: MIT License
The current DI Container works, but it is using a common anti-pattern called the Service Locator.
I was wondering your thoughts on adding a configuration option to enhance the container to read functions and classes for their dependencies based on explicit name. Something like so:
container.register({
dep1: asFunction(makeDep1)
dep2: asFunction(makeDep2)
})
function makeDep1() {
function do() { }
return { do }
}
function makeDep2(dep1) {
function useDep1() {
dep1.do();
}
}
Where makeDep2
would automatically have an instance of dep1
injected into it because the container recognizes that dep1
is a registered dependency.
We could easily inspect function and constructor calls for strictly named dependencies and save the required dependencies in the registration object by name.
The advantage to this is that I will know exactly what is being used by a class or function based on the arguments. This is nice for testing, and it makes the dependencies more explicit, which is good.
The issues come into play with minification. If someone wants to minify to take advantage of V8 function inlining or something then the naming conventions would break. We could either tell the users, "Hey, minification will break your app in this configuration" or offer something like Aurelia or Angular where they have special functions and/or string based dependency names to avoid minification issues.
If you like the idea I can try and draft a legitimate proposal and potential write some prototype code to start with. I'd have no problem contributing to this container, I think it has promise!
I am looking to implement a collection of domain event handlers managed by a mediator object. The mediator will need to identify all event handlers for a given event and pass the event to an instance of each handler. I am already using Awilix as my IoC container, so it would be helpful if I could register all of my event handlers there. The mediator would have a reference to the container, and would resolve the event handler at runtime. For example, I suggest the following syntax.
container.register({
mediator: asClass(Mediator)
createdEventHandlers: [
asClass(DatabaseEventHandler),
asClass(MessageBusEventHandler)
],
deletedEventHandler: [
asClass(CleanUpEventHandler)
]
});
The resolve method would return all event handlers as an array.
const handlers = container.resolve('createdEventHandlers');
// or
const handlers = container.cradle.createdEventHandlers;
for (const handler of handlers) {
// Send the event to the handler
}
As part of #50 we're introducing AwilixTypeError
which has a different signature but the same goal. In v3, we remove AwilixNotAFunctionError
and use AwilixTypeError
instead.
Since v2.0, Awilix has injected an ES6 Proxy
which was used to resolve dependencies so we wouldn't have to use a shoddy regex to parse function signatures.
@cjhoward92 submitted PR #21 that added support for what is now referred to as CLASSIC
injection mode, where—using a decent regex—the function signature was parsed to extract parameter names.
It wasn't without its issues though, for example arrow functions were not that well supported (#30), and so I wrote a basic parser to replace the regex which has worked very well.
I have personally started using CLASSIC
injection more, especially in TypeScript projects, and #59 added support for default parameters, making the parser even more tolerant of real-world code.
The parser runs once per registration, so runtime performance should actually be better than using the Proxy.
What do you guys think? Which injection mode do you use in your projects?
Should we change the default to CLASSIC
?
Note: this does not mean we're getting rid of PROXY
, this is just about changing the default.
/cc @ddproxy @cjhoward92 @blove @cikasfm @Seldszar @niyoko @thisdotrob @mikhas-rock @leorossi @ith @Vincz @neerfri @ewrogers @dboune
TypeScript has gotten really good, and we already have typings (thanks to @blove for those).
This will be for v3.
I've pushed [email protected]
to npm which has a few breaking changes for JS users and a few more for TS users (new, better typings as v3 is now written in TypeScript).
Please try it out so I can ship it as stable ASAP!
With v3 comes a few new cool features.
This has been a very requested feature. The idea is you can tell Awilix how to dispose
of a dependency—for example, to close a database connection—when calling container.dispose()
.
const pg = require('pg')
const { createContainer, asFunction } = require('awilix')
const container = createContainer()
.register({
pool: (
asFunction(() => new pg.Pool({ ... }))
.singleton()
.disposer((pool) => pool.end())
)
})
// .. later, but only if a `pool` was ever created
container.dispose().then(() => {
console.log('One disposable connection.. disposed! Huehehehe')
})
alias
resolver (#55)This new resolver lets you alias a registration. This is best illustrated with an example:
const { alias, asValue, createContainer } = require('awilix')
const container = createContainer()
container.register({
laughingOutLoud: asValue('hahahahaha'),
lol: alias('laughingOutLoud')
})
container.resolve('lol') // 'hahahahaha'
It's essentially the exact same as calling container.resolve('laughingOutLoad')
, but lol
might be easier to type out in your constructors. 😎
This is a pretty small feature but was the most difficult to land, mainly because I had to write a smarter parser and tokenizer, not to mention they are now way better at skipping over code. Check out the tests, it's pretty wild.
class MyClass {
constructor(db, timeout = 1000) { /*...*/ }
}
container.register({
db: asFunction(..)
})
// Look! No errors!! :D
container.build(MyClass) instanceof MyClass // true
Awilix now ships with 4 module flavors: CommonJS (same old), ES Modules for Node, ES Modules for the Browser and UMD.
Please see the Universal Module section in the readme for details.
The following is a list of known breaking changes. If there's any I've missed feel free to let me know.
This means a bunch of interfaces have been renamed and made more correct.
If you're a TypeScript user, this is great news for you. 😄
ResolutionMode
is now InjectionMode
(#57)ResolutionMode.js
renamed to injection-mode.ts
ResolutionMode
renamed to InjectionMode
The terminology is now "you register a resolver to a name".
REGISTRATION
symbol renamed to RESOLVER
registrations.js
renamed to resolvers.ts
registrationOptions
in loadModules
renamed to resolverOptions
registerClass
, registerFunction
and registerValue
removed (#60)This was done to simplify the API surface, and also simplifies the implementation greatly (less overloads). You should be using container.register
with asClass
, asFunction
and asValue
instead.
This simplifies the TypeScript types and is also considered a good practice. All configuration functions rely on this
, meaning you should not do:
// I don't know why you would, but DONT DO THIS!
const singleton = asClass(MyClass).singleton
singleton()
However, this also means you can now "split" a resolver to configure it differently. For example:
class GenericSender {
constructor(transport) {
this.transport = transport
}
send() {
if (this.transport === 'email') {
// ... etc
}
}
dispose() { /*...*/ }
}
const base = asClass(GenericSender).scoped().disposer((g) => g.dispose())
const emailSender = base.inject(() => ({ transport: 'email' })
const pushSender = base.inject(() => ({ transport: 'push' })
container.register({
emailSender,
pushSender
})
AwilixNotAFunctionError
in favor of a generic AwilixTypeError
(#52)This should not have an impact on userland code but I thought I'd mention it.
There are a bunch of internal uses of this error, so I thought it made sense to consolidate them into one error type.
husky
+ lint-staged
to lint, format and test every commit to ensure top code quality.Please give it whirl and let me know how it goes! I'll update the changelog once stable ships.
I'm getting below error trying to compile code. There are some typings missing or I'm using incompatible versions?
node_modules/awilix-koa/lib/invokers.d.ts(1,10): error TS2305: Module '"node_modules/awilix/lib/awilix"' has no exported member 'Registration'.
node_modules/awilix-koa/lib/invokers.d.ts(1,24): error TS2305: Module '"node_modules/awilix/lib/awilix"' has no exported member 'RegistrationOptions'.
package.json
"awilix": "^3.0.0-rc.3",
"awilix-koa": "^1.0.3",
"typescript": "2.6.1"
Container configuration
import { asFunction, asValue, createContainer } from 'awilix'
import { config } from './config'
import { run } from './some/someController'
export const configureContainer = () => {
const container = createContainer()
container.register({
config: asValue(config),
someController: asFunction(run),
})
return container
}
Using container in koa app
import { scopePerRequest } from 'awilix-koa'
const container = configureContainer()
app
.use(scopePerRequest(container))
As as side note I had to manually add "@types/glob": "^5.0.33",
into dev dependencies - it seems awilix is not pulling it as dependency an TS is whining about it.
Please let me know if you need any additional information.
It would prevent typing the names twice in several occasions:
class MyClass { /* ... */}
function myFunction(MyClass){ /* ... */ }
// AS IS
container.registerClass('MyClass', MyClass);
container.registerFunction('myFunction', myFunction);
// SUGGESTION
container.registerClass(MyClass);
container.registerFunction(myFunction);
container.register(MyClass);
container.register(myFunction);
This will allow aliasing a registration to another.
Example:
container.register({
permissionChecker: asClass(PermissionChecker).singleton(),
perms: aliasTo('permissionChecker')
})
container.resolve('perms') === container.resolve('permissionChecker')
Hi @jeffijoe,
I'm using Hydra for my project with Awilix. Everything work fine with Awilix and Awilix-express for REST API. However, if the app is fired via hydra.getHydra().on(...)
, the umfHandler cannot be resolved.
module.exports = class Hydra {
constructor (opts) {
this.config = require('./config').HYDRA;
this.umfHandler = umfHandler; // Resolve handler successfully
}
initialize () {
return new Promise(function (resolve, reject) {
hydra
.init(config, function () {
hydra.registerRoutes(routerConfig);
hydra.getHydra().on('message', (message) => {
this.umfHandler(message); // Undefined
});
})
.then(function () {
resolve(hydra);
})
.catch(function (err) {
reject(err);
});
});
}
};
Do we have any way to resolve the function like that.
// container.js
container.loadModules(
[
['../src/repository/*.js', Lifetime.SINGLETON],
['../src/service/*.js', Lifetime.SCOPED]
],
opts
).register({
mongoose: asFunction(
() => require('mongoose').createConnection(config.database.mongodb)
).singleton().disposer((conn) => conn.close()),
umfHandler: asFunction(
() => require('./umf')
).singleton(),
containerMiddleware: asValue(scopePerRequest(container))
});
Hey,
This is more of a question than an issue, but when you are using a bundler like webpack, is there still a way to do autoloading? I understand that it's not going to work, because the paths you pass to it are only valid in the source folder. Any idea how this could be resolved?
i have structure like this
-apps
---user
-----model.js
---container.js
code at container.js
container.loadModules([ './**/controller.js', './**/service.js', './**/model.js', ], { formatName: (name, descriptor) => { const splat = descriptor.path.split('/'); const namespace = splat[splat.length - 2]; const fileName = _.upperFirst(name); return namespace + fileName; }, registrationOptions: { lifetime: awilix.Lifetime.SINGLETON, }, });
error like this
node_modules/sequelize-cli/lib/assets/models/model.js:4 var <%= names %> = sequelize.define('<%= names %>', { ^ SyntaxError: Unexpected token <
When I rename the model name to another name it's not error.
How to solve this issue?
Apologies in advance for the dumb weird question.
It seems like awilix's preferred method of catching errors is from the scope(?) of the container, correct? Or is better to create it first then catch errors whenever creating services attached to it? Or is catching errors on a (general) api level, with a generic handler be the best? I understand it's kind of depends on the project, but I'm curious what would be the best practice in the context of awilix.
Let me know if this question is unclear. Not sure if you've seen silicon valley, but I feel like Bernice asking these questions and using your amazing framework!
Current syntax:
container.loadModules([
'services/*.js'
], {
registrationOptions: {
lifetime: Lifetime.SCOPED
}
})
container.loadModules([
'repositories/*.js'
], {
registrationOptions: {
lifetime: Lifetime.SINGLETON
}
})
Wanted syntax:
container.loadModules([
'repositories/*.js', // will get the default
['services/*.js', { lifetime: Lifetime.SCOPED }],
// or maybe even..
['services/*.js', Lifetime.SCOPED]
])
This is akin to Babel's plugin syntax.
OK, stupid question.
This is a great framework! As coming from Java / Spring world to Node.JS - I was looking for something similar and I'm so glad I finally found this!
I bet that the reason I wasn't able to find this lib/fw in Search Engines is because of the name...I'd never follow this name to find DI framework. I know Spring is not named as "DI" or anything similar so maybe there's really no reason for it; but it's already a 10.000x times more popular than this, but to help this project grow and be used more often - why not name it something related to "DI"? Well at least "Jeff's DI" or "Awilix DI" or smth? :)
I mean I know I could rename it myself in my local code...
const di = require('awilix')
const container = di.createContainer({
resolutionMode: di.ResolutionMode.PROXY
})
or
const ioc = require('awilix')
const container = ioc.createContainer({
resolutionMode: ioc.ResolutionMode.PROXY
})
I mean I'd suggest to rename this to one of:
jeffjoe/di
jeffjoe/node-di
( or js-di
? )jeffjoe/awilix-di
Or similar. WDYT?
I'm using this library for developing a module which doesn't (yet) need REST APIs. I'd like to inject few of the mocked dependencies and mix it up with real dependencies (may change based on test case) to do perform integration test. The (anti?) pattern I'm using right now is to have the module responsible for creating Container to accept mocked dependencies. You have any better ideas to do this?
module.exports = ({ dependency } = {}) => {
const container = createContainer();
container.register({
dependency: asValue(dependency || require('./realDependency'),
........... //Few more which don't need mocking yet
............
});
return container;
};
Then, from test case only, I pass the dependency ever, but not from production code. Let me know if there is any better way to achieve this.
The API will still be container.register
, but asClass
, asFunction
and asValue
will be referred to as resolvers
after this change.
This is a breaking change:
REGISTRATION
symbol renamed to RESOLVER
registrations.ts
renamed to resolvers.ts
registrationOptions
in loadModules
renamed to resolverOptions
In order for a parent container to dispose it's children, the parent needs to track the children.
This is a problem because children are usually created per request, meaning each request means an entry in the child array for the parent. With enough requests, that array will be very large.
I don't think there's a good way to deal with this, other than simply not disposing child containers which means we can avoid the child array.
Related to #48
Hey, good library
i want to know if there is an option or how can i resolve multiple dependencies in one call ?
example:
const {deps1, deps2, deps3} = container.resolve('deps1', 'deps2', 'deps3')
hey guys I'm getting this error when trying to create a container:
import { createContainer } from 'awilix'
container = createContainer()
Error:
/usr/src/app/node_modules/awilix/lib/createContainer.js:306
backend │ throw err
backend │ ^
backend │
backend │ TypeError: Cannot convert a Symbol value to a string
backend │ at Array.join (native)
backend │ at createErrorMessage (/usr/src/app/node_modules/awilix/lib/AwilixResolutionError.js:19:48)
backend │ at AwilixResolutionError (/usr/src/app/node_modules/awilix/lib/AwilixResolutionError.js:46:11)
backend │ at resolve (/usr/src/app/node_modules/awilix/lib/createContainer.js:252:15)
backend │ at Object.get (/usr/src/app/node_modules/awilix/lib/createContainer.js:97:28)
backend │ at formatValue (util.js:345:37)
backend │ at formatProperty (util.js:794:15)
backend │ at util.js:654:12
backend │ at Array.map (native)
backend │ at formatObject (util.js:653:15)
backend │ at formatValue (util.js:592:16)
backend │ at inspect (util.js:186:10)
backend │ at exports.format (util.js:130:20)
I'm using node v6.9.2
First of all, thanks for creating this nice and simple library. Looking at this issue (#50), I understand that in order to access the registered instances, one needs access to container. How do you recommend accessing the container from separate modules? I'd not like to pass the container instance out of band.
As an example
//index.js
const container = createContainer();
container.register({
myRepository: asClass(require('./lib/sample/myRepository')).singleton(),
logger: asClass(require('./lib/logger)).singleton()
});
//myRepository.js
class MyRepository {
createSample() {
return container.build(SampleInstance); **//How do I get access to container?**
}
}
//SampleInstance.js
class SampleInstancer{
constructor({logger}){ //**Expected to be injected by container, but it's not registered**
this.logger = logger;
}
}
Hi,
I've one question about this wonderful lib:
I load most of my modules using loadModules()
. All these modules are functions returning an object.
In some of these modules, I call a function to init some stuff, like this:
module.exports = ({ someDeps }) => {
initStuff();
return { /* Modules properties... */ };
}
When all modules are loaded, I'd like to call a function which needs all the initStuff()
calls to be done.
container.loadModules([
'server/services/**/!(*.spec).js'
], {
formatName: 'camelCase',
registrationOptions: {
lifetime: awilix.Lifetime.SINGLETON
}
});
doSomethingUsingStuff(); // This one needs all modules to be loaded
From my tests, doSomethingUsingStuff()
is called before the modules' functions are actually called.
So, my question is: when are the modules resolved? Is it when loadModules()
is called or later, when it's injected somewhere?
Hope my question makes sense :)
Thanks!
Support calling a function with dependencies / instantiate a class with dependencies without having to register it.
This would make it easier to write factories that have to return concrete implementations without being forced to inject the cradle (requiring PROXY
resolution mode).
There are 2 ways we can go about this:
container.build(ClassOrFunction, opts)
function, detecting the type of resolver to use.container.build(asClass(Class))
function, explicitly stating the resolver to use, including the ability to fluidly configure it.Perhaps we can support both with a simple heuristic like checking if ClassOrFunction.resolve
exists, in which case it's a resolver.
With Rollup we can use some tricks to strip out the Node-specific stuff, as well as provide a format for bundlers like Webpack, as well as a UMD build.
Hi! First of all, you did a really good job with Awilix. I like IOC and your lib is really good.
I’m currently working on a node framework, modular and I’d like to use Awilix (I don't want ti reinvent the wheel) as one of the main ingredient, using the power of container to have a robust plugins system (plugins able to declare services).
The idea would be to be able to “describe” services creation like the PHP Symfony Framework do (https://symfony.com/doc/current/service_container/definitions.html).
For example, imagine if I have a mail builder service sending emails thank to a “mailer” service, I would like to be able to describe the service like that (the “@“ prefix means it’s a reference to a service).
// services.js
services: {
“my-mail-builder”: {
class: “service/my-mail-builder”,
args: [“@mailer”, “[email protected]”]
}
}
And why not with other features, like calls to setters, services tagging, etc…
Currently, correct me if I’m wrong, Awilix parses the final class to detect the services that need to be injected, right ? The “resolution modes”: classic or proxy.
So, the service itself is responsible for declaring every others services it depends on. It could be great to be able to declare the dependencies (the constructor args) outside the service itself. An "explicit" way. So, for the example above, we would end with something like that:
container.register({
“my-amazing-service”: asClass(require(“service/my-amazing-service”)).withArgs(“@mailer”, “[email protected]”)
});
This way, we would be able to change the way the services are wired really easily, with config.
What do you think ?
First of all congratulations for this module, really simple and powerful!
Maybe a stupid question but, how can I inject dependencies into my koa route files?
For example, I would like to inject my controller in my route, like this :
import Router from 'koa-router';
const productsRoute = ({ productController }) => {
const router = new Router({
prefix: '/products',
});
router.get('/', productController.getProducts());
return router;
};
It's possibile with makeInvoker from awilix-koa package ?
I'm using koa 2.3.0 and koa-router 7.2.1.
Many thanks,
Damien
Hey there,
i love your library. Great work! :)
I don't know is something like this is possible (but it would be great, maybe i just missed it in docs) to do when using loadModules so you can setup lifetime
, resolutionMode
or register
per imported module.
For example, something like this:
class FooService {
// Either this solution
static resolveConfig() {
return {
lifetime: awilix.Lifetime.SINGLETON,
resolutionMode: awilix.ResolutionMode.CLASSIC
}
}
barMethod() {}
}
// ... or this solution
// (probably this one because it can be applied on both functions and classes)
FooService.lifetime = awilix.Lifetime.SINGLETON;
FooService.resolutionMode: awilix.ResolutionMode.CLASSIC;
This way you would have total flexibility to setup imports when using automatic module loading.
Currently loadModules
returns an array of objects that tell you what paths were loaded. However given that all other container methods (apart from resolve
for obvious reasons) are chainable (returns the container itself). Should loadModules
do that as well? Is the result from loadModules
more useful than chaining?
From within a component that is getting a dependency injected, how can I instantiate multiple objects of that dep?
Basically, what should I write into Other.prototype.createTwoUsers
body?
Example files:
File: user.js
'use strict';
module.exports = User;
function User({ log }) {
this.id = Math.random();
this.logger = log;
}
User.prototype.testStuff = function() {
this.logger.info(`testStuff(): ${this.id}`);
}
File: other.js
'use strict';
module.exports = Other
function Other({ user }) {
this.user = user;
};
Other.prototype.createTwoUsers = function() {
----> what should I do here? <-----
}
File: log.js
'use strict';
module.exports = Log
function Log({}) {};
Log.prototype.info = function(message) {
console.log(message);
}
File: container.js
'use strict';
const { createContainer, Lifetime } = require('awilix');
const container = createContainer();
// Load modules
const log = require('./log');
const user = require('./user');
const other = require('./other');
container.registerClass({
log: log,
user: user,
other: other
});
module.exports = container;
File: entrypoint.js
'use strict';
const container = require('./library/container');
// Require modules
const other = container.resolve('other');
other.createTwoUsers();
This is my project's structure:
project
│
└───bin
│ │ dependencies.js
│ │ server.js
│
└───src
│ │
│ └───services
│ │ │ user.js
│ │ │ ...
│ │
│ └───lib
│ │ response-handlers.js
│ │ ...
inside bin/dependencies.js
I'm trying to load my js files from services and lib:
container.loadModules([
'../src/lib/*.js',
'../src/services/*.js'
])
When I try to resolve the functions from those modules I get the following error:
AwilixResolutionError: Could not resolve 'sendValidResponse'
Hi!
I would like to use Awilix in a Typescript project.
I'm trying to import the listModules
function:
import {listModules} from 'awilix';
But getting an error:
src/DepsManager.ts(2,43): error TS2305: Module '"[....]/node_modules/awilix/lib/awilix"' has no exported member 'listModules'.
Is it a problem with my import
or something missing in types declaration?
Thanks for your help! :)
The use case here is that some service may require additional options besides dependencies. For example:
export default function userRepository ({ db, timeout }) {
return {
find () {
return Promise.race([
db.query('select * from users'),
Promise.delay(timeout).then(() => Promise.reject(new Error('Timed out')))
])
}
}
}
db
is a registered module that can be injected anywhere. That's good.timeout
would also need to be registered, making it available to all registered modules. But what if different modules have different timeouts? Or if you just want to keep the timeout
local to one specific module?Providing a registration-time API for injecting locals to specific services/groups of services (using loadModules
).
import { createContainer, Lifetime, asFunction } from 'awilix'
import createUserRepository from './repositories/userRepository'
const container = createContainer()
// Using the fluid variant:
.register({
userRepository: asFunction(createUserRepository)
// provide an injection function that returns an object with locals.
.inject(() => ({ timeout: 2000 }))
})
// Shorthand variants
.registerFunction({
userRepository: [createUserRepository, { injector: () => ({ timeout: 2000 }) }]
})
// Stringly-typed shorthand
.registerFunction(
'userRepository',
createUserRepository,
{ injector: () => ({ timeout: 2000 }) }
)
// with `loadModules`
.loadModules([
['repositories/*.js', { injector: () => ({ timeout: 2000 }) }]
])
Awilix is pretty slick! Thank you for hard work.
I think I might be missing how injector
is meant to function.
Say I have:
registry.register('electronBrowserWindow', asClass(BrowserWindow, {
injector: () => ({ height: 600, width: 1200, backgroundColor: '#312450' }),
lifetime: Lifetime.TRANSIENT,
injectionMode: awilix.InjectionMode.PROXY
}));
I would expect when electronBrowserWindow
is resolved it to be created with the arguments provided in injector
. It would seem this is not the case.
I think I understand why - as the constructor signature to BrowserWindow
has no arguments when in reality it optionally takes quite a few: https://electronjs.org/docs/api/browser-window#new-browserwindowoptions
console.log(BrowserWindow.toString());
function BrowserWindow() { [native code] }
What would be the approach to having awilix really, pretty please, cherry on top inject - the arguments provided via the injector
. Maybe there already is a pattern to accomplish this use case?
Firstly, thanks for creating a great library!
I would like to know how one goes about registering a class to a container. At the moment I am getting a TypeError: Cannot read property 'constructor' of undefined
error.
Here is the class I want to register:
module.exports = ({ mongoose }) => {
const { Schema } = mongoose;
const bugSchema = new Schema({
id: {
type: String,
required: true,
index: true,
unique: true,
},
url: {
type: String,
required: true,
unique: true,
},
});
const Bug = mongoose.model('Bug', bugSchema);
return Bug;
};
And here is how I'm registering it:
const Bug = require('./schemas/bug');
container.register({
Bug: asClass(Bug),
});
Thanks
I made a simple test project: https://github.com/smikheiev/awilix-test
There are two classes: AppModel
and AppController
, AppController
has dependency of AppModel
. Both classes are registered in container. And when I run it in browser (built with babel and webpack) it doesn't work like it should, but with node it works like a charm.
When I run it with node AppController
has correct dependency injection. Constructor argument opts
is Proxy
(opts [AwilixContainer.cradle]
) and AppModel
instance can be simply get from it (using object property or destructuring).
But when I run it with browser (babel+webpack) argument opts
of AppControler
constructor is arguments
object (opts Arguments(1)
), where Proxy
is the first element and it's needed to do something like opts[0]
to get AppModel
instance from it.
I found that the problem in browser is with passing arguments to constructor, because if to get the model with container.cradle
it's ok.
I might be an idiot, but I bet I'm not the only one. 😄 I spent hours trying to figure out why container.listModules()
just refuses to work in my serverless/aws-lambda app, until it hit me – I'm bundling my app using webpack! So there's simply no files to look for and require at all!
I don't know, maybe if you added a short disclaimer that one shouldn't expect it to work like this, it would save somebody else a little time or more…
As pointed out by @cjhoward92, not guarding against null
or undefined
will make the container explode later.
Its as simple as adding some assert(typeof fn === 'function', 'fn must be a function/class')
I migrated from another DI library to awilix
. I migrated the codebase but now I am struggling with test suite, since I use mocks for many of the tests I am wondering how I can inject my custom modules after I register them in the container.
Is there a way to do that?
I think InjectionMode
makes more sense, given it decides whether to use CLASSIC
injection mode or PROXY
injection mode.
This is a breaking change.
resolution-mode.ts
renamed to injection-mode.ts
ResolutionMode
renamed to InjectionMode
Awilix dependencies parser fails to detect factory function that is declared using arrow function with single parameter and no bracket enclosing the parameter.
How to reproduce:
Using CLASSIC
resolution mode, declare following service. It will fail to parse the dependencies.
module.exports = config => {
return {
a: (b, c) => b
};
}
But service below will work (notice brackets that enclose the parameter):
module.exports = (config) => {
return {
a: (b, c) => b
};
}
I suspect this line causes the problem.
const DEPS_EXPR = /(\(\s*([^)]+?)\s*\)|\(\))/
Awilix version: 2.6.0
Node version: 8.2.1
First of all, thanks for developing awilix. Apart from this issue it has been super simple to get set up and running.
Say I have the following code, prior to using awilix:
// Bar.js
const Foo = require('./Foo');
class Bar {
buildResults(data) {
return data.map(elem => new Foo(elem));
}
}
module.exports = Bar;
// Foo.js
module.exports = class Foo {};
If I want to convert the project to use awilix, I would set up my container as follows:
const Foo = require('./Foo');
const Bar = require('./Bar');
const { createContainer } = require('awilix');
const container = createContainer();
container.registerClass({
foo: Foo,
bar: Bar,
});
module.exports = container;
But then injecting Foo into Bar as below obviously won't work:
// Bar.js
class Bar {
constructor({ foo }) {
this.foo = foo;
}
buildResults(data) {
return data.map(elem => new this.foo(elem));
}
}
module.exports = Bar;
Is there a way to configure awilix to do this, or is this an anti pattern that I should not be following anyway?
For example:
function createMyService (dep1, dep2 = 123) {}
Allow dep2
to not be registered, pass in undefined.
Would only work for classic mode as we parse the function signature.
This looks like a cool DI container service. Could you include a TypeScript definition file (index.d.ts) for use with TS?
Simplifies tooling, Jest handles everything.
Currently, doing asClass().singleton().injector()
modifies the same instance. I'd like to make these immutable. It shouldn't break anything but it's going in v3 anyway.
From your README.md example:
function Database({ connectionString }) {
// We can inject plain values as well!
this.conn = connectToYourDatabaseSomehow(connectionString);
}
This does not pass for me... 😢.
Dependency innitiations are often an asynchronous operation, especially when working with fail-fast policy. If a Peer cannot be found - I want the service to fail as soon as possible - preferably before it joins the load balancer.
For this I need my dependencies to be more eager-loaded and less lazy loaded.
Would you please consider support for
container.registerAsyncFunction( function(opts, next) )
container.regisgterPromiseFunction( function(opts) )
?
Thanks
At the moment, there's shortcuts registerClass
, registerFunction
and registerValue
which map to using register
with asClass
, asFunction
and asValue
respectively.
I'm beginning to think it might be beneficial to remove them to reduce the API surface, making it harder for newcomers to use the wrong signature and getting confused by the rather large README.
Are anyone using these overloads?
If we all agree on scrapping the shortcuts, I'll try to get this into v3.
I have multiple classes registered on my container, and all has been working flawlessly so far. However, I have one class that returns the following error when attempting to register it as a class:
AwilixTypeError: asClass: expected Type to be class, but got undefined.
I cannot for the life of me figure out why this class is returning this error -- I've read through the docs multiple times and didnt spot anything -- perhaps I am missing something?
Here's the class:
export default class ServerConfig {
_app;
constructor({ logger, uploadsController }) {
this._app = express();
this.logger = logger;
this.uploadsController = uploadsController;
this.initGraphql();
this.initGraphiql();
this.initUploads();
}
get app() {
return this._app;
}
initGraphql() {
// implementation
}
initGraphiql() {
// implementation
}
initUploads() {
// implementation
}
}
Here's my container setup:
const container = createContainer();
container.register({
logger: asValue(logger),
salesforceClient: asValue(salesforceGeneral),
s3client: asClass(S3, { lifetime: Lifetime.SINGLETON }),
uploadsController: asClass(UploadsController, { lifetime: Lifetime.SINGLETON }),
caseResolver: asClass(CaseResolver, { lifetime: Lifetime.SINGLETON }),
commentResolver: asClass(CommentResolver, { lifetime: Lifetime.SINGLETON }),
uploadResolver: asClass(UploadResolver, { lifetime: Lifetime.SINGLETON }),
salesforceService: asClass(SalesforceService, { lifetime: Lifetime.SINGLETON }),
s3Service: asClass(S3Service, { lifetime: Lifetime.SINGLETON }),
serverConfig: asClass(ServerConfig, { lifetime: Lifetime.SINGLETON }),
});
Again, all of the other classes and values are resolved fine, it is strictly an issue with ServerConfig
. Please let me know if I can provide any additional information.
When running Babel with ES2015/stage-X presets, the container.loadModules
method does not properly register ES6 classes due to how Babel transpiles them to ES5 (is-class
returns false).
My workaround was to ditch ES2015 preset and go with ES2016/ES2017 and selectively choose our own plugins, as we have upgraded to Node 6.9.4. However, I can understand that some people may not have that luxury.
If this issue cannot be fixed directly (detection of transpiled classes), then it would be ideal if loadModules
could take asClass
/asFunction
/asValue
methods as hints in addition to the Lifetime
.
Example:
container.loadModules([
['services/**/*.js', Lifetime.SCOPED, asClass], // these are classes
['models/**/*.js', Lifetime.SCOPED, asValue], // these are exported as schemas
['routes/**/*.js', Lifetime.SINGLETON], // these are exported as factory functions
...
])
Support disposing dependencies.
Each resolver (registration) can specify a disposer function to call when container.dispose()
is called.
This is useful for managing dependencies that have resources to dispose—connection pools for example.
In test runners like Jest, watch-mode will recycle worker processes which means connections may or may not have been closed between each run. In testing HTTP servers, It is a good practice to call server.close()
after the tests are done.
We want to be able to run cleanup on dependencies in the same way. For example:
server.on('close', () => container.dispose())
container.dispose()
disposer
option for asClass
and asFunction
resolversSCOPED
and SINGLETON
registrations can be disposed as they are the only ones the container caches.const pg = require('pg')
const { createContainer, asFunction } = require('awilix')
const container = createContainer()
.register({
pool: (
asFunction(() => new pg.Pool({ ... }))
.singleton()
.disposer((pool) => pool.end())
)
})
// .. later, but only if a `pool` was ever created
container.dispose().then(() => {
console.log('One disposable connection.. disposed! Huehehehe')
})
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.