Research
Security News
Malicious npm Packages Inject SSH Backdoors via Typosquatted Libraries
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
async-injection
Advanced tools
A robust lightweight dependency injection library for TypeScript.
A robust lightweight dependency injection library for TypeScript.
Async-Injection is a small IoC container with support for both synchronous and asynchronous dependency injection, as well as isolated and/or hierarchical scopes.
You can get the latest release using npm:
$ npm install async-injection --save
To enhance flexibility, Async-Injection does not dictate which Reflect API implementation you use. However, you will need to explicitly choose and load one.
You can use:
The Reflect polyfill import should only be added once, and before Async-Injection is used:
// entry.ts
import "reflect-metadata";
// Your code here...
Please note that this library supports a wide variety of runtimes and is distributed as both esm and cjs modules, side by side.
Here we 'get' a new transaction handling object, that itself, relies on a shared service:
@Injectable()
class SharedService {
constructor(@Inject('LogLevel') @Optional('warn') private logLevel: string) { }
}
@Injectable()
class TransactionHandler {
constructor(svc: SharedService) { }
}
// Create a simple container (we will bind providers into it).
const container = new Container();
// A single instance will be created and shared by everyone.
container.bindClass(SharedService).asSingleton();
// A new instance will be created each time one is requested.
container.bindClass(TransactionHandler);
// If we omit this line, the logLevel of SharedService will be initialized to 'warn'
container.bindConstant('LogLevel', 'info');
// In our request processing code (which would be an anti-pattern)...
// Instantiate a new transaction handler (it will be injected with the shared service).
const tx = container.get(TransactionHandler);
NOTE:
The examples in this ReadMe are contrived to quickly communicate concepts and usage.
Your real world project should of course follow best practices like separation of concerns, having a composition root, and should avoid anti-patterns like service locator.
Scopes can be created using multiple Containers, and/or a hierarchy of Containers.
Why reinvent the wheel? TypeScript is great!
Implement the "module" you want and just import it:
my-http-ioc-module.ts
import {myContainer} from './app';
import {Logger, HttpClient} from './services';
import {HttpClientGotWrapper} from './impl';
myContainer.bind(Logger).asSingleton();
myContainer.bind(HttpClient, HttpClientGotWrapper);
For simplicity, it is recommended that you use traditional synchronous injection for any class where that is possible.
But when that's just to much work, you can "blend" synchronous and asynchronous injection.
To support "blending", we introduce three new methods on the Container
which will be explained below.
Perhaps in the example above, our SharedService
is useless until it has established a database connection.
Of course such a simple scenario could easily be handled in user-land code, but as application complexity grows, this becomes more tedious and difficult to maintain.
Let's modify the example as follows:
@Injectable()
class SharedService {
constructor() { }
connect(): Promise<void> { ... }
}
const container = new Container();
// Bind a factory function that awaits until it can fully create a SharedService.
container.bindAsyncFactory(SharedService, async () => {
let svc = new SharedService();
return await svc.connect();
}).asSingleton();
// A new transient instance will be created each time one is requested.
container.bindClass(TransactionHandler);
// Wait for all bound asynchronous factory functions to complete.
// This step is optional. You could omit and use Container.resolve instead (see alternative below).
await container.resolveSingletons(true);
// We are now connected to the database
// In our request processing code...
const tx = container.get(TransactionHandler);
As an alternative, we could remove the call to Container.resolveSingletons
, and in our request processing code, simply call Container.resolve
.
const tx = await container.resolve(TransactionHandler);
Blending synchronous and asynchronous injection adds complexity to your application.
The key to successful blending is to think of the object you are requesting, not as an object, but as a tree of objects with your object at the top.
Keep in mind that you may have transient objects which need to be created each time, as well as existing singleton objects in your dependency tree.
If you know ahead of time that every object which you depend on is immediately (synchronously) available, or if they are asynchronous singletons which have already been resolved (via Container.resolveSingletons
, or a previous call to Container.resolve
), then no need to wait, you can just Container.get
the tree.
Otherwise you need to await the full resolution of the tree with await Container.resolve
.
It is not always possible to fully initialize your object in the class constructor.
This (albeit contrived) demo shows that the Employee
class is not yet initialized when the Person
subclass tries to call the overridden state
method.
class Person {
public constructor() { this.describe(); }
protected state() { return "relaxing"; }
public describe() { console.log("Hi I'm '" + this.state() + "'"); }
}
class Employee extends Person {
constructor(private manager: boolean) { super(); }
protected state() { return this.manager ? "busy" : "producing"; }
}
// This will print:
// "Hi I'm 'producing", even though the author probably expected
// "Hi I'm busy", because they passed true for the 'manager' parameter.
new Employee(true);
Can we refactor code to work around this? Sure. You may have to submit a couple of PR's, re-write legacy code that has no unit tests, trash encapsulation, skip a few nights sleep, etc. But why?
A PostConstruct annotation ensure's your initialization method is working on a fully constructed version of your object.
Even better, since constructors cannot be asynchronous, PostConstruct gives you an easy way to asynchronously prepare an object before it's put into service.
Post construction methods can be either synchronous or asynchronous.
class A {
public constructor() { }
// Called before the object is placed into the container (or is returned from get/resolve)
@PostConstruct()
public init(): void { ... }
}
class D {
public constructor() { }
// Will not be placed into the container (or returned) until the Promise has been resolved.
@PostConstruct()
public init(): Promise<void> { ... }
}
return new Promise(...)
is not sufficient! You must explicitly declare the return type.Container.get
will throw an exception if you try to retrieve a class with @PostConstruct
on a method that returns a Promise
, but which does not declare it's return type to be Promise
.Container.resolveSingletons(true)
call between your last Container.bindXXX()
call and any Container.get
call.Async-Injection tries to follow the common API patterns found in most other DI implementations. Please refer to the examples above or the linked elements below for specific syntax.
Thanks to all the contributors at InversifyJS. It is a powerful, clean, flexible, inspiring design.
Thanks to everyone at NestJS for giving us Asynchronous providers.
Thanks to Darcy Rayner for describing a DI implementation so simply and clearly.
Thanks to Carlos Delgado for the idea of a "QuerablePromise" which allowed us to blend asynchronous DI with the simplicity of synchronous DI.
Copyright (c) 2020-2023 Frank Stock
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
FAQs
A robust lightweight dependency injection library for TypeScript.
The npm package async-injection receives a total of 2,003 weekly downloads. As such, async-injection popularity was classified as popular.
We found that async-injection demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
Security News
MITRE's 2024 CWE Top 25 highlights critical software vulnerabilities like XSS, SQL Injection, and CSRF, reflecting shifts due to a refined ranking methodology.
Security News
In this segment of the Risky Business podcast, Feross Aboukhadijeh and Patrick Gray discuss the challenges of tracking malware discovered in open source softare.