@codefast/di 0.5.0-canary.5 → 0.5.0-canary.6

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.
Files changed (175) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +67 -499
  3. package/dist/binding-scope.d.ts +10 -0
  4. package/dist/binding-scope.d.ts.map +1 -0
  5. package/dist/binding-scope.js +24 -0
  6. package/dist/binding-scope.js.map +1 -0
  7. package/dist/binding-select.d.ts +16 -0
  8. package/dist/binding-select.d.ts.map +1 -0
  9. package/dist/binding-select.js +130 -0
  10. package/dist/binding-select.js.map +1 -0
  11. package/dist/binding.d.ts +194 -0
  12. package/dist/binding.d.ts.map +1 -0
  13. package/dist/binding.js +46 -0
  14. package/dist/binding.js.map +1 -0
  15. package/dist/constraints.d.ts +51 -0
  16. package/dist/constraints.d.ts.map +1 -0
  17. package/dist/constraints.js +88 -0
  18. package/dist/constraints.js.map +1 -0
  19. package/dist/{constructor-type.d.mts → constructor-type.d.ts} +3 -5
  20. package/dist/constructor-type.d.ts.map +1 -0
  21. package/dist/constructor-type.js +2 -0
  22. package/dist/constructor-type.js.map +1 -0
  23. package/dist/container.d.ts +58 -0
  24. package/dist/container.d.ts.map +1 -0
  25. package/dist/container.js +853 -0
  26. package/dist/container.js.map +1 -0
  27. package/dist/decorators/inject.d.ts +46 -0
  28. package/dist/decorators/inject.d.ts.map +1 -0
  29. package/dist/decorators/inject.js +145 -0
  30. package/dist/decorators/inject.js.map +1 -0
  31. package/dist/decorators/injectable.d.ts +29 -0
  32. package/dist/decorators/injectable.d.ts.map +1 -0
  33. package/dist/decorators/injectable.js +53 -0
  34. package/dist/decorators/injectable.js.map +1 -0
  35. package/dist/decorators/lifecycle-decorators.d.ts +9 -0
  36. package/dist/decorators/lifecycle-decorators.d.ts.map +1 -0
  37. package/dist/decorators/lifecycle-decorators.js +40 -0
  38. package/dist/decorators/lifecycle-decorators.js.map +1 -0
  39. package/dist/dependency-graph.d.ts +40 -0
  40. package/dist/dependency-graph.d.ts.map +1 -0
  41. package/dist/dependency-graph.js +66 -0
  42. package/dist/dependency-graph.js.map +1 -0
  43. package/dist/environment.d.ts +51 -0
  44. package/dist/environment.d.ts.map +1 -0
  45. package/dist/environment.js +100 -0
  46. package/dist/environment.js.map +1 -0
  47. package/dist/errors.d.ts +150 -0
  48. package/dist/errors.d.ts.map +1 -0
  49. package/dist/errors.js +197 -0
  50. package/dist/errors.js.map +1 -0
  51. package/dist/graph-adapters/cytoscape.d.ts +33 -0
  52. package/dist/graph-adapters/cytoscape.d.ts.map +1 -0
  53. package/dist/graph-adapters/cytoscape.js +30 -0
  54. package/dist/graph-adapters/cytoscape.js.map +1 -0
  55. package/dist/graph-adapters/dot.d.ts +6 -0
  56. package/dist/graph-adapters/dot.d.ts.map +1 -0
  57. package/dist/graph-adapters/dot.js +18 -0
  58. package/dist/graph-adapters/dot.js.map +1 -0
  59. package/dist/graph-adapters/reactflow.d.ts +38 -0
  60. package/dist/graph-adapters/reactflow.d.ts.map +1 -0
  61. package/dist/graph-adapters/reactflow.js +23 -0
  62. package/dist/graph-adapters/reactflow.js.map +1 -0
  63. package/dist/index.d.ts +25 -0
  64. package/dist/index.d.ts.map +1 -0
  65. package/dist/index.js +21 -0
  66. package/dist/index.js.map +1 -0
  67. package/dist/inspector.d.ts +44 -0
  68. package/dist/inspector.d.ts.map +1 -0
  69. package/dist/inspector.js +82 -0
  70. package/dist/inspector.js.map +1 -0
  71. package/dist/lifecycle.d.ts +21 -0
  72. package/dist/lifecycle.d.ts.map +1 -0
  73. package/dist/lifecycle.js +174 -0
  74. package/dist/lifecycle.js.map +1 -0
  75. package/dist/metadata/{metadata-keys.d.mts → metadata-keys.d.ts} +5 -7
  76. package/dist/metadata/metadata-keys.d.ts.map +1 -0
  77. package/dist/metadata/metadata-keys.js +25 -0
  78. package/dist/metadata/metadata-keys.js.map +1 -0
  79. package/dist/metadata/metadata-reader-token.d.ts +7 -0
  80. package/dist/metadata/metadata-reader-token.d.ts.map +1 -0
  81. package/dist/metadata/metadata-reader-token.js +6 -0
  82. package/dist/metadata/metadata-reader-token.js.map +1 -0
  83. package/dist/metadata/metadata-types.d.ts +48 -0
  84. package/dist/metadata/metadata-types.d.ts.map +1 -0
  85. package/dist/metadata/metadata-types.js +2 -0
  86. package/dist/metadata/metadata-types.js.map +1 -0
  87. package/dist/metadata/symbol-metadata-reader.d.ts +20 -0
  88. package/dist/metadata/symbol-metadata-reader.d.ts.map +1 -0
  89. package/dist/metadata/symbol-metadata-reader.js +34 -0
  90. package/dist/metadata/symbol-metadata-reader.js.map +1 -0
  91. package/dist/module.d.ts +60 -0
  92. package/dist/module.d.ts.map +1 -0
  93. package/dist/module.js +47 -0
  94. package/dist/module.js.map +1 -0
  95. package/dist/registry.d.ts +41 -0
  96. package/dist/registry.d.ts.map +1 -0
  97. package/dist/registry.js +208 -0
  98. package/dist/registry.js.map +1 -0
  99. package/dist/resolve-options.d.ts +19 -0
  100. package/dist/resolve-options.d.ts.map +1 -0
  101. package/dist/resolve-options.js +32 -0
  102. package/dist/resolve-options.js.map +1 -0
  103. package/dist/resolver.d.ts +90 -0
  104. package/dist/resolver.d.ts.map +1 -0
  105. package/dist/resolver.js +1208 -0
  106. package/dist/resolver.js.map +1 -0
  107. package/dist/scope.d.ts +25 -0
  108. package/dist/scope.d.ts.map +1 -0
  109. package/dist/scope.js +61 -0
  110. package/dist/scope.js.map +1 -0
  111. package/dist/token.d.ts +23 -0
  112. package/dist/token.d.ts.map +1 -0
  113. package/dist/token.js +25 -0
  114. package/dist/token.js.map +1 -0
  115. package/dist/types.d.ts +90 -0
  116. package/dist/types.d.ts.map +1 -0
  117. package/dist/types.js +2 -0
  118. package/dist/types.js.map +1 -0
  119. package/package.json +87 -79
  120. package/dist/binding-scope.d.mts +0 -13
  121. package/dist/binding-scope.mjs +0 -21
  122. package/dist/binding-select.d.mts +0 -19
  123. package/dist/binding-select.mjs +0 -72
  124. package/dist/binding.d.mts +0 -191
  125. package/dist/binding.mjs +0 -36
  126. package/dist/constraints.d.mts +0 -55
  127. package/dist/constraints.mjs +0 -87
  128. package/dist/constructor-type.mjs +0 -1
  129. package/dist/container.d.mts +0 -62
  130. package/dist/container.mjs +0 -741
  131. package/dist/decorators/inject.d.mts +0 -49
  132. package/dist/decorators/inject.mjs +0 -136
  133. package/dist/decorators/injectable.d.mts +0 -32
  134. package/dist/decorators/injectable.mjs +0 -57
  135. package/dist/decorators/lifecycle-decorators.d.mts +0 -11
  136. package/dist/decorators/lifecycle-decorators.mjs +0 -38
  137. package/dist/dependency-graph.d.mts +0 -43
  138. package/dist/dependency-graph.mjs +0 -63
  139. package/dist/environment.d.mts +0 -55
  140. package/dist/environment.mjs +0 -98
  141. package/dist/errors.d.mts +0 -153
  142. package/dist/errors.mjs +0 -197
  143. package/dist/graph-adapters/cytoscape.d.mts +0 -36
  144. package/dist/graph-adapters/cytoscape.mjs +0 -26
  145. package/dist/graph-adapters/dot.d.mts +0 -9
  146. package/dist/graph-adapters/dot.mjs +0 -20
  147. package/dist/graph-adapters/reactflow.d.mts +0 -41
  148. package/dist/graph-adapters/reactflow.mjs +0 -29
  149. package/dist/index.d.mts +0 -18
  150. package/dist/index.mjs +0 -12
  151. package/dist/inspector.d.mts +0 -48
  152. package/dist/inspector.mjs +0 -71
  153. package/dist/lifecycle.d.mts +0 -25
  154. package/dist/lifecycle.mjs +0 -118
  155. package/dist/metadata/metadata-keys.mjs +0 -27
  156. package/dist/metadata/metadata-reader-token.d.mts +0 -10
  157. package/dist/metadata/metadata-reader-token.mjs +0 -8
  158. package/dist/metadata/metadata-types.d.mts +0 -51
  159. package/dist/metadata/metadata-types.mjs +0 -1
  160. package/dist/metadata/symbol-metadata-reader.d.mts +0 -23
  161. package/dist/metadata/symbol-metadata-reader.mjs +0 -29
  162. package/dist/module.d.mts +0 -62
  163. package/dist/module.mjs +0 -42
  164. package/dist/registry.d.mts +0 -45
  165. package/dist/registry.mjs +0 -163
  166. package/dist/resolve-options.d.mts +0 -22
  167. package/dist/resolve-options.mjs +0 -28
  168. package/dist/resolver.d.mts +0 -94
  169. package/dist/resolver.mjs +0 -767
  170. package/dist/scope.d.mts +0 -28
  171. package/dist/scope.mjs +0 -58
  172. package/dist/token.d.mts +0 -25
  173. package/dist/token.mjs +0 -22
  174. package/dist/types.d.mts +0 -92
  175. package/dist/types.mjs +0 -1
