@tsed/di 8.37.1 → 8.38.0-beta.2

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.
@@ -13,15 +13,15 @@ import { Provider } from "./Provider.js";
13
13
  * const container = new Container();
14
14
  * container.add(MyService, {scope: ProviderScope.SINGLETON});
15
15
  *
16
- * const provider = container.getProvider(MyService);
17
- * const controllers = container.getProviders(ProviderType.CONTROLLER);
16
+ * const provider = container.get(MyService);
17
+ * const controllers = container.getMany(ProviderType.CONTROLLER);
18
18
  * ```
19
19
  *
20
20
  * @public
21
21
  */
22
22
  export class Container extends Map {
23
23
  /**
24
- *
24
+ * Add a provider to the
25
25
  * @param token
26
26
  * @param settings
27
27
  */
@@ -30,35 +30,7 @@ export class Container extends Map {
30
30
  Object.assign(provider, settings);
31
31
  return super.set(token, provider);
32
32
  }
33
- /**
34
- * Add a provider to the
35
- * @param token
36
- * @param settings
37
- */
38
- addProvider(token, settings = {}) {
39
- return this.add(token, settings);
40
- }
41
- /**
42
- *
43
- * @param token
44
- */
45
- hasProvider(token) {
46
- return super.has(token);
47
- }
48
- /**
49
- * Add a provider to the
50
- * @param token
51
- * @param provider
52
- */
53
- setProvider(token, provider) {
54
- return super.set(token, provider);
55
- }
56
- /**
57
- * The getProvider() method returns a specified element from a Map object.
58
- * @returns {T} Returns the element associated with the specified key or undefined if the key can't be found in the Map object.
59
- * @param token
60
- */
61
- getProvider(token) {
33
+ get(token) {
62
34
  return super.get(token);
63
35
  }
64
36
  /**
@@ -67,7 +39,7 @@ export class Container extends Map {
67
39
  * @param type Filter the list by the given ProviderType.
68
40
  * @returns {[TokenProvider , Provider<any>][]}
69
41
  */
70
- getProviders(type) {
42
+ getMany(type) {
71
43
  if (!type) {
72
44
  return [...this.values()];
73
45
  }
@@ -80,10 +52,10 @@ export class Container extends Map {
80
52
  }
81
53
  return providers;
82
54
  }
83
- addProviders(container) {
55
+ merge(container) {
84
56
  container.forEach((provider) => {
85
- if (!this.hasProvider(provider.token)) {
86
- this.setProvider(provider.token, provider.clone());
57
+ if (!super.has(provider.token)) {
58
+ super.set(provider.token, provider.clone());
87
59
  }
88
60
  });
89
61
  }
@@ -37,6 +37,9 @@ export class ContextLogger {
37
37
  this.level = (LEVELS[(level || this.#logger.level || "").toUpperCase()] || LEVELS.ALL);
38
38
  this.maxStackSize = maxStackSize;
39
39
  }
40
+ get level() {
41
+ return this.#level;
42
+ }
40
43
  set level(level) {
41
44
  if (typeof level === "string") {
42
45
  this.#level = LEVELS[level.toUpperCase()];
@@ -45,9 +48,6 @@ export class ContextLogger {
45
48
  this.#level = level;
46
49
  }
47
50
  }
48
- get level() {
49
- return this.#level;
50
- }
51
51
  get hooks() {
52
52
  return (this.#hooks = this.#hooks || new Hooks());
53
53
  }
@@ -27,11 +27,17 @@ import { setValue } from "@tsed/core/utils/setValue.js";
27
27
  * @public
28
28
  */
29
29
  export class DIConfiguration {
30
+ /**
31
+ * Creates a configuration service with the built-in defaults merged with the supplied initial properties.
32
+ *
33
+ * @param initialProps Initial configuration values that override built-in defaults.
34
+ */
30
35
  constructor(initialProps = {}) {
31
36
  this.default = new Map();
32
37
  this.map = new Map();
33
38
  Object.entries({
34
39
  imports: [],
40
+ lazyProviders: false,
35
41
  routes: [],
36
42
  mount: {},
37
43
  logger: {},
@@ -40,59 +46,138 @@ export class DIConfiguration {
40
46
  this.default.set(key, value);
41
47
  });
42
48
  }
49
+ /**
50
+ * Gets the application version.
51
+ */
43
52
  get version() {
44
53
  return this.get("version");
45
54
  }
55
+ /**
56
+ * Sets the application version.
57
+ *
58
+ * @param v Version to store in the configuration.
59
+ */
46
60
  set version(v) {
47
61
  this.map.set("version", v);
48
62
  }
63
+ /**
64
+ * Gets the application's root directory.
65
+ */
49
66
  get rootDir() {
50
67
  return this.get("rootDir");
51
68
  }
69
+ /**
70
+ * Sets the application's root directory.
71
+ *
72
+ * @param value Root directory to store in the configuration.
73
+ */
52
74
  set rootDir(value) {
53
75
  this.map.set("rootDir", value);
54
76
  }
77
+ /**
78
+ * Gets the active runtime environment.
79
+ */
55
80
  get env() {
56
81
  return this.map.get("env");
57
82
  }
83
+ /**
84
+ * Sets the active runtime environment.
85
+ *
86
+ * @param value Environment to store in the configuration.
87
+ */
58
88
  set env(value) {
59
89
  this.map.set("env", value);
60
90
  }
91
+ /**
92
+ * Gets the providers and modules imported by the application.
93
+ */
61
94
  get imports() {
62
95
  return this.get("imports");
63
96
  }
97
+ /**
98
+ * Sets the providers and modules imported by the application.
99
+ *
100
+ * @param imports Provider tokens or import options to configure.
101
+ */
64
102
  set imports(imports) {
65
103
  this.map.set("imports", imports);
66
104
  }
105
+ /**
106
+ * Indicates whether synchronous singleton providers without hooks are deferred until first use.
107
+ */
108
+ get lazyProviders() {
109
+ return this.get("lazyProviders");
110
+ }
111
+ /**
112
+ * Enables or disables deferred initialization of synchronous singleton providers without hooks.
113
+ *
114
+ * @param lazyProviders Whether to defer eligible providers.
115
+ */
116
+ set lazyProviders(lazyProviders) {
117
+ this.map.set("lazyProviders", lazyProviders);
118
+ }
119
+ /**
120
+ * Gets the route providers registered by the application.
121
+ */
67
122
  get routes() {
68
123
  return this.get("routes");
69
124
  }
125
+ /**
126
+ * Sets the route providers registered by the application.
127
+ *
128
+ * @param routes Route providers to configure.
129
+ */
70
130
  set routes(routes) {
71
131
  this.map.set("routes", routes);
72
132
  }
133
+ /**
134
+ * Gets the logger options.
135
+ */
73
136
  get logger() {
74
137
  return this.get("logger");
75
138
  }
139
+ /**
140
+ * Merges logger options into the current logger configuration.
141
+ *
142
+ * @param value Logger options to merge.
143
+ */
76
144
  set logger(value) {
77
145
  const logger = { ...this.logger, ...value };
78
146
  this.map.set("logger", logger);
79
147
  }
148
+ /**
149
+ * Indicates whether the logger is configured at the `debug` level.
150
+ */
80
151
  get debug() {
81
152
  return this.logger.level === "debug";
82
153
  }
154
+ /**
155
+ * Enables or disables debug-level logging.
156
+ *
157
+ * @param debug `true` to set the logger level to `debug`; `false` to set it to `info`.
158
+ */
83
159
  set debug(debug) {
84
160
  this.logger = { ...this.logger, level: debug ? "debug" : "info" };
85
161
  }
162
+ /**
163
+ * Gets route providers grouped by mount path.
164
+ */
86
165
  get mount() {
87
166
  return this.get("mount");
88
167
  }
168
+ /**
169
+ * Sets route providers grouped by mount path.
170
+ *
171
+ * @param value Route providers indexed by mount path.
172
+ */
89
173
  set mount(value) {
90
174
  this.setRaw("mount", value);
91
175
  }
92
176
  /**
177
+ * Invokes a callback once for each configured key, including default and overridden values.
93
178
  *
94
- * @param callbackfn
95
- * @param thisArg
179
+ * @param callbackfn Callback invoked with the resolved value, key, and override map.
180
+ * @param thisArg Value to use as `this` when invoking the callback.
96
181
  */
97
182
  forEach(callbackfn, thisArg) {
98
183
  return new Set([...Array.from(this.default.keys()), ...Array.from(this.map.keys())]).forEach((key) => {
@@ -117,19 +202,34 @@ export class DIConfiguration {
117
202
  }
118
203
  return this;
119
204
  }
205
+ /**
206
+ * Sets a configuration value directly in the override map, supporting nested property paths.
207
+ *
208
+ * @param propertyKey Configuration key or nested property path.
209
+ * @param value Value to store.
210
+ * @returns This configuration instance for chaining.
211
+ */
120
212
  setRaw(propertyKey, value) {
121
213
  setValue(this.map, propertyKey, value);
122
214
  return this;
123
215
  }
124
216
  /**
217
+ * Gets a resolved configuration value, preferring explicit overrides over default values.
125
218
  *
126
- * @param propertyKey
127
- * @param defaultValue
128
- * @returns {undefined|any}
219
+ * @param propertyKey Configuration key or nested property path.
220
+ * @param defaultValue Value to return when neither the overrides nor defaults define the key.
221
+ * @returns The resolved configuration value.
129
222
  */
130
223
  get(propertyKey, defaultValue) {
131
224
  return this.getRaw(propertyKey, defaultValue);
132
225
  }
226
+ /**
227
+ * Defines a custom configuration member when a member with the same key does not already exist.
228
+ *
229
+ * @param key Name of the member to define.
230
+ * @param value Function or property descriptor to assign to the configuration prototype.
231
+ * @returns This configuration instance when the member already exists.
232
+ */
133
233
  decorate(key, value) {
134
234
  if (key in this) {
135
235
  return this;
@@ -140,6 +240,13 @@ export class DIConfiguration {
140
240
  }
141
241
  : value);
142
242
  }
243
+ /**
244
+ * Resolves a configuration value from overrides, defaults, or the supplied fallback in that order.
245
+ *
246
+ * @param propertyKey Configuration key or nested property path.
247
+ * @param defaultValue Value to return when no configured value exists.
248
+ * @returns The resolved configuration value.
249
+ */
143
250
  getRaw(propertyKey, defaultValue) {
144
251
  const value = getValue(this.map, propertyKey);
145
252
  if (value !== undefined) {
@@ -43,23 +43,88 @@ const EXCLUDED_CONFIGURATION_KEYS = ["mount", "imports"];
43
43
  *
44
44
  * @public
45
45
  */
46
- export class InjectorService extends Container {
46
+ export class InjectorService {
47
+ /**
48
+ * Providers containers
49
+ * @private
50
+ */
51
+ #providers;
52
+ /**
53
+ * Cached instance
54
+ * @private
55
+ */
47
56
  #cache;
48
57
  #loaded;
58
+ #hooksReady;
59
+ #registeredSingletonHooks;
60
+ #resolvingProviders;
49
61
  constructor() {
50
- super();
51
62
  this.logger = console;
52
63
  /*readonly */
53
64
  this.settings = new DIConfiguration();
54
- this.resolvedConfiguration = false;
65
+ /**
66
+ * Providers containers
67
+ * @private
68
+ */
69
+ this.#providers = new Container();
70
+ /**
71
+ * Cached instance
72
+ * @private
73
+ */
55
74
  this.#cache = new LocalsContainer();
75
+ this.resolvedConfiguration = false;
56
76
  this.#loaded = false;
77
+ this.#hooksReady = false;
78
+ this.#registeredSingletonHooks = new Set();
79
+ this.#resolvingProviders = new Set();
57
80
  this.#cache.set(InjectorService, this);
58
81
  this.#cache.set(DIConfiguration, this.settings);
59
82
  }
83
+ get providers() {
84
+ return this.#providers;
85
+ }
60
86
  isLoaded() {
61
87
  return this.#loaded;
62
88
  }
89
+ add(token, settings = {}) {
90
+ this.#providers.add(token, settings);
91
+ this.setProvider(token, this.#providers.get(token));
92
+ return this;
93
+ }
94
+ /**
95
+ * @alias InjectorService.add
96
+ */
97
+ addProvider(token, settings = {}) {
98
+ return this.add(token, settings);
99
+ }
100
+ setProvider(token, provider) {
101
+ this.#providers.set(token, provider);
102
+ if (this.#registeredSingletonHooks.delete(token)) {
103
+ $off(token);
104
+ }
105
+ if (provider.scope === ProviderScope.SINGLETON && (!this.#loaded || this.#hooksReady)) {
106
+ this.registerHooks(provider, {});
107
+ }
108
+ return this;
109
+ }
110
+ /**
111
+ * Get a provider definition from the provider registry.
112
+ *
113
+ * Unlike `get()`, this method does not resolve the provider instance.
114
+ * @deprecated Since v8. Use injector().providers.get() instead
115
+ */
116
+ getProvider(token) {
117
+ return this.#providers.get(token);
118
+ }
119
+ /**
120
+ * Get provider definitions matching a type.
121
+ *
122
+ * Unlike `getMany()`, this method does not resolve provider instances.
123
+ * @deprecated Since v8. Use injector().providers.getMany() instead.
124
+ */
125
+ getProviders(type) {
126
+ return this.#providers.getMany(type);
127
+ }
63
128
  /**
64
129
  * Return a list of all instances built by the injector.
65
130
  *
@@ -107,7 +172,7 @@ export class InjectorService extends Container {
107
172
  * @returns Array of resolved instances matching the type
108
173
  */
109
174
  getMany(type, options) {
110
- return this.getProviders(type).map((provider) => {
175
+ return this.#providers.getMany(type).map((provider) => {
111
176
  return this.resolve(provider.token, options);
112
177
  });
113
178
  }
@@ -118,7 +183,7 @@ export class InjectorService extends Container {
118
183
  * @returns `true` if the instance is cached, `false` otherwise
119
184
  */
120
185
  has(token) {
121
- return this.#cache.get(token) !== undefined;
186
+ return this.#cache.has(token);
122
187
  }
123
188
  /**
124
189
  * Create an alias for a provider token.
@@ -157,9 +222,8 @@ export class InjectorService extends Container {
157
222
  * @returns The resolved provider instance
158
223
  */
159
224
  resolve(token, options = {}) {
160
- let instance = options.locals ? options.locals.get(token) : undefined;
161
- if (instance !== undefined) {
162
- return instance;
225
+ if (options.locals?.has(token)) {
226
+ return options.locals.get(token);
163
227
  }
164
228
  if (token === DI_USE_PARAM_OPTIONS) {
165
229
  return options.useOpts;
@@ -168,25 +232,21 @@ export class InjectorService extends Container {
168
232
  // keep this configuration for legacy code that use as custom Configuration service
169
233
  return this.settings;
170
234
  }
171
- instance = !options.rebuild ? this.#cache.get(token) : undefined;
172
- if (instance != undefined) {
173
- return instance;
235
+ if (!options.rebuild && this.#cache.has(token)) {
236
+ return this.#cache.get(token);
174
237
  }
175
238
  const provider = this.ensureProvider(token);
176
239
  // maybe not necessary
177
240
  if (!provider || options.rebuild) {
178
- instance = this.invokeToken(token, options);
241
+ const instance = this.invokeToken(token, options);
179
242
  if (provider) {
180
243
  return this.setToCache(provider, instance);
181
244
  }
182
245
  return instance;
183
246
  }
184
- instance = this.invokeToken(token, options);
247
+ const instance = this.invokeToken(token, options);
185
248
  switch (provider.scope) {
186
249
  case ProviderScope.SINGLETON:
187
- if (!options.rebuild) {
188
- this.registerHooks(provider, options);
189
- }
190
250
  return this.setToCache(provider, instance);
191
251
  case ProviderScope.REQUEST:
192
252
  if (options.locals) {
@@ -231,8 +291,11 @@ export class InjectorService extends Container {
231
291
  * Called automatically during the `load()` process.
232
292
  */
233
293
  async loadAsync() {
234
- for (const [, provider] of this) {
235
- if (!this.has(provider.token) && provider.isAsync()) {
294
+ for (const [, provider] of this.#providers) {
295
+ if (!provider.isAsync()) {
296
+ continue;
297
+ }
298
+ if (!this.has(provider.token)) {
236
299
  await this.resolve(provider.token);
237
300
  }
238
301
  }
@@ -244,9 +307,14 @@ export class InjectorService extends Container {
244
307
  * Called automatically during the `load()` process after async providers.
245
308
  */
246
309
  loadSync() {
247
- for (const [, provider] of this) {
248
- // TODO try to lazy provider instead initiate all providers (&& provider.hasRegisteredHooks())
249
- if (!this.has(provider.token) && provider.scope === ProviderScope.SINGLETON) {
310
+ if (this.settings.lazyProviders) {
311
+ return;
312
+ }
313
+ for (const [, provider] of this.#providers) {
314
+ if (provider.scope !== ProviderScope.SINGLETON || provider.isAsync()) {
315
+ continue;
316
+ }
317
+ if (!this.has(provider.token)) {
250
318
  this.resolve(provider.token);
251
319
  }
252
320
  }
@@ -271,6 +339,7 @@ export class InjectorService extends Container {
271
339
  // avoid provider registration in the GlobalContainer during the loading phase
272
340
  // using injectable() or providerBuilder()
273
341
  this.#loaded = true;
342
+ this.#hooksReady = true;
274
343
  await $asyncEmit("$beforeInit");
275
344
  await this.bootstrap(container);
276
345
  // build async and sync provider
@@ -291,7 +360,7 @@ export class InjectorService extends Container {
291
360
  return;
292
361
  }
293
362
  const mergedConfiguration = new Map();
294
- super.forEach((provider) => {
363
+ this.#providers.forEach((provider) => {
295
364
  if (provider.configuration && provider.type !== "server:module") {
296
365
  Object.entries(provider.configuration).forEach(([key, value]) => {
297
366
  if (!EXCLUDED_CONFIGURATION_KEYS.includes(key)) {
@@ -345,6 +414,11 @@ export class InjectorService extends Container {
345
414
  this.#cache.forEach((_, token) => {
346
415
  $off(token);
347
416
  });
417
+ this.#registeredSingletonHooks.forEach((token) => {
418
+ $off(token);
419
+ });
420
+ this.#registeredSingletonHooks.clear();
421
+ this.#hooksReady = false;
348
422
  }
349
423
  /**
350
424
  * Bootstrap injector from container, resolve configuration and providers.
@@ -353,7 +427,7 @@ export class InjectorService extends Container {
353
427
  */
354
428
  async bootstrap(container = createContainer()) {
355
429
  // Clone all providers in the container
356
- this.addProviders(container);
430
+ this.mergeProviders(container);
357
431
  // Resolve all configuration
358
432
  await this.resolveConfiguration();
359
433
  // allow mocking or changing provider instance before loading injector
@@ -361,10 +435,10 @@ export class InjectorService extends Container {
361
435
  return this;
362
436
  }
363
437
  ensureProvider(token, force = false) {
364
- if (!this.hasProvider(token) && (Provider.Registry.has(token) || force)) {
365
- this.addProvider(token);
438
+ if (!this.#providers.has(token) && (Provider.Registry.has(token) || force)) {
439
+ this.add(token);
366
440
  }
367
- return this.getProvider(token);
441
+ return this.#providers.get(token);
368
442
  }
369
443
  /**
370
444
  * Invoke a class method and inject service.
@@ -388,44 +462,70 @@ export class InjectorService extends Container {
388
462
  return undefined;
389
463
  }
390
464
  const { token, deps, construct, imports, provider } = resolvedOpts;
391
- $emit("$beforeInvoke", token, [resolvedOpts]);
392
- $emit(`$beforeInvoke:${String(provider.type)}`, [resolvedOpts]);
393
- let instance;
394
- let currentDependency = false;
465
+ this.#resolvingProviders.add(provider.token);
395
466
  try {
396
- const invokeDependency = (parent) => (token, index) => {
397
- currentDependency = { token, index, deps };
398
- if (isArray(token)) {
399
- return this.getMany(token[0], options);
467
+ $emit("$beforeInvoke", token, [resolvedOpts]);
468
+ $emit(`$beforeInvoke:${String(provider.type)}`, [resolvedOpts]);
469
+ let instance;
470
+ let currentDependency;
471
+ let currentDependencyIndex = -1;
472
+ let hasCurrentDependency = false;
473
+ try {
474
+ // Invoke manually imported providers
475
+ for (let index = 0; index < imports.length; index++) {
476
+ currentDependency = imports[index];
477
+ currentDependencyIndex = index;
478
+ hasCurrentDependency = true;
479
+ this.invokeDependency(currentDependency, index, undefined, provider, options);
400
480
  }
401
- return isInheritedFrom(token, Provider, 1)
402
- ? provider
403
- : this.resolve(token, {
404
- parent,
405
- locals: options.locals,
406
- useOpts: provider?.getArgOpts(index) || options.useOpts
407
- });
408
- };
409
- // Invoke manually imported providers
410
- imports.forEach(invokeDependency());
411
- // Inject dependencies
412
- const services = deps.map(invokeDependency(token));
413
- currentDependency = false;
414
- instance = construct(services);
481
+ // Inject dependencies
482
+ const services = new Array(deps.length);
483
+ for (let index = 0; index < deps.length; index++) {
484
+ currentDependency = deps[index];
485
+ currentDependencyIndex = index;
486
+ hasCurrentDependency = true;
487
+ services[index] = this.invokeDependency(currentDependency, index, token, provider, options);
488
+ }
489
+ hasCurrentDependency = false;
490
+ instance = construct(services);
491
+ }
492
+ catch (error) {
493
+ const dependency = hasCurrentDependency ? { token: currentDependency, index: currentDependencyIndex, deps } : false;
494
+ InjectionError.throwInjectorError(token, dependency, error);
495
+ }
496
+ if (instance === undefined) {
497
+ throw new InjectionError(token, `Unable to create new instance from undefined value. Check your provider declaration for ${nameOf(token)}`);
498
+ }
499
+ if (instance && isClass(classOf(instance))) {
500
+ Reflect.defineProperty(instance, DI_INVOKE_OPTIONS, {
501
+ get: () => ({ rebuild: options.rebuild, locals: options.locals })
502
+ });
503
+ }
504
+ $emit("$afterInvoke", token, [instance, resolvedOpts]);
505
+ return instance;
415
506
  }
416
- catch (error) {
417
- InjectionError.throwInjectorError(token, currentDependency, error);
507
+ finally {
508
+ this.#resolvingProviders.delete(provider.token);
418
509
  }
419
- if (instance === undefined) {
420
- throw new InjectionError(token, `Unable to create new instance from undefined value. Check your provider declaration for ${nameOf(token)}`);
510
+ }
511
+ mergeProviders(container) {
512
+ container.forEach((provider) => {
513
+ if (!this.#providers.has(provider.token)) {
514
+ this.setProvider(provider.token, provider.clone());
515
+ }
516
+ });
517
+ }
518
+ invokeDependency(token, index, parent, provider, options) {
519
+ if (isArray(token)) {
520
+ return this.getMany(token[0], options);
421
521
  }
422
- if (instance && isClass(classOf(instance))) {
423
- Reflect.defineProperty(instance, DI_INVOKE_OPTIONS, {
424
- get: () => ({ rebuild: options.rebuild, locals: options.locals })
522
+ return isInheritedFrom(token, Provider, 1)
523
+ ? provider
524
+ : this.resolve(token, {
525
+ parent,
526
+ locals: options.locals,
527
+ useOpts: provider.getArgOpts(index) || options.useOpts
425
528
  });
426
- }
427
- $emit("$afterInvoke", token, [instance, resolvedOpts]);
428
- return instance;
429
529
  }
430
530
  resolveImportsProviders() {
431
531
  const imports = this.settings.imports;
@@ -444,18 +544,22 @@ export class InjectorService extends Container {
444
544
  provider.useFactory = undefined;
445
545
  if ("useClass" in props) {
446
546
  provider.useClass = props.useClass;
547
+ this.setProvider(token, provider);
447
548
  continue;
448
549
  }
449
550
  if ("useFactory" in props) {
450
551
  provider.useFactory = props.useFactory;
552
+ this.setProvider(token, provider);
451
553
  continue;
452
554
  }
453
555
  if ("useAsyncFactory" in props) {
454
556
  provider.useAsyncFactory = props.useAsyncFactory;
557
+ this.setProvider(token, provider);
455
558
  continue;
456
559
  }
457
560
  if ("use" in props) {
458
561
  provider.useValue = props.use;
562
+ this.setProvider(token, provider);
459
563
  continue;
460
564
  }
461
565
  resolvedImports.push(meta);
@@ -476,13 +580,7 @@ export class InjectorService extends Container {
476
580
  if (!token || token === Object) {
477
581
  throw new Error("Given token is undefined. Could mean a circular dependency problem. Try to use @Inject(() => Token) to solve it.");
478
582
  }
479
- let provider;
480
- if (!this.hasProvider(token)) {
481
- provider = new Provider(token);
482
- }
483
- else {
484
- provider = this.getProvider(token);
485
- }
583
+ const provider = this.#providers.get(token) || new Provider(token);
486
584
  deps = deps || provider.deps;
487
585
  imports = imports || provider.imports;
488
586
  if (provider.useValue !== undefined) {
@@ -525,9 +623,20 @@ export class InjectorService extends Container {
525
623
  }
526
624
  return;
527
625
  }
626
+ if (this.#registeredSingletonHooks.has(provider.token)) {
627
+ return;
628
+ }
629
+ this.#registeredSingletonHooks.add(provider.token);
528
630
  Object.entries(provider.hooks).forEach(([event, cb]) => {
529
631
  const callback = (...args) => {
530
- return cb(this.#cache.get(provider.token), ...args);
632
+ let instance = this.#cache.get(provider.token);
633
+ if (instance === undefined) {
634
+ if (event === "$onDestroy" || this.#resolvingProviders.has(provider.token)) {
635
+ return;
636
+ }
637
+ instance = this.resolve(provider.token);
638
+ }
639
+ return cb(instance, ...args);
531
640
  };
532
641
  $on(event, provider.token, callback);
533
642
  });
@@ -1,4 +1,4 @@
1
- import { DI_INJECTABLE_PROPS, createContainer, destroyInjector, inject, injector } from "../../common/index.js";
1
+ import { createContainer, destroyInjector, DI_INJECTABLE_PROPS, inject, injector } from "../../common/index.js";
2
2
  import { cleanAllLocalsContainer, detachLocalsContainer, localsContainer } from "../../common/fn/localsContainer.js";
3
3
  import { $log } from "@tsed/logger";
4
4
  import { DIContext } from "../../common/domain/DIContext.js";
@@ -16,48 +16,26 @@ import type { TokenProvider } from "../interfaces/TokenProvider.js";
16
16
  * const container = new Container();
17
17
  * container.add(MyService, {scope: ProviderScope.SINGLETON});
18
18
  *
19
- * const provider = container.getProvider(MyService);
20
- * const controllers = container.getProviders(ProviderType.CONTROLLER);
19
+ * const provider = container.get(MyService);
20
+ * const controllers = container.getMany(ProviderType.CONTROLLER);
21
21
  * ```
