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 +323 -345
- package/index.d.ts +202 -13
- package/index.js +273 -21
- package/package.json +18 -5
- package/src/Container.js +262 -26
- package/src/proxy.js +15 -15
package/index.d.ts
CHANGED
|
@@ -29,10 +29,43 @@ export interface InstanceContext {
|
|
|
29
29
|
proxy?: boolean
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Registration info returned by list()
|
|
34
|
+
*/
|
|
35
|
+
export interface RegistrationInfo {
|
|
36
|
+
/** The registration key (class or string name) */
|
|
37
|
+
key: string | Constructor
|
|
38
|
+
/** Human-readable name */
|
|
39
|
+
name: string
|
|
40
|
+
/** Registration type */
|
|
41
|
+
type: 'singleton' | 'factory'
|
|
42
|
+
/** Whether this registration is mocked */
|
|
43
|
+
isMocked: boolean
|
|
44
|
+
/** Whether a cached instance exists */
|
|
45
|
+
hasInstance: boolean
|
|
46
|
+
}
|
|
47
|
+
|
|
32
48
|
/**
|
|
33
49
|
* A dependency injection container that manages singleton and factory instances.
|
|
34
50
|
*/
|
|
35
51
|
export declare class Container {
|
|
52
|
+
/**
|
|
53
|
+
* Custom string tag for better debugging.
|
|
54
|
+
* Shows as [object Container] in console.
|
|
55
|
+
*/
|
|
56
|
+
readonly [Symbol.toStringTag]: 'Container'
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Make the container iterable.
|
|
60
|
+
* Yields registration info for each registered class.
|
|
61
|
+
*/
|
|
62
|
+
[Symbol.iterator](): IterableIterator<RegistrationInfo>
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Get the number of registrations in the container.
|
|
66
|
+
*/
|
|
67
|
+
readonly size: number
|
|
68
|
+
|
|
36
69
|
/**
|
|
37
70
|
* Enable or disable debug logging.
|
|
38
71
|
* When enabled, logs when instances are created.
|
|
@@ -60,6 +93,28 @@ export declare class Container {
|
|
|
60
93
|
*/
|
|
61
94
|
has<T>(clazzOrName: InjectionToken<T>): boolean
|
|
62
95
|
|
|
96
|
+
/**
|
|
97
|
+
* Check if a class or name has a mock registered.
|
|
98
|
+
*/
|
|
99
|
+
isMocked<T>(clazzOrName: InjectionToken<T>): boolean
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Unregister a class or name from the container.
|
|
103
|
+
* @returns true if the registration was removed, false if it wasn't registered
|
|
104
|
+
*/
|
|
105
|
+
unregister<T>(clazzOrName: InjectionToken<T>): boolean
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* List all registrations in the container.
|
|
109
|
+
*/
|
|
110
|
+
list(): RegistrationInfo[]
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Resolve and return an instance by class or name.
|
|
114
|
+
* This allows non-decorator code to retrieve instances from the container.
|
|
115
|
+
*/
|
|
116
|
+
resolve<T>(clazzOrName: InjectionToken<T>, ...params: any[]): T
|
|
117
|
+
|
|
63
118
|
/**
|
|
64
119
|
* Get or create an instance based on the context.
|
|
65
120
|
*/
|
|
@@ -68,26 +123,49 @@ export declare class Container {
|
|
|
68
123
|
/**
|
|
69
124
|
* Register a mock for an existing class.
|
|
70
125
|
*/
|
|
71
|
-
registerMock<T>(
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
126
|
+
registerMock<T>(targetClazzOrName: InjectionToken<T>, mockClazz: Constructor<Partial<T>>, useProxy?: boolean): void
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Get the mock instance for a mocked class.
|
|
130
|
+
* @throws Error if the class is not mocked
|
|
131
|
+
*/
|
|
132
|
+
getMockInstance<T>(clazzOrName: InjectionToken<T>, ...params: any[]): T
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Remove a specific mock and restore the original class.
|
|
136
|
+
* This completely removes the mock - it does NOT clear mock call history.
|
|
137
|
+
*/
|
|
138
|
+
removeMock<T>(clazzOrName: InjectionToken<T>): void
|
|
76
139
|
|
|
77
140
|
/**
|
|
78
|
-
*
|
|
141
|
+
* Remove all mocks and restore original classes.
|
|
142
|
+
* This completely removes all mocks - it does NOT clear mock call history.
|
|
143
|
+
*/
|
|
144
|
+
removeAllMocks(): void
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* @deprecated Use removeMock() instead. This will be removed in a future version.
|
|
148
|
+
* WARNING: This removes the mock, it does NOT clear mock call history.
|
|
79
149
|
*/
|
|
80
150
|
resetMock<T>(clazzOrName: InjectionToken<T>): void
|
|
81
151
|
|
|
82
152
|
/**
|
|
83
|
-
*
|
|
153
|
+
* @deprecated Use removeAllMocks() instead. This will be removed in a future version.
|
|
154
|
+
* WARNING: This removes all mocks, it does NOT clear mock call history.
|
|
84
155
|
*/
|
|
85
156
|
resetAllMocks(): void
|
|
86
157
|
|
|
158
|
+
/**
|
|
159
|
+
* Reset singleton instances without removing registrations.
|
|
160
|
+
* Mock registrations are preserved by default.
|
|
161
|
+
*/
|
|
162
|
+
resetSingletons(options?: { preserveMocks?: boolean }): void
|
|
163
|
+
|
|
87
164
|
/**
|
|
88
165
|
* Clear all registered instances and mocks.
|
|
166
|
+
* @param options.preserveRegistrations If true, keeps all registrations but clears cached instances.
|
|
89
167
|
*/
|
|
90
|
-
clear(): void
|
|
168
|
+
clear(options?: { preserveRegistrations?: boolean }): void
|
|
91
169
|
}
|
|
92
170
|
|
|
93
171
|
/**
|
|
@@ -164,8 +242,33 @@ export declare function InjectLazy<T>(
|
|
|
164
242
|
|
|
165
243
|
/**
|
|
166
244
|
* Mark a class as a mock for another class.
|
|
245
|
+
* The mock class can implement only the methods you need (Partial<T>).
|
|
246
|
+
*
|
|
167
247
|
* @param mockedClazzOrName The class or name to mock
|
|
168
|
-
* @param proxy If true, unmocked methods delegate to the original
|
|
248
|
+
* @param proxy If true, unmocked methods delegate to the original implementation
|
|
249
|
+
*
|
|
250
|
+
* @example Basic mocking
|
|
251
|
+
* ```ts
|
|
252
|
+
* @Mock(UserService)
|
|
253
|
+
* class MockUserService {
|
|
254
|
+
* // Only implement methods you need to mock
|
|
255
|
+
* getUser() { return { id: 1, name: 'Test' } }
|
|
256
|
+
* }
|
|
257
|
+
* ```
|
|
258
|
+
*
|
|
259
|
+
* @example With hoisted mock functions (Vitest/Jest)
|
|
260
|
+
* ```ts
|
|
261
|
+
* const mockGetUser = vi.hoisted(() => vi.fn())
|
|
262
|
+
*
|
|
263
|
+
* @Mock(UserService)
|
|
264
|
+
* class MockUserService {
|
|
265
|
+
* getUser = mockGetUser
|
|
266
|
+
* }
|
|
267
|
+
*
|
|
268
|
+
* beforeEach(() => {
|
|
269
|
+
* mockGetUser.mockClear() // Clear call history, not removeMock()
|
|
270
|
+
* })
|
|
271
|
+
* ```
|
|
169
272
|
*/
|
|
170
273
|
export declare function Mock<T>(
|
|
171
274
|
mockedClazzOrName: InjectionToken<T>,
|
|
@@ -173,20 +276,46 @@ export declare function Mock<T>(
|
|
|
173
276
|
): ClassDecorator
|
|
174
277
|
|
|
175
278
|
/**
|
|
176
|
-
*
|
|
279
|
+
* Remove all mocks and restore original classes.
|
|
280
|
+
* This completely removes all mocks - it does NOT clear mock call history.
|
|
281
|
+
*/
|
|
282
|
+
export declare function removeAllMocks(): void
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Remove a specific mock and restore the original class.
|
|
286
|
+
* This completely removes the mock - it does NOT clear mock call history.
|
|
287
|
+
* @param clazzOrName The class or name to restore
|
|
288
|
+
*/
|
|
289
|
+
export declare function removeMock<T>(clazzOrName: InjectionToken<T>): void
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* @deprecated Use removeAllMocks() instead. This will be removed in a future version.
|
|
293
|
+
* WARNING: This removes all mocks, it does NOT clear mock call history.
|
|
177
294
|
*/
|
|
178
295
|
export declare function resetMocks(): void
|
|
179
296
|
|
|
180
297
|
/**
|
|
181
|
-
*
|
|
182
|
-
*
|
|
298
|
+
* @deprecated Use removeMock() instead. This will be removed in a future version.
|
|
299
|
+
* WARNING: This removes the mock, it does NOT clear mock call history.
|
|
300
|
+
* @param clazzOrName The class or name to restore
|
|
183
301
|
*/
|
|
184
302
|
export declare function resetMock<T>(clazzOrName: InjectionToken<T>): void
|
|
185
303
|
|
|
304
|
+
/**
|
|
305
|
+
* Reset singleton instances without removing registrations.
|
|
306
|
+
* Mock registrations are preserved by default.
|
|
307
|
+
*
|
|
308
|
+
* Ideal for test isolation where you want fresh instances but keep mocks.
|
|
309
|
+
*
|
|
310
|
+
* @param options.preserveMocks If true (default), keeps mock registrations
|
|
311
|
+
*/
|
|
312
|
+
export declare function resetSingletons(options?: { preserveMocks?: boolean }): void
|
|
313
|
+
|
|
186
314
|
/**
|
|
187
315
|
* Clear all registered instances and mocks from the container.
|
|
316
|
+
* @param options.preserveRegistrations If true, keeps all registrations but clears cached instances.
|
|
188
317
|
*/
|
|
189
|
-
export declare function clearContainer(): void
|
|
318
|
+
export declare function clearContainer(options?: { preserveRegistrations?: boolean }): void
|
|
190
319
|
|
|
191
320
|
/**
|
|
192
321
|
* Get the default container instance.
|
|
@@ -208,6 +337,43 @@ export declare function setDebug(enabled: boolean): void
|
|
|
208
337
|
*/
|
|
209
338
|
export declare function isRegistered<T>(clazzOrName: InjectionToken<T>): boolean
|
|
210
339
|
|
|
340
|
+
/**
|
|
341
|
+
* Check if a class or name has a mock registered.
|
|
342
|
+
* @param clazzOrName The class or name to check
|
|
343
|
+
* @returns true if mocked, false otherwise
|
|
344
|
+
*/
|
|
345
|
+
export declare function isMocked<T>(clazzOrName: InjectionToken<T>): boolean
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Get the mock instance for a mocked class.
|
|
349
|
+
* Useful for configuring mock behavior dynamically in tests.
|
|
350
|
+
*
|
|
351
|
+
* @param clazzOrName The original class or name that was mocked
|
|
352
|
+
* @param params Parameters to pass to the constructor
|
|
353
|
+
* @returns The mock instance
|
|
354
|
+
* @throws Error if the class is not mocked
|
|
355
|
+
*
|
|
356
|
+
* @example
|
|
357
|
+
* ```ts
|
|
358
|
+
* const mock = getMockInstance(UserService)
|
|
359
|
+
* mock.getUser.mockReturnValue({ id: 1 })
|
|
360
|
+
* ```
|
|
361
|
+
*/
|
|
362
|
+
export declare function getMockInstance<T>(clazzOrName: InjectionToken<T>, ...params: any[]): T
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Unregister a class or name from the container.
|
|
366
|
+
* @param clazzOrName The class or name to unregister
|
|
367
|
+
* @returns true if the registration was removed, false if it wasn't registered
|
|
368
|
+
*/
|
|
369
|
+
export declare function unregister<T>(clazzOrName: InjectionToken<T>): boolean
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* List all registrations in the container.
|
|
373
|
+
* Useful for debugging and introspection.
|
|
374
|
+
*/
|
|
375
|
+
export declare function listRegistrations(): RegistrationInfo[]
|
|
376
|
+
|
|
211
377
|
/**
|
|
212
378
|
* Validate that all provided injection tokens are registered.
|
|
213
379
|
* Throws an error with details about missing registrations.
|
|
@@ -217,6 +383,29 @@ export declare function isRegistered<T>(clazzOrName: InjectionToken<T>): boolean
|
|
|
217
383
|
*/
|
|
218
384
|
export declare function validateRegistrations<T extends InjectionToken[]>(...tokens: T): void
|
|
219
385
|
|
|
386
|
+
/**
|
|
387
|
+
* Resolve and return an instance by class or name.
|
|
388
|
+
* This allows non-decorator code (plain functions, modules, etc.) to retrieve
|
|
389
|
+
* instances from the DI container.
|
|
390
|
+
*
|
|
391
|
+
* @param clazzOrName The class or name to resolve
|
|
392
|
+
* @param params Optional parameters to pass to the constructor
|
|
393
|
+
* @returns The resolved instance
|
|
394
|
+
* @throws Error if the class or name is not registered
|
|
395
|
+
*
|
|
396
|
+
* @example
|
|
397
|
+
* // In a plain function:
|
|
398
|
+
* function handleRequest(req: Request) {
|
|
399
|
+
* const userService = resolve(UserService)
|
|
400
|
+
* return userService.getUser(req.userId)
|
|
401
|
+
* }
|
|
402
|
+
*
|
|
403
|
+
* @example
|
|
404
|
+
* // With a named registration:
|
|
405
|
+
* const db = resolve<Database>('database')
|
|
406
|
+
*/
|
|
407
|
+
export declare function resolve<T>(clazzOrName: InjectionToken<T>, ...params: any[]): T
|
|
408
|
+
|
|
220
409
|
/**
|
|
221
410
|
* Create a proxy that delegates to the mock first, then falls back to the original.
|
|
222
411
|
* This is an internal utility but exported for advanced use cases.
|
package/index.js
CHANGED
|
@@ -28,10 +28,7 @@ function createLazyAccessor(cache, getValue, name) {
|
|
|
28
28
|
return undefined
|
|
29
29
|
},
|
|
30
30
|
get() {
|
|
31
|
-
|
|
32
|
-
cache.set(this, getValue())
|
|
33
|
-
}
|
|
34
|
-
return cache.get(this)
|
|
31
|
+
return cache.get(this) ?? (cache.set(this, getValue()), cache.get(this))
|
|
35
32
|
},
|
|
36
33
|
set() {
|
|
37
34
|
throw new Error(`Cannot assign value to injected accessor "${name}"`)
|
|
@@ -52,7 +49,7 @@ function createLazyAccessor(cache, getValue, name) {
|
|
|
52
49
|
* @throws {Error} If the target is not a class constructor
|
|
53
50
|
*/
|
|
54
51
|
export function Singleton(name) {
|
|
55
|
-
return
|
|
52
|
+
return (clazz, context) => {
|
|
56
53
|
if (context.kind !== 'class') {
|
|
57
54
|
throw new Error('Invalid injection target')
|
|
58
55
|
}
|
|
@@ -76,7 +73,7 @@ export function Singleton(name) {
|
|
|
76
73
|
* @throws {Error} If the target is not a class constructor
|
|
77
74
|
*/
|
|
78
75
|
export function Factory(name) {
|
|
79
|
-
return
|
|
76
|
+
return (clazz, context) => {
|
|
80
77
|
if (context.kind !== 'class') {
|
|
81
78
|
throw new Error('Invalid injection target')
|
|
82
79
|
}
|
|
@@ -108,14 +105,14 @@ export function Factory(name) {
|
|
|
108
105
|
* @throws {Error} If the injected field is assigned a value
|
|
109
106
|
*/
|
|
110
107
|
export function Inject(clazzOrName, ...params) {
|
|
111
|
-
return
|
|
108
|
+
return (_, context) => {
|
|
112
109
|
const getValue = () => {
|
|
113
110
|
const instanceContext = defaultContainer.getContext(clazzOrName)
|
|
114
111
|
return defaultContainer.getInstance(instanceContext, params)
|
|
115
112
|
}
|
|
116
113
|
|
|
117
114
|
if (context.kind === 'field') {
|
|
118
|
-
return
|
|
115
|
+
return (initialValue) => {
|
|
119
116
|
if (initialValue) {
|
|
120
117
|
throw new Error(`Cannot assign value to injected field "${context.name}"`)
|
|
121
118
|
}
|
|
@@ -172,7 +169,7 @@ export function InjectLazy(clazzOrName, ...params) {
|
|
|
172
169
|
// For private fields, we cannot use Object.defineProperty to create a lazy getter.
|
|
173
170
|
// Instead, we eagerly create the value. For true lazy behavior, use accessor syntax.
|
|
174
171
|
if (context.private) {
|
|
175
|
-
return
|
|
172
|
+
return (initialValue) => {
|
|
176
173
|
if (initialValue) {
|
|
177
174
|
throw new Error(`Cannot assign value to lazy-injected field "${context.name}"`)
|
|
178
175
|
}
|
|
@@ -208,19 +205,70 @@ export function InjectLazy(clazzOrName, ...params) {
|
|
|
208
205
|
}
|
|
209
206
|
|
|
210
207
|
/**
|
|
211
|
-
* Mark a class as a mock. This will replace the class with
|
|
208
|
+
* Mark a class as a mock. This will replace the original class with the mock when injected.
|
|
209
|
+
* The mock registration persists until explicitly removed with removeMock() or removeAllMocks().
|
|
212
210
|
*
|
|
213
211
|
* @param {string|Function} mockedClazzOrName The singleton or factory class or name to be mocked
|
|
214
212
|
* @param {boolean} [proxy=false] If true, the mock will proxy to the original class.
|
|
215
213
|
* Any methods not defined in the mock will be called on the original class.
|
|
216
214
|
* @returns {(function(Function, {kind: string}): void)}
|
|
217
|
-
*
|
|
218
|
-
* @example
|
|
215
|
+
*
|
|
216
|
+
* @example Basic mocking
|
|
217
|
+
* ```js
|
|
218
|
+
* @Mock(MySingleton)
|
|
219
|
+
* class MockedSingleton {
|
|
220
|
+
* doSomething() { return 'mocked result' }
|
|
221
|
+
* }
|
|
222
|
+
* ```
|
|
223
|
+
*
|
|
224
|
+
* @example Proxy mocking (partial mock)
|
|
225
|
+
* ```js
|
|
226
|
+
* // Only override specific methods, others delegate to original
|
|
227
|
+
* @Mock(MySingleton, true)
|
|
228
|
+
* class PartialMock {
|
|
229
|
+
* onlyThisMethod() { return 'mocked' }
|
|
230
|
+
* // All other methods call the original implementation
|
|
231
|
+
* }
|
|
232
|
+
* ```
|
|
233
|
+
*
|
|
234
|
+
* @example Testing pattern with hoisted mock functions
|
|
235
|
+
* ```js
|
|
236
|
+
* // Hoist mock functions for per-test configuration
|
|
237
|
+
* const mockMethod = vi.hoisted(() => vi.fn())
|
|
238
|
+
*
|
|
239
|
+
* @Mock(MyService)
|
|
240
|
+
* class MockMyService {
|
|
241
|
+
* doSomething = mockMethod
|
|
242
|
+
* }
|
|
243
|
+
*
|
|
244
|
+
* beforeEach(() => {
|
|
245
|
+
* // Clear call history - NOT removeMock() which removes the mock entirely
|
|
246
|
+
* mockMethod.mockClear()
|
|
247
|
+
* })
|
|
248
|
+
*
|
|
249
|
+
* it('should call the service', () => {
|
|
250
|
+
* mockMethod.mockReturnValue('test value')
|
|
251
|
+
* // ... your test
|
|
252
|
+
* expect(mockMethod).toHaveBeenCalled()
|
|
253
|
+
* })
|
|
254
|
+
* ```
|
|
255
|
+
*
|
|
256
|
+
* @example Cleanup in afterEach
|
|
257
|
+
* ```js
|
|
258
|
+
* afterEach(() => {
|
|
259
|
+
* // Option 1: Remove all mocks and restore originals
|
|
260
|
+
* removeAllMocks()
|
|
261
|
+
*
|
|
262
|
+
* // Option 2: Just clear singleton instances, keep mocks registered
|
|
263
|
+
* resetSingletons()
|
|
264
|
+
* })
|
|
265
|
+
* ```
|
|
266
|
+
*
|
|
219
267
|
* @throws {Error} If the injection target is not a class
|
|
220
268
|
* @throws {Error} If the injection source is not found
|
|
221
269
|
*/
|
|
222
270
|
export function Mock(mockedClazzOrName, proxy = false) {
|
|
223
|
-
return
|
|
271
|
+
return (clazz, context) => {
|
|
224
272
|
if (context.kind !== 'class') {
|
|
225
273
|
throw new Error('Invalid injection target')
|
|
226
274
|
}
|
|
@@ -229,27 +277,118 @@ export function Mock(mockedClazzOrName, proxy = false) {
|
|
|
229
277
|
}
|
|
230
278
|
|
|
231
279
|
/**
|
|
232
|
-
*
|
|
280
|
+
* Remove all mocks and restore original classes.
|
|
281
|
+
* This completely removes all mocks - it does NOT clear mock call history.
|
|
282
|
+
*
|
|
283
|
+
* If you want to clear call history on mock functions without removing the mock,
|
|
284
|
+
* call mockClear() on your mock functions instead.
|
|
285
|
+
*
|
|
286
|
+
* @example
|
|
287
|
+
* ```js
|
|
288
|
+
* afterEach(() => {
|
|
289
|
+
* removeAllMocks() // Restores original classes
|
|
290
|
+
* })
|
|
291
|
+
* ```
|
|
292
|
+
*/
|
|
293
|
+
export function removeAllMocks() {
|
|
294
|
+
defaultContainer.removeAllMocks()
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Remove a specific mock and restore the original class.
|
|
299
|
+
* This completely removes the mock - it does NOT clear mock call history.
|
|
300
|
+
*
|
|
301
|
+
* @param {string|Function} clazzOrName The singleton or factory class or name to restore
|
|
302
|
+
*
|
|
303
|
+
* @example
|
|
304
|
+
* ```js
|
|
305
|
+
* removeMock(UserService) // Restores original UserService
|
|
306
|
+
* ```
|
|
307
|
+
*/
|
|
308
|
+
export function removeMock(clazzOrName) {
|
|
309
|
+
defaultContainer.removeMock(clazzOrName)
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* @deprecated Use removeAllMocks() instead. This will be removed in a future version.
|
|
314
|
+
*
|
|
315
|
+
* WARNING: Despite the name, this does NOT reset mock call history like mockClear().
|
|
316
|
+
* It completely removes all mocks and restores the original classes.
|
|
233
317
|
*/
|
|
234
318
|
export function resetMocks() {
|
|
235
|
-
|
|
319
|
+
console.warn(
|
|
320
|
+
'[DI] resetMocks() is deprecated. Use removeAllMocks() instead. ' +
|
|
321
|
+
'Note: This removes mocks entirely, NOT clearing call history.'
|
|
322
|
+
)
|
|
323
|
+
defaultContainer.removeAllMocks()
|
|
236
324
|
}
|
|
237
325
|
|
|
238
326
|
/**
|
|
239
|
-
*
|
|
327
|
+
* @deprecated Use removeMock() instead. This will be removed in a future version.
|
|
328
|
+
*
|
|
329
|
+
* WARNING: Despite the name, this does NOT reset mock call history like mockClear().
|
|
330
|
+
* It completely removes the mock and restores the original class.
|
|
240
331
|
*
|
|
241
|
-
* @param {string|Function} clazzOrName The singleton or factory class or name to
|
|
332
|
+
* @param {string|Function} clazzOrName The singleton or factory class or name to restore
|
|
242
333
|
*/
|
|
243
334
|
export function resetMock(clazzOrName) {
|
|
244
|
-
|
|
335
|
+
console.warn(
|
|
336
|
+
'[DI] resetMock() is deprecated. Use removeMock() instead. ' +
|
|
337
|
+
'Note: This removes the mock entirely, NOT clearing call history.'
|
|
338
|
+
)
|
|
339
|
+
defaultContainer.removeMock(clazzOrName)
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Reset singleton instances without removing registrations.
|
|
344
|
+
* This clears cached singleton instances so they will be recreated on next resolve.
|
|
345
|
+
* Mock registrations are preserved by default.
|
|
346
|
+
*
|
|
347
|
+
* This is ideal for test isolation where you want:
|
|
348
|
+
* - Fresh singleton instances per test
|
|
349
|
+
* - Mock registrations to remain intact
|
|
350
|
+
*
|
|
351
|
+
* @param {Object} [options] Options for resetting
|
|
352
|
+
* @param {boolean} [options.preserveMocks=true] If true, keeps mock registrations.
|
|
353
|
+
* If false, also removes mocks.
|
|
354
|
+
*
|
|
355
|
+
* @example
|
|
356
|
+
* ```js
|
|
357
|
+
* beforeEach(() => {
|
|
358
|
+
* // Each test gets fresh singleton instances
|
|
359
|
+
* // but mocks remain registered
|
|
360
|
+
* resetSingletons()
|
|
361
|
+
* })
|
|
362
|
+
* ```
|
|
363
|
+
*/
|
|
364
|
+
export function resetSingletons(options) {
|
|
365
|
+
defaultContainer.resetSingletons(options)
|
|
245
366
|
}
|
|
246
367
|
|
|
247
368
|
/**
|
|
248
369
|
* Clear all registered instances and mocks from the container.
|
|
249
|
-
*
|
|
370
|
+
*
|
|
371
|
+
* By default, this removes ALL registrations including @Singleton and @Factory classes.
|
|
372
|
+
* For just clearing singleton instances without removing any registrations,
|
|
373
|
+
* use resetSingletons() instead.
|
|
374
|
+
*
|
|
375
|
+
* @param {Object} [options] Options for clearing
|
|
376
|
+
* @param {boolean} [options.preserveRegistrations=false] If true, keeps all registrations but clears cached instances.
|
|
377
|
+
*
|
|
378
|
+
* @example
|
|
379
|
+
* ```js
|
|
380
|
+
* // Complete reset - removes everything
|
|
381
|
+
* clearContainer()
|
|
382
|
+
*
|
|
383
|
+
* // Clear cached instances but keep registrations (including mocks)
|
|
384
|
+
* clearContainer({ preserveRegistrations: true })
|
|
385
|
+
*
|
|
386
|
+
* // Just clear singleton instances (preferred for test isolation)
|
|
387
|
+
* resetSingletons()
|
|
388
|
+
* ```
|
|
250
389
|
*/
|
|
251
|
-
export function clearContainer() {
|
|
252
|
-
defaultContainer.clear()
|
|
390
|
+
export function clearContainer(options) {
|
|
391
|
+
defaultContainer.clear(options)
|
|
253
392
|
}
|
|
254
393
|
|
|
255
394
|
/**
|
|
@@ -313,6 +452,119 @@ export function validateRegistrations(...tokens) {
|
|
|
313
452
|
}
|
|
314
453
|
}
|
|
315
454
|
|
|
455
|
+
/**
|
|
456
|
+
* Resolve and return an instance by class or name.
|
|
457
|
+
* This allows non-decorator code (plain functions, modules, etc.) to retrieve
|
|
458
|
+
* instances from the DI container.
|
|
459
|
+
*
|
|
460
|
+
* @template T
|
|
461
|
+
* @param {string|Function} clazzOrName The class or name to resolve
|
|
462
|
+
* @param {...*} params Parameters to pass to the constructor
|
|
463
|
+
* @returns {T} The resolved instance
|
|
464
|
+
* @throws {Error} If the class or name is not registered
|
|
465
|
+
* @example
|
|
466
|
+
* // In a plain function:
|
|
467
|
+
* function handleRequest(req) {
|
|
468
|
+
* const userService = resolve(UserService)
|
|
469
|
+
* return userService.getUser(req.userId)
|
|
470
|
+
* }
|
|
471
|
+
* @example
|
|
472
|
+
* // With a named registration:
|
|
473
|
+
* const db = resolve('database')
|
|
474
|
+
* @example
|
|
475
|
+
* // With factory parameters:
|
|
476
|
+
* const logger = resolve(Logger, 'my-module')
|
|
477
|
+
*/
|
|
478
|
+
export function resolve(clazzOrName, ...params) {
|
|
479
|
+
return defaultContainer.resolve(clazzOrName, ...params)
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Get the mock instance for a mocked class.
|
|
484
|
+
* This is useful when you need to access or configure mock behavior dynamically in tests.
|
|
485
|
+
*
|
|
486
|
+
* Unlike resolve(), this explicitly checks that the class is mocked and provides
|
|
487
|
+
* better error messages if it's not.
|
|
488
|
+
*
|
|
489
|
+
* @template T
|
|
490
|
+
* @param {string|Function} clazzOrName The original class or name that was mocked
|
|
491
|
+
* @param {...*} params Parameters to pass to the constructor
|
|
492
|
+
* @returns {T} The mock instance
|
|
493
|
+
* @throws {Error} If the class is not registered
|
|
494
|
+
* @throws {Error} If the class is not mocked
|
|
495
|
+
*
|
|
496
|
+
* @example
|
|
497
|
+
* ```js
|
|
498
|
+
* @Mock(UserService)
|
|
499
|
+
* class MockUserService {
|
|
500
|
+
* getUser = vi.fn()
|
|
501
|
+
* }
|
|
502
|
+
*
|
|
503
|
+
* it('should get user', () => {
|
|
504
|
+
* const mock = getMockInstance(UserService)
|
|
505
|
+
* mock.getUser.mockReturnValue({ id: 1, name: 'Test' })
|
|
506
|
+
*
|
|
507
|
+
* // ... test code that uses UserService
|
|
508
|
+
*
|
|
509
|
+
* expect(mock.getUser).toHaveBeenCalledWith(1)
|
|
510
|
+
* })
|
|
511
|
+
* ```
|
|
512
|
+
*/
|
|
513
|
+
export function getMockInstance(clazzOrName, ...params) {
|
|
514
|
+
return defaultContainer.getMockInstance(clazzOrName, ...params)
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Check if a class or name has a mock registered.
|
|
519
|
+
* Useful for conditional test logic or debugging.
|
|
520
|
+
*
|
|
521
|
+
* @param {string|Function} clazzOrName The class or name to check
|
|
522
|
+
* @returns {boolean} true if a mock is registered, false otherwise
|
|
523
|
+
*
|
|
524
|
+
* @example
|
|
525
|
+
* ```js
|
|
526
|
+
* if (isMocked(UserService)) {
|
|
527
|
+
* console.log('UserService is mocked')
|
|
528
|
+
* }
|
|
529
|
+
* ```
|
|
530
|
+
*/
|
|
531
|
+
export function isMocked(clazzOrName) {
|
|
532
|
+
return defaultContainer.isMocked(clazzOrName)
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* Unregister a class or name from the container.
|
|
537
|
+
* This removes the registration entirely, including any mock.
|
|
538
|
+
*
|
|
539
|
+
* @param {string|Function} clazzOrName The class or name to unregister
|
|
540
|
+
* @returns {boolean} true if the registration was removed, false if it wasn't registered
|
|
541
|
+
*
|
|
542
|
+
* @example
|
|
543
|
+
* ```js
|
|
544
|
+
* unregister(MyService) // Returns true if was registered
|
|
545
|
+
* ```
|
|
546
|
+
*/
|
|
547
|
+
export function unregister(clazzOrName) {
|
|
548
|
+
return defaultContainer.unregister(clazzOrName)
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* List all registrations in the container.
|
|
553
|
+
* Useful for debugging and introspection.
|
|
554
|
+
*
|
|
555
|
+
* @returns {Array<{key: string|Function, name: string, type: 'singleton'|'factory', isMocked: boolean, hasInstance: boolean}>}
|
|
556
|
+
*
|
|
557
|
+
* @example
|
|
558
|
+
* ```js
|
|
559
|
+
* listRegistrations().forEach(reg => {
|
|
560
|
+
* console.log(`${reg.name}: ${reg.type}, mocked: ${reg.isMocked}`)
|
|
561
|
+
* })
|
|
562
|
+
* ```
|
|
563
|
+
*/
|
|
564
|
+
export function listRegistrations() {
|
|
565
|
+
return defaultContainer.list()
|
|
566
|
+
}
|
|
567
|
+
|
|
316
568
|
// Export Container class for advanced use cases (e.g., isolated containers)
|
|
317
569
|
export {Container}
|
|
318
570
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "decorator-dependency-injection",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.0.7",
|
|
4
|
+
"description": "Lightweight dependency injection (DI) library using native TC39 Stage 3 decorators. Zero dependencies, built-in mocking, TypeScript support.",
|
|
5
5
|
"author": "Ravi Gairola <mallox@pyxzl.net>",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"repository": {
|
|
@@ -16,12 +16,25 @@
|
|
|
16
16
|
"dependency-injection",
|
|
17
17
|
"di",
|
|
18
18
|
"decorators",
|
|
19
|
-
"
|
|
19
|
+
"tc39-decorators",
|
|
20
|
+
"stage-3-decorators",
|
|
21
|
+
"inject",
|
|
22
|
+
"injectable",
|
|
20
23
|
"singleton",
|
|
21
|
-
"factory"
|
|
24
|
+
"factory",
|
|
25
|
+
"ioc",
|
|
26
|
+
"inversion-of-control",
|
|
27
|
+
"container",
|
|
28
|
+
"mocking",
|
|
29
|
+
"mock",
|
|
30
|
+
"testing",
|
|
31
|
+
"jest",
|
|
32
|
+
"vitest",
|
|
33
|
+
"typescript",
|
|
34
|
+
"service-locator"
|
|
22
35
|
],
|
|
23
36
|
"engines": {
|
|
24
|
-
"node": ">=
|
|
37
|
+
"node": ">=20.0.0"
|
|
25
38
|
},
|
|
26
39
|
"main": "index.js",
|
|
27
40
|
"types": "index.d.ts",
|