Skip to content

@nwire/container — alone

Typed dependency injection. Awilix under the hood with a small, focused surface that doesn't leak the wrapper. No decorators, no reflect-metadata, no class metadata fishing.

What it does

  • createContainer<TCradle>() returns a typed Container<TCradle>.
  • container.register(name, value) registers a binding — function values become asFunction(fn).singleton(), plain values become asValue(v).
  • container.resolve<T>(name) returns the value by name.
  • container.cradle.<name> exposes the same value with autocomplete via the TCradle generic.
  • container.createScope() returns a child container for per-request scopes.
  • container.list() enumerates every registration with kind: "singleton" | "transient" | "scoped".

Install

bash
pnpm add @nwire/container

Minimal example

ts
import { createContainer } from "@nwire/container/awilix"

interface Logger { info(msg: string): void }
interface Db     { query(sql: string): Promise<unknown[]> }

interface Cradle {
  logger: Logger
  db: Db
}

const container = createContainer<Cradle>()
container.register("logger", console)
container.register("db", () => ({ query: async () => [] }))

const log = container.cradle.logger
const db  = container.cradle.db

log.info("ready")
await db.query("select * from users")

Typed resolve

ts
const repo = container.resolve<UserRepo>("userRepo")

container.cradle is the preferred form when you've supplied a TCradle generic — it gives compile-time autocomplete. container.resolve() is the right call when the name is dynamic.

Per-request scope

ts
http.use((req, res, next) => {
  const scope = container.createScope()
  scope.register("requestId", req.headers["x-request-id"] ?? crypto.randomUUID())
  req.scope = scope
  next()
})

// Inside a handler:
const id = req.scope.resolve<string>("requestId")

Scopes inherit from the parent and add their own bindings. Disposers registered on a scope fire when the scope is closed, not when the parent shuts down.

What this is not

  • It's not a service locator — pass the container or its bindings, don't import { container } from "./container" everywhere.
  • It's not an IoC framework with auto-wiring — you declare bindings explicitly. The reward is no reflect-metadata, no decorators, no class-metadata edge cases.
  • It's not opinionated about lifetimes — function bindings default to singleton (cached after first resolve); use Awilix's full API via container.raw.register({...}) if you need scoped or pure transient.

When to use this and not the @nwire/app plugin form

The definePlugin(...) form in @nwire/app 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, reach for @nwire/app.

MIT licensed.