@codefast/di 0.3.13 → 0.3.14-canary.1
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 +30 -0
- package/README.md +270 -234
- package/dist/binding-select.d.mts +17 -6
- package/dist/binding-select.mjs +17 -6
- package/dist/binding.d.mts +167 -34
- package/dist/binding.mjs +111 -14
- package/dist/constraints.d.mts +18 -3
- package/dist/constraints.mjs +18 -3
- package/dist/container.d.mts +85 -35
- package/dist/container.mjs +140 -6
- package/dist/decorators/inject.d.mts +40 -9
- package/dist/decorators/inject.mjs +50 -11
- package/dist/decorators/injectable.d.mts +2 -1
- package/dist/decorators/injectable.mjs +14 -2
- package/dist/decorators/lifecycle-decorators.d.mts +16 -4
- package/dist/decorators/lifecycle-decorators.mjs +16 -4
- package/dist/dependency-graph.d.mts +36 -13
- package/dist/dependency-graph.mjs +42 -8
- package/dist/errors.d.mts +132 -21
- package/dist/errors.mjs +126 -18
- package/dist/graph-adapters/cytoscape.d.mts +10 -0
- package/dist/graph-adapters/cytoscape.mjs +40 -0
- package/dist/graph-adapters/dot.d.mts +9 -0
- package/dist/graph-adapters/dot.mjs +97 -0
- package/dist/graph-adapters/reactflow.d.mts +10 -0
- package/dist/graph-adapters/reactflow.mjs +80 -0
- package/dist/graph-adapters/types.d.mts +91 -0
- package/dist/graph-adapters/types.mjs +1 -0
- package/dist/index.d.mts +2 -3
- package/dist/index.mjs +2 -2
- package/dist/inspector.d.mts +42 -40
- package/dist/inspector.mjs +18 -169
- package/dist/lifecycle.d.mts +28 -6
- package/dist/lifecycle.mjs +29 -10
- package/dist/metadata/metadata-keys.d.mts +17 -6
- package/dist/metadata/metadata-keys.mjs +17 -6
- package/dist/metadata/metadata-types.d.mts +42 -18
- package/dist/metadata/param-registry.mjs +6 -0
- package/dist/metadata/symbol-metadata-reader.d.mts +20 -3
- package/dist/metadata/symbol-metadata-reader.mjs +23 -4
- package/dist/module.d.mts +46 -2
- package/dist/module.mjs +19 -0
- package/dist/registry.d.mts +39 -8
- package/dist/registry.mjs +39 -8
- package/dist/resolver.d.mts +107 -12
- package/dist/resolver.mjs +134 -37
- package/dist/scope-validation.d.mts +3 -2
- package/dist/scope-validation.mjs +3 -2
- package/dist/scope.d.mts +38 -6
- package/dist/scope.mjs +42 -13
- package/dist/token.d.mts +9 -2
- package/dist/token.mjs +7 -1
- package/package.json +18 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @codefast/di
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Type-safe, ESM-only dependency injection for modern TypeScript — built on TC39 Stage 3 decorators with no runtime reflection.
|
|
4
4
|
|
|
5
5
|
[](https://github.com/codefastlabs/codefast/actions/workflows/release.yml)
|
|
6
6
|
[](https://www.npmjs.com/package/@codefast/di)
|
|
@@ -9,27 +9,33 @@ Lightweight, type-safe dependency injection for TypeScript — powered by TC39 S
|
|
|
9
9
|
|
|
10
10
|
## Table of Contents
|
|
11
11
|
|
|
12
|
-
- [
|
|
13
|
-
- [Installation](#installation)
|
|
12
|
+
- [Why @codefast/di](#why-codefastdi)
|
|
14
13
|
- [Requirements](#requirements)
|
|
15
|
-
- [
|
|
14
|
+
- [Installation](#installation)
|
|
16
15
|
- [Quick Start](#quick-start)
|
|
16
|
+
- [Core Concepts](#core-concepts)
|
|
17
17
|
- [Tokens](#tokens)
|
|
18
|
-
- [
|
|
19
|
-
- [
|
|
18
|
+
- [Bindings](#bindings)
|
|
19
|
+
- [Strategies](#strategies)
|
|
20
20
|
- [Scopes](#scopes)
|
|
21
|
-
- [Lifecycle hooks](#lifecycle-hooks)
|
|
22
21
|
- [Constraints](#constraints)
|
|
22
|
+
- [Lifecycle hooks](#lifecycle-hooks)
|
|
23
23
|
- [Decorators](#decorators)
|
|
24
|
-
- [
|
|
24
|
+
- [`@injectable`](#injectable)
|
|
25
|
+
- [`inject` / `optional` / `injectAll`](#inject--optional--injectall)
|
|
26
|
+
- [Accessor injection](#accessor-injection)
|
|
27
|
+
- [`@postConstruct` / `@preDestroy`](#postconstruct--predestroy)
|
|
28
|
+
- [Auto-registration](#auto-registration)
|
|
29
|
+
- [Container](#container)
|
|
25
30
|
- [Resolution](#resolution)
|
|
26
31
|
- [Async resolution](#async-resolution)
|
|
32
|
+
- [Rebinding and unbinding](#rebinding-and-unbinding)
|
|
27
33
|
- [Child containers](#child-containers)
|
|
28
34
|
- [Validation](#validation)
|
|
29
35
|
- [Introspection](#introspection)
|
|
30
|
-
- [
|
|
36
|
+
- [Disposal](#disposal)
|
|
31
37
|
- [Modules](#modules)
|
|
32
|
-
- [
|
|
38
|
+
- [Errors](#errors)
|
|
33
39
|
- [Package exports](#package-exports)
|
|
34
40
|
- [Contributing](#contributing)
|
|
35
41
|
- [License](#license)
|
|
@@ -37,31 +43,17 @@ Lightweight, type-safe dependency injection for TypeScript — powered by TC39 S
|
|
|
37
43
|
|
|
38
44
|
---
|
|
39
45
|
|
|
40
|
-
##
|
|
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
|
|
46
|
+
## Why @codefast/di
|
|
57
47
|
|
|
58
|
-
|
|
59
|
-
# pnpm
|
|
60
|
-
pnpm add @codefast/di
|
|
48
|
+
`@codefast/di` is a small IoC container designed for applications that compile to ESM and want strong typing without metadata reflection tricks.
|
|
61
49
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
50
|
+
- **Typed tokens.** `Token<Value>` flows through every `bind → resolve` path; the return type of `resolve()` is the one you registered.
|
|
51
|
+
- **Native Stage 3 decorators.** `@injectable`, `inject`, `optional`, `@postConstruct`, `@preDestroy` write to `Symbol.metadata`. No `reflect-metadata`, no `experimentalDecorators`.
|
|
52
|
+
- **Fluent binding API.** Constants, classes, sync/async factories, aliases, named/tagged/predicate constraints, activation + deactivation hooks.
|
|
53
|
+
- **Module system.** `Module` and `AsyncModule` bundle bindings into reusable units that can be loaded, unloaded, and re-used across containers.
|
|
54
|
+
- **Scope safety.** Detects captive dependencies (a singleton depending on a scoped or transient binding) during development and test.
|
|
55
|
+
- **Async-first.** Dedupes in-flight async singleton construction and supports `await using` for automatic cleanup.
|
|
56
|
+
- **Tree-shakeable subpaths.** Import only the surface you need.
|
|
65
57
|
|
|
66
58
|
---
|
|
67
59
|
|
|
@@ -72,7 +64,7 @@ npm install @codefast/di
|
|
|
72
64
|
| Node.js | `>= 22.0.0` |
|
|
73
65
|
| TypeScript | `>= 5.2` (Stage 3 decorators) |
|
|
74
66
|
|
|
75
|
-
Enable native decorators in `tsconfig.json`
|
|
67
|
+
Enable native decorators in `tsconfig.json` — do **not** enable `experimentalDecorators`:
|
|
76
68
|
|
|
77
69
|
```json
|
|
78
70
|
{
|
|
@@ -86,16 +78,15 @@ Enable native decorators in `tsconfig.json` (do **not** enable `experimentalDeco
|
|
|
86
78
|
|
|
87
79
|
---
|
|
88
80
|
|
|
89
|
-
##
|
|
81
|
+
## Installation
|
|
90
82
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
| **Decorator metadata** | `@injectable([…])` writes constructor dependency descriptors into `Symbol.metadata`, which the container reads at resolution time. |
|
|
83
|
+
```bash
|
|
84
|
+
pnpm add @codefast/di
|
|
85
|
+
# or
|
|
86
|
+
npm install @codefast/di
|
|
87
|
+
# or
|
|
88
|
+
yarn add @codefast/di
|
|
89
|
+
```
|
|
99
90
|
|
|
100
91
|
---
|
|
101
92
|
|
|
@@ -104,15 +95,13 @@ Enable native decorators in `tsconfig.json` (do **not** enable `experimentalDeco
|
|
|
104
95
|
```typescript
|
|
105
96
|
import { Container, inject, injectable, token } from "@codefast/di";
|
|
106
97
|
|
|
107
|
-
// 1. Define an interface and a token for it
|
|
108
98
|
interface Logger {
|
|
109
99
|
log(message: string): void;
|
|
110
100
|
}
|
|
111
101
|
|
|
112
102
|
const LoggerToken = token<Logger>("Logger");
|
|
113
103
|
|
|
114
|
-
|
|
115
|
-
@injectable([inject(LoggerToken)])
|
|
104
|
+
@injectable([LoggerToken])
|
|
116
105
|
class AppService {
|
|
117
106
|
constructor(private readonly logger: Logger) {}
|
|
118
107
|
|
|
@@ -121,22 +110,32 @@ class AppService {
|
|
|
121
110
|
}
|
|
122
111
|
}
|
|
123
112
|
|
|
124
|
-
// 3. Create a container and register bindings
|
|
125
113
|
const container = Container.create();
|
|
126
114
|
|
|
127
115
|
container.bind(LoggerToken).toConstantValue({ log: console.log });
|
|
128
116
|
container.bind(AppService).toSelf().singleton();
|
|
129
117
|
|
|
130
|
-
|
|
131
|
-
const app = container.resolve(AppService);
|
|
132
|
-
app.run(); // "App started"
|
|
118
|
+
container.resolve(AppService).run(); // "App started"
|
|
133
119
|
```
|
|
134
120
|
|
|
121
|
+
The `@injectable([LoggerToken])` call lists constructor dependencies in order. Wrap an entry with `inject(token, options)` or `optional(token, options)` when you need a named/tagged hint or optional semantics.
|
|
122
|
+
|
|
135
123
|
---
|
|
136
124
|
|
|
137
|
-
##
|
|
125
|
+
## Core Concepts
|
|
126
|
+
|
|
127
|
+
| Concept | Description |
|
|
128
|
+
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
129
|
+
| **Token** | Branded identifier pairing a name string with a TypeScript type. Tokens compare by reference — always reuse the same `const`. |
|
|
130
|
+
| **Binding** | Associates a token (or constructor) with a value strategy (constant / class / factory / alias), a scope, optional constraints, and lifecycle hooks. |
|
|
131
|
+
| **Container** | Holds a `BindingRegistry` and a `ScopeManager`; resolves bindings through the `DependencyResolver`. Supports child containers and async disposal. |
|
|
132
|
+
| **Scope** | Instance lifetime: `singleton` (one per root container, shared with children), `scoped` (one per child container), `transient` (new on every resolve). |
|
|
133
|
+
| **Module** | Reusable bundle of bindings. Loaded once per container; loading the same module twice is a no-op. |
|
|
134
|
+
| **Metadata** | `@injectable([...])` writes constructor descriptors into `Symbol.metadata`, which the container reads at resolution time. |
|
|
138
135
|
|
|
139
|
-
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Tokens
|
|
140
139
|
|
|
141
140
|
```typescript
|
|
142
141
|
import { token } from "@codefast/di";
|
|
@@ -145,50 +144,44 @@ const DbToken = token<Database>("Database");
|
|
|
145
144
|
const CacheToken = token<Cache>("Cache");
|
|
146
145
|
```
|
|
147
146
|
|
|
148
|
-
The type parameter flows through the binding and resolution chain
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
You can also use a constructor directly as a key:
|
|
147
|
+
- The type parameter flows through the binding and resolution chain.
|
|
148
|
+
- Tokens use reference equality; two tokens with the same name are two distinct keys.
|
|
149
|
+
- A class constructor can itself be a key:
|
|
153
150
|
|
|
154
151
|
```typescript
|
|
155
|
-
container.bind(
|
|
156
|
-
container.resolve(
|
|
152
|
+
container.bind(UserService).toSelf();
|
|
153
|
+
container.resolve(UserService); // returns UserService
|
|
157
154
|
```
|
|
158
155
|
|
|
159
156
|
---
|
|
160
157
|
|
|
161
|
-
##
|
|
158
|
+
## Bindings
|
|
162
159
|
|
|
163
|
-
|
|
160
|
+
Start with `container.bind(key)` and chain a strategy, then optionally a scope, constraints, and hooks.
|
|
164
161
|
|
|
165
|
-
|
|
162
|
+
### Strategies
|
|
166
163
|
|
|
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
|
|
173
|
-
| `.toDynamicAsync(factory)` | Bind to an async factory `(ctx: ResolutionContext) => Promise<Value>`.
|
|
174
|
-
| `.toResolved(factory, deps)` | Bind to a factory
|
|
175
|
-
| `.toAlias(targetToken)` | Redirect
|
|
164
|
+
| Method | Description |
|
|
165
|
+
| ---------------------------- | --------------------------------------------------------------------------- |
|
|
166
|
+
| `.toConstantValue(value)` | Bind to a fixed value. Always treated as a singleton. |
|
|
167
|
+
| `.toSelf()` | Bind a constructor to itself. Reads `@injectable()` metadata. |
|
|
168
|
+
| `.to(Constructor)` | Bind a token to a class constructor. |
|
|
169
|
+
| `.toDynamic(factory)` | Bind to a sync factory `(ctx: ResolutionContext) => Value`. |
|
|
170
|
+
| `.toDynamicAsync(factory)` | Bind to an async factory `(ctx: ResolutionContext) => Promise<Value>`. |
|
|
171
|
+
| `.toResolved(factory, deps)` | Bind to a factory whose dependency array is checked against the deps types. |
|
|
172
|
+
| `.toAlias(targetToken)` | Redirect resolution to another token; the alias adopts the target's scope. |
|
|
176
173
|
|
|
177
174
|
```typescript
|
|
178
|
-
// Constant
|
|
179
175
|
container.bind(AppConfigToken).toConstantValue({ port: 3000 });
|
|
180
176
|
|
|
181
|
-
// Class
|
|
182
177
|
container.bind(UserRepository).toSelf().singleton();
|
|
183
178
|
container.bind(UserServiceToken).to(UserService).transient();
|
|
184
179
|
|
|
185
|
-
// Factory — receive resolution context to resolve nested deps
|
|
186
180
|
container.bind(DbToken).toDynamic((ctx) => {
|
|
187
181
|
const config = ctx.resolve(AppConfigToken);
|
|
188
182
|
return new Database(config.dbUrl);
|
|
189
183
|
});
|
|
190
184
|
|
|
191
|
-
// Async factory
|
|
192
185
|
container.bind(DbToken).toDynamicAsync(async (ctx) => {
|
|
193
186
|
const config = ctx.resolve(AppConfigToken);
|
|
194
187
|
const db = new Database(config.dbUrl);
|
|
@@ -196,19 +189,20 @@ container.bind(DbToken).toDynamicAsync(async (ctx) => {
|
|
|
196
189
|
return db;
|
|
197
190
|
});
|
|
198
191
|
|
|
199
|
-
|
|
192
|
+
container
|
|
193
|
+
.bind(UserServiceToken)
|
|
194
|
+
.toResolved((repo, cfg) => new UserService(repo, cfg), [UserRepository, AppConfigToken] as const);
|
|
195
|
+
|
|
200
196
|
container.bind(LegacyServiceToken).toAlias(NewServiceToken);
|
|
201
197
|
```
|
|
202
198
|
|
|
203
199
|
### Scopes
|
|
204
200
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
|
208
|
-
|
|
|
209
|
-
| `.
|
|
210
|
-
| `.transient()` | New instance on every `resolve()`. Default for most bindings. |
|
|
211
|
-
| `.scoped()` | One instance per child container scope. Useful for request-scoped services. |
|
|
201
|
+
| Method | Lifetime |
|
|
202
|
+
| -------------- | -------------------------------------------------------------------------- |
|
|
203
|
+
| `.singleton()` | One instance per root container, shared with descendants. |
|
|
204
|
+
| `.scoped()` | One instance per child container. Useful for request-scoped services. |
|
|
205
|
+
| `.transient()` | New instance on every resolution. Default when no scope method is chained. |
|
|
212
206
|
|
|
213
207
|
```typescript
|
|
214
208
|
container.bind(DatabaseToken).toDynamic(createDb).singleton();
|
|
@@ -216,75 +210,74 @@ container.bind(RequestContextToken).toSelf().scoped();
|
|
|
216
210
|
container.bind(QueryBuilderToken).toSelf().transient();
|
|
217
211
|
```
|
|
218
212
|
|
|
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
213
|
### Constraints
|
|
241
214
|
|
|
242
|
-
|
|
215
|
+
Multiple bindings can share the same token. A constraint picks the right one at resolution time.
|
|
243
216
|
|
|
244
|
-
**Named
|
|
217
|
+
**Named**
|
|
245
218
|
|
|
246
219
|
```typescript
|
|
247
|
-
container.bind(LoggerToken).
|
|
248
|
-
container.bind(LoggerToken).
|
|
220
|
+
container.bind(LoggerToken).whenNamed("file").toConstantValue(fileLogger);
|
|
221
|
+
container.bind(LoggerToken).whenNamed("console").toConstantValue(consoleLogger);
|
|
249
222
|
|
|
250
|
-
// Resolve by name
|
|
251
223
|
container.resolve(LoggerToken, { name: "file" });
|
|
252
224
|
```
|
|
253
225
|
|
|
254
|
-
**Tagged
|
|
226
|
+
**Tagged** — the hint is a tuple `[tag, value]`:
|
|
255
227
|
|
|
256
228
|
```typescript
|
|
257
|
-
container.bind(StorageToken).
|
|
258
|
-
container.bind(StorageToken).
|
|
229
|
+
container.bind(StorageToken).whenTagged("provider", "s3").to(S3Storage);
|
|
230
|
+
container.bind(StorageToken).whenTagged("provider", "local").to(LocalStorage);
|
|
259
231
|
|
|
260
|
-
container.resolve(StorageToken, { tag: "provider",
|
|
232
|
+
container.resolve(StorageToken, { tag: ["provider", "s3"] });
|
|
261
233
|
```
|
|
262
234
|
|
|
263
|
-
**
|
|
235
|
+
**Predicate** — inspect the full resolution graph:
|
|
264
236
|
|
|
265
237
|
```typescript
|
|
266
|
-
import { whenParentIs } from "@codefast/di";
|
|
238
|
+
import { whenAnyAncestorIs, whenParentIs, whenTargetTagged } from "@codefast/di";
|
|
267
239
|
|
|
268
|
-
container.bind(LoggerToken).
|
|
240
|
+
container.bind(LoggerToken).when(whenParentIs(DiagnosticsService)).toConstantValue(verboseLogger);
|
|
269
241
|
```
|
|
270
242
|
|
|
271
|
-
Built-in
|
|
243
|
+
Built-in predicates:
|
|
244
|
+
|
|
245
|
+
| Helper | Matches when … |
|
|
246
|
+
| ------------------------------ | --------------------------------------------------------------------------- |
|
|
247
|
+
| `whenParentIs(key)` | the direct parent binding was registered for `key`. |
|
|
248
|
+
| `whenAnyAncestorIs(key)` | any ancestor binding on the materialization stack was registered for `key`. |
|
|
249
|
+
| `whenTargetTagged(tag, value)` | the immediate parent binding carries `tag` with `value`. |
|
|
250
|
+
|
|
251
|
+
For anything else, pass a custom `(ctx: ConstraintContext) => boolean` to `.when(predicate)`.
|
|
252
|
+
|
|
253
|
+
### Lifecycle hooks
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
container
|
|
257
|
+
.bind(DbToken)
|
|
258
|
+
.toDynamicAsync(async (ctx) => new Database(ctx.resolve(ConfigToken)))
|
|
259
|
+
.singleton()
|
|
260
|
+
.onActivation(async (ctx, instance) => {
|
|
261
|
+
await instance.connect();
|
|
262
|
+
return instance;
|
|
263
|
+
})
|
|
264
|
+
.onDeactivation(async (instance) => {
|
|
265
|
+
await instance.disconnect();
|
|
266
|
+
});
|
|
267
|
+
```
|
|
272
268
|
|
|
273
|
-
|
|
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. |
|
|
269
|
+
- `onActivation(ctx, instance)` runs after construction (and after `@postConstruct`). It must return the instance (possibly wrapped).
|
|
270
|
+
- `onDeactivation(instance)` runs when the instance is evicted: `container.dispose()`, `unbind`, `rebind`, or `unload`.
|
|
278
271
|
|
|
279
272
|
---
|
|
280
273
|
|
|
281
274
|
## Decorators
|
|
282
275
|
|
|
283
|
-
|
|
276
|
+
All decorators use TC39 Stage 3 syntax and write to `Symbol.metadata`.
|
|
284
277
|
|
|
285
|
-
### `@injectable
|
|
278
|
+
### `@injectable`
|
|
286
279
|
|
|
287
|
-
Registers constructor
|
|
280
|
+
Registers constructor dependencies. The array length must match the constructor arity; a mismatch throws `InternalError` at class-definition time.
|
|
288
281
|
|
|
289
282
|
```typescript
|
|
290
283
|
import { inject, injectable, optional, token } from "@codefast/di";
|
|
@@ -292,7 +285,7 @@ import { inject, injectable, optional, token } from "@codefast/di";
|
|
|
292
285
|
const ConfigToken = token<AppConfig>("AppConfig");
|
|
293
286
|
const CacheToken = token<Cache>("Cache");
|
|
294
287
|
|
|
295
|
-
@injectable([
|
|
288
|
+
@injectable([ConfigToken, optional(CacheToken)])
|
|
296
289
|
class AppService {
|
|
297
290
|
constructor(
|
|
298
291
|
private readonly config: AppConfig,
|
|
@@ -301,124 +294,166 @@ class AppService {
|
|
|
301
294
|
}
|
|
302
295
|
```
|
|
303
296
|
|
|
304
|
-
|
|
305
|
-
- `optional(token, options?)` — optional dependency. Resolves to `undefined` if not bound.
|
|
297
|
+
Each entry is either a plain `Token` / `Constructor` or an `InjectionDescriptor` produced by `inject()` / `optional()` / `injectAll()`.
|
|
306
298
|
|
|
307
|
-
|
|
299
|
+
### `inject` / `optional` / `injectAll`
|
|
308
300
|
|
|
309
301
|
```typescript
|
|
310
302
|
inject(LoggerToken, { name: "console" });
|
|
311
|
-
inject(StorageToken, { tag: "provider",
|
|
303
|
+
inject(StorageToken, { tag: ["provider", "s3"] });
|
|
304
|
+
optional(CacheToken);
|
|
305
|
+
injectAll(PluginToken);
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
- `inject(token, options?)` — required. Throws `TokenNotBoundError` if the dependency is missing.
|
|
309
|
+
- `optional(token, options?)` — optional. Resolves to `undefined` when unbound.
|
|
310
|
+
- `injectAll(token, options?)` — resolves every matching binding into an array (`Value[]`), applying `name`/`tag` filters when provided.
|
|
311
|
+
|
|
312
|
+
### Accessor injection
|
|
313
|
+
|
|
314
|
+
`inject` doubles as a TC39 accessor-field decorator for post-construction property injection:
|
|
315
|
+
|
|
316
|
+
```typescript
|
|
317
|
+
@injectable([])
|
|
318
|
+
class Controller {
|
|
319
|
+
@inject(LoggerToken) accessor logger!: Logger;
|
|
320
|
+
}
|
|
312
321
|
```
|
|
313
322
|
|
|
314
|
-
|
|
323
|
+
The container injects the accessor after construction, so it does not count toward the constructor arity declared in `@injectable([])`.
|
|
324
|
+
|
|
325
|
+
### `@postConstruct` / `@preDestroy`
|
|
315
326
|
|
|
316
|
-
|
|
327
|
+
Method decorators that hook into the instance lifecycle. Order:
|
|
328
|
+
`construct → @postConstruct → onActivation → cache … onDeactivation → @preDestroy`.
|
|
317
329
|
|
|
318
330
|
```typescript
|
|
319
|
-
@
|
|
320
|
-
|
|
331
|
+
import { postConstruct, preDestroy } from "@codefast/di";
|
|
332
|
+
|
|
333
|
+
@injectable([DbToken])
|
|
321
334
|
class UserRepository {
|
|
322
335
|
constructor(private readonly db: Database) {}
|
|
336
|
+
|
|
337
|
+
@postConstruct()
|
|
338
|
+
async init(): Promise<void> {
|
|
339
|
+
await this.db.warmCache();
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
@preDestroy()
|
|
343
|
+
async shutdown(): Promise<void> {
|
|
344
|
+
await this.db.flush();
|
|
345
|
+
}
|
|
323
346
|
}
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
Only one of each decorator is allowed per class.
|
|
350
|
+
|
|
351
|
+
### Auto-registration
|
|
324
352
|
|
|
325
|
-
|
|
326
|
-
|
|
353
|
+
Pass `autoRegister` to `@injectable` to have the class register itself in a module-scoped list:
|
|
354
|
+
|
|
355
|
+
```typescript
|
|
356
|
+
@injectable([DbToken], { autoRegister: true, scope: "singleton" })
|
|
357
|
+
class UserRepository {
|
|
358
|
+
constructor(private readonly db: Database) {}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Bind every auto-registered class at once
|
|
362
|
+
container.loadAutoRegistered();
|
|
327
363
|
```
|
|
328
364
|
|
|
329
|
-
|
|
365
|
+
`scope` defaults to `"transient"`. `getAutoRegistered()` returns the list if you prefer to iterate manually.
|
|
330
366
|
|
|
331
367
|
---
|
|
332
368
|
|
|
333
|
-
## Container
|
|
369
|
+
## Container
|
|
334
370
|
|
|
335
371
|
### Resolution
|
|
336
372
|
|
|
337
373
|
```typescript
|
|
338
374
|
const container = Container.create();
|
|
339
375
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
// Named or tagged hint
|
|
344
|
-
const logger = container.resolve(LoggerToken, { name: "console" });
|
|
376
|
+
container.resolve(ServiceToken);
|
|
377
|
+
container.resolveOptional(CacheToken); // undefined when unbound
|
|
378
|
+
container.resolveAll(HandlerToken); // all matching multi-bindings
|
|
345
379
|
|
|
346
|
-
|
|
347
|
-
|
|
380
|
+
container.resolve(LoggerToken, { name: "console" });
|
|
381
|
+
container.resolve(StorageToken, { tag: ["provider", "s3"] });
|
|
348
382
|
|
|
349
|
-
|
|
350
|
-
|
|
383
|
+
container.has(CacheToken);
|
|
384
|
+
container.has(LoggerToken, { name: "console" });
|
|
351
385
|
```
|
|
352
386
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
```typescript
|
|
356
|
-
if (container.has(CacheToken)) {
|
|
357
|
-
const cache = container.resolve(CacheToken);
|
|
358
|
-
}
|
|
359
|
-
```
|
|
387
|
+
`resolveAll()` and `ctx.resolveAll()` preserve the current resolution context (path + parent/ancestors stack), so `when(...)` predicates and scope checks behave the same as `resolve()`.
|
|
360
388
|
|
|
361
389
|
### Async resolution
|
|
362
390
|
|
|
363
|
-
Use
|
|
391
|
+
Use the `*Async` variants when any binding in the resolution chain uses `toDynamicAsync`, async `onActivation`, or async `@postConstruct`. Mixing async into a sync resolve throws `AsyncResolutionError`.
|
|
364
392
|
|
|
365
393
|
```typescript
|
|
366
394
|
const db = await container.resolveAsync(DbToken);
|
|
367
395
|
const handlers = await container.resolveAllAsync(HandlerToken);
|
|
396
|
+
// resolveOptional is sync-only — for async optional lookups, check `has` then `resolveAsync`
|
|
397
|
+
const cache = container.has(CacheToken) ? await container.resolveAsync(CacheToken) : undefined;
|
|
398
|
+
|
|
399
|
+
// Eagerly construct every singleton binding
|
|
400
|
+
await container.initializeAsync();
|
|
368
401
|
```
|
|
369
402
|
|
|
370
403
|
### Rebinding and unbinding
|
|
371
404
|
|
|
372
405
|
```typescript
|
|
373
|
-
// Replace all bindings for a token
|
|
374
406
|
container.rebind(LoggerToken).toConstantValue(testLogger);
|
|
375
407
|
|
|
376
|
-
//
|
|
377
|
-
container.
|
|
378
|
-
|
|
379
|
-
// Remove with deactivation hooks (async)
|
|
380
|
-
await container.unbindAsync(CacheToken);
|
|
408
|
+
container.unbind(CacheToken); // sync deactivation hooks only
|
|
409
|
+
await container.unbindAsync(CacheToken); // awaits async deactivation
|
|
381
410
|
```
|
|
382
411
|
|
|
383
412
|
### Child containers
|
|
384
413
|
|
|
385
|
-
Child containers share the parent's singleton cache but
|
|
414
|
+
Child containers fall through to the parent's bindings and share the parent's singleton cache, but have their own scoped cache.
|
|
386
415
|
|
|
387
416
|
```typescript
|
|
388
417
|
const requestContainer = container.createChild();
|
|
389
418
|
|
|
390
419
|
requestContainer.bind(RequestContextToken).toConstantValue(req).scoped();
|
|
391
|
-
|
|
392
420
|
const service = requestContainer.resolve(RequestScopedService);
|
|
393
421
|
|
|
394
|
-
//
|
|
395
|
-
await requestContainer.dispose();
|
|
422
|
+
await requestContainer.dispose(); // releases scoped instances
|
|
396
423
|
```
|
|
397
424
|
|
|
398
425
|
### Validation
|
|
399
426
|
|
|
400
|
-
`validate()` checks
|
|
427
|
+
`validate()` statically checks that no singleton binding depends on a `scoped` or `transient` binding (a captive dependency). In development and test environments the container runs `validate()` at most once after registry changes, so most scope violations surface without an explicit call.
|
|
401
428
|
|
|
402
429
|
```typescript
|
|
403
|
-
container.validate(); // throws ScopeViolationError
|
|
430
|
+
container.validate(); // throws ScopeViolationError on the first violation
|
|
404
431
|
```
|
|
405
432
|
|
|
433
|
+
Control the environment heuristic via `NODE_ENV` — see `isDevelopmentOrTestEnvironment` in `@codefast/di/environment`.
|
|
434
|
+
|
|
406
435
|
### Introspection
|
|
407
436
|
|
|
408
437
|
```typescript
|
|
409
|
-
|
|
410
|
-
|
|
438
|
+
import { toCytoscapeGraph } from "@codefast/di/graph-adapters/cytoscape";
|
|
439
|
+
import { toDotGraph } from "@codefast/di/graph-adapters/dot";
|
|
440
|
+
import { toReactFlowGraph } from "@codefast/di/graph-adapters/reactflow";
|
|
411
441
|
|
|
412
|
-
|
|
413
|
-
const
|
|
442
|
+
const snapshot = container.inspect();
|
|
443
|
+
const json = container.generateDependencyGraph({ hideInternals: true });
|
|
444
|
+
const dot = toDotGraph(json);
|
|
414
445
|
|
|
415
|
-
// JSON
|
|
416
|
-
const
|
|
446
|
+
// Adapters are pure converters from the canonical JSON graph.
|
|
447
|
+
const cytoscape = toCytoscapeGraph(json);
|
|
448
|
+
const reactflow = toReactFlowGraph(json);
|
|
417
449
|
```
|
|
418
450
|
|
|
419
|
-
|
|
451
|
+
`generateDependencyGraph` always returns the canonical typed `ContainerGraphJson` (`nodes` + `edges`).
|
|
452
|
+
Keep visualization adapters (`toDotGraph`, `toCytoscapeGraph`, `toReactFlowGraph`, or your own converters) outside container/inspector core APIs and import them from direct subpaths under `@codefast/di/graph-adapters/*`.
|
|
453
|
+
|
|
454
|
+
### Disposal
|
|
420
455
|
|
|
421
|
-
`Container` implements `AsyncDisposable`,
|
|
456
|
+
`Container` implements `AsyncDisposable`, so `await using` runs `dispose()` automatically:
|
|
422
457
|
|
|
423
458
|
```typescript
|
|
424
459
|
{
|
|
@@ -426,26 +461,26 @@ const graph = container.generateDependencyGraphJson();
|
|
|
426
461
|
container.bind(DbToken).toDynamicAsync(connectDb).singleton().onDeactivation(disconnectDb);
|
|
427
462
|
|
|
428
463
|
const db = await container.resolveAsync(DbToken);
|
|
429
|
-
//
|
|
430
|
-
} //
|
|
464
|
+
// …
|
|
465
|
+
} // dispose() runs all deactivation hooks
|
|
431
466
|
```
|
|
432
467
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
```typescript
|
|
436
|
-
await container.dispose();
|
|
437
|
-
```
|
|
468
|
+
Sync `using` is intentionally rejected: calling `Symbol.dispose` throws. Use `await using` or `await container.dispose()`.
|
|
438
469
|
|
|
439
470
|
---
|
|
440
471
|
|
|
441
472
|
## Modules
|
|
442
473
|
|
|
443
|
-
Modules
|
|
474
|
+
Modules bundle related bindings into reusable units. A module holds no runtime state and can be loaded into any number of containers.
|
|
444
475
|
|
|
445
|
-
|
|
476
|
+
Inside module setup, binding semantics depend on whether a disambiguator is present before `to*()`:
|
|
477
|
+
|
|
478
|
+
- `api.bind(Token).to*(...)` uses last-wins for that token.
|
|
479
|
+
- `api.bind(Token).whenNamed(...)` / `.whenTagged(...)` / `.when(...)` **before** `to*()` appends another multi-binding entry.
|
|
480
|
+
- `api.bind(Token).to*(...).whenNamed(...)` updates only that single built binding; it does not switch later lines into append mode.
|
|
446
481
|
|
|
447
482
|
```typescript
|
|
448
|
-
import { Module } from "@codefast/di";
|
|
483
|
+
import { Container, Module } from "@codefast/di";
|
|
449
484
|
|
|
450
485
|
const InfraModule = Module.create("Infra", (api) => {
|
|
451
486
|
api.bind(LoggerToken).toConstantValue(console);
|
|
@@ -453,7 +488,7 @@ const InfraModule = Module.create("Infra", (api) => {
|
|
|
453
488
|
});
|
|
454
489
|
|
|
455
490
|
const AppModule = Module.create("App", (api) => {
|
|
456
|
-
api.import(InfraModule);
|
|
491
|
+
api.import(InfraModule);
|
|
457
492
|
api.bind(UserRepository).toSelf().singleton();
|
|
458
493
|
api.bind(UserServiceToken).to(UserService).transient();
|
|
459
494
|
});
|
|
@@ -461,11 +496,9 @@ const AppModule = Module.create("App", (api) => {
|
|
|
461
496
|
const container = Container.fromModules(AppModule);
|
|
462
497
|
```
|
|
463
498
|
|
|
464
|
-
|
|
499
|
+
Async modules may `await` during setup (e.g. dynamic config):
|
|
465
500
|
|
|
466
501
|
```typescript
|
|
467
|
-
import { Module } from "@codefast/di";
|
|
468
|
-
|
|
469
502
|
const DbModule = Module.createAsync("Database", async (api) => {
|
|
470
503
|
const config = await fetchRemoteConfig();
|
|
471
504
|
api.bind(DbToken).toConstantValue(await Database.connect(config.dbUrl));
|
|
@@ -474,44 +507,45 @@ const DbModule = Module.createAsync("Database", async (api) => {
|
|
|
474
507
|
const container = await Container.fromModulesAsync(DbModule, AppModule);
|
|
475
508
|
```
|
|
476
509
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
### Loading and unloading
|
|
510
|
+
Load and unload on an existing container:
|
|
480
511
|
|
|
481
512
|
```typescript
|
|
482
|
-
// Load into existing container
|
|
483
513
|
container.load(InfraModule, AppModule);
|
|
484
514
|
await container.loadAsync(DbModule);
|
|
485
515
|
|
|
486
|
-
// Remove and run deactivation hooks
|
|
487
516
|
container.unload(AppModule);
|
|
488
517
|
await container.unloadAsync(DbModule);
|
|
489
518
|
```
|
|
490
519
|
|
|
520
|
+
Re-loading a module already present is a no-op. Circular imports between modules throw `CircularDependencyError`.
|
|
521
|
+
|
|
491
522
|
---
|
|
492
523
|
|
|
493
|
-
##
|
|
524
|
+
## Errors
|
|
494
525
|
|
|
495
|
-
All errors extend `DiError` and expose a stable `code` property
|
|
526
|
+
All errors extend `DiError` and expose a stable `code` property.
|
|
496
527
|
|
|
497
|
-
| Error class | `code` |
|
|
498
|
-
| ------------------------- | ----------------------- |
|
|
499
|
-
| `TokenNotBoundError` | `"TOKEN_NOT_BOUND"` |
|
|
500
|
-
| `NoMatchingBindingError` | `"NO_MATCHING_BINDING"` |
|
|
501
|
-
| `CircularDependencyError` | `"CIRCULAR_DEPENDENCY"` |
|
|
502
|
-
| `MissingMetadataError` | `"MISSING_METADATA"` |
|
|
503
|
-
| `AsyncModuleLoadError` | `"ASYNC_MODULE_LOAD"` | `load()`
|
|
504
|
-
| `AsyncResolutionError` | `"ASYNC_RESOLUTION"` |
|
|
505
|
-
| `ScopeViolationError` | `"SCOPE_VIOLATION"` |
|
|
528
|
+
| Error class | `code` | Thrown when |
|
|
529
|
+
| ------------------------- | ----------------------- | ----------------------------------------------------------- |
|
|
530
|
+
| `TokenNotBoundError` | `"TOKEN_NOT_BOUND"` | A required token has no binding |
|
|
531
|
+
| `NoMatchingBindingError` | `"NO_MATCHING_BINDING"` | A name/tag/predicate hint matches no registered binding |
|
|
532
|
+
| `CircularDependencyError` | `"CIRCULAR_DEPENDENCY"` | A cycle is detected in dependency or module resolution |
|
|
533
|
+
| `MissingMetadataError` | `"MISSING_METADATA"` | A class binding is missing `@injectable()` metadata |
|
|
534
|
+
| `AsyncModuleLoadError` | `"ASYNC_MODULE_LOAD"` | Sync `load()` is called with an `AsyncModule` |
|
|
535
|
+
| `AsyncResolutionError` | `"ASYNC_RESOLUTION"` | An async binding is reached during a sync `resolve()` |
|
|
536
|
+
| `ScopeViolationError` | `"SCOPE_VIOLATION"` | A singleton depends on a scoped or transient binding |
|
|
537
|
+
| `InternalError` | `"INTERNAL"` | Invariant violations (should never surface in correct code) |
|
|
506
538
|
|
|
507
539
|
```typescript
|
|
508
|
-
import { DiError,
|
|
540
|
+
import { DiError, ScopeViolationError, TokenNotBoundError } from "@codefast/di";
|
|
509
541
|
|
|
510
542
|
try {
|
|
511
|
-
|
|
543
|
+
container.resolve(ServiceToken);
|
|
512
544
|
} catch (error) {
|
|
513
545
|
if (error instanceof TokenNotBoundError) {
|
|
514
546
|
console.error(`Not registered: ${error.tokenName}`);
|
|
547
|
+
} else if (error instanceof ScopeViolationError) {
|
|
548
|
+
console.error(`Scope violation: ${error.message}`);
|
|
515
549
|
} else if (error instanceof DiError) {
|
|
516
550
|
console.error(`DI error [${error.code}]: ${error.message}`);
|
|
517
551
|
}
|
|
@@ -522,31 +556,30 @@ try {
|
|
|
522
556
|
|
|
523
557
|
## Package exports
|
|
524
558
|
|
|
525
|
-
The root entry
|
|
526
|
-
|
|
527
|
-
| Subpath
|
|
528
|
-
|
|
|
529
|
-
| `@codefast/di`
|
|
530
|
-
| `@codefast/di/container`
|
|
531
|
-
| `@codefast/di/token`
|
|
532
|
-
| `@codefast/di/binding`
|
|
533
|
-
| `@codefast/di/binding-select`
|
|
534
|
-
| `@codefast/di/module`
|
|
535
|
-
| `@codefast/di/decorators`
|
|
536
|
-
| `@codefast/di/decorators/
|
|
537
|
-
| `@codefast/di/decorators/
|
|
538
|
-
| `@codefast/di/
|
|
539
|
-
| `@codefast/di/
|
|
540
|
-
| `@codefast/di/
|
|
541
|
-
| `@codefast/di/
|
|
542
|
-
| `@codefast/di/scope`
|
|
543
|
-
| `@codefast/di/
|
|
544
|
-
| `@codefast/di/
|
|
545
|
-
| `@codefast/di/
|
|
546
|
-
| `@codefast/di/
|
|
547
|
-
| `@codefast/di/
|
|
548
|
-
| `@codefast/di/
|
|
549
|
-
| `@codefast/di/environment` | `isDevelopmentOrTestEnvironment`, `isProductionEnvironment` |
|
|
559
|
+
The root entry re-exports the full public API. Subpath exports are provided for fine-grained imports and bundler tree-shaking.
|
|
560
|
+
|
|
561
|
+
| Subpath | Contents |
|
|
562
|
+
| ---------------------------------------------- | ---------------------------------------------------------------- |
|
|
563
|
+
| `@codefast/di` | Public façade — tokens, `Container`, modules, decorators, errors |
|
|
564
|
+
| `@codefast/di/container` | `Container` interface and related types |
|
|
565
|
+
| `@codefast/di/token` | `token()`, `Token<Value>`, `TokenValue` |
|
|
566
|
+
| `@codefast/di/binding` | `BindingBuilder`, binding type definitions |
|
|
567
|
+
| `@codefast/di/binding-select` | Binding selection internals (`filterMatchingBindings`, …) |
|
|
568
|
+
| `@codefast/di/module` | `Module`, `AsyncModule`, module builders |
|
|
569
|
+
| `@codefast/di/decorators/inject` | `inject`, `optional`, `injectAll`, `isInjectionDescriptor` |
|
|
570
|
+
| `@codefast/di/decorators/injectable` | `@injectable`, `getAutoRegistered` |
|
|
571
|
+
| `@codefast/di/decorators/lifecycle-decorators` | `@postConstruct`, `@preDestroy` |
|
|
572
|
+
| `@codefast/di/constraints` | `whenParentIs`, `whenAnyAncestorIs`, `whenTargetTagged` |
|
|
573
|
+
| `@codefast/di/registry` | `BindingRegistry` |
|
|
574
|
+
| `@codefast/di/resolver` | `DependencyResolver` internals |
|
|
575
|
+
| `@codefast/di/scope` | `ScopeManager` |
|
|
576
|
+
| `@codefast/di/scope-validation` | `validateScopeRules` |
|
|
577
|
+
| `@codefast/di/lifecycle` | Activation/deactivation runners |
|
|
578
|
+
| `@codefast/di/dependency-graph` | Dependency-edge collection helpers |
|
|
579
|
+
| `@codefast/di/inspector` | `ContainerInspector`, snapshot + graph types |
|
|
580
|
+
| `@codefast/di/errors` | Full `DiError` hierarchy |
|
|
581
|
+
| `@codefast/di/environment` | `isDevelopmentOrTestEnvironment`, `isProductionEnvironment` |
|
|
582
|
+
| `@codefast/di/metadata/*` | Metadata keys, types, readers, parameter registry |
|
|
550
583
|
|
|
551
584
|
See `package.json → exports` for the authoritative list.
|
|
552
585
|
|
|
@@ -559,14 +592,17 @@ This package lives in the [Codefast monorepo](https://github.com/codefastlabs/co
|
|
|
559
592
|
```bash
|
|
560
593
|
pnpm --filter @codefast/di build
|
|
561
594
|
pnpm --filter @codefast/di test
|
|
595
|
+
pnpm --filter @codefast/di check-types
|
|
562
596
|
```
|
|
563
597
|
|
|
564
598
|
---
|
|
565
599
|
|
|
566
600
|
## License
|
|
567
601
|
|
|
568
|
-
[MIT](https://opensource.org/licenses/MIT) — see
|
|
602
|
+
[MIT](https://opensource.org/licenses/MIT) — see [`package.json`](./package.json).
|
|
603
|
+
|
|
604
|
+
---
|
|
569
605
|
|
|
570
606
|
## Changelog
|
|
571
607
|
|
|
572
|
-
|
|
608
|
+
See [CHANGELOG.md](./CHANGELOG.md) for the full version history. Releases are also published on [npm](https://www.npmjs.com/package/@codefast/di?activeTab=versions).
|