TypeScript library · v3.0.0
@zirion/ioc
A small Node.js library that creates services, passes in what they need, and controls when values are reused.
@zirion/ioc
An IoC container for Node.js with explicit dependency injection, async factories, and singleton, request, or transient providers.
- No decorators or
reflect-metadata. List dependencies explicitly withinject. - Sync and async resolution. Avoid promise overhead for synchronous graphs; await async factories and hooks when needed.
- Scopes for services and HTTP requests. Transient is the default; request instances are shared across resolutions with the same context object.
- Small runtime footprint. No runtime dependencies, TypeScript inference for chained registrations, and ESM and CommonJS exports.
Full documentation: Dependency Injection guide
Installation
Requires Node.js 20 or later.
npm install @zirion/ioc
Quick start
import { Container, InjectScope } from '@zirion/ioc';
class Database {}
class UserService {
constructor(readonly database: Database) {}
}
const container = new Container()
.add(Database, { scope: InjectScope.SINGLETON })
.add(UserService, { inject: [Database] });
const service = await container.getOrFail(UserService);
// For this synchronous graph: container.getOrFailSync(UserService)
Dependencies in inject follow constructor argument order. getOrFail() awaits async factories and lifecycle hooks; getOrFailSync() returns immediately for a fully synchronous graph. Independent async dependencies resolve in parallel.
Scopes
| Scope | Lifetime |
|---|---|
TRANSIENT (default) |
New value for each resolution or injection. |
SINGLETON |
One shared value per container. |
REQUEST |
One value per context object, shared across separate resolutions. |
Pass the same context object to each getOrFail(selector, context) call within a request. The container does not create or close HTTP request contexts automatically.
For a runnable comparison server using all three scopes, see benchmarks/http-orders.mjs. TypeScript examples for Node HTTP, Express, and Fastify are in examples.
Benchmarks
| Scenario score | @zirion/ioc | Awilix | InversifyJS | TSyringe |
|---|---|---|---|---|
| Basic graph | 92.4 | 60.8 | 90.3 | 41.3 |
| Graph width | 85.4 | 68.5 | 100.0 | 46.8 |
| Request scope | 83.0 | 50.3 | 0.0 (N/A) | 77.5 |
| HTTP request | 98.7 | 97.8 | 98.1 | 100.0 |
| Overall score | 89.9 | 69.4 | 72.1 | 66.4 |
Benchmark report and scoring methodology · Russian version
Migration
Migration guide: InversifyJS, Awilix, and TSyringe · Russian version
Contributing
See the contribution guidelines.
Changelog
See CHANGELOG.md.
License
This project is licensed under the MIT License.
Release history
- Cache validated dependency plans and direct registration references for repeated resolution; rebuild plans after a new provider is registered.
- Resolve classes with zero or one dependency through a direct constructor path, avoiding per-resolution argument arrays and spread calls; retain async dependencies and initialization hooks.
- Change the default provider scope to
TRANSIENT; registrations that need caching must explicitly selectSINGLETON. - Add
getSync()andgetOrFailSync()for synchronous graphs, withASYNC_RESOLUTION_REQUIREDwhen a provider needs a promise. - Resolve synchronous dependency paths without intermediate promises, skip
Promise.allwhen unnecessary, and classify class selectors at registration instead of each resolution. - Infer registered selector values in chained TypeScript container calls and reject unknown selectors at compile time; add a
typecheckcommand for the public API. - Resolve independent
injectdependencies in parallel while preserving argument order and validating direct dependencies before starting factories. - Cache successful cycle checks until registration changes, and snapshot each
injectarray at registration. - Add the
TRANSIENTscope to create a new value for each resolution or injection, with optional context forwarding to request-scoped dependencies. - Cache singleton factory results even when they are
false,0,'',null,undefined, orNaN. - Preserve the order and duplicates in
inject, including repeated selectors and different selectors that resolve to the same value. - Add regression tests for singleton caching and dependency injection.
- Project migrated to TypeScript 6.
- Fix the CommonJS package entry so
require('@zirion/ioc')loads the built library. - Share in-flight singleton and request-scoped resolutions between concurrent requests, and retry after factory failures.
- Detect circular
injectdependencies and report them with errorCIRCULAR_DEPENDENCY. - Allow an empty string as a selector and return ordinary functions from factories as values.
- Await asynchronous
onInitializedhooks before returning an instance, and retry resolution if a hook fails. - On
finalize()process now skip request-scoped class instances that cannot be created without a context. - Add reproducible container benchmarks, migration guides, and TypeScript integration examples for Node HTTP, Express, and Fastify.
- Made container resolution asynchronous:
get()andgetOrFail()now return promises. - Replaced provider
valuewith an async-capablevalueFactoryAPI. - Added typed container composition: each
add()call carries registered selectors into the resulting container type. - Added object selectors and support for factories that return either values or class constructors.
- Added configurable logging with a default console logger and exported logger interfaces.
- Added the
onInitializedlifecycle hook andbuild()as an alias forfinalize(). finalize()now supports registered objects as well as class instances and returns the built container.
- Removed the package-level prebuilt container export; consumers now create their own
Containerinstance. - Improved return-type inference for class selectors in
get()andgetOrFail(). - Simplified package exports and removed the
@zirion/ioc/containerentry point. - Declared Node.js 18+ support and added browser compatibility metadata.
- Updated documentation and package discovery keywords.
- Fixed the finalized lifecycle hook lookup: the container now correctly calls
onFinalized(). - Added test execution to the package release checks.
- Updated the TypeScript, ESLint, Vitest, and Node.js type toolchain.
- Added structured
DependencyInjectionErrorerrors and exported error codes for container failures. - Added validation for missing and duplicate targets, unknown selectors and scopes, and invalid resolvers.
- Added explicit errors for missing request contexts and invalid singleton-to-request dependency graphs.
- Fixed singleton caching so resolved instances are reused.
- Added utilities for readable selector names, broader test coverage, and coverage reporting.
- Maintenance publication aligning the package version metadata with the npm release.
- Reworked the package structure and public entry points.
- Added separate CommonJS, ES module, and declaration builds.
- Added package documentation, usage examples, and contributing guidelines.
- Added dependency update and stale-issue automation for the repository.
- Initial npm release of the IoC container.
- Added provider registration and lazy dependency resolution.
- Added constructor injection with singleton and request-scoped lifetimes.
- Added context-based caching for request-scoped providers and the
onFinalizedlifecycle contract.
Interactive example · @zirion/ioc 3.0.0
Try @zirion/ioc
Edit the example and run it locally in your browser. No code is sent anywhere.
Ready. Press Run code or ⌘/Ctrl + Enter.