decorator-dependency-injection 1.0.5 → 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
+ }
55
+
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.
8
68
 
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.
69
+ ---
13
70
 
14
71
  ## Installation
15
72
 
@@ -17,510 +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 {
139
+ class RequestLogger {
140
+ id = Math.random()
86
141
  }
87
142
 
88
- class Consumer {
89
- @Inject(Dependency) dependency // creates a new instance each time a new Consumer is created
143
+ class Handler {
144
+ @Inject(RequestLogger) logger // New instance for each Handler
90
145
  }
146
+
147
+ new Handler().logger.id !== new Handler().logger.id // true
91
148
  ```
92
149
 
93
- ### InjectLazy
150
+ ### Lazy Injection
94
151
 
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```.
152
+ By default, dependencies are created when the parent class is instantiated. Use `@InjectLazy` to defer creation until first access:
100
153
 
101
154
  ```javascript
102
- import {Singleton, InjectLazy} from 'decorator-dependency-injection';
155
+ import { Singleton, InjectLazy } from 'decorator-dependency-injection'
103
156
 
104
157
  @Singleton()
105
- class Dependency {
158
+ class ExpensiveService {
159
+ constructor() {
160
+ console.log('ExpensiveService created') // Only when accessed
161
+ }
106
162
  }
107
163
 
108
- class Consumer {
109
- @InjectLazy(Dependency) dependency // creates an instance only when the property is accessed
164
+ class MyClass {
165
+ @InjectLazy(ExpensiveService) service
166
+
167
+ doWork() {
168
+ this.service.process() // ExpensiveService created here
169
+ }
110
170
  }
111
171
  ```
112
172
 
113
- ### Private Field Injection
173
+ This is also useful for breaking circular dependencies.
114
174
 
115
- Both `@Inject` and `@InjectLazy` support private fields using the `#` syntax:
175
+ ### Passing Parameters
116
176
 
117
- ```javascript
118
- import {Singleton, Inject} from 'decorator-dependency-injection';
177
+ Pass constructor arguments after the class reference:
119
178
 
120
- @Singleton()
121
- class Database {
122
- query(sql) { /* ... */ }
123
- }
179
+ ```javascript
180
+ import { Factory, Inject } from 'decorator-dependency-injection'
124
181
 
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}`)
182
+ @Factory()
183
+ class Logger {
184
+ constructor(prefix, level) {
185
+ this.prefix = prefix
186
+ this.level = level
130
187
  }
131
188
  }
132
189
 
133
- const service = new UserService()
134
- service.#db // SyntaxError: Private field '#db' must be declared
190
+ class MyService {
191
+ @Inject(Logger, 'MyService', 'debug') logger
192
+ }
135
193
  ```
136
194
 
137
- ### The `accessor` Keyword
195
+ For singletons, parameters are only used on the first instantiation.
138
196
 
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**.
197
+ ---
141
198
 
142
- ```javascript
143
- class Example {
144
- accessor myField = 'value'
145
- }
199
+ ## Testing
146
200
 
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
- ```
201
+ ### Mocking Dependencies
154
202
 
155
- #### Using `accessor` with Injection
203
+ Use `@Mock` to replace a dependency with a test double:
156
204
 
157
205
  ```javascript
158
- import {Singleton, Inject, InjectLazy} from 'decorator-dependency-injection';
206
+ import { Singleton, Mock, removeMock, resolve } from 'decorator-dependency-injection'
159
207
 
160
208
  @Singleton()
161
- class ExpensiveService {
162
- constructor() {
163
- console.log('ExpensiveService created')
164
- }
209
+ class UserService {
210
+ getUser(id) { return fetchFromDatabase(id) }
165
211
  }
166
212
 
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
- }
213
+ // In your test file:
214
+ @Mock(UserService)
215
+ class MockUserService {
216
+ getUser(id) { return { id, name: 'Test User' } }
178
217
  }
179
- ```
180
218
 
181
- ### Injection Support Matrix
219
+ // Now all injections of UserService receive MockUserService
220
+ const user = resolve(UserService).getUser(1) // { id: 1, name: 'Test User' }
182
221
 
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 |
222
+ // Restore the original
223
+ removeMock(UserService)
224
+ ```
200
225
 
201
- *`@Inject` with accessors caches on first access, which is similar to lazy behavior.
226
+ ### Proxy Mocking
202
227
 
203
- #### Caveat: `@InjectLazy` with Private Fields
228
+ Mock only specific methods while keeping the rest of the original implementation:
204
229
 
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.
230
+ ```javascript
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
235
+ }
236
+ ```
208
237
 
209
- This applies to both instance and static private fields.
238
+ ### Test Lifecycle
210
239
 
211
- **Recommendation:** For true lazy injection with private members, use the `accessor` keyword:
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 |
212
246
 
213
247
  ```javascript
