@codefast/di 0.3.13-canary.4

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