@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/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