# @nwire/container

Typed DI container, generic over the app's `Cradle` shape. Awilix-backed under the hood — you get scope hierarchy, lazy proxy cradle, lifetimes (singleton/transient/scoped), disposers, and factory-with-deps for free.

```bash
pnpm add @nwire/container
```

## Why

Apps grow into needing:

- **Typed bindings.** `container.cradle.logger` should be `Logger`, not `unknown`.
- **Per-request scopes.** Each HTTP request gets its own scope so `user`, `tenant`, `requestId` don't leak across requests.
- **Lifetimes.** `singleton` for connections; `scoped` for per-request transactions; `transient` for fresh-each-time values.
- **Disposers.** Graceful shutdown calls `db.close()`, `redis.quit()` automatically when a scope ends.
- **Source location.** Studio shows "this binding was registered at `src/wires/api.ts:42`".

Awilix has done this for over a decade. We wrap it with a typed `Container<TCradle>` interface so app code stays decoupled from awilix-specific imports, and you opt into the full Awilix surface via `.raw` when you need it.

## Surface

```ts
interface Container<TCradle extends object = object> {
  resolve<T, K extends string>(name: K): K extends keyof TCradle ? TCradle[K] : T;
  register<T, K extends string>(
    name: K,
    factory: K extends keyof TCradle ? TCradle[K] | (() => TCradle[K]) : T | (() => T),
  ): void;
  readonly cradle: TCradle;
  createScope(): Container<TCradle>;
  has(name: keyof TCradle | (string & {})): boolean;
  list?(): ReadonlyArray<BindingEntry>;
  readonly raw: AwilixContainer<TCradle>;
}

function createContainer<TCradle extends object = object>(): Container<TCradle>;
```

## Consumer example

```ts
import { createContainer } from "@nwire/container";

// Each plugin exports its cradle contribution as a type.
import type { AuthCradle } from "@nwire/auth"; // { "auth.user": User; … }
import type { DbCradle } from "@nwire/drizzle"; // { "db.pg": PgClient }

// App composes the cradle (plus its own bindings).
type AppCradle = AuthCradle &
  DbCradle & {
    config: AppConfig;
    logger: Logger;
  };

const root = createContainer<AppCradle>();
root.register("config", { port: 3000, dbUrl: process.env.DATABASE_URL! });
root.register("logger", () => ({ log: (s) => process.stdout.write(s + "\n") }));
root.register("db.pg", () => new PgClient(root.cradle.config.dbUrl)); // ← typed

root.cradle.logger.log(`Listening on :${root.cradle.config.port}`); // ← typed, autocomplete

// Per-request scope — child inherits, owns request-scoped values
server.on("request", async (req, res) => {
  const scope = root.createScope();
  scope.register("requestId", crypto.randomUUID());
  scope.register("user", await loadUser(req));
  // hand `scope` to your handler / framework
});
```

## Lazy by default

`container.cradle` is an Awilix proxy. Untouched bindings never instantiate; touched ones are cached (singleton) or fresh-each-time (transient) per the registration.

```ts
const factory = vi.fn(() => new ExpensiveDb());
root.register("db", factory);

// factory not called yet
void root.cradle.db; // ← now called once
void root.cradle.db; // ← cached, not called again
```

## Full Awilix when you need it

`container.raw` is the underlying `AwilixContainer`. Use it for disposers, asClass, scoped lifetimes, async resolution, or `loadModules` — anything beyond the simple register/cradle/resolve surface:

```ts
import { asFunction, asValue, Lifetime } from "awilix";

root.raw.register({
  pgPool: asFunction(({ config }) => new PgPool(config.dbUrl))
    .singleton()
    .disposer((p) => p.end()), // graceful shutdown
  pgTx: asFunction(({ pgPool }) => pgPool.transaction())
    .setLifetime(Lifetime.SCOPED) // one transaction per request scope
    .disposer((tx) => tx.rollback()), // auto-rollback if scope ends without commit
});
```

## Used by

Every Nwire transport (HTTP, queue, cron, MCP) creates its scope chain through this surface. Plugins register into it. Handlers read from a wire-composed ctx that pulls from the cradle.

## Notes

- Awilix uses PROXY injection mode by default — destructure parameters resolve from the cradle by name.
- `keyof TCradle` keys can include any string, including dotted names (`"auth.user"`, `"db.pg"`). Namespacing is the convention to avoid plugin name clashes.
