decorator-dependency-injection 1.0.3 → 1.0.5
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 +307 -16
- package/eslint.config.js +77 -0
- package/index.d.ts +227 -0
- package/index.js +207 -143
- package/package.json +46 -7
- package/src/Container.js +211 -0
- package/src/proxy.js +42 -0
- package/.github/workflows/release.yml +0 -129
- package/babel.config.json +0 -6
- package/test/injection.test.js +0 -309
- package/test/injectionLazy.test.js +0 -249
- package/test/mock.test.js +0 -295
- package/test/proxy.test.js +0 -130
package/README.md
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](http://badge.fury.io/js/decorator-dependency-injection)
|
|
4
4
|
[](https://github.com/mallocator/decorator-dependency-injection/actions/workflows/release.yml)
|
|
5
|
+
[](https://github.com/mallocator/decorator-dependency-injection)
|
|
5
6
|
|
|
6
7
|
## Description
|
|
7
8
|
|
|
@@ -61,9 +62,9 @@ The ```@Singleton``` decorator is used to inject a single instance of a dependen
|
|
|
61
62
|
want to share the same instance of a class across multiple classes.
|
|
62
63
|
|
|
63
64
|
```javascript
|
|
64
|
-
import {Singleton} from 'decorator-dependency-injection';
|
|
65
|
+
import {Singleton, Inject} from 'decorator-dependency-injection';
|
|
65
66
|
|
|
66
|
-
@Singleton
|
|
67
|
+
@Singleton()
|
|
67
68
|
class Dependency {
|
|
68
69
|
}
|
|
69
70
|
|
|
@@ -78,9 +79,9 @@ The ```@Factory``` decorator is used to inject a new instance of a dependency in
|
|
|
78
79
|
This is useful when you want to create a new instance of a class each time it is injected.
|
|
79
80
|
|
|
80
81
|
```javascript
|
|
81
|
-
import {Factory} from 'decorator-dependency-injection';
|
|
82
|
+
import {Factory, Inject} from 'decorator-dependency-injection';
|
|
82
83
|
|
|
83
|
-
@Factory
|
|
84
|
+
@Factory()
|
|
84
85
|
class Dependency {
|
|
85
86
|
}
|
|
86
87
|
|
|
@@ -89,26 +90,185 @@ class Consumer {
|
|
|
89
90
|
}
|
|
90
91
|
```
|
|
91
92
|
|
|
92
|
-
###
|
|
93
|
+
### InjectLazy
|
|
93
94
|
|
|
94
95
|
```@Inject``` annotated properties are evaluated during instance initialization. That means that all properties should
|
|
95
96
|
be accessible in the constructor. That also means that we're creating an instance no matter if you access the property
|
|
96
|
-
or not. If you want to only create an instance when you access the property, you can use the ```@
|
|
97
|
+
or not. If you want to only create an instance when you access the property, you can use the ```@InjectLazy```
|
|
97
98
|
decorator. This will create the instance only when the property is accessed for the first time. Note that this also
|
|
98
|
-
works from the constructor, same as the regular ```@Inject```.
|
|
99
|
+
works from the constructor, same as the regular ```@Inject```.
|
|
99
100
|
|
|
100
101
|
```javascript
|
|
101
|
-
import {
|
|
102
|
+
import {Singleton, InjectLazy} from 'decorator-dependency-injection';
|
|
102
103
|
|
|
103
|
-
@Singleton
|
|
104
|
+
@Singleton()
|
|
104
105
|
class Dependency {
|
|
105
106
|
}
|
|
106
107
|
|
|
107
108
|
class Consumer {
|
|
108
|
-
@
|
|
109
|
+
@InjectLazy(Dependency) dependency // creates an instance only when the property is accessed
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Private Field Injection
|
|
114
|
+
|
|
115
|
+
Both `@Inject` and `@InjectLazy` support private fields using the `#` syntax:
|
|
116
|
+
|
|
117
|
+
```javascript
|
|
118
|
+
import {Singleton, Inject} from 'decorator-dependency-injection';
|
|
119
|
+
|
|
120
|
+
@Singleton()
|
|
121
|
+
class Database {
|
|
122
|
+
query(sql) { /* ... */ }
|
|
123
|
+
}
|
|
124
|
+
|
|
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}`)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const service = new UserService()
|
|
134
|
+
service.#db // SyntaxError: Private field '#db' must be declared
|
|
135
|
+
```
|
|
136
|
+
|
|
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**.
|
|
141
|
+
|
|
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
|
+
```
|
|
154
|
+
|
|
155
|
+
#### Using `accessor` with Injection
|
|
156
|
+
|
|
157
|
+
```javascript
|
|
158
|
+
import {Singleton, Inject, InjectLazy} from 'decorator-dependency-injection';
|
|
159
|
+
|
|
160
|
+
@Singleton()
|
|
161
|
+
class ExpensiveService {
|
|
162
|
+
constructor() {
|
|
163
|
+
console.log('ExpensiveService created')
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
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
|
+
}
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
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 |
|
|
200
|
+
|
|
201
|
+
*`@Inject` with accessors caches on first access, which is similar to lazy behavior.
|
|
202
|
+
|
|
203
|
+
#### Caveat: `@InjectLazy` with Private Fields
|
|
204
|
+
|
|
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.
|
|
208
|
+
|
|
209
|
+
This applies to both instance and static private fields.
|
|
210
|
+
|
|
211
|
+
**Recommendation:** For true lazy injection with private members, use the `accessor` keyword:
|
|
212
|
+
|
|
213
|
+
```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';
|
|
234
|
+
|
|
235
|
+
@Singleton()
|
|
236
|
+
class SharedConfig {
|
|
237
|
+
apiUrl = 'https://api.example.com'
|
|
109
238
|
}
|
|
239
|
+
|
|
240
|
+
@Factory()
|
|
241
|
+
class RequestLogger {
|
|
242
|
+
static nextId = 0
|
|
243
|
+
id = ++RequestLogger.nextId
|
|
244
|
+
}
|
|
245
|
+
|
|
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)
|
|
110
260
|
```
|
|
111
261
|
|
|
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
|
+
|
|
112
272
|
## Passing parameters to a dependency
|
|
113
273
|
|
|
114
274
|
You can pass parameters to a dependency by using the ```@Inject``` decorator with a function that returns the
|
|
@@ -138,9 +298,9 @@ will only be passed to the dependency the first time it is created.
|
|
|
138
298
|
You can mock dependencies by using the ```@Mock``` decorator with a function that returns the mock dependency.
|
|
139
299
|
|
|
140
300
|
```javascript
|
|
141
|
-
import {Factory, Inject, Mock} from 'decorator-dependency-injection'
|
|
301
|
+
import {Factory, Inject, Mock, resetMock} from 'decorator-dependency-injection'
|
|
142
302
|
|
|
143
|
-
@Factory
|
|
303
|
+
@Factory()
|
|
144
304
|
class Dependency {
|
|
145
305
|
method() {
|
|
146
306
|
return 'real'
|
|
@@ -177,18 +337,91 @@ The `resetMock` utility function allows you to remove any active mock for a depe
|
|
|
177
337
|
implementation. This is useful for cleaning up after tests or switching between real and mock dependencies.
|
|
178
338
|
|
|
179
339
|
```javascript
|
|
180
|
-
import {resetMock} from 'decorator-dependency-injection';
|
|
340
|
+
import {resetMock, resetMocks} from 'decorator-dependency-injection';
|
|
181
341
|
|
|
182
342
|
resetMock(Dependency); // Restores the original Dependency implementation
|
|
343
|
+
resetMocks(); // Restores all mocked dependencies
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### Clearing the Container
|
|
347
|
+
|
|
348
|
+
For complete test isolation, you can clear all registered instances from the container:
|
|
349
|
+
|
|
350
|
+
```javascript
|
|
351
|
+
import {clearContainer} from 'decorator-dependency-injection';
|
|
352
|
+
|
|
353
|
+
clearContainer(); // Removes all registered singletons, factories, and mocks
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
### Validation Helpers
|
|
357
|
+
|
|
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:
|
|
364
|
+
|
|
365
|
+
```javascript
|
|
366
|
+
import {Singleton, isRegistered} from 'decorator-dependency-injection';
|
|
367
|
+
|
|
368
|
+
@Singleton()
|
|
369
|
+
class MyService {}
|
|
370
|
+
|
|
371
|
+
console.log(isRegistered(MyService)); // true
|
|
372
|
+
console.log(isRegistered('unknownName')); // false
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
#### `validateRegistrations(...tokens)`
|
|
376
|
+
|
|
377
|
+
Validate multiple registrations at once. Throws an error with helpful details if any are missing:
|
|
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
|
+
}
|
|
389
|
+
```
|
|
390
|
+
|
|
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
|
|
397
|
+
|
|
398
|
+
Enable debug logging to understand the injection lifecycle:
|
|
399
|
+
|
|
400
|
+
```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
|
|
183
410
|
```
|
|
184
411
|
|
|
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
|
|
417
|
+
|
|
185
418
|
You can also use the ```@Mock``` decorator as a proxy instead of a full mock. Any method calls not implemented in the
|
|
186
419
|
mock will be passed to the real dependency.
|
|
187
420
|
|
|
188
421
|
```javascript
|
|
189
|
-
import {Factory, Inject, Mock} from 'decorator-dependency-injection'
|
|
422
|
+
import {Factory, Inject, Mock, resetMock} from 'decorator-dependency-injection'
|
|
190
423
|
|
|
191
|
-
@Factory
|
|
424
|
+
@Factory()
|
|
192
425
|
class Dependency {
|
|
193
426
|
method() {
|
|
194
427
|
return 'real'
|
|
@@ -225,6 +458,62 @@ const consumer = new Consumer() // prints 'real other'
|
|
|
225
458
|
|
|
226
459
|
For more examples, see the tests in the ```test``` directory.
|
|
227
460
|
|
|
461
|
+
## Advanced Usage
|
|
462
|
+
|
|
463
|
+
### Using Isolated Containers
|
|
464
|
+
|
|
465
|
+
For advanced scenarios like parallel test execution or module isolation, you can create separate containers:
|
|
466
|
+
|
|
467
|
+
```javascript
|
|
468
|
+
import {Container} from 'decorator-dependency-injection';
|
|
469
|
+
|
|
470
|
+
const container1 = new Container();
|
|
471
|
+
const container2 = new Container();
|
|
472
|
+
|
|
473
|
+
class MyService {}
|
|
474
|
+
|
|
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, []);
|
|
485
|
+
|
|
486
|
+
console.log(instance1 === instance2); // false - different containers
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
### Accessing the Default Container
|
|
490
|
+
|
|
491
|
+
You can access the default global container for programmatic registration:
|
|
492
|
+
|
|
493
|
+
```javascript
|
|
494
|
+
import {getContainer} from 'decorator-dependency-injection';
|
|
495
|
+
|
|
496
|
+
const container = getContainer();
|
|
497
|
+
console.log(container.has(MyService)); // Check if a class is registered
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
## TypeScript Support
|
|
501
|
+
|
|
502
|
+
The library includes TypeScript definitions with helpful type aliases:
|
|
503
|
+
|
|
504
|
+
```typescript
|
|
505
|
+
import {Constructor, InjectionToken} from 'decorator-dependency-injection';
|
|
506
|
+
|
|
507
|
+
// Constructor<T> - a class constructor that creates instances of T
|
|
508
|
+
const MyClass: Constructor<MyService> = MyService;
|
|
509
|
+
|
|
510
|
+
// InjectionToken<T> - either a class or a string name
|
|
511
|
+
const token1: InjectionToken<MyService> = MyService;
|
|
512
|
+
const token2: InjectionToken = 'myServiceName';
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
All decorator functions and utilities are fully typed with generics for better autocomplete and type safety.
|
|
516
|
+
|
|
228
517
|
## Running the tests
|
|
229
518
|
|
|
230
519
|
To run the tests, run the following command in the project root.
|
|
@@ -238,4 +527,6 @@ npm test
|
|
|
238
527
|
- 1.0.0 - Initial release
|
|
239
528
|
- 1.0.1 - Automated release with GitHub Actions
|
|
240
529
|
- 1.0.2 - Added proxy option to @Mock decorator
|
|
241
|
-
- 1.0.3 - Added @
|
|
530
|
+
- 1.0.3 - Added @InjectLazy decorator
|
|
531
|
+
- 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
|
package/eslint.config.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { defineConfig } from "eslint/config";
|
|
2
|
+
import js from "@eslint/js";
|
|
3
|
+
import babelParser from "@babel/eslint-parser";
|
|
4
|
+
|
|
5
|
+
export default defineConfig([
|
|
6
|
+
// Recommended base config
|
|
7
|
+
js.configs.recommended,
|
|
8
|
+
|
|
9
|
+
// Global ignores
|
|
10
|
+
{
|
|
11
|
+
ignores: ["node_modules/**", "coverage/**", "docs/**", ".history/**"]
|
|
12
|
+
},
|
|
13
|
+
|
|
14
|
+
// Source files configuration
|
|
15
|
+
{
|
|
16
|
+
name: "source-files",
|
|
17
|
+
files: ["index.js", "src/**/*.js"],
|
|
18
|
+
languageOptions: {
|
|
19
|
+
ecmaVersion: 2022,
|
|
20
|
+
sourceType: "module",
|
|
21
|
+
parser: babelParser,
|
|
22
|
+
parserOptions: {
|
|
23
|
+
requireConfigFile: true,
|
|
24
|
+
babelOptions: {
|
|
25
|
+
configFile: "./babel.config.json"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
globals: {
|
|
29
|
+
console: "readonly",
|
|
30
|
+
process: "readonly"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
rules: {
|
|
34
|
+
"no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
|
|
35
|
+
"prefer-const": "error",
|
|
36
|
+
"no-var": "error",
|
|
37
|
+
"eqeqeq": ["error", "always"],
|
|
38
|
+
"no-throw-literal": "error"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
// Test files with relaxed rules and Jest globals
|
|
43
|
+
{
|
|
44
|
+
name: "test-files",
|
|
45
|
+
files: ["test/**/*.js"],
|
|
46
|
+
languageOptions: {
|
|
47
|
+
ecmaVersion: 2022,
|
|
48
|
+
sourceType: "module",
|
|
49
|
+
parser: babelParser,
|
|
50
|
+
parserOptions: {
|
|
51
|
+
requireConfigFile: true,
|
|
52
|
+
babelOptions: {
|
|
53
|
+
configFile: "./babel.config.json"
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
globals: {
|
|
57
|
+
console: "readonly",
|
|
58
|
+
process: "readonly",
|
|
59
|
+
describe: "readonly",
|
|
60
|
+
it: "readonly",
|
|
61
|
+
expect: "readonly",
|
|
62
|
+
beforeEach: "readonly",
|
|
63
|
+
afterEach: "readonly",
|
|
64
|
+
jest: "readonly",
|
|
65
|
+
fail: "readonly"
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
rules: {
|
|
69
|
+
// In tests, decorated classes are often "used" by the decorator system (side effects)
|
|
70
|
+
// rather than being referenced directly. Also allow underscore-prefixed vars.
|
|
71
|
+
"no-unused-vars": ["warn", {
|
|
72
|
+
"argsIgnorePattern": "^_",
|
|
73
|
+
"varsIgnorePattern": "^_|Mock|Service|Factory|Singleton|Consumer|Injection|Lazy|^[A-Z]$|^[A-Z][0-9]$"
|
|
74
|
+
}]
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
]);
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for decorator-dependency-injection
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A class constructor type.
|
|
7
|
+
* @template T The instance type
|
|
8
|
+
*/
|
|
9
|
+
export type Constructor<T = any> = new (...args: any[]) => T
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Valid injection target: either a class constructor or a string name.
|
|
13
|
+
*/
|
|
14
|
+
export type InjectionToken<T = any> = string | Constructor<T>
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Context for registered instances in the container
|
|
18
|
+
*/
|
|
19
|
+
export interface InstanceContext {
|
|
20
|
+
/** The type of registration */
|
|
21
|
+
type: 'singleton' | 'factory'
|
|
22
|
+
/** The current class constructor (may be a mock) */
|
|
23
|
+
clazz: new (...args: any[]) => any
|
|
24
|
+
/** The original class constructor if mocked */
|
|
25
|
+
originalClazz?: new (...args: any[]) => any
|
|
26
|
+
/** The cached singleton instance */
|
|
27
|
+
instance?: any
|
|
28
|
+
/** Whether to use proxy mocking */
|
|
29
|
+
proxy?: boolean
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* A dependency injection container that manages singleton and factory instances.
|
|
34
|
+
*/
|
|
35
|
+
export declare class Container {
|
|
36
|
+
/**
|
|
37
|
+
* Enable or disable debug logging.
|
|
38
|
+
* When enabled, logs when instances are created.
|
|
39
|
+
*/
|
|
40
|
+
setDebug(enabled: boolean): void
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Register a class as a singleton.
|
|
44
|
+
*/
|
|
45
|
+
registerSingleton<T>(clazz: Constructor<T>, name?: string): void
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Register a class as a factory.
|
|
49
|
+
*/
|
|
50
|
+
registerFactory<T>(clazz: Constructor<T>, name?: string): void
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Get the context for a given class or name.
|
|
54
|
+
* @throws Error if the class/name is not registered
|
|
55
|
+
*/
|
|
56
|
+
getContext<T>(clazzOrName: InjectionToken<T>): InstanceContext
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Check if a class or name is registered.
|
|
60
|
+
*/
|
|
61
|
+
has<T>(clazzOrName: InjectionToken<T>): boolean
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get or create an instance based on the context.
|
|
65
|
+
*/
|
|
66
|
+
getInstance<T>(instanceContext: InstanceContext, params: any[]): T
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Register a mock for an existing class.
|
|
70
|
+
*/
|
|
71
|
+
registerMock<T>(
|
|
72
|
+
targetClazzOrName: InjectionToken<T>,
|
|
73
|
+
mockClazz: Constructor<T>,
|
|
74
|
+
useProxy?: boolean
|
|
75
|
+
): void
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Reset a specific mock to its original class.
|
|
79
|
+
*/
|
|
80
|
+
resetMock<T>(clazzOrName: InjectionToken<T>): void
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Reset all mocks to their original classes.
|
|
84
|
+
*/
|
|
85
|
+
resetAllMocks(): void
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Clear all registered instances and mocks.
|
|
89
|
+
*/
|
|
90
|
+
clear(): void
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Register a class as a singleton.
|
|
95
|
+
* @param name Optional name to register the singleton under
|
|
96
|
+
*/
|
|
97
|
+
export declare function Singleton(name?: string): ClassDecorator
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Register a class as a factory.
|
|
101
|
+
* @param name Optional name to register the factory under
|
|
102
|
+
*/
|
|
103
|
+
export declare function Factory(name?: string): ClassDecorator
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Decorator return type that works for both fields and accessors.
|
|
107
|
+
* For fields, returns a function that provides the initial value.
|
|
108
|
+
* For accessors, returns an object with get/set/init.
|
|
109
|
+
*/
|
|
110
|
+
export type FieldOrAccessorDecorator = (
|
|
111
|
+
target: undefined,
|
|
112
|
+
context: ClassFieldDecoratorContext | ClassAccessorDecoratorContext
|
|
113
|
+
) => void | ((initialValue: any) => any) | ClassAccessorDecoratorResult<any, any>
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Inject a singleton or factory instance into a class field or accessor.
|
|
117
|
+
*
|
|
118
|
+
* Supports:
|
|
119
|
+
* - Public fields: `@Inject(MyClass) myField`
|
|
120
|
+
* - Private fields: `@Inject(MyClass) #myField`
|
|
121
|
+
* - Public accessors: `@Inject(MyClass) accessor myField`
|
|
122
|
+
* - Private accessors: `@Inject(MyClass) accessor #myField`
|
|
123
|
+
*
|
|
124
|
+
* @param clazzOrName The class or name to inject
|
|
125
|
+
* @param params Optional parameters to pass to the constructor
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* class MyService {
|
|
129
|
+
* @Inject(Database) db
|
|
130
|
+
* @Inject(Logger) #logger // private field
|
|
131
|
+
* @Inject(Cache) accessor cache // accessor (recommended for lazy-like behavior)
|
|
132
|
+
* }
|
|
133
|
+
*/
|
|
134
|
+
export declare function Inject<T>(
|
|
135
|
+
clazzOrName: InjectionToken<T>,
|
|
136
|
+
...params: any[]
|
|
137
|
+
): FieldOrAccessorDecorator
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Inject a singleton or factory instance lazily into a class field or accessor.
|
|
141
|
+
* The instance is created on first access.
|
|
142
|
+
*
|
|
143
|
+
* Supports:
|
|
144
|
+
* - Public fields: `@InjectLazy(MyClass) myField` (true lazy)
|
|
145
|
+
* - Private fields: `@InjectLazy(MyClass) #myField` (not truly lazy - use accessor instead)
|
|
146
|
+
* - Public accessors: `@InjectLazy(MyClass) accessor myField` (true lazy)
|
|
147
|
+
* - Private accessors: `@InjectLazy(MyClass) accessor #myField` (true lazy, recommended)
|
|
148
|
+
*
|
|
149
|
+
* Note: For true lazy injection with private members, use the accessor syntax:
|
|
150
|
+
* `@InjectLazy(MyClass) accessor #myField`
|
|
151
|
+
*
|
|
152
|
+
* @param clazzOrName The class or name to inject
|
|
153
|
+
* @param params Optional parameters to pass to the constructor
|
|
154
|
+
*
|
|
155
|
+
* @example
|
|
156
|
+
* class MyService {
|
|
157
|
+
* @InjectLazy(ExpensiveService) accessor #expensiveService
|
|
158
|
+
* }
|
|
159
|
+
*/
|
|
160
|
+
export declare function InjectLazy<T>(
|
|
161
|
+
clazzOrName: InjectionToken<T>,
|
|
162
|
+
...params: any[]
|
|
163
|
+
): FieldOrAccessorDecorator
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Mark a class as a mock for another class.
|
|
167
|
+
* @param mockedClazzOrName The class or name to mock
|
|
168
|
+
* @param proxy If true, unmocked methods delegate to the original
|
|
169
|
+
*/
|
|
170
|
+
export declare function Mock<T>(
|
|
171
|
+
mockedClazzOrName: InjectionToken<T>,
|
|
172
|
+
proxy?: boolean
|
|
173
|
+
): ClassDecorator
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Reset all mocks to their original classes.
|
|
177
|
+
*/
|
|
178
|
+
export declare function resetMocks(): void
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Reset a specific mock to its original class.
|
|
182
|
+
* @param clazzOrName The class or name to reset
|
|
183
|
+
*/
|
|
184
|
+
export declare function resetMock<T>(clazzOrName: InjectionToken<T>): void
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Clear all registered instances and mocks from the container.
|
|
188
|
+
*/
|
|
189
|
+
export declare function clearContainer(): void
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Get the default container instance.
|
|
193
|
+
*/
|
|
194
|
+
export declare function getContainer(): Container
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Enable or disable debug logging for dependency injection.
|
|
198
|
+
* When enabled, logs when instances are registered, created, and mocked.
|
|
199
|
+
* @param enabled Whether to enable debug mode
|
|
200
|
+
*/
|
|
201
|
+
export declare function setDebug(enabled: boolean): void
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Check if a class or name is registered in the default container.
|
|
205
|
+
* Useful for validation before injection.
|
|
206
|
+
* @param clazzOrName The class or name to check
|
|
207
|
+
* @returns true if registered, false otherwise
|
|
208
|
+
*/
|
|
209
|
+
export declare function isRegistered<T>(clazzOrName: InjectionToken<T>): boolean
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Validate that all provided injection tokens are registered.
|
|
213
|
+
* Throws an error with details about missing registrations.
|
|
214
|
+
* Useful for fail-fast validation at application startup.
|
|
215
|
+
* @param tokens Array of classes or names to validate
|
|
216
|
+
* @throws Error if any token is not registered
|
|
217
|
+
*/
|
|
218
|
+
export declare function validateRegistrations<T extends InjectionToken[]>(...tokens: T): void
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Create a proxy that delegates to the mock first, then falls back to the original.
|
|
222
|
+
* This is an internal utility but exported for advanced use cases.
|
|
223
|
+
*
|
|
224
|
+
* @param mock The mock instance
|
|
225
|
+
* @param original The original instance to fall back to
|
|
226
|
+
*/
|
|
227
|
+
export declare function createProxy<T extends object>(mock: T, original: T): T
|