@lppedd/di-wise-neo 0.5.3 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -3,7 +3,8 @@
3
3
  <p align="center">Lightweight, type-safe, flexible dependency injection library for TypeScript and JavaScript</p>
4
4
  <div align="center">
5
5
 
6
- [![test](https://img.shields.io/github/actions/workflow/status/lppedd/di-wise-neo/test.yml.svg?branch=main)](https://github.com/lppedd/di-wise-neo/actions/workflows/test.yml)
6
+ [![build](https://img.shields.io/github/actions/workflow/status/lppedd/di-wise-neo/test.yml.svg?branch=main)](https://github.com/lppedd/di-wise-neo/actions/workflows/test.yml)
7
+ [![coverage](https://img.shields.io/codecov/c/github/lppedd/di-wise-neo/main?token=R9XZFTQ0BA)](https://app.codecov.io/gh/lppedd/di-wise-neo/tree/main/src)
7
8
  [![npm](https://img.shields.io/npm/v/@lppedd/di-wise-neo?color=%23de1f1f&logo=npm)](https://www.npmjs.com/package/@lppedd/di-wise-neo)
8
9
  [![npm gzipped size](https://img.shields.io/bundlejs/size/@lppedd/di-wise-neo)](https://bundlejs.com/?q=@lppedd/di-wise-neo)
9
10
  [![license](https://img.shields.io/github/license/lppedd/di-wise-neo?color=blue)](https://github.com/lppedd/di-wise-neo/blob/main/LICENSE)
@@ -17,9 +18,8 @@
17
18
  > in part thanks to TypeScript's experimental decorators. Shout out to [@exuanbo](https://github.com/exuanbo)
18
19
  > for the strong foundations!
19
20
 
20
- ## Table of Contents
21
+ ### Table of Contents
21
22
 
22
- - [Why yet another library](#why-yet-another-library)
23
23
  - [Installation](#installation)
24
24
  - [API reference](#api-reference)
25
25
  - [Ergonomics & Requirements](#ergonomics)
@@ -31,7 +31,7 @@
31
31
  - [Behavioral decorators](#behavioral-decorators)
32
32
  - [Testing support](#testing-support)
33
33
 
34
- ## Why yet another library
34
+ ### Why yet another library
35
35
 
36
36
  I've been developing VS Code extensions for a while as part of my daily work.
37
37
  It's enjoyable work! However, extensions always reach that tipping point where
@@ -79,7 +79,7 @@ the use of ECMAScript Stage 3 decorators, which do not support decorating method
79
79
  So what's the right move? Forking the best pick and refactoring it to suite my
80
80
  production needs.
81
81
 
82
- ## Installation
82
+ ### Installation
83
83
 
84
84
  ```sh
85
85
  npm i @lppedd/di-wise-neo
@@ -93,11 +93,11 @@ pnpm add @lppedd/di-wise-neo
93
93
  yarn add @lppedd/di-wise-neo
94
94
  ```
95
95
 
96
- ## API reference
96
+ ### API reference
97
97
 
98
98
  You can find the complete API reference at [lppedd.github.io/di-wise-neo](https://lppedd.github.io/di-wise-neo)
99
99
 
100
- ## Ergonomics
100
+ ### Ergonomics
101
101
 
102
102
  - Does **not** depend on other libraries
103
103
  - Does **not** use [reflect-metadata](https://www.npmjs.com/package/reflect-metadata) to drive decorators
@@ -281,7 +281,9 @@ The container will translate `TaskID` to `PID` before resolving the value.
281
281
 
282
282
  The primary way to perform dependency injection in **di-wise-neo** is through
283
283
  functions like `inject(T)`, `injectAll(T)`, `optional(T)`, and `optionalAll(T)`.
284
- This approach is recommended because it preserves full type safety.
284
+
285
+ > [!TIP]
286
+ > Using injection functions is recommended because it preserves type safety.
285
287
 
286
288
  ### Injection context
287
289
 
@@ -442,7 +444,7 @@ export class ExtensionContext {
442
444
 
443
445
  ## Behavioral decorators
444
446
 
445
- The library includes three behavioral decorators that influence how classes are registered in the container.
447
+ The library includes four behavioral decorators that influence how classes are registered in the container.
446
448
  These decorators attach metadata to the class type, which is then interpreted by the container during registration.
447
449
 
448
450
  ### `@Scoped`
@@ -471,12 +473,39 @@ container.register(
471
473
 
472
474
  In this example, `ExtensionContext` will be registered with **Resolution** scope instead.
473
475
 
476
+ ### `@Named`
477
+
478
+ Marks a class or injected dependency with a unique name (qualifier), allowing the container
479
+ to distinguish between multiple implementations of the same type.
480
+
481
+ ```ts
482
+ @Named("persistent")
483
+ @Scoped(Scope.Container)
484
+ export class PersistentSecretStorage implements SecretStorage {
485
+ /* ... */
486
+ }
487
+
488
+ // Register the class with Type<SecretStorage>.
489
+ // The container will automatically qualify the registration with 'persistent'.
490
+ container.register(ISecretStorage, { useClass: PersistentSecretStorage });
491
+
492
+ // Inject the SecretStorage dependency by name
493
+ export class ExtensionContext {
494
+ constructor(@Inject(ISecretStorage) @Named("persistent") readonly secretStorage: SecretStorage) {}
495
+
496
+ /* ... */
497
+ }
498
+ ```
499
+
500
+ The container will throw an error at registration time if the name is already taken by another registration.
501
+
474
502
  ### `@AutoRegister`
475
503
 
476
- Enables automatic registration of the decorated class if it has not been registered explicitly.
504
+ Enables automatic registration of the decorated class when it is resolved,
505
+ if it has not been registered beforehand.
477
506
 
478
507
  ```ts
479
- @AutoRegister
508
+ @AutoRegister()
480
509
  export class ExtensionContext {
481
510
  /* ... */
482
511
  }
@@ -487,19 +516,20 @@ container.resolve(ExtensionContext);
487
516
 
488
517
  ### `@EagerInstantiate`
489
518
 
490
- Marks a class for eager instantiation when registered with **Container** scope.
519
+ Sets the default class scope to **Container** and marks the class for eager instantiation
520
+ upon registration.
491
521
 
492
522
  This causes the container to immediately create and cache the instance of the class
493
523
  at registration time, instead of deferring instantiation until the first resolution.
494
524
 
495
525
  ```ts
496
- @EagerInstantiate
497
- @Scoped(Scope.Container)
526
+ @EagerInstantiate()
498
527
  export class ExtensionContext {
499
528
  /* ... */
500
529
  }
501
530
 
502
- // The container immediately creates and caches the instance
531
+ // ExtensionContext is registered with Container scope,
532
+ // and an instance is immediately created and cached by the container
503
533
  container.register(ExtensionContext);
504
534
  ```
505
535
 
@@ -62,22 +62,81 @@ declare function createType<T>(typeName: string): Type<T>;
62
62
  * Provides a class instance for a token via a class constructor.
63
63
  */
64
64
  interface ClassProvider<Instance extends object> {
65
+ /**
66
+ * The class to instantiate for the token.
67
+ */
65
68
  readonly useClass: Constructor<Instance>;
69
+ /**
70
+ * An optional name to qualify this provider.
71
+ * If specified, the token must be resolved using the same name.
72
+ *
73
+ * Equivalent to decorating the class with `@Named(...)`.
74
+ *
75
+ * @example
76
+ * ```ts
77
+ * export class ExtensionContext {
78
+ * // Decorator-based injection
79
+ * constructor(@Inject(ISecretStorage) @Named("persistent") secretStorage: SecretStorage) {}
80
+ *
81
+ * // Function-based injection
82
+ * constructor(secretStorage = inject(ISecretStorage, "persistent")) {}
83
+ * }
84
+ * ```
85
+ */
86
+ readonly name?: string;
66
87
  }
67
88
  /**
68
89
  * Provides a value for a token via a factory function.
69
- *
70
- * The factory function runs inside the injection context and can
71
- * thus access dependencies via {@link inject}-like functions.
72
90
  */
73
91
  interface FactoryProvider<Value> {
92
+ /**
93
+ * A function that produces the value at resolution time.
94
+ *
95
+ * The function runs inside the injection context and can
96
+ * access dependencies via {@link inject}-like helpers.
97
+ */
74
98
  readonly useFactory: (...args: []) => Value;
99
+ /**
100
+ * An optional name to qualify this provider.
101
+ * If specified, the token must be resolved using the same name.
102
+ *
103
+ * @example
104
+ * ```ts
105
+ * export class ExtensionContext {
106
+ * // Decorator-based injection
107
+ * constructor(@Inject(ISecretStorage) @Named("persistent") secretStorage: SecretStorage) {}
108
+ *
109
+ * // Function-based injection
110
+ * constructor(secretStorage = inject(ISecretStorage, "persistent")) {}
111
+ * }
112
+ * ```
113
+ */
114
+ readonly name?: string;
75
115
  }
76
116
  /**
77
117
  * Provides a static - already constructed - value for a token.
78
118
  */
79
119
  interface ValueProvider<T> {
120
+ /**
121
+ * The static value to associate with the token.
122
+ */
80
123
  readonly useValue: T;
124
+ /**
125
+ * An optional name to qualify this provider.
126
+ * If specified, the token must be resolved using the same name.
127
+ *
128
+ * @example
129
+ * ```ts
130
+ * export class ExtensionContext {
131
+ * // Decorator-based injection
132
+ * constructor(@Inject(ISecretStorage) @Named("persistent") secretStorage: SecretStorage) {}
133
+ *
134
+ * // Function-based injection
135
+ * constructor(secretStorage = inject(ISecretStorage, "persistent")) {}
136
+ * }
137
+ * ```
138
+ */
139
+ readonly name?: string;
81
140
  }
82
141
  /**
83
142
  * Aliases another registered token.
@@ -85,6 +144,9 @@ interface ValueProvider<T> {
85
144
  * Resolving this token will return the value of the aliased one.
86
145
  */
87
146
  interface ExistingProvider<Value> {
147
+ /**
148
+ * The existing token to alias.
149
+ */
88
150
  readonly useExisting: Token<Value>;
89
151
  }
90
152
  /**
@@ -212,77 +274,33 @@ interface Container {
212
274
  /**
213
275
  * Returns whether the token is registered in this container or in parent containers, if any.
214
276
  */
215
- isRegistered(token: Token): boolean;
216
- /**
217
- * Registers a concrete class, where the class acts as its own token.
218
- *
219
- * Tokens provided via the {@link Injectable} decorator applied to the class
220
- * are also registered as aliases.
221
- *
222
- * The default registration scope is determined by the {@link Scoped} decorator,
223
- * if present.
224
- */
225
- registerClass<Instance extends object>(Class: Constructor<Instance>): void;
226
- /**
227
- * Registers a concrete class with a token.
228
- *
229
- * The default registration scope is determined by the {@link Scoped} decorator
230
- * applied to the class, if present, but it can be overridden by passing explicit
231
- * registration options.
232
- */
233
- registerClass<Instance extends object, ProvidedInstance extends Instance>(token: Token<Instance>, Class: Constructor<ProvidedInstance>, options?: RegistrationOptions): void;
234
- /**
235
- * Registers a token whose value is produced by a factory function.
236
- *
237
- * The factory function runs inside the injection context and can
238
- * thus access dependencies via {@link inject}-like functions.
239
- */
240
- registerFactory<Value, ProvidedValue extends Value>(token: Token<Value>, factory: (...args: []) => ProvidedValue, options?: RegistrationOptions): void;
241
- /**
242
- * Registers a token with a fixed value.
243
- *
244
- * The provided value is returned as-is when the token is resolved (scopes do not apply).
245
- */
246
- registerValue<Value, ProvidedValue extends Value>(token: Token<Value>, value: ProvidedValue): void;
247
- /**
248
- * Registers one or more tokens as aliases for a target token.
249
- *
250
- * When an alias is resolved, the target token is resolved instead.
251
- */
252
- registerAlias<Value, ProvidedValue extends Value>(targetToken: Token<ProvidedValue>, aliasTokens: Tokens<Value>): void;
277
+ isRegistered(token: Token, name?: string): boolean;
253
278
  /**
254
279
  * Registers a {@link ClassProvider}, using the class itself as its token.
255
280
  *
256
281
  * Tokens provided via the {@link Injectable} decorator applied to the class
257
282
  * are also registered as aliases.
258
283
  *
259
- * The scope is determined by the {@link Scoped} decorator, if present.
260
- *
261
- * @see registerClass
284
+ * The scope is determined by the {@link Scoped} decorator - if present -
285
+ * or by the {@link ContainerOptions.defaultScope} value.
262
286
  */
263
287
  register<Instance extends object>(Class: Constructor<Instance>): Container;
264
288
  /**
265
289
  * Registers a {@link ClassProvider} with a token.
266
290
  *
267
291
  * The default registration scope is determined by the {@link Scoped} decorator
268
- * applied to the provided class, if present, but it can be overridden by
269
- * passing explicit registration options.
270
- *
271
- * @see registerClass
292
+ * applied to the provided class - if present - or by the {@link ContainerOptions.defaultScope}
293
+ * value, but it can be overridden by passing explicit registration options.
272
294
  */
273
295
  register<Instance extends object, ProviderInstance extends Instance>(token: Token<Instance>, provider: ClassProvider<ProviderInstance>, options?: RegistrationOptions): Container;
274
296
  /**
275
297
  * Registers a {@link FactoryProvider} with a token.
276
- *
277
- * @see registerFactory
278
298
  */
279
299
  register<Value, ProviderValue extends Value>(token: Token<Value>, provider: FactoryProvider<ProviderValue>, options?: RegistrationOptions): Container;
280
300
  /**
281
301
  * Registers an {@link ExistingProvider} with a token.
282
302
  *
283
303
  * The token will alias the one set in `useExisting`.
284
- *
285
- * @see registerAlias
286
304
  */
287
305
  register<Value, ProviderValue extends Value>(token: Token<Value>, provider: ExistingProvider<ProviderValue>): Container;
288
306
  /**
@@ -290,8 +308,6 @@ interface Container {
290
308
  *
291
309
  * Values provided via `useValue` are never cached (scopes do not apply)
292
310
  * and are simply returned as-is.
293
- *
294
- * @see registerValue
295
311
  */
296
312
  register<Value, ProviderValue extends Value>(token: Token<Value>, provider: ValueProvider<ProviderValue>): Container;
297
313
  /**
@@ -302,7 +318,7 @@ interface Container {
302
318
  *
303
319
  * Note that only this container is affected. Parent containers, if any, remain unchanged.
304
320
  */
305
- unregister<Value>(token: Token<Value>): Value[];
321
+ unregister<Value>(token: Token<Value>, name?: string): Value[];
306
322
  /**
307
323
  * Resolves the given class to the instance associated with it.
308
324
  *
@@ -328,9 +344,10 @@ interface Container {
328
344
  * If the class is registered with _container_ scope, the resolved instance is cached
329
345
  * in the container's internal registry.
330
346
  */
331
- resolve<Instance extends object>(Class: Constructor<Instance>, optional?: false): Instance;
332
- resolve<Instance extends object>(Class: Constructor<Instance>, optional: true): Instance | undefined;
333
- resolve<Instance extends object>(Class: Constructor<Instance>, optional?: boolean): Instance | undefined;
347
+ resolve<Instance extends object>(Class: Constructor<Instance>, name?: string): Instance;
348
+ resolve<Instance extends object>(Class: Constructor<Instance>, optional?: false, name?: string): Instance;
349
+ resolve<Instance extends object>(Class: Constructor<Instance>, optional: true, name?: string): Instance | undefined;
350
+ resolve<Instance extends object>(Class: Constructor<Instance>, optional?: boolean, name?: string): Instance | undefined;
334
351
  /**
335
352
  * Resolves the given token to the value associated with it.
336
353
  *
@@ -349,9 +366,10 @@ interface Container {
349
366
  * If the token is registered with _container_ scope, the resolved value is cached
350
367
  * in the container's internal registry.
351
368
  */
352
- resolve<Value>(token: Token<Value>, optional?: false): Value;
353
- resolve<Value>(token: Token<Value>, optional: true): Value | undefined;
354
- resolve<Value>(token: Token<Value>, optional?: boolean): Value | undefined;
369
+ resolve<Value>(token: Token<Value>, name?: string): Value;
370
+ resolve<Value>(token: Token<Value>, optional?: false, name?: string): Value;
371
+ resolve<Value>(token: Token<Value>, optional: true, name?: string): Value | undefined;
372
+ resolve<Value>(token: Token<Value>, optional?: boolean, name?: string): Value | undefined;
355
373
  /**
356
374
  * Resolves the given class to all instances provided by the registrations associated with it.
357
375
  *
@@ -422,7 +440,7 @@ declare function createContainer(options?: Partial<ContainerOptions>): Container
422
440
  *
423
441
  * @example
424
442
  * ```ts
425
- * @AutoRegister
443
+ * @AutoRegister()
426
444
  * class Wizard {}
427
445
  *
428
446
  * const wizard = container.resolve(Wizard);
@@ -431,28 +449,28 @@ declare function createContainer(options?: Partial<ContainerOptions>): Container
431
449
  *
432
450
  * @__NO_SIDE_EFFECTS__
433
451
  */
434
- declare function AutoRegister<Ctor extends Constructor<any>>(Class: Ctor): void;
452
+ declare function AutoRegister(): ClassDecorator;
435
453
 
436
454
  /**
437
- * Class decorator that enables eager instantiation of a class when it is registered
438
- * in the container with `Scope.Container`.
455
+ * Class decorator that sets the class scope to **Container** and enables
456
+ * eager instantiation when the class is registered in the container.
439
457
  *
440
458
  * This causes the container to immediately create and cache the instance of the class,
441
459
  * instead of deferring instantiation until the first resolution.
442
460
  *
443
461
  * @example
444
462
  * ```ts
445
- * @EagerInstantiate
446
- * @Scoped(Scope.Container)
463
+ * @EagerInstantiate()
447
464
  * class Wizard {}
448
465
  *
449
- * // A Wizard instance is immediately created and cached by the container
450
- * const wizard = container.register(Wizard);
466
+ * // Wizard is registered with Container scope, and an instance
467
+ * // is immediately created and cached by the container
468
+ * container.register(Wizard);
451
469
  * ```
452
470
  *
453
471
  * @__NO_SIDE_EFFECTS__
454
472
  */
455
- declare function EagerInstantiate<Ctor extends Constructor<any>>(Class: Ctor): void;
473
+ declare function EagerInstantiate(): ClassDecorator;
456
474
 
457
475
  interface TokensRef<Value = any> {
458
476
  readonly getRefTokens: () => Set<Token<Value>>;
@@ -568,6 +586,26 @@ declare function InjectAll<Value>(token: Token<Value>): ParameterDecorator;
568
586
  */
569
587
  declare function InjectAll<Value>(tokens: TokenRef<Value>): ParameterDecorator;
570
588
 
589
+ /**
590
+ * Qualifies a class or an injected parameter with a unique name.
591
+ *
592
+ * This allows the container to distinguish between multiple implementations
593
+ * of the same interface or type during registration and injection.
594
+ *
595
+ * @example
596
+ * ```ts
597
+ * @Named("dumbledore")
598
+ * class Dumbledore implements Wizard {}
599
+ *
600
+ * // Register Dumbledore with Type<Wizard>
601
+ * container.register(IWizard, { useClass: Dumbledore });
602
+ * const dumbledore = container.resolve(IWizard, "dumbledore");
603
+ * ```
604
+ *
605
+ * @__NO_SIDE_EFFECTS__
606
+ */
607
+ declare function Named(name: string): ClassDecorator & ParameterDecorator;
608
+
571
609
  /**
572
610
  * Parameter decorator that injects the instance associated with the given class,
573
611
  * or `undefined` if the class is not registered in the container.
@@ -656,13 +694,13 @@ declare function Scoped(scope: Scope): ClassDecorator;
656
694
  *
657
695
  * Throws an error if the class is not registered in the container.
658
696
  */
659
- declare function inject<Instance extends object>(Class: Constructor<Instance>): Instance;
697
+ declare function inject<Instance extends object>(Class: Constructor<Instance>, name?: string): Instance;
660
698
  /**
661
699
  * Injects the value associated with the given token.
662
700
  *
663
701
  * Throws an error if the token is not registered in the container.
664
702
  */
665
- declare function inject<Value>(token: Token<Value>): Value;
703
+ declare function inject<Value>(token: Token<Value>, name?: string): Value;
666
704
  /**
667
705
  * Injects the instance associated with the given class.
668
706
  *
@@ -684,8 +722,9 @@ declare function inject<Value>(token: Token<Value>): Value;
684
722
  *
685
723
  * @param thisArg - The containing instance, used to help resolve circular dependencies.
686
724
  * @param Class - The class to resolve.
725
+ * @param name - The name qualifier of the class to resolve.
687
726
  */
688
- declare function injectBy<Instance extends object>(thisArg: any, Class: Constructor<Instance>): Instance;
727
+ declare function injectBy<Instance extends object>(thisArg: any, Class: Constructor<Instance>, name?: string): Instance;
689
728
  /**
690
729
  * Injects the value associated with the given token.
691
730
  *
@@ -707,8 +746,9 @@ declare function injectBy<Instance extends object>(thisArg: any, Class: Construc
707
746
  *
708
747
  * @param thisArg - The containing instance, used to help resolve circular dependencies.
709
748
  * @param token - The token to resolve.
749
+ * @param name - The name qualifier of the token to resolve.
710
750
  */
711
- declare function injectBy<Value>(thisArg: any, token: Token<Value>): Value;
751
+ declare function injectBy<Value>(thisArg: any, token: Token<Value>, name?: string): Value;
712
752
 
713
753
  /**
714
754
  * Injects all instances provided by the registrations associated with the given class.
@@ -865,5 +905,5 @@ interface Middleware {
865
905
  */
866
906
  declare function applyMiddleware(container: Container, middlewares: Middleware[]): Container;
867
907
 
868
- export { AutoRegister, EagerInstantiate, Inject, InjectAll, Injectable, Injector, Optional, OptionalAll, Scope, Scoped, applyMiddleware, build, createContainer, createType, forwardRef, inject, injectAll, injectBy, setClassIdentityMapping };
908
+ export { AutoRegister, EagerInstantiate, Inject, InjectAll, Injectable, Injector, Named, Optional, OptionalAll, Scope, Scoped, applyMiddleware, build, createContainer, createType, forwardRef, inject, injectAll, injectBy, setClassIdentityMapping };
869
909
  export type { ClassProvider, Constructor, Container, ContainerOptions, ExistingProvider, FactoryProvider, Middleware, MiddlewareComposer, Provider, RegistrationOptions, Token, TokenRef, Tokens, TokensRef, Type, ValueProvider };