package/README.md CHANGED
@@ -1,96 +1,31 @@
1
1
  # @codefast/di
2
2
 
3
- Type-safe, ESM-only dependency injection for modern TypeScript — built on TC39 Stage 3 decorators with no runtime reflection.
4
-
5
- [![CI](https://github.com/codefastlabs/codefast/actions/workflows/release.yml/badge.svg)](https://github.com/codefastlabs/codefast/actions/workflows/release.yml)
6
- [![npm version](https://img.shields.io/npm/v/@codefast/di.svg)](https://www.npmjs.com/package/@codefast/di)
7
- [![npm downloads](https://img.shields.io/npm/dm/@codefast/di.svg)](https://www.npmjs.com/package/@codefast/di)
8
- [![license](https://img.shields.io/badge/License-MIT-yellow.svg)](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
- ## Requirements
5
+ [![npm version](https://img.shields.io/npm/v/@codefast/di)](https://www.npmjs.com/package/@codefast/di)
6
+ [![license](https://img.shields.io/npm/l/@codefast/di)](https://github.com/codefastlabs/codefast/blob/main/LICENSE)
64
7
 
65
- - Node.js `>= 24.0.0` (see `package.json` → `engines`)
66
- - TypeScript `>= 5.2` with native Stage 3 decorators (TypeScript `5.9+` recommended for best inference, consistent with other Codefast packages)
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
- Enable native decorators in `tsconfig.json` — do **not** enable `experimentalDecorators`:
15
+ > Currently published as `1.0.0-canary.x` pre-releases on the way to a stable 1.0.
69
16
 
70
- ```json
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(`[prod] ${message}`),
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
- // Same service, test wiring
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. Business classes (`CheckoutService`) stay unchanged while infrastructure (`LoggerToken`) swaps per environment.
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
- - The type parameter flows through the binding and resolution chain.
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)` and chain a strategy, then optional constraints and hooks. For strategies that support it, call `.singleton()`, `.scoped()`, or `.transient()` after constraints.
80
+ Start with `container.bind(key)`, chain a strategy, then optional constraints and a scope.
172
81
 
173
- ### Strategies
174
-
175
- | Method | Description |
176
- | --------------------------------- | ----------------------------------------------------------------------------------------------------------- |
177
- | `.toConstantValue(value)` | Fixed value. Stored as a `constant` binding with scope `singleton` (no `.scoped()` / `.transient()` chain). |
178
- | `.toSelf()` | Bind a constructor to itself. Uses `@injectable()` metadata. |
179
- | `.to(Constructor)` | Bind a token to a class constructor. |
180
- | `.toDynamic(factory)` | Sync factory `(ctx: ResolutionContext) => Value`. |
181
- | `.toDynamicAsync(factory)` | Async factory `(ctx: ResolutionContext) => Promise<Value>`. |
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(AppConfigToken).toConstantValue({ port: 3000 });
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 config = ctx.resolve(AppConfigToken);
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((repository, config) => new UserService(repository, config), [UserRepository, AppConfigToken] as const);
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
- | Method | Lifetime |
216
- | -------------- | -------------------------------------------------------------------------- |
217
- | `.singleton()` | One instance per root container, shared with descendants. |
218
- | `.scoped()` | One instance per child container. Useful for request-scoped services. |
219
- | `.transient()` | New instance on every resolution. Default when no scope method is chained. |
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(DatabaseToken).toDynamic(createDb).singleton();
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 the same token. A constraint picks the right one at resolution time.
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
- **Default slot**
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. Metadata is stored for resolution as described in [Core Concepts](#core-concepts).
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, token } from "@codefast/di";
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([ConfigToken, optional(CacheToken)])
314
- class AppService {
140
+ @injectable([DbToken, optional(CacheToken)])
141
+ class UserRepository {
315
142
  constructor(
316
- private readonly config: AppConfig,
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
- Only one of each decorator is allowed per class.
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
- import { Container, createAutoRegisterRegistry, injectable, token } from "@codefast/di";
385
-
386
- const DbToken = token<Database>("Database");
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); // all matching multi-bindings
175
+ container.resolveAll(HandlerToken); // every matching binding
412
176
 
413
- container.resolve(LoggerToken, { name: "console" });
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
- ### Container API surface
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
- Child containers fall through to the parent’s bindings and share the parent’s singleton cache, but maintain their own scoped cache.
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()` walks singleton bindings and fails fast when a captive dependency is detected (for example `singleton` → `scoped` / `transient`).
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 `dispose()` automatically:
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. A module holds no runtime state and can be loaded into any number of containers.
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.create` returns a `SyncModule`. `Module.createAsync` returns an `AsyncModule` (same as `AsyncModule.create`). `SyncModule`, `AsyncModule`, and `isSyncModule()` are available from `@codefast/di/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
- All errors extend `DiError` and expose a stable `code` property.
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
- ```typescript
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
- ```bash
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://opensource.org/licenses/MIT) — see [`package.json`](./package.json).
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.