decorator-dependency-injection 1.0.6 → 1.1.0

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/README.md CHANGED
@@ -1,15 +1,77 @@
1
1
  # Decorator Dependency Injection
2
2
 
3
3
  [![npm version](https://badge.fury.io/js/decorator-dependency-injection.svg)](http://badge.fury.io/js/decorator-dependency-injection)
4
+ [![npm downloads](https://img.shields.io/npm/dm/decorator-dependency-injection.svg)](https://www.npmjs.com/package/decorator-dependency-injection)
4
5
  [![Build Status](https://github.com/mallocator/decorator-dependency-injection/actions/workflows/release.yml/badge.svg)](https://github.com/mallocator/decorator-dependency-injection/actions/workflows/release.yml)
5
- [![Coverage](https://img.shields.io/badge/coverage-93%25-brightgreen)](https://github.com/mallocator/decorator-dependency-injection)
6
+ [![Coverage](https://img.shields.io/badge/coverage-98%25-brightgreen)](https://github.com/mallocator/decorator-dependency-injection)
7
+ [![License](https://img.shields.io/npm/l/decorator-dependency-injection.svg)](https://github.com/mallocator/decorator-dependency-injection/blob/main/LICENSE)
8
+
9
+ **A lightweight dependency injection (DI) library for JavaScript and TypeScript using native TC39 Stage 3 decorators.**
10
+
11
+ No reflection. No metadata. No configuration files. Just decorators that work.
12
+
13
+ **Why this library?**
14
+ - Modern TC39 decorator syntax - no `reflect-metadata` or `emitDecoratorMetadata` needed
15
+ - Zero dependencies - tiny bundle size
16
+ - Built-in mocking support for unit testing with Jest, Vitest, or Mocha
17
+ - Full TypeScript support with type inference
18
+ - Works with Node.js, Bun, React, Vue, Svelte, and more
19
+
20
+ > **Using a frontend framework?** See the [Framework Integration Guide](docs/FRAMEWORK_INTEGRATION.md) for React, Vue, Svelte, SSR, and other environments.
21
+
22
+ > **Building a Node.js server?** We have [Express/Koa/Fastify middleware](docs/FRAMEWORK_INTEGRATION.md#nodejs-server-middleware) for automatic request-scoped containers.
23
+
24
+ ## Table of Contents
25
+
26
+ - [Quick Start](#quick-start)
27
+ - [Installation](#installation)
28
+ - [Core Concepts](#core-concepts)
29
+ - [Singleton](#singleton)
30
+ - [Factory](#factory)
31
+ - [Lazy Injection](#lazy-injection)
32
+ - [Passing Parameters](#passing-parameters)
33
+ - [Testing](#testing)
34
+ - [Mocking Dependencies](#mocking-dependencies)
35
+ - [Proxy Mocking](#proxy-mocking)
36
+ - [Test Lifecycle](#test-lifecycle)
37
+ - [Best Practices](#testing-best-practices)
38
+ - [Advanced Features](#advanced-features)
39
+ - [Private Fields](#private-fields)
40
+ - [Static Fields](#static-fields)
41
+ - [Named Registrations](#named-registrations)
42
+ - [Manual Resolution](#manual-resolution)
43
+ - [Container Introspection](#container-introspection)
44
+ - [Isolated Containers](#isolated-containers)
45
+ - [Server Middleware](#server-middleware-expresskoa-fastify)
46
+ - [API Reference](#api-reference)
47
+ - [TypeScript Support](#typescript-support)
48
+
49
+ ---
50
+
51
+ ## Quick Start
6
52
 
7
- ## Description
53
+ ```javascript
54
+ import { Singleton, Inject } from 'decorator-dependency-injection'
55
+
56
+ @Singleton()
57
+ class Database {
58
+ query(sql) { return db.execute(sql) }
59
+ }
60
+
61
+ class UserService {
62
+ @Inject(Database) db
63
+
64
+ getUser(id) {
65
+ return this.db.query(`SELECT * FROM users WHERE id = ${id}`)
66
+ }
67
+ }
68
+
69
+ new UserService().getUser(1) // Database is automatically injected
70
+ ```
8
71
 
9
- With the [TC39 proposal-decorators](https://github.com/tc39/proposal-decorators) reaching stage 3, it's time to start
10
- thinking about how we can use them in our projects. One of the most common patterns in JavaScript is dependency
11
- injection. This pattern is used to make our code more testable and maintainable. This library provides simple decorators
12
- to help you inject dependencies into your classes and mock them for testing.
72
+ **That's it.** The `Database` instance is created once and shared everywhere it's injected.
73
+
74
+ ---
13
75
 
14
76
  ## Installation
15
77
 
@@ -17,555 +79,491 @@ to help you inject dependencies into your classes and mock them for testing.
17
79
  npm install decorator-dependency-injection
18
80
  ```
19
81
 
20
- Until we reach stage 4, you will need to enable the decorators proposal in your project. You can do this by adding the
21
- following babel transpiler options to your `.babelrc` file.
82
+ <details>
83
+ <summary><strong>Babel Configuration (required until decorators reach Stage 4)</strong></summary>
84
+
85
+ Add to your `.babelrc` or `babel.config.json`:
22
86
 
23
87
  ```json
24
88
  {
25
- "plugins": [
26
- "@babel/plugin-proposal-decorators"
27
- ]
89
+ "plugins": [["@babel/plugin-proposal-decorators", { "version": "2023-11" }]]
28
90
  }
29
91
  ```
30
92
 
31
- To run your project with decorators enabled, you will need to use the babel transpiler. You can do this by running the
32
- following command in your project root.
33
-
93
+ Run with Babel:
34
94
  ```bash
35
95
  npx babel-node index.js
36
96
  ```
37
97
 
38
- Finally, for running tests with decorators enabled, you will need to use the babel-jest package. You can do this by
39
- adding the following configuration to your `package.json` file.
40
-
98
+ For Jest, add to `package.json`:
41
99
  ```json
42
100
  {
43
101
  "jest": {
44
- "transform": {
45
- "^.+\\.jsx?$": "babel-jest"
46
- }
102
+ "transform": { "^.+\\.jsx?$": "babel-jest" }
47
103
  }
48
104
  }
49
105
  ```
50
106
 
51
- Other testing frameworks may require a different configuration.
107
+ See this project's `package.json` for a complete working example.
52
108
 
53
- For a full example of how to set up a project with decorators, see this project's ```package.json``` file.
109
+ </details>
54
110
 
55
- ## Usage
111
+ ---
56
112
 
57
- There are two ways of specifying injectable dependencies: ```@Singleton``` and ```@Factory```:
113
+ ## Core Concepts
58
114
 
59
115
  ### Singleton
60
116
 
61
- The ```@Singleton``` decorator is used to inject a single instance of a dependency into a class. This is useful when you
62
- want to share the same instance of a class across multiple classes.
117
+ A singleton creates **one shared instance** across your entire application:
63
118
 
64
119
  ```javascript
65
- import {Singleton, Inject} from 'decorator-dependency-injection';
120
+ import { Singleton, Inject } from 'decorator-dependency-injection'
66
121
 
67
122
  @Singleton()
68
- class Dependency {
123
+ class ConfigService {
124
+ apiUrl = 'https://api.example.com'
125
+ }
126
+
127
+ class ServiceA {
128
+ @Inject(ConfigService) config
69
129
  }
70
130
 
71
- class Consumer {
72
- @Inject(Dependency) dependency // creates an instance only once
131
+ class ServiceB {
132
+ @Inject(ConfigService) config // Same instance as ServiceA
73
133
  }
74
134
  ```
75
135
 
76
136
  ### Factory
77
137
 
78
- The ```@Factory``` decorator is used to inject a new instance of a dependency into a class each time it is requested.
79
- This is useful when you want to create a new instance of a class each time it is injected.
138
+ A factory creates a **new instance** each time it's injected:
80
139
 
81
140
  ```javascript
82
- import {Factory, Inject} from 'decorator-dependency-injection';
141
+ import { Factory, Inject } from 'decorator-dependency-injection'
83
142
 
84
143
  @Factory()
85
- class Dependency {
144
+ class RequestLogger {
145
+ id = Math.random()
86
146
  }
87
147
 
88
- class Consumer {
89
- @Inject(Dependency) dependency // creates a new instance each time a new Consumer is created
148
+ class Handler {
149
+ @Inject(RequestLogger) logger // New instance for each Handler
90
150
  }
151
+
152
+ new Handler().logger.id !== new Handler().logger.id // true
91
153
  ```
92
154
 
93
- ### InjectLazy
155
+ ### Lazy Injection
94
156
 
95
- ```@Inject``` annotated properties are evaluated during instance initialization. That means that all properties should
96
- be accessible in the constructor. That also means that we're creating an instance no matter if you access the property
97
- or not. If you want to only create an instance when you access the property, you can use the ```@InjectLazy```
98
- decorator. This will create the instance only when the property is accessed for the first time. Note that this also
99
- works from the constructor, same as the regular ```@Inject```.
157
+ By default, dependencies are created when the parent class is instantiated. Use `@InjectLazy` to defer creation until first access:
100
158
 
101
159
  ```javascript
102
- import {Singleton, InjectLazy} from 'decorator-dependency-injection';
160
+ import { Singleton, InjectLazy } from 'decorator-dependency-injection'
103
161
 
104
162
  @Singleton()
105
- class Dependency {
163
+ class ExpensiveService {
164
+ constructor() {
165
+ console.log('ExpensiveService created') // Only when accessed
166
+ }
106
167
  }
107
168
 
108
- class Consumer {
109
- @InjectLazy(Dependency) dependency // creates an instance only when the property is accessed
169
+ class MyClass {
170
+ @InjectLazy(ExpensiveService) service
171
+
172
+ doWork() {
173
+ this.service.process() // ExpensiveService created here
174
+ }
110
175
  }
111
176
  ```
112
177
 
113
- ### Private Field Injection
178
+ This is also useful for breaking circular dependencies.
114
179
 
115
- Both `@Inject` and `@InjectLazy` support private fields using the `#` syntax:
180
+ ### Passing Parameters
116
181
 
117
- ```javascript
118
- import {Singleton, Inject} from 'decorator-dependency-injection';
182
+ Pass constructor arguments after the class reference:
119
183
 
120
- @Singleton()
121
- class Database {
122
- query(sql) { /* ... */ }
123
- }
184
+ ```javascript
185
+ import { Factory, Inject } from 'decorator-dependency-injection'
124
186
 
125
- class UserService {
126
- @Inject(Database) #db // truly private - not accessible from outside
127
-
128
- getUser(id) {
129
- return this.#db.query(`SELECT * FROM users WHERE id = ${id}`)
187
+ @Factory()
188
+ class Logger {
189
+ constructor(prefix, level) {
190
+ this.prefix = prefix
191
+ this.level = level
130
192
  }
131
193
  }
132
194
 
133
- const service = new UserService()
134
- service.#db // SyntaxError: Private field '#db' must be declared
195
+ class MyService {
196
+ @Inject(Logger, 'MyService', 'debug') logger
197
+ }
135
198
  ```
136
199
 
137
- ### The `accessor` Keyword
200
+ For singletons, parameters are only used on the first instantiation.
138
201
 
139
- The `accessor` keyword (part of the TC39 decorators proposal) creates an auto-accessor - a private backing field with
140
- automatic getter/setter. This is particularly useful for **lazy injection with private fields**.
202
+ ---
141
203
 
142
- ```javascript
143
- class Example {
144
- accessor myField = 'value'
145
- }
204
+ ## Testing
146
205
 
147
- // Roughly equivalent to:
148
- class Example {
149
- #myField = 'value'
150
- get myField() { return this.#myField }
151
- set myField(v) { this.#myField = v }
152
- }
153
- ```
206
+ ### Mocking Dependencies
154
207
 
155
- #### Using `accessor` with Injection
208
+ Use `@Mock` to replace a dependency with a test double:
156
209
 
157
210
  ```javascript
158
- import {Singleton, Inject, InjectLazy} from 'decorator-dependency-injection';
211
+ import { Singleton, Mock, removeMock, resolve } from 'decorator-dependency-injection'
159
212
 
160
213
  @Singleton()
161
- class ExpensiveService {
162
- constructor() {
163
- console.log('ExpensiveService created')
164
- }
214
+ class UserService {
215
+ getUser(id) { return fetchFromDatabase(id) }
165
216
  }
166
217
 
167
- class Consumer {
168
- // Public accessor - works with both @Inject and @InjectLazy
169
- @Inject(ExpensiveService) accessor service
170
-
171
- // Private accessor - recommended for lazy private injection
172
- @InjectLazy(ExpensiveService) accessor #privateService
173
-
174
- doWork() {
175
- // Instance created only when first accessed
176
- return this.#privateService.process()
177
- }
218
+ // In your test file:
219
+ @Mock(UserService)
220
+ class MockUserService {
221
+ getUser(id) { return { id, name: 'Test User' } }
178
222
  }
179
- ```
180
223
 
181
- ### Injection Support Matrix
224
+ // Now all injections of UserService receive MockUserService
225
+ const user = resolve(UserService).getUser(1) // { id: 1, name: 'Test User' }
182
226
 
183
- | Decorator | Syntax | Lazy? | Notes |
184
- |-----------|--------|-------|-------|
185
- | `@Inject` | `@Inject(Dep) field` | No | Standard injection |
186
- | `@Inject` | `@Inject(Dep) #field` | No | Private field injection |
187
- | `@Inject` | `@Inject(Dep) accessor field` | No* | Accessor injection |
188
- | `@Inject` | `@Inject(Dep) accessor #field` | No* | Private accessor injection |
189
- | `@Inject` | `@Inject(Dep) static field` | No | Static field injection |
190
- | `@Inject` | `@Inject(Dep) static #field` | No | Static private field |
191
- | `@Inject` | `@Inject(Dep) static accessor field` | No* | Static accessor |
192
- | `@Inject` | `@Inject(Dep) static accessor #field` | No* | Static private accessor |
193
- | `@InjectLazy` | `@InjectLazy(Dep) field` | ✅ Yes | Lazy public field |
194
- | `@InjectLazy` | `@InjectLazy(Dep) #field` | ⚠️ No | See caveat below |
195
- | `@InjectLazy` | `@InjectLazy(Dep) accessor field` | ✅ Yes | Lazy accessor |
196
- | `@InjectLazy` | `@InjectLazy(Dep) accessor #field` | ✅ Yes | **Recommended for lazy private** |
197
- | `@InjectLazy` | `@InjectLazy(Dep) static field` | ✅ Yes | Lazy static field |
198
- | `@InjectLazy` | `@InjectLazy(Dep) static #field` | ⚠️ No | Same caveat as instance private |
199
- | `@InjectLazy` | `@InjectLazy(Dep) static accessor #field` | ✅ Yes | Lazy static private accessor |
227
+ // Restore the original
228
+ removeMock(UserService)
229
+ ```
200
230
 
201
- *`@Inject` with accessors caches on first access, which is similar to lazy behavior.
231
+ ### Proxy Mocking
202
232
 
203
- #### Caveat: `@InjectLazy` with Private Fields
233
+ Mock only specific methods while keeping the rest of the original implementation:
204
234
 
205
- Due to JavaScript limitations, `@InjectLazy` on private fields (`#field`) **cannot be truly lazy**. The instance is
206
- created at construction time (or class definition time for static fields), not on first access. This is because
207
- `Object.defineProperty()` cannot create getters on private fields.
235
+ ```javascript
236
+ @Mock(UserService, true) // true enables proxy mode
237
+ class PartialMock {
238
+ getUser(id) { return { id, name: 'Mocked' } }
239
+ // All other methods delegate to the real UserService
240
+ }
241
+ ```
208
242
 
209
- This applies to both instance and static private fields.
243
+ ### Test Lifecycle
210
244
 
211
- **Recommendation:** For true lazy injection with private members, use the `accessor` keyword:
245
+ | Function | Purpose |
246
+ |----------|---------|
247
+ | `removeMock(Class)` | Remove a specific mock, restore original |
248
+ | `removeAllMocks()` | Remove all mocks, restore all originals |
249
+ | `resetSingletons()` | Clear cached instances (keeps mocks) |
250
+ | `clearContainer()` | Remove all registrations entirely |
212
251
 
213
252
  ```javascript
214
- // Not truly lazy (created at construction)
215
- @InjectLazy(ExpensiveService) #service
216
-
217
- // ✅ Truly lazy (created on first access)
218
- @InjectLazy(ExpensiveService) accessor #service
253
+ import { removeAllMocks, resetSingletons } from 'decorator-dependency-injection'
219
254
 
220
- // Static fields work the same way:
221
- // Not truly lazy (created at class definition)
222
- @InjectLazy(ExpensiveService) static #service
223
-
224
- // ✅ Truly lazy
225
- @InjectLazy(ExpensiveService) static accessor #service
255
+ afterEach(() => {
256
+ removeAllMocks() // Restore original implementations
257
+ // OR
258
+ resetSingletons() // Keep mocks, but get fresh instances
259
+ })
226
260
  ```
227
261
 
228
- ### Static Field Injection
262
+ **Note:** These functions remove/restore mocks. They do NOT clear mock call history. If using Vitest/Jest spies, call `.mockClear()` separately.
229
263
 
230
- All injection decorators work with static fields. Static injections are shared across all instances of the class:
264
+ ### Testing Best Practices
231
265
 
232
266
  ```javascript
233
- import {Factory, Singleton, Inject} from 'decorator-dependency-injection';
267
+ import { Mock, removeAllMocks, resetSingletons } from 'decorator-dependency-injection'
268
+ import { vi, describe, it, beforeEach, afterEach } from 'vitest'
234
269
 
235
- @Singleton()
236
- class SharedConfig {
237
- apiUrl = 'https://api.example.com'
238
- }
270
+ // Hoist mock functions for per-test configuration
271
+ const mockGetUser = vi.hoisted(() => vi.fn())
239
272
 
240
- @Factory()
241
- class RequestLogger {
242
- static nextId = 0
243
- id = ++RequestLogger.nextId
273
+ @Mock(UserService)
274
+ class MockUserService {
275
+ getUser = mockGetUser
244
276
  }
245
277
 
246
- class ApiService {
247
- @Inject(SharedConfig) static config // Shared across all instances
248
- @Inject(RequestLogger) logger // New instance per ApiService
249
-
250
- getUrl() {
251
- return ApiService.config.apiUrl
252
- }
253
- }
254
-
255
- const a = new ApiService()
256
- const b = new ApiService()
257
- console.log(a.logger.id) // 1
258
- console.log(b.logger.id) // 2
259
- console.log(ApiService.config === ApiService.config) // true (singleton)
278
+ describe('MyFeature', () => {
279
+ beforeEach(() => {
280
+ mockGetUser.mockClear() // Clear call history
281
+ resetSingletons() // Fresh instances per test
282
+ })
283
+
284
+ afterEach(() => {
285
+ removeAllMocks() // Restore originals
286
+ })
287
+
288
+ it('should work', () => {
289
+ mockGetUser.mockReturnValue({ id: 1 })
290
+ // ... test code ...
291
+ expect(mockGetUser).toHaveBeenCalled()
292
+ })
293
+ })
260
294
  ```
261
295
 
262
- ### Additional Supported Features
263
-
264
- The injection decorators also support:
265
-
266
- - **Computed property names**: `@Inject(Dep) [dynamicPropertyName]`
267
- - **Symbol property names**: `@Inject(Dep) [Symbol('key')]`
268
- - **Inheritance**: Subclasses inherit parent class injections
269
- - **Multiple decorators**: Combine `@Inject` with other decorators
270
- - **Nested injection**: Singletons/Factories can have their own injected dependencies
271
-
272
- ## Passing parameters to a dependency
273
-
274
- You can pass parameters to a dependency by using the ```@Inject``` decorator with a function that returns the
275
- dependency.
296
+ Additional test utilities:
276
297
 
277
298
  ```javascript
278
- import {Factory, Inject} from 'decorator-dependency-injection';
299
+ import { isMocked, getMockInstance } from 'decorator-dependency-injection'
279
300
 
280
- @Factory
281
- class Dependency {
282
- constructor(param1, param2) {
283
- this.param1 = param1
284
- this.param2 = param2
285
- }
286
- }
301
+ // Check if a class is currently mocked
302
+ if (isMocked(UserService)) { /* ... */ }
287
303
 
288
- class Consumer {
289
- @Inject(Dependency, 'myParam', 'myOtherParam') dependency
290
- }
304
+ // Access the mock instance to configure it
305
+ getMockInstance(UserService).someMethod.mockReturnValue('test')
291
306
  ```
292
307
 
293
- While this is most useful for Factory dependencies, it can also be used with Singleton dependencies. However, parameters
294
- will only be passed to the dependency the first time it is created.
295
-
296
- ## Mocking dependencies for testing
308
+ ---
297
309
 
298
- You can mock dependencies by using the ```@Mock``` decorator with a function that returns the mock dependency.
310
+ ## Advanced Features
299
311
 
300
- ```javascript
301
- import {Factory, Inject, Mock, resetMock} from 'decorator-dependency-injection'
312
+ ### Private Fields
302
313
 
303
- @Factory()
304
- class Dependency {
305
- method() {
306
- return 'real'
307
- }
308
- }
314
+ Both `@Inject` and `@InjectLazy` support private fields:
309
315
 
310
- class Consumer {
311
- @Inject(Dependency) dependency
316
+ ```javascript
317
+ class UserService {
318
+ @Inject(Database) #db // Truly private
312
319
 
313
- constructor() {
314
- console.log(this.dependency.method())
320
+ getUser(id) {
321
+ return this.#db.query(`SELECT * FROM users WHERE id = ${id}`)
315
322
  }
316
323
  }
324
+ ```
317
325
 
318
- // Test Code
326
+ For lazy injection with private fields, use the `accessor` keyword:
319
327
 
320
- @Mock(Dependency)
321
- class MockDependency {
322
- method() {
323
- return 'mock'
324
- }
328
+ ```javascript
329
+ class UserService {
330
+ @InjectLazy(Database) accessor #db // Lazy AND private
325
331
  }
332
+ ```
326
333
 
327
- const consumer = new Consumer() // prints 'mock'
334
+ <details>
335
+ <summary><strong>Why accessor for lazy private fields?</strong></summary>
328
336
 
329
- resetMock(Dependency)
337
+ JavaScript doesn't allow `Object.defineProperty()` on private fields, so `@InjectLazy` on `#field` creates the instance at construction time (not truly lazy). The `accessor` keyword creates a private backing field with getter/setter that enables true lazy behavior.
330
338
 
331
- const consumer = new Consumer() // prints 'real'
332
- ```
339
+ </details>
333
340
 
334
- ### Resetting Mocks
341
+ ### Static Fields
335
342
 
336
- The `resetMock` utility function allows you to remove any active mock for a dependency and restore the original
337
- implementation. This is useful for cleaning up after tests or switching between real and mock dependencies.
343
+ Inject at the class level (shared across all instances):
338
344
 
339
345
  ```javascript
340
- import {resetMock, resetMocks} from 'decorator-dependency-injection';
346
+ class ApiService {
347
+ @Inject(Config) static config // Class-level singleton
348
+ @Inject(Logger) logger // Instance-level
341
349
 
342
- resetMock(Dependency); // Restores the original Dependency implementation
343
- resetMocks(); // Restores all mocked dependencies
350
+ getUrl() {
351
+ return ApiService.config.apiUrl
352
+ }
353
+ }
344
354
  ```
345
355
 
346
- ### Clearing the Container
356
+ ### Named Registrations
347
357
 
348
- For complete test isolation, you can clear all registered instances from the container:
358
+ Register dependencies under string names instead of class references:
349
359
 
350
360
  ```javascript
351
- import {clearContainer} from 'decorator-dependency-injection';
361
+ @Singleton('database')
362
+ class PostgresDatabase { }
352
363
 
353
- clearContainer(); // Removes all registered singletons, factories, and mocks
364
+ class UserService {
365
+ @Inject('database') db
366
+ }
354
367
  ```
355
368
 
356
- ### Resolving Dependencies Without Decorators
369
+ ### Manual Resolution
357
370
 
358
- The `resolve` function allows non-class code (plain functions, modules, callbacks, etc.) to retrieve instances from the DI container:
371
+ Retrieve instances programmatically (useful for non-class code):
359
372
 
360
373
  ```javascript
361
- import {Singleton, Factory, resolve} from 'decorator-dependency-injection';
362
-
363
- @Singleton()
364
- class UserService {
365
- getUser(id) {
366
- return { id, name: 'John' }
367
- }
368
- }
374
+ import { resolve } from 'decorator-dependency-injection'
369
375
 
370
- @Factory()
371
- class Logger {
372
- constructor(prefix) {
373
- this.prefix = prefix
374
- }
375
- log(msg) {
376
- console.log(`[${this.prefix}] ${msg}`)
377
- }
378
- }
379
-
380
- // Use in plain functions
381
376
  function handleRequest(req) {
382
377
  const userService = resolve(UserService)
383
378
  return userService.getUser(req.userId)
384
379
  }
385
380
 
386
- // Use with factory parameters
387
- function createLogger(moduleName) {
388
- return resolve(Logger, moduleName)
389
- }
381
+ // With parameters
382
+ const logger = resolve(Logger, 'my-module')
390
383
 
391
- // Use with named registrations
392
- const db = resolve('databaseConnection')
384
+ // With named registration
385
+ const db = resolve('database')
393
386
  ```
394
387
 
395
- This is useful when:
396
- - Integrating with frameworks that don't support decorators
397
- - Writing utility functions that need DI access
398
- - Bridging between decorator-based and non-decorator code
399
- - Testing or debugging the container directly
400
-
401
- ### Validation Helpers
388
+ ### Container Introspection
402
389
 
403
- The library provides utilities to validate registrations at runtime, which is useful for catching configuration
404
- errors early:
405
-
406
- #### `isRegistered(clazzOrName)`
407
-
408
- Check if a class or name is registered:
390
+ Debug and inspect the container state:
409
391
 
410
392
  ```javascript
411
- import {Singleton, isRegistered} from 'decorator-dependency-injection';
412
-
413
- @Singleton()
414
- class MyService {}
415
-
416
- console.log(isRegistered(MyService)); // true
417
- console.log(isRegistered('unknownName')); // false
393
+ import {
394
+ getContainer,
395
+ listRegistrations,
396
+ isRegistered,
397
+ validateRegistrations,
398
+ setDebug
399
+ } from 'decorator-dependency-injection'
400
+
401
+ // Check registration status
402
+ isRegistered(UserService) // true/false
403
+
404
+ // Fail fast at startup
405
+ validateRegistrations(UserService, AuthService, 'database')
406
+ // Throws if any are missing
407
+
408
+ // List all registrations
409
+ listRegistrations().forEach(reg => {
410
+ console.log(`${reg.name}: ${reg.type}, mocked: ${reg.isMocked}`)
411
+ })
412
+
413
+ // Enable debug logging
414
+ setDebug(true)
415
+ // [DI] Registered singleton: UserService
416
+ // [DI] Creating singleton: UserService
417
+ // [DI] Mocked UserService with MockUserService
418
418
  ```
419
419
 
420
- #### `validateRegistrations(...tokens)`
420
+ ### Isolated Containers
421
421
 
422
- Validate multiple registrations at once. Throws an error with helpful details if any are missing:
422
+ Create separate containers for parallel test execution or module isolation:
423
423
 
424
424
  ```javascript
425
- import {validateRegistrations} from 'decorator-dependency-injection';
426
-
427
- // At application startup - fail fast if dependencies are missing
428
- try {
429
- validateRegistrations(UserService, AuthService, 'databaseConnection');
430
- } catch (err) {
431
- // Error: Missing registrations: [UserService, databaseConnection].
432
- // Ensure these classes are decorated with @Singleton() or @Factory() before use.
433
- }
425
+ import { Container } from 'decorator-dependency-injection'
426
+
427
+ const container = new Container()
428
+ container.registerSingleton(MyService)
429
+ const instance = container.resolve(MyService)
434
430
  ```
435
431
 
436
- This is particularly useful in:
437
- - Application bootstrap to catch missing dependencies before runtime failures
438
- - Test setup to ensure mocks are properly configured
439
- - Module initialization to validate external dependencies
432
+ See the [Framework Integration Guide](docs/FRAMEWORK_INTEGRATION.md#server-side-rendering) for SSR request isolation patterns.
440
433
 
441
- ### Debug Mode
434
+ ### Server Middleware (Express/Koa/Fastify)
442
435
 
443
- Enable debug logging to understand the injection lifecycle:
436
+ For Node.js servers, use the middleware module to get **automatic request-scoped containers**:
444
437
 
445
438
  ```javascript
446
- import {setDebug} from 'decorator-dependency-injection';
439
+ import express from 'express'
440
+ import { containerMiddleware, resolve } from 'decorator-dependency-injection/middleware'
447
441
 
448
- setDebug(true);
442
+ const app = express()
443
+ app.use(containerMiddleware())
449
444
 
450
- // Now logs will appear when:
451
- // - Classes are registered: [DI] Registered singleton: UserService
452
- // - Instances are created: [DI] Creating singleton: UserService
453
- // - Cached singletons are returned: [DI] Returning cached singleton: UserService
454
- // - Mocks are registered: [DI] Mocked UserService with MockUserService
445
+ app.get('/user/:id', (req, res) => {
446
+ // Each request gets its own isolated container
447
+ const userService = resolve(UserService)
448
+ res.json(userService.getUser(req.params.id))
449
+ })
455
450
  ```
456
451
 
457
- This is helpful for:
458
- - Debugging injection order issues
459
- - Understanding when instances are created (eager vs lazy)
460
- - Troubleshooting circular dependencies
461
- - Verifying test mocks are applied correctly
462
-
463
- You can also use the ```@Mock``` decorator as a proxy instead of a full mock. Any method calls not implemented in the
464
- mock will be passed to the real dependency.
452
+ **Mixing Global and Request Scopes:**
465
453
 
466
454
  ```javascript
467
- import {Factory, Inject, Mock, resetMock} from 'decorator-dependency-injection'
468
-
469
- @Factory()
470
- class Dependency {
471
- method() {
472
- return 'real'
473
- }
474
-
475
- otherMethod() {
476
- return 'other'
477
- }
478
- }
479
-
480
- class Consumer {
481
- @Inject(Dependency) dependency
482
-
483
- constructor() {
484
- console.log(this.dependency.method(), this.dependency.otherMethod())
485
- }
486
- }
455
+ app.get('/data', (req, res) => {
456
+ // Use global singleton (e.g., database pool, config)
457
+ const db = resolve(DatabasePool, { scope: 'global' })
458
+
459
+ // Use request-scoped service (default)
460
+ const userService = resolve(UserService)
461
+
462
+ res.json(userService.getData(db))
463
+ })
464
+ ```
487
465
 
488
- // Test Code
466
+ See the [Framework Integration Guide](docs/FRAMEWORK_INTEGRATION.md#nodejs-server-middleware) for Koa, Fastify, and advanced patterns.
489
467
 
490
- @Mock(Dependency, true)
491
- class MockDependency {
492
- method() {
493
- return 'mock'
494
- }
495
- }
468
+ ---
496
469
 
497
- const consumer = new Consumer() // prints 'mock other'
470
+ ## API Reference
498
471
 
499
- resetMock(Dependency)
472
+ ### Decorators
500
473
 
501
- const consumer = new Consumer() // prints 'real other'
502
- ```
474
+ | Decorator | Description |
475
+ |-----------|-------------|
476
+ | `@Singleton(name?)` | Register a class as a singleton ([example](#singleton)) |
477
+ | `@Factory(name?)` | Register a class as a factory ([example](#factory)) |
478
+ | `@Inject(target, ...params)` | Inject a dependency into a field ([example](#singleton)) |
479
+ | `@InjectLazy(target, ...params)` | Inject lazily (on first access) ([example](#lazy-injection)) |
480
+ | `@Mock(target, proxy?)` | Replace a dependency with a mock ([example](#mocking-dependencies)) |
503
481
 
504
- For more examples, see the tests in the ```test``` directory.
482
+ ### Functions
505
483
 
506
- ## Advanced Usage
484
+ | Function | Description |
485
+ |----------|-------------|
486
+ | `resolve(target, ...params)` | Get an instance from the container ([example](#manual-resolution)) |
487
+ | `removeMock(target)` | Remove a mock, restore original ([example](#mocking-dependencies)) |
488
+ | `removeAllMocks()` | Remove all mocks ([example](#test-lifecycle)) |
489
+ | `resetSingletons(options?)` | Clear cached singleton instances ([example](#test-lifecycle)) |
490
+ | `clearContainer(options?)` | Clear all registrations ([example](#test-lifecycle)) |
491
+ | `isRegistered(target)` | Check if target is registered ([example](#container-introspection)) |
492
+ | `isMocked(target)` | Check if target is mocked ([example](#testing-best-practices)) |
493
+ | `getMockInstance(target)` | Get the mock instance ([example](#testing-best-practices)) |
494
+ | `validateRegistrations(...targets)` | Throw if any target is not registered ([example](#container-introspection)) |
495
+ | `listRegistrations()` | List all registrations ([example](#container-introspection)) |
496
+ | `getContainer()` | Get the default container ([example](#isolated-containers)) |
497
+ | `setDebug(enabled)` | Enable/disable debug logging ([example](#container-introspection)) |
498
+ | `unregister(target)` | Remove a registration |
507
499
 
508
- ### Using Isolated Containers
500
+ ### Middleware Functions (`/middleware`)
509
501
 
510
- For advanced scenarios like parallel test execution or module isolation, you can create separate containers:
502
+ | Function | Description |
503
+ |----------|-------------|
504
+ | `containerMiddleware(options?)` | Express/Fastify middleware ([example](#server-middleware-expresskoafastify)) |
505
+ | `koaContainerMiddleware(options?)` | Koa middleware ([example](docs/FRAMEWORK_INTEGRATION.md#koa)) |
506
+ | `resolve(target, options?)` | Get instance from request or global container ([example](#server-middleware-expresskoafastify)) |
507
+ | `getContainer()` | Get current request container (or global if outside request) |
508
+ | `getGlobalContainer()` | Get the global container |
509
+ | `runWithContainer(container, fn, options?)` | Run function with specific container ([example](docs/FRAMEWORK_INTEGRATION.md#testing-with-runwithcontainer)) |
510
+ | `withContainer(options?)` | Wrap handler with container context ([example](docs/FRAMEWORK_INTEGRATION.md#hono--fastify-handler-wrapper)) |
511
511
 
512
- ```javascript
513
- import {Container} from 'decorator-dependency-injection';
512
+ **Middleware Options:**
514
513
 
515
- const container1 = new Container();
516
- const container2 = new Container();
514
+ | Option | Type | Description |
515
+ |--------|------|-------------|
516
+ | `scope` | `'request' \| 'global'` | Container scope (default: `'request'`) |
517
+ | `debug` | `boolean` | Enable debug logging |
517
518
 
518
- class MyService {}
519
+ **Resolve Options:**
519
520
 
520
- // Register the same class in different containers
521
- container1.registerSingleton(MyService);
522
- container2.registerSingleton(MyService);
521
+ | Option | Type | Description |
522
+ |--------|------|-------------|
523
+ | `scope` | `'request' \| 'global'` | Which container to resolve from (default: `'request'`) |
524
+ | `params` | `any[]` | Constructor parameters to pass when creating instance |
523
525
 
524
- // Each container maintains its own singleton instance
525
- const ctx1 = container1.getContext(MyService);
526
- const ctx2 = container2.getContext(MyService);
526
+ ---
527
527
 
528
- const instance1 = container1.getInstance(ctx1, []);
529
- const instance2 = container2.getInstance(ctx2, []);
528
+ ## TypeScript Support
530
529
 
531
- console.log(instance1 === instance2); // false - different containers
532
- ```
530
+ Full TypeScript definitions are included:
533
531
 
534
- ### Accessing the Default Container
532
+ ```typescript
533
+ import { Constructor, InjectionToken, RegistrationInfo } from 'decorator-dependency-injection'
535
534
 
536
- You can access the default global container for programmatic registration:
535
+ // Constructor<T> - a class constructor
536
+ const MyClass: Constructor<MyService> = MyService
537
537
 
538
- ```javascript
539
- import {getContainer} from 'decorator-dependency-injection';
538
+ // InjectionToken<T> - class or string name
539
+ const token: InjectionToken<MyService> = MyService
540
+ const named: InjectionToken = 'myService'
540
541
 
541
- const container = getContainer();
542
- console.log(container.has(MyService)); // Check if a class is registered
542
+ // RegistrationInfo - from listRegistrations()
543
+ // { key, name, type, isMocked, hasInstance }
543
544
  ```
544
545
 
545
- ## TypeScript Support
546
+ ---
546
547
 
547
- The library includes TypeScript definitions with helpful type aliases:
548
+ ## Why Not [Other Library]?
548
549
 
549
- ```typescript
550
- import {Constructor, InjectionToken} from 'decorator-dependency-injection';
550
+ | Feature | This Library | InversifyJS | TSyringe | TypeDI |
551
+ |---------|--------------|-------------|----------|--------|
552
+ | Native decorators (Stage 3) | Yes | No (legacy) | No (legacy) | No (legacy) |
553
+ | Zero dependencies | Yes | No | No | No |
554
+ | No reflect-metadata | Yes | No | No | No |
555
+ | Built-in mocking | Yes | No | No | No |
556
+ | Bundle size | ~3KB | ~50KB | ~15KB | ~20KB |
551
557
 
552
- // Constructor<T> - a class constructor that creates instances of T
553
- const MyClass: Constructor<MyService> = MyService;
558
+ This library is ideal if you want simple, modern DI without the complexity of container configuration or reflection APIs.
554
559
 
555
- // InjectionToken<T> - either a class or a string name
556
- const token1: InjectionToken<MyService> = MyService;
557
- const token2: InjectionToken = 'myServiceName';
558
- ```
559
-
560
- All decorator functions and utilities are fully typed with generics for better autocomplete and type safety.
560
+ ---
561
561
 
562
- ## Running the tests
562
+ ## Related Topics
563
563
 
564
- To run the tests, run the following command in the project root.
564
+ Searching for: JavaScript dependency injection, TypeScript DI container, decorator-based IoC, inversion of control JavaScript, @Inject decorator, @Singleton pattern, service locator pattern, unit test mocking, Jest dependency injection, Vitest mocking.
565
565
 
566
- ```bash
567
- npm test
568
- ```
566
+ ---
569
567
 
570
568
  ## Version History
571
569
 
@@ -575,4 +573,6 @@ npm test
575
573
  - 1.0.3 - Added @InjectLazy decorator
576
574
  - 1.0.4 - Added Container abstraction, clearContainer(), TypeScript definitions, improved proxy support
577
575
  - 1.0.5 - Added private field and accessor support for @Inject and @InjectLazy, debug mode, validation helpers
578
- - 1.0.6 - Added resolve() function for non-decorator code
576
+ - 1.0.6 - Added resolve() function for non-decorator code
577
+ - 1.0.7 - Added more control for mocking in tests and improved compatibility
578
+ - 1.1.0 - Added framework integration guide and server middleware