@abdokouta/ts-container 1.0.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/LICENSE +21 -0
- package/dist/index.cjs +1125 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1576 -0
- package/dist/index.d.ts +1576 -0
- package/dist/index.js +1102 -0
- package/dist/index.js.map +1 -0
- package/package.json +59 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1125 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
require('reflect-metadata');
|
|
4
|
+
|
|
5
|
+
// src/index.ts
|
|
6
|
+
|
|
7
|
+
// src/constants/tokens.constant.ts
|
|
8
|
+
var MODULE_METADATA = {
|
|
9
|
+
IMPORTS: "imports",
|
|
10
|
+
PROVIDERS: "providers",
|
|
11
|
+
EXPORTS: "exports"
|
|
12
|
+
};
|
|
13
|
+
var GLOBAL_MODULE_METADATA = "__module:global__";
|
|
14
|
+
var INJECTABLE_WATERMARK = "__injectable__";
|
|
15
|
+
var SCOPE_OPTIONS_METADATA = "scope:options";
|
|
16
|
+
var PARAMTYPES_METADATA = "design:paramtypes";
|
|
17
|
+
var SELF_DECLARED_DEPS_METADATA = "self:paramtypes";
|
|
18
|
+
var OPTIONAL_DEPS_METADATA = "optional:paramtypes";
|
|
19
|
+
var PROPERTY_DEPS_METADATA = "self:properties_metadata";
|
|
20
|
+
var OPTIONAL_PROPERTY_DEPS_METADATA = "optional:properties_metadata";
|
|
21
|
+
|
|
22
|
+
// src/decorators/injectable.decorator.ts
|
|
23
|
+
function Injectable(options) {
|
|
24
|
+
return (target) => {
|
|
25
|
+
Reflect.defineMetadata(INJECTABLE_WATERMARK, true, target);
|
|
26
|
+
Reflect.defineMetadata(SCOPE_OPTIONS_METADATA, options, target);
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function Inject(token) {
|
|
30
|
+
const hasExplicitToken = arguments.length > 0;
|
|
31
|
+
return (target, key, index) => {
|
|
32
|
+
let resolvedToken = token;
|
|
33
|
+
if (!resolvedToken && !hasExplicitToken) {
|
|
34
|
+
if (key !== void 0) {
|
|
35
|
+
resolvedToken = Reflect.getMetadata("design:type", target, key);
|
|
36
|
+
} else if (index !== void 0) {
|
|
37
|
+
const paramTypes = Reflect.getMetadata(PARAMTYPES_METADATA, target) || [];
|
|
38
|
+
resolvedToken = paramTypes[index];
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (resolvedToken && typeof resolvedToken === "object" && "forwardRef" in resolvedToken) {
|
|
42
|
+
resolvedToken = resolvedToken.forwardRef();
|
|
43
|
+
}
|
|
44
|
+
if (index !== void 0) {
|
|
45
|
+
const existingDeps = Reflect.getMetadata(SELF_DECLARED_DEPS_METADATA, target) || [];
|
|
46
|
+
Reflect.defineMetadata(
|
|
47
|
+
SELF_DECLARED_DEPS_METADATA,
|
|
48
|
+
[...existingDeps, { index, param: resolvedToken }],
|
|
49
|
+
target
|
|
50
|
+
);
|
|
51
|
+
} else {
|
|
52
|
+
const existingProps = Reflect.getMetadata(PROPERTY_DEPS_METADATA, target.constructor) || [];
|
|
53
|
+
Reflect.defineMetadata(
|
|
54
|
+
PROPERTY_DEPS_METADATA,
|
|
55
|
+
[...existingProps, { key, type: resolvedToken }],
|
|
56
|
+
target.constructor
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function Optional() {
|
|
62
|
+
return (target, key, index) => {
|
|
63
|
+
if (index !== void 0) {
|
|
64
|
+
const existingOptional = Reflect.getMetadata(OPTIONAL_DEPS_METADATA, target) || [];
|
|
65
|
+
Reflect.defineMetadata(
|
|
66
|
+
OPTIONAL_DEPS_METADATA,
|
|
67
|
+
[...existingOptional, index],
|
|
68
|
+
target
|
|
69
|
+
);
|
|
70
|
+
} else {
|
|
71
|
+
const existingOptional = Reflect.getMetadata(OPTIONAL_PROPERTY_DEPS_METADATA, target.constructor) || [];
|
|
72
|
+
Reflect.defineMetadata(
|
|
73
|
+
OPTIONAL_PROPERTY_DEPS_METADATA,
|
|
74
|
+
[...existingOptional, key],
|
|
75
|
+
target.constructor
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
var VALID_MODULE_KEYS = /* @__PURE__ */ new Set(["imports", "providers", "exports"]);
|
|
81
|
+
function Module(metadata) {
|
|
82
|
+
const invalidKeys = Object.keys(metadata).filter((key) => !VALID_MODULE_KEYS.has(key));
|
|
83
|
+
if (invalidKeys.length > 0) {
|
|
84
|
+
throw new Error(
|
|
85
|
+
`Invalid property '${invalidKeys.join("', '")}' passed into the @Module() decorator. Valid properties are: ${[...VALID_MODULE_KEYS].join(", ")}.`
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
return (target) => {
|
|
89
|
+
for (const property in metadata) {
|
|
90
|
+
if (Object.prototype.hasOwnProperty.call(metadata, property)) {
|
|
91
|
+
Reflect.defineMetadata(property, metadata[property], target);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function Global() {
|
|
97
|
+
return (target) => {
|
|
98
|
+
Reflect.defineMetadata(GLOBAL_MODULE_METADATA, true, target);
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// src/interfaces/scope.enum.ts
|
|
103
|
+
var Scope = /* @__PURE__ */ ((Scope2) => {
|
|
104
|
+
Scope2[Scope2["DEFAULT"] = 0] = "DEFAULT";
|
|
105
|
+
Scope2[Scope2["TRANSIENT"] = 1] = "TRANSIENT";
|
|
106
|
+
return Scope2;
|
|
107
|
+
})(Scope || {});
|
|
108
|
+
|
|
109
|
+
// src/utils/forward-ref.util.ts
|
|
110
|
+
function forwardRef(fn) {
|
|
111
|
+
return { forwardRef: fn };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// src/interfaces/provider.interface.ts
|
|
115
|
+
function isCustomProvider(provider) {
|
|
116
|
+
return provider !== null && typeof provider === "object" && "provide" in provider;
|
|
117
|
+
}
|
|
118
|
+
function isClassProvider(provider) {
|
|
119
|
+
return isCustomProvider(provider) && "useClass" in provider && provider.useClass !== void 0;
|
|
120
|
+
}
|
|
121
|
+
function isValueProvider(provider) {
|
|
122
|
+
return isCustomProvider(provider) && "useValue" in provider;
|
|
123
|
+
}
|
|
124
|
+
function isFactoryProvider(provider) {
|
|
125
|
+
return isCustomProvider(provider) && "useFactory" in provider && typeof provider.useFactory === "function";
|
|
126
|
+
}
|
|
127
|
+
function isExistingProvider(provider) {
|
|
128
|
+
return isCustomProvider(provider) && "useExisting" in provider && provider.useExisting !== void 0;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// src/injector/instance-wrapper.ts
|
|
132
|
+
var InstanceWrapper = class {
|
|
133
|
+
/**
|
|
134
|
+
* The injection token used to look up this provider.
|
|
135
|
+
*/
|
|
136
|
+
token;
|
|
137
|
+
/**
|
|
138
|
+
* Human-readable name (class name or token string).
|
|
139
|
+
*/
|
|
140
|
+
name;
|
|
141
|
+
/**
|
|
142
|
+
* The class constructor or factory function.
|
|
143
|
+
* - For class providers: the class to `new`
|
|
144
|
+
* - For factory providers: the factory function
|
|
145
|
+
* - For value providers: `null`
|
|
146
|
+
*/
|
|
147
|
+
metatype;
|
|
148
|
+
/**
|
|
149
|
+
* The resolved instance.
|
|
150
|
+
* - `null` before resolution
|
|
151
|
+
* - The actual instance after resolution
|
|
152
|
+
* - For value providers: set immediately at registration
|
|
153
|
+
*/
|
|
154
|
+
instance = null;
|
|
155
|
+
/**
|
|
156
|
+
* Whether this provider has been fully resolved (instance created).
|
|
157
|
+
*/
|
|
158
|
+
isResolved = false;
|
|
159
|
+
/**
|
|
160
|
+
* The scope of this provider.
|
|
161
|
+
*/
|
|
162
|
+
scope = 0 /* DEFAULT */;
|
|
163
|
+
/**
|
|
164
|
+
* For factory providers: the tokens to inject as factory arguments.
|
|
165
|
+
* `null` for class and value providers.
|
|
166
|
+
*/
|
|
167
|
+
inject = null;
|
|
168
|
+
/**
|
|
169
|
+
* Whether this is an alias (useExisting) provider.
|
|
170
|
+
* Alias providers delegate resolution to another token.
|
|
171
|
+
*/
|
|
172
|
+
isAlias = false;
|
|
173
|
+
/**
|
|
174
|
+
* Whether the instance is a Promise (async factory).
|
|
175
|
+
*/
|
|
176
|
+
async = false;
|
|
177
|
+
/**
|
|
178
|
+
* The module this provider belongs to.
|
|
179
|
+
*/
|
|
180
|
+
host = null;
|
|
181
|
+
/**
|
|
182
|
+
* Create a new InstanceWrapper.
|
|
183
|
+
*
|
|
184
|
+
* @param metadata - Initial values for the wrapper properties
|
|
185
|
+
*/
|
|
186
|
+
constructor(metadata = {}) {
|
|
187
|
+
this.token = metadata.token;
|
|
188
|
+
this.name = metadata.name ?? this.getTokenName(metadata.token);
|
|
189
|
+
this.metatype = metadata.metatype ?? null;
|
|
190
|
+
this.instance = metadata.instance ?? null;
|
|
191
|
+
this.isResolved = metadata.isResolved ?? false;
|
|
192
|
+
this.scope = metadata.scope ?? 0 /* DEFAULT */;
|
|
193
|
+
this.inject = metadata.inject ?? null;
|
|
194
|
+
this.isAlias = metadata.isAlias ?? false;
|
|
195
|
+
this.async = metadata.async ?? false;
|
|
196
|
+
this.host = metadata.host ?? null;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Whether this provider is a factory (has an `inject` array).
|
|
200
|
+
* Factory providers are invoked as functions, not constructed with `new`.
|
|
201
|
+
*/
|
|
202
|
+
get isFactory() {
|
|
203
|
+
return this.inject !== null;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Whether this provider is transient (new instance per injection).
|
|
207
|
+
*/
|
|
208
|
+
get isTransient() {
|
|
209
|
+
return this.scope === 1 /* TRANSIENT */;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Extract a human-readable name from a token.
|
|
213
|
+
*/
|
|
214
|
+
getTokenName(token) {
|
|
215
|
+
if (typeof token === "function") return token.name;
|
|
216
|
+
if (typeof token === "symbol") return token.toString();
|
|
217
|
+
return String(token);
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
// src/injector/module.ts
|
|
222
|
+
var Module2 = class {
|
|
223
|
+
/**
|
|
224
|
+
* Unique identifier for this module instance.
|
|
225
|
+
*/
|
|
226
|
+
id;
|
|
227
|
+
/**
|
|
228
|
+
* The original class decorated with @Module().
|
|
229
|
+
*/
|
|
230
|
+
metatype;
|
|
231
|
+
/**
|
|
232
|
+
* Whether this module is global (its exports are available everywhere).
|
|
233
|
+
*/
|
|
234
|
+
isGlobal = false;
|
|
235
|
+
/**
|
|
236
|
+
* The opaque token used to identify this module in the container.
|
|
237
|
+
*/
|
|
238
|
+
token = "";
|
|
239
|
+
/**
|
|
240
|
+
* All providers registered in this module.
|
|
241
|
+
* Key: injection token, Value: InstanceWrapper
|
|
242
|
+
*/
|
|
243
|
+
_providers = /* @__PURE__ */ new Map();
|
|
244
|
+
/**
|
|
245
|
+
* Imported modules (their exports are available to this module).
|
|
246
|
+
*/
|
|
247
|
+
_imports = /* @__PURE__ */ new Set();
|
|
248
|
+
/**
|
|
249
|
+
* Tokens that this module exports (available to modules that import this one).
|
|
250
|
+
*/
|
|
251
|
+
_exports = /* @__PURE__ */ new Set();
|
|
252
|
+
constructor(metatype) {
|
|
253
|
+
this.metatype = metatype;
|
|
254
|
+
this.id = `${metatype.name}_${Math.random().toString(36).slice(2, 8)}`;
|
|
255
|
+
}
|
|
256
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
257
|
+
// Accessors
|
|
258
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
259
|
+
get name() {
|
|
260
|
+
return this.metatype.name;
|
|
261
|
+
}
|
|
262
|
+
get providers() {
|
|
263
|
+
return this._providers;
|
|
264
|
+
}
|
|
265
|
+
get imports() {
|
|
266
|
+
return this._imports;
|
|
267
|
+
}
|
|
268
|
+
get exports() {
|
|
269
|
+
return this._exports;
|
|
270
|
+
}
|
|
271
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
272
|
+
// Provider registration
|
|
273
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
274
|
+
/**
|
|
275
|
+
* Register a provider in this module.
|
|
276
|
+
*
|
|
277
|
+
* Handles all provider forms:
|
|
278
|
+
* - Class shorthand: `UserService`
|
|
279
|
+
* - Class provider: `{ provide: Token, useClass: UserService }`
|
|
280
|
+
* - Value provider: `{ provide: Token, useValue: someValue }`
|
|
281
|
+
* - Factory provider: `{ provide: Token, useFactory: fn, inject: [...] }`
|
|
282
|
+
* - Existing provider: `{ provide: Token, useExisting: OtherToken }`
|
|
283
|
+
*
|
|
284
|
+
* @param provider - The provider to register
|
|
285
|
+
* @returns The injection token for this provider
|
|
286
|
+
*/
|
|
287
|
+
addProvider(provider) {
|
|
288
|
+
if (isCustomProvider(provider)) {
|
|
289
|
+
return this.addCustomProvider(provider);
|
|
290
|
+
}
|
|
291
|
+
const classRef = provider;
|
|
292
|
+
const scope = this.getClassScope(classRef);
|
|
293
|
+
this._providers.set(
|
|
294
|
+
classRef,
|
|
295
|
+
new InstanceWrapper({
|
|
296
|
+
token: classRef,
|
|
297
|
+
name: classRef.name,
|
|
298
|
+
metatype: classRef,
|
|
299
|
+
instance: null,
|
|
300
|
+
isResolved: false,
|
|
301
|
+
scope,
|
|
302
|
+
host: this
|
|
303
|
+
})
|
|
304
|
+
);
|
|
305
|
+
return classRef;
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Register a custom provider (one with a `provide` property).
|
|
309
|
+
*/
|
|
310
|
+
addCustomProvider(provider) {
|
|
311
|
+
if (isClassProvider(provider)) {
|
|
312
|
+
this.addClassProvider(provider);
|
|
313
|
+
} else if (isValueProvider(provider)) {
|
|
314
|
+
this.addValueProvider(provider);
|
|
315
|
+
} else if (isFactoryProvider(provider)) {
|
|
316
|
+
this.addFactoryProvider(provider);
|
|
317
|
+
} else if (isExistingProvider(provider)) {
|
|
318
|
+
this.addExistingProvider(provider);
|
|
319
|
+
}
|
|
320
|
+
return provider.provide;
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Register a class provider: `{ provide: Token, useClass: SomeClass }`
|
|
324
|
+
*/
|
|
325
|
+
addClassProvider(provider) {
|
|
326
|
+
const scope = provider.scope ?? this.getClassScope(provider.useClass);
|
|
327
|
+
this._providers.set(
|
|
328
|
+
provider.provide,
|
|
329
|
+
new InstanceWrapper({
|
|
330
|
+
token: provider.provide,
|
|
331
|
+
name: provider.useClass?.name ?? String(provider.provide),
|
|
332
|
+
metatype: provider.useClass,
|
|
333
|
+
instance: null,
|
|
334
|
+
isResolved: false,
|
|
335
|
+
scope,
|
|
336
|
+
host: this
|
|
337
|
+
})
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Register a value provider: `{ provide: Token, useValue: value }`
|
|
342
|
+
*
|
|
343
|
+
* Value providers are immediately resolved — the value is stored as-is.
|
|
344
|
+
*/
|
|
345
|
+
addValueProvider(provider) {
|
|
346
|
+
this._providers.set(
|
|
347
|
+
provider.provide,
|
|
348
|
+
new InstanceWrapper({
|
|
349
|
+
token: provider.provide,
|
|
350
|
+
name: this.getTokenName(provider.provide),
|
|
351
|
+
metatype: null,
|
|
352
|
+
instance: provider.useValue,
|
|
353
|
+
isResolved: true,
|
|
354
|
+
async: provider.useValue instanceof Promise,
|
|
355
|
+
host: this
|
|
356
|
+
})
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Register a factory provider: `{ provide: Token, useFactory: fn, inject: [...] }`
|
|
361
|
+
*
|
|
362
|
+
* The factory function is stored as the metatype and will be called
|
|
363
|
+
* (not constructed with `new`) during resolution.
|
|
364
|
+
*/
|
|
365
|
+
addFactoryProvider(provider) {
|
|
366
|
+
this._providers.set(
|
|
367
|
+
provider.provide,
|
|
368
|
+
new InstanceWrapper({
|
|
369
|
+
token: provider.provide,
|
|
370
|
+
name: this.getTokenName(provider.provide),
|
|
371
|
+
metatype: provider.useFactory,
|
|
372
|
+
instance: null,
|
|
373
|
+
isResolved: false,
|
|
374
|
+
inject: provider.inject ?? [],
|
|
375
|
+
scope: provider.scope ?? 0 /* DEFAULT */,
|
|
376
|
+
host: this
|
|
377
|
+
})
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Register an existing (alias) provider: `{ provide: Token, useExisting: OtherToken }`
|
|
382
|
+
*
|
|
383
|
+
* Implemented as a factory that resolves the target token.
|
|
384
|
+
*/
|
|
385
|
+
addExistingProvider(provider) {
|
|
386
|
+
this._providers.set(
|
|
387
|
+
provider.provide,
|
|
388
|
+
new InstanceWrapper({
|
|
389
|
+
token: provider.provide,
|
|
390
|
+
name: this.getTokenName(provider.provide),
|
|
391
|
+
metatype: ((instance) => instance),
|
|
392
|
+
instance: null,
|
|
393
|
+
isResolved: false,
|
|
394
|
+
inject: [provider.useExisting],
|
|
395
|
+
isAlias: true,
|
|
396
|
+
host: this
|
|
397
|
+
})
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
401
|
+
// Imports & Exports
|
|
402
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
403
|
+
/**
|
|
404
|
+
* Add an imported module.
|
|
405
|
+
*/
|
|
406
|
+
addImport(moduleRef) {
|
|
407
|
+
this._imports.add(moduleRef);
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Add an exported token.
|
|
411
|
+
*
|
|
412
|
+
* @param token - The token to export (class, string, symbol, or module class)
|
|
413
|
+
*/
|
|
414
|
+
addExport(token) {
|
|
415
|
+
this._exports.add(token);
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Check if this module has a provider for the given token.
|
|
419
|
+
*/
|
|
420
|
+
hasProvider(token) {
|
|
421
|
+
return this._providers.has(token);
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Get a provider wrapper by token.
|
|
425
|
+
*/
|
|
426
|
+
getProviderByToken(token) {
|
|
427
|
+
return this._providers.get(token);
|
|
428
|
+
}
|
|
429
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
430
|
+
// Helpers
|
|
431
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
432
|
+
/**
|
|
433
|
+
* Read the scope from a class's @Injectable() metadata.
|
|
434
|
+
*/
|
|
435
|
+
getClassScope(type) {
|
|
436
|
+
const options = Reflect.getMetadata(SCOPE_OPTIONS_METADATA, type);
|
|
437
|
+
return options?.scope ?? 0 /* DEFAULT */;
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Get a human-readable name from a token.
|
|
441
|
+
*/
|
|
442
|
+
getTokenName(token) {
|
|
443
|
+
if (typeof token === "function") return token.name;
|
|
444
|
+
if (typeof token === "symbol") return token.toString();
|
|
445
|
+
return String(token);
|
|
446
|
+
}
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
// src/injector/container.ts
|
|
450
|
+
var NestContainer = class {
|
|
451
|
+
/**
|
|
452
|
+
* All registered modules, keyed by their opaque token.
|
|
453
|
+
* The token is derived from the module class name (or a hash for dynamic modules).
|
|
454
|
+
*/
|
|
455
|
+
modules = /* @__PURE__ */ new Map();
|
|
456
|
+
/**
|
|
457
|
+
* Global modules whose exports are available to all other modules.
|
|
458
|
+
*/
|
|
459
|
+
globalModules = /* @__PURE__ */ new Set();
|
|
460
|
+
/**
|
|
461
|
+
* Dynamic module metadata, keyed by module token.
|
|
462
|
+
* Stored separately because dynamic metadata is merged with static @Module() metadata.
|
|
463
|
+
*/
|
|
464
|
+
dynamicModulesMetadata = /* @__PURE__ */ new Map();
|
|
465
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
466
|
+
// Module registration
|
|
467
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
468
|
+
/**
|
|
469
|
+
* Add a module to the container.
|
|
470
|
+
*
|
|
471
|
+
* If the module is already registered (by token), returns the existing one.
|
|
472
|
+
* Otherwise, creates a new Module instance and registers it.
|
|
473
|
+
*
|
|
474
|
+
* @param metatype - The module class or dynamic module
|
|
475
|
+
* @returns The Module instance and whether it was newly inserted
|
|
476
|
+
*/
|
|
477
|
+
async addModule(metatype) {
|
|
478
|
+
const resolved = metatype instanceof Promise ? await metatype : metatype;
|
|
479
|
+
const { type, dynamicMetadata, token } = this.extractModuleMetadata(resolved);
|
|
480
|
+
if (this.modules.has(token)) {
|
|
481
|
+
return { moduleRef: this.modules.get(token), inserted: false };
|
|
482
|
+
}
|
|
483
|
+
const moduleRef = new Module2(type);
|
|
484
|
+
moduleRef.token = token;
|
|
485
|
+
this.modules.set(token, moduleRef);
|
|
486
|
+
if (dynamicMetadata) {
|
|
487
|
+
this.dynamicModulesMetadata.set(token, dynamicMetadata);
|
|
488
|
+
}
|
|
489
|
+
if (this.isGlobalModule(type, dynamicMetadata)) {
|
|
490
|
+
moduleRef.isGlobal = true;
|
|
491
|
+
this.globalModules.add(moduleRef);
|
|
492
|
+
}
|
|
493
|
+
return { moduleRef, inserted: true };
|
|
494
|
+
}
|
|
495
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
496
|
+
// Provider, Import, Export registration
|
|
497
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
498
|
+
/**
|
|
499
|
+
* Add a provider to a module.
|
|
500
|
+
*
|
|
501
|
+
* @param provider - The provider to add
|
|
502
|
+
* @param token - The module token to add the provider to
|
|
503
|
+
*/
|
|
504
|
+
addProvider(provider, token) {
|
|
505
|
+
const moduleRef = this.modules.get(token);
|
|
506
|
+
if (!moduleRef) {
|
|
507
|
+
throw new Error(`Module [${token}] not found in container.`);
|
|
508
|
+
}
|
|
509
|
+
moduleRef.addProvider(provider);
|
|
510
|
+
}
|
|
511
|
+
/**
|
|
512
|
+
* Add an import relationship between modules.
|
|
513
|
+
*
|
|
514
|
+
* @param relatedModule - The module being imported
|
|
515
|
+
* @param token - The token of the module doing the importing
|
|
516
|
+
*/
|
|
517
|
+
addImport(relatedModule, token) {
|
|
518
|
+
const moduleRef = this.modules.get(token);
|
|
519
|
+
if (!moduleRef) return;
|
|
520
|
+
const { token: relatedToken } = this.extractModuleMetadata(relatedModule);
|
|
521
|
+
const related = this.modules.get(relatedToken);
|
|
522
|
+
if (related) {
|
|
523
|
+
moduleRef.addImport(related);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* Add an export to a module.
|
|
528
|
+
*
|
|
529
|
+
* @param toExport - The token or provider to export
|
|
530
|
+
* @param token - The module token
|
|
531
|
+
*/
|
|
532
|
+
addExport(toExport, token) {
|
|
533
|
+
const moduleRef = this.modules.get(token);
|
|
534
|
+
if (!moduleRef) return;
|
|
535
|
+
if (typeof toExport === "object" && toExport !== null && "module" in toExport) {
|
|
536
|
+
moduleRef.addExport(toExport.module);
|
|
537
|
+
} else if (typeof toExport === "object" && toExport !== null && "provide" in toExport) {
|
|
538
|
+
moduleRef.addExport(toExport.provide);
|
|
539
|
+
} else {
|
|
540
|
+
moduleRef.addExport(toExport);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
544
|
+
// Global scope binding
|
|
545
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
546
|
+
/**
|
|
547
|
+
* Link all global modules to all non-global modules as imports.
|
|
548
|
+
*
|
|
549
|
+
* Called after all modules have been scanned. This makes global modules'
|
|
550
|
+
* exports available everywhere without explicit imports.
|
|
551
|
+
*/
|
|
552
|
+
bindGlobalScope() {
|
|
553
|
+
for (const moduleRef of this.modules.values()) {
|
|
554
|
+
for (const globalModule of this.globalModules) {
|
|
555
|
+
if (moduleRef !== globalModule) {
|
|
556
|
+
moduleRef.addImport(globalModule);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
562
|
+
// Accessors
|
|
563
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
564
|
+
/**
|
|
565
|
+
* Get all registered modules.
|
|
566
|
+
*/
|
|
567
|
+
getModules() {
|
|
568
|
+
return this.modules;
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* Get a module by its token.
|
|
572
|
+
*/
|
|
573
|
+
getModuleByToken(token) {
|
|
574
|
+
return this.modules.get(token);
|
|
575
|
+
}
|
|
576
|
+
getDynamicMetadata(token, key) {
|
|
577
|
+
const metadata = this.dynamicModulesMetadata.get(token);
|
|
578
|
+
if (!metadata) return key ? [] : void 0;
|
|
579
|
+
return key ? metadata[key] ?? [] : metadata;
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* Clear all modules (for testing).
|
|
583
|
+
*/
|
|
584
|
+
clear() {
|
|
585
|
+
this.modules.clear();
|
|
586
|
+
this.globalModules.clear();
|
|
587
|
+
this.dynamicModulesMetadata.clear();
|
|
588
|
+
}
|
|
589
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
590
|
+
// Private helpers
|
|
591
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
592
|
+
/**
|
|
593
|
+
* Extract the module class, dynamic metadata, and token from a module definition.
|
|
594
|
+
*
|
|
595
|
+
* Handles both static modules (just a class) and dynamic modules
|
|
596
|
+
* (objects with a `module` property).
|
|
597
|
+
*/
|
|
598
|
+
extractModuleMetadata(metatype) {
|
|
599
|
+
if (this.isDynamicModule(metatype)) {
|
|
600
|
+
const { module: type, ...dynamicMetadata } = metatype;
|
|
601
|
+
return {
|
|
602
|
+
type,
|
|
603
|
+
dynamicMetadata,
|
|
604
|
+
token: type.name
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
return {
|
|
608
|
+
type: metatype,
|
|
609
|
+
dynamicMetadata: void 0,
|
|
610
|
+
token: metatype.name
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Check if a module definition is a dynamic module (has a `module` property).
|
|
615
|
+
*/
|
|
616
|
+
isDynamicModule(metatype) {
|
|
617
|
+
return metatype && !!metatype.module;
|
|
618
|
+
}
|
|
619
|
+
/**
|
|
620
|
+
* Check if a module should be global.
|
|
621
|
+
* A module is global if:
|
|
622
|
+
* - It has the @Global() decorator, OR
|
|
623
|
+
* - Its dynamic metadata has `global: true`
|
|
624
|
+
*/
|
|
625
|
+
isGlobalModule(type, dynamicMetadata) {
|
|
626
|
+
if (dynamicMetadata?.global) return true;
|
|
627
|
+
return !!Reflect.getMetadata(GLOBAL_MODULE_METADATA, type);
|
|
628
|
+
}
|
|
629
|
+
};
|
|
630
|
+
var Injector = class {
|
|
631
|
+
/**
|
|
632
|
+
* Tracks which wrappers are currently being resolved, to detect circular dependencies.
|
|
633
|
+
* Uses a Set of tokens being resolved in the current chain.
|
|
634
|
+
*/
|
|
635
|
+
resolutionStack = /* @__PURE__ */ new Set();
|
|
636
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
637
|
+
// Public API
|
|
638
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
639
|
+
/**
|
|
640
|
+
* Resolve all providers in a module.
|
|
641
|
+
*
|
|
642
|
+
* Iterates over all providers and resolves each one.
|
|
643
|
+
* Value providers are already resolved at registration time.
|
|
644
|
+
*
|
|
645
|
+
* @param moduleRef - The module whose providers to resolve
|
|
646
|
+
*/
|
|
647
|
+
async resolveProviders(moduleRef) {
|
|
648
|
+
const providers = moduleRef.providers;
|
|
649
|
+
for (const [_token, wrapper] of providers) {
|
|
650
|
+
if (!wrapper.isResolved) {
|
|
651
|
+
await this.resolveInstance(wrapper, moduleRef);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* Resolve a single provider instance.
|
|
657
|
+
*
|
|
658
|
+
* This is the core resolution method. It handles:
|
|
659
|
+
* - Already-resolved singletons (returns cached instance)
|
|
660
|
+
* - Circular dependency detection
|
|
661
|
+
* - Factory providers (calls the factory function)
|
|
662
|
+
* - Class providers (resolves constructor deps, then `new`)
|
|
663
|
+
* - Property injection (after construction)
|
|
664
|
+
* - Async factories (awaits the result)
|
|
665
|
+
*
|
|
666
|
+
* @param wrapper - The InstanceWrapper to resolve
|
|
667
|
+
* @param moduleRef - The module context for dependency lookup
|
|
668
|
+
*/
|
|
669
|
+
async resolveInstance(wrapper, moduleRef) {
|
|
670
|
+
if (wrapper.isResolved && !wrapper.isTransient) {
|
|
671
|
+
return wrapper.instance;
|
|
672
|
+
}
|
|
673
|
+
if (this.resolutionStack.has(wrapper.token)) {
|
|
674
|
+
throw new Error(
|
|
675
|
+
`Circular dependency detected: ${this.formatResolutionStack(wrapper.token)}`
|
|
676
|
+
);
|
|
677
|
+
}
|
|
678
|
+
this.resolutionStack.add(wrapper.token);
|
|
679
|
+
try {
|
|
680
|
+
let instance;
|
|
681
|
+
if (wrapper.isFactory) {
|
|
682
|
+
instance = await this.resolveFactory(wrapper, moduleRef);
|
|
683
|
+
} else if (wrapper.metatype) {
|
|
684
|
+
instance = await this.resolveClass(wrapper, moduleRef);
|
|
685
|
+
} else {
|
|
686
|
+
instance = wrapper.instance;
|
|
687
|
+
}
|
|
688
|
+
wrapper.instance = instance;
|
|
689
|
+
wrapper.isResolved = true;
|
|
690
|
+
return instance;
|
|
691
|
+
} finally {
|
|
692
|
+
this.resolutionStack.delete(wrapper.token);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Look up a provider by token, searching the module and its imports.
|
|
697
|
+
*
|
|
698
|
+
* Resolution order:
|
|
699
|
+
* 1. The module's own providers
|
|
700
|
+
* 2. Imported modules' exported providers (breadth-first)
|
|
701
|
+
*
|
|
702
|
+
* @param token - The injection token to look up
|
|
703
|
+
* @param moduleRef - The module context
|
|
704
|
+
* @returns The InstanceWrapper and the module it was found in
|
|
705
|
+
*/
|
|
706
|
+
lookupProvider(token, moduleRef) {
|
|
707
|
+
if (moduleRef.providers.has(token)) {
|
|
708
|
+
return { wrapper: moduleRef.providers.get(token), host: moduleRef };
|
|
709
|
+
}
|
|
710
|
+
return this.lookupInImports(token, moduleRef, /* @__PURE__ */ new Set());
|
|
711
|
+
}
|
|
712
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
713
|
+
// Private: Class resolution
|
|
714
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
715
|
+
/**
|
|
716
|
+
* Resolve a class provider by:
|
|
717
|
+
* 1. Reading constructor parameter types from metadata
|
|
718
|
+
* 2. Resolving each dependency
|
|
719
|
+
* 3. Calling `new Class(...deps)`
|
|
720
|
+
* 4. Applying property injection
|
|
721
|
+
*/
|
|
722
|
+
async resolveClass(wrapper, moduleRef) {
|
|
723
|
+
const metatype = wrapper.metatype;
|
|
724
|
+
const deps = this.getConstructorDependencies(metatype);
|
|
725
|
+
const optionalIndices = this.getOptionalDependencies(metatype);
|
|
726
|
+
const resolvedDeps = await Promise.all(
|
|
727
|
+
deps.map(async (dep, index) => {
|
|
728
|
+
if (dep === void 0 || dep === null || dep === Object) {
|
|
729
|
+
if (optionalIndices.includes(index)) return void 0;
|
|
730
|
+
throw new Error(
|
|
731
|
+
`Cannot resolve dependency at index [${index}] of ${metatype.name}. The dependency is undefined \u2014 this usually means a circular import or missing @Inject() decorator.`
|
|
732
|
+
);
|
|
733
|
+
}
|
|
734
|
+
try {
|
|
735
|
+
return await this.resolveDependency(dep, moduleRef);
|
|
736
|
+
} catch (err) {
|
|
737
|
+
if (optionalIndices.includes(index)) return void 0;
|
|
738
|
+
throw new Error(
|
|
739
|
+
`Cannot resolve dependency '${this.getTokenName(dep)}' at index [${index}] of ${metatype.name}. Make sure it is provided in the module or imported. Original: ${err.message}`
|
|
740
|
+
);
|
|
741
|
+
}
|
|
742
|
+
})
|
|
743
|
+
);
|
|
744
|
+
const instance = new metatype(...resolvedDeps);
|
|
745
|
+
await this.resolveProperties(instance, metatype, moduleRef);
|
|
746
|
+
return instance;
|
|
747
|
+
}
|
|
748
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
749
|
+
// Private: Factory resolution
|
|
750
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
751
|
+
/**
|
|
752
|
+
* Resolve a factory provider by:
|
|
753
|
+
* 1. Resolving the factory's `inject` dependencies
|
|
754
|
+
* 2. Calling the factory function with the resolved deps
|
|
755
|
+
* 3. Awaiting the result if it's a Promise
|
|
756
|
+
*/
|
|
757
|
+
async resolveFactory(wrapper, moduleRef) {
|
|
758
|
+
const factory = wrapper.metatype;
|
|
759
|
+
const injectTokens = wrapper.inject ?? [];
|
|
760
|
+
const resolvedDeps = await Promise.all(
|
|
761
|
+
injectTokens.map(async (token) => {
|
|
762
|
+
try {
|
|
763
|
+
return await this.resolveDependency(token, moduleRef);
|
|
764
|
+
} catch (err) {
|
|
765
|
+
throw new Error(
|
|
766
|
+
`Cannot resolve factory dependency '${this.getTokenName(token)}' for provider '${wrapper.name}'. ${err.message}`
|
|
767
|
+
);
|
|
768
|
+
}
|
|
769
|
+
})
|
|
770
|
+
);
|
|
771
|
+
const result = factory(...resolvedDeps);
|
|
772
|
+
return result instanceof Promise ? await result : result;
|
|
773
|
+
}
|
|
774
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
775
|
+
// Private: Dependency resolution
|
|
776
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
777
|
+
/**
|
|
778
|
+
* Resolve a single dependency token to its instance.
|
|
779
|
+
*
|
|
780
|
+
* Looks up the provider, resolves it if needed, and returns the instance.
|
|
781
|
+
*/
|
|
782
|
+
async resolveDependency(token, moduleRef) {
|
|
783
|
+
const result = this.lookupProvider(token, moduleRef);
|
|
784
|
+
if (!result) {
|
|
785
|
+
throw new Error(
|
|
786
|
+
`Provider '${this.getTokenName(token)}' not found. Is it provided in the current module or an imported module?`
|
|
787
|
+
);
|
|
788
|
+
}
|
|
789
|
+
const { wrapper, host } = result;
|
|
790
|
+
if (!wrapper.isResolved || wrapper.isTransient) {
|
|
791
|
+
return this.resolveInstance(wrapper, host);
|
|
792
|
+
}
|
|
793
|
+
if (wrapper.async && wrapper.instance instanceof Promise) {
|
|
794
|
+
wrapper.instance = await wrapper.instance;
|
|
795
|
+
}
|
|
796
|
+
return wrapper.instance;
|
|
797
|
+
}
|
|
798
|
+
/**
|
|
799
|
+
* Look up a provider in imported modules' exports.
|
|
800
|
+
*
|
|
801
|
+
* Searches breadth-first through the import tree, only considering
|
|
802
|
+
* providers that are in the imported module's exports set.
|
|
803
|
+
*/
|
|
804
|
+
lookupInImports(token, moduleRef, visited) {
|
|
805
|
+
for (const importedModule of moduleRef.imports) {
|
|
806
|
+
if (visited.has(importedModule.id)) continue;
|
|
807
|
+
visited.add(importedModule.id);
|
|
808
|
+
if (importedModule.exports.has(token) && importedModule.providers.has(token)) {
|
|
809
|
+
return {
|
|
810
|
+
wrapper: importedModule.providers.get(token),
|
|
811
|
+
host: importedModule
|
|
812
|
+
};
|
|
813
|
+
}
|
|
814
|
+
const result = this.lookupInImports(token, importedModule, visited);
|
|
815
|
+
if (result) return result;
|
|
816
|
+
}
|
|
817
|
+
return void 0;
|
|
818
|
+
}
|
|
819
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
820
|
+
// Private: Metadata reading
|
|
821
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
822
|
+
/**
|
|
823
|
+
* Get the constructor dependencies for a class.
|
|
824
|
+
*
|
|
825
|
+
* Merges TypeScript's auto-emitted `design:paramtypes` with
|
|
826
|
+
* explicitly declared `self:paramtypes` (from @Inject decorators).
|
|
827
|
+
* Explicit declarations override auto-detected types.
|
|
828
|
+
*
|
|
829
|
+
* @param type - The class to read metadata from
|
|
830
|
+
* @returns Array of injection tokens, one per constructor parameter
|
|
831
|
+
*/
|
|
832
|
+
getConstructorDependencies(type) {
|
|
833
|
+
const paramTypes = [
|
|
834
|
+
...Reflect.getMetadata(PARAMTYPES_METADATA, type) || []
|
|
835
|
+
];
|
|
836
|
+
const selfDeclared = Reflect.getMetadata(SELF_DECLARED_DEPS_METADATA, type) || [];
|
|
837
|
+
for (const { index, param } of selfDeclared) {
|
|
838
|
+
paramTypes[index] = param;
|
|
839
|
+
}
|
|
840
|
+
return paramTypes;
|
|
841
|
+
}
|
|
842
|
+
/**
|
|
843
|
+
* Get the indices of optional constructor parameters.
|
|
844
|
+
*/
|
|
845
|
+
getOptionalDependencies(type) {
|
|
846
|
+
return Reflect.getMetadata(OPTIONAL_DEPS_METADATA, type) || [];
|
|
847
|
+
}
|
|
848
|
+
/**
|
|
849
|
+
* Resolve property-injected dependencies and assign them to the instance.
|
|
850
|
+
*/
|
|
851
|
+
async resolveProperties(instance, type, moduleRef) {
|
|
852
|
+
const properties = Reflect.getMetadata(PROPERTY_DEPS_METADATA, type) || [];
|
|
853
|
+
const optionalKeys = Reflect.getMetadata(OPTIONAL_PROPERTY_DEPS_METADATA, type) || [];
|
|
854
|
+
for (const prop of properties) {
|
|
855
|
+
const isOptional = optionalKeys.includes(prop.key);
|
|
856
|
+
try {
|
|
857
|
+
const resolved = await this.resolveDependency(prop.type, moduleRef);
|
|
858
|
+
instance[prop.key] = resolved;
|
|
859
|
+
} catch (err) {
|
|
860
|
+
if (!isOptional) throw err;
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
865
|
+
// Private: Helpers
|
|
866
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
867
|
+
/**
|
|
868
|
+
* Format the resolution stack for error messages.
|
|
869
|
+
*/
|
|
870
|
+
formatResolutionStack(token) {
|
|
871
|
+
const stack = [...this.resolutionStack, token];
|
|
872
|
+
return stack.map((t) => this.getTokenName(t)).join(" \u2192 ");
|
|
873
|
+
}
|
|
874
|
+
/**
|
|
875
|
+
* Get a human-readable name from a token.
|
|
876
|
+
*/
|
|
877
|
+
getTokenName(token) {
|
|
878
|
+
if (typeof token === "function") return token.name;
|
|
879
|
+
if (typeof token === "symbol") return token.toString();
|
|
880
|
+
return String(token);
|
|
881
|
+
}
|
|
882
|
+
};
|
|
883
|
+
|
|
884
|
+
// src/interfaces/lifecycle.interface.ts
|
|
885
|
+
function hasOnModuleInit(instance) {
|
|
886
|
+
return instance && typeof instance.onModuleInit === "function";
|
|
887
|
+
}
|
|
888
|
+
function hasOnModuleDestroy(instance) {
|
|
889
|
+
return instance && typeof instance.onModuleDestroy === "function";
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
// src/injector/instance-loader.ts
|
|
893
|
+
var InstanceLoader = class {
|
|
894
|
+
constructor(container) {
|
|
895
|
+
this.container = container;
|
|
896
|
+
this.injector = new Injector();
|
|
897
|
+
}
|
|
898
|
+
container;
|
|
899
|
+
injector;
|
|
900
|
+
/**
|
|
901
|
+
* Instantiate all providers in all modules.
|
|
902
|
+
*
|
|
903
|
+
* Iterates modules and resolves each module's providers.
|
|
904
|
+
* After all providers are resolved, calls `onModuleInit()` lifecycle hooks.
|
|
905
|
+
*/
|
|
906
|
+
async createInstances() {
|
|
907
|
+
const modules = this.container.getModules();
|
|
908
|
+
for (const [, moduleRef] of modules) {
|
|
909
|
+
await this.injector.resolveProviders(moduleRef);
|
|
910
|
+
}
|
|
911
|
+
for (const [, moduleRef] of modules) {
|
|
912
|
+
await this.callModuleInitHooks(moduleRef);
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
/**
|
|
916
|
+
* Call `onModuleDestroy()` on all providers that implement it.
|
|
917
|
+
*
|
|
918
|
+
* Called during application shutdown. Iterates modules in reverse
|
|
919
|
+
* order (leaf modules first, root module last).
|
|
920
|
+
*/
|
|
921
|
+
async destroy() {
|
|
922
|
+
const modules = [...this.container.getModules().values()].reverse();
|
|
923
|
+
for (const moduleRef of modules) {
|
|
924
|
+
await this.callModuleDestroyHooks(moduleRef);
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
/**
|
|
928
|
+
* Get the injector instance (for direct resolution outside the module system).
|
|
929
|
+
*/
|
|
930
|
+
getInjector() {
|
|
931
|
+
return this.injector;
|
|
932
|
+
}
|
|
933
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
934
|
+
// Private: Lifecycle hooks
|
|
935
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
936
|
+
/**
|
|
937
|
+
* Call `onModuleInit()` on all resolved providers in a module.
|
|
938
|
+
*/
|
|
939
|
+
async callModuleInitHooks(moduleRef) {
|
|
940
|
+
for (const [, wrapper] of moduleRef.providers) {
|
|
941
|
+
if (wrapper.isResolved && wrapper.instance && hasOnModuleInit(wrapper.instance)) {
|
|
942
|
+
await wrapper.instance.onModuleInit();
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
/**
|
|
947
|
+
* Call `onModuleDestroy()` on all resolved providers in a module.
|
|
948
|
+
*/
|
|
949
|
+
async callModuleDestroyHooks(moduleRef) {
|
|
950
|
+
for (const [, wrapper] of moduleRef.providers) {
|
|
951
|
+
if (wrapper.isResolved && wrapper.instance && hasOnModuleDestroy(wrapper.instance)) {
|
|
952
|
+
await wrapper.instance.onModuleDestroy();
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
};
|
|
957
|
+
var DependenciesScanner = class {
|
|
958
|
+
constructor(container) {
|
|
959
|
+
this.container = container;
|
|
960
|
+
}
|
|
961
|
+
container;
|
|
962
|
+
/**
|
|
963
|
+
* Scan the entire module tree starting from the root module.
|
|
964
|
+
*
|
|
965
|
+
* This is the main entry point. After this method completes,
|
|
966
|
+
* the container has all modules, providers, imports, and exports
|
|
967
|
+
* registered — but no instances created.
|
|
968
|
+
*
|
|
969
|
+
* @param rootModule - The root module class (your AppModule)
|
|
970
|
+
*/
|
|
971
|
+
async scan(rootModule) {
|
|
972
|
+
await this.scanForModules(rootModule, []);
|
|
973
|
+
await this.scanModulesForDependencies();
|
|
974
|
+
this.container.bindGlobalScope();
|
|
975
|
+
}
|
|
976
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
977
|
+
// Phase 1: Module discovery
|
|
978
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
979
|
+
/**
|
|
980
|
+
* Recursively discover and register all modules in the graph.
|
|
981
|
+
*
|
|
982
|
+
* Uses DFS traversal. Tracks visited modules to avoid infinite loops
|
|
983
|
+
* from circular imports.
|
|
984
|
+
*
|
|
985
|
+
* @param moduleDefinition - The module to scan (class or dynamic module)
|
|
986
|
+
* @param ctxRegistry - Already-visited modules (for cycle detection)
|
|
987
|
+
*/
|
|
988
|
+
async scanForModules(moduleDefinition, ctxRegistry) {
|
|
989
|
+
const resolved = this.resolveForwardRef(moduleDefinition);
|
|
990
|
+
if (!resolved) return;
|
|
991
|
+
if (ctxRegistry.includes(resolved)) return;
|
|
992
|
+
ctxRegistry.push(resolved);
|
|
993
|
+
await this.container.addModule(resolved);
|
|
994
|
+
const imports = this.getModuleImports(resolved);
|
|
995
|
+
for (const importedModule of imports) {
|
|
996
|
+
if (importedModule === void 0 || importedModule === null) {
|
|
997
|
+
const moduleName = this.getModuleName(resolved);
|
|
998
|
+
throw new Error(
|
|
999
|
+
`An undefined module was imported by ${moduleName}. This is usually caused by a circular dependency. Use forwardRef() to resolve it.`
|
|
1000
|
+
);
|
|
1001
|
+
}
|
|
1002
|
+
await this.scanForModules(importedModule, ctxRegistry);
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1006
|
+
// Phase 2: Dependency registration
|
|
1007
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1008
|
+
/**
|
|
1009
|
+
* For each registered module, read its metadata and register
|
|
1010
|
+
* providers, imports, and exports.
|
|
1011
|
+
*/
|
|
1012
|
+
async scanModulesForDependencies() {
|
|
1013
|
+
const modules = this.container.getModules();
|
|
1014
|
+
for (const [token, moduleRef] of modules) {
|
|
1015
|
+
await this.reflectImports(moduleRef.metatype, token);
|
|
1016
|
+
this.reflectProviders(moduleRef.metatype, token);
|
|
1017
|
+
this.reflectExports(moduleRef.metatype, token);
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
/**
|
|
1021
|
+
* Read and register a module's imports.
|
|
1022
|
+
*
|
|
1023
|
+
* Merges static @Module({ imports }) with dynamic module imports.
|
|
1024
|
+
*/
|
|
1025
|
+
async reflectImports(metatype, token) {
|
|
1026
|
+
const staticImports = Reflect.getMetadata(MODULE_METADATA.IMPORTS, metatype) || [];
|
|
1027
|
+
const dynamicImports = this.container.getDynamicMetadata(token, "imports") || [];
|
|
1028
|
+
const allImports = [...staticImports, ...dynamicImports];
|
|
1029
|
+
for (const related of allImports) {
|
|
1030
|
+
const resolved = this.resolveForwardRef(related);
|
|
1031
|
+
if (resolved) {
|
|
1032
|
+
this.container.addImport(resolved, token);
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
/**
|
|
1037
|
+
* Read and register a module's providers.
|
|
1038
|
+
*
|
|
1039
|
+
* Merges static @Module({ providers }) with dynamic module providers.
|
|
1040
|
+
*/
|
|
1041
|
+
reflectProviders(metatype, token) {
|
|
1042
|
+
const staticProviders = Reflect.getMetadata(MODULE_METADATA.PROVIDERS, metatype) || [];
|
|
1043
|
+
const dynamicProviders = this.container.getDynamicMetadata(token, "providers") || [];
|
|
1044
|
+
const allProviders = [...staticProviders, ...dynamicProviders];
|
|
1045
|
+
for (const provider of allProviders) {
|
|
1046
|
+
this.container.addProvider(provider, token);
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
/**
|
|
1050
|
+
* Read and register a module's exports.
|
|
1051
|
+
*
|
|
1052
|
+
* Merges static @Module({ exports }) with dynamic module exports.
|
|
1053
|
+
*/
|
|
1054
|
+
reflectExports(metatype, token) {
|
|
1055
|
+
const staticExports = Reflect.getMetadata(MODULE_METADATA.EXPORTS, metatype) || [];
|
|
1056
|
+
const dynamicExports = this.container.getDynamicMetadata(token, "exports") || [];
|
|
1057
|
+
const allExports = [...staticExports, ...dynamicExports];
|
|
1058
|
+
for (const exported of allExports) {
|
|
1059
|
+
const resolved = this.resolveForwardRef(exported);
|
|
1060
|
+
this.container.addExport(resolved ?? exported, token);
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1064
|
+
// Helpers
|
|
1065
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1066
|
+
/**
|
|
1067
|
+
* Get a module's imports from both static and dynamic sources.
|
|
1068
|
+
*/
|
|
1069
|
+
getModuleImports(moduleDefinition) {
|
|
1070
|
+
if (this.isDynamicModule(moduleDefinition)) {
|
|
1071
|
+
const staticImports = Reflect.getMetadata(MODULE_METADATA.IMPORTS, moduleDefinition.module) || [];
|
|
1072
|
+
const dynamicImports = moduleDefinition.imports || [];
|
|
1073
|
+
return [...staticImports, ...dynamicImports];
|
|
1074
|
+
}
|
|
1075
|
+
return Reflect.getMetadata(MODULE_METADATA.IMPORTS, moduleDefinition) || [];
|
|
1076
|
+
}
|
|
1077
|
+
/**
|
|
1078
|
+
* Resolve a forward reference to its actual value.
|
|
1079
|
+
*/
|
|
1080
|
+
resolveForwardRef(ref) {
|
|
1081
|
+
if (ref && typeof ref === "object" && "forwardRef" in ref) {
|
|
1082
|
+
return ref.forwardRef();
|
|
1083
|
+
}
|
|
1084
|
+
return ref;
|
|
1085
|
+
}
|
|
1086
|
+
/**
|
|
1087
|
+
* Check if a module definition is a dynamic module.
|
|
1088
|
+
*/
|
|
1089
|
+
isDynamicModule(module) {
|
|
1090
|
+
return module && !!module.module;
|
|
1091
|
+
}
|
|
1092
|
+
/**
|
|
1093
|
+
* Get a human-readable name for a module.
|
|
1094
|
+
*/
|
|
1095
|
+
getModuleName(module) {
|
|
1096
|
+
if (this.isDynamicModule(module)) return module.module.name;
|
|
1097
|
+
if (typeof module === "function") return module.name;
|
|
1098
|
+
return String(module);
|
|
1099
|
+
}
|
|
1100
|
+
};
|
|
1101
|
+
|
|
1102
|
+
exports.DependenciesScanner = DependenciesScanner;
|
|
1103
|
+
exports.GLOBAL_MODULE_METADATA = GLOBAL_MODULE_METADATA;
|
|
1104
|
+
exports.Global = Global;
|
|
1105
|
+
exports.INJECTABLE_WATERMARK = INJECTABLE_WATERMARK;
|
|
1106
|
+
exports.Inject = Inject;
|
|
1107
|
+
exports.Injectable = Injectable;
|
|
1108
|
+
exports.Injector = Injector;
|
|
1109
|
+
exports.InstanceLoader = InstanceLoader;
|
|
1110
|
+
exports.InstanceWrapper = InstanceWrapper;
|
|
1111
|
+
exports.MODULE_METADATA = MODULE_METADATA;
|
|
1112
|
+
exports.Module = Module;
|
|
1113
|
+
exports.ModuleRef = Module2;
|
|
1114
|
+
exports.NestContainer = NestContainer;
|
|
1115
|
+
exports.OPTIONAL_DEPS_METADATA = OPTIONAL_DEPS_METADATA;
|
|
1116
|
+
exports.OPTIONAL_PROPERTY_DEPS_METADATA = OPTIONAL_PROPERTY_DEPS_METADATA;
|
|
1117
|
+
exports.Optional = Optional;
|
|
1118
|
+
exports.PARAMTYPES_METADATA = PARAMTYPES_METADATA;
|
|
1119
|
+
exports.PROPERTY_DEPS_METADATA = PROPERTY_DEPS_METADATA;
|
|
1120
|
+
exports.SCOPE_OPTIONS_METADATA = SCOPE_OPTIONS_METADATA;
|
|
1121
|
+
exports.SELF_DECLARED_DEPS_METADATA = SELF_DECLARED_DEPS_METADATA;
|
|
1122
|
+
exports.Scope = Scope;
|
|
1123
|
+
exports.forwardRef = forwardRef;
|
|
1124
|
+
//# sourceMappingURL=index.cjs.map
|
|
1125
|
+
//# sourceMappingURL=index.cjs.map
|