22
22
  *
23
23
  * @public
24
24
  */
25
25
  export declare class Container extends Map<TokenProvider, Provider> {
26
- /**
27
- *
28
- * @param token
29
- * @param settings
30
- */
31
- add(token: TokenProvider, settings?: Partial<ProviderOpts>): this;
32
26
  /**
33
27
  * Add a provider to the
34
28
  * @param token
35
29
  * @param settings
36
30
  */
37
- addProvider(token: TokenProvider, settings?: Partial<ProviderOpts>): this;
38
- /**
39
- *
40
- * @param token
41
- */
42
- hasProvider(token: TokenProvider): boolean;
43
- /**
44
- * Add a provider to the
45
- * @param token
46
- * @param provider
47
- */
48
- setProvider(token: TokenProvider, provider: Provider): this;
49
- /**
50
- * The getProvider() method returns a specified element from a Map object.
51
- * @returns {T} Returns the element associated with the specified key or undefined if the key can't be found in the Map object.
52
- * @param token
53
- */
54
- getProvider<T extends Provider = Provider>(token: TokenProvider | undefined): T | undefined;
31
+ add(token: TokenProvider, settings?: Partial<ProviderOpts>): this;
32
+ get<T extends Provider = Provider>(token: TokenProvider | undefined): T | undefined;
55
33
  /**
56
34
  * Get all providers registered in the injector container.
57
35
  *
58
36
  * @param type Filter the list by the given ProviderType.
59
37
  * @returns {[TokenProvider , Provider<any>][]}
60
38
  */
61
- getProviders(type?: TokenProvider | ProviderType | string | string[]): Provider[];
62
- addProviders(container: Map<TokenProvider, Provider>): void;
39
+ getMany(type?: TokenProvider | ProviderType | string | string[]): Provider[];
40
+ merge(container: Map<TokenProvider, Provider>): void;
63
41
  }
