decorator-dependency-injection 1.0.6 → 1.0.7

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