@codefast/di 0.5.0-canary.5 → 1.0.0-canary.7
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 +4 -0
- package/README.md +67 -499
- package/dist/binding-scope.d.ts +10 -0
- package/dist/binding-scope.d.ts.map +1 -0
- package/dist/binding-scope.js +24 -0
- package/dist/binding-scope.js.map +1 -0
- package/dist/binding-select.d.ts +16 -0
- package/dist/binding-select.d.ts.map +1 -0
- package/dist/binding-select.js +130 -0
- package/dist/binding-select.js.map +1 -0
- package/dist/binding.d.ts +194 -0
- package/dist/binding.d.ts.map +1 -0
- package/dist/binding.js +46 -0
- package/dist/binding.js.map +1 -0
- package/dist/constraints.d.ts +51 -0
- package/dist/constraints.d.ts.map +1 -0
- package/dist/constraints.js +88 -0
- package/dist/constraints.js.map +1 -0
- package/dist/{constructor-type.d.mts → constructor-type.d.ts} +3 -5
- package/dist/constructor-type.d.ts.map +1 -0
- package/dist/constructor-type.js +2 -0
- package/dist/constructor-type.js.map +1 -0
- package/dist/container.d.ts +58 -0
- package/dist/container.d.ts.map +1 -0
- package/dist/container.js +853 -0
- package/dist/container.js.map +1 -0
- package/dist/decorators/inject.d.ts +46 -0
- package/dist/decorators/inject.d.ts.map +1 -0
- package/dist/decorators/inject.js +145 -0
- package/dist/decorators/inject.js.map +1 -0
- package/dist/decorators/injectable.d.ts +29 -0
- package/dist/decorators/injectable.d.ts.map +1 -0
- package/dist/decorators/injectable.js +53 -0
- package/dist/decorators/injectable.js.map +1 -0
- package/dist/decorators/lifecycle-decorators.d.ts +9 -0
- package/dist/decorators/lifecycle-decorators.d.ts.map +1 -0
- package/dist/decorators/lifecycle-decorators.js +40 -0
- package/dist/decorators/lifecycle-decorators.js.map +1 -0
- package/dist/dependency-graph.d.ts +40 -0
- package/dist/dependency-graph.d.ts.map +1 -0
- package/dist/dependency-graph.js +66 -0
- package/dist/dependency-graph.js.map +1 -0
- package/dist/environment.d.ts +51 -0
- package/dist/environment.d.ts.map +1 -0
- package/dist/environment.js +100 -0
- package/dist/environment.js.map +1 -0
- package/dist/errors.d.ts +150 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +197 -0
- package/dist/errors.js.map +1 -0
- package/dist/graph-adapters/cytoscape.d.ts +33 -0
- package/dist/graph-adapters/cytoscape.d.ts.map +1 -0
- package/dist/graph-adapters/cytoscape.js +30 -0
- package/dist/graph-adapters/cytoscape.js.map +1 -0
- package/dist/graph-adapters/dot.d.ts +6 -0
- package/dist/graph-adapters/dot.d.ts.map +1 -0
- package/dist/graph-adapters/dot.js +18 -0
- package/dist/graph-adapters/dot.js.map +1 -0
- package/dist/graph-adapters/reactflow.d.ts +38 -0
- package/dist/graph-adapters/reactflow.d.ts.map +1 -0
- package/dist/graph-adapters/reactflow.js +23 -0
- package/dist/graph-adapters/reactflow.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/inspector.d.ts +44 -0
- package/dist/inspector.d.ts.map +1 -0
- package/dist/inspector.js +82 -0
- package/dist/inspector.js.map +1 -0
- package/dist/lifecycle.d.ts +21 -0
- package/dist/lifecycle.d.ts.map +1 -0
- package/dist/lifecycle.js +174 -0
- package/dist/lifecycle.js.map +1 -0
- package/dist/metadata/{metadata-keys.d.mts → metadata-keys.d.ts} +5 -7
- package/dist/metadata/metadata-keys.d.ts.map +1 -0
- package/dist/metadata/metadata-keys.js +25 -0
- package/dist/metadata/metadata-keys.js.map +1 -0
- package/dist/metadata/metadata-reader-token.d.ts +7 -0
- package/dist/metadata/metadata-reader-token.d.ts.map +1 -0
- package/dist/metadata/metadata-reader-token.js +6 -0
- package/dist/metadata/metadata-reader-token.js.map +1 -0
- package/dist/metadata/metadata-types.d.ts +48 -0
- package/dist/metadata/metadata-types.d.ts.map +1 -0
- package/dist/metadata/metadata-types.js +2 -0
- package/dist/metadata/metadata-types.js.map +1 -0
- package/dist/metadata/symbol-metadata-reader.d.ts +20 -0
- package/dist/metadata/symbol-metadata-reader.d.ts.map +1 -0
- package/dist/metadata/symbol-metadata-reader.js +34 -0
- package/dist/metadata/symbol-metadata-reader.js.map +1 -0
- package/dist/module.d.ts +60 -0
- package/dist/module.d.ts.map +1 -0
- package/dist/module.js +47 -0
- package/dist/module.js.map +1 -0
- package/dist/registry.d.ts +41 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +208 -0
- package/dist/registry.js.map +1 -0
- package/dist/resolve-options.d.ts +19 -0
- package/dist/resolve-options.d.ts.map +1 -0
- package/dist/resolve-options.js +32 -0
- package/dist/resolve-options.js.map +1 -0
- package/dist/resolver.d.ts +90 -0
- package/dist/resolver.d.ts.map +1 -0
- package/dist/resolver.js +1208 -0
- package/dist/resolver.js.map +1 -0
- package/dist/scope.d.ts +25 -0
- package/dist/scope.d.ts.map +1 -0
- package/dist/scope.js +61 -0
- package/dist/scope.js.map +1 -0
- package/dist/token.d.ts +23 -0
- package/dist/token.d.ts.map +1 -0
- package/dist/token.js +25 -0
- package/dist/token.js.map +1 -0
- package/dist/types.d.ts +90 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +89 -80
- package/dist/binding-scope.d.mts +0 -13
- package/dist/binding-scope.mjs +0 -21
- package/dist/binding-select.d.mts +0 -19
- package/dist/binding-select.mjs +0 -72
- package/dist/binding.d.mts +0 -191
- package/dist/binding.mjs +0 -36
- package/dist/constraints.d.mts +0 -55
- package/dist/constraints.mjs +0 -87
- package/dist/constructor-type.mjs +0 -1
- package/dist/container.d.mts +0 -62
- package/dist/container.mjs +0 -741
- package/dist/decorators/inject.d.mts +0 -49
- package/dist/decorators/inject.mjs +0 -136
- package/dist/decorators/injectable.d.mts +0 -32
- package/dist/decorators/injectable.mjs +0 -57
- package/dist/decorators/lifecycle-decorators.d.mts +0 -11
- package/dist/decorators/lifecycle-decorators.mjs +0 -38
- package/dist/dependency-graph.d.mts +0 -43
- package/dist/dependency-graph.mjs +0 -63
- package/dist/environment.d.mts +0 -55
- package/dist/environment.mjs +0 -98
- package/dist/errors.d.mts +0 -153
- package/dist/errors.mjs +0 -197
- package/dist/graph-adapters/cytoscape.d.mts +0 -36
- package/dist/graph-adapters/cytoscape.mjs +0 -26
- package/dist/graph-adapters/dot.d.mts +0 -9
- package/dist/graph-adapters/dot.mjs +0 -20
- package/dist/graph-adapters/reactflow.d.mts +0 -41
- package/dist/graph-adapters/reactflow.mjs +0 -29
- package/dist/index.d.mts +0 -18
- package/dist/index.mjs +0 -12
- package/dist/inspector.d.mts +0 -48
- package/dist/inspector.mjs +0 -71
- package/dist/lifecycle.d.mts +0 -25
- package/dist/lifecycle.mjs +0 -118
- package/dist/metadata/metadata-keys.mjs +0 -27
- package/dist/metadata/metadata-reader-token.d.mts +0 -10
- package/dist/metadata/metadata-reader-token.mjs +0 -8
- package/dist/metadata/metadata-types.d.mts +0 -51
- package/dist/metadata/metadata-types.mjs +0 -1
- package/dist/metadata/symbol-metadata-reader.d.mts +0 -23
- package/dist/metadata/symbol-metadata-reader.mjs +0 -29
- package/dist/module.d.mts +0 -62
- package/dist/module.mjs +0 -42
- package/dist/registry.d.mts +0 -45
- package/dist/registry.mjs +0 -163
- package/dist/resolve-options.d.mts +0 -22
- package/dist/resolve-options.mjs +0 -28
- package/dist/resolver.d.mts +0 -94
- package/dist/resolver.mjs +0 -767
- package/dist/scope.d.mts +0 -28
- package/dist/scope.mjs +0 -58
- package/dist/token.d.mts +0 -25
- package/dist/token.mjs +0 -22
- package/dist/types.d.mts +0 -92
- package/dist/types.mjs +0 -1
package/README.md
CHANGED
|
@@ -1,96 +1,31 @@
|
|
|
1
1
|
# @codefast/di
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
---
|
|
11
|
-
|
|
12
|
-
## Table of Contents
|
|
13
|
-
|
|
14
|
-
- [Why @codefast/di](#why-codefastdi)
|
|
15
|
-
- [Requirements](#requirements)
|
|
16
|
-
- [Installation](#installation)
|
|
17
|
-
- [Quick Start](#quick-start)
|
|
18
|
-
- [Core Concepts](#core-concepts)
|
|
19
|
-
- [Tokens](#tokens)
|
|
20
|
-
- [Bindings](#bindings)
|
|
21
|
-
- [Strategies](#strategies)
|
|
22
|
-
- [Scopes](#scopes)
|
|
23
|
-
- [Constraints](#constraints)
|
|
24
|
-
- [Lifecycle hooks](#lifecycle-hooks)
|
|
25
|
-
- [Decorators](#decorators)
|
|
26
|
-
- [`@injectable`](#injectable)
|
|
27
|
-
- [`inject` / `optional` / `injectAll`](#inject--optional--injectall)
|
|
28
|
-
- [Accessor injection](#accessor-injection)
|
|
29
|
-
- [`@postConstruct` / `@preDestroy`](#postconstruct--predestroy)
|
|
30
|
-
- [Auto-registration](#auto-registration)
|
|
31
|
-
- [Container](#container)
|
|
32
|
-
- [Resolution](#resolution)
|
|
33
|
-
- [Async resolution](#async-resolution)
|
|
34
|
-
- [Container API surface](#container-api-surface)
|
|
35
|
-
- [Rebinding and unbinding](#rebinding-and-unbinding)
|
|
36
|
-
- [Child containers](#child-containers)
|
|
37
|
-
- [Validation](#validation)
|
|
38
|
-
- [Introspection](#introspection)
|
|
39
|
-
- [Disposal](#disposal)
|
|
40
|
-
- [Modules](#modules)
|
|
41
|
-
- [Errors](#errors)
|
|
42
|
-
- [Package exports](#package-exports)
|
|
43
|
-
- [Contributing](#contributing)
|
|
44
|
-
- [License](#license)
|
|
45
|
-
- [Changelog](#changelog)
|
|
46
|
-
|
|
47
|
-
---
|
|
48
|
-
|
|
49
|
-
## Why @codefast/di
|
|
50
|
-
|
|
51
|
-
`@codefast/di` is a small IoC container designed for applications that compile to ESM and want strong typing without metadata reflection tricks.
|
|
52
|
-
|
|
53
|
-
- **Typed tokens.** `Token<Value>` flows through every `bind → resolve` path; the return type of `resolve()` is the one you registered.
|
|
54
|
-
- **Native Stage 3 decorators.** `@injectable`, `inject`, `optional`, `@postConstruct`, `@preDestroy` record metadata for the resolver. Implementations use a `WeakMap` and mirror into `Symbol.metadata` when the decorator runtime supplies it — no `reflect-metadata`, no `experimentalDecorators`.
|
|
55
|
-
- **Fluent binding API.** Constants, classes, sync/async factories, resolved factories, aliases, named/tagged/predicate constraints, activation + deactivation hooks.
|
|
56
|
-
- **Module system.** `Module` / `AsyncModule` bundle bindings into reusable units that can be loaded, unloaded, and re-used across containers.
|
|
57
|
-
- **Scope checks.** Call `validate()` to detect captive dependencies (for example a `singleton` depending on a `scoped` or `transient` binding).
|
|
58
|
-
- **Async resolution.** Dedupes in-flight async singleton construction and supports `await using` for automatic cleanup.
|
|
59
|
-
- **Tree-shakeable subpaths.** Import only the surface you need.
|
|
60
|
-
|
|
61
|
-
---
|
|
3
|
+
Lightweight, type-safe dependency injection for modern TypeScript — built on TC39 Stage 3 decorators, with no `reflect-metadata` and no `experimentalDecorators`.
|
|
62
4
|
|
|
63
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/@codefast/di)
|
|
6
|
+
[](https://github.com/codefastlabs/codefast/blob/main/LICENSE)
|
|
64
7
|
|
|
65
|
-
-
|
|
66
|
-
-
|
|
8
|
+
- **Typed tokens.** `Token<Value>` flows through every `bind → resolve` path; `resolve()` returns the type you registered.
|
|
9
|
+
- **Native Stage 3 decorators.** `@injectable`, `inject`, `optional`, `@postConstruct`, `@preDestroy` — no runtime reflection tricks.
|
|
10
|
+
- **Fluent bindings.** Constants, classes, sync/async factories, aliases, named/tagged/predicate constraints, lifecycle hooks.
|
|
11
|
+
- **Scopes with validation.** `singleton` / `scoped` / `transient`, plus `validate()` to catch captive dependencies early.
|
|
12
|
+
- **Modules.** Bundle bindings into reusable units; load, unload, and compose them across containers.
|
|
13
|
+
- **Async-aware.** Async factories, deduped async singleton construction, and `await using` disposal.
|
|
67
14
|
|
|
68
|
-
|
|
15
|
+
> Currently published as `1.0.0-canary.x` pre-releases on the way to a stable 1.0.
|
|
69
16
|
|
|
70
|
-
|
|
71
|
-
{
|
|
72
|
-
"compilerOptions": {
|
|
73
|
-
"target": "ES2022",
|
|
74
|
-
"lib": ["ES2022"],
|
|
75
|
-
"moduleResolution": "bundler"
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
```
|
|
17
|
+
## Requirements
|
|
79
18
|
|
|
80
|
-
|
|
19
|
+
- **Node.js 26 or later — required.** The container uses the native `Map.prototype.getOrInsert` (ES2025), which ships in Node 26+. On older Node versions the package throws at runtime.
|
|
20
|
+
- **TypeScript 5.2+** with native Stage 3 decorators — leave `experimentalDecorators` **off** (it is off by default).
|
|
81
21
|
|
|
82
22
|
## Installation
|
|
83
23
|
|
|
84
24
|
```bash
|
|
85
25
|
pnpm add @codefast/di
|
|
86
|
-
# or
|
|
87
|
-
npm install @codefast/di
|
|
88
|
-
# or
|
|
89
|
-
yarn add @codefast/di
|
|
26
|
+
# or: npm install @codefast/di
|
|
90
27
|
```
|
|
91
28
|
|
|
92
|
-
---
|
|
93
|
-
|
|
94
29
|
## Quick Start
|
|
95
30
|
|
|
96
31
|
```typescript
|
|
@@ -113,254 +48,101 @@ class CheckoutService {
|
|
|
113
48
|
|
|
114
49
|
const container = Container.create();
|
|
115
50
|
|
|
116
|
-
// Production wiring
|
|
117
51
|
container.bind(LoggerToken).toConstantValue({
|
|
118
|
-
info: (message) => console.log(`[
|
|
52
|
+
info: (message) => console.log(`[app] ${message}`),
|
|
119
53
|
});
|
|
120
54
|
container.bind(CheckoutService).toSelf();
|
|
121
55
|
|
|
122
56
|
container.resolve(CheckoutService).complete("ORD-1001");
|
|
123
57
|
|
|
124
|
-
//
|
|
58
|
+
// Swap infrastructure without touching business classes
|
|
125
59
|
container.rebind(LoggerToken).toConstantValue({
|
|
126
60
|
info: (message) => console.log(`[test] ${message}`),
|
|
127
61
|
});
|
|
128
|
-
|
|
129
|
-
container.resolve(CheckoutService).complete("ORD-1002");
|
|
130
62
|
```
|
|
131
63
|
|
|
132
|
-
`@injectable([...])` lists constructor dependencies in parameter order
|
|
133
|
-
|
|
134
|
-
---
|
|
135
|
-
|
|
136
|
-
## Core Concepts
|
|
137
|
-
|
|
138
|
-
| Concept | Description |
|
|
139
|
-
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
140
|
-
| **Token** | Branded identifier pairing a name string with a TypeScript type. Tokens compare by reference — always reuse the same `const`. |
|
|
141
|
-
| **Binding** | Associates a token (or constructor) with a value strategy (constant / class / factory / alias), a scope, optional constraints, and lifecycle hooks. |
|
|
142
|
-
| **Container** | Holds a `BindingRegistry` and a `ScopeManager`; resolves bindings through the `DependencyResolver`. Supports child containers and async disposal. |
|
|
143
|
-
| **Scope** | Instance lifetime: `singleton` (one per root container, shared with children), `scoped` (one per child container), `transient` (new on every resolve). |
|
|
144
|
-
| **Module** | Reusable bundle of bindings. Loaded once per container; loading the same module twice is a no-op. |
|
|
145
|
-
| **Metadata** | `@injectable([...])` stores constructor parameter descriptors so the resolver knows what to inject at each index. |
|
|
146
|
-
|
|
147
|
-
---
|
|
64
|
+
`@injectable([...])` lists constructor dependencies in parameter order — no parameter-type reflection, so it works in any ESM runtime that supports Stage 3 decorators.
|
|
148
65
|
|
|
149
66
|
## Tokens
|
|
150
67
|
|
|
68
|
+
A token pairs a name with a TypeScript type. Tokens compare by reference — always reuse the same `const`.
|
|
69
|
+
|
|
151
70
|
```typescript
|
|
152
71
|
import { token } from "@codefast/di";
|
|
153
72
|
|
|
154
73
|
const DbToken = token<Database>("Database");
|
|
155
|
-
const CacheToken = token<Cache>("Cache");
|
|
156
74
|
```
|
|
157
75
|
|
|
158
|
-
|
|
159
|
-
- Tokens use reference equality; two tokens with the same name string are still two distinct keys.
|
|
160
|
-
- A class constructor can itself be a key:
|
|
161
|
-
|
|
162
|
-
```typescript
|
|
163
|
-
container.bind(UserService).toSelf();
|
|
164
|
-
container.resolve(UserService); // returns UserService
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
---
|
|
76
|
+
A class constructor can also be a key directly: `container.bind(UserService).toSelf()` then `container.resolve(UserService)`.
|
|
168
77
|
|
|
169
78
|
## Bindings
|
|
170
79
|
|
|
171
|
-
Start with `container.bind(key)
|
|
80
|
+
Start with `container.bind(key)`, chain a strategy, then optional constraints and a scope.
|
|
172
81
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
|
176
|
-
|
|
|
177
|
-
| `.
|
|
178
|
-
| `.
|
|
179
|
-
| `.
|
|
180
|
-
| `.
|
|
181
|
-
| `.
|
|
182
|
-
| `.toResolved(factory, deps)` | Factory with a typed dependency tuple; dependencies are resolved in order. |
|
|
183
|
-
| `.toResolvedAsync(factory, deps)` | Like `toResolved`, but the factory returns a `Promise`. |
|
|
184
|
-
| `.toAlias(targetToken)` | Redirect resolution to another token; the alias follows the target’s resolution. |
|
|
82
|
+
| Strategy | Produces |
|
|
83
|
+
| --------------------------------- | ----------------------------------------------------------- |
|
|
84
|
+
| `.toConstantValue(value)` | A fixed value (always singleton) |
|
|
85
|
+
| `.toSelf()` / `.to(Constructor)` | A class instance built from `@injectable` metadata |
|
|
86
|
+
| `.toDynamic(factory)` | Sync factory `(ctx: ResolutionContext) => Value` |
|
|
87
|
+
| `.toDynamicAsync(factory)` | Async factory — resolve with the `*Async` container methods |
|
|
88
|
+
| `.toResolved(factory, deps)` | Factory with a typed dependency tuple, resolved in order |
|
|
89
|
+
| `.toResolvedAsync(factory, deps)` | Same, returning a `Promise` |
|
|
90
|
+
| `.toAlias(targetToken)` | Redirects resolution to another token |
|
|
185
91
|
|
|
186
92
|
```typescript
|
|
187
|
-
container.bind(
|
|
188
|
-
|
|
189
|
-
container.bind(UserRepository).toSelf().singleton();
|
|
190
|
-
container.bind(UserServiceToken).to(UserService).transient();
|
|
191
|
-
|
|
192
|
-
container.bind(DbToken).toDynamic((ctx) => {
|
|
193
|
-
const config = ctx.resolve(AppConfigToken);
|
|
194
|
-
return new Database(config.dbUrl);
|
|
195
|
-
});
|
|
93
|
+
container.bind(ConfigToken).toConstantValue({ dbUrl: "postgres://…" });
|
|
196
94
|
|
|
197
95
|
container.bind(DbToken).toDynamicAsync(async (ctx) => {
|
|
198
|
-
const
|
|
199
|
-
const db = new Database(config.dbUrl);
|
|
96
|
+
const db = new Database(ctx.resolve(ConfigToken).dbUrl);
|
|
200
97
|
await db.connect();
|
|
201
98
|
return db;
|
|
202
99
|
});
|
|
203
100
|
|
|
204
101
|
container
|
|
205
102
|
.bind(UserServiceToken)
|
|
206
|
-
.toResolved((
|
|
207
|
-
|
|
208
|
-
container.bind(MetricsToken).toResolvedAsync(async (db) => new MetricsCollector(db), [DbToken] as const);
|
|
209
|
-
|
|
210
|
-
container.bind(LegacyServiceToken).toAlias(NewServiceToken);
|
|
103
|
+
.toResolved((repo, config) => new UserService(repo, config), [UserRepository, ConfigToken] as const);
|
|
211
104
|
```
|
|
212
105
|
|
|
213
106
|
### Scopes
|
|
214
107
|
|
|
215
|
-
|
|
|
216
|
-
| -------------- |
|
|
217
|
-
| `.singleton()` | One instance per root container, shared with
|
|
218
|
-
| `.scoped()` | One instance per child container
|
|
219
|
-
| `.transient()` | New instance on every resolution
|
|
220
|
-
|
|
221
|
-
`toConstantValue` does not expose scope chaining: constants are always treated as singletons internally.
|
|
108
|
+
| Scope | Lifetime |
|
|
109
|
+
| -------------- | ---------------------------------------------------------- |
|
|
110
|
+
| `.singleton()` | One instance per root container, shared with children |
|
|
111
|
+
| `.scoped()` | One instance per child container (request-scoped services) |
|
|
112
|
+
| `.transient()` | New instance on every resolution — the default |
|
|
222
113
|
|
|
223
114
|
```typescript
|
|
224
|
-
container.bind(
|
|
115
|
+
container.bind(DbToken).toDynamic(createDb).singleton();
|
|
225
116
|
container.bind(RequestContextToken).toSelf().scoped();
|
|
226
|
-
container.bind(QueryBuilderToken).toSelf().transient();
|
|
227
117
|
```
|
|
228
118
|
|
|
229
119
|
### Constraints
|
|
230
120
|
|
|
231
|
-
Multiple bindings can share
|
|
232
|
-
|
|
233
|
-
**Named**
|
|
121
|
+
Multiple bindings can share one token; a constraint picks the winner at resolution time.
|
|
234
122
|
|
|
235
123
|
```typescript
|
|
236
124
|
container.bind(LoggerToken).toConstantValue(fileLogger).whenNamed("file");
|
|
237
|
-
container.bind(LoggerToken).toConstantValue(consoleLogger).whenNamed("console");
|
|
238
|
-
|
|
239
|
-
container.resolve(LoggerToken, { name: "file" });
|
|
240
|
-
```
|
|
241
|
-
|
|
242
|
-
**Tagged** — the tag option is a tuple `[tag, value]`:
|
|
243
|
-
|
|
244
|
-
```typescript
|
|
245
125
|
container.bind(StorageToken).to(S3Storage).whenTagged("provider", "s3");
|
|
246
|
-
container.bind(StorageToken).to(LocalStorage).whenTagged("provider", "local");
|
|
247
126
|
|
|
127
|
+
container.resolve(LoggerToken, { name: "file" });
|
|
248
128
|
container.resolve(StorageToken, { tag: ["provider", "s3"] });
|
|
249
129
|
```
|
|
250
130
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
`.whenDefault()` is a documentation-only marker — it is a no-op at runtime. A binding without any constraint already participates in resolution when no `name` / `tag` option is provided. Use it to signal intent when mixing constrained and unconstrained bindings for the same token.
|
|
254
|
-
|
|
255
|
-
**Predicate** — inspect the full resolution graph:
|
|
256
|
-
|
|
257
|
-
```typescript
|
|
258
|
-
import { whenAnyAncestorIs, whenParentIs, whenParentTagged } from "@codefast/di/constraints";
|
|
259
|
-
|
|
260
|
-
container.bind(LoggerToken).toConstantValue(verboseLogger).when(whenParentIs(DiagnosticsService));
|
|
261
|
-
```
|
|
262
|
-
|
|
263
|
-
Built-in predicates (all from `@codefast/di/constraints`):
|
|
264
|
-
|
|
265
|
-
| Helper | Matches when … |
|
|
266
|
-
| ----------------------------------- | -------------------------------------------------------------------------- |
|
|
267
|
-
| `whenParentIs(key)` | The direct parent binding was registered for `key`. |
|
|
268
|
-
| `whenNoParentIs(key)` | There is no parent, or the parent is not registered for `key`. |
|
|
269
|
-
| `whenAnyAncestorIs(key)` | Any ancestor on the resolution stack was registered for `key`. |
|
|
270
|
-
| `whenNoAncestorIs(key)` | No ancestor was registered for `key`. |
|
|
271
|
-
| `whenParentNamed(name)` | The immediate parent binding’s slot name is `name`. |
|
|
272
|
-
| `whenAnyAncestorNamed(name)` | Some ancestor’s slot name is `name`. |
|
|
273
|
-
| `whenParentTagged(tag, value)` | The immediate parent binding carries `tag` with `value` (via `Object.is`). |
|
|
274
|
-
| `whenAnyAncestorTagged(tag, value)` | Some ancestor carries `tag` with `value`. |
|
|
275
|
-
|
|
276
|
-
For anything else, pass a custom `(ctx: ConstraintContext) => boolean` to `.when(predicate)`.
|
|
277
|
-
|
|
278
|
-
### Lifecycle hooks
|
|
279
|
-
|
|
280
|
-
```typescript
|
|
281
|
-
container
|
|
282
|
-
.bind(DbToken)
|
|
283
|
-
.toDynamicAsync(async (ctx) => new Database(ctx.resolve(ConfigToken)))
|
|
284
|
-
.singleton()
|
|
285
|
-
.onActivation(async (ctx, instance) => {
|
|
286
|
-
await instance.connect();
|
|
287
|
-
return instance;
|
|
288
|
-
})
|
|
289
|
-
.onDeactivation(async (instance) => {
|
|
290
|
-
await instance.disconnect();
|
|
291
|
-
});
|
|
292
|
-
```
|
|
293
|
-
|
|
294
|
-
- `onActivation(ctx, instance)` on the **binding** runs after `@postConstruct` and before **container-level** `onActivation` handlers registered with `container.onActivation(token, …)`. See `LifecycleManager.runActivation`.
|
|
295
|
-
- `onDeactivation(instance)` on the **binding** runs after **container-level** `onDeactivation` hooks and before `@preDestroy`. See `LifecycleManager.runDeactivation`.
|
|
296
|
-
|
|
297
|
-
---
|
|
131
|
+
For graph-aware selection, pass a predicate to `.when(...)` — helpers like `whenParentIs`, `whenAnyAncestorNamed`, and `whenParentTagged` ship in `@codefast/di/constraints`.
|
|
298
132
|
|
|
299
133
|
## Decorators
|
|
300
134
|
|
|
301
|
-
All decorators use TC39 Stage 3 syntax.
|
|
302
|
-
|
|
303
|
-
### `@injectable`
|
|
304
|
-
|
|
305
|
-
Registers constructor dependencies in **parameter order** (index `0` → first constructor parameter). Each entry is either a `Token` / `Constructor` or an `InjectionDescriptor` from `inject()` / `optional()` / `injectAll()`.
|
|
135
|
+
All decorators use TC39 Stage 3 syntax. `inject()` wraps a dependency with resolve options, `optional()` resolves to `undefined` when unbound, and `injectAll()` collects every matching binding into an array.
|
|
306
136
|
|
|
307
137
|
```typescript
|
|
308
|
-
import { inject, injectable, optional,
|
|
309
|
-
|
|
310
|
-
const ConfigToken = token<AppConfig>("AppConfig");
|
|
311
|
-
const CacheToken = token<Cache>("Cache");
|
|
138
|
+
import { inject, injectable, optional, postConstruct, preDestroy } from "@codefast/di";
|
|
312
139
|
|
|
313
|
-
@injectable([
|
|
314
|
-
class
|
|
140
|
+
@injectable([DbToken, optional(CacheToken)])
|
|
141
|
+
class UserRepository {
|
|
315
142
|
constructor(
|
|
316
|
-
private readonly
|
|
143
|
+
private readonly db: Database,
|
|
317
144
|
private readonly cache?: Cache,
|
|
318
145
|
) {}
|
|
319
|
-
}
|
|
320
|
-
```
|
|
321
|
-
|
|
322
|
-
Keep the metadata array aligned with the constructor parameter list. Too few entries means `new` receives `undefined` for missing positions; too many adds unused metadata entries.
|
|
323
|
-
|
|
324
|
-
### `inject` / `optional` / `injectAll`
|
|
325
|
-
|
|
326
|
-
```typescript
|
|
327
|
-
inject(LoggerToken, { name: "console" });
|
|
328
|
-
inject(StorageToken, { tag: ["provider", "s3"] });
|
|
329
|
-
optional(CacheToken);
|
|
330
|
-
injectAll(PluginToken);
|
|
331
|
-
```
|
|
332
|
-
|
|
333
|
-
- `inject(token, options?)` — required. Throws `TokenNotBoundError` when the dependency cannot be resolved.
|
|
334
|
-
- `optional(token, options?)` — optional. Resolves to `undefined` when unbound.
|
|
335
|
-
- `injectAll(token, options?)` — resolves every matching binding into an array (`Value[]`), applying `name` / `tag` filters when provided.
|
|
336
|
-
|
|
337
|
-
### Accessor injection
|
|
338
|
-
|
|
339
|
-
`inject` doubles as a TC39 accessor-field decorator for post-construction property injection:
|
|
340
|
-
|
|
341
|
-
```typescript
|
|
342
|
-
@injectable()
|
|
343
|
-
class Controller {
|
|
344
|
-
@inject(LoggerToken) accessor logger!: Logger;
|
|
345
|
-
}
|
|
346
|
-
```
|
|
347
|
-
|
|
348
|
-
The container injects the accessor after construction, so accessor fields do not use slots in the `@injectable([...])` arity list.
|
|
349
|
-
|
|
350
|
-
### `@postConstruct` / `@preDestroy`
|
|
351
|
-
|
|
352
|
-
Method decorators that hook into the instance lifecycle.
|
|
353
|
-
|
|
354
|
-
**Activation** (after `new`): `@postConstruct` → binding `onActivation` → `container.onActivation(token)` hooks (in registration order).
|
|
355
|
-
|
|
356
|
-
**Deactivation** (on eviction): `container.onDeactivation(token)` hooks → binding `onDeactivation` → `@preDestroy`.
|
|
357
|
-
|
|
358
|
-
```typescript
|
|
359
|
-
import { postConstruct, preDestroy } from "@codefast/di";
|
|
360
|
-
|
|
361
|
-
@injectable([DbToken])
|
|
362
|
-
class UserRepository {
|
|
363
|
-
constructor(private readonly db: Database) {}
|
|
364
146
|
|
|
365
147
|
@postConstruct()
|
|
366
148
|
async init(): Promise<void> {
|
|
@@ -374,306 +156,92 @@ class UserRepository {
|
|
|
374
156
|
}
|
|
375
157
|
```
|
|
376
158
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
### Auto-registration
|
|
380
|
-
|
|
381
|
-
Pass an `AutoRegisterRegistry` from `createAutoRegisterRegistry()` into `@injectable` options. Each decorated class registers itself with an optional `scope` (`"transient"` by default).
|
|
159
|
+
`inject` also works as an accessor decorator for post-construction property injection:
|
|
382
160
|
|
|
383
161
|
```typescript
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
const autoRegister = createAutoRegisterRegistry();
|
|
388
|
-
|
|
389
|
-
@injectable([DbToken], { autoRegister, scope: "singleton" })
|
|
390
|
-
class UserRepository {
|
|
391
|
-
constructor(private readonly db: Database) {}
|
|
162
|
+
@injectable()
|
|
163
|
+
class Controller {
|
|
164
|
+
@inject(LoggerToken) accessor logger!: Logger;
|
|
392
165
|
}
|
|
393
|
-
|
|
394
|
-
const container = Container.create();
|
|
395
|
-
container.loadAutoRegistered(autoRegister);
|
|
396
166
|
```
|
|
397
167
|
|
|
398
|
-
To bind manually, iterate `autoRegister.entries()` and call `container.bind(entry.target).toSelf()` (and apply the desired scope).
|
|
399
|
-
|
|
400
|
-
---
|
|
401
|
-
|
|
402
168
|
## Container
|
|
403
169
|
|
|
404
|
-
### Resolution
|
|
405
|
-
|
|
406
170
|
```typescript
|
|
407
171
|
const container = Container.create();
|
|
408
172
|
|
|
409
|
-
container.resolve(ServiceToken);
|
|
173
|
+
container.resolve(ServiceToken); // throws TokenNotBoundError when unbound
|
|
410
174
|
container.resolveOptional(CacheToken); // undefined when unbound
|
|
411
|
-
container.resolveAll(HandlerToken); //
|
|
175
|
+
container.resolveAll(HandlerToken); // every matching binding
|
|
412
176
|
|
|
413
|
-
container.
|
|
414
|
-
container.resolve(StorageToken, { tag: ["provider", "s3"] });
|
|
415
|
-
|
|
416
|
-
container.has(CacheToken);
|
|
417
|
-
container.has(LoggerToken, { name: "console" });
|
|
418
|
-
container.hasOwn(LoggerToken, { name: "console" });
|
|
419
|
-
```
|
|
420
|
-
|
|
421
|
-
`resolveAll()` and `ctx.resolveAll()` preserve the current resolution context (path + parent/ancestors stack), so `when(...)` predicates and scope checks behave the same as `resolve()`.
|
|
422
|
-
|
|
423
|
-
### Async resolution
|
|
424
|
-
|
|
425
|
-
Use the `*Async` variants when any binding in the resolution chain uses `toDynamicAsync`, `toResolvedAsync`, async `onActivation`, or async `@postConstruct`. Mixing async into a sync resolve throws `AsyncResolutionError`.
|
|
426
|
-
|
|
427
|
-
```typescript
|
|
428
|
-
const db = await container.resolveAsync(DbToken);
|
|
429
|
-
const handlers = await container.resolveAllAsync(HandlerToken);
|
|
430
|
-
const cache = await container.resolveOptionalAsync(CacheToken);
|
|
431
|
-
|
|
432
|
-
// Eagerly construct eligible singleton bindings
|
|
433
|
-
await container.initializeAsync();
|
|
177
|
+
const db = await container.resolveAsync(DbToken); // required for async bindings
|
|
434
178
|
```
|
|
435
179
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
| Area | Methods / properties |
|
|
439
|
-
| ------------------ | ----------------------------------------------------------------------------------------------------- |
|
|
440
|
-
| **Lifecycle** | `dispose()`, `[Symbol.asyncDispose]()`, `[Symbol.dispose]()` (throws `SyncDisposalNotSupportedError`) |
|
|
441
|
-
| **Bindings** | `bind`, `rebind`, `unbind`, `unbindAsync`, `unbindAll`, `unbindAllAsync` |
|
|
442
|
-
| **Modules** | `load`, `loadAsync`, `unload`, `unloadAsync`, `loadAutoRegistered` |
|
|
443
|
-
| **Global hooks** | `onActivation`, `onDeactivation` |
|
|
444
|
-
| **Resolve** | `resolve`, `resolveAsync`, `resolveOptional`, `resolveOptionalAsync`, `resolveAll`, `resolveAllAsync` |
|
|
445
|
-
| **Scopes / graph** | `createChild`, `validate`, `initializeAsync` |
|
|
446
|
-
| **Introspection** | `has`, `hasOwn`, `lookupBindings`, `inspect`, `generateDependencyGraph` |
|
|
447
|
-
| **State** | `isDisposed` |
|
|
448
|
-
|
|
449
|
-
### Rebinding and unbinding
|
|
450
|
-
|
|
451
|
-
```typescript
|
|
452
|
-
container.rebind(LoggerToken).toConstantValue(testLogger);
|
|
453
|
-
|
|
454
|
-
container.unbind(CacheToken); // sync deactivation only
|
|
455
|
-
await container.unbindAsync(CacheToken); // awaits async deactivation
|
|
456
|
-
|
|
457
|
-
container.unbindAll();
|
|
458
|
-
await container.unbindAllAsync();
|
|
459
|
-
```
|
|
180
|
+
Mixing an async binding into a sync `resolve()` throws `AsyncResolutionError` — use `resolveAsync` / `resolveAllAsync` / `resolveOptionalAsync` whenever the chain contains async work.
|
|
460
181
|
|
|
461
182
|
### Child containers
|
|
462
183
|
|
|
463
|
-
|
|
184
|
+
Children fall through to the parent's bindings and share its singleton cache, while `scoped` bindings get one instance per child — ideal for per-request wiring.
|
|
464
185
|
|
|
465
186
|
```typescript
|
|
466
187
|
const requestContainer = container.createChild();
|
|
467
|
-
|
|
468
|
-
requestContainer
|
|
469
|
-
.bind(RequestContextToken)
|
|
470
|
-
.toDynamic(() => req)
|
|
471
|
-
.scoped();
|
|
472
188
|
const service = requestContainer.resolve(RequestScopedService);
|
|
473
|
-
|
|
474
189
|
await requestContainer.dispose(); // releases scoped instances owned by this child
|
|
475
190
|
```
|
|
476
191
|
|
|
477
192
|
### Validation
|
|
478
193
|
|
|
479
|
-
`validate()`
|
|
194
|
+
`validate()` fails fast on captive dependencies — for example a `singleton` depending on a `scoped` or `transient` binding.
|
|
480
195
|
|
|
481
196
|
```typescript
|
|
482
197
|
container.validate(); // throws ScopeViolationError on the first violation
|
|
483
198
|
```
|
|
484
199
|
|
|
485
|
-
Call it after meaningful registry changes (or in tests) — the container does **not** auto-invoke `validate()` based on `NODE_ENV`.
|
|
486
|
-
|
|
487
|
-
> **Scope of checks.** Only `class`, `toResolved`, and `toResolvedAsync` bindings are inspected, because their dependency lists are statically declared. `toDynamic` and `toDynamicAsync` bindings are skipped — their factories have no declared deps and cannot be checked statically.
|
|
488
|
-
|
|
489
|
-
### Introspection
|
|
490
|
-
|
|
491
|
-
```typescript
|
|
492
|
-
import { toCytoscapeGraph } from "@codefast/di/graph-adapters/cytoscape";
|
|
493
|
-
import { toDotGraph } from "@codefast/di/graph-adapters/dot";
|
|
494
|
-
import { toReactFlowGraph } from "@codefast/di/graph-adapters/reactflow";
|
|
495
|
-
|
|
496
|
-
const snapshot = container.inspect();
|
|
497
|
-
const json = container.generateDependencyGraph({ includeParent: true });
|
|
498
|
-
const dot = toDotGraph(json);
|
|
499
|
-
|
|
500
|
-
const cytoscape = toCytoscapeGraph(json);
|
|
501
|
-
const reactflow = toReactFlowGraph(json);
|
|
502
|
-
```
|
|
503
|
-
|
|
504
|
-
`generateDependencyGraph` returns the canonical `ContainerGraphJson` (`nodes`, `edges`, `includesParent`). Adapters are pure converters; import them from `@codefast/di/graph-adapters/*`.
|
|
505
|
-
|
|
506
200
|
### Disposal
|
|
507
201
|
|
|
508
|
-
`Container` implements `AsyncDisposable`, so `await using` runs
|
|
202
|
+
`Container` implements `AsyncDisposable`, so `await using` runs deactivation hooks automatically:
|
|
509
203
|
|
|
510
204
|
```typescript
|
|
511
205
|
{
|
|
512
206
|
await using container = Container.create();
|
|
513
207
|
container.bind(DbToken).toDynamicAsync(connectDb).singleton().onDeactivation(disconnectDb);
|
|
514
|
-
|
|
515
208
|
const db = await container.resolveAsync(DbToken);
|
|
516
|
-
|
|
517
|
-
} // dispose() runs deactivation hooks for owned singletons
|
|
209
|
+
} // dispose() runs here
|
|
518
210
|
```
|
|
519
211
|
|
|
520
|
-
Synchronous `using` is rejected: `[Symbol.dispose]()` throws `SyncDisposalNotSupportedError`. Use `await using` or `await container.dispose()`.
|
|
521
|
-
|
|
522
|
-
---
|
|
523
|
-
|
|
524
212
|
## Modules
|
|
525
213
|
|
|
526
|
-
Modules bundle related bindings into reusable units.
|
|
527
|
-
|
|
528
|
-
Use the same fluent order everywhere (including inside modules): `bind(token).to*(…).when*(…)` then, when supported, `.singleton()` / `.scoped()` / `.transient()`.
|
|
529
|
-
|
|
530
|
-
- Register **multiple** implementations for one token with separate chains, e.g. `api.bind(T).to(A).whenNamed("a")` and `api.bind(T).to(B).whenNamed("b")`.
|
|
531
|
-
- **Last-wins** applies per slot (default vs named vs tag-set), matching the container API.
|
|
214
|
+
Modules bundle related bindings into reusable, stateless units.
|
|
532
215
|
|
|
533
216
|
```typescript
|
|
534
217
|
import { Container, Module } from "@codefast/di";
|
|
535
218
|
|
|
536
219
|
const InfrastructureModule = Module.create("Infra", (api) => {
|
|
537
220
|
api.bind(LoggerToken).toConstantValue(console);
|
|
538
|
-
api.bind(ConfigToken).toConstantValue(loadConfig());
|
|
539
221
|
});
|
|
540
222
|
|
|
541
223
|
const AppModule = Module.create("App", (api) => {
|
|
542
224
|
api.import(InfrastructureModule);
|
|
543
225
|
api.bind(UserRepository).toSelf().singleton();
|
|
544
|
-
api.bind(UserServiceToken).to(UserService).transient();
|
|
545
226
|
});
|
|
546
227
|
|
|
547
228
|
const container = Container.fromModules(AppModule);
|
|
548
229
|
```
|
|
549
230
|
|
|
550
|
-
`Module.
|
|
551
|
-
|
|
552
|
-
Async modules may `await` during setup (for example remote config):
|
|
553
|
-
|
|
554
|
-
```typescript
|
|
555
|
-
const DbModule = Module.createAsync("Database", async (api) => {
|
|
556
|
-
const config = await fetchRemoteConfig();
|
|
557
|
-
api.bind(DbToken).toConstantValue(await Database.connect(config.dbUrl));
|
|
558
|
-
});
|
|
559
|
-
|
|
560
|
-
const container = await Container.fromModulesAsync(DbModule, AppModule);
|
|
561
|
-
```
|
|
562
|
-
|
|
563
|
-
Load and unload on an existing container:
|
|
564
|
-
|
|
565
|
-
```typescript
|
|
566
|
-
container.load(InfrastructureModule, AppModule);
|
|
567
|
-
await container.loadAsync(DbModule);
|
|
568
|
-
|
|
569
|
-
container.unload(AppModule);
|
|
570
|
-
await container.unloadAsync(DbModule);
|
|
571
|
-
```
|
|
572
|
-
|
|
573
|
-
Re-loading a module that is already loaded increments an internal ref-count and does not re-register bindings; unloading decrements it and only removes bindings when the count reaches zero. Circular imports between modules are silently deduplicated — `CircularDependencyError` is only thrown for cycles in the dependency resolution graph (e.g. service A depends on service B which depends on A).
|
|
574
|
-
|
|
575
|
-
---
|
|
231
|
+
`Module.createAsync` supports awaiting during setup (remote config, connections); load those with `Container.fromModulesAsync` or `container.loadAsync`. Modules are ref-counted: re-loading is a no-op and `unload` only removes bindings once the count reaches zero.
|
|
576
232
|
|
|
577
233
|
## Errors
|
|
578
234
|
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
| Error class | `code` | Thrown when |
|
|
582
|
-
| ------------------------------- | ------------------------------- | ----------------------------------------------------------------------- |
|
|
583
|
-
| `AmbiguousBindingError` | `"AMBIGUOUS_BINDING"` | Multiple bindings matched without a single decisive constraint winner |
|
|
584
|
-
| `AsyncDeactivationError` | `"ASYNC_DEACTIVATION"` | Async `onDeactivation` reached through `unbind` / sync paths |
|
|
585
|
-
| `AsyncModuleLoadError` | `"ASYNC_MODULE_LOAD"` | Sync `load()` used with an `AsyncModule` |
|
|
586
|
-
| `AsyncResolutionError` | `"ASYNC_RESOLUTION"` | Async work required during a sync `resolve()` |
|
|
587
|
-
| `CircularDependencyError` | `"CIRCULAR_DEPENDENCY"` | Cycle in dependency or module graph |
|
|
588
|
-
| `DisposedContainerError` | `"DISPOSED_CONTAINER"` | Operation after `dispose()` |
|
|
589
|
-
| `InternalError` | `"INTERNAL_ERROR"` | Invariant violations (should not surface in correct consumer code) |
|
|
590
|
-
| `MissingContainerContextError` | `"MISSING_CONTAINER_CONTEXT"` | `@inject` accessor resolved without an active container |
|
|
591
|
-
| `MissingMetadataError` | `"MISSING_METADATA"` | Class resolution missing `@injectable()` metadata |
|
|
592
|
-
| `MissingScopeContextError` | `"MISSING_SCOPE_CONTEXT"` | `scoped` binding resolved without a child container context |
|
|
593
|
-
| `NoMatchingBindingError` | `"NO_MATCHING_BINDING"` | Resolve options match no registered binding |
|
|
594
|
-
| `RebindUnboundTokenError` | `"REBIND_UNBOUND_TOKEN"` | `rebind` targets a token with no binding owned by this container |
|
|
595
|
-
| `ScopeViolationError` | `"SCOPE_VIOLATION"` | Captive dependency found by `validate()` (`details` describes the path) |
|
|
596
|
-
| `SyncDisposalNotSupportedError` | `"SYNC_DISPOSAL_NOT_SUPPORTED"` | Sync `using` / `[Symbol.dispose]` on the container |
|
|
597
|
-
| `TokenNotBoundError` | `"TOKEN_NOT_BOUND"` | Required token has no binding |
|
|
235
|
+
Every error extends `DiError` and carries a stable `code` — `TokenNotBoundError` (`"TOKEN_NOT_BOUND"`), `CircularDependencyError`, `ScopeViolationError`, `AsyncResolutionError`, `AmbiguousBindingError`, and friends. Import them from the root or from `@codefast/di/errors`.
|
|
598
236
|
|
|
599
|
-
|
|
600
|
-
import { AmbiguousBindingError, DiError, ScopeViolationError, TokenNotBoundError } from "@codefast/di";
|
|
601
|
-
|
|
602
|
-
try {
|
|
603
|
-
container.resolve(ServiceToken);
|
|
604
|
-
} catch (error) {
|
|
605
|
-
if (error instanceof TokenNotBoundError) {
|
|
606
|
-
console.error(`Not registered: ${error.tokenName}`);
|
|
607
|
-
} else if (error instanceof ScopeViolationError) {
|
|
608
|
-
console.error(`Scope violation: ${error.details.consumerToken} → ${error.details.dependencyToken}`);
|
|
609
|
-
} else if (error instanceof AmbiguousBindingError) {
|
|
610
|
-
console.error(`Ambiguous: ${error.tokenName}`, error.candidateIds);
|
|
611
|
-
} else if (error instanceof DiError) {
|
|
612
|
-
console.error(`DI error [${error.code}]: ${error.message}`);
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
```
|
|
237
|
+
## Subpath exports
|
|
616
238
|
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
## Package exports
|
|
620
|
-
|
|
621
|
-
The root entry re-exports the full façade (`package.json` → `"."`). Subpaths mirror `package.json` → `exports` for tree-shaking.
|
|
622
|
-
|
|
623
|
-
| Subpath | Primary contents |
|
|
624
|
-
| ---------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
625
|
-
| `@codefast/di` | Tokens, `Container`, modules, decorators, errors, graph types, `MetadataReaderToken`, helpers from `binding-scope` / `resolve-options`, `createAutoRegisterRegistry` |
|
|
626
|
-
| `@codefast/di/binding` | `BindingBuilder` surface and binding model types |
|
|
627
|
-
| `@codefast/di/binding-scope` | `effectiveBindingScope` |
|
|
628
|
-
| `@codefast/di/binding-select` | `selectBinding`, `selectAllBindings` |
|
|
629
|
-
| `@codefast/di/constraints` | `whenParentIs`, `whenNoParentIs`, `whenAnyAncestorIs`, `whenNoAncestorIs`, `whenParentNamed`, `whenAnyAncestorNamed`, `whenParentTagged`, `whenAnyAncestorTagged` |
|
|
630
|
-
| `@codefast/di/constructor-type` | `Constructor`, `ConstructorInvocation` |
|
|
631
|
-
| `@codefast/di/container` | `Container`, `ContainerStatic` |
|
|
632
|
-
| `@codefast/di/decorators/inject` | `inject`, `optional`, `injectAll`, `isInjectionDescriptor`, descriptor types |
|
|
633
|
-
| `@codefast/di/decorators/injectable` | `injectable`, `createAutoRegisterRegistry`, `AutoRegisterRegistry` |
|
|
634
|
-
| `@codefast/di/decorators/lifecycle-decorators` | `postConstruct`, `preDestroy` |
|
|
635
|
-
| `@codefast/di/dependency-graph` | `buildDependencyGraph`, `ContainerGraphJson`, `GraphOptions`, … |
|
|
636
|
-
| `@codefast/di/environment` | `runWithContainer`, `getActiveContainer`, `DefaultResolutionContext`, `ResolverCallbacks`, `buildResolutionFrame` |
|
|
637
|
-
| `@codefast/di/errors` | Full `DiError` hierarchy |
|
|
638
|
-
| `@codefast/di/graph-adapters/cytoscape` | `toCytoscapeGraph` |
|
|
639
|
-
| `@codefast/di/graph-adapters/dot` | `toDotGraph` |
|
|
640
|
-
| `@codefast/di/graph-adapters/reactflow` | `toReactFlowGraph` |
|
|
641
|
-
| `@codefast/di/graph-adapters/types` | Re-exports graph JSON types from `dependency-graph` |
|
|
642
|
-
| `@codefast/di/inspector` | `Inspector`, `BindingSnapshot`, `ContainerSnapshot` |
|
|
643
|
-
| `@codefast/di/lifecycle` | `LifecycleManager` |
|
|
644
|
-
| `@codefast/di/metadata/metadata-keys` | Metadata keys + `WeakMap` registries |
|
|
645
|
-
| `@codefast/di/metadata/metadata-reader-token` | `MetadataReaderToken` |
|
|
646
|
-
| `@codefast/di/metadata/metadata-types` | `MetadataReader`, lifecycle metadata types |
|
|
647
|
-
| `@codefast/di/metadata/symbol-metadata-reader` | `SymbolMetadataReader`, `defaultMetadataReader` |
|
|
648
|
-
| `@codefast/di/module` | `Module`, `AsyncModule`, `SyncModule`, `isSyncModule`, builders |
|
|
649
|
-
| `@codefast/di/registry` | `BindingRegistry` |
|
|
650
|
-
| `@codefast/di/resolve-options` | `injectionSlotToResolveOptions`, `bindingSlotToResolveOptions` |
|
|
651
|
-
| `@codefast/di/resolver` | `DependencyResolver` |
|
|
652
|
-
| `@codefast/di/scope` | `ScopeManager` |
|
|
653
|
-
| `@codefast/di/token` | `token`, `Token`, `tokenName`, … |
|
|
654
|
-
| `@codefast/di/types` | Core DI types (`BindingScope`, `ResolutionContext`, `ResolveOptions`, …) |
|
|
655
|
-
| `@codefast/di/package.json` | Package manifest |
|
|
656
|
-
|
|
657
|
-
---
|
|
658
|
-
|
|
659
|
-
## Contributing
|
|
660
|
-
|
|
661
|
-
This package lives in the [Codefast monorepo](https://github.com/codefastlabs/codefast). From the repo root:
|
|
239
|
+
The root entry re-exports the full public API. Every internal area is also published as a tree-shakeable subpath — `@codefast/di/container`, `@codefast/di/token`, `@codefast/di/module`, `@codefast/di/constraints`, `@codefast/di/errors`, and graph adapters under `@codefast/di/graph-adapters/*` (DOT, Cytoscape, React Flow) for visualizing `container.generateDependencyGraph()` output.
|
|
662
240
|
|
|
663
|
-
|
|
664
|
-
pnpm --filter @codefast/di build
|
|
665
|
-
pnpm --filter @codefast/di test
|
|
666
|
-
pnpm --filter @codefast/di check-types
|
|
667
|
-
```
|
|
241
|
+
## Benchmarks
|
|
668
242
|
|
|
669
|
-
|
|
243
|
+
A head-to-head benchmark suite against InversifyJS 8 lives in the monorepo: [benchmarks/di-inversify](https://github.com/codefastlabs/codefast/tree/main/benchmarks/di-inversify). Each library runs in its canonical decorator mode, in isolated subprocesses, reported as per-trial medians with interquartile range — designed so the results are re-runnable rather than taken on faith. On the production-shaped scenarios measured there, `@codefast/di` resolves faster than InversifyJS; run `pnpm bench` in that package to reproduce the numbers on your machine.
|
|
670
244
|
|
|
671
245
|
## License
|
|
672
246
|
|
|
673
|
-
[MIT](https://
|
|
674
|
-
|
|
675
|
-
---
|
|
676
|
-
|
|
677
|
-
## Changelog
|
|
678
|
-
|
|
679
|
-
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).
|
|
247
|
+
[MIT](https://github.com/codefastlabs/codefast/blob/main/LICENSE) — part of the [codefast monorepo](https://github.com/codefastlabs/codefast). See [CHANGELOG.md](https://github.com/codefastlabs/codefast/blob/main/packages/di/CHANGELOG.md) for release history.
|