@@ -1,5 +1,5 @@
1
1
  import { DILogger } from "../../common/index.js";
2
- import { LogLevel } from "@tsed/logger";
2
+ import type { LogLevel } from "@tsed/logger";
3
3
  import { Hooks } from "@tsed/hooks";
4
4
  /**
5
5
  * Options for creating a context logger instance.
@@ -40,8 +40,8 @@ export declare class ContextLogger {
40
40
  readonly id: string;
41
41
  maxStackSize: number;
42
42
  constructor({ id, logger, dateStart, level, maxStackSize, additionalProps }: ContextLoggerOptions);
43
- set level(level: "debug" | "info" | "warn" | "error" | "off" | "all" | LogLevel);
44
43
  get level(): "debug" | "info" | "warn" | "error" | "off" | "all" | LogLevel;
44
+ set level(level: "debug" | "info" | "warn" | "error" | "off" | "all" | LogLevel);
45
45
  get hooks(): Hooks;
46
46
  private get stack();
47
47
  alterLog(cb: (data: any, level: "debug" | "info" | "warn" | "error" | "all", withRequest: boolean) => any): Hooks;
@@ -9,6 +9,11 @@ declare global {
9
9
  scopes: {
10
10
  [key: string]: ProviderScope;
11
11
  };
12
+ /**
13
+ * Defer construction of synchronous singleton providers without lifecycle hooks until first use.
14
+ * @default false
15
+ */
16
+ lazyProviders?: boolean;
12
17
  /**
13
18
  * Define dependencies to build the provider
14
19
  */
