@travetto/di 2.1.5 → 2.2.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 +11 -11
- package/package.json +5 -5
- package/src/decorator.ts +12 -9
- package/src/error.ts +1 -1
- package/src/registry.ts +62 -41
- package/support/dynamic.injection.ts +13 -11
- package/support/invoke.ts +7 -2
- package/support/transformer.injectable.ts +12 -10
package/README.md
CHANGED
|
@@ -8,10 +8,10 @@
|
|
|
8
8
|
npm install @travetto/di
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
[Dependency injection](https://en.wikipedia.org/wiki/Dependency_injection) is a framework primitive. When used in conjunction with automatic file scanning, it provides for handling of application dependency wiring. Due to the nature of [Typescript](https://typescriptlang.org) and type erasure of interfaces, dependency injection only supports `class`es as type
|
|
11
|
+
[Dependency injection](https://en.wikipedia.org/wiki/Dependency_injection) is a framework primitive. When used in conjunction with automatic file scanning, it provides for handling of application dependency wiring. Due to the nature of [Typescript](https://typescriptlang.org) and type erasure of interfaces, dependency injection only supports `class`es as a type signifier. The primary goal of dependency injection is to allow for separation of concerns of object creation and it's usage.
|
|
12
12
|
|
|
13
13
|
## Declaration
|
|
14
|
-
The [@Injectable](https://github.com/travetto/travetto/tree/main/module/di/src/decorator.ts#
|
|
14
|
+
The [@Injectable](https://github.com/travetto/travetto/tree/main/module/di/src/decorator.ts#L32) and [@InjectableFactory](https://github.com/travetto/travetto/tree/main/module/di/src/decorator.ts#L75) decorators provide the registration of dependencies. Dependency declaration revolves around exposing `class`es and subtypes thereof to provide necessary functionality. Additionally, the framework will utilize dependencies to satisfy contracts with various implementations (e.g. [MongoModelService](https://github.com/travetto/travetto/tree/main/module/model-mongo/src/service.ts#L49) provides itself as an injectable candidate for [ModelCrudSupport](https://github.com/travetto/travetto/tree/main/module/model/src/service/crud.ts).
|
|
15
15
|
|
|
16
16
|
**Code: Example Injectable**
|
|
17
17
|
```typescript
|
|
@@ -72,7 +72,7 @@ class SpecificService extends BaseService {
|
|
|
72
72
|
}
|
|
73
73
|
```
|
|
74
74
|
|
|
75
|
-
In this scenario, `SpecificService` is a valid candidate for `BaseService` due to the abstract inheritance. Sometimes, you may want to provide a slight variation to a dependency without extending a class. To this end, the [@InjectableFactory](https://github.com/travetto/travetto/tree/main/module/di/src/decorator.ts#
|
|
75
|
+
In this scenario, `SpecificService` is a valid candidate for `BaseService` due to the abstract inheritance. Sometimes, you may want to provide a slight variation to a dependency without extending a class. To this end, the [@InjectableFactory](https://github.com/travetto/travetto/tree/main/module/di/src/decorator.ts#L75) decorator denotes a `static` class method that produces an [@Injectable](https://github.com/travetto/travetto/tree/main/module/di/src/decorator.ts#L32).
|
|
76
76
|
|
|
77
77
|
**Code: Example InjectableFactory**
|
|
78
78
|
```typescript
|
|
@@ -93,15 +93,15 @@ class Config {
|
|
|
93
93
|
|
|
94
94
|
Given the `static` method `initService`, the function will be provided as a valid candidate for `CoolService`. Instead of calling the constructor of the type directly, this function will work as a factory for producing the injectable.
|
|
95
95
|
|
|
96
|
-
**Note**: Other modules are able to provide aliases to [@Injectable](https://github.com/travetto/travetto/tree/main/module/di/src/decorator.ts#
|
|
96
|
+
**Note**: Other modules are able to provide aliases to [@Injectable](https://github.com/travetto/travetto/tree/main/module/di/src/decorator.ts#L32) that also provide additional functionality. For example, the [@Config](https://github.com/travetto/travetto/tree/main/module/config/src/decorator.ts#L9) or the [@Controller](https://github.com/travetto/travetto/tree/main/module/rest/src/decorator/controller.ts#L9) decorator registers the associated class as an injectable element.
|
|
97
97
|
|
|
98
98
|
## Injection
|
|
99
99
|
|
|
100
|
-
Once all of your necessary dependencies are defined, now is the time to provide those [@Injectable](https://github.com/travetto/travetto/tree/main/module/di/src/decorator.ts#
|
|
100
|
+
Once all of your necessary dependencies are defined, now is the time to provide those [@Injectable](https://github.com/travetto/travetto/tree/main/module/di/src/decorator.ts#L32) instances to your code. There are three primary methods for injection:
|
|
101
101
|
|
|
102
|
-
The [@Inject](https://github.com/travetto/travetto/tree/main/module/di/src/decorator.ts#
|
|
102
|
+
The [@Inject](https://github.com/travetto/travetto/tree/main/module/di/src/decorator.ts#L32) decorator, which denotes a desire to inject a value directly. These will be set post construction.
|
|
103
103
|
|
|
104
|
-
**Code: Example Injectable with dependencies as [@Inject](https://github.com/travetto/travetto/tree/main/module/di/src/decorator.ts#
|
|
104
|
+
**Code: Example Injectable with dependencies as [@Inject](https://github.com/travetto/travetto/tree/main/module/di/src/decorator.ts#L32) fields**
|
|
105
105
|
```typescript
|
|
106
106
|
import { Injectable, Inject } from '@travetto/di';
|
|
107
107
|
import { DependentService } from './dep';
|
|
@@ -117,7 +117,7 @@ class CustomService {
|
|
|
117
117
|
}
|
|
118
118
|
```
|
|
119
119
|
|
|
120
|
-
The [@Injectable](https://github.com/travetto/travetto/tree/main/module/di/src/decorator.ts#
|
|
120
|
+
The [@Injectable](https://github.com/travetto/travetto/tree/main/module/di/src/decorator.ts#L32) constructor params, which will be provided as the instance is being constructed.
|
|
121
121
|
|
|
122
122
|
**Code: Example Injectable with dependencies in constructor**
|
|
123
123
|
```typescript
|
|
@@ -134,7 +134,7 @@ class CustomService {
|
|
|
134
134
|
}
|
|
135
135
|
```
|
|
136
136
|
|
|
137
|
-
Via [@InjectableFactory](https://github.com/travetto/travetto/tree/main/module/di/src/decorator.ts#
|
|
137
|
+
Via [@InjectableFactory](https://github.com/travetto/travetto/tree/main/module/di/src/decorator.ts#L75) params, which are comparable to constructor params
|
|
138
138
|
|
|
139
139
|
**Code: Example InjectableFactory with parameters as dependencies**
|
|
140
140
|
```typescript
|
|
@@ -154,7 +154,7 @@ class Config {
|
|
|
154
154
|
|
|
155
155
|
If you are building modules for others to consume, often times it is possible to end up with multiple implementations for the same class.
|
|
156
156
|
|
|
157
|
-
**Code: Example Multiple
|
|
157
|
+
**Code: Example Multiple Candidate Types**
|
|
158
158
|
```typescript
|
|
159
159
|
import { Injectable, Inject } from '@travetto/di';
|
|
160
160
|
|
|
@@ -178,7 +178,7 @@ class ContractConsumer {
|
|
|
178
178
|
|
|
179
179
|
By default, if there is only one candidate without qualification, then that candidate will be used. If multiple candidates are found, then the injection system will bail. To overcome this the end user will need to specify which candidate type should be considered `primary`:
|
|
180
180
|
|
|
181
|
-
**Code: Example Multiple
|
|
181
|
+
**Code: Example Multiple Candidate Types**
|
|
182
182
|
```typescript
|
|
183
183
|
import { InjectableFactory } from '@travetto/di';
|
|
184
184
|
import { Contract, ComplexContract } from './injectable-multiple-default';
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/di",
|
|
3
3
|
"displayName": "Dependency Injection",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.2.0",
|
|
5
5
|
"description": "Dependency registration/management and injection support.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"ast-transformations",
|
|
@@ -29,12 +29,12 @@
|
|
|
29
29
|
"directory": "module/di"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@travetto/transformer": "^2.
|
|
33
|
-
"@travetto/registry": "^2.
|
|
32
|
+
"@travetto/transformer": "^2.2.0",
|
|
33
|
+
"@travetto/registry": "^2.2.0"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
|
-
"@travetto/config": "^2.
|
|
37
|
-
"@travetto/schema": "^2.
|
|
36
|
+
"@travetto/config": "^2.2.0",
|
|
37
|
+
"@travetto/schema": "^2.2.0"
|
|
38
38
|
},
|
|
39
39
|
"docDependencies": {
|
|
40
40
|
"@travetto/model-mongo": true,
|
package/src/decorator.ts
CHANGED
|
@@ -4,7 +4,8 @@ import { MethodDescriptor } from '@travetto/base/src/internal/types';
|
|
|
4
4
|
import { InjectableFactoryConfig, InjectableConfig, Dependency } from './types';
|
|
5
5
|
import { DependencyRegistry, ResolutionType } from './registry';
|
|
6
6
|
|
|
7
|
-
function collapseConfig<T extends { qualifier?: symbol }>(...args: (symbol | Partial<InjectConfig> | undefined)[]) {
|
|
7
|
+
function collapseConfig<T extends { qualifier?: symbol }>(...args: (symbol | Partial<InjectConfig> | undefined)[]): T {
|
|
8
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
8
9
|
let out = {} as T;
|
|
9
10
|
if (args) {
|
|
10
11
|
if (Array.isArray(args)) {
|
|
@@ -16,6 +17,7 @@ function collapseConfig<T extends { qualifier?: symbol }>(...args: (symbol | Par
|
|
|
16
17
|
}
|
|
17
18
|
}
|
|
18
19
|
} else {
|
|
20
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
19
21
|
out = args as T;
|
|
20
22
|
}
|
|
21
23
|
}
|
|
@@ -28,10 +30,11 @@ function collapseConfig<T extends { qualifier?: symbol }>(...args: (symbol | Par
|
|
|
28
30
|
* @augments `@trv:di/Injectable`
|
|
29
31
|
*/
|
|
30
32
|
export function Injectable(first?: Partial<InjectableConfig> | symbol, ...args: (Partial<InjectableConfig> | undefined)[]) {
|
|
31
|
-
return <T extends Class>(target: T) => {
|
|
32
|
-
const config = collapseConfig(first, ...args)
|
|
33
|
+
return <T extends Class>(target: T): T => {
|
|
34
|
+
const config = collapseConfig<Partial<InjectableConfig>>(first, ...args);
|
|
33
35
|
|
|
34
36
|
config.class = target;
|
|
37
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
35
38
|
DependencyRegistry.registerClass(target, config as InjectableConfig);
|
|
36
39
|
return target;
|
|
37
40
|
};
|
|
@@ -40,7 +43,7 @@ export function Injectable(first?: Partial<InjectableConfig> | symbol, ...args:
|
|
|
40
43
|
export type InjectConfig = { qualifier?: symbol, optional?: boolean, resolution?: ResolutionType };
|
|
41
44
|
|
|
42
45
|
export function InjectArgs(configs?: InjectConfig[][]) {
|
|
43
|
-
return <T extends Class>(target: T) => {
|
|
46
|
+
return <T extends Class>(target: T): void => {
|
|
44
47
|
DependencyRegistry.registerConstructor(target,
|
|
45
48
|
configs?.map(x => collapseConfig(...x)));
|
|
46
49
|
};
|
|
@@ -52,14 +55,14 @@ export function InjectArgs(configs?: InjectConfig[][]) {
|
|
|
52
55
|
* @augments `@trv:di/Inject`
|
|
53
56
|
*/
|
|
54
57
|
export function Inject(first?: InjectConfig | symbol, ...args: (InjectConfig | undefined)[]) {
|
|
55
|
-
return (target: unknown, propertyKey: string | symbol, idx?: number | PropertyDescriptor) => {
|
|
58
|
+
return (target: unknown, propertyKey: string | symbol, idx?: number | PropertyDescriptor): void => {
|
|
56
59
|
if (typeof idx !== 'number') { // Only register if on property
|
|
57
60
|
const config: InjectConfig = collapseConfig(first, ...args);
|
|
58
61
|
|
|
59
62
|
DependencyRegistry.registerProperty(
|
|
60
|
-
|
|
61
|
-
propertyKey as string,
|
|
62
|
-
|
|
63
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
64
|
+
(target as ClassInstance).constructor, propertyKey as string, config as Dependency
|
|
65
|
+
);
|
|
63
66
|
}
|
|
64
67
|
};
|
|
65
68
|
}
|
|
@@ -70,7 +73,7 @@ export function Inject(first?: InjectConfig | symbol, ...args: (InjectConfig | u
|
|
|
70
73
|
* @augments `@trv:di/InjectableFactory`
|
|
71
74
|
*/
|
|
72
75
|
export function InjectableFactory(first?: Partial<InjectableFactoryConfig> | symbol, ...args: (Partial<InjectableFactoryConfig> | undefined)[]) {
|
|
73
|
-
return <T extends Class>(target: T, property: string | symbol, descriptor: MethodDescriptor) => {
|
|
76
|
+
return <T extends Class>(target: T, property: string | symbol, descriptor: MethodDescriptor): void => {
|
|
74
77
|
const config: InjectableFactoryConfig = collapseConfig(first, ...args);
|
|
75
78
|
DependencyRegistry.registerFactory({
|
|
76
79
|
...config,
|
package/src/error.ts
CHANGED
package/src/registry.ts
CHANGED
|
@@ -14,10 +14,12 @@ export type ResolutionType = 'strict' | 'loose' | 'any';
|
|
|
14
14
|
const PrimaryCandidateⲐ = Symbol.for('@trv:di/primary');
|
|
15
15
|
|
|
16
16
|
function hasPostConstruct(o: unknown): o is { postConstruct: () => Promise<unknown> } {
|
|
17
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
17
18
|
return !!o && !!(o as Record<string, unknown>)['postConstruct'];
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
function hasPreDestroy(o: unknown): o is { preDestroy: () => unknown } {
|
|
22
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
21
23
|
return !!o && !!(o as Record<string, unknown>)['preDestroy'];
|
|
22
24
|
}
|
|
23
25
|
|
|
@@ -67,7 +69,10 @@ class $DependencyRegistry extends MetadataRegistry<InjectableConfig> {
|
|
|
67
69
|
qualifier = filtered[0];
|
|
68
70
|
} else if (filtered.length > 1) {
|
|
69
71
|
// If dealing with sub types, prioritize exact matches
|
|
70
|
-
const exact = this
|
|
72
|
+
const exact = this
|
|
73
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
74
|
+
.getCandidateTypes(target as Class)
|
|
75
|
+
.filter(x => x.class === target);
|
|
71
76
|
if (exact.length === 1) {
|
|
72
77
|
qualifier = exact[0].qualifier;
|
|
73
78
|
} else {
|
|
@@ -94,6 +99,7 @@ class $DependencyRegistry extends MetadataRegistry<InjectableConfig> {
|
|
|
94
99
|
}
|
|
95
100
|
}
|
|
96
101
|
|
|
102
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
97
103
|
const config = this.get(cls!) as InjectableConfig<T>;
|
|
98
104
|
return {
|
|
99
105
|
qualifier,
|
|
@@ -105,7 +111,7 @@ class $DependencyRegistry extends MetadataRegistry<InjectableConfig> {
|
|
|
105
111
|
/**
|
|
106
112
|
* Retrieve all dependencies
|
|
107
113
|
*/
|
|
108
|
-
protected async fetchDependencies(managed: InjectableConfig, deps?: Dependency[]) {
|
|
114
|
+
protected async fetchDependencies(managed: InjectableConfig, deps?: Dependency[]): Promise<unknown[]> {
|
|
109
115
|
if (!deps || !deps.length) {
|
|
110
116
|
return [];
|
|
111
117
|
}
|
|
@@ -113,12 +119,15 @@ class $DependencyRegistry extends MetadataRegistry<InjectableConfig> {
|
|
|
113
119
|
const promises = deps.map(async x => {
|
|
114
120
|
try {
|
|
115
121
|
return await this.getInstance(x.target, x.qualifier, x.resolution);
|
|
116
|
-
} catch (
|
|
117
|
-
if (
|
|
122
|
+
} catch (err) {
|
|
123
|
+
if (!err || !(err instanceof Error)) {
|
|
124
|
+
throw err;
|
|
125
|
+
}
|
|
126
|
+
if (x.optional && err instanceof InjectionError && err.category === 'notfound') {
|
|
118
127
|
return undefined;
|
|
119
128
|
} else {
|
|
120
|
-
|
|
121
|
-
throw
|
|
129
|
+
err.message = `${err.message} via=${managed.class.ᚕid}`;
|
|
130
|
+
throw err;
|
|
122
131
|
}
|
|
123
132
|
}
|
|
124
133
|
});
|
|
@@ -129,14 +138,16 @@ class $DependencyRegistry extends MetadataRegistry<InjectableConfig> {
|
|
|
129
138
|
/**
|
|
130
139
|
* Resolve all field dependencies
|
|
131
140
|
*/
|
|
132
|
-
protected async resolveFieldDependencies<T>(config: InjectableConfig<T>, instance: T) {
|
|
141
|
+
protected async resolveFieldDependencies<T>(config: InjectableConfig<T>, instance: T): Promise<void> {
|
|
133
142
|
const keys = Object.keys(config.dependencies.fields ?? {})
|
|
143
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
134
144
|
.filter(k => instance[k as keyof T] === undefined); // Filter out already set ones
|
|
135
145
|
|
|
136
146
|
// And auto-wire
|
|
137
147
|
if (keys.length) {
|
|
138
148
|
const deps = await this.fetchDependencies(config, keys.map(x => config.dependencies.fields[x]));
|
|
139
149
|
for (let i = 0; i < keys.length; i++) {
|
|
150
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
140
151
|
instance[keys[i] as keyof T] = deps[i] as T[keyof T];
|
|
141
152
|
}
|
|
142
153
|
}
|
|
@@ -154,6 +165,7 @@ class $DependencyRegistry extends MetadataRegistry<InjectableConfig> {
|
|
|
154
165
|
// Create instance
|
|
155
166
|
const inst = managed.factory ?
|
|
156
167
|
managed.factory(...consValues) :
|
|
168
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
157
169
|
new (managed.class as ConcreteClass<T>)(...consValues);
|
|
158
170
|
|
|
159
171
|
// And auto-wire fields
|
|
@@ -161,6 +173,7 @@ class $DependencyRegistry extends MetadataRegistry<InjectableConfig> {
|
|
|
161
173
|
|
|
162
174
|
// If factory with field properties on the sub class
|
|
163
175
|
if (managed.factory) {
|
|
176
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
164
177
|
const resolved = this.get((inst as ClassInstance<T>).constructor);
|
|
165
178
|
|
|
166
179
|
if (resolved) {
|
|
@@ -179,7 +192,7 @@ class $DependencyRegistry extends MetadataRegistry<InjectableConfig> {
|
|
|
179
192
|
/**
|
|
180
193
|
* Create the instance
|
|
181
194
|
*/
|
|
182
|
-
protected async createInstance<T>(target: ClassTarget<T>, qualifier: symbol) {
|
|
195
|
+
protected async createInstance<T>(target: ClassTarget<T>, qualifier: symbol): Promise<T> {
|
|
183
196
|
const classId = this.resolveTarget(target, qualifier).id;
|
|
184
197
|
|
|
185
198
|
if (!this.instances.has(classId)) {
|
|
@@ -188,7 +201,8 @@ class $DependencyRegistry extends MetadataRegistry<InjectableConfig> {
|
|
|
188
201
|
}
|
|
189
202
|
|
|
190
203
|
if (this.instancePromises.get(classId)!.has(qualifier)) {
|
|
191
|
-
|
|
204
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
205
|
+
return this.instancePromises.get(classId)!.get(qualifier) as unknown as T;
|
|
192
206
|
}
|
|
193
207
|
|
|
194
208
|
const instancePromise = this.construct(target, qualifier);
|
|
@@ -197,17 +211,17 @@ class $DependencyRegistry extends MetadataRegistry<InjectableConfig> {
|
|
|
197
211
|
const instance = await instancePromise;
|
|
198
212
|
this.instances.get(classId)!.set(qualifier, instance);
|
|
199
213
|
return instance;
|
|
200
|
-
} catch (
|
|
214
|
+
} catch (err) {
|
|
201
215
|
// Clear it out, don't save failed constructions
|
|
202
216
|
this.instancePromises.get(classId)!.delete(qualifier);
|
|
203
|
-
throw
|
|
217
|
+
throw err;
|
|
204
218
|
}
|
|
205
219
|
}
|
|
206
220
|
|
|
207
221
|
/**
|
|
208
222
|
* Destroy an instance
|
|
209
223
|
*/
|
|
210
|
-
protected destroyInstance(cls: Class, qualifier: symbol) {
|
|
224
|
+
protected destroyInstance(cls: Class, qualifier: symbol): void {
|
|
211
225
|
const classId = cls.ᚕid;
|
|
212
226
|
|
|
213
227
|
const activeInstance = this.instances.get(classId)!.get(qualifier);
|
|
@@ -225,7 +239,7 @@ class $DependencyRegistry extends MetadataRegistry<InjectableConfig> {
|
|
|
225
239
|
/**
|
|
226
240
|
* Handle initial installation for the entire registry
|
|
227
241
|
*/
|
|
228
|
-
override initialInstall() {
|
|
242
|
+
override initialInstall(): Class[] {
|
|
229
243
|
const finalizing = this.pendingFinalize;
|
|
230
244
|
this.pendingFinalize = [];
|
|
231
245
|
|
|
@@ -233,13 +247,13 @@ class $DependencyRegistry extends MetadataRegistry<InjectableConfig> {
|
|
|
233
247
|
this.install(cls, { type: 'added', curr: cls });
|
|
234
248
|
}
|
|
235
249
|
|
|
236
|
-
return []
|
|
250
|
+
return [];
|
|
237
251
|
}
|
|
238
252
|
|
|
239
253
|
/**
|
|
240
254
|
* Register a cls as pending
|
|
241
255
|
*/
|
|
242
|
-
createPending(cls: Class) {
|
|
256
|
+
createPending(cls: Class): Partial<InjectableConfig> {
|
|
243
257
|
if (!this.resolved) {
|
|
244
258
|
this.pendingFinalize.push(cls);
|
|
245
259
|
}
|
|
@@ -265,23 +279,25 @@ class $DependencyRegistry extends MetadataRegistry<InjectableConfig> {
|
|
|
265
279
|
if (!this.instances.has(classId) || !this.instances.get(classId)!.has(qualifier)) {
|
|
266
280
|
await this.createInstance(target, qualifier); // Wait for proxy
|
|
267
281
|
}
|
|
282
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
268
283
|
return this.instances.get(classId)!.get(qualifier)! as T;
|
|
269
284
|
}
|
|
270
285
|
|
|
271
286
|
/**
|
|
272
287
|
* Get all available candidate types for the target
|
|
273
288
|
*/
|
|
274
|
-
getCandidateTypes<T>(target: Class<T>) {
|
|
289
|
+
getCandidateTypes<T>(target: Class<T>): InjectableConfig[] {
|
|
275
290
|
const targetId = target.ᚕid;
|
|
276
291
|
const qualifiers = this.targetToClass.get(targetId)!;
|
|
277
292
|
const uniqueQualifiers = qualifiers ? Array.from(new Set(qualifiers.values())) : [];
|
|
293
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
278
294
|
return uniqueQualifiers.map(id => this.get(id)! as InjectableConfig<T>);
|
|
279
295
|
}
|
|
280
296
|
|
|
281
297
|
/**
|
|
282
298
|
* Register a constructor with dependencies
|
|
283
299
|
*/
|
|
284
|
-
registerConstructor<T>(cls: Class<T>, dependencies?: Dependency[]) {
|
|
300
|
+
registerConstructor<T>(cls: Class<T>, dependencies?: Dependency[]): void {
|
|
285
301
|
const conf = this.getOrCreatePending(cls);
|
|
286
302
|
conf.dependencies!.cons = dependencies;
|
|
287
303
|
}
|
|
@@ -289,7 +305,7 @@ class $DependencyRegistry extends MetadataRegistry<InjectableConfig> {
|
|
|
289
305
|
/**
|
|
290
306
|
* Register a property as a dependency
|
|
291
307
|
*/
|
|
292
|
-
registerProperty<T>(cls: Class<T>, field: string, dependency: Dependency) {
|
|
308
|
+
registerProperty<T>(cls: Class<T>, field: string, dependency: Dependency): void {
|
|
293
309
|
const conf = this.getOrCreatePending(cls);
|
|
294
310
|
conf.dependencies!.fields[field] = dependency;
|
|
295
311
|
}
|
|
@@ -297,27 +313,28 @@ class $DependencyRegistry extends MetadataRegistry<InjectableConfig> {
|
|
|
297
313
|
/**
|
|
298
314
|
* Register a class
|
|
299
315
|
*/
|
|
300
|
-
registerClass<T>(cls: Class<T>,
|
|
301
|
-
const config = this.getOrCreatePending(
|
|
316
|
+
registerClass<T>(cls: Class<T>, pConfig: Partial<InjectableConfig<T>> = {}): void {
|
|
317
|
+
const config = this.getOrCreatePending(pConfig.class ?? cls);
|
|
302
318
|
|
|
303
319
|
config.class = cls;
|
|
304
|
-
config.qualifier =
|
|
305
|
-
if (
|
|
306
|
-
config.interfaces?.push(...
|
|
320
|
+
config.qualifier = pConfig.qualifier ?? config.qualifier ?? Symbol.for(cls.ᚕid);
|
|
321
|
+
if (pConfig.interfaces) {
|
|
322
|
+
config.interfaces?.push(...pConfig.interfaces);
|
|
307
323
|
}
|
|
308
|
-
if (
|
|
309
|
-
config.primary =
|
|
324
|
+
if (pConfig.primary !== undefined) {
|
|
325
|
+
config.primary = pConfig.primary;
|
|
310
326
|
}
|
|
311
|
-
if (
|
|
312
|
-
config.factory =
|
|
327
|
+
if (pConfig.factory) {
|
|
328
|
+
config.factory = pConfig.factory ?? config.factory;
|
|
313
329
|
}
|
|
314
|
-
if (
|
|
315
|
-
config.target =
|
|
330
|
+
if (pConfig.target) {
|
|
331
|
+
config.target = pConfig.target;
|
|
316
332
|
}
|
|
317
|
-
if (
|
|
333
|
+
if (pConfig.dependencies) {
|
|
318
334
|
config.dependencies = {
|
|
319
335
|
fields: {},
|
|
320
|
-
|
|
336
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
337
|
+
...pConfig.dependencies as Omit<InjectableConfig['dependencies'], 'fields'>
|
|
321
338
|
};
|
|
322
339
|
}
|
|
323
340
|
}
|
|
@@ -329,7 +346,7 @@ class $DependencyRegistry extends MetadataRegistry<InjectableConfig> {
|
|
|
329
346
|
id: string;
|
|
330
347
|
qualifier?: undefined | symbol;
|
|
331
348
|
fn: (...args: unknown[]) => unknown;
|
|
332
|
-
}) {
|
|
349
|
+
}): void {
|
|
333
350
|
const finalConfig: Partial<InjectableConfig> = {};
|
|
334
351
|
|
|
335
352
|
finalConfig.factory = config.fn;
|
|
@@ -349,6 +366,7 @@ class $DependencyRegistry extends MetadataRegistry<InjectableConfig> {
|
|
|
349
366
|
}
|
|
350
367
|
|
|
351
368
|
// Create mock cls for DI purposes
|
|
369
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
352
370
|
const cls = { ᚕid: config.id } as Class;
|
|
353
371
|
|
|
354
372
|
finalConfig.class = cls;
|
|
@@ -359,13 +377,14 @@ class $DependencyRegistry extends MetadataRegistry<InjectableConfig> {
|
|
|
359
377
|
this.factories.set(config.src.ᚕid, new Map());
|
|
360
378
|
}
|
|
361
379
|
|
|
380
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
362
381
|
this.factories.get(config.src.ᚕid)!.set(cls, finalConfig as InjectableConfig);
|
|
363
382
|
}
|
|
364
383
|
|
|
365
384
|
/**
|
|
366
385
|
* On Install event
|
|
367
386
|
*/
|
|
368
|
-
override onInstall<T>(cls: Class<T>, e: ChangeEvent<Class<T>>) {
|
|
387
|
+
override onInstall<T>(cls: Class<T>, e: ChangeEvent<Class<T>>): void {
|
|
369
388
|
super.onInstall(cls, e);
|
|
370
389
|
|
|
371
390
|
// Install factories separate from classes
|
|
@@ -379,9 +398,10 @@ class $DependencyRegistry extends MetadataRegistry<InjectableConfig> {
|
|
|
379
398
|
/**
|
|
380
399
|
* Handle installing a class
|
|
381
400
|
*/
|
|
382
|
-
onInstallFinalize<T>(cls: Class<T>) {
|
|
401
|
+
onInstallFinalize<T>(cls: Class<T>): InjectableConfig<T> {
|
|
383
402
|
const classId = cls.ᚕid;
|
|
384
403
|
|
|
404
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
385
405
|
const config = this.getOrCreatePending(cls) as InjectableConfig<T>;
|
|
386
406
|
|
|
387
407
|
// Allow for the factory to fulfill the target
|
|
@@ -475,11 +495,11 @@ class $DependencyRegistry extends MetadataRegistry<InjectableConfig> {
|
|
|
475
495
|
|
|
476
496
|
// Register primary if only one interface provided and no parent config
|
|
477
497
|
if (config.interfaces.length === 1 && !parentConfig) {
|
|
478
|
-
const [
|
|
479
|
-
if (!this.targetToClass.has(
|
|
480
|
-
this.targetToClass.set(
|
|
498
|
+
const [primaryInterface] = config.interfaces;
|
|
499
|
+
if (!this.targetToClass.has(primaryInterface.ᚕid)) {
|
|
500
|
+
this.targetToClass.set(primaryInterface.ᚕid, new Map());
|
|
481
501
|
}
|
|
482
|
-
this.targetToClass.get(
|
|
502
|
+
this.targetToClass.get(primaryInterface.ᚕid)!.set(PrimaryCandidateⲐ, classId);
|
|
483
503
|
}
|
|
484
504
|
}
|
|
485
505
|
|
|
@@ -489,7 +509,7 @@ class $DependencyRegistry extends MetadataRegistry<InjectableConfig> {
|
|
|
489
509
|
/**
|
|
490
510
|
* Handle uninstalling a class
|
|
491
511
|
*/
|
|
492
|
-
override onUninstallFinalize(cls: Class) {
|
|
512
|
+
override onUninstallFinalize(cls: Class): void {
|
|
493
513
|
const classId = cls.ᚕid;
|
|
494
514
|
|
|
495
515
|
if (!this.classToTarget.has(cls.ᚕid)) {
|
|
@@ -506,7 +526,7 @@ class $DependencyRegistry extends MetadataRegistry<InjectableConfig> {
|
|
|
506
526
|
/**
|
|
507
527
|
* Reset registry
|
|
508
528
|
*/
|
|
509
|
-
override onReset() {
|
|
529
|
+
override onReset(): void {
|
|
510
530
|
super.onReset();
|
|
511
531
|
this.pendingFinalize = [];
|
|
512
532
|
this.instances.clear();
|
|
@@ -519,7 +539,8 @@ class $DependencyRegistry extends MetadataRegistry<InjectableConfig> {
|
|
|
519
539
|
/**
|
|
520
540
|
* Inject fields into instance
|
|
521
541
|
*/
|
|
522
|
-
|
|
542
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
543
|
+
async injectFields<T extends { constructor: Class<T> }>(o: T, cls = o.constructor as Class<T>): Promise<void> {
|
|
523
544
|
this.verifyInitialized();
|
|
524
545
|
// Compute fields to be auto-wired
|
|
525
546
|
return await this.resolveFieldDependencies(this.get(cls), o);
|
|
@@ -2,12 +2,12 @@ import { RetargettingProxy } from '@travetto/base/src/internal/proxy';
|
|
|
2
2
|
import { Class, ClassInstance } from '@travetto/base';
|
|
3
3
|
|
|
4
4
|
import type { DependencyRegistry } from '../src/registry';
|
|
5
|
-
import type { ClassTarget } from '../src/types';
|
|
5
|
+
import type { ClassTarget, InjectableConfig } from '../src/types';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Wraps the Dependency Registry to support proxying instances
|
|
9
9
|
*/
|
|
10
|
-
export function init($DependencyRegistry: Class<typeof DependencyRegistry>) {
|
|
10
|
+
export function init($DependencyRegistry: Class<typeof DependencyRegistry>): typeof $DependencyRegistry {
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Extending the $DependencyRegistry class to add some functionality for watching
|
|
@@ -20,35 +20,37 @@ export function init($DependencyRegistry: Class<typeof DependencyRegistry>) {
|
|
|
20
20
|
*/
|
|
21
21
|
protected proxyInstance<T>(target: ClassTarget<T>, qual: symbol | undefined, instance: T): T {
|
|
22
22
|
const { qualifier, id: classId } = this.resolveTarget(target, qual);
|
|
23
|
-
let proxy: RetargettingProxy<
|
|
23
|
+
let proxy: RetargettingProxy<T>;
|
|
24
24
|
|
|
25
25
|
if (!this.#proxies.has(classId)) {
|
|
26
26
|
this.#proxies.set(classId, new Map());
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
if (!this.#proxies.get(classId)!.has(qualifier)) {
|
|
30
|
-
proxy = new RetargettingProxy<
|
|
30
|
+
proxy = new RetargettingProxy<T>(instance);
|
|
31
31
|
this.#proxies.get(classId)!.set(qualifier, proxy);
|
|
32
32
|
console.debug('Registering proxy', { id: target.ᚕid, qualifier: qualifier.toString() });
|
|
33
33
|
} else {
|
|
34
|
-
|
|
34
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
35
|
+
proxy = this.#proxies.get(classId)!.get(qualifier) as RetargettingProxy<T>;
|
|
35
36
|
proxy.setTarget(instance);
|
|
36
37
|
console.debug('Updating target', {
|
|
38
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
37
39
|
id: target.ᚕid, qualifier: qualifier.toString(), instanceType: (instance as unknown as ClassInstance<T>).constructor.name as string
|
|
38
40
|
});
|
|
39
41
|
}
|
|
40
42
|
|
|
41
|
-
return proxy.get()
|
|
43
|
+
return proxy.get();
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
/**
|
|
45
47
|
* Create instance and wrap in a proxy
|
|
46
48
|
*/
|
|
47
|
-
protected async createInstance<T>(target: ClassTarget<T>, qualifier: symbol) {
|
|
49
|
+
protected async createInstance<T>(target: ClassTarget<T>, qualifier: symbol): Promise<T> {
|
|
48
50
|
const instance = await super.createInstance(target, qualifier);
|
|
49
51
|
const classId = this.resolveTarget(target, qualifier).id;
|
|
50
52
|
// Reset as proxy instance
|
|
51
|
-
const proxied = this.proxyInstance(target, qualifier, instance);
|
|
53
|
+
const proxied = this.proxyInstance<T>(target, qualifier, instance);
|
|
52
54
|
this.instances.get(classId)!.set(qualifier, proxied);
|
|
53
55
|
return proxied;
|
|
54
56
|
}
|
|
@@ -56,7 +58,7 @@ export function init($DependencyRegistry: Class<typeof DependencyRegistry>) {
|
|
|
56
58
|
/**
|
|
57
59
|
* Reload proxy if in watch mode
|
|
58
60
|
*/
|
|
59
|
-
onInstallFinalize<T>(cls: Class<T>) {
|
|
61
|
+
onInstallFinalize<T>(cls: Class<T>): InjectableConfig<T> {
|
|
60
62
|
const config = super.onInstallFinalize(cls);
|
|
61
63
|
// If already loaded, reload
|
|
62
64
|
const classId = cls.ᚕid;
|
|
@@ -74,7 +76,7 @@ export function init($DependencyRegistry: Class<typeof DependencyRegistry>) {
|
|
|
74
76
|
return config;
|
|
75
77
|
}
|
|
76
78
|
|
|
77
|
-
destroyInstance(cls: Class, qualifier: symbol) {
|
|
79
|
+
destroyInstance(cls: Class, qualifier: symbol): void {
|
|
78
80
|
const classId = cls.ᚕid;
|
|
79
81
|
const proxy = this.#proxies.get(classId)!.get(qualifier);
|
|
80
82
|
super.destroyInstance(cls, qualifier);
|
|
@@ -83,7 +85,7 @@ export function init($DependencyRegistry: Class<typeof DependencyRegistry>) {
|
|
|
83
85
|
}
|
|
84
86
|
}
|
|
85
87
|
|
|
86
|
-
onReset() {
|
|
88
|
+
onReset(): void {
|
|
87
89
|
super.onReset();
|
|
88
90
|
this.#proxies.clear();
|
|
89
91
|
}
|
package/support/invoke.ts
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import { EnvInit } from '@travetto/base/bin/init';
|
|
2
2
|
|
|
3
|
-
export async function invoke(...[mod, cls, method, qualifier]: (string | undefined)[]) {
|
|
3
|
+
export async function invoke(...[mod, cls, method, qualifier]: (string | undefined)[]): Promise<unknown> {
|
|
4
4
|
EnvInit.init();
|
|
5
5
|
await (await import('@travetto/base')).PhaseManager.run('init');
|
|
6
6
|
const inst = await (await import('../src/registry')).DependencyRegistry
|
|
7
|
-
.getInstance(
|
|
7
|
+
.getInstance(
|
|
8
|
+
(await import(mod!))[cls!],
|
|
9
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
10
|
+
qualifier ? Symbol.for(qualifier) : qualifier as undefined
|
|
11
|
+
);
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
8
13
|
return await (inst as Record<string, () => Promise<unknown>>)[method!]();
|
|
9
14
|
}
|
|
10
15
|
|
|
@@ -16,13 +16,14 @@ export class InjectableTransformer {
|
|
|
16
16
|
/**
|
|
17
17
|
* Handle a specific declaration param/property
|
|
18
18
|
*/
|
|
19
|
-
static processDeclaration(state: TransformerState, param: ts.ParameterDeclaration | ts.SetAccessorDeclaration | ts.PropertyDeclaration) {
|
|
19
|
+
static processDeclaration(state: TransformerState, param: ts.ParameterDeclaration | ts.SetAccessorDeclaration | ts.PropertyDeclaration): ts.Expression[] {
|
|
20
20
|
const existing = state.findDecorator(this, param, 'Inject', INJECTABLE_MOD);
|
|
21
21
|
|
|
22
22
|
if (!(existing || ts.isParameter(param))) {
|
|
23
23
|
return [];
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
26
27
|
const callExpr = existing?.expression as ts.CallExpression;
|
|
27
28
|
const args: ts.Expression[] = [...(callExpr?.arguments ?? [])];
|
|
28
29
|
|
|
@@ -43,17 +44,17 @@ export class InjectableTransformer {
|
|
|
43
44
|
* Mark class as Injectable
|
|
44
45
|
*/
|
|
45
46
|
@OnClass('Injectable')
|
|
46
|
-
static registerInjectable(state: TransformerState, node: ts.ClassDeclaration) {
|
|
47
|
-
const cons = node.members.find(x => ts.isConstructorDeclaration(x))
|
|
47
|
+
static registerInjectable(state: TransformerState, node: ts.ClassDeclaration): typeof node {
|
|
48
|
+
const cons = node.members.find((x): x is ts.ConstructorDeclaration => ts.isConstructorDeclaration(x));
|
|
48
49
|
const injectArgs = cons &&
|
|
49
50
|
state.fromLiteral(cons.parameters.map(x => this.processDeclaration(state, x)));
|
|
50
51
|
|
|
51
52
|
// Extract all interfaces
|
|
52
53
|
const interfaces: ts.Node[] = [];
|
|
53
|
-
for (const
|
|
54
|
-
if (
|
|
55
|
-
for (const
|
|
56
|
-
const resolvedType = state.resolveType(
|
|
54
|
+
for (const clause of node.heritageClauses ?? []) {
|
|
55
|
+
if (clause.token === ts.SyntaxKind.ImplementsKeyword) {
|
|
56
|
+
for (const typeExpression of clause.types) {
|
|
57
|
+
const resolvedType = state.resolveType(typeExpression);
|
|
57
58
|
if (resolvedType.key === 'external') {
|
|
58
59
|
const resolved = state.getOrImport(resolvedType);
|
|
59
60
|
interfaces.push(resolved);
|
|
@@ -85,7 +86,7 @@ export class InjectableTransformer {
|
|
|
85
86
|
* Handle Inject annotations for fields/args
|
|
86
87
|
*/
|
|
87
88
|
@OnProperty('Inject')
|
|
88
|
-
static registerInjectProperty(state: TransformerState, node: ts.PropertyDeclaration, dm?: DecoratorMeta) {
|
|
89
|
+
static registerInjectProperty(state: TransformerState, node: ts.PropertyDeclaration, dm?: DecoratorMeta): typeof node {
|
|
89
90
|
const decl = state.findDecorator(this, node, 'Inject', INJECTABLE_MOD);
|
|
90
91
|
|
|
91
92
|
// Doing decls
|
|
@@ -106,7 +107,7 @@ export class InjectableTransformer {
|
|
|
106
107
|
* Handle Inject annotations for fields/args
|
|
107
108
|
*/
|
|
108
109
|
@OnSetter('Inject')
|
|
109
|
-
static registerInjectSetter(state: TransformerState, node: ts.SetAccessorDeclaration, dm?: DecoratorMeta) {
|
|
110
|
+
static registerInjectSetter(state: TransformerState, node: ts.SetAccessorDeclaration, dm?: DecoratorMeta): typeof node {
|
|
110
111
|
const decl = state.findDecorator(this, node, 'Inject', INJECTABLE_MOD);
|
|
111
112
|
|
|
112
113
|
// Doing decls
|
|
@@ -126,7 +127,7 @@ export class InjectableTransformer {
|
|
|
126
127
|
* Handle InjectableFactory creation
|
|
127
128
|
*/
|
|
128
129
|
@OnStaticMethod('InjectableFactory')
|
|
129
|
-
static registerFactory(state: TransformerState, node: ts.MethodDeclaration, dm?: DecoratorMeta) {
|
|
130
|
+
static registerFactory(state: TransformerState, node: ts.MethodDeclaration, dm?: DecoratorMeta): typeof node {
|
|
130
131
|
if (!dm?.dec) {
|
|
131
132
|
return node;
|
|
132
133
|
}
|
|
@@ -148,6 +149,7 @@ export class InjectableTransformer {
|
|
|
148
149
|
|
|
149
150
|
args.unshift(state.extendObjectLiteral({
|
|
150
151
|
dependencies,
|
|
152
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
151
153
|
src: (node.parent as ts.ClassDeclaration).name,
|
|
152
154
|
target
|
|
153
155
|
}));
|