@nwire/container — alone
Typed dependency injection. No decorators, no reflect-metadata, no class metadata fishing. Just typed tokens and three provideX methods.
What it does
token<T>(name)creates a typed identifier — the only thing your code depends on.container.provideValue/provideFactory/provideClassregisters a binding.container.resolve(token)returns aT, type-checked at compile time.- Scopes (
transient/singleton/scopedper child container) supported. - Child containers (
container.child()) for per-request scopes.
Install
bash
pnpm add @nwire/containerMinimal example
ts
import { Container, token } from "@nwire/container"
interface Logger { info(msg: string): void }
interface Db { query(sql: string): Promise<unknown[]> }
const LoggerT = token<Logger>("Logger")
const DbT = token<Db>("Db")
const container = new Container()
container.provideValue(LoggerT, console)
container.provideFactory(DbT, () => ({ query: async () => [] }))
const log = container.resolve(LoggerT)
const db = container.resolve(DbT)
log.info("ready")
await db.query("select * from users")Class providers
ts
class UserService {
constructor(private db: Db, private log: Logger) {}
list() { this.log.info("listing"); return this.db.query("select * from users") }
}
const UserServiceT = token<UserService>("UserService")
container.provideClass(UserServiceT, UserService, [DbT, LoggerT]) // typed deps arrayPer-request scope
ts
const RequestId = token<string>("RequestId")
http.use((req, res, next) => {
const scope = container.child()
scope.provideValue(RequestId, req.headers["x-request-id"] ?? uuid())
req.scope = scope
next()
})
// Inside a handler:
const id = req.scope.resolve(RequestId)What this is not
- It's not a service locator — you don't
import { container } from "./container"everywhere. Pass the container or its tokens. - It's not an IoC framework with auto-wiring — you declare bindings explicitly, by hand. The reward is no reflect-metadata, no decorators, no class-metadata edge cases.
- It's not opinionated about lifetimes — you choose scoped vs singleton per binding.
When to use this and not the L3 @nwire/app plugin form
The L3 definePlugin(...) form wraps provide + boot + shutdown + on together so a single closure packages binding + lifecycle. If your dependencies are static (created once, never replaced, no boot work, no shutdown work), @nwire/container alone is enough. The moment you need lifecycle, climb to L3.