npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

async-injection

v3.0.0

Published

A robust lightweight dependency injection library for TypeScript.

Readme

Async-Injection

CI npm version codecov License: MIT OSS Lifecycle

Lightweight TypeScript dependency injection — with first-class async support.

Most DI containers assume your dependencies are ready the moment they are constructed. async-injection doesn't.
Synchronous and asynchronous dependencies can coexist naturally in the same container, and the library resolves each correctly — whether you get them immediately or need to await them.

Install

npm install async-injection

Works in Node, browsers, Electron, and other runtimes.
Ships as both ESM and CJS side by side.

Quick start

@Injectable()
class SharedService {
    constructor(@Inject('LogLevel') @Optional('warn') private logLevel: string) { }
}

@Injectable()
class TransactionHandler {
    constructor(svc: SharedService) { }
}

const container = new Container();
container.bindClass(SharedService).asSingleton();  // one shared instance
container.bindClass(TransactionHandler);           // new instance on each get
container.bindConstant('LogLevel', 'info');        // override defaulted 'warn' level

const tx = container.get(TransactionHandler);

Tip:
Real-world projects should follow best practices like separation of concerns, having a composition root, and should avoid anti-patterns like service locator.

Setup

Two tsconfig.json settings are required:

{
  "experimentalDecorators": true,
  "emitDecoratorMetadata": true
}

Reflection metadata is also required. Rather than mandate a specific library, you have the freedom to bring your own — choose whichever fits your project:

Import it once at your entry point, before anything else:

import 'reflect-metadata';

Async dependencies

Synchronous injection is straightforward and well understood.
Asynchronous injection is also well established.
But when you are blending the two in the same container, it requires a little care.

get vs resolve

Think of get(X) / resolve(X) as a request not just for X, but for the entire tree of objects X depends on.
get is only safe when every node in that tree is already settled.

| Condition | When to use | |---|---| | All dependencies are synchronous, or async singletons are already resolved | container.get(X) | | Any dependency in the tree may still be pending | await container.resolve(X) |

Tip:
Call resolveSingletons(true) after your last bindXXX call and before any get call to avoid hard-to-debug timing issues.

When a dependency must do async work before it is usable — open a database connection, load remote config, etc. — there are two ways to handle it:

Async factory — bind an async factory that performs the initialization and returns the ready instance:

container.bindAsyncFactory(SharedService, async () => {
    const svc = new SharedService();
    return svc.connect();           // returns Promise<SharedService>
}).asSingleton();

// Option A — resolve everything up front, then use get() as normal
await container.resolveSingletons(true);
const tx = container.get(TransactionHandler);

// Option B — resolve on demand
const tx = await container.resolve(TransactionHandler);

Note:
A factory takes full responsibility for constructing and initializing its object — @PostConstruct is not called on factory-returned instances.
bindFactory and bindAsyncFactory are therefore the right choice when you need complete control over how an object is built, or when you cannot annotate the class.

@PostConstruct — mark an initialization method to run on the fully constructed object after the constructor returns.

The method can be synchronous or asynchronous, which is especially useful since a class constructor can never be async.
It is also useful because a base class constructor cannot call methods overridden by a subclass.
The method can have parameters which can be annotated with @Inject and @Optional — the container resolves and injects them before calling the method.
This lets you avoid storing dependencies from the constructor solely for post-construction use:

@Injectable()
class DatabasePool {
    @PostConstruct()
    async init(@Inject(DbConfig) config: DbConfig): Promise<void> {
        this.pool = await createPool(config);  // config is injected, not stored
    }
}

Important:
Always explicitly declare the return type (void or Promise<void>, never leave it to be inferred).
container.get() will throw if the return type is missing and the method actually does return a Promise.
Constructor and @PostConstruct parameters follow the same rules: class-typed params are auto-resolved by reflected type; use @Inject for interface or primitive types. Use @Optional() with no argument to pass undefined if you want to allow a JS parameter default.

Scopes

Create isolated or hierarchical scopes using multiple containers.
A child container searches its own bindings first, then walks up the parent hierarchy:

const child = new Container(parent);

IoC modules

No special module system needed — TypeScript's own import is your module system. Create a file, import your container, and register your bindings.

API

A Container's life follows a simple arc: configure it by registering bindings, activate it so async singletons can initialize, then use it to retrieve objects.

Configure

| | | |---|----------------------------------------------------------------| | new Container(parent?) | Create a container; optionally inherit bound ids from a parent | | bindConstant(id, value) | Bind a fixed value | | bindClass(id, class?) | Bind a class (requires @Injectable) | | bindFactory(id, fn) | Bind a synchronous factory function | | bindAsyncFactory(id, fn) | Bind an asynchronous factory function | | .asSingleton() | Chain: share one instance across the Container | | .onError(cb) | Chain: handle construction errors |

Activate

| | | |---|---| | resolveSingletons(true) | Await all async singleton initializations |

Use

| | | |---|---| | get(id) | Synchronously retrieve a bound value | | resolve(id) | Asynchronously retrieve a bound value (see get vs resolve) |

Annotate your classes

| | | |---|---| | @Injectable() | Required on any class bound with bindClass | | @Inject(id) | Explicitly declare which id to inject into a constructor parameter | | @Optional(default?) | Provide a fallback if the id is not bound; omit the argument to let a JS parameter default apply | | @PostConstruct() | Mark a method to run after full construction (sync or async); parameters annotated with @Inject/@Optional are injected by the container | | @Release() | Mark a method to call when a singleton is released | | InjectionToken<T> | Create a typed token for binding interfaces or primitives |

Acknowledgements

Inspired by InversifyJS, NestJS async providers, Darcy Rayner's DI walkthrough, and Carlos Delgado's QueryablePromise idea.

Support Resources

The support/ directory contains supplementary guides that are not part of the library itself:

License

MIT © 2020–2024 Frank Stock