214
- // Not truly lazy (created at construction)
215
- @InjectLazy(ExpensiveService) #service
248
+ import { removeAllMocks, resetSingletons } from 'decorator-dependency-injection'
216
249
 
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
250
+ afterEach(() => {
251
+ removeAllMocks() // Restore original implementations
252
+ // OR
253
+ resetSingletons() // Keep mocks, but get fresh instances
254
+ })
226
255
  ```
227
256
 
228
- ### Static Field Injection
257
+ **Note:** These functions remove/restore mocks. They do NOT clear mock call history. If using Vitest/Jest spies, call `.mockClear()` separately.
229
258
 
230
- All injection decorators work with static fields. Static injections are shared across all instances of the class:
259
+ ### Testing Best Practices
231
260
 
232
261
  ```javascript
233
- import {Factory, Singleton, Inject} from 'decorator-dependency-injection';
262
+ import { Mock, removeAllMocks, resetSingletons } from 'decorator-dependency-injection'
263
+ import { vi, describe, it, beforeEach, afterEach } from 'vitest'
234
264
 
235
- @Singleton()
236
- class SharedConfig {
237
- apiUrl = 'https://api.example.com'
238
- }
265
+ // Hoist mock functions for per-test configuration
266
+ const mockGetUser = vi.hoisted(() => vi.fn())
239
267
 
240
- @Factory()
241
- class RequestLogger {
242
- static nextId = 0
243
- id = ++RequestLogger.nextId
268
+ @Mock(UserService)
269
+ class MockUserService {
270
+ getUser = mockGetUser
244
271
  }
245
272
 
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)
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
+ })
260
289
  ```
261
290
 
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.
291
+ Additional test utilities:
276
292
 
277
293
  ```javascript
278
- import {Factory, Inject} from 'decorator-dependency-injection';
294
+ import { isMocked, getMockInstance } from 'decorator-dependency-injection'
279
295
 
280
- @Factory
281
- class Dependency {
282
- constructor(param1, param2) {
283
- this.param1 = param1
284
- this.param2 = param2
285
- }
286
- }
296
+ // Check if a class is currently mocked
297
+ if (isMocked(UserService)) { /* ... */ }
287
298
 
288
- class Consumer {
289
- @Inject(Dependency, 'myParam', 'myOtherParam') dependency
290
- }
299
+ // Access the mock instance to configure it
300
+ getMockInstance(UserService).someMethod.mockReturnValue('test')
291
301
  ```
292
302
 
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.
303
+ ---
295
304
 
296
- ## Mocking dependencies for testing
305
+ ## Advanced Features
297
306
 
298
- You can mock dependencies by using the ```@Mock``` decorator with a function that returns the mock dependency.
307
+ ### Private Fields
299
308
 
300
- ```javascript
301
- import {Factory, Inject, Mock, resetMock} from 'decorator-dependency-injection'
302
-
303
- @Factory()
304
- class Dependency {
305
- method() {
306
- return 'real'
307
- }
308
- }
309
+ Both `@Inject` and `@InjectLazy` support private fields:
309
310
 
