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

@blue.ts/di

v0.2.0

Published

A lightweight, async-first dependency injection container for TypeScript. No decorators, no `reflect-metadata`, no runtime dependencies — works in Node.js, Bun, Deno, and edge runtimes.

Readme

@blue.ts/di

A lightweight, async-first dependency injection container for TypeScript. No decorators, no reflect-metadata, no runtime dependencies — works in Node.js, Bun, Deno, and edge runtimes.

Installation

bun add @blue.ts/di
npm install @blue.ts/di

Quick start

import { Container, Token } from "@blue.ts/di";

// 1. Define identifiers
const LoggerToken = new Token<Logger>("Logger");

// 2. Create a container and register services
const container = new Container();

container.register(LoggerToken, {
  lifetime: "singleton",
  factory: () => new Logger(),
});

container.register(Database, {
  lifetime: "singleton",
  factory: async (r) => {
    const logger = await r.get(LoggerToken);
    return new Database(logger);
  },
});

// 3. Resolve
const db = await container.get(Database);

Identifiers

Every registration is keyed by an identifier. There are two kinds:

Token<T>

The recommended identifier. Carries the type T so get() returns the correct type without a manual type parameter.

const DbToken = new Token<Database>("Database");

container.register(DbToken, { lifetime: "singleton", factory: () => new Database() });

const db = await container.get(DbToken); // typed as Database

Constructor

A class itself can be used as its own identifier.

container.register(Database, { lifetime: "singleton", factory: () => new Database() });

const db = await container.get(Database); // typed as Database

Lifetimes

singleton

One instance for the lifetime of the container. Shared across all scopes.

container.register(Database, { lifetime: "singleton", factory: () => new Database() });

scoped

One instance per scope. Different scopes get different instances. Useful for per-request state.

container.register(RequestContext, { lifetime: "scoped", factory: () => new RequestContext() });

const scope = container.createScope();
const ctx = await scope.get(RequestContext); // new instance per scope

transient

A new instance on every get() call. Never cached.

container.register(Job, { lifetime: "transient", factory: () => new Job() });

Value registration

Register a pre-constructed value as a singleton. Useful for config objects or third-party instances.

container.register(ConfigToken, {
  lifetime: "singleton",
  value: { port: 3000, host: "localhost" },
});

Async factories

Factories can be async. The container resolves them transparently — callers always await container.get(...).

container.register(Database, {
  lifetime: "singleton",
  factory: async () => {
    const db = new Database();
    await db.connect("postgres://...");
    return db;
  },
});

Concurrent calls for the same singleton are deduplicated — the factory is called exactly once regardless of how many callers race.


autowire

Generates a factory from a constructor and an ordered list of dependency identifiers. Resolves all dependencies in parallel.

import { autowire } from "@blue.ts/di";

class UserService {
  constructor(readonly db: Database, readonly logger: Logger) {}
}

container.register(UserService, {
  lifetime: "singleton",
  factory: autowire(UserService, [Database, LoggerToken]),
});

This is equivalent to writing the factory manually:

factory: async (r) => {
  const [db, logger] = await Promise.all([r.get(Database), r.get(LoggerToken)]);
  return new UserService(db, logger);
}

Scopes

createScope() creates a child container that shares the same registry and singleton cache but maintains its own scoped instance cache.

// HTTP server example
app.use(async (req, res, next) => {
  await using scope = req.container = container.createScope();
  scope.register(RequestToken, { lifetime: "singleton", value: req });
  next();
});

Container implements Symbol.asyncDispose, so scopes work with await using — the scope is disposed automatically when the block exits.


Dispose

Register a dispose callback on any factory or value registration. Callbacks are called in reverse resolution order (dependents before dependencies) when dispose() is called.

container.register(Database, {
  lifetime: "singleton",
  factory: async () => {
    const db = new Database();
    await db.connect();
    return db;
  },
  dispose: (db) => db.disconnect(),
});

// On shutdown:
await container.dispose();

If multiple disposers fail, all of them are still called and the errors are collected into an AggregateError.

Scoped containers only dispose their own scoped instances. Root dispose() handles singletons.

{
  await using scope = container.createScope();
  // ... handle request
} // scope.dispose() called automatically — scoped instances cleaned up

Error handling

NotFoundException

Thrown synchronously when get() is called for an unregistered identifier.

try {
  await container.get(UnknownToken);
} catch (e) {
  if (e instanceof NotFoundException) {
    console.error("Not registered:", e.message);
  }
}

ContainerException

Thrown when a factory fails. Includes the full resolution chain so you can see exactly which dependency caused the failure.

ContainerException: Error occurred while instantiating service - Database (singleton) [UserService → Database]
Caused by: Error: ECONNREFUSED 127.0.0.1:5432

Circular dependencies are also reported with the chain:

ContainerException: Circular dependency detected - ServiceA (singleton) [ServiceA → ServiceB → ServiceA]

API reference

Container

| Method | Description | |--------|-------------| | register(id, registration) | Register a service. Re-registration invalidates the existing cached instance. | | get<T>(id) | Resolve a service. Returns Promise<T>. | | has(id) | Returns true if the identifier is registered. Does not guarantee resolution will succeed. | | createScope() | Creates a child container with its own scoped instance cache. | | dispose() | Disposes all tracked instances in reverse resolution order. Collects errors into AggregateError. | | [Symbol.asyncDispose]() | Alias for dispose(). Enables await using. |

Token<T>

const MyToken = new Token<MyService>("MyService");

autowire(constructor, dependencies)

autowire(MyService, [DepA, DepB]): Factory<MyService>

Returns a Factory<T> that resolves dependencies in parallel and passes them to constructor in order.

Resolver

The object passed into every factory. Narrower than Container by design — factories can resolve dependencies but cannot register new ones.

interface Resolver {
  get<T>(identifier: Identifier<T>): Promise<T>;
  has<T>(identifier: Identifier<T>): boolean;
}

Requirements

  • TypeScript 5+
  • Any runtime that supports ES2021 (AggregateError, Symbol.asyncDispose requires ES2022 / --lib ES2022)