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.1

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.

async-injection is a general-purpose DI library for TypeScript. It keeps ordinary dependency injection simple, while supporting asynchronous initialization when your application needs it.

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.

Why async-injection?

Designed to be a practical default choice for dependency injection in TypeScript applications.

  • TypeScript-first: Fits naturally into real TypeScript code.
  • Simple for common cases: Covers constructor injection, constants, factories, singletons, scopes, and lifecycle hooks without unnecessary complexity.
  • Async-capable by design: Handles asynchronous initialization without requiring special-case wiring.
  • Framework-independent: Works in backend services, frontend applications, Electron, libraries, or shared packages.
  • Scales with your application: Starts with straightforward synchronous DI and makes it easy to add async dependencies later.

Quick start

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

@Injectable()
class TransactionHandler {
    constructor(private 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

If all dependencies are ready, we can simply:

const tx = container.get(TransactionHandler);

If any dependency could still be asynchronously initializing, use:

const tx = await container.resolve(TransactionHandler);

Setup

Decorator-based dependency injection in TypeScript relies on emitted type metadata.
Two tsconfig.json settings are required:

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

Reflection metadata support 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.
The subtle part is when both need to coexist in the same container.

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

A child container inherits bindings from a parent container and can add or override bindings locally.

This is useful when a local scope needs its own bindings or singleton lifetime while still inheriting from a root container.

const root = new Container();
const child = new Container(root);

The child checks its own bindings first, then falls back to the parent. When the scope ends, release its singletons:

await child.releaseSingletons();

For Node-specific ambient request context that should flow implicitly through async calls, Node's AsyncLocalStorage is often a better fit.

IoC modules

No special module system is needed. TypeScript's own import mechanism is enough: create a file, import your container, and register your bindings.

Features

  • Constructor injection
  • Constants, sync factories, and async factories
  • Singleton and transient lifetimes
  • Parent/child containers for scoped resolution
  • @PostConstruct and @Release lifecycle hooks
  • Typed injection tokens for interfaces and primitives
  • Support for both synchronous and asynchronous dependency trees

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 |

Support Resources

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

Acknowledgements

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

License

MIT © 2020–2024 Frank Stock

Child Containers with async-injection

A child container inherits bindings from a parent container and can add or override bindings locally.

Use a child container when you want an explicit local scope with its own bindings or singleton lifetime, while still reusing services from the application's root container.

NOTE
The files in this directory are not built as part of this project.
They are provided as illustrative starting points for common integration patterns.


Creating a child container

typescript
import {Container} from 'async-injection';

const root = new Container();
const child = new Container(root);

The child checks its own bindings first, then falls back to the parent.


When to use one

Child containers are useful for:

  • feature-local singletons
  • temporary overrides
  • isolated test setup
  • operation-specific service graphs

This is different from Node's AsyncLocalStorage, which is usually a better fit for ambient request context that should flow implicitly through async calls.

Use a child container when you want explicit scoped bindings, local singleton lifetime, and explicit cleanup.


Example

typescript
const root = new Container();
root.bindConstant('AppName', 'demo');

const child = new Container(root);
child.bindConstant('RequestId', 'req-123');

const appName = child.get('AppName');     // inherited from root
const requestId = child.get('RequestId'); // local to child

Scoped singletons

A child container can have its own singletons that live only for that scope:

typescript
child.bindClass(SessionService).asSingleton();
await child.resolveSingletons(true);

const session = child.get(SessionService);

When the scope ends, release its singletons:

typescript
await child.releaseSingletons();

If a scoped singleton has cleanup work, mark a method with @Release().


Summary

Use child containers when you need:

  • inherited root bindings
  • local overrides
  • scoped singletons
  • explicit cleanup at the end of a feature, test, or operation