@codefast/di 0.3.13-canary.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +59 -0
- package/LICENSE +21 -0
- package/README.md +572 -0
- package/dist/binding-select.d.mts +22 -0
- package/dist/binding-select.mjs +50 -0
- package/dist/binding.d.mts +219 -0
- package/dist/binding.mjs +240 -0
- package/dist/constraints.d.mts +18 -0
- package/dist/constraints.mjs +24 -0
- package/dist/container.d.mts +82 -0
- package/dist/container.mjs +406 -0
- package/dist/decorators/inject.d.mts +24 -0
- package/dist/decorators/inject.mjs +69 -0
- package/dist/decorators/injectable.d.mts +40 -0
- package/dist/decorators/injectable.mjs +62 -0
- package/dist/decorators/lifecycle-decorators.d.mts +13 -0
- package/dist/decorators/lifecycle-decorators.mjs +34 -0
- package/dist/dependency-graph.d.mts +35 -0
- package/dist/dependency-graph.mjs +126 -0
- package/dist/environment.d.mts +14 -0
- package/dist/environment.mjs +20 -0
- package/dist/errors.d.mts +100 -0
- package/dist/errors.mjs +152 -0
- package/dist/index.d.mts +10 -0
- package/dist/index.mjs +8 -0
- package/dist/inspector.d.mts +76 -0
- package/dist/inspector.mjs +247 -0
- package/dist/lifecycle.d.mts +34 -0
- package/dist/lifecycle.mjs +83 -0
- package/dist/metadata/metadata-keys.d.mts +17 -0
- package/dist/metadata/metadata-keys.mjs +19 -0
- package/dist/metadata/metadata-types.d.mts +55 -0
- package/dist/metadata/metadata-types.mjs +1 -0
- package/dist/metadata/param-registry.d.mts +16 -0
- package/dist/metadata/param-registry.mjs +25 -0
- package/dist/metadata/symbol-metadata-reader.d.mts +15 -0
- package/dist/metadata/symbol-metadata-reader.mjs +32 -0
- package/dist/module.d.mts +60 -0
- package/dist/module.mjs +57 -0
- package/dist/registry.d.mts +38 -0
- package/dist/registry.mjs +65 -0
- package/dist/resolver.d.mts +102 -0
- package/dist/resolver.mjs +361 -0
- package/dist/scope-validation.d.mts +20 -0
- package/dist/scope-validation.mjs +34 -0
- package/dist/scope.d.mts +80 -0
- package/dist/scope.mjs +185 -0
- package/dist/token.d.mts +20 -0
- package/dist/token.mjs +9 -0
- package/package.json +157 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# @codefast/di
|
|
2
|
+
|
|
3
|
+
## 0.3.13-canary.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [`93b7399`](https://github.com/codefastlabs/codefast/commit/93b7399737eb2220866338da31023f95665021a0) Thanks [@thevuong](https://github.com/thevuong)! - feat(cli): enhance CLI structure and update dependencies
|
|
8
|
+
|
|
9
|
+
- [`78d25cd`](https://github.com/codefastlabs/codefast/commit/78d25cd2bacd37f623ceeceb211375d2daf93541) Thanks [@thevuong](https://github.com/thevuong)! - docs(di): update README.md for clarity and structure
|
|
10
|
+
|
|
11
|
+
- [`0542867`](https://github.com/codefastlabs/codefast/commit/054286713c242d3aa75eb7b6ad259693e266faed) Thanks [@thevuong](https://github.com/thevuong)! - feat(cli): add architecture graph and refine dependency injection
|
|
12
|
+
|
|
13
|
+
- [`c458ff9`](https://github.com/codefastlabs/codefast/commit/c458ff9806426dd664ebfbc71dc387973f2aa2ef) Thanks [@thevuong](https://github.com/thevuong)! - test(di): add comprehensive unit tests for binding-select, binding, constraints, and dependency-graph modules
|
|
14
|
+
|
|
15
|
+
- [`3d25484`](https://github.com/codefastlabs/codefast/commit/3d254841f2aad4e00d9e18da62369c659bdd88d6) Thanks [@thevuong](https://github.com/thevuong)! - refactor(di): enhance dependency graph generation and binding builder types
|
|
16
|
+
|
|
17
|
+
- [`c5727bb`](https://github.com/codefastlabs/codefast/commit/c5727bb3d739bb68a829a550471cfb9fb6152da8) Thanks [@thevuong](https://github.com/thevuong)! - feat(di): add unit tests for DefaultContainer and ScopeManager
|
|
18
|
+
|
|
19
|
+
- [`2ba60d2`](https://github.com/codefastlabs/codefast/commit/2ba60d256c93b2590984f3992d35b3d71c40d472) Thanks [@thevuong](https://github.com/thevuong)! - chore(knip): add knip configuration for dependency management
|
|
20
|
+
|
|
21
|
+
- [`45426cf`](https://github.com/codefastlabs/codefast/commit/45426cf610c09764d2a0fbae394f8b997d0a3312) Thanks [@thevuong](https://github.com/thevuong)! - refactor(di): enhance type safety in dependency resolution
|
|
22
|
+
|
|
23
|
+
- [`142cfbc`](https://github.com/codefastlabs/codefast/commit/142cfbc6a8e882824eacc14cbe84877a1c7c7d23) Thanks [@thevuong](https://github.com/thevuong)! - docs(di): expand examples in README.md for clarity and practical guidance
|
|
24
|
+
|
|
25
|
+
- [`733dafa`](https://github.com/codefastlabs/codefast/commit/733dafadff88e32a82f00d44599efbb0771b7b6a) Thanks [@thevuong](https://github.com/thevuong)! - refactor(cli): reorganize imports for clarity and consistency
|
|
26
|
+
|
|
27
|
+
- [`b8a9cca`](https://github.com/codefastlabs/codefast/commit/b8a9cca27306e0ce68bee5b47b61a49e568296ec) Thanks [@thevuong](https://github.com/thevuong)! - feat(di): introduce dependency injection package with core functionality
|
|
28
|
+
|
|
29
|
+
- [`5c5c103`](https://github.com/codefastlabs/codefast/commit/5c5c10374fdfbd480047111fc0e6b90c027f7c8d) Thanks [@thevuong](https://github.com/thevuong)! - refactor(di): enhance documentation and type annotations for clarity
|
|
30
|
+
|
|
31
|
+
- [`4248d75`](https://github.com/codefastlabs/codefast/commit/4248d75f2d547247dde937c322c2ed48d484f9e0) Thanks [@thevuong](https://github.com/thevuong)! - chore(tests): streamline test coverage commands and configurations
|
|
32
|
+
|
|
33
|
+
- [`fa53c0b`](https://github.com/codefastlabs/codefast/commit/fa53c0b361200eadf6238d633c8b181fd165acec) Thanks [@thevuong](https://github.com/thevuong)! - test(cli): add integration tests for arrange, mirror, and tag modules
|
|
34
|
+
|
|
35
|
+
- [`77c7b9c`](https://github.com/codefastlabs/codefast/commit/77c7b9c35960e7a3038185c7b9dfc2736e2868a9) Thanks [@thevuong](https://github.com/thevuong)! - refactor(di): rename DefaultContainer to Container and enhance validation logic
|
|
36
|
+
|
|
37
|
+
- [`cf7055c`](https://github.com/codefastlabs/codefast/commit/cf7055c916ea7c630a41c13a0398a00bbaa12fd5) Thanks [@thevuong](https://github.com/thevuong)! - refactor(di): improve variable naming for clarity and consistency
|
|
38
|
+
|
|
39
|
+
- [`2dd4641`](https://github.com/codefastlabs/codefast/commit/2dd464127708066e900deb42d88d6d38bd7849b2) Thanks [@thevuong](https://github.com/thevuong)! - refactor(di): replace DiError with InternalError for internal consistency
|
|
40
|
+
|
|
41
|
+
- [`453bc2e`](https://github.com/codefastlabs/codefast/commit/453bc2ec63f65fe03cb77bdcc36ccbf8ef8cb3d0) Thanks [@thevuong](https://github.com/thevuong)! - refactor(di): enhance binding builder and module API for clarity and consistency
|
|
42
|
+
|
|
43
|
+
- [`a626eb5`](https://github.com/codefastlabs/codefast/commit/a626eb58a56a73aab2e5cf79fba92e68d4080274) Thanks [@thevuong](https://github.com/thevuong)! - test(di): add comprehensive unit tests for Container and Module functionalities
|
|
44
|
+
|
|
45
|
+
- [`2340231`](https://github.com/codefastlabs/codefast/commit/23402311084871d238ec50aa23061afd4b14e61e) Thanks [@thevuong](https://github.com/thevuong)! - refactor(imports): standardize import paths across applications and benchmarks
|
|
46
|
+
|
|
47
|
+
- [`e0f6065`](https://github.com/codefastlabs/codefast/commit/e0f6065323255e2c1aa2c1e8f9c28a4b4c6e0ac2) Thanks [@thevuong](https://github.com/thevuong)! - refactor(di): standardize tag handling to use string keys only
|
|
48
|
+
|
|
49
|
+
- [`35329d5`](https://github.com/codefastlabs/codefast/commit/35329d5f17682542e3ef0907d4936fa513346a72) Thanks [@thevuong](https://github.com/thevuong)! - feat(tsconfig): enforce module detection in TypeScript configuration
|
|
50
|
+
|
|
51
|
+
- [`2fbf6a0`](https://github.com/codefastlabs/codefast/commit/2fbf6a07da06fd9383ab7c97fb69640a472fda19) Thanks [@thevuong](https://github.com/thevuong)! - refactor(di): improve error handling and serialization in dependency resolution
|
|
52
|
+
|
|
53
|
+
- [`2c156bb`](https://github.com/codefastlabs/codefast/commit/2c156bbef480aa1c7f312289f25c7dd19bb971d1) Thanks [@thevuong](https://github.com/thevuong)! - refactor(di): simplify binding API and enhance type definitions
|
|
54
|
+
|
|
55
|
+
- [`8120fd0`](https://github.com/codefastlabs/codefast/commit/8120fd034761f456e0706439172a7f50b4abfe1e) Thanks [@thevuong](https://github.com/thevuong)! - chore(di): update package.json and tsconfig.build.json for improved module resolution and type definitions
|
|
56
|
+
|
|
57
|
+
- [`1a66f91`](https://github.com/codefastlabs/codefast/commit/1a66f911632ed23bd9a6fb3f178909ef23e44ea6) Thanks [@thevuong](https://github.com/thevuong)! - refactor(di): enhance index file with additional exports and type definitions
|
|
58
|
+
|
|
59
|
+
- [`dea6bbc`](https://github.com/codefastlabs/codefast/commit/dea6bbcebaaecc09808c42b3f93e0fbf2296eb5b) Thanks [@thevuong](https://github.com/thevuong)! - refactor(di, theme): streamline imports and remove unused files
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 CodeFast Labs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,572 @@
|
|
|
1
|
+
# @codefast/di
|
|
2
|
+
|
|
3
|
+
Lightweight, type-safe dependency injection for TypeScript — powered by TC39 Stage 3 decorators and zero runtime reflection.
|
|
4
|
+
|
|
5
|
+
[](https://github.com/codefastlabs/codefast/actions/workflows/release.yml)
|
|
6
|
+
[](https://www.npmjs.com/package/@codefast/di)
|
|
7
|
+
[](https://www.npmjs.com/package/@codefast/di)
|
|
8
|
+
[](https://opensource.org/licenses/MIT)
|
|
9
|
+
|
|
10
|
+
## Table of Contents
|
|
11
|
+
|
|
12
|
+
- [Overview](#overview)
|
|
13
|
+
- [Installation](#installation)
|
|
14
|
+
- [Requirements](#requirements)
|
|
15
|
+
- [Core Concepts](#core-concepts)
|
|
16
|
+
- [Quick Start](#quick-start)
|
|
17
|
+
- [Tokens](#tokens)
|
|
18
|
+
- [Binding API](#binding-api)
|
|
19
|
+
- [Binding strategies](#binding-strategies)
|
|
20
|
+
- [Scopes](#scopes)
|
|
21
|
+
- [Lifecycle hooks](#lifecycle-hooks)
|
|
22
|
+
- [Constraints](#constraints)
|
|
23
|
+
- [Decorators](#decorators)
|
|
24
|
+
- [Container API](#container-api)
|
|
25
|
+
- [Resolution](#resolution)
|
|
26
|
+
- [Async resolution](#async-resolution)
|
|
27
|
+
- [Child containers](#child-containers)
|
|
28
|
+
- [Validation](#validation)
|
|
29
|
+
- [Introspection](#introspection)
|
|
30
|
+
- [Disposable resources](#disposable-resources)
|
|
31
|
+
- [Modules](#modules)
|
|
32
|
+
- [Error handling](#error-handling)
|
|
33
|
+
- [Package exports](#package-exports)
|
|
34
|
+
- [Contributing](#contributing)
|
|
35
|
+
- [License](#license)
|
|
36
|
+
- [Changelog](#changelog)
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Overview
|
|
41
|
+
|
|
42
|
+
`@codefast/di` is a small **ESM-only** IoC container for TypeScript applications.
|
|
43
|
+
|
|
44
|
+
**Key features:**
|
|
45
|
+
|
|
46
|
+
- **Type-safe tokens** — `Token<Value>` ensures `resolve()` returns the exact type you registered; no accidental `get<WrongType>(…)` at compile time.
|
|
47
|
+
- **TC39 Stage 3 decorators** — `@injectable`, `@inject`, `@optional`, `@singleton`, `@scoped` write metadata via `Symbol.metadata`. No `reflect-metadata`, no `experimentalDecorators`.
|
|
48
|
+
- **Fluent binding API** — constants, classes, sync and async factories, scopes, tags, named hints, constraint predicates, and lifecycle hooks.
|
|
49
|
+
- **Module system** — organize registrations into reusable `Module` / `AsyncModule` units.
|
|
50
|
+
- **Scope safety** — detects singleton → transient/scoped captive dependencies in development.
|
|
51
|
+
- **Async-first** — parallel inflight deduplication for async singleton initialization.
|
|
52
|
+
- **Tree-shakeable subpaths** — import only the surface you need.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Installation
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# pnpm
|
|
60
|
+
pnpm add @codefast/di
|
|
61
|
+
|
|
62
|
+
# npm
|
|
63
|
+
npm install @codefast/di
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Requirements
|
|
69
|
+
|
|
70
|
+
| Dependency | Version |
|
|
71
|
+
| ---------- | ----------------------------- |
|
|
72
|
+
| Node.js | `>= 22.0.0` |
|
|
73
|
+
| TypeScript | `>= 5.2` (Stage 3 decorators) |
|
|
74
|
+
|
|
75
|
+
Enable native decorators in `tsconfig.json` (do **not** enable `experimentalDecorators`):
|
|
76
|
+
|
|
77
|
+
```json
|
|
78
|
+
{
|
|
79
|
+
"compilerOptions": {
|
|
80
|
+
"target": "ES2022",
|
|
81
|
+
"lib": ["ES2022"],
|
|
82
|
+
"moduleResolution": "bundler"
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Core Concepts
|
|
90
|
+
|
|
91
|
+
| Concept | Description |
|
|
92
|
+
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
93
|
+
| **Token** | Branded key that pairs a name string with a TypeScript type. Tokens use reference equality — pass the same object to `bind` and `inject`. |
|
|
94
|
+
| **Binding** | Associates a token (or constructor) with a value strategy (constant, class, factory, alias) and optional scope, constraints, and lifecycle hooks. |
|
|
95
|
+
| **Container** | Stores bindings in a `BindingRegistry` and resolves them on demand via a `DependencyResolver`. |
|
|
96
|
+
| **Module** | An immutable bundle of bindings. Loaded into any container and unloaded cleanly. |
|
|
97
|
+
| **Scope** | Controls instance lifetime: `singleton` (one per container), `scoped` (one per child), or `transient` (new each time). |
|
|
98
|
+
| **Decorator metadata** | `@injectable([…])` writes constructor dependency descriptors into `Symbol.metadata`, which the container reads at resolution time. |
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Quick Start
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
import { Container, inject, injectable, token } from "@codefast/di";
|
|
106
|
+
|
|
107
|
+
// 1. Define an interface and a token for it
|
|
108
|
+
interface Logger {
|
|
109
|
+
log(message: string): void;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const LoggerToken = token<Logger>("Logger");
|
|
113
|
+
|
|
114
|
+
// 2. Implement and annotate the class
|
|
115
|
+
@injectable([inject(LoggerToken)])
|
|
116
|
+
class AppService {
|
|
117
|
+
constructor(private readonly logger: Logger) {}
|
|
118
|
+
|
|
119
|
+
run(): void {
|
|
120
|
+
this.logger.log("App started");
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// 3. Create a container and register bindings
|
|
125
|
+
const container = Container.create();
|
|
126
|
+
|
|
127
|
+
container.bind(LoggerToken).toConstantValue({ log: console.log });
|
|
128
|
+
container.bind(AppService).toSelf().singleton();
|
|
129
|
+
|
|
130
|
+
// 4. Resolve and use
|
|
131
|
+
const app = container.resolve(AppService);
|
|
132
|
+
app.run(); // "App started"
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Tokens
|
|
138
|
+
|
|
139
|
+
A `Token<Value>` is a branded object that acts as a type-safe registry key.
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
import { token } from "@codefast/di";
|
|
143
|
+
|
|
144
|
+
const DbToken = token<Database>("Database");
|
|
145
|
+
const CacheToken = token<Cache>("Cache");
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
The type parameter flows through the binding and resolution chain — `container.resolve(DbToken)` returns `Database`, not `unknown`.
|
|
149
|
+
|
|
150
|
+
> **Token identity** — tokens use reference equality. Always import and reuse the same `const` value for both `bind()` and `inject()`. Creating two tokens with the same name produces two distinct keys.
|
|
151
|
+
|
|
152
|
+
You can also use a constructor directly as a key:
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
container.bind(MyService).toSelf();
|
|
156
|
+
container.resolve(MyService); // returns MyService
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## Binding API
|
|
162
|
+
|
|
163
|
+
### Binding strategies
|
|
164
|
+
|
|
165
|
+
Start a binding with `container.bind(key)` and chain a strategy method:
|
|
166
|
+
|
|
167
|
+
| Method | Description |
|
|
168
|
+
| ---------------------------- | ---------------------------------------------------------------------- |
|
|
169
|
+
| `.toConstantValue(value)` | Bind to a fixed value. Always treated as singleton. |
|
|
170
|
+
| `.toSelf()` | Bind a constructor to itself. Reads `@injectable()` metadata. |
|
|
171
|
+
| `.to(Constructor)` | Bind a token to a class constructor. |
|
|
172
|
+
| `.toDynamic(factory)` | Bind to a synchronous factory `(ctx: ResolutionContext) => Value`. |
|
|
173
|
+
| `.toDynamicAsync(factory)` | Bind to an async factory `(ctx: ResolutionContext) => Promise<Value>`. |
|
|
174
|
+
| `.toResolved(factory, deps)` | Bind to a factory with an explicit typed dependency array. |
|
|
175
|
+
| `.toAlias(targetToken)` | Redirect this token to another token. |
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
// Constant
|
|
179
|
+
container.bind(AppConfigToken).toConstantValue({ port: 3000 });
|
|
180
|
+
|
|
181
|
+
// Class
|
|
182
|
+
container.bind(UserRepository).toSelf().singleton();
|
|
183
|
+
container.bind(UserServiceToken).to(UserService).transient();
|
|
184
|
+
|
|
185
|
+
// Factory — receive resolution context to resolve nested deps
|
|
186
|
+
container.bind(DbToken).toDynamic((ctx) => {
|
|
187
|
+
const config = ctx.resolve(AppConfigToken);
|
|
188
|
+
return new Database(config.dbUrl);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// Async factory
|
|
192
|
+
container.bind(DbToken).toDynamicAsync(async (ctx) => {
|
|
193
|
+
const config = ctx.resolve(AppConfigToken);
|
|
194
|
+
const db = new Database(config.dbUrl);
|
|
195
|
+
await db.connect();
|
|
196
|
+
return db;
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
// Alias
|
|
200
|
+
container.bind(LegacyServiceToken).toAlias(NewServiceToken);
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Scopes
|
|
204
|
+
|
|
205
|
+
After setting the strategy, chain a scope method:
|
|
206
|
+
|
|
207
|
+
| Method | Lifetime |
|
|
208
|
+
| -------------- | --------------------------------------------------------------------------- |
|
|
209
|
+
| `.singleton()` | One instance per container. Created once, reused forever. |
|
|
210
|
+
| `.transient()` | New instance on every `resolve()`. Default for most bindings. |
|
|
211
|
+
| `.scoped()` | One instance per child container scope. Useful for request-scoped services. |
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
container.bind(DatabaseToken).toDynamic(createDb).singleton();
|
|
215
|
+
container.bind(RequestContextToken).toSelf().scoped();
|
|
216
|
+
container.bind(QueryBuilderToken).toSelf().transient();
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Lifecycle hooks
|
|
220
|
+
|
|
221
|
+
Attach activation and deactivation callbacks to any binding:
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
container
|
|
225
|
+
.bind(DbToken)
|
|
226
|
+
.toDynamicAsync(async (ctx) => new Database(ctx.resolve(ConfigToken)))
|
|
227
|
+
.singleton()
|
|
228
|
+
.onActivation(async (instance, ctx) => {
|
|
229
|
+
await instance.connect();
|
|
230
|
+
return instance; // must return the instance
|
|
231
|
+
})
|
|
232
|
+
.onDeactivation(async (instance) => {
|
|
233
|
+
await instance.disconnect();
|
|
234
|
+
});
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
`onActivation` runs after the instance is created. It must return the (optionally modified) instance.
|
|
238
|
+
`onDeactivation` runs when `container.dispose()` is called.
|
|
239
|
+
|
|
240
|
+
### Constraints
|
|
241
|
+
|
|
242
|
+
Constraints let multiple bindings share the same token and select one based on resolution context.
|
|
243
|
+
|
|
244
|
+
**Named bindings** — disambiguate with a string name:
|
|
245
|
+
|
|
246
|
+
```typescript
|
|
247
|
+
container.bind(LoggerToken).toConstantValue(fileLogger).whenNamed("file");
|
|
248
|
+
container.bind(LoggerToken).toConstantValue(consoleLogger).whenNamed("console");
|
|
249
|
+
|
|
250
|
+
// Resolve by name
|
|
251
|
+
container.resolve(LoggerToken, { name: "file" });
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
**Tagged bindings** — disambiguate with a tag + value pair:
|
|
255
|
+
|
|
256
|
+
```typescript
|
|
257
|
+
container.bind(StorageToken).to(S3Storage).whenTagged("provider", "s3");
|
|
258
|
+
container.bind(StorageToken).to(LocalStorage).whenTagged("provider", "local");
|
|
259
|
+
|
|
260
|
+
container.resolve(StorageToken, { tag: "provider", tagValue: "s3" });
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
**Custom predicate** — inspect the full resolution context:
|
|
264
|
+
|
|
265
|
+
```typescript
|
|
266
|
+
import { whenParentIs } from "@codefast/di";
|
|
267
|
+
|
|
268
|
+
container.bind(LoggerToken).toConstantValue(verboseLogger).when(whenParentIs(DiagnosticsService));
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
Built-in predicate helpers:
|
|
272
|
+
|
|
273
|
+
| Helper | Description |
|
|
274
|
+
| ------------------------------ | ------------------------------------------------------------------ |
|
|
275
|
+
| `whenParentIs(key)` | Matches when the direct parent binding is for the given key. |
|
|
276
|
+
| `whenAnyAncestorIs(key)` | Matches when any ancestor binding is for the given key. |
|
|
277
|
+
| `whenTargetTagged(tag, value)` | Matches when the immediate parent was injected with the given tag. |
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
## Decorators
|
|
282
|
+
|
|
283
|
+
Decorators use TC39 Stage 3 syntax and write metadata to `Symbol.metadata`.
|
|
284
|
+
|
|
285
|
+
### `@injectable(deps)`
|
|
286
|
+
|
|
287
|
+
Registers constructor dependency metadata. Every dependency must be listed.
|
|
288
|
+
|
|
289
|
+
```typescript
|
|
290
|
+
import { inject, injectable, optional, token } from "@codefast/di";
|
|
291
|
+
|
|
292
|
+
const ConfigToken = token<AppConfig>("AppConfig");
|
|
293
|
+
const CacheToken = token<Cache>("Cache");
|
|
294
|
+
|
|
295
|
+
@injectable([inject(ConfigToken), optional(CacheToken)])
|
|
296
|
+
class AppService {
|
|
297
|
+
constructor(
|
|
298
|
+
private readonly config: AppConfig,
|
|
299
|
+
private readonly cache?: Cache,
|
|
300
|
+
) {}
|
|
301
|
+
}
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
- `inject(token, options?)` — required dependency. Throws `TokenNotBoundError` if absent.
|
|
305
|
+
- `optional(token, options?)` — optional dependency. Resolves to `undefined` if not bound.
|
|
306
|
+
|
|
307
|
+
Both accept `InjectOptions` for named or tagged resolution hints:
|
|
308
|
+
|
|
309
|
+
```typescript
|
|
310
|
+
inject(LoggerToken, { name: "console" });
|
|
311
|
+
inject(StorageToken, { tag: "provider", tagValue: "s3" });
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### `@singleton()` and `@scoped()`
|
|
315
|
+
|
|
316
|
+
Scope hints on the class itself. The container reads these when you call `.to(Constructor)` without chaining an explicit scope method.
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
@singleton()
|
|
320
|
+
@injectable([inject(DbToken)])
|
|
321
|
+
class UserRepository {
|
|
322
|
+
constructor(private readonly db: Database) {}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// No .singleton() needed — decorator provides the hint
|
|
326
|
+
container.bind(UserRepository).toSelf();
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
`@singleton()` and `@scoped()` cannot be combined on the same class.
|
|
330
|
+
|
|
331
|
+
---
|
|
332
|
+
|
|
333
|
+
## Container API
|
|
334
|
+
|
|
335
|
+
### Resolution
|
|
336
|
+
|
|
337
|
+
```typescript
|
|
338
|
+
const container = Container.create();
|
|
339
|
+
|
|
340
|
+
// Single binding
|
|
341
|
+
const service = container.resolve(ServiceToken);
|
|
342
|
+
|
|
343
|
+
// Named or tagged hint
|
|
344
|
+
const logger = container.resolve(LoggerToken, { name: "console" });
|
|
345
|
+
|
|
346
|
+
// All matching bindings
|
|
347
|
+
const handlers = container.resolveAll(HandlerToken);
|
|
348
|
+
|
|
349
|
+
// Optional — returns undefined if not bound
|
|
350
|
+
const cache = container.resolveOptional(CacheToken);
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
Check binding existence before resolving:
|
|
354
|
+
|
|
355
|
+
```typescript
|
|
356
|
+
if (container.has(CacheToken)) {
|
|
357
|
+
const cache = container.resolve(CacheToken);
|
|
358
|
+
}
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
### Async resolution
|
|
362
|
+
|
|
363
|
+
Use `resolveAsync` / `resolveAllAsync` / `resolveOptionalAsync` when any dependency in the chain uses `toDynamicAsync`:
|
|
364
|
+
|
|
365
|
+
```typescript
|
|
366
|
+
const db = await container.resolveAsync(DbToken);
|
|
367
|
+
const handlers = await container.resolveAllAsync(HandlerToken);
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
### Rebinding and unbinding
|
|
371
|
+
|
|
372
|
+
```typescript
|
|
373
|
+
// Replace all bindings for a token
|
|
374
|
+
container.rebind(LoggerToken).toConstantValue(testLogger);
|
|
375
|
+
|
|
376
|
+
// Remove bindings (sync)
|
|
377
|
+
container.unbind(CacheToken);
|
|
378
|
+
|
|
379
|
+
// Remove with deactivation hooks (async)
|
|
380
|
+
await container.unbindAsync(CacheToken);
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
### Child containers
|
|
384
|
+
|
|
385
|
+
Child containers share the parent's singleton cache but maintain an isolated scoped cache — ideal for per-request isolation.
|
|
386
|
+
|
|
387
|
+
```typescript
|
|
388
|
+
const requestContainer = container.createChild();
|
|
389
|
+
|
|
390
|
+
requestContainer.bind(RequestContextToken).toConstantValue(req).scoped();
|
|
391
|
+
|
|
392
|
+
const service = requestContainer.resolve(RequestScopedService);
|
|
393
|
+
|
|
394
|
+
// Clean up scoped instances
|
|
395
|
+
await requestContainer.dispose();
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
### Validation
|
|
399
|
+
|
|
400
|
+
`validate()` checks for captive dependency violations (a singleton depending on a transient or scoped binding). In development and test environments this runs automatically after `load()` and the first `resolve()`.
|
|
401
|
+
|
|
402
|
+
```typescript
|
|
403
|
+
container.validate(); // throws ScopeViolationError if invalid
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
### Introspection
|
|
407
|
+
|
|
408
|
+
```typescript
|
|
409
|
+
// Snapshot of all registered bindings
|
|
410
|
+
const snapshot = container.inspect();
|
|
411
|
+
|
|
412
|
+
// Graphviz DOT format for dependency graph visualization
|
|
413
|
+
const dot = container.generateDependencyGraphDot({ hideInternals: true });
|
|
414
|
+
|
|
415
|
+
// JSON format
|
|
416
|
+
const graph = container.generateDependencyGraphJson();
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
### Disposable resources
|
|
420
|
+
|
|
421
|
+
`Container` implements `AsyncDisposable`, enabling `await using` syntax:
|
|
422
|
+
|
|
423
|
+
```typescript
|
|
424
|
+
{
|
|
425
|
+
await using container = Container.create();
|
|
426
|
+
container.bind(DbToken).toDynamicAsync(connectDb).singleton().onDeactivation(disconnectDb);
|
|
427
|
+
|
|
428
|
+
const db = await container.resolveAsync(DbToken);
|
|
429
|
+
// ...
|
|
430
|
+
} // container.dispose() called automatically — runs all deactivation hooks
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
Or call explicitly:
|
|
434
|
+
|
|
435
|
+
```typescript
|
|
436
|
+
await container.dispose();
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
---
|
|
440
|
+
|
|
441
|
+
## Modules
|
|
442
|
+
|
|
443
|
+
Modules group related bindings into reusable, composable units.
|
|
444
|
+
|
|
445
|
+
### Synchronous modules
|
|
446
|
+
|
|
447
|
+
```typescript
|
|
448
|
+
import { Module } from "@codefast/di";
|
|
449
|
+
|
|
450
|
+
const InfraModule = Module.create("Infra", (api) => {
|
|
451
|
+
api.bind(LoggerToken).toConstantValue(console);
|
|
452
|
+
api.bind(ConfigToken).toConstantValue(loadConfig());
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
const AppModule = Module.create("App", (api) => {
|
|
456
|
+
api.import(InfraModule); // load dependency module
|
|
457
|
+
api.bind(UserRepository).toSelf().singleton();
|
|
458
|
+
api.bind(UserServiceToken).to(UserService).transient();
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
const container = Container.fromModules(AppModule);
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
### Async modules
|
|
465
|
+
|
|
466
|
+
```typescript
|
|
467
|
+
import { Module } from "@codefast/di";
|
|
468
|
+
|
|
469
|
+
const DbModule = Module.createAsync("Database", async (api) => {
|
|
470
|
+
const config = await fetchRemoteConfig();
|
|
471
|
+
api.bind(DbToken).toConstantValue(await Database.connect(config.dbUrl));
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
const container = await Container.fromModulesAsync(DbModule, AppModule);
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
`Module.createAsync` returns an `AsyncModule`. Use `Container.fromModulesAsync` (or `container.loadAsync`) to load async modules.
|
|
478
|
+
|
|
479
|
+
### Loading and unloading
|
|
480
|
+
|
|
481
|
+
```typescript
|
|
482
|
+
// Load into existing container
|
|
483
|
+
container.load(InfraModule, AppModule);
|
|
484
|
+
await container.loadAsync(DbModule);
|
|
485
|
+
|
|
486
|
+
// Remove and run deactivation hooks
|
|
487
|
+
container.unload(AppModule);
|
|
488
|
+
await container.unloadAsync(DbModule);
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
---
|
|
492
|
+
|
|
493
|
+
## Error handling
|
|
494
|
+
|
|
495
|
+
All errors extend `DiError` and expose a stable `code` property for `switch` / `instanceof` checks.
|
|
496
|
+
|
|
497
|
+
| Error class | `code` | When thrown |
|
|
498
|
+
| ------------------------- | ----------------------- | --------------------------------------------------- |
|
|
499
|
+
| `TokenNotBoundError` | `"TOKEN_NOT_BOUND"` | `resolve()` called for an unregistered token |
|
|
500
|
+
| `NoMatchingBindingError` | `"NO_MATCHING_BINDING"` | Name/tag hint matches no binding |
|
|
501
|
+
| `CircularDependencyError` | `"CIRCULAR_DEPENDENCY"` | Circular dependency detected during resolution |
|
|
502
|
+
| `MissingMetadataError` | `"MISSING_METADATA"` | Class binding lacks `@injectable()` metadata |
|
|
503
|
+
| `AsyncModuleLoadError` | `"ASYNC_MODULE_LOAD"` | `load()` (sync) called with an `AsyncModule` |
|
|
504
|
+
| `AsyncResolutionError` | `"ASYNC_RESOLUTION"` | Async dependency encountered during sync resolution |
|
|
505
|
+
| `ScopeViolationError` | `"SCOPE_VIOLATION"` | Singleton depends on transient/scoped binding |
|
|
506
|
+
|
|
507
|
+
```typescript
|
|
508
|
+
import { DiError, TokenNotBoundError, ScopeViolationError } from "@codefast/di";
|
|
509
|
+
|
|
510
|
+
try {
|
|
511
|
+
const service = container.resolve(ServiceToken);
|
|
512
|
+
} catch (error) {
|
|
513
|
+
if (error instanceof TokenNotBoundError) {
|
|
514
|
+
console.error(`Not registered: ${error.tokenName}`);
|
|
515
|
+
} else if (error instanceof DiError) {
|
|
516
|
+
console.error(`DI error [${error.code}]: ${error.message}`);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
---
|
|
522
|
+
|
|
523
|
+
## Package exports
|
|
524
|
+
|
|
525
|
+
The root entry `@codefast/di` re-exports the complete public API. Subpath exports are available for fine-grained imports and tree-shaking:
|
|
526
|
+
|
|
527
|
+
| Subpath | Contents |
|
|
528
|
+
| ------------------------------------ | ------------------------------------------------------------ |
|
|
529
|
+
| `@codefast/di` | Full public façade — everything below, combined |
|
|
530
|
+
| `@codefast/di/container` | `Container` interface, `DefaultContainer`, and related types |
|
|
531
|
+
| `@codefast/di/token` | `token()`, `Token<Value>`, `TokenValue` |
|
|
532
|
+
| `@codefast/di/binding` | `BindingBuilder`, `bind()`, binding type definitions |
|
|
533
|
+
| `@codefast/di/binding-select` | `filterMatchingBindings`, `selectBindingForRegistry` |
|
|
534
|
+
| `@codefast/di/module` | `Module`, `AsyncModule`, module builder types |
|
|
535
|
+
| `@codefast/di/decorators` | `@injectable`, `@singleton`, `@scoped`, `inject`, `optional` |
|
|
536
|
+
| `@codefast/di/decorators/inject` | `inject`, `optional`, `isInjectionDescriptor` |
|
|
537
|
+
| `@codefast/di/decorators/injectable` | `@injectable` |
|
|
538
|
+
| `@codefast/di/decorators/singleton` | `@singleton` |
|
|
539
|
+
| `@codefast/di/decorators/scoped` | `@scoped` |
|
|
540
|
+
| `@codefast/di/registry` | `BindingRegistry`, `registryKeyLabel` |
|
|
541
|
+
| `@codefast/di/resolver` | Resolution internals |
|
|
542
|
+
| `@codefast/di/scope` | `ScopeManager`, scope cache management |
|
|
543
|
+
| `@codefast/di/scope-validation` | `validateScopeRules` |
|
|
544
|
+
| `@codefast/di/lifecycle` | `runActivation`, `runActivationAsync`, activation types |
|
|
545
|
+
| `@codefast/di/constraints` | `whenParentIs`, `whenAnyAncestorIs`, `whenTargetTagged` |
|
|
546
|
+
| `@codefast/di/dependency-graph` | `collectStaticDependencyEdges`, `listResolvedDependencies` |
|
|
547
|
+
| `@codefast/di/inspector` | `ContainerInspector`, `ContainerSnapshot` |
|
|
548
|
+
| `@codefast/di/errors` | Complete `DiError` hierarchy |
|
|
549
|
+
| `@codefast/di/environment` | `isDevelopmentOrTestEnvironment`, `isProductionEnvironment` |
|
|
550
|
+
|
|
551
|
+
See `package.json → exports` for the authoritative list.
|
|
552
|
+
|
|
553
|
+
---
|
|
554
|
+
|
|
555
|
+
## Contributing
|
|
556
|
+
|
|
557
|
+
This package lives in the [Codefast monorepo](https://github.com/codefastlabs/codefast). From the repo root:
|
|
558
|
+
|
|
559
|
+
```bash
|
|
560
|
+
pnpm --filter @codefast/di build
|
|
561
|
+
pnpm --filter @codefast/di test
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
---
|
|
565
|
+
|
|
566
|
+
## License
|
|
567
|
+
|
|
568
|
+
[MIT](https://opensource.org/licenses/MIT) — see `license` in [`package.json`](./package.json).
|
|
569
|
+
|
|
570
|
+
## Changelog
|
|
571
|
+
|
|
572
|
+
Version history is published on [npm](https://www.npmjs.com/package/@codefast/di?activeTab=versions) with each release.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Token } from "./token.mjs";
|
|
2
|
+
import { RegistryKey } from "./registry.mjs";
|
|
3
|
+
import { Binding, ConstraintContext, Constructor, ResolveHint } from "./binding.mjs";
|
|
4
|
+
|
|
5
|
+
//#region src/binding-select.d.ts
|
|
6
|
+
/** Returns a human-readable label for a token or constructor (used in error messages and graph output). */
|
|
7
|
+
declare function registryKeyLabel(key: Token<unknown> | Constructor<unknown>): string;
|
|
8
|
+
/**
|
|
9
|
+
* Applies resolve hints and optional constraint predicates to a binding list.
|
|
10
|
+
*/
|
|
11
|
+
declare function filterMatchingBindings(bindings: readonly Binding<unknown>[], hint: ResolveHint | undefined, constraintCtx: ConstraintContext | undefined): Binding<unknown>[];
|
|
12
|
+
/**
|
|
13
|
+
* Picks the binding that would be used for resolution with the given hint (same rules as {@link DependencyResolver}).
|
|
14
|
+
* When `constraintCtx` is set, bindings with a {@link BindingBuilder.when} predicate must pass it.
|
|
15
|
+
*/
|
|
16
|
+
declare function selectBindingForRegistry(bindings: readonly Binding<unknown>[], hint: ResolveHint | undefined, tokenLabel: string, pathLabels: readonly string[], constraintCtx: ConstraintContext | undefined): Binding<unknown>;
|
|
17
|
+
/**
|
|
18
|
+
* Resolves the effective binding for a registry key using the default (no-hint) selection rules.
|
|
19
|
+
*/
|
|
20
|
+
declare function selectDefaultBindingForKey(lookup: (key: RegistryKey) => readonly Binding<unknown>[] | undefined, key: RegistryKey, pathPrefix: readonly string[]): Binding<unknown>;
|
|
21
|
+
//#endregion
|
|
22
|
+
export { filterMatchingBindings, registryKeyLabel, selectBindingForRegistry, selectDefaultBindingForKey };
|