
Security News
PolinRider: North Korea-Linked Supply Chain Campaign Expands Across Open Source Ecosystems
PolinRider expands across npm, Packagist, Go modules, and Chrome extensions, using hidden loaders to target developer environments.
@blue.ts/di
Advanced tools
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.
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.
bun add @blue.ts/di
npm install @blue.ts/di
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);
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
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
singletonOne instance for the lifetime of the container. Shared across all scopes.
container.register(Database, { lifetime: "singleton", factory: () => new Database() });
scopedOne 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
transientA new instance on every get() call. Never cached.
container.register(Job, { lifetime: "transient", factory: () => new Job() });
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" },
});
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.
autowireGenerates 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);
}
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.
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
NotFoundExceptionThrown 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);
}
}
ContainerExceptionThrown 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]
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.
ResolverThe 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;
}
AggregateError, Symbol.asyncDispose requires ES2022 / --lib ES2022)FAQs
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.
The npm package @blue.ts/di receives a total of 5 weekly downloads. As such, @blue.ts/di popularity was classified as not popular.
We found that @blue.ts/di demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer 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.

Security News
PolinRider expands across npm, Packagist, Go modules, and Chrome extensions, using hidden loaders to target developer environments.

Security News
Open source attacks are accelerating as AI coding agents pull in dependencies faster, with less human review.

Research
/Security News
Malicious Chrome and Firefox extensions posed as free VPNs while stealing clipboard data through later extension updates.