@@ -30,39 +30,150 @@ import type { TokenRoute } from "../interfaces/TokenRoute.js";
30
30
  export declare class DIConfiguration {
31
31
  readonly default: Map<string, any>;
32
32
  protected map: Map<string, any>;
33
+ /**
34
+ * Creates a configuration service with the built-in defaults merged with the supplied initial properties.
35
+ *
36
+ * @param initialProps Initial configuration values that override built-in defaults.
37
+ */
33
38
  constructor(initialProps?: {});
39
+ /**
40
+ * Gets the application version.
41
+ */
34
42
  get version(): string;
43
+ /**
44
+ * Sets the application version.
45
+ *
46
+ * @param v Version to store in the configuration.
47
+ */
35
48
  set version(v: string);
49
+ /**
50
+ * Gets the application's root directory.
51
+ */
36
52
  get rootDir(): string;
53
+ /**
54
+ * Sets the application's root directory.
55
+ *
56
+ * @param value Root directory to store in the configuration.
57
+ */
37
58
  set rootDir(value: string);
59
+ /**
60
+ * Gets the active runtime environment.
61
+ */
38
62
  get env(): Env;
63
+ /**
64
+ * Sets the active runtime environment.
65
+ *
66
+ * @param value Environment to store in the configuration.
67
+ */
39
68
  set env(value: Env);
69
+ /**
70
+ * Gets the providers and modules imported by the application.
71
+ */
40
72
  get imports(): (TokenProvider | ImportTokenProviderOpts)[];
73
+ /**
74
+ * Sets the providers and modules imported by the application.
75
+ *
76
+ * @param imports Provider tokens or import options to configure.
77
+ */
41
78
  set imports(imports: (TokenProvider | ImportTokenProviderOpts)[]);
79
+ /**
80
+ * Indicates whether synchronous singleton providers without hooks are deferred until first use.
81
+ */
82
+ get lazyProviders(): boolean;
83
+ /**
84
+ * Enables or disables deferred initialization of synchronous singleton providers without hooks.
85
+ *
86
+ * @param lazyProviders Whether to defer eligible providers.
87
+ */
88
+ set lazyProviders(lazyProviders: boolean);
89
+ /**
90
+ * Gets the route providers registered by the application.
91
+ */
42
92
  get routes(): TokenRoute[];
93
+ /**
94
+ * Sets the route providers registered by the application.
95
+ *
96
+ * @param routes Route providers to configure.
97
+ */
43
98
  set routes(routes: TokenRoute[]);
99
+ /**
100
+ * Gets the logger options.
101
+ */
44
102
  get logger(): Partial<DILoggerOptions>;
103
+ /**
104
+ * Merges logger options into the current logger configuration.
105
+ *
106
+ * @param value Logger options to merge.
107
+ */
45
108
  set logger(value: Partial<DILoggerOptions>);
109
+ /**
110
+ * Indicates whether the logger is configured at the `debug` level.
111
+ */
46
112
  get debug(): boolean;
113
+ /**
114
+ * Enables or disables debug-level logging.
115
+ *
116
+ * @param debug `true` to set the logger level to `debug`; `false` to set it to `info`.
117
+ */
47
118
  set debug(debug: boolean);
119
+ /**
120
+ * Gets route providers grouped by mount path.
121
+ */
48
122
  get mount(): Record<string, TokenProvider[]>;
123
+ /**
124
+ * Sets route providers grouped by mount path.
125
+ *
126
+ * @param value Route providers indexed by mount path.
127
+ */
49
128
  set mount(value: Record<string, TokenProvider[]>);
50
129
  /**
130
+ * Invokes a callback once for each configured key, including default and overridden values.
51
131
  *
52
- * @param callbackfn
53
- * @param thisArg
132
+ * @param callbackfn Callback invoked with the resolved value, key, and override map.
133
+ * @param thisArg Value to use as `this` when invoking the callback.
54
134
  */
55
135
  forEach(callbackfn: (value: any, index: string, map: Map<string, any>) => void, thisArg?: any): void;
136
+ /**
137
+ * Sets one or more configuration values.
138
+ *
139
+ * Property names matching a configuration accessor are delegated to that accessor; other keys support nested paths.
140
+ *
141
+ * @param propertyKey Configuration key and value, or an object containing multiple values.
142
+ * @param value Value to assign when `propertyKey` is a string.
143
+ * @returns This configuration instance for chaining.
144
+ */
56
145
  set(obj: Partial<TsED.Configuration>): this;
57
146
  set(propertyKey: string, value?: unknown): this;
147
+ /**
148
+ * Sets a configuration value directly in the override map, supporting nested property paths.
149
+ *
150
+ * @param propertyKey Configuration key or nested property path.
151
+ * @param value Value to store.
152
+ * @returns This configuration instance for chaining.
153
+ */
58
154
  setRaw(propertyKey: string, value: any): this;
59
155
  /**
156
+ * Gets a resolved configuration value, preferring explicit overrides over default values.
60
157
  *
61
- * @param propertyKey
62
- * @param defaultValue
63
- * @returns {undefined|any}
158
+ * @param propertyKey Configuration key or nested property path.
159
+ * @param defaultValue Value to return when neither the overrides nor defaults define the key.
160
+ * @returns The resolved configuration value.
64
161
  */
65
162
  get<T = any>(propertyKey: string, defaultValue?: T): T;
163
+ /**
164
+ * Defines a custom configuration member when a member with the same key does not already exist.
165
+ *
166
+ * @param key Name of the member to define.
167
+ * @param value Function or property descriptor to assign to the configuration prototype.
168
+ * @returns This configuration instance when the member already exists.
169
+ */
66
170
  decorate(key: string, value: ((...args: unknown[]) => unknown) | PropertyDescriptor): this | undefined;
171
+ /**
172
+ * Resolves a configuration value from overrides, defaults, or the supplied fallback in that order.
173
+ *
174
+ * @param propertyKey Configuration key or nested property path.
175
+ * @param defaultValue Value to return when no configured value exists.
176
+ * @returns The resolved configuration value.
177
+ */
67
178
  protected getRaw(propertyKey: string, defaultValue?: any): any;
68
179
  }
