@elsikora/cladi
Version:
Zero-dependency TypeScript DI toolkit with typed tokens and scoped lifecycles.
749 lines (592 loc) โข 29.1 kB
Markdown
<a id="top"></a>
<p align="center">
<img src="https://6jft62zmy9nx2oea.public.blob.vercel-storage.com/cladi-eBDDJPOc5fj21RO45PzAwrdkkqGCHi.png" width="700" alt="project-logo">
</p>
<h1 align="center">๐๏ธ ClaDI</h1>
<p align="center"><em>A zero-dependency TypeScript dependency injection toolkit for composition-root architecture and clean, modular applications</em></p>
<p align="center">
<a aria-label="ElsiKora logo" href="https://elsikora.com">
<img src="https://img.shields.io/badge/MADE%20BY%20ElsiKora-333333.svg?style=for-the-badge" alt="ElsiKora">
</a> <img src="https://img.shields.io/badge/TypeScript-3178C6.svg?style=for-the-badge&logo=typescript&logoColor=white" alt="TypeScript"> <img src="https://img.shields.io/badge/Node.js-339933.svg?style=for-the-badge&logo=node.js&logoColor=white" alt="Node.js"> <img src="https://img.shields.io/badge/npm-CB3837.svg?style=for-the-badge&logo=npm&logoColor=white" alt="npm"> <img src="https://img.shields.io/badge/Rollup-EC4A3F.svg?style=for-the-badge&logo=rollup&logoColor=white" alt="Rollup"> <img src="https://img.shields.io/badge/Vitest-6E9F18.svg?style=for-the-badge&logo=vitest&logoColor=white" alt="Vitest"> <img src="https://img.shields.io/badge/ESLint-4B32C3.svg?style=for-the-badge&logo=eslint&logoColor=white" alt="ESLint"> <img src="https://img.shields.io/badge/Prettier-F7B93E.svg?style=for-the-badge&logo=prettier&logoColor=black" alt="Prettier"> <img src="https://img.shields.io/badge/GitHub%20Actions-2088FF.svg?style=for-the-badge&logo=github-actions&logoColor=white" alt="GitHub Actions">
</p>
- ๐ชถ Zero runtime dependencies โ the entire DI container ships at minimal bundle cost with no transitive dependency risk
- ๐ Fully type-safe tokens via `createToken<T>()` โ dependency mismatches are caught at compile time, not runtime
- ๐งฉ Scope-aware lifecycles with deterministic disposal โ singleton, scoped, and transient providers with automatic cleanup in correct order
- ๐๏ธ Clean Architecture native โ domain and application layers never import DI APIs; only the composition root touches the container
- [Description](
- [Tech Stack](
- [Features](
- [Architecture](
- [Project Structure](
- [Prerequisites](
- [Installation](
- [Testing Toolkit](
- [Usage](
- [API Quick Reference](
- [Production Bootstrap](
- [Common Pitfalls](
- [Roadmap](
- [FAQ](
- [License](
- [Acknowledgments](
ClaDI (Class Dependency Injection) is a production-grade, zero-dependency TypeScript library that provides a complete dependency injection container with scope-aware lifecycles, typed tokens, and deterministic cleanup.
Unlike heavyweight DI frameworks that rely on decorators, reflection metadata, or runtime magic, ClaDI embraces **explicit composition roots** โ giving you full control over how your dependency graph is assembled, resolved, and disposed.
- **Backend Services**: Wire up HTTP handlers, database connections, and middleware with request-scoped isolation. Each incoming request gets its own scope with automatic cleanup.
- **CLI Tools**: Parse arguments, register them as scoped values, resolve command handlers, and dispose cleanly after execution.
- **Microservices**: Manage singleton adapters (database pools, message queues) alongside per-job transient workers with lifecycle guarantees.
- **Modular Monoliths**: Use the module system (`composeModules`) to define bounded contexts with explicit export contracts โ no accidental cross-boundary coupling.
- **Testing**: Swap out any provider at any scope level without touching production code. The typed token system catches mismatches at compile time.
ClaDI ships with **5 provider strategies** (`useValue`, `useClass`, `useFactory`, `useExisting`, `useLazy`), **3 lifecycle modes** (`singleton`, `scoped`, `transient`), built-in **circular dependency detection**, **captive dependency warnings**, and full **async resolution** support โ all in a package with zero runtime dependencies.
## ๐ ๏ธ Tech Stack
| Category | Technologies |
| ------------------- | -------------------------------- |
| **Language** | TypeScript |
| **Runtime** | Node.js |
| **Build Tool** | Rollup |
| **Testing** | Vitest |
| **Linting** | ESLint, Prettier |
| **CI/CD** | GitHub Actions, Semantic Release |
| **Package Manager** | npm |
| **Documentation** | MDX, Nextra |
## ๐ Features
- โจ **Typed Token System** โ `createToken<T>()` produces branded symbols that carry type information through the entire resolution chain, eliminating `any` casts and runtime type errors
- โจ **5 Provider Strategies** โ `useValue`, `useClass`, `useFactory`, `useExisting` (alias), and `useLazy` (deferred async) cover common dependency wiring patterns
- โจ **Scope-Aware Lifecycles** โ Singleton (process-wide), Scoped (per-request/job), and Transient (per-resolve) with deterministic cache semantics
- โจ **Hierarchical Scope Tree** โ Child scopes inherit parent registrations, can add local overrides, and dispose independently without affecting siblings
- โจ **Async Resolution Pipeline** โ `resolveAsync()` handles async factories, deduplicates concurrent singleton creation, and tracks in-flight resolutions during disposal
- โจ **Lifecycle Hooks** โ `onInit`, `afterResolve`, and `onDispose` hooks per provider for warmup, instrumentation, and cleanup
- โจ **Multi-Binding Support** โ Register multiple implementations for a single token and resolve all with `resolveAll()` / `resolveAllAsync()`
- โจ **Circular Dependency Detection** โ Detected both at resolution time and proactively via `validate()` at startup
- โจ **Captive Dependency Guard** โ Warns or errors when a singleton captures a scoped dependency, preventing subtle lifecycle bugs
- โจ **Module System** โ `createModule()` and `composeModules()` enable declarative, bounded-context module composition with explicit export contracts
- โจ **Decorator Support** โ Optional `@Injectable()`, `@Inject()`, `@Module()`, `@OnInit()`, `@AfterResolve()`, and `@OnDispose()` without requiring `reflect-metadata`
- โจ **Decorator Composition Helpers** โ `autowire()`, `createModuleFromDecorator()`, and `composeDecoratedModules()` keep decorator workflows explicit but concise
- โจ **Companion Testing Toolkit** โ `@elsikora/cladi-testing` adds test container helpers (`createTestingContainer`, `mockProvider`, `overrideProvider`) for app-level integration and unit tests
- โจ **Runtime Diagnostics** โ `explain(token)`, `snapshot()`, and `exportGraph()` provide operational visibility into provider lookup and dependency edges
- โจ **Deterministic Disposal** โ `dispose()` waits for in-flight async resolutions, runs disposers in reverse order, and supports `Symbol.dispose` / `Symbol.asyncDispose`
- โจ **Resolve Interceptors** โ Hook into every resolution with `onStart`, `onSuccess`, and `onError` callbacks for logging, metrics, or tracing
- โจ **Safe Deep Clone Utility** โ `safeDeepClone()` handles circular references, functions, Maps, Sets, and class instances unlike `structuredClone`
## ๐ Architecture
### System Architecture
```mermaid
flowchart TD
presentation[Presentation Layer]
application[Application Layer]
domain[Domain Layer]
infrastructure[Infrastructure Layer]
presentation --> application
presentation --> infrastructure
application --> domain
infrastructure --> domain
presentation --- ergonomics[Ergonomics]
presentation --- utilities[Create Utilities]
ergonomics --- decorators[Decorators]
ergonomics --- modules[Module Composer]
infrastructure --- diContainer[DI Container]
infrastructure --- coreFactory[Core Factory]
infrastructure --- loggerService[Console Logger]
diContainer --- cacheCoord[Cache Coordinator]
diContainer --- resolutionEngine[Resolution Engine]
diContainer --- registrationCoord[Registration Coordinator]
diContainer --- disposalCoord[Disposal Coordinator]
domain --- tokens[Tokens and Types]
domain --- enums[Enums]
domain --- interfaces[Contracts]
```
### Data Flow
```mermaid
sequenceDiagram
participant App as Application
participant Container as DI Container
participant Registry as Registration Coordinator
participant Engine as Resolution Engine
participant Cache as Cache Coordinator
participant Disposal as Disposal Coordinator
App->>Container: createDIContainer(options)
App->>Container: register(provider)
Container->>Registry: registerProvider(provider)
Registry->>Registry: validate provider shape
Registry->>Container: store registration
App->>Container: validate()
Container->>Container: walk dependency graph
App->>Container: resolve(token)
Container->>Engine: resolve(tokenSymbol)
Engine->>Container: findProvider(scope, key)
Container->>Cache: getScopedCacheForLifecycle()
alt Cache Hit
Cache-->>Engine: cached instance
else Cache Miss
Engine->>Engine: instantiate provider
Engine->>Engine: run onInit hook
Engine->>Cache: store in cache
Engine->>Disposal: register disposer
end
Engine->>Engine: run afterResolve hook
Engine-->>App: resolved instance
App->>Container: dispose()
Container->>Disposal: disposeInternal()
Disposal->>Disposal: wait for in-flight async
Disposal->>Disposal: dispose child scopes
Disposal->>Disposal: run disposers in reverse
Disposal->>Cache: clear all caches
```
## ๐ Project Structure
<details>
<summary>Click to expand</summary>
```
ClaDI/
โโโ .github/
โ โโโ workflows/
โ โ โโโ mirror-docs-to-docviewer.yml
โ โ โโโ mirror-to-codecommit.yml
โ โ โโโ qodana-quality-scan.yml
โ โ โโโ release.yml
โ โ โโโ snyk-security-scan.yml
โ โ โโโ test.yml
โ โโโ dependabot.yml
โโโ docs/
โ โโโ api-reference/
โ โ โโโ enums/
โ โ โโโ interfaces/
โ โ โโโ _meta.js
โ โ โโโ page.mdx
โ โโโ core-concepts/
โ โ โโโ advanced-di/
โ โ โโโ clean-architecture-playbook/
โ โ โโโ container/
โ โ โโโ error-handling/
โ โ โโโ factory/
โ โ โโโ registry/
โ โ โโโ _meta.js
โ โ โโโ page.mdx
โ โโโ getting-started/
โ โ โโโ _meta.js
โ โ โโโ composition-root-checklist.mdx
โ โ โโโ page.mdx
โ โโโ services/
โ โ โโโ logging/
โ โ โโโ _meta.js
โ โ โโโ page.mdx
โ โโโ utilities/
โ โ โโโ creation-helpers/
โ โ โโโ _meta.js
โ โ โโโ page.mdx
โ โโโ _meta.js
โ โโโ page.mdx
โโโ src/
โ โโโ application/
โ โ โโโ utility/
โ โโโ domain/
โ โ โโโ enum/
โ โ โโโ interface/
โ โ โโโ type/
โ โ โโโ index.ts
โ โโโ infrastructure/
โ โ โโโ class/
โ โ โโโ constant/
โ โ โโโ factory/
โ โ โโโ interface/
โ โ โโโ service/
โ โ โโโ index.ts
โ โโโ presentation/
โ โ โโโ ergonomics/
โ โ โโโ utility/
โ โโโ index.ts
โโโ test/
โ โโโ contract/
โ โ โโโ di-container.contract.test.ts
โ โโโ e2e/
โ โ โโโ core-integration.e2e.test.ts
โ โโโ perf/
โ โ โโโ di-container.perf.test.ts
โ โโโ unit/
โ โโโ application/
โ โโโ ergonomics/
โ โโโ infrastructure/
โ โโโ presentation/
โโโ CHANGELOG.md
โโโ commitlint.config.js
โโโ eslint.config.js
โโโ LICENSE
โโโ lint-staged.config.js
โโโ package-lock.json
โโโ package.json
โโโ prettier.config.js
โโโ release.config.js
โโโ rollup.config.js
โโโ rollup.test.config.js
โโโ tsconfig.build.json
โโโ tsconfig.json
โโโ vitest.e2e.config.js
โโโ vitest.unit.config.js
```
</details>
## ๐ Prerequisites
- Node.js >= 20.0.0
- npm >= 9.0.0
- TypeScript >= 5.0.0 (for development)
```bash
npm install @elsikora/cladi
yarn add @elsikora/cladi
pnpm add @elsikora/cladi
bun add @elsikora/cladi
```
```bash
git clone https://github.com/ElsiKora/ClaDI.git
cd ClaDI
npm install
npm run build
npm run test:all
npm run lint:all
```
For application tests, use the companion package `@elsikora/cladi-testing` with ClaDI `>=2.1.0`:
```bash
npm install -D @elsikora/cladi @elsikora/cladi-testing
```
```typescript
import { createModule, createToken } from "@elsikora/cladi";
import { createTestingContainer, mockProvider, overrideProvider, resetTestingContainer } from "@elsikora/cladi-testing";
const UserRepoToken = createToken<{ findNameById(id: string): string | undefined }>("UserRepo");
const UserServiceToken = createToken<{ readName(id: string): string }>("UserService");
const appModule = createModule({
exports: [UserServiceToken],
providers: [
mockProvider(UserRepoToken, { findNameById: () => "Alice" }),
{
deps: [UserRepoToken],
provide: UserServiceToken,
useFactory: (repository) => ({
readName: (id: string): string => repository.findNameById(id) ?? "unknown",
}),
},
],
});
const container = createTestingContainer({ modules: [appModule], shouldValidateOnCreate: true });
await overrideProvider(container, mockProvider(UserRepoToken, { findNameById: () => "Bob" }));
await resetTestingContainer(container);
```
```typescript
import { createDIContainer, createToken, EDependencyLifecycle } from "@elsikora/cladi";
// 1. Define typed tokens
const ConfigToken = createToken<{ apiUrl: string }>("Config");
const HttpClientToken = createToken<{ get(path: string): Promise<unknown> }>("HttpClient");
// 2. Create the container
const container = createDIContainer({ scopeName: "root" });
// 3. Register providers
container.register({
provide: ConfigToken,
useValue: { apiUrl: "https://api.example.com" },
});
container.register({
provide: HttpClientToken,
lifecycle: EDependencyLifecycle.SCOPED,
deps: [ConfigToken],
useFactory: (config) => ({
get: async (path: string) => fetch(`${config.apiUrl}${path}`).then((r) => r.json()),
}),
});
// 4. Validate at startup
container.validate();
// 5. Resolve dependencies
const http = container.resolve(HttpClientToken);
```
---
```typescript
async function handleRequest(requestId: string) {
const scope = container.createScope("request");
try {
// Register request-specific context
const RequestIdToken = createToken<string>("RequestId");
scope.register({ provide: RequestIdToken, useValue: requestId });
// Resolve scoped services
const http = scope.resolve(HttpClientToken);
return await http.get("/data");
} finally {
// Always dispose the scope
await scope.dispose();
}
}
```
---
```typescript
import { createLazyProvider, createToken } from "@elsikora/cladi";
const DbToken = createToken<Database>("Database");
const LazyDbToken = createToken<() => Promise<Database>>("LazyDatabase");
container.register({
provide: DbToken,
lifecycle: EDependencyLifecycle.SINGLETON,
useFactory: async () => await connectToDatabase(),
});
// Lazy provider defers resolution until invoked
container.register(createLazyProvider(LazyDbToken, DbToken));
// Database connection is NOT created yet
const getDb = container.resolve(LazyDbToken);
// Now it resolves
const db = await getDb();
```
---
```typescript
const MiddlewareToken = createToken<Middleware>("Middleware");
container.register({
provide: MiddlewareToken,
isMultiBinding: true,
useFactory: () => createAuthMiddleware(),
});
container.register({
provide: MiddlewareToken,
isMultiBinding: true,
useFactory: () => createLoggingMiddleware(),
});
// Resolve all implementations
const middlewares = container.resolveAll(MiddlewareToken);
// => [authMiddleware, loggingMiddleware]
```
---
```typescript
import { createModule, composeModules, createDIContainer } from "@elsikora/cladi";
const databaseModule = createModule({
name: "database",
exports: [DbConnectionToken],
providers: [
{ provide: DbConfigToken, useValue: { host: "localhost" } },
{
provide: DbConnectionToken,
deps: [DbConfigToken],
lifecycle: EDependencyLifecycle.SINGLETON,
useFactory: (config) => createConnection(config),
},
],
});
const appModule = createModule({
name: "app",
imports: [databaseModule],
providers: [
{
provide: UserRepoToken,
deps: [DbConnectionToken],
useFactory: (db) => new UserRepository(db),
},
],
});
const container = createDIContainer();
composeModules(container, [appModule]);
```
---
```typescript
import { Inject, Injectable, Module, composeDecoratedModules, createDIContainer, createToken, EDependencyLifecycle } from "@elsikora/cladi";
const ConfigToken = createToken<{ baseUrl: string }>("Config");
const ApiToken = createToken<{ ping(): string }>("Api");
@Injectable({ token: ApiToken, lifecycle: EDependencyLifecycle.SINGLETON })
class ApiService {
constructor(@Inject(ConfigToken) private readonly config: { baseUrl: string }) {}
public ping(): string {
return `pong:${this.config.baseUrl}`;
}
}
@Module({
exports: [ApiToken],
providers: [{ provide: ConfigToken, useValue: { baseUrl: "https://api.example.com" } }, ApiService],
})
class AppModule {}
const container = createDIContainer();
composeDecoratedModules(container, [AppModule]);
```
Use `createModuleFromDecorator()` directly when you need manual conversion to plain `IDIModule` for custom orchestration.
---
```typescript
container.register({
provide: CacheToken,
lifecycle: EDependencyLifecycle.SINGLETON,
useFactory: () => new RedisCache(),
onInit: (cache) => cache.warmup(),
afterResolve: (cache) => cache.recordUsage(),
onDispose: async (cache) => await cache.disconnect(),
});
```
---
```typescript
// Explain resolution path for a token
const explanation = container.explain(HttpClientToken);
console.log(explanation);
// { isFound: true, lifecycle: 'scoped', providerType: 'factory',
// dependencies: ['Symbol(Config)'], lookupPath: ['root'], ... }
// Full container snapshot
const snapshot = container.snapshot();
console.log(snapshot);
// { scopeId: 'root', providerCount: 3, singletonCacheSize: 1,
// tokens: ['Symbol(Config)', 'Symbol(HttpClient)', ...], ... }
```
---
```typescript
// Eagerly initialize singleton providers (and run onInit hooks)
await container.bootstrap();
// Optionally bootstrap specific tokens only
await container.bootstrap([DbToken, CacheToken]);
// Freeze registration surface for runtime safety
container.lock();
console.log(container.isLocked); // true
// Export graph for observability or visualization
const graph = container.exportGraph();
console.log(graph.nodes);
console.log(graph.edges);
```
| Goal | API | Notes |
| --------------------------------- | -------------------------------------------------------- | --------------------------------------------- | -------------------------------- |
| Resolve one dependency (sync) | `resolve(token)` | Throws if provider path is async |
| Resolve one dependency (async) | `resolveAsync(token)` | Works with async factories and async hooks |
| Resolve many implementations | `resolveAll(token)` / `resolveAllAsync(token)` | For multi-binding registrations |
| Resolve optional dependency | `resolveOptional(token)` / `resolveOptionalAsync(token)` | Returns `undefined` if not found |
| Create isolated runtime boundary | `createScope(name?)` | Use for request/job/command context |
| Register providers | `register(provider | provider[])` | Supports all provider strategies |
| Validate graph at startup | `validate()` | Checks missing deps, cycles, and policy rules |
| Pre-warm singleton graph | `bootstrap(tokens?)` | Eager resolve + `onInit` execution |
| Lock runtime registration surface | `lock()` / `isLocked` | Prevents `register` / `unregister` calls |
| Inspect runtime path and cache | `explain(token)` / `snapshot()` | Debug lookup path and cache state |
| Export dependency nodes and edges | `exportGraph()` | Structured graph for diagnostics and tooling |
| Compose plain modules | `composeModules(container, modules)` | `IDIModule` import/export contract |
| Compose decorated module classes | `composeDecoratedModules(container, modules)` | Accepts `@Module` classes and plain modules |
| Release resources | `scope.dispose()` / `container.dispose()` | Always call in `finally` or shutdown flow |
## ๐งฑ Production Bootstrap
Use explicit policies and graceful shutdown in your root composition setup:
```typescript
import { createDIContainer, EDiContainerCaptiveDependencyPolicy, EDiContainerDuplicateProviderPolicy, type IResolveInterceptor } from "@elsikora/cladi";
const metricsInterceptor: IResolveInterceptor = {
onError: ({ tokenDescription, error }) => {
console.error("resolve error", tokenDescription, error.message);
},
onStart: ({ tokenDescription }) => {
console.debug("resolve start", tokenDescription);
},
onSuccess: ({ tokenDescription }) => {
console.debug("resolve success", tokenDescription);
},
};
const container = createDIContainer({
captiveDependencyPolicy: EDiContainerCaptiveDependencyPolicy.ERROR,
duplicateProviderPolicy: EDiContainerDuplicateProviderPolicy.ERROR,
resolveInterceptors: [metricsInterceptor],
scopeName: "root",
});
// register providers...
container.validate();
const shutdown = async (): Promise<void> => {
await container.dispose();
};
process.once("SIGINT", () => {
void shutdown();
});
process.once("SIGTERM", () => {
void shutdown();
});
```
- Using `resolve()` for async providers or async hooks. Use `resolveAsync()` for those tokens.
- Registering multi-binding providers and calling `resolve()` instead of `resolveAll()` / `resolveAllAsync()`.
- Skipping `validate()` at startup and finding graph issues only at runtime.
- Forgetting `scope.dispose()` in request/job flows, causing leaked scoped resources.
- Treating optional dependencies as required. Use `resolveOptional()` for truly optional contracts.
<details>
<summary>Click to expand</summary>
| Task / Feature | Status |
| ------------------------------------------------- | -------------- |
| Core DI container with typed tokens | โ
Done |
| Singleton, Scoped, and Transient lifecycles | โ
Done |
| Async resolution with deduplication | โ
Done |
| Lazy provider strategy (`useLazy`) | โ
Done |
| Multi-binding support (`resolveAll`) | โ
Done |
| Module composition system (`composeModules`) | โ
Done |
| Lifecycle hooks (onInit, afterResolve, onDispose) | โ
Done |
| Circular dependency detection and validation | โ
Done |
| Captive dependency policy (warn/error/disabled) | โ
Done |
| Resolve interceptors for observability | โ
Done |
| Decorator-based DI (`@Injectable`, `@Inject`) | โ
Done |
| Safe deep clone utility | โ
Done |
| Comprehensive documentation with MDX | โ
Done |
| Hierarchical scope disposal with async drain | โ
Done |
| Tagged/conditional provider resolution | ๐ง In Progress |
| Provider middleware pipeline | ๐ง In Progress |
| Scope-level event emitter for lifecycle events | ๐ง In Progress |
| Performance benchmarking suite | ๐ง In Progress |
</details>
## โ FAQ
<details>
<summary>Click to expand</summary>
### Does ClaDI require `reflect-metadata` or experimental decorators?
No. ClaDI works entirely without reflection metadata. Optional decorators (`@Injectable`, `@Inject`, `@Module`, `@OnInit`, `@AfterResolve`, `@OnDispose`) store metadata directly on class constructors/prototypes using symbol keys โ no `reflect-metadata` polyfill needed. You can also skip decorators entirely and use plain providers, `createAutowireProvider()`, and `createModule()`.
### How does ClaDI compare to InversifyJS or tsyringe?
ClaDI takes a fundamentally different approach: **explicit composition roots** over implicit decorator-driven wiring. There's no global container, no automatic class scanning, and no hidden metadata. Every dependency relationship is visible at the registration site. This makes the DI graph auditable, testable, and refactoring-friendly.
### Can I use ClaDI in the browser?
ClaDI ships ESM and CJS bundles and is designed to be runtime-agnostic. It is tested in Node.js. Bun, Deno, and browser usage is generally viable when your environment supports standard JavaScript runtime features used by your providers.
### How do I handle async initialization (e.g., database connections)?
Use `useFactory` with an async function and resolve with `resolveAsync()`:
```typescript
container.register({
provide: DbToken,
lifecycle: EDependencyLifecycle.SINGLETON,
useFactory: async () => await createDatabasePool(),
onDispose: async (pool) => await pool.end(),
});
const db = await container.resolveAsync(DbToken);
```
Concurrent `resolveAsync()` calls for the same singleton are automatically deduplicated โ the factory runs exactly once.
Scoped and singleton instances with `onDispose` hooks or `dispose()` / `close()` methods will not be cleaned up, potentially causing resource leaks. Always wrap scope usage in `try/finally`:
```typescript
const scope = container.createScope("request");
try {
// ... use scope
} finally {
await scope.dispose();
}
```
Absolutely. Child scopes can register local overrides that shadow parent registrations:
```typescript
const testScope = container.createScope("test");
testScope.register({ provide: DbToken, useValue: mockDatabase });
const service = testScope.resolve(ServiceToken); // uses mockDatabase
```
No. The module system (`createModule` / `composeModules`) is entirely optional. You can register all providers directly on the container. Modules are useful for organizing large applications into bounded contexts with explicit export boundaries.
</details>
## ๐ License
This project is licensed under **MIT**.
## ๐ Acknowledgments
- Built and maintained by [ElsiKora](https://github.com/ElsiKora)
- Inspired by the dependency injection patterns from Angular, NestJS, and InversifyJS โ adapted for explicit composition-root workflows
- Thanks to all [contributors](https://github.com/ElsiKora/ClaDI/graphs/contributors) who helped shape the API and test suite
- Documentation powered by [Nextra](https://nextra.site/) with MDX
- Release automation via [semantic-release](https://github.com/semantic-release/semantic-release)
- Code quality enforced by [Qodana](https://www.jetbrains.com/qodana/) and [Snyk](https://snyk.io/)
---
<p align="center">
<a href="#top">Back to Top</a>
</p>