@lppedd/di-wise-neo 0.5.2 → 0.6.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 +16 -12
- package/dist/cjs/index.d.ts +8 -8
- package/dist/cjs/index.js +56 -18
- package/dist/cjs/index.js.map +1 -1
- package/dist/es/index.d.mts +8 -8
- package/dist/es/index.mjs +56 -18
- package/dist/es/index.mjs.map +1 -1
- package/package.json +1 -1
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
|
-
[](https://github.com/lppedd/di-wise-neo/actions/workflows/test.yml)
|
7
|
+
[](https://app.codecov.io/gh/lppedd/di-wise-neo/tree/main/src)
|
7
8
|
[](https://www.npmjs.com/package/@lppedd/di-wise-neo)
|
8
9
|
[](https://bundlejs.com/?q=@lppedd/di-wise-neo)
|
9
10
|
[](https://github.com/lppedd/di-wise-neo/blob/main/LICENSE)
|
@@ -19,7 +20,6 @@
|
|
19
20
|
|
20
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)
|
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
284
|
+
|
285
|
+
> [!TIP]
|
286
|
+
> Using injection functions is recommended because it preserves type safety.
|
285
287
|
|
286
288
|
### Injection context
|
287
289
|
|
@@ -473,10 +475,11 @@ In this example, `ExtensionContext` will be registered with **Resolution** scope
|
|
473
475
|
|
474
476
|
### `@AutoRegister`
|
475
477
|
|
476
|
-
Enables automatic registration of the decorated class
|
478
|
+
Enables automatic registration of the decorated class when it is resolved,
|
479
|
+
if it has not been registered beforehand.
|
477
480
|
|
478
481
|
```ts
|
479
|
-
@AutoRegister
|
482
|
+
@AutoRegister()
|
480
483
|
export class ExtensionContext {
|
481
484
|
/* ... */
|
482
485
|
}
|
@@ -487,19 +490,20 @@ container.resolve(ExtensionContext);
|
|
487
490
|
|
488
491
|
### `@EagerInstantiate`
|
489
492
|
|
490
|
-
|
493
|
+
Sets the default class scope to **Container** and marks the class for eager instantiation
|
494
|
+
upon registration.
|
491
495
|
|
492
496
|
This causes the container to immediately create and cache the instance of the class
|
493
497
|
at registration time, instead of deferring instantiation until the first resolution.
|
494
498
|
|
495
499
|
```ts
|
496
|
-
@EagerInstantiate
|
497
|
-
@Scoped(Scope.Container)
|
500
|
+
@EagerInstantiate()
|
498
501
|
export class ExtensionContext {
|
499
502
|
/* ... */
|
500
503
|
}
|
501
504
|
|
502
|
-
//
|
505
|
+
// ExtensionContext is registered with Container scope,
|
506
|
+
// and an instance is immediately created and cached by the container
|
503
507
|
container.register(ExtensionContext);
|
504
508
|
```
|
505
509
|
|
package/dist/cjs/index.d.ts
CHANGED
@@ -422,7 +422,7 @@ declare function createContainer(options?: Partial<ContainerOptions>): Container
|
|
422
422
|
*
|
423
423
|
* @example
|
424
424
|
* ```ts
|
425
|
-
* @AutoRegister
|
425
|
+
* @AutoRegister()
|
426
426
|
* class Wizard {}
|
427
427
|
*
|
428
428
|
* const wizard = container.resolve(Wizard);
|
@@ -431,28 +431,28 @@ declare function createContainer(options?: Partial<ContainerOptions>): Container
|
|
431
431
|
*
|
432
432
|
* @__NO_SIDE_EFFECTS__
|
433
433
|
*/
|
434
|
-
declare function AutoRegister
|
434
|
+
declare function AutoRegister(): ClassDecorator;
|
435
435
|
|
436
436
|
/**
|
437
|
-
* Class decorator that
|
438
|
-
* in the container
|
437
|
+
* Class decorator that sets the class scope to **Container** and enables
|
438
|
+
* eager instantiation when the class is registered in the container.
|
439
439
|
*
|
440
440
|
* This causes the container to immediately create and cache the instance of the class,
|
441
441
|
* instead of deferring instantiation until the first resolution.
|
442
442
|
*
|
443
443
|
* @example
|
444
444
|
* ```ts
|
445
|
-
* @EagerInstantiate
|
446
|
-
* @Scoped(Scope.Container)
|
445
|
+
* @EagerInstantiate()
|
447
446
|
* class Wizard {}
|
448
447
|
*
|
449
|
-
* //
|
448
|
+
* // Wizard is registered with Container scope, and an instance
|
449
|
+
* // is immediately created and cached by the container
|
450
450
|
* const wizard = container.register(Wizard);
|
451
451
|
* ```
|
452
452
|
*
|
453
453
|
* @__NO_SIDE_EFFECTS__
|
454
454
|
*/
|
455
|
-
declare function EagerInstantiate
|
455
|
+
declare function EagerInstantiate(): ClassDecorator;
|
456
456
|
|
457
457
|
interface TokensRef<Value = any> {
|
458
458
|
readonly getRefTokens: () => Set<Token<Value>>;
|
package/dist/cjs/index.js
CHANGED
@@ -192,6 +192,22 @@ function getMetadata(Class) {
|
|
192
192
|
}
|
193
193
|
});
|
194
194
|
}
|
195
|
+
if (metadata.provider.useClass !== Class) {
|
196
|
+
// This is part of the class identity mapping API (see setClassIdentityMapping).
|
197
|
+
//
|
198
|
+
// Scenario:
|
199
|
+
// 1. Metadata is created for class A (the original class) because of a parameter decorator.
|
200
|
+
// 2. Later, because of a class decorator that extends the decorated class, a third-party
|
201
|
+
// registers a class identity mapping from class B to class A, where B is the class
|
202
|
+
// generated from the class decorator by extending A.
|
203
|
+
//
|
204
|
+
// We must update useClass to be the extender class B so that instances created by the
|
205
|
+
// DI container match the consumer's registered class. Without this update, the DI
|
206
|
+
// system would instantiate the original class A, causing behavior inconsistencies.
|
207
|
+
//
|
208
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
209
|
+
metadata.provider.useClass = Class;
|
210
|
+
}
|
195
211
|
return metadata;
|
196
212
|
}
|
197
213
|
const classIdentityMap = new WeakMap();
|
@@ -520,7 +536,7 @@ function isDisposable(value) {
|
|
520
536
|
// The provider is of type ClassProvider, initialized by getMetadata
|
521
537
|
provider: metadata.provider,
|
522
538
|
options: {
|
523
|
-
scope: metadata.scope ?? this.myOptions.defaultScope
|
539
|
+
scope: metadata.scope?.value ?? this.myOptions.defaultScope
|
524
540
|
},
|
525
541
|
dependencies: metadata.dependencies
|
526
542
|
};
|
@@ -537,7 +553,7 @@ function isDisposable(value) {
|
|
537
553
|
}
|
538
554
|
// Eager-instantiate only if the class is container-scoped
|
539
555
|
if (metadata.eagerInstantiate && registration.options?.scope === Scope.Container) {
|
540
|
-
this.
|
556
|
+
this.resolveProviderValue(registration, registration.provider);
|
541
557
|
}
|
542
558
|
} else {
|
543
559
|
const [token, provider, options] = args;
|
@@ -548,7 +564,7 @@ function isDisposable(value) {
|
|
548
564
|
options: {
|
549
565
|
// The explicit registration options override what is specified
|
550
566
|
// via class decorators (e.g., @Scoped)
|
551
|
-
scope: metadata.scope ?? this.myOptions.defaultScope,
|
567
|
+
scope: metadata.scope?.value ?? this.myOptions.defaultScope,
|
552
568
|
...options
|
553
569
|
},
|
554
570
|
dependencies: metadata.dependencies
|
@@ -556,7 +572,7 @@ function isDisposable(value) {
|
|
556
572
|
this.myTokenRegistry.set(token, registration);
|
557
573
|
// Eager-instantiate only if the provided class is container-scoped
|
558
574
|
if (metadata.eagerInstantiate && registration.options?.scope === Scope.Container) {
|
559
|
-
this.
|
575
|
+
this.resolveProviderValue(registration, registration.provider);
|
560
576
|
}
|
561
577
|
} else {
|
562
578
|
if (isExistingProvider(provider)) {
|
@@ -672,7 +688,7 @@ function isDisposable(value) {
|
|
672
688
|
metadata.eagerInstantiate = eagerInstantiate;
|
673
689
|
}
|
674
690
|
}
|
675
|
-
const scope = this.resolveScope(metadata.scope);
|
691
|
+
const scope = this.resolveScope(metadata.scope?.value);
|
676
692
|
if (optional && scope === Scope.Container) {
|
677
693
|
// It would not be possible to resolve the class in container scope,
|
678
694
|
// as that would require prior registration.
|
@@ -862,7 +878,7 @@ function isDisposable(value) {
|
|
862
878
|
*
|
863
879
|
* @example
|
864
880
|
* ```ts
|
865
|
-
* @AutoRegister
|
881
|
+
* @AutoRegister()
|
866
882
|
* class Wizard {}
|
867
883
|
*
|
868
884
|
* const wizard = container.resolve(Wizard);
|
@@ -870,32 +886,45 @@ function isDisposable(value) {
|
|
870
886
|
* ```
|
871
887
|
*
|
872
888
|
* @__NO_SIDE_EFFECTS__
|
873
|
-
*/ function AutoRegister(
|
874
|
-
|
875
|
-
|
889
|
+
*/ function AutoRegister() {
|
890
|
+
return function(Class) {
|
891
|
+
const metadata = getMetadata(Class);
|
892
|
+
metadata.autoRegister = true;
|
893
|
+
};
|
876
894
|
}
|
877
895
|
|
878
896
|
/**
|
879
|
-
* Class decorator that
|
880
|
-
* in the container
|
897
|
+
* Class decorator that sets the class scope to **Container** and enables
|
898
|
+
* eager instantiation when the class is registered in the container.
|
881
899
|
*
|
882
900
|
* This causes the container to immediately create and cache the instance of the class,
|
883
901
|
* instead of deferring instantiation until the first resolution.
|
884
902
|
*
|
885
903
|
* @example
|
886
904
|
* ```ts
|
887
|
-
* @EagerInstantiate
|
888
|
-
* @Scoped(Scope.Container)
|
905
|
+
* @EagerInstantiate()
|
889
906
|
* class Wizard {}
|
890
907
|
*
|
891
|
-
* //
|
908
|
+
* // Wizard is registered with Container scope, and an instance
|
909
|
+
* // is immediately created and cached by the container
|
892
910
|
* const wizard = container.register(Wizard);
|
893
911
|
* ```
|
894
912
|
*
|
895
913
|
* @__NO_SIDE_EFFECTS__
|
896
|
-
*/ function EagerInstantiate(
|
897
|
-
|
898
|
-
|
914
|
+
*/ function EagerInstantiate() {
|
915
|
+
return function(Class) {
|
916
|
+
const metadata = getMetadata(Class);
|
917
|
+
const currentScope = metadata.scope;
|
918
|
+
assert(!currentScope || currentScope.value === Scope.Container, ()=>{
|
919
|
+
const { value, appliedBy } = currentScope;
|
920
|
+
return `class ${Class.name}: Scope.${value} was already set by @${appliedBy},\n ` + `but @EagerInstantiate is trying to set a conflicting Scope.Container.\n ` + `Only one decorator should set the class scope, or all must agree on the same value.`;
|
921
|
+
});
|
922
|
+
metadata.eagerInstantiate = true;
|
923
|
+
metadata.scope = {
|
924
|
+
value: Scope.Container,
|
925
|
+
appliedBy: "EagerInstantiate"
|
926
|
+
};
|
927
|
+
};
|
899
928
|
}
|
900
929
|
|
901
930
|
function forwardRef(token) {
|
@@ -1027,7 +1056,16 @@ function OptionalAll(token) {
|
|
1027
1056
|
*/ function Scoped(scope) {
|
1028
1057
|
return function(Class) {
|
1029
1058
|
const metadata = getMetadata(Class);
|
1030
|
-
|
1059
|
+
const currentScope = metadata.scope;
|
1060
|
+
assert(!currentScope || currentScope.value === scope, ()=>{
|
1061
|
+
const { value, appliedBy } = currentScope;
|
1062
|
+
const by = appliedBy === "Scoped" ? `another @${appliedBy} decorator` : `@${appliedBy}`;
|
1063
|
+
return `class ${Class.name}: Scope.${value} was already set by ${by},\n ` + `but @Scoped is trying to set a conflicting Scope.${scope}.\n ` + `Only one decorator should set the class scope, or all must agree on the same value.`;
|
1064
|
+
});
|
1065
|
+
metadata.scope = {
|
1066
|
+
value: scope,
|
1067
|
+
appliedBy: "Scoped"
|
1068
|
+
};
|
1031
1069
|
};
|
1032
1070
|
}
|
1033
1071
|
|