@@ -1,9 +1,11 @@
1
1
  import { Container } from "../domain/Container.js";
2
2
  import { DIConfiguration } from "./DIConfiguration.js";
3
3
  import type { DILogger } from "../interfaces/DILogger.js";
4
+ import type { ProviderOpts } from "../interfaces/ProviderOpts.js";
4
5
  import type { InvokeOptions } from "../interfaces/InvokeOptions.js";
5
6
  import { Provider } from "../domain/Provider.js";
6
7
  import type { TokenProvider } from "../interfaces/TokenProvider.js";
8
+ import type { ProviderType } from "../domain/ProviderType.js";
7
9
  /**
8
10
  * Core dependency injection orchestrator managing provider lifecycle and resolution.
9
11
  *
@@ -28,13 +30,34 @@ import type { TokenProvider } from "../interfaces/TokenProvider.js";
28
30
  *
29
31
  * @public
30
32
  */
31
- export declare class InjectorService extends Container {
33
+ export declare class InjectorService {
32
34
  #private;
33
35
  logger: DILogger;
34
36
  settings: DIConfiguration;
35
37
  private resolvedConfiguration;
36
38
  constructor();
39
+ get providers(): Pick<Container, "get" | "has" | "getMany">;
37
40
  isLoaded(): boolean;
41
+ add(token: TokenProvider, settings?: Partial<ProviderOpts>): this;
42
+ /**
43
+ * @alias InjectorService.add
44
+ */
45
+ addProvider(token: TokenProvider, settings?: Partial<ProviderOpts>): this;
46
+ setProvider(token: TokenProvider, provider: Provider): this;
47
+ /**
48
+ * Get a provider definition from the provider registry.
49
+ *
50
+ * Unlike `get()`, this method does not resolve the provider instance.
51
+ * @deprecated Since v8. Use injector().providers.get() instead
52
+ */
53
+ getProvider<T extends Provider = Provider>(token: TokenProvider | undefined): T | undefined;
54
+ /**
55
+ * Get provider definitions matching a type.
56
+ *
57
+ * Unlike `getMany()`, this method does not resolve provider instances.
58
+ * @deprecated Since v8. Use injector().providers.getMany() instead.
59
+ */
60
+ getProviders(type?: TokenProvider | ProviderType | string | string[]): Provider[];
38
61
  /**
39
62
  * Return a list of all instances built by the injector.
40
63
  *
@@ -237,6 +260,8 @@ export declare class InjectorService extends Container {
237
260
  * @private
238
261
  */
239
262
  protected invokeToken<T>(target: TokenProvider, options?: Partial<InvokeOptions>): T | Promise<T>;
263
+ private mergeProviders;
264
+ private invokeDependency;
240
265
  private resolveImportsProviders;
241
266
  /**
242
267
  * Create options to invoke a provider or class.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tsed/di",
3
- "version": "8.37.1",
3
+ "version": "8.38.0-beta.2",
4
4
  "description": "DI module for Ts.ED Framework",
5
5
  "source": "./src/index.ts",
6
6
  "type": "module",
@@ -29,11 +29,11 @@
29
29
  "uuid": "^14.0.0"
30
30
  },
31
31
  "devDependencies": {
32
- "@tsed/barrels": "8.37.1",
33
- "@tsed/core": "8.37.1",
34
- "@tsed/hooks": "8.37.1",
32
+ "@tsed/barrels": "8.38.0-beta.2",
33
+ "@tsed/core": "8.38.0-beta.2",
34
+ "@tsed/hooks": "8.38.0-beta.2",
35
35
  "@tsed/logger": "^8.0.0",
36
- "@tsed/typescript": "8.37.1",
36
+ "@tsed/typescript": "8.38.0-beta.2",
37
37
  "cross-env": "7.0.3",
38
38
  "typescript": "6.0.2",
39
39
  "vite": "7.3.2",
@@ -61,5 +61,8 @@
61
61
  },
62
62
  "homepage": "https://github.com/tsedio/tsed/tree/production/packages/di",
63
63
  "author": "Romain Lenzotti",
64
- "license": "MIT"
64
+ "license": "MIT",
65
+ "publishConfig": {
66
+ "tag": "beta"
67
+ }
65
68
  }