-
Notifications
You must be signed in to change notification settings - Fork 511
docs: add InferDI (dependency injection) example #879
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+151
−0
Merged
Changes from 2 commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,161 @@ | ||
| # 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`. | ||
|
|
||
| --- | ||
|
maxrendel marked this conversation as resolved.
Outdated
|
||
|
|
||
| ## 🚀 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<typeof root>` 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<InferdiHonoEnv<typeof root>>() | ||
|
|
||
| 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<typeof root, 'container'> | ||
|
|
||
| const app = new Hono<AppEnv>() | ||
| 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) | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.