decorator-dependency-injection 1.0.6 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +369 -369
- package/docs/FRAMEWORK_INTEGRATION.md +808 -0
- package/eslint.config.js +4 -1
- package/index.d.ts +35 -224
- package/index.js +81 -188
- package/package.json +23 -5
- package/src/Container.js +150 -81
- package/src/integrations/middleware.d.ts +40 -0
- package/src/integrations/middleware.js +171 -0
- package/src/proxy.js +15 -15
package/eslint.config.js
CHANGED
package/index.d.ts
CHANGED
|
@@ -1,256 +1,67 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Type definitions for decorator-dependency-injection
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* A class constructor type.
|
|
7
|
-
* @template T The instance type
|
|
8
|
-
*/
|
|
9
1
|
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
2
|
export type InjectionToken<T = any> = string | Constructor<T>
|
|
15
3
|
|
|
16
|
-
/**
|
|
17
|
-
* Context for registered instances in the container
|
|
18
|
-
*/
|
|
19
4
|
export interface InstanceContext {
|
|
20
|
-
/** The type of registration */
|
|
21
5
|
type: 'singleton' | 'factory'
|
|
22
|
-
/** The current class constructor (may be a mock) */
|
|
23
6
|
clazz: new (...args: any[]) => any
|
|
24
|
-
/** The original class constructor if mocked */
|
|
25
7
|
originalClazz?: new (...args: any[]) => any
|
|
26
|
-
/** The cached singleton instance */
|
|
27
8
|
instance?: any
|
|
28
|
-
/** Whether to use proxy mocking */
|
|
29
9
|
proxy?: boolean
|
|
30
10
|
}
|
|
31
11
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
12
|
+
export interface RegistrationInfo {
|
|
13
|
+
key: string | Constructor
|
|
14
|
+
name: string
|
|
15
|
+
type: 'singleton' | 'factory'
|
|
16
|
+
isMocked: boolean
|
|
17
|
+
hasInstance: boolean
|
|
18
|
+
}
|
|
19
|
+
|
|
35
20
|
export declare class Container {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
*/
|
|
21
|
+
readonly [Symbol.toStringTag]: 'Container'
|
|
22
|
+
[Symbol.iterator](): IterableIterator<RegistrationInfo>
|
|
23
|
+
readonly size: number
|
|
40
24
|
setDebug(enabled: boolean): void
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Register a class as a singleton.
|
|
44
|
-
*/
|
|
45
25
|
registerSingleton<T>(clazz: Constructor<T>, name?: string): void
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Register a class as a factory.
|
|
49
|
-
*/
|
|
50
26
|
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
27
|
getContext<T>(clazzOrName: InjectionToken<T>): InstanceContext
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Check if a class or name is registered.
|
|
60
|
-
*/
|
|
61
28
|
has<T>(clazzOrName: InjectionToken<T>): boolean
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
* This allows non-decorator code to retrieve instances from the container.
|
|
66
|
-
*/
|
|
29
|
+
isMocked<T>(clazzOrName: InjectionToken<T>): boolean
|
|
30
|
+
unregister<T>(clazzOrName: InjectionToken<T>): boolean
|
|
31
|
+
list(): RegistrationInfo[]
|
|
67
32
|
resolve<T>(clazzOrName: InjectionToken<T>, ...params: any[]): T
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Get or create an instance based on the context.
|
|
71
|
-
*/
|
|
72
33
|
getInstance<T>(instanceContext: InstanceContext, params: any[]): T
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
mockClazz: Constructor<T>,
|
|
80
|
-
useProxy?: boolean
|
|
81
|
-
): void
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Reset a specific mock to its original class.
|
|
85
|
-
*/
|
|
86
|
-
resetMock<T>(clazzOrName: InjectionToken<T>): void
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Reset all mocks to their original classes.
|
|
90
|
-
*/
|
|
91
|
-
resetAllMocks(): void
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Clear all registered instances and mocks.
|
|
95
|
-
*/
|
|
96
|
-
clear(): void
|
|
34
|
+
registerMock<T>(targetClazzOrName: InjectionToken<T>, mockClazz: Constructor<Partial<T>>, useProxy?: boolean): void
|
|
35
|
+
getMockInstance<T>(clazzOrName: InjectionToken<T>, ...params: any[]): T
|
|
36
|
+
removeMock<T>(clazzOrName: InjectionToken<T>): void
|
|
37
|
+
removeAllMocks(): void
|
|
38
|
+
resetSingletons(options?: { preserveMocks?: boolean }): void
|
|
39
|
+
clear(options?: { preserveRegistrations?: boolean }): void
|
|
97
40
|
}
|
|
98
41
|
|
|
99
|
-
/**
|
|
100
|
-
* Register a class as a singleton.
|
|
101
|
-
* @param name Optional name to register the singleton under
|
|
102
|
-
*/
|
|
103
|
-
export declare function Singleton(name?: string): ClassDecorator
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Register a class as a factory.
|
|
107
|
-
* @param name Optional name to register the factory under
|
|
108
|
-
*/
|
|
109
|
-
export declare function Factory(name?: string): ClassDecorator
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Decorator return type that works for both fields and accessors.
|
|
113
|
-
* For fields, returns a function that provides the initial value.
|
|
114
|
-
* For accessors, returns an object with get/set/init.
|
|
115
|
-
*/
|
|
116
42
|
export type FieldOrAccessorDecorator = (
|
|
117
43
|
target: undefined,
|
|
118
44
|
context: ClassFieldDecoratorContext | ClassAccessorDecoratorContext
|
|
119
45
|
) => void | ((initialValue: any) => any) | ClassAccessorDecoratorResult<any, any>
|
|
120
46
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
* @param params Optional parameters to pass to the constructor
|
|
132
|
-
*
|
|
133
|
-
* @example
|
|
134
|
-
* class MyService {
|
|
135
|
-
* @Inject(Database) db
|
|
136
|
-
* @Inject(Logger) #logger // private field
|
|
137
|
-
* @Inject(Cache) accessor cache // accessor (recommended for lazy-like behavior)
|
|
138
|
-
* }
|
|
139
|
-
*/
|
|
140
|
-
export declare function Inject<T>(
|
|
141
|
-
clazzOrName: InjectionToken<T>,
|
|
142
|
-
...params: any[]
|
|
143
|
-
): FieldOrAccessorDecorator
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Inject a singleton or factory instance lazily into a class field or accessor.
|
|
147
|
-
* The instance is created on first access.
|
|
148
|
-
*
|
|
149
|
-
* Supports:
|
|
150
|
-
* - Public fields: `@InjectLazy(MyClass) myField` (true lazy)
|
|
151
|
-
* - Private fields: `@InjectLazy(MyClass) #myField` (not truly lazy - use accessor instead)
|
|
152
|
-
* - Public accessors: `@InjectLazy(MyClass) accessor myField` (true lazy)
|
|
153
|
-
* - Private accessors: `@InjectLazy(MyClass) accessor #myField` (true lazy, recommended)
|
|
154
|
-
*
|
|
155
|
-
* Note: For true lazy injection with private members, use the accessor syntax:
|
|
156
|
-
* `@InjectLazy(MyClass) accessor #myField`
|
|
157
|
-
*
|
|
158
|
-
* @param clazzOrName The class or name to inject
|
|
159
|
-
* @param params Optional parameters to pass to the constructor
|
|
160
|
-
*
|
|
161
|
-
* @example
|
|
162
|
-
* class MyService {
|
|
163
|
-
* @InjectLazy(ExpensiveService) accessor #expensiveService
|
|
164
|
-
* }
|
|
165
|
-
*/
|
|
166
|
-
export declare function InjectLazy<T>(
|
|
167
|
-
clazzOrName: InjectionToken<T>,
|
|
168
|
-
...params: any[]
|
|
169
|
-
): FieldOrAccessorDecorator
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Mark a class as a mock for another class.
|
|
173
|
-
* @param mockedClazzOrName The class or name to mock
|
|
174
|
-
* @param proxy If true, unmocked methods delegate to the original
|
|
175
|
-
*/
|
|
176
|
-
export declare function Mock<T>(
|
|
177
|
-
mockedClazzOrName: InjectionToken<T>,
|
|
178
|
-
proxy?: boolean
|
|
179
|
-
): ClassDecorator
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* Reset all mocks to their original classes.
|
|
183
|
-
*/
|
|
184
|
-
export declare function resetMocks(): void
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Reset a specific mock to its original class.
|
|
188
|
-
* @param clazzOrName The class or name to reset
|
|
189
|
-
*/
|
|
190
|
-
export declare function resetMock<T>(clazzOrName: InjectionToken<T>): void
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Clear all registered instances and mocks from the container.
|
|
194
|
-
*/
|
|
195
|
-
export declare function clearContainer(): void
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* Get the default container instance.
|
|
199
|
-
*/
|
|
47
|
+
export declare function Singleton(name?: string): ClassDecorator
|
|
48
|
+
export declare function Factory(name?: string): ClassDecorator
|
|
49
|
+
export declare function Inject<T>(clazzOrName: InjectionToken<T>, ...params: any[]): FieldOrAccessorDecorator
|
|
50
|
+
export declare function InjectLazy<T>(clazzOrName: InjectionToken<T>, ...params: any[]): FieldOrAccessorDecorator
|
|
51
|
+
export declare function Mock<T>(mockedClazzOrName: InjectionToken<T>, proxy?: boolean): ClassDecorator
|
|
52
|
+
|
|
53
|
+
export declare function removeAllMocks(): void
|
|
54
|
+
export declare function removeMock<T>(clazzOrName: InjectionToken<T>): void
|
|
55
|
+
export declare function resetSingletons(options?: { preserveMocks?: boolean }): void
|
|
56
|
+
export declare function clearContainer(options?: { preserveRegistrations?: boolean }): void
|
|
200
57
|
export declare function getContainer(): Container
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
* Enable or disable debug logging for dependency injection.
|
|
204
|
-
* When enabled, logs when instances are registered, created, and mocked.
|
|
205
|
-
* @param enabled Whether to enable debug mode
|
|
206
|
-
*/
|
|
207
58
|
export declare function setDebug(enabled: boolean): void
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Check if a class or name is registered in the default container.
|
|
211
|
-
* Useful for validation before injection.
|
|
212
|
-
* @param clazzOrName The class or name to check
|
|
213
|
-
* @returns true if registered, false otherwise
|
|
214
|
-
*/
|
|
215
59
|
export declare function isRegistered<T>(clazzOrName: InjectionToken<T>): boolean
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
* Useful for fail-fast validation at application startup.
|
|
221
|
-
* @param tokens Array of classes or names to validate
|
|
222
|
-
* @throws Error if any token is not registered
|
|
223
|
-
*/
|
|
60
|
+
export declare function isMocked<T>(clazzOrName: InjectionToken<T>): boolean
|
|
61
|
+
export declare function getMockInstance<T>(clazzOrName: InjectionToken<T>, ...params: any[]): T
|
|
62
|
+
export declare function unregister<T>(clazzOrName: InjectionToken<T>): boolean
|
|
63
|
+
export declare function listRegistrations(): RegistrationInfo[]
|
|
224
64
|
export declare function validateRegistrations<T extends InjectionToken[]>(...tokens: T): void
|
|
225
|
-
|
|
226
|
-
/**
|
|
227
|
-
* Resolve and return an instance by class or name.
|
|
228
|
-
* This allows non-decorator code (plain functions, modules, etc.) to retrieve
|
|
229
|
-
* instances from the DI container.
|
|
230
|
-
*
|
|
231
|
-
* @param clazzOrName The class or name to resolve
|
|
232
|
-
* @param params Optional parameters to pass to the constructor
|
|
233
|
-
* @returns The resolved instance
|
|
234
|
-
* @throws Error if the class or name is not registered
|
|
235
|
-
*
|
|
236
|
-
* @example
|
|
237
|
-
* // In a plain function:
|
|
238
|
-
* function handleRequest(req: Request) {
|
|
239
|
-
* const userService = resolve(UserService)
|
|
240
|
-
* return userService.getUser(req.userId)
|
|
241
|
-
* }
|
|
242
|
-
*
|
|
243
|
-
* @example
|
|
244
|
-
* // With a named registration:
|
|
245
|
-
* const db = resolve<Database>('database')
|
|
246
|
-
*/
|
|
247
65
|
export declare function resolve<T>(clazzOrName: InjectionToken<T>, ...params: any[]): T
|
|
248
|
-
|
|
249
|
-
/**
|
|
250
|
-
* Create a proxy that delegates to the mock first, then falls back to the original.
|
|
251
|
-
* This is an internal utility but exported for advanced use cases.
|
|
252
|
-
*
|
|
253
|
-
* @param mock The mock instance
|
|
254
|
-
* @param original The original instance to fall back to
|
|
255
|
-
*/
|
|
256
66
|
export declare function createProxy<T extends object>(mock: T, original: T): T
|
|
67
|
+
export declare const defaultContainer: Container
|
package/index.js
CHANGED
|
@@ -1,31 +1,15 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Decorator Dependency Injection
|
|
3
|
-
*
|
|
4
|
-
* A simple library for dependency injection using TC39 Stage 3 decorators.
|
|
5
|
-
*
|
|
6
|
-
* @module decorator-dependency-injection
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
1
|
import {Container} from './src/Container.js'
|
|
10
2
|
|
|
11
|
-
/** @type {Container}
|
|
3
|
+
/** @type {Container} */
|
|
12
4
|
const defaultContainer = new Container()
|
|
13
5
|
|
|
14
|
-
/**
|
|
15
|
-
* Creates a lazy accessor descriptor with WeakMap-based caching.
|
|
16
|
-
* @param {WeakMap} cache - WeakMap for per-instance caching
|
|
17
|
-
* @param {Function} getValue - Factory function to create the value
|
|
18
|
-
* @param {string} name - The accessor name for error messages
|
|
19
|
-
* @returns {{init: Function, get: Function, set: Function}} Accessor descriptor
|
|
20
|
-
* @private
|
|
21
|
-
*/
|
|
6
|
+
/** @private */
|
|
22
7
|
function createLazyAccessor(cache, getValue, name) {
|
|
23
8
|
return {
|
|
24
9
|
init(initialValue) {
|
|
25
|
-
if (initialValue) {
|
|
10
|
+
if (initialValue !== undefined) {
|
|
26
11
|
throw new Error(`Cannot assign value to injected accessor "${name}"`)
|
|
27
12
|
}
|
|
28
|
-
return undefined
|
|
29
13
|
},
|
|
30
14
|
get() {
|
|
31
15
|
if (!cache.has(this)) {
|
|
@@ -39,20 +23,9 @@ function createLazyAccessor(cache, getValue, name) {
|
|
|
39
23
|
}
|
|
40
24
|
}
|
|
41
25
|
|
|
42
|
-
/**
|
|
43
|
-
* Register a class as a singleton. If a name is provided, it will be used as the key in the singleton map.
|
|
44
|
-
* Singleton instances only ever have one instance created via the @Inject decorator.
|
|
45
|
-
*
|
|
46
|
-
* @param {string} [name] The name of the singleton. If not provided, the class will be used as the key.
|
|
47
|
-
* @returns {(function(Function, {kind: string}): void)}
|
|
48
|
-
* @example @Singleton() class MySingleton {}
|
|
49
|
-
* @example @Singleton('customName') class MySingleton {}
|
|
50
|
-
* @throws {Error} If the injection target is not a class
|
|
51
|
-
* @throws {Error} If a singleton or factory with the same name is already defined
|
|
52
|
-
* @throws {Error} If the target is not a class constructor
|
|
53
|
-
*/
|
|
26
|
+
/** @param {string} [name] */
|
|
54
27
|
export function Singleton(name) {
|
|
55
|
-
return
|
|
28
|
+
return (clazz, context) => {
|
|
56
29
|
if (context.kind !== 'class') {
|
|
57
30
|
throw new Error('Invalid injection target')
|
|
58
31
|
}
|
|
@@ -63,20 +36,9 @@ export function Singleton(name) {
|
|
|
63
36
|
}
|
|
64
37
|
}
|
|
65
38
|
|
|
66
|
-
/**
|
|
67
|
-
* Register a class as a factory. If a name is provided, it will be used as the key in the factory map.
|
|
68
|
-
* Factory instances are created via the @Inject decorator. Each call to the factory will create a new instance.
|
|
69
|
-
*
|
|
70
|
-
* @param {string} [name] The name of the factory. If not provided, the class will be used as the key.
|
|
71
|
-
* @returns {(function(Function, {kind: string}): void)}
|
|
72
|
-
* @example @Factory() class MyFactory {}
|
|
73
|
-
* @example @Factory('customName') class MyFactory {}
|
|
74
|
-
* @throws {Error} If the injection target is not a class
|
|
75
|
-
* @throws {Error} If a factory or singleton with the same name is already defined
|
|
76
|
-
* @throws {Error} If the target is not a class constructor
|
|
77
|
-
*/
|
|
39
|
+
/** @param {string} [name] */
|
|
78
40
|
export function Factory(name) {
|
|
79
|
-
return
|
|
41
|
+
return (clazz, context) => {
|
|
80
42
|
if (context.kind !== 'class') {
|
|
81
43
|
throw new Error('Invalid injection target')
|
|
82
44
|
}
|
|
@@ -88,35 +50,19 @@ export function Factory(name) {
|
|
|
88
50
|
}
|
|
89
51
|
|
|
90
52
|
/**
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
* Supports:
|
|
95
|
-
* - Public fields: @Inject(MyClass) myField
|
|
96
|
-
* - Private fields: @Inject(MyClass) #myField
|
|
97
|
-
* - Accessors: @Inject(MyClass) accessor myField
|
|
98
|
-
* - Private accessors: @Inject(MyClass) accessor #myField
|
|
99
|
-
*
|
|
100
|
-
* @param {string|Function} clazzOrName The singleton or factory class or name
|
|
101
|
-
* @param {...*} params Parameters to pass to the constructor. Recommended to use only with factories.
|
|
102
|
-
* @returns {(function(*, {kind: string, name: string}): function(): Object)}
|
|
103
|
-
* @example @Inject(MySingleton) mySingleton
|
|
104
|
-
* @example @Inject("myCustomName") myFactory
|
|
105
|
-
* @example @Inject(MyService) #privateService
|
|
106
|
-
* @example @Inject(MyService) accessor myService
|
|
107
|
-
* @throws {Error} If the injection target is not a field or accessor
|
|
108
|
-
* @throws {Error} If the injected field is assigned a value
|
|
53
|
+
* @param {string|Function} clazzOrName
|
|
54
|
+
* @param {...*} params
|
|
109
55
|
*/
|
|
110
56
|
export function Inject(clazzOrName, ...params) {
|
|
111
|
-
return
|
|
57
|
+
return (_, context) => {
|
|
112
58
|
const getValue = () => {
|
|
113
59
|
const instanceContext = defaultContainer.getContext(clazzOrName)
|
|
114
60
|
return defaultContainer.getInstance(instanceContext, params)
|
|
115
61
|
}
|
|
116
62
|
|
|
117
63
|
if (context.kind === 'field') {
|
|
118
|
-
return
|
|
119
|
-
if (initialValue) {
|
|
64
|
+
return (initialValue) => {
|
|
65
|
+
if (initialValue !== undefined) {
|
|
120
66
|
throw new Error(`Cannot assign value to injected field "${context.name}"`)
|
|
121
67
|
}
|
|
122
68
|
return getValue()
|
|
@@ -133,31 +79,9 @@ export function Inject(clazzOrName, ...params) {
|
|
|
133
79
|
}
|
|
134
80
|
|
|
135
81
|
/**
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
* The lazy injection defers instantiation until the field is first accessed. This is useful for:
|
|
140
|
-
* - Breaking circular dependencies
|
|
141
|
-
* - Deferring expensive initializations
|
|
142
|
-
*
|
|
143
|
-
* Supports:
|
|
144
|
-
* - Public fields: @InjectLazy(MyClass) myField
|
|
145
|
-
* - Private fields: @InjectLazy(MyClass) #myField
|
|
146
|
-
* - Accessors: @InjectLazy(MyClass) accessor myField
|
|
147
|
-
* - Private accessors: @InjectLazy(MyClass) accessor #myField
|
|
148
|
-
*
|
|
149
|
-
* Note: For private fields, the lazy behavior is achieved through the field initializer
|
|
150
|
-
* returning a getter-based proxy. For accessors, it's achieved through the accessor's
|
|
151
|
-
* get/set methods directly.
|
|
152
|
-
*
|
|
153
|
-
* @param {string|Function} clazzOrName The singleton or factory class or name
|
|
154
|
-
* @param {...*} params Parameters to pass to the constructor. Recommended to use only with factories.
|
|
155
|
-
* @returns {(function(*, {kind: string, name: string, addInitializer: Function}): void)}
|
|
156
|
-
* @example @InjectLazy(MySingleton) mySingleton
|
|
157
|
-
* @example @InjectLazy("myCustomName") myFactory
|
|
158
|
-
* @example @InjectLazy(MyService) #privateService
|
|
159
|
-
* @throws {Error} If the injection target is not a field or accessor
|
|
160
|
-
* @throws {Error} If the injected field is assigned a value
|
|
82
|
+
* Defers instantiation until first access. For private fields, use accessor syntax for true lazy behavior.
|
|
83
|
+
* @param {string|Function} clazzOrName
|
|
84
|
+
* @param {...*} params
|
|
161
85
|
*/
|
|
162
86
|
export function InjectLazy(clazzOrName, ...params) {
|
|
163
87
|
const cache = new WeakMap()
|
|
@@ -172,8 +96,8 @@ export function InjectLazy(clazzOrName, ...params) {
|
|
|
172
96
|
// For private fields, we cannot use Object.defineProperty to create a lazy getter.
|
|
173
97
|
// Instead, we eagerly create the value. For true lazy behavior, use accessor syntax.
|
|
174
98
|
if (context.private) {
|
|
175
|
-
return
|
|
176
|
-
if (initialValue) {
|
|
99
|
+
return (initialValue) => {
|
|
100
|
+
if (initialValue !== undefined) {
|
|
177
101
|
throw new Error(`Cannot assign value to lazy-injected field "${context.name}"`)
|
|
178
102
|
}
|
|
179
103
|
return getValue()
|
|
@@ -208,19 +132,11 @@ export function InjectLazy(clazzOrName, ...params) {
|
|
|
208
132
|
}
|
|
209
133
|
|
|
210
134
|
/**
|
|
211
|
-
*
|
|
212
|
-
*
|
|
213
|
-
* @param {string|Function} mockedClazzOrName The singleton or factory class or name to be mocked
|
|
214
|
-
* @param {boolean} [proxy=false] If true, the mock will proxy to the original class.
|
|
215
|
-
* Any methods not defined in the mock will be called on the original class.
|
|
216
|
-
* @returns {(function(Function, {kind: string}): void)}
|
|
217
|
-
* @example @Mock(MySingleton) class MyMock {}
|
|
218
|
-
* @example @Mock("myCustomName", true) class MyMock {}
|
|
219
|
-
* @throws {Error} If the injection target is not a class
|
|
220
|
-
* @throws {Error} If the injection source is not found
|
|
135
|
+
* @param {string|Function} mockedClazzOrName
|
|
136
|
+
* @param {boolean} [proxy=false] If true, unmocked methods delegate to the original
|
|
221
137
|
*/
|
|
222
138
|
export function Mock(mockedClazzOrName, proxy = false) {
|
|
223
|
-
return
|
|
139
|
+
return (clazz, context) => {
|
|
224
140
|
if (context.kind !== 'class') {
|
|
225
141
|
throw new Error('Invalid injection target')
|
|
226
142
|
}
|
|
@@ -228,120 +144,97 @@ export function Mock(mockedClazzOrName, proxy = false) {
|
|
|
228
144
|
}
|
|
229
145
|
}
|
|
230
146
|
|
|
231
|
-
/**
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
export function resetMocks() {
|
|
235
|
-
defaultContainer.resetAllMocks()
|
|
147
|
+
/** Remove all mocks and restore originals. Does NOT clear mock call history. */
|
|
148
|
+
export function removeAllMocks() {
|
|
149
|
+
defaultContainer.removeAllMocks()
|
|
236
150
|
}
|
|
237
151
|
|
|
238
|
-
/**
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
* @param {string|Function} clazzOrName The singleton or factory class or name to reset
|
|
242
|
-
*/
|
|
243
|
-
export function resetMock(clazzOrName) {
|
|
244
|
-
defaultContainer.resetMock(clazzOrName)
|
|
152
|
+
/** @param {string|Function} clazzOrName */
|
|
153
|
+
export function removeMock(clazzOrName) {
|
|
154
|
+
defaultContainer.removeMock(clazzOrName)
|
|
245
155
|
}
|
|
246
156
|
|
|
247
|
-
/**
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
*/
|
|
251
|
-
export function clearContainer() {
|
|
252
|
-
defaultContainer.clear()
|
|
157
|
+
/** @param {{preserveMocks?: boolean}} [options] */
|
|
158
|
+
export function resetSingletons(options) {
|
|
159
|
+
defaultContainer.resetSingletons(options)
|
|
253
160
|
}
|
|
254
161
|
|
|
255
|
-
/**
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
*/
|
|
162
|
+
/** @param {{preserveRegistrations?: boolean}} [options] */
|
|
163
|
+
export function clearContainer(options) {
|
|
164
|
+
defaultContainer.clear(options)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** @returns {Container} */
|
|
261
168
|
export function getContainer() {
|
|
262
169
|
return defaultContainer
|
|
263
170
|
}
|
|
264
171
|
|
|
265
|
-
/**
|
|
266
|
-
* Enable or disable debug logging for dependency injection.
|
|
267
|
-
* When enabled, logs when instances are registered, created, and mocked.
|
|
268
|
-
*
|
|
269
|
-
* @param {boolean} enabled Whether to enable debug mode
|
|
270
|
-
* @example
|
|
271
|
-
* setDebug(true)
|
|
272
|
-
* // [DI] Registered singleton: UserService
|
|
273
|
-
* // [DI] Creating singleton: UserService
|
|
274
|
-
*/
|
|
172
|
+
/** @param {boolean} enabled */
|
|
275
173
|
export function setDebug(enabled) {
|
|
276
174
|
defaultContainer.setDebug(enabled)
|
|
277
175
|
}
|
|
278
176
|
|
|
279
177
|
/**
|
|
280
|
-
*
|
|
281
|
-
*
|
|
282
|
-
*
|
|
283
|
-
* @param {string|Function} clazzOrName The class or name to check
|
|
284
|
-
* @returns {boolean} true if registered, false otherwise
|
|
285
|
-
* @example
|
|
286
|
-
* if (!isRegistered(MyService)) {
|
|
287
|
-
* console.warn('MyService not registered!')
|
|
288
|
-
* }
|
|
178
|
+
* @param {string|Function} clazzOrName
|
|
179
|
+
* @returns {boolean}
|
|
289
180
|
*/
|
|
290
181
|
export function isRegistered(clazzOrName) {
|
|
291
182
|
return defaultContainer.has(clazzOrName)
|
|
292
183
|
}
|
|
293
184
|
|
|
294
|
-
/**
|
|
295
|
-
* Validate that all provided injection tokens are registered.
|
|
296
|
-
* Throws an error with details about missing registrations.
|
|
297
|
-
* Useful for fail-fast validation at application startup.
|
|
298
|
-
*
|
|
299
|
-
* @param {...(string|Function)} tokens Classes or names to validate
|
|
300
|
-
* @throws {Error} If any token is not registered
|
|
301
|
-
* @example
|
|
302
|
-
* // At app startup:
|
|
303
|
-
* validateRegistrations(UserService, AuthService, 'databaseConnection')
|
|
304
|
-
*/
|
|
185
|
+
/** @param {...(string|Function)} tokens */
|
|
305
186
|
export function validateRegistrations(...tokens) {
|
|
306
187
|
const missing = tokens.filter(token => !defaultContainer.has(token))
|
|
307
|
-
if (missing.length
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
)
|
|
313
|
-
|
|
188
|
+
if (missing.length === 0) return
|
|
189
|
+
|
|
190
|
+
const names = missing.map(t => t?.name ?? t).join(', ')
|
|
191
|
+
throw new Error(
|
|
192
|
+
`Missing registrations: [${names}]. ` +
|
|
193
|
+
`Ensure these classes are decorated with @Singleton() or @Factory() before use.`
|
|
194
|
+
)
|
|
314
195
|
}
|
|
315
196
|
|
|
316
197
|
/**
|
|
317
|
-
* Resolve and return an instance by class or name.
|
|
318
|
-
* This allows non-decorator code (plain functions, modules, etc.) to retrieve
|
|
319
|
-
* instances from the DI container.
|
|
320
|
-
*
|
|
321
198
|
* @template T
|
|
322
|
-
* @param {string|Function} clazzOrName
|
|
323
|
-
* @param {...*} params
|
|
324
|
-
* @returns {T}
|
|
325
|
-
* @throws {Error} If the class or name is not registered
|
|
326
|
-
* @example
|
|
327
|
-
* // In a plain function:
|
|
328
|
-
* function handleRequest(req) {
|
|
329
|
-
* const userService = resolve(UserService)
|
|
330
|
-
* return userService.getUser(req.userId)
|
|
331
|
-
* }
|
|
332
|
-
* @example
|
|
333
|
-
* // With a named registration:
|
|
334
|
-
* const db = resolve('database')
|
|
335
|
-
* @example
|
|
336
|
-
* // With factory parameters:
|
|
337
|
-
* const logger = resolve(Logger, 'my-module')
|
|
199
|
+
* @param {string|Function} clazzOrName
|
|
200
|
+
* @param {...*} params
|
|
201
|
+
* @returns {T}
|
|
338
202
|
*/
|
|
339
203
|
export function resolve(clazzOrName, ...params) {
|
|
340
204
|
return defaultContainer.resolve(clazzOrName, ...params)
|
|
341
205
|
}
|
|
342
206
|
|
|
343
|
-
|
|
344
|
-
|
|
207
|
+
/**
|
|
208
|
+
* @template T
|
|
209
|
+
* @param {string|Function} clazzOrName
|
|
210
|
+
* @param {...*} params
|
|
211
|
+
* @returns {T}
|
|
212
|
+
*/
|
|
213
|
+
export function getMockInstance(clazzOrName, ...params) {
|
|
214
|
+
return defaultContainer.getMockInstance(clazzOrName, ...params)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* @param {string|Function} clazzOrName
|
|
219
|
+
* @returns {boolean}
|
|
220
|
+
*/
|
|
221
|
+
export function isMocked(clazzOrName) {
|
|
222
|
+
return defaultContainer.isMocked(clazzOrName)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* @param {string|Function} clazzOrName
|
|
227
|
+
* @returns {boolean}
|
|
228
|
+
*/
|
|
229
|
+
export function unregister(clazzOrName) {
|
|
230
|
+
return defaultContainer.unregister(clazzOrName)
|
|
231
|
+
}
|
|
345
232
|
|
|
346
|
-
|
|
233
|
+
/** @returns {Array<{key: string|Function, name: string, type: 'singleton'|'factory', isMocked: boolean, hasInstance: boolean}>} */
|
|
234
|
+
export function listRegistrations() {
|
|
235
|
+
return defaultContainer.list()
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export {Container}
|
|
239
|
+
export {defaultContainer}
|
|
347
240
|
export {createProxy} from './src/proxy.js'
|