From 624bac295783a7dfbd1d60ebdcbc9bb8c8c77171 Mon Sep 17 00:00:00 2001 From: vkabanov Date: Sat, 27 Jun 2026 14:38:35 +0000 Subject: [PATCH 1/3] docs: add InferDI (dependency injection) example --- .vitepress/config.ts | 4 ++ examples/inferdi.md | 153 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 157 insertions(+) create mode 100644 examples/inferdi.md diff --git a/.vitepress/config.ts b/.vitepress/config.ts index 261f5dc8..c523a67c 100644 --- a/.vitepress/config.ts +++ b/.vitepress/config.ts @@ -298,6 +298,10 @@ export const sidebarsExamples = (): DefaultTheme.SidebarItem[] => [ text: 'Hono Docs Generator', link: '/examples/hono-docs', }, + { + text: 'InferDI (Dependency Injection)', + link: '/examples/inferdi', + }, ], }, { diff --git a/examples/inferdi.md b/examples/inferdi.md new file mode 100644 index 00000000..83be5c06 --- /dev/null +++ b/examples/inferdi.md @@ -0,0 +1,153 @@ +# InferDI + +[InferDI](https://github.com/inferdi/inferdi) is a zero-dependency, decorator-free, strongly typed dependency injection container for TypeScript. The [`@inferdi/hono`](https://www.npmjs.com/package/@inferdi/hono) middleware wires it into Hono's request pipeline: it creates one DI scope per request, exposes it on the context as `c.var.di`, and disposes it after the response completes — no decorators, reflection, or route scanning. + +The graph _is_ the type: a misordered dependency, a missing key, or a request-scoped value leaking into a singleton are all compile errors, not runtime surprises. + +## 🛠️ Installation + +```bash +npm install @inferdi/inferdi @inferdi/hono +``` + +> **Note:** +> InferDI ships on both npm and JSR. On Deno install it with `deno add jsr:@inferdi/inferdi jsr:@inferdi/hono npm:hono`. + +--- + +## 🚀 Getting Started + +### 1. Build a container + +Register your services on a root `Container`. Dependencies are passed as a tuple of keys and type-checked positionally against the constructor — a wrong order or type is a compile error. Each registration declares a lifetime: `singleton` (default, one instance per container), `scoped` (one per request), or `transient` (new on every resolve). + +```ts +// container.ts +import { Container } from '@inferdi/inferdi' + +export function buildRootContainer() { + return ( + new Container() + .registerClass('logger', Logger, []) + // `request` is scoped: a fresh instance per request scope. + .registerClass('request', RequestContext, [], 'scoped') + // `users` is scoped too — it depends on the scoped `request`. + .registerClass('users', UserService, ['logger', 'request'], 'scoped') + ) +} +``` + +> **Note:** +> A `singleton` cannot depend on a `scoped` or `transient` service — that would leak a short-lived value into a long-lived one, and InferDI rejects it at compile time. Keep request-bound services `scoped`. + +--- + +### 2. Add the middleware + +`inferdiHono` creates a request scope before your handlers run and disposes it afterwards. `InferdiHonoEnv` types `c.var.di` as your concrete scope, so `.get(key)` stays fully typed. + +```ts +import { Hono } from 'hono' +import { inferdiHono, type InferdiHonoEnv } from '@inferdi/hono' +import { buildRootContainer } from './container' + +const root = buildRootContainer() +const app = new Hono>() + +app.use('*', inferdiHono({ container: root })) + +export default app +``` + +--- + +### 3. Hydrate the request scope + +Use `setupScope` to fill request-scoped services with per-request data (request id, authenticated user, …) before any handler sees the scope. It runs once per request and may be async. + +```ts +app.use('*', inferdiHono({ + container: root, + setupScope: (scope, c) => { + const request = scope.get('request') + request.requestId = crypto.randomUUID() + request.userId = c.req.header('x-user-id') + } +})) +``` + +--- + +### 4. Resolve services in handlers + +Resolve any registered service from the request scope with `c.var.di.get(key)`. The returned value is fully typed, and scoped services share one instance for the whole request. + +```ts +app.get('/users/:id', async (c) => { + const user = await c.var.di.get('users').profile(c.req.param('id')) + return c.json(user) +}) +``` + +`c.get('di')` is equivalent to `c.var.di`. To use a different context key, pass `key` and reflect it in the env type: + +```ts +type AppEnv = InferdiHonoEnv + +const app = new Hono() +app.use('*', inferdiHono({ container: root, key: 'container' })) + +app.get('/users/:id', (c) => + c.json(c.var.container.get('users').profile(c.req.param('id'))) +) +``` + +--- + +## ⚙️ Options + +`inferdiHono` accepts the following options: + +| Option | Default | Description | +| ---------------- | -------------------- | ------------------------------------------------------------------------- | +| `container` | — | **Required.** The root container. The middleware never disposes the root. | +| `key` | `'di'` | Context variable key used for `c.var[key]` / `c.get(key)`. | +| `createScope` | `root.createScope()` | Overrides how the request scope is created. May be async. | +| `setupScope` | — | Hydrates the scope before handlers run. May be async. | +| `disposeScope` | `scope.dispose()` | Overrides request-scope disposal. May be async. | +| `autoDispose` | `true` | Set to `false` (or return `false`) when application code owns disposal. | +| `onDisposeError` | `console.error` | Sink for post-response disposal failures. | + +--- + +## 🌊 Streaming + +A streaming response returns before the stream callback finishes, so disable auto-disposal with `skipInferdiDispose(c)` and dispose the scope yourself when the stream ends. + +```ts +import { stream } from 'hono/streaming' +import { skipInferdiDispose } from '@inferdi/hono' + +app.get('/events', (c) => { + skipInferdiDispose(c) + const scope = c.var.di + const events = scope.get('events') + + return stream(c, async (s) => { + try { + for await (const event of events.subscribe()) { + await s.write(`data: ${JSON.stringify(event)}\n\n`) + } + } finally { + await scope.dispose() + } + }) +}) +``` + +--- + +## See also + +- [InferDI Hono adapter docs](https://inferdi.com/adapters/hono) +- [`@inferdi/hono` on GitHub](https://github.com/inferdi/inferdi/tree/main/packages/hono) From 1650b43f0cf336a2e64d51c01a0241ae8da5f262 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Tue, 30 Jun 2026 06:49:04 +0000 Subject: [PATCH 2/3] ci: apply automated fixes --- examples/inferdi.md | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/examples/inferdi.md b/examples/inferdi.md index 83be5c06..8eee152d 100644 --- a/examples/inferdi.md +++ b/examples/inferdi.md @@ -32,7 +32,12 @@ export function buildRootContainer() { // `request` is scoped: a fresh instance per request scope. .registerClass('request', RequestContext, [], 'scoped') // `users` is scoped too — it depends on the scoped `request`. - .registerClass('users', UserService, ['logger', 'request'], 'scoped') + .registerClass( + 'users', + UserService, + ['logger', 'request'], + 'scoped' + ) ) } ``` @@ -66,14 +71,17 @@ export default app Use `setupScope` to fill request-scoped services with per-request data (request id, authenticated user, …) before any handler sees the scope. It runs once per request and may be async. ```ts -app.use('*', inferdiHono({ - container: root, - setupScope: (scope, c) => { - const request = scope.get('request') - request.requestId = crypto.randomUUID() - request.userId = c.req.header('x-user-id') - } -})) +app.use( + '*', + inferdiHono({ + container: root, + setupScope: (scope, c) => { + const request = scope.get('request') + request.requestId = crypto.randomUUID() + request.userId = c.req.header('x-user-id') + }, + }) +) ``` --- From d3aa224d431472c00a53e5e6c025a4c5ff9260df Mon Sep 17 00:00:00 2001 From: vkabanov Date: Tue, 30 Jun 2026 09:29:52 +0000 Subject: [PATCH 3/3] docs: fix InferDI guide markdown syntax --- examples/inferdi.md | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/examples/inferdi.md b/examples/inferdi.md index 8eee152d..48e42753 100644 --- a/examples/inferdi.md +++ b/examples/inferdi.md @@ -10,11 +10,9 @@ The graph _is_ the type: a misordered dependency, a missing key, or a request-sc npm install @inferdi/inferdi @inferdi/hono ``` -> **Note:** +> [!NOTE] > InferDI ships on both npm and JSR. On Deno install it with `deno add jsr:@inferdi/inferdi jsr:@inferdi/hono npm:hono`. ---- - ## 🚀 Getting Started ### 1. Build a container @@ -42,11 +40,9 @@ export function buildRootContainer() { } ``` -> **Note:** +> [!NOTE] > A `singleton` cannot depend on a `scoped` or `transient` service — that would leak a short-lived value into a long-lived one, and InferDI rejects it at compile time. Keep request-bound services `scoped`. ---- - ### 2. Add the middleware `inferdiHono` creates a request scope before your handlers run and disposes it afterwards. `InferdiHonoEnv` types `c.var.di` as your concrete scope, so `.get(key)` stays fully typed. @@ -64,8 +60,6 @@ app.use('*', inferdiHono({ container: root })) export default app ``` ---- - ### 3. Hydrate the request scope Use `setupScope` to fill request-scoped services with per-request data (request id, authenticated user, …) before any handler sees the scope. It runs once per request and may be async. @@ -84,8 +78,6 @@ app.use( ) ``` ---- - ### 4. Resolve services in handlers Resolve any registered service from the request scope with `c.var.di.get(key)`. The returned value is fully typed, and scoped services share one instance for the whole request. @@ -110,8 +102,6 @@ app.get('/users/:id', (c) => ) ``` ---- - ## ⚙️ Options `inferdiHono` accepts the following options: @@ -126,8 +116,6 @@ app.get('/users/:id', (c) => | `autoDispose` | `true` | Set to `false` (or return `false`) when application code owns disposal. | | `onDisposeError` | `console.error` | Sink for post-response disposal failures. | ---- - ## 🌊 Streaming A streaming response returns before the stream callback finishes, so disable auto-disposal with `skipInferdiDispose(c)` and dispose the scope yourself when the stream ends. @@ -153,8 +141,6 @@ app.get('/events', (c) => { }) ``` ---- - ## See also - [InferDI Hono adapter docs](https://inferdi.com/adapters/hono)