310
- class Consumer {
311
- @Inject(Dependency) dependency
311
+ ```javascript
312
+ class UserService {
313
+ @Inject(Database) #db // Truly private
312
314
 
313
- constructor() {
314
- console.log(this.dependency.method())
315
+ getUser(id) {
316
+ return this.#db.query(`SELECT * FROM users WHERE id = ${id}`)
315
317
  }
316
318
  }
319
+ ```
317
320
 
318
- // Test Code
321
+ For lazy injection with private fields, use the `accessor` keyword:
319
322
 
320
- @Mock(Dependency)
321
- class MockDependency {
322
- method() {
323
- return 'mock'
324
- }
323
+ ```javascript
324
+ class UserService {
325
+ @InjectLazy(Database) accessor #db // Lazy AND private
325
326
  }
327
+ ```
326
328
 
327
- const consumer = new Consumer() // prints 'mock'
329
+ <details>
330
+ <summary><strong>Why accessor for lazy private fields?</strong></summary>
328
331
 
329
- resetMock(Dependency)
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.
330
333
 
331
- const consumer = new Consumer() // prints 'real'
332
- ```
334
+ </details>
333
335
 
334
- ### Resetting Mocks
336
+ ### Static Fields
335
337
 
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.
338
+ Inject at the class level (shared across all instances):
338
339
 
339
340
  ```javascript
340
- import {resetMock, resetMocks} from 'decorator-dependency-injection';
341
+ class ApiService {
342
+ @Inject(Config) static config // Class-level singleton
343
+ @Inject(Logger) logger // Instance-level
341
344
 
342
- resetMock(Dependency); // Restores the original Dependency implementation
343
- resetMocks(); // Restores all mocked dependencies
345
+ getUrl() {
346
+ return ApiService.config.apiUrl
347
+ }
348
+ }
344
349
  ```
345
350
 
346
- ### Clearing the Container
351
+ ### Named Registrations
347
352
 
348
- For complete test isolation, you can clear all registered instances from the container:
353
+ Register dependencies under string names instead of class references:
349
354
 
350
355
  ```javascript
351
- import {clearContainer} from 'decorator-dependency-injection';
356
+ @Singleton('database')
357
+ class PostgresDatabase { }
352
358
 
353
- clearContainer(); // Removes all registered singletons, factories, and mocks
359
+ class UserService {
360
+ @Inject('database') db
361
+ }
354
362
  ```
355
363
 
356
- ### Validation Helpers
364
+ ### Manual Resolution
357
365
 
358
- The library provides utilities to validate registrations at runtime, which is useful for catching configuration
359
- errors early:
360
-
361
- #### `isRegistered(clazzOrName)`
362
-
363
- Check if a class or name is registered:
366
+ Retrieve instances programmatically (useful for non-class code):
364
367
 
365
368
  ```javascript
366
- import {Singleton, isRegistered} from 'decorator-dependency-injection';
367
-
368
- @Singleton()
369
- class MyService {}
369
+ import { resolve } from 'decorator-dependency-injection'
370
370
 
371
- console.log(isRegistered(MyService)); // true
372
- console.log(isRegistered('unknownName')); // false
373
- ```
374
-
375
- #### `validateRegistrations(...tokens)`
371
+ function handleRequest(req) {
372
+ const userService = resolve(UserService)
373
+ return userService.getUser(req.userId)
374
+ }
376
375
 
377
- Validate multiple registrations at once. Throws an error with helpful details if any are missing:
376
+ // With parameters
377
+ const logger = resolve(Logger, 'my-module')
378
378
 
379
- ```javascript
380
- import {validateRegistrations} from 'decorator-dependency-injection';
381
-
382
- // At application startup - fail fast if dependencies are missing
383
- try {
384
- validateRegistrations(UserService, AuthService, 'databaseConnection');
385
- } catch (err) {
386
- // Error: Missing registrations: [UserService, databaseConnection].
387
- // Ensure these classes are decorated with @Singleton() or @Factory() before use.
388
- }
379
+ // With named registration
380
+ const db = resolve('database')
389
381
  ```
390
382
 
391
- This is particularly useful in:
392
- - Application bootstrap to catch missing dependencies before runtime failures
393
- - Test setup to ensure mocks are properly configured
394
- - Module initialization to validate external dependencies
395
-
396
- ### Debug Mode
383
+ ### Container Introspection
397
384
 
398
- Enable debug logging to understand the injection lifecycle:
385
+ Debug and inspect the container state:
399
386
 
400
387
  ```javascript
401
- import {setDebug} from 'decorator-dependency-injection';
402
-
403
- setDebug(true);
404
-
405
- // Now logs will appear when:
406
- // - Classes are registered: [DI] Registered singleton: UserService
407
- // - Instances are created: [DI] Creating singleton: UserService
408
- // - Cached singletons are returned: [DI] Returning cached singleton: UserService
409
- // - Mocks are registered: [DI] Mocked UserService with MockUserService
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
410
413
  ```
411
414
 
412
- This is helpful for:
413
- - Debugging injection order issues
414
- - Understanding when instances are created (eager vs lazy)
415
- - Troubleshooting circular dependencies
416
- - Verifying test mocks are applied correctly
415
+ ### Isolated Containers
417
416
 
418
- You can also use the ```@Mock``` decorator as a proxy instead of a full mock. Any method calls not implemented in the
419
- mock will be passed to the real dependency.
417
+ Create separate containers for parallel test execution or module isolation:
420
418
 
421
419
  ```javascript
422
- import {Factory, Inject, Mock, resetMock} from 'decorator-dependency-injection'
423
-
424
- @Factory()
425
- class Dependency {
426
- method() {
427
- return 'real'
428
- }
429
-
430
- otherMethod() {
431
- return 'other'
432
- }
433
- }
434
-
435
- class Consumer {
436
- @Inject(Dependency) dependency
437
-
438
- constructor() {
439
- console.log(this.dependency.method(), this.dependency.otherMethod())
440
- }
441
- }
442
-
443
- // Test Code
444
-
445
- @Mock(Dependency, true)
446
- class MockDependency {
447
- method() {
448
- return 'mock'
449
- }
450
- }
451
-
452
- const consumer = new Consumer() // prints 'mock other'
420
+ import { Container } from 'decorator-dependency-injection'
453
421
 
454
- resetMock(Dependency)
455
-
456
- const consumer = new Consumer() // prints 'real other'
422
+ const container = new Container()
423
+ container.registerSingleton(MyService)
424
+ const instance = container.resolve(MyService)
457
425
  ```
458
426
 
459
- For more examples, see the tests in the ```test``` directory.
427
+ ---
460
428
 
461
- ## Advanced Usage
429
+ ## API Reference
462
430
 
463
- ### Using Isolated Containers
431
+ ### Decorators
464
432
 
465
- For advanced scenarios like parallel test execution or module isolation, you can create separate containers:
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 |
466
440
 
467
- ```javascript
468
- import {Container} from 'decorator-dependency-injection';
441
+ ### Functions
469
442
 
470
- const container1 = new Container();
471
- const container2 = new Container();
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 |
472
458
 
473
- class MyService {}
459
+ ---
474
460
 
475
- // Register the same class in different containers
476
- container1.registerSingleton(MyService);
477
- container2.registerSingleton(MyService);
478
-
479
- // Each container maintains its own singleton instance
480
- const ctx1 = container1.getContext(MyService);
481
- const ctx2 = container2.getContext(MyService);
482
-
483
- const instance1 = container1.getInstance(ctx1, []);
484
- const instance2 = container2.getInstance(ctx2, []);
461
+ ## TypeScript Support
485
462
 
486
- console.log(instance1 === instance2); // false - different containers
487
- ```
463
+ Full TypeScript definitions are included:
488
464
 
489
- ### Accessing the Default Container
465
+ ```typescript
466
+ import { Constructor, InjectionToken, RegistrationInfo } from 'decorator-dependency-injection'
490
467
 
491
- You can access the default global container for programmatic registration:
468
+ // Constructor<T> - a class constructor
469
+ const MyClass: Constructor<MyService> = MyService
492
470
 
493
- ```javascript
494
- import {getContainer} from 'decorator-dependency-injection';
471
+ // InjectionToken<T> - class or string name
472
+ const token: InjectionToken<MyService> = MyService
473
+ const named: InjectionToken = 'myService'
495
474
 
496
- const container = getContainer();
497
- console.log(container.has(MyService)); // Check if a class is registered
475
+ // RegistrationInfo - from listRegistrations()
476
+ // { key, name, type, isMocked, hasInstance }
498
477
  ```
499
478
 
500
- ## TypeScript Support
501
-
502
- The library includes TypeScript definitions with helpful type aliases:
479
+ ---
503
480
 
504
- ```typescript
505
- import {Constructor, InjectionToken} from 'decorator-dependency-injection';
481
+ ## Why Not [Other Library]?
506
482
 
507
- // Constructor<T> - a class constructor that creates instances of T
508
- 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 |
509
490
 
510
- // InjectionToken<T> - either a class or a string name
511
- const token1: InjectionToken<MyService> = MyService;
512
- const token2: InjectionToken = 'myServiceName';
513
- ```
491
+ This library is ideal if you want simple, modern DI without the complexity of container configuration or reflection APIs.
514
492
 
515
- All decorator functions and utilities are fully typed with generics for better autocomplete and type safety.
493
+ ---
516
494
 
517
- ## Running the tests
495
+ ## Related Topics
518
496
 
519
- 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.
520
498
 
521
- ```bash
522
- npm test
523
- ```
499
+ ---
524
500
 
525
501
  ## Version History
526
502
 
@@ -529,4 +505,6 @@ npm test
529
505
  - 1.0.2 - Added proxy option to @Mock decorator
530
506
  - 1.0.3 - Added @InjectLazy decorator
531
507
  - 1.0.4 - Added Container abstraction, clearContainer(), TypeScript definitions, improved proxy support
532
- - 1.0.5 - Added private field and accessor support for @Inject and @InjectLazy, debug mode, validation helpers
508
+ - 1.0.5 - Added private field and accessor support for @Inject and @InjectLazy, debug mode, validation helpers
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